@chelone/core 0.5.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/dist/assistant/assistant-session.d.ts +28 -0
- package/dist/assistant/assistant-session.js +208 -0
- package/dist/assistant/echo-transport.d.ts +7 -0
- package/dist/assistant/echo-transport.js +28 -0
- package/dist/assistant/http-transport.d.ts +10 -0
- package/dist/assistant/http-transport.js +98 -0
- package/dist/assistant/types.d.ts +54 -0
- package/dist/auth/access-control.d.ts +13 -5
- package/dist/auth/access-control.js +25 -21
- package/dist/auth/auth-options.d.ts +21 -17
- package/dist/auth/auth-options.js +80 -48
- package/dist/auth/permission-matrix.d.ts +12 -0
- package/dist/auth/permission-matrix.js +67 -0
- package/dist/auth/types.d.ts +14 -4
- package/dist/block-editor/block-document.d.ts +60 -0
- package/dist/block-editor/block-document.js +143 -0
- package/dist/block-editor/data-blocks.d.ts +63 -0
- package/dist/block-editor/data-blocks.js +117 -0
- package/dist/chart/chart-data.d.ts +25 -0
- package/dist/chart/chart-data.js +61 -0
- package/dist/command-menu/hotkey.js +58 -47
- package/dist/command-menu/recents.js +8 -11
- package/dist/command-menu/registry.d.ts +9 -5
- package/dist/command-menu/registry.js +36 -46
- package/dist/controller/calendar-dates.js +82 -113
- package/dist/controller/calendar-events.js +93 -88
- package/dist/controller/client-list-controller.d.ts +2 -2
- package/dist/controller/client-list-controller.js +251 -254
- package/dist/controller/data-table-columns.d.ts +2 -0
- package/dist/controller/data-table-columns.js +50 -56
- package/dist/controller/deep-equal.d.ts +1 -0
- package/dist/controller/deep-equal.js +14 -0
- package/dist/controller/filter-toggle.js +36 -44
- package/dist/controller/in-place-editor.d.ts +42 -0
- package/dist/controller/in-place-editor.js +42 -0
- package/dist/controller/infinite-list-controller.js +220 -190
- package/dist/controller/inspector.d.ts +49 -0
- package/dist/controller/inspector.js +64 -0
- package/dist/controller/job-monitor.d.ts +41 -0
- package/dist/controller/job-monitor.js +49 -0
- package/dist/controller/kanban-board.d.ts +2 -3
- package/dist/controller/kanban-board.js +43 -24
- package/dist/controller/list-controller.d.ts +5 -5
- package/dist/controller/list-controller.js +444 -400
- package/dist/controller/list-params.js +96 -87
- package/dist/controller/pivot-grid.d.ts +36 -0
- package/dist/controller/pivot-grid.js +99 -0
- package/dist/controller/prev-next.d.ts +2 -2
- package/dist/controller/prev-next.js +74 -54
- package/dist/controller/record-controllers.d.ts +14 -6
- package/dist/controller/record-controllers.js +288 -257
- package/dist/controller/record-diff.d.ts +21 -0
- package/dist/controller/record-diff.js +50 -0
- package/dist/controller/record-timeline.d.ts +35 -0
- package/dist/controller/record-timeline.js +48 -0
- package/dist/controller/record-tree.js +57 -47
- package/dist/controller/record-workflow.d.ts +38 -16
- package/dist/controller/record-workflow.js +85 -64
- package/dist/controller/reference-many-controller.js +198 -192
- package/dist/controller/reference-one-controller.d.ts +1 -1
- package/dist/controller/reference-one-controller.js +63 -56
- package/dist/controller/saved-queries.js +104 -91
- package/dist/controller/translate-fallback.js +10 -6
- package/dist/csv/export.d.ts +2 -1
- package/dist/csv/export.js +43 -40
- package/dist/csv/import-controller.js +185 -169
- package/dist/csv/match-headers.js +29 -22
- package/dist/csv/parse-with.js +60 -54
- package/dist/csv/run-import.js +69 -72
- package/dist/csv/validate-rows.d.ts +1 -1
- package/dist/csv/validate-rows.js +65 -52
- package/dist/data/cache-ops.d.ts +5 -4
- package/dist/data/cache-ops.js +208 -190
- package/dist/data/combine-data-providers.js +25 -23
- package/dist/data/data-provider-log.d.ts +45 -0
- package/dist/data/data-provider-log.js +93 -0
- package/dist/data/errors.js +14 -10
- package/dist/data/fake-provider.d.ts +6 -3
- package/dist/data/fake-provider.js +129 -114
- package/dist/data/fetch-utils.js +61 -44
- package/dist/data/filter-branch.d.ts +5 -0
- package/dist/data/filter-branch.js +10 -0
- package/dist/data/get-many-aggregate.js +84 -76
- package/dist/data/http-error.js +17 -12
- package/dist/data/keys.js +50 -36
- package/dist/data/mutation-options.d.ts +14 -9
- package/dist/data/mutation-options.js +93 -109
- package/dist/data/query-options.js +73 -68
- package/dist/data/remove-empty.d.ts +3 -0
- package/dist/data/remove-empty.js +11 -0
- package/dist/data/undoable.d.ts +2 -2
- package/dist/data/undoable.js +190 -174
- package/dist/data/with-lifecycle-callbacks.d.ts +45 -17
- package/dist/data/with-lifecycle-callbacks.js +66 -52
- package/dist/environment/application-update-store.js +58 -55
- package/dist/environment/offline-store.js +21 -21
- package/dist/form/array-binding.d.ts +30 -11
- package/dist/form/array-binding.js +184 -166
- package/dist/form/choice-ids.d.ts +7 -0
- package/dist/form/choice-ids.js +11 -0
- package/dist/form/choices-controller.d.ts +3 -2
- package/dist/form/choices-controller.js +302 -275
- package/dist/form/date-format.d.ts +38 -0
- package/dist/form/date-format.js +89 -0
- package/dist/form/dirty-values.d.ts +2 -2
- package/dist/form/dirty-values.js +22 -11
- package/dist/form/duration.d.ts +22 -0
- package/dist/form/duration.js +49 -0
- package/dist/form/edit-create-form.d.ts +12 -5
- package/dist/form/edit-create-form.js +243 -188
- package/dist/form/event-value.d.ts +6 -0
- package/dist/form/event-value.js +17 -0
- package/dist/form/field-error.d.ts +14 -0
- package/dist/form/field-error.js +28 -0
- package/dist/form/field-name.d.ts +20 -0
- package/dist/form/field-name.js +6 -0
- package/dist/form/filter-form-values.js +63 -49
- package/dist/form/form-controller.d.ts +9 -1
- package/dist/form/form-controller.js +165 -160
- package/dist/form/form-groups.d.ts +4 -3
- package/dist/form/form-groups.js +79 -79
- package/dist/form/form-handle.d.ts +175 -0
- package/dist/form/form-handle.js +68 -0
- package/dist/form/input-binding.d.ts +56 -31
- package/dist/form/input-binding.js +169 -118
- package/dist/form/input-values.d.ts +130 -0
- package/dist/form/input-values.js +177 -0
- package/dist/form/json-text.js +51 -37
- package/dist/form/save-pipeline.d.ts +6 -8
- package/dist/form/save-pipeline.js +91 -82
- package/dist/form/set-form-values.d.ts +6 -0
- package/dist/form/set-form-values.js +10 -0
- package/dist/form/source.js +97 -65
- package/dist/form/text-of.d.ts +2 -0
- package/dist/form/text-of.js +7 -0
- package/dist/form/validators.d.ts +58 -19
- package/dist/form/validators.js +97 -71
- package/dist/geo/bbox.d.ts +5 -4
- package/dist/geo/bbox.js +69 -48
- package/dist/geo/bounds.d.ts +14 -3
- package/dist/geo/bounds.js +98 -68
- package/dist/geo/constraints.js +88 -93
- package/dist/geo/coordinates.js +31 -23
- package/dist/geo/editor.d.ts +13 -13
- package/dist/geo/editor.js +176 -153
- package/dist/geo/geocoding.d.ts +4 -4
- package/dist/geo/geocoding.js +38 -41
- package/dist/geo/geojson.d.ts +12 -74
- package/dist/geo/geojson.js +52 -40
- package/dist/geo/geometry-math.d.ts +4 -4
- package/dist/geo/geometry-math.js +100 -93
- package/dist/geo/index.d.ts +7 -6
- package/dist/geo/map-layers.d.ts +42 -7
- package/dist/geo/map-layers.js +88 -62
- package/dist/geo/map-style.js +31 -32
- package/dist/geo/messages.js +48 -45
- package/dist/geo/nominatim.js +88 -80
- package/dist/geo/osm-presets.d.ts +4 -3
- package/dist/geo/osm-presets.js +108 -86
- package/dist/geo/osm-tags.js +14 -12
- package/dist/geo/overpass.d.ts +14 -2
- package/dist/geo/overpass.js +83 -90
- package/dist/geo/shape-value.d.ts +9 -5
- package/dist/geo/shape-value.js +169 -146
- package/dist/geo/snap-to-roads.d.ts +3 -3
- package/dist/geo/snap-to-roads.js +55 -50
- package/dist/geo/throttle.js +36 -39
- package/dist/guards.d.ts +37 -0
- package/dist/guards.js +62 -0
- package/dist/i18n/default-messages.js +34 -29
- package/dist/i18n/english-messages.js +187 -189
- package/dist/i18n/i18n.d.ts +8 -2
- package/dist/i18n/i18n.js +47 -48
- package/dist/i18n/inflection.d.ts +34 -0
- package/dist/i18n/inflection.js +82 -0
- package/dist/i18n/messages.d.ts +10 -2
- package/dist/i18n/messages.js +17 -4
- package/dist/i18n/missing-keys.d.ts +19 -0
- package/dist/i18n/missing-keys.js +68 -0
- package/dist/i18n/translatable.d.ts +16 -1
- package/dist/i18n/translatable.js +40 -47
- package/dist/index.d.ts +96 -22
- package/dist/index.js +52 -257
- package/dist/json/deep-equal.d.ts +8 -0
- package/dist/json/deep-equal.js +24 -0
- package/dist/json/strip-json-comments.js +38 -42
- package/dist/noop.d.ts +2 -0
- package/dist/noop.js +4 -0
- package/dist/notification/error-message.d.ts +2 -0
- package/dist/notification/error-message.js +10 -0
- package/dist/notification/notification-store.d.ts +3 -2
- package/dist/notification/notification-store.js +41 -45
- package/dist/onboarding-tour/placement.d.ts +34 -0
- package/dist/onboarding-tour/placement.js +67 -0
- package/dist/onboarding-tour/tour-controller.d.ts +25 -0
- package/dist/onboarding-tour/tour-controller.js +50 -0
- package/dist/realtime/add-events-for-mutations.js +75 -81
- package/dist/realtime/announce-lock.d.ts +7 -0
- package/dist/realtime/announce-lock.js +18 -0
- package/dist/realtime/broadcast-channel-transport.d.ts +14 -0
- package/dist/realtime/broadcast-channel-transport.js +69 -0
- package/dist/realtime/copy-members.js +22 -14
- package/dist/realtime/in-memory-lock-provider.js +41 -60
- package/dist/realtime/live.js +95 -101
- package/dist/realtime/lock-options.js +16 -16
- package/dist/realtime/lock-session.js +91 -78
- package/dist/realtime/memory-transport.js +26 -67
- package/dist/realtime/presence.js +120 -122
- package/dist/realtime/realtime-data-provider.d.ts +10 -0
- package/dist/realtime/realtime-data-provider.js +45 -42
- package/dist/realtime/reconnect-backoff.d.ts +15 -0
- package/dist/realtime/reconnect-backoff.js +17 -0
- package/dist/realtime/sse-transport.d.ts +31 -0
- package/dist/realtime/sse-transport.js +235 -0
- package/dist/realtime/status-store.js +23 -28
- package/dist/realtime/topic-hub.d.ts +24 -0
- package/dist/realtime/topic-hub.js +66 -0
- package/dist/realtime/topics.js +10 -12
- package/dist/realtime/types.js +10 -12
- package/dist/realtime/unread-count.js +21 -21
- package/dist/realtime/websocket-transport.d.ts +32 -0
- package/dist/realtime/websocket-transport.js +306 -0
- package/dist/resource/describe-resource.d.ts +2 -0
- package/dist/resource/describe-resource.js +232 -219
- package/dist/resource/get-at-path.js +29 -14
- package/dist/resource/identifier.d.ts +18 -0
- package/dist/resource/identifier.js +27 -0
- package/dist/resource/record.d.ts +4 -0
- package/dist/resource/record.js +10 -0
- package/dist/resource/register.d.ts +7 -0
- package/dist/resource/registry-store.d.ts +45 -15
- package/dist/resource/registry-store.js +67 -60
- package/dist/resource/registry.d.ts +10 -0
- package/dist/resource/registry.js +8 -4
- package/dist/resource/source-path.d.ts +16 -1
- package/dist/rich-text/rich-text-extras.d.ts +132 -0
- package/dist/rich-text/rich-text-extras.js +179 -0
- package/dist/rich-text/rich-text-image.d.ts +124 -0
- package/dist/rich-text/rich-text-image.js +163 -0
- package/dist/rich-text/rich-text.d.ts +62 -0
- package/dist/rich-text/rich-text.js +142 -0
- package/dist/router/location.d.ts +22 -4
- package/dist/router/location.js +41 -18
- package/dist/router/match.js +47 -39
- package/dist/router/paths.js +40 -34
- package/dist/router/record-from-location.d.ts +1 -1
- package/dist/router/record-from-location.js +25 -17
- package/dist/store/adapters.js +51 -55
- package/dist/store/micro-store.js +57 -59
- package/dist/store/shallow-equal.d.ts +12 -0
- package/dist/store/shallow-equal.js +20 -0
- package/dist/store/store.js +54 -57
- package/package.json +13 -11
|
@@ -1,47 +1,43 @@
|
|
|
1
|
+
// src/notification/notification-store.ts
|
|
1
2
|
function createNotificationStore() {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
return () => void subscribers.delete(callback);
|
|
42
|
-
}
|
|
43
|
-
};
|
|
3
|
+
let notifications = [];
|
|
4
|
+
let nextId = 1;
|
|
5
|
+
const subscribers = /* @__PURE__ */ new Set();
|
|
6
|
+
const emit = () => {
|
|
7
|
+
for (const cb of subscribers) cb();
|
|
8
|
+
};
|
|
9
|
+
return {
|
|
10
|
+
notify(message, options = {}) {
|
|
11
|
+
const id = nextId++;
|
|
12
|
+
notifications = [...notifications, {
|
|
13
|
+
id,
|
|
14
|
+
message,
|
|
15
|
+
type: options.type ?? "info",
|
|
16
|
+
messageArgs: options.messageArgs,
|
|
17
|
+
undoable: options.undoable,
|
|
18
|
+
autoHideDuration: options.autoHideDuration,
|
|
19
|
+
undo: options.undo,
|
|
20
|
+
onClose: options.onClose
|
|
21
|
+
}];
|
|
22
|
+
emit();
|
|
23
|
+
return id;
|
|
24
|
+
},
|
|
25
|
+
dismiss(id) {
|
|
26
|
+
const next = notifications.filter((n) => n.id !== id);
|
|
27
|
+
if (next.length === notifications.length) return;
|
|
28
|
+
notifications = next;
|
|
29
|
+
emit();
|
|
30
|
+
},
|
|
31
|
+
clear() {
|
|
32
|
+
if (notifications.length === 0) return;
|
|
33
|
+
notifications = [];
|
|
34
|
+
emit();
|
|
35
|
+
},
|
|
36
|
+
getSnapshot: () => notifications,
|
|
37
|
+
subscribe(callback) {
|
|
38
|
+
subscribers.add(callback);
|
|
39
|
+
return () => void subscribers.delete(callback);
|
|
40
|
+
}
|
|
41
|
+
};
|
|
44
42
|
}
|
|
45
|
-
export {
|
|
46
|
-
createNotificationStore
|
|
47
|
-
};
|
|
43
|
+
export { createNotificationStore };
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/** A box in viewport pixels. The adapters measure it; core touches no DOM. */
|
|
2
|
+
export interface TourRect {
|
|
3
|
+
top: number;
|
|
4
|
+
left: number;
|
|
5
|
+
width: number;
|
|
6
|
+
height: number;
|
|
7
|
+
}
|
|
8
|
+
export type TourPlacement = "auto" | "top" | "bottom" | "left" | "right";
|
|
9
|
+
type Side = Exclude<TourPlacement, "auto">;
|
|
10
|
+
export interface PlaceTourCardOptions {
|
|
11
|
+
/** `null` centres the card, for a step that points at nothing. */
|
|
12
|
+
target: TourRect | null;
|
|
13
|
+
card: {
|
|
14
|
+
width: number;
|
|
15
|
+
height: number;
|
|
16
|
+
};
|
|
17
|
+
viewport: {
|
|
18
|
+
width: number;
|
|
19
|
+
height: number;
|
|
20
|
+
};
|
|
21
|
+
placement: TourPlacement;
|
|
22
|
+
/** Gap between card and target, and the least distance to a viewport edge. */
|
|
23
|
+
margin?: number;
|
|
24
|
+
}
|
|
25
|
+
export interface TourCardPosition {
|
|
26
|
+
/** The card's top-left corner. */
|
|
27
|
+
top: number;
|
|
28
|
+
left: number;
|
|
29
|
+
side: Side;
|
|
30
|
+
}
|
|
31
|
+
export declare function placeTourCard({ target, card, viewport, placement, margin, }: PlaceTourCardOptions): TourCardPosition;
|
|
32
|
+
/** A CSS `clip-path` that fills the page except the target's rect. */
|
|
33
|
+
export declare function tourSpotlightPath(target: TourRect): string;
|
|
34
|
+
export {};
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
// src/onboarding-tour/placement.ts
|
|
2
|
+
var SIDES = [
|
|
3
|
+
"bottom",
|
|
4
|
+
"top",
|
|
5
|
+
"right",
|
|
6
|
+
"left"
|
|
7
|
+
];
|
|
8
|
+
var OPPOSITE = {
|
|
9
|
+
top: "bottom",
|
|
10
|
+
bottom: "top",
|
|
11
|
+
left: "right",
|
|
12
|
+
right: "left"
|
|
13
|
+
};
|
|
14
|
+
function placeTourCard({ target, card, viewport, placement, margin = 12 }) {
|
|
15
|
+
if (!target) return {
|
|
16
|
+
top: (viewport.height - card.height) / 2,
|
|
17
|
+
left: (viewport.width - card.width) / 2,
|
|
18
|
+
side: placement === "auto" ? "bottom" : placement
|
|
19
|
+
};
|
|
20
|
+
const room = {
|
|
21
|
+
top: target.top,
|
|
22
|
+
bottom: viewport.height - (target.top + target.height),
|
|
23
|
+
left: target.left,
|
|
24
|
+
right: viewport.width - (target.left + target.width)
|
|
25
|
+
};
|
|
26
|
+
const needed = (side) => (side === "top" || side === "bottom" ? card.height : card.width) + margin * 2;
|
|
27
|
+
const fits = (side) => room[side] >= needed(side);
|
|
28
|
+
let side;
|
|
29
|
+
if (placement === "auto") {
|
|
30
|
+
const roomiest = (candidates) => candidates.reduce((best, next) => room[next] > room[best] ? next : best);
|
|
31
|
+
const fitting = SIDES.filter(fits);
|
|
32
|
+
side = roomiest(fitting.length > 0 ? fitting : SIDES);
|
|
33
|
+
} else side = !fits(placement) && fits(OPPOSITE[placement]) ? OPPOSITE[placement] : placement;
|
|
34
|
+
const centreX = target.left + target.width / 2 - card.width / 2;
|
|
35
|
+
const centreY = target.top + target.height / 2 - card.height / 2;
|
|
36
|
+
let top;
|
|
37
|
+
let left;
|
|
38
|
+
switch (side) {
|
|
39
|
+
case "bottom":
|
|
40
|
+
top = target.top + target.height + margin;
|
|
41
|
+
left = centreX;
|
|
42
|
+
break;
|
|
43
|
+
case "top":
|
|
44
|
+
top = target.top - margin - card.height;
|
|
45
|
+
left = centreX;
|
|
46
|
+
break;
|
|
47
|
+
case "right":
|
|
48
|
+
top = centreY;
|
|
49
|
+
left = target.left + target.width + margin;
|
|
50
|
+
break;
|
|
51
|
+
case "left":
|
|
52
|
+
top = centreY;
|
|
53
|
+
left = target.left - margin - card.width;
|
|
54
|
+
}
|
|
55
|
+
return {
|
|
56
|
+
top: Math.max(margin, Math.min(top, viewport.height - card.height - margin)),
|
|
57
|
+
left: Math.max(margin, Math.min(left, viewport.width - card.width - margin)),
|
|
58
|
+
side
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
/** A CSS `clip-path` that fills the page except the target's rect. */
|
|
62
|
+
function tourSpotlightPath(target) {
|
|
63
|
+
const right = target.left + target.width;
|
|
64
|
+
const bottom = target.top + target.height;
|
|
65
|
+
return `polygon(0% 0%, 0% 100%, ${target.left}px 100%, ${target.left}px ${target.top}px, ${right}px ${target.top}px, ${right}px ${bottom}px, ${target.left}px ${bottom}px, ${target.left}px 100%, 100% 100%, 100% 0%)`;
|
|
66
|
+
}
|
|
67
|
+
export { placeTourCard, tourSpotlightPath };
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { type MicroStore } from "../store/micro-store.js";
|
|
2
|
+
import type { CheloneStore } from "../store/store.js";
|
|
3
|
+
/** Where the onboarding tour keeps its done flag in a `CheloneStore`. */
|
|
4
|
+
export declare function onboardingTourKey(id: string): string;
|
|
5
|
+
export interface TourState {
|
|
6
|
+
active: boolean;
|
|
7
|
+
index: number;
|
|
8
|
+
}
|
|
9
|
+
export type TourEnd = "finished" | "skipped";
|
|
10
|
+
export interface TourControllerOptions {
|
|
11
|
+
id: string;
|
|
12
|
+
stepCount: number;
|
|
13
|
+
store: CheloneStore;
|
|
14
|
+
/** Open on the first subscribe while the done flag is not `true`. */
|
|
15
|
+
autoStart?: boolean;
|
|
16
|
+
onComplete?: (reason: TourEnd) => void;
|
|
17
|
+
}
|
|
18
|
+
export interface TourController extends MicroStore<TourState> {
|
|
19
|
+
start(): void;
|
|
20
|
+
next(): void;
|
|
21
|
+
back(): void;
|
|
22
|
+
skip(): void;
|
|
23
|
+
finish(): void;
|
|
24
|
+
}
|
|
25
|
+
export declare function createTourController({ id, stepCount, store, autoStart, onComplete, }: TourControllerOptions): TourController;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { createMicroStore } from "../store/micro-store.js";
|
|
2
|
+
// src/onboarding-tour/tour-controller.ts
|
|
3
|
+
/** Where the onboarding tour keeps its done flag in a `CheloneStore`. */
|
|
4
|
+
function onboardingTourKey(id) {
|
|
5
|
+
return `onboarding-tour.${id}.completed`;
|
|
6
|
+
}
|
|
7
|
+
function createTourController({ id, stepCount, store, autoStart = true, onComplete }) {
|
|
8
|
+
const key = onboardingTourKey(id);
|
|
9
|
+
const isDone = () => store.getItem(key) === true;
|
|
10
|
+
const micro = createMicroStore({
|
|
11
|
+
active: false,
|
|
12
|
+
index: 0
|
|
13
|
+
}, { onSubscribed() {
|
|
14
|
+
const openIfDue = () => {
|
|
15
|
+
if (autoStart && !isDone() && !micro.getSnapshot().active) start();
|
|
16
|
+
};
|
|
17
|
+
openIfDue();
|
|
18
|
+
return store.subscribe(key, openIfDue);
|
|
19
|
+
} });
|
|
20
|
+
function start() {
|
|
21
|
+
if (stepCount < 1) return;
|
|
22
|
+
micro.setState({
|
|
23
|
+
active: true,
|
|
24
|
+
index: 0
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
function end(reason) {
|
|
28
|
+
if (!micro.getSnapshot().active) return;
|
|
29
|
+
micro.setState({ active: false });
|
|
30
|
+
store.setItem(key, true);
|
|
31
|
+
onComplete?.(reason);
|
|
32
|
+
}
|
|
33
|
+
return {
|
|
34
|
+
...micro,
|
|
35
|
+
start,
|
|
36
|
+
next() {
|
|
37
|
+
const { active, index } = micro.getSnapshot();
|
|
38
|
+
if (!active) return;
|
|
39
|
+
if (index >= stepCount - 1) end("finished");
|
|
40
|
+
else micro.setState({ index: index + 1 });
|
|
41
|
+
},
|
|
42
|
+
back() {
|
|
43
|
+
const { active, index } = micro.getSnapshot();
|
|
44
|
+
if (active && index > 0) micro.setState({ index: index - 1 });
|
|
45
|
+
},
|
|
46
|
+
skip: () => end("skipped"),
|
|
47
|
+
finish: () => end("finished")
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
export { createTourController, onboardingTourKey };
|
|
@@ -1,85 +1,79 @@
|
|
|
1
1
|
import { copyMembers } from "./copy-members.js";
|
|
2
2
|
import { recordTopic, resourceTopic } from "./topics.js";
|
|
3
|
+
// src/realtime/add-events-for-mutations.ts
|
|
4
|
+
/**
|
|
5
|
+
* Publishes after each successful write, so other clients can refresh. The
|
|
6
|
+
* result waits for the publishes to settle but never fails because of them;
|
|
7
|
+
* a rejected publish goes to `onError`. Returns the type it was given, so a
|
|
8
|
+
* `RealtimeDataProvider` stays one.
|
|
9
|
+
*/
|
|
3
10
|
function addEventsForMutations(base, publisher, { onError } = {}) {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
const result = await base.updateMany(resource, params);
|
|
72
|
-
await publishAll(manyEvents("updated", resource, result.data));
|
|
73
|
-
return result;
|
|
74
|
-
},
|
|
75
|
-
deleteMany: async (resource, params) => {
|
|
76
|
-
const result = await base.deleteMany(resource, params);
|
|
77
|
-
await publishAll(manyEvents("deleted", resource, result.data));
|
|
78
|
-
return result;
|
|
79
|
-
}
|
|
80
|
-
};
|
|
81
|
-
return wrapped;
|
|
11
|
+
const publishAll = async (events) => {
|
|
12
|
+
await Promise.all(events.map(async ([topic, event]) => {
|
|
13
|
+
try {
|
|
14
|
+
await publisher.publish(topic, event);
|
|
15
|
+
} catch (error) {
|
|
16
|
+
try {
|
|
17
|
+
onError?.(error);
|
|
18
|
+
} catch {}
|
|
19
|
+
}
|
|
20
|
+
}));
|
|
21
|
+
};
|
|
22
|
+
const manyEvents = (type, resource, ids) => [[resourceTopic(resource), {
|
|
23
|
+
type,
|
|
24
|
+
payload: { ids }
|
|
25
|
+
}], ...ids.map((id) => [recordTopic(resource, id), {
|
|
26
|
+
type,
|
|
27
|
+
payload: { id }
|
|
28
|
+
}])];
|
|
29
|
+
return {
|
|
30
|
+
...copyMembers(base),
|
|
31
|
+
create: async (resource, params) => {
|
|
32
|
+
const result = await base.create(resource, params);
|
|
33
|
+
await publishAll([[resourceTopic(resource), {
|
|
34
|
+
type: "created",
|
|
35
|
+
payload: { ids: [result.data.id] }
|
|
36
|
+
}]]);
|
|
37
|
+
return result;
|
|
38
|
+
},
|
|
39
|
+
update: async (resource, params) => {
|
|
40
|
+
const result = await base.update(resource, params);
|
|
41
|
+
await publishAll([[recordTopic(resource, params.id), {
|
|
42
|
+
type: "updated",
|
|
43
|
+
payload: {
|
|
44
|
+
id: params.id,
|
|
45
|
+
data: result.data
|
|
46
|
+
}
|
|
47
|
+
}], [resourceTopic(resource), {
|
|
48
|
+
type: "updated",
|
|
49
|
+
payload: { ids: [params.id] }
|
|
50
|
+
}]]);
|
|
51
|
+
return result;
|
|
52
|
+
},
|
|
53
|
+
delete: async (resource, params) => {
|
|
54
|
+
const result = await base.delete(resource, params);
|
|
55
|
+
await publishAll([[recordTopic(resource, params.id), {
|
|
56
|
+
type: "deleted",
|
|
57
|
+
payload: {
|
|
58
|
+
id: params.id,
|
|
59
|
+
previousData: result.data
|
|
60
|
+
}
|
|
61
|
+
}], [resourceTopic(resource), {
|
|
62
|
+
type: "deleted",
|
|
63
|
+
payload: { ids: [params.id] }
|
|
64
|
+
}]]);
|
|
65
|
+
return result;
|
|
66
|
+
},
|
|
67
|
+
updateMany: async (resource, params) => {
|
|
68
|
+
const result = await base.updateMany(resource, params);
|
|
69
|
+
await publishAll(manyEvents("updated", resource, result.data));
|
|
70
|
+
return result;
|
|
71
|
+
},
|
|
72
|
+
deleteMany: async (resource, params) => {
|
|
73
|
+
const result = await base.deleteMany(resource, params);
|
|
74
|
+
await publishAll(manyEvents("deleted", resource, result.data));
|
|
75
|
+
return result;
|
|
76
|
+
}
|
|
77
|
+
};
|
|
82
78
|
}
|
|
83
|
-
export {
|
|
84
|
-
addEventsForMutations
|
|
85
|
-
};
|
|
79
|
+
export { addEventsForMutations };
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { Lock, RealtimeTransport } from "./types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Tells a publisher of a lock or an unlock, on the record's and the
|
|
4
|
+
* resource's topic. A failed publish is swallowed: the lock is already taken
|
|
5
|
+
* or released, so a failed announcement must not report it as failed.
|
|
6
|
+
*/
|
|
7
|
+
export declare function announceLock(publisher: Pick<RealtimeTransport, "publish"> | undefined, type: "locked" | "unlocked", lock: Lock): Promise<void>;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { lockResourceTopic, lockTopic } from "./topics.js";
|
|
2
|
+
// src/realtime/announce-lock.ts
|
|
3
|
+
/**
|
|
4
|
+
* Tells a publisher of a lock or an unlock, on the record's and the
|
|
5
|
+
* resource's topic. A failed publish is swallowed: the lock is already taken
|
|
6
|
+
* or released, so a failed announcement must not report it as failed.
|
|
7
|
+
*/
|
|
8
|
+
async function announceLock(publisher, type, lock) {
|
|
9
|
+
if (!publisher) return;
|
|
10
|
+
const event = {
|
|
11
|
+
type,
|
|
12
|
+
payload: lock
|
|
13
|
+
};
|
|
14
|
+
try {
|
|
15
|
+
await Promise.all([publisher.publish(lockTopic(lock.resource, lock.recordId), event), publisher.publish(lockResourceTopic(lock.resource), event)]);
|
|
16
|
+
} catch {}
|
|
17
|
+
}
|
|
18
|
+
export { announceLock };
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { RealtimeTransport, RealtimeTransportError } from "./types.js";
|
|
2
|
+
export interface BroadcastChannelTransportOptions {
|
|
3
|
+
/** The channel name; every tab that uses the same name shares events. */
|
|
4
|
+
channel: string;
|
|
5
|
+
/** Called for every transport error, like a listener from `onError()`. */
|
|
6
|
+
onError?: (error: RealtimeTransportError) => void;
|
|
7
|
+
}
|
|
8
|
+
/** A `RealtimeTransport` with every optional member, plus `close()`. */
|
|
9
|
+
export interface BroadcastChannelTransport extends Required<RealtimeTransport> {
|
|
10
|
+
/** Stops listening, closes the channel and sets `"disconnected"`. */
|
|
11
|
+
close(): void;
|
|
12
|
+
}
|
|
13
|
+
/** Carries realtime events between the tabs of one browser, with no server. */
|
|
14
|
+
export declare function broadcastChannelTransport({ channel, onError, }: BroadcastChannelTransportOptions): BroadcastChannelTransport;
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { createTopicHub } from "./topic-hub.js";
|
|
2
|
+
// src/realtime/broadcast-channel-transport.ts
|
|
3
|
+
function isEnvelope(value) {
|
|
4
|
+
if (typeof value !== "object" || value === null) return false;
|
|
5
|
+
if (!("topic" in value) || typeof value.topic !== "string") return false;
|
|
6
|
+
if (!("event" in value)) return false;
|
|
7
|
+
const { event } = value;
|
|
8
|
+
return typeof event === "object" && event !== null && "type" in event && typeof event.type === "string";
|
|
9
|
+
}
|
|
10
|
+
/** Carries realtime events between the tabs of one browser, with no server. */
|
|
11
|
+
function broadcastChannelTransport({ channel, onError }) {
|
|
12
|
+
if (typeof BroadcastChannel === "undefined") throw new Error("broadcastChannelTransport needs the global BroadcastChannel, which this environment does not have.");
|
|
13
|
+
const hub = createTopicHub({ onError });
|
|
14
|
+
const broadcast = new BroadcastChannel(channel);
|
|
15
|
+
let closed = false;
|
|
16
|
+
const onMessage = (message) => {
|
|
17
|
+
if (!isEnvelope(message.data)) {
|
|
18
|
+
hub.report({
|
|
19
|
+
kind: "parse_failed",
|
|
20
|
+
cause: message.data,
|
|
21
|
+
retrying: false
|
|
22
|
+
});
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
const { topic, event } = message.data;
|
|
26
|
+
hub.dispatch({
|
|
27
|
+
payload: void 0,
|
|
28
|
+
...event,
|
|
29
|
+
topic
|
|
30
|
+
});
|
|
31
|
+
};
|
|
32
|
+
broadcast.addEventListener("message", onMessage);
|
|
33
|
+
return {
|
|
34
|
+
subscribe: hub.subscribe,
|
|
35
|
+
async publish(topic, event) {
|
|
36
|
+
if (closed) throw new Error("broadcastChannelTransport is closed");
|
|
37
|
+
try {
|
|
38
|
+
broadcast.postMessage({
|
|
39
|
+
topic,
|
|
40
|
+
event
|
|
41
|
+
});
|
|
42
|
+
} catch (cause) {
|
|
43
|
+
hub.report({
|
|
44
|
+
kind: "send_failed",
|
|
45
|
+
topic,
|
|
46
|
+
cause,
|
|
47
|
+
retrying: false
|
|
48
|
+
});
|
|
49
|
+
throw cause;
|
|
50
|
+
}
|
|
51
|
+
hub.dispatch({
|
|
52
|
+
...event,
|
|
53
|
+
topic
|
|
54
|
+
});
|
|
55
|
+
},
|
|
56
|
+
onReconnect: hub.onReconnect,
|
|
57
|
+
onStatusChange: hub.onStatusChange,
|
|
58
|
+
onError: hub.onError,
|
|
59
|
+
getStatus: hub.getStatus,
|
|
60
|
+
close() {
|
|
61
|
+
if (closed) return;
|
|
62
|
+
closed = true;
|
|
63
|
+
broadcast.removeEventListener("message", onMessage);
|
|
64
|
+
broadcast.close();
|
|
65
|
+
hub.setStatus("disconnected");
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
export { broadcastChannelTransport };
|
|
@@ -1,16 +1,24 @@
|
|
|
1
|
+
// src/realtime/copy-members.ts
|
|
2
|
+
/**
|
|
3
|
+
* A copy of `source` that keeps what a spread drops: methods on the prototype
|
|
4
|
+
* chain of a class instance, bound to `source` so they still see its state.
|
|
5
|
+
*/
|
|
1
6
|
function copyMembers(source) {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
7
|
+
const copy = { ...source };
|
|
8
|
+
let proto = Object.getPrototypeOf(source);
|
|
9
|
+
while (proto && proto !== Object.prototype) {
|
|
10
|
+
for (const key of Reflect.ownKeys(proto)) {
|
|
11
|
+
if (key === "constructor" || Object.hasOwn(copy, key)) continue;
|
|
12
|
+
const { value } = Object.getOwnPropertyDescriptor(proto, key) ?? {};
|
|
13
|
+
if (typeof value === "function") Object.defineProperty(copy, key, {
|
|
14
|
+
value: value.bind(source),
|
|
15
|
+
writable: true,
|
|
16
|
+
enumerable: true,
|
|
17
|
+
configurable: true
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
proto = Object.getPrototypeOf(proto);
|
|
21
|
+
}
|
|
22
|
+
return copy;
|
|
13
23
|
}
|
|
14
|
-
export {
|
|
15
|
-
copyMembers
|
|
16
|
-
};
|
|
24
|
+
export { copyMembers };
|