@anchrd/intel-ui 0.28.0 → 0.29.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/package.json +2 -2
- package/src/app/app-tree/app-tree.tsx +26 -5
- package/src/app/tree-move/tree-move.tsx +31 -5
- package/src/archive/archive.tsx +3 -2
- package/src/flows/flows.tsx +32 -81
- package/src/folder-contents/folder-contents.tsx +2 -2
- package/src/i18n/de.json +0 -3
- package/src/i18n/en.json +0 -3
- package/src/i18n/es.json +0 -3
- package/src/import-bundle/import-bundle.tsx +5 -2
- package/src/resource-menu/resource-menu.tsx +7 -6
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@anchrd/intel-ui",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.29.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"license": "UNLICENSED",
|
|
6
6
|
"repository": {
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
"typecheck": "tsc --noEmit"
|
|
34
34
|
},
|
|
35
35
|
"dependencies": {
|
|
36
|
-
"@anchrd/intel-contract": "^0.
|
|
36
|
+
"@anchrd/intel-contract": "^0.18.0",
|
|
37
37
|
"@blocknote/core": "^0.52.1",
|
|
38
38
|
"@blocknote/react": "^0.52.1",
|
|
39
39
|
"@blocknote/shadcn": "^0.52.1",
|
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
} from "lucide-react";
|
|
13
13
|
import { useRef, useState } from "react";
|
|
14
14
|
import {
|
|
15
|
+
allTreeLevelsKey,
|
|
15
16
|
type MoveDestination,
|
|
16
17
|
moveErrorKey,
|
|
17
18
|
moveVerdict,
|
|
@@ -176,13 +177,18 @@ export function AppTree() {
|
|
|
176
177
|
// ⚠️ The level a row appears in is not the only thing that goes stale. The header search reads
|
|
177
178
|
// the whole flow list, and the graph is what the link picker and the flow editor's tree links
|
|
178
179
|
// offer — a row created here has to reach those too, or another screen keeps showing yesterday.
|
|
180
|
+
//
|
|
181
|
+
// ⚠️ And the level written into is not enough either: the folder's own ROW says whether it can be
|
|
182
|
+
// opened at all, and that row stands one level higher (`allTreeLevelsKey`, #519). Without it the
|
|
183
|
+
// `toggle` above opens a folder whose arrow never appeared, so the first thing filed into an empty
|
|
184
|
+
// folder was invisible until the page was reloaded.
|
|
179
185
|
async function reveal(
|
|
180
186
|
parentId: string | null,
|
|
181
187
|
collection: "flows" | "node-graph",
|
|
182
188
|
): Promise<void> {
|
|
183
189
|
if (parentId !== null) toggle({ id: parentId, type: "folder" }, true);
|
|
184
190
|
await Promise.all([
|
|
185
|
-
queryClient.invalidateQueries({ queryKey:
|
|
191
|
+
queryClient.invalidateQueries({ queryKey: allTreeLevelsKey }),
|
|
186
192
|
queryClient.invalidateQueries({ queryKey: [collection] }),
|
|
187
193
|
// The graph view of that same level draws exactly this list, so it goes stale for the same
|
|
188
194
|
// reason the level does (#19).
|
|
@@ -233,8 +239,19 @@ export function AppTree() {
|
|
|
233
239
|
},
|
|
234
240
|
onSuccess: async (created) => {
|
|
235
241
|
setCreating(null);
|
|
236
|
-
|
|
237
|
-
|
|
242
|
+
// ⚠️ The screen first, the refreshes after. Since #519 `reveal` waits on EVERY mounted level,
|
|
243
|
+
// and a React Query retry is paused while the tab is not in front (#212) — awaiting that
|
|
244
|
+
// before navigating would leave somebody who just made a document sitting on the old screen
|
|
245
|
+
// for as long as one level takes to answer.
|
|
246
|
+
//
|
|
247
|
+
// ⚠️ `finally`, because both screens are lazy chunks: a failed chunk load throws out of
|
|
248
|
+
// `navigate`, and without this the tree, the collections and the graph would silently keep
|
|
249
|
+
// yesterday's rows for the rest of the session — the very state this ticket is about.
|
|
250
|
+
try {
|
|
251
|
+
await navigate({ to: created.area, search: { select: created.id } });
|
|
252
|
+
} finally {
|
|
253
|
+
await reveal(created.parentId, created.collection);
|
|
254
|
+
}
|
|
238
255
|
},
|
|
239
256
|
});
|
|
240
257
|
|
|
@@ -275,8 +292,12 @@ export function AppTree() {
|
|
|
275
292
|
return { id: node.id, parentId };
|
|
276
293
|
},
|
|
277
294
|
onSuccess: async (created) => {
|
|
278
|
-
|
|
279
|
-
|
|
295
|
+
// The order — and the `finally` — are the ones above, for the same two reasons.
|
|
296
|
+
try {
|
|
297
|
+
await navigate({ to: "/nodes", search: { select: created.id } });
|
|
298
|
+
} finally {
|
|
299
|
+
await reveal(created.parentId, "node-graph");
|
|
300
|
+
}
|
|
280
301
|
},
|
|
281
302
|
});
|
|
282
303
|
|
|
@@ -31,6 +31,33 @@ export function treeLevelKey(parentId: string | null): readonly unknown[] {
|
|
|
31
31
|
return ["tree", parentId];
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
+
/**
|
|
35
|
+
* Every level of the tree at once — what a write that changed what is IN a folder makes stale.
|
|
36
|
+
*
|
|
37
|
+
* ⚠️ Not the level that was written into: that is the obvious half, and it was the whole of #519.
|
|
38
|
+
* A folder ROW carries `openable` — since #59 the arrow only stands where there is something to
|
|
39
|
+
* open — and that is a statement about the folder's CONTENTS living one level ABOVE it. Refresh
|
|
40
|
+
* only the level below and the row keeps yesterday's answer: the first thing filed into an EMPTY
|
|
41
|
+
* folder has no arrow to appear under, `reveal` opens a level `renderRow` then never renders, and
|
|
42
|
+
* reloading the page was the only thing that read the row again. The other direction is the same
|
|
43
|
+
* bug from the other side — the last child moved or archived out leaves an arrow promising a level
|
|
44
|
+
* that is empty.
|
|
45
|
+
*
|
|
46
|
+
* ⚠️ A prefix rather than "the level, plus the level its folder row sits in", because that second
|
|
47
|
+
* level is not always the one the record names: since #429 a shared row also stands at the root
|
|
48
|
+
* (`levelsToClear`).
|
|
49
|
+
*
|
|
50
|
+
* ⚠️ What it costs is one request per MOUNTED level, and that is not the same as "per level on
|
|
51
|
+
* screen": `expanded` in `app-tree.tsx` is only ever added to, so a folder that was opened and has
|
|
52
|
+
* since left the tree — archived, moved away — keeps its query mounted for the rest of the session
|
|
53
|
+
* and is re-read by every write from then on. Nothing is wrong on screen; the requests are simply
|
|
54
|
+
* paid for. #521 prunes that list, and until it does, this is the honest bound.
|
|
55
|
+
*
|
|
56
|
+
* The same prefix is what `archive.tsx` has always used for the same reason: a row can come back
|
|
57
|
+
* anywhere, and the screen writing it does not know where.
|
|
58
|
+
*/
|
|
59
|
+
export const allTreeLevelsKey: readonly unknown[] = ["tree"];
|
|
60
|
+
|
|
34
61
|
/**
|
|
35
62
|
* Which cached levels a move has to take the row out of.
|
|
36
63
|
*
|
|
@@ -368,12 +395,11 @@ export function useTreeMove({
|
|
|
368
395
|
for (const [key, value] of context?.snapshot ?? []) queryClient.setQueryData(key, value);
|
|
369
396
|
},
|
|
370
397
|
onSuccess: (_result, { destination }) => onMoved?.(destination),
|
|
371
|
-
onSettled: async (_result, _error, { entry
|
|
398
|
+
onSettled: async (_result, _error, { entry }) => {
|
|
372
399
|
await Promise.all([
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
),
|
|
376
|
-
queryClient.invalidateQueries({ queryKey: treeLevelKey(destination.id) }),
|
|
400
|
+
// Both ends of the move and the rows they hang under: a folder that just received its first
|
|
401
|
+
// row needs its arrow, and one that lost its last has to give it back (#519).
|
|
402
|
+
queryClient.invalidateQueries({ queryKey: allTreeLevelsKey }),
|
|
377
403
|
queryClient.invalidateQueries({
|
|
378
404
|
queryKey: [entry.type === "flow" ? "flows" : "node-graph"],
|
|
379
405
|
}),
|
package/src/archive/archive.tsx
CHANGED
|
@@ -3,6 +3,7 @@ import type { Node } from "@anchrd/intel-contract/node";
|
|
|
3
3
|
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
|
4
4
|
import { ArchiveRestore, LoaderCircle, Trash2 } from "lucide-react";
|
|
5
5
|
import { useState } from "react";
|
|
6
|
+
import { allTreeLevelsKey } from "@/app/tree-move/tree-move.tsx";
|
|
6
7
|
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip";
|
|
7
8
|
import { useI18n } from "@/i18n/i18n-context.tsx";
|
|
8
9
|
import { kindIcons } from "@/kind-icon.ts";
|
|
@@ -101,7 +102,7 @@ export function Archive() {
|
|
|
101
102
|
queryClient.invalidateQueries({ queryKey: ["archive"] }),
|
|
102
103
|
// The tree gets it back, so every level has to be asked again — the entry may sit
|
|
103
104
|
// anywhere, and the archive does not know where.
|
|
104
|
-
queryClient.invalidateQueries({ queryKey:
|
|
105
|
+
queryClient.invalidateQueries({ queryKey: allTreeLevelsKey }),
|
|
105
106
|
queryClient.invalidateQueries({ queryKey: ["node-graph"] }),
|
|
106
107
|
queryClient.invalidateQueries({ queryKey: ["flows"] }),
|
|
107
108
|
]);
|
|
@@ -126,7 +127,7 @@ export function Archive() {
|
|
|
126
127
|
queryClient.invalidateQueries({ queryKey: ["archive"] }),
|
|
127
128
|
// The tree and the graph did not show the node, but its DISAPPEARANCE can be visible
|
|
128
129
|
// elsewhere: a reference to it no longer resolves afterwards.
|
|
129
|
-
queryClient.invalidateQueries({ queryKey:
|
|
130
|
+
queryClient.invalidateQueries({ queryKey: allTreeLevelsKey }),
|
|
130
131
|
queryClient.invalidateQueries({ queryKey: ["node-graph"] }),
|
|
131
132
|
queryClient.invalidateQueries({ queryKey: ["flows"] }),
|
|
132
133
|
]);
|
package/src/flows/flows.tsx
CHANGED
|
@@ -285,6 +285,7 @@ function newNode(
|
|
|
285
285
|
index: number,
|
|
286
286
|
firstNodeId?: string,
|
|
287
287
|
firstFlowId?: string,
|
|
288
|
+
firstServer?: string,
|
|
288
289
|
): FlowNode {
|
|
289
290
|
const common = {
|
|
290
291
|
id: `${kind}-${crypto.randomUUID()}`,
|
|
@@ -315,10 +316,13 @@ function newNode(
|
|
|
315
316
|
return {
|
|
316
317
|
...common,
|
|
317
318
|
kind,
|
|
319
|
+
// ⚠️ A server, not an empty string. `ToolServerHandle` has a minimum length, so a draft
|
|
320
|
+
// with `""` cannot be saved at all — the author would build a step and lose it at the next
|
|
321
|
+
// save with a raw field-path error. The tree link one line up takes the same way out.
|
|
318
322
|
configuration: {
|
|
319
|
-
|
|
323
|
+
server: firstServer ?? "",
|
|
324
|
+
allow: null,
|
|
320
325
|
fingerprint: null,
|
|
321
|
-
arguments: {},
|
|
322
326
|
},
|
|
323
327
|
};
|
|
324
328
|
case "condition":
|
|
@@ -361,9 +365,11 @@ function FlowsEditor() {
|
|
|
361
365
|
const theme = useResolvedTheme();
|
|
362
366
|
const queryClient = useQueryClient();
|
|
363
367
|
const navigate = useNavigate();
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
368
|
+
// The servers a tool step can name (#489). The flat function list the picker used to show is
|
|
369
|
+
// what that ticket set out to remove; which function runs is decided while the flow runs.
|
|
370
|
+
const toolServers = useQuery({
|
|
371
|
+
queryKey: ["tool-servers"],
|
|
372
|
+
queryFn: () => data.listToolServers(),
|
|
367
373
|
});
|
|
368
374
|
// A tree link points at a node, so the editor needs candidates. The graph is the authorized
|
|
369
375
|
// flat list of them; walking the folder tree for a picker would be the N+1 all over again.
|
|
@@ -519,6 +525,7 @@ function FlowsEditor() {
|
|
|
519
525
|
nodes.length,
|
|
520
526
|
nodeGraph.data?.nodes[0]?.id,
|
|
521
527
|
callable.data?.items.find((entry) => entry.id !== selectedFlowId)?.id,
|
|
528
|
+
toolServers.data?.items[0]?.handle,
|
|
522
529
|
);
|
|
523
530
|
setNodes((current) => [
|
|
524
531
|
...current,
|
|
@@ -639,7 +646,7 @@ function FlowsEditor() {
|
|
|
639
646
|
<NodeInspector
|
|
640
647
|
node={selectedNode}
|
|
641
648
|
update={updateNode}
|
|
642
|
-
|
|
649
|
+
toolServers={toolServers.data?.items ?? []}
|
|
643
650
|
flows={(callable.data?.items ?? []).filter(
|
|
644
651
|
(entry) => entry.id !== selectedFlowId,
|
|
645
652
|
)}
|
|
@@ -852,12 +859,12 @@ function PublishPreview({
|
|
|
852
859
|
function NodeInspector({
|
|
853
860
|
node,
|
|
854
861
|
update,
|
|
855
|
-
|
|
862
|
+
toolServers,
|
|
856
863
|
flows,
|
|
857
864
|
}: {
|
|
858
865
|
node: CanvasNode | null;
|
|
859
866
|
update(fn: (node: FlowNode) => FlowNode): void;
|
|
860
|
-
|
|
867
|
+
toolServers: Array<{ handle: string; name: string; toolCount: number }>;
|
|
861
868
|
flows: Flow[];
|
|
862
869
|
}) {
|
|
863
870
|
const i18n = useI18n();
|
|
@@ -949,50 +956,36 @@ function NodeInspector({
|
|
|
949
956
|
<div className="mt-4">
|
|
950
957
|
<label className="block text-sm font-medium">
|
|
951
958
|
{i18n.t("flows.tool")}
|
|
959
|
+
{/* ⚠️ The step names a SERVER since #489. This picker is the smallest thing that keeps
|
|
960
|
+
the field usable on the new model; the real one — server names from the portal plus
|
|
961
|
+
an optional narrowing to single functions — is #489's second package and is
|
|
962
|
+
deliberately not smuggled in here. */}
|
|
952
963
|
<select
|
|
953
|
-
value={contract.configuration.
|
|
964
|
+
value={contract.configuration.server}
|
|
954
965
|
onChange={(event) => {
|
|
955
|
-
const
|
|
966
|
+
const server = event.target.value;
|
|
956
967
|
update((value) =>
|
|
957
968
|
value.kind === "tool"
|
|
958
969
|
? {
|
|
959
970
|
...value,
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
tools.find((entry) => entry.name === toolName)?.fingerprint ?? null,
|
|
965
|
-
},
|
|
971
|
+
// The fingerprint belongs to a surface that publishing freezes, and it is
|
|
972
|
+
// the server's to compute — a value guessed in the browser would be a
|
|
973
|
+
// second answer to a question the API already owns.
|
|
974
|
+
configuration: { ...value.configuration, server, fingerprint: null },
|
|
966
975
|
}
|
|
967
976
|
: value,
|
|
968
977
|
);
|
|
969
978
|
}}
|
|
970
979
|
className="mt-2 w-full rounded-md border bg-background px-3 py-2 outline-none focus-visible:ring-2 focus-visible:ring-ring"
|
|
971
980
|
>
|
|
972
|
-
<option value="
|
|
973
|
-
{
|
|
974
|
-
<option key={entry.
|
|
975
|
-
{entry.
|
|
981
|
+
<option value="">{i18n.t("flows.selectTool")}</option>
|
|
982
|
+
{toolServers.map((entry) => (
|
|
983
|
+
<option key={entry.handle} value={entry.handle}>
|
|
984
|
+
{entry.name}
|
|
976
985
|
</option>
|
|
977
986
|
))}
|
|
978
987
|
</select>
|
|
979
988
|
</label>
|
|
980
|
-
<ToolArgumentsEditor
|
|
981
|
-
key={contract.id}
|
|
982
|
-
label={i18n.t("tools.arguments")}
|
|
983
|
-
invalidLabel={i18n.t("tools.invalidJson")}
|
|
984
|
-
initialValue={contract.configuration.arguments}
|
|
985
|
-
onValid={(argumentsValue) => {
|
|
986
|
-
update((value) =>
|
|
987
|
-
value.kind === "tool"
|
|
988
|
-
? {
|
|
989
|
-
...value,
|
|
990
|
-
configuration: { ...value.configuration, arguments: argumentsValue },
|
|
991
|
-
}
|
|
992
|
-
: value,
|
|
993
|
-
);
|
|
994
|
-
}}
|
|
995
|
-
/>
|
|
996
989
|
</div>
|
|
997
990
|
)}
|
|
998
991
|
</div>
|
|
@@ -1015,7 +1008,7 @@ function FlowNeeds({ flowId }: { flowId: string }) {
|
|
|
1015
1008
|
needs.data !== undefined &&
|
|
1016
1009
|
needs.data.nodes.length === 0 &&
|
|
1017
1010
|
needs.data.hiddenNodes === 0 &&
|
|
1018
|
-
needs.data.
|
|
1011
|
+
needs.data.servers.length === 0;
|
|
1019
1012
|
return (
|
|
1020
1013
|
<section aria-label={i18n.t("flows.needs")} className="mt-auto border-t p-5">
|
|
1021
1014
|
<h2 className="font-semibold">{i18n.t("flows.needs")}</h2>
|
|
@@ -1052,13 +1045,13 @@ function FlowNeeds({ flowId }: { flowId: string }) {
|
|
|
1052
1045
|
</ul>
|
|
1053
1046
|
</>
|
|
1054
1047
|
)}
|
|
1055
|
-
{needs.data.
|
|
1048
|
+
{needs.data.servers.length > 0 && (
|
|
1056
1049
|
<>
|
|
1057
1050
|
<h3 className="mt-4 text-xs font-medium uppercase tracking-wide text-muted-foreground">
|
|
1058
1051
|
{i18n.t("flows.needsTools")}
|
|
1059
1052
|
</h3>
|
|
1060
1053
|
<ul className="mt-2 space-y-1 text-sm">
|
|
1061
|
-
{needs.data.
|
|
1054
|
+
{needs.data.servers.map((name) => (
|
|
1062
1055
|
<li key={name} className="flex items-center gap-2">
|
|
1063
1056
|
<Wrench aria-hidden="true" className="size-3.5 shrink-0" />
|
|
1064
1057
|
<span className="min-w-0 truncate">{name}</span>
|
|
@@ -1067,7 +1060,6 @@ function FlowNeeds({ flowId }: { flowId: string }) {
|
|
|
1067
1060
|
</ul>
|
|
1068
1061
|
</>
|
|
1069
1062
|
)}
|
|
1070
|
-
<p className="mt-4 text-xs text-muted-foreground">{i18n.t("flows.needsHint")}</p>
|
|
1071
1063
|
</>
|
|
1072
1064
|
)}
|
|
1073
1065
|
</section>
|
|
@@ -1118,47 +1110,6 @@ function SubflowVersionField({
|
|
|
1118
1110
|
);
|
|
1119
1111
|
}
|
|
1120
1112
|
|
|
1121
|
-
function ToolArgumentsEditor({
|
|
1122
|
-
label,
|
|
1123
|
-
invalidLabel,
|
|
1124
|
-
initialValue,
|
|
1125
|
-
onValid,
|
|
1126
|
-
}: {
|
|
1127
|
-
label: string;
|
|
1128
|
-
invalidLabel: string;
|
|
1129
|
-
initialValue: Record<string, unknown>;
|
|
1130
|
-
onValid(value: Record<string, unknown>): void;
|
|
1131
|
-
}) {
|
|
1132
|
-
const [text, setText] = useState(() => JSON.stringify(initialValue, null, 2));
|
|
1133
|
-
const [invalid, setInvalid] = useState(false);
|
|
1134
|
-
return (
|
|
1135
|
-
<label className="mt-4 block text-sm font-medium">
|
|
1136
|
-
{label}
|
|
1137
|
-
<textarea
|
|
1138
|
-
rows={6}
|
|
1139
|
-
value={text}
|
|
1140
|
-
onChange={(event) => {
|
|
1141
|
-
const next = event.target.value;
|
|
1142
|
-
setText(next);
|
|
1143
|
-
try {
|
|
1144
|
-
const parsed: unknown = JSON.parse(next);
|
|
1145
|
-
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
1146
|
-
setInvalid(true);
|
|
1147
|
-
return;
|
|
1148
|
-
}
|
|
1149
|
-
setInvalid(false);
|
|
1150
|
-
onValid(parsed as Record<string, unknown>);
|
|
1151
|
-
} catch {
|
|
1152
|
-
setInvalid(true);
|
|
1153
|
-
}
|
|
1154
|
-
}}
|
|
1155
|
-
className="mt-2 w-full resize-y rounded-md border bg-background px-3 py-2 font-mono text-sm outline-none focus-visible:ring-2 focus-visible:ring-ring"
|
|
1156
|
-
/>
|
|
1157
|
-
{invalid && <span className="mt-1 block text-xs text-destructive">{invalidLabel}</span>}
|
|
1158
|
-
</label>
|
|
1159
|
-
);
|
|
1160
|
-
}
|
|
1161
|
-
|
|
1162
1113
|
function Field({
|
|
1163
1114
|
label,
|
|
1164
1115
|
value,
|
|
@@ -34,8 +34,8 @@ function targetOf(entry: TreeEntry): ResourceTarget {
|
|
|
34
34
|
* fetch separately show it differently the moment one of them is stale, and that is the kind of
|
|
35
35
|
* difference a customer finds before anybody here does. The other half of the bargain is free —
|
|
36
36
|
* every invalidation the tree already does after a create, a move or an archive lands here too, and
|
|
37
|
-
* that now includes the ones this screen's own menu makes:
|
|
38
|
-
* `
|
|
37
|
+
* that now includes the ones this screen's own menu makes: since #519 they all refresh every tree
|
|
38
|
+
* level (`allTreeLevelsKey`), and this level is one of them.
|
|
39
39
|
*
|
|
40
40
|
* ⚠️ There is no "Kind" column since #101. The icon says what a row is, in the same picture the
|
|
41
41
|
* sidebar draws two centimetres to its left, and `sr-only` says it in words for everyone who does
|
package/src/i18n/de.json
CHANGED
|
@@ -215,8 +215,6 @@
|
|
|
215
215
|
"tools.toolCount": "{count} Werkzeuge",
|
|
216
216
|
"tools.toolCountOne": "1 Werkzeug",
|
|
217
217
|
"tools.liveNote": "Diese Liste ist eine Live-Abfrage mit deinem eigenen Portal-Zugriff. Intel speichert weder die Werkzeuge noch, wer sie nutzen darf; beides entscheidet das Portal.",
|
|
218
|
-
"tools.arguments": "Argumente",
|
|
219
|
-
"tools.invalidJson": "Die Argumente müssen ein JSON-Objekt in geschweiften Klammern sein; eine Liste oder ein einzelner Wert funktioniert nicht.",
|
|
220
218
|
"flows.select": "Wähle einen Flow oder lege einen an, um den visuellen Editor zu öffnen.",
|
|
221
219
|
"flows.inspect": "Wähle einen Schritt, um seine Konfiguration zu bearbeiten.",
|
|
222
220
|
"flows.publish": "Veröffentlichen",
|
|
@@ -281,7 +279,6 @@
|
|
|
281
279
|
"flows.selectTool": "Wähle ein gefundenes Werkzeug",
|
|
282
280
|
"flows.operationFailed": "Der Flow-Vorgang ist fehlgeschlagen. Lade die neueste Fassung und versuche es erneut.",
|
|
283
281
|
"flows.needs": "Was dieser Flow braucht",
|
|
284
|
-
"flows.needsHint": "Aus dem Graphen gelesen. Ob eine bestimmte Person davon etwas erreicht, entscheidet sich, wenn sie ihn ausführt.",
|
|
285
282
|
"flows.needsNodes": "Dokumente",
|
|
286
283
|
"flows.needsTools": "Werkzeuge",
|
|
287
284
|
"flows.needsHidden": "{count} weitere, die du nicht sehen kannst",
|
package/src/i18n/en.json
CHANGED
|
@@ -215,8 +215,6 @@
|
|
|
215
215
|
"tools.toolCount": "{count} tools",
|
|
216
216
|
"tools.toolCountOne": "1 tool",
|
|
217
217
|
"tools.liveNote": "This list is a live query with your own portal access. Intel stores neither the tools nor who may use them; the portal decides both.",
|
|
218
|
-
"tools.arguments": "Arguments",
|
|
219
|
-
"tools.invalidJson": "The arguments must be a JSON object in curly braces; a list or a single value will not work.",
|
|
220
218
|
"flows.select": "Select a flow or create one to open the visual editor.",
|
|
221
219
|
"flows.inspect": "Select a node to edit its configuration.",
|
|
222
220
|
"flows.publish": "Publish",
|
|
@@ -281,7 +279,6 @@
|
|
|
281
279
|
"flows.selectTool": "Select a discovered tool",
|
|
282
280
|
"flows.operationFailed": "The flow operation failed. Reload the latest version and try again.",
|
|
283
281
|
"flows.needs": "What this flow needs",
|
|
284
|
-
"flows.needsHint": "Read out of the graph. Whether a given person may reach any of it is decided when they run it.",
|
|
285
282
|
"flows.needsNodes": "Documents",
|
|
286
283
|
"flows.needsTools": "Tools",
|
|
287
284
|
"flows.needsHidden": "{count} more you cannot see",
|
package/src/i18n/es.json
CHANGED
|
@@ -215,8 +215,6 @@
|
|
|
215
215
|
"tools.toolCount": "{count} herramientas",
|
|
216
216
|
"tools.toolCountOne": "1 herramienta",
|
|
217
217
|
"tools.liveNote": "Esta lista es una consulta en vivo con tu propio acceso al portal. Intel no guarda ni las herramientas ni quién puede usarlas; ambas cosas las decide el portal.",
|
|
218
|
-
"tools.arguments": "Argumentos",
|
|
219
|
-
"tools.invalidJson": "Los argumentos tienen que ser un objeto JSON entre llaves; una lista o un valor suelto no sirven.",
|
|
220
218
|
"flows.select": "Elige un flujo o crea uno para abrir el editor visual.",
|
|
221
219
|
"flows.inspect": "Elige un paso para editar su configuración.",
|
|
222
220
|
"flows.publish": "Publicar",
|
|
@@ -281,7 +279,6 @@
|
|
|
281
279
|
"flows.selectTool": "Elige una herramienta descubierta",
|
|
282
280
|
"flows.operationFailed": "La operación del flujo ha fallado. Carga la versión más reciente e inténtalo de nuevo.",
|
|
283
281
|
"flows.needs": "Qué necesita este flujo",
|
|
284
|
-
"flows.needsHint": "Leído del grafo. Si una persona concreta llega a algo de esto se decide cuando lo ejecuta.",
|
|
285
282
|
"flows.needsNodes": "Documentos",
|
|
286
283
|
"flows.needsTools": "Herramientas",
|
|
287
284
|
"flows.needsHidden": "{count} más que no puedes ver",
|
|
@@ -2,7 +2,7 @@ import { useMutation, useQueryClient } from "@tanstack/react-query";
|
|
|
2
2
|
import { zipSync } from "fflate";
|
|
3
3
|
import { useRef, useState } from "react";
|
|
4
4
|
import { createPortal } from "react-dom";
|
|
5
|
-
import {
|
|
5
|
+
import { allTreeLevelsKey } from "@/app/tree-move/tree-move.tsx";
|
|
6
6
|
import { useI18n } from "@/i18n/i18n-context.tsx";
|
|
7
7
|
import { useIntelRouterContext } from "@/router/router-context.ts";
|
|
8
8
|
|
|
@@ -93,7 +93,10 @@ export function useBundleImport(options: {
|
|
|
93
93
|
onSuccess: async (imported) => {
|
|
94
94
|
await options.onImported?.(imported.parentId);
|
|
95
95
|
await Promise.all([
|
|
96
|
-
|
|
96
|
+
// ⚠️ Every level, not the one imported into: the target folder's own row carries whether it
|
|
97
|
+
// can be opened, and it stands one level higher (#519). An import into an empty folder was
|
|
98
|
+
// otherwise unreachable in the tree until the page was reloaded.
|
|
99
|
+
queryClient.invalidateQueries({ queryKey: allTreeLevelsKey }),
|
|
97
100
|
queryClient.invalidateQueries({ queryKey: ["node-graph"] }),
|
|
98
101
|
// The graph view of that same level draws exactly this list (#19).
|
|
99
102
|
queryClient.invalidateQueries({ queryKey: ["relation-graph"] }),
|
|
@@ -20,7 +20,7 @@ import {
|
|
|
20
20
|
import type * as React from "react";
|
|
21
21
|
import { useState } from "react";
|
|
22
22
|
import { AccessSummary } from "@/access-summary/access-summary.tsx";
|
|
23
|
-
import { moveErrorKey, useTreeMove } from "@/app/tree-move/tree-move.tsx";
|
|
23
|
+
import { allTreeLevelsKey, moveErrorKey, useTreeMove } from "@/app/tree-move/tree-move.tsx";
|
|
24
24
|
import {
|
|
25
25
|
DropdownMenu,
|
|
26
26
|
DropdownMenuContent,
|
|
@@ -80,10 +80,6 @@ function titleOf(target: ResourceTarget): string {
|
|
|
80
80
|
return target.type === "node" ? target.node.title : target.flow.title;
|
|
81
81
|
}
|
|
82
82
|
|
|
83
|
-
function parentOfTarget(target: ResourceTarget): string | null {
|
|
84
|
-
return target.type === "node" ? target.node.parentId : target.flow.parentId;
|
|
85
|
-
}
|
|
86
|
-
|
|
87
83
|
/**
|
|
88
84
|
* Which sentence explains a verb.
|
|
89
85
|
*
|
|
@@ -175,9 +171,14 @@ export function ResourceMenu({
|
|
|
175
171
|
// reads the record, and the flat collections behind the search, the link picker and the relation
|
|
176
172
|
// view all carry the title as well — a rename that only reached the tree would leave yesterday's
|
|
177
173
|
// name standing on three other screens.
|
|
174
|
+
//
|
|
175
|
+
// ⚠️ Every level rather than the one this row sits in: archiving the last row of a folder takes
|
|
176
|
+
// that folder's arrow away, and the arrow is drawn from the folder's own row one level higher
|
|
177
|
+
// (`allTreeLevelsKey`, #519). Renaming needs no more than the one level — but two invalidations
|
|
178
|
+
// that differ per entry are how one of them goes missing.
|
|
178
179
|
async function refresh(): Promise<void> {
|
|
179
180
|
await Promise.all([
|
|
180
|
-
queryClient.invalidateQueries({ queryKey:
|
|
181
|
+
queryClient.invalidateQueries({ queryKey: allTreeLevelsKey }),
|
|
181
182
|
queryClient.invalidateQueries({
|
|
182
183
|
queryKey: target.type === "flow" ? ["flow", id] : ["node", id],
|
|
183
184
|
}),
|