Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add the option to shuffle playlists and swap the order of your songs. #139

Merged
merged 6 commits into from
Oct 2, 2024
Merged
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
2 changes: 1 addition & 1 deletion .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ module.exports = {
commonjs: true,
es2021: true,
},
extends: ["plugin:vue/vue3-essential", "eslint:recommended"],
extends: ["eslint:recommended", "plugin:vue/vue3-recommended", "prettier"],
overrides: [],
parserOptions: {
ecmaVersion: "latest",
Expand Down
68 changes: 51 additions & 17 deletions client/src/components/CurrentQueue.vue
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,18 @@
<label class="text-2xl text-gray-600 dark:text-white">
Queue - {{ queueDuration }}
</label>
<div class="relative" v-if="queue.length >= 1 && admin">
<div v-if="queue.length >= 1 && admin" class="relative">
<div
class="mt-4 flex rounded-md text-center text-white duration-200 md:mt-0">
<button
@click="clearQueue()"
class="bg-proto_blue rounded-l-md px-4 py-0.5 duration-200 hover:-translate-x-1 hover:-translate-y-0.5 hover:opacity-80 hover:shadow-lg">
class="bg-proto_blue rounded-l-md px-4 py-0.5 duration-200 hover:-translate-x-1 hover:-translate-y-0.5 hover:opacity-80 hover:shadow-lg"
@click="clearQueue()">
Clear queue
</button>
<button
class="bg-proto_blue rounded-r-md border-l border-l-white px-4 py-0.5 duration-200 hover:-translate-x-1 hover:-translate-y-0.5 hover:opacity-80 hover:shadow-lg"
@click="removeVideoDropDown = true"
@focusout="hideRemoveVideoDropDown"
class="bg-proto_blue rounded-r-md border-l border-l-white px-4 py-0.5 duration-200 hover:-translate-x-1 hover:-translate-y-0.5 hover:opacity-80 hover:shadow-lg">
@focusout="hideRemoveVideoDropDown">
<font-awesome-icon
:class="removeVideoDropDown ? 'rotate-180' : ''"
class="duration-300"
Expand All @@ -36,8 +36,8 @@
<li
v-for="video in usersInQueue"
:key="video.user.id"
@click="removeVideosForUser(video.user.id)"
class="hover:bg-proto_blue w-full py-1 pl-3 pr-9 text-left text-gray-600 duration-300 hover:cursor-pointer hover:text-white dark:text-white">
class="hover:bg-proto_blue w-full py-1 pl-3 pr-9 text-left text-gray-600 duration-300 hover:cursor-pointer hover:text-white dark:text-white"
@click="removeVideosForUser(video.user.id)">
{{ video.user.name }}
</li>
</ul>
Expand All @@ -46,21 +46,21 @@
</div>
<div v-else-if="!admin && userHasItemsInQueue">
<button
@click="removeVideosForUser(userID)"
class="bg-proto_blue rounded-md px-4 py-0.5 text-white duration-200 hover:-translate-x-1 hover:-translate-y-0.5 hover:opacity-80 hover:shadow-lg">
class="bg-proto_blue rounded-md px-4 py-0.5 text-white duration-200 hover:-translate-x-1 hover:-translate-y-0.5 hover:opacity-80 hover:shadow-lg"
@click="removeVideosForUser(userID)">
Remove all my videos
</button>
</div>
</div>
<div
class="scrollbar-thin scrollbar-thumb-gray-400 scrollbar-track-proto_background_gray dark:scrollbar-thumb-neutral-800 dark:scrollbar-track-proto_background_gray-dark flex max-h-[84vh] justify-center overflow-y-scroll overscroll-contain px-0">
class="scrollbar-thin scrollbar-thumb-gray-400 scrollbar-track-proto_background_gray dark:scrollbar-thumb-neutral-800 dark:scrollbar-track-proto_background_gray-dark flex max-h-[84vh] justify-center overscroll-auto px-0">
<div v-if="skeletonLoading" class="w-full">
<ul class="grid gap-2">
<SkeletonResult v-for="index in 10" :key="index" />
</ul>
</div>
<div v-if="!skeletonLoading" class="w-full">
<ul class="grid gap-2 pr-4">
<TransitionGroup name="list" tag="ul" class="grid gap-2 pr-4">
<VideoCard
v-for="(video, index) in queue"
:key="video.id"
Expand All @@ -70,10 +70,14 @@
:channel="video.channel"
:duration="video.durationFormatted"
:thumbnail="video.thumbnail.url"
:removeButton="admin || video.user.id === userID"
:videoID="video.id"
@remove-clicked="removeFromQueue([video.id])" />
</ul>
:remove-button="props.admin || video.user.id === props.userID"
:can-move-up="canMoveVideoUp(index)"
:can-move-down="canMoveVideoDown(index)"
:video-i-d="video.id"
@remove-clicked="removeFromQueue([video.id])"
@move-clicked-up="moveVideo(video.id, true)"
@move-clicked-down="moveVideo(video.id, false)" />
</TransitionGroup>
<div
v-if="!skeletonLoading && queue.length < 1"
class="mt-4 text-gray-400">
Expand Down Expand Up @@ -110,9 +114,27 @@ const props = defineProps({
type: Boolean,
default: false,
},
userID: Number,
userID: { type: Number, default: null },
});

const canMoveVideoDown = (videoIndex) => {
return queue.value.slice(videoIndex, -1).some((video) => {
return (
video.user.id === queue.value[videoIndex].user.id &&
(props.admin || video.user.id === props.userID)
);
});
};

const canMoveVideoUp = (videoIndex) => {
return queue.value.slice(0, videoIndex).some((video) => {
return (
video.user.id === queue.value[videoIndex].user.id &&
(props.admin || video.user.id === props.userID)
);
});
};

// return array of unique users in queue
const usersInQueue = computed(() => {
return queue.value.filter(
Expand All @@ -128,7 +150,6 @@ const userHasItemsInQueue = computed(() => {
const videosOfUser = queue.value.filter((video) => {
return video.user.id === props.userID;
});
console.log(videosOfUser);
return videosOfUser.length > 0;
});

Expand All @@ -151,6 +172,19 @@ async function removeFromQueue(videoIDs) {
});
}

async function moveVideo(videoID, up) {
const data = await new Promise((resolve) => {
normalSocket.emit("move-video", videoID, up, (callback) => {
resolve(callback);
});
});
emit("display-toast", {
status: data.status ?? enums.STATUS.SUCCESS,
message:
data.message ?? `Successfully moved video` + (up ? " up!" : " down!"),
});
}

async function clearQueue() {
if (!props.admin) return;
const data = await new Promise((resolve) => {
Expand Down
26 changes: 13 additions & 13 deletions client/src/components/HeaderFieldButtons.vue
Original file line number Diff line number Diff line change
@@ -1,38 +1,38 @@
<template>
<div
:class="classes"
class="display-block mr-6 ml-2 mt-auto shrink-0 justify-center">
class="display-block ml-2 mr-6 mt-auto shrink-0 justify-center">
<div v-if="name" class="my-2 text-2xl text-white">Welcome {{ name }}!</div>
<div
class="grid grid-cols-2 gap-2 sm:grid-cols-4 md:grid-cols-2 xl:grid-cols-4">
<router-link
to="/screen/admin"
v-if="adminScreen"
class="bg-proto_blue rounded-md py-2 px-4 text-center text-white duration-200 hover:-translate-x-1 hover:-translate-y-0.5 hover:opacity-80 hover:shadow-lg">
to="/screen/admin"
class="bg-proto_blue rounded-md px-4 py-2 text-center text-white duration-200 hover:-translate-x-1 hover:-translate-y-0.5 hover:opacity-80 hover:shadow-lg">
Admin screen
</router-link>
<router-link
to="/screen"
v-if="screen"
class="bg-proto_blue rounded-md py-2 px-4 text-center text-white duration-200 hover:-translate-x-1 hover:-translate-y-0.5 hover:opacity-80 hover:shadow-lg">
to="/screen"
class="bg-proto_blue rounded-md px-4 py-2 text-center text-white duration-200 hover:-translate-x-1 hover:-translate-y-0.5 hover:opacity-80 hover:shadow-lg">
Screen
</router-link>
<router-link
to="/statistics"
v-if="statistics"
class="bg-proto_blue rounded-md py-2 px-4 text-center text-white duration-200 hover:-translate-x-1 hover:-translate-y-0.5 hover:opacity-80 hover:shadow-lg">
to="/statistics"
class="bg-proto_blue rounded-md px-4 py-2 text-center text-white duration-200 hover:-translate-x-1 hover:-translate-y-0.5 hover:opacity-80 hover:shadow-lg">
Statistics
</router-link>
<router-link
to="/remote"
v-if="remote"
class="bg-proto_blue rounded-md py-2 px-4 text-center text-white duration-200 hover:-translate-x-1 hover:-translate-y-0.5 hover:opacity-80 hover:shadow-lg">
to="/remote"
class="bg-proto_blue rounded-md px-4 py-2 text-center text-white duration-200 hover:-translate-x-1 hover:-translate-y-0.5 hover:opacity-80 hover:shadow-lg">
Remote
</router-link>
<router-link
to="/soundboard"
v-if="soundboard"
class="bg-proto_blue rounded-md py-2 px-4 text-center text-white duration-200 hover:-translate-x-1 hover:-translate-y-0.5 hover:opacity-80 hover:shadow-lg">
to="/soundboard"
class="bg-proto_blue rounded-md px-4 py-2 text-center text-white duration-200 hover:-translate-x-1 hover:-translate-y-0.5 hover:opacity-80 hover:shadow-lg">
Soundboard
</router-link>
</div>
Expand Down Expand Up @@ -67,7 +67,7 @@ defineProps({
type: Boolean,
default: false,
},
name: String,
classes: String,
name: { type: String, default: "" },
classes: { type: String, default: "" },
});
</script>
24 changes: 12 additions & 12 deletions client/src/components/MasterControls.vue
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@
Master controls
</h3>
<button
@click="switchTheme"
v-if="!inProduction"
class="ml-auto flex-none rounded-md bg-gray-800 px-4 py-1 text-center text-white duration-200 hover:-translate-x-1 hover:-translate-y-0.5 hover:opacity-80 hover:shadow-lg dark:bg-gray-100 dark:text-gray-800 md:mt-0">
class="ml-auto flex-none rounded-md bg-gray-800 px-4 py-1 text-center text-white duration-200 hover:-translate-x-1 hover:-translate-y-0.5 hover:opacity-80 hover:shadow-lg dark:bg-gray-100 dark:text-gray-800 md:mt-0"
@click="switchTheme">
Switch Theme
</button>
</div>
Expand All @@ -18,12 +18,12 @@
Volume - {{ playerSettings.volume }}
</span>
<input
@change="volumeChange"
class="bg-proto_blue hover:bg-proto_blue/80 h-2 w-full appearance-none rounded-xl border border-gray-500 outline-none"
type="range"
min="0"
max="100"
:value="playerSettings.volume" />
:value="playerSettings.volume"
@change="volumeChange" />
<div class="container mt-2 flex">
<!-- Video/Radio toggle -->
<div class="flex">
Expand All @@ -33,15 +33,15 @@
</span>
</div>
<button
@click="toggleRadioProtube"
type="button"
:class="
playerSettings.playerType === enums.TYPES.RADIO
? 'bg-proto_blue'
: 'bg-proto_green'
"
class="relative inline-flex h-6 w-11 shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2"
role="switch">
role="switch"
@click="toggleRadioProtube">
<span
:class="
playerSettings.playerType === enums.TYPES.RADIO
Expand All @@ -62,26 +62,26 @@
<!-- TODO: Add back button in admin controls -->
<!--<font-awesome-icon class="cursor-pointer text-2xl mx-2 text-gray-600 dark:text-white" icon="backward" />-->
<font-awesome-icon
@click="playPause"
class="cursor-pointer text-2xl text-gray-600 dark:text-white"
:icon="
playerSettings.playerMode === enums.MODES.PLAYING
? 'pause'
: 'play'
">
"
@click="playPause">
</font-awesome-icon>
<font-awesome-icon
@click="skipVideo"
class="mx-2 cursor-pointer text-2xl text-gray-600 dark:text-white"
icon="forward">
icon="forward"
@click="skipVideo">
</font-awesome-icon>
</div>
<!-- New code button -->
<div class="relative ml-auto">
<div class="absolute -right-full min-w-max">
<button
@click="resetScreenCode"
class="bg-proto_blue hover:bg-proto_blue/80 rounded-md py-1 px-2 text-sm text-white shadow-md duration-200 hover:-translate-x-1 hover:-translate-y-0.5 hover:opacity-80">
class="bg-proto_blue hover:bg-proto_blue/80 rounded-md px-2 py-1 text-sm text-white shadow-md duration-200 hover:-translate-x-1 hover:-translate-y-0.5 hover:opacity-80"
@click="resetScreenCode">
New code
</button>
</div>
Expand Down
2 changes: 1 addition & 1 deletion client/src/components/PincodeComponent.vue
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ onMounted(() => {
});

socket.on("connect_error", (err) => {
if (err.message == "Invalid screencode")
if (err.message === "Invalid screencode")
processPinEntered(false, "Invalid pincode entered!");
processPinEntered(false, err);
// else processPinEntered(false, "Whoops.. Can't do anything with this response..");
Expand Down
6 changes: 3 additions & 3 deletions client/src/components/RadioScreen.vue
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
style="width: 150px"
:src="`https://www.nederland.fm/i/l/${radio.z}.gif`" />
<div
class="from-proto_blue absolute -top-1/2 -left-1/4 -z-10 h-52 w-52 animate-[spin_3s_linear_infinite] bg-gradient-to-r via-white to-white" />
class="from-proto_blue absolute -left-1/4 -top-1/2 -z-10 h-52 w-52 animate-[spin_3s_linear_infinite] bg-gradient-to-r via-white to-white" />
</div>
<br />
<audio
Expand All @@ -19,7 +19,7 @@
height="200"></audio>
<button
v-if="playButton"
class="bg-proto_blue hover:bg-proto_blue/80 my-auto mx-auto mt-5 flex rounded-md py-1 px-2 text-white shadow-md"
class="bg-proto_blue hover:bg-proto_blue/80 mx-auto my-auto mt-5 flex rounded-md px-2 py-1 text-white shadow-md"
@click="playClick">
play audio
</button>
Expand All @@ -31,7 +31,7 @@ import { ref, onMounted, watch } from "vue";

const playButton = ref(true);
const props = defineProps({
radio: Object,
radio: { type: Object, default: null },
volume: {
type: Number,
default: -1,
Expand Down
4 changes: 2 additions & 2 deletions client/src/components/RadioStations.vue
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@
:key="radio"
class="inline-block px-3">
<div
@click="setRadio(radio.z, radio.o)"
class="h-[3.75rem] w-24 rounded-lg duration-200 hover:-translate-x-1 hover:-translate-y-0.5 hover:cursor-pointer hover:opacity-80 hover:shadow-lg">
class="h-[3.75rem] w-24 rounded-lg duration-200 hover:-translate-x-1 hover:-translate-y-0.5 hover:cursor-pointer hover:opacity-80 hover:shadow-lg"
@click="setRadio(radio.z, radio.o)">
<img
:alt="radio.o"
class="bg-proto_blue hover:bg-proto_blue/80 overflow-hidden truncate rounded-lg text-white"
Expand Down
2 changes: 1 addition & 1 deletion client/src/components/ReconnectionHandler.vue
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ let stopConnecting = false;
const connectionDelayS = 5;

const props = defineProps({
socket: Object,
socket: { type: Object, default: null },
maxAttempts: {
type: Number,
default: -1,
Expand Down
14 changes: 7 additions & 7 deletions client/src/components/ResultsWrapper.vue
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,13 @@
:views="video.viewsFormatted"
:thumbnail="video.thumbnail.url"
:clickable="video?.status === undefined ? 'show' : ''"
:videoID="video.id"
:statusIcon="video.status"
:video-i-d="video.id"
:status-icon="video.status"
@video-clicked="addVideoToQueue" />
</ul>
<button
v-if="videos.length > 17"
class="bg-proto_blue mt-4 flex-none rounded-md py-2 px-4 text-center text-white duration-200 hover:-translate-x-1 hover:-translate-y-0.5 hover:opacity-80 hover:shadow-lg"
class="bg-proto_blue mt-4 flex-none rounded-md px-4 py-2 text-center text-white duration-200 hover:-translate-x-1 hover:-translate-y-0.5 hover:opacity-80 hover:shadow-lg"
@click="$emit('nextPage')">
Get more results
</button>
Expand All @@ -38,12 +38,12 @@ import VideoCard from "@/components/VideoCard.vue";
import enums from "@/js/Enums";
import socket from "@/js/RemoteSocket";

const emit = defineEmits(["display-toast"]);
const emit = defineEmits(["display-toast", "nextPage"]);

const props = defineProps({
videos: Object,
continuationToken: String,
skeletonLoading: Boolean,
videos: { type: Object, default: null },
continuationToken: { type: String, default: "" },
skeletonLoading: { type: Boolean, default: false },
});

async function addVideoToQueue(videoID) {
Expand Down
Loading