dsh-vscode-mode 0.1.25 → 0.1.26
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/lib/client.js +312 -2
- package/lib/client.js.map +1 -1
- package/lib/index.js +19 -1
- package/lib/index.js.map +1 -1
- package/package.json +1 -1
- package/src/client/sidebar/panels/FileExplorer.ts +30 -0
- package/src/client/state/explorerCache.ts +112 -0
- package/src/client/state/viewStateCache.ts +119 -0
- package/src/client/ui/EditorView.ts +68 -3
- package/src/routes.ts +18 -2
package/lib/client.js
CHANGED
|
@@ -1603,6 +1603,109 @@ window.__ModuleLoader__.load({
|
|
|
1603
1603
|
}
|
|
1604
1604
|
}
|
|
1605
1605
|
//#endregion
|
|
1606
|
+
//#region src/client/state/viewStateCache.ts
|
|
1607
|
+
/**
|
|
1608
|
+
* dsh-vscode-mode client — 编辑器视图状态持久化(纯函数,可单测)。
|
|
1609
|
+
* 对齐 VSCode workbench.editor.restoreViewState:恢复每个文件的光标/滚动/折叠位置。
|
|
1610
|
+
* 视图状态 = Monaco editor.saveViewState() 返回值(JSON 可序列化),按 path 存储。
|
|
1611
|
+
* 作者 ddj 2026-08-28
|
|
1612
|
+
*/
|
|
1613
|
+
/** localStorage 键前缀(按会话隔离)。 */
|
|
1614
|
+
const KEY_PREFIX$1 = "edrv.cache.viewstate.v1.";
|
|
1615
|
+
/**
|
|
1616
|
+
* 判断对象是否为合法的 Monaco view state(保守形状校验,非深校验)。
|
|
1617
|
+
* @author ddj 2026年08月28号
|
|
1618
|
+
* @param value 待校验对象
|
|
1619
|
+
* @returns 是否可接受
|
|
1620
|
+
*/
|
|
1621
|
+
function isViewStateLike(value) {
|
|
1622
|
+
if (!value || typeof value !== "object") return false;
|
|
1623
|
+
const v = value;
|
|
1624
|
+
return Array.isArray(v.cursorState) || typeof v.viewState === "object" || Array.isArray(v.contributionsState);
|
|
1625
|
+
}
|
|
1626
|
+
/**
|
|
1627
|
+
* 序列化视图状态映射(path → viewState)。
|
|
1628
|
+
* @author ddj 2026年08月28号
|
|
1629
|
+
* @param states path → viewState 映射
|
|
1630
|
+
* @returns JSON 字符串
|
|
1631
|
+
*/
|
|
1632
|
+
function serializeViewStates(states) {
|
|
1633
|
+
return JSON.stringify({
|
|
1634
|
+
v: 1,
|
|
1635
|
+
states
|
|
1636
|
+
});
|
|
1637
|
+
}
|
|
1638
|
+
/**
|
|
1639
|
+
* 解析视图状态文本;损坏/格式不符 → null。
|
|
1640
|
+
* @author ddj 2026年08月28号
|
|
1641
|
+
* @param text localStorage 原文
|
|
1642
|
+
* @returns path → viewState 映射,非法返回 null
|
|
1643
|
+
*/
|
|
1644
|
+
function parseViewStates(text) {
|
|
1645
|
+
if (!text) return null;
|
|
1646
|
+
try {
|
|
1647
|
+
const data = JSON.parse(text);
|
|
1648
|
+
if (!data || typeof data !== "object") return null;
|
|
1649
|
+
const states = data.states;
|
|
1650
|
+
if (!states || typeof states !== "object" || Array.isArray(states)) return null;
|
|
1651
|
+
const out = {};
|
|
1652
|
+
for (const [path, state] of Object.entries(states)) {
|
|
1653
|
+
if (typeof path !== "string" || !path) continue;
|
|
1654
|
+
if (!isViewStateLike(state)) continue;
|
|
1655
|
+
out[path] = state;
|
|
1656
|
+
}
|
|
1657
|
+
return out;
|
|
1658
|
+
} catch (error) {
|
|
1659
|
+
return null;
|
|
1660
|
+
}
|
|
1661
|
+
}
|
|
1662
|
+
/**
|
|
1663
|
+
* 把新视图状态并入既有映射:同 path 覆盖、按写入顺序裁剪到容量上限。
|
|
1664
|
+
* @author ddj 2026年08月28号
|
|
1665
|
+
* @param states 既有映射
|
|
1666
|
+
* @param path 文件路径
|
|
1667
|
+
* @param state 视图状态
|
|
1668
|
+
* @returns 新映射(容量裁剪后)
|
|
1669
|
+
*/
|
|
1670
|
+
function upsertViewState(states, path, state) {
|
|
1671
|
+
if (!path || !isViewStateLike(state)) return states;
|
|
1672
|
+
try {
|
|
1673
|
+
if (JSON.stringify(state).length > 65536) return states;
|
|
1674
|
+
} catch (error) {
|
|
1675
|
+
return states;
|
|
1676
|
+
}
|
|
1677
|
+
const next = Object.assign({}, states);
|
|
1678
|
+
delete next[path];
|
|
1679
|
+
next[path] = state;
|
|
1680
|
+
const keys = Object.keys(next);
|
|
1681
|
+
if (keys.length > 50) for (const k of keys.slice(0, keys.length - 50)) delete next[k];
|
|
1682
|
+
return next;
|
|
1683
|
+
}
|
|
1684
|
+
/**
|
|
1685
|
+
* 读取某会话的视图状态缓存(localStorage 不可用 → 空映射)。
|
|
1686
|
+
* @author ddj 2026年08月28号
|
|
1687
|
+
* @param sessionId 会话 id
|
|
1688
|
+
* @returns path → viewState 映射
|
|
1689
|
+
*/
|
|
1690
|
+
function viewStatesLoad(sessionId) {
|
|
1691
|
+
try {
|
|
1692
|
+
return parseViewStates(window.localStorage.getItem(KEY_PREFIX$1 + String(sessionId))) ?? {};
|
|
1693
|
+
} catch (error) {
|
|
1694
|
+
return {};
|
|
1695
|
+
}
|
|
1696
|
+
}
|
|
1697
|
+
/**
|
|
1698
|
+
* 写入某会话的视图状态缓存(损坏/配额满 → 忽略)。
|
|
1699
|
+
* @author ddj 2026年08月28号
|
|
1700
|
+
* @param sessionId 会话 id
|
|
1701
|
+
* @param states path → viewState 映射
|
|
1702
|
+
*/
|
|
1703
|
+
function viewStatesSave(sessionId, states) {
|
|
1704
|
+
try {
|
|
1705
|
+
window.localStorage.setItem(KEY_PREFIX$1 + String(sessionId), serializeViewStates(states));
|
|
1706
|
+
} catch (error) {}
|
|
1707
|
+
}
|
|
1708
|
+
//#endregion
|
|
1606
1709
|
//#region src/client/ui/EditorView.ts
|
|
1607
1710
|
/**
|
|
1608
1711
|
* dsh-vscode-mode client — EditorView:中央 VSCode 式文件编辑器(编排层)。
|
|
@@ -1663,6 +1766,7 @@ window.__ModuleLoader__.load({
|
|
|
1663
1766
|
const viewRootRef = react.default.useRef(null);
|
|
1664
1767
|
const monacoRef = react.default.useRef(null);
|
|
1665
1768
|
const modelsRef = react.default.useRef(/* @__PURE__ */ new Map());
|
|
1769
|
+
const viewStatesRef = react.default.useRef({});
|
|
1666
1770
|
const saveTimerRef = react.default.useRef(null);
|
|
1667
1771
|
const loadSeqRef = react.default.useRef(0);
|
|
1668
1772
|
const programmaticRef = react.default.useRef(false);
|
|
@@ -1714,8 +1818,42 @@ window.__ModuleLoader__.load({
|
|
|
1714
1818
|
});
|
|
1715
1819
|
if (select) setActive(path);
|
|
1716
1820
|
};
|
|
1821
|
+
/**
|
|
1822
|
+
* 保存当前活动文件的视图状态(光标/滚动/折叠)到会话缓存。
|
|
1823
|
+
* @author ddj 2026年08月28号
|
|
1824
|
+
* @param path 要保存的文件路径(缺省 = 当前 active)
|
|
1825
|
+
*/
|
|
1826
|
+
const saveViewState = (path) => {
|
|
1827
|
+
const ed = editorRef.current;
|
|
1828
|
+
const target = path ?? active;
|
|
1829
|
+
if (!ed || !target) return;
|
|
1830
|
+
try {
|
|
1831
|
+
const state = ed.saveViewState();
|
|
1832
|
+
if (!state) return;
|
|
1833
|
+
viewStatesRef.current = upsertViewState(viewStatesRef.current, target, state);
|
|
1834
|
+
viewStatesSave(sessionId, viewStatesRef.current);
|
|
1835
|
+
} catch (e) {}
|
|
1836
|
+
};
|
|
1837
|
+
/**
|
|
1838
|
+
* 恢复指定文件的视图状态(内容就绪、setModel 后调用;恢复后即消费删除)。
|
|
1839
|
+
* @author ddj 2026年08月28号
|
|
1840
|
+
* @param path 文件路径
|
|
1841
|
+
*/
|
|
1842
|
+
const restoreViewState = (path) => {
|
|
1843
|
+
const ed = editorRef.current;
|
|
1844
|
+
if (!ed || !path) return;
|
|
1845
|
+
const saved = viewStatesRef.current[path];
|
|
1846
|
+
if (!saved) return;
|
|
1847
|
+
const next = Object.assign({}, viewStatesRef.current);
|
|
1848
|
+
delete next[path];
|
|
1849
|
+
viewStatesRef.current = next;
|
|
1850
|
+
try {
|
|
1851
|
+
ed.restoreViewState(saved);
|
|
1852
|
+
} catch (e) {}
|
|
1853
|
+
};
|
|
1717
1854
|
const closeTab = (path) => {
|
|
1718
1855
|
flushSave();
|
|
1856
|
+
if (path === active) saveViewState(path);
|
|
1719
1857
|
setTabs((prev) => {
|
|
1720
1858
|
const idx = prev.findIndex((t) => t.path === path);
|
|
1721
1859
|
if (idx < 0) return prev;
|
|
@@ -1757,8 +1895,38 @@ window.__ModuleLoader__.load({
|
|
|
1757
1895
|
setRecords((prev) => sameRecords(prev, map) ? prev : map);
|
|
1758
1896
|
}).catch((e) => setError("list异常:" + String(e)));
|
|
1759
1897
|
};
|
|
1760
|
-
|
|
1898
|
+
/**
|
|
1899
|
+
* 加载文件内容(对齐 VSCode model 复用:会话内已打开的 model 直接秒显,
|
|
1900
|
+
* 后台静默 RPC 校验防陈旧;首次打开走原读取流程)。
|
|
1901
|
+
* @author ddj 2026年08月28号
|
|
1902
|
+
* @param path 文件路径
|
|
1903
|
+
* @param sid 会话 id
|
|
1904
|
+
* @param force 强制走 RPC(reloadFile 用,跳过 model 复用)
|
|
1905
|
+
*/
|
|
1906
|
+
const loadContent = (path, sid, force) => {
|
|
1761
1907
|
const seq = ++loadSeqRef.current;
|
|
1908
|
+
const cachedModel = force ? null : modelsRef.current.get(path);
|
|
1909
|
+
if (cachedModel) {
|
|
1910
|
+
setContent(cachedModel.getValue());
|
|
1911
|
+
setContentPath(path);
|
|
1912
|
+
setLoadStage((prev) => ({
|
|
1913
|
+
progress: Math.max(84, prev.progress),
|
|
1914
|
+
message: "文件已读取,准备创建编辑器…"
|
|
1915
|
+
}));
|
|
1916
|
+
setStatus("已加载");
|
|
1917
|
+
rpc("edrv.read", {
|
|
1918
|
+
sessionId: sid,
|
|
1919
|
+
path
|
|
1920
|
+
}).then((res) => {
|
|
1921
|
+
if (seq !== loadSeqRef.current || path !== active) return;
|
|
1922
|
+
if (res && res.ok && res.content !== cachedModel.getValue()) {
|
|
1923
|
+
saveViewState(path);
|
|
1924
|
+
setContent(res.content);
|
|
1925
|
+
setStatus("已加载");
|
|
1926
|
+
}
|
|
1927
|
+
}).catch(() => {});
|
|
1928
|
+
return;
|
|
1929
|
+
}
|
|
1762
1930
|
setLoadError(null);
|
|
1763
1931
|
setLoadStage({
|
|
1764
1932
|
progress: monaco ? 72 : 12,
|
|
@@ -1886,6 +2054,7 @@ window.__ModuleLoader__.load({
|
|
|
1886
2054
|
react.default.useEffect(() => {
|
|
1887
2055
|
if (bootRef.current || !sessionId) return;
|
|
1888
2056
|
bootRef.current = true;
|
|
2057
|
+
viewStatesRef.current = viewStatesLoad(sessionId);
|
|
1889
2058
|
try {
|
|
1890
2059
|
const raw = localStorage.getItem("edrv.editor.v2." + String(sessionId));
|
|
1891
2060
|
if (raw) {
|
|
@@ -2056,6 +2225,7 @@ window.__ModuleLoader__.load({
|
|
|
2056
2225
|
const ed = editorRef.current;
|
|
2057
2226
|
const model = getModel(active, content);
|
|
2058
2227
|
if (ed.getModel() !== model) ed.setModel(model);
|
|
2228
|
+
restoreViewState(active);
|
|
2059
2229
|
setLoadStage((prev) => ({
|
|
2060
2230
|
progress: Math.max(96, prev.progress),
|
|
2061
2231
|
message: "创建编辑器视图…"
|
|
@@ -2080,6 +2250,7 @@ window.__ModuleLoader__.load({
|
|
|
2080
2250
|
]);
|
|
2081
2251
|
react.default.useEffect(() => () => {
|
|
2082
2252
|
flushSave();
|
|
2253
|
+
saveViewState(active);
|
|
2083
2254
|
diffRendererRef.current?.dispose?.();
|
|
2084
2255
|
if (editorRef.current) {
|
|
2085
2256
|
editorRef.current.dispose();
|
|
@@ -2291,7 +2462,7 @@ window.__ModuleLoader__.load({
|
|
|
2291
2462
|
};
|
|
2292
2463
|
const reloadFile = (skipStale) => {
|
|
2293
2464
|
if (!active) return;
|
|
2294
|
-
loadContent(active, sessionId);
|
|
2465
|
+
loadContent(active, sessionId, true);
|
|
2295
2466
|
refreshRecords(skipStale === true);
|
|
2296
2467
|
};
|
|
2297
2468
|
/**
|
|
@@ -2556,6 +2727,7 @@ window.__ModuleLoader__.load({
|
|
|
2556
2727
|
onClick: () => {
|
|
2557
2728
|
if (t.path !== active) {
|
|
2558
2729
|
flushSave();
|
|
2730
|
+
saveViewState(active);
|
|
2559
2731
|
setActive(t.path);
|
|
2560
2732
|
}
|
|
2561
2733
|
},
|
|
@@ -4297,6 +4469,108 @@ window.__ModuleLoader__.load({
|
|
|
4297
4469
|
return out;
|
|
4298
4470
|
}
|
|
4299
4471
|
//#endregion
|
|
4472
|
+
//#region src/client/state/explorerCache.ts
|
|
4473
|
+
/**
|
|
4474
|
+
* dsh-vscode-mode client — 资源管理栏展开状态持久化(纯函数,可单测)。
|
|
4475
|
+
* 对齐 VSCode:只持久化展开路径集合,不缓存目录条目数据(条目实时 listDir)。
|
|
4476
|
+
* 作者 ddj 2026-08-28
|
|
4477
|
+
*/
|
|
4478
|
+
/** localStorage 键前缀(按会话隔离)。 */
|
|
4479
|
+
const KEY_PREFIX = "edrv.cache.explorer.v1.";
|
|
4480
|
+
/**
|
|
4481
|
+
* 归一化展开路径:去空白、反斜杠→斜杠、去首尾斜杠,`..` 段拒绝。
|
|
4482
|
+
* @author ddj 2026年08月28号
|
|
4483
|
+
* @param rel 目录相对路径('' = 根)
|
|
4484
|
+
* @returns 归一化路径,非法返回 null
|
|
4485
|
+
*/
|
|
4486
|
+
function normalizeExpanded(rel) {
|
|
4487
|
+
const s = String(rel ?? "").trim().replace(/\\/g, "/").replace(/^\/+|\/+$/g, "");
|
|
4488
|
+
if (!s) return "";
|
|
4489
|
+
const segs = s.split("/").filter((seg) => seg !== ".");
|
|
4490
|
+
if (!segs.length) return "";
|
|
4491
|
+
for (const seg of segs) if (seg === "..") return null;
|
|
4492
|
+
return segs.join("/");
|
|
4493
|
+
}
|
|
4494
|
+
/**
|
|
4495
|
+
* 裁剪展开路径集:去重、丢弃非法路径、超容量丢弃尾部(保持传入顺序)。
|
|
4496
|
+
* @author ddj 2026年08月28号
|
|
4497
|
+
* @param expanded 原始展开路径集合
|
|
4498
|
+
* @returns 规范化后的路径数组
|
|
4499
|
+
*/
|
|
4500
|
+
function sanitizeExpanded(expanded) {
|
|
4501
|
+
if (!Array.isArray(expanded)) return [];
|
|
4502
|
+
const seen = /* @__PURE__ */ new Set();
|
|
4503
|
+
const out = [];
|
|
4504
|
+
for (const item of expanded) {
|
|
4505
|
+
const rel = normalizeExpanded(typeof item === "string" ? item : null);
|
|
4506
|
+
if (rel === null || seen.has(rel)) continue;
|
|
4507
|
+
seen.add(rel);
|
|
4508
|
+
out.push(rel);
|
|
4509
|
+
if (out.length >= 500) break;
|
|
4510
|
+
}
|
|
4511
|
+
return out;
|
|
4512
|
+
}
|
|
4513
|
+
/**
|
|
4514
|
+
* 序列化缓存数据(JSON;root 为 null 时省略,减小体积)。
|
|
4515
|
+
* @author ddj 2026年08月28号
|
|
4516
|
+
* @param data 缓存数据
|
|
4517
|
+
* @returns JSON 字符串
|
|
4518
|
+
*/
|
|
4519
|
+
function serializeExplorer(data) {
|
|
4520
|
+
const payload = data.root === null || data.root === void 0 ? {
|
|
4521
|
+
v: 1,
|
|
4522
|
+
expanded: data.expanded
|
|
4523
|
+
} : {
|
|
4524
|
+
v: 1,
|
|
4525
|
+
root: data.root,
|
|
4526
|
+
expanded: data.expanded
|
|
4527
|
+
};
|
|
4528
|
+
return JSON.stringify(payload);
|
|
4529
|
+
}
|
|
4530
|
+
/**
|
|
4531
|
+
* 解析缓存文本;损坏/格式不符 → null。
|
|
4532
|
+
* @author ddj 2026年08月28号
|
|
4533
|
+
* @param text localStorage 原文
|
|
4534
|
+
* @returns 解析后的缓存数据,非法返回 null
|
|
4535
|
+
*/
|
|
4536
|
+
function parseExplorer(text) {
|
|
4537
|
+
if (!text) return null;
|
|
4538
|
+
try {
|
|
4539
|
+
const data = JSON.parse(text);
|
|
4540
|
+
if (!data || typeof data !== "object") return null;
|
|
4541
|
+
return {
|
|
4542
|
+
root: typeof data.root === "string" && data.root ? data.root : null,
|
|
4543
|
+
expanded: sanitizeExpanded(data.expanded)
|
|
4544
|
+
};
|
|
4545
|
+
} catch (error) {
|
|
4546
|
+
return null;
|
|
4547
|
+
}
|
|
4548
|
+
}
|
|
4549
|
+
/**
|
|
4550
|
+
* 读取某会话的展开状态缓存(localStorage 不可用 → null)。
|
|
4551
|
+
* @author ddj 2026年08月28号
|
|
4552
|
+
* @param sessionId 会话 id
|
|
4553
|
+
* @returns 缓存数据或 null
|
|
4554
|
+
*/
|
|
4555
|
+
function explorerLoad(sessionId) {
|
|
4556
|
+
try {
|
|
4557
|
+
return parseExplorer(window.localStorage.getItem(KEY_PREFIX + String(sessionId)));
|
|
4558
|
+
} catch (error) {
|
|
4559
|
+
return null;
|
|
4560
|
+
}
|
|
4561
|
+
}
|
|
4562
|
+
/**
|
|
4563
|
+
* 写入某会话的展开状态缓存(损坏/配额满 → 忽略)。
|
|
4564
|
+
* @author ddj 2026年08月28号
|
|
4565
|
+
* @param sessionId 会话 id
|
|
4566
|
+
* @param data 缓存数据
|
|
4567
|
+
*/
|
|
4568
|
+
function explorerSave(sessionId, data) {
|
|
4569
|
+
try {
|
|
4570
|
+
window.localStorage.setItem(KEY_PREFIX + String(sessionId), serializeExplorer(data));
|
|
4571
|
+
} catch (error) {}
|
|
4572
|
+
}
|
|
4573
|
+
//#endregion
|
|
4300
4574
|
//#region src/client/sidebar/panels/FileExplorer.ts
|
|
4301
4575
|
/**
|
|
4302
4576
|
* dsh-vscode-mode client — 侧边栏「文件管理」面板(懒加载目录树)。
|
|
@@ -4307,6 +4581,7 @@ window.__ModuleLoader__.load({
|
|
|
4307
4581
|
* 作者 ddj 2026-08-26 / 2026-08-27
|
|
4308
4582
|
*/
|
|
4309
4583
|
const DIR_CAP = 4e3;
|
|
4584
|
+
const SAVE_DEBOUNCE_MS = 300;
|
|
4310
4585
|
/**
|
|
4311
4586
|
* 目录树面板主体。
|
|
4312
4587
|
* @param props.ctx 面板共享上下文(sessionId/openFile/activePath/pendingByPath/fileMenuItems/notify)
|
|
@@ -4326,6 +4601,7 @@ window.__ModuleLoader__.load({
|
|
|
4326
4601
|
const tokensRef = react.default.useRef({});
|
|
4327
4602
|
const expandedRef = react.default.useRef({});
|
|
4328
4603
|
expandedRef.current = expanded;
|
|
4604
|
+
const saveTimerRef = react.default.useRef(null);
|
|
4329
4605
|
const loadDir = (rel) => {
|
|
4330
4606
|
const token = (tokensRef.current[rel] || 0) + 1;
|
|
4331
4607
|
tokensRef.current[rel] = token;
|
|
@@ -4383,8 +4659,42 @@ window.__ModuleLoader__.load({
|
|
|
4383
4659
|
setError(null);
|
|
4384
4660
|
setMenu(null);
|
|
4385
4661
|
setRoot(null);
|
|
4662
|
+
const cached = sessionId ? explorerLoad(sessionId) : null;
|
|
4663
|
+
const restored = cached?.expanded ?? [];
|
|
4664
|
+
if (cached?.root) setRoot(cached.root);
|
|
4665
|
+
if (restored.length) {
|
|
4666
|
+
const next = {};
|
|
4667
|
+
for (const rel of restored) next[rel] = true;
|
|
4668
|
+
setExpanded(next);
|
|
4669
|
+
}
|
|
4386
4670
|
loadDir("");
|
|
4671
|
+
for (const rel of restored) if (rel) loadDir(rel);
|
|
4387
4672
|
}, [sessionId]);
|
|
4673
|
+
react.default.useEffect(() => {
|
|
4674
|
+
if (saveTimerRef.current) {
|
|
4675
|
+
clearTimeout(saveTimerRef.current);
|
|
4676
|
+
saveTimerRef.current = null;
|
|
4677
|
+
}
|
|
4678
|
+
if (!sessionId) return;
|
|
4679
|
+
saveTimerRef.current = setTimeout(() => {
|
|
4680
|
+
saveTimerRef.current = null;
|
|
4681
|
+
const expandedList = Object.keys(expanded).filter((k) => expanded[k] === true);
|
|
4682
|
+
explorerSave(sessionId, {
|
|
4683
|
+
root,
|
|
4684
|
+
expanded: expandedList
|
|
4685
|
+
});
|
|
4686
|
+
}, SAVE_DEBOUNCE_MS);
|
|
4687
|
+
return () => {
|
|
4688
|
+
if (saveTimerRef.current) {
|
|
4689
|
+
clearTimeout(saveTimerRef.current);
|
|
4690
|
+
saveTimerRef.current = null;
|
|
4691
|
+
}
|
|
4692
|
+
};
|
|
4693
|
+
}, [
|
|
4694
|
+
expanded,
|
|
4695
|
+
root,
|
|
4696
|
+
sessionId
|
|
4697
|
+
]);
|
|
4388
4698
|
react.default.useEffect(() => {
|
|
4389
4699
|
const onRefresh = () => refresh();
|
|
4390
4700
|
window.addEventListener("edrv:refresh", onRefresh);
|