diff --git a/media/MetadataViewer/index.html b/media/MetadataViewer/index.html new file mode 100644 index 00000000..342d0fae --- /dev/null +++ b/media/MetadataViewer/index.html @@ -0,0 +1,34 @@ + + + + + + + + Document + + + + + + + + diff --git a/media/MetadataViewer/index.js b/media/MetadataViewer/index.js new file mode 100644 index 00000000..9046b49c --- /dev/null +++ b/media/MetadataViewer/index.js @@ -0,0 +1,285 @@ +/* + * Copyright (c) 2022 Samsung Electronics Co., Ltd. All Rights Reserved + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +let currentConfigType = null; + +this.window.addEventListener('message', (event) => { + const message = event.data; + switch (message.command) { + case 'showMetadata': + updateMetadataInfo(message.metadata); + break; + default: + break; + } +}); + +// Update metadata information +function updateMetadataInfo(metadata) { + const mainViewItemBox = document.createElement('div'); + mainViewItemBox.classList.add('main-view-item-box'); + + const viewItemBox = document.createElement('div'); + viewItemBox.classList.add('view-item-box'); + viewItemBox.setAttribute('id', 'common-view-item-box'); + + const viewItemHeaderBox = document.createElement('div'); + viewItemHeaderBox.classList.add('view-item-header-box'); + + const viewItemHeader = document.createElement('div'); + viewItemHeader.classList.add('view-item-header'); + viewItemHeader.innerText = 'Common'; + + const viewItemShowButton = document.createElement('div'); + viewItemShowButton.setAttribute('id', 'common-view-item-show-button'); + viewItemShowButton.classList.add('view-item-show-button', 'codicon-collapse-all', 'codicon'); + + const viewItemContentBox = document.createElement('div'); + viewItemContentBox.setAttribute('id', 'common-view-content-box'); + viewItemContentBox.classList.add('view-item-content-box'); + + document.body.appendChild(mainViewItemBox); + mainViewItemBox.appendChild(viewItemBox); + viewItemBox.append(viewItemHeaderBox, viewItemContentBox); + viewItemHeaderBox.append(viewItemHeader, viewItemShowButton); + + // Pull out the main key.(File Name) + const mainFileName = Object.keys(metadata)[0]; + + // Store metadata information in variable + const metadataInfo = metadata[mainFileName]; + + for (const subKey in metadataInfo) { + if (subKey === 'operations') { + const viewItemBox = document.createElement('div'); + viewItemBox.setAttribute('id', 'operations-view-item-box'); + viewItemBox.classList.add('view-item-box'); + + // draw header + const viewItemHeader = document.createElement('div'); + viewItemHeader.classList.add('view-item-header'); + viewItemHeader.innerText = 'Operations'; + + // add show button image + const showButton = document.createElement('div'); + showButton.classList.add('view-item-show-button', 'codicon-collapse-all', 'codicon'); + showButton.setAttribute('id', 'operations-view-item-show-button'); + + // draw a header box to hold headers and showButton + const viewItemHeaderBox = document.createElement('div'); + viewItemHeaderBox.classList.add('view-item-header-box'); + + viewItemHeaderBox.append(viewItemHeader, showButton); + viewItemBox.appendChild(viewItemHeaderBox); + mainViewItemBox.appendChild(viewItemBox); + + // draw content box + const viewItemContentBox = document.createElement('div'); + viewItemContentBox.classList.add('view-item-content-box'); + viewItemContentBox.setAttribute('id', 'operations-view-content-box'); + viewItemBox.appendChild(viewItemContentBox); + + + for (const operationsKey in metadataInfo[subKey]) { + metadataDivCreate(operationsKey, metadataInfo[subKey][operationsKey], 'operations'); + } + } else if (subKey === 'cfg-settings') { + const viewItemBox = document.createElement('div'); + viewItemBox.setAttribute('id', 'cfg-settings-view-item-box'); + viewItemBox.classList.add('view-item-box'); + + const viewItemHeaderBox = document.createElement('div'); + viewItemHeaderBox.classList.add('view-item-header-box'); + + const viewItemHeader = document.createElement('div'); + viewItemHeader.classList.add('view-item-header'); + viewItemHeader.innerText = 'Config'; + + const showButton = document.createElement('div'); + showButton.setAttribute('id', 'cfg-settings-view-item-show-button'); + showButton.classList.add('view-item-show-button', 'codicon-collapse-all', 'codicon'); + // showButton.innerText = '-'; + + viewItemHeaderBox.append(viewItemHeader, showButton); + viewItemBox.appendChild(viewItemHeaderBox); + mainViewItemBox.appendChild(viewItemBox); + + const viewItemContentBox = document.createElement('div'); + viewItemContentBox.classList.add('view-item-content-box'); + viewItemContentBox.setAttribute('id', 'cfg-settings-view-content-box'); + viewItemBox.appendChild(viewItemContentBox); + + const oneccInfo = metadataInfo[subKey]; + + // Store values that onecc`s value is true + const oneccInfoList = []; + + for (const configKey in oneccInfo) { + const viewItemSubHeaderBox = document.createElement('div'); + viewItemSubHeaderBox.classList.add('view-item-header-box'); + + const viewItemSubHeader = document.createElement('div'); + viewItemSubHeader.innerText = `[${configKey}]`; + viewItemSubHeader.classList.add('view-item-sub-header'); + + const showButton = document.createElement('div'); + showButton.innerText = `-`; + showButton.classList.add('view-item-show-button'); + showButton.style.fontSize = '20px'; + showButton.setAttribute('id', `${configKey}-view-item-show-button`); + + viewItemSubHeaderBox.append(viewItemSubHeader, showButton); + viewItemContentBox.appendChild(viewItemSubHeaderBox); + + const subContentBox = document.createElement('div'); + subContentBox.classList.add('sub-view-item-content-box'); + subContentBox.setAttribute('id', `${configKey}-sub-view-content-box`); + + viewItemContentBox.appendChild(subContentBox); + + // Save the current key to use as id + currentConfigType = configKey; + + if (configKey === 'onecc') { + // Handle if no onecc information is available + const viewItemContent = document.createElement('div'); + viewItemContent.innerText = 'There is no config information...'; + viewItemBox.appendChild(viewItemContent); + viewItemContent.classList.add('view-item-info-content'); + + for (const oneccSubkey in oneccInfo[configKey]) { + if (oneccInfo[configKey][oneccSubkey]) { + oneccInfoList.push(oneccSubkey); + + // If you have cfg information, delete the no info statement + viewItemContent ?.remove(); + metadataDivCreate(oneccSubkey, oneccInfo[configKey][oneccSubkey], 'cfg-settings'); + } + } + } else { + if (oneccInfoList.includes(configKey)) { + viewItemSubHeader.style.marginTop = '15px'; + for (const oneccSubkey in oneccInfo[configKey]) { + metadataDivCreate(oneccSubkey, oneccInfo[configKey][oneccSubkey], 'cfg-settings'); + } + + } else { + viewItemSubHeader ?.remove(); + } + } + } + } else { + // Other common metadata information + metadataDivCreate(subKey, metadataInfo[subKey], 'common'); + } + } + showButtonClickEvent(); +} + +function metadataDivCreate(subKey, value, type) { + let viewItemContentBox = null; + let subContentBox = null; + if (type === 'common') { + viewItemContentBox = document.getElementById('common-view-content-box'); + } else if (type === 'operations') { + viewItemContentBox = document.getElementById('operations-view-content-box'); + } else if (type === 'cfg-settings') { + viewItemContentBox = document.getElementById('cfg-settings-view-content-box'); + subContentBox = document.getElementById(`${currentConfigType}-sub-view-content-box`); + } + + + const viewItemContent = document.createElement('div'); + viewItemContent.classList.add('view-item-content'); + const viewItemName = document.createElement('div'); + viewItemName.classList.add('view-item-name'); + viewItemName.innerText = subKey; + + // If the value of the key is the object structure again, turn the repetition door. + if (typeof value === 'object' && value !== null) { + const viewItemValueList = document.createElement('div'); + viewItemValueList.classList.add('view-item-value-list'); + + for (const key in value) { + const viewItemValue = document.createElement('div'); + viewItemValue.classList.add('view-item-value'); + viewItemValue.innerText = `${value[key]}`; + // if the size of the viewer is smaller, Set items to be smaller in proportion + viewItemValue.style.width = 'auto'; + viewItemValue.classList.add('margin-bottom-border-thin-gray'); + viewItemValueList.appendChild(viewItemValue); + } + + viewItemContent.append(viewItemName, viewItemValueList); + viewItemContent.classList.add('aline-items-baseline'); + + } else { + // If it's a simple string, it's shown on the screen right away. + const viewItemValue = document.createElement('div'); + viewItemValue.classList.add('view-item-value'); + viewItemValue.innerText = value; + viewItemContent.append(viewItemName, viewItemValue); + } + + if (type === 'cfg-settings') { + viewItemContent.style.marginBottom = '1px'; + subContentBox.appendChild(viewItemContent); + + } else { + viewItemContentBox.appendChild(viewItemContent); + } +} + +function showButtonClickEvent() { + const showButtons = document.getElementsByClassName('view-item-show-button'); + let isSubButton = false; + + for (let index = 0; index < showButtons.length; index++) { + const button = showButtons[index]; + const id = button.getAttribute('id'); + + button.addEventListener('click', () => { + let contentBox = document.getElementById(`${id.split('-view')[0]}-view-content-box`); + isSubButton = false; + if (!contentBox) { + contentBox = document.getElementById(`${id.split('-view')[0]}-sub-view-content-box`); + isSubButton = true; + } + + if(contentBox?.style.display === 'block' || contentBox.style.display === ''){ + isSubButton ? (() => { + button.innerText = '+'; + button.style.fontSize = '14px'; + })() : + (() => { + button.classList.remove('codicon-collapse-all'); + button.classList.add('codicon-unfold'); + })(); + contentBox.style.display = 'none'; + } else { + isSubButton ? (() => { + button.innerText = '-'; + button.style.fontSize = '20px'; + })() : + (() => { + button.classList.remove('codicon-unfold'); + button.classList.add('codicon-collapse-all'); + })(); + contentBox.style.display = 'block'; + } + }); + } +} diff --git a/media/MetadataViewer/style.css b/media/MetadataViewer/style.css new file mode 100644 index 00000000..b7da4e9d --- /dev/null +++ b/media/MetadataViewer/style.css @@ -0,0 +1,130 @@ +/* + * Copyright (c) 2022 Samsung Electronics Co., Ltd. All Rights Reserved + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + body { + background-color: #1e1e1e; + max-width: 800px; + margin-left: 20px; +} + +/* white테마일 때 처리 */ +body.vscode-light { + background-color: #e7e7e7; +} + +body.vscode-light .view-item-header { + color: #444444; +} + +body.vscode-light .view-item-name { + color: #616161; +} + +body.vscode-light .view-item-sub-header { + color: #a31515; +} + +body.vscode-light .view-item-value { + color: #444444; + background-color: white; +} + +.view-item-header { + padding: 10px; + margin-top: 20px; + margin-bottom: 10px; + font-size: 20px; + font-weight: bold; + font-family: sans-serif; + color: #e7e7e7; +} + +.view-item-content { + display: flex; + align-items: center; + justify-content: end; + margin-bottom: 10px; +} + + +.view-item-value { + margin-left: 10px; + background-color: #252526; + padding: 10px; + width: 650px; + border-radius: 3px; + font-weight: bold; +} + +.view-item-value-list { + width: 680px; +} + +.view-item-name { + color: #cccccc; + min-width: 100px; +} + +.view-item-content { + color:#e7e7e7; +} + +.view-item-sub-header { + margin-left: 10px; + font-size: 16px; + color: #d7ba7d; + margin-bottom: 10px; + font-weight: bold; +} + +.view-item-info-content { + font-size: 20px; + margin-left: 20px; + margin-top: 10px; +} + +.view-item-header-box { + display: flex; + align-items: baseline; + justify-content: space-between; +} + +.view-item-show-button { + font-size: 30px; + cursor: pointer; +} + +.view-item-show-button:hover { + color: gray; +} + +#cfg-settings-view-item-box { + margin-bottom: 30px; +} + +.view-item-content-box { + display: block; +} + +.aline-items-baseline { + align-items: baseline; +} + +.margin-bottom-border-thin-gray { + box-sizing: border-box; + border-bottom: solid rgba(0, 0, 0, 0.20); + border-width: 0.1px; +} diff --git a/media/RelationViewer/index.html b/media/RelationViewer/index.html new file mode 100644 index 00000000..d167be2c --- /dev/null +++ b/media/RelationViewer/index.html @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + Document + + + + + + diff --git a/media/RelationViewer/index.js b/media/RelationViewer/index.js new file mode 100644 index 00000000..48b7e378 --- /dev/null +++ b/media/RelationViewer/index.js @@ -0,0 +1,968 @@ +/* + * Copyright (c) 2022 Samsung Electronics Co., Ltd. All Rights Reserved + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// prevent right-clicks in a Web view +document.addEventListener('contextmenu', event => event.preventDefault()); + +const vscode = acquireVsCodeApi(); +// history saved List +let historyList = vscode.getState() ?.historyList || []; + +// Save information for currently open files +let currentFileInfo = null; + +// Save node path to the current context menu +let selectedNodePath = null; + +window.addEventListener('message', (event) => { + const message = event.data; + const selected = message.payload['selected']; + const relationData = message.payload['relation-data']; + + getSelectedFileInfo(selected, relationData); + + switch (message.type) { + case 'create': + detachTree(); + pushCurrentFileInfoObject(historyList); + attachTree(relationData); + console.log('create 메시지는: ', message); + break; + case 'update': + console.log('update 메시지는: ', message); + detachTree(); + historyList = message.historyList; + pushCurrentFileInfoObject(historyList); + attachTree(relationData); + historyMainBoxDisplay(message.type, message.isOpenHistoryBox); + vscode.setState({historyList: historyList}); + break; + case 'history': + console.log('history 메시지는: ', message); + detachTree(); + attachTree(relationData); + historyList = message.historyList; + break; + default: + break; + } +}); + +// save node +let node; + +function attachTree(relationData) { + const wheelInfoBox = document.createElement('div'); + wheelInfoBox.innerText = '* (Ctrl or Shift) + Wheel : Zoom In, Zoom Out'; + wheelInfoBox.classList.add('wheel-info-box'); + document.body.appendChild(wheelInfoBox); + + // assigns the data to a hierarchy using parent-child relationships + const treeData = d3.stratify().id(d => d.id).parentId(d => d.parent)(relationData); + + let historyDivWidth = screen.width * 0.11; + const rectSizeWidth = 130; + const rectSizeHeight = 50; + + const [maxWidthCount, maxHeightCount] = countMaxDataNum(relationData); + + // set the dimensions and margins of the diagram + const margin = {top: 70, right: 0, bottom: 60, left: 0}; + let width = (maxWidthCount + 5) * rectSizeWidth - margin.left - margin.right; + width = (width < screen.width * 0.7) ? screen.width * 0.7 : width; + let height = screen.height * 0.6 - margin.top - margin.bottom; + height = (rectSizeHeight * maxHeightCount < height) ? height : rectSizeHeight * maxHeightCount; + // declares a tree layout and assigns the size + const treemap = d3.tree().size([width, height]); + + // maps the node data to the tree layout + const nodes = treemap(treeData); + + // Main Box Create + const relationBox = document.createElement('div'); + relationBox.classList.add('relation-box'); + relationBox.setAttribute('id', 'relation-box'); + relationBox.style.width = `${historyDivWidth + width}px`; + + document.body.appendChild(relationBox); + + // draw contextMenuBox + const contextMenuBox = document.createElement('div'); + contextMenuBox.setAttribute('id', 'context-menu-box'); + contextMenuBox.classList.add('plus-button-context-box'); + contextMenuBox.style.width = `${width + historyDivWidth}px`; + + + // draw contextMenu + const contextMenu = document.createElement('div'); + contextMenu.setAttribute('id', 'context-menu'); + contextMenu.classList.add('context-menu'); + + document.body.append(contextMenu, contextMenuBox); + + const contextMenuList = ['Show Metadata of this file', 'Open this file']; + + for (let index = 0; index < contextMenuList.length; index++) { + // add menu a dividing line + if (index >= 1) { + const contextMenuInfoLine = document.createElement('div'); + contextMenuInfoLine.classList.add('context-menu-line'); + contextMenu.appendChild(contextMenuInfoLine); + } + + const element = contextMenuList[index]; + const contextMenuInfo = document.createElement('div'); + contextMenuInfo.classList.add('context-menu-info'); + contextMenuInfo.innerText = element; + contextMenu.appendChild(contextMenuInfo); + + contextMenuInfo.addEventListener('click', () => { + contextMenuBox.style.display = 'none'; + contextMenu.style.display = 'none'; + switch (element) { + case 'Show Metadata of this file': + postMessage('showMetadata', {path: selectedNodePath}); + break; + case 'Open this file': + postMessage('openFile', {path: selectedNodePath}); + break; + default: + break; + } + }); + } + // append left, right click + contextMenuBox.addEventListener('click', () => { + contextMenuBox.style.display = 'none'; + contextMenu.style.display = 'none'; + }); + contextMenuBox.addEventListener('contextmenu', () => { + contextMenuBox.style.display = 'none'; + contextMenu.style.display = 'none'; + }); + + // append the svg obgect to the body of the page + // appends a 'group' element to 'svg' + // moves the 'group' element to the top left margin + const svg = + d3.select('.relation-box') + .append('svg') + .attr('preserveAspectRatio', 'xMidYMid meet') + .attr( + 'viewBox', + `0 0 ${width + margin.left + margin.right} ${height + margin.top + margin.bottom}`) + .attr('width', width + margin.left + margin.right) + .attr('height', height + margin.top + margin.bottom) + .style('background-color', '#545454'), + g = svg.append('g').attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); + + // adds the links between the nodes + g.selectAll('.link') + .data(nodes.descendants().slice(1)) + .enter() + .append('path') + .attr('class', 'link') + .attr('d', function(d) { + return 'M' + d.x + ',' + d.y + 'C' + d.x + ',' + (d.y + d.parent.y) / 2 + ' ' + d.parent.x + + ',' + (d.y + d.parent.y) / 2 + ' ' + d.parent.x + ',' + d.parent.y; + }); + + // adds each node as a group + node = g.selectAll('.node') + .data(nodes.descendants()) + .enter() + .append('g') + .attr('class', d => 'node' + (d.children ? ' node--internal' : ' node--leaf')) + .attr('transform', d => 'translate(' + d.x + ',' + d.y + ')'); + + let waitForDouble = null; + + // adds the rectangle to the node + drawRect('extension',rectSizeWidth,rectSizeHeight,waitForDouble); + drawRect('fileName',rectSizeWidth,rectSizeHeight,waitForDouble); + drawRect('versionInfo',rectSizeWidth,rectSizeHeight,waitForDouble); + + // Create hover text_div to show File path + const hoverText = document.createElement('div'); + document.body.appendChild(hoverText); + hoverText.classList.add('hover-text'); + + // draw node`s extension + setDrawInfoInNode('extension', node, rectSizeHeight, waitForDouble); + // draw node`s fileName + setDrawInfoInNode('name', node, rectSizeHeight, waitForDouble); + + // draw version info + setDrawInfoInNode('toolchainVersion', node, rectSizeHeight, waitForDouble); + setDrawInfoInNode('oneccVersion', node, rectSizeHeight), waitForDouble; + + // add a plus button + node._groups[0].forEach((g) => { + g.setAttribute('id', `${g.__data__.data.id}-g`); + + if (g.__data__.data['data-list'].length >= 2) { + // draw plus_button_ctext_menu + const plusButtonContextMenu = document.createElement('div'); + plusButtonContextMenu.classList.add('plus-button-context-menu'); + plusButtonContextMenu.setAttribute('id', `${g.__data__.data.id}-plus-button-context-menu`); + document.body.appendChild(plusButtonContextMenu); + + // draw plus_button_context_menu_header + const plusButtonContextMenuHeader = document.createElement('div'); + plusButtonContextMenuHeader.classList.add('plus-button-context-menu-header'); + plusButtonContextMenuHeader.innerText = 'Same Content File List'; + plusButtonContextMenu.appendChild(plusButtonContextMenuHeader); + + // Add data list information to the context menu + const dataList = g.__data__.data['data-list']; + + for (let idx = 0; idx < dataList.length; idx++) { + if (idx !== g.__data__.data['represent-idx']) { + const data = dataList[idx]; + for (const key in data) { + if (key === 'name') { + // add a dividing line + const plusButtonContextMenuLine = document.createElement('div'); + plusButtonContextMenuLine.classList.add('context-menu-line'); + plusButtonContextMenu.appendChild(plusButtonContextMenuLine); + + // draw plus_button_context_menu_info + const plusButtonContextMenuInfo = document.createElement('div'); + plusButtonContextMenuInfo.innerText = data[key]; + plusButtonContextMenuInfo.classList.add('plus-button-context-menu-info'); + plusButtonContextMenu.appendChild(plusButtonContextMenuInfo); + + plusButtonContextMenuInfo.addEventListener('click', () => { + postMessage('update', {path: dataList[idx]['path'], historyList: historyList, isOpenHistoryBox: + document.getElementsByClassName('history-main-box')[0].style.display}); + // hide context_menu + hidePlusButtonContextMenu(plusButtonContextBox); + }); + } + } + } + } + } + + // When there are two versions of the information, + // the height of the rect tag is increased. + let versionInfoCount = 0; + const nodeData = g.__data__.data['data-list'][g.__data__.data['represent-idx']]; + for (const key in nodeData) { + if (key === 'onecc-version' || key === 'toolchain-version') { + versionInfoCount += 1; + } + } + + // If there is no version information, sort the file name in the middle + const versionInfoRect = g.childNodes[2]; + if (versionInfoCount === 0) { + const nameInfoRect = g.childNodes[1]; + versionInfoRect.remove(); + + nameInfoRect.style.rx = 3; + } else if (versionInfoCount === 1){ + versionInfoRect.style.height = `20px`; + } + }); + + // draw plusButtonContextBox + const plusButtonContextBox = document.createElement('div'); + plusButtonContextBox.classList.add('plus-button-context-box'); + plusButtonContextBox.setAttribute('id', `plus-button-context-box`); + plusButtonContextBox.style.width = `100%`; + document.body.appendChild(plusButtonContextBox); + + // add hide_plus_button_Event + plusButtonContextBox.addEventListener('click', () => { + // hide context_menu + hidePlusButtonContextMenu(plusButtonContextBox); + }); + + // draw plus_button and minus_button + plusMinusButtonCreate('minus', rectSizeWidth, rectSizeHeight); + plusMinusButtonCreate('plus', rectSizeWidth, rectSizeHeight); + + // add mouse drag and mouse wheel event + relationBox.addEventListener('mousedown', (e) => _mouseDownHandler(e)); + relationBox.addEventListener('mousewheel', (e) => _wheelHandler(e), {passive: false}); + + const historyOpenDiv = document.createElement('div'); + historyOpenDiv.style.width = `${historyDivWidth}px`; + historyOpenDiv.classList.add('history-open-box'); + relationBox.appendChild(historyOpenDiv); + + const historyOpenDivHeader = document.createElement('div'); + historyOpenDivHeader.classList.add('plus-button-context-menu-header'); + historyOpenDivHeader.innerText = 'History'; + historyOpenDivHeader.style.fontSize = '20px'; + + const historyOpenDivHeaderOpenButton = document.createElement('div'); + historyOpenDivHeaderOpenButton.classList.add( + 'history-open-button', 'codicon', 'codicon-fold-down'); + + historyOpenDivHeaderOpenButton.addEventListener('click', () => { + document.getElementsByClassName('history-main-box')[0].style.display = 'block'; + historyOpenDiv.style.display = 'none'; + }); + + historyOpenDivHeaderOpenButton.addEventListener('mouseover', () => { + const historyOpenButtonRect = historyOpenDivHeaderOpenButton.getBoundingClientRect(); + hoverText.innerText = `"History Open"`; + hoverText.style.top = `${historyOpenButtonRect.top - historyOpenButtonRect.height - 12}px`; + hoverText.style.left = `${historyOpenButtonRect.left - historyOpenButtonRect.width - 20}px`; + hoverText.style.display = 'block'; + hoverText.classList.add('font-bold'); + }); + historyOpenDivHeaderOpenButton.addEventListener('mouseout', () => { + hoverText.style.display = 'none'; + hoverText.classList.remove('font-bold'); + }); + + historyOpenDiv.append(historyOpenDivHeaderOpenButton, historyOpenDivHeader); + + // draw hisory_area + const historyDiv = document.createElement('div'); + historyDiv.style.width = `${historyDivWidth}px`; + historyDiv.style.height = + `${document.getElementsByTagName('svg')[0].getBoundingClientRect().height - 100}px`; + historyDiv.classList.add('history-main-box'); + relationBox.appendChild(historyDiv); + + // draw history_box + const historyDivHeaderBox = document.createElement('div'); + historyDivHeaderBox.classList.add('plus-button-context-menu-header-box'); + historyDiv.appendChild(historyDivHeaderBox); + // draw history_header + const historyDivHeader = document.createElement('div'); + historyDivHeader.classList.add('plus-button-context-menu-header'); + historyDivHeader.innerText = 'History'; + historyDivHeader.style.fontSize = '20px'; + // add clear button + const historyDivHeaderClearButton = document.createElement('div'); + historyDivHeaderClearButton.classList.add( + 'plus-button-context-menu-header-clear-button', 'codicon', 'codicon-clear-all'); + + historyDivHeaderClearButton.addEventListener('click', () => { + historyList = [historyList[0]]; + vscode.setState({historyList: historyList}); + + const historyDivChildNodes = [...historyDiv.childNodes]; + for (let index = 0; index < historyDivChildNodes.length; index++) { + const element = historyDivChildNodes[index]; + if (index >= 3) { + element.remove(); + } + } + }); + + historyDivHeaderClearButton.addEventListener('mouseover', () => { + const historyClearButtonRect = historyDivHeaderClearButton.getBoundingClientRect(); + hoverText.innerText = `"Clear history"`; + hoverText.style.top = `${historyClearButtonRect.top - historyClearButtonRect.height - 12}px`; + hoverText.style.left = `${historyClearButtonRect.left - historyClearButtonRect.width - 37}px`; + hoverText.style.display = 'block'; + hoverText.classList.add('font-bold'); + }); + + historyDivHeaderClearButton.addEventListener('mouseout', () => { + hoverText.style.display = 'none'; + hoverText.classList.remove('font-bold'); + }); + + const historyDivHeaderCloseButton = document.createElement('div'); + historyDivHeaderCloseButton.classList.add( + 'history-close-button', 'codicon', 'codicon-fold-up', + 'plus-button-context-menu-header-clear-button'); + + historyDivHeaderCloseButton.addEventListener('click', () => { + document.getElementsByClassName('history-main-box')[0].style.display = 'none'; + historyOpenDiv.style.display = 'flex'; + }); + + historyDivHeaderCloseButton.addEventListener('mouseover', () => { + const historyCloseButtonRect = historyDivHeaderCloseButton.getBoundingClientRect(); + hoverText.innerText = `"History Close"`; + hoverText.style.top = `${historyCloseButtonRect.top - historyCloseButtonRect.height - 12}px`; + hoverText.style.left = `${historyCloseButtonRect.left - historyCloseButtonRect.width - 20}px`; + hoverText.style.display = 'block'; + hoverText.classList.add('font-bold'); + }); + historyDivHeaderCloseButton.addEventListener('mouseout', () => { + hoverText.style.display = 'none'; + hoverText.classList.remove('font-bold'); + }); + + historyDivHeaderBox.append( + historyDivHeaderCloseButton, historyDivHeader, historyDivHeaderClearButton); + + // append history info + for (let index = 0; index < historyList.length; index++) { + // draw a dividing line + const historyInfoLine = document.createElement('div'); + historyInfoLine.classList.add('context-menu-line'); + + const element = historyList[index]; + const historyDivInfo = document.createElement('div'); + + let historyFileName = element.name; + // limit file_length + if (historyFileName.length > 35) { + historyFileName = historyFileName.substring(0, 35) + '...'; + } + historyDivInfo.innerText = historyFileName; + historyDivInfo.id = element.path; + historyDivInfo.classList.add('plus-button-context-menu-info'); + + historyDivInfo.addEventListener('click', () => { + postMessage('history', {path: historyDivInfo.id, historyList: historyList}); + }); + + if (index === 0) { + historyDivInfo.classList.add('current-history-color'); + } + historyDiv.append(historyInfoLine, historyDivInfo); + } + + // focus screen on the current_node + const currentNodeRect = + document.getElementsByClassName('current-node')[0].getBoundingClientRect(); + const relationBoxRect = + document.getElementsByClassName('relation-box')[0].getBoundingClientRect(); + + relationBox.scrollTo({ + left: currentNodeRect.left - relationBoxRect.width / 2 + currentNodeRect.width / 2, + top: currentNodeRect.top - relationBoxRect.height / 2 + currentNodeRect.height / 2, + behavior: 'auto' + }); +} + +function pushCurrentFileInfoObject(historyList) { + const currentFileInfoObject = { + name: currentFileInfo['data-list'][currentFileInfo['represent-idx']].name, + path: currentFileInfo['data-list'][currentFileInfo['represent-idx']].path + }; + historyList.unshift(currentFileInfoObject); +} +let _zoom = 1; +function _wheelHandler(e) { + if (e.shiftKey || e.ctrlKey) { + const delta = + -e.deltaY * (e.deltaMode === 1 ? 0.05 : e.deltaMode ? 1 : 0.002) * (e.ctrlKey ? 10 : 1); + _updateZoom(_zoom * Math.pow(2, delta), e); + e.preventDefault(); + } else { + const container = document.getElementsByClassName('relation-box')[0]; + _scrollLeft = container.scrollLeft; + _scrollTop = container.scrollTop; + } +} + + +let _scrollLeft = 0; +let _scrollTop = 0; +let _width = 0; +let _height = 0; +function _updateZoom(zoom, e) { + const canvas = document.getElementsByTagName('svg')[0]; + _width = _width || canvas.width.animVal.value; + _height = _height || canvas.height.animVal.value; + + const container = document.getElementsByClassName('relation-box')[0]; + + const limit = container.clientHeight / _height; + const min = Math.min(Math.max(limit, 0.15), 1); + zoom = Math.max(min, Math.min(zoom, 1.4)); + const width = zoom * _width; + const height = zoom * _height; + + canvas.style.width = width + 'px'; + canvas.style.height = height + 'px'; + const scrollLeft = container.scrollLeft || _scrollLeft; + const scrollTop = container.scrollTop || _scrollTop; + const x = e.pageX + scrollLeft; + const y = e.pageY + scrollTop; + _scrollLeft = Math.max(0, ((x * zoom) / _zoom) - (x - scrollLeft)); + _scrollTop = Math.max(0, ((y * zoom) / _zoom) - (y - scrollTop)); + + container.scrollLeft = _scrollLeft; + container.scrollTop = _scrollTop; + _zoom = zoom; +} + + +function _mouseDownHandler(e) { + if (e.buttons === 1) { + const html = document.getElementsByTagName('html')[0]; + html.style.cursor = 'grabbing'; + const container = document.getElementsByClassName('relation-box')[0]; + let _mousePosition = + {left: container.scrollLeft, top: container.scrollTop, x: e.clientX, y: e.clientY}; + e.stopImmediatePropagation(); + + const mouseMoveHandler = (e) => { + e.preventDefault(); + e.stopImmediatePropagation(); + const dx = e.clientX - _mousePosition.x; + const dy = e.clientY - _mousePosition.y; + _mousePosition.moved = dx * dx + dy * dy > 0; + + if (_mousePosition.moved) { + container.scrollLeft = _mousePosition.left - dx; + container.scrollTop = _mousePosition.top - dy; + _scrollLeft = container.scrollLeft; + _scrollTop = container.scrollTop; + } + }; + + const mouseUpHandler = () => { + html.style.cursor = null; + container.removeEventListener('mouseup', mouseUpHandler); + container.removeEventListener('mouseleave', mouseUpHandler); + container.removeEventListener('mousemove', mouseMoveHandler); + if (_mousePosition && _mousePosition.moved) { + e.preventDefault(); + e.stopImmediatePropagation(); + _mousePosition = null; + } + }; + container.addEventListener('mousemove', mouseMoveHandler); + container.addEventListener('mouseup', mouseUpHandler); + container.addEventListener('mouseleave', mouseUpHandler); + } +} + +function postMessage(type, object) { + vscode.postMessage({type: type, ...object}); +} + +function detachTree() { + d3.select('svg') ?.remove(); + + while (document.body.hasChildNodes()) { + document.body.removeChild(document.body.firstChild); + } +} + +function setDrawInfoInNode(type, node, rectSizeHeight, waitForDouble) { + let currentRect = ''; + + node.append('text') + .attr('x', -60) + .attr( + 'class', + d => { + let classList = ''; + if (type === 'extension') { + classList += 'file-extension-custom file-extension '; + } else if ( + type === 'name' && d.data['data-list'][d.data['represent-idx']]['is-deleted']) { + classList += 'deleted-text-decoration '; + } else if (type === 'name') { + classList += 'text-file-name'; + } + return classList; + }) + .attr( + 'y', + d => { + if (type === 'oneccVersion') { + return -rectSizeHeight + 75; + } else if (type === 'toolchainVersion') { + return -rectSizeHeight + 63; + } else if (type === 'extension') { + return -rectSizeHeight + 15; + } else if (type === 'name') { + const oneccVersion = d.data['data-list'][d.data['represent-idx']]['onecc-version']; + const toolchainVersion = + d.data['data-list'][d.data['represent-idx']]['toolchain-version']; + + if (oneccVersion || toolchainVersion) { + return -rectSizeHeight + 42; + } else { + return -rectSizeHeight + 42; + } + } + }) + .text(d => { + let versionInfo = ''; + if (type === 'oneccVersion' && + d.data['data-list'][d.data['represent-idx']]['onecc-version']) { + versionInfo = d.data['data-list'][d.data['represent-idx']]['onecc-version']; + versionInfo = `onecc-version: ${versionInfo}`; + } else if ( + type === 'toolchainVersion' && + d.data['data-list'][d.data['represent-idx']]['toolchain-version']) { + versionInfo = d.data['data-list'][d.data['represent-idx']]['toolchain-version']; + versionInfo = `toolchain-version: ${versionInfo}`; + } else if ( type === 'extension') { + versionInfo = d.data['data-list'][d.data['represent-idx']].name.split( + '.')[d.data['data-list'][d.data['represent-idx']].name.split('.').length - 1]; + } else if (type === 'name') { + let fullName = d.data['data-list'][d.data['represent-idx']].name; + if (fullName.length > 22) { + fullName = fullName.substring(0, 22) + '...'; + } + + if (d.data['data-list'][d.data['represent-idx']]['is-deleted']) { + fullName = '(deleted)'; + } + return fullName; + } + return versionInfo; + }) + .on('dblclick', + () => { + if (waitForDouble !== null) { + clearTimeout(waitForDouble); + waitForDouble = null; + } + }) + .on('click', + (_p, d) => { + if (waitForDouble === null) { + waitForDouble = setTimeout(() => { + postMessage('update', { + path: d.data['data-list'][d.data['represent-idx']].path, + historyList: historyList, + isOpenHistoryBox: + document.getElementsByClassName('history-main-box')[0].style.display + }); + waitForDouble = null; + }, 300); + } + }) + .on('contextmenu', + (mouse, d) => { + openContextMenu(mouse, d.data['data-list'][d.data['represent-idx']].path); + }) + .on('mouseover', + (mouse, node) => { + if(type === 'name'){ + const hoverText = document.getElementsByClassName('hover-text')[0]; + const fileExtensionText = mouse.path[0].getBoundingClientRect(); + hoverText.style.display = 'block'; + hoverText.innerText = `${node.data['data-list'][node.data['represent-idx']].path}`; + hoverText.style.left = `${fileExtensionText.x - 8}px`; + hoverText.style.top = `${fileExtensionText.y - 24}px`; + if (node.data['data-list'][node.data['represent-idx']]['is-deleted']) { + hoverText.classList.add('deleted-text-decoration'); + } + } + + currentRect = mouse.fromElement; + if(mouse.fromElement?.tagName !== 'rect') { + currentRect = mouse.path[1].childNodes[2]; + } + currentRect.classList.add('rect-hover-fill'); + + }) + .on('mouseout', (_mouse, node) => { + if(type === 'name'){ + const hoverText = document.getElementsByClassName('hover-text')[0]; + hoverText.style.display = 'none'; + if (node.data['data-list'][node.data['represent-idx']]['is-deleted']) { + hoverText.classList.remove('deleted-text-decoration'); + } + } + + currentRect.classList.remove('rect-hover-fill'); + + }); +} + +function hidePlusButtonContextMenu(plusButtonContextBox) { + if (plusButtonContextBox.style.display === 'block') { + plusButtonContextBox.style.display = 'none'; + const plusButtonContextMenus = document.getElementsByClassName('plus-button-context-menu'); + for (let index = 0; index < plusButtonContextMenus.length; index++) { + const element = plusButtonContextMenus[index]; + element.style.display = 'none'; + } + const plusButtons = document.getElementsByClassName('plus-button'); + for (let index = 0; index < plusButtons.length; index++) { + const element = plusButtons[index]; + element.style.display = 'block'; + } + const minusButton = document.getElementsByClassName('minus-button'); + for (let index = 0; index < minusButton.length; index++) { + const element = minusButton[index]; + element.style.display = 'none'; + } + } +} + +function openContextMenu(mouse, path) { + const contextMenuBox = document.getElementById('context-menu-box'); + const contextMenu = document.getElementById('context-menu'); + const relationBox = document.getElementById('relation-box'); + const relationBoxRect = relationBox.getBoundingClientRect(); + + contextMenuBox.style.display = 'block'; + contextMenu.style.display = 'block'; + + contextMenu.style.top = `${mouse.y}px`; + contextMenu.style.left = `${mouse.x}px`; + + const contextMenuRect = contextMenu.getBoundingClientRect(); + + // If the right of the context menu is longer than the size of the current container, + if (relationBoxRect.right - mouse.x < contextMenuRect.width) { + contextMenu.style.left = `${mouse.x - contextMenuRect.width}px`; + } + + // If the bottom of the plus menu is outside the screen, + if (relationBoxRect.height - mouse.y < contextMenuRect.height) { + contextMenu.style.top = `${mouse.y - contextMenuRect.height}px`; + } + + + selectedNodePath = path; +} + +function getSelectedFileInfo(selected, relationData) { + for (let idx = 0; idx < relationData.length; idx++) { + for (const key in relationData[idx]) { + if (key === 'id') { + if (relationData[idx]['id'] === selected) { + currentFileInfo = relationData[idx]; + } + } + } + if (!currentFileInfo) { + break; + } + } +} + +function plusMinusButtonCreate(type, rectSizeWidth, rectSizeHeight) { + node.append('text') + .attr('x', rectSizeWidth / 2 - 14) + .attr('y', -rectSizeHeight + 9) + .attr( + 'class', + () => { + return type === 'minus' ? 'minus-button' : 'plus-button'; + }) + .attr( + 'id', + node => { + return type === 'minus' ? `${node.data.id}-minus-button` : + `${node.data.id}-plus-button`; + }) + .text(node => { + if (node.data['data-list'].length >= 2) { + return type === 'minus' ? `-` : '+'; + } + }) + .on('click', + (mouse, node) => { + const minusButton = document.getElementById(`${node.data.id}-minus-button`); + const plusButton = document.getElementById(`${node.data.id}-plus-button`); + const plusButtonContextBox = document.getElementById(`plus-button-context-box`); + const plusButtonContextMenu = + document.getElementById(`${node.data.id}-plus-button-context-menu`); + const relationBox = document.getElementById(`relation-box`); + const relationBoxRect = relationBox.getBoundingClientRect(); + + if (type === 'minus') { + minusButton.style.display = 'none'; + plusButton.style.display = 'display'; + plusButtonContextBox.style.display = 'none'; + plusButtonContextMenu.style.display = 'none'; + } else { + const plusButtonRect = mouse.path[0].getBoundingClientRect(); + minusButton.style.display = 'block'; + plusButton.style.display = 'none'; + plusButtonContextBox.style.display = 'block'; + plusButtonContextMenu.style.display = 'block'; + plusButtonContextMenu.style.top = `${plusButtonRect.y}px`; + plusButtonContextMenu.style.left = `${plusButtonRect.right}px`; + + const plusButtonContextMenuWidth = + plusButtonContextMenu.getBoundingClientRect().width; + const plusButtonContextMenuHeight = + plusButtonContextMenu.getBoundingClientRect().height; + + // the area of the plus menu is outside the current screen + if (plusButtonRect.right + plusButtonContextMenuWidth > + relationBox.getBoundingClientRect().right) { + plusButtonContextMenu.style.left = + `${plusButtonRect.right - plusButtonContextMenuWidth}px`; + } + // If the bottom of the plus menu is outside the screen, + if (relationBoxRect.height - plusButtonRect.top < plusButtonContextMenuHeight) { + plusButtonContextMenu.style.top = + `${plusButtonRect.y - plusButtonContextMenuHeight}px`; + } + } + }) + .on('mouseover', + (mouse) => { + const plusButton = mouse.path[0].getBoundingClientRect(); + const hoverText = document.getElementsByClassName('hover-text')[0]; + hoverText.style.display = 'block'; + hoverText.innerText = `"Open the same content file list"`; + hoverText.style.left = `${plusButton.x - 80}px`; + hoverText.style.top = `${plusButton.y - 12}px`; + hoverText.classList.add('font-bold'); + + const hoverTextRect = hoverText.getBoundingClientRect(); + const relationBoxRect = document.getElementById('relation-box').getBoundingClientRect(); + // If the right of the hover text is longer than the size of the current container, + if (relationBoxRect.right - plusButton.x < hoverTextRect.width) { + hoverText.style.left = `${plusButton.x - hoverTextRect.width + 20}px`; + } + + // If the top of the hover text is outside the screen, + if (plusButton.y < hoverTextRect.height) { + hoverText.style.top = `${plusButton.y + hoverTextRect.height + 10}px`; + } + }) + .on('mouseout', () => { + const hoverText = document.getElementsByClassName('hover-text')[0]; + hoverText.style.display = 'none'; + hoverText.classList.remove('font-bold'); + }); +} + +function countMaxDataNum(relationData) { + // d3 graph layer info + const d3ClassData = {}; + + // d3 graph number of nodes for layer + const d3NumData = {}; + + // Maximum Number in One Floor + let _maxWidthCount = 1; + // Total number of floors + let _maxHeightCount = 1; + for (const key in relationData) { + const data = relationData[key]; + + if (!d3ClassData[data.parent]) { + d3ClassData[data.id] = 1; + } else { + d3ClassData[data.id] = d3ClassData[data.parent] + 1; + } + + d3NumData[d3ClassData[data.id]] = + (d3NumData[d3ClassData[data.id]] ? d3NumData[d3ClassData[data.id]] : 0) + 1; + if (_maxWidthCount < d3NumData[d3ClassData[data.id]]) { + _maxWidthCount = d3NumData[d3ClassData[data.id]]; + } + } + _maxHeightCount = Object.keys(d3NumData).length; + return [_maxWidthCount, _maxHeightCount]; +} + +function historyMainBoxDisplay(type, isOpenHistoryBox) { + const historyMainBox = document.getElementsByClassName('history-main-box')[0]; + const historyOpenBox = document.getElementsByClassName('history-open-box')[0]; + + if (type === 'update') { + console.log(isOpenHistoryBox); + if (isOpenHistoryBox === 'block' || isOpenHistoryBox === '') { + historyMainBox.style.display = 'block'; + historyOpenBox.style.display = 'none'; + } else { + historyMainBox.style.display = 'none'; + historyOpenBox.style.display = 'flex'; + } + } else { + historyMainBox.style.display = 'block'; + historyOpenBox.style.display = 'none'; + } +} + +function drawRect(type,rectSizeWidth,rectSizeHeight,waitForDouble) { + node.append('rect') + .attr('x', -rectSizeWidth / 2) + .attr('y',() => { + switch (type) { + case 'extension': + return -rectSizeHeight; + case 'fileName': + return -rectSizeHeight + 25; + case 'versionInfo': + return -rectSizeHeight + 25 + 25; + default: + break; + } + }) + .attr('rx',() => { + return type === 'extension' ? 3 : null; + }) + .attr('ry',() => { + return type === 'versionInfo' ? 3 : null; + }) + .attr('width', rectSizeWidth) + .attr('height', type === 'versionInfo' ? 30 : 25) + .attr( + 'class', + d => { + let classList = ''; + if (d.data.id === currentFileInfo.id) { + classList += 'current-node '; + } + if (type === 'fileName'){ + classList += 'rect-file-name'; + } + return classList; + }) + .style( + 'fill', + d => { + if(type === 'extension'){ + const nodeData = d.data['data-list'][d.data['represent-idx']]; + const nodeFileExtension = + nodeData['name'].split('.')[nodeData['name'].split('.').length - 1]; + if (nodeFileExtension === 'log') { + return 'rgb(25, 40, 60, 1.0)'; + } else if (nodeFileExtension === 'circle') { + return 'rgba(75, 27, 22, 1.0)'; + } else { + return '#161515'; + } + } else if(type === 'versionInfo'){ + return "rgb(37 50 37)"; + } + }) + .on('dblclick', + () => { + if (waitForDouble !== null) { + clearTimeout(waitForDouble); + waitForDouble = null; + } + }) + .on('click', + (_p, d) => { + if (waitForDouble === null) { + waitForDouble = setTimeout(() => { + postMessage('update', { + path: d.data['data-list'][d.data['represent-idx']].path, + historyList: historyList, + isOpenHistoryBox: + document.getElementsByClassName('history-main-box')[0].style.display + }); + waitForDouble = null; + }, 300); + } + }) + .on('contextmenu', (mouse, d) => { + openContextMenu(mouse, d.data['data-list'][d.data['represent-idx']].path); + }); +} diff --git a/media/RelationViewer/style.css b/media/RelationViewer/style.css new file mode 100644 index 00000000..9f12dc52 --- /dev/null +++ b/media/RelationViewer/style.css @@ -0,0 +1,412 @@ +/* + * Copyright (c) 2022 Samsung Electronics Co., Ltd. All Rights Reserved + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + /* light theme */ + body.vscode-light { + background-color: #f8f8f8; + } + + body.vscode-light svg { + background-color: #f8f8f8 !important; +} + +body.vscode-light .wheel-info-box { + color: black; +} + +body.vscode-light .history-main-box { + background-color: #e7e7e7; +} + +body.vscode-light .history-open-box { + background-color: #e7e7e7; +} + +body.vscode-light .context-menu-line { + border-bottom:solid rgba(0, 0, 0, 0.425); +} + +body.vscode-light .plus-button-context-menu { + background-color: #ececec; +} + +body.vscode-light .plus-button-context-menu-info { + color: black; +} + +body.vscode-light .context-menu { + background-color: #ececec; +} + +body.vscode-light .plus-button-context-menu-header-clear-button { + color: gray; +} + +body.vscode-light .link { + stroke: #1e1e1e; +} + +body.vscode-light .hover-text { + background-color: black; + color: white; +} + +body.vscode-light .plus-button { + fill: #a69c9c; +} + +body.vscode-light .minus-button { + fill: #a69c9c; +} + +body.vscode-light .context-menu-info { + color: black; +} + +body.vscode-light .plus-button-context-menu-header { + color: black; +} + +body.vscode-light .context-menu-info:hover { + background-color: white; +} + +body.vscode-light .plus-button-context-menu-info:hover { + background-color: white; +} + +body.vscode-light .plus-button:hover { + fill: white; +} + +body.vscode-light .plus-button-context-menu-header-clear-button:hover { + color: white; +} + +body.vscode-light .current-history-color { + color: rgb(163, 21, 21); +} + +body.vscode-light .node rect { + stroke: #a69c9c; +} + +body.vscode-light .text-file-name { + fill: black !important; +} + +body.vscode-light .rect-file-name { + fill: white !important; +} + +body.vscode-light .node rect:hover { + fill : rgb(141, 141, 141, 0.8) !important; +} + +body.vscode-light .rect-hover-fill { + fill : rgb(141, 141, 141, 0.8) !important; +} + +body.vscode-light .current-node { + stroke: rgb(163, 21, 21) !important; + stroke-width: 2px !important; +} + +html { + width: 100%; + height: 100%; + overflow: hidden; +} + +body { + background-color: #545454; + padding: 0; + width: 100%; + height: 100%; + overflow: hidden; + font-family: -apple-system, BlinkMacSystemFont, "Segoe WPC", "Segoe UI", "Ubuntu", "Droid Sans", sans-serif, "PingFang SC"; + /* prevent drag */ + -webkit-touch-callout: none; + -webkit-user-select: none; + -khtml-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; +} + +svg { + margin : 0 auto; + margin-top: 30px; + flex-shrink: 0; +} + +.node rect { + fill: #2a2929; + stroke: black; + stroke-width: 1px; + cursor: pointer; +} + +.node rect:hover { + fill: #525252 !important; +} + +.text-hover { + opacity: 0.5; +} + +.node text { + font: 11px sans-serif; + cursor: pointer; + fill : white; + + /* default drag function */ + -webkit-touch-callout: none; + -webkit-user-select: none; + -khtml-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; +} + +.text-shadow-white { + text-shadow: 0 1px 0 #fff, 0 -1px 0 #fff, 1px 0 0 #fff, -1px 0 0 #fff; +} + +.file-extension-custom { + fill: #e7e7e7; + font-size: 11px !important; + font-weight: bold !important; +} + +.link { + fill: none; + stroke: #ccc; + stroke-width: 2px; +} + +.relation-box { + width: 100% !important; + height: 100% !important; + display: flex; + overflow: auto; +} + +.hover-text { + position: absolute; + background-color: white; + color: black; + padding: 3px 6px; + border-radius: 3px; + width: max-content; + display: none; +} + +.minus-button { + position: absolute; + font-size: 35px !important; + font-weight: bold !important; + cursor: pointer !important; + fill: #ececec; + display: none; + z-index: 3; +} + +.plus-button { + position: absolute; + font-size: 25px !important; + font-weight: bold !important; + cursor: pointer !important; + fill: #ececec; +} + +.plus-button:hover { + fill: #888888; +} + +.plus-button-context-box { + position: absolute; + top:0; + left: 0; + width: 100%; + height: 100%; + display: none; + z-index: 1; +} + +.plus-button-context-menu { + display: none; + position: absolute; + width: 250px; + max-height: 300px; + border: 1px solid black; + z-index: 2; + box-sizing: border-box; + background-color: #1e1e1e; + border-radius: 10px; + padding-top: 10px; + padding-bottom: 10px; +} + +.plus-button-context-menu-info { + font-size: 13px; + cursor: pointer; + padding: 4px 10px; + word-break: break-all; +} + +.plus-button-context-menu-header-box { + display: flex; + justify-content: space-around; + align-items: flex-end; + padding: 5px; +} + +.plus-button-context-menu-header { + font-size: 16px; + text-align: center; + font-weight: bold; + font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; + color: #e7e7e7; + flex-grow: 1; +} + +.plus-button-context-menu-header-clear-button { + text-align: center; + padding-right: 5px; + cursor: pointer; + font-size: 10px; + color: gray; +} + +.plus-button-context-menu-header-clear-button:hover { + color: #fff; +} + +.plus-button-context-menu-info:hover { + background-color: #04395e; +} + +.context-menu-line { + box-sizing: border-box; + border-bottom: solid rgba(255, 255, 255, 0.425); + border-width: 0.1px; + margin-top: 4px; + margin-bottom: 4px; +} + +.current-node { + stroke: #d7ba7d !important; + stroke-width: 2px !important; +} + +.context-menu { + display: none; + position: absolute; + max-height: 300px; + width: max-content; + border: 1px solid black; + z-index: 2; + box-sizing: border-box; + background-color: #1e1e1e; + border-radius: 10px; + padding-top: 10px; + padding-bottom: 10px; +} + +.context-menu-info { + font-size: 13px; + cursor: pointer; + padding: 4px 20px; +} + +.context-menu-info:hover { + background-color: #04395e; +} + +.history-main-box { + background-color: #2a2929; + border-radius: 5px; + overflow: scroll; + position: absolute; + right: 30px; + top: 50px; + display: block; +} + +/* scroll hide */ +.history-main-box::-webkit-scrollbar { + display: none; +} + +.wheel-info-box { + position: absolute; + top:10px; + left: 10px; + color: white; +} + +.deleted-text-decoration { + text-decoration: line-through !important; + font-style: italic !important; + fill: gray !important; + color: gray !important; +} + +.font-bold { + font-weight: bold; +} + +.history-open-box { + display: none; + align-items: flex-end; + background-color: #2a2929; + border-radius: 5px; + height: max-content; + position: absolute; + top: 50px; + right: 30px; + padding-top: 5px; + padding-bottom: 8px; + opacity: 0.7; +} + +.history-close-button { + margin-left: 5px; + cursor: pointer; +} + +.history-open-button { + margin-left: 10px; + margin-right: 0; + cursor: pointer; +} + +.current-history-color { + color: #d7ba7d; +} + +.rect-hover-fill { + fill: #525252 !important; +} + +.text-file-name { + fill: white !important; +} + +.rect-file-name { + fill: #2d2d2d !important; +} \ No newline at end of file diff --git a/package.json b/package.json index d0f525ec..3b996e08 100644 --- a/package.json +++ b/package.json @@ -97,6 +97,26 @@ } ], "priority": "option" + }, + { + "viewType": "one.viewer.metadata", + "displayName": "Metadata Viewer", + "selector": [ + { + "filenamePattern": "*" + } + ], + "priority": "option" + }, + { + "viewType": "one.viewer.relation", + "displayName": "Relation Viewer", + "selector": [ + { + "filenamePattern": "*" + } + ], + "priority": "option" } ], "commands": [ @@ -195,6 +215,26 @@ "command": "one.viewer.jsonTracer", "title": "json tracer", "category": "ONE" + }, + { + "command": "one.viewer.metadata.showFromDefaultExplorer", + "title": "Show Metadata", + "category": "ONE" + }, + { + "command": "one.viewer.metadata.showFromOneExplorer", + "title": "ONE: Show Metadata", + "category": "ONE" + }, + { + "command": "one.viewer.relation.showFromDefaultExplorer", + "title": "Show Relation", + "category": "ONE" + }, + { + "command": "one.viewer.relation.showFromOneExplorer", + "title": "ONE: Show Relation", + "category": "ONE" } ], "menus": { @@ -275,6 +315,28 @@ "command": "one.toolchain.uninstall", "when": "view == ToolchainView && viewItem =~ /toolchain/ && !one.job:running", "group": "inline" + }, + { + "command": "one.viewer.metadata.showFromOneExplorer", + "when": "view == OneExplorerView && viewItem =~ /baseModel|product/", + "group": "7_metadata@1" + }, + { + "command": "one.viewer.relation.showFromOneExplorer", + "when": "view == OneExplorerView && viewItem =~ /baseModel|product/", + "group": "7_metadata@1" + } + ], + "explorer/context": [ + { + "command": "one.viewer.metadata.showFromDefaultExplorer", + "when": "resourceExtname in one.metadata.supportedFiles && !explorerResourceIsFolder", + "group": "7_metadata@1" + }, + { + "command": "one.viewer.relation.showFromDefaultExplorer", + "when": "resourceExtname in one.metadata.supportedFiles && !explorerResourceIsFolder", + "group": "7_metadata@1" } ], "commandPalette": [ @@ -341,6 +403,22 @@ { "command": "one.viewer.jsonTracer", "when": "false" + }, + { + "command": "one.viewer.metadata.showFromDefaultExplorer", + "when": "false" + }, + { + "command": "one.viewer.metadata.showFromOneExplorer", + "when": "false" + }, + { + "command": "one.viewer.relation.showFromDefaultExplorer", + "when": "false" + }, + { + "command": "one.viewer.relation.showFromOneExplorer", + "when": "false" } ] }, diff --git a/src/MetadataViewer/MetadataViewer.ts b/src/MetadataViewer/MetadataViewer.ts new file mode 100644 index 00000000..c7dffa19 --- /dev/null +++ b/src/MetadataViewer/MetadataViewer.ts @@ -0,0 +1,100 @@ +/* + * Copyright (c) 2022 Samsung Electronics Co., Ltd. All Rights Reserved + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import * as vscode from 'vscode'; +import {getNonce} from '../Utils/external/Nonce'; + +/* istanbul ignore next */ +export class MetadataViewer { + private readonly _panel: vscode.WebviewPanel; + private _disposable: vscode.Disposable[]; + protected readonly _webview: vscode.Webview; + protected readonly _extensionUri: vscode.Uri; + + public constructor(panel: vscode.WebviewPanel, extensionUri: vscode.Uri) { + this._disposable = []; + this._webview = panel.webview; + this._panel = panel; + this._extensionUri = extensionUri; + } + + public initWebView() { + this._webview.options = this.getWebviewOptions(); + + // Register for an event when you receive a message from a web view + this.registerEventHandlers(); + } + + private getWebviewOptions(): vscode.WebviewOptions&vscode.WebviewPanelOptions { + return { + // Enable javascript in the webview + enableScripts: true, + // to prevent view to reload after loosing focus + retainContextWhenHidden: true + }; + } + + + public loadContent() { + this._getHtmlForWebview(this._extensionUri, this._panel); + } + + private async _getHtmlForWebview(extensionUri: vscode.Uri, panel: vscode.WebviewPanel) { + panel.webview.options = { + enableScripts: true, + }; + + const nonce = getNonce(); + const jsIndex = panel.webview.asWebviewUri( + vscode.Uri.joinPath(extensionUri, 'media', 'MetadataViewer', 'index.js')); + const cssIndex = panel.webview.asWebviewUri( + vscode.Uri.joinPath(extensionUri, 'media', 'MetadataViewer', 'style.css')); + const codiconsUri = panel.webview.asWebviewUri(vscode.Uri.joinPath( + extensionUri, 'node_modules', '@vscode/codicons', 'dist', 'codicon.css')); + + const htmlUri = vscode.Uri.joinPath(extensionUri, 'media', 'MetadataViewer', 'index.html'); + + let html = Buffer.from(await vscode.workspace.fs.readFile(htmlUri)).toString(); + html = html.replace(/\${nonce}/g, `${nonce}`); + html = html.replace(/\${webview.cspSource}/g, `${panel.webview.cspSource}`); + html = html.replace(/\${index.css}/g, `${cssIndex}`); + html = html.replace(/\${index.js}/g, `${jsIndex}`); + html = html.replace(/\${codicon.css}/g, `${codiconsUri}`); + panel.webview.html = html; + } + + public owner(panel: vscode.WebviewPanel) { + return this._panel === panel; + } + + private registerEventHandlers() { + // Handle messages from the webview + this._webview.onDidReceiveMessage( + _message => { + + }, + null, this._disposable); + } + + public disposeMetadataView() { + while (this._disposable.length) { + const x = this._disposable.pop(); + if (x) { + x.dispose(); + } + } + } +} diff --git a/src/MetadataViewer/MetadataViewerProvider.ts b/src/MetadataViewer/MetadataViewerProvider.ts new file mode 100644 index 00000000..692e8e5d --- /dev/null +++ b/src/MetadataViewer/MetadataViewerProvider.ts @@ -0,0 +1,158 @@ +/* + * Copyright (c) 2022 Samsung Electronics Co., Ltd. All Rights Reserved + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import * as vscode from 'vscode'; +import { Metadata } from '../MetadataManager/Metadata'; +import { PathToHash } from '../MetadataManager/PathToHash'; +import { MetadataViewer } from './MetadataViewer'; + +export class RelationViewerDocument implements vscode.CustomDocument { + private readonly _uri: vscode.Uri; + private _metadataViwer: MetadataViewer[]; + + + static async create(uri: vscode.Uri): + Promise> { + return new RelationViewerDocument(uri); + } + + private constructor(uri: vscode.Uri) { + this._uri = uri; + this._metadataViwer = []; + } + + public get uri() { + return this._uri; + } + + // CustomDocument implements + dispose(): void { + // NOTE panel is closed before document and this is just for safety + this._metadataViwer.forEach((view) => { + while (this._metadataViwer.length) { + view.disposeMetadataView(); + } + }); + this._metadataViwer = []; + } + + public openView(panel: vscode.WebviewPanel, extensionUri: vscode.Uri, fileUri: vscode.Uri) { + let view = new MetadataViewer(panel, extensionUri); + + view.initWebView(); + view.loadContent(); + this._metadataViwer.push(view); + + //메타데이터 정보를 가져오는 로직(Uri 인자를 이용하면 됨) + getMetadata(fileUri).then((metadata: any) => { + const payload: any = {}; + payload[metadata['name']] = metadata; + console.log(metadata); + //패널 타이틀 변경(적용되지 않음) + //panel.title = `Metadata: ${this._getNameFromPath(fileUri.toString())}`; + + //가져온 메타데이터를 웹뷰로 메세지를 보낸다. + panel.webview.postMessage({command:'showMetadata',metadata: payload}); + }); + + + + + panel.onDidDispose(() => { + // TODO make faster + this._metadataViwer.forEach((view, index) => { + if (view.owner(panel)) { + view.disposeMetadataView(); + this._metadataViwer.splice(index, 1); + } + }); + }); + + return view; + } +} + +export class MetadataViewerProvider implements + vscode.CustomReadonlyEditorProvider { + public static readonly viewType = 'one.viewer.metadata'; + + private _context: vscode.ExtensionContext; + + public static register(context: vscode.ExtensionContext): void { + const provider = new MetadataViewerProvider(context); + + const registrations = [ + vscode.window.registerCustomEditorProvider(MetadataViewerProvider.viewType, provider, { + webviewOptions: { + retainContextWhenHidden: true, + } + }), + vscode.commands.registerCommand( + 'one.viewer.metadata.showFromOneExplorer', + async (uri) => { + // If the method is executed in the ONE Explorer, change the uri instance. + const fileUri = uri.uri; + + vscode.commands.executeCommand( + 'vscode.openWith', fileUri, MetadataViewerProvider.viewType); + }), + vscode.commands.registerCommand( + 'one.viewer.metadata.showFromDefaultExplorer', + async (uri) => { + const fileUri = uri; + + vscode.commands.executeCommand( + 'vscode.openWith', fileUri, MetadataViewerProvider.viewType); + }) + // Add command registration here + ]; + + // supported file extension to show relations context menu + vscode.commands.executeCommand( + 'setContext', 'one.metadata.supportedFiles', + ['.tflite', '.pb', '.onnx', '.circle', '.log']); + + registrations.forEach(disposable => context.subscriptions.push(disposable)); + } + + constructor(private readonly context: vscode.ExtensionContext) { + this._context = context; + } + + // CustomReadonlyEditorProvider implements + async openCustomDocument( + uri: vscode.Uri, _openContext: {backupId?: string}, + _token: vscode.CancellationToken): Promise { + const document: RelationViewerDocument = await RelationViewerDocument.create(uri); + // NOTE as a readonly viewer, there is not much to do + + return document; + } + + // CustomReadonlyEditorProvider implements + async resolveCustomEditor( + document: RelationViewerDocument, webviewPanel: vscode.WebviewPanel, + _token: vscode.CancellationToken): Promise { + document.openView(webviewPanel, this._context.extensionUri, document.uri); + } +} + +async function getMetadata(uri: vscode.Uri) { + const instance = await PathToHash.getInstance(); + const hash = instance.getHash(uri); + const entry = await Metadata.getEntry(uri, hash); + return entry; +} diff --git a/src/MetadataViewer/example/MetadataExample.ts b/src/MetadataViewer/example/MetadataExample.ts new file mode 100644 index 00000000..8639aca8 --- /dev/null +++ b/src/MetadataViewer/example/MetadataExample.ts @@ -0,0 +1,42 @@ +/* +NOTE this is example logic is that you can get sample MetaData +TODO Replace this sample logic into real logic +*/ +export function getMetadata(path:any) { + return { + "test.log": { + "file-extension": "log", + "created-time": new Date().toLocaleString(), + "modified-time": new Date().toLocaleString(), + "is-deleted": false, + + "toolchain-version": "toolchain v1.3.0", + "onecc-version": "1.20.0", + "operations": { + "op-total": 4, + "ops": ['conv2d', 'relu', 'conv', 'spp'] + }, + "cfg-settings": { + "onecc": { + "one-import-tf": true, + "one-import-tflite": false, + "one-import-onnx": false, + "one-quantize":true + }, + "one-import-tf": { + "converter-version": "v2", + "input-array": "a", + "output-array": "a", + "input-shapes": "1,299,299" + }, + "one-quantize":{ + "quantized-dtype":'int16', + "input-data-format":'list', + "min-percentile":'11', + "max-percentile":'100', + "mode":'movingAvg', + } + } + } + }; +} diff --git a/src/RelationViewer/RelationViewer.ts b/src/RelationViewer/RelationViewer.ts new file mode 100644 index 00000000..bfb01185 --- /dev/null +++ b/src/RelationViewer/RelationViewer.ts @@ -0,0 +1,142 @@ +/* + * Copyright (c) 2022 Samsung Electronics Co., Ltd. All Rights Reserved + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import * as vscode from 'vscode'; +import { MetadataViewerProvider } from '../MetadataViewer/MetadataViewerProvider'; +import {Balloon} from '../Utils/Balloon'; +import {getNonce} from '../Utils/external/Nonce'; +import {getUri} from '../Utils/external/Uri'; +import {obtainWorkspaceRoot} from '../Utils/Helpers'; +import {getRelationData} from './example/RelationExample'; + +/* istanbul ignore next */ +export class RelationViewer { + private readonly _panel: vscode.WebviewPanel; + private _disposable: vscode.Disposable[]; + protected readonly _webview: vscode.Webview; + protected readonly _extensionUri: vscode.Uri; + + public constructor(panel: vscode.WebviewPanel, extensionUri: vscode.Uri) { + this._disposable = []; + this._webview = panel.webview; + this._panel = panel; + this._extensionUri = extensionUri; + } + + public initWebview() { + this._webview.options = this.getWebviewOptions(); + + // Register for an event when you receive a message from a web view + this.registerEventHandlers(this._panel); + } + + private getWebviewOptions(): vscode.WebviewOptions&vscode.WebviewPanelOptions { + return { + // Enable javascript in the webview + enableScripts: true, + // to prevent view to reload after loosing focus + retainContextWhenHidden: true + }; + } + + public loadContent() { + this._getHtmlForWebview(this._extensionUri, this._panel); + } + + private async _getHtmlForWebview(extensionUri: vscode.Uri, panel: vscode.WebviewPanel) { + panel.webview.options = { + enableScripts: true, + }; + + const nonce = getNonce(); + const scriptUri = getUri(panel.webview, extensionUri, ['media', 'RelationViewer', 'index.js']); + const styleUri = getUri(panel.webview, extensionUri, ['media', 'RelationViewer', 'style.css']); + + const codiconsUri = panel.webview.asWebviewUri(vscode.Uri.joinPath( + extensionUri, 'node_modules', '@vscode/codicons', 'dist', 'codicon.css')); + + + const htmlUri = vscode.Uri.joinPath(extensionUri, 'media', 'RelationViewer', 'index.html'); + let html = Buffer.from(await vscode.workspace.fs.readFile(htmlUri)).toString(); + html = html.replace(/\${nonce}/g, `${nonce}`); + html = html.replace(/\${webview.cspSource}/g, `${panel.webview.cspSource}`); + html = html.replace(/\${codicon.css}/g, `${codiconsUri}`); + html = html.replace(/\${scriptUri}/g, `${scriptUri}`); + html = html.replace(/\${styleUri}/g, `${styleUri}`); + panel.webview.html = html; + } + + public owner(panel: vscode.WebviewPanel) { + return this._panel === panel; + } + + private registerEventHandlers(panel: vscode.WebviewPanel) { + // Handle messages from the webview + this._webview.onDidReceiveMessage(message => { + let payload; + let fileUri: vscode.Uri; + let viewType: string = 'default'; + switch (message.type) { + case 'update': + // fileUri = vscode.Uri.file(obtainWorkspaceRoot() + '/' + message.path); + payload = getRelationData(message.path); + if (!payload) { + return Balloon.error('Invalid File Path, please check if file exists.', false); + } + panel.webview.postMessage({ + type: 'update', + payload: payload, + historyList: message.historyList, + isOpenHistoryBox: message.isOpenHistoryBox + }); + break; + case 'history': + // fileUri = vscode.Uri.file(obtainWorkspaceRoot() + '/' + message.path); + payload = getRelationData(message.path); + if (!payload) { + return Balloon.error('Invalid File Path, please check if file exists.', false); + } + panel.webview.postMessage( + {type: 'history', payload: payload, historyList: message.historyList}); + break; + case 'showMetadata': + fileUri = vscode.Uri.file(obtainWorkspaceRoot() + '/' + message.path); + // This code opens the metadata viewer directly. + // It is necessary if the code of the metadata viewer is added. + vscode.commands.executeCommand('vscode.openWith', fileUri, MetadataViewerProvider.viewType); + break; + case 'openFile': + if (message.path.split('.').slice(-1)[0] === 'circle') { + viewType = `one.viewer.circle`; + } + fileUri = vscode.Uri.file(obtainWorkspaceRoot() + '/' + message.path); + vscode.commands.executeCommand('vscode.openWith', fileUri, viewType); + break; + default: + break; + } + }, null, this._disposable); + } + + public disposeMetadataView() { + while (this._disposable.length) { + const x = this._disposable.pop(); + if (x) { + x.dispose(); + } + } + } +} diff --git a/src/RelationViewer/RelationViewerProvider.ts b/src/RelationViewer/RelationViewerProvider.ts new file mode 100644 index 00000000..2cdfec55 --- /dev/null +++ b/src/RelationViewer/RelationViewerProvider.ts @@ -0,0 +1,143 @@ +/* + * Copyright (c) 2022 Samsung Electronics Co., Ltd. All Rights Reserved + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import * as vscode from 'vscode'; + +import {getRelationData} from './example/RelationExample'; +import {RelationViewer} from './RelationViewer'; + +export class RelationViewerDocument implements vscode.CustomDocument { + private readonly _uri: vscode.Uri; + private _relationViewer: RelationViewer[]; + + static async create(uri: vscode.Uri): + Promise> { + return new RelationViewerDocument(uri); + } + + private constructor(uri: vscode.Uri) { + this._uri = uri; + this._relationViewer = []; + } + + public get uri() { + return this._uri; + } + + // CustomDocument implements + dispose(): void { + // NOTE panel is closed before document and this is just for safety + this._relationViewer.forEach((view) => { + while (this._relationViewer.length) { + view.disposeMetadataView(); + } + }); + this._relationViewer = []; + } + + public openView(panel: vscode.WebviewPanel, extensionUri: vscode.Uri, fileUri: vscode.Uri) { + let view = new RelationViewer(panel, extensionUri); + + view.initWebview(); + view.loadContent(); + this._relationViewer.push(view); + + const payload = getRelationData(fileUri); + + // Send a message the relation data to the web view + panel.webview.postMessage({type: 'create', payload: payload}); + + panel.onDidDispose(() => { + // TODO make faster + this._relationViewer.forEach((view, index) => { + if (view.owner(panel)) { + view.disposeMetadataView(); + this._relationViewer.splice(index, 1); + } + }); + }); + + return view; + } +} + +export class RelationViewerProvider implements + vscode.CustomReadonlyEditorProvider { + public static readonly viewType = 'one.viewer.relation'; + private _context: vscode.ExtensionContext; + + public static register(context: vscode.ExtensionContext): void { + const provider = new RelationViewerProvider(context); + + const registrations = [ + vscode.window.registerCustomEditorProvider(RelationViewerProvider.viewType, provider, { + webviewOptions: { + retainContextWhenHidden: true, + } + }), + vscode.commands.registerCommand( + 'one.viewer.relation.showFromDefaultExplorer', + async (uri) => { + const fileUri = uri; + + vscode.commands.executeCommand( + 'vscode.openWith', fileUri, RelationViewerProvider.viewType); + }), + vscode.commands.registerCommand( + 'one.viewer.relation.showFromOneExplorer', + async (uri) => { + // If the method is executed in the ONE Explorer, change the uri instance. + const fileUri = uri.uri; + + vscode.commands.executeCommand( + 'vscode.openWith', fileUri, RelationViewerProvider.viewType); + }) + // Add command registration here + ]; + + // supported file extension to show relations context menu + vscode.commands.executeCommand( + 'setContext', 'one.relation.supportedFiles', + ['.tflite', '.pb', '.onnx', '.circle', '.log']); + + registrations.forEach(disposable => context.subscriptions.push(disposable)); + } + + constructor(private readonly context: vscode.ExtensionContext) { + this._context = context; + } + + // CustomReadonlyEditorProvider implements + async openCustomDocument( + uri: vscode.Uri, _openContext: {backupId?: string}, + _token: vscode.CancellationToken): Promise { + const document: RelationViewerDocument = await RelationViewerDocument.create(uri); + // NOTE as a readonly viewer, there is not much to do + + // TODO handle dispose + // TODO handle file change events + // TODO handle backup + + return document; + } + + // CustomReadonlyEditorProvider implements + async resolveCustomEditor( + document: RelationViewerDocument, webviewPanel: vscode.WebviewPanel, + _token: vscode.CancellationToken): Promise { + document.openView(webviewPanel, this._context.extensionUri, document.uri); + } +} diff --git a/src/RelationViewer/example/RelationExample.ts b/src/RelationViewer/example/RelationExample.ts new file mode 100644 index 00000000..8be4ea37 --- /dev/null +++ b/src/RelationViewer/example/RelationExample.ts @@ -0,0 +1,48 @@ +/* +NOTE this is example logic is that you can get sample relation data +TODO Replace this sample logic into real logic +*/ +export function getRelationData(path:any) { + + const dummyData = { + "selected": "1", + "relation-data": [ + {"id": "1", "parent": "", "represent-idx": 0, "data-list": [{"name": "baseModelTestTflite123123.tflite", "path": "baseModelTestTflite123123.tflite", "is-deleted":false},{"name": "model.tflite", "path": "model.tflite", "is-deleted":true},{"name": "c.tflite", "path": "c.tflite"},{"name": "d.tflite", "path": "d.tflite", "is-deleted":false}]}, // TODO: id, parentid: hashId + {"id": "2", "parent": "1", "represent-idx": 0, "data-list": [{"name": "test1.circle", "path": "src/hello/test1.circle", "onecc-version": "1.0.0", "toolchain-version": "1.0.0", "is-deleted":false}]}, + {"id": "3", "parent": "2", "represent-idx": 0, "data-list": [{"name": "test2.circle", "path": "src/trudiv/model/test2.circle", "onecc-version": "1.0.0", "toolchain-version": "1.0.0", "is-deleted":false}]}, + {"id": "4", "parent": "1", "represent-idx": 0, "data-list": [{"name": "test1.log", "path": "test1.log", "onecc-version": "1.0.0", "toolchain-version": "1.0.0", "is-deleted":false}]}, + {"id": "5", "parent": "2", "represent-idx": 0, "data-list": [{"name": "test2.log", "path": "test2.log", "toolchain-version": "1.0.0", "is-deleted":false}]}, + {"id": "6", "parent": "4", "represent-idx": 0, "data-list": [{"name": "baseModelTestCircle.circle", "path": "baseModelTestCircle.circle", "is-deleted":false}]}, + {"id": "7", "parent": "6", "represent-idx": 0, "data-list": [{"name": "model.q8.circle", "path": "model.q8.circle", "onecc-version": "1.0.0", "toolchain-version": "1.0.0", "is-deleted":false}]}, + {"id": "8", "parent": "6", "represent-idx": 0, "data-list": [{"name": "pbTestCircle1.log", "path": "pbTestCircle1.log", "onecc-version": "1.0.0", "toolchain-version": "1.0.0", "is-deleted":false}]}, + {"id": "9", "parent": "7", "represent-idx": 0, "data-list": [{"name": "test_onnx.circle", "path": "hello/test_onnx.circle", "toolchain-version": "1.0.0", "is-deleted":false}]}, + {"id": "10", "parent": "7", "represent-idx": 0, "data-list": [{"name": "while_000.circle", "path": "while/while_000.circle", "onecc-version": "1.0.0", "toolchain-version": "1.0.0", "is-deleted":false}]}, + {"id": "11", "parent": "8", "represent-idx": 0, "data-list": [{"name": "e1.log", "path": "e1.circle", "onecc-version": "1.0.0", "toolchain-version": "1.0.0", "is-deleted":false}]}, + {"id": "12", "parent": "8", "represent-idx": 0, "data-list": [{"name": "e2.log", "path": "e2.circle", "onecc-version": "1.0.0", "toolchain-version": "1.0.0", "is-deleted":true},{"name": "e3.circle", "path": "e3.circle", "onecc-version": "1.2.0", "toolchain-version": "1.0.0", "is-deleted":false}]} + ] + } as any; + + for (const key in dummyData) { + if(key === 'relation-data'){ + for (const idx in dummyData['relation-data']) { + for (const key2 in dummyData['relation-data'][idx]) { + if(key2 === 'data-list'){ + for (let index = 0; index < dummyData['relation-data'][idx]['data-list'].length; index++) { + const element = dummyData['relation-data'][idx]['data-list'][index]; + for (const key3 in element) { + if(key3 === 'path'){ + if(element['path'] === path){ + dummyData['relation-data'][idx]['represent-idx'] = index; + dummyData['selected'] = dummyData['relation-data'][idx]['id']; + } + } + } + } + } + } + } + } + } + + return dummyData; +} \ No newline at end of file diff --git a/src/extension.ts b/src/extension.ts index 9a34841d..c8e920f5 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -25,6 +25,8 @@ import {MondrianEditorProvider} from './Mondrian/MondrianEditor'; import {OneTreeDataProvider} from './OneExplorer/OneExplorer'; import {PartEditorProvider} from './PartEditor/PartEditor'; import {PartGraphSelPanel} from './PartEditor/PartGraphSelector'; +import {RelationViewerProvider} from './RelationViewer/RelationViewerProvider'; +import {MetadataViewerProvider} from './MetadataViewer/MetadataViewerProvider'; import {ToolchainProvider} from './Toolchain/ToolchainProvider'; import {Logger} from './Utils/Logger'; @@ -70,6 +72,10 @@ export function activate(context: vscode.ExtensionContext) { CircleViewerProvider.register(context); + RelationViewerProvider.register(context); + + MetadataViewerProvider.register(context); + // returning backend registration function that will be called by backend extensions return backendRegistrationApi(); }