diff --git a/package.json b/package.json index 9bf88b2..05d4641 100644 --- a/package.json +++ b/package.json @@ -13,12 +13,14 @@ "@dnd-kit/core": "^6.3.1", "@dnd-kit/sortable": "^10.0.0", "@heroui/react": "^2.7.6", + "@powersync/drizzle-driver": "^0.4.0", "@powersync/react": "^1.5.3", "@powersync/web": "^1.20.0", "@radix-ui/react-tooltip": "^1.2.3", "@react-aria/visually-hidden": "3.8.21", "@react-types/shared": "3.28.0", "@xyflow/react": "^12.6.0", + "drizzle-orm": "^0.43.1", "framer-motion": "11.15.0", "i18next-browser-languagedetector": "^8.0.5", "lucide-react": "^0.501.0", diff --git a/src/App.tsx b/src/App.tsx index a725ea4..5f0ede0 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -3,16 +3,19 @@ import "@/styles/globals.css" import { TooltipProvider } from "./components/tooltip/tooltip"; import { ReactFlowProvider } from "@xyflow/react"; import useAppRoutes from "./routes/app-route"; -import { SyncProvider } from "./providers/sync-provider"; +import { SyncProvider } from "./providers/sync-provider/sync-provider"; +import DatabaseProvider from "./providers/database-provider/database-provider"; function App() { const appRoutes = useAppRoutes(); return ( - - {appRoutes} - + + + {appRoutes} + + ); diff --git a/src/components/auto-complete/auto-complete.tsx b/src/components/auto-complete/auto-complete.tsx new file mode 100644 index 0000000..33a404e --- /dev/null +++ b/src/components/auto-complete/auto-complete.tsx @@ -0,0 +1,40 @@ + +import { Autocomplete as HeroUiAutocomplete, AutocompleteItem } from "@heroui/react"; +import { Key } from "react"; + +interface AutocompleteProps { + + items: any[] + label?: string, + defaultSelection: string | undefined + onSelectionChange?: (item: any) => void, + placeholder?: string +} + + + +const Autocomplete: React.FC = ({ items, label = "name", onSelectionChange, defaultSelection, placeholder }) => { + + + + const onItemChange = (item: Key | null) => { + onSelectionChange && onSelectionChange(item); + } + + return ( + + {(item: any) => {item[label]}} + + ) +} + +export default Autocomplete ; \ No newline at end of file diff --git a/src/components/toggle/toggle.tsx b/src/components/toggle/toggle.tsx new file mode 100644 index 0000000..482a593 --- /dev/null +++ b/src/components/toggle/toggle.tsx @@ -0,0 +1,46 @@ +import { cn } from "@heroui/react" +import { Tooltip, TooltipContent, TooltipTrigger } from "../tooltip/tooltip" + +interface ToggleProps { + active?: boolean, + children: React.ReactNode, + onToggle?: (value: boolean) => void, + className?: string , + label? : string +} + + +const ToggleButton: React.FC = ({ active = false, children, onToggle, className , label }) => { + + const toggle = () => { + onToggle && onToggle(!active) + } + + return ( + + + + + + {label} + + + ) +} + + +export default ToggleButton \ No newline at end of file diff --git a/src/i18/languages/en.ts b/src/i18/languages/en.ts index bbc4b3c..932226a 100644 --- a/src/i18/languages/en.ts +++ b/src/i18/languages/en.ts @@ -36,7 +36,11 @@ export const en = { many_to_many : "Many to Many" , } , delete : "Delete" , - + field_setting : "Field Setting" , + table_actions : "Field Actions" , + field_note : "Field note" , + delete_field : "Delete Field" , + }, table : { double_click : "Double click to edit" diff --git a/src/lib/data/data-types.ts b/src/lib/data/data-types.ts deleted file mode 100644 index 83967e8..0000000 --- a/src/lib/data/data-types.ts +++ /dev/null @@ -1,4 +0,0 @@ -export interface DataType { - id: string; - name: string; -} diff --git a/src/lib/interfaces/field.ts b/src/lib/interfaces/field.ts deleted file mode 100644 index b61d1d8..0000000 --- a/src/lib/interfaces/field.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { DataType } from "../data/data-types"; - -export interface Field { - id: string; - name: string; - type: DataType; - primaryKey: boolean; - unique: boolean; - nullable: boolean; - createdAt: number; - default?: string; - - -} \ No newline at end of file diff --git a/src/lib/interfaces/table.ts b/src/lib/interfaces/table.ts deleted file mode 100644 index db2075c..0000000 --- a/src/lib/interfaces/table.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { Field } from "./field"; - -export interface Table { - id: string; - name: string; - x: number; - y: number; - fields: Field[]; - width?: number; - order?: number; - color: string; - createdAt: number; - comments?: string; -} \ No newline at end of file diff --git a/src/lib/schemas/app-schema.ts b/src/lib/schemas/app-schema.ts index a54f6e5..c27f09a 100644 --- a/src/lib/schemas/app-schema.ts +++ b/src/lib/schemas/app-schema.ts @@ -1,24 +1,19 @@ -import { column, Schema, Table } from '@powersync/web'; - -export const DATATYPE_TABLE = 'dataTypes'; - - -const data_types = new Table( - { - name: column.text, - }, -); - +import { DrizzleAppSchema, wrapPowerSyncWithDrizzle } from '@powersync/drizzle-driver'; +import { data_types } from './data-type-schema'; +import { tables, tablesRelations } from './table-schema'; +import { fields, fieldsRelations } from './field-schema'; -export const AppSchema = new Schema({ - data_types, - -}); +export const drizzleSchema = { + data_types , + tables , + fields , -export type Database = (typeof AppSchema)['types']; -export type DataTypesRecord = Database['data_types']; -// OR: -// export type Todo = RowType; - + // relationships + tablesRelations , + fieldsRelations +}; + +// Infer the PowerSync schema from your Drizzle schema +export const AppSchema = new DrizzleAppSchema(drizzleSchema); diff --git a/src/lib/schemas/data-type-schema.ts b/src/lib/schemas/data-type-schema.ts new file mode 100644 index 0000000..3c1218f --- /dev/null +++ b/src/lib/schemas/data-type-schema.ts @@ -0,0 +1,11 @@ +import { InferSelectModel } from 'drizzle-orm'; +import { sqliteTable, text } from 'drizzle-orm/sqlite-core'; + +export const data_types = sqliteTable('data_types', { + id : text("id") , + name: text('name'), +}); + + + +export interface DataType extends InferSelectModel { }; \ No newline at end of file diff --git a/src/lib/schemas/database-schema.ts b/src/lib/schemas/database-schema.ts new file mode 100644 index 0000000..e69de29 diff --git a/src/lib/schemas/field-schema.ts b/src/lib/schemas/field-schema.ts new file mode 100644 index 0000000..20c68ad --- /dev/null +++ b/src/lib/schemas/field-schema.ts @@ -0,0 +1,36 @@ +import { sqliteTable, text, real, integer } from 'drizzle-orm/sqlite-core'; +import { tables } from './table-schema'; +import { data_types } from './data-type-schema'; +import { InferInsertModel, InferSelectModel, relations } from 'drizzle-orm'; + + +export const fields = sqliteTable("fields", { + id: text("id").primaryKey().notNull(), + tableId: text("tableId") + .notNull() + .references(() => tables.id, { onDelete: "cascade" }), + + name: text("name").notNull(), + isPrimary: integer("isPrimary", { mode: "boolean" }), + unique: integer("unique", { mode: "boolean" }), + nullable: integer("nullable", { mode: "boolean" }), + defaultValue: text("defaultValue"), + note: text("note"), + typeId: text("typeId").references(() => data_types.id, { onDelete: "cascade" }), + sequence: integer("sequence").default(0), +}); + +export const fieldsRelations = relations(fields, ({ one }) => ({ + table: one(tables, { + fields: [fields.tableId], + references: [tables.id], + }), +})); + + +export interface FieldType extends InferSelectModel { + sequence: number +}; + + +export interface FieldInsertType extends InferInsertModel { }; \ No newline at end of file diff --git a/src/lib/schemas/table-schema.ts b/src/lib/schemas/table-schema.ts new file mode 100644 index 0000000..720521a --- /dev/null +++ b/src/lib/schemas/table-schema.ts @@ -0,0 +1,39 @@ +import { InferInsertModel, InferSelectModel, relations } from 'drizzle-orm'; +import { sqliteTable, text, real, integer } from 'drizzle-orm/sqlite-core'; +import { fields, FieldType } from './field-schema'; + +export const tables = sqliteTable('tables', { + id: text('id') + .primaryKey() + .notNull() + .unique(), + + databaseId: text('databaseId'), + + name: text('name').notNull(), + posX: real('posX').notNull().default(0), + posY: real('posY').notNull().default(0), + + color: text('color'), + width: real('width'), + note: text('note'), + sequence: integer('sequence').default(0), + createdAt: text('createdAt'), + +}); + + +export const tablesRelations = relations(tables, ({ many }) => ({ + fields: many(fields), +})); + + + +export interface TableType extends InferSelectModel { + fields : FieldType[] +}; + + +export interface TableInsertType extends InferInsertModel { + fields? : FieldType[] +}; \ No newline at end of file diff --git a/src/pages/database/database-page.tsx b/src/pages/database/database-page.tsx index 6af68cd..d0a6613 100644 --- a/src/pages/database/database-page.tsx +++ b/src/pages/database/database-page.tsx @@ -1,12 +1,12 @@ -import { Table as TableType } from "@/lib/interfaces/table"; + import { addEdge, applyEdgeChanges, applyNodeChanges, Background, Controls, MiniMap, ReactFlow, useEdgesState, useNodesState, useReactFlow } from "@xyflow/react"; -import { useCallback, useMemo } from "react"; +import { useCallback, useEffect, useMemo } from "react"; import Table from "./table/table"; - import '@xyflow/react/dist/style.css'; import { Relationship } from "./table/relationship"; -import DBController from "./db-controller/db-controller"; -import { useQuery } from "@powersync/react"; +import DBController from "./db-controller/db-controller"; +import { Table as TableType} from "@/lib/schemas/table-schema"; + interface DatabaseProps { initialTables?: TableType @@ -173,18 +173,14 @@ const DatabasePage: React.FC = ({ initialTables }) => { const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes); const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges); - const { data , isLoading , error , isFetching } = useQuery('SELECT * FROM data_types ;' ); - - - console.log (data) - + const nodeTypes = useMemo(() => ({ table: Table }), []); const onConnect = useCallback((params: any) => setEdges((eds) => addEdge(params, eds)), [setEdges]); return (
- + = ({id }) => { + +const FieldItem: React.FC = ({ field }) => { + + const [fieldName, setFieldName] = useState(field.name); + + const [popOverOpen, setPopOverOpen] = useState(false); + const { deleteField, editField, data_types } = useDatabase(); + + const [note, setNote] = useState(field.note as string | undefined); const { t } = useTranslation(); - const { attributes, listeners, setNodeRef, transform, transition } = useSortable({ id }); + const { attributes, listeners, setNodeRef, transform } = useSortable({ id: field.id }); const style = { transform: CSS.Transform.toString(transform), - - - }; - + }; + + const removeField = () => { + setPopOverOpen(false); + deleteField(field.id) + } + + const updateFieldNote = () => { + editField({ + id: field.id, + note: note, + } as FieldInsertType) + } + const toggleUnqiue = (value: boolean) => { + editField({ + id: field.id, + unique: value + } as FieldInsertType) + } + + const saveFieldName = () => { + editField({ + id: field.id, + name: fieldName + } as FieldType); + } + const updateFieldType = (key: Key | null) => { + editField({ + id: field.id, + typeId: key + } as FieldType); + } + + + const toggleNullable = (nullable: boolean) => { + editField({ + id: field.id, + nullable: nullable + } as FieldType); + } + + const togglePrimaryKey = (primaryKey: boolean) => { + editField({ + id: field.id, + isPrimary: primaryKey + } as FieldType); + } + return (
+
- +
- {(item) => {item.label}} - + /> +
- - - - - - {t("db_controller.nullable")}? - - - - - - - - {t("db_controller.primary_key")} - - - -
+ N + + + + + + + + + + +
+

+ {t("db_controller.field_setting")} +

+
+
+ + {t("db_controller.unique")} + + + + +
+ +