@antelopejs/dms 0.9.0 → 0.9.2
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/CHANGELOG.md +34 -0
- package/data/module-store/catalog.json +71 -0
- package/dist/config.d.ts +7 -0
- package/dist/config.js +8 -0
- package/dist/config.js.map +1 -1
- package/dist/implementations/dms/frontend-modules.d.ts +2 -0
- package/dist/implementations/dms/frontend-modules.js +29 -1
- package/dist/implementations/dms/frontend-modules.js.map +1 -1
- package/dist/implementations/dms/frontend-ready.d.ts +8 -0
- package/dist/implementations/dms/frontend-ready.js +34 -0
- package/dist/implementations/dms/frontend-ready.js.map +1 -0
- package/dist/implementations/dms/manifest.d.ts +1 -0
- package/dist/implementations/dms/manifest.js.map +1 -1
- package/dist/implementations/dms/page.d.ts +2 -0
- package/dist/implementations/dms/page.js +16 -0
- package/dist/implementations/dms/page.js.map +1 -1
- package/dist/implementations/dms-auth/index.js +1 -1
- package/dist/implementations/dms-auth/index.js.map +1 -1
- package/dist/routes/index.d.ts +1 -0
- package/dist/routes/index.js +1 -0
- package/dist/routes/index.js.map +1 -1
- package/dist/routes/store.d.ts +21 -0
- package/dist/routes/store.js +130 -0
- package/dist/routes/store.js.map +1 -0
- package/dist/store/activity.d.ts +2 -0
- package/dist/store/activity.js +12 -0
- package/dist/store/activity.js.map +1 -0
- package/dist/store/cache.d.ts +8 -0
- package/dist/store/cache.js +29 -0
- package/dist/store/cache.js.map +1 -0
- package/dist/store/catalog.d.ts +4 -0
- package/dist/store/catalog.js +69 -0
- package/dist/store/catalog.js.map +1 -0
- package/dist/store/compatibility.d.ts +3 -0
- package/dist/store/compatibility.js +45 -0
- package/dist/store/compatibility.js.map +1 -0
- package/dist/store/declarations.d.ts +18 -0
- package/dist/store/declarations.js +116 -0
- package/dist/store/declarations.js.map +1 -0
- package/dist/store/frontend-compatibility.d.ts +18 -0
- package/dist/store/frontend-compatibility.js +93 -0
- package/dist/store/frontend-compatibility.js.map +1 -0
- package/dist/store/interfaces.d.ts +6 -0
- package/dist/store/interfaces.js +126 -0
- package/dist/store/interfaces.js.map +1 -0
- package/dist/store/listing.d.ts +2 -0
- package/dist/store/listing.js +180 -0
- package/dist/store/listing.js.map +1 -0
- package/dist/store/operations.d.ts +8 -0
- package/dist/store/operations.js +254 -0
- package/dist/store/operations.js.map +1 -0
- package/dist/store/registry.d.ts +4 -0
- package/dist/store/registry.js +61 -0
- package/dist/store/registry.js.map +1 -0
- package/dist/store/requirements.d.ts +14 -0
- package/dist/store/requirements.js +81 -0
- package/dist/store/requirements.js.map +1 -0
- package/dist/store/runtime.d.ts +11 -0
- package/dist/store/runtime.js +78 -0
- package/dist/store/runtime.js.map +1 -0
- package/dist/store/tarball.d.ts +2 -0
- package/dist/store/tarball.js +80 -0
- package/dist/store/tarball.js.map +1 -0
- package/dist/store/types.d.ts +98 -0
- package/dist/store/types.js +3 -0
- package/dist/store/types.js.map +1 -0
- package/dist/test/unit/interfaces/dms/page-extension-ownership.test.js +1 -1
- package/dist/test/unit/interfaces/dms/page-extension-ownership.test.js.map +1 -1
- package/dist/test/unit/store/store.test.d.ts +1 -0
- package/dist/test/unit/store/store.test.js +127 -0
- package/dist/test/unit/store/store.test.js.map +1 -0
- package/dist/test/unit/utils/user-activity.test.js +4 -12
- package/dist/test/unit/utils/user-activity.test.js.map +1 -1
- package/dist/utils/user-activity.d.ts +1 -1
- package/dist/utils/user-activity.js +6 -3
- package/dist/utils/user-activity.js.map +1 -1
- package/dist/validation/store.schema.d.ts +39 -0
- package/dist/validation/store.schema.js +27 -0
- package/dist/validation/store.schema.js.map +1 -0
- package/frontend-vue/layers/dms-layout/app/build/components/pages/modules/ModuleStoreDependencyPicker.vue +136 -0
- package/frontend-vue/layers/dms-layout/app/build/components/pages/modules/ModuleStoreRequirements.vue +98 -0
- package/frontend-vue/layers/dms-layout/app/build/components/pages/modules/ModuleStoreTile.vue +247 -29
- package/frontend-vue/layers/dms-layout/app/build/components/pages/modules/ModuleTile.vue +19 -2
- package/frontend-vue/layers/dms-layout/app/build/utils/module-store-catalog.ts +186 -120
- package/frontend-vue/layers/dms-layout/app/build/utils/module-store-install.ts +146 -0
- package/frontend-vue/layers/dms-layout/app/composables/page/useModuleStore.ts +109 -0
- package/frontend-vue/layers/dms-layout/app/custom-pages/modules.vue +335 -34
- package/frontend-vue/layers/dms-layout/app/types/module-store.ts +134 -0
- package/frontend-vue/layers/dms-layout/i18n/locales/layout-en-GB.json +107 -9
- package/frontend-vue/layers/dms-layout/i18n/locales/layout-fr-FR.json +107 -9
- package/frontend-vue/package.json +3 -3
- package/frontend-vue/pnpm-lock.yaml +16 -22
- package/frontend-vue/tests/module-store-catalog.test.ts +270 -51
- package/frontend-vue/tests/top-list-card-period-badge-ssr.test.ts +212 -0
- package/package.json +5 -5
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"store.schema.js","sourceRoot":"","sources":["../../src/validation/store.schema.ts"],"names":[],"mappings":";;;AAAA,6BAAwB;AAExB,MAAM,kBAAkB,GAAG,sBAAsB,CAAC;AAClD,MAAM,qBAAqB,GAAG,EAAE,CAAC;AAGpB,QAAA,oBAAoB,GAAG,OAAC,CAAC,MAAM,CAAC;IAC3C,EAAE,EAAE,OAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,qBAAqB,CAAC,CAAC,KAAK,CAAC,kBAAkB,CAAC;CACpE,CAAC,CAAC;AAMH,MAAM,mBAAmB,GACvB,sDAAsD,CAAC;AACzD,MAAM,sBAAsB,GAAG,GAAG,CAAC;AACnC,MAAM,gBAAgB,GAAG,EAAE,CAAC;AAE5B,MAAM,gBAAgB,GAAG,OAAC;KACvB,MAAM,EAAE;KACR,GAAG,CAAC,sBAAsB,CAAC;KAC3B,KAAK,CAAC,mBAAmB,CAAC,CAAC;AAMjB,QAAA,kBAAkB,GAAG,4BAAoB,CAAC,MAAM,CAAC;IAC5D,YAAY,EAAE,OAAC;SACZ,KAAK,CACJ,OAAC,CAAC,MAAM,CAAC;QACP,gBAAgB,EAAE,gBAAgB;QAClC,OAAO,EAAE,gBAAgB;QACzB,MAAM,EAAE,OAAC,CAAC,MAAM,CAAC,OAAC,CAAC,OAAO,EAAE,CAAC,CAAC,QAAQ,EAAE;KACzC,CAAC,CACH;SACA,GAAG,CAAC,gBAAgB,CAAC;SACrB,QAAQ,EAAE;CACd,CAAC,CAAC"}
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { MONO_CHIP_CLASS } from "#dms-ui/app/build/utils/monoChip";
|
|
3
|
+
import type { MissingInterface } from "../../../../types/module-store";
|
|
4
|
+
import {
|
|
5
|
+
type DependencyDraft,
|
|
6
|
+
type DependencyDraftError,
|
|
7
|
+
OTHER_PACKAGE_CHOICE,
|
|
8
|
+
validateDependencyDraft,
|
|
9
|
+
} from "../../../utils/module-store-install";
|
|
10
|
+
|
|
11
|
+
interface ModuleStoreDependencyPickerProps {
|
|
12
|
+
/** The interfaces the release needs and no running module implements. */
|
|
13
|
+
missing: MissingInterface[];
|
|
14
|
+
/** One draft per missing interface, in the same order. */
|
|
15
|
+
drafts: DependencyDraft[];
|
|
16
|
+
/** Confirm was tried: the fields say what is wrong. */
|
|
17
|
+
showErrors?: boolean;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
interface ModuleStoreDependencyPickerEmits {
|
|
21
|
+
update: [index: number, patch: Partial<DependencyDraft>];
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const props = withDefaults(defineProps<ModuleStoreDependencyPickerProps>(), {
|
|
25
|
+
showErrors: false,
|
|
26
|
+
});
|
|
27
|
+
const emit = defineEmits<ModuleStoreDependencyPickerEmits>();
|
|
28
|
+
const { t } = useI18n();
|
|
29
|
+
|
|
30
|
+
const CONFIG_PLACEHOLDER = "{}";
|
|
31
|
+
const CONFIG_ROWS = 3;
|
|
32
|
+
|
|
33
|
+
const groups = computed(() =>
|
|
34
|
+
props.missing.map((missing, index) => {
|
|
35
|
+
const draft = props.drafts[index];
|
|
36
|
+
const errors =
|
|
37
|
+
props.showErrors && draft ? validateDependencyDraft(draft) : {};
|
|
38
|
+
return {
|
|
39
|
+
missing,
|
|
40
|
+
draft,
|
|
41
|
+
errors,
|
|
42
|
+
items: [
|
|
43
|
+
...missing.candidates.map((candidate) => ({
|
|
44
|
+
label: candidate.name,
|
|
45
|
+
description: candidate.package,
|
|
46
|
+
value: candidate.package,
|
|
47
|
+
})),
|
|
48
|
+
{
|
|
49
|
+
label: t("modules.store.install_dialog.other"),
|
|
50
|
+
value: OTHER_PACKAGE_CHOICE,
|
|
51
|
+
},
|
|
52
|
+
],
|
|
53
|
+
};
|
|
54
|
+
}),
|
|
55
|
+
);
|
|
56
|
+
|
|
57
|
+
function errorText(error: DependencyDraftError | undefined) {
|
|
58
|
+
return error ? t(`modules.store.install_dialog.errors.${error}`) : undefined;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/** Sends `field` of draft `index` as the control typed it. */
|
|
62
|
+
function updateField(
|
|
63
|
+
index: number,
|
|
64
|
+
field: keyof DependencyDraft,
|
|
65
|
+
value: unknown,
|
|
66
|
+
): void {
|
|
67
|
+
emit("update", index, { [field]: String(value ?? "") });
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function fieldId(index: number, field: string): string {
|
|
71
|
+
return `store-dependency-${index}-${field}`;
|
|
72
|
+
}
|
|
73
|
+
</script>
|
|
74
|
+
|
|
75
|
+
<template>
|
|
76
|
+
<div class="flex flex-col gap-4">
|
|
77
|
+
<section
|
|
78
|
+
v-for="(group, index) in groups"
|
|
79
|
+
:key="group.missing.interfacePackage"
|
|
80
|
+
class="flex flex-col gap-2.5"
|
|
81
|
+
:aria-labelledby="fieldId(index, 'title')"
|
|
82
|
+
>
|
|
83
|
+
<h4
|
|
84
|
+
:id="fieldId(index, 'title')"
|
|
85
|
+
class="text-highlighted text-[13px] font-semibold"
|
|
86
|
+
>
|
|
87
|
+
<code :class="[MONO_CHIP_CLASS, 'bg-elevated text-xs']">
|
|
88
|
+
{{ group.missing.interfacePackage }}
|
|
89
|
+
</code>
|
|
90
|
+
</h4>
|
|
91
|
+
<template v-if="group.draft">
|
|
92
|
+
<URadioGroup
|
|
93
|
+
:model-value="group.draft.choice"
|
|
94
|
+
:items="group.items"
|
|
95
|
+
size="sm"
|
|
96
|
+
:aria-labelledby="fieldId(index, 'title')"
|
|
97
|
+
@update:model-value="updateField(index, 'choice', $event)"
|
|
98
|
+
/>
|
|
99
|
+
<UFormField
|
|
100
|
+
v-if="group.draft.choice === OTHER_PACKAGE_CHOICE"
|
|
101
|
+
:label="t('modules.store.install_dialog.package_label')"
|
|
102
|
+
:error="errorText(group.errors.package)"
|
|
103
|
+
>
|
|
104
|
+
<UInput
|
|
105
|
+
:id="fieldId(index, 'package')"
|
|
106
|
+
:model-value="group.draft.otherPackage"
|
|
107
|
+
class="w-full font-mono"
|
|
108
|
+
autocomplete="off"
|
|
109
|
+
spellcheck="false"
|
|
110
|
+
:placeholder="t('modules.store.install_dialog.package_placeholder')"
|
|
111
|
+
@update:model-value="updateField(index, 'otherPackage', $event)"
|
|
112
|
+
/>
|
|
113
|
+
</UFormField>
|
|
114
|
+
<UFormField
|
|
115
|
+
:label="t('modules.store.install_dialog.config_label')"
|
|
116
|
+
:hint="t('modules.store.install_dialog.config_hint')"
|
|
117
|
+
:error="errorText(group.errors.config)"
|
|
118
|
+
>
|
|
119
|
+
<UTextarea
|
|
120
|
+
:id="fieldId(index, 'config')"
|
|
121
|
+
:model-value="group.draft.config"
|
|
122
|
+
class="w-full font-mono text-xs"
|
|
123
|
+
:rows="CONFIG_ROWS"
|
|
124
|
+
autoresize
|
|
125
|
+
spellcheck="false"
|
|
126
|
+
:placeholder="CONFIG_PLACEHOLDER"
|
|
127
|
+
@update:model-value="updateField(index, 'config', $event)"
|
|
128
|
+
/>
|
|
129
|
+
</UFormField>
|
|
130
|
+
</template>
|
|
131
|
+
</section>
|
|
132
|
+
<p class="text-muted text-xs">
|
|
133
|
+
{{ t("modules.store.install_dialog.note") }}
|
|
134
|
+
</p>
|
|
135
|
+
</div>
|
|
136
|
+
</template>
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import DmsCopyButton from "#dms-ui/app/components/copy/CopyButton.vue";
|
|
3
|
+
import type { StoreListingEntry } from "../../../../types/module-store";
|
|
4
|
+
|
|
5
|
+
interface ModuleStoreRequirementsProps {
|
|
6
|
+
/** The store module whose requirements the tile spells out. */
|
|
7
|
+
entry: StoreListingEntry;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const props = defineProps<ModuleStoreRequirementsProps>();
|
|
11
|
+
const { t } = useI18n();
|
|
12
|
+
|
|
13
|
+
const LIST_SEPARATOR = ", ";
|
|
14
|
+
|
|
15
|
+
const requirement = computed(() => props.entry.frontendRequirement);
|
|
16
|
+
const missingInterfaces = computed(() =>
|
|
17
|
+
props.entry.missingInterfaces
|
|
18
|
+
.map((missing) => missing.interfacePackage)
|
|
19
|
+
.join(LIST_SEPARATOR),
|
|
20
|
+
);
|
|
21
|
+
const dependents = computed(() => props.entry.dependents.join(LIST_SEPARATOR));
|
|
22
|
+
</script>
|
|
23
|
+
|
|
24
|
+
<template>
|
|
25
|
+
<!-- The release needs another dms-frontend: what to run, what it breaks.
|
|
26
|
+
The store never moves dms-frontend itself. -->
|
|
27
|
+
<div
|
|
28
|
+
v-if="requirement"
|
|
29
|
+
class="flex flex-col gap-1.5 rounded-[8px] bg-(--ui-warning)/8 px-2.5 py-1.5 text-xs text-(--ui-warning)"
|
|
30
|
+
>
|
|
31
|
+
<p>
|
|
32
|
+
{{
|
|
33
|
+
t("modules.store.frontend_requirement.needs", {
|
|
34
|
+
range: requirement.range,
|
|
35
|
+
running: requirement.running,
|
|
36
|
+
})
|
|
37
|
+
}}
|
|
38
|
+
</p>
|
|
39
|
+
<template v-if="requirement.target">
|
|
40
|
+
<p v-if="requirement.command" class="text-toned">
|
|
41
|
+
{{
|
|
42
|
+
t("modules.store.frontend_requirement.run", {
|
|
43
|
+
target: requirement.target,
|
|
44
|
+
})
|
|
45
|
+
}}
|
|
46
|
+
</p>
|
|
47
|
+
<div
|
|
48
|
+
v-if="requirement.command"
|
|
49
|
+
class="text-highlighted flex min-w-0 items-center gap-1 rounded-[6px] border border-(--ui-border) bg-(--dms-bg-muted) ps-2 font-mono text-[11px]"
|
|
50
|
+
>
|
|
51
|
+
<code class="min-w-0 flex-1 truncate" :title="requirement.command">
|
|
52
|
+
{{ requirement.command }}
|
|
53
|
+
</code>
|
|
54
|
+
<DmsCopyButton :value="requirement.command" />
|
|
55
|
+
</div>
|
|
56
|
+
<template v-if="requirement.breaks.length > 0">
|
|
57
|
+
<p>
|
|
58
|
+
{{
|
|
59
|
+
t("modules.store.frontend_requirement.breaks", {
|
|
60
|
+
target: requirement.target,
|
|
61
|
+
})
|
|
62
|
+
}}
|
|
63
|
+
</p>
|
|
64
|
+
<ul class="text-toned flex flex-col gap-0.5">
|
|
65
|
+
<li
|
|
66
|
+
v-for="layer in requirement.breaks"
|
|
67
|
+
:key="layer.name"
|
|
68
|
+
class="flex min-w-0 items-baseline gap-1.5"
|
|
69
|
+
>
|
|
70
|
+
<span class="truncate font-mono">{{ layer.name }}</span>
|
|
71
|
+
<span class="text-dimmed shrink-0">
|
|
72
|
+
{{
|
|
73
|
+
t("modules.store.frontend_requirement.supports", {
|
|
74
|
+
range: layer.range,
|
|
75
|
+
})
|
|
76
|
+
}}
|
|
77
|
+
</span>
|
|
78
|
+
</li>
|
|
79
|
+
</ul>
|
|
80
|
+
</template>
|
|
81
|
+
</template>
|
|
82
|
+
<p v-else class="text-toned">
|
|
83
|
+
{{ t("modules.store.frontend_requirement.no_target") }}
|
|
84
|
+
</p>
|
|
85
|
+
</div>
|
|
86
|
+
|
|
87
|
+
<p
|
|
88
|
+
v-if="missingInterfaces"
|
|
89
|
+
class="text-toned truncate text-xs"
|
|
90
|
+
:title="missingInterfaces"
|
|
91
|
+
>
|
|
92
|
+
{{ t("modules.store.requires", { interfaces: missingInterfaces }) }}
|
|
93
|
+
</p>
|
|
94
|
+
|
|
95
|
+
<p v-if="dependents" class="text-xs text-(--ui-warning)">
|
|
96
|
+
{{ t("modules.store.used_by", { modules: dependents }) }}
|
|
97
|
+
</p>
|
|
98
|
+
</template>
|
package/frontend-vue/layers/dms-layout/app/build/components/pages/modules/ModuleStoreTile.vue
CHANGED
|
@@ -1,23 +1,175 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
import ModuleTile from "./ModuleTile.vue";
|
|
3
|
+
import ModuleStoreRequirements from "./ModuleStoreRequirements.vue";
|
|
3
4
|
import {
|
|
4
|
-
MODULE_INSTALL_COMMAND,
|
|
5
|
-
moduleInstallCommand,
|
|
6
5
|
type ModuleStoreEntry,
|
|
6
|
+
type StoreAction,
|
|
7
|
+
type StoreActionBlock,
|
|
8
|
+
storeActionBlock,
|
|
9
|
+
storeActions,
|
|
10
|
+
storeTileStatus,
|
|
7
11
|
} from "../../../utils/module-store-catalog";
|
|
12
|
+
import type {
|
|
13
|
+
StoreOperationKind,
|
|
14
|
+
StoreOperationStep,
|
|
15
|
+
} from "../../../../types/module-store";
|
|
8
16
|
|
|
9
17
|
interface ModuleStoreTileProps {
|
|
10
18
|
/** The store module the tile presents. */
|
|
11
19
|
entry: ModuleStoreEntry;
|
|
20
|
+
/** Another module's operation runs: every action waits for it. */
|
|
21
|
+
isLocked?: boolean;
|
|
12
22
|
}
|
|
13
23
|
|
|
14
|
-
|
|
24
|
+
interface ModuleStoreTileEmits {
|
|
25
|
+
action: [kind: StoreOperationKind];
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const props = withDefaults(defineProps<ModuleStoreTileProps>(), {
|
|
29
|
+
isLocked: false,
|
|
30
|
+
});
|
|
31
|
+
const emit = defineEmits<ModuleStoreTileEmits>();
|
|
15
32
|
const { t } = useI18n();
|
|
16
33
|
const { processI18n } = useTranslation();
|
|
17
34
|
|
|
18
35
|
const TOOLTIP_CONTENT = { side: "top", sideOffset: 6 } as const;
|
|
36
|
+
/** The steps an operation shows, in the order they run. */
|
|
37
|
+
const PROGRESS_STEPS: StoreOperationStep[] = [
|
|
38
|
+
"resolve",
|
|
39
|
+
"dependencies",
|
|
40
|
+
"unload",
|
|
41
|
+
"load",
|
|
42
|
+
"save",
|
|
43
|
+
];
|
|
44
|
+
/**
|
|
45
|
+
* Steps an operation of this kind goes through. The file is written last:
|
|
46
|
+
* it only ever names a module that loaded.
|
|
47
|
+
*/
|
|
48
|
+
const KIND_STEPS: Record<StoreOperationKind, StoreOperationStep[]> = {
|
|
49
|
+
install: ["resolve", "dependencies", "load", "save"],
|
|
50
|
+
update: ["resolve", "unload", "load", "save"],
|
|
51
|
+
enable: ["load", "save"],
|
|
52
|
+
disable: ["unload", "save"],
|
|
53
|
+
uninstall: ["unload", "save"],
|
|
54
|
+
apply: ["load"],
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
const status = computed(() => storeTileStatus(props.entry));
|
|
58
|
+
const actions = computed(() => storeActions(props.entry));
|
|
59
|
+
const primary = computed<StoreAction | undefined>(() =>
|
|
60
|
+
actions.value.find((action) => !action.isMenuOnly),
|
|
61
|
+
);
|
|
62
|
+
const secondary = computed(() =>
|
|
63
|
+
actions.value
|
|
64
|
+
.filter((action) => action !== primary.value)
|
|
65
|
+
.map((action) => ({
|
|
66
|
+
label: t(`modules.store.actions.${action.labelKey}`),
|
|
67
|
+
icon: action.icon,
|
|
68
|
+
color: action.kind === "uninstall" ? ("error" as const) : undefined,
|
|
69
|
+
disabled: storeActionBlock(props.entry, action.kind) !== undefined,
|
|
70
|
+
onSelect: () => emit("action", action.kind),
|
|
71
|
+
})),
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
const isInstalledHere = computed(
|
|
75
|
+
() => props.entry.installedVersion !== undefined,
|
|
76
|
+
);
|
|
77
|
+
const LIST_SEPARATOR = ", ";
|
|
78
|
+
|
|
79
|
+
const BLOCK_MESSAGES: Record<StoreActionBlock, () => string> = {
|
|
80
|
+
no_release: () => t("modules.store.no_release"),
|
|
81
|
+
incompatible: () =>
|
|
82
|
+
t("modules.store.incompatible", {
|
|
83
|
+
range: props.entry.requiredInterfaceRange ?? "",
|
|
84
|
+
}),
|
|
85
|
+
frontend: () =>
|
|
86
|
+
t("modules.store.frontend_requirement.needs", {
|
|
87
|
+
range: props.entry.frontendRequirement?.range ?? "",
|
|
88
|
+
running: props.entry.frontendRequirement?.running ?? "",
|
|
89
|
+
}),
|
|
90
|
+
interfaces: () =>
|
|
91
|
+
t("modules.store.requires_first", {
|
|
92
|
+
interfaces: props.entry.missingInterfaces
|
|
93
|
+
.map((missing) => missing.interfacePackage)
|
|
94
|
+
.join(LIST_SEPARATOR),
|
|
95
|
+
}),
|
|
96
|
+
dependents: () =>
|
|
97
|
+
t("modules.store.used_by", {
|
|
98
|
+
modules: props.entry.dependents.join(LIST_SEPARATOR),
|
|
99
|
+
}),
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
/** Why the main action waits, when it does. */
|
|
103
|
+
const blockedReason = computed(() => {
|
|
104
|
+
if (props.isLocked) return t("modules.store.locked");
|
|
105
|
+
if (!primary.value) return undefined;
|
|
106
|
+
const block = storeActionBlock(props.entry, primary.value.kind);
|
|
107
|
+
return block ? BLOCK_MESSAGES[block]() : undefined;
|
|
108
|
+
});
|
|
109
|
+
const isBlocked = computed(() => blockedReason.value !== undefined);
|
|
110
|
+
|
|
111
|
+
const operation = computed(() => props.entry.operation);
|
|
112
|
+
/**
|
|
113
|
+
* Whether the running install loads modules for missing interfaces. Kept
|
|
114
|
+
* for the whole operation: once they run, the listing no longer misses them.
|
|
115
|
+
*/
|
|
116
|
+
const isInstallingDependencies = ref(false);
|
|
117
|
+
watch(
|
|
118
|
+
operation,
|
|
119
|
+
(running) => {
|
|
120
|
+
if (!running) {
|
|
121
|
+
isInstallingDependencies.value = false;
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
if (
|
|
125
|
+
running.step === "dependencies" ||
|
|
126
|
+
(running.kind === "install" && props.entry.missingInterfaces.length > 0)
|
|
127
|
+
) {
|
|
128
|
+
isInstallingDependencies.value = true;
|
|
129
|
+
}
|
|
130
|
+
},
|
|
131
|
+
{ immediate: true },
|
|
132
|
+
);
|
|
133
|
+
|
|
134
|
+
const steps = computed(() => {
|
|
135
|
+
const kind = operation.value?.kind;
|
|
136
|
+
if (!kind) return [];
|
|
137
|
+
const current = PROGRESS_STEPS.indexOf(operation.value.step);
|
|
138
|
+
const kindSteps = KIND_STEPS[kind].filter(
|
|
139
|
+
(step) => step !== "dependencies" || isInstallingDependencies.value,
|
|
140
|
+
);
|
|
141
|
+
return kindSteps.map((step) => {
|
|
142
|
+
const index = PROGRESS_STEPS.indexOf(step);
|
|
143
|
+
const state =
|
|
144
|
+
index < current ? "done" : index === current ? "running" : "todo";
|
|
145
|
+
return { step, state };
|
|
146
|
+
});
|
|
147
|
+
});
|
|
19
148
|
|
|
20
|
-
const
|
|
149
|
+
const STEP_ICONS: Record<string, string> = {
|
|
150
|
+
done: "i-ph-check-circle-fill",
|
|
151
|
+
running: "i-ph-circle-notch",
|
|
152
|
+
todo: "i-ph-circle",
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
const PLAIN_VERSION = /^\d/;
|
|
156
|
+
|
|
157
|
+
/** A version as the footer shows it: `v1.2.3`, or a range as written. */
|
|
158
|
+
function formatVersion(version: string): string {
|
|
159
|
+
return PLAIN_VERSION.test(version) ? `v${version}` : version;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const versionLabel = computed(() => {
|
|
163
|
+
const { installedVersion, latestVersion, operation: running } = props.entry;
|
|
164
|
+
if (running?.targetVersion && installedVersion && running.kind === "update") {
|
|
165
|
+
return `${formatVersion(installedVersion)} → ${formatVersion(running.targetVersion)}`;
|
|
166
|
+
}
|
|
167
|
+
if (props.entry.updateAvailable && installedVersion && latestVersion) {
|
|
168
|
+
return `${formatVersion(installedVersion)} → ${formatVersion(latestVersion)}`;
|
|
169
|
+
}
|
|
170
|
+
const version = installedVersion ?? latestVersion;
|
|
171
|
+
return version ? formatVersion(version) : undefined;
|
|
172
|
+
});
|
|
21
173
|
</script>
|
|
22
174
|
|
|
23
175
|
<template>
|
|
@@ -26,49 +178,115 @@ const command = computed(() => moduleInstallCommand(props.entry));
|
|
|
26
178
|
:title="processI18n(props.entry.title)"
|
|
27
179
|
:description="processI18n(props.entry.description)"
|
|
28
180
|
:status="{
|
|
29
|
-
tone:
|
|
30
|
-
dot:
|
|
31
|
-
label: t(
|
|
181
|
+
tone: status.tone,
|
|
182
|
+
dot: status.dot,
|
|
183
|
+
label: t(`modules.store.state.${status.labelKey}`),
|
|
32
184
|
}"
|
|
185
|
+
:is-solid="isInstalledHere"
|
|
33
186
|
>
|
|
34
|
-
<!--
|
|
35
|
-
|
|
187
|
+
<!-- Running operation: its steps, the current one spinning. -->
|
|
188
|
+
<ol
|
|
189
|
+
v-if="steps.length > 0"
|
|
190
|
+
class="flex flex-col gap-1 text-xs"
|
|
191
|
+
:aria-label="t(`modules.store.progress.${operation?.kind}`)"
|
|
192
|
+
>
|
|
193
|
+
<li
|
|
194
|
+
v-for="{ step, state } in steps"
|
|
195
|
+
:key="step"
|
|
196
|
+
class="flex items-center gap-1.5"
|
|
197
|
+
:class="state === 'todo' ? 'text-dimmed' : 'text-toned'"
|
|
198
|
+
>
|
|
199
|
+
<UIcon
|
|
200
|
+
:name="STEP_ICONS[state]"
|
|
201
|
+
class="size-3.5 shrink-0"
|
|
202
|
+
:class="{
|
|
203
|
+
'animate-spin text-(--ui-primary)': state === 'running',
|
|
204
|
+
'text-(--ui-success)': state === 'done',
|
|
205
|
+
}"
|
|
206
|
+
/>
|
|
207
|
+
{{ t(`modules.store.steps.${step}`) }}
|
|
208
|
+
</li>
|
|
209
|
+
</ol>
|
|
210
|
+
|
|
211
|
+
<p
|
|
212
|
+
v-else-if="props.entry.state === 'error' && props.entry.error"
|
|
213
|
+
class="rounded-[8px] bg-(--ui-error)/8 px-2.5 py-1.5 text-xs text-(--ui-error)"
|
|
214
|
+
:title="props.entry.error"
|
|
215
|
+
>
|
|
216
|
+
<span class="line-clamp-3 break-words">{{ props.entry.error }}</span>
|
|
217
|
+
</p>
|
|
218
|
+
|
|
219
|
+
<p
|
|
220
|
+
v-else-if="props.entry.state === 'pending'"
|
|
221
|
+
class="text-xs text-(--ui-warning)"
|
|
222
|
+
>
|
|
223
|
+
{{ t("modules.store.pending_hint") }}
|
|
224
|
+
</p>
|
|
225
|
+
|
|
226
|
+
<!-- The npm package the module comes from. -->
|
|
36
227
|
<div
|
|
37
|
-
|
|
38
|
-
|
|
228
|
+
v-else
|
|
229
|
+
class="text-dimmed flex h-[30px] min-w-0 items-center rounded-[8px] border border-(--ui-border) bg-(--dms-bg-muted) px-2.5 font-mono text-[11.5px]"
|
|
230
|
+
:title="props.entry.package"
|
|
39
231
|
>
|
|
40
|
-
<span class="
|
|
41
|
-
<!-- The package name never truncates: the CLI prefix gives way first. -->
|
|
42
|
-
<span class="min-w-0 truncate">
|
|
43
|
-
<span aria-hidden="true" class="opacity-70">$</span>
|
|
44
|
-
{{ MODULE_INSTALL_COMMAND }}
|
|
45
|
-
</span>
|
|
46
|
-
<b class="text-toned shrink-0 font-semibold">
|
|
47
|
-
{{ props.entry.packageName }}
|
|
48
|
-
</b>
|
|
232
|
+
<span class="truncate">{{ props.entry.package }}</span>
|
|
49
233
|
</div>
|
|
50
234
|
|
|
235
|
+
<ModuleStoreRequirements v-if="steps.length === 0" :entry="props.entry" />
|
|
236
|
+
|
|
51
237
|
<template #footer>
|
|
52
238
|
<span class="truncate">
|
|
53
239
|
{{ processI18n(props.entry.catalogCategory) }}
|
|
54
240
|
</span>
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
241
|
+
<span
|
|
242
|
+
v-if="versionLabel"
|
|
243
|
+
class="shrink-0"
|
|
244
|
+
:class="props.entry.updateAvailable ? 'text-(--ui-warning)' : ''"
|
|
245
|
+
>
|
|
246
|
+
{{ versionLabel }}
|
|
247
|
+
</span>
|
|
248
|
+
<span class="ms-auto flex shrink-0 items-center gap-1 font-sans">
|
|
58
249
|
<UTooltip
|
|
59
|
-
|
|
250
|
+
v-if="primary"
|
|
251
|
+
:text="blockedReason"
|
|
252
|
+
:disabled="!blockedReason"
|
|
60
253
|
:content="TOOLTIP_CONTENT"
|
|
61
254
|
>
|
|
255
|
+
<!-- aria-disabled rather than disabled: the button keeps its
|
|
256
|
+
tooltip, which says why it waits. -->
|
|
62
257
|
<UButton
|
|
258
|
+
size="xs"
|
|
259
|
+
:color="primary.kind === 'update' ? 'warning' : 'neutral'"
|
|
260
|
+
:variant="primary.kind === 'install' ? 'solid' : 'outline'"
|
|
261
|
+
:icon="primary.icon"
|
|
262
|
+
:label="t(`modules.store.actions.${primary.labelKey}`)"
|
|
263
|
+
:aria-disabled="isBlocked ? 'true' : undefined"
|
|
264
|
+
:class="{ 'cursor-not-allowed opacity-50': isBlocked }"
|
|
265
|
+
@click="!isBlocked && emit('action', primary.kind)"
|
|
266
|
+
/>
|
|
267
|
+
</UTooltip>
|
|
268
|
+
<UDropdownMenu
|
|
269
|
+
v-if="secondary.length > 0 && !props.isLocked"
|
|
270
|
+
:items="secondary"
|
|
271
|
+
:content="{ align: 'end' }"
|
|
272
|
+
>
|
|
273
|
+
<UButton
|
|
274
|
+
v-if="primary"
|
|
275
|
+
size="xs"
|
|
276
|
+
color="neutral"
|
|
277
|
+
variant="ghost"
|
|
278
|
+
icon="i-ph-dots-three-vertical"
|
|
279
|
+
:aria-label="t('modules.store.more_actions')"
|
|
280
|
+
/>
|
|
281
|
+
<UButton
|
|
282
|
+
v-else
|
|
63
283
|
size="xs"
|
|
64
284
|
color="neutral"
|
|
65
285
|
variant="outline"
|
|
66
|
-
icon="i-ph-
|
|
67
|
-
:label="t('modules.store.
|
|
68
|
-
aria-disabled="true"
|
|
69
|
-
@click.prevent
|
|
286
|
+
trailing-icon="i-ph-caret-down"
|
|
287
|
+
:label="t('modules.store.actions.manage')"
|
|
70
288
|
/>
|
|
71
|
-
</
|
|
289
|
+
</UDropdownMenu>
|
|
72
290
|
</span>
|
|
73
291
|
</template>
|
|
74
292
|
</ModuleTile>
|
|
@@ -23,6 +23,11 @@ interface ModuleTileProps {
|
|
|
23
23
|
status: ModuleTileStatus;
|
|
24
24
|
/** Where the tile leads; without it, the tile is a placeholder. */
|
|
25
25
|
to?: string;
|
|
26
|
+
/**
|
|
27
|
+
* A tile with no link that still stands for a module running here (one
|
|
28
|
+
* the store manages): drawn solid rather than dashed.
|
|
29
|
+
*/
|
|
30
|
+
isSolid?: boolean;
|
|
26
31
|
}
|
|
27
32
|
|
|
28
33
|
interface ModuleTileSlots {
|
|
@@ -36,6 +41,7 @@ interface ModuleTileSlots {
|
|
|
36
41
|
|
|
37
42
|
const props = withDefaults(defineProps<ModuleTileProps>(), {
|
|
38
43
|
to: undefined,
|
|
44
|
+
isSolid: false,
|
|
39
45
|
});
|
|
40
46
|
defineSlots<ModuleTileSlots>();
|
|
41
47
|
|
|
@@ -58,10 +64,18 @@ const theme = tv({
|
|
|
58
64
|
footer: "py-[7px]",
|
|
59
65
|
},
|
|
60
66
|
},
|
|
67
|
+
isSolid: {
|
|
68
|
+
true: {
|
|
69
|
+
root: "dms-card border-solid",
|
|
70
|
+
footer: "bg-(--dms-bg-muted) py-[7px]",
|
|
71
|
+
},
|
|
72
|
+
},
|
|
61
73
|
},
|
|
62
74
|
});
|
|
63
75
|
|
|
64
|
-
const ui = computed(() =>
|
|
76
|
+
const ui = computed(() =>
|
|
77
|
+
theme({ isPlaceholder: !props.to, isSolid: !props.to && props.isSolid }),
|
|
78
|
+
);
|
|
65
79
|
</script>
|
|
66
80
|
|
|
67
81
|
<template>
|
|
@@ -72,7 +86,10 @@ const ui = computed(() => theme({ isPlaceholder: !props.to }));
|
|
|
72
86
|
:class="ui.root()"
|
|
73
87
|
>
|
|
74
88
|
<div class="flex items-start justify-between gap-2.5">
|
|
75
|
-
<DmsIconWell
|
|
89
|
+
<DmsIconWell
|
|
90
|
+
:icon="props.icon"
|
|
91
|
+
:tone="props.to || props.isSolid ? undefined : 'muted'"
|
|
92
|
+
/>
|
|
76
93
|
<DmsStatusPill
|
|
77
94
|
:tone="props.status.tone"
|
|
78
95
|
:dot="props.status.dot"
|