Compare commits

...

1 Commits

Author SHA1 Message Date
Guilhem
7b9e99520f fix layout shift when overflow in centered pages 2026-01-07 21:43:42 +00:00
6 changed files with 120 additions and 131 deletions

View File

@@ -4,14 +4,18 @@
interface Props {
class?: string
children?: import('svelte').Snippet<[{ width: number }]>
fullPage?: boolean
}
let { class: clazz = '', children }: Props = $props()
let { class: clazz = '', children, fullPage = true }: Props = $props()
let width = $state(0)
</script>
<div class="pb-8">
<div
class={twMerge('pb-8', fullPage ? 'h-full overflow-auto' : '')}
style={fullPage ? 'scrollbar-gutter: stable both-edges;' : ''}
>
<div class={twMerge('max-w-7xl mx-auto px-4 sm:px-6 md:px-8', clazz)} bind:clientWidth={width}
>{@render children?.({ width })}</div
>

View File

@@ -11,12 +11,7 @@
type Job
} from '$lib/gen'
import { initHistory, redo, undo } from '$lib/history.svelte'
import {
enterpriseLicense,
userStore,
workspaceStore,
usedTriggerKinds
} from '$lib/stores'
import { enterpriseLicense, userStore, workspaceStore, usedTriggerKinds } from '$lib/stores'
import {
cleanValueProperties,
encodeState,
@@ -1252,7 +1247,7 @@
previewOpen={flowPreviewButtons?.getPreviewOpen()}
/>
{:else}
<CenteredPage>Loading...</CenteredPage>
<CenteredPage fullPage={false}>Loading...</CenteredPage>
{/if}
</div>
{:else}

View File

@@ -8,6 +8,7 @@
import { loadCopilot } from '$lib/aiStore'
import { aiChatManager } from './AIChatManager.svelte'
import { onDestroy } from 'svelte'
import { Menu } from 'lucide-svelte'
interface Props {
noPadding?: boolean
@@ -46,18 +47,18 @@
</script>
{#if !disableAi}
<Splitpanes horizontal={false} class="flex-1 min-h-0">
<Pane size={100 - chatState.size} minSize={50} class="flex flex-col min-h-0">
<Splitpanes horizontal={false} class="h-full ">
<Pane size={100 - chatState.size} minSize={50} class="h-full">
<div
id="content"
class={classNames(
'w-full flex-1 flex flex-col overflow-y-auto',
'w-full h-full overflow-y-auto',
noBorder || $userStore?.operator ? '!pl-0' : isCollapsed ? 'md:pl-12' : 'md:pl-40',
'transition-all ease-in-out duration-200'
)}
>
<main class="flex-1 flex flex-col">
<div class="relative w-full flex-1 flex flex-col">
<main class="flex-1 flex flex-col h-full">
<div class="relative w-full h-full flex flex-col">
<div
class={classNames(
'py-0.5 px-4 sm:px-4 flex flex-row justify-between items-center shadow-sm max-w-7xl md:hidden',
@@ -72,24 +73,10 @@
}}
class="h-8 w-8 inline-flex items-center justify-center rounded-md text-primary hover:text-primary focus:outline-none focus:ring-2 focus:ring-inset focus:ring-indigo-500"
>
<svg
class="h-6 w-6"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width="2"
stroke="currentColor"
aria-hidden="true"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M4 6h16M4 12h16M4 18h16"
/>
</svg>
<Menu size={24} />
</button>
</div>
<div class="flex-1">
<div class="flex-1 min-h-0">
{@render children?.()}
</div>
</div>

View File

@@ -362,7 +362,7 @@
</DrawerContent>
</Drawer>
<CenteredPage>
<CenteredPage fullPage={false}>
<div
class="flex flex-wrap gap-2 items-center justify-between w-full"
use:triggerableByAI={{

View File

@@ -120,10 +120,7 @@
// This ensures the cross-origin isolation headers are fetched from the server
// which are required for SharedArrayBuffer and TypeScript workers to work correctly
const toPath = navigation.to?.url.pathname
if (
toPath &&
(toPath.startsWith('/apps_raw/add') || toPath.startsWith('/apps_raw/edit'))
) {
if (toPath && (toPath.startsWith('/apps_raw/add') || toPath.startsWith('/apps_raw/edit'))) {
const currentPath = navigation.from?.url.pathname
// Reload if we're not on an apps_raw path, or if we're on /apps/get_raw/ (viewing a raw app)
// The /apps/get_raw/ path doesn't have cross-origin isolation headers, so we need to reload

View File

@@ -100,7 +100,11 @@
// Provide workspaceTutorials to child components via a reactive wrapper
let workspaceTutorialsContext = $derived(workspaceTutorials)
setContext('workspaceTutorials', { get value() { return workspaceTutorialsContext } })
setContext('workspaceTutorials', {
get value() {
return workspaceTutorialsContext
}
})
onMount(() => {
// Check if there's a tutorial parameter in the URL
@@ -259,105 +263,107 @@
</DrawerContent>
</Drawer>
<ForkWorkspaceBanner />
<div class="max-w-7xl mx-auto px-4 sm:px-8 md:px-8 h-fit">
{#if $workspaceStore == 'admins'}
<div class="my-4"></div>
<div class="h-full overflow-auto" style="scrollbar-gutter: stable both-edges;">
<ForkWorkspaceBanner />
<div class="max-w-7xl mx-auto px-4 sm:px-8 md:px-8 h-fit">
{#if $workspaceStore == 'admins'}
<div class="my-4"></div>
<Alert title="Admins workspace">
The Admins workspace is for admins only and contains scripts whose purpose is to manage your
Windmill instance, such as keeping resource types up to date.
</Alert>
{/if}
<PageHeader
title="Home"
childrenWrapperDivClasses="flex-1 flex flex-row gap-4 flex-wrap justify-end items-center"
>
{#if !$userStore?.operator}
<span class="text-xs font-normal text-primary">Create a</span>
<CreateActionsScript aiId="create-script-button" aiDescription="Creates a new script" />
{#if HOME_SHOW_CREATE_FLOW}<CreateActionsFlow />{/if}
{#if HOME_SHOW_CREATE_APP}<CreateActionsApp />{/if}
<Alert title="Admins workspace">
The Admins workspace is for admins only and contains scripts whose purpose is to manage your
Windmill instance, such as keeping resource types up to date.
</Alert>
{/if}
</PageHeader>
<PageHeader
title="Home"
childrenWrapperDivClasses="flex-1 flex flex-row gap-4 flex-wrap justify-end items-center"
>
{#if !$userStore?.operator}
<span class="text-xs font-normal text-primary">Create a</span>
<CreateActionsScript aiId="create-script-button" aiDescription="Creates a new script" />
{#if HOME_SHOW_CREATE_FLOW}<CreateActionsFlow />{/if}
{#if HOME_SHOW_CREATE_APP}<CreateActionsApp />{/if}
{/if}
</PageHeader>
<TutorialBanner />
<TutorialBanner />
{#if !$userStore?.operator}
<div class="w-full overflow-auto scrollbar-hidden pb-2">
<Tabs values={['hub', 'workspace']} hashNavigation bind:selected={tab}>
<Tab value="workspace" label="Workspace" icon={Building} />
{#if HOME_SHOW_HUB}
<Tab value="hub" label="Hub" icon={Globe2} />
{/if}
</Tabs>
</div>
{/if}
{#if tab == 'hub'}
<div class="flex flex-col gap-y-16">
<div class="flex flex-col pb-8">
{#snippet toggleKinds()}
<ToggleButtonGroup
bind:selected={subtab}
onSelected={(v) => {
setQuery($page.url, 'kind', v, window.location.hash)
}}
noWFull
>
{#snippet children({ item })}
<ToggleButton value="script" label="Scripts" icon={Code} {item} />
<ToggleButton
value="flow"
label="Flows"
icon={FlowIcon}
selectedColor="#14b8a6"
{item}
/>
<ToggleButton
value="app"
label="Apps"
icon={LayoutDashboard}
selectedColor="#fb923c"
{item}
/>
{/snippet}
</ToggleButtonGroup>
<Button
startIcon={{ icon: ExternalLink }}
target="_blank"
href={$hubBaseUrlStore}
variant="default"
>
Hub
</Button>
{/snippet}
{#if subtab == 'script'}
<PickHubScript syncQuery bind:filter on:pick={(e) => viewCode(e.detail)}>
{#snippet children()}
{@render toggleKinds?.()}
{/snippet}
</PickHubScript>
{:else if subtab == 'flow'}
<PickHubFlow syncQuery bind:filter on:pick={(e) => viewFlow(e.detail)}>
{#snippet children()}
{@render toggleKinds?.()}
{/snippet}
</PickHubFlow>
{:else if subtab == 'app'}
<PickHubApp syncQuery bind:filter on:pick={(e) => viewApp(e.detail)}>
{#snippet children()}
{@render toggleKinds?.()}
{/snippet}
</PickHubApp>
{/if}
{#if !$userStore?.operator}
<div class="w-full overflow-auto scrollbar-hidden pb-2">
<Tabs values={['hub', 'workspace']} hashNavigation bind:selected={tab}>
<Tab value="workspace" label="Workspace" icon={Building} />
{#if HOME_SHOW_HUB}
<Tab value="hub" label="Hub" icon={Globe2} />
{/if}
</Tabs>
</div>
</div>
{/if}
{#if tab == 'hub'}
<div class="flex flex-col gap-y-16">
<div class="flex flex-col pb-8">
{#snippet toggleKinds()}
<ToggleButtonGroup
bind:selected={subtab}
onSelected={(v) => {
setQuery($page.url, 'kind', v, window.location.hash)
}}
noWFull
>
{#snippet children({ item })}
<ToggleButton value="script" label="Scripts" icon={Code} {item} />
<ToggleButton
value="flow"
label="Flows"
icon={FlowIcon}
selectedColor="#14b8a6"
{item}
/>
<ToggleButton
value="app"
label="Apps"
icon={LayoutDashboard}
selectedColor="#fb923c"
{item}
/>
{/snippet}
</ToggleButtonGroup>
<Button
startIcon={{ icon: ExternalLink }}
target="_blank"
href={$hubBaseUrlStore}
variant="default"
>
Hub
</Button>
{/snippet}
{#if subtab == 'script'}
<PickHubScript syncQuery bind:filter on:pick={(e) => viewCode(e.detail)}>
{#snippet children()}
{@render toggleKinds?.()}
{/snippet}
</PickHubScript>
{:else if subtab == 'flow'}
<PickHubFlow syncQuery bind:filter on:pick={(e) => viewFlow(e.detail)}>
{#snippet children()}
{@render toggleKinds?.()}
{/snippet}
</PickHubFlow>
{:else if subtab == 'app'}
<PickHubApp syncQuery bind:filter on:pick={(e) => viewApp(e.detail)}>
{#snippet children()}
{@render toggleKinds?.()}
{/snippet}
</PickHubApp>
{/if}
</div>
</div>
{/if}
</div>
{#if tab == 'workspace'}
<ItemsList bind:filter bind:subtab />
{/if}
<WorkspaceTutorials bind:this={workspaceTutorials} />
</div>
{#if tab == 'workspace'}
<ItemsList bind:filter bind:subtab />
{/if}
<WorkspaceTutorials bind:this={workspaceTutorials} />