UNPKG

doric-framework

Version:

A column-based widget UI framework for Vue 3

1 lines 52.7 kB
{"version":3,"file":"doric-framework.umd.cjs","sources":["../lib/doric.ts","../lib/components/WidgetConfig.vue","../lib/components/ScopedComponent.vue","../lib/DoricFramework.vue","../lib/useDoricInputOutput.ts"],"sourcesContent":["import type {\n Input,\n ValidatedWidget,\n Widget,\n WidgetInputState,\n Inputs,\n ValidatedInputs,\n Workspace,\n ValidatedWorkspace,\n DefaultLabels,\n UseDoricInputOptions,\n} from \"./types\"\nimport {\n defineStore\n} from 'pinia'\nimport { nextTick } from 'vue'\n\nlet defaultLabels: DefaultLabels = {}\nconst setDefaultLabels = (labels: DefaultLabels) => {\n defaultLabels = labels\n}\n\n// WORKSPACE STORE / WIDGETS STATE -----------------------------------------------------------------\n\nconst useDoricStore = defineStore('doric-workspace', {\n state: () => {\n return {\n columns: [] as ValidatedWorkspace,\n }\n },\n actions: {\n insertColumn(columnIndex: number) {\n this.columns = [...this.columns.slice(0, columnIndex), [], ...this.columns.slice(columnIndex)]\n },\n removeColumn(columnIndex: number) {\n this.columns[columnIndex].forEach(w => {\n this.removeWidget(w.id)\n })\n this.columns = [...this.columns.slice(0, columnIndex), ...this.columns.slice(columnIndex + 1)]\n },\n addWidget(widget: Widget, column: number) {\n // Add widget to workspace\n const validatedWidget = getValidatedWidget(widget)\n const validatedUniqueWidget = widgetWithUniqueId(validatedWidget, this.widgetIds)\n this.columns[column] = [...this.columns[column], validatedUniqueWidget]\n },\n removeWidget(widgetId: string) {\n const widget = this.widgets.find(w => w.id === widgetId)\n if (!widget) {\n throw new Error(`Widget with id \"${widgetId}\" not found`)\n }\n // Remove widget from all subscriptions\n this.widgets.forEach(w => {\n Object.keys(w.inputs).forEach(key => {\n w.inputs[key].subscriptions = w.inputs[key].subscriptions.filter(ws => ws !== widgetId)\n })\n })\n // Remove widget from workspace\n this.columns = this.columns.map(c => c.filter(w => w.id !== widgetId))\n },\n },\n getters: {\n workspaceShape: (state) => {\n return state.columns.map(column =>\n column.map(widget => ({\n id: widget.id,\n type: widget.type,\n label: widget.label,\n }))\n )\n },\n widgetIds: (state) => {\n return state.columns.flat().map(w => w.id)\n },\n widgets: (state) => {\n return state.columns.flat()\n },\n getSubscribers: (state) => (widgetId: string, key: string) => {\n // All widgets with an input key subscribed to widgetId\n return state.columns.flat().filter(w =>\n w.id !== widgetId &&\n key in w.inputs && (\n w.inputs[key].subscriptionState === \"all\" || (\n w.inputs[key].subscriptionState === \"some\" &&\n w.inputs[key].subscriptions.includes(widgetId)\n )\n )\n )\n },\n sharedParameters: (state) => {\n const allWidgets = state.columns.flat()\n const allInputs = allWidgets.map(w => Object.keys(w.inputs).map(key => ({ widgetId: w.id, key, input: w.inputs[key] }))).flat()\n const sharedInputs: { widgetId: string, key: string, value: string }[] = allInputs\n .filter(i => i.input.shared)\n .map(i => ({ widgetId: i.widgetId, key: i.key, value: i.input.value }))\n return Object.fromEntries(sharedInputs.map(i => [`${i.widgetId}.${i.key}`, i.value]))\n },\n }\n})\n\n\n// VALIDATION -------------------------------------------------------------------------------------\n\nconst getValidatedInputs: (i: any) => ValidatedInputs = (i) => {\n if (!i) {\n return {}\n }\n\n const validatedInputs: ValidatedInputs = {}\n // Ensure that inputs have a value, shared, and subscriptions field, create them if not\n Object.keys(i).forEach(key => {\n validatedInputs[key] = Object.assign({\n value: \"\",\n shared: false,\n subscriptions: [],\n subscriptionState: i[key]?.subscriptions?.length ? \"some\" : \"all\",\n }, i[key])\n })\n return validatedInputs\n}\n\nconst getValidatedWidget: (w: any) => ValidatedWidget = (w) => {\n // Note: We don't check that the widget type exists, we handle that case in the display\n if (!(\"type\" in w) || typeof w.type !== \"string\") {\n console.error(\"Widget is missing a type or the type is not a string:\\n\", w)\n throw new Error(`Widget is missing a type or the type is not a string`)\n }\n\n return {\n type: w.type,\n id: w?.id || \"\",\n label: w?.label || (w.type in defaultLabels ? defaultLabels[w.type] : w.type),\n inputs: getValidatedInputs(w?.inputs || {}),\n }\n}\n\nconst widgetWithUniqueId: (\n w: ValidatedWidget,\n widgetIds: string[]\n) => ValidatedWidget = (w: ValidatedWidget, widgetIds: string[]) => {\n if (!w.id || widgetIds.includes(w.id)) {\n const newW = { ...w }\n // Get lowest available id\n const prefix = w.type.replace(\"-widget\", \"\")\n const ids = widgetIds.filter(id => id.startsWith(prefix))\n .map(id => parseInt(id.replace(prefix + \"-\", \"\")))\n .filter(id => !isNaN(id))\n const newId = Math.max(-1, ...ids) + 1\n newW[\"id\"] = `${prefix}-${newId}`\n return newW\n }\n else if (w.id === w.type) {\n const newW = { ...w, id: `${w.type}-0` }\n return widgetWithUniqueId(newW, widgetIds)\n }\n return w\n}\n\n\n// WORKSPACE --------------------------------------------------------------------------------------\n\nconst getWorkspaceShape = () => {\n const store = useDoricStore()\n return store.workspaceShape\n}\n\nconst setWorkspace = (newWorkspace: unknown) => new Promise<void>((resolve, reject) => {\n const store = useDoricStore()\n\n // First check that every widget has \"subscriptions\" and \"inputs\" fields\n const validatedWidgetIds: string[] = []\n\n if (!Array.isArray(newWorkspace)) {\n reject(\"Workspace is not an array\")\n return\n }\n if (!newWorkspace.every(column => Array.isArray(column))) {\n reject(\"Workspace is not an array of arrays\")\n return\n }\n if (!newWorkspace.flat().every(widget => typeof widget === \"object\")) {\n reject(\"Widgets in new Workspace are not all objects\")\n return\n }\n const validatedNewColumns = newWorkspace.map(c =>\n c.map((widget: object) => {\n const validatedWidget = getValidatedWidget(widget)\n const validatedUniqueWidget = widgetWithUniqueId(validatedWidget, validatedWidgetIds)\n validatedWidgetIds.push(validatedUniqueWidget.id)\n return validatedUniqueWidget\n })\n )\n\n // If we give a whole new workspace, we need\n // to clear the current widget Ids so that the\n // whole structure gets rebuilt. Vue's `nextTick`\n // allows the refresh to happen. Incremental\n // workspace updates need to be handled differently.\n store.columns = []\n nextTick(() => {\n store.columns = validatedNewColumns\n resolve()\n })\n})\n\nconst insertColumn = (columnIndex: number) => {\n const store = useDoricStore()\n store.insertColumn(columnIndex)\n}\n\nconst removeColumn = (columnIndex: number) => {\n const store = useDoricStore()\n store.removeColumn(columnIndex)\n}\n\nconst getWidget = (widgetId: string) => {\n const store = useDoricStore()\n const widget = store.widgets.find(w => w.id === widgetId)\n if (!widget) {\n throw new Error(`Widget with id \"${widgetId}\" not found`)\n }\n return widget\n}\nconst getWidgetIds = () => {\n const store = useDoricStore()\n return store.widgetIds\n}\n\nconst addWidget = (widget: Widget, column: number) => {\n const store = useDoricStore()\n store.addWidget(widget, column)\n}\n\nconst removeWidget = (widgetId: string) => {\n const store = useDoricStore()\n store.removeWidget(widgetId)\n}\n\nconst moveWidget = (widgetId: string, newColumnIndex: number, newRowIndex: number) => {\n const store = useDoricStore()\n const widget = store.widgets.find(w => w.id === widgetId)\n if (!widget) {\n throw new Error(`Widget with id \"${widgetId}\" not found`)\n }\n store.columns = store.columns.map(column => {\n return column.filter(w => w.id !== widgetId)\n }).map((column, i) => {\n if (i === newColumnIndex) {\n return [...column.slice(0, newRowIndex), widget, ...column.slice(newRowIndex)]\n }\n return column\n })\n}\n\n\nconst sharedParameters = () => {\n const store = useDoricStore()\n return store.sharedParameters\n}\n\nconst pushWorkspaceState = (stateArray: WidgetInputState[]) => {\n const store = useDoricStore()\n stateArray.forEach(({ widgetId, key, value }) => {\n const widget = store.widgets.find(w => w.id === widgetId)\n if (!widget) {\n console.error(`Widget with id \"${widgetId}\" not found`)\n return\n }\n if (!(\"inputs\" in widget)) {\n console.error(`Widget with id \"${widgetId}\" has no inputs`)\n return\n }\n if (!(key in (widget?.inputs || {}))) {\n console.error(`Widget with id \"${widgetId}\" has no input \"${key}\"`)\n return\n }\n widget.inputs[key].value = value\n })\n}\n\n\n// INPUTS AND OUTPUTS -----------------------------------------------------------------------------\n\nconst primitiveTypes = new Set([\"string\", \"number\", \"boolean\"])\nconst getUseDoricOutput = (widgetId: string, key: string) => (value: any) => {\n const store = useDoricStore()\n\n // Unwrap reactive objects\n if (value instanceof Object && \"value\" in value) {\n const inspection = Object.getOwnPropertyDescriptor(value, \"value\") || {}\n if (\"get\" in inspection && \"set\" in inspection) {\n value = value.value\n }\n }\n\n // Ensure that only primitives are passed\n if (!(primitiveTypes.has(typeof value))) {\n console.error(`Widget \"${widgetId}\" tried to emit a non-primitive value to \"${key}\". Only strings, numbers, and booleans are supported.`)\n return\n }\n\n // Get widgets that are subscribed to our output key on our widget\n const widgets = store.getSubscribers(widgetId, key)\n // Loop through this filtered list and update each widget's input value\n widgets.forEach(w => {\n if (!(key in (w?.inputs || {}))) {\n console.error(`Widget subscribes to \"${key}\" but has no listener. This may be a mistake in the workspace configuration or the widget is missing a 'useDoricInput' declaration.`)\n return\n }\n w.inputs[key].value = value\n })\n}\n\nconst getUseDoricInput = (widgetId: string, key: string, options: UseDoricInputOptions) => {\n const store = useDoricStore()\n\n const widget = store.widgets.find(w => w.id === widgetId)\n if (!widget) {\n throw new Error(`Widget with id \"${widgetId}\" not found`)\n }\n\n // Ensure that the input exists\n if (!widget.inputs?.[key]) {\n widget.inputs[key] = {\n value: \"\",\n shared: options?.shared || false,\n subscriptions: [],\n subscriptionState: \"all\",\n }\n }\n\n // Return reactive object\n return {\n get value() {\n return widget.inputs[key].value\n },\n set value(newValue) {\n // Ensure that only primitives are passed\n if (!(primitiveTypes.has(typeof newValue))) {\n console.error(`Widget \"${widgetId}\" tried to give input \"${key}\" a non-primitive value. Only strings, numbers, and booleans are supported.`)\n return\n }\n widget.inputs[key].value = newValue\n }\n }\n}\n\n\n// WORKSPACE EXPORT -------------------------------------------------------------------------------\n\nconst exportWorkspace: () => Workspace = () => {\n const store = useDoricStore()\n // Deep clone the workspace using stringification trick\n const workspace: Workspace = JSON.parse(JSON.stringify(store.columns))\n\n // Reduce inputs in workspace to only the necessary information:\n // - input.value is only included if not empty (falsy)\n // - input.subscriptions is only included if state is \"some\"\n // - input.shared is only included if true (not falsy)\n workspace.forEach((column, columnIndex) => {\n column.forEach((widget, widgetIndex) => {\n if (widget.inputs && Object.keys(widget.inputs).length > 0) {\n const inputs = widget.inputs as Inputs\n Object.keys(inputs).forEach(inputKey => {\n const newInput: Input = {}\n\n if (inputs[inputKey].value) {\n newInput[\"value\"] = inputs[inputKey].value\n }\n\n if (inputs[inputKey].shared) {\n newInput[\"shared\"] = true\n }\n\n if (inputs[inputKey].subscriptionState === \"all\" || inputs[inputKey].subscriptionState === \"none\") {\n newInput[\"subscriptionState\"] = inputs[inputKey].subscriptionState\n }\n else {\n newInput[\"subscriptionState\"] = \"some\"\n newInput[\"subscriptions\"] = inputs[inputKey].subscriptions\n }\n\n (workspace[columnIndex][widgetIndex].inputs as Inputs)[inputKey] = newInput\n })\n }\n })\n })\n return workspace\n}\n\n\n// EXPORTS ---------------------------------------------------------------------------------------\n\nexport {\n getWorkspaceShape,\n getWidget,\n getWidgetIds,\n setWorkspace,\n getUseDoricInput,\n getUseDoricOutput,\n insertColumn,\n removeColumn,\n addWidget,\n removeWidget,\n moveWidget,\n pushWorkspaceState,\n sharedParameters,\n setDefaultLabels,\n exportWorkspace,\n}","<script lang=\"ts\" setup>\nimport {\n ref,\n} from 'vue'\nimport {\n getWidget,\n} from '../doric'\n\nconst props = defineProps({\n widgetId: {\n type: String,\n required: true,\n }\n})\nconst emit = defineEmits([\n 'setSubscriptionMode',\n])\n\nconst widget = getWidget(props.widgetId)\n\nconst toggleShared = (key: string) => {\n widget.inputs[key].shared = !widget.inputs[key].shared\n}\n\nconst subscriptionMode = ref(\"\")\nconst toggleSubscriptionMode = (inputKey: string) => {\n if (subscriptionMode.value === inputKey) {\n // null value unsets\n emit(\"setSubscriptionMode\")\n subscriptionMode.value = \"\"\n }\n else {\n emit(\"setSubscriptionMode\", props.widgetId, inputKey)\n subscriptionMode.value = inputKey\n }\n}\nconst turnOffSubscriptionMode = () => {\n emit(\"setSubscriptionMode\")\n subscriptionMode.value = \"\"\n}\n</script>\n\n<template>\n <div v-if=\"Object.keys(widget.inputs).length === 0\">This widget does not declare any inputs.</div>\n <div v-else>\n <div>\n <div v-for=\"(key, index) in Object.keys(widget.inputs)\" :key=\"key\" class=\"relative flex flex-col p-2\" :class=\"{'border-t-2': index > 0}\">\n <span class=\"font-bold text-blue-800 text-sm\">\n {{ key }}\n </span>\n\n <div class=\"table w-full\">\n <div class=\"table-row\">\n <div class=\"table-cell pb-1 pr-2\">Value:</div>\n <div class=\"table-cell pb-1\">\n <input type=\"text\" v-model=\"widget.inputs[key].value\" />\n </div>\n </div>\n <div class=\"table-row\">\n <div class=\"table-cell pb-1 pr-2\">Subscriptions:</div>\n <div class=\"table-cell pb-1\">\n <div class=\"flex flex-row\">\n <div class=\"flex justify-center items-center\">\n <select v-model=\"widget.inputs[key].subscriptionState\" @change=\"turnOffSubscriptionMode\">\n <!-- options for SubscriptionState -->\n <option value=\"none\">None</option>\n <option value=\"all\">All</option>\n <option value=\"some\">Some</option>\n </select>\n </div>\n <div class=\"flex justify-center items-center pl-1\">\n <button v-show=\"widget.inputs[key].subscriptionState === 'some'\" class=\"subscription-button\"\n :class=\"{ active: subscriptionMode === key }\" @click=\"toggleSubscriptionMode(key)\">\n <!-- `viewfinder-circle` icon from https://heroicons.com/, MIT license -->\n <svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\"\n stroke=\"currentColor\" class=\"w-5 h-5\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\"\n d=\"M7.5 3.75H6A2.25 2.25 0 003.75 6v1.5M16.5 3.75H18A2.25 2.25 0 0120.25 6v1.5m0 9V18A2.25 2.25 0 0118 20.25h-1.5m-9 0H6A2.25 2.25 0 013.75 18v-1.5M15 12a3 3 0 11-6 0 3 3 0 016 0z\" />\n </svg>\n </button>\n </div>\n </div>\n </div>\n </div>\n <div class=\"table-row\">\n <div class=\"table-cell pb-1 pr-2\">Share:</div>\n <div class=\"table-cell pb-1 truncate\">\n <div class=\"flex flex-row justify-between shared-toggle\">\n <input style=\"display: none;\" type=\"checkbox\" :id=\"`${widgetId}.${key}`\" :checked=\"widget.inputs[key].shared\"\n @change=\"toggleShared(key)\" />\n <label :for=\"`${widgetId}.${key}`\">\n <div class=\"dot\">\n </div>\n </label>\n </div>\n </div>\n </div>\n </div>\n </div>\n </div>\n </div>\n</template>\n\n<style lang=\"scss\" scoped>\n.title {\n font-weight: bold;\n margin-right: 1rem;\n}\n\nbutton.subscription-button {\n // reset all styles\n all: initial;\n}\n\nbutton.subscription-button,\nselect {\n border: 1px solid black;\n @apply box-border h-8 px-2 bg-gray-50 cursor-pointer text-sm font-medium text-gray-900 rounded border border-gray-200 select-none;\n\n &:hover {\n @apply bg-gray-100 text-blue-700 border-gray-300;\n }\n\n &:active {\n @apply bg-gray-200 border-blue-400;\n }\n\n &:focus {\n @apply border-blue-400;\n }\n\n &.active {\n @apply bg-blue-100 text-blue-700 border-blue-300;\n\n &:hover {\n @apply bg-blue-200 border-blue-400;\n }\n }\n}\n\ninput[type=\"text\"] {\n @apply inline-block w-full p-0.5 border border-gray-300 text-gray-900 text-sm rounded block py-1 px-2\n}\n\n.shared-toggle input {\n &+label {\n @apply flex items-center p-1 cursor-pointer bg-gray-300 relative w-8 rounded-full;\n\n &:hover {\n @apply bg-gray-200;\n }\n\n .dot {\n @apply relative bg-white border border-gray-200 w-3 h-3 rounded-full transition;\n }\n }\n\n &:checked+label {\n @apply bg-blue-500 border-blue-300;\n\n &:hover {\n @apply bg-blue-400;\n }\n\n .dot {\n transform: translateX(100%);\n }\n }\n}\n</style>\n","<script setup lang=\"ts\">\nimport type {\n UseDoricInputOptions,\n UseDoricInputFunction,\n UseDoricOutputFunction,\n} from '../types'\nimport { Component, provide } from 'vue'\nimport {\n getUseDoricInput,\n getUseDoricOutput,\n} from '../doric'\n\nconst props = defineProps<{\n widget: Component\n widgetId: string\n}>()\n\nprovide<UseDoricOutputFunction>('useDoricOutput', (inputKey: string) => getUseDoricOutput(props.widgetId, inputKey))\nprovide<UseDoricInputFunction>('useDoricInput', (inputKey: string, options: UseDoricInputOptions) => getUseDoricInput(props.widgetId, inputKey, options))\n</script>\n\n<template>\n <component :is=\"widget\" />\n</template>","<script setup lang=\"ts\">\nimport type {\n WidgetId,\n Workspace,\n WidgetComponentMap,\n} from './types'\nimport { defineComponent, ref, watch, PropType } from 'vue'\nimport { Splitpanes, Pane } from 'splitpanes'\nimport 'splitpanes/dist/splitpanes.css'\nimport draggable from \"vuedraggable\"\nimport {\n getWorkspaceShape,\n setWorkspace,\n insertColumn as insertDoricColumn,\n removeColumn as removeDoricColumn,\n addWidget as addDoricWidget,\n removeWidget as removeDoricWidget,\n moveWidget as moveDoricWidget,\n sharedParameters,\n setDefaultLabels,\n getWidget,\n} from './doric'\n\n// defineProps for vue and typescript\nconst props = defineProps({\n locked: {\n type: Boolean,\n required: false,\n default: false,\n },\n widgets: {\n type: Object as PropType<WidgetComponentMap>,\n required: true,\n default: () => ({}),\n },\n workspace: {\n type: Object as PropType<Workspace>,\n required: true,\n default: () => ([[]]),\n },\n})\n\nsetDefaultLabels(Object.fromEntries(Object.keys(props.widgets).map(key => [key, props.widgets[key].defaultLabel])))\n\nconst emit = defineEmits(['setSharedParameters', 'onWorkspaceReady'])\n\nimport DoricWidgetConfig from './components/WidgetConfig.vue'\nimport DoricMissingWidget from './components/MissingWidget.vue'\nimport ScopedComponent from './components/ScopedComponent.vue'\n\nconst loadingWorkspace = ref(false)\nconst configWidget = ref(\"\")\nconst showWidgetsToAddColumn = ref(-1)\nconst subscriptionMode = ref({\n widgetId: \"\",\n input: \"\",\n})\n\nconst turnOffConfigMode = () => {\n configWidget.value = \"\"\n subscriptionMode.value = {\n widgetId: \"\",\n input: \"\",\n }\n showWidgetsToAddColumn.value = -1\n}\n\nconst pushWorkspace = (newWorkspace: unknown) => {\n if (!newWorkspace) {\n console.warn(\"newWorkspace is null\")\n return false\n }\n loadingWorkspace.value = true\n turnOffConfigMode()\n setWorkspace(newWorkspace).then(() => {\n loadingWorkspace.value = false\n }).then(() => {\n emit(\"onWorkspaceReady\")\n })\n}\nwatch(() => props.workspace, pushWorkspace)\npushWorkspace(props.workspace)\n\nwatch(sharedParameters, (newSharedParameters, oldSharedParameters) => {\n emit(\"setSharedParameters\", newSharedParameters, oldSharedParameters)\n})\n\nwatch(() => props.widgets, (newWidgets) => {\n setDefaultLabels(Object.fromEntries(Object.keys(newWidgets).map(key => [key, newWidgets[key].defaultLabel])))\n})\n\nwatch(() => props.locked, (newLocked) => {\n if (newLocked) {\n turnOffConfigMode()\n }\n})\n\nconst configureWidget = (widgetId: WidgetId) => {\n const currentConfigWidget = configWidget.value\n turnOffConfigMode()\n if (currentConfigWidget === widgetId) {\n return\n }\n configWidget.value = widgetId\n}\n\nconst removeWidget = (widgetId: WidgetId) => {\n turnOffConfigMode()\n removeDoricWidget(widgetId)\n}\n\nconst setColumnToAddWidget = (column: number) => {\n turnOffConfigMode()\n if (showWidgetsToAddColumn.value === column) {\n return\n }\n showWidgetsToAddColumn.value = column\n}\n\nconst addWidget = (widgetType: keyof typeof props.widgets, column: number) => {\n addDoricWidget({\n id: `${widgetType}-0`,\n type: `${widgetType}`,\n }, column)\n turnOffConfigMode()\n}\n\n\nconst handleRearrange = (colIndex: number, event: any[]) => {\n Object.entries(event).forEach(([method, details]) => {\n if (details?.element?.id && Number.isInteger(details?.newIndex)) {\n const widgetId = details.element.id\n const newIndex = details.newIndex\n if (method === \"moved\" || method === \"added\") {\n moveDoricWidget(widgetId, colIndex, newIndex)\n }\n }\n // Technically there is also the `method === \"removed\"`\n // We are not handling it, because we know that when a \n // widget is \"added\" by draggable, it is also \"removed\" \n // from the previous column\n })\n}\n\nconst addColumn = (index: number) => {\n insertDoricColumn(index)\n}\n\nconst removeColumn = (index: number) => {\n removeDoricColumn(index)\n}\n\nconst createColumnForWidget = (first: boolean, event: any[]) => {\n const colIndex = first ? 0 : getWorkspaceShape().length\n insertDoricColumn(colIndex)\n Object.entries(event).forEach(([method, details]) => {\n const widgetId = details.element.id\n if (method === \"added\") {\n moveDoricWidget(widgetId, colIndex, 0)\n }\n })\n}\n\ndefineComponent({\n name: 'DoricFramework',\n props: {\n locked: {\n type: Boolean,\n required: false,\n default: false,\n },\n widgets: {\n type: Object as PropType<WidgetComponentMap>,\n required: true,\n default: () => ({}),\n },\n workspace: {\n type: Object as PropType<Workspace>,\n required: true,\n default: () => ([]),\n },\n },\n emits: ['setSharedParameters', 'onWorkspaceReady'],\n})\n\nconst setSubscriptionMode = (widgetId: WidgetId | null, input: string) => {\n if (!widgetId) {\n subscriptionMode.value = {\n widgetId: \"\",\n input: \"\",\n }\n return\n }\n subscriptionMode.value = {\n widgetId,\n input,\n }\n}\n\nconst getBorderColor = (widgetId: WidgetId) => {\n return configWidget.value === widgetId\n ? \"border-blue-600 hover:border-blue-700\"\n : \"border-gray-200 hover:border-gray-300\"\n}\nconst getHeaderColor = (widgetId: WidgetId) => {\n if (props.locked) {\n return \"bg-gray-100\"\n }\n return configWidget.value === widgetId\n ? \"bg-blue-100 group-hover:bg-blue-200\"\n : \"bg-gray-50 group-hover:bg-gray-100\"\n}\n\nconst subscriptionClasses = {\n subscribed: 'subscribed',\n unsubscribed: 'not-subscribed',\n}\nconst isSubscribedTo = (subscriberId: WidgetId, inputKey: string, targetId: WidgetId) => {\n const subscriber = getWidget(subscriberId)\n if (!subscriber) {\n console.error(`Widget not found: ${subscriberId}`)\n throw new Error(`Widget not found: ${subscriberId}`)\n }\n return subscriber.inputs[inputKey].subscriptions.includes(targetId)\n}\nconst getSubscriptionClass = (subscriberId: WidgetId, inputKey: string, targetId: WidgetId) => {\n return subscriptionClasses[isSubscribedTo(subscriberId, inputKey, targetId)\n ? 'subscribed'\n : 'unsubscribed']\n}\nconst toggleSubscription = (widgetId: WidgetId) => {\n if (!subscriptionMode.value.widgetId || !subscriptionMode.value.input) {\n console.error(\"Should be impossible: toggleSubscription called without subscriptionMode\")\n return\n }\n const subscriber = getWidget(subscriptionMode.value.widgetId)\n const key = subscriptionMode.value.input\n\n // Update the subscription's widgetSubscriptions\n const newKeySubscriptions = [...subscriber.inputs[key].subscriptions]\n if (!newKeySubscriptions.includes(widgetId)) {\n newKeySubscriptions.push(widgetId)\n } else {\n const index = newKeySubscriptions.indexOf(widgetId)\n if (index > -1) {\n newKeySubscriptions.splice(index, 1)\n }\n }\n subscriber.inputs[key].subscriptions = [...newKeySubscriptions]\n}\n</script>\n\n<template>\n <div class=\"doric-widget-framework\">\n <draggable class=\"list-group\" :list=\"[]\" group=\"widgets\" @change=\"createColumnForWidget(true, $event)\" itemKey=\"id\">\n <template #item=\"_\">\n <!-- This is just a placeholder to receive widgets and create columns on the fly -->\n </template>\n </draggable>\n <splitpanes>\n <pane min-size=\"20\" v-for=\"(column, index) in getWorkspaceShape()\" :key=\"index\"\n :size=\"100 / getWorkspaceShape().length\">\n\n <draggable class=\"list-group\" :list=\"column\" group=\"widgets\" @change=\"handleRearrange(index, $event)\" itemKey=\"id\"\n handle=\".drag-handle\">\n <template #item=\"{ element }\">\n <div class=\"relative mx-1 my-2\">\n <button v-if=\"subscriptionMode.input && configWidget !== element.id\"\n @click=\"() => toggleSubscription(element.id)\" class=\"subscription-helper\"\n :class=\"getSubscriptionClass(subscriptionMode.widgetId, subscriptionMode.input, element.id)\">\n <div>\n {{ isSubscribedTo(subscriptionMode.widgetId, subscriptionMode.input, element.id)\n ? 'Subscribed'\n : 'Click to Subscribe' }}\n </div>\n </button>\n <div class=\"doric-widget-framework__widget border-2 rounded group\" :class=\"getBorderColor(element.id)\">\n <header class=\"p-1\" :class=\"getHeaderColor(element.id) + (locked ? '' : ' drag-handle')\">\n <div class=\"flex-1 flex flex-row items-center\">\n <span class=\"text-gray-900 text-sm font-bold mx-2 my-1\">\n {{ configWidget !== element.id ? element.label : \"Label:\" }}\n </span>\n <input v-if=\"configWidget === element.id\" type=\"text\" v-model=\"getWidget(element.id).label\"\n class=\"w-full mr-2\" />\n </div>\n <div v-if=\"!locked\" :class=\"{ invisible: configWidget && configWidget !== element.id }\"\n class=\"flex flex-row items-center\">\n <button v-if=\"element?.type in widgets && 'widget' in widgets[element.type]\"\n @click=\"() => configureWidget(element.id)\" class=\"config-button\"\n :class=\"configWidget === element.id ? 'active-config' : ''\">\n <!-- `cog-6-tooth` icon from https://heroicons.com/, MIT license -->\n <svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\"\n stroke=\"currentColor\" class=\"w-5 h-5\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\"\n d=\"M9.594 3.94c.09-.542.56-.94 1.11-.94h2.593c.55 0 1.02.398 1.11.94l.213 1.281c.063.374.313.686.645.87.074.04.147.083.22.127.324.196.72.257 1.075.124l1.217-.456a1.125 1.125 0 011.37.49l1.296 2.247a1.125 1.125 0 01-.26 1.431l-1.003.827c-.293.24-.438.613-.431.992a6.759 6.759 0 010 .255c-.007.378.138.75.43.99l1.005.828c.424.35.534.954.26 1.43l-1.298 2.247a1.125 1.125 0 01-1.369.491l-1.217-.456c-.355-.133-.75-.072-1.076.124a6.57 6.57 0 01-.22.128c-.331.183-.581.495-.644.869l-.213 1.28c-.09.543-.56.941-1.11.941h-2.594c-.55 0-1.02-.398-1.11-.94l-.213-1.281c-.062-.374-.312-.686-.644-.87a6.52 6.52 0 01-.22-.127c-.325-.196-.72-.257-1.076-.124l-1.217.456a1.125 1.125 0 01-1.369-.49l-1.297-2.247a1.125 1.125 0 01.26-1.431l1.004-.827c.292-.24.437-.613.43-.992a6.932 6.932 0 010-.255c.007-.378-.138-.75-.43-.99l-1.004-.828a1.125 1.125 0 01-.26-1.43l1.297-2.247a1.125 1.125 0 011.37-.491l1.216.456c.356.133.751.072 1.076-.124.072-.044.146-.087.22-.128.332-.183.582-.495.644-.869l.214-1.281z\" />\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M15 12a3 3 0 11-6 0 3 3 0 016 0z\" />\n </svg>\n </button>\n <button @click=\"() => removeWidget(element.id)\" class=\"config-button\"\n :class=\"configWidget === element.id ? 'active-config' : ''\">\n <!-- `x-mark` icon from https://heroicons.com/, MIT license -->\n <svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\"\n stroke=\"currentColor\" class=\"w-5 h-5\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M6 18L18 6M6 6l12 12\" />\n </svg>\n </button>\n </div>\n </header>\n <div v-if=\"configWidget === element.id\" class=\"p-1\">\n <DoricWidgetConfig :widgetId=\"element.id\" @setSubscriptionMode=\"setSubscriptionMode\" />\n </div>\n <!-- We use \"hidden\", because we want the component to stay in the DOM\n whether or not it's visible -->\n <div :class=\"{ 'hidden': configWidget === element.id }\" class=\"p-1\">\n <div class=\"widget\" v-if=\"element?.type in widgets && 'widget' in widgets[element.type]\">\n <ScopedComponent :widget=\"widgets[element.type].widget\" :widgetId=\"element.id\" />\n </div>\n <DoricMissingWidget :type=\"element?.type\" v-else />\n </div>\n </div>\n </div>\n </template>\n </draggable>\n\n <div v-if=\"!locked\" class=\"column-buttons\">\n <div class=\"center\">\n <button class=\"remove-column-button\" v-if=\"column.length === 0\" @click=\"() => removeColumn(index)\">\n Remove Column\n </button>\n </div>\n <div>\n <div class=\"center\">\n <button :class=\"'add-widget-button ' + (index === showWidgetsToAddColumn ? 'toggled' : '')\"\n @click=\"() => setColumnToAddWidget(index === showWidgetsToAddColumn ? -1 : index)\" title=\"Add Widget\">\n <!-- `plus` icon from https://heroicons.com/, MIT license -->\n <svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\"\n stroke=\"currentColor\" class=\"w-4 h-4\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M12 4.5v15m7.5-7.5h-15\" />\n </svg>\n </button>\n </div>\n <div class=\"add-widget-list\" :class=\"{ collapsed: showWidgetsToAddColumn !== index }\">\n <button v-for=\"(widgetType) in Object.keys(widgets)\" :key=\"widgetType\"\n @click=\"() => addWidget(widgetType, index)\">\n {{ widgets[widgetType].defaultLabel }}\n </button>\n </div>\n </div>\n </div>\n </pane>\n </splitpanes>\n <draggable v-if=\"!locked\" class=\"list-group\" :list=\"[]\" group=\"widgets\" @change=\"createColumnForWidget(false, $event)\"\n itemKey=\"id\">\n <template #item=\"_\">\n <!-- This is just a placeholder to receive widgets and create columns on the fly -->\n </template>\n </draggable>\n <div v-if=\"!locked\" class=\"column-insert\">\n <button @click=\"addColumn(getWorkspaceShape().length)\">\n <!-- `plus mini` icon from https://heroicons.com/, MIT license -->\n <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 20 20\" fill=\"currentColor\" class=\"w-4 h-4\">\n <path\n d=\"M10.75 4.75a.75.75 0 00-1.5 0v4.5h-4.5a.75.75 0 000 1.5h4.5v4.5a.75.75 0 001.5 0v-4.5h4.5a.75.75 0 000-1.5h-4.5v-4.5z\" />\n </svg>\n </button>\n </div>\n </div>\n</template>\n\n<style lang=\"scss\" scoped>\n.doric-widget-framework {\n display: flex;\n flex-direction: row;\n flex: 1;\n margin-left: 0.25rem; // i.e. ml-1 (normalizes spacing of panes)\n overflow: hidden;\n height: 100%;\n text-align: left;\n\n .doric-widget-framework__widget {\n position: relative;\n\n &.config-mode {\n border-color: rgb(37, 99, 235);\n }\n\n &.sortable-chosen {\n /* border-blue-400 */\n border: 2px solid rgb(96, 165, 250);\n }\n\n header {\n display: flex;\n flex-direction: row;\n justify-content: space-between;\n align-items: center;\n user-select: none;\n\n button.config-button {\n all: initial;\n cursor: pointer;\n\n @apply p-1 rounded text-gray-600;\n\n &:hover {\n @apply text-black bg-gray-300;\n }\n\n &:active {\n @apply bg-gray-400 scale-90;\n }\n\n &.active-config {\n @apply text-blue-800;\n\n &:hover {\n @apply bg-blue-400 text-blue-900;\n }\n\n &:active {\n @apply bg-blue-600;\n }\n }\n }\n\n input[type=\"text\"] {\n @apply p-0.5 text-sm border-none bg-blue-100 text-gray-900 text-sm rounded block py-1 px-2 outline-none;\n\n &:hover {\n @apply bg-blue-50;\n }\n\n &:focus {\n @apply bg-white;\n }\n }\n\n &.drag-handle {\n cursor: grab;\n }\n }\n\n .hidden {\n display: none;\n }\n }\n\n button.subscription-helper {\n @apply absolute block left-0 top-0 w-full h-full m-0 flex flex-col items-center justify-center font-bold text-xl cursor-pointer z-10 rounded border-4;\n\n &.subscribed {\n // @apply bg-blue-200 border-blue-400 text-white\n background-color: rgb(96, 165, 250, 0.8);\n border-color: rgb(37, 99, 235);\n color: white;\n\n &:hover {\n // @apply bg-blue-300 border-blue-500;\n background-color: rgb(37, 99, 235, 0.8);\n border-color: rgb(37, 99, 235);\n }\n\n &:active {\n // @apply bg-blue-500 border-blue-600;\n background-color: rgb(10, 132, 255, 0.8);\n border-color: rgb(10, 132, 255);\n }\n }\n\n &.not-subscribed {\n /* bg-gray-200 border-gray-300 text-gray-800 */\n background-color: rgb(229, 231, 235, 0.8);\n border-color: rgb(209, 213, 219);\n color: rgb(75, 85, 99);\n\n &:hover {\n /* bg-gray-300 border-gray-400 */\n background-color: rgb(209, 213, 219, 0.8);\n border-color: rgb(156, 163, 175);\n }\n\n &:active {\n /* bg-gray-500 border-gray-600 */\n background-color: rgb(107, 114, 128, 0.8);\n border-color: rgb(75, 85, 99);\n }\n }\n }\n\n .column-buttons {\n @apply flex flex-col items-center mt-2 mx-2;\n\n .center {\n @apply flex flex-col items-center w-full;\n\n .remove-column-button {\n @apply w-full mb-3 p-2 bg-gray-100 text-gray-600 rounded-sm text-sm font-bold border-none;\n transition: none;\n\n &:hover {\n @apply bg-red-100 text-red-800;\n }\n\n &:active {\n @apply scale-95;\n }\n }\n\n .add-widget-button {\n @apply flex justify-center items-center p-1 rounded-full bg-gray-50 border-2 border-gray-200 text-gray-600 z-10;\n transition: transform 60ms ease;\n transform-origin: center;\n\n &.toggled {\n @apply text-red-800;\n transform: rotate(45deg);\n\n &:hover {\n @apply bg-gray-200 text-red-800 border-gray-300;\n }\n }\n\n &:hover {\n @apply bg-blue-200 text-blue-800 border-blue-300;\n }\n\n &:active {\n @apply scale-90;\n }\n }\n }\n\n .add-widget-list {\n @apply flex flex-col items-center m-1 mb-4 p-2 rounded bg-gray-50 border-2 border-gray-200;\n margin-top: -0.9rem;\n padding-top: 1rem;\n overflow: hidden;\n transition-property: transform, height;\n transition-delay: 0s, 120ms;\n transition-duration: 120ms, 0s;\n transition-timing-function: ease, linear;\n transform-origin: 50% 0%;\n\n &.collapsed {\n transform: scale(0);\n height: 0;\n }\n\n button {\n @apply m-1 py-2 px-8 text-sm font-bold border-none bg-gray-200 text-gray-700 rounded;\n\n &:hover {\n @apply bg-blue-100 text-blue-800;\n }\n }\n }\n }\n}\n\n.column-insert {\n @apply ml-1;\n\n > button {\n @apply h-full w-6 p-0 m-0 flex items-center justify-center bg-gray-200 text-gray-600 border-none rounded-none;\n \n &:hover {\n @apply bg-blue-200 text-blue-800;\n }\n &:active {\n @apply bg-blue-300;\n }\n }\n}\n</style>\n\n<style lang=\"scss\" global>\n.splitpanes {\n height: auto;\n\n .splitpanes__pane {\n overflow: auto;\n }\n}\n\n.splitpanes--vertical>.splitpanes__splitter {\n box-sizing: border-box;\n width: 12px;\n margin: 0 1px;\n border-left: 4px solid #fff;\n border-right: 4px solid #fff;\n background-color: #f8f8f8;\n user-select: none;\n\n &:hover {\n background-color: #ccc;\n }\n}\n\n.ghost {\n opacity: 0.7;\n}\n</style>","import { inject } from 'vue'\nimport type {\n UseDoricInputOptions,\n UseDoricInputFunction,\n UseDoricOutputFunction,\n} from './types'\n\nconst useDoricInput = function(param: string, options?: UseDoricInputOptions) {\n const useDoricInput = inject<UseDoricInputFunction>(\"useDoricInput\", () => {\n console.error(\"useDoricInput is not ready. This is probably a bug in Doric.\")\n return { value: \"\" }\n })\n return useDoricInput(param, options)\n}\n\nconst useDoricOutput = function (inputKey: string) {\n const useDoricOutput = inject<UseDoricOutputFunction>(\"useDoricOutput\", () => {\n console.error(\"useDoricOutput is not ready. This is probably a bug in Doric.\")\n return () => {\n console.error(\"useDoricOutput is not ready. This is probably a bug in Doric.\")\n }\n })\n return useDoricOutput(inputKey)\n}\n\nexport {\n useDoricInput,\n useDoricOutput,\n}"],"names":["defaultLabels","setDefaultLabels","labels","useDoricStore","defineStore","columnIndex","w","widget","column","validatedWidget","getValidatedWidget","validatedUniqueWidget","widgetWithUniqueId","widgetId","key","ws","c","state","sharedInputs","getValidatedInputs","i","validatedInputs","_b","_a","widgetIds","newW","prefix","ids","id","newId","getWorkspaceShape","setWorkspace","newWorkspace","resolve","reject","store","validatedWidgetIds","validatedNewColumns","nextTick","insertColumn","removeColumn","getWidget","addWidget","removeWidget","moveWidget","newColumnIndex","newRowIndex","sharedParameters","pushWorkspaceState","stateArray","value","primitiveTypes","getUseDoricOutput","inspection","getUseDoricInput","options","newValue","exportWorkspace","workspace","widgetIndex","inputs","inputKey","newInput","props","toggleShared","subscriptionMode","ref","toggleSubscriptionMode","emit","turnOffSubscriptionMode","provide","loadingWorkspace","configWidget","showWidgetsToAddColumn","turnOffConfigMode","pushWorkspace","watch","newSharedParameters","oldSharedParameters","newWidgets","newLocked","configureWidget","currentConfigWidget","removeDoricWidget","setColumnToAddWidget","widgetType","addDoricWidget","handleRearrange","colIndex","event","method","details","newIndex","moveDoricWidget","addColumn","index","insertDoricColumn","removeDoricColumn","createColumnForWidget","first","defineComponent","setSubscriptionMode","input","getBorderColor","getHeaderColor","subscriptionClasses","isSubscribedTo","subscriberId","targetId","subscriber","getSubscriptionClass","toggleSubscription","newKeySubscriptions","useDoricInput","param","inject","useDoricOutput"],"mappings":"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