dsh-gitnexus-plus 0.1.5 → 0.1.6
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/README-CN.md +5 -3
- package/README.md +32 -5
- package/lib/client.js +1220 -307
- package/lib/gitnexus/coverage.d.ts +20 -0
- package/lib/gitnexus/coverage.d.ts.map +1 -1
- package/lib/gitnexus/coverage.js +50 -0
- package/lib/gitnexus/coverage.js.map +1 -1
- package/lib/gitnexus/delta.d.ts +25 -0
- package/lib/gitnexus/delta.d.ts.map +1 -1
- package/lib/gitnexus/delta.js +86 -3
- package/lib/gitnexus/delta.js.map +1 -1
- package/lib/gitnexus/graph.d.ts +9 -1
- package/lib/gitnexus/graph.d.ts.map +1 -1
- package/lib/gitnexus/graph.js +68 -4
- package/lib/gitnexus/graph.js.map +1 -1
- package/lib/routes/graph.d.ts +1 -1
- package/lib/routes/graph.js +2 -2
- package/lib/routes/graph.js.map +1 -1
- package/package.json +5 -1
package/lib/client.js
CHANGED
|
@@ -1722,7 +1722,686 @@ define("sections/activity", ["require", "exports", "react", "format", "navigatio
|
|
|
1722
1722
|
*/
|
|
1723
1723
|
exports.MemoizedGitActivitySection = React.memo(GitActivitySection);
|
|
1724
1724
|
});
|
|
1725
|
-
define("sections/
|
|
1725
|
+
define("sections/graph3d", ["require", "exports", "react", "primitives"], function (require, exports, React, primitives_3) {
|
|
1726
|
+
"use strict";
|
|
1727
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
1728
|
+
exports.buildGraph3DLayout = buildGraph3DLayout;
|
|
1729
|
+
exports.graph3DPath = graph3DPath;
|
|
1730
|
+
exports.Graph3DView = Graph3DView;
|
|
1731
|
+
function hashHue(id) {
|
|
1732
|
+
let hash = 0;
|
|
1733
|
+
for (let index = 0; index < id.length; index += 1) {
|
|
1734
|
+
hash = (hash * 31 + id.charCodeAt(index)) >>> 0;
|
|
1735
|
+
}
|
|
1736
|
+
return hash % 360;
|
|
1737
|
+
}
|
|
1738
|
+
function fibPoint(index, count, radius) {
|
|
1739
|
+
if (count <= 1)
|
|
1740
|
+
return { x: 0, y: 0, z: 0 };
|
|
1741
|
+
const k = index + 0.5;
|
|
1742
|
+
const phi = Math.acos(1 - (2 * k) / count);
|
|
1743
|
+
const theta = Math.PI * (1 + Math.sqrt(5)) * k;
|
|
1744
|
+
return {
|
|
1745
|
+
x: Math.cos(theta) * Math.sin(phi) * radius,
|
|
1746
|
+
y: Math.cos(phi) * radius,
|
|
1747
|
+
z: Math.sin(theta) * Math.sin(phi) * radius,
|
|
1748
|
+
};
|
|
1749
|
+
}
|
|
1750
|
+
function edgeKey(source, target) {
|
|
1751
|
+
return source < target ? `${source}|${target}` : `${target}|${source}`;
|
|
1752
|
+
}
|
|
1753
|
+
/**
|
|
1754
|
+
* Build the deterministic 3D positions.
|
|
1755
|
+
*
|
|
1756
|
+
* The largest connected component sits at the origin, so the first camera angle has
|
|
1757
|
+
* a centre of gravity instead of landing on an empty hemisphere. Other components
|
|
1758
|
+
* orbit on a sphere; isolated nodes sit on an outer shell. No `Math.random()`: a
|
|
1759
|
+
* view that reshuffles on every poll is impossible to follow or test.
|
|
1760
|
+
*/
|
|
1761
|
+
function buildGraph3DLayout(nodes, edges) {
|
|
1762
|
+
const byId = new Map();
|
|
1763
|
+
const known = new Set(nodes.map((node) => node.id));
|
|
1764
|
+
const pairs = new Map();
|
|
1765
|
+
const adjacency = new Map();
|
|
1766
|
+
for (const node of nodes)
|
|
1767
|
+
adjacency.set(node.id, new Set());
|
|
1768
|
+
for (const edge of edges) {
|
|
1769
|
+
if (!known.has(edge.source) || !known.has(edge.target) || edge.source === edge.target)
|
|
1770
|
+
continue;
|
|
1771
|
+
const key = edgeKey(edge.source, edge.target);
|
|
1772
|
+
const pair = pairs.get(key);
|
|
1773
|
+
if (pair === undefined) {
|
|
1774
|
+
pairs.set(key, { source: edge.source, target: edge.target, weight: edge.weight });
|
|
1775
|
+
}
|
|
1776
|
+
else {
|
|
1777
|
+
pair.weight += edge.weight;
|
|
1778
|
+
}
|
|
1779
|
+
adjacency.get(edge.source)?.add(edge.target);
|
|
1780
|
+
adjacency.get(edge.target)?.add(edge.source);
|
|
1781
|
+
}
|
|
1782
|
+
const componentIds = [];
|
|
1783
|
+
const seen = new Set();
|
|
1784
|
+
for (const node of nodes) {
|
|
1785
|
+
if (seen.has(node.id))
|
|
1786
|
+
continue;
|
|
1787
|
+
const component = [];
|
|
1788
|
+
const queue = [node.id];
|
|
1789
|
+
seen.add(node.id);
|
|
1790
|
+
while (queue.length > 0) {
|
|
1791
|
+
const id = queue.pop();
|
|
1792
|
+
component.push(id);
|
|
1793
|
+
for (const neighbour of adjacency.get(id) ?? []) {
|
|
1794
|
+
if (seen.has(neighbour))
|
|
1795
|
+
continue;
|
|
1796
|
+
seen.add(neighbour);
|
|
1797
|
+
queue.push(neighbour);
|
|
1798
|
+
}
|
|
1799
|
+
}
|
|
1800
|
+
componentIds.push(component);
|
|
1801
|
+
}
|
|
1802
|
+
componentIds.sort((a, b) => b.length - a.length);
|
|
1803
|
+
let isolated = 0;
|
|
1804
|
+
componentIds.forEach((component, componentIndex) => {
|
|
1805
|
+
const isIsolated = component.length === 1;
|
|
1806
|
+
if (isIsolated)
|
|
1807
|
+
isolated += 1;
|
|
1808
|
+
const count = component.length;
|
|
1809
|
+
const radius = isIsolated ? 640 : 500;
|
|
1810
|
+
let centre = { x: 0, y: 0, z: 0 };
|
|
1811
|
+
if (componentIndex > 0) {
|
|
1812
|
+
const base = fibPoint(componentIndex - 1, Math.max(1, componentIds.length - 1), radius);
|
|
1813
|
+
const wobble = ((hashHue(component[0]) % 100) / 100 - 0.5) * 22;
|
|
1814
|
+
centre = { x: base.x + wobble, y: base.y + wobble * 0.5, z: base.z - wobble * 0.5 };
|
|
1815
|
+
}
|
|
1816
|
+
const clusterRadius = count === 1 ? 0 : 60 + 28 * Math.sqrt(count);
|
|
1817
|
+
component.forEach((id, index) => {
|
|
1818
|
+
const source = nodes.find((node) => node.id === id);
|
|
1819
|
+
if (source === undefined)
|
|
1820
|
+
return;
|
|
1821
|
+
const offset = fibPoint(index, count, clusterRadius);
|
|
1822
|
+
const symbols = source.symbolCount ?? 1;
|
|
1823
|
+
const degree = adjacency.get(id)?.size ?? 0;
|
|
1824
|
+
byId.set(id, {
|
|
1825
|
+
...source,
|
|
1826
|
+
position: { x: centre.x + offset.x, y: centre.y + offset.y, z: centre.z + offset.z },
|
|
1827
|
+
radius: 7 + Math.sqrt(Math.max(1, symbols)) * 1.8,
|
|
1828
|
+
degree,
|
|
1829
|
+
color: degree === 0 ? 'hsl(220 9% 58%)' : `hsl(${hashHue(id)} 68% 64%)`,
|
|
1830
|
+
isolated: degree === 0,
|
|
1831
|
+
});
|
|
1832
|
+
});
|
|
1833
|
+
});
|
|
1834
|
+
const aggregated = [];
|
|
1835
|
+
for (const pair of pairs.values()) {
|
|
1836
|
+
aggregated.push({ source: pair.source, target: pair.target, type: 'DEPENDS', weight: pair.weight });
|
|
1837
|
+
}
|
|
1838
|
+
return {
|
|
1839
|
+
nodes: nodes.map((node) => byId.get(node.id)).filter((node) => node !== undefined),
|
|
1840
|
+
edges: aggregated,
|
|
1841
|
+
byId,
|
|
1842
|
+
components: componentIds.length,
|
|
1843
|
+
isolated,
|
|
1844
|
+
};
|
|
1845
|
+
}
|
|
1846
|
+
/** Shortest undirected path between two communities. */
|
|
1847
|
+
function graph3DPath(startId, endId, edges) {
|
|
1848
|
+
if (startId === endId)
|
|
1849
|
+
return [startId];
|
|
1850
|
+
const adjacency = new Map();
|
|
1851
|
+
const link = (from, to) => {
|
|
1852
|
+
const existing = adjacency.get(from);
|
|
1853
|
+
if (existing === undefined)
|
|
1854
|
+
adjacency.set(from, [to]);
|
|
1855
|
+
else
|
|
1856
|
+
existing.push(to);
|
|
1857
|
+
};
|
|
1858
|
+
for (const edge of edges) {
|
|
1859
|
+
link(edge.source, edge.target);
|
|
1860
|
+
link(edge.target, edge.source);
|
|
1861
|
+
}
|
|
1862
|
+
const previous = new Map([[startId, null]]);
|
|
1863
|
+
const queue = [startId];
|
|
1864
|
+
while (queue.length > 0) {
|
|
1865
|
+
const id = queue.shift();
|
|
1866
|
+
if (id === endId)
|
|
1867
|
+
break;
|
|
1868
|
+
for (const neighbour of adjacency.get(id) ?? []) {
|
|
1869
|
+
if (previous.has(neighbour))
|
|
1870
|
+
continue;
|
|
1871
|
+
previous.set(neighbour, id);
|
|
1872
|
+
queue.push(neighbour);
|
|
1873
|
+
}
|
|
1874
|
+
}
|
|
1875
|
+
if (!previous.has(endId))
|
|
1876
|
+
return null;
|
|
1877
|
+
const path = [];
|
|
1878
|
+
let current = endId;
|
|
1879
|
+
while (current !== null && current !== undefined) {
|
|
1880
|
+
path.push(current);
|
|
1881
|
+
current = previous.get(current);
|
|
1882
|
+
}
|
|
1883
|
+
return path.reverse();
|
|
1884
|
+
}
|
|
1885
|
+
function project(point, view, width, height) {
|
|
1886
|
+
const cosYaw = Math.cos(view.yaw);
|
|
1887
|
+
const sinYaw = Math.sin(view.yaw);
|
|
1888
|
+
const cosPitch = Math.cos(view.pitch);
|
|
1889
|
+
const sinPitch = Math.sin(view.pitch);
|
|
1890
|
+
const x1 = point.x * cosYaw + point.z * sinYaw;
|
|
1891
|
+
const z1 = -point.x * sinYaw + point.z * cosYaw;
|
|
1892
|
+
const y2 = point.y * cosPitch - z1 * sinPitch;
|
|
1893
|
+
const z2 = point.y * sinPitch + z1 * cosPitch;
|
|
1894
|
+
const z = view.dist + z2;
|
|
1895
|
+
if (z < 1)
|
|
1896
|
+
return null;
|
|
1897
|
+
const scale = 900 / z;
|
|
1898
|
+
return { x: width / 2 + x1 * scale, y: height / 2 - y2 * scale, scale, depth: z };
|
|
1899
|
+
}
|
|
1900
|
+
function clamp(value, low, high) {
|
|
1901
|
+
return Math.max(low, Math.min(high, value));
|
|
1902
|
+
}
|
|
1903
|
+
function fileBase(filePath) {
|
|
1904
|
+
if (filePath === null || filePath === undefined || filePath === '')
|
|
1905
|
+
return null;
|
|
1906
|
+
const parts = filePath.split(/[\\/]/);
|
|
1907
|
+
return parts[parts.length - 1] ?? filePath;
|
|
1908
|
+
}
|
|
1909
|
+
function labelFor(node) {
|
|
1910
|
+
const id = `#${node.id.replace(/^comm_/, '').slice(0, 10)}`;
|
|
1911
|
+
const base = fileBase(node.representativeFile);
|
|
1912
|
+
return base === null ? `${node.name} ${id}` : `${node.name} · ${base} ${id}`;
|
|
1913
|
+
}
|
|
1914
|
+
const CONTROL = {
|
|
1915
|
+
padding: '3px 6px',
|
|
1916
|
+
border: primitives_3.HAIRLINE,
|
|
1917
|
+
borderRadius: '4px',
|
|
1918
|
+
background: 'transparent',
|
|
1919
|
+
color: 'inherit',
|
|
1920
|
+
cursor: 'pointer',
|
|
1921
|
+
fontSize: '10px',
|
|
1922
|
+
lineHeight: 1.5,
|
|
1923
|
+
};
|
|
1924
|
+
const ACTIVE = {
|
|
1925
|
+
...CONTROL,
|
|
1926
|
+
background: primitives_3.SOFT_BG,
|
|
1927
|
+
fontWeight: 600,
|
|
1928
|
+
};
|
|
1929
|
+
const OPTION_STYLE = {
|
|
1930
|
+
background: 'var(--dsw-alias-bg-base, #1e1e2e)',
|
|
1931
|
+
color: 'var(--dsw-alias-text-primary, #cdd6f4)',
|
|
1932
|
+
};
|
|
1933
|
+
function Graph3DView({ projection, onOpenCommunity }) {
|
|
1934
|
+
const layout = React.useMemo(() => buildGraph3DLayout(projection.nodes, projection.edges), [projection]);
|
|
1935
|
+
const canvasRef = React.useRef(null);
|
|
1936
|
+
const screenRef = React.useRef([]);
|
|
1937
|
+
const viewRef = React.useRef({ yaw: 0.7, pitch: 0.28, dist: 3400 });
|
|
1938
|
+
const dragRef = React.useRef({ x: 0, y: 0, moved: false, active: false });
|
|
1939
|
+
const [selectedId, setSelectedId] = React.useState(null);
|
|
1940
|
+
const [hoverId, setHoverId] = React.useState(null);
|
|
1941
|
+
const [labelsMode, setLabelsMode] = React.useState('auto');
|
|
1942
|
+
const [showIsolated, setShowIsolated] = React.useState(true);
|
|
1943
|
+
const [autoRotate, setAutoRotate] = React.useState(false);
|
|
1944
|
+
const [expanded, setExpanded] = React.useState(false);
|
|
1945
|
+
const [pathMode, setPathMode] = React.useState(false);
|
|
1946
|
+
const [startId, setStartId] = React.useState(null);
|
|
1947
|
+
const [endId, setEndId] = React.useState(null);
|
|
1948
|
+
const [query, setQuery] = React.useState('');
|
|
1949
|
+
const selected = selectedId === null ? null : (layout.byId.get(selectedId) ?? null);
|
|
1950
|
+
const start = startId === null ? null : (layout.byId.get(startId) ?? null);
|
|
1951
|
+
const end = endId === null ? null : (layout.byId.get(endId) ?? null);
|
|
1952
|
+
const path = React.useMemo(() => {
|
|
1953
|
+
if (startId === null || endId === null) {
|
|
1954
|
+
return { nodes: new Set(), edges: new Set(), list: [], error: null };
|
|
1955
|
+
}
|
|
1956
|
+
const list = graph3DPath(startId, endId, layout.edges);
|
|
1957
|
+
if (list === null) {
|
|
1958
|
+
return { nodes: new Set(), edges: new Set(), list: [], error: '这两个社区不在同一连通分量,没有路径。' };
|
|
1959
|
+
}
|
|
1960
|
+
const pathNodes = new Set(list);
|
|
1961
|
+
const pathEdges = new Set();
|
|
1962
|
+
for (let index = 1; index < list.length; index += 1) {
|
|
1963
|
+
pathEdges.add(edgeKey(list[index - 1], list[index]));
|
|
1964
|
+
}
|
|
1965
|
+
return { nodes: pathNodes, edges: pathEdges, list, error: null };
|
|
1966
|
+
}, [endId, layout, startId]);
|
|
1967
|
+
const sortedNodes = React.useMemo(() => [...layout.nodes].sort((a, b) => b.degree - a.degree || (b.symbolCount ?? 0) - (a.symbolCount ?? 0) || a.id.localeCompare(b.id)), [layout]);
|
|
1968
|
+
const filteredNodes = React.useMemo(() => {
|
|
1969
|
+
const needle = query.trim().toLowerCase();
|
|
1970
|
+
if (needle === '')
|
|
1971
|
+
return sortedNodes;
|
|
1972
|
+
return sortedNodes.filter((node) => labelFor(node).toLowerCase().includes(needle));
|
|
1973
|
+
}, [query, sortedNodes]);
|
|
1974
|
+
const hovered = hoverId === null ? null : (layout.byId.get(hoverId) ?? null);
|
|
1975
|
+
const draw = React.useCallback(() => {
|
|
1976
|
+
const canvas = canvasRef.current;
|
|
1977
|
+
if (canvas === null)
|
|
1978
|
+
return;
|
|
1979
|
+
const context = canvas.getContext('2d');
|
|
1980
|
+
if (context === null)
|
|
1981
|
+
return;
|
|
1982
|
+
const rect = canvas.getBoundingClientRect();
|
|
1983
|
+
const width = Math.max(1, rect.width);
|
|
1984
|
+
const height = Math.max(1, rect.height);
|
|
1985
|
+
const ratio = Math.min(2, window.devicePixelRatio || 1);
|
|
1986
|
+
if (canvas.width !== Math.round(width * ratio) || canvas.height !== Math.round(height * ratio)) {
|
|
1987
|
+
canvas.width = Math.round(width * ratio);
|
|
1988
|
+
canvas.height = Math.round(height * ratio);
|
|
1989
|
+
}
|
|
1990
|
+
context.setTransform(ratio, 0, 0, ratio, 0, 0);
|
|
1991
|
+
context.clearRect(0, 0, width, height);
|
|
1992
|
+
const background = context.createRadialGradient(width / 2, height / 2, 30, width / 2, height / 2, Math.max(width, height) * 0.8);
|
|
1993
|
+
background.addColorStop(0, '#191929');
|
|
1994
|
+
background.addColorStop(0.58, '#11111c');
|
|
1995
|
+
background.addColorStop(1, '#09090f');
|
|
1996
|
+
context.fillStyle = background;
|
|
1997
|
+
context.fillRect(0, 0, width, height);
|
|
1998
|
+
context.save();
|
|
1999
|
+
context.strokeStyle = 'rgba(137,180,250,0.055)';
|
|
2000
|
+
context.lineWidth = 1;
|
|
2001
|
+
for (const plane of ['xy', 'xz', 'yz']) {
|
|
2002
|
+
context.beginPath();
|
|
2003
|
+
let started = false;
|
|
2004
|
+
for (let index = 0; index <= 120; index += 1) {
|
|
2005
|
+
const angle = (index / 120) * Math.PI * 2;
|
|
2006
|
+
const point = plane === 'xy'
|
|
2007
|
+
? { x: Math.cos(angle) * 620, y: Math.sin(angle) * 620, z: 0 }
|
|
2008
|
+
: plane === 'xz'
|
|
2009
|
+
? { x: Math.cos(angle) * 620, y: 0, z: Math.sin(angle) * 620 }
|
|
2010
|
+
: { x: 0, y: Math.cos(angle) * 620, z: Math.sin(angle) * 620 };
|
|
2011
|
+
const projected = project(point, viewRef.current, width, height);
|
|
2012
|
+
if (projected === null) {
|
|
2013
|
+
started = false;
|
|
2014
|
+
continue;
|
|
2015
|
+
}
|
|
2016
|
+
if (!started) {
|
|
2017
|
+
context.moveTo(projected.x, projected.y);
|
|
2018
|
+
started = true;
|
|
2019
|
+
}
|
|
2020
|
+
else {
|
|
2021
|
+
context.lineTo(projected.x, projected.y);
|
|
2022
|
+
}
|
|
2023
|
+
}
|
|
2024
|
+
context.stroke();
|
|
2025
|
+
}
|
|
2026
|
+
context.restore();
|
|
2027
|
+
const projected = layout.nodes
|
|
2028
|
+
.map((node) => ({ node, p: project(node.position, viewRef.current, width, height) }))
|
|
2029
|
+
.filter((entry) => entry.p !== null && (showIsolated || !entry.node.isolated));
|
|
2030
|
+
screenRef.current = projected;
|
|
2031
|
+
const projectedById = new Map(projected.map((entry) => [entry.node.id, entry]));
|
|
2032
|
+
const edgeLines = [];
|
|
2033
|
+
for (const edge of layout.edges) {
|
|
2034
|
+
const source = projectedById.get(edge.source);
|
|
2035
|
+
const target = projectedById.get(edge.target);
|
|
2036
|
+
if (source === undefined || target === undefined)
|
|
2037
|
+
continue;
|
|
2038
|
+
edgeLines.push({
|
|
2039
|
+
source: source.p,
|
|
2040
|
+
target: target.p,
|
|
2041
|
+
weight: edge.weight,
|
|
2042
|
+
key: edgeKey(edge.source, edge.target),
|
|
2043
|
+
depth: source.p.depth + target.p.depth,
|
|
2044
|
+
});
|
|
2045
|
+
}
|
|
2046
|
+
edgeLines.sort((a, b) => b.depth - a.depth);
|
|
2047
|
+
for (const edge of edgeLines) {
|
|
2048
|
+
const isPath = path.edges.has(edge.key);
|
|
2049
|
+
const alpha = clamp(0.78 - (edge.depth / 2 - 700) / 2000, 0.08, 0.68);
|
|
2050
|
+
context.beginPath();
|
|
2051
|
+
context.moveTo(edge.source.x, edge.source.y);
|
|
2052
|
+
context.lineTo(edge.target.x, edge.target.y);
|
|
2053
|
+
context.strokeStyle = isPath ? 'rgba(250,179,135,0.95)' : `rgba(128,138,158,${alpha})`;
|
|
2054
|
+
context.lineWidth = (isPath ? 2.8 : 0.8) + Math.log2(1 + edge.weight) * (isPath ? 0.5 : 0.7);
|
|
2055
|
+
context.stroke();
|
|
2056
|
+
}
|
|
2057
|
+
for (const entry of projected) {
|
|
2058
|
+
const { node, p } = entry;
|
|
2059
|
+
const radius = Math.max(2.2, node.radius * p.scale);
|
|
2060
|
+
const isHot = node.id === selectedId || node.id === hoverId || path.nodes.has(node.id);
|
|
2061
|
+
context.beginPath();
|
|
2062
|
+
context.arc(p.x, p.y, radius + (isHot ? 4 : 0), 0, Math.PI * 2);
|
|
2063
|
+
context.fillStyle = isHot ? 'rgba(255,255,255,0.16)' : 'rgba(255,255,255,0.04)';
|
|
2064
|
+
context.fill();
|
|
2065
|
+
context.beginPath();
|
|
2066
|
+
context.arc(p.x, p.y, radius, 0, Math.PI * 2);
|
|
2067
|
+
context.fillStyle = node.color;
|
|
2068
|
+
context.globalAlpha = clamp(0.95 - (p.depth / 2 - 700) / 2000, 0.42, 0.95);
|
|
2069
|
+
context.fill();
|
|
2070
|
+
context.globalAlpha = 1;
|
|
2071
|
+
if (node.id === selectedId || node.id === startId || node.id === endId) {
|
|
2072
|
+
context.beginPath();
|
|
2073
|
+
context.arc(p.x, p.y, radius + 3, 0, Math.PI * 2);
|
|
2074
|
+
context.strokeStyle = node.id === selectedId ? '#f5f5ff' : '#fab387';
|
|
2075
|
+
context.lineWidth = 2;
|
|
2076
|
+
context.stroke();
|
|
2077
|
+
}
|
|
2078
|
+
}
|
|
2079
|
+
if (labelsMode !== 'off') {
|
|
2080
|
+
const boxes = [];
|
|
2081
|
+
const scored = projected
|
|
2082
|
+
.map((entry) => {
|
|
2083
|
+
const score = (entry.node.id === selectedId ? 1000 : 0) +
|
|
2084
|
+
(entry.node.id === hoverId ? 900 : 0) +
|
|
2085
|
+
(entry.node.id === startId || entry.node.id === endId ? 600 : 0) +
|
|
2086
|
+
(path.nodes.has(entry.node.id) ? 400 : 0) +
|
|
2087
|
+
entry.node.degree;
|
|
2088
|
+
return { ...entry, score };
|
|
2089
|
+
})
|
|
2090
|
+
.filter((entry) => labelsMode === 'all' || entry.score >= 4 || entry.node.id === selectedId || entry.node.id === hoverId)
|
|
2091
|
+
.sort((a, b) => b.score - a.score);
|
|
2092
|
+
const limit = labelsMode === 'all' ? scored.length : 8;
|
|
2093
|
+
for (const entry of scored.slice(0, limit)) {
|
|
2094
|
+
const { node, p } = entry;
|
|
2095
|
+
const text = labelFor(node);
|
|
2096
|
+
const force = node.id === selectedId || node.id === hoverId;
|
|
2097
|
+
context.font = '10px "Segoe UI", "Microsoft YaHei", system-ui, sans-serif';
|
|
2098
|
+
const textWidth = context.measureText(text).width;
|
|
2099
|
+
const boxWidth = textWidth + 6;
|
|
2100
|
+
const boxHeight = 14;
|
|
2101
|
+
const radius = Math.max(2.2, node.radius * p.scale);
|
|
2102
|
+
const x = clamp(p.x - boxWidth / 2, 2, Math.max(2, width - boxWidth - 2));
|
|
2103
|
+
const y = clamp(p.y + radius + 2, 2, Math.max(2, height - boxHeight - 2));
|
|
2104
|
+
const collides = boxes.some((box) => x < box.x + box.w && x + boxWidth > box.x && y < box.y + box.h && y + boxHeight > box.y);
|
|
2105
|
+
if (collides && !force)
|
|
2106
|
+
continue;
|
|
2107
|
+
boxes.push({ x, y, w: boxWidth, h: boxHeight });
|
|
2108
|
+
context.fillStyle = force ? 'rgba(30,30,45,0.94)' : 'rgba(16,16,26,0.80)';
|
|
2109
|
+
context.fillRect(x, y, boxWidth, boxHeight);
|
|
2110
|
+
context.strokeStyle = force ? 'rgba(137,180,250,0.55)' : 'rgba(42,42,61,0.8)';
|
|
2111
|
+
context.strokeRect(x, y, boxWidth, boxHeight);
|
|
2112
|
+
context.fillStyle = force ? '#ffffff' : '#e5e9f6';
|
|
2113
|
+
context.textAlign = 'left';
|
|
2114
|
+
context.textBaseline = 'middle';
|
|
2115
|
+
context.fillText(text, x + 3, y + boxHeight / 2 + 0.5);
|
|
2116
|
+
}
|
|
2117
|
+
}
|
|
2118
|
+
}, [endId, hoverId, labelsMode, layout, path, selectedId, showIsolated, startId]);
|
|
2119
|
+
React.useEffect(() => {
|
|
2120
|
+
const canvas = canvasRef.current;
|
|
2121
|
+
if (canvas === null || typeof ResizeObserver === 'undefined')
|
|
2122
|
+
return undefined;
|
|
2123
|
+
const observer = new ResizeObserver(() => draw());
|
|
2124
|
+
observer.observe(canvas);
|
|
2125
|
+
return () => observer.disconnect();
|
|
2126
|
+
}, [draw]);
|
|
2127
|
+
React.useEffect(() => {
|
|
2128
|
+
if (!autoRotate) {
|
|
2129
|
+
draw();
|
|
2130
|
+
return undefined;
|
|
2131
|
+
}
|
|
2132
|
+
let frame = 0;
|
|
2133
|
+
const tick = () => {
|
|
2134
|
+
if (!dragRef.current.active)
|
|
2135
|
+
viewRef.current.yaw += 0.0024;
|
|
2136
|
+
draw();
|
|
2137
|
+
frame = requestAnimationFrame(tick);
|
|
2138
|
+
};
|
|
2139
|
+
frame = requestAnimationFrame(tick);
|
|
2140
|
+
return () => cancelAnimationFrame(frame);
|
|
2141
|
+
}, [autoRotate, draw]);
|
|
2142
|
+
React.useEffect(() => {
|
|
2143
|
+
const canvas = canvasRef.current;
|
|
2144
|
+
if (canvas === null)
|
|
2145
|
+
return undefined;
|
|
2146
|
+
const onWheel = (event) => {
|
|
2147
|
+
event.preventDefault();
|
|
2148
|
+
viewRef.current.dist = clamp(viewRef.current.dist * Math.exp(event.deltaY * 0.0012), 1600, 5600);
|
|
2149
|
+
draw();
|
|
2150
|
+
};
|
|
2151
|
+
canvas.addEventListener('wheel', onWheel, { passive: false });
|
|
2152
|
+
return () => canvas.removeEventListener('wheel', onWheel);
|
|
2153
|
+
}, [draw]);
|
|
2154
|
+
const pointOf = (event) => {
|
|
2155
|
+
const rect = event.currentTarget.getBoundingClientRect();
|
|
2156
|
+
return { x: event.clientX - rect.left, y: event.clientY - rect.top };
|
|
2157
|
+
};
|
|
2158
|
+
const nodeAt = (event) => {
|
|
2159
|
+
const point = pointOf(event);
|
|
2160
|
+
let best = null;
|
|
2161
|
+
let bestDistance = Infinity;
|
|
2162
|
+
for (const entry of screenRef.current) {
|
|
2163
|
+
const radius = Math.max(6, entry.node.radius * entry.p.scale + 4);
|
|
2164
|
+
const distance = Math.hypot(entry.p.x - point.x, entry.p.y - point.y);
|
|
2165
|
+
if (distance <= radius && distance < bestDistance) {
|
|
2166
|
+
best = entry.node;
|
|
2167
|
+
bestDistance = distance;
|
|
2168
|
+
}
|
|
2169
|
+
}
|
|
2170
|
+
return best;
|
|
2171
|
+
};
|
|
2172
|
+
const handleClick = (node) => {
|
|
2173
|
+
if (!pathMode) {
|
|
2174
|
+
setSelectedId(node.id);
|
|
2175
|
+
return;
|
|
2176
|
+
}
|
|
2177
|
+
if (startId === null || (startId !== null && endId !== null)) {
|
|
2178
|
+
setStartId(node.id);
|
|
2179
|
+
setEndId(null);
|
|
2180
|
+
setSelectedId(node.id);
|
|
2181
|
+
}
|
|
2182
|
+
else if (node.id === startId) {
|
|
2183
|
+
setStartId(null);
|
|
2184
|
+
setEndId(null);
|
|
2185
|
+
}
|
|
2186
|
+
else {
|
|
2187
|
+
setEndId(node.id);
|
|
2188
|
+
setSelectedId(node.id);
|
|
2189
|
+
}
|
|
2190
|
+
};
|
|
2191
|
+
const onPointerDown = (event) => {
|
|
2192
|
+
dragRef.current = { x: event.clientX, y: event.clientY, moved: false, active: true };
|
|
2193
|
+
event.currentTarget.setPointerCapture?.(event.pointerId);
|
|
2194
|
+
};
|
|
2195
|
+
const onPointerMove = (event) => {
|
|
2196
|
+
if (dragRef.current.active) {
|
|
2197
|
+
const dx = event.clientX - dragRef.current.x;
|
|
2198
|
+
const dy = event.clientY - dragRef.current.y;
|
|
2199
|
+
if (Math.abs(dx) + Math.abs(dy) > 3)
|
|
2200
|
+
dragRef.current.moved = true;
|
|
2201
|
+
if (dragRef.current.moved) {
|
|
2202
|
+
viewRef.current.yaw += dx * 0.006;
|
|
2203
|
+
viewRef.current.pitch = clamp(viewRef.current.pitch + dy * 0.005, -1.2, 1.2);
|
|
2204
|
+
dragRef.current.x = event.clientX;
|
|
2205
|
+
dragRef.current.y = event.clientY;
|
|
2206
|
+
draw();
|
|
2207
|
+
return;
|
|
2208
|
+
}
|
|
2209
|
+
}
|
|
2210
|
+
setHoverId(nodeAt(event)?.id ?? null);
|
|
2211
|
+
};
|
|
2212
|
+
const onPointerUp = (event) => {
|
|
2213
|
+
const moved = dragRef.current.moved;
|
|
2214
|
+
dragRef.current.active = false;
|
|
2215
|
+
if (moved)
|
|
2216
|
+
return;
|
|
2217
|
+
const node = nodeAt(event);
|
|
2218
|
+
if (node === null) {
|
|
2219
|
+
setSelectedId(null);
|
|
2220
|
+
return;
|
|
2221
|
+
}
|
|
2222
|
+
handleClick(node);
|
|
2223
|
+
};
|
|
2224
|
+
const onDoubleClick = (event) => {
|
|
2225
|
+
const node = nodeAt(event);
|
|
2226
|
+
if (node !== null)
|
|
2227
|
+
onOpenCommunity(node.id);
|
|
2228
|
+
};
|
|
2229
|
+
const resetView = () => {
|
|
2230
|
+
viewRef.current = { yaw: 0.7, pitch: 0.28, dist: expanded ? 3000 : 3400 };
|
|
2231
|
+
draw();
|
|
2232
|
+
};
|
|
2233
|
+
const cycleLabels = () => {
|
|
2234
|
+
setLabelsMode(labelsMode === 'auto' ? 'all' : labelsMode === 'all' ? 'off' : 'auto');
|
|
2235
|
+
};
|
|
2236
|
+
const canvasHeight = expanded ? 'min(56vh, 500px)' : 'clamp(240px, 28vw, 340px)';
|
|
2237
|
+
const pathNames = start === null || end === null ? [] : path.list.map((id) => layout.byId.get(id)).filter((node) => node !== undefined);
|
|
2238
|
+
React.useEffect(() => {
|
|
2239
|
+
if (!expanded)
|
|
2240
|
+
return undefined;
|
|
2241
|
+
const onKey = (event) => {
|
|
2242
|
+
if (event.key !== 'Escape')
|
|
2243
|
+
return;
|
|
2244
|
+
setExpanded(false);
|
|
2245
|
+
viewRef.current.dist = 3400;
|
|
2246
|
+
draw();
|
|
2247
|
+
};
|
|
2248
|
+
window.addEventListener('keydown', onKey);
|
|
2249
|
+
return () => window.removeEventListener('keydown', onKey);
|
|
2250
|
+
}, [expanded, draw]);
|
|
2251
|
+
const symbolTotal = projection.nodes.reduce((sum, node) => sum + (node.symbolCount ?? 0), 0);
|
|
2252
|
+
const canvasBlock = (0, primitives_3.h)('div', {
|
|
2253
|
+
style: {
|
|
2254
|
+
position: 'relative',
|
|
2255
|
+
width: '100%',
|
|
2256
|
+
height: canvasHeight,
|
|
2257
|
+
border: primitives_3.HAIRLINE,
|
|
2258
|
+
borderRadius: '6px',
|
|
2259
|
+
background: '#11111c',
|
|
2260
|
+
overflow: 'hidden',
|
|
2261
|
+
},
|
|
2262
|
+
}, (0, primitives_3.h)('canvas', {
|
|
2263
|
+
ref: canvasRef,
|
|
2264
|
+
'aria-label': '社区关系三维图:拖拽旋转,滚轮缩放,单击社区查看详情,双击进入社区',
|
|
2265
|
+
style: { width: '100%', height: '100%', display: 'block', cursor: 'grab', touchAction: 'none' },
|
|
2266
|
+
onPointerDown,
|
|
2267
|
+
onPointerMove,
|
|
2268
|
+
onPointerUp,
|
|
2269
|
+
onDoubleClick,
|
|
2270
|
+
onPointerLeave: () => {
|
|
2271
|
+
dragRef.current.active = false;
|
|
2272
|
+
setHoverId(null);
|
|
2273
|
+
},
|
|
2274
|
+
}), hovered === null
|
|
2275
|
+
? null
|
|
2276
|
+
: (0, primitives_3.h)('div', {
|
|
2277
|
+
style: {
|
|
2278
|
+
position: 'absolute',
|
|
2279
|
+
left: '6px',
|
|
2280
|
+
bottom: '6px',
|
|
2281
|
+
maxWidth: 'calc(100% - 12px)',
|
|
2282
|
+
padding: '3px 6px',
|
|
2283
|
+
borderRadius: '4px',
|
|
2284
|
+
background: 'rgba(13,13,23,0.9)',
|
|
2285
|
+
color: '#e5e9f6',
|
|
2286
|
+
fontSize: '10px',
|
|
2287
|
+
pointerEvents: 'none',
|
|
2288
|
+
overflow: 'hidden',
|
|
2289
|
+
textOverflow: 'ellipsis',
|
|
2290
|
+
whiteSpace: 'nowrap',
|
|
2291
|
+
},
|
|
2292
|
+
}, `${labelFor(hovered)} · ${hovered.degree} 度 · ${hovered.symbolCount ?? 0} 符号`), layout.nodes.length === 0
|
|
2293
|
+
? (0, primitives_3.h)('div', {
|
|
2294
|
+
style: {
|
|
2295
|
+
position: 'absolute',
|
|
2296
|
+
inset: 0,
|
|
2297
|
+
display: 'flex',
|
|
2298
|
+
alignItems: 'center',
|
|
2299
|
+
justifyContent: 'center',
|
|
2300
|
+
...primitives_3.MUTED,
|
|
2301
|
+
padding: '0 20px',
|
|
2302
|
+
textAlign: 'center',
|
|
2303
|
+
pointerEvents: 'none',
|
|
2304
|
+
},
|
|
2305
|
+
}, '该视图没有可画的社区。')
|
|
2306
|
+
: null);
|
|
2307
|
+
const controlsRow = (0, primitives_3.h)('div', { style: { display: 'flex', flexWrap: 'wrap', gap: '4px', alignItems: 'center' } }, (0, primitives_3.h)('select', {
|
|
2308
|
+
value: selectedId ?? '',
|
|
2309
|
+
onChange: (event) => setSelectedId(event.target.value === '' ? null : event.target.value),
|
|
2310
|
+
style: { flex: '1 1 120px', minWidth: 0, padding: '3px 4px', border: primitives_3.HAIRLINE, borderRadius: '4px', background: 'var(--dsw-alias-bg-base, #1e1e2e)', color: 'var(--dsw-alias-text-primary, #cdd6f4)', colorScheme: 'dark', fontSize: '10px' },
|
|
2311
|
+
}, (0, primitives_3.h)('option', { value: '', style: OPTION_STYLE }, '选择社区…'), sortedNodes.map((node) => (0, primitives_3.h)('option', { key: node.id, value: node.id, style: OPTION_STYLE }, labelFor(node)))), (0, primitives_3.h)('button', { type: 'button', style: pathMode ? ACTIVE : CONTROL, onClick: () => { const next = !pathMode; setPathMode(next); if (!next) {
|
|
2312
|
+
setStartId(null);
|
|
2313
|
+
setEndId(null);
|
|
2314
|
+
} } }, '路径'), (0, primitives_3.h)('button', { type: 'button', style: CONTROL, onClick: cycleLabels }, `标签:${labelsMode === 'auto' ? '自动' : labelsMode === 'all' ? '全部' : '关闭'}`), (0, primitives_3.h)('button', { type: 'button', style: showIsolated ? ACTIVE : CONTROL, onClick: () => setShowIsolated(!showIsolated) }, `孤立:${showIsolated ? '显示' : '隐藏'}`), (0, primitives_3.h)('button', { type: 'button', style: autoRotate ? ACTIVE : CONTROL, onClick: () => setAutoRotate(!autoRotate) }, '自动'), (0, primitives_3.h)('button', { type: 'button', style: expanded ? ACTIVE : CONTROL, onClick: () => { const next = !expanded; setExpanded(next); viewRef.current.dist = next ? 3000 : 3400; draw(); } }, expanded ? '收起' : '展开'), (0, primitives_3.h)('button', { type: 'button', style: CONTROL, onClick: resetView }, '适配'));
|
|
2315
|
+
const detailBlock = selected === null
|
|
2316
|
+
? (0, primitives_3.h)('div', { style: { ...primitives_3.MUTED, fontSize: '10px' } }, '拖拽旋转 Z 轴、滚轮缩放;单击看代表文件,双击进入社区。')
|
|
2317
|
+
: (0, primitives_3.h)('div', { style: { display: 'flex', alignItems: 'center', gap: '6px', minWidth: 0 } }, (0, primitives_3.h)('span', { style: { width: '8px', height: '8px', flex: '0 0 8px', borderRadius: '50%', background: selected.color } }), (0, primitives_3.h)('span', { style: { flex: 1, minWidth: 0, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', fontSize: '11px' } }, `${labelFor(selected)} · ${selected.symbolCount ?? 0} 符号`), (0, primitives_3.h)('button', { type: 'button', style: CONTROL, onClick: () => onOpenCommunity(selected.id) }, '进入社区'));
|
|
2318
|
+
const pathBlock = pathMode
|
|
2319
|
+
? (0, primitives_3.h)('div', { style: { display: 'flex', flexDirection: 'column', gap: '3px', fontSize: '10px' } }, (0, primitives_3.h)('div', { style: { display: 'flex', gap: '4px', alignItems: 'center' } }, (0, primitives_3.h)('span', { style: primitives_3.MUTED }, '路径'), (0, primitives_3.h)('span', { style: { flex: 1 } }), (0, primitives_3.h)('button', { type: 'button', style: CONTROL, onClick: () => { setStartId(null); setEndId(null); } }, '清除')), start === null || end === null
|
|
2320
|
+
? (0, primitives_3.h)('div', { style: primitives_3.MUTED }, '点两个社区;路径按无向关系计算。')
|
|
2321
|
+
: path.error === null
|
|
2322
|
+
? (0, primitives_3.h)('div', { style: { display: 'flex', flexWrap: 'wrap', gap: '3px', color: '#f6d5bc' } }, pathNames.map((node) => (0, primitives_3.h)('span', { key: node.id, style: { padding: '1px 4px', borderRadius: '3px', background: 'rgba(250,179,135,0.12)' } }, node.name)))
|
|
2323
|
+
: (0, primitives_3.h)('div', { style: { color: '#f7768e' } }, path.error), start !== null && end !== null && path.error === null
|
|
2324
|
+
? (0, primitives_3.h)('button', { type: 'button', style: CONTROL, onClick: () => setSelectedId(path.list[path.list.length - 1] ?? null) }, `终点 ${end.name}`)
|
|
2325
|
+
: null)
|
|
2326
|
+
: null;
|
|
2327
|
+
const notesBlock = projection.notes.length === 0
|
|
2328
|
+
? null
|
|
2329
|
+
: (0, primitives_3.h)('div', { style: { ...primitives_3.MUTED, display: 'flex', flexDirection: 'column', gap: '1px', fontSize: '10px' } }, projection.notes.map((note, index) => (0, primitives_3.h)('div', { key: index }, `· ${note}`)));
|
|
2330
|
+
const footerBlock = (0, primitives_3.h)('div', { style: { ...primitives_3.MUTED, fontSize: '10px' } }, `拖拽旋转、滚轮缩放;单击选中,双击进入社区。节点与边预算 ${projection.budget.nodes}/${projection.budget.edges},超出部分会明确标注。`);
|
|
2331
|
+
const listBlock = (0, primitives_3.h)('div', { style: { display: 'flex', flexDirection: 'column', gap: '2px', overflow: 'auto', maxHeight: 'min(38vh, 360px)', paddingRight: '2px' } }, filteredNodes.length === 0
|
|
2332
|
+
? (0, primitives_3.h)('div', { style: { ...primitives_3.MUTED, fontSize: '10px' } }, '没有匹配的社区。')
|
|
2333
|
+
: filteredNodes.map((node) => (0, primitives_3.h)('button', {
|
|
2334
|
+
key: node.id,
|
|
2335
|
+
type: 'button',
|
|
2336
|
+
onClick: () => {
|
|
2337
|
+
setSelectedId(node.id);
|
|
2338
|
+
setHoverId(null);
|
|
2339
|
+
},
|
|
2340
|
+
style: {
|
|
2341
|
+
display: 'flex',
|
|
2342
|
+
alignItems: 'center',
|
|
2343
|
+
gap: '4px',
|
|
2344
|
+
minWidth: 0,
|
|
2345
|
+
padding: '3px 4px',
|
|
2346
|
+
border: 'none',
|
|
2347
|
+
borderLeft: node.id === selectedId ? '2px solid #89b4fa' : '2px solid transparent',
|
|
2348
|
+
borderRadius: '3px',
|
|
2349
|
+
background: node.id === selectedId ? primitives_3.SOFT_BG : 'transparent',
|
|
2350
|
+
color: 'inherit',
|
|
2351
|
+
textAlign: 'left',
|
|
2352
|
+
cursor: 'pointer',
|
|
2353
|
+
fontSize: '10px',
|
|
2354
|
+
},
|
|
2355
|
+
}, (0, primitives_3.h)('span', { style: { width: '7px', height: '7px', flex: '0 0 7px', borderRadius: '50%', background: node.color } }), (0, primitives_3.h)('span', { style: { flex: 1, minWidth: 0, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' } }, labelFor(node)), (0, primitives_3.h)('span', { style: { ...primitives_3.MUTED, flex: '0 0 auto' } }, String(node.symbolCount ?? 0)))));
|
|
2356
|
+
const close = () => {
|
|
2357
|
+
setExpanded(false);
|
|
2358
|
+
viewRef.current.dist = 3400;
|
|
2359
|
+
draw();
|
|
2360
|
+
};
|
|
2361
|
+
const sidebar = (0, primitives_3.h)('aside', {
|
|
2362
|
+
'aria-label': '社区列表与选中详情',
|
|
2363
|
+
style: { width: 'min(320px, 36vw)', flexShrink: 0, display: 'flex', flexDirection: 'column', gap: '6px', minWidth: 0, maxHeight: '72vh', overflow: 'auto' },
|
|
2364
|
+
}, (0, primitives_3.h)('div', { style: { display: 'flex', alignItems: 'flex-start', gap: '6px', padding: '0 2px 2px' } }, (0, primitives_3.h)('div', { style: { flex: 1, minWidth: 0 } }, (0, primitives_3.h)('div', { style: { fontSize: '15px', fontWeight: 700, lineHeight: 1.25 } }, '社区图 3D'), (0, primitives_3.h)('div', { style: { ...primitives_3.MUTED, fontSize: '10px' } }, `${projection.nodes.length} / ${projection.total} 个社区`)), (0, primitives_3.h)('button', { type: 'button', style: CONTROL, onClick: close }, '收起')), (0, primitives_3.h)('div', { style: { ...primitives_3.MUTED, fontSize: '10px' } }, `${projection.nodes.length} 社区 · ${projection.edges.length} 对关系 · ${layout.isolated} 孤立 · ${symbolTotal} 符号`), detailBlock, pathBlock, (0, primitives_3.h)('input', {
|
|
2365
|
+
type: 'search',
|
|
2366
|
+
value: query,
|
|
2367
|
+
onChange: (event) => setQuery(event.target.value),
|
|
2368
|
+
placeholder: '搜索社区 / 文件 / #id…',
|
|
2369
|
+
style: { padding: '4px 6px', border: primitives_3.HAIRLINE, borderRadius: '4px', background: 'transparent', color: 'inherit', fontSize: '10px' },
|
|
2370
|
+
}), (0, primitives_3.h)('div', { style: { ...primitives_3.MUTED, fontSize: '10px', fontWeight: 600 } }, '社区列表'), listBlock);
|
|
2371
|
+
const card = (0, primitives_3.h)('div', { style: { display: 'flex', flexDirection: 'column', gap: '6px' } }, canvasBlock, controlsRow, detailBlock, pathBlock, notesBlock, footerBlock);
|
|
2372
|
+
if (!expanded)
|
|
2373
|
+
return card;
|
|
2374
|
+
return (0, primitives_3.h)('div', {
|
|
2375
|
+
style: {
|
|
2376
|
+
position: 'fixed',
|
|
2377
|
+
inset: 0,
|
|
2378
|
+
zIndex: 1000,
|
|
2379
|
+
display: 'flex',
|
|
2380
|
+
alignItems: 'center',
|
|
2381
|
+
justifyContent: 'center',
|
|
2382
|
+
padding: '18px',
|
|
2383
|
+
background: 'rgba(0,0,0,0.55)',
|
|
2384
|
+
},
|
|
2385
|
+
onClick: close,
|
|
2386
|
+
}, (0, primitives_3.h)('div', {
|
|
2387
|
+
role: 'dialog',
|
|
2388
|
+
'aria-modal': 'true',
|
|
2389
|
+
'aria-label': '社区图 3D 放大视图',
|
|
2390
|
+
style: {
|
|
2391
|
+
width: 'min(94vw, 1040px)',
|
|
2392
|
+
maxHeight: '86vh',
|
|
2393
|
+
overflow: 'auto',
|
|
2394
|
+
padding: '12px',
|
|
2395
|
+
border: primitives_3.HAIRLINE,
|
|
2396
|
+
borderRadius: '8px',
|
|
2397
|
+
background: 'var(--dsw-alias-bg-base, #11111b)',
|
|
2398
|
+
boxShadow: '0 18px 60px rgba(0,0,0,0.45)',
|
|
2399
|
+
},
|
|
2400
|
+
onClick: (event) => event.stopPropagation(),
|
|
2401
|
+
}, (0, primitives_3.h)('div', { style: { display: 'flex', alignItems: 'flex-start', gap: '10px', minWidth: 0 } }, sidebar, (0, primitives_3.h)('div', { style: { flex: 1, minWidth: 0, display: 'flex', flexDirection: 'column', gap: '6px' } }, (0, primitives_3.h)('div', { style: { position: 'relative', minWidth: 0 } }, canvasBlock, (0, primitives_3.h)('div', { style: { position: 'absolute', top: '6px', left: '6px', right: '6px', display: 'flex', justifyContent: 'flex-end', pointerEvents: 'none' } }, (0, primitives_3.h)('div', { style: { pointerEvents: 'auto', maxWidth: '100%' } }, controlsRow))), notesBlock, footerBlock))));
|
|
2402
|
+
}
|
|
2403
|
+
});
|
|
2404
|
+
define("sections/graph", ["require", "exports", "react", "primitives", "sections/graph3d"], function (require, exports, React, primitives_4, graph3d_1) {
|
|
1726
2405
|
"use strict";
|
|
1727
2406
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
1728
2407
|
exports.communityColors = communityColors;
|
|
@@ -1903,7 +2582,7 @@ define("sections/graph", ["require", "exports", "react", "primitives"], function
|
|
|
1903
2582
|
};
|
|
1904
2583
|
}
|
|
1905
2584
|
function GraphSection({ state }) {
|
|
1906
|
-
const { projection, loading, error, routeState, scope, onOpenCommunity, onBack, onReload, onOpenSymbol } = state;
|
|
2585
|
+
const { projection, loading, error, routeState, scope, onOpenCommunity, onOpenSymbols, onBack, onReload, onOpenSymbol } = state;
|
|
1907
2586
|
const canvasRef = React.useRef(null);
|
|
1908
2587
|
const transform = React.useRef({ x: 0, y: 0, k: 1 });
|
|
1909
2588
|
const drag = React.useRef({
|
|
@@ -2137,103 +2816,113 @@ define("sections/graph", ["require", "exports", "react", "primitives"], function
|
|
|
2137
2816
|
// screen-reader-reachable equivalent of the picture, and the only part of this card
|
|
2138
2817
|
// that a non-canvas test can assert on.
|
|
2139
2818
|
const listed = nodes.slice(0, 8);
|
|
2140
|
-
return (0,
|
|
2141
|
-
? (0,
|
|
2142
|
-
: (0,
|
|
2819
|
+
return (0, primitives_4.h)('div', { style: { display: 'flex', flexDirection: 'column', gap: '8px', paddingTop: '4px' } }, (0, primitives_4.h)('div', { style: { display: 'flex', alignItems: 'center', gap: '6px', flexWrap: 'wrap' } }, scope === null
|
|
2820
|
+
? (0, primitives_4.h)('span', { style: { fontWeight: 600 } }, projection?.view === 'symbols' ? '符号全图' : '社区总览')
|
|
2821
|
+
: (0, primitives_4.h)('span', { style: { fontWeight: 600 } }, `社区 ${scope}`), projection === null
|
|
2143
2822
|
? null
|
|
2144
|
-
: (0,
|
|
2823
|
+
: (0, primitives_4.h)('span', { style: primitives_4.MUTED }, projection.view === 'communities'
|
|
2145
2824
|
? `${projection.nodes.length} / ${projection.total} 个社区`
|
|
2146
|
-
: `${projection.nodes.length} / ${projection.total} 个符号`), projection?.truncated === true ? (0,
|
|
2825
|
+
: `${projection.nodes.length} / ${projection.total} 个符号`), projection?.truncated === true ? (0, primitives_4.h)(primitives_4.Tag, { tone: 'outline' }, '已截断') : null, (0, primitives_4.h)('span', { style: { flex: 1 } }), projection === null
|
|
2147
2826
|
? null
|
|
2148
|
-
: (0,
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
|
|
2153
|
-
// and stretching the bitmap to fill it distorted every circle instead.
|
|
2154
|
-
height: 'clamp(260px, 32vw, 420px)',
|
|
2155
|
-
border: primitives_3.HAIRLINE,
|
|
2156
|
-
borderRadius: '6px',
|
|
2157
|
-
background: primitives_3.SOFT_BG,
|
|
2158
|
-
overflow: 'hidden',
|
|
2159
|
-
},
|
|
2160
|
-
}, (0, primitives_3.h)('canvas', {
|
|
2161
|
-
ref: canvasRef,
|
|
2162
|
-
style: { width: '100%', height: '100%', display: 'block', touchAction: 'none', cursor: 'grab' },
|
|
2163
|
-
onPointerDown,
|
|
2164
|
-
onPointerMove,
|
|
2165
|
-
onPointerUp,
|
|
2166
|
-
onPointerLeave: () => {
|
|
2167
|
-
drag.current.active = false;
|
|
2168
|
-
hovered.current = null;
|
|
2169
|
-
setHoverLabel(null);
|
|
2170
|
-
draw();
|
|
2171
|
-
},
|
|
2172
|
-
}),
|
|
2173
|
-
// An empty canvas explains itself, centred, rather than leaving a blank frame that
|
|
2174
|
-
// reads as a hang.
|
|
2175
|
-
emptyMessage === null
|
|
2827
|
+
: (0, primitives_4.h)(primitives_4.Button, {
|
|
2828
|
+
variant: 'ghost',
|
|
2829
|
+
onClick: projection.view === 'symbols' ? onBack : onOpenSymbols,
|
|
2830
|
+
disabled: loading,
|
|
2831
|
+
}, projection.view === 'symbols' ? '社区视图' : '符号视图'), scope === null ? null : (0, primitives_4.h)(primitives_4.Button, { variant: 'ghost', onClick: onBack }, '← 全部社区'), (0, primitives_4.h)(primitives_4.Button, { variant: 'ghost', onClick: onReload, disabled: loading }, loading ? '读取中…' : '重读图')), error === null
|
|
2176
2832
|
? null
|
|
2177
|
-
: (0,
|
|
2833
|
+
: (0, primitives_4.h)('div', { style: { ...primitives_4.MUTED, color: 'var(--dsw-alias-state-error-primary, inherit)' } }, error), projection?.view === 'communities'
|
|
2834
|
+
? (0, primitives_4.h)(graph3d_1.Graph3DView, { projection, onOpenCommunity })
|
|
2835
|
+
: (0, primitives_4.h)(React.Fragment, null, (0, primitives_4.h)('div', {
|
|
2178
2836
|
style: {
|
|
2179
|
-
position: '
|
|
2180
|
-
|
|
2181
|
-
|
|
2182
|
-
|
|
2183
|
-
|
|
2184
|
-
|
|
2185
|
-
|
|
2186
|
-
|
|
2187
|
-
|
|
2837
|
+
position: 'relative',
|
|
2838
|
+
width: '100%',
|
|
2839
|
+
// Responsive height: a fixed 300px box made a wide card letterbox the drawing,
|
|
2840
|
+
// and stretching the bitmap to fill it distorted every circle instead.
|
|
2841
|
+
height: 'clamp(260px, 32vw, 420px)',
|
|
2842
|
+
border: primitives_4.HAIRLINE,
|
|
2843
|
+
borderRadius: '6px',
|
|
2844
|
+
background: primitives_4.SOFT_BG,
|
|
2845
|
+
overflow: 'hidden',
|
|
2188
2846
|
},
|
|
2189
|
-
},
|
|
2190
|
-
|
|
2191
|
-
|
|
2192
|
-
|
|
2193
|
-
|
|
2194
|
-
|
|
2195
|
-
|
|
2196
|
-
|
|
2197
|
-
|
|
2198
|
-
|
|
2199
|
-
|
|
2200
|
-
borderRadius: '4px',
|
|
2201
|
-
background: 'rgba(15, 23, 42, 0.75)',
|
|
2202
|
-
fontSize: '11px',
|
|
2203
|
-
pointerEvents: 'none',
|
|
2847
|
+
}, (0, primitives_4.h)('canvas', {
|
|
2848
|
+
ref: canvasRef,
|
|
2849
|
+
style: { width: '100%', height: '100%', display: 'block', touchAction: 'none', cursor: 'grab' },
|
|
2850
|
+
onPointerDown,
|
|
2851
|
+
onPointerMove,
|
|
2852
|
+
onPointerUp,
|
|
2853
|
+
onPointerLeave: () => {
|
|
2854
|
+
drag.current.active = false;
|
|
2855
|
+
hovered.current = null;
|
|
2856
|
+
setHoverLabel(null);
|
|
2857
|
+
draw();
|
|
2204
2858
|
},
|
|
2205
|
-
}
|
|
2206
|
-
|
|
2207
|
-
|
|
2208
|
-
|
|
2209
|
-
|
|
2210
|
-
|
|
2211
|
-
|
|
2212
|
-
|
|
2213
|
-
|
|
2214
|
-
|
|
2215
|
-
|
|
2216
|
-
|
|
2217
|
-
|
|
2218
|
-
|
|
2219
|
-
|
|
2220
|
-
|
|
2221
|
-
|
|
2222
|
-
|
|
2223
|
-
|
|
2224
|
-
|
|
2225
|
-
|
|
2226
|
-
|
|
2227
|
-
:
|
|
2228
|
-
|
|
2229
|
-
|
|
2230
|
-
|
|
2231
|
-
|
|
2232
|
-
|
|
2233
|
-
|
|
2859
|
+
}),
|
|
2860
|
+
// An empty canvas explains itself, centred, rather than leaving a blank frame that
|
|
2861
|
+
// reads as a hang.
|
|
2862
|
+
emptyMessage === null
|
|
2863
|
+
? null
|
|
2864
|
+
: (0, primitives_4.h)('div', {
|
|
2865
|
+
style: {
|
|
2866
|
+
position: 'absolute',
|
|
2867
|
+
inset: 0,
|
|
2868
|
+
display: 'flex',
|
|
2869
|
+
alignItems: 'center',
|
|
2870
|
+
justifyContent: 'center',
|
|
2871
|
+
padding: '0 24px',
|
|
2872
|
+
textAlign: 'center',
|
|
2873
|
+
...primitives_4.MUTED,
|
|
2874
|
+
pointerEvents: 'none',
|
|
2875
|
+
},
|
|
2876
|
+
}, emptyMessage),
|
|
2877
|
+
// A hover caption rather than redrawing a tooltip inside the canvas: it is
|
|
2878
|
+
// readable at any zoom, and it stays honest about the node's file.
|
|
2879
|
+
hoverLabel === null
|
|
2880
|
+
? null
|
|
2881
|
+
: (0, primitives_4.h)('div', {
|
|
2882
|
+
style: {
|
|
2883
|
+
position: 'absolute',
|
|
2884
|
+
left: '8px',
|
|
2885
|
+
bottom: '6px',
|
|
2886
|
+
padding: '2px 6px',
|
|
2887
|
+
borderRadius: '4px',
|
|
2888
|
+
background: 'rgba(15, 23, 42, 0.75)',
|
|
2889
|
+
fontSize: '11px',
|
|
2890
|
+
pointerEvents: 'none',
|
|
2891
|
+
},
|
|
2892
|
+
}, hoverLabel)), projection === null || nodes.length === 0
|
|
2893
|
+
? null
|
|
2894
|
+
: (0, primitives_4.h)('div', { style: { display: 'flex', flexWrap: 'wrap', gap: '4px' } }, listed.map((node) => (0, primitives_4.h)(primitives_4.Button, {
|
|
2895
|
+
key: node.id,
|
|
2896
|
+
variant: 'ghost',
|
|
2897
|
+
onClick: () => (node.kind === 'Community' ? onOpenCommunity(node.id) : onOpenSymbol(node)),
|
|
2898
|
+
}, node.kind === 'Community'
|
|
2899
|
+
? [
|
|
2900
|
+
(0, primitives_4.h)('span', {
|
|
2901
|
+
key: 'dot',
|
|
2902
|
+
style: {
|
|
2903
|
+
display: 'inline-block',
|
|
2904
|
+
width: '8px',
|
|
2905
|
+
height: '8px',
|
|
2906
|
+
borderRadius: '50%',
|
|
2907
|
+
marginRight: '5px',
|
|
2908
|
+
verticalAlign: 'middle',
|
|
2909
|
+
background: colorsByCommunity.get(node.id) ?? COMMUNITY_COLOR,
|
|
2910
|
+
},
|
|
2911
|
+
}),
|
|
2912
|
+
`${node.name} · ${node.symbolCount ?? 0}`,
|
|
2913
|
+
]
|
|
2914
|
+
: node.name)), nodes.length > listed.length
|
|
2915
|
+
? (0, primitives_4.h)('span', { style: { ...primitives_4.MUTED, alignSelf: 'center' } }, `…还有 ${nodes.length - listed.length} 个`)
|
|
2916
|
+
: null), projection !== null && Array.isArray(projection.notes) && projection.notes.length > 0
|
|
2917
|
+
? (0, primitives_4.h)('div', { style: { ...primitives_4.MUTED, display: 'flex', flexDirection: 'column', gap: '2px' } }, projection.notes.map((note, index) => (0, primitives_4.h)('div', { key: index }, `· ${note}`)))
|
|
2918
|
+
: null, (0, primitives_4.h)('div', { style: primitives_4.MUTED }, scope === null
|
|
2919
|
+
? projection?.view === 'symbols'
|
|
2920
|
+
? '点一个符号会在文件查看器中打开它;这是全仓库符号的截断视图,社区视图更适合看整体结构。'
|
|
2921
|
+
: '点一个社区看它的成员符号;滚轮缩放、拖动平移。节点数与边数由宿主限制,超出部分会明确标注。'
|
|
2922
|
+
: '点一个符号会在文件查看器中打开它并定位到行;滚轮缩放、拖动平移。')));
|
|
2234
2923
|
}
|
|
2235
2924
|
});
|
|
2236
|
-
define("sections/capabilities", ["require", "exports", "format", "primitives"], function (require, exports, format_4,
|
|
2925
|
+
define("sections/capabilities", ["require", "exports", "format", "primitives"], function (require, exports, format_4, primitives_5) {
|
|
2237
2926
|
"use strict";
|
|
2238
2927
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
2239
2928
|
exports.CapabilityStrip = CapabilityStrip;
|
|
@@ -2245,9 +2934,9 @@ define("sections/capabilities", ["require", "exports", "format", "primitives"],
|
|
|
2245
2934
|
*/
|
|
2246
2935
|
function CapabilityStrip({ capabilities }) {
|
|
2247
2936
|
const rows = (0, format_4.capabilityRows)(capabilities);
|
|
2248
|
-
return (0,
|
|
2249
|
-
? (0,
|
|
2250
|
-
: (0,
|
|
2937
|
+
return (0, primitives_5.h)('div', { style: { display: 'flex', flexDirection: 'column', gap: '3px' } }, rows.map((row) => (0, primitives_5.h)('div', { key: row.key, style: { display: 'flex', alignItems: 'center', gap: '6px' } }, (0, primitives_5.h)(primitives_5.StateDot, { state: row.ok ? 'done' : 'idle' }), (0, primitives_5.h)('span', { style: { minWidth: '68px' } }, row.label), row.ok
|
|
2938
|
+
? (0, primitives_5.h)('span', { style: primitives_5.MUTED }, row.provider)
|
|
2939
|
+
: (0, primitives_5.h)(primitives_5.Tag, { tone: 'outline' }, row.note))));
|
|
2251
2940
|
}
|
|
2252
2941
|
/**
|
|
2253
2942
|
* Resolver confidence and analysis provenance.
|
|
@@ -2256,26 +2945,32 @@ define("sections/capabilities", ["require", "exports", "format", "primitives"],
|
|
|
2256
2945
|
*
|
|
2257
2946
|
* @param {{quality: any, provenance: any}} props
|
|
2258
2947
|
*/
|
|
2259
|
-
function ProvenanceBlock({ quality, provenance }) {
|
|
2948
|
+
function ProvenanceBlock({ quality, provenance, compatibility }) {
|
|
2260
2949
|
const summary = (0, format_4.graphQualitySummary)(quality);
|
|
2261
|
-
return (0,
|
|
2262
|
-
? (0,
|
|
2263
|
-
: (0,
|
|
2950
|
+
return (0, primitives_5.h)('div', { style: { display: 'flex', flexDirection: 'column', gap: '8px', paddingTop: '4px', flex: '1 1 auto' } }, summary === null
|
|
2951
|
+
? (0, primitives_5.h)('div', { style: primitives_5.MUTED }, '该索引未记录未解析接收者统计(旧版本写入)')
|
|
2952
|
+
: (0, primitives_5.h)('div', null, (0, primitives_5.h)('div', null, `未解析接收者 ${(0, format_4.compactNumber)(summary.total)} 处——存在这些位置时,impact / context 的结果是下界而非精确值`), (0, primitives_5.h)('div', { style: { ...primitives_5.MUTED, marginTop: '3px' } }, summary.names
|
|
2264
2953
|
.slice(0, 6)
|
|
2265
2954
|
.map((entry) => `${entry.name} ${entry.count}`)
|
|
2266
2955
|
.join(' · '))), provenance === null
|
|
2267
2956
|
? null
|
|
2268
|
-
: (0,
|
|
2957
|
+
: (0, primitives_5.h)('div',
|
|
2269
2958
|
// Pushed to the bottom: this card shares a row with the caveats card and
|
|
2270
2959
|
// stretches to its height, so anchoring the metrics at the foot turns the
|
|
2271
2960
|
// leftover space into a gap between two groups rather than a void below.
|
|
2272
|
-
{ style: { marginTop: 'auto', paddingTop: '8px' } }, (0,
|
|
2961
|
+
{ style: { marginTop: 'auto', paddingTop: '8px' } }, (0, primitives_5.h)(primitives_5.MetricGrid, {
|
|
2273
2962
|
items: [
|
|
2274
2963
|
{ label: 'schema', value: provenance.schemaFingerprint ?? '—' },
|
|
2275
2964
|
{ label: '内容保留', value: provenance.contentRetention ?? '—' },
|
|
2276
2965
|
{ label: 'FTS 配置', value: provenance.ftsProfile ?? '—' },
|
|
2277
2966
|
{ label: '跨语言属性', value: (0, format_4.compactNumber)(provenance.crossLanguageProperties ?? 0) },
|
|
2278
2967
|
{ label: '分析器', value: provenance.runnerCliVersion ?? '—' },
|
|
2968
|
+
{
|
|
2969
|
+
label: '索引/CLI',
|
|
2970
|
+
value: compatibility === undefined || compatibility === null
|
|
2971
|
+
? '—'
|
|
2972
|
+
: `${compatibility.indexedBy ?? '缺失'} / ${compatibility.installed ?? '缺失'}`,
|
|
2973
|
+
},
|
|
2279
2974
|
],
|
|
2280
2975
|
})));
|
|
2281
2976
|
}
|
|
@@ -2285,7 +2980,7 @@ define("sections/capabilities", ["require", "exports", "format", "primitives"],
|
|
|
2285
2980
|
return summary === null ? '无未解析接收者记录' : `未解析接收者 ${(0, format_4.compactNumber)(summary.total)} 处`;
|
|
2286
2981
|
}
|
|
2287
2982
|
});
|
|
2288
|
-
define("sections/languages", ["require", "exports", "react", "format", "primitives"], function (require, exports, React, format_5,
|
|
2983
|
+
define("sections/languages", ["require", "exports", "react", "format", "primitives"], function (require, exports, React, format_5, primitives_6) {
|
|
2289
2984
|
"use strict";
|
|
2290
2985
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
2291
2986
|
exports.MemoizedLanguageSection = void 0;
|
|
@@ -2297,11 +2992,11 @@ define("sections/languages", ["require", "exports", "react", "format", "primitiv
|
|
|
2297
2992
|
return null;
|
|
2298
2993
|
const visible = segments.filter((s) => s.percent >= 0.05);
|
|
2299
2994
|
const hidden = segments.length - visible.length;
|
|
2300
|
-
return (0,
|
|
2995
|
+
return (0, primitives_6.h)('div', { style: { display: 'flex', flexDirection: 'column', gap: '8px' } }, (0, primitives_6.h)('div', { style: primitives_6.styles.bar, role: 'img', 'aria-label': '语言分布' }, segments.map((segment, index) => (0, primitives_6.h)(primitives_6.Tooltip, { key: `${segment.key}-${index}`, label: `${segment.label} ${(0, format_5.fixed)(segment.percent, 1)}%`, side: 'bottom' }, (0, primitives_6.h)('div', {
|
|
2301
2996
|
// A floor keeps a 0.01% language visible; the flex ratio keeps the
|
|
2302
2997
|
// major segments proportional.
|
|
2303
2998
|
style: { width: `${Math.max(0.4, segment.percent)}%`, background: segment.color, flexShrink: 0 },
|
|
2304
|
-
})))), (0,
|
|
2999
|
+
})))), (0, primitives_6.h)('div', { style: { display: 'flex', flexWrap: 'wrap', gap: '4px 12px' } }, visible.map((segment) => (0, primitives_6.h)('span', { key: segment.key, style: { display: 'inline-flex', gap: '5px', alignItems: 'center' } }, (0, primitives_6.h)('span', {
|
|
2305
3000
|
style: {
|
|
2306
3001
|
width: '8px',
|
|
2307
3002
|
height: '8px',
|
|
@@ -2310,7 +3005,7 @@ define("sections/languages", ["require", "exports", "react", "format", "primitiv
|
|
|
2310
3005
|
display: 'inline-block',
|
|
2311
3006
|
flexShrink: 0,
|
|
2312
3007
|
},
|
|
2313
|
-
}), (0,
|
|
3008
|
+
}), (0, primitives_6.h)('span', null, segment.label), (0, primitives_6.h)('span', { style: primitives_6.MUTED }, `${(0, format_5.fixed)(segment.percent, 1)}%`))), hidden > 0 ? (0, primitives_6.h)('span', { style: primitives_6.MUTED }, `…另有 ${hidden} 种`) : null));
|
|
2314
3009
|
}
|
|
2315
3010
|
/**
|
|
2316
3011
|
* Language distribution, with the metric switchable.
|
|
@@ -2326,9 +3021,9 @@ define("sections/languages", ["require", "exports", "react", "format", "primitiv
|
|
|
2326
3021
|
function LanguageSection({ languages }) {
|
|
2327
3022
|
const [metric, setMetric] = React.useState('bytes');
|
|
2328
3023
|
const unit = metric === 'files' ? '文件' : '字节';
|
|
2329
|
-
return (0,
|
|
3024
|
+
return (0, primitives_6.h)(primitives_6.Section, {
|
|
2330
3025
|
title: `语言分布 · ${languages.rows.length} 种`,
|
|
2331
|
-
meta: (0,
|
|
3026
|
+
meta: (0, primitives_6.h)(primitives_6.SegmentedControl, {
|
|
2332
3027
|
id: 'gitnexus-language-metric',
|
|
2333
3028
|
label: '语言分布统计口径',
|
|
2334
3029
|
value: metric,
|
|
@@ -2343,12 +3038,12 @@ define("sections/languages", ["require", "exports", "react", "format", "primitiv
|
|
|
2343
3038
|
}),
|
|
2344
3039
|
// The card stretches to the row height beside the overview card; the footnote
|
|
2345
3040
|
// below uses `marginTop: auto` to sit at the foot of that height.
|
|
2346
|
-
}, (0,
|
|
3041
|
+
}, (0, primitives_6.h)(LanguageBar, {
|
|
2347
3042
|
rows: languages.rows,
|
|
2348
3043
|
totalBytes: languages.totalBytes,
|
|
2349
3044
|
totalFiles: languages.totalFiles,
|
|
2350
3045
|
metric,
|
|
2351
|
-
}), (0,
|
|
3046
|
+
}), (0, primitives_6.h)('div', { style: { marginTop: 'auto', paddingTop: '8px', fontSize: '10px', ...primitives_6.MUTED } }, languages.detector === 'builtin'
|
|
2352
3047
|
? `按${unit}计的占比;GitNexus 不可用,语言由内置扩展名映射识别(仅作参考)`
|
|
2353
3048
|
: `按${unit}计的占比;GitNexus 只解析其中一部分语言,其余仅作为文件节点存在`));
|
|
2354
3049
|
}
|
|
@@ -2358,7 +3053,7 @@ define("sections/languages", ["require", "exports", "react", "format", "primitiv
|
|
|
2358
3053
|
*/
|
|
2359
3054
|
exports.MemoizedLanguageSection = React.memo(LanguageSection);
|
|
2360
3055
|
});
|
|
2361
|
-
define("sections/large-files", ["require", "exports", "format", "primitives"], function (require, exports, format_6,
|
|
3056
|
+
define("sections/large-files", ["require", "exports", "format", "primitives"], function (require, exports, format_6, primitives_7) {
|
|
2362
3057
|
"use strict";
|
|
2363
3058
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
2364
3059
|
exports.LargeFileList = LargeFileList;
|
|
@@ -2372,10 +3067,10 @@ define("sections/large-files", ["require", "exports", "format", "primitives"], f
|
|
|
2372
3067
|
* @param {{files: any[]}} props
|
|
2373
3068
|
*/
|
|
2374
3069
|
function LargeFileList({ files }) {
|
|
2375
|
-
return (0,
|
|
3070
|
+
return (0, primitives_7.h)('div', { style: { display: 'flex', flexDirection: 'column' } }, files.map((file) => (0, primitives_7.h)('div', { key: file.path, style: primitives_7.styles.fileRow }, (0, primitives_7.h)(primitives_7.FileTypeIcon, { path: file.path, size: 16 }), (0, primitives_7.h)('span', { style: { minWidth: 0, flex: '1 1 auto' } }, (0, primitives_7.h)(primitives_7.PathLabel, { path: file.path })), (0, primitives_7.h)('span', { style: primitives_7.styles.fileMeta }, (0, primitives_7.h)('span', { style: { ...primitives_7.MUTED, whiteSpace: 'nowrap' } }, file.sizeText), (0, primitives_7.h)(primitives_7.FileFlags, { file }), (0, primitives_7.h)('span', { style: { ...primitives_7.MUTED, whiteSpace: 'nowrap' } }, `${(0, format_6.fixed)((file.shareOfLanguage ?? 0) * 100, 0)}% ${file.language?.label ?? ''}`)))));
|
|
2376
3071
|
}
|
|
2377
3072
|
});
|
|
2378
|
-
define("sections/maintenance", ["require", "exports", "react", "@deepseek-ai/dsh-client-ui-primitives", "clock", "format", "primitives"], function (require, exports, React, primitives, clock_1, format_7,
|
|
3073
|
+
define("sections/maintenance", ["require", "exports", "react", "@deepseek-ai/dsh-client-ui-primitives", "clock", "format", "primitives"], function (require, exports, React, primitives, clock_1, format_7, primitives_8) {
|
|
2379
3074
|
"use strict";
|
|
2380
3075
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
2381
3076
|
exports.MaintenanceSection = MaintenanceSection;
|
|
@@ -2476,11 +3171,11 @@ define("sections/maintenance", ["require", "exports", "react", "@deepseek-ai/dsh
|
|
|
2476
3171
|
setCopied(null);
|
|
2477
3172
|
}
|
|
2478
3173
|
};
|
|
2479
|
-
return (0,
|
|
3174
|
+
return (0, primitives_8.h)('div', { style: { display: 'flex', flexDirection: 'column', gap: '10px', paddingTop: '4px' } },
|
|
2480
3175
|
// The old line said "复制后到会话执行". That stopped being the whole story once the
|
|
2481
3176
|
// plugin could start the run itself, so the sentence says which parts it runs and
|
|
2482
3177
|
// which it only hands over.
|
|
2483
|
-
(0,
|
|
3178
|
+
(0, primitives_8.h)('div', { style: primitives_8.MUTED }, hostCurrent
|
|
2484
3179
|
? '下面列出的命令都可由本插件执行(走 DSH 的 shell 执行器,沙箱与其它命令一致,本插件不申请扩权)。' +
|
|
2485
3180
|
'全量重建只应在增量刷新卡死或结果反常时使用。'
|
|
2486
3181
|
: '当前宿主是旧版本(不包含 FTS 自适应与注册表重定向修复);重启 DSH 后再执行维护。命令仍可复制。'),
|
|
@@ -2489,23 +3184,23 @@ define("sections/maintenance", ["require", "exports", "react", "@deepseek-ai/dsh
|
|
|
2489
3184
|
// separate ones.
|
|
2490
3185
|
activeRun === null
|
|
2491
3186
|
? null
|
|
2492
|
-
: (0,
|
|
3187
|
+
: (0, primitives_8.h)('div', {
|
|
2493
3188
|
style: {
|
|
2494
3189
|
display: 'flex',
|
|
2495
3190
|
flexDirection: 'column',
|
|
2496
3191
|
gap: '4px',
|
|
2497
3192
|
padding: '6px 8px',
|
|
2498
|
-
border:
|
|
3193
|
+
border: primitives_8.HAIRLINE,
|
|
2499
3194
|
borderRadius: '6px',
|
|
2500
|
-
background:
|
|
3195
|
+
background: primitives_8.SOFT_BG,
|
|
2501
3196
|
},
|
|
2502
|
-
}, (0,
|
|
2503
|
-
? (0,
|
|
3197
|
+
}, (0, primitives_8.h)('div', { style: { display: 'flex', alignItems: 'center', gap: '6px' } }, (0, primitives_8.h)(primitives_8.StateDot, { state: (0, format_7.runStateTone)(activeRun.state) }), (0, primitives_8.h)('span', { style: { fontWeight: 600 } }, (0, format_7.runStateLabel)(activeRun.state)), activeRun.force === true ? (0, primitives_8.h)(primitives_8.Tag, { tone: 'outline' }, '全量') : null, activeRun.repairFts === true ? (0, primitives_8.h)(primitives_8.Tag, { tone: 'outline' }, '修复 FTS') : null, (0, primitives_8.h)('span', { style: primitives_8.MUTED }, activeRun.state === 'running' ? `已运行 ${elapsed}s` : `${elapsed}s`), (0, primitives_8.h)('span', { style: { flex: 1 } }), running
|
|
3198
|
+
? (0, primitives_8.h)(primitives_8.Button, { variant: 'ghost', onClick: onCancelRun }, '取消')
|
|
2504
3199
|
// Wrapped, not passed directly: once `onStartRun` takes a mode argument,
|
|
2505
3200
|
// React's click event would arrive as that argument and every "run again"
|
|
2506
3201
|
// would become a forced rebuild. Repeating the last mode is also the
|
|
2507
3202
|
// honest reading of "run again" after a repair or a full rebuild.
|
|
2508
|
-
: (0,
|
|
3203
|
+
: (0, primitives_8.h)(primitives_8.Button, {
|
|
2509
3204
|
variant: 'ghost',
|
|
2510
3205
|
disabled: !hostCurrent,
|
|
2511
3206
|
onClick: () => onStartRun(activeRun.repairFts === true
|
|
@@ -2513,22 +3208,22 @@ define("sections/maintenance", ["require", "exports", "react", "@deepseek-ai/dsh
|
|
|
2513
3208
|
: activeRun.force === true
|
|
2514
3209
|
? 'force'
|
|
2515
3210
|
: 'refresh'),
|
|
2516
|
-
}, '再次执行')), activeRun.message === null ? null : (0,
|
|
3211
|
+
}, '再次执行')), activeRun.message === null ? null : (0, primitives_8.h)('div', { style: primitives_8.MUTED }, activeRun.message),
|
|
2517
3212
|
// The output is the evidence, but a failure's first screen was thirty lines of
|
|
2518
3213
|
// JavaScript stack with the one useful sentence buried in it. The verdict above
|
|
2519
3214
|
// is the summary; the raw text stays one click away.
|
|
2520
3215
|
activeRun.output === ''
|
|
2521
3216
|
? null
|
|
2522
|
-
: (0,
|
|
3217
|
+
: (0, primitives_8.h)('details', { style: { marginTop: '2px' } }, (0, primitives_8.h)('summary', {
|
|
2523
3218
|
style: {
|
|
2524
|
-
...
|
|
3219
|
+
...primitives_8.MUTED,
|
|
2525
3220
|
cursor: 'pointer',
|
|
2526
3221
|
fontSize: '12px',
|
|
2527
3222
|
userSelect: 'none',
|
|
2528
3223
|
},
|
|
2529
|
-
}, '查看原始输出'), (0,
|
|
3224
|
+
}, '查看原始输出'), (0, primitives_8.h)('pre', {
|
|
2530
3225
|
style: {
|
|
2531
|
-
...
|
|
3226
|
+
...primitives_8.styles.mono,
|
|
2532
3227
|
margin: '4px 0 0',
|
|
2533
3228
|
whiteSpace: 'pre-wrap',
|
|
2534
3229
|
maxHeight: '12em',
|
|
@@ -2536,7 +3231,7 @@ define("sections/maintenance", ["require", "exports", "react", "@deepseek-ai/dsh
|
|
|
2536
3231
|
},
|
|
2537
3232
|
}, activeRun.output))), runError === null
|
|
2538
3233
|
? null
|
|
2539
|
-
: (0,
|
|
3234
|
+
: (0, primitives_8.h)('div', { style: { ...primitives_8.MUTED, color: 'var(--dsw-alias-state-error-primary, inherit)' } }, runError),
|
|
2540
3235
|
// The Tier 0 verdict sits directly above the buttons it governs. Showing "dirty"
|
|
2541
3236
|
// without the reason would be the same opacity the gate exists to remove, and
|
|
2542
3237
|
// showing "clean" without an escape hatch would take a decision away from the
|
|
@@ -2545,12 +3240,12 @@ define("sections/maintenance", ["require", "exports", "react", "@deepseek-ai/dsh
|
|
|
2545
3240
|
// deployment state worth naming — otherwise the gate is simply absent and the
|
|
2546
3241
|
// operator has no way to tell that from "nothing changed".
|
|
2547
3242
|
deltaError !== null && deltaError !== undefined
|
|
2548
|
-
? (0,
|
|
3243
|
+
? (0, primitives_8.h)('div', { style: primitives_8.MUTED }, `差量检查失败:${deltaError} —— 刷新仍可直接执行。`)
|
|
2549
3244
|
: deltaRoute === 'missing'
|
|
2550
|
-
? (0,
|
|
3245
|
+
? (0, primitives_8.h)('div', { style: primitives_8.MUTED }, '当前宿主未挂载差量路由:重启 DSH 后会显示"是否需要刷新"的依据;刷新按钮仍可使用。')
|
|
2551
3246
|
: gateKnown
|
|
2552
3247
|
? gateClean
|
|
2553
|
-
? (0,
|
|
3248
|
+
? (0, primitives_8.h)('div', { style: { display: 'flex', flexDirection: 'column', gap: '3px' } }, (0, primitives_8.h)('div', { style: { display: 'flex', alignItems: 'center', gap: '6px', flexWrap: 'wrap' } }, (0, primitives_8.h)(primitives_8.StateDot, { state: 'done' }), (0, primitives_8.h)('span', { style: { fontWeight: 600 } }, '无需刷新'), (0, primitives_8.h)(primitives_8.Tag, { tone: 'solid' }, '索引已是最新'), (0, primitives_8.h)('span', { style: primitives_8.MUTED }, gate?.notes[0] ?? ''), (0, primitives_8.h)('span', { style: { flex: 1 } }), (0, primitives_8.h)(primitives_8.Button, {
|
|
2554
3249
|
variant: 'ghost',
|
|
2555
3250
|
disabled: running || !hostCurrent,
|
|
2556
3251
|
// Not a bypass of the gate: it is the documented way to refresh when
|
|
@@ -2562,24 +3257,28 @@ define("sections/maintenance", ["require", "exports", "react", "@deepseek-ai/dsh
|
|
|
2562
3257
|
// policy skips. Saying "no refresh" without that explanation would read as
|
|
2563
3258
|
// "git says nothing changed", which is not what was measured.
|
|
2564
3259
|
excludedCount > 0
|
|
2565
|
-
? (0,
|
|
3260
|
+
? (0, primitives_8.h)('div', { style: primitives_8.MUTED }, `${excludedCount} 个改动属索引排除范围(超大文件等),刷新也不会收录,因此不计入刷新依据。`)
|
|
2566
3261
|
: null)
|
|
2567
|
-
: (0,
|
|
3262
|
+
: (0, primitives_8.h)('div', {
|
|
2568
3263
|
style: {
|
|
2569
3264
|
display: 'flex',
|
|
2570
3265
|
flexDirection: 'column',
|
|
2571
3266
|
gap: '4px',
|
|
2572
3267
|
padding: '6px 8px',
|
|
2573
|
-
border:
|
|
3268
|
+
border: primitives_8.HAIRLINE,
|
|
2574
3269
|
borderRadius: '6px',
|
|
2575
|
-
background:
|
|
3270
|
+
background: primitives_8.SOFT_BG,
|
|
2576
3271
|
},
|
|
2577
|
-
}, (0,
|
|
3272
|
+
}, (0, primitives_8.h)('div', { style: { display: 'flex', alignItems: 'center', gap: '6px', flexWrap: 'wrap' } }, (0, primitives_8.h)(primitives_8.StateDot, { state: 'warning' }), (0, primitives_8.h)('span', { style: { fontWeight: 600 } }, '索引内容已落后'), (0, primitives_8.h)('span', { style: primitives_8.MUTED }, `${gate?.counts.modified ?? 0} 改 / ${gate?.counts.added ?? 0} 增 / ` +
|
|
2578
3273
|
`${gate?.counts.deleted ?? 0} 删` +
|
|
2579
3274
|
(excludedCount > 0 ? ` · ${excludedCount} 个被排除` : '') +
|
|
2580
|
-
` · 检查用时 ${gate?.durationMs ?? 0}ms`), (0,
|
|
3275
|
+
` · 检查用时 ${gate?.durationMs ?? 0}ms`), (0, primitives_8.h)('span', { style: { flex: 1 } }), gate?.advice !== undefined && gate?.advice !== null
|
|
3276
|
+
? (0, primitives_8.h)(primitives_8.Tag, { tone: 'outline' }, `建议:${gate.advice.label}`)
|
|
3277
|
+
: null, gate?.truncated === true ? (0, primitives_8.h)(primitives_8.Tag, { tone: 'outline' }, '候选已截断') : null), gate?.advice !== undefined && gate?.advice !== null
|
|
3278
|
+
? (0, primitives_8.h)('div', { style: { ...primitives_8.MUTED, fontSize: '11px' } }, gate.advice.reason)
|
|
3279
|
+
: null, (0, primitives_8.h)('div', {
|
|
2581
3280
|
style: {
|
|
2582
|
-
...
|
|
3281
|
+
...primitives_8.styles.mono,
|
|
2583
3282
|
maxHeight: '7em',
|
|
2584
3283
|
overflow: 'auto',
|
|
2585
3284
|
whiteSpace: 'pre-wrap',
|
|
@@ -2589,15 +3288,19 @@ define("sections/maintenance", ["require", "exports", "react", "@deepseek-ai/dsh
|
|
|
2589
3288
|
.slice(0, 8)
|
|
2590
3289
|
.map((entry) => `${entry.kind === 'modified' ? 'M' : entry.kind === 'added' ? 'A' : 'D'} ${entry.path}`)
|
|
2591
3290
|
.join('\n') + (changed.length > 8 ? `\n…还有 ${changed.length - 8} 个` : '')), excludedCount > 0
|
|
2592
|
-
? (0,
|
|
3291
|
+
? (0, primitives_8.h)('div', { style: primitives_8.MUTED }, `${excludedCount} 个改动属索引排除范围(超大文件等),刷新也不会收录,因此不计入刷新依据。`)
|
|
2593
3292
|
: null)
|
|
2594
3293
|
: gate !== null && Array.isArray(gate.notes) && gate.notes.length > 0
|
|
2595
3294
|
? // No gate: either the comparison was impossible (legacy manifest, no git) or
|
|
2596
3295
|
// it was not anchored to the index's commit. Both are "cannot prove it is
|
|
2597
3296
|
// current", and the notes say which — print them all, because the first one
|
|
2598
3297
|
// alone explains the cause but not the consequence.
|
|
2599
|
-
(0,
|
|
2600
|
-
|
|
3298
|
+
(0, primitives_8.h)('div', { style: { ...primitives_8.MUTED, display: 'flex', flexDirection: 'column', gap: '2px' } }, gate.advice !== undefined && gate.advice !== null
|
|
3299
|
+
? (0, primitives_8.h)('div', null, `建议:${gate.advice.label} —— ${gate.advice.reason}`)
|
|
3300
|
+
: null, gate.notes.slice(0, 3).map((note, index) => (0, primitives_8.h)('div', { key: index }, `· ${note}`)))
|
|
3301
|
+
: skipGit && indexed
|
|
3302
|
+
? (0, primitives_8.h)('div', { style: primitives_8.MUTED }, '该目录没有 .git:索引由 --skip-git 建立,没有提交基线,只能重新分析,不能增量刷新。')
|
|
3303
|
+
: null, commands.map((command) => {
|
|
2601
3304
|
// The host's wording is written for the no-index state ("初始化索引", "该项目还没有
|
|
2602
3305
|
// … 索引时使用"). Reusing it on an indexed project told the operator to initialise
|
|
2603
3306
|
// an index that already existed. Only the label and the "when" line are stateful;
|
|
@@ -2615,7 +3318,7 @@ define("sections/maintenance", ["require", "exports", "react", "@deepseek-ai/dsh
|
|
|
2615
3318
|
when: '索引落后或有文件改动时使用;未变更文件的解析产物会复用。',
|
|
2616
3319
|
}
|
|
2617
3320
|
: command;
|
|
2618
|
-
return (0,
|
|
3321
|
+
return (0, primitives_8.h)('div', {
|
|
2619
3322
|
key: command.mode,
|
|
2620
3323
|
style: {
|
|
2621
3324
|
display: 'flex',
|
|
@@ -2626,13 +3329,13 @@ define("sections/maintenance", ["require", "exports", "react", "@deepseek-ai/dsh
|
|
|
2626
3329
|
// because the index says FTS is missing, so it keeps full weight.
|
|
2627
3330
|
opacity: command.mode === applicable || command.mode === 'repair-fts' ? 1 : 0.55,
|
|
2628
3331
|
},
|
|
2629
|
-
}, (0,
|
|
3332
|
+
}, (0, primitives_8.h)('div', { style: { display: 'flex', alignItems: 'center', gap: '6px' } }, (0, primitives_8.h)('span', { style: { fontWeight: 600 } }, shown.label), command.mode === applicable ? (0, primitives_8.h)(primitives_8.Tag, { tone: 'solid' }, '当前适用') : null, command.destructive ? (0, primitives_8.h)(primitives_8.Tag, { tone: 'outline' }, '破坏性') : null, (0, primitives_8.h)('span', { style: { flex: 1 } }),
|
|
2630
3333
|
// Each command is executable now, each behind its own confirmation. The
|
|
2631
3334
|
// force button exists because the host route implements `--force`; a button
|
|
2632
3335
|
// labelled "全量重建" that quietly ran an incremental refresh would be the
|
|
2633
3336
|
// exact failure the earlier revision refused to ship.
|
|
2634
3337
|
command.mode === 'init'
|
|
2635
|
-
? (0,
|
|
3338
|
+
? (0, primitives_8.h)(primitives_8.Button, {
|
|
2636
3339
|
variant: 'ghost',
|
|
2637
3340
|
// The gate is the whole point of Tier 0: a clean tree disables the
|
|
2638
3341
|
// normal action and says so, and "仍然刷新" is the way past it.
|
|
@@ -2654,29 +3357,29 @@ define("sections/maintenance", ["require", "exports", "react", "@deepseek-ai/dsh
|
|
|
2654
3357
|
? '执行增量刷新'
|
|
2655
3358
|
: '执行增量刷新(需重启)')
|
|
2656
3359
|
: command.mode === 'repair-fts'
|
|
2657
|
-
? (0,
|
|
3360
|
+
? (0, primitives_8.h)(primitives_8.Button, { variant: 'ghost', disabled: running || !hostCurrent, onClick: () => setConfirming('repair-fts') }, running ? '运行中' : '修复搜索索引')
|
|
2658
3361
|
: hostCurrent
|
|
2659
|
-
? (0,
|
|
2660
|
-
: (0,
|
|
3362
|
+
? (0, primitives_8.h)(primitives_8.Button, { variant: 'ghost', disabled: running, onClick: () => setConfirming('full') }, running ? '运行中' : '全量重建')
|
|
3363
|
+
: (0, primitives_8.h)(primitives_8.Button, {
|
|
2661
3364
|
variant: 'ghost',
|
|
2662
3365
|
disabled: true,
|
|
2663
3366
|
title: '当前宿主版本尚未支持 force=1;重启 DSH 后再试,或复制命令执行',
|
|
2664
|
-
}, '全量重建(需重启)'), (0,
|
|
3367
|
+
}, '全量重建(需重启)'), (0, primitives_8.h)(primitives_8.Button, { variant: 'ghost', onClick: () => copy(command) }, copied === command.mode ? '已复制' : '复制')), (0, primitives_8.h)('code', {
|
|
2665
3368
|
style: {
|
|
2666
|
-
...
|
|
3369
|
+
...primitives_8.styles.mono,
|
|
2667
3370
|
display: 'block',
|
|
2668
3371
|
padding: '6px 8px',
|
|
2669
|
-
border:
|
|
3372
|
+
border: primitives_8.HAIRLINE,
|
|
2670
3373
|
borderRadius: '6px',
|
|
2671
|
-
background:
|
|
3374
|
+
background: primitives_8.SOFT_BG,
|
|
2672
3375
|
wordBreak: 'break-all',
|
|
2673
3376
|
userSelect: 'all',
|
|
2674
3377
|
},
|
|
2675
|
-
}, command.shell), (0,
|
|
2676
|
-
? (0,
|
|
3378
|
+
}, command.shell), (0, primitives_8.h)('div', { style: primitives_8.MUTED }, shown.when), command.caveat !== null && command.caveat !== undefined
|
|
3379
|
+
? (0, primitives_8.h)('div', { style: primitives_8.MUTED }, command.caveat)
|
|
2677
3380
|
: null);
|
|
2678
3381
|
}), Array.isArray(maintenance?.rationale) && maintenance.rationale.length > 0
|
|
2679
|
-
? (0,
|
|
3382
|
+
? (0, primitives_8.h)('div', { style: { ...primitives_8.MUTED, display: 'flex', flexDirection: 'column', gap: '2px' } }, maintenance.rationale.map((note, index) => (0, primitives_8.h)('div', { key: index }, `· ${note}`)))
|
|
2680
3383
|
: null,
|
|
2681
3384
|
// The confirmation. Everything the operator needs before a process touches their
|
|
2682
3385
|
// working tree goes in `description`, because that is the prop the modal renders:
|
|
@@ -2686,7 +3389,7 @@ define("sections/maintenance", ["require", "exports", "react", "@deepseek-ai/dsh
|
|
|
2686
3389
|
// One dialog, two modes. The full rebuild gets its own title, wording and
|
|
2687
3390
|
// acknowledgement because it is a different risk: it discards the graph and FTS
|
|
2688
3391
|
// and can take minutes, while the incremental path reuses unchanged files.
|
|
2689
|
-
(0,
|
|
3392
|
+
(0, primitives_8.h)(primitives.RiskConfirmation, {
|
|
2690
3393
|
open: confirming !== null,
|
|
2691
3394
|
title: repairConfirm
|
|
2692
3395
|
? '修复搜索索引(FTS)?'
|
|
@@ -2757,7 +3460,91 @@ define("sections/maintenance", ["require", "exports", "react", "@deepseek-ai/dsh
|
|
|
2757
3460
|
}));
|
|
2758
3461
|
}
|
|
2759
3462
|
});
|
|
2760
|
-
define("sections/
|
|
3463
|
+
define("sections/workspace", ["require", "exports", "format", "primitives"], function (require, exports, format_8, primitives_9) {
|
|
3464
|
+
"use strict";
|
|
3465
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3466
|
+
exports.aggregateProjects = aggregateProjects;
|
|
3467
|
+
exports.WorkspaceOverview = WorkspaceOverview;
|
|
3468
|
+
/** Sum the manifest stats of every indexed project below the workspace. */
|
|
3469
|
+
function aggregateProjects(projects) {
|
|
3470
|
+
const totals = {
|
|
3471
|
+
indexed: 0,
|
|
3472
|
+
unindexed: 0,
|
|
3473
|
+
files: 0,
|
|
3474
|
+
nodes: 0,
|
|
3475
|
+
edges: 0,
|
|
3476
|
+
communities: 0,
|
|
3477
|
+
processes: 0,
|
|
3478
|
+
embeddings: 0,
|
|
3479
|
+
};
|
|
3480
|
+
for (const project of projects) {
|
|
3481
|
+
if (project.kind === 'index') {
|
|
3482
|
+
totals.indexed += 1;
|
|
3483
|
+
const stats = project.stats;
|
|
3484
|
+
if (stats !== null && stats !== undefined) {
|
|
3485
|
+
totals.files += stats.files;
|
|
3486
|
+
totals.nodes += stats.nodes;
|
|
3487
|
+
totals.edges += stats.edges;
|
|
3488
|
+
totals.communities += stats.communities;
|
|
3489
|
+
totals.processes += stats.processes;
|
|
3490
|
+
totals.embeddings += stats.embeddings;
|
|
3491
|
+
}
|
|
3492
|
+
}
|
|
3493
|
+
else if (project.kind === 'git') {
|
|
3494
|
+
totals.unindexed += 1;
|
|
3495
|
+
}
|
|
3496
|
+
}
|
|
3497
|
+
return totals;
|
|
3498
|
+
}
|
|
3499
|
+
const MAX_ROWS = 8;
|
|
3500
|
+
/**
|
|
3501
|
+
* Aggregate view for a multi-project workspace. Returns null for a single project:
|
|
3502
|
+
* there is nothing to aggregate, and the per-project cards already say the same thing.
|
|
3503
|
+
*/
|
|
3504
|
+
function WorkspaceOverview({ projects, activePath, onSelectProject, }) {
|
|
3505
|
+
const indexed = projects.filter((project) => project.kind === 'index');
|
|
3506
|
+
if (indexed.length < 2)
|
|
3507
|
+
return null;
|
|
3508
|
+
const totals = aggregateProjects(projects);
|
|
3509
|
+
const rows = [...indexed]
|
|
3510
|
+
.sort((a, b) => (b.stats?.nodes ?? 0) - (a.stats?.nodes ?? 0) || a.path.localeCompare(b.path))
|
|
3511
|
+
.slice(0, MAX_ROWS);
|
|
3512
|
+
return (0, primitives_9.h)(primitives_9.Section, {
|
|
3513
|
+
wide: true,
|
|
3514
|
+
title: '工作区概览',
|
|
3515
|
+
meta: (0, primitives_9.h)('span', { style: { ...primitives_9.MUTED, fontSize: '11px' } }, `${totals.indexed} 个已索引` + (totals.unindexed > 0 ? ` · ${totals.unindexed} 个未索引` : '')),
|
|
3516
|
+
}, (0, primitives_9.h)('div', { style: { display: 'flex', flexDirection: 'column', gap: '6px' } }, (0, primitives_9.h)('div', { style: { ...primitives_9.MUTED, fontSize: '11px' } }, `合计 ${(0, format_8.compactNumber)(totals.files)} 个文件 · ${(0, format_8.compactNumber)(totals.nodes)} 个符号 · ` +
|
|
3517
|
+
`${(0, format_8.compactNumber)(totals.edges)} 条关系 · ${(0, format_8.compactNumber)(totals.communities)} 个社区 · ` +
|
|
3518
|
+
`${(0, format_8.compactNumber)(totals.processes)} 条流程`), (0, primitives_9.h)('div', { style: { display: 'flex', flexDirection: 'column', gap: '2px' } }, rows.map((project) => (0, primitives_9.h)('div', {
|
|
3519
|
+
key: project.path,
|
|
3520
|
+
role: 'button',
|
|
3521
|
+
tabIndex: 0,
|
|
3522
|
+
title: project.path,
|
|
3523
|
+
onClick: () => onSelectProject?.(project.path),
|
|
3524
|
+
onKeyDown: (event) => {
|
|
3525
|
+
if (event.key === 'Enter' || event.key === ' ')
|
|
3526
|
+
onSelectProject?.(project.path);
|
|
3527
|
+
},
|
|
3528
|
+
style: {
|
|
3529
|
+
display: 'grid',
|
|
3530
|
+
gridTemplateColumns: 'minmax(0, 1fr) auto auto auto',
|
|
3531
|
+
gap: '8px',
|
|
3532
|
+
alignItems: 'center',
|
|
3533
|
+
padding: '4px 6px',
|
|
3534
|
+
border: primitives_9.HAIRLINE,
|
|
3535
|
+
borderRadius: '4px',
|
|
3536
|
+
background: project.path === activePath ? primitives_9.SOFT_BG : 'transparent',
|
|
3537
|
+
cursor: onSelectProject === undefined ? 'default' : 'pointer',
|
|
3538
|
+
fontSize: '11px',
|
|
3539
|
+
},
|
|
3540
|
+
}, (0, primitives_9.h)('span', { style: { minWidth: 0, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' } }, project.relPath === '' ? `${project.name}(本工作区)` : project.relPath), (0, primitives_9.h)('span', { style: { ...primitives_9.MUTED, textAlign: 'right' } }, `${(0, format_8.compactNumber)(project.stats?.nodes ?? 0)} 符号`), (0, primitives_9.h)('span', { style: { ...primitives_9.MUTED, textAlign: 'right' } }, `${(0, format_8.compactNumber)(project.stats?.files ?? 0)} 文件`), (0, primitives_9.h)('span', { style: { ...primitives_9.MUTED, textAlign: 'right' } }, project.complete ? '完整' : '缺图')))), indexed.length > MAX_ROWS
|
|
3541
|
+
? (0, primitives_9.h)('div', { style: { ...primitives_9.MUTED, fontSize: '11px' } }, `还有 ${indexed.length - MAX_ROWS} 个项目;用标题栏切换器查看。`)
|
|
3542
|
+
: null, totals.unindexed > 0
|
|
3543
|
+
? (0, primitives_9.h)('div', { style: { ...primitives_9.MUTED, fontSize: '11px' } }, `${totals.unindexed} 个 git 项目尚未建索引,未计入合计。`)
|
|
3544
|
+
: null));
|
|
3545
|
+
}
|
|
3546
|
+
});
|
|
3547
|
+
define("sections/overview", ["require", "exports", "react", "format", "primitives", "sections/capabilities"], function (require, exports, React, format_9, primitives_10, capabilities_1) {
|
|
2761
3548
|
"use strict";
|
|
2762
3549
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
2763
3550
|
exports.MemoizedOverviewBody = void 0;
|
|
@@ -2770,36 +3557,36 @@ define("sections/overview", ["require", "exports", "react", "format", "primitive
|
|
|
2770
3557
|
* section wrapper and the split-region layout around it.
|
|
2771
3558
|
*/
|
|
2772
3559
|
function OverviewBody({ report, totals, cap, ratio, }) {
|
|
2773
|
-
return (0,
|
|
3560
|
+
return (0, primitives_10.h)('div', { style: { display: 'flex', flexDirection: 'column', gap: '10px' } },
|
|
2774
3561
|
// Ring and scan summary on one line, metrics below at full card width. Keeping
|
|
2775
3562
|
// the ring in a left rail stole ~90px from the metric grid at exactly the widths
|
|
2776
3563
|
// where it needed two columns, so the card turned into a tall single column.
|
|
2777
|
-
(0,
|
|
3564
|
+
(0, primitives_10.h)('div', { style: { display: 'flex', gap: '12px', alignItems: 'center', minWidth: 0 } }, ratio === null
|
|
2778
3565
|
? null
|
|
2779
|
-
: (0,
|
|
3566
|
+
: (0, primitives_10.h)(primitives_10.Ring, {
|
|
2780
3567
|
ratio,
|
|
2781
3568
|
caption: '覆盖',
|
|
2782
3569
|
tone: ratio >= 0.9
|
|
2783
3570
|
? 'var(--dsw-alias-state-success-primary, #3fb950)'
|
|
2784
3571
|
: 'var(--dsw-alias-state-warn-primary, #d9a441)',
|
|
2785
|
-
}), (0,
|
|
3572
|
+
}), (0, primitives_10.h)('div', { style: { ...primitives_10.MUTED, minWidth: 0 } }, (0, format_9.summaryLine)(report))),
|
|
2786
3573
|
// The three readings sit in a grid instead of a column: 文件 and 图谱 share a
|
|
2787
3574
|
// row, and the capability strip spans the full width below them. A stacked
|
|
2788
3575
|
// layout left the right half of the card empty at every width above ~360px.
|
|
2789
|
-
(0,
|
|
3576
|
+
(0, primitives_10.h)('div', {
|
|
2790
3577
|
style: {
|
|
2791
3578
|
display: 'grid',
|
|
2792
3579
|
gridTemplateColumns: 'repeat(auto-fit, minmax(150px, 1fr))',
|
|
2793
3580
|
gap: '10px 20px',
|
|
2794
3581
|
alignItems: 'start',
|
|
2795
3582
|
},
|
|
2796
|
-
}, (0,
|
|
3583
|
+
}, (0, primitives_10.h)('div', { style: { minWidth: 0 } }, (0, primitives_10.h)('div', { style: { ...primitives_10.MUTED, marginBottom: '5px' } }, '文件'), (0, primitives_10.h)(primitives_10.MetricGrid, {
|
|
2797
3584
|
items: [
|
|
2798
|
-
{ label: '大小上限', value: cap.text, hint: (0,
|
|
2799
|
-
{ label: '跳过合计', value: (0,
|
|
3585
|
+
{ label: '大小上限', value: cap.text, hint: (0, format_9.capSourceLabel)(cap.source), color: '#d9a441' },
|
|
3586
|
+
{ label: '跳过合计', value: (0, format_9.fileSize)(totals.largeBytes), color: '#e0af68' },
|
|
2800
3587
|
{
|
|
2801
3588
|
label: '超限文件',
|
|
2802
|
-
value: (0,
|
|
3589
|
+
value: (0, format_9.compactNumber)(totals.large),
|
|
2803
3590
|
// Amber when something was actually dropped, grey when the count is
|
|
2804
3591
|
// zero: a zero is good news and should not shout.
|
|
2805
3592
|
color: totals.large > 0 ? '#f7768e' : undefined,
|
|
@@ -2807,17 +3594,17 @@ define("sections/overview", ["require", "exports", "react", "format", "primitive
|
|
|
2807
3594
|
],
|
|
2808
3595
|
})), report.graph === null || report.graph === undefined
|
|
2809
3596
|
? null
|
|
2810
|
-
: (0,
|
|
3597
|
+
: (0, primitives_10.h)('div', { style: { minWidth: 0 } }, (0, primitives_10.h)('div', { style: { ...primitives_10.MUTED, marginBottom: '5px' } }, '图谱'), (0, primitives_10.h)(primitives_10.MetricGrid, {
|
|
2811
3598
|
items: [
|
|
2812
|
-
{ label: '符号', value: (0,
|
|
2813
|
-
{ label: '边', value: (0,
|
|
2814
|
-
{ label: '社区', value: (0,
|
|
2815
|
-
{ label: '流程', value: (0,
|
|
2816
|
-
{ label: '向量', value: (0,
|
|
3599
|
+
{ label: '符号', value: (0, format_9.compactNumber)(report.graph.nodes), color: '#7aa2f7' },
|
|
3600
|
+
{ label: '边', value: (0, format_9.compactNumber)(report.graph.edges), color: '#9ece6a' },
|
|
3601
|
+
{ label: '社区', value: (0, format_9.compactNumber)(report.graph.communities), color: '#bb9af7' },
|
|
3602
|
+
{ label: '流程', value: (0, format_9.compactNumber)(report.graph.processes), color: '#7dcfff' },
|
|
3603
|
+
{ label: '向量', value: (0, format_9.compactNumber)(report.graph.embeddings), color: '#f7768e' },
|
|
2817
3604
|
],
|
|
2818
|
-
})), (0,
|
|
3605
|
+
})), (0, primitives_10.h)('div', { style: { gridColumn: '1 / -1', borderTop: primitives_10.HAIRLINE, paddingTop: '8px' } }, (0, primitives_10.h)('div', { style: { ...primitives_10.MUTED, marginBottom: '5px' } }, '查询能力'), (0, primitives_10.h)(capabilities_1.CapabilityStrip, { capabilities: report.capabilities }), report.capabilities?.fts?.status === 'available'
|
|
2819
3606
|
? null
|
|
2820
|
-
: (0,
|
|
3607
|
+
: (0, primitives_10.h)('div', { style: { ...primitives_10.MUTED, marginTop: '4px', fontSize: '10px' } }, '顶部「关键词搜索」会先查图;图查询不可用时回退到源码关键词。'))));
|
|
2821
3608
|
}
|
|
2822
3609
|
/**
|
|
2823
3610
|
* Memoized for the panel: all four props are stable for the lifetime of one report, so
|
|
@@ -2825,7 +3612,7 @@ define("sections/overview", ["require", "exports", "react", "format", "primitive
|
|
|
2825
3612
|
*/
|
|
2826
3613
|
exports.MemoizedOverviewBody = React.memo(OverviewBody);
|
|
2827
3614
|
});
|
|
2828
|
-
define("sections/install", ["require", "exports", "react", "@deepseek-ai/dsh-client-ui-primitives", "clock", "format", "primitives"], function (require, exports, React, primitives, clock_2,
|
|
3615
|
+
define("sections/install", ["require", "exports", "react", "@deepseek-ai/dsh-client-ui-primitives", "clock", "format", "primitives"], function (require, exports, React, primitives, clock_2, format_10, primitives_11) {
|
|
2829
3616
|
"use strict";
|
|
2830
3617
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
2831
3618
|
exports.ToolchainNotice = ToolchainNotice;
|
|
@@ -2872,27 +3659,27 @@ define("sections/install", ["require", "exports", "react", "@deepseek-ai/dsh-cli
|
|
|
2872
3659
|
setCopied(false);
|
|
2873
3660
|
}
|
|
2874
3661
|
};
|
|
2875
|
-
return (0,
|
|
3662
|
+
return (0, primitives_11.h)(primitives_11.Section, { wide: true, title: libraryOnly ? '缺少 gitnexus 命令' : '本机没有 GitNexus' }, (0, primitives_11.h)('div', { style: { display: 'flex', flexDirection: 'column', gap: '10px', paddingTop: '4px' } }, (0, primitives_11.h)('div', { style: { ...primitives_11.MUTED, display: 'flex', gap: '6px', alignItems: 'flex-start' } }, (0, primitives_11.h)(primitives_11.StateDot, { state: 'warning' }), (0, primitives_11.h)('span', null, libraryOnly
|
|
2876
3663
|
? '找到了 GitNexus 的库,但没有可执行的 gitnexus 命令(可能只作为依赖安装、未全局链接)。读取不受影响;修复后即可执行增量刷新。'
|
|
2877
|
-
: '本插件读取 .gitnexus/gitnexus.json —— 那份文件由 GitNexus 分析仓库产生,包含每个函数定义、符号关系与执行流。没有它就没什么可展示的。')), (0,
|
|
3664
|
+
: '本插件读取 .gitnexus/gitnexus.json —— 那份文件由 GitNexus 分析仓库产生,包含每个函数定义、符号关系与执行流。没有它就没什么可展示的。')), (0, primitives_11.h)('div', { style: { display: 'flex', flexDirection: 'column', gap: '4px' } }, (0, primitives_11.h)('div', { style: { fontWeight: 600 } }, '① 自己安装(推荐)'), (0, primitives_11.h)('div', { style: { display: 'flex', alignItems: 'center', gap: '6px' } }, (0, primitives_11.h)('code', {
|
|
2878
3665
|
style: {
|
|
2879
|
-
...
|
|
3666
|
+
...primitives_11.styles.mono,
|
|
2880
3667
|
flex: '1 1 auto',
|
|
2881
3668
|
padding: '6px 8px',
|
|
2882
|
-
border:
|
|
3669
|
+
border: primitives_11.HAIRLINE,
|
|
2883
3670
|
borderRadius: '6px',
|
|
2884
|
-
background:
|
|
3671
|
+
background: primitives_11.SOFT_BG,
|
|
2885
3672
|
wordBreak: 'break-all',
|
|
2886
3673
|
userSelect: 'all',
|
|
2887
3674
|
},
|
|
2888
|
-
}, command), (0,
|
|
2889
|
-
? (0,
|
|
2890
|
-
: (0,
|
|
3675
|
+
}, command), (0, primitives_11.h)(primitives_11.Button, { variant: 'ghost', onClick: copy }, copied ? '已复制' : '复制')), (0, primitives_11.h)('div', { style: primitives_11.MUTED }, '用你能访问到的源安装即可 —— 本插件不假设任何网络,也不会自行下载。')), (0, primitives_11.h)('div', { style: { display: 'flex', flexDirection: 'column', gap: '4px' } }, (0, primitives_11.h)('div', { style: { fontWeight: 600 } }, '② 授权本插件安装'), (0, primitives_11.h)('div', { style: { display: 'flex', alignItems: 'center', gap: '8px', flexWrap: 'wrap' } }, installing
|
|
3676
|
+
? (0, primitives_11.h)(primitives_11.Button, { variant: 'ghost', onClick: onCancelInstall }, `取消(已进行 ${elapsed}s)`)
|
|
3677
|
+
: (0, primitives_11.h)(primitives_11.Button, { variant: 'ghost', onClick: () => setConfirming(true) }, '授权并安装'), (0, primitives_11.h)('span', { style: primitives_11.MUTED }, '装到插件自己的目录,用你已配置的 npm 源,不改全局前缀、不需要管理员权限;' +
|
|
2891
3678
|
'若当前项目还没有索引,装完会继续初始化一次。')), activeRun === null
|
|
2892
3679
|
? null
|
|
2893
|
-
: (0,
|
|
3680
|
+
: (0, primitives_11.h)('div', { style: { display: 'flex', flexDirection: 'column', gap: '3px', paddingTop: '2px' } }, (0, primitives_11.h)('div', { style: { display: 'flex', alignItems: 'center', gap: '6px' } }, (0, primitives_11.h)(primitives_11.StateDot, { state: (0, format_10.runStateTone)(activeRun.state) }), (0, primitives_11.h)('span', { style: { fontWeight: 600 } }, (0, format_10.runStateLabel)(activeRun.state)), (0, primitives_11.h)('span', { style: primitives_11.MUTED }, installing ? `已运行 ${elapsed}s` : `${elapsed}s`)), activeRun.message === null ? null : (0, primitives_11.h)('div', { style: primitives_11.MUTED }, activeRun.message), activeRun.output === ''
|
|
2894
3681
|
? null
|
|
2895
|
-
: (0,
|
|
3682
|
+
: (0, primitives_11.h)('pre', { style: { ...primitives_11.styles.mono, margin: 0, whiteSpace: 'pre-wrap', maxHeight: '9em', overflow: 'auto' } }, activeRun.output)), runError === null ? null : (0, primitives_11.h)('div', { style: { ...primitives_11.MUTED, color: 'var(--dsw-alias-state-error-primary, inherit)' } }, runError)), (0, primitives_11.h)(primitives.RiskConfirmation, {
|
|
2896
3683
|
open: confirming,
|
|
2897
3684
|
title: '授权本插件安装 GitNexus?',
|
|
2898
3685
|
description: '本插件将执行:\n\nnpm install --prefix <插件数据目录>/gitnexus gitnexus@^1.6.12\n\n' +
|
|
@@ -2921,7 +3708,7 @@ define("sections/install", ["require", "exports", "react", "@deepseek-ai/dsh-cli
|
|
|
2921
3708
|
})));
|
|
2922
3709
|
}
|
|
2923
3710
|
});
|
|
2924
|
-
define("sections/search", ["require", "exports", "primitives"], function (require, exports,
|
|
3711
|
+
define("sections/search", ["require", "exports", "primitives"], function (require, exports, primitives_12) {
|
|
2925
3712
|
"use strict";
|
|
2926
3713
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
2927
3714
|
exports.SearchSection = SearchSection;
|
|
@@ -2937,13 +3724,13 @@ define("sections/search", ["require", "exports", "primitives"], function (requir
|
|
|
2937
3724
|
function SearchSection({ search }) {
|
|
2938
3725
|
const keyOf = (hit) => `${hit.kind}:${hit.filePath}:${hit.startLine ?? ''}:${hit.name}`;
|
|
2939
3726
|
const canSubmit = search.query.trim() !== '' && !search.searching;
|
|
2940
|
-
return (0,
|
|
3727
|
+
return (0, primitives_12.h)(primitives_12.Section, {
|
|
2941
3728
|
wide: true,
|
|
2942
3729
|
title: '关键词搜索',
|
|
2943
|
-
meta: (0,
|
|
3730
|
+
meta: (0, primitives_12.h)('span', { style: { ...primitives_12.MUTED, fontSize: '12px' } }, search.mode === 'text' ? '源码关键词(回退)' : '符号名 / 文件路径'),
|
|
2944
3731
|
// Twenty hits of `path:line` rows is a scroll area, not a page length.
|
|
2945
3732
|
scroll: 420,
|
|
2946
|
-
}, (0,
|
|
3733
|
+
}, (0, primitives_12.h)('div', { style: { display: 'flex', gap: '8px', alignItems: 'center', paddingTop: '4px', flexWrap: 'wrap' } }, (0, primitives_12.h)('input', {
|
|
2947
3734
|
type: 'search',
|
|
2948
3735
|
value: search.query,
|
|
2949
3736
|
placeholder: '搜索符号或文件,例如 analyzeCoverage',
|
|
@@ -2961,34 +3748,34 @@ define("sections/search", ["require", "exports", "primitives"], function (requir
|
|
|
2961
3748
|
flex: '1 1 auto',
|
|
2962
3749
|
minWidth: 0,
|
|
2963
3750
|
padding: '5px 8px',
|
|
2964
|
-
border:
|
|
3751
|
+
border: primitives_12.HAIRLINE,
|
|
2965
3752
|
borderRadius: '6px',
|
|
2966
|
-
background:
|
|
3753
|
+
background: primitives_12.SOFT_BG,
|
|
2967
3754
|
color: 'inherit',
|
|
2968
3755
|
font: 'inherit',
|
|
2969
3756
|
},
|
|
2970
|
-
}), (0,
|
|
2971
|
-
? (0,
|
|
3757
|
+
}), (0, primitives_12.h)(primitives_12.Button, { variant: 'primary', disabled: !canSubmit, onClick: search.onSubmit }, search.searching ? '搜索中…' : '搜索'), search.query === '' ? null : (0, primitives_12.h)(primitives_12.Button, { variant: 'ghost', onClick: search.onClear }, '清除')), search.error !== null
|
|
3758
|
+
? (0, primitives_12.h)('div', {
|
|
2972
3759
|
style: {
|
|
2973
|
-
...
|
|
3760
|
+
...primitives_12.MUTED,
|
|
2974
3761
|
color: 'var(--dsw-alias-state-error-primary, inherit)',
|
|
2975
3762
|
marginTop: '6px',
|
|
2976
3763
|
},
|
|
2977
3764
|
}, search.error)
|
|
2978
|
-
: null, (0,
|
|
3765
|
+
: null, (0, primitives_12.h)('div', { style: { ...primitives_12.MUTED, fontSize: '10px', marginTop: '4px' } }, search.warning ??
|
|
2979
3766
|
(search.mode === 'text'
|
|
2980
3767
|
? '图查询不可用,已回退到源码关键词搜索。'
|
|
2981
3768
|
: '按符号名与文件路径匹配;不搜索正文。')), search.results === null
|
|
2982
3769
|
? null
|
|
2983
3770
|
: search.results.length === 0
|
|
2984
|
-
? (0,
|
|
2985
|
-
: (0,
|
|
3771
|
+
? (0, primitives_12.h)('div', { style: { ...primitives_12.MUTED, marginTop: '6px' } }, `没有匹配“${search.query}”的符号或文件。`)
|
|
3772
|
+
: (0, primitives_12.h)('div', { style: { display: 'flex', flexDirection: 'column', marginTop: '6px' } }, (0, primitives_12.h)('div', { style: { ...primitives_12.MUTED, fontSize: '10px', paddingBottom: '2px' } }, (search.mode === 'text'
|
|
2986
3773
|
? `找到 ${search.total} 处源码匹配`
|
|
2987
3774
|
: `找到 ${search.total} 个结果`) +
|
|
2988
|
-
`${search.truncated ? '(已截断)' : ''} · 点击结果打开文件`), search.results.map((hit) => (0,
|
|
3775
|
+
`${search.truncated ? '(已截断)' : ''} · 点击结果打开文件`), search.results.map((hit) => (0, primitives_12.h)('div', {
|
|
2989
3776
|
key: keyOf(hit),
|
|
2990
|
-
style: { ...
|
|
2991
|
-
}, (0,
|
|
3777
|
+
style: { ...primitives_12.styles.fileRow, borderTop: primitives_12.HAIRLINE, padding: '5px 0' },
|
|
3778
|
+
}, (0, primitives_12.h)('button', {
|
|
2992
3779
|
type: 'button',
|
|
2993
3780
|
onClick: () => search.onOpen(hit),
|
|
2994
3781
|
title: hit.startLine === null
|
|
@@ -3008,7 +3795,7 @@ define("sections/search", ["require", "exports", "primitives"], function (requir
|
|
|
3008
3795
|
textAlign: 'left',
|
|
3009
3796
|
cursor: 'pointer',
|
|
3010
3797
|
},
|
|
3011
|
-
}, (0,
|
|
3798
|
+
}, (0, primitives_12.h)(primitives_12.Tag, { tone: 'neutral' }, hit.kind), (0, primitives_12.h)('span', {
|
|
3012
3799
|
style: {
|
|
3013
3800
|
fontWeight: 600,
|
|
3014
3801
|
minWidth: 0,
|
|
@@ -3016,20 +3803,20 @@ define("sections/search", ["require", "exports", "primitives"], function (requir
|
|
|
3016
3803
|
textOverflow: 'ellipsis',
|
|
3017
3804
|
whiteSpace: 'nowrap',
|
|
3018
3805
|
},
|
|
3019
|
-
}, hit.name), (0,
|
|
3806
|
+
}, hit.name), (0, primitives_12.h)('span', {
|
|
3020
3807
|
style: {
|
|
3021
|
-
...
|
|
3022
|
-
...
|
|
3808
|
+
...primitives_12.MUTED,
|
|
3809
|
+
...primitives_12.styles.mono,
|
|
3023
3810
|
minWidth: 0,
|
|
3024
3811
|
flex: '1 1 auto',
|
|
3025
3812
|
overflow: 'hidden',
|
|
3026
3813
|
textOverflow: 'ellipsis',
|
|
3027
3814
|
whiteSpace: 'nowrap',
|
|
3028
3815
|
},
|
|
3029
|
-
}, (0,
|
|
3816
|
+
}, (0, primitives_12.h)(primitives_12.PathLabel, { path: hit.filePath }), hit.startLine !== null ? `:${hit.startLine}` : '')), (0, primitives_12.h)(primitives_12.Button, { variant: 'ghost', onClick: () => search.onCopy(hit) }, search.copied === keyOf(hit) ? '已复制' : '复制')))));
|
|
3030
3817
|
}
|
|
3031
3818
|
});
|
|
3032
|
-
define("views/panel", ["require", "exports", "react", "@deepseek-ai/dsh-client-ui-primitives", "format", "navigation", "primitives", "sections/overview", "sections/activity", "sections/capabilities", "sections/install", "sections/languages", "sections/large-files", "sections/maintenance", "sections/search", "sections/graph"], function (require, exports, React, primitives,
|
|
3819
|
+
define("views/panel", ["require", "exports", "react", "@deepseek-ai/dsh-client-ui-primitives", "format", "navigation", "primitives", "sections/overview", "sections/activity", "sections/capabilities", "sections/install", "sections/languages", "sections/large-files", "sections/maintenance", "sections/workspace", "sections/search", "sections/graph"], function (require, exports, React, primitives, format_11, navigation_3, primitives_13, overview_1, activity_1, capabilities_2, install_1, languages_1, large_files_1, maintenance_1, workspace_1, search_1, graph_1) {
|
|
3033
3820
|
"use strict";
|
|
3034
3821
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3035
3822
|
exports.chooseProject = chooseProject;
|
|
@@ -3116,14 +3903,14 @@ define("views/panel", ["require", "exports", "react", "@deepseek-ai/dsh-client-u
|
|
|
3116
3903
|
? undefined
|
|
3117
3904
|
: Math.max(0, Math.min(100, Math.round(percent)));
|
|
3118
3905
|
const text = value === undefined ? label : `${label} · ${value}%`;
|
|
3119
|
-
return (0,
|
|
3906
|
+
return (0, primitives_13.h)('div', {
|
|
3120
3907
|
style: {
|
|
3121
3908
|
gridColumn: '1 / -1',
|
|
3122
3909
|
display: 'flex',
|
|
3123
3910
|
flexDirection: 'column',
|
|
3124
3911
|
gap: '4px',
|
|
3125
3912
|
},
|
|
3126
|
-
}, (0,
|
|
3913
|
+
}, (0, primitives_13.h)('progress', {
|
|
3127
3914
|
value,
|
|
3128
3915
|
max: 100,
|
|
3129
3916
|
'aria-label': label,
|
|
@@ -3139,13 +3926,45 @@ define("views/panel", ["require", "exports", "react", "@deepseek-ai/dsh-client-u
|
|
|
3139
3926
|
accentColor: 'var(--dsw-alias-brand-primary, #4f8cff)',
|
|
3140
3927
|
color: 'var(--dsw-alias-brand-primary, #4f8cff)',
|
|
3141
3928
|
},
|
|
3142
|
-
}), (0,
|
|
3929
|
+
}), (0, primitives_13.h)('div', { style: { ...primitives_13.MUTED, fontSize: '10px' } }, text));
|
|
3143
3930
|
}
|
|
3144
|
-
function CoverageView({ report, maintenance, projectPath, workspacePath, projects, projectsVisited, projectsTruncated, projectsError, onSelectProject, context, busy, progress, error, readAt, run, runError, delta, deltaError, deltaRoute, onRefresh, onStartRun, onCancelRun, installRun, installError, onInstall, onCancelInstall, search, graph, }) {
|
|
3931
|
+
function CoverageView({ report, maintenance, projectPath, workspacePath, projects, projectsVisited, projectsTruncated, projectsError, onSelectProject, context, busy, progress, error, readAt, run, runError, delta, deltaError, deltaRoute, onRefresh, onStartRun, onCancelRun, installRun, installError, onInstall, onCancelInstall, search, graph, onGraphNear, graphVisible = false, }) {
|
|
3145
3932
|
// Whether an index exists decides which maintenance command is applicable. When
|
|
3146
3933
|
// the project could not even be identified there is nothing to claim, so it is
|
|
3147
3934
|
// false rather than assumed.
|
|
3148
3935
|
const indexed = report?.repo?.indexed === true;
|
|
3936
|
+
const graphCardRef = React.useRef(null);
|
|
3937
|
+
// Hooks must stay above every early return below: the panel renders once with
|
|
3938
|
+
// `report === null` while the first request is in flight, then again with the
|
|
3939
|
+
// report. Declaring this state after the loading/no-index returns changed the
|
|
3940
|
+
// hook count between those renders and crashed the sidebar with React #310.
|
|
3941
|
+
const [activityDays, setActivityDays] = React.useState(7);
|
|
3942
|
+
/**
|
|
3943
|
+
* Tell the panel when the graph card is near the viewport.
|
|
3944
|
+
*
|
|
3945
|
+
* The view stays presentational: it owns the ref and the observer, but the fetch
|
|
3946
|
+
* lives in the panel. A browser without IntersectionObserver reports immediately;
|
|
3947
|
+
* the test harness records effects but deliberately does not run them.
|
|
3948
|
+
*/
|
|
3949
|
+
React.useEffect(() => {
|
|
3950
|
+
if (!indexed || graph === undefined)
|
|
3951
|
+
return undefined;
|
|
3952
|
+
const node = graphCardRef.current;
|
|
3953
|
+
if (node === null)
|
|
3954
|
+
return undefined;
|
|
3955
|
+
if (typeof IntersectionObserver === 'undefined') {
|
|
3956
|
+
onGraphNear?.();
|
|
3957
|
+
return undefined;
|
|
3958
|
+
}
|
|
3959
|
+
const observer = new IntersectionObserver((entries) => {
|
|
3960
|
+
if (entries.some((entry) => entry.isIntersecting)) {
|
|
3961
|
+
onGraphNear?.();
|
|
3962
|
+
observer.disconnect();
|
|
3963
|
+
}
|
|
3964
|
+
}, { rootMargin: '200px', threshold: 0.01 });
|
|
3965
|
+
observer.observe(node);
|
|
3966
|
+
return () => observer.disconnect();
|
|
3967
|
+
}, [indexed, graph === undefined, onGraphNear]);
|
|
3149
3968
|
// A maintenance payload with no commands is not a section: rendering its title
|
|
3150
3969
|
// produced an empty card on failed reads and on hosts that predate the route.
|
|
3151
3970
|
const maintenanceSection = maintenance !== null &&
|
|
@@ -3173,13 +3992,13 @@ define("views/panel", ["require", "exports", "react", "@deepseek-ai/dsh-client-u
|
|
|
3173
3992
|
onSelectProject !== undefined &&
|
|
3174
3993
|
projectPath !== null;
|
|
3175
3994
|
const activeDiffers = projectPath !== null && workspacePath !== null && projectPath !== workspacePath;
|
|
3176
|
-
const header = (0,
|
|
3995
|
+
const header = (0, primitives_13.h)('div', { style: { display: 'flex', flexDirection: 'column', gap: '3px', minWidth: 0 } }, (0, primitives_13.h)(primitives_13.Row, null, report !== null && report.repo?.indexed ? (0, primitives_13.h)(primitives_13.StateDot, { state: (0, format_11.healthState)(report) }) : null, (0, primitives_13.h)('span', { style: { fontWeight: 600 } }, 'GitNexus 索引'),
|
|
3177
3996
|
// The project, not a global picker. A workspace-scoped switcher is the one
|
|
3178
3997
|
// selection this panel offers; it changes what the cards describe, nothing else.
|
|
3179
3998
|
projectPath === null
|
|
3180
|
-
? (0,
|
|
3999
|
+
? (0, primitives_13.h)('span', { style: primitives_13.MUTED }, '未解析到工作区')
|
|
3181
4000
|
: showSwitcher
|
|
3182
|
-
? (0,
|
|
4001
|
+
? (0, primitives_13.h)('span', { style: { display: 'flex', alignItems: 'center', gap: '2px', minWidth: 0, flex: '1 1 auto' } }, (0, primitives_13.h)('select', {
|
|
3183
4002
|
value: projectPath,
|
|
3184
4003
|
onChange: (event) => onSelectProject?.(event.target.value),
|
|
3185
4004
|
'aria-label': '切换工作区内的 GitNexus 索引',
|
|
@@ -3195,22 +4014,22 @@ define("views/panel", ["require", "exports", "react", "@deepseek-ai/dsh-client-u
|
|
|
3195
4014
|
padding: 0,
|
|
3196
4015
|
cursor: 'pointer',
|
|
3197
4016
|
},
|
|
3198
|
-
}, projectChoices.map((project) => (0,
|
|
3199
|
-
: (0,
|
|
4017
|
+
}, projectChoices.map((project) => (0, primitives_13.h)('option', { key: project.path, value: project.path }, projectLabel(project)))), (0, primitives_13.h)('span', { style: { ...primitives_13.MUTED, fontSize: '10px' } }, '▾'))
|
|
4018
|
+
: (0, primitives_13.h)('span', { style: { minWidth: 0, flex: '1 1 auto' } }, (0, primitives_13.h)(primitives_13.PathLabel, { path: projectPath })), (0, primitives_13.h)('span', { style: { flex: 1 } }),
|
|
3200
4019
|
// The label states the scope. "刷新" alone next to a staleness warning reads as
|
|
3201
4020
|
// "rebuild the index", which this control has never done and — by design, since
|
|
3202
4021
|
// the plugin spawns nothing — cannot do.
|
|
3203
|
-
(0,
|
|
4022
|
+
(0, primitives_13.h)(primitives_13.Button, {
|
|
3204
4023
|
variant: 'ghost',
|
|
3205
4024
|
onClick: onRefresh,
|
|
3206
4025
|
disabled: busy,
|
|
3207
4026
|
title: '重新读取报告(不会重建索引;重建见下方「维护命令」)',
|
|
3208
4027
|
'aria-label': '重新读取报告',
|
|
3209
|
-
}, (0,
|
|
4028
|
+
}, (0, primitives_13.h)(primitives_13.IconRefreshOutlineRegular, { size: 14 }))), showSwitcher ||
|
|
3210
4029
|
activeDiffers ||
|
|
3211
4030
|
projectsTruncated === true ||
|
|
3212
4031
|
(projectsError !== null && projectsError !== undefined)
|
|
3213
|
-
? (0,
|
|
4032
|
+
? (0, primitives_13.h)('div', { style: { ...primitives_13.MUTED, fontSize: '11px', minWidth: 0, overflowWrap: 'anywhere' } }, [
|
|
3214
4033
|
showSwitcher
|
|
3215
4034
|
? `工作区:${workspacePath ?? ''} · ${projectChoices.length} 个项目` +
|
|
3216
4035
|
(indexedProjectCount > 0
|
|
@@ -3235,7 +4054,7 @@ define("views/panel", ["require", "exports", "react", "@deepseek-ai/dsh-client-u
|
|
|
3235
4054
|
// `percent` is null when the host does not stream (old host, or a cached read):
|
|
3236
4055
|
// the native progress element is then indeterminate, not a made-up number.
|
|
3237
4056
|
const progressBar = report === null || busy
|
|
3238
|
-
? (0,
|
|
4057
|
+
? (0, primitives_13.h)(LoadingBar, {
|
|
3239
4058
|
label: progress?.label ?? (report === null ? '正在读取索引…' : '正在刷新索引…'),
|
|
3240
4059
|
percent: progress?.percent ?? null,
|
|
3241
4060
|
})
|
|
@@ -3246,89 +4065,93 @@ define("views/panel", ["require", "exports", "react", "@deepseek-ai/dsh-client-u
|
|
|
3246
4065
|
// index for a project it had not identified. The diagnostic exists so that state
|
|
3247
4066
|
// is actionable rather than a dead end.
|
|
3248
4067
|
if (projectPath === null) {
|
|
3249
|
-
return (0,
|
|
4068
|
+
return (0, primitives_13.h)('div', { style: primitives_13.styles.root }, header, (0, primitives_13.h)(primitives_13.Section, { wide: true, style: { display: 'flex', gap: '8px', alignItems: 'flex-start' } }, (0, primitives_13.h)(primitives_13.StateDot, { state: 'warning' }), (0, primitives_13.h)('span', null, '未能确定当前项目路径。')), context === null || context === undefined
|
|
3250
4069
|
? null
|
|
3251
|
-
: (0,
|
|
4070
|
+
: (0, primitives_13.h)(primitives_13.Section, { wide: true, title: '面板收到的上下文' }, (0, primitives_13.h)('div', { style: { display: 'flex', flexDirection: 'column', gap: '3px' } }, (0, primitives_13.h)('div', null, `存在:${context.present.length > 0 ? context.present.join('、') : '(无)'}`), (0, primitives_13.h)('div', null, `缺失:${context.absent.length > 0 ? context.absent.join('、') : '(无)'}`))));
|
|
3252
4071
|
}
|
|
3253
4072
|
if (error !== null) {
|
|
3254
|
-
return (0,
|
|
3255
|
-
? (0,
|
|
4073
|
+
return (0, primitives_13.h)('div', { style: primitives_13.styles.root }, header, (0, primitives_13.h)(primitives_13.Section, { wide: true, style: { display: 'flex', gap: '8px', alignItems: 'flex-start' } }, (0, primitives_13.h)(primitives_13.StateDot, { state: 'error' }), (0, primitives_13.h)('span', null, error)), maintenanceSection !== null
|
|
4074
|
+
? (0, primitives_13.h)(primitives_13.Section, {
|
|
3256
4075
|
wide: true,
|
|
3257
4076
|
title: '维护命令',
|
|
3258
|
-
meta: (0,
|
|
4077
|
+
meta: (0, primitives_13.h)('span', { style: { ...primitives_13.MUTED, fontSize: '12px' } }, '复制后到会话执行(沙箱与提权由 DSH 处理)'),
|
|
3259
4078
|
scroll: 320,
|
|
3260
|
-
}, (0,
|
|
4079
|
+
}, (0, primitives_13.h)(maintenance_1.MaintenanceSection, { maintenance: maintenanceSection, projectPath, indexed: false, run, runError, delta, deltaError, deltaRoute, onStartRun, onCancelRun }))
|
|
3261
4080
|
: null);
|
|
3262
4081
|
}
|
|
3263
4082
|
if (report === null) {
|
|
3264
|
-
return (0,
|
|
4083
|
+
return (0, primitives_13.h)('div', { style: primitives_13.styles.root }, header, progressBar);
|
|
3265
4084
|
}
|
|
3266
4085
|
// Defaults are resolved before the no-index branch: a scan-only report still has
|
|
3267
4086
|
// totals, a cap and languages even though it has no graph. Building them only for
|
|
3268
4087
|
// an indexed report is what made "no index" mean "nothing to show".
|
|
3269
|
-
const totals = report.totals ??
|
|
3270
|
-
const cap = report.cap ??
|
|
3271
|
-
const analyzer = report.analyzer ??
|
|
4088
|
+
const totals = report.totals ?? format_11.ZERO_TOTALS;
|
|
4089
|
+
const cap = report.cap ?? format_11.ZERO_CAP;
|
|
4090
|
+
const analyzer = report.analyzer ?? format_11.ZERO_ANALYZER;
|
|
3272
4091
|
const largeFiles = report.largeFiles ?? [];
|
|
3273
4092
|
const languages = report.languages ?? null;
|
|
3274
|
-
const { notable, context: contextWarnings } = (0,
|
|
3275
|
-
const ratio = (0,
|
|
3276
|
-
//
|
|
3277
|
-
//
|
|
3278
|
-
const
|
|
4093
|
+
const { notable, context: contextWarnings } = (0, format_11.splitWarnings)(report.warnings);
|
|
4094
|
+
const ratio = (0, format_11.coverageRatio)(report);
|
|
4095
|
+
// Multi-project workspaces get one aggregate card before the project-scoped ones.
|
|
4096
|
+
// It is null for a single project, so the common case is unchanged.
|
|
4097
|
+
const workspaceSection = (0, primitives_13.h)(workspace_1.WorkspaceOverview, {
|
|
4098
|
+
projects: projects ?? [],
|
|
4099
|
+
activePath: projectPath,
|
|
4100
|
+
onSelectProject,
|
|
4101
|
+
});
|
|
3279
4102
|
// No index yet. This is the state the initialize command exists for, but it is
|
|
3280
4103
|
// also a state in which the working tree is still readable: file counts, bytes,
|
|
3281
4104
|
// the cap and the language split do not need GitNexus. The panel used to show
|
|
3282
4105
|
// only "没有索引" and a command, which made an otherwise healthy repository look
|
|
3283
4106
|
// empty. The scan is the "at least something to look at" floor.
|
|
3284
4107
|
if (report.repo?.indexed !== true) {
|
|
3285
|
-
return (0,
|
|
4108
|
+
return (0, primitives_13.h)('div', { style: primitives_13.styles.root }, (0, primitives_13.h)('div', { style: { gridColumn: '1 / -1', display: 'flex', flexDirection: 'column', gap: '6px' } }, header), progressBar, (0, primitives_13.h)(install_1.ToolchainNotice, {
|
|
3286
4109
|
toolchain: report.toolchain,
|
|
3287
4110
|
run: installRun,
|
|
3288
4111
|
runError: installError,
|
|
3289
4112
|
onInstall,
|
|
3290
4113
|
onCancelInstall,
|
|
3291
|
-
}), (0,
|
|
4114
|
+
}), (0, primitives_13.h)('div', { style: { gridColumn: '1 / -1', display: 'flex', gap: '8px', alignItems: 'flex-start' } }, (0, primitives_13.h)(primitives_13.StateDot, { state: 'idle' }), (0, primitives_13.h)('div', { style: { display: 'flex', flexDirection: 'column', gap: '2px', minWidth: 0 } }, (0, primitives_13.h)('span', null, report.repo?.hasGit === false
|
|
3292
4115
|
? '该目录不是 git 仓库,也没有 GitNexus 索引。下面是工作区扫描结果;仍可建立索引(--skip-git),但没有提交基线与增量刷新,也不会有 Git 活动。'
|
|
3293
4116
|
: '该项目还没有 GitNexus 索引。下面是工作区扫描结果;初始化后可看到图谱、查询能力与 Git 活动对账。'), Array.isArray(projects)
|
|
3294
|
-
? (0,
|
|
4117
|
+
? (0, primitives_13.h)('span', { style: { ...primitives_13.MUTED, fontSize: '11px' } }, projectsError !== null && projectsError !== undefined
|
|
3295
4118
|
? `项目列表读取失败:${projectsError};已按工作区描述。`
|
|
3296
4119
|
: indexedProjectCount === 0 && gitProjectCount > 0
|
|
3297
4120
|
? `已在 ${projectsVisited ?? 0} 个目录中查找;工作区内有 ${gitProjectCount} 个 git 项目尚未建索引,可在标题栏切换后初始化。`
|
|
3298
4121
|
: indexedProjectCount === 0
|
|
3299
4122
|
? `已在 ${projectsVisited ?? 0} 个目录中查找,没有找到 GitNexus 索引或 git 仓库。`
|
|
3300
4123
|
: null)
|
|
3301
|
-
: null)), (0,
|
|
4124
|
+
: null)), (0, primitives_13.h)(primitives_13.Section, { wide: true, title: '扫描概览 · 尚未索引' }, (0, primitives_13.h)(primitives_13.MetricGrid, {
|
|
3302
4125
|
items: [
|
|
3303
|
-
{ label: '扫描文件', value: (0,
|
|
3304
|
-
{ label: '选中文件', value: (0,
|
|
3305
|
-
{ label: '扫描字节', value: (0,
|
|
3306
|
-
{ label: '大小上限', value: cap.text, hint: (0,
|
|
4126
|
+
{ label: '扫描文件', value: (0, format_11.compactNumber)(totals.scanned) },
|
|
4127
|
+
{ label: '选中文件', value: (0, format_11.compactNumber)(totals.selected) },
|
|
4128
|
+
{ label: '扫描字节', value: (0, format_11.fileSize)(totals.scannedBytes ?? 0) },
|
|
4129
|
+
{ label: '大小上限', value: cap.text, hint: (0, format_11.capSourceLabel)(cap.source) },
|
|
3307
4130
|
],
|
|
3308
|
-
}), (0,
|
|
3309
|
-
? (0,
|
|
4131
|
+
}), (0, primitives_13.h)('div', { style: { ...primitives_13.MUTED, marginTop: '6px' } }, '这里的数字来自工作区,不是索引;索引里有什么,只有建立后才能回答。')), languages !== null && languages.rows.length > 0 ? (0, primitives_13.h)(languages_1.MemoizedLanguageSection, { languages }) : null, maintenanceSection !== null
|
|
4132
|
+
? (0, primitives_13.h)(primitives_13.Section, {
|
|
3310
4133
|
wide: true,
|
|
3311
4134
|
title: '初始化索引',
|
|
3312
|
-
meta: (0,
|
|
4135
|
+
meta: (0, primitives_13.h)('span', { style: { ...primitives_13.MUTED, fontSize: '11px' } }, maintenanceSection.supportsForce === true
|
|
3313
4136
|
? '均可直接执行,也可复制到会话(沙箱与提权由 DSH 处理)'
|
|
3314
4137
|
: '重启 DSH 后可执行;当前仅可复制(沙箱与提权由 DSH 处理)'),
|
|
3315
|
-
}, (0,
|
|
4138
|
+
}, (0, primitives_13.h)(maintenance_1.MaintenanceSection, { maintenance: maintenanceSection, projectPath, indexed, run, runError, delta, deltaError, deltaRoute, onStartRun, onCancelRun }))
|
|
3316
4139
|
: null);
|
|
3317
4140
|
}
|
|
3318
|
-
return (0,
|
|
4141
|
+
return (0, primitives_13.h)('div', { style: primitives_13.styles.root },
|
|
3319
4142
|
// The header and the two status lines span everything: they describe the whole
|
|
3320
4143
|
// panel, so giving them a column would make them look like one card's heading.
|
|
3321
|
-
(0,
|
|
4144
|
+
(0, primitives_13.h)('div', { style: { gridColumn: '1 / -1', display: 'flex', flexDirection: 'column', gap: '6px' } }, header), progressBar,
|
|
3322
4145
|
// Immediately after the header, before any index content. If GitNexus is missing,
|
|
3323
4146
|
// that single fact explains everything the panel is about to show or fail to show,
|
|
3324
4147
|
// so it belongs above the numbers rather than beside them.
|
|
3325
|
-
(0,
|
|
4148
|
+
(0, primitives_13.h)(install_1.ToolchainNotice, {
|
|
3326
4149
|
toolchain: report.toolchain,
|
|
3327
4150
|
run: installRun,
|
|
3328
4151
|
runError: installError,
|
|
3329
4152
|
onInstall,
|
|
3330
4153
|
onCancelInstall,
|
|
3331
|
-
}), (0,
|
|
4154
|
+
}), workspaceSection, (0, primitives_13.h)('div', { style: { gridColumn: '1 / -1', display: 'flex', flexDirection: 'column', gap: '3px' } }, (0, primitives_13.h)('div', { style: primitives_13.MUTED }, `${report.repo.indexedAt ?? '未知时间'} · ${report.repo.branch ?? 'unknown'}@${String(report.repo.lastCommit ?? '').slice(0, 8)}` +
|
|
3332
4155
|
` · manifest ${report.repo.metaFile ?? '?'} · GitNexus ${analyzer.gitnexusVersion}` +
|
|
3333
4156
|
// The read time is the only thing on screen a refresh can change when the
|
|
3334
4157
|
// repository has not moved, so without it the control reads as broken.
|
|
@@ -3338,12 +4161,12 @@ define("views/panel", ["require", "exports", "react", "@deepseek-ai/dsh-client-u
|
|
|
3338
4161
|
report.staleness === undefined
|
|
3339
4162
|
? null
|
|
3340
4163
|
: (() => {
|
|
3341
|
-
const s = (0,
|
|
3342
|
-
return (0,
|
|
4164
|
+
const s = (0, format_11.stalenessView)(report.staleness);
|
|
4165
|
+
return (0, primitives_13.h)('div', { style: { display: 'flex', alignItems: 'center', gap: '6px' } }, (0, primitives_13.h)(primitives_13.StateDot, { state: s.state }), (0, primitives_13.h)('span', null, s.text));
|
|
3343
4166
|
})()),
|
|
3344
4167
|
// Search sits above the report cards: it is the action a reader reaches for
|
|
3345
4168
|
// before reading, not an annotation on the numbers below it.
|
|
3346
|
-
search === undefined ? null : (0,
|
|
4169
|
+
search === undefined ? null : (0, primitives_13.h)(search_1.SearchSection, { search }),
|
|
3347
4170
|
// One card for the whole "what is this index" question — file coverage and
|
|
3348
4171
|
// graph contents together.
|
|
3349
4172
|
//
|
|
@@ -3355,7 +4178,7 @@ define("views/panel", ["require", "exports", "react", "@deepseek-ai/dsh-client-u
|
|
|
3355
4178
|
// stacked on the right. The right column stretches to the overview's height, so the
|
|
3356
4179
|
// quality card absorbs the difference instead of leaving a gap under it; a narrow
|
|
3357
4180
|
// pane collapses the three into one column.
|
|
3358
|
-
(0,
|
|
4181
|
+
(0, primitives_13.h)('div', {
|
|
3359
4182
|
style: {
|
|
3360
4183
|
gridColumn: '1 / -1',
|
|
3361
4184
|
display: 'grid',
|
|
@@ -3363,14 +4186,14 @@ define("views/panel", ["require", "exports", "react", "@deepseek-ai/dsh-client-u
|
|
|
3363
4186
|
gap: '8px',
|
|
3364
4187
|
alignItems: 'stretch',
|
|
3365
4188
|
},
|
|
3366
|
-
}, (0,
|
|
4189
|
+
}, (0, primitives_13.h)(primitives_13.Section, {
|
|
3367
4190
|
// The left column of the split region: the one card that answers "what is in
|
|
3368
4191
|
// this index". The right column stacks the two compact readings beside it.
|
|
3369
4192
|
title: '索引概览',
|
|
3370
4193
|
meta: report.graph === null || report.graph === undefined
|
|
3371
4194
|
? null
|
|
3372
|
-
: (0,
|
|
3373
|
-
}, (0,
|
|
4195
|
+
: (0, primitives_13.h)('span', { style: { ...primitives_13.MUTED, fontSize: '12px' } }, `${(0, format_11.fixed)(report.graph.edgesPerNode, 2)} 边/符号`),
|
|
4196
|
+
}, (0, primitives_13.h)(overview_1.MemoizedOverviewBody, { report, totals, cap, ratio })), (0, primitives_13.h)('div',
|
|
3374
4197
|
// The column stretches to the overview's height; the quality card below takes
|
|
3375
4198
|
// whatever the language card leaves, so the right side ends level with the left.
|
|
3376
4199
|
{ style: { display: 'flex', flexDirection: 'column', gap: '8px', minHeight: 0 } },
|
|
@@ -3379,36 +4202,39 @@ define("views/panel", ["require", "exports", "react", "@deepseek-ai/dsh-client-u
|
|
|
3379
4202
|
// repository" answers side by side. The grid gives them one row on a wide pane and
|
|
3380
4203
|
// one column in a narrow sidebar, which is the platform's own behaviour.
|
|
3381
4204
|
languages !== null && languages.rows.length > 0
|
|
3382
|
-
? (0,
|
|
4205
|
+
? (0, primitives_13.h)('div', { style: { flex: '0 0 auto', minWidth: 0 } }, (0, primitives_13.h)(languages_1.MemoizedLanguageSection, { languages }))
|
|
3383
4206
|
: null,
|
|
3384
4207
|
// Fills the rest of the column: at the overview's height it ends level with the left
|
|
3385
4208
|
// card, and its body scrolls rather than growing past it.
|
|
3386
|
-
(0,
|
|
4209
|
+
(0, primitives_13.h)(primitives_13.Section, {
|
|
3387
4210
|
title: '解析质量与溯源',
|
|
3388
4211
|
style: { flex: '1 1 auto', minHeight: 0 },
|
|
3389
4212
|
bodyStyle: { maxHeight: 'none', flex: '1 1 auto', minHeight: 0 },
|
|
3390
4213
|
scroll: true,
|
|
3391
|
-
meta: (0,
|
|
3392
|
-
}, (0,
|
|
4214
|
+
meta: (0, primitives_13.h)('span', { style: { ...primitives_13.MUTED, fontSize: '11px' } }, (0, capabilities_2.provenanceCollapsed)(report.graphQuality ?? null)),
|
|
4215
|
+
}, (0, primitives_13.h)(capabilities_2.ProvenanceBlock, {
|
|
3393
4216
|
quality: report.graphQuality ?? null,
|
|
3394
4217
|
provenance: report.provenance ?? null,
|
|
4218
|
+
compatibility: report.compatibility ?? null,
|
|
3395
4219
|
})))),
|
|
3396
4220
|
// The graph is the index's own shape, not a derived statistic: it belongs directly
|
|
3397
4221
|
// under the overview, before the caveats that explain its omissions.
|
|
3398
4222
|
graph === undefined || !indexed
|
|
3399
4223
|
? null
|
|
3400
|
-
: (0,
|
|
4224
|
+
: (0, primitives_13.h)('div', { ref: graphCardRef, style: { gridColumn: '1 / -1', minWidth: 0 } }, (0, primitives_13.h)(primitives_13.Section, {
|
|
3401
4225
|
wide: true,
|
|
3402
4226
|
title: '图视图',
|
|
3403
|
-
meta: (0,
|
|
4227
|
+
meta: (0, primitives_13.h)('span', { style: { ...primitives_13.MUTED, fontSize: '11px' } }, graph.projection === null
|
|
3404
4228
|
? graph.routeState === 'missing'
|
|
3405
4229
|
? '宿主未挂载图路由'
|
|
3406
4230
|
: graph.loading
|
|
3407
4231
|
? '读取中…'
|
|
3408
|
-
:
|
|
4232
|
+
: graphVisible
|
|
4233
|
+
? '暂无图数据'
|
|
4234
|
+
: '滚动到此处加载'
|
|
3409
4235
|
: `${graph.projection.view === 'communities' ? '社区聚合' : '单个社区'} · ` +
|
|
3410
4236
|
`上限 ${graph.projection.budget.nodes} 节点 / ${graph.projection.budget.edges} 边`),
|
|
3411
|
-
}, (0,
|
|
4237
|
+
}, (0, primitives_13.h)(graph_1.GraphSection, { state: graph }))),
|
|
3412
4238
|
// Caveats, in one card, sharing a row with the provenance card.
|
|
3413
4239
|
//
|
|
3414
4240
|
// `notable` and `contextWarnings` were two cards — one amber, one an untitled
|
|
@@ -3417,13 +4243,13 @@ define("views/panel", ["require", "exports", "react", "@deepseek-ai/dsh-client-u
|
|
|
3417
4243
|
// check two boxes to answer one question, and the untitled one had no heading
|
|
3418
4244
|
// to say what it was.
|
|
3419
4245
|
notable.length > 0 || contextWarnings.length > 0
|
|
3420
|
-
? (0,
|
|
4246
|
+
? (0, primitives_13.h)(primitives_13.Section, {
|
|
3421
4247
|
// Full width: a list of warnings would otherwise pair with whatever card
|
|
3422
4248
|
// happened to follow and leave a hole in the other column.
|
|
3423
4249
|
wide: true,
|
|
3424
4250
|
title: `需要注意 · ${notable.length + contextWarnings.length}`,
|
|
3425
4251
|
scroll: 300,
|
|
3426
|
-
}, notable.map((warning, index) => (0,
|
|
4252
|
+
}, notable.map((warning, index) => (0, primitives_13.h)('div', {
|
|
3427
4253
|
key: `${warning.code}-${index}`,
|
|
3428
4254
|
style: {
|
|
3429
4255
|
display: 'flex',
|
|
@@ -3434,45 +4260,45 @@ define("views/panel", ["require", "exports", "react", "@deepseek-ai/dsh-client-u
|
|
|
3434
4260
|
background: 'color-mix(in srgb, #d9a441 9%, transparent)',
|
|
3435
4261
|
borderRadius: '6px',
|
|
3436
4262
|
},
|
|
3437
|
-
}, (0,
|
|
4263
|
+
}, (0, primitives_13.h)(primitives_13.StateDot, { state: 'warning' }), (0, primitives_13.h)('span', { style: { minWidth: 0 } }, warning.message))),
|
|
3438
4264
|
// The milder ones stay quiet rows: giving every item an amber box would
|
|
3439
4265
|
// make a stale-index count look as urgent as a truncated activity window.
|
|
3440
4266
|
contextWarnings.length === 0
|
|
3441
4267
|
? null
|
|
3442
|
-
: (0,
|
|
4268
|
+
: (0, primitives_13.h)('div', {
|
|
3443
4269
|
style: {
|
|
3444
4270
|
display: 'flex',
|
|
3445
4271
|
flexDirection: 'column',
|
|
3446
4272
|
gap: '4px',
|
|
3447
|
-
...
|
|
3448
|
-
...(notable.length > 0 ? { borderTop:
|
|
4273
|
+
...primitives_13.MUTED,
|
|
4274
|
+
...(notable.length > 0 ? { borderTop: primitives_13.HAIRLINE, paddingTop: '8px' } : {}),
|
|
3449
4275
|
},
|
|
3450
|
-
}, contextWarnings.map((warning, index) => (0,
|
|
4276
|
+
}, contextWarnings.map((warning, index) => (0, primitives_13.h)('div', { key: `${warning.code}-${index}` }, `· ${warning.message}`))))
|
|
3451
4277
|
: null,
|
|
3452
4278
|
// Wide: file paths must keep their tails.
|
|
3453
4279
|
largeFiles.length > 0
|
|
3454
|
-
? (0,
|
|
4280
|
+
? (0, primitives_13.h)(primitives_13.Section, {
|
|
3455
4281
|
wide: true,
|
|
3456
|
-
title: `被跳过的超大文件 · ${largeFiles.length} 个 · ${(0,
|
|
4282
|
+
title: `被跳过的超大文件 · ${largeFiles.length} 个 · ${(0, format_11.fileSize)(totals.largeBytes)}`,
|
|
3457
4283
|
// A list of up to 40 paths would push every card below it off screen; the
|
|
3458
4284
|
// platform's own panels cap long lists and scroll inside the card.
|
|
3459
4285
|
scroll: 280,
|
|
3460
|
-
}, (0,
|
|
3461
|
-
? (0,
|
|
4286
|
+
}, (0, primitives_13.h)(large_files_1.LargeFileList, { files: largeFiles.slice(0, 40) }), largeFiles.length > 40
|
|
4287
|
+
? (0, primitives_13.h)('div', { style: { ...primitives_13.MUTED, marginTop: '6px' } }, `…另有 ${largeFiles.length - 40} 个未显示`)
|
|
3462
4288
|
: null)
|
|
3463
4289
|
: null, report.activity === undefined || report.activity === null
|
|
3464
4290
|
? null
|
|
3465
|
-
: (0,
|
|
4291
|
+
: (0, primitives_13.h)(primitives_13.Section, {
|
|
3466
4292
|
wide: true,
|
|
3467
4293
|
// The header names the window the reader is actually looking at (the
|
|
3468
4294
|
// timeline's 7-day default), not the 90-day fetch window — the two used to
|
|
3469
4295
|
// disagree, so the card read "近 90 天" above a list of seven days.
|
|
3470
4296
|
title: `Git 活动 · 近 ${activityDays} 天`,
|
|
3471
|
-
meta: (0,
|
|
4297
|
+
meta: (0, primitives_13.h)('span', { style: { display: 'inline-flex', alignItems: 'center', gap: '6px', ...primitives_13.MUTED, fontSize: '11px' } },
|
|
3472
4298
|
// The range control belongs to the card, not to the folded timeline: it
|
|
3473
4299
|
// scopes every number on the card, and it should be reachable without
|
|
3474
4300
|
// opening the list.
|
|
3475
|
-
(0,
|
|
4301
|
+
(0, primitives_13.h)('select', {
|
|
3476
4302
|
value: String(activityDays),
|
|
3477
4303
|
'aria-label': 'Git 活动窗口',
|
|
3478
4304
|
onChange: (event) => setActivityDays(Number(event.target.value)),
|
|
@@ -3480,17 +4306,17 @@ define("views/panel", ["require", "exports", "react", "@deepseek-ai/dsh-client-u
|
|
|
3480
4306
|
font: 'inherit',
|
|
3481
4307
|
fontSize: '11px',
|
|
3482
4308
|
padding: '1px 4px',
|
|
3483
|
-
border:
|
|
4309
|
+
border: primitives_13.HAIRLINE,
|
|
3484
4310
|
borderRadius: '4px',
|
|
3485
|
-
background:
|
|
4311
|
+
background: primitives_13.SOFT_BG,
|
|
3486
4312
|
color: 'inherit',
|
|
3487
4313
|
},
|
|
3488
|
-
}, [7, 14, 30].map((days) => (0,
|
|
4314
|
+
}, [7, 14, 30].map((days) => (0, primitives_13.h)('option', { key: days, value: String(days) }, `${days} 天`))), (0, primitives_13.h)(primitives_13.StateDot, {
|
|
3489
4315
|
state: (0, activity_1.activityView)(report.activity)?.verdicts.find((v) => v.key === 'missing')?.tone ?? 'done',
|
|
3490
4316
|
}), (0, activity_1.activityCollapsed)(report.activity)),
|
|
3491
4317
|
// Timeline + cross-tab + baseline is the longest card in the panel.
|
|
3492
4318
|
scroll: 560,
|
|
3493
|
-
}, (0,
|
|
4319
|
+
}, (0, primitives_13.h)(activity_1.MemoizedGitActivitySection, {
|
|
3494
4320
|
activity: report.activity,
|
|
3495
4321
|
projectPath,
|
|
3496
4322
|
days: activityDays,
|
|
@@ -3498,16 +4324,16 @@ define("views/panel", ["require", "exports", "react", "@deepseek-ai/dsh-client-u
|
|
|
3498
4324
|
})),
|
|
3499
4325
|
// Wide, and always open: the commands are the action this panel offers.
|
|
3500
4326
|
maintenanceSection !== null
|
|
3501
|
-
? (0,
|
|
4327
|
+
? (0, primitives_13.h)(primitives_13.Section, {
|
|
3502
4328
|
wide: true,
|
|
3503
4329
|
title: '维护命令',
|
|
3504
|
-
meta: (0,
|
|
4330
|
+
meta: (0, primitives_13.h)('span', { style: { ...primitives_13.MUTED, fontSize: '12px' } }, `${maintenanceSection.commands?.length ?? 0} 条 · ` +
|
|
3505
4331
|
(maintenanceSection.supportsForce === true
|
|
3506
4332
|
? '均可直接执行,也可复制到会话'
|
|
3507
4333
|
: '重启 DSH 后可执行;当前仅可复制') +
|
|
3508
4334
|
'(沙箱与提权由 DSH 处理)'),
|
|
3509
4335
|
scroll: 360,
|
|
3510
|
-
}, (0,
|
|
4336
|
+
}, (0, primitives_13.h)(maintenance_1.MaintenanceSection, { maintenance: maintenanceSection, projectPath, indexed, run, runError, delta, deltaError, deltaRoute, onStartRun, onCancelRun }))
|
|
3511
4337
|
: null);
|
|
3512
4338
|
}
|
|
3513
4339
|
/**
|
|
@@ -3604,7 +4430,11 @@ define("views/panel", ["require", "exports", "react", "@deepseek-ai/dsh-client-u
|
|
|
3604
4430
|
*/
|
|
3605
4431
|
const [graphRoute, setGraphRoute] = React.useState('unknown');
|
|
3606
4432
|
const [graphScope, setGraphScope] = React.useState(null);
|
|
4433
|
+
const [graphMode, setGraphMode] = React.useState('communities');
|
|
3607
4434
|
const [graphReload, setGraphReload] = React.useState(0);
|
|
4435
|
+
/** Set once the graph card has entered the viewport; keeps the first fetch lazy. */
|
|
4436
|
+
const [graphVisible, setGraphVisible] = React.useState(false);
|
|
4437
|
+
const onGraphNear = React.useCallback(() => setGraphVisible(true), []);
|
|
3608
4438
|
/** Which project the current projection belongs to, so a switch can clear it. */
|
|
3609
4439
|
const graphRepoRef = React.useRef(null);
|
|
3610
4440
|
const running = run?.state === 'running' && runProject === projectPath;
|
|
@@ -4179,6 +5009,7 @@ define("views/panel", ["require", "exports", "react", "@deepseek-ai/dsh-client-u
|
|
|
4179
5009
|
setGraphError(null);
|
|
4180
5010
|
setGraphLoading(false);
|
|
4181
5011
|
setGraphScope(null);
|
|
5012
|
+
setGraphMode('communities');
|
|
4182
5013
|
return undefined;
|
|
4183
5014
|
}
|
|
4184
5015
|
const projectChanged = graphRepoRef.current !== projectPath;
|
|
@@ -4190,6 +5021,7 @@ define("views/panel", ["require", "exports", "react", "@deepseek-ai/dsh-client-u
|
|
|
4190
5021
|
// next render fetch the overview instead of asking the new project for an id it
|
|
4191
5022
|
// cannot have.
|
|
4192
5023
|
setGraphScope(null);
|
|
5024
|
+
setGraphMode('communities');
|
|
4193
5025
|
}
|
|
4194
5026
|
if (!graphAvailable) {
|
|
4195
5027
|
setGraphProjection(null);
|
|
@@ -4197,6 +5029,10 @@ define("views/panel", ["require", "exports", "react", "@deepseek-ai/dsh-client-u
|
|
|
4197
5029
|
setGraphLoading(false);
|
|
4198
5030
|
return undefined;
|
|
4199
5031
|
}
|
|
5032
|
+
// The visibility effect flips this once the card is near the viewport; until
|
|
5033
|
+
// then the card keeps its empty state instead of issuing a request.
|
|
5034
|
+
if (!graphVisible)
|
|
5035
|
+
return undefined;
|
|
4200
5036
|
if (projectChanged && graphScope !== null) {
|
|
4201
5037
|
// The setGraphScope(null) above re-runs this effect; do not also fire a request
|
|
4202
5038
|
// for the old scope against the new project.
|
|
@@ -4206,7 +5042,7 @@ define("views/panel", ["require", "exports", "react", "@deepseek-ai/dsh-client-u
|
|
|
4206
5042
|
setGraphLoading(true);
|
|
4207
5043
|
setGraphError(null);
|
|
4208
5044
|
const effectiveScope = projectChanged ? null : graphScope;
|
|
4209
|
-
const view = effectiveScope === null ?
|
|
5045
|
+
const view = effectiveScope === null ? graphMode : 'community';
|
|
4210
5046
|
const suffix = effectiveScope === null ? '' : `&id=${encodeURIComponent(effectiveScope)}`;
|
|
4211
5047
|
void (async () => {
|
|
4212
5048
|
try {
|
|
@@ -4245,7 +5081,7 @@ define("views/panel", ["require", "exports", "react", "@deepseek-ai/dsh-client-u
|
|
|
4245
5081
|
}
|
|
4246
5082
|
})();
|
|
4247
5083
|
return () => controller.abort();
|
|
4248
|
-
}, [projectPath, reloadToken, graphScope, graphReload, graphAvailable]);
|
|
5084
|
+
}, [projectPath, reloadToken, graphScope, graphMode, graphReload, graphAvailable, graphVisible]);
|
|
4249
5085
|
/**
|
|
4250
5086
|
* Open a symbol node, with the same fallbacks the search hits use.
|
|
4251
5087
|
*
|
|
@@ -4293,7 +5129,7 @@ define("views/panel", ["require", "exports", "react", "@deepseek-ai/dsh-client-u
|
|
|
4293
5129
|
}, [workspacePath]);
|
|
4294
5130
|
const visibleRun = runProject === projectPath ? run : null;
|
|
4295
5131
|
const visibleRunError = runProject === projectPath ? runError : null;
|
|
4296
|
-
return (0,
|
|
5132
|
+
return (0, primitives_13.h)(CoverageView, {
|
|
4297
5133
|
report,
|
|
4298
5134
|
maintenance,
|
|
4299
5135
|
projectPath,
|
|
@@ -4356,17 +5192,71 @@ define("views/panel", ["require", "exports", "react", "@deepseek-ai/dsh-client-u
|
|
|
4356
5192
|
error: graphError,
|
|
4357
5193
|
routeState: graphRoute,
|
|
4358
5194
|
scope: graphScope,
|
|
4359
|
-
onOpenCommunity: (id) =>
|
|
4360
|
-
|
|
5195
|
+
onOpenCommunity: (id) => {
|
|
5196
|
+
setGraphScope(id);
|
|
5197
|
+
setGraphMode('communities');
|
|
5198
|
+
},
|
|
5199
|
+
onOpenSymbols: () => {
|
|
5200
|
+
setGraphScope(null);
|
|
5201
|
+
setGraphMode('symbols');
|
|
5202
|
+
},
|
|
5203
|
+
onBack: () => {
|
|
5204
|
+
setGraphScope(null);
|
|
5205
|
+
setGraphMode('communities');
|
|
5206
|
+
},
|
|
4361
5207
|
onReload: () => setGraphReload((n) => n + 1),
|
|
4362
5208
|
onOpenSymbol: openGraphNode,
|
|
4363
5209
|
},
|
|
5210
|
+
onGraphNear,
|
|
5211
|
+
graphVisible,
|
|
4364
5212
|
});
|
|
4365
5213
|
}
|
|
4366
5214
|
/** The tab chip's live title. */
|
|
4367
5215
|
function CoverageTitle() {
|
|
4368
|
-
return (0,
|
|
5216
|
+
return (0, primitives_13.h)('span', null, 'GitNexus');
|
|
5217
|
+
}
|
|
5218
|
+
});
|
|
5219
|
+
define("views/boundary", ["require", "exports", "react", "primitives"], function (require, exports, React, primitives_14) {
|
|
5220
|
+
"use strict";
|
|
5221
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
5222
|
+
exports.PanelBoundary = void 0;
|
|
5223
|
+
/**
|
|
5224
|
+
* Catch a render failure inside the sidebar panel.
|
|
5225
|
+
*
|
|
5226
|
+
* A thrown component currently takes the whole right pane down to a black
|
|
5227
|
+
* rectangle, which is indistinguishable from "the plugin did not load". This
|
|
5228
|
+
* boundary keeps the pane usable and puts the actual error on screen so the
|
|
5229
|
+
* failure can be reported instead of guessed at.
|
|
5230
|
+
*/
|
|
5231
|
+
class PanelBoundary extends React.Component {
|
|
5232
|
+
state = { error: null, componentStack: '' };
|
|
5233
|
+
static getDerivedStateFromError(error) {
|
|
5234
|
+
return { error, componentStack: '' };
|
|
5235
|
+
}
|
|
5236
|
+
componentDidCatch(error, info) {
|
|
5237
|
+
// Also leave a trail for a terminal/journal reader when the panel is embedded.
|
|
5238
|
+
// eslint-disable-next-line no-console
|
|
5239
|
+
console.error('gitnexus panel render failed', error, info.componentStack);
|
|
5240
|
+
this.setState({ componentStack: info.componentStack ?? '' });
|
|
5241
|
+
}
|
|
5242
|
+
render() {
|
|
5243
|
+
const { error, componentStack } = this.state;
|
|
5244
|
+
if (error === null)
|
|
5245
|
+
return this.props.children;
|
|
5246
|
+
return (0, primitives_14.h)('div', {
|
|
5247
|
+
style: {
|
|
5248
|
+
padding: '12px',
|
|
5249
|
+
color: 'var(--dsw-alias-state-error-primary, #f38ba8)',
|
|
5250
|
+
fontFamily: 'ui-monospace, SFMono-Regular, Consolas, monospace',
|
|
5251
|
+
fontSize: '11px',
|
|
5252
|
+
lineHeight: 1.5,
|
|
5253
|
+
whiteSpace: 'pre-wrap',
|
|
5254
|
+
overflow: 'auto',
|
|
5255
|
+
},
|
|
5256
|
+
}, (0, primitives_14.h)('div', { style: { fontWeight: 700, marginBottom: '6px' } }, 'GitNexus 面板渲染失败'), (0, primitives_14.h)('div', { style: primitives_14.MUTED }, error.message), componentStack === '' ? null : (0, primitives_14.h)('div', { style: { ...primitives_14.MUTED, marginTop: '8px' } }, componentStack), error.stack === undefined ? null : (0, primitives_14.h)('div', { style: { ...primitives_14.MUTED, marginTop: '8px' } }, error.stack));
|
|
5257
|
+
}
|
|
4369
5258
|
}
|
|
5259
|
+
exports.PanelBoundary = PanelBoundary;
|
|
4370
5260
|
});
|
|
4371
5261
|
/**
|
|
4372
5262
|
* dsh-gitnexus-plus — browser half.
|
|
@@ -4407,7 +5297,7 @@ define("views/panel", ["require", "exports", "react", "@deepseek-ai/dsh-client-u
|
|
|
4407
5297
|
* index actually represent?", the warning block says what was dropped, and the
|
|
4408
5298
|
* file list says whether what was dropped is source or build output.
|
|
4409
5299
|
*/
|
|
4410
|
-
define("index", ["require", "exports", "format", "navigation", "primitives", "sections/activity", "highlight", "sections/graph", "sections/capabilities", "sections/file-history", "sections/languages", "sections/large-files", "sections/maintenance", "views/panel"], function (require, exports,
|
|
5300
|
+
define("index", ["require", "exports", "format", "react", "navigation", "primitives", "sections/activity", "highlight", "sections/graph", "sections/graph3d", "sections/capabilities", "sections/file-history", "sections/languages", "sections/large-files", "sections/maintenance", "sections/workspace", "views/panel", "views/boundary"], function (require, exports, format_12, React, navigation_4, primitives_15, activity_2, highlight_2, graph_2, graph3d_2, capabilities_3, file_history_2, languages_2, large_files_2, maintenance_2, workspace_2, panel_1, boundary_1) {
|
|
4411
5301
|
"use strict";
|
|
4412
5302
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4413
5303
|
exports.__internals = exports.inject = void 0;
|
|
@@ -4472,8 +5362,26 @@ define("index", ["require", "exports", "format", "navigation", "primitives", "se
|
|
|
4472
5362
|
(0, navigation_4.bindSidebarRight)(svcCtx.sidebarRight);
|
|
4473
5363
|
});
|
|
4474
5364
|
});
|
|
5365
|
+
/**
|
|
5366
|
+
* Wrapping the whole panel keeps a render failure inside the pane: without it
|
|
5367
|
+
* React unmounts the subtree and the sidebar is simply black, which is
|
|
5368
|
+
* indistinguishable from the plugin never loading.
|
|
5369
|
+
*/
|
|
5370
|
+
const requiredComponents = [
|
|
5371
|
+
['PanelBoundary', boundary_1.PanelBoundary],
|
|
5372
|
+
['CoveragePanel', panel_1.CoveragePanel],
|
|
5373
|
+
['WorkspaceOverview', workspace_2.WorkspaceOverview],
|
|
5374
|
+
['Graph3DView', graph3d_2.Graph3DView],
|
|
5375
|
+
['ProvenanceBlock', capabilities_3.ProvenanceBlock],
|
|
5376
|
+
];
|
|
5377
|
+
const missingComponents = requiredComponents
|
|
5378
|
+
.filter(([, component]) => typeof component !== 'function')
|
|
5379
|
+
.map(([name]) => name);
|
|
5380
|
+
const CoveragePanelWithBoundary = (props) => missingComponents.length === 0
|
|
5381
|
+
? React.createElement(boundary_1.PanelBoundary, null, React.createElement(panel_1.CoveragePanel, props))
|
|
5382
|
+
: React.createElement('div', { style: { padding: 12, color: '#fff', background: '#3a1111' } }, 'GitNexus 缺少组件: ' + missingComponents.join(', '));
|
|
4475
5383
|
safe('sidebar.right.pane.tab', () => {
|
|
4476
|
-
ctx.slots.inject('sidebar.right.pane.tab', () => ctx.slots.register({ name: 'sidebar.right.pane.tab', key: PLUGIN_ID },
|
|
5384
|
+
ctx.slots.inject('sidebar.right.pane.tab', () => ctx.slots.register({ name: 'sidebar.right.pane.tab', key: PLUGIN_ID }, CoveragePanelWithBoundary));
|
|
4477
5385
|
});
|
|
4478
5386
|
safe('sidebar.right.pane.tab.title', () => {
|
|
4479
5387
|
ctx.slots.inject('sidebar.right.pane.tab.title', () => ctx.slots.register({ name: 'sidebar.right.pane.tab.title', key: PLUGIN_ID }, panel_1.CoverageTitle));
|
|
@@ -4485,22 +5393,25 @@ define("index", ["require", "exports", "format", "navigation", "primitives", "se
|
|
|
4485
5393
|
TAB_KIND,
|
|
4486
5394
|
routeUrl: navigation_4.routeUrl,
|
|
4487
5395
|
fileResourceAddress: navigation_4.fileResourceAddress,
|
|
4488
|
-
formatBytes:
|
|
4489
|
-
fileSize:
|
|
4490
|
-
percent:
|
|
4491
|
-
stackSegments:
|
|
4492
|
-
splitWarnings:
|
|
4493
|
-
summaryLine:
|
|
4494
|
-
coverageRatio:
|
|
4495
|
-
healthState:
|
|
4496
|
-
stalenessView:
|
|
4497
|
-
capabilityRows:
|
|
4498
|
-
graphQualitySummary:
|
|
5396
|
+
formatBytes: format_12.formatBytes,
|
|
5397
|
+
fileSize: format_12.fileSize,
|
|
5398
|
+
percent: format_12.percent,
|
|
5399
|
+
stackSegments: format_12.stackSegments,
|
|
5400
|
+
splitWarnings: format_12.splitWarnings,
|
|
5401
|
+
summaryLine: format_12.summaryLine,
|
|
5402
|
+
coverageRatio: format_12.coverageRatio,
|
|
5403
|
+
healthState: format_12.healthState,
|
|
5404
|
+
stalenessView: format_12.stalenessView,
|
|
5405
|
+
capabilityRows: format_12.capabilityRows,
|
|
5406
|
+
graphQualitySummary: format_12.graphQualitySummary,
|
|
4499
5407
|
activityView: activity_2.activityView,
|
|
4500
5408
|
highlightLine: highlight_2.highlightLine,
|
|
4501
5409
|
languageOf: highlight_2.languageOf,
|
|
4502
5410
|
communityColors: graph_2.communityColors,
|
|
4503
|
-
|
|
5411
|
+
buildGraph3DLayout: graph3d_2.buildGraph3DLayout,
|
|
5412
|
+
graph3DPath: graph3d_2.graph3DPath,
|
|
5413
|
+
Graph3DView: graph3d_2.Graph3DView,
|
|
5414
|
+
compactNumber: format_12.compactNumber,
|
|
4504
5415
|
useProjectPath: navigation_4.useProjectPath,
|
|
4505
5416
|
contextDiagnostic: navigation_4.contextDiagnostic,
|
|
4506
5417
|
chooseProject: panel_1.chooseProject,
|
|
@@ -4510,17 +5421,19 @@ define("index", ["require", "exports", "format", "navigation", "primitives", "se
|
|
|
4510
5421
|
LanguageBar: languages_2.LanguageBar,
|
|
4511
5422
|
LanguageSection: languages_2.LanguageSection,
|
|
4512
5423
|
LargeFileList: large_files_2.LargeFileList,
|
|
4513
|
-
Ring:
|
|
4514
|
-
MetricGrid:
|
|
4515
|
-
Histogram:
|
|
5424
|
+
Ring: primitives_15.Ring,
|
|
5425
|
+
MetricGrid: primitives_15.MetricGrid,
|
|
5426
|
+
Histogram: primitives_15.Histogram,
|
|
4516
5427
|
GitActivitySection: activity_2.GitActivitySection,
|
|
4517
5428
|
FileHistorySection: file_history_2.FileHistorySection,
|
|
4518
5429
|
ProvenanceBlock: capabilities_3.ProvenanceBlock,
|
|
4519
5430
|
MaintenanceSection: maintenance_2.MaintenanceSection,
|
|
5431
|
+
WorkspaceOverview: workspace_2.WorkspaceOverview,
|
|
5432
|
+
aggregateProjects: workspace_2.aggregateProjects,
|
|
4520
5433
|
CapabilityStrip: capabilities_3.CapabilityStrip,
|
|
4521
|
-
Section:
|
|
4522
|
-
classifyFileType:
|
|
4523
|
-
IconRefreshOutlineRegular:
|
|
5434
|
+
Section: primitives_15.Section,
|
|
5435
|
+
classifyFileType: primitives_15.classifyFileType,
|
|
5436
|
+
IconRefreshOutlineRegular: primitives_15.IconRefreshOutlineRegular,
|
|
4524
5437
|
};
|
|
4525
5438
|
});
|
|
4526
5439
|
|