diff --git a/ui/app/assets/css/main.css b/ui/app/assets/css/main.css index c91fceb..2508502 100644 --- a/ui/app/assets/css/main.css +++ b/ui/app/assets/css/main.css @@ -214,5 +214,7 @@ html, body { } } -/* .fnLink { @apply font-mono; } */ -.unsafeFnLink { @apply text-red-700 dark:text-red-400 hover:font-bold hover:text-red-500 transition-colors; } +.unsafeFnLink { + @apply text-rose-700 hover:text-rose-500 font-bold transition-colors + dark:hover:text-rose-400 dark:hover:text-rose-300; +} diff --git a/ui/app/components/Flow.vue b/ui/app/components/Flow.vue index 72716b5..5dc9b3e 100644 --- a/ui/app/components/Flow.vue +++ b/ui/app/components/Flow.vue @@ -21,10 +21,12 @@ import { adtURL, type FlowOpts } from '~/lib/topbar'; import type { PanelContent } from '~/lib/panel'; import ELK from 'elkjs/lib/elk.bundled.js' import { NodeKind, Plot, PlotConfig, type IdToItem } from '~/utils/graph'; -import type { AdtOpts } from '~/lib/output/adt'; +import type { AdtClicked, AdtOpts } from '~/lib/output/adt'; +import { extractAdtFromIdField } from '~/lib/graph'; const flowOpts = defineModel('flowOpts', { required: true }); const adtOpts = defineModel('adtOpts', { required: true }); +const adtClicked = defineModel('adtClicked', { required: true }); const panelContent = defineModel('panelContent', { required: true }); const elk = new ELK() @@ -57,17 +59,26 @@ const data = ref(EMPTY_DATA); // Respond to node click. onNodeClick(event => { - const selected = data.value.id_to_item[event.node.id] - if (selected && selected.kind === NodeKind.Adt) { + const id = event.node.id + const selected = data.value.id_to_item[id] + const selectAdt = selected?.kind === NodeKind.Adt + const selectField = selected?.kind === NodeKind.Field + + if (selectAdt || selectField) { const name = selected.name + const adt = selectField ? extractAdtFromIdField(id) : name // Update adt data. - const url = adtURL(name) + const url = adtURL(adt) $fetch(url) .then(text => { // Update nodeItem here because panel reacts to the two values. - panelContent.value.nodeItem = name - adtOpts.value = { name, data: JSON.parse(text as string) } + panelContent.value.nodeItem = adt + // Update adt data. + adtOpts.value = { name: adt, data: JSON.parse(text as string) } + // Update adt clicked. + const click = { clickedAdt: adt, clickedField: selectField ? name : undefined } + Object.assign(adtClicked.value, click) }) .catch(err => console.log("Failed to fetch adt.json", err)) return diff --git a/ui/app/components/UPG.vue b/ui/app/components/UPG.vue index 9c6889d..2f29ff7 100644 --- a/ui/app/components/UPG.vue +++ b/ui/app/components/UPG.vue @@ -2,8 +2,13 @@
- + + + +
@@ -22,7 +27,7 @@ import type { FlowOpts, UnsafeFns } from "~/lib/topbar" import { type DataTags, } from '~/lib/output/tag'; import { Panel, toPanel, toPanelStr, type PanelContent } from "~/lib/panel" import { Crate, FLOW_OPTS, defaultCrateItemQuery, tagURL, toCrate, toViewTypes, unsafeFnsURL } from "~/lib/topbar"; -import type { AdtOpts } from "~/lib/output/adt"; +import type { AdtClicked, AdtOpts } from "~/lib/output/adt"; const router = useRouter(); const route = useRoute(); @@ -85,6 +90,18 @@ const downPanel = ref(initState.down); const flowOpts = ref(initState.flowOpts); const adtOpts = ref({}); +const adtClicked = ref({ open: false }) +watch(() => ({ + isAdtPanel: upPanel.value === Panel.Adt || downPanel.value === Panel.Adt, + isClicked: adtClicked.value.clickedAdt || adtClicked.value.clickedField, + adt: adtClicked.value +}), ({ isAdtPanel, isClicked, adt }) => { + // Auto open adt panel when side panels doesn't show adt panel, and user clicked adt or field. + if (!isAdtPanel && isClicked) { + adtClicked.value = { open: true, lastClickedAdt: adt.clickedAdt, lastClickedField: adt.clickedField } + } +}) + const share = ref(false) watch(share, val => { if (!val) return; diff --git a/ui/app/components/code/AdtPopup.vue b/ui/app/components/code/AdtPopup.vue new file mode 100644 index 0000000..0159113 --- /dev/null +++ b/ui/app/components/code/AdtPopup.vue @@ -0,0 +1,262 @@ + + + diff --git a/ui/app/lib/graph.ts b/ui/app/lib/graph.ts index a2ffe56..bd4aa31 100644 --- a/ui/app/lib/graph.ts +++ b/ui/app/lib/graph.ts @@ -31,6 +31,10 @@ export function idField(adt: string, field: string) { return `field@${field}@adt@${adt}` } +export function extractAdtFromIdField(id: string) { + return id.match(/adt@([^@]+)$/)?.[1] ?? "" +} + export function isAdtID(id: string) { return id.startsWith("adt@") } diff --git a/ui/app/lib/output/adt.ts b/ui/app/lib/output/adt.ts index e95927e..86a1ac3 100644 --- a/ui/app/lib/output/adt.ts +++ b/ui/app/lib/output/adt.ts @@ -1,10 +1,11 @@ import type { SearchFnItem } from "../topbar" -export type AdtOpts = { - name?: string, data?: DataAdt, -} +export type AdtOpts = { name?: string, data?: DataAdt } -export const ADT_OPTs: AdtOpts = {} +export type AdtClicked = { + open: boolean, clickedAdt?: string, clickedField?: string, + lastClickedAdt?: string, lastClickedField?: string, +} export type DataAdt = { name: string, diff --git a/ui/app/utils/graph.ts b/ui/app/utils/graph.ts index d654fc9..eb9db7d 100644 --- a/ui/app/utils/graph.ts +++ b/ui/app/utils/graph.ts @@ -171,6 +171,7 @@ export class PlotConfig { return { id, label, width: dim.width, height: dim.height, parentNode: parent, position: { x, y }, class: this.nodeClass(id), type: "no-handle", + selectable: false, draggable: false, } } @@ -182,6 +183,7 @@ export class PlotConfig { return { id, label, width: dim.width, height: dim.height, parentNode: parent, position: { x, y }, class: this.nodeClass(id), type: "no-handle", + selectable: false, draggable: false, } } diff --git a/ui/nuxt.config.ts b/ui/nuxt.config.ts index 685b4e0..83627fa 100644 --- a/ui/nuxt.config.ts +++ b/ui/nuxt.config.ts @@ -31,6 +31,10 @@ export default defineNuxtConfig({ 'tabler:search', // search 'tabler:sitemap', // sidebar 'tabler:browser-share', // share + 'tabler:hexagon-letter-c-filled', // constructor + 'tabler:hexagon-letter-f-filled', // field + 'tabler:hexagon-letter-a-filled', // argument + 'tabler:hexagon-letter-l-filled', // locals ], } },