230 lines
6.7 KiB
TypeScript

"use client";
import { FC, useCallback, useEffect, useState } from "react";
import AnimatedPageContainer from "@/components/AnimatedPageContainer";
import { TypeEditor } from "./type-editor";
import { useObjectStateWrapper } from "@/hooks/useObjectState";
import { useInput } from "../../hooks/useInput";
import { SchemaEditAtom } from "@/recoil/atoms/schema";
import { SchemaViewer } from "./schema-viewer";
import { TemplateEditor } from "./template-editor";
import { Icon } from "@/components/Icon";
import { useParams } from "next/navigation";
import { FieldTypes } from "./fieldtypes";
import { findSchema, saveSchemaDb } from "@/actions/Schemas/index";
import { useToast } from "../toast";
import { useAtom } from "jotai";
import { Schema } from "@/types";
export const SchemaBuilder: FC = () => {
const [schema, setSchema] = useAtom(SchemaEditAtom);
// const resetSchema = useResetRecoilState(SchemaEditAtom);
const { createToast } = useToast();
const { update: updateSchema, bindProperty: bindSchemaProperty } =
useObjectStateWrapper<Schema>(schema, setSchema);
const { schemaId, id: gameSystemId } = useParams<{
schemaId: string;
id: string;
}>();
useEffect(() => {
if (schemaId !== "create" && schemaId !== schema.id)
findSchema(schemaId).then((sc) => {
if (!sc) return;
setSchema(sc);
});
}, [schema.id, schemaId, setSchema]);
useEffect(() => {
if (gameSystemId && !schema.gameSystemId)
setSchema((sc) => ({ ...sc, gameSystemId }));
}, [gameSystemId, schema.gameSystemId, setSchema]);
const {
value: typeName,
bind: bindTypeName,
reset: resetTypeName,
} = useInput("");
const [pageNumber, setPageNumber] = useState(0);
const [lastSaved, _setLastSaved] = useState(schema);
const [selectedType, setSelectedType] = useState("");
const saveType = useCallback(
(name: string, type: TypeType) => {
updateSchema((e) => ({
types: {
...e.types,
[name]: type,
},
}));
resetTypeName();
setPageNumber(0);
setSelectedType("");
},
[resetTypeName, updateSchema],
);
const saveSchema = useCallback(async () => {
createToast({ msg: "Saving Schema", fading: true });
await saveSchemaDb(schema);
}, [createToast, schema]);
const selectTypeForEdit = useCallback((typeKey: string) => {
setSelectedType(typeKey);
setPageNumber(1);
}, []);
const {
value: schemaFieldName,
bind: bindSchemaFieldName,
reset: resetSchemaFieldName,
} = useInput("", { disallowSpaces: true });
const addSchemaField = useCallback(() => {
updateSchema((s) => ({
fields: {
...s.fields,
[schemaFieldName]: FieldTypes.any,
},
}));
resetSchemaFieldName();
}, [resetSchemaFieldName, schemaFieldName, updateSchema]);
const updateSchemaField = useCallback(
(key: string, fieldType: FieldTypes) => {
updateSchema((s) => ({
fields: {
...s.fields,
[key]: fieldType,
},
}));
},
[updateSchema],
);
const deleteType = useCallback(
(key: string) => {
updateSchema((s) => {
const types = { ...s.types };
delete types[key];
return { types };
});
},
[updateSchema],
);
return (
<div className="flex gap-4 p-8">
<div className="panel w-2/3 h-full flex flex-col gap-4">
<div>
<input
type="text"
{...bindSchemaProperty("name")}
placeholder="Schema Name"
/>
</div>
<div>
<p className="subheader mb-2">Add Schema Field</p>
<div className="mb-2">
<input type="text" {...bindSchemaFieldName} />
<button onClick={addSchemaField} disabled={!schemaFieldName}>
Add
</button>
</div>
<ul className="rounded-lg overflow-hidden">
{Object.entries(schema.fields).map(
([schemaFieldKey, schemaField]) => (
<TemplateEditor
key={schemaFieldKey}
templateKey={schemaFieldKey}
fieldType={schemaField as FieldTypes}
update={updateSchemaField}
/>
),
)}
</ul>
</div>
<hr />
<div>
<AnimatedPageContainer currentPage={pageNumber}>
<div>
<p className="subheader mb-2">Add a type</p>
<input type="text" {...bindTypeName} />
<button
className="interactive"
disabled={!typeName}
onClick={() => setPageNumber(1)}
>
Configure
</button>
</div>
<TypeEditor
name={selectedType || typeName}
saveType={saveType}
type={
selectedType
? schema.types[selectedType as keyof typeof schema.types]
: undefined
}
/>
</AnimatedPageContainer>
<ul className="mt-3 w-96">
{Object.keys(schema.types).map((t) => (
<li
key={"type" + t}
className="odd:bg-black/50 flex justify-between p-2"
>
{t}
<div className="flex gap-3">
<button
title="Edit"
className="no-default"
onClick={() => selectTypeForEdit(t)}
>
<Icon
icon="Anvil"
className="anvil svg-olive-drab hover:svg-olive-drab-100 w-6 h-6"
/>
</button>
<button
title="Delete"
className="no-default"
onClick={() => deleteType(t)}
>
<Icon
icon="Trash"
className="trash-can svg-red-700 hover:svg-red-500 w-6 h-6"
/>
</button>
</div>
</li>
))}
</ul>
</div>
</div>
<div className="panel basis-1/3">
<div className="flex gap-2 mb-2">
<button
className="btn btn-small bg-green-800"
onClick={saveSchema}
disabled={lastSaved === schema}
>
Save Schema
</button>
<button
className="bg-red-800 btn btn-small"
onClick={() => setSchema(lastSaved)}
disabled={lastSaved === schema}
>
Discard Changes
</button>
</div>
<SchemaViewer schema={schema} onTypeClick={selectTypeForEdit} />
</div>
</div>
);
};