Changes all documents to have an explicit type
This commit is contained in:
46
pb_migrations/1751082417_updated_documents.js
Normal file
46
pb_migrations/1751082417_updated_documents.js
Normal file
@@ -0,0 +1,46 @@
|
||||
/// <reference path="../pb_data/types.d.ts" />
|
||||
migrate((app) => {
|
||||
const collection = app.findCollectionByNameOrId("pbc_3332084752")
|
||||
|
||||
// update collection data
|
||||
unmarshal({
|
||||
"indexes": [
|
||||
"CREATE INDEX `idx_gxNj5R3hxv` ON `documents` (`type`)"
|
||||
]
|
||||
}, collection)
|
||||
|
||||
// add field
|
||||
collection.fields.addAt(3, new Field({
|
||||
"hidden": false,
|
||||
"id": "select2363381545",
|
||||
"maxSelect": 1,
|
||||
"name": "type",
|
||||
"presentable": false,
|
||||
"required": false,
|
||||
"system": false,
|
||||
"type": "select",
|
||||
"values": [
|
||||
"location",
|
||||
"monster",
|
||||
"npc",
|
||||
"scene",
|
||||
"secret",
|
||||
"session",
|
||||
"treasure"
|
||||
]
|
||||
}))
|
||||
|
||||
return app.save(collection)
|
||||
}, (app) => {
|
||||
const collection = app.findCollectionByNameOrId("pbc_3332084752")
|
||||
|
||||
// update collection data
|
||||
unmarshal({
|
||||
"indexes": []
|
||||
}, collection)
|
||||
|
||||
// remove field
|
||||
collection.fields.removeById("select2363381545")
|
||||
|
||||
return app.save(collection)
|
||||
})
|
||||
25
pb_migrations/1751082429_updated_documents.js
Normal file
25
pb_migrations/1751082429_updated_documents.js
Normal file
@@ -0,0 +1,25 @@
|
||||
/// <reference path="../pb_data/types.d.ts" />
|
||||
migrate((app) => {
|
||||
const collection = app.findCollectionByNameOrId("pbc_3332084752")
|
||||
|
||||
// update collection data
|
||||
unmarshal({
|
||||
"indexes": [
|
||||
"CREATE INDEX `idx_gxNj5R3hxv` ON `documents` (`type`)",
|
||||
"CREATE INDEX `idx_KtpMErDe1C` ON `documents` (`campaign`)"
|
||||
]
|
||||
}, collection)
|
||||
|
||||
return app.save(collection)
|
||||
}, (app) => {
|
||||
const collection = app.findCollectionByNameOrId("pbc_3332084752")
|
||||
|
||||
// update collection data
|
||||
unmarshal({
|
||||
"indexes": [
|
||||
"CREATE INDEX `idx_gxNj5R3hxv` ON `documents` (`type`)"
|
||||
]
|
||||
}, collection)
|
||||
|
||||
return app.save(collection)
|
||||
})
|
||||
55
pb_migrations/1751082553_extract_document_types.js
Normal file
55
pb_migrations/1751082553_extract_document_types.js
Normal file
@@ -0,0 +1,55 @@
|
||||
const DocType = [
|
||||
"location",
|
||||
"monster",
|
||||
"npc",
|
||||
"scene",
|
||||
"secret",
|
||||
"session",
|
||||
"treasure",
|
||||
];
|
||||
|
||||
function parseJsonB(data) {
|
||||
if (typeof data === "string") {
|
||||
return JSON.parse(data);
|
||||
} else if (data instanceof Array) {
|
||||
return JSON.parse(String.fromCharCode.apply(String, data));
|
||||
}
|
||||
throw new Error("Unsupported data type for JSON parsing");
|
||||
}
|
||||
|
||||
/// <reference path="../pb_data/types.d.ts" />
|
||||
migrate(
|
||||
(app) => {
|
||||
let documents = app.findAllRecords("documents");
|
||||
console.log("Records to parse: ", documents.length);
|
||||
|
||||
documents: for (const doc of documents) {
|
||||
if (!doc) continue;
|
||||
|
||||
let data = parseJsonB(doc.get("data"));
|
||||
|
||||
if (data[""]) {
|
||||
data = data[""];
|
||||
}
|
||||
for (const t of DocType) {
|
||||
if (data[t]) {
|
||||
doc.set("type", t);
|
||||
doc.set("data", data[t]);
|
||||
app.save(doc);
|
||||
continue documents;
|
||||
}
|
||||
}
|
||||
throw new Error(`Unrecognized data: ${JSON.stringify(data)}`);
|
||||
}
|
||||
},
|
||||
(app) => {
|
||||
// add down queries...
|
||||
let documents = app.findAllRecords("documents");
|
||||
|
||||
for (const doc of documents) {
|
||||
if (!doc) continue;
|
||||
doc.set("data", { [doc.get("type")]: doc.get("data") });
|
||||
app.save(doc);
|
||||
}
|
||||
},
|
||||
);
|
||||
@@ -1,13 +1,4 @@
|
||||
import {
|
||||
isLocation,
|
||||
isMonster,
|
||||
isNpc,
|
||||
isScene,
|
||||
isSecret,
|
||||
isSession,
|
||||
isTreasure,
|
||||
type AnyDocument,
|
||||
} from "@/lib/types";
|
||||
import { type AnyDocument } from "@/lib/types";
|
||||
import { LocationEditForm } from "./location/LocationEditForm";
|
||||
import { MonsterEditForm } from "./monsters/MonsterEditForm";
|
||||
import { NpcEditForm } from "./npc/NpcEditForm";
|
||||
@@ -16,41 +7,24 @@ import { SecretEditForm } from "./secret/SecretEditForm";
|
||||
import { SessionEditForm } from "./session/SessionEditForm";
|
||||
import { TreasureEditForm } from "./treasure/TreasureEditForm";
|
||||
|
||||
function assertUnreachable(_x: never): never {
|
||||
throw new Error("DocumentForm switch is not exhaustive");
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders a form for any document type depending on the relationship.
|
||||
*/
|
||||
export const DocumentEditForm = ({ document }: { document: AnyDocument }) => {
|
||||
if (isLocation(document)) {
|
||||
switch (document.type) {
|
||||
case "location":
|
||||
return <LocationEditForm location={document} />;
|
||||
}
|
||||
|
||||
if (isMonster(document)) {
|
||||
case "monster":
|
||||
return <MonsterEditForm monster={document} />;
|
||||
}
|
||||
|
||||
if (isNpc(document)) {
|
||||
case "npc":
|
||||
return <NpcEditForm npc={document} />;
|
||||
}
|
||||
|
||||
if (isScene(document)) {
|
||||
case "scene":
|
||||
return <SceneEditForm scene={document} />;
|
||||
}
|
||||
|
||||
if (isSecret(document)) {
|
||||
case "secret":
|
||||
return <SecretEditForm secret={document} />;
|
||||
}
|
||||
|
||||
if (isSession(document)) {
|
||||
case "session":
|
||||
return <SessionEditForm session={document} />;
|
||||
}
|
||||
|
||||
if (isTreasure(document)) {
|
||||
case "treasure":
|
||||
return <TreasureEditForm treasure={document} />;
|
||||
}
|
||||
|
||||
return assertUnreachable(document);
|
||||
};
|
||||
|
||||
@@ -1,64 +1,33 @@
|
||||
// DocumentRow.tsx
|
||||
// Generic row component for displaying any document type.
|
||||
import {
|
||||
isLocation,
|
||||
isMonster,
|
||||
isNpc,
|
||||
isScene,
|
||||
isSecret,
|
||||
isSession,
|
||||
isTreasure,
|
||||
type Document,
|
||||
} from "@/lib/types";
|
||||
import { type AnyDocument } from "@/lib/types";
|
||||
import { LocationPrintRow } from "./location/LocationPrintRow";
|
||||
import { MonsterPrintRow } from "./monsters/MonsterPrintRow";
|
||||
import { TreasurePrintRow } from "./treasure/TreasurePrintRow";
|
||||
import { SecretPrintRow } from "./secret/SecretPrintRow";
|
||||
import { NpcPrintRow } from "./npc/NpcPrintRow";
|
||||
import { ScenePrintRow } from "./scene/ScenePrintRow";
|
||||
import { SecretPrintRow } from "./secret/SecretPrintRow";
|
||||
import { SessionPrintRow } from "./session/SessionPrintRow";
|
||||
import { TreasurePrintRow } from "./treasure/TreasurePrintRow";
|
||||
|
||||
/**
|
||||
* Renders a row for any document type. Prioritizes Session, then Secret, then falls back to ID and creation time.
|
||||
* If rendering a SecretRow, uses the provided session prop if available.
|
||||
*/
|
||||
export const DocumentPrintRow = ({ document }: { document: Document }) => {
|
||||
if (isLocation(document)) {
|
||||
export const DocumentPrintRow = ({ document }: { document: AnyDocument }) => {
|
||||
switch (document.type) {
|
||||
case "location":
|
||||
return <LocationPrintRow location={document} />;
|
||||
}
|
||||
|
||||
if (isMonster(document)) {
|
||||
case "monster":
|
||||
return <MonsterPrintRow monster={document} />;
|
||||
}
|
||||
|
||||
if (isNpc(document)) {
|
||||
case "npc":
|
||||
return <NpcPrintRow npc={document} />;
|
||||
}
|
||||
|
||||
if (isSession(document)) {
|
||||
return <SessionPrintRow session={document} />;
|
||||
}
|
||||
|
||||
if (isSecret(document)) {
|
||||
return <SecretPrintRow secret={document} />;
|
||||
}
|
||||
|
||||
if (isScene(document)) {
|
||||
case "scene":
|
||||
return <ScenePrintRow scene={document} />;
|
||||
}
|
||||
|
||||
if (isTreasure(document)) {
|
||||
case "secret":
|
||||
return <SecretPrintRow secret={document} />;
|
||||
case "session":
|
||||
return <SessionPrintRow session={document} />;
|
||||
case "treasure":
|
||||
return <TreasurePrintRow treasure={document} />;
|
||||
}
|
||||
|
||||
// Fallback: show ID and creation time
|
||||
return (
|
||||
<div>
|
||||
<div className="font-semibold text-lg text-slate-300">
|
||||
Unrecognized Document
|
||||
</div>
|
||||
<div className="text-slate-400 text-sm">ID: {document.id}</div>
|
||||
<div className="text-slate-400 text-sm">Created: {document.created}</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,17 +1,7 @@
|
||||
// DocumentRow.tsx
|
||||
// Generic row component for displaying any document type.
|
||||
import { SecretToggleRow } from "@/components/documents/secret/SecretToggleRow";
|
||||
import {
|
||||
isLocation,
|
||||
isMonster,
|
||||
isNpc,
|
||||
isScene,
|
||||
isSecret,
|
||||
isSession,
|
||||
isTreasure,
|
||||
type Document,
|
||||
type Session,
|
||||
} from "@/lib/types";
|
||||
import { type AnyDocument, type Session } from "@/lib/types";
|
||||
import { BasicRow } from "./BasicRow";
|
||||
import { TreasureToggleRow } from "./treasure/TreasureToggleRow";
|
||||
|
||||
@@ -23,63 +13,47 @@ export const DocumentRow = ({
|
||||
document,
|
||||
session,
|
||||
}: {
|
||||
document: Document;
|
||||
document: AnyDocument;
|
||||
session?: Session;
|
||||
}) => {
|
||||
if (isLocation(document)) {
|
||||
switch (document.type) {
|
||||
case "location":
|
||||
return (
|
||||
<BasicRow
|
||||
doc={document}
|
||||
title={document.data.location.name}
|
||||
description={document.data.location.description}
|
||||
title={document.data.name}
|
||||
description={document.data.description}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (isMonster(document)) {
|
||||
return <BasicRow doc={document} title={document.data.monster.name} />;
|
||||
}
|
||||
case "monster":
|
||||
return <BasicRow doc={document} title={document.data.name} />;
|
||||
|
||||
if (isNpc(document)) {
|
||||
case "npc":
|
||||
return (
|
||||
<BasicRow
|
||||
doc={document}
|
||||
title={document.data.npc.name}
|
||||
description={document.data.npc.description}
|
||||
title={document.data.name}
|
||||
description={document.data.description}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (isSession(document)) {
|
||||
case "session":
|
||||
return (
|
||||
<BasicRow
|
||||
doc={document}
|
||||
title={document.created}
|
||||
description={document.data.session.strongStart}
|
||||
description={document.data.strongStart}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (isSecret(document)) {
|
||||
case "secret":
|
||||
return <SecretToggleRow secret={document} session={session} />;
|
||||
}
|
||||
|
||||
if (isScene(document)) {
|
||||
return <BasicRow doc={document} title={document.data.scene.text} />;
|
||||
}
|
||||
case "scene":
|
||||
return <BasicRow doc={document} title={document.data.text} />;
|
||||
|
||||
if (isTreasure(document)) {
|
||||
case "treasure":
|
||||
return <TreasureToggleRow treasure={document} session={session} />;
|
||||
}
|
||||
|
||||
// Fallback: show ID and creation time
|
||||
return (
|
||||
<div>
|
||||
<div className="font-semibold text-lg text-slate-300">
|
||||
Unrecognized Document
|
||||
</div>
|
||||
<div className="text-slate-400 text-sm">ID: {document.id}</div>
|
||||
<div className="text-slate-400 text-sm">Created: {document.created}</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -10,11 +10,8 @@ export const LocationEditForm = ({ location }: { location: Location }) => {
|
||||
await pb.collection("documents").update(location.id, {
|
||||
data: {
|
||||
...location.data,
|
||||
location: {
|
||||
...location.data.location,
|
||||
name,
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@@ -22,11 +19,8 @@ export const LocationEditForm = ({ location }: { location: Location }) => {
|
||||
await pb.collection("documents").update(location.id, {
|
||||
data: {
|
||||
...location.data,
|
||||
location: {
|
||||
...location.data.location,
|
||||
description,
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@@ -34,11 +28,11 @@ export const LocationEditForm = ({ location }: { location: Location }) => {
|
||||
<div className="">
|
||||
<AutoSaveTextarea
|
||||
multiline={false}
|
||||
value={location.data.location.name}
|
||||
value={location.data.name}
|
||||
onSave={saveLocationName}
|
||||
/>
|
||||
<AutoSaveTextarea
|
||||
value={location.data.location.description}
|
||||
value={location.data.description}
|
||||
onSave={saveLocationDescription}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -6,8 +6,8 @@ import type { Location } from "@/lib/types";
|
||||
export const LocationPrintRow = ({ location }: { location: Location }) => {
|
||||
return (
|
||||
<li>
|
||||
<h4>{location.data.location.name}</h4>
|
||||
<p>{location.data.location.description}</p>
|
||||
<h4>{location.data.name}</h4>
|
||||
<p>{location.data.description}</p>
|
||||
</li>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -25,12 +25,11 @@ export const NewLocationForm = ({
|
||||
try {
|
||||
const locationDoc: Location = await pb.collection("documents").create({
|
||||
campaign,
|
||||
type: "location",
|
||||
data: {
|
||||
location: {
|
||||
name,
|
||||
description,
|
||||
},
|
||||
},
|
||||
});
|
||||
setName("");
|
||||
setDescription("");
|
||||
|
||||
@@ -10,11 +10,8 @@ export const MonsterEditForm = ({ monster }: { monster: Monster }) => {
|
||||
await pb.collection("documents").update(monster.id, {
|
||||
data: {
|
||||
...monster.data,
|
||||
monster: {
|
||||
...monster.data.monster,
|
||||
name,
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@@ -22,7 +19,7 @@ export const MonsterEditForm = ({ monster }: { monster: Monster }) => {
|
||||
<div className="">
|
||||
<AutoSaveTextarea
|
||||
multiline={false}
|
||||
value={monster.data.monster.name}
|
||||
value={monster.data.name}
|
||||
onSave={saveMonsterName}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -4,5 +4,5 @@ import type { Monster } from "@/lib/types";
|
||||
* Renders an editable monster row
|
||||
*/
|
||||
export const MonsterPrintRow = ({ monster }: { monster: Monster }) => {
|
||||
return <li>{monster.data.monster.name}</li>;
|
||||
return <li>{monster.data..name}</li>;
|
||||
};
|
||||
|
||||
@@ -25,12 +25,11 @@ export const NewMonsterForm = ({
|
||||
try {
|
||||
const monsterDoc: Monster = await pb.collection("documents").create({
|
||||
campaign,
|
||||
type: "monster",
|
||||
data: {
|
||||
monster: {
|
||||
name,
|
||||
description,
|
||||
},
|
||||
},
|
||||
});
|
||||
setName("");
|
||||
setDescription("");
|
||||
|
||||
@@ -25,12 +25,11 @@ export const NewNpcForm = ({
|
||||
try {
|
||||
const npcDoc: Npc = await pb.collection("documents").create({
|
||||
campaign,
|
||||
type: "npc",
|
||||
data: {
|
||||
npc: {
|
||||
name,
|
||||
description,
|
||||
},
|
||||
},
|
||||
});
|
||||
setName("");
|
||||
setDescription("");
|
||||
|
||||
@@ -10,11 +10,8 @@ export const NpcEditForm = ({ npc }: { npc: Npc }) => {
|
||||
await pb.collection("documents").update(npc.id, {
|
||||
data: {
|
||||
...npc.data,
|
||||
npc: {
|
||||
...npc.data.npc,
|
||||
name,
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@@ -22,11 +19,8 @@ export const NpcEditForm = ({ npc }: { npc: Npc }) => {
|
||||
await pb.collection("documents").update(npc.id, {
|
||||
data: {
|
||||
...npc.data,
|
||||
npc: {
|
||||
...npc.data.npc,
|
||||
description,
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@@ -34,11 +28,11 @@ export const NpcEditForm = ({ npc }: { npc: Npc }) => {
|
||||
<div className="">
|
||||
<AutoSaveTextarea
|
||||
multiline={false}
|
||||
value={npc.data.npc.name}
|
||||
value={npc.data.name}
|
||||
onSave={saveNpcName}
|
||||
/>
|
||||
<AutoSaveTextarea
|
||||
value={npc.data.npc.description}
|
||||
value={npc.data.description}
|
||||
onSave={saveNpcDescription}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -6,8 +6,8 @@ import type { Npc } from "@/lib/types";
|
||||
export const NpcPrintRow = ({ npc }: { npc: Npc }) => {
|
||||
return (
|
||||
<li className="">
|
||||
<h4>{npc.data.npc.name}</h4>
|
||||
<p>{npc.data.npc.description}</p>
|
||||
<h4>{npc.data.name}</h4>
|
||||
<p>{npc.data.description}</p>
|
||||
</li>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -26,11 +26,10 @@ export const NewSceneForm = ({
|
||||
try {
|
||||
const sceneDoc: Scene = await pb.collection("documents").create({
|
||||
campaign,
|
||||
type: "scene",
|
||||
data: {
|
||||
scene: {
|
||||
text,
|
||||
},
|
||||
},
|
||||
});
|
||||
setText("");
|
||||
await onCreate(sceneDoc);
|
||||
|
||||
@@ -13,10 +13,8 @@ export const SceneEditForm = ({ scene }: { scene: Scene }) => {
|
||||
await pb.collection("documents").update(scene.id, {
|
||||
data: {
|
||||
...scene.data,
|
||||
scene: {
|
||||
text,
|
||||
},
|
||||
},
|
||||
});
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: ["relationship"],
|
||||
@@ -25,7 +23,7 @@ export const SceneEditForm = ({ scene }: { scene: Scene }) => {
|
||||
|
||||
return (
|
||||
<div className="">
|
||||
<AutoSaveTextarea value={scene.data.scene.text} onSave={saveScene} />
|
||||
<AutoSaveTextarea value={scene.data.text} onSave={saveScene} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -4,5 +4,5 @@ import type { Scene } from "@/lib/types";
|
||||
* Renders an editable scene row
|
||||
*/
|
||||
export const ScenePrintRow = ({ scene }: { scene: Scene }) => {
|
||||
return <li className="">{scene.data.scene.text}</li>;
|
||||
return <li className="">{scene.data.text}</li>;
|
||||
};
|
||||
|
||||
@@ -26,12 +26,11 @@ export const NewSecretForm = ({
|
||||
try {
|
||||
const secretDoc: Secret = await pb.collection("documents").create({
|
||||
campaign,
|
||||
type: "secret",
|
||||
data: {
|
||||
secret: {
|
||||
text: newSecret,
|
||||
discovered: false,
|
||||
},
|
||||
},
|
||||
});
|
||||
setNewSecret("");
|
||||
await onCreate(secretDoc);
|
||||
|
||||
@@ -28,11 +28,8 @@ export const SecretEditForm = ({
|
||||
await pb.collection("documents").update(secret.id, {
|
||||
data: {
|
||||
...secret.data,
|
||||
secret: {
|
||||
...(secret.data as any).secret,
|
||||
discovered: newChecked,
|
||||
},
|
||||
},
|
||||
});
|
||||
if (session || !newChecked) {
|
||||
// If the session exists or the element is being unchecked, remove any
|
||||
@@ -62,11 +59,8 @@ export const SecretEditForm = ({
|
||||
await pb.collection("documents").update(secret.id, {
|
||||
data: {
|
||||
...secret.data,
|
||||
secret: {
|
||||
...secret.data.secret,
|
||||
text,
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@@ -82,7 +76,7 @@ export const SecretEditForm = ({
|
||||
/>
|
||||
<AutoSaveTextarea
|
||||
multiline={false}
|
||||
value={secret.data.secret.text}
|
||||
value={secret.data.text}
|
||||
onSave={saveText}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -68,11 +68,7 @@ export const SecretToggleRow = ({
|
||||
aria-label="Discovered"
|
||||
disabled={loading}
|
||||
/>
|
||||
<span>
|
||||
{(secret.data as any)?.secret?.text || (
|
||||
<span className="italic text-slate-400">(No secret text)</span>
|
||||
)}
|
||||
</span>
|
||||
<span>{secret.data.text}</span>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -7,11 +7,8 @@ export const SessionEditForm = ({ session }: { session: Session }) => {
|
||||
await pb.collection("documents").update(session.id, {
|
||||
data: {
|
||||
...session.data,
|
||||
session: {
|
||||
...session.data.session,
|
||||
strongStart,
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@@ -19,7 +16,7 @@ export const SessionEditForm = ({ session }: { session: Session }) => {
|
||||
<form>
|
||||
<h3 className="text-lg font-bold mb-4 text-slate-100">Strong Start</h3>
|
||||
<AutoSaveTextarea
|
||||
value={session.data.session.strongStart}
|
||||
value={session.data.strongStart}
|
||||
onSave={saveStrongStart}
|
||||
placeholder="Enter a strong start for this session..."
|
||||
aria-label="Strong Start"
|
||||
|
||||
@@ -4,7 +4,7 @@ export const SessionPrintRow = ({ session }: { session: Session }) => {
|
||||
return (
|
||||
<div>
|
||||
<h3 className="text-lg font-bold text-slate-600">StrongStart</h3>
|
||||
<div className="">{session.data.session.strongStart}</div>
|
||||
<div className="">{session.data.strongStart}</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -12,7 +12,7 @@ export const SessionRow = ({ session }: { session: Session }) => {
|
||||
>
|
||||
<FormattedDate date={session.created} />
|
||||
</Link>
|
||||
<div className="">{session.data.session.strongStart}</div>
|
||||
<div className="">{session.data.strongStart}</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -26,12 +26,11 @@ export const NewTreasureForm = ({
|
||||
try {
|
||||
const treasureDoc: Treasure = await pb.collection("documents").create({
|
||||
campaign,
|
||||
type: "treasure",
|
||||
data: {
|
||||
treasure: {
|
||||
text: newTreasure,
|
||||
discovered: false,
|
||||
},
|
||||
},
|
||||
});
|
||||
setNewTreasure("");
|
||||
await onCreate(treasureDoc);
|
||||
|
||||
@@ -62,11 +62,8 @@ export const TreasureEditForm = ({
|
||||
await pb.collection("documents").update(treasure.id, {
|
||||
data: {
|
||||
...treasure.data,
|
||||
treasure: {
|
||||
...treasure.data.treasure,
|
||||
text,
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@@ -82,7 +79,7 @@ export const TreasureEditForm = ({
|
||||
/>
|
||||
<AutoSaveTextarea
|
||||
multiline={false}
|
||||
value={treasure.data.treasure.text}
|
||||
value={treasure.data.text}
|
||||
onSave={saveText}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -68,11 +68,7 @@ export const TreasureToggleRow = ({
|
||||
aria-label="Discovered"
|
||||
disabled={loading}
|
||||
/>
|
||||
<span>
|
||||
{(treasure.data as any)?.treasure?.text || (
|
||||
<span className="italic text-slate-400">(No treasure text)</span>
|
||||
)}
|
||||
</span>
|
||||
<span>{treasure.data.text}</span>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -41,21 +41,6 @@ export type Relationship = RecordModel & {
|
||||
* Documents
|
||||
******************************************/
|
||||
|
||||
export type DocumentData<K extends string, V> = {
|
||||
data: Record<K, V>;
|
||||
};
|
||||
|
||||
export type Document = RecordModel & {
|
||||
id: DocumentId;
|
||||
campaign: CampaignId;
|
||||
data: {
|
||||
[K in DocumentType]?: unknown;
|
||||
};
|
||||
// These two are not in Pocketbase's types, but they seem to always be present
|
||||
created: ISO8601Date;
|
||||
updated: ISO8601Date;
|
||||
};
|
||||
|
||||
export type DocumentType =
|
||||
| "location"
|
||||
| "monster"
|
||||
@@ -65,6 +50,21 @@ export type DocumentType =
|
||||
| "session"
|
||||
| "treasure";
|
||||
|
||||
export type DocumentData<Type extends DocumentType, Data> = {
|
||||
type: Type;
|
||||
data: Data;
|
||||
};
|
||||
|
||||
export type Document<Type extends DocumentType, Data> = RecordModel & {
|
||||
id: DocumentId;
|
||||
campaign: CampaignId;
|
||||
type: Type;
|
||||
data: Data;
|
||||
// These two are not in Pocketbase's types, but they seem to always be present
|
||||
created: ISO8601Date;
|
||||
updated: ISO8601Date;
|
||||
};
|
||||
|
||||
export type AnyDocument =
|
||||
| Location
|
||||
| Monster
|
||||
@@ -75,28 +75,11 @@ export type AnyDocument =
|
||||
| Treasure;
|
||||
|
||||
export function getDocumentType(doc: AnyDocument): DocumentType {
|
||||
if (isLocation(doc)) {
|
||||
return "location";
|
||||
} else if (isMonster(doc)) {
|
||||
return "monster";
|
||||
} else if (isNpc(doc)) {
|
||||
return "npc";
|
||||
} else if (isScene(doc)) {
|
||||
return "scene";
|
||||
} else if (isSecret(doc)) {
|
||||
return "secret";
|
||||
} else if (isSession(doc)) {
|
||||
return "session";
|
||||
} else if (isTreasure(doc)) {
|
||||
return "treasure";
|
||||
}
|
||||
throw new Error(`Document type not found: ${JSON.stringify(doc)}`);
|
||||
return doc.type;
|
||||
}
|
||||
|
||||
/** Locations **/
|
||||
|
||||
export type Location = Document &
|
||||
DocumentData<
|
||||
export type Location = Document<
|
||||
"location",
|
||||
{
|
||||
name: string;
|
||||
@@ -104,28 +87,18 @@ export type Location = Document &
|
||||
}
|
||||
>;
|
||||
|
||||
export function isLocation(doc: Document): doc is Location {
|
||||
return Object.hasOwn(doc.data, "location");
|
||||
}
|
||||
|
||||
/** Monsters **/
|
||||
|
||||
export type Monster = Document &
|
||||
DocumentData<
|
||||
export type Monster = Document<
|
||||
"monster",
|
||||
{
|
||||
name: string;
|
||||
}
|
||||
>;
|
||||
|
||||
export function isMonster(doc: Document): doc is Monster {
|
||||
return Object.hasOwn(doc.data, "monster");
|
||||
}
|
||||
|
||||
/** NPCs **/
|
||||
|
||||
export type Npc = Document &
|
||||
DocumentData<
|
||||
export type Npc = Document<
|
||||
"npc",
|
||||
{
|
||||
name: string;
|
||||
@@ -133,42 +106,27 @@ export type Npc = Document &
|
||||
}
|
||||
>;
|
||||
|
||||
export function isNpc(doc: Document): doc is Npc {
|
||||
return Object.hasOwn(doc.data, "npc");
|
||||
}
|
||||
|
||||
/** Session **/
|
||||
|
||||
export type Session = Document &
|
||||
DocumentData<
|
||||
export type Session = Document<
|
||||
"session",
|
||||
{
|
||||
strongStart: string;
|
||||
}
|
||||
>;
|
||||
|
||||
export function isSession(doc: Document): doc is Session {
|
||||
return Object.hasOwn(doc.data, "session");
|
||||
}
|
||||
|
||||
/** Scene **/
|
||||
|
||||
export type Scene = Document &
|
||||
DocumentData<
|
||||
export type Scene = Document<
|
||||
"scene",
|
||||
{
|
||||
text: string;
|
||||
}
|
||||
>;
|
||||
|
||||
export function isScene(doc: Document): doc is Scene {
|
||||
return Object.hasOwn(doc.data, "scene");
|
||||
}
|
||||
|
||||
/** Secret **/
|
||||
|
||||
export type Secret = Document &
|
||||
DocumentData<
|
||||
export type Secret = Document<
|
||||
"secret",
|
||||
{
|
||||
text: string;
|
||||
@@ -176,21 +134,12 @@ export type Secret = Document &
|
||||
}
|
||||
>;
|
||||
|
||||
export function isSecret(doc: Document): doc is Secret {
|
||||
return Object.hasOwn(doc.data, "secret");
|
||||
}
|
||||
|
||||
/** Treasure **/
|
||||
|
||||
export type Treasure = Document &
|
||||
DocumentData<
|
||||
export type Treasure = Document<
|
||||
"treasure",
|
||||
{
|
||||
text: string;
|
||||
discovered: boolean;
|
||||
}
|
||||
>;
|
||||
|
||||
export function isTreasure(doc: Document): doc is Treasure {
|
||||
return Object.hasOwn(doc.data, "treasure");
|
||||
}
|
||||
|
||||
@@ -27,12 +27,10 @@ function RouteComponent() {
|
||||
.collection("campaigns")
|
||||
.getOne(params.campaignId);
|
||||
// Fetch all documents for this campaign
|
||||
const docs = await pb.collection("documents").getFullList({
|
||||
filter: `campaign = "${params.campaignId}"`,
|
||||
const sessions = await pb.collection("documents").getFullList({
|
||||
filter: `campaign = "${params.campaignId}" && type = 'session'`,
|
||||
sort: "-created",
|
||||
});
|
||||
// Filter to only those with data.session
|
||||
const sessions = docs.filter((doc: any) => doc.data && doc.data.session);
|
||||
return {
|
||||
campaign,
|
||||
sessions,
|
||||
@@ -45,7 +43,7 @@ function RouteComponent() {
|
||||
const prevSession = await pb
|
||||
.collection("documents")
|
||||
.getFirstListItem(
|
||||
`campaign = "${campaign.id}" && json_extract(data, '$.session') IS NOT NULL`,
|
||||
`campaign = "${campaign.id}" && json_extract(data, '$.session') != null`,
|
||||
{
|
||||
sort: "-created",
|
||||
},
|
||||
@@ -58,11 +56,10 @@ function RouteComponent() {
|
||||
|
||||
const newSession = await pb.collection("documents").create({
|
||||
campaign: campaign.id,
|
||||
type: "session",
|
||||
data: {
|
||||
session: {
|
||||
strongStart: "",
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
queryClient.invalidateQueries({ queryKey: ["campaign"] });
|
||||
|
||||
Reference in New Issue
Block a user