@aerogel/core 0.0.0-next.d824b40e5d06757cd9f47c9f771d916185df4f05 → 0.0.0-next.e4c0d5bd2801fbe93545477ea515af53df69b522
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/aerogel-core.css +1 -0
- package/dist/aerogel-core.d.ts +2226 -912
- package/dist/aerogel-core.js +3432 -0
- package/dist/aerogel-core.js.map +1 -0
- package/package.json +37 -34
- package/src/bootstrap/bootstrap.test.ts +7 -10
- package/src/bootstrap/index.ts +43 -15
- package/src/bootstrap/options.ts +4 -1
- package/src/components/AppLayout.vue +14 -0
- package/src/components/{AGAppModals.vue → AppModals.vue} +3 -4
- package/src/components/AppOverlays.vue +9 -0
- package/src/components/AppToasts.vue +16 -0
- package/src/components/contracts/AlertModal.ts +19 -0
- package/src/components/contracts/Button.ts +16 -0
- package/src/components/contracts/ConfirmModal.ts +48 -0
- package/src/components/contracts/DropdownMenu.ts +25 -0
- package/src/components/contracts/ErrorReportModal.ts +33 -0
- package/src/components/contracts/Input.ts +26 -0
- package/src/components/contracts/LoadingModal.ts +26 -0
- package/src/components/contracts/Modal.ts +21 -0
- package/src/components/contracts/PromptModal.ts +34 -0
- package/src/components/contracts/Select.ts +45 -0
- package/src/components/contracts/Toast.ts +15 -0
- package/src/components/contracts/index.ts +11 -0
- package/src/components/headless/HeadlessButton.vue +51 -0
- package/src/components/headless/HeadlessInput.vue +59 -0
- package/src/components/headless/HeadlessInputDescription.vue +27 -0
- package/src/components/headless/{forms/AGHeadlessInputError.vue → HeadlessInputError.vue} +4 -8
- package/src/components/headless/HeadlessInputInput.vue +75 -0
- package/src/components/headless/HeadlessInputLabel.vue +18 -0
- package/src/components/headless/HeadlessInputTextArea.vue +40 -0
- package/src/components/headless/HeadlessModal.vue +57 -0
- package/src/components/headless/HeadlessModalContent.vue +30 -0
- package/src/components/headless/HeadlessModalDescription.vue +12 -0
- package/src/components/headless/HeadlessModalOverlay.vue +12 -0
- package/src/components/headless/HeadlessModalTitle.vue +12 -0
- package/src/components/headless/HeadlessSelect.vue +120 -0
- package/src/components/headless/HeadlessSelectError.vue +25 -0
- package/src/components/headless/HeadlessSelectLabel.vue +25 -0
- package/src/components/headless/HeadlessSelectOption.vue +34 -0
- package/src/components/headless/HeadlessSelectOptions.vue +42 -0
- package/src/components/headless/HeadlessSelectTrigger.vue +22 -0
- package/src/components/headless/HeadlessSelectValue.vue +18 -0
- package/src/components/headless/HeadlessToast.vue +18 -0
- package/src/components/headless/HeadlessToastAction.vue +13 -0
- package/src/components/headless/index.ts +19 -3
- package/src/components/index.ts +6 -9
- package/src/components/ui/AdvancedOptions.vue +18 -0
- package/src/components/ui/AlertModal.vue +17 -0
- package/src/components/ui/Button.vue +100 -0
- package/src/components/ui/Checkbox.vue +56 -0
- package/src/components/ui/ConfirmModal.vue +50 -0
- package/src/components/ui/DropdownMenu.vue +32 -0
- package/src/components/ui/DropdownMenuOption.vue +22 -0
- package/src/components/ui/DropdownMenuOptions.vue +44 -0
- package/src/components/ui/EditableContent.vue +82 -0
- package/src/components/ui/ErrorLogs.vue +19 -0
- package/src/components/ui/ErrorLogsModal.vue +48 -0
- package/src/components/ui/ErrorMessage.vue +15 -0
- package/src/components/ui/ErrorReportModal.vue +73 -0
- package/src/components/{modals/AGErrorReportModalButtons.vue → ui/ErrorReportModalButtons.vue} +38 -29
- package/src/components/ui/ErrorReportModalTitle.vue +24 -0
- package/src/components/ui/Form.vue +24 -0
- package/src/components/ui/Input.vue +56 -0
- package/src/components/ui/Link.vue +12 -0
- package/src/components/ui/LoadingModal.vue +34 -0
- package/src/components/ui/Markdown.vue +97 -0
- package/src/components/ui/Modal.vue +123 -0
- package/src/components/ui/ModalContext.vue +31 -0
- package/src/components/ui/ProgressBar.vue +51 -0
- package/src/components/ui/PromptModal.vue +38 -0
- package/src/components/ui/Select.vue +27 -0
- package/src/components/ui/SelectLabel.vue +17 -0
- package/src/components/ui/SelectOption.vue +29 -0
- package/src/components/ui/SelectOptions.vue +35 -0
- package/src/components/ui/SelectTrigger.vue +29 -0
- package/src/components/ui/SettingsModal.vue +15 -0
- package/src/components/ui/StartupCrash.vue +31 -0
- package/src/components/ui/Toast.vue +46 -0
- package/src/components/ui/index.ts +32 -0
- package/src/directives/index.ts +13 -5
- package/src/directives/measure.ts +46 -0
- package/src/errors/Errors.state.ts +1 -1
- package/src/errors/Errors.ts +41 -37
- package/src/errors/JobCancelledError.ts +3 -0
- package/src/errors/index.ts +19 -29
- package/src/errors/utils.ts +35 -0
- package/src/forms/FormController.test.ts +110 -0
- package/src/forms/FormController.ts +246 -0
- package/src/forms/index.ts +3 -2
- package/src/forms/utils.ts +51 -20
- package/src/forms/validation.ts +19 -0
- package/src/index.css +73 -0
- package/src/{main.ts → index.ts} +3 -0
- package/src/jobs/Job.ts +147 -0
- package/src/jobs/index.ts +10 -0
- package/src/jobs/listeners.ts +3 -0
- package/src/jobs/status.ts +4 -0
- package/src/lang/DefaultLangProvider.ts +46 -0
- package/src/lang/Lang.state.ts +11 -0
- package/src/lang/Lang.ts +44 -29
- package/src/lang/index.ts +12 -6
- package/src/lang/settings/Language.vue +48 -0
- package/src/lang/settings/index.ts +10 -0
- package/src/plugins/Plugin.ts +1 -1
- package/src/plugins/index.ts +10 -7
- package/src/services/App.state.ts +39 -7
- package/src/services/App.ts +49 -6
- package/src/services/Cache.ts +43 -0
- package/src/services/Events.test.ts +39 -0
- package/src/services/Events.ts +110 -36
- package/src/services/Service.ts +154 -49
- package/src/services/Storage.ts +20 -0
- package/src/services/index.ts +18 -6
- package/src/services/store.ts +8 -5
- package/src/services/utils.ts +18 -0
- package/src/testing/index.ts +26 -0
- package/src/testing/setup.ts +11 -0
- package/src/ui/UI.state.ts +14 -12
- package/src/ui/UI.ts +314 -95
- package/src/ui/index.ts +32 -27
- package/src/ui/utils.ts +16 -0
- package/src/utils/classes.ts +41 -0
- package/src/utils/composition/events.ts +4 -5
- package/src/utils/composition/forms.ts +20 -4
- package/src/utils/composition/persistent.test.ts +33 -0
- package/src/utils/composition/persistent.ts +11 -0
- package/src/utils/composition/state.test.ts +47 -0
- package/src/utils/composition/state.ts +33 -0
- package/src/utils/index.ts +5 -0
- package/src/utils/markdown.test.ts +50 -0
- package/src/utils/markdown.ts +53 -6
- package/src/utils/types.ts +3 -0
- package/src/utils/vue.ts +38 -123
- package/.eslintrc.js +0 -3
- package/dist/aerogel-core.cjs.js +0 -2
- package/dist/aerogel-core.cjs.js.map +0 -1
- package/dist/aerogel-core.esm.js +0 -2
- package/dist/aerogel-core.esm.js.map +0 -1
- package/dist/virtual.d.ts +0 -11
- package/noeldemartin.config.js +0 -5
- package/src/components/AGAppLayout.vue +0 -11
- package/src/components/AGAppOverlays.vue +0 -37
- package/src/components/AGAppSnackbars.vue +0 -13
- package/src/components/basic/AGErrorMessage.vue +0 -16
- package/src/components/basic/AGLink.vue +0 -9
- package/src/components/basic/AGMarkdown.vue +0 -36
- package/src/components/basic/index.ts +0 -5
- package/src/components/constants.ts +0 -8
- package/src/components/forms/AGButton.vue +0 -44
- package/src/components/forms/AGCheckbox.vue +0 -35
- package/src/components/forms/AGForm.vue +0 -26
- package/src/components/forms/AGInput.vue +0 -36
- package/src/components/forms/index.ts +0 -6
- package/src/components/headless/forms/AGHeadlessButton.vue +0 -51
- package/src/components/headless/forms/AGHeadlessInput.ts +0 -8
- package/src/components/headless/forms/AGHeadlessInput.vue +0 -54
- package/src/components/headless/forms/AGHeadlessInputInput.vue +0 -45
- package/src/components/headless/forms/AGHeadlessInputLabel.vue +0 -16
- package/src/components/headless/forms/index.ts +0 -6
- package/src/components/headless/modals/AGHeadlessModal.ts +0 -7
- package/src/components/headless/modals/AGHeadlessModal.vue +0 -88
- package/src/components/headless/modals/AGHeadlessModalPanel.vue +0 -28
- package/src/components/headless/modals/AGHeadlessModalTitle.vue +0 -13
- package/src/components/headless/modals/index.ts +0 -6
- package/src/components/headless/snackbars/AGHeadlessSnackbar.vue +0 -10
- package/src/components/headless/snackbars/index.ts +0 -25
- package/src/components/modals/AGAlertModal.vue +0 -25
- package/src/components/modals/AGConfirmModal.vue +0 -30
- package/src/components/modals/AGErrorReportModal.ts +0 -20
- package/src/components/modals/AGErrorReportModal.vue +0 -62
- package/src/components/modals/AGErrorReportModalTitle.vue +0 -25
- package/src/components/modals/AGLoadingModal.vue +0 -19
- package/src/components/modals/AGModal.ts +0 -10
- package/src/components/modals/AGModal.vue +0 -37
- package/src/components/modals/AGModalContext.ts +0 -8
- package/src/components/modals/AGModalContext.vue +0 -22
- package/src/components/modals/AGModalTitle.vue +0 -9
- package/src/components/modals/index.ts +0 -23
- package/src/components/snackbars/AGSnackbar.vue +0 -42
- package/src/components/snackbars/index.ts +0 -3
- package/src/directives/initial-focus.ts +0 -11
- package/src/forms/Form.test.ts +0 -58
- package/src/forms/Form.ts +0 -176
- package/src/forms/composition.ts +0 -6
- package/src/types/virtual.d.ts +0 -11
- package/tsconfig.json +0 -11
- package/vite.config.ts +0 -14
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { objectOnly } from '@noeldemartin/utils';
|
|
2
|
+
|
|
3
|
+
export type Replace<
|
|
4
|
+
TOriginal extends Record<string, unknown>,
|
|
5
|
+
TReplacements extends Partial<Record<keyof TOriginal, unknown>>,
|
|
6
|
+
> = {
|
|
7
|
+
[K in keyof TOriginal]: TReplacements extends Record<K, infer Replacement> ? Replacement : TOriginal[K];
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export function replaceExisting<
|
|
11
|
+
TOriginal extends Record<string, unknown>,
|
|
12
|
+
TReplacements extends Partial<Record<keyof TOriginal, unknown>>,
|
|
13
|
+
>(original: TOriginal, replacements: TReplacements): Replace<TOriginal, TReplacements> {
|
|
14
|
+
return {
|
|
15
|
+
...original,
|
|
16
|
+
...objectOnly(replacements, Object.keys(original)),
|
|
17
|
+
} as Replace<TOriginal, TReplacements>;
|
|
18
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { isTesting } from '@noeldemartin/utils';
|
|
2
|
+
import type { GetClosureArgs } from '@noeldemartin/utils';
|
|
3
|
+
|
|
4
|
+
import Events from '@aerogel/core/services/Events';
|
|
5
|
+
import { definePlugin } from '@aerogel/core/plugins';
|
|
6
|
+
|
|
7
|
+
export interface AerogelTestingRuntime {
|
|
8
|
+
on: (typeof Events)['on'];
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export default definePlugin({
|
|
12
|
+
async install() {
|
|
13
|
+
if (!isTesting()) {
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
globalThis.testingRuntime = {
|
|
18
|
+
on: ((...args: GetClosureArgs<(typeof Events)['on']>) => Events.on(...args)) as (typeof Events)['on'],
|
|
19
|
+
};
|
|
20
|
+
},
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
declare global {
|
|
24
|
+
// eslint-disable-next-line no-var
|
|
25
|
+
var testingRuntime: AerogelTestingRuntime | undefined;
|
|
26
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { FakeLocalStorage } from '@noeldemartin/testing';
|
|
2
|
+
import { beforeEach, vi } from 'vitest';
|
|
3
|
+
|
|
4
|
+
vi.mock('dompurify', async () => {
|
|
5
|
+
return { default: { sanitize: (html: string) => html } };
|
|
6
|
+
});
|
|
7
|
+
|
|
8
|
+
beforeEach(() => {
|
|
9
|
+
FakeLocalStorage.reset();
|
|
10
|
+
FakeLocalStorage.patchGlobal();
|
|
11
|
+
});
|
package/src/ui/UI.state.ts
CHANGED
|
@@ -1,23 +1,19 @@
|
|
|
1
1
|
import type { Component } from 'vue';
|
|
2
2
|
|
|
3
|
-
import { defineServiceState } from '
|
|
3
|
+
import { defineServiceState } from '@aerogel/core/services/Service';
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
import { Layouts, getCurrentLayout } from './utils';
|
|
6
|
+
|
|
7
|
+
export interface UIModal<T = unknown> {
|
|
6
8
|
id: string;
|
|
7
9
|
properties: Record<string, unknown>;
|
|
8
10
|
component: Component;
|
|
11
|
+
closing: boolean;
|
|
9
12
|
beforeClose: Promise<T | undefined>;
|
|
10
13
|
afterClose: Promise<T | undefined>;
|
|
11
14
|
}
|
|
12
15
|
|
|
13
|
-
export interface
|
|
14
|
-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
15
|
-
Properties extends Record<string, unknown> = Record<string, unknown>,
|
|
16
|
-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
17
|
-
Result = unknown
|
|
18
|
-
> {}
|
|
19
|
-
|
|
20
|
-
export interface Snackbar {
|
|
16
|
+
export interface UIToast {
|
|
21
17
|
id: string;
|
|
22
18
|
component: Component;
|
|
23
19
|
properties: Record<string, unknown>;
|
|
@@ -26,7 +22,13 @@ export interface Snackbar {
|
|
|
26
22
|
export default defineServiceState({
|
|
27
23
|
name: 'ui',
|
|
28
24
|
initialState: {
|
|
29
|
-
modals: [] as
|
|
30
|
-
|
|
25
|
+
modals: [] as UIModal[],
|
|
26
|
+
toasts: [] as UIToast[],
|
|
27
|
+
layout: getCurrentLayout(),
|
|
28
|
+
},
|
|
29
|
+
computed: {
|
|
30
|
+
desktop: ({ layout }) => layout === Layouts.Desktop,
|
|
31
|
+
mobile: ({ layout }) => layout === Layouts.Mobile,
|
|
32
|
+
openModals: ({ modals }) => modals.filter(({ closing }) => !closing),
|
|
31
33
|
},
|
|
32
34
|
});
|
package/src/ui/UI.ts
CHANGED
|
@@ -1,121 +1,283 @@
|
|
|
1
|
-
import { facade, fail, uuid } from '@noeldemartin/utils';
|
|
1
|
+
import { after, facade, fail, isDevelopment, required, uuid } from '@noeldemartin/utils';
|
|
2
2
|
import { markRaw, nextTick } from 'vue';
|
|
3
|
+
import type { ComponentExposed, ComponentProps } from 'vue-component-type-helpers';
|
|
3
4
|
import type { Component } from 'vue';
|
|
4
|
-
import type {
|
|
5
|
-
|
|
6
|
-
import
|
|
7
|
-
import
|
|
5
|
+
import type { ClosureArgs } from '@noeldemartin/utils';
|
|
6
|
+
|
|
7
|
+
import App from '@aerogel/core/services/App';
|
|
8
|
+
import Events from '@aerogel/core/services/Events';
|
|
9
|
+
import type {
|
|
10
|
+
ConfirmModalCheckboxes,
|
|
11
|
+
ConfirmModalExpose,
|
|
12
|
+
ConfirmModalProps,
|
|
13
|
+
} from '@aerogel/core/components/contracts/ConfirmModal';
|
|
14
|
+
import type {
|
|
15
|
+
ErrorReportModalExpose,
|
|
16
|
+
ErrorReportModalProps,
|
|
17
|
+
} from '@aerogel/core/components/contracts/ErrorReportModal';
|
|
18
|
+
import type { AcceptRefs } from '@aerogel/core/utils';
|
|
19
|
+
import type { AlertModalExpose, AlertModalProps } from '@aerogel/core/components/contracts/AlertModal';
|
|
20
|
+
import type { ButtonVariant } from '@aerogel/core/components/contracts/Button';
|
|
21
|
+
import type { LoadingModalExpose, LoadingModalProps } from '@aerogel/core/components/contracts/LoadingModal';
|
|
22
|
+
import type { PromptModalExpose, PromptModalProps } from '@aerogel/core/components/contracts/PromptModal';
|
|
23
|
+
import type { ToastAction, ToastExpose, ToastProps, ToastVariant } from '@aerogel/core/components/contracts/Toast';
|
|
8
24
|
|
|
9
25
|
import Service from './UI.state';
|
|
10
|
-
import
|
|
26
|
+
import { MOBILE_BREAKPOINT, getCurrentLayout } from './utils';
|
|
27
|
+
import type { UIModal, UIToast } from './UI.state';
|
|
11
28
|
|
|
12
29
|
interface ModalCallbacks<T = unknown> {
|
|
13
30
|
willClose(result: T | undefined): void;
|
|
14
|
-
|
|
31
|
+
hasClosed(result: T | undefined): void;
|
|
15
32
|
}
|
|
16
33
|
|
|
17
|
-
type
|
|
18
|
-
type
|
|
19
|
-
|
|
20
|
-
|
|
34
|
+
export type ModalResult<T> = ModalExposeResult<ComponentExposed<T>>;
|
|
35
|
+
export type ModalExposeResult<T> = T extends { close(result?: infer Result): Promise<void> } ? Result : unknown;
|
|
36
|
+
export type UIComponent<Props = {}, Exposed = {}> = { new (...args: ClosureArgs): Exposed & { $props: Props } };
|
|
37
|
+
|
|
38
|
+
export interface UIComponents {
|
|
39
|
+
'alert-modal': UIComponent<AlertModalProps, AlertModalExpose>;
|
|
40
|
+
'confirm-modal': UIComponent<ConfirmModalProps, ConfirmModalExpose>;
|
|
41
|
+
'error-report-modal': UIComponent<ErrorReportModalProps, ErrorReportModalExpose>;
|
|
42
|
+
'loading-modal': UIComponent<LoadingModalProps, LoadingModalExpose>;
|
|
43
|
+
'prompt-modal': UIComponent<PromptModalProps, PromptModalExpose>;
|
|
44
|
+
'router-link': UIComponent;
|
|
45
|
+
'startup-crash': UIComponent;
|
|
46
|
+
toast: UIComponent<ToastProps, ToastExpose>;
|
|
47
|
+
}
|
|
21
48
|
|
|
22
|
-
export
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
LoadingModal: 'loading-modal',
|
|
27
|
-
Snackbar: 'snackbar',
|
|
28
|
-
} as const;
|
|
49
|
+
export interface UIModalContext {
|
|
50
|
+
modal: UIModal;
|
|
51
|
+
childIndex?: number;
|
|
52
|
+
}
|
|
29
53
|
|
|
30
|
-
export type
|
|
54
|
+
export type ConfirmOptions = AcceptRefs<{
|
|
55
|
+
acceptText?: string;
|
|
56
|
+
acceptVariant?: ButtonVariant;
|
|
57
|
+
cancelText?: string;
|
|
58
|
+
cancelVariant?: ButtonVariant;
|
|
59
|
+
actions?: Record<string, () => unknown>;
|
|
60
|
+
required?: boolean;
|
|
61
|
+
}>;
|
|
62
|
+
|
|
63
|
+
export type LoadingOptions = AcceptRefs<{
|
|
64
|
+
title?: string;
|
|
65
|
+
message?: string;
|
|
66
|
+
progress?: number;
|
|
67
|
+
}>;
|
|
68
|
+
|
|
69
|
+
export interface ConfirmOptionsWithCheckboxes<T extends ConfirmModalCheckboxes = ConfirmModalCheckboxes>
|
|
70
|
+
extends ConfirmOptions {
|
|
71
|
+
checkboxes?: T;
|
|
72
|
+
}
|
|
31
73
|
|
|
32
|
-
export
|
|
74
|
+
export type PromptOptions = AcceptRefs<{
|
|
75
|
+
label?: string;
|
|
76
|
+
defaultValue?: string;
|
|
77
|
+
placeholder?: string;
|
|
78
|
+
acceptText?: string;
|
|
79
|
+
acceptVariant?: ButtonVariant;
|
|
80
|
+
cancelText?: string;
|
|
81
|
+
cancelVariant?: ButtonVariant;
|
|
82
|
+
trim?: boolean;
|
|
83
|
+
}>;
|
|
84
|
+
|
|
85
|
+
export interface ToastOptions {
|
|
33
86
|
component?: Component;
|
|
34
|
-
|
|
35
|
-
actions?:
|
|
87
|
+
variant?: ToastVariant;
|
|
88
|
+
actions?: ToastAction[];
|
|
36
89
|
}
|
|
37
90
|
|
|
38
91
|
export class UIService extends Service {
|
|
39
92
|
|
|
40
93
|
private modalCallbacks: Record<string, Partial<ModalCallbacks>> = {};
|
|
41
|
-
private components: Partial<
|
|
94
|
+
private components: Partial<UIComponents> = {};
|
|
95
|
+
|
|
96
|
+
public registerComponent<T extends keyof UIComponents>(name: T, component: UIComponents[T]): void {
|
|
97
|
+
this.components[name] = component;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
public resolveComponent<T extends keyof UIComponents>(name: T): UIComponents[T] | null {
|
|
101
|
+
return this.components[name] ?? null;
|
|
102
|
+
}
|
|
42
103
|
|
|
43
|
-
public requireComponent(name:
|
|
44
|
-
return this.
|
|
104
|
+
public requireComponent<T extends keyof UIComponents>(name: T): UIComponents[T] {
|
|
105
|
+
return this.resolveComponent(name) ?? fail(`UI Component '${name}' is not defined!`);
|
|
45
106
|
}
|
|
46
107
|
|
|
47
108
|
public alert(message: string): void;
|
|
48
109
|
public alert(title: string, message: string): void;
|
|
49
110
|
public alert(messageOrTitle: string, message?: string): void {
|
|
50
|
-
const
|
|
111
|
+
const getProperties = (): AlertModalProps => {
|
|
112
|
+
if (typeof message !== 'string') {
|
|
113
|
+
return { message: messageOrTitle };
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
return {
|
|
117
|
+
title: messageOrTitle,
|
|
118
|
+
message,
|
|
119
|
+
};
|
|
120
|
+
};
|
|
51
121
|
|
|
52
|
-
this.
|
|
122
|
+
this.modal(this.requireComponent('alert-modal'), getProperties());
|
|
53
123
|
}
|
|
54
124
|
|
|
55
|
-
|
|
56
|
-
public async confirm(
|
|
57
|
-
public async confirm(
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
125
|
+
/* eslint-disable max-len */
|
|
126
|
+
public async confirm(message: string, options?: ConfirmOptions): Promise<boolean>;
|
|
127
|
+
public async confirm(title: string, message: string, options?: ConfirmOptions): Promise<boolean>;
|
|
128
|
+
public async confirm<T extends ConfirmModalCheckboxes>(message: string, options?: ConfirmOptionsWithCheckboxes<T>): Promise<[boolean, Record<keyof T, boolean>]>; // prettier-ignore
|
|
129
|
+
public async confirm<T extends ConfirmModalCheckboxes>(title: string, message: string, options?: ConfirmOptionsWithCheckboxes<T>): Promise<[boolean, Record<keyof T, boolean>]>; // prettier-ignore
|
|
130
|
+
/* eslint-enable max-len */
|
|
131
|
+
|
|
132
|
+
public async confirm(
|
|
133
|
+
messageOrTitle: string,
|
|
134
|
+
messageOrOptions?: string | ConfirmOptions | ConfirmOptionsWithCheckboxes,
|
|
135
|
+
options?: ConfirmOptions | ConfirmOptionsWithCheckboxes,
|
|
136
|
+
): Promise<boolean | [boolean, Record<string, boolean>]> {
|
|
137
|
+
const getProperties = (): AcceptRefs<ConfirmModalProps> => {
|
|
138
|
+
if (typeof messageOrOptions !== 'string') {
|
|
139
|
+
return {
|
|
140
|
+
...(messageOrOptions ?? {}),
|
|
141
|
+
message: messageOrTitle,
|
|
142
|
+
required: !!messageOrOptions?.required,
|
|
143
|
+
};
|
|
144
|
+
}
|
|
64
145
|
|
|
65
|
-
|
|
66
|
-
|
|
146
|
+
return {
|
|
147
|
+
...(options ?? {}),
|
|
148
|
+
title: messageOrTitle,
|
|
149
|
+
message: messageOrOptions,
|
|
150
|
+
required: !!options?.required,
|
|
151
|
+
};
|
|
152
|
+
};
|
|
67
153
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
154
|
+
const properties = getProperties();
|
|
155
|
+
const result = await this.modalForm(this.requireComponent('confirm-modal'), properties);
|
|
156
|
+
const confirmed = typeof result === 'object' ? result[0] : (result ?? false);
|
|
157
|
+
const checkboxes =
|
|
158
|
+
typeof result === 'object'
|
|
159
|
+
? result[1]
|
|
160
|
+
: Object.entries(properties.checkboxes ?? {}).reduce(
|
|
161
|
+
(values, [checkbox, { default: defaultValue }]) => ({
|
|
162
|
+
[checkbox]: defaultValue ?? false,
|
|
163
|
+
...values,
|
|
164
|
+
}),
|
|
165
|
+
{} as Record<string, boolean>,
|
|
166
|
+
);
|
|
167
|
+
|
|
168
|
+
for (const [name, checkbox] of Object.entries(properties.checkboxes ?? {})) {
|
|
169
|
+
if (!checkbox.required || checkboxes[name]) {
|
|
170
|
+
continue;
|
|
171
|
+
}
|
|
72
172
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
173
|
+
if (confirmed && isDevelopment()) {
|
|
174
|
+
// eslint-disable-next-line no-console
|
|
175
|
+
console.warn(`Confirmed confirm modal was suppressed because required '${name}' checkbox was missing`);
|
|
176
|
+
}
|
|
76
177
|
|
|
77
|
-
|
|
178
|
+
return [false, checkboxes];
|
|
179
|
+
}
|
|
78
180
|
|
|
79
|
-
return
|
|
181
|
+
return 'checkboxes' in properties ? [confirmed, checkboxes] : confirmed;
|
|
80
182
|
}
|
|
81
183
|
|
|
82
|
-
public
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
184
|
+
public async prompt(message: string, options?: PromptOptions): Promise<string | null>;
|
|
185
|
+
public async prompt(title: string, message: string, options?: PromptOptions): Promise<string | null>;
|
|
186
|
+
public async prompt(
|
|
187
|
+
messageOrTitle: string,
|
|
188
|
+
messageOrOptions?: string | PromptOptions,
|
|
189
|
+
options?: PromptOptions,
|
|
190
|
+
): Promise<string | null> {
|
|
191
|
+
const trim = options?.trim ?? true;
|
|
192
|
+
const getProperties = (): PromptModalProps => {
|
|
193
|
+
if (typeof messageOrOptions !== 'string') {
|
|
194
|
+
return {
|
|
195
|
+
message: messageOrTitle,
|
|
196
|
+
...(messageOrOptions ?? {}),
|
|
197
|
+
} as PromptModalProps;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
return {
|
|
201
|
+
title: messageOrTitle,
|
|
202
|
+
message: messageOrOptions,
|
|
203
|
+
...(options ?? {}),
|
|
204
|
+
} as PromptModalProps;
|
|
87
205
|
};
|
|
88
206
|
|
|
89
|
-
this.
|
|
207
|
+
const rawResult = await this.modalForm(this.requireComponent('prompt-modal'), getProperties());
|
|
208
|
+
const result = trim && typeof rawResult === 'string' ? rawResult?.trim() : rawResult;
|
|
90
209
|
|
|
91
|
-
|
|
210
|
+
return result ?? null;
|
|
92
211
|
}
|
|
93
212
|
|
|
94
|
-
public
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
)
|
|
213
|
+
public async loading<T>(operation: Promise<T> | (() => T)): Promise<T>;
|
|
214
|
+
public async loading<T>(message: string, operation: Promise<T> | (() => T)): Promise<T>;
|
|
215
|
+
public async loading<T>(options: LoadingOptions, operation: Promise<T> | (() => T)): Promise<T>;
|
|
216
|
+
public async loading<T>(
|
|
217
|
+
operationOrMessageOrOptions: string | LoadingOptions | Promise<T> | (() => T),
|
|
218
|
+
operation?: Promise<T> | (() => T),
|
|
219
|
+
): Promise<T> {
|
|
220
|
+
const processOperation = (o: Promise<T> | (() => T)) => (typeof o === 'function' ? Promise.resolve(o()) : o);
|
|
221
|
+
const processArgs = (): { operationPromise: Promise<T>; props?: AcceptRefs<LoadingModalProps> } => {
|
|
222
|
+
if (typeof operationOrMessageOrOptions === 'string') {
|
|
223
|
+
return {
|
|
224
|
+
props: { message: operationOrMessageOrOptions },
|
|
225
|
+
operationPromise: processOperation(operation as Promise<T> | (() => T)),
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
if (typeof operationOrMessageOrOptions === 'function' || operationOrMessageOrOptions instanceof Promise) {
|
|
230
|
+
return { operationPromise: processOperation(operationOrMessageOrOptions) };
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
return {
|
|
234
|
+
props: operationOrMessageOrOptions,
|
|
235
|
+
operationPromise: processOperation(operation as Promise<T> | (() => T)),
|
|
236
|
+
};
|
|
237
|
+
};
|
|
238
|
+
|
|
239
|
+
const { operationPromise, props } = processArgs();
|
|
240
|
+
const modal = await this.modal(this.requireComponent('loading-modal'), props);
|
|
241
|
+
|
|
242
|
+
try {
|
|
243
|
+
const result = await operationPromise;
|
|
244
|
+
|
|
245
|
+
await after({ ms: 500 });
|
|
246
|
+
|
|
247
|
+
return result;
|
|
248
|
+
} finally {
|
|
249
|
+
await this.closeModal(modal.id);
|
|
250
|
+
}
|
|
99
251
|
}
|
|
100
252
|
|
|
101
|
-
public
|
|
102
|
-
|
|
253
|
+
public toast(message: string, options: ToastOptions = {}): void {
|
|
254
|
+
const { component, ...otherOptions } = options;
|
|
255
|
+
const toast: UIToast = {
|
|
256
|
+
id: uuid(),
|
|
257
|
+
properties: { message, ...otherOptions },
|
|
258
|
+
component: markRaw(component ?? this.requireComponent('toast')),
|
|
259
|
+
};
|
|
260
|
+
|
|
261
|
+
this.setState('toasts', this.toasts.concat(toast));
|
|
103
262
|
}
|
|
104
263
|
|
|
105
|
-
public
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
264
|
+
public modal<T extends Component>(
|
|
265
|
+
...args: {} extends ComponentProps<T>
|
|
266
|
+
? [component: T, props?: AcceptRefs<ComponentProps<T>>]
|
|
267
|
+
: [component: T, props: AcceptRefs<ComponentProps<T>>]
|
|
268
|
+
): Promise<UIModal<ModalResult<T>>>;
|
|
269
|
+
|
|
270
|
+
public async modal<T extends Component>(component: T, props?: ComponentProps<T>): Promise<UIModal<ModalResult<T>>> {
|
|
109
271
|
const id = uuid();
|
|
110
|
-
const callbacks: Partial<ModalCallbacks<ModalResult<
|
|
111
|
-
const modal:
|
|
272
|
+
const callbacks: Partial<ModalCallbacks<ModalResult<T>>> = {};
|
|
273
|
+
const modal: UIModal<ModalResult<T>> = {
|
|
112
274
|
id,
|
|
113
|
-
|
|
275
|
+
closing: false,
|
|
276
|
+
properties: props ?? {},
|
|
114
277
|
component: markRaw(component),
|
|
115
278
|
beforeClose: new Promise((resolve) => (callbacks.willClose = resolve)),
|
|
116
|
-
afterClose: new Promise((resolve) => (callbacks.
|
|
279
|
+
afterClose: new Promise((resolve) => (callbacks.hasClosed = resolve)),
|
|
117
280
|
};
|
|
118
|
-
const activeModal = this.modals.at(-1);
|
|
119
281
|
const modals = this.modals.concat(modal);
|
|
120
282
|
|
|
121
283
|
this.modalCallbacks[modal.id] = callbacks;
|
|
@@ -123,58 +285,115 @@ export class UIService extends Service {
|
|
|
123
285
|
this.setState({ modals });
|
|
124
286
|
|
|
125
287
|
await nextTick();
|
|
126
|
-
await (activeModal && Events.emit('hide-modal', { id: activeModal.id }));
|
|
127
|
-
await Promise.all([
|
|
128
|
-
activeModal || Events.emit('show-overlays-backdrop'),
|
|
129
|
-
Events.emit('show-modal', { id: modal.id }),
|
|
130
|
-
]);
|
|
131
288
|
|
|
132
289
|
return modal;
|
|
133
290
|
}
|
|
134
291
|
|
|
292
|
+
public modalForm<T extends Component>(
|
|
293
|
+
...args: {} extends ComponentProps<T>
|
|
294
|
+
? [component: T, props?: AcceptRefs<ComponentProps<T>>]
|
|
295
|
+
: [component: T, props: AcceptRefs<ComponentProps<T>>]
|
|
296
|
+
): Promise<ModalResult<T> | undefined>;
|
|
297
|
+
|
|
298
|
+
public async modalForm<T extends Component>(
|
|
299
|
+
component: T,
|
|
300
|
+
props?: ComponentProps<T>,
|
|
301
|
+
): Promise<ModalResult<T> | undefined> {
|
|
302
|
+
const modal = await this.modal<T>(component, props as ComponentProps<T>);
|
|
303
|
+
const result = await modal.beforeClose;
|
|
304
|
+
|
|
305
|
+
return result;
|
|
306
|
+
}
|
|
307
|
+
|
|
135
308
|
public async closeModal(id: string, result?: unknown): Promise<void> {
|
|
309
|
+
if (!App.isMounted()) {
|
|
310
|
+
await this.removeModal(id, result);
|
|
311
|
+
|
|
312
|
+
return;
|
|
313
|
+
}
|
|
314
|
+
|
|
136
315
|
await Events.emit('close-modal', { id, result });
|
|
137
316
|
}
|
|
138
317
|
|
|
139
|
-
|
|
318
|
+
public async closeAllModals(): Promise<void> {
|
|
319
|
+
while (this.modals.length > 0) {
|
|
320
|
+
await this.closeModal(required(this.modals[this.modals.length - 1]).id);
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
protected override async boot(): Promise<void> {
|
|
140
325
|
this.watchModalEvents();
|
|
326
|
+
this.watchMountedEvent();
|
|
327
|
+
this.watchViewportBreakpoints();
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
private async removeModal(id: string, result?: unknown): Promise<void> {
|
|
331
|
+
this.setState(
|
|
332
|
+
'modals',
|
|
333
|
+
this.modals.filter((m) => m.id !== id),
|
|
334
|
+
);
|
|
335
|
+
|
|
336
|
+
this.modalCallbacks[id]?.hasClosed?.(result);
|
|
337
|
+
|
|
338
|
+
delete this.modalCallbacks[id];
|
|
141
339
|
}
|
|
142
340
|
|
|
143
341
|
private watchModalEvents(): void {
|
|
144
|
-
Events.on('modal-will-close', ({ modal, result }) => {
|
|
145
|
-
this.
|
|
342
|
+
Events.on('modal-will-close', ({ modal: { id }, result }) => {
|
|
343
|
+
const modal = this.modals.find((_modal) => id === _modal.id);
|
|
146
344
|
|
|
147
|
-
if (
|
|
148
|
-
|
|
345
|
+
if (modal) {
|
|
346
|
+
modal.closing = true;
|
|
149
347
|
}
|
|
348
|
+
|
|
349
|
+
this.modalCallbacks[id]?.willClose?.(result);
|
|
350
|
+
});
|
|
351
|
+
|
|
352
|
+
Events.on('modal-has-closed', async ({ modal: { id }, result }) => {
|
|
353
|
+
await this.removeModal(id, result);
|
|
150
354
|
});
|
|
355
|
+
}
|
|
151
356
|
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
357
|
+
private watchMountedEvent(): void {
|
|
358
|
+
Events.once('application-mounted', async () => {
|
|
359
|
+
if (!globalThis.document || !globalThis.getComputedStyle) {
|
|
360
|
+
return;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
const splash = globalThis.document.getElementById('splash');
|
|
157
364
|
|
|
158
|
-
|
|
365
|
+
if (!splash) {
|
|
366
|
+
return;
|
|
367
|
+
}
|
|
159
368
|
|
|
160
|
-
|
|
369
|
+
if (globalThis.getComputedStyle(splash).opacity !== '0') {
|
|
370
|
+
splash.style.opacity = '0';
|
|
161
371
|
|
|
162
|
-
|
|
372
|
+
await after({ ms: 600 });
|
|
373
|
+
}
|
|
163
374
|
|
|
164
|
-
|
|
375
|
+
splash.remove();
|
|
165
376
|
});
|
|
166
377
|
}
|
|
167
378
|
|
|
379
|
+
private watchViewportBreakpoints(): void {
|
|
380
|
+
if (!globalThis.matchMedia) {
|
|
381
|
+
return;
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
const media = globalThis.matchMedia(`(min-width: ${MOBILE_BREAKPOINT}px)`);
|
|
385
|
+
|
|
386
|
+
media.addEventListener('change', () => this.setState({ layout: getCurrentLayout() }));
|
|
387
|
+
}
|
|
388
|
+
|
|
168
389
|
}
|
|
169
390
|
|
|
170
|
-
export default facade(
|
|
391
|
+
export default facade(UIService);
|
|
171
392
|
|
|
172
|
-
declare module '
|
|
393
|
+
declare module '@aerogel/core/services/Events' {
|
|
173
394
|
export interface EventsPayload {
|
|
174
|
-
'modal-will-close': { modal: Modal; result?: unknown };
|
|
175
|
-
'modal-closed': { modal: Modal; result?: unknown };
|
|
176
395
|
'close-modal': { id: string; result?: unknown };
|
|
177
|
-
'
|
|
178
|
-
'
|
|
396
|
+
'modal-will-close': { modal: UIModal; result?: unknown };
|
|
397
|
+
'modal-has-closed': { modal: UIModal; result?: unknown };
|
|
179
398
|
}
|
|
180
399
|
}
|