diff --git a/context.js b/context.js
index e2d4a5e..05d0b54 100644
--- a/context.js
+++ b/context.js
@@ -11,14 +11,18 @@ export function bindContextMenu(node) {
const type = node.asn1.typeName();
const valueEnabled = type != 'SET' && type != 'SEQUENCE';
node.onclick = function (event) {
- contextMenu.style.left = event.pageX + 'px';
- contextMenu.style.top = event.pageY + 'px';
- contextMenu.style.visibility = 'visible';
- contextMenu.node = this;
- btnHideTree.innerText = (node.className == 'node') ? 'Hide subtree' : 'Show subtree';
- btnHideTree.style.display = node.className.startsWith('node') ? 'block' : 'none';
- btnCopyValue.style.display = valueEnabled ? 'block' : 'none';
- event.stopPropagation();
+ // do not show the menu in case of clicking the icon
+ if (event.srcElement.nodeName === 'SPAN') {
+ contextMenu.style.left = event.pageX + 'px';
+ contextMenu.style.top = event.pageY + 'px';
+ contextMenu.style.visibility = 'visible';
+ contextMenu.node = this;
+ btnHideTree.innerText = (node.className == 'node') ? 'Hide subtree' : 'Show subtree';
+ btnHideTree.style.display = node.className.startsWith('node') ? 'block' : 'none';
+ btnCopyValue.style.display = valueEnabled ? 'block' : 'none';
+ event.preventDefault();
+ event.stopPropagation();
+ }
};
}
diff --git a/dom.js b/dom.js
index 8ae7c1e..bd89805 100644
--- a/dom.js
+++ b/dom.js
@@ -53,13 +53,17 @@ const
export class ASN1DOM extends ASN1 {
+ buf2hex(buffer) {
+ return [...new Uint8Array(buffer)].map((x) => x.toString(16).padStart(2, '0')).join(' ');
+ }
+
toDOM(spaces) {
spaces = spaces || '';
let isOID = (typeof oids === 'object') && (this.tag.isUniversal() && (this.tag.tagNumber == 0x06) || (this.tag.tagNumber == 0x0D));
- let node = DOM.tag('div', 'node');
- node.asn1 = this;
+ let node;
+ node = document.createElement('li');
+ node.asn1 = this;
let head = DOM.tag('span', 'head');
- head.appendChild(DOM.tag('span', 'spaces', spaces));
const typeName = this.typeName().replace(/_/g, ' ');
if (this.def) {
if (this.def.id) {
@@ -75,6 +79,8 @@ export class ASN1DOM extends ASN1 {
head.appendChild(DOM.space());
}
}
+ head.setAttribute('pos', this.posStart());
+ head.setAttribute('end', this.posEnd());
head.appendChild(DOM.text(typeName));
let content;
try {
@@ -111,8 +117,28 @@ export class ASN1DOM extends ASN1 {
content = content.replace(/');
}
- node.appendChild(head);
- this.node = node;
+ // add the li and details section for this node
+ let contentNode;
+ let childNode;
+ if (this.sub !== null) {
+ let details = document.createElement('details');
+ details.setAttribute('open', '');
+ node.appendChild(details);
+ let summary = document.createElement('summary');
+ summary.setAttribute('class', 'node');
+ details.appendChild(summary);
+ summary.appendChild(head);
+ // summary.setAttribute('class', 'node');
+ contentNode = summary;
+ childNode = details;
+ }
+ else {
+ contentNode = node;
+ contentNode.setAttribute('class', 'node');
+ contentNode.appendChild(head);
+ }
+
+ this.node = contentNode;
this.head = head;
let value = DOM.tag('div', 'value');
let s = 'Offset: ' + this.stream.pos + '
';
@@ -135,14 +161,16 @@ export class ASN1DOM extends ASN1 {
}
}
value.innerHTML = s;
- node.appendChild(value);
+ contentNode.appendChild(value);
let sub = DOM.tag('div', 'sub');
if (this.sub !== null) {
+ let ul = document.createElement('ul');
+ childNode.appendChild(ul);
+
spaces += '\xA0 ';
for (let i = 0, max = this.sub.length; i < max; ++i)
- sub.appendChild(this.sub[i].toDOM(spaces));
+ ul.appendChild(this.sub[i].toDOM(spaces));
}
- node.appendChild(sub);
bindContextMenu(node);
return node;
}
@@ -169,13 +197,14 @@ export class ASN1DOM extends ASN1 {
this.head.onmouseover = function () { this.hexNode.className = 'hexCurrent'; };
this.head.onmouseout = function () { this.hexNode.className = 'hex'; };
node.asn1 = this;
- node.onmouseover = function () {
+ node.onmouseover = function (event) {
let current = !root.selected;
if (current) {
root.selected = this.asn1;
this.className = 'hexCurrent';
}
this.asn1.fakeHover(current);
+ event.stopPropagation();
};
node.onmouseout = function () {
let current = (root.selected == this.asn1);
@@ -199,6 +228,9 @@ export class ASN1DOM extends ASN1 {
node.appendChild(skip);
}
}
+ // set the current start and end position as an attribute at the node to know the selected area
+ node.setAttribute('pos', this.posStart());
+ node.setAttribute('end', this.posEnd());
this.toHexDOM_sub(node, 'tag', this.stream, this.posStart(), this.posLen());
this.toHexDOM_sub(node, (this.length >= 0) ? 'dlen' : 'ulen', this.stream, this.posLen(), this.posContent());
if (this.sub === null) {
diff --git a/index.css b/index.css
index ad495a1..d418189 100644
--- a/index.css
+++ b/index.css
@@ -29,6 +29,7 @@ html[data-theme="light"] {
--dump-hex-current-hex: #A0A0A0;
--dump-hex-current-dlen: #004040;
--hover-bg-color: #E0E0E0;
+ --zoom-fix: -1px;
}
html[data-theme="dark"] {
--main-bg-color: #0d1116;
@@ -126,7 +127,7 @@ header {
#main-page {
background-color: var(--main-bg-color);
border: 0px;
- padding: 15px;
+ padding: 5px;
}
#main-page > div {
position: relative;
@@ -158,13 +159,6 @@ header {
/*display: block;*/
visibility: visible;
}
-.node {
- position: relative;
-}
-.sub {
- padding-left: 1.5em;
- border-left: solid 1px var(--sub-border-color);
-}
.head {
height: 1em;
white-space: nowrap;
@@ -197,7 +191,7 @@ header {
position: absolute;
z-index: 2;
top: 1.2em;
- left: 0;
+ left: 30px;
background-color: #efefef; /*minimal support for IE11*/
background-color: var(--button-bg-color);
border: solid 1px var(--button-border-color);
@@ -267,3 +261,77 @@ header {
#contextmenu > button:hover {
background-color: var(--button-bghover-color);
}
+.treecollapse {
+ --spacing: 1.5rem;
+ --radius: 7px;
+ padding-inline-start: 0px;
+}
+
+.treecollapse li{
+ display: block;
+ position: relative;
+ padding-left: calc(2 * var(--spacing) - var(--radius) - 2px);
+}
+
+.treecollapse ul{
+ padding-left: 0;
+ margin-left: calc(var(--radius) - var(--spacing));
+}
+
+.treecollapse ul li{
+ border-left: 1px solid #999;
+}
+
+.treecollapse ul li:last-child{
+ border-color: transparent;
+}
+
+.treecollapse ul li::before{
+ content: '';
+ display: block;
+ position: absolute;
+ top: calc(var(--spacing) / -1.6);
+ left: var(--zoom-fix);
+ width: calc(var(--spacing) + 2px);
+ height: calc(var(--spacing) + 1px);
+ border: solid #999;
+ border-width: 0 0 1px 1px;
+}
+
+.treecollapse summary{
+ display : block;
+ cursor : pointer;
+}
+
+.treecollapse summary::marker,
+.treecollapse summary::-webkit-details-marker{
+ display : none;
+}
+
+.treecollapse summary:focus{
+ outline : none;
+}
+
+.treecollapse summary:focus-visible{
+ outline : 1px dotted #000;
+}
+
+.treecollapse summary::before{
+ content: '';
+ display: block;
+ position: absolute;
+ top: calc(var(--spacing) / 2 - var(--radius));
+ left: calc(var(--spacing) - var(--radius) - 1px);
+ width: calc(2 * var(--radius));
+ height: calc(2 * var(--radius));
+}
+
+.treecollapse summary::before{
+ z-index: 1;
+ top: 1px;
+ background: url('tree-icon-light.svg');
+}
+
+.treecollapse details[open] > summary::before{
+ background-position : calc(-2 * var(--radius)) 0;
+}
\ No newline at end of file
diff --git a/index.js b/index.js
index a0a2a4e..3e4385f 100644
--- a/index.js
+++ b/index.js
@@ -41,7 +41,10 @@ function checkbox(name) {
function show(asn1) {
tree.innerHTML = '';
dump.innerHTML = '';
- tree.appendChild(asn1.toDOM());
+ let ul = document.createElement('ul');
+ ul.setAttribute('class', 'treecollapse');
+ tree.appendChild(ul);
+ ul.appendChild(asn1.toDOM());
if (wantHex.checked) dump.appendChild(asn1.toHexDOM(undefined, trimHex.checked));
}
export function decode(der, offset) {
@@ -211,3 +214,19 @@ selectTag.onchange = function (ev) {
let tag = ev.target.selectedOptions[0].value;
window.location.href = 'https://rawcdn.githack.com/lapo-luchini/asn1js/' + tag + '/index.html';
};
+
+// zoom fix to have straight lines in treeview
+if (window.devicePixelRatio >= 2 ) {
+ let treecollapse = document.querySelector(':root');
+ treecollapse.style.setProperty('--zoom-fix', '-0.8px');
+} else if (window.devicePixelRatio >= 1.5 ) {
+ let treecollapse = document.querySelector(':root');
+ treecollapse.style.setProperty('--zoom-fix', '-0.5px');
+} else if (window.devicePixelRatio <= 0.81 ) {
+ let treecollapse = document.querySelector(':root');
+ treecollapse.style.setProperty('--zoom-fix', '-1.4px');
+} else if (window.devicePixelRatio <= 0.9 ) {
+ let treecollapse = document.querySelector(':root');
+ treecollapse.style.setProperty('--zoom-fix', '-1.5px');
+}
+console.log(window.devicePixelRatio);
\ No newline at end of file
diff --git a/tree-icon-dark.svg b/tree-icon-dark.svg
new file mode 100644
index 0000000..93c7fb3
--- /dev/null
+++ b/tree-icon-dark.svg
@@ -0,0 +1,67 @@
+
+
diff --git a/tree-icon-light.svg b/tree-icon-light.svg
new file mode 100644
index 0000000..fc1ddd6
--- /dev/null
+++ b/tree-icon-light.svg
@@ -0,0 +1,64 @@
+
+