fix tab selection on app builder
This commit is contained in:
@@ -343,7 +343,6 @@
|
||||
on:resultSet={(e) => {
|
||||
const res = e.detail
|
||||
if ($initialized?.runnableInitialized?.[fullId] === undefined) {
|
||||
console.log('resultSet', id)
|
||||
$initialized.runnableInitialized = {
|
||||
...($initialized.runnableInitialized ?? {}),
|
||||
[fullId]: res
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
import InitializeComponent from '../helpers/InitializeComponent.svelte'
|
||||
import { twMerge } from 'tailwind-merge'
|
||||
import ResolveStyle from '../helpers/ResolveStyle.svelte'
|
||||
import { deepEqual } from 'fast-equals'
|
||||
|
||||
interface Props {
|
||||
id: string
|
||||
@@ -121,8 +122,24 @@
|
||||
}
|
||||
}
|
||||
|
||||
let lastTabs: string[] | undefined = tabs
|
||||
$effect.pre(() => {
|
||||
if (!untrack(() => selected)) {
|
||||
return
|
||||
}
|
||||
if (
|
||||
!deepEqual(
|
||||
untrack(() => lastTabs),
|
||||
tabs
|
||||
)
|
||||
) {
|
||||
lastTabs = structuredClone($state.snapshot(tabs))
|
||||
} else {
|
||||
return
|
||||
}
|
||||
untrack(() => handleTabSelection())
|
||||
})
|
||||
$effect.pre(() => {
|
||||
tabs
|
||||
selected != undefined && untrack(() => handleTabSelection())
|
||||
})
|
||||
|
||||
|
||||
@@ -847,6 +847,7 @@
|
||||
}}
|
||||
/>
|
||||
|
||||
<!-- {$focusedGrid?.parentComponentId} -->
|
||||
{#if !$userStore?.operator}
|
||||
{#if $appStore}
|
||||
<AppEditorHeader
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
<script context="module" lang="ts">
|
||||
<script module lang="ts">
|
||||
import { writable } from 'svelte/store'
|
||||
import Popover from '../../Popover.svelte'
|
||||
|
||||
import { handleCut, handleCopy } from './component/componentCallbacks.svelte'
|
||||
interface ContextMenuRegistry {
|
||||
id: string
|
||||
close: () => void
|
||||
@@ -24,7 +25,6 @@
|
||||
Scissors,
|
||||
Trash
|
||||
} from 'lucide-svelte'
|
||||
import ComponentCallbacks from './component/ComponentCallbacks.svelte'
|
||||
import { createEventDispatcher, getContext } from 'svelte'
|
||||
import type { AppEditorContext, AppViewerContext } from '../types'
|
||||
import DeleteComponent from './settingsPanel/DeleteComponent.svelte'
|
||||
@@ -34,12 +34,9 @@
|
||||
|
||||
import { twMerge } from 'tailwind-merge'
|
||||
|
||||
let contextMenuVisible = false
|
||||
let menuX = 0
|
||||
let menuY = 0
|
||||
|
||||
export let locked: boolean = false
|
||||
export let fullHeight: boolean = false
|
||||
let contextMenuVisible = $state(false)
|
||||
let menuX = $state(0)
|
||||
let menuY = $state(0)
|
||||
|
||||
function handleRightClick(event: MouseEvent) {
|
||||
event.preventDefault()
|
||||
@@ -69,21 +66,39 @@
|
||||
}
|
||||
}
|
||||
|
||||
export let id: string
|
||||
let componentCallbacks: ComponentCallbacks | undefined = undefined
|
||||
interface Props {
|
||||
locked?: boolean
|
||||
fullHeight?: boolean
|
||||
id: string
|
||||
children?: import('svelte').Snippet
|
||||
}
|
||||
|
||||
const { selectedComponent } = getContext<AppViewerContext>('AppViewerContext')
|
||||
const { movingcomponents, stylePanel } = getContext<AppEditorContext>('AppEditorContext')
|
||||
let { locked = false, fullHeight = false, id, children }: Props = $props()
|
||||
|
||||
const { selectedComponent, focusedGrid, componentControl, app } = getContext<AppViewerContext>(
|
||||
'AppViewerContext'
|
||||
) as AppViewerContext
|
||||
const { movingcomponents, stylePanel, jobsDrawerOpen, history } = getContext<AppEditorContext>(
|
||||
'AppEditorContext'
|
||||
) as AppEditorContext
|
||||
const ctx = {
|
||||
history,
|
||||
app,
|
||||
selectedComponent,
|
||||
focusedGrid,
|
||||
componentControl,
|
||||
movingcomponents,
|
||||
jobsDrawerOpen
|
||||
}
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
let deleteComponent: DeleteComponent | undefined = undefined
|
||||
let deleteComponent: DeleteComponent | undefined = $state(undefined)
|
||||
|
||||
const menuItems = [
|
||||
{
|
||||
label: () => 'Cut',
|
||||
onClick: () => {
|
||||
componentCallbacks?.handleCut(new KeyboardEvent('keydown'))
|
||||
handleCut(new KeyboardEvent('keydown'), ctx)
|
||||
},
|
||||
icon: Scissors,
|
||||
shortcut: `${getModifierKey()}X`,
|
||||
@@ -92,7 +107,7 @@
|
||||
{
|
||||
label: () => 'Copy',
|
||||
onClick: () => {
|
||||
componentCallbacks?.handleCopy(new KeyboardEvent('keydown'))
|
||||
handleCopy(new KeyboardEvent('keydown'), ctx)
|
||||
},
|
||||
icon: Copy,
|
||||
shortcut: `${getModifierKey()}C`
|
||||
@@ -155,26 +170,25 @@
|
||||
]
|
||||
</script>
|
||||
|
||||
<ComponentCallbacks bind:this={componentCallbacks} />
|
||||
<DeleteComponent bind:this={deleteComponent} />
|
||||
|
||||
<svelte:window on:click={handleClickOutside} />
|
||||
<svelte:window onclick={handleClickOutside} />
|
||||
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
||||
<div on:click={closeContextMenu} on:contextmenu={handleRightClick} class="h-full w-full">
|
||||
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
||||
<slot />
|
||||
<!-- svelte-ignore a11y_click_events_have_key_events -->
|
||||
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
||||
<div onclick={closeContextMenu} oncontextmenu={handleRightClick} class="h-full w-full">
|
||||
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
||||
{@render children?.()}
|
||||
|
||||
{#if contextMenuVisible}
|
||||
<Portal name="grid-editor">
|
||||
<div style="position: fixed; top: {menuY}px; left: {menuX}px; z-index:6000;">
|
||||
<div class="rounded-md bg-surface border shadow-md divide-y w-64">
|
||||
<div class="p-1" use:clickOutside={false}>
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<!-- svelte-ignore a11y_click_events_have_key_events -->
|
||||
{#each menuItems as item}
|
||||
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
||||
<!-- svelte-ignore a11y_click_events_have_key_events -->
|
||||
|
||||
<Popover
|
||||
notClickable
|
||||
@@ -183,8 +197,8 @@
|
||||
disablePopup={!item.tooltip}
|
||||
appearTimeout={800}
|
||||
>
|
||||
<svelte:fragment slot="text"
|
||||
>{item.tooltip?.text}
|
||||
{#snippet text()}
|
||||
{item.tooltip?.text}
|
||||
{#if item.tooltip?.link}
|
||||
<a href={item.tooltip.link} target="_blank" class="text-blue-300 text-xs">
|
||||
<div class="flex flex-row gap-2 mt-4">
|
||||
@@ -192,8 +206,8 @@
|
||||
<ExternalLink size="16" />
|
||||
</div>
|
||||
</a>
|
||||
{/if}</svelte:fragment
|
||||
>
|
||||
{/if}
|
||||
{/snippet}
|
||||
|
||||
<button
|
||||
class={twMerge(
|
||||
@@ -203,14 +217,13 @@
|
||||
item.color === 'blue' && 'text-blue-500',
|
||||
item.disabled && 'opacity-50 cursor-not-allowed'
|
||||
)}
|
||||
on:click={() => {
|
||||
onclick={() => {
|
||||
item.onClick()
|
||||
closeContextMenu()
|
||||
}}
|
||||
disabled={item.disabled}
|
||||
>
|
||||
<!-- svelte-ignore missing-declaration -->
|
||||
<svelte:component this={item.icon} class="w-4 h-4" />
|
||||
<item.icon class="w-4 h-4" />
|
||||
|
||||
<span class="ml-2 text-xs">{item.label()}</span>
|
||||
{#if item.shortcut}
|
||||
|
||||
@@ -1,269 +0,0 @@
|
||||
<script lang="ts">
|
||||
import { getContext } from 'svelte'
|
||||
import {
|
||||
copyComponent,
|
||||
findGridItem,
|
||||
findGridItemParentGrid,
|
||||
getAllSubgridsAndComponentIds,
|
||||
insertNewGridItem
|
||||
} from '../appUtils'
|
||||
import type { AppEditorContext, AppViewerContext, GridItem } from '../../types'
|
||||
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')
|
||||
|
||||
const { history, movingcomponents, jobsDrawerOpen } =
|
||||
getContext<AppEditorContext>('AppEditorContext')
|
||||
|
||||
let tempGridItems: GridItem[] | undefined = undefined
|
||||
|
||||
const ITEM_TYPE = 'wm-grid-items'
|
||||
function getSortedGridItemsOfChildren(): GridItem[] {
|
||||
if (!$focusedGrid) {
|
||||
return $app.grid
|
||||
}
|
||||
|
||||
if (!$app.subgrids) {
|
||||
return []
|
||||
}
|
||||
|
||||
return $app.subgrids[`${$focusedGrid.parentComponentId}-${$focusedGrid.subGridIndex}`] ?? []
|
||||
}
|
||||
|
||||
function getGridItems(): GridItem[] {
|
||||
if ($app.grid.find((item) => item.id === $selectedComponent?.[0])) {
|
||||
return $app.grid
|
||||
}
|
||||
|
||||
if (!$app.subgrids) {
|
||||
return []
|
||||
}
|
||||
|
||||
return (
|
||||
Object.values($app.subgrids ?? {}).find((grid) =>
|
||||
grid.find((item) => item.id === $selectedComponent?.[0])
|
||||
) ?? []
|
||||
)
|
||||
}
|
||||
|
||||
export function left(event: KeyboardEvent) {
|
||||
if (!$componentControl[$selectedComponent?.[0] ?? '']?.left?.()) {
|
||||
const sortedGridItems = getGridItems()
|
||||
const currentIndex = sortedGridItems.findIndex(
|
||||
(item) => item.id === ($selectedComponent?.[0] ?? '')
|
||||
)
|
||||
|
||||
if (currentIndex !== -1 && currentIndex > 0) {
|
||||
const left = sortedGridItems[currentIndex - 1]
|
||||
|
||||
if (left.data.type === 'tablecomponent' && left.data.actionButtons.length >= 1) {
|
||||
$selectedComponent = [left.data.actionButtons[left.data.actionButtons.length - 1].id]
|
||||
} else if (
|
||||
(left.data.type === 'aggridcomponent' ||
|
||||
left.data.type === 'aggridcomponentee' ||
|
||||
left.data.type === 'dbexplorercomponent') &&
|
||||
Array.isArray(left.data.actions) &&
|
||||
left.data.actions.length >= 1
|
||||
) {
|
||||
$selectedComponent = [left.data.actions[left.data.actions.length - 1].id]
|
||||
} else {
|
||||
$selectedComponent = [left.id]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
event.preventDefault()
|
||||
}
|
||||
|
||||
export function right(event: KeyboardEvent) {
|
||||
let r = $componentControl[$selectedComponent?.[0] ?? '']?.right?.()
|
||||
|
||||
if (typeof r === 'string') {
|
||||
$selectedComponent = [r]
|
||||
r = $componentControl[r]?.right?.(true)
|
||||
}
|
||||
|
||||
if (!r) {
|
||||
const sortedGridItems = getGridItems()
|
||||
const currentIndex = sortedGridItems.findIndex(
|
||||
(item) => item.id === ($selectedComponent?.[0] ?? '')
|
||||
)
|
||||
|
||||
if (currentIndex !== -1 && currentIndex < sortedGridItems.length - 1) {
|
||||
$selectedComponent = [sortedGridItems[currentIndex + 1].id]
|
||||
}
|
||||
}
|
||||
|
||||
event.preventDefault()
|
||||
}
|
||||
|
||||
export function down(event: KeyboardEvent) {
|
||||
if (!$focusedGrid) {
|
||||
$selectedComponent = [getSortedGridItemsOfChildren()[0]?.id]
|
||||
event.preventDefault()
|
||||
} else if ($app.subgrids) {
|
||||
const index = $focusedGrid?.subGridIndex ?? 0
|
||||
const subgrid = $app.subgrids[`${$selectedComponent}-${index}`]
|
||||
|
||||
if (!subgrid || subgrid.length === 0) {
|
||||
return
|
||||
}
|
||||
|
||||
if (subgrid) {
|
||||
$selectedComponent = [subgrid[0].id]
|
||||
}
|
||||
event.preventDefault()
|
||||
}
|
||||
}
|
||||
|
||||
export function handleEscape(event: KeyboardEvent) {
|
||||
$selectedComponent = undefined
|
||||
$focusedGrid = undefined
|
||||
event.preventDefault()
|
||||
}
|
||||
|
||||
export function handleArrowUp(event: KeyboardEvent) {
|
||||
if (!$selectedComponent) return
|
||||
let parentId = findGridItemParentGrid($app, $selectedComponent?.[0])?.split('-')[0]
|
||||
|
||||
if (parentId) {
|
||||
$selectedComponent = [parentId]
|
||||
} else {
|
||||
$selectedComponent = undefined
|
||||
$focusedGrid = undefined
|
||||
}
|
||||
}
|
||||
|
||||
export async function handleCopy(event: KeyboardEvent) {
|
||||
if (!$selectedComponent || $jobsDrawerOpen || window.getSelection()?.toString() != '') {
|
||||
return
|
||||
}
|
||||
tempGridItems = undefined
|
||||
const copiedGridItems = $selectedComponent
|
||||
.map((x) => findGridItem($app, x))
|
||||
.filter((x) => x != undefined) as GridItem[]
|
||||
|
||||
copyGridItemsToClipboard(copiedGridItems, 'copy')
|
||||
}
|
||||
|
||||
async function copyGridItemsToClipboard(
|
||||
items: GridItem[],
|
||||
type: 'copy' | 'cut' | undefined = undefined
|
||||
) {
|
||||
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 = await 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)
|
||||
} else if (type) {
|
||||
const text = type == 'copy' ? 'Copied' : 'Cut'
|
||||
sendUserToast(`${text} ${items.length} component${items.length > 1 ? 's' : ''}`)
|
||||
}
|
||||
}
|
||||
|
||||
export function handleCut(event: KeyboardEvent) {
|
||||
if (!$selectedComponent) {
|
||||
return
|
||||
}
|
||||
$movingcomponents = JSON.parse(JSON.stringify($selectedComponent))
|
||||
push(history, $app)
|
||||
|
||||
let gridItems = $selectedComponent
|
||||
.map((x) => findGridItem($app, x))
|
||||
.filter((x) => x != undefined) as GridItem[]
|
||||
copyGridItemsToClipboard(gridItems, 'cut')
|
||||
|
||||
if (!gridItems) {
|
||||
return
|
||||
}
|
||||
|
||||
// Store the grid item in a temp variable so we can paste it later
|
||||
tempGridItems = gridItems
|
||||
}
|
||||
|
||||
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
|
||||
} else {
|
||||
copiedGridItems = undefined
|
||||
}
|
||||
} catch {}
|
||||
}
|
||||
|
||||
if (tempGridItems != undefined) {
|
||||
for (let tempGridItem of tempGridItems) {
|
||||
if (
|
||||
$focusedGrid &&
|
||||
getAllSubgridsAndComponentIds($app, tempGridItem.data)[0].includes(
|
||||
`${$focusedGrid.parentComponentId}-${$focusedGrid.subGridIndex}`
|
||||
)
|
||||
) {
|
||||
sendUserToast('Cannot paste a component into itself', true)
|
||||
return
|
||||
}
|
||||
let parentGrid = findGridItemParentGrid($app, tempGridItem.id)
|
||||
if (parentGrid) {
|
||||
$app.subgrids &&
|
||||
($app.subgrids[parentGrid] = $app.subgrids[parentGrid].filter(
|
||||
(item) => item.id !== tempGridItem?.id
|
||||
))
|
||||
} else {
|
||||
$app.grid = $app.grid.filter((item) => item.id !== tempGridItem?.id)
|
||||
}
|
||||
|
||||
const gridItem = tempGridItem
|
||||
insertNewGridItem(
|
||||
$app,
|
||||
(id) => ({ ...gridItem.data, id }),
|
||||
$focusedGrid,
|
||||
Object.fromEntries(gridColumns.map((column) => [column, gridItem[column]])),
|
||||
tempGridItem.id
|
||||
)
|
||||
}
|
||||
copyGridItemsToClipboard(tempGridItems)
|
||||
$selectedComponent = tempGridItems.map((x) => x.id)
|
||||
|
||||
tempGridItems = undefined
|
||||
} else if (copiedGridItems) {
|
||||
let nitems: string[] = []
|
||||
for (let copiedGridItem of copiedGridItems) {
|
||||
let newItem = copyComponent($app, copiedGridItem, $focusedGrid, subgrids, [])
|
||||
newItem && nitems.push(newItem)
|
||||
}
|
||||
$selectedComponent = nitems.map((x) => x)
|
||||
}
|
||||
|
||||
$app = $app
|
||||
}
|
||||
</script>
|
||||
@@ -1,9 +1,34 @@
|
||||
<script lang="ts">
|
||||
import ComponentCallbacks from './ComponentCallbacks.svelte'
|
||||
|
||||
let componentCallbacks: ComponentCallbacks | undefined = undefined
|
||||
import { getContext } from 'svelte'
|
||||
import {
|
||||
handleEscape,
|
||||
handlePaste,
|
||||
handleArrowUp,
|
||||
handleCut,
|
||||
handleCopy,
|
||||
down,
|
||||
right,
|
||||
left
|
||||
} from './componentCallbacks.svelte'
|
||||
import type { AppEditorContext, AppViewerContext } from '../../types'
|
||||
|
||||
const { history, movingcomponents, jobsDrawerOpen } = getContext<AppEditorContext>(
|
||||
'AppEditorContext'
|
||||
) as AppEditorContext
|
||||
const { app, selectedComponent, focusedGrid, componentControl } = getContext<AppViewerContext>(
|
||||
'AppViewerContext'
|
||||
) as AppViewerContext
|
||||
const ctx = {
|
||||
history,
|
||||
app,
|
||||
selectedComponent,
|
||||
focusedGrid,
|
||||
componentControl,
|
||||
movingcomponents,
|
||||
jobsDrawerOpen
|
||||
}
|
||||
function keydown(event: KeyboardEvent) {
|
||||
// console.log('keydown', $focusedGrid, $selectedComponent)
|
||||
// Ignore keydown events if the user is typing in monaco
|
||||
let classes = event.target?.['className']
|
||||
if (
|
||||
@@ -14,38 +39,38 @@
|
||||
}
|
||||
switch (event.key) {
|
||||
case 'Escape':
|
||||
componentCallbacks?.handleEscape(event)
|
||||
handleEscape(event, ctx)
|
||||
break
|
||||
|
||||
case 'ArrowUp': {
|
||||
componentCallbacks?.handleArrowUp(event)
|
||||
handleArrowUp(event, ctx)
|
||||
break
|
||||
}
|
||||
|
||||
case 'ArrowDown': {
|
||||
componentCallbacks?.down(event)
|
||||
down(event, ctx)
|
||||
break
|
||||
}
|
||||
|
||||
case 'ArrowRight': {
|
||||
componentCallbacks?.right(event)
|
||||
right(event, ctx)
|
||||
break
|
||||
}
|
||||
|
||||
case 'ArrowLeft': {
|
||||
componentCallbacks?.left(event)
|
||||
left(event, ctx)
|
||||
break
|
||||
}
|
||||
|
||||
case 'c':
|
||||
if (event.ctrlKey || event.metaKey) {
|
||||
componentCallbacks?.handleCopy(event)
|
||||
handleCopy(event, ctx)
|
||||
}
|
||||
break
|
||||
|
||||
case 'x':
|
||||
if (event.ctrlKey || event.metaKey) {
|
||||
componentCallbacks?.handleCut(event)
|
||||
handleCut(event, ctx)
|
||||
}
|
||||
break
|
||||
|
||||
@@ -55,6 +80,4 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<ComponentCallbacks bind:this={componentCallbacks} />
|
||||
|
||||
<svelte:window on:keydown={keydown} on:paste={componentCallbacks?.handlePaste} />
|
||||
<svelte:window onkeydown={keydown} onpaste={(e) => handlePaste(e, ctx)} />
|
||||
|
||||
@@ -0,0 +1,357 @@
|
||||
// import { getContext } from 'svelte'
|
||||
import {
|
||||
copyComponent,
|
||||
findGridItem,
|
||||
findGridItemParentGrid,
|
||||
getAllSubgridsAndComponentIds,
|
||||
insertNewGridItem
|
||||
} from '../appUtils'
|
||||
import type { AppEditorContext, AppViewerContext, GridItem } from '../../types'
|
||||
import { push } from '$lib/history'
|
||||
import { sendUserToast } from '$lib/toast'
|
||||
import { gridColumns } from '../../gridUtils'
|
||||
import { copyToClipboard } from '$lib/utils'
|
||||
import { get } from 'svelte/store'
|
||||
|
||||
// const { app, selectedComponent, focusedGrid, componentControl } =
|
||||
// getContext<AppViewerContext>('AppViewerContext')
|
||||
|
||||
// const { history, movingcomponents, jobsDrawerOpen } =
|
||||
// getContext<AppEditorContext>('AppEditorContext')
|
||||
|
||||
let tempGridItems: GridItem[] | undefined = undefined
|
||||
|
||||
const ITEM_TYPE = 'wm-grid-items'
|
||||
|
||||
function getSortedGridItemsOfChildren(ctx: {
|
||||
app: AppViewerContext['app']
|
||||
focusedGrid: AppViewerContext['focusedGrid']
|
||||
}): GridItem[] {
|
||||
const focusedGrid = get(ctx.focusedGrid)
|
||||
if (!focusedGrid) {
|
||||
return get(ctx.app).grid
|
||||
}
|
||||
|
||||
let subgrids = get(ctx.app).subgrids
|
||||
if (!subgrids) {
|
||||
return []
|
||||
}
|
||||
|
||||
return subgrids[`${focusedGrid.parentComponentId}-${focusedGrid.subGridIndex}`] ?? []
|
||||
}
|
||||
|
||||
function getGridItems(ctx: {
|
||||
app: AppViewerContext['app']
|
||||
selectedComponent: AppViewerContext['selectedComponent']
|
||||
}): GridItem[] {
|
||||
const app = get(ctx.app)
|
||||
const selectedComponent = get(ctx.selectedComponent)
|
||||
if (app.grid.find((item) => item.id === selectedComponent?.[0])) {
|
||||
return get(ctx.app).grid
|
||||
}
|
||||
|
||||
if (!app.subgrids) {
|
||||
return []
|
||||
}
|
||||
|
||||
return (
|
||||
Object.values(app.subgrids ?? {}).find((grid) =>
|
||||
grid.find((item) => item.id === selectedComponent?.[0])
|
||||
) ?? []
|
||||
)
|
||||
}
|
||||
|
||||
export function left(
|
||||
event: KeyboardEvent,
|
||||
ctx: {
|
||||
app: AppViewerContext['app']
|
||||
componentControl: AppViewerContext['componentControl']
|
||||
selectedComponent: AppViewerContext['selectedComponent']
|
||||
}
|
||||
) {
|
||||
const componentControl = get(ctx.componentControl)
|
||||
const selectedComponent = get(ctx.selectedComponent)
|
||||
if (!componentControl[selectedComponent?.[0] ?? '']?.left?.()) {
|
||||
const sortedGridItems = getGridItems(ctx)
|
||||
const currentIndex = sortedGridItems.findIndex(
|
||||
(item) => item.id === (selectedComponent?.[0] ?? '')
|
||||
)
|
||||
|
||||
if (currentIndex !== -1 && currentIndex > 0) {
|
||||
const left = sortedGridItems[currentIndex - 1]
|
||||
|
||||
if (left.data.type === 'tablecomponent' && left.data.actionButtons.length >= 1) {
|
||||
ctx.selectedComponent.set([left.data.actionButtons[left.data.actionButtons.length - 1].id])
|
||||
} else if (
|
||||
(left.data.type === 'aggridcomponent' ||
|
||||
left.data.type === 'aggridcomponentee' ||
|
||||
left.data.type === 'dbexplorercomponent') &&
|
||||
Array.isArray(left.data.actions) &&
|
||||
left.data.actions.length >= 1
|
||||
) {
|
||||
ctx.selectedComponent.set([left.data.actions[left.data.actions.length - 1].id])
|
||||
} else {
|
||||
ctx.selectedComponent.set([left.id])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
event.preventDefault()
|
||||
}
|
||||
|
||||
export function right(
|
||||
event: KeyboardEvent,
|
||||
ctx: {
|
||||
app: AppViewerContext['app']
|
||||
componentControl: AppViewerContext['componentControl']
|
||||
selectedComponent: AppViewerContext['selectedComponent']
|
||||
}
|
||||
) {
|
||||
const componentControl = get(ctx.componentControl)
|
||||
const selectedComponent = get(ctx.selectedComponent)
|
||||
let r = componentControl[selectedComponent?.[0] ?? '']?.right?.()
|
||||
|
||||
if (typeof r === 'string') {
|
||||
ctx.selectedComponent.set([r])
|
||||
r = componentControl[r]?.right?.(true)
|
||||
}
|
||||
|
||||
if (!r) {
|
||||
const sortedGridItems = getGridItems(ctx)
|
||||
const currentIndex = sortedGridItems.findIndex(
|
||||
(item) => item.id === (selectedComponent?.[0] ?? '')
|
||||
)
|
||||
|
||||
if (currentIndex !== -1 && currentIndex < sortedGridItems.length - 1) {
|
||||
ctx.selectedComponent.set([sortedGridItems[currentIndex + 1].id])
|
||||
}
|
||||
}
|
||||
|
||||
event.preventDefault()
|
||||
}
|
||||
|
||||
export function down(
|
||||
event: KeyboardEvent,
|
||||
ctx: {
|
||||
app: AppViewerContext['app']
|
||||
componentControl: AppViewerContext['componentControl']
|
||||
selectedComponent: AppViewerContext['selectedComponent']
|
||||
focusedGrid: AppViewerContext['focusedGrid']
|
||||
}
|
||||
) {
|
||||
const focusedGrid = get(ctx.focusedGrid)
|
||||
const selectedComponent = get(ctx.selectedComponent)
|
||||
const app = get(ctx.app)
|
||||
if (!focusedGrid) {
|
||||
ctx.selectedComponent.set([getSortedGridItemsOfChildren(ctx)[0]?.id])
|
||||
event.preventDefault()
|
||||
} else if (app.subgrids) {
|
||||
const index = focusedGrid?.subGridIndex ?? 0
|
||||
const subgrid = app.subgrids[`${selectedComponent}-${index}`]
|
||||
|
||||
if (!subgrid || subgrid.length === 0) {
|
||||
return
|
||||
}
|
||||
|
||||
if (subgrid) {
|
||||
ctx.selectedComponent.set([subgrid[0].id])
|
||||
}
|
||||
event.preventDefault()
|
||||
}
|
||||
}
|
||||
|
||||
export function handleEscape(
|
||||
event: KeyboardEvent,
|
||||
ctx: {
|
||||
selectedComponent: AppViewerContext['selectedComponent']
|
||||
focusedGrid: AppViewerContext['focusedGrid']
|
||||
}
|
||||
) {
|
||||
ctx.selectedComponent.set(undefined)
|
||||
ctx.focusedGrid.set(undefined)
|
||||
event.preventDefault()
|
||||
}
|
||||
|
||||
export function handleArrowUp(
|
||||
event: KeyboardEvent,
|
||||
ctx: {
|
||||
app: AppViewerContext['app']
|
||||
selectedComponent: AppViewerContext['selectedComponent']
|
||||
focusedGrid: AppViewerContext['focusedGrid']
|
||||
}
|
||||
) {
|
||||
const selectedComponent = get(ctx.selectedComponent)
|
||||
if (!selectedComponent) return
|
||||
let parentId = findGridItemParentGrid(get(ctx.app), selectedComponent?.[0])?.split('-')[0]
|
||||
|
||||
if (parentId) {
|
||||
ctx.selectedComponent.set([parentId])
|
||||
} else {
|
||||
ctx.selectedComponent.set(undefined)
|
||||
ctx.focusedGrid.set(undefined)
|
||||
}
|
||||
}
|
||||
|
||||
export async function handleCopy(
|
||||
event: KeyboardEvent,
|
||||
ctx: {
|
||||
app: AppViewerContext['app']
|
||||
selectedComponent: AppViewerContext['selectedComponent']
|
||||
jobsDrawerOpen: AppEditorContext['jobsDrawerOpen']
|
||||
}
|
||||
) {
|
||||
const selectedComponent = get(ctx.selectedComponent)
|
||||
if (!selectedComponent || get(ctx.jobsDrawerOpen) || window.getSelection()?.toString() != '') {
|
||||
return
|
||||
}
|
||||
tempGridItems = undefined
|
||||
const copiedGridItems = selectedComponent
|
||||
.map((x) => findGridItem(get(ctx.app), x))
|
||||
.filter((x) => x != undefined) as GridItem[]
|
||||
|
||||
copyGridItemsToClipboard(copiedGridItems, ctx, 'copy')
|
||||
}
|
||||
|
||||
async function copyGridItemsToClipboard(
|
||||
items: GridItem[],
|
||||
ctx: {
|
||||
app: AppViewerContext['app']
|
||||
},
|
||||
type: 'copy' | 'cut' | undefined = undefined
|
||||
) {
|
||||
const app = get(ctx.app)
|
||||
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 = await 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)
|
||||
} else if (type) {
|
||||
const text = type == 'copy' ? 'Copied' : 'Cut'
|
||||
sendUserToast(`${text} ${items.length} component${items.length > 1 ? 's' : ''}`)
|
||||
}
|
||||
}
|
||||
|
||||
export function handleCut(
|
||||
event: KeyboardEvent,
|
||||
ctx: {
|
||||
selectedComponent: AppViewerContext['selectedComponent']
|
||||
history: AppEditorContext['history']
|
||||
app: AppViewerContext['app']
|
||||
movingcomponents: AppEditorContext['movingcomponents']
|
||||
}
|
||||
) {
|
||||
const selectedComponent = get(ctx.selectedComponent)
|
||||
const app = get(ctx.app)
|
||||
if (!selectedComponent) {
|
||||
return
|
||||
}
|
||||
ctx.movingcomponents.set(JSON.parse(JSON.stringify(selectedComponent)))
|
||||
push(ctx.history, app)
|
||||
|
||||
let gridItems = selectedComponent
|
||||
.map((x) => findGridItem(app, x))
|
||||
.filter((x) => x != undefined) as GridItem[]
|
||||
copyGridItemsToClipboard(gridItems, ctx, 'cut')
|
||||
|
||||
if (!gridItems) {
|
||||
return
|
||||
}
|
||||
|
||||
// Store the grid item in a temp variable so we can paste it later
|
||||
tempGridItems = gridItems
|
||||
}
|
||||
|
||||
export async function handlePaste(
|
||||
event: ClipboardEvent,
|
||||
ctx: {
|
||||
app: AppViewerContext['app']
|
||||
selectedComponent: AppViewerContext['selectedComponent']
|
||||
focusedGrid: AppViewerContext['focusedGrid']
|
||||
componentControl: AppViewerContext['componentControl']
|
||||
history: AppEditorContext['history']
|
||||
jobsDrawerOpen: AppEditorContext['jobsDrawerOpen']
|
||||
movingcomponents: AppEditorContext['movingcomponents']
|
||||
}
|
||||
) {
|
||||
let classes = event.target?.['className']
|
||||
if (
|
||||
(typeof classes === 'string' && classes.includes('inputarea')) ||
|
||||
['INPUT', 'TEXTAREA'].includes(document.activeElement?.tagName!)
|
||||
) {
|
||||
return
|
||||
}
|
||||
event.preventDefault()
|
||||
const app = get(ctx.app)
|
||||
const focusedGrid = get(ctx.focusedGrid)
|
||||
|
||||
push(ctx.history, app)
|
||||
ctx.movingcomponents.set(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
|
||||
} else {
|
||||
copiedGridItems = undefined
|
||||
}
|
||||
} catch {}
|
||||
}
|
||||
|
||||
if (tempGridItems != undefined) {
|
||||
for (let tempGridItem of tempGridItems) {
|
||||
if (
|
||||
focusedGrid &&
|
||||
getAllSubgridsAndComponentIds(app, tempGridItem.data)[0].includes(
|
||||
`${focusedGrid.parentComponentId}-${focusedGrid.subGridIndex}`
|
||||
)
|
||||
) {
|
||||
sendUserToast('Cannot paste a component into itself', true)
|
||||
return
|
||||
}
|
||||
let parentGrid = findGridItemParentGrid(app, tempGridItem.id)
|
||||
if (parentGrid) {
|
||||
app.subgrids &&
|
||||
(app.subgrids[parentGrid] = app.subgrids[parentGrid].filter(
|
||||
(item) => item.id !== tempGridItem?.id
|
||||
))
|
||||
} else {
|
||||
app.grid = app.grid.filter((item) => item.id !== tempGridItem?.id)
|
||||
}
|
||||
|
||||
const gridItem = tempGridItem
|
||||
insertNewGridItem(
|
||||
app,
|
||||
(id) => ({ ...gridItem.data, id }),
|
||||
focusedGrid,
|
||||
Object.fromEntries(gridColumns.map((column) => [column, gridItem[column]])),
|
||||
tempGridItem.id
|
||||
)
|
||||
}
|
||||
copyGridItemsToClipboard(tempGridItems, ctx)
|
||||
ctx.selectedComponent.set(tempGridItems.map((x) => x.id))
|
||||
|
||||
tempGridItems = undefined
|
||||
} else if (copiedGridItems) {
|
||||
let nitems: string[] = []
|
||||
for (let copiedGridItem of copiedGridItems) {
|
||||
let newItem = copyComponent(app, copiedGridItem, focusedGrid, subgrids, [])
|
||||
newItem && nitems.push(newItem)
|
||||
}
|
||||
ctx.selectedComponent.set(nitems.map((x) => x))
|
||||
}
|
||||
|
||||
ctx.app.update((x) => x)
|
||||
}
|
||||
@@ -23,14 +23,17 @@
|
||||
}
|
||||
|
||||
let {
|
||||
tabs = $bindable([]),
|
||||
disabledTabs = $bindable([]),
|
||||
tabs = $bindable(undefined),
|
||||
disabledTabs = $bindable(undefined),
|
||||
canDisableTabs = false,
|
||||
word = 'Tab',
|
||||
component = $bindable()
|
||||
}: Props = $props()
|
||||
|
||||
$effect.pre(() => {
|
||||
if (tabs == undefined) {
|
||||
tabs = []
|
||||
}
|
||||
if (disabledTabs == undefined) {
|
||||
disabledTabs = [
|
||||
{ type: 'static', value: false, fieldType: 'boolean' },
|
||||
@@ -40,7 +43,7 @@
|
||||
})
|
||||
|
||||
let items = $state.raw(
|
||||
tabs.map((tab, index) => {
|
||||
(tabs ?? []).map((tab, index) => {
|
||||
return { value: tab, id: generateRandomString(), originalIndex: index }
|
||||
})
|
||||
)
|
||||
@@ -134,7 +137,7 @@
|
||||
|
||||
const newDisabledTabs: RichConfiguration[] = []
|
||||
for (let i = 0; i < items.length; i++) {
|
||||
newDisabledTabs.push(disabledTabs[items[i].originalIndex])
|
||||
disabledTabs && newDisabledTabs.push(disabledTabs[items[i].originalIndex])
|
||||
}
|
||||
disabledTabs = newDisabledTabs
|
||||
|
||||
|
||||
@@ -185,9 +185,8 @@
|
||||
if (detail.id && !selectedIds?.includes(detail.id)) {
|
||||
nselectedIds = [detail.id, ...(selectedIds ?? [])]
|
||||
}
|
||||
sortedItems = citems
|
||||
for (let id of nselectedIds) {
|
||||
let activeItem = getItemById(id, sortedItems)
|
||||
let activeItem = getItemById(id, citems)
|
||||
|
||||
if (activeItem && getComputedCols) {
|
||||
activeItem = {
|
||||
@@ -200,7 +199,7 @@
|
||||
|
||||
if ($isCtrlOrMetaPressedStore) {
|
||||
if ($componentDraggedParentIdStore === $overlappedStore) {
|
||||
const fixedContainer = sortedItems.map((item) => {
|
||||
const fixedContainer = citems.map((item) => {
|
||||
if (isContainer(item.data['type'])) {
|
||||
initialFixedStates.set(item.id, {
|
||||
item3Fixed: item[3].fixed,
|
||||
@@ -231,7 +230,7 @@
|
||||
sortedItems = nitems
|
||||
}
|
||||
} else {
|
||||
let { items: nitems } = moveItem(activeItem, sortedItems, getComputedCols)
|
||||
let { items: nitems } = moveItem(activeItem, citems, getComputedCols)
|
||||
|
||||
sortedItems = nitems
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user