dsh-db-tool 1.5.3 → 1.5.5
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/client/client.js +158 -17
- package/package.json +3 -2
package/client/client.js
CHANGED
|
@@ -107,7 +107,6 @@ window.__ModuleLoader__.load({
|
|
|
107
107
|
truncatedNoEdit: "值已截断显示,就地编辑可能覆盖数据,请改用 SQL 控制台",
|
|
108
108
|
blobNoEdit: "二进制值不支持就地编辑",
|
|
109
109
|
redisTypeRo: "Redis 该类型不支持就地编辑",
|
|
110
|
-
cellSaved: "已保存",
|
|
111
110
|
// SQL 控制台
|
|
112
111
|
modeQuery: "只读查询",
|
|
113
112
|
modeExecute: "写入执行",
|
|
@@ -217,7 +216,6 @@ window.__ModuleLoader__.load({
|
|
|
217
216
|
truncatedNoEdit: "Value shown truncated; edit via SQL Console instead",
|
|
218
217
|
blobNoEdit: "Binary values cannot be edited here",
|
|
219
218
|
redisTypeRo: "This Redis type cannot be edited here",
|
|
220
|
-
cellSaved: "Saved",
|
|
221
219
|
modeQuery: "query (read-only)",
|
|
222
220
|
modeExecute: "execute (write)",
|
|
223
221
|
modeScript: "script",
|
|
@@ -352,7 +350,7 @@ window.__ModuleLoader__.load({
|
|
|
352
350
|
".dbt-btn.danger:hover{background:rgba(255,69,58,.12);}",
|
|
353
351
|
".dbt-btn:disabled{opacity:.4;cursor:default;transform:none;}",
|
|
354
352
|
"/* ===== 文本反馈 ===== */",
|
|
355
|
-
".dbt-muted{color:var(--dbt-text-secondary);font-size:11px;}",
|
|
353
|
+
".dbt-muted,.dbt-readhint{color:var(--dbt-text-secondary);font-size:11px;}",
|
|
356
354
|
".dbt-err{color:var(--dbt-danger);font-size:12px;white-space:pre-wrap;}",
|
|
357
355
|
".dbt-msg{color:var(--dbt-success);font-size:12px;white-space:pre-wrap;}",
|
|
358
356
|
"/* ===== 行 hairline 表格 + 毛玻璃 sticky 表头 ===== */",
|
|
@@ -388,7 +386,7 @@ window.__ModuleLoader__.load({
|
|
|
388
386
|
".dbt-chev.leaf{visibility:hidden;}",
|
|
389
387
|
".dbt-treename{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}",
|
|
390
388
|
/* ===== Navicat 式浏览弹窗 ===== */
|
|
391
|
-
".dbt-browse-dialog{position:fixed;width:min(1100px,calc(100vw - 48px));height:min(720px,calc(100vh - 48px));min-width:min(680px,calc(100vw - 16px));min-height:min(420px,calc(100vh - 16px));background:var(--dbt-dialog-bg);color:inherit;border:1px solid var(--dbt-separator);border-radius:16px;box-shadow:0 24px 64px rgba(0,0,0,.32),0 4px 16px rgba(0,0,0,.18);-webkit-backdrop-filter:blur(20px) saturate(180%);backdrop-filter:blur(20px) saturate(180%);display:flex;flex-direction:column;overflow:hidden;box-sizing:border-box;font-size:14px;animation:dbt-in .22s var(--dbt-ease);}",
|
|
389
|
+
".dbt-browse-dialog{position:fixed;z-index:2147483000;width:min(1100px,calc(100vw - 48px));height:min(720px,calc(100vh - 48px));min-width:min(680px,calc(100vw - 16px));min-height:min(420px,calc(100vh - 16px));background:var(--dbt-dialog-bg);color:inherit;border:1px solid var(--dbt-separator);border-radius:16px;box-shadow:0 24px 64px rgba(0,0,0,.32),0 4px 16px rgba(0,0,0,.18);-webkit-backdrop-filter:blur(20px) saturate(180%);backdrop-filter:blur(20px) saturate(180%);display:flex;flex-direction:column;overflow:hidden;box-sizing:border-box;font-size:14px;animation:dbt-in .22s var(--dbt-ease);}",
|
|
392
390
|
".dbt-browse-dialog.dragging{user-select:none;}",
|
|
393
391
|
".dbt-browse-header{flex:none;display:flex;align-items:center;gap:10px;padding:12px 16px;border-bottom:1px solid var(--dbt-separator);}",
|
|
394
392
|
".dbt-browse-title{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-weight:600;font-size:17px;}",
|
|
@@ -417,7 +415,6 @@ window.__ModuleLoader__.load({
|
|
|
417
415
|
".dbt-celledit textarea{font-family:var(--dbt-mono);font-size:13px;min-height:96px;resize:vertical;background:var(--dbt-surface-strong);border:none;color:inherit;border-radius:8px;padding:8px;}",
|
|
418
416
|
".dbt-celledit textarea:focus{outline:2px solid var(--dbt-accent);outline-offset:-1px;}",
|
|
419
417
|
".dbt-nullchip{display:inline-flex;align-items:center;border:1px solid var(--dbt-warning);color:var(--dbt-warning);border-radius:999px;padding:1px 8px;font-size:11px;font-weight:600;}",
|
|
420
|
-
".dbt-readhint{color:var(--dbt-text-secondary);font-size:11px;}",
|
|
421
418
|
"/* ===== 视图切换入场(仅 transform/opacity;reduced-motion 全局已禁) ===== */",
|
|
422
419
|
"@keyframes dbt-in{from{opacity:0;transform:translateY(4px);}to{opacity:1;transform:none;}}",
|
|
423
420
|
".dbt-view{display:flex;flex-direction:column;gap:10px;animation:dbt-in .22s var(--dbt-ease);}",
|
|
@@ -427,7 +424,7 @@ window.__ModuleLoader__.load({
|
|
|
427
424
|
".dbt-seg button.active{background:var(--dbt-seg-active);font-weight:600;box-shadow:0 1px 3px rgba(0,0,0,.12);}",
|
|
428
425
|
".dbt-browse-root .dbt-seg button{font-size:12px;padding:4px 12px;}",
|
|
429
426
|
"/* ===== 降级:透明度减弱 / 动效减弱 ===== */",
|
|
430
|
-
"@media (prefers-reduced-transparency: reduce){.dbt-overlay{backdrop-filter:none;-webkit-backdrop-filter:none;background:rgba(0,0,0,.55);}.dbt-dialog,.dbt-browse-dialog,.dbt-cellpop{backdrop-filter:none;-webkit-backdrop-filter:none;
|
|
427
|
+
"@media (prefers-reduced-transparency: reduce){.dbt-overlay{backdrop-filter:none;-webkit-backdrop-filter:none;background:rgba(0,0,0,.55);}.dbt-dialog,.dbt-browse-dialog,.dbt-cellpop{backdrop-filter:none;-webkit-backdrop-filter:none;background:#2c2c2e;}.dbt-table th{backdrop-filter:none;-webkit-backdrop-filter:none;background:#1e1e20;}.dbt-browse-root .dbt-table th{background:#1e1e20;}}",
|
|
431
428
|
"@media (prefers-color-scheme: light) and (prefers-reduced-transparency: reduce){.dbt-dialog,.dbt-browse-dialog,.dbt-cellpop{background:#f5f5f7;}.dbt-table th,.dbt-browse-root .dbt-table th{background:#f2f2f7;}}",
|
|
432
429
|
"@media (prefers-reduced-motion: reduce){.dbt-panel *,.dbt-browse-root *{transition:none!important;animation:none!important;}.dbt-btn:active{transform:none;}}",
|
|
433
430
|
].join("\n");
|
|
@@ -454,7 +451,8 @@ window.__ModuleLoader__.load({
|
|
|
454
451
|
React.createElement("div", { className: "dbt-muted" }, t("confirmHint")),
|
|
455
452
|
React.createElement(
|
|
456
453
|
"div",
|
|
457
|
-
|
|
454
|
+
// 末对齐由样式层 .dbt-dialog .dbt-row 规则承担,无需内联 style
|
|
455
|
+
{ className: "dbt-row" },
|
|
458
456
|
React.createElement("button", { className: "dbt-btn danger", onClick: () => done(true) }, t("confirmRun")),
|
|
459
457
|
React.createElement("button", { className: "dbt-btn", onClick: () => done(false) }, t("confirmCancel")),
|
|
460
458
|
),
|
|
@@ -493,8 +491,47 @@ window.__ModuleLoader__.load({
|
|
|
493
491
|
(portalPending = portalPending || []).push(cb);
|
|
494
492
|
}
|
|
495
493
|
function mountDialog(children) {
|
|
496
|
-
return portalImpl ? portalImpl(children, document.body) : children;
|
|
494
|
+
return (portalImpl && !portalDisabled) ? portalImpl(children, document.body) : children;
|
|
497
495
|
}
|
|
496
|
+
// PiP 独立窗口渲染时禁 portal:PiP root 本身就挂在 pipDoc.body 上,再 portal 回主 document 会把 DOM 拉出独立窗口
|
|
497
|
+
let portalDisabled = false;
|
|
498
|
+
// createRoot 三重解析(PiP 独立窗口需要独立 React root,结构对齐上方 portal 解析):
|
|
499
|
+
// ①模块加载时同步 require react-dom → react-dom/client(宿主按 specifier 解析,两者是不同键)
|
|
500
|
+
// ②宿主模块系统异步 import("react-dom/client") ③均不可用则 PiP 超时降级 DOM 浮窗
|
|
501
|
+
let reactCreateRoot = null;
|
|
502
|
+
let rootTried = false; // 异步 import 只发起一次
|
|
503
|
+
let rootPending = null; // 订阅解析成功的回调列表
|
|
504
|
+
let rootSource = null; // 就绪方式:sync / async / null(未就绪,诊断日志用)
|
|
505
|
+
try {
|
|
506
|
+
var rd2 = require("react-dom");
|
|
507
|
+
if (rd2 && typeof rd2.createRoot === "function") {
|
|
508
|
+
reactCreateRoot = rd2.createRoot.bind(rd2); rootTried = true; rootSource = "sync";
|
|
509
|
+
} else {
|
|
510
|
+
try { var rdc = require("react-dom/client"); if (rdc && typeof rdc.createRoot === "function") { reactCreateRoot = rdc.createRoot.bind(rdc); rootTried = true; rootSource = "sync"; } } catch (e2) { /* 保持 null */ }
|
|
511
|
+
}
|
|
512
|
+
} catch (e) { /* 保持 null,等异步解析 */ }
|
|
513
|
+
function resolveRootAsync(ctx) {
|
|
514
|
+
if (rootTried) return;
|
|
515
|
+
rootTried = true;
|
|
516
|
+
if (reactCreateRoot) return;
|
|
517
|
+
var mods = ctx && ctx.modules;
|
|
518
|
+
var p = mods && typeof mods.import === "function" ? mods.import("react-dom/client") : null;
|
|
519
|
+
if (p && typeof p.then === "function") {
|
|
520
|
+
p.then(function (m) {
|
|
521
|
+
if (m && typeof m.createRoot === "function") {
|
|
522
|
+
reactCreateRoot = m.createRoot.bind(m);
|
|
523
|
+
rootSource = "async";
|
|
524
|
+
var waiters = rootPending || []; rootPending = null;
|
|
525
|
+
for (var i = 0; i < waiters.length; i++) waiters[i]();
|
|
526
|
+
}
|
|
527
|
+
}).catch(function () { /* 解析失败,PiP 走超时降级 */ });
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
function onRootReady(cb) {
|
|
531
|
+
if (reactCreateRoot) { cb(); return; }
|
|
532
|
+
(rootPending = rootPending || []).push(cb);
|
|
533
|
+
}
|
|
534
|
+
let pipResult = "not-tried"; // 最近一次 PiP 尝试结果:opened / rejected:<err.name>(诊断日志用)
|
|
498
535
|
|
|
499
536
|
/* ---------------- 连接管理 ---------------- */
|
|
500
537
|
const EMPTY_FORM = { id: "", kind: "mysql", name: "", mode: "url", url: "", urlUser: "", urlPassword: "", host: "", port: "", user: "", password: "", database: "", ssl: false };
|
|
@@ -1334,17 +1371,20 @@ window.__ModuleLoader__.load({
|
|
|
1334
1371
|
|
|
1335
1372
|
function BrowseDialog(props) {
|
|
1336
1373
|
const { conns, projectPath, grants, askConfirm, onClose, ctx } = props;
|
|
1337
|
-
// grants
|
|
1374
|
+
// grants(editable 判定)与 askConfirm(单元格写回确认)下传 PreviewGrid
|
|
1338
1375
|
// portal 异步解析接入:portalReady 仅用于触发重渲染(mountDialog 内部读 portalImpl)。
|
|
1339
1376
|
// createPortal 不换组件实例——portalReady 切换前后是同一 DOM 节点,pos state 保持,居中定位不受 portal 重挂影响。
|
|
1340
1377
|
const [portalReady, setPortalReady] = React.useState(!!portalImpl);
|
|
1341
1378
|
React.useEffect(() => {
|
|
1342
1379
|
resolvePortalAsync(ctx);
|
|
1380
|
+
resolveRootAsync(ctx);
|
|
1343
1381
|
onPortalReady(function () { setPortalReady(true); });
|
|
1344
|
-
// 诊断:一次性输出(帮助真机排查 iframe 隔离与 portal
|
|
1382
|
+
// 诊断:一次性输出(帮助真机排查 iframe 隔离与 portal/createRoot 解析结果、PiP 尝试结果)
|
|
1345
1383
|
if (!window.__dbtPortalDiag) {
|
|
1346
1384
|
window.__dbtPortalDiag = true;
|
|
1347
|
-
console.info("[dbt] portal:", portalImpl ? "sync/async ok" : "unavailable",
|
|
1385
|
+
console.info("[dbt] portal:", portalImpl ? "sync/async ok" : "unavailable",
|
|
1386
|
+
"createRoot:", rootSource || "none", "pip:", pipResult,
|
|
1387
|
+
"sameDoc:", window.top === window.self);
|
|
1348
1388
|
}
|
|
1349
1389
|
}, []);
|
|
1350
1390
|
const [sel, setSel] = React.useState(null); // 弹窗内当前选中(null=未选表,右栏显示引导空态)
|
|
@@ -1384,13 +1424,14 @@ window.__ModuleLoader__.load({
|
|
|
1384
1424
|
if (closeRef.current) closeRef.current.focus();
|
|
1385
1425
|
}, []);
|
|
1386
1426
|
|
|
1387
|
-
// 拖动定位:首帧给个保守值,mount
|
|
1427
|
+
// 拖动定位:首帧给个保守值,mount 后按浮窗实测尺寸居中(坐标基准取浮窗所在 document 的视口——DOM 浮窗为主视口,PiP 独立窗口为 PiP 视口)
|
|
1388
1428
|
const dialogRef2 = React.useRef(null);
|
|
1389
1429
|
const [pos, setPos] = React.useState(() => ({ top: 72, left: 12 }));
|
|
1390
1430
|
React.useEffect(() => {
|
|
1391
1431
|
const el = dialogRef2.current;
|
|
1392
1432
|
if (!el) return;
|
|
1393
|
-
const
|
|
1433
|
+
const view = (el.ownerDocument && el.ownerDocument.defaultView) || window;
|
|
1434
|
+
const w = view.innerWidth || 1200, h = view.innerHeight || 800;
|
|
1394
1435
|
setPos({
|
|
1395
1436
|
top: Math.max(0, Math.round((h - el.offsetHeight) / 2)),
|
|
1396
1437
|
left: Math.max(0, Math.round((w - el.offsetWidth) / 2)),
|
|
@@ -1408,7 +1449,8 @@ window.__ModuleLoader__.load({
|
|
|
1408
1449
|
}
|
|
1409
1450
|
function onHeaderPointerMove(e) {
|
|
1410
1451
|
if (!draggingRef.current) return;
|
|
1411
|
-
const
|
|
1452
|
+
const view = (e.currentTarget.ownerDocument && e.currentTarget.ownerDocument.defaultView) || window;
|
|
1453
|
+
const h = view.innerHeight || 800, w = view.innerWidth || 1200;
|
|
1412
1454
|
// clamp 全部用浮窗实测尺寸:整体保持在视口内(窄视口下常量估宽会把窗拖丢)
|
|
1413
1455
|
const dlg = e.currentTarget.parentElement;
|
|
1414
1456
|
const dlgH = (dlg && dlg.offsetHeight) || 720;
|
|
@@ -1544,6 +1586,88 @@ window.__ModuleLoader__.load({
|
|
|
1544
1586
|
);
|
|
1545
1587
|
}
|
|
1546
1588
|
|
|
1589
|
+
/* ---------------- PiP 独立窗口(Document Picture-in-Picture) ---------------- */
|
|
1590
|
+
// OS 级独立窗口:可拖出桌面端主窗口、系统级置顶。内容为完整数据浏览 UI(同 JS 环境复用
|
|
1591
|
+
// BrowseDialog/DangerDialog 与 mock 之外的宿主 fetch 同源);DOM 浮窗作为不可用时的降级。
|
|
1592
|
+
// 打开成功返回 pipWindow,失败返回 null(调用方降级为 DOM 浮窗)。
|
|
1593
|
+
function PipBrowseApp(props) { // {ctx, conns, projectPath, grants, onClose}
|
|
1594
|
+
// 危险操作确认渲染在 PiP 窗口内(Panel 的确认对话框在主窗口,PiP 用户看不见)
|
|
1595
|
+
const [confirmReq, setConfirmReq] = React.useState(null);
|
|
1596
|
+
const askConfirm = React.useCallback((info) => new Promise((resolve) => {
|
|
1597
|
+
setConfirmReq(Object.assign({}, info, { resolve }));
|
|
1598
|
+
}), []);
|
|
1599
|
+
return React.createElement(
|
|
1600
|
+
React.Fragment,
|
|
1601
|
+
null,
|
|
1602
|
+
React.createElement(BrowseDialog, {
|
|
1603
|
+
ctx: props.ctx, conns: props.conns, projectPath: props.projectPath,
|
|
1604
|
+
grants: props.grants, askConfirm: askConfirm, onClose: props.onClose,
|
|
1605
|
+
}),
|
|
1606
|
+
confirmReq ? React.createElement(DangerDialog, { challenge: confirmReq, onClose: () => setConfirmReq(null) }) : null,
|
|
1607
|
+
);
|
|
1608
|
+
}
|
|
1609
|
+
function openBrowsePip(ctx, deps) { // deps: {conns, projectPath, grants, onClosed, onFallback} → 返回 pipWindow | null
|
|
1610
|
+
try {
|
|
1611
|
+
const dpip = window.documentPictureInPicture;
|
|
1612
|
+
// 入口只硬性要求 dpip;createRoot/portal 未就绪不再放弃——手势内先开窗,root 异步就绪后渲染,超时由调用方降级
|
|
1613
|
+
if (!dpip || typeof dpip.requestWindow !== "function") return null;
|
|
1614
|
+
resolveRootAsync(ctx); // createRoot 同步未就绪时,趁 3 秒超时窗口发起宿主异步 import
|
|
1615
|
+
// PiP 只能在用户手势内同步发起 requestWindow,Promise then 里再做 DOM 装配
|
|
1616
|
+
const p = dpip.requestWindow({ width: 1100, height: 720 });
|
|
1617
|
+
pipResult = "opened"; // requestWindow 已发起;拒绝走下方 catch
|
|
1618
|
+
return p.then((w) => {
|
|
1619
|
+
const doc = w.document;
|
|
1620
|
+
// 相对地址基准:PiP 文档是 about:blank,fetch 的相对 API 路径须按主 origin 解析
|
|
1621
|
+
const base = doc.createElement("base");
|
|
1622
|
+
base.href = window.location.origin + "/";
|
|
1623
|
+
doc.head.appendChild(base);
|
|
1624
|
+
// 样式:整套 --dbt-* token 与规则注入 PiP 文档
|
|
1625
|
+
if (styleEl) doc.head.appendChild(doc.importNode(styleEl, true));
|
|
1626
|
+
doc.body.style.margin = "0";
|
|
1627
|
+
// 加载占位:createRoot 异步解析期间给用户可见反馈(PiP 场景少用,不做 i18n key)
|
|
1628
|
+
const ph = doc.createElement("div");
|
|
1629
|
+
ph.style.cssText = "display:flex;align-items:center;justify-content:center;height:100%;font:14px sans-serif;color:#888;";
|
|
1630
|
+
ph.textContent = "正在打开数据浏览…";
|
|
1631
|
+
doc.body.appendChild(ph);
|
|
1632
|
+
let root = null;
|
|
1633
|
+
let settled = false; // root 已渲染或窗口已关闭:晚到的 onRootReady 回调与超时兜底均不再处理
|
|
1634
|
+
const closePip = () => {
|
|
1635
|
+
if (root) { try { root.unmount(); } catch (e) { /* 已卸载 */ } }
|
|
1636
|
+
portalDisabled = false;
|
|
1637
|
+
try { w.close(); } catch (e2) { /* 已关闭 */ }
|
|
1638
|
+
};
|
|
1639
|
+
onRootReady(() => {
|
|
1640
|
+
if (settled) return;
|
|
1641
|
+
settled = true;
|
|
1642
|
+
portalDisabled = true; // PiP root 已在 pipDoc.body 上,禁 portal 防 DOM 被拉回主窗口
|
|
1643
|
+
root = reactCreateRoot(doc.body);
|
|
1644
|
+
root.render(React.createElement(PipBrowseApp, {
|
|
1645
|
+
ctx, conns: deps.conns, projectPath: deps.projectPath, grants: deps.grants,
|
|
1646
|
+
onClose: () => { closePip(); deps.onClosed(); },
|
|
1647
|
+
}));
|
|
1648
|
+
ph.remove();
|
|
1649
|
+
});
|
|
1650
|
+
w.addEventListener("pagehide", () => { // 用户点 PiP 窗口系统关闭钮
|
|
1651
|
+
settled = true;
|
|
1652
|
+
if (root) { try { root.unmount(); } catch (e) { /* 已卸载 */ } }
|
|
1653
|
+
portalDisabled = false;
|
|
1654
|
+
deps.onClosed();
|
|
1655
|
+
});
|
|
1656
|
+
// 超时兜底:3 秒内 createRoot 没就绪 → 关 PiP 清理并回调降级(调用方打开 DOM 浮窗)
|
|
1657
|
+
setTimeout(() => {
|
|
1658
|
+
if (settled) return;
|
|
1659
|
+
settled = true;
|
|
1660
|
+
closePip();
|
|
1661
|
+
deps.onFallback();
|
|
1662
|
+
}, 3000);
|
|
1663
|
+
return w;
|
|
1664
|
+
}).catch((err) => { pipResult = "rejected:" + (err && err.name || "Error"); return null; });
|
|
1665
|
+
} catch (e) {
|
|
1666
|
+
pipResult = "rejected:" + (e && e.name || "Error");
|
|
1667
|
+
return Promise.resolve(null);
|
|
1668
|
+
}
|
|
1669
|
+
}
|
|
1670
|
+
|
|
1547
1671
|
/* ---------------- SQL 控制台 ---------------- */
|
|
1548
1672
|
// Apple 等宽字体栈(规范 §1):优先样式层 --dbt-mono token,fallback 内联栈
|
|
1549
1673
|
const MONO_FONT = "var(--dbt-mono, ui-monospace, \"SF Mono\", Menlo, Consolas, monospace)";
|
|
@@ -1672,6 +1796,23 @@ window.__ModuleLoader__.load({
|
|
|
1672
1796
|
const [confirmReq, setConfirmReq] = React.useState(null); // {statement,danger,reason,resolve}
|
|
1673
1797
|
// 数据浏览浮窗开关:由 Panel 层持有(不随 view 切换卸载),跨 tab 常驻
|
|
1674
1798
|
const [browseOpen, setBrowseOpen] = React.useState(false);
|
|
1799
|
+
// PiP 独立窗口句柄:优先于 DOM 浮窗(OS 级窗口,可拖出桌面端主窗口);存在时 DOM 浮窗不渲染
|
|
1800
|
+
const [pipWin, setPipWin] = React.useState(null);
|
|
1801
|
+
function openBrowse() {
|
|
1802
|
+
if (pipWin) { try { pipWin.focus(); } catch (e) { /* 已关闭 */ } return; }
|
|
1803
|
+
// requestWindow 必须在用户手势内同步发起(本函数仅由 onClick 直调)
|
|
1804
|
+
const p = openBrowsePip(props.ctx, {
|
|
1805
|
+
conns, projectPath, grants,
|
|
1806
|
+
onClosed: () => setPipWin(null),
|
|
1807
|
+
// PiP 超时降级(createRoot 未就绪):清掉窗口句柄并回退 DOM 浮窗
|
|
1808
|
+
onFallback: () => { setPipWin(null); setBrowseOpen(true); },
|
|
1809
|
+
});
|
|
1810
|
+
if (p && typeof p.then === "function") {
|
|
1811
|
+
p.then((w) => { if (w) setPipWin(w); else setBrowseOpen(true); });
|
|
1812
|
+
} else {
|
|
1813
|
+
setBrowseOpen(true);
|
|
1814
|
+
}
|
|
1815
|
+
}
|
|
1675
1816
|
|
|
1676
1817
|
const askConfirm = React.useCallback((info) => new Promise((resolve) => {
|
|
1677
1818
|
setConfirmReq(Object.assign({}, info, { resolve }));
|
|
@@ -1729,7 +1870,7 @@ window.__ModuleLoader__.load({
|
|
|
1729
1870
|
// browse tab:切视图的同时直接打开浮窗(单击即用,不必再进树点表)
|
|
1730
1871
|
React.createElement("button", {
|
|
1731
1872
|
key: v, className: view === v ? "active" : "",
|
|
1732
|
-
onClick: () => { setView(v); if (v === "browse")
|
|
1873
|
+
onClick: () => { setView(v); if (v === "browse") openBrowse(); },
|
|
1733
1874
|
}, label)),
|
|
1734
1875
|
),
|
|
1735
1876
|
(projectEdited || !projectPath)
|
|
@@ -1772,7 +1913,7 @@ window.__ModuleLoader__.load({
|
|
|
1772
1913
|
// 主提示用正常文字色(dbt-muted 11px 次要色压深底对比不足,视觉审查命中)
|
|
1773
1914
|
React.createElement("div", { style: { fontSize: 13 } }, t("browsePanelHint")),
|
|
1774
1915
|
React.createElement("div", { className: "dbt-row" },
|
|
1775
|
-
React.createElement("button", { className: "dbt-btn primary", onClick:
|
|
1916
|
+
React.createElement("button", { className: "dbt-btn primary", onClick: openBrowse }, t("reopenBrowse"))),
|
|
1776
1917
|
) : null,
|
|
1777
1918
|
view === "console" ? React.createElement(ConsoleView, Object.assign({}, shared, { askConfirm })) : null,
|
|
1778
1919
|
),
|
|
@@ -1829,7 +1970,7 @@ window.__ModuleLoader__.load({
|
|
|
1829
1970
|
return ctx.betterSidebar.registerTab({
|
|
1830
1971
|
id: TAB_ID,
|
|
1831
1972
|
title: function () { return t("tabTitle"); },
|
|
1832
|
-
icon
|
|
1973
|
+
icon,
|
|
1833
1974
|
order: 45,
|
|
1834
1975
|
single: true,
|
|
1835
1976
|
component: function (p) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-db-tool",
|
|
3
|
-
"version": "1.5.
|
|
3
|
+
"version": "1.5.5",
|
|
4
4
|
"description": "DSH community plugin: chat-operated multi-database admin tool with sidebar management, project-scoped grants, ro/rw modes and dangerous-operation confirmation",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -39,7 +39,8 @@
|
|
|
39
39
|
"client": {
|
|
40
40
|
"inject": [
|
|
41
41
|
"@deepseek-ai/dsh-client-locale",
|
|
42
|
-
"react-dom"
|
|
42
|
+
"react-dom",
|
|
43
|
+
"react-dom/client"
|
|
43
44
|
],
|
|
44
45
|
"platform": "web"
|
|
45
46
|
}
|