@cisri/db-schema-editor 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +70 -0
- package/dist/db-schema-editor.d.ts +10 -0
- package/dist/db-schema-editor.d.ts.map +1 -0
- package/dist/index.cjs +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +373 -0
- package/dist/schema-utils.d.ts +20 -0
- package/dist/schema-utils.d.ts.map +1 -0
- package/package.json +73 -0
package/README.md
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# @cisri/db-schema-editor
|
|
2
|
+
|
|
3
|
+
A database table schema editor based on shadcn/ui. Structurally express a database table — table name, columns, types, and constraints — and edit it through a tree/table UI.
|
|
4
|
+
|
|
5
|
+
## 安装
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @cisri/db-schema-editor
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Peer Dependencies
|
|
12
|
+
|
|
13
|
+
本组件依赖以下库,请确保你的项目已安装:
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npm install react react-dom @radix-ui/react-checkbox @radix-ui/react-select @radix-ui/react-slot class-variance-authority clsx lucide-react tailwind-merge
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## 使用
|
|
20
|
+
|
|
21
|
+
```tsx
|
|
22
|
+
import { useState } from 'react';
|
|
23
|
+
import { DbSchemaEditor } from '@cisri/db-schema-editor';
|
|
24
|
+
import type { DbTable } from '@cisri/db-schema-core';
|
|
25
|
+
|
|
26
|
+
export default function App() {
|
|
27
|
+
const [table, setTable] = useState<DbTable>({
|
|
28
|
+
id: 't1',
|
|
29
|
+
name: 'users',
|
|
30
|
+
columns: [
|
|
31
|
+
{ id: 'c1', name: 'id', type: 'uuid', nullable: false, primaryKey: true },
|
|
32
|
+
{ id: 'c2', name: 'email', type: 'varchar', nullable: false, unique: true },
|
|
33
|
+
],
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
return <DbSchemaEditor value={table} onChange={setTable} />;
|
|
37
|
+
}
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Tailwind 配置
|
|
41
|
+
|
|
42
|
+
确保 `tailwind.config.ts` 的 `content` 包含以下路径,否则组件的 Tailwind 类名不会被扫描到:
|
|
43
|
+
|
|
44
|
+
```ts
|
|
45
|
+
content: [
|
|
46
|
+
'./node_modules/@cisri/db-schema-editor/dist/**/*.{js,cjs}',
|
|
47
|
+
];
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
如果同时使用多个 `@cisri/*` 组件,可以使用通配符:
|
|
51
|
+
|
|
52
|
+
```ts
|
|
53
|
+
content: [
|
|
54
|
+
'./node_modules/@cisri/*/dist/**/*.{js,cjs}',
|
|
55
|
+
];
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## CSS 变量
|
|
59
|
+
|
|
60
|
+
本组件使用 shadcn/ui 标准 CSS 变量,可通过覆盖 `:root` 中的变量自定义主题。
|
|
61
|
+
|
|
62
|
+
## 自定义样式
|
|
63
|
+
|
|
64
|
+
```tsx
|
|
65
|
+
<DbSchemaEditor
|
|
66
|
+
value={table}
|
|
67
|
+
onChange={setTable}
|
|
68
|
+
className="w-full max-w-2xl border-primary"
|
|
69
|
+
/>
|
|
70
|
+
```
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { type DbTable } from '@cisri/db-schema-core';
|
|
2
|
+
export interface DbSchemaEditorProps {
|
|
3
|
+
value: DbTable;
|
|
4
|
+
onChange: (value: DbTable) => void;
|
|
5
|
+
onSave?: (value: DbTable) => void;
|
|
6
|
+
readOnly?: boolean;
|
|
7
|
+
className?: string;
|
|
8
|
+
}
|
|
9
|
+
export declare function DbSchemaEditor({ value, onChange, onSave, readOnly, className, }: DbSchemaEditorProps): import("react").JSX.Element;
|
|
10
|
+
//# sourceMappingURL=db-schema-editor.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"db-schema-editor.d.ts","sourceRoot":"","sources":["../src/db-schema-editor.tsx"],"names":[],"mappings":"AAoCA,OAAO,EAIL,KAAK,OAAO,EACb,MAAM,uBAAuB,CAAC;AAY/B,MAAM,WAAW,mBAAmB;IAClC,KAAK,EAAE,OAAO,CAAC;IACf,QAAQ,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,CAAC;IACnC,MAAM,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,CAAC;IAClC,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAsKD,wBAAgB,cAAc,CAAC,EAC7B,KAAK,EACL,QAAQ,EACR,MAAM,EACN,QAAQ,EACR,SAAS,GACV,EAAE,mBAAmB,+BAuOrB"}
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const e=require("react/jsx-runtime"),m=require("react"),p=require("@dnd-kit/core"),w=require("@dnd-kit/sortable"),B=require("@dnd-kit/modifiers"),A=require("@dnd-kit/utilities"),t=require("@cisri/shadcn"),k=require("@cisri/core"),N=require("lucide-react"),v=require("@cisri/db-schema-core");function V(s,n,c){return{...s,columns:s.columns.map(a=>a.id===n?{...a,...c}:a)}}function T(s,n){const c=s.columns.findIndex(x=>x.id===n);if(c<0)return s;const a=[...s.columns];return a.splice(c+1,0,v.createEmptyColumn()),{...s,columns:a}}function z(s,n){return{...s,columns:s.columns.filter(c=>c.id!==n)}}function P(s,n,c){const a=s.columns.findIndex(l=>l.id===n),x=s.columns.findIndex(l=>l.id===c);if(a<0||x<0||a===x)return s;const d=[...s.columns],[h]=d.splice(a,1);return d.splice(x,0,h),{...s,columns:d}}function K(s,n){return{...s,name:n}}function F(s,n){return{...s,description:n}}function y(s,n){if(s===n)return!0;if(typeof s!="object"||typeof n!="object"||s==null||n==null)return!1;const c=Array.isArray(s),a=Array.isArray(n);if(c!==a)return!1;if(c){const b=s,j=n;return b.length===j.length&&b.every((f,C)=>y(f,j[C]))}const x=s,d=n,h=Object.keys(x),l=Object.keys(d);return h.length===l.length&&h.every(b=>y(x[b],d[b]))}const G=["varchar","text","integer","bigint","decimal","boolean","date","timestamp","uuid","jsonb"];function S({column:s,readOnly:n,disableDelete:c,onUpdate:a,onInsertBelow:x,onDelete:d}){const{attributes:h,listeners:l,setNodeRef:b,transform:j,transition:f,isDragging:C}=w.useSortable({id:s.id,disabled:n}),i=s.name||"未命名";return e.jsxs(t.TableRow,{ref:b,style:{transform:A.CSS.Transform.toString(j),transition:f},className:k.cn(C&&"opacity-50"),children:[e.jsx(t.TableCell,{className:"w-10 px-2",children:n?e.jsx("span",{className:"flex h-7 w-7 items-center justify-center text-muted-foreground/30",children:e.jsx(N.GripVertical,{className:"h-4 w-4"})}):e.jsx("button",{type:"button",className:"flex h-7 w-7 cursor-grab items-center justify-center text-muted-foreground active:cursor-grabbing","aria-label":`拖动列 ${i} 排序`,...h,...l,children:e.jsx(N.GripVertical,{className:"pointer-events-none h-4 w-4"})})}),e.jsx(t.TableCell,{children:e.jsx(t.Input,{"aria-label":`列 ${i} 名称`,autoComplete:"off",disabled:n,className:"h-7",value:s.name,onChange:o=>a({name:o.target.value})})}),e.jsx(t.TableCell,{children:e.jsxs(t.Select,{value:s.type,onValueChange:o=>a({type:o}),disabled:n,children:[e.jsx(t.SelectTrigger,{"aria-label":`列 ${i} 类型`,className:"h-7 w-28 text-xs",children:e.jsx(t.SelectValue,{})}),e.jsx(t.SelectContent,{children:G.map(o=>e.jsx(t.SelectItem,{value:o,className:"text-xs",children:o},o))})]})}),e.jsx(t.TableCell,{children:e.jsx(t.Checkbox,{"aria-label":`列 ${i} 可空`,checked:s.nullable,disabled:n,onCheckedChange:o=>a({nullable:o===!0})})}),e.jsx(t.TableCell,{children:e.jsx(t.Checkbox,{"aria-label":`列 ${i} 主键`,checked:s.primaryKey,disabled:n,onCheckedChange:o=>a({primaryKey:o===!0})})}),e.jsx(t.TableCell,{children:e.jsx(t.Checkbox,{"aria-label":`列 ${i} 唯一`,checked:s.unique,disabled:n,onCheckedChange:o=>a({unique:o===!0})})}),e.jsx(t.TableCell,{children:e.jsx(t.Input,{"aria-label":`列 ${i} 默认值`,autoComplete:"off",disabled:n,className:"h-7",value:s.defaultValue??"",onChange:o=>a({defaultValue:o.target.value||void 0})})}),e.jsx(t.TableCell,{children:e.jsx(t.Input,{"aria-label":`列 ${i} 描述`,autoComplete:"off",disabled:n,className:"h-7",value:s.description??"",onChange:o=>a({description:o.target.value||void 0})})}),e.jsx(t.TableCell,{children:!n&&e.jsxs("div",{className:"flex items-center justify-end gap-1",children:[e.jsx(t.Button,{type:"button",variant:"ghost",size:"icon",className:"h-7 w-7",onClick:x,"aria-label":`在列 ${i} 下方添加列`,children:e.jsx(N.Plus,{className:"h-4 w-4"})}),e.jsx(t.Button,{type:"button",variant:"ghost",size:"icon",className:"h-7 w-7",disabled:c,onClick:d,"aria-label":`删除列 ${i}`,children:e.jsx(N.Trash2,{className:"h-4 w-4 text-destructive"})})]})})]})}function L({value:s,onChange:n,onSave:c,readOnly:a,className:x}){const[d,h]=m.useState(()=>({history:[s],index:0})),l=d.history[d.index],b=m.useRef(d);m.useEffect(()=>{b.current=d},[d]);const j=m.useRef(s),[f]=m.useState(()=>v.createEmptyColumn());m.useEffect(()=>{y(s,j.current)||(h({history:[s],index:0}),j.current=s)},[s]);const C=m.useCallback(r=>{h(u=>y(u.history[u.index],r)?u:{history:[...u.history.slice(0,u.index+1),r],index:u.index+1})},[]),i=m.useCallback(r=>{j.current=r,C(r),n(r)},[n,C]),o=d.index>0,I=d.index<d.history.length-1,R=m.useCallback(()=>{const r=b.current;if(r.index<=0)return;const u=r.index-1,g=r.history[u];h({...r,index:u}),j.current=g,n(g)},[n]),q=m.useCallback(()=>{const r=b.current;if(r.index>=r.history.length-1)return;const u=r.index+1,g=r.history[u];h({...r,index:u}),j.current=g,n(g)},[n]),E=m.useCallback(()=>{c==null||c(l)},[c,l]),D=p.useSensors(p.useSensor(p.PointerSensor,{activationConstraint:{distance:5}}),p.useSensor(p.KeyboardSensor,{coordinateGetter:w.sortableKeyboardCoordinates})),$=m.useCallback(r=>{const{active:u,over:g}=r;!g||u.id===g.id||i(P(l,String(u.id),String(g.id)))},[i,l]),H=l.columns.length>0?l.columns.map(r=>r.id):a?[]:[f.id];return e.jsxs("div",{className:k.cn("rounded-md border border-border bg-card text-card-foreground",x),children:[e.jsxs("div",{className:"flex flex-wrap items-center justify-between gap-2 border-b border-border p-2",children:[e.jsx("div",{className:"text-sm font-medium",children:"数据库表编辑器"}),e.jsxs("div",{className:"flex items-center gap-2",children:[!a&&c&&e.jsx(t.Button,{type:"button",size:"sm",onClick:E,className:"h-7 text-xs",children:"保存"}),!a&&e.jsxs(e.Fragment,{children:[e.jsx(t.Button,{type:"button",variant:"ghost",size:"icon",onClick:R,disabled:!o,className:"h-7 w-7","aria-label":"撤销",children:e.jsx(N.Undo2,{className:"h-4 w-4"})}),e.jsx(t.Button,{type:"button",variant:"ghost",size:"icon",onClick:q,disabled:!I,className:"h-7 w-7","aria-label":"恢复",children:e.jsx(N.Redo2,{className:"h-4 w-4"})})]})]})]}),e.jsxs("div",{className:"space-y-2 border-b border-border p-2",children:[e.jsxs("div",{className:"flex items-center gap-2",children:[e.jsx("label",{htmlFor:"db-table-name",className:"w-10 shrink-0 text-xs text-muted-foreground",children:"表名"}),e.jsx(t.Input,{id:"db-table-name",autoComplete:"off",disabled:a,className:"h-7",value:l.name,onChange:r=>i(K(l,r.target.value))})]}),e.jsxs("div",{className:"flex items-center gap-2",children:[e.jsx("label",{htmlFor:"db-table-description",className:"w-10 shrink-0 text-xs text-muted-foreground",children:"描述"}),e.jsx(t.Input,{id:"db-table-description",autoComplete:"off",disabled:a,className:"h-7",value:l.description??"",onChange:r=>i(F(l,r.target.value||void 0))})]})]}),e.jsx(p.DndContext,{sensors:D,collisionDetection:p.closestCenter,modifiers:[B.restrictToVerticalAxis],onDragEnd:$,children:e.jsxs(t.Table,{children:[e.jsx(t.TableHeader,{children:e.jsxs(t.TableRow,{children:[e.jsx(t.TableHead,{className:"w-10"}),e.jsx(t.TableHead,{className:"w-[20%]",children:"名称"}),e.jsx(t.TableHead,{className:"w-[12%]",children:"类型"}),e.jsx(t.TableHead,{className:"w-[8%]",children:"可空"}),e.jsx(t.TableHead,{className:"w-[8%]",children:"主键"}),e.jsx(t.TableHead,{className:"w-[8%]",children:"唯一"}),e.jsx(t.TableHead,{className:"w-[14%]",children:"默认值"}),e.jsx(t.TableHead,{className:"w-[20%]",children:"描述"}),e.jsx(t.TableHead,{className:"w-[10%]"})]})}),e.jsx(t.TableBody,{children:e.jsx(w.SortableContext,{items:H,strategy:w.verticalListSortingStrategy,children:l.columns.length===0?a?e.jsx(t.TableRow,{children:e.jsx(t.TableCell,{colSpan:9,className:"h-12 text-center text-sm text-muted-foreground",children:"暂无列"})}):e.jsx(S,{column:f,disableDelete:!0,onUpdate:r=>i({...l,columns:[{...f,...r}]}),onInsertBelow:()=>i(T({...l,columns:[f]},f.id)),onDelete:()=>{}},f.id):l.columns.map(r=>e.jsx(S,{column:r,readOnly:a,onUpdate:u=>i(V(l,r.id,u)),onInsertBelow:()=>i(T(l,r.id)),onDelete:()=>i(z(l,r.id))},r.id))})})]})})]})}exports.DbSchemaEditor=L;
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AACpD,YAAY,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AAC9D,YAAY,EAAE,OAAO,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,373 @@
|
|
|
1
|
+
import { jsxs as u, jsx as e, Fragment as L } from "react/jsx-runtime";
|
|
2
|
+
import { useState as j, useRef as I, useEffect as D, useCallback as w } from "react";
|
|
3
|
+
import { useSensors as Y, useSensor as $, PointerSensor as _, KeyboardSensor as J, DndContext as M, closestCenter as Q } from "@dnd-kit/core";
|
|
4
|
+
import { sortableKeyboardCoordinates as W, SortableContext as X, verticalListSortingStrategy as Z, useSortable as U } from "@dnd-kit/sortable";
|
|
5
|
+
import { restrictToVerticalAxis as O } from "@dnd-kit/modifiers";
|
|
6
|
+
import { CSS as ee } from "@dnd-kit/utilities";
|
|
7
|
+
import { Button as y, Input as v, Table as te, TableHeader as ne, TableRow as T, TableHead as N, TableBody as re, TableCell as p, Select as se, SelectTrigger as ie, SelectValue as ae, SelectContent as oe, SelectItem as le, Checkbox as k } from "@cisri/shadcn";
|
|
8
|
+
import { cn as V } from "@cisri/core";
|
|
9
|
+
import { Undo2 as ce, Redo2 as de, GripVertical as E, Plus as me, Trash2 as ue } from "lucide-react";
|
|
10
|
+
import { createEmptyColumn as z } from "@cisri/db-schema-core";
|
|
11
|
+
function he(t, n, o) {
|
|
12
|
+
return {
|
|
13
|
+
...t,
|
|
14
|
+
columns: t.columns.map((s) => s.id === n ? { ...s, ...o } : s)
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
function R(t, n) {
|
|
18
|
+
const o = t.columns.findIndex((m) => m.id === n);
|
|
19
|
+
if (o < 0) return t;
|
|
20
|
+
const s = [...t.columns];
|
|
21
|
+
return s.splice(o + 1, 0, z()), { ...t, columns: s };
|
|
22
|
+
}
|
|
23
|
+
function fe(t, n) {
|
|
24
|
+
return { ...t, columns: t.columns.filter((o) => o.id !== n) };
|
|
25
|
+
}
|
|
26
|
+
function be(t, n, o) {
|
|
27
|
+
const s = t.columns.findIndex((i) => i.id === n), m = t.columns.findIndex((i) => i.id === o);
|
|
28
|
+
if (s < 0 || m < 0 || s === m) return t;
|
|
29
|
+
const c = [...t.columns], [h] = c.splice(s, 1);
|
|
30
|
+
return c.splice(m, 0, h), { ...t, columns: c };
|
|
31
|
+
}
|
|
32
|
+
function xe(t, n) {
|
|
33
|
+
return { ...t, name: n };
|
|
34
|
+
}
|
|
35
|
+
function pe(t, n) {
|
|
36
|
+
return { ...t, description: n };
|
|
37
|
+
}
|
|
38
|
+
function S(t, n) {
|
|
39
|
+
if (t === n) return !0;
|
|
40
|
+
if (typeof t != "object" || typeof n != "object" || t == null || n == null) return !1;
|
|
41
|
+
const o = Array.isArray(t), s = Array.isArray(n);
|
|
42
|
+
if (o !== s) return !1;
|
|
43
|
+
if (o) {
|
|
44
|
+
const f = t, b = n;
|
|
45
|
+
return f.length === b.length && f.every((x, C) => S(x, b[C]));
|
|
46
|
+
}
|
|
47
|
+
const m = t, c = n, h = Object.keys(m), i = Object.keys(c);
|
|
48
|
+
return h.length === i.length && h.every((f) => S(m[f], c[f]));
|
|
49
|
+
}
|
|
50
|
+
const ge = [
|
|
51
|
+
"varchar",
|
|
52
|
+
"text",
|
|
53
|
+
"integer",
|
|
54
|
+
"bigint",
|
|
55
|
+
"decimal",
|
|
56
|
+
"boolean",
|
|
57
|
+
"date",
|
|
58
|
+
"timestamp",
|
|
59
|
+
"uuid",
|
|
60
|
+
"jsonb"
|
|
61
|
+
];
|
|
62
|
+
function A({
|
|
63
|
+
column: t,
|
|
64
|
+
readOnly: n,
|
|
65
|
+
disableDelete: o,
|
|
66
|
+
onUpdate: s,
|
|
67
|
+
onInsertBelow: m,
|
|
68
|
+
onDelete: c
|
|
69
|
+
}) {
|
|
70
|
+
const { attributes: h, listeners: i, setNodeRef: f, transform: b, transition: x, isDragging: C } = U({ id: t.id, disabled: n }), a = t.name || "未命名";
|
|
71
|
+
return /* @__PURE__ */ u(
|
|
72
|
+
T,
|
|
73
|
+
{
|
|
74
|
+
ref: f,
|
|
75
|
+
style: { transform: ee.Transform.toString(b), transition: x },
|
|
76
|
+
className: V(C && "opacity-50"),
|
|
77
|
+
children: [
|
|
78
|
+
/* @__PURE__ */ e(p, { className: "w-10 px-2", children: n ? /* @__PURE__ */ e("span", { className: "flex h-7 w-7 items-center justify-center text-muted-foreground/30", children: /* @__PURE__ */ e(E, { className: "h-4 w-4" }) }) : /* @__PURE__ */ e(
|
|
79
|
+
"button",
|
|
80
|
+
{
|
|
81
|
+
type: "button",
|
|
82
|
+
className: "flex h-7 w-7 cursor-grab items-center justify-center text-muted-foreground active:cursor-grabbing",
|
|
83
|
+
"aria-label": `拖动列 ${a} 排序`,
|
|
84
|
+
...h,
|
|
85
|
+
...i,
|
|
86
|
+
children: /* @__PURE__ */ e(E, { className: "pointer-events-none h-4 w-4" })
|
|
87
|
+
}
|
|
88
|
+
) }),
|
|
89
|
+
/* @__PURE__ */ e(p, { children: /* @__PURE__ */ e(
|
|
90
|
+
v,
|
|
91
|
+
{
|
|
92
|
+
"aria-label": `列 ${a} 名称`,
|
|
93
|
+
autoComplete: "off",
|
|
94
|
+
disabled: n,
|
|
95
|
+
className: "h-7",
|
|
96
|
+
value: t.name,
|
|
97
|
+
onChange: (l) => s({ name: l.target.value })
|
|
98
|
+
}
|
|
99
|
+
) }),
|
|
100
|
+
/* @__PURE__ */ e(p, { children: /* @__PURE__ */ u(
|
|
101
|
+
se,
|
|
102
|
+
{
|
|
103
|
+
value: t.type,
|
|
104
|
+
onValueChange: (l) => s({ type: l }),
|
|
105
|
+
disabled: n,
|
|
106
|
+
children: [
|
|
107
|
+
/* @__PURE__ */ e(ie, { "aria-label": `列 ${a} 类型`, className: "h-7 w-28 text-xs", children: /* @__PURE__ */ e(ae, {}) }),
|
|
108
|
+
/* @__PURE__ */ e(oe, { children: ge.map((l) => /* @__PURE__ */ e(le, { value: l, className: "text-xs", children: l }, l)) })
|
|
109
|
+
]
|
|
110
|
+
}
|
|
111
|
+
) }),
|
|
112
|
+
/* @__PURE__ */ e(p, { children: /* @__PURE__ */ e(
|
|
113
|
+
k,
|
|
114
|
+
{
|
|
115
|
+
"aria-label": `列 ${a} 可空`,
|
|
116
|
+
checked: t.nullable,
|
|
117
|
+
disabled: n,
|
|
118
|
+
onCheckedChange: (l) => s({ nullable: l === !0 })
|
|
119
|
+
}
|
|
120
|
+
) }),
|
|
121
|
+
/* @__PURE__ */ e(p, { children: /* @__PURE__ */ e(
|
|
122
|
+
k,
|
|
123
|
+
{
|
|
124
|
+
"aria-label": `列 ${a} 主键`,
|
|
125
|
+
checked: t.primaryKey,
|
|
126
|
+
disabled: n,
|
|
127
|
+
onCheckedChange: (l) => s({ primaryKey: l === !0 })
|
|
128
|
+
}
|
|
129
|
+
) }),
|
|
130
|
+
/* @__PURE__ */ e(p, { children: /* @__PURE__ */ e(
|
|
131
|
+
k,
|
|
132
|
+
{
|
|
133
|
+
"aria-label": `列 ${a} 唯一`,
|
|
134
|
+
checked: t.unique,
|
|
135
|
+
disabled: n,
|
|
136
|
+
onCheckedChange: (l) => s({ unique: l === !0 })
|
|
137
|
+
}
|
|
138
|
+
) }),
|
|
139
|
+
/* @__PURE__ */ e(p, { children: /* @__PURE__ */ e(
|
|
140
|
+
v,
|
|
141
|
+
{
|
|
142
|
+
"aria-label": `列 ${a} 默认值`,
|
|
143
|
+
autoComplete: "off",
|
|
144
|
+
disabled: n,
|
|
145
|
+
className: "h-7",
|
|
146
|
+
value: t.defaultValue ?? "",
|
|
147
|
+
onChange: (l) => s({ defaultValue: l.target.value || void 0 })
|
|
148
|
+
}
|
|
149
|
+
) }),
|
|
150
|
+
/* @__PURE__ */ e(p, { children: /* @__PURE__ */ e(
|
|
151
|
+
v,
|
|
152
|
+
{
|
|
153
|
+
"aria-label": `列 ${a} 描述`,
|
|
154
|
+
autoComplete: "off",
|
|
155
|
+
disabled: n,
|
|
156
|
+
className: "h-7",
|
|
157
|
+
value: t.description ?? "",
|
|
158
|
+
onChange: (l) => s({ description: l.target.value || void 0 })
|
|
159
|
+
}
|
|
160
|
+
) }),
|
|
161
|
+
/* @__PURE__ */ e(p, { children: !n && /* @__PURE__ */ u("div", { className: "flex items-center justify-end gap-1", children: [
|
|
162
|
+
/* @__PURE__ */ e(
|
|
163
|
+
y,
|
|
164
|
+
{
|
|
165
|
+
type: "button",
|
|
166
|
+
variant: "ghost",
|
|
167
|
+
size: "icon",
|
|
168
|
+
className: "h-7 w-7",
|
|
169
|
+
onClick: m,
|
|
170
|
+
"aria-label": `在列 ${a} 下方添加列`,
|
|
171
|
+
children: /* @__PURE__ */ e(me, { className: "h-4 w-4" })
|
|
172
|
+
}
|
|
173
|
+
),
|
|
174
|
+
/* @__PURE__ */ e(
|
|
175
|
+
y,
|
|
176
|
+
{
|
|
177
|
+
type: "button",
|
|
178
|
+
variant: "ghost",
|
|
179
|
+
size: "icon",
|
|
180
|
+
className: "h-7 w-7",
|
|
181
|
+
disabled: o,
|
|
182
|
+
onClick: c,
|
|
183
|
+
"aria-label": `删除列 ${a}`,
|
|
184
|
+
children: /* @__PURE__ */ e(ue, { className: "h-4 w-4 text-destructive" })
|
|
185
|
+
}
|
|
186
|
+
)
|
|
187
|
+
] }) })
|
|
188
|
+
]
|
|
189
|
+
}
|
|
190
|
+
);
|
|
191
|
+
}
|
|
192
|
+
function De({
|
|
193
|
+
value: t,
|
|
194
|
+
onChange: n,
|
|
195
|
+
onSave: o,
|
|
196
|
+
readOnly: s,
|
|
197
|
+
className: m
|
|
198
|
+
}) {
|
|
199
|
+
const [c, h] = j(() => ({
|
|
200
|
+
history: [t],
|
|
201
|
+
index: 0
|
|
202
|
+
})), i = c.history[c.index], f = I(c);
|
|
203
|
+
D(() => {
|
|
204
|
+
f.current = c;
|
|
205
|
+
}, [c]);
|
|
206
|
+
const b = I(t), [x] = j(() => z());
|
|
207
|
+
D(() => {
|
|
208
|
+
S(t, b.current) || (h({ history: [t], index: 0 }), b.current = t);
|
|
209
|
+
}, [t]);
|
|
210
|
+
const C = w((r) => {
|
|
211
|
+
h((d) => S(d.history[d.index], r) ? d : {
|
|
212
|
+
history: [...d.history.slice(0, d.index + 1), r],
|
|
213
|
+
index: d.index + 1
|
|
214
|
+
});
|
|
215
|
+
}, []), a = w(
|
|
216
|
+
(r) => {
|
|
217
|
+
b.current = r, C(r), n(r);
|
|
218
|
+
},
|
|
219
|
+
[n, C]
|
|
220
|
+
), l = c.index > 0, B = c.index < c.history.length - 1, K = w(() => {
|
|
221
|
+
const r = f.current;
|
|
222
|
+
if (r.index <= 0) return;
|
|
223
|
+
const d = r.index - 1, g = r.history[d];
|
|
224
|
+
h({ ...r, index: d }), b.current = g, n(g);
|
|
225
|
+
}, [n]), P = w(() => {
|
|
226
|
+
const r = f.current;
|
|
227
|
+
if (r.index >= r.history.length - 1) return;
|
|
228
|
+
const d = r.index + 1, g = r.history[d];
|
|
229
|
+
h({ ...r, index: d }), b.current = g, n(g);
|
|
230
|
+
}, [n]), q = w(() => {
|
|
231
|
+
o == null || o(i);
|
|
232
|
+
}, [o, i]), F = Y(
|
|
233
|
+
$(_, { activationConstraint: { distance: 5 } }),
|
|
234
|
+
$(J, { coordinateGetter: W })
|
|
235
|
+
), G = w(
|
|
236
|
+
(r) => {
|
|
237
|
+
const { active: d, over: g } = r;
|
|
238
|
+
!g || d.id === g.id || a(be(i, String(d.id), String(g.id)));
|
|
239
|
+
},
|
|
240
|
+
[a, i]
|
|
241
|
+
), H = i.columns.length > 0 ? i.columns.map((r) => r.id) : s ? [] : [x.id];
|
|
242
|
+
return /* @__PURE__ */ u(
|
|
243
|
+
"div",
|
|
244
|
+
{
|
|
245
|
+
className: V(
|
|
246
|
+
"rounded-md border border-border bg-card text-card-foreground",
|
|
247
|
+
m
|
|
248
|
+
),
|
|
249
|
+
children: [
|
|
250
|
+
/* @__PURE__ */ u("div", { className: "flex flex-wrap items-center justify-between gap-2 border-b border-border p-2", children: [
|
|
251
|
+
/* @__PURE__ */ e("div", { className: "text-sm font-medium", children: "数据库表编辑器" }),
|
|
252
|
+
/* @__PURE__ */ u("div", { className: "flex items-center gap-2", children: [
|
|
253
|
+
!s && o && /* @__PURE__ */ e(y, { type: "button", size: "sm", onClick: q, className: "h-7 text-xs", children: "保存" }),
|
|
254
|
+
!s && /* @__PURE__ */ u(L, { children: [
|
|
255
|
+
/* @__PURE__ */ e(
|
|
256
|
+
y,
|
|
257
|
+
{
|
|
258
|
+
type: "button",
|
|
259
|
+
variant: "ghost",
|
|
260
|
+
size: "icon",
|
|
261
|
+
onClick: K,
|
|
262
|
+
disabled: !l,
|
|
263
|
+
className: "h-7 w-7",
|
|
264
|
+
"aria-label": "撤销",
|
|
265
|
+
children: /* @__PURE__ */ e(ce, { className: "h-4 w-4" })
|
|
266
|
+
}
|
|
267
|
+
),
|
|
268
|
+
/* @__PURE__ */ e(
|
|
269
|
+
y,
|
|
270
|
+
{
|
|
271
|
+
type: "button",
|
|
272
|
+
variant: "ghost",
|
|
273
|
+
size: "icon",
|
|
274
|
+
onClick: P,
|
|
275
|
+
disabled: !B,
|
|
276
|
+
className: "h-7 w-7",
|
|
277
|
+
"aria-label": "恢复",
|
|
278
|
+
children: /* @__PURE__ */ e(de, { className: "h-4 w-4" })
|
|
279
|
+
}
|
|
280
|
+
)
|
|
281
|
+
] })
|
|
282
|
+
] })
|
|
283
|
+
] }),
|
|
284
|
+
/* @__PURE__ */ u("div", { className: "space-y-2 border-b border-border p-2", children: [
|
|
285
|
+
/* @__PURE__ */ u("div", { className: "flex items-center gap-2", children: [
|
|
286
|
+
/* @__PURE__ */ e("label", { htmlFor: "db-table-name", className: "w-10 shrink-0 text-xs text-muted-foreground", children: "表名" }),
|
|
287
|
+
/* @__PURE__ */ e(
|
|
288
|
+
v,
|
|
289
|
+
{
|
|
290
|
+
id: "db-table-name",
|
|
291
|
+
autoComplete: "off",
|
|
292
|
+
disabled: s,
|
|
293
|
+
className: "h-7",
|
|
294
|
+
value: i.name,
|
|
295
|
+
onChange: (r) => a(xe(i, r.target.value))
|
|
296
|
+
}
|
|
297
|
+
)
|
|
298
|
+
] }),
|
|
299
|
+
/* @__PURE__ */ u("div", { className: "flex items-center gap-2", children: [
|
|
300
|
+
/* @__PURE__ */ e(
|
|
301
|
+
"label",
|
|
302
|
+
{
|
|
303
|
+
htmlFor: "db-table-description",
|
|
304
|
+
className: "w-10 shrink-0 text-xs text-muted-foreground",
|
|
305
|
+
children: "描述"
|
|
306
|
+
}
|
|
307
|
+
),
|
|
308
|
+
/* @__PURE__ */ e(
|
|
309
|
+
v,
|
|
310
|
+
{
|
|
311
|
+
id: "db-table-description",
|
|
312
|
+
autoComplete: "off",
|
|
313
|
+
disabled: s,
|
|
314
|
+
className: "h-7",
|
|
315
|
+
value: i.description ?? "",
|
|
316
|
+
onChange: (r) => a(pe(i, r.target.value || void 0))
|
|
317
|
+
}
|
|
318
|
+
)
|
|
319
|
+
] })
|
|
320
|
+
] }),
|
|
321
|
+
/* @__PURE__ */ e(
|
|
322
|
+
M,
|
|
323
|
+
{
|
|
324
|
+
sensors: F,
|
|
325
|
+
collisionDetection: Q,
|
|
326
|
+
modifiers: [O],
|
|
327
|
+
onDragEnd: G,
|
|
328
|
+
children: /* @__PURE__ */ u(te, { children: [
|
|
329
|
+
/* @__PURE__ */ e(ne, { children: /* @__PURE__ */ u(T, { children: [
|
|
330
|
+
/* @__PURE__ */ e(N, { className: "w-10" }),
|
|
331
|
+
/* @__PURE__ */ e(N, { className: "w-[20%]", children: "名称" }),
|
|
332
|
+
/* @__PURE__ */ e(N, { className: "w-[12%]", children: "类型" }),
|
|
333
|
+
/* @__PURE__ */ e(N, { className: "w-[8%]", children: "可空" }),
|
|
334
|
+
/* @__PURE__ */ e(N, { className: "w-[8%]", children: "主键" }),
|
|
335
|
+
/* @__PURE__ */ e(N, { className: "w-[8%]", children: "唯一" }),
|
|
336
|
+
/* @__PURE__ */ e(N, { className: "w-[14%]", children: "默认值" }),
|
|
337
|
+
/* @__PURE__ */ e(N, { className: "w-[20%]", children: "描述" }),
|
|
338
|
+
/* @__PURE__ */ e(N, { className: "w-[10%]" })
|
|
339
|
+
] }) }),
|
|
340
|
+
/* @__PURE__ */ e(re, { children: /* @__PURE__ */ e(X, { items: H, strategy: Z, children: i.columns.length === 0 ? s ? /* @__PURE__ */ e(T, { children: /* @__PURE__ */ e(p, { colSpan: 9, className: "h-12 text-center text-sm text-muted-foreground", children: "暂无列" }) }) : /* @__PURE__ */ e(
|
|
341
|
+
A,
|
|
342
|
+
{
|
|
343
|
+
column: x,
|
|
344
|
+
disableDelete: !0,
|
|
345
|
+
onUpdate: (r) => a({ ...i, columns: [{ ...x, ...r }] }),
|
|
346
|
+
onInsertBelow: () => a(
|
|
347
|
+
R({ ...i, columns: [x] }, x.id)
|
|
348
|
+
),
|
|
349
|
+
onDelete: () => {
|
|
350
|
+
}
|
|
351
|
+
},
|
|
352
|
+
x.id
|
|
353
|
+
) : i.columns.map((r) => /* @__PURE__ */ e(
|
|
354
|
+
A,
|
|
355
|
+
{
|
|
356
|
+
column: r,
|
|
357
|
+
readOnly: s,
|
|
358
|
+
onUpdate: (d) => a(he(i, r.id, d)),
|
|
359
|
+
onInsertBelow: () => a(R(i, r.id)),
|
|
360
|
+
onDelete: () => a(fe(i, r.id))
|
|
361
|
+
},
|
|
362
|
+
r.id
|
|
363
|
+
)) }) })
|
|
364
|
+
] })
|
|
365
|
+
}
|
|
366
|
+
)
|
|
367
|
+
]
|
|
368
|
+
}
|
|
369
|
+
);
|
|
370
|
+
}
|
|
371
|
+
export {
|
|
372
|
+
De as DbSchemaEditor
|
|
373
|
+
};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { DbColumn, DbTable } from '@cisri/db-schema-core';
|
|
2
|
+
/** 列字段的部分更新(不可更新 id)。 */
|
|
3
|
+
export type ColumnPatch = Partial<Omit<DbColumn, 'id'>>;
|
|
4
|
+
/** 更新指定列的字段,返回新表。 */
|
|
5
|
+
export declare function updateColumn(table: DbTable, id: string, patch: ColumnPatch): DbTable;
|
|
6
|
+
/** 追加一个空列(默认 varchar、可空)。 */
|
|
7
|
+
export declare function addColumn(table: DbTable, name?: string): DbTable;
|
|
8
|
+
/** 在指定列下方插入一个空列;找不到 id 时返回原表。 */
|
|
9
|
+
export declare function insertColumnBelow(table: DbTable, id: string): DbTable;
|
|
10
|
+
/** 按 id 删除列。 */
|
|
11
|
+
export declare function deleteColumn(table: DbTable, id: string): DbTable;
|
|
12
|
+
/** 将 fromId 列移动到 toId 列的位置(dnd-kit onDragEnd 语义:active 落到 over 的位置);任一 id 不存在或相同时返回原表。 */
|
|
13
|
+
export declare function reorderColumns(table: DbTable, fromId: string, toId: string): DbTable;
|
|
14
|
+
/** 更新表名。 */
|
|
15
|
+
export declare function updateTableName(table: DbTable, name: string): DbTable;
|
|
16
|
+
/** 更新表描述(传 undefined 清除)。 */
|
|
17
|
+
export declare function updateTableDescription(table: DbTable, description: string | undefined): DbTable;
|
|
18
|
+
/** 深比较,用于历史去重与外部 value 同步判断。 */
|
|
19
|
+
export declare function deepEqual(a: unknown, b: unknown): boolean;
|
|
20
|
+
//# sourceMappingURL=schema-utils.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"schema-utils.d.ts","sourceRoot":"","sources":["../src/schema-utils.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,uBAAuB,CAAC;AAG/D,yBAAyB;AACzB,MAAM,MAAM,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC,CAAC;AAExD,qBAAqB;AACrB,wBAAgB,YAAY,CAC1B,KAAK,EAAE,OAAO,EACd,EAAE,EAAE,MAAM,EACV,KAAK,EAAE,WAAW,GACjB,OAAO,CAKT;AAED,6BAA6B;AAC7B,wBAAgB,SAAS,CAAC,KAAK,EAAE,OAAO,EAAE,IAAI,SAAK,GAAG,OAAO,CAE5D;AAED,iCAAiC;AACjC,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,OAAO,EAAE,EAAE,EAAE,MAAM,GAAG,OAAO,CAMrE;AAED,gBAAgB;AAChB,wBAAgB,YAAY,CAAC,KAAK,EAAE,OAAO,EAAE,EAAE,EAAE,MAAM,GAAG,OAAO,CAEhE;AAED,0FAA0F;AAC1F,wBAAgB,cAAc,CAAC,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAQpF;AAED,YAAY;AACZ,wBAAgB,eAAe,CAAC,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAErE;AAED,6BAA6B;AAC7B,wBAAgB,sBAAsB,CACpC,KAAK,EAAE,OAAO,EACd,WAAW,EAAE,MAAM,GAAG,SAAS,GAC9B,OAAO,CAET;AAED,gCAAgC;AAChC,wBAAgB,SAAS,CAAC,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,OAAO,GAAG,OAAO,CAgBzD"}
|
package/package.json
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@cisri/db-schema-editor",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Database table schema editor business component based on shadcn/ui",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"module": "./dist/index.js",
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"import": "./dist/index.js",
|
|
13
|
+
"require": "./dist/index.cjs"
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
"files": [
|
|
17
|
+
"dist"
|
|
18
|
+
],
|
|
19
|
+
"sideEffects": false,
|
|
20
|
+
"publishConfig": {
|
|
21
|
+
"access": "public"
|
|
22
|
+
},
|
|
23
|
+
"dependencies": {
|
|
24
|
+
"@cisri/core": "1.0.0",
|
|
25
|
+
"@cisri/db-schema-core": "1.0.0",
|
|
26
|
+
"@cisri/shadcn": "1.0.0"
|
|
27
|
+
},
|
|
28
|
+
"peerDependencies": {
|
|
29
|
+
"react": "^18.0.0 || ^19.0.0",
|
|
30
|
+
"react-dom": "^18.0.0 || ^19.0.0",
|
|
31
|
+
"lucide-react": "^0.300.0",
|
|
32
|
+
"@dnd-kit/core": "^6.0.0",
|
|
33
|
+
"@dnd-kit/sortable": "^8.0.0",
|
|
34
|
+
"@dnd-kit/modifiers": "^7.0.0",
|
|
35
|
+
"@dnd-kit/utilities": "^3.2.0"
|
|
36
|
+
},
|
|
37
|
+
"peerDependenciesMeta": {
|
|
38
|
+
"lucide-react": {
|
|
39
|
+
"optional": false
|
|
40
|
+
},
|
|
41
|
+
"@dnd-kit/core": {
|
|
42
|
+
"optional": false
|
|
43
|
+
},
|
|
44
|
+
"@dnd-kit/sortable": {
|
|
45
|
+
"optional": false
|
|
46
|
+
},
|
|
47
|
+
"@dnd-kit/modifiers": {
|
|
48
|
+
"optional": false
|
|
49
|
+
},
|
|
50
|
+
"@dnd-kit/utilities": {
|
|
51
|
+
"optional": false
|
|
52
|
+
}
|
|
53
|
+
},
|
|
54
|
+
"devDependencies": {
|
|
55
|
+
"@testing-library/jest-dom": "^6.4.0",
|
|
56
|
+
"@testing-library/react": "^15.0.0",
|
|
57
|
+
"@testing-library/user-event": "^14.5.0",
|
|
58
|
+
"@types/react": "^18.3.0",
|
|
59
|
+
"@types/react-dom": "^18.3.0",
|
|
60
|
+
"jsdom": "^24.0.0",
|
|
61
|
+
"react": "^18.3.1",
|
|
62
|
+
"react-dom": "^18.3.1",
|
|
63
|
+
"typescript": "^5.4.5",
|
|
64
|
+
"vite": "^5.2.11",
|
|
65
|
+
"vitest": "^1.6.0",
|
|
66
|
+
"@vitejs/plugin-react": "^4.2.0"
|
|
67
|
+
},
|
|
68
|
+
"scripts": {
|
|
69
|
+
"build": "tsc && vite build",
|
|
70
|
+
"test": "vitest run",
|
|
71
|
+
"test:watch": "vitest"
|
|
72
|
+
}
|
|
73
|
+
}
|