mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-08-18 16:02:10 +00:00
feat(frontend): app splitpanes (#1248)
* feat(frontend): app splitpanes * feat(frontend): app splitpanes vertical * feat(frontend): support both splitpanes * done * done * default select value * container height --------- Co-authored-by: Ruben Fiszel <ruben@rubenfiszel.com>
This commit is contained in:
@@ -213,7 +213,7 @@
|
||||
<div class="my-10" />
|
||||
<Button
|
||||
color="light"
|
||||
size="sm"
|
||||
size="xs"
|
||||
endIcon={{ icon: viewCliOptions ? faChevronUp : faChevronDown }}
|
||||
on:click={() => (viewCliOptions = !viewCliOptions)}
|
||||
>
|
||||
|
||||
@@ -52,14 +52,21 @@
|
||||
<div class="flex flex-col w-full">
|
||||
<div class="flex items-center w-full gap-1 px-1">
|
||||
<span class={css?.limits?.class ?? ''} style={css?.limits?.style ?? ''}>
|
||||
{min}
|
||||
{+min}
|
||||
</span>
|
||||
<div class="grow" on:pointerdown|stopPropagation>
|
||||
<RangeSlider bind:slider bind:values {step} min={min ?? 0} max={max ?? 1} range />
|
||||
<RangeSlider
|
||||
bind:slider
|
||||
bind:values
|
||||
{step}
|
||||
min={!min ? 0 : +min}
|
||||
max={!max ? 1 : +max}
|
||||
range
|
||||
/>
|
||||
<!-- <RangeSlider {step} range min={min ?? 0} max={max ?? 1} bind:values /> -->
|
||||
</div>
|
||||
<span class={css?.limits?.class ?? ''} style={css?.limits?.style ?? ''}>
|
||||
{max}
|
||||
{+max}
|
||||
</span>
|
||||
</div>
|
||||
<div class="flex justify-between px-1">
|
||||
|
||||
@@ -70,11 +70,21 @@
|
||||
}
|
||||
|
||||
$: css = concatCustomCss($app.css?.selectcomponent, customCss)
|
||||
|
||||
let defaultValue: any = undefined
|
||||
|
||||
$: {
|
||||
if (defaultValue) {
|
||||
value = JSON.stringify(defaultValue)
|
||||
outputs?.result.set(defaultValue)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<InputValue {id} input={configuration.items} bind:value={items} />
|
||||
<InputValue {id} input={configuration.multiple} bind:value={multiple} />
|
||||
<InputValue {id} input={configuration.placeholder} bind:value={placeholder} />
|
||||
<InputValue {id} input={configuration.defaultValue} bind:value={defaultValue} />
|
||||
|
||||
<AlignWrapper {horizontalAlignment} {verticalAlignment}>
|
||||
<div class="app-select w-full mx-0.5" style="height: 34px" on:pointerdown|stopPropagation>
|
||||
|
||||
@@ -58,13 +58,13 @@
|
||||
<AlignWrapper {verticalAlignment}>
|
||||
<div class="flex items-center w-full gap-1 px-1">
|
||||
<span class={css?.limits?.class ?? ''} style={css?.limits?.style ?? ''}>
|
||||
{min}
|
||||
{+min}
|
||||
</span>
|
||||
<div class="grow" on:pointerdown|stopPropagation={() => ($selectedComponent = id)}>
|
||||
<RangeSlider bind:slider bind:values {step} min={min ?? 0} max={max ?? 1} />
|
||||
<RangeSlider bind:slider bind:values {step} min={+min} max={+max} />
|
||||
</div>
|
||||
<span class={css?.limits?.class ?? ''} style={css?.limits?.style ?? ''}>
|
||||
{max}
|
||||
{+max}
|
||||
</span>
|
||||
<span class="mx-2">
|
||||
<span
|
||||
|
||||
@@ -16,8 +16,6 @@
|
||||
export const staticOutputs: string[] = []
|
||||
const { app, focusedGrid, selectedComponent } = getContext<AppEditorContext>('AppEditorContext')
|
||||
|
||||
let gridContent: string[] | undefined = undefined
|
||||
|
||||
function onFocus() {
|
||||
$focusedGrid = {
|
||||
parentComponentId: id,
|
||||
@@ -31,18 +29,19 @@
|
||||
</script>
|
||||
|
||||
<InputValue {id} input={configuration.noPadding} bind:value={noPadding} />
|
||||
<InputValue {id} input={configuration.gridContent} bind:value={gridContent} />
|
||||
|
||||
{#if $app.subgrids?.[`${id}-0`]}
|
||||
<SubGridEditor
|
||||
{noPadding}
|
||||
{id}
|
||||
class={css?.container.class}
|
||||
style={css?.container.style}
|
||||
bind:subGrid={$app.subgrids[`${id}-0`]}
|
||||
containerHeight={componentContainerHeight}
|
||||
on:focus={() => {
|
||||
$selectedComponent = id
|
||||
}}
|
||||
/>
|
||||
{/if}
|
||||
<div class="border">
|
||||
{#if $app.subgrids?.[`${id}-0`]}
|
||||
<SubGridEditor
|
||||
{noPadding}
|
||||
{id}
|
||||
class={css?.container.class}
|
||||
style={css?.container.style}
|
||||
bind:subGrid={$app.subgrids[`${id}-0`]}
|
||||
containerHeight={componentContainerHeight}
|
||||
on:focus={() => {
|
||||
$selectedComponent = id
|
||||
}}
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
<script lang="ts">
|
||||
import { getContext } from 'svelte'
|
||||
import SubGridEditor from '../../editor/SubGridEditor.svelte'
|
||||
import type { AppInput } from '../../inputType'
|
||||
import type { AppEditorContext, ComponentCustomCSS } from '../../types'
|
||||
import { concatCustomCss } from '../../utils'
|
||||
import InputValue from '../helpers/InputValue.svelte'
|
||||
import { Pane, Splitpanes } from 'svelte-splitpanes'
|
||||
|
||||
export let id: string
|
||||
export let configuration: Record<string, AppInput>
|
||||
export let componentContainerHeight: number
|
||||
export let customCss: ComponentCustomCSS<'container'> | undefined = undefined
|
||||
export let horizontal: boolean = false
|
||||
export let panes: number[]
|
||||
|
||||
export const staticOutputs: string[] = []
|
||||
|
||||
const { app, focusedGrid, selectedComponent } = getContext<AppEditorContext>('AppEditorContext')
|
||||
|
||||
let noPadding: boolean | undefined = undefined
|
||||
|
||||
function onFocus() {
|
||||
$focusedGrid = {
|
||||
parentComponentId: id,
|
||||
subGridIndex: 0
|
||||
}
|
||||
}
|
||||
|
||||
$: $selectedComponent === id && onFocus()
|
||||
$: css = concatCustomCss($app.css?.containercomponent, customCss)
|
||||
|
||||
$: sumedup = panes.map((x) => (x / panes.reduce((a, b) => a + b, 0)) * 100)
|
||||
</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}
|
||||
<Pane bind:size={paneSize}>
|
||||
{#if $app.subgrids?.[`${id}-${index}`]}
|
||||
<SubGridEditor
|
||||
noYPadding
|
||||
{noPadding}
|
||||
{id}
|
||||
shouldHighlight={$focusedGrid?.subGridIndex === index}
|
||||
class={css?.container.class}
|
||||
style={css?.container.style}
|
||||
bind:subGrid={$app.subgrids[`${id}-${index}`]}
|
||||
containerHeight={horizontal
|
||||
? (componentContainerHeight * paneSize) / 100 - 1
|
||||
: componentContainerHeight}
|
||||
on:focus={() => {
|
||||
$selectedComponent = id
|
||||
$focusedGrid = {
|
||||
parentComponentId: id,
|
||||
subGridIndex: index
|
||||
}
|
||||
}}
|
||||
/>
|
||||
{/if}
|
||||
</Pane>
|
||||
{/each}
|
||||
</Splitpanes>
|
||||
</div>
|
||||
@@ -164,6 +164,10 @@
|
||||
<Splitpanes horizontal>
|
||||
<Pane size={$connectingInput?.opened ? 100 : 70}>
|
||||
<div
|
||||
on:pointerdown={(e) => {
|
||||
$selectedComponent = undefined
|
||||
$focusedGrid = undefined
|
||||
}}
|
||||
class={twMerge(
|
||||
'bg-gray-100 h-full w-full',
|
||||
$appStore.css?.['app']?.['viewer']?.class
|
||||
@@ -177,7 +181,7 @@
|
||||
)}
|
||||
>
|
||||
{#if $appStore.grid}
|
||||
<div class={width}>
|
||||
<div on:pointerdown|stopPropagation class={width}>
|
||||
<GridEditor {policy} />
|
||||
</div>
|
||||
|
||||
|
||||
@@ -145,7 +145,7 @@
|
||||
$focusedGrid = undefined
|
||||
}}
|
||||
>
|
||||
<div class={!$focusedGrid ? 'border-indigo-600 border-2 border-dashed' : ''}>
|
||||
<div class={!$focusedGrid && $mode !== 'preview' ? 'border-gray-400 border border-dashed' : ''}>
|
||||
<Grid
|
||||
onTopId={$selectedComponent}
|
||||
fillSpace={false}
|
||||
|
||||
@@ -13,16 +13,18 @@
|
||||
export { classes as class }
|
||||
export let style = ''
|
||||
export let noPadding = false
|
||||
export let noYPadding = false
|
||||
export let subGrid: GridItem[] = []
|
||||
export let visible: boolean = true
|
||||
export let id: string
|
||||
export let shouldHighlight: boolean = true
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
const { app, connectingInput, selectedComponent, focusedGrid } =
|
||||
const { app, connectingInput, selectedComponent, focusedGrid, mode } =
|
||||
getContext<AppEditorContext>('AppEditorContext')
|
||||
|
||||
$: highlight = id === $focusedGrid?.parentComponentId
|
||||
$: highlight = id === $focusedGrid?.parentComponentId && shouldHighlight
|
||||
|
||||
let pointerdown = false
|
||||
let onComponent: string | undefined = undefined
|
||||
@@ -69,13 +71,20 @@
|
||||
bind:this={container}
|
||||
>
|
||||
<div
|
||||
class={twMerge('py-2 overflow-auto', classes ?? '', noPadding ? 'px-0' : 'px-2')}
|
||||
class={twMerge(
|
||||
'overflow-auto',
|
||||
noYPadding ? '' : 'py-2',
|
||||
classes ?? '',
|
||||
noPadding ? 'px-0' : 'px-2'
|
||||
)}
|
||||
on:pointerdown|stopPropagation={onpointerdown}
|
||||
on:pointerleave={onpointerup}
|
||||
on:pointerup={onpointerup}
|
||||
style="height: {containerHeight}px; {style ?? ''}"
|
||||
>
|
||||
<div class={highlight ? 'border-indigo-600 border-2 border-dashed' : ''}>
|
||||
<div
|
||||
class={highlight && $mode !== 'preview' ? 'border-gray-400 border border-dashed h-full' : ''}
|
||||
>
|
||||
<Grid
|
||||
bind:items={subGrid}
|
||||
let:dataItem
|
||||
|
||||
@@ -38,6 +38,7 @@
|
||||
import type { AppComponent } from './components'
|
||||
import AppAggridTable from '../../components/display/table/AppAggridTable.svelte'
|
||||
import AppDrawer from '../../components/layout/AppDrawer.svelte'
|
||||
import AppSplitpanes from '../../components/layout/AppSplitpanes.svelte'
|
||||
import { deepEqual } from 'fast-equals'
|
||||
|
||||
export let component: AppComponent
|
||||
@@ -176,6 +177,8 @@
|
||||
{:else if component.type === 'textcomponent'}
|
||||
<AppText
|
||||
id={component.id}
|
||||
verticalAlignment={component.verticalAlignment}
|
||||
horizontalAlignment={component.horizontalAlignment}
|
||||
configuration={component.configuration}
|
||||
customCss={component.customCss}
|
||||
bind:initializing
|
||||
@@ -185,6 +188,8 @@
|
||||
{:else if component.type === 'buttoncomponent'}
|
||||
<AppButton
|
||||
id={component.id}
|
||||
verticalAlignment={component.verticalAlignment}
|
||||
horizontalAlignment={component.horizontalAlignment}
|
||||
configuration={component.configuration}
|
||||
customCss={component.customCss}
|
||||
componentInput={component.componentInput}
|
||||
@@ -193,6 +198,8 @@
|
||||
{:else if component.type === 'selectcomponent'}
|
||||
<AppSelect
|
||||
id={component.id}
|
||||
verticalAlignment={component.verticalAlignment}
|
||||
horizontalAlignment={component.horizontalAlignment}
|
||||
configuration={component.configuration}
|
||||
customCss={component.customCss}
|
||||
bind:staticOutputs={$staticOutputs[component.id]}
|
||||
@@ -200,6 +207,7 @@
|
||||
{:else if component.type === 'formcomponent'}
|
||||
<AppForm
|
||||
id={component.id}
|
||||
horizontalAlignment={component.horizontalAlignment}
|
||||
configuration={component.configuration}
|
||||
customCss={component.customCss}
|
||||
componentInput={component.componentInput}
|
||||
@@ -208,6 +216,8 @@
|
||||
{:else if component.type === 'formbuttoncomponent'}
|
||||
<AppFormButton
|
||||
id={component.id}
|
||||
verticalAlignment={component.verticalAlignment}
|
||||
horizontalAlignment={component.horizontalAlignment}
|
||||
configuration={component.configuration}
|
||||
customCss={component.customCss}
|
||||
componentInput={component.componentInput}
|
||||
@@ -216,6 +226,8 @@
|
||||
{:else if component.type === 'checkboxcomponent'}
|
||||
<AppCheckbox
|
||||
id={component.id}
|
||||
verticalAlignment={component.verticalAlignment}
|
||||
horizontalAlignment={component.horizontalAlignment}
|
||||
configuration={component.configuration}
|
||||
customCss={component.customCss}
|
||||
bind:staticOutputs={$staticOutputs[component.id]}
|
||||
@@ -223,12 +235,14 @@
|
||||
{:else if component.type === 'textinputcomponent'}
|
||||
<AppTextInput
|
||||
id={component.id}
|
||||
verticalAlignment={component.verticalAlignment}
|
||||
configuration={component.configuration}
|
||||
customCss={component.customCss}
|
||||
bind:staticOutputs={$staticOutputs[component.id]}
|
||||
/>
|
||||
{:else if component.type === 'passwordinputcomponent'}
|
||||
<AppTextInput
|
||||
verticalAlignment={component.verticalAlignment}
|
||||
configuration={component.configuration}
|
||||
inputType="password"
|
||||
appCssKey="passwordinputcomponent"
|
||||
@@ -238,6 +252,7 @@
|
||||
/>
|
||||
{:else if component.type === 'dateinputcomponent'}
|
||||
<AppDateInput
|
||||
verticalAlignment={component.verticalAlignment}
|
||||
configuration={component.configuration}
|
||||
inputType="date"
|
||||
id={component.id}
|
||||
@@ -246,6 +261,7 @@
|
||||
/>
|
||||
{:else if component.type === 'numberinputcomponent'}
|
||||
<AppNumberInput
|
||||
verticalAlignment={component.verticalAlignment}
|
||||
configuration={component.configuration}
|
||||
id={component.id}
|
||||
customCss={component.customCss}
|
||||
@@ -253,6 +269,7 @@
|
||||
/>
|
||||
{:else if component.type === 'currencycomponent'}
|
||||
<AppCurrencyInput
|
||||
verticalAlignment={component.verticalAlignment}
|
||||
configuration={component.configuration}
|
||||
id={component.id}
|
||||
customCss={component.customCss}
|
||||
@@ -260,6 +277,7 @@
|
||||
/>
|
||||
{:else if component.type === 'slidercomponent'}
|
||||
<AppSliderInputs
|
||||
verticalAlignment={component.verticalAlignment}
|
||||
configuration={component.configuration}
|
||||
id={component.id}
|
||||
customCss={component.customCss}
|
||||
@@ -267,6 +285,8 @@
|
||||
/>
|
||||
{:else if component.type === 'horizontaldividercomponent'}
|
||||
<AppDivider
|
||||
verticalAlignment={component.verticalAlignment}
|
||||
horizontalAlignment={component.horizontalAlignment}
|
||||
configuration={component.configuration}
|
||||
id={component.id}
|
||||
customCss={component.customCss}
|
||||
@@ -274,6 +294,8 @@
|
||||
/>
|
||||
{:else if component.type === 'verticaldividercomponent'}
|
||||
<AppDivider
|
||||
verticalAlignment={component.verticalAlignment}
|
||||
horizontalAlignment={component.horizontalAlignment}
|
||||
configuration={component.configuration}
|
||||
id={component.id}
|
||||
customCss={component.customCss}
|
||||
@@ -281,6 +303,7 @@
|
||||
/>
|
||||
{:else if component.type === 'rangecomponent'}
|
||||
<AppRangeInput
|
||||
verticalAlignment={component.verticalAlignment}
|
||||
configuration={component.configuration}
|
||||
id={component.id}
|
||||
customCss={component.customCss}
|
||||
@@ -303,8 +326,29 @@
|
||||
bind:staticOutputs={$staticOutputs[component.id]}
|
||||
{componentContainerHeight}
|
||||
/>
|
||||
{:else if component.type === 'verticalsplitpanescomponent'}
|
||||
<AppSplitpanes
|
||||
configuration={component.configuration}
|
||||
id={component.id}
|
||||
customCss={component.customCss}
|
||||
panes={component.panes}
|
||||
bind:staticOutputs={$staticOutputs[component.id]}
|
||||
{componentContainerHeight}
|
||||
/>
|
||||
{:else if component.type === 'horizontalsplitpanescomponent'}
|
||||
<AppSplitpanes
|
||||
configuration={component.configuration}
|
||||
id={component.id}
|
||||
customCss={component.customCss}
|
||||
panes={component.panes}
|
||||
bind:staticOutputs={$staticOutputs[component.id]}
|
||||
{componentContainerHeight}
|
||||
horizontal={true}
|
||||
/>
|
||||
{:else if component.type === 'iconcomponent'}
|
||||
<AppIcon
|
||||
verticalAlignment={component.verticalAlignment}
|
||||
horizontalAlignment={component.horizontalAlignment}
|
||||
configuration={component.configuration}
|
||||
id={component.id}
|
||||
customCss={component.customCss}
|
||||
@@ -326,6 +370,8 @@
|
||||
/>
|
||||
{:else if component.type === 'drawercomponent'}
|
||||
<AppDrawer
|
||||
verticalAlignment={component.verticalAlignment}
|
||||
horizontalAlignment={component.horizontalAlignment}
|
||||
configuration={component.configuration}
|
||||
id={component.id}
|
||||
customCss={component.customCss}
|
||||
|
||||
@@ -28,7 +28,9 @@ import {
|
||||
SeparatorVertical,
|
||||
Paperclip,
|
||||
Image,
|
||||
SidebarClose
|
||||
SidebarClose,
|
||||
FlipHorizontal,
|
||||
FlipVertical
|
||||
} from 'lucide-svelte'
|
||||
import type { BaseAppComponent } from '../../types'
|
||||
|
||||
@@ -80,6 +82,12 @@ export type TabsComponent = BaseComponent<'tabscomponent'> & {
|
||||
}
|
||||
export type ContainerComponent = BaseComponent<'containercomponent'>
|
||||
export type DrawerComponent = BaseComponent<'drawercomponent'>
|
||||
export type VerticalSplitPanesComponent = BaseComponent<'verticalsplitpanescomponent'> & {
|
||||
panes: number[]
|
||||
}
|
||||
export type HorizontalSplitPanesComponent = BaseComponent<'horizontalsplitpanescomponent'> & {
|
||||
panes: number[]
|
||||
}
|
||||
|
||||
export type AppComponent = BaseAppComponent &
|
||||
(
|
||||
@@ -115,6 +123,8 @@ export type AppComponent = BaseAppComponent &
|
||||
| ImageComponent
|
||||
| AggridComponent
|
||||
| DrawerComponent
|
||||
| VerticalSplitPanesComponent
|
||||
| HorizontalSplitPanesComponent
|
||||
)
|
||||
|
||||
export type AppComponentDimensions = `${IntRange<
|
||||
@@ -864,6 +874,11 @@ Hello \${ctx.username}
|
||||
fieldType: 'text',
|
||||
value: 'Select an item',
|
||||
onlyStatic: true
|
||||
},
|
||||
defaultValue: {
|
||||
type: 'static',
|
||||
value: undefined,
|
||||
fieldType: 'object'
|
||||
}
|
||||
},
|
||||
customCss: {
|
||||
@@ -1365,5 +1380,55 @@ Hello \${ctx.username}
|
||||
card: false,
|
||||
numberOfSubgrids: 1
|
||||
}
|
||||
},
|
||||
verticalsplitpanescomponent: {
|
||||
name: 'Vertical Split Panes',
|
||||
icon: FlipHorizontal,
|
||||
dims: '2:8-6:8',
|
||||
data: {
|
||||
softWrap: true,
|
||||
id: '',
|
||||
type: 'verticalsplitpanescomponent',
|
||||
configuration: {
|
||||
noPadding: {
|
||||
type: 'static',
|
||||
fieldType: 'boolean',
|
||||
value: false,
|
||||
onlyStatic: true
|
||||
}
|
||||
},
|
||||
customCss: {
|
||||
container: { class: '', style: '' }
|
||||
} as const,
|
||||
componentInput: undefined,
|
||||
card: false,
|
||||
panes: [50, 50],
|
||||
numberOfSubgrids: 2
|
||||
}
|
||||
},
|
||||
horizontalsplitpanescomponent: {
|
||||
name: 'Horizontal Split Panes',
|
||||
icon: FlipVertical,
|
||||
dims: '2:8-6:8',
|
||||
data: {
|
||||
softWrap: true,
|
||||
id: '',
|
||||
type: 'horizontalsplitpanescomponent',
|
||||
configuration: {
|
||||
noPadding: {
|
||||
type: 'static',
|
||||
fieldType: 'boolean',
|
||||
value: false,
|
||||
onlyStatic: true
|
||||
},
|
||||
},
|
||||
customCss: {
|
||||
container: { class: '', style: '' }
|
||||
} as const,
|
||||
componentInput: undefined,
|
||||
card: false,
|
||||
panes: [50, 50],
|
||||
numberOfSubgrids: 2
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,9 @@ const layout: ComponentSet = {
|
||||
'containercomponent',
|
||||
'horizontaldividercomponent',
|
||||
'verticaldividercomponent',
|
||||
'drawercomponent'
|
||||
'drawercomponent',
|
||||
'verticalsplitpanescomponent',
|
||||
'horizontalsplitpanescomponent'
|
||||
]
|
||||
} as const
|
||||
|
||||
|
||||
@@ -9,6 +9,18 @@ export const staticValues = {
|
||||
chartThemeOptions: ['theme1', 'theme2', 'theme3'],
|
||||
textStyleOptions: ['Title', 'Subtitle', 'Body', 'Label', 'Caption'],
|
||||
currencyOptions: ['USD', 'EUR', 'GBP', 'CAD', 'AUD', 'JPY', 'CNY', 'INR', 'BRL'],
|
||||
localeOptions: ['en-US', 'en-GB', 'en-IE', 'de-DE', 'fr-FR', 'br-FR', 'ja-JP', 'pt-TL', 'fr-CA', 'en-CA'],
|
||||
objectFitOptions: ['contain', 'cover', 'fill']
|
||||
localeOptions: [
|
||||
'en-US',
|
||||
'en-GB',
|
||||
'en-IE',
|
||||
'de-DE',
|
||||
'fr-FR',
|
||||
'br-FR',
|
||||
'ja-JP',
|
||||
'pt-TL',
|
||||
'fr-CA',
|
||||
'en-CA'
|
||||
],
|
||||
objectFitOptions: ['contain', 'cover', 'fill'],
|
||||
splitPanelOptions: ['2', '3', '4']
|
||||
} as const
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script lang="ts">
|
||||
import Button from '$lib/components/common/button/Button.svelte'
|
||||
import { faCopy, faTrashAlt } from '@fortawesome/free-solid-svg-icons'
|
||||
import { faChevronDown, faChevronUp, faCopy, faTrashAlt } from '@fortawesome/free-solid-svg-icons'
|
||||
import { getContext } from 'svelte'
|
||||
import type { AppEditorContext, GridItem } from '../../types'
|
||||
import PanelSection from './common/PanelSection.svelte'
|
||||
@@ -24,6 +24,8 @@
|
||||
import { duplicateGridItem } from '../appUtils'
|
||||
import { deleteGridItem } from '../appUtils'
|
||||
import MoveToOtherGrid from './MoveToOtherGrid.svelte'
|
||||
import GridPane from './GridPane.svelte'
|
||||
import { slide } from 'svelte/transition'
|
||||
|
||||
export let component: AppComponent
|
||||
export let rowColumns = false
|
||||
@@ -67,6 +69,8 @@
|
||||
onDelete?.()
|
||||
}
|
||||
|
||||
let viewCssOptions = false
|
||||
|
||||
$: extraLib =
|
||||
component?.componentInput?.type === 'template' && $worldStore
|
||||
? buildExtraLib($worldStore?.outputsById ?? {}, component?.id, false)
|
||||
@@ -163,9 +167,9 @@
|
||||
|
||||
{#if component.type === 'tabscomponent'}
|
||||
<GridTab bind:tabs={component.tabs} {component} />
|
||||
{/if}
|
||||
|
||||
{#if component.type === 'tablecomponent' && Array.isArray(component.actionButtons)}
|
||||
{:else if component.type === 'verticalsplitpanescomponent' || component.type === 'horizontalsplitpanescomponent'}
|
||||
<GridPane bind:panes={component.panes} {component} />
|
||||
{:else if component.type === 'tablecomponent' && Array.isArray(component.actionButtons)}
|
||||
<TableActions id={component.id} bind:components={component.actionButtons} />
|
||||
{/if}
|
||||
|
||||
@@ -176,13 +180,27 @@
|
||||
|
||||
{#if Object.keys(component.customCss ?? {}).length > 0}
|
||||
<PanelSection title="Custom CSS">
|
||||
{#each Object.keys(component.customCss ?? {}) as name}
|
||||
{#if component?.customCss?.[name]}
|
||||
<div class="w-full mb-2">
|
||||
<CssProperty {name} bind:value={component.customCss[name]} />
|
||||
</div>
|
||||
{/if}
|
||||
{/each}
|
||||
<div slot="action">
|
||||
<Button
|
||||
color="light"
|
||||
size="xs"
|
||||
endIcon={{ icon: viewCssOptions ? faChevronUp : faChevronDown }}
|
||||
on:click={() => (viewCssOptions = !viewCssOptions)}
|
||||
>
|
||||
{viewCssOptions ? 'Hide' : 'Show'}
|
||||
</Button>
|
||||
</div>
|
||||
{#if viewCssOptions}
|
||||
<div transition:slide>
|
||||
{#each Object.keys(component.customCss ?? {}) as name}
|
||||
{#if component?.customCss?.[name]}
|
||||
<div class="w-full mb-2">
|
||||
<CssProperty {name} bind:value={component.customCss[name]} />
|
||||
</div>
|
||||
{/if}
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
</PanelSection>
|
||||
{/if}
|
||||
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
<script lang="ts">
|
||||
import Button from '$lib/components/common/button/Button.svelte'
|
||||
import { faPlus, faTrashAlt } from '@fortawesome/free-solid-svg-icons'
|
||||
import { getContext } from 'svelte'
|
||||
import type { AppEditorContext } from '../../types'
|
||||
import { deleteGridItem } from '../appUtils'
|
||||
import type { AppComponent } from '../component'
|
||||
import PanelSection from './common/PanelSection.svelte'
|
||||
|
||||
export let panes: number[]
|
||||
export let component: AppComponent
|
||||
|
||||
const { app, staticOutputs, runnableComponents } =
|
||||
getContext<AppEditorContext>('AppEditorContext')
|
||||
|
||||
function addTab() {
|
||||
const numberOfPanes = panes.length
|
||||
panes = Array(panes.length + 1)
|
||||
.fill(0)
|
||||
.map((_) => Math.floor(100 / (panes.length + 1)))
|
||||
|
||||
if (!$app.subgrids) {
|
||||
$app.subgrids = {}
|
||||
}
|
||||
|
||||
$app.subgrids[`${component.id}-${numberOfPanes}`] = []
|
||||
component.numberOfSubgrids = panes.length
|
||||
}
|
||||
|
||||
function deleteSubgrid(index: number) {
|
||||
let subgrid = `${component.id}-${index}`
|
||||
for (const item of $app!.subgrids![subgrid]) {
|
||||
const components = deleteGridItem($app, item.data, subgrid, false)
|
||||
console.log(components)
|
||||
for (const key in components) {
|
||||
delete $staticOutputs[key]
|
||||
delete $runnableComponents[key]
|
||||
}
|
||||
}
|
||||
$staticOutputs = $staticOutputs
|
||||
$runnableComponents = $runnableComponents
|
||||
for (let i = index; i < panes.length - 1; i++) {
|
||||
$app!.subgrids![`${component.id}-${i}`] = $app!.subgrids![`${component.id}-${i + 1}`]
|
||||
}
|
||||
panes.splice(index, 1)
|
||||
panes = panes
|
||||
component.numberOfSubgrids = panes.length
|
||||
$app = $app
|
||||
}
|
||||
</script>
|
||||
|
||||
<PanelSection title={`panes ${panes.length > 0 ? `(${panes.length})` : ''}`}>
|
||||
{#if panes.length == 0}
|
||||
<span class="text-xs text-gray-500">No panes</span>
|
||||
{/if}
|
||||
<div class="flex gap-2 flex-col mt-2">
|
||||
{#each panes as value, index (index)}
|
||||
<div class="flex flex-row gap-2 items-center relative">
|
||||
<input type="number" bind:value />
|
||||
|
||||
<div class="absolute top-1 right-1">
|
||||
<Button
|
||||
size="xs"
|
||||
color="light"
|
||||
variant="border"
|
||||
on:click={() => {
|
||||
deleteSubgrid(index)
|
||||
}}
|
||||
iconOnly
|
||||
btnClasses="!text-red-500"
|
||||
startIcon={{ icon: faTrashAlt }}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{/each}
|
||||
<Button
|
||||
size="xs"
|
||||
color="light"
|
||||
variant="border"
|
||||
startIcon={{ icon: faPlus }}
|
||||
on:click={addTab}
|
||||
iconOnly
|
||||
/>
|
||||
</div>
|
||||
</PanelSection>
|
||||
+26
-49
@@ -1,59 +1,36 @@
|
||||
DO
|
||||
$do$
|
||||
BEGIN
|
||||
IF NOT EXISTS (
|
||||
SELECT
|
||||
FROM pg_catalog.pg_roles
|
||||
WHERE rolname = 'windmill_user') THEN
|
||||
CREATE ROLE windmill_user;
|
||||
|
||||
LOCK TABLE pg_catalog.pg_roles;
|
||||
GRANT ALL
|
||||
ON ALL TABLES IN SCHEMA public
|
||||
TO windmill_user;
|
||||
|
||||
CREATE ROLE windmill_user;
|
||||
GRANT ALL PRIVILEGES
|
||||
ON ALL SEQUENCES IN SCHEMA public
|
||||
TO windmill_user;
|
||||
|
||||
GRANT ALL
|
||||
ON ALL TABLES IN SCHEMA public
|
||||
TO windmill_user;
|
||||
ALTER DEFAULT PRIVILEGES
|
||||
IN SCHEMA public
|
||||
GRANT ALL ON TABLES TO windmill_user;
|
||||
|
||||
GRANT ALL PRIVILEGES
|
||||
ON ALL SEQUENCES IN SCHEMA public
|
||||
TO windmill_user;
|
||||
ALTER DEFAULT PRIVILEGES
|
||||
IN SCHEMA public
|
||||
GRANT ALL ON SEQUENCES TO windmill_user;
|
||||
|
||||
ALTER DEFAULT PRIVILEGES
|
||||
IN SCHEMA public
|
||||
GRANT ALL ON TABLES TO windmill_user;
|
||||
|
||||
ALTER DEFAULT PRIVILEGES
|
||||
IN SCHEMA public
|
||||
GRANT ALL ON SEQUENCES TO windmill_user;
|
||||
CREATE ROLE windmill_admin WITH BYPASSRLS;
|
||||
|
||||
END IF;
|
||||
END
|
||||
$do$;
|
||||
GRANT ALL
|
||||
ON ALL TABLES IN SCHEMA public
|
||||
TO windmill_admin;
|
||||
|
||||
DO
|
||||
$do$
|
||||
BEGIN
|
||||
IF NOT EXISTS (
|
||||
SELECT
|
||||
FROM pg_catalog.pg_roles
|
||||
WHERE rolname = 'windmill_admin') THEN
|
||||
CREATE ROLE windmill_admin WITH BYPASSRLS;
|
||||
GRANT ALL PRIVILEGES
|
||||
ON ALL SEQUENCES IN SCHEMA public
|
||||
TO windmill_admin;
|
||||
|
||||
GRANT ALL
|
||||
ON ALL TABLES IN SCHEMA public
|
||||
TO windmill_admin;
|
||||
ALTER DEFAULT PRIVILEGES
|
||||
IN SCHEMA public
|
||||
GRANT ALL ON TABLES TO windmill_admin;
|
||||
|
||||
GRANT ALL PRIVILEGES
|
||||
ON ALL SEQUENCES IN SCHEMA public
|
||||
TO windmill_admin;
|
||||
|
||||
ALTER DEFAULT PRIVILEGES
|
||||
IN SCHEMA public
|
||||
GRANT ALL ON TABLES TO windmill_admin;
|
||||
|
||||
ALTER DEFAULT PRIVILEGES
|
||||
IN SCHEMA public
|
||||
GRANT ALL ON SEQUENCES TO windmill_admin;
|
||||
END IF;
|
||||
END
|
||||
$do$;
|
||||
ALTER DEFAULT PRIVILEGES
|
||||
IN SCHEMA public
|
||||
GRANT ALL ON SEQUENCES TO windmill_admin;
|
||||
|
||||
Reference in New Issue
Block a user