@k2b/cloud 0.6.0-rc.0 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/package.json +30 -17
- package/scripts/browser-performance.ts +14 -0
- package/scripts/build-pdf-renderer.ts +39 -0
- package/scripts/build.ts +15 -35
- package/scripts/preload.ts +9 -12
- package/scripts/sync-recovery-smoke.ts +2 -1
- package/src/_internal/app-presentation.ts +14 -2
- package/src/_internal/capabilities.ts +108 -7
- package/src/_internal/capability-streams.ts +81 -0
- package/src/_internal/define-app.ts +62 -36
- package/src/_internal/heartbeat.ts +4 -0
- package/src/_internal/help-catalog.ts +27 -172
- package/src/_internal/help.ts +13 -76
- package/src/_internal/page-responses.ts +2 -0
- package/src/_internal/postgres-application-name.ts +30 -0
- package/src/_internal/process-identity.ts +15 -0
- package/src/_internal/process-sync.ts +1 -1
- package/src/_internal/registry-validation.ts +29 -25
- package/src/_internal/registry.ts +2 -53
- package/src/_internal/runtime-context.ts +5 -16
- package/src/_internal/server-timing.ts +13 -0
- package/src/_internal/status-preserving-ssr.ts +18 -6
- package/src/_internal/web-vitals-asset.ts +10 -0
- package/src/access/PermissionEditor.tsx +14 -153
- package/src/access/PrincipalPicker.tsx +104 -0
- package/src/access/ui.ts +2 -0
- package/src/ai/admin.ts +14 -13
- package/src/ai/assistant-models.ts +12 -1
- package/src/ai/audio-format.ts +34 -0
- package/src/ai/audio-tool.ts +90 -0
- package/src/ai/browser-code-contracts.ts +79 -0
- package/src/ai/browser.ts +2 -0
- package/src/ai/capabilities.ts +55 -36
- package/src/ai/capability-execution.ts +47 -12
- package/src/ai/chat/blocks.tsx +157 -95
- package/src/ai/chat/capability-result.ts +15 -0
- package/src/ai/chat/capability-table.tsx +2 -10
- package/src/ai/chat/composer-adapter.ts +48 -3
- package/src/ai/chat/message-actions.tsx +8 -7
- package/src/ai/chat/message-utils.ts +12 -1
- package/src/ai/chat/messages.ts +2 -0
- package/src/ai/chat/presentation.tsx +19 -48
- package/src/ai/chat/primitives.tsx +43 -1
- package/src/ai/chat/tool-disclosure.tsx +17 -2
- package/src/ai/chat/tool-groups.ts +52 -0
- package/src/ai/chat-quotas.ts +25 -0
- package/src/ai/chat-task-contracts.ts +9 -1
- package/src/ai/chat-tasks.ts +276 -43
- package/src/ai/client/controller.ts +110 -33
- package/src/ai/client/file-source.ts +12 -6
- package/src/ai/client/live-connection.ts +1 -0
- package/src/ai/client/projection.ts +22 -11
- package/src/ai/client/transport.ts +4 -0
- package/src/ai/code-capability-routes.ts +110 -0
- package/src/ai/code-capability-transport.ts +38 -0
- package/src/ai/code-mode-skill.ts +114 -0
- package/src/ai/code-runtime-tools.ts +166 -0
- package/src/ai/code-source-contracts.ts +202 -0
- package/src/ai/code-source-tools.ts +145 -0
- package/src/ai/compaction.ts +27 -6
- package/src/ai/data-analysis-skill.ts +10 -0
- package/src/ai/default-tools.ts +25 -2
- package/src/ai/dictation-runtime.ts +171 -0
- package/src/ai/dictations.ts +188 -0
- package/src/ai/draft-content.ts +34 -0
- package/src/ai/enrich.ts +6 -0
- package/src/ai/executor.ts +191 -31
- package/src/ai/file-content-version.ts +22 -0
- package/src/ai/file-context.ts +2 -1
- package/src/ai/file-media-type.ts +12 -0
- package/src/ai/file-reference-contracts.ts +15 -0
- package/src/ai/file-tools.ts +2 -1
- package/src/ai/files-store.ts +174 -71
- package/src/ai/fixtures/vision-encrypted.pdf +0 -0
- package/src/ai/grids-skill.ts +40 -10
- package/src/ai/http.ts +15 -8
- package/src/ai/index.ts +26 -2
- package/src/ai/inference-calls.ts +198 -0
- package/src/ai/live-events.ts +5 -1
- package/src/ai/memory-learning.ts +60 -28
- package/src/ai/message-queue.ts +167 -0
- package/src/ai/migrate.ts +187 -11
- package/src/ai/model-pricing.ts +43 -0
- package/src/ai/pdf-render-worker.ts +74 -0
- package/src/ai/pdf-render.fixture.ts +28 -0
- package/src/ai/pdf-render.ts +77 -0
- package/src/ai/projects-routes.ts +3 -3
- package/src/ai/projects.ts +20 -6
- package/src/ai/protocol.ts +3 -0
- package/src/ai/provider.ts +1 -8
- package/src/ai/quota-provider.ts +191 -0
- package/src/ai/quota-report.ts +152 -0
- package/src/ai/quotas-migrate.ts +45 -0
- package/src/ai/quotas.ts +168 -0
- package/src/ai/routes.ts +215 -11
- package/src/ai/run-timeout.ts +10 -0
- package/src/ai/runtime-tools.ts +3 -0
- package/src/ai/runtime.ts +55 -16
- package/src/ai/settings.ts +36 -8
- package/src/ai/short-id.ts +2 -2
- package/src/ai/skill-catalog.ts +0 -10
- package/src/ai/skill-search.ts +47 -0
- package/src/ai/skill-seeds.ts +105 -20
- package/src/ai/skill-tool.ts +16 -5
- package/src/ai/skills-routes.ts +32 -4
- package/src/ai/skills.ts +324 -155
- package/src/ai/store.ts +373 -191
- package/src/ai/stream.ts +5 -0
- package/src/ai/structured-runs.ts +4 -3
- package/src/ai/structured.ts +8 -1
- package/src/ai/system-prompt.ts +12 -2
- package/src/ai/timeline.ts +2 -5
- package/src/ai/todo-contracts.ts +20 -0
- package/src/ai/todo-tool.ts +16 -0
- package/src/ai/tool-audit.ts +30 -40
- package/src/ai/tools.ts +6 -0
- package/src/ai/transcription.ts +202 -0
- package/src/ai/turn-timing.ts +114 -0
- package/src/ai/types.ts +58 -10
- package/src/ai/ui.tsx +2 -0
- package/src/ai/usage.ts +118 -118
- package/src/ai/vision-tool.ts +39 -10
- package/src/api/admin-ai-projects.ts +2 -2
- package/src/api/admin-ai-quotas.ts +74 -0
- package/src/api/admin-ai-skills.ts +48 -5
- package/src/api/admin-ai-usage.ts +2 -2
- package/src/api/admin-core-settings.ts +25 -0
- package/src/api/admin-identity.ts +91 -51
- package/src/api/admin-rail.ts +58 -0
- package/src/api/announcements.ts +14 -0
- package/src/api/capabilities.ts +292 -43
- package/src/api/capability-streams.ts +262 -0
- package/src/api/help.ts +8 -44
- package/src/api/index.ts +6 -0
- package/src/api/mcp.ts +44 -26
- package/src/api/me-web-vitals.ts +35 -0
- package/src/api/me.ts +2 -0
- package/src/api/search/schemas.ts +26 -15
- package/src/api/search.bench.ts +1 -1
- package/src/api/search.ts +54 -17
- package/src/browser/CloudResourceSearch.tsx +640 -702
- package/src/browser/app-approval-vault.ts +19 -7
- package/src/browser/command-bridge.ts +109 -0
- package/src/browser/command-shortcuts.ts +76 -0
- package/src/browser/commands.ts +66 -0
- package/src/browser/navigation-search.ts +48 -0
- package/src/browser/notifications.ts +1 -1
- package/src/browser/resource-picker.tsx +5 -18
- package/src/browser/resource-search-dialog.ts +9 -0
- package/src/browser/resource-search-input.ts +46 -0
- package/src/browser/resource-search-messages.ts +76 -40
- package/src/browser/resource-search.ts +7 -0
- package/src/browser/search-bridge.ts +102 -0
- package/src/browser/search-commands.ts +59 -0
- package/src/browser/search.ts +13 -0
- package/src/browser/spotlight-position.ts +208 -0
- package/src/browser/testing.ts +5 -0
- package/src/browser/web-vitals.ts +25 -0
- package/src/capabilities/claims.ts +179 -0
- package/src/capabilities/client.ts +34 -0
- package/src/capabilities/command-link.ts +16 -0
- package/src/capabilities/executions.ts +298 -0
- package/src/capabilities/migrate.ts +81 -0
- package/src/capabilities/server.ts +114 -34
- package/src/capabilities/stream-body.ts +16 -0
- package/src/capabilities/streams.ts +28 -0
- package/src/cli/admin/ai-quotas.ts +161 -0
- package/src/cli/admin/ai-skills.ts +64 -0
- package/src/cli/admin/ai-usage.ts +53 -30
- package/src/cli/admin/app-credentials.ts +79 -0
- package/src/cli/admin/data.ts +17 -0
- package/src/cli/admin/index.ts +9 -0
- package/src/cli/capabilities.ts +29 -0
- package/src/cli/capability-streams.ts +25 -0
- package/src/cli/index.ts +3 -2
- package/src/config/env.ts +11 -8
- package/src/contracts/app.ts +23 -3
- package/src/contracts/capabilities.ts +68 -5
- package/src/contracts/capability-streams.ts +46 -0
- package/src/contracts/commands.ts +49 -0
- package/src/contracts/index.ts +6 -1
- package/src/contracts/rail-admin.ts +38 -0
- package/src/contracts/rail-preferences.ts +16 -0
- package/src/contracts/registry.ts +3 -27
- package/src/contracts/settings-types.ts +1 -1
- package/src/contracts/shared.ts +9 -5
- package/src/contracts/web-vitals.ts +27 -0
- package/src/desktop/solid.tsx +3 -3
- package/src/index.ts +0 -4
- package/src/server/index.ts +3 -0
- package/src/server/middleware/auth.ts +3 -2
- package/src/server/middleware/route-template.ts +9 -3
- package/src/server/middleware/runtime.ts +2 -1
- package/src/server/middleware/settings.ts +17 -11
- package/src/server/services/access-revision.ts +10 -0
- package/src/server/services/access.ts +1 -1
- package/src/server/services/index.ts +1 -0
- package/src/services/account-category-policy.ts +9 -0
- package/src/services/account-lifecycle/audit.ts +1 -1
- package/src/services/accounts/app.ts +12 -2
- package/src/services/accounts/email-write.ts +23 -0
- package/src/services/accounts/groups.ts +15 -4
- package/src/services/accounts/identities.ts +249 -0
- package/src/services/accounts/identity-reconciliation.ts +87 -0
- package/src/services/accounts/ipa-data.ts +2 -2
- package/src/services/accounts/local-groups.ts +2 -2
- package/src/services/accounts/posix.ts +45 -29
- package/src/services/accounts/users.ts +2 -2
- package/src/services/announcements/index.ts +69 -8
- package/src/services/audit/index.ts +1 -1
- package/src/services/cache-fill.ts +35 -0
- package/src/services/help/index.ts +109 -0
- package/src/services/help/maintenance.ts +25 -0
- package/src/services/help/store.ts +180 -0
- package/src/services/help/types.ts +46 -0
- package/src/services/identity/invocation-actor.ts +2 -2
- package/src/services/identity/key-config.ts +2 -1
- package/src/services/identity/key-ring.ts +1 -1
- package/src/services/index.ts +15 -0
- package/src/services/ipa/users.ts +268 -221
- package/src/services/legal-consent.ts +1 -1
- package/src/services/logging/index.ts +15 -13
- package/src/services/logging/trace.ts +2 -1
- package/src/services/logging/web-vitals.ts +81 -0
- package/src/services/mandates/index.ts +18 -4
- package/src/services/mandates/policy.ts +65 -2
- package/src/services/notifications/batches.ts +124 -57
- package/src/services/notifications/browser.ts +12 -0
- package/src/services/notifications/index.ts +0 -2
- package/src/services/notifications/platform.ts +0 -4
- package/src/services/oauth-tokens.ts +3 -3
- package/src/services/pdf/gotenberg.ts +107 -84
- package/src/services/pdf/index.ts +3 -0
- package/src/services/providers/local/users.ts +25 -16
- package/src/services/public-http.ts +101 -0
- package/src/services/rail-shortcuts.ts +95 -0
- package/src/services/rail-snapshot.ts +53 -0
- package/src/services/request-cache-redis.ts +28 -0
- package/src/services/session/index.ts +15 -2
- package/src/services/session/user.ts +62 -37
- package/src/services/settings/core-settings.ts +21 -28
- package/src/services/settings/defaults.ts +5 -0
- package/src/services/settings/store.ts +58 -84
- package/src/shared/ai-costs.ts +42 -0
- package/src/shared/ai-platform-prompt.ts +1 -1
- package/src/shared/ai-quotas.ts +136 -0
- package/src/shared/ai-usage.ts +6 -16
- package/src/shared/app-presentation.ts +21 -15
- package/src/shared/capability-messages.ts +14 -0
- package/src/shared/help.ts +6 -42
- package/src/shared/index.ts +6 -0
- package/src/ssr/AdminLayout.tsx +7 -5
- package/src/ssr/AdminSidebar.tsx +29 -15
- package/src/ssr/AppLaunchpad.island.tsx +24 -137
- package/src/ssr/AppLaunchpadPanel.tsx +181 -0
- package/src/ssr/BrowserPushRegistration.island.tsx +12 -0
- package/src/ssr/GlobalSearchDialog.tsx +167 -20
- package/src/ssr/GlobalSearchTrigger.island.tsx +33 -22
- package/src/ssr/Layout.tsx +17 -6
- package/src/ssr/LayoutBreadcrumbs.island.tsx +3 -5
- package/src/ssr/LayoutFooter.tsx +1 -1
- package/src/ssr/LayoutHeader.tsx +46 -14
- package/src/ssr/LayoutHelp.tsx +23 -22
- package/src/ssr/{HotkeysHelpRail.island.tsx → LayoutHelpTrigger.island.tsx} +18 -12
- package/src/ssr/LayoutRail.tsx +17 -9
- package/src/ssr/MobileNavigation.tsx +71 -0
- package/src/ssr/MobileProfileActions.tsx +49 -0
- package/src/ssr/ProfilePreferences.island.tsx +3 -36
- package/src/ssr/RailApps.island.tsx +2 -2
- package/src/ssr/RailEditor.tsx +17 -2
- package/src/ssr/WorkspaceNavigation.island.tsx +7 -0
- package/src/ssr/WorkspaceNavigationProvider.tsx +16 -0
- package/src/ssr/admin-navigation.ts +7 -1
- package/src/ssr/help.ts +29 -0
- package/src/ssr/islands/SearchBar.tsx +67 -0
- package/src/ssr/islands/index.ts +3 -1
- package/src/ssr/layout-context.ts +2 -2
- package/src/ssr/layout-help-search.ts +15 -0
- package/src/ssr/mobile-menu-history.ts +70 -0
- package/src/ssr/platform-messages.ts +16 -8
- package/src/ssr/profile-actions.ts +42 -0
- package/src/ssr/rail-context.ts +8 -4
- package/src/ssr/rail-messages.ts +2 -0
- package/src/ssr/rail-navigation.ts +3 -3
- package/src/ssr/workspace-navigation.ts +75 -0
- package/src/styles/effects.css +6 -6
- package/src/styles/global.css +54 -0
- package/src/styles/input.css +43 -41
- package/src/styles/resource-search.css +505 -0
- package/src/styles/utilities-feedback.css +13 -0
- package/src/styles/utilities-layout.css +1 -5
- package/src/styles/utilities-navigation.css +3 -39
- package/src/types/ambient.d.ts +5 -0
- package/src/workflows/ai/runtime.ts +5 -1
- package/src/workflows/ai/store.ts +2 -0
- package/src/workflows/ai-actions.ts +16 -13
- package/src/workflows/definition.ts +10 -5
- package/src/workflows/store/actions.ts +44 -20
- package/src/workflows/store/index.ts +2 -0
- package/src/workflows/store/runs.ts +36 -0
- package/src/workflows/store/worker-pool.ts +55 -0
- package/src/workflows/store/worker-runtime.ts +90 -0
- package/scripts/app-favicon.test.ts +0 -49
- package/src/ai/chat/blocks.render.test.tsx +0 -1108
- package/src/ai/kit-skill.ts +0 -22
- package/src/ai/usage-migrate.ts +0 -81
- package/src/browser/CloudResourceSearch.behavior.test.tsx +0 -74
- package/src/browser/notification-target.ts +0 -27
- package/src/contracts/notification-live.ts +0 -66
- package/src/services/notifications/live.ts +0 -50
- package/src/ssr/BrowserNotifications.island.tsx +0 -135
- package/src/ssr/GlobalSearchDialog.behavior.test.tsx +0 -96
- package/src/ssr/Layout.render.test.tsx +0 -148
- package/src/ssr/islands/SearchBar.island.tsx +0 -80
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
import type { CloudTheme } from "../shared/theme";
|
|
2
|
+
import { MobileProfileActions } from "./MobileProfileActions";
|
|
3
|
+
import { Dynamic } from "solid-js/web";
|
|
4
|
+
import { platformMessages } from "./platform-messages";
|
|
5
|
+
import { IconButton, ScrollArea, TextInput, useLocale } from "@k2b/ui";
|
|
6
|
+
import { createSignal, For, Show, type JSX } from "solid-js";
|
|
7
|
+
import { documentNavigate } from "@k2b/ssr/nav";
|
|
8
|
+
import { openRailEditor } from "./RailEditor";
|
|
9
|
+
import { readRailContext } from "./rail-context";
|
|
10
|
+
import { railMessages } from "./rail-messages";
|
|
11
|
+
import { projectRailNavigation } from "./rail-navigation";
|
|
12
|
+
|
|
13
|
+
export type AppLaunchpadApp = {
|
|
14
|
+
id: string;
|
|
15
|
+
iconClass: string;
|
|
16
|
+
label: string;
|
|
17
|
+
href: string;
|
|
18
|
+
description?: string;
|
|
19
|
+
accent?: string;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export type AppLaunchpadLegalLink = {
|
|
23
|
+
label: string;
|
|
24
|
+
href: string;
|
|
25
|
+
icon?: string;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export type AppLaunchpadContext = {
|
|
29
|
+
profile?: { name: string; theme: CloudTheme };
|
|
30
|
+
apps: AppLaunchpadApp[];
|
|
31
|
+
legalLinks: AppLaunchpadLegalLink[];
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
type AppIconPaletteEntry = { from: string };
|
|
35
|
+
|
|
36
|
+
const appIconPalette: readonly [AppIconPaletteEntry, ...AppIconPaletteEntry[]] = [
|
|
37
|
+
{ from: "#2563eb" },
|
|
38
|
+
{ from: "#059669" },
|
|
39
|
+
{ from: "#7c3aed" },
|
|
40
|
+
{ from: "#d97706" },
|
|
41
|
+
{ from: "#e11d48" },
|
|
42
|
+
{ from: "#0891b2" },
|
|
43
|
+
{ from: "#52525b" },
|
|
44
|
+
];
|
|
45
|
+
|
|
46
|
+
const paletteForId = (id: string) => {
|
|
47
|
+
let hash = 0;
|
|
48
|
+
for (let i = 0; i < id.length; i++) hash = (hash + id.charCodeAt(i)) % appIconPalette.length;
|
|
49
|
+
return appIconPalette[hash] ?? appIconPalette[0];
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
const appIconStyle = (app: AppLaunchpadApp) => {
|
|
53
|
+
const tone = /^#[0-9a-f]{6}$/i.test(app.accent ?? "") ? app.accent! : paletteForId(app.id).from;
|
|
54
|
+
return `--app-icon-color:${tone}`;
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
export const AppLaunchpadPanel = (
|
|
58
|
+
props: AppLaunchpadContext & { close: () => void; beforeSelect?: () => Promise<boolean>; surface?: "panel" | "sheet" },
|
|
59
|
+
) => {
|
|
60
|
+
const locale = useLocale();
|
|
61
|
+
const t = () => railMessages.resolve([locale()]).t;
|
|
62
|
+
const rail = readRailContext();
|
|
63
|
+
const [query, setQuery] = createSignal("");
|
|
64
|
+
const messages = () => platformMessages.resolve([locale()]).t;
|
|
65
|
+
const matches = (label: string, description = "") =>
|
|
66
|
+
`${label} ${description}`.toLocaleLowerCase(locale()).includes(query().trim().toLocaleLowerCase(locale()));
|
|
67
|
+
const apps = () => props.apps.filter((app) => matches(app.label, app.description));
|
|
68
|
+
const shortcuts = () =>
|
|
69
|
+
(rail ? projectRailNavigation(rail.apps, rail.settings, locale()).shortcuts : []).filter((shortcut) => matches(shortcut.label));
|
|
70
|
+
const follow: JSX.EventHandler<HTMLAnchorElement, MouseEvent> = (event) => {
|
|
71
|
+
if (!props.beforeSelect || event.button !== 0 || event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) return;
|
|
72
|
+
event.preventDefault();
|
|
73
|
+
const href = event.currentTarget.href;
|
|
74
|
+
void props.beforeSelect().then((allowed) => {
|
|
75
|
+
if (allowed) documentNavigate(href);
|
|
76
|
+
});
|
|
77
|
+
};
|
|
78
|
+
return (
|
|
79
|
+
<Dynamic
|
|
80
|
+
component={props.surface === "sheet" ? "div" : ScrollArea}
|
|
81
|
+
class={
|
|
82
|
+
props.surface === "sheet"
|
|
83
|
+
? "cloud-mobile-apps"
|
|
84
|
+
: "launchpad-panel mx-auto max-h-[min(86vh,var(--ui-dialog-available-height))] w-[var(--ui-dialog-available-width)] max-w-[var(--ui-dialog-available-width)] overscroll-contain p-4 text-primary sm:w-fit sm:p-6 md:p-7 dark:text-white"
|
|
85
|
+
}
|
|
86
|
+
>
|
|
87
|
+
<div class="launchpad-tools">
|
|
88
|
+
<Show when={props.surface === "sheet"}>
|
|
89
|
+
<TextInput
|
|
90
|
+
value={query}
|
|
91
|
+
onValueChange={setQuery}
|
|
92
|
+
icon="ti ti-search"
|
|
93
|
+
clearable
|
|
94
|
+
placeholder={messages().findApps}
|
|
95
|
+
aria-label={messages().findApps}
|
|
96
|
+
/>
|
|
97
|
+
</Show>
|
|
98
|
+
<Show when={rail}>
|
|
99
|
+
<div class="launchpad-customize mb-2 flex justify-end">
|
|
100
|
+
<IconButton
|
|
101
|
+
variant="ghost"
|
|
102
|
+
size="sm"
|
|
103
|
+
label={t().customize}
|
|
104
|
+
tooltip={t().customize}
|
|
105
|
+
tooltipDelay={0}
|
|
106
|
+
onClick={async () => {
|
|
107
|
+
if (props.beforeSelect) {
|
|
108
|
+
if (!(await props.beforeSelect())) return;
|
|
109
|
+
} else props.close();
|
|
110
|
+
openRailEditor(locale());
|
|
111
|
+
}}
|
|
112
|
+
>
|
|
113
|
+
<i class="ti ti-adjustments-horizontal" aria-hidden="true" />
|
|
114
|
+
</IconButton>
|
|
115
|
+
</div>
|
|
116
|
+
</Show>
|
|
117
|
+
</div>
|
|
118
|
+
<Show when={shortcuts().length > 0}>
|
|
119
|
+
<section class="mb-5" aria-label={t().shortcuts}>
|
|
120
|
+
<div class="flex flex-wrap justify-center gap-3">
|
|
121
|
+
<For each={shortcuts()}>
|
|
122
|
+
{(shortcut) => (
|
|
123
|
+
<a
|
|
124
|
+
onClick={follow}
|
|
125
|
+
href={shortcut.href}
|
|
126
|
+
class="group flex w-16 min-w-0 flex-col items-center gap-1 rounded-lg p-1 text-center focus-ui"
|
|
127
|
+
>
|
|
128
|
+
<span class="grid h-9 w-9 place-items-center rounded-xl bg-[var(--ui-hover)] text-lg transition-colors group-hover:bg-[var(--ui-active)]">
|
|
129
|
+
<i class={shortcut.iconClass} aria-hidden="true" />
|
|
130
|
+
</span>
|
|
131
|
+
<span class="max-w-full truncate text-[11px] font-medium text-primary dark:text-white">{shortcut.label}</span>
|
|
132
|
+
</a>
|
|
133
|
+
)}
|
|
134
|
+
</For>
|
|
135
|
+
</div>
|
|
136
|
+
</section>
|
|
137
|
+
</Show>
|
|
138
|
+
<div class="launchpad-apps-grid flex flex-wrap justify-center gap-x-4 gap-y-4 sm:gap-x-7 sm:gap-y-6">
|
|
139
|
+
<For each={apps()}>
|
|
140
|
+
{(app) => (
|
|
141
|
+
<a
|
|
142
|
+
onClick={follow}
|
|
143
|
+
href={app.href}
|
|
144
|
+
class="launchpad-app group flex w-[4.75rem] min-w-0 flex-col items-center gap-1.5 rounded-2xl p-1 text-center focus-ui sm:w-[6.25rem] sm:gap-2 sm:p-2"
|
|
145
|
+
>
|
|
146
|
+
<span
|
|
147
|
+
class="app-icon grid h-12 w-12 place-items-center rounded-[0.95rem] text-[1.25rem] sm:h-16 sm:w-16 sm:rounded-[1.25rem] sm:text-[1.7rem]"
|
|
148
|
+
style={appIconStyle(app)}
|
|
149
|
+
>
|
|
150
|
+
<i class={app.iconClass} />
|
|
151
|
+
</span>
|
|
152
|
+
<span class="max-w-full truncate text-[11px] font-medium text-primary sm:text-xs dark:text-white">{app.label}</span>
|
|
153
|
+
</a>
|
|
154
|
+
)}
|
|
155
|
+
</For>
|
|
156
|
+
</div>
|
|
157
|
+
<Show when={!apps().length && !shortcuts().length}>
|
|
158
|
+
<p class="py-6 text-center text-dimmed">{messages().noAppsFound}</p>
|
|
159
|
+
</Show>
|
|
160
|
+
<Show when={props.surface === "sheet" && props.profile && props.beforeSelect}>
|
|
161
|
+
<MobileProfileActions profile={props.profile!} beforeSelect={props.beforeSelect!} />
|
|
162
|
+
</Show>
|
|
163
|
+
<Show when={props.legalLinks.length > 0}>
|
|
164
|
+
<div class="mt-7 flex flex-wrap justify-center text-[11px] text-dimmed dark:text-white/56">
|
|
165
|
+
<For each={props.legalLinks.filter((link) => !(props.surface === "sheet" && props.profile && link.href === "/me"))}>
|
|
166
|
+
{(link) => (
|
|
167
|
+
<a
|
|
168
|
+
onClick={follow}
|
|
169
|
+
href={link.href}
|
|
170
|
+
class="inline-flex items-center gap-1.5 rounded-md px-2 py-1 transition-colors hover:text-primary dark:hover:text-white"
|
|
171
|
+
>
|
|
172
|
+
<i class={link.icon ?? "ti ti-file-text"} />
|
|
173
|
+
{link.label}
|
|
174
|
+
</a>
|
|
175
|
+
)}
|
|
176
|
+
</For>
|
|
177
|
+
</div>
|
|
178
|
+
</Show>
|
|
179
|
+
</Dynamic>
|
|
180
|
+
);
|
|
181
|
+
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { onMount } from "solid-js";
|
|
2
|
+
import { browserNotificationClient } from "../browser/notifications";
|
|
3
|
+
|
|
4
|
+
export default function BrowserPushRegistration() {
|
|
5
|
+
onMount(() => {
|
|
6
|
+
void browserNotificationClient.refreshExisting().catch((error) => {
|
|
7
|
+
console.warn("[notifications] Failed to refresh the browser endpoint", error);
|
|
8
|
+
});
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
return null;
|
|
12
|
+
}
|
|
@@ -1,35 +1,182 @@
|
|
|
1
|
-
import { dialogCore, useLocale } from "@k2b/ui";
|
|
1
|
+
import { type DialogRender, dialogCore, toast, useLocale } from "@k2b/ui";
|
|
2
|
+
import { query } from "@k2b/stdlib/solid";
|
|
3
|
+
import { resolveCommand } from "../capabilities/client";
|
|
4
|
+
import { COMMANDS_CHANGED, collectContextAwareCommands } from "../browser/command-bridge";
|
|
5
|
+
import { contextCommandsWithShortcuts } from "../browser/command-shortcuts";
|
|
6
|
+
import { openCommand, runContextAwareCommand } from "../browser/commands";
|
|
7
|
+
import { loadSearchCommands, type PaletteCommand } from "../browser/search-commands";
|
|
8
|
+
import { createEffect, createSignal, For, onCleanup, onMount } from "solid-js";
|
|
2
9
|
import type { SearchItem } from "../api/search/schemas";
|
|
3
10
|
import CloudResourceSearch from "../browser/CloudResourceSearch";
|
|
4
|
-
import
|
|
11
|
+
import type { NavigationSearchItem } from "../browser/navigation-search";
|
|
12
|
+
import { resourceSearchDialogOptions } from "../browser/resource-search-dialog";
|
|
13
|
+
import { resourceSearchMessages } from "../browser/resource-search-messages";
|
|
14
|
+
import { type GlobalSearchOptions, requestSearchNavigation } from "../browser/search-bridge";
|
|
15
|
+
import { attachSpotlightPosition } from "../browser/spotlight-position";
|
|
5
16
|
|
|
6
17
|
type GlobalSearchDialogProps = {
|
|
18
|
+
request?: GlobalSearchOptions;
|
|
7
19
|
close: () => void;
|
|
8
|
-
|
|
20
|
+
context?: Parameters<DialogRender<void>>[1];
|
|
21
|
+
searchLinks?: NavigationSearchItem[];
|
|
22
|
+
searchResources?: boolean;
|
|
9
23
|
};
|
|
10
24
|
|
|
11
25
|
export default function GlobalSearchDialog(props: GlobalSearchDialogProps) {
|
|
12
26
|
const locale = useLocale();
|
|
13
|
-
const
|
|
27
|
+
const messages = () => resourceSearchMessages.resolve([locale()]).t;
|
|
28
|
+
const [contextCommands, setContextCommands] = createSignal<PaletteCommand[]>([]);
|
|
29
|
+
const updateContextCommands = () => setContextCommands(contextCommandsWithShortcuts().map((command) => ({ ...command, context: true })));
|
|
30
|
+
const commandsQuery = query.create({
|
|
31
|
+
source: locale,
|
|
32
|
+
enabled: () => props.searchResources !== false,
|
|
33
|
+
load: (locale, { abortSignal }) => loadSearchCommands(locale, abortSignal),
|
|
34
|
+
});
|
|
35
|
+
onMount(() => {
|
|
36
|
+
window.addEventListener(COMMANDS_CHANGED, updateContextCommands);
|
|
37
|
+
updateContextCommands();
|
|
38
|
+
onCleanup(() => window.removeEventListener(COMMANDS_CHANGED, updateContextCommands));
|
|
39
|
+
});
|
|
40
|
+
const runCommand = async (command: PaletteCommand, newTab: boolean) => {
|
|
41
|
+
if (navigating()) return;
|
|
42
|
+
// Check live ownership once more; a background panel may have closed since rendering.
|
|
43
|
+
if (
|
|
44
|
+
command.context &&
|
|
45
|
+
!collectContextAwareCommands().some((current) => current.id === command.id && current.action === command.action)
|
|
46
|
+
) {
|
|
47
|
+
updateContextCommands();
|
|
48
|
+
toast.error(messages().commandUnavailable);
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
const target = command.action;
|
|
52
|
+
if (typeof target !== "function" && "search" in target) {
|
|
53
|
+
if (!newTab) await runContextAwareCommand(command);
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
setNavigating(true);
|
|
57
|
+
setNavigationError(false);
|
|
58
|
+
if (newTab && typeof target !== "function") {
|
|
59
|
+
// Reserve synchronously inside the user gesture; never lose it to popup blocking.
|
|
60
|
+
const tab = window.open("about:blank", "_blank");
|
|
61
|
+
if (tab) tab.opener = null;
|
|
62
|
+
try {
|
|
63
|
+
if (!tab) throw new Error("Popup blocked");
|
|
64
|
+
const result = await resolveCommand(target.command, target.input, target.options);
|
|
65
|
+
if (!result.ok) throw new Error(result.error.message);
|
|
66
|
+
tab.location.replace(result.data.href);
|
|
67
|
+
} catch {
|
|
68
|
+
tab?.close();
|
|
69
|
+
if (active) setNavigationError(true);
|
|
70
|
+
} finally {
|
|
71
|
+
if (active) setNavigating(false);
|
|
72
|
+
}
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
14
75
|
props.close();
|
|
15
|
-
|
|
76
|
+
try {
|
|
77
|
+
if (command.context) await runContextAwareCommand(command);
|
|
78
|
+
else if (typeof target === "function") await target();
|
|
79
|
+
else await openCommand(target.command, target.input, target.options);
|
|
80
|
+
} catch (error) {
|
|
81
|
+
toast.error(error instanceof Error && error.message ? error.message : messages().commandFailed);
|
|
82
|
+
}
|
|
16
83
|
};
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
props.
|
|
20
|
-
|
|
84
|
+
let host!: HTMLDivElement;
|
|
85
|
+
onMount(() => {
|
|
86
|
+
if (props.context) onCleanup(attachSpotlightPosition(host, props.context, messages));
|
|
87
|
+
});
|
|
88
|
+
const openInNewTab = (item: SearchItem) => {
|
|
89
|
+
window.open(item.href, "_blank", "noopener,noreferrer");
|
|
21
90
|
};
|
|
22
|
-
|
|
23
|
-
|
|
91
|
+
const [navigating, setNavigating] = createSignal(false);
|
|
92
|
+
const [navigationError, setNavigationError] = createSignal(false);
|
|
93
|
+
createEffect(() => {
|
|
94
|
+
props.request;
|
|
95
|
+
setNavigationError(false);
|
|
96
|
+
});
|
|
97
|
+
let active = true;
|
|
98
|
+
onCleanup(() => {
|
|
99
|
+
active = false;
|
|
100
|
+
});
|
|
101
|
+
const openItem = async (item: SearchItem) => {
|
|
102
|
+
if (navigating()) return;
|
|
103
|
+
setNavigating(true);
|
|
104
|
+
setNavigationError(false);
|
|
105
|
+
try {
|
|
106
|
+
const handled = await requestSearchNavigation({
|
|
107
|
+
href: item.href,
|
|
108
|
+
...(item.ref.type === "cloud.navigation" ? {} : { ref: item.ref }),
|
|
109
|
+
});
|
|
110
|
+
if (!active) return;
|
|
111
|
+
props.close();
|
|
112
|
+
if (!handled) window.location.assign(item.href);
|
|
113
|
+
} catch {
|
|
114
|
+
if (active) setNavigationError(true);
|
|
115
|
+
} finally {
|
|
116
|
+
if (active) setNavigating(false);
|
|
117
|
+
}
|
|
118
|
+
};
|
|
119
|
+
return (
|
|
120
|
+
<div ref={host} class="cloud-global-search">
|
|
121
|
+
<CloudResourceSearch
|
|
122
|
+
commands={[
|
|
123
|
+
...contextCommands(),
|
|
124
|
+
...(commandsQuery.data() ?? []).filter((command) => !contextCommands().some((context) => context.id === command.id)),
|
|
125
|
+
]}
|
|
126
|
+
commandsLoading={commandsQuery.loading()}
|
|
127
|
+
commandsError={Boolean(commandsQuery.error())}
|
|
128
|
+
onCommand={props.searchResources === false ? undefined : (command, newTab) => void runCommand(command, newTab)}
|
|
129
|
+
request={props.request}
|
|
130
|
+
disabled={navigating()}
|
|
131
|
+
navigationError={navigationError() ? messages().navigationFailed : undefined}
|
|
132
|
+
navigationItems={props.searchLinks}
|
|
133
|
+
searchResources={props.searchResources}
|
|
134
|
+
onClose={props.close}
|
|
135
|
+
onSelect={(item) => void openItem(item)}
|
|
136
|
+
onOpenInNewTab={openInNewTab}
|
|
137
|
+
/>
|
|
138
|
+
<For each={["top", "right", "bottom", "left"]}>{(edge) => <div aria-hidden="true" data-search-grip={edge} />}</For>
|
|
139
|
+
</div>
|
|
140
|
+
);
|
|
24
141
|
}
|
|
25
142
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
143
|
+
/** Owned by the layout island; neither dialog state nor callbacks cross SSR props. */
|
|
144
|
+
export const createGlobalSearchHost = (searchLinks: () => NavigationSearchItem[], searchResources = true) => {
|
|
145
|
+
const [request, setRequest] = createSignal<GlobalSearchOptions>({});
|
|
146
|
+
let controller: AbortController | undefined;
|
|
147
|
+
return {
|
|
148
|
+
open: (options: GlobalSearchOptions) => {
|
|
149
|
+
setRequest({ ...options });
|
|
150
|
+
if (controller) {
|
|
151
|
+
document.querySelector<HTMLInputElement>(".cloud-global-search input[role=combobox]")?.focus();
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
const current = new AbortController();
|
|
155
|
+
controller = current;
|
|
156
|
+
const locale = document.documentElement.lang || "en";
|
|
157
|
+
void dialogCore
|
|
158
|
+
.open<void>(
|
|
159
|
+
(close, context) => (
|
|
160
|
+
<GlobalSearchDialog
|
|
161
|
+
request={request()}
|
|
162
|
+
searchLinks={searchLinks()}
|
|
163
|
+
searchResources={searchResources}
|
|
164
|
+
close={close}
|
|
165
|
+
context={context}
|
|
166
|
+
/>
|
|
167
|
+
),
|
|
168
|
+
{ ...resourceSearchDialogOptions(resourceSearchMessages.resolve([locale]).t.searchCloudResources), signal: current.signal },
|
|
169
|
+
)
|
|
170
|
+
.catch(() => {
|
|
171
|
+
toast.error(resourceSearchMessages.resolve([locale]).t.searchFailed);
|
|
172
|
+
})
|
|
173
|
+
.finally(() => {
|
|
174
|
+
if (controller === current) controller = undefined;
|
|
175
|
+
});
|
|
176
|
+
},
|
|
177
|
+
dispose: () => {
|
|
178
|
+
controller?.abort();
|
|
179
|
+
controller = undefined;
|
|
180
|
+
},
|
|
181
|
+
};
|
|
35
182
|
};
|
|
@@ -1,38 +1,54 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { registerContextAwareCommand, runContextAwareCommand } from "../browser/commands";
|
|
2
|
+
import { attachCommandShortcuts } from "../browser/command-shortcuts";
|
|
2
3
|
import { IconButton, Tooltip, useLocale } from "@k2b/ui";
|
|
3
|
-
import {
|
|
4
|
-
import type {
|
|
4
|
+
import { createEffect, onCleanup, onMount } from "solid-js";
|
|
5
|
+
import type { NavigationSearchItem } from "../browser/navigation-search";
|
|
6
|
+
import { openGlobalSearch } from "../browser/search";
|
|
7
|
+
import { registerGlobalSearchHost } from "../browser/search-bridge";
|
|
8
|
+
import { createGlobalSearchHost } from "./GlobalSearchDialog";
|
|
5
9
|
import { platformMessages } from "./platform-messages";
|
|
6
10
|
|
|
7
11
|
type GlobalSearchTriggerProps = {
|
|
8
|
-
variant: "header" | "rail";
|
|
12
|
+
variant: "header" | "rail" | "host";
|
|
9
13
|
class?: string;
|
|
10
|
-
|
|
11
|
-
|
|
14
|
+
registerCommand?: boolean;
|
|
15
|
+
searchLinks?: NavigationSearchItem[];
|
|
16
|
+
searchResources?: boolean;
|
|
12
17
|
};
|
|
13
18
|
|
|
14
19
|
/** Opens the spotlight-style global search dialog from nav/header trigger points. */
|
|
15
20
|
export default function GlobalSearchTrigger(props: GlobalSearchTriggerProps) {
|
|
16
21
|
const locale = useLocale();
|
|
17
22
|
const t = () => platformMessages.resolve([locale()]).t;
|
|
18
|
-
const searchHelpApps = props.searchHelpApps ?? [];
|
|
19
23
|
|
|
20
|
-
if (props.
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
24
|
+
if (props.registerCommand) {
|
|
25
|
+
const host = createGlobalSearchHost(() => props.searchLinks ?? [], props.searchResources);
|
|
26
|
+
onMount(() => {
|
|
27
|
+
onCleanup(registerGlobalSearchHost(host.open, host.dispose));
|
|
28
|
+
});
|
|
29
|
+
onMount(() => onCleanup(attachCommandShortcuts((command) => void runContextAwareCommand(command))));
|
|
30
|
+
createEffect(() =>
|
|
31
|
+
onCleanup(
|
|
32
|
+
registerContextAwareCommand({
|
|
33
|
+
id: "cloud.search",
|
|
34
|
+
title: t().openGlobalSearch,
|
|
35
|
+
description: t().globalSearchDescription,
|
|
36
|
+
icon: "ti ti-search",
|
|
37
|
+
shortcut: "mod+k",
|
|
38
|
+
action: { search: {} },
|
|
39
|
+
}),
|
|
40
|
+
),
|
|
41
|
+
);
|
|
28
42
|
}
|
|
29
43
|
|
|
44
|
+
if (props.variant === "host") return null;
|
|
45
|
+
|
|
30
46
|
if (props.variant === "rail")
|
|
31
47
|
return (
|
|
32
48
|
<Tooltip.Trigger
|
|
33
49
|
type="button"
|
|
34
50
|
class={`rail-item text-blue-500 hover:bg-blue-500/10 hover:text-blue-600 dark:text-blue-400 dark:hover:bg-blue-500/15 dark:hover:text-blue-300 ${props.class ?? ""}`}
|
|
35
|
-
onClick={() =>
|
|
51
|
+
onClick={() => openGlobalSearch()}
|
|
36
52
|
aria-label={t().openGlobalSearch}
|
|
37
53
|
placement="right"
|
|
38
54
|
delay={0}
|
|
@@ -43,12 +59,7 @@ export default function GlobalSearchTrigger(props: GlobalSearchTriggerProps) {
|
|
|
43
59
|
);
|
|
44
60
|
|
|
45
61
|
return (
|
|
46
|
-
<IconButton
|
|
47
|
-
class={props.class}
|
|
48
|
-
onClick={() => openGlobalSearchDialog(searchHelpApps)}
|
|
49
|
-
label={t().openGlobalSearch}
|
|
50
|
-
title={t().searchShortcut}
|
|
51
|
-
>
|
|
62
|
+
<IconButton class={props.class} onClick={() => openGlobalSearch()} label={t().openGlobalSearch} title={t().searchShortcut}>
|
|
52
63
|
<i class="ti ti-search text-base" />
|
|
53
64
|
</IconButton>
|
|
54
65
|
);
|
package/src/ssr/Layout.tsx
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { getLayoutHelp } from "./help";
|
|
2
|
+
import { navigationSearchItems } from "../browser/navigation-search";
|
|
1
3
|
import type { DateContext } from "@k2b/stdlib";
|
|
2
4
|
import { AppWorkspace, appWorkspaceLayoutStyle, LocaleProvider, NoticeCard } from "@k2b/ui";
|
|
3
5
|
import type { JSX } from "solid-js/jsx-runtime";
|
|
@@ -7,13 +9,13 @@ import { defaultRailPreferences } from "../contracts/rail-preferences";
|
|
|
7
9
|
import { hasRole, type User } from "../contracts/shared";
|
|
8
10
|
import { getLocale } from "../server/locale";
|
|
9
11
|
import { getDateConfig } from "../server/time";
|
|
10
|
-
import { dates
|
|
12
|
+
import { dates } from "../shared";
|
|
11
13
|
import { readThemeFromCookieHeader } from "../shared/theme";
|
|
12
14
|
import type { AppLaunchpadApp } from "./AppLaunchpad.island";
|
|
13
15
|
import AppWorkspaceController from "./AppWorkspaceController.island";
|
|
14
16
|
import { appAppearanceStyle, resolveCurrentApp } from "./app-appearance";
|
|
15
17
|
import { visibleNavigationApps } from "./app-navigation";
|
|
16
|
-
import
|
|
18
|
+
import BrowserPushRegistration from "./BrowserPushRegistration.island";
|
|
17
19
|
import GlobalAnnouncements from "./GlobalAnnouncements.island";
|
|
18
20
|
import type { GlobalSearchHelpApp } from "./GlobalSearchHelpDialog";
|
|
19
21
|
import LayoutFooter from "./LayoutFooter";
|
|
@@ -155,7 +157,7 @@ export default function Layout(props: LayoutProps) {
|
|
|
155
157
|
const user = c.get("user");
|
|
156
158
|
const pathname = new URL(c.req.raw.url).pathname;
|
|
157
159
|
const currentApp = resolveCurrentApp(runtime.apps, pathname);
|
|
158
|
-
const registeredHelp =
|
|
160
|
+
const registeredHelp = getLayoutHelp(c);
|
|
159
161
|
const workspaceLayout = resolveAppWorkspaceLayoutForSidebar(
|
|
160
162
|
readAppWorkspaceLayoutCookie(cookie, currentApp?.id),
|
|
161
163
|
workspaceSidebarCollapsible,
|
|
@@ -170,6 +172,10 @@ export default function Layout(props: LayoutProps) {
|
|
|
170
172
|
description: app.description,
|
|
171
173
|
accent: app.accent,
|
|
172
174
|
}));
|
|
175
|
+
const searchLinks = navigationSearchItems(
|
|
176
|
+
runtime.apps,
|
|
177
|
+
allApps.map((app) => app.id),
|
|
178
|
+
);
|
|
173
179
|
const searchHelpApps: GlobalSearchHelpApp[] = runtime.apps
|
|
174
180
|
.filter((app) => (app.searchTags?.length ?? 0) > 0)
|
|
175
181
|
.map((app) => ({
|
|
@@ -203,7 +209,7 @@ export default function Layout(props: LayoutProps) {
|
|
|
203
209
|
user?.avatarHash && user.id
|
|
204
210
|
? `/api/accounts/users/${encodeURIComponent(user.id)}/avatar?rev=${encodeURIComponent(user.avatarHash)}`
|
|
205
211
|
: undefined;
|
|
206
|
-
const mainLayoutClass = fullPage || fullWidth ? "flex flex-col" : "
|
|
212
|
+
const mainLayoutClass = fullPage || fullWidth ? "flex flex-col" : "lg:overflow-auto";
|
|
207
213
|
const canvasStyle =
|
|
208
214
|
[appAppearanceStyle(currentApp?.appearance), appWorkspaceLayoutStyle(workspaceLayout), focusMode && flushCanvas ? "padding:0" : ""]
|
|
209
215
|
.filter(Boolean)
|
|
@@ -215,6 +221,7 @@ export default function Layout(props: LayoutProps) {
|
|
|
215
221
|
class="cloud-app-canvas flex h-dvh w-full overflow-hidden"
|
|
216
222
|
style={canvasStyle}
|
|
217
223
|
data-app-id={currentApp?.id}
|
|
224
|
+
data-layout-full-page="true"
|
|
218
225
|
data-workspace-sidebar-collapsed={workspaceLayout?.sidebarCollapsed ? "true" : undefined}
|
|
219
226
|
>
|
|
220
227
|
<TimezoneCookie />
|
|
@@ -229,16 +236,17 @@ export default function Layout(props: LayoutProps) {
|
|
|
229
236
|
return (
|
|
230
237
|
<LocaleProvider locale={lang}>
|
|
231
238
|
<div
|
|
232
|
-
class={`cloud-app-canvas relative flex w-full ${fullPage ? "h-dvh overflow-hidden" : "min-h-screen
|
|
239
|
+
class={`cloud-app-canvas relative flex w-full ${fullPage ? "h-dvh overflow-hidden" : "min-h-screen lg:h-screen lg:overflow-hidden"}`}
|
|
233
240
|
style={canvasStyle}
|
|
234
241
|
data-app-id={currentApp?.id}
|
|
242
|
+
data-layout-full-page={fullPage ? "true" : undefined}
|
|
235
243
|
data-layout-authenticated={showRail ? "true" : undefined}
|
|
236
244
|
data-workspace-sidebar-collapsed={workspaceLayout?.sidebarCollapsed ? "true" : undefined}
|
|
237
245
|
>
|
|
238
246
|
<TimezoneCookie />
|
|
239
247
|
{registeredHelp && <RegisteredHelpDocuments documents={registeredHelp.documents} pageBase={registeredHelp.pageBase} />}
|
|
240
248
|
<AppWorkspaceController appId={currentApp?.id} />
|
|
241
|
-
{user && <
|
|
249
|
+
{user && <BrowserPushRegistration />}
|
|
242
250
|
{showRail && (
|
|
243
251
|
<LayoutRail
|
|
244
252
|
accent={currentApp?.appearance?.accent}
|
|
@@ -253,11 +261,13 @@ export default function Layout(props: LayoutProps) {
|
|
|
253
261
|
profileAvatarSrc={profileAvatarSrc}
|
|
254
262
|
profileName={profileName}
|
|
255
263
|
searchHelpApps={searchHelpApps}
|
|
264
|
+
searchLinks={searchLinks}
|
|
256
265
|
theme={theme}
|
|
257
266
|
/>
|
|
258
267
|
)}
|
|
259
268
|
<div class="layout-shell-content flex min-h-0 min-w-0 flex-1 flex-col">
|
|
260
269
|
<LayoutHeader
|
|
270
|
+
appLabel={currentApp?.name ?? (pathname.startsWith("/admin") ? t.admin : undefined)}
|
|
261
271
|
accent={currentApp?.appearance?.accent}
|
|
262
272
|
authenticated={showRail}
|
|
263
273
|
breadcrumbs={breadcrumbs}
|
|
@@ -268,6 +278,7 @@ export default function Layout(props: LayoutProps) {
|
|
|
268
278
|
profileAvatarSrc={profileAvatarSrc}
|
|
269
279
|
profileName={profileName}
|
|
270
280
|
searchHelpApps={searchHelpApps}
|
|
281
|
+
searchLinks={searchLinks}
|
|
271
282
|
signInLabel={t.signIn}
|
|
272
283
|
theme={theme}
|
|
273
284
|
/>
|
|
@@ -3,12 +3,10 @@ import { LAYOUT_UPDATE_EVENT, type LayoutBreadcrumb, type LayoutUpdate } from ".
|
|
|
3
3
|
|
|
4
4
|
type Props = {
|
|
5
5
|
breadcrumbs: LayoutBreadcrumb[];
|
|
6
|
-
mobile?: boolean;
|
|
7
6
|
};
|
|
8
7
|
|
|
9
8
|
export default function LayoutBreadcrumbs(props: Props) {
|
|
10
9
|
const [breadcrumbs, setBreadcrumbs] = createSignal(props.breadcrumbs);
|
|
11
|
-
const visibleBreadcrumbs = () => (props.mobile ? breadcrumbs().slice(-1) : breadcrumbs());
|
|
12
10
|
|
|
13
11
|
onMount(() => {
|
|
14
12
|
const onUpdate = (event: Event) => {
|
|
@@ -21,10 +19,10 @@ export default function LayoutBreadcrumbs(props: Props) {
|
|
|
21
19
|
});
|
|
22
20
|
|
|
23
21
|
return (
|
|
24
|
-
<nav class="flex items-center gap-1 sm:gap-2 min-w-0 text-
|
|
25
|
-
<For each={
|
|
22
|
+
<nav class="flex items-center gap-1 sm:gap-2 min-w-0 text-xs">
|
|
23
|
+
<For each={breadcrumbs()}>
|
|
26
24
|
{(crumb, i) => {
|
|
27
|
-
const isLast = () => i() ===
|
|
25
|
+
const isLast = () => i() === breadcrumbs().length - 1;
|
|
28
26
|
return (
|
|
29
27
|
<>
|
|
30
28
|
{i() > 0 && <span class="text-zinc-400 dark:text-zinc-600 text-xs">/</span>}
|
package/src/ssr/LayoutFooter.tsx
CHANGED
|
@@ -5,7 +5,7 @@ type LayoutFooterProps = {
|
|
|
5
5
|
|
|
6
6
|
export default function LayoutFooter(props: LayoutFooterProps) {
|
|
7
7
|
return (
|
|
8
|
-
<footer class="hidden shrink-0 flex-wrap items-center justify-center gap-x-4 gap-y-1 px-3 py-2 text-xs text-dimmed
|
|
8
|
+
<footer class="hidden shrink-0 flex-wrap items-center justify-center gap-x-4 gap-y-1 px-3 py-2 text-xs text-dimmed lg:flex">
|
|
9
9
|
{props.legalLinks.map((link) => (
|
|
10
10
|
<a href={link.href} class="flex items-center gap-1 transition-colors hover:text-primary">
|
|
11
11
|
{link.icon && <i class={`${link.icon} text-xs`} />}
|