@parhelia/localization 0.1.12534 → 0.1.12555
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/LocalizeItemDialog.d.ts.map +1 -1
- package/dist/LocalizeItemDialog.js +17 -13
- package/dist/api/discovery.d.ts +4 -1
- package/dist/api/discovery.d.ts.map +1 -1
- package/dist/api/discovery.js +4 -3
- package/dist/hooks/useTranslationWizard.d.ts.map +1 -1
- package/dist/hooks/useTranslationWizard.js +2 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +28 -13
- package/dist/services/translationService.d.ts +7 -48
- package/dist/services/translationService.d.ts.map +1 -1
- package/dist/services/translationService.js +0 -3
- package/dist/settings/TranslationServicesPanel.d.ts.map +1 -1
- package/dist/settings/TranslationServicesPanel.js +42 -111
- package/dist/setup/LocalizationSetupStep.d.ts.map +1 -1
- package/dist/setup/LocalizationSetupStep.js +7 -8
- package/dist/steps/ServiceLanguageSelectionStep.d.ts.map +1 -1
- package/dist/steps/ServiceLanguageSelectionStep.js +1 -18
- package/dist/steps/SubitemDiscoveryStep.d.ts.map +1 -1
- package/dist/steps/SubitemDiscoveryStep.js +181 -95
- package/dist/translation-center/BatchTranslationView.d.ts +1 -1
- package/dist/translation-center/BatchTranslationView.d.ts.map +1 -1
- package/dist/translation-center/BatchTranslationView.js +96 -355
- package/dist/translation-center/RecentTranslations.d.ts.map +1 -1
- package/dist/translation-center/RecentTranslations.js +9 -18
- package/package.json +10 -8
|
@@ -2,7 +2,7 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
|
|
|
2
2
|
import { useEffect, useState, useRef, useMemo, useCallback } from "react";
|
|
3
3
|
import { Button, PerfectTree } from "@parhelia/core";
|
|
4
4
|
import { convertFullItemToStub, convertStubToFullItem } from "@parhelia/core";
|
|
5
|
-
import { discoverItemsTree, convertBackendTreeToTreeNodes,
|
|
5
|
+
import { discoverItemsTree, convertBackendTreeToTreeNodes, flattenPagesFromBackendTrees } from "../api/discovery";
|
|
6
6
|
// We need to implement a basic Spinner component since it's not in core
|
|
7
7
|
const Spinner = ({ ...props }) => (_jsx("div", { className: "animate-spin rounded-full h-4 w-4 border-b-2 border-[#9650fb]", ...props }));
|
|
8
8
|
export function SubitemDiscoveryStep({ stepIndex, isActive = true, data, setData, editContext, onStepCompleted, setBeforeNextCallback, setFooterActions, requestClose }) {
|
|
@@ -15,6 +15,7 @@ export function SubitemDiscoveryStep({ stepIndex, isActive = true, data, setData
|
|
|
15
15
|
const [userHasInteracted, setUserHasInteracted] = useState(false);
|
|
16
16
|
const [discoveredCount, setDiscoveredCount] = useState(0);
|
|
17
17
|
const [totalItemsCount, setTotalItemsCount] = useState(0);
|
|
18
|
+
const [translatableItemsCount, setTranslatableItemsCount] = useState(0);
|
|
18
19
|
const [treeInitialized, setTreeInitialized] = useState(false);
|
|
19
20
|
const [expandedIds, setExpandedIds] = useState(new Set());
|
|
20
21
|
// Refs for async safety
|
|
@@ -46,19 +47,26 @@ export function SubitemDiscoveryStep({ stepIndex, isActive = true, data, setData
|
|
|
46
47
|
const nodes = buildTreeNodes(stubs);
|
|
47
48
|
setTreeNodes(nodes);
|
|
48
49
|
setExpandedIds(new Set(nodes.map(n => n.key)));
|
|
50
|
+
// Count total items and translatable items in the tree
|
|
49
51
|
const countItems = (nodeList) => {
|
|
50
52
|
let total = 0;
|
|
53
|
+
let translatable = 0;
|
|
51
54
|
const walk = (nodeList) => {
|
|
52
55
|
nodeList.forEach(node => {
|
|
53
56
|
total++;
|
|
57
|
+
if (node?.data?.isTranslatable === true) {
|
|
58
|
+
translatable++;
|
|
59
|
+
}
|
|
54
60
|
if (node.children)
|
|
55
61
|
walk(node.children);
|
|
56
62
|
});
|
|
57
63
|
};
|
|
58
64
|
walk(nodeList);
|
|
59
|
-
return total;
|
|
65
|
+
return { total, translatable };
|
|
60
66
|
};
|
|
61
|
-
|
|
67
|
+
const counts = countItems(nodes);
|
|
68
|
+
setTotalItemsCount(counts.total);
|
|
69
|
+
setTranslatableItemsCount(counts.translatable);
|
|
62
70
|
setDiscoveryComplete(true);
|
|
63
71
|
setTreeInitialized(true);
|
|
64
72
|
return;
|
|
@@ -79,11 +87,11 @@ export function SubitemDiscoveryStep({ stepIndex, isActive = true, data, setData
|
|
|
79
87
|
}, editContext.sessionId ?? undefined);
|
|
80
88
|
if (!isMountedRef.current)
|
|
81
89
|
return;
|
|
82
|
-
const
|
|
90
|
+
const pages = flattenPagesFromBackendTrees(resp.trees);
|
|
83
91
|
const language = editContext?.item?.language ??
|
|
84
92
|
editContext?.currentItemDescriptor?.language ??
|
|
85
93
|
"en";
|
|
86
|
-
const stubs =
|
|
94
|
+
const stubs = pages.map(x => ({
|
|
87
95
|
id: x.id,
|
|
88
96
|
name: x.name,
|
|
89
97
|
path: x.path,
|
|
@@ -106,19 +114,26 @@ export function SubitemDiscoveryStep({ stepIndex, isActive = true, data, setData
|
|
|
106
114
|
const nodes = convertBackendTreeToTreeNodes(resp.trees);
|
|
107
115
|
setTreeNodes(nodes);
|
|
108
116
|
setExpandedIds(new Set(nodes.map(n => n.key)));
|
|
117
|
+
// Count total items and translatable items in the tree
|
|
109
118
|
const countItems = (nodeList) => {
|
|
110
119
|
let total = 0;
|
|
120
|
+
let translatable = 0;
|
|
111
121
|
const walk = (nodeList) => {
|
|
112
122
|
nodeList.forEach(node => {
|
|
113
123
|
total++;
|
|
124
|
+
if (node?.data?.isTranslatable === true) {
|
|
125
|
+
translatable++;
|
|
126
|
+
}
|
|
114
127
|
if (node.children)
|
|
115
128
|
walk(node.children);
|
|
116
129
|
});
|
|
117
130
|
};
|
|
118
131
|
walk(nodeList);
|
|
119
|
-
return total;
|
|
132
|
+
return { total, translatable };
|
|
120
133
|
};
|
|
121
|
-
|
|
134
|
+
const counts = countItems(nodes);
|
|
135
|
+
setTotalItemsCount(counts.total);
|
|
136
|
+
setTranslatableItemsCount(counts.translatable);
|
|
122
137
|
setDiscoveryComplete(true);
|
|
123
138
|
setTreeInitialized(true);
|
|
124
139
|
}
|
|
@@ -143,19 +158,19 @@ export function SubitemDiscoveryStep({ stepIndex, isActive = true, data, setData
|
|
|
143
158
|
initialSelection = new Set(data.discoveredItems.map(item => item.descriptor.id));
|
|
144
159
|
}
|
|
145
160
|
else {
|
|
146
|
-
// Default: select all
|
|
147
|
-
const
|
|
161
|
+
// Default: select all translatable items in the tree
|
|
162
|
+
const allTranslatableIds = new Set();
|
|
148
163
|
const walk = (nodes) => {
|
|
149
164
|
nodes.forEach(node => {
|
|
150
|
-
if (node?.data?.
|
|
151
|
-
|
|
165
|
+
if (node?.data?.isTranslatable) {
|
|
166
|
+
allTranslatableIds.add(node.key);
|
|
152
167
|
}
|
|
153
168
|
if (node.children)
|
|
154
169
|
walk(node.children);
|
|
155
170
|
});
|
|
156
171
|
};
|
|
157
172
|
walk(treeNodes);
|
|
158
|
-
initialSelection =
|
|
173
|
+
initialSelection = allTranslatableIds;
|
|
159
174
|
}
|
|
160
175
|
setSelectedItemIds(initialSelection);
|
|
161
176
|
}, [isActive, discoveryComplete, treeNodes, data.discoveredItems, userHasInteracted]);
|
|
@@ -180,7 +195,7 @@ export function SubitemDiscoveryStep({ stepIndex, isActive = true, data, setData
|
|
|
180
195
|
const buildNode = (item) => ({
|
|
181
196
|
key: item.id,
|
|
182
197
|
label: item.name || item.id,
|
|
183
|
-
data: { id: item.id, name: item.name, path: item.path,
|
|
198
|
+
data: { id: item.id, name: item.name, path: item.path, hasChildren: item.hasChildren },
|
|
184
199
|
children: (childrenMap.get(item.id) || []).map(buildNode)
|
|
185
200
|
});
|
|
186
201
|
const originalItemIds = new Set(data.items.map(item => item.descriptor.id));
|
|
@@ -190,6 +205,8 @@ export function SubitemDiscoveryStep({ stepIndex, isActive = true, data, setData
|
|
|
190
205
|
// PerfectTree integrations
|
|
191
206
|
const expandedKeys = useMemo(() => Array.from(expandedIds), [expandedIds]);
|
|
192
207
|
const selectedKeys = useMemo(() => Array.from(selectedItemIds), [selectedItemIds]);
|
|
208
|
+
// Memoize base item IDs for checkbox rendering
|
|
209
|
+
const baseItemIdsForCheckbox = useMemo(() => new Set(data.items.map(item => item.descriptor.id)), [data.items]);
|
|
193
210
|
const getDescendantIds = (nodeKey) => {
|
|
194
211
|
const findNode = (nodes) => {
|
|
195
212
|
for (const node of nodes) {
|
|
@@ -217,98 +234,167 @@ export function SubitemDiscoveryStep({ stepIndex, isActive = true, data, setData
|
|
|
217
234
|
traverse(node.children);
|
|
218
235
|
return descendants;
|
|
219
236
|
};
|
|
237
|
+
const getDescendantTranslatableIds = (nodeKey) => {
|
|
238
|
+
const findNode = (nodes) => {
|
|
239
|
+
for (const node of nodes) {
|
|
240
|
+
if (node.key === nodeKey)
|
|
241
|
+
return node;
|
|
242
|
+
if (node.children) {
|
|
243
|
+
const found = findNode(node.children);
|
|
244
|
+
if (found)
|
|
245
|
+
return found;
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
return null;
|
|
249
|
+
};
|
|
250
|
+
const root = findNode(treeNodes);
|
|
251
|
+
if (!root?.children)
|
|
252
|
+
return [];
|
|
253
|
+
const out = [];
|
|
254
|
+
const walk = (children) => {
|
|
255
|
+
children.forEach(child => {
|
|
256
|
+
const isTranslatable = child?.data?.isTranslatable === true;
|
|
257
|
+
if (isTranslatable)
|
|
258
|
+
out.push(child.key);
|
|
259
|
+
if (child.children)
|
|
260
|
+
walk(child.children);
|
|
261
|
+
});
|
|
262
|
+
};
|
|
263
|
+
walk(root.children);
|
|
264
|
+
return out;
|
|
265
|
+
};
|
|
220
266
|
const handleCancel = () => {
|
|
221
267
|
// Simplified cancel - just stop discovery
|
|
222
268
|
inFlightRef.current = false;
|
|
223
269
|
setIsDiscovering(false);
|
|
224
270
|
setDiscoveryComplete(true);
|
|
225
271
|
};
|
|
226
|
-
return (_jsxs("div", { className: "flex
|
|
272
|
+
return (_jsxs("div", { className: "flex flex-col gap-4 p-6 h-full", "data-testid": "subitem-discovery-step", children: [_jsxs("div", { className: "mb-2", children: [_jsx("h3", { className: "text-lg font-semibold text-[var(--color-dark)] mb-1", children: "Discover Subitems" }), _jsx("p", { className: "text-sm text-[var(--color-gray-2)]", children: "Scanning for subitems to include in translation..." })] }), _jsxs("div", { className: "border border-[var(--color-gray-3)] rounded-lg p-4 bg-background min-h-[120px] shadow-sm", "data-testid": "discovery-status-container", children: [_jsxs("div", { className: "flex items-center justify-between mb-4", children: [_jsxs("div", { className: "flex items-center gap-2", children: [isDiscovering && _jsx(Spinner, { "data-testid": "discovery-spinner" }), _jsx("span", { className: "font-medium text-[var(--color-dark)]", "data-testid": "discovery-status-text", children: isDiscovering ? 'Discovering subitems...' :
|
|
227
273
|
discoveryComplete ? 'Discovery Complete' :
|
|
228
|
-
'Ready to discover' })] }), isDiscovering && (_jsx(Button, { size: "sm", variant: "outline", onClick: handleCancel, "data-testid": "discovery-cancel-button", children: "Cancel" }))] }), _jsx("div", { className: "text-sm text-[var(--color-gray-2)] mb-3", "data-testid": "discovery-summary", children: discoveredCount > 0 ? (_jsxs("p", { "data-testid": "discovery-total-count", children: [_jsxs("span", { className: "font-medium text-[#9650fb]", children: [totalItemsCount, " total items found"] }), _jsx("br", {}), _jsxs("span", { className: "text-xs text-[var(--color-gray-2)]", children: ["Original items: ", data.items.length, " | Subitems discovered: ", Math.max(0, totalItemsCount - data.items.length)] }), isDiscovering && (_jsxs(_Fragment, { children: [_jsx("br", {}), _jsx("span", { className: "text-xs text-[var(--color-gray-2)] italic", children: "Item tree will appear when discovery completes" })] }))] })) : (_jsx("p", { className: "text-[var(--color-gray-2)]", children: "No items discovered yet." })) }), _jsx("div", { className: "mt-4
|
|
229
|
-
if (!isActive)
|
|
230
|
-
return;
|
|
231
|
-
const allIds = new Set();
|
|
232
|
-
const walk = (nodes) => {
|
|
233
|
-
nodes.forEach(node => {
|
|
234
|
-
allIds.add(node.key);
|
|
235
|
-
if (node.children)
|
|
236
|
-
walk(node.children);
|
|
237
|
-
});
|
|
238
|
-
};
|
|
239
|
-
walk(treeNodes);
|
|
240
|
-
setUserHasInteracted(true);
|
|
241
|
-
setSelectedItemIds(allIds);
|
|
242
|
-
onStepCompleted(allIds.size > 0);
|
|
243
|
-
}, "data-testid": "select-all-items-button", children: "Select All" }), _jsx(Button, { size: "sm", variant: "ghost", onClick: () => {
|
|
244
|
-
if (!isActive)
|
|
245
|
-
return;
|
|
246
|
-
setUserHasInteracted(true);
|
|
247
|
-
setSelectedItemIds(new Set());
|
|
248
|
-
onStepCompleted(false);
|
|
249
|
-
}, "data-testid": "select-none-items-button", children: "Select None" })] })] }), _jsx("div", { className: "text-xs text-[var(--color-gray-2)] mb-3", "data-testid": "selection-summary", children: _jsx("span", { children: "Tip: Hold Shift and click a checkbox to toggle all descendants." }) }), treeNodes.length > 0 && (_jsx("div", { className: "h-80 w-full min-w-0 shrink-0 overflow-auto rounded-lg border border-[var(--color-gray-3)] bg-[var(--color-gray-5)]", "data-testid": "item-tree-view", children: _jsx(PerfectTree, { nodes: treeNodes, expandedKeys: expandedKeys, selectedKeys: selectedKeys, onToggleExpand: (key) => {
|
|
250
|
-
setExpandedIds(prev => {
|
|
251
|
-
const next = new Set(prev);
|
|
252
|
-
if (next.has(key))
|
|
253
|
-
next.delete(key);
|
|
254
|
-
else
|
|
255
|
-
next.add(key);
|
|
256
|
-
return next;
|
|
257
|
-
});
|
|
258
|
-
}, onSelect: (key, event) => {
|
|
259
|
-
if (!isActive)
|
|
260
|
-
return; // Don't handle selection when inactive
|
|
261
|
-
setUserHasInteracted(true);
|
|
262
|
-
const targetNode = key;
|
|
263
|
-
const shift = event?.shiftKey === true;
|
|
264
|
-
const next = new Set(selectedItemIds);
|
|
265
|
-
const findNode = (nodes) => {
|
|
266
|
-
for (const n of nodes) {
|
|
267
|
-
if (n.key === targetNode)
|
|
268
|
-
return n;
|
|
269
|
-
if (n.children) {
|
|
270
|
-
const f = findNode(n.children);
|
|
271
|
-
if (f)
|
|
272
|
-
return f;
|
|
273
|
-
}
|
|
274
|
-
}
|
|
275
|
-
return null;
|
|
276
|
-
};
|
|
277
|
-
const n = findNode(treeNodes);
|
|
278
|
-
if (!n)
|
|
279
|
-
return;
|
|
280
|
-
const ids = shift ? [key, ...getDescendantIds(key)] : [key];
|
|
281
|
-
if (!next.has(key))
|
|
282
|
-
ids.forEach((id) => next.add(id));
|
|
283
|
-
else
|
|
284
|
-
ids.forEach((id) => next.delete(id));
|
|
285
|
-
setSelectedItemIds(next);
|
|
286
|
-
// Update completion when user changes selection
|
|
287
|
-
onStepCompleted(next.size > 0);
|
|
288
|
-
}, renderNode: (node) => {
|
|
289
|
-
const isChecked = selectedItemIds.has(node.key);
|
|
290
|
-
const icon = node?.data?.icon;
|
|
291
|
-
return (_jsxs("div", { className: "flex items-center gap-2", children: [icon && (_jsx("img", { src: icon, alt: "", className: "w-4 h-4" })), _jsx("input", { type: "checkbox", checked: isChecked, "data-testid": `tree-item-checkbox-${node.key}`, onMouseDown: (e) => {
|
|
292
|
-
shiftToggleRef.current = e.shiftKey;
|
|
293
|
-
}, onChange: (e) => {
|
|
274
|
+
'Ready to discover' })] }), isDiscovering && (_jsx(Button, { size: "sm", variant: "outline", onClick: handleCancel, "data-testid": "discovery-cancel-button", children: "Cancel" }))] }), _jsx("div", { className: "text-sm text-[var(--color-gray-2)] mb-3", "data-testid": "discovery-summary", children: discoveredCount > 0 ? (_jsxs("p", { "data-testid": "discovery-total-count", children: [_jsxs("span", { className: "font-medium text-[#9650fb]", children: [totalItemsCount, " total items found"] }), totalItemsCount !== translatableItemsCount && (_jsxs(_Fragment, { children: [_jsx("br", {}), _jsxs("span", { className: "text-xs text-[var(--color-gray-2)]", children: ["Translatable items: ", translatableItemsCount, " | Non-translatable: ", totalItemsCount - translatableItemsCount] })] })), _jsx("br", {}), _jsxs("span", { className: "text-xs text-[var(--color-gray-2)]", children: ["Original items: ", data.items.length, " | Subitems discovered: ", Math.max(0, totalItemsCount - data.items.length)] }), isDiscovering && (_jsxs(_Fragment, { children: [_jsx("br", {}), _jsx("span", { className: "text-xs text-[var(--color-gray-2)] italic", children: "Item tree will appear when discovery completes" })] }))] })) : (_jsx("p", { className: "text-[var(--color-gray-2)]", children: "No items discovered yet." })) }), _jsx("div", { className: "mt-4 min-h-[400px]", children: !isDiscovering && discoveryComplete && treeInitialized && allDiscoveredItems.length > 0 && treeNodes.length > 0 ? (_jsx("div", { children: _jsxs("div", { className: "border-t border-[var(--color-gray-3)] pt-4", "data-testid": "item-selection-section", children: [_jsxs("div", { className: "flex items-center justify-between mb-3", children: [_jsx("h3", { className: "text-lg font-semibold text-[var(--color-dark)]", children: "Select Items to Translate" }), _jsxs("div", { className: "flex items-center gap-4", children: [_jsx("span", { className: "text-sm text-[var(--color-gray-2)]", "data-testid": "selection-summary-header", children: selectedItemIds.size > 0 ? _jsxs(_Fragment, { children: [_jsx("span", { className: "font-medium text-[#9650fb]", children: selectedItemIds.size }), " selected"] }) : "No items selected" }), _jsx(Button, { size: "sm", variant: "outline", onClick: () => {
|
|
294
275
|
if (!isActive)
|
|
295
276
|
return;
|
|
277
|
+
// Only select translatable items
|
|
278
|
+
const translatableIds = new Set();
|
|
279
|
+
const walk = (nodes) => {
|
|
280
|
+
nodes.forEach(node => {
|
|
281
|
+
if (node?.data?.isTranslatable === true) {
|
|
282
|
+
translatableIds.add(node.key);
|
|
283
|
+
}
|
|
284
|
+
if (node.children)
|
|
285
|
+
walk(node.children);
|
|
286
|
+
});
|
|
287
|
+
};
|
|
288
|
+
walk(treeNodes);
|
|
296
289
|
setUserHasInteracted(true);
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
if (
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
290
|
+
setSelectedItemIds(translatableIds);
|
|
291
|
+
onStepCompleted(translatableIds.size > 0);
|
|
292
|
+
}, "data-testid": "select-all-items-button", children: "Select All" }), _jsx(Button, { size: "sm", variant: "ghost", onClick: () => {
|
|
293
|
+
if (!isActive)
|
|
294
|
+
return;
|
|
295
|
+
setUserHasInteracted(true);
|
|
296
|
+
setSelectedItemIds(new Set());
|
|
297
|
+
onStepCompleted(false);
|
|
298
|
+
}, "data-testid": "select-none-items-button", children: "Select None" })] })] }), _jsx("div", { className: "text-xs text-[var(--color-gray-2)] mb-3", "data-testid": "selection-summary", children: _jsx("span", { children: "Tip: Hold Shift and click a checkbox to toggle all descendants." }) }), treeNodes.length > 0 && (_jsx("div", { className: "border border-[var(--color-gray-3)] rounded-lg h-80 overflow-auto bg-[var(--color-gray-5)]", "data-testid": "item-tree-view", children: _jsx(PerfectTree, { nodes: treeNodes, expandedKeys: expandedKeys, selectedKeys: selectedKeys, onToggleExpand: (key) => {
|
|
299
|
+
setExpandedIds(prev => {
|
|
300
|
+
const next = new Set(prev);
|
|
301
|
+
if (next.has(key))
|
|
302
|
+
next.delete(key);
|
|
303
|
+
else
|
|
304
|
+
next.add(key);
|
|
305
|
+
return next;
|
|
306
|
+
});
|
|
307
|
+
}, onSelect: (key, event) => {
|
|
308
|
+
if (!isActive)
|
|
309
|
+
return; // Don't handle selection when inactive
|
|
310
|
+
setUserHasInteracted(true);
|
|
311
|
+
const targetNode = key;
|
|
312
|
+
const shift = event?.shiftKey === true;
|
|
313
|
+
const next = new Set(selectedItemIds);
|
|
314
|
+
// Only select translatable items
|
|
315
|
+
const findNode = (nodes) => {
|
|
316
|
+
for (const n of nodes) {
|
|
317
|
+
if (n.key === targetNode)
|
|
318
|
+
return n;
|
|
319
|
+
if (n.children) {
|
|
320
|
+
const f = findNode(n.children);
|
|
321
|
+
if (f)
|
|
322
|
+
return f;
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
return null;
|
|
326
|
+
};
|
|
327
|
+
const n = findNode(treeNodes);
|
|
328
|
+
const isTranslatable = n?.data?.isTranslatable === true;
|
|
329
|
+
if (isTranslatable) {
|
|
330
|
+
const ids = shift ? [key, ...getDescendantIds(key)] : [key];
|
|
331
|
+
if (!next.has(key))
|
|
332
|
+
ids.forEach((id) => next.add(id));
|
|
333
|
+
else
|
|
334
|
+
ids.forEach((id) => next.delete(id));
|
|
335
|
+
}
|
|
336
|
+
else {
|
|
337
|
+
// Folder row click toggles all descendant translatable items
|
|
338
|
+
const translatableIds = getDescendantTranslatableIds(key);
|
|
339
|
+
const allSelected = translatableIds.length > 0 && translatableIds.every((id) => next.has(id));
|
|
340
|
+
if (allSelected)
|
|
341
|
+
translatableIds.forEach((id) => next.delete(id));
|
|
342
|
+
else
|
|
343
|
+
translatableIds.forEach((id) => next.add(id));
|
|
344
|
+
}
|
|
345
|
+
setSelectedItemIds(next);
|
|
346
|
+
// Update completion when user changes selection
|
|
347
|
+
onStepCompleted(next.size > 0);
|
|
348
|
+
}, renderNode: (node) => {
|
|
349
|
+
const isTranslatable = node?.data?.isTranslatable === true;
|
|
350
|
+
const translatableIds = !isTranslatable ? getDescendantTranslatableIds(node.key) : [];
|
|
351
|
+
const allSelected = !isTranslatable ? (translatableIds.length > 0 && translatableIds.every((id) => selectedItemIds.has(id))) : false;
|
|
352
|
+
const someSelected = !isTranslatable ? (translatableIds.some((id) => selectedItemIds.has(id)) && !allSelected) : false;
|
|
353
|
+
// Simple checkbox logic: if it's translatable, check direct selection; otherwise check descendant translatable items
|
|
354
|
+
// Base items are treated like any other selected item - if selected, show checked
|
|
355
|
+
const isChecked = isTranslatable ? selectedItemIds.has(node.key) : allSelected;
|
|
356
|
+
const icon = node?.data?.icon;
|
|
357
|
+
return (_jsxs("div", { className: `flex items-center gap-2 ${!isTranslatable ? 'opacity-70' : ''}`, children: [icon && (_jsx("img", { src: icon, alt: "", className: `w-4 h-4 ${!isTranslatable ? 'opacity-50' : ''}` })), _jsx("input", { type: "checkbox", checked: isChecked, disabled: !isTranslatable, "data-testid": `tree-item-checkbox-${node.key}`, ref: (el) => { if (el)
|
|
358
|
+
el.indeterminate = someSelected; }, onMouseDown: (e) => {
|
|
359
|
+
if (!isTranslatable)
|
|
360
|
+
return; // Don't handle mouse down for untranslatable items
|
|
361
|
+
shiftToggleRef.current = e.shiftKey;
|
|
362
|
+
}, onChange: (e) => {
|
|
363
|
+
if (!isActive || !isTranslatable)
|
|
364
|
+
return; // Don't handle changes when inactive or untranslatable
|
|
365
|
+
setUserHasInteracted(true);
|
|
366
|
+
const next = new Set(selectedItemIds);
|
|
367
|
+
if (isTranslatable) {
|
|
368
|
+
const withDesc = shiftToggleRef.current && (node.children?.length ?? 0) > 0;
|
|
369
|
+
const ids = withDesc ? [node.key, ...getDescendantIds(node.key)] : [node.key];
|
|
370
|
+
if (e.currentTarget.checked)
|
|
371
|
+
ids.forEach((id) => next.add(id));
|
|
372
|
+
else
|
|
373
|
+
ids.forEach((id) => next.delete(id));
|
|
374
|
+
}
|
|
375
|
+
else {
|
|
376
|
+
const ids = getDescendantTranslatableIds(node.key);
|
|
377
|
+
const allSel = ids.length > 0 && ids.every((id) => next.has(id));
|
|
378
|
+
if (e.currentTarget.checked && !allSel)
|
|
379
|
+
ids.forEach((id) => next.add(id));
|
|
380
|
+
else if (!e.currentTarget.checked && allSel)
|
|
381
|
+
ids.forEach((id) => next.delete(id));
|
|
382
|
+
else {
|
|
383
|
+
// Toggle based on current
|
|
384
|
+
if (allSel)
|
|
385
|
+
ids.forEach((id) => next.delete(id));
|
|
386
|
+
else
|
|
387
|
+
ids.forEach((id) => next.add(id));
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
setSelectedItemIds(next);
|
|
391
|
+
shiftToggleRef.current = false;
|
|
392
|
+
// Update completion when user changes selection
|
|
393
|
+
onStepCompleted(next.size > 0);
|
|
394
|
+
} }), _jsx("span", { className: `${!isTranslatable ? 'opacity-70 italic' : ''}`, children: node.label })] }));
|
|
395
|
+
} }) }))] }) })) : isDiscovering ? (
|
|
310
396
|
// Loading skeleton for discovery results
|
|
311
|
-
_jsxs("div", {
|
|
397
|
+
_jsxs("div", { children: [_jsx("div", { className: "p-3 bg-[#f6eeff] border border-[#9650fb]/20 rounded-lg mb-4", children: _jsx("div", { className: "h-4 bg-[#9650fb]/20 rounded w-64 animate-pulse" }) }), _jsxs("div", { className: "border-t border-[var(--color-gray-3)] pt-4", children: [_jsxs("div", { className: "flex items-center justify-between mb-3", children: [_jsx("div", { className: "h-6 bg-[var(--color-gray-3)] rounded w-48 animate-pulse" }), _jsxs("div", { className: "flex gap-2", children: [_jsx("div", { className: "h-8 bg-[var(--color-gray-3)] rounded w-20 animate-pulse" }), _jsx("div", { className: "h-8 bg-[var(--color-gray-3)] rounded w-20 animate-pulse" })] })] }), _jsx("div", { className: "h-4 bg-[var(--color-gray-3)] rounded w-64 mb-3 animate-pulse" }), _jsx("div", { className: "border border-[var(--color-gray-3)] rounded-lg h-80 overflow-auto bg-[var(--color-gray-5)]", children: _jsx("div", { className: "p-3 space-y-2", children: [...Array(8)].map((_, i) => (_jsxs("div", { className: "flex items-center gap-3 animate-pulse", children: [_jsx("div", { className: "h-4 w-4 bg-[var(--color-gray-3)] rounded" }), _jsx("div", { className: "h-4 bg-[var(--color-gray-3)] rounded w-48" })] }, i))) }) })] })] })) : (
|
|
312
398
|
// Empty state
|
|
313
|
-
_jsx("div", { className: "flex
|
|
399
|
+
_jsx("div", { className: "flex items-center justify-center h-80 border border-[var(--color-gray-3)] rounded-lg bg-[var(--color-gray-5)]", children: _jsxs("div", { className: "text-center text-[var(--color-gray-2)]", children: [_jsx("p", { className: "font-medium text-[var(--color-gray-1)]", children: "Ready to discover subitems" }), _jsx("p", { className: "text-sm mt-1", children: "Discovery will start automatically" })] }) })) })] })] }));
|
|
314
400
|
}
|
|
@@ -3,6 +3,6 @@ interface BatchTranslationViewProps {
|
|
|
3
3
|
batchId: string;
|
|
4
4
|
onBack?: () => void;
|
|
5
5
|
}
|
|
6
|
-
export declare function BatchTranslationView({ batchId, onBack
|
|
6
|
+
export declare function BatchTranslationView({ batchId, onBack }: BatchTranslationViewProps): import("react/jsx-runtime").JSX.Element;
|
|
7
7
|
export {};
|
|
8
8
|
//# sourceMappingURL=BatchTranslationView.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"BatchTranslationView.d.ts","sourceRoot":"","sources":["../../src/translation-center/BatchTranslationView.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"BatchTranslationView.d.ts","sourceRoot":"","sources":["../../src/translation-center/BatchTranslationView.tsx"],"names":[],"mappings":"AAyBA,OAAO,qCAAqC,CAAC;AAsB7C,UAAU,yBAAyB;IACjC,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,IAAI,CAAC;CACrB;AAiCD,wBAAgB,oBAAoB,CAAC,EAAE,OAAO,EAAE,MAAM,EAAE,EAAE,yBAAyB,2CAy8BlF"}
|