@aerogel/core 0.0.0-next.fd1bd21aea7a9ab8c4eab69a5f5776db5de8bf35 → 0.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/dist/aerogel-core.css +1 -0
- package/dist/aerogel-core.d.ts +2263 -1698
- package/dist/aerogel-core.js +3809 -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 +25 -16
- 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/{forms/AGHeadlessInputDescription.vue → HeadlessInputDescription.vue} +7 -8
- 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/{forms/AGHeadlessInputTextArea.vue → HeadlessInputTextArea.vue} +11 -11
- 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/{forms/AGForm.vue → ui/Form.vue} +4 -5
- 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/ui/StartupCrash.vue +76 -0
- 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 +9 -5
- package/src/directives/measure.ts +33 -8
- package/src/errors/Errors.state.ts +2 -1
- package/src/errors/Errors.ts +56 -33
- package/src/errors/JobCancelledError.ts +3 -0
- package/src/errors/index.ts +15 -8
- package/src/errors/settings/Debug.vue +14 -0
- package/src/errors/settings/index.ts +10 -0
- package/src/errors/utils.ts +17 -1
- package/src/forms/FormController.test.ts +113 -0
- package/src/forms/{Form.ts → FormController.ts} +73 -42
- package/src/forms/index.ts +3 -3
- package/src/forms/utils.ts +65 -24
- package/src/forms/validation.ts +50 -0
- package/src/index.css +76 -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 +48 -21
- 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 +1 -1
- package/src/services/Events.test.ts +8 -8
- package/src/services/Events.ts +16 -12
- package/src/services/Service.ts +135 -59
- package/src/services/Storage.ts +20 -0
- package/src/services/index.ts +16 -7
- package/src/services/utils.ts +18 -0
- package/src/testing/index.ts +8 -3
- package/src/testing/setup.ts +11 -0
- package/src/ui/UI.state.ts +14 -12
- package/src/ui/UI.ts +250 -123
- 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/AGInput.vue +0 -40
- 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 -56
- package/src/components/headless/forms/AGHeadlessInput.ts +0 -32
- package/src/components/headless/forms/AGHeadlessInput.vue +0 -57
- package/src/components/headless/forms/AGHeadlessInputInput.vue +0 -81
- 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/composition.ts +0 -10
- package/src/components/headless/forms/index.ts +0 -17
- 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 -41
- package/src/components/lib/AGMeasured.vue +0 -15
- package/src/components/lib/AGStartupCrash.vue +0 -31
- 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/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/{main.ts → index.ts} +0 -0
package/src/ui/UI.ts
CHANGED
|
@@ -1,70 +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;
|
|
82
|
+
cancelVariant?: ButtonVariant;
|
|
46
83
|
trim?: boolean;
|
|
47
|
-
}
|
|
84
|
+
}>;
|
|
48
85
|
|
|
49
|
-
export interface
|
|
86
|
+
export interface ToastOptions {
|
|
50
87
|
component?: Component;
|
|
51
|
-
|
|
52
|
-
actions?:
|
|
88
|
+
variant?: ToastVariant;
|
|
89
|
+
actions?: ToastAction[];
|
|
53
90
|
}
|
|
54
91
|
|
|
55
92
|
export class UIService extends Service {
|
|
56
93
|
|
|
57
94
|
private modalCallbacks: Record<string, Partial<ModalCallbacks>> = {};
|
|
58
|
-
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
|
+
}
|
|
59
104
|
|
|
60
|
-
public requireComponent(name:
|
|
61
|
-
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!`);
|
|
62
107
|
}
|
|
63
108
|
|
|
64
109
|
public alert(message: string): void;
|
|
65
110
|
public alert(title: string, message: string): void;
|
|
66
111
|
public alert(messageOrTitle: string, message?: string): void {
|
|
67
|
-
const getProperties = ():
|
|
112
|
+
const getProperties = (): AlertModalProps => {
|
|
68
113
|
if (typeof message !== 'string') {
|
|
69
114
|
return { message: messageOrTitle };
|
|
70
115
|
}
|
|
@@ -75,38 +120,66 @@ export class UIService extends Service {
|
|
|
75
120
|
};
|
|
76
121
|
};
|
|
77
122
|
|
|
78
|
-
this.
|
|
123
|
+
this.modal(this.requireComponent('alert-modal'), getProperties());
|
|
79
124
|
}
|
|
80
125
|
|
|
126
|
+
/* eslint-disable max-len */
|
|
81
127
|
public async confirm(message: string, options?: ConfirmOptions): Promise<boolean>;
|
|
82
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
|
+
|
|
83
133
|
public async confirm(
|
|
84
134
|
messageOrTitle: string,
|
|
85
|
-
messageOrOptions?: string | ConfirmOptions,
|
|
86
|
-
options?: ConfirmOptions,
|
|
87
|
-
): Promise<boolean> {
|
|
88
|
-
const getProperties = ():
|
|
135
|
+
messageOrOptions?: string | ConfirmOptions | ConfirmOptionsWithCheckboxes,
|
|
136
|
+
options?: ConfirmOptions | ConfirmOptionsWithCheckboxes,
|
|
137
|
+
): Promise<boolean | [boolean, Record<string, boolean>]> {
|
|
138
|
+
const getProperties = (): AcceptRefs<ConfirmModalProps> => {
|
|
89
139
|
if (typeof messageOrOptions !== 'string') {
|
|
90
140
|
return {
|
|
91
|
-
message: messageOrTitle,
|
|
92
141
|
...(messageOrOptions ?? {}),
|
|
142
|
+
message: messageOrTitle,
|
|
143
|
+
required: !!messageOrOptions?.required,
|
|
93
144
|
};
|
|
94
145
|
}
|
|
95
146
|
|
|
96
147
|
return {
|
|
148
|
+
...(options ?? {}),
|
|
97
149
|
title: messageOrTitle,
|
|
98
150
|
message: messageOrOptions,
|
|
99
|
-
|
|
151
|
+
required: !!options?.required,
|
|
100
152
|
};
|
|
101
153
|
};
|
|
102
154
|
|
|
103
|
-
const
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
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
|
+
}
|
|
108
173
|
|
|
109
|
-
|
|
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;
|
|
110
183
|
}
|
|
111
184
|
|
|
112
185
|
public async prompt(message: string, options?: PromptOptions): Promise<string | null>;
|
|
@@ -117,48 +190,75 @@ export class UIService extends Service {
|
|
|
117
190
|
options?: PromptOptions,
|
|
118
191
|
): Promise<string | null> {
|
|
119
192
|
const trim = options?.trim ?? true;
|
|
120
|
-
const getProperties = ():
|
|
193
|
+
const getProperties = (): PromptModalProps => {
|
|
121
194
|
if (typeof messageOrOptions !== 'string') {
|
|
122
195
|
return {
|
|
123
196
|
message: messageOrTitle,
|
|
124
197
|
...(messageOrOptions ?? {}),
|
|
125
|
-
};
|
|
198
|
+
} as PromptModalProps;
|
|
126
199
|
}
|
|
127
200
|
|
|
128
201
|
return {
|
|
129
202
|
title: messageOrTitle,
|
|
130
203
|
message: messageOrOptions,
|
|
131
204
|
...(options ?? {}),
|
|
132
|
-
};
|
|
205
|
+
} as PromptModalProps;
|
|
133
206
|
};
|
|
134
207
|
|
|
135
|
-
const
|
|
136
|
-
|
|
137
|
-
getProperties(),
|
|
138
|
-
);
|
|
139
|
-
const rawResult = await modal.beforeClose;
|
|
140
|
-
const result = trim ? rawResult?.trim() : rawResult;
|
|
208
|
+
const rawResult = await this.modalForm(this.requireComponent('prompt-modal'), getProperties());
|
|
209
|
+
const result = trim && typeof rawResult === 'string' ? rawResult?.trim() : rawResult;
|
|
141
210
|
|
|
142
211
|
return result ?? null;
|
|
143
212
|
}
|
|
144
213
|
|
|
145
|
-
public async loading<T>(operation: Promise<T>): Promise<T>;
|
|
146
|
-
public async loading<T>(message: string, operation: Promise<T>): Promise<T>;
|
|
147
|
-
public async loading<T>(
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
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
|
+
};
|
|
151
232
|
}
|
|
152
233
|
|
|
153
|
-
|
|
234
|
+
if (typeof operationOrMessageOrOptions === 'function' || operationOrMessageOrOptions instanceof Promise) {
|
|
235
|
+
return { operationPromise: processOperation(operationOrMessageOrOptions) };
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
const { delay, ...props } = operationOrMessageOrOptions;
|
|
239
|
+
|
|
240
|
+
return {
|
|
241
|
+
props,
|
|
242
|
+
delay: unref(delay),
|
|
243
|
+
operationPromise: processOperation(operation as Promise<T> | (() => T)),
|
|
244
|
+
};
|
|
154
245
|
};
|
|
155
246
|
|
|
156
|
-
|
|
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);
|
|
157
257
|
|
|
158
258
|
try {
|
|
159
|
-
|
|
259
|
+
const result = await operationPromise;
|
|
160
260
|
|
|
161
|
-
|
|
261
|
+
await after({ ms: 500 });
|
|
162
262
|
|
|
163
263
|
return result;
|
|
164
264
|
} finally {
|
|
@@ -166,43 +266,34 @@ export class UIService extends Service {
|
|
|
166
266
|
}
|
|
167
267
|
}
|
|
168
268
|
|
|
169
|
-
public
|
|
170
|
-
const
|
|
269
|
+
public toast(message: string, options: ToastOptions = {}): void {
|
|
270
|
+
const { component, ...otherOptions } = options;
|
|
271
|
+
const toast: UIToast = {
|
|
171
272
|
id: uuid(),
|
|
172
|
-
properties: { message, ...
|
|
173
|
-
component: markRaw(
|
|
273
|
+
properties: { message, ...otherOptions },
|
|
274
|
+
component: markRaw(component ?? this.requireComponent('toast')),
|
|
174
275
|
};
|
|
175
276
|
|
|
176
|
-
this.setState('
|
|
177
|
-
|
|
178
|
-
setTimeout(() => this.hideSnackbar(snackbar.id), 5000);
|
|
277
|
+
this.setState('toasts', this.toasts.concat(toast));
|
|
179
278
|
}
|
|
180
279
|
|
|
181
|
-
public
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
}
|
|
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>>>;
|
|
187
285
|
|
|
188
|
-
public
|
|
189
|
-
this.components[name] = component;
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
public async openModal<TModalComponent extends ModalComponent>(
|
|
193
|
-
component: TModalComponent,
|
|
194
|
-
properties?: ModalProperties<TModalComponent>,
|
|
195
|
-
): Promise<Modal<ModalResult<TModalComponent>>> {
|
|
286
|
+
public async modal<T extends Component>(component: T, props?: ComponentProps<T>): Promise<UIModal<ModalResult<T>>> {
|
|
196
287
|
const id = uuid();
|
|
197
|
-
const callbacks: Partial<ModalCallbacks<ModalResult<
|
|
198
|
-
const modal:
|
|
288
|
+
const callbacks: Partial<ModalCallbacks<ModalResult<T>>> = {};
|
|
289
|
+
const modal: UIModal<ModalResult<T>> = {
|
|
199
290
|
id,
|
|
200
|
-
|
|
291
|
+
closing: false,
|
|
292
|
+
properties: props ?? {},
|
|
201
293
|
component: markRaw(component),
|
|
202
294
|
beforeClose: new Promise((resolve) => (callbacks.willClose = resolve)),
|
|
203
|
-
afterClose: new Promise((resolve) => (callbacks.
|
|
295
|
+
afterClose: new Promise((resolve) => (callbacks.hasClosed = resolve)),
|
|
204
296
|
};
|
|
205
|
-
const activeModal = this.modals.at(-1);
|
|
206
297
|
const modals = this.modals.concat(modal);
|
|
207
298
|
|
|
208
299
|
this.modalCallbacks[modal.id] = callbacks;
|
|
@@ -210,58 +301,88 @@ export class UIService extends Service {
|
|
|
210
301
|
this.setState({ modals });
|
|
211
302
|
|
|
212
303
|
await nextTick();
|
|
213
|
-
await (activeModal && Events.emit('hide-modal', { id: activeModal.id }));
|
|
214
|
-
await Promise.all([
|
|
215
|
-
activeModal || Events.emit('show-overlays-backdrop'),
|
|
216
|
-
Events.emit('show-modal', { id: modal.id }),
|
|
217
|
-
]);
|
|
218
304
|
|
|
219
305
|
return modal;
|
|
220
306
|
}
|
|
221
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
|
+
|
|
222
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
|
+
|
|
223
331
|
await Events.emit('close-modal', { id, result });
|
|
224
332
|
}
|
|
225
333
|
|
|
226
|
-
|
|
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> {
|
|
227
341
|
this.watchModalEvents();
|
|
228
342
|
this.watchMountedEvent();
|
|
343
|
+
this.watchViewportBreakpoints();
|
|
229
344
|
}
|
|
230
345
|
|
|
231
|
-
private
|
|
232
|
-
|
|
233
|
-
|
|
346
|
+
private async removeModal(id: string, result?: unknown): Promise<void> {
|
|
347
|
+
this.setState(
|
|
348
|
+
'modals',
|
|
349
|
+
this.modals.filter((m) => m.id !== id),
|
|
350
|
+
);
|
|
234
351
|
|
|
235
|
-
|
|
236
|
-
Events.emit('hide-overlays-backdrop');
|
|
237
|
-
}
|
|
238
|
-
});
|
|
352
|
+
this.modalCallbacks[id]?.hasClosed?.(result);
|
|
239
353
|
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
'modals',
|
|
243
|
-
this.modals.filter((m) => m.id !== modal.id),
|
|
244
|
-
);
|
|
354
|
+
delete this.modalCallbacks[id];
|
|
355
|
+
}
|
|
245
356
|
|
|
246
|
-
|
|
357
|
+
private watchModalEvents(): void {
|
|
358
|
+
Events.on('modal-will-close', ({ modal: { id }, result }) => {
|
|
359
|
+
const modal = this.modals.find((_modal) => id === _modal.id);
|
|
247
360
|
|
|
248
|
-
|
|
361
|
+
if (modal) {
|
|
362
|
+
modal.closing = true;
|
|
363
|
+
}
|
|
249
364
|
|
|
250
|
-
|
|
365
|
+
this.modalCallbacks[id]?.willClose?.(result);
|
|
366
|
+
});
|
|
251
367
|
|
|
252
|
-
|
|
368
|
+
Events.on('modal-has-closed', async ({ modal: { id }, result }) => {
|
|
369
|
+
await this.removeModal(id, result);
|
|
253
370
|
});
|
|
254
371
|
}
|
|
255
372
|
|
|
256
373
|
private watchMountedEvent(): void {
|
|
257
374
|
Events.once('application-mounted', async () => {
|
|
258
|
-
|
|
375
|
+
if (!globalThis.document || !globalThis.getComputedStyle) {
|
|
376
|
+
return;
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
const splash = globalThis.document.getElementById('splash');
|
|
259
380
|
|
|
260
381
|
if (!splash) {
|
|
261
382
|
return;
|
|
262
383
|
}
|
|
263
384
|
|
|
264
|
-
if (
|
|
385
|
+
if (globalThis.getComputedStyle(splash).opacity !== '0') {
|
|
265
386
|
splash.style.opacity = '0';
|
|
266
387
|
|
|
267
388
|
await after({ ms: 600 });
|
|
@@ -271,18 +392,24 @@ export class UIService extends Service {
|
|
|
271
392
|
});
|
|
272
393
|
}
|
|
273
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
|
+
|
|
274
405
|
}
|
|
275
406
|
|
|
276
407
|
export default facade(UIService);
|
|
277
408
|
|
|
278
|
-
declare module '
|
|
409
|
+
declare module '@aerogel/core/services/Events' {
|
|
279
410
|
export interface EventsPayload {
|
|
280
411
|
'close-modal': { id: string; result?: unknown };
|
|
281
|
-
'
|
|
282
|
-
'
|
|
283
|
-
'modal-closed': { modal: Modal; result?: unknown };
|
|
284
|
-
'modal-will-close': { modal: Modal; result?: unknown };
|
|
285
|
-
'show-modal': { id: string };
|
|
286
|
-
'show-overlays-backdrop': void;
|
|
412
|
+
'modal-will-close': { modal: UIModal; result?: unknown };
|
|
413
|
+
'modal-has-closed': { modal: UIModal; result?: unknown };
|
|
287
414
|
}
|
|
288
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
|
+
}
|