@aerogel/core 0.0.0-next.9487bb13082b9d479112445804d906125ded5cbc → 0.0.0-next.97312fd206b83ac5ae520da32b1bb3f12fb55969
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.cjs.js +1 -1
- package/dist/aerogel-core.cjs.js.map +1 -1
- package/dist/aerogel-core.d.ts +603 -123
- package/dist/aerogel-core.esm.js +1 -1
- package/dist/aerogel-core.esm.js.map +1 -1
- package/package.json +12 -6
- package/src/bootstrap/bootstrap.test.ts +4 -3
- package/src/bootstrap/index.ts +19 -3
- package/src/bootstrap/options.ts +3 -0
- package/src/components/AGAppLayout.vue +7 -2
- package/src/components/AGAppOverlays.vue +5 -1
- package/src/components/AGAppSnackbars.vue +1 -1
- package/src/components/forms/AGCheckbox.vue +7 -1
- package/src/components/forms/AGInput.vue +8 -6
- package/src/components/forms/AGSelect.story.vue +21 -3
- package/src/components/forms/AGSelect.vue +10 -3
- package/src/components/headless/forms/AGHeadlessInput.ts +21 -1
- package/src/components/headless/forms/AGHeadlessInput.vue +4 -1
- package/src/components/headless/forms/AGHeadlessInputLabel.vue +8 -2
- package/src/components/headless/forms/AGHeadlessSelect.ts +21 -22
- package/src/components/headless/forms/AGHeadlessSelect.vue +24 -24
- package/src/components/headless/forms/AGHeadlessSelectLabel.vue +4 -1
- package/src/components/headless/forms/AGHeadlessSelectOption.vue +6 -6
- package/src/components/headless/modals/AGHeadlessModal.ts +27 -0
- package/src/components/headless/modals/AGHeadlessModal.vue +3 -5
- package/src/components/headless/modals/index.ts +4 -6
- package/src/components/headless/snackbars/index.ts +23 -8
- package/src/components/index.ts +1 -1
- package/src/components/lib/AGMeasured.vue +15 -0
- package/src/components/lib/AGStartupCrash.vue +31 -0
- package/src/components/lib/index.ts +5 -0
- package/src/components/modals/AGAlertModal.ts +15 -0
- package/src/components/modals/AGAlertModal.vue +4 -15
- package/src/components/modals/AGConfirmModal.ts +27 -0
- package/src/components/modals/AGConfirmModal.vue +8 -12
- package/src/components/modals/AGErrorReportModal.ts +27 -1
- package/src/components/modals/AGErrorReportModal.vue +8 -16
- package/src/components/modals/AGErrorReportModalButtons.vue +4 -2
- package/src/components/modals/AGErrorReportModalTitle.vue +1 -1
- package/src/components/modals/AGLoadingModal.ts +23 -0
- package/src/components/modals/AGLoadingModal.vue +4 -8
- package/src/components/modals/AGModal.ts +2 -2
- package/src/components/modals/AGModal.vue +14 -12
- package/src/components/modals/AGPromptModal.ts +30 -0
- package/src/components/modals/AGPromptModal.vue +34 -0
- package/src/components/modals/index.ts +13 -19
- package/src/components/snackbars/AGSnackbar.vue +3 -9
- package/src/components/utils.ts +10 -0
- package/src/directives/index.ts +3 -1
- package/src/directives/measure.ts +12 -0
- package/src/errors/Errors.ts +11 -6
- package/src/errors/index.ts +9 -13
- package/src/forms/Form.ts +1 -0
- package/src/services/App.state.ts +3 -4
- package/src/services/App.ts +23 -2
- package/src/services/Service.ts +21 -11
- package/src/services/index.ts +1 -1
- package/src/ui/UI.ts +118 -15
- package/src/ui/index.ts +8 -3
- package/src/utils/composition/events.ts +1 -0
- package/src/utils/index.ts +1 -0
- package/src/utils/tailwindcss.test.ts +26 -0
- package/src/utils/tailwindcss.ts +7 -0
- package/src/utils/vue.ts +10 -1
- package/tsconfig.json +1 -1
- package/.eslintrc.js +0 -3
- package/dist/virtual.d.ts +0 -11
- package/src/components/basic/index.ts +0 -5
- package/src/types/virtual.d.ts +0 -11
- /package/src/components/{basic → lib}/AGErrorMessage.vue +0 -0
- /package/src/components/{basic → lib}/AGLink.vue +0 -0
- /package/src/components/{basic → lib}/AGMarkdown.vue +0 -0
package/src/directives/index.ts
CHANGED
|
@@ -3,9 +3,11 @@ import type { Directive } from 'vue';
|
|
|
3
3
|
import { definePlugin } from '@/plugins';
|
|
4
4
|
|
|
5
5
|
import initialFocus from './initial-focus';
|
|
6
|
+
import measure from './measure';
|
|
6
7
|
|
|
7
8
|
const builtInDirectives: Record<string, Directive> = {
|
|
8
9
|
'initial-focus': initialFocus,
|
|
10
|
+
'measure': measure,
|
|
9
11
|
};
|
|
10
12
|
|
|
11
13
|
export default definePlugin({
|
|
@@ -22,7 +24,7 @@ export default definePlugin({
|
|
|
22
24
|
});
|
|
23
25
|
|
|
24
26
|
declare module '@/bootstrap/options' {
|
|
25
|
-
interface AerogelOptions {
|
|
27
|
+
export interface AerogelOptions {
|
|
26
28
|
directives?: Record<string, Directive>;
|
|
27
29
|
}
|
|
28
30
|
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { defineDirective } from '@/utils/vue';
|
|
2
|
+
|
|
3
|
+
export default defineDirective({
|
|
4
|
+
mounted(element: HTMLElement, { value }: { value?: () => unknown }) {
|
|
5
|
+
const sizes = element.getBoundingClientRect();
|
|
6
|
+
|
|
7
|
+
element.style.setProperty('--width', `${sizes.width}px`);
|
|
8
|
+
element.style.setProperty('--height', `${sizes.height}px`);
|
|
9
|
+
|
|
10
|
+
value?.();
|
|
11
|
+
},
|
|
12
|
+
});
|
package/src/errors/Errors.ts
CHANGED
|
@@ -7,7 +7,9 @@ import { translateWithDefault } from '@/lang/utils';
|
|
|
7
7
|
|
|
8
8
|
import Service from './Errors.state';
|
|
9
9
|
import { Colors } from '@/components/constants';
|
|
10
|
+
import type { AGErrorReportModalProps } from '@/components/modals/AGErrorReportModal';
|
|
10
11
|
import type { ErrorReport, ErrorReportLog, ErrorSource } from './Errors.state';
|
|
12
|
+
import type { ModalComponent } from '@/ui/UI.state';
|
|
11
13
|
|
|
12
14
|
export class ErrorsService extends Service {
|
|
13
15
|
|
|
@@ -23,7 +25,7 @@ export class ErrorsService extends Service {
|
|
|
23
25
|
}
|
|
24
26
|
|
|
25
27
|
public async inspect(error: ErrorSource | ErrorReport[]): Promise<void> {
|
|
26
|
-
const reports = Array.isArray(error) ? error : [await this.createErrorReport(error)];
|
|
28
|
+
const reports = Array.isArray(error) ? (error as ErrorReport[]) : [await this.createErrorReport(error)];
|
|
27
29
|
|
|
28
30
|
if (reports.length === 0) {
|
|
29
31
|
UI.alert(translateWithDefault('errors.inspectEmpty', 'Nothing to inspect!'));
|
|
@@ -31,7 +33,9 @@ export class ErrorsService extends Service {
|
|
|
31
33
|
return;
|
|
32
34
|
}
|
|
33
35
|
|
|
34
|
-
UI.openModal(UI.requireComponent(UIComponents.ErrorReportModal), {
|
|
36
|
+
UI.openModal<ModalComponent<AGErrorReportModalProps>>(UI.requireComponent(UIComponents.ErrorReportModal), {
|
|
37
|
+
reports,
|
|
38
|
+
});
|
|
35
39
|
}
|
|
36
40
|
|
|
37
41
|
public async report(error: ErrorSource, message?: string): Promise<void> {
|
|
@@ -43,7 +47,7 @@ export class ErrorsService extends Service {
|
|
|
43
47
|
throw error;
|
|
44
48
|
}
|
|
45
49
|
|
|
46
|
-
if (!App.isMounted) {
|
|
50
|
+
if (!App.isMounted()) {
|
|
47
51
|
const startupError = await this.createStartupErrorReport(error);
|
|
48
52
|
|
|
49
53
|
if (startupError) {
|
|
@@ -70,9 +74,10 @@ export class ErrorsService extends Service {
|
|
|
70
74
|
text: translateWithDefault('errors.viewDetails', 'View details'),
|
|
71
75
|
dismiss: true,
|
|
72
76
|
handler: () =>
|
|
73
|
-
UI.openModal(
|
|
74
|
-
|
|
75
|
-
|
|
77
|
+
UI.openModal<ModalComponent<AGErrorReportModalProps>>(
|
|
78
|
+
UI.requireComponent(UIComponents.ErrorReportModal),
|
|
79
|
+
{ reports: [report] },
|
|
80
|
+
),
|
|
76
81
|
},
|
|
77
82
|
],
|
|
78
83
|
},
|
package/src/errors/index.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import type { App } from 'vue';
|
|
2
2
|
|
|
3
3
|
import { bootServices } from '@/services';
|
|
4
4
|
import { definePlugin } from '@/plugins';
|
|
@@ -25,14 +25,12 @@ const frameworkHandler: ErrorHandler = (error) => {
|
|
|
25
25
|
return true;
|
|
26
26
|
};
|
|
27
27
|
|
|
28
|
-
function setUpErrorHandler(baseHandler: ErrorHandler = () => false):
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
},
|
|
35
|
-
);
|
|
28
|
+
function setUpErrorHandler(app: App, baseHandler: ErrorHandler = () => false): void {
|
|
29
|
+
const errorHandler: ErrorHandler = (error) => baseHandler(error) || frameworkHandler(error);
|
|
30
|
+
|
|
31
|
+
app.config.errorHandler = errorHandler;
|
|
32
|
+
globalThis.onerror = (event, _, __, ___, error) => errorHandler(error ?? event);
|
|
33
|
+
globalThis.onunhandledrejection = (event) => errorHandler(event.reason);
|
|
36
34
|
}
|
|
37
35
|
|
|
38
36
|
export type ErrorHandler = (error: ErrorSource) => boolean;
|
|
@@ -40,16 +38,14 @@ export type ErrorsServices = typeof services;
|
|
|
40
38
|
|
|
41
39
|
export default definePlugin({
|
|
42
40
|
async install(app, options) {
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
app.config.errorHandler = errorHandler;
|
|
41
|
+
setUpErrorHandler(app, options.handleError);
|
|
46
42
|
|
|
47
43
|
await bootServices(app, services);
|
|
48
44
|
},
|
|
49
45
|
});
|
|
50
46
|
|
|
51
47
|
declare module '@/bootstrap/options' {
|
|
52
|
-
interface AerogelOptions {
|
|
48
|
+
export interface AerogelOptions {
|
|
53
49
|
handleError?(error: ErrorSource): boolean;
|
|
54
50
|
}
|
|
55
51
|
}
|
package/src/forms/Form.ts
CHANGED
|
@@ -18,6 +18,7 @@ export interface FormFieldDefinition<TType extends FormFieldType = FormFieldType
|
|
|
18
18
|
|
|
19
19
|
export type FormFieldDefinitions = Record<string, FormFieldDefinition>;
|
|
20
20
|
export type FormFieldType = ObjectValues<typeof FormFieldTypes>;
|
|
21
|
+
export type FormFieldValue = GetFormFieldValue<FormFieldType>;
|
|
21
22
|
|
|
22
23
|
export type FormData<T> = {
|
|
23
24
|
-readonly [k in keyof T]: T[k] extends FormFieldDefinition<infer TType, infer TRules>
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import Aerogel from 'virtual:aerogel';
|
|
2
2
|
|
|
3
3
|
import { defineServiceState } from '@/services/Service';
|
|
4
4
|
import type { Plugin } from '@/plugins/Plugin';
|
|
@@ -7,9 +7,8 @@ export default defineServiceState({
|
|
|
7
7
|
name: 'app',
|
|
8
8
|
initialState: {
|
|
9
9
|
plugins: {} as Record<string, Plugin>,
|
|
10
|
-
environment:
|
|
11
|
-
sourceUrl:
|
|
12
|
-
isMounted: false,
|
|
10
|
+
environment: Aerogel.environment,
|
|
11
|
+
sourceUrl: Aerogel.sourceUrl,
|
|
13
12
|
},
|
|
14
13
|
computed: {
|
|
15
14
|
development: (state) => state.environment === 'development',
|
package/src/services/App.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { facade } from '@noeldemartin/utils';
|
|
1
|
+
import { PromisedValue, facade, forever, updateLocationQueryParameters } from '@noeldemartin/utils';
|
|
2
2
|
|
|
3
3
|
import Events from '@/services/Events';
|
|
4
4
|
import type { Plugin } from '@/plugins';
|
|
@@ -7,12 +7,33 @@ import Service from './App.state';
|
|
|
7
7
|
|
|
8
8
|
export class AppService extends Service {
|
|
9
9
|
|
|
10
|
+
public readonly ready = new PromisedValue<void>();
|
|
11
|
+
public readonly mounted = new PromisedValue<void>();
|
|
12
|
+
|
|
13
|
+
public isReady(): boolean {
|
|
14
|
+
return this.ready.isResolved();
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
public isMounted(): boolean {
|
|
18
|
+
return this.mounted.isResolved();
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
public async reload(queryParameters?: Record<string, string | undefined>): Promise<void> {
|
|
22
|
+
queryParameters && updateLocationQueryParameters(queryParameters);
|
|
23
|
+
|
|
24
|
+
location.reload();
|
|
25
|
+
|
|
26
|
+
// Stall until the reload happens
|
|
27
|
+
await forever();
|
|
28
|
+
}
|
|
29
|
+
|
|
10
30
|
public plugin<T extends Plugin = Plugin>(name: string): T | null {
|
|
11
31
|
return (this.plugins[name] as T) ?? null;
|
|
12
32
|
}
|
|
13
33
|
|
|
14
34
|
protected async boot(): Promise<void> {
|
|
15
|
-
Events.once('application-
|
|
35
|
+
Events.once('application-ready', () => this.ready.resolve());
|
|
36
|
+
Events.once('application-mounted', () => this.mounted.resolve());
|
|
16
37
|
}
|
|
17
38
|
|
|
18
39
|
}
|
package/src/services/Service.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { MagicObject, PromisedValue, Storage, isEmpty, objectDeepClone, objectOnly } from '@noeldemartin/utils';
|
|
2
2
|
import type { Constructor } from '@noeldemartin/utils';
|
|
3
|
+
import type { MaybeRef } from 'vue';
|
|
3
4
|
import type { Store } from 'pinia';
|
|
4
5
|
|
|
5
6
|
import ServiceBootError from '@/errors/ServiceBootError';
|
|
@@ -8,6 +9,9 @@ import { defineServiceStore } from '@/services/store';
|
|
|
8
9
|
export type ServiceState = Record<string, any>; // eslint-disable-line @typescript-eslint/no-explicit-any
|
|
9
10
|
export type DefaultServiceState = any; // eslint-disable-line @typescript-eslint/no-explicit-any
|
|
10
11
|
export type ServiceConstructor<T extends Service = Service> = Constructor<T> & typeof Service;
|
|
12
|
+
export type UnrefServiceState<State extends ServiceState> = {
|
|
13
|
+
[K in keyof State]: State[K] extends MaybeRef<infer T> ? T : State[K];
|
|
14
|
+
};
|
|
11
15
|
|
|
12
16
|
export type ComputedStateDefinition<TState extends ServiceState, TComputedState extends ServiceState> = {
|
|
13
17
|
[K in keyof TComputedState]: (state: TState) => TComputedState[K];
|
|
@@ -24,8 +28,10 @@ export function defineServiceState<
|
|
|
24
28
|
persist?: (keyof State)[];
|
|
25
29
|
computed?: ComputedStateDefinition<State, ComputedState>;
|
|
26
30
|
serialize?: (state: Partial<State>) => Partial<State>;
|
|
27
|
-
}): Constructor<State
|
|
28
|
-
|
|
31
|
+
}): Constructor<UnrefServiceState<State>> &
|
|
32
|
+
Constructor<ComputedState> &
|
|
33
|
+
Constructor<Service<UnrefServiceState<State>, ComputedState, Partial<UnrefServiceState<State>>>> {
|
|
34
|
+
return class extends Service<UnrefServiceState<State>, ComputedState> {
|
|
29
35
|
|
|
30
36
|
public static persist = (options.persist as string[]) ?? [];
|
|
31
37
|
|
|
@@ -37,21 +43,21 @@ export function defineServiceState<
|
|
|
37
43
|
return options.name ?? null;
|
|
38
44
|
}
|
|
39
45
|
|
|
40
|
-
protected getInitialState(): State {
|
|
46
|
+
protected getInitialState(): UnrefServiceState<State> {
|
|
41
47
|
return options.initialState;
|
|
42
48
|
}
|
|
43
49
|
|
|
44
|
-
protected getComputedStateDefinition(): ComputedStateDefinition<State
|
|
45
|
-
return options.computed ??
|
|
50
|
+
protected getComputedStateDefinition(): ComputedStateDefinition<UnrefServiceState<State>, ComputedState> {
|
|
51
|
+
return (options.computed ?? {}) as ComputedStateDefinition<UnrefServiceState<State>, ComputedState>;
|
|
46
52
|
}
|
|
47
53
|
|
|
48
54
|
protected serializePersistedState(state: Partial<State>): Partial<State> {
|
|
49
55
|
return options.serialize?.(state) ?? state;
|
|
50
56
|
}
|
|
51
57
|
|
|
52
|
-
} as unknown as Constructor<State
|
|
58
|
+
} as unknown as Constructor<UnrefServiceState<State>> &
|
|
53
59
|
Constructor<ComputedState> &
|
|
54
|
-
Constructor<Service<State
|
|
60
|
+
Constructor<Service<UnrefServiceState<State>, ComputedState, Partial<UnrefServiceState<State>>>>;
|
|
55
61
|
}
|
|
56
62
|
|
|
57
63
|
export default class Service<
|
|
@@ -162,7 +168,11 @@ export default class Service<
|
|
|
162
168
|
return;
|
|
163
169
|
}
|
|
164
170
|
|
|
165
|
-
const storage = Storage.
|
|
171
|
+
const storage = Storage.get<ServiceStorage>(this._name);
|
|
172
|
+
|
|
173
|
+
if (!storage) {
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
166
176
|
|
|
167
177
|
Storage.set(this._name, {
|
|
168
178
|
...storage,
|
|
@@ -191,14 +201,14 @@ export default class Service<
|
|
|
191
201
|
}
|
|
192
202
|
|
|
193
203
|
protected async frameworkBoot(): Promise<void> {
|
|
194
|
-
this.
|
|
204
|
+
this.initializePersistedState();
|
|
195
205
|
}
|
|
196
206
|
|
|
197
207
|
protected async boot(): Promise<void> {
|
|
198
|
-
//
|
|
208
|
+
// Placeholder for overrides, don't place any functionality here.
|
|
199
209
|
}
|
|
200
210
|
|
|
201
|
-
protected
|
|
211
|
+
protected initializePersistedState(): void {
|
|
202
212
|
// TODO fix this.static()
|
|
203
213
|
const persist = (this.constructor as unknown as { persist: string[] }).persist;
|
|
204
214
|
|
package/src/services/index.ts
CHANGED
package/src/ui/UI.ts
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
import { facade, fail, uuid } from '@noeldemartin/utils';
|
|
1
|
+
import { after, facade, fail, uuid } from '@noeldemartin/utils';
|
|
2
2
|
import { markRaw, nextTick } from 'vue';
|
|
3
3
|
import type { Component } from 'vue';
|
|
4
4
|
import type { ObjectValues } from '@noeldemartin/utils';
|
|
5
5
|
|
|
6
6
|
import Events from '@/services/Events';
|
|
7
7
|
import type { SnackbarAction, SnackbarColor } from '@/components/headless/snackbars';
|
|
8
|
+
import type { AGAlertModalProps, AGConfirmModalProps, AGLoadingModalProps, AGPromptModalProps } from '@/components';
|
|
8
9
|
|
|
9
10
|
import Service from './UI.state';
|
|
10
11
|
import type { Modal, ModalComponent, Snackbar } from './UI.state';
|
|
@@ -24,11 +25,26 @@ export const UIComponents = {
|
|
|
24
25
|
ConfirmModal: 'confirm-modal',
|
|
25
26
|
ErrorReportModal: 'error-report-modal',
|
|
26
27
|
LoadingModal: 'loading-modal',
|
|
28
|
+
PromptModal: 'prompt-modal',
|
|
27
29
|
Snackbar: 'snackbar',
|
|
30
|
+
StartupCrash: 'startup-crash',
|
|
28
31
|
} as const;
|
|
29
32
|
|
|
30
33
|
export type UIComponent = ObjectValues<typeof UIComponents>;
|
|
31
34
|
|
|
35
|
+
export interface ConfirmOptions {
|
|
36
|
+
acceptText?: string;
|
|
37
|
+
cancelText?: string;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export interface PromptOptions {
|
|
41
|
+
label?: string;
|
|
42
|
+
defaultValue?: string;
|
|
43
|
+
placeholder?: string;
|
|
44
|
+
acceptText?: string;
|
|
45
|
+
cancelText?: string;
|
|
46
|
+
}
|
|
47
|
+
|
|
32
48
|
export interface ShowSnackbarOptions {
|
|
33
49
|
component?: Component;
|
|
34
50
|
color?: SnackbarColor;
|
|
@@ -47,36 +63,104 @@ export class UIService extends Service {
|
|
|
47
63
|
public alert(message: string): void;
|
|
48
64
|
public alert(title: string, message: string): void;
|
|
49
65
|
public alert(messageOrTitle: string, message?: string): void {
|
|
50
|
-
const
|
|
66
|
+
const getProperties = (): AGAlertModalProps => {
|
|
67
|
+
if (typeof message !== 'string') {
|
|
68
|
+
return { message: messageOrTitle };
|
|
69
|
+
}
|
|
51
70
|
|
|
52
|
-
|
|
71
|
+
return {
|
|
72
|
+
title: messageOrTitle,
|
|
73
|
+
message,
|
|
74
|
+
};
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
this.openModal(this.requireComponent(UIComponents.AlertModal), getProperties());
|
|
53
78
|
}
|
|
54
79
|
|
|
55
|
-
public async confirm(message: string): Promise<boolean>;
|
|
56
|
-
public async confirm(title: string, message: string): Promise<boolean>;
|
|
57
|
-
public async confirm(
|
|
58
|
-
|
|
59
|
-
|
|
80
|
+
public async confirm(message: string, options?: ConfirmOptions): Promise<boolean>;
|
|
81
|
+
public async confirm(title: string, message: string, options?: ConfirmOptions): Promise<boolean>;
|
|
82
|
+
public async confirm(
|
|
83
|
+
messageOrTitle: string,
|
|
84
|
+
messageOrOptions?: string | ConfirmOptions,
|
|
85
|
+
options?: ConfirmOptions,
|
|
86
|
+
): Promise<boolean> {
|
|
87
|
+
const getProperties = (): AGConfirmModalProps => {
|
|
88
|
+
if (typeof messageOrOptions !== 'string') {
|
|
89
|
+
return {
|
|
90
|
+
message: messageOrTitle,
|
|
91
|
+
...(messageOrOptions ?? {}),
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return {
|
|
96
|
+
title: messageOrTitle,
|
|
97
|
+
message: messageOrOptions,
|
|
98
|
+
...(options ?? {}),
|
|
99
|
+
};
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
const modal = await this.openModal<ModalComponent<AGConfirmModalProps, boolean>>(
|
|
60
103
|
this.requireComponent(UIComponents.ConfirmModal),
|
|
61
|
-
|
|
104
|
+
getProperties(),
|
|
62
105
|
);
|
|
63
106
|
const result = await modal.beforeClose;
|
|
64
107
|
|
|
65
108
|
return result ?? false;
|
|
66
109
|
}
|
|
67
110
|
|
|
111
|
+
public async prompt(message: string, options?: PromptOptions): Promise<string | null>;
|
|
112
|
+
public async prompt(title: string, message: string, options?: PromptOptions): Promise<string | null>;
|
|
113
|
+
public async prompt(
|
|
114
|
+
messageOrTitle: string,
|
|
115
|
+
messageOrOptions?: string | PromptOptions,
|
|
116
|
+
options?: PromptOptions,
|
|
117
|
+
): Promise<string | null> {
|
|
118
|
+
const getProperties = (): AGPromptModalProps => {
|
|
119
|
+
if (typeof messageOrOptions !== 'string') {
|
|
120
|
+
return {
|
|
121
|
+
message: messageOrTitle,
|
|
122
|
+
...(messageOrOptions ?? {}),
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
return {
|
|
127
|
+
title: messageOrTitle,
|
|
128
|
+
message: messageOrOptions,
|
|
129
|
+
...(options ?? {}),
|
|
130
|
+
};
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
const modal = await this.openModal<ModalComponent<AGPromptModalProps, string | null>>(
|
|
134
|
+
this.requireComponent(UIComponents.PromptModal),
|
|
135
|
+
getProperties(),
|
|
136
|
+
);
|
|
137
|
+
const result = await modal.beforeClose;
|
|
138
|
+
|
|
139
|
+
return result ?? null;
|
|
140
|
+
}
|
|
141
|
+
|
|
68
142
|
public async loading<T>(operation: Promise<T>): Promise<T>;
|
|
69
143
|
public async loading<T>(message: string, operation: Promise<T>): Promise<T>;
|
|
70
144
|
public async loading<T>(messageOrOperation: string | Promise<T>, operation?: Promise<T>): Promise<T> {
|
|
71
|
-
|
|
145
|
+
const getProperties = (): AGLoadingModalProps => {
|
|
146
|
+
if (typeof messageOrOperation !== 'string') {
|
|
147
|
+
return {};
|
|
148
|
+
}
|
|
72
149
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
150
|
+
return { message: messageOrOperation };
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
const modal = await this.openModal(this.requireComponent(UIComponents.LoadingModal), getProperties());
|
|
76
154
|
|
|
77
|
-
|
|
155
|
+
try {
|
|
156
|
+
operation = typeof messageOrOperation === 'string' ? (operation as Promise<T>) : messageOrOperation;
|
|
78
157
|
|
|
79
|
-
|
|
158
|
+
const [result] = await Promise.all([operation, after({ seconds: 1 })]);
|
|
159
|
+
|
|
160
|
+
return result;
|
|
161
|
+
} finally {
|
|
162
|
+
await this.closeModal(modal.id);
|
|
163
|
+
}
|
|
80
164
|
}
|
|
81
165
|
|
|
82
166
|
public showSnackbar(message: string, options: ShowSnackbarOptions = {}): void {
|
|
@@ -138,6 +222,7 @@ export class UIService extends Service {
|
|
|
138
222
|
|
|
139
223
|
protected async boot(): Promise<void> {
|
|
140
224
|
this.watchModalEvents();
|
|
225
|
+
this.watchMountedEvent();
|
|
141
226
|
}
|
|
142
227
|
|
|
143
228
|
private watchModalEvents(): void {
|
|
@@ -165,6 +250,24 @@ export class UIService extends Service {
|
|
|
165
250
|
});
|
|
166
251
|
}
|
|
167
252
|
|
|
253
|
+
private watchMountedEvent(): void {
|
|
254
|
+
Events.once('application-mounted', async () => {
|
|
255
|
+
const splash = document.getElementById('splash');
|
|
256
|
+
|
|
257
|
+
if (!splash) {
|
|
258
|
+
return;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
if (window.getComputedStyle(splash).opacity !== '0') {
|
|
262
|
+
splash.style.opacity = '0';
|
|
263
|
+
|
|
264
|
+
await after({ ms: 600 });
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
splash.remove();
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
|
|
168
271
|
}
|
|
169
272
|
|
|
170
273
|
export default facade(new UIService());
|
package/src/ui/index.ts
CHANGED
|
@@ -8,13 +8,16 @@ import AGAlertModal from '../components/modals/AGAlertModal.vue';
|
|
|
8
8
|
import AGConfirmModal from '../components/modals/AGConfirmModal.vue';
|
|
9
9
|
import AGErrorReportModal from '../components/modals/AGErrorReportModal.vue';
|
|
10
10
|
import AGLoadingModal from '../components/modals/AGLoadingModal.vue';
|
|
11
|
+
import AGPromptModal from '../components/modals/AGPromptModal.vue';
|
|
11
12
|
import AGSnackbar from '../components/snackbars/AGSnackbar.vue';
|
|
13
|
+
import AGStartupCrash from '../components/lib/AGStartupCrash.vue';
|
|
12
14
|
import type { UIComponent } from './UI';
|
|
13
15
|
|
|
14
|
-
export { UI, UIComponents, UIComponent };
|
|
15
|
-
|
|
16
16
|
const services = { $ui: UI };
|
|
17
17
|
|
|
18
|
+
export * from './UI';
|
|
19
|
+
export { default as UI } from './UI';
|
|
20
|
+
|
|
18
21
|
export type UIServices = typeof services;
|
|
19
22
|
|
|
20
23
|
export default definePlugin({
|
|
@@ -24,7 +27,9 @@ export default definePlugin({
|
|
|
24
27
|
[UIComponents.ConfirmModal]: AGConfirmModal,
|
|
25
28
|
[UIComponents.ErrorReportModal]: AGErrorReportModal,
|
|
26
29
|
[UIComponents.LoadingModal]: AGLoadingModal,
|
|
30
|
+
[UIComponents.PromptModal]: AGPromptModal,
|
|
27
31
|
[UIComponents.Snackbar]: AGSnackbar,
|
|
32
|
+
[UIComponents.StartupCrash]: AGStartupCrash,
|
|
28
33
|
};
|
|
29
34
|
|
|
30
35
|
Object.entries({
|
|
@@ -37,7 +42,7 @@ export default definePlugin({
|
|
|
37
42
|
});
|
|
38
43
|
|
|
39
44
|
declare module '@/bootstrap/options' {
|
|
40
|
-
interface AerogelOptions {
|
|
45
|
+
export interface AerogelOptions {
|
|
41
46
|
components?: Partial<Record<UIComponent, Component>>;
|
|
42
47
|
}
|
|
43
48
|
}
|
|
@@ -14,6 +14,7 @@ export function useEvent<Event extends EventWithPayload>(
|
|
|
14
14
|
event: Event,
|
|
15
15
|
listener: EventListener<EventsPayload[Event]>
|
|
16
16
|
): void;
|
|
17
|
+
export function useEvent<Payload>(event: string, listener: (payload: Payload) => unknown): void;
|
|
17
18
|
export function useEvent<Event extends string>(event: UnknownEvent<Event>, listener: EventListener): void;
|
|
18
19
|
|
|
19
20
|
export function useEvent(event: string, listener: EventListener): void {
|
package/src/utils/index.ts
CHANGED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
|
|
3
|
+
import { removeInteractiveClasses } from './tailwindcss';
|
|
4
|
+
|
|
5
|
+
describe('TailwindCSS utils', () => {
|
|
6
|
+
|
|
7
|
+
it('Removes interactive classes', () => {
|
|
8
|
+
const cases: [string, string][] = [
|
|
9
|
+
['text-red hover:text-green', 'text-red'],
|
|
10
|
+
['text-red hover:text-green text-lg', 'text-red text-lg'],
|
|
11
|
+
[
|
|
12
|
+
`
|
|
13
|
+
text-red text-lg
|
|
14
|
+
focus:text-yellow
|
|
15
|
+
hover:focus:text-black
|
|
16
|
+
`,
|
|
17
|
+
'text-red text-lg',
|
|
18
|
+
],
|
|
19
|
+
];
|
|
20
|
+
|
|
21
|
+
cases.forEach(([original, expected]) => {
|
|
22
|
+
expect(removeInteractiveClasses(original)).toEqual(expected);
|
|
23
|
+
});
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
});
|
package/src/utils/vue.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { fail } from '@noeldemartin/utils';
|
|
2
|
-
import { inject, reactive, ref } from 'vue';
|
|
2
|
+
import { computed, inject, reactive, ref, watch } from 'vue';
|
|
3
3
|
import type { Directive, InjectionKey, PropType, Ref, UnwrapNestedRefs } from 'vue';
|
|
4
4
|
|
|
5
5
|
type BaseProp<T> = {
|
|
@@ -30,6 +30,15 @@ export function componentRef<T>(): Ref<UnwrapNestedRefs<T> | undefined> {
|
|
|
30
30
|
return ref<UnwrapNestedRefs<T>>();
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
+
export function computedAsync<T>(getter: () => Promise<T>): Ref<T | undefined> {
|
|
34
|
+
const result = ref<T>();
|
|
35
|
+
const asyncValue = computed(getter);
|
|
36
|
+
|
|
37
|
+
watch(asyncValue, async () => (result.value = await asyncValue.value), { immediate: true });
|
|
38
|
+
|
|
39
|
+
return result;
|
|
40
|
+
}
|
|
41
|
+
|
|
33
42
|
export function defineDirective(directive: Directive): Directive {
|
|
34
43
|
return directive;
|
|
35
44
|
}
|
package/tsconfig.json
CHANGED
package/.eslintrc.js
DELETED
package/dist/virtual.d.ts
DELETED
package/src/types/virtual.d.ts
DELETED
|
File without changes
|
|
File without changes
|
|
File without changes
|