feat(frontend): hide/show app editor panels (#4266)

* feat(frontend): wip

* feat(frontend): wip

* feat(frontend): wip

* feat(frontend): wip

* feat(frontend): add all shortcuts

* feat(frontend): improve the shorcuts

* feat(frontend): improve the shorcuts

* feat(frontend): hide the buttons in preview mode

* feat(frontend): improve the shorcuts

* feat(frontend): fix shortcuts

* feat(frontend): fix shared state between log panels
This commit is contained in:
Faton Ramadani
2024-08-26 10:48:36 +02:00
committed by GitHub
parent 32c0b89729
commit fdfd385a68
9 changed files with 489 additions and 158 deletions

View File

@@ -28,12 +28,10 @@
import ComponentList from './componentsPanel/ComponentList.svelte'
import ContextPanel from './contextPanel/ContextPanel.svelte'
import InlineScriptsPanel from './inlineScriptsPanel/InlineScriptsPanel.svelte'
import { page } from '$app/stores'
import ItemPicker from '$lib/components/ItemPicker.svelte'
import VariableEditor from '$lib/components/VariableEditor.svelte'
import { VariableService, type Policy } from '$lib/gen'
import { VariableService, type Job, type Policy } from '$lib/gen'
import { initHistory } from '$lib/history'
import { Component, Minus, Paintbrush, Plus, Smartphone } from 'lucide-svelte'
import { findGridItem, findGridItemParentGrid } from './appUtils'
@@ -56,6 +54,8 @@
import type DiffDrawer from '$lib/components/DiffDrawer.svelte'
import RunnableJobPanel from './RunnableJobPanel.svelte'
import { goto, replaceState } from '$app/navigation'
import HideButton from './settingsPanel/HideButton.svelte'
import AppEditorBottomPanel from './AppEditorBottomPanel.svelte'
export let app: App
export let path: string
@@ -311,7 +311,7 @@
let gridPanelSize = 70
let leftPanelSize = 22
let centerPanelSize = 63
let centerPanelSize = 56
let rightPanelSize = 22
let tmpRunnablePanelSize = -1
@@ -540,11 +540,105 @@
let runnableJobEnterTimeout: NodeJS.Timeout | undefined = undefined
let stillInJobEnter = false
let storedLeftPanelSize = 0
let storedRightPanelSize = 0
let storedBottomPanelSize = 0
let centerPanelWidth = 0
function hideLeftPanel() {
storedLeftPanelSize = leftPanelSize
leftPanelSize = 0
centerPanelSize = centerPanelSize + storedLeftPanelSize
}
function hideRightPanel() {
storedRightPanelSize = rightPanelSize
rightPanelSize = 0
centerPanelSize = centerPanelSize + storedRightPanelSize
}
function hideBottomPanel() {
storedBottomPanelSize = runnablePanelSize
gridPanelSize = 99
runnablePanelSize = 0
}
function showLeftPanel() {
leftPanelSize = storedLeftPanelSize
centerPanelSize = centerPanelSize - storedLeftPanelSize
storedLeftPanelSize = 0
}
function showRightPanel() {
rightPanelSize = storedRightPanelSize
centerPanelSize = centerPanelSize - storedRightPanelSize
storedRightPanelSize = 0
}
function showBottomPanel() {
runnablePanelSize = storedBottomPanelSize
gridPanelSize = gridPanelSize - storedBottomPanelSize
storedBottomPanelSize = 0
}
function keydown(event: KeyboardEvent) {
let classes = event.target?.['className']
if (
(typeof classes === 'string' && classes.includes('inputarea')) ||
['INPUT', 'TEXTAREA'].includes(document.activeElement?.tagName!)
) {
return
}
switch (event.key) {
case 'b': {
if (event.ctrlKey || event.metaKey) {
event.preventDefault()
if (leftPanelSize !== 0) {
hideLeftPanel()
} else {
showLeftPanel()
}
}
break
}
case 'u': {
if (event.ctrlKey || event.metaKey) {
event.preventDefault()
if (rightPanelSize !== 0) {
hideRightPanel()
} else {
showRightPanel()
}
}
break
}
case 'l': {
if (event.ctrlKey || event.metaKey) {
event.preventDefault()
if (runnablePanelSize !== 0) {
hideBottomPanel()
} else {
showBottomPanel()
}
}
break
}
}
}
let testJob: Job | undefined = undefined
let jobToWatch: { componentId: string; job: string } | undefined = undefined
</script>
<DarkModeObserver on:change={onThemeChange} />
<svelte:window on:hashchange={hashchange} />
<svelte:window on:hashchange={hashchange} on:keydown={keydown} />
{#if !$userStore?.operator}
{#if $appStore}
@@ -556,6 +650,15 @@
{diffDrawer}
bind:savedApp
{version}
leftPanelHidden={leftPanelSize === 0}
rightPanelHidden={rightPanelSize === 0}
bottomPanelHidden={runnablePanelSize === 0}
on:showLeftPanel={() => showLeftPanel()}
on:showRightPanel={() => showRightPanel()}
on:hideLeftPanel={() => hideLeftPanel()}
on:hideRightPanel={() => hideRightPanel()}
on:hideBottomPanel={() => hideBottomPanel()}
on:showBottomPanel={() => showBottomPanel()}
/>
{#if $mode === 'preview'}
<SplitPanesWrapper>
@@ -601,7 +704,7 @@
<!-- {yTop} -->
<SecondaryMenu right={false} />
<ContextPanel />
<ContextPanel on:hidePanel={() => hideLeftPanel()} />
</div>
</Pane>
<Pane bind:size={centerPanelSize}>
@@ -618,7 +721,39 @@
'wm-app-viewer h-full overflow-visible'
)}
style={$appStore.css?.['app']?.['viewer']?.style}
bind:clientWidth={centerPanelWidth}
>
{#if leftPanelSize === 0}
<div class="absolute top-0.5 left-0.5 z-50">
<HideButton
on:click={() => showLeftPanel()}
direction="right"
hidden
btnClasses="border bg-surface"
/>
</div>
{/if}
{#if rightPanelSize === 0}
<div class="absolute top-0.5 right-0.5 z-50">
<HideButton
on:click={() => showRightPanel()}
direction="left"
hidden
btnClasses="border bg-surface"
/>
</div>
{/if}
{#if runnablePanelSize === 0}
<div class="absolute bottom-0.5 right-0.5 z-50">
<HideButton
on:click={() => showBottomPanel()}
direction="bottom"
hidden
btnClasses="border bg-surface"
/>
</div>
{/if}
<div class="absolute bottom-2 left-2 z-50 border bg-surface">
<div class="flex flex-row gap-2 text-xs items-center h-8 px-1">
<Button
@@ -706,9 +841,7 @@
</Pane>
{#if $connectingInput?.opened == false && !$componentActive}
<Pane bind:size={runnablePanelSize}>
<!-- svelte-ignore a11y-no-static-element-interactions -->
<div
class="relative h-full w-full overflow-x-visible"
<AppEditorBottomPanel
on:mouseenter={() => {
runnableJobEnterTimeout && clearTimeout(runnableJobEnterTimeout)
stillInJobEnter = true
@@ -718,6 +851,7 @@
}
}, 200)
}}
on:hidePanel={() => hideBottomPanel()}
on:mouseleave={() => {
stillInJobEnter = false
runnableJobEnterTimeout = setTimeout(
@@ -725,98 +859,119 @@
200
)
}}
{rightPanelSize}
{centerPanelWidth}
{runnablePanelSize}
>
<InlineScriptsPanel />
<RunnableJobPanel />
</div>
<RunnableJobPanel
float={rightPanelSize !== 0}
hidden={runnablePanelSize === 0}
bind:testJob
bind:jobToWatch
/>
</AppEditorBottomPanel>
</Pane>
{/if}
</Splitpanes>
</Pane>
<Pane bind:size={rightPanelSize} minSize={15} maxSize={33}>
<div bind:clientWidth={$runnableJob.width} class="relative flex flex-col h-full">
<Tabs bind:selected={selectedTab} wrapperClass="!min-h-[42px]" class="!h-full">
<Popover disappearTimeout={0} notClickable placement="bottom">
<svelte:fragment slot="text">Component library</svelte:fragment>
<Tab
value="insert"
size="xs"
class="h-full"
on:pointerdown={() => {
if ($cssEditorOpen) {
$cssEditorOpen = false
selectedTab = 'insert'
}
}}
id="app-editor-component-library-tab"
>
<div class="m-1 center-center">
<Plus size={18} />
</div>
</Tab>
</Popover>
<Popover disappearTimeout={0} notClickable placement="bottom">
<svelte:fragment slot="text">Component settings</svelte:fragment>
<Tab
value="settings"
size="xs"
class="h-full"
on:pointerdown={() => {
if ($cssEditorOpen) {
$cssEditorOpen = false
selectedTab = 'settings'
}
}}
>
<div class="m-1 center-center">
<Component size={18} />
</div>
</Tab>
</Popover>
<Popover disappearTimeout={0} notClickable placement="bottom">
<svelte:fragment slot="text">Global styling</svelte:fragment>
<Tab
value="css"
size="xs"
class="h-full"
on:pointerdown={() => {
if (!$cssEditorOpen) {
$cssEditorOpen = true
selectedTab = 'css'
}
}}
>
<div class="m-1 center-center">
<Paintbrush size={18} />
</div>
</Tab>
</Popover>
<div slot="content" class="h-full overflow-y-auto">
<TabContent class="overflow-auto h-full" value="settings">
{#if $selectedComponent !== undefined}
<SettingsPanel
on:delete={() => {
befSelected = undefined
{#if rightPanelSize === 0}
<div class="relative flex flex-col h-full" />
{:else}
<Pane bind:size={rightPanelSize} minSize={15} maxSize={33}>
<div bind:clientWidth={$runnableJob.width} class="relative flex flex-col h-full">
<Tabs bind:selected={selectedTab} wrapperClass="!min-h-[42px]" class="!h-full">
<Popover disappearTimeout={0} notClickable placement="bottom">
<svelte:fragment slot="text">Component library</svelte:fragment>
<Tab
value="insert"
size="xs"
class="h-full"
on:pointerdown={() => {
if ($cssEditorOpen) {
$cssEditorOpen = false
selectedTab = 'insert'
}}
/>
<SecondaryMenu right />
{:else}
<div class="min-w-[150px] text-sm !text-secondary text-center py-8 px-2">
Select a component to see the settings&nbsp;for&nbsp;it
}
}}
id="app-editor-component-library-tab"
>
<div class="m-1 center-center">
<Plus size={18} />
</div>
{/if}
</TabContent>
<TabContent value="insert">
<ComponentList />
</TabContent>
<TabContent value="css" class="h-full">
<CssSettings />
</TabContent>
</div>
</Tabs>
</div>
</Pane>
</Tab>
</Popover>
<Popover disappearTimeout={0} notClickable placement="bottom">
<svelte:fragment slot="text">Component settings</svelte:fragment>
<Tab
value="settings"
size="xs"
class="h-full"
on:pointerdown={() => {
if ($cssEditorOpen) {
$cssEditorOpen = false
selectedTab = 'settings'
}
}}
>
<div class="m-1 center-center">
<Component size={18} />
</div>
</Tab>
</Popover>
<Popover disappearTimeout={0} notClickable placement="bottom">
<svelte:fragment slot="text">Global styling</svelte:fragment>
<Tab
value="css"
size="xs"
class="h-full"
on:pointerdown={() => {
if (!$cssEditorOpen) {
$cssEditorOpen = true
selectedTab = 'css'
}
}}
>
<div class="m-1 center-center">
<Paintbrush size={18} />
</div>
</Tab>
</Popover>
<div class="h-full w-full flex justify-end px-1">
<HideButton
on:click={() => {
storedRightPanelSize = rightPanelSize
rightPanelSize = 0
centerPanelSize = centerPanelSize + storedRightPanelSize
}}
direction="right"
/>
</div>
<div slot="content" class="h-full overflow-y-auto">
<TabContent class="overflow-auto h-full" value="settings">
{#if $selectedComponent !== undefined}
<SettingsPanel
on:delete={() => {
befSelected = undefined
selectedTab = 'insert'
}}
/>
<SecondaryMenu right />
{:else}
<div class="min-w-[150px] text-sm !text-secondary text-center py-8 px-2">
Select a component to see the settings&nbsp;for&nbsp;it
</div>
{/if}
</TabContent>
<TabContent value="insert">
<ComponentList />
</TabContent>
<TabContent value="css" class="h-full">
<CssSettings />
</TabContent>
</div>
</Tabs>
</div>
</Pane>
{/if}
</Splitpanes>
</SplitPanesWrapper>
{/if}

View File

@@ -0,0 +1,24 @@
<script lang="ts">
import { twMerge } from 'tailwind-merge'
import InlineScriptsPanel from './inlineScriptsPanel/InlineScriptsPanel.svelte'
import RunnableJobPanel from './RunnableJobPanel.svelte'
export let rightPanelSize: number = 0
export let centerPanelWidth: number = 0
export let runnablePanelSize: number = 0
</script>
{#if rightPanelSize !== 0}
<!-- svelte-ignore a11y-no-static-element-interactions -->
<div class={twMerge('relative h-full w-full overflow-x-visible')} on:mouseenter on:mouseleave>
<InlineScriptsPanel on:hidePanel />
<RunnableJobPanel hidden={runnablePanelSize === 0} />
<slot />
</div>
{:else}
<div class="flex flex-row relative w-full h-full">
<InlineScriptsPanel width={centerPanelWidth - 400} on:hidePanel />
<slot />
</div>
{/if}

View File

@@ -33,7 +33,7 @@
Smartphone,
FileClock
} from 'lucide-svelte'
import { getContext } from 'svelte'
import { createEventDispatcher, getContext } from 'svelte'
import { Pane, Splitpanes } from 'svelte-splitpanes'
import {
classNames,
@@ -84,6 +84,7 @@
import { collectOneOfFields } from './appUtils'
import Summary from '$lib/components/Summary.svelte'
import ToggleEnable from '$lib/components/common/toggleButton-v2/ToggleEnable.svelte'
import HideButton from './settingsPanel/HideButton.svelte'
async function hash(message) {
try {
@@ -116,6 +117,9 @@
}
| undefined = undefined
export let version: number | undefined = undefined
export let leftPanelHidden: boolean = false
export let rightPanelHidden: boolean = false
export let bottomPanelHidden: boolean = false
const {
app,
@@ -126,7 +130,8 @@
jobsById,
staticExporter,
errorByComponent,
openDebugRun
openDebugRun,
mode
} = getContext<AppViewerContext>('AppViewerContext')
const { history, jobsDrawerOpen, refreshComponents } =
@@ -743,6 +748,8 @@
export function openTroubleshootPanel() {
debugAppDrawerOpen = true
}
const dispatch = createEventDispatcher()
</script>
<svelte:window on:keydown={onKeyDown} />
@@ -1322,6 +1329,43 @@
</div>
</div>
{#if $mode !== 'preview'}
<div class="flex gap-1">
<HideButton
direction="left"
hidden={leftPanelHidden}
on:click={() => {
if (leftPanelHidden) {
dispatch('showLeftPanel')
} else {
dispatch('hideLeftPanel')
}
}}
/>
<HideButton
hidden={bottomPanelHidden}
direction="bottom"
on:click={() => {
if (bottomPanelHidden) {
dispatch('showBottomPanel')
} else {
dispatch('hideBottomPanel')
}
}}
/>
<HideButton
hidden={rightPanelHidden}
direction="right"
on:click={() => {
if (rightPanelHidden) {
dispatch('showRightPanel')
} else {
dispatch('hideRightPanel')
}
}}
/>
</div>
{/if}
{#if $enterpriseLicense && appPath != ''}
<Awareness />
{/if}

View File

@@ -1,21 +1,21 @@
<script lang="ts">
import DisplayResult from '$lib/components/DisplayResult.svelte'
import LogViewer from '$lib/components/LogViewer.svelte'
import { getContext } from 'svelte'
import { Splitpanes, Pane } from 'svelte-splitpanes'
import type { AppEditorContext } from '../types'
import TestJobLoader from '$lib/components/TestJobLoader.svelte'
import type { Job } from '$lib/gen'
import { Loader2 } from 'lucide-svelte'
import RunnalbeJobPanelInner from './RunnalbeJobPanelInner.svelte'
export let float: boolean = true
export let hidden: boolean = false
export let testJob: Job | undefined = undefined
export let jobToWatch: { componentId: string; job: string } | undefined = undefined
const { runnableJobEditorPanel, selectedComponentInEditor } =
getContext<AppEditorContext>('AppEditorContext')
let testJob: Job | undefined = undefined
let testIsLoading = false
let testJobLoader: TestJobLoader
let jobToWatch: { componentId: string; job: string } | undefined = undefined
$: $runnableJobEditorPanel.focused &&
$selectedComponentInEditor &&
$runnableJobEditorPanel.jobs &&
@@ -48,57 +48,25 @@
<TestJobLoader bind:this={testJobLoader} bind:isLoading={testIsLoading} bind:job={testJob} />
{#if ($runnableJobEditorPanel.focused && $selectedComponentInEditor) || logDrawerOpen || resultDrawerOpen}
{#if ($runnableJobEditorPanel.focused && $selectedComponentInEditor) || logDrawerOpen || resultDrawerOpen || !float}
{@const frontendJob = $runnableJobEditorPanel?.frontendJobs[$selectedComponentInEditor ?? '']}
<!-- svelte-ignore a11y-no-static-element-interactions -->
<div
class="absolute z-[100] top-0 right-0 border-t h-full"
style="width: {$runnableJobEditorPanel.width}px; transform: translateX({$runnableJobEditorPanel.width}px);"
>
<Splitpanes horizontal>
<Pane size={frontendJob ? 30 : 50} minSize={10}>
{#if frontendJob}
<div class="p-2 bg-surface-secondary h-full w-full">
<div class="text-sm text-tertiary pb-4">Frontend Job</div>
<div class="text-2xs text-tertiary">Check your browser console to see the logs</div>
</div>
{:else}
<LogViewer
bind:drawerOpen={logDrawerOpen}
small
jobId={testJob?.id}
duration={testJob?.['duration_ms']}
mem={testJob?.['mem_peak']}
content={testJob?.logs}
isLoading={testIsLoading && testJob?.['running'] == false}
tag={testJob?.tag}
/>
{/if}
</Pane>
<Pane size={frontendJob ? 70 : 50} minSize={10} class="text-sm text-tertiary">
{#if frontendJob}
<div class="break-words relative h-full px-1">
<DisplayResult bind:drawerOpen={resultDrawerOpen} result={frontendJob} />
</div>
{:else if testJob != undefined && 'result' in testJob && testJob.result != undefined}
<div class="break-words relative h-full px-1">
<DisplayResult
bind:drawerOpen={resultDrawerOpen}
workspaceId={testJob?.workspace_id}
jobId={testJob?.id}
result={testJob.result}
/></div
>
{:else}
<div class="px-1 pt-1">
{#if testIsLoading}
<Loader2 class="animate-spin" />
{:else}
Test to see the result here
{/if}
</div>
{/if}
</Pane>
</Splitpanes>
</div>
{#if float}
<div
class="absolute z-[100] top-0 right-0 border-t h-full"
style="width: {$runnableJobEditorPanel.width}px; transform: translateX({$runnableJobEditorPanel.width}px);"
>
<RunnalbeJobPanelInner {frontendJob} {testJob} />
</div>
{:else}
<div class="flex flex-col w-full">
{#if $selectedComponentInEditor}
<RunnalbeJobPanelInner {frontendJob} {testJob} />
{:else if !hidden}
<div class="text-sm text-secondary text-center py-8 px-2 border-l h-full">
Logs and results will be displayed here
</div>
{/if}
</div>
{/if}
{/if}

View File

@@ -0,0 +1,61 @@
<script lang="ts">
import DisplayResult from '$lib/components/DisplayResult.svelte'
import LogViewer from '$lib/components/LogViewer.svelte'
import { Splitpanes, Pane } from 'svelte-splitpanes'
import type { Job } from '$lib/gen'
import { Loader2 } from 'lucide-svelte'
export let frontendJob: boolean | any = false
export let testJob: Job | any = undefined
let testIsLoading = false
let logDrawerOpen = false
let resultDrawerOpen = false
</script>
<Splitpanes horizontal>
<Pane size={frontendJob ? 30 : 50} minSize={10}>
{#if frontendJob}
<div class="p-2 bg-surface-secondary h-full w-full">
<div class="text-sm text-tertiary pb-4">Frontend Job</div>
<div class="text-2xs text-tertiary">Check your browser console to see the logs</div>
</div>
{:else}
<LogViewer
bind:drawerOpen={logDrawerOpen}
small
jobId={testJob?.id}
duration={testJob?.['duration_ms']}
mem={testJob?.['mem_peak']}
content={testJob?.logs}
isLoading={testIsLoading && testJob?.['running'] == false}
tag={testJob?.tag}
/>
{/if}
</Pane>
<Pane size={frontendJob ? 70 : 50} minSize={10} class="text-sm text-tertiary">
{#if frontendJob}
<div class="break-words relative h-full px-1">
<DisplayResult bind:drawerOpen={resultDrawerOpen} result={frontendJob} />
</div>
{:else if testJob != undefined && 'result' in testJob && testJob.result != undefined}
<div class="break-words relative h-full px-1">
<DisplayResult
bind:drawerOpen={resultDrawerOpen}
workspaceId={testJob?.workspace_id}
jobId={testJob?.id}
result={testJob.result}
/></div
>
{:else}
<div class="px-1 pt-1">
{#if testIsLoading}
<Loader2 class="animate-spin" />
{:else}
Test to see the result here
{/if}
</div>
{/if}
</Pane>
</Splitpanes>

View File

@@ -1,6 +1,6 @@
<script lang="ts">
import { classNames } from '$lib/utils'
import { getContext } from 'svelte'
import { createEventDispatcher, getContext } from 'svelte'
import type { AppViewerContext, ContextPanelContext } from '../../types'
import { connectInput } from '../appUtils'
@@ -11,16 +11,25 @@
import OutputHeader from './components/OutputHeader.svelte'
import { ClearableInput } from '../../../common'
import DocLink from '../settingsPanel/DocLink.svelte'
import HideButton from '../settingsPanel/HideButton.svelte'
const { connectingInput, app } = getContext<AppViewerContext>('AppViewerContext')
const { search } = getContext<ContextPanelContext>('ContextPanel')
const dispatch = createEventDispatcher()
let hasState: boolean = false
</script>
<PanelSection noPadding titlePadding="px-2 pt-2" title="Outputs">
<svelte:fragment slot="action">
<div class="p-0.5">
<HideButton
on:click={() => {
dispatch('hidePanel')
}}
direction="left"
/>
<DocLink docLink="https://www.windmill.dev/docs/apps/outputs" />
</div>
</svelte:fragment>

View File

@@ -14,6 +14,7 @@
import { ScriptService } from '$lib/gen'
import { workspaceStore } from '$lib/stores'
import { findNextAvailablePath } from '$lib/path'
import { twMerge } from 'tailwind-merge'
const { app, runnableComponents } = getContext<AppViewerContext>('AppViewerContext')
const { selectedComponentInEditor } = getContext<AppEditorContext>('AppEditorContext')
@@ -100,12 +101,17 @@
$app = $app
}
export let width: number | undefined = undefined
</script>
<SplitPanesWrapper>
<Splitpanes class="!overflow-visible">
<Splitpanes
class={twMerge('!overflow-visible')}
style={width !== undefined ? `width:${width}px;` : ''}
>
<Pane size={25}>
<InlineScriptsPanelList />
<InlineScriptsPanelList on:hidePanel />
</Pane>
<Pane size={75}>
{#if !$selectedComponentInEditor}

View File

@@ -1,7 +1,7 @@
<script lang="ts">
import { Badge, Button } from '$lib/components/common'
import { Plus } from 'lucide-svelte'
import { getContext } from 'svelte'
import { createEventDispatcher, getContext } from 'svelte'
import Tooltip from '../../../Tooltip.svelte'
import type { AppEditorContext, AppViewerContext } from '../../types'
import { BG_PREFIX, getAllScriptNames } from '../../utils'
@@ -12,6 +12,7 @@
import { ignoredTutorials } from '$lib/components/tutorials/ignoredTutorials'
import { tutorialInProgress } from '$lib/tutorialUtils'
import DocLink from '../settingsPanel/DocLink.svelte'
import HideButton from '../settingsPanel/HideButton.svelte'
const PREFIX = 'script-selector-' as const
@@ -79,11 +80,22 @@
}
let appTutorials: AppTutorials | undefined = undefined
const dispatch = createEventDispatcher()
</script>
<PanelSection title="Runnables" id="app-editor-runnable-panel">
<svelte:fragment slot="action">
<DocLink docLink="https://www.windmill.dev/docs/apps/app-runnable-panel#creating-a-runnable" />
<div class="flex flex-row gap-1">
<HideButton
direction="bottom"
on:click={() => {
dispatch('hidePanel')
}}
/>
<DocLink
docLink="https://www.windmill.dev/docs/apps/app-runnable-panel#creating-a-runnable"
/>
</div>
</svelte:fragment>
<div class="w-full flex flex-col gap-6 py-1">
<div>

View File

@@ -0,0 +1,52 @@
<script lang="ts">
import Popover from '$lib/components/Popover.svelte'
import { ButtonType } from '$lib/components/common'
import Button from '$lib/components/common/button/Button.svelte'
import { getModifierKey } from '$lib/utils'
import { PanelBottomClose, PanelLeftClose, PanelRightClose } from 'lucide-svelte'
import { twMerge } from 'tailwind-merge'
export let btnClasses: string | undefined = undefined
export let size: ButtonType.Size = 'xs'
export let direction: 'left' | 'right' | 'bottom' = 'right'
export let hidden: boolean = false
const IconMap = {
left: PanelLeftClose,
right: PanelRightClose,
bottom: PanelBottomClose
}
const shortcuts = {
left: 'B',
right: 'U',
bottom: 'L'
}
</script>
<Popover>
<svelte:fragment slot="text">
<div class="flex flex-row gap-1">
{hidden ? 'Show' : 'Hide '} the {direction} panel.
<div class="flex flex-row items-center !text-md opacity-60 gap-0 font-normal">
{getModifierKey()}{shortcuts[direction]}
</div>
</div>
</svelte:fragment>
<Button
iconOnly
startIcon={{
icon: IconMap[direction]
}}
{size}
btnClasses={twMerge(
'p-1 text-gray-300 hover:!text-gray-600 dark:text-gray-500 dark:hover:!text-gray-200 bg-transparent',
hidden ? 'bg-surface-selected !text-primary' : '',
btnClasses
)}
on:click
color="light"
/>
</Popover>