nexus-shared 3.2.0 → 4.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.
- package/CHANGELOG.md +2359 -0
- package/README.md +1911 -1123
- package/dist/Client.Index.d.ts +27 -0
- package/dist/Client.Index.js +27 -0
- package/dist/Components/Chats/ChatComposer.d.ts +7 -1
- package/dist/Components/Chats/ChatComposer.js +9 -6
- package/dist/Components/Chats/ChatParts.d.ts +15 -2
- package/dist/Components/Chats/ChatParts.js +15 -4
- package/dist/Components/Chats/ChatPopup.d.ts +46 -0
- package/dist/Components/Chats/ChatPopup.js +34 -0
- package/dist/Components/Chats/ChatThread.d.ts +9 -1
- package/dist/Components/Chats/ChatThread.js +54 -6
- package/dist/Components/Dashboards/DashboardBoard.d.ts +62 -0
- package/dist/Components/Dashboards/DashboardBoard.js +594 -0
- package/dist/Components/Dashboards/DashboardWidgets.d.ts +155 -0
- package/dist/Components/Dashboards/DashboardWidgets.js +159 -0
- package/dist/Components/Dashboards/WidgetFrame.d.ts +51 -0
- package/dist/Components/Dashboards/WidgetFrame.js +45 -0
- package/dist/Components/Documents/Button.d.ts +6 -1
- package/dist/Components/Documents/Button.js +2 -2
- package/dist/Components/Documents/DocumentFrame.d.ts +50 -0
- package/dist/Components/Documents/DocumentFrame.js +83 -0
- package/dist/Components/Documents/Menu.d.ts +4 -0
- package/dist/Components/Documents/Menu.js +40 -24
- package/dist/Components/Documents/PrintDocument.d.ts +95 -0
- package/dist/Components/Documents/PrintDocument.js +62 -0
- package/dist/Components/Documents/TabButtons.d.ts +6 -0
- package/dist/Components/Documents/TabButtons.js +5 -4
- package/dist/Components/Documents/Tabs.d.ts +59 -0
- package/dist/Components/Documents/Tabs.js +70 -0
- package/dist/Components/Domains/DomainCard.d.ts +98 -0
- package/dist/Components/Domains/DomainCard.js +182 -0
- package/dist/Components/Forms/ApiForm.js +24 -3
- package/dist/Components/Forms/Crud.d.ts +2 -2
- package/dist/Components/Forms/Crud.js +628 -247
- package/dist/Components/Forms/CrudActions.d.ts +137 -0
- package/dist/Components/Forms/CrudActions.js +214 -0
- package/dist/Components/Forms/CrudHistory.d.ts +6 -1
- package/dist/Components/Forms/CrudHistory.js +14 -6
- package/dist/Components/Forms/CrudNarrowPicker.d.ts +31 -0
- package/dist/Components/Forms/CrudNarrowPicker.js +180 -0
- package/dist/Components/Forms/CrudParts.d.ts +37 -2
- package/dist/Components/Forms/CrudParts.js +123 -7
- package/dist/Components/Forms/CrudRowForms.d.ts +26 -0
- package/dist/Components/Forms/CrudRowForms.js +174 -0
- package/dist/Components/Forms/CrudScopes.d.ts +28 -0
- package/dist/Components/Forms/CrudScopes.js +76 -0
- package/dist/Components/Forms/CrudTree.d.ts +90 -0
- package/dist/Components/Forms/CrudTree.js +363 -0
- package/dist/Components/Forms/CrudWorkspace.d.ts +73 -0
- package/dist/Components/Forms/CrudWorkspace.js +177 -0
- package/dist/Components/Forms/ExcelImport.js +3 -3
- package/dist/Components/Forms/Form.d.ts +6 -5
- package/dist/Components/Forms/Form.js +134 -21
- package/dist/Components/Forms/FormLeaveGuard.d.ts +44 -0
- package/dist/Components/Forms/FormLeaveGuard.js +65 -0
- package/dist/Components/Forms/FormQuestion.d.ts +6 -0
- package/dist/Components/Forms/FormQuestion.js +28 -0
- package/dist/Components/Forms/RecordFlows.d.ts +50 -0
- package/dist/Components/Forms/RecordFlows.js +282 -0
- package/dist/Components/Forms/SubmitForm.d.ts +37 -2
- package/dist/Components/Forms/SubmitForm.js +182 -10
- package/dist/Components/Inputs/CheckBox.js +1 -1
- package/dist/Components/Inputs/ChoiceField.d.ts +21 -1
- package/dist/Components/Inputs/ChoiceField.js +28 -2
- package/dist/Components/Inputs/ComputedInput.d.ts +51 -0
- package/dist/Components/Inputs/ComputedInput.js +179 -0
- package/dist/Components/Inputs/Dropdown.d.ts +13 -0
- package/dist/Components/Inputs/Dropdown.js +8 -6
- package/dist/Components/Inputs/DropdownParts.d.ts +3 -1
- package/dist/Components/Inputs/DropdownParts.js +16 -4
- package/dist/Components/Inputs/Field.d.ts +7 -1
- package/dist/Components/Inputs/Field.js +2 -2
- package/dist/Components/Inputs/GroupForm.d.ts +6 -6
- package/dist/Components/Inputs/GroupForm.js +12 -12
- package/dist/Components/Inputs/InputRenderer.d.ts +13 -6
- package/dist/Components/Inputs/InputRenderer.js +37 -32
- package/dist/Components/Inputs/MarkdownEditor.d.ts +6 -0
- package/dist/Components/Inputs/MarkdownEditor.js +32 -6
- package/dist/Components/Inputs/PictureInput.d.ts +41 -0
- package/dist/Components/Inputs/PictureInput.js +75 -0
- package/dist/Components/Inputs/RadioGroup.d.ts +16 -3
- package/dist/Components/Inputs/RadioGroup.js +8 -3
- package/dist/Components/Inputs/RowsInput.d.ts +15 -2
- package/dist/Components/Inputs/RowsInput.js +96 -8
- package/dist/Components/Inputs/TabularForm.d.ts +5 -5
- package/dist/Components/Inputs/TabularForm.js +45 -32
- package/dist/Components/Inputs/TextArea.d.ts +6 -0
- package/dist/Components/Inputs/TextArea.js +11 -2
- package/dist/Components/Inputs/TextBoxTypes.d.ts +6 -0
- package/dist/Components/Inputs/TextBoxTypes.js +17 -0
- package/dist/Components/Inputs/TreeView.d.ts +76 -2
- package/dist/Components/Inputs/TreeView.js +227 -16
- package/dist/Components/Layouts/MessageBox.d.ts +15 -2
- package/dist/Components/Layouts/MessageBox.js +56 -7
- package/dist/Components/Layouts/Popup.d.ts +34 -7
- package/dist/Components/Layouts/Popup.js +143 -13
- package/dist/Components/Layouts/PopupDock.d.ts +3 -1
- package/dist/Components/Layouts/PopupDock.js +2 -1
- package/dist/Components/Layouts/PopupHost.js +5 -4
- package/dist/Components/Messages/MailClient.d.ts +50 -0
- package/dist/Components/Messages/MailClient.js +410 -0
- package/dist/Components/Messages/MailColumns.d.ts +21 -0
- package/dist/Components/Messages/MailColumns.js +92 -0
- package/dist/Components/Messages/MailCompose.d.ts +46 -0
- package/dist/Components/Messages/MailCompose.js +154 -0
- package/dist/Components/Messages/MailReader.d.ts +43 -0
- package/dist/Components/Messages/MailReader.js +207 -0
- package/dist/Components/Messages/MailThread.d.ts +71 -0
- package/dist/Components/Messages/MailThread.js +185 -0
- package/dist/Components/Messages/MessageBell.d.ts +41 -0
- package/dist/Components/Messages/MessageBell.js +76 -0
- package/dist/Components/Viewers/DataTable.js +153 -49
- package/dist/Components/Viewers/DataTableColumns.d.ts +2 -2
- package/dist/Components/Viewers/DataTableColumns.js +2 -1
- package/dist/Components/Viewers/DataTableFilters.d.ts +1 -1
- package/dist/Components/Viewers/DataTableFilters.js +17 -2
- package/dist/Components/Viewers/DataTableParts.d.ts +23 -5
- package/dist/Components/Viewers/DataTableParts.js +26 -14
- package/dist/Components/Viewers/OwnerFiles.d.ts +227 -0
- package/dist/Components/Viewers/OwnerFiles.js +700 -0
- package/dist/Components/Viewers/QrCodeImage.d.ts +22 -0
- package/dist/Components/Viewers/QrCodeImage.js +15 -0
- package/dist/Components/Viewers/RecordCards.d.ts +16 -0
- package/dist/Components/Viewers/RecordCards.js +509 -0
- package/dist/Helpers/ApiModules.js +4 -0
- package/dist/Helpers/AuditEntries.d.ts +50 -0
- package/dist/Helpers/AuditEntries.js +96 -0
- package/dist/Helpers/BusyHooks.d.ts +6 -0
- package/dist/Helpers/BusyHooks.js +39 -0
- package/dist/Helpers/ChatBackend.d.ts +8 -1
- package/dist/Helpers/ChatBackend.js +18 -0
- package/dist/Helpers/ChatHelpers.d.ts +2 -0
- package/dist/Helpers/ChatHelpers.js +4 -1
- package/dist/Helpers/CrudBackend.d.ts +20 -2
- package/dist/Helpers/CrudBackend.js +46 -2
- package/dist/Helpers/CrudColumns.d.ts +91 -6
- package/dist/Helpers/CrudColumns.js +148 -7
- package/dist/Helpers/CrudDetailsTabs.d.ts +58 -0
- package/dist/Helpers/CrudDetailsTabs.js +126 -0
- package/dist/Helpers/CrudHelpers.d.ts +116 -2
- package/dist/Helpers/CrudHelpers.js +256 -0
- package/dist/Helpers/CrudNarrowPick.d.ts +83 -0
- package/dist/Helpers/CrudNarrowPick.js +157 -0
- package/dist/Helpers/CrudPickerPlan.d.ts +58 -0
- package/dist/Helpers/CrudPickerPlan.js +46 -0
- package/dist/Helpers/CrudRowForms.d.ts +32 -0
- package/dist/Helpers/CrudRowForms.js +76 -0
- package/dist/Helpers/CrudScopes.d.ts +32 -0
- package/dist/Helpers/CrudScopes.js +79 -0
- package/dist/Helpers/DashboardHelpers.d.ts +115 -0
- package/dist/Helpers/DashboardHelpers.js +305 -0
- package/dist/Helpers/DomainCards.d.ts +140 -0
- package/dist/Helpers/DomainCards.js +370 -0
- package/dist/Helpers/DropdownHelpers.d.ts +3 -1
- package/dist/Helpers/DropdownHelpers.js +6 -2
- package/dist/Helpers/ExcelHelpers.d.ts +1 -1
- package/dist/Helpers/ExcelHelpers.js +12 -11
- package/dist/Helpers/FileAdapter.d.ts +158 -0
- package/dist/Helpers/FileAdapter.js +173 -0
- package/dist/Helpers/FilePrimaries.d.ts +48 -0
- package/dist/Helpers/FilePrimaries.js +138 -0
- package/dist/Helpers/FormConfirm.d.ts +32 -0
- package/dist/Helpers/FormConfirm.js +40 -0
- package/dist/Helpers/FormStore.d.ts +100 -5
- package/dist/Helpers/FormStore.js +368 -25
- package/dist/Helpers/InputConditions.d.ts +56 -0
- package/dist/Helpers/InputConditions.js +192 -0
- package/dist/Helpers/InputMemory.d.ts +106 -0
- package/dist/Helpers/InputMemory.js +395 -0
- package/dist/Helpers/InputNarrowing.d.ts +50 -0
- package/dist/Helpers/InputNarrowing.js +148 -0
- package/dist/Helpers/InputParamsHelpers.d.ts +44 -1
- package/dist/Helpers/InputParamsHelpers.js +157 -74
- package/dist/Helpers/MailClientHelpers.d.ts +96 -0
- package/dist/Helpers/MailClientHelpers.js +211 -0
- package/dist/Helpers/MessageBackend.d.ts +152 -0
- package/dist/Helpers/MessageBackend.js +329 -0
- package/dist/Helpers/MessageHelpers.d.ts +160 -0
- package/dist/Helpers/MessageHelpers.js +573 -0
- package/dist/Helpers/MessageUnread.d.ts +95 -0
- package/dist/Helpers/MessageUnread.js +245 -0
- package/dist/Helpers/NexusDriveAdapter.d.ts +71 -0
- package/dist/Helpers/NexusDriveAdapter.js +236 -0
- package/dist/Helpers/NumberHelpers.d.ts +156 -0
- package/dist/Helpers/NumberHelpers.js +242 -0
- package/dist/Helpers/PagedPicker.d.ts +42 -0
- package/dist/Helpers/PagedPicker.js +85 -0
- package/dist/Helpers/Permissions.js +2 -1
- package/dist/Helpers/QrCode.d.ts +26 -0
- package/dist/Helpers/QrCode.js +463 -0
- package/dist/Helpers/RecordCards.d.ts +205 -0
- package/dist/Helpers/RecordCards.js +378 -0
- package/dist/Helpers/RequestCache.d.ts +14 -2
- package/dist/Helpers/RequestCache.js +22 -3
- package/dist/Helpers/RowsStore.d.ts +61 -2
- package/dist/Helpers/RowsStore.js +203 -16
- package/dist/Helpers/ServerPreview.d.ts +69 -0
- package/dist/Helpers/ServerPreview.js +99 -0
- package/dist/Helpers/TableColumns.js +4 -3
- package/dist/Helpers/TableExport.d.ts +7 -1
- package/dist/Helpers/TableExport.js +9 -3
- package/dist/Helpers/TableFilters.d.ts +1 -1
- package/dist/Helpers/TableFilters.js +24 -23
- package/dist/Helpers/TableHelpers.d.ts +38 -4
- package/dist/Helpers/TableHelpers.js +78 -10
- package/dist/Helpers/TableJoins.d.ts +13 -0
- package/dist/Helpers/TableJoins.js +34 -0
- package/dist/Helpers/TableStore.d.ts +28 -4
- package/dist/Helpers/TableStore.js +73 -11
- package/dist/Helpers/ToolbarFit.d.ts +23 -0
- package/dist/Helpers/ToolbarFit.js +30 -0
- package/dist/Helpers/TreeStore.d.ts +156 -6
- package/dist/Helpers/TreeStore.js +547 -18
- package/dist/Interfaces/ApiInterfaces.d.ts +10 -1
- package/dist/Interfaces/ApiInterfaces.js +10 -1
- package/dist/Interfaces/ChatInterfaces.d.ts +22 -0
- package/dist/Interfaces/CrudInterfaces.d.ts +539 -6
- package/dist/Interfaces/DashboardInterfaces.d.ts +123 -0
- package/dist/Interfaces/DashboardInterfaces.js +1 -0
- package/dist/Interfaces/DomainInterfaces.d.ts +145 -0
- package/dist/Interfaces/DomainInterfaces.js +1 -0
- package/dist/Interfaces/FileInterfaces.d.ts +183 -0
- package/dist/Interfaces/FileInterfaces.js +6 -0
- package/dist/Interfaces/FormInterfaces.d.ts +460 -32
- package/dist/Interfaces/FormInterfaces.js +79 -1
- package/dist/Interfaces/InputInterfaces.d.ts +22 -3
- package/dist/Interfaces/MailInterfaces.d.ts +192 -0
- package/dist/Interfaces/MailInterfaces.js +1 -0
- package/dist/Interfaces/RecordInterfaces.d.ts +367 -0
- package/dist/Interfaces/RecordInterfaces.js +1 -0
- package/dist/Interfaces/TableInterfaces.d.ts +102 -3
- package/dist/Services/ExcelApi.js +5 -0
- package/dist/Services/FileApi.d.ts +13 -0
- package/dist/Services/FileApi.js +29 -0
- package/dist/Services/ImageEditorLoader.d.ts +14 -0
- package/dist/Services/ImageEditorLoader.js +51 -0
- package/dist/Services/JoinSources.d.ts +79 -6
- package/dist/Services/JoinSources.js +132 -21
- package/dist/Services/MessageApi.d.ts +18 -0
- package/dist/Services/MessageApi.js +64 -0
- package/dist/Services/ModuleImport.d.ts +7 -0
- package/dist/Services/ModuleImport.js +22 -0
- package/dist/Shared.Index.d.ts +28 -0
- package/dist/Shared.Index.js +28 -0
- package/package.json +13 -2
- package/src/Styles/Nexus.Avatar.css +6 -5
- package/src/Styles/Nexus.Button.css +39 -4
- package/src/Styles/Nexus.Chat.css +114 -22
- package/src/Styles/Nexus.Choice.css +16 -0
- package/src/Styles/Nexus.Crud.css +486 -21
- package/src/Styles/Nexus.Dashboard.css +763 -0
- package/src/Styles/Nexus.Domain.css +384 -0
- package/src/Styles/Nexus.Dropdown.css +8 -0
- package/src/Styles/Nexus.Editor.css +20 -0
- package/src/Styles/Nexus.Files.css +437 -0
- package/src/Styles/Nexus.Form.css +5 -2
- package/src/Styles/Nexus.Index.css +7 -0
- package/src/Styles/Nexus.Input.css +73 -0
- package/src/Styles/Nexus.Mail.css +1062 -0
- package/src/Styles/Nexus.Menu.css +10 -1
- package/src/Styles/Nexus.Popup.css +135 -1
- package/src/Styles/Nexus.Print.css +288 -0
- package/src/Styles/Nexus.QrCode.css +12 -0
- package/src/Styles/Nexus.Record.css +430 -0
- package/src/Styles/Nexus.Tab.Buttons.css +87 -22
- package/src/Styles/Nexus.Table.css +1458 -1387
- package/src/Styles/Nexus.Theme.Picker.css +1 -0
- package/src/Styles/Nexus.Themes.css +12 -6
- package/src/Styles/Nexus.Tokens.css +8 -3
- package/src/Styles/Nexus.Tree.css +41 -1
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import type { ChatDomainInfo, ChatDomainValue, ChatId } from "../Interfaces/ChatInterfaces.ts";
|
|
2
|
+
import type { DatePreferences } from "../Interfaces/DateInterfaces.ts";
|
|
3
|
+
import type { DomainCardContext, DomainCardDefinition, DomainCardField, DomainCardParams, DomainCardShape, DomainCardSnapshot, DomainCardValueKind } from "../Interfaces/DomainInterfaces.ts";
|
|
4
|
+
/** How long a card's answer is kept for every card of the page that draws the same record, in milliseconds. */
|
|
5
|
+
export declare const DOMAIN_CARD_LIFETIME = 30000;
|
|
6
|
+
/**
|
|
7
|
+
* A kind's card, with `view` typed by what `load` answers.
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* defineChatDomains([{ domain: EDomainTypes.LeaveRequest, key: "leave-request", label: "Leave request", card: domainCard({
|
|
11
|
+
* load: (id, signal) => api.get<LeaveRequestCard>(REQUEST_CARD.leave(id), { signal }),
|
|
12
|
+
* view: leave => ({ title: `${leave.leaveTypeName} · ${formatDays(leave.days)}`, code: leave.requestCode, ... }),
|
|
13
|
+
* }) }]);
|
|
14
|
+
*/
|
|
15
|
+
export declare function domainCard<TData>(definition: DomainCardDefinition<TData>): DomainCardDefinition<TData>;
|
|
16
|
+
/** A kind's registered row and its card, or `undefined` when the app gave it none. */
|
|
17
|
+
export declare function domainCardOf(domainType: ChatDomainValue): {
|
|
18
|
+
info: ChatDomainInfo<ChatDomainValue>;
|
|
19
|
+
card: DomainCardDefinition;
|
|
20
|
+
} | undefined;
|
|
21
|
+
/** The key a record's answer is kept under: its kind, its id, and who reads it (the same record reads otherwise for another). */
|
|
22
|
+
export declare function domainCardKey(domainType: ChatDomainValue, id: ChatId, viewer?: ChatId | null): string;
|
|
23
|
+
/** The start of every key of one record (every reader's), or of every record of a kind without an id. */
|
|
24
|
+
export declare function domainCardRecordPrefix(domainType: ChatDomainValue, id?: ChatId): string;
|
|
25
|
+
/** One wait for a read: its answer, and a way to stop waiting (the last to stop aborts it). */
|
|
26
|
+
export interface DomainCardReading<T> {
|
|
27
|
+
promise: Promise<T>;
|
|
28
|
+
leave(): void;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Reads shared by every card on the page: one read of a record at a time, its answer kept for a lifetime, a failure not
|
|
32
|
+
* kept. A read nobody waits for any more is aborted.
|
|
33
|
+
*/
|
|
34
|
+
export declare class DomainCardReads {
|
|
35
|
+
private entries;
|
|
36
|
+
private now;
|
|
37
|
+
constructor(now?: () => number);
|
|
38
|
+
/** The answer kept under `key` while it is younger than `lifetime` and was read from a read started at or after `since`. */
|
|
39
|
+
peek<T>(key: string, lifetime?: number, since?: number): T | undefined;
|
|
40
|
+
/**
|
|
41
|
+
* The answer under `key`: a read on its way that started at or after `since`, else a kept fresh one, else a new read by
|
|
42
|
+
* `load`. `since` is the moment the record was seen to change: what was read before it does not answer.
|
|
43
|
+
*/
|
|
44
|
+
read<T>(key: string, load: (signal: AbortSignal) => Promise<T>, options?: {
|
|
45
|
+
lifetime?: number;
|
|
46
|
+
since?: number;
|
|
47
|
+
}): DomainCardReading<T>;
|
|
48
|
+
private wait;
|
|
49
|
+
/**
|
|
50
|
+
* Drops what is kept or on its way under keys that start with `prefix` (every key when none); a read on its way still
|
|
51
|
+
* answers its waiters. `keptOnly` leaves the reads on their way, so cards reading again for the same change still share one.
|
|
52
|
+
*/
|
|
53
|
+
drop(prefix?: string, keptOnly?: boolean): number;
|
|
54
|
+
/** Answers kept and reads on their way. */
|
|
55
|
+
get size(): number;
|
|
56
|
+
}
|
|
57
|
+
/** The reads every card of the page shares. */
|
|
58
|
+
export declare const domainCardReads: DomainCardReads;
|
|
59
|
+
/** A read the page called off (the card closed): not a failure to show. */
|
|
60
|
+
export declare function isDomainCardAbort(error: unknown): boolean;
|
|
61
|
+
/** Whether a failure means the reader may not open the record, or it is not there for them: 403 and 404 alike. */
|
|
62
|
+
export declare function isDomainCardRefusal(error: unknown): boolean;
|
|
63
|
+
/** What a run reads and how. */
|
|
64
|
+
export interface DomainCardRunOptions<TData> {
|
|
65
|
+
domainType: ChatDomainValue;
|
|
66
|
+
id: ChatId;
|
|
67
|
+
/** Who reads: part of the key, and told to `load`. */
|
|
68
|
+
viewer?: ChatId | null;
|
|
69
|
+
load: (signal: AbortSignal) => Promise<TData>;
|
|
70
|
+
/** Default 30,000. */
|
|
71
|
+
lifetime?: number;
|
|
72
|
+
/** Default: the page's `domainCardReads`. */
|
|
73
|
+
reads?: DomainCardReads;
|
|
74
|
+
now?: () => number;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* One card's read: loading, then the record, a refusal or a failure; read again when the record changes (the card stays
|
|
78
|
+
* on screen meanwhile, and says so when the record turned out different); tried again on Retry. Subscribe to draw it.
|
|
79
|
+
*/
|
|
80
|
+
export declare class DomainCardRun<TData = unknown> {
|
|
81
|
+
readonly domainType: ChatDomainValue;
|
|
82
|
+
readonly id: ChatId;
|
|
83
|
+
readonly key: string;
|
|
84
|
+
private options;
|
|
85
|
+
private reads;
|
|
86
|
+
private now;
|
|
87
|
+
private snapshot;
|
|
88
|
+
private listeners;
|
|
89
|
+
private reading;
|
|
90
|
+
private round;
|
|
91
|
+
constructor(options: DomainCardRunOptions<TData>);
|
|
92
|
+
private get lifetime();
|
|
93
|
+
readonly subscribe: (listener: () => void) => (() => void);
|
|
94
|
+
readonly getSnapshot: () => DomainCardSnapshot<TData>;
|
|
95
|
+
private set;
|
|
96
|
+
/** The card is drawn: reads, unless it already shows a kept answer. */
|
|
97
|
+
start(): void;
|
|
98
|
+
/** The card is gone: stops waiting (the read is aborted when no other card waits for it). */
|
|
99
|
+
stop(): void;
|
|
100
|
+
/**
|
|
101
|
+
* The record changed: reads it again, from a read started now. The card on screen stays until the answer lands, and
|
|
102
|
+
* what other readers kept of the record is forgotten, since it changed for them too.
|
|
103
|
+
*/
|
|
104
|
+
reload(): void;
|
|
105
|
+
/** After a failure: reads once more. */
|
|
106
|
+
retry(): void;
|
|
107
|
+
private read;
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* A record changed (the page approved it, a message said so): every card drawing it reads it again, and no answer kept
|
|
111
|
+
* from before is handed out. Without an id, every record of the kind.
|
|
112
|
+
*/
|
|
113
|
+
export declare function refreshDomainCards(domainType: ChatDomainValue, id?: ChatId, reads?: DomainCardReads): number;
|
|
114
|
+
/** The kind's name inside a sentence: "Leave request" reads "leave request"; "HR letter" keeps its capitals. */
|
|
115
|
+
export declare function domainCardNoun(kind?: string | null): string;
|
|
116
|
+
/** What a reader the record's module refused reads: the same for "not yours" and "not there", so a stranger learns nothing. */
|
|
117
|
+
export declare function domainCardRefusedText(kind?: string | null): string;
|
|
118
|
+
/** What a failed read says, beside Try again. */
|
|
119
|
+
export declare function domainCardFailedText(kind?: string | null): string;
|
|
120
|
+
/** What a card's values are written with: the reader's date settings and zone, number locale and currency. */
|
|
121
|
+
export interface DomainCardValueOptions {
|
|
122
|
+
preferences?: Partial<DatePreferences>;
|
|
123
|
+
timeZone?: string | null;
|
|
124
|
+
locale?: string;
|
|
125
|
+
currency?: string | null;
|
|
126
|
+
}
|
|
127
|
+
/** A value written by its kind, or "" when it is missing. A value its kind cannot read is written as it is. */
|
|
128
|
+
export declare function domainCardValueText(value: string | number | null | undefined, kind?: DomainCardValueKind, options?: DomainCardValueOptions): string;
|
|
129
|
+
/** The fields a card draws: the ones with a value, and the ones that say what stands in for a missing one. */
|
|
130
|
+
export declare function domainCardFields(params: Pick<DomainCardParams, "fields">): DomainCardField[];
|
|
131
|
+
/** What a placeholder holds when a kind gave nothing and none of its cards has loaded yet: a title, a state, four fields. */
|
|
132
|
+
export declare const DEFAULT_DOMAIN_CARD_SHAPE: DomainCardShape;
|
|
133
|
+
/** How many of each part a card drew. */
|
|
134
|
+
export declare function domainCardShape(params: DomainCardParams): DomainCardShape;
|
|
135
|
+
/** Keeps a kind's last drawn shape, so its next placeholder holds the same parts. */
|
|
136
|
+
export declare function rememberDomainCardShape(domainType: ChatDomainValue, shape: DomainCardShape): void;
|
|
137
|
+
/** The shape a kind's placeholder holds: its last card's, else what its card says, else the default. */
|
|
138
|
+
export declare function heldDomainCardShape(domainType: ChatDomainValue, card?: Pick<DomainCardDefinition, "held"> | null): DomainCardShape;
|
|
139
|
+
/** What `load`, `view` and `extra` are told about a record. */
|
|
140
|
+
export declare function domainCardContext(domainType: ChatDomainValue, id: ChatId, extra?: Omit<DomainCardContext, "domainType" | "id">): DomainCardContext;
|
|
@@ -0,0 +1,370 @@
|
|
|
1
|
+
import { getChatDomains } from "./ChatBackend.js";
|
|
2
|
+
import { formatDate } from "./DateHelpers.js";
|
|
3
|
+
import { formatDateTime } from "./DateTimeHelpers.js";
|
|
4
|
+
import { formatDays, formatMoney, formatNumber } from "./NumberHelpers.js";
|
|
5
|
+
/*
|
|
6
|
+
* A record's card outside React: which card a kind of record has, the reads every card on the page shares, one card's
|
|
7
|
+
* read from its first answer through reading it again, and the words and values it is drawn with. `DomainCard` draws
|
|
8
|
+
* a `DomainCardRun`'s snapshot; nothing here touches the DOM, so it is checked without a browser.
|
|
9
|
+
*
|
|
10
|
+
* The reads keep the owner's caching rules (as `RequestCache` does): one read of a record at a time however many cards
|
|
11
|
+
* draw it, an answer kept for a short life (30 seconds: a card is live, so less than the lists' five minutes), a
|
|
12
|
+
* failure never kept. A card read again because the record changed takes only a read started after the change.
|
|
13
|
+
*/
|
|
14
|
+
/** How long a card's answer is kept for every card of the page that draws the same record, in milliseconds. */
|
|
15
|
+
export const DOMAIN_CARD_LIFETIME = 30_000;
|
|
16
|
+
/**
|
|
17
|
+
* A kind's card, with `view` typed by what `load` answers.
|
|
18
|
+
*
|
|
19
|
+
* @example
|
|
20
|
+
* defineChatDomains([{ domain: EDomainTypes.LeaveRequest, key: "leave-request", label: "Leave request", card: domainCard({
|
|
21
|
+
* load: (id, signal) => api.get<LeaveRequestCard>(REQUEST_CARD.leave(id), { signal }),
|
|
22
|
+
* view: leave => ({ title: `${leave.leaveTypeName} · ${formatDays(leave.days)}`, code: leave.requestCode, ... }),
|
|
23
|
+
* }) }]);
|
|
24
|
+
*/
|
|
25
|
+
export function domainCard(definition) {
|
|
26
|
+
return definition;
|
|
27
|
+
}
|
|
28
|
+
/** A kind's registered row and its card, or `undefined` when the app gave it none. */
|
|
29
|
+
export function domainCardOf(domainType) {
|
|
30
|
+
const info = getChatDomains().find(row => String(row.domain) === String(domainType));
|
|
31
|
+
return info?.card ? { info, card: info.card } : undefined;
|
|
32
|
+
}
|
|
33
|
+
/** The key a record's answer is kept under: its kind, its id, and who reads it (the same record reads otherwise for another). */
|
|
34
|
+
export function domainCardKey(domainType, id, viewer) {
|
|
35
|
+
return [domainType, id, viewer ?? ""].map(part => encodeURIComponent(String(part))).join("|");
|
|
36
|
+
}
|
|
37
|
+
/** The start of every key of one record (every reader's), or of every record of a kind without an id. */
|
|
38
|
+
export function domainCardRecordPrefix(domainType, id) {
|
|
39
|
+
const kind = `${encodeURIComponent(String(domainType))}|`;
|
|
40
|
+
return id === undefined ? kind : `${kind}${encodeURIComponent(String(id))}|`;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Reads shared by every card on the page: one read of a record at a time, its answer kept for a lifetime, a failure not
|
|
44
|
+
* kept. A read nobody waits for any more is aborted.
|
|
45
|
+
*/
|
|
46
|
+
export class DomainCardReads {
|
|
47
|
+
entries = new Map();
|
|
48
|
+
now;
|
|
49
|
+
constructor(now = Date.now) {
|
|
50
|
+
this.now = now;
|
|
51
|
+
}
|
|
52
|
+
/** The answer kept under `key` while it is younger than `lifetime` and was read from a read started at or after `since`. */
|
|
53
|
+
peek(key, lifetime = DOMAIN_CARD_LIFETIME, since = 0) {
|
|
54
|
+
const entry = this.entries.get(key);
|
|
55
|
+
if (entry?.state !== "ready")
|
|
56
|
+
return undefined;
|
|
57
|
+
if (this.now() - entry.at >= lifetime) {
|
|
58
|
+
this.entries.delete(key);
|
|
59
|
+
return undefined;
|
|
60
|
+
}
|
|
61
|
+
return entry.startedAt >= since ? entry.value : undefined;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* The answer under `key`: a read on its way that started at or after `since`, else a kept fresh one, else a new read by
|
|
65
|
+
* `load`. `since` is the moment the record was seen to change: what was read before it does not answer.
|
|
66
|
+
*/
|
|
67
|
+
read(key, load, options = {}) {
|
|
68
|
+
const { lifetime = DOMAIN_CARD_LIFETIME, since = 0 } = options;
|
|
69
|
+
const entry = this.entries.get(key);
|
|
70
|
+
if (entry?.state === "loading" && entry.startedAt >= since)
|
|
71
|
+
return this.wait(key, entry);
|
|
72
|
+
const kept = this.peek(key, lifetime, since);
|
|
73
|
+
if (kept !== undefined)
|
|
74
|
+
return { promise: Promise.resolve(kept), leave: () => { } };
|
|
75
|
+
const controller = new AbortController();
|
|
76
|
+
const startedAt = this.now();
|
|
77
|
+
let answer;
|
|
78
|
+
try {
|
|
79
|
+
answer = Promise.resolve(load(controller.signal));
|
|
80
|
+
}
|
|
81
|
+
catch (error) {
|
|
82
|
+
answer = Promise.reject(error);
|
|
83
|
+
}
|
|
84
|
+
const loading = { state: "loading", startedAt, controller, waiters: 0, promise: answer };
|
|
85
|
+
loading.promise = answer.then(value => {
|
|
86
|
+
// A read dropped or overtaken on its way answers whoever waits for it, and is not kept.
|
|
87
|
+
if (this.entries.get(key) === loading) {
|
|
88
|
+
if (lifetime > 0)
|
|
89
|
+
this.entries.set(key, { state: "ready", value, startedAt, at: this.now() });
|
|
90
|
+
else
|
|
91
|
+
this.entries.delete(key);
|
|
92
|
+
}
|
|
93
|
+
return value;
|
|
94
|
+
}, (error) => {
|
|
95
|
+
if (this.entries.get(key) === loading)
|
|
96
|
+
this.entries.delete(key);
|
|
97
|
+
throw error;
|
|
98
|
+
});
|
|
99
|
+
// Nobody may be waiting when it fails (every card left): that is not an unhandled rejection.
|
|
100
|
+
loading.promise.catch(() => { });
|
|
101
|
+
this.entries.set(key, loading);
|
|
102
|
+
return this.wait(key, loading);
|
|
103
|
+
}
|
|
104
|
+
wait(key, loading) {
|
|
105
|
+
loading.waiters++;
|
|
106
|
+
let waiting = true;
|
|
107
|
+
return {
|
|
108
|
+
promise: loading.promise,
|
|
109
|
+
leave: () => {
|
|
110
|
+
if (!waiting)
|
|
111
|
+
return;
|
|
112
|
+
waiting = false;
|
|
113
|
+
loading.waiters--;
|
|
114
|
+
// A card that leaves and comes straight back (a remount) keeps the read: the abort waits a turn.
|
|
115
|
+
setTimeout(() => {
|
|
116
|
+
if (loading.waiters > 0)
|
|
117
|
+
return;
|
|
118
|
+
if (this.entries.get(key) === loading)
|
|
119
|
+
this.entries.delete(key);
|
|
120
|
+
loading.controller.abort();
|
|
121
|
+
}, 0);
|
|
122
|
+
},
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Drops what is kept or on its way under keys that start with `prefix` (every key when none); a read on its way still
|
|
127
|
+
* answers its waiters. `keptOnly` leaves the reads on their way, so cards reading again for the same change still share one.
|
|
128
|
+
*/
|
|
129
|
+
drop(prefix = "", keptOnly = false) {
|
|
130
|
+
let dropped = 0;
|
|
131
|
+
for (const [key, entry] of [...this.entries]) {
|
|
132
|
+
if (!key.startsWith(prefix) || (keptOnly && entry.state !== "ready"))
|
|
133
|
+
continue;
|
|
134
|
+
this.entries.delete(key);
|
|
135
|
+
dropped++;
|
|
136
|
+
}
|
|
137
|
+
return dropped;
|
|
138
|
+
}
|
|
139
|
+
/** Answers kept and reads on their way. */
|
|
140
|
+
get size() {
|
|
141
|
+
return this.entries.size;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
/** The reads every card of the page shares. */
|
|
145
|
+
export const domainCardReads = new DomainCardReads();
|
|
146
|
+
// Failures
|
|
147
|
+
const statusOf = (error) => {
|
|
148
|
+
const status = error?.status;
|
|
149
|
+
return typeof status === "number" ? status : undefined;
|
|
150
|
+
};
|
|
151
|
+
/** A read the page called off (the card closed): not a failure to show. */
|
|
152
|
+
export function isDomainCardAbort(error) {
|
|
153
|
+
return error?.name === "AbortError";
|
|
154
|
+
}
|
|
155
|
+
/** Whether a failure means the reader may not open the record, or it is not there for them: 403 and 404 alike. */
|
|
156
|
+
export function isDomainCardRefusal(error) {
|
|
157
|
+
const status = statusOf(error);
|
|
158
|
+
return status === 403 || status === 404;
|
|
159
|
+
}
|
|
160
|
+
/** Every run that is drawn, for `refreshDomainCards`. */
|
|
161
|
+
const drawn = new Set();
|
|
162
|
+
/** Two answers of the same record: the same when their JSON is. */
|
|
163
|
+
const sameData = (a, b) => {
|
|
164
|
+
try {
|
|
165
|
+
return JSON.stringify(a) === JSON.stringify(b);
|
|
166
|
+
}
|
|
167
|
+
catch {
|
|
168
|
+
return a === b;
|
|
169
|
+
}
|
|
170
|
+
};
|
|
171
|
+
/**
|
|
172
|
+
* One card's read: loading, then the record, a refusal or a failure; read again when the record changes (the card stays
|
|
173
|
+
* on screen meanwhile, and says so when the record turned out different); tried again on Retry. Subscribe to draw it.
|
|
174
|
+
*/
|
|
175
|
+
export class DomainCardRun {
|
|
176
|
+
domainType;
|
|
177
|
+
id;
|
|
178
|
+
key;
|
|
179
|
+
options;
|
|
180
|
+
reads;
|
|
181
|
+
now;
|
|
182
|
+
snapshot;
|
|
183
|
+
listeners = new Set();
|
|
184
|
+
reading = null;
|
|
185
|
+
round = 0;
|
|
186
|
+
constructor(options) {
|
|
187
|
+
this.options = options;
|
|
188
|
+
this.domainType = options.domainType;
|
|
189
|
+
this.id = options.id;
|
|
190
|
+
this.key = domainCardKey(options.domainType, options.id, options.viewer);
|
|
191
|
+
this.reads = options.reads ?? domainCardReads;
|
|
192
|
+
this.now = options.now ?? Date.now;
|
|
193
|
+
// A record read a moment ago by another card is drawn at once, without a placeholder.
|
|
194
|
+
const kept = this.reads.peek(this.key, this.lifetime);
|
|
195
|
+
this.snapshot = kept === undefined ? { status: "loading", refreshing: false, refreshFailed: false } : { status: "ready", data: kept, refreshing: false, refreshFailed: false };
|
|
196
|
+
}
|
|
197
|
+
get lifetime() {
|
|
198
|
+
return this.options.lifetime ?? DOMAIN_CARD_LIFETIME;
|
|
199
|
+
}
|
|
200
|
+
subscribe = (listener) => {
|
|
201
|
+
this.listeners.add(listener);
|
|
202
|
+
return () => this.listeners.delete(listener);
|
|
203
|
+
};
|
|
204
|
+
getSnapshot = () => this.snapshot;
|
|
205
|
+
set(next) {
|
|
206
|
+
this.snapshot = { ...this.snapshot, ...next };
|
|
207
|
+
for (const listener of this.listeners)
|
|
208
|
+
listener();
|
|
209
|
+
}
|
|
210
|
+
/** The card is drawn: reads, unless it already shows a kept answer. */
|
|
211
|
+
start() {
|
|
212
|
+
drawn.add(this);
|
|
213
|
+
if (this.snapshot.status === "ready" || this.reading)
|
|
214
|
+
return;
|
|
215
|
+
this.read(0);
|
|
216
|
+
}
|
|
217
|
+
/** The card is gone: stops waiting (the read is aborted when no other card waits for it). */
|
|
218
|
+
stop() {
|
|
219
|
+
drawn.delete(this);
|
|
220
|
+
this.round++;
|
|
221
|
+
this.reading?.leave();
|
|
222
|
+
this.reading = null;
|
|
223
|
+
}
|
|
224
|
+
/**
|
|
225
|
+
* The record changed: reads it again, from a read started now. The card on screen stays until the answer lands, and
|
|
226
|
+
* what other readers kept of the record is forgotten, since it changed for them too.
|
|
227
|
+
*/
|
|
228
|
+
reload() {
|
|
229
|
+
this.reads.drop(domainCardRecordPrefix(this.domainType, this.id), true);
|
|
230
|
+
this.read(this.now());
|
|
231
|
+
}
|
|
232
|
+
/** After a failure: reads once more. */
|
|
233
|
+
retry() {
|
|
234
|
+
this.read(this.now());
|
|
235
|
+
}
|
|
236
|
+
read(since) {
|
|
237
|
+
const round = ++this.round;
|
|
238
|
+
this.reading?.leave();
|
|
239
|
+
const shown = this.snapshot.status === "ready" || this.snapshot.status === "refused";
|
|
240
|
+
if (shown)
|
|
241
|
+
this.set({ refreshing: true });
|
|
242
|
+
else
|
|
243
|
+
this.set({ status: "loading", refreshing: false, refreshFailed: false, error: undefined });
|
|
244
|
+
const reading = this.reads.read(this.key, this.options.load, { lifetime: this.lifetime, since });
|
|
245
|
+
this.reading = reading;
|
|
246
|
+
reading.promise.then(data => {
|
|
247
|
+
if (round !== this.round)
|
|
248
|
+
return;
|
|
249
|
+
this.reading = null;
|
|
250
|
+
const before = this.snapshot;
|
|
251
|
+
const changed = before.status === "ready" && before.data !== undefined && !sameData(before.data, data);
|
|
252
|
+
this.set({ status: "ready", data, refreshing: false, refreshFailed: false, error: undefined, ...(changed && { changedAt: this.now() }) });
|
|
253
|
+
}, (error) => {
|
|
254
|
+
if (round !== this.round)
|
|
255
|
+
return;
|
|
256
|
+
this.reading = null;
|
|
257
|
+
if (isDomainCardAbort(error)) {
|
|
258
|
+
this.set({ refreshing: false });
|
|
259
|
+
return;
|
|
260
|
+
}
|
|
261
|
+
if (isDomainCardRefusal(error)) {
|
|
262
|
+
// The reader lost the record, or it is gone: nothing of it stays on screen.
|
|
263
|
+
this.set({ status: "refused", data: undefined, refreshing: false, refreshFailed: false, changedAt: undefined, error });
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
266
|
+
if (this.snapshot.status === "ready")
|
|
267
|
+
this.set({ refreshing: false, refreshFailed: true, error });
|
|
268
|
+
else
|
|
269
|
+
this.set({ status: "failed", refreshing: false, error });
|
|
270
|
+
});
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
/**
|
|
274
|
+
* A record changed (the page approved it, a message said so): every card drawing it reads it again, and no answer kept
|
|
275
|
+
* from before is handed out. Without an id, every record of the kind.
|
|
276
|
+
*/
|
|
277
|
+
export function refreshDomainCards(domainType, id, reads = domainCardReads) {
|
|
278
|
+
reads.drop(domainCardRecordPrefix(domainType, id));
|
|
279
|
+
let count = 0;
|
|
280
|
+
for (const run of drawn) {
|
|
281
|
+
if (String(run.domainType) !== String(domainType) || (id !== undefined && String(run.id) !== String(id)))
|
|
282
|
+
continue;
|
|
283
|
+
run.reload();
|
|
284
|
+
count++;
|
|
285
|
+
}
|
|
286
|
+
return count;
|
|
287
|
+
}
|
|
288
|
+
// Words
|
|
289
|
+
/** The kind's name inside a sentence: "Leave request" reads "leave request"; "HR letter" keeps its capitals. */
|
|
290
|
+
export function domainCardNoun(kind) {
|
|
291
|
+
const name = kind?.trim();
|
|
292
|
+
if (!name)
|
|
293
|
+
return "record";
|
|
294
|
+
return /^[A-Z][a-z]/.test(name) ? name[0].toLowerCase() + name.slice(1) : name;
|
|
295
|
+
}
|
|
296
|
+
/** What a reader the record's module refused reads: the same for "not yours" and "not there", so a stranger learns nothing. */
|
|
297
|
+
export function domainCardRefusedText(kind) {
|
|
298
|
+
return `You cannot open this ${domainCardNoun(kind)}.`;
|
|
299
|
+
}
|
|
300
|
+
/** What a failed read says, beside Try again. */
|
|
301
|
+
export function domainCardFailedText(kind) {
|
|
302
|
+
return `Could not load this ${domainCardNoun(kind)}.`;
|
|
303
|
+
}
|
|
304
|
+
const isMissing = (value) => value === null || value === undefined || (typeof value === "string" && value.trim() === "") || (typeof value === "number" && Number.isNaN(value));
|
|
305
|
+
const numberOf = (value) => {
|
|
306
|
+
const number = typeof value === "number" ? value : Number(value);
|
|
307
|
+
return Number.isFinite(number) ? number : null;
|
|
308
|
+
};
|
|
309
|
+
/** A value written by its kind, or "" when it is missing. A value its kind cannot read is written as it is. */
|
|
310
|
+
export function domainCardValueText(value, kind = "text", options = {}) {
|
|
311
|
+
if (isMissing(value))
|
|
312
|
+
return "";
|
|
313
|
+
const held = value;
|
|
314
|
+
const { preferences, timeZone, locale, currency } = options;
|
|
315
|
+
switch (kind) {
|
|
316
|
+
case "number": {
|
|
317
|
+
const number = numberOf(held);
|
|
318
|
+
return number === null ? String(held) : formatNumber(number, { decimals: 2, grouping: true, locale });
|
|
319
|
+
}
|
|
320
|
+
case "days": {
|
|
321
|
+
const number = numberOf(held);
|
|
322
|
+
return number === null ? String(held) : formatDays(number, { locale });
|
|
323
|
+
}
|
|
324
|
+
case "money": {
|
|
325
|
+
const number = numberOf(held);
|
|
326
|
+
return number === null ? String(held) : formatMoney(number, { currency, locale });
|
|
327
|
+
}
|
|
328
|
+
case "date":
|
|
329
|
+
return formatDate(String(held), preferences) || String(held);
|
|
330
|
+
case "datetime":
|
|
331
|
+
return formatDateTime(String(held), { ...preferences, ...(timeZone && { timeZone }) }) || String(held);
|
|
332
|
+
default:
|
|
333
|
+
return String(held);
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
/** The fields a card draws: the ones with a value, and the ones that say what stands in for a missing one. */
|
|
337
|
+
export function domainCardFields(params) {
|
|
338
|
+
return (params.fields ?? []).filter(field => !isMissing(field.value) || Boolean(field.empty));
|
|
339
|
+
}
|
|
340
|
+
// Shapes
|
|
341
|
+
/** What a placeholder holds when a kind gave nothing and none of its cards has loaded yet: a title, a state, four fields. */
|
|
342
|
+
export const DEFAULT_DOMAIN_CARD_SHAPE = { subtitle: false, state: true, fields: 4, figures: 0, hints: [], lines: 0, columns: 0, more: false, link: false };
|
|
343
|
+
/** How many of each part a card drew. */
|
|
344
|
+
export function domainCardShape(params) {
|
|
345
|
+
const lines = params.lines;
|
|
346
|
+
return {
|
|
347
|
+
subtitle: Boolean(params.subtitle || params.code || params.link),
|
|
348
|
+
state: Boolean(params.state),
|
|
349
|
+
fields: domainCardFields(params).length,
|
|
350
|
+
figures: params.figures?.length ?? 0,
|
|
351
|
+
hints: (params.figures ?? []).flatMap((figure, index) => (figure.hint ? [index] : [])),
|
|
352
|
+
lines: lines ? lines.rows.length : 0,
|
|
353
|
+
columns: lines && lines.rows.length > 0 ? lines.columns.length : 0,
|
|
354
|
+
more: Boolean(lines && (lines.more ?? 0) > 0),
|
|
355
|
+
link: Boolean(params.link),
|
|
356
|
+
};
|
|
357
|
+
}
|
|
358
|
+
const learned = new Map();
|
|
359
|
+
/** Keeps a kind's last drawn shape, so its next placeholder holds the same parts. */
|
|
360
|
+
export function rememberDomainCardShape(domainType, shape) {
|
|
361
|
+
learned.set(String(domainType), shape);
|
|
362
|
+
}
|
|
363
|
+
/** The shape a kind's placeholder holds: its last card's, else what its card says, else the default. */
|
|
364
|
+
export function heldDomainCardShape(domainType, card) {
|
|
365
|
+
return learned.get(String(domainType)) ?? { ...DEFAULT_DOMAIN_CARD_SHAPE, ...card?.held };
|
|
366
|
+
}
|
|
367
|
+
/** What `load`, `view` and `extra` are told about a record. */
|
|
368
|
+
export function domainCardContext(domainType, id, extra = {}) {
|
|
369
|
+
return { domainType, id, ...extra };
|
|
370
|
+
}
|
|
@@ -45,8 +45,10 @@ export declare function filterItems<TItem extends DropdownItem>(items: readonly
|
|
|
45
45
|
export declare function defaultSearchKeys(labelKey: string, descriptionKey: string, valueKey: string, columns?: readonly DropdownColumn<never>[]): string[];
|
|
46
46
|
/** Whether a column draws a picture: an avatar, an image, or an icon. The first one also leads the value in the box. */
|
|
47
47
|
export declare function isMediaColumn(column: Pick<DropdownColumn<never>, "type">): boolean;
|
|
48
|
-
/** A column's grid track: its `width`, or `auto` for pictures and
|
|
48
|
+
/** A column's grid track: its `width`, or `auto` for pictures, codes and money and `minmax(0, 1fr)` for text. */
|
|
49
49
|
export declare function columnTrack(column: Pick<DropdownColumn<never>, "type" | "width">): string;
|
|
50
|
+
/** Where a column's cells and heading sit: its own `align`, else at the end for money (figures line up), else at the start. */
|
|
51
|
+
export declare function dropdownColumnAlign(column: Pick<DropdownColumn<never>, "type" | "align">): "start" | "center" | "end";
|
|
50
52
|
/**
|
|
51
53
|
* Up to two initials of a name, from its first and last words: "Ram Bahadur Sharma" → "RS", "admin" → "A",
|
|
52
54
|
* "sita.rai" → "SR". Letters keep their script, so "राम शर्मा" → "रश".
|
|
@@ -150,11 +150,15 @@ export function defaultSearchKeys(labelKey, descriptionKey, valueKey, columns) {
|
|
|
150
150
|
export function isMediaColumn(column) {
|
|
151
151
|
return column.type === "avatar" || column.type === "image" || column.type === "icon";
|
|
152
152
|
}
|
|
153
|
-
/** A column's grid track: its `width`, or `auto` for pictures and
|
|
153
|
+
/** A column's grid track: its `width`, or `auto` for pictures, codes and money and `minmax(0, 1fr)` for text. */
|
|
154
154
|
export function columnTrack(column) {
|
|
155
155
|
if (column.width)
|
|
156
156
|
return column.width;
|
|
157
|
-
return isMediaColumn(column) || column.type === "code" ? "auto" : "minmax(0, 1fr)";
|
|
157
|
+
return isMediaColumn(column) || column.type === "code" || column.type === "money" ? "auto" : "minmax(0, 1fr)";
|
|
158
|
+
}
|
|
159
|
+
/** Where a column's cells and heading sit: its own `align`, else at the end for money (figures line up), else at the start. */
|
|
160
|
+
export function dropdownColumnAlign(column) {
|
|
161
|
+
return column.align ?? (column.type === "money" ? "end" : "start");
|
|
158
162
|
}
|
|
159
163
|
/**
|
|
160
164
|
* Up to two initials of a name, from its first and last words: "Ram Bahadur Sharma" → "RS", "admin" → "A",
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type InputParams } from "../Interfaces/FormInterfaces.ts";
|
|
2
2
|
import type { ExcelCellProblem, ExcelColumnInfo, ExcelDownload, ExcelReviewRow, ExcelSheetRow } from "../Interfaces/ExcelInterfaces.ts";
|
|
3
3
|
/** What a spreadsheet file is called in a file picker. */
|
|
4
4
|
export declare const EXCEL_FILE_ACCEPT = ".xlsx,application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { EInputTypes } from "../Interfaces/FormInterfaces.js";
|
|
1
2
|
import { ExcelDataType, ExcelRowKind } from "../Interfaces/ExcelInterfaces.js";
|
|
2
3
|
import { toIsoDate } from "./DateHelpers.js";
|
|
3
4
|
import { toIsoTime } from "./TimeHelpers.js";
|
|
@@ -111,28 +112,28 @@ export function excelColumnInput(column) {
|
|
|
111
112
|
...(column.maximum === null || column.maximum === undefined ? {} : { max: column.maximum }),
|
|
112
113
|
};
|
|
113
114
|
if (column.isReference)
|
|
114
|
-
return { ...shared, type:
|
|
115
|
+
return { ...shared, type: EInputTypes.Text, readOnly: true, readOnlyMessage: excelText("referenceLocked") };
|
|
115
116
|
if (column.isReadOnly)
|
|
116
|
-
return { ...shared, type:
|
|
117
|
+
return { ...shared, type: EInputTypes.Text, readOnly: true, readOnlyMessage: excelText("generated") };
|
|
117
118
|
switch (column.dataType) {
|
|
118
119
|
case ExcelDataType.Note:
|
|
119
|
-
return { ...shared, type:
|
|
120
|
+
return { ...shared, type: EInputTypes.TextArea, rows: 2, required, ...lengths };
|
|
120
121
|
case ExcelDataType.WholeNumber:
|
|
121
|
-
return { ...shared, type:
|
|
122
|
+
return { ...shared, type: EInputTypes.Number, required, decimals: 0, ...limits };
|
|
122
123
|
case ExcelDataType.Decimal:
|
|
123
|
-
return { ...shared, type:
|
|
124
|
+
return { ...shared, type: EInputTypes.Number, required, ...limits };
|
|
124
125
|
case ExcelDataType.Amount:
|
|
125
|
-
return { ...shared, type:
|
|
126
|
+
return { ...shared, type: EInputTypes.Number, required, decimals: 2, fixedDecimals: true, grouping: true, ...limits };
|
|
126
127
|
case ExcelDataType.Date:
|
|
127
|
-
return { ...shared, type:
|
|
128
|
+
return { ...shared, type: EInputTypes.Date, required };
|
|
128
129
|
case ExcelDataType.DateTime:
|
|
129
|
-
return { ...shared, type:
|
|
130
|
+
return { ...shared, type: EInputTypes.Text, required, pattern: MOMENT_PATTERN, placeholder: "2026-09-29 14:30" };
|
|
130
131
|
case ExcelDataType.Time:
|
|
131
|
-
return { ...shared, type:
|
|
132
|
+
return { ...shared, type: EInputTypes.Time, required };
|
|
132
133
|
case ExcelDataType.YesNo:
|
|
133
|
-
return { ...shared, type:
|
|
134
|
+
return { ...shared, type: EInputTypes.Dropdown, required, options: YES_NO, searchable: false };
|
|
134
135
|
default:
|
|
135
|
-
return { ...shared, type:
|
|
136
|
+
return { ...shared, type: EInputTypes.Text, required, ...lengths };
|
|
136
137
|
}
|
|
137
138
|
}
|
|
138
139
|
/** Every column of a sheet as the inputs of one editor row, in the order they read. */
|