Skip to content

Commit

Permalink
Add Timer functionality (#462)
Browse files Browse the repository at this point in the history
  • Loading branch information
antoinejaussoin authored Feb 2, 2023
1 parent fe37dcd commit 389a8d2
Show file tree
Hide file tree
Showing 61 changed files with 1,278 additions and 295 deletions.
4 changes: 4 additions & 0 deletions backend/src/common/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ const actions = {
REQUEST_BOARD: 'retrospected/session/request',
USER_READY: 'retrospected/user-ready',
CHAT_MESSAGE: 'retrospected/session/chat',
START_TIMER: 'retrospected/timer/start',
STOP_TIMER: 'retrospected/timer/stop',

RECEIVE_POST: 'retrospected/posts/receive/add',
RECEIVE_DELETE_POST: 'retrospected/posts/receive/delete',
Expand All @@ -40,6 +42,8 @@ const actions = {
RECEIVE_ERROR: 'retrospected/receive/error',
RECEIVE_USER_READY: 'retrospected/receive/user-ready',
RECEIVE_CHAT_MESSAGE: 'retrospected/session/chat/receive',
RECEIVE_TIMER_START: 'retrospected/timer/start/receive',
RECEIVE_TIMER_STOP: 'retrospected/timer/stop/receive',
};

export default actions;
4 changes: 4 additions & 0 deletions backend/src/common/models.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ export const defaultOptions: SessionOptions = {
allowCancelVote: false,
blurCards: false,
newPostsFirst: true,
allowTimer: false,
timerDuration: 0,
readonlyOnTimerEnd: true,
};

export const defaultSession: Omit<Session, 'createdBy'> = {
Expand All @@ -31,4 +34,5 @@ export const defaultSession: Omit<Session, 'createdBy'> = {
encrypted: null,
locked: false,
ready: [],
timer: null,
};
4 changes: 4 additions & 0 deletions backend/src/common/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ export interface Session extends PostContainer, Entity {
locked: boolean;
createdBy: User;
ready: string[];
timer: Date | null;
}

export interface SessionMetadata extends Entity {
Expand Down Expand Up @@ -60,6 +61,9 @@ export interface SessionOptions {
allowCancelVote: boolean;
blurCards: boolean;
newPostsFirst: boolean;
allowTimer: boolean;
timerDuration: number;
readonlyOnTimerEnd: boolean;
}

export interface Entity {
Expand Down
7 changes: 7 additions & 0 deletions backend/src/common/ws.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,3 +90,10 @@ export interface WsErrorPayload {
type: WsErrorType;
details: string | null;
}

export interface WsReceiveTimerStartPayload {
/**
* Duration in seconds
*/
duration: number;
}
30 changes: 30 additions & 0 deletions backend/src/db/actions/timer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { addSeconds } from 'date-fns';
import SessionRepository from '../repositories/SessionRepository.js';
import { transaction } from './transaction.js';

export async function startTimer(sessionId: string): Promise<number> {
return await transaction(async (manager) => {
const sessionRepository = manager.withRepository(SessionRepository);
const session = await sessionRepository.findOneBy({ id: sessionId });
if (!session) {
throw new Error('Session not found');
}
const duration = session.options.timerDuration;
session.timer = addSeconds(new Date(), duration);
await sessionRepository.save(session);

return duration;
});
}

export async function stopTimer(sessionId: string): Promise<void> {
return await transaction(async (manager) => {
const sessionRepository = manager.withRepository(SessionRepository);
const session = await sessionRepository.findOneBy({ id: sessionId });
if (!session) {
throw new Error('Session not found');
}
session.timer = null;
await sessionRepository.save(session);
});
}
4 changes: 4 additions & 0 deletions backend/src/db/entities/Session.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,8 @@ export default class SessionEntity {
visitors: UserEntity[] | undefined;
@Column({ default: false })
public locked: boolean;
@Column({ default: null, type: 'timestamp with time zone', nullable: true })
public timer: Date | null;
@Column('text', { array: true, default: '{}' })
public ready: string[];
@CreateDateColumn({ type: 'timestamp with time zone' })
Expand All @@ -87,6 +89,7 @@ export default class SessionEntity {
encrypted: this.encrypted,
locked: this.locked,
ready: this.ready,
timer: this.timer,
};
}

Expand All @@ -103,5 +106,6 @@ export default class SessionEntity {
this.encrypted = null;
this.locked = false;
this.ready = [];
this.timer = null;
}
}
9 changes: 9 additions & 0 deletions backend/src/db/entities/SessionOptions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,12 @@ export default class SessionOptionsEntity {
@Column({ default: false })
public allowCancelVote: boolean;
@Column({ default: false })
public allowTimer: boolean;
@Column({ type: 'numeric', default: 15 * 60 })
public timerDuration: number;
@Column({ default: true })
public readonlyOnTimerEnd: boolean;
@Column({ default: false })
public blurCards: boolean;
@Column({ default: true })
public newPostsFirst: boolean;
Expand All @@ -54,6 +60,9 @@ export default class SessionOptionsEntity {
this.allowCancelVote = optionsWithDefault.allowCancelVote;
this.blurCards = optionsWithDefault.blurCards;
this.newPostsFirst = optionsWithDefault.newPostsFirst;
this.allowTimer = optionsWithDefault.allowTimer;
this.timerDuration = optionsWithDefault.timerDuration;
this.readonlyOnTimerEnd = optionsWithDefault.readonlyOnTimerEnd;
}
}

Expand Down
14 changes: 14 additions & 0 deletions backend/src/db/migrations/1674905273870-TimerOnSession.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { MigrationInterface, QueryRunner } from "typeorm";

export class TimerOnSession1674905273870 implements MigrationInterface {
name = 'TimerOnSession1674905273870'

public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`ALTER TABLE "sessions" ADD "timer" TIMESTAMP WITH TIME ZONE`);
}

public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`ALTER TABLE "sessions" DROP COLUMN "timer"`);
}

}
20 changes: 20 additions & 0 deletions backend/src/db/migrations/1674905786619-TimerOptions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { MigrationInterface, QueryRunner } from "typeorm";

export class TimerOptions1674905786619 implements MigrationInterface {
name = 'TimerOptions1674905786619'

public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`ALTER TABLE "templates" ADD "options_allow_timer" boolean NOT NULL DEFAULT false`);
await queryRunner.query(`ALTER TABLE "templates" ADD "options_timer_duration" numeric NOT NULL DEFAULT '900'`);
await queryRunner.query(`ALTER TABLE "sessions" ADD "options_allow_timer" boolean NOT NULL DEFAULT false`);
await queryRunner.query(`ALTER TABLE "sessions" ADD "options_timer_duration" numeric NOT NULL DEFAULT '900'`);
}

public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`ALTER TABLE "sessions" DROP COLUMN "options_timer_duration"`);
await queryRunner.query(`ALTER TABLE "sessions" DROP COLUMN "options_allow_timer"`);
await queryRunner.query(`ALTER TABLE "templates" DROP COLUMN "options_timer_duration"`);
await queryRunner.query(`ALTER TABLE "templates" DROP COLUMN "options_allow_timer"`);
}

}
16 changes: 16 additions & 0 deletions backend/src/db/migrations/1675096520361-LockControlsTimerEnd.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { MigrationInterface, QueryRunner } from "typeorm";

export class LockControlsTimerEnd1675096520361 implements MigrationInterface {
name = 'LockControlsTimerEnd1675096520361'

public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`ALTER TABLE "templates" ADD "options_readonly_on_timer_end" boolean NOT NULL DEFAULT true`);
await queryRunner.query(`ALTER TABLE "sessions" ADD "options_readonly_on_timer_end" boolean NOT NULL DEFAULT true`);
}

public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`ALTER TABLE "sessions" DROP COLUMN "options_readonly_on_timer_end"`);
await queryRunner.query(`ALTER TABLE "templates" DROP COLUMN "options_readonly_on_timer_end"`);
}

}
50 changes: 49 additions & 1 deletion backend/src/game.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import {
Message,
WsCancelVotesPayload,
WsReceiveCancelVotesPayload,
WsReceiveTimerStartPayload,
} from './common/index.js';
import { RateLimiterMemory } from 'rate-limiter-flexible';
import chalk from 'chalk-template';
Expand Down Expand Up @@ -64,6 +65,8 @@ import { cancelVotes, registerVote } from './db/actions/votes.js';
import { deserialiseIds, UserIds } from './utils.js';
import { QueryFailedError } from 'typeorm';
import { saveChatMessage } from './db/actions/chat.js';
import { startTimer, stopTimer } from './db/actions/timer.js';
import { differenceInSeconds } from 'date-fns';

const {
ACK,
Expand Down Expand Up @@ -104,6 +107,10 @@ const {
REQUEST_BOARD,
CHAT_MESSAGE,
RECEIVE_CHAT_MESSAGE,
START_TIMER,
STOP_TIMER,
RECEIVE_TIMER_START,
RECEIVE_TIMER_STOP,
} = Actions;

interface Users {
Expand Down Expand Up @@ -333,7 +340,7 @@ export default (io: Server) => {
if (user) {
const userEntity = await getUser(user.id);
if (userEntity) {
// TODO : inneficient, rework all this
// TODO : inefficient, rework all this
await storeVisitor(sessionId, userEntity);
const sessionEntity2 = await getSessionWithVisitors(sessionId);
if (sessionEntity2) {
Expand All @@ -346,6 +353,15 @@ export default (io: Server) => {
const session = await getSession(sessionId);
if (session) {
sendToSelf<Session>(socket, RECEIVE_BOARD, session);
if (session.timer) {
sendToSelf<WsReceiveTimerStartPayload>(
socket,
RECEIVE_TIMER_START,
{
duration: differenceInSeconds(session.timer, new Date()),
}
);
}
} else {
sendToSelf<WsErrorPayload>(socket, RECEIVE_ERROR, {
type: 'cannot_get_session',
Expand Down Expand Up @@ -595,6 +611,35 @@ export default (io: Server) => {
sendToAll<boolean>(socket, sessionId, RECEIVE_LOCK_SESSION, locked);
};

const onStartTimer = async (
userIds: UserIds | null,
sessionId: string,
_: unknown,
socket: Socket
) => {
if (checkUser(userIds, socket)) {
const duration = await startTimer(sessionId);
sendToAll<WsReceiveTimerStartPayload>(
socket,
sessionId,
RECEIVE_TIMER_START,
{ duration }
);
}
};

const onStopTimer = async (
userIds: UserIds | null,
sessionId: string,
_: unknown,
socket: Socket
) => {
if (checkUser(userIds, socket)) {
await stopTimer(sessionId);
sendToAll<undefined>(socket, sessionId, RECEIVE_TIMER_STOP, undefined);
}
};

io.on('connection', async (socket) => {
const ip =
// eslint-disable-next-line @typescript-eslint/no-explicit-any
Expand Down Expand Up @@ -641,6 +686,9 @@ export default (io: Server) => {

{ type: CHAT_MESSAGE, handler: onChatMessage },

{ type: START_TIMER, handler: onStartTimer },
{ type: STOP_TIMER, handler: onStopTimer },

{ type: JOIN_SESSION, handler: onJoinSession },
{ type: REQUEST_BOARD, handler: onRequestBoard },
{ type: RENAME_SESSION, handler: onRenameSession },
Expand Down
Binary file added frontend/public/fonts/digital/DIGITALDREAM.ttf
Binary file not shown.
Binary file added frontend/public/fonts/digital/DIGITALDREAM.woff2
Binary file not shown.
Binary file added frontend/public/fonts/digital/DIGITALDREAMFAT.ttf
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
7 changes: 7 additions & 0 deletions frontend/public/fonts/digital/pizzadude.dk License.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
All fonts designed and copyrighted Jakob Fischer / pizzadude.dk

The fonts are provided free for personal or commercial use, however they may not be redistributed, sold or modified without the permission of Jakob Fischer / pizzadude.dk.

I have decided to let people use my freeware fonts without paying the usual $US25 commercial fee - but, I urge people to buy one of my commercial fonts as compensation and/or creating a link to www.pizzadude.dk

Jakob Fischer / pizzadude.dk is not liable for any damage resulting from the use of these fonts.
5 changes: 5 additions & 0 deletions frontend/src/GlobalStyles.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
import { css } from '@emotion/react';

const GlobalStyle = css`
@font-face {
font-family: 'DIGITALDREAM';
src: url('/fonts/digital/DIGITALDREAM.woff2') format('woff2');
}
html {
touch-action: manipulation;
font-family: 'Roboto', 'Helvetica', 'Arial', sans-serif;
Expand Down
4 changes: 4 additions & 0 deletions frontend/src/common/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ const actions = {
REQUEST_BOARD: 'retrospected/session/request',
USER_READY: 'retrospected/user-ready',
CHAT_MESSAGE: 'retrospected/session/chat',
START_TIMER: 'retrospected/timer/start',
STOP_TIMER: 'retrospected/timer/stop',

RECEIVE_POST: 'retrospected/posts/receive/add',
RECEIVE_DELETE_POST: 'retrospected/posts/receive/delete',
Expand All @@ -40,6 +42,8 @@ const actions = {
RECEIVE_ERROR: 'retrospected/receive/error',
RECEIVE_USER_READY: 'retrospected/receive/user-ready',
RECEIVE_CHAT_MESSAGE: 'retrospected/session/chat/receive',
RECEIVE_TIMER_START: 'retrospected/timer/start/receive',
RECEIVE_TIMER_STOP: 'retrospected/timer/stop/receive',
};

export default actions;
4 changes: 4 additions & 0 deletions frontend/src/common/models.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ export const defaultOptions: SessionOptions = {
blurCards: false,
newPostsFirst: true,
allowCancelVote: false,
allowTimer: false,
timerDuration: 0,
readonlyOnTimerEnd: true,
};

export const defaultSession: Omit<Session, 'createdBy'> = {
Expand All @@ -31,4 +34,5 @@ export const defaultSession: Omit<Session, 'createdBy'> = {
encrypted: null,
locked: false,
ready: [],
timer: null,
};
4 changes: 4 additions & 0 deletions frontend/src/common/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ export interface Session extends PostContainer, Entity {
locked: boolean;
createdBy: User;
ready: string[];
timer: Date | null;
}

export interface SessionMetadata extends Entity {
Expand Down Expand Up @@ -60,6 +61,9 @@ export interface SessionOptions {
allowCancelVote: boolean;
blurCards: boolean;
newPostsFirst: boolean;
allowTimer: boolean;
timerDuration: number;
readonlyOnTimerEnd: boolean;
}

export interface Entity {
Expand Down
7 changes: 7 additions & 0 deletions frontend/src/common/ws.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,3 +90,10 @@ export interface WsErrorPayload {
type: WsErrorType;
details: string | null;
}

export interface WsReceiveTimerStartPayload {
/**
* Duration in seconds
*/
duration: number;
}
Loading

0 comments on commit 389a8d2

Please sign in to comment.