pixelize-design-library 3.0.0 → 3.1.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.
Files changed (176) hide show
  1. package/CHANGELOG.md +64 -1
  2. package/COMPONENT-GOTCHAS.md +37 -13
  3. package/DESIGN-GUIDE.md +4 -1
  4. package/dist/Components/NotificationBell/NotificationBellProps.d.ts +3 -3
  5. package/dist/Components/NotificationBell/NotificationItem.d.ts +1 -1
  6. package/dist/Components/NotificationBell/NotificationItem.js +1 -1
  7. package/dist/Components/NotificationBell/NotificationList.d.ts +1 -1
  8. package/dist/Components/NotificationBell/NotificationList.js +1 -1
  9. package/dist/Components/NotificationBell/NotificationPanel.d.ts +1 -1
  10. package/dist/Components/NotificationBell/NotificationPanel.js +1 -1
  11. package/dist/Components/NotificationCenter/center/CenterPanel.d.ts +15 -0
  12. package/dist/Components/NotificationCenter/center/CenterPanel.js +87 -0
  13. package/dist/Components/NotificationCenter/center/NotificationCenter.d.ts +4 -0
  14. package/dist/Components/NotificationCenter/center/NotificationCenter.js +130 -0
  15. package/dist/Components/NotificationCenter/center/NotificationCenterProps.d.ts +71 -0
  16. package/dist/Components/NotificationCenter/center/NotificationCenterProps.js +2 -0
  17. package/dist/Components/NotificationCenter/center/PanelRows.d.ts +10 -0
  18. package/dist/Components/NotificationCenter/center/PanelRows.js +35 -0
  19. package/dist/Components/NotificationCenter/center/PanelStates.d.ts +17 -0
  20. package/dist/Components/NotificationCenter/center/PanelStates.js +21 -0
  21. package/dist/Components/NotificationCenter/center/centerLabels.d.ts +4 -0
  22. package/dist/Components/NotificationCenter/center/centerLabels.js +38 -0
  23. package/dist/Components/NotificationCenter/center/useArrivals.d.ts +14 -0
  24. package/dist/Components/NotificationCenter/center/useArrivals.js +71 -0
  25. package/dist/Components/NotificationCenter/center/useIsSheet.d.ts +2 -0
  26. package/dist/Components/NotificationCenter/center/useIsSheet.js +20 -0
  27. package/dist/Components/NotificationCenter/core/NotificationCenterProvider.d.ts +17 -0
  28. package/dist/Components/NotificationCenter/core/NotificationCenterProvider.js +46 -0
  29. package/dist/Components/NotificationCenter/core/NotificationEngineProps.d.ts +182 -0
  30. package/dist/Components/NotificationCenter/core/NotificationEngineProps.js +2 -0
  31. package/dist/Components/NotificationCenter/core/NotificationSourceProps.d.ts +284 -0
  32. package/dist/Components/NotificationCenter/core/NotificationSourceProps.js +2 -0
  33. package/dist/Components/NotificationCenter/core/actionError.d.ts +4 -0
  34. package/dist/Components/NotificationCenter/core/actionError.js +45 -0
  35. package/dist/Components/NotificationCenter/core/format.d.ts +4 -0
  36. package/dist/Components/NotificationCenter/core/format.js +91 -0
  37. package/dist/Components/NotificationCenter/core/grouping.d.ts +14 -0
  38. package/dist/Components/NotificationCenter/core/grouping.js +102 -0
  39. package/dist/Components/NotificationCenter/core/labels.d.ts +6 -0
  40. package/dist/Components/NotificationCenter/core/labels.js +94 -0
  41. package/dist/Components/NotificationCenter/core/overlay.d.ts +109 -0
  42. package/dist/Components/NotificationCenter/core/overlay.js +281 -0
  43. package/dist/Components/NotificationCenter/core/useNotificationEngine.d.ts +13 -0
  44. package/dist/Components/NotificationCenter/core/useNotificationEngine.js +415 -0
  45. package/dist/Components/NotificationCenter/inbox/InboxList.d.ts +17 -0
  46. package/dist/Components/NotificationCenter/inbox/InboxList.js +25 -0
  47. package/dist/Components/NotificationCenter/inbox/InboxStates.d.ts +17 -0
  48. package/dist/Components/NotificationCenter/inbox/InboxStates.js +27 -0
  49. package/dist/Components/NotificationCenter/inbox/NotificationInbox.d.ts +4 -0
  50. package/dist/Components/NotificationCenter/inbox/NotificationInbox.js +434 -0
  51. package/dist/Components/NotificationCenter/inbox/NotificationInboxProps.d.ts +106 -0
  52. package/dist/Components/NotificationCenter/inbox/NotificationInboxProps.js +2 -0
  53. package/dist/Components/NotificationCenter/inbox/ShortcutsDialog.d.ts +8 -0
  54. package/dist/Components/NotificationCenter/inbox/ShortcutsDialog.js +63 -0
  55. package/dist/Components/NotificationCenter/inbox/inboxLabels.d.ts +4 -0
  56. package/dist/Components/NotificationCenter/inbox/inboxLabels.js +56 -0
  57. package/dist/Components/NotificationCenter/inbox/inboxModel.d.ts +17 -0
  58. package/dist/Components/NotificationCenter/inbox/inboxModel.js +47 -0
  59. package/dist/Components/NotificationCenter/inbox/useControllable.d.ts +1 -0
  60. package/dist/Components/NotificationCenter/inbox/useControllable.js +15 -0
  61. package/dist/Components/NotificationCenter/index.d.ts +8 -0
  62. package/dist/Components/NotificationCenter/index.js +26 -0
  63. package/dist/Components/NotificationCenter/preferences/NotificationPreferences.d.ts +4 -0
  64. package/dist/Components/NotificationCenter/preferences/NotificationPreferences.js +72 -0
  65. package/dist/Components/NotificationCenter/preferences/NotificationPreferencesProps.d.ts +111 -0
  66. package/dist/Components/NotificationCenter/preferences/NotificationPreferencesProps.js +2 -0
  67. package/dist/Components/NotificationCenter/preferences/PreferenceCells.d.ts +16 -0
  68. package/dist/Components/NotificationCenter/preferences/PreferenceCells.js +35 -0
  69. package/dist/Components/NotificationCenter/preferences/PreferenceGroups.d.ts +15 -0
  70. package/dist/Components/NotificationCenter/preferences/PreferenceGroups.js +26 -0
  71. package/dist/Components/NotificationCenter/preferences/preferencesLabels.d.ts +2 -0
  72. package/dist/Components/NotificationCenter/preferences/preferencesLabels.js +28 -0
  73. package/dist/Components/NotificationCenter/preferences/useAutosave.d.ts +8 -0
  74. package/dist/Components/NotificationCenter/preferences/useAutosave.js +41 -0
  75. package/dist/Components/NotificationCenter/row/NotificationDetail.d.ts +4 -0
  76. package/dist/Components/NotificationCenter/row/NotificationDetail.js +65 -0
  77. package/dist/Components/NotificationCenter/row/NotificationDetailProps.d.ts +15 -0
  78. package/dist/Components/NotificationCenter/row/NotificationDetailProps.js +2 -0
  79. package/dist/Components/NotificationCenter/row/NotificationRow.d.ts +3 -0
  80. package/dist/Components/NotificationCenter/row/NotificationRow.js +124 -0
  81. package/dist/Components/NotificationCenter/row/NotificationRowProps.d.ts +33 -0
  82. package/dist/Components/NotificationCenter/row/NotificationRowProps.js +2 -0
  83. package/dist/Components/NotificationCenter/row/ReasonForm.d.ts +14 -0
  84. package/dist/Components/NotificationCenter/row/ReasonForm.js +59 -0
  85. package/dist/Components/NotificationCenter/row/RowActions.d.ts +16 -0
  86. package/dist/Components/NotificationCenter/row/RowActions.js +36 -0
  87. package/dist/Components/NotificationCenter/row/RowQuickActions.d.ts +17 -0
  88. package/dist/Components/NotificationCenter/row/RowQuickActions.js +57 -0
  89. package/dist/Components/NotificationCenter/row/RowStatus.d.ts +10 -0
  90. package/dist/Components/NotificationCenter/row/RowStatus.js +42 -0
  91. package/dist/Components/NotificationCenter/row/category.d.ts +8 -0
  92. package/dist/Components/NotificationCenter/row/category.js +13 -0
  93. package/dist/Components/NotificationCenter/row/rowMenu.d.ts +14 -0
  94. package/dist/Components/NotificationCenter/row/rowMenu.js +37 -0
  95. package/dist/Components/NotificationCenter/row/rowParts.d.ts +40 -0
  96. package/dist/Components/NotificationCenter/row/rowParts.js +72 -0
  97. package/dist/Components/NotificationCenter/row/rowStyles.d.ts +11 -0
  98. package/dist/Components/NotificationCenter/row/rowStyles.js +25 -0
  99. package/dist/Components/SegmentedControl/segmentedInk.d.ts +4 -0
  100. package/dist/Components/SegmentedControl/segmentedInk.js +16 -2
  101. package/dist/Components/SideBar/SecondaryBar.js +2 -2
  102. package/dist/Components/SideBar/SideBar.js +18 -8
  103. package/dist/Components/SideBar/SideBarProps.d.ts +4 -2
  104. package/dist/Components/SideBar/components/MenuItemBox.d.ts +0 -2
  105. package/dist/Components/SideBar/components/MenuItemBox.js +2 -3
  106. package/dist/Components/SideBar/components/MenuItems.js +2 -2
  107. package/dist/Components/SideBar/components/PinnedSection.js +3 -4
  108. package/dist/Components/SideBar/sidebarScale.d.ts +0 -2
  109. package/dist/Components/SideBar/sidebarScale.js +0 -2
  110. package/dist/Components/Switch/Switch.d.ts +1 -1
  111. package/dist/Components/Switch/Switch.js +2 -2
  112. package/dist/Components/Switch/SwitchProps.d.ts +3 -0
  113. package/dist/Components/Toggle/TableToggle.js +4 -5
  114. package/dist/Components/Toggle/tableToggleInk.d.ts +7 -0
  115. package/dist/Components/Toggle/tableToggleInk.js +28 -0
  116. package/dist/esm/Components/NotificationBell/NotificationItem.js +1 -1
  117. package/dist/esm/Components/NotificationBell/NotificationList.js +1 -1
  118. package/dist/esm/Components/NotificationBell/NotificationPanel.js +1 -1
  119. package/dist/esm/Components/NotificationCenter/center/CenterPanel.js +81 -0
  120. package/dist/esm/Components/NotificationCenter/center/NotificationCenter.js +92 -0
  121. package/dist/esm/Components/NotificationCenter/center/NotificationCenterProps.js +1 -0
  122. package/dist/esm/Components/NotificationCenter/center/PanelRows.js +28 -0
  123. package/dist/esm/Components/NotificationCenter/center/PanelStates.js +11 -0
  124. package/dist/esm/Components/NotificationCenter/center/centerLabels.js +34 -0
  125. package/dist/esm/Components/NotificationCenter/center/useArrivals.js +67 -0
  126. package/dist/esm/Components/NotificationCenter/center/useIsSheet.js +16 -0
  127. package/dist/esm/Components/NotificationCenter/core/NotificationCenterProvider.js +35 -0
  128. package/dist/esm/Components/NotificationCenter/core/NotificationEngineProps.js +1 -0
  129. package/dist/esm/Components/NotificationCenter/core/NotificationSourceProps.js +1 -0
  130. package/dist/esm/Components/NotificationCenter/core/actionError.js +39 -0
  131. package/dist/esm/Components/NotificationCenter/core/format.js +87 -0
  132. package/dist/esm/Components/NotificationCenter/core/grouping.js +88 -0
  133. package/dist/esm/Components/NotificationCenter/core/labels.js +88 -0
  134. package/dist/esm/Components/NotificationCenter/core/overlay.js +274 -0
  135. package/dist/esm/Components/NotificationCenter/core/useNotificationEngine.js +408 -0
  136. package/dist/esm/Components/NotificationCenter/inbox/InboxList.js +18 -0
  137. package/dist/esm/Components/NotificationCenter/inbox/InboxStates.js +18 -0
  138. package/dist/esm/Components/NotificationCenter/inbox/NotificationInbox.js +396 -0
  139. package/dist/esm/Components/NotificationCenter/inbox/NotificationInboxProps.js +1 -0
  140. package/dist/esm/Components/NotificationCenter/inbox/ShortcutsDialog.js +23 -0
  141. package/dist/esm/Components/NotificationCenter/inbox/inboxLabels.js +52 -0
  142. package/dist/esm/Components/NotificationCenter/inbox/inboxModel.js +37 -0
  143. package/dist/esm/Components/NotificationCenter/inbox/useControllable.js +11 -0
  144. package/dist/esm/Components/NotificationCenter/index.js +8 -0
  145. package/dist/esm/Components/NotificationCenter/preferences/NotificationPreferences.js +67 -0
  146. package/dist/esm/Components/NotificationCenter/preferences/NotificationPreferencesProps.js +1 -0
  147. package/dist/esm/Components/NotificationCenter/preferences/PreferenceCells.js +27 -0
  148. package/dist/esm/Components/NotificationCenter/preferences/PreferenceGroups.js +21 -0
  149. package/dist/esm/Components/NotificationCenter/preferences/preferencesLabels.js +24 -0
  150. package/dist/esm/Components/NotificationCenter/preferences/useAutosave.js +37 -0
  151. package/dist/esm/Components/NotificationCenter/row/NotificationDetail.js +60 -0
  152. package/dist/esm/Components/NotificationCenter/row/NotificationDetailProps.js +1 -0
  153. package/dist/esm/Components/NotificationCenter/row/NotificationRow.js +119 -0
  154. package/dist/esm/Components/NotificationCenter/row/NotificationRowProps.js +1 -0
  155. package/dist/esm/Components/NotificationCenter/row/ReasonForm.js +52 -0
  156. package/dist/esm/Components/NotificationCenter/row/RowActions.js +28 -0
  157. package/dist/esm/Components/NotificationCenter/row/RowQuickActions.js +49 -0
  158. package/dist/esm/Components/NotificationCenter/row/RowStatus.js +35 -0
  159. package/dist/esm/Components/NotificationCenter/row/category.js +9 -0
  160. package/dist/esm/Components/NotificationCenter/row/rowMenu.js +30 -0
  161. package/dist/esm/Components/NotificationCenter/row/rowParts.js +59 -0
  162. package/dist/esm/Components/NotificationCenter/row/rowStyles.js +19 -0
  163. package/dist/esm/Components/SegmentedControl/segmentedInk.js +15 -2
  164. package/dist/esm/Components/SideBar/SecondaryBar.js +3 -3
  165. package/dist/esm/Components/SideBar/SideBar.js +18 -8
  166. package/dist/esm/Components/SideBar/components/MenuItemBox.js +2 -3
  167. package/dist/esm/Components/SideBar/components/MenuItems.js +2 -2
  168. package/dist/esm/Components/SideBar/components/PinnedSection.js +3 -4
  169. package/dist/esm/Components/SideBar/sidebarScale.js +0 -2
  170. package/dist/esm/Components/Switch/Switch.js +2 -2
  171. package/dist/esm/Components/Toggle/TableToggle.js +4 -5
  172. package/dist/esm/Components/Toggle/tableToggleInk.js +24 -0
  173. package/dist/esm/index.js +1 -0
  174. package/dist/index.d.ts +8 -0
  175. package/dist/index.js +16 -3
  176. package/package.json +1 -1
@@ -0,0 +1,88 @@
1
+ export const OTHER_CATEGORY_KEY = "__other";
2
+ export const plainTitle = (parts) => parts.map((part) => (typeof part === "string" ? part : part.b)).join("");
3
+ export const findCategory = (categories, key) => Object.prototype.hasOwnProperty.call(categories, key) ? categories[key] : undefined;
4
+ export const zoneFor = (entry, categories) => {
5
+ var _a;
6
+ if (entry.archived)
7
+ return "archived";
8
+ const rule = (_a = findCategory(categories, entry.category)) === null || _a === void 0 ? void 0 : _a.needsYou;
9
+ if (rule === "always")
10
+ return "needsYou";
11
+ if (rule === "untilRead" && entry.isRead === false)
12
+ return "needsYou";
13
+ return "activity";
14
+ };
15
+ export const byNewest = (a, b) => b.entry.createdAt - a.entry.createdAt;
16
+ export const matchesNotificationQuery = (entry, query) => {
17
+ const words = query.toLowerCase().split(/\s+/).filter(Boolean);
18
+ if (!words.length)
19
+ return true;
20
+ const haystack = [plainTitle(entry.title), entry.context, entry.preview, entry.eyebrow, entry.subject, entry.body]
21
+ .filter((part) => Boolean(part))
22
+ .join(" ")
23
+ .toLowerCase();
24
+ return words.every((word) => haystack.includes(word));
25
+ };
26
+ export const threadify = (items) => {
27
+ const threads = [];
28
+ const byKey = new Map();
29
+ items.forEach((item) => {
30
+ const key = item.entry.groupKey;
31
+ const existing = key === undefined ? undefined : byKey.get(key);
32
+ if (existing) {
33
+ existing.rest.push(item);
34
+ return;
35
+ }
36
+ const thread = { key: item.entry.id, lead: item, rest: [] };
37
+ threads.push(thread);
38
+ if (key !== undefined)
39
+ byKey.set(key, thread);
40
+ });
41
+ return threads;
42
+ };
43
+ export const threadEntries = (thread) => [thread.lead, ...thread.rest];
44
+ export const threadIds = (thread) => threadEntries(thread).map((item) => item.entry.id);
45
+ export const threadHasUnread = (thread) => threadEntries(thread).some((item) => item.entry.isRead === false);
46
+ const isShown = (item) => !item.state.isHidden;
47
+ const single = (item) => ({ key: item.entry.id, lead: item, rest: [] });
48
+ export const groupByCategory = (items, categories, otherLabel) => {
49
+ const byCategory = new Map();
50
+ items.forEach((item) => {
51
+ const key = findCategory(categories, item.entry.category) ? item.entry.category : OTHER_CATEGORY_KEY;
52
+ const list = byCategory.get(key);
53
+ if (list)
54
+ list.push(item);
55
+ else
56
+ byCategory.set(key, [item]);
57
+ });
58
+ const order = [...Object.keys(categories), OTHER_CATEGORY_KEY].filter((key) => byCategory.has(key));
59
+ const groups = order.flatMap((key) => {
60
+ const list = byCategory.get(key);
61
+ if (!list || !list.some(isShown))
62
+ return [];
63
+ const category = findCategory(categories, key);
64
+ return [
65
+ {
66
+ key,
67
+ label: category ? category.label : otherLabel,
68
+ count: list.filter((item) => item.state.isPending).length,
69
+ threads: list.map(single),
70
+ },
71
+ ];
72
+ });
73
+ return groups.length === 1 ? [{ ...groups[0], label: null }] : groups;
74
+ };
75
+ export const groupByDay = (items, format, now) => {
76
+ const buckets = new Map();
77
+ items.forEach((item) => {
78
+ const bucket = format.dayBucket(item.entry.createdAt, now);
79
+ const list = buckets.get(bucket);
80
+ if (list)
81
+ list.push(item);
82
+ else
83
+ buckets.set(bucket, [item]);
84
+ });
85
+ return [...buckets]
86
+ .filter(([, list]) => list.some(isShown))
87
+ .map(([bucket, list]) => ({ key: bucket, label: format.dayLabel(bucket), count: 0, threads: threadify(list) }));
88
+ };
@@ -0,0 +1,88 @@
1
+ export const DEFAULT_NOTIFICATION_LOCALE = "en-IN";
2
+ export const defaultNotificationLabels = (locale = DEFAULT_NOTIFICATION_LOCALE) => {
3
+ const english = new Intl.PluralRules("en");
4
+ const digits = new Intl.NumberFormat(locale);
5
+ const n = (count) => digits.format(count);
6
+ const many = (count, one, other) => (english.select(count) === "one" ? one : other);
7
+ const verb = (label) => label.toLowerCase();
8
+ return {
9
+ unread: "Unread",
10
+ actionName: (actionLabel, subject) => `${actionLabel}: ${subject}`,
11
+ markRead: "Mark as read",
12
+ markUnread: "Mark as unread",
13
+ archive: "Archive",
14
+ restore: "Move back to Activity",
15
+ moreActions: (title) => `More actions: ${title}`,
16
+ select: (title) => `Select: ${title}`,
17
+ threadShow: (count) => `Show ${n(count)} more from this thread`,
18
+ threadHide: (count) => `Hide ${n(count)} more from this thread`,
19
+ waiting: (days) => `Waiting ${n(days)} ${many(days, "day", "days")}`,
20
+ noAccess: "No access",
21
+ changedTo: "changed to",
22
+ offlineNote: "Reconnect to respond",
23
+ reasonLabel: "Add a note",
24
+ reasonOptional: "(optional)",
25
+ reasonPlaceholder: "The requester sees this",
26
+ reasonMissingNegative: "Add a reason so they know what to change.",
27
+ reasonMissing: "This needs a comment before it can be sent.",
28
+ keyCtrl: "Ctrl",
29
+ keyEnter: "Enter",
30
+ keyEsc: "Esc",
31
+ toSend: "to send",
32
+ toCancel: "to cancel",
33
+ cancel: "Cancel",
34
+ undo: "Undo",
35
+ sending: "Saving…",
36
+ couldNot: (actionLabel) => `Couldn't ${verb(actionLabel)}.`,
37
+ retry: "Try again",
38
+ dismiss: "Dismiss",
39
+ conflictMessage: "Someone already decided this.",
40
+ forbiddenMessage: "You can't decide this anymore.",
41
+ failedMessage: "The server didn't respond. Nothing was changed.",
42
+ reasonRequiredMessage: "This needs a comment before it can be sent.",
43
+ offlineMessage: "You're offline. Reconnect, then try again.",
44
+ decided: (doneLabel, subject) => `${doneLabel} · ${subject}`,
45
+ decidedAnnouncement: (doneLabel, subject) => `${doneLabel}: ${subject}. Undo available for 6 seconds.`,
46
+ requests: (count) => `${n(count)} ${many(count, "request", "requests")}`,
47
+ reasonQuote: (reason) => `Reason: ${reason}`,
48
+ decisionFailed: (actionLabel, count) => count > 1 ? `Couldn't ${verb(actionLabel)} ${n(count)} requests` : `Couldn't ${verb(actionLabel)}`,
49
+ conflicted: (count) => (count > 1 ? `Someone already decided ${n(count)} of these` : "Someone already decided this"),
50
+ forbidden: (count) => (count > 1 ? `You can't decide ${n(count)} of these anymore` : "You can't decide this anymore"),
51
+ reasonNeeded: (actionLabel) => `${actionLabel} needs a comment`,
52
+ archived: (count) => (count > 1 ? `${n(count)} notifications archived` : "Archived"),
53
+ restored: "Moved back to Activity",
54
+ markedRead: (count) => `Marked ${n(count)} as read`,
55
+ markAllFailed: "Couldn't mark them as read",
56
+ readFailed: (read) => (read ? "Couldn't mark as read" : "Couldn't mark as unread"),
57
+ archiveFailed: "Couldn't archive",
58
+ restoreFailed: "Couldn't move it back",
59
+ extraFailed: (actionLabel) => `Couldn't ${verb(actionLabel)}`,
60
+ undone: "Undone",
61
+ decisionUndone: "Undone. The request is back in Needs you.",
62
+ arrivedOne: (title) => `New notification: ${title}`,
63
+ arrivedMany: (count) => `${n(count)} new notifications`,
64
+ openIn: (appName) => (appName ? `Open in ${appName}` : "Open"),
65
+ blockedTitle: "You can't open this anymore",
66
+ back: "Back",
67
+ otherCategory: "Other",
68
+ now: "now",
69
+ justNow: "just now",
70
+ todayAt: (time) => `Today, ${time}`,
71
+ yesterdayAt: (time) => `Yesterday, ${time}`,
72
+ today: "Today",
73
+ yesterday: "Yesterday",
74
+ thisWeek: "This week",
75
+ older: "Older",
76
+ };
77
+ };
78
+ export const DEFAULT_NOTIFICATION_LABELS = defaultNotificationLabels();
79
+ export const mergeDefined = (base, overrides = {}) => {
80
+ const merged = { ...base };
81
+ Object.keys(overrides).forEach((key) => {
82
+ const value = overrides[key];
83
+ if (value !== undefined)
84
+ merged[key] = value;
85
+ });
86
+ return merged;
87
+ };
88
+ export const resolveNotificationLabels = (locale = DEFAULT_NOTIFICATION_LOCALE, overrides = {}) => mergeDefined(defaultNotificationLabels(locale), overrides);
@@ -0,0 +1,274 @@
1
+ import { byNewest, zoneFor } from "./grouping.js";
2
+ export const EMPTY_OVERLAY = {
3
+ decisions: {},
4
+ read: {},
5
+ archived: {},
6
+ markAll: [],
7
+ reasonRequests: {},
8
+ dismissed: {},
9
+ epoch: 0,
10
+ };
11
+ const mapById = (record, fn) => {
12
+ let changed = false;
13
+ const next = {};
14
+ Object.keys(record).forEach((id) => {
15
+ const current = record[id];
16
+ if (current === undefined)
17
+ return;
18
+ const value = fn(current, id);
19
+ if (value !== current)
20
+ changed = true;
21
+ if (value !== undefined)
22
+ next[id] = value;
23
+ });
24
+ return changed ? next : record;
25
+ };
26
+ const hasAction = (entry, actionId) => Boolean(entry.actions && entry.actions.some((action) => action.id === actionId));
27
+ const foldsWithDecision = (phase) => phase === "undo" || phase === "sending" || phase === "resolved";
28
+ const withPrevious = (current) => current ? { ...current, previous: undefined } : undefined;
29
+ const setFlags = (record, ids, batch, value, phase) => {
30
+ const next = { ...record };
31
+ ids.forEach((id) => {
32
+ next[id] = { batch, value, phase, folded: false, previous: withPrevious(record[id]) };
33
+ });
34
+ return next;
35
+ };
36
+ const stepFlags = (record, batch, fn) => mapById(record, (rec) => (rec.batch === batch ? fn(rec) : rec));
37
+ const firstById = (entries) => {
38
+ const byId = new Map();
39
+ entries.forEach((entry) => {
40
+ if (!byId.has(entry.id))
41
+ byId.set(entry.id, entry);
42
+ });
43
+ return byId;
44
+ };
45
+ const reconcile = (state, entries) => {
46
+ const byId = firstById(entries);
47
+ const decisions = mapById(state.decisions, (rec, id) => {
48
+ const base = byId.get(id);
49
+ if (!base)
50
+ return undefined;
51
+ if ((rec.phase === "resolved" || rec.phase === "denied") && !hasAction(base, rec.decision.actionId))
52
+ return undefined;
53
+ return rec;
54
+ });
55
+ const dismissed = mapById(state.dismissed, (rec, id) => {
56
+ const base = byId.get(id);
57
+ return base && hasAction(base, rec.actionId) ? rec : undefined;
58
+ });
59
+ const settledFlag = (agrees) => (rec, id) => {
60
+ const base = byId.get(id);
61
+ if (!base)
62
+ return undefined;
63
+ return rec.phase === "settled" && agrees(base, rec) ? undefined : rec;
64
+ };
65
+ const read = mapById(state.read, settledFlag((base, rec) => base.isRead === rec.value));
66
+ const archived = mapById(state.archived, settledFlag((base, rec) => Boolean(base.archived) === rec.value));
67
+ const reasonRequests = mapById(state.reasonRequests, (req, id) => (byId.has(id) ? req : undefined));
68
+ const unchanged = decisions === state.decisions &&
69
+ dismissed === state.dismissed &&
70
+ read === state.read &&
71
+ archived === state.archived &&
72
+ reasonRequests === state.reasonRequests;
73
+ return unchanged ? state : { ...state, decisions, dismissed, read, archived, reasonRequests };
74
+ };
75
+ const settleDecisions = (state, outcomes) => {
76
+ const decisions = { ...state.decisions };
77
+ const reasonRequests = { ...state.reasonRequests };
78
+ Object.keys(outcomes).forEach((id) => {
79
+ const rec = decisions[id];
80
+ const outcome = outcomes[id];
81
+ if (!rec || rec.batch !== outcome.batch)
82
+ return;
83
+ const { error } = outcome;
84
+ if (!error) {
85
+ decisions[id] = { ...rec, phase: "resolved", folded: true, settledAt: state.epoch };
86
+ }
87
+ else if (error.kind === "reasonRequired") {
88
+ delete decisions[id];
89
+ reasonRequests[id] = { actionId: rec.decision.actionId, message: error.message };
90
+ }
91
+ else if (error.kind === "failed") {
92
+ decisions[id] = { ...rec, phase: "failed", folded: false, error };
93
+ }
94
+ else {
95
+ decisions[id] = { ...rec, phase: "denied", folded: false, error, settledAt: state.epoch };
96
+ }
97
+ });
98
+ return { ...state, decisions, reasonRequests };
99
+ };
100
+ export const overlayReducer = (state, action) => {
101
+ switch (action.type) {
102
+ case "decide": {
103
+ const decisions = { ...state.decisions };
104
+ const reasonRequests = { ...state.reasonRequests };
105
+ action.ids.forEach((id) => {
106
+ decisions[id] = { batch: action.batch, decision: action.decision, phase: "undo", folded: false };
107
+ delete reasonRequests[id];
108
+ });
109
+ return { ...state, decisions, reasonRequests };
110
+ }
111
+ case "retry":
112
+ return {
113
+ ...state,
114
+ decisions: mapById(state.decisions, (rec, id) => action.ids.includes(id) && rec.phase === "failed"
115
+ ? { batch: action.batch, decision: rec.decision, phase: "sending", folded: false }
116
+ : rec),
117
+ };
118
+ case "fold":
119
+ return {
120
+ ...state,
121
+ decisions: mapById(state.decisions, (rec) => rec.batch === action.batch && foldsWithDecision(rec.phase) && !rec.folded ? { ...rec, folded: true } : rec),
122
+ archived: stepFlags(state.archived, action.batch, (rec) => (rec.value && !rec.folded ? { ...rec, folded: true } : rec)),
123
+ };
124
+ case "send": {
125
+ const toSending = (rec) => (rec.phase === "undo" ? { ...rec, phase: "sending" } : rec);
126
+ return {
127
+ ...state,
128
+ decisions: mapById(state.decisions, (rec) => rec.batch === action.batch && rec.phase === "undo" ? { ...rec, phase: "sending" } : rec),
129
+ read: stepFlags(state.read, action.batch, toSending),
130
+ archived: stepFlags(state.archived, action.batch, toSending),
131
+ markAll: state.markAll.map((rec) => (rec.batch === action.batch && rec.phase === "undo" ? { ...rec, phase: "sending" } : rec)),
132
+ };
133
+ }
134
+ case "drop":
135
+ return {
136
+ ...state,
137
+ decisions: mapById(state.decisions, (rec) => (rec.batch === action.batch ? undefined : rec)),
138
+ read: stepFlags(state.read, action.batch, (rec) => rec.previous),
139
+ archived: stepFlags(state.archived, action.batch, (rec) => rec.previous),
140
+ markAll: state.markAll.filter((rec) => rec.batch !== action.batch),
141
+ };
142
+ case "settleDecisions":
143
+ return settleDecisions(state, action.outcomes);
144
+ case "dismiss": {
145
+ const rec = state.decisions[action.id];
146
+ if (!rec || rec.phase !== "denied")
147
+ return state;
148
+ const decisions = { ...state.decisions };
149
+ delete decisions[action.id];
150
+ return {
151
+ ...state,
152
+ decisions,
153
+ dismissed: { ...state.dismissed, [action.id]: { actionId: rec.decision.actionId, settledAt: rec.settledAt } },
154
+ };
155
+ }
156
+ case "clearReasonRequest": {
157
+ if (!state.reasonRequests[action.id])
158
+ return state;
159
+ const reasonRequests = { ...state.reasonRequests };
160
+ delete reasonRequests[action.id];
161
+ return { ...state, reasonRequests };
162
+ }
163
+ case "flag": {
164
+ const next = setFlags(state[action.key], action.ids, action.batch, action.value, action.phase);
165
+ return action.key === "read" ? { ...state, read: next } : { ...state, archived: next };
166
+ }
167
+ case "markAll":
168
+ return {
169
+ ...state,
170
+ read: setFlags(state.read, action.ids, action.batch, true, "undo"),
171
+ markAll: [...state.markAll, { batch: action.batch, scope: action.scope, phase: "undo" }],
172
+ };
173
+ case "settle": {
174
+ const settle = (rec) => rec.phase === "sending" ? { ...rec, phase: "settled", settledAt: state.epoch } : rec;
175
+ return {
176
+ ...state,
177
+ read: stepFlags(state.read, action.batch, settle),
178
+ archived: stepFlags(state.archived, action.batch, settle),
179
+ markAll: state.markAll.map((rec) => rec.batch === action.batch && rec.phase === "sending" ? { ...rec, phase: "settled", settledAt: state.epoch } : rec),
180
+ };
181
+ }
182
+ case "reconcile":
183
+ return reconcile(state, action.entries);
184
+ case "countsChanged":
185
+ return { ...state, epoch: state.epoch + 1, markAll: state.markAll.filter((rec) => rec.phase !== "settled") };
186
+ }
187
+ };
188
+ const counts = (rec, epoch) => rec.settledAt === undefined || rec.settledAt >= epoch;
189
+ const resolveFlags = (entry, overlay, countingOnly) => {
190
+ const keep = (rec) => rec && (!countingOnly || counts(rec, overlay.epoch)) ? rec : undefined;
191
+ const read = keep(overlay.read[entry.id]);
192
+ const archive = keep(overlay.archived[entry.id]);
193
+ const shownArchive = countingOnly ? archive === null || archive === void 0 ? void 0 : archive.value : archive && (archive.value ? archive.folded : false);
194
+ return {
195
+ isRead: read && entry.isRead !== undefined ? read.value : entry.isRead,
196
+ archived: shownArchive === undefined ? Boolean(entry.archived) : shownArchive,
197
+ isArchiving: Boolean(!countingOnly && archive && archive.value && !archive.folded),
198
+ decision: keep(overlay.decisions[entry.id]),
199
+ isDismissed: keep(overlay.dismissed[entry.id]) !== undefined,
200
+ };
201
+ };
202
+ const isTouched = (id, overlay) => overlay.decisions[id] !== undefined ||
203
+ overlay.read[id] !== undefined ||
204
+ overlay.archived[id] !== undefined ||
205
+ overlay.dismissed[id] !== undefined ||
206
+ overlay.reasonRequests[id] !== undefined;
207
+ const pendingStatus = (status) => status === "open" || status === "failed";
208
+ export const liveEntryFor = (entry, overlay, categories) => {
209
+ const flags = resolveFlags(entry, overlay, false);
210
+ const changed = flags.isRead !== entry.isRead || flags.archived !== Boolean(entry.archived);
211
+ const shown = changed ? { ...entry, isRead: flags.isRead, archived: flags.archived } : entry;
212
+ const zone = zoneFor(shown, categories);
213
+ const record = flags.decision;
214
+ const status = record ? record.phase : "open";
215
+ const state = {
216
+ zone,
217
+ status,
218
+ decision: record === null || record === void 0 ? void 0 : record.decision,
219
+ error: record === null || record === void 0 ? void 0 : record.error,
220
+ reasonRequest: overlay.reasonRequests[entry.id],
221
+ isHidden: flags.isArchiving || flags.isDismissed || Boolean(record && record.folded && foldsWithDecision(record.phase)),
222
+ isPending: zone === "needsYou" && !flags.isDismissed && pendingStatus(status),
223
+ };
224
+ return { entry: shown, state };
225
+ };
226
+ const contribution = (entry, overlay, categories) => {
227
+ const flags = overlay
228
+ ? resolveFlags(entry, overlay, true)
229
+ : { isRead: entry.isRead, archived: Boolean(entry.archived), isDismissed: false, decision: undefined };
230
+ const zone = zoneFor({ category: entry.category, isRead: flags.isRead, archived: flags.archived }, categories);
231
+ const status = flags.decision ? flags.decision.phase : "open";
232
+ return {
233
+ needsYou: zone === "needsYou" && !flags.isDismissed && pendingStatus(status) ? 1 : 0,
234
+ unread: zone === "activity" && flags.isRead === false ? 1 : 0,
235
+ };
236
+ };
237
+ export const buildLiveLists = (entries, overlay, categories, cache) => {
238
+ const byId = new Map();
239
+ const unique = [];
240
+ const lists = { needsYou: [], activity: [], archived: [] };
241
+ entries.forEach((entry) => {
242
+ if (byId.has(entry.id))
243
+ return;
244
+ unique.push(entry);
245
+ const cached = cache === null || cache === void 0 ? void 0 : cache.get(entry);
246
+ const touched = isTouched(entry.id, overlay);
247
+ const live = !touched && cached && cached.categories === categories ? cached.live : liveEntryFor(entry, overlay, categories);
248
+ if (!touched && cache)
249
+ cache.set(entry, { categories, live });
250
+ byId.set(entry.id, live);
251
+ lists[live.state.zone].push(live);
252
+ });
253
+ lists.needsYou.sort(byNewest);
254
+ lists.activity.sort(byNewest);
255
+ lists.archived.sort(byNewest);
256
+ return { lists, byId, entries: unique };
257
+ };
258
+ const isWholeActivity = (scope) => scope.zone === "activity" && !scope.category && !(scope.query && scope.query.trim());
259
+ export const optimisticCounts = (live, overlay, categories, server) => {
260
+ let needsYou = server.needsYou;
261
+ let unread = server.unread;
262
+ live.entries.forEach((entry) => {
263
+ if (!isTouched(entry.id, overlay))
264
+ return;
265
+ const before = contribution(entry, null, categories);
266
+ const after = contribution(entry, overlay, categories);
267
+ needsYou += after.needsYou - before.needsYou;
268
+ unread += after.unread - before.unread;
269
+ });
270
+ if (overlay.markAll.some((rec) => isWholeActivity(rec.scope) && counts(rec, overlay.epoch))) {
271
+ unread = live.lists.activity.filter((item) => item.entry.isRead === false && !item.state.isHidden).length;
272
+ }
273
+ return { needsYou: Math.max(0, needsYou), unread: Math.max(0, unread) };
274
+ };