|
@@ -50,153 +50,193 @@
|
|
|
<h2>My Favorites</h2>
|
|
|
</div>
|
|
|
</div>
|
|
|
- <router-link
|
|
|
- v-for="(station, index) in favoriteStations"
|
|
|
- :key="index"
|
|
|
- :to="{
|
|
|
- name: 'station',
|
|
|
- params: { id: station.name }
|
|
|
- }"
|
|
|
- class="card station-card"
|
|
|
- :class="{
|
|
|
- isPrivate: station.privacy === 'private',
|
|
|
- isMine: isOwner(station)
|
|
|
- }"
|
|
|
- :style="'--primary-color: var(--' + station.theme + ')'"
|
|
|
+
|
|
|
+ <draggable
|
|
|
+ class="scrollable-list"
|
|
|
+ v-model="favoriteStations"
|
|
|
+ v-bind="dragOptions"
|
|
|
+ @start="drag = true"
|
|
|
+ @end="drag = false"
|
|
|
+ @change="changeFavoriteOrder"
|
|
|
>
|
|
|
- <song-thumbnail
|
|
|
- class="card-image"
|
|
|
- :song="station.currentSong"
|
|
|
- />
|
|
|
- <div class="card-content">
|
|
|
- <div class="media">
|
|
|
- <div class="media-left displayName">
|
|
|
- <i
|
|
|
- v-if="loggedIn && !station.isFavorited"
|
|
|
- @click.prevent="favoriteStation(station)"
|
|
|
- class="favorite material-icons"
|
|
|
- content="Favorite Station"
|
|
|
- v-tippy
|
|
|
- >star_border</i
|
|
|
- >
|
|
|
- <i
|
|
|
- v-if="loggedIn && station.isFavorited"
|
|
|
- @click.prevent="unfavoriteStation(station)"
|
|
|
- class="favorite material-icons"
|
|
|
- content="Unfavorite Station"
|
|
|
- v-tippy
|
|
|
- >star</i
|
|
|
- >
|
|
|
- <h5>{{ station.displayName }}</h5>
|
|
|
- <i
|
|
|
- v-if="station.type === 'official'"
|
|
|
- class="material-icons verified-station"
|
|
|
- content="Verified Station"
|
|
|
- v-tippy
|
|
|
- >
|
|
|
- check_circle
|
|
|
- </i>
|
|
|
+ <transition-group
|
|
|
+ type="transition"
|
|
|
+ :name="!drag ? 'draggable-list-transition' : null"
|
|
|
+ >
|
|
|
+ <router-link
|
|
|
+ v-for="(station, index) in favoriteStations"
|
|
|
+ :key="`key-${index}`"
|
|
|
+ :to="{
|
|
|
+ name: 'station',
|
|
|
+ params: { id: station.name }
|
|
|
+ }"
|
|
|
+ :class="{
|
|
|
+ card: true,
|
|
|
+ 'station-card': true,
|
|
|
+ 'item-draggable': true,
|
|
|
+ isPrivate: station.privacy === 'private',
|
|
|
+ isMine: isOwner(station)
|
|
|
+ }"
|
|
|
+ :style="
|
|
|
+ '--primary-color: var(--' + station.theme + ')'
|
|
|
+ "
|
|
|
+ >
|
|
|
+ <song-thumbnail
|
|
|
+ class="card-image"
|
|
|
+ :song="station.currentSong"
|
|
|
+ />
|
|
|
+ <div class="card-content">
|
|
|
+ <div class="media">
|
|
|
+ <div class="media-left displayName">
|
|
|
+ <i
|
|
|
+ v-if="
|
|
|
+ loggedIn && !station.isFavorited
|
|
|
+ "
|
|
|
+ @click.prevent="
|
|
|
+ favoriteStation(station)
|
|
|
+ "
|
|
|
+ class="favorite material-icons"
|
|
|
+ content="Favorite Station"
|
|
|
+ v-tippy
|
|
|
+ >star_border</i
|
|
|
+ >
|
|
|
+ <i
|
|
|
+ v-if="
|
|
|
+ loggedIn && station.isFavorited
|
|
|
+ "
|
|
|
+ @click.prevent="
|
|
|
+ unfavoriteStation(station)
|
|
|
+ "
|
|
|
+ class="favorite material-icons"
|
|
|
+ content="Unfavorite Station"
|
|
|
+ v-tippy
|
|
|
+ >star</i
|
|
|
+ >
|
|
|
+ <h5>{{ station.displayName }}</h5>
|
|
|
+ <i
|
|
|
+ v-if="station.type === 'official'"
|
|
|
+ class="material-icons verified-station"
|
|
|
+ content="Verified Station"
|
|
|
+ v-tippy
|
|
|
+ >
|
|
|
+ check_circle
|
|
|
+ </i>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="content">
|
|
|
+ {{ station.description }}
|
|
|
+ </div>
|
|
|
+ <div class="under-content">
|
|
|
+ <p class="hostedBy">
|
|
|
+ Hosted by
|
|
|
+ <span class="host">
|
|
|
+ <span
|
|
|
+ v-if="
|
|
|
+ station.type === 'official'
|
|
|
+ "
|
|
|
+ title="Musare"
|
|
|
+ >Musare</span
|
|
|
+ >
|
|
|
+ <user-id-to-username
|
|
|
+ v-else
|
|
|
+ :user-id="station.owner"
|
|
|
+ :link="true"
|
|
|
+ />
|
|
|
+ </span>
|
|
|
+ </p>
|
|
|
+ <div class="icons">
|
|
|
+ <i
|
|
|
+ v-if="
|
|
|
+ station.type === 'community' &&
|
|
|
+ isOwner(station)
|
|
|
+ "
|
|
|
+ class="homeIcon material-icons"
|
|
|
+ content="This is your station."
|
|
|
+ v-tippy
|
|
|
+ >home</i
|
|
|
+ >
|
|
|
+ <i
|
|
|
+ v-if="station.privacy === 'private'"
|
|
|
+ class="privateIcon material-icons"
|
|
|
+ content="This station is not visible to other users."
|
|
|
+ v-tippy
|
|
|
+ >lock</i
|
|
|
+ >
|
|
|
+ <i
|
|
|
+ v-if="
|
|
|
+ station.privacy === 'unlisted'
|
|
|
+ "
|
|
|
+ class="unlistedIcon material-icons"
|
|
|
+ content="Unlisted Station"
|
|
|
+ v-tippy
|
|
|
+ >link</i
|
|
|
+ >
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
</div>
|
|
|
- </div>
|
|
|
-
|
|
|
- <div class="content">
|
|
|
- {{ station.description }}
|
|
|
- </div>
|
|
|
- <div class="under-content">
|
|
|
- <p class="hostedBy">
|
|
|
- Hosted by
|
|
|
- <span class="host">
|
|
|
- <span
|
|
|
- v-if="station.type === 'official'"
|
|
|
- title="Musare"
|
|
|
- >Musare</span
|
|
|
- >
|
|
|
- <user-id-to-username
|
|
|
- v-else
|
|
|
- :user-id="station.owner"
|
|
|
- :link="true"
|
|
|
- />
|
|
|
- </span>
|
|
|
- </p>
|
|
|
- <div class="icons">
|
|
|
+ <div class="bottomBar">
|
|
|
<i
|
|
|
v-if="
|
|
|
- station.type === 'community' &&
|
|
|
- isOwner(station)
|
|
|
+ station.paused &&
|
|
|
+ station.currentSong.title
|
|
|
"
|
|
|
- class="homeIcon material-icons"
|
|
|
- content="This is your station."
|
|
|
+ class="material-icons"
|
|
|
+ content="Station Paused"
|
|
|
v-tippy
|
|
|
- >home</i
|
|
|
+ >pause</i
|
|
|
>
|
|
|
<i
|
|
|
- v-if="station.privacy === 'private'"
|
|
|
- class="privateIcon material-icons"
|
|
|
- content="This station is not visible to other users."
|
|
|
- v-tippy
|
|
|
- >lock</i
|
|
|
+ v-else-if="station.currentSong.title"
|
|
|
+ class="material-icons"
|
|
|
+ >music_note</i
|
|
|
+ >
|
|
|
+ <i v-else class="material-icons">music_off</i>
|
|
|
+ <span
|
|
|
+ v-if="station.currentSong.title"
|
|
|
+ class="songTitle"
|
|
|
+ :title="
|
|
|
+ station.currentSong.artists.length > 0
|
|
|
+ ? 'Now Playing: ' +
|
|
|
+ station.currentSong.title +
|
|
|
+ ' by ' +
|
|
|
+ station.currentSong.artists.join(
|
|
|
+ ','
|
|
|
+ )
|
|
|
+ : 'Now Playing: ' +
|
|
|
+ station.currentSong.title
|
|
|
+ "
|
|
|
+ >{{ station.currentSong.title }}
|
|
|
+ {{
|
|
|
+ station.currentSong.artists.length > 0
|
|
|
+ ? " by " +
|
|
|
+ station.currentSong.artists.join(
|
|
|
+ ","
|
|
|
+ )
|
|
|
+ : ""
|
|
|
+ }}</span
|
|
|
+ >
|
|
|
+ <span v-else class="songTitle"
|
|
|
+ >No Songs Playing</span
|
|
|
>
|
|
|
<i
|
|
|
- v-if="station.privacy === 'unlisted'"
|
|
|
- class="unlistedIcon material-icons"
|
|
|
- content="Unlisted Station"
|
|
|
+ class="material-icons stationMode"
|
|
|
+ :content="
|
|
|
+ station.partyMode
|
|
|
+ ? 'Station in Party mode'
|
|
|
+ : 'Station in Playlist mode'
|
|
|
+ "
|
|
|
v-tippy
|
|
|
- >link</i
|
|
|
+ >{{
|
|
|
+ station.partyMode
|
|
|
+ ? "emoji_people"
|
|
|
+ : "playlist_play"
|
|
|
+ }}</i
|
|
|
>
|
|
|
</div>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- <div class="bottomBar">
|
|
|
- <i
|
|
|
- v-if="station.paused && station.currentSong.title"
|
|
|
- class="material-icons"
|
|
|
- content="Station Paused"
|
|
|
- v-tippy
|
|
|
- >pause</i
|
|
|
- >
|
|
|
- <i
|
|
|
- v-else-if="station.currentSong.title"
|
|
|
- class="material-icons"
|
|
|
- >music_note</i
|
|
|
- >
|
|
|
- <i v-else class="material-icons">music_off</i>
|
|
|
- <span
|
|
|
- v-if="station.currentSong.title"
|
|
|
- class="songTitle"
|
|
|
- :title="
|
|
|
- station.currentSong.artists.length > 0
|
|
|
- ? 'Now Playing: ' +
|
|
|
- station.currentSong.title +
|
|
|
- ' by ' +
|
|
|
- station.currentSong.artists.join(',')
|
|
|
- : 'Now Playing: ' +
|
|
|
- station.currentSong.title
|
|
|
- "
|
|
|
- >{{ station.currentSong.title }}
|
|
|
- {{
|
|
|
- station.currentSong.artists.length > 0
|
|
|
- ? " by " +
|
|
|
- station.currentSong.artists.join(",")
|
|
|
- : ""
|
|
|
- }}</span
|
|
|
- >
|
|
|
- <span v-else class="songTitle">No Songs Playing</span>
|
|
|
- <i
|
|
|
- class="material-icons stationMode"
|
|
|
- :content="
|
|
|
- station.partyMode
|
|
|
- ? 'Station in Party mode'
|
|
|
- : 'Station in Playlist mode'
|
|
|
- "
|
|
|
- v-tippy
|
|
|
- >{{
|
|
|
- station.partyMode
|
|
|
- ? "emoji_people"
|
|
|
- : "playlist_play"
|
|
|
- }}</i
|
|
|
- >
|
|
|
- </div>
|
|
|
- </router-link>
|
|
|
+ </router-link>
|
|
|
+ </transition-group>
|
|
|
+ </draggable>
|
|
|
</div>
|
|
|
<div class="group bottom">
|
|
|
<div class="group-title">
|
|
@@ -418,6 +458,7 @@
|
|
|
|
|
|
<script>
|
|
|
import { mapState, mapGetters, mapActions } from "vuex";
|
|
|
+import draggable from "vuedraggable";
|
|
|
import Toast from "toasters";
|
|
|
|
|
|
import MainHeader from "@/components/layout/MainHeader.vue";
|
|
@@ -434,14 +475,18 @@ export default {
|
|
|
SongThumbnail,
|
|
|
CreateCommunityStation: () =>
|
|
|
import("@/components/modals/CreateCommunityStation.vue"),
|
|
|
- UserIdToUsername
|
|
|
+ UserIdToUsername,
|
|
|
+ draggable
|
|
|
},
|
|
|
data() {
|
|
|
return {
|
|
|
recaptcha: { key: "" },
|
|
|
stations: [],
|
|
|
+ favoriteStations: [],
|
|
|
searchQuery: "",
|
|
|
- siteName: "Musare"
|
|
|
+ siteName: "Musare",
|
|
|
+ orderOfFavoriteStations: [],
|
|
|
+ drag: false
|
|
|
};
|
|
|
},
|
|
|
computed: {
|
|
@@ -472,10 +517,18 @@ export default {
|
|
|
b.userCount - a.userCount
|
|
|
);
|
|
|
},
|
|
|
- favoriteStations() {
|
|
|
- return this.filteredStations.filter(
|
|
|
- station => station.isFavorited === true
|
|
|
- );
|
|
|
+ dragOptions() {
|
|
|
+ return {
|
|
|
+ animation: 200,
|
|
|
+ group: "favoriteStations",
|
|
|
+ disabled: false,
|
|
|
+ ghostClass: "draggable-list-ghost"
|
|
|
+ };
|
|
|
+ }
|
|
|
+ },
|
|
|
+ watch: {
|
|
|
+ orderOfFavoriteStations() {
|
|
|
+ this.calculateFavoriteStations();
|
|
|
}
|
|
|
},
|
|
|
async mounted() {
|
|
@@ -504,135 +557,139 @@ export default {
|
|
|
}
|
|
|
});
|
|
|
|
|
|
- this.socket.on("event:station.removed", response => {
|
|
|
- const { stationId } = response;
|
|
|
+ this.socket.on("event:station.removed", res => {
|
|
|
+ const { stationId } = res;
|
|
|
const station = this.stations.find(
|
|
|
station => station._id === stationId
|
|
|
);
|
|
|
+
|
|
|
if (station) {
|
|
|
const stationIndex = this.stations.indexOf(station);
|
|
|
this.stations.splice(stationIndex, 1);
|
|
|
+
|
|
|
+ if (station.isFavorited)
|
|
|
+ this.orderOfFavoriteStations.filter(
|
|
|
+ favoritedId => favoritedId !== stationId
|
|
|
+ );
|
|
|
}
|
|
|
});
|
|
|
|
|
|
this.socket.on("event:userCount.updated", (stationId, userCount) => {
|
|
|
- this.stations.forEach(s => {
|
|
|
- const station = s;
|
|
|
- if (station._id === stationId) {
|
|
|
- station.userCount = userCount;
|
|
|
- }
|
|
|
- });
|
|
|
+ const station = this.stations.find(
|
|
|
+ station => station._id === stationId
|
|
|
+ );
|
|
|
+
|
|
|
+ if (station) station.userCount = userCount;
|
|
|
});
|
|
|
|
|
|
- this.socket.on("event:station.updatePrivacy", response => {
|
|
|
- const { stationId, privacy } = response;
|
|
|
- this.stations.forEach(s => {
|
|
|
- const station = s;
|
|
|
- if (station._id === stationId) {
|
|
|
- station.privacy = privacy;
|
|
|
- }
|
|
|
- });
|
|
|
+ this.socket.on("event:station.updatePrivacy", res => {
|
|
|
+ const station = this.stations.find(
|
|
|
+ station => station._id === res.stationId
|
|
|
+ );
|
|
|
+
|
|
|
+ if (station) station.privacy = res.privacy;
|
|
|
});
|
|
|
|
|
|
- this.socket.on("event:station.updateName", response => {
|
|
|
- const { stationId, name } = response;
|
|
|
- this.stations.forEach(s => {
|
|
|
- const station = s;
|
|
|
- if (station._id === stationId) {
|
|
|
- station.name = name;
|
|
|
- }
|
|
|
- });
|
|
|
+ this.socket.on("event:station.updateName", res => {
|
|
|
+ const station = this.stations.find(
|
|
|
+ station => station._id === res.stationId
|
|
|
+ );
|
|
|
+
|
|
|
+ if (station) station.name = res.name;
|
|
|
});
|
|
|
|
|
|
- this.socket.on("event:station.updateDisplayName", response => {
|
|
|
- const { stationId, displayName } = response;
|
|
|
- this.stations.forEach(s => {
|
|
|
- const station = s;
|
|
|
- if (station._id === stationId) {
|
|
|
- station.displayName = displayName;
|
|
|
- }
|
|
|
- });
|
|
|
+ this.socket.on("event:station.updateDisplayName", res => {
|
|
|
+ const station = this.stations.find(
|
|
|
+ station => station._id === res.stationId
|
|
|
+ );
|
|
|
+
|
|
|
+ if (station) station.displayName = res.displayName;
|
|
|
});
|
|
|
|
|
|
- this.socket.on("event:station.updateDescription", response => {
|
|
|
- const { stationId, description } = response;
|
|
|
- this.stations.forEach(s => {
|
|
|
- const station = s;
|
|
|
- if (station._id === stationId) {
|
|
|
- station.description = description;
|
|
|
- }
|
|
|
- });
|
|
|
+ this.socket.on("event:station.updateDescription", res => {
|
|
|
+ const station = this.stations.find(
|
|
|
+ station => station._id === res.stationId
|
|
|
+ );
|
|
|
+
|
|
|
+ if (station) station.description = res.description;
|
|
|
});
|
|
|
|
|
|
- this.socket.on("event:station.updateTheme", response => {
|
|
|
- const { stationId, theme } = response;
|
|
|
- this.stations.forEach(s => {
|
|
|
- const station = s;
|
|
|
- if (station._id === stationId) {
|
|
|
- station.theme = theme;
|
|
|
- }
|
|
|
- });
|
|
|
+ this.socket.on("event:station.updateTheme", res => {
|
|
|
+ const station = this.stations.find(
|
|
|
+ station => station._id === res.stationId
|
|
|
+ );
|
|
|
+
|
|
|
+ if (station) station.theme = res.theme;
|
|
|
});
|
|
|
|
|
|
this.socket.on("event:station.nextSong", (stationId, song) => {
|
|
|
- let newSong = song;
|
|
|
- this.stations.forEach(s => {
|
|
|
- const station = s;
|
|
|
- if (station._id === stationId) {
|
|
|
- if (!newSong)
|
|
|
- newSong = {
|
|
|
- thumbnail: "/assets/notes-transparent.png"
|
|
|
- };
|
|
|
- station.currentSong = newSong;
|
|
|
- }
|
|
|
- });
|
|
|
+ const station = this.stations.find(
|
|
|
+ station => station._id === stationId
|
|
|
+ );
|
|
|
+
|
|
|
+ if (station) {
|
|
|
+ let newSong = song;
|
|
|
+
|
|
|
+ if (!newSong)
|
|
|
+ newSong = {
|
|
|
+ thumbnail: "/assets/notes-transparent.png"
|
|
|
+ };
|
|
|
+
|
|
|
+ station.currentSong = newSong;
|
|
|
+ }
|
|
|
});
|
|
|
|
|
|
- this.socket.on("event:station.pause", response => {
|
|
|
- const { stationId } = response;
|
|
|
- this.stations.forEach(s => {
|
|
|
- const station = s;
|
|
|
- if (station._id === stationId) {
|
|
|
- station.paused = true;
|
|
|
- }
|
|
|
- });
|
|
|
+ this.socket.on("event:station.pause", res => {
|
|
|
+ const station = this.stations.find(
|
|
|
+ station => station._id === res.stationId
|
|
|
+ );
|
|
|
+
|
|
|
+ if (station) station.paused = true;
|
|
|
});
|
|
|
|
|
|
- this.socket.on("event:station.resume", response => {
|
|
|
- const { stationId } = response;
|
|
|
- this.stations.forEach(s => {
|
|
|
- const station = s;
|
|
|
- if (station._id === stationId) {
|
|
|
- station.paused = false;
|
|
|
- }
|
|
|
- });
|
|
|
+ this.socket.on("event:station.resume", res => {
|
|
|
+ const station = this.stations.find(
|
|
|
+ station => station._id === res.stationId
|
|
|
+ );
|
|
|
+
|
|
|
+ if (station) station.paused = false;
|
|
|
});
|
|
|
|
|
|
this.socket.on("event:user.favoritedStation", stationId => {
|
|
|
- this.stations.forEach(s => {
|
|
|
- const station = s;
|
|
|
- if (station._id === stationId) {
|
|
|
- station.isFavorited = true;
|
|
|
- }
|
|
|
- });
|
|
|
+ const station = this.stations.find(
|
|
|
+ station => station._id === stationId
|
|
|
+ );
|
|
|
+
|
|
|
+ if (station) {
|
|
|
+ station.isFavorited = true;
|
|
|
+ this.orderOfFavoriteStations.push(stationId);
|
|
|
+ }
|
|
|
});
|
|
|
|
|
|
this.socket.on("event:user.unfavoritedStation", stationId => {
|
|
|
- this.stations.forEach(s => {
|
|
|
- const station = s;
|
|
|
- if (station._id === stationId) {
|
|
|
- station.isFavorited = false;
|
|
|
- }
|
|
|
- });
|
|
|
+ const station = this.stations.find(
|
|
|
+ station => station._id === stationId
|
|
|
+ );
|
|
|
+
|
|
|
+ if (station) {
|
|
|
+ station.isFavorited = false;
|
|
|
+ this.orderOfFavoriteStations = this.orderOfFavoriteStations.filter(
|
|
|
+ favoritedId => favoritedId !== stationId
|
|
|
+ );
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ this.socket.on("event:user.orderOfFavoriteStations.changed", order => {
|
|
|
+ this.orderOfFavoriteStations = order;
|
|
|
});
|
|
|
},
|
|
|
methods: {
|
|
|
init() {
|
|
|
- this.socket.dispatch("stations.index", data => {
|
|
|
+ this.socket.dispatch("stations.index", res => {
|
|
|
this.stations = [];
|
|
|
|
|
|
- if (data.status === "success")
|
|
|
- data.stations.forEach(station => {
|
|
|
+ if (res.status === "success") {
|
|
|
+ res.data.stations.forEach(station => {
|
|
|
const modifiableStation = station;
|
|
|
|
|
|
if (!modifiableStation.currentSong)
|
|
@@ -648,6 +705,9 @@ export default {
|
|
|
|
|
|
this.stations.push(modifiableStation);
|
|
|
});
|
|
|
+
|
|
|
+ this.orderOfFavoriteStations = res.data.favorited;
|
|
|
+ }
|
|
|
});
|
|
|
|
|
|
this.socket.dispatch("apis.joinRoom", "home", () => {});
|
|
@@ -680,6 +740,29 @@ export default {
|
|
|
}
|
|
|
);
|
|
|
},
|
|
|
+ calculateFavoriteStations() {
|
|
|
+ this.favoriteStations = this.filteredStations
|
|
|
+ .filter(station => station.isFavorited === true)
|
|
|
+ .sort(
|
|
|
+ (a, b) =>
|
|
|
+ this.orderOfFavoriteStations.indexOf(a._id) -
|
|
|
+ this.orderOfFavoriteStations.indexOf(b._id)
|
|
|
+ );
|
|
|
+ },
|
|
|
+ changeFavoriteOrder() {
|
|
|
+ const recalculatedOrder = [];
|
|
|
+ this.favoriteStations.forEach(station =>
|
|
|
+ recalculatedOrder.push(station._id)
|
|
|
+ );
|
|
|
+
|
|
|
+ this.socket.dispatch(
|
|
|
+ "users.updateOrderOfFavoriteStations",
|
|
|
+ recalculatedOrder,
|
|
|
+ res => {
|
|
|
+ return new Toast(res.message);
|
|
|
+ }
|
|
|
+ );
|
|
|
+ },
|
|
|
...mapActions("modalVisibility", ["openModal"]),
|
|
|
...mapActions("station", ["updateIfStationIsFavorited"])
|
|
|
}
|