diff --git a/src/components/DBOverview.vue b/src/components/DBOverview.vue index 141d8955af6b725f71564d595c5c953b05ce32de..97bfef4fd8bb1919d1e9b375616b62a4b49ed405 100644 --- a/src/components/DBOverview.vue +++ b/src/components/DBOverview.vue @@ -1,19 +1,21 @@ <script setup> import {useAPI} from "@/services/api"; -import {computed, onMounted, onUnmounted, ref} from "vue"; +import {computed, onMounted, onUnmounted, ref, useTemplateRef} from "vue"; import VueMermaidString from "vue-mermaid-string"; import QueryDBSchemaView from "@/components/subcomponents/view/QueryDBSchemaView.vue"; import {useConstantsStore} from "@/services/constantsStore"; import SourceDBSelector from "@/components/helpers/SourceDBSelector.vue"; import MarkFK from "@/components/subcomponents/view/MarkFK.vue"; import * as d3 from "d3" +import {useSelectionStore} from "@/services/selectionStore"; const api = useAPI() const mermaidText = ref("") const loading = ref(false) const constants = useConstantsStore() +const selection = useSelectionStore() const sourceDB = ref(constants.dbSources[0]) @@ -23,16 +25,22 @@ async function getMermaid(selection) { loading.value = true const params = {source: sourceDB.value, version: "mermaid"}; - const r = selection == null ? await api.makeERD(params) : await api.makeERD(params, { selection}) + const r = selection == null ? await api.makeERD(params) : await api.makeERD(params, {selection}) if (r?.data) { + console.log('updated mermaid text', r.data.mermaid) + mermaidText.value = r.data.mermaid - new Promise(() => setTimeout(unlockSVG, 500)) // never do this again + // new Promise(() => setTimeout(unlockSVG, 500)) // never do this again } loading.value = false } -function nodeClick(nodeId) { - console.log(nodeId) +function classLabelClick(nodeId) { + const source = sourceDB.value + if (!!source && !!nodeId) { + const [schema, name] = nodeId.split("_") + selection.toggleFavorite({source, schema, name}) + } } @@ -50,24 +58,102 @@ window.addEventListener('load', function () { }); }); */ -const mermaidSVG = ref(null) -function unlockSVG() { - d3.select(".make-zoomy div svg").attr("height", "100vh").attr("preserveAspectRatio", "xMidYMin meet") + +const mermaidSVG = useTemplateRef("mermaidSVG") +const zoomyDiv = useTemplateRef("zoomyDiv") + +function injectStuff() { + const svgElement = mermaidSVG.value?.$el.querySelector('svg') + if (svgElement) { + //const nodes = svgElement.querySelectorAll('g g.root g.nodes g.node') + + const classTitles = svgElement.querySelectorAll('g g.root g.nodes g.node g.label .classTitle div span.nodeLabel') + //classTitles.forEach(el => { + // const className = el.innerHTML + // el.innerHTML = `<a>${className}</a>` + // //el.innerHTML = `<a>${className}</a>` + // el.addEventListener('click', e => { + // console.log('clicked on title of', className, e) + // }) + // el.classList.add('clickable') + // console.log('inserted some html in', el) + //}) + //const Cls = Vue.extend(CheckboxButton) + //const instance = new Cls({vuetify, propsData: {trueIcon: "mdi-star", falseIcon: "mdi-star-outline"}}) + //instance.$mount() + + //const favBtn = '<v-checkbox-btn density="compact" true-icon="mdi-star" false-icon="mdi-star-outline"></v-checkbox-btn>' //'<v-checkbox-btn density="compact" true-icon="mdi-star" false-icon="mdi-star-outline" v-model="selectionStore.favoritedTables" :value="item.tableIdentifier" @click.stop=""></v-checkbox-btn>' + classTitles.forEach((el) => { + const className = el.innerHTML + el.classList.add('makeClickable') + //const a = document.createElement("a", {href: "#"}) + //a.innerHTML = "fav" + el.addEventListener('click', e => { + classLabelClick(className) + console.log('clicked on title of', className, e) + }) + console.log('inserted some html in', el) + }) + } } -onUnmounted(() => { - var g = d3.select(".make-zoomy"); - g.on(".zoom", null) +const resizeSVG = () => { + const svgElement = mermaidSVG.value?.$el.querySelector('svg') + const container = zoomyDiv.value + + if (svgElement && container) { + const width = container.clientWidth + const height = container.clientHeight + + // Set the width and height of the SVG to match the container's dimensions + d3.select(svgElement) + .attr('width', width) + .attr('height', height) + .attr('viewBox', `0 0 ${width} ${height}`) + .attr('preserveAspectRatio', 'xMidYMin meet') + } +} + +const observer = new MutationObserver((mr, mo) => { + const svgElement = mermaidSVG.value?.$el.querySelector('svg') + if (svgElement) { + // Reapply zoom behavior and resize the SVG after each diagram update + injectStuff() + initializeZoom(svgElement) + resizeSVG() + } }) -onMounted(() => { - var g = d3.select(".make-zoomy"); - //gs.each(g => { - var zoom = d3.zoom().on("zoom", function (event) { - g.select("div svg g").attr("transform", event.transform); + +const initializeZoom = (svgElement) => { + const gElement = d3.select(svgElement.querySelector('g')) + + // Define zoom behavior + const zoom = d3.zoom().on('zoom', (event) => { + gElement.attr('transform', event.transform) // Apply zoom/pan transformation to the <g> element }); - g.call(zoom); - //}) + + // Apply the zoom behavior to the SVG + d3.select(svgElement).call(zoom) +} + + +onMounted(() => { + // var g = d3.select(".make-zoomy") + // //gs.each(g => { + // var zoom = d3.zoom().on("zoom", function (event) { + // g.select("div svg g").attr("transform", event.transform) + // }); + // g.call(zoom) + // //}) + observer.observe(mermaidSVG.value.$el, {childList: true}) + window.addEventListener("resize", resizeSVG) +}) +onUnmounted(() => { + // var g = d3.select(".make-zoomy") + // g.on(".zoom", null) + window.removeEventListener("resize", resizeSVG) + observer.disconnect() }) async function applyFilter(selection) { @@ -104,9 +190,9 @@ async function applyFilter(selection) { <v-divider></v-divider> <v-row class="flex-grow-1 fill-height"> <v-col> - <div class="make-zoomy"> - <vue-mermaid-string :value="mermaidText" :options="{securityLevel: 'loose'}" - @node-click="nodeClick" ref="mermaidSVG"></vue-mermaid-string> + <div class="make-zoomy" style="height: 100%;" ref="zoomyDiv"> + <vue-mermaid-string :value="mermaidText" :options="{securityLevel: 'loose', er: {useMaxWidth: false}}" + ref="mermaidSVG"></vue-mermaid-string> </div> </v-col> </v-row> @@ -122,6 +208,7 @@ async function applyFilter(selection) { </template> <style scoped> -.make-zoomy { +.v-container >>> .makeClickable { + cursor: pointer; } </style> diff --git a/src/components/StatusBar.vue b/src/components/StatusBar.vue index 3e54c7245a2a1b047a8aaf8e702f92d1fd0830d3..c32a3accc2c9ecfe5b7d54e2a20c26393dfc705a 100644 --- a/src/components/StatusBar.vue +++ b/src/components/StatusBar.vue @@ -34,7 +34,7 @@ async function onResetAction() { <v-app-bar-title>MitM Exporter</v-app-bar-title> - <v-select class="pa-2 align-self-center" :items="mitms" v-model="selectedMitM" variant="plain" hide-details> + <v-select class="pa-2 align-self-center flex-grow-0" :items="mitms" v-model="selectedMitM" variant="plain" hide-details> </v-select> <v-spacer></v-spacer> diff --git a/src/services/selectionStore.ts b/src/services/selectionStore.ts index 7e2945f5352b79a987f39a84242d6b9d8b413857..e0e57b82e5a6b23f42ccb3550a0864a9f3810b95 100644 --- a/src/services/selectionStore.ts +++ b/src/services/selectionStore.ts @@ -2,6 +2,7 @@ import {defineStore} from "pinia"; import {computed, ref} from "vue"; import {MitM, MitMDefinition, TableIdentifier, useAPI} from "@/services/api"; import {useConstantsStore} from "@/services/constantsStore"; +import {isSameTID} from "@/services/utils"; export const useSelectionStore = defineStore('selection', () => { @@ -20,17 +21,19 @@ export const useSelectionStore = defineStore('selection', () => { function toggleFavorite(table: TableIdentifier) { const favs = favoritedTables.value - if (favs.includes(table)) - favs.splice(favs.indexOf(table), 1) + const idx = favs.findIndex(tid => isSameTID(tid, table)) + if (idx >= 0) + favs.splice(idx, 1) else favs.push(table) } function updateFavorite(table: TableIdentifier, newState: boolean = true) { const favs = favoritedTables.value - if (favs.includes(table)) { + const idx = favs.findIndex(tid => isSameTID(tid, table)) + if (idx >= 0) { if (!newState) - favs.splice(favs.indexOf(table), 1) + favs.splice(idx, 1) } else if (newState) favs.push(table) }