@fungi.computer/tourtois 0.0.0-stage → 0.0.1
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/LICENSE +21 -0
- package/README.md +127 -2
- package/dist/AgentMcpServers.d.ts +34 -0
- package/dist/AgentMcpServers.js +124 -0
- package/dist/AppAccessConsent.d.ts +23 -0
- package/dist/AppAccessConsent.js +122 -0
- package/dist/AppCallPermissions.d.ts +9 -0
- package/dist/AppCallPermissions.js +21 -0
- package/dist/AppDesktopSettings.d.ts +11 -0
- package/dist/AppDesktopSettings.js +102 -0
- package/dist/AppPane.d.ts +37 -0
- package/dist/AppPane.js +184 -0
- package/dist/AppWorkbench.d.ts +44 -0
- package/dist/AppWorkbench.js +575 -0
- package/dist/AvatarPicker.d.ts +7 -0
- package/dist/AvatarPicker.js +78 -0
- package/dist/AvatarPreview.d.ts +10 -0
- package/dist/AvatarPreview.js +6 -0
- package/dist/DesktopMenuBar.d.ts +18 -0
- package/dist/DesktopMenuBar.js +23 -0
- package/dist/FocusedAppMenus.d.ts +2 -0
- package/dist/FocusedAppMenus.js +10 -0
- package/dist/Keys.d.ts +131 -0
- package/dist/Keys.js +201 -0
- package/dist/MushroomMark.d.ts +2 -0
- package/dist/MushroomMark.js +5 -0
- package/dist/SettingsHome.d.ts +18 -0
- package/dist/SettingsHome.js +48 -0
- package/dist/SettingsNavigation.d.ts +17 -0
- package/dist/SettingsNavigation.js +153 -0
- package/dist/SystemSettings.d.ts +24 -0
- package/dist/SystemSettings.js +177 -0
- package/dist/TeamDesktop.d.ts +36 -0
- package/dist/TeamDesktop.js +39 -0
- package/dist/WorkbenchAppBar.d.ts +5 -0
- package/dist/WorkbenchAppBar.js +12 -0
- package/dist/app-authority.d.ts +150 -0
- package/dist/app-authority.js +41 -0
- package/dist/app-directory-reconciliation.d.ts +14 -0
- package/dist/app-directory-reconciliation.js +72 -0
- package/dist/app-directory.d.ts +206 -0
- package/dist/app-directory.js +21 -0
- package/dist/app-icon.d.ts +9 -0
- package/dist/app-icon.js +27 -0
- package/dist/app-launch.d.ts +53 -0
- package/dist/app-launch.js +204 -0
- package/dist/app-menus.d.ts +6 -0
- package/dist/app-menus.js +81 -0
- package/dist/app-open-url.d.ts +8 -0
- package/dist/app-open-url.js +63 -0
- package/dist/app-surface.d.ts +62 -0
- package/dist/app-surface.js +10 -0
- package/dist/app-windows.d.ts +107 -0
- package/dist/app-windows.js +21 -0
- package/dist/chrome.css +153 -0
- package/dist/command-dialogs.d.ts +42 -0
- package/dist/command-dialogs.js +194 -0
- package/dist/desktop/Desktop.d.ts +34 -0
- package/dist/desktop/Desktop.js +275 -0
- package/dist/desktop/consent.d.ts +4 -0
- package/dist/desktop/consent.js +64 -0
- package/dist/desktop/gill-frame.d.ts +14 -0
- package/dist/desktop/gill-frame.js +41 -0
- package/dist/desktop/gill-param-picker.d.ts +15 -0
- package/dist/desktop/gill-param-picker.js +145 -0
- package/dist/desktop/launcher-gill.d.ts +20 -0
- package/dist/desktop/launcher-gill.js +49 -0
- package/dist/desktop/own-launchers.d.ts +9 -0
- package/dist/desktop/own-launchers.js +9 -0
- package/dist/desktop/row-gill.d.ts +24 -0
- package/dist/desktop/row-gill.js +136 -0
- package/dist/desktop-api.d.ts +63 -0
- package/dist/desktop-api.js +31 -0
- package/dist/desktop-client.d.ts +19 -0
- package/dist/desktop-client.js +127 -0
- package/dist/desktop-layout.d.ts +117 -0
- package/dist/desktop-layout.js +305 -0
- package/dist/desktop-visibility.d.ts +8 -0
- package/dist/desktop-visibility.js +5 -0
- package/dist/layers.d.ts +5 -0
- package/dist/layers.js +31 -0
- package/dist/main.d.ts +2 -0
- package/dist/main.js +17 -0
- package/dist/member-avatar-cache.d.ts +15 -0
- package/dist/member-avatar-cache.js +69 -0
- package/dist/member-avatar.d.ts +39 -0
- package/dist/member-avatar.js +88 -0
- package/dist/menu-bar.d.ts +5 -0
- package/dist/menu-bar.js +5 -0
- package/dist/model.d.ts +6 -0
- package/dist/model.js +13 -0
- package/dist/oyster-platform-controls.d.ts +4 -0
- package/dist/oyster-platform-controls.js +107 -0
- package/dist/platform-controls.d.ts +84 -0
- package/dist/platform-controls.js +266 -0
- package/dist/queries.d.ts +7 -0
- package/dist/queries.js +7 -0
- package/dist/session-registry.d.ts +16 -0
- package/dist/session-registry.js +69 -0
- package/dist/session-selection.d.ts +6 -0
- package/dist/session-selection.js +22 -0
- package/dist/settings-leave-guard.d.ts +5 -0
- package/dist/settings-leave-guard.js +9 -0
- package/dist/settings-navigation.css +129 -0
- package/dist/settings-routes.d.ts +118 -0
- package/dist/settings-routes.js +216 -0
- package/dist/settings-sections.d.ts +74 -0
- package/dist/settings-sections.js +17 -0
- package/dist/shell-commands.d.ts +15 -0
- package/dist/shell-commands.js +95 -0
- package/dist/shell-host.d.ts +52 -0
- package/dist/shell-host.js +224 -0
- package/dist/shell-module.d.ts +12 -0
- package/dist/shell-module.js +17 -0
- package/dist/shell-release.d.ts +3 -0
- package/dist/shell-release.js +4 -0
- package/dist/shell.css +858 -0
- package/dist/shell.d.ts +6 -0
- package/dist/shell.js +83 -0
- package/dist/spa.css +11 -0
- package/dist/status-bar.d.ts +9 -0
- package/dist/status-bar.js +51 -0
- package/dist/team-directory.d.ts +3 -0
- package/dist/team-directory.js +6 -0
- package/dist/use-window-motion.d.ts +3 -0
- package/dist/use-window-motion.js +37 -0
- package/dist/useAppOpenUrlHandoff.d.ts +11 -0
- package/dist/useAppOpenUrlHandoff.js +62 -0
- package/dist/useAppWindowManager.d.ts +323 -0
- package/dist/useAppWindowManager.js +47 -0
- package/dist/useDirectoryCommands.d.ts +12 -0
- package/dist/useDirectoryCommands.js +80 -0
- package/dist/workbench.d.ts +3 -0
- package/dist/workbench.js +2 -0
- package/package.json +222 -3
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { type BentoPlacement, type BentoOp } from "@fungi.computer/maosaico/bento";
|
|
3
|
+
import { type AppGillDesktopSize, type AppGillParam } from "@fungi.computer/gills";
|
|
4
|
+
export declare const desktopLayoutSchema: z.ZodObject<{
|
|
5
|
+
version: z.ZodLiteral<1>;
|
|
6
|
+
items: z.ZodArray<z.ZodObject<{
|
|
7
|
+
id: z.ZodString;
|
|
8
|
+
surfaceId: z.ZodString;
|
|
9
|
+
gillId: z.ZodString;
|
|
10
|
+
size: z.ZodEnum<{
|
|
11
|
+
S: "S";
|
|
12
|
+
M: "M";
|
|
13
|
+
L: "L";
|
|
14
|
+
}>;
|
|
15
|
+
col: z.ZodNumber;
|
|
16
|
+
row: z.ZodNumber;
|
|
17
|
+
params: z.ZodExactOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
|
|
18
|
+
}, z.core.$strict>>;
|
|
19
|
+
launcher: z.ZodObject<{
|
|
20
|
+
pinned: z.ZodArray<z.ZodString>;
|
|
21
|
+
hidden: z.ZodArray<z.ZodString>;
|
|
22
|
+
}, z.core.$strict>;
|
|
23
|
+
}, z.core.$strict>;
|
|
24
|
+
export type DesktopLayout = z.output<typeof desktopLayoutSchema>;
|
|
25
|
+
export type DesktopGill = Readonly<{
|
|
26
|
+
surfaceId: string;
|
|
27
|
+
gillId: string;
|
|
28
|
+
title: string;
|
|
29
|
+
sizes: readonly AppGillDesktopSize[];
|
|
30
|
+
kind: "app" | "launcher";
|
|
31
|
+
params?: readonly AppGillParam[] | undefined;
|
|
32
|
+
}>;
|
|
33
|
+
export declare const desktopArrangeSchema: z.ZodObject<{
|
|
34
|
+
scope: z.ZodEnum<{
|
|
35
|
+
member: "member";
|
|
36
|
+
team: "team";
|
|
37
|
+
}>;
|
|
38
|
+
basedOn: z.ZodNullable<z.ZodNumber>;
|
|
39
|
+
ops: z.ZodArray<z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
40
|
+
kind: z.ZodLiteral<"add">;
|
|
41
|
+
surfaceId: z.ZodString;
|
|
42
|
+
gillId: z.ZodString;
|
|
43
|
+
size: z.ZodEnum<{
|
|
44
|
+
S: "S";
|
|
45
|
+
M: "M";
|
|
46
|
+
L: "L";
|
|
47
|
+
}>;
|
|
48
|
+
params: z.ZodExactOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
|
|
49
|
+
at: z.ZodOptional<z.ZodObject<{
|
|
50
|
+
col: z.ZodNumber;
|
|
51
|
+
row: z.ZodNumber;
|
|
52
|
+
}, z.core.$strict>>;
|
|
53
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
54
|
+
kind: z.ZodLiteral<"move">;
|
|
55
|
+
id: z.ZodString;
|
|
56
|
+
to: z.ZodObject<{
|
|
57
|
+
col: z.ZodNumber;
|
|
58
|
+
row: z.ZodNumber;
|
|
59
|
+
}, z.core.$strict>;
|
|
60
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
61
|
+
kind: z.ZodLiteral<"resize">;
|
|
62
|
+
id: z.ZodString;
|
|
63
|
+
size: z.ZodEnum<{
|
|
64
|
+
S: "S";
|
|
65
|
+
M: "M";
|
|
66
|
+
L: "L";
|
|
67
|
+
}>;
|
|
68
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
69
|
+
kind: z.ZodLiteral<"remove">;
|
|
70
|
+
id: z.ZodString;
|
|
71
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
72
|
+
kind: z.ZodLiteral<"reset">;
|
|
73
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
74
|
+
kind: z.ZodLiteral<"launcher">;
|
|
75
|
+
id: z.ZodString;
|
|
76
|
+
state: z.ZodEnum<{
|
|
77
|
+
Shown: "Shown";
|
|
78
|
+
Pinned: "Pinned";
|
|
79
|
+
Hidden: "Hidden";
|
|
80
|
+
}>;
|
|
81
|
+
}, z.core.$strict>], "kind">>;
|
|
82
|
+
}, z.core.$strict>;
|
|
83
|
+
export type DesktopArrange = z.output<typeof desktopArrangeSchema>;
|
|
84
|
+
export declare const desktopArrangeResultSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
85
|
+
kind: z.ZodLiteral<"accepted">;
|
|
86
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
87
|
+
kind: z.ZodLiteral<"rejected">;
|
|
88
|
+
code: z.ZodEnum<{
|
|
89
|
+
invalid: "invalid";
|
|
90
|
+
owner_required: "owner_required";
|
|
91
|
+
unknown_gill: "unknown_gill";
|
|
92
|
+
unknown_app: "unknown_app";
|
|
93
|
+
size_not_allowed: "size_not_allowed";
|
|
94
|
+
unknown_item: "unknown_item";
|
|
95
|
+
too_many: "too_many";
|
|
96
|
+
conflict: "conflict";
|
|
97
|
+
too_large: "too_large";
|
|
98
|
+
quota_exceeded: "quota_exceeded";
|
|
99
|
+
params_invalid: "params_invalid";
|
|
100
|
+
}>;
|
|
101
|
+
}, z.core.$strict>], "kind">;
|
|
102
|
+
export type DesktopArrangeResult = z.output<typeof desktopArrangeResultSchema>;
|
|
103
|
+
/** Only the selected Gill's declared keys may be persisted. */
|
|
104
|
+
export declare function validDesktopGillParams(params: Readonly<Record<string, string>> | undefined, declared?: readonly AppGillParam[]): boolean;
|
|
105
|
+
export declare function desktopPlacements(layout: DesktopLayout): readonly BentoPlacement[];
|
|
106
|
+
export declare function compactDesktopLayout(layout: DesktopLayout): DesktopLayout;
|
|
107
|
+
export declare function availableDesktopLayout(layout: DesktopLayout, catalog: readonly DesktopGill[]): DesktopLayout;
|
|
108
|
+
export declare function initialDesktopLayout(catalog: readonly DesktopGill[], inboxSurfaceId: string | null): DesktopLayout;
|
|
109
|
+
/** Apply one atomic operation batch without reading or writing storage. */
|
|
110
|
+
export declare function applyDesktopOps(current: DesktopLayout, ops: DesktopArrange["ops"], catalog: readonly DesktopGill[], launchableAppIds: readonly string[], createId: () => string): {
|
|
111
|
+
kind: "accepted";
|
|
112
|
+
layout: DesktopLayout | null;
|
|
113
|
+
} | Extract<DesktopArrangeResult, {
|
|
114
|
+
kind: "rejected";
|
|
115
|
+
}>;
|
|
116
|
+
/** Responsive cells are a projection; only the relative intent reaches the wide layout. */
|
|
117
|
+
export declare function desktopBentoIntent(layout: DesktopLayout, projected: readonly BentoPlacement[], op: BentoOp): DesktopArrange["ops"][number] | null;
|
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { applyBentoOps, arrangeBento, } from "@fungi.computer/maosaico/bento";
|
|
3
|
+
import { gillParamsSchema, } from "@fungi.computer/gills";
|
|
4
|
+
import * as Schema from "effect/Schema";
|
|
5
|
+
/** Stored placement values use the maintained Gill grammar. */
|
|
6
|
+
const desktopGillParamsSchema = z
|
|
7
|
+
.record(z.string().min(1).max(64), z.string().max(256))
|
|
8
|
+
.refine((value) => Schema.is(gillParamsSchema)(value));
|
|
9
|
+
const identity = z.string().min(1).max(513);
|
|
10
|
+
const cell = z
|
|
11
|
+
.number()
|
|
12
|
+
.int()
|
|
13
|
+
.nonnegative()
|
|
14
|
+
.max(Number.MAX_SAFE_INTEGER - 2);
|
|
15
|
+
const size = z.enum(["S", "M", "L"]);
|
|
16
|
+
const launcherIds = z
|
|
17
|
+
.array(identity)
|
|
18
|
+
.max(128)
|
|
19
|
+
.refine((ids) => new Set(ids).size === ids.length, "App identities must be unique");
|
|
20
|
+
const uniqueIds = (items) => new Set(items.map((item) => item.id)).size === items.length;
|
|
21
|
+
export const desktopLayoutSchema = z.strictObject({
|
|
22
|
+
version: z.literal(1),
|
|
23
|
+
items: z
|
|
24
|
+
.array(z.strictObject({
|
|
25
|
+
id: identity,
|
|
26
|
+
surfaceId: identity,
|
|
27
|
+
gillId: identity,
|
|
28
|
+
size,
|
|
29
|
+
col: cell.max(3),
|
|
30
|
+
row: cell.max(47),
|
|
31
|
+
params: desktopGillParamsSchema.exactOptional(),
|
|
32
|
+
}))
|
|
33
|
+
.max(24)
|
|
34
|
+
.refine(uniqueIds, "Gill identities must be unique"),
|
|
35
|
+
launcher: z
|
|
36
|
+
.strictObject({ pinned: launcherIds, hidden: launcherIds })
|
|
37
|
+
.refine((value) => value.pinned.every((id) => !value.hidden.includes(id)), "Pinned Apps cannot be hidden"),
|
|
38
|
+
});
|
|
39
|
+
export const desktopArrangeSchema = z.strictObject({
|
|
40
|
+
scope: z.enum(["member", "team"]),
|
|
41
|
+
basedOn: z.number().int().positive().nullable(),
|
|
42
|
+
ops: z
|
|
43
|
+
.array(z.discriminatedUnion("kind", [
|
|
44
|
+
z.strictObject({
|
|
45
|
+
kind: z.literal("add"),
|
|
46
|
+
surfaceId: identity,
|
|
47
|
+
gillId: identity,
|
|
48
|
+
size,
|
|
49
|
+
params: desktopGillParamsSchema.exactOptional(),
|
|
50
|
+
at: z
|
|
51
|
+
.strictObject({ col: cell.max(3), row: cell.max(47) })
|
|
52
|
+
.optional(),
|
|
53
|
+
}),
|
|
54
|
+
z.strictObject({
|
|
55
|
+
kind: z.literal("move"),
|
|
56
|
+
id: identity,
|
|
57
|
+
to: z.strictObject({ col: cell.max(3), row: cell.max(47) }),
|
|
58
|
+
}),
|
|
59
|
+
z.strictObject({ kind: z.literal("resize"), id: identity, size }),
|
|
60
|
+
z.strictObject({ kind: z.literal("remove"), id: identity }),
|
|
61
|
+
z.strictObject({ kind: z.literal("reset") }),
|
|
62
|
+
z.strictObject({
|
|
63
|
+
kind: z.literal("launcher"),
|
|
64
|
+
id: identity,
|
|
65
|
+
state: z.enum(["Shown", "Pinned", "Hidden"]),
|
|
66
|
+
}),
|
|
67
|
+
]))
|
|
68
|
+
.max(24),
|
|
69
|
+
});
|
|
70
|
+
export const desktopArrangeResultSchema = z.discriminatedUnion("kind", [
|
|
71
|
+
z.strictObject({ kind: z.literal("accepted") }),
|
|
72
|
+
z.strictObject({
|
|
73
|
+
kind: z.literal("rejected"),
|
|
74
|
+
code: z.enum([
|
|
75
|
+
"invalid",
|
|
76
|
+
"owner_required",
|
|
77
|
+
"unknown_gill",
|
|
78
|
+
"unknown_app",
|
|
79
|
+
"size_not_allowed",
|
|
80
|
+
"unknown_item",
|
|
81
|
+
"too_many",
|
|
82
|
+
"conflict",
|
|
83
|
+
"too_large",
|
|
84
|
+
"quota_exceeded",
|
|
85
|
+
"params_invalid",
|
|
86
|
+
]),
|
|
87
|
+
}),
|
|
88
|
+
]);
|
|
89
|
+
/** Only the selected Gill's declared keys may be persisted. */
|
|
90
|
+
export function validDesktopGillParams(params, declared = []) {
|
|
91
|
+
const values = params ?? {};
|
|
92
|
+
return (Object.keys(values).every((key) => declared.some((param) => param.key === key)) &&
|
|
93
|
+
declared.every((param) => !param.required ||
|
|
94
|
+
(Object.hasOwn(values, param.key) && Boolean(values[param.key]))));
|
|
95
|
+
}
|
|
96
|
+
export function desktopPlacements(layout) {
|
|
97
|
+
return layout.items.map((item) => ({
|
|
98
|
+
id: item.id,
|
|
99
|
+
col: item.col,
|
|
100
|
+
row: item.row,
|
|
101
|
+
w: item.size === "S" ? 1 : 2,
|
|
102
|
+
h: item.size === "L" ? 2 : 1,
|
|
103
|
+
}));
|
|
104
|
+
}
|
|
105
|
+
export function compactDesktopLayout(layout) {
|
|
106
|
+
const cells = arrangeBento(desktopPlacements(layout), 4);
|
|
107
|
+
return {
|
|
108
|
+
...layout,
|
|
109
|
+
items: layout.items.map((item) => {
|
|
110
|
+
const placement = cells.find((position) => position.id === item.id);
|
|
111
|
+
return {
|
|
112
|
+
...item,
|
|
113
|
+
col: placement?.col ?? item.col,
|
|
114
|
+
row: placement?.row ?? item.row,
|
|
115
|
+
};
|
|
116
|
+
}),
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
export function availableDesktopLayout(layout, catalog) {
|
|
120
|
+
return {
|
|
121
|
+
...layout,
|
|
122
|
+
items: layout.items.filter((item) => catalog.some((gill) => gill.surfaceId === item.surfaceId &&
|
|
123
|
+
gill.gillId === item.gillId &&
|
|
124
|
+
gill.sizes.includes(item.size))),
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
export function initialDesktopLayout(catalog, inboxSurfaceId) {
|
|
128
|
+
const inbox = catalog.find((gill) => gill.kind === "app" &&
|
|
129
|
+
gill.surfaceId === inboxSurfaceId &&
|
|
130
|
+
gill.sizes.includes("L"));
|
|
131
|
+
return {
|
|
132
|
+
version: 1,
|
|
133
|
+
items: [
|
|
134
|
+
...(inbox
|
|
135
|
+
? [
|
|
136
|
+
{
|
|
137
|
+
id: "inbox",
|
|
138
|
+
surfaceId: inbox.surfaceId,
|
|
139
|
+
gillId: inbox.gillId,
|
|
140
|
+
size: "L",
|
|
141
|
+
col: 0,
|
|
142
|
+
row: 0,
|
|
143
|
+
},
|
|
144
|
+
]
|
|
145
|
+
: []),
|
|
146
|
+
{
|
|
147
|
+
id: "launcher",
|
|
148
|
+
surfaceId: "tourtois",
|
|
149
|
+
gillId: "launcher",
|
|
150
|
+
size: "M",
|
|
151
|
+
col: inbox ? 2 : 0,
|
|
152
|
+
row: 0,
|
|
153
|
+
},
|
|
154
|
+
],
|
|
155
|
+
launcher: { pinned: [], hidden: [] },
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
/** Apply one atomic operation batch without reading or writing storage. */
|
|
159
|
+
export function applyDesktopOps(current, ops, catalog, launchableAppIds, createId) {
|
|
160
|
+
let layout = current;
|
|
161
|
+
for (const op of ops) {
|
|
162
|
+
if (op.kind === "reset") {
|
|
163
|
+
if (ops.length !== 1)
|
|
164
|
+
return { kind: "rejected", code: "invalid" };
|
|
165
|
+
continue;
|
|
166
|
+
}
|
|
167
|
+
if (op.kind === "launcher") {
|
|
168
|
+
if (!launchableAppIds.includes(op.id))
|
|
169
|
+
return { kind: "rejected", code: "unknown_app" };
|
|
170
|
+
layout = {
|
|
171
|
+
...layout,
|
|
172
|
+
launcher: {
|
|
173
|
+
pinned: [
|
|
174
|
+
...layout.launcher.pinned.filter((id) => id !== op.id),
|
|
175
|
+
...(op.state === "Pinned" ? [op.id] : []),
|
|
176
|
+
],
|
|
177
|
+
hidden: [
|
|
178
|
+
...layout.launcher.hidden.filter((id) => id !== op.id),
|
|
179
|
+
...(op.state === "Hidden" ? [op.id] : []),
|
|
180
|
+
],
|
|
181
|
+
},
|
|
182
|
+
};
|
|
183
|
+
continue;
|
|
184
|
+
}
|
|
185
|
+
if (op.kind === "add") {
|
|
186
|
+
const gill = catalog.find((entry) => entry.surfaceId === op.surfaceId && entry.gillId === op.gillId);
|
|
187
|
+
if (!gill)
|
|
188
|
+
return { kind: "rejected", code: "unknown_gill" };
|
|
189
|
+
if (!gill.sizes.includes(op.size))
|
|
190
|
+
return { kind: "rejected", code: "size_not_allowed" };
|
|
191
|
+
if (!validDesktopGillParams(op.params, gill.params))
|
|
192
|
+
return { kind: "rejected", code: "params_invalid" };
|
|
193
|
+
if (layout.items.length >= 24)
|
|
194
|
+
return { kind: "rejected", code: "too_many" };
|
|
195
|
+
const item = {
|
|
196
|
+
id: createId(),
|
|
197
|
+
surfaceId: op.surfaceId,
|
|
198
|
+
gillId: op.gillId,
|
|
199
|
+
size: op.size,
|
|
200
|
+
col: op.at?.col ?? 0,
|
|
201
|
+
row: op.at?.row ?? 0,
|
|
202
|
+
};
|
|
203
|
+
if (op.params !== undefined)
|
|
204
|
+
item.params = op.params;
|
|
205
|
+
layout = compactDesktopLayout({
|
|
206
|
+
...layout,
|
|
207
|
+
items: [...layout.items, item],
|
|
208
|
+
});
|
|
209
|
+
continue;
|
|
210
|
+
}
|
|
211
|
+
const item = layout.items.find((entry) => entry.id === op.id);
|
|
212
|
+
if (!item)
|
|
213
|
+
return { kind: "rejected", code: "unknown_item" };
|
|
214
|
+
if (op.kind === "remove") {
|
|
215
|
+
layout = compactDesktopLayout({
|
|
216
|
+
...layout,
|
|
217
|
+
items: layout.items.filter((entry) => entry.id !== op.id),
|
|
218
|
+
});
|
|
219
|
+
continue;
|
|
220
|
+
}
|
|
221
|
+
if (op.kind === "resize") {
|
|
222
|
+
const gill = catalog.find((entry) => entry.surfaceId === item.surfaceId && entry.gillId === item.gillId);
|
|
223
|
+
if (!gill?.sizes.includes(op.size))
|
|
224
|
+
return { kind: "rejected", code: "size_not_allowed" };
|
|
225
|
+
}
|
|
226
|
+
const cells = applyBentoOps(desktopPlacements(layout), [
|
|
227
|
+
op.kind === "move"
|
|
228
|
+
? { kind: "move", id: op.id, col: op.to.col, row: op.to.row }
|
|
229
|
+
: {
|
|
230
|
+
kind: "resize",
|
|
231
|
+
id: op.id,
|
|
232
|
+
w: op.size === "S" ? 1 : 2,
|
|
233
|
+
h: op.size === "L" ? 2 : 1,
|
|
234
|
+
},
|
|
235
|
+
], 4);
|
|
236
|
+
layout = {
|
|
237
|
+
...layout,
|
|
238
|
+
items: layout.items.map((entry) => {
|
|
239
|
+
const position = cells.find((cell) => cell.id === entry.id);
|
|
240
|
+
return {
|
|
241
|
+
...entry,
|
|
242
|
+
size: op.kind === "resize" && entry.id === op.id ? op.size : entry.size,
|
|
243
|
+
col: position?.col ?? entry.col,
|
|
244
|
+
row: position?.row ?? entry.row,
|
|
245
|
+
};
|
|
246
|
+
}),
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
const parsed = desktopLayoutSchema.safeParse(layout);
|
|
250
|
+
if (!parsed.success)
|
|
251
|
+
return { kind: "rejected", code: "invalid" };
|
|
252
|
+
return {
|
|
253
|
+
kind: "accepted",
|
|
254
|
+
layout: ops[0]?.kind === "reset" ? null : parsed.data,
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
/** Responsive cells are a projection; only the relative intent reaches the wide layout. */
|
|
258
|
+
export function desktopBentoIntent(layout, projected, op) {
|
|
259
|
+
const item = layout.items.find((item) => item.id === op.id);
|
|
260
|
+
const shown = projected.find((item) => item.id === op.id);
|
|
261
|
+
if (!item || !shown)
|
|
262
|
+
return null;
|
|
263
|
+
if (op.kind === "resize")
|
|
264
|
+
return {
|
|
265
|
+
kind: "resize",
|
|
266
|
+
id: op.id,
|
|
267
|
+
size: op.h >= 2 ? "L" : op.w >= 2 ? "M" : "S",
|
|
268
|
+
};
|
|
269
|
+
// A phone row crossing is an ordering intent between tiles. Translate it to
|
|
270
|
+
// the neighbour's canonical cell; writing the phone cell would erase the wide layout.
|
|
271
|
+
const reflowed = projected.some((cell) => {
|
|
272
|
+
const canonical = layout.items.find((item) => item.id === cell.id);
|
|
273
|
+
return (canonical && (canonical.col !== cell.col || canonical.row !== cell.row));
|
|
274
|
+
});
|
|
275
|
+
if (reflowed && op.row !== shown.row) {
|
|
276
|
+
const neighbours = projected
|
|
277
|
+
.filter((other) => other.id !== op.id &&
|
|
278
|
+
op.col < other.col + other.w &&
|
|
279
|
+
op.col + shown.w > other.col &&
|
|
280
|
+
op.row < other.row + other.h &&
|
|
281
|
+
op.row + shown.h > other.row)
|
|
282
|
+
.sort((a, b) => Math.abs(a.row - shown.row) - Math.abs(b.row - shown.row));
|
|
283
|
+
const neighbour = neighbours[0];
|
|
284
|
+
const canonical = layout.items.find((item) => item.id === neighbour?.id);
|
|
285
|
+
if (canonical && neighbour)
|
|
286
|
+
return {
|
|
287
|
+
kind: "move",
|
|
288
|
+
id: op.id,
|
|
289
|
+
to: {
|
|
290
|
+
col: canonical.col,
|
|
291
|
+
row: op.row < shown.row
|
|
292
|
+
? canonical.row
|
|
293
|
+
: canonical.row + Math.max(1, neighbour.h - shown.h),
|
|
294
|
+
},
|
|
295
|
+
};
|
|
296
|
+
}
|
|
297
|
+
return {
|
|
298
|
+
kind: "move",
|
|
299
|
+
id: op.id,
|
|
300
|
+
to: {
|
|
301
|
+
col: Math.max(0, Math.min(3, item.col + op.col - shown.col)),
|
|
302
|
+
row: Math.max(0, item.row + op.row - shown.row),
|
|
303
|
+
},
|
|
304
|
+
};
|
|
305
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/** The desktop stays mounted; layout determines whether it can receive input. */
|
|
2
|
+
export type DesktopVisibility = "revealed" | "partial" | "covered";
|
|
3
|
+
export declare function desktopVisibility({ hasVisibleWindows, mode, zoomedId, phone, }: {
|
|
4
|
+
hasVisibleWindows: boolean;
|
|
5
|
+
mode: "tiled" | "floating";
|
|
6
|
+
zoomedId: string | null;
|
|
7
|
+
phone: boolean;
|
|
8
|
+
}): DesktopVisibility;
|
package/dist/layers.d.ts
ADDED
package/dist/layers.js
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { OverlayRoot } from "@fungi.computer/caps/components/overlay-root";
|
|
3
|
+
import { MaosaicoLayers, useLayer } from "@fungi.computer/maosaico";
|
|
4
|
+
import { useMemo } from "react";
|
|
5
|
+
function OverlayContainers({ children, subscribeDismiss, }) {
|
|
6
|
+
const popover = useLayer("popover");
|
|
7
|
+
const dialog = useLayer("dialog");
|
|
8
|
+
const toast = useLayer("toast");
|
|
9
|
+
const tooltip = useLayer("tooltip");
|
|
10
|
+
const containers = useMemo(() => ({ popover, dialog, toast, tooltip }), [popover, dialog, toast, tooltip]);
|
|
11
|
+
return (_jsx(OverlayRoot, { containers: containers, subscribeDismiss: subscribeDismiss, children: children }));
|
|
12
|
+
}
|
|
13
|
+
/** Tourtois composes Caps portal destinations with Maosaico's ordered roots. */
|
|
14
|
+
export function TourtoisLayers({ children }) {
|
|
15
|
+
const dismissal = useMemo(() => {
|
|
16
|
+
const listeners = new Set();
|
|
17
|
+
return {
|
|
18
|
+
subscribe: (listener) => {
|
|
19
|
+
listeners.add(listener);
|
|
20
|
+
return () => {
|
|
21
|
+
listeners.delete(listener);
|
|
22
|
+
};
|
|
23
|
+
},
|
|
24
|
+
dismiss: () => {
|
|
25
|
+
for (const listener of listeners)
|
|
26
|
+
listener();
|
|
27
|
+
},
|
|
28
|
+
};
|
|
29
|
+
}, []);
|
|
30
|
+
return (_jsx(MaosaicoLayers, { onDismiss: dismissal.dismiss, children: _jsx(OverlayContainers, { subscribeDismiss: dismissal.subscribe, children: children }) }));
|
|
31
|
+
}
|
package/dist/main.d.ts
ADDED
package/dist/main.js
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
/// <reference types="vite/client" />
|
|
3
|
+
import { createRoot } from "react-dom/client";
|
|
4
|
+
import { ResultState } from "@fungi.computer/caps/blocks/result-state";
|
|
5
|
+
import "@fungi.computer/parakeet/styles.css";
|
|
6
|
+
import "@fungi.computer/caps/fonts.css";
|
|
7
|
+
const root = document.getElementById("tourtois-root");
|
|
8
|
+
if (!root)
|
|
9
|
+
throw new Error("Tourtois root is missing");
|
|
10
|
+
// Tourtois is a shell, not a framed App: its host composes it into the host's
|
|
11
|
+
// own top-level document with escalated permissions. This Release keeps the
|
|
12
|
+
// desktop's storage and methods; its page only says where the shell lives.
|
|
13
|
+
const reactRoot = createRoot(root);
|
|
14
|
+
reactRoot.render(_jsx("main", { className: "tourtois-shell", "data-tourtois-release": true, children: _jsx(ResultState, { title: "Tourtois runs as your shell", description: "Your host serves Tourtois as its trusted home. Open your host's home page to use it." }) }));
|
|
15
|
+
import.meta.hot?.dispose(() => {
|
|
16
|
+
reactRoot.unmount();
|
|
17
|
+
});
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { type MemberAvatar } from "@fungi.computer/sprite-zdog/avatar";
|
|
2
|
+
/** Memory fallback lasts for this page; it does not claim durable storage. */
|
|
3
|
+
type AvatarCacheOutcome = {
|
|
4
|
+
readonly kind: "persistent";
|
|
5
|
+
} | {
|
|
6
|
+
readonly kind: "memory";
|
|
7
|
+
readonly stage: "read" | "write";
|
|
8
|
+
};
|
|
9
|
+
/** Guard acquisition as well as use: a sandbox may throw on the getter itself. */
|
|
10
|
+
export declare function createMemberAvatarCache(storage: () => Pick<Storage, "getItem" | "setItem">): {
|
|
11
|
+
snapshot: () => MemberAvatar;
|
|
12
|
+
write: (next: MemberAvatar) => AvatarCacheOutcome;
|
|
13
|
+
subscribe: (listener: () => void) => () => void;
|
|
14
|
+
};
|
|
15
|
+
export {};
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import * as Schema from "effect/Schema";
|
|
2
|
+
import * as Option from "effect/Option";
|
|
3
|
+
import { defaultMemberAvatar, memberAvatarSchema, } from "@fungi.computer/sprite-zdog/avatar";
|
|
4
|
+
const storageKey = "fungi.member.avatar.v1";
|
|
5
|
+
/** Guard acquisition as well as use: a sandbox may throw on the getter itself. */
|
|
6
|
+
export function createMemberAvatarCache(storage) {
|
|
7
|
+
const listeners = new Set();
|
|
8
|
+
let outcome = { kind: "persistent" };
|
|
9
|
+
let cachedRaw;
|
|
10
|
+
let cached = defaultMemberAvatar;
|
|
11
|
+
function snapshot() {
|
|
12
|
+
if (outcome.kind === "memory")
|
|
13
|
+
return cached;
|
|
14
|
+
let raw;
|
|
15
|
+
try {
|
|
16
|
+
raw = storage().getItem(storageKey);
|
|
17
|
+
}
|
|
18
|
+
catch (cause) {
|
|
19
|
+
outcome = { kind: "memory", stage: "read" };
|
|
20
|
+
console.warn("Member avatar using memory cache", outcome, cause);
|
|
21
|
+
return cached;
|
|
22
|
+
}
|
|
23
|
+
if (raw === cachedRaw)
|
|
24
|
+
return cached;
|
|
25
|
+
cachedRaw = raw;
|
|
26
|
+
const decoded = Schema.decodeUnknownOption(Schema.fromJsonString(Schema.Unknown))(raw);
|
|
27
|
+
const parsed = Option.isSome(decoded)
|
|
28
|
+
? memberAvatarSchema.safeParse(decoded.value)
|
|
29
|
+
: undefined;
|
|
30
|
+
cached = parsed?.success ? parsed.data : defaultMemberAvatar;
|
|
31
|
+
return cached;
|
|
32
|
+
}
|
|
33
|
+
function write(next) {
|
|
34
|
+
const value = memberAvatarSchema.parse(next);
|
|
35
|
+
const raw = JSON.stringify(value);
|
|
36
|
+
snapshot();
|
|
37
|
+
if (raw === cachedRaw)
|
|
38
|
+
return outcome;
|
|
39
|
+
if (outcome.kind === "persistent") {
|
|
40
|
+
try {
|
|
41
|
+
storage().setItem(storageKey, raw);
|
|
42
|
+
}
|
|
43
|
+
catch (cause) {
|
|
44
|
+
outcome = { kind: "memory", stage: "write" };
|
|
45
|
+
console.warn("Member avatar using memory cache", outcome, cause);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
cachedRaw = raw;
|
|
49
|
+
cached = value;
|
|
50
|
+
for (const listener of listeners)
|
|
51
|
+
listener();
|
|
52
|
+
return outcome;
|
|
53
|
+
}
|
|
54
|
+
function subscribe(listener) {
|
|
55
|
+
listeners.add(listener);
|
|
56
|
+
const onStorage = (event) => {
|
|
57
|
+
if (outcome.kind === "persistent" &&
|
|
58
|
+
(event.key === storageKey || event.key === null)) {
|
|
59
|
+
listener();
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
window.addEventListener("storage", onStorage);
|
|
63
|
+
return () => {
|
|
64
|
+
listeners.delete(listener);
|
|
65
|
+
window.removeEventListener("storage", onStorage);
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
return { snapshot, write, subscribe };
|
|
69
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { type MemberAvatar } from "@fungi.computer/sprite-zdog/avatar";
|
|
2
|
+
import type { RenderStyle } from "@fungi.computer/sprite";
|
|
3
|
+
import { type CSSProperties, type ReactNode } from "react";
|
|
4
|
+
import type { WhistleJsonValue } from "@fungi.computer/whistle";
|
|
5
|
+
/** Decode the host-granted avatar preference. */
|
|
6
|
+
export declare const parseMemberAvatar: (value: WhistleJsonValue) => MemberAvatar | null;
|
|
7
|
+
/** Account edits and host settings changes invalidate this query. */
|
|
8
|
+
export declare const accountAvatarQuery: readonly ["tourtois", "account-avatar"];
|
|
9
|
+
/** Save the person's avatar through the host's account; returns the saved value. */
|
|
10
|
+
export type MemberAvatarSave = (next: MemberAvatar) => Promise<MemberAvatar>;
|
|
11
|
+
/** Read the person's saved avatar, parsed by the reader. */
|
|
12
|
+
export type MemberAvatarReader = (signal: AbortSignal) => Promise<MemberAvatar | null>;
|
|
13
|
+
/**
|
|
14
|
+
* Read the avatar through a host-granted source: an isolated dashboard's
|
|
15
|
+
* capability, or the host's own account read. Without one, the sprite draws
|
|
16
|
+
* from this browser's cache.
|
|
17
|
+
*/
|
|
18
|
+
export declare function MemberAvatarSource({ read, children, }: {
|
|
19
|
+
read: MemberAvatarReader;
|
|
20
|
+
children: ReactNode;
|
|
21
|
+
}): import("react/jsx-runtime").JSX.Element;
|
|
22
|
+
/**
|
|
23
|
+
* The current avatar: the account's value once read (refreshing the
|
|
24
|
+
* cache), otherwise the cached value, updated across tabs.
|
|
25
|
+
*/
|
|
26
|
+
export declare function useMemberAvatar(): MemberAvatar;
|
|
27
|
+
/** The person's sprite as the shell presents it: saved style and appearance. */
|
|
28
|
+
export declare function OwnSprite(props: {
|
|
29
|
+
readonly activity?: string;
|
|
30
|
+
readonly expression?: string;
|
|
31
|
+
readonly className?: string;
|
|
32
|
+
readonly style?: CSSProperties;
|
|
33
|
+
readonly renderStyle?: RenderStyle;
|
|
34
|
+
readonly "aria-hidden"?: "true";
|
|
35
|
+
}): import("react/jsx-runtime").JSX.Element;
|
|
36
|
+
/** Host-rendered picker: Apps read the preference but never write account routes. */
|
|
37
|
+
export declare function AvatarSettings({ save: write }: {
|
|
38
|
+
save: MemberAvatarSave;
|
|
39
|
+
}): import("react/jsx-runtime").JSX.Element;
|