App editor improvements (#2328)
* feat(frontend): add zoomable app editor * feat(frontend): add viewport zoom * fix(frontend): Fix build * improvements --------- Co-authored-by: Ruben Fiszel <ruben@rubenfiszel.com>
This commit is contained in:
@@ -62,7 +62,11 @@
|
||||
|
||||
let resourceTypes: string[] | undefined = undefined
|
||||
|
||||
getResourceTypes()
|
||||
async function loadResourceTypes() {
|
||||
resourceTypes = await getResourceTypes()
|
||||
}
|
||||
|
||||
loadResourceTypes()
|
||||
</script>
|
||||
|
||||
<div class="w-full pt-2">
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
export let size: 'sm' | 'xs' = 'sm'
|
||||
const id = (Math.random() + 1).toString(36).substring(10)
|
||||
const dispatch = createEventDispatcher()
|
||||
const bothOptions = Boolean(options.left) && Boolean(options.right);
|
||||
const bothOptions = Boolean(options.left) && Boolean(options.right)
|
||||
</script>
|
||||
|
||||
<span class="{$$props.class} z-auto">
|
||||
@@ -29,20 +29,21 @@
|
||||
: 'cursor-pointer'}"
|
||||
>
|
||||
{#if Boolean(options?.left)}
|
||||
<span
|
||||
class={twMerge(
|
||||
'mr-2 font-medium duration-50',
|
||||
bothOptions ? (checked ? 'text-disabled' : 'text-primary') : 'text-primary',
|
||||
size === 'xs' ? 'text-xs' : 'text-sm',
|
||||
textClass
|
||||
)}
|
||||
style={textStyle}
|
||||
>
|
||||
{options?.left}
|
||||
</span>
|
||||
<span
|
||||
class={twMerge(
|
||||
'mr-2 font-medium duration-50',
|
||||
bothOptions ? (checked ? 'text-disabled' : 'text-primary') : 'text-primary',
|
||||
size === 'xs' ? 'text-xs' : 'text-sm',
|
||||
textClass
|
||||
)}
|
||||
style={textStyle}
|
||||
>
|
||||
{options?.left}
|
||||
</span>
|
||||
{/if}
|
||||
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
||||
<div class="relative" on:click|stopPropagation>
|
||||
<input
|
||||
on:focus
|
||||
@@ -67,20 +68,20 @@
|
||||
/>
|
||||
</div>
|
||||
{#if Boolean(options?.right)}
|
||||
<span
|
||||
class={twMerge(
|
||||
'ml-2 font-medium duration-50',
|
||||
bothOptions ? (checked ? 'text-primary' : 'text-disabled') : 'text-primary',
|
||||
size === 'xs' ? 'text-xs' : 'text-sm',
|
||||
textClass
|
||||
)}
|
||||
style={textStyle}
|
||||
>
|
||||
{options?.right}
|
||||
{#if options?.rightTooltip}
|
||||
<Tooltip>{options?.rightTooltip}</Tooltip>
|
||||
{/if}
|
||||
</span>
|
||||
<span
|
||||
class={twMerge(
|
||||
'ml-2 font-medium duration-50',
|
||||
bothOptions ? (checked ? 'text-primary' : 'text-disabled') : 'text-primary',
|
||||
size === 'xs' ? 'text-xs' : 'text-sm',
|
||||
textClass
|
||||
)}
|
||||
style={textStyle}
|
||||
>
|
||||
{options?.right}
|
||||
{#if options?.rightTooltip}
|
||||
<Tooltip>{options?.rightTooltip}</Tooltip>
|
||||
{/if}
|
||||
</span>
|
||||
{/if}
|
||||
</label>
|
||||
</span>
|
||||
|
||||
@@ -106,7 +106,7 @@
|
||||
<InputValue key="disable" {id} input={disableTab} bind:value={resolvedDisabledTabs[index]} />
|
||||
{/each}
|
||||
|
||||
<div class={resolvedConfig.tabsKind == 'sidebar' ? 'flex gap-4 w-full' : 'w-full'}>
|
||||
<div class={resolvedConfig.tabsKind == 'sidebar' ? 'flex gap-4 w-full h-full' : 'w-full'}>
|
||||
{#if !resolvedConfig.tabsKind || resolvedConfig.tabsKind == 'tabs' || (resolvedConfig.tabsKind == 'invisibleOnView' && $mode == 'dnd')}
|
||||
<div bind:clientHeight={tabHeight}>
|
||||
<Tabs
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
import VariableEditor from '$lib/components/VariableEditor.svelte'
|
||||
import { VariableService, type Policy } from '$lib/gen'
|
||||
import { initHistory } from '$lib/history'
|
||||
import { Component, Paintbrush, Plus } from 'lucide-svelte'
|
||||
import { Component, Minus, Paintbrush, Plus } from 'lucide-svelte'
|
||||
import { findGridItem, findGridItemParentGrid } from './appUtils'
|
||||
import ComponentNavigation from './component/ComponentNavigation.svelte'
|
||||
import CssSettings from './componentsPanel/CssSettings.svelte'
|
||||
@@ -130,12 +130,15 @@
|
||||
previewTheme
|
||||
})
|
||||
|
||||
let scale = writable(100)
|
||||
|
||||
setContext<AppEditorContext>('AppEditorContext', {
|
||||
history,
|
||||
pickVariableCallback,
|
||||
movingcomponents: writable(undefined),
|
||||
selectedComponentInEditor: writable(undefined),
|
||||
jobsDrawerOpen: writable(false)
|
||||
jobsDrawerOpen: writable(false),
|
||||
scale
|
||||
})
|
||||
|
||||
let timeout: NodeJS.Timeout | undefined = undefined
|
||||
@@ -443,17 +446,48 @@
|
||||
$focusedGrid = undefined
|
||||
}}
|
||||
class={twMerge(
|
||||
'bg-surface h-full w-full relative',
|
||||
'bg-surface-secondary h-full w-full relative',
|
||||
$appStore.css?.['app']?.['viewer']?.class,
|
||||
'wm-app-viewer'
|
||||
)}
|
||||
style={$appStore.css?.['app']?.['viewer']?.style}
|
||||
>
|
||||
<div class="absolute bottom-4 left-4 z-50">
|
||||
<div class="flex flex-row gap-2 text-xs items-center">
|
||||
<Button
|
||||
color="light"
|
||||
variant="border"
|
||||
size="xs2"
|
||||
disabled={$scale <= 30}
|
||||
on:click={() => {
|
||||
$scale -= 10
|
||||
}}
|
||||
>
|
||||
<Minus size={14} />
|
||||
</Button>
|
||||
{$scale}
|
||||
<Button
|
||||
color="light"
|
||||
variant="border"
|
||||
size="xs2"
|
||||
disabled={$scale >= 100}
|
||||
on:click={() => {
|
||||
$scale += 10
|
||||
}}
|
||||
>
|
||||
<Plus size={14} />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="app-editor-top-level-drawer" />
|
||||
<div
|
||||
class="absolute inset-0 h-full w-full surface-secondary bg-[radial-gradient(#dbdbdb_1px,transparent_1px)] dark:bg-[radial-gradient(#666666_1px,transparent_1px)] [background-size:16px_16px]"
|
||||
/>
|
||||
<div
|
||||
class={classNames(
|
||||
'bg-surface-secondary/80 relative mx-auto w-full h-full overflow-auto',
|
||||
app.fullscreen ? '' : 'max-w-6xl'
|
||||
'relative mx-auto w-full h-full overflow-auto',
|
||||
$appStore.fullscreen ? '' : 'max-w-6xl border-x'
|
||||
)}
|
||||
>
|
||||
{#if $appStore.grid}
|
||||
@@ -485,7 +519,6 @@
|
||||
size="xs"
|
||||
class="h-full"
|
||||
on:pointerdown={() => {
|
||||
console.log('click', $cssEditorOpen)
|
||||
if ($cssEditorOpen) {
|
||||
$cssEditorOpen = false
|
||||
selectedTab = 'insert'
|
||||
|
||||
@@ -189,9 +189,9 @@
|
||||
<div
|
||||
style={app.css?.['app']?.['grid']?.style}
|
||||
class={twMerge(
|
||||
'px-4 pt-4 pb-2 overflow-visible',
|
||||
'p-2 overflow-visible',
|
||||
app.css?.['app']?.['grid']?.class ?? '',
|
||||
'wm-app-grid'
|
||||
'wm-app-grid subgrid'
|
||||
)}
|
||||
bind:clientWidth={$parentWidth}
|
||||
>
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
allIdsInPath
|
||||
} = getContext<AppViewerContext>('AppViewerContext')
|
||||
|
||||
const { history } = getContext<AppEditorContext>('AppEditorContext')
|
||||
const { history, scale } = getContext<AppEditorContext>('AppEditorContext')
|
||||
|
||||
let previousSelectedIds: string[] | undefined = undefined
|
||||
$: if (!deepEqual(previousSelectedIds, $selectedComponent)) {
|
||||
@@ -73,9 +73,9 @@
|
||||
<div
|
||||
style={$app.css?.['app']?.['grid']?.style}
|
||||
class={twMerge(
|
||||
'px-4 pt-4 pb-2 overflow-visible',
|
||||
'p-2 overflow-visible',
|
||||
$app.css?.['app']?.['grid']?.class ?? '',
|
||||
'wm-app-grid'
|
||||
'wm-app-grid border'
|
||||
)}
|
||||
on:pointerdown={() => {
|
||||
$selectedComponent = undefined
|
||||
@@ -83,7 +83,14 @@
|
||||
}}
|
||||
bind:clientWidth={$parentWidth}
|
||||
>
|
||||
<div class={!$focusedGrid && $mode !== 'preview' ? ' border border-dashed' : ''}>
|
||||
<div
|
||||
class={twMerge(
|
||||
!$focusedGrid && $mode !== 'preview' ? 'border-dashed' : '',
|
||||
'subgrid',
|
||||
'border-[#999999] dark:border-[#aaaaaa] border '
|
||||
)}
|
||||
style={`transform: scale(${$scale / 100})`}
|
||||
>
|
||||
<Grid
|
||||
allIdsInPath={$allIdsInPath}
|
||||
selectedIds={$selectedComponent}
|
||||
|
||||
@@ -72,7 +72,7 @@ export function getComponentControl(type: keyof typeof components): Array<Compon
|
||||
case 'modalcomponent':
|
||||
return [openModal, closeModal]
|
||||
case 'aggridcomponent':
|
||||
return [getAgGrid]
|
||||
return [getAgGrid, setSelectedIndex]
|
||||
case 'displaycomponent':
|
||||
case 'dateinputcomponent':
|
||||
case 'textinputcomponent':
|
||||
@@ -90,7 +90,6 @@ export function getComponentControl(type: keyof typeof components): Array<Compon
|
||||
case 'textareainputcomponent':
|
||||
return [setValue]
|
||||
case 'tablecomponent':
|
||||
case 'aggridcomponent':
|
||||
return [setSelectedIndex]
|
||||
default:
|
||||
if (components[type].initialData['componentInput']) {
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
)}
|
||||
>
|
||||
<div class="min-w-[150px]">
|
||||
<div class="sticky z-10 top-0 left-0 w-full p-1.5">
|
||||
<div class="sticky z-10 top-0 left-0 w-full p-1.5 bg-surface">
|
||||
<ClearableInput bind:value={$search} placeholder="Search outputs..." />
|
||||
</div>
|
||||
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
import PanelSection from './common/PanelSection.svelte'
|
||||
import Toggle from '$lib/components/Toggle.svelte'
|
||||
import InputsSpecsEditor from './InputsSpecsEditor.svelte'
|
||||
import Tooltip from '$lib/components/Tooltip.svelte'
|
||||
|
||||
export let groupFields: RichConfigurations | undefined
|
||||
|
||||
@@ -40,13 +39,13 @@
|
||||
}
|
||||
console.log(groupFields)
|
||||
}}
|
||||
options={{ right: 'container is a component group' }}
|
||||
/>
|
||||
<Tooltip
|
||||
>Group fields allow inner components to depend on the group fields which make the container a
|
||||
options={{
|
||||
right: 'container is a component group',
|
||||
rightTooltip: `Group fields allow inner components to depend on the group fields which make the container a
|
||||
group of component that is encapsulated. Inside the group, it is possible to retrieve the values
|
||||
using `group.<x />` where x is the group field name</Tooltip
|
||||
>
|
||||
using \`group.<x />\` where x is the group field name`
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
{#if groupFields != undefined}
|
||||
<PanelSection
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
)}
|
||||
>
|
||||
<div class="flex justify-between flex-wrap items-center w-full gap-1">
|
||||
<div class="text-sm inline-flex items-center font-semibold text-primary {titlePadding} gap-1">
|
||||
<div class="text-xs inline-flex items-center font-semibold text-primary {titlePadding} gap-1">
|
||||
<span class="truncate">
|
||||
{title}
|
||||
</span>
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
<script lang="ts">
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import { createEventDispatcher, getContext } from 'svelte'
|
||||
import type { AppEditorContext } from '../types'
|
||||
import { writable } from 'svelte/store'
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
@@ -25,6 +27,9 @@
|
||||
export let onTop
|
||||
export let shadow: { x: number; y: number; w: number; h: number } | undefined = undefined
|
||||
|
||||
const ctx = getContext<AppEditorContext>('AppEditorContext')
|
||||
const scale = ctx ? ctx.scale : writable(100)
|
||||
|
||||
const divId = `component-${id}`
|
||||
let shadowElement
|
||||
|
||||
@@ -50,19 +55,14 @@
|
||||
let irect = shadowElement.getBoundingClientRect()
|
||||
let shadowBound
|
||||
|
||||
const factor = $scale / 100
|
||||
|
||||
if (subgrid) {
|
||||
let subGridParent = subgrid.parentElement
|
||||
let subGridParentRect = subGridParent.getBoundingClientRect()
|
||||
let prect = subgrid.getBoundingClientRect()
|
||||
|
||||
const subGridOffset = {
|
||||
x: subGridParentRect.x - prect.x,
|
||||
y: subGridParentRect.y - prect.y
|
||||
}
|
||||
|
||||
shadowBound = {
|
||||
x: irect.x - prect.left - subGridOffset.x,
|
||||
y: irect.y - prect.top - subGridOffset.y
|
||||
x: irect.x - prect.left,
|
||||
y: irect.y - prect.top
|
||||
}
|
||||
} else {
|
||||
shadowBound = irect
|
||||
@@ -71,8 +71,8 @@
|
||||
const xdragBound = rect.left + cordDiff.x
|
||||
const ydragBound = rect.top + cordDiff.y
|
||||
|
||||
cordDiff.x = shadow.x * xPerPx + gapX - (shadowBound.x - xdragBound)
|
||||
cordDiff.y = shadow.y * yPerPx + gapY - (shadowBound.y - ydragBound)
|
||||
cordDiff.x = shadow.x * xPerPx + gapX - (shadowBound.x - xdragBound * factor) * factor
|
||||
cordDiff.y = shadow.y * yPerPx + gapY - (shadowBound.y - ydragBound * factor) * factor
|
||||
|
||||
active = false
|
||||
trans = true
|
||||
@@ -121,19 +121,13 @@
|
||||
|
||||
let irect = gridItem.getBoundingClientRect()
|
||||
if (subgrid && subgrid.parentElement) {
|
||||
let subGridParent = subgrid.parentElement
|
||||
let subGridParentRect = subGridParent.getBoundingClientRect()
|
||||
|
||||
let prect = subgrid.getBoundingClientRect()
|
||||
|
||||
const subGridOffset = {
|
||||
x: subGridParentRect.x - prect.x,
|
||||
y: subGridParentRect.y - prect.y
|
||||
}
|
||||
const factor = $scale / 100
|
||||
|
||||
rect = {
|
||||
top: irect.top - prect.top - subGridOffset.y,
|
||||
left: irect.left - prect.left - subGridOffset.x
|
||||
top: (irect.top - prect.top) / factor,
|
||||
left: (irect.left - prect.left) / factor
|
||||
}
|
||||
} else {
|
||||
rect = irect
|
||||
@@ -145,8 +139,8 @@
|
||||
dragClosure = () => {
|
||||
dragClosure = undefined
|
||||
|
||||
initX = clientX
|
||||
initY = clientY
|
||||
initX = (clientX / $scale) * 100
|
||||
initY = (clientY / $scale) * 100
|
||||
dispatch('initmove')
|
||||
}
|
||||
window.addEventListener('pointermove', pointermove)
|
||||
@@ -200,7 +194,7 @@
|
||||
event.stopImmediatePropagation()
|
||||
|
||||
const { clientX, clientY } = event
|
||||
const cordDiff = { x: clientX - initX, y: clientY - initY }
|
||||
const cordDiff = { x: (clientX / $scale) * 100 - initX, y: (clientY / $scale) * 100 - initY }
|
||||
|
||||
dispatch('move', { cordDiff, clientY })
|
||||
}
|
||||
@@ -286,8 +280,8 @@
|
||||
|
||||
const resizePointerMove = ({ pageX, pageY }) => {
|
||||
if (shadow) {
|
||||
newSize.width = initSize.width + pageX - resizeInitPos.x
|
||||
newSize.height = initSize.height + pageY - resizeInitPos.y
|
||||
newSize.width = initSize.width + ((pageX - resizeInitPos.x) / $scale) * 100
|
||||
newSize.height = initSize.height + ((pageY - resizeInitPos.y) / $scale) * 100
|
||||
|
||||
// Get max col number
|
||||
let maxWidth = cols - shadow.x
|
||||
@@ -314,6 +308,7 @@
|
||||
</script>
|
||||
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
|
||||
<div
|
||||
draggable="false"
|
||||
on:pointerdown|stopPropagation|preventDefault={pointerdown}
|
||||
|
||||
@@ -249,7 +249,8 @@ export type AppEditorContext = {
|
||||
pickVariableCallback: Writable<((path: string) => void) | undefined>
|
||||
selectedComponentInEditor: Writable<string | undefined>
|
||||
movingcomponents: Writable<string[] | undefined>
|
||||
jobsDrawerOpen: Writable<boolean>
|
||||
jobsDrawerOpen: Writable<boolean>,
|
||||
scale: Writable<number>
|
||||
}
|
||||
|
||||
export type FocusedGrid = { parentComponentId: string; subGridIndex: number }
|
||||
|
||||
@@ -14,3 +14,5 @@ export async function getResourceTypes() {
|
||||
return nrts
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -789,7 +789,7 @@ const config = {
|
||||
overflow: 'auto !important'
|
||||
},
|
||||
'.splitpanes__splitter': {
|
||||
backgroundColor: lightTheme.border + ' !important',
|
||||
backgroundColor: 'rgb(243 244 246) !important',
|
||||
margin: '0 !important',
|
||||
border: 'none !important',
|
||||
'&::after': {
|
||||
|
||||
Reference in New Issue
Block a user