From ee7b18cf90dd497d8b03282116204885840af536 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Victor=20L=C3=B6fgren?= <victor.l0fgr3n@gmail.com> Date: Sun, 4 Apr 2021 16:33:41 +0200 Subject: [PATCH 01/20] #39: Add first socketio --- client/package-lock.json | 88 +++++++++++++++++++++++++++++++++++++++ client/package.json | 2 + client/src/App.tsx | 3 ++ server/app/__init__.py | 7 +++- server/app/sockets.py | 13 ++++++ server/configmodule.py | 1 + server/main.py | 23 ++-------- server/requirements.txt | Bin 2106 -> 2308 bytes server/tests/__init__.py | 2 +- 9 files changed, 117 insertions(+), 22 deletions(-) create mode 100644 server/app/sockets.py diff --git a/client/package-lock.json b/client/package-lock.json index babf4067..7edbd634 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -2318,6 +2318,11 @@ "@types/node": "*" } }, + "@types/component-emitter": { + "version": "1.2.10", + "resolved": "https://registry.npmjs.org/@types/component-emitter/-/component-emitter-1.2.10.tgz", + "integrity": "sha512-bsjleuRKWmGqajMerkzox19aGbscQX5rmmvvXl3wlIp5gMG1HgkiwPxsN5p070fBDKTNSPgojVbuY1+HWMbFhg==" + }, "@types/enzyme": { "version": "3.10.8", "resolved": "https://registry.npmjs.org/@types/enzyme/-/enzyme-3.10.8.tgz", @@ -2526,6 +2531,11 @@ "@types/node": "*" } }, + "@types/socket.io-client": { + "version": "1.4.36", + "resolved": "https://registry.npmjs.org/@types/socket.io-client/-/socket.io-client-1.4.36.tgz", + "integrity": "sha512-ZJWjtFBeBy1kRSYpVbeGYTElf6BqPQUkXDlHHD4k/42byCN5Rh027f4yARHCink9sKAkbtGZXEAmR0ZCnc2/Ag==" + }, "@types/source-list-map": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/@types/source-list-map/-/source-list-map-0.1.2.tgz", @@ -3909,6 +3919,11 @@ "resolved": "https://registry.npmjs.org/babylon/-/babylon-6.18.0.tgz", "integrity": "sha512-q/UEjfGJ2Cm3oKV71DJz9d25TPnq5rhBVL2Q4fA5wcC3jcrdn7+SssEybFIxwAvvP+YCsCYNKughoF33GxgycQ==" }, + "backo2": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/backo2/-/backo2-1.0.2.tgz", + "integrity": "sha1-MasayLEpNjRj41s+u2n038+6eUc=" + }, "balanced-match": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", @@ -3964,6 +3979,11 @@ } } }, + "base64-arraybuffer": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-0.1.4.tgz", + "integrity": "sha1-mBjHngWbE1X5fgQooBfIOOkLqBI=" + }, "base64-js": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", @@ -5928,6 +5948,30 @@ "once": "^1.4.0" } }, + "engine.io-client": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-5.0.0.tgz", + "integrity": "sha512-e6GK0Fqvq45Nu/j7YdIVqXtDPvlsggAcfml3QiEiGdJ1qeh7IQU6knxSN3+yy9BmbnXtIfjo1hK4MFyHKdc9mQ==", + "requires": { + "base64-arraybuffer": "0.1.4", + "component-emitter": "~1.3.0", + "debug": "~4.3.1", + "engine.io-parser": "~4.0.1", + "has-cors": "1.1.0", + "parseqs": "0.0.6", + "parseuri": "0.0.6", + "ws": "~7.4.2", + "yeast": "0.1.2" + } + }, + "engine.io-parser": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-4.0.2.tgz", + "integrity": "sha512-sHfEQv6nmtJrq6TKuIz5kyEKH/qSdK56H/A+7DnAuUPWosnIZAS2NHNcPLmyjtY3cGS/MqJdZbUjW97JU72iYg==", + "requires": { + "base64-arraybuffer": "0.1.4" + } + }, "enhanced-resolve": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-4.5.0.tgz", @@ -7845,6 +7889,11 @@ "integrity": "sha512-LSBS2LjbNBTf6287JEbEzvJgftkF5qFkmCo9hDRpAzKhUOlJ+hx8dd4USs00SgsUNwc4617J9ki5YtEClM2ffA==", "dev": true }, + "has-cors": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-cors/-/has-cors-1.1.0.tgz", + "integrity": "sha1-XkdHk/fqmEPRu5nCPu9J/xJv/zk=" + }, "has-flag": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", @@ -11816,6 +11865,16 @@ } } }, + "parseqs": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/parseqs/-/parseqs-0.0.6.tgz", + "integrity": "sha512-jeAGzMDbfSHHA091hr0r31eYfTig+29g3GKKE/PPbEQ65X0lmMwlEoqmhzu0iztID5uJpZsFlUPDP8ThPL7M8w==" + }, + "parseuri": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/parseuri/-/parseuri-0.0.6.tgz", + "integrity": "sha512-AUjen8sAkGgao7UyCX6Ahv0gIK2fABKmYjvP4xmy5JaKvcbTRueIqIPHLAfq30xJddqSE033IOMUSOMCcK3Sow==" + }, "parseurl": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", @@ -15008,6 +15067,30 @@ } } }, + "socket.io-client": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.0.0.tgz", + "integrity": "sha512-27yQxmXJAEYF19Ygyl8FPJ0if0wegpSmkIIbrWJeI7n7ST1JyH8bbD5v3fjjGY5cfCanACJ3dARUAyiVFNrlTQ==", + "requires": { + "@types/component-emitter": "^1.2.10", + "backo2": "~1.0.2", + "component-emitter": "~1.3.0", + "debug": "~4.3.1", + "engine.io-client": "~5.0.0", + "parseuri": "0.0.6", + "socket.io-parser": "~4.0.4" + } + }, + "socket.io-parser": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.0.4.tgz", + "integrity": "sha512-t+b0SS+IxG7Rxzda2EVvyBZbvFPBCjJoyHuE0P//7OAsN23GItzDRdWa6ALxZI/8R5ygK7jAR6t028/z+7295g==", + "requires": { + "@types/component-emitter": "^1.2.10", + "component-emitter": "~1.3.0", + "debug": "~4.3.1" + } + }, "sockjs": { "version": "0.3.20", "resolved": "https://registry.npmjs.org/sockjs/-/sockjs-0.3.20.tgz", @@ -17951,6 +18034,11 @@ } } }, + "yeast": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/yeast/-/yeast-0.1.2.tgz", + "integrity": "sha1-AI4G2AlDIMNy28L47XagymyKxBk=" + }, "yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", diff --git a/client/package.json b/client/package.json index e5fbde97..6ae23b2e 100644 --- a/client/package.json +++ b/client/package.json @@ -14,6 +14,7 @@ "@types/node": "^12.19.16", "@types/react": "^17.0.1", "@types/react-dom": "^17.0.0", + "@types/socket.io-client": "^1.4.36", "axios": "^0.21.1", "formik": "^2.2.6", "react": "^17.0.1", @@ -24,6 +25,7 @@ "react-scripts": "4.0.2", "redux": "^4.0.5", "redux-devtools-extension": "^2.13.8", + "socket.io-client": "^4.0.0", "styled-components": "^5.2.1", "typescript": "^4.1.3", "web-vitals": "^1.1.0", diff --git a/client/src/App.tsx b/client/src/App.tsx index d3e92beb..33268c68 100644 --- a/client/src/App.tsx +++ b/client/src/App.tsx @@ -1,9 +1,12 @@ import { createMuiTheme, MuiThemeProvider, StylesProvider } from '@material-ui/core' import React from 'react' +import io from 'socket.io-client' import { ThemeProvider } from 'styled-components' import Main from './Main' import { Wrapper } from './styled' +const socket = io('localhost:5000') + const theme = createMuiTheme({ palette: { primary: { diff --git a/server/app/__init__.py b/server/app/__init__.py index 7b5a8add..5f0a8b52 100644 --- a/server/app/__init__.py +++ b/server/app/__init__.py @@ -14,6 +14,11 @@ def create_app(config_name="configmodule.DevelopmentConfig"): bcrypt.init_app(app) jwt.init_app(app) db.init_app(app) + db.create_all() + + from .sockets import sio + + sio.init_app(app) from app.apis import flask_api @@ -25,7 +30,7 @@ def create_app(config_name="configmodule.DevelopmentConfig"): if rp != "/" and rp.endswith("/"): return redirect(rp[:-1]) - return app + return app, sio def identity(payload): diff --git a/server/app/sockets.py b/server/app/sockets.py new file mode 100644 index 00000000..598d10f5 --- /dev/null +++ b/server/app/sockets.py @@ -0,0 +1,13 @@ +from flask_socketio import SocketIO + +sio = SocketIO(cors_allowed_origins="http://localhost:3000") + + +@sio.on("connect") +def connect(): + print("A client connected!") + + +@sio.on("message") +def handle_message(data): + print("received message" + data) diff --git a/server/configmodule.py b/server/configmodule.py index f7c2935f..35afc88a 100644 --- a/server/configmodule.py +++ b/server/configmodule.py @@ -16,6 +16,7 @@ class Config: class DevelopmentConfig(Config): DEBUG = True SQLALCHEMY_DATABASE_URI = "sqlite:///database.db" + SQLALCHEMY_TRACK_MODIFICATIONS = False class TestingConfig(Config): diff --git a/server/main.py b/server/main.py index cb616855..bf4a2393 100644 --- a/server/main.py +++ b/server/main.py @@ -1,22 +1,5 @@ -from app import create_app, db - -# Development port -DEFAULT_DEV_PORT = 5000 - -# Production port -DEFAULT_PRO_PORT = 8080 +from app import create_app if __name__ == "__main__": - app = create_app("configmodule.DevelopmentConfig") - with app.app_context(): - db.create_all() - app.run(port=5000) - # CONFIG = "configmodule.DevelopmentConfig" - - # if "production-teknik8" in os.environ: - # CONFIG = "configmodule.ProductionConfig" - - # if "configmodule.DevelopmentConfig" == CONFIG: - # app.run(port=DEFAULT_DEV_PORT) - # else: - # app.run(host="0.0.0.0", port=DEFAULT_PRO_PORT) + app, sio = create_app("configmodule.DevelopmentConfig") + sio.run(app, port=5000) diff --git a/server/requirements.txt b/server/requirements.txt index 463ef4fe07f14b03b7771a25842170c8903f73f7..c64222b754a34a97ff5c2f57bd017ac682dbd42c 100644 GIT binary patch delta 236 zcmdlb&?2-Ugt<PQA(f#Fi1Qdq7;J&ih{2FS4~&f%cp11DkmYq5%7H4A7>XJ48Il>Y zfozB#1F#MV3DHvolmnTW16Knw6{NxdY-%P$3Q#tY!FF>jb09M-SaS1bwsyvlDxlc~ oKwWx3jO;EGpol44KSEU|*yX7VMGR>`^OAwOK)wK}G={4N0R1N>^#A|> delta 23 ecmZn>+9j|dgn9ER<|t-X0|q??lg*m!?Ti3d7zRfG diff --git a/server/tests/__init__.py b/server/tests/__init__.py index 0b0deaf0..c5b8f20d 100644 --- a/server/tests/__init__.py +++ b/server/tests/__init__.py @@ -4,7 +4,7 @@ from app import create_app, db @pytest.fixture def app(): - app = create_app("configmodule.TestingConfig") + app, _ = create_app("configmodule.TestingConfig") """ with app.app_context(): -- GitLab From 9d8820b3b36d904f30c0fb6d4f48a7f5f1393b0c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Victor=20L=C3=B6fgren?= <victor.l0fgr3n@gmail.com> Date: Mon, 5 Apr 2021 20:25:09 +0200 Subject: [PATCH 02/20] Add competition state to redux and sockets --- client/src/actions/competition.ts | 19 +++++++++++ client/src/actions/sockets.ts | 3 -- client/src/actions/types.ts | 4 +++ client/src/pages/views/JudgeViewPage.tsx | 24 +++++++++++-- client/src/reducers/allReducers.ts | 2 ++ client/src/reducers/competitionReducer.ts | 41 +++++++++++++++++++++++ client/src/sockets.ts | 24 +++++++++++++ server/app/sockets.py | 23 ++++++++++--- 8 files changed, 130 insertions(+), 10 deletions(-) create mode 100644 client/src/actions/competition.ts delete mode 100644 client/src/actions/sockets.ts create mode 100644 client/src/reducers/competitionReducer.ts create mode 100644 client/src/sockets.ts diff --git a/client/src/actions/competition.ts b/client/src/actions/competition.ts new file mode 100644 index 00000000..009f1107 --- /dev/null +++ b/client/src/actions/competition.ts @@ -0,0 +1,19 @@ +import types from './types' + +export const setSlide = (slide: number) => ({ + type: types.SET_SLIDE, + payload: slide, +}) + +export const nextSlide = () => ({ + type: types.NEXT_SLIDE, +}) + +export const prevSlide = () => ({ + type: types.PREV_SLIDE, +}) + +export const setCompetitionID = (competitionID: string) => ({ + type: types.SET_COMPEITION_ID, + payload: competitionID, +}) diff --git a/client/src/actions/sockets.ts b/client/src/actions/sockets.ts deleted file mode 100644 index b7157820..00000000 --- a/client/src/actions/sockets.ts +++ /dev/null @@ -1,3 +0,0 @@ -import io from 'socket.io-client' - -export const connect = () => io('localhost:5000') diff --git a/client/src/actions/types.ts b/client/src/actions/types.ts index d265a692..0d9313f4 100644 --- a/client/src/actions/types.ts +++ b/client/src/actions/types.ts @@ -17,4 +17,8 @@ export default { AXIOS_POST: 'AXIOS_POST', AXIOS_POST_SUCCESS: 'AXIOS_POST_SUCCESS', AXIOS_POST_ERROR: 'AXIOS_POST_ERROR', + SET_SLIDE: 'SET_SLIDE', + NEXT_SLIDE: 'NEXT_SLIDE', + PREV_SLIDE: 'PREV_SLIDE', + SET_COMPEITION_ID: 'SET_COMPETITION_ID', } diff --git a/client/src/pages/views/JudgeViewPage.tsx b/client/src/pages/views/JudgeViewPage.tsx index 457ebe08..d3ee6cbe 100644 --- a/client/src/pages/views/JudgeViewPage.tsx +++ b/client/src/pages/views/JudgeViewPage.tsx @@ -1,7 +1,27 @@ -import React from 'react' +import React, { useEffect } from 'react' +import { nextSlide, prevSlide, setCompetitionID, setSlide } from '../../actions/competition' +import { useAppDispatch } from '../../hooks' +import { connect, syncSlide } from '../../sockets' +import store from '../../store' const JudgeViewPage: React.FC = () => { - return <div>Judge</div> + const dispatch = useAppDispatch() + + useEffect(() => { + dispatch(setCompetitionID('123456')) + dispatch(setSlide(1)) + connect() + }, []) + + return ( + <div> + <div>Judge</div> + <button onClick={syncSlide}>Sync</button> + <button onClick={() => dispatch(nextSlide())}>Next</button> + <button onClick={() => dispatch(prevSlide())}>Prev</button> + <div>{store.getState().competion.slide}</div> + </div> + ) } export default JudgeViewPage diff --git a/client/src/reducers/allReducers.ts b/client/src/reducers/allReducers.ts index b12b5346..700720bc 100644 --- a/client/src/reducers/allReducers.ts +++ b/client/src/reducers/allReducers.ts @@ -2,6 +2,7 @@ import { combineReducers } from 'redux' import citiesReducer from './citiesReducer' +import competitionReducer from './competitionReducer' import competitionsReducer from './competitionsReducer' import uiReducer from './uiReducer' import userReducer from './userReducer' @@ -11,6 +12,7 @@ const allReducers = combineReducers({ user: userReducer, UI: uiReducer, competitions: competitionsReducer, + competion: competitionReducer, cities: citiesReducer, }) export default allReducers diff --git a/client/src/reducers/competitionReducer.ts b/client/src/reducers/competitionReducer.ts new file mode 100644 index 00000000..65bd5a0c --- /dev/null +++ b/client/src/reducers/competitionReducer.ts @@ -0,0 +1,41 @@ +import { AnyAction } from 'redux' +import Types from '../actions/types' + +interface CompetitionState { + competitionID: string | null + slide: number + timer: number | null +} + +const initialState: CompetitionState = { + competitionID: null, + slide: 0, + timer: null, +} + +export default function (state = initialState, action: AnyAction) { + switch (action.type) { + case Types.SET_SLIDE: + return { + ...state, + slide: action.payload, + } + case Types.NEXT_SLIDE: + return { + ...state, + slide: state.slide + 1, + } + case Types.PREV_SLIDE: + return { + ...state, + slide: state.slide - 1, + } + case Types.SET_COMPEITION_ID: + return { + ...state, + competitionID: action.payload, + } + default: + return state + } +} diff --git a/client/src/sockets.ts b/client/src/sockets.ts new file mode 100644 index 00000000..7de89b5e --- /dev/null +++ b/client/src/sockets.ts @@ -0,0 +1,24 @@ +import io from 'socket.io-client' +import { setSlide } from './actions/competition' +import store from './store' + +let socket: SocketIOClient.Socket + +interface SyncSlideInterface { + slide: number +} + +export const connect = () => { + if (!socket) { + socket = io('localhost:5000') + socket.emit('join_competition', { competitionID: store.getState().competion.competitionID }) + } + + socket.on('sync_slide', (data: SyncSlideInterface) => { + store.dispatch(setSlide(data.slide)) + }) +} + +export const syncSlide = () => { + socket.emit('sync_slide', { slide: store.getState().competion.slide }) +} diff --git a/server/app/sockets.py b/server/app/sockets.py index 598d10f5..cb1d776c 100644 --- a/server/app/sockets.py +++ b/server/app/sockets.py @@ -1,13 +1,26 @@ -from flask_socketio import SocketIO +from flask.globals import request +from flask_socketio import SocketIO, emit, join_room, leave_room sio = SocketIO(cors_allowed_origins="http://localhost:3000") @sio.on("connect") def connect(): - print("A client connected!") + print(f"[Connected]: {request.sid}") -@sio.on("message") -def handle_message(data): - print("received message" + data) +@sio.on("disconnect") +def disconnect(): + print(f"[Disconnected]: {request.sid}") + + +@sio.on("join_competition") +def join_competition(data): + join_room(data["competitionID"]) + print(f"[Join room]: {request.sid} -> {data['competitionID']}") + + +@sio.on("sync_slide") +def sync_slide(data): + emit("sync_slide", {"slide": data["slide"]}, room="123456") + print("[Sync slide]") -- GitLab From 4d161e137d52945c75901f2df0d36173ce6f73e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Victor=20L=C3=B6fgren?= <victor.l0fgr3n@gmail.com> Date: Mon, 5 Apr 2021 21:28:29 +0200 Subject: [PATCH 03/20] Add timer state to redux --- client/src/actions/competition.ts | 9 +++++++++ client/src/actions/types.ts | 2 ++ client/src/pages/views/JudgeViewPage.tsx | 17 +++++++++++++++-- client/src/reducers/competitionReducer.ts | 16 ++++++++++++++-- client/src/sockets.ts | 22 ++++++++++++++++++++-- server/app/sockets.py | 10 ++++++++-- 6 files changed, 68 insertions(+), 8 deletions(-) diff --git a/client/src/actions/competition.ts b/client/src/actions/competition.ts index 009f1107..d3010bed 100644 --- a/client/src/actions/competition.ts +++ b/client/src/actions/competition.ts @@ -17,3 +17,12 @@ export const setCompetitionID = (competitionID: string) => ({ type: types.SET_COMPEITION_ID, payload: competitionID, }) + +export const tickTimer = () => ({ + type: types.TICK_TIMER, +}) + +export const setTimer = (timer: number) => ({ + type: types.SET_TIMER, + payload: timer, +}) diff --git a/client/src/actions/types.ts b/client/src/actions/types.ts index 0d9313f4..514f4d8e 100644 --- a/client/src/actions/types.ts +++ b/client/src/actions/types.ts @@ -21,4 +21,6 @@ export default { NEXT_SLIDE: 'NEXT_SLIDE', PREV_SLIDE: 'PREV_SLIDE', SET_COMPEITION_ID: 'SET_COMPETITION_ID', + TICK_TIMER: 'TICK_TIMER', + SET_TIMER: 'SET_TIMER', } diff --git a/client/src/pages/views/JudgeViewPage.tsx b/client/src/pages/views/JudgeViewPage.tsx index d3ee6cbe..4d12e031 100644 --- a/client/src/pages/views/JudgeViewPage.tsx +++ b/client/src/pages/views/JudgeViewPage.tsx @@ -1,7 +1,7 @@ import React, { useEffect } from 'react' -import { nextSlide, prevSlide, setCompetitionID, setSlide } from '../../actions/competition' +import { nextSlide, prevSlide, setCompetitionID, setSlide, setTimer, tickTimer } from '../../actions/competition' import { useAppDispatch } from '../../hooks' -import { connect, syncSlide } from '../../sockets' +import { connect, syncSlide, syncTimer } from '../../sockets' import store from '../../store' const JudgeViewPage: React.FC = () => { @@ -10,7 +10,20 @@ const JudgeViewPage: React.FC = () => { useEffect(() => { dispatch(setCompetitionID('123456')) dispatch(setSlide(1)) + dispatch(setTimer(5)) connect() + + const timerTimer = setInterval(() => { + dispatch(tickTimer()) + syncTimer() + + console.log(store.getState().competion.timer) + + if (store.getState().competion.timer === 0) { + clearInterval(timerTimer) + // TODO: lock the competitors slide when this happens + } + }, 1000) }, []) return ( diff --git a/client/src/reducers/competitionReducer.ts b/client/src/reducers/competitionReducer.ts index 65bd5a0c..a2de6903 100644 --- a/client/src/reducers/competitionReducer.ts +++ b/client/src/reducers/competitionReducer.ts @@ -4,13 +4,15 @@ import Types from '../actions/types' interface CompetitionState { competitionID: string | null slide: number - timer: number | null + timer: number + isLocked: boolean } const initialState: CompetitionState = { competitionID: null, slide: 0, - timer: null, + timer: 0, + isLocked: false, } export default function (state = initialState, action: AnyAction) { @@ -35,6 +37,16 @@ export default function (state = initialState, action: AnyAction) { ...state, competitionID: action.payload, } + case Types.TICK_TIMER: + return { + ...state, + timer: state.timer - 1, + } + case Types.SET_TIMER: + return { + ...state, + timer: action.payload, + } default: return state } diff --git a/client/src/sockets.ts b/client/src/sockets.ts index 7de89b5e..501547c7 100644 --- a/client/src/sockets.ts +++ b/client/src/sockets.ts @@ -1,5 +1,5 @@ import io from 'socket.io-client' -import { setSlide } from './actions/competition' +import { setSlide, setTimer } from './actions/competition' import store from './store' let socket: SocketIOClient.Socket @@ -8,6 +8,10 @@ interface SyncSlideInterface { slide: number } +interface SyncTimerInterface { + timer: number +} + export const connect = () => { if (!socket) { socket = io('localhost:5000') @@ -17,8 +21,22 @@ export const connect = () => { socket.on('sync_slide', (data: SyncSlideInterface) => { store.dispatch(setSlide(data.slide)) }) + + socket.on('sync_timer', (data: SyncTimerInterface) => { + store.dispatch(setTimer(data.timer)) + }) } export const syncSlide = () => { - socket.emit('sync_slide', { slide: store.getState().competion.slide }) + socket.emit('sync_slide', { + slide: store.getState().competion.slide, + room: store.getState().competion.competitionID, + }) +} + +export const syncTimer = () => { + socket.emit('sync_timer', { + room: store.getState().competion.competitionID, + timer: store.getState().competion.timer, + }) } diff --git a/server/app/sockets.py b/server/app/sockets.py index cb1d776c..76011dc5 100644 --- a/server/app/sockets.py +++ b/server/app/sockets.py @@ -22,5 +22,11 @@ def join_competition(data): @sio.on("sync_slide") def sync_slide(data): - emit("sync_slide", {"slide": data["slide"]}, room="123456") - print("[Sync slide]") + emit("sync_slide", {"slide": data["slide"]}, room=data["room"]) + print(f"[Sync slide]: {data['slide']} -> {data['room']}") + + +@sio.on("sync_timer") +def sync_timer(data): + emit("sync_timer", {"timer": data["timer"]}, room=data["room"]) + print(f"[Sync timer]: {data['timer']} -> {data['room']}") -- GitLab From f2ef03182b54917551e837854d1d1fcf4305039f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Victor=20L=C3=B6fgren?= <victor.l0fgr3n@gmail.com> Date: Mon, 5 Apr 2021 21:43:07 +0200 Subject: [PATCH 04/20] Refactor competitionReducer a bit --- client/src/actions/competition.ts | 10 +++++++--- client/src/actions/types.ts | 3 --- client/src/reducers/competitionReducer.ts | 15 --------------- server/app/sockets.py | 4 ++-- 4 files changed, 9 insertions(+), 23 deletions(-) diff --git a/client/src/actions/competition.ts b/client/src/actions/competition.ts index d3010bed..b3c7d434 100644 --- a/client/src/actions/competition.ts +++ b/client/src/actions/competition.ts @@ -1,3 +1,4 @@ +import store from '../store' import types from './types' export const setSlide = (slide: number) => ({ @@ -6,11 +7,13 @@ export const setSlide = (slide: number) => ({ }) export const nextSlide = () => ({ - type: types.NEXT_SLIDE, + type: types.SET_SLIDE, + payload: store.getState().competion.slide + 1, }) export const prevSlide = () => ({ - type: types.PREV_SLIDE, + type: types.SET_SLIDE, + payload: store.getState().competion.slide - 1, }) export const setCompetitionID = (competitionID: string) => ({ @@ -19,7 +22,8 @@ export const setCompetitionID = (competitionID: string) => ({ }) export const tickTimer = () => ({ - type: types.TICK_TIMER, + type: types.SET_TIMER, + payload: store.getState().competion.timer - 1, }) export const setTimer = (timer: number) => ({ diff --git a/client/src/actions/types.ts b/client/src/actions/types.ts index 514f4d8e..c8a85764 100644 --- a/client/src/actions/types.ts +++ b/client/src/actions/types.ts @@ -18,9 +18,6 @@ export default { AXIOS_POST_SUCCESS: 'AXIOS_POST_SUCCESS', AXIOS_POST_ERROR: 'AXIOS_POST_ERROR', SET_SLIDE: 'SET_SLIDE', - NEXT_SLIDE: 'NEXT_SLIDE', - PREV_SLIDE: 'PREV_SLIDE', SET_COMPEITION_ID: 'SET_COMPETITION_ID', - TICK_TIMER: 'TICK_TIMER', SET_TIMER: 'SET_TIMER', } diff --git a/client/src/reducers/competitionReducer.ts b/client/src/reducers/competitionReducer.ts index a2de6903..19866e2d 100644 --- a/client/src/reducers/competitionReducer.ts +++ b/client/src/reducers/competitionReducer.ts @@ -22,26 +22,11 @@ export default function (state = initialState, action: AnyAction) { ...state, slide: action.payload, } - case Types.NEXT_SLIDE: - return { - ...state, - slide: state.slide + 1, - } - case Types.PREV_SLIDE: - return { - ...state, - slide: state.slide - 1, - } case Types.SET_COMPEITION_ID: return { ...state, competitionID: action.payload, } - case Types.TICK_TIMER: - return { - ...state, - timer: state.timer - 1, - } case Types.SET_TIMER: return { ...state, diff --git a/server/app/sockets.py b/server/app/sockets.py index 76011dc5..b4f97045 100644 --- a/server/app/sockets.py +++ b/server/app/sockets.py @@ -22,11 +22,11 @@ def join_competition(data): @sio.on("sync_slide") def sync_slide(data): - emit("sync_slide", {"slide": data["slide"]}, room=data["room"]) + emit("sync_slide", {"slide": data["slide"]}, room=data["room"], include_self=False) print(f"[Sync slide]: {data['slide']} -> {data['room']}") @sio.on("sync_timer") def sync_timer(data): - emit("sync_timer", {"timer": data["timer"]}, room=data["room"]) + emit("sync_timer", {"timer": data["timer"]}, room=data["room"], include_self=False) print(f"[Sync timer]: {data['timer']} -> {data['room']}") -- GitLab From fc59d94cf777e91809478a2f23d11235d5d27653 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Victor=20L=C3=B6fgren?= <victor.l0fgr3n@gmail.com> Date: Tue, 6 Apr 2021 10:46:05 +0200 Subject: [PATCH 05/20] Add proof-of-concept timer and slide sync --- client/src/actions/competition.ts | 17 ++++--- client/src/actions/types.ts | 1 + client/src/pages/views/JudgeViewPage.tsx | 28 ++++++------ client/src/pages/views/components/Timer.tsx | 50 +++++++++++++++++++++ client/src/reducers/allReducers.ts | 2 +- client/src/reducers/competitionReducer.ts | 22 +++++++-- client/src/sockets.ts | 22 +++++---- server/app/sockets.py | 16 ++++--- 8 files changed, 119 insertions(+), 39 deletions(-) create mode 100644 client/src/pages/views/components/Timer.tsx diff --git a/client/src/actions/competition.ts b/client/src/actions/competition.ts index b3c7d434..ef18927b 100644 --- a/client/src/actions/competition.ts +++ b/client/src/actions/competition.ts @@ -8,12 +8,12 @@ export const setSlide = (slide: number) => ({ export const nextSlide = () => ({ type: types.SET_SLIDE, - payload: store.getState().competion.slide + 1, + payload: store.getState().competition.slide + 1, }) export const prevSlide = () => ({ type: types.SET_SLIDE, - payload: store.getState().competion.slide - 1, + payload: store.getState().competition.slide - 1, }) export const setCompetitionID = (competitionID: string) => ({ @@ -21,12 +21,17 @@ export const setCompetitionID = (competitionID: string) => ({ payload: competitionID, }) -export const tickTimer = () => ({ +export const setTimer = (timer: number) => ({ type: types.SET_TIMER, - payload: store.getState().competion.timer - 1, + payload: timer, }) -export const setTimer = (timer: number) => ({ +export const tickTimer = (timestep: number) => ({ type: types.SET_TIMER, - payload: timer, + payload: store.getState().competition.timer.value - timestep, +}) + +export const setTimerEnabled = (enabled: boolean) => ({ + type: types.SET_TIMER_ENABLED, + payload: enabled, }) diff --git a/client/src/actions/types.ts b/client/src/actions/types.ts index c8a85764..3cc59479 100644 --- a/client/src/actions/types.ts +++ b/client/src/actions/types.ts @@ -20,4 +20,5 @@ export default { SET_SLIDE: 'SET_SLIDE', SET_COMPEITION_ID: 'SET_COMPETITION_ID', SET_TIMER: 'SET_TIMER', + SET_TIMER_ENABLED: 'SET_TIMER_ENABLED', } diff --git a/client/src/pages/views/JudgeViewPage.tsx b/client/src/pages/views/JudgeViewPage.tsx index 4d12e031..c1b0f11c 100644 --- a/client/src/pages/views/JudgeViewPage.tsx +++ b/client/src/pages/views/JudgeViewPage.tsx @@ -1,8 +1,8 @@ import React, { useEffect } from 'react' -import { nextSlide, prevSlide, setCompetitionID, setSlide, setTimer, tickTimer } from '../../actions/competition' +import { nextSlide, prevSlide, setCompetitionID, setSlide, setTimer, setTimerEnabled } from '../../actions/competition' import { useAppDispatch } from '../../hooks' import { connect, syncSlide, syncTimer } from '../../sockets' -import store from '../../store' +import Timer from './components/Timer' const JudgeViewPage: React.FC = () => { const dispatch = useAppDispatch() @@ -12,27 +12,25 @@ const JudgeViewPage: React.FC = () => { dispatch(setSlide(1)) dispatch(setTimer(5)) connect() - - const timerTimer = setInterval(() => { - dispatch(tickTimer()) - syncTimer() - - console.log(store.getState().competion.timer) - - if (store.getState().competion.timer === 0) { - clearInterval(timerTimer) - // TODO: lock the competitors slide when this happens - } - }, 1000) + // syncTimer() }, []) + useEffect + return ( <div> <div>Judge</div> + Slides: <button onClick={syncSlide}>Sync</button> <button onClick={() => dispatch(nextSlide())}>Next</button> <button onClick={() => dispatch(prevSlide())}>Prev</button> - <div>{store.getState().competion.slide}</div> + <br></br> + Timer: + <button onClick={syncTimer}>Sync</button> + <button onClick={() => dispatch(setTimerEnabled(true))}>Enable</button> + <button onClick={() => dispatch(setTimerEnabled(false))}>Disable</button> + <button onClick={() => dispatch(setTimer(5))}>5 Seconds</button> + <Timer></Timer> </div> ) } diff --git a/client/src/pages/views/components/Timer.tsx b/client/src/pages/views/components/Timer.tsx new file mode 100644 index 00000000..4cbdab5a --- /dev/null +++ b/client/src/pages/views/components/Timer.tsx @@ -0,0 +1,50 @@ +import React, { useEffect } from 'react' +import { connect } from 'react-redux' +import { setTimerEnabled, tickTimer } from '../../../actions/competition' +import { useAppDispatch } from '../../../hooks' + +const mapStateToProps = (state: any) => { + return { + timer: state.competition.timer, + } +} + +const mapDispatchToProps = (dispatch: any) => { + return { + tickTimer: () => dispatch(tickTimer(1)), + } +} + +let timerIntervalId: NodeJS.Timeout + +const Timer: React.FC = (props: any) => { + const dispatch = useAppDispatch() + + useEffect(() => { + if (props.timer.enabled) { + timerIntervalId = setInterval(() => { + if (!props.timer.enabled) { + clearInterval(timerIntervalId) + } else { + dispatch(tickTimer(1)) + } + }, 1000) + } + }, [props.timer.enabled]) + + useEffect(() => { + if (props.timer.value <= 0) { + clearInterval(timerIntervalId) + dispatch(setTimerEnabled(false)) + } + }, [props.timer.value]) + + return ( + <> + <div>Timer: {props.timer.value}</div> + <div>Enabled: {props.timer.enabled.toString()}</div> + </> + ) +} + +export default connect(mapStateToProps, mapDispatchToProps)(Timer) diff --git a/client/src/reducers/allReducers.ts b/client/src/reducers/allReducers.ts index 700720bc..4453ad92 100644 --- a/client/src/reducers/allReducers.ts +++ b/client/src/reducers/allReducers.ts @@ -12,7 +12,7 @@ const allReducers = combineReducers({ user: userReducer, UI: uiReducer, competitions: competitionsReducer, - competion: competitionReducer, + competition: competitionReducer, cities: citiesReducer, }) export default allReducers diff --git a/client/src/reducers/competitionReducer.ts b/client/src/reducers/competitionReducer.ts index 19866e2d..431ee504 100644 --- a/client/src/reducers/competitionReducer.ts +++ b/client/src/reducers/competitionReducer.ts @@ -1,17 +1,22 @@ import { AnyAction } from 'redux' import Types from '../actions/types' +interface TimerState { + value: number + enabled: boolean +} + interface CompetitionState { competitionID: string | null slide: number - timer: number + timer: TimerState isLocked: boolean } const initialState: CompetitionState = { competitionID: null, slide: 0, - timer: 0, + timer: { value: 0, enabled: false }, isLocked: false, } @@ -30,7 +35,18 @@ export default function (state = initialState, action: AnyAction) { case Types.SET_TIMER: return { ...state, - timer: action.payload, + timer: { + ...state.timer, + value: action.payload, + }, + } + case Types.SET_TIMER_ENABLED: + return { + ...state, + timer: { + ...state.timer, + enabled: action.payload, + }, } default: return state diff --git a/client/src/sockets.ts b/client/src/sockets.ts index 501547c7..7e3e3ad1 100644 --- a/client/src/sockets.ts +++ b/client/src/sockets.ts @@ -1,5 +1,5 @@ import io from 'socket.io-client' -import { setSlide, setTimer } from './actions/competition' +import { setSlide, setTimer, setTimerEnabled } from './actions/competition' import store from './store' let socket: SocketIOClient.Socket @@ -8,14 +8,19 @@ interface SyncSlideInterface { slide: number } +interface TimerInterface { + value: number + enabled: boolean +} + interface SyncTimerInterface { - timer: number + timer: TimerInterface } export const connect = () => { if (!socket) { socket = io('localhost:5000') - socket.emit('join_competition', { competitionID: store.getState().competion.competitionID }) + socket.emit('join_competition', { competitionID: store.getState().competition.competitionID }) } socket.on('sync_slide', (data: SyncSlideInterface) => { @@ -23,20 +28,21 @@ export const connect = () => { }) socket.on('sync_timer', (data: SyncTimerInterface) => { - store.dispatch(setTimer(data.timer)) + store.dispatch(setTimer(data.timer.value)) + store.dispatch(setTimerEnabled(data.timer.enabled)) }) } export const syncSlide = () => { socket.emit('sync_slide', { - slide: store.getState().competion.slide, - room: store.getState().competion.competitionID, + slide: store.getState().competition.slide, + competitionID: store.getState().competition.competitionID, }) } export const syncTimer = () => { socket.emit('sync_timer', { - room: store.getState().competion.competitionID, - timer: store.getState().competion.timer, + competitionID: store.getState().competition.competitionID, + timer: store.getState().competition.timer, }) } diff --git a/server/app/sockets.py b/server/app/sockets.py index b4f97045..d9407a69 100644 --- a/server/app/sockets.py +++ b/server/app/sockets.py @@ -1,5 +1,5 @@ from flask.globals import request -from flask_socketio import SocketIO, emit, join_room, leave_room +from flask_socketio import SocketIO, emit, join_room sio = SocketIO(cors_allowed_origins="http://localhost:3000") @@ -16,17 +16,21 @@ def disconnect(): @sio.on("join_competition") def join_competition(data): + competitionID = data["competitionID"] join_room(data["competitionID"]) - print(f"[Join room]: {request.sid} -> {data['competitionID']}") + print(f"[Join room]: {request.sid} -> {competitionID}") @sio.on("sync_slide") def sync_slide(data): - emit("sync_slide", {"slide": data["slide"]}, room=data["room"], include_self=False) - print(f"[Sync slide]: {data['slide']} -> {data['room']}") + slide, competitionID = data["slide"], data["competitionID"] + emit("sync_slide", {"slide": slide}, room=competitionID, include_self=False) + print(f"[Sync slide]: {slide} -> {competitionID}") @sio.on("sync_timer") def sync_timer(data): - emit("sync_timer", {"timer": data["timer"]}, room=data["room"], include_self=False) - print(f"[Sync timer]: {data['timer']} -> {data['room']}") + competitionID = data["competitionID"] + timer = data["timer"] + emit("sync_timer", {"timer": timer}, room=competitionID, include_self=False) + print(f"[Sync timer]: {competitionID=} {timer=}") -- GitLab From 1c1692fc1773a296f0ef14f08b82789461d52932 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Victor=20L=C3=B6fgren?= <victor.l0fgr3n@gmail.com> Date: Tue, 6 Apr 2021 12:01:36 +0200 Subject: [PATCH 06/20] Fix disable function in timer --- client/src/pages/views/components/Timer.tsx | 8 +++----- client/src/sockets.ts | 2 +- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/client/src/pages/views/components/Timer.tsx b/client/src/pages/views/components/Timer.tsx index 4cbdab5a..26f42133 100644 --- a/client/src/pages/views/components/Timer.tsx +++ b/client/src/pages/views/components/Timer.tsx @@ -23,12 +23,10 @@ const Timer: React.FC = (props: any) => { useEffect(() => { if (props.timer.enabled) { timerIntervalId = setInterval(() => { - if (!props.timer.enabled) { - clearInterval(timerIntervalId) - } else { - dispatch(tickTimer(1)) - } + dispatch(tickTimer(1)) }, 1000) + } else { + clearInterval(timerIntervalId) } }, [props.timer.enabled]) diff --git a/client/src/sockets.ts b/client/src/sockets.ts index 7e3e3ad1..12dcbb3e 100644 --- a/client/src/sockets.ts +++ b/client/src/sockets.ts @@ -28,8 +28,8 @@ export const connect = () => { }) socket.on('sync_timer', (data: SyncTimerInterface) => { - store.dispatch(setTimer(data.timer.value)) store.dispatch(setTimerEnabled(data.timer.enabled)) + store.dispatch(setTimer(data.timer.value)) }) } -- GitLab From 02c7c5fafb25370804c3473304aca4e028fb4215 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Victor=20L=C3=B6fgren?= <viclo211@student.liu.se> Date: Tue, 13 Apr 2021 09:33:21 +0200 Subject: [PATCH 07/20] Save code in redux --- client/package.json | 2 +- client/src/actions/presentation.ts | 4 ++++ client/src/actions/types.ts | 1 + client/src/pages/views/PresenterViewPage.tsx | 4 ++++ client/src/reducers/presentationReducer.ts | 7 +++++++ client/src/sockets.ts | 8 ++++---- server/app/sockets.py | 6 +++--- 7 files changed, 24 insertions(+), 8 deletions(-) diff --git a/client/package.json b/client/package.json index bbc78d52..7129c30e 100644 --- a/client/package.json +++ b/client/package.json @@ -92,4 +92,4 @@ ] }, "proxy": "http://localhost:5000/api/" -} \ No newline at end of file +} diff --git a/client/src/actions/presentation.ts b/client/src/actions/presentation.ts index 9e482d2b..f53d34cc 100644 --- a/client/src/actions/presentation.ts +++ b/client/src/actions/presentation.ts @@ -42,3 +42,7 @@ export const setCurrentSlidePrevious = () => (dispatch: AppDispatch) => { export const setCurrentSlideNext = () => (dispatch: AppDispatch) => { dispatch({ type: Types.SET_PRESENTATION_SLIDE_NEXT }) } + +export const setPresentationCode = (code: string) => (dispatch: AppDispatch) => { + dispatch({ type: Types.SET_PRESENTATION_CODE, payload: code }) +} diff --git a/client/src/actions/types.ts b/client/src/actions/types.ts index 7d0ad17c..7698dc94 100644 --- a/client/src/actions/types.ts +++ b/client/src/actions/types.ts @@ -20,6 +20,7 @@ export default { SET_PRESENTATION_SLIDE_PREVIOUS: 'SET_PRESENTATION_SLIDE_PREVIOUS', SET_PRESENTATION_SLIDE_NEXT: 'SET_PRESENTATION_SLIDE_NEXT', SET_PRESENTATION_TEAMS: 'SET_PRESENTATION_TEAMS', + SET_PRESENTATION_CODE: 'SET_PRESENTATION_CODE', SET_CITIES: 'SET_CITIES', SET_CITIES_TOTAL: 'SET_CITIES_TOTAL', SET_CITIES_COUNT: 'SET_CITIES_COUNT', diff --git a/client/src/pages/views/PresenterViewPage.tsx b/client/src/pages/views/PresenterViewPage.tsx index 131bde22..8c73c420 100644 --- a/client/src/pages/views/PresenterViewPage.tsx +++ b/client/src/pages/views/PresenterViewPage.tsx @@ -7,9 +7,11 @@ import { getPresentationTeams, setCurrentSlideNext, setCurrentSlidePrevious, + setPresentationCode, } from '../../actions/presentation' import { useAppDispatch, useAppSelector } from '../../hooks' import { ViewParams } from '../../interfaces/ViewParams' +import { connectSocket } from '../../sockets' import SlideDisplay from './components/SlideDisplay' import { PresenterButton, PresenterContainer, PresenterFooter, PresenterHeader } from './styled' @@ -22,6 +24,8 @@ const PresenterViewPage: React.FC = () => { useEffect(() => { dispatch(getPresentationCompetition(id)) dispatch(getPresentationTeams(id)) + dispatch(setPresentationCode(code)) + connectSocket() }, []) const handleOpenPopover = (event: React.MouseEvent<HTMLButtonElement>) => { setAnchorEl(event.currentTarget) diff --git a/client/src/reducers/presentationReducer.ts b/client/src/reducers/presentationReducer.ts index d71bcafb..8b56a09b 100644 --- a/client/src/reducers/presentationReducer.ts +++ b/client/src/reducers/presentationReducer.ts @@ -8,6 +8,7 @@ interface PresentationState { competition: RichCompetition slide: Slide teams: Team[] + code: string } const initialState: PresentationState = { @@ -30,6 +31,7 @@ const initialState: PresentationState = { title: '', }, teams: [], + code: '', } export default function (state = initialState, action: AnyAction) { @@ -45,6 +47,11 @@ export default function (state = initialState, action: AnyAction) { ...state, teams: action.payload as Team[], } + case Types.SET_PRESENTATION_CODE: + return { + ...state, + code: action.payload, + } case Types.SET_PRESENTATION_SLIDE: return { ...state, diff --git a/client/src/sockets.ts b/client/src/sockets.ts index 12dcbb3e..f07956a0 100644 --- a/client/src/sockets.ts +++ b/client/src/sockets.ts @@ -17,10 +17,10 @@ interface SyncTimerInterface { timer: TimerInterface } -export const connect = () => { +export const connectSocket = () => { if (!socket) { socket = io('localhost:5000') - socket.emit('join_competition', { competitionID: store.getState().competition.competitionID }) + socket.emit('join_competition', { code: store.getState().presentation.code }) } socket.on('sync_slide', (data: SyncSlideInterface) => { @@ -33,14 +33,14 @@ export const connect = () => { }) } -export const syncSlide = () => { +export const syncSlideSocket = () => { socket.emit('sync_slide', { slide: store.getState().competition.slide, competitionID: store.getState().competition.competitionID, }) } -export const syncTimer = () => { +export const syncTimerSocket = () => { socket.emit('sync_timer', { competitionID: store.getState().competition.competitionID, timer: store.getState().competition.timer, diff --git a/server/app/sockets.py b/server/app/sockets.py index d9407a69..f526c21a 100644 --- a/server/app/sockets.py +++ b/server/app/sockets.py @@ -16,9 +16,9 @@ def disconnect(): @sio.on("join_competition") def join_competition(data): - competitionID = data["competitionID"] - join_room(data["competitionID"]) - print(f"[Join room]: {request.sid} -> {competitionID}") + code = data["code"] + join_room(data["code"]) + print(f"[Join room]: {request.sid} -> {code}") @sio.on("sync_slide") -- GitLab From daca37d2773fb407e6eeca1104c338bd69ad1cbd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Victor=20L=C3=B6fgren?= <viclo211@student.liu.se> Date: Tue, 13 Apr 2021 10:45:48 +0200 Subject: [PATCH 08/20] Add functionality to sync slide --- client/src/actions/presentation.ts | 4 ++ client/src/actions/types.ts | 1 + client/src/pages/views/JudgeViewPage.tsx | 12 ++++- client/src/pages/views/PresenterViewPage.tsx | 17 ++++++-- client/src/reducers/presentationReducer.ts | 7 +++ client/src/sockets.ts | 46 ++++++++++---------- server/app/sockets.py | 7 +-- 7 files changed, 62 insertions(+), 32 deletions(-) diff --git a/client/src/actions/presentation.ts b/client/src/actions/presentation.ts index f53d34cc..70345f42 100644 --- a/client/src/actions/presentation.ts +++ b/client/src/actions/presentation.ts @@ -43,6 +43,10 @@ export const setCurrentSlideNext = () => (dispatch: AppDispatch) => { dispatch({ type: Types.SET_PRESENTATION_SLIDE_NEXT }) } +export const setCurrentSlideByOrder = (order: number) => (dispatch: AppDispatch) => { + dispatch({ type: Types.SET_PRESENTATION_SLIDE_BY_ORDER, payload: order }) +} + export const setPresentationCode = (code: string) => (dispatch: AppDispatch) => { dispatch({ type: Types.SET_PRESENTATION_CODE, payload: code }) } diff --git a/client/src/actions/types.ts b/client/src/actions/types.ts index 7698dc94..afd71e8b 100644 --- a/client/src/actions/types.ts +++ b/client/src/actions/types.ts @@ -19,6 +19,7 @@ export default { SET_PRESENTATION_SLIDE: 'SET_PRESENTATION_SLIDE', SET_PRESENTATION_SLIDE_PREVIOUS: 'SET_PRESENTATION_SLIDE_PREVIOUS', SET_PRESENTATION_SLIDE_NEXT: 'SET_PRESENTATION_SLIDE_NEXT', + SET_PRESENTATION_SLIDE_BY_ORDER: 'SET_PRESENTATION_SLIDE_BY_ORDER', SET_PRESENTATION_TEAMS: 'SET_PRESENTATION_TEAMS', SET_PRESENTATION_CODE: 'SET_PRESENTATION_CODE', SET_CITIES: 'SET_CITIES', diff --git a/client/src/pages/views/JudgeViewPage.tsx b/client/src/pages/views/JudgeViewPage.tsx index 3d373c85..5c664557 100644 --- a/client/src/pages/views/JudgeViewPage.tsx +++ b/client/src/pages/views/JudgeViewPage.tsx @@ -2,9 +2,15 @@ import { Divider, List, ListItemText } from '@material-ui/core' import { createStyles, makeStyles, Theme } from '@material-ui/core/styles' import React, { useEffect, useState } from 'react' import { useParams } from 'react-router-dom' -import { getPresentationCompetition, getPresentationTeams, setCurrentSlide } from '../../actions/presentation' +import { + getPresentationCompetition, + getPresentationTeams, + setCurrentSlide, + setPresentationCode, +} from '../../actions/presentation' import { useAppDispatch, useAppSelector } from '../../hooks' import { ViewParams } from '../../interfaces/ViewParams' +import { connect } from '../../sockets' import { SlideListItem } from '../presentationEditor/styled' import JudgeScoreDisplay from './components/JudgeScoreDisplay' import SlideDisplay from './components/SlideDisplay' @@ -15,7 +21,7 @@ import { JudgeQuestionsLabel, JudgeToolbar, LeftDrawer, - RightDrawer + RightDrawer, } from './styled' const leftDrawerWidth = 150 @@ -41,6 +47,8 @@ const JudgeViewPage: React.FC = () => { useEffect(() => { dispatch(getPresentationCompetition(id)) dispatch(getPresentationTeams(id)) + dispatch(setPresentationCode(code)) + connect() }, []) const teams = useAppSelector((state) => state.presentation.teams) const slides = useAppSelector((state) => state.presentation.competition.slides) diff --git a/client/src/pages/views/PresenterViewPage.tsx b/client/src/pages/views/PresenterViewPage.tsx index 8c73c420..ee569110 100644 --- a/client/src/pages/views/PresenterViewPage.tsx +++ b/client/src/pages/views/PresenterViewPage.tsx @@ -11,7 +11,7 @@ import { } from '../../actions/presentation' import { useAppDispatch, useAppSelector } from '../../hooks' import { ViewParams } from '../../interfaces/ViewParams' -import { connectSocket } from '../../sockets' +import { connect, syncSlide } from '../../sockets' import SlideDisplay from './components/SlideDisplay' import { PresenterButton, PresenterContainer, PresenterFooter, PresenterHeader } from './styled' @@ -25,7 +25,7 @@ const PresenterViewPage: React.FC = () => { dispatch(getPresentationCompetition(id)) dispatch(getPresentationTeams(id)) dispatch(setPresentationCode(code)) - connectSocket() + connect() }, []) const handleOpenPopover = (event: React.MouseEvent<HTMLButtonElement>) => { setAnchorEl(event.currentTarget) @@ -33,6 +33,15 @@ const PresenterViewPage: React.FC = () => { const handleClose = () => { setAnchorEl(null) } + const handleNextSlidePressed = () => { + dispatch(setCurrentSlideNext()) + syncSlide() + } + const handlePreviousSlidePressed = () => { + dispatch(setCurrentSlidePrevious()) + syncSlide() + } + return ( <PresenterContainer> <PresenterHeader> @@ -45,10 +54,10 @@ const PresenterViewPage: React.FC = () => { </PresenterHeader> <SlideDisplay /> <PresenterFooter> - <PresenterButton onClick={() => dispatch(setCurrentSlidePrevious())} variant="contained"> + <PresenterButton onClick={handlePreviousSlidePressed} variant="contained"> <ChevronRightIcon fontSize="large" /> </PresenterButton> - <PresenterButton onClick={() => dispatch(setCurrentSlideNext())} variant="contained"> + <PresenterButton onClick={handleNextSlidePressed} variant="contained"> <ChevronRightIcon fontSize="large" /> </PresenterButton> </PresenterFooter> diff --git a/client/src/reducers/presentationReducer.ts b/client/src/reducers/presentationReducer.ts index 8b56a09b..d14e5623 100644 --- a/client/src/reducers/presentationReducer.ts +++ b/client/src/reducers/presentationReducer.ts @@ -73,6 +73,13 @@ export default function (state = initialState, action: AnyAction) { } } return state + case Types.SET_PRESENTATION_SLIDE_BY_ORDER: + if (0 <= action.payload && action.payload < state.competition.slides.length) + return { + ...state, + slide: state.competition.slides[action.payload], + } + return state default: return state } diff --git a/client/src/sockets.ts b/client/src/sockets.ts index f07956a0..b0efdcb6 100644 --- a/client/src/sockets.ts +++ b/client/src/sockets.ts @@ -1,5 +1,5 @@ import io from 'socket.io-client' -import { setSlide, setTimer, setTimerEnabled } from './actions/competition' +import { setCurrentSlideByOrder } from './actions/presentation' import store from './store' let socket: SocketIOClient.Socket @@ -8,41 +8,41 @@ interface SyncSlideInterface { slide: number } -interface TimerInterface { - value: number - enabled: boolean -} +// interface TimerInterface { +// value: number +// enabled: boolean +// } -interface SyncTimerInterface { - timer: TimerInterface -} +// interface SyncTimerInterface { +// timer: TimerInterface +// } -export const connectSocket = () => { +export const connect = () => { if (!socket) { socket = io('localhost:5000') socket.emit('join_competition', { code: store.getState().presentation.code }) } socket.on('sync_slide', (data: SyncSlideInterface) => { - store.dispatch(setSlide(data.slide)) + setCurrentSlideByOrder(data.slide)(store.dispatch) }) - socket.on('sync_timer', (data: SyncTimerInterface) => { - store.dispatch(setTimerEnabled(data.timer.enabled)) - store.dispatch(setTimer(data.timer.value)) - }) + // socket.on('sync_timer', (data: SyncTimerInterface) => { + // store.dispatch(setTimerEnabled(data.timer.enabled)) + // store.dispatch(setTimer(data.timer.value)) + // }) } -export const syncSlideSocket = () => { +export const syncSlide = () => { socket.emit('sync_slide', { - slide: store.getState().competition.slide, - competitionID: store.getState().competition.competitionID, + code: store.getState().presentation.code, + slide: store.getState().presentation.slide.order, }) } -export const syncTimerSocket = () => { - socket.emit('sync_timer', { - competitionID: store.getState().competition.competitionID, - timer: store.getState().competition.timer, - }) -} +// export const syncTimerSocket = () => { +// socket.emit('sync_timer', { +// competitionID: store.getState().competition.competitionID, +// timer: store.getState().competition.timer, +// }) +// } diff --git a/server/app/sockets.py b/server/app/sockets.py index f526c21a..d06ae769 100644 --- a/server/app/sockets.py +++ b/server/app/sockets.py @@ -23,9 +23,10 @@ def join_competition(data): @sio.on("sync_slide") def sync_slide(data): - slide, competitionID = data["slide"], data["competitionID"] - emit("sync_slide", {"slide": slide}, room=competitionID, include_self=False) - print(f"[Sync slide]: {slide} -> {competitionID}") + code = data["code"] + slide = data["slide"] + emit("sync_slide", {"slide": slide}, room=code, include_self=False) + print(f"[Sync slide]: {slide} -> {code}") @sio.on("sync_timer") -- GitLab From 8ff4d82ac72aebf0c6a6202c2f1b6ef749d8e4c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Victor=20L=C3=B6fgren?= <viclo211@student.liu.se> Date: Wed, 14 Apr 2021 10:10:06 +0200 Subject: [PATCH 09/20] Add sync timer (not finished) --- client/src/pages/views/JudgeViewPage.tsx | 2 + client/src/pages/views/components/Timer.tsx | 14 ++++++- client/src/sockets.ts | 41 +++++++++++---------- server/app/sockets.py | 31 +++++++++++++--- 4 files changed, 61 insertions(+), 27 deletions(-) diff --git a/client/src/pages/views/JudgeViewPage.tsx b/client/src/pages/views/JudgeViewPage.tsx index 5c664557..c6601532 100644 --- a/client/src/pages/views/JudgeViewPage.tsx +++ b/client/src/pages/views/JudgeViewPage.tsx @@ -14,6 +14,7 @@ import { connect } from '../../sockets' import { SlideListItem } from '../presentationEditor/styled' import JudgeScoreDisplay from './components/JudgeScoreDisplay' import SlideDisplay from './components/SlideDisplay' +import Timer from './components/Timer' import { Content, JudgeAnswersLabel, @@ -73,6 +74,7 @@ const JudgeViewPage: React.FC = () => { anchor="left" > <div className={classes.toolbar} /> + <Timer></Timer> <List> {slides.map((slide, index) => ( <SlideListItem diff --git a/client/src/pages/views/components/Timer.tsx b/client/src/pages/views/components/Timer.tsx index 26f42133..4fe85973 100644 --- a/client/src/pages/views/components/Timer.tsx +++ b/client/src/pages/views/components/Timer.tsx @@ -1,7 +1,8 @@ import React, { useEffect } from 'react' import { connect } from 'react-redux' -import { setTimerEnabled, tickTimer } from '../../../actions/competition' +import { setTimer, setTimerEnabled, tickTimer } from '../../../actions/competition' import { useAppDispatch } from '../../../hooks' +import { syncTimer } from '../../../sockets' const mapStateToProps = (state: any) => { return { @@ -41,6 +42,17 @@ const Timer: React.FC = (props: any) => { <> <div>Timer: {props.timer.value}</div> <div>Enabled: {props.timer.enabled.toString()}</div> + <button onClick={syncTimer}>Sync</button> + <button onClick={() => dispatch(setTimer(5))}>5 Sec</button> + <button + onClick={() => { + dispatch(setTimer(5)) + dispatch(setTimerEnabled(true)) + syncTimer() + }} + > + Sync and 5 sec + </button> </> ) } diff --git a/client/src/sockets.ts b/client/src/sockets.ts index b0efdcb6..4d1600ec 100644 --- a/client/src/sockets.ts +++ b/client/src/sockets.ts @@ -1,36 +1,37 @@ import io from 'socket.io-client' +import { setTimer, setTimerEnabled } from './actions/competition' import { setCurrentSlideByOrder } from './actions/presentation' import store from './store' -let socket: SocketIOClient.Socket - interface SyncSlideInterface { slide: number } -// interface TimerInterface { -// value: number -// enabled: boolean -// } +interface TimerInterface { + value: number + enabled: boolean +} + +interface SyncTimerInterface { + timer: TimerInterface +} -// interface SyncTimerInterface { -// timer: TimerInterface -// } +let socket: SocketIOClient.Socket export const connect = () => { if (!socket) { socket = io('localhost:5000') - socket.emit('join_competition', { code: store.getState().presentation.code }) + socket.emit('join_presentation', { code: store.getState().presentation.code }) } socket.on('sync_slide', (data: SyncSlideInterface) => { setCurrentSlideByOrder(data.slide)(store.dispatch) }) - // socket.on('sync_timer', (data: SyncTimerInterface) => { - // store.dispatch(setTimerEnabled(data.timer.enabled)) - // store.dispatch(setTimer(data.timer.value)) - // }) + socket.on('sync_timer', (data: SyncTimerInterface) => { + store.dispatch(setTimerEnabled(data.timer.enabled)) + store.dispatch(setTimer(data.timer.value)) + }) } export const syncSlide = () => { @@ -40,9 +41,9 @@ export const syncSlide = () => { }) } -// export const syncTimerSocket = () => { -// socket.emit('sync_timer', { -// competitionID: store.getState().competition.competitionID, -// timer: store.getState().competition.timer, -// }) -// } +export const syncTimer = () => { + socket.emit('sync_timer', { + code: store.getState().presentation.code, + timer: store.getState().competition.timer, + }) +} diff --git a/server/app/sockets.py b/server/app/sockets.py index d06ae769..830c2489 100644 --- a/server/app/sockets.py +++ b/server/app/sockets.py @@ -3,6 +3,20 @@ from flask_socketio import SocketIO, emit, join_room sio = SocketIO(cors_allowed_origins="http://localhost:3000") +# competitions = { +# "code": { +# "clients": { +# "id": { +# "type": "judge", +# }, +# "id2": {}, +# }, +# "current_slide": 2, +# "timer": {"enabled": True, "start_value": 100, "value": 10}, +# }, +# "code1": {}, +# } + @sio.on("connect") def connect(): @@ -14,11 +28,16 @@ def disconnect(): print(f"[Disconnected]: {request.sid}") -@sio.on("join_competition") -def join_competition(data): +@sio.on("join_presentation") +def join_presentation(data): code = data["code"] + # client_type = data["type"] + + # if client_type == "judge": + # judges.append(request.sid) + join_room(data["code"]) - print(f"[Join room]: {request.sid} -> {code}") + print(f"[Join presentation]: {request.sid} -> {code}") @sio.on("sync_slide") @@ -31,7 +50,7 @@ def sync_slide(data): @sio.on("sync_timer") def sync_timer(data): - competitionID = data["competitionID"] + code = data["code"] timer = data["timer"] - emit("sync_timer", {"timer": timer}, room=competitionID, include_self=False) - print(f"[Sync timer]: {competitionID=} {timer=}") + emit("sync_timer", {"timer": timer}, room=code, include_self=False) + print(f"[Sync timer]: {code=} {timer=}") -- GitLab From 6a0b929a8a239ef87c85150f71e3e463f18e87d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Victor=20L=C3=B6fgren?= <viclo211@student.liu.se> Date: Sun, 18 Apr 2021 00:03:23 +0200 Subject: [PATCH 10/20] Add presentations in backend --- client/package-lock.json | 12 +-- client/package.json | 2 +- client/src/pages/views/JudgeViewPage.tsx | 4 - client/src/pages/views/PresenterViewPage.tsx | 20 ++-- .../src/pages/views/components/SocketTest.tsx | 79 ++++++++++++++ client/src/sockets.ts | 47 +++++--- server/app/__init__.py | 2 +- server/app/core/sockets.py | 100 +++++++++++++++--- server/app/sockets.py | 56 ---------- server/populate.py | 2 +- 10 files changed, 214 insertions(+), 110 deletions(-) create mode 100644 client/src/pages/views/components/SocketTest.tsx delete mode 100644 server/app/sockets.py diff --git a/client/package-lock.json b/client/package-lock.json index 52dd6723..667b7585 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -6082,9 +6082,9 @@ } }, "engine.io-client": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-5.0.0.tgz", - "integrity": "sha512-e6GK0Fqvq45Nu/j7YdIVqXtDPvlsggAcfml3QiEiGdJ1qeh7IQU6knxSN3+yy9BmbnXtIfjo1hK4MFyHKdc9mQ==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-5.0.1.tgz", + "integrity": "sha512-CQtGN3YwfvbxVwpPugcsHe5rHT4KgT49CEcQppNtu9N7WxbPN0MAG27lGaem7bvtCFtGNLSL+GEqXsFSz36jTg==", "requires": { "base64-arraybuffer": "0.1.4", "component-emitter": "~1.3.0", @@ -15263,9 +15263,9 @@ } }, "socket.io-client": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.0.0.tgz", - "integrity": "sha512-27yQxmXJAEYF19Ygyl8FPJ0if0wegpSmkIIbrWJeI7n7ST1JyH8bbD5v3fjjGY5cfCanACJ3dARUAyiVFNrlTQ==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.0.1.tgz", + "integrity": "sha512-6AkaEG5zrVuSVW294cH1chioag9i1OqnCYjKwTc3EBGXbnyb98Lw7yMa40ifLjFj3y6fsFKsd0llbUZUCRf3Qw==", "requires": { "@types/component-emitter": "^1.2.10", "backo2": "~1.0.2", diff --git a/client/package.json b/client/package.json index 067c9846..7c354ece 100644 --- a/client/package.json +++ b/client/package.json @@ -31,9 +31,9 @@ "react-scripts": "4.0.2", "redux": "^4.0.5", "redux-devtools-extension": "^2.13.8", - "socket.io-client": "^4.0.0", "redux-mock-store": "^1.5.4", "redux-thunk": "^2.3.0", + "socket.io-client": "^4.0.1", "styled-components": "^5.2.1", "typescript": "^4.1.3", "web-vitals": "^1.1.0", diff --git a/client/src/pages/views/JudgeViewPage.tsx b/client/src/pages/views/JudgeViewPage.tsx index c6601532..12e866a0 100644 --- a/client/src/pages/views/JudgeViewPage.tsx +++ b/client/src/pages/views/JudgeViewPage.tsx @@ -10,11 +10,9 @@ import { } from '../../actions/presentation' import { useAppDispatch, useAppSelector } from '../../hooks' import { ViewParams } from '../../interfaces/ViewParams' -import { connect } from '../../sockets' import { SlideListItem } from '../presentationEditor/styled' import JudgeScoreDisplay from './components/JudgeScoreDisplay' import SlideDisplay from './components/SlideDisplay' -import Timer from './components/Timer' import { Content, JudgeAnswersLabel, @@ -49,7 +47,6 @@ const JudgeViewPage: React.FC = () => { dispatch(getPresentationCompetition(id)) dispatch(getPresentationTeams(id)) dispatch(setPresentationCode(code)) - connect() }, []) const teams = useAppSelector((state) => state.presentation.teams) const slides = useAppSelector((state) => state.presentation.competition.slides) @@ -74,7 +71,6 @@ const JudgeViewPage: React.FC = () => { anchor="left" > <div className={classes.toolbar} /> - <Timer></Timer> <List> {slides.map((slide, index) => ( <SlideListItem diff --git a/client/src/pages/views/PresenterViewPage.tsx b/client/src/pages/views/PresenterViewPage.tsx index ee569110..0958bc51 100644 --- a/client/src/pages/views/PresenterViewPage.tsx +++ b/client/src/pages/views/PresenterViewPage.tsx @@ -2,17 +2,11 @@ import { List, ListItem, Popover } from '@material-ui/core' import ChevronRightIcon from '@material-ui/icons/ChevronRight' import React, { useEffect } from 'react' import { useHistory, useParams } from 'react-router-dom' -import { - getPresentationCompetition, - getPresentationTeams, - setCurrentSlideNext, - setCurrentSlidePrevious, - setPresentationCode, -} from '../../actions/presentation' +import { getPresentationCompetition, getPresentationTeams, setPresentationCode } from '../../actions/presentation' import { useAppDispatch, useAppSelector } from '../../hooks' import { ViewParams } from '../../interfaces/ViewParams' -import { connect, syncSlide } from '../../sockets' import SlideDisplay from './components/SlideDisplay' +import SocketTest from './components/SocketTest' import { PresenterButton, PresenterContainer, PresenterFooter, PresenterHeader } from './styled' const PresenterViewPage: React.FC = () => { @@ -25,7 +19,6 @@ const PresenterViewPage: React.FC = () => { dispatch(getPresentationCompetition(id)) dispatch(getPresentationTeams(id)) dispatch(setPresentationCode(code)) - connect() }, []) const handleOpenPopover = (event: React.MouseEvent<HTMLButtonElement>) => { setAnchorEl(event.currentTarget) @@ -34,12 +27,12 @@ const PresenterViewPage: React.FC = () => { setAnchorEl(null) } const handleNextSlidePressed = () => { - dispatch(setCurrentSlideNext()) - syncSlide() + // dispatch(setCurrentSlideNext()) + // syncSlide() } const handlePreviousSlidePressed = () => { - dispatch(setCurrentSlidePrevious()) - syncSlide() + // dispatch(setCurrentSlidePrevious()) + // syncSlide() } return ( @@ -57,6 +50,7 @@ const PresenterViewPage: React.FC = () => { <PresenterButton onClick={handlePreviousSlidePressed} variant="contained"> <ChevronRightIcon fontSize="large" /> </PresenterButton> + <SocketTest></SocketTest> <PresenterButton onClick={handleNextSlidePressed} variant="contained"> <ChevronRightIcon fontSize="large" /> </PresenterButton> diff --git a/client/src/pages/views/components/SocketTest.tsx b/client/src/pages/views/components/SocketTest.tsx new file mode 100644 index 00000000..9e7e5379 --- /dev/null +++ b/client/src/pages/views/components/SocketTest.tsx @@ -0,0 +1,79 @@ +import React, { useEffect } from 'react' +import { connect } from 'react-redux' +import { setTimerEnabled, tickTimer } from '../../../actions/competition' +import { getPresentationCompetition, getPresentationTeams } from '../../../actions/presentation' +import { useAppDispatch } from '../../../hooks' +import { + socket_connect, + socket_end_presentation, + socket_join_presentation, + socket_set_slide, + socket_start_presentation, +} from '../../../sockets' + +const mapStateToProps = (state: any) => { + return { + timer: state.competition.timer, + slide_order: state.presentation.slide.order, + } +} + +const mapDispatchToProps = (dispatch: any) => { + return { + tickTimer: () => dispatch(tickTimer(1)), + } +} + +let timerIntervalId: NodeJS.Timeout + +const SocketTest: React.FC = (props: any) => { + const dispatch = useAppDispatch() + + useEffect(() => { + socket_connect() + dispatch(getPresentationCompetition('1')) // TODO: Use ID of item_code gotten from auth/login/<code> api call + dispatch(getPresentationTeams('1')) // TODO: Use ID of item_code gotten from auth/login/<code> api call + }, []) + + useEffect(() => { + if (props.timer.enabled) { + timerIntervalId = setInterval(() => { + dispatch(tickTimer(1)) + }, 1000) + } else { + clearInterval(timerIntervalId) + } + }, [props.timer.enabled]) + + useEffect(() => { + if (props.timer.value <= 0) { + clearInterval(timerIntervalId) + dispatch(setTimerEnabled(false)) + } + }, [props.timer.value]) + + return ( + <> + <button onClick={socket_start_presentation}>Start presentation</button> + <button onClick={socket_join_presentation}>Join presentation</button> + <button onClick={socket_end_presentation}>End presentation</button> + <button onClick={() => socket_set_slide(1)}>Set slide</button> {/* TODO: Send next slide */} + {/* <div>Current slide: {props.slide_order}</div> */} + {/* <div>Timer: {props.timer.value}</div> + <div>Enabled: {props.timer.enabled.toString()}</div> + <button onClick={syncTimer}>Sync</button> + <button onClick={() => dispatch(setTimer(5))}>5 Sec</button> + <button + onClick={() => { + dispatch(setTimer(5)) + dispatch(setTimerEnabled(true)) + syncTimer() + }} + > + Sync and 5 sec + </button> */} + </> + ) +} + +export default connect(mapStateToProps, mapDispatchToProps)(SocketTest) diff --git a/client/src/sockets.ts b/client/src/sockets.ts index 4d1600ec..f9c2ee48 100644 --- a/client/src/sockets.ts +++ b/client/src/sockets.ts @@ -3,8 +3,8 @@ import { setTimer, setTimerEnabled } from './actions/competition' import { setCurrentSlideByOrder } from './actions/presentation' import store from './store' -interface SyncSlideInterface { - slide: number +interface SetSlideInterface { + slide_order: number } interface TimerInterface { @@ -18,29 +18,46 @@ interface SyncTimerInterface { let socket: SocketIOClient.Socket -export const connect = () => { +export const socket_connect = () => { if (!socket) { socket = io('localhost:5000') - socket.emit('join_presentation', { code: store.getState().presentation.code }) + + socket.on('set_slide', (data: SetSlideInterface) => { + setCurrentSlideByOrder(data.slide_order)(store.dispatch) + }) + + socket.on('sync_timer', (data: SyncTimerInterface) => { + store.dispatch(setTimerEnabled(data.timer.enabled)) + store.dispatch(setTimer(data.timer.value)) + }) + + socket.on('end_presentation', () => { + socket.disconnect() + }) } +} - socket.on('sync_slide', (data: SyncSlideInterface) => { - setCurrentSlideByOrder(data.slide)(store.dispatch) - }) +export const socket_join_presentation = () => { + socket.emit('join_presentation', { code: '8WZ12G' }) // TODO: Send code gotten from auth/login/<code> api call +} - socket.on('sync_timer', (data: SyncTimerInterface) => { - store.dispatch(setTimerEnabled(data.timer.enabled)) - store.dispatch(setTimer(data.timer.value)) - }) +export const socket_start_presentation = () => { + socket.emit('start_presentation', { competition_id: 1 }) // TODO: Send current competition id } -export const syncSlide = () => { - socket.emit('sync_slide', { - code: store.getState().presentation.code, - slide: store.getState().presentation.slide.order, +export const socket_end_presentation = () => { + socket.emit('end_presentation', { competition_id: 1 }) // TODO: Send current competition id +} + +export const socket_set_slide = (slide_order: number) => { + socket.emit('set_slide', { + competition_id: 1, // TODO: Send current competition id + slide_order: slide_order, }) } +// Add socket_next_slide and socket_prev_slide + export const syncTimer = () => { socket.emit('sync_timer', { code: store.getState().presentation.code, diff --git a/server/app/__init__.py b/server/app/__init__.py index 54c2470b..8eebb113 100644 --- a/server/app/__init__.py +++ b/server/app/__init__.py @@ -19,7 +19,7 @@ def create_app(config_name="configmodule.DevelopmentConfig"): ma.init_app(app) configure_uploads(app, (MediaDTO.image_set,)) - from .sockets import sio + from app.core.sockets import sio sio.init_app(app) diff --git a/server/app/core/sockets.py b/server/app/core/sockets.py index d9407a69..903f49c6 100644 --- a/server/app/core/sockets.py +++ b/server/app/core/sockets.py @@ -1,8 +1,16 @@ +import app.database.controller as dbc +from app.core import db +from app.database.models import ViewType from flask.globals import request from flask_socketio import SocketIO, emit, join_room +# Presentation is an active competition + + sio = SocketIO(cors_allowed_origins="http://localhost:3000") +presentations = {} + @sio.on("connect") def connect(): @@ -14,23 +22,89 @@ def disconnect(): print(f"[Disconnected]: {request.sid}") -@sio.on("join_competition") -def join_competition(data): - competitionID = data["competitionID"] - join_room(data["competitionID"]) - print(f"[Join room]: {request.sid} -> {competitionID}") +@sio.on("start_presentation") +def start_presentation(data): + competition_id = data["competition_id"] + + # TODO: Do proper error handling + if competition_id in presentations: + print("THAT PRESENTATION IS ALREADY ACTIVE") + return + + presentations[competition_id] = { + "clients": {request.sid: {"view_type": "Operator"}}, + "slide": None, + "timer": {"enabled": False, "start_value": None, "value": None}, + } + + join_room(competition_id) + print(f"[start_presentation]: {request.sid} -> {competition_id}.") + + +@sio.on("end_presentation") +def end_presentation(data): + competition_id = data["competition_id"] + + if competition_id not in presentations: + print("NO PRESENTATION WITH THAT NAME EXISTS") + return + + if presentations[competition_id]["clients"][request.sid]["view_type"] != "Operator": + print("YOU DONT HAVE ACCESS TO DO THAT") + return + + emit("end_presentation", room=competition_id, include_self=True) + + +@sio.on("join_presentation") +def join_presentation(data): + team_view_id = 1 + code = data["code"] + item_code = dbc.get.code_by_code(code) + + # TODO: Do proper error handling + if not item_code: + print("CODE DOES NOT EXIST") + return + + if item_code.view_type_id == team_view_id: + pass # TODO: Get competition_id from team instead + else: + competition_id = item_code.pointer + + if competition_id not in presentations: + print("THAT COMPETITION IS CURRENTLY NOT ACTIVE") + return + + if request.sid in presentations[competition_id]["clients"]: + print("CLIENT ALREADY IN COMPETITION") + return + + # TODO: Write function in database controller to do this + view_type_name = db.session.query(ViewType).filter(ViewType.id == item_code.view_type_id).one().name + + presentations[competition_id]["clients"][request.sid] = {"view_type": view_type_name} + join_room(competition_id) + + print(f"[Join presentation]: {request.sid} -> {competition_id}. {view_type_name=}") + + +@sio.on("set_slide") +def set_slide(data): + competition_id = data["competition_id"] + slide_order = data["slide_order"] + if presentations[competition_id]["clients"][request.sid]["view_type"] != "Operator": + print("YOU DONT HAVE ACCESS TO DO THAT") + return -@sio.on("sync_slide") -def sync_slide(data): - slide, competitionID = data["slide"], data["competitionID"] - emit("sync_slide", {"slide": slide}, room=competitionID, include_self=False) - print(f"[Sync slide]: {slide} -> {competitionID}") + emit("set_slide", {"slide": slide_order}, room=competition_id, include_self=True) + print(f"[Set slide]: {slide_order} -> {competition_id}") @sio.on("sync_timer") def sync_timer(data): - competitionID = data["competitionID"] + code = data["code"] timer = data["timer"] - emit("sync_timer", {"timer": timer}, room=competitionID, include_self=False) - print(f"[Sync timer]: {competitionID=} {timer=}") + emit("sync_timer", {"timer": timer}, room=code, include_self=False) + print(f"[Sync timer]: {code=} {timer=}") diff --git a/server/app/sockets.py b/server/app/sockets.py deleted file mode 100644 index 830c2489..00000000 --- a/server/app/sockets.py +++ /dev/null @@ -1,56 +0,0 @@ -from flask.globals import request -from flask_socketio import SocketIO, emit, join_room - -sio = SocketIO(cors_allowed_origins="http://localhost:3000") - -# competitions = { -# "code": { -# "clients": { -# "id": { -# "type": "judge", -# }, -# "id2": {}, -# }, -# "current_slide": 2, -# "timer": {"enabled": True, "start_value": 100, "value": 10}, -# }, -# "code1": {}, -# } - - -@sio.on("connect") -def connect(): - print(f"[Connected]: {request.sid}") - - -@sio.on("disconnect") -def disconnect(): - print(f"[Disconnected]: {request.sid}") - - -@sio.on("join_presentation") -def join_presentation(data): - code = data["code"] - # client_type = data["type"] - - # if client_type == "judge": - # judges.append(request.sid) - - join_room(data["code"]) - print(f"[Join presentation]: {request.sid} -> {code}") - - -@sio.on("sync_slide") -def sync_slide(data): - code = data["code"] - slide = data["slide"] - emit("sync_slide", {"slide": slide}, room=code, include_self=False) - print(f"[Sync slide]: {slide} -> {code}") - - -@sio.on("sync_timer") -def sync_timer(data): - code = data["code"] - timer = data["timer"] - emit("sync_timer", {"timer": timer}, room=code, include_self=False) - print(f"[Sync timer]: {code=} {timer=}") diff --git a/server/populate.py b/server/populate.py index bebcaa8b..5a9988f7 100644 --- a/server/populate.py +++ b/server/populate.py @@ -7,7 +7,7 @@ def _add_items(): media_types = ["Image", "Video"] question_types = ["Boolean", "Multiple", "Text"] component_types = ["Text", "Image"] - view_types = ["Team", "Judge", "Audience"] + view_types = ["Team", "Judge", "Audience", "Operator"] roles = ["Admin", "Editor"] cities = ["Linköping", "Stockholm", "Norrköping", "Örkelljunga"] -- GitLab From 43874499a4b3ef78d5e7b995e82abd774f862336 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Victor=20L=C3=B6fgren?= <viclo211@student.liu.se> Date: Sun, 18 Apr 2021 00:25:51 +0200 Subject: [PATCH 11/20] Remove competition and fix sync slide --- client/src/actions/competition.ts | 37 ------------ client/src/actions/types.ts | 4 -- .../src/pages/views/components/SocketTest.tsx | 31 ++-------- client/src/pages/views/components/Timer.tsx | 60 ------------------- client/src/reducers/allReducers.ts | 2 - client/src/reducers/competitionReducer.ts | 54 ----------------- client/src/reducers/presentationReducer.ts | 1 + client/src/sockets.ts | 41 +++++++------ server/app/core/sockets.py | 16 ++--- 9 files changed, 39 insertions(+), 207 deletions(-) delete mode 100644 client/src/actions/competition.ts delete mode 100644 client/src/pages/views/components/Timer.tsx delete mode 100644 client/src/reducers/competitionReducer.ts diff --git a/client/src/actions/competition.ts b/client/src/actions/competition.ts deleted file mode 100644 index ef18927b..00000000 --- a/client/src/actions/competition.ts +++ /dev/null @@ -1,37 +0,0 @@ -import store from '../store' -import types from './types' - -export const setSlide = (slide: number) => ({ - type: types.SET_SLIDE, - payload: slide, -}) - -export const nextSlide = () => ({ - type: types.SET_SLIDE, - payload: store.getState().competition.slide + 1, -}) - -export const prevSlide = () => ({ - type: types.SET_SLIDE, - payload: store.getState().competition.slide - 1, -}) - -export const setCompetitionID = (competitionID: string) => ({ - type: types.SET_COMPEITION_ID, - payload: competitionID, -}) - -export const setTimer = (timer: number) => ({ - type: types.SET_TIMER, - payload: timer, -}) - -export const tickTimer = (timestep: number) => ({ - type: types.SET_TIMER, - payload: store.getState().competition.timer.value - timestep, -}) - -export const setTimerEnabled = (enabled: boolean) => ({ - type: types.SET_TIMER_ENABLED, - payload: enabled, -}) diff --git a/client/src/actions/types.ts b/client/src/actions/types.ts index fe41c262..9ab39b4d 100644 --- a/client/src/actions/types.ts +++ b/client/src/actions/types.ts @@ -26,9 +26,5 @@ export default { SET_CITIES: 'SET_CITIES', SET_CITIES_TOTAL: 'SET_CITIES_TOTAL', SET_CITIES_COUNT: 'SET_CITIES_COUNT', - SET_SLIDE: 'SET_SLIDE', - SET_COMPEITION_ID: 'SET_COMPETITION_ID', - SET_TIMER: 'SET_TIMER', - SET_TIMER_ENABLED: 'SET_TIMER_ENABLED', SET_TYPES: 'SET_TYPES', } diff --git a/client/src/pages/views/components/SocketTest.tsx b/client/src/pages/views/components/SocketTest.tsx index 9e7e5379..a03c2b9c 100644 --- a/client/src/pages/views/components/SocketTest.tsx +++ b/client/src/pages/views/components/SocketTest.tsx @@ -1,31 +1,28 @@ import React, { useEffect } from 'react' import { connect } from 'react-redux' -import { setTimerEnabled, tickTimer } from '../../../actions/competition' import { getPresentationCompetition, getPresentationTeams } from '../../../actions/presentation' import { useAppDispatch } from '../../../hooks' import { socket_connect, socket_end_presentation, socket_join_presentation, - socket_set_slide, + socket_set_slide_next, + socket_set_slide_prev, socket_start_presentation, } from '../../../sockets' const mapStateToProps = (state: any) => { return { - timer: state.competition.timer, slide_order: state.presentation.slide.order, } } const mapDispatchToProps = (dispatch: any) => { return { - tickTimer: () => dispatch(tickTimer(1)), + // tickTimer: () => dispatch(tickTimer(1)), } } -let timerIntervalId: NodeJS.Timeout - const SocketTest: React.FC = (props: any) => { const dispatch = useAppDispatch() @@ -35,30 +32,14 @@ const SocketTest: React.FC = (props: any) => { dispatch(getPresentationTeams('1')) // TODO: Use ID of item_code gotten from auth/login/<code> api call }, []) - useEffect(() => { - if (props.timer.enabled) { - timerIntervalId = setInterval(() => { - dispatch(tickTimer(1)) - }, 1000) - } else { - clearInterval(timerIntervalId) - } - }, [props.timer.enabled]) - - useEffect(() => { - if (props.timer.value <= 0) { - clearInterval(timerIntervalId) - dispatch(setTimerEnabled(false)) - } - }, [props.timer.value]) - return ( <> <button onClick={socket_start_presentation}>Start presentation</button> <button onClick={socket_join_presentation}>Join presentation</button> <button onClick={socket_end_presentation}>End presentation</button> - <button onClick={() => socket_set_slide(1)}>Set slide</button> {/* TODO: Send next slide */} - {/* <div>Current slide: {props.slide_order}</div> */} + <button onClick={socket_set_slide_next}>Next slide</button> + <button onClick={socket_set_slide_prev}>Prev slide</button> + <div>Current slide: {props.slide_order}</div> {/* <div>Timer: {props.timer.value}</div> <div>Enabled: {props.timer.enabled.toString()}</div> <button onClick={syncTimer}>Sync</button> diff --git a/client/src/pages/views/components/Timer.tsx b/client/src/pages/views/components/Timer.tsx deleted file mode 100644 index 4fe85973..00000000 --- a/client/src/pages/views/components/Timer.tsx +++ /dev/null @@ -1,60 +0,0 @@ -import React, { useEffect } from 'react' -import { connect } from 'react-redux' -import { setTimer, setTimerEnabled, tickTimer } from '../../../actions/competition' -import { useAppDispatch } from '../../../hooks' -import { syncTimer } from '../../../sockets' - -const mapStateToProps = (state: any) => { - return { - timer: state.competition.timer, - } -} - -const mapDispatchToProps = (dispatch: any) => { - return { - tickTimer: () => dispatch(tickTimer(1)), - } -} - -let timerIntervalId: NodeJS.Timeout - -const Timer: React.FC = (props: any) => { - const dispatch = useAppDispatch() - - useEffect(() => { - if (props.timer.enabled) { - timerIntervalId = setInterval(() => { - dispatch(tickTimer(1)) - }, 1000) - } else { - clearInterval(timerIntervalId) - } - }, [props.timer.enabled]) - - useEffect(() => { - if (props.timer.value <= 0) { - clearInterval(timerIntervalId) - dispatch(setTimerEnabled(false)) - } - }, [props.timer.value]) - - return ( - <> - <div>Timer: {props.timer.value}</div> - <div>Enabled: {props.timer.enabled.toString()}</div> - <button onClick={syncTimer}>Sync</button> - <button onClick={() => dispatch(setTimer(5))}>5 Sec</button> - <button - onClick={() => { - dispatch(setTimer(5)) - dispatch(setTimerEnabled(true)) - syncTimer() - }} - > - Sync and 5 sec - </button> - </> - ) -} - -export default connect(mapStateToProps, mapDispatchToProps)(Timer) diff --git a/client/src/reducers/allReducers.ts b/client/src/reducers/allReducers.ts index 7f98d8eb..2aee4697 100644 --- a/client/src/reducers/allReducers.ts +++ b/client/src/reducers/allReducers.ts @@ -2,7 +2,6 @@ import { combineReducers } from 'redux' import citiesReducer from './citiesReducer' -import competitionReducer from './competitionReducer' import competitionsReducer from './competitionsReducer' import editorReducer from './editorReducer' import presentationReducer from './presentationReducer' @@ -17,7 +16,6 @@ const allReducers = combineReducers({ user: userReducer, UI: uiReducer, competitions: competitionsReducer, - competition: competitionReducer, cities: citiesReducer, editor: editorReducer, presentation: presentationReducer, diff --git a/client/src/reducers/competitionReducer.ts b/client/src/reducers/competitionReducer.ts deleted file mode 100644 index 431ee504..00000000 --- a/client/src/reducers/competitionReducer.ts +++ /dev/null @@ -1,54 +0,0 @@ -import { AnyAction } from 'redux' -import Types from '../actions/types' - -interface TimerState { - value: number - enabled: boolean -} - -interface CompetitionState { - competitionID: string | null - slide: number - timer: TimerState - isLocked: boolean -} - -const initialState: CompetitionState = { - competitionID: null, - slide: 0, - timer: { value: 0, enabled: false }, - isLocked: false, -} - -export default function (state = initialState, action: AnyAction) { - switch (action.type) { - case Types.SET_SLIDE: - return { - ...state, - slide: action.payload, - } - case Types.SET_COMPEITION_ID: - return { - ...state, - competitionID: action.payload, - } - case Types.SET_TIMER: - return { - ...state, - timer: { - ...state.timer, - value: action.payload, - }, - } - case Types.SET_TIMER_ENABLED: - return { - ...state, - timer: { - ...state.timer, - enabled: action.payload, - }, - } - default: - return state - } -} diff --git a/client/src/reducers/presentationReducer.ts b/client/src/reducers/presentationReducer.ts index 39d22a5e..e04f5d69 100644 --- a/client/src/reducers/presentationReducer.ts +++ b/client/src/reducers/presentationReducer.ts @@ -71,6 +71,7 @@ export default function (state = initialState, action: AnyAction) { } return state case Types.SET_PRESENTATION_SLIDE_BY_ORDER: + console.log(action.payload) if (0 <= action.payload && action.payload < state.competition.slides.length) return { ...state, diff --git a/client/src/sockets.ts b/client/src/sockets.ts index f9c2ee48..5395fdc3 100644 --- a/client/src/sockets.ts +++ b/client/src/sockets.ts @@ -1,5 +1,4 @@ import io from 'socket.io-client' -import { setTimer, setTimerEnabled } from './actions/competition' import { setCurrentSlideByOrder } from './actions/presentation' import store from './store' @@ -26,10 +25,10 @@ export const socket_connect = () => { setCurrentSlideByOrder(data.slide_order)(store.dispatch) }) - socket.on('sync_timer', (data: SyncTimerInterface) => { - store.dispatch(setTimerEnabled(data.timer.enabled)) - store.dispatch(setTimer(data.timer.value)) - }) + // socket.on('sync_timer', (data: SyncTimerInterface) => { + // // store.dispatch(setTimerEnabled(data.timer.enabled)) + // // store.dispatch(setTimer(data.timer.value)) + // }) socket.on('end_presentation', () => { socket.disconnect() @@ -37,30 +36,36 @@ export const socket_connect = () => { } } +export const socket_start_presentation = () => { + socket.emit('start_presentation', { competition_id: store.getState().presentation.competition.id }) +} + export const socket_join_presentation = () => { socket.emit('join_presentation', { code: '8WZ12G' }) // TODO: Send code gotten from auth/login/<code> api call } -export const socket_start_presentation = () => { - socket.emit('start_presentation', { competition_id: 1 }) // TODO: Send current competition id +export const socket_end_presentation = () => { + socket.emit('end_presentation', { competition_id: store.getState().presentation.competition.id }) } -export const socket_end_presentation = () => { - socket.emit('end_presentation', { competition_id: 1 }) // TODO: Send current competition id +export const socket_set_slide_next = () => { + socket_set_slide(store.getState().presentation.slide.order + 1) // TODO: Check that this slide exists +} + +export const socket_set_slide_prev = () => { + socket_set_slide(store.getState().presentation.slide.order - 1) // TODO: Check that this slide exists } export const socket_set_slide = (slide_order: number) => { socket.emit('set_slide', { - competition_id: 1, // TODO: Send current competition id + competition_id: store.getState().presentation.competition.id, slide_order: slide_order, }) } -// Add socket_next_slide and socket_prev_slide - -export const syncTimer = () => { - socket.emit('sync_timer', { - code: store.getState().presentation.code, - timer: store.getState().competition.timer, - }) -} +// export const syncTimer = () => { +// socket.emit('sync_timer', { +// // code: store.getState().presentation.code, +// // timer: store.getState().competition.timer, +// }) +// } diff --git a/server/app/core/sockets.py b/server/app/core/sockets.py index 903f49c6..de7078ee 100644 --- a/server/app/core/sockets.py +++ b/server/app/core/sockets.py @@ -53,6 +53,8 @@ def end_presentation(data): print("YOU DONT HAVE ACCESS TO DO THAT") return + del presentations[competition_id] + emit("end_presentation", room=competition_id, include_self=True) @@ -98,13 +100,13 @@ def set_slide(data): print("YOU DONT HAVE ACCESS TO DO THAT") return - emit("set_slide", {"slide": slide_order}, room=competition_id, include_self=True) + emit("set_slide", {"slide_order": slide_order}, room=competition_id, include_self=True) print(f"[Set slide]: {slide_order} -> {competition_id}") -@sio.on("sync_timer") -def sync_timer(data): - code = data["code"] - timer = data["timer"] - emit("sync_timer", {"timer": timer}, room=code, include_self=False) - print(f"[Sync timer]: {code=} {timer=}") +# @sio.on("sync_timer") +# def sync_timer(data): +# code = data["code"] +# timer = data["timer"] +# emit("sync_timer", {"timer": timer}, room=code, include_self=False) +# print(f"[Sync timer]: {code=} {timer=}") -- GitLab From b23c519c8109132166382a06f34a9cea07aa86db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Victor=20L=C3=B6fgren?= <viclo211@student.liu.se> Date: Sun, 18 Apr 2021 20:50:21 +0200 Subject: [PATCH 12/20] Remove self from competition if disconnect --- server/app/core/sockets.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/server/app/core/sockets.py b/server/app/core/sockets.py index de7078ee..c42e92df 100644 --- a/server/app/core/sockets.py +++ b/server/app/core/sockets.py @@ -19,6 +19,16 @@ def connect(): @sio.on("disconnect") def disconnect(): + for competition_id, presentation in presentations.items(): + if request.sid in presentation["clients"]: + del presentation["clients"][request.sid] + break + + if presentations and not presentations[competition_id]["clients"]: + del presentations[competition_id] + + print(f"{presentations=}") + print(f"[Disconnected]: {request.sid}") @@ -37,6 +47,8 @@ def start_presentation(data): "timer": {"enabled": False, "start_value": None, "value": None}, } + print(f"{presentations=}") + join_room(competition_id) print(f"[start_presentation]: {request.sid} -> {competition_id}.") @@ -55,6 +67,8 @@ def end_presentation(data): del presentations[competition_id] + print(f"{presentations=}") + emit("end_presentation", room=competition_id, include_self=True) @@ -88,6 +102,8 @@ def join_presentation(data): presentations[competition_id]["clients"][request.sid] = {"view_type": view_type_name} join_room(competition_id) + print(f"{presentations=}") + print(f"[Join presentation]: {request.sid} -> {competition_id}. {view_type_name=}") @@ -100,6 +116,10 @@ def set_slide(data): print("YOU DONT HAVE ACCESS TO DO THAT") return + presentations[competition_id]["slide"] = slide_order + + print(f"{presentations=}") + emit("set_slide", {"slide_order": slide_order}, room=competition_id, include_self=True) print(f"[Set slide]: {slide_order} -> {competition_id}") -- GitLab From c1c3548336669b218243c927f529bd088f3cc724 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Victor=20L=C3=B6fgren?= <viclo211@student.liu.se> Date: Sun, 18 Apr 2021 21:06:11 +0200 Subject: [PATCH 13/20] Prevent client from changing to non existent slide --- client/src/pages/views/components/SocketTest.tsx | 2 +- client/src/reducers/presentationReducer.ts | 1 - client/src/sockets.ts | 5 +++++ server/app/core/sockets.py | 8 +++++++- 4 files changed, 13 insertions(+), 3 deletions(-) diff --git a/client/src/pages/views/components/SocketTest.tsx b/client/src/pages/views/components/SocketTest.tsx index a03c2b9c..d0ac015d 100644 --- a/client/src/pages/views/components/SocketTest.tsx +++ b/client/src/pages/views/components/SocketTest.tsx @@ -37,8 +37,8 @@ const SocketTest: React.FC = (props: any) => { <button onClick={socket_start_presentation}>Start presentation</button> <button onClick={socket_join_presentation}>Join presentation</button> <button onClick={socket_end_presentation}>End presentation</button> - <button onClick={socket_set_slide_next}>Next slide</button> <button onClick={socket_set_slide_prev}>Prev slide</button> + <button onClick={socket_set_slide_next}>Next slide</button> <div>Current slide: {props.slide_order}</div> {/* <div>Timer: {props.timer.value}</div> <div>Enabled: {props.timer.enabled.toString()}</div> diff --git a/client/src/reducers/presentationReducer.ts b/client/src/reducers/presentationReducer.ts index e04f5d69..39d22a5e 100644 --- a/client/src/reducers/presentationReducer.ts +++ b/client/src/reducers/presentationReducer.ts @@ -71,7 +71,6 @@ export default function (state = initialState, action: AnyAction) { } return state case Types.SET_PRESENTATION_SLIDE_BY_ORDER: - console.log(action.payload) if (0 <= action.payload && action.payload < state.competition.slides.length) return { ...state, diff --git a/client/src/sockets.ts b/client/src/sockets.ts index 5395fdc3..4fd2c189 100644 --- a/client/src/sockets.ts +++ b/client/src/sockets.ts @@ -57,6 +57,11 @@ export const socket_set_slide_prev = () => { } export const socket_set_slide = (slide_order: number) => { + if (slide_order < 0 || store.getState().presentation.competition.slides.length <= slide_order) { + console.log('CANT CHANGE TO NON EXISTENT SLIDE') + return + } + socket.emit('set_slide', { competition_id: store.getState().presentation.competition.id, slide_order: slide_order, diff --git a/server/app/core/sockets.py b/server/app/core/sockets.py index c42e92df..108fd20b 100644 --- a/server/app/core/sockets.py +++ b/server/app/core/sockets.py @@ -1,6 +1,6 @@ import app.database.controller as dbc from app.core import db -from app.database.models import ViewType +from app.database.models import Competition, Slide, ViewType from flask.globals import request from flask_socketio import SocketIO, emit, join_room @@ -116,6 +116,12 @@ def set_slide(data): print("YOU DONT HAVE ACCESS TO DO THAT") return + num_slides = db.session.query(Slide).filter(Slide.competition_id == competition_id).count() + + if not (0 <= slide_order < num_slides): + print("CANT CHANGE TO NON EXISTENT SLIDE") + return + presentations[competition_id]["slide"] = slide_order print(f"{presentations=}") -- GitLab From 340b9cf41a7a61dc5248e9a98f8ed5d69fd273cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Victor=20L=C3=B6fgren?= <viclo211@student.liu.se> Date: Sun, 18 Apr 2021 21:12:11 +0200 Subject: [PATCH 14/20] Rename socket functions in client --- .../src/pages/views/components/SocketTest.tsx | 20 ++++++------- client/src/sockets.ts | 28 +++++++++---------- 2 files changed, 24 insertions(+), 24 deletions(-) diff --git a/client/src/pages/views/components/SocketTest.tsx b/client/src/pages/views/components/SocketTest.tsx index d0ac015d..b0474bb8 100644 --- a/client/src/pages/views/components/SocketTest.tsx +++ b/client/src/pages/views/components/SocketTest.tsx @@ -3,12 +3,12 @@ import { connect } from 'react-redux' import { getPresentationCompetition, getPresentationTeams } from '../../../actions/presentation' import { useAppDispatch } from '../../../hooks' import { + socketEndPresentation, + socketJoinPresentation, + socketSetSlideNext, + socketSetSlidePrev, + socketStartPresentation, socket_connect, - socket_end_presentation, - socket_join_presentation, - socket_set_slide_next, - socket_set_slide_prev, - socket_start_presentation, } from '../../../sockets' const mapStateToProps = (state: any) => { @@ -34,11 +34,11 @@ const SocketTest: React.FC = (props: any) => { return ( <> - <button onClick={socket_start_presentation}>Start presentation</button> - <button onClick={socket_join_presentation}>Join presentation</button> - <button onClick={socket_end_presentation}>End presentation</button> - <button onClick={socket_set_slide_prev}>Prev slide</button> - <button onClick={socket_set_slide_next}>Next slide</button> + <button onClick={socketStartPresentation}>Start presentation</button> + <button onClick={socketJoinPresentation}>Join presentation</button> + <button onClick={socketEndPresentation}>End presentation</button> + <button onClick={socketSetSlidePrev}>Prev slide</button> + <button onClick={socketSetSlideNext}>Next slide</button> <div>Current slide: {props.slide_order}</div> {/* <div>Timer: {props.timer.value}</div> <div>Enabled: {props.timer.enabled.toString()}</div> diff --git a/client/src/sockets.ts b/client/src/sockets.ts index 4fd2c189..519ed6ed 100644 --- a/client/src/sockets.ts +++ b/client/src/sockets.ts @@ -36,27 +36,27 @@ export const socket_connect = () => { } } -export const socket_start_presentation = () => { +export const socketStartPresentation = () => { socket.emit('start_presentation', { competition_id: store.getState().presentation.competition.id }) } -export const socket_join_presentation = () => { +export const socketJoinPresentation = () => { socket.emit('join_presentation', { code: '8WZ12G' }) // TODO: Send code gotten from auth/login/<code> api call } -export const socket_end_presentation = () => { +export const socketEndPresentation = () => { socket.emit('end_presentation', { competition_id: store.getState().presentation.competition.id }) } -export const socket_set_slide_next = () => { - socket_set_slide(store.getState().presentation.slide.order + 1) // TODO: Check that this slide exists +export const socketSetSlideNext = () => { + socketSetSlide(store.getState().presentation.slide.order + 1) // TODO: Check that this slide exists } -export const socket_set_slide_prev = () => { - socket_set_slide(store.getState().presentation.slide.order - 1) // TODO: Check that this slide exists +export const socketSetSlidePrev = () => { + socketSetSlide(store.getState().presentation.slide.order - 1) // TODO: Check that this slide exists } -export const socket_set_slide = (slide_order: number) => { +export const socketSetSlide = (slide_order: number) => { if (slide_order < 0 || store.getState().presentation.competition.slides.length <= slide_order) { console.log('CANT CHANGE TO NON EXISTENT SLIDE') return @@ -68,9 +68,9 @@ export const socket_set_slide = (slide_order: number) => { }) } -// export const syncTimer = () => { -// socket.emit('sync_timer', { -// // code: store.getState().presentation.code, -// // timer: store.getState().competition.timer, -// }) -// } +export const socket_set_timer = () => { + socket.emit('sync_timer', { + // code: store.getState().presentation.code, + // timer: store.getState().competition.timer, + }) +} -- GitLab From 26ea211b688ae7bdc92f5386c9d3eff38211cfca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Victor=20L=C3=B6fgren?= <viclo211@student.liu.se> Date: Sun, 18 Apr 2021 21:12:25 +0200 Subject: [PATCH 15/20] Check that competition exists when setting slide --- server/app/core/sockets.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/server/app/core/sockets.py b/server/app/core/sockets.py index 108fd20b..920f1207 100644 --- a/server/app/core/sockets.py +++ b/server/app/core/sockets.py @@ -112,6 +112,10 @@ def set_slide(data): competition_id = data["competition_id"] slide_order = data["slide_order"] + if competition_id not in presentations: + print("CANT SET SLIDE IN NON ACTIVE COMPETITION") + return + if presentations[competition_id]["clients"][request.sid]["view_type"] != "Operator": print("YOU DONT HAVE ACCESS TO DO THAT") return -- GitLab From d1aa5e0cc8150cc40a8edda820d43f1844fba92b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Victor=20L=C3=B6fgren?= <viclo211@student.liu.se> Date: Sun, 18 Apr 2021 22:21:28 +0200 Subject: [PATCH 16/20] Add socketSetTimer --- client/src/actions/presentation.ts | 5 ++ client/src/actions/types.ts | 1 + client/src/interfaces/Timer.ts | 4 ++ client/src/pages/views/PresenterViewPage.tsx | 2 + .../src/pages/views/components/SocketTest.tsx | 7 +- client/src/pages/views/components/Timer.tsx | 65 +++++++++++++++++++ client/src/reducers/presentationReducer.ts | 12 ++++ client/src/sockets.ts | 28 ++++---- server/app/core/sockets.py | 22 +++++-- server/populate.py | 4 +- 10 files changed, 129 insertions(+), 21 deletions(-) create mode 100644 client/src/interfaces/Timer.ts create mode 100644 client/src/pages/views/components/Timer.tsx diff --git a/client/src/actions/presentation.ts b/client/src/actions/presentation.ts index 70345f42..db6f2ec4 100644 --- a/client/src/actions/presentation.ts +++ b/client/src/actions/presentation.ts @@ -1,5 +1,6 @@ import axios from 'axios' import { Slide } from '../interfaces/Slide' +import { Timer } from '../interfaces/Timer' import { AppDispatch } from './../store' import Types from './types' @@ -50,3 +51,7 @@ export const setCurrentSlideByOrder = (order: number) => (dispatch: AppDispatch) export const setPresentationCode = (code: string) => (dispatch: AppDispatch) => { dispatch({ type: Types.SET_PRESENTATION_CODE, payload: code }) } + +export const setPresentationTimer = (timer: Timer) => (dispatch: AppDispatch) => { + dispatch({ type: Types.SET_PRESENTATION_TIMER, payload: timer }) +} diff --git a/client/src/actions/types.ts b/client/src/actions/types.ts index 9ab39b4d..848d41f0 100644 --- a/client/src/actions/types.ts +++ b/client/src/actions/types.ts @@ -23,6 +23,7 @@ export default { SET_PRESENTATION_SLIDE_BY_ORDER: 'SET_PRESENTATION_SLIDE_BY_ORDER', SET_PRESENTATION_TEAMS: 'SET_PRESENTATION_TEAMS', SET_PRESENTATION_CODE: 'SET_PRESENTATION_CODE', + SET_PRESENTATION_TIMER: 'SET_PRESENTATION_TIMER', SET_CITIES: 'SET_CITIES', SET_CITIES_TOTAL: 'SET_CITIES_TOTAL', SET_CITIES_COUNT: 'SET_CITIES_COUNT', diff --git a/client/src/interfaces/Timer.ts b/client/src/interfaces/Timer.ts new file mode 100644 index 00000000..49d1909e --- /dev/null +++ b/client/src/interfaces/Timer.ts @@ -0,0 +1,4 @@ +export interface Timer { + enabled: boolean + value: number +} diff --git a/client/src/pages/views/PresenterViewPage.tsx b/client/src/pages/views/PresenterViewPage.tsx index 0958bc51..22a672ba 100644 --- a/client/src/pages/views/PresenterViewPage.tsx +++ b/client/src/pages/views/PresenterViewPage.tsx @@ -7,6 +7,7 @@ import { useAppDispatch, useAppSelector } from '../../hooks' import { ViewParams } from '../../interfaces/ViewParams' import SlideDisplay from './components/SlideDisplay' import SocketTest from './components/SocketTest' +import Timer from './components/Timer' import { PresenterButton, PresenterContainer, PresenterFooter, PresenterHeader } from './styled' const PresenterViewPage: React.FC = () => { @@ -51,6 +52,7 @@ const PresenterViewPage: React.FC = () => { <ChevronRightIcon fontSize="large" /> </PresenterButton> <SocketTest></SocketTest> + <Timer></Timer> <PresenterButton onClick={handleNextSlidePressed} variant="contained"> <ChevronRightIcon fontSize="large" /> </PresenterButton> diff --git a/client/src/pages/views/components/SocketTest.tsx b/client/src/pages/views/components/SocketTest.tsx index b0474bb8..7647fa65 100644 --- a/client/src/pages/views/components/SocketTest.tsx +++ b/client/src/pages/views/components/SocketTest.tsx @@ -1,6 +1,5 @@ import React, { useEffect } from 'react' import { connect } from 'react-redux' -import { getPresentationCompetition, getPresentationTeams } from '../../../actions/presentation' import { useAppDispatch } from '../../../hooks' import { socketEndPresentation, @@ -8,6 +7,7 @@ import { socketSetSlideNext, socketSetSlidePrev, socketStartPresentation, + socketStartTimer, socket_connect, } from '../../../sockets' @@ -28,8 +28,8 @@ const SocketTest: React.FC = (props: any) => { useEffect(() => { socket_connect() - dispatch(getPresentationCompetition('1')) // TODO: Use ID of item_code gotten from auth/login/<code> api call - dispatch(getPresentationTeams('1')) // TODO: Use ID of item_code gotten from auth/login/<code> api call + // dispatch(getPresentationCompetition('1')) // TODO: Use ID of item_code gotten from auth/login/<code> api call + // dispatch(getPresentationTeams('1')) // TODO: Use ID of item_code gotten from auth/login/<code> api call }, []) return ( @@ -39,6 +39,7 @@ const SocketTest: React.FC = (props: any) => { <button onClick={socketEndPresentation}>End presentation</button> <button onClick={socketSetSlidePrev}>Prev slide</button> <button onClick={socketSetSlideNext}>Next slide</button> + <button onClick={socketStartTimer}>Set timer</button> <div>Current slide: {props.slide_order}</div> {/* <div>Timer: {props.timer.value}</div> <div>Enabled: {props.timer.enabled.toString()}</div> diff --git a/client/src/pages/views/components/Timer.tsx b/client/src/pages/views/components/Timer.tsx new file mode 100644 index 00000000..2304aa2a --- /dev/null +++ b/client/src/pages/views/components/Timer.tsx @@ -0,0 +1,65 @@ +import React, { useEffect } from 'react' +import { connect } from 'react-redux' +import { setPresentationTimer } from '../../../actions/presentation' +import { useAppDispatch } from '../../../hooks' +import store from '../../../store' + +const mapStateToProps = (state: any) => { + return { + timer: state.presentation.timer, + timer_start_value: state.presentation.slide.timer, + } +} + +const mapDispatchToProps = (dispatch: any) => { + return { + // tickTimer: () => dispatch(tickTimer(1)), + } +} + +let timerIntervalId: NodeJS.Timeout + +const Timer: React.FC = (props: any) => { + const dispatch = useAppDispatch() + + useEffect(() => { + dispatch(setPresentationTimer({ enabled: false, value: store.getState().presentation.slide.timer })) + }, [props.timer_start_value]) + + // useEffect(() => { + // if (props.timer.enabled) { + // timerIntervalId = setInterval(() => { + // dispatch(tickTimer(1)) + // }, 1000) + // } else { + // clearInterval(timerIntervalId) + // } + // }, [props.timer.enabled]) + + // useEffect(() => { + // if (props.timer.value <= 0) { + // clearInterval(timerIntervalId) + // } + // }, [props.timer.value]) + + return ( + <> + <div>Timer: {props.timer.value}</div> + <div>Enabled: {props.timer.enabled.toString()}</div> + + {/* <button onClick={syncTimer}>Sync</button> + <button onClick={() => dispatch(setTimer(5))}>5 Sec</button> + <button + onClick={() => { + dispatch(setTimer(5)) + dispatch(setTimerEnabled(true)) + syncTimer() + }} + > + Sync and 5 sec + </button> */} + </> + ) +} + +export default connect(mapStateToProps, mapDispatchToProps)(Timer) diff --git a/client/src/reducers/presentationReducer.ts b/client/src/reducers/presentationReducer.ts index 39d22a5e..46908033 100644 --- a/client/src/reducers/presentationReducer.ts +++ b/client/src/reducers/presentationReducer.ts @@ -1,5 +1,6 @@ import { AnyAction } from 'redux' import Types from '../actions/types' +import { Timer } from '../interfaces/Timer' import { RichCompetition } from './../interfaces/ApiRichModels' import { Slide } from './../interfaces/Slide' import { Team } from './../interfaces/Team' @@ -9,6 +10,7 @@ interface PresentationState { slide: Slide teams: Team[] code: string + timer: Timer } const initialState: PresentationState = { @@ -29,6 +31,10 @@ const initialState: PresentationState = { }, teams: [], code: '', + timer: { + enabled: false, + value: 0, + }, } export default function (state = initialState, action: AnyAction) { @@ -77,6 +83,12 @@ export default function (state = initialState, action: AnyAction) { slide: state.competition.slides[action.payload], } return state + case Types.SET_PRESENTATION_TIMER: + console.log(action.payload) + return { + ...state, + timer: action.payload, + } default: return state } diff --git a/client/src/sockets.ts b/client/src/sockets.ts index 519ed6ed..e4e9b923 100644 --- a/client/src/sockets.ts +++ b/client/src/sockets.ts @@ -1,5 +1,6 @@ import io from 'socket.io-client' -import { setCurrentSlideByOrder } from './actions/presentation' +import { setCurrentSlideByOrder, setPresentationTimer } from './actions/presentation' +import { Timer } from './interfaces/Timer' import store from './store' interface SetSlideInterface { @@ -11,7 +12,7 @@ interface TimerInterface { enabled: boolean } -interface SyncTimerInterface { +interface SetTimerInterface { timer: TimerInterface } @@ -25,10 +26,11 @@ export const socket_connect = () => { setCurrentSlideByOrder(data.slide_order)(store.dispatch) }) - // socket.on('sync_timer', (data: SyncTimerInterface) => { - // // store.dispatch(setTimerEnabled(data.timer.enabled)) - // // store.dispatch(setTimer(data.timer.value)) - // }) + socket.on('set_timer', (data: SetTimerInterface) => { + setPresentationTimer(data.timer)(store.dispatch) + // store.dispatch(setTimerEnabled(data.timer.enabled)) + // store.dispatch(setTimer(data.timer.value)) + }) socket.on('end_presentation', () => { socket.disconnect() @@ -41,7 +43,7 @@ export const socketStartPresentation = () => { } export const socketJoinPresentation = () => { - socket.emit('join_presentation', { code: '8WZ12G' }) // TODO: Send code gotten from auth/login/<code> api call + socket.emit('join_presentation', { code: 'OEM1V4' }) // TODO: Send code gotten from auth/login/<code> api call } export const socketEndPresentation = () => { @@ -68,9 +70,13 @@ export const socketSetSlide = (slide_order: number) => { }) } -export const socket_set_timer = () => { - socket.emit('sync_timer', { - // code: store.getState().presentation.code, - // timer: store.getState().competition.timer, +export const socketSetTimer = (timer: Timer) => { + socket.emit('set_timer', { + competition_id: store.getState().presentation.competition.id, + timer: timer, }) } + +export const socketStartTimer = () => { + socketSetTimer({ enabled: true, value: store.getState().presentation.timer.value }) +} diff --git a/server/app/core/sockets.py b/server/app/core/sockets.py index 920f1207..93be095b 100644 --- a/server/app/core/sockets.py +++ b/server/app/core/sockets.py @@ -134,9 +134,19 @@ def set_slide(data): print(f"[Set slide]: {slide_order} -> {competition_id}") -# @sio.on("sync_timer") -# def sync_timer(data): -# code = data["code"] -# timer = data["timer"] -# emit("sync_timer", {"timer": timer}, room=code, include_self=False) -# print(f"[Sync timer]: {code=} {timer=}") +@sio.on("set_timer") +def sync_timer(data): + competition_id = data["competition_id"] + timer = data["timer"] + + if competition_id not in presentations: + print("CANT SET TIMER IN NON EXISTENT COMPETITION") + return + + if presentations[competition_id]["clients"][request.sid]["view_type"] != "Operator": + print("YOU DONT HAVE ACCESS TO DO THAT") + return + + emit("set_timer", {"timer": timer}, room=competition_id, include_self=True) + + print(f"[Set timer]: {timer=}, {competition_id=}") diff --git a/server/populate.py b/server/populate.py index 5a9988f7..7044db60 100644 --- a/server/populate.py +++ b/server/populate.py @@ -50,6 +50,8 @@ def _add_items(): for item_comp in item_comps: for item_slide in item_comp.slides: + dbc.edit.slide(item_slide, timer=5) + for i in range(3): dbc.add.question(f"Q{i+1}", i + 1, text_id, item_slide) @@ -59,7 +61,7 @@ def _add_items(): if __name__ == "__main__": - app = create_app("configmodule.DevelopmentConfig") + app, _ = create_app("configmodule.DevelopmentConfig") with app.app_context(): db.drop_all() -- GitLab From 6d65e8b866ae7fbf343e76a313b565879ccaa5e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Victor=20L=C3=B6fgren?= <viclo211@student.liu.se> Date: Sun, 18 Apr 2021 22:35:41 +0200 Subject: [PATCH 17/20] Timer now also counts down when enabled --- client/src/actions/presentation.ts | 12 +++++- .../src/pages/views/components/SocketTest.tsx | 2 +- client/src/pages/views/components/Timer.tsx | 38 +++++-------------- client/src/reducers/presentationReducer.ts | 4 +- server/app/core/sockets.py | 5 ++- 5 files changed, 29 insertions(+), 32 deletions(-) diff --git a/client/src/actions/presentation.ts b/client/src/actions/presentation.ts index db6f2ec4..60248e1f 100644 --- a/client/src/actions/presentation.ts +++ b/client/src/actions/presentation.ts @@ -1,7 +1,7 @@ import axios from 'axios' import { Slide } from '../interfaces/Slide' import { Timer } from '../interfaces/Timer' -import { AppDispatch } from './../store' +import store, { AppDispatch } from './../store' import Types from './types' export const getPresentationCompetition = (id: string) => async (dispatch: AppDispatch) => { @@ -55,3 +55,13 @@ export const setPresentationCode = (code: string) => (dispatch: AppDispatch) => export const setPresentationTimer = (timer: Timer) => (dispatch: AppDispatch) => { dispatch({ type: Types.SET_PRESENTATION_TIMER, payload: timer }) } + +export const setPresentationTimerDecrement = () => (dispatch: AppDispatch) => { + dispatch({ + type: Types.SET_PRESENTATION_TIMER, + payload: { + enabled: store.getState().presentation.timer.enabled, + value: store.getState().presentation.timer.value - 1, + }, + }) +} diff --git a/client/src/pages/views/components/SocketTest.tsx b/client/src/pages/views/components/SocketTest.tsx index 7647fa65..d99f5b2a 100644 --- a/client/src/pages/views/components/SocketTest.tsx +++ b/client/src/pages/views/components/SocketTest.tsx @@ -39,7 +39,7 @@ const SocketTest: React.FC = (props: any) => { <button onClick={socketEndPresentation}>End presentation</button> <button onClick={socketSetSlidePrev}>Prev slide</button> <button onClick={socketSetSlideNext}>Next slide</button> - <button onClick={socketStartTimer}>Set timer</button> + <button onClick={socketStartTimer}>Start timer</button> <div>Current slide: {props.slide_order}</div> {/* <div>Timer: {props.timer.value}</div> <div>Enabled: {props.timer.enabled.toString()}</div> diff --git a/client/src/pages/views/components/Timer.tsx b/client/src/pages/views/components/Timer.tsx index 2304aa2a..b4401a69 100644 --- a/client/src/pages/views/components/Timer.tsx +++ b/client/src/pages/views/components/Timer.tsx @@ -1,6 +1,6 @@ import React, { useEffect } from 'react' import { connect } from 'react-redux' -import { setPresentationTimer } from '../../../actions/presentation' +import { setPresentationTimer, setPresentationTimerDecrement } from '../../../actions/presentation' import { useAppDispatch } from '../../../hooks' import store from '../../../store' @@ -26,38 +26,20 @@ const Timer: React.FC = (props: any) => { dispatch(setPresentationTimer({ enabled: false, value: store.getState().presentation.slide.timer })) }, [props.timer_start_value]) - // useEffect(() => { - // if (props.timer.enabled) { - // timerIntervalId = setInterval(() => { - // dispatch(tickTimer(1)) - // }, 1000) - // } else { - // clearInterval(timerIntervalId) - // } - // }, [props.timer.enabled]) - - // useEffect(() => { - // if (props.timer.value <= 0) { - // clearInterval(timerIntervalId) - // } - // }, [props.timer.value]) + useEffect(() => { + if (props.timer.enabled) { + timerIntervalId = setInterval(() => { + dispatch(setPresentationTimerDecrement()) + }, 1000) + } else { + clearInterval(timerIntervalId) + } + }, [props.timer.enabled]) return ( <> <div>Timer: {props.timer.value}</div> <div>Enabled: {props.timer.enabled.toString()}</div> - - {/* <button onClick={syncTimer}>Sync</button> - <button onClick={() => dispatch(setTimer(5))}>5 Sec</button> - <button - onClick={() => { - dispatch(setTimer(5)) - dispatch(setTimerEnabled(true)) - syncTimer() - }} - > - Sync and 5 sec - </button> */} </> ) } diff --git a/client/src/reducers/presentationReducer.ts b/client/src/reducers/presentationReducer.ts index 46908033..2b3c5eb1 100644 --- a/client/src/reducers/presentationReducer.ts +++ b/client/src/reducers/presentationReducer.ts @@ -84,7 +84,9 @@ export default function (state = initialState, action: AnyAction) { } return state case Types.SET_PRESENTATION_TIMER: - console.log(action.payload) + if (action.payload.value == 0) { + action.payload.enabled = false + } return { ...state, timer: action.payload, diff --git a/server/app/core/sockets.py b/server/app/core/sockets.py index 93be095b..9c6137ca 100644 --- a/server/app/core/sockets.py +++ b/server/app/core/sockets.py @@ -147,6 +147,9 @@ def sync_timer(data): print("YOU DONT HAVE ACCESS TO DO THAT") return - emit("set_timer", {"timer": timer}, room=competition_id, include_self=True) + # TODO: Save timer in presentation, maybe? + + print(f"{presentations=}") + emit("set_timer", {"timer": timer}, room=competition_id, include_self=True) print(f"[Set timer]: {timer=}, {competition_id=}") -- GitLab From 278a1713a1ecc46bd05d7b8fa72cd14773f16af5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Victor=20L=C3=B6fgren?= <viclo211@student.liu.se> Date: Sun, 18 Apr 2021 22:45:55 +0200 Subject: [PATCH 18/20] Get competition id from team when joining with a team code --- client/src/sockets.ts | 2 -- server/app/core/sockets.py | 11 ++++++----- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/client/src/sockets.ts b/client/src/sockets.ts index e4e9b923..874bfc46 100644 --- a/client/src/sockets.ts +++ b/client/src/sockets.ts @@ -28,8 +28,6 @@ export const socket_connect = () => { socket.on('set_timer', (data: SetTimerInterface) => { setPresentationTimer(data.timer)(store.dispatch) - // store.dispatch(setTimerEnabled(data.timer.enabled)) - // store.dispatch(setTimer(data.timer.value)) }) socket.on('end_presentation', () => { diff --git a/server/app/core/sockets.py b/server/app/core/sockets.py index 9c6137ca..f4909531 100644 --- a/server/app/core/sockets.py +++ b/server/app/core/sockets.py @@ -1,6 +1,6 @@ import app.database.controller as dbc from app.core import db -from app.database.models import Competition, Slide, ViewType +from app.database.models import Competition, Slide, Team, ViewType from flask.globals import request from flask_socketio import SocketIO, emit, join_room @@ -83,10 +83,11 @@ def join_presentation(data): print("CODE DOES NOT EXIST") return - if item_code.view_type_id == team_view_id: - pass # TODO: Get competition_id from team instead - else: - competition_id = item_code.pointer + competition_id = ( + item_code.pointer + if item_code.view_type_id != team_view_id + else db.session.query(Team).filter(Team.id == item_code.pointer).one().competition_id + ) if competition_id not in presentations: print("THAT COMPETITION IS CURRENTLY NOT ACTIVE") -- GitLab From b6d3752a15d0c10e97e3327ef0e521beb46393e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Victor=20L=C3=B6fgren?= <viclo211@student.liu.se> Date: Sun, 18 Apr 2021 23:00:35 +0200 Subject: [PATCH 19/20] Add all dependencies to requirements.txt --- server/requirements.txt | Bin 2308 -> 2448 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/server/requirements.txt b/server/requirements.txt index c64222b754a34a97ff5c2f57bd017ac682dbd42c..bda47a88036c81470bc7a6b2112b2ecedd904ed7 100644 GIT binary patch delta 180 zcmZn>njpNvj!D#+A)ld$p_suI2#p!^7z}{eaPmPW;mLMP(`17fQW;7a3K(*LsuCGe zfV>p2N<)y!$@`fyH-|7EVYEyH8<D|~3sjT?R$R`Y3pBJ4$OefdGh_g@<}y@*HG`}$ oVlV-blLc9)O9n7x!gYb1VF8v0TCjOBYa=5oNV&;oP4?T209K(StpET3 delta 80 zcmbOr+#<BWj%jil)3nKY%pvM-3^@#m48;uD47v=V3<W@5K9HXR6tM+D0|q??BOo^1 dT+4itadI2$gv};wHH@r=Krw^OTiI_j0ssk<6HNdB -- GitLab From 32dd6ea08b8d2dec1972bb7beb13e8f6b6a84f69 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Victor=20L=C3=B6fgren?= <viclo211@student.liu.se> Date: Sun, 18 Apr 2021 23:13:25 +0200 Subject: [PATCH 20/20] Fix client tests --- .../src/reducers/presentationReducer.test.ts | 21 ++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/client/src/reducers/presentationReducer.test.ts b/client/src/reducers/presentationReducer.test.ts index 15bcd337..a155eab5 100644 --- a/client/src/reducers/presentationReducer.test.ts +++ b/client/src/reducers/presentationReducer.test.ts @@ -20,6 +20,11 @@ const initialState = { title: '', }, teams: [], + code: '', + timer: { + enabled: false, + value: 0, + }, } it('should return the initial state', () => { @@ -46,7 +51,9 @@ it('should handle SET_PRESENTATION_COMPETITION', () => { ).toEqual({ competition: testCompetition, slide: testCompetition.slides[0], - teams: [], + teams: initialState.teams, + code: initialState.code, + timer: initialState.timer, }) }) @@ -70,6 +77,8 @@ it('should handle SET_PRESENTATION_TEAMS', () => { competition: initialState.competition, slide: initialState.slide, teams: testTeams, + code: initialState.code, + timer: initialState.timer, }) }) @@ -92,6 +101,8 @@ it('should handle SET_PRESENTATION_SLIDE', () => { competition: initialState.competition, slide: testSlide, teams: initialState.teams, + code: initialState.code, + timer: initialState.timer, }) }) @@ -107,6 +118,8 @@ describe('should handle SET_PRESENTATION_SLIDE_PREVIOUS', () => { }, teams: initialState.teams, slide: { competition_id: 0, order: 1 } as Slide, + code: initialState.code, + timer: initialState.timer, } expect( presentationReducer(testPresentationState, { @@ -116,6 +129,8 @@ describe('should handle SET_PRESENTATION_SLIDE_PREVIOUS', () => { competition: testPresentationState.competition, slide: testPresentationState.competition.slides[0], teams: testPresentationState.teams, + code: initialState.code, + timer: initialState.timer, }) }) it('by not changing slide if there is no previous one', () => { @@ -129,6 +144,8 @@ describe('should handle SET_PRESENTATION_SLIDE_PREVIOUS', () => { }, teams: initialState.teams, slide: { competition_id: 0, order: 0 } as Slide, + code: initialState.code, + timer: initialState.timer, } expect( presentationReducer(testPresentationState, { @@ -138,6 +155,8 @@ describe('should handle SET_PRESENTATION_SLIDE_PREVIOUS', () => { competition: testPresentationState.competition, slide: testPresentationState.competition.slides[0], teams: testPresentationState.teams, + code: initialState.code, + timer: initialState.timer, }) }) }) -- GitLab