diff --git a/src/handbook/package.json b/src/handbook/package.json index e95b73b3f..96df1926a 100644 --- a/src/handbook/package.json +++ b/src/handbook/package.json @@ -16,6 +16,9 @@ "dependencies": { "react": "^18.2.0", "react-dom": "^18.2.0", + "react-icons": "^4.8.0", + "react-d3-tree": "^3.6.1", + "react-collapsible": "^2.10.0", "react-virtualized": "^9.22.3", "events": "^3.3.0" diff --git a/src/handbook/src/backend/data.ts b/src/handbook/src/backend/data.ts index f8a1fc699..cb2a50f35 100644 --- a/src/handbook/src/backend/data.ts +++ b/src/handbook/src/backend/data.ts @@ -6,6 +6,8 @@ import scenes from "@data/scenes.csv"; import quests from "@data/quests.csv"; import items from "@data/items.csv"; +import type { RawNodeDatum } from "react-d3-tree"; + import { Quality, ItemType, ItemCategory, SceneType } from "@backend/types"; import type { MainQuest, Command, Avatar, Item, Scene, Entity, Quest } from "@backend/types"; @@ -209,3 +211,41 @@ export function listMainQuests(): MainQuestDump[] { export function getMainQuestFor(quest: Quest): MainQuest { return allMainQuests[quest.mainId]; } + +/** + * Fetches all quests for a main quest. + * + * @param mainQuest The main quest to fetch quests for. + */ +export function listSubQuestsFor(mainQuest: MainQuest): Quest[] { + return listQuests() + .filter((quest) => quest.mainId == mainQuest.id); +} + +/* + * Tree conversion methods. + */ + +/** + * Converts a quest to a tree. + * + * @param mainQuest The main quest to convert. + */ +export function questToTree(mainQuest: MainQuest): RawNodeDatum { + return { + name: mainQuest.title, + attributes: { + id: mainQuest.id + }, + children: listSubQuestsFor(mainQuest) + .map((quest) => { + return { + name: quest.id.toString(), + attributes: { + description: quest.description + }, + children: [] + } as RawNodeDatum; + }) + }; +} diff --git a/src/handbook/src/css/widgets/quest/NormalQuest.scss b/src/handbook/src/css/widgets/quest/NormalQuest.scss new file mode 100644 index 000000000..1050290ac --- /dev/null +++ b/src/handbook/src/css/widgets/quest/NormalQuest.scss @@ -0,0 +1,54 @@ +.NormalQuest { + display: flex; + align-items: center; + justify-content: space-between; + + width: 431px; + height: 100%; + + min-width: 100px; + min-height: 25px; + max-height: 53px; + + background-color: var(--quest-unselected); + padding: 11px 20px 11px 20px; + box-sizing: border-box; + + p { + user-select: none; + cursor: pointer; + } +} + +.NormalQuest[datatype="right"] { + margin-left: auto; + margin-right: 0; +} + +.NormalQuest:hover { + background-color: var(--quest-selected); + + p { + color: var(--qt-selected); + } +} + +.NormalQuest_Info { + display: flex; + flex-direction: column; + + :nth-child(1) { + font-size: 16px; + color: var(--qt-unselected); + } + + :nth-child(2) { + font-size: 13px; + color: var(--qt2-unselected); + } +} + +.NormalQuest_Icon { + font-size: 16px; + color: var(--quest-accent); +} diff --git a/src/handbook/src/css/widgets/quest/PrimaryQuest.scss b/src/handbook/src/css/widgets/quest/PrimaryQuest.scss new file mode 100644 index 000000000..7f51d322b --- /dev/null +++ b/src/handbook/src/css/widgets/quest/PrimaryQuest.scss @@ -0,0 +1,65 @@ +.PrimaryQuest { + display: flex; + flex-direction: column; + + height: min-content; +} + +.PrimaryQuest_List { + display: flex; + flex-direction: column; + + width: 97%; + margin-left: auto; + margin-right: 5px; + + gap: 8px; + padding: 8px 8px 8px 8px; + background-color: var(--primary-color); +} + +/* Trigger related CSS. */ + +.Trigger { + display: flex; + flex-direction: row; + + gap: 10px; + padding: 10px 10px 10px 10px; + box-sizing: border-box; + + width: 461px; + height: 100%; + min-width: 100px; + min-height: 25px; + max-height: 60px; + + background-color: var(--pq-bg); + + p { + user-select: none; + cursor: pointer; + } +} + +.Trigger_Icon { + font-size: 20px; + padding-top: 5px; + + color: var(--pq-text); +} + +.Trigger_Info { + display: flex; + flex-direction: column; + + :nth-child(1) { + font-size: 16px; + color: var(--pq-text); + } + + :nth-child(2) { + font-size: 14px; + color: var(--pq-text2); + } +} diff --git a/src/handbook/src/ui/widgets/quest/NormalQuest.tsx b/src/handbook/src/ui/widgets/quest/NormalQuest.tsx new file mode 100644 index 000000000..da5303b65 --- /dev/null +++ b/src/handbook/src/ui/widgets/quest/NormalQuest.tsx @@ -0,0 +1,38 @@ +import React from "react"; + +import { IoLocationSharp } from "react-icons/io5" + +import type { Quest } from "@backend/types"; + +import "@css/widgets/quest/NormalQuest.scss"; + +interface IProps { + quest: Quest; + right?: boolean; +} + +class NormalQuest extends React.PureComponent { + constructor(props: IProps) { + super(props); + } + + render() { + const { quest } = this.props; + + return ( +
+
+

{quest.description}

+

ID: {quest.id} | Main: {quest.mainId}

+
+ + +
+ ); + } +} + +export default NormalQuest; diff --git a/src/handbook/src/ui/widgets/quest/PrimaryQuest.tsx b/src/handbook/src/ui/widgets/quest/PrimaryQuest.tsx new file mode 100644 index 000000000..167df5405 --- /dev/null +++ b/src/handbook/src/ui/widgets/quest/PrimaryQuest.tsx @@ -0,0 +1,54 @@ +import React from "react"; + +import { GiSupersonicArrow } from "react-icons/gi"; + +import Collapsible from "react-collapsible"; +import NormalQuest from "@widgets/quest/NormalQuest"; + +import type { MainQuest } from "@backend/types"; +import { listSubQuestsFor } from "@backend/data"; + +import "@css/widgets/quest/PrimaryQuest.scss"; + +interface IProps { + quest: MainQuest; +} + +function Trigger(props: IProps): React.ReactElement { + return ( +
+ +
+

{props.quest.title}

+

ID: {props.quest.id}

+
+
+ ); +} + +class PrimaryQuest extends React.PureComponent { + constructor(props: IProps) { + super(props); + } + + render() { + return ( + } + transitionTime={50} + > +
+ { + listSubQuestsFor(this.props.quest) + .map((quest) => ) + } +
+
+ ); + } +} + +export default PrimaryQuest;