Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
e591efe
Add unique ID to app main container div for DOM portalling
nikolay-panovski Jan 15, 2024
e87ce44
Add static character card screen with sample character
nikolay-panovski Jan 15, 2024
4b6dbb8
Downgrade Typescript??
nikolay-panovski Jan 15, 2024
0a37b68
Rename character card init method
nikolay-panovski Jan 15, 2024
bd18008
Add notepad icon button
nikolay-panovski Jan 15, 2024
a7660f8
Remove redundant code after custom games refactor from character card
nikolay-panovski Jan 16, 2024
a114942
Add character card to custom games map (not entire murder mystery)
nikolay-panovski Jan 16, 2024
90fef62
Enable meaningful position: fixed/absolute for child elements
nikolay-panovski Jan 16, 2024
c097ac1
Shorten clickable area of back button within games
nikolay-panovski Jan 16, 2024
41522df
Add notepad component
nikolay-panovski Jan 16, 2024
5a8a0ce
Reposition character card on screen + drill links/buttons
nikolay-panovski Jan 16, 2024
9e4b683
Make "save notes" button + style text area
nikolay-panovski Jan 17, 2024
ce17cb5
Set max length for notes text area
nikolay-panovski Jan 17, 2024
754c36d
Rename character files
nikolay-panovski Jan 18, 2024
66ea5a9
Invalidate only auth settings from localStorage on logout
nikolay-panovski Jan 19, 2024
331e745
Fetch and save predefined notes from solved Toto elements
nikolay-panovski Jan 19, 2024
ce208b9
Make background image scale instead of tile on bigger resolutions
nikolay-panovski Jan 22, 2024
f10a670
Prevent empty page bottom space from excessive div
nikolay-panovski Jan 22, 2024
3c7532d
Replace all plain text with translation variables
nikolay-panovski Jan 22, 2024
e0c4df6
Move murder mystery components near other custom game components
nikolay-panovski Jan 22, 2024
40025b5
Turn background image into HTML img
nikolay-panovski Jan 22, 2024
cc327ce
Replace "upgrade account" plain text in settings
nikolay-panovski Jan 22, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 7 additions & 7 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Binary file added public/MurderMystery/MM_CaseFile_Amelie.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion src/App.js
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ function App() {
<div className='h-[100%] w-screen flex flex-col overflow-hidden'>
<SettingsContext.Provider value={[settings, setSettings]}>
<InstallBar />
<div className='flex-1 flex overflow-auto'>
<div id='main-container' className='relative flex-1 flex overflow-auto'>
{settings.auth === null && !location.pathname.includes("/auth") ? (<Navigate to="/auth" />) : <></>}
<Routes>
<Route path='/' element={<HomeScreen />} />
Expand Down
3 changes: 3 additions & 0 deletions src/components/game/CustomGamesMap.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,14 @@ import ScrMult from "./custom/ScrMult";
import LanguageGame from "./custom/LanguageGame";
import DefaultGame from "./custom/Default";

import CharacterCard from "./custom/MurderMystery/CharacterCard";

const map = new Map();

map.set("default", DefaultGame);
map.set("scrmult", ScrMult);
map.set("language", LanguageGame);
map.set("murdermystery_cc", CharacterCard);


export default map;
9 changes: 7 additions & 2 deletions src/components/game/Game.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
import React, { useEffect, useState } from 'react'
import React, { useEffect, useState, useContext } from 'react'
import { CUSTOM_GAME_REGEX } from '../../constants';
import SkeletonLoader from "./SkeletonLoader";
import { acknowledge, getSessionInfo } from "../../services/totoSessionService";
import CustomGamesMap from "./CustomGamesMap";

import SettingsContext from "services/SettingsContext";


function Game({ elementId, sessionId }) {
const [settings] = useContext(SettingsContext);
const translations = settings.translations[settings.language];

const [showSkeletonLoader, setShowSkeletonLoader] = useState(true);
const [updateKey, setUpdateKey] = useState(Math.random());
const [error, setError] = useState(false);
Expand Down Expand Up @@ -63,7 +68,7 @@ function Game({ elementId, sessionId }) {
{showSkeletonLoader && <SkeletonLoader />}

{error && <div className="m-5 h-full bg-red-400 border border-red-700 rounded-xl">
<span>The game was not able to load.</span>
<span>{translations.GAME_LOAD_ERROR_GENERIC}</span>
</div>}

{!showSkeletonLoader && !error && <div className="flex flex-col justify-between p-10 h-full">
Expand Down
56 changes: 56 additions & 0 deletions src/components/game/custom/MurderMystery/CharacterCard.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { useState, useContext } from "react";
import { createPortal } from "react-dom";
import { useParams, Link } from "react-router-dom";

import ReactMarkdown from "react-markdown";
import remarkGfm from 'remark-gfm'
import { PiNotepadFill } from "react-icons/pi";
import { MdArrowBackIos } from "react-icons/md";
import { FaWindowClose } from "react-icons/fa";
import SettingsContext from "services/SettingsContext";

import MMNotepad from "components/game/custom/MurderMystery/MMNotepad";

function CharacterCard({ markdown }) {
const [settings] = useContext(SettingsContext);
const translations = settings.translations[settings.language];

const { elementId, sessionId } = useParams();

const [showNotepad, setShowNotepad] = useState(false);

return (<>
{!showNotepad && <>
{/* TODO: Randomize/appropriate backgroundImage per character */}
<div className="absolute inset-0 w-full h-full" >
<img className="absolute top-0 left-0 w-full h-full" src="/MurderMystery/MM_CaseFile_Amelie.png" alt="Case file background with character" />
{/* Link drilled in the component (it should be identical to the global back button for every game - the real one is buried below the div and image here) */}
<Link to={`/quest/${sessionId}`} className={`absolute flex flex-row items-center m-5 mb-0 pr-10 self-start`} >
<MdArrowBackIos />
<span>{translations.BACK_BUTTON}</span>
</Link>

<ReactMarkdown className="relative top-[40%] h-[50%] mt-6 px-4" remarkPlugins={[remarkGfm]} children={markdown}/>

<button className="absolute top-4 right-4" onClick={() => { setShowNotepad(true) } }>
<PiNotepadFill className="w-8 h-8" />
</button>
</div>
</>
}

{showNotepad && <>
<MMNotepad markdown={markdown} />

{/* Button to toggle notepad, element being independent from the actual other content. Caveat: Notepad save operations get VERY coupled with this component. */}
<button className="absolute top-4 right-4" onClick={ () => { setShowNotepad(false) } }>
<FaWindowClose className="w-8 h-8" />
</button>
</>
}

</>);
}


export default CharacterCard;
111 changes: 111 additions & 0 deletions src/components/game/custom/MurderMystery/MMNotepad.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
import { useEffect, useState, useContext } from "react";
import { useParams, Link } from "react-router-dom";

import { MdArrowBackIos, MdArrowForwardIos } from "react-icons/md";
import { FaSave } from "react-icons/fa";
import SettingsContext from "services/SettingsContext";

import { MAX_NOTEPAD_CHARS, TOTO_VAR_MMYSTERY_IDS } from "constants";
import { getSessionInfo } from "services/totoSessionService";

function MMNotepad({ markdown }) {
const [settings] = useContext(SettingsContext);
const translations = settings.translations[settings.language];

const [predefinedNotes, setPredefinedNotes] = useState([]);
const [predefinedNoteIndex, setPredefinedNoteIndex] = useState(0);

const { elementId, sessionId } = useParams();

function savePredefinedNotes() {
// better?: store in Toto variable instead (we do not have the permissions to do that)
window.localStorage.setItem("mm_notes_predefined", predefinedNotes);
}

function saveUserNotes() {
// seems to be sanitized automatically by React
window.localStorage.setItem("mm_notes_user", document.getElementById("freenotes").value);
}

async function setPredefinedNotesFromIds() {
const sessionInfo = await getSessionInfo(sessionId);
// the variable name is hardcoded here because there exists no more reasonable way to locate the info storage for this code
const notesIds = sessionInfo.variables.find(variable => variable.name === TOTO_VAR_MMYSTERY_IDS);
if (!notesIds) return; // silent return - bad for error handling, but necessary to handle not having any notes yet
const notesIdsArray = notesIds.value.split(",");

const updatedNotes = new Array();
// -1 because if every trigger in Toto follows the same generic pattern of 'var = var + "id,"', there will be an excess element caused by the last comma.
// as an alternative to this, check for each element if it is an empty string instead.
for (let i = 0; i < notesIdsArray.length - 1; i++) {
const noteText = sessionInfo.elements.find(element => element.elementId === notesIdsArray[i]).processed.postDescription;
console.log(updatedNotes);
updatedNotes.push(noteText);
}
setPredefinedNotes(updatedNotes);
}

useEffect(() => {
setPredefinedNotesFromIds();

document.getElementById("freenotes").value = window.localStorage.getItem("mm_notes_user");
}, []);

useEffect(() => {
savePredefinedNotes();
}, [predefinedNotes]);

return (
<>
{/* if someone can make this positioning CSS less hardcoded (and awful), please do it */}
<div className="absolute inset-0 w-full h-full" >
{/* Link drilled in the component (it should be identical to the global back button for every game - the real one is buried below the div and image here) */}
<img className="absolute top-0 left-0 w-full h-full" src="/MurderMystery/MM_CaseFile_Amelie.png" alt="Case file background with character" />
<Link to={`/quest/${sessionId}`} className={`absolute flex flex-row items-center m-5 mb-0 pr-10 self-start`} >
<MdArrowBackIos />
<span>{translations.BACK_BUTTON}</span>
</Link>

<div className="fixed top-[40%] h-[60%] mt-8 inset-x-4">
<div className="relative h-[40%]">
<p>{translations.MM_FOUND_NOTES_LABEL}</p>
<br />
<div className="flex">
{predefinedNotes && predefinedNotes.length > 0 ? <>
<button className="" onClick={ () =>
setPredefinedNoteIndex(predefinedNoteIndex === 0 ? predefinedNotes.length - 1 : predefinedNoteIndex - 1) }>
<MdArrowBackIos className="w-8 h-8" />
</button>
<p id="foundnotes" className="basis-[80%]">{predefinedNotes.length > 0 ? predefinedNotes[predefinedNoteIndex] : ""}</p>
<button className="" onClick={ () =>
setPredefinedNoteIndex(predefinedNoteIndex === predefinedNotes.length - 1 ? 0 : predefinedNoteIndex + 1) }>
<MdArrowForwardIos className="w-8 h-8" />
</button>
</>
:
<p className="justify-center self-center">{translations.MM_FOUND_NOTES_NONE}</p>
}
</div>
</div>

<div className="relative h-[50%]">
<label htmlFor="freenotes">{translations.MM_USER_NOTES_LABEL}</label>
<br />
{/* !! maxLength not displayed to the users !! */}
<textarea id="freenotes"
className="resize-none w-full h-[80%]"
style={{backgroundColor:"rgba(235,160,160,0.33)"}}
maxLength={MAX_NOTEPAD_CHARS}></textarea>
</div>
</div>
</div>

<button className="absolute top-4 right-24" onClick={ saveUserNotes }>
<FaSave className="w-8 h-8" />
</button>
</>
);
}


export default MMNotepad;
5 changes: 4 additions & 1 deletion src/constants.js
Original file line number Diff line number Diff line change
@@ -1 +1,4 @@
export const CUSTOM_GAME_REGEX = /{customgame:(.+)}/;
export const CUSTOM_GAME_REGEX = /{customgame:(.+)}/;

export const TOTO_VAR_MMYSTERY_IDS = "MMystery_Notes";
export const MAX_NOTEPAD_CHARS = 2048;
10 changes: 7 additions & 3 deletions src/pages/Game.js
Original file line number Diff line number Diff line change
@@ -1,15 +1,19 @@
import React from "react";
import React, {useContext} from "react";
import {useParams, Link} from "react-router-dom";
import Game from "../components/game/Game";
import { MdArrowBackIos } from "react-icons/md";
import SettingsContext from "services/SettingsContext";

function GameScreen() {
const [settings] = useContext(SettingsContext);
const translations = settings.translations[settings.language];

const { elementId, sessionId } = useParams();

return (<div className="w-full h-full flex flex-col">
<Link to={`/quest/${sessionId}`} className={`flex flex-row items-center m-5 mb-0`} >
<Link to={`/quest/${sessionId}`} className={`flex flex-row items-center m-5 mb-0 pr-10 self-start`} >
<MdArrowBackIos />
<span>back</span>
<span>{translations.BACK_BUTTON}</span>
</Link>
<Game elementId={elementId} sessionId={sessionId} />
</div>)
Expand Down
5 changes: 3 additions & 2 deletions src/pages/Settings.js
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,8 @@ function Settings() {
if (loading === true) return;
setLoading(true);
await logout();
localStorage.clear();
localStorage.removeItem("sessionids");
localStorage.removeItem("auth");
setSettings({...settings, auth: null});
}}>
<div className="border my-4 rounded-lg flex flex-col p-3">
Expand All @@ -103,7 +104,7 @@ function Settings() {

{ settings?.auth?.role === "Anonymised" &&
<Link to="/settings/upgrade" className="underline cursor-pointer border my-4 rounded-lg flex flex-col p-3">
Upgrade account
{translations.SETTINGS_ACOUNTSETTING_UPGRADE_ACCOUNT}
</Link>
}

Expand Down
10 changes: 10 additions & 0 deletions src/services/translations/en.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ export default {
SETTINGS_ACOUNTSETTING_UPLOAD_FILE: 'Upload file',
SETTINGS_ACOUNTSETTING_UPLOAD_FAILED: 'Failed to upload file',
SETTINGS_ACOUNTSETTING_UPLOADED: 'File uploaded',
SETTINGS_ACOUNTSETTING_UPGRADE_ACCOUNT: 'Upgrade account',

// Instalation banner
INSTALL_BANNER_TITLE: 'Install city game app',
Expand Down Expand Up @@ -99,4 +100,13 @@ export default {

//next button
NEXT_BUTTON: "next",
BACK_BUTTON: "back",

// murder mystery notepad
MM_FOUND_NOTES_LABEL: "Found notes:",
MM_FOUND_NOTES_NONE: "No notes",
MM_USER_NOTES_LABEL: "Type your notes here:",

// game load error
GAME_LOAD_ERROR_GENERIC: "The game was not able to load.",
};
10 changes: 10 additions & 0 deletions src/services/translations/it.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ export default {
SETTINGS_ACOUNTSETTING_UPLOAD_FILE: 'Caricare un file',
SETTINGS_ACOUNTSETTING_UPLOAD_FAILED: 'Impossibile caricare il file',
SETTINGS_ACOUNTSETTING_UPLOADED: 'File caricato',
SETTINGS_ACOUNTSETTING_UPGRADE_ACCOUNT: 'Account di aggiornamento',

// Instalation banner
INSTALL_BANNER_TITLE: 'Installa l\'app di gioco della città',
Expand Down Expand Up @@ -99,4 +100,13 @@ export default {

//next button
NEXT_BUTTON: "prossima",
BACK_BUTTON: "indietro",

// murder mystery notepad
MM_FOUND_NOTES_LABEL: "Note trovate:",
MM_FOUND_NOTES_NONE: "Nessuna nota",
MM_USER_NOTES_LABEL: "Scrivi qui i tuoi appunti:",

// game load error
GAME_LOAD_ERROR_GENERIC: "Impossibile caricare il gioco.",
};
10 changes: 10 additions & 0 deletions src/services/translations/sl.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ export default {
SETTINGS_ACOUNTSETTING_UPLOAD_FILE: 'Naloži datoteko',
SETTINGS_ACOUNTSETTING_UPLOAD_FAILED: 'Datoteke ni bilo mogoče naložiti',
SETTINGS_ACOUNTSETTING_UPLOADED: 'Datoteka naložena',
SETTINGS_ACOUNTSETTING_UPGRADE_ACCOUNT: 'Nadgradnja računa',

// Instalation banner
INSTALL_BANNER_TITLE: 'Namestite aplikacijo City Game',
Expand Down Expand Up @@ -99,4 +100,13 @@ export default {

//next button
NEXT_BUTTON: "Naslednji",
BACK_BUTTON: "nazaj",

// murder mystery notepad
MM_FOUND_NOTES_LABEL: "Najdeni zapiski:",
MM_FOUND_NOTES_NONE: "Brez opomb",
MM_USER_NOTES_LABEL: "Tukaj vnesite svoje opombe:",

// game load error
GAME_LOAD_ERROR_GENERIC: "Igre ni bilo mogoče naložiti.",
};