mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-08-23 00:00:33 +00:00
feat(frontend): Global CSS editor (#2178)
* feat(frontend): add global css * feat(frontend): working styling * feat(frontend): Add default classes * wip * wip * wip * wip * wip * wip * wip * wip * feat(frontend): Add global css v0 * feat(frontend): Add global css v0 * add css workers * fix(frontend): Fix overflow issue * wip * feat(frontend): check for EE before injecting global css * wip * wip * wip * fix(frontend): fix typing issues * fix(frontend): fix typing issues * fix(frontend): fix global css * fix(frontend): add missing mapping * fix(frontend): Fix how styles are loaded * fix(frontend): fix preview * feat(frontend): fix everything * feat(frontend): fix class autocomplete * feat(frontend): remove console.log * feat(frontend): update tooltup * feat(frontend): eval * feat(frontend): eval * feat(frontend): fix build * feat(frontend): fix initial binding * feat(frontend): wip * wip * feat(frontend): Finish theme v0 * feat(frontend): Fix resource page * feat(frontend): fix build * feat(frontend): theme UI * feat(frontend): theme UI * feat(frontend): theme UI * feat(frontend): fix EE * feat(frontend): add missing warning * feat(frontend): fix preview * feat(frontend): fix global css by component initialisation * feat(frontend): remove unused libraries * feat(frontend): fix EE check * feat(frontend): fix EE check * feat(frontend): fix preview * feat(frontend): Fix migration * feat(frontend): Fix issues * feat(frontend): add missing disabled in migration modal * feat(frontend): Fix preview * feat(frontend): Fix preview * all * all * all * sqlx --------- Co-authored-by: Ruben Fiszel <ruben@rubenfiszel.com>
This commit is contained in:
@@ -58,7 +58,7 @@ RUN /usr/local/bin/python3 -m pip install nltk
|
||||
RUN mkdir -p /nsjail_data/python && HOME=/nsjail_data/python /usr/local/bin/python3 -m nltk.downloader vader_lexicon
|
||||
|
||||
COPY --from=nsjail /nsjail/nsjail /bin/nsjail
|
||||
|
||||
COPY --from=oven/bun:1.0.0 /usr/local/bin/bun /usr/bin/bun
|
||||
COPY --from=denoland/deno:latest /usr/bin/deno /usr/bin/deno
|
||||
|
||||
RUN apt-get update \
|
||||
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "INSERT INTO folder (workspace_id, name, display_name, owners, extra_perms) VALUES ($1, 'app_themes', 'App Themes', ARRAY[]::TEXT[], '{\"g/all\": false}') ON CONFLICT DO NOTHING",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Varchar"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "584cb984ea6528baac48c5c437ad2ee3bef92e3fada73dcf519147964c0f4f4a"
|
||||
}
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "INSERT INTO resource (workspace_id, path, value, description, resource_type) VALUES ($1, 'f/app_themes/theme_0', '{\"name\": \"Default Theme\", \"value\": \"\"}', 'The default app theme', 'app_theme') ON CONFLICT DO NOTHING",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Varchar"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "d956b7525f83e6d03beadc4bb3ee2798f53d990b01b17bdbc044719d4908e3f4"
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
-- Add down migration script here
|
||||
@@ -0,0 +1,3 @@
|
||||
-- Add up migration script here
|
||||
INSERT INTO folder (workspace_id, name, display_name, owners, extra_perms) SELECT id, 'app_themes', 'App Themes', ARRAY[]::TEXT[], '{"g/all": false}' FROM workspace ON CONFLICT DO NOTHING;
|
||||
INSERT INTO resource (workspace_id, path, value, description, resource_type) SELECT id, 'f/app_themes/theme_0', '{"name": "Default Theme", "value": ""}', 'The default app theme', 'app_theme' FROM workspace ON CONFLICT DO NOTHING
|
||||
@@ -3519,6 +3519,22 @@ paths:
|
||||
schema:
|
||||
$ref: "#/components/schemas/AppWithLastVersion"
|
||||
|
||||
/w/{workspace}/apps_u/public_resource/{path}:
|
||||
get:
|
||||
summary: get public resource
|
||||
operationId: get public resource
|
||||
tags:
|
||||
- app
|
||||
parameters:
|
||||
- $ref: "#/components/parameters/WorkspaceId"
|
||||
- $ref: "#/components/parameters/Path"
|
||||
responses:
|
||||
"200":
|
||||
description: resource value
|
||||
content:
|
||||
application/json:
|
||||
schema: {}
|
||||
|
||||
/w/{workspace}/apps/secret_of/{path}:
|
||||
get:
|
||||
summary: get public secret of app
|
||||
|
||||
@@ -57,6 +57,7 @@ pub fn unauthed_service() -> Router {
|
||||
Router::new()
|
||||
.route("/execute_component/*path", post(execute_component))
|
||||
.route("/public_app/:secret", get(get_public_app_by_secret))
|
||||
.route("/public_resource/*path", get(get_public_resource))
|
||||
}
|
||||
|
||||
pub fn global_service() -> Router {
|
||||
@@ -341,6 +342,27 @@ async fn get_public_app_by_secret(
|
||||
Ok(Json(app))
|
||||
}
|
||||
|
||||
async fn get_public_resource(
|
||||
Extension(db): Extension<DB>,
|
||||
Path((w_id, path)): Path<(String, StripPath)>,
|
||||
) -> JsonResult<Option<serde_json::Value>> {
|
||||
let path = path.to_path();
|
||||
if !path.starts_with("f/app_themes/") {
|
||||
return Err(Error::BadRequest(
|
||||
"Only app themes are public resources".to_string(),
|
||||
));
|
||||
}
|
||||
let res = sqlx::query_scalar!(
|
||||
"SELECT value from resource WHERE path = $1 AND workspace_id = $2",
|
||||
path.to_owned(),
|
||||
&w_id
|
||||
)
|
||||
.fetch_optional(&db)
|
||||
.await?
|
||||
.flatten();
|
||||
Ok(Json(res))
|
||||
}
|
||||
|
||||
async fn get_secret_id(
|
||||
authed: ApiAuthed,
|
||||
Extension(user_db): Extension<UserDB>,
|
||||
|
||||
@@ -950,6 +950,21 @@ async fn create_workspace(
|
||||
.execute(&mut *tx)
|
||||
.await?;
|
||||
|
||||
sqlx::query!(
|
||||
"INSERT INTO folder (workspace_id, name, display_name, owners, extra_perms) VALUES ($1, 'app_themes', 'App Themes', ARRAY[]::TEXT[], '{\"g/all\": false}') ON CONFLICT DO NOTHING",
|
||||
nw.id,
|
||||
)
|
||||
.execute(&mut *tx)
|
||||
.await?;
|
||||
|
||||
|
||||
sqlx::query!(
|
||||
"INSERT INTO resource (workspace_id, path, value, description, resource_type) VALUES ($1, 'f/app_themes/theme_0', '{\"name\": \"Default Theme\", \"value\": \"\"}', 'The default app theme', 'app_theme') ON CONFLICT DO NOTHING",
|
||||
nw.id,
|
||||
)
|
||||
.execute(&mut *tx)
|
||||
.await?;
|
||||
|
||||
audit_log(
|
||||
&mut *tx,
|
||||
&authed.username,
|
||||
|
||||
@@ -608,7 +608,7 @@ pub async fn run_worker<R: rsmq_async::RsmqConnection + Send + Sync + Clone + 's
|
||||
let vacuum_shift = rand::thread_rng().gen_range(0..VACUUM_PERIOD);
|
||||
|
||||
IS_READY.store(true, Ordering::Relaxed);
|
||||
tracing::info!(worker = %worker_name, "listening for jobs, config: {:#?}", WORKER_CONFIG.read().await);
|
||||
tracing::info!(worker = %worker_name, "listening for jobs, config: {:?}", WORKER_CONFIG.read().await);
|
||||
|
||||
let (dedicated_worker_tx, dedicated_worker_handle) = if let Some(_wp) =
|
||||
WORKER_CONFIG.read().await.dedicated_worker.clone()
|
||||
|
||||
@@ -236,7 +236,7 @@
|
||||
{:else if !forceJson && resultKind == 'file'}
|
||||
<div
|
||||
><a
|
||||
download={result.filename ?? 'windmill.file'}
|
||||
download={result.filename ?? result.file?.filename ?? 'windmill.file'}
|
||||
href="data:application/octet-stream;base64,{contentOrRootString(result.file)}">Download</a
|
||||
>
|
||||
</div>
|
||||
|
||||
@@ -24,6 +24,8 @@
|
||||
import 'monaco-editor/esm/vs/basic-languages/graphql/graphql.contribution'
|
||||
import 'monaco-editor/esm/vs/basic-languages/powershell/powershell.contribution'
|
||||
import 'monaco-editor/esm/vs/language/typescript/monaco.contribution'
|
||||
import 'monaco-editor/esm/vs/basic-languages/css/css.contribution'
|
||||
|
||||
import { MonacoLanguageClient, initServices } from 'monaco-languageclient'
|
||||
import { toSocket, WebSocketMessageReader, WebSocketMessageWriter } from 'vscode-ws-jsonrpc'
|
||||
import { CloseAction, ErrorAction, RequestType, NotificationType } from 'vscode-languageclient'
|
||||
@@ -49,7 +51,15 @@
|
||||
let divEl: HTMLDivElement | null = null
|
||||
let editor: meditor.IStandaloneCodeEditor
|
||||
|
||||
export let lang: 'typescript' | 'python' | 'go' | 'shell' | 'sql' | 'graphql' | 'powershell'
|
||||
export let lang:
|
||||
| 'typescript'
|
||||
| 'python'
|
||||
| 'go'
|
||||
| 'shell'
|
||||
| 'sql'
|
||||
| 'graphql'
|
||||
| 'powershell'
|
||||
| 'css'
|
||||
export let deno: boolean
|
||||
export let code: string = ''
|
||||
export let cmdEnterAction: (() => void) | undefined = undefined
|
||||
|
||||
@@ -42,6 +42,7 @@
|
||||
return sql
|
||||
case 'powershell':
|
||||
return powershell
|
||||
|
||||
default:
|
||||
return typescript
|
||||
}
|
||||
|
||||
@@ -18,6 +18,9 @@
|
||||
import 'monaco-editor/esm/vs/basic-languages/graphql/graphql.contribution'
|
||||
import 'monaco-editor/esm/vs/language/json/monaco.contribution'
|
||||
import 'monaco-editor/esm/vs/language/typescript/monaco.contribution'
|
||||
import 'monaco-editor/esm/vs/basic-languages/css/css.contribution'
|
||||
import 'monaco-editor/esm/vs/language/css/monaco.contribution'
|
||||
import { allClasses, authorizedClassnames } from './apps/editor/componentsPanel/cssUtils'
|
||||
|
||||
import { createEventDispatcher, onDestroy, onMount } from 'svelte'
|
||||
|
||||
@@ -185,6 +188,40 @@
|
||||
})
|
||||
}
|
||||
|
||||
$: lang == 'css' && addCSSClassCompletions()
|
||||
|
||||
function addCSSClassCompletions() {
|
||||
languages.registerCompletionItemProvider('css', {
|
||||
provideCompletionItems: function (model, position, context, token) {
|
||||
const word = model.getWordUntilPosition(position)
|
||||
const range = {
|
||||
startLineNumber: position.lineNumber,
|
||||
startColumn: word.startColumn,
|
||||
endLineNumber: position.lineNumber,
|
||||
endColumn: word.endColumn
|
||||
}
|
||||
|
||||
if (word && word.word) {
|
||||
const currentWord = word.word
|
||||
|
||||
const suggestions = allClasses
|
||||
.filter((className) => className.includes(currentWord))
|
||||
.map((className) => ({
|
||||
label: className,
|
||||
kind: languages.CompletionItemKind.Class,
|
||||
insertText: className,
|
||||
documentation: 'Custom CSS class',
|
||||
range: range
|
||||
}))
|
||||
|
||||
return { suggestions }
|
||||
}
|
||||
|
||||
return { suggestions: [] }
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function loadExtraLib() {
|
||||
if (lang == 'javascript') {
|
||||
const stdLib = { content: libStdContent, filePath: 'es5.d.ts' }
|
||||
@@ -199,6 +236,38 @@
|
||||
} else {
|
||||
languages.typescript.javascriptDefaults.setExtraLibs([stdLib])
|
||||
}
|
||||
} else if (lang === 'css') {
|
||||
const cssClasses = authorizedClassnames.map((className) => '.' + className)
|
||||
|
||||
languages.registerCompletionItemProvider('css', {
|
||||
provideCompletionItems: function (model, position, context, token) {
|
||||
const word = model.getWordUntilPosition(position)
|
||||
const range = {
|
||||
startLineNumber: position.lineNumber,
|
||||
startColumn: word.startColumn,
|
||||
endLineNumber: position.lineNumber,
|
||||
endColumn: word.endColumn
|
||||
}
|
||||
|
||||
if (word && word.word) {
|
||||
const currentWord = word.word
|
||||
|
||||
const suggestions = cssClasses
|
||||
.filter((className) => className.includes(currentWord))
|
||||
.map((className) => ({
|
||||
label: className,
|
||||
kind: languages.CompletionItemKind.Class,
|
||||
insertText: className,
|
||||
documentation: 'Custom CSS class',
|
||||
range: range
|
||||
}))
|
||||
|
||||
return { suggestions }
|
||||
}
|
||||
|
||||
return { suggestions: [] }
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -6,10 +6,11 @@
|
||||
export let headers: string[] | undefined
|
||||
export let data: any[] | undefined // Object containing the data
|
||||
export let keys: string[]
|
||||
export let size: 'sm' | 'md' | 'lg' = 'md'
|
||||
</script>
|
||||
|
||||
<div class="mt-2">
|
||||
<DataTable>
|
||||
<div class="mt-2 w-full">
|
||||
<DataTable {size}>
|
||||
<Head>
|
||||
<tr>
|
||||
{#if headers}
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
export let wrapperClass = ''
|
||||
export let placement: PopoverPlacement | undefined = undefined
|
||||
export let documentationLink: string | undefined = undefined
|
||||
export let small = false
|
||||
</script>
|
||||
|
||||
<Popover notClickable {placement} class={wrapperClass}>
|
||||
@@ -15,7 +16,7 @@
|
||||
? 'text-tertiary-inverse'
|
||||
: 'text-tertiary'} {$$props.class} relative"
|
||||
>
|
||||
<InfoIcon class="-bottom-0.5 absolute" size={16} />
|
||||
<InfoIcon class="{small ? 'bottom-0' : '-bottom-0.5'} absolute" size={small ? 12 : 16} />
|
||||
</div>
|
||||
<svelte:fragment slot="text">
|
||||
<slot />
|
||||
|
||||
@@ -17,7 +17,8 @@
|
||||
import { initConfig, initOutput } from '../../editor/appUtils'
|
||||
import { components } from '../../editor/component'
|
||||
import ResolveConfig from '../helpers/ResolveConfig.svelte'
|
||||
import { concatCustomCss } from '../../utils'
|
||||
import ResolveStyle from '../helpers/ResolveStyle.svelte'
|
||||
import { initCss } from '../../utils'
|
||||
|
||||
export let id: string
|
||||
export let componentInput: AppInput | undefined
|
||||
@@ -123,7 +124,7 @@
|
||||
}
|
||||
let loading = false
|
||||
|
||||
$: css = concatCustomCss($app.css?.buttoncomponent, customCss)
|
||||
let css = initCss($app.css?.buttoncomponent, customCss)
|
||||
</script>
|
||||
|
||||
{#each Object.keys(components['buttoncomponent'].initialData.configuration) as key (key)}
|
||||
@@ -136,6 +137,16 @@
|
||||
/>
|
||||
{/each}
|
||||
|
||||
{#each Object.keys(css ?? {}) as key (key)}
|
||||
<ResolveStyle
|
||||
{id}
|
||||
{customCss}
|
||||
{key}
|
||||
bind:css={css[key]}
|
||||
componentStyle={$app.css?.buttoncomponent}
|
||||
/>
|
||||
{/each}
|
||||
|
||||
<!-- gotoNewTab={resolvedConfig.onSuccess.selected == 'goto'} -->
|
||||
<RunnableWrapper
|
||||
bind:this={runnableWrapper}
|
||||
@@ -154,16 +165,17 @@
|
||||
{extraKey}
|
||||
refreshOnStart={resolvedConfig.triggerOnAppLoad}
|
||||
>
|
||||
<AlignWrapper {noWFull} {horizontalAlignment} {verticalAlignment}>
|
||||
<AlignWrapper {noWFull} {horizontalAlignment} {verticalAlignment} class="wm-button-wrapper">
|
||||
{#if errorsMessage}
|
||||
<div class="text-red-500 text-xs">{errorsMessage}</div>
|
||||
{/if}
|
||||
<Button
|
||||
on:pointerdown={(e) => e.stopPropagation()}
|
||||
btnClasses={css?.button?.class}
|
||||
btnClasses={twMerge(css?.button?.class, 'wm-button')}
|
||||
wrapperClasses={twMerge(
|
||||
css?.container?.class,
|
||||
resolvedConfig.fillContainer ? 'w-full h-full' : ''
|
||||
resolvedConfig.fillContainer ? 'w-full h-full' : '',
|
||||
'wm-button-container'
|
||||
)}
|
||||
wrapperStyle={css?.container?.style}
|
||||
style={css?.button?.style}
|
||||
|
||||
@@ -7,11 +7,13 @@
|
||||
import { components } from '../../editor/component'
|
||||
import type { AppInput } from '../../inputType'
|
||||
import type { AppViewerContext, ComponentCustomCSS, RichConfigurations } from '../../types'
|
||||
import { concatCustomCss } from '../../utils'
|
||||
import { initCss } from '../../utils'
|
||||
import AlignWrapper from '../helpers/AlignWrapper.svelte'
|
||||
import ResolveConfig from '../helpers/ResolveConfig.svelte'
|
||||
import type RunnableComponent from '../helpers/RunnableComponent.svelte'
|
||||
import RunnableWrapper from '../helpers/RunnableWrapper.svelte'
|
||||
import { twMerge } from 'tailwind-merge'
|
||||
import ResolveStyle from '../helpers/ResolveStyle.svelte'
|
||||
|
||||
export let id: string
|
||||
export let componentInput: AppInput | undefined
|
||||
@@ -47,7 +49,7 @@
|
||||
$stateId != undefined &&
|
||||
(componentInput?.type != 'runnable' || Object.keys(componentInput?.fields ?? {}).length == 0)
|
||||
|
||||
$: css = concatCustomCss($app.css?.formcomponent, customCss)
|
||||
let css = initCss($app.css?.formcomponent, customCss)
|
||||
</script>
|
||||
|
||||
{#each Object.keys(components['formcomponent'].initialData.configuration) as key (key)}
|
||||
@@ -59,6 +61,16 @@
|
||||
/>
|
||||
{/each}
|
||||
|
||||
{#each Object.keys(css ?? {}) as key (key)}
|
||||
<ResolveStyle
|
||||
{id}
|
||||
{customCss}
|
||||
{key}
|
||||
bind:css={css[key]}
|
||||
componentStyle={$app.css?.formcomponent}
|
||||
/>
|
||||
{/each}
|
||||
|
||||
<RunnableWrapper
|
||||
{recomputeIds}
|
||||
{render}
|
||||
@@ -78,7 +90,7 @@
|
||||
>
|
||||
<AlignWrapper {horizontalAlignment}>
|
||||
<div
|
||||
class="flex flex-col gap-2 px-4 w-full {css?.container?.class ?? ''}"
|
||||
class={twMerge('flex flex-col gap-2 px-4 w-full', css?.container?.class, 'wm-submit')}
|
||||
style={css?.container?.style ?? ''}
|
||||
>
|
||||
<div>
|
||||
@@ -98,7 +110,7 @@
|
||||
{#if !noInputs}
|
||||
<Button
|
||||
{loading}
|
||||
btnClasses={css?.button?.class}
|
||||
btnClasses={twMerge(css?.button?.class, 'wm-submit-button')}
|
||||
style={css?.button?.style ?? ''}
|
||||
on:pointerdown={(e) => {
|
||||
e?.stopPropagation()
|
||||
|
||||
@@ -9,11 +9,13 @@
|
||||
import type RunnableComponent from '../helpers/RunnableComponent.svelte'
|
||||
import RunnableWrapper from '../helpers/RunnableWrapper.svelte'
|
||||
|
||||
import { concatCustomCss } from '../../utils'
|
||||
import { initCss } from '../../utils'
|
||||
import { initConfig, initOutput } from '../../editor/appUtils'
|
||||
import { components } from '../../editor/component'
|
||||
import ResolveConfig from '../helpers/ResolveConfig.svelte'
|
||||
import AlwaysMountedModal from '$lib/components/common/modal/AlwaysMountedModal.svelte'
|
||||
import { twMerge } from 'tailwind-merge'
|
||||
import ResolveStyle from '../helpers/ResolveStyle.svelte'
|
||||
|
||||
export let id: string
|
||||
export let componentInput: AppInput | undefined
|
||||
@@ -56,12 +58,22 @@
|
||||
$: noInputs =
|
||||
componentInput?.type != 'runnable' || Object.keys(componentInput?.fields ?? {}).length == 0
|
||||
|
||||
$: css = concatCustomCss($app?.css?.formbuttoncomponent, customCss)
|
||||
let css = initCss($app?.css?.formbuttoncomponent, customCss)
|
||||
let runnableWrapper: RunnableWrapper
|
||||
let loading = false
|
||||
let modal: AlwaysMountedModal
|
||||
</script>
|
||||
|
||||
{#each Object.keys(css ?? {}) as key (key)}
|
||||
<ResolveStyle
|
||||
{id}
|
||||
{customCss}
|
||||
{key}
|
||||
bind:css={css[key]}
|
||||
componentStyle={$app.css?.formbuttoncomponent}
|
||||
/>
|
||||
{/each}
|
||||
|
||||
{#each Object.keys(components['formbuttoncomponent'].initialData.configuration) as key (key)}
|
||||
<ResolveConfig
|
||||
{id}
|
||||
@@ -70,7 +82,6 @@
|
||||
configuration={configuration[key]}
|
||||
/>
|
||||
{/each}
|
||||
|
||||
<AlwaysMountedModal {css} title={resolvedConfig.modalTitle ?? ''} bind:this={modal}>
|
||||
<div class="flex flex-col gap-2 px-4 w-full pt-2">
|
||||
<RunnableWrapper
|
||||
@@ -134,7 +145,7 @@
|
||||
disabled={resolvedConfig.disabled ?? false}
|
||||
size={resolvedConfig.size ?? 'md'}
|
||||
color={resolvedConfig.color}
|
||||
btnClasses={css?.button?.class ?? ''}
|
||||
btnClasses={twMerge(css?.button?.class, 'wm-button', 'wm-modal-form-button')}
|
||||
style={css?.button?.style ?? ''}
|
||||
on:click={(e) => {
|
||||
modal?.open()
|
||||
|
||||
@@ -12,10 +12,11 @@
|
||||
import RunnableWrapper from '../helpers/RunnableWrapper.svelte'
|
||||
import LightweightSchemaForm from '$lib/components/LightweightSchemaForm.svelte'
|
||||
import type { Schema } from '$lib/common'
|
||||
import { concatCustomCss } from '../../utils'
|
||||
import { initCss } from '../../utils'
|
||||
import { twMerge } from 'tailwind-merge'
|
||||
import { components } from '../../editor/component'
|
||||
import ResolveConfig from '../helpers/ResolveConfig.svelte'
|
||||
import ResolveStyle from '../helpers/ResolveStyle.svelte'
|
||||
|
||||
export let id: string
|
||||
export let componentInput: AppInput | undefined
|
||||
@@ -64,7 +65,7 @@
|
||||
|
||||
$: outputs.valid.set(valid)
|
||||
|
||||
$: css = concatCustomCss($app.css?.schemaformcomponent, customCss)
|
||||
let css = initCss($app.css?.schemaformcomponent, customCss)
|
||||
|
||||
const resolvedConfig = initConfig(
|
||||
components['schemaformcomponent'].initialData.configuration,
|
||||
@@ -83,10 +84,20 @@
|
||||
/>
|
||||
{/each}
|
||||
|
||||
{#each Object.keys(css ?? {}) as key (key)}
|
||||
<ResolveStyle
|
||||
{id}
|
||||
{customCss}
|
||||
{key}
|
||||
bind:css={css[key]}
|
||||
componentStyle={$app.css?.schemaformcomponent}
|
||||
/>
|
||||
{/each}
|
||||
|
||||
<RunnableWrapper {outputs} {render} autoRefresh {componentInput} {id} bind:initializing bind:result>
|
||||
{#if result && Object.keys(result?.properties ?? {}).length > 0}
|
||||
<div
|
||||
class={twMerge('p-2 overflow-auto h-full', css?.container?.class)}
|
||||
class={twMerge('p-2 overflow-auto h-full', css?.container?.class, 'wm-schema-form')}
|
||||
style={css?.container?.style}
|
||||
on:pointerdown|stopPropagation={(e) =>
|
||||
!$connectingInput.opened && selectId(e, id, selectedComponent, $app)}
|
||||
|
||||
@@ -16,10 +16,12 @@
|
||||
import { initConfig, initOutput } from '../../editor/appUtils'
|
||||
import type { AppInput } from '../../inputType'
|
||||
import type { AppViewerContext, ComponentCustomCSS, RichConfigurations } from '../../types'
|
||||
import { concatCustomCss } from '../../utils'
|
||||
import { initCss } from '../../utils'
|
||||
import RunnableWrapper from '../helpers/RunnableWrapper.svelte'
|
||||
import { components } from '../../editor/component'
|
||||
import ResolveConfig from '../helpers/ResolveConfig.svelte'
|
||||
import { twMerge } from 'tailwind-merge'
|
||||
import ResolveStyle from '../helpers/ResolveStyle.svelte'
|
||||
export let id: string
|
||||
export let componentInput: AppInput | undefined
|
||||
export let configuration: RichConfigurations
|
||||
@@ -105,7 +107,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
$: css = concatCustomCss($app.css?.barchartcomponent, customCss)
|
||||
let css = initCss($app.css?.barchartcomponent, customCss)
|
||||
</script>
|
||||
|
||||
{#each Object.keys(components['barchartcomponent'].initialData.configuration) as key (key)}
|
||||
@@ -117,8 +119,21 @@
|
||||
/>
|
||||
{/each}
|
||||
|
||||
{#each Object.keys(css ?? {}) as key (key)}
|
||||
<ResolveStyle
|
||||
{id}
|
||||
{customCss}
|
||||
{key}
|
||||
bind:css={css[key]}
|
||||
componentStyle={$app.css?.barchartcomponent}
|
||||
/>
|
||||
{/each}
|
||||
|
||||
<RunnableWrapper {outputs} {render} autoRefresh {componentInput} {id} bind:initializing bind:result>
|
||||
<div class="w-full h-full {css?.container?.class ?? ''}" style={css?.container?.style ?? ''}>
|
||||
<div
|
||||
class={twMerge('w-full h-full', css?.container?.class, 'wm-bar-chart')}
|
||||
style={css?.container?.style ?? ''}
|
||||
>
|
||||
{#if result}
|
||||
{#if resolvedConfig.line}
|
||||
<Line {data} options={lineOptions} />
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
import { initConfig, initOutput } from '../../editor/appUtils'
|
||||
import SubGridEditor from '../../editor/SubGridEditor.svelte'
|
||||
import type { AppViewerContext, ComponentCustomCSS, RichConfigurations } from '../../types'
|
||||
import { concatCustomCss } from '../../utils'
|
||||
import { initCss } from '../../utils'
|
||||
import InitializeComponent from '../helpers/InitializeComponent.svelte'
|
||||
import type { AppInput } from '../../inputType'
|
||||
import ResolveConfig from '../helpers/ResolveConfig.svelte'
|
||||
@@ -13,11 +13,13 @@
|
||||
import Carousel from 'svelte-carousel'
|
||||
import { ArrowLeftCircle, ArrowRightCircle } from 'lucide-svelte'
|
||||
import { Button } from '$lib/components/common'
|
||||
import { twMerge } from 'tailwind-merge'
|
||||
import ResolveStyle from '../helpers/ResolveStyle.svelte'
|
||||
|
||||
export let id: string
|
||||
export let componentInput: AppInput | undefined
|
||||
export let configuration: RichConfigurations
|
||||
export let customCss: ComponentCustomCSS<'containercomponent'> | undefined = undefined
|
||||
export let customCss: ComponentCustomCSS<'carousellistcomponent'> | undefined = undefined
|
||||
export let render: boolean
|
||||
export let initializing: boolean | undefined
|
||||
export let componentContainerHeight: number
|
||||
@@ -43,7 +45,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
$: css = concatCustomCss($app.css?.containercomponent, customCss)
|
||||
let css = initCss($app.css?.carousellistcomponent, customCss)
|
||||
let result: any[] | undefined = undefined
|
||||
|
||||
let inputs = {}
|
||||
@@ -67,6 +69,16 @@
|
||||
/>
|
||||
{/each}
|
||||
|
||||
{#each Object.keys(css ?? {}) as key (key)}
|
||||
<ResolveStyle
|
||||
{id}
|
||||
{customCss}
|
||||
{key}
|
||||
bind:css={css[key]}
|
||||
componentStyle={$app.css?.carousellistcomponent}
|
||||
/>
|
||||
{/each}
|
||||
|
||||
<InitializeComponent {id} />
|
||||
|
||||
<RunnableWrapper
|
||||
@@ -148,7 +160,7 @@
|
||||
<SubGridEditor
|
||||
{id}
|
||||
visible={render}
|
||||
class={css?.container?.class}
|
||||
class={twMerge(css?.container?.class, 'wm-carousel')}
|
||||
style={css?.container?.style}
|
||||
subGridId={`${id}-0`}
|
||||
containerHeight={componentContainerHeight - 40}
|
||||
|
||||
@@ -4,11 +4,13 @@
|
||||
import RunnableWrapper from '../helpers/RunnableWrapper.svelte'
|
||||
import type { AppInput } from '../../inputType'
|
||||
import type { AppViewerContext, ComponentCustomCSS, RichConfigurations } from '../../types'
|
||||
import { concatCustomCss } from '../../utils'
|
||||
import { initCss } from '../../utils'
|
||||
import { getContext } from 'svelte'
|
||||
import { initConfig, initOutput } from '../../editor/appUtils'
|
||||
import { components } from '../../editor/component'
|
||||
import ResolveConfig from '../helpers/ResolveConfig.svelte'
|
||||
import { twMerge } from 'tailwind-merge'
|
||||
import ResolveStyle from '../helpers/ResolveStyle.svelte'
|
||||
|
||||
export let id: string
|
||||
export let componentInput: AppInput | undefined
|
||||
@@ -39,7 +41,7 @@
|
||||
...(resolvedConfig.options ?? {})
|
||||
} as ChartOptions
|
||||
|
||||
$: css = concatCustomCss($app.css?.piechartcomponent, customCss)
|
||||
let css = initCss($app.css?.chartjscomponent, customCss)
|
||||
</script>
|
||||
|
||||
{#each Object.keys(components['chartjscomponent'].initialData.configuration) as key (key)}
|
||||
@@ -51,11 +53,26 @@
|
||||
/>
|
||||
{/each}
|
||||
|
||||
{#each Object.keys(css ?? {}) as key (key)}
|
||||
<ResolveStyle
|
||||
{id}
|
||||
{customCss}
|
||||
{key}
|
||||
bind:css={css[key]}
|
||||
componentStyle={$app.css?.chartjscomponent}
|
||||
/>
|
||||
{/each}
|
||||
|
||||
<RunnableWrapper {outputs} {render} autoRefresh {componentInput} {id} bind:initializing bind:result>
|
||||
<div class="w-full h-full {css?.container?.class ?? ''}" style={css?.container?.style ?? ''}>
|
||||
<div
|
||||
class={twMerge('w-full h-full', css?.container?.class, 'wm-chartjs')}
|
||||
style={css?.container?.style ?? ''}
|
||||
>
|
||||
{#if result && resolvedConfig.type}
|
||||
{#key resolvedConfig.type}
|
||||
<Chart type={resolvedConfig.type} data={result} {options} />
|
||||
{#key options}
|
||||
<Chart type={resolvedConfig.type} data={result} {options} />
|
||||
{/key}
|
||||
{/key}
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
@@ -10,7 +10,8 @@
|
||||
type ComponentCustomCSS
|
||||
} from '../../types'
|
||||
import RunnableWrapper from '../helpers/RunnableWrapper.svelte'
|
||||
import { concatCustomCss } from '../../utils'
|
||||
import { initCss } from '../../utils'
|
||||
import ResolveStyle from '../helpers/ResolveStyle.svelte'
|
||||
|
||||
export let id: string
|
||||
export let componentInput: AppInput | undefined
|
||||
@@ -33,15 +34,26 @@
|
||||
loading: false
|
||||
})
|
||||
|
||||
$: css = concatCustomCss($app.css?.displaycomponent, customCss)
|
||||
let css = initCss($app.css?.displaycomponent, customCss)
|
||||
</script>
|
||||
|
||||
{#each Object.keys(css ?? {}) as key (key)}
|
||||
<ResolveStyle
|
||||
{id}
|
||||
{customCss}
|
||||
{key}
|
||||
bind:css={css[key]}
|
||||
componentStyle={$app.css?.displaycomponent}
|
||||
/>
|
||||
{/each}
|
||||
|
||||
<RunnableWrapper {outputs} {render} {componentInput} {id} bind:initializing bind:result>
|
||||
<div class="flex flex-col w-full h-full">
|
||||
<div
|
||||
class={twMerge(
|
||||
'w-full border-b px-2 text-xs p-1 font-semibold bg-gray-500 text-white rounded-t-sm',
|
||||
css?.header?.class
|
||||
css?.header?.class,
|
||||
'wm-rich-result-header'
|
||||
)}
|
||||
style={css?.header?.style}
|
||||
>
|
||||
@@ -50,7 +62,8 @@
|
||||
<div
|
||||
style={twMerge(
|
||||
$app.css?.['displaycomponent']?.['container']?.style,
|
||||
customCss?.container?.style
|
||||
customCss?.container?.style,
|
||||
'wm-rich-result-container'
|
||||
)}
|
||||
class={twMerge(
|
||||
'p-2 grow overflow-auto',
|
||||
|
||||
@@ -4,13 +4,14 @@
|
||||
import { initConfig, initOutput } from '../../editor/appUtils'
|
||||
import { components } from '../../editor/component'
|
||||
import type { AppViewerContext, ComponentCustomCSS, RichConfigurations } from '../../types'
|
||||
import { concatCustomCss, transformBareBase64IfNecessary } from '../../utils'
|
||||
import { initCss, transformBareBase64IfNecessary } from '../../utils'
|
||||
import ResolveConfig from '../helpers/ResolveConfig.svelte'
|
||||
import InitializeComponent from '../helpers/InitializeComponent.svelte'
|
||||
import { AlignWrapper } from '../helpers'
|
||||
import { Button } from '$lib/components/common'
|
||||
import { loadIcon } from '../icon'
|
||||
import ComponentErrorHandler from '../helpers/ComponentErrorHandler.svelte'
|
||||
import ResolveStyle from '../helpers/ResolveStyle.svelte'
|
||||
|
||||
export let id: string
|
||||
export let configuration: RichConfigurations
|
||||
@@ -48,7 +49,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
$: css = concatCustomCss($app.css?.downloadcomponent, customCss)
|
||||
let css = initCss($app.css?.downloadcomponent, customCss)
|
||||
</script>
|
||||
|
||||
<InitializeComponent {id} />
|
||||
@@ -62,6 +63,16 @@
|
||||
/>
|
||||
{/each}
|
||||
|
||||
{#each Object.keys(css ?? {}) as key (key)}
|
||||
<ResolveStyle
|
||||
{id}
|
||||
{customCss}
|
||||
{key}
|
||||
bind:css={css[key]}
|
||||
componentStyle={$app.css?.downloadcomponent}
|
||||
/>
|
||||
{/each}
|
||||
|
||||
{#if render}
|
||||
<AlignWrapper {noWFull} {horizontalAlignment} {verticalAlignment}>
|
||||
<ComponentErrorHandler
|
||||
@@ -71,9 +82,15 @@
|
||||
on:pointerdown={(e) => e.stopPropagation()}
|
||||
btnClasses={twMerge(
|
||||
css?.button?.class,
|
||||
'wm-button',
|
||||
'wm-download-button',
|
||||
resolvedConfig.fillContainer ? 'w-full h-full' : ''
|
||||
)}
|
||||
wrapperClasses={twMerge(
|
||||
'wm-button-container',
|
||||
'wm-download-button-container',
|
||||
resolvedConfig.fillContainer ? 'w-full h-full' : ''
|
||||
)}
|
||||
wrapperClasses={resolvedConfig.fillContainer ? 'w-full h-full' : ''}
|
||||
style={css?.button?.style}
|
||||
disabled={resolvedConfig.source == undefined}
|
||||
size={resolvedConfig.size}
|
||||
|
||||
@@ -5,8 +5,9 @@
|
||||
import type { AppInput } from '../../inputType'
|
||||
import type { AppViewerContext, ComponentCustomCSS } from '../../types'
|
||||
import RunnableWrapper from '../helpers/RunnableWrapper.svelte'
|
||||
import { concatCustomCss } from '../../utils'
|
||||
import { initCss } from '../../utils'
|
||||
import FlowStatusViewer from '$lib/components/FlowStatusViewer.svelte'
|
||||
import ResolveStyle from '../helpers/ResolveStyle.svelte'
|
||||
|
||||
export let id: string
|
||||
export let componentInput: AppInput | undefined
|
||||
@@ -23,11 +24,20 @@
|
||||
|
||||
initializing = false
|
||||
|
||||
$: css = concatCustomCss($app.css?.flowstatuscomponent, customCss)
|
||||
|
||||
let css = initCss($app.css?.flowstatuscomponent, customCss)
|
||||
let jobId: string | undefined
|
||||
</script>
|
||||
|
||||
{#each Object.keys(css ?? {}) as key (key)}
|
||||
<ResolveStyle
|
||||
{id}
|
||||
{customCss}
|
||||
{key}
|
||||
bind:css={css[key]}
|
||||
componentStyle={$app.css?.flowstatuscomponent}
|
||||
/>
|
||||
{/each}
|
||||
|
||||
<RunnableWrapper
|
||||
on:started={(e) => {
|
||||
jobId = e.detail
|
||||
@@ -41,7 +51,8 @@
|
||||
<div
|
||||
class={twMerge(
|
||||
'w-full border-b px-2 text-xs p-1 font-semibold bg-gray-500 text-white rounded-t-sm',
|
||||
css?.header?.class
|
||||
css?.header?.class,
|
||||
'wm-flow-status-header'
|
||||
)}
|
||||
style={css?.header?.style}
|
||||
>
|
||||
@@ -55,7 +66,8 @@
|
||||
class={twMerge(
|
||||
'p-2 grow overflow-auto',
|
||||
$app.css?.['flowstatuscomponent']?.['container']?.class,
|
||||
customCss?.container?.class
|
||||
customCss?.container?.class,
|
||||
'wm-flow-status-container'
|
||||
)}
|
||||
>
|
||||
{#if jobId}
|
||||
|
||||
@@ -3,8 +3,10 @@
|
||||
import { initOutput } from '../../editor/appUtils'
|
||||
import type { AppInput } from '../../inputType'
|
||||
import type { AppViewerContext, ComponentCustomCSS } from '../../types'
|
||||
import { concatCustomCss } from '../../utils'
|
||||
import { initCss } from '../../utils'
|
||||
import RunnableWrapper from '../helpers/RunnableWrapper.svelte'
|
||||
import { twMerge } from 'tailwind-merge'
|
||||
import ResolveStyle from '../helpers/ResolveStyle.svelte'
|
||||
|
||||
export let id: string
|
||||
export let componentInput: AppInput | undefined
|
||||
@@ -23,9 +25,19 @@
|
||||
let h: number | undefined = undefined
|
||||
let w: number | undefined = undefined
|
||||
|
||||
$: css = concatCustomCss($app.css?.htmlcomponent, customCss)
|
||||
let css = initCss($app.css?.htmlcomponent, customCss)
|
||||
</script>
|
||||
|
||||
{#each Object.keys(css ?? {}) as key (key)}
|
||||
<ResolveStyle
|
||||
{id}
|
||||
{customCss}
|
||||
{key}
|
||||
bind:css={css[key]}
|
||||
componentStyle={$app.css?.htmlcomponent}
|
||||
/>
|
||||
{/each}
|
||||
|
||||
<div
|
||||
on:pointerdown={(e) => {
|
||||
e?.preventDefault()
|
||||
@@ -47,7 +59,7 @@
|
||||
<iframe
|
||||
frameborder="0"
|
||||
style="height: {h}px; width: {w}px; {css?.container?.style ?? ''}"
|
||||
class="p-0 {css?.container?.class ?? ''}"
|
||||
class={twMerge('p-0', css?.container?.class, 'wm-html')}
|
||||
title="sandbox"
|
||||
srcdoc={result
|
||||
? '<base target="_parent" /><scr' +
|
||||
|
||||
@@ -2,12 +2,14 @@
|
||||
import { getContext } from 'svelte'
|
||||
import { initConfig, initOutput } from '../../editor/appUtils'
|
||||
import type { AppViewerContext, ComponentCustomCSS, RichConfigurations } from '../../types'
|
||||
import { concatCustomCss } from '../../utils'
|
||||
import { initCss } from '../../utils'
|
||||
import { AlignWrapper } from '../helpers'
|
||||
import { loadIcon } from '../icon'
|
||||
import InitializeComponent from '../helpers/InitializeComponent.svelte'
|
||||
import { components } from '../../editor/component'
|
||||
import ResolveConfig from '../helpers/ResolveConfig.svelte'
|
||||
import { twMerge } from 'tailwind-merge'
|
||||
import ResolveStyle from '../helpers/ResolveStyle.svelte'
|
||||
|
||||
export let id: string
|
||||
export let horizontalAlignment: 'left' | 'center' | 'right' | undefined = 'left'
|
||||
@@ -33,7 +35,7 @@
|
||||
iconComponent = i ? await loadIcon(i) : undefined
|
||||
}
|
||||
|
||||
$: css = concatCustomCss($app.css?.iconcomponent, customCss)
|
||||
let css = initCss($app.css?.iconcomponent, customCss)
|
||||
</script>
|
||||
|
||||
{#each Object.keys(components['iconcomponent'].initialData.configuration) as key (key)}
|
||||
@@ -44,13 +46,24 @@
|
||||
configuration={configuration[key]}
|
||||
/>
|
||||
{/each}
|
||||
|
||||
{#each Object.keys(css ?? {}) as key (key)}
|
||||
<ResolveStyle
|
||||
{id}
|
||||
{customCss}
|
||||
{key}
|
||||
bind:css={css[key]}
|
||||
componentStyle={$app.css?.iconcomponent}
|
||||
/>
|
||||
{/each}
|
||||
|
||||
<InitializeComponent {id} />
|
||||
|
||||
<AlignWrapper
|
||||
{render}
|
||||
{horizontalAlignment}
|
||||
{verticalAlignment}
|
||||
class={css?.container?.class ?? ''}
|
||||
class={twMerge(css?.container?.class, 'wm-icon-container')}
|
||||
style={css?.container?.style ?? ''}
|
||||
>
|
||||
{#if resolvedConfig.icon && iconComponent}
|
||||
@@ -59,7 +72,7 @@
|
||||
size={resolvedConfig.size || 24}
|
||||
color={resolvedConfig.color || 'currentColor'}
|
||||
strokeWidth={resolvedConfig.strokeWidth || 2}
|
||||
class={css?.icon?.class ?? ''}
|
||||
class={twMerge(css?.icon?.class, 'wm-icon')}
|
||||
style={css?.icon?.style ?? ''}
|
||||
/>
|
||||
{/if}
|
||||
|
||||
@@ -4,10 +4,11 @@
|
||||
import { initConfig, initOutput } from '../../editor/appUtils'
|
||||
import { components } from '../../editor/component'
|
||||
import type { AppViewerContext, ComponentCustomCSS, RichConfigurations } from '../../types'
|
||||
import { concatCustomCss } from '../../utils'
|
||||
import { initCss } from '../../utils'
|
||||
import Loader from '../helpers/Loader.svelte'
|
||||
import ResolveConfig from '../helpers/ResolveConfig.svelte'
|
||||
import InitializeComponent from '../helpers/InitializeComponent.svelte'
|
||||
import ResolveStyle from '../helpers/ResolveStyle.svelte'
|
||||
|
||||
export let id: string
|
||||
export let configuration: RichConfigurations
|
||||
@@ -29,7 +30,7 @@
|
||||
//used so that we can count number of outputs setup for first refresh
|
||||
initOutput($worldStore, id, {})
|
||||
|
||||
$: css = concatCustomCss($app.css?.imagecomponent, customCss)
|
||||
let css = initCss($app.css?.imagecomponent, customCss)
|
||||
</script>
|
||||
|
||||
<InitializeComponent {id} />
|
||||
@@ -43,6 +44,16 @@
|
||||
/>
|
||||
{/each}
|
||||
|
||||
{#each Object.keys(css ?? {}) as key (key)}
|
||||
<ResolveStyle
|
||||
{id}
|
||||
{customCss}
|
||||
{key}
|
||||
bind:css={css[key]}
|
||||
componentStyle={$app.css?.imagecomponent}
|
||||
/>
|
||||
{/each}
|
||||
|
||||
{#if render}
|
||||
<Loader loading={resolvedConfig.source == undefined}>
|
||||
<img
|
||||
@@ -52,7 +63,8 @@
|
||||
style={css?.image?.style ?? ''}
|
||||
class={twMerge(
|
||||
`w-full h-full ${fit[resolvedConfig.imageFit || 'cover']}`,
|
||||
css?.image?.class ?? ''
|
||||
css?.image?.class,
|
||||
'wm-image'
|
||||
)}
|
||||
/>
|
||||
</Loader>
|
||||
|
||||
@@ -5,10 +5,11 @@
|
||||
import type { AppInput } from '../../inputType'
|
||||
import type { AppViewerContext, ComponentCustomCSS, RichConfigurations } from '../../types'
|
||||
import RunnableWrapper from '../helpers/RunnableWrapper.svelte'
|
||||
import { concatCustomCss } from '../../utils'
|
||||
import { initCss } from '../../utils'
|
||||
import FlowStatusViewer from '$lib/components/FlowStatusViewer.svelte'
|
||||
import { components } from '../../editor/component'
|
||||
import ResolveConfig from '../helpers/ResolveConfig.svelte'
|
||||
import ResolveStyle from '../helpers/ResolveStyle.svelte'
|
||||
|
||||
export let id: string
|
||||
export let componentInput: AppInput | undefined
|
||||
@@ -31,7 +32,7 @@
|
||||
|
||||
initializing = false
|
||||
|
||||
$: css = concatCustomCss($app.css?.jobidflowstatuscomponent, customCss)
|
||||
let css = initCss($app.css?.jobidflowstatuscomponent, customCss)
|
||||
|
||||
$: jobId = resolvedConfig.jobId
|
||||
</script>
|
||||
@@ -45,6 +46,16 @@
|
||||
/>
|
||||
{/each}
|
||||
|
||||
{#each Object.keys(css ?? {}) as key (key)}
|
||||
<ResolveStyle
|
||||
{id}
|
||||
{customCss}
|
||||
{key}
|
||||
bind:css={css[key]}
|
||||
componentStyle={$app.css?.jobidflowstatuscomponent}
|
||||
/>
|
||||
{/each}
|
||||
|
||||
<RunnableWrapper {outputs} {render} {componentInput} {id}>
|
||||
<div class="flex flex-col w-full h-full">
|
||||
<div
|
||||
|
||||
@@ -5,17 +5,18 @@
|
||||
import type { AppInput } from '../../inputType'
|
||||
import type { AppViewerContext, ComponentCustomCSS, RichConfigurations } from '../../types'
|
||||
import RunnableWrapper from '../helpers/RunnableWrapper.svelte'
|
||||
import { concatCustomCss } from '../../utils'
|
||||
import { initCss } from '../../utils'
|
||||
import LogViewer from '$lib/components/LogViewer.svelte'
|
||||
import TestJobLoader from '$lib/components/TestJobLoader.svelte'
|
||||
import type { Job } from '$lib/gen'
|
||||
import { components } from '../../editor/component'
|
||||
import ResolveConfig from '../helpers/ResolveConfig.svelte'
|
||||
import ResolveStyle from '../helpers/ResolveStyle.svelte'
|
||||
|
||||
export let id: string
|
||||
export let componentInput: AppInput | undefined
|
||||
export let initializing: boolean | undefined = false
|
||||
export let customCss: ComponentCustomCSS<'logcomponent'> | undefined = undefined
|
||||
export let customCss: ComponentCustomCSS<'jobidlogcomponent'> | undefined = undefined
|
||||
export let render: boolean
|
||||
export let configuration: RichConfigurations
|
||||
|
||||
@@ -34,7 +35,7 @@
|
||||
|
||||
initializing = false
|
||||
|
||||
$: css = concatCustomCss($app.css?.logcomponent, customCss)
|
||||
let css = initCss($app.css?.jobidlogcomponent, customCss)
|
||||
|
||||
let testJobLoader: TestJobLoader | undefined = undefined
|
||||
let testIsLoading: boolean = false
|
||||
@@ -54,6 +55,16 @@
|
||||
/>
|
||||
{/each}
|
||||
|
||||
{#each Object.keys(css ?? {}) as key (key)}
|
||||
<ResolveStyle
|
||||
{id}
|
||||
{customCss}
|
||||
{key}
|
||||
bind:css={css[key]}
|
||||
componentStyle={$app.css?.jobidlogcomponent}
|
||||
/>
|
||||
{/each}
|
||||
|
||||
<TestJobLoader bind:this={testJobLoader} bind:isLoading={testIsLoading} bind:job={testJob} />
|
||||
|
||||
<RunnableWrapper {outputs} {render} {componentInput} {id}>
|
||||
@@ -68,12 +79,8 @@
|
||||
Logs
|
||||
</div>
|
||||
<div
|
||||
style={twMerge($app.css?.['logcomponent']?.['container']?.style, customCss?.container?.style)}
|
||||
class={twMerge(
|
||||
'p-2 grow overflow-auto',
|
||||
$app.css?.['logcomponent']?.['container']?.class,
|
||||
customCss?.container?.class
|
||||
)}
|
||||
style={css?.container?.style}
|
||||
class={twMerge('p-2 grow overflow-auto', css?.container?.class, 'wm-log-container')}
|
||||
>
|
||||
<LogViewer
|
||||
jobId={testJob?.id}
|
||||
|
||||
@@ -5,10 +5,11 @@
|
||||
import type { AppInput } from '../../inputType'
|
||||
import type { AppViewerContext, ComponentCustomCSS } from '../../types'
|
||||
import RunnableWrapper from '../helpers/RunnableWrapper.svelte'
|
||||
import { concatCustomCss } from '../../utils'
|
||||
import { initCss } from '../../utils'
|
||||
import LogViewer from '$lib/components/LogViewer.svelte'
|
||||
import TestJobLoader from '$lib/components/TestJobLoader.svelte'
|
||||
import type { Job } from '$lib/gen'
|
||||
import ResolveStyle from '../helpers/ResolveStyle.svelte'
|
||||
|
||||
export let id: string
|
||||
export let componentInput: AppInput | undefined
|
||||
@@ -25,8 +26,7 @@
|
||||
|
||||
initializing = false
|
||||
|
||||
$: css = concatCustomCss($app.css?.logcomponent, customCss)
|
||||
|
||||
let css = initCss($app.css?.logcomponent, customCss)
|
||||
let testJobLoader: TestJobLoader | undefined = undefined
|
||||
let testIsLoading: boolean = false
|
||||
let testJob: Job | undefined = undefined
|
||||
@@ -34,6 +34,16 @@
|
||||
|
||||
<TestJobLoader bind:this={testJobLoader} bind:isLoading={testIsLoading} bind:job={testJob} />
|
||||
|
||||
{#each Object.keys(css ?? {}) as key (key)}
|
||||
<ResolveStyle
|
||||
{id}
|
||||
{customCss}
|
||||
{key}
|
||||
bind:css={css[key]}
|
||||
componentStyle={$app.css?.logcomponent}
|
||||
/>
|
||||
{/each}
|
||||
|
||||
<RunnableWrapper
|
||||
on:started={(e) => {
|
||||
testJobLoader?.watchJob(e.detail)
|
||||
@@ -47,19 +57,16 @@
|
||||
<div
|
||||
class={twMerge(
|
||||
'w-full border-b px-2 text-xs p-1 font-semibold bg-gray-500 text-white rounded-t-sm',
|
||||
css?.header?.class
|
||||
css?.header?.class,
|
||||
'wm-log-header'
|
||||
)}
|
||||
style={css?.header?.style}
|
||||
>
|
||||
Logs
|
||||
</div>
|
||||
<div
|
||||
style={twMerge($app.css?.['logcomponent']?.['container']?.style, customCss?.container?.style)}
|
||||
class={twMerge(
|
||||
'p-2 grow overflow-auto',
|
||||
$app.css?.['logcomponent']?.['container']?.class,
|
||||
customCss?.container?.class
|
||||
)}
|
||||
style={css?.container?.style}
|
||||
class={twMerge('p-2 grow overflow-auto', css?.container?.class, 'wm-log-container')}
|
||||
>
|
||||
<LogViewer
|
||||
jobId={testJob?.id}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { getContext } from 'svelte'
|
||||
import { concatCustomCss } from '../../utils'
|
||||
import { initCss } from '../../utils'
|
||||
import type { AppViewerContext, ComponentCustomCSS, RichConfigurations } from '../../types'
|
||||
import { InputValue } from '../helpers'
|
||||
import { twMerge } from 'tailwind-merge'
|
||||
@@ -12,6 +12,7 @@
|
||||
import { defaults as defaultControls } from 'ol/control'
|
||||
import { findGridItem, initOutput } from '../../editor/appUtils'
|
||||
import InitializeComponent from '../helpers/InitializeComponent.svelte'
|
||||
import ResolveStyle from '../helpers/ResolveStyle.svelte'
|
||||
|
||||
interface Marker {
|
||||
lon: number
|
||||
@@ -152,7 +153,7 @@
|
||||
updateRegionOutput()
|
||||
}
|
||||
|
||||
$: css = concatCustomCss($app.css?.mapcomponent, customCss)
|
||||
let css = initCss($app.css?.mapcomponent, customCss)
|
||||
|
||||
function updateRegionOutput() {
|
||||
if (map) {
|
||||
@@ -203,12 +204,22 @@
|
||||
|
||||
<InitializeComponent {id} />
|
||||
|
||||
{#each Object.keys(css ?? {}) as key (key)}
|
||||
<ResolveStyle
|
||||
{id}
|
||||
{customCss}
|
||||
{key}
|
||||
bind:css={css[key]}
|
||||
componentStyle={$app.css?.mapcomponent}
|
||||
/>
|
||||
{/each}
|
||||
|
||||
{#if render}
|
||||
<div class="relative h-full w-full">
|
||||
<div
|
||||
on:pointerdown|stopPropagation={selectComponent}
|
||||
bind:this={mapElement}
|
||||
class={twMerge(`w-full h-full`, css?.map?.class ?? '')}
|
||||
class={twMerge(`w-full h-full`, css?.map?.class, 'wm-map')}
|
||||
style={css?.map?.style ?? ''}
|
||||
/>
|
||||
|
||||
|
||||
@@ -3,12 +3,13 @@
|
||||
import { initConfig, initOutput } from '../../editor/appUtils'
|
||||
import type { AppInput } from '../../inputType'
|
||||
import type { AppViewerContext, ComponentCustomCSS, RichConfigurations } from '../../types'
|
||||
import { concatCustomCss } from '../../utils'
|
||||
import { initCss } from '../../utils'
|
||||
import RunnableWrapper from '../helpers/RunnableWrapper.svelte'
|
||||
import Markdown from 'svelte-exmarkdown'
|
||||
import { classNames } from '$lib/utils'
|
||||
import { components } from '../../editor/component'
|
||||
import ResolveConfig from '../helpers/ResolveConfig.svelte'
|
||||
import ResolveStyle from '../helpers/ResolveStyle.svelte'
|
||||
|
||||
export let id: string
|
||||
export let componentInput: AppInput | undefined
|
||||
@@ -31,7 +32,7 @@
|
||||
|
||||
let result: string | undefined = undefined
|
||||
|
||||
$: css = concatCustomCss($app.css?.mardowncomponent, customCss)
|
||||
let css = initCss($app.css?.mardowncomponent, customCss)
|
||||
|
||||
const proseMapping = {
|
||||
sm: 'prose-sm',
|
||||
@@ -51,6 +52,16 @@
|
||||
/>
|
||||
{/each}
|
||||
|
||||
{#each Object.keys(css ?? {}) as key (key)}
|
||||
<ResolveStyle
|
||||
{id}
|
||||
{customCss}
|
||||
{key}
|
||||
bind:css={css[key]}
|
||||
componentStyle={$app.css?.mardowncomponent}
|
||||
/>
|
||||
{/each}
|
||||
|
||||
<div
|
||||
on:pointerdown={(e) => {
|
||||
e?.preventDefault()
|
||||
@@ -58,8 +69,10 @@
|
||||
class={classNames(
|
||||
'h-full w-full overflow-y-auto prose',
|
||||
resolvedConfig?.size ? proseMapping[resolvedConfig.size] : '',
|
||||
css?.container?.class
|
||||
css?.container?.class,
|
||||
'wm-markdown'
|
||||
)}
|
||||
style={css?.container?.style}
|
||||
>
|
||||
<RunnableWrapper
|
||||
{outputs}
|
||||
|
||||
@@ -9,9 +9,10 @@
|
||||
import { Button } from '../../../common'
|
||||
import { findGridItem, initOutput } from '../../editor/appUtils'
|
||||
import type { AppViewerContext, ComponentCustomCSS, RichConfigurations } from '../../types'
|
||||
import { concatCustomCss } from '../../utils'
|
||||
import { initCss } from '../../utils'
|
||||
import InputValue from '../helpers/InputValue.svelte'
|
||||
import InitializeComponent from '../helpers/InitializeComponent.svelte'
|
||||
import ResolveStyle from '../helpers/ResolveStyle.svelte'
|
||||
|
||||
export let id: string
|
||||
export let configuration: RichConfigurations
|
||||
@@ -187,12 +188,22 @@
|
||||
return value
|
||||
}
|
||||
|
||||
$: css = concatCustomCss($app.css?.pdfcomponent, customCss)
|
||||
let css = initCss($app.css?.pdfcomponent, customCss)
|
||||
</script>
|
||||
|
||||
<InputValue key="source" {id} input={configuration.source} bind:value={source} />
|
||||
<InputValue key="zoom" {id} input={configuration.zoom} bind:value={zoom} />
|
||||
|
||||
{#each Object.keys(css ?? {}) as key (key)}
|
||||
<ResolveStyle
|
||||
{id}
|
||||
{customCss}
|
||||
{key}
|
||||
bind:css={css[key]}
|
||||
componentStyle={$app.css?.pdfcomponent}
|
||||
/>
|
||||
{/each}
|
||||
|
||||
<InitializeComponent {id} />
|
||||
|
||||
{#if render}
|
||||
@@ -306,7 +317,12 @@
|
||||
<div
|
||||
bind:this={wrapper}
|
||||
on:scroll={throttledScroll}
|
||||
class={twMerge('w-full h-full overflow-auto', css?.container?.class ?? '', 'bg-gray-100')}
|
||||
class={twMerge(
|
||||
'w-full h-full overflow-auto',
|
||||
css?.container?.class ?? '',
|
||||
'bg-gray-100',
|
||||
'wm-pdf'
|
||||
)}
|
||||
style={css?.container?.style ?? ''}
|
||||
/>
|
||||
{/if}
|
||||
|
||||
@@ -15,9 +15,11 @@
|
||||
import type { AppInput } from '../../inputType'
|
||||
import InputValue from '../helpers/InputValue.svelte'
|
||||
import type { AppViewerContext, ComponentCustomCSS, RichConfigurations } from '../../types'
|
||||
import { concatCustomCss } from '../../utils'
|
||||
import { initCss } from '../../utils'
|
||||
import { getContext } from 'svelte'
|
||||
import { initOutput } from '../../editor/appUtils'
|
||||
import { twMerge } from 'tailwind-merge'
|
||||
import ResolveStyle from '../helpers/ResolveStyle.svelte'
|
||||
|
||||
export let id: string
|
||||
export let componentInput: AppInput | undefined
|
||||
@@ -72,14 +74,27 @@
|
||||
]
|
||||
}
|
||||
|
||||
$: css = concatCustomCss($app.css?.piechartcomponent, customCss)
|
||||
let css = initCss($app.css?.piechartcomponent, customCss)
|
||||
</script>
|
||||
|
||||
{#each Object.keys(css ?? {}) as key (key)}
|
||||
<ResolveStyle
|
||||
{id}
|
||||
{customCss}
|
||||
{key}
|
||||
bind:css={css[key]}
|
||||
componentStyle={$app.css?.piechartcomponent}
|
||||
/>
|
||||
{/each}
|
||||
|
||||
<InputValue key="theme" {id} input={configuration.theme} bind:value={theme} />
|
||||
<InputValue key="doughnut" {id} input={configuration.doughnutStyle} bind:value={doughnut} />
|
||||
|
||||
<RunnableWrapper {outputs} {render} autoRefresh {componentInput} {id} bind:initializing bind:result>
|
||||
<div class="w-full h-full {css?.container?.class ?? ''}" style={css?.container?.style ?? ''}>
|
||||
<div
|
||||
class={twMerge('w-full h-full', css?.container?.class, 'wm-pie-chart')}
|
||||
style={css?.container?.style ?? ''}
|
||||
>
|
||||
{#if result}
|
||||
{#if doughnut}
|
||||
<Doughnut {data} {options} />
|
||||
|
||||
@@ -17,10 +17,12 @@
|
||||
import Scatter from 'svelte-chartjs/Scatter.svelte'
|
||||
import InputValue from '../helpers/InputValue.svelte'
|
||||
import type { ChartOptions, ChartData } from 'chart.js'
|
||||
import { concatCustomCss } from '../../utils'
|
||||
import { initCss } from '../../utils'
|
||||
import { getContext } from 'svelte'
|
||||
import type { AppViewerContext, ComponentCustomCSS, RichConfigurations } from '../../types'
|
||||
import { initOutput } from '../../editor/appUtils'
|
||||
import { twMerge } from 'tailwind-merge'
|
||||
import ResolveStyle from '../helpers/ResolveStyle.svelte'
|
||||
|
||||
export let id: string
|
||||
export let componentInput: AppInput | undefined
|
||||
@@ -78,14 +80,27 @@
|
||||
datasets: result ?? []
|
||||
} as ChartData<'scatter', (number | Point)[], unknown>
|
||||
|
||||
$: css = concatCustomCss($app.css?.scatterchartcomponent, customCss)
|
||||
let css = initCss($app.css?.scatterchartcomponent, customCss)
|
||||
</script>
|
||||
|
||||
{#each Object.keys(css ?? {}) as key (key)}
|
||||
<ResolveStyle
|
||||
{id}
|
||||
{customCss}
|
||||
{key}
|
||||
bind:css={css[key]}
|
||||
componentStyle={$app.css?.scatterchartcomponent}
|
||||
/>
|
||||
{/each}
|
||||
|
||||
<InputValue key="zoomable" {id} input={configuration.zoomable} bind:value={zoomable} />
|
||||
<InputValue key="pannable" {id} input={configuration.pannable} bind:value={pannable} />
|
||||
|
||||
<RunnableWrapper {outputs} {render} autoRefresh {componentInput} {id} bind:initializing bind:result>
|
||||
<div class="w-full h-full {css?.container?.class ?? ''}" style={css?.container?.style ?? ''}>
|
||||
<div
|
||||
class={twMerge('w-full h-full', css?.container?.class, 'wm-scatter-chart')}
|
||||
style={css?.container?.style ?? ''}
|
||||
>
|
||||
{#if result}
|
||||
<Scatter {data} {options} />
|
||||
{/if}
|
||||
|
||||
@@ -12,6 +12,8 @@
|
||||
import AlignWrapper from '../helpers/AlignWrapper.svelte'
|
||||
import ResolveConfig from '../helpers/ResolveConfig.svelte'
|
||||
import RunnableWrapper from '../helpers/RunnableWrapper.svelte'
|
||||
import { initCss } from '../../utils'
|
||||
import ResolveStyle from '../helpers/ResolveStyle.svelte'
|
||||
|
||||
export let id: string
|
||||
export let componentInput: AppInput | undefined
|
||||
@@ -31,6 +33,8 @@
|
||||
const { app, worldStore, mode, componentControl } =
|
||||
getContext<AppViewerContext>('AppViewerContext')
|
||||
|
||||
let css = initCss($app.css?.textcomponent, customCss)
|
||||
|
||||
let result: string | undefined = undefined
|
||||
|
||||
if (
|
||||
@@ -155,9 +159,24 @@
|
||||
/>
|
||||
{/each}
|
||||
|
||||
{#each Object.keys(css ?? {}) as key (key)}
|
||||
<ResolveStyle
|
||||
{id}
|
||||
{customCss}
|
||||
{key}
|
||||
bind:css={css[key]}
|
||||
componentStyle={$app.css?.textcomponent}
|
||||
/>
|
||||
{/each}
|
||||
|
||||
<RunnableWrapper {outputs} {render} {componentInput} {id} bind:initializing bind:result>
|
||||
<div
|
||||
class="h-full w-full overflow-hidden"
|
||||
class={twMerge(
|
||||
'h-full w-full overflow-hidden',
|
||||
customCss?.container?.class,
|
||||
'wm-text-container'
|
||||
)}
|
||||
style={customCss?.container?.style}
|
||||
on:dblclick={() => {
|
||||
if (!editorMode) {
|
||||
editorMode = true
|
||||
@@ -172,15 +191,16 @@
|
||||
<textarea
|
||||
class={twMerge(
|
||||
'whitespace-pre-wrap !outline-none !border-0 !bg-transparent !resize-none !overflow-hidden !ring-0 !p-0 text-center',
|
||||
$app.css?.['textcomponent']?.['text']?.class,
|
||||
css?.text?.class,
|
||||
customCss?.text?.class,
|
||||
'wm-text',
|
||||
classes,
|
||||
getClasses(),
|
||||
getClassesByType(),
|
||||
getHorizontalAlignement()
|
||||
)}
|
||||
on:pointerdown|stopPropagation
|
||||
style={[$app.css?.['textcomponent']?.['text']?.style, customCss?.text?.style].join(';')}
|
||||
style={customCss?.text?.style}
|
||||
id={`text-${id}`}
|
||||
on:pointerenter={() => {
|
||||
const elem = document.getElementById(`text-${id}`)
|
||||
|
||||
@@ -22,8 +22,10 @@
|
||||
import type { ChartOptions, ChartData } from 'chart.js'
|
||||
import type { AppViewerContext, ComponentCustomCSS, RichConfigurations } from '../../types'
|
||||
import { getContext } from 'svelte'
|
||||
import { concatCustomCss } from '../../utils'
|
||||
import { initCss } from '../../utils'
|
||||
import { initOutput } from '../../editor/appUtils'
|
||||
import { twMerge } from 'tailwind-merge'
|
||||
import ResolveStyle from '../helpers/ResolveStyle.svelte'
|
||||
|
||||
export let id: string
|
||||
export let componentInput: AppInput | undefined
|
||||
@@ -92,9 +94,19 @@
|
||||
datasets: result ?? []
|
||||
} as ChartData<'scatter', (number | Point)[], unknown>
|
||||
|
||||
$: css = concatCustomCss($app.css?.timeseriescomponent, customCss)
|
||||
let css = initCss($app.css?.timeseriescomponent, customCss)
|
||||
</script>
|
||||
|
||||
{#each Object.keys(css ?? {}) as key (key)}
|
||||
<ResolveStyle
|
||||
{id}
|
||||
{customCss}
|
||||
{key}
|
||||
bind:css={css[key]}
|
||||
componentStyle={$app.css?.timeseriescomponent}
|
||||
/>
|
||||
{/each}
|
||||
|
||||
<InputValue
|
||||
key="logarithmicScale"
|
||||
{id}
|
||||
@@ -105,7 +117,10 @@
|
||||
<InputValue key="pannable" {id} input={configuration.pannable} bind:value={pannable} />
|
||||
|
||||
<RunnableWrapper {outputs} {render} autoRefresh {componentInput} {id} bind:initializing bind:result>
|
||||
<div class="w-full h-full {css?.container?.class ?? ''}" style={css?.container?.style ?? ''}>
|
||||
<div
|
||||
class={twMerge('w-full h-full', css?.container?.class, 'wm-timeseries')}
|
||||
style={css?.container?.style ?? ''}
|
||||
>
|
||||
{#if result}
|
||||
<Scatter {data} {options} />
|
||||
{/if}
|
||||
|
||||
@@ -26,13 +26,14 @@
|
||||
import { tableOptions } from './tableOptions'
|
||||
import Alert from '$lib/components/common/alert/Alert.svelte'
|
||||
import { components, type ButtonComponent } from '../../../editor/component'
|
||||
import { concatCustomCss } from '../../../utils'
|
||||
import { initCss } from '../../../utils'
|
||||
import { twMerge } from 'tailwind-merge'
|
||||
import { initConfig, initOutput } from '$lib/components/apps/editor/appUtils'
|
||||
import ResolveConfig from '../../helpers/ResolveConfig.svelte'
|
||||
import AppCheckbox from '../../inputs/AppCheckbox.svelte'
|
||||
import AppSelect from '../../inputs/AppSelect.svelte'
|
||||
import RowWrapper from '../../layout/RowWrapper.svelte'
|
||||
import ResolveStyle from '../../helpers/ResolveStyle.svelte'
|
||||
|
||||
export let id: string
|
||||
export let componentInput: AppInput | undefined
|
||||
@@ -206,7 +207,7 @@
|
||||
|
||||
$: filteredResult != undefined && rerender()
|
||||
|
||||
$: css = concatCustomCss($app.css?.tablecomponent, customCss)
|
||||
let css = initCss($app.css?.tablecomponent, customCss)
|
||||
|
||||
$componentControl[id] = {
|
||||
right: (skipActions: boolean | undefined) => {
|
||||
@@ -259,6 +260,16 @@
|
||||
/>
|
||||
{/each}
|
||||
|
||||
{#each Object.keys(css ?? {}) as key (key)}
|
||||
<ResolveStyle
|
||||
{id}
|
||||
{customCss}
|
||||
{key}
|
||||
bind:css={css[key]}
|
||||
componentStyle={$app.css?.tablecomponent}
|
||||
/>
|
||||
{/each}
|
||||
|
||||
<RunnableWrapper
|
||||
{outputs}
|
||||
{render}
|
||||
@@ -273,6 +284,7 @@
|
||||
class={twMerge(
|
||||
'border shadow-sm divide-y h-full',
|
||||
css?.container?.class ?? '',
|
||||
'wm-table-container',
|
||||
'flex flex-col'
|
||||
)}
|
||||
style={css?.container?.style ?? ''}
|
||||
@@ -293,6 +305,7 @@
|
||||
class={twMerge(
|
||||
'bg-surface-secondary text-left',
|
||||
css?.tableHeader?.class ?? '',
|
||||
'wm-table-header',
|
||||
'sticky top-0 z-40'
|
||||
)}
|
||||
style={css?.tableHeader?.style ?? ''}
|
||||
@@ -323,21 +336,17 @@
|
||||
{/each}
|
||||
</thead>
|
||||
<tbody
|
||||
class={twMerge('divide-y bg-surface', css?.tableBody?.class ?? '')}
|
||||
class={twMerge('divide-y bg-surface', css?.tableBody?.class ?? '', 'wm-table-body')}
|
||||
style={css?.tableBody?.style ?? ''}
|
||||
>
|
||||
{#each $table.getRowModel().rows as row (row.id)}
|
||||
{@const rowIndex = row.original['__index']}
|
||||
<tr
|
||||
class={classNames(
|
||||
'last-of-type:!border-b-0',
|
||||
'last-of-type:!border-b-0 divide-x w-full',
|
||||
selectedRowIndex === rowIndex
|
||||
? 'bg-blue-100 hover:bg-blue-200 dark:bg-surface-selected dark:hover:bg-surface-hover'
|
||||
: 'hover:bg-blue-50 dark:hover:bg-surface-hover',
|
||||
'divide-x w-full',
|
||||
selectedRowIndex === rowIndex
|
||||
? 'divide-blue-200 hover:divide-blue-300 dark:divide-gray-600 dark:hover:divide-gray-700'
|
||||
: ''
|
||||
? 'bg-blue-100 hover:bg-blue-200 dark:bg-surface-selected dark:hover:bg-surface-hover divide-blue-200 hover:divide-blue-300 dark:divide-gray-600 dark:hover:divide-gray-700 wm-table-row-selected'
|
||||
: 'hover:bg-blue-50 dark:hover:bg-surface-hover wm-table-row'
|
||||
)}
|
||||
>
|
||||
{#each safeVisibleCell(row) as cell, index (index)}
|
||||
@@ -544,7 +553,7 @@
|
||||
manualPagination={resolvedConfig?.pagination?.selected == 'manual'}
|
||||
result={filteredResult}
|
||||
{table}
|
||||
class={css?.tableFooter?.class}
|
||||
class={twMerge(css?.tableFooter?.class, 'wm-table-footer')}
|
||||
style={css?.tableFooter?.style}
|
||||
{loading}
|
||||
/>
|
||||
@@ -553,9 +562,9 @@
|
||||
<div class="flex flex-col h-full w-full overflow-auto">
|
||||
<Alert title="Parsing issues" type="error" size="xs" class="h-full w-full ">
|
||||
The result should be an array of objects. Received:
|
||||
<pre class="w-full bg-surface p-2 rounded-md whitespace-pre-wrap"
|
||||
>{JSON.stringify(result, null, 4)}</pre
|
||||
>
|
||||
<pre class="w-full bg-surface p-2 rounded-md whitespace-pre-wrap">
|
||||
{JSON.stringify(result, null, 4)}
|
||||
</pre>
|
||||
</Alert>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
<script lang="ts">
|
||||
import type { ComponentCssProperty } from '../../types'
|
||||
import InputValue from './InputValue.svelte'
|
||||
|
||||
export let css: ComponentCssProperty
|
||||
export let id: string
|
||||
export let key: string
|
||||
|
||||
export let customCss: Record<string, ComponentCssProperty> | undefined = undefined
|
||||
export let componentStyle: Record<string, ComponentCssProperty> | undefined = undefined
|
||||
|
||||
let evalClassValue: string | undefined = undefined
|
||||
|
||||
function updateCss(
|
||||
componentStyle: Record<string, ComponentCssProperty> | undefined,
|
||||
customCss: Record<string, ComponentCssProperty> | undefined,
|
||||
evalClassValue: string | undefined
|
||||
) {
|
||||
const { class: componentClass, style: componentStyleValue } = componentStyle?.[key] ?? {}
|
||||
const { class: customClass, style: customStyleValue } = customCss?.[key] ?? {}
|
||||
|
||||
css.class = [componentClass, customClass, evalClassValue].filter(Boolean).join(' ')
|
||||
css.style = [componentStyleValue, customStyleValue].filter(Boolean).join(';')
|
||||
}
|
||||
|
||||
// When any of the values change, update the css
|
||||
$: updateCss(componentStyle, customCss, evalClassValue)
|
||||
|
||||
// We need to clear the evalClassValue if the user has disabled the evalClass
|
||||
$: if (customCss?.[key].evalClass === undefined && evalClassValue !== undefined) {
|
||||
evalClassValue = undefined
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if customCss}
|
||||
{@const property = customCss[key]}
|
||||
{#if property.evalClass}
|
||||
<InputValue {id} bind:value={evalClassValue} input={property.evalClass} />
|
||||
{/if}
|
||||
{/if}
|
||||
@@ -9,11 +9,13 @@
|
||||
ListInputs,
|
||||
RichConfigurations
|
||||
} from '../../types'
|
||||
import { concatCustomCss } from '../../utils'
|
||||
import { initCss } from '../../utils'
|
||||
import AlignWrapper from '../helpers/AlignWrapper.svelte'
|
||||
import InitializeComponent from '../helpers/InitializeComponent.svelte'
|
||||
import { components } from '../../editor/component'
|
||||
import ResolveConfig from '../helpers/ResolveConfig.svelte'
|
||||
import { twMerge } from 'tailwind-merge'
|
||||
import ResolveStyle from '../helpers/ResolveStyle.svelte'
|
||||
|
||||
export let id: string
|
||||
export let configuration: RichConfigurations
|
||||
@@ -81,7 +83,7 @@
|
||||
|
||||
$: resolvedConfig.defaultValue != undefined && handleDefault()
|
||||
|
||||
$: css = concatCustomCss($app.css?.checkboxcomponent, customCss)
|
||||
let css = initCss($app.css?.checkboxcomponent, customCss)
|
||||
</script>
|
||||
|
||||
{#each Object.keys(components['checkboxcomponent'].initialData.configuration) as key (key)}
|
||||
@@ -94,13 +96,29 @@
|
||||
/>
|
||||
{/each}
|
||||
|
||||
{#each Object.keys(css ?? {}) as key (key)}
|
||||
<ResolveStyle
|
||||
{id}
|
||||
{customCss}
|
||||
{key}
|
||||
bind:css={css[key]}
|
||||
componentStyle={$app.css?.checkboxcomponent}
|
||||
/>
|
||||
{/each}
|
||||
|
||||
<InitializeComponent {id} />
|
||||
<AlignWrapper {render} {horizontalAlignment} {verticalAlignment}>
|
||||
<AlignWrapper
|
||||
{render}
|
||||
{horizontalAlignment}
|
||||
{verticalAlignment}
|
||||
class={twMerge(css?.container?.class, 'wm-toggle-container')}
|
||||
style={css?.container?.style}
|
||||
>
|
||||
<Toggle
|
||||
size="sm"
|
||||
bind:checked={value}
|
||||
options={{ right: resolvedConfig.label }}
|
||||
textClass={css?.text?.class ?? ''}
|
||||
textClass={twMerge(css?.text?.class, 'wm-toggle-text')}
|
||||
textStyle={css?.text?.style ?? ''}
|
||||
on:change={(e) => {
|
||||
preclickAction?.()
|
||||
|
||||
@@ -3,12 +3,13 @@
|
||||
import { twMerge } from 'tailwind-merge'
|
||||
import { initConfig, initOutput } from '../../editor/appUtils'
|
||||
import type { AppViewerContext, ComponentCustomCSS, RichConfigurations } from '../../types'
|
||||
import { concatCustomCss } from '../../utils'
|
||||
import { initCss } from '../../utils'
|
||||
import AlignWrapper from '../helpers/AlignWrapper.svelte'
|
||||
import InitializeComponent from '../helpers/InitializeComponent.svelte'
|
||||
import { parseISO, format as formatDateFns } from 'date-fns'
|
||||
import { components } from '../../editor/component'
|
||||
import ResolveConfig from '../helpers/ResolveConfig.svelte'
|
||||
import ResolveStyle from '../helpers/ResolveStyle.svelte'
|
||||
|
||||
export let id: string
|
||||
export let configuration: RichConfigurations
|
||||
@@ -57,7 +58,7 @@
|
||||
function handleDefault(defaultValue: string | undefined) {
|
||||
value = defaultValue
|
||||
}
|
||||
$: css = concatCustomCss($app.css?.dateinputcomponent, customCss)
|
||||
let css = initCss($app.css?.dateinputcomponent, customCss)
|
||||
</script>
|
||||
|
||||
{#each Object.keys(components['dateinputcomponent'].initialData.configuration) as key (key)}
|
||||
@@ -69,6 +70,16 @@
|
||||
/>
|
||||
{/each}
|
||||
|
||||
{#each Object.keys(css ?? {}) as key (key)}
|
||||
<ResolveStyle
|
||||
{id}
|
||||
{customCss}
|
||||
{key}
|
||||
bind:css={css[key]}
|
||||
componentStyle={$app.css?.dateinputcomponent}
|
||||
/>
|
||||
{/each}
|
||||
|
||||
<InitializeComponent {id} />
|
||||
|
||||
<AlignWrapper {render} {verticalAlignment}>
|
||||
@@ -81,7 +92,7 @@
|
||||
min={resolvedConfig.minDate}
|
||||
max={resolvedConfig.maxDate}
|
||||
placeholder="Type..."
|
||||
class={twMerge(css?.input?.class ?? '')}
|
||||
class={twMerge(css?.input?.class, 'wm-date-input')}
|
||||
style={css?.input?.style ?? ''}
|
||||
/>
|
||||
{/if}
|
||||
|
||||
@@ -4,9 +4,10 @@
|
||||
import { FileInput } from '../../../common'
|
||||
import { initOutput } from '../../editor/appUtils'
|
||||
import type { AppViewerContext, ComponentCustomCSS, RichConfigurations } from '../../types'
|
||||
import { concatCustomCss } from '../../utils'
|
||||
import { initCss } from '../../utils'
|
||||
import InputValue from '../helpers/InputValue.svelte'
|
||||
import InitializeComponent from '../helpers/InitializeComponent.svelte'
|
||||
import ResolveStyle from '../helpers/ResolveStyle.svelte'
|
||||
|
||||
export let id: string
|
||||
export let configuration: RichConfigurations
|
||||
@@ -36,9 +37,19 @@
|
||||
outputs?.result.set(files)
|
||||
}
|
||||
|
||||
$: css = concatCustomCss($app.css?.fileinputcomponent, customCss)
|
||||
let css = initCss($app.css?.fileinputcomponent, customCss)
|
||||
</script>
|
||||
|
||||
{#each Object.keys(css ?? {}) as key (key)}
|
||||
<ResolveStyle
|
||||
{id}
|
||||
{customCss}
|
||||
{key}
|
||||
bind:css={css[key]}
|
||||
componentStyle={$app.css?.fileinputcomponent}
|
||||
/>
|
||||
{/each}
|
||||
|
||||
<InputValue
|
||||
key="accepted"
|
||||
{id}
|
||||
@@ -61,7 +72,7 @@
|
||||
on:change={({ detail }) => {
|
||||
handleChange(detail)
|
||||
}}
|
||||
class={twMerge('w-full h-full', css?.container?.class)}
|
||||
class={twMerge('w-full h-full', css?.container?.class, 'wm-file-input')}
|
||||
style={css?.container?.style}
|
||||
>
|
||||
{text}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
import { getContext } from 'svelte'
|
||||
import { initConfig, initOutput } from '../../editor/appUtils'
|
||||
import type { AppViewerContext, ComponentCustomCSS, RichConfigurations } from '../../types'
|
||||
import { concatCustomCss } from '../../utils'
|
||||
import { initCss } from '../../utils'
|
||||
import AlignWrapper from '../helpers/AlignWrapper.svelte'
|
||||
import InitializeComponent from '../helpers/InitializeComponent.svelte'
|
||||
import { components } from '../../editor/component'
|
||||
@@ -14,6 +14,7 @@
|
||||
import { extractCustomProperties } from '$lib/utils'
|
||||
import { tick } from 'svelte'
|
||||
import { offset, flip, shift } from 'svelte-floating-ui/dom'
|
||||
import ResolveStyle from '../helpers/ResolveStyle.svelte'
|
||||
|
||||
export let id: string
|
||||
export let configuration: RichConfigurations
|
||||
@@ -69,7 +70,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
$: css = concatCustomCss($app.css?.multiselectcomponent, customCss)
|
||||
let css = initCss($app.css?.multiselectcomponent, customCss)
|
||||
|
||||
function setOuterDivStyle(outerDiv: HTMLDivElement, portalRef: HTMLDivElement, style: string) {
|
||||
outerDiv.setAttribute('style', style)
|
||||
@@ -116,6 +117,16 @@
|
||||
/>
|
||||
{/each}
|
||||
|
||||
{#each Object.keys(css ?? {}) as key (key)}
|
||||
<ResolveStyle
|
||||
{id}
|
||||
{customCss}
|
||||
{key}
|
||||
bind:css={css[key]}
|
||||
componentStyle={$app.css?.multiselectcomponent}
|
||||
/>
|
||||
{/each}
|
||||
|
||||
<InitializeComponent {id} />
|
||||
|
||||
<AlignWrapper {render} hFull>
|
||||
|
||||
@@ -9,11 +9,12 @@
|
||||
ListInputs,
|
||||
RichConfigurations
|
||||
} from '../../types'
|
||||
import { concatCustomCss } from '../../utils'
|
||||
import { initCss } from '../../utils'
|
||||
import AlignWrapper from '../helpers/AlignWrapper.svelte'
|
||||
import InitializeComponent from '../helpers/InitializeComponent.svelte'
|
||||
import ResolveConfig from '../helpers/ResolveConfig.svelte'
|
||||
import { components } from '../../editor/component'
|
||||
import ResolveStyle from '../helpers/ResolveStyle.svelte'
|
||||
|
||||
export let id: string
|
||||
export let configuration: RichConfigurations
|
||||
@@ -56,7 +57,7 @@
|
||||
value = defaultValue
|
||||
}
|
||||
|
||||
$: css = concatCustomCss($app.css?.numberinputcomponent, customCss)
|
||||
let css = initCss($app.css?.numberinputcomponent, customCss)
|
||||
</script>
|
||||
|
||||
{#each Object.keys(components['numberinputcomponent'].initialData.configuration) as key (key)}
|
||||
@@ -68,6 +69,16 @@
|
||||
/>
|
||||
{/each}
|
||||
|
||||
{#each Object.keys(css ?? {}) as key (key)}
|
||||
<ResolveStyle
|
||||
{id}
|
||||
{customCss}
|
||||
{key}
|
||||
bind:css={css[key]}
|
||||
componentStyle={$app.css?.numberinputcomponent}
|
||||
/>
|
||||
{/each}
|
||||
|
||||
<InitializeComponent {id} />
|
||||
|
||||
<AlignWrapper {render} {verticalAlignment}>
|
||||
@@ -76,7 +87,8 @@
|
||||
on:focus={() => ($selectedComponent = [id])}
|
||||
class={twMerge(
|
||||
'windmillapp w-full py-1.5 text-sm focus:ring-indigo-100 px-2',
|
||||
css?.input?.class ?? ''
|
||||
css?.input?.class ?? '',
|
||||
'wm-number-input'
|
||||
)}
|
||||
style={css?.input?.style ?? ''}
|
||||
bind:value
|
||||
|
||||
@@ -10,11 +10,12 @@
|
||||
ListInputs,
|
||||
RichConfigurations
|
||||
} from '../../types'
|
||||
import { concatCustomCss } from '../../utils'
|
||||
import { initCss } from '../../utils'
|
||||
import AlignWrapper from '../helpers/AlignWrapper.svelte'
|
||||
import InitializeComponent from '../helpers/InitializeComponent.svelte'
|
||||
import ResolveConfig from '../helpers/ResolveConfig.svelte'
|
||||
import { components } from '../../editor/component'
|
||||
import ResolveStyle from '../helpers/ResolveStyle.svelte'
|
||||
|
||||
export let id: string
|
||||
export let configuration: RichConfigurations
|
||||
@@ -61,7 +62,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
$: css = concatCustomCss($app.css?.rangecomponent, customCss)
|
||||
let css = initCss($app.css?.rangecomponent, customCss)
|
||||
|
||||
let lastStyle: string | undefined = undefined
|
||||
$: if (css && slider && lastStyle !== css?.handles?.style) {
|
||||
@@ -82,16 +83,29 @@
|
||||
/>
|
||||
{/each}
|
||||
|
||||
{#each Object.keys(css ?? {}) as key (key)}
|
||||
<ResolveStyle
|
||||
{id}
|
||||
{customCss}
|
||||
{key}
|
||||
bind:css={css[key]}
|
||||
componentStyle={$app.css?.rangecomponent}
|
||||
/>
|
||||
{/each}
|
||||
|
||||
<InitializeComponent {id} />
|
||||
|
||||
<AlignWrapper {render} {verticalAlignment}>
|
||||
<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 ?? ''}>
|
||||
<span
|
||||
class={twMerge(css?.limits?.class ?? '', 'wm-slider-limits')}
|
||||
style={css?.limits?.style ?? ''}
|
||||
>
|
||||
{+(resolvedConfig.min ?? 0)}
|
||||
</span>
|
||||
<div
|
||||
class="grow"
|
||||
class={twMerge('grow', 'wm-slider-bar')}
|
||||
style="--range-handle-focus: {'#7e9abd'}; --range-handle: {'#7e9abd'}; {css?.bar?.style ??
|
||||
''}"
|
||||
on:pointerdown|stopPropagation
|
||||
@@ -107,7 +121,10 @@
|
||||
/>
|
||||
<!-- <RangeSlider {step} range min={min ?? 0} max={max ?? 1} bind:values /> -->
|
||||
</div>
|
||||
<span class={css?.limits?.class ?? ''} style={css?.limits?.style ?? ''}>
|
||||
<span
|
||||
class={twMerge(css?.limits?.class ?? '', 'wm-slider-limits')}
|
||||
style={css?.limits?.style ?? ''}
|
||||
>
|
||||
{+(resolvedConfig.max ?? 1)}
|
||||
</span>
|
||||
</div>
|
||||
@@ -115,7 +132,8 @@
|
||||
<span
|
||||
class={twMerge(
|
||||
'text-center text-sm font-medium bg-blue-100 text-blue-800 rounded px-2.5 py-0.5',
|
||||
css?.values?.class ?? ''
|
||||
css?.values?.class ?? '',
|
||||
'wm-slider-value'
|
||||
)}
|
||||
style={css?.values?.style ?? ''}
|
||||
>
|
||||
@@ -124,7 +142,8 @@
|
||||
<span
|
||||
class={twMerge(
|
||||
'text-center text-sm font-medium bg-blue-100 text-blue-800 rounded px-2.5 py-0.5',
|
||||
css?.values?.class ?? ''
|
||||
css?.values?.class ?? '',
|
||||
'wm-slider-value'
|
||||
)}
|
||||
style={css?.values?.style ?? ''}
|
||||
>
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
ListInputs,
|
||||
RichConfigurations
|
||||
} from '../../types'
|
||||
import { concatCustomCss } from '../../utils'
|
||||
import { initCss } from '../../utils'
|
||||
import AlignWrapper from '../helpers/AlignWrapper.svelte'
|
||||
import { SELECT_INPUT_DEFAULT_STYLE } from '../../../../defaults'
|
||||
import { initConfig, initOutput } from '../../editor/appUtils'
|
||||
@@ -20,6 +20,7 @@
|
||||
import { classNames } from '$lib/utils'
|
||||
import { Bug } from 'lucide-svelte'
|
||||
import Popover from '$lib/components/Popover.svelte'
|
||||
import ResolveStyle from '../helpers/ResolveStyle.svelte'
|
||||
|
||||
export let id: string
|
||||
export let configuration: RichConfigurations
|
||||
@@ -138,7 +139,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
$: css = concatCustomCss($app.css?.selectcomponent, customCss)
|
||||
let css = initCss($app.css?.selectcomponent, customCss)
|
||||
|
||||
function handleFilter(e) {
|
||||
if (resolvedConfig.create) {
|
||||
@@ -172,6 +173,17 @@
|
||||
configuration={configuration[key]}
|
||||
/>
|
||||
{/each}
|
||||
|
||||
{#each Object.keys(css ?? {}) as key (key)}
|
||||
<ResolveStyle
|
||||
{id}
|
||||
{customCss}
|
||||
{key}
|
||||
bind:css={css[key]}
|
||||
componentStyle={$app.css?.selectcomponent}
|
||||
/>
|
||||
{/each}
|
||||
|
||||
<InitializeComponent {id} />
|
||||
|
||||
<AlignWrapper {render} {verticalAlignment}>
|
||||
|
||||
@@ -1,21 +1,25 @@
|
||||
<script lang="ts">
|
||||
import { getContext } from 'svelte'
|
||||
|
||||
import type { AppViewerContext, RichConfigurations } from '../../types'
|
||||
import type { AppViewerContext, ComponentCustomCSS, RichConfigurations } from '../../types'
|
||||
import AlignWrapper from '../helpers/AlignWrapper.svelte'
|
||||
import { initConfig, initOutput } from '../../editor/appUtils'
|
||||
import InitializeComponent from '../helpers/InitializeComponent.svelte'
|
||||
import { components } from '../../editor/component'
|
||||
import ResolveConfig from '../helpers/ResolveConfig.svelte'
|
||||
import Stepper from '$lib/components/common/stepper/Stepper.svelte'
|
||||
import { twMerge } from 'tailwind-merge'
|
||||
import { initCss } from '../../utils'
|
||||
import ResolveStyle from '../helpers/ResolveStyle.svelte'
|
||||
|
||||
export let id: string
|
||||
export let configuration: RichConfigurations
|
||||
export let horizontalAlignment: 'left' | 'center' | 'right' | undefined = undefined
|
||||
export let verticalAlignment: 'top' | 'center' | 'bottom' | undefined = undefined
|
||||
export let render: boolean
|
||||
export let customCss: ComponentCustomCSS<'selectstepcomponent'> | undefined = undefined
|
||||
|
||||
const { worldStore, componentControl } = getContext<AppViewerContext>('AppViewerContext')
|
||||
const { app, worldStore, componentControl } = getContext<AppViewerContext>('AppViewerContext')
|
||||
|
||||
const resolvedConfig = initConfig(
|
||||
components['selectstepcomponent'].initialData.configuration,
|
||||
@@ -68,6 +72,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
let css = initCss($app.css?.selectstepcomponent, customCss)
|
||||
$: selected && handleSelection(selected)
|
||||
</script>
|
||||
|
||||
@@ -80,9 +85,25 @@
|
||||
/>
|
||||
{/each}
|
||||
|
||||
{#each Object.keys(css ?? {}) as key (key)}
|
||||
<ResolveStyle
|
||||
{id}
|
||||
{customCss}
|
||||
{key}
|
||||
bind:css={css[key]}
|
||||
componentStyle={$app.css?.selectstepcomponent}
|
||||
/>
|
||||
{/each}
|
||||
|
||||
<InitializeComponent {id} />
|
||||
|
||||
<AlignWrapper {render} {horizontalAlignment} {verticalAlignment}>
|
||||
<AlignWrapper
|
||||
{render}
|
||||
{horizontalAlignment}
|
||||
{verticalAlignment}
|
||||
class={twMerge(css?.container?.class, 'wm-select-step')}
|
||||
style={css?.container?.style}
|
||||
>
|
||||
<div class="w-full" on:pointerdown={onPointerDown}>
|
||||
<Stepper
|
||||
tabs={(resolvedConfig?.items ?? []).map((item) => item.label)}
|
||||
|
||||
@@ -2,13 +2,15 @@
|
||||
import { getContext } from 'svelte'
|
||||
|
||||
import type { AppViewerContext, ComponentCustomCSS, RichConfigurations } from '../../types'
|
||||
import { concatCustomCss } from '../../utils'
|
||||
import { initCss } from '../../utils'
|
||||
import AlignWrapper from '../helpers/AlignWrapper.svelte'
|
||||
import { initConfig, initOutput } from '../../editor/appUtils'
|
||||
import InitializeComponent from '../helpers/InitializeComponent.svelte'
|
||||
import { Tab, Tabs } from '$lib/components/common'
|
||||
import { components } from '../../editor/component'
|
||||
import ResolveConfig from '../helpers/ResolveConfig.svelte'
|
||||
import { twMerge } from 'tailwind-merge'
|
||||
import ResolveStyle from '../helpers/ResolveStyle.svelte'
|
||||
|
||||
export let id: string
|
||||
export let configuration: RichConfigurations
|
||||
@@ -52,7 +54,7 @@
|
||||
}
|
||||
|
||||
$: selected && handleSelection(selected)
|
||||
$: css = concatCustomCss($app.css?.selecttabcomponent, customCss)
|
||||
let css = initCss($app.css?.selecttabcomponent, customCss)
|
||||
</script>
|
||||
|
||||
{#each Object.keys(components['selecttabcomponent'].initialData.configuration) as key (key)}
|
||||
@@ -64,17 +66,31 @@
|
||||
/>
|
||||
{/each}
|
||||
|
||||
{#each Object.keys(css ?? {}) as key (key)}
|
||||
<ResolveStyle
|
||||
{id}
|
||||
{customCss}
|
||||
{key}
|
||||
bind:css={css[key]}
|
||||
componentStyle={$app.css?.selecttabcomponent}
|
||||
/>
|
||||
{/each}
|
||||
|
||||
<InitializeComponent {id} />
|
||||
|
||||
<AlignWrapper {render} {horizontalAlignment} {verticalAlignment}>
|
||||
<div class="w-full">
|
||||
<Tabs bind:selected class={css?.tabRow?.class} style={css?.tabRow?.style}>
|
||||
<Tabs
|
||||
bind:selected
|
||||
class={twMerge(css?.tabRow?.class, 'wm-select-tab-row')}
|
||||
style={css?.tabRow?.style}
|
||||
>
|
||||
{#each resolvedConfig?.items ?? [] as item}
|
||||
<Tab
|
||||
value={item.value}
|
||||
class={css?.allTabs?.class}
|
||||
class={twMerge(css?.allTabs?.class, 'wm-select-tab')}
|
||||
style={css?.allTabs?.style}
|
||||
selectedClass={css?.selectedTab?.class}
|
||||
selectedClass={twMerge(css?.selectedTab?.class, 'wm-select-tab-selected')}
|
||||
selectedStyle={css?.selectedTab?.style}
|
||||
size={resolvedConfig?.tabSize}
|
||||
>
|
||||
|
||||
@@ -10,11 +10,12 @@
|
||||
} from '../../types'
|
||||
import AlignWrapper from '../helpers/AlignWrapper.svelte'
|
||||
import { twMerge } from 'tailwind-merge'
|
||||
import { concatCustomCss } from '../../utils'
|
||||
import { initCss } from '../../utils'
|
||||
import { initConfig, initOutput } from '../../editor/appUtils'
|
||||
import InitializeComponent from '../helpers/InitializeComponent.svelte'
|
||||
import { components } from '../../editor/component'
|
||||
import ResolveConfig from '../helpers/ResolveConfig.svelte'
|
||||
import ResolveStyle from '../helpers/ResolveStyle.svelte'
|
||||
|
||||
export let id: string
|
||||
export let configuration: RichConfigurations
|
||||
@@ -67,7 +68,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
$: css = concatCustomCss($app.css?.slidercomponent, customCss)
|
||||
let css = initCss($app.css?.slidercomponent, customCss)
|
||||
|
||||
let lastStyle: string | undefined = undefined
|
||||
$: if (css && slider && lastStyle !== css?.handle?.style) {
|
||||
@@ -114,17 +115,26 @@
|
||||
/>
|
||||
{/each}
|
||||
|
||||
{#each Object.keys(css ?? {}) as key (key)}
|
||||
<ResolveStyle
|
||||
{id}
|
||||
{customCss}
|
||||
{key}
|
||||
bind:css={css[key]}
|
||||
componentStyle={$app.css?.slidercomponent}
|
||||
/>
|
||||
{/each}
|
||||
|
||||
<InitializeComponent {id} />
|
||||
|
||||
<AlignWrapper {render} hFull {verticalAlignment}>
|
||||
<div class="flex {vertical ? 'flex-col' : ''} items-center w-full h-full gap-1 px-1">
|
||||
<span class={css?.limits?.class ?? ''} style={css?.limits?.style ?? ''}>
|
||||
<span class={twMerge(css?.limits?.class, 'wm-slider-limits')} style={css?.limits?.style ?? ''}>
|
||||
{vertical ? +(resolvedConfig?.max ?? 0) : +(resolvedConfig?.min ?? 0)}
|
||||
</span>
|
||||
<div
|
||||
class="grow"
|
||||
style="--range-handle-focus: {'#7e9abd'}; --range-handle: {'#7e9abd'}; {css?.bar?.style ??
|
||||
''}"
|
||||
class={twMerge('grow', css?.bar?.class, 'wm-slider-bar')}
|
||||
style={css?.bar?.style}
|
||||
on:pointerdown|stopPropagation={() => ($selectedComponent = [id])}
|
||||
>
|
||||
<RangeSlider
|
||||
@@ -136,12 +146,12 @@
|
||||
max={+(resolvedConfig?.max ?? 0)}
|
||||
/>
|
||||
</div>
|
||||
<span class={css?.limits?.class ?? ''} style={css?.limits?.style ?? ''}>
|
||||
<span class={twMerge(css?.limits?.class, 'wm-slider-limits')} style={css?.limits?.style ?? ''}>
|
||||
{vertical ? +(resolvedConfig?.min ?? 0) : +(resolvedConfig?.max ?? 1)}
|
||||
</span>
|
||||
<span class="mx-2">
|
||||
<span
|
||||
class={twMerge(spanClass, css?.value?.class ?? '')}
|
||||
class={twMerge(spanClass, css?.value?.class ?? '', 'wm-slider-value')}
|
||||
style={`${css?.value?.style ?? ''} ${width ? `width: ${width}px;` : ''}`}
|
||||
>
|
||||
{values[0]}
|
||||
|
||||
@@ -9,11 +9,12 @@
|
||||
ListInputs,
|
||||
RichConfigurations
|
||||
} from '../../types'
|
||||
import { concatCustomCss } from '../../utils'
|
||||
import { initCss } from '../../utils'
|
||||
import AlignWrapper from '../helpers/AlignWrapper.svelte'
|
||||
import InitializeComponent from '../helpers/InitializeComponent.svelte'
|
||||
import { components } from '../../editor/component'
|
||||
import ResolveConfig from '../helpers/ResolveConfig.svelte'
|
||||
import ResolveStyle from '../helpers/ResolveStyle.svelte'
|
||||
|
||||
export let id: string
|
||||
export let configuration: RichConfigurations
|
||||
@@ -64,7 +65,7 @@
|
||||
value = defaultValue
|
||||
}
|
||||
|
||||
$: css = concatCustomCss($app.css?.[appCssKey], customCss)
|
||||
let css = initCss($app.css?.[appCssKey], customCss)
|
||||
</script>
|
||||
|
||||
{#each Object.keys(components['textinputcomponent'].initialData.configuration) as key (key)}
|
||||
@@ -75,13 +76,25 @@
|
||||
configuration={configuration[key]}
|
||||
/>
|
||||
{/each}
|
||||
|
||||
{#each Object.keys(css ?? {}) as key (key)}
|
||||
<ResolveStyle
|
||||
{id}
|
||||
{customCss}
|
||||
{key}
|
||||
bind:css={css[key]}
|
||||
componentStyle={$app.css?.textinputcomponent}
|
||||
/>
|
||||
{/each}
|
||||
|
||||
<InitializeComponent {id} />
|
||||
{#if render}
|
||||
{#if inputType === 'textarea'}
|
||||
<textarea
|
||||
class={twMerge(
|
||||
'windmillapp w-full h-full py-1.5 text-sm focus:ring-indigo-100 px-2 ',
|
||||
css?.input?.class ?? ''
|
||||
css?.input?.class ?? '',
|
||||
'wm-text-input'
|
||||
)}
|
||||
style="resize:none; {css?.input?.style ?? ''}"
|
||||
on:pointerdown|stopPropagation={(e) =>
|
||||
@@ -96,7 +109,8 @@
|
||||
<input
|
||||
class={twMerge(
|
||||
'windmillapp w-full py-1.5 text-sm focus:ring-indigo-100 px-2 ',
|
||||
css?.input?.class ?? ''
|
||||
css?.input?.class ?? '',
|
||||
'wm-text-input'
|
||||
)}
|
||||
style={css?.input?.style ?? ''}
|
||||
on:pointerdown|stopPropagation={(e) =>
|
||||
@@ -110,7 +124,8 @@
|
||||
<input
|
||||
class={twMerge(
|
||||
'windmillapp w-full py-1.5 text-sm focus:ring-indigo-100 px-2 ',
|
||||
css?.input?.class ?? ''
|
||||
css?.input?.class ?? '',
|
||||
'wm-text-input'
|
||||
)}
|
||||
style={css?.input?.style ?? ''}
|
||||
on:pointerdown|stopPropagation={(e) =>
|
||||
@@ -124,7 +139,8 @@
|
||||
<input
|
||||
class={twMerge(
|
||||
'windmillapp w-full py-1.5 text-sm focus:ring-indigo-100 px-2 ',
|
||||
css?.input?.class ?? ''
|
||||
css?.input?.class ?? '',
|
||||
'wm-text-input'
|
||||
)}
|
||||
style={css?.input?.style ?? ''}
|
||||
on:pointerdown|stopPropagation={(e) =>
|
||||
|
||||
+19
-4
@@ -9,12 +9,13 @@
|
||||
ListInputs,
|
||||
RichConfigurations
|
||||
} from '../../../types'
|
||||
import { concatCustomCss } from '../../../utils'
|
||||
import { initCss } from '../../../utils'
|
||||
import AlignWrapper from '../../helpers/AlignWrapper.svelte'
|
||||
import CurrencyInput from './CurrencyInput.svelte'
|
||||
import InitializeComponent from '../../helpers/InitializeComponent.svelte'
|
||||
import ResolveConfig from '../../helpers/ResolveConfig.svelte'
|
||||
import { components } from '$lib/components/apps/editor/component'
|
||||
import ResolveStyle from '../../helpers/ResolveStyle.svelte'
|
||||
|
||||
export let id: string
|
||||
export let configuration: RichConfigurations
|
||||
@@ -60,7 +61,7 @@
|
||||
|
||||
$: handleDefault(resolvedConfig.defaultValue)
|
||||
|
||||
$: css = concatCustomCss($app.css?.currencycomponent, customCss)
|
||||
let css = initCss($app.css?.currencycomponent, customCss)
|
||||
</script>
|
||||
|
||||
{#each Object.keys(components['currencycomponent'].initialData.configuration) as key (key)}
|
||||
@@ -72,6 +73,16 @@
|
||||
/>
|
||||
{/each}
|
||||
|
||||
{#each Object.keys(css ?? {}) as key (key)}
|
||||
<ResolveStyle
|
||||
{id}
|
||||
{customCss}
|
||||
{key}
|
||||
bind:css={css[key]}
|
||||
componentStyle={$app.css?.currencycomponent}
|
||||
/>
|
||||
{/each}
|
||||
|
||||
<InitializeComponent {id} />
|
||||
|
||||
<AlignWrapper {render} {verticalAlignment}>
|
||||
@@ -81,9 +92,13 @@
|
||||
<div class="w-full" on:pointerdown|stopPropagation={() => ($selectedComponent = [id])}>
|
||||
<CurrencyInput
|
||||
inputClasses={{
|
||||
formatted: twMerge('px-2 w-full py-1.5 windmillapp', css?.input?.class),
|
||||
formatted: twMerge(
|
||||
'px-2 w-full py-1.5 windmillapp',
|
||||
css?.input?.class,
|
||||
'wm-currency-input'
|
||||
),
|
||||
wrapper: 'w-full windmillapp',
|
||||
formattedZero: twMerge('text-black windmillapp ', css?.input?.class)
|
||||
formattedZero: twMerge('text-black windmillapp ', css?.input?.class, 'wm-currency')
|
||||
}}
|
||||
style={css?.input?.style}
|
||||
bind:value
|
||||
|
||||
@@ -3,9 +3,11 @@
|
||||
import { initOutput } from '../../editor/appUtils'
|
||||
import SubGridEditor from '../../editor/SubGridEditor.svelte'
|
||||
import type { AppViewerContext, ComponentCustomCSS, RichConfiguration } from '../../types'
|
||||
import { concatCustomCss } from '../../utils'
|
||||
import { initCss } from '../../utils'
|
||||
import InitializeComponent from '../helpers/InitializeComponent.svelte'
|
||||
import { InputValue } from '../helpers'
|
||||
import { twMerge } from 'tailwind-merge'
|
||||
import ResolveStyle from '../helpers/ResolveStyle.svelte'
|
||||
|
||||
export let id: string
|
||||
export let componentContainerHeight: number
|
||||
@@ -28,7 +30,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
$: css = concatCustomCss($app.css?.containercomponent, customCss)
|
||||
let css = initCss($app.css?.containercomponent, customCss)
|
||||
|
||||
let resolvedConditions: boolean[] = []
|
||||
let selectedConditionIndex = 0
|
||||
@@ -69,6 +71,16 @@
|
||||
<InputValue key="conditions" {id} input={condition} bind:value={resolvedConditions[index]} />
|
||||
{/each}
|
||||
|
||||
{#each Object.keys(css ?? {}) as key (key)}
|
||||
<ResolveStyle
|
||||
{id}
|
||||
{customCss}
|
||||
{key}
|
||||
bind:css={css[key]}
|
||||
componentStyle={$app.css?.conditionalwrapper}
|
||||
/>
|
||||
{/each}
|
||||
|
||||
<InitializeComponent {id} />
|
||||
|
||||
<div class="w-full h-full">
|
||||
@@ -77,7 +89,7 @@
|
||||
<SubGridEditor
|
||||
visible={render && i == selectedConditionIndex}
|
||||
{id}
|
||||
class={css?.container?.class}
|
||||
class={twMerge(css?.container?.class, 'wm-conditional-tabs')}
|
||||
style={css?.container?.style}
|
||||
subGridId={`${id}-${i}`}
|
||||
containerHeight={componentContainerHeight}
|
||||
|
||||
@@ -3,8 +3,10 @@
|
||||
import { initOutput } from '../../editor/appUtils'
|
||||
import SubGridEditor from '../../editor/SubGridEditor.svelte'
|
||||
import type { AppViewerContext, ComponentCustomCSS, RichConfigurations } from '../../types'
|
||||
import { concatCustomCss } from '../../utils'
|
||||
import { initCss } from '../../utils'
|
||||
import InitializeComponent from '../helpers/InitializeComponent.svelte'
|
||||
import { twMerge } from 'tailwind-merge'
|
||||
import ResolveStyle from '../helpers/ResolveStyle.svelte'
|
||||
// import type { EvalV2AppInput, StaticAppInput } from '../../inputType'
|
||||
import { writable } from 'svelte/store'
|
||||
import { InputValue } from '../helpers'
|
||||
@@ -32,11 +34,21 @@
|
||||
}
|
||||
}
|
||||
|
||||
$: css = concatCustomCss($app.css?.containercomponent, customCss)
|
||||
let css = initCss($app.css?.containercomponent, customCss)
|
||||
</script>
|
||||
|
||||
<InitializeComponent {id} />
|
||||
|
||||
{#each Object.keys(css ?? {}) as key (key)}
|
||||
<ResolveStyle
|
||||
{id}
|
||||
{customCss}
|
||||
{key}
|
||||
bind:css={css[key]}
|
||||
componentStyle={$app.css?.containercomponent}
|
||||
/>
|
||||
{/each}
|
||||
|
||||
{#each Object.keys(groupFields ?? {}) as field}
|
||||
{#if groupFields && field in groupFields}
|
||||
<InputValue key={field} {id} input={groupFields[field]} bind:value={$groupContext[field]} />
|
||||
@@ -49,7 +61,7 @@
|
||||
<SubGridEditor
|
||||
visible={render}
|
||||
{id}
|
||||
class={css?.container?.class}
|
||||
class={twMerge(css?.container?.class, 'wm-container')}
|
||||
style={css?.container?.style}
|
||||
subGridId={`${id}-0`}
|
||||
containerHeight={componentContainerHeight}
|
||||
|
||||
@@ -9,10 +9,11 @@
|
||||
RichConfigurations,
|
||||
VerticalAlignment
|
||||
} from '../../types'
|
||||
import { TailwindClassPatterns, concatCustomCss, hasTailwindClass } from '../../utils'
|
||||
import { TailwindClassPatterns, initCss, hasTailwindClass } from '../../utils'
|
||||
import AlignWrapper from '../helpers/AlignWrapper.svelte'
|
||||
import InputValue from '../helpers/InputValue.svelte'
|
||||
import InitializeComponent from '../helpers/InitializeComponent.svelte'
|
||||
import ResolveStyle from '../helpers/ResolveStyle.svelte'
|
||||
|
||||
export let id: string
|
||||
export let configuration: RichConfigurations
|
||||
@@ -28,7 +29,7 @@
|
||||
let size = 2
|
||||
let color = '#00000060'
|
||||
|
||||
$: css = concatCustomCss($app.css?.[position + 'dividercomponent'], customCss)
|
||||
let css = initCss($app.css?.[position + 'dividercomponent'], customCss)
|
||||
|
||||
//used so that we can count number of outputs setup for first refresh
|
||||
initOutput($worldStore, id, {})
|
||||
@@ -50,17 +51,32 @@
|
||||
<InputValue key="color" {id} input={configuration.color} bind:value={color} />
|
||||
<InitializeComponent {id} />
|
||||
|
||||
{#each Object.keys(css ?? {}) as key (key)}
|
||||
<ResolveStyle
|
||||
{id}
|
||||
{customCss}
|
||||
{key}
|
||||
bind:css={css[key]}
|
||||
componentStyle={$app.css?.[position + 'dividercomponent']}
|
||||
/>
|
||||
{/each}
|
||||
|
||||
<AlignWrapper
|
||||
{horizontalAlignment}
|
||||
{verticalAlignment}
|
||||
class={twMerge(css?.container?.class, 'h-full')}
|
||||
class={twMerge(
|
||||
css?.container?.class,
|
||||
position === 'horizontal' ? 'wm-horizontal-divider-container' : 'wm-vertical-divider-container',
|
||||
'h-full'
|
||||
)}
|
||||
style={css?.container?.style}
|
||||
{render}
|
||||
>
|
||||
<div
|
||||
class={twMerge(
|
||||
`rounded-full ${position === 'horizontal' ? 'w-full' : 'h-full'}`,
|
||||
css?.divider?.class ?? ''
|
||||
css?.divider?.class,
|
||||
position === 'horizontal' ? 'wm-horizontal-divider' : 'wm-vertical-divider'
|
||||
)}
|
||||
style="
|
||||
{getSize()}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
import SubGridEditor from '../../editor/SubGridEditor.svelte'
|
||||
import type { AppViewerContext, ComponentCustomCSS, RichConfigurations } from '../../types'
|
||||
import Portal from 'svelte-portal'
|
||||
import { concatCustomCss } from '../../utils'
|
||||
import { initCss } from '../../utils'
|
||||
import { Button, Drawer, DrawerContent } from '$lib/components/common'
|
||||
import { twMerge } from 'tailwind-merge'
|
||||
import { AlignWrapper } from '../helpers'
|
||||
@@ -11,6 +11,7 @@
|
||||
import InitializeComponent from '../helpers/InitializeComponent.svelte'
|
||||
import { components } from '../../editor/component'
|
||||
import ResolveConfig from '../helpers/ResolveConfig.svelte'
|
||||
import ResolveStyle from '../helpers/ResolveStyle.svelte'
|
||||
|
||||
export let customCss: ComponentCustomCSS<'drawercomponent'> | undefined = undefined
|
||||
export let id: string
|
||||
@@ -31,7 +32,7 @@
|
||||
|
||||
let appDrawer: Drawer
|
||||
|
||||
$: css = concatCustomCss($app.css?.drawercomponent, customCss)
|
||||
let css = initCss($app.css?.drawercomponent, customCss)
|
||||
</script>
|
||||
|
||||
{#each Object.keys(components['drawercomponent'].initialData.configuration) as key (key)}
|
||||
@@ -43,14 +44,25 @@
|
||||
/>
|
||||
{/each}
|
||||
|
||||
{#each Object.keys(css ?? {}) as key (key)}
|
||||
<ResolveStyle
|
||||
{id}
|
||||
{customCss}
|
||||
{key}
|
||||
bind:css={css[key]}
|
||||
componentStyle={$app.css?.drawercomponent}
|
||||
/>
|
||||
{/each}
|
||||
|
||||
<InitializeComponent {id} />
|
||||
|
||||
<div class="h-full w-full">
|
||||
<AlignWrapper {noWFull} {horizontalAlignment} {verticalAlignment}>
|
||||
<Button
|
||||
btnClasses={css?.button?.class}
|
||||
btnClasses={twMerge(css?.button?.class, 'wm-drawer-button')}
|
||||
wrapperClasses={twMerge(
|
||||
css?.container?.class,
|
||||
'wm-drawer-button-container',
|
||||
resolvedConfig?.fillContainer ? 'w-full h-full' : ''
|
||||
)}
|
||||
wrapperStyle={css?.container?.style}
|
||||
@@ -93,7 +105,8 @@
|
||||
fullScreen={$mode !== 'dnd'}
|
||||
>
|
||||
<div
|
||||
class="h-full"
|
||||
class={twMerge('h-full', css?.drawer?.class, 'wm-drawer')}
|
||||
style={css?.drawer?.style}
|
||||
on:pointerdown={(e) => {
|
||||
e?.stopPropagation()
|
||||
if (!$connectingInput.opened) {
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
import { initConfig, initOutput } from '../../editor/appUtils'
|
||||
import SubGridEditor from '../../editor/SubGridEditor.svelte'
|
||||
import type { AppViewerContext, ComponentCustomCSS, RichConfigurations } from '../../types'
|
||||
import { concatCustomCss } from '../../utils'
|
||||
import { initCss } from '../../utils'
|
||||
import InitializeComponent from '../helpers/InitializeComponent.svelte'
|
||||
import ListWrapper from './ListWrapper.svelte'
|
||||
import type { AppInput } from '../../inputType'
|
||||
@@ -12,6 +12,8 @@
|
||||
import RunnableWrapper from '../helpers/RunnableWrapper.svelte'
|
||||
import { Button } from '$lib/components/common'
|
||||
import { Loader2, ChevronLeft, ChevronRight } from 'lucide-svelte'
|
||||
import { twMerge } from 'tailwind-merge'
|
||||
import ResolveStyle from '../helpers/ResolveStyle.svelte'
|
||||
|
||||
export let id: string
|
||||
export let componentInput: AppInput | undefined
|
||||
@@ -43,7 +45,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
$: css = concatCustomCss($app.css?.containercomponent, customCss)
|
||||
let css = initCss($app.css?.containercomponent, customCss)
|
||||
let result: any[] | undefined = undefined
|
||||
|
||||
$: isCard = resolvedConfig.width?.selected == 'card'
|
||||
@@ -111,6 +113,16 @@
|
||||
/>
|
||||
{/each}
|
||||
|
||||
{#each Object.keys(css ?? {}) as key (key)}
|
||||
<ResolveStyle
|
||||
{id}
|
||||
{customCss}
|
||||
{key}
|
||||
bind:css={css[key]}
|
||||
componentStyle={$app.css?.listcomponent}
|
||||
/>
|
||||
{/each}
|
||||
|
||||
<InitializeComponent {id} />
|
||||
|
||||
<RunnableWrapper
|
||||
@@ -124,7 +136,10 @@
|
||||
bind:result
|
||||
bind:loading
|
||||
>
|
||||
<div class="flex flex-col divide-y h-full">
|
||||
<div
|
||||
class={twMerge('flex flex-col divide-y h-full', css?.container?.class, 'wm-list')}
|
||||
style={css?.container?.style}
|
||||
>
|
||||
<div
|
||||
class="w-full flex flex-wrap overflow-auto {isCard ? 'h-full gap-2' : 'divide-y max-h-full'}"
|
||||
>
|
||||
@@ -150,8 +165,6 @@
|
||||
<SubGridEditor
|
||||
visible={render}
|
||||
{id}
|
||||
class={css?.container?.class}
|
||||
style={css?.container?.style}
|
||||
subGridId={`${id}-0`}
|
||||
containerHeight={resolvedConfig.heightPx}
|
||||
on:focus={() => {
|
||||
@@ -175,11 +188,12 @@
|
||||
{/if}
|
||||
</div>
|
||||
{#if pagination.shouldDisplayPagination}
|
||||
<div class="bg-surface-secondary h-8 flex flex-row gap-1 p-1 items-center">
|
||||
<div class="bg-surface-secondary h-8 flex flex-row gap-1 p-1 items-center wm-list-pagination">
|
||||
<Button
|
||||
size="xs2"
|
||||
variant="border"
|
||||
color="light"
|
||||
btnClasses="flex flex-row gap-1 items-center wm-list-pagination-buttons"
|
||||
on:click={() => {
|
||||
isPreviousLoading = true
|
||||
page = page - 1
|
||||
@@ -187,19 +201,18 @@
|
||||
}}
|
||||
disabled={page === 0}
|
||||
>
|
||||
<div class="flex flex-row gap-1 items-center">
|
||||
{#if isPreviousLoading && loading}
|
||||
<Loader2 size={14} class="animate-spin" />
|
||||
{:else}
|
||||
<ChevronLeft size={14} />
|
||||
{/if}
|
||||
Previous
|
||||
</div>
|
||||
{#if isPreviousLoading && loading}
|
||||
<Loader2 size={14} class="animate-spin" />
|
||||
{:else}
|
||||
<ChevronLeft size={14} />
|
||||
{/if}
|
||||
Previous
|
||||
</Button>
|
||||
<Button
|
||||
size="xs2"
|
||||
variant="border"
|
||||
color="light"
|
||||
btnClasses="flex flex-row gap-1 items-center wm-list-pagination-buttons"
|
||||
on:click={() => {
|
||||
isNextLoading = true
|
||||
page = page + 1
|
||||
@@ -207,15 +220,13 @@
|
||||
}}
|
||||
disabled={pagination.disableNext && pagination.total > 0}
|
||||
>
|
||||
<div class="flex flex-row gap-1 items-center">
|
||||
Next
|
||||
Next
|
||||
|
||||
{#if isNextLoading && loading}
|
||||
<Loader2 size={14} class="animate-spin" />
|
||||
{:else}
|
||||
<ChevronRight size={14} />
|
||||
{/if}
|
||||
</div>
|
||||
{#if isNextLoading && loading}
|
||||
<Loader2 size={14} class="animate-spin" />
|
||||
{:else}
|
||||
<ChevronRight size={14} />
|
||||
{/if}
|
||||
</Button>
|
||||
<div class="text-xs">{page + 1} {pagination.total > 0 ? `of ${pagination.total}` : ''}</div>
|
||||
</div>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
import { getContext } from 'svelte'
|
||||
import SubGridEditor from '../../editor/SubGridEditor.svelte'
|
||||
import type { AppViewerContext, ComponentCustomCSS, RichConfigurations } from '../../types'
|
||||
import { concatCustomCss } from '../../utils'
|
||||
import { initCss } from '../../utils'
|
||||
import { Button } from '$lib/components/common'
|
||||
import { twMerge } from 'tailwind-merge'
|
||||
import { AlignWrapper } from '../helpers'
|
||||
@@ -13,6 +13,7 @@
|
||||
import { X } from 'lucide-svelte'
|
||||
import { components } from '../../editor/component'
|
||||
import ResolveConfig from '../helpers/ResolveConfig.svelte'
|
||||
import ResolveStyle from '../helpers/ResolveStyle.svelte'
|
||||
|
||||
export let customCss: ComponentCustomCSS<'modalcomponent'> | undefined = undefined
|
||||
export let id: string
|
||||
@@ -35,7 +36,7 @@
|
||||
//used so that we can count number of outputs setup for first refresh
|
||||
initOutput($worldStore, id, {})
|
||||
|
||||
$: css = concatCustomCss($app.css?.modalcomponent, customCss)
|
||||
let css = initCss($app.css?.modalcomponent, customCss)
|
||||
let open = false
|
||||
|
||||
function handleKeyUp(event: KeyboardEvent): void {
|
||||
@@ -79,15 +80,29 @@
|
||||
configuration={configuration[key]}
|
||||
/>
|
||||
{/each}
|
||||
|
||||
{#each Object.keys(css ?? {}) as key (key)}
|
||||
<ResolveStyle
|
||||
{id}
|
||||
{customCss}
|
||||
{key}
|
||||
bind:css={css[key]}
|
||||
componentStyle={$app.css?.modalcomponent}
|
||||
/>
|
||||
{/each}
|
||||
|
||||
{#if render}
|
||||
<div class="h-full w-full">
|
||||
<AlignWrapper {noWFull} {horizontalAlignment} {verticalAlignment}>
|
||||
<Button
|
||||
btnClasses={css?.button?.class}
|
||||
btnClasses={twMerge(css?.button?.class, 'wm-button', 'wm-modal-button')}
|
||||
wrapperClasses={twMerge(
|
||||
resolvedConfig?.buttonFillContainer ? 'w-full h-full' : '',
|
||||
css?.buttonContainer?.class
|
||||
css?.buttonContainer?.class,
|
||||
'wm-button-container',
|
||||
'wm-modal-button-container'
|
||||
)}
|
||||
style={css?.button?.style}
|
||||
wrapperStyle={css?.buttonContainer?.style}
|
||||
disabled={resolvedConfig.buttonDisabled}
|
||||
on:pointerdown={(e) => {
|
||||
@@ -121,10 +136,7 @@
|
||||
>
|
||||
<div
|
||||
style={css?.popup?.style}
|
||||
class={twMerge(
|
||||
'm-24 max-h-[80%] bg-surface overflow-y-auto rounded-lg relative',
|
||||
css?.popup?.class
|
||||
)}
|
||||
class={twMerge('mx-24 mt-8 bg-surface rounded-lg relative', css?.popup?.class)}
|
||||
use:clickOutside={false}
|
||||
on:click_outside={() => {
|
||||
if ($mode !== 'dnd') {
|
||||
@@ -139,7 +151,6 @@
|
||||
on:click={() => {
|
||||
open = false
|
||||
}}
|
||||
style={css?.button?.style}
|
||||
class="hover:bg-surface-hover bg-surface-secondary rounded-full w-8 h-8 flex items-center justify-center transition-all"
|
||||
>
|
||||
<X class="text-tertiary" />
|
||||
@@ -147,7 +158,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class=""
|
||||
class="wm-modal"
|
||||
on:pointerdown={(e) => {
|
||||
e?.stopPropagation()
|
||||
if (!$connectingInput.opened) {
|
||||
|
||||
@@ -2,11 +2,13 @@
|
||||
import { getContext } from 'svelte'
|
||||
import SubGridEditor from '../../editor/SubGridEditor.svelte'
|
||||
import type { AppViewerContext, ComponentCustomCSS } from '../../types'
|
||||
import { concatCustomCss } from '../../utils'
|
||||
import { initCss } from '../../utils'
|
||||
import { Pane, Splitpanes } from 'svelte-splitpanes'
|
||||
import { deepEqual } from 'fast-equals'
|
||||
import { initOutput } from '../../editor/appUtils'
|
||||
import InitializeComponent from '../helpers/InitializeComponent.svelte'
|
||||
import { twMerge } from 'tailwind-merge'
|
||||
import ResolveStyle from '../helpers/ResolveStyle.svelte'
|
||||
|
||||
export let id: string
|
||||
export let componentContainerHeight: number
|
||||
@@ -30,7 +32,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
$: css = concatCustomCss($app.css?.containercomponent, customCss)
|
||||
let css = initCss($app.css?.containercomponent, customCss)
|
||||
|
||||
$componentControl[id] = {
|
||||
left: () => {
|
||||
@@ -66,6 +68,18 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
{#each Object.keys(css ?? {}) as key (key)}
|
||||
<ResolveStyle
|
||||
{id}
|
||||
{customCss}
|
||||
{key}
|
||||
bind:css={css[key]}
|
||||
componentStyle={$app.css?.[
|
||||
horizontal ? 'horizontalsplitpanescomponent' : 'verticalsplitpanescomponent'
|
||||
]}
|
||||
/>
|
||||
{/each}
|
||||
|
||||
<InitializeComponent {id} />
|
||||
|
||||
<div class="h-full w-full border" on:pointerdown={onFocus}>
|
||||
@@ -88,7 +102,10 @@
|
||||
visible={render}
|
||||
{id}
|
||||
shouldHighlight={$focusedGrid?.subGridIndex === index}
|
||||
class={css?.container?.class}
|
||||
class={twMerge(
|
||||
css?.container?.class,
|
||||
horizontal ? 'wm-horizontal-split-panes' : 'wm-vertical-split-panes'
|
||||
)}
|
||||
style={css?.container?.style}
|
||||
subGridId={`${id}-${index}`}
|
||||
containerHeight={horizontal ? undefined : componentContainerHeight - 8}
|
||||
|
||||
@@ -3,13 +3,15 @@
|
||||
import { initOutput } from '../../editor/appUtils'
|
||||
import SubGridEditor from '../../editor/SubGridEditor.svelte'
|
||||
import type { AppViewerContext, ComponentCustomCSS } from '../../types'
|
||||
import { concatCustomCss } from '../../utils'
|
||||
import { initCss } from '../../utils'
|
||||
import InitializeComponent from '../helpers/InitializeComponent.svelte'
|
||||
import Button from '$lib/components/common/button/Button.svelte'
|
||||
import { RunnableComponent, RunnableWrapper } from '../helpers'
|
||||
import type { AppInput } from '../../inputType'
|
||||
import { ArrowLeftIcon, ArrowRightIcon, Loader2 } from 'lucide-svelte'
|
||||
import Stepper from '$lib/components/common/stepper/Stepper.svelte'
|
||||
import { twMerge } from 'tailwind-merge'
|
||||
import ResolveStyle from '../helpers/ResolveStyle.svelte'
|
||||
|
||||
export let id: string
|
||||
export let componentContainerHeight: number
|
||||
@@ -117,12 +119,22 @@
|
||||
}
|
||||
|
||||
$: selected != undefined && handleTabSelection()
|
||||
$: css = concatCustomCss($app.css?.steppercomponent, customCss)
|
||||
let css = initCss($app.css?.steppercomponent, customCss)
|
||||
$: lastStep = selectedIndex === tabs.length - 1
|
||||
|
||||
let directionClicked: 'left' | 'right' | undefined = undefined
|
||||
</script>
|
||||
|
||||
{#each Object.keys(css ?? {}) as key (key)}
|
||||
<ResolveStyle
|
||||
{id}
|
||||
{customCss}
|
||||
{key}
|
||||
bind:css={css[key]}
|
||||
componentStyle={$app.css?.steppercomponent}
|
||||
/>
|
||||
{/each}
|
||||
|
||||
<InitializeComponent {id} />
|
||||
<RunnableWrapper
|
||||
hasChildrens
|
||||
@@ -163,7 +175,7 @@
|
||||
{id}
|
||||
visible={render && i === selectedIndex}
|
||||
subGridId={`${id}-${i}`}
|
||||
class={css?.container?.class}
|
||||
class={twMerge(css?.container?.class, 'wm-stepper')}
|
||||
style={css?.container?.style}
|
||||
containerHeight={componentContainerHeight - tabHeight - footerHeight}
|
||||
on:focus={() => {
|
||||
|
||||
@@ -10,9 +10,11 @@
|
||||
RichConfiguration,
|
||||
RichConfigurations
|
||||
} from '../../types'
|
||||
import { concatCustomCss } from '../../utils'
|
||||
import { initCss } from '../../utils'
|
||||
import InputValue from '../helpers/InputValue.svelte'
|
||||
import InitializeComponent from '../helpers/InitializeComponent.svelte'
|
||||
import { twMerge } from 'tailwind-merge'
|
||||
import ResolveStyle from '../helpers/ResolveStyle.svelte'
|
||||
|
||||
export let id: string
|
||||
export let configuration: RichConfigurations
|
||||
@@ -81,13 +83,23 @@
|
||||
|
||||
$: selected != undefined && handleTabSelection()
|
||||
let selectedIndex = tabs?.indexOf(selected) ?? -1
|
||||
$: css = concatCustomCss($app.css?.tabscomponent, customCss)
|
||||
let css = initCss($app.css?.tabscomponent, customCss)
|
||||
|
||||
let resolvedDisabledTabs: boolean[] = []
|
||||
</script>
|
||||
|
||||
<InputValue key="kind" {id} input={configuration.tabsKind} bind:value={resolvedConfig.tabsKind} />
|
||||
|
||||
{#each Object.keys(css ?? {}) as key (key)}
|
||||
<ResolveStyle
|
||||
{id}
|
||||
{customCss}
|
||||
{key}
|
||||
bind:css={css[key]}
|
||||
componentStyle={$app.css?.tabscomponent}
|
||||
/>
|
||||
{/each}
|
||||
|
||||
<InitializeComponent {id} />
|
||||
|
||||
{#each disabledTabs ?? [] as disableTab, index}
|
||||
@@ -97,13 +109,17 @@
|
||||
<div class={resolvedConfig.tabsKind == 'sidebar' ? 'flex gap-4 w-full' : 'w-full'}>
|
||||
{#if !resolvedConfig.tabsKind || resolvedConfig.tabsKind == 'tabs' || (resolvedConfig.tabsKind == 'invisibleOnView' && $mode == 'dnd')}
|
||||
<div bind:clientHeight={tabHeight}>
|
||||
<Tabs bind:selected class={css?.tabRow?.class} style={css?.tabRow?.style}>
|
||||
<Tabs
|
||||
bind:selected
|
||||
class={twMerge(css?.tabRow?.class, 'wm-tabs-tabRow')}
|
||||
style={css?.tabRow?.style}
|
||||
>
|
||||
{#each tabs ?? [] as res, index}
|
||||
<Tab
|
||||
value={res}
|
||||
class={css?.allTabs?.class}
|
||||
class={twMerge(css?.allTabs?.class, 'wm-tabs-alltabs')}
|
||||
style={css?.allTabs?.style}
|
||||
selectedClass={css?.selectedTab?.class}
|
||||
selectedClass={twMerge(css?.selectedTab?.class, 'wm-tabs-selectedTab')}
|
||||
selectedStyle={css?.selectedTab?.style}
|
||||
disabled={resolvedDisabledTabs[index]}
|
||||
>
|
||||
@@ -136,7 +152,7 @@
|
||||
{id}
|
||||
visible={render && i === selectedIndex}
|
||||
subGridId={`${id}-${i}`}
|
||||
class={css?.container?.class}
|
||||
class={twMerge(css?.container?.class, 'wm-tabs-container')}
|
||||
style={css?.container?.style}
|
||||
containerHeight={resolvedConfig.tabsKind !== 'sidebar' && $mode !== 'preview'
|
||||
? componentContainerHeight - tabHeight
|
||||
|
||||
@@ -42,10 +42,18 @@
|
||||
import CssSettings from './componentsPanel/CssSettings.svelte'
|
||||
import ConnectionInstructions from './ConnectionInstructions.svelte'
|
||||
import SettingsPanel from './SettingsPanel.svelte'
|
||||
import { secondaryMenu, SecondaryMenu } from './settingsPanel/secondaryMenu'
|
||||
import {
|
||||
SecondaryMenu,
|
||||
secondaryMenuLeft,
|
||||
secondaryMenuLeftStore,
|
||||
secondaryMenuRight,
|
||||
secondaryMenuRightStore
|
||||
} from './settingsPanel/secondaryMenu'
|
||||
import Popover from '../../Popover.svelte'
|
||||
import { BG_PREFIX, migrateApp } from '../utils'
|
||||
import DarkModeObserver from '$lib/components/DarkModeObserver.svelte'
|
||||
import { getTheme } from './componentsPanel/themeUtils'
|
||||
import StylePanel from './settingsPanel/StylePanel.svelte'
|
||||
|
||||
export let app: App
|
||||
export let path: string
|
||||
@@ -66,6 +74,9 @@
|
||||
input: undefined,
|
||||
hoveredComponent: undefined
|
||||
})
|
||||
|
||||
const cssEditorOpen = writable<boolean>(false)
|
||||
|
||||
const history = initHistory(app)
|
||||
|
||||
const errorByComponent = writable<Record<string, { error: string; componentId: string }>>({})
|
||||
@@ -83,6 +94,11 @@
|
||||
const darkMode: Writable<boolean> = writable(document.documentElement.classList.contains('dark'))
|
||||
|
||||
const worldStore = buildWorld(context)
|
||||
const previewTheme: Writable<string | undefined> = writable(undefined)
|
||||
|
||||
$secondaryMenuRightStore.isOpen = false
|
||||
$secondaryMenuLeftStore.isOpen = false
|
||||
|
||||
setContext<AppViewerContext>('AppViewerContext', {
|
||||
worldStore,
|
||||
app: appStore,
|
||||
@@ -109,7 +125,9 @@
|
||||
componentControl: writable({}),
|
||||
hoverStore: writable(undefined),
|
||||
allIdsInPath: writable([]),
|
||||
darkMode
|
||||
darkMode,
|
||||
cssEditorOpen,
|
||||
previewTheme
|
||||
})
|
||||
|
||||
setContext<AppEditorContext>('AppEditorContext', {
|
||||
@@ -144,7 +162,7 @@
|
||||
|
||||
$: width = $breakpoint === 'sm' ? 'min-w-[400px] max-w-[656px]' : 'min-w-[710px] w-full'
|
||||
|
||||
let selectedTab: 'insert' | 'settings' = 'insert'
|
||||
let selectedTab: 'insert' | 'settings' | 'css' = 'insert'
|
||||
|
||||
let befSelected: string | undefined = undefined
|
||||
$: if ($selectedComponent?.[0] != befSelected) {
|
||||
@@ -197,16 +215,184 @@
|
||||
})
|
||||
|
||||
$: if ($connectingInput.opened) {
|
||||
secondaryMenu.open(ConnectionInstructions, {}, () => {
|
||||
secondaryMenuRight.open(ConnectionInstructions, {}, () => {
|
||||
$connectingInput.opened = false
|
||||
})
|
||||
secondaryMenuLeft.close()
|
||||
} else {
|
||||
secondaryMenu.close()
|
||||
secondaryMenuRight.close()
|
||||
}
|
||||
|
||||
function onThemeChange() {
|
||||
$darkMode = document.documentElement.classList.contains('dark')
|
||||
}
|
||||
|
||||
let runnablePanelSize = 30
|
||||
let gridPanelSize = 70
|
||||
|
||||
let leftPanelSize = 22
|
||||
let centerPanelSize = 63
|
||||
let rightPanelSize = 22
|
||||
|
||||
let tmpRunnablePanelSize = -1
|
||||
let tmpGridPanelSize = -1
|
||||
|
||||
let tmpLeftPanelSize = -1
|
||||
let tmpCenterPanelSize = -1
|
||||
let tmpRightPanelSize = -1
|
||||
|
||||
let toggled = false
|
||||
let cssToggled = false
|
||||
|
||||
$: if ($connectingInput.opened && !toggled) {
|
||||
tmpRunnablePanelSize = runnablePanelSize
|
||||
tmpGridPanelSize = gridPanelSize
|
||||
|
||||
animateTo(runnablePanelSize, 0, (newValue: number) => (runnablePanelSize = newValue))
|
||||
animateTo(gridPanelSize, 100, (newValue: number) => (gridPanelSize = newValue))
|
||||
|
||||
toggled = true
|
||||
} else if (!$connectingInput.opened && toggled) {
|
||||
animateTo(
|
||||
runnablePanelSize,
|
||||
tmpRunnablePanelSize,
|
||||
(newValue: number) => (runnablePanelSize = newValue)
|
||||
)
|
||||
animateTo(gridPanelSize, tmpGridPanelSize, (newValue: number) => (gridPanelSize = newValue))
|
||||
|
||||
tmpRunnablePanelSize = -1
|
||||
tmpGridPanelSize = -1
|
||||
|
||||
toggled = false
|
||||
}
|
||||
|
||||
// Animation logic for cssInput
|
||||
$: animateCssInput($cssEditorOpen)
|
||||
$: $cssEditorOpen && secondaryMenuLeft?.open(StylePanel, {})
|
||||
|
||||
function animateCssInput(cssEditorOpen: boolean) {
|
||||
console.log(cssEditorOpen, cssToggled)
|
||||
if (cssEditorOpen && !cssToggled) {
|
||||
cssToggled = true
|
||||
|
||||
tmpLeftPanelSize = leftPanelSize
|
||||
tmpCenterPanelSize = centerPanelSize
|
||||
tmpRightPanelSize = rightPanelSize
|
||||
|
||||
animateTo(leftPanelSize, 20, (newValue: number) => (leftPanelSize = newValue))
|
||||
animateTo(centerPanelSize, 55, (newValue: number) => (centerPanelSize = newValue))
|
||||
animateTo(rightPanelSize, 25, (newValue: number) => (rightPanelSize = newValue))
|
||||
|
||||
tmpRunnablePanelSize = runnablePanelSize
|
||||
tmpGridPanelSize = gridPanelSize
|
||||
|
||||
animateTo(runnablePanelSize, 0, (newValue: number) => (runnablePanelSize = newValue))
|
||||
animateTo(gridPanelSize, 100, (newValue: number) => (gridPanelSize = newValue))
|
||||
} else if (!cssEditorOpen && cssToggled) {
|
||||
cssToggled = false
|
||||
|
||||
animateTo(leftPanelSize, tmpLeftPanelSize, (newValue: number) => (leftPanelSize = newValue))
|
||||
animateTo(
|
||||
centerPanelSize,
|
||||
tmpCenterPanelSize,
|
||||
(newValue: number) => (centerPanelSize = newValue)
|
||||
)
|
||||
animateTo(
|
||||
rightPanelSize,
|
||||
tmpRightPanelSize,
|
||||
(newValue: number) => (rightPanelSize = newValue)
|
||||
)
|
||||
|
||||
tmpLeftPanelSize = -1
|
||||
tmpCenterPanelSize = -1
|
||||
tmpRightPanelSize = -1
|
||||
|
||||
animateTo(
|
||||
runnablePanelSize,
|
||||
tmpRunnablePanelSize,
|
||||
(newValue: number) => (runnablePanelSize = newValue)
|
||||
)
|
||||
animateTo(gridPanelSize, tmpGridPanelSize, (newValue: number) => (gridPanelSize = newValue))
|
||||
|
||||
tmpRunnablePanelSize = -1
|
||||
tmpGridPanelSize = -1
|
||||
}
|
||||
}
|
||||
|
||||
function animateTo(start: number, end: number, onUpdate: (newValue: number) => void) {
|
||||
const duration = 400
|
||||
const startTime = performance.now()
|
||||
|
||||
function animate(time: number) {
|
||||
const elapsed = time - startTime
|
||||
const progress = Math.min(elapsed / duration, 1)
|
||||
const currentValue = start + (end - start) * easeInOut(progress)
|
||||
onUpdate(currentValue)
|
||||
if (progress < 1) {
|
||||
requestAnimationFrame(animate)
|
||||
}
|
||||
}
|
||||
|
||||
requestAnimationFrame(animate)
|
||||
}
|
||||
|
||||
function easeInOut(t: number) {
|
||||
return t < 0.5 ? 2 * t * t : -1 + (4 - 2 * t) * t
|
||||
}
|
||||
|
||||
$: $cssEditorOpen && selectCss()
|
||||
|
||||
function selectCss() {
|
||||
selectedTab !== 'css' && (selectedTab = 'css')
|
||||
}
|
||||
|
||||
const cssId = 'wm-global-style'
|
||||
|
||||
$: addOrRemoveCss(true, $mode === 'preview')
|
||||
|
||||
let css: string | undefined = undefined
|
||||
|
||||
appStore.subscribe(async (currentAppStore) => {
|
||||
if (!currentAppStore.theme) {
|
||||
return
|
||||
}
|
||||
|
||||
if (currentAppStore.theme.type === 'inlined') {
|
||||
css = currentAppStore.theme.css
|
||||
} else if (currentAppStore.theme.type === 'path' && currentAppStore.theme?.path) {
|
||||
let loadedCss = await getTheme($workspaceStore!, currentAppStore.theme.path)
|
||||
css = loadedCss.value
|
||||
}
|
||||
})
|
||||
|
||||
$: updateCssContent(css, $previewTheme)
|
||||
|
||||
function addOrRemoveCss(isPremium: boolean, isPreview: boolean = false) {
|
||||
const existingElement = document.getElementById(cssId)
|
||||
|
||||
if (!isPremium && isPreview) {
|
||||
if (existingElement) {
|
||||
existingElement.remove()
|
||||
}
|
||||
} else {
|
||||
if (!existingElement) {
|
||||
const head = document.head
|
||||
const link = document.createElement('style')
|
||||
link.id = cssId
|
||||
link.innerHTML = css ?? ''
|
||||
head.appendChild(link)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function updateCssContent(cssString: string | undefined, previewTheme: string | undefined) {
|
||||
const theme = previewTheme ?? cssString ?? ''
|
||||
|
||||
const existingElement = document.getElementById(cssId)
|
||||
if (existingElement && theme !== existingElement.innerHTML) {
|
||||
existingElement.innerHTML = theme
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<DarkModeObserver on:change={onThemeChange} />
|
||||
@@ -220,7 +406,11 @@
|
||||
{#if $mode === 'preview'}
|
||||
<SplitPanesWrapper>
|
||||
<div
|
||||
class={twMerge('h-full w-full relative', $appStore.css?.['app']?.['viewer']?.class)}
|
||||
class={twMerge(
|
||||
'h-full w-full relative',
|
||||
$appStore.css?.['app']?.['viewer']?.class,
|
||||
'wm-app-viewer'
|
||||
)}
|
||||
style={$appStore.css?.['app']?.['viewer']?.style}
|
||||
>
|
||||
<AppPreview
|
||||
@@ -239,57 +429,70 @@
|
||||
{:else}
|
||||
<SplitPanesWrapper>
|
||||
<Splitpanes class="max-w-full overflow-hidden">
|
||||
<Pane size={15} minSize={5} maxSize={33}>
|
||||
<ContextPanel />
|
||||
<Pane bind:size={leftPanelSize} minSize={5} maxSize={33}>
|
||||
<div class="w-full h-full relative">
|
||||
<SecondaryMenu right={false} />
|
||||
<ContextPanel />
|
||||
</div>
|
||||
</Pane>
|
||||
<Pane size={63}>
|
||||
<SplitPanesWrapper>
|
||||
<Splitpanes horizontal>
|
||||
<Pane size={$connectingInput?.opened ? 100 : 70}>
|
||||
<Pane bind:size={centerPanelSize}>
|
||||
<Splitpanes horizontal class="overflow-hidden">
|
||||
<Pane bind:size={gridPanelSize}>
|
||||
<div
|
||||
on:pointerdown={(e) => {
|
||||
$selectedComponent = undefined
|
||||
$focusedGrid = undefined
|
||||
}}
|
||||
class={twMerge(
|
||||
'bg-surface h-full w-full relative',
|
||||
$appStore.css?.['app']?.['viewer']?.class,
|
||||
'wm-app-viewer'
|
||||
)}
|
||||
style={$appStore.css?.['app']?.['viewer']?.style}
|
||||
>
|
||||
<div id="app-editor-top-level-drawer" />
|
||||
<div
|
||||
on:pointerdown={(e) => {
|
||||
$selectedComponent = undefined
|
||||
$focusedGrid = undefined
|
||||
}}
|
||||
class={twMerge(
|
||||
'bg-surface h-full w-full relative',
|
||||
$appStore.css?.['app']?.['viewer']?.class
|
||||
class={classNames(
|
||||
'bg-surface-secondary/80 relative mx-auto w-full h-full overflow-auto',
|
||||
app.fullscreen ? '' : 'max-w-6xl'
|
||||
)}
|
||||
style={$appStore.css?.['app']?.['viewer']?.style}
|
||||
>
|
||||
<div id="app-editor-top-level-drawer" />
|
||||
<div
|
||||
class={classNames(
|
||||
'bg-surface-secondary/80 relative mx-auto w-full h-full overflow-auto',
|
||||
app.fullscreen ? '' : 'max-w-6xl'
|
||||
)}
|
||||
>
|
||||
{#if $appStore.grid}
|
||||
<ComponentNavigation />
|
||||
{#if $appStore.grid}
|
||||
<ComponentNavigation />
|
||||
|
||||
<div on:pointerdown|stopPropagation class={twMerge(width, 'mx-auto')}>
|
||||
<GridEditor {policy} />
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
<div on:pointerdown|stopPropagation class={twMerge(width, 'mx-auto')}>
|
||||
<GridEditor {policy} />
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
</Pane>
|
||||
{#if $connectingInput?.opened == false}
|
||||
<Pane bind:size={runnablePanelSize}>
|
||||
<div class="relative h-full w-full">
|
||||
<InlineScriptsPanel />
|
||||
</div>
|
||||
</Pane>
|
||||
{#if $connectingInput?.opened == false}
|
||||
<Pane size={$connectingInput?.opened ? 0 : 30}>
|
||||
<div class="relative h-full w-full">
|
||||
<InlineScriptsPanel />
|
||||
</div>
|
||||
</Pane>
|
||||
{/if}
|
||||
</Splitpanes>
|
||||
</SplitPanesWrapper>
|
||||
{/if}
|
||||
</Splitpanes>
|
||||
</Pane>
|
||||
<Pane size={22} minSize={15} maxSize={33}>
|
||||
<Pane bind:size={rightPanelSize} minSize={15} maxSize={33}>
|
||||
<div class="relative flex flex-col h-full">
|
||||
<Tabs bind:selected={selectedTab} wrapperClass="!min-h-[42px]" class="!h-full">
|
||||
<Popover disappearTimeout={0} notClickable placement="bottom">
|
||||
<svelte:fragment slot="text">Component library</svelte:fragment>
|
||||
<Tab value="insert" size="xs" class="h-full">
|
||||
<Tab
|
||||
value="insert"
|
||||
size="xs"
|
||||
class="h-full"
|
||||
on:pointerdown={() => {
|
||||
console.log('click', $cssEditorOpen)
|
||||
if ($cssEditorOpen) {
|
||||
$cssEditorOpen = false
|
||||
selectedTab = 'insert'
|
||||
}
|
||||
}}
|
||||
>
|
||||
<div class="m-1 center-center">
|
||||
<Plus size={18} />
|
||||
</div>
|
||||
@@ -297,7 +500,17 @@
|
||||
</Popover>
|
||||
<Popover disappearTimeout={0} notClickable placement="bottom">
|
||||
<svelte:fragment slot="text">Component settings</svelte:fragment>
|
||||
<Tab value="settings" size="xs" class="h-full">
|
||||
<Tab
|
||||
value="settings"
|
||||
size="xs"
|
||||
class="h-full"
|
||||
on:pointerdown={() => {
|
||||
if ($cssEditorOpen) {
|
||||
$cssEditorOpen = false
|
||||
selectedTab = 'settings'
|
||||
}
|
||||
}}
|
||||
>
|
||||
<div class="m-1 center-center">
|
||||
<Component size={18} />
|
||||
</div>
|
||||
@@ -305,7 +518,17 @@
|
||||
</Popover>
|
||||
<Popover disappearTimeout={0} notClickable placement="bottom">
|
||||
<svelte:fragment slot="text">Global styling</svelte:fragment>
|
||||
<Tab value="css" size="xs" class="h-full">
|
||||
<Tab
|
||||
value="css"
|
||||
size="xs"
|
||||
class="h-full"
|
||||
on:pointerdown={() => {
|
||||
if (!$cssEditorOpen) {
|
||||
$cssEditorOpen = true
|
||||
selectedTab = 'css'
|
||||
}
|
||||
}}
|
||||
>
|
||||
<div class="m-1 center-center">
|
||||
<Paintbrush size={18} />
|
||||
</div>
|
||||
@@ -315,7 +538,7 @@
|
||||
<TabContent class="overflow-auto h-full" value="settings">
|
||||
{#if $selectedComponent !== undefined}
|
||||
<SettingsPanel />
|
||||
<SecondaryMenu />
|
||||
<SecondaryMenu right />
|
||||
{:else}
|
||||
<div class="min-w-[150px] text-sm text-secondary text-center py-8 px-2">
|
||||
Select a component to see the settings for it
|
||||
|
||||
@@ -61,6 +61,7 @@
|
||||
import { sendUserToast } from '$lib/toast'
|
||||
import DeploymentHistory from './DeploymentHistory.svelte'
|
||||
import Awareness from '$lib/components/Awareness.svelte'
|
||||
import { secondaryMenuLeftStore, secondaryMenuRightStore } from './settingsPanel/secondaryMenu'
|
||||
|
||||
async function hash(message) {
|
||||
try {
|
||||
@@ -255,6 +256,9 @@
|
||||
}
|
||||
|
||||
async function save() {
|
||||
$secondaryMenuLeftStore.isOpen = false
|
||||
$secondaryMenuRightStore.isOpen = false
|
||||
|
||||
$dirtyStore = false
|
||||
saveDrawerOpen = true
|
||||
return
|
||||
|
||||
@@ -22,8 +22,9 @@
|
||||
import { deepEqual } from 'fast-equals'
|
||||
import { dfs } from './appUtils'
|
||||
import { BG_PREFIX, migrateApp } from '../utils'
|
||||
import { workspaceStore } from '$lib/stores'
|
||||
import { workspaceStore, enterpriseLicense } from '$lib/stores'
|
||||
import DarkModeObserver from '$lib/components/DarkModeObserver.svelte'
|
||||
import { getTheme } from './componentsPanel/themeUtils'
|
||||
|
||||
export let app: App
|
||||
export let appPath: string = ''
|
||||
@@ -93,7 +94,9 @@
|
||||
componentControl: writable({}),
|
||||
hoverStore: writable(undefined),
|
||||
allIdsInPath,
|
||||
darkMode
|
||||
darkMode,
|
||||
cssEditorOpen: writable(false),
|
||||
previewTheme: writable(undefined)
|
||||
})
|
||||
|
||||
let previousSelectedIds: string[] | undefined = undefined
|
||||
@@ -109,6 +112,47 @@
|
||||
function onThemeChange() {
|
||||
$darkMode = document.documentElement.classList.contains('dark')
|
||||
}
|
||||
const cssId = 'wm-global-style'
|
||||
|
||||
let css: string | undefined = undefined
|
||||
|
||||
appStore.subscribe(loadTheme)
|
||||
|
||||
async function loadTheme(currentAppStore: App) {
|
||||
console.log(currentAppStore)
|
||||
if (!currentAppStore.theme) {
|
||||
return
|
||||
}
|
||||
|
||||
if (currentAppStore.theme.type === 'inlined') {
|
||||
css = currentAppStore.theme.css
|
||||
} else if (currentAppStore.theme.type === 'path' && currentAppStore.theme.path) {
|
||||
let loadedCss = await getTheme(workspace, currentAppStore.theme.path)
|
||||
css = loadedCss.value
|
||||
}
|
||||
}
|
||||
|
||||
$: addOrRemoveCss($enterpriseLicense !== undefined || isEditor, css)
|
||||
|
||||
function addOrRemoveCss(isPremium: boolean, cssString: string | undefined) {
|
||||
const existingElement = document.getElementById(cssId)
|
||||
|
||||
if (!isPremium) {
|
||||
if (existingElement) {
|
||||
existingElement.remove()
|
||||
}
|
||||
} else {
|
||||
if (!existingElement && cssString) {
|
||||
const head = document.head
|
||||
const link = document.createElement('style')
|
||||
link.id = cssId
|
||||
link.innerHTML = cssString
|
||||
head.appendChild(link)
|
||||
} else if (existingElement && cssString) {
|
||||
existingElement.innerHTML = cssString
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<DarkModeObserver on:change={onThemeChange} />
|
||||
@@ -145,7 +189,11 @@
|
||||
|
||||
<div
|
||||
style={app.css?.['app']?.['grid']?.style}
|
||||
class={twMerge('px-4 pt-4 pb-2 overflow-visible', app.css?.['app']?.['grid']?.class ?? '')}
|
||||
class={twMerge(
|
||||
'px-4 pt-4 pb-2 overflow-visible',
|
||||
app.css?.['app']?.['grid']?.class ?? '',
|
||||
'wm-app-grid'
|
||||
)}
|
||||
bind:clientWidth={$parentWidth}
|
||||
>
|
||||
<div>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
import { Alert, Button } from '$lib/components/common'
|
||||
import { getContext } from 'svelte'
|
||||
import type { AppViewerContext } from '../types'
|
||||
import { secondaryMenu } from './settingsPanel/secondaryMenu'
|
||||
import { secondaryMenuRight } from './settingsPanel/secondaryMenu'
|
||||
|
||||
const { connectingInput } = getContext<AppViewerContext>('AppViewerContext')
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
$connectingInput.opened = false
|
||||
$connectingInput.input = undefined
|
||||
|
||||
secondaryMenu.close()
|
||||
secondaryMenuRight.close()
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
@@ -72,7 +72,11 @@
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<div
|
||||
style={$app.css?.['app']?.['grid']?.style}
|
||||
class={twMerge('px-4 pt-4 pb-2 overflow-visible', $app.css?.['app']?.['grid']?.class ?? '')}
|
||||
class={twMerge(
|
||||
'px-4 pt-4 pb-2 overflow-visible',
|
||||
$app.css?.['app']?.['grid']?.class ?? '',
|
||||
'wm-app-grid'
|
||||
)}
|
||||
on:pointerdown={() => {
|
||||
$selectedComponent = undefined
|
||||
$focusedGrid = undefined
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
import { getContext } from 'svelte'
|
||||
import type { App, AppViewerContext } from '../types'
|
||||
import { BG_PREFIX, allItems } from '../utils'
|
||||
import { findGridItem } from './appUtils'
|
||||
import { findComponentSettings, findGridItem } from './appUtils'
|
||||
import PanelSection from './settingsPanel/common/PanelSection.svelte'
|
||||
import ComponentPanel from './settingsPanel/ComponentPanel.svelte'
|
||||
import InputsSpecsEditor from './settingsPanel/InputsSpecsEditor.svelte'
|
||||
@@ -32,27 +32,6 @@
|
||||
})
|
||||
.find((x) => x)
|
||||
}
|
||||
|
||||
function findComponentSettings(app: App, id: string | undefined) {
|
||||
if (!id) return undefined
|
||||
if (app?.grid) {
|
||||
const gridItem = app.grid.find((x) => x.data?.id === id)
|
||||
if (gridItem) {
|
||||
return { item: gridItem, parent: undefined }
|
||||
}
|
||||
}
|
||||
|
||||
if (app?.subgrids) {
|
||||
for (const key of Object.keys(app.subgrids ?? {})) {
|
||||
const gridItem = app.subgrids[key].find((x) => x.data?.id === id)
|
||||
if (gridItem) {
|
||||
return { item: gridItem, parent: key }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return undefined
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if componentSettings}
|
||||
|
||||
@@ -27,6 +27,26 @@ import { deepMergeWithPriority } from '$lib/utils'
|
||||
import { sendUserToast } from '$lib/toast'
|
||||
import { getNextId } from '$lib/components/flows/idUtils'
|
||||
|
||||
export function findComponentSettings(app: App, id: string | undefined) {
|
||||
if (!id) return undefined
|
||||
if (app?.grid) {
|
||||
const gridItem = app.grid.find((x) => x.data?.id === id)
|
||||
if (gridItem) {
|
||||
return { item: gridItem, parent: undefined }
|
||||
}
|
||||
}
|
||||
|
||||
if (app?.subgrids) {
|
||||
for (const key of Object.keys(app.subgrids ?? {})) {
|
||||
const gridItem = app.subgrids[key].find((x) => x.data?.id === id)
|
||||
if (gridItem) {
|
||||
return { item: gridItem, parent: key }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return undefined
|
||||
}
|
||||
export function dfs(
|
||||
grid: GridItem[],
|
||||
id: string,
|
||||
@@ -261,7 +281,7 @@ export function appComponentFromType<T extends keyof typeof components>(
|
||||
panes: init.panes,
|
||||
tabs: init.tabs,
|
||||
conditions: init.conditions,
|
||||
customCss: {},
|
||||
customCss: ccomponents[type].customCss as any,
|
||||
recomputeIds: init.recomputeIds ? [] : undefined,
|
||||
actionButtons: init.actionButtons ? [] : undefined,
|
||||
numberOfSubgrids: init.numberOfSubgrids,
|
||||
|
||||
@@ -134,6 +134,7 @@
|
||||
$mode != 'preview' ? 'cursor-pointer' : '',
|
||||
'relative z-auto',
|
||||
$app.css?.['app']?.['component']?.class,
|
||||
'wm-app-component',
|
||||
ismoving ? 'animate-pulse' : ''
|
||||
)}
|
||||
style={$app.css?.['app']?.['component']?.style}
|
||||
@@ -591,6 +592,7 @@
|
||||
verticalAlignment={component.verticalAlignment}
|
||||
horizontalAlignment={component.horizontalAlignment}
|
||||
configuration={component.configuration}
|
||||
customCss={component.customCss}
|
||||
{render}
|
||||
/>
|
||||
{:else if component.type === 'chartjscomponent'}
|
||||
@@ -607,6 +609,7 @@
|
||||
id={component.id}
|
||||
configuration={component.configuration}
|
||||
componentInput={component.componentInput}
|
||||
customCss={component.customCss}
|
||||
{componentContainerHeight}
|
||||
{render}
|
||||
bind:initializing
|
||||
|
||||
@@ -480,7 +480,7 @@ export const components = {
|
||||
}
|
||||
},
|
||||
jobidlogcomponent: {
|
||||
name: 'Log',
|
||||
name: 'Log by Job Id',
|
||||
icon: Monitor,
|
||||
documentationLink: `${documentationBaseUrl}#log-display`,
|
||||
dims: '2:8-6:8' as AppComponentDimensions,
|
||||
@@ -538,7 +538,7 @@ export const components = {
|
||||
}
|
||||
},
|
||||
jobidflowstatuscomponent: {
|
||||
name: 'Flow Status',
|
||||
name: 'Flow Status by Job Id',
|
||||
icon: Monitor,
|
||||
documentationLink: `${documentationBaseUrl}#flow-status`,
|
||||
dims: '2:8-6:8' as AppComponentDimensions,
|
||||
@@ -651,7 +651,8 @@ export const components = {
|
||||
dims: '1:1-3:1' as AppComponentDimensions,
|
||||
documentationLink: `${documentationBaseUrl}#text`,
|
||||
customCss: {
|
||||
text: { class: '', style: '' }
|
||||
text: { class: '', style: '' },
|
||||
container: { class: '', style: '' }
|
||||
},
|
||||
initialData: {
|
||||
horizontalAlignment: 'left',
|
||||
@@ -1400,7 +1401,8 @@ This is a paragraph.
|
||||
documentationLink: `${documentationBaseUrl}#toggle`,
|
||||
dims: '1:1-2:1' as AppComponentDimensions,
|
||||
customCss: {
|
||||
text: { class: '', style: '' }
|
||||
text: { class: '', style: '' },
|
||||
container: { class: '', style: '' }
|
||||
},
|
||||
initialData: {
|
||||
...defaultAlignement,
|
||||
@@ -1455,9 +1457,7 @@ This is a paragraph.
|
||||
icon: TextCursorInput,
|
||||
documentationLink: `${documentationBaseUrl}#rich-text-editor`,
|
||||
dims: '2:1-4:4' as AppComponentDimensions,
|
||||
customCss: {
|
||||
input: { class: '', style: '' }
|
||||
},
|
||||
customCss: {},
|
||||
initialData: {
|
||||
componentInput: undefined,
|
||||
configuration: {
|
||||
@@ -1734,8 +1734,8 @@ This is a paragraph.
|
||||
documentationLink: `${documentationBaseUrl}#slider`,
|
||||
dims: '3:1-4:1' as AppComponentDimensions,
|
||||
customCss: {
|
||||
bar: { style: '' },
|
||||
handle: { style: '' },
|
||||
bar: { style: '', class: '' },
|
||||
handle: { style: '', class: '' },
|
||||
limits: { class: '', style: '' },
|
||||
value: { class: '', style: '' }
|
||||
},
|
||||
@@ -2171,7 +2171,8 @@ This is a paragraph.
|
||||
dims: '1:1-2:1' as AppComponentDimensions,
|
||||
customCss: {
|
||||
button: { style: '', class: '' },
|
||||
container: { class: '', style: '' }
|
||||
container: { class: '', style: '' },
|
||||
drawer: { class: '', style: '' }
|
||||
},
|
||||
initialData: {
|
||||
horizontalAlignment: 'center',
|
||||
@@ -2479,9 +2480,7 @@ This is a paragraph.
|
||||
documentationLink: `${documentationBaseUrl}#select-step`,
|
||||
dims: '2:1-3:1' as AppComponentDimensions,
|
||||
customCss: {
|
||||
tabRow: { class: '', style: '' },
|
||||
allTabs: { class: '', style: '' },
|
||||
selectedTab: { class: '', style: '' }
|
||||
container: { class: '', style: '' }
|
||||
},
|
||||
initialData: {
|
||||
verticalAlignment: 'center',
|
||||
|
||||
@@ -54,12 +54,15 @@
|
||||
|
||||
let search = ''
|
||||
|
||||
// Filter COMPONENT_SETS by search
|
||||
$: componentsFiltered = COMPONENT_SETS.map((set) => ({
|
||||
...set,
|
||||
components: set.components.filter((component) => {
|
||||
const name = componentsRecord[component].name.toLowerCase()
|
||||
return name.includes(search.toLowerCase())
|
||||
}),
|
||||
presets: set.presets?.filter((preset) => {
|
||||
const presetName = presetsRecord[preset].name.toLowerCase()
|
||||
return presetName.includes(search.toLowerCase())
|
||||
})
|
||||
}))
|
||||
</script>
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
<script lang="ts">
|
||||
import { getContext } from 'svelte'
|
||||
import type { AppViewerContext, RichConfiguration } from '../../types'
|
||||
import InputsSpecEditor from '../settingsPanel/InputsSpecEditor.svelte'
|
||||
|
||||
export let evalClass: RichConfiguration
|
||||
|
||||
const { selectedComponent } = getContext<AppViewerContext>('AppViewerContext')
|
||||
|
||||
$: id = Array.isArray($selectedComponent) ? $selectedComponent[0] : $selectedComponent
|
||||
</script>
|
||||
|
||||
{#if evalClass && id}
|
||||
<InputsSpecEditor
|
||||
bind:componentInput={evalClass}
|
||||
{id}
|
||||
userInputEnabled={false}
|
||||
shouldCapitalize={true}
|
||||
resourceOnly={false}
|
||||
fieldType="text"
|
||||
tooltip="Eval an expression that return a list of class as string to dynamically add classes to the component. The styling can then be dynamic using the global CSS Editor."
|
||||
customTitle="Dynamic class (eval)"
|
||||
displayType={false}
|
||||
placeholder={undefined}
|
||||
format={undefined}
|
||||
selectOptions={undefined}
|
||||
subFieldType={undefined}
|
||||
allowTypeChange={false}
|
||||
key=""
|
||||
/>
|
||||
{/if}
|
||||
@@ -0,0 +1,191 @@
|
||||
<script lang="ts">
|
||||
import { getContext } from 'svelte'
|
||||
import { LayoutDashboardIcon, ArrowUpSquare, ExternalLink, TextCursorInput } from 'lucide-svelte'
|
||||
import { Badge, Button, ClearableInput, Tab, TabContent, Tabs } from '../../../common'
|
||||
import type { AppViewerContext } from '../../types'
|
||||
import ListItem from './ListItem.svelte'
|
||||
import { ccomponents, components } from '../component'
|
||||
import { customisationByComponent } from './cssUtils'
|
||||
import DataTable from '$lib/components/table/DataTable.svelte'
|
||||
import Head from '$lib/components/table/Head.svelte'
|
||||
import Cell from '$lib/components/table/Cell.svelte'
|
||||
import Row from '$lib/components/table/Row.svelte'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
|
||||
const STATIC_ELEMENTS = ['app'] as const
|
||||
const TITLE_PREFIX = 'Css.' as const
|
||||
|
||||
type CustomCSSType = (typeof STATIC_ELEMENTS)[number] | keyof typeof components
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
interface CustomCSSEntry {
|
||||
type: CustomCSSType
|
||||
name: string
|
||||
icon: any
|
||||
ids: { id: string; forceStyle: boolean; forceClass: boolean }[]
|
||||
}
|
||||
|
||||
const { app } = getContext<AppViewerContext>('AppViewerContext')
|
||||
|
||||
const entries: CustomCSSEntry[] = [
|
||||
{
|
||||
type: 'app',
|
||||
name: 'App',
|
||||
icon: LayoutDashboardIcon,
|
||||
ids: ['viewer', 'grid', 'component'].map((id) => ({ id, forceStyle: true, forceClass: true }))
|
||||
},
|
||||
{
|
||||
type: 'quillcomponent',
|
||||
name: 'Rich Text Editor',
|
||||
icon: TextCursorInput,
|
||||
ids: ['q'].map((id) => ({ id, forceStyle: true, forceClass: true }))
|
||||
},
|
||||
...Object.entries(ccomponents)
|
||||
.filter(([key]) => key !== 'quillcomponent')
|
||||
.map(([type, { name, icon, customCss }]) => ({
|
||||
type: type as keyof typeof components,
|
||||
name,
|
||||
icon,
|
||||
ids: Object.entries(customCss).map(([id, v]) => ({
|
||||
id,
|
||||
forceStyle: v?.style != undefined,
|
||||
forceClass: v?.['class'] != undefined
|
||||
}))
|
||||
}))
|
||||
]
|
||||
|
||||
entries.sort((a, b) => a.name.localeCompare(b.name))
|
||||
|
||||
let search = ''
|
||||
</script>
|
||||
|
||||
<div class="p-2">
|
||||
<ClearableInput bind:value={search} placeholder="Search..." />
|
||||
</div>
|
||||
<div class="h-[calc(100%-50px)] overflow-auto relative">
|
||||
{#each search != '' ? entries.filter((x) => x.name
|
||||
.toLowerCase()
|
||||
.includes(search.toLowerCase())) : entries as { type, name, icon, ids } (name + type)}
|
||||
{#if ids.length > 0}
|
||||
<ListItem
|
||||
title={name}
|
||||
prefix={TITLE_PREFIX}
|
||||
on:open={(e) => {
|
||||
if ($app.css != undefined) {
|
||||
if (e.detail && $app.css[type] == undefined) {
|
||||
$app.css[type] = Object.fromEntries(ids.map(({ id }) => [id, {}]))
|
||||
}
|
||||
}
|
||||
}}
|
||||
>
|
||||
<div slot="title" class="flex items-center">
|
||||
<svelte:component this={icon} size={18} />
|
||||
<span class="ml-1">
|
||||
{name}
|
||||
</span>
|
||||
</div>
|
||||
<div class="py-2">
|
||||
{#each customisationByComponent.filter( (c) => c.components.includes(type) ) as customisation (customisation.components.join('-'))}
|
||||
{#if customisation.link}
|
||||
<a
|
||||
href={customisation.link}
|
||||
target="_blank"
|
||||
class="text-frost-500 dark:text-frost-300 font-semibold text-xs"
|
||||
>
|
||||
<div class="flex flex-row gap-2">
|
||||
See documentation
|
||||
<ExternalLink size="16" />
|
||||
</div>
|
||||
</a>
|
||||
{/if}
|
||||
|
||||
<Tabs selected="selectors">
|
||||
{#if customisation.selectors.length > 0}
|
||||
<Tab value="selectors" size="xs">
|
||||
Selectors ({customisation.selectors.length})
|
||||
</Tab>
|
||||
{/if}
|
||||
{#if customisation.variables.length > 0}
|
||||
<Tab value="variables" size="xs">
|
||||
<div class="flex flex-row gap-2 justify-center-center items-center">
|
||||
Variables ({customisation.variables.length})
|
||||
</div>
|
||||
</Tab>
|
||||
{/if}
|
||||
<div slot="content" class="h-full">
|
||||
<TabContent value="selectors" class="h-full mt-2">
|
||||
<DataTable size="sm">
|
||||
<Head>
|
||||
<tr>
|
||||
<Cell head first>Selector</Cell>
|
||||
<Cell head>Comment</Cell>
|
||||
<Cell head last />
|
||||
</tr>
|
||||
</Head>
|
||||
{#each customisation.selectors as { selector, comment }}
|
||||
<Row>
|
||||
<Cell first>
|
||||
<Badge color="gray">{selector}</Badge>
|
||||
</Cell>
|
||||
<Cell>
|
||||
{#if comment}
|
||||
<div class="max-w-24 whitespace-pre-wrap">{comment}</div>
|
||||
{/if}
|
||||
</Cell>
|
||||
<Cell>
|
||||
<Button
|
||||
size="xs2"
|
||||
color="light"
|
||||
on:click={() => {
|
||||
dispatch('insertSelector', `${selector} {}`)
|
||||
}}
|
||||
>
|
||||
<ArrowUpSquare size={16} />
|
||||
</Button>
|
||||
</Cell>
|
||||
</Row>
|
||||
{/each}
|
||||
</DataTable>
|
||||
</TabContent>
|
||||
<TabContent value="variables" class="h-full mt-2">
|
||||
<DataTable>
|
||||
<Head>
|
||||
<tr>
|
||||
<Cell head first>Variable</Cell>
|
||||
<Cell head>Default value</Cell>
|
||||
<Cell head>Comment</Cell>
|
||||
|
||||
<Cell head last />
|
||||
</tr>
|
||||
</Head>
|
||||
{#each customisation.variables as { variable, value, comment }}
|
||||
<Row>
|
||||
<Cell first>
|
||||
<Badge color="gray">{variable}</Badge>
|
||||
</Cell>
|
||||
<Cell>
|
||||
<Badge color="gray">{value}</Badge>
|
||||
</Cell>
|
||||
<Cell>
|
||||
{#if comment}
|
||||
<div class="max-w-24 whitespace-pre-wrap">{comment}</div>
|
||||
{/if}
|
||||
</Cell>
|
||||
<Cell>
|
||||
<Button size="xs2" color="light">
|
||||
<ArrowUpSquare size={16} />
|
||||
</Button>
|
||||
</Cell>
|
||||
</Row>
|
||||
{/each}
|
||||
</DataTable>
|
||||
</TabContent>
|
||||
</div>
|
||||
</Tabs>
|
||||
{/each}
|
||||
</div>
|
||||
</ListItem>
|
||||
{/if}
|
||||
{/each}
|
||||
</div>
|
||||
@@ -1,15 +1,18 @@
|
||||
<script lang="ts">
|
||||
import { Paintbrush2 } from 'lucide-svelte'
|
||||
import { Copy, MoveLeft, MoveRight, Paintbrush2 } from 'lucide-svelte'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import { fade } from 'svelte/transition'
|
||||
import { addWhitespaceBeforeCapitals } from '../../../../utils'
|
||||
import { addWhitespaceBeforeCapitals, copyToClipboard } from '../../../../utils'
|
||||
import { Button, ClearableInput } from '../../../common'
|
||||
import Popover from '../../../Popover.svelte'
|
||||
import type { ComponentCssProperty } from '../../types'
|
||||
import type { TypedComponent } from '../component'
|
||||
import { ccomponents, type TypedComponent } from '../component'
|
||||
import QuickStyleMenu from './QuickStyleMenu.svelte'
|
||||
import type { PropertyGroup } from './quickStyleProperties'
|
||||
import Tooltip from '$lib/components/Tooltip.svelte'
|
||||
import Badge from '$lib/components/common/badge/Badge.svelte'
|
||||
import Toggle from '$lib/components/Toggle.svelte'
|
||||
import CssEval from './CssEval.svelte'
|
||||
|
||||
export let name: string
|
||||
export let value: ComponentCssProperty = {}
|
||||
@@ -18,6 +21,11 @@
|
||||
export let quickStyleProperties: PropertyGroup[] | undefined = undefined
|
||||
export let componentType: TypedComponent['type'] | undefined = undefined
|
||||
export let tooltip: string | undefined = undefined
|
||||
export let shouldDisplayLeft: boolean = false
|
||||
export let shouldDisplayRight: boolean = false
|
||||
export let overriden: boolean = false
|
||||
export let overridding: boolean = false
|
||||
export let wmClass: string | undefined = undefined
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
let isQuickMenuOpen = false
|
||||
@@ -27,30 +35,90 @@
|
||||
function toggleQuickMenu() {
|
||||
isQuickMenuOpen = !isQuickMenuOpen
|
||||
}
|
||||
let dynamicClass: boolean = value.evalClass !== undefined
|
||||
</script>
|
||||
|
||||
<div
|
||||
class="sticky top-0 z-20 text-lg bg-surface-secondary font-semibold lowercase leading-none [font-variant:small-caps] text-secondary px-3 pb-1 mt-4 mb-1"
|
||||
>
|
||||
{addWhitespaceBeforeCapitals(name)}
|
||||
<div class=" border-b flex justify-between items-center p-2 text-xs leading-6 font-bold">
|
||||
<div class="flex flex-col gap-1 w-full items-start">
|
||||
<div class="flex flex-row h-8 items-center justify-between w-full">
|
||||
<div class="capitalize">
|
||||
{addWhitespaceBeforeCapitals(name)}
|
||||
</div>
|
||||
{#if shouldDisplayLeft}
|
||||
<Button
|
||||
color="light"
|
||||
size="xs2"
|
||||
variant="border"
|
||||
on:click={() => {
|
||||
dispatch('left')
|
||||
}}
|
||||
>
|
||||
<div class="flex flex-row gap-2 text-2xs items-center">
|
||||
<MoveLeft size={14} />
|
||||
Copy for this component
|
||||
</div>
|
||||
</Button>
|
||||
{/if}
|
||||
{#if shouldDisplayRight}
|
||||
<Button
|
||||
color="light"
|
||||
size="xs2"
|
||||
variant="border"
|
||||
on:click={() => {
|
||||
dispatch('right')
|
||||
}}
|
||||
>
|
||||
<div class="flex flex-row gap-2 text-2xs items-center">
|
||||
Copy for every {componentType ? ccomponents[componentType].name : 'component'}
|
||||
<MoveRight size={14} />
|
||||
</div>
|
||||
</Button>
|
||||
{/if}
|
||||
</div>
|
||||
{#if wmClass}
|
||||
<Badge small>
|
||||
<div class="flex flex-row gap-1 items-center">
|
||||
{wmClass}
|
||||
<Button
|
||||
color="light"
|
||||
size="xs2"
|
||||
on:click={() => {
|
||||
copyToClipboard(wmClass)
|
||||
}}
|
||||
>
|
||||
<Copy size={14} />
|
||||
</Button>
|
||||
</div>
|
||||
</Badge>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{#if value}
|
||||
<div class="px-3">
|
||||
<div class="p-2">
|
||||
{#if tooltip}
|
||||
<div class="text-tertiary text-2xs py-2">{tooltip}</div>
|
||||
{/if}
|
||||
{#if value.style !== undefined || forceStyle}
|
||||
<div class="pb-2">
|
||||
<!-- svelte-ignore a11y-label-has-associated-control -->
|
||||
<label class="block">
|
||||
<div class="text-sm font-medium text-tertiary pb-0.5"> Plain CSS </div>
|
||||
<label class="block w-full">
|
||||
<div class="flex flex-row justify-between items-center w-full h-8">
|
||||
<div class="text-xs font-medium text-tertiary"> Plain CSS </div>
|
||||
{#if overriden}
|
||||
<Badge color="red" small>Overriden by local</Badge>
|
||||
{:else if overridding}
|
||||
<Badge color="blue" small>Overriding global</Badge>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<div class="flex gap-1">
|
||||
<div class="relative grow">
|
||||
<ClearableInput
|
||||
bind:value={value.style}
|
||||
type="textarea"
|
||||
wrapperClass="h-full min-h-[72px]"
|
||||
inputClass="h-full"
|
||||
inputClass="h-full !text-xs !rounded-none !p-2"
|
||||
/>
|
||||
</div>
|
||||
<div class="flex flex-col gap-1">
|
||||
@@ -88,18 +156,44 @@
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if value.class !== undefined || forceClass}
|
||||
<!-- svelte-ignore a11y-label-has-associated-control -->
|
||||
<label class="block">
|
||||
<div class="text-sm font-medium text-tertiary pb-0.5">
|
||||
Tailwind classes<Tooltip documentationLink="https://tailwindcss.com/"
|
||||
>Use any tailwind classes to style your component</Tooltip
|
||||
></div
|
||||
>
|
||||
<div class="text-xs font-medium text-tertiary">
|
||||
Tailwind classes
|
||||
<Tooltip light documentationLink="https://tailwindcss.com/">
|
||||
Use any tailwind classes to style your component
|
||||
</Tooltip>
|
||||
</div>
|
||||
<div class="relative">
|
||||
<ClearableInput bind:value={value.class} />
|
||||
</div>
|
||||
</label>
|
||||
{/if}
|
||||
|
||||
<Toggle
|
||||
options={{
|
||||
right: 'Use dynamic class'
|
||||
}}
|
||||
size="xs"
|
||||
bind:checked={dynamicClass}
|
||||
on:change={(e) => {
|
||||
if (e.detail && !value.evalClass) {
|
||||
value.evalClass = {
|
||||
type: 'evalv2',
|
||||
expr: '',
|
||||
connections: [],
|
||||
fieldType: 'text'
|
||||
}
|
||||
} else {
|
||||
value.evalClass = undefined
|
||||
}
|
||||
}}
|
||||
/>
|
||||
|
||||
{#if value.evalClass && dynamicClass}
|
||||
<CssEval bind:evalClass={value.evalClass} />
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
@@ -1,147 +1,122 @@
|
||||
<script lang="ts">
|
||||
import { getContext } from 'svelte'
|
||||
import { LayoutDashboardIcon, MousePointer2, CurlyBraces } from 'lucide-svelte'
|
||||
import { AlertTriangle, GitBranch } from 'lucide-svelte'
|
||||
import SimpleEditor from '$lib/components/SimpleEditor.svelte'
|
||||
import { emptyString } from '$lib/utils'
|
||||
import { ClearableInput, Tab, TabContent, Tabs } from '../../../common'
|
||||
import type { AppViewerContext } from '../../types'
|
||||
import ListItem from './ListItem.svelte'
|
||||
import CssProperty from './CssProperty.svelte'
|
||||
import { ccomponents, components } from '../component'
|
||||
import { slide } from 'svelte/transition'
|
||||
|
||||
const STATIC_ELEMENTS = ['app'] as const
|
||||
const TITLE_PREFIX = 'Css.' as const
|
||||
|
||||
type CustomCSSType = (typeof STATIC_ELEMENTS)[number] | keyof typeof components
|
||||
|
||||
interface CustomCSSEntry {
|
||||
type: CustomCSSType
|
||||
name: string
|
||||
icon: any
|
||||
ids: { id: string; forceStyle: boolean; forceClass: boolean }[]
|
||||
}
|
||||
import { Pane, Splitpanes } from 'svelte-splitpanes'
|
||||
import CssHelperPanel from './CssHelperPanel.svelte'
|
||||
import { enterpriseLicense, workspaceStore } from '$lib/stores'
|
||||
import Tooltip from '$lib/components/Tooltip.svelte'
|
||||
import { Button, Drawer, DrawerContent, Tab, Tabs } from '$lib/components/common'
|
||||
import ThemeList from './ThemeList.svelte'
|
||||
import SplitPanesWrapper from '$lib/components/splitPanes/SplitPanesWrapper.svelte'
|
||||
import { resolveTheme } from './themeUtils'
|
||||
import ThemeCodePreview from './ThemeCodePreview.svelte'
|
||||
import { sendUserToast } from '$lib/toast'
|
||||
|
||||
const { app } = getContext<AppViewerContext>('AppViewerContext')
|
||||
|
||||
let rawCode = ''
|
||||
let cssEditor: SimpleEditor | undefined = undefined
|
||||
let alertHeight: number | undefined = undefined
|
||||
let themeViewer: any = undefined
|
||||
let selectedTab: 'css' | 'theme' = 'css'
|
||||
|
||||
$: rawCode && parseJson()
|
||||
let jsonError = ''
|
||||
let jsonErrorHeight: number
|
||||
|
||||
function parseJson() {
|
||||
try {
|
||||
$app.css = JSON.parse(rawCode ?? '')
|
||||
jsonError = ''
|
||||
} catch (e) {
|
||||
jsonError = e.message
|
||||
function insertSelector(selector: string) {
|
||||
if ($app?.theme?.type === 'path') {
|
||||
sendUserToast(
|
||||
'You cannot edit the theme because it is a path theme. Fork the theme to edit it.',
|
||||
true
|
||||
)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
function switchTab(asJson: boolean) {
|
||||
if (asJson) {
|
||||
rawCode = JSON.stringify($app.css, null, 2)
|
||||
} else {
|
||||
parseJson()
|
||||
}
|
||||
const code = cssEditor?.getCode()
|
||||
cssEditor?.setCode(code + '\n' + selector)
|
||||
$app = $app
|
||||
}
|
||||
|
||||
const entries: CustomCSSEntry[] = [
|
||||
{
|
||||
type: 'app',
|
||||
name: 'App',
|
||||
icon: LayoutDashboardIcon,
|
||||
ids: ['viewer', 'grid', 'component'].map((id) => ({ id, forceStyle: true, forceClass: true }))
|
||||
},
|
||||
...Object.entries(ccomponents).map(([type, { name, icon, customCss }]) => ({
|
||||
type: type as keyof typeof components,
|
||||
name,
|
||||
icon,
|
||||
ids: Object.entries(customCss).map(([id, v]) => ({
|
||||
id,
|
||||
forceStyle: v?.style != undefined,
|
||||
forceClass: v?.['class'] != undefined
|
||||
}))
|
||||
}))
|
||||
]
|
||||
entries.sort((a, b) => a.name.localeCompare(b.name))
|
||||
let search = ''
|
||||
</script>
|
||||
|
||||
<!-- <div class="w-full text-lg font-semibold text-center text-tertiary p-2">Global Styling</div> -->
|
||||
<Tabs selected="ui" on:selected={(e) => switchTab(e.detail === 'json')} class="h-full">
|
||||
<Tab value="ui" size="xs" class="w-1/2">
|
||||
<div class="m-1 center-center">
|
||||
<MousePointer2 size={16} />
|
||||
<span class="pl-1">UI</span>
|
||||
</div>
|
||||
</Tab>
|
||||
<Tab value="json" size="xs" class="w-1/2">
|
||||
<div class="m-1 center-center">
|
||||
<CurlyBraces size={16} />
|
||||
<span class="pl-1">JSON</span>
|
||||
</div>
|
||||
</Tab>
|
||||
<div slot="content" class="h-[calc(100%-35px)] overflow-auto">
|
||||
<TabContent value="ui" class="h-full">
|
||||
<div class="p-2">
|
||||
<ClearableInput bind:value={search} placeholder="Search..." />
|
||||
</div>
|
||||
<div class="h-[calc(100%-50px)] overflow-auto relative">
|
||||
{#each search != '' ? entries.filter((x) => x.name
|
||||
.toLowerCase()
|
||||
.includes(search.toLowerCase())) : entries as { type, name, icon, ids } (name + type)}
|
||||
{#if ids.length > 0}
|
||||
<ListItem
|
||||
title={name}
|
||||
prefix={TITLE_PREFIX}
|
||||
on:open={(e) => {
|
||||
if ($app.css != undefined) {
|
||||
if (e.detail && $app.css[type] == undefined) {
|
||||
$app.css[type] = Object.fromEntries(ids.map(({ id }) => [id, {}]))
|
||||
}
|
||||
}
|
||||
}}
|
||||
>
|
||||
<div slot="title" class="flex items-center">
|
||||
<svelte:component this={icon} size={18} />
|
||||
<span class="ml-1">
|
||||
{name}
|
||||
</span>
|
||||
</div>
|
||||
<div class="pb-2">
|
||||
{#each ids as { id, forceStyle, forceClass }}
|
||||
<div class="mb-3">
|
||||
{#if $app?.css?.[type]}
|
||||
<CssProperty
|
||||
{forceClass}
|
||||
{forceStyle}
|
||||
name={id}
|
||||
bind:value={$app.css[type][id]}
|
||||
/>
|
||||
{/if}
|
||||
<Drawer bind:this={themeViewer} size="800px">
|
||||
<DrawerContent title="View themes" on:close={themeViewer.closeDrawer}>sa</DrawerContent>
|
||||
</Drawer>
|
||||
|
||||
<Tabs bind:selected={selectedTab}>
|
||||
<Tab size="xs" value="css">Code</Tab>
|
||||
<Tab size="xs" value="theme">Theme</Tab>
|
||||
<svelte:fragment slot="content">
|
||||
{#if selectedTab === 'css'}
|
||||
<SplitPanesWrapper>
|
||||
<Splitpanes horizontal>
|
||||
<Pane size={60}>
|
||||
{#if $enterpriseLicense === undefined}
|
||||
<div bind:clientHeight={alertHeight} class="p-2 flex flex-row gap-2">
|
||||
<div class="flex flex-row items-center text-yellow-500 text-xs">
|
||||
<div class="flex">
|
||||
<AlertTriangle size={16} />
|
||||
EE only
|
||||
</div>
|
||||
{/each}
|
||||
<Tooltip>
|
||||
App CSS editor is an exclusive feature of the Enterprise Edition. You can
|
||||
experiment with this feature in the editor, but please note that the changes
|
||||
will not be visible once deployed.
|
||||
</Tooltip>
|
||||
</div>
|
||||
<div class="flex flex-row items-center text-blue-500 text-xs">
|
||||
Component styling is still available in the Community Edition
|
||||
<Tooltip>
|
||||
You can still style components individually in the Community Edition.
|
||||
</Tooltip>
|
||||
</div>
|
||||
</div>
|
||||
</ListItem>
|
||||
{/if}
|
||||
{/each}
|
||||
</div>
|
||||
</TabContent>
|
||||
<TabContent value="json" class="h-full">
|
||||
{#if !emptyString(jsonError)}
|
||||
<div
|
||||
transition:slide={{ duration: 200 }}
|
||||
bind:clientHeight={jsonErrorHeight}
|
||||
class="text-red-500 text-xs p-1"
|
||||
>
|
||||
{jsonError}
|
||||
</div>
|
||||
{/if}
|
||||
<div style="height: calc(100% - {jsonErrorHeight || 0}px);">
|
||||
<SimpleEditor class="h-full" lang="json" bind:code={rawCode} fixedOverflowWidgets={false} />
|
||||
</div>
|
||||
</TabContent>
|
||||
</div>
|
||||
{/if}
|
||||
<div style="height: calc(100% - {alertHeight || 0}px);">
|
||||
{#if $app.theme?.type === 'inlined'}
|
||||
<SimpleEditor
|
||||
class="h-full"
|
||||
lang="css"
|
||||
bind:code={$app.theme.css}
|
||||
fixedOverflowWidgets={false}
|
||||
small
|
||||
automaticLayout
|
||||
bind:this={cssEditor}
|
||||
deno={false}
|
||||
/>
|
||||
{:else}
|
||||
<ThemeCodePreview theme={$app.theme}>
|
||||
<div class="p-2 w-min">
|
||||
<Button
|
||||
size="xs"
|
||||
color="dark"
|
||||
on:click={async () => {
|
||||
const theme = await resolveTheme($app.theme, $workspaceStore)
|
||||
$app.theme = {
|
||||
type: 'inlined',
|
||||
css: theme
|
||||
}
|
||||
}}
|
||||
>
|
||||
<div class="flex flex-row gap-2 items-center">
|
||||
<GitBranch size={16} />
|
||||
Fork theme to edit
|
||||
</div>
|
||||
</Button>
|
||||
</div>
|
||||
</ThemeCodePreview>
|
||||
{/if}
|
||||
</div>
|
||||
</Pane>
|
||||
<Pane size={40}>
|
||||
<CssHelperPanel on:insertSelector={(e) => insertSelector(e.detail)} />
|
||||
</Pane>
|
||||
</Splitpanes>
|
||||
</SplitPanesWrapper>
|
||||
{/if}
|
||||
{#if selectedTab === 'theme'}
|
||||
<ThemeList
|
||||
on:setCodeTab={() => {
|
||||
selectedTab = 'css'
|
||||
}}
|
||||
/>
|
||||
{/if}
|
||||
</svelte:fragment>
|
||||
</Tabs>
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
<script lang="ts">
|
||||
import type { AppTheme } from '../../types'
|
||||
import { resolveTheme } from './themeUtils'
|
||||
import { workspaceStore } from '$lib/stores'
|
||||
import { onMount } from 'svelte'
|
||||
import SimpleEditor from '$lib/components/SimpleEditor.svelte'
|
||||
|
||||
export let theme: AppTheme | undefined = undefined
|
||||
|
||||
let code: string | undefined = undefined
|
||||
let cssEditor: SimpleEditor | undefined = undefined
|
||||
|
||||
onMount(async () => {
|
||||
code = await resolveTheme(theme, $workspaceStore)
|
||||
cssEditor?.setCode(code)
|
||||
})
|
||||
</script>
|
||||
|
||||
<div class="relative h-full">
|
||||
<div class="absolute z-[100] left-[50%] top-[50%] translate-x-[-50%] translate-y-[-50%]">
|
||||
<slot />
|
||||
</div>
|
||||
<div class="absolute top-0 left-0 right-0 bottom-0 bg-gray-100 bg-opacity-50 z-50" />
|
||||
<SimpleEditor
|
||||
class="h-full"
|
||||
lang="css"
|
||||
{code}
|
||||
fixedOverflowWidgets={false}
|
||||
small
|
||||
automaticLayout
|
||||
bind:this={cssEditor}
|
||||
deno={false}
|
||||
/>
|
||||
</div>
|
||||
@@ -0,0 +1,26 @@
|
||||
<script lang="ts">
|
||||
import { Drawer, DrawerContent } from '$lib/components/common'
|
||||
import { Highlight } from 'svelte-highlight'
|
||||
import type { AppTheme } from '../../types'
|
||||
import css from 'svelte-highlight/languages/css'
|
||||
import { resolveTheme } from './themeUtils'
|
||||
import { workspaceStore } from '$lib/stores'
|
||||
|
||||
export let theme: AppTheme
|
||||
|
||||
let code: string | undefined = undefined
|
||||
let codeDrawer: Drawer
|
||||
|
||||
export async function openDrawer() {
|
||||
codeDrawer.openDrawer()
|
||||
code = await resolveTheme(theme, $workspaceStore)
|
||||
}
|
||||
</script>
|
||||
|
||||
<Drawer bind:this={codeDrawer}>
|
||||
<DrawerContent title="Theme viewer" on:close={codeDrawer.closeDrawer}>
|
||||
<div class="p-2 border rounded-sm">
|
||||
<Highlight code={code ?? ''} language={css} />
|
||||
</div>
|
||||
</DrawerContent>
|
||||
</Drawer>
|
||||
@@ -0,0 +1,159 @@
|
||||
<script lang="ts">
|
||||
import { listThemes, type Theme, createTheme } from './themeUtils'
|
||||
import { enterpriseLicense, workspaceStore } from '$lib/stores'
|
||||
import { getContext, onMount } from 'svelte'
|
||||
import Button from '$lib/components/common/button/Button.svelte'
|
||||
import DataTable from '$lib/components/table/DataTable.svelte'
|
||||
import Head from '$lib/components/table/Head.svelte'
|
||||
import Cell from '$lib/components/table/Cell.svelte'
|
||||
|
||||
import { EyeOff } from 'lucide-svelte'
|
||||
import type { AppViewerContext } from '../../types'
|
||||
import { sendUserToast } from '$lib/toast'
|
||||
import { ResourceService } from '$lib/gen'
|
||||
import { Alert } from '$lib/components/common'
|
||||
import ThemeRow from './ThemeRow.svelte'
|
||||
import { onDestroy } from 'svelte'
|
||||
import Skeleton from '$lib/components/common/skeleton/Skeleton.svelte'
|
||||
|
||||
const { previewTheme, app } = getContext<AppViewerContext>('AppViewerContext')
|
||||
|
||||
let cssString: string | undefined = $app?.theme?.type === 'inlined' ? $app.theme.css : undefined
|
||||
$: type = $app?.theme?.type
|
||||
|
||||
let themes: Array<{
|
||||
name: string
|
||||
path: string
|
||||
}> = []
|
||||
|
||||
let loading: boolean = false
|
||||
|
||||
async function getThemes() {
|
||||
loading = true
|
||||
themes = await listThemes($workspaceStore!)
|
||||
loading = false
|
||||
}
|
||||
|
||||
async function addTheme(nameField: string) {
|
||||
const themes = await ResourceService.listResourceNames({
|
||||
workspace: $workspaceStore!,
|
||||
name: 'app_theme'
|
||||
})
|
||||
|
||||
const theme: Theme = {
|
||||
path: 'f/app_themes/theme_' + themes.length,
|
||||
value: {
|
||||
value: cssString ?? '',
|
||||
name: nameField
|
||||
}
|
||||
}
|
||||
|
||||
const message = await createTheme($workspaceStore!, theme)
|
||||
|
||||
getThemes()
|
||||
|
||||
nameField = ''
|
||||
|
||||
sendUserToast('Theme created:' + message)
|
||||
|
||||
$app.theme = {
|
||||
type: 'path',
|
||||
path: theme.path
|
||||
}
|
||||
}
|
||||
|
||||
let nameField: string = ''
|
||||
let previewThemePath: string | undefined = undefined
|
||||
|
||||
onMount(() => {
|
||||
getThemes()
|
||||
})
|
||||
|
||||
onDestroy(() => {
|
||||
previewTheme.set(undefined)
|
||||
previewThemePath = undefined
|
||||
})
|
||||
</script>
|
||||
|
||||
<div class="p-4 flex flex-col items-start w-auto gap-2 relative">
|
||||
{#if $enterpriseLicense === undefined}
|
||||
<div class="absolute top-0 left-0 w-full h-full bg-gray-50 opacity-50 z-10 bottom-0" />
|
||||
<Alert
|
||||
type="warning"
|
||||
title="Themes are available in the enterprise edition."
|
||||
class="w-full z-50"
|
||||
size="xs"
|
||||
>
|
||||
Upgrade to the enterprise edition to use themes.
|
||||
</Alert>
|
||||
{/if}
|
||||
<div class="w-full flex flex-row gap-2 items-center">
|
||||
<input
|
||||
disabled={type != 'inlined'}
|
||||
bind:value={nameField}
|
||||
placeholder={type == 'inlined'
|
||||
? 'Theme name'
|
||||
: 'Fork a theme and edit it to create a new one'}
|
||||
/>
|
||||
<Button
|
||||
disabled={type != 'inlined' || nameField == ''}
|
||||
on:click={() => addTheme(nameField)}
|
||||
color="dark"
|
||||
size="xs">Create theme</Button
|
||||
>
|
||||
</div>
|
||||
|
||||
{#if loading}
|
||||
<div class="flex flex-col w-full pt-12">
|
||||
{#each new Array(6) as _}
|
||||
<Skeleton layout={[[2], 0.5]} />
|
||||
{/each}
|
||||
</div>
|
||||
{:else if Array.isArray(themes) && themes.length > 0}
|
||||
<div class="flex flex-row justify-end items-center w-full h-10">
|
||||
{#if $previewTheme != undefined}
|
||||
<Button
|
||||
color="dark"
|
||||
variant="border"
|
||||
size="xs"
|
||||
on:click={() => {
|
||||
previewTheme.set(undefined)
|
||||
previewThemePath = undefined
|
||||
}}
|
||||
>
|
||||
<div class="flex flex-row gap-1 items-center">
|
||||
<EyeOff size={16} />
|
||||
Clear preview
|
||||
</div>
|
||||
</Button>
|
||||
{/if}
|
||||
</div>
|
||||
<div class="w-full">
|
||||
<DataTable size="sm">
|
||||
<Head>
|
||||
<tr>
|
||||
<Cell first head>Path</Cell>
|
||||
<Cell last head />
|
||||
</tr>
|
||||
</Head>
|
||||
<tbody class="divide-y">
|
||||
{#if themes && themes.length > 0}
|
||||
{#each themes as row}
|
||||
{#key row}
|
||||
<ThemeRow
|
||||
{row}
|
||||
bind:previewThemePath
|
||||
on:reloadThemes={() => {
|
||||
getThemes()
|
||||
}}
|
||||
/>
|
||||
{/key}
|
||||
{/each}
|
||||
{:else}
|
||||
<tr>Loading...</tr>
|
||||
{/if}
|
||||
</tbody>
|
||||
</DataTable>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
@@ -0,0 +1,57 @@
|
||||
<script lang="ts">
|
||||
import { updateTheme } from './themeUtils'
|
||||
import { workspaceStore } from '$lib/stores'
|
||||
import Button from '$lib/components/common/button/Button.svelte'
|
||||
|
||||
import { Popup } from '$lib/components/common'
|
||||
import { Pen } from 'lucide-svelte'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import { sendUserToast } from '$lib/toast'
|
||||
|
||||
export let row: {
|
||||
name: string
|
||||
path: string
|
||||
}
|
||||
|
||||
let editedName = row.name
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
</script>
|
||||
|
||||
<Popup
|
||||
floatingConfig={{ strategy: 'absolute', placement: 'bottom-end' }}
|
||||
containerClasses="border rounded-lg shadow-lg p-4 bg-surface"
|
||||
let:close
|
||||
>
|
||||
<svelte:fragment slot="button">
|
||||
<Button color="light" size="xs2" nonCaptureEvent={true}>
|
||||
<div class="flex flex-row gap-1 items-center">
|
||||
<Pen size={16} />
|
||||
</div>
|
||||
</Button>
|
||||
</svelte:fragment>
|
||||
<div class="flex flex-col w-80 gap-2">
|
||||
<div class="leading-6 font-semibold text-xs">Edit theme name</div>
|
||||
<div class="flex flex-row gap-2">
|
||||
<input bind:value={editedName} />
|
||||
<Button
|
||||
color="dark"
|
||||
size="xs"
|
||||
on:click={async () => {
|
||||
if (!$workspaceStore) return
|
||||
await updateTheme($workspaceStore, row.path, {
|
||||
value: {
|
||||
...row,
|
||||
name: editedName
|
||||
}
|
||||
})
|
||||
dispatch('reloadThemes')
|
||||
close(null)
|
||||
sendUserToast('Theme name updated:\n' + editedName)
|
||||
}}
|
||||
>
|
||||
Update
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</Popup>
|
||||
@@ -0,0 +1,215 @@
|
||||
<script lang="ts">
|
||||
import { deleteTheme, getTheme, updateTheme, resolveTheme, DEFAULT_THEME } from './themeUtils'
|
||||
import { workspaceStore } from '$lib/stores'
|
||||
import { getContext } from 'svelte'
|
||||
import Button from '$lib/components/common/button/Button.svelte'
|
||||
import Cell from '$lib/components/table/Cell.svelte'
|
||||
|
||||
import { Code, Eye, GitBranch, Pin, Save, Trash } from 'lucide-svelte'
|
||||
import type { AppViewerContext } from '../../types'
|
||||
import { sendUserToast } from '$lib/toast'
|
||||
import Badge from '$lib/components/common/badge/Badge.svelte'
|
||||
import { twMerge } from 'tailwind-merge'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import ThemeNameEditor from './ThemeNameEditor.svelte'
|
||||
|
||||
import ThemeDrawer from './ThemeDrawer.svelte'
|
||||
import ButtonDropdown from '$lib/components/common/button/ButtonDropdown.svelte'
|
||||
import { classNames } from '$lib/utils'
|
||||
import { MenuItem } from '@rgossiaux/svelte-headlessui'
|
||||
|
||||
export let previewThemePath: string | undefined = undefined
|
||||
|
||||
export let row: {
|
||||
name: string
|
||||
path: string
|
||||
}
|
||||
|
||||
const { previewTheme, app } = getContext<AppViewerContext>('AppViewerContext')
|
||||
|
||||
let cssString: string | undefined = $app?.theme?.type === 'inlined' ? $app.theme.css : undefined
|
||||
$: type = $app?.theme?.type
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
async function toggleUpdate(row) {
|
||||
if (!$workspaceStore) return
|
||||
|
||||
try {
|
||||
await updateTheme($workspaceStore, row.path, {
|
||||
value: {
|
||||
name: row.name,
|
||||
value: cssString ?? ''
|
||||
}
|
||||
})
|
||||
|
||||
$app.theme = {
|
||||
type: 'path',
|
||||
path: row.path
|
||||
}
|
||||
|
||||
sendUserToast('Theme updated:\n' + row.name)
|
||||
} catch (e) {
|
||||
sendUserToast('Theme update failed:\n' + e)
|
||||
}
|
||||
}
|
||||
|
||||
async function makeDefaultTheme(path: string) {
|
||||
const defaultTheme = await getTheme($workspaceStore!, DEFAULT_THEME)
|
||||
const theme = await getTheme($workspaceStore!, path)
|
||||
|
||||
await updateTheme($workspaceStore!, DEFAULT_THEME, {
|
||||
value: theme.value
|
||||
})
|
||||
|
||||
await updateTheme($workspaceStore!, path, {
|
||||
value: defaultTheme.value
|
||||
})
|
||||
|
||||
dispatch('reloadThemes')
|
||||
}
|
||||
|
||||
async function toggleDelete() {
|
||||
stopPreview()
|
||||
if ($workspaceStore) {
|
||||
await deleteTheme($workspaceStore, row.path)
|
||||
}
|
||||
dispatch('reloadThemes')
|
||||
sendUserToast('Theme deleted:\n' + row.name)
|
||||
}
|
||||
|
||||
async function preview() {
|
||||
previewThemePath = row.path
|
||||
|
||||
const theme = await resolveTheme(
|
||||
{
|
||||
type: 'path',
|
||||
path: row.path ?? ''
|
||||
},
|
||||
$workspaceStore
|
||||
)
|
||||
$previewTheme = theme ?? ''
|
||||
}
|
||||
|
||||
async function fork() {
|
||||
stopPreview()
|
||||
const theme = await resolveTheme($app.theme, $workspaceStore)
|
||||
$app.theme = {
|
||||
type: 'inlined',
|
||||
css: theme
|
||||
}
|
||||
dispatch('setCodeTab')
|
||||
}
|
||||
|
||||
function stopPreview() {
|
||||
previewThemePath = undefined
|
||||
$previewTheme = undefined
|
||||
}
|
||||
|
||||
function apply() {
|
||||
stopPreview()
|
||||
$app.theme = {
|
||||
type: 'path',
|
||||
path: row.path ?? ''
|
||||
}
|
||||
}
|
||||
|
||||
let themeDrawer: ThemeDrawer
|
||||
</script>
|
||||
|
||||
<tr class={twMerge(previewThemePath === row.path ? 'bg-blue-200' : '', 'transition-all')}>
|
||||
<Cell first>
|
||||
<div class="flex flex-row gap-1 items-center">
|
||||
<ThemeNameEditor on:reloadThemes {row} />
|
||||
{row.name}
|
||||
</div>
|
||||
</Cell>
|
||||
|
||||
<Cell last>
|
||||
<div class={twMerge('flex flex-row gap-1 justify-end ')}>
|
||||
{#if row.path === DEFAULT_THEME}
|
||||
<Badge color="blue">Default theme</Badge>
|
||||
{/if}
|
||||
|
||||
{#if $app?.theme?.type === 'path' && $app.theme.path === row.path}
|
||||
<Badge color="green">Active</Badge>
|
||||
{/if}
|
||||
|
||||
{#if type === 'inlined'}
|
||||
<Button color="light" size="xs" on:click={() => toggleUpdate(row)}>
|
||||
<div class="flex flex-row gap-1 items-center">
|
||||
<Save size={16} />
|
||||
Update
|
||||
</div>
|
||||
</Button>
|
||||
{/if}
|
||||
{#if $app?.theme?.type !== 'path' || $app.theme.path !== row.path}
|
||||
<Button color="light" size="xs" on:click={preview}>
|
||||
<div class="flex flex-row gap-1 items-center">
|
||||
<Eye size={16} />
|
||||
Preview
|
||||
</div>
|
||||
</Button>
|
||||
<Button color="dark" size="xs" on:click={apply}>Apply</Button>
|
||||
{/if}
|
||||
|
||||
<button on:pointerdown|stopPropagation>
|
||||
<ButtonDropdown hasPadding={false}>
|
||||
<svelte:fragment slot="items">
|
||||
<MenuItem on:click={() => themeDrawer?.openDrawer()}>
|
||||
<div
|
||||
class={classNames(
|
||||
'!text-primary flex flex-row items-center text-left px-4 py-2 gap-2 cursor-pointer hover:bg-gray-100 !text-xs font-semibold'
|
||||
)}
|
||||
>
|
||||
<Code size={16} />
|
||||
View code
|
||||
</div>
|
||||
</MenuItem>
|
||||
<MenuItem on:click={fork}>
|
||||
<div
|
||||
class={classNames(
|
||||
'!text-primary flex flex-row items-center text-left px-4 py-2 gap-2 cursor-pointer hover:bg-gray-100 !text-xs font-semibold'
|
||||
)}
|
||||
>
|
||||
<GitBranch size={16} />
|
||||
Fork
|
||||
</div>
|
||||
</MenuItem>
|
||||
{#if row.path !== DEFAULT_THEME}
|
||||
<MenuItem on:click={() => makeDefaultTheme(row.path)}>
|
||||
<div
|
||||
class={classNames(
|
||||
'!text-primary flex flex-row items-center text-left px-4 py-2 gap-2 cursor-pointer hover:bg-gray-100 !text-xs font-semibold'
|
||||
)}
|
||||
>
|
||||
<Pin size={16} />
|
||||
Make default
|
||||
</div>
|
||||
</MenuItem>
|
||||
|
||||
<MenuItem on:click={toggleDelete}>
|
||||
<div
|
||||
class={classNames(
|
||||
'!text-red-600 flex flex-row items-center text-left px-4 py-2 gap-2 cursor-pointer hover:bg-gray-100 !text-xs font-semibold'
|
||||
)}
|
||||
>
|
||||
<Trash size={16} />
|
||||
Delete
|
||||
</div>
|
||||
</MenuItem>
|
||||
{/if}
|
||||
</svelte:fragment>
|
||||
</ButtonDropdown>
|
||||
</button>
|
||||
</div>
|
||||
</Cell>
|
||||
</tr>
|
||||
|
||||
<ThemeDrawer
|
||||
bind:this={themeDrawer}
|
||||
theme={{
|
||||
type: 'path',
|
||||
path: row.path ?? ''
|
||||
}}
|
||||
/>
|
||||
@@ -0,0 +1,790 @@
|
||||
import * as csstree from 'css-tree'
|
||||
import type { ComponentCssProperty } from '../../types'
|
||||
|
||||
export function sanitizeCss(css: string, authorizedClassNames: string[]) {
|
||||
const ast = csstree.parse(css)
|
||||
const removedClassNames: string[] = []
|
||||
|
||||
csstree.walk(ast, (node: any, item, list) => {
|
||||
if (node.type === 'Rule') {
|
||||
let shouldRemoveRule = true
|
||||
|
||||
csstree.walk(node, (innerNode: any) => {
|
||||
if (innerNode.type === 'ClassSelector' && authorizedClassNames.includes(innerNode.name)) {
|
||||
shouldRemoveRule = false
|
||||
}
|
||||
if (shouldRemoveRule && innerNode.name) {
|
||||
removedClassNames.push(innerNode.name)
|
||||
}
|
||||
})
|
||||
|
||||
if (shouldRemoveRule) {
|
||||
list.remove(item)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
return {
|
||||
css: csstree.generate(ast),
|
||||
removedClassNames
|
||||
}
|
||||
}
|
||||
|
||||
export const authorizedClassnames = [
|
||||
'wm-container',
|
||||
'wm-list',
|
||||
'wm-list-pagination',
|
||||
'wm-list-pagination-buttons',
|
||||
'wm-drawer',
|
||||
'wm-drawer-button',
|
||||
'wm-drawer-button-container',
|
||||
'wm-button',
|
||||
'wm-button-wrapper',
|
||||
'wm-button-container',
|
||||
'wm-vertical-split-panes',
|
||||
'wm-horizontal-split-panes',
|
||||
'wm-modal',
|
||||
'wm-modal-button',
|
||||
'wm-modal-button-container',
|
||||
'wm-tabs-container',
|
||||
'wm-tabs-tabRow',
|
||||
'wm-tabs-alltabs',
|
||||
'wm-tabs-selectedTab',
|
||||
'wm-carousel',
|
||||
'wm-submit',
|
||||
'wm-submit-button',
|
||||
'wm-number-input',
|
||||
'wm-currency-input',
|
||||
'wm-date-input',
|
||||
'wm-text-input',
|
||||
'wm-html',
|
||||
'wm-table-container',
|
||||
'wm-table-header',
|
||||
'wm-table-body',
|
||||
'wm-table-footer',
|
||||
'wm-table-row-selected',
|
||||
'wm-table-row',
|
||||
'wm-stepper',
|
||||
'wm-file-input',
|
||||
'wm-toggle-text',
|
||||
'wm-toggle-container',
|
||||
'wm-image',
|
||||
'wm-pdf',
|
||||
'wm-horizontal-divider',
|
||||
'wm-vertical-divider',
|
||||
'wm-horizontal-divider-container',
|
||||
'wm-vertical-divider-container',
|
||||
'wm-log-header',
|
||||
'wm-log-container',
|
||||
'wm-map',
|
||||
'wm-icon',
|
||||
'wm-icon-container',
|
||||
'wm-flow-status-header',
|
||||
'wm-flow-status-container',
|
||||
'wm-select-tab-row',
|
||||
'wm-select-tab',
|
||||
'wm-select-tab-selected',
|
||||
|
||||
'ql-toolbar',
|
||||
'ql-stroke',
|
||||
'ql-fill',
|
||||
'ql-container',
|
||||
|
||||
'wm-pie-chart',
|
||||
|
||||
'wm-modal-form-popup',
|
||||
'wm-modal-form-button',
|
||||
|
||||
'wm-download-button',
|
||||
'wm-download-button-container',
|
||||
|
||||
'wm-bar-chart',
|
||||
'wm-scatter-chart',
|
||||
'wm-chartjs',
|
||||
'wm-timeseries',
|
||||
'wm-conditional-tabs',
|
||||
|
||||
'wm-rich-result-header',
|
||||
'wm-rich-result-container'
|
||||
// TODO: Select and mutltiselect
|
||||
]
|
||||
|
||||
interface Selector {
|
||||
selector: string
|
||||
comment?: string | undefined
|
||||
customCssKey?: string | undefined
|
||||
}
|
||||
|
||||
interface Variable {
|
||||
variable: string
|
||||
value: string
|
||||
comment?: string | undefined
|
||||
}
|
||||
|
||||
interface Customisation {
|
||||
components: string[]
|
||||
selectors: Selector[]
|
||||
variables: Variable[]
|
||||
link?: string | undefined
|
||||
variablesTooltip?: string
|
||||
}
|
||||
|
||||
export const customisationByComponent: Customisation[] = [
|
||||
{
|
||||
components: ['app'],
|
||||
selectors: [
|
||||
{ selector: '.wm-app-viewer', comment: 'Applied to the div under all components' },
|
||||
{
|
||||
selector: '.wm-app-grid',
|
||||
comment: 'Applied to the div that contains the grid of components'
|
||||
},
|
||||
{
|
||||
selector: '.wm-app-component',
|
||||
comment: 'Applied to the div that is around every components'
|
||||
}
|
||||
],
|
||||
variables: []
|
||||
},
|
||||
{
|
||||
components: ['buttoncomponent'],
|
||||
selectors: [
|
||||
{ selector: '.wm-button', comment: 'Applied to the button', customCssKey: 'button' },
|
||||
{ selector: '.wm-button-wrapper', comment: 'Applied to the div around the button' },
|
||||
{
|
||||
selector: '.wm-button-container',
|
||||
comment: 'Applied to the button container',
|
||||
customCssKey: 'container'
|
||||
}
|
||||
],
|
||||
variables: []
|
||||
},
|
||||
{
|
||||
components: ['containercomponent'],
|
||||
selectors: [
|
||||
{ selector: '.wm-container', comment: 'Container component', customCssKey: 'container' }
|
||||
],
|
||||
variables: []
|
||||
},
|
||||
{
|
||||
components: ['listcomponent'],
|
||||
selectors: [
|
||||
{ selector: '.wm-list', comment: 'List component', customCssKey: 'container' },
|
||||
{ selector: '.wm-list-pagination', comment: 'Pagination component' },
|
||||
{ selector: '.wm-list-pagination-buttons', comment: 'Pagination buttons component' }
|
||||
],
|
||||
variables: []
|
||||
},
|
||||
{
|
||||
components: ['drawercomponent'],
|
||||
selectors: [
|
||||
{ selector: '.wm-drawer', comment: 'main drawer element', customCssKey: 'drawer' },
|
||||
{ selector: '.wm-drawer-button', comment: 'button to open drawer', customCssKey: 'button' },
|
||||
{
|
||||
selector: '.wm-drawer-button-container',
|
||||
comment: 'container for button to open drawer',
|
||||
customCssKey: 'container'
|
||||
}
|
||||
],
|
||||
variables: []
|
||||
},
|
||||
{
|
||||
components: ['verticalsplitpanescomponent'],
|
||||
selectors: [
|
||||
{
|
||||
selector: '.wm-vertical-split-panes',
|
||||
comment: 'Vertical split panes component',
|
||||
customCssKey: 'container'
|
||||
}
|
||||
],
|
||||
variables: []
|
||||
},
|
||||
{
|
||||
components: ['horizontalsplitpanescomponent'],
|
||||
selectors: [
|
||||
{
|
||||
selector: '.wm-horizontal-split-panes',
|
||||
comment: 'Horizontal split panes component',
|
||||
customCssKey: 'container'
|
||||
}
|
||||
],
|
||||
variables: []
|
||||
},
|
||||
{
|
||||
components: ['modalcomponent'],
|
||||
selectors: [
|
||||
{ selector: '.wm-modal', comment: 'main modal element', customCssKey: 'popup' },
|
||||
{ selector: '.wm-modal-button', comment: 'button to open modal', customCssKey: 'button' },
|
||||
{
|
||||
selector: '.wm-modal-button-container',
|
||||
comment: 'container for button to open modal',
|
||||
customCssKey: 'buttonContainer'
|
||||
}
|
||||
],
|
||||
variables: []
|
||||
},
|
||||
{
|
||||
components: ['rangecomponent', 'slidercomponent'],
|
||||
selectors: [
|
||||
{ selector: '.wm-slider-bar', comment: 'Slider bar', customCssKey: 'bar' },
|
||||
{ selector: '.wm-slider-handle', comment: 'Slider handle', customCssKey: 'handle' },
|
||||
{ selector: '.wm-slider-limits', comment: 'Slider limits', customCssKey: 'limits' },
|
||||
{ selector: '.wm-slider-value', comment: 'Slider value', customCssKey: 'value' },
|
||||
|
||||
{ selector: '.rangeSlider', comment: 'main slider element' },
|
||||
{ selector: '.rangeSlider.vertical', comment: 'if slider is vertical' },
|
||||
{ selector: '.rangeSlider.focus', comment: 'if slider is focussed' },
|
||||
{ selector: '.rangeSlider.range', comment: 'if slider is a range' },
|
||||
{ selector: '.rangeSlider.min', comment: 'if slider is a min-range' },
|
||||
{ selector: '.rangeSlider.max', comment: 'if slider is a max-range' },
|
||||
{ selector: '.rangeSlider.pips', comment: 'if slider has visible pips' },
|
||||
{ selector: '.rangeSlider.pip-labels', comment: 'if slider has labels for pips' },
|
||||
{
|
||||
selector: '.rangeSlider > .rangeHandle',
|
||||
comment: 'positioned wrapper for the handle/float'
|
||||
},
|
||||
{
|
||||
selector: '.rangeSlider > .rangeHandle.active',
|
||||
comment: 'if a handle is active in any way'
|
||||
},
|
||||
{
|
||||
selector: '.rangeSlider > .rangeHandle.press',
|
||||
comment: 'if a handle is being pressed down'
|
||||
},
|
||||
{
|
||||
selector: '.rangeSlider > .rangeHandle.hoverable',
|
||||
comment: 'if the handles allow hover effect'
|
||||
},
|
||||
{
|
||||
selector: '.rangeSlider > .rangeHandle > .rangeNub',
|
||||
comment: 'the actual nub rendered as a handle'
|
||||
},
|
||||
{
|
||||
selector: '.rangeSlider > .rangeHandle > .rangeFloat',
|
||||
comment: 'the floating value above the handle'
|
||||
},
|
||||
{ selector: '.rangeSlider > .rangeBar', comment: 'the range between the two handles' },
|
||||
{ selector: '.rangeSlider > .rangePips', comment: 'the container element for the pips' },
|
||||
{ selector: '.rangeSlider > .rangePips.focus', comment: 'if slider is focussed' },
|
||||
{ selector: '.rangeSlider > .rangePips.vertical', comment: 'if slider is vertical' },
|
||||
{ selector: '.rangeSlider > .rangePips > .pip', comment: 'an individual pip' },
|
||||
{
|
||||
selector: '.rangeSlider > .rangePips > .pip.first',
|
||||
comment: 'the first pip on the slider'
|
||||
},
|
||||
{ selector: '.rangeSlider > .rangePips > .pip.last', comment: 'the last pip on the slider' },
|
||||
{ selector: '.rangeSlider > .rangePips > .pip.selected', comment: 'if a pip is selected' },
|
||||
{
|
||||
selector: '.rangeSlider > .rangePips > .pip.in-range',
|
||||
comment: 'if a pip is somewhere in the range'
|
||||
},
|
||||
{ selector: '.rangeSlider > .rangePips > .pip > .pipVal', comment: 'the label for the pip' }
|
||||
],
|
||||
variables: [
|
||||
{ variable: '--range-slider', value: '#d7dada', comment: 'slider main background color' },
|
||||
{ variable: '--range-handle-inactive', value: '#99a2a2', comment: 'inactive handle color' },
|
||||
{ variable: '--range-handle', value: '#838de7', comment: 'non-focussed handle color' },
|
||||
{ variable: '--range-handle-focus', value: '#4a40d4', comment: 'focussed handle color' },
|
||||
{ variable: '--range-handle-border', value: 'var(--range-handle)' },
|
||||
{
|
||||
variable: '--range-range-inactive',
|
||||
value: 'var(--range-handle-inactive)',
|
||||
comment: 'inactive range bar background color'
|
||||
},
|
||||
{
|
||||
variable: '--range-range',
|
||||
value: 'var(--range-handle-focus)',
|
||||
comment: 'active range bar background color'
|
||||
},
|
||||
{
|
||||
variable: '--range-float-inactive',
|
||||
value: 'var(--range-handle-inactive)',
|
||||
comment: 'inactive floating label background color'
|
||||
},
|
||||
{
|
||||
variable: '--range-float',
|
||||
value: 'var(--range-handle-focus)',
|
||||
comment: 'floating label background color'
|
||||
},
|
||||
{ variable: '--range-float-text', value: 'white', comment: 'text color on floating label' }
|
||||
],
|
||||
link: 'https://simeydotme.github.io/svelte-range-slider-pips/#styling'
|
||||
},
|
||||
{
|
||||
components: ['tabscomponent'],
|
||||
selectors: [
|
||||
{ selector: '.wm-tabs-container', comment: 'Tabs container', customCssKey: 'container' },
|
||||
{ selector: '.wm-tabs-tabRow', comment: 'Tabs row', customCssKey: 'tabRow' },
|
||||
{ selector: '.wm-tabs-alltabs', comment: 'All tabs', customCssKey: 'allTabs' },
|
||||
{ selector: '.wm-tabs-selectedTab', comment: 'Selected tab', customCssKey: 'selectedTab' }
|
||||
],
|
||||
variables: []
|
||||
},
|
||||
{
|
||||
components: ['carousellistcomponent'],
|
||||
selectors: [
|
||||
{ selector: '.wm-carousel', comment: 'Carousel component', customCssKey: 'container' }
|
||||
],
|
||||
variables: []
|
||||
},
|
||||
{
|
||||
components: ['formcomponent'],
|
||||
selectors: [
|
||||
{ selector: '.wm-submit', comment: 'Submit component', customCssKey: 'container' },
|
||||
{ selector: '.wm-submit-button', comment: 'Submit button', customCssKey: 'button' }
|
||||
],
|
||||
variables: []
|
||||
},
|
||||
{
|
||||
components: ['numberinputcomponent'],
|
||||
selectors: [
|
||||
{ selector: '.wm-number-input', comment: 'Number component', customCssKey: 'input' }
|
||||
],
|
||||
variables: []
|
||||
},
|
||||
{
|
||||
components: ['currencycomponent'],
|
||||
selectors: [
|
||||
{ selector: '.wm-currency-input', comment: 'Currency component', customCssKey: 'input' }
|
||||
],
|
||||
variables: []
|
||||
},
|
||||
{
|
||||
components: ['dateinputcomponent'],
|
||||
selectors: [{ selector: '.wm-date-input', comment: 'Date component', customCssKey: 'input' }],
|
||||
variables: []
|
||||
},
|
||||
{
|
||||
components: [
|
||||
'emailinputcomponent',
|
||||
'textinputcomponent',
|
||||
'textareainputcomponent',
|
||||
'passwordinputcomponent'
|
||||
],
|
||||
selectors: [{ selector: '.wm-text-input', comment: 'Text component', customCssKey: 'input' }],
|
||||
variables: []
|
||||
},
|
||||
{
|
||||
components: ['htmlcomponent'],
|
||||
selectors: [{ selector: '.wm-html', comment: 'HTML component', customCssKey: 'container' }],
|
||||
variables: []
|
||||
},
|
||||
{
|
||||
components: ['tablecomponent'],
|
||||
selectors: [
|
||||
{ selector: '.wm-table-container', comment: 'Table component', customCssKey: 'container' },
|
||||
{ selector: '.wm-table-header', comment: 'Table header', customCssKey: 'tableHeader' },
|
||||
{ selector: '.wm-table-body', comment: 'Table body', customCssKey: 'tableBody' },
|
||||
{ selector: '.wm-table-footer', comment: 'Table footer', customCssKey: 'tableFooter' },
|
||||
{ selector: '.wm-table-row-selected', comment: 'Selected row' },
|
||||
{ selector: '.wm-table-row', comment: 'Table row' }
|
||||
],
|
||||
variables: []
|
||||
},
|
||||
{
|
||||
components: ['steppercomponent'],
|
||||
selectors: [
|
||||
{ selector: 'wm-stepper', comment: 'Stepper component', customCssKey: 'container' }
|
||||
],
|
||||
variables: []
|
||||
},
|
||||
{
|
||||
components: ['fileinputcomponent'],
|
||||
selectors: [
|
||||
{ selector: '.wm-file-input', comment: 'File input component', customCssKey: 'input' }
|
||||
],
|
||||
variables: []
|
||||
},
|
||||
{
|
||||
components: ['checkboxcomponent'],
|
||||
selectors: [
|
||||
{ selector: '.wm-toggle-text', comment: 'Checkbox component label', customCssKey: 'text' },
|
||||
{
|
||||
selector: '.wm-toggle-container',
|
||||
comment: 'Checkbox component container',
|
||||
customCssKey: 'container'
|
||||
}
|
||||
],
|
||||
variables: []
|
||||
},
|
||||
{
|
||||
components: ['imagecomponent'],
|
||||
selectors: [{ selector: '.wm-image', comment: 'Image component', customCssKey: 'image' }],
|
||||
variables: []
|
||||
},
|
||||
{
|
||||
components: ['pdfcomponent'],
|
||||
selectors: [{ selector: '.wm-pdf', comment: 'PDF component', customCssKey: 'container' }],
|
||||
variables: []
|
||||
},
|
||||
{
|
||||
components: ['horizontaldividercomponent'],
|
||||
selectors: [
|
||||
{
|
||||
selector: '.wm-horizontal-divider',
|
||||
comment: 'Horizontal divider component',
|
||||
customCssKey: 'divider'
|
||||
},
|
||||
{
|
||||
selector: '.wm-horizontal-divider-container',
|
||||
comment: 'Horizontal divider container',
|
||||
customCssKey: 'container'
|
||||
}
|
||||
],
|
||||
variables: []
|
||||
},
|
||||
{
|
||||
components: ['verticaldividercomponent'],
|
||||
selectors: [
|
||||
{
|
||||
selector: '.wm-vertical-divider',
|
||||
comment: 'Vertical divider component',
|
||||
customCssKey: 'divider'
|
||||
},
|
||||
{
|
||||
selector: '.wm-vertical-divider-container',
|
||||
comment: 'Vertical divider container',
|
||||
customCssKey: 'container'
|
||||
}
|
||||
],
|
||||
variables: []
|
||||
},
|
||||
{
|
||||
components: ['logcomponent', 'jobidlogcomponent'],
|
||||
selectors: [
|
||||
{ selector: '.wm-log-header', comment: 'Log header', customCssKey: 'header' },
|
||||
{ selector: '.wm-log-container', comment: 'Log container', customCssKey: 'container' }
|
||||
],
|
||||
variables: []
|
||||
},
|
||||
{
|
||||
components: ['mapcomponent'],
|
||||
selectors: [{ selector: '.wm-map', comment: 'Map component', customCssKey: 'map' }],
|
||||
variables: []
|
||||
},
|
||||
{
|
||||
components: ['iconcomponent'],
|
||||
selectors: [
|
||||
{ selector: '.wm-icon', comment: 'Icon component', customCssKey: 'icon' },
|
||||
{ selector: '.wm-icon-container', comment: 'Icon container', customCssKey: 'container' }
|
||||
],
|
||||
variables: []
|
||||
},
|
||||
{
|
||||
components: ['flowstatuscomponent', 'jobidflowstatuscomponent'],
|
||||
selectors: [
|
||||
{ selector: '.wm-flow-status-header', comment: 'Flow status header', customCssKey: 'header' },
|
||||
{
|
||||
selector: '.wm-flow-status-container',
|
||||
comment: 'Flow status container',
|
||||
customCssKey: 'container'
|
||||
}
|
||||
],
|
||||
variables: []
|
||||
},
|
||||
{
|
||||
components: ['selecttabcomponent'],
|
||||
selectors: [
|
||||
{ selector: '.wm-select-tab-row', comment: 'Select tab row', customCssKey: 'tabRow' },
|
||||
{ selector: '.wm-select-tab', comment: 'Select tab', customCssKey: 'tab' },
|
||||
{
|
||||
selector: '.wm-select-tab-selected',
|
||||
comment: 'Select tab selected',
|
||||
customCssKey: 'selectedTab'
|
||||
}
|
||||
],
|
||||
variables: []
|
||||
},
|
||||
{
|
||||
components: ['piechartcomponent'],
|
||||
selectors: [{ selector: '.wm-pie-chart', comment: 'Pie chart', customCssKey: 'container' }],
|
||||
variables: []
|
||||
},
|
||||
{
|
||||
components: ['quillcomponent'],
|
||||
selectors: [
|
||||
{ selector: '.ql-toolbar', comment: 'Quill toolbar' },
|
||||
{ selector: '.ql-stroke', comment: 'Quill stroke' },
|
||||
{ selector: '.ql-fill', comment: 'Quill fill' },
|
||||
{ selector: '.ql-container', comment: 'Quill container' }
|
||||
],
|
||||
variables: []
|
||||
},
|
||||
{
|
||||
components: ['formbuttoncomponent'],
|
||||
selectors: [
|
||||
{ selector: '.wm-modal-form-popup', comment: 'Modal form popup', customCssKey: 'popup' },
|
||||
{ selector: '.wm-modal-form-button', comment: 'Modal form button', customCssKey: 'button' }
|
||||
],
|
||||
variables: []
|
||||
},
|
||||
{
|
||||
components: ['downloadcomponent'],
|
||||
selectors: [
|
||||
{ selector: '.wm-download-button', comment: 'Download button', customCssKey: 'button' },
|
||||
{ selector: '.wm-download-button-container', comment: 'Download button container' }
|
||||
],
|
||||
variables: []
|
||||
},
|
||||
{
|
||||
components: ['barchartcomponent'],
|
||||
selectors: [{ selector: '.wm-bar-chart', comment: 'Bar chart', customCssKey: 'container' }],
|
||||
variables: []
|
||||
},
|
||||
{
|
||||
components: ['scatterchartcomponent'],
|
||||
selectors: [
|
||||
{ selector: '.wm-scatter-chart', comment: 'Scatter chart', customCssKey: 'container' }
|
||||
],
|
||||
variables: []
|
||||
},
|
||||
{
|
||||
components: ['textcomponent'],
|
||||
selectors: [
|
||||
{ selector: '.wm-text', comment: 'Text component', customCssKey: 'text' },
|
||||
{ selector: '.wm-text-container', comment: 'Text container', customCssKey: 'container' }
|
||||
],
|
||||
variables: []
|
||||
},
|
||||
{
|
||||
components: ['chartjscomponent'],
|
||||
selectors: [{ selector: '.wm-chartjs', comment: 'ChartJS', customCssKey: 'container' }],
|
||||
variables: []
|
||||
},
|
||||
{
|
||||
components: ['timeseriescomponent'],
|
||||
selectors: [{ selector: '.wm-timeseries', comment: 'Time series', customCssKey: 'container' }],
|
||||
variables: []
|
||||
},
|
||||
{
|
||||
components: ['conditionalwrapper'],
|
||||
selectors: [
|
||||
{ selector: '.wm-conditional-tabs', comment: 'Conditional tabs', customCssKey: 'container' }
|
||||
],
|
||||
variables: []
|
||||
},
|
||||
{
|
||||
components: ['displaycomponent'],
|
||||
selectors: [
|
||||
{ selector: '.wm-rich-result-header', comment: 'Rich result header', customCssKey: 'header' },
|
||||
{
|
||||
selector: '.wm-rich-result-container',
|
||||
comment: 'Rich result container',
|
||||
customCssKey: 'container'
|
||||
}
|
||||
],
|
||||
variables: []
|
||||
},
|
||||
{
|
||||
components: ['mardowncomponent'],
|
||||
selectors: [
|
||||
{ selector: '.wm-markdown', comment: 'Markdown component', customCssKey: 'container' }
|
||||
],
|
||||
variables: []
|
||||
},
|
||||
{
|
||||
components: ['schemaformcomponent'],
|
||||
selectors: [
|
||||
{ selector: '.wm-schema-form', comment: 'Schema form component', customCssKey: 'container' }
|
||||
],
|
||||
variables: []
|
||||
},
|
||||
{
|
||||
components: ['selectstepcomponent'],
|
||||
selectors: [{ selector: '.wm-select-step', comment: 'Select step', customCssKey: 'container' }],
|
||||
variables: []
|
||||
},
|
||||
{
|
||||
components: ['selectcomponent', 'resourceselectcomponent'],
|
||||
selectors: [{ selector: '.svelte-select', comment: 'Svelte select', customCssKey: 'input' }],
|
||||
variables: []
|
||||
},
|
||||
|
||||
{
|
||||
components: ['multiselectcomponent'],
|
||||
selectors: [
|
||||
{
|
||||
selector: '.multiselect',
|
||||
comment: 'top-level wrapper div'
|
||||
},
|
||||
{
|
||||
selector: 'multiselect.open',
|
||||
comment: 'top-level wrapper div when dropdown open'
|
||||
},
|
||||
{
|
||||
selector: '.multiselect.disabled',
|
||||
comment: 'top-level wrapper div when in disabled state'
|
||||
},
|
||||
{
|
||||
selector: '.multiselect > ul.selected',
|
||||
comment: 'selected list'
|
||||
},
|
||||
{
|
||||
selector: '.multiselect > ul.selected > li',
|
||||
comment: 'selected list items'
|
||||
},
|
||||
{
|
||||
selector: '.multiselect button',
|
||||
comment: 'target all buttons in this component'
|
||||
},
|
||||
{
|
||||
selector: '.multiselect > ul.selected > li button, button.remove-all',
|
||||
comment: 'buttons to remove a single or all selected options at once'
|
||||
},
|
||||
{
|
||||
selector: '.multiselect > input[autocomplete]',
|
||||
comment: 'input inside the top-level wrapper div'
|
||||
},
|
||||
{
|
||||
selector: '.multiselect > ul.options',
|
||||
comment: 'dropdown options'
|
||||
},
|
||||
{
|
||||
selector: '.multiselect > ul.options > li',
|
||||
comment: 'dropdown list items'
|
||||
},
|
||||
{
|
||||
selector: '.multiselect > ul.options > li.selected',
|
||||
comment: 'selected options in the dropdown list'
|
||||
},
|
||||
{
|
||||
selector: '.multiselect > ul.options > li:not(.selected):hover',
|
||||
comment: 'unselected but hovered options in the dropdown list'
|
||||
},
|
||||
{
|
||||
selector: '.multiselect > ul.options > li.active',
|
||||
comment:
|
||||
'active item, navigated to with up/down arrow keys and ready to be selected by pressing enter'
|
||||
},
|
||||
{
|
||||
selector: '.multiselect > ul.options > li.disabled',
|
||||
comment: 'options with disabled key set to true'
|
||||
}
|
||||
],
|
||||
variables: [
|
||||
{
|
||||
variable: '--sms-border',
|
||||
value: '1pt solid lightgray',
|
||||
comment:
|
||||
'Change this to e.g. to 1px solid red to indicate this form field is in an invalid state.'
|
||||
},
|
||||
{ variable: '--sms-border-radius', value: '3pt' },
|
||||
{ variable: '--sms-padding', value: '0 3pt' },
|
||||
{ variable: '--sms-bg', value: '' },
|
||||
{ variable: '--sms-text-color', value: '' },
|
||||
{ variable: '--sms-min-height', value: '22pt' },
|
||||
{ variable: '--sms-width', value: '' },
|
||||
{ variable: '--sms-max-width', value: '' },
|
||||
{ variable: '--sms-margin', value: '' },
|
||||
{ variable: '--sms-font-size', value: 'inherit' },
|
||||
{
|
||||
variable: '--sms-open-z-index',
|
||||
value: '4',
|
||||
comment:
|
||||
'Increase this if needed to ensure the dropdown list is displayed atop all other page elements.'
|
||||
},
|
||||
{
|
||||
variable: '--sms-focus-border',
|
||||
value: '1pt solid var(--sms-active-color, cornflowerblue)',
|
||||
comment:
|
||||
'Border when component has focus. Defaults to --sms-active-color which in turn defaults to cornflowerblue.'
|
||||
},
|
||||
{
|
||||
variable: '--sms-disabled-bg',
|
||||
value: 'lightgray',
|
||||
comment: 'Background when in disabled state.'
|
||||
},
|
||||
{ variable: '--sms-placeholder-color', value: '' },
|
||||
{ variable: '--sms-placeholder-opacity', value: '' },
|
||||
{
|
||||
variable: '--sms-selected-bg',
|
||||
value: 'rgba(0, 0, 0, 0.15)',
|
||||
comment: 'Background of selected options.'
|
||||
},
|
||||
{
|
||||
variable: '--sms-selected-li-padding',
|
||||
value: '1pt 5pt',
|
||||
comment: 'Height of selected options.'
|
||||
},
|
||||
{
|
||||
variable: '--sms-selected-text-color',
|
||||
value: 'var(--sms-text-color)',
|
||||
comment: 'Text color for selected options.'
|
||||
},
|
||||
{
|
||||
variable: '--sms-remove-btn-hover-color',
|
||||
value: 'lightskyblue',
|
||||
comment:
|
||||
'Color of the remove-icon buttons for removing all or individual selected options when in :focus or :hover state.'
|
||||
},
|
||||
{
|
||||
variable: '--sms-remove-btn-hover-bg',
|
||||
value: 'rgba(0, 0, 0, 0.2)',
|
||||
comment: 'Background for hovered remove buttons.'
|
||||
},
|
||||
{ variable: '--sms-options-bg', value: 'white', comment: 'Background of dropdown list.' },
|
||||
{
|
||||
variable: '--sms-options-max-height',
|
||||
value: '50vh',
|
||||
comment: 'Maximum height of options dropdown.'
|
||||
},
|
||||
{
|
||||
variable: '--sms-options-overscroll',
|
||||
value: 'none',
|
||||
comment:
|
||||
'Whether scroll events bubble to parent elements when reaching the top/bottom of the options dropdown. See MDN.'
|
||||
},
|
||||
{
|
||||
variable: '--sms-options-shadow',
|
||||
value: '0 0 14pt -8pt black',
|
||||
comment: 'Box shadow of dropdown list.'
|
||||
},
|
||||
{ variable: '--sms-options-border', value: '' },
|
||||
{ variable: '--sms-options-border-width', value: '' },
|
||||
{ variable: '--sms-options-border-radius', value: '1ex' },
|
||||
{ variable: '--sms-options-padding', value: '' },
|
||||
{ variable: '--sms-options-margin', value: 'inherit' },
|
||||
{
|
||||
variable: '--sms-options-scroll-margin',
|
||||
value: '100px',
|
||||
comment:
|
||||
'Top/bottom margin to keep between dropdown list items and top/bottom screen edge when auto-scrolling list to keep items in view.'
|
||||
},
|
||||
{
|
||||
variable: '--sms-li-selected-bg',
|
||||
comment: 'Background of selected list items in options pane.',
|
||||
value: ''
|
||||
},
|
||||
{
|
||||
variable: '--sms-li-selected-color',
|
||||
comment: 'Text color of selected list items in options pane.',
|
||||
value: ''
|
||||
},
|
||||
{
|
||||
variable: '--sms-li-active-bg',
|
||||
value: 'var(--sms-active-color, rgba(0, 0, 0, 0.15))',
|
||||
comment:
|
||||
'Background of active options. Options in the dropdown list become active either by mouseover or by navigating to them with arrow keys. Selected options become active when selectedOptionsDraggable=true and an option is being dragged to a new position. Note the active option in that case is not the dragged option but the option under it whose place it will take on drag end.'
|
||||
},
|
||||
{
|
||||
variable: '--sms-li-disabled-bg',
|
||||
value: '#f5f5f6',
|
||||
comment: 'Background of disabled options in the dropdown list.'
|
||||
},
|
||||
{
|
||||
variable: '--sms-li-disabled-text',
|
||||
value: '#b8b8b8',
|
||||
comment: 'Text color of disabled option in the dropdown list.'
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
export const allClasses = customisationByComponent
|
||||
.map((c) => c.selectors.map((x) => x.selector))
|
||||
.flat()
|
||||
|
||||
export function hasStyleValue(obj: ComponentCssProperty | undefined) {
|
||||
if (!obj) return false
|
||||
|
||||
return obj.style !== ''
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
import { ResourceService, AppService } from '$lib/gen'
|
||||
import { sendUserToast } from '$lib/toast'
|
||||
import type { AppTheme } from '../../types'
|
||||
|
||||
export interface Theme {
|
||||
path: string
|
||||
value: {
|
||||
value?: string | undefined
|
||||
name: string
|
||||
}
|
||||
}
|
||||
|
||||
export const DEFAULT_THEME: string = 'f/app_themes/theme_0'
|
||||
|
||||
export function createTheme(workspace: string, theme: Theme): Promise<string> {
|
||||
const createThemeRequest = {
|
||||
workspace,
|
||||
requestBody: {
|
||||
...theme,
|
||||
resource_type: 'app_theme',
|
||||
value: theme.value || ''
|
||||
}
|
||||
}
|
||||
return ResourceService.createResource(createThemeRequest)
|
||||
}
|
||||
|
||||
export async function getTheme(
|
||||
workspace: string,
|
||||
path: string
|
||||
): Promise<{
|
||||
value?: string | undefined
|
||||
name: string
|
||||
}> {
|
||||
try {
|
||||
return AppService.getPublicResource({
|
||||
workspace,
|
||||
path
|
||||
})
|
||||
} catch (e) {
|
||||
sendUserToast(`Theme not found ${path}`)
|
||||
return {
|
||||
value: '',
|
||||
name: 'Not found'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function updateTheme(workspace: string, path: string, updatedTheme: any): Promise<string> {
|
||||
const updateThemeRequest = {
|
||||
workspace,
|
||||
path,
|
||||
requestBody: updatedTheme
|
||||
}
|
||||
return ResourceService.updateResource(updateThemeRequest)
|
||||
}
|
||||
|
||||
export function deleteTheme(workspace: string, path: string): Promise<string> {
|
||||
const deleteThemeRequest = {
|
||||
workspace,
|
||||
path: path
|
||||
}
|
||||
return ResourceService.deleteResource(deleteThemeRequest)
|
||||
}
|
||||
|
||||
export async function listThemes(workspace: string): Promise<
|
||||
Array<{
|
||||
name: string
|
||||
path: string
|
||||
}>
|
||||
> {
|
||||
const listThemesRequest = {
|
||||
workspace,
|
||||
name: 'app_theme'
|
||||
}
|
||||
return ResourceService.listResourceNames(listThemesRequest)
|
||||
}
|
||||
|
||||
export async function resolveTheme(
|
||||
theme: AppTheme | undefined,
|
||||
workspace: string | undefined
|
||||
): Promise<string> {
|
||||
let css = ''
|
||||
if (theme?.type === 'inlined') {
|
||||
css = theme.css
|
||||
} else if (theme?.type === 'path' && theme.path && workspace) {
|
||||
let loadedCss = await ResourceService.getResourceValue({
|
||||
workspace: workspace,
|
||||
path: theme.path
|
||||
})
|
||||
|
||||
css = loadedCss.value ?? ''
|
||||
}
|
||||
return css
|
||||
}
|
||||
@@ -0,0 +1,300 @@
|
||||
<script lang="ts">
|
||||
import { getContext } from 'svelte'
|
||||
import type { AppViewerContext, ComponentCssProperty } from '../../types'
|
||||
import { ccomponents, type AppComponent } from '../component'
|
||||
|
||||
import Badge from '$lib/components/common/badge/Badge.svelte'
|
||||
import Button from '$lib/components/common/button/Button.svelte'
|
||||
|
||||
import { twMerge } from 'tailwind-merge'
|
||||
import { fade } from 'svelte/transition'
|
||||
import css from 'svelte-highlight/languages/css'
|
||||
import { Highlight } from 'svelte-highlight'
|
||||
import { MoveRight } from 'lucide-svelte'
|
||||
import { customisationByComponent } from '../componentsPanel/cssUtils'
|
||||
import { sendUserToast } from '$lib/toast'
|
||||
import CloseButton from '$lib/components/common/CloseButton.svelte'
|
||||
export let component: AppComponent | undefined
|
||||
|
||||
const { app } = getContext<AppViewerContext>('AppViewerContext')
|
||||
|
||||
function fadeFast(node: HTMLElement) {
|
||||
return fade(node, { duration: 100 })
|
||||
}
|
||||
let migrationModalOpen: boolean = false
|
||||
|
||||
export function open() {
|
||||
migrationModalOpen = true
|
||||
}
|
||||
|
||||
function generateCodeFromMigrations(migrations: Map<string, string[]>) {
|
||||
let code = ''
|
||||
for (const [key, value] of migrations) {
|
||||
code += `${key} {\n\t${value.join(';\n\t')}\n}\n\n`
|
||||
}
|
||||
|
||||
return code
|
||||
}
|
||||
|
||||
$: generatedCode = generateCodeFromMigrations(migrations)
|
||||
$: migrations = new Map<string, string[]>()
|
||||
|
||||
function getSelector(key: string) {
|
||||
return customisationByComponent
|
||||
.find((c) => c.components.includes(component?.type ?? ''))
|
||||
?.selectors.find((s) => {
|
||||
return s.customCssKey === key
|
||||
})?.selector
|
||||
}
|
||||
|
||||
function setOrUpdateMigration(key: string, value: string) {
|
||||
const selector = getSelector(key)
|
||||
if (!selector) {
|
||||
return
|
||||
}
|
||||
|
||||
if (migrations.has(selector)) {
|
||||
const arr = migrations.get(selector)
|
||||
|
||||
if (arr) {
|
||||
arr.push(value)
|
||||
|
||||
migrations.set(selector, arr)
|
||||
}
|
||||
} else {
|
||||
migrations.set(selector, [value])
|
||||
}
|
||||
|
||||
migrations = migrations
|
||||
}
|
||||
|
||||
function appendMigrationsToCss(migrations: Map<string, string[]>) {
|
||||
const theme = $app.theme
|
||||
|
||||
if (theme?.type === 'path') {
|
||||
sendUserToast('Cannot migrate to CSS editor when using a theme by path', true)
|
||||
return
|
||||
} else if (theme?.type === 'inlined') {
|
||||
let cssString = theme.css
|
||||
|
||||
if (!cssString) {
|
||||
cssString = ''
|
||||
}
|
||||
|
||||
for (const [key, value] of migrations) {
|
||||
if (cssString.includes(`${key} {` || `${key}{`)) {
|
||||
// append value to existing value
|
||||
const regex = new RegExp(`\\${key}\\s*{\\s*([\\s\\S]*?)\\s*}`, 'g')
|
||||
|
||||
const match = regex.exec(cssString)
|
||||
|
||||
if (match) {
|
||||
const existingValue = match[1]
|
||||
|
||||
cssString = cssString.replace(
|
||||
regex,
|
||||
`${key} {\n\t${existingValue}\n\t${value.join('\n\t')}\n}`
|
||||
)
|
||||
}
|
||||
} else {
|
||||
const firstBreakline = cssString === '' ? '' : '\n\n'
|
||||
|
||||
// append key and value
|
||||
cssString += `${firstBreakline}${key} {\n\t${value.join('\n\t')}\n}\n\n`
|
||||
}
|
||||
}
|
||||
|
||||
theme.css = cssString
|
||||
|
||||
$app.theme = theme
|
||||
}
|
||||
}
|
||||
|
||||
function hasStyles(customCss: Record<string, ComponentCssProperty> | undefined) {
|
||||
if (!customCss) {
|
||||
return false
|
||||
}
|
||||
|
||||
return Object.keys(customCss ?? {})
|
||||
.map((key) => customCss[key])
|
||||
.some((c) => c.style !== '')
|
||||
}
|
||||
|
||||
let type: string | undefined = component?.type
|
||||
</script>
|
||||
|
||||
{#if migrationModalOpen}
|
||||
<div
|
||||
transition:fadeFast|local
|
||||
class={'absolute top-0 bottom-0 left-0 right-0 z-[5000]'}
|
||||
role="dialog"
|
||||
>
|
||||
<div
|
||||
class={twMerge(
|
||||
'fixed inset-0 bg-gray-500 bg-opacity-75 transition-opacity',
|
||||
migrationModalOpen ? 'ease-out duration-300 opacity-100' : 'ease-in duration-200 opacity-0'
|
||||
)}
|
||||
/>
|
||||
|
||||
<div class="fixed inset-0 z-10 overflow-y-auto">
|
||||
<div class="flex min-h-full items-center justify-center p-4">
|
||||
<div
|
||||
class={twMerge(
|
||||
'relative transform overflow-hidden rounded-lg bg-surface px-4 pt-5 pb-4 text-left shadow-xl transition-all sm:my-8 sm:w-full sm:max-w-5xl sm:p-6 ',
|
||||
migrationModalOpen
|
||||
? 'ease-out duration-300 opacity-100 translate-y-0 sm:scale-100'
|
||||
: 'ease-in duration-200 opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95'
|
||||
)}
|
||||
>
|
||||
<div class="leading-6 font-semibold text-sm w-full flex justify-between">
|
||||
<div>Migrate to CSS editor</div><CloseButton
|
||||
on:close={() => (migrationModalOpen = false)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="">
|
||||
<div class="">
|
||||
{#if hasStyles(component?.customCss)}
|
||||
<div class="leading-6 text-xs font-semibold">
|
||||
ID <Badge color="indigo" size="xs">
|
||||
{component?.id}
|
||||
</Badge>
|
||||
</div>
|
||||
{/if}
|
||||
{#if component?.type && $app.css}
|
||||
{#each Object.keys(component.customCss ?? {}) as cssKey}
|
||||
{#if component.customCss?.[cssKey].style != undefined && component.customCss[cssKey].style !== ''}
|
||||
<div class="grid grid-cols-2 gap-2">
|
||||
<div>
|
||||
<div class="flex flex-row justify-between items-center py-0.5">
|
||||
<div class="leading-6 text-xs font-semibold">
|
||||
<Badge>
|
||||
{cssKey}
|
||||
</Badge>
|
||||
</div>
|
||||
<Button
|
||||
color="dark"
|
||||
size="xs"
|
||||
on:click={() => {
|
||||
if (component?.customCss?.[cssKey]?.style != undefined) {
|
||||
setOrUpdateMigration(
|
||||
cssKey,
|
||||
component.customCss[cssKey].style ?? ''
|
||||
)
|
||||
component.customCss[cssKey].style = ''
|
||||
}
|
||||
}}
|
||||
>
|
||||
<div class="flex flex-row gap-2 items-center">
|
||||
Migrate
|
||||
<MoveRight size={16} />
|
||||
</div>
|
||||
</Button>
|
||||
</div>
|
||||
<div class="border p-2 rounded-md">
|
||||
<Highlight code={component.customCss[cssKey].style} language={css} />
|
||||
</div>
|
||||
</div>
|
||||
<div class="">
|
||||
<div class="leading-6 text-xs font-semibold my-1">Preview</div>
|
||||
<div class="border rounded-md p-2">
|
||||
<Highlight
|
||||
code={`${getSelector(cssKey)} {\n\t${
|
||||
component.customCss[cssKey].style
|
||||
}\n}`}
|
||||
language={css}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
{/each}
|
||||
{/if}
|
||||
|
||||
{#if hasStyles(component?.type ? $app.css?.[component?.type] : undefined)}
|
||||
<div class="leading-6 text-xs font-semibold">
|
||||
Global: {component?.type ? ccomponents[component.type]?.name : ''}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if component?.type && $app.css}
|
||||
{#each Object.keys($app.css[component?.type] ?? {}) as cssKey}
|
||||
{#if type && $app.css?.[type]?.[cssKey].style != undefined && $app.css[type]?.[cssKey].style !== ''}
|
||||
<div class="grid grid-cols-2 gap-2">
|
||||
<div>
|
||||
<div class="flex flex-row justify-between items-center py-0.5">
|
||||
<div class="leading-6 text-xs font-semibold">
|
||||
<Badge>
|
||||
{cssKey}
|
||||
</Badge>
|
||||
</div>
|
||||
<Button
|
||||
color="dark"
|
||||
size="xs"
|
||||
on:click={() => {
|
||||
if (type && $app.css?.[type]) {
|
||||
setOrUpdateMigration(cssKey, $app.css[type][cssKey].style)
|
||||
$app.css[type][cssKey].style = ''
|
||||
}
|
||||
}}
|
||||
>
|
||||
<div class="flex flex-row gap-2 items-center">
|
||||
Migrate
|
||||
<MoveRight size={16} />
|
||||
</div>
|
||||
</Button>
|
||||
</div>
|
||||
<div class="border p-2 rounded-md">
|
||||
<Highlight code={$app.css[type][cssKey].style} language={css} />
|
||||
</div>
|
||||
</div>
|
||||
<div class="">
|
||||
<div class="leading-6 text-xs font-semibold my-1">Preview</div>
|
||||
<div class="border rounded-md p-2">
|
||||
<Highlight
|
||||
code={`${getSelector(cssKey)} {\n\t${$app.css[type][cssKey].style}\n}`}
|
||||
language={css}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
{/each}
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="leading-6 text-xs font-semibold my-1">Current migrations</div>
|
||||
{#if migrations.size > 0}
|
||||
<div class="border rounded-md p-2">
|
||||
<Highlight code={generatedCode} language={css} />
|
||||
</div>
|
||||
{:else}
|
||||
<div class="text-gray-500 text-xs">No migrations</div>
|
||||
{/if}
|
||||
</div>
|
||||
<div class="mt-2 flex flex-row justify-end items-center gap-2">
|
||||
<div class="text-xs">
|
||||
If the class is already present in the CSS editor, the migration will append the new
|
||||
values to the existing ones.
|
||||
</div>
|
||||
<Button
|
||||
size="xs"
|
||||
color="dark"
|
||||
on:click={() => {
|
||||
appendMigrationsToCss(migrations)
|
||||
migrationModalOpen = false
|
||||
}}
|
||||
disabled={migrations.size === 0}
|
||||
>
|
||||
<div class="flex flex-row gap-2 items-center">
|
||||
Apply migration
|
||||
<MoveRight size={16} />
|
||||
</div>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
@@ -1,6 +1,6 @@
|
||||
<script lang="ts">
|
||||
import Button from '$lib/components/common/button/Button.svelte'
|
||||
import { faChevronRight } from '@fortawesome/free-solid-svg-icons'
|
||||
import { faChevronLeft } from '@fortawesome/free-solid-svg-icons'
|
||||
import { getContext } from 'svelte'
|
||||
import type { AppEditorContext, AppViewerContext, GridItem, RichConfiguration } from '../../types'
|
||||
import PanelSection from './common/PanelSection.svelte'
|
||||
@@ -24,7 +24,6 @@
|
||||
import { slide } from 'svelte/transition'
|
||||
import { push } from '$lib/history'
|
||||
import Kbd from '$lib/components/common/kbd/Kbd.svelte'
|
||||
import { secondaryMenu } from './secondaryMenu'
|
||||
import StylePanel from './StylePanel.svelte'
|
||||
import { Delete, ExternalLink } from 'lucide-svelte'
|
||||
import GridCondition from './GridCondition.svelte'
|
||||
@@ -33,6 +32,7 @@
|
||||
import EvalV2InputEditor from './inputEditor/EvalV2InputEditor.svelte'
|
||||
import type { ResultAppInput } from '../../inputType'
|
||||
import GridGroup from './GridGroup.svelte'
|
||||
import { secondaryMenuLeft } from './secondaryMenu'
|
||||
|
||||
export let componentSettings: { item: GridItem; parent: string | undefined } | undefined =
|
||||
undefined
|
||||
@@ -361,18 +361,10 @@
|
||||
color="light"
|
||||
size="xs"
|
||||
variant="border"
|
||||
on:click={() => (viewCssOptions = !viewCssOptions)}
|
||||
startIcon={{ icon: faChevronLeft }}
|
||||
on:click={() => secondaryMenuLeft.toggle(StylePanel, {})}
|
||||
>
|
||||
{viewCssOptions ? 'Hide' : 'Show'}
|
||||
</Button>
|
||||
<Button
|
||||
color="light"
|
||||
size="xs"
|
||||
variant="border"
|
||||
endIcon={{ icon: faChevronRight }}
|
||||
on:click={() => secondaryMenu.open(StylePanel, { component })}
|
||||
>
|
||||
Rich Editor
|
||||
Show
|
||||
</Button>
|
||||
</div>
|
||||
<AlignmentEditor bind:component={componentSettings.item.data} />
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
<script lang="ts">
|
||||
import type { ComponentCssProperty } from '../../types'
|
||||
import CssProperty from '../componentsPanel/CssProperty.svelte'
|
||||
|
||||
export let forceStyle: boolean = false
|
||||
export let forceClass: boolean = false
|
||||
export let id: string
|
||||
export let property: ComponentCssProperty | undefined = undefined
|
||||
export let overriden: boolean = false
|
||||
export let overridding: boolean = false
|
||||
export let wmClass: string | undefined = undefined
|
||||
|
||||
function hasValues(obj: ComponentCssProperty | undefined) {
|
||||
if (!obj) return false
|
||||
|
||||
return Object.values(obj).some((v) => v !== '')
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if property}
|
||||
<CssProperty
|
||||
{forceStyle}
|
||||
{forceClass}
|
||||
name={id}
|
||||
bind:value={property[id]}
|
||||
shouldDisplayLeft={hasValues(property[id])}
|
||||
on:left
|
||||
on:right
|
||||
{overriden}
|
||||
{overridding}
|
||||
{wmClass}
|
||||
/>
|
||||
{/if}
|
||||
@@ -32,6 +32,7 @@
|
||||
export let placeholder: string | undefined
|
||||
export let customTitle: string | undefined = undefined
|
||||
export let displayType: boolean = false
|
||||
export let allowTypeChange: boolean = true
|
||||
|
||||
const { connectingInput, app } = getContext<AppViewerContext>('AppViewerContext')
|
||||
|
||||
@@ -62,18 +63,20 @@
|
||||
<div class={classNames('flex gap-1', 'flex-col')}>
|
||||
<div class="flex justify-between items-end">
|
||||
<div class="flex flex-row gap-4 items-center">
|
||||
<span class="text-xs font-semibold truncate text-primary">
|
||||
{customTitle
|
||||
? customTitle
|
||||
: shouldCapitalize
|
||||
? capitalize(addWhitespaceBeforeCapitals(key))
|
||||
: key}
|
||||
<div class="flex items-center">
|
||||
<span class="text-xs font-semibold truncate text-primary">
|
||||
{customTitle
|
||||
? customTitle
|
||||
: shouldCapitalize
|
||||
? capitalize(addWhitespaceBeforeCapitals(key))
|
||||
: key}
|
||||
</span>
|
||||
{#if tooltip}
|
||||
<Tooltip>
|
||||
<Tooltip small>
|
||||
{tooltip}
|
||||
</Tooltip>
|
||||
{/if}
|
||||
</span>
|
||||
</div>
|
||||
{#if displayType}
|
||||
<div class="text-xs text-tertiary mr-1">
|
||||
{fieldType === 'array' && subFieldType
|
||||
@@ -84,7 +87,7 @@
|
||||
</div>
|
||||
|
||||
<div class={classNames('flex gap-x-2 gap-y-1 justify-end items-center')}>
|
||||
{#if componentInput?.type}
|
||||
{#if componentInput?.type && allowTypeChange}
|
||||
<ToggleButtonGroup
|
||||
class="h-7"
|
||||
bind:selected={componentInput.type}
|
||||
|
||||
@@ -1,71 +1,276 @@
|
||||
<script lang="ts">
|
||||
import { Button } from '$lib/components/common'
|
||||
import { Tab, TabContent } from '$lib/components/common'
|
||||
import { sendUserToast } from '$lib/toast'
|
||||
import { Copy } from 'lucide-svelte'
|
||||
import { getContext } from 'svelte'
|
||||
import type { AppViewerContext } from '../../types'
|
||||
import { ccomponents, type AppComponent } from '../component'
|
||||
import type { AppViewerContext, ComponentCssProperty } from '../../types'
|
||||
import { ccomponents, components } from '../component'
|
||||
import CssProperty from '../componentsPanel/CssProperty.svelte'
|
||||
import { quickStyleProperties } from '../componentsPanel/quickStyleProperties'
|
||||
import Tabs from '$lib/components/common/tabs/Tabs.svelte'
|
||||
import ConfirmationModal from '$lib/components/common/confirmationModal/ConfirmationModal.svelte'
|
||||
import Badge from '$lib/components/common/badge/Badge.svelte'
|
||||
import Button from '$lib/components/common/button/Button.svelte'
|
||||
import { enterpriseLicense } from '$lib/stores'
|
||||
import Tooltip from '$lib/components/Tooltip.svelte'
|
||||
import { customisationByComponent, hasStyleValue } from '../componentsPanel/cssUtils'
|
||||
import CssMigrationModal from './CSSMigrationModal.svelte'
|
||||
import CssPropertyWrapper from './CssPropertyWrapper.svelte'
|
||||
import { onMount } from 'svelte'
|
||||
import { findComponentSettings } from '../appUtils'
|
||||
|
||||
export let component: AppComponent | undefined
|
||||
const { app } = getContext<AppViewerContext>('AppViewerContext')
|
||||
const { app, cssEditorOpen, selectedComponent } = getContext<AppViewerContext>('AppViewerContext')
|
||||
|
||||
function applyToAllInstances() {
|
||||
if (component) {
|
||||
if (!$app.css) {
|
||||
$app.css = {}
|
||||
}
|
||||
let componentType = component?.type
|
||||
if (!$app.css![componentType]) {
|
||||
$app.css![componentType] = {}
|
||||
}
|
||||
Object.keys(ccomponents[component.type].customCss ?? {}).forEach((name) => {
|
||||
if (!$app.css![componentType]![name]) {
|
||||
$app.css![componentType]![name] = {}
|
||||
$: component = findComponentSettings($app, $selectedComponent?.[0])?.item?.data
|
||||
|
||||
let tab: 'local' | 'global' = 'local'
|
||||
let overrideGlobalCSS: (() => void) | undefined = undefined
|
||||
let overrideLocalCSS: (() => void) | undefined = undefined
|
||||
let type = component?.type
|
||||
let migrationModal: CssMigrationModal | undefined = undefined
|
||||
|
||||
$: customCssByComponentType =
|
||||
component?.type && $app.css
|
||||
? Object.entries($app.css[component.type] || {}).map(([id, v]) => ({
|
||||
id,
|
||||
forceStyle: v?.style != undefined,
|
||||
forceClass: v?.['class'] != undefined
|
||||
}))
|
||||
: undefined
|
||||
|
||||
function copyLocalToGlobal(name: string, value: ComponentCssProperty | undefined) {
|
||||
if (!value) {
|
||||
sendUserToast('No local CSS to copy')
|
||||
} else {
|
||||
const type = component?.type
|
||||
|
||||
if (!type) return
|
||||
|
||||
if (hasStyleValue($app.css?.[type]?.[name])) {
|
||||
overrideGlobalCSS = () => {
|
||||
$app.css![type]![name] = JSON.parse(JSON.stringify(value))
|
||||
app.set($app)
|
||||
}
|
||||
if (component) {
|
||||
let nstyle = component.customCss![name]
|
||||
if (nstyle.style) {
|
||||
$app.css![componentType]![name].style = nstyle.style
|
||||
}
|
||||
if (nstyle.class) {
|
||||
$app.css![componentType]![name].class = nstyle.class
|
||||
}
|
||||
} else {
|
||||
if (!$app.css![type]) {
|
||||
initGlobalCss()
|
||||
}
|
||||
})
|
||||
|
||||
sendUserToast(
|
||||
`Applied style to all instances of the ${componentType.replace('component', '')} component`
|
||||
)
|
||||
$app.css![type]![name] = JSON.parse(JSON.stringify(value))
|
||||
app.set($app)
|
||||
sendUserToast('Global CSS copied')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function copyGlobalToLocal(id: string, value: any) {
|
||||
if (!value) {
|
||||
sendUserToast('No global CSS to copy')
|
||||
} else {
|
||||
if (hasStyleValue(value)) {
|
||||
overrideLocalCSS = () => {
|
||||
component!.customCss![id] = JSON.parse(JSON.stringify(value))
|
||||
app.set($app)
|
||||
}
|
||||
} else {
|
||||
component!.customCss![id] = JSON.parse(JSON.stringify(value))
|
||||
app.set($app)
|
||||
sendUserToast('Local CSS copied')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function initGlobalCss() {
|
||||
// If the global css is not initialised, we initialise it.
|
||||
// Should only happen once per app
|
||||
if (!$app.css) {
|
||||
$app.css = {}
|
||||
}
|
||||
|
||||
// If the global css for this component type is not initialised, we initialise it.
|
||||
// Should only happen once per component type
|
||||
if (
|
||||
$app.css &&
|
||||
component &&
|
||||
!$app.css[component.type]?.style &&
|
||||
components[component.type] &&
|
||||
$app.css[component.type] === undefined
|
||||
) {
|
||||
$app.css[component.type] = JSON.parse(JSON.stringify(components[component.type].customCss))
|
||||
app.set($app)
|
||||
}
|
||||
}
|
||||
|
||||
function getSelector(key: string) {
|
||||
return customisationByComponent
|
||||
.find((c) => c.components.includes(component?.type ?? ''))
|
||||
?.selectors.find((s) => {
|
||||
return s.customCssKey === key
|
||||
})?.selector
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
initGlobalCss()
|
||||
})
|
||||
</script>
|
||||
|
||||
<Button
|
||||
variant="border"
|
||||
color="light"
|
||||
size="xs"
|
||||
aria-label="Apply to all instances of this component"
|
||||
btnClasses="ml-3 mt-2"
|
||||
on:click={applyToAllInstances}
|
||||
>
|
||||
Copy style to global CSS <Copy size={18} />
|
||||
</Button>
|
||||
{#if component}
|
||||
<div class="px-2 flex items-center gap-2 flex-row justify-between">
|
||||
{#if !cssEditorOpen}
|
||||
<Button
|
||||
color="blue"
|
||||
size="xs2"
|
||||
variant="border"
|
||||
on:click={() => {
|
||||
$cssEditorOpen = true
|
||||
}}
|
||||
>
|
||||
<div class="flex flex-row gap-1 text-xs items-center">
|
||||
Open CSS editor{$enterpriseLicense === undefined ? ' (EE only)' : ''}
|
||||
<Tooltip light>
|
||||
You can also use the App CSS Editor to customise the CSS of all components.
|
||||
</Tooltip>
|
||||
</div>
|
||||
</Button>
|
||||
{:else}
|
||||
<div />
|
||||
{/if}
|
||||
|
||||
{#if component && component.customCss !== undefined}
|
||||
{#each Object.keys(ccomponents[component.type].customCss ?? {}) as name}
|
||||
<div class="w-full">
|
||||
<CssProperty
|
||||
quickStyleProperties={quickStyleProperties?.[component.type]?.[name]}
|
||||
forceStyle={ccomponents[component.type].customCss[name].style !== undefined}
|
||||
forceClass={ccomponents[component.type].customCss[name].class !== undefined}
|
||||
tooltip={ccomponents[component.type].customCss[name].tooltip}
|
||||
{name}
|
||||
componentType={component.type}
|
||||
bind:value={component.customCss[name]}
|
||||
on:change={() => app.set($app)}
|
||||
/>
|
||||
<div class="flex flex-row gap-2 items-center justify-between">
|
||||
{#if $enterpriseLicense !== undefined}
|
||||
<Button
|
||||
color="dark"
|
||||
size="xs2"
|
||||
on:click={() => {
|
||||
migrationModal?.open()
|
||||
}}
|
||||
>
|
||||
Convert to global CSS
|
||||
</Button>
|
||||
{/if}
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
<Tabs bind:selected={tab}>
|
||||
<Tab value="local" size="xs">
|
||||
<div class="flex flex-row gap-2 items-center">
|
||||
ID
|
||||
<Badge color="indigo" size="xs">
|
||||
{component?.id}
|
||||
</Badge>
|
||||
|
||||
<Tooltip light>
|
||||
You can customise the CSS and the classes of this component instance. Theses
|
||||
customisations will only be applied to this component.
|
||||
</Tooltip>
|
||||
</div>
|
||||
</Tab>
|
||||
<Tab value="global" size="xs">
|
||||
<div class="flex flex-row gap-2 items-center">
|
||||
Global: {type ? ccomponents[type].name : ''}
|
||||
|
||||
<Tooltip light>
|
||||
You can customise the CSS and the classes of all components of this type. Theses
|
||||
customisations will be applied to all components of this type.
|
||||
</Tooltip>
|
||||
</div>
|
||||
</Tab>
|
||||
<svelte:fragment slot="content">
|
||||
<TabContent value="local">
|
||||
{#if component && component.customCss !== undefined}
|
||||
{#each Object.keys(ccomponents[component.type].customCss ?? {}) as name}
|
||||
<div class="w-full">
|
||||
<CssProperty
|
||||
quickStyleProperties={quickStyleProperties?.[component.type]?.[name]}
|
||||
forceStyle={ccomponents[component.type].customCss[name].style !== undefined}
|
||||
forceClass={ccomponents[component.type].customCss[name].class !== undefined}
|
||||
tooltip={ccomponents[component.type].customCss[name].tooltip}
|
||||
{name}
|
||||
wmClass={getSelector(name)}
|
||||
componentType={component.type}
|
||||
bind:value={component.customCss[name]}
|
||||
on:change={() => app.set($app)}
|
||||
shouldDisplayRight={hasStyleValue(component.customCss[name])}
|
||||
on:right={() => {
|
||||
copyLocalToGlobal(name, component?.customCss?.[name])
|
||||
tab = 'global'
|
||||
}}
|
||||
overridding={hasStyleValue($app.css?.[component.type]?.[name]) &&
|
||||
hasStyleValue(component.customCss[name])}
|
||||
/>
|
||||
</div>
|
||||
{/each}
|
||||
{/if}
|
||||
</TabContent>
|
||||
<TabContent value="global">
|
||||
{#if type}
|
||||
{#each customCssByComponentType ?? [] as { id, forceStyle, forceClass }}
|
||||
<div class="w-full">
|
||||
{#if $app.css && type && $app.css[type] && component?.customCss}
|
||||
<CssPropertyWrapper
|
||||
{forceStyle}
|
||||
{forceClass}
|
||||
{id}
|
||||
bind:property={$app.css[type]}
|
||||
on:left={() => {
|
||||
copyGlobalToLocal(
|
||||
id,
|
||||
component?.type ? $app?.css?.[component?.type]?.[id] : undefined
|
||||
)
|
||||
tab = 'local'
|
||||
}}
|
||||
overriden={hasStyleValue(component.customCss[id])}
|
||||
wmClass={getSelector(id)}
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
{/each}
|
||||
{/if}
|
||||
</TabContent>
|
||||
</svelte:fragment>
|
||||
</Tabs>
|
||||
|
||||
<ConfirmationModal
|
||||
title="Confirm overriding global CSS"
|
||||
confirmationText="Override global CSS"
|
||||
open={Boolean(overrideGlobalCSS)}
|
||||
on:confirmed={() => {
|
||||
if (overrideGlobalCSS) {
|
||||
overrideGlobalCSS()
|
||||
overrideGlobalCSS = undefined
|
||||
}
|
||||
|
||||
sendUserToast('Global CSS overridden')
|
||||
}}
|
||||
on:canceled={() => {
|
||||
overrideGlobalCSS = undefined
|
||||
}}
|
||||
>
|
||||
<div class="text-primary pb-2">
|
||||
The global CSS for this component already exists. Do you want to override it?
|
||||
</div>
|
||||
</ConfirmationModal>
|
||||
|
||||
<ConfirmationModal
|
||||
title="Confirm overriding local CSS"
|
||||
confirmationText="Override local CSS"
|
||||
open={Boolean(overrideLocalCSS)}
|
||||
on:confirmed={() => {
|
||||
if (overrideLocalCSS) {
|
||||
overrideLocalCSS()
|
||||
overrideLocalCSS = undefined
|
||||
}
|
||||
|
||||
sendUserToast('Local CSS overridden')
|
||||
}}
|
||||
on:canceled={() => {
|
||||
overrideLocalCSS = undefined
|
||||
}}
|
||||
>
|
||||
<div class="text-primary pb-2">
|
||||
The local CSS for this component already exists. Do you want to override it?
|
||||
</div>
|
||||
</ConfirmationModal>
|
||||
<CssMigrationModal bind:this={migrationModal} bind:component />
|
||||
{:else}
|
||||
<span class="text-sm text-gray-600 mx-2">Select a component to style it in this panel</span>
|
||||
{/if}
|
||||
|
||||
+18
-19
@@ -1,16 +1,19 @@
|
||||
<script lang="ts">
|
||||
import { fly } from 'svelte/transition'
|
||||
import { faChevronLeft } from '@fortawesome/free-solid-svg-icons'
|
||||
import { Badge, Button } from '../../../../common'
|
||||
import { secondaryMenu, SECONDARY_MENU_ID } from './'
|
||||
import { Badge } from '../../../../common'
|
||||
import { secondaryMenuLeft, secondaryMenuRight } from './'
|
||||
import { getContext } from 'svelte'
|
||||
import type { AppViewerContext } from '../../../types'
|
||||
import CloseButton from '$lib/components/common/CloseButton.svelte'
|
||||
|
||||
const { selectedComponent } = getContext<AppViewerContext>('AppViewerContext')
|
||||
export let right: boolean
|
||||
|
||||
let secondaryMenu = right ? secondaryMenuRight : secondaryMenuLeft
|
||||
let width: number
|
||||
let lastSelected = $selectedComponent
|
||||
|
||||
$: if (lastSelected !== $selectedComponent) {
|
||||
$: if (right && lastSelected !== $selectedComponent) {
|
||||
secondaryMenu.close()
|
||||
lastSelected = $selectedComponent
|
||||
}
|
||||
@@ -19,27 +22,23 @@
|
||||
<!-- z-index must be above the split pane handles' z-index (which is 1001 atm.) -->
|
||||
<div
|
||||
bind:clientWidth={width}
|
||||
class="absolute z-[1002] inset-0 overflow-hidden"
|
||||
class="absolute z-[1002] inset-0 overflow-hidden w-full"
|
||||
class:pointer-events-none={!$secondaryMenu.isOpen}
|
||||
>
|
||||
{#if $secondaryMenu.isOpen && $secondaryMenu.component}
|
||||
<div
|
||||
transition:fly|local={{ duration: 300, x: width, y: 0, opacity: 1 }}
|
||||
id={SECONDARY_MENU_ID}
|
||||
transition:fly|local={{ duration: 300, x: right ? width : -width, y: 0, opacity: 1 }}
|
||||
class="flex flex-col w-full h-full bg-surface"
|
||||
>
|
||||
<div class="flex justify-between items-center gap-1 px-3 py-2">
|
||||
<Button
|
||||
color="light"
|
||||
size="xs2"
|
||||
spacingSize="xs"
|
||||
variant="border"
|
||||
startIcon={{ icon: faChevronLeft }}
|
||||
on:click={secondaryMenu.close}
|
||||
>
|
||||
Back
|
||||
</Button>
|
||||
<Badge color="blue">{$selectedComponent}</Badge>
|
||||
<div
|
||||
class="flex justify-between {right ? '' : 'flex-row-reverse'} items-center gap-1 px-3 py-2"
|
||||
>
|
||||
<CloseButton on:close={() => secondaryMenu?.close()} />
|
||||
{#if $selectedComponent}
|
||||
<Badge color="blue">{$selectedComponent}</Badge>
|
||||
{:else}
|
||||
<div />
|
||||
{/if}
|
||||
</div>
|
||||
<div class="relative h-full overflow-y-auto">
|
||||
{#if typeof $secondaryMenu.component === 'string'}
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
import { writable } from 'svelte/store'
|
||||
|
||||
export const SECONDARY_MENU_ID = 'app-secondary-menu' as const
|
||||
import { writable, type Writable } from 'svelte/store'
|
||||
|
||||
export interface SecondaryMenuStore {
|
||||
isOpen: boolean
|
||||
@@ -9,21 +7,42 @@ export interface SecondaryMenuStore {
|
||||
onClose?: (() => void) | undefined
|
||||
}
|
||||
|
||||
const store = writable<SecondaryMenuStore>({ isOpen: false, component: undefined, props: {} })
|
||||
export const secondaryMenuRightStore = writable<SecondaryMenuStore>({
|
||||
isOpen: false,
|
||||
component: undefined,
|
||||
props: {}
|
||||
})
|
||||
export const secondaryMenuLeftStore = writable<SecondaryMenuStore>({
|
||||
isOpen: false,
|
||||
component: undefined,
|
||||
props: {}
|
||||
})
|
||||
|
||||
export const secondaryMenu = {
|
||||
subscribe: store.subscribe,
|
||||
open: (
|
||||
component: SecondaryMenuStore['component'],
|
||||
props: SecondaryMenuStore['props'] = {},
|
||||
onClose: (() => void) | undefined = undefined
|
||||
) => {
|
||||
store.set({ isOpen: true, component, props, onClose })
|
||||
},
|
||||
close: () => {
|
||||
store.update((state) => {
|
||||
if (state.onClose) state.onClose()
|
||||
return { isOpen: false, component: undefined, props: {} }
|
||||
})
|
||||
}
|
||||
} as const
|
||||
export const secondaryMenuRight = secondaryMenuController(secondaryMenuRightStore)
|
||||
export const secondaryMenuLeft = secondaryMenuController(secondaryMenuLeftStore)
|
||||
|
||||
export function secondaryMenuController(store: Writable<SecondaryMenuStore>) {
|
||||
return {
|
||||
subscribe: store.subscribe,
|
||||
toggle: (
|
||||
component: SecondaryMenuStore['component'],
|
||||
props: SecondaryMenuStore['props'] = {},
|
||||
onClose: (() => void) | undefined = undefined
|
||||
) => {
|
||||
store.update((str) => ({ isOpen: !str.isOpen, component, props, onClose }))
|
||||
},
|
||||
open: (
|
||||
component: SecondaryMenuStore['component'],
|
||||
props: SecondaryMenuStore['props'] = {},
|
||||
onClose: (() => void) | undefined = undefined
|
||||
) => {
|
||||
store.set({ isOpen: true, component, props, onClose })
|
||||
},
|
||||
close: () => {
|
||||
store.update((state) => {
|
||||
if (state.onClose) state.onClose()
|
||||
return { isOpen: false, component: undefined, props: {} }
|
||||
})
|
||||
}
|
||||
} as const
|
||||
}
|
||||
|
||||
@@ -43,6 +43,7 @@ export interface GeneralAppInput {
|
||||
export type ComponentCssProperty = {
|
||||
class?: string
|
||||
style?: string
|
||||
evalClass?: RichConfiguration
|
||||
}
|
||||
|
||||
export type ComponentCustomCSS<T extends keyof typeof components> = Partial<
|
||||
@@ -129,6 +130,16 @@ export type HiddenRunnable = {
|
||||
} & Runnable &
|
||||
RecomputeOthersSource
|
||||
|
||||
export type AppTheme =
|
||||
| {
|
||||
type: 'path'
|
||||
path: string
|
||||
}
|
||||
| {
|
||||
type: 'inlined'
|
||||
css: string
|
||||
}
|
||||
|
||||
export type App = {
|
||||
grid: GridItem[]
|
||||
fullscreen: boolean
|
||||
@@ -141,6 +152,7 @@ export type App = {
|
||||
hiddenInlineScripts: Array<HiddenRunnable>
|
||||
css?: Partial<Record<AppCssItemName, Record<string, ComponentCssProperty>>>
|
||||
subgrids?: Record<string, GridItem[]>
|
||||
theme: AppTheme | undefined
|
||||
}
|
||||
|
||||
export type ConnectingInput = {
|
||||
@@ -228,6 +240,8 @@ export type AppViewerContext = {
|
||||
hoverStore: Writable<string | undefined>
|
||||
allIdsInPath: Writable<string[]>
|
||||
darkMode: Writable<boolean>
|
||||
cssEditorOpen: Writable<boolean>
|
||||
previewTheme: Writable<string | undefined>
|
||||
}
|
||||
|
||||
export type AppEditorContext = {
|
||||
|
||||
@@ -233,11 +233,11 @@ export function toKebabCase(text: string) {
|
||||
return text.replace(/[A-Z]+(?![a-z])|[A-Z]/g, ($, ofs) => (ofs ? '-' : '') + $.toLowerCase())
|
||||
}
|
||||
|
||||
export function concatCustomCss<T extends Record<string, ComponentCssProperty>>(
|
||||
export function initCss<T extends Record<string, ComponentCssProperty>>(
|
||||
appCss?: Record<string, ComponentCssProperty>,
|
||||
componentCss?: T
|
||||
): T | undefined {
|
||||
if (!componentCss) return undefined
|
||||
): T {
|
||||
if (!componentCss) return {} as T
|
||||
|
||||
return Object.fromEntries(
|
||||
Object.entries(componentCss).map(([key, v]) => {
|
||||
@@ -253,7 +253,8 @@ export function concatCustomCss<T extends Record<string, ComponentCssProperty>>(
|
||||
key,
|
||||
{
|
||||
style: (appStyle + appEnding + compStyle + compEnding).trim(),
|
||||
class: twMerge(appCss?.[key]?.class, v?.class)
|
||||
class: twMerge(appCss?.[key]?.class, v?.class),
|
||||
evalClass: appCss?.[key]?.evalClass || v?.evalClass
|
||||
}
|
||||
]
|
||||
})
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import type { Environment } from 'monaco-editor/esm/vs/editor/editor.api.js'
|
||||
import cssWorker from 'monaco-editor/esm/vs/language/css/css.worker?worker'
|
||||
|
||||
interface MonacoEnvironmentEnhanced extends Environment {
|
||||
workerOverrideGlobals: WorkerOverrideGlobals
|
||||
@@ -67,6 +68,7 @@ export function buildWorkerDefinition(
|
||||
case 'razor':
|
||||
return buildWorker(workerOverrideGlobals, label, 'htmlWorker', 'HTML Worker')
|
||||
case 'css':
|
||||
return new cssWorker()
|
||||
case 'scss':
|
||||
case 'less':
|
||||
return buildWorker(workerOverrideGlobals, label, 'cssWorker', 'CSS Worker')
|
||||
|
||||
@@ -37,7 +37,8 @@
|
||||
style={css?.popup?.style}
|
||||
class={twMerge(
|
||||
'bg-surface max-w-5xl m-24 overflow-y-auto rounded-lg relative',
|
||||
css?.popup?.class
|
||||
css?.popup?.class,
|
||||
'wm-modal-form-popup'
|
||||
)}
|
||||
use:clickOutside
|
||||
on:click_outside={() => {
|
||||
@@ -51,7 +52,6 @@
|
||||
on:click={() => {
|
||||
isOpen = false
|
||||
}}
|
||||
style={css?.button?.style}
|
||||
class="hover:bg-surface-hover bg-surface-secondary rounded-full w-8 h-8 flex items-center justify-center transition-all"
|
||||
>
|
||||
<X class="text-tertiary" />
|
||||
|
||||
@@ -67,6 +67,8 @@ export function langToExt(lang: string): string {
|
||||
return 'ts'
|
||||
case 'graphql':
|
||||
return 'gql'
|
||||
case 'css':
|
||||
return 'css'
|
||||
|
||||
default:
|
||||
return 'unknown'
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
import type { App } from '$lib/components/apps/types'
|
||||
import { goto } from '$app/navigation'
|
||||
import { sendUserToast } from '$lib/toast'
|
||||
import { DEFAULT_THEME } from '$lib/components/apps/editor/componentsPanel/themeUtils'
|
||||
|
||||
let nodraft = $page.url.searchParams.get('nodraft')
|
||||
const hubId = $page.url.searchParams.get('hub')
|
||||
@@ -29,7 +30,10 @@
|
||||
fullscreen: false,
|
||||
unusedInlineScripts: [],
|
||||
hiddenInlineScripts: [],
|
||||
css: {}
|
||||
theme: {
|
||||
type: 'path',
|
||||
path: DEFAULT_THEME
|
||||
}
|
||||
}
|
||||
|
||||
if (nodraft) {
|
||||
@@ -93,7 +97,7 @@
|
||||
fullscreen: false,
|
||||
unusedInlineScripts: [],
|
||||
hiddenInlineScripts: [],
|
||||
css: {}
|
||||
theme: undefined
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,7 +29,11 @@
|
||||
{#if app}
|
||||
{#key app}
|
||||
<div
|
||||
class={twMerge('min-h-screen h-full w-full', app?.value.css?.['app']?.['viewer']?.class)}
|
||||
class={twMerge(
|
||||
'min-h-screen h-full w-full',
|
||||
app?.value.css?.['app']?.['viewer']?.class,
|
||||
'wm-app-viewer'
|
||||
)}
|
||||
style={app?.value.css?.['app']?.['viewer']?.style}
|
||||
>
|
||||
<AppPreview
|
||||
|
||||
@@ -109,20 +109,22 @@
|
||||
$: preFilteredType =
|
||||
typeFilter == undefined
|
||||
? preFilteredItemsOwners?.filter((x) =>
|
||||
tab == 'states'
|
||||
? x.resource_type == 'state'
|
||||
: tab == 'cache'
|
||||
? x.resource_type == 'cache'
|
||||
: x.resource_type != 'state' && x.resource_type != 'cache'
|
||||
tab === 'workspace'
|
||||
? x.resource_type !== 'app_theme' && x.resource_type !== 'state'
|
||||
: tab === 'states'
|
||||
? x.resource_type === 'state'
|
||||
: tab === 'cache'
|
||||
? x.resource_type === 'cache'
|
||||
: tab === 'theme'
|
||||
? x.resource_type === 'app_theme'
|
||||
: true
|
||||
)
|
||||
: preFilteredItemsOwners?.filter(
|
||||
(x) =>
|
||||
x.resource_type == typeFilter &&
|
||||
(tab == 'states'
|
||||
? x.resource_type == 'state'
|
||||
: tab == 'cache'
|
||||
? x.resource_type == 'cache'
|
||||
: x.resource_type != 'state' && x.resource_type != 'cache')
|
||||
x.resource_type === typeFilter &&
|
||||
(tab === 'workspace'
|
||||
? x.resource_type !== 'app_theme' && x.resource_type !== 'state'
|
||||
: true)
|
||||
)
|
||||
|
||||
async function loadResources(): Promise<void> {
|
||||
@@ -137,6 +139,7 @@
|
||||
...x
|
||||
}
|
||||
})
|
||||
|
||||
loading.resources = false
|
||||
}
|
||||
|
||||
@@ -257,7 +260,7 @@
|
||||
}
|
||||
|
||||
let disableCustomPrefix = false
|
||||
let tab: 'workspace' | 'types' | 'states' | 'cache' = 'workspace'
|
||||
let tab: 'workspace' | 'types' | 'states' | 'cache' | 'theme' = 'workspace'
|
||||
|
||||
let inferrer: Drawer | undefined = undefined
|
||||
let inferrerJson = ''
|
||||
@@ -472,8 +475,17 @@
|
||||
</Tooltip>
|
||||
</div>
|
||||
</Tab>
|
||||
<Tab size="md" value="theme">
|
||||
<div class="flex gap-2 items-center my-1">
|
||||
Theme
|
||||
<Tooltip>
|
||||
Theme are actually resources (but excluded from the Workspace tab for clarity). Theme are
|
||||
used by the apps to customize their look and feel.
|
||||
</Tooltip>
|
||||
</div>
|
||||
</Tab>
|
||||
</Tabs>
|
||||
{#if tab == 'workspace' || tab == 'states' || tab == 'cache'}
|
||||
{#if tab == 'workspace' || tab == 'states' || tab == 'cache' || tab == 'theme'}
|
||||
<div class="pt-2">
|
||||
<input placeholder="Search Resource" bind:value={filter} class="input mt-1" />
|
||||
</div>
|
||||
|
||||
@@ -6,8 +6,8 @@
|
||||
|
||||
import { Alert, Skeleton } from '$lib/components/common'
|
||||
import { WindmillIcon } from '$lib/components/icons'
|
||||
import { AppService, AppWithLastVersion } from '$lib/gen'
|
||||
import { userStore } from '$lib/stores'
|
||||
import { AppService, AppWithLastVersion, SettingsService } from '$lib/gen'
|
||||
import { userStore, enterpriseLicense } from '$lib/stores'
|
||||
import { twMerge } from 'tailwind-merge'
|
||||
|
||||
import { setContext } from 'svelte'
|
||||
@@ -21,6 +21,7 @@
|
||||
|
||||
async function loadApp() {
|
||||
try {
|
||||
setLicense()
|
||||
app = await AppService.getPublicAppBySecret({
|
||||
workspace: $page.params.workspace,
|
||||
path: $page.params.secret
|
||||
@@ -35,6 +36,13 @@
|
||||
}
|
||||
|
||||
const breakpoint = writable<EditorBreakpoint>('lg')
|
||||
|
||||
async function setLicense() {
|
||||
const license = await SettingsService.getLicenseId()
|
||||
if (license) {
|
||||
$enterpriseLicense = license
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
@@ -57,7 +65,11 @@
|
||||
{:else if app}
|
||||
{#key app}
|
||||
<div
|
||||
class={twMerge('min-h-screen h-full w-full', app?.value.css?.['app']?.['viewer']?.class)}
|
||||
class={twMerge(
|
||||
'min-h-screen h-full w-full',
|
||||
app?.value.css?.['app']?.['viewer']?.class,
|
||||
'wm-app-viewer'
|
||||
)}
|
||||
style={app?.value.css?.['app']?.['viewer']?.style}
|
||||
>
|
||||
<AppPreview
|
||||
|
||||
Reference in New Issue
Block a user