dsh-db-tool 1.5.3 → 1.5.4
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 +92 -7
- package/package.json +1 -1
package/client/client.js
CHANGED
|
@@ -388,7 +388,7 @@ window.__ModuleLoader__.load({
|
|
|
388
388
|
".dbt-chev.leaf{visibility:hidden;}",
|
|
389
389
|
".dbt-treename{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}",
|
|
390
390
|
/* ===== 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);}",
|
|
391
|
+
".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
392
|
".dbt-browse-dialog.dragging{user-select:none;}",
|
|
393
393
|
".dbt-browse-header{flex:none;display:flex;align-items:center;gap:10px;padding:12px 16px;border-bottom:1px solid var(--dbt-separator);}",
|
|
394
394
|
".dbt-browse-title{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-weight:600;font-size:17px;}",
|
|
@@ -493,8 +493,19 @@ window.__ModuleLoader__.load({
|
|
|
493
493
|
(portalPending = portalPending || []).push(cb);
|
|
494
494
|
}
|
|
495
495
|
function mountDialog(children) {
|
|
496
|
-
return portalImpl ? portalImpl(children, document.body) : children;
|
|
496
|
+
return (portalImpl && !portalDisabled) ? portalImpl(children, document.body) : children;
|
|
497
497
|
}
|
|
498
|
+
// PiP 独立窗口渲染时禁 portal:PiP root 本身就挂在 pipDoc.body 上,再 portal 回主 document 会把 DOM 拉出独立窗口
|
|
499
|
+
let portalDisabled = false;
|
|
500
|
+
// createRoot 多级探测(PiP 独立窗口需要独立 React root):react-dom → react-dom/client
|
|
501
|
+
let reactCreateRoot = null;
|
|
502
|
+
try {
|
|
503
|
+
var rd2 = require("react-dom");
|
|
504
|
+
reactCreateRoot = rd2 && typeof rd2.createRoot === "function" ? rd2.createRoot.bind(rd2) : null;
|
|
505
|
+
if (!reactCreateRoot) {
|
|
506
|
+
try { var rdc = require("react-dom/client"); reactCreateRoot = rdc && typeof rdc.createRoot === "function" ? rdc.createRoot.bind(rdc) : null; } catch (e2) { /* 保持 null */ }
|
|
507
|
+
}
|
|
508
|
+
} catch (e) { /* 保持 null,PiP 不可用走 DOM 浮窗降级 */ }
|
|
498
509
|
|
|
499
510
|
/* ---------------- 连接管理 ---------------- */
|
|
500
511
|
const EMPTY_FORM = { id: "", kind: "mysql", name: "", mode: "url", url: "", urlUser: "", urlPassword: "", host: "", port: "", user: "", password: "", database: "", ssl: false };
|
|
@@ -1384,13 +1395,14 @@ window.__ModuleLoader__.load({
|
|
|
1384
1395
|
if (closeRef.current) closeRef.current.focus();
|
|
1385
1396
|
}, []);
|
|
1386
1397
|
|
|
1387
|
-
// 拖动定位:首帧给个保守值,mount
|
|
1398
|
+
// 拖动定位:首帧给个保守值,mount 后按浮窗实测尺寸居中(坐标基准取浮窗所在 document 的视口——DOM 浮窗为主视口,PiP 独立窗口为 PiP 视口)
|
|
1388
1399
|
const dialogRef2 = React.useRef(null);
|
|
1389
1400
|
const [pos, setPos] = React.useState(() => ({ top: 72, left: 12 }));
|
|
1390
1401
|
React.useEffect(() => {
|
|
1391
1402
|
const el = dialogRef2.current;
|
|
1392
1403
|
if (!el) return;
|
|
1393
|
-
const
|
|
1404
|
+
const view = (el.ownerDocument && el.ownerDocument.defaultView) || window;
|
|
1405
|
+
const w = view.innerWidth || 1200, h = view.innerHeight || 800;
|
|
1394
1406
|
setPos({
|
|
1395
1407
|
top: Math.max(0, Math.round((h - el.offsetHeight) / 2)),
|
|
1396
1408
|
left: Math.max(0, Math.round((w - el.offsetWidth) / 2)),
|
|
@@ -1408,7 +1420,8 @@ window.__ModuleLoader__.load({
|
|
|
1408
1420
|
}
|
|
1409
1421
|
function onHeaderPointerMove(e) {
|
|
1410
1422
|
if (!draggingRef.current) return;
|
|
1411
|
-
const
|
|
1423
|
+
const view = (e.currentTarget.ownerDocument && e.currentTarget.ownerDocument.defaultView) || window;
|
|
1424
|
+
const h = view.innerHeight || 800, w = view.innerWidth || 1200;
|
|
1412
1425
|
// clamp 全部用浮窗实测尺寸:整体保持在视口内(窄视口下常量估宽会把窗拖丢)
|
|
1413
1426
|
const dlg = e.currentTarget.parentElement;
|
|
1414
1427
|
const dlgH = (dlg && dlg.offsetHeight) || 720;
|
|
@@ -1544,6 +1557,63 @@ window.__ModuleLoader__.load({
|
|
|
1544
1557
|
);
|
|
1545
1558
|
}
|
|
1546
1559
|
|
|
1560
|
+
/* ---------------- PiP 独立窗口(Document Picture-in-Picture) ---------------- */
|
|
1561
|
+
// OS 级独立窗口:可拖出桌面端主窗口、系统级置顶。内容为完整数据浏览 UI(同 JS 环境复用
|
|
1562
|
+
// BrowseDialog/DangerDialog 与 mock 之外的宿主 fetch 同源);DOM 浮窗作为不可用时的降级。
|
|
1563
|
+
// 打开成功返回 pipWindow,失败返回 null(调用方降级为 DOM 浮窗)。
|
|
1564
|
+
function PipBrowseApp(props) { // {ctx, conns, projectPath, grants, onClose}
|
|
1565
|
+
// 危险操作确认渲染在 PiP 窗口内(Panel 的确认对话框在主窗口,PiP 用户看不见)
|
|
1566
|
+
const [confirmReq, setConfirmReq] = React.useState(null);
|
|
1567
|
+
const askConfirm = React.useCallback((info) => new Promise((resolve) => {
|
|
1568
|
+
setConfirmReq(Object.assign({}, info, { resolve }));
|
|
1569
|
+
}), []);
|
|
1570
|
+
return React.createElement(
|
|
1571
|
+
React.Fragment,
|
|
1572
|
+
null,
|
|
1573
|
+
React.createElement(BrowseDialog, {
|
|
1574
|
+
ctx: props.ctx, conns: props.conns, projectPath: props.projectPath,
|
|
1575
|
+
grants: props.grants, askConfirm: askConfirm, onClose: props.onClose,
|
|
1576
|
+
}),
|
|
1577
|
+
confirmReq ? React.createElement(DangerDialog, { challenge: confirmReq, onClose: () => setConfirmReq(null) }) : null,
|
|
1578
|
+
);
|
|
1579
|
+
}
|
|
1580
|
+
function openBrowsePip(ctx, deps) { // deps: {conns, projectPath, grants, onClosed} → 返回 pipWindow | null
|
|
1581
|
+
try {
|
|
1582
|
+
const dpip = window.documentPictureInPicture;
|
|
1583
|
+
if (!dpip || typeof dpip.requestWindow !== "function" || !reactCreateRoot || !portalImpl) return null;
|
|
1584
|
+
// PiP 只能在用户手势内同步发起 requestWindow,Promise then 里再做 DOM 装配
|
|
1585
|
+
return dpip.requestWindow({ width: 1100, height: 720 }).then((w) => {
|
|
1586
|
+
const doc = w.document;
|
|
1587
|
+
// 相对地址基准:PiP 文档是 about:blank,fetch 的相对 API 路径须按主 origin 解析
|
|
1588
|
+
const base = doc.createElement("base");
|
|
1589
|
+
base.href = window.location.origin + "/";
|
|
1590
|
+
doc.head.appendChild(base);
|
|
1591
|
+
// 样式:整套 --dbt-* token 与规则注入 PiP 文档
|
|
1592
|
+
if (styleEl) doc.head.appendChild(doc.importNode(styleEl, true));
|
|
1593
|
+
doc.body.style.margin = "0";
|
|
1594
|
+
portalDisabled = true; // PiP root 已在 pipDoc.body 上,禁 portal 防 DOM 被拉回主窗口
|
|
1595
|
+
const root = reactCreateRoot(doc.body);
|
|
1596
|
+
const closePip = () => {
|
|
1597
|
+
try { root.unmount(); } catch (e) { /* 已卸载 */ }
|
|
1598
|
+
portalDisabled = false;
|
|
1599
|
+
try { w.close(); } catch (e2) { /* 已关闭 */ }
|
|
1600
|
+
};
|
|
1601
|
+
root.render(React.createElement(PipBrowseApp, {
|
|
1602
|
+
ctx, conns: deps.conns, projectPath: deps.projectPath, grants: deps.grants,
|
|
1603
|
+
onClose: () => { closePip(); deps.onClosed(); },
|
|
1604
|
+
}));
|
|
1605
|
+
w.addEventListener("pagehide", () => { // 用户点 PiP 窗口系统关闭钮
|
|
1606
|
+
portalDisabled = false;
|
|
1607
|
+
try { root.unmount(); } catch (e) { /* 已卸载 */ }
|
|
1608
|
+
deps.onClosed();
|
|
1609
|
+
});
|
|
1610
|
+
return w;
|
|
1611
|
+
}).catch(() => null);
|
|
1612
|
+
} catch (e) {
|
|
1613
|
+
return Promise.resolve(null);
|
|
1614
|
+
}
|
|
1615
|
+
}
|
|
1616
|
+
|
|
1547
1617
|
/* ---------------- SQL 控制台 ---------------- */
|
|
1548
1618
|
// Apple 等宽字体栈(规范 §1):优先样式层 --dbt-mono token,fallback 内联栈
|
|
1549
1619
|
const MONO_FONT = "var(--dbt-mono, ui-monospace, \"SF Mono\", Menlo, Consolas, monospace)";
|
|
@@ -1672,6 +1742,21 @@ window.__ModuleLoader__.load({
|
|
|
1672
1742
|
const [confirmReq, setConfirmReq] = React.useState(null); // {statement,danger,reason,resolve}
|
|
1673
1743
|
// 数据浏览浮窗开关:由 Panel 层持有(不随 view 切换卸载),跨 tab 常驻
|
|
1674
1744
|
const [browseOpen, setBrowseOpen] = React.useState(false);
|
|
1745
|
+
// PiP 独立窗口句柄:优先于 DOM 浮窗(OS 级窗口,可拖出桌面端主窗口);存在时 DOM 浮窗不渲染
|
|
1746
|
+
const [pipWin, setPipWin] = React.useState(null);
|
|
1747
|
+
function openBrowse() {
|
|
1748
|
+
if (pipWin) { try { pipWin.focus(); } catch (e) { /* 已关闭 */ } return; }
|
|
1749
|
+
// requestWindow 必须在用户手势内同步发起(本函数仅由 onClick 直调)
|
|
1750
|
+
const p = openBrowsePip(props.ctx, {
|
|
1751
|
+
conns, projectPath, grants,
|
|
1752
|
+
onClosed: () => setPipWin(null),
|
|
1753
|
+
});
|
|
1754
|
+
if (p && typeof p.then === "function") {
|
|
1755
|
+
p.then((w) => { if (w) setPipWin(w); else setBrowseOpen(true); });
|
|
1756
|
+
} else {
|
|
1757
|
+
setBrowseOpen(true);
|
|
1758
|
+
}
|
|
1759
|
+
}
|
|
1675
1760
|
|
|
1676
1761
|
const askConfirm = React.useCallback((info) => new Promise((resolve) => {
|
|
1677
1762
|
setConfirmReq(Object.assign({}, info, { resolve }));
|
|
@@ -1729,7 +1814,7 @@ window.__ModuleLoader__.load({
|
|
|
1729
1814
|
// browse tab:切视图的同时直接打开浮窗(单击即用,不必再进树点表)
|
|
1730
1815
|
React.createElement("button", {
|
|
1731
1816
|
key: v, className: view === v ? "active" : "",
|
|
1732
|
-
onClick: () => { setView(v); if (v === "browse")
|
|
1817
|
+
onClick: () => { setView(v); if (v === "browse") openBrowse(); },
|
|
1733
1818
|
}, label)),
|
|
1734
1819
|
),
|
|
1735
1820
|
(projectEdited || !projectPath)
|
|
@@ -1772,7 +1857,7 @@ window.__ModuleLoader__.load({
|
|
|
1772
1857
|
// 主提示用正常文字色(dbt-muted 11px 次要色压深底对比不足,视觉审查命中)
|
|
1773
1858
|
React.createElement("div", { style: { fontSize: 13 } }, t("browsePanelHint")),
|
|
1774
1859
|
React.createElement("div", { className: "dbt-row" },
|
|
1775
|
-
React.createElement("button", { className: "dbt-btn primary", onClick:
|
|
1860
|
+
React.createElement("button", { className: "dbt-btn primary", onClick: openBrowse }, t("reopenBrowse"))),
|
|
1776
1861
|
) : null,
|
|
1777
1862
|
view === "console" ? React.createElement(ConsoleView, Object.assign({}, shared, { askConfirm })) : null,
|
|
1778
1863
|
),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-db-tool",
|
|
3
|
-
"version": "1.5.
|
|
3
|
+
"version": "1.5.4",
|
|
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",
|