mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-08-23 00:00:33 +00:00
feat(apps): copy paste across apps
This commit is contained in:
@@ -3,7 +3,6 @@
|
||||
import Tooltip from '$lib/components/Tooltip.svelte'
|
||||
import { Clipboard } from 'lucide-svelte'
|
||||
import { getContext } from 'svelte'
|
||||
import { get } from 'svelte/store'
|
||||
import { twMerge } from 'tailwind-merge'
|
||||
import { copyToClipboard } from '../../../../utils'
|
||||
import Button from '../../../common/button/Button.svelte'
|
||||
@@ -11,12 +10,7 @@
|
||||
import { initConfig, initOutput } from '../../editor/appUtils'
|
||||
import { components } from '../../editor/component'
|
||||
import type { AppInput } from '../../inputType'
|
||||
import type {
|
||||
AppEditorContext,
|
||||
AppViewerContext,
|
||||
ComponentCustomCSS,
|
||||
RichConfigurations
|
||||
} from '../../types'
|
||||
import type { AppViewerContext, ComponentCustomCSS, RichConfigurations } from '../../types'
|
||||
import AlignWrapper from '../helpers/AlignWrapper.svelte'
|
||||
import ResolveConfig from '../helpers/ResolveConfig.svelte'
|
||||
import RunnableWrapper from '../helpers/RunnableWrapper.svelte'
|
||||
@@ -39,8 +33,6 @@
|
||||
const { app, worldStore, mode, componentControl } =
|
||||
getContext<AppViewerContext>('AppViewerContext')
|
||||
|
||||
const editorcontext = getContext<AppEditorContext>('AppEditorContext')
|
||||
|
||||
let result: string | undefined = undefined
|
||||
|
||||
if (componentInput?.type == 'template' && !isCodeInjection(componentInput.eval)) {
|
||||
@@ -133,7 +125,7 @@
|
||||
function onInput(e: Event) {
|
||||
const target = e.target as HTMLTextAreaElement
|
||||
|
||||
if (target.value) {
|
||||
if (target.value != undefined) {
|
||||
$componentControl[id]?.setCode?.(target.value)
|
||||
autosize()
|
||||
}
|
||||
@@ -210,14 +202,6 @@
|
||||
componentInput?.type == 'template'
|
||||
? 'cursor-text'
|
||||
: ''}"
|
||||
on:click={() => {
|
||||
if ($mode === 'dnd' && componentInput?.type == 'template') {
|
||||
let ontextfocus = editorcontext?.ontextfocus
|
||||
if (ontextfocus) {
|
||||
get(ontextfocus)?.()
|
||||
}
|
||||
}
|
||||
}}
|
||||
>
|
||||
<svelte:element
|
||||
this={component}
|
||||
|
||||
@@ -73,7 +73,6 @@
|
||||
$: lastInput && $worldStore && debounce(handleConnection)
|
||||
|
||||
const debounceTemplate = async () => {
|
||||
console.log('template')
|
||||
let nvalue = await getValue(lastInput)
|
||||
if (!deepEqual(nvalue, value)) {
|
||||
value = nvalue
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
outputs?.result?.set(v, true)
|
||||
}
|
||||
|
||||
$: result && outputs && setOutput(result)
|
||||
$: result != undefined && outputs && setOutput(result)
|
||||
</script>
|
||||
|
||||
<InitializeComponent {id} />
|
||||
|
||||
@@ -111,7 +111,6 @@
|
||||
setContext<AppEditorContext>('AppEditorContext', {
|
||||
history,
|
||||
pickVariableCallback,
|
||||
ontextfocus: writable(undefined),
|
||||
movingcomponents: writable(undefined),
|
||||
selectedComponentInEditor: writable(undefined)
|
||||
})
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
import { push } from '$lib/history'
|
||||
import { sendUserToast } from '$lib/toast'
|
||||
import { gridColumns } from '../../gridUtils'
|
||||
import { copyToClipboard } from '$lib/utils'
|
||||
|
||||
const { app, selectedComponent, focusedGrid, componentControl } =
|
||||
getContext<AppViewerContext>('AppViewerContext')
|
||||
@@ -17,8 +18,8 @@
|
||||
const { history, movingcomponents } = getContext<AppEditorContext>('AppEditorContext')
|
||||
|
||||
let tempGridItems: GridItem[] | undefined = undefined
|
||||
let copiedGridItems: GridItem[] | undefined = undefined
|
||||
|
||||
const ITEM_TYPE = 'wm-grid-items'
|
||||
function getSortedGridItemsOfChildren(): GridItem[] {
|
||||
if (!$focusedGrid) {
|
||||
return $app.grid
|
||||
@@ -127,14 +128,33 @@
|
||||
}
|
||||
}
|
||||
|
||||
function handleCopy(event: KeyboardEvent) {
|
||||
async function handleCopy(event: KeyboardEvent) {
|
||||
if (!$selectedComponent) {
|
||||
return
|
||||
}
|
||||
tempGridItems = undefined
|
||||
copiedGridItems = $selectedComponent
|
||||
const copiedGridItems = $selectedComponent
|
||||
.map((x) => findGridItem($app, x))
|
||||
.filter((x) => x != undefined) as GridItem[]
|
||||
|
||||
copyGridItemsToClipboard(copiedGridItems)
|
||||
}
|
||||
|
||||
function copyGridItemsToClipboard(items: GridItem[]) {
|
||||
let allSubgrids = {}
|
||||
for (let item of items) {
|
||||
let subgrids = getAllSubgridsAndComponentIds($app, item.data)[0]
|
||||
for (let key of subgrids) {
|
||||
allSubgrids[key] = $app.subgrids?.[key]
|
||||
}
|
||||
}
|
||||
let success = copyToClipboard(
|
||||
JSON.stringify({ type: ITEM_TYPE, items, subgrids: allSubgrids }),
|
||||
false
|
||||
)
|
||||
if (!success) {
|
||||
sendUserToast('Could not copy to clipboard. Are you in an unsecure context?', true)
|
||||
}
|
||||
}
|
||||
|
||||
function handleCut(event: KeyboardEvent) {
|
||||
@@ -147,7 +167,7 @@
|
||||
let gridItems = $selectedComponent
|
||||
.map((x) => findGridItem($app, x))
|
||||
.filter((x) => x != undefined) as GridItem[]
|
||||
copiedGridItems = gridItems
|
||||
copyGridItemsToClipboard(gridItems)
|
||||
|
||||
if (!gridItems) {
|
||||
return
|
||||
@@ -157,9 +177,34 @@
|
||||
tempGridItems = gridItems
|
||||
}
|
||||
|
||||
function handlePaste(event: KeyboardEvent) {
|
||||
export async function handlePaste(event: ClipboardEvent) {
|
||||
let classes = event.target?.['className']
|
||||
if (
|
||||
(typeof classes === 'string' && classes.includes('inputarea')) ||
|
||||
['INPUT', 'TEXTAREA'].includes(document.activeElement?.tagName!)
|
||||
) {
|
||||
return
|
||||
}
|
||||
event.preventDefault()
|
||||
|
||||
push(history, $app)
|
||||
$movingcomponents = undefined
|
||||
let copiedGridItems: GridItem[] | undefined = undefined
|
||||
let subgrids = $app.subgrids ?? {}
|
||||
const txt = event?.clipboardData?.getData('text')
|
||||
if (txt) {
|
||||
try {
|
||||
const parsed = JSON.parse(txt)
|
||||
if (parsed.type == ITEM_TYPE) {
|
||||
copiedGridItems = parsed.items
|
||||
subgrids = parsed.subgrids ?? subgrids
|
||||
console.log('subgrids', subgrids)
|
||||
} else {
|
||||
copiedGridItems = undefined
|
||||
}
|
||||
} catch {}
|
||||
}
|
||||
|
||||
if (tempGridItems != undefined) {
|
||||
for (let tempGridItem of tempGridItems) {
|
||||
if (
|
||||
@@ -190,14 +235,14 @@
|
||||
tempGridItem.id
|
||||
)
|
||||
}
|
||||
copiedGridItems = tempGridItems
|
||||
copyGridItemsToClipboard(tempGridItems)
|
||||
$selectedComponent = tempGridItems.map((x) => x.id)
|
||||
|
||||
tempGridItems = undefined
|
||||
} else if (copiedGridItems) {
|
||||
let nitems: string[] = []
|
||||
for (let copiedGridItem of copiedGridItems) {
|
||||
let newItem = copyComponent(copiedGridItem, $focusedGrid)
|
||||
let newItem = copyComponent(copiedGridItem, $focusedGrid, subgrids)
|
||||
newItem && nitems.push(newItem)
|
||||
}
|
||||
$selectedComponent = nitems.map((x) => x)
|
||||
@@ -206,7 +251,12 @@
|
||||
$app = $app
|
||||
}
|
||||
|
||||
function copyComponent(item: GridItem, parentGrid: FocusedGrid | undefined, doNotVisit?: string) {
|
||||
function copyComponent(
|
||||
item: GridItem,
|
||||
parentGrid: FocusedGrid | undefined,
|
||||
subgrids: Record<string, GridItem[]>,
|
||||
doNotVisit?: string
|
||||
) {
|
||||
if (item.id === doNotVisit) {
|
||||
return
|
||||
}
|
||||
@@ -217,17 +267,17 @@
|
||||
Object.fromEntries(gridColumns.map((column) => [column, item[column]]))
|
||||
)
|
||||
|
||||
if ($app.subgrids && item.data.numberOfSubgrids) {
|
||||
for (let i = 0; i < item.data.numberOfSubgrids; i++) {
|
||||
$app.subgrids[`${item.id}-${i}`].forEach((subgridItem) => {
|
||||
copyComponent(
|
||||
subgridItem,
|
||||
{ parentComponentId: newItem, subGridIndex: i },
|
||||
doNotVisit ?? newItem
|
||||
)
|
||||
})
|
||||
}
|
||||
for (let i = 0; i < (item?.data?.numberOfSubgrids ?? 0); i++) {
|
||||
subgrids[`${item.id}-${i}`].forEach((subgridItem) => {
|
||||
copyComponent(
|
||||
subgridItem,
|
||||
{ parentComponentId: newItem, subGridIndex: i },
|
||||
subgrids,
|
||||
doNotVisit ?? newItem
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
return newItem
|
||||
}
|
||||
|
||||
@@ -271,12 +321,6 @@
|
||||
}
|
||||
break
|
||||
|
||||
case 'v':
|
||||
if (event.ctrlKey || event.metaKey) {
|
||||
handlePaste(event)
|
||||
}
|
||||
break
|
||||
|
||||
case 'x':
|
||||
if (event.ctrlKey || event.metaKey) {
|
||||
handleCut(event)
|
||||
@@ -289,4 +333,4 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<svelte:window on:keydown={keydown} />
|
||||
<svelte:window on:keydown={keydown} on:paste={handlePaste} />
|
||||
|
||||
@@ -37,8 +37,6 @@
|
||||
export let noGrid = false
|
||||
export let duplicateMoveAllowed = true
|
||||
|
||||
let editor: TemplateEditor | undefined = undefined
|
||||
|
||||
const {
|
||||
app,
|
||||
runnableComponents,
|
||||
@@ -52,10 +50,7 @@
|
||||
componentControl
|
||||
} = getContext<AppViewerContext>('AppViewerContext')
|
||||
|
||||
const { history, ontextfocus, movingcomponents } =
|
||||
getContext<AppEditorContext>('AppEditorContext')
|
||||
|
||||
$: editor && ($ontextfocus = () => editor?.focus())
|
||||
const { history, movingcomponents } = getContext<AppEditorContext>('AppEditorContext')
|
||||
|
||||
function removeGridElement() {
|
||||
push(history, $app)
|
||||
@@ -174,7 +169,6 @@
|
||||
{:else if componentSettings.item.data.componentInput.type === 'template' && componentSettings.item.data.componentInput !== undefined}
|
||||
<div class="py-1 min-h-[28px] rounded border border-1 border-gray-500">
|
||||
<TemplateEditor
|
||||
bind:this={editor}
|
||||
fontSize={12}
|
||||
bind:code={componentSettings.item.data.componentInput.eval}
|
||||
{extraLib}
|
||||
|
||||
@@ -206,7 +206,6 @@ export type AppViewerContext = {
|
||||
export type AppEditorContext = {
|
||||
history: History<App> | undefined
|
||||
pickVariableCallback: Writable<((path: string) => void) | undefined>
|
||||
ontextfocus: Writable<(() => void) | undefined>
|
||||
selectedComponentInEditor: Writable<string | undefined>
|
||||
movingcomponents: Writable<string[] | undefined>
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user