mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-08-26 00:01:37 +00:00
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:
@@ -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
|
||||
|
||||
@@ -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} />
|
||||
|
||||
Reference in New Issue
Block a user