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

[VPS-121] Retrieve and Save scene tags to database #126

Merged
merged 8 commits into from
Oct 26, 2023
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
19 changes: 10 additions & 9 deletions backend/src/db/daos/sceneDao.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,10 @@ const createScene = async (scenarioId, scene) => {
*/
const retrieveSceneList = async (scenarioId) => {
const dbScenario = await Scenario.findById(scenarioId);
const dbScenes = await Scene.find(
{ _id: { $in: dbScenario.scenes } },
"name"
);
const dbScenes = await Scene.find({ _id: { $in: dbScenario.scenes } }, [
"name",
"tag",
]);

return dbScenes;
};
Expand Down Expand Up @@ -77,19 +77,20 @@ const updateScene = async (sceneId, updatedScene) => {
}

// if we are updating name only, components will be null
const dbScene = await Scene.findById(sceneId);
let dbScene = await Scene.findById(sceneId);

// store temp variable incase new name is invalid
const previousName = dbScene.name;
dbScene.name = updatedScene.name;

// is new name empty or null?
if (dbScene.name === "" || dbScene.name === null) {
dbScene.name = previousName;
// eslint-disable-next-line no-param-reassign
updatedScene.name = previousName;
}

dbScene.time = updatedScene.time;
await dbScene.save();
dbScene = await Scene.updateOne({ _id: sceneId }, updatedScene, {
new: true,
});
return dbScene;
};

Expand Down
3 changes: 3 additions & 0 deletions backend/src/db/models/scene.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ const sceneSchema = new Schema({
type: Number,
default: 0,
},
tag: {
type: String,
},
});

// before removal of scene from the database, first attempt to delete all user-uploaded images from firebase
Expand Down
19 changes: 19 additions & 0 deletions backend/src/routes/api/scene.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,14 @@ router.get("/full/:sceneId", async (req, res) => {
});

// Retrieve all scenes of a scenario
const symbolForNoTag = "-";
router.get("/", async (req, res) => {
const scenes = await retrieveSceneList(req.params.scenarioId);
scenes.map((scene) => {
// eslint-disable-next-line no-underscore-dangle, no-param-reassign
scene._doc.tag = scene._doc.tag || symbolForNoTag;
return scene;
});
res.json(scenes);
});

Expand Down Expand Up @@ -58,6 +64,19 @@ router.post("/", async (req, res) => {

res.status(HTTP_OK).json(scene);
});
// update the tags
router.put("/tags", async (req, res) => {
const updatedTags = req.body;

await Promise.all(
updatedTags.map(async (scene) => {
// eslint-disable-next-line no-underscore-dangle
await updateScene(scene._id, scene);
})
);

res.status(HTTP_OK).json(updatedTags);
});

// Update a scene
router.put("/:sceneId", async (req, res) => {
Expand Down
15 changes: 15 additions & 0 deletions frontend/src/containers/pages/AuthoringTool/AuthoringToolPage.js
Original file line number Diff line number Diff line change
Expand Up @@ -79,9 +79,23 @@ export default function AuthoringToolPage() {
reFetch();
}

async function saveTags() {
const updatedScenes = scenes.map(({ tag, _id, name }) => ({
tag,
_id,
name,
}));
await usePut(
`/api/scenario/${scenarioId}/scene/tags`,
updatedScenes,
getUserIdToken
);
}

/** called when save button is clicked */
async function save() {
saveScene();
saveTags();
setSaveButtonText("Saved!");
setTimeout(() => {
setSaveButtonText("Save");
Expand All @@ -91,6 +105,7 @@ export default function AuthoringToolPage() {
/** called when save and close button is clicked */
function savePlusClose() {
saveScene();
saveTags();
/* redirects user to the scenario page */
window.location.href = `/scenario/${currentScenario?._id}`;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
/* eslint-disable jsx-a11y/no-static-element-interactions */
/* eslint-disable jsx-a11y/click-events-have-key-events */
/* eslint-disable jsx-a11y/no-noninteractive-element-interactions */

import React, { useRef } from "react";
import styles from "styling/SceneNavigator.module.scss";
import { noTagSymbol, tagColours, tagOptions } from "./SceneTagCustomization";

const SceneListItem = ({
sceneId,
tag,
thumbnail,
showingTagInputFor,
showTagInputFor,
selectTagEl,
}) => {
const tagRef = useRef(null);
const fullTagRef = useRef(null);

function toggleTagOnHover() {
const tagEl = tagRef.current;
const fullTagEl = fullTagRef.current;

tagEl.classList.toggle(styles.hidden);
fullTagEl.classList.toggle(styles.showing);
}

function toggleInputBox() {
showTagInputFor(showingTagInputFor === sceneId ? null : sceneId);
selectTagEl(showingTagInputFor === sceneId ? null : tagRef.current);
}

const noTagClassName = tag === noTagSymbol ? styles.noTag : "";

return (
<li key={sceneId}>
{thumbnail}
<p
className={`${noTagClassName} ${styles.playersTag}`}
style={{ borderColor: tagColours[tag] || "black" }}
onMouseEnter={() => toggleTagOnHover(sceneId)}
onClick={() => toggleInputBox(sceneId)}
ref={tagRef}
>
{tag}
</p>

<p
className={`${noTagClassName} ${styles.playersTagFull}`}
style={{ borderColor: tagColours[tag] || "black" }}
onMouseLeave={() => toggleTagOnHover(sceneId)}
onClick={() => toggleInputBox(sceneId)}
ref={fullTagRef}
>
{tagOptions[tag]}
</p>
</li>
);
};

export default SceneListItem;
Original file line number Diff line number Diff line change
@@ -1,61 +1,126 @@
import React, { useEffect, useState, useContext } from "react";
import { useParams, useHistory } from "react-router-dom";
import SceneContext from "../../../../context/SceneContext";
import Thumbnail from "../../../../components/Thumbnail";
import styles from "../../../../styling/SceneNavigator.module.scss";
import SceneContext from "context/SceneContext";
import Thumbnail from "components/Thumbnail";
import styles from "styling/SceneNavigator.module.scss";
import SceneListItem from "./SceneListItem";
import SceneTagInput from "./SceneTagInput";

const SceneNavigator = (props) => {
const SceneNavigator = ({ saveScene }) => {
const [thumbnails, setThumbnails] = useState(null);
const { scenes, currentScene, currentSceneRef, setCurrentScene } =
const { scenes, setScenes, currentScene, currentSceneRef, setCurrentScene } =
useContext(SceneContext);
const { scenarioId } = useParams();
const history = useHistory();
const { saveScene } = props;

// showing tag modal

const [showingTagInputFor, showTagInputFor] = useState(null);

function getIndexOfSceneId(sceneId) {
if (!thumbnails) return -1;
return thumbnails.findIndex((thumbnail) => thumbnail.sceneId === sceneId);
}

function changeTag(sceneId, newTag) {
const sceneIndex = getIndexOfSceneId(sceneId);

setThumbnails((currentThumbnails) => {
currentThumbnails[sceneIndex].tag = newTag;
return [...currentThumbnails];
});

setScenes((currScenes) => {
const index = currScenes.findIndex((scene) => scene._id === sceneId);
currScenes[index].tag = newTag;
return currScenes;
});
}

useEffect(() => {
if (scenes !== undefined) {
setThumbnails(
scenes.map((scene, index) => (
<button
type="button"
onClick={() => {
if (currentSceneRef.current._id === scene._id) return;
setCurrentScene(scene);
saveScene();
history.push({
pathname: `/scenario/${scenarioId}/scene/${scene._id}`,
});
}}
className={styles.sceneButton}
style={
currentScene._id === scene._id
? {
border: "3px solid #035084",
}
: null
}
key={scene._id}
>
<p
className={styles.sceneText}
style={
currentScene._id === scene._id ? { color: "#035084" } : null
}
>
{index + 1}
</p>
<Thumbnail
url={`${process.env.PUBLIC_URL}/play/${scenarioId}/${scene._id}`}
width="160"
height="90"
/>
</button>
))
scenes.map((scene, index) => ({
sceneId: scene._id,
tag: scene.tag,
sceneListItem: (
<>
<p
className={styles.sceneText}
style={
currentScene._id === scene._id ? { color: "#035084" } : null
}
>
{index + 1}
</p>
<button
type="button"
onClick={() => {
if (currentSceneRef.current._id === scene._id) return;
setCurrentScene(scene);
saveScene();
history.push({
pathname: `/scenario/${scenarioId}/scene/${scene._id}`,
});
}}
className={styles.sceneButton}
style={
currentScene._id === scene._id
? {
border: "3px solid #035084",
}
: null
}
key={scene._id}
>
<Thumbnail
url={`${process.env.PUBLIC_URL}/play/${scenarioId}/${scene._id}`}
width="160"
height="90"
/>
</button>
</>
),
}))
);
}
}, [scenes, currentScene]);

return <div className={styles.sceneNavigator}>{thumbnails}</div>;
// for the scene/tag inputs
const [selectedTagEl, selectTagEl] = useState(null);

return (
thumbnails && (
<div style={{ display: "flex", position: "relative" }}>
<ul className={styles.sceneNavigator}>
{thumbnails.map(({ sceneListItem: thumbnail, sceneId, tag }) => (
<SceneListItem
{...{
thumbnail,
sceneId,
tag,
showingTagInputFor,
showTagInputFor,
selectTagEl,
key: sceneId,
}}
/>
))}
</ul>

{showingTagInputFor && (
<SceneTagInput
{...{
selectedTagEl,
changeTag,
showTagInputFor,
showingTagInputFor,
}}
/>
)}
</div>
)
);
};

export default SceneNavigator;
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
export const tagOptions = {
"-": "No tag",
D: "Doctor",
P: "Pharmacist",
N: "Nurse",
};

export const tagColours = {
"-": "rgba(0,0,0,0)",
D: "magenta",
P: "blue",
N: "green",
};

export const noTagSymbol = "-";
Loading
Loading