@aerogel/core 0.0.0-next.88c59e62f64db70aedfbc4c31b5bbc287be44483 → 0.0.0-next.8bd66d5f5e264650120ea3cc37519f2409c6cc39
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 +2376 -990
- package/dist/aerogel-core.js +3669 -0
- package/dist/aerogel-core.js.map +1 -0
- package/package.json +38 -34
- package/src/bootstrap/bootstrap.test.ts +7 -10
- package/src/bootstrap/index.ts +41 -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 +86 -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/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} +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 +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 +31 -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 +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 +22 -28
- 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 +110 -0
- package/src/forms/FormController.ts +250 -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 +76 -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 +20 -8
- package/src/services/store.ts +8 -5
- 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 +331 -96
- 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 -4
- 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/AGHeadlessSelect.ts +0 -31
- package/src/components/headless/forms/AGHeadlessSelect.vue +0 -45
- package/src/components/headless/forms/AGHeadlessSelectButton.ts +0 -3
- package/src/components/headless/forms/AGHeadlessSelectLabel.ts +0 -3
- package/src/components/headless/forms/AGHeadlessSelectOption.ts +0 -8
- package/src/components/headless/forms/AGHeadlessSelectOptions.ts +0 -3
- package/src/components/headless/forms/index.ts +0 -12
- 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 -179
- 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
package/src/ui/UI.ts
CHANGED
|
@@ -1,121 +1,299 @@
|
|
|
1
|
-
import { 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
|
|
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
|
+
delay?: number;
|
|
68
|
+
}>;
|
|
69
|
+
|
|
70
|
+
export interface ConfirmOptionsWithCheckboxes<T extends ConfirmModalCheckboxes = ConfirmModalCheckboxes>
|
|
71
|
+
extends ConfirmOptions {
|
|
72
|
+
checkboxes?: T;
|
|
73
|
+
}
|
|
31
74
|
|
|
32
|
-
export
|
|
75
|
+
export type PromptOptions = AcceptRefs<{
|
|
76
|
+
label?: string;
|
|
77
|
+
defaultValue?: string;
|
|
78
|
+
placeholder?: string;
|
|
79
|
+
acceptText?: string;
|
|
80
|
+
acceptVariant?: ButtonVariant;
|
|
81
|
+
cancelText?: string;
|
|
82
|
+
cancelVariant?: ButtonVariant;
|
|
83
|
+
trim?: boolean;
|
|
84
|
+
}>;
|
|
85
|
+
|
|
86
|
+
export interface ToastOptions {
|
|
33
87
|
component?: Component;
|
|
34
|
-
|
|
35
|
-
actions?:
|
|
88
|
+
variant?: ToastVariant;
|
|
89
|
+
actions?: ToastAction[];
|
|
36
90
|
}
|
|
37
91
|
|
|
38
92
|
export class UIService extends Service {
|
|
39
93
|
|
|
40
94
|
private modalCallbacks: Record<string, Partial<ModalCallbacks>> = {};
|
|
41
|
-
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
|
+
}
|
|
42
104
|
|
|
43
|
-
public requireComponent(name:
|
|
44
|
-
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!`);
|
|
45
107
|
}
|
|
46
108
|
|
|
47
109
|
public alert(message: string): void;
|
|
48
110
|
public alert(title: string, message: string): void;
|
|
49
111
|
public alert(messageOrTitle: string, message?: string): void {
|
|
50
|
-
const
|
|
112
|
+
const getProperties = (): AlertModalProps => {
|
|
113
|
+
if (typeof message !== 'string') {
|
|
114
|
+
return { message: messageOrTitle };
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return {
|
|
118
|
+
title: messageOrTitle,
|
|
119
|
+
message,
|
|
120
|
+
};
|
|
121
|
+
};
|
|
51
122
|
|
|
52
|
-
this.
|
|
123
|
+
this.modal(this.requireComponent('alert-modal'), getProperties());
|
|
53
124
|
}
|
|
54
125
|
|
|
55
|
-
|
|
56
|
-
public async confirm(
|
|
57
|
-
public async confirm(
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
126
|
+
/* eslint-disable max-len */
|
|
127
|
+
public async confirm(message: string, options?: ConfirmOptions): Promise<boolean>;
|
|
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
|
+
|
|
133
|
+
public async confirm(
|
|
134
|
+
messageOrTitle: string,
|
|
135
|
+
messageOrOptions?: string | ConfirmOptions | ConfirmOptionsWithCheckboxes,
|
|
136
|
+
options?: ConfirmOptions | ConfirmOptionsWithCheckboxes,
|
|
137
|
+
): Promise<boolean | [boolean, Record<string, boolean>]> {
|
|
138
|
+
const getProperties = (): AcceptRefs<ConfirmModalProps> => {
|
|
139
|
+
if (typeof messageOrOptions !== 'string') {
|
|
140
|
+
return {
|
|
141
|
+
...(messageOrOptions ?? {}),
|
|
142
|
+
message: messageOrTitle,
|
|
143
|
+
required: !!messageOrOptions?.required,
|
|
144
|
+
};
|
|
145
|
+
}
|
|
64
146
|
|
|
65
|
-
|
|
66
|
-
|
|
147
|
+
return {
|
|
148
|
+
...(options ?? {}),
|
|
149
|
+
title: messageOrTitle,
|
|
150
|
+
message: messageOrOptions,
|
|
151
|
+
required: !!options?.required,
|
|
152
|
+
};
|
|
153
|
+
};
|
|
67
154
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
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
|
+
}
|
|
72
173
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
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
|
+
}
|
|
76
178
|
|
|
77
|
-
|
|
179
|
+
return [false, checkboxes];
|
|
180
|
+
}
|
|
78
181
|
|
|
79
|
-
return
|
|
182
|
+
return 'checkboxes' in properties ? [confirmed, checkboxes] : confirmed;
|
|
80
183
|
}
|
|
81
184
|
|
|
82
|
-
public
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
185
|
+
public async prompt(message: string, options?: PromptOptions): Promise<string | null>;
|
|
186
|
+
public async prompt(title: string, message: string, options?: PromptOptions): Promise<string | null>;
|
|
187
|
+
public async prompt(
|
|
188
|
+
messageOrTitle: string,
|
|
189
|
+
messageOrOptions?: string | PromptOptions,
|
|
190
|
+
options?: PromptOptions,
|
|
191
|
+
): Promise<string | null> {
|
|
192
|
+
const trim = options?.trim ?? true;
|
|
193
|
+
const getProperties = (): PromptModalProps => {
|
|
194
|
+
if (typeof messageOrOptions !== 'string') {
|
|
195
|
+
return {
|
|
196
|
+
message: messageOrTitle,
|
|
197
|
+
...(messageOrOptions ?? {}),
|
|
198
|
+
} as PromptModalProps;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
return {
|
|
202
|
+
title: messageOrTitle,
|
|
203
|
+
message: messageOrOptions,
|
|
204
|
+
...(options ?? {}),
|
|
205
|
+
} as PromptModalProps;
|
|
87
206
|
};
|
|
88
207
|
|
|
89
|
-
this.
|
|
208
|
+
const rawResult = await this.modalForm(this.requireComponent('prompt-modal'), getProperties());
|
|
209
|
+
const result = trim && typeof rawResult === 'string' ? rawResult?.trim() : rawResult;
|
|
90
210
|
|
|
91
|
-
|
|
211
|
+
return result ?? null;
|
|
92
212
|
}
|
|
93
213
|
|
|
94
|
-
public
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
)
|
|
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) };
|
|
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
|
+
};
|
|
245
|
+
};
|
|
246
|
+
|
|
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);
|
|
257
|
+
|
|
258
|
+
try {
|
|
259
|
+
const result = await operationPromise;
|
|
260
|
+
|
|
261
|
+
await after({ ms: 500 });
|
|
262
|
+
|
|
263
|
+
return result;
|
|
264
|
+
} finally {
|
|
265
|
+
await this.closeModal(modal.id);
|
|
266
|
+
}
|
|
99
267
|
}
|
|
100
268
|
|
|
101
|
-
public
|
|
102
|
-
|
|
269
|
+
public toast(message: string, options: ToastOptions = {}): void {
|
|
270
|
+
const { component, ...otherOptions } = options;
|
|
271
|
+
const toast: UIToast = {
|
|
272
|
+
id: uuid(),
|
|
273
|
+
properties: { message, ...otherOptions },
|
|
274
|
+
component: markRaw(component ?? this.requireComponent('toast')),
|
|
275
|
+
};
|
|
276
|
+
|
|
277
|
+
this.setState('toasts', this.toasts.concat(toast));
|
|
103
278
|
}
|
|
104
279
|
|
|
105
|
-
public
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
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>>>;
|
|
285
|
+
|
|
286
|
+
public async modal<T extends Component>(component: T, props?: ComponentProps<T>): Promise<UIModal<ModalResult<T>>> {
|
|
109
287
|
const id = uuid();
|
|
110
|
-
const callbacks: Partial<ModalCallbacks<ModalResult<
|
|
111
|
-
const modal:
|
|
288
|
+
const callbacks: Partial<ModalCallbacks<ModalResult<T>>> = {};
|
|
289
|
+
const modal: UIModal<ModalResult<T>> = {
|
|
112
290
|
id,
|
|
113
|
-
|
|
291
|
+
closing: false,
|
|
292
|
+
properties: props ?? {},
|
|
114
293
|
component: markRaw(component),
|
|
115
294
|
beforeClose: new Promise((resolve) => (callbacks.willClose = resolve)),
|
|
116
|
-
afterClose: new Promise((resolve) => (callbacks.
|
|
295
|
+
afterClose: new Promise((resolve) => (callbacks.hasClosed = resolve)),
|
|
117
296
|
};
|
|
118
|
-
const activeModal = this.modals.at(-1);
|
|
119
297
|
const modals = this.modals.concat(modal);
|
|
120
298
|
|
|
121
299
|
this.modalCallbacks[modal.id] = callbacks;
|
|
@@ -123,58 +301,115 @@ export class UIService extends Service {
|
|
|
123
301
|
this.setState({ modals });
|
|
124
302
|
|
|
125
303
|
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
304
|
|
|
132
305
|
return modal;
|
|
133
306
|
}
|
|
134
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
|
+
|
|
135
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
|
+
|
|
136
331
|
await Events.emit('close-modal', { id, result });
|
|
137
332
|
}
|
|
138
333
|
|
|
139
|
-
|
|
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> {
|
|
140
341
|
this.watchModalEvents();
|
|
342
|
+
this.watchMountedEvent();
|
|
343
|
+
this.watchViewportBreakpoints();
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
private async removeModal(id: string, result?: unknown): Promise<void> {
|
|
347
|
+
this.setState(
|
|
348
|
+
'modals',
|
|
349
|
+
this.modals.filter((m) => m.id !== id),
|
|
350
|
+
);
|
|
351
|
+
|
|
352
|
+
this.modalCallbacks[id]?.hasClosed?.(result);
|
|
353
|
+
|
|
354
|
+
delete this.modalCallbacks[id];
|
|
141
355
|
}
|
|
142
356
|
|
|
143
357
|
private watchModalEvents(): void {
|
|
144
|
-
Events.on('modal-will-close', ({ modal, result }) => {
|
|
145
|
-
this.
|
|
358
|
+
Events.on('modal-will-close', ({ modal: { id }, result }) => {
|
|
359
|
+
const modal = this.modals.find((_modal) => id === _modal.id);
|
|
146
360
|
|
|
147
|
-
if (
|
|
148
|
-
|
|
361
|
+
if (modal) {
|
|
362
|
+
modal.closing = true;
|
|
149
363
|
}
|
|
364
|
+
|
|
365
|
+
this.modalCallbacks[id]?.willClose?.(result);
|
|
150
366
|
});
|
|
151
367
|
|
|
152
|
-
Events.on('modal-closed', async ({ modal, result }) => {
|
|
153
|
-
this.
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
);
|
|
368
|
+
Events.on('modal-has-closed', async ({ modal: { id }, result }) => {
|
|
369
|
+
await this.removeModal(id, result);
|
|
370
|
+
});
|
|
371
|
+
}
|
|
157
372
|
|
|
158
|
-
|
|
373
|
+
private watchMountedEvent(): void {
|
|
374
|
+
Events.once('application-mounted', async () => {
|
|
375
|
+
if (!globalThis.document || !globalThis.getComputedStyle) {
|
|
376
|
+
return;
|
|
377
|
+
}
|
|
159
378
|
|
|
160
|
-
|
|
379
|
+
const splash = globalThis.document.getElementById('splash');
|
|
161
380
|
|
|
162
|
-
|
|
381
|
+
if (!splash) {
|
|
382
|
+
return;
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
if (globalThis.getComputedStyle(splash).opacity !== '0') {
|
|
386
|
+
splash.style.opacity = '0';
|
|
387
|
+
|
|
388
|
+
await after({ ms: 600 });
|
|
389
|
+
}
|
|
163
390
|
|
|
164
|
-
|
|
391
|
+
splash.remove();
|
|
165
392
|
});
|
|
166
393
|
}
|
|
167
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
|
+
|
|
168
405
|
}
|
|
169
406
|
|
|
170
|
-
export default facade(
|
|
407
|
+
export default facade(UIService);
|
|
171
408
|
|
|
172
|
-
declare module '
|
|
409
|
+
declare module '@aerogel/core/services/Events' {
|
|
173
410
|
export interface EventsPayload {
|
|
174
|
-
'modal-will-close': { modal: Modal; result?: unknown };
|
|
175
|
-
'modal-closed': { modal: Modal; result?: unknown };
|
|
176
411
|
'close-modal': { id: string; result?: unknown };
|
|
177
|
-
'
|
|
178
|
-
'
|
|
412
|
+
'modal-will-close': { modal: UIModal; result?: unknown };
|
|
413
|
+
'modal-has-closed': { modal: UIModal; result?: unknown };
|
|
179
414
|
}
|
|
180
415
|
}
|
package/src/ui/index.ts
CHANGED
|
@@ -1,47 +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 AGSnackbar from '../components/snackbars/AGSnackbar.vue';
|
|
12
|
-
import type { UIComponent } from './UI';
|
|
13
|
-
|
|
14
|
-
export { UI, UIComponents, UIComponent };
|
|
15
|
-
|
|
16
15
|
const services = { $ui: UI };
|
|
17
16
|
|
|
17
|
+
export * from './UI';
|
|
18
|
+
export * from './utils';
|
|
19
|
+
export { default as UI } from './UI';
|
|
20
|
+
|
|
18
21
|
export type UIServices = typeof services;
|
|
19
22
|
|
|
20
23
|
export default definePlugin({
|
|
21
24
|
async install(app, options) {
|
|
22
|
-
const
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
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,
|
|
28
34
|
};
|
|
29
35
|
|
|
30
|
-
Object.entries({
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
}).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
|
+
}
|
|
34
39
|
|
|
35
40
|
await bootServices(app, services);
|
|
36
41
|
},
|
|
37
42
|
});
|
|
38
43
|
|
|
39
|
-
declare module '
|
|
40
|
-
interface AerogelOptions {
|
|
41
|
-
components?: Partial<
|
|
44
|
+
declare module '@aerogel/core/bootstrap/options' {
|
|
45
|
+
export interface AerogelOptions {
|
|
46
|
+
components?: Partial<Partial<UIComponents>>;
|
|
42
47
|
}
|
|
43
48
|
}
|
|
44
49
|
|
|
45
|
-
declare module '
|
|
50
|
+
declare module '@aerogel/core/services' {
|
|
46
51
|
export interface Services extends UIServices {}
|
|
47
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
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import clsx from 'clsx';
|
|
2
|
+
import { unref } from 'vue';
|
|
3
|
+
import { cva } from 'class-variance-authority';
|
|
4
|
+
import { twMerge } from 'tailwind-merge';
|
|
5
|
+
import type { ClassValue } from 'clsx';
|
|
6
|
+
import type { PropType } from 'vue';
|
|
7
|
+
import type { GetClosureArgs, GetClosureResult } from '@noeldemartin/utils';
|
|
8
|
+
|
|
9
|
+
export type CVAConfig<T> = NonNullable<GetClosureArgs<typeof cva<T>>[1]>;
|
|
10
|
+
export type CVAProps<T> = NonNullable<GetClosureArgs<GetClosureResult<typeof cva<T>>>[0]>;
|
|
11
|
+
export type Variants<T extends Record<string, string | boolean>> = Required<{
|
|
12
|
+
[K in keyof T]: Exclude<T[K], undefined> extends string
|
|
13
|
+
? { [key in Exclude<T[K], undefined>]: string | null }
|
|
14
|
+
: { true: string | null; false: string | null };
|
|
15
|
+
}>;
|
|
16
|
+
|
|
17
|
+
export type ComponentPropDefinitions<T> = {
|
|
18
|
+
[K in keyof T]: {
|
|
19
|
+
type?: PropType<T[K]>;
|
|
20
|
+
default: T[K] | (() => T[K]) | null;
|
|
21
|
+
};
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export type PickComponentProps<TValues, TDefinitions> = {
|
|
25
|
+
[K in keyof TValues]: K extends keyof TDefinitions ? TValues[K] : never;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export function variantClasses<T>(
|
|
29
|
+
value: { baseClasses?: string } & CVAProps<T>,
|
|
30
|
+
config: { baseClasses?: string } & CVAConfig<T>,
|
|
31
|
+
): string {
|
|
32
|
+
const { baseClasses: valueBaseClasses, ...values } = value;
|
|
33
|
+
const { baseClasses: configBaseClasses, ...configs } = config;
|
|
34
|
+
const variants = cva(configBaseClasses, configs as CVAConfig<T>);
|
|
35
|
+
|
|
36
|
+
return classes(variants(values as CVAProps<T>), unref(valueBaseClasses));
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function classes(...inputs: ClassValue[]): string {
|
|
40
|
+
return twMerge(clsx(inputs));
|
|
41
|
+
}
|