Relationship and Tables in diagram integrated with the API

This commit is contained in:
KarimTamani
2025-05-13 23:11:48 +01:00
parent 69b49b2b2b
commit 84d086c585
26 changed files with 696 additions and 348 deletions
@@ -0,0 +1,60 @@
export interface CardinalityMarkerProps {
selected?: boolean,
direction?: "start" | "end",
type: "one" | "many"
}
const CardinalityMarker: React.FC<CardinalityMarkerProps> = ({ selected = false, type, direction = "start" }) => {
const id = `${type}_${direction}${selected ? "_selected" : ""}`;
const renderMarker = () => {
if (type == "many") {
if (direction == "start")
return (<path d="M 0 50 L 100 50 M 100 50 L 0 0 M 100 50 L 0 100 " />)
else if (direction == "end")
return (<path d="M 100 50 L 0 50 M 0 50 L 100 0 M 0 50 L 100 100" />)
}
if (type == "one") {
if (direction == "start") {
return (<path d="M 0 50 L 100 50 M 50 50 M 50 50 M 75 25 L 75 75" />)
}
if (direction == "end") {
return (<path d="M 100 50 L 0 50 M 50 50 M 50 50 M 25 25 L 25 75" />)
}
}
}
return (
<>
<marker
id={id}
markerWidth="24"
markerHeight="24"
refX="12"
refY="12"
orient="auto"
markerUnits="userSpaceOnUse"
>
<svg
fill="transparent"
className={selected ? "stroke-primary" : "stroke-slate-300"}
strokeWidth="8"
width="24"
height="24"
viewBox="0 0 100 100">
{
renderMarker()
}
</svg>
</marker>
</>
)
}
export default CardinalityMarker;
+30 -6
View File
@@ -1,22 +1,26 @@
import { colorOptions } from "@/lib/colors";
import { Button, Popover, PopoverContent, PopoverTrigger } from "@heroui/react";
import { Slash } from "lucide-react";
import { useCallback, useState } from "react";
import { Tooltip, TooltipContent, TooltipTrigger } from "../tooltip/tooltip";
import { useTranslation } from "react-i18next";
interface ColorPickerProps {
defaultColor?: string;
onChange?: (color: string) => void
onChange?: (color: string | undefined) => void
}
const ColorPicker: React.FC<ColorPickerProps> = ({ defaultColor, onChange }) => {
const [color , setColor] = useState<string>( defaultColor as string ) ;
const [color, setColor] = useState<string | undefined>(defaultColor as string);
const [isOpen, setIsOpen] = useState(false);
const { t } = useTranslation();
const onSelect = useCallback((color: string) => {
const onSelect = useCallback((color: string | undefined) => {
setIsOpen(false);
onChange && onChange(color) ;
setColor ( color)
onChange && onChange(color);
setColor(color)
}, [onChange])
return (
@@ -26,7 +30,7 @@ const ColorPicker: React.FC<ColorPickerProps> = ({ defaultColor, onChange }) =>
className="size-8 cursor-pointer rounded-md border-2 border-muted transition-shadow hover:shadow-md"
isIconOnly
style={{
backgroundColor : color
backgroundColor: color ? color : undefined
}}
>
@@ -46,6 +50,26 @@ const ColorPicker: React.FC<ColorPickerProps> = ({ defaultColor, onChange }) =>
</div>
))}
<Tooltip>
<TooltipTrigger>
<div
key={undefined}
className="size-8 cursor-pointer rounded-md border-2 border-muted transition-shadow hover:shadow-md"
style={{
backgroundColor: "white"
}}
onClick={() => onSelect(undefined)}
>
<Slash className="size-full text-danger-500" />
</div>
</TooltipTrigger>
<TooltipContent>
{t("color_picker.default_color")}
</TooltipContent>
</Tooltip>
</div>
</PopoverContent>
</Popover>
@@ -6,7 +6,9 @@ import { useTranslation } from "react-i18next";
export const useRelationshipName = (relationship: RelationshipType) => {
const { t } = useTranslation();
const name: string = useMemo(() => {
return `${relationship.sourceTable.name}_${relationship.sourceField.name} - ${relationship.targetTable.name}_${relationship.targetField.name}_fk`
if ( !relationship.sourceTable || !relationship.targetTable || !relationship.sourceField || !relationship.targetField)
return "" ;
return `${relationship.sourceTable?.name}_${relationship.sourceField?.name} - ${relationship.targetTable?.name}_${relationship.targetField?.name}_fk`
}, [relationship, t])
return {
name
+26
View File
@@ -0,0 +1,26 @@
import { RelationshipType } from "@/lib/schemas/relationship-schema";
import { LEFT_PREFIX, TARGET_PREFIX } from "@/pages/database/table/field";
import { Edge, useReactFlow } from "@xyflow/react";
import { useEffect } from "react";
export const useRelationshipToEdge = (relationships: RelationshipType[]): void => {
const { setEdges } = useReactFlow();
useEffect(() => {
const edges = relationships.map((relationship: RelationshipType) => {
return {
id: relationship.id,
source: relationship.sourceTableId,
sourceHandle: LEFT_PREFIX + relationship.sourceFieldId,
target: relationship.targetTableId,
targetHandle: TARGET_PREFIX + relationship.targetFieldId,
data : {
relationship
}
} as Edge
})
setEdges(edges)
}, [relationships])
}
+28
View File
@@ -0,0 +1,28 @@
import { TableType } from "@/lib/schemas/table-schema";
import { Node, useReactFlow } from "@xyflow/react";
import { useEffect } from "react";
export const useTableToNode = (tables: TableType[]): void => {
const { setNodes } = useReactFlow();
useEffect(() => {
const nodes = tables.map((table: TableType) => {
return {
id: table.id,
type: "table",
position: {
x: table.posX,
y: table.posY
},
data: {
table
},
style: {
width: 224
}
} as Node
})
setNodes(nodes)
}, [tables])
}
+5
View File
@@ -12,6 +12,9 @@ export const en = {
close: "Close",
create: "Create"
},
color_picker : {
default_color : "Default color"
} ,
db_controller: {
filter: "Filter",
add_table: "Add Table",
@@ -53,6 +56,8 @@ export const en = {
delete: "Delete",
field_setting: "Field Setting",
table_actions: "Field Actions",
actions : "Actions" ,
field_note: "Field note",
delete_field: "Delete Field",
+19 -2
View File
@@ -1,9 +1,8 @@
export const colorOptions = [
"#fd7f6f", // Warm coral red
"#7eb0d5", // Soft sky blue
"#b2e061", // Fresh lime green
"#bd7ebe", // Soft lavender pink
"#ffb55a", // Vibrant orange
"#ffee65", // Warm yellow
"#beb9db", // Light purple grey
@@ -16,6 +15,24 @@ export const colorOptions = [
];
/*
export const colorOptions = [
'#ff6363', // A brighter red.
'#ff6b8a', // A vibrant pink.
'#c05dcf', // A rich purple.
'#b067e9', // A lighter purple.
'#8a61f5', // A bold indigo.
'#7175fa', // A lighter indigo.
'#8eb7ff', // A sky blue.
'#42e0c0', // A fresh aqua.
'#4dee8a', // A mint green.
'#9ef07a', // A lime green.
'#ffe374', // A warm yellow.
'#ff9f74', // A peachy orange.
];
*/
export const randomColor = () => {
return colorOptions[Math.floor(Math.random() * colorOptions.length)];
};
+2 -1
View File
@@ -1,6 +1,6 @@
import { DrizzleAppSchema, wrapPowerSyncWithDrizzle } from '@powersync/drizzle-driver';
import { data_types } from './data-type-schema';
import { data_types, dataTypeRelations } from './data-type-schema';
import { tables, tablesRelations } from './table-schema';
import { fields, fieldsRelations } from './field-schema';
import { relationshipRelations, relationships } from './relationship-schema';
@@ -15,6 +15,7 @@ export const drizzleSchema = {
tablesRelations ,
fieldsRelations ,
relationshipRelations ,
dataTypeRelations
};
// Infer the PowerSync schema from your Drizzle schema
+7 -1
View File
@@ -1,5 +1,6 @@
import { InferSelectModel } from 'drizzle-orm';
import { InferSelectModel, relations } from 'drizzle-orm';
import { sqliteTable, text } from 'drizzle-orm/sqlite-core';
import { fields } from './field-schema';
export const data_types = sqliteTable('data_types', {
id : text("id") ,
@@ -8,4 +9,9 @@ export const data_types = sqliteTable('data_types', {
export const dataTypeRelations = relations(fields, ({ many }) => ({
fields : many(fields) ,
}));
export interface DataType extends InferSelectModel<typeof data_types> { };
+7 -3
View File
@@ -1,6 +1,6 @@
import { sqliteTable, text, real, integer } from 'drizzle-orm/sqlite-core';
import { tables } from './table-schema';
import { data_types } from './data-type-schema';
import { data_types, DataType } from './data-type-schema';
import { InferInsertModel, InferSelectModel, relations } from 'drizzle-orm';
import { relationships } from './relationship-schema';
@@ -25,8 +25,11 @@ export const fieldsRelations = relations(fields, ({ one }) => ({
table: one(tables, {
fields: [fields.tableId],
references: [tables.id],
}),
type : one(data_types , {
fields : [ fields.typeId] ,
references : [data_types.id]
}) ,
sourceRelations: one(relationships),
targetRelations: one(relationships),
@@ -34,7 +37,8 @@ export const fieldsRelations = relations(fields, ({ one }) => ({
export interface FieldType extends InferSelectModel<typeof fields> {
sequence: number
sequence: number ,
type : DataType
};
+113 -169
View File
@@ -1,209 +1,153 @@
import { addEdge, applyEdgeChanges, applyNodeChanges, Background, Controls, MiniMap, ReactFlow, useEdgesState, useNodesState, useReactFlow } from "@xyflow/react";
import { useCallback, useEffect, useMemo } from "react";
import { addEdge, applyEdgeChanges, applyNodeChanges, Background, Connection, Controls, Edge, EdgeChange, EdgeRemoveChange, MiniMap, Node, NodeChange, NodePositionChange, NodeRemoveChange, NodeSelectionChange, OnEdgesChange, OnNodesChange, ReactFlow, useEdgesState, useNodesState, useReactFlow } from "@xyflow/react";
import { SetStateAction, useCallback, useEffect, useMemo, useState } 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 { Table as TableType} from "@/lib/schemas/table-schema";
import Relationship from "./table/relationship";
import DBController from "./db-controller/db-controller";
import { TableInsertType, TableType } from "@/lib/schemas/table-schema";
import { useDatabase } from "@/providers/database-provider/database-provider";
import { useTableToNode } from "@/hooks/use-table-to-node";
import { useRelationshipToEdge } from "@/hooks/use-relationship-to-edge";
import { RelationshipInsertType, RelationshipType } from "@/lib/schemas/relationship-schema";
import { v4 } from "uuid";
import { LEFT_PREFIX, TARGET_PREFIX } from "./table/field";
import CardinalityMarker from "@/components/cardinality-marker/cardinality-marker";
interface DatabaseProps {
initialTables?: TableType
}
const initialNodes = [
{
id: '1', type: "table", position: { x: 500, y: 500 }, data: {
table: {
id: "1",
name: "users",
fields: [
{
id: 'e5n46eojbyxpg65pb1sesysi2',
name: 'id',
type: {
id: 'integer',
name: 'integer',
},
primaryKey: true,
unique: true,
nullable: false,
createdAt: Date.now(),
},
{
id: '7nbmg2bt6dzltr8rfouxctdlc',
name: 'reservation_id',
type: {
id: 'integer',
name: 'integer',
},
primaryKey: false,
unique: false,
nullable: true,
createdAt: Date.now(),
},
{
id: 'f9kcpflp010xe5ad3rmexla63',
name: 'rating',
type: {
id: 'integer',
name: 'integer',
},
primaryKey: false,
unique: false,
nullable: false,
createdAt: Date.now(),
},
{
id: '3znekob0pqusyvdoq14nhlvgo',
name: 'comment',
type: {
id: 'character_varying',
name: 'character varying',
},
primaryKey: false,
unique: false,
nullable: true,
createdAt: Date.now(),
},
],
}
},
style: { width: 224 }
},
{
id: '2', type: "table", position: { x: 200, y: 500 }, data: {
table: {
id: "1",
name: "products",
fields: [
{
id: 'e5n46eojbyxpg65pb1sesysi2',
name: 'id',
type: {
id: 'integer',
name: 'integer',
},
primaryKey: true,
unique: true,
nullable: false,
createdAt: Date.now(),
},
{
id: '7nbmg2bt6dzltr8rfouxctdlc',
name: 'user_id',
type: {
id: 'integer',
name: 'integer',
},
primaryKey: false,
unique: false,
nullable: true,
createdAt: Date.now(),
},
{
id: 'f9kcpflp010xe5ad3rmexla63',
name: 'rating',
type: {
id: 'integer',
name: 'integer',
},
primaryKey: false,
unique: false,
nullable: false,
createdAt: Date.now(),
},
{
id: 'f9kcpflp010xe5ad3rmexla63',
name: 'rating',
type: {
id: 'integer',
name: 'integer',
},
primaryKey: false,
unique: false,
nullable: false,
createdAt: Date.now(),
},
{
id: 'f9kcpflp010xe5ad3rmexla63',
name: 'rating',
type: {
id: 'integer',
name: 'integer',
},
primaryKey: false,
unique: false,
nullable: false,
createdAt: Date.now(),
},
{
id: '3znekob0pqusyvdoq14nhlvgo',
name: 'comment',
type: {
id: 'character_varying',
name: 'character varying',
},
primaryKey: false,
unique: false,
nullable: true,
createdAt: Date.now(),
},
],
}
},
style: { width: 224 }
},
];
const initialEdges: any[] = [];
const edgeTypes = {
'relationship-edge': Relationship,
};
const DatabasePage: React.FC<DatabaseProps> = ({ initialTables }) => {
const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes);
const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges);
const { tables, relationships, updateTablePositions, deleteMultiTables, deleteMultiRelationships, createRelationship } = useDatabase();
const [nodes, setNodes, onNodesChange] = useNodesState([]);
const [edges, setEdges, onEdgesChange] = useEdgesState([]);
useTableToNode(tables);
useRelationshipToEdge(relationships);
const nodeTypes = useMemo(() => ({ table: Table }), []);
const onConnect = useCallback((params: any) => setEdges((eds) => addEdge(params, eds)), [setEdges]);
const onConnect = useCallback((connection: Connection) => {
createRelationship({
id: v4(),
sourceTableId: connection.source,
targetTableId: connection.target,
sourceFieldId: (connection.sourceHandle as string).split("_").pop(),
targetFieldId: (connection.targetHandle as string).replace(TARGET_PREFIX, "")
} as RelationshipInsertType)
setEdges((eds) => addEdge(connection, eds));
}, [setEdges]);
const handleNodesChanges: OnNodesChange<never> = useCallback((changes: NodeChange<never>[]) => {
const nodePositionChanges: NodePositionChange[] = changes.filter((change: NodeChange) => change.type == "position" && !change.dragging) as NodePositionChange[];
const nodeRemoveChanges: NodeRemoveChange[] = changes.filter((change: NodeChange) => change.type == "remove");
if (nodePositionChanges.length > 0)
updateTablePositions(nodePositionChanges.map((change: NodePositionChange) => ({
id: change.id,
posX: change.position?.x,
posY: change.position?.y
} as TableInsertType)));
if (nodeRemoveChanges.length > 0)
deleteMultiTables(nodeRemoveChanges.map((change: NodeRemoveChange) => change.id));
onNodesChange(changes);
}, [onNodesChange]);
const handleEdgeChanges: OnEdgesChange<any> = useCallback((changes: EdgeChange<any>[]) => {
const edgeRemoveChanges: EdgeRemoveChange[] = changes.filter((change: EdgeChange) => change.type == "remove") as EdgeRemoveChange[];
if (edgeRemoveChanges.length > 0) {
deleteMultiRelationships(edgeRemoveChanges.map((change: EdgeRemoveChange) => change.id))
}
onEdgesChange(changes as EdgeChange<never>[]);
}, [onEdgesChange]);
const selectedTableIds: string[] = useMemo(() => {
return nodes.filter((node: Node) => node.selected).map((node: Node) => node.id)
}, [nodes]);
const selectedRelationshipIds: string[] = useMemo(() => {
return relationships.filter((relationship: RelationshipType) =>
selectedTableIds.includes(relationship.sourceTableId) ||
selectedTableIds.includes(relationship.targetTableId)
).map((relationship: RelationshipType) => relationship.id);
}, [selectedTableIds, relationships]);
useEffect(() => {
setEdges((edges: any) => {
return edges.map((edge: any) => {
const selected: boolean = selectedRelationshipIds.includes(edge.id);
return {
...edge ,
animated : selected ,
} as Edge
})
})
}, [setEdges, selectedRelationshipIds])
return (
<div className="w-full h-screen flex">
<DBController />
<ReactFlow
nodes={nodes}
edges={edges}
fitView
className="w-full h-full"
onNodesChange={onNodesChange}
onEdgesChange={onEdgesChange}
className="w-full h-full nodes-animated"
onNodesChange={handleNodesChanges}
onEdgesChange={handleEdgeChanges}
onConnect={onConnect}
defaultEdgeOptions={{
animated: false,
type: 'relationship-edge',
}}
onlyRenderVisibleElements
panOnDrag={true}
zoomOnScroll={true}
nodeTypes={nodeTypes}
edgeTypes={edgeTypes}
snapGrid={[20, 20]}
>
<Controls />
<Background className="bg-default/10" />
<Background className="bg-default/30" />
</ReactFlow>
<svg style={{ position: 'absolute', width: 0, height: 0 }}>
<defs>
<CardinalityMarker type="one" direction="start" />
<CardinalityMarker type="one" direction="start" selected />
<CardinalityMarker type="one" direction="end" />
<CardinalityMarker type="one" direction="end" selected />
<CardinalityMarker type="many" direction="start" />
<CardinalityMarker type="many" direction="start" selected />
<CardinalityMarker type="many" direction="end" />
<CardinalityMarker type="many" direction="end" selected />
</defs>
</svg>
</div>
)
}
@@ -37,6 +37,9 @@ const RelationshipAccordionBody: React.FC<RelationshipAccordionBodyProps> = ({ r
const removeRelationship = () => {
deleteRelationship(relationship.id);
}
if (!relationship.sourceTable || !relationship.targetTable)
return;
return (
<div className="w-full p-2 space-y-4">
<div className="flex">
@@ -48,12 +51,11 @@ const RelationshipAccordionBody: React.FC<RelationshipAccordionBodyProps> = ({ r
<Tooltip>
<TooltipTrigger>
<span className="truncate text-left text-sm">
{relationship.sourceTable.name}({relationship.sourceField.name})
{relationship.sourceTable?.name}({relationship.sourceField?.name})
</span>
</TooltipTrigger>
<TooltipContent>
{relationship.sourceTable.name}({relationship.sourceField.name})
{relationship.sourceTable?.name}({relationship.sourceField?.name})
</TooltipContent>
</Tooltip>
</div>
@@ -68,11 +70,11 @@ const RelationshipAccordionBody: React.FC<RelationshipAccordionBodyProps> = ({ r
<Tooltip>
<TooltipTrigger>
<span className="truncate text-left text-sm ">
{relationship.targetTable.name}({relationship.targetField.name})
{relationship.targetTable?.name}({relationship.targetField?.name})
</span>
</TooltipTrigger>
<TooltipContent>
{relationship.targetTable.name}({relationship.targetField.name})
{relationship.targetTable?.name}({relationship.targetField?.name})
</TooltipContent>
</Tooltip>
</div>
@@ -1,9 +1,8 @@
import { useRelationshipName } from "@/components/hooks/use-relationship-name";
import { randomColor } from "@/lib/colors";
import { useRelationshipName } from "@/hooks/use-relationship-name";
import { RelationshipInsertType, RelationshipType } from "@/lib/schemas/relationship-schema";
import { useDatabase } from "@/providers/database-provider/database-provider";
import { Button, cn, Input } from "@heroui/react";
import { Check, ChevronRight, EllipsisVertical, Focus, Pencil } from "lucide-react";
import { Button, cn, Input, Listbox, ListboxItem, Popover, PopoverContent, PopoverTrigger } from "@heroui/react";
import { Check, ChevronRight, EllipsisVertical, Focus, Pencil, Trash } from "lucide-react";
import { useState } from "react";
import { useTranslation } from "react-i18next";
@@ -19,8 +18,9 @@ const RelationshipAccordionHeader: React.FC<RelationshipAccordionHeaderProps> =
const [editMode, setEditMode] = useState<boolean>(false);
const { name: defaultName } = useRelationshipName(relationship);
const { editRelationship } = useDatabase();
const { editRelationship, deleteRelationship } = useDatabase();
const { t } = useTranslation();
const [popOverOpen, setPopOverOpen] = useState<boolean>(false);
const [name, setName] = useState<string>(relationship.name ? relationship.name : defaultName)
@@ -33,6 +33,11 @@ const RelationshipAccordionHeader: React.FC<RelationshipAccordionHeaderProps> =
setEditMode(false);
}
const onDeleteRelationship = () => {
deleteRelationship(relationship.id);
setPopOverOpen(false);
}
return (
<div className="group w-full flex h-12 gap-1 flex p-2 items-center" >
<div className={cn(
@@ -92,13 +97,40 @@ const RelationshipAccordionHeader: React.FC<RelationshipAccordionHeaderProps> =
<Pencil className="size-4 text-icon" />
</Button>
</div>
<Button
size="sm"
isIconOnly
variant="light"
>
<EllipsisVertical className="size-4 text-slate-500" />
</Button>
<Popover placement="bottom" radius="sm" shadow="sm" showArrow isOpen={popOverOpen} onOpenChange={setPopOverOpen}>
<PopoverTrigger>
<Button
size="sm"
isIconOnly
variant="light"
>
<EllipsisVertical className="size-4 text-slate-500" />
</Button>
</PopoverTrigger>
<PopoverContent className="w-[160px]" >
<div className="w-full flex flex-col gap-2 ">
<h3 className="font-semibold text-sm text-gray p-2">
{t("db_controller.actions")}
</h3>
</div>
<hr className="text-default-200" />
<Listbox aria-label="Actions" className="p-0 pb-1" >
<ListboxItem
key="delete"
className="text-danger"
color="danger"
onPressEnd={onDeleteRelationship}
endContent={<Trash className="size-4" />}
>
{t("db_controller.delete")}
</ListboxItem>
</Listbox>
</PopoverContent>
</Popover>
</>
}
</div>
@@ -6,7 +6,7 @@ import { EllipsisVertical, GripVertical, KeyRound, Trash2 } from "lucide-react"
import { useTranslation } from "react-i18next";
import { CSS } from "@dnd-kit/utilities";
import { FieldInsertType, FieldType } from "@/lib/schemas/field-schema";
import { Key , useState } from "react";
import { Key , useEffect, useState } from "react";
import { useDatabase } from "@/providers/database-provider/database-provider";
import Autocomplete from "@/components/auto-complete/auto-complete";
import ToggleButton from "@/components/toggle/toggle";
@@ -32,6 +32,10 @@ const FieldItem: React.FC<Props> = ({ field }) => {
transform: CSS.Transform.toString(transform),
};
useEffect(() => {
setFieldName(field.name) ;
} , [field.name])
const removeField = () => {
setPopOverOpen(false);
deleteField(field.id)
@@ -24,8 +24,9 @@ const TableAccordionBody: React.FC<TableAccordionBodyProps> = ({ table }) => {
const { t } = useTranslation();
const { editTable, createField } = useDatabase();
const onColorChange = useCallback((color: string) => {
editTable({ id: table.id, color } as TableType);
const onColorChange = useCallback((color: string | undefined) => {
console.log(color)
editTable({ id: table.id, color: color ? color : null } as TableType);
}, [table]);
const addField = (event: any) => {
@@ -42,8 +43,8 @@ const TableAccordionBody: React.FC<TableAccordionBodyProps> = ({ table }) => {
const saveNote = () => {
editTable({
id : table.id ,
note
id: table.id,
note
} as TableType)
}
@@ -1,8 +1,8 @@
import { Tooltip, TooltipTrigger, TooltipContent } from "@/components/tooltip/tooltip";
import { Button, cn, Divider, Dropdown, DropdownItem, DropdownMenu, DropdownTrigger, Input, Listbox, ListboxItem, Popover, PopoverContent, PopoverTrigger, useDisclosure } from "@heroui/react";
import { Button, cn, Input, Listbox, ListboxItem, Popover, PopoverContent, PopoverTrigger, useDisclosure } from "@heroui/react";
import { Check, ChevronRight, Copy, EllipsisVertical, FileKey, FileType, Focus, Pencil, Trash } from "lucide-react";
import { useState } from "react";
import { useEffect, useMemo, useState } from "react";
import { useTranslation } from "react-i18next";
import { TableType } from "@/lib/schemas/table-schema";
@@ -19,15 +19,16 @@ export interface TableAccordionHeaderProps {
const TableAccordionHeader: React.FC<TableAccordionHeaderProps> = ({ table, isOpen }) => {
const { editTable, deleteTable, createField } = useDatabase();
const [popOverOpen, setPopOverOpen] = useState<boolean>(false);
const style = {
borderLeft: "6px solid " + table.color,
};
const [tableName, setTableName] = useState<string>(table.name);
const { t } = useTranslation();
const [editMode, setEditMode] = useState<boolean>(false);
useEffect(()=> {
setTableName(table.name) ;
} , [table.name])
const saveTableName = async () => {
await editTable({ id: table.id, name: tableName });
setEditMode(false);
@@ -50,11 +51,12 @@ const TableAccordionHeader: React.FC<TableAccordionHeaderProps> = ({ table, isOp
nullable: true,
})
}
return (
<div className="group w-full flex h-12 gap-1 border-l-4 flex p-2 items-center"
style={style}
<div className="group w-full flex h-12 gap-1 border-l-4 flex p-2 items-center border-l-[6px] "
style={{
borderColor: table.color as string | undefined
}}
>
<div className={cn(
'tarnsition-all duration-200',
@@ -94,7 +96,7 @@ const TableAccordionHeader: React.FC<TableAccordionHeaderProps> = ({ table, isOp
variant="bordered"
onBlur={saveTableName}
type="text"
className="rounded-md px-2 py-0.5 w-full border-blue-400 focus-visible:ring-0 dark:bg-slate-900 text-sm "
className="rounded-md px-2 py-0.5 w-full border-primary-700 focus-visible:ring-0 dark:bg-slate-900 text-sm "
/>
<Button
variant="light"
@@ -28,7 +28,6 @@ const TablesController: React.FC<Props> = ({ }) => {
await createTable({
id: newTableId,
name: `table_${tables.length + 1}`,
color: randomColor(),
createdAt: new Date().toISOString()
});
+74 -24
View File
@@ -1,54 +1,105 @@
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/tooltip/tooltip";
import { Field as FieldType } from "@/lib/interfaces/field";
import { FieldType } from "@/lib/schemas/field-schema";
import { useDatabase } from "@/providers/database-provider/database-provider";
import { Button, cn, select } from "@heroui/react";
import { Handle, Position, useConnection } from "@xyflow/react";
import { Check } from "lucide-react";
import React, { useState } from "react";
import { Check, KeyRound, Trash, Trash2 } from "lucide-react";
import React, { useEffect, useState } from "react";
interface Props {
field: FieldType,
showHandles?: boolean
showHandles?: boolean,
highlight?: boolean
}
export const LEFT_PREFIX = "left_";
export const RIGHT_PREFIX = "right_";
export const TARGET_PREFIX = "target_";
const Field: React.FC<Props> = ({ field, showHandles }) => {
const Field: React.FC<Props> = ({ field, showHandles, highlight }) => {
const [editMode, setEditMode] = useState<boolean>(false);
const { deleteField, editField } = useDatabase();
const [fieldName, setFieldName] = useState<string>(field.name);
useEffect(() => {
setFieldName(field.name);
}, [field.name]);
const removeField = () => {
deleteField(field.id)
}
const saveFieldName = () => {
editField({
id: field.id,
name: fieldName
} as FieldType);
setEditMode(false);
}
const connection = useConnection();
console.log(highlight)
return (
<div className="group relative flex h-8 items-center justify-between gap-1 border-t px-3 text-sm last:rounded-b-[6px] hover:bg-slate-100 dark:hover:bg-slate-800 transition-all duration-200 ease-in-out ">
<div className={cn(
"group relative flex h-8 items-center justify-between gap-1 border-t border-default px-3 text-sm last:rounded-b-[6px] hover:bg-slate-100 dark:hover:bg-slate-800 transition-all duration-200 ease-in-out" ,
highlight ? "bg-primary/5" : ""
)}>
{
!editMode &&
<>
<label
className=" truncate text-sm "
className={cn(
"truncate text-xs text-slate-900",
field.isPrimary ? "font-semibold" : ""
)}
onDoubleClick={() => setEditMode(true)}
>
{field.name}
</label>
<span className="content-center truncate text-right text-xs text-muted-foreground text-default-600" >
{field.type.name.split(' ')[0]}
<span className={cn("content-center truncate flex items-center h-full gap-1 text-right text-xs text-default-600 group-hover:hidden font-semibold text-icon",
field.isPrimary ? "font-semibold text-slate-700" : ""
)}>
{field.isPrimary && <KeyRound className="size-3" />} {field.type?.name?.split(' ')[0]}{field.nullable ? "?" : ""}
</span>
<Button
radius="none"
isIconOnly
variant="faded"
size="sm"
color="danger"
className=" bg-transparent border-none hidden group-hover:flex "
onPressEnd={removeField}
>
<Trash2 className="size-3" />
</Button>
</>
}
{
editMode &&
<>
<input
// ref={inputRef}
onBlur={() => setEditMode(false)}
onBlur={saveFieldName}
placeholder={field.name}
autoFocus
type="text"
// value={fieldName}
value={fieldName}
onClick={(e) => e.stopPropagation()}
// onChange={(e) => setFieldName(e.target.value)}
className="rounded-md px-2 py-0.5 w-full border-[0.5px] border-blue-400 bg-slate-100 focus-visible:ring-0 dark:bg-slate-900 text-sm "
onChange={(e) => setFieldName(e.target.value)}
className="rounded-md outline-none px-2 py-0.5 w-full border-[0.5px] border-primary-700 bg-slate-100 focus-visible:ring-0 dark:bg-slate-900 text-sm "
/>
<Button
@@ -56,12 +107,13 @@ const Field: React.FC<Props> = ({ field, showHandles }) => {
className="size-6 p-0 text-slate-500 hover:bg-primary-foreground hover:text-slate-700 dark:text-slate-400 dark:hover:bg-slate-800 dark:hover:text-slate-200"
size="sm"
isIconOnly
onPress={() => setEditMode(false)}
onPress={saveFieldName}
>
<Check className="size-4" />
<Check className="size-3" />
</Button>
</>
}
<div className={
cn(
"absolute w-full left-0 ",
@@ -70,16 +122,14 @@ const Field: React.FC<Props> = ({ field, showHandles }) => {
<Handle
type="source"
position={Position.Left}
id={"left-"+ field.id}
className="w-4 h-4 border-3 bg-primary"
id={LEFT_PREFIX + field.id}
className="w-4 h-4 border-4 bg-primary"
/>
<Handle
type="source"
position={Position.Right}
className="w-4 h-4 border-3 bg-primary"
id={"right-"+ field.id}
className="w-4 h-4 border-4 bg-primary"
id={RIGHT_PREFIX + field.id}
/>
</div>
{
@@ -89,7 +139,7 @@ const Field: React.FC<Props> = ({ field, showHandles }) => {
!connection.inProgress ? "invisible" : "visible"
)} >
<Handle
id={`${"target"}_${field.id}`}
id={`${TARGET_PREFIX}${field.id}`}
className={
true
? '!absolute !left-0 !top-0 !h-full !w-full !transform-none !rounded-none !border-none !opacity-0'
+64 -34
View File
@@ -1,7 +1,9 @@
import { Relationship as RelationshipType } from "@/lib/interfaces/relationship";
import { cn } from "@heroui/react";
import { Edge, EdgeProps, getSmoothStepPath, Position, useReactFlow } from "@xyflow/react";
import { useMemo } from "react";
import { Cardinality, RelationshipType } from "@/lib/schemas/relationship-schema";
import { card, cn } from "@heroui/react";
import { Edge, EdgeProps, getBezierPath, getSmoothStepPath, InternalNode, Node, Position, useReactFlow } from "@xyflow/react";
import React, { useMemo } from "react";
@@ -11,40 +13,31 @@ export type RelationshipProps = Edge<{
selected?: boolean
}, 'relationship-edge'>
export const Relationship: React.FC<EdgeProps<RelationshipProps>> = ({
id,
sourceX,
sourceY,
targetX,
targetY,
source,
target,
selected,
data,
}) => {
const Relationship: React.FC<EdgeProps<RelationshipProps>> = (props) => {
let { id, sourceX, sourceY, targetX, targetY, source, target, selected, data , animated } = props;
const { getInternalNode, getEdge } = useReactFlow();
//const { openRelationshipFromSidebar, selectSidebarSection } = useLayout();
//const { checkIfRelationshipRemoved, checkIfNewRelationship } = useDiff();
const sourceNode = getInternalNode(source);
const targetNode = getInternalNode(target);
const edge = getEdge(id);
const sourceHandle: 'left' | 'right' = edge?.sourceHandle?.startsWith?.(
"right-"
"right_"
)
? 'right'
: 'left';
const sourceWidth = sourceNode?.measured.width ?? 0;
const sourceLeftX =
sourceHandle === 'left' ? sourceX + 3 : sourceX - sourceWidth - 10;
const sourceRightX =
sourceHandle === 'left' ? sourceX + sourceWidth + 9 : sourceX;
const sourceLeftX = sourceHandle === 'left' ? sourceX + 6 : sourceX - sourceWidth - 10;
const sourceRightX = sourceHandle === 'left' ? sourceX + sourceWidth + 10 : sourceX;
const targetWidth = targetNode?.measured.width ?? 0;
const targetLeftX = targetX - 1;
const targetRightX = targetX + targetWidth + 10;
const targetLeftX = targetX - 2;
const targetRightX = targetX + targetWidth + 3;
const { sourceSide, targetSide } = useMemo(() => {
const distances = {
@@ -84,12 +77,12 @@ export const Relationship: React.FC<EdgeProps<RelationshipProps>> = ({
sourceY,
targetX: targetSide === 'left' ? targetLeftX : targetRightX,
targetY,
borderRadius: 14,
borderRadius: 6,
sourcePosition:
sourceSide === 'left' ? Position.Left : Position.Right,
targetPosition:
targetSide === 'left' ? Position.Left : Position.Right,
// offset: (edgeNumber + 1) * 14,
}),
[
sourceSide,
@@ -100,20 +93,37 @@ export const Relationship: React.FC<EdgeProps<RelationshipProps>> = ({
targetRightX,
sourceY,
targetY,
// edgeNumber,
]
);
const { startMarker, endMarker } = useMemo(() => {
const cardinality: Cardinality = data?.relationship.cardinality as Cardinality;
if (cardinality) {
const cardinalities: string[] = cardinality.split("_to_");
return {
startMarker: `${cardinalities[0]}_${"start"}${selected ? "_selected" : ""}`,
endMarker: `${cardinalities[1]}_${"end"}${selected ? "_selected" : ""}`
}
}
return {
startMarker: `${"one"}_${"start"}${selected ? "_selected" : ""}`,
endMarker: `${"many"}_${"end"}${selected ? "_selected" : ""}`
}
}, [data?.relationship.cardinality, selected]);
return (
<>
<path
id={id}
d={edgePath}
//markerStart={`url(#${sourceMarker})`}
//markerEnd={`url(#${targetMarker})`}
markerStart={`url(#${startMarker})`}
markerEnd={`url(#${endMarker})`}
fill="none"
className={cn([
'react-flow__edge-path',
`!stroke-2 ${selected ? '!stroke-pink-600' : '!stroke-slate-400'}`,
`!stroke-2 ${selected ? '!stroke-primary' : 'stroke-slate-300'}`,
])}
onClick={(e) => {
@@ -122,13 +132,18 @@ export const Relationship: React.FC<EdgeProps<RelationshipProps>> = ({
// openRelationshipInEditor();
}
}}
style={{
strokeDasharray: (selected || animated) ? '5, 5' : '0',
animation: (selected || animated) ? 'dash 0.5s linear infinite' : 'none',
}}
/>
<path
d={edgePath}
fill="none"
strokeOpacity={0}
strokeWidth={20}
// eslint-disable-next-line tailwindcss/no-custom-classname
strokeWidth={16}
className="react-flow__edge-interaction"
onClick={(e) => {
if (e.detail === 2) {
@@ -136,5 +151,20 @@ export const Relationship: React.FC<EdgeProps<RelationshipProps>> = ({
}
}}
/>
</>)
}
}
export default Relationship;
/*
*/
+75 -38
View File
@@ -1,8 +1,8 @@
import { Table as TableType } from "@/lib/interfaces/table";
import { Handle, Node, NodeProps, NodeResizer, Position } from "@xyflow/react";
import React, { useCallback, useState } from 'react';
import { Edge, Handle, Node, NodeProps, NodeResizer, Position, useReactFlow, useStore } from "@xyflow/react";
import React, { useCallback, useEffect, useMemo, useState } from 'react';
import { Button, Card, CardBody, CardHeader, cn, Divider, Input } from "@heroui/react";
import {
ChevronsLeftRight,
@@ -16,10 +16,16 @@ import {
SquarePlus,
SquareMinus,
Divide,
Focus,
} from 'lucide-react';
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/tooltip/tooltip";
import { Field } from "@/lib/interfaces/field";
import FieldComponent from "./field";
import { FieldType } from "@/lib/schemas/field-schema";
import { TableType } from "@/lib/schemas/table-schema";
import { useDatabase } from "@/providers/database-provider/database-provider";
import { useTranslation } from "react-i18next";
import { RelationshipType } from "@/lib/schemas/relationship-schema";
export const MAX_TABLE_SIZE = 450;
@@ -38,40 +44,64 @@ const Table: React.FC<NodeProps<TableProps>> = React.memo(({
id,
data: { table, },
}) => {
const [editMode, setEditMode] = useState<boolean>(false);
const [editMode, setEditMode] = useState<boolean>(false);
const [tableName, setTableName] = useState<string>(table.name);
const { editTable } = useDatabase();
const { t } = useTranslation();
useEffect(() => {
setTableName(table.name);
}, [table.name])
const saveTableName = async () => {
await editTable({ id: table.id, name: tableName });
setEditMode(false);
}
const edges = useStore((store) => Array.from(store.edges.values())) as Edge[];
const highlightedEdges: Edge[] = useMemo(() => {
return edges.filter((edge: Edge) => edge.animated || edge.selected);
}, [edges]);
console.log(highlightedEdges)
return (
<div className={cn(
"w-full h-full bg-background rounded-lg entity-card border-2 noselect ",
<Card className={cn(
"w-full h-full bg-background rounded-lg entity-card noselect overflow-visible ",
selected
? ' border-2 border-primary-500'
? 'ring-1 ring-primary'
: '',
)}>
<div
className="h-2 rounded-t-[6px] "
style={{ backgroundColor: "rgb(255, 107, 138)" }}
></div>
<div className="group gap-2 flex h-9 items-center justify-between bg-default px-2 dark:bg-default">
<Table2 className="size-3.5 shrink-0 text-gray-600 dark:text-primary" />
)}
shadow="sm"
>
<div className="px-[2px] ">
<div
className=" border-t-[4px] rounded-t-[6px] border-primary"
style={{ borderColor: table.color as string }}
></div>
</div>
<div className="group gap-2 flex h-9 items-center justify-between bg-default/50 px-2 dark:bg-default ">
<Table2 className="size-4 shrink-0 text-icon dark:text-primary" />
{
editMode && <>
<input
placeholder={table.name}
autoFocus
onBlur={() => setEditMode(false)}
onChange={(event: any) => setTableName(event.target.value)}
value={tableName}
onBlur={saveTableName}
type="text"
className="rounded-md px-2 py-0.5 w-full border-[0.5px] border-blue-400 bg-slate-100 focus-visible:ring-0 dark:bg-slate-900 text-sm "
className="rounded-md outline-none px-2 py-0.5 w-full border-[0.5px] border-primary-700 font-bold bg-slate-100 focus-visible:ring-0 dark:bg-slate-900 text-sm "
/>
<Button
variant="light"
className="size-6 p-0 text-slate-500 hover:bg-primary-foreground hover:text-slate-700 dark:text-slate-400 dark:hover:bg-slate-800 dark:hover:text-slate-200"
size="sm"
onClick={() => setEditMode(false)}
onPressEnd={saveTableName}
isIconOnly
>
<Check className="size-3" />
@@ -91,7 +121,7 @@ const Table: React.FC<NodeProps<TableProps>> = React.memo(({
</label>
</TooltipTrigger>
<TooltipContent>
Double click to edit
{t("table.double_click")}
</TooltipContent>
</Tooltip>
@@ -103,7 +133,7 @@ const Table: React.FC<NodeProps<TableProps>> = React.memo(({
className="size-6 p-0 text-slate-500 hover:bg-primary-foreground hover:text-slate-700 dark:text-slate-400 dark:hover:bg-slate-800 dark:hover:text-slate-200"
isIconOnly
>
<CircleDotDashed className="size-4" />
<Focus className="size-4 text-icon" />
</Button>
<Button
@@ -123,26 +153,33 @@ const Table: React.FC<NodeProps<TableProps>> = React.memo(({
}
</div>
<div>
<div
className="transition-[max-height] duration-200 ease-in-out"
>
{table.fields.map((field: Field) => (
<FieldComponent
key={field.id}
field={field}
showHandles={selected}
/>
))}
</div>
<div
className="transition-[max-height] duration-200 ease-in-out"
>
{table.fields.map((field: FieldType) => {
const highlight: boolean = highlightedEdges.find((edge: any) =>
(edge.data?.relationship as RelationshipType).sourceFieldId == field.id ||
(edge.data?.relationship as RelationshipType).targetFieldId == field.id) != null;
return (<FieldComponent
key={field.id}
field={field}
showHandles={selected}
highlight={highlight}
/>)
})}
</div>
</div>
</Card>
)
});
Table.displayName = 'table';
export default Table;
@@ -11,21 +11,25 @@ import { createContext } from "react";
export interface DatabaseContextType {
tables: TableType[],
data_types: DataType[] ,
relationships : RelationshipType[] ,
data_types: DataType[],
relationships: RelationshipType[],
// table operations
createTable: (table: TableInsertType) => Promise<QueryResult>,
editTable: (table: TableInsertType) => Promise<QueryResult>,
deleteTable: (id: string) => Promise<QueryResult>,
updateTablePositions: (tables: TableInsertType[]) => Promise<QueryResult> ,
deleteMultiTables: (ids: string[]) => Promise<QueryResult>
// field operations
createField: (field: FieldInsertType) => Promise<QueryResult>,
editField: (field: FieldInsertType) => Promise<QueryResult>,
deleteField: (id: string) => Promise<QueryResult>,
orderTableFields: (fields: FieldType[]) => Promise<QueryResult> ,
orderTableFields: (fields: FieldType[]) => Promise<QueryResult>,
// relationship operations
createRelationship : ( relationship : RelationshipInsertType) => Promise<QueryResult> ,
editRelationship : ( relationship : RelationshipInsertType) => Promise<QueryResult> ,
deleteRelationship : ( id : string) => Promise<QueryResult> ,
createRelationship: (relationship: RelationshipInsertType) => Promise<QueryResult>,
editRelationship: (relationship: RelationshipInsertType) => Promise<QueryResult>,
deleteRelationship: (id: string) => Promise<QueryResult>,
deleteMultiRelationships: (ids: string[]) => Promise<QueryResult>
}
@@ -5,7 +5,7 @@ import { db, powerSyncDb } from "../sync-provider/sync-provider";
import { TableInsertType, tables, TableType } from "@/lib/schemas/table-schema";
import { useQuery } from "@powersync/react";
import { toCompilableQuery } from "@powersync/drizzle-driver";
import { asc, desc, eq, sql } from "drizzle-orm";
import { asc, desc, eq, inArray, sql } from "drizzle-orm";
import { QueryResult } from "@powersync/web";
import { FieldInsertType, fields, FieldType } from "@/lib/schemas/field-schema";
import { RelationshipInsertType, relationships, RelationshipType } from "@/lib/schemas/relationship-schema";
@@ -16,11 +16,14 @@ interface Props { children: React.ReactNode }
const DatabaseProvider: React.FC<Props> = ({ children }) => {
const { data: tablesList, isLoading, error } = useQuery(toCompilableQuery(
const { data: tablesList } = useQuery(toCompilableQuery(
db.query.tables.findMany({
with: {
fields: {
orderBy: asc(fields.sequence),
with: {
type: true
}
}
},
orderBy: desc(tables.createdAt)
@@ -53,6 +56,9 @@ const DatabaseProvider: React.FC<Props> = ({ children }) => {
const deleteTable = async (id: string): Promise<QueryResult> => {
return await db.delete(tables).where(eq(tables.id, id));
}
const deleteMultiTables = async (ids: string[]): Promise<QueryResult> => {
return await db.delete(tables).where(inArray(tables.id, ids ))
}
const createField = async (field: FieldInsertType): Promise<QueryResult> => {
return await db.insert(fields).values(field);
@@ -90,13 +96,44 @@ const DatabaseProvider: React.FC<Props> = ({ children }) => {
return await db.delete(relationships).where(eq(relationships.id, id));
}
const deleteMultiRelationships = async (ids: string[]): Promise<QueryResult> => {
return await db.delete(relationships).where(inArray(relationships.id, ids ))
}
const updateTablePositions = async (tables: TableInsertType[]): Promise<QueryResult> => {
const posXCases = tables
.map((table: TableInsertType) => `WHEN '${table.id}' THEN ${table.posX}`)
.join('\n ');
const posYCases = tables
.map((table: TableInsertType) => `WHEN '${table.id}' THEN ${table.posY}`)
.join('\n ');
const ids = tables.map((table: TableInsertType) => `'${table.id}'`).join(',\n ');
const sql = `
UPDATE tables
SET
posX = CASE id
${posXCases}
END,
posY = CASE id
${posYCases}
END
WHERE id IN (
${ids}
);`;
return await powerSyncDb.execute(sql);
}
return (
<DatabaseContext.Provider value={{
createTable,
editTable,
deleteTable,
updateTablePositions,
deleteMultiTables ,
createField,
editField,
@@ -106,9 +143,10 @@ const DatabaseProvider: React.FC<Props> = ({ children }) => {
createRelationship,
editRelationship,
deleteRelationship,
deleteMultiRelationships ,
tables: tablesList as TableType[],
relationships : relationshipsList as RelationshipType[] ,
relationships: relationshipsList as RelationshipType[],
data_types
}}>
{children}
+6 -3
View File
@@ -5,7 +5,7 @@
.text-editable {
@apply dark:group-hover:bg-slate-900 group-hover:bg-slate-100 group-hover:ring-[0.5px] rounded-md cursor-pointer;
@apply dark:group-hover:bg-slate-900 group-hover:bg-slate-100 group-hover:ring-[0.5px] group-hover:ring-primary-700 rounded-md cursor-pointer;
}
@@ -51,6 +51,9 @@ div[data-slot="content"] hr[role="separator"] {
}
button[data-slot="trogger"] {
background-color: red !important;
@keyframes dash {
to {
stroke-dashoffset: -10;
}
}
+6 -2
View File
@@ -1,5 +1,5 @@
import { FieldType } from "@/lib/schemas/field-schema";
@@ -12,4 +12,8 @@ export const getNextSequence = (fields: FieldType[]): number => {
return field.sequence > max.sequence ? field : max
});
return maxSequenceItem.sequence + 1;
}
}
+20 -20
View File
@@ -19,17 +19,17 @@ export default {
foreground: 'hsl(240 5.3% 26.1%)',
},
primary: {
DEFAULT: "#961FFE",
50: '#961FFE0D', // 5% opacity
100: '#961FFE1A', // 10% opacity
200: '#961FFE33', // 20% opacity
300: '#961FFE4D', // 30% opacity
400: '#961FFE66', // 40% opacity
500: '#961FFE80', // 50% opacity
600: '#961FFE99', // 60% opacity
700: '#961FFEB3', // 70% opacity
DEFAULT: "#9822ff",
50: '#9822ff0D', // 5% opacity
100: '#9822ff1A', // 10% opacity
200: '#9822ff33', // 20% opacity
300: '#9822ff4D', // 30% opacity
400: '#9822ff66', // 40% opacity
500: '#9822ff80', // 50% opacity
600: '#9822ff99', // 60% opacity
700: '#9822ffB3', // 70% opacity
800: '#961FFFCC', // 80% opacity
900: '#961FFEE6', // 90% opacity
900: '#9822ffE6', // 90% opacity
},
},
},
@@ -50,17 +50,17 @@ export default {
} ,
primary: {
DEFAULT: "#961FFE",
50: '#961FFE0D', // 5% opacity
100: '#961FFE1A', // 10% opacity
200: '#961FFE33', // 20% opacity
300: '#961FFE4D', // 30% opacity
400: '#961FFE66', // 40% opacity
500: '#961FFE80', // 50% opacity
600: '#961FFE99', // 60% opacity
700: '#961FFEB3', // 70% opacity
DEFAULT: "#9822ff",
50: '#9822ff0D', // 5% opacity
100: '#9822ff1A', // 10% opacity
200: '#9822ff33', // 20% opacity
300: '#9822ff4D', // 30% opacity
400: '#9822ff66', // 40% opacity
500: '#9822ff80', // 50% opacity
600: '#9822ff99', // 60% opacity
700: '#9822ffB3', // 70% opacity
800: '#961FFFCC', // 80% opacity
900: '#961FFEE6', // 90% opacity
900: '#9822ffE6', // 90% opacity
},
},
},
@@ -0,0 +1,25 @@
// vite.config.ts
import { defineConfig } from "file:///C:/Users/taman/OneDrive/Desktop/stackrender/stackrender/node_modules/vite/dist/node/index.js";
import react from "file:///C:/Users/taman/OneDrive/Desktop/stackrender/stackrender/node_modules/@vitejs/plugin-react/dist/index.mjs";
import tsconfigPaths from "file:///C:/Users/taman/OneDrive/Desktop/stackrender/stackrender/node_modules/vite-tsconfig-paths/dist/index.mjs";
import topLevelAwait from "file:///C:/Users/taman/OneDrive/Desktop/stackrender/stackrender/node_modules/vite-plugin-top-level-await/exports/import.mjs";
var vite_config_default = defineConfig({
plugins: [react(), tsconfigPaths(), topLevelAwait()],
server: {
port: 3e3
},
optimizeDeps: {
// Don't optimize these packages as they contain web workers and WASM files.
// https://github.com/vitejs/vite/issues/11672#issuecomment-1415820673
exclude: ["@journeyapps/wa-sqlite", "@powersync/web"],
include: ["@powersync/web > js-logger"]
},
worker: {
format: "es",
plugins: () => [topLevelAwait()]
}
});
export {
vite_config_default as default
};
//# sourceMappingURL=data:application/json;base64,ewogICJ2ZXJzaW9uIjogMywKICAic291cmNlcyI6IFsidml0ZS5jb25maWcudHMiXSwKICAic291cmNlc0NvbnRlbnQiOiBbImNvbnN0IF9fdml0ZV9pbmplY3RlZF9vcmlnaW5hbF9kaXJuYW1lID0gXCJDOlxcXFxVc2Vyc1xcXFx0YW1hblxcXFxPbmVEcml2ZVxcXFxEZXNrdG9wXFxcXHN0YWNrcmVuZGVyXFxcXHN0YWNrcmVuZGVyXCI7Y29uc3QgX192aXRlX2luamVjdGVkX29yaWdpbmFsX2ZpbGVuYW1lID0gXCJDOlxcXFxVc2Vyc1xcXFx0YW1hblxcXFxPbmVEcml2ZVxcXFxEZXNrdG9wXFxcXHN0YWNrcmVuZGVyXFxcXHN0YWNrcmVuZGVyXFxcXHZpdGUuY29uZmlnLnRzXCI7Y29uc3QgX192aXRlX2luamVjdGVkX29yaWdpbmFsX2ltcG9ydF9tZXRhX3VybCA9IFwiZmlsZTovLy9DOi9Vc2Vycy90YW1hbi9PbmVEcml2ZS9EZXNrdG9wL3N0YWNrcmVuZGVyL3N0YWNrcmVuZGVyL3ZpdGUuY29uZmlnLnRzXCI7aW1wb3J0IHsgZGVmaW5lQ29uZmlnIH0gZnJvbSAndml0ZSdcbmltcG9ydCByZWFjdCBmcm9tICdAdml0ZWpzL3BsdWdpbi1yZWFjdCdcbmltcG9ydCB0c2NvbmZpZ1BhdGhzIGZyb20gJ3ZpdGUtdHNjb25maWctcGF0aHMnXG5pbXBvcnQgdG9wTGV2ZWxBd2FpdCBmcm9tICd2aXRlLXBsdWdpbi10b3AtbGV2ZWwtYXdhaXQnO1xuLy8gaHR0cHM6Ly92aXRlanMuZGV2L2NvbmZpZy9cbmV4cG9ydCBkZWZhdWx0IGRlZmluZUNvbmZpZyh7XG4gIHBsdWdpbnM6IFtyZWFjdCgpLCB0c2NvbmZpZ1BhdGhzKCkgLCB0b3BMZXZlbEF3YWl0KCldLFxuICBzZXJ2ZXIgOiB7IFxuICAgIHBvcnQgOiAzMDAwXG4gIH0gLCBcbiAgb3B0aW1pemVEZXBzOiB7XG4gICAgLy8gRG9uJ3Qgb3B0aW1pemUgdGhlc2UgcGFja2FnZXMgYXMgdGhleSBjb250YWluIHdlYiB3b3JrZXJzIGFuZCBXQVNNIGZpbGVzLlxuICAgIC8vIGh0dHBzOi8vZ2l0aHViLmNvbS92aXRlanMvdml0ZS9pc3N1ZXMvMTE2NzIjaXNzdWVjb21tZW50LTE0MTU4MjA2NzNcbiAgICBleGNsdWRlOiBbJ0Bqb3VybmV5YXBwcy93YS1zcWxpdGUnLCAnQHBvd2Vyc3luYy93ZWInXSxcbiAgICBpbmNsdWRlOiBbJ0Bwb3dlcnN5bmMvd2ViID4ganMtbG9nZ2VyJ11cbiAgfSxcbiAgd29ya2VyOiB7XG4gICAgZm9ybWF0OiAnZXMnLFxuICAgIHBsdWdpbnM6ICgpID0+IFsgdG9wTGV2ZWxBd2FpdCgpXVxuICB9XG59KVxuIl0sCiAgIm1hcHBpbmdzIjogIjtBQUFxVyxTQUFTLG9CQUFvQjtBQUNsWSxPQUFPLFdBQVc7QUFDbEIsT0FBTyxtQkFBbUI7QUFDMUIsT0FBTyxtQkFBbUI7QUFFMUIsSUFBTyxzQkFBUSxhQUFhO0FBQUEsRUFDMUIsU0FBUyxDQUFDLE1BQU0sR0FBRyxjQUFjLEdBQUksY0FBYyxDQUFDO0FBQUEsRUFDcEQsUUFBUztBQUFBLElBQ1AsTUFBTztBQUFBLEVBQ1Q7QUFBQSxFQUNBLGNBQWM7QUFBQTtBQUFBO0FBQUEsSUFHWixTQUFTLENBQUMsMEJBQTBCLGdCQUFnQjtBQUFBLElBQ3BELFNBQVMsQ0FBQyw0QkFBNEI7QUFBQSxFQUN4QztBQUFBLEVBQ0EsUUFBUTtBQUFBLElBQ04sUUFBUTtBQUFBLElBQ1IsU0FBUyxNQUFNLENBQUUsY0FBYyxDQUFDO0FBQUEsRUFDbEM7QUFDRixDQUFDOyIsCiAgIm5hbWVzIjogW10KfQo=