feat(apps): add custom css for apps

This commit is contained in:
Ruben Fiszel
2023-02-19 12:51:59 +01:00
parent aa289f644a
commit d6f002f2e7
20 changed files with 346 additions and 84 deletions
+11
View File
@@ -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",
+1
View File
@@ -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": {
+1
View File
@@ -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={{
+27 -20
View File
@@ -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',