From 7e9ee39aa69bc31766b5e4f4aab498c8f14067cd Mon Sep 17 00:00:00 2001 From: Faton Ramadani Date: Fri, 15 Sep 2023 22:55:47 +0200 Subject: [PATCH] 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 --- .github/DockerfileBackendTests | 2 +- ...d2ee3bef92e3fada73dcf519147964c0f4f4a.json | 14 + ...e2798f53d990b01b17bdbc044719d4908e3f4.json | 14 + .../20230915084031_add_themes.down.sql | 1 + .../20230915084031_add_themes.up.sql | 3 + backend/windmill-api/openapi.yaml | 16 + backend/windmill-api/src/apps.rs | 22 + backend/windmill-api/src/workspaces.rs | 15 + backend/windmill-worker/src/worker.rs | 2 +- .../src/lib/components/DisplayResult.svelte | 2 +- frontend/src/lib/components/Editor.svelte | 12 +- .../src/lib/components/HighlightCode.svelte | 1 + .../src/lib/components/SimpleEditor.svelte | 69 ++ .../src/lib/components/TableSimple.svelte | 5 +- frontend/src/lib/components/Tooltip.svelte | 3 +- .../apps/components/buttons/AppButton.svelte | 22 +- .../apps/components/buttons/AppForm.svelte | 20 +- .../components/buttons/AppFormButton.svelte | 19 +- .../components/buttons/AppSchemaForm.svelte | 17 +- .../components/display/AppBarChart.svelte | 21 +- .../components/display/AppCarouselList.svelte | 20 +- .../apps/components/display/AppChartJs.svelte | 25 +- .../display/AppDisplayComponent.svelte | 21 +- .../components/display/AppDownload.svelte | 23 +- .../display/AppFlowStatusComponent.svelte | 22 +- .../apps/components/display/AppHtml.svelte | 18 +- .../apps/components/display/AppIcon.svelte | 21 +- .../apps/components/display/AppImage.svelte | 18 +- .../display/AppJobIdFlowStatus.svelte | 15 +- .../display/AppJobIdLogComponent.svelte | 25 +- .../display/AppLogsComponent.svelte | 27 +- .../apps/components/display/AppMap.svelte | 17 +- .../components/display/AppMarkdown.svelte | 19 +- .../apps/components/display/AppPdf.svelte | 22 +- .../components/display/AppPieChart.svelte | 21 +- .../components/display/AppScatterChart.svelte | 21 +- .../apps/components/display/AppText.svelte | 26 +- .../components/display/AppTimeseries.svelte | 21 +- .../components/display/table/AppTable.svelte | 37 +- .../components/helpers/ResolveStyle.svelte | 40 + .../apps/components/inputs/AppCheckbox.svelte | 26 +- .../components/inputs/AppDateInput.svelte | 17 +- .../components/inputs/AppFileInput.svelte | 17 +- .../components/inputs/AppMultiSelect.svelte | 15 +- .../components/inputs/AppNumberInput.svelte | 18 +- .../components/inputs/AppRangeInput.svelte | 33 +- .../apps/components/inputs/AppSelect.svelte | 16 +- .../components/inputs/AppSelectStep.svelte | 27 +- .../components/inputs/AppSelectTab.svelte | 26 +- .../components/inputs/AppSliderInputs.svelte | 26 +- .../components/inputs/AppTextInput.svelte | 28 +- .../inputs/currency/AppCurrencyInput.svelte | 23 +- .../layout/AppConditionalWrapper.svelte | 18 +- .../components/layout/AppContainer.svelte | 18 +- .../apps/components/layout/AppDivider.svelte | 24 +- .../apps/components/layout/AppDrawer.svelte | 21 +- .../apps/components/layout/AppList.svelte | 55 +- .../apps/components/layout/AppModal.svelte | 31 +- .../components/layout/AppSplitpanes.svelte | 23 +- .../apps/components/layout/AppStepper.svelte | 18 +- .../apps/components/layout/AppTabs.svelte | 28 +- .../components/apps/editor/AppEditor.svelte | 319 +++++-- .../apps/editor/AppEditorHeader.svelte | 4 + .../components/apps/editor/AppPreview.svelte | 54 +- .../apps/editor/ConnectionInstructions.svelte | 4 +- .../components/apps/editor/GridEditor.svelte | 6 +- .../apps/editor/SettingsPanel.svelte | 23 +- .../lib/components/apps/editor/appUtils.ts | 22 +- .../apps/editor/component/Component.svelte | 3 + .../apps/editor/component/components.ts | 25 +- .../componentsPanel/ComponentList.svelte | 5 +- .../editor/componentsPanel/CssEval.svelte | 31 + .../componentsPanel/CssHelperPanel.svelte | 191 +++++ .../editor/componentsPanel/CssProperty.svelte | 126 ++- .../editor/componentsPanel/CssSettings.svelte | 237 +++--- .../componentsPanel/ThemeCodePreview.svelte | 34 + .../editor/componentsPanel/ThemeDrawer.svelte | 26 + .../editor/componentsPanel/ThemeList.svelte | 159 ++++ .../componentsPanel/ThemeNameEditor.svelte | 57 ++ .../editor/componentsPanel/ThemeRow.svelte | 215 +++++ .../apps/editor/componentsPanel/cssUtils.ts | 790 ++++++++++++++++++ .../apps/editor/componentsPanel/themeUtils.ts | 94 +++ .../settingsPanel/CSSMigrationModal.svelte | 300 +++++++ .../settingsPanel/ComponentPanel.svelte | 18 +- .../settingsPanel/CssPropertyWrapper.svelte | 33 + .../settingsPanel/InputsSpecEditor.svelte | 21 +- .../editor/settingsPanel/StylePanel.svelte | 313 +++++-- .../secondaryMenu/SecondaryMenu.svelte | 37 +- .../settingsPanel/secondaryMenu/menuStore.ts | 59 +- frontend/src/lib/components/apps/types.ts | 14 + frontend/src/lib/components/apps/utils.ts | 9 +- frontend/src/lib/components/build_workers.ts | 2 + .../common/modal/AlwaysMountedModal.svelte | 4 +- frontend/src/lib/editorUtils.ts | 2 + .../(root)/(logged)/apps/add/+page.svelte | 8 +- .../(logged)/apps/get/[...path]/+page.svelte | 6 +- .../(root)/(logged)/resources/+page.svelte | 38 +- .../[workspace]/[...secret]/+page.svelte | 18 +- 98 files changed, 3956 insertions(+), 598 deletions(-) create mode 100644 backend/.sqlx/query-584cb984ea6528baac48c5c437ad2ee3bef92e3fada73dcf519147964c0f4f4a.json create mode 100644 backend/.sqlx/query-d956b7525f83e6d03beadc4bb3ee2798f53d990b01b17bdbc044719d4908e3f4.json create mode 100644 backend/migrations/20230915084031_add_themes.down.sql create mode 100644 backend/migrations/20230915084031_add_themes.up.sql create mode 100644 frontend/src/lib/components/apps/components/helpers/ResolveStyle.svelte create mode 100644 frontend/src/lib/components/apps/editor/componentsPanel/CssEval.svelte create mode 100644 frontend/src/lib/components/apps/editor/componentsPanel/CssHelperPanel.svelte create mode 100644 frontend/src/lib/components/apps/editor/componentsPanel/ThemeCodePreview.svelte create mode 100644 frontend/src/lib/components/apps/editor/componentsPanel/ThemeDrawer.svelte create mode 100644 frontend/src/lib/components/apps/editor/componentsPanel/ThemeList.svelte create mode 100644 frontend/src/lib/components/apps/editor/componentsPanel/ThemeNameEditor.svelte create mode 100644 frontend/src/lib/components/apps/editor/componentsPanel/ThemeRow.svelte create mode 100644 frontend/src/lib/components/apps/editor/componentsPanel/cssUtils.ts create mode 100644 frontend/src/lib/components/apps/editor/componentsPanel/themeUtils.ts create mode 100644 frontend/src/lib/components/apps/editor/settingsPanel/CSSMigrationModal.svelte create mode 100644 frontend/src/lib/components/apps/editor/settingsPanel/CssPropertyWrapper.svelte diff --git a/.github/DockerfileBackendTests b/.github/DockerfileBackendTests index 00082bf42f..89ef4b79e2 100644 --- a/.github/DockerfileBackendTests +++ b/.github/DockerfileBackendTests @@ -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 \ diff --git a/backend/.sqlx/query-584cb984ea6528baac48c5c437ad2ee3bef92e3fada73dcf519147964c0f4f4a.json b/backend/.sqlx/query-584cb984ea6528baac48c5c437ad2ee3bef92e3fada73dcf519147964c0f4f4a.json new file mode 100644 index 0000000000..c4d5eb1f75 --- /dev/null +++ b/backend/.sqlx/query-584cb984ea6528baac48c5c437ad2ee3bef92e3fada73dcf519147964c0f4f4a.json @@ -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" +} diff --git a/backend/.sqlx/query-d956b7525f83e6d03beadc4bb3ee2798f53d990b01b17bdbc044719d4908e3f4.json b/backend/.sqlx/query-d956b7525f83e6d03beadc4bb3ee2798f53d990b01b17bdbc044719d4908e3f4.json new file mode 100644 index 0000000000..a1e9f97ed7 --- /dev/null +++ b/backend/.sqlx/query-d956b7525f83e6d03beadc4bb3ee2798f53d990b01b17bdbc044719d4908e3f4.json @@ -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" +} diff --git a/backend/migrations/20230915084031_add_themes.down.sql b/backend/migrations/20230915084031_add_themes.down.sql new file mode 100644 index 0000000000..d2f607c5b8 --- /dev/null +++ b/backend/migrations/20230915084031_add_themes.down.sql @@ -0,0 +1 @@ +-- Add down migration script here diff --git a/backend/migrations/20230915084031_add_themes.up.sql b/backend/migrations/20230915084031_add_themes.up.sql new file mode 100644 index 0000000000..323b31a049 --- /dev/null +++ b/backend/migrations/20230915084031_add_themes.up.sql @@ -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 \ No newline at end of file diff --git a/backend/windmill-api/openapi.yaml b/backend/windmill-api/openapi.yaml index 479ebc23a1..4dd7f35b95 100644 --- a/backend/windmill-api/openapi.yaml +++ b/backend/windmill-api/openapi.yaml @@ -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 diff --git a/backend/windmill-api/src/apps.rs b/backend/windmill-api/src/apps.rs index 1e8e3e8768..370b093f7c 100644 --- a/backend/windmill-api/src/apps.rs +++ b/backend/windmill-api/src/apps.rs @@ -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, + Path((w_id, path)): Path<(String, StripPath)>, +) -> JsonResult> { + 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, diff --git a/backend/windmill-api/src/workspaces.rs b/backend/windmill-api/src/workspaces.rs index 019e658efc..97a289580a 100644 --- a/backend/windmill-api/src/workspaces.rs +++ b/backend/windmill-api/src/workspaces.rs @@ -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, diff --git a/backend/windmill-worker/src/worker.rs b/backend/windmill-worker/src/worker.rs index ba88e79349..d4466622f1 100644 --- a/backend/windmill-worker/src/worker.rs +++ b/backend/windmill-worker/src/worker.rs @@ -608,7 +608,7 @@ pub async fn run_workerDownload diff --git a/frontend/src/lib/components/Editor.svelte b/frontend/src/lib/components/Editor.svelte index 06915ee038..2d7e85a544 100644 --- a/frontend/src/lib/components/Editor.svelte +++ b/frontend/src/lib/components/Editor.svelte @@ -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 diff --git a/frontend/src/lib/components/HighlightCode.svelte b/frontend/src/lib/components/HighlightCode.svelte index 4602e7fe8f..977115b830 100644 --- a/frontend/src/lib/components/HighlightCode.svelte +++ b/frontend/src/lib/components/HighlightCode.svelte @@ -42,6 +42,7 @@ return sql case 'powershell': return powershell + default: return typescript } diff --git a/frontend/src/lib/components/SimpleEditor.svelte b/frontend/src/lib/components/SimpleEditor.svelte index 4319c8893f..c41362f872 100644 --- a/frontend/src/lib/components/SimpleEditor.svelte +++ b/frontend/src/lib/components/SimpleEditor.svelte @@ -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: [] } + } + }) } } diff --git a/frontend/src/lib/components/TableSimple.svelte b/frontend/src/lib/components/TableSimple.svelte index 464be0a514..49f35f0edb 100644 --- a/frontend/src/lib/components/TableSimple.svelte +++ b/frontend/src/lib/components/TableSimple.svelte @@ -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' -
- +
+ {#if headers} diff --git a/frontend/src/lib/components/Tooltip.svelte b/frontend/src/lib/components/Tooltip.svelte index a97fb3b67e..cc8ac82c15 100644 --- a/frontend/src/lib/components/Tooltip.svelte +++ b/frontend/src/lib/components/Tooltip.svelte @@ -7,6 +7,7 @@ export let wrapperClass = '' export let placement: PopoverPlacement | undefined = undefined export let documentationLink: string | undefined = undefined + export let small = false @@ -15,7 +16,7 @@ ? 'text-tertiary-inverse' : 'text-tertiary'} {$$props.class} relative" > - +
diff --git a/frontend/src/lib/components/apps/components/buttons/AppButton.svelte b/frontend/src/lib/components/apps/components/buttons/AppButton.svelte index fb5b6eafad..59c0b2a4d6 100644 --- a/frontend/src/lib/components/apps/components/buttons/AppButton.svelte +++ b/frontend/src/lib/components/apps/components/buttons/AppButton.svelte @@ -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) {#each Object.keys(components['buttoncomponent'].initialData.configuration) as key (key)} @@ -136,6 +137,16 @@ /> {/each} +{#each Object.keys(css ?? {}) as key (key)} + +{/each} + - + {#if errorsMessage}
{errorsMessage}
{/if}