@aerogel/core 0.0.0-next.824cf5311c4335d119158f507dad094ed4f4f0b6 → 0.0.0-next.8ae083000611b11799d37033e9a5250d0d07c324
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 +2101 -1608
- package/dist/aerogel-core.js +3273 -0
- package/dist/aerogel-core.js.map +1 -0
- package/package.json +31 -37
- package/src/bootstrap/bootstrap.test.ts +4 -8
- package/src/bootstrap/index.ts +23 -13
- package/src/bootstrap/options.ts +1 -1
- package/src/components/AppLayout.vue +14 -0
- package/src/components/{AGAppModals.vue → AppModals.vue} +3 -4
- package/src/components/AppOverlays.vue +9 -0
- package/src/components/AppToasts.vue +16 -0
- package/src/components/contracts/AlertModal.ts +8 -0
- package/src/components/contracts/Button.ts +16 -0
- package/src/components/contracts/ConfirmModal.ts +46 -0
- package/src/components/contracts/DropdownMenu.ts +20 -0
- package/src/components/contracts/ErrorReportModal.ts +32 -0
- package/src/components/contracts/Input.ts +26 -0
- package/src/components/contracts/LoadingModal.ts +26 -0
- package/src/components/contracts/Modal.ts +21 -0
- package/src/components/contracts/PromptModal.ts +34 -0
- package/src/components/contracts/Select.ts +45 -0
- package/src/components/contracts/Toast.ts +15 -0
- package/src/components/contracts/index.ts +11 -0
- package/src/components/headless/HeadlessButton.vue +51 -0
- package/src/components/headless/HeadlessInput.vue +59 -0
- package/src/components/headless/HeadlessInputDescription.vue +27 -0
- package/src/components/headless/{forms/AGHeadlessInputError.vue → HeadlessInputError.vue} +4 -8
- package/src/components/headless/HeadlessInputInput.vue +75 -0
- package/src/components/headless/{forms/AGHeadlessInputLabel.vue → HeadlessInputLabel.vue} +3 -7
- package/src/components/headless/HeadlessInputTextArea.vue +40 -0
- package/src/components/headless/HeadlessModal.vue +57 -0
- package/src/components/headless/HeadlessModalContent.vue +30 -0
- package/src/components/headless/HeadlessModalDescription.vue +12 -0
- package/src/components/headless/HeadlessModalOverlay.vue +12 -0
- package/src/components/headless/HeadlessModalTitle.vue +12 -0
- package/src/components/headless/HeadlessSelect.vue +120 -0
- package/src/components/headless/{forms/AGHeadlessSelectError.vue → HeadlessSelectError.vue} +5 -6
- package/src/components/headless/HeadlessSelectLabel.vue +25 -0
- package/src/components/headless/HeadlessSelectOption.vue +34 -0
- package/src/components/headless/HeadlessSelectOptions.vue +42 -0
- package/src/components/headless/HeadlessSelectTrigger.vue +22 -0
- package/src/components/headless/HeadlessSelectValue.vue +18 -0
- package/src/components/headless/HeadlessToast.vue +18 -0
- package/src/components/headless/HeadlessToastAction.vue +13 -0
- package/src/components/headless/index.ts +19 -3
- package/src/components/index.ts +6 -9
- package/src/components/ui/AdvancedOptions.vue +18 -0
- package/src/components/ui/AlertModal.vue +14 -0
- package/src/components/ui/Button.vue +98 -0
- package/src/components/ui/Checkbox.vue +56 -0
- package/src/components/ui/ConfirmModal.vue +45 -0
- package/src/components/ui/DropdownMenu.vue +32 -0
- package/src/components/ui/DropdownMenuOption.vue +14 -0
- package/src/components/ui/DropdownMenuOptions.vue +27 -0
- package/src/components/ui/EditableContent.vue +82 -0
- package/src/components/ui/ErrorMessage.vue +15 -0
- package/src/components/ui/ErrorReportModal.vue +67 -0
- package/src/components/{modals/AGErrorReportModalButtons.vue → ui/ErrorReportModalButtons.vue} +34 -27
- package/src/components/ui/ErrorReportModalTitle.vue +24 -0
- package/src/components/ui/Form.vue +24 -0
- package/src/components/ui/Input.vue +56 -0
- package/src/components/ui/Link.vue +12 -0
- package/src/components/ui/LoadingModal.vue +34 -0
- package/src/components/ui/Markdown.vue +85 -0
- package/src/components/ui/Modal.vue +122 -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 +44 -0
- package/src/components/ui/index.ts +30 -0
- package/src/directives/index.ts +11 -5
- package/src/directives/measure.ts +34 -6
- package/src/errors/Errors.state.ts +1 -1
- package/src/errors/Errors.ts +21 -37
- package/src/errors/JobCancelledError.ts +3 -0
- package/src/errors/index.ts +10 -6
- package/src/errors/utils.ts +35 -0
- package/src/forms/FormController.test.ts +110 -0
- package/src/forms/FormController.ts +246 -0
- package/src/forms/index.ts +3 -2
- package/src/forms/utils.ts +51 -20
- package/src/forms/validation.ts +19 -0
- package/src/index.css +73 -0
- package/src/{main.ts → index.ts} +2 -0
- package/src/jobs/Job.ts +144 -2
- package/src/jobs/index.ts +4 -1
- package/src/jobs/listeners.ts +3 -0
- package/src/jobs/status.ts +4 -0
- package/src/lang/DefaultLangProvider.ts +46 -0
- package/src/lang/Lang.state.ts +11 -0
- package/src/lang/Lang.ts +43 -28
- package/src/lang/index.ts +12 -6
- package/src/lang/settings/Language.vue +48 -0
- package/src/lang/settings/index.ts +10 -0
- package/src/plugins/Plugin.ts +1 -1
- package/src/plugins/index.ts +10 -7
- package/src/services/App.state.ts +36 -3
- package/src/services/App.ts +19 -3
- package/src/services/Cache.ts +43 -0
- package/src/services/Events.test.ts +8 -8
- package/src/services/Events.ts +26 -10
- package/src/services/Service.ts +125 -54
- package/src/services/Storage.ts +20 -0
- package/src/services/index.ts +16 -5
- package/src/services/utils.ts +18 -0
- package/src/testing/index.ts +26 -0
- package/src/testing/setup.ts +11 -0
- package/src/ui/UI.state.ts +14 -12
- package/src/ui/UI.ts +235 -119
- package/src/ui/index.ts +28 -28
- package/src/ui/utils.ts +16 -0
- package/src/utils/classes.ts +49 -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 +19 -6
- package/src/utils/types.ts +3 -0
- package/src/utils/vue.ts +28 -127
- package/dist/aerogel-core.cjs.js +0 -2
- package/dist/aerogel-core.cjs.js.map +0 -1
- package/dist/aerogel-core.esm.js +0 -2
- package/dist/aerogel-core.esm.js.map +0 -1
- package/histoire.config.ts +0 -7
- package/noeldemartin.config.js +0 -5
- package/postcss.config.js +0 -6
- package/src/assets/histoire.css +0 -3
- package/src/components/AGAppLayout.vue +0 -16
- package/src/components/AGAppOverlays.vue +0 -41
- package/src/components/AGAppSnackbars.vue +0 -13
- package/src/components/constants.ts +0 -8
- package/src/components/forms/AGButton.vue +0 -44
- package/src/components/forms/AGCheckbox.vue +0 -41
- package/src/components/forms/AGForm.vue +0 -26
- package/src/components/forms/AGInput.vue +0 -38
- package/src/components/forms/AGSelect.story.vue +0 -46
- package/src/components/forms/AGSelect.vue +0 -60
- package/src/components/forms/index.ts +0 -5
- package/src/components/headless/forms/AGHeadlessButton.vue +0 -51
- package/src/components/headless/forms/AGHeadlessInput.ts +0 -28
- package/src/components/headless/forms/AGHeadlessInput.vue +0 -57
- package/src/components/headless/forms/AGHeadlessInputInput.vue +0 -45
- package/src/components/headless/forms/AGHeadlessSelect.ts +0 -42
- package/src/components/headless/forms/AGHeadlessSelect.vue +0 -77
- package/src/components/headless/forms/AGHeadlessSelectButton.vue +0 -24
- package/src/components/headless/forms/AGHeadlessSelectLabel.vue +0 -24
- package/src/components/headless/forms/AGHeadlessSelectOption.ts +0 -4
- package/src/components/headless/forms/AGHeadlessSelectOption.vue +0 -39
- package/src/components/headless/forms/AGHeadlessSelectOptions.ts +0 -3
- package/src/components/headless/forms/index.ts +0 -14
- package/src/components/headless/modals/AGHeadlessModal.ts +0 -34
- package/src/components/headless/modals/AGHeadlessModal.vue +0 -86
- package/src/components/headless/modals/AGHeadlessModalPanel.vue +0 -28
- package/src/components/headless/modals/AGHeadlessModalTitle.vue +0 -13
- package/src/components/headless/modals/index.ts +0 -4
- package/src/components/headless/snackbars/AGHeadlessSnackbar.vue +0 -10
- package/src/components/headless/snackbars/index.ts +0 -40
- package/src/components/lib/AGErrorMessage.vue +0 -16
- package/src/components/lib/AGLink.vue +0 -9
- package/src/components/lib/AGMarkdown.vue +0 -36
- package/src/components/lib/AGMeasured.vue +0 -15
- package/src/components/lib/index.ts +0 -5
- package/src/components/modals/AGAlertModal.ts +0 -15
- package/src/components/modals/AGAlertModal.vue +0 -14
- package/src/components/modals/AGConfirmModal.ts +0 -27
- package/src/components/modals/AGConfirmModal.vue +0 -26
- package/src/components/modals/AGErrorReportModal.ts +0 -46
- package/src/components/modals/AGErrorReportModal.vue +0 -54
- package/src/components/modals/AGErrorReportModalTitle.vue +0 -25
- package/src/components/modals/AGLoadingModal.ts +0 -23
- package/src/components/modals/AGLoadingModal.vue +0 -15
- package/src/components/modals/AGModal.ts +0 -10
- package/src/components/modals/AGModal.vue +0 -39
- package/src/components/modals/AGModalContext.ts +0 -8
- package/src/components/modals/AGModalContext.vue +0 -22
- package/src/components/modals/AGModalTitle.vue +0 -9
- package/src/components/modals/AGPromptModal.ts +0 -30
- package/src/components/modals/AGPromptModal.vue +0 -34
- package/src/components/modals/index.ts +0 -17
- package/src/components/snackbars/AGSnackbar.vue +0 -36
- package/src/components/snackbars/index.ts +0 -3
- package/src/components/utils.ts +0 -10
- package/src/directives/initial-focus.ts +0 -11
- package/src/forms/Form.test.ts +0 -58
- package/src/forms/Form.ts +0 -185
- package/src/forms/composition.ts +0 -6
- package/src/main.histoire.ts +0 -1
- package/src/utils/tailwindcss.test.ts +0 -26
- package/src/utils/tailwindcss.ts +0 -7
- package/tailwind.config.js +0 -4
- package/tsconfig.json +0 -11
- package/vite.config.ts +0 -14
package/src/ui/UI.ts
CHANGED
|
@@ -1,69 +1,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 {
|
|
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
|
+
}>;
|
|
68
|
+
|
|
69
|
+
export interface ConfirmOptionsWithCheckboxes<T extends ConfirmModalCheckboxes = ConfirmModalCheckboxes>
|
|
70
|
+
extends ConfirmOptions {
|
|
71
|
+
checkboxes?: T;
|
|
38
72
|
}
|
|
39
73
|
|
|
40
|
-
export
|
|
74
|
+
export type PromptOptions = AcceptRefs<{
|
|
41
75
|
label?: string;
|
|
42
76
|
defaultValue?: string;
|
|
43
77
|
placeholder?: string;
|
|
44
78
|
acceptText?: string;
|
|
79
|
+
acceptVariant?: ButtonVariant;
|
|
45
80
|
cancelText?: string;
|
|
46
|
-
|
|
81
|
+
cancelVariant?: ButtonVariant;
|
|
82
|
+
trim?: boolean;
|
|
83
|
+
}>;
|
|
47
84
|
|
|
48
|
-
export interface
|
|
85
|
+
export interface ToastOptions {
|
|
49
86
|
component?: Component;
|
|
50
|
-
|
|
51
|
-
actions?:
|
|
87
|
+
variant?: ToastVariant;
|
|
88
|
+
actions?: ToastAction[];
|
|
52
89
|
}
|
|
53
90
|
|
|
54
91
|
export class UIService extends Service {
|
|
55
92
|
|
|
56
93
|
private modalCallbacks: Record<string, Partial<ModalCallbacks>> = {};
|
|
57
|
-
private components: Partial<
|
|
94
|
+
private components: Partial<UIComponents> = {};
|
|
58
95
|
|
|
59
|
-
public
|
|
60
|
-
|
|
96
|
+
public registerComponent<T extends keyof UIComponents>(name: T, component: UIComponents[T]): void {
|
|
97
|
+
this.components[name] = component;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
public resolveComponent<T extends keyof UIComponents>(name: T): UIComponents[T] | null {
|
|
101
|
+
return this.components[name] ?? null;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
public requireComponent<T extends keyof UIComponents>(name: T): UIComponents[T] {
|
|
105
|
+
return this.resolveComponent(name) ?? fail(`UI Component '${name}' is not defined!`);
|
|
61
106
|
}
|
|
62
107
|
|
|
63
108
|
public alert(message: string): void;
|
|
64
109
|
public alert(title: string, message: string): void;
|
|
65
110
|
public alert(messageOrTitle: string, message?: string): void {
|
|
66
|
-
const getProperties = ():
|
|
111
|
+
const getProperties = (): AlertModalProps => {
|
|
67
112
|
if (typeof message !== 'string') {
|
|
68
113
|
return { message: messageOrTitle };
|
|
69
114
|
}
|
|
@@ -74,38 +119,66 @@ export class UIService extends Service {
|
|
|
74
119
|
};
|
|
75
120
|
};
|
|
76
121
|
|
|
77
|
-
this.
|
|
122
|
+
this.modal(this.requireComponent('alert-modal'), getProperties());
|
|
78
123
|
}
|
|
79
124
|
|
|
125
|
+
/* eslint-disable max-len */
|
|
80
126
|
public async confirm(message: string, options?: ConfirmOptions): Promise<boolean>;
|
|
81
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
|
+
|
|
82
132
|
public async confirm(
|
|
83
133
|
messageOrTitle: string,
|
|
84
|
-
messageOrOptions?: string | ConfirmOptions,
|
|
85
|
-
options?: ConfirmOptions,
|
|
86
|
-
): Promise<boolean> {
|
|
87
|
-
const getProperties = ():
|
|
134
|
+
messageOrOptions?: string | ConfirmOptions | ConfirmOptionsWithCheckboxes,
|
|
135
|
+
options?: ConfirmOptions | ConfirmOptionsWithCheckboxes,
|
|
136
|
+
): Promise<boolean | [boolean, Record<string, boolean>]> {
|
|
137
|
+
const getProperties = (): AcceptRefs<ConfirmModalProps> => {
|
|
88
138
|
if (typeof messageOrOptions !== 'string') {
|
|
89
139
|
return {
|
|
90
|
-
message: messageOrTitle,
|
|
91
140
|
...(messageOrOptions ?? {}),
|
|
141
|
+
message: messageOrTitle,
|
|
142
|
+
required: !!messageOrOptions?.required,
|
|
92
143
|
};
|
|
93
144
|
}
|
|
94
145
|
|
|
95
146
|
return {
|
|
147
|
+
...(options ?? {}),
|
|
96
148
|
title: messageOrTitle,
|
|
97
149
|
message: messageOrOptions,
|
|
98
|
-
|
|
150
|
+
required: !!options?.required,
|
|
99
151
|
};
|
|
100
152
|
};
|
|
101
153
|
|
|
102
|
-
const
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
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
|
+
}
|
|
177
|
+
|
|
178
|
+
return [false, checkboxes];
|
|
179
|
+
}
|
|
107
180
|
|
|
108
|
-
return
|
|
181
|
+
return 'checkboxes' in properties ? [confirmed, checkboxes] : confirmed;
|
|
109
182
|
}
|
|
110
183
|
|
|
111
184
|
public async prompt(message: string, options?: PromptOptions): Promise<string | null>;
|
|
@@ -115,47 +188,61 @@ export class UIService extends Service {
|
|
|
115
188
|
messageOrOptions?: string | PromptOptions,
|
|
116
189
|
options?: PromptOptions,
|
|
117
190
|
): Promise<string | null> {
|
|
118
|
-
const
|
|
191
|
+
const trim = options?.trim ?? true;
|
|
192
|
+
const getProperties = (): PromptModalProps => {
|
|
119
193
|
if (typeof messageOrOptions !== 'string') {
|
|
120
194
|
return {
|
|
121
195
|
message: messageOrTitle,
|
|
122
196
|
...(messageOrOptions ?? {}),
|
|
123
|
-
};
|
|
197
|
+
} as PromptModalProps;
|
|
124
198
|
}
|
|
125
199
|
|
|
126
200
|
return {
|
|
127
201
|
title: messageOrTitle,
|
|
128
202
|
message: messageOrOptions,
|
|
129
203
|
...(options ?? {}),
|
|
130
|
-
};
|
|
204
|
+
} as PromptModalProps;
|
|
131
205
|
};
|
|
132
206
|
|
|
133
|
-
const
|
|
134
|
-
|
|
135
|
-
getProperties(),
|
|
136
|
-
);
|
|
137
|
-
const result = await modal.beforeClose;
|
|
207
|
+
const rawResult = await this.modalForm(this.requireComponent('prompt-modal'), getProperties());
|
|
208
|
+
const result = trim && typeof rawResult === 'string' ? rawResult?.trim() : rawResult;
|
|
138
209
|
|
|
139
210
|
return result ?? null;
|
|
140
211
|
}
|
|
141
212
|
|
|
142
|
-
public async loading<T>(operation: Promise<T>): Promise<T>;
|
|
143
|
-
public async loading<T>(message: string, operation: Promise<T>): Promise<T>;
|
|
144
|
-
public async loading<T>(
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
213
|
+
public async loading<T>(operation: Promise<T> | (() => T)): Promise<T>;
|
|
214
|
+
public async loading<T>(message: string, operation: Promise<T> | (() => T)): Promise<T>;
|
|
215
|
+
public async loading<T>(options: LoadingOptions, operation: Promise<T> | (() => T)): Promise<T>;
|
|
216
|
+
public async loading<T>(
|
|
217
|
+
operationOrMessageOrOptions: string | LoadingOptions | Promise<T> | (() => T),
|
|
218
|
+
operation?: Promise<T> | (() => T),
|
|
219
|
+
): Promise<T> {
|
|
220
|
+
const processOperation = (o: Promise<T> | (() => T)) => (typeof o === 'function' ? Promise.resolve(o()) : o);
|
|
221
|
+
const processArgs = (): { operationPromise: Promise<T>; props?: AcceptRefs<LoadingModalProps> } => {
|
|
222
|
+
if (typeof operationOrMessageOrOptions === 'string') {
|
|
223
|
+
return {
|
|
224
|
+
props: { message: operationOrMessageOrOptions },
|
|
225
|
+
operationPromise: processOperation(operation as Promise<T> | (() => T)),
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
if (typeof operationOrMessageOrOptions === 'function' || operationOrMessageOrOptions instanceof Promise) {
|
|
230
|
+
return { operationPromise: processOperation(operationOrMessageOrOptions) };
|
|
148
231
|
}
|
|
149
232
|
|
|
150
|
-
return {
|
|
233
|
+
return {
|
|
234
|
+
props: operationOrMessageOrOptions,
|
|
235
|
+
operationPromise: processOperation(operation as Promise<T> | (() => T)),
|
|
236
|
+
};
|
|
151
237
|
};
|
|
152
238
|
|
|
153
|
-
const
|
|
239
|
+
const { operationPromise, props } = processArgs();
|
|
240
|
+
const modal = await this.modal(this.requireComponent('loading-modal'), props);
|
|
154
241
|
|
|
155
242
|
try {
|
|
156
|
-
|
|
243
|
+
const result = await operationPromise;
|
|
157
244
|
|
|
158
|
-
|
|
245
|
+
await after({ ms: 500 });
|
|
159
246
|
|
|
160
247
|
return result;
|
|
161
248
|
} finally {
|
|
@@ -163,43 +250,34 @@ export class UIService extends Service {
|
|
|
163
250
|
}
|
|
164
251
|
}
|
|
165
252
|
|
|
166
|
-
public
|
|
167
|
-
const
|
|
253
|
+
public toast(message: string, options: ToastOptions = {}): void {
|
|
254
|
+
const { component, ...otherOptions } = options;
|
|
255
|
+
const toast: UIToast = {
|
|
168
256
|
id: uuid(),
|
|
169
|
-
properties: { message, ...
|
|
170
|
-
component:
|
|
257
|
+
properties: { message, ...otherOptions },
|
|
258
|
+
component: markRaw(component ?? this.requireComponent('toast')),
|
|
171
259
|
};
|
|
172
260
|
|
|
173
|
-
this.setState('
|
|
174
|
-
|
|
175
|
-
setTimeout(() => this.hideSnackbar(snackbar.id), 5000);
|
|
261
|
+
this.setState('toasts', this.toasts.concat(toast));
|
|
176
262
|
}
|
|
177
263
|
|
|
178
|
-
public
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
}
|
|
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>>>;
|
|
184
269
|
|
|
185
|
-
public
|
|
186
|
-
this.components[name] = component;
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
public async openModal<TModalComponent extends ModalComponent>(
|
|
190
|
-
component: TModalComponent,
|
|
191
|
-
properties?: ModalProperties<TModalComponent>,
|
|
192
|
-
): Promise<Modal<ModalResult<TModalComponent>>> {
|
|
270
|
+
public async modal<T extends Component>(component: T, props?: ComponentProps<T>): Promise<UIModal<ModalResult<T>>> {
|
|
193
271
|
const id = uuid();
|
|
194
|
-
const callbacks: Partial<ModalCallbacks<ModalResult<
|
|
195
|
-
const modal:
|
|
272
|
+
const callbacks: Partial<ModalCallbacks<ModalResult<T>>> = {};
|
|
273
|
+
const modal: UIModal<ModalResult<T>> = {
|
|
196
274
|
id,
|
|
197
|
-
|
|
275
|
+
closing: false,
|
|
276
|
+
properties: props ?? {},
|
|
198
277
|
component: markRaw(component),
|
|
199
278
|
beforeClose: new Promise((resolve) => (callbacks.willClose = resolve)),
|
|
200
|
-
afterClose: new Promise((resolve) => (callbacks.
|
|
279
|
+
afterClose: new Promise((resolve) => (callbacks.hasClosed = resolve)),
|
|
201
280
|
};
|
|
202
|
-
const activeModal = this.modals.at(-1);
|
|
203
281
|
const modals = this.modals.concat(modal);
|
|
204
282
|
|
|
205
283
|
this.modalCallbacks[modal.id] = callbacks;
|
|
@@ -207,58 +285,88 @@ export class UIService extends Service {
|
|
|
207
285
|
this.setState({ modals });
|
|
208
286
|
|
|
209
287
|
await nextTick();
|
|
210
|
-
await (activeModal && Events.emit('hide-modal', { id: activeModal.id }));
|
|
211
|
-
await Promise.all([
|
|
212
|
-
activeModal || Events.emit('show-overlays-backdrop'),
|
|
213
|
-
Events.emit('show-modal', { id: modal.id }),
|
|
214
|
-
]);
|
|
215
288
|
|
|
216
289
|
return modal;
|
|
217
290
|
}
|
|
218
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
|
+
|
|
219
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
|
+
|
|
220
315
|
await Events.emit('close-modal', { id, result });
|
|
221
316
|
}
|
|
222
317
|
|
|
223
|
-
|
|
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> {
|
|
224
325
|
this.watchModalEvents();
|
|
225
326
|
this.watchMountedEvent();
|
|
327
|
+
this.watchViewportBreakpoints();
|
|
226
328
|
}
|
|
227
329
|
|
|
228
|
-
private
|
|
229
|
-
|
|
230
|
-
|
|
330
|
+
private async removeModal(id: string, result?: unknown): Promise<void> {
|
|
331
|
+
this.setState(
|
|
332
|
+
'modals',
|
|
333
|
+
this.modals.filter((m) => m.id !== id),
|
|
334
|
+
);
|
|
231
335
|
|
|
232
|
-
|
|
233
|
-
Events.emit('hide-overlays-backdrop');
|
|
234
|
-
}
|
|
235
|
-
});
|
|
336
|
+
this.modalCallbacks[id]?.hasClosed?.(result);
|
|
236
337
|
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
'modals',
|
|
240
|
-
this.modals.filter((m) => m.id !== modal.id),
|
|
241
|
-
);
|
|
338
|
+
delete this.modalCallbacks[id];
|
|
339
|
+
}
|
|
242
340
|
|
|
243
|
-
|
|
341
|
+
private watchModalEvents(): void {
|
|
342
|
+
Events.on('modal-will-close', ({ modal: { id }, result }) => {
|
|
343
|
+
const modal = this.modals.find((_modal) => id === _modal.id);
|
|
244
344
|
|
|
245
|
-
|
|
345
|
+
if (modal) {
|
|
346
|
+
modal.closing = true;
|
|
347
|
+
}
|
|
246
348
|
|
|
247
|
-
|
|
349
|
+
this.modalCallbacks[id]?.willClose?.(result);
|
|
350
|
+
});
|
|
248
351
|
|
|
249
|
-
|
|
352
|
+
Events.on('modal-has-closed', async ({ modal: { id }, result }) => {
|
|
353
|
+
await this.removeModal(id, result);
|
|
250
354
|
});
|
|
251
355
|
}
|
|
252
356
|
|
|
253
357
|
private watchMountedEvent(): void {
|
|
254
358
|
Events.once('application-mounted', async () => {
|
|
255
|
-
|
|
359
|
+
if (!globalThis.document || !globalThis.getComputedStyle) {
|
|
360
|
+
return;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
const splash = globalThis.document.getElementById('splash');
|
|
256
364
|
|
|
257
365
|
if (!splash) {
|
|
258
366
|
return;
|
|
259
367
|
}
|
|
260
368
|
|
|
261
|
-
if (
|
|
369
|
+
if (globalThis.getComputedStyle(splash).opacity !== '0') {
|
|
262
370
|
splash.style.opacity = '0';
|
|
263
371
|
|
|
264
372
|
await after({ ms: 600 });
|
|
@@ -268,16 +376,24 @@ export class UIService extends Service {
|
|
|
268
376
|
});
|
|
269
377
|
}
|
|
270
378
|
|
|
379
|
+
private watchViewportBreakpoints(): void {
|
|
380
|
+
if (!globalThis.matchMedia) {
|
|
381
|
+
return;
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
const media = globalThis.matchMedia(`(min-width: ${MOBILE_BREAKPOINT}px)`);
|
|
385
|
+
|
|
386
|
+
media.addEventListener('change', () => this.setState({ layout: getCurrentLayout() }));
|
|
387
|
+
}
|
|
388
|
+
|
|
271
389
|
}
|
|
272
390
|
|
|
273
391
|
export default facade(UIService);
|
|
274
392
|
|
|
275
|
-
declare module '
|
|
393
|
+
declare module '@aerogel/core/services/Events' {
|
|
276
394
|
export interface EventsPayload {
|
|
277
|
-
'modal-will-close': { modal: Modal; result?: unknown };
|
|
278
|
-
'modal-closed': { modal: Modal; result?: unknown };
|
|
279
395
|
'close-modal': { id: string; result?: unknown };
|
|
280
|
-
'
|
|
281
|
-
'
|
|
396
|
+
'modal-will-close': { modal: UIModal; result?: unknown };
|
|
397
|
+
'modal-has-closed': { modal: UIModal; result?: unknown };
|
|
282
398
|
}
|
|
283
399
|
}
|
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
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import clsx from 'clsx';
|
|
2
|
+
import { computed, 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 { ComputedRef, PropType, Ref } 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 RefsObject<T> = { [K in keyof T]: Ref<T[K]> | T[K] };
|
|
12
|
+
export type Variants<T extends Record<string, string | boolean>> = Required<{
|
|
13
|
+
[K in keyof T]: Exclude<T[K], undefined> extends string
|
|
14
|
+
? { [key in Exclude<T[K], undefined>]: string | null }
|
|
15
|
+
: { true: string | null; false: string | null };
|
|
16
|
+
}>;
|
|
17
|
+
|
|
18
|
+
export type ComponentPropDefinitions<T> = {
|
|
19
|
+
[K in keyof T]: {
|
|
20
|
+
type?: PropType<T[K]>;
|
|
21
|
+
default: T[K] | (() => T[K]) | null;
|
|
22
|
+
};
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
export type PickComponentProps<TValues, TDefinitions> = {
|
|
26
|
+
[K in keyof TValues]: K extends keyof TDefinitions ? TValues[K] : never;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
export function computedVariantClasses<T>(
|
|
30
|
+
value: RefsObject<{ baseClasses?: string } & CVAProps<T>>,
|
|
31
|
+
config: { baseClasses?: string } & CVAConfig<T>,
|
|
32
|
+
): ComputedRef<string> {
|
|
33
|
+
return computed(() => {
|
|
34
|
+
const { baseClasses: valueBaseClasses, ...valueRefs } = value;
|
|
35
|
+
const { baseClasses: configBaseClasses, ...configs } = config;
|
|
36
|
+
const variants = cva(configBaseClasses, configs as CVAConfig<T>);
|
|
37
|
+
const values = Object.entries(valueRefs).reduce((extractedValues, [name, valueRef]) => {
|
|
38
|
+
extractedValues[name as keyof CVAProps<T>] = unref(valueRef);
|
|
39
|
+
|
|
40
|
+
return extractedValues;
|
|
41
|
+
}, {} as CVAProps<T>);
|
|
42
|
+
|
|
43
|
+
return classes(variants(values), unref(valueBaseClasses));
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function classes(...inputs: ClassValue[]): string {
|
|
48
|
+
return twMerge(clsx(inputs));
|
|
49
|
+
}
|