Ver código fonte

Added station theme feature

Owen Diffey 4 anos atrás
pai
commit
9d852c5d67

+ 1 - 1
backend/logic/actions/playlists.js

@@ -1231,7 +1231,7 @@ export default {
 	 * Updates the privacy of a private playlist
 	 *
 	 * @param {object} session - the session object automatically added by socket.io
-	 * @param {string} playlistId - the id of the playlist we are updating the displayName for
+	 * @param {string} playlistId - the id of the playlist we are updating the privacy for
 	 * @param {Function} cb - gets called with the result
 	 */
 	updatePrivacy: isLoginRequired(async function updatePrivacy(session, playlistId, privacy, cb) {

+ 98 - 1
backend/logic/actions/stations.js

@@ -70,6 +70,16 @@ CacheModule.runJob("SUB", {
 	}
 });
 
+CacheModule.runJob("SUB", {
+	channel: "station.updateTheme",
+	cb: data => {
+		IOModule.runJob("EMIT_TO_ROOM", {
+			room: `station.${data.stationId}`,
+			args: ["event:theme.updated", data.theme]
+		});
+	}
+});
+
 CacheModule.runJob("SUB", {
 	channel: "station.queueLockToggled",
 	cb: data => {
@@ -256,6 +266,19 @@ CacheModule.runJob("SUB", {
 	}
 });
 
+CacheModule.runJob("SUB", {
+	channel: "station.themeUpdate",
+	cb: response => {
+		const { stationId } = response;
+		StationsModule.runJob("GET_STATION", { stationId }).then(station => {
+			IOModule.runJob("EMIT_TO_ROOM", {
+				room: `station.${stationId}`,
+				args: ["event:theme.updated", station.theme]
+			});
+		});
+	}
+});
+
 CacheModule.runJob("SUB", {
 	channel: "station.queueUpdate",
 	cb: stationId => {
@@ -663,7 +686,8 @@ export default {
 						owner: station.owner,
 						privatePlaylist: station.privatePlaylist,
 						genres: station.genres,
-						blacklistedGenres: station.blacklistedGenres
+						blacklistedGenres: station.blacklistedGenres,
+						theme: station.theme
 					};
 
 					StationsModule.userList[session.socketId] = station._id;
@@ -1468,6 +1492,79 @@ export default {
 		);
 	}),
 
+	/**
+	 * Updates a station's theme
+	 *
+	 * @param session
+	 * @param stationId - the station id
+	 * @param newTheme - the new station theme
+	 * @param cb
+	 */
+	updateTheme: isOwnerRequired(async function updateTheme(session, stationId, newTheme, cb) {
+		const stationModel = await DBModule.runJob(
+			"GET_MODEL",
+			{
+				modelName: "station"
+			},
+			this
+		);
+		async.waterfall(
+			[
+				next => {
+					StationsModule.runJob("GET_STATION", { stationId }, this)
+						.then(station => {
+							next(null, station);
+						})
+						.catch(next);
+				},
+
+				(station, next) => {
+					if (!station) return next("Station not found.");
+					if (station.theme === newTheme)
+						return next("No change in theme.");
+					return stationModel.updateOne(
+						{ _id: stationId },
+						{ $set: { theme: newTheme } },
+						{ runValidators: true },
+						next
+					);
+				},
+
+				(res, next) => {
+					StationsModule.runJob("UPDATE_STATION", { stationId }, this)
+						.then(station => {
+							next(null, station);
+						})
+						.catch(next);
+				}
+			],
+			async err => {
+				if (err) {
+					err = await UtilsModule.runJob("GET_ERROR", { error: err }, this);
+					this.log(
+						"ERROR",
+						"STATIONS_UPDATE_THEME",
+						`Updating station "${stationId}" theme to "${newTheme}" failed. "${err}"`
+					);
+					return cb({ status: "failure", message: err });
+				}
+				this.log(
+					"SUCCESS",
+					"STATIONS_UPDATE_THEME",
+					`Updated station "${stationId}" theme to "${newTheme}" successfully.`
+				);
+				CacheModule.runJob("PUB", {
+					channel: "station.themeUpdate",
+					value: { stationId }
+				});
+				return cb({
+					status: "success",
+					message: "Successfully updated the theme."
+				});
+			}
+		);
+	}),
+
 	/**
 	 * Pauses a station
 	 *

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

@@ -44,5 +44,6 @@ export default {
 	],
 	owner: { type: String },
 	privatePlaylist: { type: mongoose.Schema.Types.ObjectId },
-	partyMode: { type: Boolean }
+	partyMode: { type: Boolean },
+	theme: { type: String, enum: ["blue", "purple", "teal"], default: "blue" }
 };

+ 2 - 3
frontend/src/components/layout/MainHeader.vue

@@ -113,9 +113,8 @@ export default {
 @import "../../styles/global.scss";
 
 .night-mode {
-	.nav-left,
-	.nav-right {
-		background-color: $night-mode-bg-secondary;
+	.nav {
+		background-color: $night-mode-bg-secondary !important;
 	}
 
 	.nav-item {

+ 102 - 0
frontend/src/components/modals/EditStation.vue

@@ -387,6 +387,62 @@
 							</transition>
 						</div>
 					</div>
+					<div>
+						<label class="label">Theme</label>
+						<div
+							@mouseenter="themeDropdownActive = true"
+							@mouseleave="themeDropdownActive = false"
+							class="button-wrapper"
+						>
+							<button
+								style="text-transform: capitalize"
+								:class="editing.theme"
+								@click="updateThemeLocal(editing.theme)"
+							>
+								<i class="material-icons">palette</i>
+								{{ editing.theme }}
+							</button>
+							<transition name="slide-down">
+								<button
+									class="blue"
+									v-if="
+										themeDropdownActive &&
+											editing.theme !== 'blue'
+									"
+									@click="updateThemeLocal('blue')"
+								>
+									<i class="material-icons">palette</i>
+									Blue
+								</button>
+							</transition>
+							<transition name="slide-down">
+								<button
+									class="purple"
+									v-if="
+										themeDropdownActive &&
+											editing.theme !== 'purple'
+									"
+									@click="updateThemeLocal('purple')"
+								>
+									<i class="material-icons">palette</i>
+									Purple
+								</button>
+							</transition>
+							<transition name="slide-down">
+								<button
+									class="teal"
+									v-if="
+										themeDropdownActive &&
+											editing.theme !== 'teal'
+									"
+									@click="updateThemeLocal('teal')"
+								>
+									<i class="material-icons">palette</i>
+									Teal
+								</button>
+							</transition>
+						</div>
+					</div>
 				</div>
 			</div>
 		</template>
@@ -434,6 +490,7 @@ export default {
 			privacyDropdownActive: false,
 			modeDropdownActive: false,
 			queueLockDropdownActive: false,
+			themeDropdownActive: false,
 			genres: [
 				"Blues",
 				"Country",
@@ -617,6 +674,7 @@ export default {
 				this.editing.blacklistedGenres.toString()
 			)
 				this.updateBlacklistedGenres();
+			if (this.station.theme !== this.editing.theme) this.updateTheme();
 		},
 		updateName() {
 			const { name } = this.editing;
@@ -902,6 +960,42 @@ export default {
 				});
 			});
 		},
+		updateThemeLocal(theme) {
+			if (this.editing.theme === theme) return;
+			this.editing.theme = theme;
+			this.themeDropdownActive = false;
+		},
+		updateTheme() {
+			this.socket.emit(
+				"stations.updateTheme",
+				this.editing._id,
+				this.editing.theme,
+				res => {
+					if (res.status === "success") {
+						if (this.station)
+							this.station.theme = this.editing.theme;
+						else {
+							this.stations.forEach((station, index) => {
+								if (station._id === this.editing._id) {
+									this.stations[
+										index
+									].theme = this.editing.theme;
+									return this.editing.theme;
+								}
+
+								return false;
+							});
+						}
+						return new Toast({
+							content: res.message,
+							timeout: 4000
+						});
+					}
+
+					return new Toast({ content: res.message, timeout: 8000 });
+				}
+			);
+		},
 		deleteStation() {
 			this.socket.emit("stations.remove", this.editing._id, res => {
 				if (res.status === "success")
@@ -1244,6 +1338,14 @@ export default {
 			background-color: $yellow;
 		}
 
+		&.purple {
+			background-color: $purple;
+		}
+
+		&.teal {
+			background-color: $teal;
+		}
+
 		i {
 			font-size: 20px;
 			margin-right: 4px;

+ 1 - 1
frontend/src/pages/Station/components/AddToPlaylistDropdown.vue

@@ -229,7 +229,7 @@ export default {
 				left: 2px;
 				top: 2px;
 				border-radius: 3px;
-				background-color: $musare-blue;
+				background-color: var(--station-theme);
 				position: absolute;
 			}
 		}

+ 9 - 1
frontend/src/pages/Station/components/CurrentlyPlaying.vue

@@ -173,7 +173,7 @@ export default {
 			flex-direction: column;
 			flex-grow: 1;
 			h6 {
-				color: $musare-blue !important;
+				color: var(--station-theme) !important;
 				font-weight: bold;
 				font-size: 17px;
 			}
@@ -222,6 +222,14 @@ export default {
 					background-color: #fff;
 				}
 			}
+
+			#editsong-icon.button.is-primary {
+				background-color: var(--station-theme) !important;
+				&:hover,
+				&:focus {
+					filter: brightness(90%);
+				}
+			}
 		}
 	}
 }

+ 12 - 4
frontend/src/pages/Station/components/Sidebar/MyPlaylists.vue

@@ -190,8 +190,15 @@ export default {
 		display: flex;
 		align-items: center;
 
-		button:not(:first-of-type) {
-			margin-left: 5px;
+		button {
+			background-color: var(--station-theme) !important;
+			&:not(:first-of-type) {
+				margin-left: 5px;
+			}
+			&:hover,
+			&:focus {
+				filter: brightness(90%);
+			}
 		}
 	}
 }
@@ -215,7 +222,7 @@ export default {
 	width: 100%;
 	height: 40px;
 	border-radius: 5px;
-	background-color: rgba(3, 169, 244, 1);
+	background-color: var(--station-theme);
 	color: $white !important;
 	border: 0;
 
@@ -224,8 +231,9 @@ export default {
 		border: 0;
 	}
 
+	&:hover,
 	&:focus {
-		background-color: $primary-color;
+		filter: brightness(90%);
 	}
 }
 </style>

+ 2 - 2
frontend/src/pages/Station/components/Sidebar/Queue/QueueItem.vue

@@ -202,10 +202,10 @@ export default {
 
 	#edit-queue-item {
 		cursor: pointer;
-		color: $musare-blue;
+		color: var(--station-theme);
 		&:hover,
 		&:focus {
-			color: darken($musare-blue, 5%);
+			filter: brightness(90%);
 		}
 	}
 

+ 3 - 2
frontend/src/pages/Station/components/Sidebar/Queue/index.vue

@@ -132,11 +132,12 @@ export default {
 	}
 
 	#add-song-to-queue {
-		background-color: rgba(3, 169, 244, 1);
+		background-color: var(--station-theme) !important;
 		color: $white !important;
 
+		&:hover,
 		&:focus {
-			background-color: $primary-color;
+			filter: brightness(90%);
 		}
 	}
 }

+ 46 - 13
frontend/src/pages/Station/index.vue

@@ -1,5 +1,5 @@
 <template>
-	<div>
+	<div :style="'--station-theme: ' + theme">
 		<metadata v-if="exists && !loading" :title="`${station.displayName}`" />
 		<metadata v-else-if="!exists && !loading" :title="`Not found`" />
 
@@ -413,7 +413,8 @@ export default {
 			playbackRate: 1,
 			volumeSliderValue: 0,
 			showPlaylistDropdown: false,
-			editingSongId: ""
+			editingSongId: "",
+			theme: "rgb(2, 166, 242)"
 		};
 	},
 	computed: {
@@ -614,6 +615,17 @@ export default {
 				}
 			});
 
+			this.socket.on("event:theme.updated", theme => {
+				this.station.theme = theme;
+				if (theme === "blue") {
+					this.theme = "rgb(2, 166, 242)";
+				} else if (theme === "purple") {
+					this.theme = "rgb(143, 40, 140)";
+				} else if (theme === "teal") {
+					this.theme = "rgb(0, 209, 178)";
+				}
+			});
+
 			this.socket.on("event:newOfficialPlaylist", playlist => {
 				if (this.station.type === "official") {
 					this.updateSongsList(playlist);
@@ -696,7 +708,8 @@ export default {
 				displayName: this.station.displayName,
 				locked: this.station.locked,
 				genres: this.station.genres,
-				blacklistedGenres: this.station.blacklistedGenres
+				blacklistedGenres: this.station.blacklistedGenres,
+				theme: this.station.theme
 			});
 			this.openModal({
 				sector: "station",
@@ -1241,7 +1254,8 @@ export default {
 						type,
 						genres,
 						blacklistedGenres,
-						isFavorited
+						isFavorited,
+						theme
 					} = res.data;
 
 					this.joinStation({
@@ -1257,9 +1271,18 @@ export default {
 						type,
 						genres,
 						blacklistedGenres,
-						isFavorited
+						isFavorited,
+						theme
 					});
 
+					if (this.station.theme === "blue") {
+						this.theme = "rgb(2, 166, 242)";
+					} else if (this.station.theme === "purple") {
+						this.theme = "rgb(143, 40, 140)";
+					} else if (this.station.theme === "teal") {
+						this.theme = "rgb(0, 209, 178)";
+					}
+
 					const currentSong = res.data.currentSong
 						? res.data.currentSong
 						: {};
@@ -1502,8 +1525,13 @@ export default {
 	}
 }
 
-.experimental {
-	display: none !important;
+.nav,
+.button.is-primary {
+	background-color: var(--station-theme) !important;
+}
+.button.is-primary:hover,
+.button.is-primary:focus {
+	filter: brightness(90%);
 }
 
 #player-debug-box {
@@ -1520,7 +1548,7 @@ export default {
 	#currently-playing-container,
 	#about-station-container,
 	#control-bar-container,
-	.player-container.nothing-here-text {
+	.player-container {
 		background-color: $night-mode-bg-secondary !important;
 	}
 
@@ -1529,6 +1557,10 @@ export default {
 		border: 0 !important;
 	}
 
+	#seeker-bar-container {
+		background-color: $night-mode-bg-secondary !important;
+	}
+
 	#dropdown-toggle {
 		background-color: $dark-grey !important;
 		border: 0;
@@ -1649,6 +1681,7 @@ export default {
 			&.nothing-here-text {
 				border: 1px solid $light-grey-2;
 				border-radius: 5px;
+				margin: 10px;
 			}
 
 			#video-container {
@@ -1660,7 +1693,7 @@ export default {
 					width: 100%;
 					height: 100%;
 					bottom: calc(100% + 5px);
-					background: rgba(3, 169, 244, 0.95);
+					background: var(--station-theme);
 					display: flex;
 					align-items: center;
 					justify-content: center;
@@ -1682,7 +1715,7 @@ export default {
 				overflow: hidden;
 
 				#seeker-bar {
-					background-color: $musare-blue;
+					background-color: var(--station-theme);
 					top: 0;
 					left: 0;
 					bottom: 0;
@@ -1768,7 +1801,7 @@ export default {
 						height: 19px;
 						width: 19px;
 						border-radius: 15px;
-						background: $primary-color;
+						background: var(--station-theme);
 						cursor: pointer;
 						-webkit-appearance: none;
 						margin-top: -6.5px;
@@ -1790,7 +1823,7 @@ export default {
 						height: 19px;
 						width: 19px;
 						border-radius: 15px;
-						background: $primary-color;
+						background: var(--station-theme);
 						cursor: pointer;
 						-webkit-appearance: none;
 						margin-top: -6.5px;
@@ -1824,7 +1857,7 @@ export default {
 						height: 15px;
 						width: 15px;
 						border-radius: 15px;
-						background: $primary-color;
+						background: var(--station-theme);
 						cursor: pointer;
 						-webkit-appearance: none;
 						margin-top: 1.5px;