@aerogel/core 0.0.0-next.c8f032a868370824898e171969aec1bb6827688e → 0.0.0-next.d547095ca85c86c1e41f5c7fa170d0ba856c3f4d
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 +1511 -236
- package/dist/aerogel-core.esm.js +1 -1
- package/dist/aerogel-core.esm.js.map +1 -1
- package/histoire.config.ts +7 -0
- package/noeldemartin.config.js +4 -1
- package/package.json +14 -4
- package/postcss.config.js +6 -0
- package/src/assets/histoire.css +3 -0
- package/src/bootstrap/bootstrap.test.ts +4 -3
- package/src/bootstrap/index.ts +27 -4
- package/src/bootstrap/options.ts +3 -0
- package/src/components/AGAppLayout.vue +7 -2
- package/src/components/AGAppModals.vue +15 -0
- package/src/components/AGAppOverlays.vue +10 -8
- package/src/components/AGAppSnackbars.vue +13 -0
- package/src/components/constants.ts +8 -0
- package/src/components/forms/AGButton.vue +33 -10
- package/src/components/forms/AGCheckbox.vue +41 -0
- package/src/components/forms/AGInput.vue +15 -9
- package/src/components/forms/AGSelect.story.vue +46 -0
- package/src/components/forms/AGSelect.vue +60 -0
- package/src/components/forms/index.ts +5 -5
- package/src/components/headless/forms/AGHeadlessButton.vue +12 -12
- package/src/components/headless/forms/AGHeadlessInput.ts +23 -3
- package/src/components/headless/forms/AGHeadlessInput.vue +11 -8
- package/src/components/headless/forms/AGHeadlessInputError.vue +1 -1
- package/src/components/headless/forms/AGHeadlessInputInput.vue +17 -3
- package/src/components/headless/forms/AGHeadlessInputLabel.vue +8 -2
- package/src/components/headless/forms/AGHeadlessSelect.ts +42 -0
- package/src/components/headless/forms/AGHeadlessSelect.vue +77 -0
- package/src/components/headless/forms/AGHeadlessSelectButton.vue +24 -0
- package/src/components/headless/forms/AGHeadlessSelectError.vue +26 -0
- package/src/components/headless/forms/AGHeadlessSelectLabel.vue +24 -0
- package/src/components/headless/forms/AGHeadlessSelectOption.ts +4 -0
- package/src/components/headless/forms/AGHeadlessSelectOption.vue +39 -0
- package/src/components/headless/forms/AGHeadlessSelectOptions.ts +3 -0
- package/src/components/headless/forms/index.ts +9 -1
- package/src/components/headless/index.ts +1 -0
- package/src/components/headless/modals/AGHeadlessModal.ts +27 -0
- package/src/components/headless/modals/AGHeadlessModal.vue +3 -5
- package/src/components/headless/modals/AGHeadlessModalPanel.vue +5 -1
- package/src/components/headless/modals/index.ts +4 -6
- package/src/components/headless/snackbars/AGHeadlessSnackbar.vue +10 -0
- package/src/components/headless/snackbars/index.ts +40 -0
- package/src/components/index.ts +3 -1
- package/src/components/lib/AGErrorMessage.vue +16 -0
- package/src/components/lib/AGLink.vue +9 -0
- package/src/components/lib/AGMarkdown.vue +36 -0
- 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 -16
- package/src/components/modals/AGConfirmModal.ts +27 -0
- package/src/components/modals/AGConfirmModal.vue +8 -12
- package/src/components/modals/AGErrorReportModal.ts +46 -0
- package/src/components/modals/AGErrorReportModal.vue +54 -0
- package/src/components/modals/AGErrorReportModalButtons.vue +111 -0
- package/src/components/modals/AGErrorReportModalTitle.vue +25 -0
- package/src/components/modals/AGLoadingModal.ts +23 -0
- package/src/components/modals/AGLoadingModal.vue +15 -0
- package/src/components/modals/AGModal.ts +1 -1
- package/src/components/modals/AGModal.vue +26 -5
- package/src/components/modals/AGModalTitle.vue +9 -0
- package/src/components/modals/AGPromptModal.ts +30 -0
- package/src/components/modals/AGPromptModal.vue +34 -0
- package/src/components/modals/index.ts +16 -6
- package/src/components/snackbars/AGSnackbar.vue +36 -0
- package/src/components/snackbars/index.ts +3 -0
- package/src/components/utils.ts +10 -0
- package/src/directives/index.ts +20 -3
- package/src/directives/measure.ts +21 -0
- package/src/errors/Errors.state.ts +31 -0
- package/src/errors/Errors.ts +185 -0
- package/src/errors/index.ts +46 -0
- package/src/errors/utils.ts +19 -0
- package/src/forms/Form.test.ts +21 -0
- package/src/forms/Form.ts +34 -15
- package/src/forms/utils.ts +17 -0
- package/src/jobs/Job.ts +5 -0
- package/src/jobs/index.ts +7 -0
- package/src/lang/Lang.ts +11 -15
- package/src/lang/index.ts +3 -5
- package/src/lang/utils.ts +4 -0
- package/src/main.histoire.ts +1 -0
- package/src/main.ts +4 -2
- package/src/plugins/Plugin.ts +1 -0
- package/src/plugins/index.ts +19 -0
- package/src/services/App.state.ts +9 -2
- package/src/services/App.ts +43 -3
- package/src/services/Events.test.ts +39 -0
- package/src/services/Events.ts +100 -30
- package/src/services/Service.ts +174 -53
- package/src/services/index.ts +22 -4
- package/src/services/store.ts +30 -0
- package/src/testing/index.ts +25 -0
- package/src/ui/UI.state.ts +11 -1
- package/src/ui/UI.ts +169 -20
- package/src/ui/index.ts +15 -4
- package/src/utils/composition/events.ts +1 -0
- package/src/utils/composition/forms.ts +11 -0
- package/src/utils/index.ts +2 -0
- package/src/utils/markdown.ts +11 -2
- package/src/utils/tailwindcss.test.ts +26 -0
- package/src/utils/tailwindcss.ts +7 -0
- package/src/utils/vue.ts +15 -4
- package/tailwind.config.js +4 -0
- package/tsconfig.json +1 -0
- package/vite.config.ts +2 -1
- package/.eslintrc.js +0 -3
- package/src/components/basic/AGMarkdown.vue +0 -35
- package/src/components/basic/index.ts +0 -3
- package/src/globals.ts +0 -6
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { JSError } from '@noeldemartin/utils';
|
|
2
|
+
|
|
3
|
+
import { defineServiceState } from '@/services';
|
|
4
|
+
|
|
5
|
+
export type ErrorSource = string | Error | JSError | unknown;
|
|
6
|
+
|
|
7
|
+
export interface ErrorReport {
|
|
8
|
+
title: string;
|
|
9
|
+
description?: string;
|
|
10
|
+
details?: string;
|
|
11
|
+
error?: Error | JSError | unknown;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface ErrorReportLog {
|
|
15
|
+
report: ErrorReport;
|
|
16
|
+
seen: boolean;
|
|
17
|
+
date: Date;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export default defineServiceState({
|
|
21
|
+
name: 'errors',
|
|
22
|
+
initialState: {
|
|
23
|
+
logs: [] as ErrorReportLog[],
|
|
24
|
+
startupErrors: [] as ErrorReport[],
|
|
25
|
+
},
|
|
26
|
+
computed: {
|
|
27
|
+
hasErrors: ({ logs }) => logs.length > 0,
|
|
28
|
+
hasNewErrors: ({ logs }) => logs.some((error) => !error.seen),
|
|
29
|
+
hasStartupErrors: ({ startupErrors }) => startupErrors.length > 0,
|
|
30
|
+
},
|
|
31
|
+
});
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
import { JSError, facade, isObject, objectWithoutEmpty, toString } from '@noeldemartin/utils';
|
|
2
|
+
|
|
3
|
+
import App from '@/services/App';
|
|
4
|
+
import ServiceBootError from '@/errors/ServiceBootError';
|
|
5
|
+
import UI, { UIComponents } from '@/ui/UI';
|
|
6
|
+
import { translateWithDefault } from '@/lang/utils';
|
|
7
|
+
|
|
8
|
+
import Service from './Errors.state';
|
|
9
|
+
import { Colors } from '@/components/constants';
|
|
10
|
+
import { Events } from '@/services';
|
|
11
|
+
import type { AGErrorReportModalProps } from '@/components/modals/AGErrorReportModal';
|
|
12
|
+
import type { ErrorReport, ErrorReportLog, ErrorSource } from './Errors.state';
|
|
13
|
+
import type { ModalComponent } from '@/ui/UI.state';
|
|
14
|
+
|
|
15
|
+
export class ErrorsService extends Service {
|
|
16
|
+
|
|
17
|
+
public forceReporting: boolean = false;
|
|
18
|
+
private enabled: boolean = true;
|
|
19
|
+
|
|
20
|
+
public enable(): void {
|
|
21
|
+
this.enabled = true;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
public disable(): void {
|
|
25
|
+
this.enabled = false;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
public async inspect(error: ErrorSource | ErrorReport[]): Promise<void> {
|
|
29
|
+
const reports = Array.isArray(error) ? (error as ErrorReport[]) : [await this.createErrorReport(error)];
|
|
30
|
+
|
|
31
|
+
if (reports.length === 0) {
|
|
32
|
+
UI.alert(translateWithDefault('errors.inspectEmpty', 'Nothing to inspect!'));
|
|
33
|
+
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
UI.openModal<ModalComponent<AGErrorReportModalProps>>(UI.requireComponent(UIComponents.ErrorReportModal), {
|
|
38
|
+
reports,
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
public async report(error: ErrorSource, message?: string): Promise<void> {
|
|
43
|
+
await Events.emit('error', { error, message });
|
|
44
|
+
|
|
45
|
+
if (App.testing) {
|
|
46
|
+
throw error;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (App.development) {
|
|
50
|
+
this.logError(error);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (!this.enabled) {
|
|
54
|
+
throw error;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (!App.isMounted()) {
|
|
58
|
+
const startupError = await this.createStartupErrorReport(error);
|
|
59
|
+
|
|
60
|
+
if (startupError) {
|
|
61
|
+
this.setState({ startupErrors: this.startupErrors.concat(startupError) });
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const report = await this.createErrorReport(error);
|
|
68
|
+
const log: ErrorReportLog = {
|
|
69
|
+
report,
|
|
70
|
+
seen: false,
|
|
71
|
+
date: new Date(),
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
UI.showSnackbar(
|
|
75
|
+
message ??
|
|
76
|
+
translateWithDefault('errors.notice', 'Something went wrong, but it\'s not your fault. Try again!'),
|
|
77
|
+
{
|
|
78
|
+
color: Colors.Danger,
|
|
79
|
+
actions: [
|
|
80
|
+
{
|
|
81
|
+
text: translateWithDefault('errors.viewDetails', 'View details'),
|
|
82
|
+
dismiss: true,
|
|
83
|
+
handler: () =>
|
|
84
|
+
UI.openModal<ModalComponent<AGErrorReportModalProps>>(
|
|
85
|
+
UI.requireComponent(UIComponents.ErrorReportModal),
|
|
86
|
+
{ reports: [report] },
|
|
87
|
+
),
|
|
88
|
+
},
|
|
89
|
+
],
|
|
90
|
+
},
|
|
91
|
+
);
|
|
92
|
+
|
|
93
|
+
this.setState({ logs: [log].concat(this.logs) });
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
public see(report: ErrorReport): void {
|
|
97
|
+
this.setState({
|
|
98
|
+
logs: this.logs.map((log) => {
|
|
99
|
+
if (log.report !== report) {
|
|
100
|
+
return log;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return {
|
|
104
|
+
...log,
|
|
105
|
+
seen: true,
|
|
106
|
+
};
|
|
107
|
+
}),
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
public seeAll(): void {
|
|
112
|
+
this.setState({
|
|
113
|
+
logs: this.logs.map((log) => ({
|
|
114
|
+
...log,
|
|
115
|
+
seen: true,
|
|
116
|
+
})),
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
private logError(error: unknown): void {
|
|
121
|
+
// eslint-disable-next-line no-console
|
|
122
|
+
console.error(error);
|
|
123
|
+
|
|
124
|
+
if (isObject(error) && error.cause) {
|
|
125
|
+
this.logError(error.cause);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
private async createErrorReport(error: ErrorSource): Promise<ErrorReport> {
|
|
130
|
+
if (typeof error === 'string') {
|
|
131
|
+
return { title: error };
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
if (error instanceof Error || error instanceof JSError) {
|
|
135
|
+
return this.createErrorReportFromError(error);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
if (isObject(error)) {
|
|
139
|
+
return objectWithoutEmpty({
|
|
140
|
+
title: toString(
|
|
141
|
+
error['name'] ?? error['title'] ?? translateWithDefault('errors.unknown', 'Unknown Error'),
|
|
142
|
+
),
|
|
143
|
+
description: toString(
|
|
144
|
+
error['message'] ??
|
|
145
|
+
error['description'] ??
|
|
146
|
+
translateWithDefault('errors.unknownDescription', 'Unknown error object'),
|
|
147
|
+
),
|
|
148
|
+
error,
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
return {
|
|
153
|
+
title: translateWithDefault('errors.unknown', 'Unknown Error'),
|
|
154
|
+
error,
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
private async createStartupErrorReport(error: ErrorSource): Promise<ErrorReport | null> {
|
|
159
|
+
if (error instanceof ServiceBootError) {
|
|
160
|
+
// Ignore second-order boot errors in order to have a cleaner startup crash screen.
|
|
161
|
+
return error.cause instanceof ServiceBootError ? null : this.createErrorReport(error.cause);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
return this.createErrorReport(error);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
private createErrorReportFromError(error: Error | JSError, defaults: Partial<ErrorReport> = {}): ErrorReport {
|
|
168
|
+
return {
|
|
169
|
+
title: error.name,
|
|
170
|
+
description: error.message,
|
|
171
|
+
details: error.stack,
|
|
172
|
+
error,
|
|
173
|
+
...defaults,
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
export default facade(ErrorsService);
|
|
180
|
+
|
|
181
|
+
declare module '@/services/Events' {
|
|
182
|
+
export interface EventsPayload {
|
|
183
|
+
error: { error: ErrorSource; message?: string };
|
|
184
|
+
}
|
|
185
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import type { App } from 'vue';
|
|
2
|
+
|
|
3
|
+
import { bootServices } from '@/services';
|
|
4
|
+
import { definePlugin } from '@/plugins';
|
|
5
|
+
|
|
6
|
+
import Errors from './Errors';
|
|
7
|
+
import { ErrorReport, ErrorReportLog, ErrorSource } from './Errors.state';
|
|
8
|
+
|
|
9
|
+
export * from './utils';
|
|
10
|
+
export { Errors, ErrorSource, ErrorReport, ErrorReportLog };
|
|
11
|
+
|
|
12
|
+
const services = { $errors: Errors };
|
|
13
|
+
const frameworkHandler: ErrorHandler = (error) => {
|
|
14
|
+
Errors.report(error);
|
|
15
|
+
|
|
16
|
+
return true;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
function setUpErrorHandler(app: App, baseHandler: ErrorHandler = () => false): void {
|
|
20
|
+
const errorHandler: ErrorHandler = (error) => baseHandler(error) || frameworkHandler(error);
|
|
21
|
+
|
|
22
|
+
app.config.errorHandler = errorHandler;
|
|
23
|
+
globalThis.onerror = (event, _, __, ___, error) => errorHandler(error ?? event);
|
|
24
|
+
globalThis.onunhandledrejection = (event) => errorHandler(event.reason);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export type ErrorHandler = (error: ErrorSource) => boolean;
|
|
28
|
+
export type ErrorsServices = typeof services;
|
|
29
|
+
|
|
30
|
+
export default definePlugin({
|
|
31
|
+
async install(app, options) {
|
|
32
|
+
setUpErrorHandler(app, options.handleError);
|
|
33
|
+
|
|
34
|
+
await bootServices(app, services);
|
|
35
|
+
},
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
declare module '@/bootstrap/options' {
|
|
39
|
+
export interface AerogelOptions {
|
|
40
|
+
handleError?(error: ErrorSource): boolean;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
declare module '@/services' {
|
|
45
|
+
export interface Services extends ErrorsServices {}
|
|
46
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { JSError, isObject, toString } from '@noeldemartin/utils';
|
|
2
|
+
import { translateWithDefault } from '@/lang/utils';
|
|
3
|
+
import type { ErrorSource } from './Errors.state';
|
|
4
|
+
|
|
5
|
+
export function getErrorMessage(error: ErrorSource): string {
|
|
6
|
+
if (typeof error === 'string') {
|
|
7
|
+
return error;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
if (error instanceof Error || error instanceof JSError) {
|
|
11
|
+
return error.message;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
if (isObject(error)) {
|
|
15
|
+
return toString(error['message'] ?? error['description'] ?? 'Unknown error object');
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
return translateWithDefault('errors.unknown', 'Unknown Error');
|
|
19
|
+
}
|
package/src/forms/Form.test.ts
CHANGED
|
@@ -34,4 +34,25 @@ describe('Form', () => {
|
|
|
34
34
|
expect(form.errors.name).toEqual(['required']);
|
|
35
35
|
});
|
|
36
36
|
|
|
37
|
+
it('resets form', () => {
|
|
38
|
+
// Arrange
|
|
39
|
+
const form = useForm({
|
|
40
|
+
name: {
|
|
41
|
+
type: FormFieldTypes.String,
|
|
42
|
+
rules: 'required',
|
|
43
|
+
},
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
form.name = 'Foo bar';
|
|
47
|
+
form.submit();
|
|
48
|
+
|
|
49
|
+
// Act
|
|
50
|
+
form.reset();
|
|
51
|
+
|
|
52
|
+
// Assert
|
|
53
|
+
expect(form.valid).toBe(true);
|
|
54
|
+
expect(form.submitted).toBe(false);
|
|
55
|
+
expect(form.name).toBeNull();
|
|
56
|
+
});
|
|
57
|
+
|
|
37
58
|
});
|
package/src/forms/Form.ts
CHANGED
|
@@ -6,6 +6,8 @@ import type { ComputedRef, DeepReadonly, Ref, UnwrapNestedRefs } from 'vue';
|
|
|
6
6
|
export const FormFieldTypes = {
|
|
7
7
|
String: 'string',
|
|
8
8
|
Number: 'number',
|
|
9
|
+
Boolean: 'boolean',
|
|
10
|
+
Object: 'object',
|
|
9
11
|
} as const;
|
|
10
12
|
|
|
11
13
|
export interface FormFieldDefinition<TType extends FormFieldType = FormFieldType, TRules extends string = string> {
|
|
@@ -16,9 +18,10 @@ export interface FormFieldDefinition<TType extends FormFieldType = FormFieldType
|
|
|
16
18
|
|
|
17
19
|
export type FormFieldDefinitions = Record<string, FormFieldDefinition>;
|
|
18
20
|
export type FormFieldType = ObjectValues<typeof FormFieldTypes>;
|
|
21
|
+
export type FormFieldValue = GetFormFieldValue<FormFieldType>;
|
|
19
22
|
|
|
20
23
|
export type FormData<T> = {
|
|
21
|
-
[k in keyof T]: T[k] extends FormFieldDefinition<infer TType, infer TRules>
|
|
24
|
+
-readonly [k in keyof T]: T[k] extends FormFieldDefinition<infer TType, infer TRules>
|
|
22
25
|
? TRules extends 'required'
|
|
23
26
|
? GetFormFieldValue<TType>
|
|
24
27
|
: GetFormFieldValue<TType> | null
|
|
@@ -33,15 +36,20 @@ export type GetFormFieldValue<TType> = TType extends typeof FormFieldTypes.Strin
|
|
|
33
36
|
? string
|
|
34
37
|
: TType extends typeof FormFieldTypes.Number
|
|
35
38
|
? number
|
|
39
|
+
: TType extends typeof FormFieldTypes.Boolean
|
|
40
|
+
? boolean
|
|
41
|
+
: TType extends typeof FormFieldTypes.Object
|
|
42
|
+
? object
|
|
36
43
|
: never;
|
|
37
44
|
|
|
45
|
+
const validForms: WeakMap<Form, ComputedRef<boolean>> = new WeakMap();
|
|
46
|
+
|
|
38
47
|
export default class Form<Fields extends FormFieldDefinitions = FormFieldDefinitions> extends MagicObject {
|
|
39
48
|
|
|
40
49
|
public errors: DeepReadonly<UnwrapNestedRefs<FormErrors<Fields>>>;
|
|
41
50
|
|
|
42
51
|
private _fields: Fields;
|
|
43
52
|
private _data: FormData<Fields>;
|
|
44
|
-
private _valid: ComputedRef<boolean>;
|
|
45
53
|
private _submitted: Ref<boolean>;
|
|
46
54
|
private _errors: FormErrors<Fields>;
|
|
47
55
|
|
|
@@ -52,13 +60,17 @@ export default class Form<Fields extends FormFieldDefinitions = FormFieldDefinit
|
|
|
52
60
|
this._submitted = ref(false);
|
|
53
61
|
this._data = this.getInitialData(fields);
|
|
54
62
|
this._errors = this.getInitialErrors(fields);
|
|
55
|
-
|
|
63
|
+
|
|
64
|
+
validForms.set(
|
|
65
|
+
this,
|
|
66
|
+
computed(() => !Object.values(this._errors).some((error) => error !== null)),
|
|
67
|
+
);
|
|
56
68
|
|
|
57
69
|
this.errors = readonly(this._errors);
|
|
58
70
|
}
|
|
59
71
|
|
|
60
72
|
public get valid(): boolean {
|
|
61
|
-
return this
|
|
73
|
+
return !!validForms.get(this)?.value;
|
|
62
74
|
}
|
|
63
75
|
|
|
64
76
|
public get submitted(): boolean {
|
|
@@ -78,10 +90,10 @@ export default class Form<Fields extends FormFieldDefinitions = FormFieldDefinit
|
|
|
78
90
|
}
|
|
79
91
|
|
|
80
92
|
public validate(): boolean {
|
|
81
|
-
const errors = Object.entries(this._fields).reduce((
|
|
82
|
-
|
|
93
|
+
const errors = Object.entries(this._fields).reduce((formErrors, [name, definition]) => {
|
|
94
|
+
formErrors[name] = this.getFieldErrors(name, definition);
|
|
83
95
|
|
|
84
|
-
return
|
|
96
|
+
return formErrors;
|
|
85
97
|
}, {} as Record<string, string[] | null>);
|
|
86
98
|
|
|
87
99
|
this.resetErrors(errors);
|
|
@@ -89,10 +101,11 @@ export default class Form<Fields extends FormFieldDefinitions = FormFieldDefinit
|
|
|
89
101
|
return this.valid;
|
|
90
102
|
}
|
|
91
103
|
|
|
92
|
-
public reset(): void {
|
|
104
|
+
public reset(options: { keepData?: boolean; keepErrors?: boolean } = {}): void {
|
|
93
105
|
this._submitted.value = false;
|
|
94
106
|
|
|
95
|
-
this.
|
|
107
|
+
options.keepData || this.resetData();
|
|
108
|
+
options.keepErrors || this.resetErrors();
|
|
96
109
|
}
|
|
97
110
|
|
|
98
111
|
public submit(): boolean {
|
|
@@ -134,10 +147,10 @@ export default class Form<Fields extends FormFieldDefinitions = FormFieldDefinit
|
|
|
134
147
|
return {} as FormData<Fields>;
|
|
135
148
|
}
|
|
136
149
|
|
|
137
|
-
const data = Object.entries(fields).reduce((
|
|
138
|
-
|
|
150
|
+
const data = Object.entries(fields).reduce((formData, [name, definition]) => {
|
|
151
|
+
formData[name as keyof Fields] = (definition.default ?? null) as FormData<Fields>[keyof Fields];
|
|
139
152
|
|
|
140
|
-
return
|
|
153
|
+
return formData;
|
|
141
154
|
}, {} as FormData<Fields>);
|
|
142
155
|
|
|
143
156
|
return reactive(data) as FormData<Fields>;
|
|
@@ -148,15 +161,21 @@ export default class Form<Fields extends FormFieldDefinitions = FormFieldDefinit
|
|
|
148
161
|
return {} as FormErrors<Fields>;
|
|
149
162
|
}
|
|
150
163
|
|
|
151
|
-
const errors = Object.keys(fields).reduce((
|
|
152
|
-
|
|
164
|
+
const errors = Object.keys(fields).reduce((formErrors, name) => {
|
|
165
|
+
formErrors[name as keyof Fields] = null;
|
|
153
166
|
|
|
154
|
-
return
|
|
167
|
+
return formErrors;
|
|
155
168
|
}, {} as FormErrors<Fields>);
|
|
156
169
|
|
|
157
170
|
return reactive(errors) as FormErrors<Fields>;
|
|
158
171
|
}
|
|
159
172
|
|
|
173
|
+
private resetData(): void {
|
|
174
|
+
for (const [name, field] of Object.entries(this._fields)) {
|
|
175
|
+
this._data[name as keyof Fields] = (field.default ?? null) as FormData<Fields>[keyof Fields];
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
160
179
|
private resetErrors(errors?: Record<string, string[] | null>): void {
|
|
161
180
|
Object.keys(this._errors).forEach((key) => delete this._errors[key as keyof Fields]);
|
|
162
181
|
|
package/src/forms/utils.ts
CHANGED
|
@@ -1,6 +1,23 @@
|
|
|
1
1
|
import { FormFieldTypes } from './Form';
|
|
2
2
|
import type { FormFieldDefinition } from './Form';
|
|
3
3
|
|
|
4
|
+
export function booleanInput(defaultValue?: boolean): FormFieldDefinition<typeof FormFieldTypes.Boolean> {
|
|
5
|
+
return {
|
|
6
|
+
default: defaultValue,
|
|
7
|
+
type: FormFieldTypes.Boolean,
|
|
8
|
+
};
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function requiredBooleanInput(
|
|
12
|
+
defaultValue?: boolean,
|
|
13
|
+
): FormFieldDefinition<typeof FormFieldTypes.Boolean, 'required'> {
|
|
14
|
+
return {
|
|
15
|
+
default: defaultValue,
|
|
16
|
+
type: FormFieldTypes.Boolean,
|
|
17
|
+
rules: 'required',
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
|
|
4
21
|
export function requiredNumberInput(
|
|
5
22
|
defaultValue?: number,
|
|
6
23
|
): FormFieldDefinition<typeof FormFieldTypes.Number, 'required'> {
|
package/src/jobs/Job.ts
ADDED
package/src/lang/Lang.ts
CHANGED
|
@@ -5,6 +5,7 @@ import Service from '@/services/Service';
|
|
|
5
5
|
|
|
6
6
|
export interface LangProvider {
|
|
7
7
|
translate(key: string, parameters?: Record<string, unknown>): string;
|
|
8
|
+
translateWithDefault(key: string, defaultMessage: string, parameters?: Record<string, unknown>): string;
|
|
8
9
|
}
|
|
9
10
|
|
|
10
11
|
export class LangService extends Service {
|
|
@@ -17,10 +18,16 @@ export class LangService extends Service {
|
|
|
17
18
|
this.provider = {
|
|
18
19
|
translate: (key) => {
|
|
19
20
|
// eslint-disable-next-line no-console
|
|
20
|
-
App.
|
|
21
|
+
App.development && console.warn('Lang provider is missing');
|
|
21
22
|
|
|
22
23
|
return key;
|
|
23
24
|
},
|
|
25
|
+
translateWithDefault: (_, defaultMessage) => {
|
|
26
|
+
// eslint-disable-next-line no-console
|
|
27
|
+
App.development && console.warn('Lang provider is missing');
|
|
28
|
+
|
|
29
|
+
return defaultMessage;
|
|
30
|
+
},
|
|
24
31
|
};
|
|
25
32
|
}
|
|
26
33
|
|
|
@@ -32,21 +39,10 @@ export class LangService extends Service {
|
|
|
32
39
|
return this.provider.translate(key, parameters) ?? key;
|
|
33
40
|
}
|
|
34
41
|
|
|
35
|
-
public translateWithDefault(key: string, defaultMessage: string): string
|
|
36
|
-
|
|
37
|
-
public translateWithDefault(
|
|
38
|
-
key: string,
|
|
39
|
-
defaultMessageOrParameters?: string | Record<string, unknown>,
|
|
40
|
-
defaultMessage?: string,
|
|
41
|
-
): string {
|
|
42
|
-
defaultMessage ??= defaultMessageOrParameters as string;
|
|
43
|
-
|
|
44
|
-
const parameters = typeof defaultMessageOrParameters === 'string' ? {} : defaultMessageOrParameters;
|
|
45
|
-
const message = this.provider.translate(key, parameters) ?? key;
|
|
46
|
-
|
|
47
|
-
return message === key ? defaultMessage : message;
|
|
42
|
+
public translateWithDefault(key: string, defaultMessage: string, parameters: Record<string, unknown> = {}): string {
|
|
43
|
+
return this.provider.translateWithDefault(key, defaultMessage, parameters);
|
|
48
44
|
}
|
|
49
45
|
|
|
50
46
|
}
|
|
51
47
|
|
|
52
|
-
export default facade(
|
|
48
|
+
export default facade(LangService);
|
package/src/lang/index.ts
CHANGED
|
@@ -2,16 +2,14 @@ import { bootServices } from '@/services';
|
|
|
2
2
|
import { definePlugin } from '@/plugins';
|
|
3
3
|
|
|
4
4
|
import Lang, { LangProvider } from './Lang';
|
|
5
|
+
import { translate, translateWithDefault } from './utils';
|
|
5
6
|
|
|
6
|
-
export { Lang, LangProvider };
|
|
7
|
+
export { Lang, LangProvider, translate, translateWithDefault };
|
|
7
8
|
|
|
8
9
|
const services = { $lang: Lang };
|
|
9
10
|
|
|
10
11
|
export type LangServices = typeof services;
|
|
11
12
|
|
|
12
|
-
export const translate = Lang.translate.bind(Lang);
|
|
13
|
-
export const translateWithDefault = Lang.translateWithDefault.bind(Lang);
|
|
14
|
-
|
|
15
13
|
export default definePlugin({
|
|
16
14
|
async install(app) {
|
|
17
15
|
app.config.globalProperties.$t ??= translate;
|
|
@@ -22,7 +20,7 @@ export default definePlugin({
|
|
|
22
20
|
});
|
|
23
21
|
|
|
24
22
|
declare module '@/services' {
|
|
25
|
-
interface Services extends LangServices {}
|
|
23
|
+
export interface Services extends LangServices {}
|
|
26
24
|
}
|
|
27
25
|
|
|
28
26
|
declare module '@vue/runtime-core' {
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import './assets/histoire.css';
|
package/src/main.ts
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
|
-
import './globals';
|
|
2
|
-
|
|
3
1
|
export * from './bootstrap';
|
|
4
2
|
export * from './components';
|
|
3
|
+
export * from './directives';
|
|
4
|
+
export * from './errors';
|
|
5
5
|
export * from './forms';
|
|
6
|
+
export * from './jobs';
|
|
6
7
|
export * from './lang';
|
|
7
8
|
export * from './plugins';
|
|
8
9
|
export * from './services';
|
|
10
|
+
export * from './testing';
|
|
9
11
|
export * from './ui';
|
|
10
12
|
export * from './utils';
|
package/src/plugins/Plugin.ts
CHANGED
package/src/plugins/index.ts
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
import type { GetClosureArgs } from '@noeldemartin/utils';
|
|
2
|
+
|
|
3
|
+
import App from '@/services/App';
|
|
4
|
+
|
|
1
5
|
import type { Plugin } from './Plugin';
|
|
2
6
|
|
|
3
7
|
export * from './Plugin';
|
|
@@ -5,3 +9,18 @@ export * from './Plugin';
|
|
|
5
9
|
export function definePlugin<T extends Plugin>(plugin: T): T {
|
|
6
10
|
return plugin;
|
|
7
11
|
}
|
|
12
|
+
|
|
13
|
+
export async function installPlugins(plugins: Plugin[], ...args: GetClosureArgs<Plugin['install']>): Promise<void> {
|
|
14
|
+
App.setState(
|
|
15
|
+
'plugins',
|
|
16
|
+
plugins.reduce((pluginsMap, plugin) => {
|
|
17
|
+
if (plugin.name) {
|
|
18
|
+
pluginsMap[plugin.name] = plugin;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
return pluginsMap;
|
|
22
|
+
}, {} as Record<string, Plugin>),
|
|
23
|
+
);
|
|
24
|
+
|
|
25
|
+
await Promise.all(plugins.map((plugin) => plugin.install(...args)) ?? []);
|
|
26
|
+
}
|
|
@@ -1,10 +1,17 @@
|
|
|
1
|
+
import Aerogel from 'virtual:aerogel';
|
|
2
|
+
|
|
1
3
|
import { defineServiceState } from '@/services/Service';
|
|
4
|
+
import type { Plugin } from '@/plugins/Plugin';
|
|
2
5
|
|
|
3
6
|
export default defineServiceState({
|
|
7
|
+
name: 'app',
|
|
4
8
|
initialState: {
|
|
5
|
-
|
|
9
|
+
plugins: {} as Record<string, Plugin>,
|
|
10
|
+
environment: Aerogel.environment,
|
|
11
|
+
sourceUrl: Aerogel.sourceUrl,
|
|
6
12
|
},
|
|
7
13
|
computed: {
|
|
8
|
-
|
|
14
|
+
development: (state) => state.environment === 'development',
|
|
15
|
+
testing: (state) => state.environment === 'test' || state.environment === 'testing',
|
|
9
16
|
},
|
|
10
17
|
});
|