fix(frontend): Clean up app editor (#1267)

* fix(frontend): Clean up app editor

* fix(frontend): Add outputs search empty state

* fix(frontend): Add remove button to icon input

* label

* fix(frontend): Iconed app button
This commit is contained in:
Ádám Kovács
2023-03-08 19:02:19 +01:00
committed by GitHub
parent ce8f48101e
commit d83c98bafd
11 changed files with 249 additions and 200 deletions
@@ -166,11 +166,11 @@
{loading}
>
<span class="truncate inline-flex gap-2 items-center">
{#if beforeIconComponent}
{#if beforeIcon && beforeIconComponent}
<svelte:component this={beforeIconComponent} size={14} />
{/if}
<div>{labelValue}</div>
{#if afterIconComponent}
{#if afterIcon && afterIconComponent}
<svelte:component this={afterIconComponent} size={14} />
{/if}
</span>
@@ -22,12 +22,10 @@
let strokeWidth: number
let iconComponent: any
$: icon && handleIcon()
$: handleIcon(icon)
async function handleIcon() {
if (icon) {
iconComponent = await loadIcon(icon)
}
async function handleIcon(i?: string) {
iconComponent = i ? await loadIcon(i) : undefined
}
$: css = concatCustomCss($app.css?.iconcomponent, customCss)
@@ -45,7 +43,7 @@
class={css?.container?.class ?? ''}
style={css?.container?.style ?? ''}
>
{#if iconComponent}
{#if icon && iconComponent}
<svelte:component
this={iconComponent}
size={size || 24}
@@ -204,7 +204,7 @@
</Pane>
<Pane size={21} minSize={5} maxSize={33}>
<div class="relative flex flex-col h-full">
<Tabs bind:selected={selectedTab} class="!border-b-2 !border-gray-200 !h-full">
<Tabs bind:selected={selectedTab} wrapperClass="!h-[40px]" class="!h-full">
<Tab value="insert" size="xs">
<div class="m-1 center-center gap-2">
<Icon data={faPlus} />
@@ -471,13 +471,14 @@
<div class="min-w-64 w-64">
<input type="text" placeholder="App summary" class="text-sm w-full" bind:value={$summary} />
</div>
<div class="flex gap-1">
<div class="flex">
<Button
title="Undo"
disabled={$history.index == 0}
variant="border"
color="dark"
color="light"
size="xs"
btnClasses="!min-h-[30px] !rounded-r-none"
on:click={async () => {
$app = undo(history, $app)
}}
@@ -488,8 +489,9 @@
title="Redo"
disabled={$history.index == $history.history.length - 1}
variant="border"
color="dark"
color="light"
size="xs"
btnClasses="!min-h-[30px] !rounded-l-none !border-r-0"
on:click={async () => {
$app = redo(history)
}}
@@ -529,7 +531,7 @@
<ToggleButton position="left" value={false} size="xs">
<div class="flex gap-1 justify-start items-center">
<AlignHorizontalSpaceAround size={14} />
<Tooltip light>
<Tooltip light class="mb-0.5">
The max width is 1168px and the content stay centered instead of taking the full page
width
</Tooltip>
@@ -1,12 +1,14 @@
<script lang="ts">
import type { AppEditorContext } from '../../types'
import { getContext } from 'svelte'
import { fade, slide } from 'svelte/transition'
import { dirtyStore } from '$lib/components/common/confirmationModal/dirtyStore'
import { components as componentsRecord, COMPONENT_SETS, type AppComponent } from '../component'
import ListItem from './ListItem.svelte'
import { insertNewGridItem } from '../appUtils'
import { X } from 'lucide-svelte'
import { push } from '$lib/history'
import { flip } from 'svelte/animate'
const { app, selectedComponent, focusedGrid, history } =
getContext<AppEditorContext>('AppEditorContext')
@@ -34,7 +36,7 @@
}))
</script>
<section class="p-2 sticky bg-white border-b w-full h-12 z-20 top-0">
<section class="p-2 sticky bg-white w-full z-20 top-0">
<div class="relative">
<input
bind:value={search}
@@ -52,32 +54,41 @@
</div>
</section>
{#if componentsFiltered.reduce((acc, { components }) => acc + components.length, 0) === 0}
<div class="text-xs text-gray-500 py-1 px-2"> No components found </div>
{:else}
{#each componentsFiltered as { title, components }, index (index)}
<ListItem title={`${title} (${components.length})`}>
{#if components.length}
<div class="flex flex-wrap gap-2 py-2">
{#each components as item}
<button
on:click={() => addComponent(item)}
title={componentsRecord[item].name}
class="border w-24 shadow-sm h-16 p-2 flex flex-col gap-2 items-center
justify-center bg-white rounded-md hover:bg-gray-100 duration-200"
>
<svelte:component this={componentsRecord[item].icon} />
<div class="text-xs w-full text-center ellipsize">
{componentsRecord[item].name}
<div class="relative">
{#if componentsFiltered.reduce((acc, { components }) => acc + components.length, 0) === 0}
<div
in:fade|local={{ duration: 50, delay: 50 }}
out:fade|local={{ duration: 50 }}
class="absolute left-0 top-0 w-full text-sm text-gray-500 text-center py-6 px-2"
>
No components found
</div>
{:else}
<div in:fade|local={{ duration: 50, delay: 50 }} out:fade|local={{ duration: 50 }}>
{#each componentsFiltered as { title, components }, index (index)}
{#if components.length}
<div transition:slide|local={{ duration: 300 }}>
<ListItem title={`${title} (${components.length})`}>
<div class="flex flex-wrap gap-2 py-2">
{#each components as item (item)}
<button
animate:flip={{ duration: 300 }}
on:click={() => addComponent(item)}
title={componentsRecord[item].name}
class="border w-24 shadow-sm h-16 p-2 flex flex-col gap-2 items-center
justify-center bg-white rounded-md hover:bg-gray-100 duration-200"
>
<svelte:component this={componentsRecord[item].icon} />
<div class="text-xs w-full text-center ellipsize">
{componentsRecord[item].name}
</div>
</button>
{/each}
</div>
</button>
{/each}
</div>
{:else}
<div class="text-xs text-gray-500 py-1 px-2">
There are no components in this group yet
</div>
{/if}
</ListItem>
{/each}
{/if}
</ListItem>
</div>
{/if}
{/each}
</div>
{/if}
</div>
@@ -2,6 +2,8 @@
import { classNames } from '$lib/utils'
import { X } from 'lucide-svelte'
import { getContext } from 'svelte'
import { flip } from 'svelte/animate'
import { fade } from 'svelte/transition'
import type { AppEditorContext } from '../../types'
import { findGridItem } from '../appUtils'
import { components } from '../component'
@@ -58,9 +60,7 @@
</script>
<PanelSection noPadding titlePadding="px-4 pt-2 pb-0.5" title="Outputs">
<div
class="overflow-auto min-w-[150px] border-t w-full relative flex flex-col gap-4 px-2 pt-4 pb-2"
>
<div class="overflow-auto h-full min-w-[150px] w-full relative flex flex-col gap-4 p-2">
<div class="relative {$connectingInput?.opened ? 'bg-white z-50' : ''}">
<input
bind:value={search}
@@ -76,65 +76,81 @@
</button>
{/if}
</div>
{#each filteredPanels as [componentId, outputs] (componentId)}
{#if outputs.length > 0 && $worldStore?.outputsById[componentId]}
{@const name = getComponentNameById(componentId)}
<div>
<div
class="flex {$connectingInput?.opened
? 'bg-white z-50'
: ''} flex-row justify-between w-full"
>
<button
on:click|stopPropagation|preventDefault={$connectingInput.opened
? undefined
: () => ($selectedComponent = componentId)}
class={classNames(
'px-2 text-2xs py-0.5 border-t border-x font-bold rounded-t-sm w-fit',
$selectedComponent === componentId
? ' bg-blue-500 text-white border-blue-500'
: 'bg-gray-100 text-gray-500 border-gray-200'
)}
>
{componentId}
</button>
<span
class={classNames(
'px-1 text-2xs py-0.5 font-semibold rounded-t-sm w-fit',
'bg-gray-700 text-white'
)}
>
{name}
</span>
</div>
<!-- svelte-ignore a11y-click-events-have-key-events -->
<div
class={classNames(
$connectingInput?.opened ? 'bg-white z-50' : '',
`w-full py-2 grow border relative break-all `,
$selectedComponent === componentId ? 'border border-blue-500 ' : '',
$connectingInput.hoveredComponent === componentId ? 'outline outline-blue-500' : ''
)}
>
{#key $selectedComponent}
{#key $connectingInput?.opened}
<ComponentOutputViewer
outputs={$connectingInput?.opened && $selectedComponent === componentId
? name == 'Table'
? ['search']
: []
: outputs}
<div class="relative">
{#each filteredPanels as [componentId, outputs] (componentId)}
<div
animate:flip={{ duration: 300 }}
in:fade|local={{ duration: 100, delay: 50 }}
out:fade|local={{ duration: 100 }}
>
{#if outputs.length > 0 && $worldStore?.outputsById[componentId]}
{@const name = getComponentNameById(componentId)}
<div>
<div
class="flex {$connectingInput?.opened
? 'bg-white z-50'
: ''} flex-row justify-between w-full"
>
<button
on:click|stopPropagation|preventDefault={$connectingInput.opened
? undefined
: () => ($selectedComponent = componentId)}
class={classNames(
'px-2 text-2xs py-0.5 border-t border-x font-bold rounded-t-sm w-fit',
$selectedComponent === componentId
? ' bg-indigo-500/90 text-white border-indigo-500/90'
: 'bg-gray-100 text-gray-500 border-gray-200'
)}
>
{componentId}
on:select={({ detail }) => {
connectInput(componentId, detail)
}}
/>
{/key}
{/key}
</div>
</button>
<span
class={classNames(
'px-1 text-2xs py-0.5 font-semibold rounded-t-sm w-fit',
'bg-gray-700 text-white'
)}
>
{name}
</span>
</div>
<div
class={classNames(
$connectingInput?.opened ? 'bg-white z-50' : '',
`w-full py-2 grow border relative break-all `,
$selectedComponent === componentId ? 'border border-indigo-500/90 ' : '',
$connectingInput.hoveredComponent === componentId
? 'outline outline-indigo-500/90'
: ''
)}
>
{#key $selectedComponent}
{#key $connectingInput?.opened}
<ComponentOutputViewer
outputs={$connectingInput?.opened && $selectedComponent === componentId
? name == 'Table'
? ['search']
: []
: outputs}
{componentId}
on:select={({ detail }) => {
connectInput(componentId, detail)
}}
/>
{/key}
{/key}
</div>
</div>
{/if}
</div>
{/if}
{/each}
{:else}
<div
in:fade|local={{ duration: 50, delay: 100 }}
out:fade|local={{ duration: 50 }}
class="absolute left-0 top-0 w-full text-sm text-gray-500 text-center py-4 px-2"
>
No outputs found
</div>
{/each}
</div>
</div>
</PanelSection>
@@ -1,8 +1,8 @@
<script lang="ts">
import { Badge, Button } from '$lib/components/common'
import { faPlus } from '@fortawesome/free-solid-svg-icons'
import { Plus } from 'lucide-svelte'
import { getContext } from 'svelte'
import Tooltip from '../../../Tooltip.svelte'
import type { AppEditorContext } from '../../types'
import { getAllScriptNames } from '../../utils'
import PanelSection from '../settingsPanel/common/PanelSection.svelte'
@@ -12,7 +12,6 @@
export let selectedScriptComponentId: string | undefined = undefined
const { app, selectedComponent } = getContext<AppEditorContext>('AppEditorContext')
let list: HTMLElement
function selectScript(id: string) {
selectedScriptComponentId = id
@@ -53,106 +52,110 @@
}
</script>
<div
class="sticky top-0 py-0.5 px-2 text-left bg-gray-50 text-gray-800 text-xs font-semibold tracking-wide border-b border-gray-300"
>
Runnables
</div>
<div bind:this={list} class="grow flex flex-col gap-4">
<PanelSection title="Inline scripts" smallPadding>
<div class="flex flex-col gap-2 w-full">
{#if runnables.inline.length > 0}
<div class="flex gap-1 flex-col ">
{#each runnables.inline as { name, id }, index (index)}
<PanelSection title="Runnables" smallPadding>
<div class="w-full flex flex-col gap-6 py-1">
<div>
<div class="text-sm font-semibold truncate mb-1"> Inline scripts </div>
<div class="flex flex-col gap-2 w-full">
{#if runnables.inline.length > 0}
<div class="flex gap-1 flex-col ">
{#each runnables.inline as { name, id }, index (index)}
<button
id={PREFIX + id}
class="border flex gap-1 truncate font-normal justify-between w-full items-center py-1 px-2 rounded-sm duration-200
{selectedScriptComponentId === id ? 'border-blue-500 bg-blue-100' : 'hover:bg-blue-50'}"
on:click={() => selectScript(id)}
>
<span class="text-2xs truncate">{name}</span>
<div>
<Badge color="dark-indigo">{id}</Badge>
</div>
</button>
{/each}
</div>
{/if}
{#if $app.unusedInlineScripts?.length > 0}
<div class="flex gap-1 flex-col ">
{#each $app.unusedInlineScripts as unusedInlineScript, index (index)}
{@const id = `unused-${index}`}
<button
id={PREFIX + id}
class="border flex gap-1 truncate font-normal justify-between w-full items-center py-1 px-2 rounded-sm duration-200
{selectedScriptComponentId === id ? 'border-blue-500 bg-blue-100' : 'hover:bg-blue-50'}"
on:click={() => selectScript(id)}
>
<span class="text-2xs truncate">{unusedInlineScript.name}</span>
<Badge color="red">Detached</Badge>
</button>
{/each}
</div>
{/if}
{#if runnables.inline.length == 0 && $app.unusedInlineScripts?.length == 0}
<div class="text-xs text-gray-500">No inline scripts</div>
{/if}
</div>
</div>
<div>
<div class="text-sm font-semibold truncate mb-1"> Workspace/Hub </div>
<div class="flex flex-col gap-1 w-full">
{#if runnables.imported.length > 0}
{#each runnables.imported as { name, id }, index (index)}
<button
id={PREFIX + id}
class="border flex gap-1 truncate font-normal justify-between w-full items-center py-1 px-2 rounded-sm duration-200
{selectedScriptComponentId === id ? 'border-blue-500 bg-blue-100' : 'hover:bg-blue-50'}"
class="border flex gap-1 truncate font-normal justify-between w-full items-center py-1 px-2 rounded-sm duration-200
{selectedScriptComponentId === id ? 'border-blue-500 bg-blue-100' : 'hover:bg-blue-50'}"
on:click={() => selectScript(id)}
>
<span class="text-2xs truncate">{name}</span>
<div>
<Badge color="dark-indigo">{id}</Badge>
</div>
<Badge color="dark-indigo">{id}</Badge>
</button>
{/each}
</div>
{/if}
{:else}
<div class="text-xs text-gray-500">No imported scripts/flows</div>
{/if}
</div>
</div>
{#if $app.unusedInlineScripts?.length > 0}
<div class="flex gap-1 flex-col ">
{#each $app.unusedInlineScripts as unusedInlineScript, index (index)}
{@const id = `unused-${index}`}
<div>
<div class="w-full flex justify-between items-center">
<div class="text-sm font-semibold truncate mb-1">
Background scripts
<Tooltip class="mb-0.5">
Background scripts are triggered upon global refresh or when their input changes. The
result of a background script can be shared among many components.
</Tooltip>
</div>
<Button
size="xs"
color="light"
variant="border"
btnClasses="!rounded-full !p-1"
title="Create a new background script"
aria-label="Create a new background script"
on:click={createBackgroundScript}
>
<Plus size={14} class="text-gray-500" />
</Button>
</div>
<div class="flex flex-col gap-1 w-full">
{#if $app.hiddenInlineScripts?.length > 0}
{#each $app.hiddenInlineScripts as { name }, index (index)}
{@const id = `bg_${index}`}
<button
id={PREFIX + id}
class="border flex gap-1 truncate font-normal justify-between w-full items-center py-1 px-2 rounded-sm duration-200
{selectedScriptComponentId === id ? 'border-blue-500 bg-blue-100' : 'hover:bg-blue-50'}"
class="border flex gap-1 truncate font-normal justify-between w-full items-center py-1 px-2 rounded-sm duration-200
{selectedScriptComponentId === id ? 'border-blue-500 bg-blue-100' : 'hover:bg-blue-50'}"
on:click={() => selectScript(id)}
>
<span class="text-2xs truncate">{unusedInlineScript.name}</span>
<Badge color="red">Detached</Badge>
<span class="text-2xs truncate">{name}</span>
<Badge color="dark-indigo">{id}</Badge>
</button>
{/each}
</div>
{/if}
{#if runnables.inline.length == 0 && $app.unusedInlineScripts?.length == 0}
<div class="text-sm text-gray-500">No inline scripts</div>
{/if}
{:else}
<div class="text-xs text-gray-500">No background scripts</div>
{/if}
</div>
</div>
</PanelSection>
<PanelSection title="Workspace/Hub" smallPadding>
<div class="flex flex-col gap-1 w-full">
{#if runnables.imported.length > 0}
{#each runnables.imported as { name, id }, index (index)}
<button
id={PREFIX + id}
class="border flex gap-1 truncate font-normal justify-between w-full items-center py-1 px-2 rounded-sm duration-200
{selectedScriptComponentId === id ? 'border-blue-500 bg-blue-100' : 'hover:bg-blue-50'}"
on:click={() => selectScript(id)}
>
<span class="text-2xs truncate">{name}</span>
<Badge color="dark-indigo">{id}</Badge>
</button>
{/each}
{:else}
<div class="text-sm text-gray-500">No imported scripts/flows</div>
{/if}
</div>
</PanelSection>
<PanelSection
title="Background scripts"
tooltip="Background scripts are triggered upon global refresh or when their input changes. The result
of a background script can be shared among many components."
smallPadding
>
<svelte:fragment slot="action">
<button
class="rounded-full bg-gray-100 hover:bg-gray-200 p-1 border border-gray-200"
on:click={createBackgroundScript}
>
<Plus size={14} class="text-gray-500" />
</button>
</svelte:fragment>
<div class="flex flex-col gap-1 w-full">
{#if $app.hiddenInlineScripts?.length > 0}
{#each $app.hiddenInlineScripts as { name }, index (index)}
{@const id = `bg_${index}`}
<button
id={PREFIX + id}
class="border flex gap-1 truncate font-normal justify-between w-full items-center py-1 px-2 rounded-sm duration-200
{selectedScriptComponentId === id ? 'border-blue-500 bg-blue-100' : 'hover:bg-blue-50'}"
on:click={() => selectScript(id)}
>
<span class="text-2xs truncate">{name}</span>
<Badge color="dark-indigo">{id}</Badge>
</button>
{/each}
{:else}
<div class="text-sm text-gray-500">No items</div>
{/if}
</div>
</PanelSection>
</div>
</div>
</PanelSection>
@@ -53,9 +53,9 @@
{#if panes.length == 0}
<span class="text-xs text-gray-500">No panes</span>
{/if}
<div class="flex gap-2 flex-col mt-2">
<div class="w-full flex gap-2 flex-col mt-2">
{#each panes as value, index (index)}
<div class="flex flex-row gap-2 items-center relative">
<div class="w-full flex flex-row gap-2 items-center relative">
<input type="number" bind:value />
<div class="absolute top-1 right-1">
@@ -51,9 +51,9 @@
{#if tabs.length == 0}
<span class="text-xs text-gray-500">No Tabs</span>
{/if}
<div class="flex gap-2 flex-col mt-2">
<div class="w-full flex gap-2 flex-col mt-2">
{#each tabs as value, index (index)}
<div class="flex flex-row gap-2 items-center relative">
<div class="w-full flex flex-row gap-2 items-center relative">
<input type="text" bind:value />
<div class="absolute top-1 right-1">
@@ -1,6 +1,6 @@
<script lang="ts">
import type { AppInput, StaticInput } from '../../../inputType'
import { Loader2 } from 'lucide-svelte'
import { Loader2, X } from 'lucide-svelte'
import { Popup } from '../../../../common'
export let componentInput: StaticInput<string> & Extract<AppInput, { fieldType: 'icon-select' }>
@@ -46,7 +46,25 @@
}
</script>
<input readonly value={formatName(componentInput.value)} bind:this={anchor} on:focus={getData} />
<div class="relative">
<input
readonly
value={formatName(componentInput.value)}
bind:this={anchor}
on:focus={getData}
class="pr-8"
/>
{#if componentInput.value}
<button
class="absolute right-2 top-1/2 transform -translate-y-1/2 hover:bg-gray-200 rounded-full p-0.5"
on:click|stopPropagation|preventDefault={() => (componentInput.value = undefined)}
title="Clear"
aria-label="Clear"
>
<X size="14" />
</button>
{/if}
</div>
{#if anchor}
<Popup ref={anchor} options={{ placement: 'bottom' }} let:close>
<div class="max-w-xs shadow-[0_10px_40px_-5px_rgba(0,0,0,0.25)] bg-white rounded-md p-2">
@@ -16,6 +16,7 @@
export let selected: string
let c = ''
export { c as class }
export let wrapperClass = ''
export let style = ''
$: selected && updateSelected()
@@ -37,7 +38,7 @@
}
</script>
<div class="overflow-x-auto">
<div class="overflow-x-auto {wrapperClass}">
<div
class={twMerge('border-b border-gray-200 flex flex-row whitespace-nowrap scrollbar-hidden', c)}
{style}