@aerogel/core 0.0.0-next.824cf5311c4335d119158f507dad094ed4f4f0b6 → 0.0.0-next.8323c60b905020dcb3bd9d4b0bc8d9b6529e1082
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 +2311 -1612
- package/dist/aerogel-core.js +3722 -0
- package/dist/aerogel-core.js.map +1 -0
- package/package.json +32 -37
- package/src/bootstrap/bootstrap.test.ts +4 -8
- package/src/bootstrap/index.ts +23 -13
- package/src/bootstrap/options.ts +1 -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 +86 -0
- package/src/components/headless/{forms/AGHeadlessInputLabel.vue → HeadlessInputLabel.vue} +3 -7
- 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/{forms/AGHeadlessSelectError.vue → HeadlessSelectError.vue} +5 -6
- 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/HeadlessSwitch.vue +96 -0
- package/src/components/headless/HeadlessToast.vue +18 -0
- package/src/components/headless/HeadlessToastAction.vue +13 -0
- package/src/components/headless/index.ts +20 -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 +115 -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} +34 -27
- 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 +131 -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 +21 -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/Setting.vue +31 -0
- package/src/components/ui/SettingsModal.vue +15 -0
- package/src/components/{lib/AGStartupCrash.vue → ui/StartupCrash.vue} +8 -8
- package/src/components/ui/Switch.vue +11 -0
- package/src/components/ui/TextArea.vue +56 -0
- package/src/components/ui/Toast.vue +46 -0
- package/src/components/ui/index.ts +35 -0
- package/src/directives/index.ts +11 -5
- package/src/directives/measure.ts +41 -7
- package/src/errors/Errors.state.ts +1 -1
- package/src/errors/Errors.ts +49 -41
- package/src/errors/JobCancelledError.ts +3 -0
- package/src/errors/index.ts +16 -8
- package/src/errors/settings/Debug.vue +32 -0
- package/src/errors/settings/index.ts +10 -0
- package/src/errors/utils.ts +35 -0
- package/src/forms/FormController.test.ts +113 -0
- package/src/forms/FormController.ts +255 -0
- package/src/forms/index.ts +3 -2
- package/src/forms/utils.ts +76 -20
- package/src/forms/validation.ts +50 -0
- package/src/index.css +76 -0
- package/src/{main.ts → index.ts} +2 -0
- package/src/jobs/Job.ts +144 -2
- package/src/jobs/index.ts +4 -1
- 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 +43 -28
- 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 +36 -3
- package/src/services/App.ts +19 -3
- package/src/services/Cache.ts +43 -0
- package/src/services/Events.test.ts +8 -8
- package/src/services/Events.ts +26 -10
- package/src/services/Service.ts +135 -59
- package/src/services/Storage.ts +20 -0
- package/src/services/index.ts +18 -7
- package/src/services/utils.ts +18 -0
- package/src/testing/index.ts +30 -0
- package/src/testing/setup.ts +11 -0
- package/src/ui/UI.state.ts +14 -12
- package/src/ui/UI.ts +252 -120
- package/src/ui/index.ts +28 -28
- package/src/ui/utils.ts +16 -0
- package/src/utils/app.ts +7 -0
- package/src/utils/classes.ts +41 -0
- package/src/utils/composition/events.ts +4 -6
- 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 +6 -1
- 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 -132
- 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/histoire.config.ts +0 -7
- package/noeldemartin.config.js +0 -5
- package/postcss.config.js +0 -6
- package/src/assets/histoire.css +0 -3
- package/src/components/AGAppLayout.vue +0 -16
- package/src/components/AGAppOverlays.vue +0 -41
- package/src/components/AGAppSnackbars.vue +0 -13
- package/src/components/constants.ts +0 -8
- package/src/components/forms/AGButton.vue +0 -44
- package/src/components/forms/AGCheckbox.vue +0 -41
- package/src/components/forms/AGForm.vue +0 -26
- package/src/components/forms/AGInput.vue +0 -38
- package/src/components/forms/AGSelect.story.vue +0 -46
- package/src/components/forms/AGSelect.vue +0 -60
- package/src/components/forms/index.ts +0 -5
- package/src/components/headless/forms/AGHeadlessButton.vue +0 -51
- package/src/components/headless/forms/AGHeadlessInput.ts +0 -28
- package/src/components/headless/forms/AGHeadlessInput.vue +0 -57
- package/src/components/headless/forms/AGHeadlessInputInput.vue +0 -45
- package/src/components/headless/forms/AGHeadlessSelect.ts +0 -42
- package/src/components/headless/forms/AGHeadlessSelect.vue +0 -77
- package/src/components/headless/forms/AGHeadlessSelectButton.vue +0 -24
- package/src/components/headless/forms/AGHeadlessSelectLabel.vue +0 -24
- package/src/components/headless/forms/AGHeadlessSelectOption.ts +0 -4
- package/src/components/headless/forms/AGHeadlessSelectOption.vue +0 -39
- package/src/components/headless/forms/AGHeadlessSelectOptions.ts +0 -3
- package/src/components/headless/forms/index.ts +0 -14
- package/src/components/headless/modals/AGHeadlessModal.ts +0 -34
- package/src/components/headless/modals/AGHeadlessModal.vue +0 -86
- 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 -4
- package/src/components/headless/snackbars/AGHeadlessSnackbar.vue +0 -10
- package/src/components/headless/snackbars/index.ts +0 -40
- package/src/components/lib/AGErrorMessage.vue +0 -16
- package/src/components/lib/AGLink.vue +0 -9
- package/src/components/lib/AGMarkdown.vue +0 -36
- package/src/components/lib/AGMeasured.vue +0 -15
- package/src/components/lib/index.ts +0 -5
- package/src/components/modals/AGAlertModal.ts +0 -15
- package/src/components/modals/AGAlertModal.vue +0 -14
- package/src/components/modals/AGConfirmModal.ts +0 -27
- package/src/components/modals/AGConfirmModal.vue +0 -26
- package/src/components/modals/AGErrorReportModal.ts +0 -46
- package/src/components/modals/AGErrorReportModal.vue +0 -54
- package/src/components/modals/AGErrorReportModalTitle.vue +0 -25
- package/src/components/modals/AGLoadingModal.ts +0 -23
- package/src/components/modals/AGLoadingModal.vue +0 -15
- package/src/components/modals/AGModal.ts +0 -10
- package/src/components/modals/AGModal.vue +0 -39
- 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/AGPromptModal.ts +0 -30
- package/src/components/modals/AGPromptModal.vue +0 -34
- package/src/components/modals/index.ts +0 -17
- package/src/components/snackbars/AGSnackbar.vue +0 -36
- package/src/components/snackbars/index.ts +0 -3
- package/src/components/utils.ts +0 -10
- package/src/directives/initial-focus.ts +0 -11
- package/src/forms/Form.test.ts +0 -58
- package/src/forms/Form.ts +0 -185
- package/src/forms/composition.ts +0 -6
- package/src/main.histoire.ts +0 -1
- package/src/utils/tailwindcss.test.ts +0 -26
- package/src/utils/tailwindcss.ts +0 -7
- package/tailwind.config.js +0 -4
- package/tsconfig.json +0 -11
- package/vite.config.ts +0 -14
package/src/ui/UI.ts
CHANGED
|
@@ -1,69 +1,115 @@
|
|
|
1
|
-
import { after, facade, fail, uuid } from '@noeldemartin/utils';
|
|
2
|
-
import { markRaw, nextTick } from 'vue';
|
|
1
|
+
import { after, facade, fail, isDevelopment, required, uuid } from '@noeldemartin/utils';
|
|
2
|
+
import { markRaw, nextTick, unref } 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
|
|
8
|
-
import type {
|
|
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';
|
|
9
24
|
|
|
10
25
|
import Service from './UI.state';
|
|
11
|
-
import
|
|
26
|
+
import { MOBILE_BREAKPOINT, getCurrentLayout } from './utils';
|
|
27
|
+
import type { UIModal, UIToast } from './UI.state';
|
|
12
28
|
|
|
13
29
|
interface ModalCallbacks<T = unknown> {
|
|
14
30
|
willClose(result: T | undefined): void;
|
|
15
|
-
|
|
31
|
+
hasClosed(result: T | undefined): void;
|
|
16
32
|
}
|
|
17
33
|
|
|
18
|
-
type
|
|
19
|
-
type
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
}
|
|
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
|
+
}
|
|
32
48
|
|
|
33
|
-
export
|
|
49
|
+
export interface UIModalContext {
|
|
50
|
+
modal: UIModal;
|
|
51
|
+
childIndex?: number;
|
|
52
|
+
}
|
|
34
53
|
|
|
35
|
-
export
|
|
54
|
+
export type ConfirmOptions = AcceptRefs<{
|
|
36
55
|
acceptText?: string;
|
|
56
|
+
acceptVariant?: ButtonVariant;
|
|
37
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
|
+
delay?: number;
|
|
68
|
+
}>;
|
|
69
|
+
|
|
70
|
+
export interface ConfirmOptionsWithCheckboxes<T extends ConfirmModalCheckboxes = ConfirmModalCheckboxes>
|
|
71
|
+
extends ConfirmOptions {
|
|
72
|
+
checkboxes?: T;
|
|
38
73
|
}
|
|
39
74
|
|
|
40
|
-
export
|
|
75
|
+
export type PromptOptions = AcceptRefs<{
|
|
41
76
|
label?: string;
|
|
42
77
|
defaultValue?: string;
|
|
43
78
|
placeholder?: string;
|
|
44
79
|
acceptText?: string;
|
|
80
|
+
acceptVariant?: ButtonVariant;
|
|
45
81
|
cancelText?: string;
|
|
46
|
-
|
|
82
|
+
cancelVariant?: ButtonVariant;
|
|
83
|
+
trim?: boolean;
|
|
84
|
+
}>;
|
|
47
85
|
|
|
48
|
-
export interface
|
|
86
|
+
export interface ToastOptions {
|
|
49
87
|
component?: Component;
|
|
50
|
-
|
|
51
|
-
actions?:
|
|
88
|
+
variant?: ToastVariant;
|
|
89
|
+
actions?: ToastAction[];
|
|
52
90
|
}
|
|
53
91
|
|
|
54
92
|
export class UIService extends Service {
|
|
55
93
|
|
|
56
94
|
private modalCallbacks: Record<string, Partial<ModalCallbacks>> = {};
|
|
57
|
-
private components: Partial<
|
|
95
|
+
private components: Partial<UIComponents> = {};
|
|
96
|
+
|
|
97
|
+
public registerComponent<T extends keyof UIComponents>(name: T, component: UIComponents[T]): void {
|
|
98
|
+
this.components[name] = component;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
public resolveComponent<T extends keyof UIComponents>(name: T): UIComponents[T] | null {
|
|
102
|
+
return this.components[name] ?? null;
|
|
103
|
+
}
|
|
58
104
|
|
|
59
|
-
public requireComponent(name:
|
|
60
|
-
return this.
|
|
105
|
+
public requireComponent<T extends keyof UIComponents>(name: T): UIComponents[T] {
|
|
106
|
+
return this.resolveComponent(name) ?? fail(`UI Component '${name}' is not defined!`);
|
|
61
107
|
}
|
|
62
108
|
|
|
63
109
|
public alert(message: string): void;
|
|
64
110
|
public alert(title: string, message: string): void;
|
|
65
111
|
public alert(messageOrTitle: string, message?: string): void {
|
|
66
|
-
const getProperties = ():
|
|
112
|
+
const getProperties = (): AlertModalProps => {
|
|
67
113
|
if (typeof message !== 'string') {
|
|
68
114
|
return { message: messageOrTitle };
|
|
69
115
|
}
|
|
@@ -74,38 +120,66 @@ export class UIService extends Service {
|
|
|
74
120
|
};
|
|
75
121
|
};
|
|
76
122
|
|
|
77
|
-
this.
|
|
123
|
+
this.modal(this.requireComponent('alert-modal'), getProperties());
|
|
78
124
|
}
|
|
79
125
|
|
|
126
|
+
/* eslint-disable max-len */
|
|
80
127
|
public async confirm(message: string, options?: ConfirmOptions): Promise<boolean>;
|
|
81
128
|
public async confirm(title: string, message: string, options?: ConfirmOptions): Promise<boolean>;
|
|
129
|
+
public async confirm<T extends ConfirmModalCheckboxes>(message: string, options?: ConfirmOptionsWithCheckboxes<T>): Promise<[boolean, Record<keyof T, boolean>]>; // prettier-ignore
|
|
130
|
+
public async confirm<T extends ConfirmModalCheckboxes>(title: string, message: string, options?: ConfirmOptionsWithCheckboxes<T>): Promise<[boolean, Record<keyof T, boolean>]>; // prettier-ignore
|
|
131
|
+
/* eslint-enable max-len */
|
|
132
|
+
|
|
82
133
|
public async confirm(
|
|
83
134
|
messageOrTitle: string,
|
|
84
|
-
messageOrOptions?: string | ConfirmOptions,
|
|
85
|
-
options?: ConfirmOptions,
|
|
86
|
-
): Promise<boolean> {
|
|
87
|
-
const getProperties = ():
|
|
135
|
+
messageOrOptions?: string | ConfirmOptions | ConfirmOptionsWithCheckboxes,
|
|
136
|
+
options?: ConfirmOptions | ConfirmOptionsWithCheckboxes,
|
|
137
|
+
): Promise<boolean | [boolean, Record<string, boolean>]> {
|
|
138
|
+
const getProperties = (): AcceptRefs<ConfirmModalProps> => {
|
|
88
139
|
if (typeof messageOrOptions !== 'string') {
|
|
89
140
|
return {
|
|
90
|
-
message: messageOrTitle,
|
|
91
141
|
...(messageOrOptions ?? {}),
|
|
142
|
+
message: messageOrTitle,
|
|
143
|
+
required: !!messageOrOptions?.required,
|
|
92
144
|
};
|
|
93
145
|
}
|
|
94
146
|
|
|
95
147
|
return {
|
|
148
|
+
...(options ?? {}),
|
|
96
149
|
title: messageOrTitle,
|
|
97
150
|
message: messageOrOptions,
|
|
98
|
-
|
|
151
|
+
required: !!options?.required,
|
|
99
152
|
};
|
|
100
153
|
};
|
|
101
154
|
|
|
102
|
-
const
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
155
|
+
const properties = getProperties();
|
|
156
|
+
const result = await this.modalForm(this.requireComponent('confirm-modal'), properties);
|
|
157
|
+
const confirmed = typeof result === 'object' ? result[0] : (result ?? false);
|
|
158
|
+
const checkboxes =
|
|
159
|
+
typeof result === 'object'
|
|
160
|
+
? result[1]
|
|
161
|
+
: Object.entries(properties.checkboxes ?? {}).reduce(
|
|
162
|
+
(values, [checkbox, { default: defaultValue }]) => ({
|
|
163
|
+
[checkbox]: defaultValue ?? false,
|
|
164
|
+
...values,
|
|
165
|
+
}),
|
|
166
|
+
{} as Record<string, boolean>,
|
|
167
|
+
);
|
|
168
|
+
|
|
169
|
+
for (const [name, checkbox] of Object.entries(properties.checkboxes ?? {})) {
|
|
170
|
+
if (!checkbox.required || checkboxes[name]) {
|
|
171
|
+
continue;
|
|
172
|
+
}
|
|
107
173
|
|
|
108
|
-
|
|
174
|
+
if (confirmed && isDevelopment()) {
|
|
175
|
+
// eslint-disable-next-line no-console
|
|
176
|
+
console.warn(`Confirmed confirm modal was suppressed because required '${name}' checkbox was missing`);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
return [false, checkboxes];
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
return 'checkboxes' in properties ? [confirmed, checkboxes] : confirmed;
|
|
109
183
|
}
|
|
110
184
|
|
|
111
185
|
public async prompt(message: string, options?: PromptOptions): Promise<string | null>;
|
|
@@ -115,47 +189,76 @@ export class UIService extends Service {
|
|
|
115
189
|
messageOrOptions?: string | PromptOptions,
|
|
116
190
|
options?: PromptOptions,
|
|
117
191
|
): Promise<string | null> {
|
|
118
|
-
const
|
|
192
|
+
const trim = options?.trim ?? true;
|
|
193
|
+
const getProperties = (): PromptModalProps => {
|
|
119
194
|
if (typeof messageOrOptions !== 'string') {
|
|
120
195
|
return {
|
|
121
196
|
message: messageOrTitle,
|
|
122
197
|
...(messageOrOptions ?? {}),
|
|
123
|
-
};
|
|
198
|
+
} as PromptModalProps;
|
|
124
199
|
}
|
|
125
200
|
|
|
126
201
|
return {
|
|
127
202
|
title: messageOrTitle,
|
|
128
203
|
message: messageOrOptions,
|
|
129
204
|
...(options ?? {}),
|
|
130
|
-
};
|
|
205
|
+
} as PromptModalProps;
|
|
131
206
|
};
|
|
132
207
|
|
|
133
|
-
const
|
|
134
|
-
|
|
135
|
-
getProperties(),
|
|
136
|
-
);
|
|
137
|
-
const result = await modal.beforeClose;
|
|
208
|
+
const rawResult = await this.modalForm(this.requireComponent('prompt-modal'), getProperties());
|
|
209
|
+
const result = trim && typeof rawResult === 'string' ? rawResult?.trim() : rawResult;
|
|
138
210
|
|
|
139
211
|
return result ?? null;
|
|
140
212
|
}
|
|
141
213
|
|
|
142
|
-
public async loading<T>(operation: Promise<T>): Promise<T>;
|
|
143
|
-
public async loading<T>(message: string, operation: Promise<T>): Promise<T>;
|
|
144
|
-
public async loading<T>(
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
214
|
+
public async loading<T>(operation: Promise<T> | (() => T)): Promise<T>;
|
|
215
|
+
public async loading<T>(message: string, operation: Promise<T> | (() => T)): Promise<T>;
|
|
216
|
+
public async loading<T>(options: LoadingOptions, operation: Promise<T> | (() => T)): Promise<T>;
|
|
217
|
+
public async loading<T>(
|
|
218
|
+
operationOrMessageOrOptions: string | LoadingOptions | Promise<T> | (() => T),
|
|
219
|
+
operation?: Promise<T> | (() => T),
|
|
220
|
+
): Promise<T> {
|
|
221
|
+
const processOperation = (o: Promise<T> | (() => T)) => (typeof o === 'function' ? Promise.resolve(o()) : o);
|
|
222
|
+
const processArgs = (): {
|
|
223
|
+
operationPromise: Promise<T>;
|
|
224
|
+
props?: AcceptRefs<LoadingModalProps>;
|
|
225
|
+
delay?: number;
|
|
226
|
+
} => {
|
|
227
|
+
if (typeof operationOrMessageOrOptions === 'string') {
|
|
228
|
+
return {
|
|
229
|
+
props: { message: operationOrMessageOrOptions },
|
|
230
|
+
operationPromise: processOperation(operation as Promise<T> | (() => T)),
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
if (typeof operationOrMessageOrOptions === 'function' || operationOrMessageOrOptions instanceof Promise) {
|
|
235
|
+
return { operationPromise: processOperation(operationOrMessageOrOptions) };
|
|
148
236
|
}
|
|
149
237
|
|
|
150
|
-
|
|
238
|
+
const { delay, ...props } = operationOrMessageOrOptions;
|
|
239
|
+
|
|
240
|
+
return {
|
|
241
|
+
props,
|
|
242
|
+
delay: unref(delay),
|
|
243
|
+
operationPromise: processOperation(operation as Promise<T> | (() => T)),
|
|
244
|
+
};
|
|
151
245
|
};
|
|
152
246
|
|
|
153
|
-
|
|
247
|
+
let delayed = false;
|
|
248
|
+
const { operationPromise, props, delay } = processArgs();
|
|
249
|
+
|
|
250
|
+
delay && (await Promise.race([after({ ms: delay }).then(() => (delayed = true)), operationPromise]));
|
|
251
|
+
|
|
252
|
+
if (delay && !delayed) {
|
|
253
|
+
return operationPromise;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
const modal = await this.modal(this.requireComponent('loading-modal'), props);
|
|
154
257
|
|
|
155
258
|
try {
|
|
156
|
-
|
|
259
|
+
const result = await operationPromise;
|
|
157
260
|
|
|
158
|
-
|
|
261
|
+
await after({ ms: 500 });
|
|
159
262
|
|
|
160
263
|
return result;
|
|
161
264
|
} finally {
|
|
@@ -163,43 +266,34 @@ export class UIService extends Service {
|
|
|
163
266
|
}
|
|
164
267
|
}
|
|
165
268
|
|
|
166
|
-
public
|
|
167
|
-
const
|
|
269
|
+
public toast(message: string, options: ToastOptions = {}): void {
|
|
270
|
+
const { component, ...otherOptions } = options;
|
|
271
|
+
const toast: UIToast = {
|
|
168
272
|
id: uuid(),
|
|
169
|
-
properties: { message, ...
|
|
170
|
-
component:
|
|
273
|
+
properties: { message, ...otherOptions },
|
|
274
|
+
component: markRaw(component ?? this.requireComponent('toast')),
|
|
171
275
|
};
|
|
172
276
|
|
|
173
|
-
this.setState('
|
|
174
|
-
|
|
175
|
-
setTimeout(() => this.hideSnackbar(snackbar.id), 5000);
|
|
277
|
+
this.setState('toasts', this.toasts.concat(toast));
|
|
176
278
|
}
|
|
177
279
|
|
|
178
|
-
public
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
}
|
|
280
|
+
public modal<T extends Component>(
|
|
281
|
+
...args: {} extends ComponentProps<T>
|
|
282
|
+
? [component: T, props?: AcceptRefs<ComponentProps<T>>]
|
|
283
|
+
: [component: T, props: AcceptRefs<ComponentProps<T>>]
|
|
284
|
+
): Promise<UIModal<ModalResult<T>>>;
|
|
184
285
|
|
|
185
|
-
public
|
|
186
|
-
this.components[name] = component;
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
public async openModal<TModalComponent extends ModalComponent>(
|
|
190
|
-
component: TModalComponent,
|
|
191
|
-
properties?: ModalProperties<TModalComponent>,
|
|
192
|
-
): Promise<Modal<ModalResult<TModalComponent>>> {
|
|
286
|
+
public async modal<T extends Component>(component: T, props?: ComponentProps<T>): Promise<UIModal<ModalResult<T>>> {
|
|
193
287
|
const id = uuid();
|
|
194
|
-
const callbacks: Partial<ModalCallbacks<ModalResult<
|
|
195
|
-
const modal:
|
|
288
|
+
const callbacks: Partial<ModalCallbacks<ModalResult<T>>> = {};
|
|
289
|
+
const modal: UIModal<ModalResult<T>> = {
|
|
196
290
|
id,
|
|
197
|
-
|
|
291
|
+
closing: false,
|
|
292
|
+
properties: props ?? {},
|
|
198
293
|
component: markRaw(component),
|
|
199
294
|
beforeClose: new Promise((resolve) => (callbacks.willClose = resolve)),
|
|
200
|
-
afterClose: new Promise((resolve) => (callbacks.
|
|
295
|
+
afterClose: new Promise((resolve) => (callbacks.hasClosed = resolve)),
|
|
201
296
|
};
|
|
202
|
-
const activeModal = this.modals.at(-1);
|
|
203
297
|
const modals = this.modals.concat(modal);
|
|
204
298
|
|
|
205
299
|
this.modalCallbacks[modal.id] = callbacks;
|
|
@@ -207,58 +301,88 @@ export class UIService extends Service {
|
|
|
207
301
|
this.setState({ modals });
|
|
208
302
|
|
|
209
303
|
await nextTick();
|
|
210
|
-
await (activeModal && Events.emit('hide-modal', { id: activeModal.id }));
|
|
211
|
-
await Promise.all([
|
|
212
|
-
activeModal || Events.emit('show-overlays-backdrop'),
|
|
213
|
-
Events.emit('show-modal', { id: modal.id }),
|
|
214
|
-
]);
|
|
215
304
|
|
|
216
305
|
return modal;
|
|
217
306
|
}
|
|
218
307
|
|
|
308
|
+
public modalForm<T extends Component>(
|
|
309
|
+
...args: {} extends ComponentProps<T>
|
|
310
|
+
? [component: T, props?: AcceptRefs<ComponentProps<T>>]
|
|
311
|
+
: [component: T, props: AcceptRefs<ComponentProps<T>>]
|
|
312
|
+
): Promise<ModalResult<T> | undefined>;
|
|
313
|
+
|
|
314
|
+
public async modalForm<T extends Component>(
|
|
315
|
+
component: T,
|
|
316
|
+
props?: ComponentProps<T>,
|
|
317
|
+
): Promise<ModalResult<T> | undefined> {
|
|
318
|
+
const modal = await this.modal<T>(component, props as ComponentProps<T>);
|
|
319
|
+
const result = await modal.beforeClose;
|
|
320
|
+
|
|
321
|
+
return result;
|
|
322
|
+
}
|
|
323
|
+
|
|
219
324
|
public async closeModal(id: string, result?: unknown): Promise<void> {
|
|
325
|
+
if (!App.isMounted()) {
|
|
326
|
+
await this.removeModal(id, result);
|
|
327
|
+
|
|
328
|
+
return;
|
|
329
|
+
}
|
|
330
|
+
|
|
220
331
|
await Events.emit('close-modal', { id, result });
|
|
221
332
|
}
|
|
222
333
|
|
|
223
|
-
|
|
334
|
+
public async closeAllModals(): Promise<void> {
|
|
335
|
+
while (this.modals.length > 0) {
|
|
336
|
+
await this.closeModal(required(this.modals[this.modals.length - 1]).id);
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
protected override async boot(): Promise<void> {
|
|
224
341
|
this.watchModalEvents();
|
|
225
342
|
this.watchMountedEvent();
|
|
343
|
+
this.watchViewportBreakpoints();
|
|
226
344
|
}
|
|
227
345
|
|
|
228
|
-
private
|
|
229
|
-
|
|
230
|
-
|
|
346
|
+
private async removeModal(id: string, result?: unknown): Promise<void> {
|
|
347
|
+
this.setState(
|
|
348
|
+
'modals',
|
|
349
|
+
this.modals.filter((m) => m.id !== id),
|
|
350
|
+
);
|
|
231
351
|
|
|
232
|
-
|
|
233
|
-
Events.emit('hide-overlays-backdrop');
|
|
234
|
-
}
|
|
235
|
-
});
|
|
352
|
+
this.modalCallbacks[id]?.hasClosed?.(result);
|
|
236
353
|
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
'modals',
|
|
240
|
-
this.modals.filter((m) => m.id !== modal.id),
|
|
241
|
-
);
|
|
354
|
+
delete this.modalCallbacks[id];
|
|
355
|
+
}
|
|
242
356
|
|
|
243
|
-
|
|
357
|
+
private watchModalEvents(): void {
|
|
358
|
+
Events.on('modal-will-close', ({ modal: { id }, result }) => {
|
|
359
|
+
const modal = this.modals.find((_modal) => id === _modal.id);
|
|
244
360
|
|
|
245
|
-
|
|
361
|
+
if (modal) {
|
|
362
|
+
modal.closing = true;
|
|
363
|
+
}
|
|
246
364
|
|
|
247
|
-
|
|
365
|
+
this.modalCallbacks[id]?.willClose?.(result);
|
|
366
|
+
});
|
|
248
367
|
|
|
249
|
-
|
|
368
|
+
Events.on('modal-has-closed', async ({ modal: { id }, result }) => {
|
|
369
|
+
await this.removeModal(id, result);
|
|
250
370
|
});
|
|
251
371
|
}
|
|
252
372
|
|
|
253
373
|
private watchMountedEvent(): void {
|
|
254
374
|
Events.once('application-mounted', async () => {
|
|
255
|
-
|
|
375
|
+
if (!globalThis.document || !globalThis.getComputedStyle) {
|
|
376
|
+
return;
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
const splash = globalThis.document.getElementById('splash');
|
|
256
380
|
|
|
257
381
|
if (!splash) {
|
|
258
382
|
return;
|
|
259
383
|
}
|
|
260
384
|
|
|
261
|
-
if (
|
|
385
|
+
if (globalThis.getComputedStyle(splash).opacity !== '0') {
|
|
262
386
|
splash.style.opacity = '0';
|
|
263
387
|
|
|
264
388
|
await after({ ms: 600 });
|
|
@@ -268,16 +392,24 @@ export class UIService extends Service {
|
|
|
268
392
|
});
|
|
269
393
|
}
|
|
270
394
|
|
|
395
|
+
private watchViewportBreakpoints(): void {
|
|
396
|
+
if (!globalThis.matchMedia) {
|
|
397
|
+
return;
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
const media = globalThis.matchMedia(`(min-width: ${MOBILE_BREAKPOINT}px)`);
|
|
401
|
+
|
|
402
|
+
media.addEventListener('change', () => this.setState({ layout: getCurrentLayout() }));
|
|
403
|
+
}
|
|
404
|
+
|
|
271
405
|
}
|
|
272
406
|
|
|
273
407
|
export default facade(UIService);
|
|
274
408
|
|
|
275
|
-
declare module '
|
|
409
|
+
declare module '@aerogel/core/services/Events' {
|
|
276
410
|
export interface EventsPayload {
|
|
277
|
-
'modal-will-close': { modal: Modal; result?: unknown };
|
|
278
|
-
'modal-closed': { modal: Modal; result?: unknown };
|
|
279
411
|
'close-modal': { id: string; result?: unknown };
|
|
280
|
-
'
|
|
281
|
-
'
|
|
412
|
+
'modal-will-close': { modal: UIModal; result?: unknown };
|
|
413
|
+
'modal-has-closed': { modal: UIModal; result?: unknown };
|
|
282
414
|
}
|
|
283
415
|
}
|
package/src/ui/index.ts
CHANGED
|
@@ -1,52 +1,52 @@
|
|
|
1
|
+
import AlertModal from '@aerogel/core/components/ui/AlertModal.vue';
|
|
2
|
+
import ConfirmModal from '@aerogel/core/components/ui/ConfirmModal.vue';
|
|
3
|
+
import ErrorReportModal from '@aerogel/core/components/ui/ErrorReportModal.vue';
|
|
4
|
+
import LoadingModal from '@aerogel/core/components/ui/LoadingModal.vue';
|
|
5
|
+
import PromptModal from '@aerogel/core/components/ui/PromptModal.vue';
|
|
6
|
+
import StartupCrash from '@aerogel/core/components/ui/StartupCrash.vue';
|
|
7
|
+
import Toast from '@aerogel/core/components/ui/Toast.vue';
|
|
8
|
+
import { bootServices } from '@aerogel/core/services';
|
|
9
|
+
import { definePlugin } from '@aerogel/core/plugins';
|
|
10
|
+
|
|
11
|
+
import UI from './UI';
|
|
12
|
+
import type { UIComponents } from './UI';
|
|
1
13
|
import type { Component } from 'vue';
|
|
2
14
|
|
|
3
|
-
import { bootServices } from '@/services';
|
|
4
|
-
import { definePlugin } from '@/plugins';
|
|
5
|
-
|
|
6
|
-
import UI, { UIComponents } from './UI';
|
|
7
|
-
import AGAlertModal from '../components/modals/AGAlertModal.vue';
|
|
8
|
-
import AGConfirmModal from '../components/modals/AGConfirmModal.vue';
|
|
9
|
-
import AGErrorReportModal from '../components/modals/AGErrorReportModal.vue';
|
|
10
|
-
import AGLoadingModal from '../components/modals/AGLoadingModal.vue';
|
|
11
|
-
import AGPromptModal from '../components/modals/AGPromptModal.vue';
|
|
12
|
-
import AGSnackbar from '../components/snackbars/AGSnackbar.vue';
|
|
13
|
-
import AGStartupCrash from '../components/lib/AGStartupCrash.vue';
|
|
14
|
-
import type { UIComponent } from './UI';
|
|
15
|
-
|
|
16
15
|
const services = { $ui: UI };
|
|
17
16
|
|
|
18
17
|
export * from './UI';
|
|
18
|
+
export * from './utils';
|
|
19
19
|
export { default as UI } from './UI';
|
|
20
20
|
|
|
21
21
|
export type UIServices = typeof services;
|
|
22
22
|
|
|
23
23
|
export default definePlugin({
|
|
24
24
|
async install(app, options) {
|
|
25
|
-
const
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
25
|
+
const components: Partial<Record<keyof UIComponents, Component>> = {
|
|
26
|
+
'alert-modal': AlertModal,
|
|
27
|
+
'confirm-modal': ConfirmModal,
|
|
28
|
+
'error-report-modal': ErrorReportModal,
|
|
29
|
+
'loading-modal': LoadingModal,
|
|
30
|
+
'prompt-modal': PromptModal,
|
|
31
|
+
'startup-crash': StartupCrash,
|
|
32
|
+
'toast': Toast,
|
|
33
|
+
...options.components,
|
|
33
34
|
};
|
|
34
35
|
|
|
35
|
-
Object.entries({
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
}).forEach(([name, component]) => UI.registerComponent(name as UIComponent, component));
|
|
36
|
+
for (const [name, component] of Object.entries(components)) {
|
|
37
|
+
UI.registerComponent(name as keyof UIComponents, component as UIComponents[keyof UIComponents]);
|
|
38
|
+
}
|
|
39
39
|
|
|
40
40
|
await bootServices(app, services);
|
|
41
41
|
},
|
|
42
42
|
});
|
|
43
43
|
|
|
44
|
-
declare module '
|
|
44
|
+
declare module '@aerogel/core/bootstrap/options' {
|
|
45
45
|
export interface AerogelOptions {
|
|
46
|
-
components?: Partial<
|
|
46
|
+
components?: Partial<Partial<UIComponents>>;
|
|
47
47
|
}
|
|
48
48
|
}
|
|
49
49
|
|
|
50
|
-
declare module '
|
|
50
|
+
declare module '@aerogel/core/services' {
|
|
51
51
|
export interface Services extends UIServices {}
|
|
52
52
|
}
|
package/src/ui/utils.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export const MOBILE_BREAKPOINT = 768;
|
|
2
|
+
|
|
3
|
+
export const Layouts = {
|
|
4
|
+
Mobile: 'mobile',
|
|
5
|
+
Desktop: 'desktop',
|
|
6
|
+
} as const;
|
|
7
|
+
|
|
8
|
+
export type Layout = (typeof Layouts)[keyof typeof Layouts];
|
|
9
|
+
|
|
10
|
+
export function getCurrentLayout(): Layout {
|
|
11
|
+
if (globalThis.innerWidth > MOBILE_BREAKPOINT) {
|
|
12
|
+
return Layouts.Desktop;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
return Layouts.Mobile;
|
|
16
|
+
}
|