feat(apps): tabs can be made pages or invisible + better frontend scripts reactivity
This commit is contained in:
@@ -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} />
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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'],
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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) => {
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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]
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user