dsh-mcp-connector 0.2.71 → 0.2.72
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/CHANGELOG.md +8 -0
- package/README.en.md +1 -1
- package/README.md +1 -1
- package/docs/DESKTOP-E2E.md +7 -0
- package/package.json +1 -1
- package/ui/index.html +93 -4
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
本项目遵循 [Keep a Changelog](https://keepachangelog.com/zh-CN/1.1.0/);首个公开版本从 `0.1.0` 开始。
|
|
4
4
|
|
|
5
|
+
## [0.2.72] - 2026-10-14
|
|
6
|
+
|
|
7
|
+
### Fixed
|
|
8
|
+
|
|
9
|
+
- 宿主切换面板销毁 iframe 后,按工作区恢复页签、搜索、市场分类、展开章节、工具筛选/分页及列表滚动。状态仅保留在同源宿主当前页面内存,最多 32 个工作区,不保存凭据表单、API 返回或授权弹框;刷新宿主页面即清除。
|
|
10
|
+
- 市场与已安装列表加入请求序列和工作区检查,防止较早响应覆盖新页签/工作区;加载中的滚动归零不覆盖恢复位置。
|
|
11
|
+
- 本次为浏览状态恢复而非 iframe 保活:仍重新拉取数据、重建 SSE,详情和未完成表单不恢复。
|
|
12
|
+
|
|
5
13
|
## [0.2.71] - 2026-10-14
|
|
6
14
|
|
|
7
15
|
### Added
|
package/README.en.md
CHANGED
|
@@ -215,7 +215,7 @@ npm run dev:ui
|
|
|
215
215
|
|
|
216
216
|
Every Registry merge regenerates `catalog-stats.json`; an hourly workflow in this repository synchronizes the Chinese and English product copy plus a local stats snapshot. The static npm README updates with package releases, while the live badges above read the Registry directly and therefore stay current without another npm release.
|
|
217
217
|
|
|
218
|
-
The current public version is [`dsh-mcp-connector@0.2.
|
|
218
|
+
The current public version is [`dsh-mcp-connector@0.2.72`](https://www.npmjs.com/package/dsh-mcp-connector), with [GitHub Release v0.2.72](https://github.com/duhu2000/dsh-mcp-connector/releases/tag/v0.2.72).
|
|
219
219
|
|
|
220
220
|
See [CHANGELOG.md](CHANGELOG.md) for version history and [docs/DESKTOP-E2E.md](docs/DESKTOP-E2E.md) for the Desktop release checklist.
|
|
221
221
|
|
package/README.md
CHANGED
|
@@ -210,7 +210,7 @@ npm run dev:ui
|
|
|
210
210
|
|
|
211
211
|
公共 Registry 每次合并后会生成 `catalog-stats.json`;本仓库的定时工作流每小时同步中英文介绍和统计快照。npm 页面中的静态正文随版本发布更新,上方动态统计徽标则直接读取 Registry,可在不发布新 npm 版本时保持实时数量一致。
|
|
212
212
|
|
|
213
|
-
当前公开版本为 [`dsh-mcp-connector@0.2.
|
|
213
|
+
当前公开版本为 [`dsh-mcp-connector@0.2.72`](https://www.npmjs.com/package/dsh-mcp-connector),对应 [GitHub Release v0.2.72](https://github.com/duhu2000/dsh-mcp-connector/releases/tag/v0.2.72)。
|
|
214
214
|
|
|
215
215
|
版本能力与变更记录见 [CHANGELOG.md](CHANGELOG.md)。
|
|
216
216
|
Desktop 发版回归见 [docs/DESKTOP-E2E.md](docs/DESKTOP-E2E.md)。
|
package/docs/DESKTOP-E2E.md
CHANGED
|
@@ -23,6 +23,13 @@ npm run dev:ui
|
|
|
23
23
|
|
|
24
24
|
## 不自动执行的项目
|
|
25
25
|
|
|
26
|
+
### 面板浏览状态恢复(#135)
|
|
27
|
+
|
|
28
|
+
- 在工作区 A 设置市场分类、搜索、展开章节并滚动;切换其他宿主面板再回来,检查恢复。已安装和工具页签(含筛选、分页)分别重复。
|
|
29
|
+
- 切换工作区 B 不应带入 A 的筛选;回到 A 应恢复 A 的状态。慢网络下快速切页签/工作区,旧响应不得覆盖新页面。
|
|
30
|
+
- 返回仍重新读取数据、重建 SSE;这是状态恢复,不是 iframe 常驻。详情、授权弹框、凭据及 JSON 编辑表单不恢复。
|
|
31
|
+
- 浏览状态只在同源宿主当前页面内存保留,最多 32 个工作区;刷新宿主后清空。跨源/受限父页面不可访问时降级为不跨 iframe 生命周期保存。
|
|
32
|
+
|
|
26
33
|
- 不在 CI 中完成真实 OAuth 登录。
|
|
27
34
|
- 不自动删除旧插件、旧 storage domain 或凭据。
|
|
28
35
|
- 不因探针一次失败直接删除社区条目;定时报告供维护者复核。
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-mcp-connector",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.72",
|
|
4
4
|
"description": "DeepSeek Harness MCP Connector: connect servers, search tools across active connections, and troubleshoot discovery. Includes a continuously updated catalog; supports OAuth 2.0 PKCE, API keys, Streamable HTTP/stdio, and mcpServers JSON import.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|
package/ui/index.html
CHANGED
|
@@ -628,6 +628,74 @@
|
|
|
628
628
|
let pendingScopeKey = null;
|
|
629
629
|
let installedItems = [];
|
|
630
630
|
|
|
631
|
+
// Same-tab memory only: no credentials, modal contents or API responses are retained.
|
|
632
|
+
// The Host owns this memory across iframe unmounts; reloading the Host clears it.
|
|
633
|
+
const viewStateMemory = (() => {
|
|
634
|
+
try {
|
|
635
|
+
const host = window.parent;
|
|
636
|
+
if (host.location.origin !== window.location.origin) return new Map();
|
|
637
|
+
const key = '__mcpConnectorViewStateV1';
|
|
638
|
+
if (!(host[key] instanceof host.Map)) host[key] = new host.Map();
|
|
639
|
+
return host[key];
|
|
640
|
+
} catch { return new Map(); }
|
|
641
|
+
})();
|
|
642
|
+
let viewStateReady = window.parent === window;
|
|
643
|
+
let viewRenderSequence = 0;
|
|
644
|
+
let viewStateLoading = false;
|
|
645
|
+
let pendingViewScroll = null;
|
|
646
|
+
|
|
647
|
+
function normalizeViewState(value) {
|
|
648
|
+
const text = (s) => typeof s === 'string' ? s.slice(0, 500) : '';
|
|
649
|
+
const category = (s) => CATEGORIES.some((item) => item.value === s) ? s : '';
|
|
650
|
+
return {
|
|
651
|
+
view: ['market', 'installed', 'tools'].includes(value?.view) ? value.view : 'market',
|
|
652
|
+
search: text(value?.search),
|
|
653
|
+
category: category(value?.category),
|
|
654
|
+
expanded: Array.isArray(value?.expanded) ? [...new Set(value.expanded.map(category).filter(Boolean))] : [],
|
|
655
|
+
filter: { connectionKey: text(value?.filter?.connectionKey), serverName: text(value?.filter?.serverName),
|
|
656
|
+
status: ['', 'pending', 'unknown', 'success', 'failed'].includes(value?.filter?.status) ? value.filter.status : '' },
|
|
657
|
+
offset: Number.isSafeInteger(value?.offset) && value.offset >= 0 ? Math.min(value.offset, 100000) : 0,
|
|
658
|
+
scroll: Number.isFinite(value?.scroll) ? Math.max(0, Math.min(value.scroll, 10000000)) : 0,
|
|
659
|
+
};
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
function viewStateKey() { return JSON.stringify([currentWorkspace?.workspaceId ?? null]); }
|
|
663
|
+
|
|
664
|
+
function saveViewState() {
|
|
665
|
+
if (!viewStateReady || viewStateLoading) return;
|
|
666
|
+
const key = viewStateKey();
|
|
667
|
+
viewStateMemory.delete(key);
|
|
668
|
+
viewStateMemory.set(key, normalizeViewState({ view, search: searchQuery, category: marketCategory,
|
|
669
|
+
expanded: [...expandedMarketSections], filter: explorerFilter, offset: explorerOffset, scroll: main.scrollTop }));
|
|
670
|
+
while (viewStateMemory.size > 32) viewStateMemory.delete(viewStateMemory.keys().next().value);
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
function restoreViewState() {
|
|
674
|
+
const state = normalizeViewState(viewStateMemory.get(viewStateKey()));
|
|
675
|
+
view = state.view; searchQuery = state.search; searchInput.value = searchQuery;
|
|
676
|
+
marketCategory = state.category;
|
|
677
|
+
expandedMarketSections.clear(); state.expanded.forEach((value) => expandedMarketSections.add(value));
|
|
678
|
+
explorerFilter = state.filter; explorerOffset = state.offset;
|
|
679
|
+
pendingViewScroll = state.scroll;
|
|
680
|
+
syncTabs();
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
function beginViewRender() {
|
|
684
|
+
if (pendingViewScroll === null) saveViewState();
|
|
685
|
+
viewStateLoading = true;
|
|
686
|
+
return { sequence: ++viewRenderSequence, workspace: viewStateKey(), view };
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
function isViewRenderCurrent(render) {
|
|
690
|
+
return render.sequence === viewRenderSequence && render.workspace === viewStateKey() && render.view === view;
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
function finishViewRender(render) {
|
|
694
|
+
if (!isViewRenderCurrent(render)) return;
|
|
695
|
+
if (pendingViewScroll !== null) { main.scrollTop = pendingViewScroll; pendingViewScroll = null; }
|
|
696
|
+
viewStateLoading = false;
|
|
697
|
+
}
|
|
698
|
+
|
|
631
699
|
async function call(method, params, { timeoutMs: timeoutOverride } = {}) {
|
|
632
700
|
const controller = new AbortController();
|
|
633
701
|
const timeoutMs = timeoutOverride || API_TIMEOUTS[method] || DEFAULT_API_TIMEOUT_MS;
|
|
@@ -716,16 +784,20 @@
|
|
|
716
784
|
const workspace = event.data.workspace;
|
|
717
785
|
const previousWorkspaceId = currentWorkspace?.workspaceId ?? null;
|
|
718
786
|
const previousWorkspaceOptions = JSON.stringify(availableWorkspaces);
|
|
787
|
+
const firstContext = !viewStateReady;
|
|
788
|
+
saveViewState();
|
|
719
789
|
currentWorkspace = workspace && typeof workspace.workspaceId === 'string'
|
|
720
790
|
? { workspaceId: workspace.workspaceId, title: String(workspace.title || workspace.workspaceId) }
|
|
721
791
|
: null;
|
|
722
792
|
updateAvailableWorkspaces(event.data.workspaces);
|
|
723
|
-
if ((currentWorkspace?.workspaceId ?? null) !== previousWorkspaceId || JSON.stringify(availableWorkspaces) !== previousWorkspaceOptions) {
|
|
793
|
+
if (firstContext || (currentWorkspace?.workspaceId ?? null) !== previousWorkspaceId || JSON.stringify(availableWorkspaces) !== previousWorkspaceOptions) {
|
|
724
794
|
explorerSequence++; explorerDetailSequence++;
|
|
725
795
|
explorerData = null; explorerOffset = 0;
|
|
726
796
|
explorerFilter = { connectionKey: '', serverName: '', status: '' };
|
|
727
797
|
closeModal(); closeDetail();
|
|
728
|
-
|
|
798
|
+
viewStateReady = true;
|
|
799
|
+
if (firstContext || (currentWorkspace?.workspaceId ?? null) !== previousWorkspaceId) restoreViewState();
|
|
800
|
+
main.innerHTML = '';
|
|
729
801
|
refresh();
|
|
730
802
|
}
|
|
731
803
|
return;
|
|
@@ -1275,10 +1347,13 @@
|
|
|
1275
1347
|
}
|
|
1276
1348
|
|
|
1277
1349
|
async function loadMarket({ checkHealth = true, preserveScroll = false } = {}) {
|
|
1350
|
+
const render = beginViewRender();
|
|
1351
|
+
try {
|
|
1278
1352
|
const previousScrollTop = preserveScroll ? main.scrollTop : 0;
|
|
1279
1353
|
syncMarketNav();
|
|
1280
1354
|
main.innerHTML = '<div class="empty">加载中…</div>';
|
|
1281
1355
|
const [r, status] = await Promise.all([call('catalog', workspaceParams()), call('status', workspaceParams())]);
|
|
1356
|
+
if (!isViewRenderCurrent(render)) return;
|
|
1282
1357
|
if (status.ok) {
|
|
1283
1358
|
updateAvailableWorkspaces(status.detail?.scope?.workspaces);
|
|
1284
1359
|
setTabCount('installed', (status.detail?.items ?? []).length);
|
|
@@ -1300,9 +1375,10 @@
|
|
|
1300
1375
|
if (checkHealth && !initialHealthCheckStarted && items.some((item) => item.connected?.length > 0)) {
|
|
1301
1376
|
initialHealthCheckStarted = true;
|
|
1302
1377
|
call('healthCheck', {}).then(() => {
|
|
1303
|
-
if (view === 'market') loadMarket({ checkHealth: false });
|
|
1378
|
+
if (view === 'market') loadMarket({ checkHealth: false, preserveScroll: true });
|
|
1304
1379
|
});
|
|
1305
1380
|
}
|
|
1381
|
+
} finally { finishViewRender(render); }
|
|
1306
1382
|
}
|
|
1307
1383
|
|
|
1308
1384
|
function connectionLabels(d) {
|
|
@@ -1370,6 +1446,8 @@
|
|
|
1370
1446
|
const formatObserved = (value) => value ? new Date(value).toLocaleString('zh-CN', { hour12: false }) : '尚无记录';
|
|
1371
1447
|
|
|
1372
1448
|
async function loadToolExplorer() {
|
|
1449
|
+
const render = beginViewRender();
|
|
1450
|
+
try {
|
|
1373
1451
|
const sequence = ++explorerSequence;
|
|
1374
1452
|
const workspaceId = currentWorkspace?.workspaceId;
|
|
1375
1453
|
syncMarketNav();
|
|
@@ -1377,7 +1455,7 @@
|
|
|
1377
1455
|
main.innerHTML = `<section id="tool-explorer"><p class="hint">搜索当前范围内已启用连接的工具缓存;不会执行工具。最近发现成功不代表每次调用均可成功。</p><div class="explorer-filters"><label>连接<select id="explorer-connection" aria-label="按连接筛选"></select></label><label>服务<select id="explorer-server" aria-label="按服务筛选"></select></label><label>发现状态<select id="explorer-status" aria-label="按发现状态筛选"><option value="">全部状态</option>${Object.entries(EXPLORER_STATUS).map(([key, label]) => `<option value="${key}">${label}</option>`).join('')}</select></label><button class="btn ghost" data-explorer-clear>清除筛选</button></div><p id="explorer-summary" role="status">正在读取缓存…</p><div id="explorer-connections"></div><div id="explorer-results"></div><div id="explorer-pages" class="m-actions"></div></section>`;
|
|
1378
1456
|
}
|
|
1379
1457
|
const response = await call('toolExplorer', { ...workspaceParams(), ...explorerFilter, query: searchQuery, offset: explorerOffset, limit: 20 });
|
|
1380
|
-
if (sequence !== explorerSequence || view !== 'tools' || workspaceId !== currentWorkspace?.workspaceId) return;
|
|
1458
|
+
if (!isViewRenderCurrent(render) || sequence !== explorerSequence || view !== 'tools' || workspaceId !== currentWorkspace?.workspaceId) return;
|
|
1381
1459
|
if (!response.ok) { $('#explorer-summary').textContent = response.message; return; }
|
|
1382
1460
|
explorerData = response.detail;
|
|
1383
1461
|
const { connections = [], selectedConnections = [], items = [], total = 0 } = explorerData;
|
|
@@ -1392,8 +1470,10 @@
|
|
|
1392
1470
|
$('#explorer-connections').innerHTML = `<details ${explorerFilter.connectionKey || !total ? 'open' : ''}><summary>连接状态与故障处理(${selectedConnections.length})</summary>${selectedConnections.map((c) => `<div class="explorer-card"><strong>${esc(c.connectionName)}</strong><div>${esc(c.serverName)} · ${esc(EXPLORER_STATUS[c.status] || c.status)} · 缓存 ${c.toolCount} 个工具</div><div class="hint">最近发现:${esc(formatObserved(c.checkedAt))};最后成功:${esc(formatObserved(c.observedAt))}${c.stale ? ' · 缓存超过 24 小时' : ''}${c.cacheTruncated ? ' · 缓存清单已截断' : ''}</div>${c.diagnostic ? `<div>${esc(c.diagnostic.stageLabel)}:${esc(c.diagnostic.message)}<br>建议:${esc(c.diagnostic.action)}</div>` : ''}${c.retryReason === 'auth' ? '<p>鉴权失败已暂停后台重试,请到已安装页更新凭据或重新授权。</p>' : c.nextDiscoveryAt ? `<p class="hint">下次后台发现不早于:${esc(formatObserved(c.nextDiscoveryAt))}(页面保持连接时)</p>` : ''}<div class="m-actions"><button class="btn ghost" data-explorer-action="discover" data-key="${esc(c.connectionKey)}" ${explorerBusy.has(c.connectionKey) ? 'disabled' : ''}>重新发现工具</button><button class="btn ghost" data-explorer-action="check" data-key="${esc(c.connectionKey)}" ${explorerBusy.has(c.connectionKey) ? 'disabled' : ''}>检查连接</button></div></div>`).join('') || '<p>当前筛选下没有连接。</p>'}</details>`;
|
|
1393
1471
|
$('#explorer-results').innerHTML = items.map((item, index) => `<article class="explorer-card"><strong>${esc(item.name)}</strong>${item.title !== item.name ? `<div>${esc(item.title)}</div>` : ''}<p>${esc(item.description || '暂无描述')}</p><div class="hint">来源:${esc(item.connection.connectorName)} / ${esc(item.connection.connectionName)} / ${esc(item.serverName)}<br>${esc(EXPLORER_STATUS[item.connection.status])} · 最后成功缓存 ${esc(formatObserved(item.observedAt))}${item.stale ? ' · 已超过 24 小时' : ''}</div><button class="btn ghost" data-explorer-detail="${index}">参数详情</button></article>`).join('') || `<p class="empty">${connections.length ? '没有匹配工具。请调整关键词或筛选;也可展开连接状态检查是否尚未发现或读取失败。' : '当前范围没有已启用连接。请先添加连接,或选择对应工作区。'}</p>`;
|
|
1394
1472
|
$('#explorer-pages').innerHTML = `<button class="btn ghost" data-explorer-page="prev" ${explorerOffset === 0 ? 'disabled' : ''}>上一页</button><span>${Math.floor(explorerOffset / 20) + 1} / ${Math.max(1, Math.ceil(total / 20))}</span><button class="btn ghost" data-explorer-page="next" ${explorerOffset + 20 >= total ? 'disabled' : ''}>下一页</button>`;
|
|
1473
|
+
} finally { finishViewRender(render); }
|
|
1395
1474
|
}
|
|
1396
1475
|
|
|
1476
|
+
main.addEventListener('scroll', () => saveViewState(), { passive: true });
|
|
1397
1477
|
main.addEventListener('change', (event) => {
|
|
1398
1478
|
const fields = { 'explorer-connection': 'connectionKey', 'explorer-server': 'serverName', 'explorer-status': 'status' };
|
|
1399
1479
|
const field = fields[event.target.id];
|
|
@@ -1429,10 +1509,13 @@
|
|
|
1429
1509
|
});
|
|
1430
1510
|
|
|
1431
1511
|
async function loadInstalled({ preserveScroll = false } = {}) {
|
|
1512
|
+
const render = beginViewRender();
|
|
1513
|
+
try {
|
|
1432
1514
|
const previousScrollTop = preserveScroll ? main.scrollTop : 0;
|
|
1433
1515
|
syncMarketNav();
|
|
1434
1516
|
main.innerHTML = '<div class="empty">加载中…</div>';
|
|
1435
1517
|
const [r, migration] = await Promise.all([call('status', workspaceParams()), call('migrationPreview', { scanStored: false })]);
|
|
1518
|
+
if (!isViewRenderCurrent(render)) return;
|
|
1436
1519
|
if (!r.ok) {
|
|
1437
1520
|
main.innerHTML = `<div class="empty">${esc(r.message)}</div>`;
|
|
1438
1521
|
if (preserveScroll) main.scrollTop = previousScrollTop;
|
|
@@ -1456,6 +1539,7 @@
|
|
|
1456
1539
|
const rollbackDisabled = scopeRollbackRevision === null ? 'disabled' : '';
|
|
1457
1540
|
main.innerHTML = `${migrationHtml}<div class="stats"><span class="count">共 <strong>${filtered.length}</strong> 条连接${searchQuery ? ` · 搜索「${esc(searchQuery)}」` : ''} · 作用域 revision=${scopeRevision}</span><button class="btn ghost" id="scope-rollback" type="button" ${rollbackDisabled} onclick="window.__mcp.rollbackScope()">回滚上次范围变更</button><button class="btn ghost" id="config-backup" type="button" onclick="window.__mcp.openConfigBackup()">配置备份</button><button class="btn ghost" type="button" title="扫描企查查旧 OAuth 插件;不会删除旧插件或原凭据" onclick="window.__mcp.scanLegacy()">检测企查查旧授权</button></div><div class="rows">${filtered.map(rowHtml).join('')}</div>${communityCtaHtml()}`;
|
|
1458
1541
|
if (preserveScroll) main.scrollTop = previousScrollTop;
|
|
1542
|
+
} finally { finishViewRender(render); }
|
|
1459
1543
|
}
|
|
1460
1544
|
|
|
1461
1545
|
function rowHtml(r) {
|
|
@@ -2425,6 +2509,7 @@
|
|
|
2425
2509
|
}
|
|
2426
2510
|
});
|
|
2427
2511
|
window.addEventListener('pagehide', () => {
|
|
2512
|
+
saveViewState();
|
|
2428
2513
|
notifyDetailState(false);
|
|
2429
2514
|
clearTimeout(statusRefreshTimer);
|
|
2430
2515
|
statusEventSource?.close();
|
|
@@ -2445,6 +2530,7 @@
|
|
|
2445
2530
|
const categoryFilter = event.target.closest('[data-category-filter]');
|
|
2446
2531
|
if (categoryFilter) {
|
|
2447
2532
|
marketCategory = categoryFilter.dataset.categoryFilter || '';
|
|
2533
|
+
pendingViewScroll = null; main.scrollTop = 0;
|
|
2448
2534
|
loadMarket({ checkHealth: false });
|
|
2449
2535
|
}
|
|
2450
2536
|
});
|
|
@@ -2472,6 +2558,7 @@
|
|
|
2472
2558
|
const btn = e.target.closest('button[data-view]');
|
|
2473
2559
|
if (!btn) return;
|
|
2474
2560
|
view = btn.dataset.view;
|
|
2561
|
+
pendingViewScroll = null; main.scrollTop = 0;
|
|
2475
2562
|
explorerSequence++; explorerDetailSequence++;
|
|
2476
2563
|
syncTabs();
|
|
2477
2564
|
refresh();
|
|
@@ -2479,6 +2566,7 @@
|
|
|
2479
2566
|
|
|
2480
2567
|
searchInput.addEventListener('input', (e) => {
|
|
2481
2568
|
searchQuery = e.target.value.trim();
|
|
2569
|
+
pendingViewScroll = null; main.scrollTop = 0;
|
|
2482
2570
|
explorerSequence++; explorerOffset = 0;
|
|
2483
2571
|
clearTimeout(searchTimer);
|
|
2484
2572
|
if (view === 'tools') { searchTimer = setTimeout(() => { if (view === 'tools') void loadToolExplorer(); }, 200); return; }
|
|
@@ -2486,6 +2574,7 @@
|
|
|
2486
2574
|
refresh();
|
|
2487
2575
|
});
|
|
2488
2576
|
|
|
2577
|
+
if (viewStateReady) restoreViewState();
|
|
2489
2578
|
requestWorkspaceContext();
|
|
2490
2579
|
connectStatusEvents();
|
|
2491
2580
|
refresh();
|