Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -134,3 +134,4 @@ streamdeck-plugin/com.esamarathon.streamdeck.sdPlugin
streamdeck-plugin/DistributionTool.exe
*.psd
/boxart/
.idea/
25 changes: 25 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,14 @@
"file": "rabbitmq-test.html",
"workspace": "Z9 - Debug",
"headerColor": "#c49215"
},
{
"name": "taskmaster-scoreboard",
"title": "Taskmaster Scoreboard",
"width": 5,
"file": "taskmaster-scoreboard.html",
"headerColor": "#33cbcb",
"workspace": "Taskmaster"
}
],
"graphics": [
Expand Down Expand Up @@ -296,6 +304,11 @@
"file": "media-box-only.html",
"width": 1920,
"height": 1080
},
{
"file": "taskmaster-scoreboard.html",
"width": 1920,
"height": 1000
}
],
"mount": [
Expand Down Expand Up @@ -356,6 +369,18 @@
"webp",
"gif"
]
},
{
"name": "taskmaster-participant-headshots",
"title": "Taskmaster Participant Headshots",
"allowedTypes": [
"jpg",
"jpeg",
"png",
"svg",
"webp",
"gif"
]
}
]
}
Expand Down
8 changes: 8 additions & 0 deletions schemas/taskMasterContestantList.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"$schema": "http://json-schema.org/draft-07/schema",
"type": "array",
"items": {
"$ref": "./taskmasterContestant.json"
},
"default": []
}
30 changes: 30 additions & 0 deletions schemas/taskmasterContestant.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
{
"$schema": "http://json-schema.org/draft-07/schema",
"type": "object",
"additionalProperties": false,
"properties": {
"uuid": {
"type": "string",
"default": null
},
"name": {
"type": "string",
"default": null
},
"currentScore": {
"type": "number",
"default": 0
},
"visibleScore": {
"type": "number",
"default": 0
}
},
"required": [
"uuid",
"name",
"currentScore",
"transitionScore",
"visibleScore"
]
}
2 changes: 1 addition & 1 deletion shared
Submodule shared updated 2 files
+70 −74 package-lock.json
+7 −7 package.json
31 changes: 30 additions & 1 deletion src/browser_shared/replicant_store.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,27 @@
import type { Bids, BigbuttonPlayerMap, Commentators, Countdown, CurrentRunDelay, DonationReader, DonationsToRead, DonationTotal, DonationTotalMilestones, GameLayouts, IntermissionSlides, ObsData, Omnibar, OtherStreamData, Prizes, ReaderIntroduction, ServerTimestamp, StreamDeckData, TtsVoices, UpcomingRunID, VideoPlayer } from '@esa-layouts/types/schemas';
import type {
Bids,
BigbuttonPlayerMap,
Commentators,
Countdown,
CurrentRunDelay,
DonationReader,
DonationsToRead,
DonationTotal,
DonationTotalMilestones,
GameLayouts,
IntermissionSlides,
ObsData,
Omnibar,
OtherStreamData,
Prizes,
ReaderIntroduction,
ServerTimestamp,
StreamDeckData,
TaskMasterContestantList,
TtsVoices,
UpcomingRunID,
VideoPlayer,
} from '@esa-layouts/types/schemas';
import type NodeCGTypes from '@nodecg/types';
import clone from 'clone';
import { SpeedcontrolUtilBrowser } from 'speedcontrol-util';
Expand All @@ -15,6 +38,7 @@ const sc = new SpeedcontrolUtilBrowser(nodecg);
export const reps: {
assetsIntermissionSlides: NodeCGTypes.ClientReplicant<NodeCGTypes.AssetFile[]>;
assetsReaderIntroductionImages: NodeCGTypes.ClientReplicant<NodeCGTypes.AssetFile[]>;
assetsTaskmasterParticipantHeadshots: NodeCGTypes.ClientReplicant<NodeCGTypes.AssetFile[]>;
bids: NodeCGTypes.ClientReplicant<Bids>;
bigbuttonPlayerMap: NodeCGTypes.ClientReplicant<BigbuttonPlayerMap>;
commentators: NodeCGTypes.ClientReplicant<Commentators>;
Expand All @@ -36,6 +60,7 @@ export const reps: {
runDataArray: NodeCGTypes.ClientReplicant<RunDataArray>;
serverTimestamp: NodeCGTypes.ClientReplicant<ServerTimestamp>;
streamDeckData: NodeCGTypes.ClientReplicant<StreamDeckData>;
taskmasterContestantList: NodeCGTypes.ClientReplicant<TaskMasterContestantList>;
timer: NodeCGTypes.ClientReplicant<Timer>;
ttsVoices: NodeCGTypes.ClientReplicant<TtsVoices>;
upcomingRunID: NodeCGTypes.ClientReplicant<UpcomingRunID>;
Expand All @@ -44,6 +69,7 @@ export const reps: {
} = {
assetsIntermissionSlides: nodecg.Replicant('assets:intermission-slides'),
assetsReaderIntroductionImages: nodecg.Replicant('assets:reader-introduction-images'),
assetsTaskmasterParticipantHeadshots: nodecg.Replicant('assets:taskmaster-participant-headshots'),
bids: nodecg.Replicant('bids'),
bigbuttonPlayerMap: nodecg.Replicant('bigbuttonPlayerMap'),
commentators: nodecg.Replicant('commentators'),
Expand All @@ -65,6 +91,7 @@ export const reps: {
runDataArray: sc.runDataArray,
serverTimestamp: nodecg.Replicant('serverTimestamp'),
streamDeckData: nodecg.Replicant('streamDeckData'),
taskmasterContestantList: nodecg.Replicant('taskmasterContestantList'),
timer: sc.timer,
ttsVoices: nodecg.Replicant('ttsVoices'),
upcomingRunID: nodecg.Replicant('upcomingRunID'),
Expand All @@ -75,6 +102,7 @@ export const reps: {
export interface ReplicantTypes {
assetsIntermissionSlides: NodeCGTypes.AssetFile[];
assetsReaderIntroductionImages: NodeCGTypes.AssetFile[];
assetsTaskmasterParticipantHeadshots: NodeCGTypes.AssetFile[];
bids: Bids;
bigbuttonPlayerMap: BigbuttonPlayerMap;
commentators: Commentators;
Expand All @@ -96,6 +124,7 @@ export interface ReplicantTypes {
runDataArray: RunDataArray;
serverTimestamp: ServerTimestamp;
streamDeckData: StreamDeckData;
taskmasterContestantList: TaskMasterContestantList;
timer: Timer;
ttsVoices: TtsVoices;
upcomingRunID: UpcomingRunID;
Expand Down
16 changes: 16 additions & 0 deletions src/dashboard/taskmaster-scoreboard/main.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
/* eslint no-new: off, @typescript-eslint/explicit-function-return-type: off */

import { setUpReplicants } from '@esa-layouts/browser_shared/replicant_store';
import Vue from 'vue';
import vuetify from '../_misc/vuetify';
import App from './main.vue';
import store from './store';

setUpReplicants(store).then(() => {
new Vue({
vuetify,
store,
el: '#App',
render: (h) => h(App),
});
});
86 changes: 86 additions & 0 deletions src/dashboard/taskmaster-scoreboard/main.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
<template>
<v-app>
<v-row>
<v-col>
<p>Contestant name is used for image lookup in the assets ({name}.ext)</p>
<p>Temp score / visible score</p>
</v-col>
<v-col>
<p>Add points or remove contestant</p>
</v-col>
</v-row>
<div v-for="contestant in contestants" :key="contestant.uuid">
<div>
<v-row>
<v-col>
<p>{{ contestant.name }} -
({{ contestant.currentScore }} / {{ contestant.visibleScore }})
</p>
</v-col>
<v-col>
<v-btn v-for="i in [1,2,3,4,5]" :key="`${contestant.uuid}-${i}`"
@click="addContestantPoints(contestant.uuid, i)"
small>
{{ i }}
</v-btn>
<v-btn color="red" @click="removeContestant(contestant.uuid)" small icon>
<v-icon>
mdi-delete
</v-icon>
</v-btn>
</v-col>
</v-row>
</div>
</div>
<v-row>
<v-col>
<v-btn @click="addContestant">Add contestant</v-btn>
</v-col>
<v-col>
<v-btn color="warning" @click="sendToGraphic" class="ml-2">Send to graphic</v-btn>
</v-col>
</v-row>
<v-row>
<v-col>
<v-btn color="red" @click="resetAllScores">Reset ALL scores</v-btn>
</v-col>
<v-col>
<v-btn color="red" @click="resetTempScores">Reset temp scores</v-btn>
</v-col>
</v-row>
</v-app>
</template>

<script lang="ts">
import { Vue, Component } from 'vue-property-decorator';
import { replicantNS } from '@esa-layouts/browser_shared/replicant_store';
import { TaskMasterContestantList } from '@esa-layouts/types/schemas';
import { storeModule } from './store';

@Component
export default class extends Vue {
@replicantNS.State(
(s) => s.reps.taskmasterContestantList,
) readonly contestants!: TaskMasterContestantList;

removeContestant = storeModule.removeParticipant;
resetTempScores = storeModule.resetTempScores;
resetAllScores = storeModule.resetAllScores;
sendToGraphic = storeModule.sendUpdate;

addContestantPoints(id: string, points: number): void {
storeModule.addPoints({ id, points });
}

addContestant(): void {
// eslint-disable-next-line no-alert
const newName = prompt('Enter contestant name (used for image lookup)');

if (!newName) {
return;
}

storeModule.addParticipant(newName);
}
}
</script>
121 changes: 121 additions & 0 deletions src/dashboard/taskmaster-scoreboard/store.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
import { replicantModule, ReplicantModule, ReplicantTypes } from '@esa-layouts/browser_shared/replicant_store';
import Vue from 'vue';
import Vuex, { Store } from 'vuex';
import { getModule, Module, Mutation, VuexModule } from 'vuex-module-decorators';
import { v4 as uuid } from 'uuid';
import { TaskMasterContestantList } from '@esa-layouts/types/schemas';

Vue.use(Vuex);

@Module({ name: 'OurModule' })
class OurModule extends VuexModule {
// Helper getter to return all replicants.
get reps(): ReplicantTypes {
return this.context.rootState.ReplicantModule.reps;
}

@Mutation
addParticipant(name: string): void {
const contestants = replicantModule.repsTyped.taskmasterContestantList;

contestants.push({
uuid: uuid(),
name,
visibleScore: 0,
currentScore: 0,
});

replicantModule.setReplicant<TaskMasterContestantList>({
name: 'taskmasterContestantList',
val: contestants,
});
}

@Mutation
removeParticipant(id: string): void {
const contestants = replicantModule.repsTyped.taskmasterContestantList;
const rightPerson = contestants.findIndex((contestant) => contestant.uuid === id);

if (rightPerson > -1) {
contestants.splice(rightPerson, 1);

replicantModule.setReplicant<TaskMasterContestantList>({
name: 'taskmasterContestantList',
val: contestants,
});
}
}

@Mutation
addPoints({ id, points }: { id: string, points: number }): void {
const contestants = replicantModule.repsTyped.taskmasterContestantList;
const rightPerson = contestants.findIndex((contestant) => contestant.uuid === id);

if (rightPerson > -1) {
const person = contestants[rightPerson];

if (Number.isNaN(person.currentScore)) {
person.currentScore = 0;
}

person.currentScore = (person.currentScore || 0) + points;

contestants[rightPerson] = person;

replicantModule.setReplicant<TaskMasterContestantList>({
name: 'taskmasterContestantList',
val: contestants,
});
}
}

@Mutation
resetTempScores(): void {
const contestants = replicantModule.repsTyped.taskmasterContestantList;

contestants.forEach((contestant) => {
// eslint-disable-next-line no-param-reassign
contestant.currentScore = contestant.visibleScore;
});

replicantModule.setReplicant<TaskMasterContestantList>({
name: 'taskmasterContestantList',
val: contestants,
});
}

@Mutation
resetAllScores(): void {
replicantModule.setReplicant<TaskMasterContestantList>({
name: 'taskmasterContestantList',
val: replicantModule.repsTyped.taskmasterContestantList.map((c) => ({
...c,
visibleScore: 0,
currentScore: 0,
})),
});
}

@Mutation
sendUpdate(): void {
const contestants = replicantModule.repsTyped.taskmasterContestantList;

contestants.forEach((contestant) => {
// eslint-disable-next-line no-param-reassign
contestant.visibleScore = contestant.currentScore;
});

replicantModule.setReplicant<TaskMasterContestantList>({
name: 'taskmasterContestantList',
val: contestants,
});
}
}

const store = new Store({
strict: process.env.NODE_ENV !== 'production',
state: {},
modules: { ReplicantModule, OurModule },
});
export default store;
export const storeModule = getModule(OurModule, store);
Loading