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,14 @@
|
|
|
1
|
+
import type { ImageEditorModule } from "../Interfaces/FileInterfaces.ts";
|
|
2
|
+
/**
|
|
3
|
+
* The image editor module, downloaded once per page. A failed download is not kept, so the next Edit tries again.
|
|
4
|
+
* Rejects with an `Error` in plain words ("The image editor is not installed on this server yet.", "The image editor
|
|
5
|
+
* could not be downloaded..."), and logs the cause (a network error, a 404, a bundler that rewrote the import) to the
|
|
6
|
+
* console, so a page that shows the message never hides why.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* const { openImageEditor } = await loadImageEditor();
|
|
10
|
+
* const picture = await openImageEditor({ src: file, aspect: 1, thumbnail: 256 });
|
|
11
|
+
*/
|
|
12
|
+
export declare function loadImageEditor(): Promise<ImageEditorModule>;
|
|
13
|
+
/** Starts the download early, on the hover or focus of an Edit button, so the editor opens at once. Never rejects. */
|
|
14
|
+
export declare function preloadImageEditor(): void;
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { getFileAdapter } from "./FileApi.js";
|
|
3
|
+
import { importModule } from "./ModuleImport.js";
|
|
4
|
+
/*
|
|
5
|
+
* The image editor (`nexus-image-editor`, about 45 KB gzipped) is never in an app's bundle: the file backend serves it
|
|
6
|
+
* at a versioned address, and this loader downloads it the first time someone edits a picture. The address comes from
|
|
7
|
+
* the adapter (with Nexus, Drive's `Assets/File/nexus-image-editor.txt` names the current file), and the import is left
|
|
8
|
+
* alone by the bundler (`importModule`, ModuleImport.ts), so the module is fetched by the browser itself and cached by
|
|
9
|
+
* its immutable name.
|
|
10
|
+
*/
|
|
11
|
+
let loading = null;
|
|
12
|
+
/** What a person reads when the module itself could not be downloaded or run: the cause goes to the console. */
|
|
13
|
+
const NOT_DOWNLOADED = "The image editor could not be downloaded. Check the connection and try again; if it keeps failing, tell the site's support.";
|
|
14
|
+
/**
|
|
15
|
+
* The image editor module, downloaded once per page. A failed download is not kept, so the next Edit tries again.
|
|
16
|
+
* Rejects with an `Error` in plain words ("The image editor is not installed on this server yet.", "The image editor
|
|
17
|
+
* could not be downloaded..."), and logs the cause (a network error, a 404, a bundler that rewrote the import) to the
|
|
18
|
+
* console, so a page that shows the message never hides why.
|
|
19
|
+
*
|
|
20
|
+
* @example
|
|
21
|
+
* const { openImageEditor } = await loadImageEditor();
|
|
22
|
+
* const picture = await openImageEditor({ src: file, aspect: 1, thumbnail: 256 });
|
|
23
|
+
*/
|
|
24
|
+
export function loadImageEditor() {
|
|
25
|
+
loading ??= getFileAdapter()
|
|
26
|
+
.editorModuleUrl()
|
|
27
|
+
.then(url => importModule(url).catch((cause) => {
|
|
28
|
+
console.error(`loadImageEditor(): ${url} could not be imported.`, cause);
|
|
29
|
+
throw new Error(NOT_DOWNLOADED);
|
|
30
|
+
}), (cause) => {
|
|
31
|
+
// The address could not be read: the adapter says why in plain words ("not installed on this server yet").
|
|
32
|
+
console.error("loadImageEditor(): the image editor's address could not be read.", cause);
|
|
33
|
+
throw cause instanceof Error && cause.message ? cause : new Error(NOT_DOWNLOADED);
|
|
34
|
+
})
|
|
35
|
+
.then(module => {
|
|
36
|
+
if (typeof module?.openImageEditor !== "function") {
|
|
37
|
+
console.error("loadImageEditor(): the module downloaded has no openImageEditor.", module);
|
|
38
|
+
throw new Error("The image editor on the server is not one this page can open.");
|
|
39
|
+
}
|
|
40
|
+
return module;
|
|
41
|
+
})
|
|
42
|
+
.catch((error) => {
|
|
43
|
+
loading = null;
|
|
44
|
+
throw error instanceof Error ? error : new Error(NOT_DOWNLOADED);
|
|
45
|
+
});
|
|
46
|
+
return loading;
|
|
47
|
+
}
|
|
48
|
+
/** Starts the download early, on the hover or focus of an Edit button, so the editor opens at once. Never rejects. */
|
|
49
|
+
export function preloadImageEditor() {
|
|
50
|
+
loadImageEditor().catch(() => undefined);
|
|
51
|
+
}
|
|
@@ -6,27 +6,87 @@ import type { TableJoinSource, TableJoinedRecord } from "../Interfaces/TableInte
|
|
|
6
6
|
* A join's source as the table calls it. An endpoint becomes one that POSTs the keys to it as an array and reads the
|
|
7
7
|
* records it answers - a Nexus controller's `findListing` - made once per endpoint, so every table and picker naming it
|
|
8
8
|
* shares one source and so one cache. A source of the page's own is used as it is.
|
|
9
|
+
*
|
|
10
|
+
* `key` is the field the records answered are filed under, `id` unless told: one whose values are another field (a
|
|
11
|
+
* person's account, which their company row carries beside its own key) is a source of its own, kept apart.
|
|
12
|
+
*/
|
|
13
|
+
export declare function joinSource(source: ApiEndpoint | TableJoinSource, backend?: CrudBackend, key?: string): TableJoinSource;
|
|
14
|
+
/**
|
|
15
|
+
* The record a `findListing` answered for a key, or one the page remembered there (`TableJoins.remember`), at once and
|
|
16
|
+
* with no call, while it is fresh: `undefined` while it is not in, and once it was marked old (a save on that controller,
|
|
17
|
+
* a Reload), so a picker asks again and shows the record as it is now. A picker over that controller's records names a
|
|
18
|
+
* value with it (`peekOption`), and so does a CRUD's save, which takes an old one too (`aged`) over no name at all.
|
|
9
19
|
*/
|
|
10
|
-
export declare function
|
|
20
|
+
export declare function peekFound(findListing: ApiEndpoint, key: string, aged?: boolean): TableJoinedRecord | undefined;
|
|
11
21
|
/** The controller a picker offers records from: any with an `activeListing` and a `findListing` (`MODULES.controller(...)`). */
|
|
12
22
|
export interface PickerController {
|
|
13
23
|
activeListing: ApiEndpoint;
|
|
14
24
|
findListing: ApiEndpoint;
|
|
15
25
|
}
|
|
26
|
+
/**
|
|
27
|
+
* A controller whose picker reads another listing (`CrudPickerOptions.list`): it may have no `activeListing`, and a
|
|
28
|
+
* `findListing` only when it has one - a pay run's CRUD endpoints, every run of the company in its NormalListing.
|
|
29
|
+
*/
|
|
30
|
+
export interface PickerListController {
|
|
31
|
+
activeListing?: ApiEndpoint;
|
|
32
|
+
findListing?: ApiEndpoint;
|
|
33
|
+
}
|
|
16
34
|
/** What `crudPicker` needs to know of the records: the field a person reads, and the one under it. */
|
|
17
35
|
export interface CrudPickerOptions {
|
|
18
36
|
/** The field shown as the option: `"regionName"`. */
|
|
19
37
|
labelKey: string;
|
|
20
38
|
/** A field shown under it, such as the parent's name: `"regionName"` under a sub-region. */
|
|
21
39
|
descriptionKey?: string;
|
|
22
|
-
/**
|
|
40
|
+
/**
|
|
41
|
+
* The field that holds the value. Default `id`: the records' own key, which is what the controller's `findListing`
|
|
42
|
+
* takes. Another field - a person's account (`userId`), which their company row carries beside its own key - needs
|
|
43
|
+
* `find` for the values the kept list does not offer.
|
|
44
|
+
*/
|
|
23
45
|
valueKey?: string;
|
|
46
|
+
/**
|
|
47
|
+
* Where a value the kept list does not offer is looked up when `valueKey` is not `id`: an endpoint that takes such
|
|
48
|
+
* values, POSTed as an array as `findListing` takes keys, and answers the records, each read by its `valueKey` field;
|
|
49
|
+
* or a source of the page's own. Its answers are kept apart from `findListing`'s and marked old with them, after a save
|
|
50
|
+
* on the controller. Without it such a value is never looked up - `findListing` could not answer it - so a form names it
|
|
51
|
+
* from the record it holds (`selectedOptions`), or it shows as not found.
|
|
52
|
+
*/
|
|
53
|
+
find?: ApiEndpoint | TableJoinSource;
|
|
54
|
+
/**
|
|
55
|
+
* The query parameter the controller's `activeListing` narrows by on the server, for a picker another input narrows
|
|
56
|
+
* (`narrowedBy`): `"subscriptionGroupId"` asks `...?subscriptionGroupId={the group}` and keeps each answer apart. Without
|
|
57
|
+
* it, the whole list is read once and narrowed in the browser by the records' own field (`narrowedBy.key`). Narrowed by
|
|
58
|
+
* several values at once (`narrowedBy` as a list), it names the narrowings' keys the server takes as query parameters
|
|
59
|
+
* (a list of them, or one); a value the listing's path takes (`{itemId}` in `list`) always goes there, and the rest
|
|
60
|
+
* narrow in the browser.
|
|
61
|
+
*/
|
|
62
|
+
narrowQuery?: string | readonly string[];
|
|
63
|
+
/**
|
|
64
|
+
* The listing the options are read from, in place of the controller's `activeListing`: a controller with none, or a
|
|
65
|
+
* picker that offers more than the usable records - every pay run of the company (`PAY_RUNS.lists.normal`), drafts too.
|
|
66
|
+
* Read once and kept for the page as the active list is, and marked old when a CRUD on that controller changes a
|
|
67
|
+
* record (`expireLookups` reads the CRUD's `lists`).
|
|
68
|
+
*/
|
|
69
|
+
list?: ApiEndpoint;
|
|
70
|
+
/**
|
|
71
|
+
* Which records are usable: offered, and shown untagged when a form holds one. Default the backend's rule
|
|
72
|
+
* (`CrudBackend.isPickable`: for Nexus a record under 30, so one pending in 30-39 - an invited person - is not). A list
|
|
73
|
+
* whose records use a band of their own says so: `usable: run => run.recordStatus < NEXUS_TRASH_STATUS` offers a pay
|
|
74
|
+
* run's draft (31) beside the locked runs.
|
|
75
|
+
*/
|
|
76
|
+
usable?: (record: TableJoinedRecord) => boolean;
|
|
77
|
+
/**
|
|
78
|
+
* The option shown for a record, when its words are not one field: `run => ({ ...run, label: run.monthName + " · " +
|
|
79
|
+
* run.runCode, state: STATES[run.payRunState] })` with `labelKey: "label"` and `descriptionKey: "state"`. Keep the
|
|
80
|
+
* record's fields (spread it): the value, the search and the page read the option. Made once per record read.
|
|
81
|
+
*/
|
|
82
|
+
option?: (record: TableJoinedRecord) => TableJoinedRecord;
|
|
24
83
|
backend?: CrudBackend;
|
|
25
84
|
}
|
|
26
85
|
/** The dropdown params `crudPicker` gives: spread them into a dropdown's input. */
|
|
27
86
|
export interface CrudPickerParams {
|
|
28
87
|
loadOptions: (request: DropdownPageRequest) => Promise<DropdownPage<TableJoinedRecord>>;
|
|
29
88
|
resolveOptions: (values: OptionValue[]) => Promise<readonly TableJoinedRecord[]>;
|
|
89
|
+
peekOption: (key: string, aged?: boolean) => TableJoinedRecord | undefined;
|
|
30
90
|
valueKey: string;
|
|
31
91
|
labelKey: string;
|
|
32
92
|
descriptionKey?: string;
|
|
@@ -37,12 +97,25 @@ export interface CrudPickerParams {
|
|
|
37
97
|
* for every form on the page (the owner's caching rule: a second dropdown, or an Add row, never asks again), searched
|
|
38
98
|
* and paged in the browser. A value the form already holds that the list does not offer - a parent since trashed - is
|
|
39
99
|
* found through `findListing`, sharing the calls and answers of the tables' joins, and shows tagged, never offered again.
|
|
100
|
+
* A value that is not the records' key (`valueKey`) is found through the picker's own `find` instead.
|
|
101
|
+
*
|
|
102
|
+
* A picker can read another listing (`list`), say which records are usable (`usable`) and make each option's words
|
|
103
|
+
* (`option`): every pay run of the company, drafts too, named by month and code with its state under it.
|
|
40
104
|
*
|
|
41
|
-
* @example { type:
|
|
105
|
+
* @example { type: EInputTypes.Dropdown, name: "regionId", label: "Region", required: true, ...crudPicker(REGIONS, { labelKey: "regionName" }) }
|
|
106
|
+
* @example crudPicker(COMPANY_USERS, { labelKey: "fullname", valueKey: "userId", find: COMPANY_USERS_BY_ACCOUNT })
|
|
107
|
+
* @example crudPicker(PAY_RUNS, { list: PAY_RUNS.lists.normal, usable: run => run.recordStatus < NEXUS_TRASH_STATUS, labelKey: "label", descriptionKey: "state", option: runOption })
|
|
42
108
|
*/
|
|
43
|
-
export declare function crudPicker(controller: PickerController,
|
|
109
|
+
export declare function crudPicker(controller: PickerController, options: CrudPickerOptions): CrudPickerParams;
|
|
110
|
+
export declare function crudPicker(controller: PickerListController, options: CrudPickerOptions & {
|
|
111
|
+
list: ApiEndpoint;
|
|
112
|
+
}): CrudPickerParams;
|
|
44
113
|
/**
|
|
45
114
|
* Marks what the page looked up from a controller as old, after a write there may have changed it: the records its
|
|
46
|
-
* `findListing` named
|
|
115
|
+
* `findListing` named and its pickers' own finds answered, and the lists its pickers offer - its `activeListing`, and
|
|
116
|
+
* each of a CRUD's `lists` a picker reads instead (`CrudPickerOptions.list`). Drawn as they are until the next ask brings
|
|
117
|
+
* the new ones.
|
|
47
118
|
*/
|
|
48
|
-
export declare function expireLookups(controller:
|
|
119
|
+
export declare function expireLookups(controller: PickerListController & {
|
|
120
|
+
lists?: Partial<Record<string, ApiEndpoint>>;
|
|
121
|
+
}): void;
|
|
@@ -1,26 +1,37 @@
|
|
|
1
1
|
import { isApiEndpoint } from "../Helpers/ApiRoutes.js";
|
|
2
2
|
import { getCrudBackend } from "../Helpers/CrudBackend.js";
|
|
3
|
+
import { crudPickerNarrowAsk, crudPickerPlan, fitsNarrow, pathValueNames } from "../Helpers/CrudPickerPlan.js";
|
|
3
4
|
import { filterItems } from "../Helpers/DropdownHelpers.js";
|
|
4
|
-
import { joinSourceName, tableJoins } from "../Helpers/TableJoins.js";
|
|
5
|
+
import { JOIN_LIFETIME, joinSourceName, tableJoins } from "../Helpers/TableJoins.js";
|
|
5
6
|
import { api } from "./BrowserApi.js";
|
|
6
7
|
/*
|
|
7
8
|
* The browser's half of a join: the call that looks records up, and a picker that shares what it looked up. The cache
|
|
8
9
|
* itself (`tableJoins`) knows nothing of the API; this is where an endpoint becomes a source it can call.
|
|
9
10
|
*/
|
|
10
11
|
const made = new Map();
|
|
12
|
+
/** The sources of the pickers' own finds (`crudPicker` `find`), by the name of the `findListing` of their controller. */
|
|
13
|
+
const finds = new Map();
|
|
14
|
+
/** The lists a picker asked the server for, one per narrowing value (`narrowQuery`), by the name of their whole list. */
|
|
15
|
+
const narrowedLists = new Map();
|
|
16
|
+
/** A field with no value: what `narrow.orUnset` adds to a narrowed list. */
|
|
17
|
+
const unset = (value) => value === null || value === undefined || value === "";
|
|
11
18
|
/**
|
|
12
19
|
* A join's source as the table calls it. An endpoint becomes one that POSTs the keys to it as an array and reads the
|
|
13
20
|
* records it answers - a Nexus controller's `findListing` - made once per endpoint, so every table and picker naming it
|
|
14
21
|
* shares one source and so one cache. A source of the page's own is used as it is.
|
|
22
|
+
*
|
|
23
|
+
* `key` is the field the records answered are filed under, `id` unless told: one whose values are another field (a
|
|
24
|
+
* person's account, which their company row carries beside its own key) is a source of its own, kept apart.
|
|
15
25
|
*/
|
|
16
|
-
export function joinSource(source, backend = getCrudBackend()) {
|
|
26
|
+
export function joinSource(source, backend = getCrudBackend(), key = "id") {
|
|
17
27
|
if (!isApiEndpoint(source))
|
|
18
28
|
return source;
|
|
19
|
-
const name = joinSourceName(source)
|
|
29
|
+
const name = key === "id" ? joinSourceName(source) : `${joinSourceName(source)} by ${key}`;
|
|
20
30
|
let found = made.get(name);
|
|
21
31
|
if (!found) {
|
|
22
32
|
found = {
|
|
23
33
|
name,
|
|
34
|
+
...(key !== "id" && { key }),
|
|
24
35
|
async load(keys, signal) {
|
|
25
36
|
const answer = await api.send(source, { body: keys, signal, toast: false });
|
|
26
37
|
if (!answer.ok)
|
|
@@ -32,43 +43,143 @@ export function joinSource(source, backend = getCrudBackend()) {
|
|
|
32
43
|
}
|
|
33
44
|
return found;
|
|
34
45
|
}
|
|
46
|
+
/** What a source answered for a key, fresh, or - `aged` - also once it was marked old. */
|
|
47
|
+
function peekSource(name, key, aged) {
|
|
48
|
+
if (!aged)
|
|
49
|
+
return tableJoins.peekFresh(name, key);
|
|
50
|
+
const state = tableJoins.peek(name, key);
|
|
51
|
+
return state.status === "ready" ? state.record : undefined;
|
|
52
|
+
}
|
|
35
53
|
/**
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
* @example { type: "dropdown", name: "regionId", label: "Region", required: true, ...crudPicker(REGIONS, { labelKey: "regionName" }) }
|
|
54
|
+
* The record a `findListing` answered for a key, or one the page remembered there (`TableJoins.remember`), at once and
|
|
55
|
+
* with no call, while it is fresh: `undefined` while it is not in, and once it was marked old (a save on that controller,
|
|
56
|
+
* a Reload), so a picker asks again and shows the record as it is now. A picker over that controller's records names a
|
|
57
|
+
* value with it (`peekOption`), and so does a CRUD's save, which takes an old one too (`aged`) over no name at all.
|
|
42
58
|
*/
|
|
43
|
-
export function
|
|
44
|
-
|
|
59
|
+
export function peekFound(findListing, key, aged = false) {
|
|
60
|
+
return peekSource(joinSourceName(findListing), key, aged);
|
|
61
|
+
}
|
|
62
|
+
export function crudPicker(controller, options) {
|
|
63
|
+
const { labelKey, descriptionKey, find, narrowQuery, option, backend = getCrudBackend() } = options;
|
|
64
|
+
const plan = crudPickerPlan(controller, options, backend);
|
|
65
|
+
const valueKey = options.valueKey ?? "id";
|
|
66
|
+
const listName = joinSourceName(plan.list);
|
|
67
|
+
// Where a value the kept list does not offer is looked up: `findListing` while the values are the records' keys, the
|
|
68
|
+
// picker's own find for another field, and nowhere without either.
|
|
69
|
+
const lookup = plan.lookup ? joinSource(plan.lookup.source, backend, plan.lookup.key) : null;
|
|
70
|
+
if (find && lookup && controller.findListing) {
|
|
71
|
+
// Marked old with the controller's own lookups, after a save there (`expireLookups`).
|
|
72
|
+
const beside = joinSourceName(controller.findListing);
|
|
73
|
+
const kept = finds.get(beside) ?? new Set();
|
|
74
|
+
kept.add(lookup.name);
|
|
75
|
+
finds.set(beside, kept);
|
|
76
|
+
}
|
|
77
|
+
// Each record's option, made once per record read (a new read makes new records, and so new options).
|
|
78
|
+
const made = new WeakMap();
|
|
79
|
+
const shown = (record) => {
|
|
80
|
+
if (!option)
|
|
81
|
+
return record;
|
|
82
|
+
let kept = made.get(record);
|
|
83
|
+
if (!kept)
|
|
84
|
+
made.set(record, (kept = option(record)));
|
|
85
|
+
return kept;
|
|
86
|
+
};
|
|
87
|
+
const shownAll = (records) => (option ? records.map(shown) : records);
|
|
45
88
|
// The words a person reads, not the key: a Nexus key is an encrypted token, and a search would find letters in it.
|
|
46
89
|
const searchKeys = descriptionKey ? [labelKey, descriptionKey] : [labelKey];
|
|
47
|
-
const loadList = (signal) => api.send(
|
|
90
|
+
const loadList = (signal, query, params) => api.send(plan.list, { signal, toast: false, ...(query && { query }), ...(params && { params }) }).then(answer => {
|
|
48
91
|
if (!answer.ok)
|
|
49
92
|
throw new Error(answer.message ?? "The list could not be loaded.");
|
|
50
93
|
return backend.readList(answer.result);
|
|
51
94
|
});
|
|
95
|
+
// A listing whose path takes values (`StockBalance/Units/{storeId}/{itemId}`) is only ever read narrowed by them: its
|
|
96
|
+
// lists are kept one per values, and a saved value is named from them.
|
|
97
|
+
const pathNarrowed = pathValueNames(plan.list).length > 0;
|
|
98
|
+
const keepNarrowedName = (name) => {
|
|
99
|
+
const kept = narrowedLists.get(listName) ?? new Set();
|
|
100
|
+
kept.add(name);
|
|
101
|
+
narrowedLists.set(listName, kept);
|
|
102
|
+
};
|
|
103
|
+
/**
|
|
104
|
+
* The options narrowed by several values at once (`narrowedBy` as a list, a line's Tags of its Item in the form's Store),
|
|
105
|
+
* or by values the listing's path takes: each in the path, the query (`narrowQuery`) or the browser, as
|
|
106
|
+
* `crudPickerNarrowAsk` says.
|
|
107
|
+
*/
|
|
108
|
+
const narrowedByAll = async (narrows) => {
|
|
109
|
+
const ask = crudPickerNarrowAsk(plan.list, listName, narrows, narrowQuery);
|
|
110
|
+
if (!ask)
|
|
111
|
+
return [];
|
|
112
|
+
const server = Object.keys(ask.params).length + Object.keys(ask.query).length > 0;
|
|
113
|
+
if (server)
|
|
114
|
+
keepNarrowedName(ask.name);
|
|
115
|
+
const rows = server ? await tableJoins.list(ask.name, signal => loadList(signal, Object.keys(ask.query).length ? ask.query : undefined, Object.keys(ask.params).length ? ask.params : undefined)) : await tableJoins.list(listName, loadList);
|
|
116
|
+
return ask.filters.length ? rows.filter(row => ask.filters.every(narrow => fitsNarrow(row, narrow))) : rows;
|
|
117
|
+
};
|
|
118
|
+
/** Every record the page holds of a path-narrowed listing: each list kept, fresh unless `aged`. */
|
|
119
|
+
const heldNarrowed = (lifetime = JOIN_LIFETIME) => (pathNarrowed ? [...(narrowedLists.get(listName) ?? [])].flatMap(name => tableJoins.peekList(name, lifetime) ?? []) : []);
|
|
120
|
+
/** The options narrowed by another input's value (`narrowedBy`): asked of the server by `narrowQuery`, else the kept list's. */
|
|
121
|
+
const narrowedList = async ({ key, value, orUnset }) => {
|
|
122
|
+
// The server narrows by a value; which records it adds for `orUnset` is its endpoint's rule.
|
|
123
|
+
if (typeof narrowQuery === "string" && value !== null) {
|
|
124
|
+
const name = `${listName}?${narrowQuery}=${String(value)}`;
|
|
125
|
+
keepNarrowedName(name);
|
|
126
|
+
return tableJoins.list(name, signal => loadList(signal, { [narrowQuery]: String(value) }));
|
|
127
|
+
}
|
|
128
|
+
const all = await tableJoins.list(listName, loadList);
|
|
129
|
+
return all.filter(row => (value !== null && String(row[key]) === String(value)) || (orUnset && unset(row[key])));
|
|
130
|
+
};
|
|
52
131
|
return {
|
|
53
|
-
async loadOptions({ search, skip, pageSize }) {
|
|
54
|
-
|
|
132
|
+
async loadOptions({ search, skip, pageSize, narrow, narrows }) {
|
|
133
|
+
// Several values at once, or values the path takes: each where it goes. One value alone: as before.
|
|
134
|
+
const several = narrows && narrows.length > 1 ? narrows : null;
|
|
135
|
+
const all = shownAll(several || (narrow && pathNarrowed) ? await narrowedByAll(several ?? [narrow]) : narrow ? await narrowedList(narrow) : pathNarrowed ? [] : await tableJoins.list(listName, loadList));
|
|
55
136
|
const matches = search ? filterItems(all, search, searchKeys) : all;
|
|
56
137
|
return { items: matches.slice(skip, skip + pageSize), total: matches.length, hasMore: skip + pageSize < matches.length };
|
|
57
138
|
},
|
|
58
|
-
|
|
139
|
+
// A saved value the kept list offers is named from it, with no call (a form opened on a tree's country); only one it
|
|
140
|
+
// does not offer - a parent since trashed - is looked up.
|
|
141
|
+
async resolveOptions(values) {
|
|
142
|
+
const keys = values.map(String);
|
|
143
|
+
const kept = [...(tableJoins.peekList(listName) ?? []), ...heldNarrowed()];
|
|
144
|
+
const found = kept.filter((row, index) => keys.includes(String(row[valueKey])) && kept.findIndex(other => String(other[valueKey]) === String(row[valueKey])) === index);
|
|
145
|
+
const missing = keys.filter(key => !found.some(row => String(row[valueKey]) === key));
|
|
146
|
+
if (missing.length === 0 || !lookup)
|
|
147
|
+
return shownAll(found);
|
|
148
|
+
return shownAll([...found, ...(await tableJoins.records(lookup, missing))]);
|
|
149
|
+
},
|
|
150
|
+
// The same at once, from what the page holds: a box named on its first draw, a saved record named with no call. Only
|
|
151
|
+
// what is fresh, unless an old record is asked for: a save names a value with one rather than with nothing.
|
|
152
|
+
peekOption(key, aged = false) {
|
|
153
|
+
const kept = (tableJoins.peekList(listName, aged ? Infinity : JOIN_LIFETIME) ?? heldNarrowed(aged ? Infinity : JOIN_LIFETIME)).find(row => String(row[valueKey]) === key);
|
|
154
|
+
const record = kept ?? (lookup ? peekSource(lookup.name, key, aged) : undefined);
|
|
155
|
+
return record ? shown(record) : undefined;
|
|
156
|
+
},
|
|
59
157
|
valueKey,
|
|
60
158
|
labelKey,
|
|
61
159
|
...(descriptionKey && { descriptionKey }),
|
|
62
|
-
isOptionInactive: item => !
|
|
160
|
+
isOptionInactive: item => !plan.usable(item),
|
|
63
161
|
};
|
|
64
162
|
}
|
|
65
163
|
/**
|
|
66
164
|
* Marks what the page looked up from a controller as old, after a write there may have changed it: the records its
|
|
67
|
-
* `findListing` named
|
|
165
|
+
* `findListing` named and its pickers' own finds answered, and the lists its pickers offer - its `activeListing`, and
|
|
166
|
+
* each of a CRUD's `lists` a picker reads instead (`CrudPickerOptions.list`). Drawn as they are until the next ask brings
|
|
167
|
+
* the new ones.
|
|
68
168
|
*/
|
|
69
169
|
export function expireLookups(controller) {
|
|
70
|
-
if (controller.findListing)
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
170
|
+
if (controller.findListing) {
|
|
171
|
+
const name = joinSourceName(controller.findListing);
|
|
172
|
+
tableJoins.expire(name);
|
|
173
|
+
for (const other of finds.get(name) ?? [])
|
|
174
|
+
tableJoins.expire(other);
|
|
175
|
+
}
|
|
176
|
+
const lists = [controller.activeListing, ...Object.values(controller.lists ?? {})];
|
|
177
|
+
for (const list of lists) {
|
|
178
|
+
if (!list)
|
|
179
|
+
continue;
|
|
180
|
+
const name = joinSourceName(list);
|
|
181
|
+
tableJoins.expireList(name);
|
|
182
|
+
for (const narrowed of narrowedLists.get(name) ?? [])
|
|
183
|
+
tableJoins.expireList(narrowed);
|
|
184
|
+
}
|
|
74
185
|
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { type MessageBackend } from "../Helpers/MessageBackend.ts";
|
|
2
|
+
import type { ApiCallOptions } from "../Interfaces/ApiInterfaces.ts";
|
|
3
|
+
import type { MessageSource } from "../Interfaces/MailInterfaces.ts";
|
|
4
|
+
/** Options of a mailbox's source. */
|
|
5
|
+
export interface ApiMessageSourceOptions {
|
|
6
|
+
/** The backend to use, when it is not the app's registered one. */
|
|
7
|
+
backend?: MessageBackend;
|
|
8
|
+
/** Extras on every call, such as a header. */
|
|
9
|
+
call?: Omit<ApiCallOptions, "body" | "signal" | "params">;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* A mailbox through the app's API. One is enough for a page: a thread's lines come from `threadSource(id)`.
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* const mail = useMemo(() => apiMessageSource(), []);
|
|
16
|
+
* <MailThread source={mail} threadId={row.id} userId={me.id} />
|
|
17
|
+
*/
|
|
18
|
+
export declare function apiMessageSource(options?: ApiMessageSourceOptions): MessageSource;
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { unwrapApiResult } from "../Helpers/ApiResponses.js";
|
|
3
|
+
import { getMessageBackend } from "../Helpers/MessageBackend.js";
|
|
4
|
+
import { api } from "./BrowserApi.js";
|
|
5
|
+
/**
|
|
6
|
+
* A mailbox through the app's API. One is enough for a page: a thread's lines come from `threadSource(id)`.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* const mail = useMemo(() => apiMessageSource(), []);
|
|
10
|
+
* <MailThread source={mail} threadId={row.id} userId={me.id} />
|
|
11
|
+
*/
|
|
12
|
+
export function apiMessageSource(options) {
|
|
13
|
+
const backend = options?.backend ?? getMessageBackend();
|
|
14
|
+
// Each action's method is its endpoint's: these two only say whether a body goes with it.
|
|
15
|
+
const call = (signal) => ({ ...options?.call, ...(signal && { signal }), toast: false });
|
|
16
|
+
const withBody = async (action, body, signal) => unwrapApiResult(await api.send(backend.endpoint(action), { ...call(signal), body }));
|
|
17
|
+
const withoutBody = async (action, signal, params) => unwrapApiResult(await api.send(backend.endpoint(action), { ...call(signal), ...(params && { params }) }));
|
|
18
|
+
const changed = async (action, ids, signal) => backend.readChanged(await withBody(action, backend.markRequest(ids), signal));
|
|
19
|
+
const readFolders = backend.readFolders?.bind(backend);
|
|
20
|
+
return {
|
|
21
|
+
// The optional doors, only where the backend has them: a mail client hides what a source cannot do.
|
|
22
|
+
...(readFolders && backend.supports("folders") && { folders: async (signal) => readFolders(await withoutBody("folders", signal)) }),
|
|
23
|
+
...(backend.supports("markRead") && { markRead: (ids, signal) => changed("markRead", ids, signal) }),
|
|
24
|
+
...(backend.supports("markUnread") && { markUnread: (ids, signal) => changed("markUnread", ids, signal) }),
|
|
25
|
+
...(backend.supports("star") && { star: (ids, starred, signal) => changed(starred ? "star" : "unstar", ids, signal) }),
|
|
26
|
+
async inbox(request) {
|
|
27
|
+
return backend.readInbox(await withBody("inbox", backend.inboxRequest(request), request.signal));
|
|
28
|
+
},
|
|
29
|
+
async thread(id, signal) {
|
|
30
|
+
return backend.readThread(await withoutBody("thread", signal, backend.threadParams(id)));
|
|
31
|
+
},
|
|
32
|
+
threadSource(id) {
|
|
33
|
+
return {
|
|
34
|
+
async history(request) {
|
|
35
|
+
return backend.readHistory(await withBody("history", backend.historyRequest(id, request), request.signal));
|
|
36
|
+
},
|
|
37
|
+
async send(draft, signal) {
|
|
38
|
+
return backend.readReply(await withBody("reply", backend.replyRequest(id, draft), signal), draft);
|
|
39
|
+
},
|
|
40
|
+
async markRead(upTo, signal) {
|
|
41
|
+
await withBody("read", backend.readMarkRequest(id, upTo), signal);
|
|
42
|
+
},
|
|
43
|
+
};
|
|
44
|
+
},
|
|
45
|
+
async compose(draft, signal) {
|
|
46
|
+
return backend.readComposed(await withBody("compose", backend.composeRequest(draft), signal));
|
|
47
|
+
},
|
|
48
|
+
async archive(ids, signal) {
|
|
49
|
+
return backend.readChanged(await withBody("archive", backend.markRequest(ids), signal));
|
|
50
|
+
},
|
|
51
|
+
async unarchive(ids, signal) {
|
|
52
|
+
return backend.readChanged(await withBody("unarchive", backend.markRequest(ids), signal));
|
|
53
|
+
},
|
|
54
|
+
async unread(signal) {
|
|
55
|
+
return backend.readUnread(await withoutBody("unread", signal));
|
|
56
|
+
},
|
|
57
|
+
async people(query) {
|
|
58
|
+
return backend.readPeople(await withBody("people", backend.peopleRequest(query), query.signal));
|
|
59
|
+
},
|
|
60
|
+
async defaults(signal) {
|
|
61
|
+
return backend.readDefaults(await withoutBody("defaults", signal));
|
|
62
|
+
},
|
|
63
|
+
};
|
|
64
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Imports an ES module by its full address, as the browser does, never through the app's bundler: a module a server
|
|
3
|
+
* serves at a versioned address (the image editor from the Nexus Drive).
|
|
4
|
+
*
|
|
5
|
+
* @example const editor = await importModule<ImageEditorModule>("https://drive.example/Assets/File/nexus-image-editor.3f2a.js");
|
|
6
|
+
*/
|
|
7
|
+
export declare function importModule<TModule>(url: string): Promise<TModule>;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* The one dynamic `import()` of an address known only at run time, kept alone in this file on purpose.
|
|
3
|
+
*
|
|
4
|
+
* The package build rewrites "./Button.tsx" to "./Button.js" (`rewriteRelativeImportExtensions`), and for an `import()`
|
|
5
|
+
* of a variable it does so at run time: it wraps the address in a helper call, `import(helper(url))`. The bundler-ignore
|
|
6
|
+
* comments are then inside a call, so Next's Turbopack no longer leaves the import alone: it compiles it to "Cannot find
|
|
7
|
+
* module as expression is too dynamic", and the image editor never loads in an app's production build. This file imports
|
|
8
|
+
* nothing, so it is built a second time without that rewrite (`tsconfig.build.module-import.json`, after the main build,
|
|
9
|
+
* in `npm run build`), and the published file keeps the plain `import(/* webpackIgnore: true *\/ ... url)` that webpack,
|
|
10
|
+
* Turbopack and Vite all leave to the browser. `scripts/test-dist-loader.mts` (in `npm test`) fails if it ever does not.
|
|
11
|
+
*
|
|
12
|
+
* Add no import to this file: the second build would leave a "./x.ts" in it unrewritten.
|
|
13
|
+
*/
|
|
14
|
+
/**
|
|
15
|
+
* Imports an ES module by its full address, as the browser does, never through the app's bundler: a module a server
|
|
16
|
+
* serves at a versioned address (the image editor from the Nexus Drive).
|
|
17
|
+
*
|
|
18
|
+
* @example const editor = await importModule<ImageEditorModule>("https://drive.example/Assets/File/nexus-image-editor.3f2a.js");
|
|
19
|
+
*/
|
|
20
|
+
export function importModule(url) {
|
|
21
|
+
return import(/* webpackIgnore: true */ /* turbopackIgnore: true */ /* @vite-ignore */ url);
|
|
22
|
+
}
|
package/dist/Shared.Index.d.ts
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
export * from "./Components/Documents/Button.tsx";
|
|
2
|
+
export * from "./Components/Documents/PrintDocument.tsx";
|
|
2
3
|
export * from "./Components/Inputs/Field.tsx";
|
|
3
4
|
export * from "./Components/Layouts/ThemeScript.tsx";
|
|
4
5
|
export * from "./Components/Viewers/MarkdownView.tsx";
|
|
6
|
+
export * from "./Components/Viewers/QrCodeImage.tsx";
|
|
5
7
|
export * from "./Components/Viewers/RichTextView.tsx";
|
|
6
8
|
export * from "./Helpers/ApiClient.ts";
|
|
7
9
|
export * from "./Helpers/ApiFormHelpers.ts";
|
|
@@ -15,23 +17,43 @@ export * from "./Helpers/ChatStore.ts";
|
|
|
15
17
|
export * from "./Helpers/ChatSummaries.ts";
|
|
16
18
|
export * from "./Helpers/ClassHelpers.ts";
|
|
17
19
|
export * from "./Helpers/CodeHelpers.ts";
|
|
20
|
+
export { setAuditActorNames, type AuditActorNamesResolver } from "./Helpers/AuditEntries.ts";
|
|
18
21
|
export * from "./Helpers/CrudBackend.ts";
|
|
19
22
|
export * from "./Helpers/CrudColumns.ts";
|
|
23
|
+
export * from "./Helpers/CrudDetailsTabs.ts";
|
|
20
24
|
export * from "./Helpers/CrudHelpers.ts";
|
|
25
|
+
export * from "./Helpers/DashboardHelpers.ts";
|
|
21
26
|
export * from "./Helpers/DateHelpers.ts";
|
|
22
27
|
export * from "./Helpers/DateTimeHelpers.ts";
|
|
28
|
+
export * from "./Helpers/DomainCards.ts";
|
|
23
29
|
export * from "./Helpers/DropdownHelpers.ts";
|
|
24
30
|
export * from "./Helpers/ExcelBackend.ts";
|
|
25
31
|
export * from "./Helpers/ExcelHelpers.ts";
|
|
32
|
+
export * from "./Helpers/FileAdapter.ts";
|
|
33
|
+
export * from "./Helpers/FilePrimaries.ts";
|
|
34
|
+
export * from "./Helpers/NexusDriveAdapter.ts";
|
|
35
|
+
export * from "./Helpers/FormConfirm.ts";
|
|
26
36
|
export * from "./Helpers/FormDrafts.ts";
|
|
27
37
|
export * from "./Helpers/FormStore.ts";
|
|
28
38
|
export * from "./Helpers/InputParamsHelpers.ts";
|
|
39
|
+
export * from "./Helpers/InputConditions.ts";
|
|
40
|
+
export * from "./Helpers/InputNarrowing.ts";
|
|
41
|
+
export * from "./Helpers/CrudNarrowPick.ts";
|
|
42
|
+
export * from "./Helpers/CrudScopes.ts";
|
|
43
|
+
export * from "./Helpers/CrudRowForms.ts";
|
|
29
44
|
export * from "./Helpers/MarkdownEditing.ts";
|
|
30
45
|
export * from "./Helpers/MarkdownHelpers.ts";
|
|
46
|
+
export * from "./Helpers/MailClientHelpers.ts";
|
|
47
|
+
export * from "./Helpers/MessageBackend.ts";
|
|
31
48
|
export * from "./Helpers/MessageBuilder.ts";
|
|
49
|
+
export * from "./Helpers/MessageHelpers.ts";
|
|
50
|
+
export * from "./Helpers/MessageUnread.ts";
|
|
32
51
|
export * from "./Helpers/NepaliCalendar.ts";
|
|
33
52
|
export * from "./Helpers/NumberHelpers.ts";
|
|
53
|
+
export * from "./Helpers/PagedPicker.ts";
|
|
34
54
|
export * from "./Helpers/Permissions.ts";
|
|
55
|
+
export * from "./Helpers/QrCode.ts";
|
|
56
|
+
export * from "./Helpers/RecordCards.ts";
|
|
35
57
|
export * from "./Helpers/RequestCache.ts";
|
|
36
58
|
export * from "./Helpers/RichTextHelpers.ts";
|
|
37
59
|
export * from "./Helpers/RowsStore.ts";
|
|
@@ -41,17 +63,23 @@ export * from "./Helpers/TableJoins.ts";
|
|
|
41
63
|
export * from "./Helpers/TableStore.ts";
|
|
42
64
|
export * from "./Helpers/ThemeHelpers.ts";
|
|
43
65
|
export * from "./Helpers/TimeHelpers.ts";
|
|
66
|
+
export * from "./Helpers/ToolbarFit.ts";
|
|
44
67
|
export * from "./Helpers/TreeStore.ts";
|
|
45
68
|
export * from "./Helpers/ValidationHelpers.ts";
|
|
46
69
|
export * from "./Interfaces/ApiInterfaces.ts";
|
|
47
70
|
export * from "./Interfaces/ChatInterfaces.ts";
|
|
48
71
|
export * from "./Interfaces/CrudInterfaces.ts";
|
|
72
|
+
export * from "./Interfaces/DashboardInterfaces.ts";
|
|
49
73
|
export * from "./Interfaces/DateInterfaces.ts";
|
|
74
|
+
export * from "./Interfaces/DomainInterfaces.ts";
|
|
50
75
|
export * from "./Interfaces/ExcelInterfaces.ts";
|
|
76
|
+
export * from "./Interfaces/FileInterfaces.ts";
|
|
51
77
|
export * from "./Interfaces/FormInterfaces.ts";
|
|
52
78
|
export * from "./Interfaces/InputInterfaces.ts";
|
|
79
|
+
export * from "./Interfaces/MailInterfaces.ts";
|
|
53
80
|
export * from "./Interfaces/MessageInterfaces.ts";
|
|
54
81
|
export * from "./Interfaces/PreferenceInterfaces.ts";
|
|
82
|
+
export * from "./Interfaces/RecordInterfaces.ts";
|
|
55
83
|
export * from "./Interfaces/TableInterfaces.ts";
|
|
56
84
|
export * from "./Interfaces/ThemeInterfaces.ts";
|
|
57
85
|
export * from "./Interfaces/TypeInterfaces.ts";
|