Skip to content

Vps 137/Create State Variables #286

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

Merged
merged 18 commits into from
May 29, 2025
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: 19 additions & 0 deletions backend/src/db/daos/scenarioDao.js
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,24 @@ const deleteScenario = async (scenarioId) => {
}
};

/**
* Creates a new state variable for a scenario
* @param {String} sceneId MongoDB ID of scene
* @param {updatedStateVariables: Array} stateVariable new state variable to be added
* @returns updated state variables for the scenario
*/
const createStateVariable = async (scenarioId, stateVariable) => {
// TODO Add validation for state variable (e.g. name should be unique)
const scenario = await Scenario.findById(scenarioId);
try {
scenario.stateVariables.push(stateVariable);
await scenario.save();
return scenario.stateVariables;
} catch {
return scenario.stateVariables;
}
};

export {
createScenario,
deleteScenario,
Expand All @@ -170,4 +188,5 @@ export {
updateDurations,
updateRoleList,
updateScenario,
createStateVariable,
};
5 changes: 5 additions & 0 deletions backend/src/db/models/scenario.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,11 @@ const scenarioSchema = new Schema({
type: String,
},
],
stateVariables: [
{
type: Object,
},
],
});

// before removal of a scenario from the database, first remove all its scenes
Expand Down
12 changes: 12 additions & 0 deletions backend/src/routes/api/scenario.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import validScenarioId from "../../middleware/validScenarioId.js";

import {
createScenario,
createStateVariable,
deleteScenario,
retrieveScenario,
retrieveScenarioList,
Expand Down Expand Up @@ -83,4 +84,15 @@ router.delete("/:scenarioId", async (req, res) => {
}
});

// Update the state variables of a scenario
router.post("/:scenarioId/stateVariables", async (req, res) => {
const { newStateVariable } = req.body;
let updatedStateVariables = await createStateVariable(
req.params.scenarioId,
newStateVariable
);

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

export default router;
158 changes: 158 additions & 0 deletions frontend/src/components/StateVariables/CreateStateVariable.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
import {
Button,
FormControl,
FormGroup,
InputLabel,
MenuItem,
Select,
TextField,
Typography,
} from "@material-ui/core";
import { useState, useEffect, useContext } from "react";
import StateTypes from "./StateTypes";
import { api } from "../../util/api";
import AuthenticationContext from "../../context/AuthenticationContext";
import toast from "react-hot-toast";

const DEFAULT_STATE_TYPE = StateTypes.STRING;

export const getDefaultValue = (type) => {
switch (type) {
case StateTypes.STRING:
return "";
case StateTypes.NUMBER:
return 0;
case StateTypes.BOOLEAN:
return false;
default:
return "";
}
};

/**
* Component used for creating state variables
*
* @component
* @example
* return (
* <CreateStateVariable />
* )
*/
const CreateStateVariable = ({ scenarioId, setStateVariables }) => {
const { user } = useContext(AuthenticationContext);

// Info for the new state variable
const [name, setName] = useState("");
const [type, setType] = useState(DEFAULT_STATE_TYPE);
const [value, setValue] = useState(getDefaultValue(DEFAULT_STATE_TYPE));

// Reset to default value upon type change
useEffect(() => {
setValue(getDefaultValue(type));
}, [type]);

const handleSubmit = (e) => {
e.preventDefault();
const newStateVariable = {
name,
type,
value,
};
api
.post(user, `/api/scenario/${scenarioId}/stateVariables`, {
newStateVariable,
})
.then((response) => {
setStateVariables(response.data);
toast.success("State variable created successfully");
// Reset name and value fields (but not type)
setName("");
setValue(getDefaultValue(type));
})
.catch((error) => {
console.error("Error creating state variable:", error);
toast.error("Error creating state variable");
});
};

return (
<form
style={{
backgroundColor: "#f9f9f9",
padding: "20px",
borderRadius: "8px",
maxWidth: "100%",
boxShadow: "0 2px 8px rgba(0,0,0,0.1)",
}}
>
<Typography variant="subtitle1">Create State Variable</Typography>
<FormGroup
style={{ flexDirection: "row", justifyContent: "space-between" }}
>
<FormControl style={{ width: "250px" }} margin="normal">
<TextField
label="Name"
value={name}
onChange={(e) => setName(e.target.value)}
required
/>
</FormControl>

<FormControl style={{ width: "250px" }} margin="normal">
<InputLabel>Type</InputLabel>
<Select
value={type}
onChange={(e) => setType(e.target.value)}
required
>
<MenuItem value={StateTypes.STRING}>String</MenuItem>
<MenuItem value={StateTypes.NUMBER}>Number</MenuItem>
<MenuItem value={StateTypes.BOOLEAN}>Boolean</MenuItem>
</Select>
</FormControl>

<FormControl style={{ width: "250px" }} margin="normal">
{type === StateTypes.BOOLEAN ? (
<>
<InputLabel>Initial Value</InputLabel>
<Select
value={value}
onChange={(e) => setValue(e.target.value)}
required
>
<MenuItem value={true}>True</MenuItem>
<MenuItem value={false}>False</MenuItem>
</Select>
</>
) : (
<TextField
value={value}
label={`Initial Value`}
onChange={(e) =>
setValue(
type === StateTypes.NUMBER
? Number(e.target.value)
: e.target.value
)
}
required
type={type === StateTypes.NUMBER ? "number" : "text"}
/>
)}
</FormControl>

<Button
type="submit"
variant="contained"
color="primary"
style={{ height: "40px" }}
onClick={handleSubmit}
>
Create
</Button>
</FormGroup>
</form>
);
};

export default CreateStateVariable;
7 changes: 7 additions & 0 deletions frontend/src/components/StateVariables/StateTypes.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
const StateTypes = {
STRING: "string",
NUMBER: "number",
BOOLEAN: "boolean",
};

export default StateTypes;
61 changes: 61 additions & 0 deletions frontend/src/components/StateVariables/StateVariableMenu.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import { Box, Button, Modal, Typography } from "@material-ui/core";
import CreateStateVariable from "./CreateStateVariable";
import { useParams } from "react-router-dom/cjs/react-router-dom.min";
import { useState } from "react";

const style = {
position: "absolute",
top: "50%",
left: "50%",
transform: "translate(-50%, -50%)",
width: "75vw",
height: "75vh",
bgcolor: "background.paper",
boxShadow: 24,
p: 4,
};

/**
* Component used for creating, editing, and deleting state variables
*
* @component
* @example
* const [show, setShow] = useState(false);
* return (
* <Button onClick={() => setShow(true)}>Open State Variable Menu</Button>
* <StateVariableMenu show={show} setShow={setShow} />
* )
*/
const StateVariableMenu = ({ show, setShow }) => {
const { scenarioId } = useParams();

const [stateVariables, setStateVariables] = useState([]);

return (
<Modal open={show} onClose={() => setShow(false)}>
<Box sx={style}>
<Typography variant="h5">State Variable Menu</Typography>
<CreateStateVariable
scenarioId={scenarioId}
setStateVariables={setStateVariables}
/>
{stateVariables.map((stateVariable) => (
<Box key={stateVariable.id} sx={{ margin: "10px 0" }}>
<Typography variant="subtitle1">
{stateVariable.name}: {stateVariable.value.toString()}
</Typography>
</Box>
))}
<Button
variant="contained"
color="primary"
onClick={() => setShow(false)}
>
Close
</Button>
</Box>
</Modal>
);
};

export default StateVariableMenu;
28 changes: 28 additions & 0 deletions frontend/src/features/authoring/ToolBar/OpenStateVariableMenu.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { MenuItem } from "@material-ui/core";
import { useState } from "react";
import styles from "./ToolBar.module.scss";
import StateVariableMenu from "../../../components/StateVariables/StateVariableMenu";

/**
* Used to open the State Variable Menu
* @component
*/
const OpenStateVariableMenu = () => {
const [show, setShow] = useState(false);

return (
<div>
<MenuItem
className={styles.menuItem}
color="default"
variant="contained"
onClick={() => setShow(true)}
>
Open State Menu
</MenuItem>
<StateVariableMenu show={show} setShow={setShow} />
</div>
);
};

export default OpenStateVariableMenu;
2 changes: 1 addition & 1 deletion frontend/src/features/authoring/ToolBar/ToolBar.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ export default function ToolBar() {
key={tool.title}
onClick={menuOnClick}
>
{tool.icon} <p>Add {tool.title}</p>
{tool.icon} <p>{tool.title}</p>
{tool.dropdown && <ArrowDropDownIcon fontSize="small" />}
</Button>
</Tooltip>
Expand Down
22 changes: 16 additions & 6 deletions frontend/src/features/authoring/ToolBar/ToolBarData.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,14 @@ import {
addSpeechText,
addText,
} from "./ToolBarActions";
import OpenStateVariableMenu from "./OpenStateVariableMenu";

/**
* This file contains the data for the add component icons to be added into the ToolBar
*/
const toolBarData = [
{
title: "Image",
title: "Add Image",
icon: <ImageIcon fontSize="medium" />,
dropdown: [
{
Expand All @@ -30,22 +31,22 @@ const toolBarData = [
],
},
{
title: "Text",
title: "Add Text",
icon: <TextFieldsIcon fontSize="medium" />,
onClick: addText,
},
{
title: "Button",
title: "Add Button",
icon: <ButtonIcon fontSize="medium" />,
onClick: addButton,
},
{
title: "Reset",
title: "Add Reset",
icon: <ButtonIcon fontSize="medium" />,
onClick: addResetButton,
},
{
title: "Audio",
title: "Add Audio",
icon: <VolumeUpIcon fontSize="medium" />,
dropdown: [
{
Expand All @@ -54,10 +55,19 @@ const toolBarData = [
],
},
{
title: "Speech text",
title: "Add Speech text",
icon: <ChatBubbleIcon fontSize="medium" />,
onClick: addSpeechText,
},
{
title: "State Variables",
icon: <TextFieldsIcon fontSize="medium" />,
dropdown: [
{
component: <OpenStateVariableMenu />,
},
],
},
];

export default toolBarData;