diff --git a/web/main.js b/web/main.js index e07318b..797c527 100644 --- a/web/main.js +++ b/web/main.js @@ -7,23 +7,26 @@ import getLocation from './location.js' const { scheme, host } = getLocation(window); const client = createClient(scheme, host); -const parse = function(blob) { +const parse = (blob) => { const parser = new DOMParser(); const doc = parser.parseFromString(blob, "text/xml"); return doc.documentElement; } -const loadDocument = function(config) { +const loadDocument = (config) => { return client.showWorkflow("/workflow/default", config); } -const loadGraph = function() { +let panzoomInstance; + +const loadGraph = () => { loadDocument().then((resp) => { try { const viewport = document.querySelector("#viewport"); viewport.innerHTML = ""; viewport.appendChild(parse(resp.data)); - panzoom(viewport, { minZoom: 0.01, zoomSpeed: 0.3, autocenter: true }).zoomAbs(0, 0, 0.1); + panzoomInstance = panzoom(viewport, { minZoom: 0.01, zoomSpeed: 0.3, autocenter: false }); + panzoomInstance.zoomAbs(0, 0, 0.1); } catch (error) { console.error(error); alert("An error occurred while loading the graph."); @@ -34,7 +37,7 @@ const loadGraph = function() { }) }; -const saveGraph = function() { +const saveGraph = () => { loadDocument({ responseType: "blob" }).then((resp) => { saveAs(resp.data, "workflow.svg", {type: "image/svg+xml"}); }).catch((error) => { @@ -43,11 +46,49 @@ const saveGraph = function() { }) } +const search = (text) => { + const svg = document.querySelector('svg'); + const textElements = svg.querySelectorAll('*'); + const matchingNodesSet = new Set(); + const lctext = text.toLowerCase(); + + for (let element of textElements) { + if (element.textContent.toLowerCase().includes(lctext)) { + let parent = element.parentElement; + while (parent) { + if (parent.classList.contains('node')) { + matchingNodesSet.add(parent); + break; + } + parent = parent.parentElement; + } + } + } + + return Array.from(matchingNodesSet); +} + +const zoomElement = (element) => { + const svg = document.querySelector('svg'); + const rect = element.getBBox(); + const point = svg.createSVGPoint(); + point.x = rect.x + rect.width / 2; + point.y = rect.y + rect.height / 2; + const globalPoint = point.matrixTransform(element.getScreenCTM()); + const viewportWidth = window.innerWidth || document.documentElement.clientWidth; + const viewportHeight = window.innerHeight || document.documentElement.clientHeight; + const panX = (viewportWidth / 2 - globalPoint.x); + const panY = (viewportHeight / 2 - globalPoint.y); + panzoomInstance.moveBy(panX, panY, false); +} + +// Write the initial layout of the application. document.querySelector('#app').innerHTML = `
@@ -55,10 +96,36 @@ document.querySelector('#app').innerHTML = `
` -document.querySelector('.reload').addEventListener('click', loadGraph); +// Focus the search box when shift and `/` keys are pressed simultaneously. +window.addEventListener('keydown', (event) => { + if (event.shiftKey && event.key === '/') { + event.preventDefault(); + const search = document.querySelector('.search'); + if (search) { + search.focus(); + } + } +}); -document.querySelector('.save').addEventListener('click', saveGraph); +let results; + +document.querySelector(".search").addEventListener("keydown", function(event) { + if (event.key == "Escape") { + event.preventDefault(); + this.value = ""; + } else if (event.key == "Enter") { + event.preventDefault(); + if (this.value.trim() != "") { + results = search(this.value); + if (results.length > 0) { + zoomElement(results[0]); + results.forEach((item) => console.log(this.value, item)); + } + } + } +}) +// Highlight graph nodes when they are clicked by the user. document.body.addEventListener('click', function(event) { const el = event.target.closest('.node'); if (el) { @@ -66,4 +133,7 @@ document.body.addEventListener('click', function(event) { } }); +document.querySelector('.reload').addEventListener('click', loadGraph); +document.querySelector('.save').addEventListener('click', saveGraph); + loadGraph(); diff --git a/web/style.css b/web/style.css index 05d4fb1..f814453 100644 --- a/web/style.css +++ b/web/style.css @@ -37,15 +37,30 @@ body { z-index: 1; } -.box {} +.box, #viewport { + width: 100%; + display: block; +} button { padding: 4px; - font-size: 11px; + font-size: 14px; border: 1px solid #333; cursor: pointer; } +input.search { + all: unset; + width: 40%; + margin-left: 8px; + background-color: white; + font-family: Arial, Helvetica, sans-serif; + padding: 4px; + border: 1px solid black; + font-size: 14px; +} + + /* SVG */ svg {