@lesliechan721/aw 0.1.0-beta.14 → 0.1.0-beta.16
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/src/commands/aw-capabilities.d.ts +1 -1
- package/dist/src/commands/aw-capabilities.js +1 -0
- package/dist/src/commands/aw-capabilities.js.map +1 -1
- package/dist/src/commands/review-inspection.d.ts +169 -0
- package/dist/src/commands/review-inspection.js +916 -0
- package/dist/src/commands/review-inspection.js.map +1 -0
- package/dist/src/commands/skill-eval.d.ts +5 -0
- package/dist/src/commands/skill-eval.js +86 -63
- package/dist/src/commands/skill-eval.js.map +1 -1
- package/dist/src/commands/status-v6.d.ts +2 -1
- package/dist/src/commands/status-v6.js +7 -1
- package/dist/src/commands/status-v6.js.map +1 -1
- package/dist/src/eval/manifest-v1.js +0 -5
- package/dist/src/eval/manifest-v1.js.map +1 -1
- package/dist/src/index.js +39 -6
- package/dist/src/index.js.map +1 -1
- package/dist/src/portable/commands.d.ts +1 -0
- package/dist/src/portable/commands.js +43 -1
- package/dist/src/portable/commands.js.map +1 -1
- package/dist/src/portable/materializer.d.ts +12 -0
- package/dist/src/portable/materializer.js +67 -41
- package/dist/src/portable/materializer.js.map +1 -1
- package/dist/src/review/eval-source-input.d.ts +43 -0
- package/dist/src/review/eval-source-input.js +207 -0
- package/dist/src/review/eval-source-input.js.map +1 -0
- package/dist/src/review/source-binding.d.ts +170 -0
- package/dist/src/review/source-binding.js +622 -0
- package/dist/src/review/source-binding.js.map +1 -0
- package/dist/src/tui/console-app-v4.js +523 -133
- package/dist/src/tui/console-app-v4.js.map +1 -1
- package/dist/src/tui/controller-runtime.js +59 -37
- package/dist/src/tui/controller-runtime.js.map +1 -1
- package/dist/src/tui/controller.d.ts +15 -1
- package/dist/src/tui/controller.js +86 -3
- package/dist/src/tui/controller.js.map +1 -1
- package/dist/src/tui/model.js +2 -3
- package/dist/src/tui/model.js.map +1 -1
- package/dist/src/tui/operation-review-planner.js +22 -1
- package/dist/src/tui/operation-review-planner.js.map +1 -1
- package/dist/src/tui/plugin-write-task.d.ts +2 -1
- package/dist/src/tui/plugin-write-task.js +17 -0
- package/dist/src/tui/plugin-write-task.js.map +1 -1
- package/dist/src/tui/router.d.ts +1 -0
- package/dist/src/tui/router.js +13 -5
- package/dist/src/tui/router.js.map +1 -1
- package/dist/src/tui/screen-registry.d.ts +21 -1
- package/dist/src/tui/screen-registry.js +5 -1
- package/dist/src/tui/screen-registry.js.map +1 -1
- package/package.json +3 -3
|
@@ -13,6 +13,7 @@ import { projectCatalogSourceLocation } from '../utils/catalog-source-presentati
|
|
|
13
13
|
import { ActionRow, Callout, ChoiceRow, diagnosticDetailRowCount, DiagnosticSummary, InputCard, PluginListRow, ProgressRow, ResultCard, ResultRow, ReviewCard, ReviewChangeRow, ScreenFrame, SectionCard, SectionRow, StaticRow, StatusCard, TaskRow, TechnicalDetails, ToggleRow, } from './components.js';
|
|
14
14
|
import { safeDiagnosticCode, sanitizeDiagnosticText, } from './diagnostics.js';
|
|
15
15
|
import { extractPortableError, TuiConfigInvalidStoreError, userIssueKey, } from './controller.js';
|
|
16
|
+
import { pluginWriteTaskVersionEvidence } from './plugin-write-task.js';
|
|
16
17
|
import { catalogSourceFreshnessLabel, catalogSourceListPresentation, } from './catalog-sources/screens.js';
|
|
17
18
|
import { CatalogManagementIntro, CatalogPublicationSummary, CatalogStoreSummary, CatalogWorkspaceSummary, } from './catalog-management-screens.js';
|
|
18
19
|
import { operationDiagnosticLineCount, operationHistoryMeta, operationHistoryState, operationReportLineCount, OperationDiagnosticScreen, OperationReportPreviewScreen, } from './operation-diagnostic-screens.js';
|
|
@@ -175,13 +176,16 @@ function pluginWriteTaskTargetKey(candidate) {
|
|
|
175
176
|
const { pluginId, platform, scope } = candidate.target;
|
|
176
177
|
return [pluginId, platform, scope].join('\0');
|
|
177
178
|
}
|
|
178
|
-
function
|
|
179
|
-
return
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
179
|
+
function dependencyGraphContextKey(pluginId, direction) {
|
|
180
|
+
return `${pluginId}\0${direction}`;
|
|
181
|
+
}
|
|
182
|
+
function dependencyGraphActionIds(projection) {
|
|
183
|
+
return [
|
|
184
|
+
...projection.rows.map((row) => `row:${row.id}`),
|
|
185
|
+
...projection.scopeSummary.unknownConsumerPluginIds.map((pluginId) => `unverified:${pluginId}`),
|
|
186
|
+
'direction',
|
|
187
|
+
'technical',
|
|
188
|
+
];
|
|
185
189
|
}
|
|
186
190
|
function statusText(plugin) {
|
|
187
191
|
if (plugin.configOnly)
|
|
@@ -927,6 +931,7 @@ export function ConsoleAppV4(props) {
|
|
|
927
931
|
const [pendingLayerFieldKey, setPendingLayerFieldKey] = useState();
|
|
928
932
|
const [confirmedSharedReviewToken, setConfirmedSharedReviewToken] = useState();
|
|
929
933
|
const dynamicMenuFocus = useRef(undefined);
|
|
934
|
+
const dependencyGraphContexts = useRef(new Map());
|
|
930
935
|
const externalDraftRevision = useRef({ config: 0, source: 0, batch: 0, catalog: 0 });
|
|
931
936
|
const previousExternalDraftValues = useRef({
|
|
932
937
|
draftUpdates, draftRemoves, objectDrafts, changedObjectDraftKeys, sourceDraft, batchDraft,
|
|
@@ -1039,9 +1044,9 @@ export function ConsoleAppV4(props) {
|
|
|
1039
1044
|
|| snapshot.result.operationKind !== 'pluginWriteTask'
|
|
1040
1045
|
|| !('returnRoute' in snapshot.result)
|
|
1041
1046
|
|| snapshot.result.returnRoute?.name !== 'pluginWriteTask'
|
|
1047
|
+
|| snapshot.result.returnRoute.goal !== snapshot.result.summary.task?.goal
|
|
1042
1048
|
|| snapshot.result.returnRoute.batchOrigin?.owner !== 'batch-write'
|
|
1043
|
-
|| snapshot.result.returnRoute.batchOrigin.flowId
|
|
1044
|
-
!== batchDraft?.flowId)
|
|
1049
|
+
|| snapshot.result.returnRoute.batchOrigin.flowId !== batchDraft?.flowId)
|
|
1045
1050
|
return;
|
|
1046
1051
|
setBatchDraft(undefined);
|
|
1047
1052
|
setDirty(false);
|
|
@@ -1439,6 +1444,7 @@ export function ConsoleAppV4(props) {
|
|
|
1439
1444
|
const menuCount = itemCount > 0 && !query ? 1
|
|
1440
1445
|
+ Number(route.mode === 'installed' && props.controller.listPluginWriteTaskCandidates('update')
|
|
1441
1446
|
.some((candidate) => candidate.eligible && listed.has(candidate.target.pluginId)))
|
|
1447
|
+
+ Number(route.mode === 'installed' && props.controller.listCurrentProjectIntentRoots().length > 0)
|
|
1442
1448
|
+ Number(route.mode === 'installed' && props.controller.listPluginWriteTaskCandidates('restore')
|
|
1443
1449
|
.some((candidate) => candidate.target.scope === 'project')) : 0;
|
|
1444
1450
|
count = itemCount + menuCount || 1;
|
|
@@ -1465,13 +1471,48 @@ export function ConsoleAppV4(props) {
|
|
|
1465
1471
|
useEffect(() => {
|
|
1466
1472
|
if (route.name !== 'pluginDependencies')
|
|
1467
1473
|
return;
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1474
|
+
const direction = route.direction ?? 'dependencies';
|
|
1475
|
+
const key = dependencyGraphContextKey(route.pluginId, direction);
|
|
1476
|
+
const stored = dependencyGraphContexts.current.get(key);
|
|
1477
|
+
const overviews = props.controller.listPluginDependencyOverviews();
|
|
1478
|
+
const knownPluginIds = new Set(overviews.flatMap((overview) => [
|
|
1479
|
+
overview.pluginId,
|
|
1480
|
+
...overview.dependencies.map((item) => item.pluginId),
|
|
1481
|
+
...overview.dependents.map((item) => item.pluginId),
|
|
1482
|
+
]));
|
|
1483
|
+
const expandedPluginIds = stored
|
|
1484
|
+
? new Set([...stored.expandedPluginIds].filter((pluginId) => knownPluginIds.has(pluginId)))
|
|
1485
|
+
: undefined;
|
|
1486
|
+
const projection = buildDependencyGraphProjection({
|
|
1487
|
+
overviews,
|
|
1488
|
+
rootPluginId: route.pluginId,
|
|
1489
|
+
direction,
|
|
1490
|
+
...(expandedPluginIds ? { expandedPluginIds } : {}),
|
|
1491
|
+
});
|
|
1492
|
+
const actionIds = dependencyGraphActionIds(projection);
|
|
1493
|
+
const normalizedExpanded = expandedPluginIds ?? new Set(projection.defaultExpandedPluginIds);
|
|
1494
|
+
const focusedIndex = stored?.focusedActionId
|
|
1495
|
+
? actionIds.indexOf(stored.focusedActionId)
|
|
1496
|
+
: 0;
|
|
1497
|
+
const nextFocus = focusedIndex >= 0 ? focusedIndex : 0;
|
|
1498
|
+
const anchorIndex = stored?.scrollAnchorActionId
|
|
1499
|
+
? actionIds.indexOf(stored.scrollAnchorActionId)
|
|
1500
|
+
: 0;
|
|
1501
|
+
const nextScroll = Math.max(0, anchorIndex >= 0 ? Math.min(anchorIndex, nextFocus) : 0);
|
|
1502
|
+
setExpandedDependencyPluginIds(normalizedExpanded);
|
|
1503
|
+
setFocus(nextFocus);
|
|
1504
|
+
setScrollOffset(nextScroll);
|
|
1505
|
+
dependencyGraphContexts.current.set(key, {
|
|
1506
|
+
expandedPluginIds: normalizedExpanded,
|
|
1507
|
+
...(actionIds[nextFocus] ? { focusedActionId: actionIds[nextFocus] } : {}),
|
|
1508
|
+
...(actionIds[nextScroll] ? { scrollAnchorActionId: actionIds[nextScroll] } : {}),
|
|
1509
|
+
});
|
|
1471
1510
|
}, [
|
|
1511
|
+
props.controller,
|
|
1472
1512
|
route.name,
|
|
1473
1513
|
route.name === 'pluginDependencies' ? route.pluginId : '',
|
|
1474
1514
|
route.name === 'pluginDependencies' ? route.direction ?? 'dependencies' : '',
|
|
1515
|
+
snapshot.viewModel,
|
|
1475
1516
|
]);
|
|
1476
1517
|
useEffect(() => {
|
|
1477
1518
|
if (route.name !== 'pluginList' || snapshot.phase !== 'ready'
|
|
@@ -1489,6 +1530,7 @@ export function ConsoleAppV4(props) {
|
|
|
1489
1530
|
const menuCount = query ? 0 : 1
|
|
1490
1531
|
+ Number(route.mode === 'installed' && props.controller.listPluginWriteTaskCandidates('update')
|
|
1491
1532
|
.some((candidate) => candidate.eligible && listed.has(candidate.target.pluginId)))
|
|
1533
|
+
+ Number(route.mode === 'installed' && props.controller.listCurrentProjectIntentRoots().length > 0)
|
|
1492
1534
|
+ Number(route.mode === 'installed' && props.controller.listPluginWriteTaskCandidates('restore')
|
|
1493
1535
|
.some((candidate) => candidate.target.scope === 'project'));
|
|
1494
1536
|
setFocus(menuCount);
|
|
@@ -1678,6 +1720,7 @@ export function ConsoleAppV4(props) {
|
|
|
1678
1720
|
return 1
|
|
1679
1721
|
+ Number(mode === 'installed' && props.controller.listPluginWriteTaskCandidates('update')
|
|
1680
1722
|
.some((candidate) => candidate.eligible && listed.has(candidate.target.pluginId)))
|
|
1723
|
+
+ Number(mode === 'installed' && props.controller.listCurrentProjectIntentRoots().length > 0)
|
|
1681
1724
|
+ Number(mode === 'installed' && props.controller.listPluginWriteTaskCandidates('restore')
|
|
1682
1725
|
.some((candidate) => candidate.target.scope === 'project'));
|
|
1683
1726
|
};
|
|
@@ -1820,6 +1863,38 @@ export function ConsoleAppV4(props) {
|
|
|
1820
1863
|
setFocus(0);
|
|
1821
1864
|
setScrollOffset(0);
|
|
1822
1865
|
};
|
|
1866
|
+
useEffect(() => {
|
|
1867
|
+
if (route.name !== 'pluginWriteTask' || route.goal !== 'restore')
|
|
1868
|
+
return;
|
|
1869
|
+
if (snapshot.router.current.context.autoPreparedRestore)
|
|
1870
|
+
return;
|
|
1871
|
+
const retainedTargetKeys = route.targetKeys ? new Set(route.targetKeys) : undefined;
|
|
1872
|
+
const allCandidates = props.controller.listPluginWriteTaskCandidates('restore');
|
|
1873
|
+
const currentTargetKeys = new Set(allCandidates.map(pluginWriteTaskTargetKey));
|
|
1874
|
+
if (route.targetKeys?.some((targetKey) => !currentTargetKeys.has(targetKey)))
|
|
1875
|
+
return;
|
|
1876
|
+
const candidates = allCandidates
|
|
1877
|
+
.filter((candidate) => retainedTargetKeys
|
|
1878
|
+
? retainedTargetKeys.has(pluginWriteTaskTargetKey(candidate))
|
|
1879
|
+
: candidate.target.pluginId === route.pluginId);
|
|
1880
|
+
if (candidates.length === 0 || candidates.some((candidate) => !candidate.eligible))
|
|
1881
|
+
return;
|
|
1882
|
+
props.controller.updatePageContext({ autoPreparedRestore: true });
|
|
1883
|
+
beginReview({
|
|
1884
|
+
kind: 'pluginWriteTask',
|
|
1885
|
+
goal: 'restore',
|
|
1886
|
+
targets: candidates.map((candidate) => candidate.target),
|
|
1887
|
+
executionPolicy: 'all',
|
|
1888
|
+
}, props.controller.pluginWriteTaskReturnRoute(route));
|
|
1889
|
+
}, [
|
|
1890
|
+
props.controller,
|
|
1891
|
+
route.name,
|
|
1892
|
+
route.name === 'pluginWriteTask' ? route.goal : '',
|
|
1893
|
+
route.name === 'pluginWriteTask' ? route.pluginId : '',
|
|
1894
|
+
route.name === 'pluginWriteTask' ? route.targetKeys?.join('\0') ?? '' : '',
|
|
1895
|
+
snapshot.router.current.context.autoPreparedRestore,
|
|
1896
|
+
snapshot.viewModel,
|
|
1897
|
+
]);
|
|
1823
1898
|
const openBatchSelection = (action) => {
|
|
1824
1899
|
props.controller.updatePageContext({
|
|
1825
1900
|
focusIndex: focus,
|
|
@@ -1848,7 +1923,7 @@ export function ConsoleAppV4(props) {
|
|
|
1848
1923
|
showSelected: false,
|
|
1849
1924
|
});
|
|
1850
1925
|
setDirty(true);
|
|
1851
|
-
setFocus(
|
|
1926
|
+
setFocus(1);
|
|
1852
1927
|
setScrollOffset(0);
|
|
1853
1928
|
setSearchFocus(false);
|
|
1854
1929
|
};
|
|
@@ -1856,21 +1931,43 @@ export function ConsoleAppV4(props) {
|
|
|
1856
1931
|
const mode = draft.mode;
|
|
1857
1932
|
const all = props.controller.listPlugins({ mode });
|
|
1858
1933
|
if (draft.showSelected) {
|
|
1859
|
-
const
|
|
1860
|
-
return
|
|
1934
|
+
const byId = new Map(all.map((candidate) => [candidate.pluginId, candidate]));
|
|
1935
|
+
return draft.selectedPluginIds.map((pluginId) => ({
|
|
1936
|
+
pluginId,
|
|
1937
|
+
...(byId.get(pluginId) ? { plugin: byId.get(pluginId) } : {}),
|
|
1938
|
+
}));
|
|
1861
1939
|
}
|
|
1862
1940
|
return props.controller.listPlugins({
|
|
1863
1941
|
mode,
|
|
1864
1942
|
...(draft.sourceId ? { sourceId: draft.sourceId } : {}),
|
|
1865
1943
|
...(draft.query ? { query: draft.query } : {}),
|
|
1866
1944
|
installFilter: draft.installFilter,
|
|
1945
|
+
}).map((plugin) => ({ pluginId: plugin.pluginId, plugin }));
|
|
1946
|
+
};
|
|
1947
|
+
const batchCandidateBlockReason = (draft, candidate) => !candidate.plugin
|
|
1948
|
+
? '插件已不在当前状态中,可取消选择'
|
|
1949
|
+
: candidate.plugin.configOnly
|
|
1950
|
+
? '来源中没有可信插件定义'
|
|
1951
|
+
: draft.mode === 'browse' && hasInstalledPlacement(candidate.plugin)
|
|
1952
|
+
? '这个插件已经添加'
|
|
1953
|
+
: undefined;
|
|
1954
|
+
const invalidBatchSelection = (draft) => {
|
|
1955
|
+
const byId = new Map(props.controller.listPlugins({ mode: draft.mode })
|
|
1956
|
+
.map((plugin) => [plugin.pluginId, plugin]));
|
|
1957
|
+
return draft.selectedPluginIds.filter((pluginId) => batchCandidateBlockReason(draft, {
|
|
1958
|
+
pluginId,
|
|
1959
|
+
...(byId.get(pluginId) ? { plugin: byId.get(pluginId) } : {}),
|
|
1960
|
+
}) !== undefined);
|
|
1961
|
+
};
|
|
1962
|
+
const persistDependencyGraphContext = (graphRoute, projection, expandedPluginIds, focusIndex, offset) => {
|
|
1963
|
+
const direction = graphRoute.direction ?? 'dependencies';
|
|
1964
|
+
const actionIds = dependencyGraphActionIds(projection);
|
|
1965
|
+
dependencyGraphContexts.current.set(dependencyGraphContextKey(graphRoute.pluginId, direction), {
|
|
1966
|
+
expandedPluginIds: new Set(expandedPluginIds),
|
|
1967
|
+
...(actionIds[focusIndex] ? { focusedActionId: actionIds[focusIndex] } : {}),
|
|
1968
|
+
...(actionIds[offset] ? { scrollAnchorActionId: actionIds[offset] } : {}),
|
|
1867
1969
|
});
|
|
1868
1970
|
};
|
|
1869
|
-
const batchCandidateBlockReason = (draft, candidate) => candidate.configOnly
|
|
1870
|
-
? '来源中没有可信插件定义'
|
|
1871
|
-
: draft.mode === 'browse' && hasInstalledPlacement(candidate)
|
|
1872
|
-
? '这个插件已经添加'
|
|
1873
|
-
: undefined;
|
|
1874
1971
|
const toggleBatchPlugin = (pluginId) => {
|
|
1875
1972
|
setBatchDraft((current) => current ? {
|
|
1876
1973
|
...current,
|
|
@@ -1879,15 +1976,24 @@ export function ConsoleAppV4(props) {
|
|
|
1879
1976
|
: [...current.selectedPluginIds, pluginId],
|
|
1880
1977
|
} : current);
|
|
1881
1978
|
};
|
|
1882
|
-
const
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
:
|
|
1979
|
+
const prepareCurrentProjectPlugins = () => {
|
|
1980
|
+
if (props.controller.listCurrentProjectIntentRoots().length === 0) {
|
|
1981
|
+
setActionError('当前项目没有已保存的插件清单。');
|
|
1982
|
+
return;
|
|
1983
|
+
}
|
|
1984
|
+
beginReview({
|
|
1985
|
+
kind: 'pluginWriteBatch',
|
|
1986
|
+
action: 'sync',
|
|
1987
|
+
selection: { kind: 'currentProjectIntentRoots' },
|
|
1988
|
+
platform: 'both',
|
|
1989
|
+
scope: 'project',
|
|
1990
|
+
});
|
|
1991
|
+
};
|
|
1890
1992
|
const openSelectedPluginTask = (goal, pluginIds, scopeConstraint) => {
|
|
1993
|
+
if (batchDraft && invalidBatchSelection(batchDraft).length > 0) {
|
|
1994
|
+
setActionError('已选插件状态已变化。请返回选择页检查并取消失效项。');
|
|
1995
|
+
return;
|
|
1996
|
+
}
|
|
1891
1997
|
const selected = new Set(pluginIds);
|
|
1892
1998
|
const candidates = props.controller.listPluginWriteTaskCandidates(goal)
|
|
1893
1999
|
.filter((candidate) => selected.has(candidate.target.pluginId)
|
|
@@ -1908,9 +2014,10 @@ export function ConsoleAppV4(props) {
|
|
|
1908
2014
|
batchOrigin: { flowId: batchDraft.flowId, owner: 'batch-write' },
|
|
1909
2015
|
} : {}),
|
|
1910
2016
|
...(goal === 'update'
|
|
1911
|
-
? { versionEvidence: Object.
|
|
1912
|
-
|
|
1913
|
-
|
|
2017
|
+
? { versionEvidence: Object.assign({}, ...[...selected].map((pluginId) => {
|
|
2018
|
+
const candidatePlugin = props.controller.getPluginOverview(pluginId);
|
|
2019
|
+
return candidatePlugin ? pluginWriteTaskVersionEvidence(candidatePlugin, candidates.filter((candidate) => candidate.target.pluginId === pluginId)) : {};
|
|
2020
|
+
})) }
|
|
1914
2021
|
: {}),
|
|
1915
2022
|
});
|
|
1916
2023
|
};
|
|
@@ -2401,18 +2508,19 @@ export function ConsoleAppV4(props) {
|
|
|
2401
2508
|
const openConfigLayerTarget = (layer) => {
|
|
2402
2509
|
if (route.name !== 'configLayer')
|
|
2403
2510
|
return;
|
|
2404
|
-
const
|
|
2405
|
-
|
|
2511
|
+
const definitionPage = configPage ?? configLayerPages.user ?? configLayerPages.project;
|
|
2512
|
+
const field = route.fieldKey && definitionPage
|
|
2513
|
+
? definitionPage.groups.flatMap((group) => group.fields).find((candidate) => candidate.key === route.fieldKey)
|
|
2406
2514
|
: undefined;
|
|
2407
|
-
const fieldTarget =
|
|
2408
|
-
const preset = route.presetId &&
|
|
2409
|
-
?
|
|
2515
|
+
const fieldTarget = definitionPage ? configFieldTarget(definitionPage, route.fieldKey) : undefined;
|
|
2516
|
+
const preset = route.presetId && definitionPage
|
|
2517
|
+
? definitionPage.presets.find((candidate) => candidate.id === route.presetId)
|
|
2410
2518
|
: undefined;
|
|
2411
2519
|
const presetTarget = preset
|
|
2412
|
-
? configFieldTarget(
|
|
2520
|
+
? configFieldTarget(definitionPage, Object.keys(preset.values)[0])
|
|
2413
2521
|
: undefined;
|
|
2414
2522
|
const target = fieldTarget ?? (route.groupId ? { groupId: route.groupId, focusIndex: 0 } : undefined) ?? presetTarget
|
|
2415
|
-
?? (
|
|
2523
|
+
?? (definitionPage?.groups[0] ? { groupId: definitionPage.groups[0].id, focusIndex: 0 } : undefined);
|
|
2416
2524
|
const context = target
|
|
2417
2525
|
? { configGroupId: target.groupId, focusIndex: target.focusIndex, scrollOffset: 0 }
|
|
2418
2526
|
: {};
|
|
@@ -2703,7 +2811,7 @@ export function ConsoleAppV4(props) {
|
|
|
2703
2811
|
? '选择一个插件,或从列表前部选择多个。'
|
|
2704
2812
|
: '先处理推荐任务,或查看一个插件。';
|
|
2705
2813
|
context = `${items.length} 个插件`;
|
|
2706
|
-
help = `↑↓ 移动 · Enter 查看 · m 批量${route.mode === 'installed' ? ' · a
|
|
2814
|
+
help = `↑↓ 移动 · Enter 查看 · m 批量${route.mode === 'installed' ? ' · a 按项目清单准备 · f 可更新' : ''} · ←→ 来源 · / 搜索${route.mode === 'browse' ? ' · f 未安装' : ''} · Esc 返回`;
|
|
2707
2815
|
const sourceError = sourceId !== 'all'
|
|
2708
2816
|
&& props.controller.getSourceOverview(sourceId).health?.state === 'unavailable';
|
|
2709
2817
|
actions = sourceError
|
|
@@ -2716,6 +2824,7 @@ export function ConsoleAppV4(props) {
|
|
|
2716
2824
|
.map((candidate) => candidate.target.pluginId))];
|
|
2717
2825
|
const projectRestoreCandidates = props.controller.listPluginWriteTaskCandidates('restore')
|
|
2718
2826
|
.filter((candidate) => candidate.target.scope === 'project');
|
|
2827
|
+
const projectIntentRoots = props.controller.listCurrentProjectIntentRoots();
|
|
2719
2828
|
const menuRows = sourceError || items.length === 0 || query ? [] : [
|
|
2720
2829
|
...(route.mode === 'installed' && recommendedUpdatePluginIds.length > 0 ? [{
|
|
2721
2830
|
pluginId: '@menu:update',
|
|
@@ -2727,11 +2836,21 @@ export function ConsoleAppV4(props) {
|
|
|
2727
2836
|
status: 'warning',
|
|
2728
2837
|
menuAction: () => openSelectedPluginTask('update', recommendedUpdatePluginIds),
|
|
2729
2838
|
}] : []),
|
|
2730
|
-
...(route.mode === 'installed' &&
|
|
2839
|
+
...(route.mode === 'installed' && projectIntentRoots.length > 0 ? [{
|
|
2731
2840
|
pluginId: '@menu:restore-project',
|
|
2732
|
-
name: '
|
|
2841
|
+
name: '按项目清单准备…',
|
|
2842
|
+
sourceLabel: '',
|
|
2843
|
+
description: `${projectIntentRoots.length} 个已保存根插件;允许补齐当前项目缺少的安装`,
|
|
2844
|
+
placementSummary: '',
|
|
2845
|
+
statusLabel: '项目',
|
|
2846
|
+
status: 'info',
|
|
2847
|
+
menuAction: prepareCurrentProjectPlugins,
|
|
2848
|
+
}] : []),
|
|
2849
|
+
...(route.mode === 'installed' && projectRestoreCandidates.length > 0 ? [{
|
|
2850
|
+
pluginId: '@menu:repair-project',
|
|
2851
|
+
name: '修复本项目异常…',
|
|
2733
2852
|
sourceLabel: '',
|
|
2734
|
-
description: '
|
|
2853
|
+
description: '严格按已确认的现有版本修复异常,不改变插件版本',
|
|
2735
2854
|
placementSummary: '',
|
|
2736
2855
|
statusLabel: '推荐',
|
|
2737
2856
|
status: 'warning',
|
|
@@ -2839,18 +2958,19 @@ export function ConsoleAppV4(props) {
|
|
|
2839
2958
|
const hiddenSelected = batchDraft.selectedPluginIds.filter((pluginId) => !currentIds.has(pluginId)).length;
|
|
2840
2959
|
const candidateActions = candidates.map((candidate) => {
|
|
2841
2960
|
const blockReason = batchCandidateBlockReason(batchDraft, candidate);
|
|
2961
|
+
const isSelected = selected.has(candidate.pluginId);
|
|
2842
2962
|
return {
|
|
2843
|
-
label: candidate.displayName
|
|
2963
|
+
label: candidate.plugin?.displayName ?? `${candidate.pluginId}(已不可用,可取消选择)`,
|
|
2844
2964
|
description: blockReason
|
|
2845
|
-
? `${candidate.sourceLabel} ·
|
|
2846
|
-
: `${candidate.sourceLabel} · ${pluginPlacementSummary(candidate)}`,
|
|
2847
|
-
selected:
|
|
2965
|
+
? `${candidate.plugin?.sourceLabel ?? '状态已失效'} · ${isSelected ? '可取消选择' : '不可选择'}:${blockReason}`
|
|
2966
|
+
: `${candidate.plugin.sourceLabel} · ${pluginPlacementSummary(candidate.plugin)}`,
|
|
2967
|
+
selected: isSelected,
|
|
2848
2968
|
pluginId: candidate.pluginId,
|
|
2849
|
-
|
|
2969
|
+
canSelectNew: blockReason === undefined,
|
|
2850
2970
|
status: blockReason ? 'muted' : undefined,
|
|
2851
|
-
run: () => go(batchDraft.mode === 'browse'
|
|
2971
|
+
run: candidate.plugin ? () => go(batchDraft.mode === 'browse'
|
|
2852
2972
|
? { name: 'pluginPreview', pluginId: candidate.pluginId }
|
|
2853
|
-
: { name: 'pluginDetail', pluginId: candidate.pluginId }),
|
|
2973
|
+
: { name: 'pluginDetail', pluginId: candidate.pluginId }) : undefined,
|
|
2854
2974
|
};
|
|
2855
2975
|
});
|
|
2856
2976
|
const selectableIds = candidates
|
|
@@ -2861,6 +2981,11 @@ export function ConsoleAppV4(props) {
|
|
|
2861
2981
|
setActionError('请至少选择一个插件。');
|
|
2862
2982
|
return;
|
|
2863
2983
|
}
|
|
2984
|
+
const invalid = invalidBatchSelection(batchDraft);
|
|
2985
|
+
if (invalid.length > 0) {
|
|
2986
|
+
setActionError(`${invalid.length} 个已选插件已不可用。请先在“查看全部已选”中取消选择。`);
|
|
2987
|
+
return;
|
|
2988
|
+
}
|
|
2864
2989
|
if (batchDraft.mode === 'browse') {
|
|
2865
2990
|
setBatchDraft((current) => current ? { ...current, action: 'install' } : current);
|
|
2866
2991
|
props.controller.push({ name: 'batchPlacement' });
|
|
@@ -2871,16 +2996,20 @@ export function ConsoleAppV4(props) {
|
|
|
2871
2996
|
setFocus(0);
|
|
2872
2997
|
setScrollOffset(0);
|
|
2873
2998
|
};
|
|
2999
|
+
const continueAction = {
|
|
3000
|
+
label: `查看确认(${batchDraft.selectedPluginIds.length})`,
|
|
3001
|
+
description: batchDraft.selectedPluginIds.length > 0 ? '继续选择任务和确认范围' : '先选择至少一个插件',
|
|
3002
|
+
run: continueSelection,
|
|
3003
|
+
};
|
|
2874
3004
|
const utilityActions = [
|
|
2875
|
-
{
|
|
2876
|
-
label: `查看确认(${batchDraft.selectedPluginIds.length})`,
|
|
2877
|
-
description: batchDraft.selectedPluginIds.length > 0 ? '继续选择任务和确认范围' : '先选择至少一个插件',
|
|
2878
|
-
run: continueSelection,
|
|
2879
|
-
},
|
|
2880
3005
|
{
|
|
2881
3006
|
label: batchDraft.showSelected ? '返回当前结果' : `查看全部已选(${batchDraft.selectedPluginIds.length})`,
|
|
2882
3007
|
description: hiddenSelected > 0 ? `${hiddenSelected} 个选择在其他筛选中` : '完整 canonical 选择集合',
|
|
2883
|
-
run: () =>
|
|
3008
|
+
run: () => {
|
|
3009
|
+
setBatchDraft((current) => current ? { ...current, showSelected: !current.showSelected } : current);
|
|
3010
|
+
setFocus(0);
|
|
3011
|
+
setScrollOffset(0);
|
|
3012
|
+
},
|
|
2884
3013
|
},
|
|
2885
3014
|
{
|
|
2886
3015
|
label: '全选当前结果',
|
|
@@ -2903,13 +3032,19 @@ export function ConsoleAppV4(props) {
|
|
|
2903
3032
|
run: () => setBatchDraft((current) => current ? { ...current, selectedPluginIds: [] } : current),
|
|
2904
3033
|
},
|
|
2905
3034
|
];
|
|
2906
|
-
actions = [...candidateActions, ...utilityActions];
|
|
3035
|
+
actions = [continueAction, ...candidateActions, ...utilityActions];
|
|
3036
|
+
const batchFocus = Math.min(focus, actions.length - 1);
|
|
2907
3037
|
context = `已选 ${batchDraft.selectedPluginIds.length} 个 · 显示 ${candidates.length} 个${hiddenSelected > 0 ? ` · 隐藏 ${hiddenSelected} 个` : ''}`;
|
|
2908
3038
|
help = '↑↓ 移动 · Space 选择 · Enter 查看/操作 · c 查看确认 · ←→ 来源 · / 搜索 · Esc 放弃';
|
|
2909
|
-
const
|
|
3039
|
+
const scrollActions = [...candidateActions, ...utilityActions];
|
|
3040
|
+
const viewport = actionViewport(scrollActions, policy.mode === 'compact', Math.max(0, batchFocus - 1), Math.max(0, scrollOffset - 1), Math.max(1, pageActionBudget(5)));
|
|
3041
|
+
const focusedCandidate = batchFocus > 0 && batchFocus <= candidateActions.length
|
|
3042
|
+
? candidates[batchFocus - 1]?.plugin : undefined;
|
|
2910
3043
|
content = _jsxs(Box, { flexDirection: "column", children: [_jsx(SourceTabs, { controller: props.controller, current: batchDraft.showSelected ? 'all' : batchDraft.sourceId ?? 'all', compact: policy.mode === 'compact', columns: size.columns - 2, mode: batchDraft.mode, query: batchDraft.showSelected ? undefined : batchDraft.query || undefined, installFilter: batchDraft.showSelected ? 'all' : batchDraft.installFilter }), _jsxs(Text, { color: searchFocus ? tuiTheme.colors.accent : tuiTheme.colors.muted, children: ["\u641C\u7D22 / ", searchFocus
|
|
2911
3044
|
? _jsx(InputDisplay, { value: batchDraft.query, placeholder: "\u8F93\u5165\u641C\u7D22\u5185\u5BB9", columns: Math.max(8, size.columns - 14) })
|
|
2912
|
-
: _jsxs(_Fragment, { children: [batchDraft.query || '搜索名称、用途或来源…', batchDraft.query ? ' / 编辑' : ''] })] }), _jsx(SemanticRows, { actions:
|
|
3045
|
+
: _jsxs(_Fragment, { children: [batchDraft.query || '搜索名称、用途或来源…', batchDraft.query ? ' / 编辑' : ''] })] }), _jsx(SemanticRows, { actions: [continueAction], focus: batchFocus, kind: "action", compact: true }), focusedCandidate
|
|
3046
|
+
? _jsx(StaticRow, { title: `用途 · ${focusedCandidate.description}`, status: "info", compact: true })
|
|
3047
|
+
: null, _jsx(SemanticRows, { actions: scrollActions, focus: batchFocus - 1, kind: "toggle", compact: policy.mode === 'compact', offset: viewport.offset, limit: viewport.limit })] });
|
|
2913
3048
|
}
|
|
2914
3049
|
else {
|
|
2915
3050
|
const selectedIds = batchDraft.selectedPluginIds;
|
|
@@ -2918,8 +3053,12 @@ export function ConsoleAppV4(props) {
|
|
|
2918
3053
|
return props.controller.listPluginWriteTaskCandidates(goal)
|
|
2919
3054
|
.filter((candidate) => selected.has(candidate.target.pluginId));
|
|
2920
3055
|
};
|
|
2921
|
-
const exactTargets = existingBatchTargets(selectedIds);
|
|
2922
3056
|
const startExistingBatch = (action) => {
|
|
3057
|
+
if (invalidBatchSelection(batchDraft).length > 0) {
|
|
3058
|
+
setActionError('已选插件状态已变化。请返回选择页检查并取消失效项。');
|
|
3059
|
+
return;
|
|
3060
|
+
}
|
|
3061
|
+
const exactTargets = props.controller.listPluginWriteBatchTargets(action, selectedIds);
|
|
2923
3062
|
if (exactTargets.length === 0) {
|
|
2924
3063
|
setActionError('所选插件没有可用的已有安装位置。');
|
|
2925
3064
|
return;
|
|
@@ -2931,11 +3070,13 @@ export function ConsoleAppV4(props) {
|
|
|
2931
3070
|
scope: exactTargets[0].scope,
|
|
2932
3071
|
});
|
|
2933
3072
|
};
|
|
3073
|
+
const syncTargets = props.controller.listPluginWriteBatchTargets('sync', selectedIds);
|
|
3074
|
+
const uninstallTargets = props.controller.listPluginWriteBatchTargets('uninstall', selectedIds);
|
|
2934
3075
|
if (batchDraft.stage === 'maintenance') {
|
|
2935
3076
|
actions = [
|
|
2936
|
-
{ label: '同步已保存使用设置…', description:
|
|
2937
|
-
{ label: '重新安装…', description: '
|
|
2938
|
-
{ label: '清理遗留内容…', description: '
|
|
3077
|
+
{ label: '同步已保存使用设置…', description: `重新应用 ${syncTargets.length} 个已保存或现场目标`, run: () => startExistingBatch('sync') },
|
|
3078
|
+
{ label: '重新安装…', description: `重新生成 ${props.controller.listPluginWriteBatchTargets('reinstall', selectedIds).length} 个已保存或现场目标`, run: () => startExistingBatch('reinstall') },
|
|
3079
|
+
{ label: '清理遗留内容…', description: `${props.controller.listPluginWriteBatchTargets('cleanup', selectedIds).length} 个有受管证据的位置;保留 foreign 和 shared 内容`, run: () => startExistingBatch('cleanup') },
|
|
2939
3080
|
{
|
|
2940
3081
|
label: '返回任务选择',
|
|
2941
3082
|
run: () => {
|
|
@@ -2964,7 +3105,7 @@ export function ConsoleAppV4(props) {
|
|
|
2964
3105
|
status: 'warning',
|
|
2965
3106
|
run: () => openSelectedPluginTask('restore', selectedIds),
|
|
2966
3107
|
}] : []),
|
|
2967
|
-
{ label: '移除所选插件…', description: `${
|
|
3108
|
+
{ label: '移除所选插件…', description: `${uninstallTargets.length} 个现场目标;执行前检查所有权;保留设置`, status: 'error', run: () => startExistingBatch('uninstall') },
|
|
2968
3109
|
{
|
|
2969
3110
|
label: '其他维护方式…',
|
|
2970
3111
|
description: '同步、重新安装和普通清理',
|
|
@@ -2977,7 +3118,7 @@ export function ConsoleAppV4(props) {
|
|
|
2977
3118
|
{ label: '返回调整选择', run: () => setBatchDraft((current) => current ? { ...current, stage: 'select' } : current) },
|
|
2978
3119
|
];
|
|
2979
3120
|
}
|
|
2980
|
-
context = `${selectedIds.length} 个插件 · ${
|
|
3121
|
+
context = `${selectedIds.length} 个插件 · ${syncTargets.length} 个已保存或现场目标`;
|
|
2981
3122
|
help = '↑↓ 移动 · Enter 选择 · Esc 返回修改';
|
|
2982
3123
|
const viewport = actionViewport(actions, policy.mode === 'compact', focus, scrollOffset, pageActionBudget(1));
|
|
2983
3124
|
content = _jsx(SemanticRows, { actions: actions, focus: focus, kind: "task", compact: policy.mode === 'compact', offset: viewport.offset, limit: viewport.limit });
|
|
@@ -3015,6 +3156,10 @@ export function ConsoleAppV4(props) {
|
|
|
3015
3156
|
disabled: batchDraft.selection === 'selected' && batchDraft.selectedPluginIds.length === 0
|
|
3016
3157
|
? '请至少选择一个插件' : undefined,
|
|
3017
3158
|
run: () => {
|
|
3159
|
+
if (invalidBatchSelection(batchDraft).length > 0) {
|
|
3160
|
+
setActionError('已选插件状态已变化。请返回选择页检查并取消失效项。');
|
|
3161
|
+
return;
|
|
3162
|
+
}
|
|
3018
3163
|
const intent = {
|
|
3019
3164
|
kind: 'pluginWriteBatch',
|
|
3020
3165
|
action: batchDraft.action,
|
|
@@ -3386,24 +3531,51 @@ export function ConsoleAppV4(props) {
|
|
|
3386
3531
|
: row.depth === 0 ? '当前插件' : undefined,
|
|
3387
3532
|
meta: state.value,
|
|
3388
3533
|
status: state.status,
|
|
3389
|
-
run: () =>
|
|
3390
|
-
|
|
3391
|
-
|
|
3392
|
-
|
|
3393
|
-
|
|
3394
|
-
|
|
3534
|
+
run: () => {
|
|
3535
|
+
persistDependencyGraphContext(route, projection, expandedDependencyPluginIds ?? new Set(projection.defaultExpandedPluginIds), focus, scrollOffset);
|
|
3536
|
+
go({
|
|
3537
|
+
name: 'pluginDependencyNode',
|
|
3538
|
+
pluginId: plugin.pluginId,
|
|
3539
|
+
nodePluginId: row.pluginId,
|
|
3540
|
+
direction,
|
|
3541
|
+
});
|
|
3542
|
+
},
|
|
3395
3543
|
};
|
|
3396
3544
|
});
|
|
3545
|
+
const unverifiedConsumerActions = direction === 'dependents'
|
|
3546
|
+
? projection.scopeSummary.unknownConsumerPluginIds.map((pluginId) => {
|
|
3547
|
+
const declared = overview?.dependents.find((entry) => entry.pluginId === pluginId);
|
|
3548
|
+
return {
|
|
3549
|
+
label: `未验证声明者 · ${declared?.displayName ?? pluginId}`,
|
|
3550
|
+
description: '目录声明它依赖当前插件,但当前没有实际引用证据',
|
|
3551
|
+
meta: declared?.aggregateStatus === 'notInstalled' ? '未安装' : '未验证',
|
|
3552
|
+
status: 'muted',
|
|
3553
|
+
run: () => {
|
|
3554
|
+
persistDependencyGraphContext(route, projection, expandedDependencyPluginIds ?? new Set(projection.defaultExpandedPluginIds), focus, scrollOffset);
|
|
3555
|
+
go({
|
|
3556
|
+
name: 'pluginDependencyNode',
|
|
3557
|
+
pluginId: plugin.pluginId,
|
|
3558
|
+
nodePluginId: pluginId,
|
|
3559
|
+
direction,
|
|
3560
|
+
});
|
|
3561
|
+
},
|
|
3562
|
+
};
|
|
3563
|
+
})
|
|
3564
|
+
: [];
|
|
3397
3565
|
actions = [
|
|
3398
3566
|
...rowActions,
|
|
3567
|
+
...unverifiedConsumerActions,
|
|
3399
3568
|
{
|
|
3400
3569
|
label: direction === 'dependencies' ? '切换到“谁在使用它”' : '切换到“它需要的”',
|
|
3401
3570
|
description: '切换关系方向',
|
|
3402
|
-
run: () =>
|
|
3403
|
-
|
|
3404
|
-
|
|
3405
|
-
|
|
3406
|
-
|
|
3571
|
+
run: () => {
|
|
3572
|
+
persistDependencyGraphContext(route, projection, expandedDependencyPluginIds ?? new Set(projection.defaultExpandedPluginIds), focus, scrollOffset);
|
|
3573
|
+
props.controller.replace({
|
|
3574
|
+
name: 'pluginDependencies',
|
|
3575
|
+
pluginId: plugin.pluginId,
|
|
3576
|
+
direction: direction === 'dependencies' ? 'dependents' : 'dependencies',
|
|
3577
|
+
});
|
|
3578
|
+
},
|
|
3407
3579
|
},
|
|
3408
3580
|
{ label: showTechnical ? '隐藏检查详情' : '查看检查详情', run: () => toggleTechnical() },
|
|
3409
3581
|
];
|
|
@@ -3413,7 +3585,9 @@ export function ConsoleAppV4(props) {
|
|
|
3413
3585
|
const relationViewport = actionViewport(actions, policy.mode === 'compact', focus, scrollOffset, showTechnical ? responsiveBudget(3, 6) : responsiveBudget(7, 11));
|
|
3414
3586
|
help = '↑↓ 选择 · → 展开 · ← 收起 · Enter 节点详情 · Esc 返回';
|
|
3415
3587
|
content = _jsxs(Box, { flexDirection: "column", children: [_jsx(StaticRow, { title: `当前检查范围 · ${scopeText}`, status: "muted", compact: true }), projection.status.state === 'invalid'
|
|
3416
|
-
? _jsx(Callout, { status: "error", title: "\u4F9D\u8D56\u5173\u7CFB\u65E0\u6548", description: projection.status.message }) : null,
|
|
3588
|
+
? _jsx(Callout, { status: "error", title: "\u4F9D\u8D56\u5173\u7CFB\u65E0\u6548", description: projection.status.message }) : null, unverifiedConsumerActions.length > 0
|
|
3589
|
+
? _jsx(StaticRow, { title: `未验证声明者 · ${unverifiedConsumerActions.length} 个`, description: "\u4EE5\u4E0B\u58F0\u660E\u8005\u4E0D\u8BA1\u5165\u5B9E\u9645\u4F7F\u7528\u8005", status: "muted", compact: true })
|
|
3590
|
+
: null, _jsx(SemanticRows, { actions: actions, focus: focus, kind: "action", compact: true, offset: relationViewport.offset, limit: relationViewport.limit }), showTechnical ? _jsxs(TechnicalDetails, { children: [_jsx(StaticRow, { title: `${dependencyCount} 个依赖 · ${projection.scopeSummary.actualConsumerPluginIds.length} 个实际使用者`, meta: plugin.pluginId, compact: true }), _jsx(StaticRow, { title: `未验证声明者 · ${projection.scopeSummary.unknownConsumerPluginIds.length} 个`, compact: true })] }) : null] });
|
|
3417
3591
|
}
|
|
3418
3592
|
}
|
|
3419
3593
|
else if (route.name === 'pluginDependencyNode' && plugin) {
|
|
@@ -3423,6 +3597,8 @@ export function ConsoleAppV4(props) {
|
|
|
3423
3597
|
: route.direction === 'dependencies'
|
|
3424
3598
|
? overview?.dependencies.find((entry) => entry.pluginId === route.nodePluginId)
|
|
3425
3599
|
: overview?.dependents.find((entry) => entry.pluginId === route.nodePluginId);
|
|
3600
|
+
const verifiedDependent = route.direction !== 'dependents'
|
|
3601
|
+
|| (item?.placementReferences?.length ?? 0) > 0;
|
|
3426
3602
|
const state = item ? dependencyStatus(item) : undefined;
|
|
3427
3603
|
breadcrumb = `${plugin.displayName} / 依赖关系 / ${node?.displayName ?? route.nodePluginId}`;
|
|
3428
3604
|
context = route.direction === 'dependencies' ? '它需要的' : '谁在使用它';
|
|
@@ -3431,7 +3607,9 @@ export function ConsoleAppV4(props) {
|
|
|
3431
3607
|
? '这是当前关系图的根插件。'
|
|
3432
3608
|
: route.direction === 'dependencies'
|
|
3433
3609
|
? `${item?.requiredBy.map((entry) => entry.displayName).join('、') || plugin.displayName} 需要这个插件。`
|
|
3434
|
-
:
|
|
3610
|
+
: verifiedDependent
|
|
3611
|
+
? `${node?.displayName ?? route.nodePluginId} 的已安装位置实际引用当前插件。`
|
|
3612
|
+
: `${item?.displayName ?? route.nodePluginId} 声明依赖当前插件,但当前没有实际引用证据。`;
|
|
3435
3613
|
actions = [
|
|
3436
3614
|
...(route.nodePluginId !== plugin.pluginId ? [{
|
|
3437
3615
|
label: '以此节点查看关系',
|
|
@@ -3631,7 +3809,10 @@ export function ConsoleAppV4(props) {
|
|
|
3631
3809
|
}
|
|
3632
3810
|
else if (route.name === 'pluginWriteTask' && plugin) {
|
|
3633
3811
|
const retainedTargetKeys = route.targetKeys ? new Set(route.targetKeys) : undefined;
|
|
3634
|
-
const
|
|
3812
|
+
const allTaskCandidates = props.controller.listPluginWriteTaskCandidates(route.goal);
|
|
3813
|
+
const currentTargetKeys = new Set(allTaskCandidates.map(pluginWriteTaskTargetKey));
|
|
3814
|
+
const missingTargetKeys = route.targetKeys?.filter((targetKey) => !currentTargetKeys.has(targetKey)) ?? [];
|
|
3815
|
+
const candidates = allTaskCandidates
|
|
3635
3816
|
.filter((candidate) => retainedTargetKeys
|
|
3636
3817
|
? retainedTargetKeys.has(pluginWriteTaskTargetKey(candidate))
|
|
3637
3818
|
: candidate.target.pluginId === route.pluginId);
|
|
@@ -3648,14 +3829,134 @@ export function ConsoleAppV4(props) {
|
|
|
3648
3829
|
const activationSources = [...new Set(blockedCandidates
|
|
3649
3830
|
.filter((candidate) => candidate.blockReason === 'catalogActivationRequired')
|
|
3650
3831
|
.map((candidate) => candidate.sourceId))];
|
|
3832
|
+
const taskIssues = new Map(props.controller.listUserIssues().map((issue) => [issue.issueId, issue]));
|
|
3833
|
+
const recoveryActions = [...new Map(blockedCandidates.flatMap((candidate) => {
|
|
3834
|
+
if (candidate.blockReason === 'catalogActivationRequired')
|
|
3835
|
+
return [];
|
|
3836
|
+
const target = candidate.target;
|
|
3837
|
+
const targetLabel = `${candidate.displayName} · ${platformLabel(target.platform)} · ${scopeLabel(target.scope)}`;
|
|
3838
|
+
const linkedIssue = candidate.issueIds.map((issueId) => taskIssues.get(issueId)).find(Boolean);
|
|
3839
|
+
const sourceIssue = linkedIssue && (linkedIssue.group === 'sourceRemoved'
|
|
3840
|
+
|| linkedIssue.group === 'sourceUnavailable')
|
|
3841
|
+
? linkedIssue
|
|
3842
|
+
: [...taskIssues.values()].find((entry) => (entry.category === 'sourceUnavailable' || entry.category === 'sourceMissing'
|
|
3843
|
+
|| entry.category === 'sourceIdentityChanged')
|
|
3844
|
+
&& (entry.sourceId === candidate.sourceId || entry.pluginId === target.pluginId));
|
|
3845
|
+
const issue = linkedIssue ?? sourceIssue;
|
|
3846
|
+
const candidatePlugin = props.controller.getPluginOverview(target.pluginId);
|
|
3847
|
+
const configField = issue?.requiredFields?.[0];
|
|
3848
|
+
if (issue?.group === 'config' || configField) {
|
|
3849
|
+
const fieldKey = configField?.key;
|
|
3850
|
+
return [[`config:${target.pluginId}:${fieldKey ?? ''}`, {
|
|
3851
|
+
label: fieldKey ? `完成设置:${configField?.title ?? fieldKey}` : `检查 ${candidate.displayName} 的设置`,
|
|
3852
|
+
description: `${targetLabel};保存后返回任务重新读取当前证据`,
|
|
3853
|
+
status: 'warning',
|
|
3854
|
+
run: () => {
|
|
3855
|
+
beginConfigFlow(target.pluginId);
|
|
3856
|
+
props.controller.replace({
|
|
3857
|
+
name: 'configLayer',
|
|
3858
|
+
pluginId: target.pluginId,
|
|
3859
|
+
...(fieldKey ? { fieldKey } : {}),
|
|
3860
|
+
});
|
|
3861
|
+
},
|
|
3862
|
+
}]];
|
|
3863
|
+
}
|
|
3864
|
+
if (candidate.blockReason === 'restoreVersionChanged') {
|
|
3865
|
+
const updateCandidates = props.controller.listPluginWriteTaskCandidates('update')
|
|
3866
|
+
.filter((entry) => pluginWriteTaskTargetKey(entry) === pluginWriteTaskTargetKey(candidate));
|
|
3867
|
+
const updatePlugin = props.controller.getPluginOverview(target.pluginId);
|
|
3868
|
+
if (updateCandidates.length > 0 && updatePlugin) {
|
|
3869
|
+
return [[`update:${pluginWriteTaskTargetKey(candidate)}`, {
|
|
3870
|
+
label: `更新并恢复 ${targetLabel}…`,
|
|
3871
|
+
description: '改用当前候选版本建立新任务;进入 Review 前仍会重新检查证据',
|
|
3872
|
+
status: 'warning',
|
|
3873
|
+
run: () => props.controller.beginNestedPluginWriteTask({
|
|
3874
|
+
name: 'pluginWriteTask',
|
|
3875
|
+
pluginId: target.pluginId,
|
|
3876
|
+
goal: 'update',
|
|
3877
|
+
targetKeys: updateCandidates.map(pluginWriteTaskTargetKey),
|
|
3878
|
+
...(route.batchOrigin ? { batchOrigin: route.batchOrigin } : {}),
|
|
3879
|
+
versionEvidence: pluginWriteTaskVersionEvidence(updatePlugin, updateCandidates),
|
|
3880
|
+
}),
|
|
3881
|
+
}]];
|
|
3882
|
+
}
|
|
3883
|
+
}
|
|
3884
|
+
if (sourceIssue || candidatePlugin?.sourceUnavailable) {
|
|
3885
|
+
const sourceId = sourceIssue?.sourceId ?? candidate.sourceId;
|
|
3886
|
+
const sourceNeedsIssue = sourceIssue?.category === 'sourceMissing'
|
|
3887
|
+
|| sourceIssue?.category === 'sourceIdentityChanged'
|
|
3888
|
+
|| sourceIssue?.group === 'sourceRemoved';
|
|
3889
|
+
if (sourceNeedsIssue && sourceIssue) {
|
|
3890
|
+
return [[`source-issue:${sourceIssue.issueId}`, {
|
|
3891
|
+
label: sourceIssue.category === 'sourceMissing'
|
|
3892
|
+
? `重新添加来源 ${sourceId}` : `核对来源身份 ${sourceId}`,
|
|
3893
|
+
description: `${targetLabel};先查看来源问题的安全恢复选择`,
|
|
3894
|
+
status: 'warning',
|
|
3895
|
+
run: () => go({ name: 'issueDetail', issueId: sourceIssue.issueId }),
|
|
3896
|
+
}]];
|
|
3897
|
+
}
|
|
3898
|
+
return [[`source:${sourceId}`, {
|
|
3899
|
+
label: `核对来源 ${sourceId}`,
|
|
3900
|
+
description: `${targetLabel};修复或重新检查后返回任务`,
|
|
3901
|
+
status: 'warning',
|
|
3902
|
+
run: () => go({ name: 'sourceDetail', sourceId }),
|
|
3903
|
+
}]];
|
|
3904
|
+
}
|
|
3905
|
+
if (candidate.blockReason === 'restoreVersionUnverified'
|
|
3906
|
+
|| candidate.blockReason === 'updateEvidenceIncomplete') {
|
|
3907
|
+
return [
|
|
3908
|
+
[`reload:${pluginWriteTaskTargetKey(candidate)}`, {
|
|
3909
|
+
label: `重新检查 ${targetLabel}`,
|
|
3910
|
+
description: '重新读取安装、版本、来源和受管内容证据',
|
|
3911
|
+
run: async () => {
|
|
3912
|
+
await props.controller.load();
|
|
3913
|
+
setFocus(0);
|
|
3914
|
+
setScrollOffset(0);
|
|
3915
|
+
},
|
|
3916
|
+
}],
|
|
3917
|
+
[`diagnostic:${pluginWriteTaskTargetKey(candidate)}`, {
|
|
3918
|
+
label: `查看 ${targetLabel} 的诊断`,
|
|
3919
|
+
description: '版本仍无法确认时查看组件状态和安全恢复方向',
|
|
3920
|
+
run: () => go({ name: 'pluginTechnical', pluginId: target.pluginId }),
|
|
3921
|
+
}],
|
|
3922
|
+
];
|
|
3923
|
+
}
|
|
3924
|
+
if (issue) {
|
|
3925
|
+
return [[`issue:${issue.issueId}`, {
|
|
3926
|
+
label: `处理问题:${issue.title}`,
|
|
3927
|
+
description: `${targetLabel};只提供该问题允许的恢复动作`,
|
|
3928
|
+
status: 'warning',
|
|
3929
|
+
run: () => go({ name: 'issueDetail', issueId: issue.issueId }),
|
|
3930
|
+
}]];
|
|
3931
|
+
}
|
|
3932
|
+
if (candidate.blockReason === 'targetUnavailable'
|
|
3933
|
+
|| candidate.blockReason === 'manualRecoveryRequired'
|
|
3934
|
+
|| candidate.blockReason === 'taskActionConflict') {
|
|
3935
|
+
return [[`maintenance:${pluginWriteTaskTargetKey(candidate)}`, {
|
|
3936
|
+
label: `维护 ${targetLabel}`,
|
|
3937
|
+
description: '检查这个精确平台位置可用的安全维护动作',
|
|
3938
|
+
status: 'warning',
|
|
3939
|
+
run: () => go({ name: 'platform', pluginId: target.pluginId, platform: target.platform }),
|
|
3940
|
+
}]];
|
|
3941
|
+
}
|
|
3942
|
+
return [[`diagnostic:${target.pluginId}`, {
|
|
3943
|
+
label: `查看 ${candidate.displayName} 的诊断`,
|
|
3944
|
+
description: `${pluginWriteTaskBlockLabel(candidate.blockReason)};当前没有可安全自动执行的动作`,
|
|
3945
|
+
run: () => go({ name: 'pluginTechnical', pluginId: target.pluginId }),
|
|
3946
|
+
}]];
|
|
3947
|
+
})).values()];
|
|
3651
3948
|
const taskLabel = pluginWriteTaskLabel(route.goal);
|
|
3652
3949
|
const startTaskReview = (executionPolicy) => {
|
|
3950
|
+
if (missingTargetKeys.length > 0) {
|
|
3951
|
+
setActionError(`${missingTargetKeys.length} 个冻结位置已失效或无需处理。请返回调整后重新开始。`);
|
|
3952
|
+
return;
|
|
3953
|
+
}
|
|
3653
3954
|
props.controller.startPluginWriteTaskReview({
|
|
3654
3955
|
kind: 'pluginWriteTask',
|
|
3655
3956
|
goal: route.goal,
|
|
3656
3957
|
targets: candidates.map((candidate) => candidate.target),
|
|
3657
3958
|
executionPolicy,
|
|
3658
|
-
}, route);
|
|
3959
|
+
}, props.controller.pluginWriteTaskReturnRoute(route));
|
|
3659
3960
|
setFocus(0);
|
|
3660
3961
|
setScrollOffset(0);
|
|
3661
3962
|
};
|
|
@@ -3673,13 +3974,15 @@ export function ConsoleAppV4(props) {
|
|
|
3673
3974
|
? 'PgUp/PgDn 查看全部位置 · ↑↓ 移动 · Enter 选择 · Esc 返回'
|
|
3674
3975
|
: '↑↓ 移动 · Enter 选择 · Esc 返回';
|
|
3675
3976
|
actions = [
|
|
3676
|
-
...(
|
|
3977
|
+
...(missingTargetKeys.length === 0 && !versionEvidenceChanged
|
|
3978
|
+
&& blockedCandidates.length === 0 && executable.length > 0 ? [{
|
|
3677
3979
|
label: `确认${taskLabel} ${executable.length} 个位置…`,
|
|
3678
3980
|
description: '进入统一影响确认;执行前再次检查当前证据',
|
|
3679
3981
|
status: 'success',
|
|
3680
3982
|
run: () => startTaskReview('all'),
|
|
3681
3983
|
}] : []),
|
|
3682
|
-
...(
|
|
3984
|
+
...(missingTargetKeys.length === 0 && !versionEvidenceChanged
|
|
3985
|
+
&& blockedCandidates.length > 0 && executable.length > 0 ? [{
|
|
3683
3986
|
label: `先处理可执行的 ${executable.length} 个位置…`,
|
|
3684
3987
|
description: `${blockedCandidates.length} 个受阻位置保留,不会无声跳过`,
|
|
3685
3988
|
status: 'warning',
|
|
@@ -3691,6 +3994,7 @@ export function ConsoleAppV4(props) {
|
|
|
3691
3994
|
status: 'warning',
|
|
3692
3995
|
run: () => beginReview({ kind: 'catalogGeneration', action: 'update', sourceId }),
|
|
3693
3996
|
})),
|
|
3997
|
+
...recoveryActions,
|
|
3694
3998
|
...(versionEvidenceChanged ? [{
|
|
3695
3999
|
label: '使用当前版本重新准备…',
|
|
3696
4000
|
description: '来源启用后的插件版本与先前候选不同;先明确采用当前版本',
|
|
@@ -3719,7 +4023,9 @@ export function ConsoleAppV4(props) {
|
|
|
3719
4023
|
const taskViewport = actionViewport(actions, policy.mode === 'compact', focus, scrollOffset, pageActionBudget(Math.min(candidates.length, policy.mode === 'compact' ? 3 : 6) + 2));
|
|
3720
4024
|
content = _jsxs(Box, { flexDirection: "column", children: [_jsxs(SectionCard, { title: "\u5904\u7406\u8303\u56F4", meta: `${executable.length} 个可执行 · ${blockedCandidates.length} 个需决定`, compact: policy.mode === 'compact', children: [taskTargetOffset > 0 ? _jsx(StaticRow, { title: `↑ 前面还有 ${taskTargetOffset} 个位置`, status: "muted", compact: true }) : null, targetRows.length > 0 ? targetRows
|
|
3721
4025
|
: _jsx(StaticRow, { title: "\u5F53\u524D\u6CA1\u6709\u53EF\u6267\u884C\u76EE\u6807", status: "muted", compact: true }), candidates.length > taskTargetOffset + targetRows.length
|
|
3722
|
-
? _jsx(StaticRow, { title: `↓ 后面还有 ${candidates.length - taskTargetOffset - targetRows.length} 个位置`, status: "muted", compact: true }) : null] }), blockedCandidates.length > 0 ? _jsx(Callout, { status: "warning", title: `${blockedCandidates.length} 个位置需要先处理或决定`, description:
|
|
4026
|
+
? _jsx(StaticRow, { title: `↓ 后面还有 ${candidates.length - taskTargetOffset - targetRows.length} 个位置`, status: "muted", compact: true }) : null] }), blockedCandidates.length > 0 ? _jsx(Callout, { status: "warning", title: `${blockedCandidates.length} 个位置需要先处理或决定`, description: executable.length > 0
|
|
4027
|
+
? '可以先处理可执行项,或进入下方对应入口处理受阻位置。'
|
|
4028
|
+
: '请进入下方对应入口处理;返回后会重新读取当前证据。', compact: policy.mode === 'compact' }) : null, missingTargetKeys.length > 0 ? _jsx(Callout, { status: "warning", title: `${missingTargetKeys.length} 个冻结位置已失效或无需处理`, description: "\u4E0D\u4F1A\u628A\u8FD9\u4E9B\u4F4D\u7F6E\u5F53\u4F5C\u5DF2\u5B8C\u6210\uFF0C\u4E5F\u4E0D\u4F1A\u81EA\u52A8\u7F29\u5C0F\u672C\u6B21\u8303\u56F4\u3002\u8BF7\u8FD4\u56DE\u8C03\u6574\u9009\u62E9\u540E\u91CD\u65B0\u5F00\u59CB\u3002", compact: policy.mode === 'compact' }) : null, versionEvidenceChanged ? _jsx(Callout, { status: "warning", title: "\u63D2\u4EF6\u76EE\u6807\u7248\u672C\u5DF2\u53D8\u5316", description: "\u4E0D\u4F1A\u6CBF\u7528\u6765\u6E90\u542F\u7528\u524D\u7684\u7248\u672C\u8BC1\u636E\uFF0C\u4E5F\u4E0D\u4F1A\u81EA\u52A8\u6267\u884C\u65B0\u7248\u672C\u3002", compact: policy.mode === 'compact' }) : null, _jsx(SemanticRows, { actions: actions, focus: focus, kind: "action", compact: true, offset: taskViewport.offset, limit: taskViewport.limit })] });
|
|
3723
4029
|
}
|
|
3724
4030
|
else if (route.name === 'pluginDetail' && plugin) {
|
|
3725
4031
|
breadcrumb = `我的插件 / ${plugin.displayName}`;
|
|
@@ -4158,11 +4464,12 @@ export function ConsoleAppV4(props) {
|
|
|
4158
4464
|
}
|
|
4159
4465
|
else if (route.name === 'configLayer' && plugin) {
|
|
4160
4466
|
breadcrumb = `${plugin.displayName} / 设置`;
|
|
4161
|
-
const
|
|
4162
|
-
|
|
4467
|
+
const configLayerDefinitionPage = configPage ?? configLayerPages.user ?? configLayerPages.project;
|
|
4468
|
+
const selectedField = route.fieldKey && configLayerDefinitionPage
|
|
4469
|
+
? configLayerDefinitionPage.groups.flatMap((group) => group.fields).find((field) => field.key === route.fieldKey)
|
|
4163
4470
|
: undefined;
|
|
4164
|
-
const selectedPreset = route.presetId &&
|
|
4165
|
-
?
|
|
4471
|
+
const selectedPreset = route.presetId && configLayerDefinitionPage
|
|
4472
|
+
? configLayerDefinitionPage.presets.find((preset) => preset.id === route.presetId)
|
|
4166
4473
|
: undefined;
|
|
4167
4474
|
title = selectedField ? `修改“${selectedField.title}”`
|
|
4168
4475
|
: selectedPreset ? `应用“${selectedPreset.title}”` : `修改 ${plugin.displayName} 设置`;
|
|
@@ -4177,8 +4484,8 @@ export function ConsoleAppV4(props) {
|
|
|
4177
4484
|
return true;
|
|
4178
4485
|
if (selectedField)
|
|
4179
4486
|
return !selectedField.sensitive && !selectedField.sharedNativeRender;
|
|
4180
|
-
if (selectedPreset &&
|
|
4181
|
-
const effectiveFields =
|
|
4487
|
+
if (selectedPreset && configLayerDefinitionPage) {
|
|
4488
|
+
const effectiveFields = configLayerDefinitionPage.groups.flatMap((group) => group.fields);
|
|
4182
4489
|
return Object.keys(selectedPreset.values).some((key) => {
|
|
4183
4490
|
const field = effectiveFields.find((candidate) => candidate.key === key);
|
|
4184
4491
|
return Boolean(field && !field.sensitive && !field.sharedNativeRender);
|
|
@@ -6083,21 +6390,36 @@ export function ConsoleAppV4(props) {
|
|
|
6083
6390
|
const taskOperation = operationKind === 'pluginWriteTask';
|
|
6084
6391
|
const taskSummary = resultSummary?.task;
|
|
6085
6392
|
const taskBlockedCount = taskSummary?.blockedTargets.length ?? 0;
|
|
6086
|
-
const
|
|
6393
|
+
const frozenTaskReturnRoute = 'returnRoute' in result
|
|
6087
6394
|
&& result.returnRoute?.name === 'pluginWriteTask'
|
|
6088
|
-
? result.returnRoute
|
|
6089
|
-
const
|
|
6090
|
-
|
|
6091
|
-
|
|
6092
|
-
|
|
6093
|
-
|
|
6094
|
-
|
|
6095
|
-
|
|
6096
|
-
|
|
6097
|
-
|
|
6098
|
-
|
|
6099
|
-
|
|
6100
|
-
|
|
6395
|
+
? result.returnRoute : undefined;
|
|
6396
|
+
const nestedTaskReturn = frozenTaskReturnRoute !== undefined
|
|
6397
|
+
&& taskSummary !== undefined
|
|
6398
|
+
&& frozenTaskReturnRoute.goal !== taskSummary.goal;
|
|
6399
|
+
const taskBatchOrigin = frozenTaskReturnRoute?.batchOrigin;
|
|
6400
|
+
const remainingTaskTargets = taskSummary?.blockedTargets.map((entry) => entry.target) ?? [];
|
|
6401
|
+
const taskReturnRoute = nestedTaskReturn
|
|
6402
|
+
? frozenTaskReturnRoute
|
|
6403
|
+
: taskSummary && remainingTaskTargets[0] ? {
|
|
6404
|
+
name: 'pluginWriteTask',
|
|
6405
|
+
pluginId: remainingTaskTargets[0].pluginId,
|
|
6406
|
+
goal: taskSummary.goal,
|
|
6407
|
+
targetKeys: remainingTaskTargets.map((target) => [target.pluginId, target.platform, target.scope].join('\0')),
|
|
6408
|
+
...(taskBatchOrigin ? { batchOrigin: taskBatchOrigin } : {}),
|
|
6409
|
+
...(taskSummary.goal === 'update' ? {
|
|
6410
|
+
versionEvidence: 'returnRoute' in result
|
|
6411
|
+
&& result.returnRoute?.name === 'pluginWriteTask'
|
|
6412
|
+
&& result.returnRoute.versionEvidence
|
|
6413
|
+
? result.returnRoute.versionEvidence
|
|
6414
|
+
: Object.fromEntries(remainingTaskTargets.flatMap((target) => target.expectedUpdate
|
|
6415
|
+
? [[[target.pluginId, target.platform, target.scope].join('\0'), JSON.stringify({
|
|
6416
|
+
version: target.expectedUpdate.activeVersion,
|
|
6417
|
+
sourceIdentity: target.expectedUpdate.sourceIdentity,
|
|
6418
|
+
binding: target.expectedUpdate.generationKey,
|
|
6419
|
+
})]]
|
|
6420
|
+
: [])),
|
|
6421
|
+
} : {}),
|
|
6422
|
+
} : undefined;
|
|
6101
6423
|
const batchOperation = operationKind === 'pluginWriteBatch';
|
|
6102
6424
|
const portableOperation = operationKind === 'portableInstall';
|
|
6103
6425
|
const portableReceipt = result.state === 'succeeded' || result.state === 'partial'
|
|
@@ -6319,6 +6641,12 @@ export function ConsoleAppV4(props) {
|
|
|
6319
6641
|
]
|
|
6320
6642
|
: taskOperation
|
|
6321
6643
|
? [
|
|
6644
|
+
...(nestedTaskReturn && taskReturnRoute ? [{
|
|
6645
|
+
label: '返回剩余恢复任务',
|
|
6646
|
+
description: '保留父任务的冻结目标;返回后重新读取当前证据',
|
|
6647
|
+
status: 'success',
|
|
6648
|
+
run: () => leaveResult(taskReturnRoute),
|
|
6649
|
+
}] : []),
|
|
6322
6650
|
...((partial || blocked) && (taskSummary?.retryTargets?.length ?? 0) > 0 ? [{
|
|
6323
6651
|
label: `继续处理剩余 ${taskSummary.retryTargets.length} 项…`,
|
|
6324
6652
|
description: '重新检查失败的精确目标和必要依赖,并进入新的确认页',
|
|
@@ -6334,14 +6662,14 @@ export function ConsoleAppV4(props) {
|
|
|
6334
6662
|
status: 'warning',
|
|
6335
6663
|
run: () => leaveResult(taskReturnRoute),
|
|
6336
6664
|
}] : []),
|
|
6337
|
-
{ label: '完成,返回我的插件', run: () => {
|
|
6338
|
-
|
|
6339
|
-
|
|
6340
|
-
|
|
6341
|
-
|
|
6342
|
-
|
|
6343
|
-
|
|
6344
|
-
|
|
6665
|
+
...(!nestedTaskReturn ? [{ label: '完成,返回我的插件', run: () => {
|
|
6666
|
+
if (taskBatchOrigin?.owner === 'batch-write'
|
|
6667
|
+
&& taskBatchOrigin.flowId === batchDraft?.flowId) {
|
|
6668
|
+
setBatchDraft(undefined);
|
|
6669
|
+
setDirty(false);
|
|
6670
|
+
}
|
|
6671
|
+
leaveResult({ name: 'pluginList', mode: 'installed' });
|
|
6672
|
+
} }] : []),
|
|
6345
6673
|
{ label: '查看本次记录', run: () => result.operationRecordId
|
|
6346
6674
|
&& go({ name: 'operationDiagnostic', recordId: result.operationRecordId }) },
|
|
6347
6675
|
]
|
|
@@ -6450,7 +6778,8 @@ export function ConsoleAppV4(props) {
|
|
|
6450
6778
|
{ label: '返回首页', run: () => requestSessionNavigation('home') },
|
|
6451
6779
|
]
|
|
6452
6780
|
: [{ label: partial || blockedDependencyFailure ? '查看失败项' : successfulConfigOperation && configOrigin
|
|
6453
|
-
? configOrigin
|
|
6781
|
+
? configOrigin.name === 'pluginPreview' ? '返回插件预览'
|
|
6782
|
+
: configOrigin.name === 'pluginWriteTask' ? '返回恢复任务' : '返回插件详情'
|
|
6454
6783
|
: configOperation ? '查看插件'
|
|
6455
6784
|
: action === 'uninstall' || action === 'cleanup' ? '返回插件详情' : '查看插件', run: () => {
|
|
6456
6785
|
if (successfulConfigOperation && configOrigin) {
|
|
@@ -6745,6 +7074,22 @@ export function ConsoleAppV4(props) {
|
|
|
6745
7074
|
const targetActionSignature = route.name === 'installTargets' && route.view !== 'blocked'
|
|
6746
7075
|
? actions.map((action) => String(Boolean(action.disabled))).join(',')
|
|
6747
7076
|
: '';
|
|
7077
|
+
const batchSelectionActionSignature = route.name === 'batchSelection'
|
|
7078
|
+
&& batchDraft?.stage === 'select'
|
|
7079
|
+
? actions.map((action) => action.pluginId ?? action.label).join('\0')
|
|
7080
|
+
: '';
|
|
7081
|
+
useEffect(() => {
|
|
7082
|
+
if (!batchSelectionActionSignature)
|
|
7083
|
+
return;
|
|
7084
|
+
const lastActionIndex = Math.max(0, actions.length - 1);
|
|
7085
|
+
if (focus > lastActionIndex) {
|
|
7086
|
+
setFocus(lastActionIndex);
|
|
7087
|
+
setScrollOffset(lastActionIndex);
|
|
7088
|
+
return;
|
|
7089
|
+
}
|
|
7090
|
+
if (scrollOffset > lastActionIndex)
|
|
7091
|
+
setScrollOffset(focus);
|
|
7092
|
+
}, [batchSelectionActionSignature, focus, scrollOffset]);
|
|
6748
7093
|
useEffect(() => {
|
|
6749
7094
|
if (!targetActionSignature || !actions[focus]?.disabled)
|
|
6750
7095
|
return;
|
|
@@ -6851,6 +7196,9 @@ export function ConsoleAppV4(props) {
|
|
|
6851
7196
|
setOverlayFocus(0);
|
|
6852
7197
|
setOverlay('sessionNavigation');
|
|
6853
7198
|
}, [props.pendingSessionNavigation, props.pendingSessionNavigationDrafts]);
|
|
7199
|
+
const normalizedEffectiveFocus = route.name === 'batchSelection' && batchDraft?.stage === 'select'
|
|
7200
|
+
? Math.min(effectiveFocus, Math.max(0, actions.length - 1))
|
|
7201
|
+
: effectiveFocus;
|
|
6854
7202
|
useInput((input, key) => {
|
|
6855
7203
|
if (!overlay && route.name === 'reviewPreparing' && snapshot.reviewPreparation?.state === 'failed'
|
|
6856
7204
|
&& (key.escape || input.toLowerCase() === 'b')) {
|
|
@@ -7598,7 +7946,7 @@ export function ConsoleAppV4(props) {
|
|
|
7598
7946
|
showSelected: false,
|
|
7599
7947
|
} : draft);
|
|
7600
7948
|
}
|
|
7601
|
-
setFocus(0);
|
|
7949
|
+
setFocus(route.name === 'batchSelection' ? 1 : 0);
|
|
7602
7950
|
setScrollOffset(0);
|
|
7603
7951
|
return;
|
|
7604
7952
|
}
|
|
@@ -7624,8 +7972,18 @@ export function ConsoleAppV4(props) {
|
|
|
7624
7972
|
next.add(row.pluginId);
|
|
7625
7973
|
if (key.leftArrow && row.canExpand)
|
|
7626
7974
|
next.delete(row.pluginId);
|
|
7975
|
+
const nextProjection = buildDependencyGraphProjection({
|
|
7976
|
+
overviews: props.controller.listPluginDependencyOverviews(),
|
|
7977
|
+
rootPluginId: route.pluginId,
|
|
7978
|
+
direction: route.direction ?? 'dependencies',
|
|
7979
|
+
expandedPluginIds: next,
|
|
7980
|
+
});
|
|
7981
|
+
const nextFocus = Math.max(0, nextProjection.rows.findIndex((candidate) => candidate.id === row.id));
|
|
7982
|
+
const nextScroll = Math.min(scrollOffset, nextFocus);
|
|
7627
7983
|
setExpandedDependencyPluginIds(next);
|
|
7628
|
-
|
|
7984
|
+
setFocus(nextFocus);
|
|
7985
|
+
setScrollOffset(nextScroll);
|
|
7986
|
+
persistDependencyGraphContext(route, nextProjection, next, nextFocus, nextScroll);
|
|
7629
7987
|
return;
|
|
7630
7988
|
}
|
|
7631
7989
|
const sessionNavigation = resolveTuiSessionNavigationKey({ input, key }, { characterInput: isCharacterInputMode(route, searchFocus) });
|
|
@@ -7706,10 +8064,7 @@ export function ConsoleAppV4(props) {
|
|
|
7706
8064
|
openBatchSelection(route.mode === 'browse' ? 'install' : undefined);
|
|
7707
8065
|
}
|
|
7708
8066
|
else if (route.name === 'pluginList' && route.mode === 'installed' && input.toLowerCase() === 'a') {
|
|
7709
|
-
|
|
7710
|
-
.filter((candidate) => candidate.target.scope === 'project')
|
|
7711
|
-
.map((candidate) => candidate.target.pluginId))];
|
|
7712
|
-
openSelectedPluginTask('restore', projectRoots, 'project');
|
|
8067
|
+
prepareCurrentProjectPlugins();
|
|
7713
8068
|
}
|
|
7714
8069
|
else if (route.name === 'batchSelection' && batchDraft?.stage === 'select' && input.toLowerCase() === 'a') {
|
|
7715
8070
|
const candidates = batchCandidates(batchDraft)
|
|
@@ -7724,6 +8079,11 @@ export function ConsoleAppV4(props) {
|
|
|
7724
8079
|
}
|
|
7725
8080
|
else if (route.name === 'batchSelection' && batchDraft?.stage === 'select' && input.toLowerCase() === 'c') {
|
|
7726
8081
|
if (batchDraft.selectedPluginIds.length > 0) {
|
|
8082
|
+
const invalid = invalidBatchSelection(batchDraft);
|
|
8083
|
+
if (invalid.length > 0) {
|
|
8084
|
+
setActionError(`${invalid.length} 个已选插件已不可用。请先在“查看全部已选”中取消选择。`);
|
|
8085
|
+
return;
|
|
8086
|
+
}
|
|
7727
8087
|
if (batchDraft.mode === 'browse') {
|
|
7728
8088
|
setBatchDraft((current) => current ? { ...current, action: 'install' } : current);
|
|
7729
8089
|
props.controller.push({ name: 'batchPlacement' });
|
|
@@ -7737,9 +8097,11 @@ export function ConsoleAppV4(props) {
|
|
|
7737
8097
|
setActionError('请至少选择一个插件。');
|
|
7738
8098
|
}
|
|
7739
8099
|
else if (route.name === 'batchSelection' && batchDraft?.stage === 'select' && input === ' ') {
|
|
7740
|
-
const action = actions[
|
|
7741
|
-
if (action?.pluginId
|
|
8100
|
+
const action = actions[normalizedEffectiveFocus];
|
|
8101
|
+
if (action?.pluginId
|
|
8102
|
+
&& (batchDraft.selectedPluginIds.includes(action.pluginId) || action.canSelectNew)) {
|
|
7742
8103
|
toggleBatchPlugin(action.pluginId);
|
|
8104
|
+
}
|
|
7743
8105
|
}
|
|
7744
8106
|
else if (route.name === 'batchSelection' && batchDraft?.stage === 'select' && input === '/') {
|
|
7745
8107
|
setSearchFocus(true);
|
|
@@ -7755,7 +8117,7 @@ export function ConsoleAppV4(props) {
|
|
|
7755
8117
|
...(next === 'all' ? { sourceId: undefined } : { sourceId: next }),
|
|
7756
8118
|
showSelected: false,
|
|
7757
8119
|
} : draft);
|
|
7758
|
-
setFocus(
|
|
8120
|
+
setFocus(1);
|
|
7759
8121
|
setScrollOffset(0);
|
|
7760
8122
|
}
|
|
7761
8123
|
else if (route.name === 'configFields' && route.layer.scope !== 'effective' && input.toLowerCase() === 's') {
|
|
@@ -7812,7 +8174,8 @@ export function ConsoleAppV4(props) {
|
|
|
7812
8174
|
}
|
|
7813
8175
|
else if (key.upArrow || input.toLowerCase() === 'k') {
|
|
7814
8176
|
setFocus((value) => {
|
|
7815
|
-
const current =
|
|
8177
|
+
const current = route.name === 'batchSelection' && batchDraft?.stage === 'select'
|
|
8178
|
+
? normalizedEffectiveFocus : dynamicActionIds ? effectiveFocus : value;
|
|
7816
8179
|
const next = pluginRows.length > 0 ? moveIndex(pluginRows.length, current, -1) : moveFocus(actions, current, -1);
|
|
7817
8180
|
if (dynamicActionIds && dynamicActionSignature !== undefined) {
|
|
7818
8181
|
dynamicMenuFocus.current = {
|
|
@@ -7821,17 +8184,31 @@ export function ConsoleAppV4(props) {
|
|
|
7821
8184
|
focusedActionId: dynamicActionIds[next],
|
|
7822
8185
|
};
|
|
7823
8186
|
}
|
|
7824
|
-
const viewport = route.name === '
|
|
7825
|
-
?
|
|
7826
|
-
|
|
7827
|
-
|
|
7828
|
-
|
|
8187
|
+
const viewport = route.name === 'batchSelection' && batchDraft?.stage === 'select'
|
|
8188
|
+
? actionViewport(actions.slice(1), policy.mode === 'compact', Math.max(0, next - 1), Math.max(0, scrollOffset - 1), Math.max(1, pageActionBudget(5)))
|
|
8189
|
+
: route.name === 'portableSkillSelect' && snapshot.portableDraft
|
|
8190
|
+
? portableSkillCandidateViewport(actions.slice(0, snapshot.portableDraft.candidates.length), snapshot.portableDraft.candidates, policy.mode === 'compact', next, scrollOffset, pageActionBudget(actions.length - snapshot.portableDraft.candidates.length
|
|
8191
|
+
+ (portableVariantCandidates.length > 0 ? 3 : 0)))
|
|
8192
|
+
: actionViewport(actions, policy.mode === 'compact', next, scrollOffset, pageActionBudget(actionViewportStaticRows));
|
|
8193
|
+
if (route.name === 'pluginDependencies' && route.view !== 'removeBlocked') {
|
|
8194
|
+
const projection = buildDependencyGraphProjection({
|
|
8195
|
+
overviews: props.controller.listPluginDependencyOverviews(),
|
|
8196
|
+
rootPluginId: route.pluginId,
|
|
8197
|
+
direction: route.direction ?? 'dependencies',
|
|
8198
|
+
...(expandedDependencyPluginIds ? { expandedPluginIds: expandedDependencyPluginIds } : {}),
|
|
8199
|
+
});
|
|
8200
|
+
persistDependencyGraphContext(route, projection, expandedDependencyPluginIds ?? new Set(projection.defaultExpandedPluginIds), next, viewport.offset);
|
|
8201
|
+
}
|
|
8202
|
+
setScrollOffset(route.name === 'batchSelection' && batchDraft?.stage === 'select'
|
|
8203
|
+
? next === 0 ? 0 : viewport.offset + 1
|
|
8204
|
+
: viewport.offset);
|
|
7829
8205
|
return next;
|
|
7830
8206
|
});
|
|
7831
8207
|
}
|
|
7832
8208
|
else if (key.downArrow || input.toLowerCase() === 'j') {
|
|
7833
8209
|
setFocus((value) => {
|
|
7834
|
-
const current =
|
|
8210
|
+
const current = route.name === 'batchSelection' && batchDraft?.stage === 'select'
|
|
8211
|
+
? normalizedEffectiveFocus : dynamicActionIds ? effectiveFocus : value;
|
|
7835
8212
|
const next = pluginRows.length > 0 ? moveIndex(pluginRows.length, current, 1) : moveFocus(actions, current, 1);
|
|
7836
8213
|
if (dynamicActionIds && dynamicActionSignature !== undefined) {
|
|
7837
8214
|
dynamicMenuFocus.current = {
|
|
@@ -7840,11 +8217,24 @@ export function ConsoleAppV4(props) {
|
|
|
7840
8217
|
focusedActionId: dynamicActionIds[next],
|
|
7841
8218
|
};
|
|
7842
8219
|
}
|
|
7843
|
-
const viewport = route.name === '
|
|
7844
|
-
?
|
|
7845
|
-
|
|
7846
|
-
|
|
7847
|
-
|
|
8220
|
+
const viewport = route.name === 'batchSelection' && batchDraft?.stage === 'select'
|
|
8221
|
+
? actionViewport(actions.slice(1), policy.mode === 'compact', Math.max(0, next - 1), Math.max(0, scrollOffset - 1), Math.max(1, pageActionBudget(5)))
|
|
8222
|
+
: route.name === 'portableSkillSelect' && snapshot.portableDraft
|
|
8223
|
+
? portableSkillCandidateViewport(actions.slice(0, snapshot.portableDraft.candidates.length), snapshot.portableDraft.candidates, policy.mode === 'compact', next, scrollOffset, pageActionBudget(actions.length - snapshot.portableDraft.candidates.length
|
|
8224
|
+
+ (portableVariantCandidates.length > 0 ? 3 : 0)))
|
|
8225
|
+
: actionViewport(actions, policy.mode === 'compact', next, scrollOffset, pageActionBudget(actionViewportStaticRows));
|
|
8226
|
+
if (route.name === 'pluginDependencies' && route.view !== 'removeBlocked') {
|
|
8227
|
+
const projection = buildDependencyGraphProjection({
|
|
8228
|
+
overviews: props.controller.listPluginDependencyOverviews(),
|
|
8229
|
+
rootPluginId: route.pluginId,
|
|
8230
|
+
direction: route.direction ?? 'dependencies',
|
|
8231
|
+
...(expandedDependencyPluginIds ? { expandedPluginIds: expandedDependencyPluginIds } : {}),
|
|
8232
|
+
});
|
|
8233
|
+
persistDependencyGraphContext(route, projection, expandedDependencyPluginIds ?? new Set(projection.defaultExpandedPluginIds), next, viewport.offset);
|
|
8234
|
+
}
|
|
8235
|
+
setScrollOffset(route.name === 'batchSelection' && batchDraft?.stage === 'select'
|
|
8236
|
+
? next === 0 ? 0 : viewport.offset + 1
|
|
8237
|
+
: viewport.offset);
|
|
7848
8238
|
return next;
|
|
7849
8239
|
});
|
|
7850
8240
|
}
|
|
@@ -7858,7 +8248,7 @@ export function ConsoleAppV4(props) {
|
|
|
7858
8248
|
: { name: 'pluginDetail', pluginId: row.pluginId });
|
|
7859
8249
|
}
|
|
7860
8250
|
else if (key.return) {
|
|
7861
|
-
runAction(actions[
|
|
8251
|
+
runAction(actions[normalizedEffectiveFocus]);
|
|
7862
8252
|
}
|
|
7863
8253
|
});
|
|
7864
8254
|
if (tooSmall) {
|