@aerogel/core 0.0.0-next.9a1c5ba39a454b316eba36ec7bdf579fed3d95d2 → 0.0.0-next.9e0c0bbdcff5db68a1087ef53cbdc0f53299f6bb
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.d.ts +1615 -1452
- package/dist/aerogel-core.js +2505 -2003
- package/dist/aerogel-core.js.map +1 -1
- package/package.json +10 -3
- package/src/components/{AGAppLayout.vue → AppLayout.vue} +3 -3
- package/src/components/{AGAppModals.vue → AppModals.vue} +2 -3
- package/src/components/{AGAppOverlays.vue → AppOverlays.vue} +4 -9
- package/src/components/AppToasts.vue +16 -0
- package/src/components/contracts/AlertModal.ts +4 -0
- package/src/components/contracts/Button.ts +16 -0
- package/src/components/contracts/ConfirmModal.ts +41 -0
- package/src/components/contracts/DropdownMenu.ts +20 -0
- package/src/components/{modals/AGErrorReportModal.ts → contracts/ErrorReportModal.ts} +3 -23
- package/src/components/contracts/Input.ts +26 -0
- package/src/components/contracts/LoadingModal.ts +22 -0
- package/src/components/contracts/Modal.ts +14 -9
- package/src/components/contracts/PromptModal.ts +30 -0
- package/src/components/contracts/Select.ts +44 -0
- package/src/components/contracts/Toast.ts +13 -0
- package/src/components/contracts/index.ts +10 -1
- package/src/components/headless/HeadlessButton.vue +51 -0
- package/src/components/headless/HeadlessInput.vue +59 -0
- package/src/components/headless/{forms/AGHeadlessInputDescription.vue → HeadlessInputDescription.vue} +6 -7
- package/src/components/headless/{forms/AGHeadlessInputError.vue → HeadlessInputError.vue} +2 -6
- package/src/components/headless/{forms/AGHeadlessInputInput.vue → HeadlessInputInput.vue} +16 -25
- package/src/components/headless/{forms/AGHeadlessInputLabel.vue → HeadlessInputLabel.vue} +2 -6
- package/src/components/headless/{forms/AGHeadlessInputTextArea.vue → HeadlessInputTextArea.vue} +9 -12
- package/src/components/headless/{modals/AGHeadlessModal.vue → HeadlessModal.vue} +47 -43
- 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 +113 -0
- package/src/components/headless/{forms/AGHeadlessSelectError.vue → HeadlessSelectError.vue} +3 -4
- package/src/components/headless/HeadlessSelectLabel.vue +25 -0
- package/src/components/headless/HeadlessSelectOption.vue +34 -0
- package/src/components/headless/HeadlessSelectOptions.vue +37 -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 +5 -10
- package/src/components/ui/AdvancedOptions.vue +18 -0
- package/src/components/ui/AlertModal.vue +13 -0
- package/src/components/ui/Button.vue +98 -0
- package/src/components/ui/Checkbox.vue +56 -0
- package/src/components/ui/ConfirmModal.vue +42 -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 +62 -0
- package/src/components/{modals/AGErrorReportModalButtons.vue → ui/ErrorReportModalButtons.vue} +28 -21
- package/src/components/ui/ErrorReportModalTitle.vue +24 -0
- package/src/components/{forms/AGForm.vue → ui/Form.vue} +4 -5
- package/src/components/ui/Input.vue +56 -0
- package/src/components/ui/Link.vue +12 -0
- package/src/components/ui/LoadingModal.vue +32 -0
- package/src/components/ui/Markdown.vue +69 -0
- package/src/components/ui/Modal.vue +91 -0
- package/src/components/{modals/AGModalContext.vue → ui/ModalContext.vue} +7 -9
- package/src/components/ui/ProgressBar.vue +51 -0
- package/src/components/ui/PromptModal.vue +35 -0
- package/src/components/ui/Select.vue +25 -0
- package/src/components/ui/SelectLabel.vue +17 -0
- package/src/components/ui/SelectOption.vue +29 -0
- package/src/components/ui/SelectOptions.vue +30 -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} +7 -7
- package/src/components/ui/Toast.vue +42 -0
- package/src/components/ui/index.ts +30 -0
- package/src/errors/Errors.ts +9 -10
- package/src/forms/{Form.test.ts → FormController.test.ts} +2 -2
- package/src/forms/{Form.ts → FormController.ts} +5 -5
- package/src/forms/composition.ts +4 -4
- package/src/forms/index.ts +2 -2
- package/src/forms/utils.ts +2 -2
- package/src/index.css +54 -0
- package/src/lang/index.ts +4 -0
- package/src/lang/settings/Language.vue +48 -0
- package/src/lang/settings/index.ts +10 -0
- package/src/services/App.state.ts +11 -1
- package/src/services/App.ts +9 -1
- package/src/services/index.ts +3 -0
- package/src/ui/UI.state.ts +10 -5
- package/src/ui/UI.ts +59 -53
- package/src/ui/index.ts +14 -14
- package/src/utils/classes.ts +49 -0
- package/src/utils/composition/forms.ts +14 -4
- package/src/utils/composition/state.ts +11 -2
- package/src/utils/index.ts +3 -1
- package/src/utils/types.ts +3 -0
- package/src/utils/vue.ts +22 -128
- package/src/components/AGAppSnackbars.vue +0 -13
- package/src/components/composition.ts +0 -23
- package/src/components/constants.ts +0 -8
- package/src/components/contracts/shared.ts +0 -9
- package/src/components/forms/AGButton.vue +0 -44
- package/src/components/forms/AGCheckbox.vue +0 -42
- package/src/components/forms/AGInput.vue +0 -42
- package/src/components/forms/AGSelect.story.vue +0 -46
- package/src/components/forms/AGSelect.vue +0 -54
- package/src/components/forms/index.ts +0 -5
- package/src/components/headless/forms/AGHeadlessButton.ts +0 -3
- package/src/components/headless/forms/AGHeadlessButton.vue +0 -62
- package/src/components/headless/forms/AGHeadlessInput.ts +0 -41
- package/src/components/headless/forms/AGHeadlessInput.vue +0 -70
- package/src/components/headless/forms/AGHeadlessSelect.ts +0 -42
- package/src/components/headless/forms/AGHeadlessSelect.vue +0 -77
- package/src/components/headless/forms/AGHeadlessSelectOption.ts +0 -4
- package/src/components/headless/forms/AGHeadlessSelectOption.vue +0 -31
- package/src/components/headless/forms/AGHeadlessSelectOptions.vue +0 -19
- package/src/components/headless/forms/AGHeadlessSelectTrigger.vue +0 -25
- package/src/components/headless/forms/composition.ts +0 -10
- package/src/components/headless/forms/index.ts +0 -17
- package/src/components/headless/modals/AGHeadlessModal.ts +0 -33
- package/src/components/headless/modals/AGHeadlessModalContent.vue +0 -25
- package/src/components/headless/modals/index.ts +0 -5
- 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 -54
- package/src/components/lib/AGMeasured.vue +0 -16
- package/src/components/lib/AGProgressBar.vue +0 -55
- package/src/components/lib/index.ts +0 -6
- package/src/components/modals/AGAlertModal.ts +0 -18
- package/src/components/modals/AGAlertModal.vue +0 -14
- package/src/components/modals/AGConfirmModal.ts +0 -42
- package/src/components/modals/AGConfirmModal.vue +0 -27
- package/src/components/modals/AGErrorReportModal.vue +0 -54
- package/src/components/modals/AGErrorReportModalTitle.vue +0 -25
- package/src/components/modals/AGLoadingModal.ts +0 -29
- package/src/components/modals/AGLoadingModal.vue +0 -15
- package/src/components/modals/AGModal.vue +0 -40
- package/src/components/modals/AGModalContext.ts +0 -8
- package/src/components/modals/AGModalTitle.vue +0 -9
- package/src/components/modals/AGPromptModal.ts +0 -41
- package/src/components/modals/AGPromptModal.vue +0 -35
- package/src/components/modals/index.ts +0 -16
- package/src/components/snackbars/AGSnackbar.vue +0 -36
- package/src/components/snackbars/index.ts +0 -3
- package/src/components/utils.ts +0 -63
- package/src/utils/tailwindcss.test.ts +0 -26
- package/src/utils/tailwindcss.ts +0 -7
package/src/errors/Errors.ts
CHANGED
|
@@ -4,13 +4,12 @@ import App from '@aerogel/core/services/App';
|
|
|
4
4
|
import ServiceBootError from '@aerogel/core/errors/ServiceBootError';
|
|
5
5
|
import UI, { UIComponents } from '@aerogel/core/ui/UI';
|
|
6
6
|
import { translateWithDefault } from '@aerogel/core/lang/utils';
|
|
7
|
+
import { Events } from '@aerogel/core/services';
|
|
8
|
+
import type { ErrorReportModalProps } from '@aerogel/core/components/contracts/ErrorReportModal';
|
|
9
|
+
import type { ModalComponent } from '@aerogel/core/ui/UI.state';
|
|
7
10
|
|
|
8
11
|
import Service from './Errors.state';
|
|
9
|
-
import { Colors } from '@aerogel/core/components/constants';
|
|
10
|
-
import { Events } from '@aerogel/core/services';
|
|
11
|
-
import type { AGErrorReportModalProps } from '@aerogel/core/components/modals/AGErrorReportModal';
|
|
12
12
|
import type { ErrorReport, ErrorReportLog, ErrorSource } from './Errors.state';
|
|
13
|
-
import type { ModalComponent } from '@aerogel/core/ui/UI.state';
|
|
14
13
|
|
|
15
14
|
export class ErrorsService extends Service {
|
|
16
15
|
|
|
@@ -34,7 +33,7 @@ export class ErrorsService extends Service {
|
|
|
34
33
|
return;
|
|
35
34
|
}
|
|
36
35
|
|
|
37
|
-
UI.openModal<ModalComponent<
|
|
36
|
+
UI.openModal<ModalComponent<ErrorReportModalProps>>(UI.requireComponent(UIComponents.ErrorReportModal), {
|
|
38
37
|
reports,
|
|
39
38
|
});
|
|
40
39
|
}
|
|
@@ -71,17 +70,17 @@ export class ErrorsService extends Service {
|
|
|
71
70
|
date: new Date(),
|
|
72
71
|
};
|
|
73
72
|
|
|
74
|
-
UI.
|
|
73
|
+
UI.toast(
|
|
75
74
|
message ??
|
|
76
75
|
translateWithDefault('errors.notice', 'Something went wrong, but it\'s not your fault. Try again!'),
|
|
77
76
|
{
|
|
78
|
-
|
|
77
|
+
variant: 'danger',
|
|
79
78
|
actions: [
|
|
80
79
|
{
|
|
81
|
-
|
|
80
|
+
label: translateWithDefault('errors.viewDetails', 'View details'),
|
|
82
81
|
dismiss: true,
|
|
83
|
-
|
|
84
|
-
UI.openModal<ModalComponent<
|
|
82
|
+
click: () =>
|
|
83
|
+
UI.openModal<ModalComponent<ErrorReportModalProps>>(
|
|
85
84
|
UI.requireComponent(UIComponents.ErrorReportModal),
|
|
86
85
|
{ reports: [report] },
|
|
87
86
|
),
|
|
@@ -3,9 +3,9 @@ import { describe, expect, expectTypeOf, it } from 'vitest';
|
|
|
3
3
|
import { useForm } from '@aerogel/core/forms/composition';
|
|
4
4
|
import { numberInput, requiredStringInput } from '@aerogel/core/forms/utils';
|
|
5
5
|
|
|
6
|
-
import { FormFieldTypes } from './
|
|
6
|
+
import { FormFieldTypes } from './FormController';
|
|
7
7
|
|
|
8
|
-
describe('
|
|
8
|
+
describe('FormController', () => {
|
|
9
9
|
|
|
10
10
|
it('defines magic fields', () => {
|
|
11
11
|
const form = useForm({
|
|
@@ -47,12 +47,12 @@ export type GetFormFieldValue<TType> = TType extends typeof FormFieldTypes.Strin
|
|
|
47
47
|
? Date
|
|
48
48
|
: never;
|
|
49
49
|
|
|
50
|
-
const validForms: WeakMap<
|
|
50
|
+
const validForms: WeakMap<FormController, ComputedRef<boolean>> = new WeakMap();
|
|
51
51
|
|
|
52
52
|
export type SubmitFormListener = () => unknown;
|
|
53
53
|
export type FocusFormListener = (input: string) => unknown;
|
|
54
54
|
|
|
55
|
-
export default class
|
|
55
|
+
export default class FormController<Fields extends FormFieldDefinitions = FormFieldDefinitions> extends MagicObject {
|
|
56
56
|
|
|
57
57
|
public errors: DeepReadonly<UnwrapNestedRefs<FormErrors<Fields>>>;
|
|
58
58
|
|
|
@@ -207,10 +207,10 @@ export default class Form<Fields extends FormFieldDefinitions = FormFieldDefinit
|
|
|
207
207
|
return {} as FormData<Fields>;
|
|
208
208
|
}
|
|
209
209
|
|
|
210
|
-
const data = Object.entries(fields).reduce((
|
|
211
|
-
|
|
210
|
+
const data = Object.entries(fields).reduce((initialData, [name, definition]) => {
|
|
211
|
+
initialData[name as keyof Fields] = (definition.default ?? null) as FormData<Fields>[keyof Fields];
|
|
212
212
|
|
|
213
|
-
return
|
|
213
|
+
return initialData;
|
|
214
214
|
}, {} as FormData<Fields>);
|
|
215
215
|
|
|
216
216
|
return reactive(data) as FormData<Fields>;
|
package/src/forms/composition.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import
|
|
2
|
-
import type { FormData, FormFieldDefinitions } from '@aerogel/core/forms/
|
|
1
|
+
import FormController from '@aerogel/core/forms/FormController';
|
|
2
|
+
import type { FormData, FormFieldDefinitions } from '@aerogel/core/forms/FormController';
|
|
3
3
|
|
|
4
|
-
export function useForm<const T extends FormFieldDefinitions>(fields: T):
|
|
5
|
-
return new
|
|
4
|
+
export function useForm<const T extends FormFieldDefinitions>(fields: T): FormController<T> & FormData<T> {
|
|
5
|
+
return new FormController(fields) as FormController<T> & FormData<T>;
|
|
6
6
|
}
|
package/src/forms/index.ts
CHANGED
package/src/forms/utils.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { FormFieldTypes } from './
|
|
2
|
-
import type { FormFieldDefinition } from './
|
|
1
|
+
import { FormFieldTypes } from './FormController';
|
|
2
|
+
import type { FormFieldDefinition } from './FormController';
|
|
3
3
|
|
|
4
4
|
export function booleanInput(
|
|
5
5
|
defaultValue?: boolean,
|
package/src/index.css
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
@import 'tailwindcss';
|
|
2
|
+
|
|
3
|
+
@plugin '@tailwindcss/forms';
|
|
4
|
+
@plugin '@tailwindcss/typography';
|
|
5
|
+
|
|
6
|
+
@source './';
|
|
7
|
+
|
|
8
|
+
@theme {
|
|
9
|
+
--color-background: oklch(1 0 0);
|
|
10
|
+
|
|
11
|
+
--color-primary: oklch(0.205 0 0);
|
|
12
|
+
--color-primary-50: color-mix(in_oklab, var(--color-primary-600) 5%, transparent);
|
|
13
|
+
--color-primary-100: color-mix(in_oklab, var(--color-primary-600) 15%, transparent);
|
|
14
|
+
--color-primary-200: color-mix(in_oklab, var(--color-primary-600) 30%, transparent);
|
|
15
|
+
--color-primary-300: color-mix(in_oklab, var(--color-primary-600) 50%, transparent);
|
|
16
|
+
--color-primary-400: color-mix(in_oklab, var(--color-primary-600) 65%, transparent);
|
|
17
|
+
--color-primary-500: color-mix(in_oklab, var(--color-primary-600) 80%, transparent);
|
|
18
|
+
--color-primary-600: var(--color-primary);
|
|
19
|
+
--color-primary-700: color-mix(in_oklab, var(--color-primary-600) 90%, black);
|
|
20
|
+
--color-primary-800: color-mix(in_oklab, var(--color-primary-600) 80%, black);
|
|
21
|
+
--color-primary-900: color-mix(in_oklab, var(--color-primary-600) 70%, black);
|
|
22
|
+
--color-primary-950: color-mix(in_oklab, var(--color-primary-600) 50%, black);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
.clickable {
|
|
26
|
+
position: relative;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
.clickable::after {
|
|
30
|
+
--clickable-size: 44px;
|
|
31
|
+
--clickable-inset-by: min(0px, calc((100% - var(--clickable-size)) / 2));
|
|
32
|
+
|
|
33
|
+
content: '';
|
|
34
|
+
position: absolute;
|
|
35
|
+
top: var(--clickable-inset-by);
|
|
36
|
+
left: var(--clickable-inset-by);
|
|
37
|
+
right: var(--clickable-inset-by);
|
|
38
|
+
bottom: var(--clickable-inset-by);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
input[type='number'].appearance-textfield {
|
|
42
|
+
appearance: textfield;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
input[type='number'].appearance-textfield::-webkit-outer-spin-button,
|
|
46
|
+
input[type='number'].appearance-textfield::-webkit-inner-spin-button {
|
|
47
|
+
appearance: none;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
button[data-markdown-action] {
|
|
51
|
+
color: var(--tw-prose-links);
|
|
52
|
+
text-decoration: underline;
|
|
53
|
+
font-weight: 500;
|
|
54
|
+
}
|
package/src/lang/index.ts
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
|
+
import App from '@aerogel/core/services/App';
|
|
1
2
|
import { bootServices } from '@aerogel/core/services';
|
|
2
3
|
import { definePlugin } from '@aerogel/core/plugins';
|
|
3
4
|
|
|
4
5
|
import Lang from './Lang';
|
|
6
|
+
import settings from './settings';
|
|
5
7
|
import type { LangProvider } from './Lang';
|
|
6
8
|
import { translate, translateWithDefault } from './utils';
|
|
7
9
|
|
|
@@ -17,6 +19,8 @@ export default definePlugin({
|
|
|
17
19
|
app.config.globalProperties.$t ??= translate;
|
|
18
20
|
app.config.globalProperties.$td = translateWithDefault;
|
|
19
21
|
|
|
22
|
+
settings.forEach((setting) => App.addSetting(setting));
|
|
23
|
+
|
|
20
24
|
await bootServices(app, services);
|
|
21
25
|
},
|
|
22
26
|
});
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<Select
|
|
3
|
+
v-model="$lang.locale"
|
|
4
|
+
class="flex flex-col items-start md:flex-row"
|
|
5
|
+
as="div"
|
|
6
|
+
:options
|
|
7
|
+
:render-option="renderLocale"
|
|
8
|
+
>
|
|
9
|
+
<div class="grow">
|
|
10
|
+
<SelectLabel>
|
|
11
|
+
{{ $td('settings.locale', 'Language') }}
|
|
12
|
+
</SelectLabel>
|
|
13
|
+
<Markdown
|
|
14
|
+
lang-key="settings.localeDescription"
|
|
15
|
+
lang-default="Choose the application's language."
|
|
16
|
+
class="mt-1 text-sm text-gray-500"
|
|
17
|
+
/>
|
|
18
|
+
</div>
|
|
19
|
+
<Button variant="ghost" :as="SelectTrigger" class="grid w-auto outline-none" />
|
|
20
|
+
<SelectOptions />
|
|
21
|
+
</Select>
|
|
22
|
+
</template>
|
|
23
|
+
|
|
24
|
+
<script setup lang="ts">
|
|
25
|
+
import Aerogel from 'virtual:aerogel';
|
|
26
|
+
|
|
27
|
+
import { computed } from 'vue';
|
|
28
|
+
|
|
29
|
+
import Markdown from '@aerogel/core/components/ui/Markdown.vue';
|
|
30
|
+
import Button from '@aerogel/core/components/ui/Button.vue';
|
|
31
|
+
import Select from '@aerogel/core/components/ui/Select.vue';
|
|
32
|
+
import SelectLabel from '@aerogel/core/components/ui/SelectLabel.vue';
|
|
33
|
+
import SelectTrigger from '@aerogel/core/components/ui/SelectTrigger.vue';
|
|
34
|
+
import SelectOptions from '@aerogel/core/components/ui/SelectOptions.vue';
|
|
35
|
+
import { Lang, translateWithDefault } from '@aerogel/core/lang';
|
|
36
|
+
|
|
37
|
+
const browserLocale = Lang.getBrowserLocale();
|
|
38
|
+
const options = computed(() => [null, ...Lang.locales]);
|
|
39
|
+
|
|
40
|
+
function renderLocale(locale: string | null): string {
|
|
41
|
+
return (
|
|
42
|
+
(locale && Aerogel.locales[locale]) ??
|
|
43
|
+
translateWithDefault('settings.localeDefault', '{locale} (default)', {
|
|
44
|
+
locale: Aerogel.locales[browserLocale] ?? browserLocale,
|
|
45
|
+
})
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
</script>
|
|
@@ -1,11 +1,20 @@
|
|
|
1
1
|
import Aerogel from 'virtual:aerogel';
|
|
2
2
|
|
|
3
3
|
import { getEnv } from '@noeldemartin/utils';
|
|
4
|
-
import type { App } from 'vue';
|
|
4
|
+
import type { App, Component } from 'vue';
|
|
5
5
|
|
|
6
6
|
import { defineServiceState } from '@aerogel/core/services/Service';
|
|
7
7
|
import type { Plugin } from '@aerogel/core/plugins/Plugin';
|
|
8
8
|
|
|
9
|
+
export interface AppSetting {
|
|
10
|
+
component: Component;
|
|
11
|
+
priority: number;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function defineSettings<T extends AppSetting[]>(settings: T): T {
|
|
15
|
+
return settings;
|
|
16
|
+
}
|
|
17
|
+
|
|
9
18
|
export default defineServiceState({
|
|
10
19
|
name: 'app',
|
|
11
20
|
initialState: {
|
|
@@ -14,6 +23,7 @@ export default defineServiceState({
|
|
|
14
23
|
environment: getEnv() ?? 'development',
|
|
15
24
|
version: Aerogel.version,
|
|
16
25
|
sourceUrl: Aerogel.sourceUrl,
|
|
26
|
+
settings: [] as AppSetting[],
|
|
17
27
|
},
|
|
18
28
|
computed: {
|
|
19
29
|
development: (state) => state.environment === 'development',
|
package/src/services/App.ts
CHANGED
|
@@ -1,13 +1,17 @@
|
|
|
1
1
|
import Aerogel from 'virtual:aerogel';
|
|
2
2
|
|
|
3
3
|
import { PromisedValue, facade, forever, updateLocationQueryParameters } from '@noeldemartin/utils';
|
|
4
|
+
import { markRaw } from 'vue';
|
|
4
5
|
|
|
5
6
|
import Events from '@aerogel/core/services/Events';
|
|
6
7
|
import type { Plugin } from '@aerogel/core/plugins';
|
|
7
|
-
import type { Services } from '@aerogel/core/services';
|
|
8
|
+
import type { AppSetting, Services } from '@aerogel/core/services';
|
|
8
9
|
|
|
9
10
|
import Service from './App.state';
|
|
10
11
|
|
|
12
|
+
export { defineSettings } from './App.state';
|
|
13
|
+
export type { AppSetting } from './App.state';
|
|
14
|
+
|
|
11
15
|
export class AppService extends Service {
|
|
12
16
|
|
|
13
17
|
public readonly name = Aerogel.name;
|
|
@@ -22,6 +26,10 @@ export class AppService extends Service {
|
|
|
22
26
|
return this.mounted.isResolved();
|
|
23
27
|
}
|
|
24
28
|
|
|
29
|
+
public addSetting(setting: AppSetting): void {
|
|
30
|
+
this.settings.push(markRaw(setting));
|
|
31
|
+
}
|
|
32
|
+
|
|
25
33
|
public async whenReady<T>(callback: () => T): Promise<T> {
|
|
26
34
|
const result = await this.ready.then(callback);
|
|
27
35
|
|
package/src/services/index.ts
CHANGED
|
@@ -9,6 +9,7 @@ import Events from './Events';
|
|
|
9
9
|
import Service from './Service';
|
|
10
10
|
import Storage from './Storage';
|
|
11
11
|
import { getPiniaStore } from './store';
|
|
12
|
+
import type { AppSetting } from './App.state';
|
|
12
13
|
|
|
13
14
|
export * from './App';
|
|
14
15
|
export * from './Cache';
|
|
@@ -53,6 +54,7 @@ export default definePlugin({
|
|
|
53
54
|
};
|
|
54
55
|
|
|
55
56
|
app.use(getPiniaStore());
|
|
57
|
+
options.settings?.forEach((setting) => App.addSetting(setting));
|
|
56
58
|
|
|
57
59
|
await bootServices(app, services);
|
|
58
60
|
},
|
|
@@ -61,6 +63,7 @@ export default definePlugin({
|
|
|
61
63
|
declare module '@aerogel/core/bootstrap/options' {
|
|
62
64
|
export interface AerogelOptions {
|
|
63
65
|
services?: Record<string, Service>;
|
|
66
|
+
settings?: AppSetting[];
|
|
64
67
|
}
|
|
65
68
|
}
|
|
66
69
|
|
package/src/ui/UI.state.ts
CHANGED
|
@@ -4,7 +4,7 @@ import { defineServiceState } from '@aerogel/core/services/Service';
|
|
|
4
4
|
|
|
5
5
|
import { Layouts, getCurrentLayout } from './utils';
|
|
6
6
|
|
|
7
|
-
export interface
|
|
7
|
+
export interface UIModal<T = unknown> {
|
|
8
8
|
id: string;
|
|
9
9
|
properties: Record<string, unknown>;
|
|
10
10
|
component: Component;
|
|
@@ -12,14 +12,19 @@ export interface Modal<T = unknown> {
|
|
|
12
12
|
afterClose: Promise<T | undefined>;
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
+
export interface UIModalContext {
|
|
16
|
+
modal: UIModal;
|
|
17
|
+
childIndex?: number;
|
|
18
|
+
}
|
|
19
|
+
|
|
15
20
|
export interface ModalComponent<
|
|
16
21
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
17
|
-
Properties extends
|
|
22
|
+
Properties extends object = object,
|
|
18
23
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
19
24
|
Result = unknown,
|
|
20
25
|
> {}
|
|
21
26
|
|
|
22
|
-
export interface
|
|
27
|
+
export interface UIToast {
|
|
23
28
|
id: string;
|
|
24
29
|
component: Component;
|
|
25
30
|
properties: Record<string, unknown>;
|
|
@@ -28,8 +33,8 @@ export interface Snackbar {
|
|
|
28
33
|
export default defineServiceState({
|
|
29
34
|
name: 'ui',
|
|
30
35
|
initialState: {
|
|
31
|
-
modals: [] as
|
|
32
|
-
|
|
36
|
+
modals: [] as UIModal[],
|
|
37
|
+
toasts: [] as UIToast[],
|
|
33
38
|
layout: getCurrentLayout(),
|
|
34
39
|
},
|
|
35
40
|
computed: {
|
package/src/ui/UI.ts
CHANGED
|
@@ -6,18 +6,16 @@ import type { ObjectValues } from '@noeldemartin/utils';
|
|
|
6
6
|
import App from '@aerogel/core/services/App';
|
|
7
7
|
import Events from '@aerogel/core/services/Events';
|
|
8
8
|
import type { AcceptRefs } from '@aerogel/core/utils';
|
|
9
|
-
import type {
|
|
10
|
-
import type {
|
|
11
|
-
import type {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
AGPromptModalProps,
|
|
16
|
-
} from '@aerogel/core/components';
|
|
9
|
+
import type { AlertModalProps } from '@aerogel/core/components/contracts/AlertModal';
|
|
10
|
+
import type { ButtonVariant } from '@aerogel/core/components/contracts/Button';
|
|
11
|
+
import type { ConfirmModalCheckboxes, ConfirmModalProps } from '@aerogel/core/components/contracts/ConfirmModal';
|
|
12
|
+
import type { LoadingModalProps } from '@aerogel/core/components/contracts/LoadingModal';
|
|
13
|
+
import type { PromptModalProps } from '@aerogel/core/components/contracts/PromptModal';
|
|
14
|
+
import type { ToastAction, ToastVariant } from '@aerogel/core/components/contracts/Toast';
|
|
17
15
|
|
|
18
16
|
import Service from './UI.state';
|
|
19
17
|
import { MOBILE_BREAKPOINT, getCurrentLayout } from './utils';
|
|
20
|
-
import type {
|
|
18
|
+
import type { ModalComponent, UIModal, UIToast } from './UI.state';
|
|
21
19
|
|
|
22
20
|
interface ModalCallbacks<T = unknown> {
|
|
23
21
|
willClose(result: T | undefined): void;
|
|
@@ -34,19 +32,18 @@ export const UIComponents = {
|
|
|
34
32
|
ErrorReportModal: 'error-report-modal',
|
|
35
33
|
LoadingModal: 'loading-modal',
|
|
36
34
|
PromptModal: 'prompt-modal',
|
|
37
|
-
|
|
35
|
+
Toast: 'toast',
|
|
38
36
|
StartupCrash: 'startup-crash',
|
|
37
|
+
RouterLink: 'router-link',
|
|
39
38
|
} as const;
|
|
40
39
|
|
|
41
40
|
export type UIComponent = ObjectValues<typeof UIComponents>;
|
|
42
41
|
|
|
43
|
-
export type ConfirmCheckboxes = Record<string, { label: string; default?: boolean; required?: boolean }>;
|
|
44
|
-
|
|
45
42
|
export type ConfirmOptions = AcceptRefs<{
|
|
46
43
|
acceptText?: string;
|
|
47
|
-
|
|
44
|
+
acceptVariant?: ButtonVariant;
|
|
48
45
|
cancelText?: string;
|
|
49
|
-
|
|
46
|
+
cancelVariant?: ButtonVariant;
|
|
50
47
|
actions?: Record<string, () => unknown>;
|
|
51
48
|
required?: boolean;
|
|
52
49
|
}>;
|
|
@@ -57,7 +54,8 @@ export type LoadingOptions = AcceptRefs<{
|
|
|
57
54
|
progress?: number;
|
|
58
55
|
}>;
|
|
59
56
|
|
|
60
|
-
export interface ConfirmOptionsWithCheckboxes<T extends
|
|
57
|
+
export interface ConfirmOptionsWithCheckboxes<T extends ConfirmModalCheckboxes = ConfirmModalCheckboxes>
|
|
58
|
+
extends ConfirmOptions {
|
|
61
59
|
checkboxes?: T;
|
|
62
60
|
}
|
|
63
61
|
|
|
@@ -66,16 +64,16 @@ export type PromptOptions = AcceptRefs<{
|
|
|
66
64
|
defaultValue?: string;
|
|
67
65
|
placeholder?: string;
|
|
68
66
|
acceptText?: string;
|
|
69
|
-
|
|
67
|
+
acceptVariant?: ButtonVariant;
|
|
70
68
|
cancelText?: string;
|
|
71
|
-
|
|
69
|
+
cancelVariant?: ButtonVariant;
|
|
72
70
|
trim?: boolean;
|
|
73
71
|
}>;
|
|
74
72
|
|
|
75
|
-
export interface
|
|
73
|
+
export interface ToastOptions {
|
|
76
74
|
component?: Component;
|
|
77
|
-
|
|
78
|
-
actions?:
|
|
75
|
+
variant?: ToastVariant;
|
|
76
|
+
actions?: ToastAction[];
|
|
79
77
|
}
|
|
80
78
|
|
|
81
79
|
export class UIService extends Service {
|
|
@@ -83,14 +81,18 @@ export class UIService extends Service {
|
|
|
83
81
|
private modalCallbacks: Record<string, Partial<ModalCallbacks>> = {};
|
|
84
82
|
private components: Partial<Record<UIComponent, Component>> = {};
|
|
85
83
|
|
|
84
|
+
public resolveComponent(name: UIComponent): Component | null {
|
|
85
|
+
return this.components[name] ?? null;
|
|
86
|
+
}
|
|
87
|
+
|
|
86
88
|
public requireComponent(name: UIComponent): Component {
|
|
87
|
-
return this.
|
|
89
|
+
return this.resolveComponent(name) ?? fail(`UI Component '${name}' is not defined!`);
|
|
88
90
|
}
|
|
89
91
|
|
|
90
92
|
public alert(message: string): void;
|
|
91
93
|
public alert(title: string, message: string): void;
|
|
92
94
|
public alert(messageOrTitle: string, message?: string): void {
|
|
93
|
-
const getProperties = ():
|
|
95
|
+
const getProperties = (): AlertModalProps => {
|
|
94
96
|
if (typeof message !== 'string') {
|
|
95
97
|
return { message: messageOrTitle };
|
|
96
98
|
}
|
|
@@ -101,14 +103,17 @@ export class UIService extends Service {
|
|
|
101
103
|
};
|
|
102
104
|
};
|
|
103
105
|
|
|
104
|
-
this.openModal(
|
|
106
|
+
this.openModal<ModalComponent<AlertModalProps>>(
|
|
107
|
+
this.requireComponent(UIComponents.AlertModal),
|
|
108
|
+
getProperties(),
|
|
109
|
+
);
|
|
105
110
|
}
|
|
106
111
|
|
|
107
112
|
/* eslint-disable max-len */
|
|
108
113
|
public async confirm(message: string, options?: ConfirmOptions): Promise<boolean>;
|
|
109
114
|
public async confirm(title: string, message: string, options?: ConfirmOptions): Promise<boolean>;
|
|
110
|
-
public async confirm<T extends
|
|
111
|
-
public async confirm<T extends
|
|
115
|
+
public async confirm<T extends ConfirmModalCheckboxes>(message: string, options?: ConfirmOptionsWithCheckboxes<T>): Promise<[boolean, Record<keyof T, boolean>]>; // prettier-ignore
|
|
116
|
+
public async confirm<T extends ConfirmModalCheckboxes>(title: string, message: string, options?: ConfirmOptionsWithCheckboxes<T>): Promise<[boolean, Record<keyof T, boolean>]>; // prettier-ignore
|
|
112
117
|
/* eslint-enable max-len */
|
|
113
118
|
|
|
114
119
|
public async confirm(
|
|
@@ -116,7 +121,7 @@ export class UIService extends Service {
|
|
|
116
121
|
messageOrOptions?: string | ConfirmOptions | ConfirmOptionsWithCheckboxes,
|
|
117
122
|
options?: ConfirmOptions | ConfirmOptionsWithCheckboxes,
|
|
118
123
|
): Promise<boolean | [boolean, Record<string, boolean>]> {
|
|
119
|
-
const getProperties = ():
|
|
124
|
+
const getProperties = (): AcceptRefs<ConfirmModalProps> => {
|
|
120
125
|
if (typeof messageOrOptions !== 'string') {
|
|
121
126
|
return {
|
|
122
127
|
...(messageOrOptions ?? {}),
|
|
@@ -132,10 +137,17 @@ export class UIService extends Service {
|
|
|
132
137
|
required: !!options?.required,
|
|
133
138
|
};
|
|
134
139
|
};
|
|
140
|
+
|
|
141
|
+
type ConfirmModalComponent = ModalComponent<
|
|
142
|
+
AcceptRefs<ConfirmModalProps>,
|
|
143
|
+
boolean | [boolean, Record<string, boolean>]
|
|
144
|
+
>;
|
|
145
|
+
|
|
135
146
|
const properties = getProperties();
|
|
136
|
-
const modal = await this.openModal<
|
|
137
|
-
|
|
138
|
-
|
|
147
|
+
const modal = await this.openModal<ConfirmModalComponent>(
|
|
148
|
+
this.requireComponent(UIComponents.ConfirmModal),
|
|
149
|
+
properties,
|
|
150
|
+
);
|
|
139
151
|
const result = await modal.beforeClose;
|
|
140
152
|
|
|
141
153
|
const confirmed = typeof result === 'object' ? result[0] : (result ?? false);
|
|
@@ -174,22 +186,22 @@ export class UIService extends Service {
|
|
|
174
186
|
options?: PromptOptions,
|
|
175
187
|
): Promise<string | null> {
|
|
176
188
|
const trim = options?.trim ?? true;
|
|
177
|
-
const getProperties = ():
|
|
189
|
+
const getProperties = (): PromptModalProps => {
|
|
178
190
|
if (typeof messageOrOptions !== 'string') {
|
|
179
191
|
return {
|
|
180
192
|
message: messageOrTitle,
|
|
181
193
|
...(messageOrOptions ?? {}),
|
|
182
|
-
} as
|
|
194
|
+
} as PromptModalProps;
|
|
183
195
|
}
|
|
184
196
|
|
|
185
197
|
return {
|
|
186
198
|
title: messageOrTitle,
|
|
187
199
|
message: messageOrOptions,
|
|
188
200
|
...(options ?? {}),
|
|
189
|
-
} as
|
|
201
|
+
} as PromptModalProps;
|
|
190
202
|
};
|
|
191
203
|
|
|
192
|
-
const modal = await this.openModal<ModalComponent<
|
|
204
|
+
const modal = await this.openModal<ModalComponent<PromptModalProps, string | null>>(
|
|
193
205
|
this.requireComponent(UIComponents.PromptModal),
|
|
194
206
|
getProperties(),
|
|
195
207
|
);
|
|
@@ -207,7 +219,7 @@ export class UIService extends Service {
|
|
|
207
219
|
operation?: Promise<T> | (() => T),
|
|
208
220
|
): Promise<T> {
|
|
209
221
|
const processOperation = (o: Promise<T> | (() => T)) => (typeof o === 'function' ? Promise.resolve(o()) : o);
|
|
210
|
-
const processArgs = (): { operationPromise: Promise<T>; props?:
|
|
222
|
+
const processArgs = (): { operationPromise: Promise<T>; props?: AcceptRefs<LoadingModalProps> } => {
|
|
211
223
|
if (typeof operationOrMessageOrOptions === 'string') {
|
|
212
224
|
return {
|
|
213
225
|
props: { message: operationOrMessageOrOptions },
|
|
@@ -229,7 +241,9 @@ export class UIService extends Service {
|
|
|
229
241
|
const modal = await this.openModal(this.requireComponent(UIComponents.LoadingModal), props);
|
|
230
242
|
|
|
231
243
|
try {
|
|
232
|
-
const
|
|
244
|
+
const result = await operationPromise;
|
|
245
|
+
|
|
246
|
+
await after({ ms: 500 });
|
|
233
247
|
|
|
234
248
|
return result;
|
|
235
249
|
} finally {
|
|
@@ -237,23 +251,15 @@ export class UIService extends Service {
|
|
|
237
251
|
}
|
|
238
252
|
}
|
|
239
253
|
|
|
240
|
-
public
|
|
241
|
-
const
|
|
254
|
+
public toast(message: string, options: ToastOptions = {}): void {
|
|
255
|
+
const { component, ...otherOptions } = options;
|
|
256
|
+
const toast: UIToast = {
|
|
242
257
|
id: uuid(),
|
|
243
|
-
properties: { message, ...
|
|
244
|
-
component: markRaw(
|
|
258
|
+
properties: { message, ...otherOptions },
|
|
259
|
+
component: markRaw(component ?? this.requireComponent(UIComponents.Toast)),
|
|
245
260
|
};
|
|
246
261
|
|
|
247
|
-
this.setState('
|
|
248
|
-
|
|
249
|
-
setTimeout(() => this.hideSnackbar(snackbar.id), 5000);
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
public hideSnackbar(id: string): void {
|
|
253
|
-
this.setState(
|
|
254
|
-
'snackbars',
|
|
255
|
-
this.snackbars.filter((snackbar) => snackbar.id !== id),
|
|
256
|
-
);
|
|
262
|
+
this.setState('toasts', this.toasts.concat(toast));
|
|
257
263
|
}
|
|
258
264
|
|
|
259
265
|
public registerComponent(name: UIComponent, component: Component): void {
|
|
@@ -263,10 +269,10 @@ export class UIService extends Service {
|
|
|
263
269
|
public async openModal<TModalComponent extends ModalComponent>(
|
|
264
270
|
component: TModalComponent,
|
|
265
271
|
properties?: ModalProperties<TModalComponent>,
|
|
266
|
-
): Promise<
|
|
272
|
+
): Promise<UIModal<ModalResult<TModalComponent>>> {
|
|
267
273
|
const id = uuid();
|
|
268
274
|
const callbacks: Partial<ModalCallbacks<ModalResult<TModalComponent>>> = {};
|
|
269
|
-
const modal:
|
|
275
|
+
const modal: UIModal<ModalResult<TModalComponent>> = {
|
|
270
276
|
id,
|
|
271
277
|
properties: properties ?? {},
|
|
272
278
|
component: markRaw(component),
|
|
@@ -382,8 +388,8 @@ declare module '@aerogel/core/services/Events' {
|
|
|
382
388
|
'close-modal': { id: string; result?: unknown };
|
|
383
389
|
'hide-modal': { id: string };
|
|
384
390
|
'hide-overlays-backdrop': void;
|
|
385
|
-
'modal-closed': { modal:
|
|
386
|
-
'modal-will-close': { modal:
|
|
391
|
+
'modal-closed': { modal: UIModal; result?: unknown };
|
|
392
|
+
'modal-will-close': { modal: UIModal; result?: unknown };
|
|
387
393
|
'show-modal': { id: string };
|
|
388
394
|
'show-overlays-backdrop': void;
|
|
389
395
|
}
|