@revenexx/studio-integrations 0.5.1 → 0.7.0
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 +428 -0
- package/README.md +48 -4
- package/dist/module.d.mts +17 -1
- package/dist/module.json +1 -1
- package/dist/module.mjs +10 -2
- package/dist/runtime/components/Integrations/CredentialCreateDialog.vue +43 -41
- package/dist/runtime/components/Integrations/CredentialDetailDialog.vue +123 -50
- package/dist/runtime/components/Integrations/CredentialRevenexxSource.vue +108 -71
- package/dist/runtime/components/Integrations/CredentialTypePicker.vue +12 -15
- package/dist/runtime/components/Integrations/DeadLettersPanel.vue +18 -17
- package/dist/runtime/components/Integrations/EventTypeCombobox.d.vue.ts +1 -0
- package/dist/runtime/components/Integrations/EventTypeCombobox.vue +7 -4
- package/dist/runtime/components/Integrations/EventTypeCombobox.vue.d.ts +1 -0
- package/dist/runtime/components/Integrations/ExpressionInput.d.vue.ts +4 -0
- package/dist/runtime/components/Integrations/ExpressionInput.vue +67 -37
- package/dist/runtime/components/Integrations/ExpressionInput.vue.d.ts +4 -0
- package/dist/runtime/components/Integrations/GroupFrameView.vue +30 -20
- package/dist/runtime/components/Integrations/Layout.vue +48 -4
- package/dist/runtime/components/Integrations/NodeConfigDynamicField.vue +23 -23
- package/dist/runtime/components/Integrations/NodeConfigDynamicSchema.vue +11 -13
- package/dist/runtime/components/Integrations/NodeConfigField.d.vue.ts +2 -0
- package/dist/runtime/components/Integrations/NodeConfigField.vue +187 -93
- package/dist/runtime/components/Integrations/NodeConfigField.vue.d.ts +2 -0
- package/dist/runtime/components/Integrations/NodeInspector.d.vue.ts +6 -0
- package/dist/runtime/components/Integrations/NodeInspector.vue +1157 -376
- package/dist/runtime/components/Integrations/NodeInspector.vue.d.ts +6 -0
- package/dist/runtime/components/Integrations/NodeLogo.d.vue.ts +12 -0
- package/dist/runtime/components/Integrations/NodeLogo.vue +2 -30
- package/dist/runtime/components/Integrations/NodeLogo.vue.d.ts +12 -0
- package/dist/runtime/components/Integrations/NodeMark.d.vue.ts +15 -0
- package/dist/runtime/components/Integrations/NodeMark.vue +79 -0
- package/dist/runtime/components/Integrations/NodeMark.vue.d.ts +15 -0
- package/dist/runtime/components/Integrations/NodeOutputSchema.d.vue.ts +21 -0
- package/dist/runtime/components/Integrations/NodeOutputSchema.vue +43 -0
- package/dist/runtime/components/Integrations/NodeOutputSchema.vue.d.ts +21 -0
- package/dist/runtime/components/Integrations/NodePalette.vue +122 -55
- package/dist/runtime/components/Integrations/QueryBuilder.vue +46 -38
- package/dist/runtime/components/Integrations/RunActionsMenu.vue +38 -12
- package/dist/runtime/components/Integrations/RunDetailPanel.vue +114 -62
- package/dist/runtime/components/Integrations/RunPayloadInput.vue +27 -13
- package/dist/runtime/components/Integrations/RunResponseModal.vue +11 -10
- package/dist/runtime/components/Integrations/RunsTable.d.vue.ts +0 -1
- package/dist/runtime/components/Integrations/RunsTable.vue +38 -16
- package/dist/runtime/components/Integrations/RunsTable.vue.d.ts +0 -1
- package/dist/runtime/components/Integrations/SftpAccountImportDialog.vue +8 -7
- package/dist/runtime/components/Integrations/Sidebar.vue +14 -10
- package/dist/runtime/components/Integrations/StickyNoteView.d.vue.ts +14 -4
- package/dist/runtime/components/Integrations/StickyNoteView.vue +38 -23
- package/dist/runtime/components/Integrations/StickyNoteView.vue.d.ts +14 -4
- package/dist/runtime/components/Integrations/TemplateCard.d.vue.ts +18 -0
- package/dist/runtime/components/Integrations/TemplateCard.vue +119 -0
- package/dist/runtime/components/Integrations/TemplateCard.vue.d.ts +18 -0
- package/dist/runtime/components/Integrations/TemplateFlowPreview.d.vue.ts +2 -2
- package/dist/runtime/components/Integrations/TemplateFlowPreview.vue +16 -18
- package/dist/runtime/components/Integrations/TemplateFlowPreview.vue.d.ts +2 -2
- package/dist/runtime/components/Integrations/WorkflowContextMenu.vue +34 -33
- package/dist/runtime/components/Integrations/WorkflowCreateDialog.vue +52 -67
- package/dist/runtime/components/Integrations/WorkflowDuplicateDialog.vue +8 -7
- package/dist/runtime/components/Integrations/WorkflowEdgeView.d.vue.ts +18 -4
- package/dist/runtime/components/Integrations/WorkflowEdgeView.vue +29 -11
- package/dist/runtime/components/Integrations/WorkflowEdgeView.vue.d.ts +18 -4
- package/dist/runtime/components/Integrations/WorkflowEditor.d.vue.ts +20 -1
- package/dist/runtime/components/Integrations/WorkflowEditor.vue +240 -116
- package/dist/runtime/components/Integrations/WorkflowEditor.vue.d.ts +20 -1
- package/dist/runtime/components/Integrations/WorkflowHistoryPanel.vue +28 -43
- package/dist/runtime/components/Integrations/WorkflowIconPicker.d.vue.ts +19 -1
- package/dist/runtime/components/Integrations/WorkflowIconPicker.vue +17 -7
- package/dist/runtime/components/Integrations/WorkflowIconPicker.vue.d.ts +19 -1
- package/dist/runtime/components/Integrations/WorkflowNodeView.d.vue.ts +4 -0
- package/dist/runtime/components/Integrations/WorkflowNodeView.vue +198 -56
- package/dist/runtime/components/Integrations/WorkflowNodeView.vue.d.ts +4 -0
- package/dist/runtime/components/Integrations/WorkflowRunReplayPanel.vue +56 -59
- package/dist/runtime/components/Integrations/WorkflowSettingsDialog.vue +28 -21
- package/dist/runtime/components/Integrations/WorkflowTagsInput.d.vue.ts +1 -0
- package/dist/runtime/components/Integrations/WorkflowTagsInput.vue +15 -9
- package/dist/runtime/components/Integrations/WorkflowTagsInput.vue.d.ts +1 -0
- package/dist/runtime/components/Integrations/WorkflowTriggerView.vue +15 -10
- package/dist/runtime/components/Integrations/WorkflowVariablesDialog.vue +50 -36
- package/dist/runtime/components/Integrations/WorkflowsTable.vue +126 -48
- package/dist/runtime/composables/integrationsApi.js +4 -2
- package/dist/runtime/composables/useCredentialTypes.js +8 -6
- package/dist/runtime/composables/useIntegrationsAudit.js +4 -3
- package/dist/runtime/composables/useIntegrationsCredentials.js +10 -8
- package/dist/runtime/composables/useIntegrationsDeadLetters.js +7 -6
- package/dist/runtime/composables/useIntegrationsFocus.d.ts +12 -0
- package/dist/runtime/composables/useIntegrationsLocale.d.ts +26 -0
- package/dist/runtime/composables/useIntegrationsLocale.js +8 -0
- package/dist/runtime/composables/useIntegrationsNodes.js +7 -5
- package/dist/runtime/composables/useIntegrationsRealtime.d.ts +149 -0
- package/dist/runtime/composables/useIntegrationsRealtime.js +80 -0
- package/dist/runtime/composables/useIntegrationsRevisions.js +9 -7
- package/dist/runtime/composables/useIntegrationsRuns.d.ts +1 -1
- package/dist/runtime/composables/useIntegrationsRuns.js +14 -7
- package/dist/runtime/composables/useIntegrationsSchedules.d.ts +4 -0
- package/dist/runtime/composables/useIntegrationsSchedules.js +10 -3
- package/dist/runtime/composables/useIntegrationsSecrets.js +14 -9
- package/dist/runtime/composables/useIntegrationsT.d.ts +35 -0
- package/dist/runtime/composables/useIntegrationsT.js +9 -0
- package/dist/runtime/composables/useIntegrationsTemplates.d.ts +3 -3
- package/dist/runtime/composables/useIntegrationsTemplates.js +28 -16
- package/dist/runtime/composables/useIntegrationsTriggers.js +10 -8
- package/dist/runtime/composables/useIntegrationsWorkflows.js +13 -7
- package/dist/runtime/composables/useLoadGate.d.ts +17 -0
- package/dist/runtime/composables/useLoadGate.js +8 -0
- package/dist/runtime/composables/useNodeConfigResolve.js +2 -1
- package/dist/runtime/data/branchReorder.d.ts +37 -0
- package/dist/runtime/data/branchReorder.js +71 -0
- package/dist/runtime/data/handleId.d.ts +23 -0
- package/dist/runtime/data/handleId.js +6 -0
- package/dist/runtime/data/integrationNodeCatalog.d.ts +41 -0
- package/dist/runtime/data/integrationNodeCatalog.js +14 -0
- package/dist/runtime/data/integrationTemplates.d.ts +48 -0
- package/dist/runtime/data/integrationTemplates.js +21 -0
- package/dist/runtime/data/integrationWorkflowSamples.js +1 -1
- package/dist/runtime/data/nodeIcons.d.ts +2 -0
- package/dist/runtime/data/nodeIcons.js +73 -0
- package/dist/runtime/data/nodeInputData.d.ts +16 -4
- package/dist/runtime/data/nodeInputData.js +4 -0
- package/dist/runtime/data/payloadForm.d.ts +9 -2
- package/dist/runtime/data/payloadForm.js +29 -4
- package/dist/runtime/data/portTone.d.ts +17 -0
- package/dist/runtime/data/portTone.js +7 -0
- package/dist/runtime/data/webhookAuth.d.ts +18 -0
- package/dist/runtime/data/webhookAuth.js +19 -0
- package/dist/runtime/data/workflowIconCatalog.js +4 -6
- package/dist/runtime/i18n/messages/de.json +1207 -0
- package/dist/runtime/i18n/messages/en.json +1207 -0
- package/dist/runtime/i18n/translate.d.ts +83 -0
- package/dist/runtime/i18n/translate.js +41 -0
- package/dist/runtime/pages/integrations/audit.vue +140 -37
- package/dist/runtime/pages/integrations/credentials/oauth-complete.vue +9 -6
- package/dist/runtime/pages/integrations/credentials.vue +180 -64
- package/dist/runtime/pages/integrations/index.vue +91 -92
- package/dist/runtime/pages/integrations/runs.vue +33 -21
- package/dist/runtime/pages/integrations/schedules.vue +88 -23
- package/dist/runtime/pages/integrations/secrets.vue +57 -35
- package/dist/runtime/pages/integrations/templates/[slug].vue +119 -88
- package/dist/runtime/pages/integrations/templates/index.vue +82 -103
- package/dist/runtime/pages/integrations/workflows/[id].vue +344 -91
- package/dist/runtime/pages/integrations/workflows/index.vue +23 -5
- package/dist/runtime/plugins/locale.d.ts +10 -0
- package/dist/runtime/plugins/locale.js +6 -0
- package/dist/runtime/plugins/realtime.client.d.ts +32 -0
- package/dist/runtime/plugins/realtime.client.js +4 -0
- package/dist/runtime/types/integrations.d.ts +65 -6
- package/dist/runtime/utils/apiError.d.ts +47 -0
- package/dist/runtime/utils/apiError.js +32 -0
- package/dist/runtime/utils/cockpitSources.d.ts +153 -0
- package/dist/runtime/utils/cockpitSources.js +14 -0
- package/dist/runtime/utils/credentialState.d.ts +24 -1
- package/dist/runtime/utils/credentialState.js +26 -11
- package/dist/runtime/utils/loadGate.d.ts +61 -0
- package/dist/runtime/utils/loadGate.js +22 -0
- package/dist/runtime/utils/localize.d.ts +46 -4
- package/dist/runtime/utils/localize.js +34 -7
- package/dist/runtime/utils/notify.d.ts +59 -0
- package/dist/runtime/utils/notify.js +21 -0
- package/dist/runtime/utils/pollBudget.d.ts +72 -0
- package/dist/runtime/utils/pollBudget.js +17 -0
- package/dist/runtime/utils/revenexxCredentialSource.d.ts +26 -0
- package/dist/runtime/utils/revenexxCredentialSource.js +8 -0
- package/dist/runtime/utils/schedule.d.ts +116 -0
- package/dist/runtime/utils/schedule.js +219 -0
- package/dist/runtime/utils/statusStyle.d.ts +26 -2
- package/dist/runtime/utils/statusStyle.js +9 -1
- package/dist/runtime/utils/validationFindings.d.ts +101 -0
- package/dist/runtime/utils/validationFindings.js +54 -0
- package/dist/runtime/utils/whenIdle.d.ts +42 -0
- package/dist/runtime/utils/whenIdle.js +12 -0
- package/dist/types.d.mts +2 -6
- package/package.json +10 -3
|
@@ -1,41 +1,48 @@
|
|
|
1
1
|
<script setup>
|
|
2
2
|
import { computed, ref, watch } from "vue";
|
|
3
|
-
import { Magnifier as Search,
|
|
3
|
+
import { Magnifier as Search, Layers, DangerTriangle as AlertTriangle } from "#solar-icons";
|
|
4
4
|
import { Input, Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@revenexx/studio";
|
|
5
|
-
import
|
|
6
|
-
import {
|
|
7
|
-
|
|
5
|
+
import TemplateCard from "../../../components/Integrations/TemplateCard.vue";
|
|
6
|
+
import { templateCategoryLabel, templatePackageLabel } from "../../../data/integrationTemplates";
|
|
7
|
+
const t = useIntegrationsT();
|
|
8
|
+
useHead({ title: () => t("integration.templates.gallery.head.title") });
|
|
8
9
|
const { templates, loading, error, loaded, load, ensureDetails } = useIntegrationsTemplates();
|
|
9
10
|
const { load: loadNodeCatalog } = useIntegrationsNodes();
|
|
10
11
|
onMounted(async () => {
|
|
11
12
|
await Promise.all([load(true), loadNodeCatalog()]);
|
|
12
|
-
void ensureDetails(templates.value.map((
|
|
13
|
+
void ensureDetails(templates.value.map((t2) => t2.slug));
|
|
13
14
|
});
|
|
14
15
|
const search = ref("");
|
|
15
16
|
const activeCategory = ref("all");
|
|
16
17
|
const activeLevel = ref("all");
|
|
17
18
|
const sortBy = ref("name");
|
|
18
|
-
const CATEGORY_LABELS = Object.fromEntries(
|
|
19
|
-
TEMPLATE_CATEGORIES.map((c) => [c.key, c.label])
|
|
20
|
-
);
|
|
21
|
-
function categoryLabel(key) {
|
|
22
|
-
return CATEGORY_LABELS[key] ?? key.charAt(0).toUpperCase() + key.slice(1);
|
|
23
|
-
}
|
|
24
19
|
const categories = computed(() => {
|
|
25
20
|
const counts = /* @__PURE__ */ new Map();
|
|
26
|
-
for (const
|
|
27
|
-
|
|
21
|
+
for (const t2 of templates.value) {
|
|
22
|
+
if (!t2.category) continue;
|
|
23
|
+
counts.set(t2.category, (counts.get(t2.category) ?? 0) + 1);
|
|
24
|
+
}
|
|
25
|
+
return [...counts.entries()].sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0])).map(([key, count]) => ({ key, label: templateCategoryLabel(key), count }));
|
|
28
26
|
});
|
|
29
27
|
const LEVELS = ["beginner", "intermediate", "advanced"];
|
|
30
28
|
const LEVEL_RANK = { beginner: 0, intermediate: 1, advanced: 2 };
|
|
29
|
+
const levelOptions = computed(() => LEVELS.map((l) => ({
|
|
30
|
+
value: l,
|
|
31
|
+
label: t(`integration.templates.gallery.filter.level.${l}`)
|
|
32
|
+
})));
|
|
33
|
+
const sortOptions = computed(() => [
|
|
34
|
+
{ value: "name", label: t("integration.templates.gallery.sort.name") },
|
|
35
|
+
{ value: "level", label: t("integration.templates.gallery.sort.level") },
|
|
36
|
+
{ value: "nodes", label: t("integration.templates.gallery.sort.nodes") }
|
|
37
|
+
]);
|
|
31
38
|
const filtered = computed(() => {
|
|
32
39
|
const q = search.value.trim().toLowerCase();
|
|
33
40
|
let items = templates.value;
|
|
34
|
-
if (activeCategory.value !== "all") items = items.filter((
|
|
35
|
-
if (activeLevel.value !== "all") items = items.filter((
|
|
41
|
+
if (activeCategory.value !== "all") items = items.filter((t2) => t2.category === activeCategory.value);
|
|
42
|
+
if (activeLevel.value !== "all") items = items.filter((t2) => t2.level === activeLevel.value);
|
|
36
43
|
if (q) {
|
|
37
44
|
items = items.filter(
|
|
38
|
-
(
|
|
45
|
+
(t2) => t2.name.toLowerCase().includes(q) || t2.description.toLowerCase().includes(q) || t2.vendors.some((v) => v.toLowerCase().includes(q)) || (templatePackageLabel(t2) ?? "").toLowerCase().includes(q) || t2.industries.some((i) => i.toLowerCase().includes(q))
|
|
39
46
|
);
|
|
40
47
|
}
|
|
41
48
|
const sorted = [...items];
|
|
@@ -45,23 +52,10 @@ const filtered = computed(() => {
|
|
|
45
52
|
return sorted;
|
|
46
53
|
});
|
|
47
54
|
watch(() => templates.value.length, () => {
|
|
48
|
-
if (templates.value.length) void ensureDetails(templates.value.map((
|
|
55
|
+
if (templates.value.length) void ensureDetails(templates.value.map((t2) => t2.slug));
|
|
49
56
|
});
|
|
50
|
-
const
|
|
51
|
-
const
|
|
52
|
-
function levelDot(level) {
|
|
53
|
-
return level === "beginner" ? "bg-emerald-500" : level === "intermediate" ? "bg-amber-500" : "bg-rose-500";
|
|
54
|
-
}
|
|
55
|
-
function bannerFor(tpl) {
|
|
56
|
-
return tpl.images.find((i) => i.category === "banner")?.src ?? null;
|
|
57
|
-
}
|
|
58
|
-
function packageLabel(tpl) {
|
|
59
|
-
const pkg = tpl.package;
|
|
60
|
-
if (!pkg) return null;
|
|
61
|
-
const tail = (pkg.name.split("/").pop() ?? pkg.name).replace(/^integrations-nodes-/, "");
|
|
62
|
-
if (tail === "core") return null;
|
|
63
|
-
return pkg.label ?? tail.split("-").map((s) => s.charAt(0).toUpperCase() + s.slice(1)).join(" ");
|
|
64
|
-
}
|
|
57
|
+
const awaitingCatalogue = computed(() => loading.value && !templates.value.length);
|
|
58
|
+
const statusText = computed(() => awaitingCatalogue.value ? t("integration.templates.gallery.state.loading") : "");
|
|
65
59
|
</script>
|
|
66
60
|
|
|
67
61
|
<template>
|
|
@@ -69,8 +63,8 @@ function packageLabel(tpl) {
|
|
|
69
63
|
<!-- The toolbar lives in the header's #tabs slot: PageHeader is the
|
|
70
64
|
sticky element (top-0 z-20), so filters + search stay pinned. -->
|
|
71
65
|
<PageHeader
|
|
72
|
-
title="
|
|
73
|
-
description="
|
|
66
|
+
:title="t('integration.templates.gallery.header.title')"
|
|
67
|
+
:description="t('integration.templates.gallery.header.description')"
|
|
74
68
|
>
|
|
75
69
|
<template #tabs>
|
|
76
70
|
<div class="-mx-3 w-[calc(100%+1.5rem)] flex items-center gap-3 border-t border-black/[0.06] px-4 pt-2.5 pb-1">
|
|
@@ -81,7 +75,7 @@ function packageLabel(tpl) {
|
|
|
81
75
|
:class="activeCategory === 'all' ? 'border-primary/30 bg-primary/10 text-primary font-medium' : 'border-black/[0.08] text-muted-foreground hover:border-black/20'"
|
|
82
76
|
@click="activeCategory = 'all'"
|
|
83
77
|
>
|
|
84
|
-
|
|
78
|
+
{{ t("integration.templates.gallery.filter.all") }} <span class="opacity-70">({{ templates.length }})</span>
|
|
85
79
|
</button>
|
|
86
80
|
<button
|
|
87
81
|
v-for="cat in categories"
|
|
@@ -96,35 +90,58 @@ function packageLabel(tpl) {
|
|
|
96
90
|
</div>
|
|
97
91
|
|
|
98
92
|
<Select v-model="activeLevel">
|
|
99
|
-
<SelectTrigger class="h-7 data-[size=default]:h-7 min-h-0 py-0 w-36 rounded-full bg-white text-sm">
|
|
93
|
+
<SelectTrigger :aria-label="t('integration.templates.gallery.filter.levelAria')" class="h-7 data-[size=default]:h-7 min-h-0 py-0 w-36 rounded-full bg-white text-sm">
|
|
100
94
|
<SelectValue />
|
|
101
95
|
</SelectTrigger>
|
|
102
96
|
<SelectContent>
|
|
103
|
-
<SelectItem value="all">
|
|
104
|
-
<SelectItem v-for="
|
|
97
|
+
<SelectItem value="all">{{ t("integration.templates.gallery.filter.levelAllOption") }}</SelectItem>
|
|
98
|
+
<SelectItem v-for="opt in levelOptions" :key="opt.value" :value="opt.value">{{ opt.label }}</SelectItem>
|
|
105
99
|
</SelectContent>
|
|
106
100
|
</Select>
|
|
107
101
|
<Select v-model="sortBy">
|
|
108
|
-
<SelectTrigger class="h-7 data-[size=default]:h-7 min-h-0 py-0 w-40 rounded-full bg-white text-sm">
|
|
102
|
+
<SelectTrigger :aria-label="t('integration.templates.gallery.sort.aria')" class="h-7 data-[size=default]:h-7 min-h-0 py-0 w-40 rounded-full bg-white text-sm">
|
|
109
103
|
<SelectValue />
|
|
110
104
|
</SelectTrigger>
|
|
111
105
|
<SelectContent>
|
|
112
|
-
<SelectItem value="
|
|
113
|
-
<SelectItem value="level">Sort: Simplest first</SelectItem>
|
|
114
|
-
<SelectItem value="nodes">Sort: Most steps</SelectItem>
|
|
106
|
+
<SelectItem v-for="opt in sortOptions" :key="opt.value" :value="opt.value">{{ opt.label }}</SelectItem>
|
|
115
107
|
</SelectContent>
|
|
116
108
|
</Select>
|
|
117
109
|
<div class="relative w-60">
|
|
118
110
|
<Search :size="12" class="absolute left-2 top-1/2 -translate-y-1/2 text-muted-foreground" />
|
|
119
|
-
<Input v-model="search"
|
|
111
|
+
<Input v-model="search" :aria-label="t('integration.templates.gallery.search.aria')" :placeholder="t('integration.templates.gallery.search.placeholder')" class="h-7 min-h-0 py-0 rounded-full pl-7 text-sm bg-white" />
|
|
120
112
|
</div>
|
|
121
113
|
</div>
|
|
122
114
|
</template>
|
|
123
115
|
</PageHeader>
|
|
124
116
|
|
|
125
117
|
<div class="p-4">
|
|
126
|
-
<!--
|
|
127
|
-
|
|
118
|
+
<!-- What the page is doing, in words (specs/templates.md AC-9).
|
|
119
|
+
The shimmer says "still working" only to somebody who can see it, and
|
|
120
|
+
six blank tiles carry no text, no name and no state — so waiting and
|
|
121
|
+
"nothing is registered" were the same silence, which are the two states
|
|
122
|
+
that ask for opposite reactions.
|
|
123
|
+
|
|
124
|
+
The region is OUTSIDE the v-if chain below and always in the DOM, with
|
|
125
|
+
only its text changing. A live region that appears together with its
|
|
126
|
+
content is a region assistive technology may never have been watching:
|
|
127
|
+
what is announced is a change inside an existing region.
|
|
128
|
+
|
|
129
|
+
Named, because it is not the only `role="status"` on this page: every
|
|
130
|
+
card draws one while its flow miniature loads ("Loading the flow"). Two
|
|
131
|
+
unnamed status regions on one page are two things a reader — and a test —
|
|
132
|
+
cannot tell apart, and this one is about the gallery rather than a card. -->
|
|
133
|
+
<p role="status" aria-live="polite" :aria-label="t('integration.templates.gallery.state.regionLabel')" class="sr-only">{{ statusText }}</p>
|
|
134
|
+
|
|
135
|
+
<!-- The placeholders are taken out of the accessibility tree rather than
|
|
136
|
+
marked busy. `aria-busy` only asks assistive technology to defer
|
|
137
|
+
exposing changes, and on a role-less div that is not a live region NVDA
|
|
138
|
+
and JAWS effectively ignore it — it would have expressed the state
|
|
139
|
+
without keeping anything from being walked. A placeholder carries no
|
|
140
|
+
information, so there is nothing to lose by removing it, and the region
|
|
141
|
+
above says in words what this area means. Which is also why the named
|
|
142
|
+
"Templates" group below is absent while this branch renders: the set of
|
|
143
|
+
cards does not exist yet, rather than existing and being empty. -->
|
|
144
|
+
<div v-if="awaitingCatalogue" aria-hidden="true" class="grid gap-3 sm:grid-cols-2 xl:grid-cols-3">
|
|
128
145
|
<div v-for="n in 6" :key="`sk-${n}`" class="rounded-lg border border-black/[0.06] bg-white p-0 overflow-hidden">
|
|
129
146
|
<div class="h-36 bg-black/[0.04] animate-pulse" />
|
|
130
147
|
<div class="p-4 space-y-2">
|
|
@@ -135,11 +152,18 @@ function packageLabel(tpl) {
|
|
|
135
152
|
</div>
|
|
136
153
|
</div>
|
|
137
154
|
|
|
138
|
-
<!-- Fetch failed
|
|
139
|
-
|
|
155
|
+
<!-- Fetch failed. `role="alert"` because this block is how the wait ends for
|
|
156
|
+
somebody who was told through the region above to hold on: it is
|
|
157
|
+
announced when it is inserted, so the sequence is "Loading templates…"
|
|
158
|
+
then the reason, rather than "Loading templates…" then silence. An alert
|
|
159
|
+
rather than a copy of the text in the status region, which would put the
|
|
160
|
+
same sentence in the page twice and read it out twice to anybody walking
|
|
161
|
+
it. The other two empty states are not alerts: nothing had spoken before
|
|
162
|
+
them, and their copy is where the reader is going anyway. -->
|
|
163
|
+
<div v-else-if="error && !templates.length" role="alert" class="rounded-lg border border-rose-500/20 bg-rose-500/5 p-4 text-sm text-rose-700 flex items-start gap-2">
|
|
140
164
|
<AlertTriangle :size="15" class="mt-0.5 shrink-0" />
|
|
141
165
|
<div>
|
|
142
|
-
<div class="font-medium">
|
|
166
|
+
<div class="font-medium">{{ t("integration.templates.gallery.error.title") }}</div>
|
|
143
167
|
<div class="text-rose-700/80">{{ error }}</div>
|
|
144
168
|
</div>
|
|
145
169
|
</div>
|
|
@@ -149,71 +173,26 @@ function packageLabel(tpl) {
|
|
|
149
173
|
<span class="mx-auto mb-3 flex size-10 items-center justify-center rounded-lg bg-slate-100 text-slate-400">
|
|
150
174
|
<Layers :size="18" />
|
|
151
175
|
</span>
|
|
152
|
-
<div class="text-sm font-semibold text-foreground">
|
|
176
|
+
<div class="text-sm font-semibold text-foreground">{{ t("integration.templates.gallery.empty.registered.title") }}</div>
|
|
153
177
|
<p class="mx-auto mt-1 max-w-sm text-sm text-muted-foreground">
|
|
154
|
-
|
|
178
|
+
{{ t("integration.templates.gallery.empty.registered.description") }}
|
|
155
179
|
</p>
|
|
156
180
|
</div>
|
|
157
181
|
|
|
158
|
-
<!--
|
|
159
|
-
|
|
160
|
-
|
|
182
|
+
<!-- The group is named so the cards can be addressed as a set; a card names
|
|
183
|
+
itself from what it shows, which TemplateCard says more about. -->
|
|
184
|
+
<div v-else role="group" :aria-label="t('integration.templates.gallery.cards.ariaLabel')" class="grid gap-3 sm:grid-cols-2 xl:grid-cols-3">
|
|
185
|
+
<TemplateCard
|
|
161
186
|
v-for="tpl in filtered"
|
|
162
187
|
:key="tpl.slug"
|
|
188
|
+
:template="tpl"
|
|
189
|
+
density="full"
|
|
163
190
|
:to="`/integrations/templates/${tpl.slug}`"
|
|
164
|
-
|
|
165
|
-
>
|
|
166
|
-
<!-- Live workflow preview (or the registry banner, when one ships) -->
|
|
167
|
-
<div class="relative h-36 shrink-0 border-b border-black/[0.06] bg-slate-50/70 [background-image:radial-gradient(circle,rgba(15,23,42,0.07)_1px,transparent_1px)] [background-size:14px_14px]">
|
|
168
|
-
<img
|
|
169
|
-
v-if="bannerFor(tpl)"
|
|
170
|
-
:src="bannerFor(tpl)"
|
|
171
|
-
alt=""
|
|
172
|
-
class="h-full w-full object-cover"
|
|
173
|
-
>
|
|
174
|
-
<TemplateFlowPreview v-else-if="tpl.detailLoaded" :blob="tpl.blob" :height="144" class="px-2" />
|
|
175
|
-
<div v-else class="flex h-full items-center justify-center">
|
|
176
|
-
<Loader2 :size="16" class="animate-spin text-muted-foreground/50" />
|
|
177
|
-
</div>
|
|
178
|
-
<div class="absolute left-2 top-2 flex items-center gap-1.5">
|
|
179
|
-
<span class="inline-flex items-center whitespace-nowrap rounded-full bg-slate-100 px-2 py-0.5 uppercase text-xs font-medium text-slate-700">
|
|
180
|
-
{{ categoryLabel(tpl.category) }}
|
|
181
|
-
</span>
|
|
182
|
-
<span
|
|
183
|
-
v-if="packageLabel(tpl)"
|
|
184
|
-
class="inline-flex items-center whitespace-nowrap rounded-full bg-violet-100 px-2 py-0.5 uppercase text-xs font-medium text-violet-700"
|
|
185
|
-
>
|
|
186
|
-
{{ packageLabel(tpl) }}
|
|
187
|
-
</span>
|
|
188
|
-
</div>
|
|
189
|
-
</div>
|
|
190
|
-
|
|
191
|
-
<div class="flex min-h-0 flex-1 flex-col p-4">
|
|
192
|
-
<div class="text-sm font-semibold text-foreground line-clamp-1">{{ tpl.name }}</div>
|
|
193
|
-
<p class="mt-1 flex-1 text-sm text-muted-foreground line-clamp-2">{{ tpl.description }}</p>
|
|
194
|
-
|
|
195
|
-
<div class="mt-3 flex items-center gap-2 text-xs text-muted-foreground">
|
|
196
|
-
<span
|
|
197
|
-
v-for="t in tpl.triggerTypes"
|
|
198
|
-
:key="t"
|
|
199
|
-
class="inline-flex items-center gap-1 whitespace-nowrap rounded-full bg-slate-100 px-2 py-0.5 uppercase"
|
|
200
|
-
>
|
|
201
|
-
<component :is="TRIGGER_ICON[t] ?? Zap" :size="10" />
|
|
202
|
-
{{ TRIGGER_LABEL[t] ?? t }}
|
|
203
|
-
</span>
|
|
204
|
-
<span class="flex-1" />
|
|
205
|
-
<span class="inline-flex items-center gap-1 whitespace-nowrap rounded-full bg-slate-100 px-2 py-0.5 uppercase">
|
|
206
|
-
<span class="size-1.5 rounded-full" :class="levelDot(tpl.level)" />
|
|
207
|
-
{{ tpl.level }}
|
|
208
|
-
</span>
|
|
209
|
-
<span v-if="tpl.publisher.kind !== 'revenexx'" class="inline-flex items-center rounded-full bg-slate-100 px-2 py-0.5 uppercase">{{ tpl.publisher.name }}</span>
|
|
210
|
-
</div>
|
|
211
|
-
</div>
|
|
212
|
-
</NuxtLink>
|
|
191
|
+
/>
|
|
213
192
|
</div>
|
|
214
193
|
|
|
215
194
|
<div v-if="templates.length && !filtered.length" class="mt-4 rounded-lg border border-black/[0.06] bg-white p-8 text-center text-sm text-muted-foreground">
|
|
216
|
-
|
|
195
|
+
{{ t("integration.templates.gallery.empty.filtered") }}
|
|
217
196
|
</div>
|
|
218
197
|
</div>
|
|
219
198
|
</IntegrationsLayout>
|