Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Don't forget the tutorial! #44

Merged
merged 1 commit into from
Aug 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 1 addition & 1 deletion src/components/BranchNotesDisplay/BranchNotesDisplay.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ const BranchNotesDisplay: React.FC = () => {
StorageManager.setItem(`${route.game.name}_${route.name}_${route.version}_b_${branchIndex}`, content);
};

return <NoteEditor notes={notes} onNotesChange={handleNotesChange} />;
return <NoteEditor notes={notes} onNotesChange={handleNotesChange} id="branchNotesDisplay" />;
};

export default BranchNotesDisplay;
11 changes: 10 additions & 1 deletion src/components/MapDisplay/MapDisplay.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,16 @@ const MapDisplay: React.FC = () => {
};

return (
<MapContainer style={style} bounds={outerBounds} zoom={0} maxZoom={7} minZoom={1} crs={crs} keyboard={false}>
<MapContainer
style={style}
bounds={outerBounds}
zoom={0}
maxZoom={7}
minZoom={1}
crs={crs}
keyboard={false}
id="mapDisplay"
>
<RouteMarkers branch={route.branches[branchIndex]} activeThing={activeThing} />
<RouteLines />
<MapUpdate activePoint={activePoint} />
Expand Down
5 changes: 3 additions & 2 deletions src/components/NoteEditor/NoteEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,11 @@ Quill.register("modules/blotFormatter", BlotFormatter);

interface NoteEditorProps {
notes: string;
id: string;
onNotesChange: (content: string) => void;
}

const NoteEditor: React.FC<NoteEditorProps> = ({ notes, onNotesChange }) => {
const NoteEditor: React.FC<NoteEditorProps> = ({ notes, onNotesChange, id }) => {
const quillRef = useRef<ReactQuill>(null);

const insertToEditor = useCallback(
Expand All @@ -56,7 +57,7 @@ const NoteEditor: React.FC<NoteEditorProps> = ({ notes, onNotesChange }) => {
}
}, [quillRef, selectLocalImage]);

return <ReactQuill ref={quillRef} modules={modules} value={notes} onChange={onNotesChange} />;
return <ReactQuill ref={quillRef} modules={modules} value={notes} onChange={onNotesChange} id={id} />;
};

export default NoteEditor;
2 changes: 1 addition & 1 deletion src/components/PointNotesDisplay/PointNotesDisplay.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ const PointNotesDisplay: React.FC = () => {
StorageManager.setItem(`${route.game.name}_${route.name}_${route.version}_p_${branchIndex}_${pointIndex}`, content);
};

return <NoteEditor notes={notes} onNotesChange={handleNotesChange} />;
return <NoteEditor notes={notes} onNotesChange={handleNotesChange} id="pointNotesDisplay" />;
};

export default PointNotesDisplay;
2 changes: 1 addition & 1 deletion src/components/ProgressDisplay/ProgressDisplay.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ const ProgressDisplay: React.FC = () => {
}, [route, branchIndex, pointIndex]);

return (
<div className={"progress-window"}>
<div className={"progress-window"} id="progressDisplay">
{" "}
<button className={"overlay-window-button"} onClick={openOverlayWindow}>
Open Overlay Window
Expand Down
2 changes: 1 addition & 1 deletion src/components/RouteListDisplay/RouteListDisplay.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ const RouteListDisplay: React.FC = () => {
};

return (
<div className="routeList">
<div className="routeList" id="routeListDisplay">
{route.branches.map((branch, bIdx) => (
<div className="routeList__branch" key={bIdx}>
<div className="routeList__branchName">
Expand Down
43 changes: 43 additions & 0 deletions src/components/RunMosaic/RunMosaic.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { useParams } from "react-router-dom";
import { useSelector } from "react-redux";
import dropRight from "lodash/dropRight";
import { filter } from "lodash";
import TutorialDisplay from "../TutorialDisplay/TutorialDisplay";

import {
Mosaic,
Expand Down Expand Up @@ -53,6 +54,47 @@ const RunMosaic: React.FC = () => {
const dispatch = useAppDispatch();
const darkMode = useSelector((state: RootState) => state.userPreferences.darkMode);

const tutorialSteps = [
{
title: "Map",
description:
"This is the primary map. \nNavigate using the mouse.\nTo Advance or Decrement the route, use the Left/Right arrow keys.",
elementId: "mapDisplay",
},
{
title: "Route",
description: "Here's the list of all of the upcoming points on the map. Some may have additional comments.",
elementId: "routeListDisplay",
},
{
title: "Upcoming",
description: "This displays information that informs you of how many of a specific interaction are coming up.",
elementId: "upcomingDisplay",
},
{
title: "Progress",
description:
"This keeps track of how many of each item you have already collected, with optional offsets to account for mistakes.",
elementId: "progressDisplay",
},
{
title: "Overlay",
description: "The overlay can be opened and chroma-keyed to provide information on livestreams.",
elementId: "progressDisplay",
},
{
title: "Point Notes",
description: "Here you can add notes for specific points. These will be saved between sessions.",
elementId: "pointNotesDisplay",
},
{
title: "Branch Notes",
description:
"Here you can add notes that stay visible for entire branches. These will be saved between sessions.",
elementId: "branchNotesDisplay",
},
];

const initialLayoutStorage = StorageManager.getItem("layout");
const initialLayout = initialLayoutStorage
? JSON.parse(initialLayoutStorage)
Expand Down Expand Up @@ -199,6 +241,7 @@ const RunMosaic: React.FC = () => {

return (
<div className={"run-mosaic " + darkModeClass}>
<TutorialDisplay steps={tutorialSteps} />
{availableDisplays.length !== 0 && (
<Toolbar
onButtonClick={handleToolbarButtonClick}
Expand Down
137 changes: 137 additions & 0 deletions src/components/TutorialDisplay/TutorialDisplay.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
import { useState, useEffect, useRef } from "react";
import { motion, AnimatePresence } from "framer-motion";

const Tutorial = ({ steps }: { steps: Array<any> }) => {
const [currentStep, setCurrentStep] = useState(0);
const [showTutorial, setShowTutorial] = useState(true);
const [position, setPosition] = useState({ x: 0, y: 0 });
const [spotlight, setSpotlight] = useState({ x: 0, y: 0, width: 0, height: 0 });
const tooltipRef = useRef(null);

const calculatePosition = (targetRect: DOMRect, tooltipRect: { width: number; height: number }) => {
const margin = 10; // Margin from viewport edges
let x = targetRect.left + targetRect.width / 2;
let y = targetRect.bottom + 20; // 20px below the target element

// Adjust horizontal position if tooltip goes off-screen
if (x + tooltipRect.width / 2 > window.innerWidth - margin) {
x = window.innerWidth - tooltipRect.width / 2 - margin;
} else if (x - tooltipRect.width / 2 < margin) {
x = tooltipRect.width / 2 + margin;
}

// Adjust vertical position if tooltip goes off-screen
if (y + tooltipRect.height > window.innerHeight - margin) {
y = targetRect.top - tooltipRect.height - 20; // 20px above the target element
}

return { x, y };
};

useEffect(() => {
if (showTutorial && steps[currentStep].elementId) {
const element = document.getElementById(steps[currentStep].elementId);
if (element && tooltipRef.current) {
const targetRect = element.getBoundingClientRect();
const tooltipRect = (tooltipRef.current as HTMLElement).getBoundingClientRect();
const newPosition = calculatePosition(targetRect, tooltipRect);
setPosition(newPosition);
setSpotlight({
x: targetRect.left,
y: targetRect.top,
width: targetRect.width,
height: targetRect.height,
});
}
} else {
setSpotlight({ x: 0, y: 0, width: 0, height: 0 });
}
}, [currentStep, showTutorial, steps]);

const nextStep = () => {
if (currentStep < steps.length - 1) {
setCurrentStep(currentStep + 1);
} else {
setShowTutorial(false);
}
};

return (
<AnimatePresence>
{showTutorial && (
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
style={{
position: "fixed",
top: 0,
left: 0,
right: 0,
bottom: 0,
zIndex: 9999,
}}
>
<svg
width="100%"
height="100%"
style={{
position: "absolute",
top: 0,
left: 0,
}}
>
<defs>
<mask id="spotlight-mask">
<rect width="100%" height="100%" fill="white" />
<rect x={spotlight.x} y={spotlight.y} width={spotlight.width} height={spotlight.height} fill="black" />
</mask>
</defs>
<rect width="100%" height="100%" fill="rgba(0, 0, 0, 0.5)" mask="url(#spotlight-mask)" />
</svg>

<motion.div
ref={tooltipRef}
initial={{ scale: 0.8, opacity: 0 }}
animate={{
scale: 1,
opacity: 1,
x: position.x,
y: position.y,
}}
exit={{ scale: 0.8, opacity: 0 }}
transition={{ type: "spring", damping: 10, stiffness: 100 }}
style={{
backgroundColor: "white",
padding: "20px",
borderRadius: "10px",
maxWidth: "300px",
textAlign: "center",
position: "absolute",
transform: "translate(-50%, 0)",
boxShadow: "0 4px 6px rgba(0, 0, 0, 0.1)",
}}
>
<h2
style={{
color: "black",
}}
>
{steps[currentStep].title}
</h2>
<p
style={{
color: "black",
}}
>
{steps[currentStep].description}
</p>
<button onClick={nextStep}>{currentStep < steps.length - 1 ? "Next" : "Finish"}</button>
</motion.div>
</motion.div>
)}
</AnimatePresence>
);
};

export default Tutorial;
2 changes: 1 addition & 1 deletion src/components/UpcomingDisplay/UpcomingDisplay.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ const UpcomingDisplay: React.FC = () => {
}, [route, branchIndex, pointIndex]);

return (
<div className={"upcoming-window"}>
<div className={"upcoming-window"} id="upcomingDisplay">
<p>Before next proving ground:</p>
<p>Points: {points}</p>
<p>Rock Koroks: {rockKorokCount}</p>
Expand Down
Loading