Jelajahi Sumber

Started on import album improvements

Owen Diffey 3 tahun lalu
induk
melakukan
e8048948fe

+ 1 - 1
frontend/Dockerfile

@@ -8,7 +8,7 @@ WORKDIR /opt
 ADD package.json /opt/package.json
 ADD package-lock.json /opt/package-lock.json
 
-RUN npm install -g webpack@5.44.0 webpack-cli@4.7.2
+RUN npm install -g webpack@5.38.0 webpack-cli@4.8.0
 
 RUN npm install
 

+ 1 - 1
frontend/src/components/modals/EditSong/Tabs/Reports.vue

@@ -202,9 +202,9 @@
 </template>
 
 <script>
-import ReportInfoItem from "@/components/ReportInfoItem.vue";
 import { mapState, mapGetters, mapActions } from "vuex";
 import Toast from "toasters";
+import ReportInfoItem from "@/components/ReportInfoItem.vue";
 
 export default {
 	components: { ReportInfoItem },

+ 9 - 9
frontend/src/components/modals/EditSong/index.vue

@@ -892,16 +892,18 @@ export default {
 				this.song._id,
 				res => {
 					if (res.status === "success") {
-						const { song } = res.data;
-						// this.song = { ...song };
-						// if (this.song.discogs === undefined)
-						// 	this.song.discogs = null;
+						let { song } = res.data;
+
+						if (this.song.prefill)
+							song = Object.assign(song, this.song.prefill);
+
 						if (this.song.discogs)
-							this.editSong({
+							song = {
 								...song,
 								discogs: this.song.discogs
-							});
-						else this.editSong(song);
+							};
+
+						this.editSong(song);
 
 						this.songDataLoaded = true;
 
@@ -910,8 +912,6 @@ export default {
 							`edit-song.${this.song._id}`
 						);
 
-						// this.edit(res.data.song);
-
 						this.interval = setInterval(() => {
 							if (
 								this.song.duration !== -1 &&

+ 490 - 335
frontend/src/components/modals/ImportAlbum.vue

@@ -2,186 +2,232 @@
 	<div>
 		<modal title="Import Album" class="import-album-modal">
 			<template #body>
-				<div class="search-discogs-album">
-					<p class="control is-expanded">
-						<label class="label">Search query</label>
-						<input
-							class="input"
-							type="text"
-							ref="discogs-input"
-							v-model="discogsQuery"
-							@keyup.enter="searchDiscogsForPage(1)"
-							@change="onDiscogsQueryChange"
-							v-focus
-						/>
-					</p>
-					<button
-						class="button is-fullwidth is-info"
-						@click="searchDiscogsForPage(1)"
-					>
-						Search
-					</button>
-					<button
-						class="button is-fullwidth is-danger"
-						@click="clearDiscogsResults()"
-					>
-						Clear
-					</button>
-					<label class="label" v-if="discogs.apiResults.length > 0"
-						>API results</label
-					>
+				<div class="tabs-container discogs-container">
+					<div class="tab-selection">
+						<button
+							class="button is-default"
+							:class="{ selected: discogsTab === 'search' }"
+							ref="discogs-search-tab"
+							@click="showDiscogsTab('search')"
+						>
+							Search
+						</button>
+						<button
+							v-if="discogsAlbum && discogsAlbum.album"
+							class="button is-default"
+							:class="{ selected: discogsTab === 'selected' }"
+							ref="discogs-selected-tab"
+							@click="showDiscogsTab('selected')"
+						>
+							Selected
+						</button>
+						<button
+							v-else
+							class="button is-default"
+							content="No album selected"
+							v-tippy="{ theme: 'info' }"
+						>
+							Selected
+						</button>
+					</div>
 					<div
-						class="api-results-container"
-						v-if="discogs.apiResults.length > 0"
+						class="tab search-discogs-album"
+						v-show="discogsTab === 'search'"
 					>
+						<p class="control is-expanded">
+							<label class="label">Search query</label>
+							<input
+								class="input"
+								type="text"
+								ref="discogs-input"
+								v-model="discogsQuery"
+								@keyup.enter="searchDiscogsForPage(1)"
+								@change="onDiscogsQueryChange"
+								v-focus
+							/>
+						</p>
+						<button
+							class="button is-fullwidth is-info"
+							@click="searchDiscogsForPage(1)"
+						>
+							Search
+						</button>
+						<button
+							class="button is-fullwidth is-danger"
+							@click="clearDiscogsResults()"
+						>
+							Clear
+						</button>
+						<label
+							class="label"
+							v-if="discogs.apiResults.length > 0"
+							>API results</label
+						>
 						<div
-							class="api-result"
-							v-for="(result, index) in discogs.apiResults"
-							:key="result.album.id"
-							tabindex="0"
-							@keydown.space.prevent
-							@keyup.enter="toggleAPIResult(index)"
+							class="api-results-container"
+							v-if="discogs.apiResults.length > 0"
 						>
-							<div class="top-container">
-								<img :src="result.album.albumArt" />
-								<div class="right-container">
-									<p class="album-title">
-										{{ result.album.title }}
-									</p>
-									<div class="bottom-row">
-										<img
-											src="/assets/arrow_up.svg"
-											v-if="result.expanded"
-											@click="toggleAPIResult(index)"
-										/>
-										<img
-											src="/assets/arrow_down.svg"
-											v-if="!result.expanded"
-											@click="toggleAPIResult(index)"
-										/>
-										<p class="type-year">
-											<span>{{ result.album.type }}</span>
-											•
-											<span>{{ result.album.year }}</span>
+							<div
+								class="api-result"
+								v-for="(result, index) in discogs.apiResults"
+								:key="result.album.id"
+								tabindex="0"
+								@keydown.space.prevent
+								@keyup.enter="toggleAPIResult(index)"
+							>
+								<div class="top-container">
+									<img :src="result.album.albumArt" />
+									<div class="right-container">
+										<p class="album-title">
+											{{ result.album.title }}
 										</p>
+										<div class="bottom-row">
+											<img
+												src="/assets/arrow_up.svg"
+												v-if="result.expanded"
+												@click="toggleAPIResult(index)"
+											/>
+											<img
+												src="/assets/arrow_down.svg"
+												v-if="!result.expanded"
+												@click="toggleAPIResult(index)"
+											/>
+											<p class="type-year">
+												<span>{{
+													result.album.type
+												}}</span>
+												•
+												<span>{{
+													result.album.year
+												}}</span>
+											</p>
+										</div>
 									</div>
 								</div>
-							</div>
-							<div
-								class="bottom-container"
-								v-if="result.expanded"
-							>
-								<p class="bottom-container-field">
-									Artists:
-									<span>{{
-										result.album.artists.join(", ")
-									}}</span>
-								</p>
-								<p class="bottom-container-field">
-									Genres:
-									<span>{{
-										result.album.genres.join(", ")
-									}}</span>
-								</p>
-								<p class="bottom-container-field">
-									Data quality:
-									<span>{{ result.dataQuality }}</span>
-								</p>
-								<button
-									class="button is-primary"
-									@click="selectAlbum(result)"
+								<div
+									class="bottom-container"
+									v-if="result.expanded"
 								>
-									Import album
-								</button>
-								<div class="tracks">
-									<div
-										class="track"
-										v-for="track in result.tracks"
-										:key="`${track.position}-${track.title}`"
+									<p class="bottom-container-field">
+										Artists:
+										<span>{{
+											result.album.artists.join(", ")
+										}}</span>
+									</p>
+									<p class="bottom-container-field">
+										Genres:
+										<span>{{
+											result.album.genres.join(", ")
+										}}</span>
+									</p>
+									<p class="bottom-container-field">
+										Data quality:
+										<span>{{ result.dataQuality }}</span>
+									</p>
+									<button
+										class="button is-primary"
+										@click="selectAlbum(result)"
 									>
-										<span>{{ track.position }}.</span>
-										<p>{{ track.title }}</p>
+										Import album
+									</button>
+									<div class="tracks">
+										<div
+											class="track"
+											v-for="track in result.tracks"
+											:key="`${track.position}-${track.title}`"
+										>
+											<span>{{ track.position }}.</span>
+											<p>{{ track.title }}</p>
+										</div>
 									</div>
 								</div>
 							</div>
 						</div>
+						<button
+							v-if="
+								discogs.apiResults.length > 0 &&
+								!discogs.disableLoadMore &&
+								discogs.page < discogs.pages
+							"
+							class="
+								button
+								is-fullwidth is-info
+								discogs-load-more
+							"
+							@click="loadNextDiscogsPage()"
+						>
+							Load more...
+						</button>
 					</div>
-					<button
-						v-if="
-							discogs.apiResults.length > 0 &&
-							!discogs.disableLoadMore &&
-							discogs.page < discogs.pages
-						"
-						class="button is-fullwidth is-info discogs-load-more"
-						@click="loadNextDiscogsPage()"
+					<div
+						v-if="discogsAlbum && discogsAlbum.album"
+						class="tab discogs-album"
+						v-show="discogsTab === 'selected'"
 					>
-						Load more...
-					</button>
-				</div>
-				<div
-					class="discogs-album"
-					v-if="discogsAlbum && discogsAlbum.album"
-				>
-					<div class="top-container">
-						<img :src="discogsAlbum.album.albumArt" />
-						<div class="right-container">
-							<p class="album-title">
-								{{ discogsAlbum.album.title }}
-							</p>
-							<div class="bottom-row">
-								<img
-									src="/assets/arrow_up.svg"
-									v-if="discogsAlbum.expanded"
-									@click="toggleDiscogsAlbum()"
-								/>
-								<img
-									src="/assets/arrow_down.svg"
-									v-if="!discogsAlbum.expanded"
-									@click="toggleDiscogsAlbum()"
-								/>
-								<p class="type-year">
-									<span>{{ discogsAlbum.album.type }}</span>
-									•
-									<span>{{ discogsAlbum.album.year }}</span>
+						<div class="top-container">
+							<img :src="discogsAlbum.album.albumArt" />
+							<div class="right-container">
+								<p class="album-title">
+									{{ discogsAlbum.album.title }}
 								</p>
+								<div class="bottom-row">
+									<img
+										src="/assets/arrow_up.svg"
+										v-if="discogsAlbum.expanded"
+										@click="toggleDiscogsAlbum()"
+									/>
+									<img
+										src="/assets/arrow_down.svg"
+										v-if="!discogsAlbum.expanded"
+										@click="toggleDiscogsAlbum()"
+									/>
+									<p class="type-year">
+										<span>{{
+											discogsAlbum.album.type
+										}}</span>
+										•
+										<span>{{
+											discogsAlbum.album.year
+										}}</span>
+									</p>
+								</div>
 							</div>
 						</div>
-					</div>
-					<div class="bottom-container" v-if="discogsAlbum.expanded">
-						<p class="bottom-container-field">
-							Artists:
-							<span>{{
-								discogsAlbum.album.artists.join(", ")
-							}}</span>
-						</p>
-						<p class="bottom-container-field">
-							Genres:
-							<span>{{
-								discogsAlbum.album.genres.join(", ")
-							}}</span>
-						</p>
-						<p class="bottom-container-field">
-							Data quality:
-							<span>{{ discogsAlbum.dataQuality }}</span>
-						</p>
-						<div class="tracks">
-							<div
-								class="track"
-								tabindex="0"
-								v-for="track in discogsAlbum.tracks"
-								:key="`${track.position}-${track.title}`"
-							>
-								<span>{{ track.position }}.</span>
-								<p>{{ track.title }}</p>
+						<div
+							class="bottom-container"
+							v-if="discogsAlbum.expanded"
+						>
+							<p class="bottom-container-field">
+								Artists:
+								<span>{{
+									discogsAlbum.album.artists.join(", ")
+								}}</span>
+							</p>
+							<p class="bottom-container-field">
+								Genres:
+								<span>{{
+									discogsAlbum.album.genres.join(", ")
+								}}</span>
+							</p>
+							<p class="bottom-container-field">
+								Data quality:
+								<span>{{ discogsAlbum.dataQuality }}</span>
+							</p>
+							<div class="tracks">
+								<div
+									class="track"
+									tabindex="0"
+									v-for="track in discogsAlbum.tracks"
+									:key="`${track.position}-${track.title}`"
+								>
+									<span>{{ track.position }}.</span>
+									<p>{{ track.title }}</p>
+								</div>
 							</div>
 						</div>
 					</div>
 				</div>
-				<div class="break"></div>
-				<div
-					class="import-youtube-playlist"
-					v-if="discogsAlbum && discogsAlbum.album"
-				>
+				<div class="import-youtube-playlist">
 					<p class="control is-expanded">
 						<input
 							class="input"
@@ -262,6 +308,16 @@
 				<button class="button is-primary" @click="editSongs()">
 					Edit songs
 				</button>
+				<button
+					:class="{
+						button: true,
+						'is-success': prefillDiscogs,
+						'is-danger': !prefillDiscogs
+					}"
+					@click="togglePrefillDiscogs()"
+				>
+					Prefill Discogs
+				</button>
 			</template>
 		</modal>
 	</div>
@@ -285,7 +341,6 @@ export default {
 	},
 	data() {
 		return {
-			stuff: false,
 			isImportingPlaylist: false,
 			trackSongs: [],
 			songsToEdit: [],
@@ -317,8 +372,10 @@ export default {
 			}
 		},
 		...mapState("modals/importAlbum", {
+			discogsTab: state => state.discogsTab,
 			discogsAlbum: state => state.discogsAlbum,
-			editingSongs: state => state.editingSongs
+			editingSongs: state => state.editingSongs,
+			prefillDiscogs: state => state.prefillDiscogs
 		}),
 		...mapState("modalVisibility", {
 			modals: state => state.modals
@@ -337,6 +394,7 @@ export default {
 	beforeUnmount() {
 		this.selectDiscogsAlbum({});
 		this.setPlaylistSongs([]);
+		this.showDiscogsTab("search");
 	},
 	methods: {
 		editSongs() {
@@ -363,11 +421,24 @@ export default {
 		editNextSong() {
 			if (this.editingSongs) {
 				setTimeout(() => {
-					this.editSong({
+					const song = {
 						_id: this.songsToEdit[this.currentEditSongIndex].songId,
 						discogs:
 							this.songsToEdit[this.currentEditSongIndex].discogs
-					});
+					};
+					if (song.discogs && this.prefillDiscogs)
+						song.prefill = {
+							title: song.discogs.track.title,
+							thumbnail: song.discogs.album.albumArt,
+							genres: JSON.parse(
+								JSON.stringify(song.discogs.album.genres)
+							),
+							artists: JSON.parse(
+								JSON.stringify(song.discogs.album.artists)
+							)
+						};
+					console.log(song);
+					this.editSong(song);
 					this.currentEditSongIndex += 1;
 					this.openModal("editSong");
 				}, 500);
@@ -417,8 +488,12 @@ export default {
 					const songsAlreadyVerified =
 						res.songs.length - songs.length;
 					this.setPlaylistSongs(songs);
-					this.trackSongs = this.discogsAlbum.tracks.map(() => []);
-					this.tryToAutoMove();
+					if (this.discogsAlbum.tracks) {
+						this.trackSongs = this.discogsAlbum.tracks.map(
+							() => []
+						);
+						this.tryToAutoMove();
+					}
 					if (songsAlreadyVerified > 0)
 						new Toast(
 							`${songsAlreadyVerified} songs were already verified, skipping those.`
@@ -459,7 +534,10 @@ export default {
 		},
 		selectAlbum(result) {
 			this.selectDiscogsAlbum(result);
-			this.clearDiscogsResults();
+			this.trackSongs = this.discogsAlbum.tracks.map(() => []);
+			if (this.playlistSongs.length > 0) this.tryToAutoMove();
+			// this.clearDiscogsResults();
+			this.showDiscogsTab("selected");
 		},
 		toggleAPIResult(index) {
 			const apiResult = this.discogs.apiResults[index];
@@ -552,13 +630,23 @@ export default {
 			this.discogs.apiResults = [];
 			this.discogs.disableLoadMore = false;
 		},
+		...mapActions({
+			showDiscogsTab(dispatch, payload) {
+				if (this.$refs[`discogs-${payload}-tab`])
+					this.$refs[`discogs-${payload}-tab`].scrollIntoView({
+						block: "nearest"
+					});
+				return dispatch("modals/importAlbum/showDiscogsTab", payload);
+			}
+		}),
 		...mapActions("modals/importAlbum", [
 			"toggleDiscogsAlbum",
 			"setPlaylistSongs",
 			"updatePlaylistSongs",
 			"selectDiscogsAlbum",
 			"updateEditingSongs",
-			"resetPlaylistSongs"
+			"resetPlaylistSongs",
+			"togglePrefillDiscogs"
 		]),
 		...mapActions("modals/editSong", ["editSong"]),
 		...mapActions("modalVisibility", ["closeModal", "openModal"])
@@ -571,8 +659,18 @@ export default {
 	.search-discogs-album,
 	.discogs-album,
 	.import-youtube-playlist,
-	.track-boxes {
+	.track-boxes,
+	#tabs-container {
 		background-color: var(--dark-grey-3) !important;
+		border: 0 !important;
+		.tab {
+			border: 0 !important;
+		}
+	}
+
+	#tabs-container #tab-selection .button {
+		background: var(--dark-grey) !important;
+		color: var(--white) !important;
 	}
 
 	.api-result {
@@ -621,6 +719,9 @@ export default {
 		.modal-card-foot {
 			.button {
 				margin: 0;
+				&:not(:first-of-type) {
+					margin-left: 5px;
+				}
 			}
 
 			div div {
@@ -645,210 +746,146 @@ export default {
 	margin-bottom: 16px;
 }
 
-.search-discogs-album {
-	width: 376px;
-	background-color: var(--light-grey);
-	border: 1px rgba(163, 224, 255, 0.75) solid;
-	border-radius: 5px;
-	padding: 16px;
-	overflow: auto;
+.tabs-container {
+	max-width: 376px;
 	height: 100%;
+	// overflow: auto;
+	display: flex;
+	flex-direction: column;
+	flex-grow: 1;
 
-	> label {
-		margin-top: 12px;
-	}
-
-	.top-container {
+	.tab-selection {
 		display: flex;
-
-		img {
-			height: 85px;
-			width: 85px;
-		}
-
-		.right-container {
-			padding: 8px;
-			display: flex;
-			flex-direction: column;
-			flex: 1;
-
-			.album-title {
-				flex: 1;
-				font-weight: 600;
-			}
-
-			.bottom-row {
-				display: flex;
-				flex-flow: row;
-				line-height: 15px;
-
-				img {
-					height: 15px;
-					align-self: end;
-					flex: 1;
-					user-select: none;
-					-moz-user-select: none;
-					-ms-user-select: none;
-					-webkit-user-select: none;
-					cursor: pointer;
-				}
-
-				p {
-					text-align: right;
-				}
-
-				.type-year {
-					font-size: 13px;
-					align-self: end;
-				}
+		overflow-x: auto;
+
+		.button {
+			border-radius: 5px 5px 0 0;
+			border: 0;
+			text-transform: uppercase;
+			font-size: 14px;
+			color: var(--dark-grey-3);
+			background-color: var(--light-grey-2);
+			flex-grow: 1;
+			height: 32px;
+
+			&:not(:first-of-type) {
+				margin-left: 5px;
 			}
 		}
-	}
 
-	.bottom-container {
-		padding: 12px;
-
-		.bottom-container-field {
-			line-height: 16px;
-			margin-bottom: 8px;
+		.selected {
+			background-color: var(--primary-color) !important;
+			color: var(--white) !important;
 			font-weight: 600;
-
-			span {
-				font-weight: 400;
-			}
-		}
-
-		.bottom-container-field:last-of-type {
-			margin-bottom: 8px;
 		}
 	}
-
-	.api-result {
-		background-color: var(--white);
-		border: 0.5px solid var(--primary-color);
-		border-radius: 5px;
-		margin-bottom: 16px;
-	}
-
-	button {
-		&:focus,
-		&:hover {
-			filter: contrast(0.75);
-		}
+	.tab {
+		border: 1px solid var(--light-grey-3);
+		border-radius: 0 0 3px 3px;
+		padding: 15px;
+		height: calc(100% - 32px);
+		overflow: auto;
 	}
+}
 
-	.tracks {
-		margin-top: 12px;
+.tabs-container.discogs-container {
+	--primary-color: var(--purple);
 
-		.track:first-child {
-			margin-top: 0;
-			border-radius: 3px 3px 0 0;
-		}
+	.search-discogs-album {
+		// width: 376px;
+		background-color: var(--light-grey);
+		border: 1px rgba(143, 40, 140, 0.75) solid;
+		// border: 1px rgba(163, 224, 255, 0.75) solid;
+		// border-radius: 5px;
+		// padding: 16px;
+		// overflow: auto;
+		// height: 100%;
 
-		.track:last-child {
-			border-radius: 0 0 3px 3px;
+		> label {
+			margin-top: 12px;
 		}
 
-		.track {
-			border: 0.5px solid var(--black);
-			margin-top: -1px;
-			line-height: 16px;
+		.top-container {
 			display: flex;
 
-			span {
-				font-weight: 600;
-				display: inline-block;
-				margin-top: 7px;
-				margin-bottom: 7px;
-				margin-left: 7px;
+			img {
+				height: 85px;
+				width: 85px;
 			}
 
-			p {
-				display: inline-block;
-				margin: 7px;
+			.right-container {
+				padding: 8px;
+				display: flex;
+				flex-direction: column;
 				flex: 1;
-			}
-		}
-	}
 
-	.discogs-load-more {
-		margin-bottom: 8px;
-	}
-}
+				.album-title {
+					flex: 1;
+					font-weight: 600;
+				}
 
-.discogs-album {
-	width: 376px;
-	background-color: var(--light-grey);
-	border: 1px rgba(163, 224, 255, 0.75) solid;
-	border-radius: 5px;
-	padding: 16px;
-	overflow: auto;
-	height: 100%;
+				.bottom-row {
+					display: flex;
+					flex-flow: row;
+					line-height: 15px;
+
+					img {
+						height: 15px;
+						align-self: end;
+						flex: 1;
+						user-select: none;
+						-moz-user-select: none;
+						-ms-user-select: none;
+						-webkit-user-select: none;
+						cursor: pointer;
+					}
 
-	.top-container {
-		display: flex;
+					p {
+						text-align: right;
+					}
 
-		img {
-			height: 85px;
-			width: 85px;
+					.type-year {
+						font-size: 13px;
+						align-self: end;
+					}
+				}
+			}
 		}
 
-		.right-container {
-			padding: 8px;
-			display: flex;
-			flex-direction: column;
-			flex: 1;
+		.bottom-container {
+			padding: 12px;
 
-			.album-title {
-				flex: 1;
+			.bottom-container-field {
+				line-height: 16px;
+				margin-bottom: 8px;
 				font-weight: 600;
-			}
-
-			.bottom-row {
-				display: flex;
-				flex-flow: row;
-				line-height: 15px;
 
-				img {
-					height: 15px;
-					align-self: end;
-					flex: 1;
-					user-select: none;
-					-moz-user-select: none;
-					-ms-user-select: none;
-					-webkit-user-select: none;
-					cursor: pointer;
-				}
-
-				p {
-					text-align: right;
+				span {
+					font-weight: 400;
 				}
+			}
 
-				.type-year {
-					font-size: 13px;
-					align-self: end;
-				}
+			.bottom-container-field:last-of-type {
+				margin-bottom: 8px;
 			}
 		}
-	}
 
-	.bottom-container {
-		padding: 12px;
+		.api-result {
+			background-color: var(--white);
+			border: 0.5px solid var(--primary-color);
+			border-radius: 5px;
+			margin-bottom: 16px;
+		}
 
-		.bottom-container-field {
-			line-height: 16px;
-			margin-bottom: 8px;
-			font-weight: 600;
+		button {
+			margin: 5px 0;
 
-			span {
-				font-weight: 400;
+			&:focus,
+			&:hover {
+				filter: contrast(0.75);
 			}
 		}
 
-		.bottom-container-field:last-of-type {
-			margin-bottom: 0;
-		}
-
 		.tracks {
 			margin-top: 12px;
 
@@ -881,10 +918,124 @@ export default {
 					flex: 1;
 				}
 			}
+		}
+
+		.discogs-load-more {
+			margin-bottom: 8px;
+		}
+	}
+
+	.discogs-album {
+		// width: 376px;
+		background-color: var(--light-grey);
+		border: 1px rgba(143, 40, 140, 0.75) solid;
+		// border: 1px rgba(163, 224, 255, 0.75) solid;
+		// border-radius: 5px;
+		// padding: 16px;
+		// overflow: auto;
+		// height: 100%;
+
+		.top-container {
+			display: flex;
+
+			img {
+				height: 85px;
+				width: 85px;
+			}
+
+			.right-container {
+				padding: 8px;
+				display: flex;
+				flex-direction: column;
+				flex: 1;
+
+				.album-title {
+					flex: 1;
+					font-weight: 600;
+				}
+
+				.bottom-row {
+					display: flex;
+					flex-flow: row;
+					line-height: 15px;
+
+					img {
+						height: 15px;
+						align-self: end;
+						flex: 1;
+						user-select: none;
+						-moz-user-select: none;
+						-ms-user-select: none;
+						-webkit-user-select: none;
+						cursor: pointer;
+					}
+
+					p {
+						text-align: right;
+					}
+
+					.type-year {
+						font-size: 13px;
+						align-self: end;
+					}
+				}
+			}
+		}
+
+		.bottom-container {
+			padding: 12px;
+
+			.bottom-container-field {
+				line-height: 16px;
+				margin-bottom: 8px;
+				font-weight: 600;
+
+				span {
+					font-weight: 400;
+				}
+			}
+
+			.bottom-container-field:last-of-type {
+				margin-bottom: 0;
+			}
 
-			.track:hover,
-			.track:focus {
-				background-color: var(--light-grey);
+			.tracks {
+				margin-top: 12px;
+
+				.track:first-child {
+					margin-top: 0;
+					border-radius: 3px 3px 0 0;
+				}
+
+				.track:last-child {
+					border-radius: 0 0 3px 3px;
+				}
+
+				.track {
+					border: 0.5px solid var(--black);
+					margin-top: -1px;
+					line-height: 16px;
+					display: flex;
+
+					span {
+						font-weight: 600;
+						display: inline-block;
+						margin-top: 7px;
+						margin-bottom: 7px;
+						margin-left: 7px;
+					}
+
+					p {
+						display: inline-block;
+						margin: 7px;
+						flex: 1;
+					}
+				}
+
+				.track:hover,
+				.track:focus {
+					background-color: var(--light-grey);
+				}
 			}
 		}
 	}
@@ -898,6 +1049,10 @@ export default {
 	padding: 16px;
 	overflow: auto;
 	height: 100%;
+
+	button {
+		margin: 5px 0;
+	}
 }
 
 .track-boxes {

+ 1 - 1
frontend/src/components/modals/ViewPunishment.vue

@@ -11,9 +11,9 @@
 <script>
 import { mapState, mapGetters, mapActions } from "vuex";
 import { format, formatDistance, parseISO } from "date-fns";
+import Toast from "toasters";
 import ws from "@/ws";
 
-import Toast from "toasters";
 import Modal from "../Modal.vue";
 import PunishmentItem from "../PunishmentItem.vue";
 

+ 1 - 1
frontend/src/pages/Admin/tabs/Reports.vue

@@ -85,11 +85,11 @@
 </template>
 
 <script>
-import ReportInfoItem from "@/components/ReportInfoItem.vue";
 import { mapState, mapActions, mapGetters } from "vuex";
 import { defineAsyncComponent } from "vue";
 
 import Toast from "toasters";
+import ReportInfoItem from "@/components/ReportInfoItem.vue";
 import ws from "@/ws";
 
 export default {

+ 12 - 2
frontend/src/store/modules/modals/importAlbum.js

@@ -16,10 +16,13 @@ export default {
 		},
 		originalPlaylistSongs: [],
 		playlistSongs: [],
-		editingSongs: false
+		editingSongs: false,
+		discogsTab: "search",
+		prefillDiscogs: false
 	},
 	getters: {},
 	actions: {
+		showDiscogsTab: ({ commit }, tab) => commit("showDiscogsTab", tab),
 		selectDiscogsAlbum: ({ commit }, discogsAlbum) =>
 			commit("selectDiscogsAlbum", discogsAlbum),
 		toggleDiscogsAlbum: ({ commit }) => {
@@ -31,9 +34,13 @@ export default {
 			commit("updatePlaylistSongs", playlistSongs),
 		updateEditingSongs: ({ commit }, editingSongs) =>
 			commit("updateEditingSongs", editingSongs),
-		resetPlaylistSongs: ({ commit }) => commit("resetPlaylistSongs")
+		resetPlaylistSongs: ({ commit }) => commit("resetPlaylistSongs"),
+		togglePrefillDiscogs: ({ commit }) => commit("togglePrefillDiscogs")
 	},
 	mutations: {
+		showDiscogsTab(state, tab) {
+			state.discogsTab = tab;
+		},
 		selectDiscogsAlbum(state, discogsAlbum) {
 			state.discogsAlbum = JSON.parse(JSON.stringify(discogsAlbum));
 			if (state.discogsAlbum && state.discogsAlbum.tracks) {
@@ -62,6 +69,9 @@ export default {
 			state.playlistSongs = JSON.parse(
 				JSON.stringify(state.originalPlaylistSongs)
 			);
+		},
+		togglePrefillDiscogs(state) {
+			state.prefillDiscogs = !state.prefillDiscogs;
 		}
 	}
 };