fix(frontend): fix centered page shift when scroll (#7610)

* fix(frontend): fix centered page shift overflow

* fix home page overflow

* fix all other page overflow
This commit is contained in:
Guilhem
2026-01-19 17:47:10 +00:00
committed by GitHub
parent 7f2dc707b0
commit 27886175d8
6 changed files with 144 additions and 148 deletions
@@ -4,14 +4,19 @@
interface Props {
class?: string
children?: import('svelte').Snippet<[{ width: number }]>
wrapperClasses?: string
handleOverflow?: boolean
}
let { class: clazz = '', children }: Props = $props()
let { class: clazz = '', children, wrapperClasses = '', handleOverflow = true }: Props = $props()
let width = $state(0)
</script>
<div class="pb-8">
<div
class={twMerge('pb-8', wrapperClasses, handleOverflow ? 'h-full overflow-y-auto' : '')}
style={handleOverflow ? '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
>
@@ -68,10 +68,7 @@
}
}
function forkAheadBehindMessage(
changesAhead: number,
changesBehind: number,
) {
function forkAheadBehindMessage(changesAhead: number, changesBehind: number) {
let msg: string[] = []
if (changesAhead > 0 || changesBehind > 0) {
msg.push('This fork is ')
@@ -86,11 +83,11 @@
</script>
{#if isFork}
<div class="w-full bg-blue-50 dark:bg-blue-900/20 border-b border-blue-200 dark:border-blue-800">
<div class="w-full bg-blue-50 dark:bg-blue-900 text-xs rounded-b-md max-w-7xl mx-auto">
<div class="px-4 py-2">
<div class="flex items-center justify-between">
<div class="flex items-center gap-3">
<GitFork class="w-4 h-4 text-blue-600 dark:text-blue-400" />
<GitFork class="w-4 h-4 text-accent" />
<div class="text-sm">
<span class="font-medium text-blue-900 dark:text-blue-100">
Fork of <b>{parentWorkspaceData?.name}</b> ({parentWorkspaceId})
@@ -106,17 +103,17 @@
{:else if comparison}
<div class="flex items-center gap-4 text-xs">
{#if comparison.summary.total_diffs > 0}
<span>
<span class="text-blue-700 dark:text-blue-100">
{forkAheadBehindMessage(
comparison.summary.total_ahead,
comparison.summary.total_behind,
comparison.summary.total_behind
)}
<span class="font-semibold underline">{parentWorkspaceId}</span> over {comparison.summary
.total_diffs} items:
<span class="font-semibold underline">{parentWorkspaceId}</span> over {comparison
.summary.total_diffs} items:
</span>
<div class="flex items-center gap-2">
{#if comparison.summary.scripts_changed > 0}
<span class="text-blue-700 dark:text-blue-300">
<span class="text-blue-700 dark:text-blue-100">
{comparison.summary.scripts_changed} script{comparison.summary
.scripts_changed !== 1
? 's'
@@ -124,21 +121,21 @@
</span>
{/if}
{#if comparison.summary.flows_changed > 0}
<span class="text-blue-700 dark:text-blue-300">
<span class="text-blue-700 dark:text-blue-100">
{comparison.summary.flows_changed} flow{comparison.summary.flows_changed !== 1
? 's'
: ''}
</span>
{/if}
{#if comparison.summary.apps_changed > 0}
<span class="text-blue-700 dark:text-blue-300">
<span class="text-blue-700 dark:text-blue-100">
{comparison.summary.apps_changed} app{comparison.summary.apps_changed !== 1
? 's'
: ''}
</span>
{/if}
{#if comparison.summary.resources_changed > 0}
<span class="text-blue-700 dark:text-blue-300">
<span class="text-blue-700 dark:text-blue-100">
{comparison.summary.resources_changed} resource{comparison.summary
.resources_changed !== 1
? 's'
@@ -146,7 +143,7 @@
</span>
{/if}
{#if comparison.summary.variables_changed > 0}
<span class="text-blue-700 dark:text-blue-300">
<span class="text-blue-700 dark:text-blue-100">
{comparison.summary.variables_changed} variable{comparison.summary
.variables_changed !== 1
? 's'
@@ -167,9 +164,12 @@
</div>
{/if}
{:else if comparison.skipped_comparison}
<span class="text-blue-600 dark:text-blue-400"> This fork was created before the addition of certain windmill features, and therefore the changes with its parent workspace cannot be displayed.</span>
<span class="text-blue-600 dark:text-blue-200">
This fork was created before the addition of certain windmill features, and
therefore the changes with its parent workspace cannot be displayed.</span
>
{:else}
<span class="text-blue-600 dark:text-blue-400"> Everything is up to date </span>
<span class="text-blue-600 dark:text-blue-200"> Everything is up to date </span>
{/if}
</div>
{/if}
@@ -8,6 +8,8 @@
import { loadCopilot } from '$lib/aiStore'
import { aiChatManager } from './AIChatManager.svelte'
import { onDestroy } from 'svelte'
import Button from '$lib/components/common/button/Button.svelte'
import { Menu } from 'lucide-svelte'
interface Props {
noPadding?: boolean
@@ -47,49 +49,32 @@
{#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">
<Pane size={100 - chatState.size} minSize={50} class="flex flex-col grow min-h-0 ">
<div
id="content"
class={classNames(
'w-full flex-1 flex flex-col overflow-y-auto',
'w-full flex-1 flex flex-col overflow-y-auto min-h-0',
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 min-h-0">
<div class="relative w-full flex-1 flex flex-col min-h-0">
<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',
'py-0.5 px-4 sm:px-4 shadow-sm max-w-7xl md:hidden justify-start flex',
noBorder || $userStore?.operator ? 'hidden' : ''
)}
>
<button
aria-label="Menu"
type="button"
onclick={() => {
onMenuOpen?.()
}}
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>
</button>
<Button
variant="subtle"
unifiedSize="lg"
onClick={() => onMenuOpen?.()}
startIcon={{ icon: Menu }}
iconOnly
></Button>
</div>
<div class="flex-1">
<div class="flex-1 min-h-0">
{@render children?.()}
</div>
</div>
@@ -362,7 +362,7 @@
</DrawerContent>
</Drawer>
<CenteredPage>
<CenteredPage wrapperClasses="w-full" handleOverflow={false}>
<div
class="flex flex-wrap gap-2 items-center justify-between w-full"
use:triggerableByAI={{
@@ -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
+103 -94
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,110 @@
</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="flex flex-col w-full h-full overflow-y-auto items-center"
style="scrollbar-gutter: stable both-edges;"
>
<ForkWorkspaceBanner />
<div class="max-w-7xl px-4 sm:px-8 md:px-8 h-fit w-full">
{#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}
</div>
{#if tab == 'workspace'}
<ItemsList bind:filter bind:subtab />
{/if}
<WorkspaceTutorials bind:this={workspaceTutorials} />