dsh-db-tool 1.5.1 → 1.5.3

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.
Files changed (2) hide show
  1. package/client/client.js +63 -10
  2. package/package.json +3 -2
package/client/client.js CHANGED
@@ -463,10 +463,37 @@ window.__ModuleLoader__.load({
463
463
  }
464
464
 
465
465
  // --- 弹窗挂载:优先 createPortal 挂 document.body(避免侧栏容器裁剪);宿主未提供 react-dom 时回退 Panel 内 fixed overlay(DangerDialog 同模式) ---
466
- let createPortal = null;
467
- try { createPortal = require("react-dom").createPortal; } catch (e) { /* 留 null,走回退 */ }
466
+ // 三重降级解析(模块级缓存,全模块一次):①模块加载时同步 require(dsh.client.inject 声明 react-dom 后应成功)
467
+ // ②宿主模块系统异步 import(ctx.modules,宿主 rc.8+ 提供)③均不可用则保持 Panel 内回退渲染。
468
+ let portalImpl = null; // 解析成功的 createPortal(缓存,全模块一次)
469
+ let portalTried = false; // 是否已尝试解析(异步 import 只发起一次)
470
+ let portalPending = null; // 订阅解析成功的回调列表
471
+ try {
472
+ var rd = require("react-dom");
473
+ if (rd && typeof rd.createPortal === "function") { portalImpl = rd.createPortal.bind(rd); portalTried = true; }
474
+ } catch (e) { /* 留 null,等异步解析 */ }
475
+ function resolvePortalAsync(ctx) {
476
+ if (portalTried) return;
477
+ portalTried = true;
478
+ if (portalImpl) return;
479
+ var mods = ctx && ctx.modules;
480
+ var p = mods && typeof mods.import === "function" ? mods.import("react-dom") : null;
481
+ if (p && typeof p.then === "function") {
482
+ p.then(function (m) {
483
+ if (m && typeof m.createPortal === "function") {
484
+ portalImpl = m.createPortal.bind(m);
485
+ var waiters = portalPending || []; portalPending = null;
486
+ for (var i = 0; i < waiters.length; i++) waiters[i]();
487
+ }
488
+ }).catch(function () { /* 解析失败保持回退渲染 */ });
489
+ }
490
+ }
491
+ function onPortalReady(cb) {
492
+ if (portalImpl) { cb(); return; }
493
+ (portalPending = portalPending || []).push(cb);
494
+ }
468
495
  function mountDialog(children) {
469
- return createPortal ? createPortal(children, document.body) : children;
496
+ return portalImpl ? portalImpl(children, document.body) : children;
470
497
  }
471
498
 
472
499
  /* ---------------- 连接管理 ---------------- */
@@ -1306,8 +1333,20 @@ window.__ModuleLoader__.load({
1306
1333
  }
1307
1334
 
1308
1335
  function BrowseDialog(props) {
1309
- const { conns, projectPath, grants, askConfirm, onClose } = props;
1336
+ const { conns, projectPath, grants, askConfirm, onClose, ctx } = props;
1310
1337
  // grants / askConfirm:预留给后续单元格编辑的危险操作确认通道,本任务先接住不使用
1338
+ // portal 异步解析接入:portalReady 仅用于触发重渲染(mountDialog 内部读 portalImpl)。
1339
+ // createPortal 不换组件实例——portalReady 切换前后是同一 DOM 节点,pos state 保持,居中定位不受 portal 重挂影响。
1340
+ const [portalReady, setPortalReady] = React.useState(!!portalImpl);
1341
+ React.useEffect(() => {
1342
+ resolvePortalAsync(ctx);
1343
+ onPortalReady(function () { setPortalReady(true); });
1344
+ // 诊断:一次性输出(帮助真机排查 iframe 隔离与 portal 解析结果)
1345
+ if (!window.__dbtPortalDiag) {
1346
+ window.__dbtPortalDiag = true;
1347
+ console.info("[dbt] portal:", portalImpl ? "sync/async ok" : "unavailable", "sameDoc:", window.top === window.self);
1348
+ }
1349
+ }, []);
1311
1350
  const [sel, setSel] = React.useState(null); // 弹窗内当前选中(null=未选表,右栏显示引导空态)
1312
1351
  const [view, setView] = React.useState("structure"); // structure | preview(纯视图切换,不影响数据加载)
1313
1352
  const [schema, setSchema] = React.useState([]);
@@ -1345,8 +1384,18 @@ window.__ModuleLoader__.load({
1345
1384
  if (closeRef.current) closeRef.current.focus();
1346
1385
  }, []);
1347
1386
 
1348
- // 拖动定位:初始停靠视口右上(宽度按 1100 常量估算,.dbt-browse-dialog 为 max-width:1400 的自适应宽)
1349
- const [pos, setPos] = React.useState(() => ({ top: 72, left: Math.max(12, (window.innerWidth || 1200) - 1100 - 24) }));
1387
+ // 拖动定位:首帧给个保守值,mount 后按浮窗实测尺寸居中(侧栏 iframe 视口窄,常量估宽会错位)
1388
+ const dialogRef2 = React.useRef(null);
1389
+ const [pos, setPos] = React.useState(() => ({ top: 72, left: 12 }));
1390
+ React.useEffect(() => {
1391
+ const el = dialogRef2.current;
1392
+ if (!el) return;
1393
+ const w = window.innerWidth || 1200, h = window.innerHeight || 800;
1394
+ setPos({
1395
+ top: Math.max(0, Math.round((h - el.offsetHeight) / 2)),
1396
+ left: Math.max(0, Math.round((w - el.offsetWidth) / 2)),
1397
+ });
1398
+ }, []);
1350
1399
  const draggingRef = React.useRef(null); // {dx, dy}
1351
1400
  const [dragging, setDragging] = React.useState(false);
1352
1401
  function stopDrag() { draggingRef.current = null; setDragging(false); }
@@ -1360,12 +1409,13 @@ window.__ModuleLoader__.load({
1360
1409
  function onHeaderPointerMove(e) {
1361
1410
  if (!draggingRef.current) return;
1362
1411
  const h = window.innerHeight || 800, w = window.innerWidth || 1200;
1363
- // 垂直 clamp 用浮窗实际高度:整体保持在视口内(视觉审查:底部分页栏不得溢出被裁);
1364
- // 水平放宽到至少留 120px 可抓回(允许左右拖出一半)
1365
- const dlgH = (e.currentTarget.parentElement && e.currentTarget.parentElement.offsetHeight) || 720;
1412
+ // clamp 全部用浮窗实测尺寸:整体保持在视口内(窄视口下常量估宽会把窗拖丢)
1413
+ const dlg = e.currentTarget.parentElement;
1414
+ const dlgH = (dlg && dlg.offsetHeight) || 720;
1415
+ const dlgW = (dlg && dlg.offsetWidth) || 1100;
1366
1416
  setPos({
1367
1417
  top: Math.min(Math.max(e.clientY - draggingRef.current.dy, 0), Math.max(0, h - dlgH)),
1368
- left: Math.min(Math.max(e.clientX - draggingRef.current.dx, -(1100 - 120)), w - 120),
1418
+ left: Math.min(Math.max(e.clientX - draggingRef.current.dx, 0), Math.max(0, w - dlgW)),
1369
1419
  });
1370
1420
  }
1371
1421
 
@@ -1419,6 +1469,7 @@ window.__ModuleLoader__.load({
1419
1469
  "div",
1420
1470
  {
1421
1471
  className: "dbt-browse-root dbt-browse-dialog" + (dragging ? " dragging" : ""),
1472
+ ref: dialogRef2,
1422
1473
  style: { position: "fixed", top: pos.top, left: pos.left },
1423
1474
  role: "dialog", "aria-labelledby": "dbt-browse-title",
1424
1475
  onKeyDown: (e) => {
@@ -1730,6 +1781,7 @@ window.__ModuleLoader__.load({
1730
1781
  // 浮窗常驻:portal 到 body 且在 dbt-view 外,切 tab 时不消失
1731
1782
  browseOpen ? React.createElement(BrowseDialog, {
1732
1783
  conns, projectPath, grants, askConfirm,
1784
+ ctx: props.ctx, // 宿主 ctx 透传给浮窗,供 portal 异步解析(ctx.modules.import)
1733
1785
  onClose: () => setBrowseOpen(false),
1734
1786
  }) : null,
1735
1787
  );
@@ -1754,6 +1806,7 @@ window.__ModuleLoader__.load({
1754
1806
  key: "db-tool-" + String(localeKey || "zh"),
1755
1807
  visible: props.visible,
1756
1808
  scope: props.scope,
1809
+ ctx: ctx, // 宿主 ctx 下传,Panel 透传给 BrowseDialog 供 portal 解析
1757
1810
  });
1758
1811
  }
1759
1812
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-db-tool",
3
- "version": "1.5.1",
3
+ "version": "1.5.3",
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",
@@ -38,7 +38,8 @@
38
38
  },
39
39
  "client": {
40
40
  "inject": [
41
- "@deepseek-ai/dsh-client-locale"
41
+ "@deepseek-ai/dsh-client-locale",
42
+ "react-dom"
42
43
  ],
43
44
  "platform": "web"
44
45
  }