From 165fb2ea00840ebeff601a7186b63b8db30fb56c Mon Sep 17 00:00:00 2001 From: Jack Papel Date: Fri, 1 Nov 2024 14:57:31 -0400 Subject: [PATCH 1/5] Reorganize search results, main page + role set page --- client/src/components/Wiki.tsx | 192 ++++++++++++++++++-------- client/src/components/WikiArticle.tsx | 58 ++++---- client/src/components/wiki.css | 15 ++ client/src/resources/lang/en_us.json | 1 + 4 files changed, 173 insertions(+), 93 deletions(-) diff --git a/client/src/components/Wiki.tsx b/client/src/components/Wiki.tsx index 7d958b2ea..990f57716 100644 --- a/client/src/components/Wiki.tsx +++ b/client/src/components/Wiki.tsx @@ -1,4 +1,4 @@ -import React, { ReactElement, useCallback, useEffect, useState } from "react"; +import React, { ReactElement, useCallback, useEffect, useMemo, useState } from "react"; import translate from "../game/lang"; import "./wiki.css"; import { Role, getMainRoleSetFromRole } from "../game/roleState.d"; @@ -10,6 +10,8 @@ import Icon from "./Icon"; import { ContentMenu, MenuController } from "../menu/game/GameScreen"; import { AnchorController } from "../menu/Anchor"; import WikiCoverCard from "./WikiCoverCard"; +import { getAllRoles, RoleSet } from "../game/roleListState.d"; +import { useLobbyOrGameState } from "./useHooks"; export function setWikiSearchPage(page: WikiArticleLink, anchorController: AnchorController, menuController?: MenuController) { @@ -76,10 +78,6 @@ export default function Wiki(props: Readonly<{ } } - - - - return
: @@ -123,79 +119,153 @@ function WikiSearchBar(props: {
} -function WikiSearchResults(props: { +function WikiSearchResults(props: Readonly<{ searchQuery: string, - article: WikiArticleLink | null, - enabledRoles?: Role[], onChooseArticle: (article: WikiArticleLink) => void -}): ReactElement { +}>): ReactElement { + const enabledRoles = useLobbyOrGameState( + gameState => gameState.enabledRoles, + ["enabledRoles"], + getAllRoles() + )!; - function getSearchResults(search: string): WikiArticleLink[] { + const getSearchResults = useCallback((search: string) => { const out = [ ...ARTICLES.filter((page) => {return RegExp(regEscape(search.trim()), 'i').test(getArticleTitle(page))}), ...ARTICLES.filter((page) => {return getSearchStrings(page).some((str) => RegExp(regEscape(search.trim()), 'i').test(str))}) ]; - return out.filter((item, index) => out.indexOf(item) === index); - } + return out + .filter((item, index) => out.indexOf(item) === index) + .sort((a, b) => wikiPageSortFunction(a, b)); + }, []); - let searchResultsHtml = []; - const results = getSearchResults(props.searchQuery); - if (props.searchQuery === ""){ - let standardHeaderAdded = false; - let lastArticleRoleFaction = null; - for(let page of results){ + const results = useMemo(() => + getSearchResults(props.searchQuery), + [props.searchQuery, getSearchResults]) - let articleType = page.split("/")[0]; + return
+ {props.searchQuery === "" + ? + : results.map(page => { + let className = undefined; + if( + page.includes("role/") && + !enabledRoles.map(role => `role/${role}`).includes(page) + ) { + className = "keyword-disabled"; + } + + return props.onChooseArticle(page)} + /> + })} +
+} - if(!standardHeaderAdded && articleType === "standard"){ - searchResultsHtml.push(

{translate(articleType)}

); - standardHeaderAdded = true; - } +function WikiMainPage(props: Readonly<{ + articles: WikiArticleLink[], + enabledRoles: Role[], + onChooseArticle: (article: WikiArticleLink) => void +}>): ReactElement { + const articlePartitions = useMemo(() => + partitionWikiPages(props.articles), + [props.articles]); - if(articleType === "role"){ - const role = page.split("/")[1] as Role; - const faction = getMainRoleSetFromRole(role); + return <> + {articlePartitions.roleSets.map(roleSetPartition => <> +

+ {translate(roleSetPartition.roleSet)} +

+ {roleSetPartition.pages.map(page => { + const enabled = props.enabledRoles.map(role => `role/${role}`).includes(page); + return props.onChooseArticle(page)} + />; + })} + )} +

+ {translate("standard")} +

+
+ {articlePartitions.standard.map(letterPartition =>
+ {letterPartition.letterCategory} + {letterPartition.pages.map(page => + props.onChooseArticle(page)}/> + )} +
)} +
+ +} - if(faction !== lastArticleRoleFaction){ - searchResultsHtml.push(

{translate(faction)}

); - lastArticleRoleFaction = faction; - } - } +type WikiPagePartitions = { + roleSets: { + roleSet: RoleSet, + pages: WikiArticleLink[] + }[], + standard: { + letterCategory: string, + pages: WikiArticleLink[] + }[] +} - let className = undefined; - if( - page.includes("role/") && - props.enabledRoles !== undefined && props.enabledRoles.length !== 0 && !props.enabledRoles.map(role => `role/${role}`).includes(page) - ) { - className = "keyword-disabled"; - } +function partitionWikiPages(wikiPages: WikiArticleLink[]): WikiPagePartitions { + const partitions: WikiPagePartitions = { roleSets: [], standard: [] }; - searchResultsHtml.push( - props.onChooseArticle(page)}/> - ); - } - }else{ - for(let page of results){ - - let className = undefined; - if( - page.includes("role/") && - props.enabledRoles !== undefined && props.enabledRoles.length !== 0 && !props.enabledRoles.map(role => `role/${role}`).includes(page) - ) { - className = "keyword-disabled"; - } + for (const wikiPage of wikiPages) { + const articleType = wikiPage.split("/")[0]; - searchResultsHtml.push( - props.onChooseArticle(page)}/> - ); + if (articleType === "role") { + const role = wikiPage.split("/")[1] as Role; + const roleSet = getMainRoleSetFromRole(role); + + const roleSetPartition = partitions.roleSets.find(p => p.roleSet === roleSet) + if (roleSetPartition) { + roleSetPartition.pages.push(wikiPage); + } else { + partitions.roleSets.push({ roleSet, pages: [wikiPage] }); + } + } else { + const title = getArticleTitle(wikiPage) + const firstLetter = title.length === 0 ? "#" : title[0]; + const letterCategory = /[a-zA-Z]/.test(firstLetter) ? firstLetter : "#"; + + const letterPartition = partitions.standard.find(p => p.letterCategory === letterCategory) + if (letterPartition) { + letterPartition.pages.push(wikiPage); + } else { + partitions.standard.push({ letterCategory, pages: [wikiPage] }); + } } } - + return partitions; +} - return
- {searchResultsHtml} -
+function wikiPageSortFunction(first: WikiArticleLink, second: WikiArticleLink): number { + const firstRole = getRoleFromWikiPage(first); + const secondRole = getRoleFromWikiPage(second); + + if (firstRole && secondRole) { + return getAllRoles().indexOf(firstRole) - getAllRoles().indexOf(secondRole) + } else if (firstRole) { + return -1; + } else if (secondRole) { + return 1; + } else { + return getArticleTitle(first).localeCompare(getArticleTitle(second)); + } +} + +function getRoleFromWikiPage(page: WikiArticleLink): Role | null { + if (page.startsWith('role/')) { + return page.substring(5) as Role; + } else { + return null; + } } function WikiSearchResult(props: { diff --git a/client/src/components/WikiArticle.tsx b/client/src/components/WikiArticle.tsx index f4a4b4c4d..eb22afba9 100644 --- a/client/src/components/WikiArticle.tsx +++ b/client/src/components/WikiArticle.tsx @@ -8,7 +8,7 @@ import ChatElement, { ChatMessageVariant } from "./ChatMessage"; import DUMMY_NAMES from "../resources/dummyNames.json"; import { GeneratedArticle, WikiArticleLink } from "./WikiArticleLink"; import "./wiki.css"; -import { replaceMentions } from ".."; +import GAME_MANAGER, { replaceMentions } from ".."; import { useLobbyOrGameState } from "./useHooks"; function WikiStyledText(props: Omit): ReactElement { @@ -129,42 +129,36 @@ function RoleSetArticle(): ReactElement { state => state.enabledRoles, ["enabledRoles"] ); - - let mainElements = [ -
- {"# "+translate("wiki.article.generated.roleSet.title")} -
- ]; - - for(let set of ROLE_SETS){ - let elements = getRolesFromRoleSet(set).map((role)=>{ - let className = ""; - if(enabledRoles !== undefined && !enabledRoles.includes(role)) { - className = "keyword-disabled"; - } + return
+
+ {"# "+translate("wiki.article.generated.roleSet.title")} +
+ {ROLE_SETS.map(set => { + const description = translateChecked(`${set}.description`); + return <> +

+ {translate(set)} +

+ {description &&

{description}

} + {getRolesFromRoleSet(set).map((role)=>{ + let className = ""; + if(enabledRoles !== undefined && !enabledRoles.includes(role)) { + className = "keyword-disabled"; + } - return - }); - - mainElements.push(
- {"### "+translate(set)} -
); - mainElements.push(
- {elements} -
); - } - mainElements.push( + return + })} + + })} {translate("wiki.article.generated.roleSet.extra", Object.keys(roleJsonData()).length)} - ); - - return
{mainElements}
; +
; } function getSearchStringsGenerated(article: GeneratedArticle): string[]{ diff --git a/client/src/components/wiki.css b/client/src/components/wiki.css index 4444536a9..f187f099a 100644 --- a/client/src/components/wiki.css +++ b/client/src/components/wiki.css @@ -23,6 +23,21 @@ flex: 1 1; padding-bottom: 1rem; } +.wiki-results > .alphabetized-articles { + display: flex; + flex-direction: row; + flex-wrap: wrap; + justify-content: center; + column-gap: 1rem; +} +.wiki-results > .alphabetized-articles > div > .letter { + padding: .13rem .25rem; + margin: .13rem; + background-color: var(--secondary-color); + border: .13rem solid var(--primary-border-color); + border-radius: .5rem; + font-weight: bold; +} .wiki-message-section { display: flex; flex-direction: column; diff --git a/client/src/resources/lang/en_us.json b/client/src/resources/lang/en_us.json index 78f940f8b..4007d1215 100644 --- a/client/src/resources/lang/en_us.json +++ b/client/src/resources/lang/en_us.json @@ -616,6 +616,7 @@ "townCommon":"Town Common", "townCommon:var.0":"TC", + "townCommon.description":"All Town roles except Jailor, Villager, and Drunk.", "townInvestigative":"Town Investigative", "townInvestigative:var.0":"TI", "townProtective":"Town Protective", From 5de9da96d5be68add8cdcd337d861725abd38b60 Mon Sep 17 00:00:00 2001 From: Jack Papel Date: Sun, 22 Dec 2024 00:27:02 -0800 Subject: [PATCH 2/5] Add categories --- .vscode/settings.json | 1 + client/package-lock.json | 32 ++- client/package.json | 4 +- client/src/components/Wiki.tsx | 211 +++++++++++------- client/src/components/WikiArticle.tsx | 122 +++++++--- client/src/components/WikiArticleLink.tsx | 30 ++- client/src/components/WikiCoverCard.tsx | 11 +- .../gameModeSettings/OutlineSelector.tsx | 124 +++++----- client/src/components/wiki.css | 16 +- client/src/game/roleListState.d.tsx | 10 +- client/src/menu/Settings.tsx | 4 +- .../menu/game/gameScreenContent/WikiMenu.tsx | 11 +- client/src/menu/main/StandaloneWiki.tsx | 4 + client/src/resources/keywords.json | 6 + client/src/resources/lang/en_us.json | 47 ++-- 15 files changed, 416 insertions(+), 217 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index d0273e8ef..c98edfa9e 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -45,6 +45,7 @@ "Thanos", "Tidyable", "tungstenite", + "uncategorized", "unclicked", "Unswappable", "Uzumaki", diff --git a/client/package-lock.json b/client/package-lock.json index 8b1f69227..d002fec18 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -22,6 +22,7 @@ "react-dom": "^18.2.0", "react-helmet": "^6.1.0", "react-resizable-panels": "^2.1.6", + "react-responsive-masonry": "^2.6.0", "react-scripts": "^5.0.1", "react-virtuoso": "^4.12.0", "typescript": "^4", @@ -29,7 +30,8 @@ }, "devDependencies": { "@babel/plugin-proposal-private-property-in-object": "7.21.0-placeholder-for-preset-env.2", - "@types/react-helmet": "^6.1.11" + "@types/react-helmet": "^6.1.11", + "@types/react-responsive-masonry": "^2.1.3" } }, "node_modules/@aashutoshrathi/word-wrap": { @@ -4310,6 +4312,15 @@ "@types/react": "*" } }, + "node_modules/@types/react-responsive-masonry": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@types/react-responsive-masonry/-/react-responsive-masonry-2.1.3.tgz", + "integrity": "sha512-aOFUtv3QwNMmy0BgpQpvivQ/+vivMTB6ARrzf9eTSXsLzXpVnfEtjpHpSknYDnr8KaQmlgeauAj8E7wo/qMOTg==", + "dev": true, + "dependencies": { + "@types/react": "*" + } + }, "node_modules/@types/resolve": { "version": "1.17.1", "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.17.1.tgz", @@ -14616,6 +14627,11 @@ "react-dom": "^16.14.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" } }, + "node_modules/react-responsive-masonry": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/react-responsive-masonry/-/react-responsive-masonry-2.6.0.tgz", + "integrity": "sha512-aD1NMUOoRHoL2PT6k4b/+MtH8ZbSLxk8nr6O04HVLXjy7hFRrVXcHNTHscuTtBC70w0hEsJTATHHUxToQsY3PA==" + }, "node_modules/react-scripts": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/react-scripts/-/react-scripts-5.0.1.tgz", @@ -20917,6 +20933,15 @@ "@types/react": "*" } }, + "@types/react-responsive-masonry": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@types/react-responsive-masonry/-/react-responsive-masonry-2.1.3.tgz", + "integrity": "sha512-aOFUtv3QwNMmy0BgpQpvivQ/+vivMTB6ARrzf9eTSXsLzXpVnfEtjpHpSknYDnr8KaQmlgeauAj8E7wo/qMOTg==", + "dev": true, + "requires": { + "@types/react": "*" + } + }, "@types/resolve": { "version": "1.17.1", "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.17.1.tgz", @@ -28338,6 +28363,11 @@ "integrity": "sha512-oIqo/7pp2TsR+Dp1qZMr1l4RBDV4Zz/0HEG5zxliBJoHqqFnG0MbmFbk+5Q1VMGfPQ4uhXxefunLC1o7v38PDQ==", "requires": {} }, + "react-responsive-masonry": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/react-responsive-masonry/-/react-responsive-masonry-2.6.0.tgz", + "integrity": "sha512-aD1NMUOoRHoL2PT6k4b/+MtH8ZbSLxk8nr6O04HVLXjy7hFRrVXcHNTHscuTtBC70w0hEsJTATHHUxToQsY3PA==" + }, "react-scripts": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/react-scripts/-/react-scripts-5.0.1.tgz", diff --git a/client/package.json b/client/package.json index 74fa3140e..19afc49b2 100644 --- a/client/package.json +++ b/client/package.json @@ -17,6 +17,7 @@ "react-dom": "^18.2.0", "react-helmet": "^6.1.0", "react-resizable-panels": "^2.1.6", + "react-responsive-masonry": "^2.6.0", "react-scripts": "^5.0.1", "react-virtuoso": "^4.12.0", "typescript": "^4", @@ -24,7 +25,8 @@ }, "devDependencies": { "@babel/plugin-proposal-private-property-in-object": "7.21.0-placeholder-for-preset-env.2", - "@types/react-helmet": "^6.1.11" + "@types/react-helmet": "^6.1.11", + "@types/react-responsive-masonry": "^2.1.3" }, "scripts": { "start": "react-scripts start", diff --git a/client/src/components/Wiki.tsx b/client/src/components/Wiki.tsx index 990f57716..a68df7689 100644 --- a/client/src/components/Wiki.tsx +++ b/client/src/components/Wiki.tsx @@ -1,10 +1,10 @@ -import React, { ReactElement, useCallback, useEffect, useMemo, useState } from "react"; +import React, { ReactElement, useCallback, useEffect, useMemo, useRef, useState } from "react"; import translate from "../game/lang"; import "./wiki.css"; import { Role, getMainRoleSetFromRole } from "../game/roleState.d"; import GAME_MANAGER, { regEscape } from ".."; -import WikiArticle, { getSearchStrings } from "./WikiArticle"; -import { ARTICLES, WikiArticleLink, getArticleTitle } from "./WikiArticleLink"; +import WikiArticle, { getSearchStrings, PageCollection } from "./WikiArticle"; +import { ARTICLES, WikiArticleLink, getArticleTitle, wikiPageIsEnabled } from "./WikiArticleLink"; import StyledText from "./StyledText"; import Icon from "./Icon"; import { ContentMenu, MenuController } from "../menu/game/GameScreen"; @@ -12,6 +12,8 @@ import { AnchorController } from "../menu/Anchor"; import WikiCoverCard from "./WikiCoverCard"; import { getAllRoles, RoleSet } from "../game/roleListState.d"; import { useLobbyOrGameState } from "./useHooks"; +import { MODIFIERS, ModifierType } from "../game/gameState.d"; +import Masonry from "react-responsive-masonry"; export function setWikiSearchPage(page: WikiArticleLink, anchorController: AnchorController, menuController?: MenuController) { @@ -30,7 +32,8 @@ export function setWikiSearchPage(page: WikiArticleLink, anchorController: Ancho export default function Wiki(props: Readonly<{ - enabledRoles?: Role[], + enabledRoles: Role[], + enabledModifiers: ModifierType[], initialWikiPage?: WikiArticleLink, onPageChange?: (page: WikiArticleLink | null) => void, }>): ReactElement { @@ -89,6 +92,8 @@ export default function Wiki(props: Readonly<{ article === null ? : @@ -121,6 +126,8 @@ function WikiSearchBar(props: { function WikiSearchResults(props: Readonly<{ searchQuery: string, + enabledRoles: Role[], + enabledModifiers: ModifierType[], onChooseArticle: (article: WikiArticleLink) => void }>): ReactElement { const enabledRoles = useLobbyOrGameState( @@ -128,15 +135,18 @@ function WikiSearchResults(props: Readonly<{ ["enabledRoles"], getAllRoles() )!; + const enabledModifiers = useLobbyOrGameState( + gameState => gameState.enabledModifiers, + ["enabledRoles"], + MODIFIERS as any as ModifierType[] + )!; const getSearchResults = useCallback((search: string) => { const out = [ ...ARTICLES.filter((page) => {return RegExp(regEscape(search.trim()), 'i').test(getArticleTitle(page))}), ...ARTICLES.filter((page) => {return getSearchStrings(page).some((str) => RegExp(regEscape(search.trim()), 'i').test(str))}) ]; - return out - .filter((item, index) => out.indexOf(item) === index) - .sort((a, b) => wikiPageSortFunction(a, b)); + return out.filter((item, index) => out.indexOf(item) === index); }, []); const results = useMemo(() => @@ -145,13 +155,16 @@ function WikiSearchResults(props: Readonly<{ return
{props.searchQuery === "" - ? + ? : results.map(page => { let className = undefined; - if( + if(( page.includes("role/") && !enabledRoles.map(role => `role/${role}`).includes(page) - ) { + ) || ( + page.includes("modifier/") && + !enabledModifiers.map(modifier => `modifier/${modifier}`).includes(page) + )) { className = "keyword-disabled"; } @@ -167,112 +180,150 @@ function WikiSearchResults(props: Readonly<{ function WikiMainPage(props: Readonly<{ articles: WikiArticleLink[], enabledRoles: Role[], + enabledModifiers: ModifierType[] onChooseArticle: (article: WikiArticleLink) => void }>): ReactElement { const articlePartitions = useMemo(() => - partitionWikiPages(props.articles), - [props.articles]); - - return <> - {articlePartitions.roleSets.map(roleSetPartition => <> -

- {translate(roleSetPartition.roleSet)} -

- {roleSetPartition.pages.map(page => { - const enabled = props.enabledRoles.map(role => `role/${role}`).includes(page); - return props.onChooseArticle(page)} - />; + partitionWikiPages(props.articles, props.enabledRoles, props.enabledModifiers), + [props.articles, props.enabledRoles, props.enabledModifiers]); + + const ref = useRef(null); + + const [columnCount, setColumnCount] = useState(1); + + useEffect(() => { + const redetermineColumnWidths = () => { + if (ref.current) { + setColumnCount(Math.max(Math.floor(ref.current.clientWidth / 300), 1)) + } + } + + const resizeObserver = new ResizeObserver(redetermineColumnWidths) + + redetermineColumnWidths() + + setTimeout(() => { + resizeObserver.observe(ref.current!); + }) + return resizeObserver.unobserve(ref.current!) + }, [ref]) + + return
+ + {Object.entries(articlePartitions.categories).map(([category, pages]) => { + const enabled = pages.filter((page) => wikiPageIsEnabled(page, props.enabledRoles, props.enabledModifiers)); + const disabled = pages.filter((page) => !enabled.includes(page)); + + return
+ +
})} - )} -

- {translate("standard")} -

-
- {articlePartitions.standard.map(letterPartition =>
- {letterPartition.letterCategory} - {letterPartition.pages.map(page => - props.onChooseArticle(page)}/> - )} -
)} -
- +
+ +
} +export const WIKI_CATEGORIES = ["categories", "town", "mafia", "cult", "neutral", "minions", "fiends", "modifiers"] as const; +export type WikiCategory = (typeof WIKI_CATEGORIES)[number] + type WikiPagePartitions = { - roleSets: { - roleSet: RoleSet, - pages: WikiArticleLink[] - }[], - standard: { - letterCategory: string, - pages: WikiArticleLink[] - }[] + categories: Partial>, + uncategorized: WikiArticleLink[] } -function partitionWikiPages(wikiPages: WikiArticleLink[]): WikiPagePartitions { - const partitions: WikiPagePartitions = { roleSets: [], standard: [] }; +export function partitionWikiPages( + wikiPages: WikiArticleLink[], + enabledRoles: Role[], + enabledModifiers: ModifierType[] +): WikiPagePartitions { + const partitions: WikiPagePartitions = { + categories: Object.fromEntries(WIKI_CATEGORIES.map(a => [a, []])), + uncategorized: [] + }; for (const wikiPage of wikiPages) { const articleType = wikiPage.split("/")[0]; if (articleType === "role") { const role = wikiPage.split("/")[1] as Role; - const roleSet = getMainRoleSetFromRole(role); + const category = getCategoryForRole(role); - const roleSetPartition = partitions.roleSets.find(p => p.roleSet === roleSet) - if (roleSetPartition) { - roleSetPartition.pages.push(wikiPage); + if (partitions.categories[category]) { + partitions.categories[category]!.push(wikiPage) } else { - partitions.roleSets.push({ roleSet, pages: [wikiPage] }); + partitions.categories[category] = [wikiPage]; } - } else { - const title = getArticleTitle(wikiPage) - const firstLetter = title.length === 0 ? "#" : title[0]; - const letterCategory = /[a-zA-Z]/.test(firstLetter) ? firstLetter : "#"; - - const letterPartition = partitions.standard.find(p => p.letterCategory === letterCategory) - if (letterPartition) { - letterPartition.pages.push(wikiPage); + } else if (articleType === "modifier") { + if (partitions.categories["modifiers"]) { + partitions.categories["modifiers"]!.push(wikiPage) + } else { + partitions.categories["modifiers"] = [wikiPage]; + } + } else if (articleType === "category") { + if (partitions.categories["categories"]) { + partitions.categories["categories"]!.push(wikiPage) } else { - partitions.standard.push({ letterCategory, pages: [wikiPage] }); + partitions.categories["categories"] = [wikiPage]; } + } else { + partitions.uncategorized.push(wikiPage); } } + const sortFunction = getWikiPageSortFunction(enabledRoles, enabledModifiers); + + for (const category of Object.keys(partitions.categories) as WikiCategory[]) { + partitions.categories[category]!.sort(sortFunction); + } + + partitions.uncategorized.sort(sortFunction); + return partitions; } -function wikiPageSortFunction(first: WikiArticleLink, second: WikiArticleLink): number { - const firstRole = getRoleFromWikiPage(first); - const secondRole = getRoleFromWikiPage(second); +function getCategoryForRole(role: Role): WikiCategory { + return getMainRoleSetFromRole(role) as WikiCategory; +} - if (firstRole && secondRole) { - return getAllRoles().indexOf(firstRole) - getAllRoles().indexOf(secondRole) - } else if (firstRole) { - return -1; - } else if (secondRole) { - return 1; - } else { - return getArticleTitle(first).localeCompare(getArticleTitle(second)); - } +function getWikiPageSortFunction( + enabledRole: Role[], + enabledModifiers: ModifierType[] +): (first: WikiArticleLink, second: WikiArticleLink) => number { + return (first, second) => wikiPageSortFunction(first, second, enabledRole, enabledModifiers) } -function getRoleFromWikiPage(page: WikiArticleLink): Role | null { - if (page.startsWith('role/')) { - return page.substring(5) as Role; +function wikiPageSortFunction( + first: WikiArticleLink, + second: WikiArticleLink, + enabledRoles: Role[], + enabledModifiers: ModifierType[] +): number { + const isPageEnabled = (page: WikiArticleLink) => wikiPageIsEnabled(page, enabledRoles, enabledModifiers); + + if (isPageEnabled(first) && !isPageEnabled(second)) { + return -1; + } else if (!isPageEnabled(first) && isPageEnabled(second)) { + return 1 } else { - return null; + return getArticleTitle(first).localeCompare(getArticleTitle(second)) } } -function WikiSearchResult(props: { +function WikiSearchResult(props: Readonly<{ page: WikiArticleLink, className?: string, onChooseArticle: (article: WikiArticleLink) => void -}): ReactElement { +}>): ReactElement { return + })} + +} + function GeneratedArticleElement(props: Readonly<{ article: GeneratedArticle }>): ReactElement { switch(props.article){ @@ -127,8 +187,9 @@ function GeneratedArticleElement(props: Readonly<{ article: GeneratedArticle }>) function RoleSetArticle(): ReactElement { const enabledRoles = useLobbyOrGameState( state => state.enabledRoles, - ["enabledRoles"] - ); + ["enabledRoles"], + getAllRoles() + )!; return
@@ -136,24 +197,15 @@ function RoleSetArticle(): ReactElement {
{ROLE_SETS.map(set => { const description = translateChecked(`${set}.description`); - return <> -

- {translate(set)} -

+ return `role/${role}` as WikiArticleLink)} + enabledRoles={enabledRoles} + enabledModifiers={[]} + > {description &&

{description}

} - {getRolesFromRoleSet(set).map((role)=>{ - let className = ""; - if(enabledRoles !== undefined && !enabledRoles.includes(role)) { - className = "keyword-disabled"; - } - - return - })} - +
})} {translate("wiki.article.generated.roleSet.extra", Object.keys(roleJsonData()).length)} @@ -163,12 +215,13 @@ function RoleSetArticle(): ReactElement { function getSearchStringsGenerated(article: GeneratedArticle): string[]{ switch(article){ - case "roleSet": + case "roleSet": { let out = [translate("wiki.article.generated.roleSet.title")]; for(let set of ROLE_SETS){ out.push(translate(set)); } return out; + } case "all_text": return []; } @@ -178,8 +231,7 @@ export function getSearchStrings(article: WikiArticleLink): string[]{ const path = article.split('/'); switch (path[0]) { - case "role": - + case "role": { const role = path[1] as Role; const roleData = roleJsonData()[role]; let out = []; @@ -211,15 +263,17 @@ export function getSearchStrings(article: WikiArticleLink): string[]{ out.push(translate("wiki.article.standard.roleLimit.title")); return out; - - case "standard": + } + case "modifiers": + case "standard": { return [ - translate(`wiki.article.standard.${path[1]}.title`), - translate(`wiki.article.standard.${path[1]}.text`), + translate(`wiki.article.${path[0]}.${path[1]}.title`), + translate(`wiki.article.${path[0]}.${path[1]}.text`), ] + } case "generated": return getSearchStringsGenerated(path[1] as GeneratedArticle); - default: + default: // Categories don't show up in search results return []; } } \ No newline at end of file diff --git a/client/src/components/WikiArticleLink.tsx b/client/src/components/WikiArticleLink.tsx index 150fdd8bc..9e848ac62 100644 --- a/client/src/components/WikiArticleLink.tsx +++ b/client/src/components/WikiArticleLink.tsx @@ -1,9 +1,14 @@ +import { MODIFIERS, ModifierType } from "../game/gameState.d"; import translate, { langJson } from "../game/lang"; -import { Role, roleJsonData } from "../game/roleState.d"; +import { Role } from "../game/roleState.d"; +import { WIKI_CATEGORIES, WikiCategory } from "./Wiki"; import "./wiki.css"; +import { getAllRoles } from "../game/roleListState.d" export type WikiArticleLink = `role/${Role}` | + `modifier/${ModifierType}` | + `category/${WikiCategory}` | `standard/${StandardArticle}` | `generated/${GeneratedArticle}`; @@ -16,7 +21,9 @@ const GENERATED_ARTICLES = ["roleSet", "all_text"] as const; export type GeneratedArticle = typeof GENERATED_ARTICLES[number]; export const ARTICLES: WikiArticleLink[] = - Object.keys(roleJsonData()).map(role => `role/${role}`) + WIKI_CATEGORIES.map(category => `category/${category}`) + .concat(getAllRoles().map(role => `role/${role}`)) + .concat(MODIFIERS.map(modifier => `modifier/${modifier}`)) .concat(STANDARD_ARTICLES.map(article => `standard/${article}`)) .concat(GENERATED_ARTICLES.map(article => `generated/${article}`)) as WikiArticleLink[]; @@ -28,6 +35,10 @@ export function getArticleLangKey(page: WikiArticleLink): string { switch (path[0]) { case "role": return `role.${path[1]}.name`; + case "modifier": + return `wiki.article.modifier.${path[1]}.title`; + case "category": + return `wiki.category.${path[1]}`; case "standard": return `wiki.article.standard.${path[1]}.title`; case "generated": @@ -40,4 +51,19 @@ export function getArticleLangKey(page: WikiArticleLink): string { export function getArticleTitle(page: WikiArticleLink): string { return translate(getArticleLangKey(page)); +} + +export function wikiPageIsEnabled( + page: WikiArticleLink, + enabledRoles: Role[], + enabledModifiers: ModifierType[] +): boolean { + switch (page.split("/")[0]) { + case "role": + return enabledRoles.map(role => `role/${role}`).includes(page) + case "modifiers": + return enabledModifiers.map(modifier => `modifier/${modifier}`).includes(page) + default: + return true; + } } \ No newline at end of file diff --git a/client/src/components/WikiCoverCard.tsx b/client/src/components/WikiCoverCard.tsx index 893047747..355e4d0e5 100644 --- a/client/src/components/WikiCoverCard.tsx +++ b/client/src/components/WikiCoverCard.tsx @@ -4,6 +4,8 @@ import Wiki from '../components/Wiki'; import "./wiki.css"; import { WikiArticleLink } from './WikiArticleLink'; import { useLobbyOrGameState } from './useHooks'; +import { MODIFIERS, ModifierType } from '../game/gameState.d'; +import { getAllRoles } from '../game/roleListState.d'; export default function WikiCoverCard(props: Readonly<{ initialWikiPage?: WikiArticleLink @@ -11,11 +13,16 @@ export default function WikiCoverCard(props: Readonly<{ const enabledRoles = useLobbyOrGameState( state => state.enabledRoles, ["enabledRoles"], - [] + getAllRoles() + )!; + const enabledModifiers = useLobbyOrGameState( + state => state.enabledModifiers, + ["enabledModifiers"], + MODIFIERS as any as ModifierType[] )!; return

{translate("menu.wiki.title")}

- +
; } diff --git a/client/src/components/gameModeSettings/OutlineSelector.tsx b/client/src/components/gameModeSettings/OutlineSelector.tsx index e2362e135..3d20e2262 100644 --- a/client/src/components/gameModeSettings/OutlineSelector.tsx +++ b/client/src/components/gameModeSettings/OutlineSelector.tsx @@ -1,13 +1,14 @@ -import React, { useContext } from "react"; +import React, { ReactElement, useCallback, useContext } from "react"; import "./outlineSelector.css"; import translate from "../../game/lang"; -import { ROLE_SETS, RoleList, RoleOutline, RoleOutlineOption, simplifyRoleOutline, translateRoleOutlineOption} from "../../game/roleListState.d"; -import { Role, roleJsonData } from "../../game/roleState.d"; +import { getAllRoles, getRolesFromRoleSet, ROLE_SETS, RoleList, RoleOutline, RoleOutlineOption, simplifyRoleOutline, translateRoleOutlineOption} from "../../game/roleListState.d"; +import { Role } from "../../game/roleState.d"; import Icon from "../Icon"; import { DragAndDrop } from "../DragAndDrop"; import { GameModeContext } from "./GameModesEditor"; import Select, { SelectOptionsSearch } from "../Select"; import StyledText from "../StyledText"; +import { useLobbyOrGameState } from "../useHooks"; type RoleOutlineSelectorProps = { roleOutline: RoleOutline, @@ -113,7 +114,13 @@ export default class RoleOutlineSelector extends React.Component void, -}) - -function translateRoleOutlineOptionOrAny(roleOutlineOption: RoleOutlineOption | "any"): string { - if(roleOutlineOption === "any") { - return translate("any"); - }else - return translateRoleOutlineOption(roleOutlineOption); -} -export class RoleOutlineOptionSelector extends React.Component { - - - render(): React.ReactNode { - - - const optionsSearch: SelectOptionsSearch = new Map(); - - optionsSearch.set("any", [ +})>): ReactElement { + const enabledRoles = useLobbyOrGameState( + state => state.enabledRoles, + ["enabledRoles"], + getAllRoles() + )!; + + const isRoleEnabled = useCallback((role: Role) => { + return enabledRoles.includes(role) + }, [enabledRoles]) + + const optionsSearch: SelectOptionsSearch = new Map(); + + optionsSearch.set("any", [ + + {translate("any")} + , + translate("any") + ]); + + ROLE_SETS.forEach((roleSet) => { + optionsSearch.set(JSON.stringify({type: "roleSet", roleSet: roleSet}), [ !isRoleEnabled(role)) ? "keyword-disabled" : ""} > - {translate("any")} + {translateRoleOutlineOptionOrAny({type: "roleSet", roleSet: roleSet})} , - translate("any") + translateRoleOutlineOptionOrAny({type: "roleSet", roleSet: roleSet})] + ); + }); + + getAllRoles().forEach((role) => { + optionsSearch.set(JSON.stringify({type: "role", role: role}), [ + + {translateRoleOutlineOptionOrAny({type: "role", role})} + , + translateRoleOutlineOptionOrAny({type: "role", role}) ]); - - ROLE_SETS.forEach((roleSet) => { - optionsSearch.set(JSON.stringify({type: "roleSet", roleSet: roleSet}), [ - - {translateRoleOutlineOptionOrAny({type: "roleSet", roleSet: roleSet})} - , - translateRoleOutlineOptionOrAny({type: "roleSet", roleSet: roleSet})] + }); + + return { - this.props.onChange( - value === "any" ? "any" : JSON.parse(value) - ); - }} - optionsSearch={optionsSearch} - /> - } + }} + optionsSearch={optionsSearch} + /> } export function OutlineListSelector(props: { diff --git a/client/src/components/wiki.css b/client/src/components/wiki.css index bf01f2e0c..43542f720 100644 --- a/client/src/components/wiki.css +++ b/client/src/components/wiki.css @@ -23,20 +23,8 @@ flex: 1 1; padding-bottom: 1rem; } -.wiki-results > .alphabetized-articles { - display: flex; - flex-direction: row; - flex-wrap: wrap; - justify-content: center; - column-gap: 1rem; -} -.wiki-results > .alphabetized-articles > div > .letter { - padding: .13rem .25rem; - margin: .13rem; - background-color: var(--secondary-color); - border: .13rem solid var(--primary-border-color); - border-radius: .5rem; - font-weight: bold; +.wiki-results .category-pages { + width: 100%; } .wiki-message-section { display: flex; diff --git a/client/src/game/roleListState.d.tsx b/client/src/game/roleListState.d.tsx index 61e30d99e..a525f3120 100644 --- a/client/src/game/roleListState.d.tsx +++ b/client/src/game/roleListState.d.tsx @@ -16,8 +16,7 @@ export function getRolesFromRoleList(roleList: RoleList): Role[] { } export function getRolesComplement(roleList: Role[]): Role[] { - let roles = Object.keys(roleJsonData()) as Role[]; - return roles.filter((role) => { + return getAllRoles().filter((role) => { return !roleList.includes(role); }); } @@ -85,7 +84,7 @@ export function translateRoleOutlineOption(roleOutlineOption: RoleOutlineOption) export function getRolesFromOutline(roleOutline: RoleOutline): Role[] { switch(roleOutline.type){ case "any": - return Object.keys(roleJsonData()) as Role[]; + return getAllRoles(); case "roleOutlineOptions": return roleOutline.options.flatMap((option) => getRolesFromOutlineOption(option)); } @@ -130,5 +129,8 @@ function outlineOptionCompare(optionA: RoleOutlineOption, optionB: RoleOutlineOp } export function getAllRoles(): Role[] { - return Object.keys(roleJsonData()) as Role[]; + return Object.entries(roleJsonData()) + .sort((a, b) => a[0].localeCompare(b[0])) + .sort((a, b) => ROLE_SETS.indexOf(a[1].mainRoleSet) - ROLE_SETS.indexOf(b[1].mainRoleSet)) + .map((a) => a[0]) as Role[]; } \ No newline at end of file diff --git a/client/src/menu/Settings.tsx b/client/src/menu/Settings.tsx index a0be87de8..c27d1fe68 100644 --- a/client/src/menu/Settings.tsx +++ b/client/src/menu/Settings.tsx @@ -5,7 +5,7 @@ import StyledText, { computeKeywordData } from "../components/StyledText"; import Icon from "../components/Icon"; import { loadSettingsParsed, RoleSpecificMenuType, saveSettings } from "../game/localStorage"; import { MobileContext, AnchorControllerContext, ANCHOR_CONTROLLER } from "./Anchor"; -import { Role, roleJsonData } from "../game/roleState.d"; +import { Role } from "../game/roleState.d"; import AudioController from "./AudioController"; import { getAllRoles } from "../game/roleListState.d"; import CheckBox from "../components/CheckBox"; @@ -113,7 +113,7 @@ export default function SettingsMenu(): ReactElement { {translate("menu.settings.roleSpecificMenus")} { - Object.entries(roleJsonData()).map(([role, roleJsonData]) => { + getAllRoles().map(role => { // const roleSpecificMenuExists = type.roleSpecificMenu; const menuType: RoleSpecificMenuType = roleSpecificMenuSettings.includes(role as Role) ? "standalone" : "playerList"; diff --git a/client/src/menu/game/gameScreenContent/WikiMenu.tsx b/client/src/menu/game/gameScreenContent/WikiMenu.tsx index 0f538b269..00d6e29b7 100644 --- a/client/src/menu/game/gameScreenContent/WikiMenu.tsx +++ b/client/src/menu/game/gameScreenContent/WikiMenu.tsx @@ -4,19 +4,26 @@ import "./wikiMenu.css" import translate from "../../../game/lang"; import Wiki from "../../../components/Wiki"; import { useLobbyOrGameState } from "../../../components/useHooks"; +import { getAllRoles } from "../../../game/roleListState.d"; +import { MODIFIERS, ModifierType } from "../../../game/gameState.d"; export default function WikiMenu(): ReactElement { const enabledRoles = useLobbyOrGameState( state => state.enabledRoles, ["enabledRoles"], - [] + getAllRoles() + )!; + const enabledModifiers = useLobbyOrGameState( + state => state.enabledModifiers, + ["enabledModifiers"], + MODIFIERS as any as ModifierType[] )!; return
{translate("menu.wiki.title")}
- +
} \ No newline at end of file diff --git a/client/src/menu/main/StandaloneWiki.tsx b/client/src/menu/main/StandaloneWiki.tsx index 79312c5a4..51aff11f8 100644 --- a/client/src/menu/main/StandaloneWiki.tsx +++ b/client/src/menu/main/StandaloneWiki.tsx @@ -3,6 +3,8 @@ import Wiki from "../../components/Wiki"; import translate from "../../game/lang"; import "./standaloneWiki.css"; import { WikiArticleLink } from "../../components/WikiArticleLink"; +import { MODIFIERS, ModifierType } from "../../game/gameState.d"; +import { getAllRoles } from "../../game/roleListState.d"; export default function StandaloneWiki(props: Readonly<{ initialWikiPage?: WikiArticleLink @@ -13,6 +15,8 @@ export default function StandaloneWiki(props: Readonly<{
{ if (page !== null) { diff --git a/client/src/resources/keywords.json b/client/src/resources/keywords.json index 7d0ebec21..ccbd32403 100644 --- a/client/src/resources/keywords.json +++ b/client/src/resources/keywords.json @@ -36,6 +36,12 @@ "wiki.article.role.attributes": { "style": "keyword-info"}, "wiki.article.role.extra": { "style": "keyword-info"}, "wiki.article.role.guide": { "style": "keyword-info"}, + "wiki.category.town": { "style": "keyword-good", "link": "category/town" }, + "wiki.category.mafia": { "style": "keyword-evil", "link": "category/mafia" }, + "wiki.category.fiends": { "style": "keyword-fiends", "link": "category/fiends" }, + "wiki.category.minions": { "style": "keyword-minions", "link": "category/minions" }, + "wiki.category.cult": { "style": "keyword-cult", "link": "category/cult" }, + "wiki.category.neutral": { "style": "keyword-neutral", "link": "category/neutral" }, "grave.deathCause.execution": { "style": "keyword-info"}, "grave.deathCause.leftTown": { "style": "keyword-info"}, "grave.killer.suicide": { "style": "keyword-info"}, diff --git a/client/src/resources/lang/en_us.json b/client/src/resources/lang/en_us.json index dd60d54ff..b51fcf9b6 100644 --- a/client/src/resources/lang/en_us.json +++ b/client/src/resources/lang/en_us.json @@ -636,7 +636,7 @@ "controllerId.forfeitVote.name": "Forfeit Nomination Vote", - "standard":"Standard", + "standard":"Uncategorized", "union": "∪", "onTrial": "On Trial", @@ -903,6 +903,23 @@ "kiraResult.wrongSpot": "Somebody has this role, but not this player", "kiraResult.notInGame": "Nobody has this role", + "wiki.category.categories": "Categories", + "wiki.category.categories.text": "These are the categories used to help organize the encyclopedia", + "wiki.category.town": "Town roles", + "wiki.category.town.text": "These are the roles that tend to be town loyalists - that is, by default all of these roles win with the town.", + "wiki.category.mafia": "Syndicate roles", + "wiki.category.mafia.text": "These are the roles that tend to be syndicate loyalists - that is, by default all of these roles win with the syndicate.", + "wiki.category.neutral": "Neutral roles", + "wiki.category.neutral.text": "These roles tend to win irrespective of any particular game conclusion. For example, the Jester wins when it dies.", + "wiki.category.minions": "Minion roles", + "wiki.category.minions.text": "These are the roles that tend to win as long as town doesn't win.", + "wiki.category.cult": "Cult roles", + "wiki.category.cult.text": "These are the roles that tend to be cult loyalists - that is, by default all of these roles win with the cult.", + "wiki.category.fiends": "Fiend roles", + "wiki.category.fiends.text": "These are the roles that tend to be fiends loyalists - that is, by default all of these roles win with the fiends.", + "wiki.category.modifiers": "Modifiers", + "wiki.category.modifiers.text": "These are options that can be enabled which change the behavior of the game.", + "wiki.article.standard.town.title": "Town", "wiki.article.standard.town.text": "Town is the main good faction. Usually, the majority of the players in each game are town loyalists. They don't start knowing who each other are, unlike the syndicate or the cult. Town wins by eliminating all opposing teams (syndicate, cult, arsonist, werewolf, etc). The townies need to work together to find out who the evils are. Their main power is in communication and voting, so if you are a townie who does not communicate, you are not helping your team win.", "wiki.article.standard.mafia.title": "Syndicate", @@ -1165,22 +1182,18 @@ "wiki.article.standard.gameMode.text":"The game mode menu lets you keep track of the game settings during the game.\n- Outline list\n- Enabled Roles\n- Enabled Modifiers", - "wiki.article.standard.modifiers.title":"Modifiers", - "wiki.article.standard.modifiers.title:var.0":"Modifier", - "wiki.article.standard.modifiers.text":"A game modifier is a setting set before the game that changes the game somehow. Some examples are the obscured graves modifier and the random love links modifier.", - - "wiki.article.standard.obscuredGraves.title":"Obscured Graves", - "wiki.article.standard.obscuredGraves.text":"Obscured Graves is a game modifier that makes it so all graves are obscured.", - "wiki.article.standard.randomLoveLinks.title":"Random Love Links", - "wiki.article.standard.randomLoveLinks.text":"Random Love Links is a game modifier that makes it so all players are love linked to a random player. The game is set up such that all players are love linked to at least 1 other player, and at most 1 player is love linked to more than 1 other player.", - "wiki.article.standard.deadCanChat.title":"Dead Can Chat", - "wiki.article.standard.deadCanChat.text":"Dead Can Chat is a game modifier that changes the game such that dead players can still use chats as if they were alive. Dead players can not whisper or be whispered to.", - "wiki.article.standard.noAbstaining.title":"No Abstaining", - "wiki.article.standard.noAbstaining.text":"No Abstaining is a game modifier that changes the game such that no players can abstain during judgement. It will default to voting innocent instead of defaulting to abstaining.", - "wiki.article.standard.noDeathCause.title":"No Death Cause", - "wiki.article.standard.noDeathCause.text":"No Death Cause is a game modifier that changes the game such that every grave does not have a death cause listed.", - "wiki.article.standard.roleSetGraveKillers.title":"Role Set Grave Killers", - "wiki.article.standard.roleSetGraveKillers.text":"Role Set Grave Killers is a game modifier that changes the game such that every grave killer which is a role is replaced with one of:\n* Syndicate;\n* Town;\n* Minions;\n* Neutral;\n* Fiends; or\n* Cult.\n\nOr, if the killer's role is not in one of these role sets, it is not changed.", + "wiki.article.modifier.obscuredGraves.title":"Obscured Graves", + "wiki.article.modifier.obscuredGraves.text":"Obscured Graves is a game modifier that makes it so all graves are obscured.", + "wiki.article.modifier.randomLoveLinks.title":"Random Love Links", + "wiki.article.modifier.randomLoveLinks.text":"Random Love Links is a game modifier that makes it so all players are love linked to a random player. The game is set up such that all players are love linked to at least 1 other player, and at most 1 player is love linked to more than 1 other player.", + "wiki.article.modifier.deadCanChat.title":"Dead Can Chat", + "wiki.article.modifier.deadCanChat.text":"Dead Can Chat is a game modifier that changes the game such that dead players can still use chats as if they were alive. Dead players can not whisper or be whispered to.", + "wiki.article.modifier.noAbstaining.title":"No Abstaining", + "wiki.article.modifier.noAbstaining.text":"No Abstaining is a game modifier that changes the game such that no players can abstain during judgement. It will default to voting innocent instead of defaulting to abstaining.", + "wiki.article.modifier.noDeathCause.title":"No Death Cause", + "wiki.article.modifier.noDeathCause.text":"No Death Cause is a game modifier that changes the game such that every grave does not have a death cause listed.", + "wiki.article.modifier.roleSetGraveKillers.title":"Role Set Grave Killers", + "wiki.article.modifier.roleSetGraveKillers.text":"Role Set Grave Killers is a game modifier that changes the game such that every grave killer which is a role is replaced with one of:\n* Syndicate;\n* Town;\n* Minions;\n* Neutral;\n* Fiends; or\n* Cult.\n\nOr, if the killer's role is not in one of these role sets, it is not changed.", "wiki.article.standard.syndicateGunItem.title":"Syndicate Gun", "wiki.article.standard.syndicateGunItem.text":"Syndicate Gun is an ability a syndicate member gets when theres no syndicate killing role present\n- A syndicate member is given a gun they can use to kill a player with a basic attack at night\n- The gun can't be used on the first night\n- The gun can be passed around between syndicate members, so a different syndicate member can shoot it every night\n- The gun still uses visits, so getting roleblocked still erases that visit, and transports still move it\n- The killer appears to be syndicate on the grave of the player who died to the gun", "syndicateGunItem.description":"Choose a player to shoot with the syndicate gun. Or choose to give it to another syndicate insider.", From a74d2e7ded3209ebde43f32bb3e84667d5f4466e Mon Sep 17 00:00:00 2001 From: Jack Papel Date: Sun, 22 Dec 2024 00:34:26 -0800 Subject: [PATCH 3/5] Fix build --- client/src/components/Wiki.tsx | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/client/src/components/Wiki.tsx b/client/src/components/Wiki.tsx index a68df7689..77cdaf694 100644 --- a/client/src/components/Wiki.tsx +++ b/client/src/components/Wiki.tsx @@ -10,7 +10,7 @@ import Icon from "./Icon"; import { ContentMenu, MenuController } from "../menu/game/GameScreen"; import { AnchorController } from "../menu/Anchor"; import WikiCoverCard from "./WikiCoverCard"; -import { getAllRoles, RoleSet } from "../game/roleListState.d"; +import { getAllRoles } from "../game/roleListState.d"; import { useLobbyOrGameState } from "./useHooks"; import { MODIFIERS, ModifierType } from "../game/gameState.d"; import Masonry from "react-responsive-masonry"; @@ -102,12 +102,12 @@ export default function Wiki(props: Readonly<{
} -function WikiSearchBar(props: { +function WikiSearchBar(props: Readonly<{ searchQuery: string, onSearchChange: (search: string) => void, onBack: () => void, onClear: () => void, -}): ReactElement { +}>): ReactElement { return