Browse Source

feat(Station): Added setting to configure skip vote threshold

Owen Diffey 1 year ago
parent
commit
746be4c74e

+ 21 - 5
backend/logic/actions/stations.js

@@ -296,7 +296,19 @@ CacheModule.runJob("SUB", {
 
 		stationModel.findOne(
 			{ _id: stationId },
-			["_id", "name", "displayName", "description", "type", "privacy", "owner", "requests", "autofill", "theme"],
+			[
+				"_id",
+				"name",
+				"displayName",
+				"description",
+				"type",
+				"privacy",
+				"owner",
+				"requests",
+				"autofill",
+				"theme",
+				"skipVoteThreshold"
+			],
 			(err, station) => {
 				WSModule.runJob("EMIT_TO_ROOMS", {
 					rooms: [`station.${stationId}`, `manage-station.${stationId}`, "admin.stations"],
@@ -820,7 +832,8 @@ export default {
 						owner: station.owner,
 						blacklist: station.blacklist,
 						theme: station.theme,
-						djs: station.djs
+						djs: station.djs,
+						skipVoteThreshold: station.skipVoteThreshold
 					};
 
 					StationsModule.userList[session.socketId] = station._id;
@@ -964,7 +977,8 @@ export default {
 						paused: station.paused,
 						currentSong: station.currentSong,
 						isFavorited: station.isFavorited,
-						djs: station.djs
+						djs: station.djs,
+						skipVoteThreshold: station.skipVoteThreshold
 					};
 
 					next(null, data);
@@ -1322,7 +1336,8 @@ export default {
 				},
 
 				(previousStation, next) => {
-					const { name, displayName, description, privacy, requests, autofill, theme } = newStation;
+					const { name, displayName, description, privacy, requests, autofill, theme, skipVoteThreshold } =
+						newStation;
 					const { enabled, limit, mode } = autofill;
 					// This object makes sure only certain properties can be changed by a user
 					const setObject = {
@@ -1334,7 +1349,8 @@ export default {
 						"autofill.enabled": enabled,
 						"autofill.limit": limit,
 						"autofill.mode": mode,
-						theme
+						theme,
+						skipVoteThreshold
 					};
 
 					stationModel.updateOne({ _id: stationId }, { $set: setObject }, { runValidators: true }, err => {

+ 1 - 1
backend/logic/db/index.js

@@ -13,7 +13,7 @@ const REQUIRED_DOCUMENT_VERSIONS = {
 	queueSong: 1,
 	report: 6,
 	song: 9,
-	station: 8,
+	station: 9,
 	user: 4,
 	youtubeApiRequest: 1,
 	youtubeVideo: 1,

+ 2 - 1
backend/logic/db/schemas/station.js

@@ -54,5 +54,6 @@ export default {
 	theme: { type: String, enum: ["blue", "purple", "teal", "orange", "red"], default: "blue" },
 	blacklist: [{ type: mongoose.Schema.Types.ObjectId, ref: "playlists" }],
 	djs: [{ type: mongoose.Schema.Types.ObjectId, ref: "users" }],
-	documentVersion: { type: Number, default: 8, required: true }
+	skipVoteThreshold: { type: Number, min: 0, max: 100, default: 50, required: true },
+	documentVersion: { type: Number, default: 9, required: true }
 };

+ 28 - 0
backend/logic/migration/migrations/migration24.js

@@ -0,0 +1,28 @@
+/**
+ * Migration 24
+ *
+ * Migration for setting station skip vote threshold
+ *
+ * @param {object} MigrationModule - the MigrationModule
+ * @returns {Promise} - returns promise
+ */
+export default async function migrate(MigrationModule) {
+	const stationModel = await MigrationModule.runJob("GET_MODEL", { modelName: "station" }, this);
+
+	return new Promise((resolve, reject) => {
+		this.log("INFO", `Migration 24. Updating stations with document version 8.`);
+		stationModel.updateMany(
+			{ documentVersion: 8 },
+			{
+				$set: {
+					documentVersion: 9,
+					skipVoteThreshold: 100
+				}
+			},
+			err => {
+				if (err) reject(new Error(err));
+				else resolve();
+			}
+		);
+	});
+}

+ 5 - 1
backend/logic/stations.js

@@ -893,7 +893,11 @@ class _StationsModule extends CoreClass {
 							err => {
 								if (err) return next(err);
 
-								if (!station.paused && users.length <= skipVotes) shouldSkip = true;
+								if (
+									!station.paused &&
+									Math.min(skipVotes, users.length) / users.length >= station.skipVoteThreshold / 100
+								)
+									shouldSkip = true;
 								return next(null, shouldSkip);
 							}
 						);

+ 122 - 0
frontend/src/components/modals/ManageStation/Settings.vue

@@ -60,6 +60,7 @@ const { inputs, save, setOriginalValue } = useForm(
 		},
 		theme: station.value.theme,
 		privacy: station.value.privacy,
+		skipVoteThreshold: station.value.skipVoteThreshold,
 		requestsEnabled: station.value.requests.enabled,
 		requestsAccess: station.value.requests.access,
 		requestsLimit: station.value.requests.limit,
@@ -77,6 +78,7 @@ const { inputs, save, setOriginalValue } = useForm(
 				description: values.description,
 				theme: values.theme,
 				privacy: values.privacy,
+				skipVoteThreshold: values.skipVoteThreshold,
 				requests: {
 					...oldStation.requests,
 					enabled: values.requestsEnabled,
@@ -121,6 +123,7 @@ watch(station, value => {
 		description: value.description,
 		theme: value.theme,
 		privacy: value.privacy,
+		skipVoteThreshold: value.skipVoteThreshold,
 		requestsEnabled: value.requests.enabled,
 		requestsAccess: value.requests.access,
 		requestsLimit: value.requests.limit,
@@ -181,6 +184,24 @@ watch(station, value => {
 				</div>
 			</div>
 
+			<div class="small-section">
+				<label class="label">
+					Skip Vote Threshold
+					<info-icon
+						tooltip="The % of logged-in station users required to vote to skip a song"
+					/>
+				</label>
+				<div class="control is-expanded input-slider">
+					<input
+						v-model="inputs['skipVoteThreshold'].value"
+						type="range"
+						min="0"
+						max="100"
+					/>
+					<span>{{ inputs["skipVoteThreshold"].value }}%</span>
+				</div>
+			</div>
+
 			<div
 				class="requests-settings"
 				:class="{ enabled: inputs['requestsEnabled'].value }"
@@ -342,6 +363,107 @@ watch(station, value => {
 		}
 	}
 
+	.input-slider {
+		display: flex;
+
+		input[type="range"] {
+			-webkit-appearance: none;
+			margin: 0;
+			padding: 0;
+			width: 100%;
+			min-width: 100px;
+			background: transparent;
+		}
+
+		input[type="range"]:focus {
+			outline: none;
+		}
+
+		input[type="range"]::-webkit-slider-runnable-track {
+			width: 100%;
+			height: 5.2px;
+			cursor: pointer;
+			box-shadow: 0;
+			background: var(--light-grey-3);
+			border-radius: @border-radius;
+			border: 0;
+		}
+
+		input[type="range"]::-webkit-slider-thumb {
+			box-shadow: 0;
+			border: 0;
+			height: 19px;
+			width: 19px;
+			border-radius: 100%;
+			background: var(--primary-color);
+			cursor: pointer;
+			-webkit-appearance: none;
+			margin-top: -6.5px;
+		}
+
+		input[type="range"]::-moz-range-track {
+			width: 100%;
+			height: 5.2px;
+			cursor: pointer;
+			box-shadow: 0;
+			background: var(--light-grey-3);
+			border-radius: @border-radius;
+			border: 0;
+		}
+
+		input[type="range"]::-moz-range-thumb {
+			box-shadow: 0;
+			border: 0;
+			height: 19px;
+			width: 19px;
+			border-radius: 100%;
+			background: var(--primary-color);
+			cursor: pointer;
+			-webkit-appearance: none;
+			margin-top: -6.5px;
+		}
+		input[type="range"]::-ms-track {
+			width: 100%;
+			height: 5.2px;
+			cursor: pointer;
+			box-shadow: 0;
+			background: var(--light-grey-3);
+			border-radius: @border-radius;
+		}
+
+		input[type="range"]::-ms-fill-lower {
+			background: var(--light-grey-3);
+			border: 0;
+			border-radius: 0;
+			box-shadow: 0;
+		}
+
+		input[type="range"]::-ms-fill-upper {
+			background: var(--light-grey-3);
+			border: 0;
+			border-radius: 0;
+			box-shadow: 0;
+		}
+
+		input[type="range"]::-ms-thumb {
+			box-shadow: 0;
+			border: 0;
+			height: 15px;
+			width: 15px;
+			border-radius: 100%;
+			background: var(--primary-color);
+			cursor: pointer;
+			-webkit-appearance: none;
+			margin-top: 1.5px;
+		}
+
+		& > span {
+			min-width: 40px;
+			margin-left: 10px;
+			text-align: center;
+		}
+	}
+
 	.requests-settings,
 	.autofill-settings {
 		display: flex;