From 89abb68f635d062778485c15d88fd6272ebd40a2 Mon Sep 17 00:00:00 2001 From: Faton Ramadani Date: Tue, 28 Nov 2023 22:37:11 +0100 Subject: [PATCH] feat(frontend): Stat card improvement (#2709) * feat(frontend): add class+ style support + added support for custom images * feat(frontend): add class+ style support + added support for custom images * feat(frontend): fix styling * feat(frontend): add quick style buttons * feat(frontend): fix quick tailwind class * feat(frontend): add space only if needed * feat(frontend): fix build * feat(frontend): fix line heights --- .../components/display/AppStatCard.svelte | 71 +++- .../apps/editor/component/components.ts | 56 ++- .../editor/componentsPanel/CssProperty.svelte | 348 ++++++++++-------- .../OneOfInputSpecsEditor.svelte | 1 + frontend/src/lib/components/apps/types.ts | 4 +- frontend/src/lib/components/apps/utils.ts | 17 + 6 files changed, 321 insertions(+), 176 deletions(-) diff --git a/frontend/src/lib/components/apps/components/display/AppStatCard.svelte b/frontend/src/lib/components/apps/components/display/AppStatCard.svelte index 37f35b39f9..54425d6fd3 100644 --- a/frontend/src/lib/components/apps/components/display/AppStatCard.svelte +++ b/frontend/src/lib/components/apps/components/display/AppStatCard.svelte @@ -2,13 +2,14 @@ import { getContext } from 'svelte' import { initConfig, initOutput } from '../../editor/appUtils' import type { AppViewerContext, ComponentCustomCSS, RichConfigurations } from '../../types' - import { initCss } from '../../utils' + import { getImageDataURL, initCss } from '../../utils' import { components } from '../../editor/component' import ResolveConfig from '../helpers/ResolveConfig.svelte' import ResolveStyle from '../helpers/ResolveStyle.svelte' import { ArrowDown } from 'lucide-svelte' import { twMerge } from 'tailwind-merge' import { loadIcon } from '../icon' + import Loader from '../helpers/Loader.svelte' export let id: string export let configuration: RichConfigurations @@ -28,13 +29,15 @@ let iconComponent: any - $: resolvedConfig.icon && handleIcon() + $: isIcon && resolvedConfig?.media?.configuration?.icon.icon && handleIcon() async function handleIcon() { - if (resolvedConfig.icon) { - iconComponent = await loadIcon(resolvedConfig.icon) + if (resolvedConfig?.media?.configuration?.icon.icon) { + iconComponent = await loadIcon(resolvedConfig.media.configuration.icon.icon) } } + + $: isIcon = resolvedConfig.media?.selected == 'icon' {#each Object.keys(components['statcomponent'].initialData.configuration) as key (key)} @@ -57,16 +60,60 @@ {/each} {#if render} -
- {#if resolvedConfig.icon && iconComponent} -
- -
- {/if} +
+
+ {#if isIcon} + {#if resolvedConfig.media && iconComponent} + + {/if} + {:else} + + {resolvedConfig.title} + + {/if} +
+
-
{resolvedConfig.title}
+
+ {resolvedConfig.title} +
-
+
{resolvedConfig?.value}
diff --git a/frontend/src/lib/components/apps/editor/component/components.ts b/frontend/src/lib/components/apps/editor/component/components.ts index 4416ff8c6d..77f1263fe2 100644 --- a/frontend/src/lib/components/apps/editor/component/components.ts +++ b/frontend/src/lib/components/apps/editor/component/components.ts @@ -2721,7 +2721,22 @@ This is a paragraph. icon: FileBarChart, documentationLink: `${documentationBaseUrl}/statistic_card`, dims: '2:4-3:4' as AppComponentDimensions, - customCss: {}, + customCss: { + title: { + class: '', + style: '', + quickCss: ['font-size: 1rem', 'font-size: 1.5rem', 'font-size: 2rem'], + quickTailwindClasses: ['text-xs', 'text-sm', 'text-base', 'text-lg', 'text-xl', 'text-2xl'] + }, + container: { class: '', style: '' }, + value: { + class: '', + style: '', + quickCss: ['font-size: 1rem', 'font-size: 1.5rem', 'font-size: 2rem'], + quickTailwindClasses: ['text-xs', 'text-sm', 'text-base', 'text-lg', 'text-xl', 'text-2xl'] + }, + media: { class: '', style: '' } + }, initialData: { configuration: { title: { @@ -2739,11 +2754,40 @@ This is a paragraph. value: 0, fieldType: 'number' }, - icon: { - type: 'static', - value: undefined, - fieldType: 'icon-select' - } + media: { + type: 'oneOf', + selected: 'image', + labels: { + icon: 'Icon', + image: 'Image' + }, + configuration: { + icon: { + icon: { + type: 'static', + value: undefined, + fieldType: 'icon-select' + } + }, + image: { + source: { + type: 'static', + value: '/logo.svg', + fieldType: 'text', + fileUpload: { + accept: 'image/*', + convertTo: 'base64' + } + }, + sourceKind: { + fieldType: 'select', + type: 'static', + selectOptions: selectOptions.imageSourceKind, + value: 'url' as (typeof selectOptions.imageSourceKind)[number] + } + } + } + } as const } } } diff --git a/frontend/src/lib/components/apps/editor/componentsPanel/CssProperty.svelte b/frontend/src/lib/components/apps/editor/componentsPanel/CssProperty.svelte index f8f5246085..5de6011b88 100644 --- a/frontend/src/lib/components/apps/editor/componentsPanel/CssProperty.svelte +++ b/frontend/src/lib/components/apps/editor/componentsPanel/CssProperty.svelte @@ -47,173 +47,207 @@ } let dynamicClass: boolean = value?.evalClass !== undefined + let render = 0 -
-
-
-
- {addWhitespaceBeforeCapitals(name)} -
- {#if shouldDisplayLeft} - - {/if} - {#if shouldDisplayRight} - - {/if} -
- {#if wmClass} - -
- {wmClass} +{#key render} +
+
+
+
+ {addWhitespaceBeforeCapitals(name)} +
+ {#if shouldDisplayLeft} -
- - {/if} -
-
- -{#if value} -
- {#if tooltip} -
{tooltip}
- {/if} - {#if value.style !== undefined || forceStyle} -
- - - {#if quickStyleProperties?.length && isQuickMenuOpen} -
- -
+ {/if}
- {/if} - - {#if value.class !== undefined || forceClass} - - - {/if} - - { - if (e.detail && !value.evalClass) { - value.evalClass = { - type: 'evalv2', - expr: '', - connections: [], - fieldType: 'text' - } - } else { - value.evalClass = undefined - } - }} - /> - - {#if value?.evalClass && dynamicClass} - - {/if} + {#if wmClass} + +
+ {wmClass} + +
+
+ {/if} +
-{/if} + + {#if value} +
+ {#if tooltip} +
{tooltip}
+ {/if} + {#if value.style !== undefined || forceStyle} +
+ + + {#if quickStyleProperties?.length && isQuickMenuOpen} +
+ +
+ {/if} + {#if value.quickCss} +
+ {#each value.quickCss as v} + { + value.style = value.style === '' ? `${v};` : `${value.style} ${v};` + }} + > + {v} + + {/each} +
+ {/if} +
+ {/if} + + {#if value.class !== undefined || forceClass} + + + {/if} + + { + if (e.detail && !value.evalClass) { + value.evalClass = { + type: 'evalv2', + expr: '', + connections: [], + fieldType: 'text' + } + } else { + value.evalClass = undefined + } + }} + /> + + {#if value?.evalClass && dynamicClass} + + {/if} +
+ {/if} +{/key} diff --git a/frontend/src/lib/components/apps/editor/settingsPanel/OneOfInputSpecsEditor.svelte b/frontend/src/lib/components/apps/editor/settingsPanel/OneOfInputSpecsEditor.svelte index 9cfa89e862..0fbdf8596d 100644 --- a/frontend/src/lib/components/apps/editor/settingsPanel/OneOfInputSpecsEditor.svelte +++ b/frontend/src/lib/components/apps/editor/settingsPanel/OneOfInputSpecsEditor.svelte @@ -89,6 +89,7 @@ placeholder={config?.['placeholder']} customTitle={config?.['customTitle']} tooltip={config?.['tooltip']} + fileUpload={config?.['fileUpload']} /> {/if} {/each} diff --git a/frontend/src/lib/components/apps/types.ts b/frontend/src/lib/components/apps/types.ts index 3799bd808b..a36692601b 100644 --- a/frontend/src/lib/components/apps/types.ts +++ b/frontend/src/lib/components/apps/types.ts @@ -44,6 +44,8 @@ export type ComponentCssProperty = { class?: string style?: string evalClass?: RichConfiguration + quickCss?: readonly string[] | undefined + quickTailwindClasses?: readonly string[] | undefined } export type ComponentCustomCSS = Partial< @@ -173,7 +175,7 @@ export type ListContext = Writable<{ disabled: boolean }> -export type ListInputs = {set: (id: string, value: any) => void, remove: (id: string) => void} +export type ListInputs = { set: (id: string, value: any) => void; remove: (id: string) => void } export type GroupContext = Writable> diff --git a/frontend/src/lib/components/apps/utils.ts b/frontend/src/lib/components/apps/utils.ts index dcc0c462da..b7eee7c3f7 100644 --- a/frontend/src/lib/components/apps/utils.ts +++ b/frontend/src/lib/components/apps/utils.ts @@ -365,3 +365,20 @@ export function transformBareBase64IfNecessary(source: string | undefined) { return `data:application/octet-stream;base64,${source}` } } + +export function getImageDataURL(imageKind: string | undefined, image: string | undefined) { + if (!imageKind || !image) { + return null + } + + switch (imageKind) { + case 'png encoded as base64': + return 'data:image/png;base64,' + image + case 'jpeg encoded as base64': + return 'data:image/jpeg;base64,' + image + case 'svg encoded as base64': + return 'data:image/svg+xml;base64,' + image + default: + return image + } +}