feat(frontend): App component triggers (#1303)

* feat(frontend): add trigger list

* feat(frontend): add trigger list

* feat(frontend): add support for refreshOn

* feat(frontend): add support for refreshOn

* feat(frontend): rework

* feat(frontend): rework

* feat(frontend): code cleanup
This commit is contained in:
Faton Ramadani
2023-03-17 20:51:00 +01:00
committed by GitHub
parent a6db050bcc
commit 806a7593f0
10 changed files with 292 additions and 13 deletions
@@ -125,8 +125,6 @@
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
@@ -248,7 +248,7 @@
<span>CSS</span>
</div>
</Tab>
<div slot="content" class="h-full overflow-y-auto pb-4">
<div slot="content" class="h-full overflow-y-auto">
<TabContent class="overflow-auto h-full" value="settings">
{#if $selectedComponent !== undefined}
<SettingsPanel />
@@ -5,6 +5,7 @@
import GridPanel from './GridPanel.svelte'
import PanelSection from './settingsPanel/common/PanelSection.svelte'
import InputsSpecsEditor from './settingsPanel/InputsSpecsEditor.svelte'
import BackgroundScriptTriggerList from './settingsPanel/triggerLists/BackgroundScriptTriggerList.svelte'
const { selectedComponent, app, stateId } = getContext<AppViewerContext>('AppViewerContext')
</script>
@@ -29,6 +30,16 @@
/>
</PanelSection>
<div class="p-2">
{#if script.inlineScript}
<BackgroundScriptTriggerList
fields={script.fields}
autoRefresh={script.autoRefresh}
id={`bg_${index}`}
bind:inlineScript={script.inlineScript}
/>
{/if}
</div>
{#if Object.keys(script.fields).length > 0}
<PanelSection title={`Inputs`}>
{#key $stateId}
@@ -55,6 +55,22 @@ export function getNextGridItemId(app: App): string {
return id
}
export function getAllRecomputeIdsForComponent(app: App, id: string) {
const items = allItems(app.grid, app.subgrids)
const recomputedBy: string[] = []
items.forEach((item) => {
if (item.data.type === 'buttoncomponent') {
if (item.data.recomputeIds?.includes(id)) {
recomputedBy.push(item.id)
}
}
})
return recomputedBy
}
export function createNewGridItem(grid: GridItem[], id: string, data: AppComponent): GridItem {
const newComponent = {
resizable: true,
@@ -3,7 +3,7 @@
import type { Preview } from '$lib/gen'
import { createEventDispatcher, getContext, onMount } from 'svelte'
import type { AppViewerContext, InlineScript } from '../../types'
import { CornerDownLeft, Maximize2, Plus, Trash2, X } from 'lucide-svelte'
import { CornerDownLeft, Maximize2, Trash2 } from 'lucide-svelte'
import InlineScriptEditorDrawer from './InlineScriptEditorDrawer.svelte'
import { inferArgs } from '$lib/infer'
import type { Schema } from '$lib/common'
@@ -206,15 +206,6 @@
</div>
</div>
{#if inlineScript.language == 'frontend'}
<div class="flex flex-row text-xs px-1"
>List of outputs to listen to (e.g 'a.result b.result'): <input
on:keydown|stopPropagation
type="text"
bind:value={refreshOn}
/></div
>
{/if}
<div class="border h-full">
{#if inlineScript.language != 'frontend'}
<Editor
@@ -7,6 +7,7 @@
import type { AppViewerContext } from '../../types'
import { clearResultAppInput } from '../../utils'
import InlineScriptEditorDrawer from '../inlineScriptsPanel/InlineScriptEditorDrawer.svelte'
import ComponentTriggerList from './triggerLists/ComponentTriggerList.svelte'
const { app } = getContext<AppViewerContext>('AppViewerContext')
@@ -66,6 +67,9 @@
</Button>
</div>
</div>
{#if appInput.runnable?.type === 'runnableByName' && appInput.runnable.inlineScript}
<ComponentTriggerList fields={appInput.fields} />
{/if}
{#if appInput.runnable?.type === 'runnableByName' && !appInput.runnable.inlineScript}
<span class="text-xs text-gray-500">
Please configure the language in the inline script panel
@@ -0,0 +1,100 @@
<script lang="ts">
import type {
ConnectedAppInput,
RowAppInput,
StaticAppInput,
UserAppInput
} from '$lib/components/apps/inputType'
import type { AppViewerContext, InlineScript } from '$lib/components/apps/types'
import { Button } from '$lib/components/common'
import { Plus } from 'lucide-svelte'
import { getContext } from 'svelte'
import TriggerBadgesList from './TriggerBadgesList.svelte'
import { getDependencies } from './triggerListUtils'
export let fields: Record<string, StaticAppInput | ConnectedAppInput | RowAppInput | UserAppInput>
export let autoRefresh: boolean = false
export let id: string
export let inlineScript: InlineScript
const { connectingInput } = getContext<AppViewerContext>('AppViewerContext')
let addingDependency: boolean = false
function applyConnection() {
if (!$connectingInput.opened && $connectingInput.input !== undefined && addingDependency) {
if ($connectingInput.input.connection) {
const x = {
id: $connectingInput.input.connection.componentId,
key: $connectingInput.input.connection.path
}
if (!inlineScript) {
return
}
if (inlineScript.refreshOn?.find((y) => y.id === x.id && y.key === x.key)) {
return
}
if (!inlineScript.refreshOn) {
inlineScript.refreshOn = [x]
} else {
inlineScript.refreshOn.push(x)
}
inlineScript = JSON.parse(JSON.stringify(inlineScript))
addingDependency = false
}
$connectingInput = {
opened: false,
input: undefined,
hoveredComponent: undefined
}
}
}
$: $connectingInput && applyConnection()
</script>
<TriggerBadgesList
{id}
inputDependencies={getDependencies(fields)}
frontendDependencies={inlineScript?.language === 'frontend'
? inlineScript?.refreshOn?.map((x) => `${x.id} - ${x.key}`) ?? undefined
: undefined}
onLoad={autoRefresh}
on:delete={(e) => {
const index = e.detail.index
if (inlineScript) {
inlineScript.refreshOn?.splice(index, 1)
}
inlineScript = JSON.parse(JSON.stringify(inlineScript))
}}
>
{#if inlineScript?.language === 'frontend'}
<Button
variant="border"
size="xs"
color="light"
btnClasses="!px-1 !py-0.5"
on:click={() => {
addingDependency = true
$connectingInput = {
opened: true,
input: undefined,
hoveredComponent: undefined
}
}}
>
<div class="flex flex-row gap-1 items-center">
Add dependency
<Plus size={14} />
</div>
</Button>
{/if}
</TriggerBadgesList>
@@ -0,0 +1,30 @@
<script lang="ts">
import type {
ConnectedAppInput,
RowAppInput,
StaticAppInput,
UserAppInput
} from '$lib/components/apps/inputType'
import type { AppViewerContext } from '$lib/components/apps/types'
import { getContext } from 'svelte'
import { findGridItem } from '../../appUtils'
import TriggerBadgesList from './TriggerBadgesList.svelte'
import { getDependencies } from './triggerListUtils'
const { selectedComponent, app } = getContext<AppViewerContext>('AppViewerContext')
export let fields: Record<string, StaticAppInput | ConnectedAppInput | RowAppInput | UserAppInput>
$: gridItem = $selectedComponent ? findGridItem($app, $selectedComponent) : undefined
$: onClick =
gridItem?.data.type &&
['buttoncomponent', 'formbuttoncomponent', 'formcomponent'].includes(gridItem?.data.type)
</script>
<TriggerBadgesList
inputDependencies={getDependencies(fields)}
onLoad={!onClick}
id={$selectedComponent}
{onClick}
/>
@@ -0,0 +1,106 @@
<script lang="ts">
import type { AppViewerContext } from '$lib/components/apps/types'
import { classNames } from '$lib/utils'
import { X } from 'lucide-svelte'
import { createEventDispatcher, getContext } from 'svelte'
import { getAllRecomputeIdsForComponent } from '../../appUtils'
export let inputDependencies: string[] = []
export let frontendDependencies: string[] | undefined = undefined
export let onClick: boolean = false
export let onLoad: boolean = false
export let id: string | undefined = undefined
const colors = {
red: 'text-red-800 border-red-600 bg-red-100',
green: 'text-green-800 border-green-600 bg-green-100',
indigo: 'text-indigo-800 border-indigo-600 bg-indigo-100',
blue: 'text-blue-800 border-blue-600 bg-blue-100'
}
let badgeClass = 'inline-flex items-center rounded-md px-2 py-0.5 text-xs font-medium border'
const recomputedBadges: string[] = []
const { app, selectedComponent } = getContext<AppViewerContext>('AppViewerContext')
const dispatch = createEventDispatcher()
$: if ($app && $selectedComponent && id) {
const recomputeIds = getAllRecomputeIdsForComponent($app, id)
if ($selectedComponent && recomputeIds) {
recomputeIds.forEach((x) => {
recomputedBadges.push(x)
})
}
}
</script>
<div class="flex w-full flex-col items-start gap-2 mt-2 mb-1">
{#if recomputedBadges.length === 0 && !onLoad && !onClick && inputDependencies?.length === 0 && !frontendDependencies}
<p class="text-xs font-semibold text-slate-800 ">
This script has no triggers. It will never run.
</p>
{:else}
<div class="text-sm font-semibold text-slate-800 ">Triggered by</div>
{#if onLoad || onClick}
<div class="w-full">
<div class="text-xs font-semibold text-slate-800 mb-1">Event</div>
<div class="flex flex-row gap-2 flex-wrap">
{#if onLoad}
<span class={classNames(badgeClass, colors['green'])}>On load</span>
{/if}
{#if onClick}
<span class={classNames(badgeClass, colors['green'])}>On click</span>
{/if}
</div>
</div>
{/if}
{#if inputDependencies.length > 0}
<div class="w-full">
<div class="flex justify-between items-center mb-2">
<div class="text-xs font-semibold text-slate-800 mb-1">Values change </div>
</div>
<div class="flex flex-row gap-2 flex-wrap">
{#each inputDependencies as label, index}
<span class={classNames(badgeClass, colors['blue'])}>
{label}
</span>
{/each}
</div>
</div>
{/if}
{#if recomputedBadges?.length > 0}
<div class="w-full">
<div class="text-xs font-semibold text-slate-800 mb-1">Success of</div>
<div class="flex flex-row gap-2 flex-wrap">
{#each recomputedBadges as badge}
<span class={classNames(badgeClass, colors['indigo'])}>{badge}</span>
{/each}
</div>
</div>
{/if}
{/if}
{#if frontendDependencies}
<div class="w-full">
<div class="flex justify-between items-center mb-2">
<div class="text-xs font-semibold text-slate-800 mb-1">Values change (Frontend)</div>
<slot />
</div>
<div class="flex flex-row gap-2 flex-wrap">
{#each frontendDependencies as label, index}
<span class={classNames(badgeClass, colors['red'])}>
{label}
<button
on:click={() => dispatch('delete', { index })}
class="bg-red-300 cursor-pointer hover:bg-red-400 ml-1 rounded-md"
>
<X size={18} class="p-0.5" />
</button>
</span>
{/each}
</div>
</div>
{/if}
</div>
@@ -0,0 +1,23 @@
import type {
StaticAppInput,
ConnectedAppInput,
RowAppInput,
UserAppInput
} from '$lib/components/apps/inputType'
export function getDependencies(
fields:
| Record<string, StaticAppInput | ConnectedAppInput | RowAppInput | UserAppInput>
| undefined
): string[] {
let dependencies: string[] = []
if (!fields) return dependencies
Object.values(fields).forEach((field) => {
if (field.type === 'connected' && dependencies && field.connection) {
dependencies.push(`${field.connection.componentId} - ${field.connection.path}`)
}
})
return dependencies
}