feat(apps): tabs can be made pages or invisible + better frontend scripts reactivity

This commit is contained in:
Ruben Fiszel
2023-03-14 12:34:08 +01:00
parent ac9bd7ef8c
commit cd645d0935
15 changed files with 135 additions and 122 deletions

View File

@@ -23,6 +23,7 @@
export let preclickAction: (() => Promise<void>) | undefined = undefined
export let customCss: ComponentCustomCSS<'button'> | undefined = undefined
export let render: boolean
export let initializing: boolean | undefined = true
export const staticOutputs: string[] = ['loading', 'result']
@@ -112,7 +113,12 @@
}
</script>
<InputValue {id} input={configuration.label} bind:value={labelValue} />
<InputValue
on:done={() => initializing && (initializing = false)}
{id}
input={configuration.label}
bind:value={labelValue}
/>
<InputValue {id} input={configuration.goto} bind:value={gotoUrl} />
<InputValue {id} input={configuration.color} bind:value={color} />
<InputValue {id} input={configuration.size} bind:value={size} />

View File

@@ -1,7 +1,7 @@
<script lang="ts">
import { isCodeInjection } from '$lib/components/flows/utils'
import { deepEqual } from 'fast-equals'
import { getContext } from 'svelte'
import { createEventDispatcher, getContext } from 'svelte'
import type { AppInput, EvalAppInput, UploadAppInput } from '../../inputType'
import type { AppViewerContext } from '../../types'
import { accessPropertyByPath } from '../../utils'
@@ -9,12 +9,18 @@
type T = string | number | boolean | Record<string | number, any> | undefined
export let input: AppInput
export let input: AppInput | undefined
export let value: T
export let id: string | undefined = undefined
export let error: string = ''
export let extraContext: Record<string, any> = {}
const dispatch = createEventDispatcher()
if (input == undefined) {
dispatch('done')
}
let lastInput = input ? JSON.parse(JSON.stringify(input)) : undefined
$: if (input && !deepEqual(input, lastInput)) {
@@ -43,11 +49,14 @@
lastInput.type == 'template' &&
$stateId &&
$state &&
debounce(async () => (value = await getValue(lastInput)))
debounce(async () => {
value = await getValue(lastInput)
dispatch('done')
})
$: lastInput &&
lastInput.type == 'eval' &&
$stateId &&
state &&
$state &&
debounce(async () => (value = await evalExpr(lastInput)))
async function handleConnection() {
@@ -62,6 +71,7 @@
} else {
value = undefined
}
dispatch('done')
}
async function evalExpr(input: EvalAppInput) {

View File

@@ -95,15 +95,19 @@
refreshIfAutoRefresh()
}
$: fields && (lazyStaticValues = computeStaticValues())
$: (runnableInputValues || extraQueryParams || args) && testJobLoader && refreshIfAutoRefresh()
$: runnable?.type == 'runnableByName' &&
runnable.inlineScript?.language == 'frontend' &&
($stateId || $state) &&
refreshIfAutoRefresh()
function refreshIfAutoRefresh() {
if (autoRefresh) {
setDebouncedExecute()
}
}
$: fields && (lazyStaticValues = computeStaticValues())
$: (runnableInputValues || extraQueryParams || args) && testJobLoader && refreshIfAutoRefresh()
// Test job internal state
let testJob: CompletedJob | undefined = undefined
let testJobLoader: TestJobLoader | undefined = undefined

View File

@@ -4,7 +4,6 @@
import type { AppInput } from '../../inputType'
import type { AppViewerContext, ComponentCustomCSS, GridItem } from '../../types'
import { concatCustomCss } from '../../utils'
import InputValue from '../helpers/InputValue.svelte'
export let id: string
export let configuration: Record<string, AppInput>
@@ -12,8 +11,6 @@
export let customCss: ComponentCustomCSS<'container'> | undefined = undefined
export let render: boolean
let noPadding: boolean | undefined = undefined
export const staticOutputs: string[] = []
const { app, focusedGrid, selectedComponent } = getContext<AppViewerContext>('AppViewerContext')
@@ -29,13 +26,10 @@
$: css = concatCustomCss($app.css?.containercomponent, customCss)
</script>
<InputValue {id} input={configuration.noPadding} bind:value={noPadding} />
<div>
<div class="w-full h-full">
{#if $app.subgrids?.[`${id}-0`]}
<SubGridEditor
visible={render}
{noPadding}
{id}
class={css?.container.class}
style={css?.container.style}

View File

@@ -21,7 +21,6 @@
const { app, focusedGrid, selectedComponent } = getContext<AppViewerContext>('AppViewerContext')
let gridContent: string[] | undefined = undefined
let noPadding: boolean | undefined = undefined
let drawerTitle: string | undefined = undefined
let appDrawer: Drawer
@@ -62,7 +61,6 @@
</div>
<InputValue {id} input={configuration.gridContent} bind:value={gridContent} />
<InputValue {id} input={configuration.noPadding} bind:value={noPadding} />
<InputValue {id} input={configuration.drawerTitle} bind:value={drawerTitle} />
<InputValue {id} input={configuration.label} bind:value={labelValue} />
<InputValue {id} input={configuration.color} bind:value={color} />
@@ -81,7 +79,6 @@
{#if $app.subgrids?.[`${id}-0`]}
<SubGridEditor
visible={open && render}
{noPadding}
{id}
class={css?.container.class}
style={css?.container.style}

View File

@@ -20,8 +20,6 @@
const { componentControl } = getContext<AppEditorContext>('AppEditorContext')
let noPadding: boolean | undefined = undefined
function onFocus() {
$focusedGrid = {
parentComponentId: id,
@@ -66,8 +64,6 @@
}
</script>
<InputValue {id} input={configuration.noPadding} bind:value={noPadding} />
<div class="h-full w-full border" on:pointerdown|stopPropagation>
<Splitpanes {horizontal}>
{#each sumedup as paneSize, index (index)}
@@ -76,7 +72,6 @@
<SubGridEditor
visible={render}
noYPadding
{noPadding}
{id}
shouldHighlight={$focusedGrid?.subGridIndex === index}
class={css?.container.class}

View File

@@ -4,7 +4,6 @@
import { initOutput } from '../../editor/appUtils'
import SubGridEditor from '../../editor/SubGridEditor.svelte'
import type { AppInput } from '../../inputType'
import type { Output } from '../../rx'
import type { AppEditorContext, AppViewerContext, ComponentCustomCSS } from '../../types'
import { concatCustomCss } from '../../utils'
import InputValue from '../helpers/InputValue.svelte'
@@ -17,15 +16,16 @@
| ComponentCustomCSS<'tabRow' | 'tabs' | 'selectedTab' | 'container'>
| undefined = undefined
export let render: boolean
export let initializing: boolean | undefined = true
export const staticOutputs: string[] = ['selectedTabIndex']
const { app, worldStore, focusedGrid, selectedComponent } =
const { app, worldStore, focusedGrid, selectedComponent, mode } =
getContext<AppViewerContext>('AppViewerContext')
const { componentControl } = getContext<AppEditorContext>('AppEditorContext')
let selected: string = tabs[0]
let noPadding: boolean | undefined = undefined
let kind: 'tabs' | 'sidebar' | 'invisibleOnView' | undefined = undefined
let tabHeight: number = 0
$: outputs = $worldStore
@@ -86,40 +86,64 @@
$: selectedIndex = tabs?.indexOf(selected) ?? -1
$: css = concatCustomCss($app.css?.tabscomponent, customCss)
let subgridWidth: number | undefined = undefined
</script>
<InputValue {id} input={configuration.noPadding} bind:value={noPadding} />
<InputValue
on:done={() => initializing && (initializing = false)}
{id}
input={configuration.tabsKind}
bind:value={kind}
/>
<div>
<div bind:clientHeight={tabHeight}>
<Tabs bind:selected class={css?.tabRow?.class} style={css?.tabRow?.style}>
<div class={kind == 'sidebar' ? 'flex gap-4 w-full' : 'w-full'}>
{#if !kind || kind == 'tabs' || (kind == 'invisibleOnView' && $mode == 'dnd')}
<div bind:clientHeight={tabHeight}>
<Tabs bind:selected class={css?.tabRow?.class} style={css?.tabRow?.style}>
{#each tabs ?? [] as res}
<Tab
value={res}
class={css?.tabs?.class}
style={css?.tabs?.style}
selectedClass={css?.selectedTab?.class}
selectedStyle={css?.selectedTab?.style}
>
<span class="font-semibold">{res}</span>
</Tab>
{/each}
</Tabs>
</div>
{:else if kind == 'sidebar'}
<div
class="flex gap-y-2 flex-col w-1/6 max-w-[160px] bg-white text-[#2e3440] opacity-80 px-4 pt-4 border-r border-gray-400"
>
{#each tabs ?? [] as res}
<Tab
value={res}
class={css?.tabs?.class}
style={css?.tabs?.style}
selectedClass={css?.selectedTab?.class}
selectedStyle={css?.selectedTab?.style}
<button
class="rounded-sm !truncate text-sm hover:bg-gray-100 hover:border hover:text-black px-1 py-2 {selected ==
res
? 'outline outline-gray-500 outline-1 bg-white text-black'
: ''}"
on:pointerdown|stopPropagation
on:click={() => (selected = res)}>{res}</button
>
<span class="font-semibold">{res}</span>
</Tab>
{/each}
</Tabs>
</div>
{#if $app.subgrids}
{#each tabs ?? [] as res, i}
<SubGridEditor
{id}
visible={render && i === selectedIndex}
bind:subGrid={$app.subgrids[`${id}-${i}`]}
{noPadding}
class={css?.container?.class}
style={css?.container?.style}
containerHeight={componentContainerHeight - tabHeight}
on:focus={() => {
$selectedComponent = id
}}
/>
{/each}
</div>
{/if}
<div bind:clientWidth={subgridWidth} class="w-full">
{#if $app.subgrids}
{#each tabs ?? [] as res, i}<SubGridEditor
{id}
visible={render && i === selectedIndex}
bind:subGrid={$app.subgrids[`${id}-${i}`]}
class={css?.container?.class}
style={css?.container?.style}
containerHeight={componentContainerHeight - tabHeight}
on:focus={() => {
$selectedComponent = id
}}
/>{/each}
{/if}
</div>
</div>

View File

@@ -10,6 +10,7 @@
import Grid from '../svelte-grid/Grid.svelte'
export let containerHeight: number
export let containerWidth: number | undefined = undefined
let classes = ''
export { classes as class }
export let style = ''
@@ -71,7 +72,9 @@
</script>
<div
class="relative w-full subgrid {visible ? 'visible' : 'invisible h-0 overflow-hidden'} "
class="translate-x-0 translate-y-0 relative w-full subgrid {visible
? 'visible'
: 'invisible h-0 overflow-hidden'} "
bind:this={container}
>
<div
@@ -96,6 +99,7 @@
gap={[4, 2]}
scroller={container}
parentWidth={$parentWidth - 17}
{containerWidth}
>
{#each subGrid as gridComponent (gridComponent.id)}
{#if gridComponent?.data?.id && gridComponent?.data?.id === dataItem?.data?.id}

View File

@@ -66,7 +66,7 @@
<div
on:pointerenter={() => (hover = true)}
on:pointerleave={() => (hover = false)}
class="h-full flex flex-col w-full component"
class="h-full flex flex-col w-full component {initializing ? 'overflow-hidden h-0' : ''}"
>
{#if $mode !== 'preview'}
<ComponentHeader
@@ -220,6 +220,7 @@
componentInput={component.componentInput}
bind:staticOutputs={$staticOutputs[component.id]}
recomputeIds={component.recomputeIds}
bind:initializing
{render}
/>
{:else if component.type === 'selectcomponent' || component.type === 'resourceselectcomponent'}
@@ -381,6 +382,7 @@
bind:staticOutputs={$staticOutputs[component.id]}
{componentContainerHeight}
{render}
bind:initializing
/>
{:else if component.type === 'containercomponent'}
<AppContainer
@@ -465,10 +467,7 @@
</div>
</div>
{#if initializing}
<div
out:fade|local={{ duration: 200 }}
class="absolute inset-0 center-center flex-col bg-white text-gray-600 border"
>
<div class="absolute inset-0 center-center flex-col bg-white text-gray-600 border">
<Loader2 class="animate-spin" size={16} />
<span class="text-xs mt-1">Loading</span>
</div>

View File

@@ -192,14 +192,7 @@ export const components: Record<AppComponent['type'], AppComponentConfig> = {
softWrap: true,
id: '',
type: 'containercomponent',
configuration: {
noPadding: {
type: 'static',
fieldType: 'boolean',
value: false,
onlyStatic: true
}
},
configuration: {},
customCss: {
container: { class: '', style: '' }
} as const,
@@ -1242,11 +1235,12 @@ Hello \${ctx.username}
id: '',
type: 'tabscomponent',
configuration: {
noPadding: {
tabsKind: {
fieldType: 'select',
type: 'static',
fieldType: 'boolean',
value: false,
onlyStatic: true
onlyStatic: true,
optionValuesKey: 'tabsKindOptions',
value: 'tabs'
}
},
customCss: {
@@ -1448,12 +1442,6 @@ Hello \${ctx.username}
horizontalAlignment: 'center',
verticalAlignment: 'center',
configuration: {
noPadding: {
type: 'static',
fieldType: 'boolean',
value: false,
onlyStatic: true
},
drawerTitle: {
type: 'static',
fieldType: 'text',
@@ -1560,14 +1548,7 @@ Hello \${ctx.username}
softWrap: true,
id: '',
type: 'verticalsplitpanescomponent',
configuration: {
noPadding: {
type: 'static',
fieldType: 'boolean',
value: false,
onlyStatic: true
}
},
configuration: {},
customCss: {
container: { class: '', style: '' }
} as const,
@@ -1585,14 +1566,7 @@ Hello \${ctx.username}
softWrap: true,
id: '',
type: 'horizontalsplitpanescomponent',
configuration: {
noPadding: {
type: 'static',
fieldType: 'boolean',
value: false,
onlyStatic: true
}
},
configuration: {},
customCss: {
container: { class: '', style: '' }
} as const,

View File

@@ -4,6 +4,7 @@ const buttonColorOptions = [...BUTTON_COLORS]
export const staticValues = {
buttonColorOptions,
tabsKindOptions: ['tabs', 'sidebar', 'invisibleOnView'],
buttonSizeOptions: ['xs', 'sm', 'md', 'lg', 'xl'],
tableSearchOptions: ['By Component', 'By Runnable', 'Disabled'],
chartThemeOptions: ['theme1', 'theme2', 'theme3'],

View File

@@ -71,3 +71,9 @@
Please configure the language in the inline script panel
</span>
{/if}
{#if appInput.runnable?.type === 'runnableByName' && appInput.runnable.inlineScript?.language === 'frontend'}
<span class="text-xs text-gray-500">
If the component is a display component. The script will be recomputed upon any changes to any
output or to the state.
</span>
{/if}

View File

@@ -19,6 +19,7 @@
export let throttleUpdate = 100
export let throttleResize = 100
export let onTopId: string | undefined = undefined
export let containerWidth: number | undefined = undefined
export let scroller = undefined
export let sensor = 20
@@ -34,8 +35,6 @@
let xPerPx = 0
let yPerPx = rowHeight
let containerWidth
$: containerHeight = getContainerHeight(items, yPerPx, getComputedCols)
const pointerup = (ev) => {

View File

@@ -326,7 +326,7 @@
? `transform: translate(${cordDiff.x}px, ${cordDiff.y}px);top:${rect.top}px;left:${rect.left}px;`
: trans
? `transform: translate(${cordDiff.x}px, ${cordDiff.y}px); position:absolute; transition: width 0.2s, height 0.2s;`
: `transition: transform 0.2s, opacity 0.2s; transform: translate(${left}px, ${top}px); `} "
: `transition: transform 0.1s, opacity 0.1s; transform: translate(${left}px, ${top}px); `} "
>
<slot movePointerDown={pointerdown} {resizePointerDown} />
{#if resizable && !item.customResizer}

View File

@@ -1,35 +1,35 @@
export function throttle(func, timeFrame) {
let lastTime = new Date().getTime();
return function (...args) {
let now = new Date().getTime();
if (now - lastTime >= timeFrame) {
func(...args);
lastTime = now;
}
};
let lastTime = new Date().getTime()
return function (...args) {
let now = new Date().getTime()
if (now - lastTime >= timeFrame) {
func(...args)
lastTime = now
}
}
}
export function getRowsCount(items, cols) {
const getItemsMaxHeight = items.map((val) => {
const item = val[cols];
const getItemsMaxHeight = items.map((val) => {
const item = val[cols]
return (item && item.y) + (item && item.h) || 0;
});
return (item && item.y) + (item && item.h) || 0
})
return Math.max(...getItemsMaxHeight, 1);
return Math.max(...getItemsMaxHeight, 1)
}
export const getColumn = (containerWidth, columns) => {
const sortColumns = columns.slice().sort((a, b) => a[0] - b[0]);
const sortColumns = columns.slice().sort((a, b) => a[0] - b[0])
const breakpoint = sortColumns.find((value) => {
const [width] = value;
return containerWidth <= width;
});
const breakpoint = sortColumns.find((value) => {
const [width] = value
return containerWidth <= width
})
if (breakpoint) {
return breakpoint[1];
} else {
return sortColumns[sortColumns.length - 1][1];
}
};
if (breakpoint) {
return breakpoint[1]
} else {
return sortColumns[sortColumns.length - 1][1]
}
}