mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-08-24 16:01:42 +00:00
feat(apps): add custom css for apps
This commit is contained in:
Generated
+11
@@ -27,6 +27,7 @@
|
||||
"svelte-chartjs": "^3.1.0",
|
||||
"svelte-portal": "^2.2.0",
|
||||
"svelte-select": "^5.0.2",
|
||||
"tailwind-merge": "^1.9.1",
|
||||
"vscode-ws-jsonrpc": "^2.0.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
@@ -6467,6 +6468,11 @@
|
||||
"dev": true,
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/tailwind-merge": {
|
||||
"version": "1.9.1",
|
||||
"resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-1.9.1.tgz",
|
||||
"integrity": "sha512-ED9MkiUHlmfh58EC1xHRqXcH1IQyRtmDP0AmXlugYkP2tvfu7ejtjFEHJLJt93mQ7ZJkcqSIgm9M394bq5vOJg=="
|
||||
},
|
||||
"node_modules/tailwindcss": {
|
||||
"version": "3.2.3",
|
||||
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.2.3.tgz",
|
||||
@@ -11691,6 +11697,11 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"tailwind-merge": {
|
||||
"version": "1.9.1",
|
||||
"resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-1.9.1.tgz",
|
||||
"integrity": "sha512-ED9MkiUHlmfh58EC1xHRqXcH1IQyRtmDP0AmXlugYkP2tvfu7ejtjFEHJLJt93mQ7ZJkcqSIgm9M394bq5vOJg=="
|
||||
},
|
||||
"tailwindcss": {
|
||||
"version": "3.2.3",
|
||||
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.2.3.tgz",
|
||||
|
||||
@@ -79,6 +79,7 @@
|
||||
"svelte-chartjs": "^3.1.0",
|
||||
"svelte-portal": "^2.2.0",
|
||||
"svelte-select": "^5.0.2",
|
||||
"tailwind-merge": "^1.9.1",
|
||||
"vscode-ws-jsonrpc": "^2.0.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
|
||||
Vendored
+1
@@ -57,6 +57,7 @@ declare module 'svelte-grid' {
|
||||
Props<T>,
|
||||
{
|
||||
pointerup: CustomEvent<{ id: string }>
|
||||
mount: CustomEvent<>
|
||||
},
|
||||
Slots<T>
|
||||
> { }
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { Button, type ButtonType } from '$lib/components/common'
|
||||
import { getContext } from 'svelte'
|
||||
import { getContext, onMount } from 'svelte'
|
||||
import type { AppInput } from '../../inputType'
|
||||
import type { Output } from '../../rx'
|
||||
import type { AppEditorContext } from '../../types'
|
||||
@@ -9,6 +9,7 @@
|
||||
import type RunnableComponent from '../helpers/RunnableComponent.svelte'
|
||||
import RunnableWrapper from '../helpers/RunnableWrapper.svelte'
|
||||
import { loadIcon } from '../icon'
|
||||
import { twMerge } from 'tailwind-merge'
|
||||
|
||||
export let id: string
|
||||
export let componentInput: AppInput | undefined
|
||||
@@ -19,10 +20,11 @@
|
||||
export let verticalAlignment: 'top' | 'center' | 'bottom' | undefined = undefined
|
||||
export let noWFull = false
|
||||
export let preclickAction: (() => Promise<void>) | undefined = undefined
|
||||
export let customCss: Record<'button', { class: string; style: string }> | undefined = undefined
|
||||
|
||||
export const staticOutputs: string[] = ['loading', 'result']
|
||||
|
||||
const { runnableComponents, worldStore } = getContext<AppEditorContext>('AppEditorContext')
|
||||
const { runnableComponents, worldStore, app } = getContext<AppEditorContext>('AppEditorContext')
|
||||
|
||||
let labelValue: string
|
||||
let color: ButtonType.Color
|
||||
@@ -115,7 +117,12 @@
|
||||
<div class="text-red-500 text-xs">{errorsMessage}</div>
|
||||
{/if}
|
||||
<Button
|
||||
btnClasses={fillContainer ? 'w-full h-full' : ''}
|
||||
btnClasses={twMerge(
|
||||
$app.css?.['buttoncomponent']?.['button']?.class,
|
||||
customCss?.button?.class,
|
||||
fillContainer ? 'w-full h-full' : ''
|
||||
)}
|
||||
style={[$app.css?.['buttoncomponent']?.['button']?.style, customCss?.button?.style].join(';')}
|
||||
{disabled}
|
||||
on:pointerdown={(e) => {
|
||||
e?.stopPropagation()
|
||||
|
||||
@@ -7,6 +7,9 @@
|
||||
import AlignWrapper from '../helpers/AlignWrapper.svelte'
|
||||
import InputValue from '../helpers/InputValue.svelte'
|
||||
import RunnableWrapper from '../helpers/RunnableWrapper.svelte'
|
||||
import { twMerge } from 'tailwind-merge'
|
||||
import type { AppEditorContext } from '../../types'
|
||||
import { getContext } from 'svelte'
|
||||
|
||||
export let id: string
|
||||
export let componentInput: AppInput | undefined
|
||||
@@ -14,10 +17,12 @@
|
||||
export let verticalAlignment: 'top' | 'center' | 'bottom' | undefined = undefined
|
||||
export let configuration: Record<string, AppInput>
|
||||
export let initializing: boolean | undefined = undefined
|
||||
export let customCss: Record<'text', { class: string; style: string }> | undefined = undefined
|
||||
|
||||
export const staticOutputs: string[] = ['result', 'loading']
|
||||
|
||||
let extraStyle: string | undefined = undefined
|
||||
const { app } = getContext<AppEditorContext>('AppEditorContext')
|
||||
|
||||
let result: string | undefined = undefined
|
||||
let style: 'Title' | 'Subtitle' | 'Body' | 'Caption' | 'Label' | undefined = undefined
|
||||
let copyButton: boolean
|
||||
@@ -56,7 +61,6 @@
|
||||
$: style && (classes = getClasses())
|
||||
</script>
|
||||
|
||||
<InputValue {id} input={configuration.extraStyle} bind:value={extraStyle} />
|
||||
<InputValue {id} input={configuration.style} bind:value={style} />
|
||||
<InputValue {id} input={configuration.copyButton} bind:value={copyButton} />
|
||||
|
||||
@@ -68,7 +72,16 @@
|
||||
</div>
|
||||
{:else}
|
||||
<div class="flex flex-wrap gap-2 pb-0.5 overflow-x-auto">
|
||||
<svelte:element this={component} class="whitespace-pre-wrap {classes}" style={extraStyle}>
|
||||
<svelte:element
|
||||
this={component}
|
||||
class={twMerge(
|
||||
'whitespace-pre-wrap',
|
||||
$app.css?.['textcomponent']?.['text']?.class,
|
||||
customCss?.text?.class,
|
||||
classes
|
||||
)}
|
||||
style={[$app.css?.['textcomponent']?.['text']?.style, customCss?.text?.style].join(';')}
|
||||
>
|
||||
{String(result)}
|
||||
</svelte:element>
|
||||
{#if copyButton && result}
|
||||
|
||||
@@ -36,6 +36,7 @@
|
||||
|
||||
<AlignWrapper {verticalAlignment}>
|
||||
<input
|
||||
class="mx-0.5"
|
||||
on:focus={(e) => {
|
||||
e?.stopPropagation()
|
||||
window.dispatchEvent(new Event('pointerup'))
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<script lang="ts">
|
||||
import SplitPanesWrapper from '$lib/components/splitPanes/SplitPanesWrapper.svelte'
|
||||
import { onMount, setContext } from 'svelte'
|
||||
import { twMerge } from 'tailwind-merge'
|
||||
|
||||
import { Pane, Splitpanes } from 'svelte-splitpanes'
|
||||
import { writable } from 'svelte/store'
|
||||
@@ -20,9 +21,9 @@
|
||||
import { Alert, Button, Tab } from '$lib/components/common'
|
||||
import ComponentList from './componentsPanel/ComponentList.svelte'
|
||||
import Icon from 'svelte-awesome'
|
||||
import { faPlus, faSliders } from '@fortawesome/free-solid-svg-icons'
|
||||
import { faCode, faPlus, faSliders } from '@fortawesome/free-solid-svg-icons'
|
||||
import ContextPanel from './contextPanel/ContextPanel.svelte'
|
||||
import { classNames, encodeState } from '$lib/utils'
|
||||
import { encodeState } from '$lib/utils'
|
||||
import AppPreview from './AppPreview.svelte'
|
||||
import { userStore, workspaceStore } from '$lib/stores'
|
||||
|
||||
@@ -33,6 +34,7 @@
|
||||
import type { Policy } from '$lib/gen'
|
||||
import UnsavedConfirmationModal from '$lib/components/common/confirmationModal/UnsavedConfirmationModal.svelte'
|
||||
import { page } from '$app/stores'
|
||||
import CssSettings from './componentsPanel/CssSettings.svelte'
|
||||
|
||||
export let app: App
|
||||
export let path: string
|
||||
@@ -130,17 +132,24 @@
|
||||
{/if}
|
||||
|
||||
{#if previewing}
|
||||
<AppPreview
|
||||
workspace={$workspaceStore ?? ''}
|
||||
summary={$summaryStore}
|
||||
app={$appStore}
|
||||
appPath={path}
|
||||
{breakpoint}
|
||||
{policy}
|
||||
isEditor
|
||||
{context}
|
||||
noBackend={false}
|
||||
/>
|
||||
<SplitPanesWrapper>
|
||||
<div
|
||||
class={twMerge('bg-gray-100 h-full w-full', $appStore.css?.['app']?.['viewer']?.class)}
|
||||
style={$appStore.css?.['app']?.['viewer']?.style}
|
||||
>
|
||||
<AppPreview
|
||||
workspace={$workspaceStore ?? ''}
|
||||
summary={$summaryStore}
|
||||
app={$appStore}
|
||||
appPath={path}
|
||||
{breakpoint}
|
||||
{policy}
|
||||
isEditor
|
||||
{context}
|
||||
noBackend={false}
|
||||
/>
|
||||
</div>
|
||||
</SplitPanesWrapper>
|
||||
{:else}
|
||||
<SplitPanesWrapper>
|
||||
<Splitpanes class="max-w-full overflow-hidden">
|
||||
@@ -152,15 +161,23 @@
|
||||
<Splitpanes horizontal>
|
||||
<Pane size={$connectingInput?.opened ? 100 : 70}>
|
||||
<div
|
||||
class="bg-gray-100 relative w-full h-full overflow-auto {app.fullscreen
|
||||
? ''
|
||||
: 'max-w-6xl'}"
|
||||
class={twMerge(
|
||||
'bg-gray-100 h-full w-full',
|
||||
$appStore.css?.['app']?.['viewer']?.class
|
||||
)}
|
||||
style={$appStore.css?.['app']?.['viewer']?.style}
|
||||
>
|
||||
{#if $appStore.grid}
|
||||
<div class={classNames('pb-4 mx-auto', width)}>
|
||||
<GridEditor {policy} />
|
||||
</div>
|
||||
{/if}
|
||||
<div
|
||||
class="relative mx-auto w-full h-full overflow-auto {app.fullscreen
|
||||
? ''
|
||||
: 'max-w-6xl'}"
|
||||
>
|
||||
{#if $appStore.grid}
|
||||
<div class={width}>
|
||||
<GridEditor {policy} />
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
</Pane>
|
||||
<Pane size={$connectingInput?.opened ? 0 : 30}>
|
||||
@@ -186,6 +203,12 @@
|
||||
<span>Settings</span>
|
||||
</div>
|
||||
</Tab>
|
||||
<Tab value="css" size="xs">
|
||||
<div class="m-1 flex flex-row gap-2">
|
||||
<Icon data={faCode} />
|
||||
<span>CSS</span>
|
||||
</div>
|
||||
</Tab>
|
||||
<div slot="content" class="h-full overflow-y-auto pb-4">
|
||||
<TabContent class="overflow-auto" value="settings">
|
||||
{#if $selectedComponent !== undefined}
|
||||
@@ -197,6 +220,9 @@
|
||||
<TabContent value="insert">
|
||||
<ComponentList />
|
||||
</TabContent>
|
||||
<TabContent value="css">
|
||||
<CssSettings />
|
||||
</TabContent>
|
||||
</div>
|
||||
</Tabs>
|
||||
{#if $connectingInput.opened}
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
import {
|
||||
faBug,
|
||||
faClipboard,
|
||||
faCode,
|
||||
faExternalLink,
|
||||
faFileExport,
|
||||
faGlobe,
|
||||
|
||||
@@ -81,7 +81,7 @@
|
||||
w-full {app.fullscreen ? '' : 'max-w-6xl'} mx-auto"
|
||||
>
|
||||
{#if $appStore.grid}
|
||||
<div class={classNames('mx-auto pb-4', width)}>
|
||||
<div class={classNames('mx-auto', width)}>
|
||||
<GridEditor {policy} />
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
SeparatorVertical
|
||||
} from 'lucide-svelte'
|
||||
import type { Size } from 'svelte-grid'
|
||||
import { twMerge } from 'tailwind-merge'
|
||||
|
||||
type BaseComponent<T extends string> = {
|
||||
type: T
|
||||
@@ -108,7 +109,13 @@
|
||||
// Dimensions key formula: <mobile width>:<mobile height>-<desktop width>:<desktop height>
|
||||
export const components: Record<
|
||||
AppComponent['type'],
|
||||
{ name: string; icon: any; dims: `${number}:${number}-${number}:${number}`; data: AppComponent }
|
||||
{
|
||||
name: string
|
||||
icon: any
|
||||
dims: `${number}:${number}-${number}:${number}`
|
||||
data: AppComponent
|
||||
cssIds?: string[]
|
||||
}
|
||||
> = {
|
||||
displaycomponent: {
|
||||
name: 'Rich Result',
|
||||
@@ -123,6 +130,7 @@
|
||||
value: { foo: 42 }
|
||||
},
|
||||
configuration: {},
|
||||
customCss: {},
|
||||
card: false
|
||||
}
|
||||
},
|
||||
@@ -130,6 +138,7 @@
|
||||
name: 'Text',
|
||||
icon: Type,
|
||||
dims: '1:1-3:1',
|
||||
cssIds: ['text'],
|
||||
data: {
|
||||
softWrap: false,
|
||||
horizontalAlignment: 'left',
|
||||
@@ -149,12 +158,6 @@
|
||||
optionValuesKey: 'textStyleOptions',
|
||||
value: 'Body'
|
||||
},
|
||||
extraStyle: {
|
||||
type: 'static',
|
||||
fieldType: 'text',
|
||||
value: '',
|
||||
tooltip: 'CSS rules like "color: blue;"'
|
||||
},
|
||||
copyButton: {
|
||||
type: 'static',
|
||||
value: false,
|
||||
@@ -162,6 +165,9 @@
|
||||
onlyStatic: true
|
||||
}
|
||||
},
|
||||
customCss: {
|
||||
text: { class: '', style: '' }
|
||||
},
|
||||
card: false
|
||||
}
|
||||
},
|
||||
@@ -169,6 +175,7 @@
|
||||
name: 'Button',
|
||||
icon: Inspect,
|
||||
dims: '1:1-2:1',
|
||||
cssIds: ['button'],
|
||||
data: {
|
||||
...defaultAlignement,
|
||||
softWrap: true,
|
||||
@@ -237,7 +244,9 @@
|
||||
onlyStatic: true
|
||||
}
|
||||
},
|
||||
|
||||
customCss: {
|
||||
button: { style: '', class: '' }
|
||||
},
|
||||
card: false
|
||||
}
|
||||
},
|
||||
@@ -283,7 +292,7 @@
|
||||
value: ''
|
||||
}
|
||||
},
|
||||
|
||||
customCss: {},
|
||||
card: true
|
||||
}
|
||||
},
|
||||
@@ -324,7 +333,7 @@
|
||||
optionValuesKey: 'buttonSizeOptions'
|
||||
}
|
||||
},
|
||||
|
||||
customCss: {},
|
||||
card: true
|
||||
}
|
||||
},
|
||||
@@ -355,6 +364,7 @@
|
||||
fieldType: 'object',
|
||||
value: { data: [25, 50, 25], labels: ['Pie', 'Charts', '<3'] }
|
||||
},
|
||||
customCss: {},
|
||||
card: true
|
||||
}
|
||||
},
|
||||
@@ -385,6 +395,7 @@
|
||||
fieldType: 'object',
|
||||
value: { data: [25, 50, 25], labels: ['Bar', 'Charts', '<3'] }
|
||||
},
|
||||
customCss: {},
|
||||
card: true
|
||||
}
|
||||
},
|
||||
@@ -407,6 +418,7 @@
|
||||
</h1>`
|
||||
},
|
||||
configuration: {},
|
||||
customCss: {},
|
||||
card: false
|
||||
}
|
||||
},
|
||||
@@ -446,6 +458,7 @@
|
||||
tooltip: 'use the canvas renderer instead of the svg one for more interactive plots'
|
||||
}
|
||||
},
|
||||
customCss: {},
|
||||
card: false
|
||||
}
|
||||
},
|
||||
@@ -473,6 +486,7 @@
|
||||
}
|
||||
},
|
||||
configuration: {},
|
||||
customCss: {},
|
||||
card: false
|
||||
}
|
||||
},
|
||||
@@ -546,6 +560,7 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
customCss: {},
|
||||
card: true
|
||||
}
|
||||
},
|
||||
@@ -595,6 +610,7 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
customCss: {},
|
||||
card: true
|
||||
}
|
||||
},
|
||||
@@ -631,6 +647,7 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
customCss: {},
|
||||
card: true,
|
||||
actionButtons: []
|
||||
}
|
||||
@@ -657,6 +674,7 @@
|
||||
fieldType: 'boolean'
|
||||
}
|
||||
},
|
||||
customCss: {},
|
||||
card: false
|
||||
}
|
||||
},
|
||||
@@ -683,6 +701,7 @@
|
||||
fieldType: 'text'
|
||||
}
|
||||
},
|
||||
customCss: {},
|
||||
card: false
|
||||
}
|
||||
},
|
||||
@@ -718,6 +737,7 @@
|
||||
onlyStatic: true
|
||||
}
|
||||
},
|
||||
customCss: {},
|
||||
card: false,
|
||||
softWrap: true
|
||||
}
|
||||
@@ -763,6 +783,7 @@
|
||||
onlyStatic: true
|
||||
}
|
||||
},
|
||||
customCss: {},
|
||||
card: false
|
||||
}
|
||||
},
|
||||
@@ -803,6 +824,7 @@
|
||||
optionValuesKey: 'localeOptions'
|
||||
}
|
||||
},
|
||||
customCss: {},
|
||||
card: false
|
||||
}
|
||||
},
|
||||
@@ -842,6 +864,7 @@
|
||||
onlyStatic: true
|
||||
}
|
||||
},
|
||||
customCss: {},
|
||||
card: false
|
||||
}
|
||||
},
|
||||
@@ -887,6 +910,7 @@
|
||||
onlyStatic: true
|
||||
}
|
||||
},
|
||||
customCss: {},
|
||||
card: false
|
||||
}
|
||||
},
|
||||
@@ -908,6 +932,7 @@
|
||||
onlyStatic: true
|
||||
}
|
||||
},
|
||||
customCss: {},
|
||||
card: false
|
||||
}
|
||||
},
|
||||
@@ -938,6 +963,7 @@
|
||||
fieldType: 'date'
|
||||
}
|
||||
},
|
||||
customCss: {},
|
||||
card: false
|
||||
}
|
||||
},
|
||||
@@ -976,6 +1002,7 @@
|
||||
onlyStatic: true
|
||||
}
|
||||
},
|
||||
customCss: {},
|
||||
card: false
|
||||
}
|
||||
},
|
||||
@@ -1002,6 +1029,7 @@
|
||||
onlyStatic: true
|
||||
}
|
||||
},
|
||||
customCss: {},
|
||||
card: false
|
||||
}
|
||||
},
|
||||
@@ -1028,6 +1056,7 @@
|
||||
onlyStatic: true
|
||||
}
|
||||
},
|
||||
customCss: {},
|
||||
card: false
|
||||
}
|
||||
}
|
||||
@@ -1446,7 +1475,8 @@
|
||||
export let locked: boolean = false
|
||||
export let pointerdown: boolean = false
|
||||
|
||||
const { staticOutputs, mode, connectingInput } = getContext<AppEditorContext>('AppEditorContext')
|
||||
const { staticOutputs, mode, connectingInput, app } =
|
||||
getContext<AppEditorContext>('AppEditorContext')
|
||||
let hover = false
|
||||
let initializing: boolean | undefined = undefined
|
||||
</script>
|
||||
@@ -1469,15 +1499,17 @@
|
||||
// e?.stopPropagation()
|
||||
// }
|
||||
}}
|
||||
class={classNames(
|
||||
'border h-full bg-white',
|
||||
selected && $mode !== 'preview' ? 'border-blue-500' : 'border-white',
|
||||
class={twMerge(
|
||||
'h-full bg-white/40',
|
||||
selected && $mode !== 'preview' ? 'border border-blue-500' : '',
|
||||
!selected && $mode !== 'preview' && !component.card ? 'border-gray-100' : '',
|
||||
$mode !== 'preview' && !$connectingInput.opened ? 'hover:border-blue-500' : '',
|
||||
component.softWrap ? '' : 'overflow-auto',
|
||||
$mode != 'preview' ? 'cursor-pointer' : '',
|
||||
'relative z-auto'
|
||||
'relative z-auto',
|
||||
$app.css?.['app']?.['component']?.class
|
||||
)}
|
||||
style={$app.css?.['app']?.['component']?.style}
|
||||
>
|
||||
{#if component.type === 'displaycomponent'}
|
||||
<AppDisplayComponent
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
import Grid from 'svelte-grid'
|
||||
import { classNames } from '$lib/utils'
|
||||
import { columnConfiguration, disableDrag, enableDrag, isFixed, toggleFixed } from '../gridUtils'
|
||||
import { twMerge } from 'tailwind-merge'
|
||||
|
||||
import RecomputeAllComponents from './RecomputeAllComponents.svelte'
|
||||
import type { Policy } from '$lib/gen'
|
||||
@@ -114,9 +115,10 @@
|
||||
})
|
||||
}
|
||||
})
|
||||
let mounted = false
|
||||
</script>
|
||||
|
||||
<div class="pb-2 relative w-full z-20 overflow-visible border">
|
||||
<div class="relative w-full z-20 overflow-visible">
|
||||
<div
|
||||
class="w-full sticky top-0 flex justify-between border-l border-r border-b {$connectingInput?.opened
|
||||
? ''
|
||||
@@ -132,12 +134,14 @@
|
||||
>
|
||||
</div>
|
||||
<div
|
||||
class="px-4 pt-4 overflow-visible {$connectingInput?.opened ? '' : ''}"
|
||||
style={$app.css?.['app']?.['grid']?.style}
|
||||
class={twMerge('px-4 pt-4 pb-2 overflow-visible', $app.css?.['app']?.['grid']?.class ?? '')}
|
||||
on:pointerdown={onpointerdown}
|
||||
on:pointerleave={onpointerup}
|
||||
on:pointerup={onpointerup}
|
||||
>
|
||||
<Grid
|
||||
fillSpace={false}
|
||||
bind:items={$app.grid}
|
||||
let:dataItem
|
||||
rowHeight={36}
|
||||
|
||||
@@ -0,0 +1,125 @@
|
||||
<script lang="ts">
|
||||
import Toggle from '$lib/components/Toggle.svelte'
|
||||
import Tooltip from '$lib/components/Tooltip.svelte'
|
||||
import { faAngleDown } from '@fortawesome/free-solid-svg-icons'
|
||||
import { LayoutDashboardIcon } from 'lucide-svelte'
|
||||
import { getContext } from 'svelte'
|
||||
import Icon from 'svelte-awesome'
|
||||
import { slide } from 'svelte/transition'
|
||||
import type { AppEditorContext } from '../../types'
|
||||
import { components, type AppComponent } from '../Component.svelte'
|
||||
import { isOpenStoreCss } from './store'
|
||||
|
||||
const { app } = getContext<AppEditorContext>('AppEditorContext')
|
||||
|
||||
function switchTab(detail: any): void {
|
||||
throw new Error('Function not implemented.')
|
||||
}
|
||||
|
||||
const entries: { type: 'app' | AppComponent['type']; name: string; icon: any; ids: string[] }[] =
|
||||
[
|
||||
{
|
||||
type: 'app' as 'app' | AppComponent['type'],
|
||||
name: 'App',
|
||||
icon: LayoutDashboardIcon,
|
||||
ids: ['viewer', 'grid', 'component']
|
||||
}
|
||||
].concat(
|
||||
Object.entries(components).map((c) => ({
|
||||
type: c[1].data.type as 'app' | AppComponent['type'],
|
||||
name: c[1].name,
|
||||
icon: c[1].icon,
|
||||
ids: c[1].cssIds ?? []
|
||||
}))
|
||||
)
|
||||
let isCustom: Record<string, boolean> = Object.fromEntries(
|
||||
Object.keys(entries).map((k) => [k, false])
|
||||
)
|
||||
if (Object.keys($isOpenStoreCss).length == 0) {
|
||||
$isOpenStoreCss = Object.fromEntries(Object.keys(entries).map((k) => [k, false]))
|
||||
}
|
||||
|
||||
let newCss = $app.css ?? {}
|
||||
entries.forEach((e) => {
|
||||
if (!newCss[e.type]) {
|
||||
isCustom[e.type] = true
|
||||
newCss[e.type] = {}
|
||||
}
|
||||
e.ids.forEach((id) => {
|
||||
if (!newCss[e.type][id]) {
|
||||
newCss[e.type][id] = { style: '', class: '' }
|
||||
}
|
||||
})
|
||||
e.ids
|
||||
.map((id) => newCss[e.type][id].class != '' || newCss[e.type][id].style != '')
|
||||
.forEach((c) => {
|
||||
if (c) {
|
||||
isCustom[e.type] = true
|
||||
}
|
||||
})
|
||||
})
|
||||
//@ts-ignore
|
||||
$app.css = newCss
|
||||
</script>
|
||||
|
||||
<div class="flex items-center">
|
||||
<Toggle
|
||||
on:change={(e) => switchTab(e.detail)}
|
||||
options={{
|
||||
right: 'As JSON'
|
||||
}}
|
||||
/>
|
||||
<div class="ml-2">
|
||||
<Tooltip>
|
||||
Arguments can be edited either using the wizard, or by editing their JSON Schema,
|
||||
<a href="https://docs.windmill.dev/docs/reference/#script-parameters-to-json-schema"
|
||||
>see docs</a
|
||||
>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col gap-2 p-1">
|
||||
{#each entries as { type, name, icon, ids }}
|
||||
{#if ids.length > 0}
|
||||
<div>
|
||||
<button
|
||||
on:click|preventDefault={() => ($isOpenStoreCss[type] = !$isOpenStoreCss[type])}
|
||||
class="w-full flex justify-between items-center px-1 py-1
|
||||
rounded-sm duration-200 hover:bg-gray-100"
|
||||
>
|
||||
<h3 class="inline-flex gap-2 {isCustom[type] ? 'text-gray-800' : 'text-gray-500'}"
|
||||
>{name} <svelte:component this={icon} />
|
||||
</h3>
|
||||
<Icon
|
||||
data={faAngleDown}
|
||||
class="rotate-0 duration-300 {$isOpenStoreCss[type] ? '!rotate-180' : ''}"
|
||||
/>
|
||||
</button>
|
||||
{#if $isOpenStoreCss[type]}
|
||||
<div transition:slide|local={{ duration: 300 }} class="flex flex-col px-2 border">
|
||||
{#each ids as id}
|
||||
<div class="mb-2">
|
||||
<div class="mt-1 font-semibold">{id}</div>
|
||||
{#if $app?.css?.[type]?.[id]}
|
||||
<span class="text-xs">style</span>
|
||||
<input
|
||||
type="text"
|
||||
on:focus={() => (isCustom[type] = true)}
|
||||
bind:value={$app.css[type][id].style}
|
||||
/>
|
||||
<span class="text-xs">class</span>
|
||||
<input
|
||||
type="text"
|
||||
on:focus={() => (isCustom[type] = true)}
|
||||
bind:value={$app.css[type][id].class}
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
{/each}
|
||||
</div>
|
||||
@@ -12,10 +12,12 @@ export const isOpenStore = {
|
||||
let newItems = {}
|
||||
items.forEach(item => newItems = { ...newItems, ...item })
|
||||
store.update(last => ({ ...newItems, ...last }))
|
||||
|
||||
|
||||
},
|
||||
toggle: (id: string) => {
|
||||
store.update(last => ({ ...last, [id]: !last[id] }))
|
||||
},
|
||||
reset: () => store.set({})
|
||||
}
|
||||
}
|
||||
|
||||
export const isOpenStoreCss = writable<Record<string, boolean>>({})
|
||||
@@ -168,6 +168,20 @@
|
||||
<Recompute bind:recomputeIds={component.recomputeIds} ownId={component.id} />
|
||||
{/if}
|
||||
|
||||
{#if Object.keys(component.customCss ?? {}).length > 0}
|
||||
<PanelSection title="Custom CSS">
|
||||
{#each Object.entries(component.customCss ?? {}) as [key, value]}
|
||||
<div class="mb-2">
|
||||
<div class="mt-1 font-semibold">{key}</div>
|
||||
<span class="text-xs">style</span>
|
||||
<input type="text" bind:value={value.style} />
|
||||
<span class="text-xs">class</span>
|
||||
<input type="text" bind:value={value.class} />
|
||||
</div>
|
||||
{/each}
|
||||
</PanelSection>
|
||||
{/if}
|
||||
|
||||
<PanelSection title="Danger zone">
|
||||
<Button
|
||||
size="xs"
|
||||
|
||||
@@ -35,6 +35,10 @@ export interface BaseAppComponent extends Partial<Aligned> {
|
||||
}
|
||||
>
|
||||
card: boolean | undefined
|
||||
customCss?: Record<string, {
|
||||
class: string
|
||||
style: string
|
||||
}>
|
||||
/**
|
||||
* If `true` then the wrapper will allow items to flow outside of it's borders.
|
||||
*
|
||||
@@ -81,6 +85,7 @@ export type App = {
|
||||
inlineScript: InlineScript | undefined
|
||||
fields: Record<string, StaticAppInput | ConnectedAppInput | RowAppInput | UserAppInput>
|
||||
}>
|
||||
css?: Record<'viewer' | 'grid' | AppComponent['type'], Record<string, { style?: string, class?: string }>>
|
||||
}
|
||||
|
||||
export type ConnectingInput = {
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
<script lang="ts">
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import { classNames } from '$lib/utils'
|
||||
import Icon from 'svelte-awesome'
|
||||
import { ButtonType } from './model'
|
||||
import { goto } from '$app/navigation'
|
||||
import { Loader2 } from 'lucide-svelte'
|
||||
|
||||
import { twMerge } from 'tailwind-merge'
|
||||
|
||||
export let size: ButtonType.Size = 'md'
|
||||
export let spacingSize: ButtonType.Size = size
|
||||
export let color: ButtonType.Color = 'blue'
|
||||
@@ -23,6 +24,7 @@
|
||||
export let buttonType: 'button' | 'submit' | 'reset' = 'button'
|
||||
export let loading = false
|
||||
export let title: string | undefined = undefined
|
||||
export let style: string = ''
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
// Order of classes: border, border modifier, bg, bg modifier, text, text modifier, everything else
|
||||
@@ -64,13 +66,13 @@
|
||||
|
||||
$: buttonProps = {
|
||||
id,
|
||||
class: classNames(
|
||||
class: twMerge(
|
||||
colorVariants?.[color]?.[variant],
|
||||
variant === 'border' ? 'border' : '',
|
||||
ButtonType.FontSizeClasses[size],
|
||||
ButtonType.SpacingClasses[spacingSize][variant],
|
||||
'focus:ring-2 font-semibold',
|
||||
'duration-200 rounded-md',
|
||||
'rounded-md',
|
||||
'justify-center items-center text-center whitespace-nowrap inline-flex',
|
||||
btnClasses,
|
||||
disabled ? '!bg-gray-300 !text-gray-600 !cursor-not-allowed' : ''
|
||||
@@ -101,11 +103,8 @@
|
||||
}
|
||||
|
||||
$: isSmall = size === 'xs' || size === 'sm'
|
||||
$: startIconClass = classNames(
|
||||
iconOnly ? undefined : isSmall ? 'mr-1' : 'mr-2',
|
||||
startIcon?.classes
|
||||
)
|
||||
$: endIconClass = classNames(iconOnly ? undefined : isSmall ? 'ml-1' : 'ml-2', endIcon?.classes)
|
||||
$: startIconClass = twMerge(iconOnly ? undefined : isSmall ? 'mr-1' : 'mr-2', startIcon?.classes)
|
||||
$: endIconClass = twMerge(iconOnly ? undefined : isSmall ? 'ml-1' : 'ml-2', endIcon?.classes)
|
||||
</script>
|
||||
|
||||
<svelte:element
|
||||
@@ -118,6 +117,7 @@
|
||||
{...buttonProps}
|
||||
disabled={disabled || loading}
|
||||
type="submit"
|
||||
{style}
|
||||
>
|
||||
{#if loading}
|
||||
<Loader2 class="animate-spin mr-1" size={14} />
|
||||
|
||||
@@ -34,12 +34,14 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<div
|
||||
class={classNames(
|
||||
'border-b border-gray-200 flex flex-row whitespace-nowrap scrollbar-hidden',
|
||||
$$props.class
|
||||
)}
|
||||
>
|
||||
<slot />
|
||||
<div class="overflow-x-auto">
|
||||
<div
|
||||
class={classNames(
|
||||
'border-b border-gray-200 flex flex-row whitespace-nowrap scrollbar-hidden',
|
||||
$$props.class
|
||||
)}
|
||||
>
|
||||
<slot />
|
||||
</div>
|
||||
</div>
|
||||
<slot name="content" />
|
||||
|
||||
@@ -6,7 +6,9 @@
|
||||
import { Skeleton } from '$lib/components/common'
|
||||
import { AppService, AppWithLastVersion } from '$lib/gen'
|
||||
import { userStore, workspaceStore } from '$lib/stores'
|
||||
import { classNames } from '$lib/utils'
|
||||
import { writable } from 'svelte/store'
|
||||
import { twMerge } from 'tailwind-merge'
|
||||
|
||||
let app: AppWithLastVersion | undefined = undefined
|
||||
|
||||
@@ -27,7 +29,10 @@
|
||||
</script>
|
||||
|
||||
{#if app}
|
||||
<div class="w-full">
|
||||
<div
|
||||
class={twMerge('bg-gray-100 min-h-screen w-full', app?.value.css?.['app']?.['viewer']?.class)}
|
||||
style={app?.value.css?.['app']?.['viewer']?.style}
|
||||
>
|
||||
<AppPreview
|
||||
context={{
|
||||
email: $userStore?.email,
|
||||
|
||||
@@ -8,6 +8,8 @@
|
||||
import { WindmillIcon } from '$lib/components/icons'
|
||||
import { AppService, AppWithLastVersion, GlobalUserInfo, UserService } from '$lib/gen'
|
||||
import { userStore } from '$lib/stores'
|
||||
import { twMerge } from 'tailwind-merge'
|
||||
|
||||
import { setContext } from 'svelte'
|
||||
import github from 'svelte-highlight/styles/github'
|
||||
import { writable } from 'svelte/store'
|
||||
@@ -69,7 +71,10 @@
|
||||
</Alert></div
|
||||
>
|
||||
{:else if app}
|
||||
<div class="border rounded-md p-2 w-full">
|
||||
<div
|
||||
class={twMerge('bg-gray-100 min-h-screen w-full', app?.value.css?.['app']?.['viewer']?.class)}
|
||||
style={app?.value.css?.['app']?.['viewer']?.style}
|
||||
>
|
||||
<AppPreview
|
||||
noBackend={false}
|
||||
context={{
|
||||
|
||||
@@ -3,7 +3,14 @@ const plugin = require('tailwindcss/plugin')
|
||||
/** @type {import('tailwindcss').Config} */
|
||||
const config = {
|
||||
content: ['./src/**/*.{html,js,svelte,ts}'],
|
||||
safelist: ['hljs', 'splitpanes__pane', 'splitpanes__splitter'],
|
||||
safelist: [
|
||||
'hljs',
|
||||
'splitpanes__pane',
|
||||
'splitpanes__splitter',
|
||||
{
|
||||
pattern: /.*/
|
||||
}
|
||||
],
|
||||
theme: {
|
||||
colors: {
|
||||
current: 'currentcolor',
|
||||
@@ -237,26 +244,26 @@ const config = {
|
||||
color: theme('colors.blue.500')
|
||||
},
|
||||
'input,input[type="text"],input[type="email"],input[type="url"],input[type="password"],input[type="number"],input[type="date"],input[type="datetime-local"],input[type="month"],input[type="search"],input[type="tel"],input[type="time"],input[type="week"],textarea:not(.monaco-mouse-cursor-text),select':
|
||||
{
|
||||
display: 'block',
|
||||
fontSize: theme('fontSize.sm'),
|
||||
width: '100%',
|
||||
padding: `${theme('spacing.1')} ${theme('spacing.2')}`,
|
||||
border: `1px solid ${theme('colors.gray.300')}`,
|
||||
borderRadius: theme('borderRadius.md'),
|
||||
'&:focus': {
|
||||
'--tw-ring-color': theme('colors.indigo.100'),
|
||||
'--tw-ring-offset-shadow':
|
||||
'var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color)',
|
||||
'--tw-ring-shadow':
|
||||
'var(--tw-ring-inset) 0 0 0 calc(3px + var(--tw-ring-offset-width)) var(--tw-ring-color)',
|
||||
boxShadow:
|
||||
'var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000)'
|
||||
{
|
||||
display: 'block',
|
||||
fontSize: theme('fontSize.sm'),
|
||||
width: '100%',
|
||||
padding: `${theme('spacing.1')} ${theme('spacing.2')}`,
|
||||
border: `1px solid ${theme('colors.gray.300')}`,
|
||||
borderRadius: theme('borderRadius.md'),
|
||||
'&:focus': {
|
||||
'--tw-ring-color': theme('colors.indigo.100'),
|
||||
'--tw-ring-offset-shadow':
|
||||
'var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color)',
|
||||
'--tw-ring-shadow':
|
||||
'var(--tw-ring-inset) 0 0 0 calc(3px + var(--tw-ring-offset-width)) var(--tw-ring-color)',
|
||||
boxShadow:
|
||||
'var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000)'
|
||||
},
|
||||
'&:disabled,[disabled]': {
|
||||
backgroundColor: theme('colors.gray.100') + ' !important'
|
||||
}
|
||||
},
|
||||
'&:disabled,[disabled]': {
|
||||
backgroundColor: theme('colors.gray.100') + ' !important'
|
||||
}
|
||||
},
|
||||
'button:disabled,button[disabled=true],a:disabled,a[disabled=true]': {
|
||||
pointerEvents: 'none',
|
||||
cursor: 'default',
|
||||
|
||||
Reference in New Issue
Block a user