diff --git a/components/Button.tsx b/components/Button.tsx index 4de581a..4ed6888 100644 --- a/components/Button.tsx +++ b/components/Button.tsx @@ -39,7 +39,7 @@ const Button = ( {...rest} disabled={disabled} className={classNames( - "font-medium rounded-md py-2 px-4 text-sm relative disabled:opacity-50", + "select-none font-medium rounded-md py-2 px-4 text-sm relative disabled:opacity-50", "focus:outline-none focus-visible:ring-1", { primary: classNames( diff --git a/components/Dropdown.tsx b/components/Dropdown.tsx index 4005443..e0f2c01 100644 --- a/components/Dropdown.tsx +++ b/components/Dropdown.tsx @@ -162,7 +162,7 @@ function renderItems( } return ( - + {renderItem(subItems, false, renderLink)} ); diff --git a/components/Models.tsx b/components/Models.tsx index e8f21a1..598806f 100644 --- a/components/Models.tsx +++ b/components/Models.tsx @@ -38,6 +38,12 @@ import { PRISMA_DATABASES } from "../lib/prisma"; import { useEffect, useState } from "react"; import { useRouter } from "next/dist/client/router"; import { useSchemaContext } from "../lib/context"; +import { + DragDropContext, + Draggable, + Droppable, + DropResult, +} from "react-beautiful-dnd"; export default function Models() { const { schema, schemas, setSchema, setSchemas } = useSchemaContext(); @@ -76,7 +82,9 @@ export default function Models() { }; const handleCreateModel = () => { - if (schema.models.some((model: Model) => model.name === "New")) { + const newModelName = "New"; + + if (schema.models.some((model: Model) => model.name === newModelName)) { toast.error("A model called New exists"); } else { const newSchema = { @@ -84,14 +92,14 @@ export default function Models() { models: [ ...schema.models, { - name: "New", + name: newModelName, fields: [ID_FIELD], enums: [], }, ], }; setSchema(newSchema); - push(`/schemas/${schema.name}/models/${newSchema.models.length - 1}`); + push(`/schemas/${schema.name}/models/${newModelName}`); } }; @@ -204,6 +212,24 @@ export default function Models() { if (!schema) return null; + function handleDragModelsEnd(result: DropResult) { + const newModels = [...schema.models]; + + if (result.destination) { + newModels.splice(result.source.index, 1); + + newModels.splice( + result.destination.index, + 0, + schema.models[result.source.index] + ); + + updateSchema({ + models: newModels, + }); + } + } + return ( <> {schema.models.length ? ( - + + + {({ droppableProps, innerRef, placeholder }) => ( +
    + {schema.models.map((model, i) => { + const isActive = query.id === model.name; + + return ( +
  • + + {( + { draggableProps, innerRef, dragHandleProps }, + { isDragging } + ) => ( +
    + + {model.name} + +
    + )} +
    +
  • + ); + })} + + {placeholder} +
+ )} +
+
) : null} diff --git a/components/Schemas.tsx b/components/Schemas.tsx index a70d187..dc5c645 100644 --- a/components/Schemas.tsx +++ b/components/Schemas.tsx @@ -120,8 +120,8 @@ export default function Schemas() {
- -

My schemas

+ +

My schemas

    diff --git a/components/SidebarItem.tsx b/components/SidebarItem.tsx index bdadad8..419269b 100644 --- a/components/SidebarItem.tsx +++ b/components/SidebarItem.tsx @@ -6,6 +6,7 @@ import { classNames } from "react-cmdk"; interface SidebarItemProps { children: ReactNode; onClick?: () => void; + isDragging?: boolean; isActive?: boolean; target?: string; icon: HeroIcon; @@ -14,6 +15,7 @@ interface SidebarItemProps { } export default function SidebarItem({ + isDragging, icon: Icon, children, isActive, @@ -26,9 +28,14 @@ export default function SidebarItem({ {...rest} href={href} className={classNames( - "hover:bg-gray-100 px-2 py-1.5 -mx-2 rounded-md flex items-center gap-2.5 group", - "focus:outline-none focus-visible:ring-1 focus-visible:ring-black focus-visible:bg-gray-100", - "dark:hover:bg-neutral-800 dark:focus-visible:ring-white dark:focus-visible:bg-neutral-800" + "select-none px-2 py-1.5 -mx-2 rounded-md flex items-center gap-2.5 group", + isDragging + ? "bg-gray-100 dark:bg-neutral-800" + : classNames( + "hover:bg-gray-100", + "focus:outline-none focus-visible:ring-1 focus-visible:ring-black focus-visible:bg-gray-100", + "dark:hover:bg-neutral-800 dark:focus-visible:ring-white dark:focus-visible:bg-neutral-800" + ) )} onClick={ onClick diff --git a/pages/schemas/[schemaId]/models/[id]/index.tsx b/pages/schemas/[schemaId]/models/[id]/index.tsx index 1f0ae2d..3f4d899 100644 --- a/pages/schemas/[schemaId]/models/[id]/index.tsx +++ b/pages/schemas/[schemaId]/models/[id]/index.tsx @@ -16,32 +16,38 @@ import { Field, FieldType, Model } from "../../../../../lib/types"; import { TYPES } from "../../../../../lib/fields"; import { classNames } from "react-cmdk"; import { prismaTypesToIcons } from "../../../../../lib/icons"; -import { useEffect, useState } from "react"; +import { useState } from "react"; import { useRouter } from "next/dist/client/router"; import { useSchemaContext } from "../../../../../lib/context"; const Model = () => { const { schema, setSchema } = useSchemaContext(); - const { query, asPath, push } = useRouter(); + const { query, push } = useRouter(); const { id } = query; - const model = schema.models?.[Number(id)]; + const model = schema.models?.find((m) => m.name === id); const [addingField, setAddingField] = useState(); const [editingField, setEditingField] = useState(); - const [name, setName] = useState(""); + + if (!schema) return null; const handleChangeName = (newName: string) => { + if (!model) return; + if (newName && newName !== model.name) { if (schema.models.some((m: Model) => m.name === newName)) { toast.error(`A model called ${newName} already exists`); } else { updateModel({ name: newName }); + push(`/schemas/${schema.name}/models/${newName}`); } } }; const updateModel = (values: any) => { + if (!model) return; + setSchema({ ...schema, models: schema.models.map((m: Model) => @@ -55,17 +61,9 @@ const Model = () => { }); }; - useEffect(() => { - if (model?.name) { - setName(model.name); - } - }, [model]); - - if (!schema) return null; - return ( <> - {addingField && ( + {addingField && model && ( setAddingField(undefined)} @@ -80,7 +78,7 @@ const Model = () => { /> )} - {editingField && ( + {editingField && model && ( { setEditingField(undefined); @@ -103,7 +101,7 @@ const Model = () => { - {model && ( + {model ? ( { }} contentEditable > - {name} + {model.name} { ], }); push( - `/schemas/${schema.name}/models/${schema.models.length}` + `/schemas/${schema.name}/models/${duplicatedModelName}` ); }, }, @@ -359,7 +357,7 @@ const Model = () => {
    -

    Add field

    +

    Add field

    {[ @@ -416,6 +414,14 @@ const Model = () => {
    + ) : ( + + +

    + Model not found +

    +
    +
    )} ); diff --git a/styles/globals.css b/styles/globals.css index 5a4bb12..97452f1 100644 --- a/styles/globals.css +++ b/styles/globals.css @@ -10,11 +10,11 @@ @layer components { .label { - @apply text-gray-500 dark:text-neutral-500 text-sm mb-2 inline-block; + @apply select-none text-gray-500 dark:text-neutral-500 text-sm mb-2 inline-block; } .label-flat { - @apply text-gray-500 dark:text-neutral-500 text-sm inline-block leading-none; + @apply select-none text-gray-500 dark:text-neutral-500 text-sm inline-block leading-none; } .input {