@revenexx/studio-integrations 0.7.0 → 1.0.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 +416 -0
- package/dist/module.json +1 -1
- package/dist/module.mjs +2 -1
- package/dist/runtime/components/Integrations/CredentialCreateDialog.vue +4 -1
- package/dist/runtime/components/Integrations/CredentialDetailDialog.vue +8 -2
- package/dist/runtime/components/Integrations/ExpressionInput.vue +5 -0
- package/dist/runtime/components/Integrations/NodeConfigDynamicField.d.vue.ts +3 -0
- package/dist/runtime/components/Integrations/NodeConfigDynamicField.vue +16 -3
- package/dist/runtime/components/Integrations/NodeConfigDynamicField.vue.d.ts +3 -0
- package/dist/runtime/components/Integrations/NodeConfigDynamicSchema.vue +1 -0
- package/dist/runtime/components/Integrations/NodeConfigField.d.vue.ts +3 -0
- package/dist/runtime/components/Integrations/NodeConfigField.vue +2 -0
- package/dist/runtime/components/Integrations/NodeConfigField.vue.d.ts +3 -0
- package/dist/runtime/components/Integrations/NodeInspector.d.vue.ts +2 -2
- package/dist/runtime/components/Integrations/NodeInspector.vue +95 -10
- package/dist/runtime/components/Integrations/NodeInspector.vue.d.ts +2 -2
- package/dist/runtime/components/Integrations/NodeOutputSchema.d.vue.ts +2 -1
- package/dist/runtime/components/Integrations/NodeOutputSchema.vue +34 -8
- package/dist/runtime/components/Integrations/NodeOutputSchema.vue.d.ts +2 -1
- package/dist/runtime/components/Integrations/NodePalette.vue +12 -8
- package/dist/runtime/components/Integrations/StickyNoteView.vue +45 -11
- package/dist/runtime/components/Integrations/SurfaceCard.d.vue.ts +50 -0
- package/dist/runtime/components/Integrations/SurfaceCard.vue +55 -0
- package/dist/runtime/components/Integrations/SurfaceCard.vue.d.ts +50 -0
- package/dist/runtime/components/Integrations/TemplateCard.vue +3 -3
- package/dist/runtime/components/Integrations/TemplateFlowPreview.vue +12 -3
- package/dist/runtime/components/Integrations/WorkflowCreateDialog.vue +5 -4
- package/dist/runtime/components/Integrations/WorkflowEditor.d.vue.ts +16 -0
- package/dist/runtime/components/Integrations/WorkflowEditor.vue +52 -11
- package/dist/runtime/components/Integrations/WorkflowEditor.vue.d.ts +16 -0
- package/dist/runtime/components/Integrations/WorkflowHistoryPanel.d.vue.ts +6 -0
- package/dist/runtime/components/Integrations/WorkflowHistoryPanel.vue +4 -2
- package/dist/runtime/components/Integrations/WorkflowHistoryPanel.vue.d.ts +6 -0
- package/dist/runtime/components/Integrations/WorkflowNodeView.vue +59 -28
- package/dist/runtime/components/Integrations/WorkflowRunReplayPanel.vue +3 -4
- package/dist/runtime/components/Integrations/WorkflowSettingsDialog.d.vue.ts +16 -17
- package/dist/runtime/components/Integrations/WorkflowSettingsDialog.vue +37 -26
- package/dist/runtime/components/Integrations/WorkflowSettingsDialog.vue.d.ts +16 -17
- package/dist/runtime/components/Integrations/WorkflowTriggerView.vue +4 -5
- package/dist/runtime/components/Integrations/WorkflowVariablesDialog.vue +53 -14
- package/dist/runtime/composables/useIntegrationsCredentials.d.ts +11 -0
- package/dist/runtime/composables/useIntegrationsCredentials.js +2 -2
- package/dist/runtime/composables/useIntegrationsNodes.js +11 -1
- package/dist/runtime/composables/useIntegrationsSchedules.d.ts +3 -2
- package/dist/runtime/composables/useIntegrationsTriggers.d.ts +23 -3
- package/dist/runtime/composables/useIntegrationsTriggers.js +18 -7
- package/dist/runtime/composables/useIntegrationsWorkflows.d.ts +10 -1
- package/dist/runtime/composables/useIntegrationsWorkflows.js +25 -4
- package/dist/runtime/composables/useNodeConfigResolve.js +2 -1
- package/dist/runtime/data/integrationNodeCatalog.d.ts +22 -1
- package/dist/runtime/data/integrationNodeCatalog.js +8 -4
- package/dist/runtime/data/nodeInputData.d.ts +58 -1
- package/dist/runtime/data/nodeInputData.js +30 -5
- package/dist/runtime/data/triggerGlyphs.d.ts +47 -0
- package/dist/runtime/data/triggerGlyphs.js +11 -0
- package/dist/runtime/i18n/messages/de.json +20 -3
- package/dist/runtime/i18n/messages/en.json +20 -3
- package/dist/runtime/pages/integrations/credentials.vue +4 -1
- package/dist/runtime/pages/integrations/index.vue +16 -17
- package/dist/runtime/pages/integrations/templates/[slug].vue +13 -5
- package/dist/runtime/pages/integrations/workflows/[id].vue +193 -104
- package/dist/runtime/types/integrations.d.ts +25 -1
- package/dist/runtime/utils/cockpitSources.d.ts +22 -3
- package/dist/runtime/utils/credentialState.d.ts +6 -1
- package/dist/runtime/utils/credentialState.js +1 -1
- package/dist/runtime/utils/localize.d.ts +34 -0
- package/dist/runtime/utils/localize.js +22 -7
- package/dist/runtime/utils/pollBudget.d.ts +1 -1
- package/dist/runtime/utils/servedPaths.d.ts +22 -0
- package/dist/runtime/utils/servedPaths.js +3 -0
- package/package.json +1 -1
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
<script setup>
|
|
2
2
|
import { computed, nextTick, onMounted, provide, ref, watch } from "vue";
|
|
3
|
-
import { AddCircle as Plus, TrashBinMinimalistic as TrashBin
|
|
4
|
-
import {
|
|
3
|
+
import { AddCircle as Plus, Copy, Magnifier as Search, TrashBinMinimalistic as TrashBin } from "#solar-icons";
|
|
4
|
+
import { triggerGlyph as glyphForTrigger } from "../../data/triggerGlyphs";
|
|
5
|
+
import { ChevronDown, ChevronLeft, ChevronRight, Database, MessageSquareText, X } from "lucide-vue-next";
|
|
5
6
|
import { toast } from "vue-sonner";
|
|
6
7
|
import {
|
|
7
8
|
Button,
|
|
@@ -27,9 +28,10 @@ import {
|
|
|
27
28
|
ToggleGroupItem
|
|
28
29
|
} from "@revenexx/studio";
|
|
29
30
|
import EventTypeCombobox from "./EventTypeCombobox.vue";
|
|
30
|
-
import { findManifest, nodeCategoryLabel } from "../../data/integrationNodeCatalog";
|
|
31
|
+
import { findManifest, nodeCategoryLabel, isVersionExecutable, newestExecutableVersion } from "../../data/integrationNodeCatalog";
|
|
31
32
|
import {
|
|
32
33
|
collectUpstreamNodes,
|
|
34
|
+
inputPortsForManifest,
|
|
33
35
|
nodeOutputExpr,
|
|
34
36
|
portsForNode,
|
|
35
37
|
triggerPayloadExpr,
|
|
@@ -87,14 +89,21 @@ const manifest = computed(() => {
|
|
|
87
89
|
const wn = draftNode.value;
|
|
88
90
|
return wn ? findManifest(wn.nodeSlug, wn.nodeVersion) : null;
|
|
89
91
|
});
|
|
90
|
-
const
|
|
92
|
+
const versionExecutable = computed(() => {
|
|
93
|
+
const wn = draftNode.value;
|
|
94
|
+
return wn ? isVersionExecutable(wn.nodeSlug, wn.nodeVersion) : true;
|
|
95
|
+
});
|
|
96
|
+
const readyVersion = computed(() => {
|
|
97
|
+
const wn = draftNode.value;
|
|
98
|
+
return wn ? newestExecutableVersion(wn.nodeSlug) : null;
|
|
99
|
+
});
|
|
91
100
|
const TRIGGER_LABELS = computed(() => ({
|
|
92
101
|
manual: t("integration.nodeInspector.trigger.type.manual"),
|
|
93
102
|
schedule: t("integration.nodeInspector.trigger.type.schedule"),
|
|
94
103
|
webhook: t("integration.nodeInspector.trigger.type.webhook"),
|
|
95
104
|
event: t("integration.nodeInspector.trigger.type.event")
|
|
96
105
|
}));
|
|
97
|
-
const triggerGlyph = computed(() =>
|
|
106
|
+
const triggerGlyph = computed(() => glyphForTrigger(draftTrigger.value?.type));
|
|
98
107
|
const namePlaceholder = computed(() => {
|
|
99
108
|
if (draftNode.value) return resolveLocalized(manifest.value?.name) || draftNode.value.nodeSlug;
|
|
100
109
|
return t("integration.nodeInspector.trigger.placeholder", { type: TRIGGER_LABELS.value[draftTrigger.value?.type ?? "manual"] });
|
|
@@ -115,6 +124,7 @@ const nameValue = computed(() => {
|
|
|
115
124
|
const name = draft.value.name;
|
|
116
125
|
return resolveDisplayName(name);
|
|
117
126
|
});
|
|
127
|
+
const dialogTitle = computed(() => t("integration.nodeInspector.dialogTitle", { name: nameValue.value || namePlaceholder.value }));
|
|
118
128
|
function writeName(name) {
|
|
119
129
|
draft.value = { ...draft.value, name };
|
|
120
130
|
}
|
|
@@ -759,6 +769,7 @@ function navigate(offset) {
|
|
|
759
769
|
emit("navigate", target);
|
|
760
770
|
}
|
|
761
771
|
const outputView = ref("schema");
|
|
772
|
+
const selfInputs = computed(() => inputPortsForManifest(manifest.value));
|
|
762
773
|
const selfSample = computed(() => draftNode.value ? sampleFor(draftNode.value.id) : void 0);
|
|
763
774
|
const selfPorts = computed(() => draftNode.value ? withSampledFields(portsForNode(draftNode.value, manifest.value, props.dynamicOutputs?.[draftNode.value.id]), selfSample.value) : []);
|
|
764
775
|
const selfSampleJson = computed(() => selfSample.value ? JSON.stringify(selfSample.value, null, 2) : "");
|
|
@@ -867,6 +878,11 @@ async function runTestStep() {
|
|
|
867
878
|
<!-- Fills the editor's near-fullscreen config lightbox; the Dialog owns
|
|
868
879
|
width/height, border and the close control. -->
|
|
869
880
|
<div class="flex min-h-0 flex-1 flex-col bg-white">
|
|
881
|
+
<!-- The dialog's accessible name. Not drawn: the header below says the same
|
|
882
|
+
thing in the name field and the line under it, and says it better. -->
|
|
883
|
+
<DialogTitle class="sr-only">
|
|
884
|
+
{{ dialogTitle }}
|
|
885
|
+
</DialogTitle>
|
|
870
886
|
<!-- Header -->
|
|
871
887
|
<div class="flex shrink-0 items-center gap-3 border-b border-black/[0.06] px-4 py-3">
|
|
872
888
|
<div class="flex size-9 shrink-0 items-center justify-center rounded-md border border-black/[0.06] bg-slate-50 text-slate-600">
|
|
@@ -1032,6 +1048,36 @@ async function runTestStep() {
|
|
|
1032
1048
|
<p class="mt-0.5 text-xs leading-snug text-muted-foreground">
|
|
1033
1049
|
{{ t("integration.nodeInspector.input.description") }}
|
|
1034
1050
|
</p>
|
|
1051
|
+
<!-- What this node accepts, as opposed to the list below, which is what
|
|
1052
|
+
other nodes offer it. It sits in the header because somebody
|
|
1053
|
+
choosing a value is already reading here, and because the port
|
|
1054
|
+
belongs to the node being configured rather than to any row in the
|
|
1055
|
+
list (AC-20). -->
|
|
1056
|
+
<div v-if="isNode && selfInputs.length" class="mt-2 space-y-1 border-t border-black/[0.06] pt-2">
|
|
1057
|
+
<div v-for="p in selfInputs" :key="p.name" class="space-y-0.5">
|
|
1058
|
+
<div class="flex items-baseline gap-1.5">
|
|
1059
|
+
<!-- Not the uppercase treatment the section labels below use, and
|
|
1060
|
+
that is the point: NODES / TRIGGER / VARIABLES each head a list
|
|
1061
|
+
of values a click inserts, so they are a series of *sources*.
|
|
1062
|
+
This heads nothing and is not a source — it is a statement about
|
|
1063
|
+
the node being configured. Dressed as a fourth member of that
|
|
1064
|
+
series it told the reader there was a fourth place to take data
|
|
1065
|
+
from. -->
|
|
1066
|
+
<span
|
|
1067
|
+
class="text-xs font-medium text-muted-foreground"
|
|
1068
|
+
:class="selfInputs.length > 1 ? 'font-mono' : ''"
|
|
1069
|
+
>
|
|
1070
|
+
{{ selfInputs.length > 1 ? `${p.name}:` : t("integration.nodeInspector.input.expects.heading") }}
|
|
1071
|
+
</span>
|
|
1072
|
+
<span class="font-mono text-[10px] text-slate-500">
|
|
1073
|
+
{{ p.dataType }}<template v-if="p.dataType"> · </template>{{ p.required ? t("integration.nodeInspector.input.expects.required") : t("integration.nodeInspector.input.expects.optional") }}
|
|
1074
|
+
</span>
|
|
1075
|
+
</div>
|
|
1076
|
+
<p v-if="resolveLocalized(p.description)" class="text-xs leading-snug text-muted-foreground">
|
|
1077
|
+
{{ resolveLocalized(p.description) }}
|
|
1078
|
+
</p>
|
|
1079
|
+
</div>
|
|
1080
|
+
</div>
|
|
1035
1081
|
<div class="relative mt-2">
|
|
1036
1082
|
<Search :size="12" class="pointer-events-none absolute left-2 top-1/2 -translate-y-1/2 text-muted-foreground" />
|
|
1037
1083
|
<input
|
|
@@ -1098,7 +1144,30 @@ async function runTestStep() {
|
|
|
1098
1144
|
</span>
|
|
1099
1145
|
</button>
|
|
1100
1146
|
<div v-show="!sectionClosed(s.id)" class="space-y-2 border-t border-black/[0.06] px-2 py-2">
|
|
1101
|
-
|
|
1147
|
+
<!-- These ports belong to an UPSTREAM node, so the sentence is that
|
|
1148
|
+
node's. On a 300px column it is a tooltip rather than text: the
|
|
1149
|
+
values below are what this panel is for, and a sentence per port
|
|
1150
|
+
pushes them out of view. The readable copy is the Output tab
|
|
1151
|
+
(AC-19).
|
|
1152
|
+
|
|
1153
|
+
The title hangs on the port's whole block, not on its name row:
|
|
1154
|
+
that row is only drawn for a node with several ports or a
|
|
1155
|
+
branch/error one, so a single `default` port — which is most
|
|
1156
|
+
nodes, No Operation among them — would have had nowhere to carry
|
|
1157
|
+
it and the tooltip would have been dead in the commonest case.
|
|
1158
|
+
|
|
1159
|
+
What that buys is uneven, and it is the reason this is a
|
|
1160
|
+
convenience rather than a promise: each field row carries its own
|
|
1161
|
+
title (the expression to copy), and a child title wins, so the
|
|
1162
|
+
sentence is read by resting on the port's name or the space around
|
|
1163
|
+
its rows rather than on a row itself. Where a reader is owed the
|
|
1164
|
+
text outright, that is the Output tab. -->
|
|
1165
|
+
<div
|
|
1166
|
+
v-for="p in s.ports"
|
|
1167
|
+
:key="p.name"
|
|
1168
|
+
class="space-y-1 pl-0.5"
|
|
1169
|
+
:title="resolveLocalized(p.description) || void 0"
|
|
1170
|
+
>
|
|
1102
1171
|
<div v-if="s.ports.length > 1 || p.kind !== 'default'" class="flex items-center gap-1.5">
|
|
1103
1172
|
<span class="size-1.5 rounded-full" :class="portDotClass(p.kind, p.name)" />
|
|
1104
1173
|
<span class="text-[10px] font-medium uppercase tracking-wider text-muted-foreground">{{ p.name }}</span>
|
|
@@ -1207,6 +1276,19 @@ async function runTestStep() {
|
|
|
1207
1276
|
<!-- Same TabBar as the workflow page — one tab look everywhere. -->
|
|
1208
1277
|
<TabBar v-model="tab" :tabs="inspectorTabs" class="shrink-0 px-4" />
|
|
1209
1278
|
|
|
1279
|
+
<!-- The pinned version has no built code, so nothing here can be tested
|
|
1280
|
+
or resolved. Above the tabs' content rather than inside one of them:
|
|
1281
|
+
the state belongs to the node, not to a tab, and a reader who opens
|
|
1282
|
+
this node on Parameters would otherwise meet the card's mark outside
|
|
1283
|
+
and no word about it inside. Outside the scrolling area so it cannot
|
|
1284
|
+
be scrolled away from. -->
|
|
1285
|
+
<p
|
|
1286
|
+
v-if="isNode && !versionExecutable"
|
|
1287
|
+
class="mx-4 mb-1 shrink-0 rounded-md border border-rose-200 bg-rose-50 px-2.5 py-1.5 text-sm text-rose-700"
|
|
1288
|
+
>
|
|
1289
|
+
{{ readyVersion ? t("integration.nodeInspector.notice.versionNotReady.upgrade", { version: draftNode?.nodeVersion ?? "", ready: readyVersion }) : t("integration.nodeInspector.notice.versionNotReady.none", { version: draftNode?.nodeVersion ?? "" }) }}
|
|
1290
|
+
</p>
|
|
1291
|
+
|
|
1210
1292
|
<div
|
|
1211
1293
|
class="min-h-0 flex-1 overflow-y-auto p-4"
|
|
1212
1294
|
:class="dragActive ? 'chip-drag-live' : ''"
|
|
@@ -1654,6 +1736,7 @@ async function runTestStep() {
|
|
|
1654
1736
|
:version="draftNode.nodeVersion"
|
|
1655
1737
|
:secret-keys="secretKeys"
|
|
1656
1738
|
:static-keys="(manifest.config ?? []).map((f2) => f2.key)"
|
|
1739
|
+
:sibling-fields="manifest.config ?? []"
|
|
1657
1740
|
:depth="0"
|
|
1658
1741
|
:variables="variables"
|
|
1659
1742
|
:upstream-outputs="upstreamOutputs"
|
|
@@ -1693,6 +1776,7 @@ async function runTestStep() {
|
|
|
1693
1776
|
:version="draftNode.nodeVersion"
|
|
1694
1777
|
:secret-keys="secretKeys"
|
|
1695
1778
|
:static-keys="(manifest.config ?? []).map((f2) => f2.key)"
|
|
1779
|
+
:sibling-fields="manifest.config ?? []"
|
|
1696
1780
|
:depth="0"
|
|
1697
1781
|
:variables="variables"
|
|
1698
1782
|
:upstream-outputs="upstreamOutputs"
|
|
@@ -1734,6 +1818,7 @@ async function runTestStep() {
|
|
|
1734
1818
|
:version="draftNode.nodeVersion"
|
|
1735
1819
|
:secret-keys="secretKeys"
|
|
1736
1820
|
:static-keys="(manifest?.config ?? []).map((f2) => f2.key)"
|
|
1821
|
+
:sibling-fields="manifest?.config ?? []"
|
|
1737
1822
|
:depth="0"
|
|
1738
1823
|
:variables="variables"
|
|
1739
1824
|
:upstream-outputs="upstreamOutputs"
|
|
@@ -1878,8 +1963,8 @@ async function runTestStep() {
|
|
|
1878
1963
|
<Button
|
|
1879
1964
|
size="sm"
|
|
1880
1965
|
class="h-8"
|
|
1881
|
-
:disabled="testRunning"
|
|
1882
|
-
:title="t('integration.nodeInspector.output.test.runTitle')"
|
|
1966
|
+
:disabled="testRunning || !versionExecutable"
|
|
1967
|
+
:title="versionExecutable ? t('integration.nodeInspector.output.test.runTitle') : void 0"
|
|
1883
1968
|
@click="runTestStep"
|
|
1884
1969
|
>
|
|
1885
1970
|
{{ testRunning ? t("integration.nodeInspector.output.test.running") : t("integration.nodeInspector.output.test.action") }}
|
|
@@ -1892,7 +1977,7 @@ async function runTestStep() {
|
|
|
1892
1977
|
{{ testInputOpen ? t("integration.nodeInspector.output.test.hideInput") : t("integration.nodeInspector.output.test.editInput") }}
|
|
1893
1978
|
</button>
|
|
1894
1979
|
<span class="text-xs text-muted-foreground">
|
|
1895
|
-
{{ t("integration.nodeInspector.output.test.runHint") }}
|
|
1980
|
+
{{ versionExecutable ? t("integration.nodeInspector.output.test.runHint") : "" }}
|
|
1896
1981
|
</span>
|
|
1897
1982
|
</div>
|
|
1898
1983
|
<Textarea
|
|
@@ -1999,7 +2084,7 @@ async function runTestStep() {
|
|
|
1999
2084
|
{{ t("integration.common.action.cancel") }}
|
|
2000
2085
|
</Button>
|
|
2001
2086
|
<Button size="sm" class="h-8" @click="save">
|
|
2002
|
-
{{ t("integration.
|
|
2087
|
+
{{ t("integration.nodeInspector.footer.done") }}
|
|
2003
2088
|
</Button>
|
|
2004
2089
|
</div>
|
|
2005
2090
|
</div>
|
|
@@ -28,13 +28,13 @@ type __VLS_Props = {
|
|
|
28
28
|
refusedFields?: Record<string, string>;
|
|
29
29
|
};
|
|
30
30
|
declare const _default: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
31
|
-
close: () => any;
|
|
32
31
|
update: (next: WorkflowTrigger | WorkflowNode) => any;
|
|
32
|
+
close: () => any;
|
|
33
33
|
remove: () => any;
|
|
34
34
|
navigate: (id: string) => any;
|
|
35
35
|
}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
|
|
36
|
-
onClose?: (() => any) | undefined;
|
|
37
36
|
onUpdate?: ((next: WorkflowTrigger | WorkflowNode) => any) | undefined;
|
|
37
|
+
onClose?: (() => any) | undefined;
|
|
38
38
|
onRemove?: (() => any) | undefined;
|
|
39
39
|
onNavigate?: ((id: string) => any) | undefined;
|
|
40
40
|
}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
@@ -3,7 +3,8 @@ import type { PortEntry } from '../../data/nodeInputData.js';
|
|
|
3
3
|
* The ports a node hands downstream, with their declared fields and whatever
|
|
4
4
|
* sample value is known for each.
|
|
5
5
|
*
|
|
6
|
-
* Rendered from one place — the Output tab (specs/node-configuration.md AC-4
|
|
6
|
+
* Rendered from one place — the Output tab (specs/node-configuration.md AC-4,
|
|
7
|
+
* and AC-19 for the sentences the publisher wrote about a port and its fields). It
|
|
7
8
|
* keeps its own file for readability rather than for reuse: the schema list is
|
|
8
9
|
* a self-contained tree with its own type glyphs and sample formatting, and
|
|
9
10
|
* inlining it would bury the tab's own markup.
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
<script setup>
|
|
2
2
|
import { portDotClass } from "../../data/portTone";
|
|
3
|
-
|
|
3
|
+
import { resolveLocalized } from "../../utils/localize";
|
|
4
|
+
const props = defineProps({
|
|
4
5
|
ports: { type: Array, required: true },
|
|
5
6
|
valueAt: { type: Function, required: true }
|
|
6
7
|
});
|
|
@@ -16,6 +17,15 @@ const TYPE_GLYPHS = {
|
|
|
16
17
|
function typeGlyph(dataType) {
|
|
17
18
|
return dataType ? TYPE_GLYPHS[dataType] ?? null : null;
|
|
18
19
|
}
|
|
20
|
+
function portText(p) {
|
|
21
|
+
return resolveLocalized(p.description);
|
|
22
|
+
}
|
|
23
|
+
function fieldText(f) {
|
|
24
|
+
return resolveLocalized(f.description);
|
|
25
|
+
}
|
|
26
|
+
function saysNoFields(p) {
|
|
27
|
+
return !portText(p) || props.valueAt(p.name) !== null;
|
|
28
|
+
}
|
|
19
29
|
</script>
|
|
20
30
|
|
|
21
31
|
<template>
|
|
@@ -25,14 +35,30 @@ function typeGlyph(dataType) {
|
|
|
25
35
|
<span class="size-1.5 rounded-full" :class="portDotClass(p.kind, p.name)" />
|
|
26
36
|
<span class="text-[10px] font-medium uppercase tracking-wider text-muted-foreground">{{ p.name }}</span>
|
|
27
37
|
</div>
|
|
28
|
-
<
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
38
|
+
<p v-if="portText(p)" class="text-[11px] leading-snug text-muted-foreground">
|
|
39
|
+
{{ portText(p) }}
|
|
40
|
+
</p>
|
|
41
|
+
<!-- Two columns, sized to the widest field rather than split down the
|
|
42
|
+
middle: `max-content` puts every sentence one gap after the longest
|
|
43
|
+
name, so short names like `count` do not leave the reader crossing the
|
|
44
|
+
dialog to find what they mean, and the names still line up with each
|
|
45
|
+
other. The sample value is capped for the same reason — left free it
|
|
46
|
+
would widen the column and push the sentences back out. Both cells are
|
|
47
|
+
always emitted, or a field with nothing to say would slide the next
|
|
48
|
+
row's name into the sentence column. -->
|
|
49
|
+
<div v-if="p.fields.length" class="grid grid-cols-[max-content_minmax(0,1fr)] items-baseline gap-x-6 gap-y-0.5">
|
|
50
|
+
<template v-for="f in p.fields" :key="f.name">
|
|
51
|
+
<div class="flex min-w-0 items-baseline gap-1 font-mono text-[11px]">
|
|
52
|
+
<span v-if="typeGlyph(f.dataType)" class="shrink-0 font-semibold" :class="typeGlyph(f.dataType).cls">{{ typeGlyph(f.dataType).char }}</span>
|
|
53
|
+
<span class="shrink-0 text-slate-700">{{ f.name }}</span>
|
|
54
|
+
<span v-if="valueAt(p.name, f.name) !== null" class="max-w-56 truncate text-slate-400">: {{ valueAt(p.name, f.name) }}</span>
|
|
55
|
+
</div>
|
|
56
|
+
<p class="min-w-0 text-[11px] leading-snug text-muted-foreground">
|
|
57
|
+
{{ fieldText(f) }}
|
|
58
|
+
</p>
|
|
59
|
+
</template>
|
|
34
60
|
</div>
|
|
35
|
-
<p v-else class="text-[11px] leading-snug text-muted-foreground">
|
|
61
|
+
<p v-else-if="saysNoFields(p)" class="text-[11px] leading-snug text-muted-foreground">
|
|
36
62
|
{{ t("integration.nodeInspector.output.schema.noFields") }}<template v-if="valueAt(p.name) !== null">{{ t("integration.nodeInspector.output.schema.sampledSuffix") }} <span class="font-mono">{{ valueAt(p.name) }}</span></template>.
|
|
37
63
|
</p>
|
|
38
64
|
</div>
|
|
@@ -3,7 +3,8 @@ import type { PortEntry } from '../../data/nodeInputData.js';
|
|
|
3
3
|
* The ports a node hands downstream, with their declared fields and whatever
|
|
4
4
|
* sample value is known for each.
|
|
5
5
|
*
|
|
6
|
-
* Rendered from one place — the Output tab (specs/node-configuration.md AC-4
|
|
6
|
+
* Rendered from one place — the Output tab (specs/node-configuration.md AC-4,
|
|
7
|
+
* and AC-19 for the sentences the publisher wrote about a port and its fields). It
|
|
7
8
|
* keeps its own file for readability rather than for reuse: the schema list is
|
|
8
9
|
* a self-contained tree with its own type glyphs and sample formatting, and
|
|
9
10
|
* inlining it would bury the tab's own markup.
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
<script setup>
|
|
2
2
|
import { computed, ref, watch } from "vue";
|
|
3
|
+
import { AltArrowLeft as ArrowLeft, Bolt as Zap, Magnifier as Search } from "#solar-icons";
|
|
4
|
+
import { triggerGlyph } from "../../data/triggerGlyphs";
|
|
3
5
|
import * as Lucide from "lucide-vue-next";
|
|
4
6
|
import { INTEGRATION_NODE_CATALOG, NODE_CATEGORIES, TRIGGER_TEMPLATES } from "../../data/integrationNodeCatalog";
|
|
5
|
-
import { nodeIcon } from "../../data/nodeIcons";
|
|
6
7
|
import { resolveLocalized } from "../../utils/localize";
|
|
7
8
|
const { load: loadNodes } = useIntegrationsNodes();
|
|
8
9
|
onMounted(() => {
|
|
@@ -13,7 +14,6 @@ const emit = defineEmits(["insert"]);
|
|
|
13
14
|
const search = ref("");
|
|
14
15
|
const searchInput = ref(null);
|
|
15
16
|
defineExpose({ focusSearch: () => searchInput.value?.focus() });
|
|
16
|
-
const iconFor = nodeIcon;
|
|
17
17
|
const CORE_GROUP_BY_CATEGORY = {
|
|
18
18
|
control: "Flow",
|
|
19
19
|
transform: "Data",
|
|
@@ -76,11 +76,14 @@ function isIntegration(m) {
|
|
|
76
76
|
function makeGroup(label) {
|
|
77
77
|
return { label, children: /* @__PURE__ */ new Map(), leaves: [], count: 0 };
|
|
78
78
|
}
|
|
79
|
+
function isExecutable(m) {
|
|
80
|
+
return m.__hasBundle !== false;
|
|
81
|
+
}
|
|
79
82
|
const latestManifests = computed(() => {
|
|
80
83
|
const seen = /* @__PURE__ */ new Set();
|
|
81
84
|
const out = [];
|
|
82
85
|
for (const m of INTEGRATION_NODE_CATALOG) {
|
|
83
|
-
if (seen.has(m.slug)) continue;
|
|
86
|
+
if (seen.has(m.slug) || !isExecutable(m)) continue;
|
|
84
87
|
seen.add(m.slug);
|
|
85
88
|
out.push(m);
|
|
86
89
|
}
|
|
@@ -89,6 +92,7 @@ const latestManifests = computed(() => {
|
|
|
89
92
|
const versionsBySlug = computed(() => {
|
|
90
93
|
const map = /* @__PURE__ */ new Map();
|
|
91
94
|
for (const m of INTEGRATION_NODE_CATALOG) {
|
|
95
|
+
if (!isExecutable(m)) continue;
|
|
92
96
|
const list = map.get(m.slug);
|
|
93
97
|
if (list) list.push(m);
|
|
94
98
|
else map.set(m.slug, [m]);
|
|
@@ -282,14 +286,14 @@ const searching = computed(() => search.value.trim().length > 0);
|
|
|
282
286
|
:title="t('integration.canvas.palette.action.back')"
|
|
283
287
|
@click="popLevel"
|
|
284
288
|
>
|
|
285
|
-
<
|
|
289
|
+
<ArrowLeft :size="14" />
|
|
286
290
|
</button>
|
|
287
291
|
<div class="min-w-0 flex-1 truncate text-mono text-muted-foreground">
|
|
288
292
|
{{ levelTitle }}
|
|
289
293
|
</div>
|
|
290
294
|
</div>
|
|
291
295
|
<div class="relative">
|
|
292
|
-
<
|
|
296
|
+
<Search :size="12" class="absolute left-2 top-1/2 -translate-y-1/2 text-muted-foreground" />
|
|
293
297
|
<input
|
|
294
298
|
ref="searchInput"
|
|
295
299
|
v-model="search"
|
|
@@ -333,7 +337,7 @@ const searching = computed(() => search.value.trim().length > 0);
|
|
|
333
337
|
@click="insertTrigger(tpl)"
|
|
334
338
|
>
|
|
335
339
|
<span class="flex size-6 shrink-0 items-center justify-center rounded-md bg-emerald-100 text-emerald-600">
|
|
336
|
-
<component :is="
|
|
340
|
+
<component :is="triggerGlyph(tpl.type)" :size="13" />
|
|
337
341
|
</span>
|
|
338
342
|
<div class="min-w-0">
|
|
339
343
|
<div class="truncate text-sm font-medium">{{ resolveLocalized(tpl.name) }}</div>
|
|
@@ -397,7 +401,7 @@ const searching = computed(() => search.value.trim().length > 0);
|
|
|
397
401
|
@click="insertTrigger(tpl)"
|
|
398
402
|
>
|
|
399
403
|
<span class="flex size-6 shrink-0 items-center justify-center rounded-md bg-emerald-100 text-emerald-600">
|
|
400
|
-
<component :is="
|
|
404
|
+
<component :is="triggerGlyph(tpl.type)" :size="13" />
|
|
401
405
|
</span>
|
|
402
406
|
<div class="min-w-0">
|
|
403
407
|
<div class="truncate text-sm font-medium">{{ resolveLocalized(tpl.name) }}</div>
|
|
@@ -417,7 +421,7 @@ const searching = computed(() => search.value.trim().length > 0);
|
|
|
417
421
|
@click="pushPath(['__triggers'])"
|
|
418
422
|
>
|
|
419
423
|
<span class="flex size-6 shrink-0 items-center justify-center rounded-md bg-slate-100 text-emerald-600/80">
|
|
420
|
-
<
|
|
424
|
+
<Zap :size="13" />
|
|
421
425
|
</span>
|
|
422
426
|
<span class="min-w-0 flex-1 truncate text-sm font-medium">{{ t("integration.canvas.palette.group.triggers") }}</span>
|
|
423
427
|
<span class="text-sm tabular-nums text-muted-foreground">{{ TRIGGER_TEMPLATES.length }}</span>
|
|
@@ -100,24 +100,58 @@ function onHoverLeave() {
|
|
|
100
100
|
}
|
|
101
101
|
const toolbarVisible = computed(() => editable.value && !editing.value && (hovered.value || !!props.selected));
|
|
102
102
|
function escapeHtml(s) {
|
|
103
|
-
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
103
|
+
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
104
104
|
}
|
|
105
105
|
function inline(s) {
|
|
106
106
|
return s.replace(/`([^`\n]+)`/g, '<code class="rounded bg-black/[0.07] px-1 py-px font-mono text-[0.85em]">$1</code>').replace(/\[([^\]]+)\]\((https?:\/\/[^\s)]+)\)/g, '<a href="$2" target="_blank" rel="noopener noreferrer" class="font-medium underline decoration-current/40 underline-offset-2 hover:decoration-current">$1</a>').replace(/\*\*([^*]+)\*\*/g, "<strong>$1</strong>").replace(/(^|[^*])\*([^*\n]+)\*(?!\*)/g, "$1<em>$2</em>");
|
|
107
107
|
}
|
|
108
|
+
const NOTE_HEADINGS = [
|
|
109
|
+
{ tag: "h2", cls: "m-0 text-base font-semibold" },
|
|
110
|
+
{ tag: "h3", cls: "m-0 text-sm font-semibold" },
|
|
111
|
+
{ tag: "h4", cls: "m-0 text-sm font-medium" }
|
|
112
|
+
];
|
|
113
|
+
const HEADING_RE = /^(#{1,3})\s+(.+)$/;
|
|
114
|
+
const UL_RE = /^[-*]\s+(.+)$/;
|
|
115
|
+
const OL_RE = /^(\d+)\.\s+(.+)$/;
|
|
116
|
+
function renderList(tag, items) {
|
|
117
|
+
const li = items.map(({ marker, text }) => {
|
|
118
|
+
const numeric = tag === "ol" ? " tabular-nums" : "";
|
|
119
|
+
const hidden = tag === "ul" ? ' aria-hidden="true"' : "";
|
|
120
|
+
const ordinal = tag === "ol" ? ` value="${Number.parseInt(marker, 10)}"` : "";
|
|
121
|
+
return `<li${ordinal} class="flex gap-1.5"><span${hidden} class="select-none${numeric}">${marker}</span><span class="min-w-0">${text}</span></li>`;
|
|
122
|
+
}).join("");
|
|
123
|
+
return `<${tag} role="list" class="m-0 list-none p-0 pl-1">${li}</${tag}>`;
|
|
124
|
+
}
|
|
108
125
|
function renderMarkdownLite(src) {
|
|
109
|
-
|
|
110
|
-
|
|
126
|
+
const lines = escapeHtml(src).split("\n");
|
|
127
|
+
const out = [];
|
|
128
|
+
let i = 0;
|
|
129
|
+
while (i < lines.length) {
|
|
130
|
+
const line = lines[i] ?? "";
|
|
131
|
+
const h = HEADING_RE.exec(line);
|
|
111
132
|
if (h) {
|
|
112
|
-
const cls = h[1]
|
|
113
|
-
|
|
133
|
+
const { tag, cls } = NOTE_HEADINGS[(h[1] ?? "#").length - 1] ?? NOTE_HEADINGS[2];
|
|
134
|
+
out.push(`<${tag} class="${cls}">${inline(h[2] ?? "")}</${tag}>`);
|
|
135
|
+
i++;
|
|
136
|
+
continue;
|
|
114
137
|
}
|
|
115
|
-
const
|
|
116
|
-
if (
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
138
|
+
const listRe = UL_RE.test(line) ? UL_RE : OL_RE.test(line) ? OL_RE : null;
|
|
139
|
+
if (listRe) {
|
|
140
|
+
const tag = listRe === UL_RE ? "ul" : "ol";
|
|
141
|
+
const items = [];
|
|
142
|
+
while (i < lines.length) {
|
|
143
|
+
const m = listRe.exec(lines[i] ?? "");
|
|
144
|
+
if (!m) break;
|
|
145
|
+
items.push({ marker: tag === "ul" ? "\u2022" : `${m[1]}.`, text: inline(m[m.length - 1] ?? "") });
|
|
146
|
+
i++;
|
|
147
|
+
}
|
|
148
|
+
out.push(renderList(tag, items));
|
|
149
|
+
continue;
|
|
150
|
+
}
|
|
151
|
+
out.push(line ? `<div>${inline(line)}</div>` : "<div> </div>");
|
|
152
|
+
i++;
|
|
153
|
+
}
|
|
154
|
+
return out.join("");
|
|
121
155
|
}
|
|
122
156
|
const rendered = computed(() => renderMarkdownLite(props.data.sticky.content));
|
|
123
157
|
function onViewMouseDown(e) {
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One shortcut into a surface the *host* provides — Import & Export, the SFTP
|
|
3
|
+
* server, the event stream, inbound webhooks. None of the four is a page of this
|
|
4
|
+
* module: they are the cockpit's, and this card is the studio's landing page
|
|
5
|
+
* pointing at them.
|
|
6
|
+
*
|
|
7
|
+
* So the card has two states, and which one it is in is not its own decision:
|
|
8
|
+
*
|
|
9
|
+
* - **`available`** — the host has the surface. The card is a link and behaves
|
|
10
|
+
* exactly as it did before PO-223.
|
|
11
|
+
* - **not available** — the card stays on screen, looks the same, and is a button
|
|
12
|
+
* that explains where the surface lives instead of leading into a route the host
|
|
13
|
+
* does not serve. Before PO-223 it was a link into nothing: in the playground
|
|
14
|
+
* and in the standalone dev studio all four led to a 404.
|
|
15
|
+
*
|
|
16
|
+
* **Staying on screen is the point**, and it is the call the managed credential
|
|
17
|
+
* sources already made (`CredentialRevenexxSource.vue`, PO-222): a control that
|
|
18
|
+
* vanishes leaves nothing to read the explanation from, and the reader cannot tell
|
|
19
|
+
* a broken feature from one that belongs somewhere else.
|
|
20
|
+
*
|
|
21
|
+
* **No `aria-label`, on purpose** — like `TemplateCard`, the card takes its name
|
|
22
|
+
* from what it shows. The state is carried by the *role*: "Import & Export,
|
|
23
|
+
* button" instead of "link" is what says to a screen reader that this does not
|
|
24
|
+
* navigate, and it says it without announcing something no sighted reader can see
|
|
25
|
+
* (`specs/accessibility.md` AC-1 names the on-screen text as the source of a name).
|
|
26
|
+
*
|
|
27
|
+
* Who decides `available`, and why it is the router rather than a guess about the
|
|
28
|
+
* host, is `docs/host-surfaces.md`.
|
|
29
|
+
*
|
|
30
|
+
* The two branches draw the same body twice rather than sharing it: `as-child`
|
|
31
|
+
* wants a real element as its child, and the alternatives — a wrapper element
|
|
32
|
+
* around the card, or a second component for eight lines — cost either the grid
|
|
33
|
+
* layout or a file. **Keep the two bodies identical.**
|
|
34
|
+
*/
|
|
35
|
+
import type { Component } from 'vue';
|
|
36
|
+
type __VLS_Props = {
|
|
37
|
+
/** What the surface is called, in the reader's language. */
|
|
38
|
+
label: string;
|
|
39
|
+
/** One line on what it is for. Shown in both states. */
|
|
40
|
+
hint: string;
|
|
41
|
+
icon: Component;
|
|
42
|
+
/** Where the surface lives in a host that has it. */
|
|
43
|
+
to: string;
|
|
44
|
+
/** Whether this host has it. See the comment above; no default on purpose. */
|
|
45
|
+
available: boolean;
|
|
46
|
+
/** What the popover says when it does not. */
|
|
47
|
+
note: string;
|
|
48
|
+
};
|
|
49
|
+
declare const _default: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
50
|
+
export default _default;
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
import { ArrowRight } from "#solar-icons";
|
|
3
|
+
import { Popover, PopoverContent, PopoverTrigger } from "@revenexx/studio";
|
|
4
|
+
defineProps({
|
|
5
|
+
label: { type: String, required: true },
|
|
6
|
+
hint: { type: String, required: true },
|
|
7
|
+
icon: { type: null, required: true },
|
|
8
|
+
to: { type: String, required: true },
|
|
9
|
+
available: { type: Boolean, required: true },
|
|
10
|
+
note: { type: String, required: true }
|
|
11
|
+
});
|
|
12
|
+
const CARD_CLASS = "group rounded-lg border border-black/[0.06] bg-white p-3 transition hover:border-black/20";
|
|
13
|
+
</script>
|
|
14
|
+
|
|
15
|
+
<template>
|
|
16
|
+
<NuxtLink v-if="available" :to="to" :class="CARD_CLASS">
|
|
17
|
+
<div class="flex items-center gap-2">
|
|
18
|
+
<span class="flex size-6 items-center justify-center rounded-md bg-slate-100 text-slate-500">
|
|
19
|
+
<component :is="icon" :size="12" />
|
|
20
|
+
</span>
|
|
21
|
+
<span class="min-w-0 flex-1 truncate text-sm font-semibold">{{ label }}</span>
|
|
22
|
+
<ArrowRight :size="11" class="text-muted-foreground transition-transform group-hover:translate-x-0.5" />
|
|
23
|
+
</div>
|
|
24
|
+
<p class="mt-2 text-sm text-muted-foreground">
|
|
25
|
+
{{ hint }}
|
|
26
|
+
</p>
|
|
27
|
+
</NuxtLink>
|
|
28
|
+
|
|
29
|
+
<Popover v-else>
|
|
30
|
+
<PopoverTrigger as-child>
|
|
31
|
+
<!-- No `id` here: `PopoverTrigger` puts reka-ui's generated `triggerId` on
|
|
32
|
+
its child and `PopoverContentImpl` points the opened dialog's
|
|
33
|
+
`aria-labelledby` at exactly that id, so the popover is named by this
|
|
34
|
+
card. Ours would replace it (reka's `Slot` merges with the child's
|
|
35
|
+
props winning) and leave the dialog naming nothing. -->
|
|
36
|
+
<button type="button" :class="[CARD_CLASS, 'w-full text-left']">
|
|
37
|
+
<div class="flex items-center gap-2">
|
|
38
|
+
<span class="flex size-6 items-center justify-center rounded-md bg-slate-100 text-slate-500">
|
|
39
|
+
<component :is="icon" :size="12" />
|
|
40
|
+
</span>
|
|
41
|
+
<span class="min-w-0 flex-1 truncate text-sm font-semibold">{{ label }}</span>
|
|
42
|
+
<ArrowRight :size="11" class="text-muted-foreground transition-transform group-hover:translate-x-0.5" />
|
|
43
|
+
</div>
|
|
44
|
+
<p class="mt-2 text-sm text-muted-foreground">
|
|
45
|
+
{{ hint }}
|
|
46
|
+
</p>
|
|
47
|
+
</button>
|
|
48
|
+
</PopoverTrigger>
|
|
49
|
+
<PopoverContent align="start" class="w-72 rounded-lg border border-black/[0.06] bg-white p-3 shadow-lg">
|
|
50
|
+
<p class="text-sm text-muted-foreground">
|
|
51
|
+
{{ note }}
|
|
52
|
+
</p>
|
|
53
|
+
</PopoverContent>
|
|
54
|
+
</Popover>
|
|
55
|
+
</template>
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One shortcut into a surface the *host* provides — Import & Export, the SFTP
|
|
3
|
+
* server, the event stream, inbound webhooks. None of the four is a page of this
|
|
4
|
+
* module: they are the cockpit's, and this card is the studio's landing page
|
|
5
|
+
* pointing at them.
|
|
6
|
+
*
|
|
7
|
+
* So the card has two states, and which one it is in is not its own decision:
|
|
8
|
+
*
|
|
9
|
+
* - **`available`** — the host has the surface. The card is a link and behaves
|
|
10
|
+
* exactly as it did before PO-223.
|
|
11
|
+
* - **not available** — the card stays on screen, looks the same, and is a button
|
|
12
|
+
* that explains where the surface lives instead of leading into a route the host
|
|
13
|
+
* does not serve. Before PO-223 it was a link into nothing: in the playground
|
|
14
|
+
* and in the standalone dev studio all four led to a 404.
|
|
15
|
+
*
|
|
16
|
+
* **Staying on screen is the point**, and it is the call the managed credential
|
|
17
|
+
* sources already made (`CredentialRevenexxSource.vue`, PO-222): a control that
|
|
18
|
+
* vanishes leaves nothing to read the explanation from, and the reader cannot tell
|
|
19
|
+
* a broken feature from one that belongs somewhere else.
|
|
20
|
+
*
|
|
21
|
+
* **No `aria-label`, on purpose** — like `TemplateCard`, the card takes its name
|
|
22
|
+
* from what it shows. The state is carried by the *role*: "Import & Export,
|
|
23
|
+
* button" instead of "link" is what says to a screen reader that this does not
|
|
24
|
+
* navigate, and it says it without announcing something no sighted reader can see
|
|
25
|
+
* (`specs/accessibility.md` AC-1 names the on-screen text as the source of a name).
|
|
26
|
+
*
|
|
27
|
+
* Who decides `available`, and why it is the router rather than a guess about the
|
|
28
|
+
* host, is `docs/host-surfaces.md`.
|
|
29
|
+
*
|
|
30
|
+
* The two branches draw the same body twice rather than sharing it: `as-child`
|
|
31
|
+
* wants a real element as its child, and the alternatives — a wrapper element
|
|
32
|
+
* around the card, or a second component for eight lines — cost either the grid
|
|
33
|
+
* layout or a file. **Keep the two bodies identical.**
|
|
34
|
+
*/
|
|
35
|
+
import type { Component } from 'vue';
|
|
36
|
+
type __VLS_Props = {
|
|
37
|
+
/** What the surface is called, in the reader's language. */
|
|
38
|
+
label: string;
|
|
39
|
+
/** One line on what it is for. Shown in both states. */
|
|
40
|
+
hint: string;
|
|
41
|
+
icon: Component;
|
|
42
|
+
/** Where the surface lives in a host that has it. */
|
|
43
|
+
to: string;
|
|
44
|
+
/** Whether this host has it. See the comment above; no default on purpose. */
|
|
45
|
+
available: boolean;
|
|
46
|
+
/** What the popover says when it does not. */
|
|
47
|
+
note: string;
|
|
48
|
+
};
|
|
49
|
+
declare const _default: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
50
|
+
export default _default;
|