diff --git a/client/src/App.js b/client/src/App.js
index 336fa9eb..bf6c4c0f 100644
--- a/client/src/App.js
+++ b/client/src/App.js
@@ -7,6 +7,7 @@ import About from "./views/About/About"
import Workspace from "./views/Workspace/Workspace"
import Dashboard from "./views/Dashboard/Dashboard"
import Student from "./views/Student/Student"
+import StudentDetail from './views/StudentDetail/StudentDetail'
import NotFound from "./views/NotFound"
import StudentLogin from "./views/StudentLogin/StudentLogin";
import Sandbox from "./views/Sandbox/Sandbox"
@@ -16,6 +17,7 @@ import TeacherLogin from "./views/TeacherLogin/TeacherLogin"
import ContentCreator from './views/ContentCreator/ContentCreator'
import UnitCreator from './views/ContentCreator/UnitCreator/UnitCreator'
import UploadBlocks from './views/UploadBlocks/UploadBlocks'
+import Replay from './components/Replay/Replay';
const App = () => {
let history = useHistory();
@@ -23,20 +25,41 @@ const App = () => {
return (
- }/>
- }/>
- }/>
- }/>
+
+
+
+
+
+
+
+
+
+
+
+
}/>
} />
- }/>
- } />
+
+
+
+
+
+
} />
} />
- }/>
- }/>
- }/>
-
+ } />
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/client/src/Utils/requests.js b/client/src/Utils/requests.js
index 8763500a..bc6d3db6 100644
--- a/client/src/Utils/requests.js
+++ b/client/src/Utils/requests.js
@@ -212,13 +212,14 @@ export const setSelection = async (classroom, learningStandard) => (
})
);
-export const saveWorkspace = async (day, workspace) => (
+export const saveWorkspace = async (day, workspace, replay) => (
makeRequest({
method: POST,
path: `${server}/saves`,
data: {
- day: day,
- workspace: workspace
+ day,
+ workspace,
+ replay
},
auth: true,
error: 'Failed to save your workspace.'
@@ -234,6 +235,15 @@ export const getSaves = async (day) => (
})
);
+export const getAllSaves = async () => (
+ makeRequest({
+ method: GET,
+ path: `${server}/saves`,
+ auth: true,
+ error: 'Past saves could not be retrieved.'
+ })
+);
+
export const createSubmission = async (day, workspace, sketch, path, isAuth) => (
makeRequest({
method: POST,
diff --git a/client/src/assets/style.less b/client/src/assets/style.less
index 2bb22992..e56b4ba2 100644
--- a/client/src/assets/style.less
+++ b/client/src/assets/style.less
@@ -64,4 +64,12 @@
.nav-padding {
padding-top: 8vh;
+}
+
+.replayButton {
+ cursor: pointer;
+}
+
+.bold {
+ font-weight: bold;
}
\ No newline at end of file
diff --git a/client/src/components/DayPanels/BlocklyCanvasPanel/BlocklyCanvasPanel.js b/client/src/components/DayPanels/BlocklyCanvasPanel/BlocklyCanvasPanel.js
index 3547bea5..795ccf13 100644
--- a/client/src/components/DayPanels/BlocklyCanvasPanel/BlocklyCanvasPanel.js
+++ b/client/src/components/DayPanels/BlocklyCanvasPanel/BlocklyCanvasPanel.js
@@ -20,12 +20,15 @@ export default function BlocklyCanvasPanel(props) {
const workspaceRef = useRef(null);
const dayRef = useRef(null);
+ const replayRef = useRef([]);
+ const undoLength = useRef(0);
const { SubMenu } = Menu;
- const setWorkspace = () =>
+ const setWorkspace = () => {
workspaceRef.current = window.Blockly.inject('blockly-canvas',
{ toolbox: document.getElementById('toolbox') }
);
+ }
const loadSave = selectedSave => {
try {
@@ -64,18 +67,31 @@ export default function BlocklyCanvasPanel(props) {
// automatically save workspace every min
setInterval(async () => {
if (isStudent && workspaceRef.current && dayRef.current) {
- const res = await handleSave(dayRef.current.id, workspaceRef);
+ const res = await handleSave(dayRef.current.id, workspaceRef, replayRef.current);
if (res.data) {
setLastAutoSave(res.data[0]);
setLastSavedTime(getFormattedDate(res.data[0].updated_at))
}
}
}, 60000);
+ setInterval(async () => {
+ if (workspaceRef.current.undoStack_.length !== undoLength.current) {
+ undoLength.current = workspaceRef.current.undoStack_.length;
+ let xml = window.Blockly.Xml.workspaceToDom(workspaceRef.current);
+ let xml_text = window.Blockly.Xml.domToText(xml);
+ const replay = {
+ xml: xml_text,
+ timestamp: Date.now()
+ }
+ replayRef.current.push(replay);
+ console.log(replayRef.current);
+ }
+ }, 1000);
// clean up - saves workspace and removes blockly div from DOM
return async () => {
if (isStudent && dayRef.current && workspaceRef.current)
- await handleSave(dayRef.current.id, workspaceRef);
+ await handleSave(dayRef.current.id, workspaceRef, replayRef.current);
if (workspaceRef.current) workspaceRef.current.dispose();
dayRef.current = null
}
@@ -102,6 +118,7 @@ export default function BlocklyCanvasPanel(props) {
if (onLoadSave) {
let xml = window.Blockly.Xml.textToDom(onLoadSave.workspace);
window.Blockly.Xml.domToWorkspace(xml, workspaceRef.current);
+ replayRef.current = onLoadSave.replay;
setLastSavedTime(getFormattedDate(onLoadSave.updated_at));
} else if (day.template) {
let xml = window.Blockly.Xml.textToDom(day.template);
@@ -116,7 +133,7 @@ export default function BlocklyCanvasPanel(props) {
const handleManualSave = async () => {
// save workspace then update load save options
- const res = await handleSave(day.id, workspaceRef);
+ const res = await handleSave(day.id, workspaceRef, replayRef.current);
if (res.err) {
message.error(res.err)
} else {
diff --git a/client/src/components/DayPanels/helpers.js b/client/src/components/DayPanels/helpers.js
index 4fb56d4a..c7bedfa8 100644
--- a/client/src/components/DayPanels/helpers.js
+++ b/client/src/components/DayPanels/helpers.js
@@ -100,10 +100,10 @@ const flashArduino = async (response) => {
}
// save current workspace
-export const handleSave = async (dayId, workspaceRef) => {
+export const handleSave = async (dayId, workspaceRef, replay) => {
let xml = window.Blockly.Xml.workspaceToDom(workspaceRef.current);
let xml_text = window.Blockly.Xml.domToText(xml);
- return await saveWorkspace(dayId, xml_text);
+ return await saveWorkspace(dayId, xml_text, replay);
};
export const handleCreatorSaveDay = async (dayId, workspaceRef, blocksList) => {
diff --git a/client/src/components/Replay/Replay.js b/client/src/components/Replay/Replay.js
new file mode 100644
index 00000000..ee527f36
--- /dev/null
+++ b/client/src/components/Replay/Replay.js
@@ -0,0 +1,109 @@
+import React, { useEffect, useRef, useState } from 'react';
+import { Link } from 'react-router-dom'
+import NavBar from '../NavBar/NavBar';
+
+const Replay = () => {
+ const workspaceRef = useRef(null);
+ const [step, setStep] = useState(0);
+ let playback;
+ const setWorkspace = () => {
+ workspaceRef.current = window.Blockly.inject('blockly-canvas',
+ { toolbox: document.getElementById('toolbox') }
+ );
+ }
+ const replay = [
+ {
+ "xml": "WHILEitem",
+ "timestamp": 1618859201398
+ },
+ {
+ "xml": "WHILEitem",
+ "timestamp": 1618859202401
+ },
+ {
+ "xml": "WHILEitemAND",
+ "timestamp": 1618859203397
+ },
+ {
+ "xml": "WHILEitemAND",
+ "timestamp": 1618859204397
+ },
+ {
+ "xml": "WHILEitemROOTAND",
+ "timestamp": 1618859206397
+ },
+ {
+ "xml": "WHILEitemROOTAND",
+ "timestamp": 1618859209397
+ },
+ {
+ "xml": "WHILEitemWHILEROOTAND",
+ "timestamp": 1618859211400
+ },
+ {
+ "xml": "WHILEitemROOTANDWHILE",
+ "timestamp": 1618859214404
+ },
+ {
+ "xml": "WHILEitemROOTAND",
+ "timestamp": 1618859216400
+ }
+ ];
+ useEffect(() => {
+ workspaceRef.current ? workspaceRef.current.clear() : setWorkspace();
+ const xml = window.Blockly.Xml.textToDom(replay[step].xml);
+ window.Blockly.Xml.domToWorkspace(xml, workspaceRef.current);
+ }, [step]);
+
+ const goBack = () => {
+ setStep(step - 1);
+ }
+ // const play = () => {
+ // playback = setInterval(() => {
+ // console.log('firing');
+ // setStep(step + 1);
+ // console.log(step);
+ // }, 1000);
+ // }
+ const goForward = () => {
+ setStep(step + 1);
+ }
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ { replay.map((item, index) =>
{item.timestamp}
)}
+
+
+
+
+
+
+ )
+};
+
+export default Replay;
\ No newline at end of file
diff --git a/client/src/views/Classroom/Classroom.js b/client/src/views/Classroom/Classroom.js
index 4b177546..cab62665 100644
--- a/client/src/views/Classroom/Classroom.js
+++ b/client/src/views/Classroom/Classroom.js
@@ -1,4 +1,5 @@
import React, {useEffect, useState} from "react"
+import {Link} from 'react-router-dom';
import {message, Tabs} from 'antd';
import "./Classroom.less"
@@ -27,7 +28,7 @@ export default function Classroom(props) {
}
});
}, [classroomId]);
-
+ console.log(classroom);
return (
@@ -47,6 +48,10 @@ export default function Classroom(props) {
+ Student List
+
+ {classroom.students?.map(student => - {student.name}
)}
+
);
diff --git a/client/src/views/ContentCreator/ContentCreator.js b/client/src/views/ContentCreator/ContentCreator.js
index ab265cfe..17b88930 100644
--- a/client/src/views/ContentCreator/ContentCreator.js
+++ b/client/src/views/ContentCreator/ContentCreator.js
@@ -1,5 +1,5 @@
import React, { useState, useEffect } from 'react';
-
+import {useHistory} from 'react-router-dom';
import { Tabs, Table, Popconfirm, message } from 'antd'
import Navbar from '../../components/NavBar/NavBar'
import { QuestionCircleOutlined } from '@ant-design/icons';
@@ -12,11 +12,12 @@ import UnitEditor from './UnitEditor/UnitEditor'
const { TabPane } = Tabs;
-export default function ContentCreator(props) {
+export default function ContentCreator() {
const [dataSource, setDataSource] = useState([])
const [dataSourceMap, setDataSourceMap] = useState([])
const [gradeMenu, setGradeMenu] = useState([])
+ const history = useHistory();
useEffect(() => {
//console.log(getLearningStandardcount())
@@ -44,7 +45,7 @@ export default function ContentCreator(props) {
width: '22.5%',
align: 'left',
render: (_, key) => (
-
+
)
},
{
@@ -83,7 +84,7 @@ export default function ContentCreator(props) {
// width: '10%',
// align: 'right',
// render: (_, key) => (
- //
+ //
// )
// },
// {
@@ -93,7 +94,7 @@ export default function ContentCreator(props) {
// width: '10%',
// align: 'right',
// render: (_, key) => (
- //
+ //
// )
// },
// {
diff --git a/client/src/views/ContentCreator/LearningStandardDayCreator/DayEditor.js b/client/src/views/ContentCreator/LearningStandardDayCreator/DayEditor.js
index 81d45de9..55b98aaa 100644
--- a/client/src/views/ContentCreator/LearningStandardDayCreator/DayEditor.js
+++ b/client/src/views/ContentCreator/LearningStandardDayCreator/DayEditor.js
@@ -1,4 +1,5 @@
import React, { useState } from 'react'
+import {useHistory} from 'react-router-dom'
import { Button, List, Card, Modal, Form, Input } from 'antd'
import { createDay, deleteDay, getDayToolboxAll, getLearningStandard } from '../../../Utils/requests'
@@ -13,13 +14,12 @@ export default function ContentCreator(props) {
const [newDay, setNewDay] = useState();
const learningStandardId = props.learningStandardId
const learningStandardName = props.learningStandardName
-
+ const history = useHistory();
const handleCancel = () => {
setVisible(false)
};
-
const showModal = () => {
console.log("got days", props.days)
setDay([...props.days])
@@ -27,7 +27,6 @@ export default function ContentCreator(props) {
setVisible(true)
};
-
const addBasicDay = () => {
const res = createDay(newDay, learningStandardId)
res.then(function (a) {
@@ -60,7 +59,7 @@ export default function ContentCreator(props) {
day.toolbox = res.data.toolbox;
localStorage.setItem("my-day", JSON.stringify(day));
- props.history.push('/day')
+ history.push('/day')
};
//figure out how to set these up in the css file colors[] stuff causes problems
diff --git a/client/src/views/ContentCreator/viewDayModal/viewDayModal.js b/client/src/views/ContentCreator/viewDayModal/viewDayModal.js
index 624cb268..ef72a27e 100644
--- a/client/src/views/ContentCreator/viewDayModal/viewDayModal.js
+++ b/client/src/views/ContentCreator/viewDayModal/viewDayModal.js
@@ -1,5 +1,5 @@
import React, { useState } from 'react'
-
+import {useHistory} from 'react-router-dom'
import { Modal } from 'antd';
import {CloseOutlined} from '@ant-design/icons'
@@ -26,7 +26,7 @@ export default function ViewDayModal(props) {
day.toolbox = res.data.toolbox;
localStorage.setItem("my-day", JSON.stringify(day));
- props.history.push('/day')
+ history.push('/day')
};
return (
diff --git a/client/src/views/Home/Home.js b/client/src/views/Home/Home.js
index 060ed186..1b31f952 100644
--- a/client/src/views/Home/Home.js
+++ b/client/src/views/Home/Home.js
@@ -4,14 +4,13 @@ import Logo from "../../assets/casmm_logo.png"
import HomeJoin from "./HomeJoin"
import NavBar from "../../components/NavBar/NavBar";
-export default function Home(props) {
-
+export default function Home() {
return(

-
+
)
diff --git a/client/src/views/Home/HomeJoin.js b/client/src/views/Home/HomeJoin.js
index a5d49e28..a65b27ef 100644
--- a/client/src/views/Home/HomeJoin.js
+++ b/client/src/views/Home/HomeJoin.js
@@ -2,10 +2,12 @@ import React, {useState} from 'react'
import {message} from 'antd'
import './Home.less'
import { getStudents } from "../../Utils/requests";
+import { useHistory } from 'react-router-dom'
-export default function HomeJoin(props) {
+export default function HomeJoin() {
const [loading, setLoading] = useState(false);
const [joinCode, setJoinCode] = useState('');
+ const history = useHistory();
const handleLogin = () => {
setLoading(true);
@@ -14,7 +16,7 @@ export default function HomeJoin(props) {
if(res.data){
setLoading(false);
localStorage.setItem('join-code', joinCode);
- props.history.push('/login');
+ history.push('/login');
} else {
setLoading(false);
message.error('Join failed. Please input a valid join code.');
diff --git a/client/src/views/Student/Student.js b/client/src/views/Student/Student.js
index 01ec694a..10705f5d 100644
--- a/client/src/views/Student/Student.js
+++ b/client/src/views/Student/Student.js
@@ -3,11 +3,12 @@ import {getStudentClassroom} from "../../Utils/requests"
import './Student.less'
import {message} from "antd";
import NavBar from "../../components/NavBar/NavBar";
+import {useHistory} from 'react-router-dom';
function Student(props) {
const [learningStandard, setLearningStandard] = useState({});
const [selectedDay, setSelectedDay] = useState({});
-
+ const history = useHistory();
useEffect(() => {
const fetchData = async () => {
try {
@@ -28,7 +29,7 @@ function Student(props) {
setSelectedDay(day);
localStorage.setItem("my-day", JSON.stringify(day));
- props.history.push("/workspace")
+ history.push("/workspace")
};
return (
diff --git a/client/src/views/StudentDetail/StudentDetail.js b/client/src/views/StudentDetail/StudentDetail.js
new file mode 100644
index 00000000..1d7cfabc
--- /dev/null
+++ b/client/src/views/StudentDetail/StudentDetail.js
@@ -0,0 +1,18 @@
+import React, {useState, useEffect} from 'react'
+import { useParams } from 'react-router-dom';
+import { getSaves } from '../../Utils/requests';
+
+const StudentDetail = () => {
+ const {classroomId, studentId} = useParams();
+ const [studentSaves, setStudentSaves] = useState();
+ useEffect(() => {
+ const getStudentSaves = async () => await getSaves(1);
+ setStudentSaves(getStudentSaves);
+ }, [])
+ console.log(studentSaves);
+ return (
+ Student Detail
+ )
+}
+
+export default StudentDetail;
\ No newline at end of file
diff --git a/client/src/views/StudentLogin/StudentLogin.js b/client/src/views/StudentLogin/StudentLogin.js
index b26fa8b9..3160a069 100644
--- a/client/src/views/StudentLogin/StudentLogin.js
+++ b/client/src/views/StudentLogin/StudentLogin.js
@@ -6,15 +6,17 @@ import StudentLoginForm from "./StudentLoginForm";
import {setUserSession} from "../../Utils/AuthRequests";
import {message} from "antd";
import NavBar from "../../components/NavBar/NavBar";
+import { useHistory } from 'react-router-dom';
-export default function StudentLogin(props) {
+export default function StudentLogin() {
const [studentList, setStudentList] = useState([]);
const [animalList, setAnimalList] = useState([])
const [studentIds, setStudentIds] = useState([null, null, null]);
const [studentAnimals, setStudentAnimals] = useState(['', '', '']);
const [numForms, setNumForms] = useState(2);
const joinCode = localStorage.getItem('join-code');
+ const history = useHistory();
useEffect(() => {
getStudents(joinCode).then(res => {
@@ -32,7 +34,7 @@ export default function StudentLogin(props) {
const res = await postJoin(joinCode, ids);
if (res.data) {
setUserSession(res.data.jwt, JSON.stringify(res.data.students));
- props.history.push('/student')
+ history.push('/student')
} else {
message.error(res.err);
}
diff --git a/client/src/views/TeacherLogin/TeacherLogin.js b/client/src/views/TeacherLogin/TeacherLogin.js
index 0aae2bd8..df4a0964 100644
--- a/client/src/views/TeacherLogin/TeacherLogin.js
+++ b/client/src/views/TeacherLogin/TeacherLogin.js
@@ -1,6 +1,7 @@
import {message} from "antd";
import NavBar from "../../components/NavBar/NavBar";
import React, {useState} from "react";
+import {useHistory} from 'react-router-dom';
import {postUser, setUserSession} from "../../Utils/AuthRequests";
import "./TeacherLogin.less"
@@ -8,7 +9,7 @@ export default function TeacherLogin(props) {
const email = useFormInput('');
const password = useFormInput('');
const [loading, setLoading] = useState(false);
-
+ const history = useHistory();
const handleLogin = () => {
setLoading(true);
let body = {identifier: email.value, password: password.value};
@@ -17,9 +18,9 @@ export default function TeacherLogin(props) {
setUserSession(response.data.jwt, JSON.stringify(response.data.user));
setLoading(false);
if (response.data.user.role.name === "Content Creator") {
- props.history.push('/ccdashboard');
+ history.push('/ccdashboard');
} else {
- props.history.push('/dashboard');
+ history.push('/dashboard');
}
diff --git a/client/src/views/Workspace/Workspace.js b/client/src/views/Workspace/Workspace.js
index 0d2e2c58..265794d5 100644
--- a/client/src/views/Workspace/Workspace.js
+++ b/client/src/views/Workspace/Workspace.js
@@ -3,11 +3,12 @@ import {getDayToolbox} from "../../Utils/requests.js"
import BlocklyCanvasPanel from "../../components/DayPanels/BlocklyCanvasPanel/BlocklyCanvasPanel";
import {message} from "antd";
import NavBar from "../../components/NavBar/NavBar";
-
+import { useHistory } from 'react-router-dom';
export default function Workspace(props) {
const [day, setDay] = useState({});
- const {handleLogout, history} = props;
+ const {handleLogout} = props;
+ const history = useHistory();
useEffect(() => {
const localDay = JSON.parse(localStorage.getItem("my-day"));
diff --git a/server/api/save/controllers/save.js b/server/api/save/controllers/save.js
index 3bb13b0c..6a9962d1 100644
--- a/server/api/save/controllers/save.js
+++ b/server/api/save/controllers/save.js
@@ -32,14 +32,14 @@ module.exports = {
// ensure the request has the right number of params
const params = Object.keys(ctx.request.body).length
- if (params !== 2) return ctx.badRequest(
+ if (params !== 3) return ctx.badRequest(
'Invalid number of params!',
{ id: 'Save.create.body.invalid', error: 'ValidationError' }
)
// validate the request
- // at somept validate the xml...could lead to bad things...
- const { day, workspace } = ctx.request.body
+ // TODO: at somept validate the xml...could lead to bad things...
+ const { day, workspace, replay } = ctx.request.body
if (!strapi.services.validator.isInt(day) || !workspace) return ctx.badRequest(
'A day and workspace must be provided!',
{ id: 'Save.create.body.invalid', error: 'ValidationError' }
@@ -66,10 +66,10 @@ module.exports = {
return await Promise.all(ids.map(id => {
// save exists, update
const saveId = studentSaves[id]
- if (saveId) return strapi.services.save.update({id: saveId}, {workspace: workspace})
+ if (saveId) return strapi.services.save.update({id: saveId}, {workspace, replay})
// else, create a new save
- return strapi.services.save.create({ student: id, day, workspace, session })
+ return strapi.services.save.create({ student: id, day, workspace, session, replay })
}))
},
}
diff --git a/server/api/save/models/save.settings.json b/server/api/save/models/save.settings.json
index a5f3111c..89d16355 100644
--- a/server/api/save/models/save.settings.json
+++ b/server/api/save/models/save.settings.json
@@ -21,6 +21,9 @@
"workspace": {
"type": "text",
"required": true
+ },
+ "replay": {
+ "type": "json"
}
}
}