mirror of
https://github.com/stackrender/stackrender.git
synced 2026-09-10 03:05:42 +00:00
code editor implemented
This commit is contained in:
@@ -10,6 +10,7 @@
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"@codemirror/lang-sql": "^6.9.0",
|
||||
"@dnd-kit/core": "^6.3.1",
|
||||
"@dnd-kit/sortable": "^10.0.0",
|
||||
"@heroui/react": "^2.7.6",
|
||||
@@ -20,6 +21,7 @@
|
||||
"@radix-ui/react-tooltip": "^1.2.3",
|
||||
"@react-aria/visually-hidden": "3.8.21",
|
||||
"@react-types/shared": "3.28.0",
|
||||
"@uiw/react-codemirror": "^4.23.12",
|
||||
"@xyflow/react": "^12.6.3",
|
||||
"date-fns": "^4.1.0",
|
||||
"drizzle-orm": "^0.43.1",
|
||||
@@ -35,6 +37,7 @@
|
||||
"react-dom": "18.3.1",
|
||||
"react-i18next": "^15.5.1",
|
||||
"react-router-dom": "6.23.0",
|
||||
"sql-formatter": "^15.6.3",
|
||||
"tailwind-merge": "^3.2.0",
|
||||
"tailwind-variants": "0.3.0",
|
||||
"tailwindcss": "3.4.16",
|
||||
|
||||
@@ -6,6 +6,7 @@ import { useEffect, useState } from "react";
|
||||
|
||||
import { Parser } from "node-sql-parser";
|
||||
import { DatabaseType } from "@/lib/schemas/database-schema";
|
||||
import { format } from 'sql-formatter';
|
||||
|
||||
const parser = new Parser();
|
||||
|
||||
@@ -16,13 +17,14 @@ export const useRenderSql = (database: DatabaseType) => {
|
||||
|
||||
useEffect(() => {
|
||||
const dbAst: any = DatabaseToAst(database, data_types);
|
||||
const formattedSqlCode = format(parser.sqlify(dbAst), { language: 'sql' }); // or 'mysql', 'postgresql', etc.
|
||||
setSql(
|
||||
parser.sqlify(dbAst)
|
||||
formattedSqlCode
|
||||
);
|
||||
|
||||
}, [database]) ;
|
||||
}, [database]);
|
||||
|
||||
|
||||
return sql ;
|
||||
return sql;
|
||||
|
||||
}
|
||||
@@ -2,7 +2,7 @@ import { sqliteTable, text, integer } from 'drizzle-orm/sqlite-core';
|
||||
import { tables } from './table-schema';
|
||||
import { data_types, DataType } from './data-type-schema';
|
||||
import { InferInsertModel, InferSelectModel, relations } from 'drizzle-orm';
|
||||
import { relationships } from './relationship-schema';
|
||||
import { relationships, RelationshipType } from './relationship-schema';
|
||||
import { field_indices } from './field_index-schema';
|
||||
import { indices } from './index-schema';
|
||||
|
||||
@@ -32,8 +32,8 @@ export const fieldsRelations = relations(fields, ({ one, many }) => ({
|
||||
fields: [fields.typeId],
|
||||
references: [data_types.id]
|
||||
}),
|
||||
sourceRelations: one(relationships),
|
||||
targetRelations: one(relationships),
|
||||
sourceRelations: many(relationships),
|
||||
targetRelations: many(relationships),
|
||||
|
||||
fieldIndices: many(field_indices),
|
||||
indices: many(indices)
|
||||
@@ -42,8 +42,12 @@ export const fieldsRelations = relations(fields, ({ one, many }) => ({
|
||||
|
||||
export interface FieldType extends InferSelectModel<typeof fields> {
|
||||
sequence: number,
|
||||
type: DataType
|
||||
type: DataType,
|
||||
sourceRelations?: RelationshipType[],
|
||||
targetRelations?: RelationshipType[]
|
||||
};
|
||||
|
||||
|
||||
export interface FieldInsertType extends InferInsertModel<typeof fields> { };
|
||||
export interface FieldInsertType extends InferInsertModel<typeof fields> {
|
||||
|
||||
};
|
||||
@@ -4,7 +4,7 @@ import { sqliteTable, text, integer } from 'drizzle-orm/sqlite-core';
|
||||
import { tables } from './table-schema';
|
||||
import { InferInsertModel, InferSelectModel, relations } from 'drizzle-orm';
|
||||
import { field_indices, FieldIndexInsertType, FieldIndexType } from './field_index-schema';
|
||||
import { fields } from './field-schema';
|
||||
import { fields, FieldType } from './field-schema';
|
||||
|
||||
|
||||
|
||||
@@ -32,7 +32,8 @@ export const indicesRlationships = relations(indices, ({ one, many }) => ({
|
||||
|
||||
|
||||
export interface IndexType extends InferSelectModel<typeof indices> {
|
||||
fieldIndices: FieldIndexType[]
|
||||
fieldIndices: FieldIndexType[] ;
|
||||
fields : FieldType[] ;
|
||||
};
|
||||
export interface IndexInsertType extends InferInsertModel<typeof indices> {
|
||||
fieldIndices? : FieldIndexInsertType[]
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { InferInsertModel, InferSelectModel, relations } from 'drizzle-orm';
|
||||
import { sqliteTable, text, real, integer } from 'drizzle-orm/sqlite-core';
|
||||
import { fields, FieldType } from './field-schema';
|
||||
import { relationships } from './relationship-schema';
|
||||
import { relationships, RelationshipType } from './relationship-schema';
|
||||
import { databases } from './database-schema';
|
||||
import { IndexType, indices } from './index-schema';
|
||||
|
||||
@@ -43,11 +43,14 @@ export const tablesRelations = relations(tables, ({ many, one }) => ({
|
||||
export interface TableType extends InferSelectModel<typeof tables> {
|
||||
fields: FieldType[];
|
||||
indices : IndexType[] ;
|
||||
|
||||
sourceRelations? :RelationshipType[] ;
|
||||
targetRelations? : RelationshipType[] ;
|
||||
|
||||
};
|
||||
|
||||
|
||||
export interface TableInsertType extends InferInsertModel<typeof tables> {
|
||||
fields?: FieldType[];
|
||||
|
||||
|
||||
};
|
||||
@@ -1,27 +1,67 @@
|
||||
import { useRenderSql } from "@/hooks/user-render-sql";
|
||||
import { useDatabase } from "@/providers/database-provider/database-provider";
|
||||
import React from "react";
|
||||
import React, { useEffect, useMemo } from "react";
|
||||
|
||||
import CodeMirror from '@uiw/react-codemirror';
|
||||
import { sql } from '@codemirror/lang-sql';
|
||||
import { oneDark } from '@codemirror/theme-one-dark';
|
||||
import { useTheme } from "next-themes";
|
||||
import { Parser } from "node-sql-parser";
|
||||
|
||||
const parser = new Parser();
|
||||
const code = `
|
||||
|
||||
-- Addresses table with a foreign key to users
|
||||
CREATE TABLE addresses (
|
||||
id INTEGER PRIMARY KEY,
|
||||
user_id INT NOT NULL,
|
||||
street VARCHAR(255),
|
||||
city VARCHAR(100),
|
||||
state VARCHAR(100),
|
||||
postal_code VARCHAR(20),
|
||||
country VARCHAR(100),
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
-- Indexes to improve lookup performance
|
||||
CREATE UNIQUE INDEX idx_addresses_user_id ON addresses(user_id);
|
||||
CREATE INDEX idx_addresses_city ON addresses(city , country);
|
||||
|
||||
|
||||
`
|
||||
|
||||
|
||||
|
||||
const SqlPreview: React.FC = ({ }) => {
|
||||
|
||||
const { database } = useDatabase();
|
||||
const sqlCode = useRenderSql(database);
|
||||
const {resolvedTheme} = useTheme();
|
||||
|
||||
useEffect(() => {
|
||||
const ast = parser.astify(code )
|
||||
console.log (ast)
|
||||
}, [])
|
||||
|
||||
const SqlPreview : React.FC = ({}) => {
|
||||
|
||||
const {database} = useDatabase() ;
|
||||
const sql = useRenderSql(database) ;
|
||||
|
||||
|
||||
return(
|
||||
<p>
|
||||
{sql
|
||||
return (
|
||||
<div className="flex w-full h-full ">
|
||||
{
|
||||
|
||||
<CodeMirror
|
||||
value={sqlCode}
|
||||
className="flex flex-1 w-full"
|
||||
|
||||
|
||||
extensions={[sql()]}
|
||||
theme={resolvedTheme == "light" ? undefined : oneDark}
|
||||
/>
|
||||
}
|
||||
</p>
|
||||
</div>
|
||||
)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
export default React.memo(SqlPreview) ;
|
||||
export default React.memo(SqlPreview);
|
||||
@@ -79,15 +79,10 @@ const TablesController: React.FC<Props> = ({ }) => {
|
||||
}, [nameRef, allTables]);
|
||||
|
||||
|
||||
|
||||
const toggleSqlPreview = useCallback(() => {
|
||||
setShowSqlPreview(preview => !preview);
|
||||
}, [])
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
return (
|
||||
<div className="w-full h-full flex flex-col gap-2">
|
||||
<div className="flex items-center justify-between gap-4 py-1">
|
||||
@@ -179,7 +174,11 @@ const TablesController: React.FC<Props> = ({ }) => {
|
||||
</div>
|
||||
}
|
||||
{
|
||||
showSqlPreview && <SqlPreview/>
|
||||
showSqlPreview &&
|
||||
<div className=" flex-1 overflow-auto ">
|
||||
<SqlPreview />
|
||||
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -173,4 +173,9 @@ tbody[role="rowgroup"] tr td:last-child {
|
||||
tbody[role="rowgroup"] tr td:first-child {
|
||||
border-radius: 4px 0px 0px 4px !important;
|
||||
|
||||
}
|
||||
|
||||
.cm-editor {
|
||||
|
||||
min-width : 100% !important ;
|
||||
}
|
||||
@@ -3,38 +3,94 @@
|
||||
import { DataType } from "@/lib/schemas/data-type-schema";
|
||||
import { DatabaseType } from "@/lib/schemas/database-schema";
|
||||
import { FieldType } from "@/lib/schemas/field-schema";
|
||||
import { FieldIndexType } from "@/lib/schemas/field_index-schema";
|
||||
import { IndexType } from "@/lib/schemas/index-schema";
|
||||
import { RelationshipType } from "@/lib/schemas/relationship-schema";
|
||||
import { TableType } from "@/lib/schemas/table-schema";
|
||||
|
||||
|
||||
export const DatabaseToAst = (database: DatabaseType , data_types : DataType[]) => {
|
||||
export const DatabaseToAst = (database: DatabaseType, data_types: DataType[]) => {
|
||||
let dbAst: any = [];
|
||||
if ( ! data_types || data_types.length == 0)
|
||||
return dbAst ;
|
||||
if (!data_types || data_types.length == 0)
|
||||
return dbAst;
|
||||
for (const table of database.tables) {
|
||||
dbAst.push(TableToAst(table , data_types ))
|
||||
dbAst.push(TableToAst({
|
||||
...table,
|
||||
sourceRelations: database.relationships.filter((relationship: RelationshipType) => relationship.sourceTableId == table.id),
|
||||
targetRelations: database.relationships.filter((relationship: RelationshipType) => relationship.targetTableId == table.id),
|
||||
}, data_types));
|
||||
|
||||
if (table.indices && table.indices.length > 0)
|
||||
for (const index of table.indices)
|
||||
dbAst.push(IndexToAst({
|
||||
...index,
|
||||
fields: index.fieldIndices.map((fieldIndex: FieldIndexType) => table.fields.find((field: FieldType) => field.id == fieldIndex.fieldId)) as FieldType[]
|
||||
}, table));
|
||||
};
|
||||
|
||||
return dbAst;
|
||||
}
|
||||
|
||||
|
||||
export const TableToAst = (table: TableType , data_types : DataType[]) => {
|
||||
export const TableToAst = (table: TableType, data_types: DataType[]) => {
|
||||
|
||||
const constraints: any[] = table.sourceRelations ? table.sourceRelations?.map((relationship: RelationshipType) => {
|
||||
return {
|
||||
constraint_type: "FOREIGN KEY",
|
||||
resource: "constraint",
|
||||
definition: [
|
||||
{
|
||||
type: "column_ref",
|
||||
column: relationship.sourceField.name,
|
||||
|
||||
}
|
||||
],
|
||||
reference_definition: {
|
||||
on_action: [
|
||||
{
|
||||
"type": "on delete",
|
||||
"value": {
|
||||
"type": "origin",
|
||||
"value": "cascade"
|
||||
}
|
||||
}
|
||||
],
|
||||
keyword: "references",
|
||||
table: [{
|
||||
table: relationship.targetTable.name,
|
||||
}],
|
||||
definition: [
|
||||
{
|
||||
type: "column_ref",
|
||||
column: relationship.targetField.name,
|
||||
}
|
||||
],
|
||||
}
|
||||
}
|
||||
|
||||
}) : [];
|
||||
|
||||
const filed_definitions = table.fields.map((field: FieldType) => FieldToAst({
|
||||
...field,
|
||||
type: data_types.find((dataType: DataType) => dataType.id == field.typeId) as DataType,
|
||||
sourceRelations: table.sourceRelations?.filter((relationship: RelationshipType) => relationship.sourceFieldId == field.id),
|
||||
targetRelations: table.targetRelations?.filter((relationship: RelationshipType) => relationship.targetFieldId == field.id),
|
||||
|
||||
}));
|
||||
return {
|
||||
keyword: "table",
|
||||
type: "create",
|
||||
table: [{
|
||||
table: table.name
|
||||
}],
|
||||
create_definitions: table.fields.map((field: FieldType) => FieldToAst({
|
||||
...field ,
|
||||
type : data_types.find((dataType : DataType) => dataType.id == field.typeId) as DataType
|
||||
} ))
|
||||
create_definitions: [...filed_definitions, ...constraints]
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
export const FieldToAst = (field: FieldType ) => {
|
||||
export const FieldToAst = (field: FieldType) => {
|
||||
|
||||
return {
|
||||
column: {
|
||||
type: "column_ref",
|
||||
@@ -51,7 +107,7 @@ export const FieldToAst = (field: FieldType ) => {
|
||||
value: field.nullable ? "null" : "not null",
|
||||
},
|
||||
definition: {
|
||||
dataType: field.type?.name?.toLocaleUpperCase() ,
|
||||
dataType: field.type?.name?.toLocaleUpperCase(),
|
||||
|
||||
},
|
||||
primary_key: field.isPrimary ? "primary key" : null,
|
||||
@@ -60,3 +116,18 @@ export const FieldToAst = (field: FieldType ) => {
|
||||
}
|
||||
|
||||
|
||||
|
||||
export const IndexToAst = (index: IndexType, table: TableType) => {
|
||||
|
||||
return {
|
||||
index: index.name,
|
||||
type: "create",
|
||||
table: { table: table.name },
|
||||
keyword: "index",
|
||||
index_type: index.unique ? "unique" : null,
|
||||
index_columns: index.fields.map((field: FieldType) => ({
|
||||
column: field.name,
|
||||
type: "column_ref"
|
||||
}))
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user