diff --git a/frontend/src/lib/components/FlowBuilder.svelte b/frontend/src/lib/components/FlowBuilder.svelte index 9874495110..a347cd4003 100644 --- a/frontend/src/lib/components/FlowBuilder.svelte +++ b/frontend/src/lib/components/FlowBuilder.svelte @@ -1570,6 +1570,8 @@ {#if ownsPageHeader} + {:else} diff --git a/frontend/src/lib/components/PageHeaderBar.svelte b/frontend/src/lib/components/PageHeaderBar.svelte index 4b541c1592..ca2927712a 100644 --- a/frontend/src/lib/components/PageHeaderBar.svelte +++ b/frontend/src/lib/components/PageHeaderBar.svelte @@ -70,21 +70,24 @@ The row's height matches the sidebar's own header row, so the two read as one ba // A page left while scrolled would otherwise hand its edge to the next one, which may have // nothing to scroll at all. afterNavigate(() => (scrolledUnder = false)) + + const separator = $derived(pageHeader.content?.separator ?? 'onScroll') + const showEdge = $derived(separator === 'always' || (separator === 'onScroll' && scrolledUnder)) - +
pageHeader.barWidth, (w) => pageHeader.setBarWidth(w)} class={twMerge( 'flex items-center gap-1 shrink-0 min-w-0 bg-surface transition-shadow duration-150', phone ? 'flex-wrap content-center min-h-11 py-1 pl-1 pr-2' : 'h-11 pl-2 pr-4', - scrolledUnder && + showEdge && 'shadow-[inset_0_-1px_0_0_rgb(var(--color-border-light))] dark:shadow-[inset_0_-1px_0_0_#374151] [html.github-dark_&]:shadow-[inset_0_-1px_0_0_rgb(var(--color-border-light))]' )} > diff --git a/frontend/src/lib/components/PageHeaderContent.svelte b/frontend/src/lib/components/PageHeaderContent.svelte index 462984e6e7..5e023a68b3 100644 --- a/frontend/src/lib/components/PageHeaderContent.svelte +++ b/frontend/src/lib/components/PageHeaderContent.svelte @@ -17,6 +17,7 @@ the header, so nesting is decided by the caller rather than guessed from context actions, actionsOrder, actionsFlexible, + separator, contexts, actingWorkspaceId, barRightInset @@ -30,6 +31,7 @@ the header, so nesting is decided by the caller rather than guessed from context actions, actionsOrder, actionsFlexible, + separator, contexts, actingWorkspaceId, barRightInset diff --git a/frontend/src/lib/components/PathEditPen.svelte b/frontend/src/lib/components/PathEditPen.svelte new file mode 100644 index 0000000000..c74ea8dded --- /dev/null +++ b/frontend/src/lib/components/PathEditPen.svelte @@ -0,0 +1,37 @@ + + + + {:else} - {path} -

Only the owner can change the path

+ +
+
Path
+ +
{/if} - - {#if onBehalfOfEmail} - - This flow will be redeployed on behalf of you ({$userStore?.email}) instead of {onBehalfOfEmail} - - {/if} - - {:else} - -
-
Path
-
- {/if} -
- {/snippet} - + {/snippet} + + + {:else}
{#if !emptyString(summary) && !compact} diff --git a/frontend/src/lib/components/apps/editor/AppEditorHeader.svelte b/frontend/src/lib/components/apps/editor/AppEditorHeader.svelte index 6e83eb824e..2ee69a7380 100644 --- a/frontend/src/lib/components/apps/editor/AppEditorHeader.svelte +++ b/frontend/src/lib/components/apps/editor/AppEditorHeader.svelte @@ -634,6 +634,22 @@ icon: $breakpoint === 'sm' ? Laptop2 : Smartphone, action: () => ($breakpoint = $breakpoint === 'sm' ? 'lg' : 'sm') }, + // The setting the mobile view is edited for, which otherwise lives beside the + // toggle group that stood down: without it here a deployed app could be put on + // the mobile layout at this width and never taken off it. + ...($breakpoint === 'sm' + ? [ + { + displayName: $app?.mobileViewOnSmallerScreens + ? 'Mobile view on small screens: on' + : 'Mobile view on small screens: off', + icon: Smartphone, + action: () => { + if ($app) $app.mobileViewOnSmallerScreens = !$app.mobileViewOnSmallerScreens + } + } + ] + : []), { displayName: $app?.fullscreen ? 'Centered canvas' : 'Full-width canvas', icon: $app?.fullscreen ? AlignHorizontalSpaceAround : Expand, @@ -989,6 +1005,7 @@ }} actions={appHeaderActions} contexts={headerContexts} + separator="always" /> {:else}
(undefined) + async function loadPublicUrl() { + try { + const secret = await AppService.getPublicSecretOfApp({ workspace, path }) + // Built from this viewer's workspace rather than the navigation one, like everything + // else here: the two differ inside a session's preview panel. + publicUrl = secret + ? `${window.location.origin}${base}/public/${workspace}/${secret}` + : undefined + } catch (_) { + publicUrl = undefined + } + } + $effect(() => { if (workspace && path) loadPerms() }) + $effect(() => { + publicUrl = undefined + if (ownsPageHeader && !menuHidden && workspace && path) loadPublicUrl() + }) + + // Both of this page's buttons carry a label, and a phone's bar has room for neither beside the + // app's path — so below that width they become one menu. Unmeasured (0) counts as wide: the bar + // measures itself on mount, and starting compact would pop them out a frame later. + const compact = $derived(pageHeader.barWidth > 0 && pageHeader.barWidth < PHONE_BAR) + const compactItems: Item[] = $derived([ + ...(publicUrl + ? [ + { + displayName: 'Public url', + icon: ExternalLink, + href: publicUrl, + hrefTarget: '_blank' as const + } + ] + : []), + { + displayName: 'Edit', + icon: Pen, + href: onEdit ? undefined : editHref, + action: onEdit ? () => onEdit() : undefined + } + ])
@@ -208,14 +255,15 @@ {#if ownsPageHeader} {/if} - + {#if showEdit && (menuHidden || !ownsPageHeader)}
{@render editAction()} @@ -223,14 +271,34 @@ {/if} {#snippet editAction()} - - + {#if compact} + + + {:else} + {#if publicUrl} + + + {/if} + + + {/if} {/snippet} diff --git a/frontend/src/lib/components/apps/editor/PublicApp.svelte b/frontend/src/lib/components/apps/editor/PublicApp.svelte index bb73bc171f..6d073ee711 100644 --- a/frontend/src/lib/components/apps/editor/PublicApp.svelte +++ b/frontend/src/lib/components/apps/editor/PublicApp.svelte @@ -187,10 +187,12 @@
{#snippet trigger()} - {/snippet} diff --git a/frontend/src/lib/components/auditLogs/AuditLogsFilters.svelte b/frontend/src/lib/components/auditLogs/AuditLogsFilters.svelte index 4090a39dbb..ae6ac5eba6 100644 --- a/frontend/src/lib/components/auditLogs/AuditLogsFilters.svelte +++ b/frontend/src/lib/components/auditLogs/AuditLogsFilters.svelte @@ -266,12 +266,20 @@ ;[username, perPage, before, after, operation, resource, actionKind, scope, pageIndex] untrack(() => updateQueryParams()) }) + + // `leading-none`: the label's own line box is what decides whether a stacked field fits the + // header band. At the default leading the field is 52px against a 50px bar and the label is + // clipped off the top; at the font's own height it is 44px. + const fieldLabel = 'text-xs font-semibold text-emphasis shrink-0 leading-none' -
+ +
{#if $workspaceStore == 'admins'} -
- Scope +
+ Scope { @@ -301,43 +309,48 @@
{/if} -
- From - - { - after = new Date(detail).toISOString() - }} - on:clear={() => { - after = undefined - }} - /> +
+ From +
+ + { + after = new Date(detail).toISOString() + }} + on:clear={() => { + after = undefined + }} + /> +
-
- To - - { - before = new Date(detail).toISOString() - }} - on:clear={() => { - before = undefined - }} - /> +
+ To +
+ + { + before = new Date(detail).toISOString() + }} + on:clear={() => { + before = undefined + }} + /> +
-
- Username +
+ Username (resources.value?.push(r), (resource = r))} createText="Press enter to use this value" bind:value={resource} @@ -368,11 +381,11 @@ />
-
- Operation - +
+ Operation
-
+ +
-
-
-
- - {#if !isCollapsed}{#if $whitelabelNameStore}{capitalize( - $whitelabelNameStore - )}{:else}Windmill{/if}{/if} -
- -
- - {#snippet children({ createMenu })} - - - {/snippet} - - openSearchModal()} - {isCollapsed} - icon={Search} - label="Search" - class="!text-xs" - shortcut={`${getModifierKey()}k`} - /> - {#if !$copilotInfo.workspaceDisabled} - aiChatManager.toggleOpen()} - {isCollapsed} - icon={WandSparkles} - iconProps={{ - forceDarkMode: true - }} - label="Ask AI" - class="!text-xs" - iconClasses="!text-ai" - shortcut={`${getModifierKey()}L`} - /> - {/if} -
- - -
-
-
-
{/if}
- + {#snippet auditHint()} @@ -121,7 +121,10 @@ {/snippet} {#snippet auditActions()} -