diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 264af1205b..d8d3d0a3b0 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -23,6 +23,7 @@ "date-fns": "^2.29.3", "fast-equals": "^4.0.3", "highlight.js": "^11.7.0", + "lodash": "^4.17.21", "lucide-svelte": "^0.115.0", "monaco-editor": "0.33.0", "monaco-editor-workers": "0.33.0", @@ -44,6 +45,7 @@ "@tailwindcss/typography": "^0.5.8", "@types/d3": "^7.4.0", "@types/d3-zoom": "^3.0.2", + "@types/lodash": "^4.14.191", "@types/node": "^18.11.18", "@types/vscode": "~1.74.0", "@typescript-eslint/eslint-plugin": "^5.49.0", @@ -1285,6 +1287,12 @@ "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz", "integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==" }, + "node_modules/@types/lodash": { + "version": "4.14.191", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.191.tgz", + "integrity": "sha512-BdZ5BCCvho3EIXw6wUCXHe7rS53AIDPLE+JzwgT+OsJk53oBfbSmZZ7CX4VaRoN78N+TJpFi9QPlfIVNmJYWxQ==", + "dev": true + }, "node_modules/@types/minimist": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/@types/minimist/-/minimist-1.2.2.tgz", @@ -4401,6 +4409,11 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" + }, "node_modules/lodash.castarray": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/lodash.castarray/-/lodash.castarray-4.4.0.tgz", @@ -9082,6 +9095,12 @@ "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz", "integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==" }, + "@types/lodash": { + "version": "4.14.191", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.191.tgz", + "integrity": "sha512-BdZ5BCCvho3EIXw6wUCXHe7rS53AIDPLE+JzwgT+OsJk53oBfbSmZZ7CX4VaRoN78N+TJpFi9QPlfIVNmJYWxQ==", + "dev": true + }, "@types/minimist": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/@types/minimist/-/minimist-1.2.2.tgz", @@ -11358,6 +11377,11 @@ "p-locate": "^5.0.0" } }, + "lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" + }, "lodash.castarray": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/lodash.castarray/-/lodash.castarray-4.4.0.tgz", diff --git a/frontend/package.json b/frontend/package.json index e18b2bdf0e..ccc40401fe 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -23,6 +23,7 @@ "@tailwindcss/typography": "^0.5.8", "@types/d3": "^7.4.0", "@types/d3-zoom": "^3.0.2", + "@types/lodash": "^4.14.191", "@types/node": "^18.11.18", "@types/vscode": "~1.74.0", "@typescript-eslint/eslint-plugin": "^5.49.0", @@ -78,6 +79,7 @@ "date-fns": "^2.29.3", "fast-equals": "^4.0.3", "highlight.js": "^11.7.0", + "lodash": "^4.17.21", "lucide-svelte": "^0.115.0", "monaco-editor": "0.33.0", "monaco-editor-workers": "0.33.0", diff --git a/frontend/src/lib/components/apps/components/helpers/InputValue.svelte b/frontend/src/lib/components/apps/components/helpers/InputValue.svelte index 2d1423054d..b35bd3722a 100644 --- a/frontend/src/lib/components/apps/components/helpers/InputValue.svelte +++ b/frontend/src/lib/components/apps/components/helpers/InputValue.svelte @@ -14,6 +14,7 @@ export let id: string | undefined = undefined export let error: string = '' export let extraContext: Record = {} + export let key: string = '' const { componentControl } = getContext('AppViewerContext') @@ -63,6 +64,7 @@ async function handleConnection() { if (lastInput.type === 'connected') { + console.log('connected', lastInput, `${id}-${key}`) $worldStore?.connect(lastInput, onValueChange, id) } else if (lastInput.type === 'static' || lastInput.type == 'template') { value = await getValue(lastInput) @@ -121,6 +123,8 @@ function onValueChange(newValue: any): void { if (lastInput.type === 'connected' && newValue !== undefined && newValue !== null) { + console.log(newValue, id, lastInput) + const { connection } = lastInput if (!connection) { // No connection diff --git a/frontend/src/lib/components/apps/components/helpers/RunnableComponent.svelte b/frontend/src/lib/components/apps/components/helpers/RunnableComponent.svelte index 20b429b2e7..ed59b375f8 100644 --- a/frontend/src/lib/components/apps/components/helpers/RunnableComponent.svelte +++ b/frontend/src/lib/components/apps/components/helpers/RunnableComponent.svelte @@ -98,10 +98,6 @@ $: fields && (lazyStaticValues = computeStaticValues()) $: (runnableInputValues || extraQueryParams || args) && testJobLoader && refreshIfAutoRefresh() - $: runnable?.type == 'runnableByName' && - runnable.inlineScript?.language == 'frontend' && - ($stateId || $state) && - refreshIfAutoRefresh() function refreshIfAutoRefresh() { if (autoRefresh) { @@ -264,12 +260,24 @@ $: result?.error && recordError(result.error) -{#each Object.entries(fields ?? {}) as [key, v]} +{#each Object.entries(fields ?? {}) as [key, v] (key)} {#if v.type != 'static' && v.type != 'user'} - + {/if} {/each} +{#if runnable?.type == 'runnableByName' && runnable.inlineScript?.language == 'frontend'} + {#each runnable.inlineScript.refreshOn ?? [] as { id: tid, key } (`${tid}-${key}`)} + {@const fkey = `${tid}-${key}`} + + {/each} +{/if} + { diff --git a/frontend/src/lib/components/apps/components/helpers/eval.ts b/frontend/src/lib/components/apps/components/helpers/eval.ts index 459c99936a..97abb70627 100644 --- a/frontend/src/lib/components/apps/components/helpers/eval.ts +++ b/frontend/src/lib/components/apps/components/helpers/eval.ts @@ -1,4 +1,5 @@ import { sendUserToast } from '$lib/utils' +import { isPlainObject } from 'lodash' import type { World } from '../../rx' export function computeGlobalContext( @@ -45,6 +46,38 @@ function make_context_evaluator( return functor() } +function isSerializable(obj) { + var isNestedSerializable + function isPlain(val) { + return ( + val == null || + typeof val === 'undefined' || + typeof val === 'string' || + typeof val === 'boolean' || + typeof val === 'number' || + Array.isArray(val) || + isPlainObject(val) + ) + } + if (!isPlain(obj)) { + return false + } + for (var property in obj) { + if (obj.hasOwnProperty(property)) { + if (!isPlain(obj[property])) { + return false + } + if (typeof obj[property] == 'object') { + isNestedSerializable = isSerializable(obj[property]) + if (!isNestedSerializable) { + return false + } + } + } + } + return true +} + export async function eval_like( text: string, context = {}, @@ -60,7 +93,11 @@ export async function eval_like( throw new Error('Invalid key') } let o = worldStore?.newOutput('state', key, value) - o?.set(value) + if (isSerializable(value)) { + o?.set(value, true) + } else { + o?.set('Not serializable object usable only by frontend scripts', true) + } target[key] = value return true } diff --git a/frontend/src/lib/components/apps/editor/contextPanel/components/OutputHeader.svelte b/frontend/src/lib/components/apps/editor/contextPanel/components/OutputHeader.svelte index 31cf9fe035..5e35e05767 100644 --- a/frontend/src/lib/components/apps/editor/contextPanel/components/OutputHeader.svelte +++ b/frontend/src/lib/components/apps/editor/contextPanel/components/OutputHeader.svelte @@ -3,7 +3,6 @@ import { classNames } from '$lib/utils' import { ChevronDown, ChevronUp } from 'lucide-svelte' import { createEventDispatcher, getContext } from 'svelte' - import { slide } from 'svelte/transition' import { allsubIds } from '../../appUtils' export let id: string @@ -15,7 +14,7 @@ const { expanded, manuallyOpened, search, hasResult } = getContext('ContextPanel') - const { selectedComponent, app } = getContext('AppViewerContext') + const { selectedComponent, app, hoverStore } = getContext('AppViewerContext') $: subids = allsubIds($app, id) $: inSearch = @@ -50,7 +49,18 @@
+
{ + if (id !== $hoverStore) { + $hoverStore = id + } + }} + on:mouseout|stopPropagation={() => { + if ($hoverStore !== undefined) { + $hoverStore = undefined + } + }} class={classNames( 'flex items-center justify-between p-1 cursor-pointer border-b gap-1 truncate', hoverColor[color], diff --git a/frontend/src/lib/components/apps/editor/inlineScriptsPanel/EmptyInlineScript.svelte b/frontend/src/lib/components/apps/editor/inlineScriptsPanel/EmptyInlineScript.svelte index edc92abcf6..358a6cc0f4 100644 --- a/frontend/src/lib/components/apps/editor/inlineScriptsPanel/EmptyInlineScript.svelte +++ b/frontend/src/lib/components/apps/editor/inlineScriptsPanel/EmptyInlineScript.svelte @@ -198,7 +198,7 @@
Frontend only script: Script executed in the client's browser directly: Frontend scripts are executed in the browser and can manipulate the app context directly
diff --git a/frontend/src/lib/components/apps/editor/inlineScriptsPanel/InlineScriptEditor.svelte b/frontend/src/lib/components/apps/editor/inlineScriptsPanel/InlineScriptEditor.svelte index 2946a28e80..88feae9400 100644 --- a/frontend/src/lib/components/apps/editor/inlineScriptsPanel/InlineScriptEditor.svelte +++ b/frontend/src/lib/components/apps/editor/inlineScriptsPanel/InlineScriptEditor.svelte @@ -87,6 +87,22 @@ inlineScript.language == 'frontend' && worldStore ? buildExtraLib($worldStore?.outputsById ?? {}, id, false, $state, true) : undefined + + let refreshOn: string = inlineScript.refreshOn?.map((x) => `${x.id}.${x.key}`).join(' ') ?? '' + + $: handleRefreshOn(refreshOn) + + function handleRefreshOn(refreshOn: string) { + if (refreshOn && refreshOn != '') { + inlineScript.refreshOn = refreshOn + .split(' ') + .filter((x) => x.split('.').length == 2) + .map((x) => { + const [id, key] = x.split('.') + return { id, key } + }) + } + } {#if inlineScript.language != 'frontend'} @@ -190,6 +206,15 @@
+ {#if inlineScript.language == 'frontend'} +
List of outputs to listen to (e.g 'a.result b.result'):
+ {/if}
{#if inlineScript.language != 'frontend'} {} } } else if (inputSpec.type === 'connected') { - const input = cachedInput(next, id) - const connection = inputSpec.connection if (!connection) { @@ -81,6 +79,8 @@ export function buildObservableWorld() { } const { componentId, path } = connection + console.log('connection', connection) + const input = cachedInput(next, `${componentId}-${path}-${id}`) const [p] = path ? path.split('.')[0].split('[') : [undefined] @@ -164,7 +164,6 @@ export function settableOutput(state: Writable, previousValue: T): Ou function set(x: T, force: boolean = false) { if (!deepEqual(value, x) || force) { state.update((x) => x + 1) - value = x subscribers.forEach((x) => x.next(value!)) } diff --git a/frontend/src/lib/components/apps/types.ts b/frontend/src/lib/components/apps/types.ts index fb728091af..7e116677e4 100644 --- a/frontend/src/lib/components/apps/types.ts +++ b/frontend/src/lib/components/apps/types.ts @@ -83,6 +83,7 @@ export type InlineScript = { language: Preview.language | 'frontend' path?: string schema?: Schema + refreshOn?: { id: string; key: string }[] } export type App = {