feat(frontend): App bar as components (#4103)

* feat(frontend): wip

* feat(frontend): add migration to the new top bar

* feat(frontend): fix topbar styling

* feat(frontend): fix migration code

* feat(frontend): fix migration code

* feat(frontend): fix migration code

* feat(frontend): wip

* feat(frontend): done

* feat(frontend): done

* feat(frontend): remove migration

* feat(frontend): fix top bar styling

* feat(frontend): fix sync issues

* feat(frontend): improve style

* feat(frontend): Redesign Recompute all

* feat(frontend): change icon + make the default title font bigger

* feat(frontend): Remove unecesary clearInterval

* feat(frontend): Fix dropdown menu button style
This commit is contained in:
Faton Ramadani
2024-07-29 17:26:18 +02:00
committed by GitHub
parent eb6557a6be
commit fb89eed8fa
18 changed files with 576 additions and 141 deletions
@@ -0,0 +1,68 @@
<script lang="ts">
import { getContext } from 'svelte'
import { initConfig, initOutput } from '../../editor/appUtils'
import {
type AppViewerContext,
type ComponentCustomCSS,
type RichConfigurations
} from '../../types'
import { initCss } from '../../utils'
import { components } from '../../editor/component'
import InitializeComponent from '../helpers/InitializeComponent.svelte'
import ResolveConfig from '../helpers/ResolveConfig.svelte'
import ResolveStyle from '../helpers/ResolveStyle.svelte'
import RecomputeAllWrapper from '../../editor/RecomputeAllWrapper.svelte'
import AlignWrapper from '../helpers/AlignWrapper.svelte'
export let id: string
export let initializing: boolean | undefined = false
export let customCss: ComponentCustomCSS<'jobiddisplaycomponent'> | undefined = undefined
export let configuration: RichConfigurations
export let render: boolean
export let horizontalAlignment: 'left' | 'center' | 'right' | undefined = undefined
const { app, worldStore, policy } = getContext<AppViewerContext>('AppViewerContext')
let resolvedConfig = initConfig(
components['recomputeallcomponent'].initialData.configuration,
configuration
)
initOutput($worldStore, id, {
loading: undefined
})
initializing = false
let css = initCss($app.css?.recomputeallcomponent, customCss)
</script>
{#each Object.keys(components['recomputeallcomponent'].initialData.configuration) as key (key)}
<ResolveConfig
{id}
{key}
bind:resolvedConfig={resolvedConfig[key]}
configuration={configuration[key]}
/>
{/each}
{#each Object.keys(css ?? {}) as key (key)}
<ResolveStyle
{id}
{customCss}
{key}
bind:css={css[key]}
componentStyle={$app.css?.recomputeallcomponent}
/>
{/each}
<InitializeComponent {id} />
<AlignWrapper {horizontalAlignment}>
{#if render && policy}
<RecomputeAllWrapper
containerClass={css?.container?.class}
containerStyle={css?.container?.style}
/>
{/if}
</AlignWrapper>
@@ -204,7 +204,9 @@
<div
class="text-ternary bg-surface-primary flex justify-center items-center h-full w-full"
>
No text
{#if resolvedConfig?.disableNoText === false}
No text
{/if}
</div>
{:else}
<!-- svelte-ignore a11y-click-events-have-key-events -->
@@ -230,11 +232,11 @@
style={css?.text?.style}
>
{String(result)}
{#if resolvedConfig.tooltip && resolvedConfig.tooltip != ''}
<Tooltip>{resolvedConfig.tooltip}</Tooltip>
{/if}
</svelte:element>
{#if resolvedConfig.tooltip && resolvedConfig.tooltip != ''}
<Tooltip>{resolvedConfig.tooltip}</Tooltip>
{/if}
{#if resolvedConfig.copyButton && result}
<div class="flex">
<Button
@@ -79,6 +79,10 @@
const appStore = writable<App>(app)
const selectedComponent = writable<string[] | undefined>(undefined)
$: selectedComponent.subscribe((s) => {
console.log('selectedComponent', s)
})
const mode = writable<EditorMode>('dnd')
const breakpoint = writable<EditorBreakpoint>('lg')
const summaryStore = writable(summary)
@@ -88,6 +92,10 @@
hoveredComponent: undefined
})
summaryStore.subscribe((s) => {
$worldStore?.outputsById['ctx'].summary.set(s)
})
const cssEditorOpen = writable<boolean>(false)
const history = initHistory(app)
@@ -114,7 +122,9 @@
query: Object.fromEntries($page.url.searchParams.entries()),
hash: $page.url.hash,
workspace: $workspaceStore,
mode: 'editor'
mode: 'editor',
summary: $summaryStore,
author: policy.on_behalf_of_email
}
const darkMode: Writable<boolean> = writable(document.documentElement.classList.contains('dark'))
@@ -157,7 +167,8 @@
cssEditorOpen,
previewTheme,
debuggingComponents: writable({}),
replaceStateFn: (path) => replaceState(path, $page.state)
replaceStateFn: (path) => replaceState(path, $page.state),
policy: policy
})
let scale = writable(100)
@@ -9,11 +9,9 @@
EditorBreakpoint,
EditorMode
} from '../types'
import { classNames } from '$lib/utils'
import type { Policy } from '$lib/gen'
import Button from '../../common/button/Button.svelte'
import { Unlock } from 'lucide-svelte'
import RecomputeAllComponents from './RecomputeAllComponents.svelte'
import GridViewer from './GridViewer.svelte'
import Component from './component/Component.svelte'
import { twMerge } from 'tailwind-merge'
@@ -25,6 +23,7 @@
import DarkModeObserver from '$lib/components/DarkModeObserver.svelte'
import { getTheme } from './componentsPanel/themeUtils'
import HiddenComponent from '../components/helpers/HiddenComponent.svelte'
import RecomputeAllComponents from './RecomputeAllComponents.svelte'
export let app: App
export let appPath: string = ''
@@ -37,6 +36,7 @@
export let noBackend: boolean = false
export let isLocked = false
export let hideRefreshBar = false
export let replaceStateFn: (path: string) => void = (path: string) =>
window.history.replaceState(null, '', path)
export let gotoFn: (path: string, opt?: Record<string, any> | undefined) => void = (
@@ -58,7 +58,13 @@
const allIdsInPath = writable<string[]>([])
let ncontext: any = { ...context, workspace, mode: 'viewer' }
let ncontext: any = {
...context,
workspace,
mode: 'viewer',
summary: summary,
author: policy.on_behalf_of_email
}
function hashchange(e: HashChangeEvent) {
ncontext.hash = e.newURL.split('#')[1]
@@ -111,7 +117,8 @@
previewTheme: writable(undefined),
debuggingComponents: writable({}),
replaceStateFn,
gotoFn
gotoFn,
policy
})
let previousSelectedIds: string[] | undefined = undefined
@@ -183,7 +190,7 @@
<svelte:window on:hashchange={hashchange} on:resize={resizeWindow} />
<div class="relative min-h-screen h-full" bind:clientHeight={appHeight}>
<div class="relative h-full" bind:clientHeight={appHeight}>
<div id="app-editor-top-level-drawer" />
<div id="app-editor-select" />
@@ -193,9 +200,9 @@
: 'max-w-7xl'} mx-auto"
id="app-content"
>
{#if $appStore.grid}
{#if $appStore.grid && $appStore.hideLegacyTopBar !== true}
<div
class={classNames(
class={twMerge(
'mx-auto',
hideRefreshBar || $appStore?.norefreshbar ? 'invisible h-0 overflow-hidden' : ''
)}
@@ -4,8 +4,6 @@
import { columnConfiguration, isFixed, toggleFixed } from '../gridUtils'
import { twMerge } from 'tailwind-merge'
import RecomputeAllComponents from './RecomputeAllComponents.svelte'
import type { Policy } from '$lib/gen'
import HiddenComponent from '../components/helpers/HiddenComponent.svelte'
import Component from './component/Component.svelte'
import { push } from '$lib/history'
@@ -14,12 +12,14 @@
import { deepEqual } from 'fast-equals'
import ComponentWrapper from './component/ComponentWrapper.svelte'
import { classNames } from '$lib/utils'
import { BG_PREFIX } from '../utils'
import GridEditorMenu from './GridEditorMenu.svelte'
import RecomputeAllComponents from './RecomputeAllComponents.svelte'
import Toggle from '$lib/components/Toggle.svelte'
import Tooltip from '$lib/components/Tooltip.svelte'
import { BG_PREFIX } from '../utils'
import { Loader2 } from 'lucide-svelte'
import Popover from '$lib/components/Popover.svelte'
import GridEditorMenu from './GridEditorMenu.svelte'
import type { Policy } from '$lib/gen'
export let policy: Policy
@@ -48,52 +48,53 @@
</script>
<div class="w-full z-[1000] overflow-visible h-full">
<div
class="w-full sticky top-0 flex justify-between border-b {$componentActive
? 'invisible'
: 'z-50'} {$connectingInput?.opened ? '' : 'bg-surface'} px-4 py-1 items-center gap-4"
>
<h3 class="truncate">{$summary}</h3>
<div class="flex gap-2 items-center">
<div>
{#if !$connectingInput.opened}
<RecomputeAllComponents />
{#if $app.hideLegacyTopBar !== true}
<div
class="w-full sticky top-0 flex justify-between border-b {$componentActive
? 'invisible'
: 'z-50'} {$connectingInput?.opened ? '' : 'bg-surface'} px-4 py-1 items-center gap-4"
>
<h3 class="truncate">{$summary}</h3>
<div class="flex gap-2 items-center">
<div>
{#if !$connectingInput.opened}
<RecomputeAllComponents />
{/if}
</div>
{#if $bgRuns.length > 0}
<Popover notClickable>
<span class="!text-2xs text-tertiary inline-flex gap-1 items-center"
><Loader2 size={10} class="animate-spin" /> {$bgRuns.length}
</span>
<span slot="text"
><div class="flex flex-col">
{#each $bgRuns as bgRun}
<div class="flex gap-2 items-center">
<div class="text-2xs text-tertiary">{bgRun}</div>
</div>
{/each}
</div></span
>
</Popover>
{:else}
<span class="w-9" />
{/if}
</div>
{#if $bgRuns.length > 0}
<Popover notClickable>
<span class="!text-2xs text-tertiary inline-flex gap-1 items-center"
><Loader2 size={10} class="animate-spin" /> {$bgRuns.length}
</span>
<span slot="text"
><div class="flex flex-col">
{#each $bgRuns as bgRun}
<div class="flex gap-2 items-center">
<div class="text-2xs text-tertiary">{bgRun}</div>
</div>
{/each}
</div></span
>
</Popover>
{:else}
<span class="w-9" />
{/if}
</div>
<div class="flex text-2xs gap-8 items-center">
<div class="py-2 pr-2 text-secondary flex gap-1 items-center">
Hide bar on view
<Toggle size="xs" bind:checked={$app.norefreshbar} />
</div>
<div>
{policy.on_behalf_of ? `Author ${policy.on_behalf_of_email}` : ''}
<Tooltip>
The scripts will be run on behalf of the author and a tight policy ensure security about
the possible inputs of the runnables.
</Tooltip>
<div class="flex text-2xs gap-8 items-center">
<div class="py-2 pr-2 text-secondary flex gap-1 items-center">
Hide bar on view
<Toggle size="xs" bind:checked={$app.norefreshbar} />
</div>
<div>
{policy.on_behalf_of ? `Author ${policy.on_behalf_of_email}` : ''}
<Tooltip>
The scripts will be run on behalf of the author and a tight policy ensure security about
the possible inputs of the runnables.
</Tooltip>
</div>
</div>
</div>
</div>
{/if}
<!-- svelte-ignore a11y-click-events-have-key-events -->
<div
style={$app.css?.['app']?.['grid']?.style}
@@ -1,5 +1,10 @@
<script lang="ts" context="module">
let loading: Writable<boolean> = writable(false)
let progress: Writable<number> = writable(100)
</script>
<script lang="ts">
import { RefreshCw } from 'lucide-svelte'
import { Loader2, RefreshCw, TimerReset } from 'lucide-svelte'
import { getContext, onMount } from 'svelte'
import Button from '../../common/button/Button.svelte'
import type { AppEditorContext, AppViewerContext } from '../types'
@@ -7,15 +12,18 @@
import ButtonDropdown from '$lib/components/common/button/ButtonDropdown.svelte'
import { MenuItem } from '@rgossiaux/svelte-headlessui'
import { classNames } from '$lib/utils'
import { twMerge } from 'tailwind-merge'
import { writable, type Writable } from 'svelte/store'
import Badge from '$lib/components/common/badge/Badge.svelte'
const { runnableComponents, app, initialized } = getContext<AppViewerContext>('AppViewerContext')
const appEditorContext = getContext<AppEditorContext>('AppEditorContext')
let loading: boolean = false
let timeout: NodeJS.Timeout | undefined = undefined
let interval: number | undefined = undefined
let shouldRefresh = false
let firstLoad = false
let progressTimer: NodeJS.Timeout | undefined = undefined
$: !firstLoad &&
$initialized.initializedComponents?.length ==
@@ -32,23 +40,43 @@
return () => {
document.removeEventListener('visibilitychange', visChange)
if (timeout) clearInterval(timeout)
if (progressTimer) clearInterval(progressTimer)
}
})
function onClick(stopAfterClear = true) {
function onClick(stopAfterClear = false) {
if (timeout) {
clearInterval(timeout)
timeout = undefined
shouldRefresh = false
if (progressTimer) {
clearInterval(progressTimer)
progressTimer = undefined
}
if (stopAfterClear) return
}
refresh()
if (interval) {
shouldRefresh = true
timeout = setInterval(refresh, interval)
startProgress()
}
}
function startProgress() {
progress.set(100)
if (progressTimer) clearInterval(progressTimer)
progressTimer = setInterval(() => {
progress.update((n) => {
const newProgress = n - 100 / ((interval ?? 1000) / 100)
if (newProgress <= 0) {
return 0
}
return newProgress
})
}, 100)
}
function setInter(inter: number | undefined) {
interval = inter
onClick(!inter)
@@ -62,7 +90,8 @@
firstLoad = true
isFirstLoad = true
}
loading = true
$loading = true
$progress = 100
console.log('refresh all')
refreshing = []
@@ -95,7 +124,7 @@
.filter(Boolean)
Promise.all(promises).finally(() => {
loading = false
$loading = false
})
}
@@ -104,9 +133,11 @@
if (timeout) {
clearInterval(timeout)
timeout = undefined
if (progressTimer) clearInterval(progressTimer)
}
} else if (shouldRefresh) {
timeout = setInterval(refresh, interval)
startProgress()
}
}
@@ -132,53 +163,86 @@
.join(', ')} -->
<!-- {allItems($app.grid, $app.subgrids).map((x) => x.id)} -->
<div class="flex items-center">
<Button
disabled={componentNumber == 0}
on:click={() => onClick()}
color={timeout ? 'blue' : 'light'}
variant={timeout ? 'contained' : 'border'}
size="xs"
btnClasses="!rounded-r-none text-tertiary !text-2xs {timeout ? '!border !border-blue-500' : ''}"
title="Refresh {componentNumber} component{componentNumber > 1 ? 's' : ''} {interval
? `every ${interval / 1000} seconds`
: 'once'} {refreshing.length > 0 ? `(live: ${refreshing.join(', ')}))` : ''}"
>
<RefreshCw class={loading ? 'animate-spin' : ''} size={14} /> &nbsp;{componentNumber}
</Button>
<div class=" border rounded-md overflow-hidden">
<div class={twMerge('flex items-center')}>
<Button
disabled={componentNumber == 0}
on:click={() => onClick()}
color="light"
size="xs"
variant="border"
btnClasses={twMerge(
'!rounded-none text-tertiary !text-2xs !border-r border-y-0 border-l-0 group'
)}
title="Refresh {componentNumber} component{componentNumber > 1 ? 's' : ''} {interval
? `every ${interval / 1000} seconds`
: 'Once'} {refreshing.length > 0 ? `(live: ${refreshing.join(', ')}))` : ''}"
>
<div class="z-10 flex flex-row items-center gap-2">
{#if !$loading}
<RefreshCw size={14} />
{:else}
<Loader2 class="animate-spin text-blue-500" size={14} />
{/if}
<ButtonDropdown hasPadding={true}>
<svelte:fragment slot="label">
<span
class={classNames('text-xs min-w-[2rem]', interval ? 'text-blue-500' : 'text-tertiary')}
>
{interval ? `${interval / 1000}s` : 'once'}
</span>
</svelte:fragment>
<svelte:fragment slot="items">
{#each items ?? [] as { }, index}
<MenuItem
on:click={() => {
if (index === 0) {
setInter(undefined)
} else {
setInter(index * 5000)
}
}}
>
<div
class={classNames(
'!text-tertiary text-left px-4 py-2 gap-2 cursor-pointer hover:bg-surface-hover !text-xs font-semibold'
)}
>
{#if index === 0}
Once
{:else}
{`Every ${index * 5} seconds`}
{/if}
({componentNumber})
</div>
</Button>
<ButtonDropdown hasPadding={false}>
<slot:fragment slot="buttonReplacement">
<div class="flex flex-row gap-2 text-xs hover:bg-surface-hover px-2 items-center h-7">
{#if interval}
<Badge color="blue" small>
{interval ? `Every ${interval / 1000}s` : 'Once'}
</Badge>
{/if}
<div class="flex justify-center items-center">
<TimerReset size={14} />
</div>
</MenuItem>
{/each}
</svelte:fragment>
</ButtonDropdown>
</div>
</slot:fragment>
<svelte:fragment slot="label">
<span
class={twMerge('text-xs min-w-[2rem] ', interval ? 'text-blue-500' : 'text-tertiary')}
>
{interval ? `${interval / 1000}s` : 'Once'}
</span>
</svelte:fragment>
<svelte:fragment slot="items">
{#each items ?? [] as { }, index}
<MenuItem
on:click={() => {
if (index === 0) {
setInter(undefined)
} else {
setInter(index * 5000)
}
}}
>
<div
class={classNames(
'!text-tertiary text-left px-4 py-2 gap-2 cursor-pointer hover:bg-surface-hover !text-xs font-semibold'
)}
>
{#if index === 0}
Once
{:else}
{`Every ${index * 5} seconds`}
{/if}
</div>
</MenuItem>
{/each}
</svelte:fragment>
</ButtonDropdown>
</div>
{#if interval}
<div class="w-full bg-gray-200 rounded-full h-0.5 dark:bg-gray-700">
<div
class="bg-blue-300 h-0.5 rounded-full dark:bg-blue-500 transition-all"
style="width: {$progress}%"
/>
</div>
{/if}
</div>
@@ -0,0 +1,53 @@
<script lang="ts">
import { getContext } from 'svelte'
import type { AppViewerContext } from '../types'
import RecomputeAllComponents from './RecomputeAllComponents.svelte'
import { dfs } from './appUtils'
import { deepEqual } from 'fast-equals'
import { Loader2 } from 'lucide-svelte'
import Popover from '$lib/components/Popover.svelte'
import { twMerge } from 'tailwind-merge'
export let containerClass: string | undefined = undefined
export let containerStyle: string | undefined = undefined
const { selectedComponent, app, connectingInput, allIdsInPath, bgRuns } =
getContext<AppViewerContext>('AppViewerContext')
let previousSelectedIds: string[] | undefined = undefined
$: if (!deepEqual(previousSelectedIds, $selectedComponent)) {
previousSelectedIds = $selectedComponent
$allIdsInPath = ($selectedComponent ?? [])
.flatMap((id) => dfs($app.grid, id, $app.subgrids ?? {}))
.filter((x) => x != undefined) as string[]
}
</script>
<div
class={twMerge('flex justify-center h-8 items-center gap-2', containerClass)}
style={containerStyle}
>
<div class="w-9">
{#if $bgRuns.length > 0}
<Popover notClickable>
<span class="!text-2xs text-tertiary inline-flex gap-1 items-center"
><Loader2 size={10} class="animate-spin" /> {$bgRuns.length}
</span>
<span slot="text"
><div class="flex flex-col">
{#each $bgRuns as bgRun}
<div class="flex gap-2 items-center">
<div class="text-2xs">{bgRun}</div>
</div>
{/each}
</div></span
>
</Popover>
{/if}
</div>
<div>
{#if !$connectingInput.opened}
<RecomputeAllComponents />
{/if}
</div>
</div>
@@ -1,6 +1,7 @@
import type {
App,
BaseAppComponent,
ComponentCustomCSS,
ConnectingInput,
EditorBreakpoint,
FocusedGrid,
@@ -20,7 +21,7 @@ import { gridColumns } from '../gridUtils'
import { allItems } from '../utils'
import type { Output, World } from '../rx'
import gridHelp from '../svelte-grid/utils/helper'
import type { FilledItem } from '../svelte-grid/types'
import type { FilledItem, Size } from '../svelte-grid/types'
import type {
StaticAppInput,
EvalAppInput,
@@ -241,12 +242,14 @@ export function createNewGridItem(
grid: GridItem[],
id: string,
data: AppComponent,
columns?: Record<number, any>
columns?: Record<number, any>,
initialPosition: { x: number; y: number } = { x: 0, y: 0 },
recOverride?: Record<number, Size>
): GridItem {
const newComponent = {
fixed: false,
x: 0,
y: 0,
x: initialPosition.x,
y: initialPosition.y,
fullHeight: false
}
@@ -260,7 +263,7 @@ export function createNewGridItem(
gridColumns.forEach((column) => {
if (!columns) {
const rec = getRecommendedDimensionsByComponent(newData.type, column)
const rec = recOverride?.[column] ?? getRecommendedDimensionsByComponent(newData.type, column)
newItem[column] = {
...newComponent,
@@ -271,6 +274,7 @@ export function createNewGridItem(
newItem[column] = columns[column]
}
const position = gridHelp.findSpace(newItem, grid, column) as { x: number; y: number }
newItem[column] = { ...newItem[column], ...position }
})
@@ -325,7 +329,13 @@ export function cleanseOneOfConfiguration(
export function appComponentFromType<T extends keyof typeof components>(
type: T,
overrideConfiguration?: Partial<InitialAppComponent['configuration']>,
extra?: any
extra?: any,
override?: {
customCss?: ComponentCustomCSS<T>
verticalAlignment?: 'top' | 'center' | 'bottom'
horizontalAlignment?: 'left' | 'center' | 'right'
componnetInput?: Partial<InitialAppComponent['componentInput']>
}
): (id: string) => BaseAppComponent & BaseComponent<T> {
return (id: string) => {
const init = JSON.parse(JSON.stringify(ccomponents[type].initialData)) as InitialAppComponent
@@ -351,19 +361,19 @@ export function appComponentFromType<T extends keyof typeof components>(
type,
//TODO remove tooltip from there
configuration: deepMergeWithPriority(configuration, overrideConfiguration ?? {}),
componentInput: init.componentInput,
componentInput: override?.componnetInput ?? init.componentInput,
panes: init.panes,
tabs: init.tabs,
conditions: init.conditions,
nodes: init.nodes,
customCss: ccomponents[type].customCss as any,
customCss: deepMergeWithPriority(ccomponents[type].customCss as any, override?.customCss),
recomputeIds: init.recomputeIds ? [] : undefined,
actionButtons: init.actionButtons ? [] : undefined,
actions: [],
menuItems: init.menuItems ? [] : undefined,
numberOfSubgrids: init.numberOfSubgrids,
horizontalAlignment: init.horizontalAlignment,
verticalAlignment: init.verticalAlignment,
horizontalAlignment: override?.horizontalAlignment ?? init.horizontalAlignment,
verticalAlignment: override?.verticalAlignment ?? init.verticalAlignment,
id,
...(extra ?? {})
}
@@ -374,7 +384,9 @@ export function insertNewGridItem(
builddata: (id: string) => AppComponent,
focusedGrid: FocusedGrid | undefined,
columns?: Record<string, any>,
keepId?: string
keepId?: string,
initialPosition: { x: number; y: number } = { x: 0, y: 0 },
recOverride?: Record<number, Size>
): string {
const id = keepId ?? getNextGridItemId(app)
@@ -421,7 +433,7 @@ export function insertNewGridItem(
let grid = focusedGrid ? app.subgrids[key!] : app.grid
const newItem = createNewGridItem(grid, id, data, columns)
const newItem = createNewGridItem(grid, id, data, columns, initialPosition, recOverride)
grid.push(newItem)
return id
}
@@ -1026,3 +1038,94 @@ export function isTableAction(id: string, app: App): boolean {
}
return true
}
export function setUpTopBarComponentContent(id: string, app: App) {
insertNewGridItem(
app,
appComponentFromType(
'textcomponent',
{
disableNoText: {
value: true,
type: 'static',
fieldType: 'boolean'
},
tooltip: {
type: 'evalv2',
fieldType: 'text',
expr: '`Author: ${ctx.author}`',
connections: [
{
componentId: 'ctx',
id: 'author'
}
]
}
},
undefined,
{
customCss: {
text: {
class: 'text-xl font-semibold whitespace-nowrap truncate' as any,
style: ''
}
},
verticalAlignment: 'center',
componnetInput: {
type: 'templatev2',
fieldType: 'template',
eval: '${ctx.summary}',
connections: [
{
id: 'summary',
componentId: 'ctx'
}
] as InputConnectionEval[]
}
}
) as (id: string) => AppComponent,
{
parentComponentId: id,
subGridIndex: 0
},
undefined,
undefined,
undefined,
{
3: {
w: 6,
h: 1
},
12: {
w: 6,
h: 1
}
}
)
insertNewGridItem(
app,
appComponentFromType('recomputeallcomponent', undefined, undefined, {
horizontalAlignment: 'right'
}) as (id: string) => AppComponent,
{
parentComponentId: id,
subGridIndex: 0
},
undefined,
undefined,
undefined,
{
3: {
w: 3,
h: 1
},
12: {
w: 6,
h: 1
}
}
)
}
@@ -77,7 +77,8 @@
import AppNumberInput from '../../components/inputs/AppNumberInput.svelte'
import AppNavbar from '../../components/display/AppNavbar.svelte'
import AppDateSelect from '../../components/inputs/AppDateSelect.svelte'
import AppDisplayComponentByJobId from '../../components/display/AppDisplayComponentByJobId.svelte'
import AppDisplayComponentByJobId from '../../components/display/AppRecomputeAll.svelte'
import AppRecomputeAll from '../../components/display/AppRecomputeAll.svelte'
export let component: AppComponent
export let selected: boolean
@@ -856,6 +857,15 @@
configuration={component.configuration}
{render}
/>
{:else if component.type === 'recomputeallcomponent'}
<AppRecomputeAll
id={component.id}
customCss={component.customCss}
bind:initializing
configuration={component.configuration}
horizontalAlignment={component.horizontalAlignment}
{render}
/>
{/if}
</div>
</div>
@@ -49,7 +49,9 @@ import {
AlertTriangle,
Clock,
CalendarClock,
AppWindow
AppWindow,
PanelTop,
RefreshCw
} from 'lucide-svelte'
import type {
Aligned,
@@ -279,6 +281,8 @@ export type NavBarComponent = BaseComponent<'navbarcomponent'> & {
export type DateSelectComponent = BaseComponent<'dateselectcomponent'>
export type RecomputeAllComponent = BaseComponent<'recomputeallcomponent'>
export type TypedComponent =
| DBExplorerComponent
| DisplayComponent
@@ -356,6 +360,7 @@ export type TypedComponent =
| NavBarComponent
| DateSelectComponent
| JobIdDisplayComponent
| RecomputeAllComponent
export type AppComponent = BaseAppComponent & TypedComponent
@@ -368,7 +373,18 @@ export function getRecommendedDimensionsByComponent(
componentType: AppComponent['type'],
column: number
): Size {
const size = components[componentType].dims.split('-')[column === 3 ? 0 : 1].split(':')
return processDimension(components[componentType].dims, column)
}
export function processDimension(
dimension: AppComponentDimensions | undefined,
column: number
): Size {
if (!dimension) {
return { w: 1, h: 1 }
}
const size = dimension.split('-')[column === 3 ? 0 : 1].split(':')
return { w: +size[0], h: +size[1] }
}
@@ -398,6 +414,7 @@ export type PresetComponentConfig = {
targetComponent: keyof typeof components
configuration: object
type: string
dims?: AppComponentDimensions
}
export interface InitialAppComponent extends Partial<Aligned> {
@@ -1134,6 +1151,12 @@ export const components = {
fieldType: 'text',
tooltip: 'Tooltip text if not empty'
},
disableNoText: {
type: 'static',
value: false,
fieldType: 'boolean',
tooltip: 'Remove the "No text" placeholder'
}
}
}
@@ -4029,6 +4052,21 @@ See date-fns format for more information. By default, it is 'dd.MM.yyyy HH:mm'
}
}
}
},
recomputeallcomponent: {
name: 'Recompute all',
icon: RefreshCw,
documentationLink: `${documentationBaseUrl}/top_bar`,
dims: '4:1-6:1' as AppComponentDimensions,
customCss: {
container: { style: '', class: '' }
},
initialData: {
...defaultAlignement,
componentInput: undefined,
configuration: {},
menuItems: true
}
}
} as const
@@ -4054,6 +4092,14 @@ export const presetComponents = {
}
},
type: 'invisibletabscomponent'
},
topbarcomponent: {
name: 'Top Bar',
icon: PanelTop,
targetComponent: 'containercomponent' as const,
configuration: {},
type: 'topbarcomponent',
dims: '6:2-12:2' as AppComponentDimensions
}
}
@@ -20,8 +20,10 @@ const layout: ComponentSet = {
'steppercomponent',
'carousellistcomponent',
'decisiontreecomponent',
'navbarcomponent'
]
'navbarcomponent',
'recomputeallcomponent'
],
presets: ['topbarcomponent']
} as const
const buttons: ComponentSet = {
@@ -7,10 +7,16 @@
COMPONENT_SETS,
type AppComponent,
type TypedComponent,
DEPRECATED_COMPONENTS
DEPRECATED_COMPONENTS,
processDimension
} from '../component'
import ListItem from './ListItem.svelte'
import { appComponentFromType, copyComponent, insertNewGridItem } from '../appUtils'
import {
appComponentFromType,
copyComponent,
insertNewGridItem,
setUpTopBarComponentContent
} from '../appUtils'
import { push } from '$lib/history'
import { ClearableInput, Drawer, DrawerContent } from '../../../common'
import { enterpriseLicense, workspaceStore } from '$lib/stores'
@@ -136,13 +142,30 @@
const id = insertNewGridItem(
$app,
appComponentFromType(preset.targetComponent, preset.configuration) as (
id: string
) => AppComponent,
$focusedGrid
appComponentFromType(preset.targetComponent, preset.configuration, undefined, {
customCss: {
container: {
class: '!p-0' as any,
style: ''
}
}
}) as (id: string) => AppComponent,
$focusedGrid,
undefined,
undefined,
{ x: 0, y: 0 },
{
3: processDimension(preset.dims, 3),
12: processDimension(preset.dims, 12)
}
)
$selectedComponent = [id]
if (appComponentType === 'topbarcomponent') {
setUpTopBarComponentContent(id, $app)
}
$app = $app
}
@@ -150,7 +173,7 @@
$: componentsFiltered = COMPONENT_SETS.map((set) => ({
...set,
components: set.components.filter((component) => {
components: set.components?.filter((component) => {
const name = componentsRecord[component].name.toLowerCase()
return name.includes(search.toLowerCase().trim())
}),
@@ -796,5 +796,8 @@ export const quickStyleProperties: Record<
jobiddisplaycomponent: {
header: [...containerDefaultProps, typographyGrouping],
container: containerDefaultProps
},
recomputeallcomponent: {
container: containerDefaultProps
}
}
+4 -1
View File
@@ -1,5 +1,5 @@
import type { Schema } from '$lib/common'
import type { Preview } from '$lib/gen'
import type { Policy, Preview } from '$lib/gen'
import type { History } from '$lib/history'
import type { Writable } from 'svelte/store'
@@ -154,11 +154,13 @@ export type App = {
name: string
inlineScript: InlineScript
}>
//TODO: should be called hidden runnables but migration tbd
hiddenInlineScripts: Array<HiddenRunnable>
css?: Partial<Record<AppCssItemName, Record<string, ComponentCssProperty>>>
subgrids?: Record<string, GridItem[]>
theme: AppTheme | undefined
hideLegacyTopBar?: boolean | undefined
}
export type ConnectingInput = {
@@ -271,6 +273,7 @@ export type AppViewerContext = {
debuggingComponents: Writable<Record<string, number>>
replaceStateFn?: ((url: string) => void) | undefined
gotoFn?: ((url: string, opt?: Record<string, any> | undefined) => void) | undefined
policy: Policy
}
export type AppEditorContext = {
+1 -1
View File
@@ -1,7 +1,7 @@
import type { Schema } from '$lib/common'
import { twMerge } from 'tailwind-merge'
import type { AppComponent } from './editor/component'
import { type AppComponent } from './editor/component'
import type { AppInput, InputType, ResultAppInput, StaticAppInput } from './inputType'
import type { Output } from './rx'
import type {
@@ -7,6 +7,8 @@
export let hasPadding: boolean = true
export let target: string | undefined = 'body'
export let disabled: boolean = false
const [popperRef, popperContent] = createPopperActions({ placement: 'auto' })
const popperOptions: PopperOptions<{}> = {
@@ -28,7 +30,7 @@
<span use:popperRef>
<MenuButton
class={twMerge('h-full w-full flex flex-row gap-2 items-center', hasPadding ? 'px-2' : '')}
{disabled}
>
{#if $$slots.buttonReplacement}
<slot name="buttonReplacement" />
-1
View File
@@ -556,7 +556,6 @@ export function deepMergeWithPriority<T>(target: T, source: T): T {
for (const key in source) {
if (source.hasOwnProperty(key) && merged?.hasOwnProperty(key)) {
console.log(target)
if (target?.hasOwnProperty(key)) {
merged[key] = deepMergeWithPriority(target[key], source[key])
} else {
@@ -8,9 +8,19 @@
import { userStore, workspaceStore } from '$lib/stores'
import type { App } from '$lib/components/apps/types'
import { afterNavigate, replaceState } from '$app/navigation'
import { goto } from '$lib/navigation'
import { sendUserToast } from '$lib/toast'
import { goto } from '$lib/navigation'
import { sendUserToast } from '$lib/toast'
import { DEFAULT_THEME } from '$lib/components/apps/editor/componentsPanel/themeUtils'
import {
presets,
processDimension,
type AppComponent
} from '$lib/components/apps/editor/component'
import {
appComponentFromType,
insertNewGridItem,
setUpTopBarComponentContent
} from '$lib/components/apps/editor/appUtils'
let nodraft = $page.url.searchParams.get('nodraft')
const hubId = $page.url.searchParams.get('hub')
@@ -105,6 +115,34 @@
}
])
value = decodeState(state)
} else {
const preset = presets['topbarcomponent']
const id = insertNewGridItem(
value,
appComponentFromType(preset.targetComponent, preset.configuration, undefined, {
customCss: {
container: {
class: '!p-0' as any,
style: ''
}
}
}) as (id: string) => AppComponent,
undefined,
undefined,
undefined,
{ x: 0, y: 0 },
{
3: processDimension(preset.dims, 3),
12: processDimension(preset.dims, 12)
}
)
setUpTopBarComponentContent(id, value)
value.hideLegacyTopBar = true
value = value
}
}
</script>