Skip to content

Commit

Permalink
Add manual guest editor and dashboard style changes
Browse files Browse the repository at this point in the history
  • Loading branch information
slmnio committed May 16, 2023
1 parent 0cd847f commit 341bec9
Show file tree
Hide file tree
Showing 9 changed files with 161 additions and 38 deletions.
9 changes: 6 additions & 3 deletions server/src/actions/update-broadcast.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,18 @@ const { safeInput } = require("../action-utils/action-utils");
module.exports = {
key: "update-broadcast",
auth: ["client"],
optionalParams: ["match", "advertise", "playerCams", "mapAttack", "title"],
optionalParams: ["match", "advertise", "playerCams", "mapAttack", "title", "manualGuests"],
/***
* @param {AnyAirtableID} match
* @param {ClientData} client
* @returns {Promise<void>}
*/
// eslint-disable-next-line no-empty-pattern
async handler({ match: matchID, advertise, playerCams, mapAttack, title }, { client }) {
async handler({ match: matchID, advertise, playerCams, mapAttack, title, manualGuests }, { client }) {
let broadcast = await this.helpers.get(client?.broadcast?.[0]);
if (!broadcast) throw ("No broadcast associated");

console.log({ matchID, advertise, playerCams, mapAttack, title });
console.log({ matchID, advertise, playerCams, mapAttack, title, manualGuests });
let validatedData = {};

if (matchID !== undefined) {
Expand All @@ -40,6 +40,9 @@ module.exports = {
if (title !== undefined) {
validatedData["Title"] = safeInput(title);
}
if (manualGuests !== undefined) {
validatedData["Manual Guests"] = safeInput(manualGuests);
}

console.log(validatedData);

Expand Down
9 changes: 5 additions & 4 deletions website/src/assets/app.css
Original file line number Diff line number Diff line change
Expand Up @@ -83,10 +83,6 @@ a:not(.link-text), a:not(.link-text):hover {
background-color: var(--theme);
}

.btn-primary {
background-color: var(--brand-theme);
border-color: var(--brand-theme);
}
/*.btn:hover:not(:disabled), .btn.hover:not(:disabled) {*/
/* background-color: #00a367 !important;*/
/* border-color: #00a367 !important;*/
Expand Down Expand Up @@ -279,3 +275,8 @@ input.no-arrows[type=number] {
width: 500px;
height: 500px;
}

.table.border-no-top tr:first-child td,
.table.border-no-top tr:first-child th {
border-top: none;
}
3 changes: 3 additions & 0 deletions website/src/components/broadcast/desk/CasterCam.vue
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,12 @@ export default {
return this.manualCamera ? true : this.apiVisible;
},
useCam() {
if (this.guest?.webcam?.includes("view=")) return true;
if (this.disableVideo) return null;
return this.guest?.use_cam || false;
},
streamID() {
if (this.guest?.webcam) return this.guest.webcam;
if (this.relayPrefix) return this.relayPrefix;
return this.guest?.cam_code || "";
},
Expand Down Expand Up @@ -175,6 +177,7 @@ export default {
border-radius: 50%;
box-shadow: 0 0 8px 0 black;
background-size: cover;
background-position: center;
transform: translate(0, -10%);
transition: all .4s ease;
}
Expand Down
3 changes: 1 addition & 2 deletions website/src/components/broadcast/desk/DeskOverlay.vue
Original file line number Diff line number Diff line change
Expand Up @@ -67,8 +67,7 @@ export default {
return manualGuests;
},
guests: function() {
if (!this.broadcast?.guests) return [];
const guests = ReactiveArray("guests", {
const guests = (!this.broadcast?.guests) ? [] : ReactiveArray("guests", {
player: ReactiveThing("player", {
socials: ReactiveArray("socials")
}),
Expand Down
9 changes: 9 additions & 0 deletions website/src/components/website/dashboard/DashboardModule.vue
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,15 @@ export default {
border: none;
}
.module-content >>> .table tr th:last-child,
.module-content >>> .table tr td:last-child {
border-right: none;
}
.module-content >>> .table tr th:first-child,
.module-content >>> .table tr td:first-child {
border-left: none;
}
.clip-swipe-down-enter-active,
.clip-swipe-down-leave-active {
transition: clip-path 200ms ease, max-height 200ms ease;
Expand Down
124 changes: 118 additions & 6 deletions website/src/components/website/dashboard/DeskEditor.vue
Original file line number Diff line number Diff line change
@@ -1,23 +1,79 @@
<template>
<div class="desk-editor">
<GuestEditor v-for="guest in manualGuests" v-model="guest" />
<table class="table table-bordered table-sm table-dark mb-0 opacity-changes border-no-top" :class="{'low-opacity': processing }">
<tr v-if="manualGuests?.length">
<th>Name</th>
<th>Avatar</th>
<th>Webcam link</th>
<th>Twitter handle</th>
<th>Pronouns</th>
<th></th>
</tr>
<tr class="guest-row" v-for="(guest, i) in manualGuests" :key="i">
<td>
<b-form-input v-model="manualGuests[i].name" type="text" placeholder="Guest name" :state="!manualGuests[i]?.name ? false : checkInputKeyMatches(manualGuests[i], 'name')" />
<b-form-invalid-feedback>{{ manualGuests[i]?.name ? 'Name must be distinguishable from other inputs' : 'Name is required' }}</b-form-invalid-feedback>
</td>
<td>
<div class="d-flex">
<div class="avatar-preview rounded-circle mr-1 bg-center flex-shrink-0" :style="{'backgroundImage': manualGuests[i]?.avatar && `url(${manualGuests[i]?.avatar})`}"></div>
<div>
<b-form-input v-model="manualGuests[i].avatar" type="text" placeholder="https://i.imgur.com/HHvEClX.png" :state="checkInputKeyMatches(manualGuests[i], 'avatar')" />
<b-form-invalid-feedback>Avatars must be valid links</b-form-invalid-feedback>
</div>
</div>
</td>
<td>
<b-form-input v-model="manualGuests[i].webcam" type="text" placeholder="https://vdo.ninja/?view=SLMN" :state="checkInputKeyMatches(manualGuests[i], 'webcam')" />
<b-form-invalid-feedback>Webcams must be valid VDO.Ninja view links</b-form-invalid-feedback>
</td>
<td>
<b-form-input v-model="manualGuests[i].twitter" type="text" placeholder="@slmnio" :state="checkInputKeyMatches(manualGuests[i], 'twitter')" />
<b-form-invalid-feedback>Twitter handles must start with @</b-form-invalid-feedback>
</td>
<td>
<b-form-input v-model="manualGuests[i].pronouns" type="text" placeholder="he/him" :state="checkInputKeyMatches(manualGuests[i], 'pronouns')" />
<b-form-invalid-feedback>Pronouns must contain "/"</b-form-invalid-feedback>
</td>
<td>
<b-button variant="danger" @click="confirmRemoveGuest(i)"><i class="fas fa-trash"></i></b-button>
</td>
</tr>
<tr v-if="!manualGuests?.length">
<td class="text-center" colspan="5">
<i>No manual guests listed</i>
</td>
</tr>
</table>
<div class="p-2 w-100 d-flex">
<b-button variant="success" @click="addGuest">
<i class="fas fa-fw fa-plus"></i> Add guest
</b-button>
<div class="spacer flex-grow-1"></div>
<b-button :disabled="processing" class="ml-2" :variant="saveData === lastSavedData ? 'secondary' : 'primary'" @click="saveGuests">
<i class="fas fa-fw" :class="{'fa-save': !processing, 'fa-pulse fa-spinner': processing}"></i> Save guests
</b-button>
</div>
</div>
</template>

<script>
import { ReactiveArray, ReactiveThing } from "@/utils/reactive";
import GuestEditor from "@/components/website/dashboard/GuestEditor.vue";
import { createGuestObject } from "@/utils/content-utils";
import { createGuestObject, getGuestString } from "@/utils/content-utils";
import { BButton, BFormInput, BFormInvalidFeedback } from "bootstrap-vue";
import { updateBroadcastData } from "@/utils/dashboard";
export default {
name: "DeskEditor",
components: { GuestEditor },
components: { BFormInvalidFeedback, BButton, BFormInput },
props: {
broadcast: {}
},
data: () => ({
processing: false,
manualGuests: [],
lastLoadedManualGuests: []
lastLoadedManualGuests: [],
lastSavedData: null
}),
computed: {
guests() {
Expand All @@ -31,15 +87,51 @@ export default {
theme: ReactiveThing("theme")
})
})(this.broadcast);
},
saveData() {
return this.manualGuests.map(guest => getGuestString(guest)).join("\n");
}
},
methods: {
addGuest() {
this.manualGuests.push({});
},
checkInputKeyMatches(guest, key) {
if (!guest?.[key]) return null;
const tempGuest = createGuestObject(getGuestString(guest));
console.log(guest[key], tempGuest[key], tempGuest[key] === guest[key]);
return tempGuest[key]?.trim() === guest[key]?.trim();
},
confirmRemoveGuest(i) {
const okay = window.confirm(`Remove guest "${this.manualGuests?.[i]?.name}"?`) === true;
if (!okay) return;
this.manualGuests.splice(i, 1);
},
async saveGuests() {
this.processing = true;
this.lastSavedData = this.saveData;
try {
const response = await updateBroadcastData(this.$root.auth, {
manualGuests: this.saveData
});
if (!response.error) {
this.$notyf.success("Updated guests");
}
} finally {
this.processing = false;
}
}
},
watch: {
broadcast: {
deep: true,
immediate: true,
handler(newData) {
if (newData?.manual_guests && JSON.stringify(newData?.manual_guests) !== JSON.stringify(this.lastLoadedManualGuests)) {
this.manualGuests = newData.manual_guests.split("\n").map(e => createGuestObject(e));
this.lastLoadedManualGuests = newData.manual_guests.split("\n").map(e => createGuestObject(e));
this.lastSavedData = newData.manual_guests.replaceAll(",", "|");
}
}
}
Expand All @@ -48,5 +140,25 @@ export default {
</script>
<style scoped>
.guest-row >>> .invalid-feedback {
color: #ff6473;
}
::placeholder {
color: rgba(0,0,0,0.3);
}
.avatar-preview {
width: 2.375em;
height: 2.375em;
background-color: rgba(255,255,255,0.2);
}
.opacity-changes {
opacity: 1;
transition: opacity .3s ease;
}
.low-opacity {
opacity: 0.5;
pointer-events: none;
user-select: none;
cursor: wait;
}
</style>
22 changes: 0 additions & 22 deletions website/src/components/website/dashboard/GuestEditor.vue

This file was deleted.

7 changes: 7 additions & 0 deletions website/src/utils/content-utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -429,6 +429,8 @@ export function createGuestObject(str) {

if (part.startsWith("@")) {
guest.twitter = part;
} else if (part.includes("view=")) {
guest.webcam = part;
} else if (part.startsWith("http")) {
guest.avatar = part;
} else if (part.includes("/")) {
Expand All @@ -439,3 +441,8 @@ export function createGuestObject(str) {
});
return guest;
}

export function getGuestString(guest) {
delete guest.manual;
return Object.values(guest).join("|");
}
13 changes: 12 additions & 1 deletion website/src/views/Dashboard.vue
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@
<DashboardModule title="Match Editor" icon-class="fas fa-pennant" class="broadcast-match-editor mb-2" v-if="liveMatch" start-opened>
<MatchEditor :hide-match-extras="true" :match="liveMatch"></MatchEditor>
</DashboardModule>
<DashboardModule title="Desk" icon-class="fas fa-users" class="desk-editor mb-2" start-opened>
<DashboardModule title="Desk Guests" icon-class="fas fa-users" class="desk-editor mb-2" start-opened>
<template v-slot:header v-if="deskGuestSource">Desk guests pulled from: {{ deskGuestSource }}</template>
<DeskEditor :broadcast="broadcast" />
</DashboardModule>
<DashboardModule title="Bracket Implications" icon-class="fas fa-sitemap" class="broadcast-bracket-editor mb-2" v-if="bracketCount">
Expand Down Expand Up @@ -144,6 +145,16 @@ export default {
})
})
})(this.liveMatch);
},
deskGuestSource() {
if (this.broadcast?.guests) {
return "Broadcast › Guests";
} else if (this.liveMatch?.casters) {
return "Broadcast › Live Match › Casters";
} else if (this.broadcast?.manual_guests) {
return "Broadcast › Manual Guests";
}
return null;
}
},
methods: {
Expand Down

0 comments on commit 341bec9

Please sign in to comment.