Pārlūkot izejas kodu

feat(stations): Added theme functionality to EditStation and Homepage

Owen Diffey 5 gadi atpakaļ
vecāks
revīzija
bb290d1b1f

+ 22 - 22
README.md

@@ -1,14 +1,14 @@
 
 
   
   
-# MusareNode
+# MusicSite
 
 
 Staging: [![Build Status](https://travis-ci.org/odiffey/MusicSite.svg?branch=staging)](https://travis-ci.org/odiffey/MusicSite)
 Staging: [![Build Status](https://travis-ci.org/odiffey/MusicSite.svg?branch=staging)](https://travis-ci.org/odiffey/MusicSite)
 
 
 Experimental: [![Build Status](https://travis-ci.org/odiffey/MusicSite.svg?branch=experimental)](https://travis-ci.org/odiffey/MusicSite)
 Experimental: [![Build Status](https://travis-ci.org/odiffey/MusicSite.svg?branch=experimental)](https://travis-ci.org/odiffey/MusicSite)
 
 
-Built off of [Musare](https://github.com/Musare/MusareNode).
+Built off of [MusicSite](https://github.com/MusicSite/MusicSite).
 
 
-MusareNode now uses NodeJS, Express, SocketIO and VueJS - among other technologies. We have also implemented the ability to host Musare in [Docker Containers](https://www.docker.com/).
+MusicSite now uses NodeJS, Express, SocketIO and VueJS - among other technologies. We have also implemented the ability to host MusicSite in [Docker Containers](https://www.docker.com/).
 
 
 A live version of the site can be found at [music.diffey.dev](https://music.diffey.dev)
 A live version of the site can be found at [music.diffey.dev](https://music.diffey.dev)
 
 
@@ -52,9 +52,9 @@ Standard Installation:
 
 
 Once you've installed the required tools:
 Once you've installed the required tools:
 
 
-1. `git clone https://github.com/Musare/MusareNode.git`
+1. `git clone https://github.com/odiffey/MusicSiteSite.git`
 
 
-2. `cd MusareNode`
+2. `cd MusicSite`
 
 
 3. `cp backend/config/template.json backend/config/default.json`
 3. `cp backend/config/template.json backend/config/default.json`
 
 
@@ -78,7 +78,7 @@ Once you've installed the required tools:
 |`apis.discogs`|Can be obtained by setting up a [Discogs application](https://www.discogs.com/settings/developers), or you can disable it.|
 |`apis.discogs`|Can be obtained by setting up a [Discogs application](https://www.discogs.com/settings/developers), or you can disable it.|
 |`redis.url`|Should be left alone for Docker, and changed to `redis://localhost:6379/0` for non-Docker.|
 |`redis.url`|Should be left alone for Docker, and changed to `redis://localhost:6379/0` for non-Docker.|
 |`redis.password`|Should be the Redis password you either put in your `startRedis.cmd` file for Windows, or `.env` for docker.|
 |`redis.password`|Should be the Redis password you either put in your `startRedis.cmd` file for Windows, or `.env` for docker.|
-|`mongo.url`|Needs to have the proper password for the MongoDB musare user, and for non-Docker you need to replace `@musare:27017` with `@localhost:27017`.|
+|`mongo.url`|Needs to have the proper password for the MongoDB music user, and for non-Docker you need to replace `@music:27017` with `@localhost:27017`.|
 |`cookie.domain`|Should be the ip or address you use to access the site, without protocols (http/https), so for example `localhost`.|
 |`cookie.domain`|Should be the ip or address you use to access the site, without protocols (http/https), so for example `localhost`.|
 |`cookie.secure`|Should be `true` for SSL connections, and `false` for normal http connections.|
 |`cookie.secure`|Should be `true` for SSL connections, and `false` for normal http connections.|
 
 
@@ -94,7 +94,7 @@ Once you've installed the required tools:
 |`cookie.secure`|Should be `true` for SSL connections, and `false` for normal http connections.|
 |`cookie.secure`|Should be `true` for SSL connections, and `false` for normal http connections.|
 |`siteSettings.logo`|Path to the logo image, by default it is `/assets/wordmark.png`.|
 |`siteSettings.logo`|Path to the logo image, by default it is `/assets/wordmark.png`.|
 |`siteSettings.siteName`|Should be the name of the site.|
 |`siteSettings.siteName`|Should be the name of the site.|
-|`siteSettings.socialLinks`|`github`, `twitter` and `facebook` are set to the official Musare accounts by default, but can be changed.|
+|`siteSettings.socialLinks`|`github`, `twitter` and `facebook` are set to # by default, but can be changed.|
 
 
 5. Simply `cp .env.example .env` to setup your environment variables.
 5. Simply `cp .env.example .env` to setup your environment variables.
 
 
@@ -111,7 +111,7 @@ _Configuration_
 
 
 To configure docker configure the `.env` file to match your settings in `backend/config/default.json`.  
 To configure docker configure the `.env` file to match your settings in `backend/config/default.json`.  
 The configurable ports will be how you access the services on your machine, or what ports you will need to specify in your nginx files when using proxy_pass. 
 The configurable ports will be how you access the services on your machine, or what ports you will need to specify in your nginx files when using proxy_pass. 
-`COMPOSE_PROJECT_NAME` should be a unique name for this installation, especially if you have multiple instances of Musare on the same machine.
+`COMPOSE_PROJECT_NAME` should be a unique name for this installation, especially if you have multiple instances of MusicSite on the same machine.
 `FRONTEND_MODE` should be either `dev` or `prod` (self-explanatory).
 `FRONTEND_MODE` should be either `dev` or `prod` (self-explanatory).
 
 
 1. Build the backend and frontend Docker images (from the main folder)
 1. Build the backend and frontend Docker images (from the main folder)
@@ -124,7 +124,7 @@ The configurable ports will be how you access the services on your machine, or w
 
 
       In `.env` set the environment variable of `MONGO_ROOT_PASSWORD`.
       In `.env` set the environment variable of `MONGO_ROOT_PASSWORD`.
 
 
-   2. Set the password for the musare user (the one the backend will use).
+   2. Set the password for the music user (the one the backend will use).
 
 
       In `.env` set the environment variable of `MONGO_USER_USERNAME` and `MONGO_USER_PASSWORD`.
       In `.env` set the environment variable of `MONGO_USER_USERNAME` and `MONGO_USER_PASSWORD`.
 
 
@@ -143,7 +143,7 @@ The configurable ports will be how you access the services on your machine, or w
 
 
 5) You should now be able to begin development! The backend is auto reloaded when
 5) You should now be able to begin development! The backend is auto reloaded when
    you make changes and the frontend is auto compiled and live reloaded by webpack
    you make changes and the frontend is auto compiled and live reloaded by webpack
-   when you make changes. You should be able to access Musare in your local browser
+   when you make changes. You should be able to access MusicSite in your local browser
    at `http://<docker-machine-ip>:8080/` where `<docker-machine-ip>` can be found below:
    at `http://<docker-machine-ip>:8080/` where `<docker-machine-ip>` can be found below:
 
 
    - Docker for Windows / Mac: This is just `localhost`
    - Docker for Windows / Mac: This is just `localhost`
@@ -164,7 +164,7 @@ Steps 1-4 are things you only have to do once. The steps to start servers follow
 
 
 2.  Create a file called `startMongo.cmd` in the main folder with the contents:
 2.  Create a file called `startMongo.cmd` in the main folder with the contents:
 
 
-        "C:\Program Files\MongoDB\Server\3.2\bin\mongod.exe" --dbpath "D:\Programming\HTML\MusareNode\.database"
+        "C:\Program Files\MongoDB\Server\3.2\bin\mongod.exe" --dbpath "D:\Programming\HTML\MusicSite\.database"
 
 
     Make sure to adjust your paths accordingly.
     Make sure to adjust your paths accordingly.
 
 
@@ -180,13 +180,13 @@ Steps 1-4 are things you only have to do once. The steps to start servers follow
 
 
        `db.createUser({user: 'admin', pwd: 'PASSWORD_HERE', roles: [{role: 'userAdminAnyDatabase', db: 'admin'}]})`
        `db.createUser({user: 'admin', pwd: 'PASSWORD_HERE', roles: [{role: 'userAdminAnyDatabase', db: 'admin'}]})`
 
 
-    4. Connect to the Musare database
+    4. Connect to the MusicSite database
 
 
-       `use musare`
+       `use music`
 
 
-    5. Create the musare user
+    5. Create the music user
 
 
-       `db.createUser({user: 'musare', pwd: 'OTHER_PASSWORD_HERE', roles: [{role: 'readWrite', db: 'musare'}]})`
+       `db.createUser({user: 'music', pwd: 'OTHER_PASSWORD_HERE', roles: [{role: 'readWrite', db: 'music'}]})`
 
 
     6. Exit
     6. Exit
 
 
@@ -220,7 +220,7 @@ Steps 1-4 are things you only have to do once. The steps to start servers follow
 
 
 ## Extra
 ## Extra
 
 
-Below is a list of helpful tips / solutions we've collected while developing MusareNode.
+Below is a list of helpful tips / solutions we've collected while developing MusicSite.
 
 
 ### Mounting a non-standard directory in Docker Toolbox on Windows
 ### Mounting a non-standard directory in Docker Toolbox on Windows
 
 
@@ -235,7 +235,7 @@ of the following commands to give Docker Toolbox access to those files.
 
 
 1. Next we'll want to tell the machine about the folder we want to share.
 1. Next we'll want to tell the machine about the folder we want to share.
 
 
-   `"C:\Program Files\Oracle\VirtualBox\VBoxManage.exe" sharedfolder add default --name "d/Projects/MusareNode" --hostpath "D:\Projects\MusareNode" --automount`
+   `"C:\Program Files\Oracle\VirtualBox\VBoxManage.exe" sharedfolder add default --name "d/Projects/MusicSite" --hostpath "D:\Projects\MusicSite" --automount`
 
 
 1. Now start the machine back up and ssh into it
 1. Now start the machine back up and ssh into it
 
 
@@ -246,8 +246,8 @@ of the following commands to give Docker Toolbox access to those files.
    ```bash
    ```bash
    sudo tee -a /mnt/sda1/var/lib/boot2docker/profile >/dev/null <<EOF
    sudo tee -a /mnt/sda1/var/lib/boot2docker/profile >/dev/null <<EOF
 
 
-   mkdir -p /d/Projects/MusareNode
-   mount -t vboxsf -o uid=1000,gid=50 d/Projects/MusareNode /d/Projects/MusareNode
+   mkdir -p /d/Projects/MusicSite
+   mount -t vboxsf -o uid=1000,gid=50 d/Projects/MusicSite /d/Projects/MusicSite
    EOF
    EOF
    ```
    ```
 
 
@@ -265,7 +265,7 @@ This command will print various variables.
 At the bottom, it will say something similar to `@FOR /f "tokens=*" %i IN ('docker-machine env default') DO @%i`.
 At the bottom, it will say something similar to `@FOR /f "tokens=*" %i IN ('docker-machine env default') DO @%i`.
 Run this command in your shell. You will have to do this command for every shell you want to run `docker-compose` in (every session).
 Run this command in your shell. You will have to do this command for every shell you want to run `docker-compose` in (every session).
 
 
-### Running Musare locally without using Docker
+### Running MusicSite locally without using Docker
 
 
 1. Install [Redis](http://redis.io/download) and [MongoDB](https://www.mongodb.com/download-center#community)
 1. Install [Redis](http://redis.io/download) and [MongoDB](https://www.mongodb.com/download-center#community)
 
 
@@ -301,7 +301,7 @@ When setting up you will need to grant yourself the admin role, using the follow
 ```
 ```
 docker-compose exec mongo mongo admin
 docker-compose exec mongo mongo admin
 
 
-use musare
+use music
 db.auth("MUSAREDBUSER","MUSAREDBPASSWORD")
 db.auth("MUSAREDBUSER","MUSAREDBPASSWORD")
 db.users.update({username: "USERNAME"}, {$set: {role: "admin"}})
 db.users.update({username: "USERNAME"}, {$set: {role: "admin"}})
 ```
 ```
@@ -312,5 +312,5 @@ We use lerna to add an additional package to either the frontend or the backend.
 
 
 For example, this is how we would to add the `webpack-bundle-analyser` package as a dev-dependency to the frontend:
 For example, this is how we would to add the `webpack-bundle-analyser` package as a dev-dependency to the frontend:
 ```
 ```
-npx lerna add webpack-bundle-analyser --scope=musare-frontend --dev
+npx lerna add webpack-bundle-analyser --scope=music-frontend --dev
 ```
 ```

+ 34 - 3
backend/logic/actions/stations.js

@@ -359,7 +359,8 @@ module.exports = {
 					locked: station.locked,
 					locked: station.locked,
 					partyMode: station.partyMode,
 					partyMode: station.partyMode,
 					owner: station.owner,
 					owner: station.owner,
-					privatePlaylist: station.privatePlaylist
+					privatePlaylist: station.privatePlaylist,
+					theme: station.theme
 				};
 				};
 				userList[session.socketId] = station._id;
 				userList[session.socketId] = station._id;
 				next(null, data);
 				next(null, data);
@@ -879,7 +880,8 @@ module.exports = {
 							playlist,
 							playlist,
 							genres,
 							genres,
 							blacklistedGenres,
 							blacklistedGenres,
-							currentSong: stations.defaultSong
+							currentSong: stations.defaultSong,
+							theme: 'blue'
 						}, next);
 						}, next);
 					});
 					});
 				} else if (type === 'community') {
 				} else if (type === 'community') {
@@ -892,7 +894,8 @@ module.exports = {
 						privacy: 'private',
 						privacy: 'private',
 						owner: session.userId,
 						owner: session.userId,
 						queue: [],
 						queue: [],
-						currentSong: null
+						currentSong: null,
+						theme: 'blue'
 					}, next);
 					}, next);
 				}
 				}
 			}
 			}
@@ -1215,4 +1218,32 @@ module.exports = {
 			return cb({'status': 'success', 'message': 'Succesfully unfavorited station.'});
 			return cb({'status': 'success', 'message': 'Succesfully unfavorited station.'});
 		});
 		});
 	}),
 	}),
+
+	/**
+	 * Updates a station's theme
+	 *
+	 * @param session
+	 * @param stationId - the station id
+	 * @param newTheme - the new station theme
+	 * @param cb
+	 */
+	updateTheme: hooks.ownerRequired((session, stationId, newTheme, cb) => {
+		async.waterfall([
+			(next) => {
+				db.models.station.updateOne({_id: stationId}, {$set: {theme: newTheme}}, {runValidators: true}, next);
+			},
+
+			(res, next) => {
+				stations.updateStation(stationId, next);
+			}
+		], async (err) => {
+			if (err) {
+				err = await utils.getError(err);
+				logger.error("STATIONS_UPDATE_THEME", `Updating station "${stationId}" theme to "${newTheme}" failed. "${err}"`);
+				return cb({'status': 'failure', 'message': err});
+			}
+			logger.success("STATIONS_UPDATE_THEME", `Updated station "${stationId}" theme to "${newTheme}" successfully.`);
+			return cb({'status': 'success', 'message': 'Successfully updated the theme.'});
+		});
+	}),
 };
 };

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

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

+ 2 - 1
frontend/components/Admin/Stations.vue

@@ -273,7 +273,8 @@ export default {
 				privacy: station.privacy,
 				privacy: station.privacy,
 				displayName: station.displayName,
 				displayName: station.displayName,
 				genres: station.genres,
 				genres: station.genres,
-				blacklistedGenres: station.blacklistedGenres
+				blacklistedGenres: station.blacklistedGenres,
+				theme: station.theme
 			});
 			});
 			this.openModal({
 			this.openModal({
 				sector: "station",
 				sector: "station",

+ 105 - 0
frontend/components/Modals/EditStation.vue

@@ -275,6 +275,55 @@
 						</button>
 						</button>
 					</div>
 					</div>
 				</div>
 				</div>
+				<div>
+					<label class="label">Theme</label>
+					<div
+						@mouseenter="themeDropdownActive = true"
+						@mouseleave="themeDropdownActive = false"
+						class="button-wrapper"
+					>
+						<button
+							v-bind:class="{
+								blue: true,
+								current: editing.theme === 'blue'
+							}"
+							v-if="
+								themeDropdownActive || editing.theme === 'blue'
+							"
+							@click="updateThemeLocal('blue')"
+						>
+							<i class="material-icons">palette</i>
+							Blue
+						</button>
+						<button
+							v-bind:class="{
+								purple: true,
+								current: editing.theme === 'purple'
+							}"
+							v-if="
+								themeDropdownActive ||
+									editing.theme === 'purple'
+							"
+							@click="updateThemeLocal('purple')"
+						>
+							<i class="material-icons">palette</i>
+							Purple
+						</button>
+						<button
+							v-bind:class="{
+								teal: true,
+								current: editing.theme === 'teal'
+							}"
+							v-if="
+								themeDropdownActive || editing.theme === 'teal'
+							"
+							@click="updateThemeLocal('teal')"
+						>
+							<i class="material-icons">palette</i>
+							Teal
+						</button>
+					</div>
+				</div>
 			</div>
 			</div>
 		</template>
 		</template>
 		<template v-slot:footer>
 		<template v-slot:footer>
@@ -332,6 +381,7 @@ export default {
 			privacyDropdownActive: false,
 			privacyDropdownActive: false,
 			modeDropdownActive: false,
 			modeDropdownActive: false,
 			queueLockDropdownActive: false,
 			queueLockDropdownActive: false,
+			themeDropdownActive: false,
 			genres: [
 			genres: [
 				"Blues",
 				"Blues",
 				"Country",
 				"Country",
@@ -376,6 +426,7 @@ export default {
 				this.updateDescription();
 				this.updateDescription();
 			if (this.station.privacy !== this.editing.privacy)
 			if (this.station.privacy !== this.editing.privacy)
 				this.updatePrivacy();
 				this.updatePrivacy();
+			if (this.station.theme !== this.editing.theme) this.updateTheme();
 			if (
 			if (
 				this.station.type === "community" &&
 				this.station.type === "community" &&
 				this.station.partyMode !== this.editing.partyMode
 				this.station.partyMode !== this.editing.partyMode
@@ -684,6 +735,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.$parent.stations.forEach((station, index) => {
+								if (station._id === this.editing._id) {
+									this.$parent.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() {
 		deleteStation() {
 			this.socket.emit("stations.remove", this.editing._id, res => {
 			this.socket.emit("stations.remove", this.editing._id, res => {
 				if (res.status === "success")
 				if (res.status === "success")
@@ -1019,6 +1106,15 @@ export default {
 			transition: all 0.3s ease-in-out;
 			transition: all 0.3s ease-in-out;
 		}
 		}
 
 
+		&.teal {
+			&.current,
+			&:hover {
+				background-color: $teal;
+			}
+			background-color: rgba($teal, 0.7);
+			transition: all 0.3s ease-in-out;
+		}
+
 		&.blue {
 		&.blue {
 			&.current,
 			&.current,
 			&:hover {
 			&:hover {
@@ -1037,6 +1133,15 @@ export default {
 			transition: all 0.3s ease-in-out;
 			transition: all 0.3s ease-in-out;
 		}
 		}
 
 
+		&.purple {
+			&.current,
+			&:hover {
+				background-color: $purple;
+			}
+			background-color: rgba($purple, 0.7);
+			transition: all 0.3s ease-in-out;
+		}
+
 		&.yellow {
 		&.yellow {
 			&.current,
 			&.current,
 			&:hover {
 			&:hover {

+ 4 - 2
frontend/components/Station/Station.vue

@@ -999,7 +999,8 @@ export default {
 						partyMode,
 						partyMode,
 						owner,
 						owner,
 						privatePlaylist,
 						privatePlaylist,
-						type
+						type,
+						theme
 					} = res.data;
 					} = res.data;
 
 
 					this.joinStation({
 					this.joinStation({
@@ -1012,7 +1013,8 @@ export default {
 						partyMode,
 						partyMode,
 						owner,
 						owner,
 						privatePlaylist,
 						privatePlaylist,
-						type
+						type,
+						theme
 					});
 					});
 					const currentSong = res.data.currentSong
 					const currentSong = res.data.currentSong
 						? res.data.currentSong
 						? res.data.currentSong

+ 2 - 1
frontend/components/Station/StationHeader.vue

@@ -260,7 +260,8 @@ export default {
 				description: this.station.description,
 				description: this.station.description,
 				privacy: this.station.privacy,
 				privacy: this.station.privacy,
 				displayName: this.station.displayName,
 				displayName: this.station.displayName,
-				locked: this.station.locked
+				locked: this.station.locked,
+				theme: this.station.theme
 			});
 			});
 			this.openModal({
 			this.openModal({
 				sector: "station",
 				sector: "station",

+ 37 - 5
frontend/components/pages/Home.vue

@@ -29,7 +29,12 @@
 							name: 'station',
 							name: 'station',
 							params: { id: station.name }
 							params: { id: station.name }
 						}"
 						}"
-						class="stationCard"
+						:class="{
+							stationCard: true,
+							blueTheme: station.theme === 'blue',
+							purpleTheme: station.theme === 'purple',
+							tealTheme: station.theme === 'teal'
+						}"
 					>
 					>
 						<div class="topContent">
 						<div class="topContent">
 							<div class="albumArt">
 							<div class="albumArt">
@@ -90,8 +95,9 @@
 									<i
 									<i
 										v-if="station.type === 'official'"
 										v-if="station.type === 'official'"
 										class="badge material-icons"
 										class="badge material-icons"
+										title="Official Station"
 									>
 									>
-										verified_user
+										check_circle
 									</i>
 									</i>
 								</div>
 								</div>
 								<p class="description">
 								<p class="description">
@@ -462,9 +468,9 @@ html {
 				.badge {
 				.badge {
 					position: relative;
 					position: relative;
 					padding-left: 5px;
 					padding-left: 5px;
-					color: $lime;
-					top: 3px;
-					font-size: 22px;
+					color: $primary-color;
+					top: 5px;
+					font-size: 18px;
 				}
 				}
 			}
 			}
 			.description {
 			.description {
@@ -546,6 +552,32 @@ html {
 			}
 			}
 		}
 		}
 	}
 	}
+	&.purpleTheme {
+		.topContent .info {
+			.hostedBy .host,
+			.hostedBy .host a,
+			.displayName .badge {
+				color: $purple;
+			}
+		}
+		.bottomBar {
+			background: $purple;
+			box-shadow: inset 0px 2px 4px rgba(103, 12, 100, 0.7);
+		}
+	}
+	&.tealTheme {
+		.topContent .info {
+			.hostedBy .host,
+			.hostedBy .host a,
+			.displayName .badge {
+				color: $teal;
+			}
+		}
+		.bottomBar {
+			background: $teal;
+			box-shadow: inset 0px 2px 4px rgba(20, 160, 122, 0.7);
+		}
+	}
 }
 }
 .stationCard:hover {
 .stationCard:hover {
 	box-shadow: 0 2px 3px rgba(10, 10, 10, 0.3), 0 0 10px rgba(10, 10, 10, 0.3);
 	box-shadow: 0 2px 3px rgba(10, 10, 10, 0.3), 0 0 10px rgba(10, 10, 10, 0.3);