diff --git a/firestore.rules b/firestore.rules index d87cbb91..e48205af 100644 --- a/firestore.rules +++ b/firestore.rules @@ -116,6 +116,10 @@ service cloud.firestore { allow read: if request.auth.uid != null; allow write: if checkIsHomebrewOwner(); } + match /oracles/oracles { + allow read: if request.auth.uid != null; + allow write: if checkIsHomebrewOwner(); + } } match /users/{userId} { allow read: if true; diff --git a/src/api-calls/homebrew/_getRef.ts b/src/api-calls/homebrew/_getRef.ts index 22786283..79e47d77 100644 --- a/src/api-calls/homebrew/_getRef.ts +++ b/src/api-calls/homebrew/_getRef.ts @@ -5,7 +5,7 @@ import { collection, doc, } from "firebase/firestore"; -import { BaseExpansionOrRuleset } from "types/HomebrewCollection.type"; +import { BaseExpansionOrRuleset } from "types/homebrew/HomebrewCollection.type"; export function constructHomebrewCollectionPath() { return "/homebrew"; diff --git a/src/api-calls/homebrew/createHomebrewExpansion.ts b/src/api-calls/homebrew/createHomebrewExpansion.ts index a6121b5f..5c93979b 100644 --- a/src/api-calls/homebrew/createHomebrewExpansion.ts +++ b/src/api-calls/homebrew/createHomebrewExpansion.ts @@ -1,6 +1,6 @@ import { addDoc } from "firebase/firestore"; import { getHomebrewCollection } from "./_getRef"; -import { BaseExpansion } from "types/HomebrewCollection.type"; +import { BaseExpansion } from "types/homebrew/HomebrewCollection.type"; import { createApiFunction } from "api-calls/createApiFunction"; export const createHomebrewExpansion = createApiFunction( diff --git a/src/api-calls/homebrew/listenToHomebrewCollections.ts b/src/api-calls/homebrew/listenToHomebrewCollections.ts index dd8ff5a4..9914c60f 100644 --- a/src/api-calls/homebrew/listenToHomebrewCollections.ts +++ b/src/api-calls/homebrew/listenToHomebrewCollections.ts @@ -1,5 +1,5 @@ import { onSnapshot, query, where } from "firebase/firestore"; -import { BaseExpansionOrRuleset } from "types/HomebrewCollection.type"; +import { BaseExpansionOrRuleset } from "types/homebrew/HomebrewCollection.type"; import { getHomebrewCollection } from "./_getRef"; export function listenToHomebrewCollections( diff --git a/src/api-calls/homebrew/oracles/_getRef.ts b/src/api-calls/homebrew/oracles/_getRef.ts new file mode 100644 index 00000000..3302b660 --- /dev/null +++ b/src/api-calls/homebrew/oracles/_getRef.ts @@ -0,0 +1,15 @@ +import { DocumentReference, doc } from "firebase/firestore"; +import { constructHomebrewCollectionDocPath } from "../_getRef"; +import { firestore } from "config/firebase.config"; +import { DictKey, OracleTablesCollection } from "@datasworn/core"; + +function constructHomebrewOracleDocPath(homebrewId: string) { + return `${constructHomebrewCollectionDocPath(homebrewId)}/oracles/oracles`; +} + +export function getHomebrewOraclesDoc(homebrewId: string) { + return doc( + firestore, + constructHomebrewOracleDocPath(homebrewId) + ) as DocumentReference>; +} diff --git a/src/api-calls/homebrew/oracles/listenToHomebrewOracles.ts b/src/api-calls/homebrew/oracles/listenToHomebrewOracles.ts new file mode 100644 index 00000000..cd49e3ef --- /dev/null +++ b/src/api-calls/homebrew/oracles/listenToHomebrewOracles.ts @@ -0,0 +1,29 @@ +import { OracleTablesCollection } from "@datasworn/core"; +import { getHomebrewOraclesDoc } from "./_getRef"; +import { onSnapshot } from "firebase/firestore"; + +export function listenToHomebrewOracles( + homebrewId: string, + updateOracles: ( + homebrewId: string, + oracles: Record + ) => void, + onError: (error: unknown) => void, + onLoaded: () => void +) { + return onSnapshot( + getHomebrewOraclesDoc(homebrewId), + (snapshot) => { + const doc = snapshot.data(); + if (doc) { + updateOracles(homebrewId, doc); + } else { + onLoaded(); + } + }, + (error) => { + console.error(error); + onError(error); + } + ); +} diff --git a/src/api-calls/homebrew/oracles/updateHomebrewOracles.ts b/src/api-calls/homebrew/oracles/updateHomebrewOracles.ts new file mode 100644 index 00000000..393374ec --- /dev/null +++ b/src/api-calls/homebrew/oracles/updateHomebrewOracles.ts @@ -0,0 +1,22 @@ +import { OracleTablesCollection } from "@datasworn/core"; +import { createApiFunction } from "api-calls/createApiFunction"; +import { PartialWithFieldValue, setDoc } from "firebase/firestore"; +import { getHomebrewOraclesDoc } from "./_getRef"; + +export const updateHomebrewOracles = createApiFunction< + { + homebrewId: string; + oracles: PartialWithFieldValue>; + }, + void +>((params) => { + const { homebrewId, oracles } = params; + + return new Promise((resolve, reject) => { + setDoc(getHomebrewOraclesDoc(homebrewId), oracles, { merge: true }) + .then(() => { + resolve(); + }) + .catch(reject); + }); +}, "Failed to update oracles."); diff --git a/src/api-calls/homebrew/rules/_getRef.ts b/src/api-calls/homebrew/rules/_getRef.ts index 74f98dd4..a1d05472 100644 --- a/src/api-calls/homebrew/rules/_getRef.ts +++ b/src/api-calls/homebrew/rules/_getRef.ts @@ -1,7 +1,7 @@ import { DocumentReference, doc } from "firebase/firestore"; import { constructHomebrewCollectionDocPath } from "../_getRef"; import { firestore } from "config/firebase.config"; -import { StoredRules } from "types/HomebrewCollection.type"; +import { StoredRules } from "types/homebrew/HomebrewRules.type"; export function constructHomebrewRulesDocPath(homebrewId: string) { return `${constructHomebrewCollectionDocPath(homebrewId)}/rules/rules`; diff --git a/src/api-calls/homebrew/rules/createHomebrewRules.ts b/src/api-calls/homebrew/rules/createHomebrewRules.ts index e07684dc..babf3106 100644 --- a/src/api-calls/homebrew/rules/createHomebrewRules.ts +++ b/src/api-calls/homebrew/rules/createHomebrewRules.ts @@ -1,7 +1,7 @@ import { createApiFunction } from "api-calls/createApiFunction"; import { PartialWithFieldValue, updateDoc } from "firebase/firestore"; import { getHomebrewRulesDoc } from "./_getRef"; -import { StoredRules } from "types/HomebrewCollection.type"; +import { StoredRules } from "types/homebrew/HomebrewRules.type"; export const updateHomebrewRules = createApiFunction< { diff --git a/src/api-calls/homebrew/rules/listenToHomebrewRules.ts b/src/api-calls/homebrew/rules/listenToHomebrewRules.ts index 83c16dc1..6c222e61 100644 --- a/src/api-calls/homebrew/rules/listenToHomebrewRules.ts +++ b/src/api-calls/homebrew/rules/listenToHomebrewRules.ts @@ -1,5 +1,5 @@ import { onSnapshot } from "firebase/firestore"; -import { StoredRules } from "types/HomebrewCollection.type"; +import { StoredRules } from "types/homebrew/HomebrewRules.type"; import { getHomebrewRulesDoc } from "./_getRef"; export function listenToHomebrewRules( diff --git a/src/api-calls/homebrew/rules/updateExpansionRules.ts b/src/api-calls/homebrew/rules/updateExpansionRules.ts index 79c59d91..49f84666 100644 --- a/src/api-calls/homebrew/rules/updateExpansionRules.ts +++ b/src/api-calls/homebrew/rules/updateExpansionRules.ts @@ -1,7 +1,7 @@ import { createApiFunction } from "api-calls/createApiFunction"; import { PartialWithFieldValue, setDoc } from "firebase/firestore"; import { getHomebrewRulesDoc } from "./_getRef"; -import { StoredRules } from "types/HomebrewCollection.type"; +import { StoredRules } from "types/homebrew/HomebrewRules.type"; export const updateExpansionRules = createApiFunction< { diff --git a/src/api-calls/homebrew/updateHomebrewExpansion.ts b/src/api-calls/homebrew/updateHomebrewExpansion.ts index c73763d0..7de398f9 100644 --- a/src/api-calls/homebrew/updateHomebrewExpansion.ts +++ b/src/api-calls/homebrew/updateHomebrewExpansion.ts @@ -1,6 +1,6 @@ import { updateDoc } from "firebase/firestore"; import { getHomebrewCollectionDoc } from "./_getRef"; -import { BaseExpansion } from "types/HomebrewCollection.type"; +import { BaseExpansion } from "types/homebrew/HomebrewCollection.type"; import { createApiFunction } from "api-calls/createApiFunction"; export const updateHomebrewExpansion = createApiFunction< diff --git a/src/components/features/charactersAndCampaigns/CollapsibleSectionHeader.tsx b/src/components/features/charactersAndCampaigns/CollapsibleSectionHeader.tsx index b80c25cb..01c5b07f 100644 --- a/src/components/features/charactersAndCampaigns/CollapsibleSectionHeader.tsx +++ b/src/components/features/charactersAndCampaigns/CollapsibleSectionHeader.tsx @@ -4,21 +4,25 @@ import { ListItemButton, ListItemIcon, ListSubheader, + ListSubheaderProps, } from "@mui/material"; import OpenIcon from "@mui/icons-material/ChevronRight"; export interface CollapsibleSectionHeaderProps { + component?: ListSubheaderProps["component"]; text: string; forcedOpen?: boolean; open: boolean; toggleOpen: () => void; + disabled?: boolean; } export function CollapsibleSectionHeader(props: CollapsibleSectionHeaderProps) { - const { text, open, forcedOpen, toggleOpen } = props; + const { component, text, open, forcedOpen, toggleOpen, disabled } = props; return ( ({ mt: open ? 0.5 : 0, @@ -35,7 +39,7 @@ export function CollapsibleSectionHeader(props: CollapsibleSectionHeaderProps) { })} > {!forcedOpen ? ( - + {text} store.appState.openDialogState ); const handleBack = useStore((store) => store.appState.prevDialog); const handleClose = useStore((store) => store.appState.closeDialog); + const isMobile = useIsMobile(); + + if (isMobile && newVersion) { + return ( + {}} + disableSwipeToOpen + disableDiscovery + onClose={handleClose} + sx={{ pt: 1 }} + PaperProps={{ + sx: (theme) => ({ + top: theme.spacing(2), + borderTopLeftRadius: theme.shape.borderRadius, + borderTopRightRadius: theme.shape.borderRadius, + }), + }} + > + + + ); + } + return ( ); diff --git a/src/components/features/charactersAndCampaigns/LinkedDialog/LinkedDialogContent/LinkedDialogContent.tsx b/src/components/features/charactersAndCampaigns/LinkedDialog/LinkedDialogContent/LinkedDialogContent.tsx index 2c755257..e71b9b98 100644 --- a/src/components/features/charactersAndCampaigns/LinkedDialog/LinkedDialogContent/LinkedDialogContent.tsx +++ b/src/components/features/charactersAndCampaigns/LinkedDialog/LinkedDialogContent/LinkedDialogContent.tsx @@ -2,16 +2,18 @@ import { DialogContent } from "@mui/material"; import { LinkedDialogContentTitle } from "./LinkedDialogContentTitle"; import { MoveDialogContent } from "./MoveDialogContent"; import { OracleDialogContent } from "./OracleDialogContent"; +import { NewOracleDialogContent } from "./NewOracleDialogContent"; export interface LinkedDialogContentProps { id?: string; handleBack: () => void; handleClose: () => void; isLastItem: boolean; + newVersion?: boolean; } export function LinkedDialogContent(props: LinkedDialogContentProps) { - const { id, handleBack, handleClose, isLastItem } = props; + const { id, handleBack, handleClose, isLastItem, newVersion } = props; if (id?.startsWith("ironsworn/moves") || id?.startsWith("starforged/moves")) { return ( @@ -26,8 +28,19 @@ export function LinkedDialogContent(props: LinkedDialogContentProps) { if ( id?.startsWith("ironsworn/oracles") || - id?.startsWith("starforged/oracles") + id?.startsWith("starforged/oracles") || + (newVersion && id?.startsWith("starforged/collections/oracles")) ) { + if (newVersion) { + return ( + + ); + } return ( void; handleClose: () => void; isLastItem: boolean; actions?: ReactNode; } export function LinkedDialogContentTitle(props: LinkedDialogContentTitleProps) { - const { children, handleBack, handleClose, isLastItem, actions } = props; + const { children, id, handleBack, handleClose, isLastItem, actions } = props; + + const { success } = useSnackbar(); + + const handleCopy = () => { + navigator.clipboard.writeText(id).then(() => { + success("Copied ID to clipboard"); + }); + }; return ( {actions} + {getIsLocalEnvironment() && ( + + + + + + )} {!isLastItem && ( - handleBack()} - sx={{ flexShrink: 0, marginLeft: 1 }} - > - - + + handleBack()} + sx={{ flexShrink: 0, marginLeft: 1 }} + > + + + )} } diff --git a/src/components/features/charactersAndCampaigns/LinkedDialog/LinkedDialogContent/MoveDialogContent.tsx b/src/components/features/charactersAndCampaigns/LinkedDialog/LinkedDialogContent/MoveDialogContent.tsx index 9ede0fde..f3e24f30 100644 --- a/src/components/features/charactersAndCampaigns/LinkedDialog/LinkedDialogContent/MoveDialogContent.tsx +++ b/src/components/features/charactersAndCampaigns/LinkedDialog/LinkedDialogContent/MoveDialogContent.tsx @@ -40,6 +40,7 @@ export function MoveDialogContent(props: MoveDialogContentProps) { return ( <> void; + handleClose: () => void; + isLastItem: boolean; +} + +export function NewOracleDialogContent(props: NewOracleDialogContentProps) { + const { id, handleBack, handleClose, isLastItem } = props; + + const oracles = useOracleMap(); + const oracle = oracles[id]; + + const pinnedOracles = useStore((store) => store.settings.pinnedOraclesIds); + const updatePinnedOracles = useStore( + (store) => store.settings.togglePinnedOracle + ); + // const { rollOracleTable } = useRoller(); + // console.debug(oracles); + // console.debug(id); + // console.debug(oracle); + + if (!oracle) { + return ( + <> + + Oracle Not Found + + Sorry, we could not find that oracle. + + ); + } + + const pinned = !!pinnedOracles[id]; + + return ( + <> + + updatePinnedOracles(id, !pinned).catch(() => {})} + > + + + + } + > + {oracle.name} + + + {oracle.summary && + (oracle.oracle_type === "column_simple" || + oracle.oracle_type === "column_details" || + !oracle.description) && ( + + )} + {oracle.oracle_type !== "column_simple" && + oracle.oracle_type !== "column_details" && + oracle.description && ( + + )} + {(oracle.oracle_type === "table_simple" || + oracle.oracle_type === "column_simple" || + oracle.oracle_type === "table_details" || + oracle.oracle_type === "column_details") && ( + + )} + {oracle.oracle_type === "table_shared_rolls" && ( + + )} + {(oracle.oracle_type === "table_shared_results" || + oracle.oracle_type === "table_shared_details") && ( + + )} + {oracle.oracle_type === "tables" && ( + + )} + + + ); + + // const table = oracle.Table; + // const pinned = !!pinnedOracles[id]; + + // return ( + // <> + // {oracle.Description && ( + // + // )} + // + // theme.palette.divider} + // borderRadius={(theme) => `${theme.shape.borderRadius}px`} + // sx={{ borderCollapse: "collapse" }} + // width={"100%"} + // > + // theme.palette.background.paperInlayDarker} + // > + // + // + // Roll + // + // + // Result + // + // + // + // + // {table.map((entry, index) => { + // const { Floor, Ceiling, Result } = entry; + + // if (Floor === null || Ceiling === null) { + // return null; + // } + + // const diff = (Ceiling ?? 100) - (Floor ?? 1); + + // return ( + // ({ + // "&:nth-of-type(even)": { + // backgroundColor: theme.palette.background.paperInlay, + // }, + // })} + // > + // theme.palette.text.secondary} + // > + // {diff === 0 ? Floor : `${Floor} - ${Ceiling}`} + // + // theme.palette.text.primary} + // > + // + // + // + // ); + // })} + // + // + // + // + // ); +} diff --git a/src/components/features/charactersAndCampaigns/LinkedDialog/LinkedDialogContent/NewOracleDialogContent/OracleCollection.tsx b/src/components/features/charactersAndCampaigns/LinkedDialog/LinkedDialogContent/NewOracleDialogContent/OracleCollection.tsx new file mode 100644 index 00000000..90c0812b --- /dev/null +++ b/src/components/features/charactersAndCampaigns/LinkedDialog/LinkedDialogContent/NewOracleDialogContent/OracleCollection.tsx @@ -0,0 +1,26 @@ +import { OracleTablesCollection } from "@datasworn/core"; +import { OracleTablesCollectionSubList } from "components/sharedIronsworn/NewOracles/OracleTablesCollectionSubList"; +import { defaultActions } from "components/sharedIronsworn/NewOracles/defaultActions"; + +export interface OracleCollectionProps { + collection: OracleTablesCollection; +} + +export function OracleCollection(props: OracleCollectionProps) { + const { collection } = props; + + return ( + + ); +} diff --git a/src/components/features/charactersAndCampaigns/LinkedDialog/LinkedDialogContent/NewOracleDialogContent/OracleRollableTable.tsx b/src/components/features/charactersAndCampaigns/LinkedDialog/LinkedDialogContent/NewOracleDialogContent/OracleRollableTable.tsx new file mode 100644 index 00000000..c2ab2266 --- /dev/null +++ b/src/components/features/charactersAndCampaigns/LinkedDialog/LinkedDialogContent/NewOracleDialogContent/OracleRollableTable.tsx @@ -0,0 +1,62 @@ +import { + OracleColumnDetails, + OracleColumnSimple, + OracleTableDetails, + OracleTableRowDetails, + OracleTableSimple, +} from "@datasworn/core"; +import { MarkdownRenderer } from "components/shared/MarkdownRenderer"; +import { + SimpleTable, + SimpleTableColumnDefinition, +} from "components/shared/SimpleTable"; + +export interface OracleRollableTableProps { + oracle: + | OracleTableSimple + | OracleColumnSimple + | OracleTableDetails + | OracleColumnDetails; +} + +export function OracleRollableTable(props: OracleRollableTableProps) { + const { oracle } = props; + + const columns: SimpleTableColumnDefinition<(typeof oracle)["rows"][0]>[] = [ + { + label: oracle.column_labels.roll, + renderer: (row) => + row.min !== null && row.max !== null + ? row.max - row.min === 0 + ? row.min + : `${row.min} - ${row.max}` + : null, + textColor: "text.secondary", + }, + { + label: oracle.column_labels.result, + renderer: (row) => , + }, + ]; + + if ( + oracle.oracle_type === "table_details" || + oracle.oracle_type === "column_details" + ) { + columns.push({ + label: oracle.column_labels.detail, + renderer: (row) => + (row as OracleTableRowDetails).detail ? ( + + ) : null, + }); + } + + return ( + <> + + + ); +} diff --git a/src/components/features/charactersAndCampaigns/LinkedDialog/LinkedDialogContent/NewOracleDialogContent/OracleTableSharedResults.tsx b/src/components/features/charactersAndCampaigns/LinkedDialog/LinkedDialogContent/NewOracleDialogContent/OracleTableSharedResults.tsx new file mode 100644 index 00000000..976e2328 --- /dev/null +++ b/src/components/features/charactersAndCampaigns/LinkedDialog/LinkedDialogContent/NewOracleDialogContent/OracleTableSharedResults.tsx @@ -0,0 +1,69 @@ +import { + OracleTableSharedResults as IOracleTableSharedResults, + OracleTableSharedDetails as IOracleTableSharedDetails, + OracleTableRowSimple, + OracleColumnSimple, + OracleColumnDetails, + OracleTableRowDetails, +} from "@datasworn/core"; +import { MarkdownRenderer } from "components/shared/MarkdownRenderer"; +import { + SimpleTable, + SimpleTableColumnDefinition, +} from "components/shared/SimpleTable"; + +export interface OracleTableSharedResultsProps { + oracle: IOracleTableSharedResults | IOracleTableSharedDetails; +} + +export function OracleTableSharedResults(props: OracleTableSharedResultsProps) { + const { oracle } = props; + + const contentArr = Object.values(oracle.contents ?? {}); + const rows = contentArr.length > 0 ? contentArr[0].rows : undefined; + + if (!rows) { + return null; + } + + const columns: SimpleTableColumnDefinition[] = []; + + const contentValues: + | (OracleColumnSimple | OracleColumnDetails)[] + | undefined = oracle.contents ? Object.values(oracle.contents) : undefined; + + contentValues?.forEach((subOracle) => { + columns.push({ + label: subOracle.name, + renderer: (_, index) => { + const row = subOracle.rows[index]; + + return row.min !== null && row.max !== null + ? row.max - row.min === 0 + ? row.min + : `${row.min} - ${row.max}` + : null; + }, + textColor: "text.secondary", + }); + }); + + columns.push({ + label: oracle.column_labels.result, + renderer: (row) => , + }); + + if (oracle.oracle_type === "table_shared_details") { + columns.push({ + label: oracle.column_labels.detail, + renderer: (row) => + (row as OracleTableRowDetails).detail ? ( + + ) : null, + }); + } + + return ; +} diff --git a/src/components/features/charactersAndCampaigns/LinkedDialog/LinkedDialogContent/NewOracleDialogContent/OracleTableSharedRolls.tsx b/src/components/features/charactersAndCampaigns/LinkedDialog/LinkedDialogContent/NewOracleDialogContent/OracleTableSharedRolls.tsx new file mode 100644 index 00000000..cc61ee86 --- /dev/null +++ b/src/components/features/charactersAndCampaigns/LinkedDialog/LinkedDialogContent/NewOracleDialogContent/OracleTableSharedRolls.tsx @@ -0,0 +1,50 @@ +import { + OracleTableSharedRolls as IOracleTableSharedRows, + OracleTableRowSimple, +} from "@datasworn/core"; +import { MarkdownRenderer } from "components/shared/MarkdownRenderer"; +import { + SimpleTable, + SimpleTableColumnDefinition, +} from "components/shared/SimpleTable"; + +export interface OracleTableSharedRollsDialogContentProps { + oracle: IOracleTableSharedRows; +} + +export function OracleTableSharedRolls( + props: OracleTableSharedRollsDialogContentProps +) { + const { oracle } = props; + + const contentArr = Object.values(oracle.contents ?? {}); + const rows = contentArr.length > 0 ? contentArr[0].rows : undefined; + + if (!rows) { + return null; + } + + const columns: SimpleTableColumnDefinition[] = [ + { + label: oracle.column_labels.roll, + renderer: (row) => + row.min !== null && row.max !== null + ? row.max - row.min === 0 + ? row.min + : `${row.min} - ${row.max}` + : null, + textColor: "text.secondary", + }, + ]; + + Object.values(oracle.contents ?? {}).forEach((subOracle) => { + columns.push({ + label: subOracle.name, + renderer: (_, index) => ( + + ), + }); + }); + + return ; +} diff --git a/src/components/features/charactersAndCampaigns/LinkedDialog/LinkedDialogContent/NewOracleDialogContent/index.ts b/src/components/features/charactersAndCampaigns/LinkedDialog/LinkedDialogContent/NewOracleDialogContent/index.ts new file mode 100644 index 00000000..ba50934f --- /dev/null +++ b/src/components/features/charactersAndCampaigns/LinkedDialog/LinkedDialogContent/NewOracleDialogContent/index.ts @@ -0,0 +1 @@ +export * from "./NewOracleDialogContent"; diff --git a/src/components/features/charactersAndCampaigns/LinkedDialog/LinkedDialogContent/OracleDialogContent.tsx b/src/components/features/charactersAndCampaigns/LinkedDialog/LinkedDialogContent/OracleDialogContent.tsx index 2dccecfa..e5f1bec7 100644 --- a/src/components/features/charactersAndCampaigns/LinkedDialog/LinkedDialogContent/OracleDialogContent.tsx +++ b/src/components/features/charactersAndCampaigns/LinkedDialog/LinkedDialogContent/OracleDialogContent.tsx @@ -39,6 +39,7 @@ export function OracleDialogContent(props: OracleDialogContentProps) { return ( <> {props.children}; } - const href = props.href ?? ""; + + const propertiesHref = props.node.properties?.href; + + const href = typeof propertiesHref === "string" ? propertiesHref : ""; + // V2 versions + if (href.startsWith("id:")) { + const strippedHref = href.slice(3); + if (newOracleMap[strippedHref]) { + return ( + openDialog(strippedHref, true)} + > + {props.children} + + ); + } + + console.error("Link: ", href); + + // TODO - add handlers for this situation; + return {props.children}; + } + // V1 versions if (href.startsWith("ironsworn/") || href.startsWith("starforged/")) { if ( (href.startsWith("ironsworn/moves") || diff --git a/src/components/shared/SimpleTable/SimpleTable.tsx b/src/components/shared/SimpleTable/SimpleTable.tsx new file mode 100644 index 00000000..b3e82dd9 --- /dev/null +++ b/src/components/shared/SimpleTable/SimpleTable.tsx @@ -0,0 +1,90 @@ +import { Box, Typography, TypographyProps } from "@mui/material"; +import { ReactNode } from "react"; + +export interface SimpleTableColumnDefinition { + label: string; + renderer: (row: T, index: number) => ReactNode; + textColor?: TypographyProps["color"]; +} + +export interface SimpleTableProps { + columns: SimpleTableColumnDefinition[]; + rows: T[]; +} + +export function SimpleTable(props: SimpleTableProps) { + const { columns, rows } = props; + + return ( + + + + + {columns.map((column, index) => ( + + {column.label} + + ))} + + + + {rows.map((row, index) => { + return ( + ({ + "&:nth-of-type(even)": { + backgroundColor: theme.palette.background.paperInlay, + }, + })} + > + {columns.map((column, columnIndex) => ( + + {column.renderer(row, index)} + + ))} + + ); + })} + + + + ); +} diff --git a/src/components/shared/SimpleTable/index.ts b/src/components/shared/SimpleTable/index.ts new file mode 100644 index 00000000..f4e4cd6e --- /dev/null +++ b/src/components/shared/SimpleTable/index.ts @@ -0,0 +1 @@ +export * from "./SimpleTable"; diff --git a/src/components/sharedIronsworn/NewOracles/Actions.tsx b/src/components/sharedIronsworn/NewOracles/Actions.tsx new file mode 100644 index 00000000..c9f77c58 --- /dev/null +++ b/src/components/sharedIronsworn/NewOracles/Actions.tsx @@ -0,0 +1,25 @@ +import { Box } from "@mui/material"; +import { + OracleListItemActionProps, + extraOracleListItemActionsProp, +} from "./oracleListItemActions"; + +export interface ActionsProps extends OracleListItemActionProps { + actions?: extraOracleListItemActionsProp; +} + +export function Actions(props: ActionsProps) { + const { actions = [], ...actionProps } = props; + + if (actions.length === 0) { + return null; + } + + return ( + + {actions.map((Action, index) => ( + + ))} + + ); +} diff --git a/src/components/sharedIronsworn/NewOracles/OracleCollectionList.tsx b/src/components/sharedIronsworn/NewOracles/OracleCollectionList.tsx new file mode 100644 index 00000000..75623b3d --- /dev/null +++ b/src/components/sharedIronsworn/NewOracles/OracleCollectionList.tsx @@ -0,0 +1,36 @@ +import { OracleTablesCollection } from "@datasworn/core"; +import { Box } from "@mui/material"; +import { OracleTablesCollectionItem } from "./OracleTablesCollectionItem"; +import { extraOracleListItemActionsProp } from "./oracleListItemActions"; +import { defaultActions } from "./defaultActions"; + +export interface OracleCollectionListProps { + oracles: Record; + listItemActions?: extraOracleListItemActionsProp; +} + +export function OracleCollectionList(props: OracleCollectionListProps) { + const { oracles, listItemActions } = props; + + const orderedOracleKeys = Object.keys(oracles).sort((o1, o2) => { + return oracles[o1].name.localeCompare(oracles[o2].name); + }); + + const listItemActionsWithDefault = [ + ...(listItemActions ?? []), + ...defaultActions, + ]; + + return ( + + {orderedOracleKeys.map((oracleCollectionKey) => ( + + ))} + + ); +} diff --git a/src/components/sharedIronsworn/NewOracles/OracleListItemActionOpenDialogButton.tsx b/src/components/sharedIronsworn/NewOracles/OracleListItemActionOpenDialogButton.tsx new file mode 100644 index 00000000..7c6fff85 --- /dev/null +++ b/src/components/sharedIronsworn/NewOracles/OracleListItemActionOpenDialogButton.tsx @@ -0,0 +1,34 @@ +import { IconButton } from "@mui/material"; +import { OracleListItemActionProps } from "./oracleListItemActions"; +import TableIcon from "@mui/icons-material/ListAlt"; +import { useStore } from "stores/store"; + +export function OracleListItemActionOpenDialogButton( + props: OracleListItemActionProps +) { + const { item, disabled } = props; + const openDialog = useStore((store) => store.appState.openDialog); + + const handleOpenClick = () => { + openDialog(item.id, true); + }; + + return ( + + + + ); +} diff --git a/src/components/sharedIronsworn/NewOracles/OracleRollableCollectionListItem.tsx b/src/components/sharedIronsworn/NewOracles/OracleRollableCollectionListItem.tsx new file mode 100644 index 00000000..73f93ab3 --- /dev/null +++ b/src/components/sharedIronsworn/NewOracles/OracleRollableCollectionListItem.tsx @@ -0,0 +1,47 @@ +import { + OracleTableSharedDetails, + OracleTableSharedResults, + OracleTableSharedRolls, +} from "@datasworn/core"; +import { ListItem, ListItemText } from "@mui/material"; +import { OracleSelectableRollableCollectionListItem } from "./OracleSelectableRollableCollectionListItem"; +import { extraOracleListItemActionsProp } from "./oracleListItemActions"; +import { Actions } from "./Actions"; + +export interface OracleRollableCollectionListItemProps { + collection: + | OracleTableSharedRolls + | OracleTableSharedResults + | OracleTableSharedDetails; + actions?: extraOracleListItemActionsProp; + disabled?: boolean; +} + +export function OracleRollableCollectionListItem( + props: OracleRollableCollectionListItemProps +) { + const { collection, actions, disabled } = props; + + if (collection.oracle_type === "table_shared_rolls") { + return ( + + + + + ); + } else { + return ( + + ); + } +} diff --git a/src/components/sharedIronsworn/NewOracles/OracleRollableListItem.tsx b/src/components/sharedIronsworn/NewOracles/OracleRollableListItem.tsx new file mode 100644 index 00000000..1a837c0d --- /dev/null +++ b/src/components/sharedIronsworn/NewOracles/OracleRollableListItem.tsx @@ -0,0 +1,26 @@ +import { OracleRollable } from "@datasworn/core"; +import { ListItem, ListItemText } from "@mui/material"; +import { extraOracleListItemActionsProp } from "./oracleListItemActions"; +import { Actions } from "./Actions"; + +export interface OracleRollableListItemProps { + oracleRollable: OracleRollable; + actions?: extraOracleListItemActionsProp; + disabled?: boolean; +} + +export function OracleRollableListItem(props: OracleRollableListItemProps) { + const { oracleRollable, actions, disabled } = props; + return ( + + + + + ); +} diff --git a/src/components/sharedIronsworn/NewOracles/OracleSelectableRollableCollectionListItem.tsx b/src/components/sharedIronsworn/NewOracles/OracleSelectableRollableCollectionListItem.tsx new file mode 100644 index 00000000..a49d0c8a --- /dev/null +++ b/src/components/sharedIronsworn/NewOracles/OracleSelectableRollableCollectionListItem.tsx @@ -0,0 +1,63 @@ +import { + OracleTableSharedResults, + OracleTableSharedDetails, +} from "@datasworn/core"; +import { + Box, + ListItem, + ListItemText, + MenuItem, + TextField, +} from "@mui/material"; +import { useState } from "react"; +import { extraOracleListItemActionsProp } from "./oracleListItemActions"; +import { Actions } from "./Actions"; + +export interface OracleSelectableRollableCollectionListItemProps { + collection: OracleTableSharedResults | OracleTableSharedDetails; + actions?: extraOracleListItemActionsProp; + disabled?: boolean; +} + +export function OracleSelectableRollableCollectionListItem( + props: OracleSelectableRollableCollectionListItemProps +) { + const { collection, actions, disabled } = props; + + const options = collection.contents ?? {}; + const keys = Object.keys(options); + + const [selectedOption, setSelectedOption] = useState(keys[0] ?? ""); + + return ( + + + + {keys.length > 0 && ( + setSelectedOption(evt.target.value)} + disabled={disabled} + > + {keys.map((key) => ( + + {options[key].name} + + ))} + + )} + + + + ); +} diff --git a/src/components/sharedIronsworn/NewOracles/OracleTablesCollectionItem.tsx b/src/components/sharedIronsworn/NewOracles/OracleTablesCollectionItem.tsx new file mode 100644 index 00000000..d0289b56 --- /dev/null +++ b/src/components/sharedIronsworn/NewOracles/OracleTablesCollectionItem.tsx @@ -0,0 +1,50 @@ +import { OracleTablesCollection } from "@datasworn/core"; +import { Box, Collapse } from "@mui/material"; +import { CollapsibleSectionHeader } from "components/features/charactersAndCampaigns/CollapsibleSectionHeader"; +import { useState } from "react"; +import { extraOracleListItemActionsProp } from "./oracleListItemActions"; +import { OracleTablesCollectionSubList } from "./OracleTablesCollectionSubList"; + +export interface OracleTablesCollectionItemProps { + collectionKey: string; + collection: OracleTablesCollection; + labelPrefix?: string; + listItemActions?: extraOracleListItemActionsProp; + disabled?: boolean; +} + +export function OracleTablesCollectionItem( + props: OracleTablesCollectionItemProps +) { + const { collectionKey, collection, labelPrefix, listItemActions, disabled } = + props; + + const [isExpanded, setIsExpanded] = useState(false); + + const title = labelPrefix + ? `${labelPrefix} ꞏ ${collection.name}` + : collection.name; + + return ( + + setIsExpanded((prevValue) => !prevValue)} + disabled={disabled} + /> + + + + + ); +} diff --git a/src/components/sharedIronsworn/NewOracles/OracleTablesCollectionSubList.tsx b/src/components/sharedIronsworn/NewOracles/OracleTablesCollectionSubList.tsx new file mode 100644 index 00000000..77cb1b1b --- /dev/null +++ b/src/components/sharedIronsworn/NewOracles/OracleTablesCollectionSubList.tsx @@ -0,0 +1,125 @@ +import { + OracleCollection, + OracleTableRollable, + OracleTableSharedRolls, + OracleTableSharedDetails, + OracleTableSharedResults, + OracleTablesCollection, +} from "@datasworn/core"; +import { List, SxProps } from "@mui/material"; +import { OracleRollableListItem } from "./OracleRollableListItem"; +import { extraOracleListItemActionsProp } from "./oracleListItemActions"; +import { OracleRollableCollectionListItem } from "./OracleRollableCollectionListItem"; +import { OracleTablesCollectionItem } from "./OracleTablesCollectionItem"; + +export interface OracleTablesCollectionSubListProps { + oracles: Record; + subCollections: Record; + actions?: extraOracleListItemActionsProp; + disabled?: boolean; + collectionPrefixLabel?: string; + sx?: SxProps; +} + +export function OracleTablesCollectionSubList( + props: OracleTablesCollectionSubListProps +) { + const { + oracles, + subCollections, + actions, + disabled, + collectionPrefixLabel, + sx, + } = props; + + const rollableSubCollections: Record< + string, + OracleTableSharedRolls | OracleTableSharedDetails | OracleTableSharedResults + > = {}; + const nonRollableSubCollections: Record = {}; + + const unsortedSubCollections = subCollections ?? {}; + Object.keys(unsortedSubCollections).forEach((subCollectionKey) => { + if (unsortedSubCollections[subCollectionKey].oracle_type === "tables") { + nonRollableSubCollections[subCollectionKey] = unsortedSubCollections[ + subCollectionKey + ] as OracleTablesCollection; + } else if ( + unsortedSubCollections[subCollectionKey].oracle_type === + "table_shared_rolls" + ) { + rollableSubCollections[subCollectionKey] = unsortedSubCollections[ + subCollectionKey + ] as OracleTableSharedRolls; + } else if ( + unsortedSubCollections[subCollectionKey].oracle_type === + "table_shared_details" + ) { + rollableSubCollections[subCollectionKey] = unsortedSubCollections[ + subCollectionKey + ] as OracleTableSharedDetails; + } else if ( + unsortedSubCollections[subCollectionKey].oracle_type === + "table_shared_results" + ) { + rollableSubCollections[subCollectionKey] = unsortedSubCollections[ + subCollectionKey + ] as OracleTableSharedResults; + } else { + console.error( + "CAME ACROSS UNKNOWN COLLECTION TYPE:", + unsortedSubCollections[subCollectionKey].oracle_type + ); + } + }); + + const sortedRollableKeys = Object.keys(oracles).sort((r1, r2) => + oracles[r1].name.localeCompare(oracles[r2].name) + ); + const sortedRollableSubCollectionKeys = Object.keys( + rollableSubCollections + ).sort((k1, k2) => + rollableSubCollections[k1].name.localeCompare( + rollableSubCollections[k2].name + ) + ); + const sortedNonRollableSubCollectionKeys = Object.keys( + nonRollableSubCollections + ).sort((k1, k2) => + nonRollableSubCollections[k1].name.localeCompare( + nonRollableSubCollections[k2].name + ) + ); + + return ( + + {sortedRollableKeys.map((oracleKey) => ( + + ))} + {sortedRollableSubCollectionKeys.map((subCollectionKey) => ( + + ))} + {sortedNonRollableSubCollectionKeys.map((subCollectionKey) => ( + + ))} + + ); +} diff --git a/src/components/sharedIronsworn/NewOracles/defaultActions.ts b/src/components/sharedIronsworn/NewOracles/defaultActions.ts new file mode 100644 index 00000000..32ceb403 --- /dev/null +++ b/src/components/sharedIronsworn/NewOracles/defaultActions.ts @@ -0,0 +1,3 @@ +import { OracleListItemActionOpenDialogButton } from "./OracleListItemActionOpenDialogButton"; + +export const defaultActions = [OracleListItemActionOpenDialogButton]; diff --git a/src/components/sharedIronsworn/NewOracles/index.ts b/src/components/sharedIronsworn/NewOracles/index.ts new file mode 100644 index 00000000..829fef21 --- /dev/null +++ b/src/components/sharedIronsworn/NewOracles/index.ts @@ -0,0 +1 @@ +export * from "./OracleCollectionList"; diff --git a/src/components/sharedIronsworn/NewOracles/oracleListItemActions.tsx b/src/components/sharedIronsworn/NewOracles/oracleListItemActions.tsx new file mode 100644 index 00000000..e26add41 --- /dev/null +++ b/src/components/sharedIronsworn/NewOracles/oracleListItemActions.tsx @@ -0,0 +1,20 @@ +import { + OracleRollable, + OracleTableSharedDetails, + OracleTableSharedResults, + OracleTableSharedRolls, +} from "@datasworn/core"; +import { ReactElement } from "react"; + +export interface OracleListItemActionProps { + item: + | OracleTableSharedRolls + | OracleTableSharedResults + | OracleTableSharedDetails + | OracleRollable; + disabled?: boolean; +} + +export type extraOracleListItemActionsProp = (( + props: OracleListItemActionProps +) => ReactElement)[]; diff --git a/src/data/hooks/useOracles.ts b/src/data/hooks/useOracles.ts new file mode 100644 index 00000000..135f5815 --- /dev/null +++ b/src/data/hooks/useOracles.ts @@ -0,0 +1,5 @@ +import { oracles } from "data/oraclesNew"; + +export function useOracles() { + return oracles; +} diff --git a/src/data/hooks/useRollableOracleMap.ts b/src/data/hooks/useRollableOracleMap.ts new file mode 100644 index 00000000..e0256c1b --- /dev/null +++ b/src/data/hooks/useRollableOracleMap.ts @@ -0,0 +1,5 @@ +import { oracleMap } from "data/oraclesNew"; + +export function useOracleMap() { + return oracleMap; +} diff --git a/src/data/oraclesNew.ts b/src/data/oraclesNew.ts new file mode 100644 index 00000000..eed90a2e --- /dev/null +++ b/src/data/oraclesNew.ts @@ -0,0 +1,61 @@ +import { + OracleRollable, + OracleTableSharedDetails, + OracleTableSharedResults, + OracleTableSharedRolls, + OracleTablesCollection, +} from "@datasworn/core"; +import { oracles as ironswornOracles } from "@datasworn/ironsworn-classic/json/classic.json"; +import { oracles as starforgedOracles } from "@datasworn/starforged/json/starforged.json"; +import { getSystem } from "hooks/useGameSystem"; +import { GAME_SYSTEMS, GameSystemChooser } from "types/GameSystems.type"; + +const gameSystem = getSystem(); +const gameSystemOracles: GameSystemChooser< + Record +> = { + [GAME_SYSTEMS.IRONSWORN]: ironswornOracles as Record< + string, + OracleTablesCollection + >, + [GAME_SYSTEMS.STARFORGED]: starforgedOracles as Record< + string, + OracleTablesCollection + >, +}; + +export const oracles = gameSystemOracles[gameSystem]; + +/** ORACLES MAP - ALL ORACLES / TABLES THAT COULD POSSIBLY END UP BEING OPENED IN A DIALOG */ +export const oracleMap: Record< + string, + | OracleRollable + | OracleTableSharedRolls + | OracleTableSharedResults + | OracleTableSharedDetails + | OracleTablesCollection +> = {}; + +function parseOracleTablesCollectionIntoRollableOracleMap( + category: OracleTablesCollection +) { + Object.values(category.contents ?? {}).forEach((oracleContent) => { + oracleMap[oracleContent.id] = oracleContent; + }); + Object.values(category.collections ?? {}).forEach((subCollection) => { + if (subCollection.oracle_type === "tables") { + oracleMap[subCollection.id] = subCollection; + parseOracleTablesCollectionIntoRollableOracleMap(subCollection); + } else if ( + subCollection.oracle_type === "table_shared_rolls" || + subCollection.oracle_type === "table_shared_results" || + subCollection.oracle_type === "table_shared_details" + ) { + oracleMap[subCollection.id] = subCollection; + } + }); +} + +Object.values(oracles).forEach((oracleCategory) => { + parseOracleTablesCollectionIntoRollableOracleMap(oracleCategory); +}); diff --git a/src/data/rules.ts b/src/data/rules.ts index 295b77e6..5f503c80 100644 --- a/src/data/rules.ts +++ b/src/data/rules.ts @@ -1,10 +1,10 @@ import { Rules } from "@datasworn/core"; import { rules as ironswornRules } from "@datasworn/ironsworn-classic/json/classic.json"; import { rules as starforgedRules } from "@datasworn/starforged/json/starforged.json"; -import { getGameSystem } from "functions/getGameSystem"; +import { getSystem } from "hooks/useGameSystem"; import { GAME_SYSTEMS, GameSystemChooser } from "types/GameSystems.type"; -const gameSystem = getGameSystem(); +const gameSystem = getSystem(); const rulesMap: GameSystemChooser = { [GAME_SYSTEMS.IRONSWORN]: ironswornRules as Rules, [GAME_SYSTEMS.STARFORGED]: starforgedRules as Rules, diff --git a/src/pages/Homebrew/HomebrewEditorPage/HomebrewEditorPage.tsx b/src/pages/Homebrew/HomebrewEditorPage/HomebrewEditorPage.tsx index 7edd8a63..edef3313 100644 --- a/src/pages/Homebrew/HomebrewEditorPage/HomebrewEditorPage.tsx +++ b/src/pages/Homebrew/HomebrewEditorPage/HomebrewEditorPage.tsx @@ -15,6 +15,7 @@ import { BASE_ROUTES, basePaths } from "routes"; import { RulesSection } from "./RulesSection"; import { useListenToHomebrewContent } from "stores/homebrew/useListenToHomebrewContent"; import { useUpdateQueryStringValueWithoutNavigation } from "hooks/useUpdateQueryStringValueWithoutNavigation"; +import { OracleSection } from "./OracleSection"; enum TABS { ABOUT = "about", @@ -127,6 +128,7 @@ export function HomebrewEditorPage() { {selectedTab === TABS.ABOUT && } + {selectedTab === TABS.ORACLES && } {selectedTab === TABS.RULES && } diff --git a/src/pages/Homebrew/HomebrewEditorPage/OracleSection/OracleSection.tsx b/src/pages/Homebrew/HomebrewEditorPage/OracleSection/OracleSection.tsx new file mode 100644 index 00000000..d1f5f504 --- /dev/null +++ b/src/pages/Homebrew/HomebrewEditorPage/OracleSection/OracleSection.tsx @@ -0,0 +1,44 @@ +import { Box, LinearProgress } from "@mui/material"; +import { EmptyState } from "components/shared/EmptyState"; +import { OracleCollectionList } from "components/sharedIronsworn/NewOracles"; +import { useOracles } from "data/hooks/useOracles"; +import { useStore } from "stores/store"; + +export interface OracleSectionProps { + id: string; +} + +export function OracleSection(props: OracleSectionProps) { + const { id } = props; + + const oracles = useStore( + (store) => store.homebrew.collections[id].oracles?.data ?? {} + ); + const loading = useStore( + (store) => !store.homebrew.collections[id].oracles?.loaded + ); + + const testOracles = useOracles(); + + return ( + + + + ); + + if (loading) { + return ; + } + + if (Object.keys(oracles).length === 0) { + return ( + + ); + } + + return <>Oracles Section; +} diff --git a/src/pages/Homebrew/HomebrewEditorPage/OracleSection/index.ts b/src/pages/Homebrew/HomebrewEditorPage/OracleSection/index.ts new file mode 100644 index 00000000..80c0e597 --- /dev/null +++ b/src/pages/Homebrew/HomebrewEditorPage/OracleSection/index.ts @@ -0,0 +1 @@ +export * from "./OracleSection"; diff --git a/src/pages/Homebrew/HomebrewEditorPage/RulesSection/ConditionMeters/ConditionMeterAutocomplete.tsx b/src/pages/Homebrew/HomebrewEditorPage/RulesSection/ConditionMeters/ConditionMeterAutocomplete.tsx index 19f73c95..17e10773 100644 --- a/src/pages/Homebrew/HomebrewEditorPage/RulesSection/ConditionMeters/ConditionMeterAutocomplete.tsx +++ b/src/pages/Homebrew/HomebrewEditorPage/RulesSection/ConditionMeters/ConditionMeterAutocomplete.tsx @@ -1,6 +1,6 @@ import { Autocomplete, TextField, capitalize } from "@mui/material"; import { useRules } from "data/hooks/useRules"; -import { StoredConditionMeter } from "types/HomebrewCollection.type"; +import { StoredConditionMeter } from "types/homebrew/HomebrewRules.type"; export interface StatAutocompleteProps { conditionMeters: Record; diff --git a/src/pages/Homebrew/HomebrewEditorPage/RulesSection/ConditionMeters/ConditionMeterDialog.tsx b/src/pages/Homebrew/HomebrewEditorPage/RulesSection/ConditionMeters/ConditionMeterDialog.tsx index dc4b664c..bfdf78d9 100644 --- a/src/pages/Homebrew/HomebrewEditorPage/RulesSection/ConditionMeters/ConditionMeterDialog.tsx +++ b/src/pages/Homebrew/HomebrewEditorPage/RulesSection/ConditionMeters/ConditionMeterDialog.tsx @@ -16,7 +16,7 @@ import { useEffect, useState } from "react"; import { StoredConditionMeter, StoredRules, -} from "types/HomebrewCollection.type"; +} from "types/homebrew/HomebrewRules.type"; import { useForm, SubmitHandler, Controller } from "react-hook-form"; import { convertIdPart } from "functions/dataswornIdEncoder"; import { Preview } from "../../Preview"; diff --git a/src/pages/Homebrew/HomebrewEditorPage/RulesSection/ConditionMeters/ConditionMeterPreview.tsx b/src/pages/Homebrew/HomebrewEditorPage/RulesSection/ConditionMeters/ConditionMeterPreview.tsx index 48154cbd..db776d48 100644 --- a/src/pages/Homebrew/HomebrewEditorPage/RulesSection/ConditionMeters/ConditionMeterPreview.tsx +++ b/src/pages/Homebrew/HomebrewEditorPage/RulesSection/ConditionMeters/ConditionMeterPreview.tsx @@ -2,7 +2,7 @@ import { Typography } from "@mui/material"; import { Track } from "components/features/Track"; import { StatComponent } from "components/features/characters/StatComponent"; import { Control, useWatch } from "react-hook-form"; -import { StoredConditionMeter } from "types/HomebrewCollection.type"; +import { StoredConditionMeter } from "types/homebrew/HomebrewRules.type"; export interface ConditionMeterPreviewProps { control: Control; diff --git a/src/pages/Homebrew/HomebrewEditorPage/RulesSection/ConditionMeters/ConditionMeters.tsx b/src/pages/Homebrew/HomebrewEditorPage/RulesSection/ConditionMeters/ConditionMeters.tsx index f0cf75e2..55283736 100644 --- a/src/pages/Homebrew/HomebrewEditorPage/RulesSection/ConditionMeters/ConditionMeters.tsx +++ b/src/pages/Homebrew/HomebrewEditorPage/RulesSection/ConditionMeters/ConditionMeters.tsx @@ -10,7 +10,7 @@ import { import { StoredConditionMeter, StoredRules, -} from "types/HomebrewCollection.type"; +} from "types/homebrew/HomebrewRules.type"; import EditIcon from "@mui/icons-material/Edit"; import DeleteIcon from "@mui/icons-material/Delete"; import { useState } from "react"; @@ -96,6 +96,7 @@ export function ConditionMeters(props: ConditionMetersProps) { }} > ( ; diff --git a/src/pages/Homebrew/HomebrewEditorPage/RulesSection/Stats/StatDialog.tsx b/src/pages/Homebrew/HomebrewEditorPage/RulesSection/Stats/StatDialog.tsx index f71df7d8..4baa9f46 100644 --- a/src/pages/Homebrew/HomebrewEditorPage/RulesSection/Stats/StatDialog.tsx +++ b/src/pages/Homebrew/HomebrewEditorPage/RulesSection/Stats/StatDialog.tsx @@ -1,4 +1,4 @@ -import { StoredRules, StoredStat } from "types/HomebrewCollection.type"; +import { StoredRules, StoredStat } from "types/homebrew/HomebrewRules.type"; import { useForm, SubmitHandler, Controller } from "react-hook-form"; import { Button, diff --git a/src/pages/Homebrew/HomebrewEditorPage/RulesSection/Stats/StatPreviewComponent.tsx b/src/pages/Homebrew/HomebrewEditorPage/RulesSection/Stats/StatPreviewComponent.tsx index 99f1d80c..b5ae3845 100644 --- a/src/pages/Homebrew/HomebrewEditorPage/RulesSection/Stats/StatPreviewComponent.tsx +++ b/src/pages/Homebrew/HomebrewEditorPage/RulesSection/Stats/StatPreviewComponent.tsx @@ -1,6 +1,6 @@ import { Control, useWatch } from "react-hook-form"; import { StatComponent } from "components/features/characters/StatComponent"; -import { StoredStat } from "types/HomebrewCollection.type"; +import { StoredStat } from "types/homebrew/HomebrewRules.type"; export interface StatPreviewComponentProps { control: Control; } diff --git a/src/pages/Homebrew/HomebrewEditorPage/RulesSection/Stats/Stats.tsx b/src/pages/Homebrew/HomebrewEditorPage/RulesSection/Stats/Stats.tsx index 1be5bbec..a7b91840 100644 --- a/src/pages/Homebrew/HomebrewEditorPage/RulesSection/Stats/Stats.tsx +++ b/src/pages/Homebrew/HomebrewEditorPage/RulesSection/Stats/Stats.tsx @@ -8,7 +8,7 @@ import { Typography, } from "@mui/material"; import { useState } from "react"; -import { StoredRules, StoredStat } from "types/HomebrewCollection.type"; +import { StoredRules, StoredStat } from "types/homebrew/HomebrewRules.type"; import { StatDialog } from "./StatDialog"; import { useStore } from "stores/store"; import { deleteField } from "firebase/firestore"; @@ -81,6 +81,7 @@ export function Stats(props: StatsProps) { }} > = (set) => ({ ...defaultAppState, - openDialog: (id) => { + openDialog: (id, newVersion) => { set((store) => { const currentState = store.appState.openDialogState; @@ -14,12 +14,14 @@ export const createAppStateSlice: CreateSliceType = (set) => ({ isOpen: true, previousIds: [...currentState.previousIds, currentState.openId], openId: id, + newVersion, }; } else { store.appState.openDialogState = { isOpen: true, previousIds: [], openId: id, + newVersion, }; } }); @@ -35,6 +37,7 @@ export const createAppStateSlice: CreateSliceType = (set) => ({ isOpen: true, openId: nextId, previousIds: newIds, + newVersion: store.appState.openDialogState.newVersion, }; } }); diff --git a/src/stores/appState/appState.slice.type.ts b/src/stores/appState/appState.slice.type.ts index b2c2dc83..78c2c6a5 100644 --- a/src/stores/appState/appState.slice.type.ts +++ b/src/stores/appState/appState.slice.type.ts @@ -3,6 +3,7 @@ import { Roll } from "types/DieRolls.type"; export interface AppStateData { openDialogState: { isOpen: boolean; + newVersion?: boolean; openId?: string; previousIds: string[]; }; @@ -17,7 +18,7 @@ export interface AppStateData { } export interface AppStateActions { - openDialog: (id: string) => void; + openDialog: (id: string, newVersion?: boolean) => void; prevDialog: () => void; closeDialog: () => void; diff --git a/src/stores/homebrew/homebrew.slice.ts b/src/stores/homebrew/homebrew.slice.ts index af20b664..122d529c 100644 --- a/src/stores/homebrew/homebrew.slice.ts +++ b/src/stores/homebrew/homebrew.slice.ts @@ -9,6 +9,8 @@ import { deleteHomebrewExpansion } from "api-calls/homebrew/deleteHomebrewExpans import { Unsubscribe } from "firebase/firestore"; import { listenToHomebrewRules } from "api-calls/homebrew/rules/listenToHomebrewRules"; import { updateExpansionRules } from "api-calls/homebrew/rules/updateExpansionRules"; +import { updateHomebrewOracles } from "api-calls/homebrew/oracles/updateHomebrewOracles"; +import { listenToHomebrewOracles } from "api-calls/homebrew/oracles/listenToHomebrewOracles"; export const createHomebrewSlice: CreateSliceType = (set) => ({ ...defaultHomebrewSlice, @@ -91,6 +93,46 @@ export const createHomebrewSlice: CreateSliceType = (set) => ({ } ) ); + + unsubscribes.push( + listenToHomebrewOracles( + homebrewId, + (id, oracles) => { + set((store) => { + store.homebrew.collections[homebrewId] = { + ...(store.homebrew.collections[homebrewId] ?? {}), + oracles: { + data: oracles, + loaded: true, + }, + }; + }); + }, + (error) => { + set((store) => { + store.homebrew.collections[homebrewId] = { + ...(store.homebrew.collections[homebrewId] ?? {}), + oracles: { + ...(store.homebrew.collections[homebrewId].oracles ?? {}), + loaded: true, + error: getErrorMessage(error, "Failed to load oracles"), + }, + }; + }); + }, + () => { + set((store) => { + store.homebrew.collections[homebrewId] = { + ...(store.homebrew.collections[homebrewId] ?? {}), + oracles: { + ...(store.homebrew.collections[homebrewId].oracles ?? {}), + loaded: true, + }, + }; + }); + } + ) + ); }); return () => { @@ -111,4 +153,7 @@ export const createHomebrewSlice: CreateSliceType = (set) => ({ updateExpansionRules: (homebrewId, rules) => { return updateExpansionRules({ homebrewId, rules }); }, + updateExpansionOracles: (homebrewId, oracles) => { + return updateHomebrewOracles({ homebrewId, oracles }); + }, }); diff --git a/src/stores/homebrew/homebrew.slice.type.ts b/src/stores/homebrew/homebrew.slice.type.ts index 78f6dbec..c374e427 100644 --- a/src/stores/homebrew/homebrew.slice.type.ts +++ b/src/stores/homebrew/homebrew.slice.type.ts @@ -1,9 +1,10 @@ +import { OracleTablesCollection } from "@datasworn/core"; import { PartialWithFieldValue, Unsubscribe } from "firebase/firestore"; import { BaseExpansion, BaseExpansionOrRuleset, - StoredRules, -} from "types/HomebrewCollection.type"; +} from "types/homebrew/HomebrewCollection.type"; +import { StoredRules } from "types/homebrew/HomebrewRules.type"; export interface HomebrewEntry { base: BaseExpansionOrRuleset; @@ -12,6 +13,11 @@ export interface HomebrewEntry { loaded: boolean; error?: string; }; + oracles?: { + data?: Record; + loaded: boolean; + error?: string; + }; } export interface HomebrewSliceData { @@ -35,6 +41,11 @@ export interface HomebrewSliceActions { expansionId: string, rules: PartialWithFieldValue ) => Promise; + + updateExpansionOracles: ( + expansionId: string, + oracles: PartialWithFieldValue> + ) => Promise; } export type HomebrewSlice = HomebrewSliceData & HomebrewSliceActions; diff --git a/src/types/homebrew/HomebrewCollection.type.ts b/src/types/homebrew/HomebrewCollection.type.ts new file mode 100644 index 00000000..95de8c88 --- /dev/null +++ b/src/types/homebrew/HomebrewCollection.type.ts @@ -0,0 +1,24 @@ +import { Expansion, Ruleset } from "@datasworn/core"; + +type RuleKeys = + | "oracles" + | "moves" + | "assets" + | "atlas" + | "npcs" + | "truths" + | "rarities" + | "delve_sites" + | "site_themes" + | "site_domains" + | "rules"; + +type additions = { + uids: string[]; + creatorUid: string; +}; + +export type BaseExpansion = Omit & additions; +export type BaseRuleset = Omit & additions; + +export type BaseExpansionOrRuleset = BaseExpansion | BaseRuleset; diff --git a/src/types/homebrew/HomebrewOracles.type.ts b/src/types/homebrew/HomebrewOracles.type.ts new file mode 100644 index 00000000..e58dcfcb --- /dev/null +++ b/src/types/homebrew/HomebrewOracles.type.ts @@ -0,0 +1,82 @@ +import { DiceExpression, OracleDuplicateBehavior } from "@datasworn/core"; + +/** ORACLE ROLLS */ + +export interface StoredOracleRoll { + oracleId: string | null; + auto: boolean; + duplicates: OracleDuplicateBehavior; + dice: DiceExpression | null; + numberOfRolls: number; +} + +export interface StoredOracleTableBase { + id: string; + name: string; + result: string; + oracleRolls?: StoredOracleRoll; + min: number | null; + max: number | null; +} + +export interface StoredOracleTableSimple extends StoredOracleTableBase {} + +export interface StoredOracleTableDetails extends StoredOracleTableBase { + detail: string; +} + +export type StoredOracleTableRollable = + | StoredOracleTableSimple + | StoredOracleTableDetails; + +/** ORACLE COLUMNS */ + +export interface StoredOracleColumnSimple { + id: string; + type: "column_simple"; + name: string; + summary?: string; + columnLabels: { + roll: string; + result: string; + }; + replaces?: string; + dice: string; +} + +/** ORACLE TABLES */ + +export type StoredOracleCollection = + | StoredOracleTablesCollection + | StoredOracleTableSharedRolls + | StoredOracleTableSharedResults + | StoredOracleTableSharedDetails; + +interface BaseStoredOracleCollection { + id: string; + type: "tables" | "table_shared_rolls"; + name: string; + description?: string; + enhances?: string; + replaces?: string; +} + +export interface StoredOracleTableSharedRolls + extends BaseStoredOracleCollection { + type: "table_shared_rolls"; + contents: { [key: string]: StoredOracleColumnSimple }; + column_labels: { + roll: string; + }; +} + +export interface StoredOracleTableSharedResults {} + +export interface StoredOracleTableSharedDetails {} + +export interface StoredOracleTablesCollection + extends BaseStoredOracleCollection { + type: "tables"; + contents?: { [key: string]: StoredOracleTableRollable }; + collections?: { [key: string]: StoredOracleCollection }; +} diff --git a/src/types/HomebrewCollection.type.ts b/src/types/homebrew/HomebrewRules.type.ts similarity index 66% rename from src/types/HomebrewCollection.type.ts rename to src/types/homebrew/HomebrewRules.type.ts index b4ee40b4..6bd805f5 100644 --- a/src/types/HomebrewCollection.type.ts +++ b/src/types/homebrew/HomebrewRules.type.ts @@ -1,28 +1,3 @@ -import { Expansion, Ruleset } from "@datasworn/core"; - -type RuleKeys = - | "oracles" - | "moves" - | "assets" - | "atlas" - | "npcs" - | "truths" - | "rarities" - | "delve_sites" - | "site_themes" - | "site_domains" - | "rules"; - -type additions = { - uids: string[]; - creatorUid: string; -}; - -export type BaseExpansion = Omit & additions; -export type BaseRuleset = Omit & additions; - -export type BaseExpansionOrRuleset = BaseExpansion | BaseRuleset; - export interface StoredStat { label: string; description: string;