🎩 You're Invited:Meet the Socket team at Black Hat in Las Vegas, August 3-6.RSVP
Sign In

navidog

Package Overview
Dependencies
Maintainers
1
Versions
13
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

navidog - npm Package Compare versions

Comparing version
1.2.0
to
1.2.1
+1
dist/assets/_commonjsHelpers-CqkleIqs.js
function e(t){return t&&t.__esModule&&Object.prototype.hasOwnProperty.call(t,"default")?t.default:t}export{e as g};

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display

:root{--bg-app: #ececec;--bg-toolbar: #e8e8e8;--bg-sidebar: #f5f5f5;--bg-main: #ffffff;--bg-info: #fafafa;--bg-statusbar: #e8e8e8;--bg-modal-overlay: rgba(0, 0, 0, .35);--border: #d4d4d4;--border-light: #e6e6e6;--border-dark: #bbb;--select-bg: #2968c8;--select-bg-hover: #e8f0fe;--select-text: #ffffff;--text: #333333;--text-secondary: #777777;--text-muted: #aaaaaa;--accent: #2968c8;--accent-hover: #1a54b0;--danger: #d93025;--success: #1e8e3e;--warning: #f9a825;--table-header-bg: #f0f0f0;--table-row-alt: #f9f9fc;--table-row-hover: #eef3fb;--table-border: #e2e2e2;--tab-active-bg: #ffffff;--tab-inactive-bg: #e8e8e8;--tab-border: #cccccc;--toolbar-h: 72px;--statusbar-h: 26px;--sidebar-w: 240px;--info-w: 360px;--tab-h: 32px;--font-sans: -apple-system, BlinkMacSystemFont, "PingFang SC", "Helvetica Neue", "Segoe UI", sans-serif;--font-mono: "SF Mono", Menlo, Monaco, Consolas, "Liberation Mono", monospace;--font-size: 13px;--font-size-sm: 13px;--font-size-xs: 12px}[data-theme=dark]{--bg-app: #1e1e1e;--bg-toolbar: #252526;--bg-sidebar: #1e1e1e;--bg-main: #1e1e1e;--bg-info: #252526;--bg-statusbar: #1e1e1e;--bg-modal-overlay: rgba(0, 0, 0, .6);--border: #3c3c3c;--border-light: #2d2d2d;--border-dark: #4a4a4a;--select-bg: #264f78;--select-bg-hover: #2a2d2e;--select-text: #ffffff;--text: #cccccc;--text-secondary: #969696;--text-muted: #5a5a5a;--accent: #0078d4;--accent-hover: #1a8aed;--danger: #f44747;--success: #4ec9b0;--warning: #cca700;--table-header-bg: #2d2d2d;--table-row-alt: #232323;--table-row-hover: #2a2d2e;--table-border: #3c3c3c;--tab-active-bg: #1e1e1e;--tab-inactive-bg: #2d2d2d;--tab-border: #3c3c3c;color-scheme:dark}[data-theme=dark] input,[data-theme=dark] select,[data-theme=dark] textarea{background:#2d2d2d;color:#ccc;border-color:#3c3c3c}[data-theme=dark] .history-panel,[data-theme=dark] .combo-box-dropdown{background:#252526}[data-theme=dark] .cli-container{background:#1b1b1b}[data-theme=dark] .cli-output{color:#ccc}[data-theme=dark] .cli-welcome{color:#5a5a5a;border-bottom-color:#3c3c3c}[data-theme=dark] .cli-result{color:#969696}[data-theme=dark] .import-error-list{background:#3a2020;border-color:#5a3030}[data-theme=dark] .toolbar-btn:hover{background:#ffffff14}[data-theme=dark] .toolbar-btn:active{background:#ffffff1f}[data-theme=dark] .toolbar-btn.active{background:#264f78;color:#6cb6ff}*,*:before,*:after{box-sizing:border-box}body{margin:0;font-family:var(--font-sans);font-size:var(--font-size);color:var(--text);background:var(--bg-app);overflow:hidden}button,input,textarea,select{font:inherit}button{cursor:pointer;border:none;background:none}ul,ol{list-style:none;margin:0;padding:0}#root{height:100vh}.app-shell{display:flex;flex-direction:column;height:100vh;overflow:hidden}.toolbar{height:var(--toolbar-h);background:var(--bg-toolbar);border-bottom:1px solid var(--border);display:flex;align-items:center;padding:0 12px;gap:2px;flex-shrink:0;-webkit-user-select:none;user-select:none}.toolbar-btn{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:3px;padding:6px 12px;border-radius:6px;color:var(--text);font-size:var(--font-size-xs);min-width:56px;transition:background .12s}.toolbar-btn:hover{background:#0000000f}.toolbar-btn:active{background:#0000001a}.toolbar-btn.active{background:#e8f0fe;color:#1a73e8}.toolbar-btn .tb-icon{font-size:24px;line-height:1;display:flex;align-items:center;justify-content:center;width:32px;height:32px}.toolbar-btn .tb-label{white-space:nowrap}.toolbar-sep{width:1px;height:44px;background:var(--border);margin:0 6px;flex-shrink:0}.toolbar-spacer{flex:1}.workspace{flex:1;display:flex;overflow:hidden;min-height:0}.sidebar{width:var(--sidebar-w);min-width:140px;background:var(--bg-sidebar);border-right:none;display:flex;flex-direction:column;flex-shrink:0;overflow:hidden}.panel-resize-handle.vertical{width:5px;cursor:col-resize;background:var(--border);flex-shrink:0;position:relative;transition:background .15s;z-index:5;align-self:stretch}.panel-resize-handle.vertical:before{content:"";position:absolute;inset:0 -3px;z-index:6}.panel-resize-handle.vertical:hover,.panel-resize-handle.vertical:active{background:var(--accent)}.panel-resize-handle.vertical:after{content:"";position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);height:30px;width:2px;border-left:1px solid rgba(0,0,0,.15);border-right:1px solid rgba(0,0,0,.15)}.sidebar-header{padding:8px 12px 6px;font-size:var(--font-size-sm);font-weight:600;color:var(--text-secondary);border-bottom:1px solid var(--border-light);display:flex;align-items:center;gap:6px;flex-shrink:0}.sidebar-search{margin:6px 8px;padding:4px 8px;border:1px solid var(--border);border-radius:4px;font-size:var(--font-size-sm);background:#fff;outline:none;flex-shrink:0}.sidebar-search:focus{border-color:var(--accent)}.sidebar-search::placeholder{color:var(--text-muted)}.tree-container{flex:1;overflow-y:auto;overflow-x:hidden;padding:2px 0}.tree-node{display:flex;align-items:center;padding:2px 8px 2px 0;cursor:pointer;font-size:var(--font-size-sm);white-space:nowrap;-webkit-user-select:none;user-select:none;border:1px solid transparent;min-height:24px}.tree-node:hover{background:var(--select-bg-hover)}.tree-node.selected{background:var(--select-bg);color:var(--select-text)}.tree-node.selected:hover{background:var(--accent-hover)}.tree-arrow{width:16px;height:16px;display:flex;align-items:center;justify-content:center;font-size:10px;color:var(--text-secondary);flex-shrink:0;transition:transform .12s ease}.tree-arrow.expanded{transform:rotate(90deg)}.tree-node.selected .tree-arrow{color:var(--select-text)}.conn-color-dot{width:8px;height:8px;border-radius:50%;flex-shrink:0}.tree-icon{width:18px;height:18px;display:flex;align-items:center;justify-content:center;flex-shrink:0;margin-right:4px;font-size:14px}.tree-label{overflow:hidden;text-overflow:ellipsis}.main-area{flex:1;display:flex;flex-direction:column;min-width:0;background:var(--bg-main);overflow:hidden}.tab-bar{display:flex;align-items:flex-end;background:var(--tab-inactive-bg);border-bottom:1px solid var(--border);flex-shrink:0;min-height:var(--tab-h);overflow-x:auto;overflow-y:hidden;-webkit-user-select:none;user-select:none}.tab-bar::-webkit-scrollbar{height:0}.tab-item{display:flex;align-items:center;gap:6px;padding:6px 12px;font-size:var(--font-size-sm);border:1px solid var(--tab-border);border-bottom:none;border-radius:4px 4px 0 0;background:var(--tab-inactive-bg);color:var(--text-secondary);margin-left:-1px;cursor:pointer;white-space:nowrap;max-width:200px;position:relative;transition:background .1s}.tab-item:first-child{margin-left:4px}.tab-item:hover{background:#f0f0f0}.tab-item.active{background:var(--tab-active-bg);color:var(--text);border-bottom:1px solid var(--tab-active-bg);z-index:1;font-weight:500}.tab-item .tab-icon{font-size:14px}.tab-item .tab-title{overflow:hidden;text-overflow:ellipsis}.tab-close{width:16px;height:16px;display:flex;align-items:center;justify-content:center;border-radius:3px;font-size:14px;color:var(--text-muted);flex-shrink:0;line-height:1}.tab-close:hover{background:#0000001a;color:var(--text)}.tab-item.active .tab-close{color:var(--text-secondary)}.tab-content{flex:1;display:flex;flex-direction:column;overflow:hidden;min-height:0}.empty-state{flex:1;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:12px;color:var(--text-muted);font-size:14px}.empty-state .empty-icon{font-size:48px;opacity:.4}.empty-state .empty-title{font-size:16px;color:var(--text-secondary);font-weight:500}.objects-toolbar{display:flex;align-items:center;padding:6px 8px;gap:6px;border-bottom:1px solid var(--border-light);flex-shrink:0}.objects-toolbar button{padding:3px 6px;border-radius:3px;font-size:var(--font-size-sm);color:var(--text-secondary)}.objects-toolbar button:hover{background:#0000000f}.objects-search{padding:3px 8px;border:1px solid var(--border);border-radius:4px;font-size:var(--font-size-sm);width:180px;background:#fff;outline:none;margin-left:auto}.objects-search:focus{border-color:var(--accent)}.objects-view-toggle{display:flex;gap:2px}.view-btn{padding:4px 6px!important;font-size:14px!important}.view-btn.active{background:#00000014!important}.objects-grid{flex:1;overflow:auto}.objects-table{width:100%;border-collapse:collapse}.objects-table th{position:sticky;top:0;background:var(--table-header-bg);border-bottom:1px solid var(--table-border);padding:5px 12px;text-align:left;font-size:var(--font-size-sm);font-weight:500;color:var(--text-secondary);white-space:nowrap;z-index:1}.objects-table td{padding:4px 12px;border-bottom:1px solid var(--border-light);font-size:var(--font-size-sm);white-space:nowrap}.objects-table tr:hover td{background:var(--table-row-hover)}.objects-table .obj-icon{margin-right:6px;font-size:14px}.data-toolbar{display:flex;align-items:center;padding:4px 8px;gap:4px;border-bottom:1px solid var(--border-light);flex-shrink:0;background:var(--bg-info)}.data-toolbar button{padding:3px 8px;border-radius:3px;font-size:var(--font-size-sm);color:var(--text-secondary)}.data-toolbar button:hover{background:#0000000f}.filter-label{font-size:var(--font-size-xs);color:var(--text-secondary);white-space:nowrap}.filter-input{flex:1;max-width:300px;min-width:120px;padding:2px 8px;border:1px solid var(--border);border-radius:4px;font-size:var(--font-size-xs);font-family:var(--font-mono);outline:none;transition:border-color .15s}.filter-input:focus{border-color:var(--accent)}.filter-clear{background:none;border:none;color:var(--danger);cursor:pointer;font-size:12px;padding:2px 4px;border-radius:3px}.filter-clear:hover{background:#fee}.sort-indicator{font-size:11px;color:var(--accent);font-weight:700}.col-header{position:relative;cursor:default}.col-header-label{flex:1}.col-sort-trigger{opacity:0;font-size:10px;color:var(--fg-dim);cursor:pointer;padding:0 2px;transition:opacity .15s;-webkit-user-select:none;user-select:none}.col-header:hover .col-sort-trigger{opacity:1}.col-sort-menu{position:absolute;top:100%;left:0;z-index:200;background:#fff;border:1px solid var(--border);border-radius:6px;box-shadow:0 4px 16px #00000038;min-width:140px;padding:4px 0;font-size:13px;font-weight:400;white-space:nowrap}.col-sort-item{padding:6px 12px;cursor:pointer;display:flex;align-items:center;gap:2px}.col-sort-item:hover{background:var(--hover)}.col-sort-sep{height:1px;background:var(--border);margin:4px 0}.filter-tag{display:inline-flex;align-items:center;gap:4px;background:#e8f0fe;color:#1a73e8;font-size:12px;padding:2px 8px;border-radius:12px;margin-right:4px;white-space:nowrap}.filter-tag strong{font-weight:600}.filter-tag-close{background:none;border:none;color:#1a73e8;cursor:pointer;font-size:11px;padding:0 2px;line-height:1}.filter-tag-close:hover{color:#c62828}.filter-clear-all{background:none;border:1px solid var(--border);border-radius:4px;color:var(--fg-dim);font-size:11px;cursor:pointer;padding:2px 8px;margin-left:4px}.filter-clear-all:hover{background:var(--hover)}.toolbar-icon-btn{background:none;border:1px solid transparent;border-radius:4px;font-size:16px;cursor:pointer;padding:2px 6px}.toolbar-icon-btn:hover{background:var(--hover)}.toolbar-icon-btn.active{background:#e8f0fe;border-color:#4285f4}.navi-filter-panel{border-bottom:1px solid #d8d8d8;background:#fafafa}.navi-filter-conditions{padding:10px 14px;min-height:40px}.navi-filter-row{display:flex;align-items:center;gap:6px;padding:3px 0;font-size:13px}.navi-filter-check{width:16px;height:16px;margin:0;cursor:pointer;accent-color:#4285f4}.navi-filter-col-select{color:#06c;font-weight:600;font-size:13px;border:none;background:transparent;cursor:pointer;outline:none;padding:2px 4px;appearance:none;-webkit-appearance:none}.navi-filter-col-select:hover{text-decoration:underline}.navi-filter-op{color:#333;padding:1px 2px;border:1px solid #d0d0d0;border-radius:3px;background:#fff;font-size:12px;cursor:pointer;min-width:50px;outline:none}.navi-filter-val{color:#333;cursor:pointer;padding:2px 8px;background:#fff;border:1px solid #d0d0d0;border-radius:3px;min-width:20px;max-width:200px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.navi-filter-val:hover{border-color:#999}.navi-filter-connector{color:#999;font-size:12px;padding:0 4px}.navi-filter-btn-icon{display:inline-flex;align-items:center;justify-content:center;width:22px;height:22px;font-size:14px;font-weight:700;color:#fff;background:#4285f4;border-radius:3px;cursor:pointer;margin-left:2px}.navi-filter-btn-icon:hover{background:#3367d6}.navi-filter-btn-group{background:#7baaf7;font-size:11px}.navi-filter-btn-group:hover{background:#5a94e6}.navi-filter-remove{background:none;border:none;color:#ccc;cursor:pointer;font-size:12px;padding:0 4px;margin-left:auto}.navi-filter-remove:hover{color:#c62828}.navi-filter-group{margin:2px 0}.navi-filter-group-paren{font-size:15px;font-weight:600;color:#555;padding:0 2px}.navi-filter-group-children{padding-left:24px}.navi-filter-toolbar{display:flex;align-items:center;gap:2px;padding:4px 14px;background:#f0f0f0;border-top:1px solid #e0e0e0}.navi-filter-tool{width:26px;height:24px;display:flex;align-items:center;justify-content:center;font-size:14px;color:#555;cursor:pointer;border-radius:4px}.navi-filter-tool:hover{background:#d6d6d6}.navi-filter-apply{font-size:13px;color:#333;cursor:pointer;padding:2px 10px;border-radius:4px;font-weight:500}.navi-filter-apply:hover{background:#d6d6d6}.navi-filter-status{font-size:12px;color:#999;margin-left:8px}.navi-picker-overlay{position:absolute;inset:0;z-index:300}.navi-picker{position:absolute;top:60px;right:40px;width:380px;max-height:440px;background:#f6f6f6;border-radius:10px;box-shadow:0 8px 32px #00000040;display:flex;flex-direction:column;overflow:hidden;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,sans-serif}.navi-picker-input{margin:12px 14px 0;padding:7px 8px;border:2px solid #4a90d9;border-radius:4px;font-size:13px;outline:none;background:#fff}.navi-picker-input:focus{border-color:#2563eb}.navi-picker-section{padding:10px 14px 4px;font-size:13px;color:#666;font-weight:500}.navi-picker-list{flex:1;overflow-y:auto;min-height:60px;max-height:220px;margin:4px 10px;background:#fff;border:1px solid #d4d4d4;border-radius:6px}.navi-picker-loading{text-align:center;padding:20px;color:#999;font-size:13px}.navi-picker-item{display:flex;align-items:center;gap:8px;padding:5px 10px;cursor:pointer;font-size:13px;border-bottom:1px solid #f0f0f0;color:#333}.navi-picker-item:last-child{border-bottom:none}.navi-picker-item:hover{background:#f0f5ff}.navi-picker-item input[type=checkbox]{width:15px;height:15px;cursor:pointer;flex-shrink:0;margin:0}.navi-picker-text{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;flex:1}.navi-picker-search-row{display:flex;align-items:center;margin:6px 14px;border:1px solid #d4d4d4;border-radius:6px;background:#fff;overflow:hidden}.navi-picker-search-icon{padding:5px 6px 5px 10px;font-size:14px;color:#999;flex-shrink:0}.navi-picker-search{flex:1;border:none;outline:none;padding:6px 8px 6px 2px;font-size:13px;background:transparent}.navi-picker-actions{display:flex;justify-content:flex-end;gap:8px;padding:8px 14px 12px}.navi-picker-btn{padding:5px 22px;border:1px solid #c0c0c0;border-radius:6px;background:#fff;font-size:13px;cursor:pointer;color:#333}.navi-picker-btn:hover{background:#eaeaea}.navi-picker-btn-ok{background:#4285f4;color:#fff;border-color:#4285f4;font-weight:500}.navi-picker-btn-ok:hover{background:#3367d6}.data-grid-wrap{flex:1;overflow:auto;min-height:0}.data-grid{width:auto;border-collapse:collapse;table-layout:auto}.query-result-grid{width:max-content;table-layout:fixed}.data-grid th{top:0;background:var(--table-header-bg);border-bottom:2px solid var(--table-border);border-right:1px solid var(--table-border);padding:4px 10px;text-align:left;font-size:var(--font-size-sm);font-weight:600;white-space:nowrap;z-index:1;min-width:60px;position:sticky;overflow:visible}.col-resize-handle{position:absolute;right:-2px;top:0;bottom:0;width:5px;cursor:col-resize;z-index:3;-webkit-user-select:none;user-select:none}.col-resize-handle:hover,.col-resize-handle:active{background:var(--accent)}.data-grid td{padding:4px 10px;border-bottom:1px solid var(--border-light);border-right:1px solid var(--border-light);font-size:var(--font-size-xs);white-space:nowrap;max-width:400px;overflow:hidden;text-overflow:ellipsis}.data-grid tr:nth-child(2n) td{background:var(--table-row-alt)}.data-grid tr:hover td{background:var(--table-row-hover)}.data-grid tr.row-selected td{background:#cce5ff!important}.data-grid tr.row-selected:hover td{background:#b8daff!important}.data-grid tr.row-selected .row-num{background:#b8daff!important;color:#004085}.data-grid td.cell-selected{outline:2px solid #4285f4;outline-offset:-2px}.data-grid tr{cursor:default}.data-grid .row-num{color:var(--text-muted);text-align:right;background:var(--table-header-bg);border-right:2px solid var(--table-border);font-family:var(--font-mono);font-size:var(--font-size-xs);min-width:40px;padding:3px 6px;position:sticky;left:0;z-index:0}.data-grid th.row-num-header{min-width:40px;z-index:2;position:sticky;left:0}.cell-null{color:var(--text-muted);font-style:italic}.data-footer{display:flex;align-items:center;justify-content:space-between;padding:4px 8px;border-top:1px solid var(--border-light);background:var(--bg-info);font-size:var(--font-size-xs);color:var(--text-secondary);flex-shrink:0;gap:8px;flex-wrap:wrap}.data-footer-right{display:flex;align-items:center;gap:12px}.data-footer-sql{font-family:var(--font-mono);color:var(--text-muted);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;max-width:50%}.cell-edit-input{width:100%;min-width:60px;padding:1px 4px;border:2px solid var(--accent);border-radius:2px;font-family:var(--font-mono);font-size:var(--font-size-xs);outline:none;background:#fff;box-sizing:border-box}.cell-modified{background:#fff3cd!important}.data-grid tr.row-new td{background:#d4edda!important}.data-grid tr.row-new .row-num{background:#c3e6cb!important;color:#155724}.data-grid tr.row-deleted td{background:#f8d7da!important;text-decoration:line-through;color:#999}.data-grid tr.row-deleted .row-num{background:#f5c6cb!important;color:#721c24}.data-action-bar{display:flex;align-items:center;gap:2px;flex-shrink:0}.action-btn{width:26px;height:22px;border:1px solid var(--border);border-radius:3px;background:#fff;cursor:pointer;font-size:14px;line-height:1;display:flex;align-items:center;justify-content:center;color:var(--text-secondary);transition:background .1s,color .1s}.action-btn:hover:not(:disabled){background:var(--table-row-hover);color:var(--text)}.action-btn:disabled{opacity:.35;cursor:default}.action-btn.apply{color:#28a745;font-weight:700}.action-btn.apply:hover:not(:disabled){background:#d4edda}.action-btn.cancel{color:#dc3545;font-weight:700}.action-btn.cancel:hover:not(:disabled){background:#f8d7da}.action-status{font-size:var(--font-size-xs);color:var(--text-secondary);margin-left:8px}.pagination{display:flex;align-items:center;gap:2px}.page-btn{padding:2px 8px!important;border:1px solid var(--border)!important;border-radius:3px!important;font-size:11px!important;background:#fff!important;cursor:pointer!important;line-height:1.4!important;min-width:24px!important;text-align:center!important}.page-btn:hover{background:var(--select-bg-hover)!important}.page-btn:disabled{opacity:.35;cursor:default!important;background:#f5f5f5!important}.page-info{padding:0 6px;font-size:11px;color:var(--text-secondary);white-space:nowrap}.page-input{width:38px;padding:1px 4px;margin:0 4px;border:1px solid var(--border);border-radius:3px;font-size:11px;font-weight:600;color:var(--accent);text-align:center;background:#fff;outline:none;line-height:1.4}.page-input:focus{border-color:var(--accent);box-shadow:0 0 0 2px #2968c826}.page-count{font-size:11px;color:var(--text-secondary);white-space:nowrap;padding-left:4px;border-left:1px solid var(--border-light)}.query-pane{flex:1;display:flex;flex-direction:column;min-height:0;overflow:hidden}.query-toolbar{display:flex;align-items:center;padding:2px 8px;gap:2px;border-bottom:1px solid var(--border-light);flex-shrink:0;background:var(--bg-info);height:32px}.query-toolbar select{padding:2px 6px;border:1px solid var(--border);border-radius:3px;font-size:var(--font-size-sm);background:var(--bg-main);color:var(--text)}.query-snippet-select{min-width:88px;max-width:160px;height:24px}.query-db-selector{display:flex;align-items:center;gap:4px}.query-db-selector select{padding:2px 20px 2px 6px;border:1px solid var(--border);border-radius:4px;font-size:12px;background:var(--bg-main);min-width:140px;cursor:pointer;appearance:auto;color:var(--text);height:24px}.query-db-selector select:focus{outline:none;border-color:var(--accent);box-shadow:0 0 0 2px #2968c826}.tb-icon-btn{display:flex;align-items:center;justify-content:center;width:28px;height:24px;border:none;background:transparent;color:var(--text-muted, #666);border-radius:4px;cursor:pointer;padding:0;transition:all .12s ease}.tb-icon-btn:hover{background:#0000000f;color:var(--text)}.tb-icon-btn:active{background:#0000001a}.tb-icon-btn:disabled{opacity:.3;cursor:default}.tb-icon-btn:disabled:hover{background:transparent}.tb-icon-btn.tb-run{color:#2ea043}.tb-icon-btn.tb-run:hover{background:#2ea0431a;color:#238636}.toolbar-sep{width:1px;height:16px;background:var(--border);margin:0 4px;flex-shrink:0}.run-btn{padding:3px 12px!important;background:var(--accent)!important;color:#fff!important;border-radius:4px!important;font-weight:500!important;font-size:var(--font-size-sm)!important}.run-btn:hover{background:var(--accent-hover)!important}.run-btn:disabled{opacity:.5}.query-split-container{flex:1;display:flex;flex-direction:column;min-height:0;overflow:hidden}.query-editor-area{min-height:60px;height:200px;flex-shrink:0;position:relative;overflow:hidden}.query-editor-area .cm-editor,.query-editor-area .cm-scroller,.query-editor-area .cm-content,.query-editor-area .cm-line{user-select:text!important;-webkit-user-select:text!important}.query-split-handle{height:4px;background:var(--border-light);cursor:row-resize;flex-shrink:0;position:relative;transition:background .15s}.query-split-handle:hover,.query-split-handle:active{background:var(--accent)}.query-split-handle:after{content:"";position:absolute;left:50%;top:50%;transform:translate(-50%,-50%);width:30px;height:2px;border-top:1px solid rgba(0,0,0,.15);border-bottom:1px solid rgba(0,0,0,.15)}.query-results{flex:1;display:flex;flex-direction:column;min-height:0;overflow:hidden;contain:strict;isolation:isolate}.result-view-tabs{display:flex;align-items:stretch;background:var(--bg-info);border-bottom:1px solid var(--border-light);flex-shrink:0;gap:0}.result-view-actions{display:flex;align-items:center;gap:6px;padding:0 8px}.result-action-btn{min-width:38px;height:26px;padding:0 10px;border:1px solid var(--border);border-radius:6px;background:#fff;cursor:pointer;font-size:12px;line-height:1;font-weight:600;display:inline-flex;align-items:center;justify-content:center;color:var(--text-secondary);transition:background .12s,color .12s,border-color .12s,box-shadow .12s;white-space:nowrap}.result-action-btn:hover{background:var(--table-row-hover);color:var(--text);border-color:var(--accent-soft)}.result-action-btn:focus-visible{outline:none;border-color:var(--accent);box-shadow:0 0 0 2px #2968c829}.result-action-btn-icon{min-width:30px;width:30px;padding:0;font-size:13px}.result-view-tab{padding:6px 16px;font-size:var(--font-size-xs);color:var(--text-secondary);border:1px solid transparent;border-bottom:none;border-radius:4px 4px 0 0;cursor:pointer;background:none;margin-bottom:-1px;transition:all .12s}.result-view-tab:hover{color:var(--text);background:#00000008}.result-view-tab.active{color:var(--accent);background:#fff;border-color:var(--border-light);font-weight:500}.result-grid-wrap{flex:1;display:flex;flex-direction:column;overflow:hidden;min-height:0;contain:layout paint}.virtual-grid-scroll{contain:layout paint;isolation:isolate;will-change:scroll-position}.query-messages{padding:12px 16px;font-family:var(--font-mono);font-size:var(--font-size-xs);line-height:1.7;color:var(--text);overflow:auto}.query-message-item{margin-bottom:12px;padding-bottom:12px;border-bottom:1px solid var(--border-light)}.query-message-item:last-child{border-bottom:none;margin-bottom:0}.qm-sql{color:var(--text);word-break:break-all}.qm-info{color:#666}.qm-time{color:#999}.info-panel{width:clamp(240px,20vw,500px);background:var(--bg-info);border-left:none;display:flex;flex-direction:column;flex-shrink:0;overflow:hidden}.info-header{padding:16px 16px 12px;border-bottom:1px solid var(--border-light);display:flex;align-items:center;gap:12px;flex-shrink:0}.info-icon{width:48px;height:48px;border-radius:8px;display:flex;align-items:center;justify-content:center;font-size:28px;flex-shrink:0}.info-icon.conn-icon{background:linear-gradient(135deg,#e8d5b7,#c9a96e)}.info-icon.db-icon{background:linear-gradient(135deg,#b8e6c8,#5cb87a)}.info-icon.table-icon{background:linear-gradient(135deg,#b8d4f0,#5a9fd4)}.info-title{font-size:16px;font-weight:600;line-height:1.2}.info-subtitle{font-size:var(--font-size-xs);color:var(--text-muted);margin-top:2px}.info-body{flex:1;overflow-y:auto;padding:12px 16px}.info-row{display:flex;flex-direction:column;gap:2px;margin-bottom:10px}.info-label{font-size:var(--font-size-xs);color:var(--text-muted);font-weight:500}.info-value{font-size:var(--font-size-sm);color:var(--text);word-break:break-all}.info-tab-bar{display:flex;padding:0 8px;border-bottom:1px solid var(--border-light);flex-shrink:0;background:var(--bg-info);gap:0}.info-tab{padding:6px 12px;font-size:var(--font-size-xs);color:var(--text-secondary);border:none;border-bottom:2px solid transparent;background:none;cursor:pointer;border-radius:0;font-weight:500;transition:color .12s,border-color .12s}.info-tab:hover{color:var(--text)}.info-tab.active{color:var(--accent);border-bottom-color:var(--accent)}.ddl-area{margin-top:0;padding:12px;background:#f8f8f8;border:none;border-radius:0;font-family:var(--font-mono);font-size:var(--font-size-xs);line-height:1.6;white-space:pre;overflow:auto;color:var(--text);-webkit-user-select:text;user-select:text}.sql-kw{color:#1a0dab}.sql-type{color:#0b7285}.sql-str{color:#e65100}.sql-num{color:#6a1b9a}.sql-comment{color:#6a9955;font-style:italic}.sql-punct{color:#777}.ddl-body{padding:0!important}.ddl-body .ddl-area{flex:1;max-height:none;height:100%}[data-theme=dark] .ddl-area{background:#1e1e1e}[data-theme=dark] .sql-kw{color:#569cd6}[data-theme=dark] .sql-type{color:#4ec9b0}[data-theme=dark] .sql-str{color:#ce9178}[data-theme=dark] .sql-num{color:#b5cea8}[data-theme=dark] .sql-comment{color:#6a9955}[data-theme=dark] .sql-punct{color:gray}.columns-body{padding:0!important;overflow:auto}.info-inline-actions{display:flex;justify-content:flex-end;gap:8px;padding:8px;border-bottom:1px solid var(--border-light);background:var(--bg-info)}.info-columns-table{width:100%;border-collapse:collapse;font-size:var(--font-size-xs)}.info-columns-table th{position:sticky;top:0;background:var(--table-header-bg);border-bottom:1px solid var(--table-border);padding:4px 8px;text-align:left;font-weight:600;color:var(--text-secondary);white-space:nowrap;z-index:1}.info-columns-table td{padding:3px 8px;border-bottom:1px solid var(--border-light);white-space:nowrap}.info-columns-table tr:hover td{background:var(--table-row-hover)}.info-columns-table .col-name{font-weight:500;font-family:var(--font-mono);color:var(--text)}.info-columns-table .col-type{color:var(--accent);font-family:var(--font-mono)}.info-columns-table .col-key{color:var(--warning);font-weight:600}.status-bar{height:var(--statusbar-h);background:var(--bg-statusbar);border-top:1px solid var(--border);display:flex;align-items:center;padding:0 12px;gap:16px;font-size:var(--font-size-xs);color:var(--text-secondary);flex-shrink:0;-webkit-user-select:none;user-select:none}.status-dot{width:7px;height:7px;border-radius:50%;display:inline-block;margin-right:4px}.status-dot.online{background:var(--success)}.status-dot.offline{background:var(--text-muted)}.modal-overlay{position:fixed;inset:0;background:var(--bg-modal-overlay);display:flex;align-items:center;justify-content:center;z-index:1000;animation:fade-in .15s ease-out}.modal-card{width:560px;max-height:80vh;background:#fff;border-radius:8px;box-shadow:0 12px 40px #00000040;display:flex;flex-direction:column;animation:slide-up .2s ease-out}.modal-title-bar{padding:14px 20px 12px;border-bottom:1px solid var(--border-light);display:flex;align-items:center;justify-content:space-between}.modal-title-bar h3{margin:0;font-size:14px;font-weight:600}.modal-close{width:24px;height:24px;display:flex;align-items:center;justify-content:center;border-radius:4px;font-size:18px;color:var(--text-secondary)}.modal-close:hover{background:#0000000f}.modal-tabs{display:flex;padding:0 20px;border-bottom:1px solid var(--border-light)}.modal-tab{padding:8px 14px;font-size:var(--font-size-sm);color:var(--text-secondary);border-bottom:2px solid transparent}.modal-tab:hover{color:var(--text)}.modal-tab.active{color:var(--accent);border-bottom-color:var(--accent);font-weight:500}.modal-body{padding:20px;flex:1;overflow-y:auto}.modal-row{display:grid;grid-template-columns:80px 1fr;align-items:center;gap:8px;margin-bottom:12px}.modal-row label{font-size:var(--font-size-sm);color:var(--text-secondary);text-align:right}.modal-row input{padding:6px 10px;border:1px solid var(--border);border-radius:4px;font-size:var(--font-size-sm);outline:none;background:#fff}.modal-row input:focus{border-color:var(--accent)}.modal-row-split{display:grid;grid-template-columns:80px 1fr 60px 1fr;align-items:center;gap:8px;margin-bottom:12px}.modal-row-split label{font-size:var(--font-size-sm);color:var(--text-secondary);text-align:right}.modal-row-split input{padding:6px 10px;border:1px solid var(--border);border-radius:4px;font-size:var(--font-size-sm);outline:none;background:#fff}.modal-row-split input:focus{border-color:var(--accent)}.modal-diagram{display:flex;align-items:center;justify-content:center;gap:20px;padding:12px 0 20px}.modal-diagram-node{display:flex;flex-direction:column;align-items:center;gap:4px;font-size:var(--font-size-sm);color:var(--text-secondary)}.modal-diagram-icon{font-size:32px}.modal-diagram-line{width:40px;height:2px;background:var(--border)}.modal-checkbox{display:flex;align-items:center;gap:8px;margin-bottom:12px;padding-left:88px}.modal-checkbox input{width:16px;height:16px}.modal-checkbox span{font-size:var(--font-size-sm);color:var(--text-secondary)}.modal-footer{padding:12px 20px;border-top:1px solid var(--border-light);display:flex;align-items:center;justify-content:space-between;gap:8px}.modal-footer-left,.modal-footer-right{display:flex;gap:8px}.btn{padding:6px 16px;border-radius:4px;font-size:var(--font-size-sm);font-weight:500;border:1px solid var(--border);background:#fff;color:var(--text);transition:background .1s}.btn:hover{background:#f0f0f0}.btn-primary{background:var(--accent);color:#fff;border-color:var(--accent)}.btn-primary:hover{background:var(--accent-hover)}.btn-danger{color:var(--danger);border-color:#d930254d}.btn-danger:hover{background:#d930250f}.btn-sm{padding:3px 10px;font-size:var(--font-size-xs)}.btn:disabled{opacity:.5;cursor:default}.btn-link{color:var(--accent);font-size:var(--font-size-sm);padding:4px 8px}.btn-link:hover{text-decoration:underline}.profiles-section{margin-top:16px;border-top:1px solid var(--border-light);padding-top:12px}.profiles-section h4{margin:0 0 8px;font-size:var(--font-size-sm);font-weight:600;color:var(--text-secondary)}.profile-item{display:flex;align-items:center;justify-content:space-between;padding:6px 8px;border-radius:4px;cursor:pointer;font-size:var(--font-size-sm)}.profile-item:hover{background:var(--select-bg-hover)}.profile-item .profile-name{font-weight:500}.profile-item .profile-host{color:var(--text-muted);font-size:var(--font-size-xs)}.profile-actions{display:flex;gap:4px}@keyframes slide-up{0%{opacity:0;transform:translateY(16px)}to{opacity:1;transform:translateY(0)}}.resize-handle{width:4px;cursor:col-resize;background:transparent;transition:background .2s;flex-shrink:0}.resize-handle:hover{background:var(--accent)}::-webkit-scrollbar{width:8px;height:8px}::-webkit-scrollbar-track{background:transparent}::-webkit-scrollbar-thumb{background:#ccc;border-radius:4px}::-webkit-scrollbar-thumb:hover{background:#aaa}.context-menu-overlay{position:fixed;inset:0;z-index:800}.context-menu{position:fixed;z-index:801;min-width:200px;max-height:calc(100vh - 16px);overflow-y:auto;background:#fffffff5;-webkit-backdrop-filter:blur(20px) saturate(180%);backdrop-filter:blur(20px) saturate(180%);border:1px solid rgba(0,0,0,.12);border-radius:8px;box-shadow:0 8px 32px #0000002e,0 2px 8px #00000014;padding:4px 0;animation:ctx-pop .12s ease-out;font-size:var(--font-size-sm);-webkit-user-select:none;user-select:none}.ctx-item{display:flex;align-items:center;gap:8px;padding:6px 16px 6px 12px;cursor:pointer;color:var(--text);border-radius:4px;margin:0 4px;white-space:nowrap}.ctx-item:hover{background:var(--select-bg);color:#fff}.ctx-item.danger{color:var(--danger)}.ctx-item.danger:hover{background:var(--danger);color:#fff}.ctx-item.disabled{color:var(--text-muted);cursor:default}.ctx-item.disabled:hover{background:none;color:var(--text-muted)}.ctx-item .ctx-icon{width:18px;text-align:center;font-size:14px;flex-shrink:0}.ctx-item .ctx-label{flex:1}.ctx-item .ctx-shortcut{color:var(--text-muted);font-size:var(--font-size-xs);margin-left:16px}.ctx-item:hover .ctx-shortcut{color:#ffffffb3}.ctx-item.has-submenu{position:relative}.ctx-submenu{display:none;position:absolute;left:100%;top:-4px;min-width:200px;background:var(--bg-info);border:1px solid var(--border);border-radius:8px;padding:4px 0;box-shadow:0 6px 24px #00000026;z-index:1100}.ctx-item.has-submenu:hover>.ctx-submenu{display:block}.ctx-sep{height:1px;background:#00000014;margin:4px 8px}@keyframes ctx-pop{0%{opacity:0;transform:scale(.95)}to{opacity:1;transform:scale(1)}}[data-theme=dark] .context-menu{background:#2d2d2df7;border-color:#4a4a4a;box-shadow:0 8px 32px #00000080}[data-theme=dark] .ctx-sep{background:#ffffff14}[data-theme=dark] .ctx-submenu{background:#2d2d2df7;border-color:#4a4a4a;box-shadow:0 8px 32px #00000080}[data-theme=dark] .context-menu-overlay{background:transparent}[data-theme=dark] .cm-editor{background:#1e1e1e!important}[data-theme=dark] .cm-editor .cm-content{caret-color:#aeafad}[data-theme=dark] .cm-editor .cm-cursor{border-left-color:#aeafad!important}[data-theme=dark] .cm-editor .cm-activeLine{background:#2a2a2a!important}[data-theme=dark] .cm-editor .cm-gutters{background:#1e1e1e!important;border-right-color:#3c3c3c!important;color:#5a5a5a!important}[data-theme=dark] .cm-editor .cm-activeLineGutter{background:#2a2a2a!important;color:#ccc!important}[data-theme=dark] .cm-editor .cm-selectionBackground{background:#264f78!important}[data-theme=dark] .cm-editor .cm-matchingBracket{background:#3a5a3a!important;outline-color:#5a8a5a!important}[data-theme=dark] .cm-editor .cm-panels{background:#252526!important;border-bottom-color:#3c3c3c!important}[data-theme=dark] .cm-editor .cm-tooltip{background:#252526!important;border-color:#4a4a4a!important}[data-theme=dark] .cm-editor .cm-tooltip-autocomplete ul>li[aria-selected]{background:#264f78!important}[data-theme=dark] .cm-editor .cm-completionDetail{color:#6a6a6a!important}[data-theme=dark] .result-view-tab.active{background:var(--bg-main);color:#6cb6ff;border-color:var(--border-light)}[data-theme=dark] .result-view-tab:hover{background:#ffffff0d}[data-theme=dark] .qm-info{color:#8b949e}[data-theme=dark] .qm-time{color:#6e7681}[data-theme=dark] .query-split-handle{background:#30363d}[data-theme=dark] .query-split-handle:after{border-top-color:#ffffff26;border-bottom-color:#ffffff26}[data-theme=dark] .data-grid th{background:#21262d;color:#c9d1d9;border-color:#30363d}[data-theme=dark] .data-grid td{border-color:#21262d}[data-theme=dark] .data-grid tr:nth-child(2n) td{background:#ffffff05}[data-theme=dark] .data-grid tr:hover td{background:#ffffff0f}[data-theme=dark] .data-grid tr.row-selected td{background:#1c3a5c!important}[data-theme=dark] .data-grid tr.row-selected:hover td{background:#214a6e!important}[data-theme=dark] .data-grid tr.row-selected .row-num{background:#214a6e!important;color:#6cb6ff}[data-theme=dark] .data-grid .row-num{background:#161b22;color:#484f58;border-color:#21262d}[data-theme=dark] .data-grid td.cell-selected{outline-color:#58a6ff}[data-theme=dark] .col-sort-popup{background:#21262d;border-color:#30363d}[data-theme=dark] .col-sort-item:hover{background:#ffffff14}[data-theme=dark] .page-btn{color:#8b949e;border-color:#30363d}[data-theme=dark] .page-btn:hover:not(:disabled){background:#ffffff14}[data-theme=dark] .page-input{background:#0d1117;color:#c9d1d9;border-color:#30363d}[data-theme=dark] .page-count{color:#6e7681}[data-theme=dark] .summary-kv{border-color:#21262d}[data-theme=dark] .summary-kv .sk-label{color:#8b949e}[data-theme=dark] .summary-kv .sk-value{color:#c9d1d9}[data-theme=dark] .query-toolbar{background:#161b22;border-bottom-color:#30363d}[data-theme=dark] .tb-icon-btn{color:#8b949e}[data-theme=dark] .tb-icon-btn:hover{background:#ffffff14;color:#c9d1d9}[data-theme=dark] .tb-icon-btn:active{background:#ffffff1f}[data-theme=dark] .tb-icon-btn.tb-run{color:#3fb950}[data-theme=dark] .tb-icon-btn.tb-run:hover{background:#3fb95026;color:#3fb950}[data-theme=dark] .toolbar-sep{background:#30363d}[data-theme=dark] .run-btn{background:#238636!important;color:#fff!important}[data-theme=dark] .run-btn:hover{background:#2ea043!important}[data-theme=dark] .query-toolbar select,[data-theme=dark] .query-db-selector select{background:#21262d;color:#c9d1d9;border-color:#30363d}[data-theme=dark] .query-db-selector select:focus{border-color:#58a6ff;box-shadow:0 0 0 2px #58a6ff33}[data-theme=dark] .result-view-tab.active{background:#0d1117;border-color:#30363d}[data-theme=dark] .history-panel{background:#161b22;border-color:#30363d}[data-theme=dark] .history-item{border-bottom-color:#21262d}[data-theme=dark] .history-item:hover{background:#ffffff0a}[data-theme=dark] .history-panel-header{border-bottom-color:#30363d}[data-theme=dark] .empty-state{color:#484f58}[data-theme=dark] .tab-bar{background:#161b22;border-bottom-color:#30363d}[data-theme=dark] .tab-item{color:#8b949e;border-color:transparent}[data-theme=dark] .tab-item:hover{background:#ffffff0a}[data-theme=dark] .tab-item.active{background:var(--bg-main);color:#c9d1d9;border-color:#30363d;border-bottom-color:var(--bg-main)}[data-theme=dark] .tab-close:hover{background:#ffffff1a}[data-theme=dark] .action-bar{background:#161b22;border-bottom-color:#30363d}[data-theme=dark] .action-btn{color:#8b949e}[data-theme=dark] .action-btn:hover{background:#ffffff14;color:#c9d1d9}[data-theme=dark] .pagination-bar{background:#161b22;border-top-color:#30363d}.notice-toast{position:fixed;top:calc(var(--toolbar-h) + 8px);right:12px;padding:8px 16px;border-radius:6px;font-size:var(--font-size-sm);z-index:900;animation:slide-in-right .2s ease-out;max-width:400px;box-shadow:0 4px 16px #00000026}.notice-toast.success{background:#e6f4ea;color:#137333;border:1px solid #a8dab5}.notice-toast.error{background:#fce8e6;color:#c5221f;border:1px solid #f5c6cb}@keyframes slide-in-right{0%{opacity:0;transform:translate(20px)}to{opacity:1;transform:translate(0)}}.history-panel{position:absolute;top:100%;left:0;width:480px;max-height:360px;background:#fff;border:1px solid var(--border);border-radius:8px;box-shadow:0 8px 30px #00000026;z-index:100;display:flex;flex-direction:column;overflow:hidden;margin-top:4px}.history-panel-header{display:flex;align-items:center;justify-content:space-between;padding:8px 12px;border-bottom:1px solid var(--border-light);font-size:12px;font-weight:600}.history-panel-header button{background:none;border:none;cursor:pointer;font-size:14px;padding:2px 4px;border-radius:4px}.history-panel-header button:hover{background:#0000000f}.history-empty{padding:24px;text-align:center;font-size:12px;color:var(--text-muted)}.history-list{overflow-y:auto;flex:1}.history-item{padding:6px 12px;cursor:pointer;border-bottom:1px solid var(--border-light);transition:background 80ms}.history-item:hover{background:var(--select-bg-hover)}.history-item:last-child{border-bottom:none}.history-item code{font-family:var(--font-mono);font-size:11px;color:var(--text);white-space:pre-wrap;word-break:break-all;line-height:1.4}.cli-container{height:100%;display:flex;flex-direction:column;background:#fafbfc;cursor:text}.cli-output{flex:1;overflow-y:auto;padding:12px 16px;font-family:SF Mono,Monaco,Menlo,Consolas,monospace;font-size:13px;line-height:1.5;color:#333}.cli-welcome{color:#888;padding-bottom:8px;border-bottom:1px solid #e0e0e0;margin-bottom:8px;font-size:12px}.cli-entry{margin-bottom:6px}.cli-prompt-line{display:flex;align-items:baseline}.cli-prompt{color:#2e7d32;font-weight:600;flex-shrink:0;-webkit-user-select:none;user-select:none}.cli-cmd{color:#1565c0}.cli-result{margin:2px 0 0;white-space:pre;font-family:inherit;font-size:12px;color:#444;overflow-x:auto}.cli-result.cli-error{color:#c62828}.cli-input-line{display:flex;align-items:center;padding-top:2px}.cli-input{flex:1;background:transparent;border:none;outline:none;color:#1565c0;font-family:inherit;font-size:13px;caret-color:#333}.cli-input::placeholder{color:#aaa}.cli-input:disabled{opacity:.5}.import-wizard-overlay{position:fixed;inset:0;background:var(--bg-modal-overlay);z-index:1000;display:flex;align-items:center;justify-content:center;animation:fade-in .15s ease-out}@keyframes fade-in{0%{opacity:0}to{opacity:1}}.import-wizard-modal{background:var(--bg-main);border-radius:10px;box-shadow:0 20px 60px #0000004d;width:760px;max-width:90vw;max-height:85vh;display:flex;flex-direction:column;overflow:hidden;animation:modal-in .2s ease-out}@keyframes modal-in{0%{opacity:0;transform:translateY(20px) scale(.97)}to{opacity:1;transform:translateY(0) scale(1)}}.import-wizard-header{display:flex;align-items:center;justify-content:space-between;padding:14px 20px;border-bottom:1px solid var(--border-light);flex-shrink:0}.import-wizard-title{font-size:15px;font-weight:600}.import-close-btn{width:28px;height:28px;display:flex;align-items:center;justify-content:center;border-radius:6px;font-size:14px;color:var(--text-secondary)}.import-close-btn:hover{background:#00000014}.import-step-indicator{display:flex;align-items:center;justify-content:center;gap:4px;padding:14px 20px 10px;flex-shrink:0}.import-step-dot{display:flex;flex-direction:column;align-items:center;gap:4px;flex:1;position:relative}.import-step-dot:after{content:"";position:absolute;top:12px;left:calc(50% + 14px);right:calc(-50% + 14px);height:2px;background:var(--border)}.import-step-dot:last-child:after{display:none}.import-step-dot.done:after{background:var(--accent)}.import-step-dot-circle{width:24px;height:24px;border-radius:50%;background:var(--border-light);color:var(--text-secondary);display:flex;align-items:center;justify-content:center;font-size:11px;font-weight:600;z-index:1;transition:all .2s}.import-step-dot.active .import-step-dot-circle{background:var(--accent);color:#fff;box-shadow:0 0 0 3px #2968c833}.import-step-dot.done .import-step-dot-circle{background:var(--success);color:#fff}.import-step-dot-label{font-size:11px;color:var(--text-muted);white-space:nowrap}.import-step-dot.active .import-step-dot-label{color:var(--accent);font-weight:500}.import-step-dot.done .import-step-dot-label{color:var(--success)}.import-wizard-body{flex:1;overflow-y:auto;padding:16px 24px;min-height:300px}.import-step-title{font-size:15px;font-weight:600;margin-bottom:4px}.import-step-desc{font-size:12px;color:var(--text-secondary);margin-bottom:14px}.import-dropzone{border:2px dashed var(--border);border-radius:10px;padding:32px 20px;text-align:center;cursor:pointer;transition:border-color .2s,background .2s;margin-bottom:14px}.import-dropzone:hover{border-color:var(--accent);background:#2968c80a}.import-dropzone-hint{display:flex;flex-direction:column;align-items:center;gap:6px;color:var(--text-secondary);font-size:13px}.import-file-info{display:flex;align-items:center;justify-content:center;gap:8px;font-size:13px}.import-file-icon{font-size:24px}.import-file-name{font-weight:500}.import-file-size{color:var(--text-muted);font-size:12px}.import-options-row{display:flex;align-items:center;gap:6px;font-size:12px;color:var(--text-secondary);flex-wrap:wrap}.import-options-row select{padding:3px 6px;border:1px solid var(--border);border-radius:4px;font-size:12px;background:#fff}.import-options-row input[type=checkbox]{margin:0;accent-color:var(--accent)}.import-preview-table-wrap{max-height:340px;overflow:auto;border:1px solid var(--border-light);border-radius:6px}.import-preview-table{width:100%;border-collapse:collapse;font-size:12px}.import-preview-table th{position:sticky;top:0;background:var(--table-header-bg);padding:5px 10px;text-align:left;font-weight:500;color:var(--text-secondary);border-bottom:1px solid var(--table-border);white-space:nowrap;z-index:1}.import-preview-table td{padding:3px 10px;border-bottom:1px solid var(--border-light);white-space:nowrap;max-width:200px;overflow:hidden;text-overflow:ellipsis}.import-preview-table .import-row-num{color:var(--text-muted);text-align:center;width:36px;font-size:11px}.import-preview-table .cell-null{color:var(--text-muted);font-style:italic}.import-preview-hint{font-size:11px;color:var(--text-muted);text-align:center;padding:6px 0}.import-target-form{display:flex;flex-direction:column;gap:12px}.import-form-row{display:flex;align-items:center;gap:8px}.import-form-row label{width:80px;font-size:13px;font-weight:500;flex-shrink:0;text-align:right}.import-form-row select{flex:1;padding:6px 10px;border:1px solid var(--border);border-radius:5px;font-size:13px;background:#fff}.import-loading{font-size:12px;color:var(--accent)}.import-mapping-table-wrap{max-height:360px;overflow:auto;border:1px solid var(--border-light);border-radius:6px}.import-mapping-table{width:100%;border-collapse:collapse;font-size:12px}.import-mapping-table th{position:sticky;top:0;background:var(--table-header-bg);padding:6px 10px;text-align:left;font-weight:500;color:var(--text-secondary);border-bottom:1px solid var(--table-border);z-index:1}.import-mapping-table td{padding:4px 10px;border-bottom:1px solid var(--border-light)}.import-mapping-table code{background:#0000000a;padding:1px 5px;border-radius:3px;font-family:var(--font-mono);font-size:12px}.import-mapping-table select{width:100%;padding:4px 6px;border:1px solid var(--border);border-radius:4px;font-size:12px;background:#fff}.import-mapping-skipped{opacity:.4}.import-preview-cell{max-width:120px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--text-secondary);font-family:var(--font-mono);font-size:11px}.import-mapping-summary{font-size:12px;color:var(--text-secondary);text-align:right;padding:8px 0 0}.import-stats-grid{display:grid;grid-template-columns:repeat(4,1fr);gap:12px;margin-bottom:16px}.import-stat{background:var(--bg-info);border:1px solid var(--border-light);border-radius:8px;padding:12px;text-align:center}.import-stat-label{font-size:11px;color:var(--text-secondary);margin-bottom:4px}.import-stat-value{font-size:20px;font-weight:600}.import-progress-bar-wrap{height:8px;background:var(--border-light);border-radius:4px;overflow:hidden;margin-bottom:6px}.import-progress-bar{height:100%;background:linear-gradient(90deg,var(--accent),#4fc3f7);border-radius:4px;transition:width .3s ease}.import-progress-text{font-size:12px;color:var(--text-secondary);text-align:center}.import-error-log{margin-top:14px}.import-error-list{max-height:120px;overflow-y:auto;border:1px solid #f5c6cb;border-radius:6px;background:#fef5f5;padding:8px 12px}.import-error-item{font-size:11px;color:#c62828;padding:2px 0;font-family:var(--font-mono)}.import-wizard-footer{display:flex;align-items:center;padding:12px 20px;border-top:1px solid var(--border-light);gap:8px;flex-shrink:0}.import-btn{padding:7px 18px;border-radius:6px;font-size:13px;font-weight:500;border:none;cursor:pointer;transition:background .12s}.import-btn:disabled{opacity:.4;cursor:not-allowed}.import-btn-primary{background:var(--accent);color:#fff}.import-btn-primary:hover:not(:disabled){background:var(--accent-hover)}.import-btn-secondary{background:var(--border-light);color:var(--text)}.import-btn-secondary:hover:not(:disabled){background:var(--border)}.combo-box-input{width:100%;padding:6px 10px;border:1px solid var(--border);border-radius:5px;font-size:13px;background:#fff;outline:none;transition:border-color .15s}.combo-box-input:focus{border-color:var(--accent)}.combo-box-dropdown{position:absolute;top:100%;left:0;right:0;z-index:100;background:#fff;border:1px solid var(--border);border-top:none;border-radius:0 0 6px 6px;box-shadow:0 6px 20px #00000026;max-height:240px;overflow-y:auto}.combo-box-item{display:flex;align-items:center;justify-content:space-between;padding:6px 10px;font-size:12px;cursor:pointer;transition:background 80ms}.combo-box-item:hover{background:var(--select-bg-hover)}.combo-box-item.selected{background:var(--select-bg);color:var(--select-text)}.combo-box-item.combo-box-new{color:var(--accent);border-bottom:1px solid var(--border-light)}.combo-box-meta{font-size:11px;color:var(--text-muted)}.combo-box-item.selected .combo-box-meta{color:#ffffffb3}.combo-box-empty{padding:8px 10px;font-size:12px;color:var(--text-muted);text-align:center}.export-step-title{font-size:15px;font-weight:600;margin-bottom:4px}.export-step-desc{font-size:12px;color:var(--text-secondary);margin-bottom:14px}.export-format-list{display:flex;flex-direction:column;gap:6px}.export-format-item{display:flex;align-items:center;gap:10px;padding:10px 14px;border:1px solid var(--border-light);border-radius:8px;cursor:pointer;transition:border-color .15s,background .15s}.export-format-item:hover{border-color:var(--border);background:#00000004}.export-format-item.selected{border-color:var(--accent);background:#2968c80a}.export-format-item input[type=radio]{accent-color:var(--accent);margin:0}.export-format-info{display:flex;flex-direction:column;gap:2px}.export-format-label{font-size:13px;font-weight:500}.export-format-desc{font-size:11px;color:var(--text-muted)}.export-form-row{display:flex;align-items:center;gap:8px;margin-bottom:12px}.export-form-row label{width:70px;font-size:13px;font-weight:500;flex-shrink:0;text-align:right}.export-form-row select{flex:1;padding:6px 10px;border:1px solid var(--border);border-radius:5px;font-size:13px;background:#fff}.export-table-search-row{display:flex;align-items:center;gap:4px;margin-bottom:6px}.export-table-search{flex:1;padding:5px 10px;border:1px solid var(--border);border-radius:5px;font-size:12px;background:#fff;outline:none}.export-table-search:focus{border-color:var(--accent)}.export-table-search-clear{padding:2px 6px;border:1px solid var(--border);border-radius:4px;font-size:12px;background:#fff;cursor:pointer;color:var(--text-muted)}.export-table-search-clear:hover{color:var(--danger);border-color:var(--danger)}.export-table-list-header{font-size:12px;padding:4px 0;color:var(--text-secondary);display:flex;align-items:center;gap:6px}.export-table-list-header input[type=checkbox]{accent-color:var(--accent)}.export-table-list{border:1px solid var(--border-light);border-radius:6px;max-height:280px;overflow-y:auto}.export-table-list-row{display:flex;align-items:center;gap:8px;padding:5px 10px;font-size:12px;border-bottom:1px solid var(--border-light)}.export-table-list-row:last-child{border-bottom:none}.export-table-list-head{background:var(--table-header-bg);font-weight:500;color:var(--text-secondary);position:sticky;top:0;z-index:1}.export-table-list-row.disabled{opacity:.4}.export-table-list-row input[type=checkbox]{accent-color:var(--accent);margin:0}.export-table-name{display:flex;align-items:center;gap:4px}.export-table-icon{font-size:14px}.export-filename-input{width:100%;padding:2px 6px;border:1px solid var(--border-light);border-radius:3px;font-size:12px;font-family:var(--font-mono);background:#fff}.export-filename-input:focus{border-color:var(--accent);outline:none}.export-loading{font-size:12px;color:var(--accent);padding:20px;text-align:center}.export-col-list{border:1px solid var(--border-light);border-radius:6px;max-height:300px;overflow-y:auto;padding:4px 0}.export-col-item{display:flex;align-items:center;gap:8px;padding:4px 14px;cursor:pointer;font-size:13px;transition:background .1s}.export-col-item:hover{background:var(--select-bg-hover)}.export-col-item input[type=checkbox]{accent-color:var(--accent);margin:0}.export-col-name{flex:1}.export-col-type{font-size:11px;color:var(--text-muted);font-family:var(--font-mono)}.export-col-key{font-size:12px}.export-col-actions{display:flex;align-items:center;gap:8px;padding:8px 0 0;font-size:12px}.export-col-actions button{padding:3px 10px;border:1px solid var(--border);border-radius:4px;font-size:12px;background:#fff;cursor:pointer}.export-col-actions button:hover{background:var(--select-bg-hover)}.export-col-actions input[type=checkbox]{accent-color:var(--accent)}.export-options-section{margin-bottom:16px}.export-options-title{font-size:13px;font-weight:600;margin-bottom:8px;padding-bottom:4px;border-bottom:1px solid var(--border-light)}.export-options-grid{display:flex;flex-direction:column;gap:8px}.export-options-grid input[type=checkbox]{accent-color:var(--accent)}.export-option-row{display:flex;align-items:center;gap:8px;font-size:13px}.export-option-row span:first-child{width:110px;text-align:right;color:var(--text-secondary);flex-shrink:0}.export-option-row select{padding:3px 8px;border:1px solid var(--border);border-radius:4px;font-size:12px;background:#fff}.export-stats-row{display:flex;gap:20px;margin-bottom:12px;font-size:13px}.export-stat-item{display:flex;gap:6px}.export-stat-label{font-weight:500;color:var(--text-secondary)}.export-log-box{border:1px solid var(--border-light);border-radius:6px;background:var(--bg-info);min-height:180px;max-height:280px;overflow-y:auto;padding:8px 12px;font-family:var(--font-mono);font-size:12px;margin-bottom:12px}.export-log-line{padding:2px 0;line-height:1.5}.td-container{display:flex;flex-direction:column;height:100%;background:var(--bg-main)}.td-topbar{display:flex;align-items:center;justify-content:space-between;padding:8px 12px;border-bottom:1px solid var(--border);background:var(--bg-info);gap:12px;flex-shrink:0}.td-topbar-left{display:flex;align-items:center;gap:8px}.td-name-label{font-size:12px;color:var(--text-secondary);font-weight:500}.td-name-input{width:220px;padding:4px 8px;border:1px solid var(--border);border-radius:4px;font-size:13px;font-family:var(--font-mono);background:var(--bg-main);color:var(--text)}.td-topbar-right{display:flex;gap:6px}.td-btn{padding:4px 10px;border:1px solid var(--border);border-radius:4px;background:var(--bg-info);color:var(--text);font-size:12px;cursor:pointer;white-space:nowrap}.td-btn:hover{background:var(--select-bg-hover)}.td-btn:disabled{opacity:.4;cursor:default}.td-btn-primary{background:var(--accent);color:#fff;border-color:var(--accent)}.td-btn-primary:hover{background:var(--accent-hover)}.td-tab-bar{display:flex;border-bottom:1px solid var(--border);background:var(--bg-info);flex-shrink:0}.td-tab{padding:6px 16px;font-size:12px;font-weight:500;color:var(--text-secondary);background:none;border:none;border-bottom:2px solid transparent;cursor:pointer}.td-tab:hover{color:var(--text)}.td-tab.active{color:var(--accent);border-bottom-color:var(--accent)}.td-field-toolbar{display:flex;align-items:center;gap:4px;padding:6px 12px;border-bottom:1px solid var(--border-light);background:var(--bg-info);flex-shrink:0}.td-toolbar-sep{width:1px;height:18px;background:var(--border);margin:0 4px}.td-grid-wrapper{flex:1;overflow:auto}.td-grid{width:100%;border-collapse:collapse;font-size:12px}.td-grid thead th{position:sticky;top:0;background:var(--table-header-bg);border-bottom:1px solid var(--border);padding:5px 6px;text-align:left;font-weight:600;color:var(--text-secondary);font-size:11px;white-space:nowrap;z-index:1}.td-grid tbody tr{border-bottom:1px solid var(--border-light);cursor:pointer}.td-grid tbody tr:hover{background:var(--table-row-hover)}.td-grid tbody tr.td-row-selected{background:var(--select-bg-hover)}.td-grid td{padding:1px 2px}.td-cell-num{text-align:center;color:var(--text-muted);font-size:10px;width:30px}.td-cell-input{width:100%;padding:3px 6px;border:1px solid transparent;border-radius:3px;font-size:12px;font-family:var(--font-mono);background:transparent;color:var(--text);outline:none}.td-cell-input:focus{border-color:var(--accent);background:var(--bg-main)}.td-cell-select{width:100%;padding:3px 4px;border:1px solid transparent;border-radius:3px;font-size:12px;font-family:var(--font-mono);background:transparent;color:var(--text);outline:none;cursor:pointer}.td-cell-select:focus{border-color:var(--accent);background:var(--bg-main)}.td-cell-center{text-align:center}.td-cell-short{width:100%}.td-sql-preview{flex:1;overflow:auto;padding:12px;background:var(--bg-main)}.td-sql-preview pre{margin:0;font-family:var(--font-mono);font-size:12px;line-height:1.6;color:var(--text);white-space:pre-wrap;word-break:break-all}
import{g as ve}from"./_commonjsHelpers-CqkleIqs.js";function be(G,ae){for(var Q=0;Q<ae.length;Q++){const m=ae[Q];if(typeof m!="string"&&!Array.isArray(m)){for(const M in m)if(M!=="default"&&!(M in G)){const B=Object.getOwnPropertyDescriptor(m,M);B&&Object.defineProperty(G,M,B.get?B:{enumerable:!0,get:()=>m[M]})}}}return Object.freeze(Object.defineProperty(G,Symbol.toStringTag,{value:"Module"}))}var se={exports:{}};var Ee=se.exports,ge;function we(){return ge||(ge=1,(function(G,ae){((Q,m)=>{G.exports=m()})(Ee,function Q(){var m=typeof self<"u"?self:typeof window<"u"?window:m!==void 0?m:{},M,B=!m.document&&!!m.postMessage,oe=m.IS_PAPA_WORKER||!1,te={},ye=0,u={};function K(e){this._handle=null,this._finished=!1,this._completed=!1,this._halted=!1,this._input=null,this._baseIndex=0,this._partialLine="",this._rowCount=0,this._start=0,this._nextChunk=null,this.isFirstChunk=!0,this._completeResults={data:[],errors:[],meta:{}},(function(t){var r=he(t);r.chunkSize=parseInt(r.chunkSize),t.step||t.chunk||(r.chunkSize=null),this._handle=new le(r),(this._handle.streamer=this)._config=r}).call(this,e),this.parseChunk=function(t,r){var n=parseInt(this._config.skipFirstNLines)||0;if(this.isFirstChunk&&0<n){let l=this._config.newline;l||(i=this._config.quoteChar||'"',l=this._handle.guessLineEndings(t,i)),t=[...t.split(l).slice(n)].join(l)}this.isFirstChunk&&k(this._config.beforeFirstChunk)&&(i=this._config.beforeFirstChunk(t))!==void 0&&(t=i),this.isFirstChunk=!1,this._halted=!1;var n=this._partialLine+t,i=(this._partialLine="",this._handle.parse(n,this._baseIndex,!this._finished));if(!this._handle.paused()&&!this._handle.aborted()){if(t=i.meta.cursor,n=(this._finished||(this._partialLine=n.substring(t-this._baseIndex),this._baseIndex=t),i&&i.data&&(this._rowCount+=i.data.length),this._finished||this._config.preview&&this._rowCount>=this._config.preview),oe)m.postMessage({results:i,workerId:u.WORKER_ID,finished:n});else if(k(this._config.chunk)&&!r){if(this._config.chunk(i,this._handle),this._handle.paused()||this._handle.aborted())return void(this._halted=!0);this._completeResults=i=void 0}return this._config.step||this._config.chunk||(this._completeResults.data=this._completeResults.data.concat(i.data),this._completeResults.errors=this._completeResults.errors.concat(i.errors),this._completeResults.meta=i.meta),this._completed||!n||!k(this._config.complete)||i&&i.meta.aborted||(this._config.complete(this._completeResults,this._input),this._completed=!0),n||i&&i.meta.paused||this._nextChunk(),i}this._halted=!0},this._sendError=function(t){k(this._config.error)?this._config.error(t):oe&&this._config.error&&m.postMessage({workerId:u.WORKER_ID,error:t,finished:!1})}}function re(e){var t;(e=e||{}).chunkSize||(e.chunkSize=u.RemoteChunkSize),K.call(this,e),this._nextChunk=B?function(){this._readChunk(),this._chunkLoaded()}:function(){this._readChunk()},this.stream=function(r){this._input=r,this._nextChunk()},this._readChunk=function(){if(this._finished)this._chunkLoaded();else{if(t=new XMLHttpRequest,this._config.withCredentials&&(t.withCredentials=this._config.withCredentials),B||(t.onload=H(this._chunkLoaded,this),t.onerror=H(this._chunkError,this)),t.open(this._config.downloadRequestBody?"POST":"GET",this._input,!B),this._config.downloadRequestHeaders){var r,n=this._config.downloadRequestHeaders;for(r in n)t.setRequestHeader(r,n[r])}var i;this._config.chunkSize&&(i=this._start+this._config.chunkSize-1,t.setRequestHeader("Range","bytes="+this._start+"-"+i));try{t.send(this._config.downloadRequestBody)}catch(l){this._chunkError(l.message)}B&&t.status===0&&this._chunkError()}},this._chunkLoaded=function(){t.readyState===4&&(t.status<200||400<=t.status?this._chunkError():(this._start+=this._config.chunkSize||t.responseText.length,this._finished=!this._config.chunkSize||this._start>=(r=>(r=r.getResponseHeader("Content-Range"))!==null?parseInt(r.substring(r.lastIndexOf("/")+1)):-1)(t),this.parseChunk(t.responseText)))},this._chunkError=function(r){r=t.statusText||r,this._sendError(new Error(r))}}function ie(e){(e=e||{}).chunkSize||(e.chunkSize=u.LocalChunkSize),K.call(this,e);var t,r,n=typeof FileReader<"u";this.stream=function(i){this._input=i,r=i.slice||i.webkitSlice||i.mozSlice,n?((t=new FileReader).onload=H(this._chunkLoaded,this),t.onerror=H(this._chunkError,this)):t=new FileReaderSync,this._nextChunk()},this._nextChunk=function(){this._finished||this._config.preview&&!(this._rowCount<this._config.preview)||this._readChunk()},this._readChunk=function(){var i=this._input,l=(this._config.chunkSize&&(l=Math.min(this._start+this._config.chunkSize,this._input.size),i=r.call(i,this._start,l)),t.readAsText(i,this._config.encoding));n||this._chunkLoaded({target:{result:l}})},this._chunkLoaded=function(i){this._start+=this._config.chunkSize,this._finished=!this._config.chunkSize||this._start>=this._input.size,this.parseChunk(i.target.result)},this._chunkError=function(){this._sendError(t.error)}}function Y(e){var t;K.call(this,e=e||{}),this.stream=function(r){return t=r,this._nextChunk()},this._nextChunk=function(){var r,n;if(!this._finished)return r=this._config.chunkSize,t=r?(n=t.substring(0,r),t.substring(r)):(n=t,""),this._finished=!t,this.parseChunk(n)}}function ne(e){K.call(this,e=e||{});var t=[],r=!0,n=!1;this.pause=function(){K.prototype.pause.apply(this,arguments),this._input.pause()},this.resume=function(){K.prototype.resume.apply(this,arguments),this._input.resume()},this.stream=function(i){this._input=i,this._input.on("data",this._streamData),this._input.on("end",this._streamEnd),this._input.on("error",this._streamError)},this._checkIsFinished=function(){n&&t.length===1&&(this._finished=!0)},this._nextChunk=function(){this._checkIsFinished(),t.length?this.parseChunk(t.shift()):r=!0},this._streamData=H(function(i){try{t.push(typeof i=="string"?i:i.toString(this._config.encoding)),r&&(r=!1,this._checkIsFinished(),this.parseChunk(t.shift()))}catch(l){this._streamError(l)}},this),this._streamError=H(function(i){this._streamCleanUp(),this._sendError(i)},this),this._streamEnd=H(function(){this._streamCleanUp(),n=!0,this._streamData("")},this),this._streamCleanUp=H(function(){this._input.removeListener("data",this._streamData),this._input.removeListener("end",this._streamEnd),this._input.removeListener("error",this._streamError)},this)}function le(e){var t,r,n,i,l=Math.pow(2,53),S=-l,F=/^\s*-?(\d+\.?|\.\d+|\d+\.\d+)([eE][-+]?\d+)?\s*$/,j=/^((\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d:[0-5]\d\.\d+([+-][0-2]\d:[0-5]\d|Z))|(\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d:[0-5]\d([+-][0-2]\d:[0-5]\d|Z))|(\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d([+-][0-2]\d:[0-5]\d|Z)))$/,d=this,E=0,a=0,T=!1,h=!1,c=[],s={data:[],errors:[],meta:{}};function x(p){return e.skipEmptyLines==="greedy"?p.join("").trim()==="":p.length===1&&p[0].length===0}function O(){if(s&&n&&(z("Delimiter","UndetectableDelimiter","Unable to auto-detect delimiting character; defaulted to '"+u.DefaultDelimiter+"'"),n=!1),e.skipEmptyLines&&(s.data=s.data.filter(function(o){return!x(o)})),I()){let o=function(R,C){k(e.transformHeader)&&(R=e.transformHeader(R,C)),c.push(R)};if(s)if(Array.isArray(s.data[0])){for(var p=0;I()&&p<s.data.length;p++)s.data[p].forEach(o);s.data.splice(0,1)}else s.data.forEach(o)}function g(o,R){for(var C=e.header?{}:[],y=0;y<o.length;y++){var v=y,_=o[y],_=((P,f)=>(b=>(e.dynamicTypingFunction&&e.dynamicTyping[b]===void 0&&(e.dynamicTyping[b]=e.dynamicTypingFunction(b)),(e.dynamicTyping[b]||e.dynamicTyping)===!0))(P)?f==="true"||f==="TRUE"||f!=="false"&&f!=="FALSE"&&((b=>{if(F.test(b)&&(b=parseFloat(b),S<b&&b<l))return 1})(f)?parseFloat(f):j.test(f)?new Date(f):f===""?null:f):f)(v=e.header?y>=c.length?"__parsed_extra":c[y]:v,_=e.transform?e.transform(_,v):_);v==="__parsed_extra"?(C[v]=C[v]||[],C[v].push(_)):C[v]=_}return e.header&&(y>c.length?z("FieldMismatch","TooManyFields","Too many fields: expected "+c.length+" fields but parsed "+y,a+R):y<c.length&&z("FieldMismatch","TooFewFields","Too few fields: expected "+c.length+" fields but parsed "+y,a+R)),C}var w;s&&(e.header||e.dynamicTyping||e.transform)&&(w=1,!s.data.length||Array.isArray(s.data[0])?(s.data=s.data.map(g),w=s.data.length):s.data=g(s.data,0),e.header&&s.meta&&(s.meta.fields=c),a+=w)}function I(){return e.header&&c.length===0}function z(p,g,w,o){p={type:p,code:g,message:w},o!==void 0&&(p.row=o),s.errors.push(p)}k(e.step)&&(i=e.step,e.step=function(p){s=p,I()?O():(O(),s.data.length!==0&&(E+=p.data.length,e.preview&&E>e.preview?r.abort():(s.data=s.data[0],i(s,d))))}),this.parse=function(p,g,w){var o=e.quoteChar||'"',o=(e.newline||(e.newline=this.guessLineEndings(p,o)),n=!1,e.delimiter?k(e.delimiter)&&(e.delimiter=e.delimiter(p),s.meta.delimiter=e.delimiter):((o=((R,C,y,v,_)=>{var P,f,b,$;_=_||[","," ","|",";",u.RECORD_SEP,u.UNIT_SEP];for(var J=0;J<_.length;J++){for(var U,V=_[J],D=0,q=0,A=0,L=(b=void 0,new ue({comments:v,delimiter:V,newline:C,preview:10}).parse(R)),W=0;W<L.data.length;W++)y&&x(L.data[W])?A++:(U=L.data[W].length,q+=U,b===void 0?b=U:0<U&&(D+=Math.abs(U-b),b=U));0<L.data.length&&(q/=L.data.length-A),(f===void 0||D<=f)&&($===void 0||$<q)&&1.99<q&&(f=D,P=V,$=q)}return{successful:!!(e.delimiter=P),bestDelimiter:P}})(p,e.newline,e.skipEmptyLines,e.comments,e.delimitersToGuess)).successful?e.delimiter=o.bestDelimiter:(n=!0,e.delimiter=u.DefaultDelimiter),s.meta.delimiter=e.delimiter),he(e));return e.preview&&e.header&&o.preview++,t=p,r=new ue(o),s=r.parse(t,g,w),O(),T?{meta:{paused:!0}}:s||{meta:{paused:!1}}},this.paused=function(){return T},this.pause=function(){T=!0,r.abort(),t=k(e.chunk)?"":t.substring(r.getCharIndex())},this.resume=function(){d.streamer._halted?(T=!1,d.streamer.parseChunk(t,!0)):setTimeout(d.resume,3)},this.aborted=function(){return h},this.abort=function(){h=!0,r.abort(),s.meta.aborted=!0,k(e.complete)&&e.complete(s),t=""},this.guessLineEndings=function(R,o){R=R.substring(0,1048576);var o=new RegExp(X(o)+"([^]*?)"+X(o),"gm"),w=(R=R.replace(o,"")).split("\r"),o=R.split(`
`),R=1<o.length&&o[0].length<w[0].length;if(w.length===1||R)return`
`;for(var C=0,y=0;y<w.length;y++)w[y][0]===`
`&&C++;return C>=w.length/2?`\r
`:"\r"}}function X(e){return e.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}function ue(e){var t=(e=e||{}).delimiter,r=e.newline,n=e.comments,i=e.step,l=e.preview,S=e.fastMode,F=null,j=!1,d=e.quoteChar==null?'"':e.quoteChar,E=d;if(e.escapeChar!==void 0&&(E=e.escapeChar),(typeof t!="string"||-1<u.BAD_DELIMITERS.indexOf(t))&&(t=","),n===t)throw new Error("Comment character same as delimiter");n===!0?n="#":(typeof n!="string"||-1<u.BAD_DELIMITERS.indexOf(n))&&(n=!1),r!==`
`&&r!=="\r"&&r!==`\r
`&&(r=`
`);var a=0,T=!1;this.parse=function(h,c,s){if(typeof h!="string")throw new Error("Input must be a string");var x=h.length,O=t.length,I=r.length,z=n.length,p=k(i),g=[],w=[],o=[],R=a=0;if(!h)return D();if(S||S!==!1&&h.indexOf(d)===-1){for(var C=h.split(r),y=0;y<C.length;y++){if(o=C[y],a+=o.length,y!==C.length-1)a+=r.length;else if(s)return D();if(!n||o.substring(0,z)!==n){if(p){if(g=[],$(o.split(t)),q(),T)return D()}else $(o.split(t));if(l&&l<=y)return g=g.slice(0,l),D(!0)}}return D()}for(var v=h.indexOf(t,a),_=h.indexOf(r,a),P=new RegExp(X(E)+X(d),"g"),f=h.indexOf(d,a);;)if(h[a]===d)for(f=a,a++;;){if((f=h.indexOf(d,f+1))===-1)return s||w.push({type:"Quotes",code:"MissingQuotes",message:"Quoted field unterminated",row:g.length,index:a}),U();if(f===x-1)return U(h.substring(a,f).replace(P,d));if(d===E&&h[f+1]===E)f++;else if(d===E||f===0||h[f-1]!==E){v!==-1&&v<f+1&&(v=h.indexOf(t,f+1));var b=J((_=_!==-1&&_<f+1?h.indexOf(r,f+1):_)===-1?v:Math.min(v,_));if(h.substr(f+1+b,O)===t){o.push(h.substring(a,f).replace(P,d)),h[a=f+1+b+O]!==d&&(f=h.indexOf(d,a)),v=h.indexOf(t,a),_=h.indexOf(r,a);break}if(b=J(_),h.substring(f+1+b,f+1+b+I)===r){if(o.push(h.substring(a,f).replace(P,d)),V(f+1+b+I),v=h.indexOf(t,a),f=h.indexOf(d,a),p&&(q(),T))return D();if(l&&g.length>=l)return D(!0);break}w.push({type:"Quotes",code:"InvalidQuotes",message:"Trailing quote on quoted field is malformed",row:g.length,index:a}),f++}}else if(n&&o.length===0&&h.substring(a,a+z)===n){if(_===-1)return D();a=_+I,_=h.indexOf(r,a),v=h.indexOf(t,a)}else if(v!==-1&&(v<_||_===-1))o.push(h.substring(a,v)),a=v+O,v=h.indexOf(t,a);else{if(_===-1)break;if(o.push(h.substring(a,_)),V(_+I),p&&(q(),T))return D();if(l&&g.length>=l)return D(!0)}return U();function $(A){g.push(A),R=a}function J(A){var L=0;return L=A!==-1&&(A=h.substring(f+1,A))&&A.trim()===""?A.length:L}function U(A){return s||(A===void 0&&(A=h.substring(a)),o.push(A),a=x,$(o),p&&q()),D()}function V(A){a=A,$(o),o=[],_=h.indexOf(r,a)}function D(A){if(e.header&&!c&&g.length&&!j){var L=g[0],W=Object.create(null),fe=new Set(L);let pe=!1;for(let Z=0;Z<L.length;Z++){let N=L[Z];if(W[N=k(e.transformHeader)?e.transformHeader(N,Z):N]){let ee,_e=W[N];for(;ee=N+"_"+_e,_e++,fe.has(ee););fe.add(ee),L[Z]=ee,W[N]++,pe=!0,(F=F===null?{}:F)[ee]=N}else W[N]=1,L[Z]=N;fe.add(N)}pe&&console.warn("Duplicate headers found and renamed."),j=!0}return{data:g,errors:w,meta:{delimiter:t,linebreak:r,aborted:T,truncated:!!A,cursor:R+(c||0),renamedHeaders:F}}}function q(){i(D()),g=[],w=[]}},this.abort=function(){T=!0},this.getCharIndex=function(){return a}}function ke(e){var t=e.data,r=te[t.workerId],n=!1;if(t.error)r.userError(t.error,t.file);else if(t.results&&t.results.data){var i={abort:function(){n=!0,de(t.workerId,{data:[],errors:[],meta:{aborted:!0}})},pause:ce,resume:ce};if(k(r.userStep)){for(var l=0;l<t.results.data.length&&(r.userStep({data:t.results.data[l],errors:t.results.errors,meta:t.results.meta},i),!n);l++);delete t.results}else k(r.userChunk)&&(r.userChunk(t.results,i,t.file),delete t.results)}t.finished&&!n&&de(t.workerId,t.results)}function de(e,t){var r=te[e];k(r.userComplete)&&r.userComplete(t),r.terminate(),delete te[e]}function ce(){throw new Error("Not implemented.")}function he(e){if(typeof e!="object"||e===null)return e;var t,r=Array.isArray(e)?[]:{};for(t in e)r[t]=he(e[t]);return r}function H(e,t){return function(){e.apply(t,arguments)}}function k(e){return typeof e=="function"}return u.parse=function(e,t){var r=(t=t||{}).dynamicTyping||!1;if(k(r)&&(t.dynamicTypingFunction=r,r={}),t.dynamicTyping=r,t.transform=!!k(t.transform)&&t.transform,!t.worker||!u.WORKERS_SUPPORTED)return r=null,u.NODE_STREAM_INPUT,typeof e=="string"?(e=(n=>n.charCodeAt(0)!==65279?n:n.slice(1))(e),r=new(t.download?re:Y)(t)):e.readable===!0&&k(e.read)&&k(e.on)?r=new ne(t):(m.File&&e instanceof File||e instanceof Object)&&(r=new ie(t)),r.stream(e);(r=(()=>{var n;return!!u.WORKERS_SUPPORTED&&(n=(()=>{var i=m.URL||m.webkitURL||null,l=Q.toString();return u.BLOB_URL||(u.BLOB_URL=i.createObjectURL(new Blob(["var global = (function() { if (typeof self !== 'undefined') { return self; } if (typeof window !== 'undefined') { return window; } if (typeof global !== 'undefined') { return global; } return {}; })(); global.IS_PAPA_WORKER=true; ","(",l,")();"],{type:"text/javascript"})))})(),(n=new m.Worker(n)).onmessage=ke,n.id=ye++,te[n.id]=n)})()).userStep=t.step,r.userChunk=t.chunk,r.userComplete=t.complete,r.userError=t.error,t.step=k(t.step),t.chunk=k(t.chunk),t.complete=k(t.complete),t.error=k(t.error),delete t.worker,r.postMessage({input:e,config:t,workerId:r.id})},u.unparse=function(e,t){var r=!1,n=!0,i=",",l=`\r
`,S='"',F=S+S,j=!1,d=null,E=!1,a=((()=>{if(typeof t=="object"){if(typeof t.delimiter!="string"||u.BAD_DELIMITERS.filter(function(c){return t.delimiter.indexOf(c)!==-1}).length||(i=t.delimiter),typeof t.quotes!="boolean"&&typeof t.quotes!="function"&&!Array.isArray(t.quotes)||(r=t.quotes),typeof t.skipEmptyLines!="boolean"&&typeof t.skipEmptyLines!="string"||(j=t.skipEmptyLines),typeof t.newline=="string"&&(l=t.newline),typeof t.quoteChar=="string"&&(S=t.quoteChar),typeof t.header=="boolean"&&(n=t.header),Array.isArray(t.columns)){if(t.columns.length===0)throw new Error("Option columns is empty");d=t.columns}t.escapeChar!==void 0&&(F=t.escapeChar+S),t.escapeFormulae instanceof RegExp?E=t.escapeFormulae:typeof t.escapeFormulae=="boolean"&&t.escapeFormulae&&(E=/^[=+\-@\t\r].*$/)}})(),new RegExp(X(S),"g"));if(typeof e=="string"&&(e=JSON.parse(e)),Array.isArray(e)){if(!e.length||Array.isArray(e[0]))return T(null,e,j);if(typeof e[0]=="object")return T(d||Object.keys(e[0]),e,j)}else if(typeof e=="object")return typeof e.data=="string"&&(e.data=JSON.parse(e.data)),Array.isArray(e.data)&&(e.fields||(e.fields=e.meta&&e.meta.fields||d),e.fields||(e.fields=Array.isArray(e.data[0])?e.fields:typeof e.data[0]=="object"?Object.keys(e.data[0]):[]),Array.isArray(e.data[0])||typeof e.data[0]=="object"||(e.data=[e.data])),T(e.fields||[],e.data||[],j);throw new Error("Unable to serialize unrecognized input");function T(c,s,x){var O="",I=(typeof c=="string"&&(c=JSON.parse(c)),typeof s=="string"&&(s=JSON.parse(s)),Array.isArray(c)&&0<c.length),z=!Array.isArray(s[0]);if(I&&n){for(var p=0;p<c.length;p++)0<p&&(O+=i),O+=h(c[p],p);0<s.length&&(O+=l)}for(var g=0;g<s.length;g++){var w=(I?c:s[g]).length,o=!1,R=I?Object.keys(s[g]).length===0:s[g].length===0;if(x&&!I&&(o=x==="greedy"?s[g].join("").trim()==="":s[g].length===1&&s[g][0].length===0),x==="greedy"&&I){for(var C=[],y=0;y<w;y++){var v=z?c[y]:y;C.push(s[g][v])}o=C.join("").trim()===""}if(!o){for(var _=0;_<w;_++){0<_&&!R&&(O+=i);var P=I&&z?c[_]:_;O+=h(s[g][P],_)}g<s.length-1&&(!x||0<w&&!R)&&(O+=l)}}return O}function h(c,s){var x,O;return c==null?"":c.constructor===Date?JSON.stringify(c).slice(1,25):(O=!1,E&&typeof c=="string"&&E.test(c)&&(c="'"+c,O=!0),x=c.toString().replace(a,F),(O=O||r===!0||typeof r=="function"&&r(c,s)||Array.isArray(r)&&r[s]||((I,z)=>{for(var p=0;p<z.length;p++)if(-1<I.indexOf(z[p]))return!0;return!1})(x,u.BAD_DELIMITERS)||-1<x.indexOf(i)||x.charAt(0)===" "||x.charAt(x.length-1)===" ")?S+x+S:x)}},u.RECORD_SEP="",u.UNIT_SEP="",u.BYTE_ORDER_MARK="\uFEFF",u.BAD_DELIMITERS=["\r",`
`,'"',u.BYTE_ORDER_MARK],u.WORKERS_SUPPORTED=!B&&!!m.Worker,u.NODE_STREAM_INPUT=1,u.LocalChunkSize=10485760,u.RemoteChunkSize=5242880,u.DefaultDelimiter=",",u.Parser=ue,u.ParserHandle=le,u.NetworkStreamer=re,u.FileStreamer=ie,u.StringStreamer=Y,u.ReadableStreamStreamer=ne,m.jQuery&&((M=m.jQuery).fn.parse=function(e){var t=e.config||{},r=[];return this.each(function(l){if(!(M(this).prop("tagName").toUpperCase()==="INPUT"&&M(this).attr("type").toLowerCase()==="file"&&m.FileReader)||!this.files||this.files.length===0)return!0;for(var S=0;S<this.files.length;S++)r.push({file:this.files[S],inputElem:this,instanceConfig:M.extend({},t)})}),n(),this;function n(){if(r.length===0)k(e.complete)&&e.complete();else{var l,S,F,j,d=r[0];if(k(e.before)){var E=e.before(d.file,d.inputElem);if(typeof E=="object"){if(E.action==="abort")return l="AbortError",S=d.file,F=d.inputElem,j=E.reason,void(k(e.error)&&e.error({name:l},S,F,j));if(E.action==="skip")return void i();typeof E.config=="object"&&(d.instanceConfig=M.extend(d.instanceConfig,E.config))}else if(E==="skip")return void i()}var a=d.instanceConfig.complete;d.instanceConfig.complete=function(T){k(a)&&a(T,d.file,d.inputElem),i()},u.parse(d.file,d.instanceConfig)}}function i(){r.splice(0,1),n()}}),oe&&(m.onmessage=function(e){e=e.data,u.WORKER_ID===void 0&&e&&(u.WORKER_ID=e.workerId),typeof e.input=="string"?m.postMessage({workerId:u.WORKER_ID,results:u.parse(e.input,e.config),finished:!0}):(m.File&&e.input instanceof File||e.input instanceof Object)&&(e=u.parse(e.input,e.config))&&m.postMessage({workerId:u.WORKER_ID,results:e,finished:!0})}),(re.prototype=Object.create(K.prototype)).constructor=re,(ie.prototype=Object.create(K.prototype)).constructor=ie,(Y.prototype=Object.create(Y.prototype)).constructor=Y,(ne.prototype=Object.create(K.prototype)).constructor=ne,u})})(se)),se.exports}var me=we();const Re=ve(me),Se=be({__proto__:null,default:Re},[me]);export{Se as p};

Sorry, the diff of this file is too big to display

import type { TableColumn } from './types'
type CursorValueMap = Record<string, unknown>
function q(identifier: string) {
return `\`${identifier.replaceAll('`', '``')}\``
}
function sqlLiteral(value: unknown) {
if (value === null || value === undefined) return 'NULL'
if (typeof value === 'number' || typeof value === 'bigint') return String(value)
if (typeof value === 'boolean') return value ? '1' : '0'
return `'${String(value).replace(/\\/g, '\\\\').replace(/'/g, "''")}'`
}
export function getPrimaryKeyColumns(columns: Pick<TableColumn, 'name' | 'key'>[]) {
return columns
.filter((column) => column.key === 'PRI')
.map((column) => column.name)
}
export function readPrimaryKeyCursor(
row: Record<string, unknown>,
primaryKeyColumns: string[],
): CursorValueMap | null {
if (primaryKeyColumns.length === 0) {
return null
}
const cursor: CursorValueMap = {}
for (const column of primaryKeyColumns) {
if (!(column in row)) {
return null
}
cursor[column] = row[column]
}
return cursor
}
export function buildBatchSelectQuery(options: {
batchSize: number
baseWhereClause?: string
fallbackOffset: number
primaryKeyColumns: string[]
schemaName: string
selectColumns: string[]
tableName: string
cursor?: CursorValueMap | null
}) {
const {
batchSize,
baseWhereClause,
cursor,
fallbackOffset,
primaryKeyColumns,
schemaName,
selectColumns,
tableName,
} = options
const queryColumns = Array.from(new Set([
...selectColumns,
...primaryKeyColumns,
]))
if (primaryKeyColumns.length > 0) {
const orderClause = primaryKeyColumns
.map((column) => `${q(column)} ASC`)
.join(', ')
const filters: string[] = []
if (baseWhereClause) {
filters.push(`(${baseWhereClause})`)
}
if (cursor) {
filters.push(`(${primaryKeyColumns.map((column) => q(column)).join(', ')}) > (${primaryKeyColumns.map((column) => sqlLiteral(cursor[column])).join(', ')})`)
}
const whereClause = filters.length > 0
? ` WHERE ${filters.join(' AND ')}`
: ''
return {
mode: 'primaryKey' as const,
queryColumns,
sql: `SELECT ${queryColumns.map((column) => q(column)).join(', ')} FROM ${q(schemaName)}.${q(tableName)}${whereClause} ORDER BY ${orderClause} LIMIT ${batchSize}`,
}
}
return {
mode: 'offset' as const,
queryColumns,
sql: `SELECT ${queryColumns.map((column) => q(column)).join(', ')} FROM ${q(schemaName)}.${q(tableName)}${baseWhereClause ? ` WHERE ${baseWhereClause}` : ''} LIMIT ${batchSize} OFFSET ${fallbackOffset}`,
}
}
+2
-2

@@ -8,4 +8,4 @@ <!doctype html>

<title>NaviDog</title>
<script type="module" crossorigin src="/assets/index-D_lCxgd0.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-BnM_oG_z.css">
<script type="module" crossorigin src="/assets/index-DFmUIsKl.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-DJykcpp2.css">
</head>

@@ -12,0 +12,0 @@ <body>

{
"name": "navidog",
"version": "1.2.0",
"version": "1.2.1",
"description": "🐕 轻量级 Web 端 MySQL 管理工具,风格类似 Navicat",

@@ -58,2 +58,3 @@ "repository": {

"react-dom": "^19.2.0",
"sql-formatter": "^15.7.2",
"ssh2": "^1.17.0",

@@ -60,0 +61,0 @@ "tsx": "^4.20.6",

@@ -6,8 +6,12 @@ import express from 'express'

import {
cancelActiveQuery,
closePools,
disposeConnection,
executeSql,
executeStatements,
fetchSchemaTree,
fetchTableColumns,
fetchTableMetadata,
normalizeConnectionPayload,
QueryCancelledError,
testConnection,

@@ -83,2 +87,21 @@ } from './mysql.js'

app.post('/api/schema/table-meta', async (request, response) => {
try {
const connection = normalizeConnectionPayload(request.body.connection)
const schemaName = String(request.body.schemaName ?? '').trim()
const tableName = String(request.body.tableName ?? '').trim()
if (!schemaName || !tableName) {
throw new Error('Schema and table are required.')
}
const metadata = await fetchTableMetadata(connection, schemaName, tableName)
response.json(metadata)
} catch (error) {
response.status(400).json({
error: error instanceof Error ? error.message : 'Could not inspect table metadata.',
})
}
})
app.post('/api/query', async (request, response) => {

@@ -89,4 +112,5 @@ try {

const database = request.body.database ? String(request.body.database).trim() : undefined
const queryId = request.body.queryId ? String(request.body.queryId).trim() : undefined
const startedAt = Date.now()
const results = await executeSql(connection, sql, database)
const results = await executeSql(connection, sql, database, queryId)
response.json({

@@ -98,3 +122,3 @@ durationMs: Date.now() - startedAt,

console.error('[/api/query] ERROR')
response.status(400).json({
response.status(error instanceof QueryCancelledError ? 409 : 400).json({
error: error instanceof Error ? error.message : 'SQL execution failed.',

@@ -105,2 +129,40 @@ })

app.post('/api/query/batch', async (request, response) => {
try {
const connection = normalizeConnectionPayload(request.body.connection)
const statements = Array.isArray(request.body.statements)
? request.body.statements.map((statement: unknown) => String(statement ?? ''))
: []
const database = request.body.database ? String(request.body.database).trim() : undefined
const transaction = request.body.transaction !== false
const startedAt = Date.now()
const results = await executeStatements(connection, statements, database, transaction)
response.json({
durationMs: Date.now() - startedAt,
results,
})
} catch (error) {
console.error('[/api/query/batch] ERROR')
response.status(400).json({
error: error instanceof Error ? error.message : 'Batch SQL execution failed.',
})
}
})
app.post('/api/query/cancel', async (request, response) => {
try {
const queryId = String(request.body.queryId ?? '').trim()
if (!queryId) {
throw new Error('Query ID is required.')
}
const ok = await cancelActiveQuery(queryId)
response.json({ ok })
} catch (error) {
response.status(400).json({
error: error instanceof Error ? error.message : 'Could not cancel query.',
})
}
})
/* ── Data Import ────────────────────────────── */

@@ -107,0 +169,0 @@ app.post('/api/import/batch', async (request, response) => {

@@ -5,2 +5,3 @@ import crypto from 'node:crypto'

type Pool,
type PoolConnection,
type ResultSetHeader,

@@ -68,2 +69,22 @@ type RowDataPacket,

export type TableIndexInfo = {
name: string
unique: boolean
primary: boolean
type: string
columns: string[]
cardinality?: number | null
comment?: string
}
export type TableForeignKeyInfo = {
name: string
columns: string[]
referencedSchema: string
referencedTable: string
referencedColumns: string[]
onUpdate: string
onDelete: string
}
export type QueryResultSet =

@@ -95,4 +116,10 @@ | {

type ActiveQueryRecord = {
cancelled: boolean
connection: PoolConnection & { destroy?: () => void; connection?: { destroy?: () => void } }
}
const pools = new Map<string, Pool>()
const pendingPools = new Map<string, Promise<Pool>>()
const activeQueries = new Map<string, ActiveQueryRecord>()
const hiddenSchemas = new Set([

@@ -105,2 +132,18 @@ 'information_schema',

export class QueryCancelledError extends Error {
constructor() {
super('Query was cancelled.')
this.name = 'QueryCancelledError'
}
}
function destroyPoolConnection(connection: ActiveQueryRecord['connection']) {
if (typeof connection.destroy === 'function') {
connection.destroy()
return
}
connection.connection?.destroy?.()
}
function readBoolean(value: unknown, fallback: boolean) {

@@ -517,2 +560,8 @@ if (typeof value === 'boolean') {

export async function closePools() {
for (const [, activeQuery] of activeQueries) {
activeQuery.cancelled = true
destroyPoolConnection(activeQuery.connection)
}
activeQueries.clear()
await Promise.all(Array.from(pools.values(), (pool) => pool.end()))

@@ -626,2 +675,135 @@ pools.clear()

export async function fetchTableMetadata(
config: ConnectionConfig,
schemaName: string,
tableName: string,
): Promise<{ indexes: TableIndexInfo[]; foreignKeys: TableForeignKeyInfo[] }> {
return withSslFallback(config, (effectiveConfig) =>
withReconnect(effectiveConfig, async () => {
const pool = await getPool(effectiveConfig)
const [indexRows] = await pool.query<RowDataPacket[]>(
`SHOW INDEX FROM ${q(schemaName)}.${q(tableName)}`,
)
const indexesByName = new Map<string, {
name: string
unique: boolean
primary: boolean
type: string
cardinality?: number | null
comment?: string
columns: { seq: number; name: string }[]
}>()
for (const row of indexRows) {
const indexName = String(row.Key_name ?? '')
if (!indexName) continue
const existing = indexesByName.get(indexName) ?? {
name: indexName,
unique: Number(row.Non_unique ?? 1) === 0,
primary: indexName === 'PRIMARY',
type: String(row.Index_type ?? ''),
cardinality: row.Cardinality != null ? Number(row.Cardinality) : null,
comment: row.Index_comment ? String(row.Index_comment) : undefined,
columns: [],
}
existing.columns.push({
seq: Number(row.Seq_in_index ?? existing.columns.length + 1),
name: String(row.Column_name ?? ''),
})
indexesByName.set(indexName, existing)
}
const indexes = Array.from(indexesByName.values())
.map((index) => ({
name: index.name,
unique: index.unique,
primary: index.primary,
type: index.type,
columns: index.columns
.sort((a, b) => a.seq - b.seq)
.map((column) => column.name),
cardinality: index.cardinality ?? null,
comment: index.comment,
}))
.sort((a, b) => {
if (a.primary && !b.primary) return -1
if (!a.primary && b.primary) return 1
return a.name.localeCompare(b.name)
})
const [fkRows] = await pool.query<RowDataPacket[]>(
`
SELECT
kcu.CONSTRAINT_NAME AS constraintName,
kcu.COLUMN_NAME AS columnName,
kcu.ORDINAL_POSITION AS ordinalPosition,
kcu.REFERENCED_TABLE_SCHEMA AS referencedSchema,
kcu.REFERENCED_TABLE_NAME AS referencedTable,
kcu.REFERENCED_COLUMN_NAME AS referencedColumn,
rc.UPDATE_RULE AS updateRule,
rc.DELETE_RULE AS deleteRule
FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE kcu
INNER JOIN INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS rc
ON rc.CONSTRAINT_SCHEMA = kcu.TABLE_SCHEMA
AND rc.TABLE_NAME = kcu.TABLE_NAME
AND rc.CONSTRAINT_NAME = kcu.CONSTRAINT_NAME
WHERE kcu.TABLE_SCHEMA = ?
AND kcu.TABLE_NAME = ?
AND kcu.REFERENCED_TABLE_NAME IS NOT NULL
ORDER BY kcu.CONSTRAINT_NAME, kcu.ORDINAL_POSITION
`,
[schemaName, tableName],
)
const foreignKeysByName = new Map<string, {
name: string
referencedSchema: string
referencedTable: string
onUpdate: string
onDelete: string
columns: { seq: number; name: string }[]
referencedColumns: { seq: number; name: string }[]
}>()
for (const row of fkRows) {
const name = String(row.constraintName ?? '')
if (!name) continue
const existing = foreignKeysByName.get(name) ?? {
name,
referencedSchema: String(row.referencedSchema ?? schemaName),
referencedTable: String(row.referencedTable ?? ''),
onUpdate: String(row.updateRule ?? 'RESTRICT'),
onDelete: String(row.deleteRule ?? 'RESTRICT'),
columns: [],
referencedColumns: [],
}
const seq = Number(row.ordinalPosition ?? existing.columns.length + 1)
existing.columns.push({ seq, name: String(row.columnName ?? '') })
existing.referencedColumns.push({ seq, name: String(row.referencedColumn ?? '') })
foreignKeysByName.set(name, existing)
}
const foreignKeys = Array.from(foreignKeysByName.values()).map((fk) => ({
name: fk.name,
columns: fk.columns.sort((a, b) => a.seq - b.seq).map((column) => column.name),
referencedSchema: fk.referencedSchema,
referencedTable: fk.referencedTable,
referencedColumns: fk.referencedColumns
.sort((a, b) => a.seq - b.seq)
.map((column) => column.name),
onUpdate: fk.onUpdate,
onDelete: fk.onDelete,
}))
return { indexes, foreignKeys }
}),
)
}
function isResultSetHeader(value: unknown): value is ResultSetHeader {

@@ -681,2 +863,29 @@ return value !== null && typeof value === 'object' && 'affectedRows' in value

function normalizeQueryPayload(
rows: unknown,
fields: FieldPacket[] | FieldPacket[][] | undefined,
startIndex = 0,
): QueryResultSet[] {
if (
Array.isArray(rows) &&
rows.some((entry) => Array.isArray(entry) || isResultSetHeader(entry))
) {
return rows.map((entry, index) => {
const fieldSet =
Array.isArray(fields) && Array.isArray(fields[index])
? (fields[index] as FieldPacket[])
: undefined
return normalizeSingleResult(entry, fieldSet, startIndex + index)
})
}
const singleFieldSet =
Array.isArray(fields) && fields.length > 0 && !Array.isArray(fields[0])
? (fields as FieldPacket[])
: undefined
return [normalizeSingleResult(rows, singleFieldSet, startIndex)]
}
export async function executeSql(

@@ -686,2 +895,3 @@ config: ConnectionConfig,

database?: string,
queryId?: string,
): Promise<QueryResultSet[]> {

@@ -697,4 +907,11 @@ const trimmedSql = sql.trim()

const pool = await getPool(effectiveConfig)
const conn = await pool.getConnection()
const conn = await pool.getConnection() as PoolConnection & { destroy?: () => void; connection?: { destroy?: () => void } }
const activeQuery = queryId
? { cancelled: false, connection: conn }
: null
if (queryId && activeQuery) {
activeQueries.set(queryId, activeQuery)
}
try {

@@ -706,28 +923,95 @@ if (database) {

const [rows, fields] = await conn.query(trimmedSql)
return normalizeQueryPayload(rows, fields)
} catch (error) {
if (activeQuery?.cancelled) {
throw new QueryCancelledError()
}
throw error
} finally {
if (queryId) {
activeQueries.delete(queryId)
}
try {
conn.release()
} catch {
// Destroyed connections may already be closed.
}
}
}),
)
}
if (
Array.isArray(rows) &&
rows.some((entry) => Array.isArray(entry) || isResultSetHeader(entry))
) {
return rows.map((entry, index) => {
const fieldSet =
Array.isArray(fields) && Array.isArray(fields[index])
? (fields[index] as FieldPacket[])
: undefined
export async function executeStatements(
config: ConnectionConfig,
statements: string[],
database?: string,
transaction = false,
): Promise<QueryResultSet[]> {
const trimmedStatements = statements
.map((statement) => statement.trim())
.filter(Boolean)
return normalizeSingleResult(entry, fieldSet, index)
})
if (trimmedStatements.length === 0) {
throw new Error('No SQL statements provided.')
}
return withSslFallback(config, async (effectiveConfig) => {
const pool = await getPool(effectiveConfig)
const conn = await pool.getConnection()
let transactionStarted = false
try {
if (database) {
await conn.query(`USE \`${database.replaceAll('`', '``')}\``)
}
if (transaction) {
await conn.beginTransaction()
transactionStarted = true
}
const results: QueryResultSet[] = []
for (let index = 0; index < trimmedStatements.length; index += 1) {
const statement = trimmedStatements[index]
try {
const [rows, fields] = await conn.query(statement)
results.push(...normalizeQueryPayload(rows, fields, results.length))
} catch (error) {
const message = error instanceof Error ? error.message : String(error)
throw new Error(`Statement ${index + 1} failed: ${message}`)
}
}
const singleFieldSet =
Array.isArray(fields) && fields.length > 0 && !Array.isArray(fields[0])
? (fields as FieldPacket[])
: undefined
if (transactionStarted) {
await conn.commit()
transactionStarted = false
}
return [normalizeSingleResult(rows, singleFieldSet, 0)]
} finally {
return results
} catch (error) {
if (transactionStarted) {
await conn.rollback().catch(() => undefined)
}
throw formatConnectionError(effectiveConfig, error)
} finally {
try {
conn.release()
} catch {
// Destroyed connections may already be closed.
}
}),
)
}
})
}
export async function cancelActiveQuery(queryId: string): Promise<boolean> {
const activeQuery = activeQueries.get(queryId)
if (!activeQuery) {
return false
}
activeQuery.cancelled = true
destroyPoolConnection(activeQuery.connection)
return true
}

@@ -7,2 +7,3 @@ import type {

TableColumnsResponse,
TableMetadataResponse,
TestConnectionResponse,

@@ -47,5 +48,28 @@ } from './types'

},
runQuery(connection: ConnectionProfile, sql: string, database?: string) {
return request<QueryExecutionResponse>('/query', { connection, sql, database })
fetchTableMetadata(connection: ConnectionProfile, schemaName: string, tableName: string) {
return request<TableMetadataResponse>('/schema/table-meta', {
connection,
schemaName,
tableName,
})
},
runQuery(connection: ConnectionProfile, sql: string, database?: string, queryId?: string) {
return request<QueryExecutionResponse>('/query', { connection, sql, database, queryId })
},
runStatements(
connection: ConnectionProfile,
statements: string[],
database?: string,
transaction = true,
) {
return request<QueryExecutionResponse>('/query/batch', {
connection,
statements,
database,
transaction,
})
},
cancelQuery(queryId: string) {
return request<{ ok: boolean }>('/query/cancel', { queryId })
},
importBatch(

@@ -52,0 +76,0 @@ connection: ConnectionProfile,

import { useEffect, useRef, useState } from 'react'
import * as XLSX from 'xlsx'
import { api } from '../api'
import {
buildBatchSelectQuery,
getPrimaryKeyColumns,
readPrimaryKeyCursor,
} from '../sqlPaging'
import type { ConnectionProfile, SchemaNode, TableColumn } from '../types'

@@ -24,2 +28,98 @@

type SavePickerWindow = Window & {
showSaveFilePicker?: (options: {
suggestedName: string
types: Array<{
description: string
accept: Record<string, string[]>
}>
}) => Promise<FileSystemFileHandle>
}
type TextExportTarget = {
kind: 'download' | 'file'
write: (chunk: string) => Promise<void>
close: () => Promise<Blob | null>
}
function escapeCsvValue(
value: unknown,
separator: string,
qualifier: string,
) {
if (value === null || value === undefined) return ''
const text = String(value)
if (!qualifier) {
return text
}
if (
text.includes(separator) ||
text.includes(qualifier) ||
text.includes('\n') ||
text.includes('\r')
) {
const escapedQualifier = qualifier.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
return `${qualifier}${text.replace(new RegExp(escapedQualifier, 'g'), qualifier + qualifier)}${qualifier}`
}
return text
}
function toSqlLiteral(value: unknown) {
if (value === null || value === undefined) return 'NULL'
if (typeof value === 'number' || typeof value === 'bigint') return String(value)
if (typeof value === 'boolean') return value ? '1' : '0'
return `'${String(value)
.replace(/\\/g, '\\\\')
.replace(/'/g, "\\'")
.replace(/\n/g, '\\n')
.replace(/\r/g, '\\r')
.replace(/\t/g, '\\t')}'`
}
async function createTextExportTarget(
fileName: string,
mimeType: string,
useFileSystemAccess: boolean,
): Promise<TextExportTarget> {
if (useFileSystemAccess) {
const pickerWindow = window as SavePickerWindow
const handle = await pickerWindow.showSaveFilePicker?.({
suggestedName: fileName,
types: [{
description: 'Export Files',
accept: { [mimeType]: [`.${fileName.split('.').pop() ?? 'txt'}`] },
}],
})
if (handle) {
const writable = await handle.createWritable()
return {
kind: 'file',
async write(chunk) {
await writable.write(chunk)
},
async close() {
await writable.close()
return null
},
}
}
}
const parts: BlobPart[] = []
return {
kind: 'download',
async write(chunk) {
parts.push(chunk)
},
async close() {
return new Blob(parts, { type: mimeType })
},
}
}
type Props = {

@@ -137,2 +237,4 @@ connection: ConnectionProfile

}
// `loadColumns` is intentionally not included to avoid reloading on every render.
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [step, activeTable])

@@ -199,3 +301,9 @@

const allFiles: { name: string; blob: Blob }[] = []
const useFileSystemAccess =
enabledTables.length === 1 &&
format !== 'xlsx' &&
typeof window !== 'undefined' &&
'showSaveFilePicker' in window
let totalProcessed = 0
let completedExports = 0

@@ -207,2 +315,3 @@ for (let ti = 0; ti < enabledTables.length; ti++) {

const activeCols = cols.filter((c) => selCols.has(c.name))
const fileName = tc.fileName.trim() || `${tc.name}.${FORMAT_EXT[format]}`

@@ -217,3 +326,2 @@ if (activeCols.length === 0) {

const BATCH = 5000
const MAX_ROWS = 500000
let offset = 0

@@ -223,99 +331,161 @@ let hasMore = true

const colNames = activeCols.map((c) => c.name)
const primaryKeyColumns = getPrimaryKeyColumns(cols)
let cursor: Record<string, unknown> | null = null
let blob: Blob | null = null
// Collect all rows for this table
const allRows: Record<string, unknown>[] = []
try {
if (format === 'xlsx') {
const XLSX = await import('xlsx')
const MAX_XLSX_ROWS = 500000
const wsData: unknown[][] = []
if (includeHeader) {
wsData.push(colNames)
}
setExportLog((prev) => [...prev, `⚠️ ${tc.name}: xlsx 导出会在浏览器内存中组装文件,大表建议改用 CSV / SQL`])
while (hasMore) {
try {
const colList = colNames.map((c) => `\`${c.replace(/`/g, '``')}\``).join(', ')
const sql = `SELECT ${colList} FROM \`${selectedSchema}\`.\`${tc.name}\` LIMIT ${BATCH} OFFSET ${offset}`
const result = await api.runQuery(connection, sql, selectedSchema)
const dataResult = result.results?.[0]
while (hasMore) {
const { mode, sql } = buildBatchSelectQuery({
schemaName: selectedSchema,
tableName: tc.name,
selectColumns: colNames,
primaryKeyColumns,
batchSize: BATCH,
fallbackOffset: offset,
cursor,
})
const result = await api.runQuery(connection, sql, selectedSchema)
const dataResult = result.results?.[0]
if (dataResult?.kind === 'rows' && dataResult.rows.length > 0) {
allRows.push(...dataResult.rows)
rowCount += dataResult.rows.length
offset += dataResult.rows.length
if (dataResult.rows.length < BATCH) hasMore = false
if (rowCount >= MAX_ROWS) {
setExportLog((prev) => [...prev, `⚠️ ${tc.name}: 达到 ${MAX_ROWS} 行限制`])
if (dataResult?.kind === 'rows' && dataResult.rows.length > 0) {
for (const row of dataResult.rows) {
wsData.push(colNames.map((c) => row[c] ?? ''))
}
rowCount += dataResult.rows.length
if (mode === 'primaryKey') {
cursor = readPrimaryKeyCursor(dataResult.rows[dataResult.rows.length - 1], primaryKeyColumns)
} else {
offset += dataResult.rows.length
}
totalProcessed += dataResult.rows.length
setProgress((prev) => ({ ...prev, processed: totalProcessed, tables: ti }))
if (dataResult.rows.length < BATCH) {
hasMore = false
}
if (rowCount >= MAX_XLSX_ROWS) {
setExportLog((prev) => [...prev, `⚠️ ${tc.name}: xlsx 达到 ${MAX_XLSX_ROWS.toLocaleString()} 行限制`])
hasMore = false
}
} else {
hasMore = false
}
}
const ws = XLSX.utils.aoa_to_sheet(wsData)
const wb = XLSX.utils.book_new()
XLSX.utils.book_append_sheet(wb, ws, tc.name.slice(0, 31))
const xlsxData = XLSX.write(wb, { bookType: 'xlsx', type: 'array' }) as ArrayBuffer
blob = new Blob([xlsxData], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' })
} else {
const mimeType = format === 'json'
? 'application/json;charset=utf-8'
: 'text/plain;charset=utf-8'
const target = await createTextExportTarget(fileName, mimeType, useFileSystemAccess)
let firstJsonRow = true
if (format === 'csv' || format === 'txt') {
if (includeHeader) {
await target.write(colNames.map((c) => escapeCsvValue(c, delimiter, textQualifier)).join(delimiter) + eol)
}
} else if (format === 'json') {
await target.write('[')
} else {
hasMore = false
await target.write(`-- Export of ${tc.name}${eol}-- Date: ${new Date().toISOString()}${eol}${eol}`)
}
} catch (err) {
setExportLog((prev) => [...prev, `❌ ${tc.name}: ${err instanceof Error ? err.message : String(err)}`])
hasMore = false
}
totalProcessed += Math.min(BATCH, rowCount - (offset - BATCH))
setProgress((prev) => ({ ...prev, processed: totalProcessed, tables: ti }))
}
// Generate file content based on format
let blob: Blob
while (hasMore) {
const { mode, sql } = buildBatchSelectQuery({
schemaName: selectedSchema,
tableName: tc.name,
selectColumns: colNames,
primaryKeyColumns,
batchSize: BATCH,
fallbackOffset: offset,
cursor,
})
const result = await api.runQuery(connection, sql, selectedSchema)
const dataResult = result.results?.[0]
if (format === 'csv' || format === 'txt') {
const sep = delimiter
let fileContent = ''
if (includeHeader) {
fileContent += colNames.map((c) => `${textQualifier}${c}${textQualifier}`).join(sep) + eol
}
for (const row of allRows) {
const vals = colNames.map((c) => {
const v = row[c]
if (v === null || v === undefined) return ''
const s = String(v)
if (s.includes(sep) || s.includes(textQualifier) || s.includes('\n') || s.includes('\r')) {
return `${textQualifier}${s.replace(new RegExp(textQualifier.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'g'), textQualifier + textQualifier)}${textQualifier}`
if (dataResult?.kind === 'rows' && dataResult.rows.length > 0) {
rowCount += dataResult.rows.length
if (mode === 'primaryKey') {
cursor = readPrimaryKeyCursor(dataResult.rows[dataResult.rows.length - 1], primaryKeyColumns)
} else {
offset += dataResult.rows.length
}
totalProcessed += dataResult.rows.length
setProgress((prev) => ({ ...prev, processed: totalProcessed, tables: ti }))
let chunk = ''
if (format === 'csv' || format === 'txt') {
chunk = dataResult.rows.map((row) =>
colNames
.map((columnName) => escapeCsvValue(row[columnName], delimiter, textQualifier))
.join(delimiter),
).join(eol)
if (chunk) {
chunk += eol
}
} else if (format === 'json') {
chunk = dataResult.rows.map((row) => {
const obj: Record<string, unknown> = {}
for (const columnName of colNames) {
obj[columnName] = row[columnName] ?? null
}
const prefix = firstJsonRow ? `${eol} ` : `,${eol} `
firstJsonRow = false
return prefix + JSON.stringify(obj)
}).join('')
} else {
chunk = dataResult.rows.map((row) =>
`INSERT INTO \`${tc.name}\` (${colNames.map((columnName) => `\`${columnName}\``).join(', ')}) VALUES (${colNames.map((columnName) => toSqlLiteral(row[columnName])).join(', ')});`,
).join(eol)
if (chunk) {
chunk += eol
}
}
if (chunk) {
await target.write(chunk)
}
if (dataResult.rows.length < BATCH) {
hasMore = false
}
} else {
hasMore = false
}
return s
})
fileContent += vals.join(sep) + eol
}
blob = new Blob([fileContent], { type: 'text/plain;charset=utf-8' })
}
} else if (format === 'xlsx') {
// Build worksheet data
const wsData: unknown[][] = []
if (includeHeader) {
wsData.push(colNames)
if (format === 'json') {
await target.write((firstJsonRow ? '' : eol) + ']' + eol)
}
blob = await target.close()
}
for (const row of allRows) {
wsData.push(colNames.map((c) => row[c] ?? ''))
} catch (err) {
if (err instanceof DOMException && err.name === 'AbortError') {
setExportLog((prev) => [...prev, '⏹️ 已取消导出'])
setExporting(false)
return
}
const ws = XLSX.utils.aoa_to_sheet(wsData)
const wb = XLSX.utils.book_new()
XLSX.utils.book_append_sheet(wb, ws, tc.name.slice(0, 31)) // Sheet name max 31 chars
const xlsxData = XLSX.write(wb, { bookType: 'xlsx', type: 'array' }) as ArrayBuffer
blob = new Blob([xlsxData], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' })
} else if (format === 'json') {
let fileContent = '['
let first = true
for (const row of allRows) {
const obj: Record<string, unknown> = {}
for (const c of colNames) { obj[c] = row[c] ?? null }
if (!first) fileContent += ','
fileContent += eol + ' ' + JSON.stringify(obj)
first = false
}
fileContent += eol + ']' + eol
blob = new Blob([fileContent], { type: 'application/json;charset=utf-8' })
setExportLog((prev) => [...prev, `❌ ${tc.name}: ${err instanceof Error ? err.message : String(err)}`])
continue
}
} else {
// SQL
let fileContent = `-- Export of ${tc.name}${eol}-- Date: ${new Date().toISOString()}${eol}${eol}`
for (const row of allRows) {
const vals = colNames.map((c) => {
const v = row[c]
if (v === null || v === undefined) return 'NULL'
return `'${String(v).replace(/\\/g, '\\\\').replace(/'/g, "\\'")}'`
})
fileContent += `INSERT INTO \`${tc.name}\` (${colNames.map((c) => `\`${c}\``).join(', ')}) VALUES (${vals.join(', ')});${eol}`
}
blob = new Blob([fileContent], { type: 'text/plain;charset=utf-8' })
if (blob) {
allFiles.push({ name: fileName, blob })
}
allFiles.push({ name: tc.fileName, blob })
completedExports += 1
setExportLog((prev) => [...prev, `✅ ${tc.name}: ${rowCount.toLocaleString()} 行`])

@@ -335,3 +505,3 @@ setProgress((prev) => ({ ...prev, total: prev.total + rowCount, tables: ti + 1 }))

setExporting(false)
onFlash('success', `导出完成: ${allFiles.length} 个文件`)
onFlash('success', `导出完成: ${completedExports} 个文件`)
}

@@ -338,0 +508,0 @@

import { useEffect, useRef, useState } from 'react'
import Papa from 'papaparse'
import * as XLSX from 'xlsx'
import { api } from '../api'

@@ -210,2 +208,3 @@ import type { ConnectionProfile, ImportCreateTablePlan, SchemaNode, TableColumn } from '../types'

const [fileType, setFileType] = useState<'csv' | 'json' | 'excel' | 'sql'>('csv')
const [parsingFile, setParsingFile] = useState(false)

@@ -279,57 +278,68 @@ /* Step 2: Target */

/* ── Parse file ────────────────────────── */
function parseFile() {
async function parseFile() {
if (!rawFile) return
setParsingFile(true)
if (fileType === 'json') {
parseJsonFile()
} else if (fileType === 'excel') {
parseExcelFile()
await parseExcelFile()
} else if (fileType === 'sql') {
parseSqlFile()
} else {
parseCsvFile()
await parseCsvFile()
}
}
function parseCsvFile() {
async function parseCsvFile() {
if (!rawFile) return
Papa.parse(rawFile, {
delimiter: delimiter === 'auto' ? undefined : delimiter,
header: hasHeader,
encoding,
skipEmptyLines: true,
dynamicTyping: true,
complete: (results) => {
if (hasHeader) {
const fields = results.meta.fields ?? []
setSourceFields(fields)
const rows = results.data as Record<string, unknown>[]
setAllParsedRows(rows)
setTotalRowCount(rows.length)
const preview = rows.slice(0, 20).map((row) =>
fields.map((f) => {
const v = row[f]
return v === null || v === undefined ? '' : String(v)
}),
)
setPreviewRows(preview)
} else {
const data = results.data as string[][]
const fields = data[0]?.map((_, i) => `Column ${i + 1}`) ?? []
setSourceFields(fields)
const rows = data.map((row) => {
const obj: Record<string, unknown> = {}
fields.forEach((f, i) => { obj[f] = row[i] })
return obj
})
setAllParsedRows(rows)
setTotalRowCount(rows.length)
setPreviewRows(data.slice(0, 20))
}
setStep(1)
},
error: (err) => {
onFlash('error', `解析失败: ${err.message}`)
},
})
try {
const { default: Papa } = await import('papaparse')
Papa.parse(rawFile, {
delimiter: delimiter === 'auto' ? undefined : delimiter,
header: hasHeader,
encoding,
skipEmptyLines: true,
dynamicTyping: true,
complete: (results) => {
if (hasHeader) {
const fields = results.meta.fields ?? []
setSourceFields(fields)
const rows = results.data as Record<string, unknown>[]
setAllParsedRows(rows)
setTotalRowCount(rows.length)
const preview = rows.slice(0, 20).map((row) =>
fields.map((f) => {
const v = row[f]
return v === null || v === undefined ? '' : String(v)
}),
)
setPreviewRows(preview)
} else {
const data = results.data as string[][]
const fields = data[0]?.map((_, i) => `Column ${i + 1}`) ?? []
setSourceFields(fields)
const rows = data.map((row) => {
const obj: Record<string, unknown> = {}
fields.forEach((f, i) => { obj[f] = row[i] })
return obj
})
setAllParsedRows(rows)
setTotalRowCount(rows.length)
setPreviewRows(data.slice(0, 20))
}
setParsingFile(false)
setStep(1)
},
error: (err) => {
setParsingFile(false)
onFlash('error', `解析失败: ${err.message}`)
},
})
} catch (err) {
setParsingFile(false)
onFlash('error', `加载 CSV 解析器失败: ${err instanceof Error ? err.message : String(err)}`)
}
}

@@ -349,2 +359,3 @@

else {
setParsingFile(false)
onFlash('error', 'JSON 文件必须是数组格式')

@@ -355,2 +366,3 @@ return

if (data.length === 0) {
setParsingFile(false)
onFlash('error', 'JSON 文件为空')

@@ -370,42 +382,62 @@ return

setPreviewRows(preview)
setParsingFile(false)
setStep(1)
} catch (err) {
setParsingFile(false)
onFlash('error', `JSON 解析失败: ${err instanceof Error ? err.message : String(err)}`)
}
}
reader.onerror = () => {
setParsingFile(false)
onFlash('error', '读取 JSON 文件失败')
}
reader.readAsText(rawFile, encoding)
}
function parseExcelFile() {
async function parseExcelFile() {
if (!rawFile) return
const reader = new FileReader()
reader.onload = (e) => {
try {
const data = new Uint8Array(e.target?.result as ArrayBuffer)
const workbook = XLSX.read(data, { type: 'array' })
const sheetName = workbook.SheetNames[0]
const sheet = workbook.Sheets[sheetName]
const jsonData = XLSX.utils.sheet_to_json<Record<string, unknown>>(sheet, { defval: '' })
if (jsonData.length === 0) {
onFlash('error', 'Excel 文件为空')
return
try {
const XLSX = await import('xlsx')
const reader = new FileReader()
reader.onload = (e) => {
try {
const data = new Uint8Array(e.target?.result as ArrayBuffer)
const workbook = XLSX.read(data, { type: 'array' })
const sheetName = workbook.SheetNames[0]
const sheet = workbook.Sheets[sheetName]
const jsonData = XLSX.utils.sheet_to_json<Record<string, unknown>>(sheet, { defval: '' })
if (jsonData.length === 0) {
setParsingFile(false)
onFlash('error', 'Excel 文件为空')
return
}
const fields = Object.keys(jsonData[0])
setSourceFields(fields)
setAllParsedRows(jsonData)
setTotalRowCount(jsonData.length)
const preview = jsonData.slice(0, 20).map((row) =>
fields.map((f) => {
const v = row[f]
return v === null || v === undefined ? '' : String(v)
}),
)
setPreviewRows(preview)
setParsingFile(false)
setStep(1)
} catch (err) {
setParsingFile(false)
onFlash('error', `Excel 解析失败: ${err instanceof Error ? err.message : String(err)}`)
}
const fields = Object.keys(jsonData[0])
setSourceFields(fields)
setAllParsedRows(jsonData)
setTotalRowCount(jsonData.length)
const preview = jsonData.slice(0, 20).map((row) =>
fields.map((f) => {
const v = row[f]
return v === null || v === undefined ? '' : String(v)
}),
)
setPreviewRows(preview)
setStep(1)
} catch (err) {
onFlash('error', `Excel 解析失败: ${err instanceof Error ? err.message : String(err)}`)
}
reader.onerror = () => {
setParsingFile(false)
onFlash('error', '读取 Excel 文件失败')
}
reader.readAsArrayBuffer(rawFile)
} catch (err) {
setParsingFile(false)
onFlash('error', `加载 Excel 解析器失败: ${err instanceof Error ? err.message : String(err)}`)
}
reader.readAsArrayBuffer(rawFile)
}

@@ -442,2 +474,3 @@

if (rows.length === 0) {
setParsingFile(false)
onFlash('error', 'SQL 文件中未找到 INSERT 语句')

@@ -456,7 +489,13 @@ return

setPreviewRows(preview)
setParsingFile(false)
setStep(1)
} catch (err) {
setParsingFile(false)
onFlash('error', `SQL 解析失败: ${err instanceof Error ? err.message : String(err)}`)
}
}
reader.onerror = () => {
setParsingFile(false)
onFlash('error', '读取 SQL 文件失败')
}
reader.readAsText(rawFile, encoding)

@@ -951,3 +990,3 @@ }

if (step === 0) {
parseFile()
void parseFile()
return

@@ -1061,6 +1100,6 @@ }

className="import-btn import-btn-primary"
disabled={!canNext}
onClick={handleNext}
disabled={!canNext || parsingFile}
onClick={() => void handleNext()}
>
{step === 3 ? '开始导入' : '下一步 →'}
{step === 0 && parsingFile ? '解析中...' : step === 3 ? '开始导入' : '下一步 →'}
</button>

@@ -1067,0 +1106,0 @@ )}

@@ -1,91 +0,135 @@

import { useEffect, useRef, useMemo } from 'react'
import { useEffect, useRef, useMemo, useImperativeHandle, forwardRef } from 'react'
import { EditorView, keymap, placeholder as cmPlaceholder } from '@codemirror/view'
import { EditorState, Compartment } from '@codemirror/state'
import { sql, MySQL, type SQLConfig } from '@codemirror/lang-sql'
import { EditorState, Compartment, Prec } from '@codemirror/state'
import { sql, MySQL, keywordCompletionSource } from '@codemirror/lang-sql'
import { basicSetup } from 'codemirror'
import { defaultKeymap, indentWithTab } from '@codemirror/commands'
import { autocompletion } from '@codemirror/autocomplete'
import {
acceptCompletion,
autocompletion,
completionKeymap,
completionStatus,
type Completion,
type CompletionContext,
} from '@codemirror/autocomplete'
import { HighlightStyle, syntaxHighlighting } from '@codemirror/language'
import { tags } from '@lezer/highlight'
import type { SchemaNode } from '../types'
/* ═══════════════════════════════════════════════
Light theme – matches NaviDog IDE style
Dark syntax highlighting – VS Code Dark+ palette
(matches the right panel DDL colors)
═══════════════════════════════════════════════ */
const darkHighlightStyle = HighlightStyle.define([
{ tag: tags.keyword, color: '#569cd6' },
{ tag: tags.operatorKeyword, color: '#569cd6' },
{ tag: tags.definitionKeyword, color: '#569cd6' },
{ tag: tags.typeName, color: '#4ec9b0' },
{ tag: tags.string, color: '#ce9178' },
{ tag: tags.number, color: '#b5cea8' },
{ tag: tags.bool, color: '#569cd6' },
{ tag: tags.null, color: '#569cd6' },
{ tag: tags.comment, color: '#6a9955' },
{ tag: tags.lineComment, color: '#6a9955' },
{ tag: tags.blockComment, color: '#6a9955' },
{ tag: tags.punctuation, color: '#808080' },
{ tag: tags.paren, color: '#808080' },
{ tag: tags.squareBracket, color: '#808080' },
{ tag: tags.brace, color: '#808080' },
{ tag: tags.operator, color: '#d4d4d4' },
{ tag: tags.variableName, color: '#9cdcfe' },
{ tag: tags.propertyName, color: '#9cdcfe' },
])
const lightHighlightStyle = HighlightStyle.define([
{ tag: tags.keyword, color: '#0033b3' },
{ tag: tags.operatorKeyword, color: '#0033b3' },
{ tag: tags.definitionKeyword, color: '#0033b3' },
{ tag: tags.typeName, color: '#067d17' },
{ tag: tags.string, color: '#a5060e' },
{ tag: tags.number, color: '#1750eb' },
{ tag: tags.bool, color: '#0033b3' },
{ tag: tags.null, color: '#0033b3' },
{ tag: tags.comment, color: '#8c8c8c' },
{ tag: tags.lineComment, color: '#8c8c8c' },
{ tag: tags.blockComment, color: '#8c8c8c' },
{ tag: tags.punctuation, color: '#333' },
{ tag: tags.paren, color: '#333' },
{ tag: tags.squareBracket, color: '#333' },
{ tag: tags.brace, color: '#333' },
{ tag: tags.operator, color: '#333' },
{ tag: tags.variableName, color: '#871094' },
{ tag: tags.propertyName, color: '#871094' },
])
/* ═══════════════════════════════════════════════
Editor base theme
═══════════════════════════════════════════════ */
const navidogTheme = EditorView.theme({
'&': {
fontSize: '13px',
height: '100%',
fontSize: '13px',
fontFamily: "'SF Mono', 'Menlo', 'Monaco', 'Consolas', monospace",
background: '#ffffff',
},
'.cm-content': {
caretColor: '#2968c8',
padding: '8px 0',
'.cm-scroller': {
fontFamily: 'var(--font-mono)',
overflow: 'auto',
},
'.cm-cursor': {
borderLeftColor: '#2968c8',
'.cm-gutters': {
backgroundColor: 'transparent',
border: 'none',
paddingRight: '4px',
},
'.cm-selectionBackground, &.cm-focused .cm-selectionBackground': {
background: '#cce2ff !important',
'.cm-lineNumbers .cm-gutterElement': {
paddingLeft: '8px',
paddingRight: '6px',
minWidth: '24px',
color: '#b0b0b0',
fontSize: '12px',
},
'.cm-activeLine': {
background: '#f5f8ff',
backgroundColor: 'rgba(0,0,0,0.03)',
},
'.cm-gutters': {
background: '#f8f8f8',
borderRight: '1px solid #e2e2e2',
color: '#aaa',
fontSize: '11px',
},
'.cm-activeLineGutter': {
background: '#eef3fb',
color: '#333',
backgroundColor: 'transparent',
},
/* Autocomplete tooltip */
'.cm-tooltip': {
border: '1px solid rgba(0,0,0,0.12)',
borderRadius: '6px',
boxShadow: '0 6px 24px rgba(0,0,0,0.15)',
background: 'rgba(255,255,255,0.97)',
backdropFilter: 'blur(12px)',
overflow: 'hidden',
'.cm-cursor': {
borderLeftColor: '#333',
},
'.cm-tooltip.cm-tooltip-autocomplete': {
'& > ul': {
fontFamily: "'SF Mono', 'Menlo', monospace",
fontSize: '12px',
maxHeight: '240px',
},
'& > ul > li': {
padding: '3px 10px 3px 6px',
lineHeight: '1.5',
},
'& > ul > li[aria-selected]': {
background: '#2968c8',
color: '#fff',
},
'.cm-selectionBackground': {
backgroundColor: 'rgba(41,104,200,0.2) !important',
},
'.cm-completionIcon': {
width: '18px',
paddingRight: '4px',
opacity: '0.7',
'&.cm-focused .cm-selectionBackground': {
backgroundColor: 'rgba(41,104,200,0.3) !important',
},
'.cm-tooltip-autocomplete': {
maxHeight: '280px',
fontSize: '12px',
border: '1px solid #d0d0d0',
borderRadius: '6px',
boxShadow: '0 4px 14px rgba(0,0,0,0.12)',
},
'.cm-completionLabel': {
fontWeight: '500',
fontSize: '12px',
},
'.cm-completionDetail': {
marginLeft: '8px',
fontSize: '11px',
fontStyle: 'normal',
color: '#999',
fontSize: '11px',
marginLeft: '8px',
},
/* Search panel */
'.cm-completionIcon': {
fontSize: '12px',
opacity: '0.7',
},
'.cm-tooltip-autocomplete ul li[aria-selected]': {
backgroundColor: 'rgba(41,104,200,0.12)',
color: 'inherit',
},
'.cm-panels': {
borderBottom: '1px solid #e2e2e2',
background: '#f8f8f8',
backgroundColor: 'transparent',
},
/* Matching brackets */
'.cm-matchingBracket': {
background: '#d4edbc',
outline: '1px solid #92c353',
'.cm-search': {
fontSize: '13px',
},

@@ -95,3 +139,3 @@ })

/* ═══════════════════════════════════════════════
Props
Exported types
═══════════════════════════════════════════════ */

@@ -103,2 +147,3 @@

onRun?: () => void
onRunSelection?: (selectedSql: string) => void
schemas?: SchemaNode[]

@@ -108,4 +153,14 @@ currentSchema?: string

placeholder?: string
suppressExternalValueSync?: boolean
}
export type SqlEditorHandle = {
getSelectedText: () => string
getValue: () => string
getRunnableSql: () => string
setValue: (value: string) => void
formatSql: () => void
getView: () => EditorView | null
}
/* ═══════════════════════════════════════════════

@@ -115,9 +170,145 @@ Component

// Compartment for dynamic SQL language reconfiguration
const sqlCompartment = new Compartment()
const highlightCompartment = new Compartment()
const autocompleteCompartment = new Compartment()
export default function SqlEditor({
function isDark() {
return document.documentElement.getAttribute('data-theme') === 'dark'
}
async function formatSqlDocument(sqlText: string) {
const { format } = await import('sql-formatter')
return format(sqlText, {
language: 'mysql',
tabWidth: 2,
keywordCase: 'upper',
})
}
/** Check if the semicolon at `index` is a real statement separator (not inside a string/comment) */
function isStatementSeparator(sqlText: string, index: number) {
let inSingle = false
let inDouble = false
let inBacktick = false
let inLineComment = false
let inBlockComment = false
for (let i = 0; i <= index; i += 1) {
const ch = sqlText[i]
const next = sqlText[i + 1]
const prev = sqlText[i - 1]
if (inLineComment) {
if (ch === '\n') inLineComment = false
continue
}
if (inBlockComment) {
if (prev === '*' && ch === '/') inBlockComment = false
continue
}
if (inSingle) {
if (ch === "'" && prev !== '\\') inSingle = false
continue
}
if (inDouble) {
if (ch === '"' && prev !== '\\') inDouble = false
continue
}
if (inBacktick) {
if (ch === '`') inBacktick = false
continue
}
if (ch === '-' && next === '-') {
inLineComment = true
i += 1
continue
}
if (ch === '#') {
inLineComment = true
continue
}
if (ch === '/' && next === '*') {
inBlockComment = true
i += 1
continue
}
if (ch === "'") {
inSingle = true
continue
}
if (ch === '"') {
inDouble = true
continue
}
if (ch === '`') {
inBacktick = true
continue
}
if (ch === ';' && i === index) {
return true
}
}
return false
}
/** Find the boundaries of the SQL statement surrounding the cursor position */
function getStatementBoundaries(sqlText: string, cursor: number) {
if (!sqlText.trim()) {
return { from: 0, to: sqlText.length }
}
let from = 0
for (let i = Math.min(cursor - 1, sqlText.length - 1); i >= 0; i -= 1) {
if (sqlText[i] === ';' && isStatementSeparator(sqlText, i)) {
from = i + 1
break
}
}
let to = sqlText.length
for (let i = Math.max(0, cursor); i < sqlText.length; i += 1) {
if (sqlText[i] === ';' && isStatementSeparator(sqlText, i)) {
to = i + 1
break
}
}
return { from, to }
}
/** Get the SQL that should be run: selection if present, else current statement, else all */
function getRunnableSqlFromView(view: EditorView) {
const { from, to, head } = view.state.selection.main
if (from !== to) {
return view.state.sliceDoc(from, to).trim()
}
const sqlText = view.state.doc.toString()
const current = getStatementBoundaries(sqlText, head)
const activeSql = sqlText.slice(current.from, current.to).trim()
if (activeSql) {
return activeSql
}
return sqlText.trim()
}
const SqlEditor = forwardRef<SqlEditorHandle, SqlEditorProps>(function SqlEditor({
value,
onChange,
onRun,
onRunSelection,
schemas = [],

@@ -127,3 +318,4 @@ currentSchema,

placeholder = '在这里输入 SQL...',
}: SqlEditorProps) {
suppressExternalValueSync = false,
}, ref) {
const containerRef = useRef<HTMLDivElement>(null)

@@ -133,32 +325,125 @@ const viewRef = useRef<EditorView | null>(null)

const onRunRef = useRef(onRun)
const onRunSelectionRef = useRef(onRunSelection)
const lastEmittedRef = useRef(value)
// Keep refs current
onChangeRef.current = onChange
onRunRef.current = onRun
onRunSelectionRef.current = onRunSelection
// Build SQL schema config for autocomplete
const sqlConfig = useMemo<SQLConfig>(() => {
const schemaMap: Record<string, string[]> = {}
useImperativeHandle(ref, () => ({
getSelectedText() {
const view = viewRef.current
if (!view) return ''
const { from, to } = view.state.selection.main
return from === to ? '' : view.state.sliceDoc(from, to)
},
getValue() {
return viewRef.current?.state.doc.toString() ?? ''
},
getRunnableSql() {
const view = viewRef.current
if (!view) return ''
return getRunnableSqlFromView(view)
},
setValue(nextValue: string) {
const view = viewRef.current
if (!view) return
const currentDoc = view.state.doc.toString()
if (currentDoc === nextValue) return
lastEmittedRef.current = nextValue
view.dispatch({
changes: {
from: 0,
to: currentDoc.length,
insert: nextValue,
},
})
},
formatSql() {
const view = viewRef.current
if (!view) return
void (async () => {
try {
const formatted = await formatSqlDocument(view.state.doc.toString())
view.dispatch({
changes: { from: 0, to: view.state.doc.length, insert: formatted },
})
lastEmittedRef.current = formatted
onChangeRef.current(formatted)
} catch {
// Ignore formatter load/parse errors and keep the editor usable.
}
})()
},
getView() {
return viewRef.current
},
}))
for (const schema of schemas) {
for (const table of schema.tables) {
const qualifiedName = `${schema.name}.${table.name}`
// Look up columns from tableColumns prop
const colKey = Object.keys(tableColumns).find(k => k.endsWith(`:${schema.name}.${table.name}`))
const cols = colKey ? tableColumns[colKey] : []
schemaMap[qualifiedName] = cols
if (schema.name === currentSchema) {
schemaMap[table.name] = cols
}
const identifierOptions = useMemo<Completion[]>(() => {
const activeSchema = schemas.find((schema) => schema.name === currentSchema) ?? null
const options: Completion[] = []
const seen = new Set<string>()
if (!activeSchema) {
return options
}
for (const table of activeSchema.tables) {
if (!seen.has(table.name)) {
seen.add(table.name)
options.push({
label: table.name,
type: 'class',
detail: 'table',
})
}
}
return {
dialect: MySQL,
schema: schemaMap,
defaultSchema: currentSchema,
for (const [cacheKey, columns] of Object.entries(tableColumns)) {
if (!cacheKey.includes(`:${activeSchema.name}.`)) continue
for (const column of columns) {
if (seen.has(column)) continue
seen.add(column)
options.push({
label: column,
type: 'property',
detail: 'column',
})
}
}
return options
}, [schemas, currentSchema, tableColumns])
// Create editor
const identifierCompletionSource = useMemo(() => {
const normalizedOptions = identifierOptions.map((option) => ({
...option,
boost: option.detail === 'table' ? 2 : 1,
apply: option.label,
}))
return (context: CompletionContext) => {
const word = context.matchBefore(/[`\w$]*$/)
if (!word) return null
if (word.from === word.to && !context.explicit) return null
const query = word.text.replaceAll('`', '').toLowerCase()
const filtered = query
? normalizedOptions.filter((option) => option.label.toLowerCase().includes(query)).slice(0, 80)
: normalizedOptions.slice(0, 80)
if (filtered.length === 0) {
return null
}
return {
from: word.from,
to: word.to,
options: filtered,
validFor: /[`\w$]*/,
}
}
}, [identifierOptions])
useEffect(() => {

@@ -171,2 +456,21 @@ if (!containerRef.current) return

run: () => {
const view = viewRef.current
if (!view) {
onRunRef.current?.()
return true
}
const { from, to } = view.state.selection.main
if (from !== to && onRunSelectionRef.current) {
onRunSelectionRef.current(view.state.sliceDoc(from, to))
return true
}
onRunSelectionRef.current?.(getRunnableSqlFromView(view))
return true
},
},
{
key: 'Shift-Mod-Enter',
run: () => {
onRunRef.current?.()

@@ -177,3 +481,15 @@ return true

])
const completionAcceptKeymap = Prec.highest(keymap.of([
{
key: 'Enter',
run: (view) => completionStatus(view.state) === 'active' ? acceptCompletion(view) : false,
},
{
key: 'Tab',
run: (view) => completionStatus(view.state) === 'active' ? acceptCompletion(view) : false,
},
]))
const dark = isDark()
const startState = EditorState.create({

@@ -184,10 +500,18 @@ doc: value,

navidogTheme,
sqlCompartment.of(sql({
dialect: MySQL,
schema: {},
})),
autocompletion({
highlightCompartment.of(syntaxHighlighting(dark ? darkHighlightStyle : lightHighlightStyle)),
sqlCompartment.of(sql({ dialect: MySQL })),
autocompleteCompartment.of(autocompletion({
activateOnTyping: true,
activateOnTypingDelay: 180,
maxRenderedOptions: 30,
}),
interactionDelay: 0,
selectOnOpen: true,
defaultKeymap: true,
override: [
identifierCompletionSource,
keywordCompletionSource(MySQL),
],
})),
completionAcceptKeymap,
Prec.highest(keymap.of(completionKeymap)),
runKeymap,

@@ -198,3 +522,5 @@ keymap.of([...defaultKeymap, indentWithTab]),

if (update.docChanged) {
onChangeRef.current(update.state.doc.toString())
const newDoc = update.state.doc.toString()
lastEmittedRef.current = newDoc
onChangeRef.current(newDoc)
}

@@ -213,3 +539,15 @@ }),

// Watch for theme changes
const observer = new MutationObserver(() => {
const nowDark = isDark()
view.dispatch({
effects: highlightCompartment.reconfigure(
syntaxHighlighting(nowDark ? darkHighlightStyle : lightHighlightStyle)
),
})
})
observer.observe(document.documentElement, { attributes: true, attributeFilter: ['data-theme'] })
return () => {
observer.disconnect()
view.destroy()

@@ -219,11 +557,14 @@ viewRef.current = null

// eslint-disable-next-line react-hooks/exhaustive-deps
}, []) // Only create once
}, [])
// Sync external value changes (e.g., from history clicks)
useEffect(() => {
if (suppressExternalValueSync) return
const view = viewRef.current
if (!view) return
// Skip if this value came from our own onChange emission
if (value === lastEmittedRef.current) return
const currentDoc = view.state.doc.toString()
if (currentDoc !== value) {
lastEmittedRef.current = value
view.dispatch({

@@ -237,5 +578,4 @@ changes: {

}
}, [value])
}, [value, suppressExternalValueSync])
// Reconfigure SQL language when schema changes
useEffect(() => {

@@ -246,5 +586,16 @@ const view = viewRef.current

view.dispatch({
effects: sqlCompartment.reconfigure(sql(sqlConfig)),
effects: autocompleteCompartment.reconfigure(autocompletion({
activateOnTyping: true,
activateOnTypingDelay: 180,
maxRenderedOptions: 30,
interactionDelay: 0,
selectOnOpen: true,
defaultKeymap: true,
override: [
identifierCompletionSource,
keywordCompletionSource(MySQL),
],
})),
})
}, [sqlConfig])
}, [identifierCompletionSource])

@@ -257,2 +608,4 @@ return (

)
}
})
export default SqlEditor

@@ -49,2 +49,22 @@ /* ── Connection ──────────────────────────────── */

export type TableIndex = {
name: string
unique: boolean
primary: boolean
type: string
columns: string[]
cardinality?: number | null
comment?: string
}
export type TableForeignKey = {
name: string
columns: string[]
referencedSchema: string
referencedTable: string
referencedColumns: string[]
onUpdate: string
onDelete: string
}
export type ImportCreateTablePlan = {

@@ -100,2 +120,7 @@ columns: TableColumn[]

export type TableMetadataResponse = {
indexes: TableIndex[]
foreignKeys: TableForeignKey[]
}
export type QueryExecutionResponse = {

@@ -127,3 +152,7 @@ durationMs: number

page: number
pageCursors: (Record<string, unknown> | null)[]
pagingMode: 'offset' | 'primaryKey'
totalRows: number
hasMore: boolean
rowCountExact: boolean
loading: boolean

@@ -142,2 +171,3 @@ }

durationMs: number
activeQueryId?: string
loading: boolean

@@ -144,0 +174,0 @@ }

:root{--bg-app: #ececec;--bg-toolbar: #e8e8e8;--bg-sidebar: #f5f5f5;--bg-main: #ffffff;--bg-info: #fafafa;--bg-statusbar: #e8e8e8;--bg-modal-overlay: rgba(0, 0, 0, .35);--border: #d4d4d4;--border-light: #e6e6e6;--border-dark: #bbb;--select-bg: #2968c8;--select-bg-hover: #e8f0fe;--select-text: #ffffff;--text: #333333;--text-secondary: #777777;--text-muted: #aaaaaa;--accent: #2968c8;--accent-hover: #1a54b0;--danger: #d93025;--success: #1e8e3e;--warning: #f9a825;--table-header-bg: #f0f0f0;--table-row-alt: #f9f9fc;--table-row-hover: #eef3fb;--table-border: #e2e2e2;--tab-active-bg: #ffffff;--tab-inactive-bg: #e8e8e8;--tab-border: #cccccc;--toolbar-h: 72px;--statusbar-h: 26px;--sidebar-w: 240px;--info-w: 360px;--tab-h: 32px;--font-sans: -apple-system, BlinkMacSystemFont, "PingFang SC", "Helvetica Neue", "Segoe UI", sans-serif;--font-mono: "SF Mono", Menlo, Monaco, Consolas, "Liberation Mono", monospace;--font-size: 13px;--font-size-sm: 13px;--font-size-xs: 12px}[data-theme=dark]{--bg-app: #1e1e1e;--bg-toolbar: #252526;--bg-sidebar: #1e1e1e;--bg-main: #1e1e1e;--bg-info: #252526;--bg-statusbar: #1e1e1e;--bg-modal-overlay: rgba(0, 0, 0, .6);--border: #3c3c3c;--border-light: #2d2d2d;--border-dark: #4a4a4a;--select-bg: #264f78;--select-bg-hover: #2a2d2e;--select-text: #ffffff;--text: #cccccc;--text-secondary: #969696;--text-muted: #5a5a5a;--accent: #0078d4;--accent-hover: #1a8aed;--danger: #f44747;--success: #4ec9b0;--warning: #cca700;--table-header-bg: #2d2d2d;--table-row-alt: #232323;--table-row-hover: #2a2d2e;--table-border: #3c3c3c;--tab-active-bg: #1e1e1e;--tab-inactive-bg: #2d2d2d;--tab-border: #3c3c3c;color-scheme:dark}[data-theme=dark] input,[data-theme=dark] select,[data-theme=dark] textarea{background:#2d2d2d;color:#ccc;border-color:#3c3c3c}[data-theme=dark] .history-panel,[data-theme=dark] .combo-box-dropdown{background:#252526}[data-theme=dark] .cli-container{background:#1b1b1b}[data-theme=dark] .cli-output{color:#ccc}[data-theme=dark] .cli-welcome{color:#5a5a5a;border-bottom-color:#3c3c3c}[data-theme=dark] .cli-result{color:#969696}[data-theme=dark] .import-error-list{background:#3a2020;border-color:#5a3030}[data-theme=dark] .toolbar-btn:hover{background:#ffffff14}[data-theme=dark] .toolbar-btn:active{background:#ffffff1f}[data-theme=dark] .toolbar-btn.active{background:#264f78;color:#6cb6ff}*,*:before,*:after{box-sizing:border-box}body{margin:0;font-family:var(--font-sans);font-size:var(--font-size);color:var(--text);background:var(--bg-app);overflow:hidden}button,input,textarea,select{font:inherit}button{cursor:pointer;border:none;background:none}ul,ol{list-style:none;margin:0;padding:0}#root{height:100vh}.app-shell{display:flex;flex-direction:column;height:100vh;overflow:hidden}.toolbar{height:var(--toolbar-h);background:var(--bg-toolbar);border-bottom:1px solid var(--border);display:flex;align-items:center;padding:0 12px;gap:2px;flex-shrink:0;-webkit-user-select:none;user-select:none}.toolbar-btn{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:3px;padding:6px 12px;border-radius:6px;color:var(--text);font-size:var(--font-size-xs);min-width:56px;transition:background .12s}.toolbar-btn:hover{background:#0000000f}.toolbar-btn:active{background:#0000001a}.toolbar-btn.active{background:#e8f0fe;color:#1a73e8}.toolbar-btn .tb-icon{font-size:24px;line-height:1;display:flex;align-items:center;justify-content:center;width:32px;height:32px}.toolbar-btn .tb-label{white-space:nowrap}.toolbar-sep{width:1px;height:44px;background:var(--border);margin:0 6px;flex-shrink:0}.toolbar-spacer{flex:1}.workspace{flex:1;display:flex;overflow:hidden;min-height:0}.sidebar{width:var(--sidebar-w);min-width:140px;background:var(--bg-sidebar);border-right:none;display:flex;flex-direction:column;flex-shrink:0;overflow:hidden}.panel-resize-handle.vertical{width:5px;cursor:col-resize;background:var(--border);flex-shrink:0;position:relative;transition:background .15s;z-index:5;align-self:stretch}.panel-resize-handle.vertical:before{content:"";position:absolute;inset:0 -3px;z-index:6}.panel-resize-handle.vertical:hover,.panel-resize-handle.vertical:active{background:var(--accent)}.panel-resize-handle.vertical:after{content:"";position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);height:30px;width:2px;border-left:1px solid rgba(0,0,0,.15);border-right:1px solid rgba(0,0,0,.15)}.sidebar-header{padding:8px 12px 6px;font-size:var(--font-size-sm);font-weight:600;color:var(--text-secondary);border-bottom:1px solid var(--border-light);display:flex;align-items:center;gap:6px;flex-shrink:0}.sidebar-search{margin:6px 8px;padding:4px 8px;border:1px solid var(--border);border-radius:4px;font-size:var(--font-size-sm);background:#fff;outline:none;flex-shrink:0}.sidebar-search:focus{border-color:var(--accent)}.sidebar-search::placeholder{color:var(--text-muted)}.tree-container{flex:1;overflow-y:auto;overflow-x:hidden;padding:2px 0}.tree-node{display:flex;align-items:center;padding:2px 8px 2px 0;cursor:pointer;font-size:var(--font-size-sm);white-space:nowrap;-webkit-user-select:none;user-select:none;border:1px solid transparent;min-height:24px}.tree-node:hover{background:var(--select-bg-hover)}.tree-node.selected{background:var(--select-bg);color:var(--select-text)}.tree-node.selected:hover{background:var(--accent-hover)}.tree-arrow{width:16px;height:16px;display:flex;align-items:center;justify-content:center;font-size:10px;color:var(--text-secondary);flex-shrink:0;transition:transform .12s ease}.tree-arrow.expanded{transform:rotate(90deg)}.tree-node.selected .tree-arrow{color:var(--select-text)}.conn-color-dot{width:8px;height:8px;border-radius:50%;flex-shrink:0}.tree-icon{width:18px;height:18px;display:flex;align-items:center;justify-content:center;flex-shrink:0;margin-right:4px;font-size:14px}.tree-label{overflow:hidden;text-overflow:ellipsis}.main-area{flex:1;display:flex;flex-direction:column;min-width:0;background:var(--bg-main);overflow:hidden}.tab-bar{display:flex;align-items:flex-end;background:var(--tab-inactive-bg);border-bottom:1px solid var(--border);flex-shrink:0;min-height:var(--tab-h);overflow-x:auto;overflow-y:hidden;-webkit-user-select:none;user-select:none}.tab-bar::-webkit-scrollbar{height:0}.tab-item{display:flex;align-items:center;gap:6px;padding:6px 12px;font-size:var(--font-size-sm);border:1px solid var(--tab-border);border-bottom:none;border-radius:4px 4px 0 0;background:var(--tab-inactive-bg);color:var(--text-secondary);margin-left:-1px;cursor:pointer;white-space:nowrap;max-width:200px;position:relative;transition:background .1s}.tab-item:first-child{margin-left:4px}.tab-item:hover{background:#f0f0f0}.tab-item.active{background:var(--tab-active-bg);color:var(--text);border-bottom:1px solid var(--tab-active-bg);z-index:1;font-weight:500}.tab-item .tab-icon{font-size:14px}.tab-item .tab-title{overflow:hidden;text-overflow:ellipsis}.tab-close{width:16px;height:16px;display:flex;align-items:center;justify-content:center;border-radius:3px;font-size:14px;color:var(--text-muted);flex-shrink:0;line-height:1}.tab-close:hover{background:#0000001a;color:var(--text)}.tab-item.active .tab-close{color:var(--text-secondary)}.tab-content{flex:1;display:flex;flex-direction:column;overflow:hidden;min-height:0}.empty-state{flex:1;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:12px;color:var(--text-muted);font-size:14px}.empty-state .empty-icon{font-size:48px;opacity:.4}.empty-state .empty-title{font-size:16px;color:var(--text-secondary);font-weight:500}.objects-toolbar{display:flex;align-items:center;padding:6px 8px;gap:6px;border-bottom:1px solid var(--border-light);flex-shrink:0}.objects-toolbar button{padding:3px 6px;border-radius:3px;font-size:var(--font-size-sm);color:var(--text-secondary)}.objects-toolbar button:hover{background:#0000000f}.objects-search{padding:3px 8px;border:1px solid var(--border);border-radius:4px;font-size:var(--font-size-sm);width:180px;background:#fff;outline:none;margin-left:auto}.objects-search:focus{border-color:var(--accent)}.objects-view-toggle{display:flex;gap:2px}.view-btn{padding:4px 6px!important;font-size:14px!important}.view-btn.active{background:#00000014!important}.objects-grid{flex:1;overflow:auto}.objects-table{width:100%;border-collapse:collapse}.objects-table th{position:sticky;top:0;background:var(--table-header-bg);border-bottom:1px solid var(--table-border);padding:5px 12px;text-align:left;font-size:var(--font-size-sm);font-weight:500;color:var(--text-secondary);white-space:nowrap;z-index:1}.objects-table td{padding:4px 12px;border-bottom:1px solid var(--border-light);font-size:var(--font-size-sm);white-space:nowrap}.objects-table tr:hover td{background:var(--table-row-hover)}.objects-table .obj-icon{margin-right:6px;font-size:14px}.data-toolbar{display:flex;align-items:center;padding:4px 8px;gap:4px;border-bottom:1px solid var(--border-light);flex-shrink:0;background:var(--bg-info)}.data-toolbar button{padding:3px 8px;border-radius:3px;font-size:var(--font-size-sm);color:var(--text-secondary)}.data-toolbar button:hover{background:#0000000f}.filter-label{font-size:var(--font-size-xs);color:var(--text-secondary);white-space:nowrap}.filter-input{flex:1;max-width:300px;min-width:120px;padding:2px 8px;border:1px solid var(--border);border-radius:4px;font-size:var(--font-size-xs);font-family:var(--font-mono);outline:none;transition:border-color .15s}.filter-input:focus{border-color:var(--accent)}.filter-clear{background:none;border:none;color:var(--danger);cursor:pointer;font-size:12px;padding:2px 4px;border-radius:3px}.filter-clear:hover{background:#fee}.sort-indicator{font-size:11px;color:var(--accent);font-weight:700}.col-header{position:relative;cursor:default}.col-header-label{flex:1}.col-sort-trigger{opacity:0;font-size:10px;color:var(--fg-dim);cursor:pointer;padding:0 2px;transition:opacity .15s;-webkit-user-select:none;user-select:none}.col-header:hover .col-sort-trigger{opacity:1}.col-sort-menu{position:absolute;top:100%;left:0;z-index:200;background:#fff;border:1px solid var(--border);border-radius:6px;box-shadow:0 4px 16px #00000038;min-width:140px;padding:4px 0;font-size:13px;font-weight:400;white-space:nowrap}.col-sort-item{padding:6px 12px;cursor:pointer;display:flex;align-items:center;gap:2px}.col-sort-item:hover{background:var(--hover)}.col-sort-sep{height:1px;background:var(--border);margin:4px 0}.filter-tag{display:inline-flex;align-items:center;gap:4px;background:#e8f0fe;color:#1a73e8;font-size:12px;padding:2px 8px;border-radius:12px;margin-right:4px;white-space:nowrap}.filter-tag strong{font-weight:600}.filter-tag-close{background:none;border:none;color:#1a73e8;cursor:pointer;font-size:11px;padding:0 2px;line-height:1}.filter-tag-close:hover{color:#c62828}.filter-clear-all{background:none;border:1px solid var(--border);border-radius:4px;color:var(--fg-dim);font-size:11px;cursor:pointer;padding:2px 8px;margin-left:4px}.filter-clear-all:hover{background:var(--hover)}.toolbar-icon-btn{background:none;border:1px solid transparent;border-radius:4px;font-size:16px;cursor:pointer;padding:2px 6px}.toolbar-icon-btn:hover{background:var(--hover)}.toolbar-icon-btn.active{background:#e8f0fe;border-color:#4285f4}.navi-filter-panel{border-bottom:1px solid #d8d8d8;background:#fafafa}.navi-filter-conditions{padding:10px 14px;min-height:40px}.navi-filter-row{display:flex;align-items:center;gap:6px;padding:3px 0;font-size:13px}.navi-filter-check{width:16px;height:16px;margin:0;cursor:pointer;accent-color:#4285f4}.navi-filter-col-select{color:#06c;font-weight:600;font-size:13px;border:none;background:transparent;cursor:pointer;outline:none;padding:2px 4px;appearance:none;-webkit-appearance:none}.navi-filter-col-select:hover{text-decoration:underline}.navi-filter-op{color:#333;padding:1px 2px;border:1px solid #d0d0d0;border-radius:3px;background:#fff;font-size:12px;cursor:pointer;min-width:50px;outline:none}.navi-filter-val{color:#333;cursor:pointer;padding:2px 8px;background:#fff;border:1px solid #d0d0d0;border-radius:3px;min-width:20px;max-width:200px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.navi-filter-val:hover{border-color:#999}.navi-filter-connector{color:#999;font-size:12px;padding:0 4px}.navi-filter-btn-icon{display:inline-flex;align-items:center;justify-content:center;width:22px;height:22px;font-size:14px;font-weight:700;color:#fff;background:#4285f4;border-radius:3px;cursor:pointer;margin-left:2px}.navi-filter-btn-icon:hover{background:#3367d6}.navi-filter-btn-group{background:#7baaf7;font-size:11px}.navi-filter-btn-group:hover{background:#5a94e6}.navi-filter-remove{background:none;border:none;color:#ccc;cursor:pointer;font-size:12px;padding:0 4px;margin-left:auto}.navi-filter-remove:hover{color:#c62828}.navi-filter-group{margin:2px 0}.navi-filter-group-paren{font-size:15px;font-weight:600;color:#555;padding:0 2px}.navi-filter-group-children{padding-left:24px}.navi-filter-toolbar{display:flex;align-items:center;gap:2px;padding:4px 14px;background:#f0f0f0;border-top:1px solid #e0e0e0}.navi-filter-tool{width:26px;height:24px;display:flex;align-items:center;justify-content:center;font-size:14px;color:#555;cursor:pointer;border-radius:4px}.navi-filter-tool:hover{background:#d6d6d6}.navi-filter-apply{font-size:13px;color:#333;cursor:pointer;padding:2px 10px;border-radius:4px;font-weight:500}.navi-filter-apply:hover{background:#d6d6d6}.navi-filter-status{font-size:12px;color:#999;margin-left:8px}.navi-picker-overlay{position:absolute;inset:0;z-index:300}.navi-picker{position:absolute;top:60px;right:40px;width:380px;max-height:440px;background:#f6f6f6;border-radius:10px;box-shadow:0 8px 32px #00000040;display:flex;flex-direction:column;overflow:hidden;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,sans-serif}.navi-picker-input{margin:12px 14px 0;padding:7px 8px;border:2px solid #4a90d9;border-radius:4px;font-size:13px;outline:none;background:#fff}.navi-picker-input:focus{border-color:#2563eb}.navi-picker-section{padding:10px 14px 4px;font-size:13px;color:#666;font-weight:500}.navi-picker-list{flex:1;overflow-y:auto;min-height:60px;max-height:220px;margin:4px 10px;background:#fff;border:1px solid #d4d4d4;border-radius:6px}.navi-picker-loading{text-align:center;padding:20px;color:#999;font-size:13px}.navi-picker-item{display:flex;align-items:center;gap:8px;padding:5px 10px;cursor:pointer;font-size:13px;border-bottom:1px solid #f0f0f0;color:#333}.navi-picker-item:last-child{border-bottom:none}.navi-picker-item:hover{background:#f0f5ff}.navi-picker-item input[type=checkbox]{width:15px;height:15px;cursor:pointer;flex-shrink:0;margin:0}.navi-picker-text{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;flex:1}.navi-picker-search-row{display:flex;align-items:center;margin:6px 14px;border:1px solid #d4d4d4;border-radius:6px;background:#fff;overflow:hidden}.navi-picker-search-icon{padding:5px 6px 5px 10px;font-size:14px;color:#999;flex-shrink:0}.navi-picker-search{flex:1;border:none;outline:none;padding:6px 8px 6px 2px;font-size:13px;background:transparent}.navi-picker-actions{display:flex;justify-content:flex-end;gap:8px;padding:8px 14px 12px}.navi-picker-btn{padding:5px 22px;border:1px solid #c0c0c0;border-radius:6px;background:#fff;font-size:13px;cursor:pointer;color:#333}.navi-picker-btn:hover{background:#eaeaea}.navi-picker-btn-ok{background:#4285f4;color:#fff;border-color:#4285f4;font-weight:500}.navi-picker-btn-ok:hover{background:#3367d6}.data-grid-wrap{flex:1;overflow:auto;min-height:0}.data-grid{width:auto;border-collapse:collapse;table-layout:auto}.data-grid th{top:0;background:var(--table-header-bg);border-bottom:2px solid var(--table-border);border-right:1px solid var(--table-border);padding:4px 10px;text-align:left;font-size:var(--font-size-sm);font-weight:600;white-space:nowrap;z-index:1;min-width:60px;position:sticky;overflow:visible}.col-resize-handle{position:absolute;right:-2px;top:0;bottom:0;width:5px;cursor:col-resize;z-index:3;-webkit-user-select:none;user-select:none}.col-resize-handle:hover,.col-resize-handle:active{background:var(--accent)}.data-grid td{padding:4px 10px;border-bottom:1px solid var(--border-light);border-right:1px solid var(--border-light);font-size:var(--font-size-xs);white-space:nowrap;max-width:400px;overflow:hidden;text-overflow:ellipsis}.data-grid tr:nth-child(2n) td{background:var(--table-row-alt)}.data-grid tr:hover td{background:var(--table-row-hover)}.data-grid tr.row-selected td{background:#cce5ff!important}.data-grid tr.row-selected:hover td{background:#b8daff!important}.data-grid tr.row-selected .row-num{background:#b8daff!important;color:#004085}.data-grid tr{cursor:default}.data-grid .row-num{color:var(--text-muted);text-align:right;background:var(--table-header-bg);border-right:2px solid var(--table-border);font-family:var(--font-mono);font-size:var(--font-size-xs);min-width:40px;padding:3px 6px;position:sticky;left:0;z-index:0}.data-grid th.row-num-header{min-width:40px;z-index:2;position:sticky;left:0}.cell-null{color:var(--text-muted);font-style:italic}.data-footer{display:flex;align-items:center;justify-content:space-between;padding:4px 8px;border-top:1px solid var(--border-light);background:var(--bg-info);font-size:var(--font-size-xs);color:var(--text-secondary);flex-shrink:0;gap:8px;flex-wrap:wrap}.data-footer-right{display:flex;align-items:center;gap:12px}.data-footer-sql{font-family:var(--font-mono);color:var(--text-muted);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;max-width:50%}.cell-edit-input{width:100%;min-width:60px;padding:1px 4px;border:2px solid var(--accent);border-radius:2px;font-family:var(--font-mono);font-size:var(--font-size-xs);outline:none;background:#fff;box-sizing:border-box}.cell-modified{background:#fff3cd!important}.data-grid tr.row-new td{background:#d4edda!important}.data-grid tr.row-new .row-num{background:#c3e6cb!important;color:#155724}.data-grid tr.row-deleted td{background:#f8d7da!important;text-decoration:line-through;color:#999}.data-grid tr.row-deleted .row-num{background:#f5c6cb!important;color:#721c24}.data-action-bar{display:flex;align-items:center;gap:2px;flex-shrink:0}.action-btn{width:26px;height:22px;border:1px solid var(--border);border-radius:3px;background:#fff;cursor:pointer;font-size:14px;line-height:1;display:flex;align-items:center;justify-content:center;color:var(--text-secondary);transition:background .1s,color .1s}.action-btn:hover:not(:disabled){background:var(--table-row-hover);color:var(--text)}.action-btn:disabled{opacity:.35;cursor:default}.action-btn.apply{color:#28a745;font-weight:700}.action-btn.apply:hover:not(:disabled){background:#d4edda}.action-btn.cancel{color:#dc3545;font-weight:700}.action-btn.cancel:hover:not(:disabled){background:#f8d7da}.action-status{font-size:var(--font-size-xs);color:var(--text-secondary);margin-left:8px}.pagination{display:flex;align-items:center;gap:2px}.page-btn{padding:2px 8px!important;border:1px solid var(--border)!important;border-radius:3px!important;font-size:11px!important;background:#fff!important;cursor:pointer!important;line-height:1.4!important;min-width:24px!important;text-align:center!important}.page-btn:hover{background:var(--select-bg-hover)!important}.page-btn:disabled{opacity:.35;cursor:default!important;background:#f5f5f5!important}.page-info{padding:0 6px;font-size:11px;color:var(--text-secondary);white-space:nowrap}.page-input{width:38px;padding:1px 4px;margin:0 4px;border:1px solid var(--border);border-radius:3px;font-size:11px;font-weight:600;color:var(--accent);text-align:center;background:#fff;outline:none;line-height:1.4}.page-input:focus{border-color:var(--accent);box-shadow:0 0 0 2px #2968c826}.page-count{font-size:11px;color:var(--text-secondary);white-space:nowrap;padding-left:4px;border-left:1px solid var(--border-light)}.query-pane{flex:1;display:flex;flex-direction:column;min-height:0;overflow:hidden}.query-toolbar{display:flex;align-items:center;padding:4px 8px;gap:8px;border-bottom:1px solid var(--border-light);flex-shrink:0;background:var(--bg-info)}.query-toolbar select{padding:2px 6px;border:1px solid var(--border);border-radius:3px;font-size:var(--font-size-sm);background:#fff}.query-db-selector{display:flex;align-items:center;gap:4px;margin-left:4px}.query-db-selector .db-selector-icon{font-size:14px}.query-db-selector select{padding:3px 24px 3px 6px;border:1px solid var(--border);border-radius:4px;font-size:var(--font-size-sm);background:#fff;min-width:160px;cursor:pointer;appearance:auto;color:var(--text)}.query-db-selector select:focus{outline:none;border-color:var(--accent);box-shadow:0 0 0 2px #2968c826}.run-btn{padding:3px 12px!important;background:var(--accent)!important;color:#fff!important;border-radius:4px!important;font-weight:500!important;font-size:var(--font-size-sm)!important}.run-btn:hover{background:var(--accent-hover)!important}.run-btn:disabled{opacity:.5}.query-split-container{flex:1;display:flex;flex-direction:column;min-height:0;overflow:hidden}.query-editor-area{min-height:60px;height:200px;flex-shrink:0;position:relative;overflow:hidden}.query-split-handle{height:4px;background:var(--border-light);cursor:row-resize;flex-shrink:0;position:relative;transition:background .15s}.query-split-handle:hover,.query-split-handle:active{background:var(--accent)}.query-split-handle:after{content:"";position:absolute;left:50%;top:50%;transform:translate(-50%,-50%);width:30px;height:2px;border-top:1px solid rgba(0,0,0,.15);border-bottom:1px solid rgba(0,0,0,.15)}.query-results{flex:1;display:flex;flex-direction:column;min-height:0;overflow:hidden}.result-view-tabs{display:flex;align-items:stretch;background:var(--bg-info);border-bottom:1px solid var(--border-light);flex-shrink:0;gap:0}.result-view-tab{padding:6px 16px;font-size:var(--font-size-xs);color:var(--text-secondary);border:1px solid transparent;border-bottom:none;border-radius:4px 4px 0 0;cursor:pointer;background:none;margin-bottom:-1px;transition:all .12s}.result-view-tab:hover{color:var(--text);background:#00000008}.result-view-tab.active{color:var(--accent);background:#fff;border-color:var(--border-light);font-weight:500}.result-grid-wrap{flex:1;overflow:auto;min-height:0}.query-messages{padding:12px 16px;font-family:var(--font-mono);font-size:var(--font-size-xs);line-height:1.7;color:var(--text);overflow:auto}.query-message-item{margin-bottom:12px;padding-bottom:12px;border-bottom:1px solid var(--border-light)}.query-message-item:last-child{border-bottom:none;margin-bottom:0}.qm-sql{color:var(--text);word-break:break-all}.qm-info{color:#666}.qm-time{color:#999}.info-panel{width:clamp(240px,20vw,500px);background:var(--bg-info);border-left:none;display:flex;flex-direction:column;flex-shrink:0;overflow:hidden}.info-header{padding:16px 16px 12px;border-bottom:1px solid var(--border-light);display:flex;align-items:center;gap:12px;flex-shrink:0}.info-icon{width:48px;height:48px;border-radius:8px;display:flex;align-items:center;justify-content:center;font-size:28px;flex-shrink:0}.info-icon.conn-icon{background:linear-gradient(135deg,#e8d5b7,#c9a96e)}.info-icon.db-icon{background:linear-gradient(135deg,#b8e6c8,#5cb87a)}.info-icon.table-icon{background:linear-gradient(135deg,#b8d4f0,#5a9fd4)}.info-title{font-size:16px;font-weight:600;line-height:1.2}.info-subtitle{font-size:var(--font-size-xs);color:var(--text-muted);margin-top:2px}.info-body{flex:1;overflow-y:auto;padding:12px 16px}.info-row{display:flex;flex-direction:column;gap:2px;margin-bottom:10px}.info-label{font-size:var(--font-size-xs);color:var(--text-muted);font-weight:500}.info-value{font-size:var(--font-size-sm);color:var(--text);word-break:break-all}.info-tab-bar{display:flex;padding:0 8px;border-bottom:1px solid var(--border-light);flex-shrink:0;background:var(--bg-info);gap:0}.info-tab{padding:6px 12px;font-size:var(--font-size-xs);color:var(--text-secondary);border:none;border-bottom:2px solid transparent;background:none;cursor:pointer;border-radius:0;font-weight:500;transition:color .12s,border-color .12s}.info-tab:hover{color:var(--text)}.info-tab.active{color:var(--accent);border-bottom-color:var(--accent)}.ddl-area{margin-top:0;padding:12px;background:#f8f8f8;border:none;border-radius:0;font-family:var(--font-mono);font-size:var(--font-size-xs);line-height:1.6;white-space:pre;overflow:auto;color:var(--text);-webkit-user-select:text;user-select:text}.sql-kw{color:#1a0dab}.sql-type{color:#0b7285}.sql-str{color:#e65100}.sql-num{color:#6a1b9a}.sql-comment{color:#6a9955;font-style:italic}.sql-punct{color:#777}.ddl-body{padding:0!important}.ddl-body .ddl-area{flex:1;max-height:none;height:100%}[data-theme=dark] .ddl-area{background:#1e1e1e}[data-theme=dark] .sql-kw{color:#569cd6}[data-theme=dark] .sql-type{color:#4ec9b0}[data-theme=dark] .sql-str{color:#ce9178}[data-theme=dark] .sql-num{color:#b5cea8}[data-theme=dark] .sql-comment{color:#6a9955}[data-theme=dark] .sql-punct{color:gray}.columns-body{padding:0!important;overflow:auto}.info-columns-table{width:100%;border-collapse:collapse;font-size:var(--font-size-xs)}.info-columns-table th{position:sticky;top:0;background:var(--table-header-bg);border-bottom:1px solid var(--table-border);padding:4px 8px;text-align:left;font-weight:600;color:var(--text-secondary);white-space:nowrap;z-index:1}.info-columns-table td{padding:3px 8px;border-bottom:1px solid var(--border-light);white-space:nowrap}.info-columns-table tr:hover td{background:var(--table-row-hover)}.info-columns-table .col-name{font-weight:500;font-family:var(--font-mono);color:var(--text)}.info-columns-table .col-type{color:var(--accent);font-family:var(--font-mono)}.info-columns-table .col-key{color:var(--warning);font-weight:600}.status-bar{height:var(--statusbar-h);background:var(--bg-statusbar);border-top:1px solid var(--border);display:flex;align-items:center;padding:0 12px;gap:16px;font-size:var(--font-size-xs);color:var(--text-secondary);flex-shrink:0;-webkit-user-select:none;user-select:none}.status-dot{width:7px;height:7px;border-radius:50%;display:inline-block;margin-right:4px}.status-dot.online{background:var(--success)}.status-dot.offline{background:var(--text-muted)}.modal-overlay{position:fixed;inset:0;background:var(--bg-modal-overlay);display:flex;align-items:center;justify-content:center;z-index:1000;animation:fade-in .15s ease-out}.modal-card{width:560px;max-height:80vh;background:#fff;border-radius:8px;box-shadow:0 12px 40px #00000040;display:flex;flex-direction:column;animation:slide-up .2s ease-out}.modal-title-bar{padding:14px 20px 12px;border-bottom:1px solid var(--border-light);display:flex;align-items:center;justify-content:space-between}.modal-title-bar h3{margin:0;font-size:14px;font-weight:600}.modal-close{width:24px;height:24px;display:flex;align-items:center;justify-content:center;border-radius:4px;font-size:18px;color:var(--text-secondary)}.modal-close:hover{background:#0000000f}.modal-tabs{display:flex;padding:0 20px;border-bottom:1px solid var(--border-light)}.modal-tab{padding:8px 14px;font-size:var(--font-size-sm);color:var(--text-secondary);border-bottom:2px solid transparent}.modal-tab:hover{color:var(--text)}.modal-tab.active{color:var(--accent);border-bottom-color:var(--accent);font-weight:500}.modal-body{padding:20px;flex:1;overflow-y:auto}.modal-row{display:grid;grid-template-columns:80px 1fr;align-items:center;gap:8px;margin-bottom:12px}.modal-row label{font-size:var(--font-size-sm);color:var(--text-secondary);text-align:right}.modal-row input{padding:6px 10px;border:1px solid var(--border);border-radius:4px;font-size:var(--font-size-sm);outline:none;background:#fff}.modal-row input:focus{border-color:var(--accent)}.modal-row-split{display:grid;grid-template-columns:80px 1fr 60px 1fr;align-items:center;gap:8px;margin-bottom:12px}.modal-row-split label{font-size:var(--font-size-sm);color:var(--text-secondary);text-align:right}.modal-row-split input{padding:6px 10px;border:1px solid var(--border);border-radius:4px;font-size:var(--font-size-sm);outline:none;background:#fff}.modal-row-split input:focus{border-color:var(--accent)}.modal-diagram{display:flex;align-items:center;justify-content:center;gap:20px;padding:12px 0 20px}.modal-diagram-node{display:flex;flex-direction:column;align-items:center;gap:4px;font-size:var(--font-size-sm);color:var(--text-secondary)}.modal-diagram-icon{font-size:32px}.modal-diagram-line{width:40px;height:2px;background:var(--border)}.modal-checkbox{display:flex;align-items:center;gap:8px;margin-bottom:12px;padding-left:88px}.modal-checkbox input{width:16px;height:16px}.modal-checkbox span{font-size:var(--font-size-sm);color:var(--text-secondary)}.modal-footer{padding:12px 20px;border-top:1px solid var(--border-light);display:flex;align-items:center;justify-content:space-between;gap:8px}.modal-footer-left,.modal-footer-right{display:flex;gap:8px}.btn{padding:6px 16px;border-radius:4px;font-size:var(--font-size-sm);font-weight:500;border:1px solid var(--border);background:#fff;color:var(--text);transition:background .1s}.btn:hover{background:#f0f0f0}.btn-primary{background:var(--accent);color:#fff;border-color:var(--accent)}.btn-primary:hover{background:var(--accent-hover)}.btn-danger{color:var(--danger);border-color:#d930254d}.btn-danger:hover{background:#d930250f}.btn-sm{padding:3px 10px;font-size:var(--font-size-xs)}.btn:disabled{opacity:.5;cursor:default}.btn-link{color:var(--accent);font-size:var(--font-size-sm);padding:4px 8px}.btn-link:hover{text-decoration:underline}.profiles-section{margin-top:16px;border-top:1px solid var(--border-light);padding-top:12px}.profiles-section h4{margin:0 0 8px;font-size:var(--font-size-sm);font-weight:600;color:var(--text-secondary)}.profile-item{display:flex;align-items:center;justify-content:space-between;padding:6px 8px;border-radius:4px;cursor:pointer;font-size:var(--font-size-sm)}.profile-item:hover{background:var(--select-bg-hover)}.profile-item .profile-name{font-weight:500}.profile-item .profile-host{color:var(--text-muted);font-size:var(--font-size-xs)}.profile-actions{display:flex;gap:4px}@keyframes slide-up{0%{opacity:0;transform:translateY(16px)}to{opacity:1;transform:translateY(0)}}.resize-handle{width:4px;cursor:col-resize;background:transparent;transition:background .2s;flex-shrink:0}.resize-handle:hover{background:var(--accent)}::-webkit-scrollbar{width:8px;height:8px}::-webkit-scrollbar-track{background:transparent}::-webkit-scrollbar-thumb{background:#ccc;border-radius:4px}::-webkit-scrollbar-thumb:hover{background:#aaa}.context-menu-overlay{position:fixed;inset:0;z-index:800}.context-menu{position:fixed;z-index:801;min-width:200px;background:#fffffff5;-webkit-backdrop-filter:blur(20px) saturate(180%);backdrop-filter:blur(20px) saturate(180%);border:1px solid rgba(0,0,0,.12);border-radius:8px;box-shadow:0 8px 32px #0000002e,0 2px 8px #00000014;padding:4px 0;animation:ctx-pop .12s ease-out;font-size:var(--font-size-sm);-webkit-user-select:none;user-select:none}.ctx-item{display:flex;align-items:center;gap:8px;padding:6px 16px 6px 12px;cursor:pointer;color:var(--text);border-radius:4px;margin:0 4px;white-space:nowrap}.ctx-item:hover{background:var(--select-bg);color:#fff}.ctx-item.danger{color:var(--danger)}.ctx-item.danger:hover{background:var(--danger);color:#fff}.ctx-item.disabled{color:var(--text-muted);cursor:default}.ctx-item.disabled:hover{background:none;color:var(--text-muted)}.ctx-item .ctx-icon{width:18px;text-align:center;font-size:14px;flex-shrink:0}.ctx-item .ctx-label{flex:1}.ctx-item .ctx-shortcut{color:var(--text-muted);font-size:var(--font-size-xs);margin-left:16px}.ctx-item:hover .ctx-shortcut{color:#ffffffb3}.ctx-item.has-submenu{position:relative}.ctx-submenu{display:none;position:absolute;left:100%;top:-4px;min-width:200px;background:var(--bg-info);border:1px solid var(--border);border-radius:8px;padding:4px 0;box-shadow:0 6px 24px #00000026;z-index:1100}.ctx-item.has-submenu:hover>.ctx-submenu{display:block}.ctx-sep{height:1px;background:#00000014;margin:4px 8px}@keyframes ctx-pop{0%{opacity:0;transform:scale(.95)}to{opacity:1;transform:scale(1)}}[data-theme=dark] .context-menu{background:#2d2d2df7;border-color:#4a4a4a;box-shadow:0 8px 32px #00000080}[data-theme=dark] .ctx-sep{background:#ffffff14}[data-theme=dark] .ctx-submenu{background:#2d2d2df7;border-color:#4a4a4a;box-shadow:0 8px 32px #00000080}[data-theme=dark] .context-menu-overlay{background:transparent}[data-theme=dark] .cm-editor{background:#1e1e1e!important}[data-theme=dark] .cm-editor .cm-content{caret-color:#aeafad}[data-theme=dark] .cm-editor .cm-cursor{border-left-color:#aeafad!important}[data-theme=dark] .cm-editor .cm-activeLine{background:#2a2a2a!important}[data-theme=dark] .cm-editor .cm-gutters{background:#1e1e1e!important;border-right-color:#3c3c3c!important;color:#5a5a5a!important}[data-theme=dark] .cm-editor .cm-activeLineGutter{background:#2a2a2a!important;color:#ccc!important}[data-theme=dark] .cm-editor .cm-selectionBackground{background:#264f78!important}[data-theme=dark] .cm-editor .cm-matchingBracket{background:#3a5a3a!important;outline-color:#5a8a5a!important}[data-theme=dark] .cm-editor .cm-panels{background:#252526!important;border-bottom-color:#3c3c3c!important}[data-theme=dark] .cm-editor .cm-tooltip{background:#252526!important;border-color:#4a4a4a!important}[data-theme=dark] .cm-editor .cm-tooltip-autocomplete ul>li[aria-selected]{background:#264f78!important}[data-theme=dark] .cm-editor .cm-completionDetail{color:#6a6a6a!important}.notice-toast{position:fixed;top:calc(var(--toolbar-h) + 8px);right:12px;padding:8px 16px;border-radius:6px;font-size:var(--font-size-sm);z-index:900;animation:slide-in-right .2s ease-out;max-width:400px;box-shadow:0 4px 16px #00000026}.notice-toast.success{background:#e6f4ea;color:#137333;border:1px solid #a8dab5}.notice-toast.error{background:#fce8e6;color:#c5221f;border:1px solid #f5c6cb}@keyframes slide-in-right{0%{opacity:0;transform:translate(20px)}to{opacity:1;transform:translate(0)}}.history-panel{position:absolute;top:100%;left:0;width:480px;max-height:360px;background:#fff;border:1px solid var(--border);border-radius:8px;box-shadow:0 8px 30px #00000026;z-index:100;display:flex;flex-direction:column;overflow:hidden;margin-top:4px}.history-panel-header{display:flex;align-items:center;justify-content:space-between;padding:8px 12px;border-bottom:1px solid var(--border-light);font-size:12px;font-weight:600}.history-panel-header button{background:none;border:none;cursor:pointer;font-size:14px;padding:2px 4px;border-radius:4px}.history-panel-header button:hover{background:#0000000f}.history-empty{padding:24px;text-align:center;font-size:12px;color:var(--text-muted)}.history-list{overflow-y:auto;flex:1}.history-item{padding:6px 12px;cursor:pointer;border-bottom:1px solid var(--border-light);transition:background 80ms}.history-item:hover{background:var(--select-bg-hover)}.history-item:last-child{border-bottom:none}.history-item code{font-family:var(--font-mono);font-size:11px;color:var(--text);white-space:pre-wrap;word-break:break-all;line-height:1.4}.cli-container{height:100%;display:flex;flex-direction:column;background:#fafbfc;cursor:text}.cli-output{flex:1;overflow-y:auto;padding:12px 16px;font-family:SF Mono,Monaco,Menlo,Consolas,monospace;font-size:13px;line-height:1.5;color:#333}.cli-welcome{color:#888;padding-bottom:8px;border-bottom:1px solid #e0e0e0;margin-bottom:8px;font-size:12px}.cli-entry{margin-bottom:6px}.cli-prompt-line{display:flex;align-items:baseline}.cli-prompt{color:#2e7d32;font-weight:600;flex-shrink:0;-webkit-user-select:none;user-select:none}.cli-cmd{color:#1565c0}.cli-result{margin:2px 0 0;white-space:pre;font-family:inherit;font-size:12px;color:#444;overflow-x:auto}.cli-result.cli-error{color:#c62828}.cli-input-line{display:flex;align-items:center;padding-top:2px}.cli-input{flex:1;background:transparent;border:none;outline:none;color:#1565c0;font-family:inherit;font-size:13px;caret-color:#333}.cli-input::placeholder{color:#aaa}.cli-input:disabled{opacity:.5}.import-wizard-overlay{position:fixed;inset:0;background:var(--bg-modal-overlay);z-index:1000;display:flex;align-items:center;justify-content:center;animation:fade-in .15s ease-out}@keyframes fade-in{0%{opacity:0}to{opacity:1}}.import-wizard-modal{background:var(--bg-main);border-radius:10px;box-shadow:0 20px 60px #0000004d;width:760px;max-width:90vw;max-height:85vh;display:flex;flex-direction:column;overflow:hidden;animation:modal-in .2s ease-out}@keyframes modal-in{0%{opacity:0;transform:translateY(20px) scale(.97)}to{opacity:1;transform:translateY(0) scale(1)}}.import-wizard-header{display:flex;align-items:center;justify-content:space-between;padding:14px 20px;border-bottom:1px solid var(--border-light);flex-shrink:0}.import-wizard-title{font-size:15px;font-weight:600}.import-close-btn{width:28px;height:28px;display:flex;align-items:center;justify-content:center;border-radius:6px;font-size:14px;color:var(--text-secondary)}.import-close-btn:hover{background:#00000014}.import-step-indicator{display:flex;align-items:center;justify-content:center;gap:4px;padding:14px 20px 10px;flex-shrink:0}.import-step-dot{display:flex;flex-direction:column;align-items:center;gap:4px;flex:1;position:relative}.import-step-dot:after{content:"";position:absolute;top:12px;left:calc(50% + 14px);right:calc(-50% + 14px);height:2px;background:var(--border)}.import-step-dot:last-child:after{display:none}.import-step-dot.done:after{background:var(--accent)}.import-step-dot-circle{width:24px;height:24px;border-radius:50%;background:var(--border-light);color:var(--text-secondary);display:flex;align-items:center;justify-content:center;font-size:11px;font-weight:600;z-index:1;transition:all .2s}.import-step-dot.active .import-step-dot-circle{background:var(--accent);color:#fff;box-shadow:0 0 0 3px #2968c833}.import-step-dot.done .import-step-dot-circle{background:var(--success);color:#fff}.import-step-dot-label{font-size:11px;color:var(--text-muted);white-space:nowrap}.import-step-dot.active .import-step-dot-label{color:var(--accent);font-weight:500}.import-step-dot.done .import-step-dot-label{color:var(--success)}.import-wizard-body{flex:1;overflow-y:auto;padding:16px 24px;min-height:300px}.import-step-title{font-size:15px;font-weight:600;margin-bottom:4px}.import-step-desc{font-size:12px;color:var(--text-secondary);margin-bottom:14px}.import-dropzone{border:2px dashed var(--border);border-radius:10px;padding:32px 20px;text-align:center;cursor:pointer;transition:border-color .2s,background .2s;margin-bottom:14px}.import-dropzone:hover{border-color:var(--accent);background:#2968c80a}.import-dropzone-hint{display:flex;flex-direction:column;align-items:center;gap:6px;color:var(--text-secondary);font-size:13px}.import-file-info{display:flex;align-items:center;justify-content:center;gap:8px;font-size:13px}.import-file-icon{font-size:24px}.import-file-name{font-weight:500}.import-file-size{color:var(--text-muted);font-size:12px}.import-options-row{display:flex;align-items:center;gap:6px;font-size:12px;color:var(--text-secondary);flex-wrap:wrap}.import-options-row select{padding:3px 6px;border:1px solid var(--border);border-radius:4px;font-size:12px;background:#fff}.import-options-row input[type=checkbox]{margin:0;accent-color:var(--accent)}.import-preview-table-wrap{max-height:340px;overflow:auto;border:1px solid var(--border-light);border-radius:6px}.import-preview-table{width:100%;border-collapse:collapse;font-size:12px}.import-preview-table th{position:sticky;top:0;background:var(--table-header-bg);padding:5px 10px;text-align:left;font-weight:500;color:var(--text-secondary);border-bottom:1px solid var(--table-border);white-space:nowrap;z-index:1}.import-preview-table td{padding:3px 10px;border-bottom:1px solid var(--border-light);white-space:nowrap;max-width:200px;overflow:hidden;text-overflow:ellipsis}.import-preview-table .import-row-num{color:var(--text-muted);text-align:center;width:36px;font-size:11px}.import-preview-table .cell-null{color:var(--text-muted);font-style:italic}.import-preview-hint{font-size:11px;color:var(--text-muted);text-align:center;padding:6px 0}.import-target-form{display:flex;flex-direction:column;gap:12px}.import-form-row{display:flex;align-items:center;gap:8px}.import-form-row label{width:80px;font-size:13px;font-weight:500;flex-shrink:0;text-align:right}.import-form-row select{flex:1;padding:6px 10px;border:1px solid var(--border);border-radius:5px;font-size:13px;background:#fff}.import-loading{font-size:12px;color:var(--accent)}.import-mapping-table-wrap{max-height:360px;overflow:auto;border:1px solid var(--border-light);border-radius:6px}.import-mapping-table{width:100%;border-collapse:collapse;font-size:12px}.import-mapping-table th{position:sticky;top:0;background:var(--table-header-bg);padding:6px 10px;text-align:left;font-weight:500;color:var(--text-secondary);border-bottom:1px solid var(--table-border);z-index:1}.import-mapping-table td{padding:4px 10px;border-bottom:1px solid var(--border-light)}.import-mapping-table code{background:#0000000a;padding:1px 5px;border-radius:3px;font-family:var(--font-mono);font-size:12px}.import-mapping-table select{width:100%;padding:4px 6px;border:1px solid var(--border);border-radius:4px;font-size:12px;background:#fff}.import-mapping-skipped{opacity:.4}.import-preview-cell{max-width:120px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--text-secondary);font-family:var(--font-mono);font-size:11px}.import-mapping-summary{font-size:12px;color:var(--text-secondary);text-align:right;padding:8px 0 0}.import-stats-grid{display:grid;grid-template-columns:repeat(4,1fr);gap:12px;margin-bottom:16px}.import-stat{background:var(--bg-info);border:1px solid var(--border-light);border-radius:8px;padding:12px;text-align:center}.import-stat-label{font-size:11px;color:var(--text-secondary);margin-bottom:4px}.import-stat-value{font-size:20px;font-weight:600}.import-progress-bar-wrap{height:8px;background:var(--border-light);border-radius:4px;overflow:hidden;margin-bottom:6px}.import-progress-bar{height:100%;background:linear-gradient(90deg,var(--accent),#4fc3f7);border-radius:4px;transition:width .3s ease}.import-progress-text{font-size:12px;color:var(--text-secondary);text-align:center}.import-error-log{margin-top:14px}.import-error-list{max-height:120px;overflow-y:auto;border:1px solid #f5c6cb;border-radius:6px;background:#fef5f5;padding:8px 12px}.import-error-item{font-size:11px;color:#c62828;padding:2px 0;font-family:var(--font-mono)}.import-wizard-footer{display:flex;align-items:center;padding:12px 20px;border-top:1px solid var(--border-light);gap:8px;flex-shrink:0}.import-btn{padding:7px 18px;border-radius:6px;font-size:13px;font-weight:500;border:none;cursor:pointer;transition:background .12s}.import-btn:disabled{opacity:.4;cursor:not-allowed}.import-btn-primary{background:var(--accent);color:#fff}.import-btn-primary:hover:not(:disabled){background:var(--accent-hover)}.import-btn-secondary{background:var(--border-light);color:var(--text)}.import-btn-secondary:hover:not(:disabled){background:var(--border)}.combo-box-input{width:100%;padding:6px 10px;border:1px solid var(--border);border-radius:5px;font-size:13px;background:#fff;outline:none;transition:border-color .15s}.combo-box-input:focus{border-color:var(--accent)}.combo-box-dropdown{position:absolute;top:100%;left:0;right:0;z-index:100;background:#fff;border:1px solid var(--border);border-top:none;border-radius:0 0 6px 6px;box-shadow:0 6px 20px #00000026;max-height:240px;overflow-y:auto}.combo-box-item{display:flex;align-items:center;justify-content:space-between;padding:6px 10px;font-size:12px;cursor:pointer;transition:background 80ms}.combo-box-item:hover{background:var(--select-bg-hover)}.combo-box-item.selected{background:var(--select-bg);color:var(--select-text)}.combo-box-item.combo-box-new{color:var(--accent);border-bottom:1px solid var(--border-light)}.combo-box-meta{font-size:11px;color:var(--text-muted)}.combo-box-item.selected .combo-box-meta{color:#ffffffb3}.combo-box-empty{padding:8px 10px;font-size:12px;color:var(--text-muted);text-align:center}.export-step-title{font-size:15px;font-weight:600;margin-bottom:4px}.export-step-desc{font-size:12px;color:var(--text-secondary);margin-bottom:14px}.export-format-list{display:flex;flex-direction:column;gap:6px}.export-format-item{display:flex;align-items:center;gap:10px;padding:10px 14px;border:1px solid var(--border-light);border-radius:8px;cursor:pointer;transition:border-color .15s,background .15s}.export-format-item:hover{border-color:var(--border);background:#00000004}.export-format-item.selected{border-color:var(--accent);background:#2968c80a}.export-format-item input[type=radio]{accent-color:var(--accent);margin:0}.export-format-info{display:flex;flex-direction:column;gap:2px}.export-format-label{font-size:13px;font-weight:500}.export-format-desc{font-size:11px;color:var(--text-muted)}.export-form-row{display:flex;align-items:center;gap:8px;margin-bottom:12px}.export-form-row label{width:70px;font-size:13px;font-weight:500;flex-shrink:0;text-align:right}.export-form-row select{flex:1;padding:6px 10px;border:1px solid var(--border);border-radius:5px;font-size:13px;background:#fff}.export-table-search-row{display:flex;align-items:center;gap:4px;margin-bottom:6px}.export-table-search{flex:1;padding:5px 10px;border:1px solid var(--border);border-radius:5px;font-size:12px;background:#fff;outline:none}.export-table-search:focus{border-color:var(--accent)}.export-table-search-clear{padding:2px 6px;border:1px solid var(--border);border-radius:4px;font-size:12px;background:#fff;cursor:pointer;color:var(--text-muted)}.export-table-search-clear:hover{color:var(--danger);border-color:var(--danger)}.export-table-list-header{font-size:12px;padding:4px 0;color:var(--text-secondary);display:flex;align-items:center;gap:6px}.export-table-list-header input[type=checkbox]{accent-color:var(--accent)}.export-table-list{border:1px solid var(--border-light);border-radius:6px;max-height:280px;overflow-y:auto}.export-table-list-row{display:flex;align-items:center;gap:8px;padding:5px 10px;font-size:12px;border-bottom:1px solid var(--border-light)}.export-table-list-row:last-child{border-bottom:none}.export-table-list-head{background:var(--table-header-bg);font-weight:500;color:var(--text-secondary);position:sticky;top:0;z-index:1}.export-table-list-row.disabled{opacity:.4}.export-table-list-row input[type=checkbox]{accent-color:var(--accent);margin:0}.export-table-name{display:flex;align-items:center;gap:4px}.export-table-icon{font-size:14px}.export-filename-input{width:100%;padding:2px 6px;border:1px solid var(--border-light);border-radius:3px;font-size:12px;font-family:var(--font-mono);background:#fff}.export-filename-input:focus{border-color:var(--accent);outline:none}.export-loading{font-size:12px;color:var(--accent);padding:20px;text-align:center}.export-col-list{border:1px solid var(--border-light);border-radius:6px;max-height:300px;overflow-y:auto;padding:4px 0}.export-col-item{display:flex;align-items:center;gap:8px;padding:4px 14px;cursor:pointer;font-size:13px;transition:background .1s}.export-col-item:hover{background:var(--select-bg-hover)}.export-col-item input[type=checkbox]{accent-color:var(--accent);margin:0}.export-col-name{flex:1}.export-col-type{font-size:11px;color:var(--text-muted);font-family:var(--font-mono)}.export-col-key{font-size:12px}.export-col-actions{display:flex;align-items:center;gap:8px;padding:8px 0 0;font-size:12px}.export-col-actions button{padding:3px 10px;border:1px solid var(--border);border-radius:4px;font-size:12px;background:#fff;cursor:pointer}.export-col-actions button:hover{background:var(--select-bg-hover)}.export-col-actions input[type=checkbox]{accent-color:var(--accent)}.export-options-section{margin-bottom:16px}.export-options-title{font-size:13px;font-weight:600;margin-bottom:8px;padding-bottom:4px;border-bottom:1px solid var(--border-light)}.export-options-grid{display:flex;flex-direction:column;gap:8px}.export-options-grid input[type=checkbox]{accent-color:var(--accent)}.export-option-row{display:flex;align-items:center;gap:8px;font-size:13px}.export-option-row span:first-child{width:110px;text-align:right;color:var(--text-secondary);flex-shrink:0}.export-option-row select{padding:3px 8px;border:1px solid var(--border);border-radius:4px;font-size:12px;background:#fff}.export-stats-row{display:flex;gap:20px;margin-bottom:12px;font-size:13px}.export-stat-item{display:flex;gap:6px}.export-stat-label{font-weight:500;color:var(--text-secondary)}.export-log-box{border:1px solid var(--border-light);border-radius:6px;background:var(--bg-info);min-height:180px;max-height:280px;overflow-y:auto;padding:8px 12px;font-family:var(--font-mono);font-size:12px;margin-bottom:12px}.export-log-line{padding:2px 0;line-height:1.5}.td-container{display:flex;flex-direction:column;height:100%;background:var(--bg-main)}.td-topbar{display:flex;align-items:center;justify-content:space-between;padding:8px 12px;border-bottom:1px solid var(--border);background:var(--bg-info);gap:12px;flex-shrink:0}.td-topbar-left{display:flex;align-items:center;gap:8px}.td-name-label{font-size:12px;color:var(--text-secondary);font-weight:500}.td-name-input{width:220px;padding:4px 8px;border:1px solid var(--border);border-radius:4px;font-size:13px;font-family:var(--font-mono);background:var(--bg-main);color:var(--text)}.td-topbar-right{display:flex;gap:6px}.td-btn{padding:4px 10px;border:1px solid var(--border);border-radius:4px;background:var(--bg-info);color:var(--text);font-size:12px;cursor:pointer;white-space:nowrap}.td-btn:hover{background:var(--select-bg-hover)}.td-btn:disabled{opacity:.4;cursor:default}.td-btn-primary{background:var(--accent);color:#fff;border-color:var(--accent)}.td-btn-primary:hover{background:var(--accent-hover)}.td-tab-bar{display:flex;border-bottom:1px solid var(--border);background:var(--bg-info);flex-shrink:0}.td-tab{padding:6px 16px;font-size:12px;font-weight:500;color:var(--text-secondary);background:none;border:none;border-bottom:2px solid transparent;cursor:pointer}.td-tab:hover{color:var(--text)}.td-tab.active{color:var(--accent);border-bottom-color:var(--accent)}.td-field-toolbar{display:flex;align-items:center;gap:4px;padding:6px 12px;border-bottom:1px solid var(--border-light);background:var(--bg-info);flex-shrink:0}.td-toolbar-sep{width:1px;height:18px;background:var(--border);margin:0 4px}.td-grid-wrapper{flex:1;overflow:auto}.td-grid{width:100%;border-collapse:collapse;font-size:12px}.td-grid thead th{position:sticky;top:0;background:var(--table-header-bg);border-bottom:1px solid var(--border);padding:5px 6px;text-align:left;font-weight:600;color:var(--text-secondary);font-size:11px;white-space:nowrap;z-index:1}.td-grid tbody tr{border-bottom:1px solid var(--border-light);cursor:pointer}.td-grid tbody tr:hover{background:var(--table-row-hover)}.td-grid tbody tr.td-row-selected{background:var(--select-bg-hover)}.td-grid td{padding:1px 2px}.td-cell-num{text-align:center;color:var(--text-muted);font-size:10px;width:30px}.td-cell-input{width:100%;padding:3px 6px;border:1px solid transparent;border-radius:3px;font-size:12px;font-family:var(--font-mono);background:transparent;color:var(--text);outline:none}.td-cell-input:focus{border-color:var(--accent);background:var(--bg-main)}.td-cell-select{width:100%;padding:3px 4px;border:1px solid transparent;border-radius:3px;font-size:12px;font-family:var(--font-mono);background:transparent;color:var(--text);outline:none;cursor:pointer}.td-cell-select:focus{border-color:var(--accent);background:var(--bg-main)}.td-cell-center{text-align:center}.td-cell-short{width:100%}.td-sql-preview{flex:1;overflow:auto;padding:12px;background:var(--bg-main)}.td-sql-preview pre{margin:0;font-family:var(--font-mono);font-size:12px;line-height:1.6;color:var(--text);white-space:pre-wrap;word-break:break-all}

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display