Compare commits
2 Commits
4a109d152c
...
3310be9e9b
| Author | SHA1 | Date | |
|---|---|---|---|
| 3310be9e9b | |||
| c7083a9b56 |
35
src/components/EditToggle.tsx
Normal file
35
src/components/EditToggle.tsx
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
import * as Icons from "./Icons";
|
||||||
|
import { useState, Children } from "react";
|
||||||
|
|
||||||
|
export function EditToggle({ children }: React.PropsWithChildren) {
|
||||||
|
const [isEditing, setIsEditing] = useState(false);
|
||||||
|
const editChildren = (
|
||||||
|
Children.toArray(children) as React.ReactElement[]
|
||||||
|
).filter((c) => c.type === Editing);
|
||||||
|
const nonEditChildren = (
|
||||||
|
Children.toArray(children) as React.ReactElement[]
|
||||||
|
).filter((c) => c.type !== Editing);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="relative">
|
||||||
|
<div className="absolute right-0 top-0 z-50">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="inline-flex items-center justify-center rounded-full bg-violet-600 hover:bg-violet-700 text-white w-8 h-8 focus:outline-none focus:ring-2 focus:ring-violet-400"
|
||||||
|
aria-label={isEditing ? "Exit edit mode" : "Enter edit mode"}
|
||||||
|
onClick={() => setIsEditing(!isEditing)}
|
||||||
|
>
|
||||||
|
<Icons.Edit />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
{isEditing ? editChildren : nonEditChildren}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Editing = ({ children }: React.PropsWithChildren) => (
|
||||||
|
<>{children}</>
|
||||||
|
);
|
||||||
|
export const NotEditing = ({ children }: React.PropsWithChildren) => (
|
||||||
|
<>{children}</>
|
||||||
|
);
|
||||||
15
src/components/documents/BasicPreview.tsx
Normal file
15
src/components/documents/BasicPreview.tsx
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
import { FormattedText } from "../FormattedText";
|
||||||
|
|
||||||
|
export type Props = {
|
||||||
|
title?: string;
|
||||||
|
description?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const BasicPreview = ({ title, description }: Props) => {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
{title && <h4 className="font-bold">{title}</h4>}
|
||||||
|
{description && <FormattedText>{description}</FormattedText>}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
68
src/components/documents/DocumentPreview.tsx
Normal file
68
src/components/documents/DocumentPreview.tsx
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
// Shows a preview of a document with it's relationships.
|
||||||
|
import { makeDocumentPath } from "@/lib/documentPath";
|
||||||
|
import { relationshipsForDocument } from "@/lib/relationships";
|
||||||
|
import { type AnyDocument } from "@/lib/types";
|
||||||
|
import { Link } from "@tanstack/react-router";
|
||||||
|
import { Editing, EditToggle, NotEditing } from "../EditToggle";
|
||||||
|
import { BasicPreview } from "./BasicPreview";
|
||||||
|
import { DocumentEditForm } from "./DocumentEditForm";
|
||||||
|
|
||||||
|
export const DocumentPreview = ({ doc }: { doc: AnyDocument }) => {
|
||||||
|
const relationships = relationshipsForDocument(doc);
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<EditToggle>
|
||||||
|
<Editing>
|
||||||
|
<DocumentEditForm document={doc} />
|
||||||
|
</Editing>
|
||||||
|
<NotEditing>
|
||||||
|
<ShowDocument doc={doc} />
|
||||||
|
</NotEditing>
|
||||||
|
</EditToggle>
|
||||||
|
<ul>
|
||||||
|
{relationships.map((relType) => (
|
||||||
|
<li>
|
||||||
|
<Link to={makeDocumentPath(doc.id, relType)}>{relType}</Link>
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const ShowDocument = ({ doc }: { doc: AnyDocument }) => {
|
||||||
|
switch (doc.type) {
|
||||||
|
case "location":
|
||||||
|
return (
|
||||||
|
<BasicPreview
|
||||||
|
title={doc.data.name}
|
||||||
|
description={doc.data.description}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
case "monster":
|
||||||
|
return <BasicPreview title={doc.data.name} />;
|
||||||
|
|
||||||
|
case "npc":
|
||||||
|
return (
|
||||||
|
<BasicPreview
|
||||||
|
title={doc.data.name}
|
||||||
|
description={doc.data.description}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
case "session":
|
||||||
|
return (
|
||||||
|
<BasicPreview title={doc.created} description={doc.data.strongStart} />
|
||||||
|
);
|
||||||
|
|
||||||
|
case "secret":
|
||||||
|
return <BasicPreview title={doc.data.text} />;
|
||||||
|
|
||||||
|
case "scene":
|
||||||
|
return <BasicPreview description={doc.data.text} />;
|
||||||
|
|
||||||
|
case "treasure":
|
||||||
|
return <BasicPreview title={doc.data.text} />;
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -9,6 +9,7 @@ import { DocumentTitle } from "./DocumentTitle";
|
|||||||
import { Tab, TabbedLayout } from "../layout/TabbedLayout";
|
import { Tab, TabbedLayout } from "../layout/TabbedLayout";
|
||||||
import { DocumentEditForm } from "./DocumentEditForm";
|
import { DocumentEditForm } from "./DocumentEditForm";
|
||||||
import { RelatedDocumentList } from "./RelatedDocumentList";
|
import { RelatedDocumentList } from "./RelatedDocumentList";
|
||||||
|
import { DocumentPreview } from "./DocumentPreview";
|
||||||
|
|
||||||
export function DocumentView({
|
export function DocumentView({
|
||||||
documentId,
|
documentId,
|
||||||
@@ -59,6 +60,7 @@ export function DocumentView({
|
|||||||
tabs={[
|
tabs={[
|
||||||
<Tab
|
<Tab
|
||||||
to="/document/$documentId"
|
to="/document/$documentId"
|
||||||
|
key="attributes"
|
||||||
params={{
|
params={{
|
||||||
documentId,
|
documentId,
|
||||||
}}
|
}}
|
||||||
@@ -68,6 +70,7 @@ export function DocumentView({
|
|||||||
...relationshipList.map((relationshipEntry) => (
|
...relationshipList.map((relationshipEntry) => (
|
||||||
<Tab
|
<Tab
|
||||||
to="/document/$documentId/$relationshipType"
|
to="/document/$documentId/$relationshipType"
|
||||||
|
key={relationshipEntry}
|
||||||
params={{
|
params={{
|
||||||
documentId,
|
documentId,
|
||||||
relationshipType: relationshipEntry,
|
relationshipType: relationshipEntry,
|
||||||
@@ -101,5 +104,5 @@ function Flyout({ docId }: { docId: DocumentId }) {
|
|||||||
|
|
||||||
const doc = docResult.value.doc;
|
const doc = docResult.value.doc;
|
||||||
|
|
||||||
return <DocumentEditForm document={doc} />;
|
return <DocumentPreview doc={doc} />;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,8 +6,9 @@ export const SessionRow = ({ session }: { session: Session }) => {
|
|||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<Link
|
<Link
|
||||||
to="/document/$documentId"
|
to="/campaigns/$campaignId"
|
||||||
params={{ documentId: session.id }}
|
params={{ campaignId: session.campaign }}
|
||||||
|
search={{ tab: "sessions", docId: session.id }}
|
||||||
className="block font-semibold text-lg text-slate-300"
|
className="block font-semibold text-lg text-slate-300"
|
||||||
>
|
>
|
||||||
<FormattedDate date={session.created} />
|
<FormattedDate date={session.created} />
|
||||||
|
|||||||
@@ -18,15 +18,17 @@ export function TabbedLayout({
|
|||||||
<div className="grow p-2 flex flex-col gap-2">
|
<div className="grow p-2 flex flex-col gap-2">
|
||||||
<div className="flex flex-row gap-2">{navigation}</div>
|
<div className="flex flex-row gap-2">{navigation}</div>
|
||||||
<div>{title}</div>
|
<div>{title}</div>
|
||||||
<div className="flex flex-row justify-start grow">
|
<div className="flex flex-col md:flex-row justify-start grow">
|
||||||
<div className="shrink-0 grow-0 w-40 p-0">{tabs}</div>
|
<div className="shrink-0 grow-0 md:w-40 p-0 flex flex-row flex-wrap md:flex-col md:flex-nowrap">
|
||||||
|
{tabs}
|
||||||
|
</div>
|
||||||
<div
|
<div
|
||||||
className={`grow p-2 bg-slate-800 border-t border-b border-r border-slate-700`}
|
className={`grow p-2 bg-slate-800 border-t border-b border-r border-slate-700 ${flyout && "hidden"} md:block`}
|
||||||
>
|
>
|
||||||
{content}
|
{content}
|
||||||
</div>
|
</div>
|
||||||
{flyout && (
|
{flyout && (
|
||||||
<div className="w-md p-2 bg-slate-800 border border-slate-700">
|
<div className="grow md:w-md p-2 bg-slate-800 border border-slate-700">
|
||||||
{flyout}
|
{flyout}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@@ -38,8 +40,8 @@ export function TabbedLayout({
|
|||||||
export type TabProps = {
|
export type TabProps = {
|
||||||
label: string;
|
label: string;
|
||||||
to: string;
|
to: string;
|
||||||
params: Record<string, any>;
|
params?: Record<string, any>;
|
||||||
search: Record<string, any>;
|
search?: Record<string, any>;
|
||||||
active?: boolean;
|
active?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -19,8 +19,6 @@ export const DocumentContext = createContext<DocumentContextValue | undefined>(
|
|||||||
export function DocumentProvider({ children }: { children: ReactNode }) {
|
export function DocumentProvider({ children }: { children: ReactNode }) {
|
||||||
const [state, dispatch] = useReducer(reducer, initialState());
|
const [state, dispatch] = useReducer(reducer, initialState());
|
||||||
|
|
||||||
console.log("State: ", state);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DocumentContext.Provider
|
<DocumentContext.Provider
|
||||||
value={{
|
value={{
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
|
import { DocumentPreview } from "@/components/documents/DocumentPreview";
|
||||||
import { DocumentRow } from "@/components/documents/DocumentRow";
|
import { DocumentRow } from "@/components/documents/DocumentRow";
|
||||||
import { SessionRow } from "@/components/documents/session/SessionRow";
|
import { SessionRow } from "@/components/documents/session/SessionRow";
|
||||||
import { Tab, TabbedLayout } from "@/components/layout/TabbedLayout";
|
import { Tab, TabbedLayout } from "@/components/layout/TabbedLayout";
|
||||||
import { Loader } from "@/components/Loader";
|
import { Loader } from "@/components/Loader";
|
||||||
|
import { DocumentLoader } from "@/context/document/DocumentLoader";
|
||||||
import { useDocument } from "@/context/document/hooks";
|
import { useDocument } from "@/context/document/hooks";
|
||||||
import { pb } from "@/lib/pocketbase";
|
import { pb } from "@/lib/pocketbase";
|
||||||
import type { Campaign, DocumentId, Relationship, Session } from "@/lib/types";
|
import type { Campaign, DocumentId, Relationship, Session } from "@/lib/types";
|
||||||
@@ -166,11 +168,14 @@ function Flyout({ docId }: { docId: DocumentId }) {
|
|||||||
const { docResult } = useDocument(docId);
|
const { docResult } = useDocument(docId);
|
||||||
|
|
||||||
if (docResult?.type !== "ready") {
|
if (docResult?.type !== "ready") {
|
||||||
return <Loader />;
|
return (
|
||||||
|
<DocumentLoader documentId={docId}>
|
||||||
|
<Loader />
|
||||||
|
</DocumentLoader>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const doc = docResult.value.doc;
|
const doc = docResult.value.doc;
|
||||||
|
|
||||||
// TODO: Document preview
|
return <DocumentPreview doc={doc} />;
|
||||||
return <DocumentRow document={doc} root={doc} />;
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user