@k2b/cloud 0.43.0 → 0.44.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/package.json +7 -2
- package/src/_internal/app-access-request.ts +13 -0
- package/src/_internal/app-access-resources.ts +141 -0
- package/src/_internal/app-event-catalog.ts +40 -0
- package/src/_internal/app-event-schemas.ts +127 -0
- package/src/_internal/application-package.ts +40 -0
- package/src/_internal/built-in-app-ids.json +27 -0
- package/src/_internal/capabilities.ts +158 -12
- package/src/_internal/capability-origin.ts +4 -0
- package/src/_internal/capability-streams.ts +7 -5
- package/src/_internal/define-app.ts +57 -6
- package/src/_internal/object-store-area.ts +9 -0
- package/src/_internal/object-store-config.ts +101 -0
- package/src/_internal/object-store-key.ts +14 -0
- package/src/_internal/process-identity.ts +7 -2
- package/src/_internal/registry-validation.ts +11 -1
- package/src/_internal/registry.ts +8 -1
- package/src/_internal/runtime-context.ts +8 -1
- package/src/access/AppAccessEditor.tsx +2 -0
- package/src/access/PermissionEditor.tsx +9 -3
- package/src/access/PrincipalPicker.tsx +14 -4
- package/src/access/ResourceApiKeys.tsx +3 -4
- package/src/access/app-access-messages.ts +6 -0
- package/src/account/ProfilePictureDialog.tsx +699 -0
- package/src/account/profile-picture-messages.ts +269 -0
- package/src/account/ui.ts +7 -0
- package/src/ai/admin.ts +3 -0
- package/src/ai/app-frame.ts +12 -0
- package/src/ai/application-tasks.ts +101 -0
- package/src/ai/assistant-models.ts +126 -64
- package/src/ai/capabilities.ts +19 -4
- package/src/ai/chart-block.ts +63 -30
- package/src/ai/chat/chart-block.tsx +12 -3
- package/src/ai/chat/composer-adapter.ts +8 -7
- package/src/ai/chat/message-actions.tsx +1 -1
- package/src/ai/chat/message-utils.ts +2 -2
- package/src/ai/chat/presentation.tsx +22 -1
- package/src/ai/chat-quotas.ts +5 -4
- package/src/ai/chat-tasks.ts +2 -2
- package/src/ai/client/controller.ts +11 -10
- package/src/ai/client/projection.ts +3 -3
- package/src/ai/compaction.ts +3 -3
- package/src/ai/credentials.ts +13 -114
- package/src/ai/default-tool-contracts.ts +4 -3
- package/src/ai/dictation-runtime.ts +4 -4
- package/src/ai/enrich.ts +2 -2
- package/src/ai/executor.ts +31 -22
- package/src/ai/http.ts +17 -29
- package/src/ai/index.ts +11 -5
- package/src/ai/inference-calls.ts +5 -4
- package/src/ai/maintenance.ts +1 -1
- package/src/ai/memory-learning-runs.ts +5 -5
- package/src/ai/memory-learning.ts +3 -3
- package/src/ai/message-queue.ts +1 -2
- package/src/ai/migrate.ts +75 -42
- package/src/ai/model-access-migrate.ts +3 -17
- package/src/ai/model-access.ts +44 -85
- package/src/ai/model-card.tsx +134 -0
- package/src/ai/model-catalog.ts +359 -0
- package/src/ai/model-chooser-messages.ts +88 -0
- package/src/ai/model-chooser.tsx +577 -0
- package/src/ai/model-delete.ts +234 -0
- package/src/ai/model-fixture.ts +39 -0
- package/src/ai/model-icon.tsx +36 -0
- package/src/ai/model-overview.ts +53 -0
- package/src/ai/model-usage.ts +45 -0
- package/src/ai/openai-api-messages.ts +0 -4
- package/src/ai/openai-api.ts +26 -9
- package/src/ai/personal-agent.ts +1 -1
- package/src/ai/prefs.ts +12 -10
- package/src/ai/projects-routes.ts +2 -1
- package/src/ai/projects.ts +13 -12
- package/src/ai/protocol.ts +2 -2
- package/src/ai/provider-connections.ts +161 -0
- package/src/ai/provider-errors.ts +55 -6
- package/src/ai/provider-fetch.ts +4 -0
- package/src/ai/provider.ts +1 -1
- package/src/ai/quota-limits.ts +36 -0
- package/src/ai/quota-provider.ts +4 -2
- package/src/ai/quota-report.ts +7 -4
- package/src/ai/quotas.ts +19 -8
- package/src/ai/recent-resources.ts +6 -0
- package/src/ai/request-headers.ts +14 -87
- package/src/ai/resource-refs.ts +6 -2
- package/src/ai/routes.ts +17 -18
- package/src/ai/runtime.ts +50 -22
- package/src/ai/settings.ts +93 -580
- package/src/ai/store.ts +45 -23
- package/src/ai/stream.ts +1 -1
- package/src/ai/structured-runs.ts +2 -2
- package/src/ai/structured.ts +21 -44
- package/src/ai/task-contracts.ts +25 -5
- package/src/ai/task-execution.ts +15 -6
- package/src/ai/task-model.ts +9 -0
- package/src/ai/transcription.ts +27 -22
- package/src/ai/types.ts +50 -62
- package/src/ai/ui.tsx +13 -0
- package/src/ai/usage.ts +8 -8
- package/src/ai/validate.ts +8 -7
- package/src/ai/view-block.ts +2 -2
- package/src/api/accounts-entities.ts +16 -0
- package/src/api/admin-ai-quotas.ts +34 -56
- package/src/api/admin-ai-usage.ts +1 -1
- package/src/api/admin-ai.ts +173 -0
- package/src/api/admin-core-settings.ts +68 -244
- package/src/api/app-approval.ts +3 -1
- package/src/api/app-event-delivery.ts +51 -0
- package/src/api/app-events.ts +123 -0
- package/src/api/app-resource-access.ts +48 -0
- package/src/api/auth/account-mismatch.ts +24 -0
- package/src/api/auth.ts +26 -3
- package/src/api/capabilities.ts +28 -4
- package/src/api/index.ts +7 -1
- package/src/api/mcp.ts +175 -2
- package/src/api/me.ts +6 -4
- package/src/api/pwa-phone.ts +75 -26
- package/src/api/pwa.ts +34 -8
- package/src/api/resource-links.ts +83 -6
- package/src/api/resource-resolve.ts +50 -0
- package/src/api/search/schemas.ts +3 -1
- package/src/api/search.ts +9 -7
- package/src/app-pwa/contracts.ts +4 -0
- package/src/app-pwa/core-query-error.ts +20 -0
- package/src/app-pwa/index.ts +1 -0
- package/src/app-pwa/service.ts +91 -0
- package/src/app-pwa/transport.ts +22 -0
- package/src/approvals/messages.ts +2 -2
- package/src/browser/cloud-id.ts +86 -0
- package/src/browser/resource-clipboard.ts +4 -0
- package/src/browser/resource-links.ts +52 -0
- package/src/capabilities/server-invoke.ts +2 -11
- package/src/capabilities/testing.ts +5 -2
- package/src/cli/access.ts +6 -5
- package/src/cli/admin/ai-models.ts +199 -0
- package/src/cli/admin/ai-quotas.ts +6 -102
- package/src/cli/admin/ai-usage.ts +4 -4
- package/src/cli/admin/app-credentials.ts +5 -3
- package/src/cli/admin/index.ts +5 -2
- package/src/cli/apps.ts +89 -26
- package/src/cli/capabilities.ts +22 -1
- package/src/cli/events.ts +320 -0
- package/src/config/env.ts +40 -0
- package/src/contracts/app-access.ts +26 -2
- package/src/contracts/app-details.ts +89 -0
- package/src/contracts/app-events.ts +51 -0
- package/src/contracts/app.ts +21 -5
- package/src/contracts/capabilities.ts +143 -3
- package/src/contracts/index.ts +2 -0
- package/src/contracts/notification-types.ts +36 -9
- package/src/contracts/pwa.ts +17 -1
- package/src/contracts/registry.ts +6 -0
- package/src/contracts/resource-clipboard.ts +35 -0
- package/src/contracts/resource-pages.ts +140 -0
- package/src/contracts/resource-resolution.ts +13 -0
- package/src/contracts/shared.ts +4 -0
- package/src/contracts/user-notifications.ts +2 -0
- package/src/events/app-event-delivery.ts +100 -0
- package/src/events/app-events.testing.ts +65 -0
- package/src/events/app-events.ts +134 -0
- package/src/events/core-user-created.ts +24 -0
- package/src/events/index.ts +9 -0
- package/src/events/testing.ts +2 -0
- package/src/index.ts +1 -1
- package/src/server/api/respond.ts +8 -3
- package/src/server/index.ts +1 -0
- package/src/server/middleware/app-access.ts +1 -1
- package/src/server/middleware/auth.ts +21 -7
- package/src/server/middleware/index.ts +1 -0
- package/src/server/middleware/invocation.ts +2 -0
- package/src/server/public-storage.ts +106 -0
- package/src/server/services/access.ts +8 -0
- package/src/services/accounts/app.ts +6 -0
- package/src/services/accounts/entities.ts +14 -3
- package/src/services/accounts/groups.ts +3 -1
- package/src/services/accounts/identities.ts +62 -10
- package/src/services/accounts/users.ts +7 -6
- package/src/services/app-access.ts +25 -7
- package/src/services/app-details.ts +224 -0
- package/src/services/app-discovery.ts +2 -1
- package/src/services/app-event-shapes.ts +139 -0
- package/src/services/app-events/accept.ts +116 -0
- package/src/services/app-events/admin.ts +250 -0
- package/src/services/app-events/declarations.ts +187 -0
- package/src/services/app-events/deliver.ts +218 -0
- package/src/services/app-events.ts +92 -0
- package/src/services/config-conflict.ts +11 -0
- package/src/services/core-user-events.ts +1 -0
- package/src/services/freeipa-config.ts +9 -0
- package/src/services/geo/ledger.ts +28 -10
- package/src/services/geo/service.ts +43 -24
- package/src/services/identity/core-origin.ts +12 -0
- package/src/services/identity/invocation-actor.ts +20 -2
- package/src/services/identity/invocation-authority.ts +4 -0
- package/src/services/identity/invocation-token.ts +238 -56
- package/src/services/identity/issue-invocation.ts +6 -1
- package/src/services/image-metadata.ts +65 -1
- package/src/services/index.ts +13 -1
- package/src/services/ipa/sync.ts +6 -2
- package/src/services/ipa/users.ts +4 -1
- package/src/services/mandates/policy.ts +19 -12
- package/src/services/notifications/browser.ts +4 -0
- package/src/services/notifications/catalog.ts +5 -2
- package/src/services/notifications/platform.ts +7 -2
- package/src/services/notifications/user.ts +18 -7
- package/src/services/oauth-tokens.ts +28 -2
- package/src/services/object-store.ts +217 -0
- package/src/services/providers/local/users.ts +5 -0
- package/src/services/pwa-devices.ts +147 -37
- package/src/services/session/index.ts +14 -0
- package/src/services/settings/core-settings.ts +71 -43
- package/src/services/settings/index.ts +8 -0
- package/src/services/settings/versions.ts +26 -0
- package/src/shared/ai-models.ts +274 -0
- package/src/shared/ai-quotas.ts +2 -2
- package/src/shared/ai-usage.ts +2 -2
- package/src/shared/index.ts +2 -0
- package/src/shared-text/documents.ts +25 -8
- package/src/shared-text/log.ts +1 -1
- package/src/shared-text/text-diff.ts +75 -0
- package/src/ssr/AdminArea.tsx +131 -0
- package/src/ssr/AdminLayout.tsx +9 -3
- package/src/ssr/AdminSidebar.tsx +7 -7
- package/src/ssr/AppAccessDenied.tsx +2 -0
- package/src/ssr/AppBadge.tsx +14 -0
- package/src/ssr/AppLaunchpadPanel.tsx +1 -1
- package/src/ssr/CloudShell.island.tsx +25 -4
- package/src/ssr/Layout.tsx +8 -6
- package/src/ssr/PwaLayout.tsx +16 -1
- package/src/ssr/PwaPartCount.island.tsx +23 -0
- package/src/ssr/SettingsSaveOutcome.tsx +58 -0
- package/src/ssr/SettingsSaveStatus.island.tsx +16 -0
- package/src/ssr/__snapshots__/Layout.unchanged.render.test.tsx.snap +3 -3
- package/src/ssr/admin-active-link.ts +16 -0
- package/src/ssr/admin-navigation.ts +25 -20
- package/src/ssr/app-badges.ts +31 -7
- package/src/ssr/index.ts +8 -0
- package/src/ssr/page-trail-runtime.ts +3 -1
- package/src/ssr/platform-messages.ts +9 -1
- package/src/ssr/pwa-messages.ts +2 -2
- package/src/ssr/rail-navigation.ts +5 -1
- package/src/ssr/settings-draft.ts +153 -0
- package/src/ssr/shell-data.tsx +2 -0
- package/src/styles/global.css +1 -0
- package/src/styles/profile-picture.css +270 -0
- package/src/styles/utilities-navigation.css +6 -0
- package/src/workflows/ai/policy.ts +49 -0
- package/src/workflows/ai/runtime.ts +131 -21
- package/src/workflows/ai/store.ts +283 -35
- package/src/workflows/ai/types.ts +8 -2
- package/src/workflows/ai-actions.ts +148 -120
- package/src/workflows/authoring.ts +31 -0
- package/src/workflows/contracts.ts +1 -0
- package/src/workflows/definition.ts +2 -0
- package/src/workflows/editor.ts +15 -0
- package/src/workflows/index.ts +2 -1
- package/src/workflows/language/compiler.ts +34 -3
- package/src/workflows/runtime/executor.ts +132 -33
- package/src/workflows/runtime/index.ts +2 -0
- package/src/workflows/runtime/ports.ts +5 -1
- package/src/workflows/store/actions.ts +1 -1
- package/src/workflows/store/definitions.ts +5 -0
- package/src/workflows/store/runs.ts +5 -3
- package/src/workflows/store/worker.ts +7 -2
- package/src/ai/model-pricing.ts +0 -43
- package/src/ai/model-request-settings.ts +0 -98
- package/src/ai/settings-messages.ts +0 -129
- package/src/shared/ai-model-request-settings.ts +0 -21
package/src/ssr/AppBadge.tsx
CHANGED
|
@@ -18,6 +18,20 @@ export const useAppBadgeDescription = (input: () => { endpoint?: string; appId?:
|
|
|
18
18
|
* takes layout space, so appearing, growing to "99+", or clearing moves
|
|
19
19
|
* nothing. Decorative: the link carries the description.
|
|
20
20
|
*/
|
|
21
|
+
/**
|
|
22
|
+
* The count on a phone bar item: the rail's count, laid over the tab icon's corner, and its description for screen
|
|
23
|
+
* readers after the item's label.
|
|
24
|
+
*/
|
|
25
|
+
export function TabBarAppBadge(props: { endpoint?: string; appId?: string }) {
|
|
26
|
+
const description = useAppBadgeDescription(() => props);
|
|
27
|
+
return (
|
|
28
|
+
<Show when={description()}>
|
|
29
|
+
<AppBadge endpoint={props.endpoint} appId={props.appId} />
|
|
30
|
+
<span class="sr-only">, {description()}</span>
|
|
31
|
+
</Show>
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
|
|
21
35
|
export function AppBadge(props: { endpoint?: string; appId?: string }) {
|
|
22
36
|
const count = () => appBadgeCount(props);
|
|
23
37
|
return (
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { TabBar, useLocale } from "@k2b/ui";
|
|
2
2
|
import { createEffect, createMemo, createSignal, onCleanup, onMount, Show } from "solid-js";
|
|
3
3
|
import { isServer, Portal } from "solid-js/web";
|
|
4
|
+
import { TabBarAppBadge } from "./AppBadge";
|
|
5
|
+
import { appBadgeCount } from "./app-badges";
|
|
4
6
|
import { AppLaunchpadButton, setAppLaunchpadContext } from "./app-launchpad";
|
|
5
7
|
import GlobalSearchTrigger from "./GlobalSearchTrigger";
|
|
6
8
|
import LayoutBreadcrumbs from "./LayoutBreadcrumbs";
|
|
@@ -35,8 +37,9 @@ export default function CloudShell(props: CloudShellProps) {
|
|
|
35
37
|
/**
|
|
36
38
|
* The phone bar's live copy, mounted with the rail's island so the shell adds no island instance. It renders nothing
|
|
37
39
|
* while the server's bar is current, so mounting moves and refocuses nothing. When the saved rail preferences or the
|
|
38
|
-
* URL (a fragment, Back and Forward, an in-page navigation) change what the bar shows,
|
|
39
|
-
* bar's row, and CSS hides the server's.
|
|
40
|
+
* URL (a fragment, Back and Forward, an in-page navigation) change what the bar shows, or an app in it has a count,
|
|
41
|
+
* it renders the new bar into the bar's row, and CSS hides the server's. Counts, like the rail's, exist only in the
|
|
42
|
+
* browser.
|
|
40
43
|
*/
|
|
41
44
|
function BarNavigation(props: { currentPath: string }) {
|
|
42
45
|
if (isServer) return null;
|
|
@@ -51,6 +54,12 @@ function BarNavigation(props: { currentPath: string }) {
|
|
|
51
54
|
phoneBarItems(projectRailNavigation(data.apps, rail, locale()), path, { label: t().home, path: data.home ?? "/" });
|
|
52
55
|
const served = JSON.stringify(itemsFor(initial, props.currentPath));
|
|
53
56
|
const items = createMemo(() => itemsFor(settings(), currentPath()));
|
|
57
|
+
const counted = () => items().some((item) => item.counted && appBadgeCount(item.counted) > 0);
|
|
58
|
+
const live = createMemo(() =>
|
|
59
|
+
items().map((item) =>
|
|
60
|
+
item.counted ? { ...item, badge: <TabBarAppBadge endpoint={item.counted.endpoint} appId={item.counted.appId} /> } : item,
|
|
61
|
+
),
|
|
62
|
+
);
|
|
54
63
|
onMount(() => {
|
|
55
64
|
const sync = () => setSettings(readRailContext()?.settings ?? initial);
|
|
56
65
|
const syncLocation = () => setCurrentPath(`${window.location.pathname}${window.location.search}${window.location.hash}`);
|
|
@@ -76,10 +85,22 @@ function BarNavigation(props: { currentPath: string }) {
|
|
|
76
85
|
});
|
|
77
86
|
const row = document.querySelector<HTMLElement>(".cloud-tab-bar__row");
|
|
78
87
|
if (!row) return null;
|
|
88
|
+
// Swapping bars keeps a focused link: the bar that takes over focuses the same destination, once CSS shows it.
|
|
89
|
+
const [shown, setShown] = createSignal(false);
|
|
90
|
+
const focusIn = (selector: string, tab: string) =>
|
|
91
|
+
[...row.querySelectorAll<HTMLElement>(`${selector} a[data-tab]`)].find((link) => link.dataset.tab === tab)?.focus();
|
|
92
|
+
createEffect(() => {
|
|
93
|
+
const next = counted() || JSON.stringify(items()) !== served;
|
|
94
|
+
if (next === shown()) return;
|
|
95
|
+
const focused = document.activeElement;
|
|
96
|
+
const tab = focused instanceof HTMLElement && row.contains(focused) ? focused.dataset.tab : undefined;
|
|
97
|
+
setShown(next);
|
|
98
|
+
if (tab) queueMicrotask(() => focusIn(next ? ".cloud-tab-bar__live" : ".k2b-tab-bar:not(.cloud-tab-bar__live)", tab));
|
|
99
|
+
});
|
|
79
100
|
return (
|
|
80
|
-
<Show when={
|
|
101
|
+
<Show when={shown()}>
|
|
81
102
|
<Portal mount={row}>
|
|
82
|
-
<TabBar variant="floating" class="cloud-tab-bar__live" label={t().apps} items={
|
|
103
|
+
<TabBar variant="floating" class="cloud-tab-bar__live" label={t().apps} items={live()} />
|
|
83
104
|
</Portal>
|
|
84
105
|
</Show>
|
|
85
106
|
);
|
package/src/ssr/Layout.tsx
CHANGED
|
@@ -14,6 +14,7 @@ import { isAppSessionActor } from "../server/actor";
|
|
|
14
14
|
import { getLocale } from "../server/locale";
|
|
15
15
|
import { getDateConfig } from "../server/time";
|
|
16
16
|
import { dates, resolveHomePath } from "../shared";
|
|
17
|
+
import { publicCloudOrigin } from "../shared/app-url";
|
|
17
18
|
import { readThemePreferenceFromCookieHeader } from "../shared/theme";
|
|
18
19
|
import AnnouncementBanners from "./AnnouncementBanners";
|
|
19
20
|
import { appAppearanceStyle, resolveCurrentApp } from "./app-appearance";
|
|
@@ -258,7 +259,9 @@ export default function Layout(props: LayoutProps) {
|
|
|
258
259
|
const panel: ShellData["panel"] = providers.length
|
|
259
260
|
? { providers, provider: panelState.provider ?? providers[0]?.id, context: pageContext, consent: c.get("panelConsent") }
|
|
260
261
|
: undefined;
|
|
262
|
+
const cloudOrigin = settings?.app?.url ? publicCloudOrigin(settings.app.url) : undefined;
|
|
261
263
|
const shellData: ShellData = {
|
|
264
|
+
...(cloudOrigin ? { cloudOrigin } : {}),
|
|
262
265
|
...(panel ? { panel } : {}),
|
|
263
266
|
platform: keyboardPlatform(c.req.raw.headers.get("User-Agent") ?? ""),
|
|
264
267
|
...(viewHelp.length ? { help: viewHelp } : {}),
|
|
@@ -294,6 +297,7 @@ export default function Layout(props: LayoutProps) {
|
|
|
294
297
|
apps: allApps,
|
|
295
298
|
legalLinks,
|
|
296
299
|
searchLinks,
|
|
300
|
+
cloudOrigin: shellData.cloudOrigin,
|
|
297
301
|
home: shellData.home,
|
|
298
302
|
profile: shellData.profile,
|
|
299
303
|
announcements: user ? (announcements?.latestAnnouncementVersion ?? null) : null,
|
|
@@ -325,12 +329,10 @@ export default function Layout(props: LayoutProps) {
|
|
|
325
329
|
<AppWorkspace.LayoutStateProvider state={workspaceLayout}>{props.children}</AppWorkspace.LayoutStateProvider>
|
|
326
330
|
</main>
|
|
327
331
|
</PageTrailProvider>
|
|
328
|
-
{/* Focus pages
|
|
329
|
-
{panel
|
|
330
|
-
<
|
|
331
|
-
|
|
332
|
-
</ShellDataProvider>
|
|
333
|
-
)}
|
|
332
|
+
{/* Focus pages also expose the canonical origin to their islands. */}
|
|
333
|
+
<ShellDataProvider data={{ cloudOrigin, panel, platform: shellData.platform, theme, apps: [], legalLinks: [], searchLinks: {} }}>
|
|
334
|
+
{panel && <PanelSlot panel={panel} state={{ ...panelState, open: false }} platform={shellData.platform} />}
|
|
335
|
+
</ShellDataProvider>
|
|
334
336
|
<div data-print-fallback="">
|
|
335
337
|
<p>{t.printFallbackTitle}</p>
|
|
336
338
|
<p>{props.printHint ?? t.printFallbackHint}</p>
|
package/src/ssr/PwaLayout.tsx
CHANGED
|
@@ -7,6 +7,7 @@ import { pwaBuild } from "../contracts/pwa-builds";
|
|
|
7
7
|
import { getLocale } from "../server/locale";
|
|
8
8
|
import { visiblePwaParts } from "./app-navigation";
|
|
9
9
|
import type { LayoutContext } from "./layout-context";
|
|
10
|
+
import PwaPartCount from "./PwaPartCount.island";
|
|
10
11
|
import PwaRuntime from "./PwaRuntime.island";
|
|
11
12
|
import { pwaMessages } from "./pwa-messages";
|
|
12
13
|
import { getDisplayRuntimeContext } from "./runtime";
|
|
@@ -62,12 +63,24 @@ export default function PwaLayout(props: PwaLayoutProps) {
|
|
|
62
63
|
const ownBuild = pwaBuild(buildMetadata);
|
|
63
64
|
const coreBuild = registeredPwaBuilds().core;
|
|
64
65
|
const builds = { ...(owner && ownBuild ? { [owner]: ownBuild } : {}), ...(coreBuild ? { core: coreBuild } : {}) };
|
|
66
|
+
// A part's badge calls for attention in the sidebar and on its tab; without one, its summary's count stays quiet in
|
|
67
|
+
// the sidebar. One watcher reads every route of the page.
|
|
68
|
+
const countRoutes = [...new Set(parts.flatMap((part) => [part.pwa.badge, part.pwa.summary].filter((path) => path !== undefined)))];
|
|
69
|
+
const badge = (part: (typeof parts)[number]) =>
|
|
70
|
+
part.pwa.badge ? <PwaPartCount endpoint={part.pwa.badge} endpoints={countRoutes} attention /> : undefined;
|
|
65
71
|
const tabs = parts.slice(0, TAB_PARTS);
|
|
66
72
|
const currentTab = tabs.find((part) => path === part.pwa.href || path.startsWith(`${part.pwa.href}/`));
|
|
67
73
|
// Start is titled with the installation's name; a part's first page carries the part's name.
|
|
68
74
|
const items: TabBarItem[] = [
|
|
69
75
|
{ id: "start", label: t.start, title: cloud, icon: "ti ti-home", href: PWA_SCOPE, current: !currentTab },
|
|
70
|
-
...tabs.map((part) => ({
|
|
76
|
+
...tabs.map((part) => ({
|
|
77
|
+
id: part.id,
|
|
78
|
+
label: part.name,
|
|
79
|
+
icon: part.icon,
|
|
80
|
+
href: part.pwa.href,
|
|
81
|
+
current: part === currentTab,
|
|
82
|
+
badge: badge(part),
|
|
83
|
+
})),
|
|
71
84
|
];
|
|
72
85
|
// The sidebar names the installation itself, so Start is titled Start there; Settings is a destination of its own.
|
|
73
86
|
const settingsHref = `${PWA_SCOPE}settings`;
|
|
@@ -87,6 +100,8 @@ export default function PwaLayout(props: PwaLayoutProps) {
|
|
|
87
100
|
icon: part.icon,
|
|
88
101
|
href: part.pwa.href,
|
|
89
102
|
current: part === currentPart,
|
|
103
|
+
badge: badge(part),
|
|
104
|
+
count: part.pwa.summary ? <PwaPartCount endpoint={part.pwa.summary} endpoints={countRoutes} /> : undefined,
|
|
90
105
|
})),
|
|
91
106
|
},
|
|
92
107
|
]}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { AppWorkspace } from "@k2b/ui";
|
|
2
|
+
import { onCleanup, onMount, Show } from "solid-js";
|
|
3
|
+
import { appBadgeCount, watchAppBadges } from "./app-badges";
|
|
4
|
+
|
|
5
|
+
export type PwaPartCountProps = {
|
|
6
|
+
/** This part's `pwa.badge` or, without one, its `pwa.summary` route. */
|
|
7
|
+
endpoint: string;
|
|
8
|
+
/** Every count route of the page; the first count on a page reads them all, once a minute like the web's. */
|
|
9
|
+
endpoints: string[];
|
|
10
|
+
/** A badge calls for attention, such as unread messages; a summary's count stays quiet beside the label. */
|
|
11
|
+
attention?: boolean;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
/** The count of a part in the Cloud app's sidebar and tab bar; nothing at zero. */
|
|
15
|
+
export default function PwaPartCount(props: PwaPartCountProps) {
|
|
16
|
+
onMount(() => onCleanup(watchAppBadges(props.endpoints)));
|
|
17
|
+
const count = () => appBadgeCount({ endpoint: props.endpoint });
|
|
18
|
+
return (
|
|
19
|
+
<Show when={props.attention} fallback={<Show when={count() > 0}>{count() > 99 ? "99+" : count()}</Show>}>
|
|
20
|
+
<AppWorkspace.SidebarItemStatus unread={count()} />
|
|
21
|
+
</Show>
|
|
22
|
+
);
|
|
23
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { i18n } from "@k2b/stdlib";
|
|
2
|
+
import { Button, InlineGuidance, useLocale } from "@k2b/ui";
|
|
3
|
+
import { Show } from "solid-js";
|
|
4
|
+
import type { SettingsDraftOutcome } from "./settings-draft";
|
|
5
|
+
|
|
6
|
+
const messages = i18n.define({
|
|
7
|
+
baseLocale: "en",
|
|
8
|
+
messages: {
|
|
9
|
+
en: {
|
|
10
|
+
notSaved: "Not saved.",
|
|
11
|
+
notSavedKeep: "The previous values still apply. Your changes stay here.",
|
|
12
|
+
unknownOutcome: "The connection broke while saving.",
|
|
13
|
+
unknownOutcomeKeep: "Whether the new values apply is open. Cloud does not save again on its own.",
|
|
14
|
+
checkNow: "Check current state",
|
|
15
|
+
retry: "Try again",
|
|
16
|
+
},
|
|
17
|
+
de: {
|
|
18
|
+
notSaved: "Nicht gespeichert.",
|
|
19
|
+
notSavedKeep: "Es gelten weiter die alten Werte. Deine Änderungen bleiben hier stehen.",
|
|
20
|
+
unknownOutcome: "Die Verbindung brach beim Speichern ab.",
|
|
21
|
+
unknownOutcomeKeep: "Ob die neuen Werte gelten, ist offen. Cloud speichert nicht von selbst noch einmal.",
|
|
22
|
+
checkNow: "Stand jetzt prüfen",
|
|
23
|
+
retry: "Erneut versuchen",
|
|
24
|
+
},
|
|
25
|
+
},
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
export const checkSettingsSaveOutcomeMessages = () => messages.check();
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* What became of the last save when it did not succeed, above the form: not saved with the reason and a retry, or an
|
|
32
|
+
* unclear outcome with a way to check the current state instead of saving twice.
|
|
33
|
+
*/
|
|
34
|
+
export default function SettingsSaveOutcome(props: { outcome: SettingsDraftOutcome | null; onRetry: () => void }) {
|
|
35
|
+
const locale = useLocale();
|
|
36
|
+
const t = () => messages.resolve([locale()]).t;
|
|
37
|
+
return (
|
|
38
|
+
<Show when={props.outcome}>
|
|
39
|
+
{(outcome) =>
|
|
40
|
+
outcome().kind === "failed" ? (
|
|
41
|
+
<InlineGuidance tone="danger" icon="ti ti-alert-triangle">
|
|
42
|
+
<b>{t().notSaved}</b> {outcome().message} {t().notSavedKeep}{" "}
|
|
43
|
+
<Button type="button" variant="ghost" size="xs" onClick={props.onRetry}>
|
|
44
|
+
{t().retry}
|
|
45
|
+
</Button>
|
|
46
|
+
</InlineGuidance>
|
|
47
|
+
) : (
|
|
48
|
+
<InlineGuidance tone="warning" icon="ti ti-help-circle">
|
|
49
|
+
<b>{t().unknownOutcome}</b> {t().unknownOutcomeKeep}{" "}
|
|
50
|
+
<Button type="button" variant="ghost" size="xs" onClick={() => window.location.reload()}>
|
|
51
|
+
{t().checkNow}
|
|
52
|
+
</Button>
|
|
53
|
+
</InlineGuidance>
|
|
54
|
+
)
|
|
55
|
+
}
|
|
56
|
+
</Show>
|
|
57
|
+
);
|
|
58
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { SaveStatus } from "@k2b/ui";
|
|
2
|
+
import { settingsSaveControls } from "./settings-draft";
|
|
3
|
+
|
|
4
|
+
/** Status, Discard and Save in the head of a form level. Until the form registers, it shows "Saved" with both buttons disabled. */
|
|
5
|
+
export default function SettingsSaveStatus() {
|
|
6
|
+
const controls = settingsSaveControls;
|
|
7
|
+
return (
|
|
8
|
+
<SaveStatus
|
|
9
|
+
state={() => controls()?.state ?? "saved"}
|
|
10
|
+
savedAt={() => controls()?.savedAt}
|
|
11
|
+
invalidCount={() => controls()?.invalidCount ?? 1}
|
|
12
|
+
onSave={() => controls()?.save()}
|
|
13
|
+
onDiscard={() => controls()?.discard()}
|
|
14
|
+
/>
|
|
15
|
+
);
|
|
16
|
+
}
|