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