Adding UI for threads
This commit is contained in:
@@ -6,6 +6,7 @@ import { SceneEditForm } from "./scene/SceneEditForm";
|
|||||||
import { SecretEditForm } from "./secret/SecretEditForm";
|
import { SecretEditForm } from "./secret/SecretEditForm";
|
||||||
import { SessionEditForm } from "./session/SessionEditForm";
|
import { SessionEditForm } from "./session/SessionEditForm";
|
||||||
import { TreasureEditForm } from "./treasure/TreasureEditForm";
|
import { TreasureEditForm } from "./treasure/TreasureEditForm";
|
||||||
|
import { GenericEditForm } from "./GenericEditForm";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Renders a form for any document type depending on the relationship.
|
* Renders a form for any document type depending on the relationship.
|
||||||
@@ -26,5 +27,15 @@ export const DocumentEditForm = ({ document }: { document: AnyDocument }) => {
|
|||||||
return <SessionEditForm session={document} />;
|
return <SessionEditForm session={document} />;
|
||||||
case "treasure":
|
case "treasure":
|
||||||
return <TreasureEditForm treasure={document} />;
|
return <TreasureEditForm treasure={document} />;
|
||||||
|
case "thread":
|
||||||
|
return (
|
||||||
|
<GenericEditForm
|
||||||
|
doc={document}
|
||||||
|
fields={{
|
||||||
|
text: "multiline",
|
||||||
|
resolved: "checkbox",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -32,6 +32,15 @@ export const DocumentPreview = ({ doc }: { doc: AnyDocument }) => {
|
|||||||
|
|
||||||
const ShowDocument = ({ doc }: { doc: AnyDocument }) => {
|
const ShowDocument = ({ doc }: { doc: AnyDocument }) => {
|
||||||
switch (doc.type) {
|
switch (doc.type) {
|
||||||
|
case "front":
|
||||||
|
return (
|
||||||
|
<BasicPreview
|
||||||
|
id={doc.id}
|
||||||
|
title={doc.data.name}
|
||||||
|
description={doc.data.description}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
case "location":
|
case "location":
|
||||||
return (
|
return (
|
||||||
<BasicPreview
|
<BasicPreview
|
||||||
@@ -68,6 +77,9 @@ const ShowDocument = ({ doc }: { doc: AnyDocument }) => {
|
|||||||
case "scene":
|
case "scene":
|
||||||
return <BasicPreview id={doc.id} description={doc.data.text} />;
|
return <BasicPreview id={doc.id} description={doc.data.text} />;
|
||||||
|
|
||||||
|
case "thread":
|
||||||
|
return <BasicPreview id={doc.id} title={doc.data.text} />;
|
||||||
|
|
||||||
case "treasure":
|
case "treasure":
|
||||||
return <BasicPreview id={doc.id} title={doc.data.text} />;
|
return <BasicPreview id={doc.id} title={doc.data.text} />;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,6 +17,15 @@ export const DocumentRow = ({
|
|||||||
root?: AnyDocument;
|
root?: AnyDocument;
|
||||||
}) => {
|
}) => {
|
||||||
switch (document.type) {
|
switch (document.type) {
|
||||||
|
case "front":
|
||||||
|
return (
|
||||||
|
<BasicRow
|
||||||
|
doc={document}
|
||||||
|
title={document.data.name}
|
||||||
|
description={document.data.description}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
case "location":
|
case "location":
|
||||||
return (
|
return (
|
||||||
<BasicRow
|
<BasicRow
|
||||||
@@ -53,6 +62,9 @@ export const DocumentRow = ({
|
|||||||
case "scene":
|
case "scene":
|
||||||
return <BasicRow doc={document} description={document.data.text} />;
|
return <BasicRow doc={document} description={document.data.text} />;
|
||||||
|
|
||||||
|
case "thread":
|
||||||
|
return <BasicRow doc={document} description={document.data.text} />;
|
||||||
|
|
||||||
case "treasure":
|
case "treasure":
|
||||||
return <TreasureToggleRow treasure={document} root={root} />;
|
return <TreasureToggleRow treasure={document} root={root} />;
|
||||||
}
|
}
|
||||||
|
|||||||
87
src/components/documents/GenericEditForm.tsx
Normal file
87
src/components/documents/GenericEditForm.tsx
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
import { AutoSaveTextarea } from "@/components/AutoSaveTextarea";
|
||||||
|
import { pb } from "@/lib/pocketbase";
|
||||||
|
import type { AnyDocument, Location } from "@/lib/types";
|
||||||
|
import { useDocumentCache } from "@/context/document/hooks";
|
||||||
|
|
||||||
|
export type GenericFieldType = "multiline" | "singleline" | "checkbox";
|
||||||
|
|
||||||
|
export type Props<T extends AnyDocument> = {
|
||||||
|
doc: T;
|
||||||
|
fields: { [K in keyof T["data"]]: GenericFieldType };
|
||||||
|
};
|
||||||
|
|
||||||
|
export const GenericEditForm = <T extends AnyDocument>({
|
||||||
|
doc,
|
||||||
|
fields,
|
||||||
|
}: Props<T>) => {
|
||||||
|
return (
|
||||||
|
<div className="">
|
||||||
|
{
|
||||||
|
// The type checker seems to lose the types when using Object.entries here.
|
||||||
|
Object.entries(fields).map(
|
||||||
|
([fieldName, fieldType]: [string, unknown]) => (
|
||||||
|
<GenericEditFormField
|
||||||
|
key={fieldName}
|
||||||
|
doc={doc}
|
||||||
|
fieldName={fieldName as keyof T["data"]}
|
||||||
|
fieldType={fieldType as GenericFieldType}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const GenericEditFormField = <T extends AnyDocument>({
|
||||||
|
doc,
|
||||||
|
fieldName,
|
||||||
|
fieldType,
|
||||||
|
}: {
|
||||||
|
doc: T;
|
||||||
|
fieldName: keyof T["data"];
|
||||||
|
fieldType: GenericFieldType;
|
||||||
|
}) => {
|
||||||
|
const { dispatch } = useDocumentCache();
|
||||||
|
|
||||||
|
// The type checker really doesn't like indexing into this type implicitly, so we'll store it in a temporary to give it the right hints.
|
||||||
|
const data = doc.data as T["data"];
|
||||||
|
|
||||||
|
async function saveField(value: string) {
|
||||||
|
const updated: T = await pb.collection("documents").update(doc.id, {
|
||||||
|
data: {
|
||||||
|
...doc.data,
|
||||||
|
[fieldName]: value,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
dispatch({ type: "setDocument", doc: updated });
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (fieldType) {
|
||||||
|
case "multiline":
|
||||||
|
return (
|
||||||
|
<AutoSaveTextarea
|
||||||
|
multiline={true}
|
||||||
|
value={data[fieldName] as string}
|
||||||
|
onSave={saveField}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
case "singleline":
|
||||||
|
return (
|
||||||
|
<AutoSaveTextarea
|
||||||
|
multiline={false}
|
||||||
|
value={data[fieldName] as string}
|
||||||
|
onSave={saveField}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
case "checkbox":
|
||||||
|
return (
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
checked={data[fieldName] as boolean}
|
||||||
|
onChange={(e) => saveField(e.target.value)}
|
||||||
|
className="accent-emerald-500 w-5 h-5"
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -20,6 +20,8 @@ export const NewCampaignDocumentForm = ({
|
|||||||
switch (docType) {
|
switch (docType) {
|
||||||
case "session":
|
case "session":
|
||||||
return <NewSessionForm campaignId={campaignId} onCreate={onCreate} />;
|
return <NewSessionForm campaignId={campaignId} onCreate={onCreate} />;
|
||||||
|
case "thread":
|
||||||
|
return <NewThreadForm campaignId={campaignId} onCreate={onCreate} />;
|
||||||
default:
|
default:
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Rendered NewCampaignDocumentForm with unsupported docType: ${docType}`,
|
`Rendered NewCampaignDocumentForm with unsupported docType: ${docType}`,
|
||||||
|
|||||||
Reference in New Issue
Block a user