@dxos/app-framework 0.6.5 → 0.6.6-main.e1a6e1f
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/dist/lib/browser/index.mjs +69 -40
- package/dist/lib/browser/index.mjs.map +3 -3
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/node/index.cjs +76 -47
- package/dist/lib/node/index.cjs.map +3 -3
- package/dist/lib/node/meta.json +1 -1
- package/dist/types/src/plugins/PluginHost/PluginContext.d.ts.map +1 -1
- package/dist/types/src/plugins/common/graph.d.ts +30 -1
- package/dist/types/src/plugins/common/graph.d.ts.map +1 -1
- package/dist/types/src/plugins/common/navigation.d.ts +163 -43
- package/dist/types/src/plugins/common/navigation.d.ts.map +1 -1
- package/package.json +11 -11
- package/project.json +1 -1
- package/src/plugins/PluginHost/PluginContext.tsx +1 -0
- package/src/plugins/common/graph.ts +39 -1
- package/src/plugins/common/navigation.ts +115 -71
|
@@ -4,9 +4,13 @@
|
|
|
4
4
|
|
|
5
5
|
import { z } from 'zod';
|
|
6
6
|
|
|
7
|
+
import { S } from '@dxos/echo-schema';
|
|
8
|
+
|
|
7
9
|
import type { IntentData } from '../IntentPlugin';
|
|
8
10
|
import type { Plugin } from '../PluginHost';
|
|
9
11
|
|
|
12
|
+
//
|
|
13
|
+
// --- Constants --------------------------------------------------------------
|
|
10
14
|
// NOTE(thure): These are chosen from RFC 1738’s `safe` characters: http://www.faqs.org/rfcs/rfc1738.html
|
|
11
15
|
export const SLUG_LIST_SEPARATOR = '+';
|
|
12
16
|
export const SLUG_ENTRY_SEPARATOR = '_';
|
|
@@ -15,93 +19,130 @@ export const SLUG_PATH_SEPARATOR = '~';
|
|
|
15
19
|
export const SLUG_COLLECTION_INDICATOR = '';
|
|
16
20
|
export const SLUG_SOLO_INDICATOR = '$';
|
|
17
21
|
|
|
18
|
-
export const parseSlug = (slug: string): { id: string; path: string[]; solo: boolean } => {
|
|
19
|
-
const solo = slug.startsWith(SLUG_SOLO_INDICATOR);
|
|
20
|
-
const cleanSlug = solo ? slug.replace(SLUG_SOLO_INDICATOR, '') : slug;
|
|
21
|
-
const [id, ...path] = cleanSlug.split(SLUG_PATH_SEPARATOR);
|
|
22
|
-
|
|
23
|
-
return { id, path, solo };
|
|
24
|
-
};
|
|
25
|
-
|
|
26
22
|
//
|
|
27
|
-
//
|
|
28
|
-
|
|
29
|
-
|
|
23
|
+
// --- Types ------------------------------------------------------------------
|
|
24
|
+
const LayoutEntrySchema = S.mutable(
|
|
25
|
+
S.Struct({
|
|
26
|
+
id: S.String,
|
|
27
|
+
solo: S.optional(S.Boolean),
|
|
28
|
+
path: S.optional(S.String),
|
|
29
|
+
}),
|
|
30
|
+
);
|
|
31
|
+
|
|
32
|
+
export type LayoutEntry = S.Schema.Type<typeof LayoutEntrySchema>;
|
|
33
|
+
|
|
34
|
+
// TODO(Zan): Consider making solo it's own part. It's not really a function of the 'main' part?
|
|
35
|
+
// TODO(Zan): Consider renaming the 'main' part to 'deck' part now that we are throwing out the old layout plugin.
|
|
36
|
+
// TODO(Zan): Extend to all strings?
|
|
37
|
+
const LayoutPartSchema = S.Union(
|
|
38
|
+
S.Literal('sidebar'),
|
|
39
|
+
S.Literal('main'),
|
|
40
|
+
S.Literal('complementary'),
|
|
41
|
+
S.Literal('fullScreen'),
|
|
42
|
+
);
|
|
43
|
+
export type LayoutPart = S.Schema.Type<typeof LayoutPartSchema>;
|
|
44
|
+
|
|
45
|
+
const LayoutPartsSchema = S.partial(S.mutable(S.Record(LayoutPartSchema, S.mutable(S.Array(LayoutEntrySchema)))));
|
|
46
|
+
export type LayoutParts = S.Schema.Type<typeof LayoutPartsSchema>;
|
|
47
|
+
|
|
48
|
+
const LayoutCoordinateSchema = S.mutable(S.Struct({ part: LayoutPartSchema, entryId: S.String }));
|
|
49
|
+
export type LayoutCoordinate = S.Schema.Type<typeof LayoutCoordinateSchema>;
|
|
50
|
+
|
|
51
|
+
const PartAdjustmentSchema = S.Union(S.Literal('increment-start'), S.Literal('increment-end'), S.Literal('solo'));
|
|
52
|
+
export type PartAdjustment = S.Schema.Type<typeof PartAdjustmentSchema>;
|
|
53
|
+
|
|
54
|
+
const LayoutAdjustmentSchema = S.mutable(
|
|
55
|
+
S.Struct({ layoutCoordinate: LayoutCoordinateSchema, type: PartAdjustmentSchema }),
|
|
56
|
+
);
|
|
57
|
+
export type LayoutAdjustment = S.Schema.Type<typeof LayoutAdjustmentSchema>;
|
|
58
|
+
|
|
59
|
+
/** @deprecated */
|
|
30
60
|
export const ActiveParts = z.record(z.string(), z.union([z.string(), z.array(z.string())]));
|
|
61
|
+
export type ActiveParts = z.infer<typeof ActiveParts>;
|
|
31
62
|
|
|
32
63
|
/**
|
|
33
64
|
* Basic state provided by a navigation plugin.
|
|
34
65
|
*/
|
|
35
|
-
// TODO(wittjosiah): Replace Zod w/ Effect Schema to align with ECHO.
|
|
36
|
-
// TODO(wittjosiah): We should align this more with `window.location` along the lines of what React Router does.
|
|
37
|
-
export const Location = z.object({
|
|
38
|
-
active: z
|
|
39
|
-
.union([z.string(), ActiveParts])
|
|
40
|
-
.optional()
|
|
41
|
-
.describe('Id of currently active item, or record of item id(s) keyed by the app part in which they are active.'),
|
|
42
|
-
closed: z
|
|
43
|
-
.union([z.string(), z.array(z.string())])
|
|
44
|
-
.optional()
|
|
45
|
-
.describe('Id or ids of recently closed items, in order of when they were closed.'),
|
|
46
|
-
});
|
|
47
|
-
|
|
48
66
|
export const Attention = z.object({
|
|
49
67
|
attended: z.set(z.string()).optional().describe('Ids of items which have focus.'),
|
|
50
68
|
});
|
|
51
|
-
|
|
52
|
-
export type ActiveParts = z.infer<typeof ActiveParts>;
|
|
53
|
-
export type Location = z.infer<typeof Location>;
|
|
54
69
|
export type Attention = z.infer<typeof Attention>;
|
|
55
70
|
|
|
56
|
-
// QUESTION(Zan): Is fullscreen a part? Or a special case of 'main'?
|
|
57
|
-
export type LayoutPart = 'sidebar' | 'main' | 'complementary';
|
|
58
|
-
|
|
59
|
-
export type LayoutCoordinate = { part: LayoutPart; index: number; partSize: number; solo?: boolean };
|
|
60
|
-
export type NavigationAdjustmentType = `${'pin' | 'increment'}-${'start' | 'end'}`;
|
|
61
|
-
export type NavigationAdjustment = { layoutCoordinate: LayoutCoordinate; type: NavigationAdjustmentType };
|
|
62
|
-
|
|
63
|
-
export const isActiveParts = (active: string | ActiveParts | undefined): active is ActiveParts =>
|
|
64
|
-
!!active && typeof active !== 'string';
|
|
65
|
-
|
|
66
|
-
export const isAdjustTransaction = (data: IntentData | undefined): data is NavigationAdjustment =>
|
|
67
|
-
!!data &&
|
|
68
|
-
('layoutCoordinate' satisfies keyof NavigationAdjustment) in data &&
|
|
69
|
-
('type' satisfies keyof NavigationAdjustment) in data;
|
|
70
|
-
|
|
71
|
-
export const firstMainId = (active: Location['active']): string =>
|
|
72
|
-
isActiveParts(active) ? (Array.isArray(active.main) ? active.main[0] : active.main) : active ?? '';
|
|
73
|
-
|
|
74
|
-
export const activeIds = (active: string | ActiveParts | undefined): Set<string> =>
|
|
75
|
-
active
|
|
76
|
-
? isActiveParts(active)
|
|
77
|
-
? Object.values(active).reduce((acc, ids) => {
|
|
78
|
-
Array.isArray(ids) ? ids.forEach((id) => acc.add(id)) : acc.add(ids);
|
|
79
|
-
return acc;
|
|
80
|
-
}, new Set<string>())
|
|
81
|
-
: new Set([active])
|
|
82
|
-
: new Set();
|
|
83
|
-
|
|
84
|
-
export const isIdActive = (active: string | ActiveParts | undefined, id: string): boolean => {
|
|
85
|
-
return active
|
|
86
|
-
? isActiveParts(active)
|
|
87
|
-
? Object.values(active).findIndex((ids) => (Array.isArray(ids) ? ids.indexOf(id) > -1 : ids === id)) > -1
|
|
88
|
-
: active === id
|
|
89
|
-
: false;
|
|
90
|
-
};
|
|
91
|
-
|
|
92
71
|
/**
|
|
93
72
|
* Provides for a plugin that can manage the app navigation.
|
|
94
73
|
*/
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
74
|
+
const LocationProvidesSchema = S.mutable(
|
|
75
|
+
S.Struct({
|
|
76
|
+
location: S.Struct({
|
|
77
|
+
active: LayoutPartsSchema,
|
|
78
|
+
closed: S.Array(S.String),
|
|
79
|
+
}),
|
|
80
|
+
}),
|
|
81
|
+
);
|
|
82
|
+
export type LocationProvides = S.Schema.Type<typeof LocationProvidesSchema>;
|
|
98
83
|
|
|
99
84
|
/**
|
|
100
85
|
* Type guard for layout plugins.
|
|
101
86
|
*/
|
|
102
|
-
export const
|
|
103
|
-
|
|
104
|
-
|
|
87
|
+
export const isLayoutParts = (value: unknown): value is LayoutParts => {
|
|
88
|
+
return S.is(LayoutPartsSchema)(value);
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
// Type guard for PartAdjustment
|
|
92
|
+
export const isLayoutAdjustment = (value: unknown): value is LayoutAdjustment => {
|
|
93
|
+
return S.is(LayoutAdjustmentSchema)(value);
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
export const parseNavigationPlugin = (plugin: Plugin): Plugin<LocationProvides> | undefined => {
|
|
97
|
+
const location = (plugin.provides as any)?.location;
|
|
98
|
+
if (!location) {
|
|
99
|
+
return undefined;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
if (S.is(LocationProvidesSchema)({ location })) {
|
|
103
|
+
return plugin as Plugin<LocationProvides>;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return undefined;
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Utilities.
|
|
111
|
+
*/
|
|
112
|
+
|
|
113
|
+
/** Extracts all unique IDs from the layout parts. */
|
|
114
|
+
export const openIds = (layout: LayoutParts): string[] => {
|
|
115
|
+
return Object.values(layout)
|
|
116
|
+
.flatMap((part) => part?.map((entry) => entry.id) ?? [])
|
|
117
|
+
.filter((id): id is string => id !== undefined);
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
export const firstIdInPart = (layout: LayoutParts | undefined, part: LayoutPart): string | undefined => {
|
|
121
|
+
if (!layout) {
|
|
122
|
+
return undefined;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
return layout[part]?.at(0)?.id;
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
export const indexInPart = (
|
|
129
|
+
layout: LayoutParts | undefined,
|
|
130
|
+
layoutCoordinate: LayoutCoordinate | undefined,
|
|
131
|
+
): number | undefined => {
|
|
132
|
+
if (!layout || !layoutCoordinate) {
|
|
133
|
+
return undefined;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const { part, entryId } = layoutCoordinate;
|
|
137
|
+
return layout[part]?.findIndex((entry) => entry.id === entryId);
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
export const partLength = (layout: LayoutParts | undefined, part: LayoutPart | undefined): number => {
|
|
141
|
+
if (!layout || !part) {
|
|
142
|
+
return 0;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
return layout[part]?.length ?? 0;
|
|
105
146
|
};
|
|
106
147
|
|
|
107
148
|
//
|
|
@@ -115,6 +156,7 @@ export enum NavigationAction {
|
|
|
115
156
|
SET = `${NAVIGATION_ACTION}/set`,
|
|
116
157
|
ADJUST = `${NAVIGATION_ACTION}/adjust`,
|
|
117
158
|
CLOSE = `${NAVIGATION_ACTION}/close`,
|
|
159
|
+
EXPOSE = `${NAVIGATION_ACTION}/expose`,
|
|
118
160
|
}
|
|
119
161
|
|
|
120
162
|
/**
|
|
@@ -129,9 +171,11 @@ export namespace NavigationAction {
|
|
|
129
171
|
* Payload for adding an item to the active items.
|
|
130
172
|
*/
|
|
131
173
|
export type AddToActive = IntentData<{
|
|
174
|
+
part: LayoutPart;
|
|
132
175
|
id: string;
|
|
133
176
|
scrollIntoView?: boolean;
|
|
134
|
-
|
|
177
|
+
pivotId?: string;
|
|
178
|
+
positioning?: 'start' | 'end';
|
|
135
179
|
}>;
|
|
136
180
|
/**
|
|
137
181
|
* A subtractive overlay to apply to `location.active` (i.e. the result is a subtraction from the previous active of the argument)
|
|
@@ -144,5 +188,5 @@ export namespace NavigationAction {
|
|
|
144
188
|
/**
|
|
145
189
|
* An atomic transaction to apply to `location.active`, describing which element to (attempt to) move to which location.
|
|
146
190
|
*/
|
|
147
|
-
export type Adjust = IntentData<
|
|
191
|
+
export type Adjust = IntentData<LayoutAdjustment>;
|
|
148
192
|
}
|