improve refresh and background script behavior

This commit is contained in:
Ruben Fiszel
2023-03-13 23:38:14 +01:00
parent c46b79dd4b
commit 03a4f2cfa1
17 changed files with 153 additions and 102 deletions

View File

@@ -26,7 +26,7 @@
export const staticOutputs: string[] = ['loading', 'result']
const { runnableComponents, worldStore, app } = getContext<AppViewerContext>('AppViewerContext')
const { worldStore, app } = getContext<AppViewerContext>('AppViewerContext')
let labelValue: string
let color: ButtonType.Color
@@ -109,10 +109,6 @@
} else {
await runnableComponent?.runComponent()
}
if (recomputeIds) {
await Promise.all(recomputeIds.map((id) => $runnableComponents?.[id]?.()))
}
}
</script>
@@ -136,6 +132,7 @@
<RunnableWrapper
flexWrap
{recomputeIds}
bind:runnableComponent
{componentInput}
{id}

View File

@@ -23,8 +23,7 @@
export const staticOutputs: string[] = ['loading', 'result']
const { app, runnableComponents, worldStore, stateId } =
getContext<AppViewerContext>('AppViewerContext')
const { app, worldStore, stateId } = getContext<AppViewerContext>('AppViewerContext')
let labelValue: string = 'Default label'
let color: ButtonType.Color
@@ -62,6 +61,7 @@
<InputValue {id} input={configuration.size} bind:value={size} />
<RunnableWrapper
{recomputeIds}
{render}
bind:runnableComponent
{componentInput}
@@ -103,12 +103,6 @@
}}
on:click={() => {
runnableComponent?.runComponent()
if (recomputeIds) {
recomputeIds.forEach((id) => {
$runnableComponents[id]?.()
})
}
}}
{size}
{color}

View File

@@ -94,6 +94,7 @@
}}
>
<RunnableWrapper
{recomputeIds}
{render}
bind:runnableComponent
{componentInput}
@@ -128,12 +129,6 @@
on:click={async () => {
await runnableComponent?.runComponent()
if (recomputeIds) {
recomputeIds.forEach((id) => {
$runnableComponents[id]?.()
})
}
open = false
}}
size="xs"

View File

@@ -12,6 +12,7 @@
export let name: string
export let inlineScript: InlineScript | undefined
export let fields: Record<string, StaticAppInput | ConnectedAppInput | RowAppInput | UserAppInput>
export let autoRefresh: boolean = false
let result: any = undefined
@@ -22,6 +23,7 @@
render={false}
{id}
{fields}
{autoRefresh}
bind:result
runnable={{
name,
@@ -29,6 +31,7 @@
type: 'runnableByName'
}}
wrapperClass="hidden"
recomputable
>
<slot />
</RunnableComponent>

View File

@@ -25,7 +25,7 @@
}
}
const { worldStore, state } = getContext<AppViewerContext>('AppViewerContext')
const { worldStore, state, mode } = getContext<AppViewerContext>('AppViewerContext')
$: stateId = $worldStore?.state
@@ -70,7 +70,8 @@
input.expr,
computeGlobalContext($worldStore, id, extraContext),
true,
$state
$state,
$mode == 'dnd'
)
error = ''
return r
@@ -87,7 +88,8 @@
'`' + input.eval + '`',
computeGlobalContext($worldStore, id, extraContext),
true,
$state
$state,
$mode == 'dnd'
)
error = ''
return r

View File

@@ -1,7 +1,5 @@
<script lang="ts">
import Button from '$lib/components/common/button/Button.svelte'
import { faRefresh } from '@fortawesome/free-solid-svg-icons'
import { RefreshCcw, RefreshCw } from 'lucide-svelte'
import { RefreshCw } from 'lucide-svelte'
import { getContext } from 'svelte'
import type { AppViewerContext } from '../../types'

View File

@@ -7,12 +7,10 @@
import TestJobLoader from '$lib/components/TestJobLoader.svelte'
import { AppService, type CompletedJob } from '$lib/gen'
import { classNames, defaultIfEmptyString, emptySchema, sendUserToast } from '$lib/utils'
import { Bug, Loader2 } from 'lucide-svelte'
import { getContext, onMount } from 'svelte'
import { fade } from 'svelte/transition'
import { Bug } from 'lucide-svelte'
import { getContext } from 'svelte'
import { initOutput } from '../../editor/appUtils'
import type { AppInputs, Runnable } from '../../inputType'
import type { Output } from '../../rx'
import type { AppViewerContext } from '../../types'
import { computeGlobalContext, eval_like } from './eval'
import InputValue from './InputValue.svelte'
@@ -33,6 +31,8 @@
export let gotoUrl: string | undefined = undefined
export let gotoNewTab: boolean | undefined = undefined
export let render: boolean
export let recomputable: boolean = false
export let recomputeIds: string[] = []
const {
worldStore,
@@ -48,14 +48,23 @@
state
} = getContext<AppViewerContext>('AppViewerContext')
onMount(() => {
if (autoRefresh) {
$: autoRefresh && handleAutorefresh()
if (recomputable) {
$runnableComponents[id] = async () => {
await executeComponent(true)
}
$runnableComponents = $runnableComponents
}
function handleAutorefresh() {
if (autoRefresh && $worldStore) {
$runnableComponents[id] = async () => {
await executeComponent(true)
}
executeComponent(true)
}
})
}
let args: Record<string, any> | undefined = undefined
let testIsLoading = false
@@ -83,17 +92,17 @@
$: fields && (currentStaticValues = computeStaticValues())
$: if (JSON.stringify(currentStaticValues) != JSON.stringify(lazyStaticValues)) {
lazyStaticValues = currentStaticValues
refreshIfAutoRefresh()
}
function refreshIfAutoRefresh() {
if (autoRefresh) {
setDebouncedExecute()
}
}
$: fields && (lazyStaticValues = computeStaticValues())
$: (runnableInputValues || extraQueryParams || args) &&
autoRefresh &&
testJobLoader &&
setDebouncedExecute()
$: (runnableInputValues || extraQueryParams || args) && testJobLoader && refreshIfAutoRefresh()
// Test job internal state
let testJob: CompletedJob | undefined = undefined
@@ -147,12 +156,13 @@
runnable.inlineScript?.content,
computeGlobalContext($worldStore, id, {}),
false,
$state
$state,
$mode == 'dnd'
)
setResult(r)
$state = $state
} catch (e) {
sendUserToast('Error running frontend script: ' + e.message)
sendUserToast('Error running frontend script: ' + e.message, true)
}
outputs?.loading?.set(false)
return
@@ -231,6 +241,7 @@
function setResult(res: any) {
outputs.result?.set(res)
result = res
const previousJobId = Object.keys($errorByComponent).find(
@@ -248,6 +259,10 @@
goto(gotoUrl)
}
}
if (recomputeIds) {
recomputeIds.map((id) => $runnableComponents?.[id]?.())
}
}
$: result?.error && recordError(result.error)
</script>

View File

@@ -21,6 +21,7 @@
export let goto: string | undefined = undefined
export let gotoNewTab: boolean | undefined = undefined
export let render: boolean
export let recomputeIds: string[] = []
const { staticExporter, noBackend } = getContext<AppViewerContext>('AppViewerContext')
@@ -44,6 +45,7 @@
<slot />
{:else if componentInput.type === 'runnable' && isRunnableDefined()}
<RunnableComponent
{recomputeIds}
gotoUrl={goto}
{gotoNewTab}
{flexWrap}

View File

@@ -1,4 +1,5 @@
import { goto } from '$app/navigation'
import { sendUserToast } from '$lib/utils'
import type { World } from '../../rx'
export function computeGlobalContext(
@@ -45,9 +46,24 @@ function make_context_evaluator(
return functor()
}
export async function eval_like(text, context = {}, noReturn: boolean = true, state: any = {}) {
export async function eval_like(
text,
context = {},
noReturn: boolean,
state: any,
editor: boolean
) {
let evaluator = make_context_evaluator(text, context, noReturn)
return await evaluator(context, state, async (x) => {
await goto(x)
return await evaluator(context, state, async (x, newTab) => {
if (newTab || editor) {
if (!newTab) {
sendUserToast(
'In editor mode, `goto` opens a new tab to prevent losing your work. To test the redirection , use the preview mode.'
)
}
window.open(x, '_blank')
} else {
await goto(x)
}
})
}

View File

@@ -240,6 +240,7 @@
inlineScript={script.inlineScript}
name={script.name}
fields={script.fields}
autoRefresh={script.autoRefresh ?? false}
bind:staticOutputs={$staticOutputs[`bg_${index}`]}
/>
{/if}

View File

@@ -5,7 +5,7 @@
import Button from '../../common/button/Button.svelte'
import type { AppViewerContext } from '../types'
const { runnableComponents } = getContext<AppViewerContext>('AppViewerContext')
const { runnableComponents, app } = getContext<AppViewerContext>('AppViewerContext')
let loading: boolean = false
let timeout: NodeJS.Timer | undefined = undefined
let interval: number | undefined = undefined
@@ -36,6 +36,12 @@
loading = true
Promise.all(
Object.keys($runnableComponents).map((id) => {
if (id.startsWith('bg_')) {
let index = parseInt(id.split('_')[1])
if (!$app.hiddenInlineScripts[index]?.autoRefresh) {
return
}
}
return $runnableComponents?.[id]?.()
})
).finally(() => {

View File

@@ -1,4 +1,5 @@
<script lang="ts">
import Toggle from '$lib/components/Toggle.svelte'
import { getContext } from 'svelte'
import type { AppViewerContext } from '../types'
import GridPanel from './GridPanel.svelte'
@@ -20,15 +21,27 @@
{#each $app?.hiddenInlineScripts ?? [] as script, index (script.name)}
{#if $selectedComponent === `bg_${index}`}
<PanelSection title={`Background script inputs`}>
{#key $stateId}
<InputsSpecsEditor
id={`bg_${index}`}
shouldCapitalize={false}
bind:inputSpecs={script.fields}
userInputEnabled={false}
<div class="min-h-full flex flex-col divide-y">
<PanelSection title={`Configuration`}>
<Toggle
bind:checked={script.autoRefresh}
options={{ right: 'Run on start and app refresh' }}
/>
{/key}
</PanelSection>
</PanelSection>
{#if Object.keys(script.fields).length > 0}
<PanelSection title={`Inputs`}>
{#key $stateId}
<InputsSpecsEditor
id={`bg_${index}`}
shouldCapitalize={false}
bind:inputSpecs={script.fields}
userInputEnabled={false}
/>
{/key}
</PanelSection>
{/if}
<div class="grow shrink" />
</div>
{/if}
{/each}

View File

@@ -47,6 +47,7 @@
</td>
<td class="relative whitespace-nowrap px-4 py-2 ">
<input
class="windmillapp"
type="checkbox"
on:change={(event) => onChange(event, id)}
checked={recomputeIds?.includes(id)}

View File

@@ -100,6 +100,7 @@ export type App = {
name: string
inlineScript: InlineScript | undefined
fields: Record<string, StaticAppInput | ConnectedAppInput | RowAppInput | UserAppInput>
autoRefresh?: boolean
}>
css?: Record<'viewer' | 'grid' | AppComponent['type'], ComponentCustomCSS>
subgrids?: Record<string, GridItem[]>

View File

@@ -177,7 +177,7 @@ export function buildExtraLib(
return `${cs}
${hasRows ? 'declare const row: Record<string, any>;' : ''}
${goto ? 'declare const goto: async (path: string) => void)' : ''}
${goto ? 'declare const goto: async (path: string, newTab?: boolean) => void)' : ''}
declare const state: ${JSON.stringify(state)};
`
}

View File

@@ -24,7 +24,7 @@
$workspaceStore = $page.url.searchParams.get('workspace_id')!
}
$: if ($workspaceStore) {
$: if ($workspaceStore && $page.params.path) {
loadApp()
}
@@ -32,34 +32,39 @@
</script>
{#if app}
<div
class={twMerge('min-h-screen h-full w-full', app?.value.css?.['app']?.['viewer']?.class)}
style={app?.value.css?.['app']?.['viewer']?.style}
>
<AppPreview
context={{
email: $userStore?.email,
username: $userStore?.username,
query: Object.fromEntries($page.url.searchParams.entries()),
hash: $page.url.hash
}}
workspace={$workspaceStore ?? ''}
summary={app.summary}
app={app.value}
appPath={app.path}
{breakpoint}
policy={app.policy}
isEditor={false}
noBackend={false}
/>
{#if can_write}
<div class="absolute bottom-4 z-20 right-4">
<Button size="sm" startIcon={{ icon: faPen }} variant="border" href="/apps/edit/{app.path}"
>Edit</Button
>
</div>
{/if}
</div>
{#key app}
<div
class={twMerge('min-h-screen h-full w-full', app?.value.css?.['app']?.['viewer']?.class)}
style={app?.value.css?.['app']?.['viewer']?.style}
>
<AppPreview
context={{
email: $userStore?.email,
username: $userStore?.username,
query: Object.fromEntries($page.url.searchParams.entries()),
hash: $page.url.hash
}}
workspace={$workspaceStore ?? ''}
summary={app.summary}
app={app.value}
appPath={app.path}
{breakpoint}
policy={app.policy}
isEditor={false}
noBackend={false}
/>
{#if can_write}
<div class="absolute bottom-4 z-20 right-4">
<Button
size="sm"
startIcon={{ icon: faPen }}
variant="border"
href="/apps/edit/{app.path}">Edit</Button
>
</div>
{/if}
</div>
{/key}
{:else}
<Skeleton layout={[10]} />
{/if}

View File

@@ -71,27 +71,29 @@
</Alert></div
>
{:else if app}
<div
class={twMerge('min-h-screen h-full w-full', app?.value.css?.['app']?.['viewer']?.class)}
style={app?.value.css?.['app']?.['viewer']?.style}
>
<AppPreview
noBackend={false}
context={{
email: $userStore?.email,
username: $userStore?.username,
query: Object.fromEntries($page.url.searchParams.entries()),
hash: $page.url.hash
}}
workspace={$page.params.workspace}
summary={app.summary}
app={app.value}
appPath={app.path}
{breakpoint}
policy={app.policy}
isEditor={false}
/>
</div>
{#key app}
<div
class={twMerge('min-h-screen h-full w-full', app?.value.css?.['app']?.['viewer']?.class)}
style={app?.value.css?.['app']?.['viewer']?.style}
>
<AppPreview
noBackend={false}
context={{
email: $userStore?.email,
username: $userStore?.username,
query: Object.fromEntries($page.url.searchParams.entries()),
hash: $page.url.hash
}}
workspace={$page.params.workspace}
summary={app.summary}
app={app.value}
appPath={app.path}
{breakpoint}
policy={app.policy}
isEditor={false}
/>
</div>
{/key}
{:else}
<Skeleton layout={[[4], 0.5, [50]]} />
{/if}