mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-09-11 16:09:39 +00:00
feat(frontend): app textcomponent editable + tooltip
This commit is contained in:
@@ -577,6 +577,10 @@
|
||||
})
|
||||
}
|
||||
|
||||
export function focus() {
|
||||
editor?.focus()
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
if (browser) {
|
||||
loadMonaco()
|
||||
|
||||
@@ -8,10 +8,10 @@
|
||||
import InputValue from '../helpers/InputValue.svelte'
|
||||
import RunnableWrapper from '../helpers/RunnableWrapper.svelte'
|
||||
import { twMerge } from 'tailwind-merge'
|
||||
import type { AppViewerContext, ComponentCustomCSS } from '../../types'
|
||||
import type { AppEditorContext, AppViewerContext, ComponentCustomCSS } from '../../types'
|
||||
import { getContext } from 'svelte'
|
||||
import ResizeWrapper from '../helpers/ResizeWrapper.svelte'
|
||||
import { initOutput } from '../../editor/appUtils'
|
||||
import Tooltip from '$lib/components/Tooltip.svelte'
|
||||
|
||||
export let id: string
|
||||
export let componentInput: AppInput | undefined
|
||||
@@ -22,7 +22,9 @@
|
||||
export let customCss: ComponentCustomCSS<'text'> | undefined = undefined
|
||||
export let render: boolean
|
||||
|
||||
const { app, worldStore } = getContext<AppViewerContext>('AppViewerContext')
|
||||
const { app, worldStore, mode } = getContext<AppViewerContext>('AppViewerContext')
|
||||
|
||||
const { ontextfocus } = getContext<AppEditorContext>('AppEditorContext')
|
||||
|
||||
initOutput($worldStore, id, {
|
||||
result: undefined,
|
||||
@@ -32,7 +34,7 @@
|
||||
let result: string | undefined = undefined
|
||||
let style: 'Title' | 'Subtitle' | 'Body' | 'Caption' | 'Label' | undefined = undefined
|
||||
let copyButton: boolean
|
||||
let fitContent = false
|
||||
let tooltip: string = ''
|
||||
|
||||
function getComponent() {
|
||||
switch (style) {
|
||||
@@ -70,17 +72,27 @@
|
||||
|
||||
<InputValue {id} input={configuration.style} bind:value={style} />
|
||||
<InputValue {id} input={configuration.copyButton} bind:value={copyButton} />
|
||||
<InputValue {id} input={configuration.fitContent} bind:value={fitContent} />
|
||||
<InputValue {id} input={configuration.tooltip} bind:value={tooltip} />
|
||||
|
||||
<RunnableWrapper {render} {componentInput} {id} bind:initializing bind:result>
|
||||
<ResizeWrapper {id} shouldWrap={fitContent}>
|
||||
<div class="h-full w-full overflow-hidden">
|
||||
<AlignWrapper {horizontalAlignment} {verticalAlignment}>
|
||||
{#if !result || result === ''}
|
||||
<div class="text-gray-400 bg-gray-100 flex justify-center items-center h-full w-full">
|
||||
No text
|
||||
</div>
|
||||
{:else}
|
||||
<div class="flex flex-wrap gap-2 pb-0.5 overflow-x-auto">
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<div
|
||||
class="flex flex-wrap gap-2 pb-0.5 {$mode === 'dnd' && componentInput?.type == 'template'
|
||||
? 'cursor-text'
|
||||
: ''}"
|
||||
on:click={() => {
|
||||
if ($mode === 'dnd' && componentInput?.type == 'template') {
|
||||
$ontextfocus?.()
|
||||
}
|
||||
}}
|
||||
>
|
||||
<svelte:element
|
||||
this={component}
|
||||
class={twMerge(
|
||||
@@ -92,17 +104,26 @@
|
||||
style={[$app.css?.['textcomponent']?.['text']?.style, customCss?.text?.style].join(';')}
|
||||
>
|
||||
{String(result)}
|
||||
{#if tooltip != ''}
|
||||
<Tooltip>{tooltip}</Tooltip>
|
||||
{/if}
|
||||
{#if copyButton && result}
|
||||
<Popover notClickable>
|
||||
<Button
|
||||
variant="border"
|
||||
size="xs"
|
||||
color="dark"
|
||||
btnClasses="!p-1"
|
||||
on:click={() => copyToClipboard(result)}
|
||||
>
|
||||
<Clipboard size={14} strokeWidth={2} />
|
||||
</Button>
|
||||
<svelte:fragment slot="text">Copy to clipboard</svelte:fragment>
|
||||
</Popover>
|
||||
{/if}
|
||||
</svelte:element>
|
||||
{#if copyButton && result}
|
||||
<Popover notClickable>
|
||||
<Button size="xs" btnClasses="!px-2" on:click={() => copyToClipboard(result)}>
|
||||
<Clipboard size={14} strokeWidth={2} />
|
||||
</Button>
|
||||
<svelte:fragment slot="text">Copy to clipboard</svelte:fragment>
|
||||
</Popover>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
</AlignWrapper>
|
||||
</ResizeWrapper>
|
||||
</div>
|
||||
</RunnableWrapper>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
export let id: string
|
||||
export let shouldWrap: boolean = false
|
||||
const { app, breakpoint } = getContext<AppViewerContext>('AppViewerContext')
|
||||
const { app, breakpoint, mode } = getContext<AppViewerContext>('AppViewerContext')
|
||||
|
||||
$: gridItem = findGridItem($app, id)
|
||||
|
||||
@@ -16,13 +16,12 @@
|
||||
const wrapperHeight = wrapper.getBoundingClientRect().height
|
||||
const width = $breakpoint === 'sm' ? 3 : 12
|
||||
gridItem[width].h = Math.ceil(wrapperHeight / 36)
|
||||
gridItem = gridItem
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if shouldWrap}
|
||||
<div bind:this={wrapper}>
|
||||
{#if shouldWrap && $mode !== 'preview'}
|
||||
<div class="h-full w-full" bind:this={wrapper}>
|
||||
<slot />
|
||||
</div>
|
||||
{:else}
|
||||
|
||||
@@ -98,7 +98,8 @@
|
||||
|
||||
setContext<AppEditorContext>('AppEditorContext', {
|
||||
history,
|
||||
pickVariableCallback
|
||||
pickVariableCallback,
|
||||
ontextfocus: writable(undefined)
|
||||
})
|
||||
|
||||
let timeout: NodeJS.Timeout | undefined = undefined
|
||||
|
||||
@@ -72,7 +72,8 @@
|
||||
|
||||
setContext<AppEditorContext>('AppEditorContext', {
|
||||
history: undefined,
|
||||
pickVariableCallback: writable(undefined)
|
||||
pickVariableCallback: writable(undefined),
|
||||
ontextfocus: writable(undefined)
|
||||
})
|
||||
|
||||
let ncontext = context
|
||||
|
||||
@@ -205,7 +205,7 @@ export const components: Record<AppComponent['type'], AppComponentConfig> = {
|
||||
icon: Type,
|
||||
dims: '1:1-3:1',
|
||||
data: {
|
||||
softWrap: false,
|
||||
softWrap: true,
|
||||
horizontalAlignment: 'left',
|
||||
verticalAlignment: 'top',
|
||||
id: '',
|
||||
@@ -229,11 +229,12 @@ export const components: Record<AppComponent['type'], AppComponentConfig> = {
|
||||
fieldType: 'boolean',
|
||||
onlyStatic: true
|
||||
},
|
||||
fitContent: {
|
||||
tooltip: {
|
||||
type: 'static',
|
||||
value: false,
|
||||
fieldType: 'boolean',
|
||||
onlyStatic: true
|
||||
value: '',
|
||||
fieldType: 'text',
|
||||
onlyStatic: true,
|
||||
tooltip: 'Tooltip text if not empty'
|
||||
}
|
||||
},
|
||||
customCss: {
|
||||
|
||||
@@ -33,10 +33,14 @@
|
||||
export let noGrid = false
|
||||
export let duplicateMoveAllowed = true
|
||||
|
||||
let editor: TemplateEditor | undefined = undefined
|
||||
|
||||
const { app, runnableComponents, selectedComponent, worldStore, focusedGrid, stateId, state } =
|
||||
getContext<AppViewerContext>('AppViewerContext')
|
||||
|
||||
const { history } = getContext<AppEditorContext>('AppEditorContext')
|
||||
const { history, ontextfocus } = getContext<AppEditorContext>('AppEditorContext')
|
||||
|
||||
$: editor && ($ontextfocus = () => editor?.focus())
|
||||
|
||||
function removeGridElement() {
|
||||
push(history, $app)
|
||||
@@ -109,7 +113,12 @@
|
||||
<StaticInputEditor bind:componentInput={component.componentInput} />
|
||||
{:else if component.componentInput.type === 'template' && component.componentInput !== undefined}
|
||||
<div class="py-1 min-h-[28px] rounded border border-1 border-gray-500">
|
||||
<TemplateEditor fontSize={12} bind:code={component.componentInput.eval} {extraLib} />
|
||||
<TemplateEditor
|
||||
bind:this={editor}
|
||||
fontSize={12}
|
||||
bind:code={component.componentInput.eval}
|
||||
{extraLib}
|
||||
/>
|
||||
</div>
|
||||
{:else if component.componentInput.type === 'connected' && component.componentInput !== undefined}
|
||||
<ConnectedInputEditor bind:componentInput={component.componentInput} />
|
||||
|
||||
@@ -142,6 +142,7 @@ export type AppViewerContext = {
|
||||
export type AppEditorContext = {
|
||||
history: History<App> | undefined
|
||||
pickVariableCallback: Writable<((path: string) => void) | undefined>
|
||||
ontextfocus: Writable<(() => void) | undefined>
|
||||
}
|
||||
|
||||
export type FocusedGrid = { parentComponentId: string; subGridIndex: number }
|
||||
|
||||
Reference in New Issue
Block a user