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 @@ + + + + + + + + + + + +