Compare commits
4 Commits
1c26daa828
...
ab323798e9
| Author | SHA1 | Date | |
|---|---|---|---|
| ab323798e9 | |||
| 6979bc4b8f | |||
| c9d27bce75 | |||
| 43afdc8684 |
@@ -28,14 +28,6 @@ export const DocumentEditForm = ({ document }: { document: AnyDocument }) => {
|
||||
case "treasure":
|
||||
return <TreasureEditForm treasure={document} />;
|
||||
case "thread":
|
||||
return (
|
||||
<GenericEditForm
|
||||
doc={document}
|
||||
fields={{
|
||||
text: "multiline",
|
||||
resolved: "checkbox",
|
||||
}}
|
||||
/>
|
||||
);
|
||||
return <GenericEditForm doc={document} />;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { makeDocumentPath, useDocumentPath } from "@/lib/documentPath";
|
||||
import { makeDocumentPath } from "@/lib/documentPath";
|
||||
import type { DocumentId } from "@/lib/types";
|
||||
import { Link, useParams, useSearch } from "@tanstack/react-router";
|
||||
import { Link } from "@tanstack/react-router";
|
||||
|
||||
export type Props = React.PropsWithChildren<{
|
||||
childDocId: DocumentId;
|
||||
@@ -8,37 +8,45 @@ export type Props = React.PropsWithChildren<{
|
||||
}>;
|
||||
|
||||
export function DocumentLink({ childDocId, className, children }: Props) {
|
||||
const docPath = useDocumentPath();
|
||||
// const docPath = useDocumentPath();
|
||||
//
|
||||
// const params = useParams({
|
||||
// strict: false,
|
||||
// });
|
||||
//
|
||||
// const campaignSearch = useSearch({
|
||||
// from: "/_app/_authenticated/campaigns/$campaignId",
|
||||
// shouldThrow: false,
|
||||
// });
|
||||
//
|
||||
// const to = params.campaignId
|
||||
// ? `/campaigns/${params.campaignId}`
|
||||
// : docPath
|
||||
// ? makeDocumentPath(
|
||||
// docPath.documentId,
|
||||
// docPath?.relationshipType,
|
||||
// childDocId,
|
||||
// )
|
||||
// : undefined;
|
||||
//
|
||||
// const search = campaignSearch
|
||||
// ? { tab: campaignSearch.tab, docId: childDocId }
|
||||
// : undefined;
|
||||
//
|
||||
// if (to === undefined) {
|
||||
// throw new Error("Not in a document or campaign context");
|
||||
// }
|
||||
//
|
||||
// return (
|
||||
// <Link to={to} search={search} className={className}>
|
||||
// {children}
|
||||
// </Link>
|
||||
// );
|
||||
|
||||
const params = useParams({
|
||||
strict: false,
|
||||
});
|
||||
|
||||
const campaignSearch = useSearch({
|
||||
from: "/_app/_authenticated/campaigns/$campaignId",
|
||||
shouldThrow: false,
|
||||
});
|
||||
|
||||
const to = params.campaignId
|
||||
? `/campaigns/${params.campaignId}`
|
||||
: docPath
|
||||
? makeDocumentPath(
|
||||
docPath.documentId,
|
||||
docPath?.relationshipType,
|
||||
childDocId,
|
||||
)
|
||||
: undefined;
|
||||
|
||||
const search = campaignSearch
|
||||
? { tab: campaignSearch.tab, docId: childDocId }
|
||||
: undefined;
|
||||
|
||||
if (to === undefined) {
|
||||
throw new Error("Not in a document or campaign context");
|
||||
}
|
||||
const to = makeDocumentPath(childDocId);
|
||||
|
||||
return (
|
||||
<Link to={to} search={search} className={className}>
|
||||
<Link to={to} className={className}>
|
||||
{children}
|
||||
</Link>
|
||||
);
|
||||
|
||||
@@ -1,33 +1,31 @@
|
||||
import { AutoSaveTextarea } from "@/components/AutoSaveTextarea";
|
||||
import { pb } from "@/lib/pocketbase";
|
||||
import type { AnyDocument, Location } from "@/lib/types";
|
||||
import { getDocumentType, type AnyDocument } from "@/lib/types";
|
||||
import { useDocumentCache } from "@/context/document/hooks";
|
||||
import {
|
||||
getFieldsForType,
|
||||
type DocumentField,
|
||||
type FieldType,
|
||||
} from "@/lib/fields";
|
||||
import { ToggleInput } from "../form/ToggleInput";
|
||||
|
||||
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>) => {
|
||||
export const GenericEditForm = <T extends AnyDocument>({ doc }: Props<T>) => {
|
||||
const docType = getDocumentType(doc) as T["type"];
|
||||
const fields = getFieldsForType(docType);
|
||||
|
||||
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}
|
||||
/>
|
||||
),
|
||||
)
|
||||
fields.map((documentField) => (
|
||||
<GenericEditFormField doc={doc} field={documentField} />
|
||||
))
|
||||
}
|
||||
</div>
|
||||
);
|
||||
@@ -35,52 +33,49 @@ export const GenericEditForm = <T extends AnyDocument>({
|
||||
|
||||
const GenericEditFormField = <T extends AnyDocument>({
|
||||
doc,
|
||||
fieldName,
|
||||
fieldType,
|
||||
field,
|
||||
}: {
|
||||
doc: T;
|
||||
fieldName: keyof T["data"];
|
||||
fieldType: GenericFieldType;
|
||||
field: DocumentField<T["type"], FieldType>;
|
||||
}) => {
|
||||
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) {
|
||||
async function saveField(value: string | boolean) {
|
||||
const updated: T = await pb.collection("documents").update(doc.id, {
|
||||
data: {
|
||||
...doc.data,
|
||||
[fieldName]: value,
|
||||
},
|
||||
data: field.setter(value, doc.data),
|
||||
});
|
||||
dispatch({ type: "setDocument", doc: updated });
|
||||
}
|
||||
|
||||
switch (fieldType) {
|
||||
case "multiline":
|
||||
switch (field.fieldType) {
|
||||
case "longText":
|
||||
return (
|
||||
<AutoSaveTextarea
|
||||
multiline={true}
|
||||
value={data[fieldName] as string}
|
||||
value={field.getter(data) as string}
|
||||
onSave={saveField}
|
||||
id={field.name}
|
||||
/>
|
||||
);
|
||||
case "singleline":
|
||||
case "shortText":
|
||||
return (
|
||||
<AutoSaveTextarea
|
||||
multiline={false}
|
||||
value={data[fieldName] as string}
|
||||
value={field.getter(data) as string}
|
||||
onSave={saveField}
|
||||
id={field.name}
|
||||
/>
|
||||
);
|
||||
case "checkbox":
|
||||
case "toggle":
|
||||
return (
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={data[fieldName] as boolean}
|
||||
onChange={(e) => saveField(e.target.value)}
|
||||
className="accent-emerald-500 w-5 h-5"
|
||||
<ToggleInput
|
||||
label={field.name}
|
||||
value={!!field.getter(data)}
|
||||
onChange={saveField}
|
||||
placeholder={field.name}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
141
src/components/documents/GenericNewDocumentForm.tsx
Normal file
141
src/components/documents/GenericNewDocumentForm.tsx
Normal file
@@ -0,0 +1,141 @@
|
||||
import { useDocumentCache } from "@/context/document/hooks";
|
||||
import { DocumentTypeLabel } from "@/lib/documents";
|
||||
import {
|
||||
getFieldsForType,
|
||||
type DocumentField,
|
||||
type FieldType,
|
||||
type ValueForFieldType,
|
||||
} from "@/lib/fields";
|
||||
import { pb } from "@/lib/pocketbase";
|
||||
import {
|
||||
type CampaignId,
|
||||
type DocumentData,
|
||||
type DocumentsByType,
|
||||
type DocumentType,
|
||||
} from "@/lib/types";
|
||||
import { useCallback, useState } from "react";
|
||||
import { BaseForm } from "../form/BaseForm";
|
||||
import { MultiLineInput } from "../form/MultiLineInput";
|
||||
import { SingleLineInput } from "../form/SingleLineInput";
|
||||
import { ToggleInput } from "../form/ToggleInput";
|
||||
|
||||
export type GenericFieldType = "multiline" | "singleline" | "checkbox";
|
||||
|
||||
export type Props<T extends DocumentType> = {
|
||||
docType: T;
|
||||
campaignId: CampaignId;
|
||||
onCreate: (doc: DocumentsByType[T]) => Promise<void>;
|
||||
};
|
||||
|
||||
export const GenericNewDocumentForm = <T extends DocumentType>({
|
||||
docType,
|
||||
campaignId,
|
||||
onCreate,
|
||||
}: Props<T>) => {
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const { dispatch } = useDocumentCache();
|
||||
|
||||
const fields = getFieldsForType(docType);
|
||||
|
||||
const [docData, setDocData] = useState<DocumentData<T>>(
|
||||
fields.reduce((d, f) => f.setDefault(d), {} as DocumentData<T>),
|
||||
);
|
||||
|
||||
const updateData =
|
||||
<F extends FieldType>(field: DocumentField<T, F>) =>
|
||||
(value: ValueForFieldType<F>) =>
|
||||
setDocData(field.setter(value, docData));
|
||||
|
||||
const saveData = useCallback(async () => {
|
||||
setIsLoading(true);
|
||||
console.log(`Creating ${docType}: `, docData);
|
||||
try {
|
||||
const newDocument: DocumentsByType[T] = await pb
|
||||
.collection("documents")
|
||||
.create({
|
||||
campaign: campaignId,
|
||||
type: docType,
|
||||
data: docData,
|
||||
});
|
||||
await onCreate(newDocument);
|
||||
dispatch({
|
||||
type: "setDocument",
|
||||
doc: newDocument,
|
||||
});
|
||||
} catch (e: unknown) {
|
||||
if (e instanceof Error) {
|
||||
setError(e.message);
|
||||
} else {
|
||||
setError("An unknown error occurred while creating the session.");
|
||||
}
|
||||
}
|
||||
setIsLoading(false);
|
||||
}, [campaignId, setIsLoading, setError, docData]);
|
||||
|
||||
// TODO: display name for docType
|
||||
return (
|
||||
<BaseForm
|
||||
title={`Create new ${DocumentTypeLabel[docType]}`}
|
||||
onSubmit={saveData}
|
||||
isLoading={isLoading}
|
||||
error={error}
|
||||
content={
|
||||
// The type checker seems to lose the types when using Object.entries here.
|
||||
fields.map((field) => (
|
||||
<GenericNewFormField
|
||||
field={field}
|
||||
value={field.getter(docData)}
|
||||
isLoading={isLoading}
|
||||
onUpdate={updateData(field)}
|
||||
/>
|
||||
))
|
||||
}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const GenericNewFormField = <T extends DocumentType, F extends FieldType>({
|
||||
field,
|
||||
value,
|
||||
isLoading,
|
||||
onUpdate,
|
||||
}: {
|
||||
field: DocumentField<T, F>;
|
||||
value: ValueForFieldType<F>;
|
||||
isLoading: boolean;
|
||||
onUpdate: (value: ValueForFieldType<F>) => void;
|
||||
}) => {
|
||||
switch (field.fieldType) {
|
||||
case "longText":
|
||||
return (
|
||||
<MultiLineInput
|
||||
label={field.name}
|
||||
value={value as string}
|
||||
onChange={onUpdate as (v: string) => void}
|
||||
disabled={isLoading}
|
||||
placeholder={field.name}
|
||||
/>
|
||||
);
|
||||
case "shortText":
|
||||
return (
|
||||
<SingleLineInput
|
||||
label={field.name}
|
||||
value={value as string}
|
||||
onChange={onUpdate as (v: string) => void}
|
||||
disabled={isLoading}
|
||||
placeholder={field.name}
|
||||
/>
|
||||
);
|
||||
case "toggle":
|
||||
return (
|
||||
<ToggleInput
|
||||
label={field.name}
|
||||
value={value as boolean}
|
||||
onChange={onUpdate as (v: boolean) => void}
|
||||
disabled={isLoading}
|
||||
placeholder={field.name}
|
||||
/>
|
||||
);
|
||||
}
|
||||
};
|
||||
@@ -4,6 +4,7 @@ import {
|
||||
type DocumentType,
|
||||
} from "@/lib/types";
|
||||
import { NewSessionForm } from "./session/NewSessionForm";
|
||||
import { GenericNewDocumentForm } from "./GenericNewDocumentForm";
|
||||
|
||||
/**
|
||||
* Renders a form for any document type depending on the relationship.
|
||||
@@ -21,7 +22,14 @@ export const NewCampaignDocumentForm = ({
|
||||
case "session":
|
||||
return <NewSessionForm campaignId={campaignId} onCreate={onCreate} />;
|
||||
case "thread":
|
||||
return <NewThreadForm campaignId={campaignId} onCreate={onCreate} />;
|
||||
case "location":
|
||||
return (
|
||||
<GenericNewDocumentForm
|
||||
docType={docType}
|
||||
campaignId={campaignId}
|
||||
onCreate={onCreate}
|
||||
/>
|
||||
);
|
||||
default:
|
||||
throw new Error(
|
||||
`Rendered NewCampaignDocumentForm with unsupported docType: ${docType}`,
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
import { BaseForm } from "@/components/form/BaseForm";
|
||||
import { SingleLineInput } from "@/components/form/SingleLineInput";
|
||||
import { useDocumentCache } from "@/context/document/hooks";
|
||||
import { pb } from "@/lib/pocketbase";
|
||||
import type {
|
||||
@@ -8,7 +6,8 @@ import type {
|
||||
Relationship,
|
||||
Session,
|
||||
} from "@/lib/types";
|
||||
import { useCallback, useState } from "react";
|
||||
import { useCallback } from "react";
|
||||
import { GenericNewDocumentForm } from "../GenericNewDocumentForm";
|
||||
|
||||
export type Props = {
|
||||
campaignId: CampaignId;
|
||||
@@ -16,17 +15,10 @@ export type Props = {
|
||||
};
|
||||
|
||||
export const NewSessionForm = ({ campaignId, onCreate }: Props) => {
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [name, setName] = useState<string>("");
|
||||
const { dispatch } = useDocumentCache();
|
||||
|
||||
console.log("Rendering with name: ", name);
|
||||
|
||||
const createNewSession = useCallback(async () => {
|
||||
setIsLoading(true);
|
||||
console.log("Creating session: ", name);
|
||||
try {
|
||||
const createSessionRelations = useCallback(
|
||||
async (newSession: Session) => {
|
||||
// Check for a previous session
|
||||
const prevSession = await pb
|
||||
.collection("documents")
|
||||
@@ -34,15 +26,6 @@ export const NewSessionForm = ({ campaignId, onCreate }: Props) => {
|
||||
sort: "-created",
|
||||
});
|
||||
|
||||
const newSession: Session = await pb.collection("documents").create({
|
||||
campaign: campaignId,
|
||||
type: "session",
|
||||
data: {
|
||||
name,
|
||||
strongStart: "",
|
||||
},
|
||||
});
|
||||
|
||||
// If any relations, then copy things over
|
||||
if (prevSession) {
|
||||
const prevRelations = await pb
|
||||
@@ -59,38 +42,16 @@ export const NewSessionForm = ({ campaignId, onCreate }: Props) => {
|
||||
});
|
||||
}
|
||||
}
|
||||
dispatch({
|
||||
type: "setDocument",
|
||||
doc: newSession,
|
||||
});
|
||||
await onCreate?.(newSession);
|
||||
} catch (e: unknown) {
|
||||
if (e instanceof Error) {
|
||||
setError(e.message);
|
||||
} else {
|
||||
setError("An unknown error occurred while creating the session.");
|
||||
}
|
||||
}
|
||||
setIsLoading(false);
|
||||
}, [campaignId, name, dispatch, setIsLoading, setError]);
|
||||
await onCreate(newSession);
|
||||
},
|
||||
[campaignId, dispatch],
|
||||
);
|
||||
|
||||
return (
|
||||
<BaseForm
|
||||
title="Create new session"
|
||||
onSubmit={createNewSession}
|
||||
isLoading={isLoading}
|
||||
error={error}
|
||||
content={
|
||||
<>
|
||||
<SingleLineInput
|
||||
label="Name"
|
||||
value={name}
|
||||
onChange={setName}
|
||||
disabled={isLoading}
|
||||
placeholder="Enter session name"
|
||||
/>
|
||||
</>
|
||||
}
|
||||
<GenericNewDocumentForm
|
||||
docType="session"
|
||||
campaignId={campaignId}
|
||||
onCreate={createSessionRelations}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
29
src/components/form/ToggleInput.tsx
Normal file
29
src/components/form/ToggleInput.tsx
Normal file
@@ -0,0 +1,29 @@
|
||||
export type Props = {
|
||||
value: boolean;
|
||||
onChange: (value: boolean) => void;
|
||||
label?: string;
|
||||
className?: string;
|
||||
} & Omit<
|
||||
React.InputHTMLAttributes<HTMLInputElement>,
|
||||
"value" | "onChange" | "className"
|
||||
>;
|
||||
|
||||
export const ToggleInput = ({
|
||||
value,
|
||||
onChange,
|
||||
className = "",
|
||||
label,
|
||||
...props
|
||||
}: Props) => (
|
||||
<div className="flex flex-row gap-4 p-2">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={value}
|
||||
onChange={(e) => onChange(e.target.checked)}
|
||||
className={`rounded border bg-slate-800 text-slate-100 border-slate-700 focus:outline-none focus:ring-2 focus:ring-violet-500 transition-colors ${className}`}
|
||||
aria-label={label}
|
||||
{...props}
|
||||
/>
|
||||
{label && <label>{label}</label>}
|
||||
</div>
|
||||
);
|
||||
25
src/lib/documents.ts
Normal file
25
src/lib/documents.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import type { DocumentType } from "./types";
|
||||
|
||||
export const DocumentTypeLabel: Record<DocumentType, string> = {
|
||||
session: "Session",
|
||||
secret: "Secret",
|
||||
npc: "NPC",
|
||||
location: "Location",
|
||||
thread: "Thread",
|
||||
front: "Front",
|
||||
monster: "Monster",
|
||||
scene: "Scene",
|
||||
treasure: "Treasure",
|
||||
};
|
||||
|
||||
export const DocumentTypeLabePlural: Record<DocumentType, string> = {
|
||||
session: "Sessions",
|
||||
secret: "Secrets",
|
||||
npc: "NPCs",
|
||||
location: "Locations",
|
||||
thread: "Threads",
|
||||
front: "Fronts",
|
||||
monster: "Monsters",
|
||||
scene: "Scenes",
|
||||
treasure: "Treasures",
|
||||
};
|
||||
96
src/lib/fields.ts
Normal file
96
src/lib/fields.ts
Normal file
@@ -0,0 +1,96 @@
|
||||
import { type DocumentData, type DocumentType } from "./types";
|
||||
|
||||
export type FieldType = "shortText" | "longText" | "toggle";
|
||||
|
||||
export type ValueForFieldType<F extends FieldType> = {
|
||||
shortText: string;
|
||||
longText: string;
|
||||
toggle: boolean;
|
||||
}[F];
|
||||
|
||||
function defaultValue<F extends FieldType>(fieldType: F): ValueForFieldType<F> {
|
||||
switch (fieldType) {
|
||||
case "shortText":
|
||||
case "longText":
|
||||
return "" as ValueForFieldType<F>;
|
||||
case "toggle":
|
||||
return false as ValueForFieldType<F>;
|
||||
}
|
||||
}
|
||||
|
||||
export type DocumentField<D extends DocumentType, F extends FieldType> = {
|
||||
name: string;
|
||||
fieldType: F;
|
||||
getter: (doc: DocumentData<D>) => ValueForFieldType<F>;
|
||||
setter: (
|
||||
value: ValueForFieldType<F>,
|
||||
doc: DocumentData<D>,
|
||||
) => DocumentData<D>;
|
||||
setDefault: (doc: DocumentData<D>) => DocumentData<D>;
|
||||
};
|
||||
|
||||
const simpleField = <D extends DocumentType, F extends FieldType>(
|
||||
name: string,
|
||||
key: keyof DocumentData<D>,
|
||||
fieldType: F,
|
||||
): DocumentField<D, F> => ({
|
||||
name,
|
||||
fieldType,
|
||||
getter: (doc) => doc[key] as unknown as ValueForFieldType<F>,
|
||||
setter: (value, doc) => ({ ...doc, [key]: value }),
|
||||
setDefault: (doc) => ({ ...doc, [key]: defaultValue(fieldType) }),
|
||||
});
|
||||
|
||||
const simpleFields = <D extends DocumentType>(
|
||||
fields: Record<string, [keyof DocumentData<D>, FieldType]>,
|
||||
): DocumentField<D, FieldType>[] =>
|
||||
Object.entries(fields).map(([name, [key, fieldType]]) =>
|
||||
simpleField(name, key, fieldType),
|
||||
);
|
||||
|
||||
export function getFieldsForType<D extends DocumentType>(
|
||||
docType: D,
|
||||
): DocumentField<D, FieldType>[] {
|
||||
switch (docType) {
|
||||
case "front":
|
||||
return simpleFields<"front">({
|
||||
Name: ["name", "shortText"],
|
||||
Description: ["description", "longText"],
|
||||
Resolved: ["resolved", "toggle"],
|
||||
});
|
||||
case "location":
|
||||
return [
|
||||
simpleField("Name", "name", "shortText"),
|
||||
simpleField("Description", "description", "longText"),
|
||||
];
|
||||
case "monster":
|
||||
return [simpleField("Name", "name", "shortText")];
|
||||
case "npc":
|
||||
return [
|
||||
simpleField("Name", "name", "shortText"),
|
||||
simpleField("Description", "description", "longText"),
|
||||
];
|
||||
case "scene":
|
||||
return [simpleField("Text", "text", "longText")];
|
||||
case "secret":
|
||||
return [
|
||||
simpleField("Discovered", "discovered", "toggle"),
|
||||
simpleField("Text", "text", "shortText"),
|
||||
];
|
||||
case "session":
|
||||
return [
|
||||
simpleField("Name", "name", "shortText"),
|
||||
simpleField("Strong Start", "strongStart", "longText"),
|
||||
];
|
||||
case "thread":
|
||||
return [
|
||||
simpleField("Resolved", "resolved", "toggle"),
|
||||
simpleField("Text", "text", "shortText"),
|
||||
];
|
||||
case "treasure":
|
||||
return [
|
||||
simpleField("Discovered", "discovered", "toggle"),
|
||||
simpleField("Text", "text", "shortText"),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -75,11 +75,6 @@ export type DocumentType =
|
||||
| "thread"
|
||||
| "treasure";
|
||||
|
||||
export type DocumentData<Type extends DocumentType, Data> = {
|
||||
type: Type;
|
||||
data: Data;
|
||||
};
|
||||
|
||||
export type Document<Type extends DocumentType, Data> = RecordModel & {
|
||||
id: DocumentId;
|
||||
collectionName: typeof CollectionIds.Documents;
|
||||
@@ -102,6 +97,23 @@ export type AnyDocument =
|
||||
| Thread
|
||||
| Treasure;
|
||||
|
||||
export type DocumentsByType = {
|
||||
front: Front;
|
||||
location: Location;
|
||||
monster: Monster;
|
||||
npc: Npc;
|
||||
scene: Scene;
|
||||
secret: Secret;
|
||||
session: Session;
|
||||
thread: Thread;
|
||||
treasure: Treasure;
|
||||
};
|
||||
|
||||
export type DocumentData<Type extends DocumentType> =
|
||||
DocumentsByType[Type]["data"];
|
||||
|
||||
export type GetDocumentType<D extends AnyDocument> = D["type"];
|
||||
|
||||
export function getDocumentType(doc: AnyDocument): DocumentType {
|
||||
return doc.type;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user