Browse Source

feat: Various admin pages related tweaks and fixes

Owen Diffey 3 years ago
parent
commit
2e58c7ef1a

+ 130 - 66
frontend/src/components/AdvancedTable.vue

@@ -307,7 +307,6 @@
 										>
 											<label class="switch">
 												<input
-													v-if="column.hidable"
 													type="checkbox"
 													:id="index"
 													:checked="
@@ -343,7 +342,12 @@
 				</div>
 			</div>
 			<div class="table-container">
-				<table class="table">
+				<table
+					:class="{
+						table: true,
+						'has-checkboxes': hasCheckboxes
+					}"
+				>
 					<thead>
 						<draggable
 							item-key="name"
@@ -572,7 +576,7 @@
 			</div>
 		</div>
 		<div
-			v-if="selectedRows.length > 0"
+			v-if="hasCheckboxes && selectedRows.length > 0"
 			class="bulk-popup"
 			:style="{
 				top: bulkPopup.top + 'px',
@@ -735,6 +739,12 @@ export default {
 		selectedRows() {
 			return this.data.filter(data => data.selected);
 		},
+		hasCheckboxes() {
+			return (
+				this.$slots["bulk-actions"] != null ||
+				this.$slots["bulk-actions-right"] != null
+			);
+		},
 		...mapGetters({
 			socket: "websockets/getSocket"
 		})
@@ -930,6 +940,7 @@ export default {
 			}
 		},
 		toggleColumnVisibility(column) {
+			if (!column.hidable) return false;
 			if (this.shownColumns.indexOf(column.name) !== -1) {
 				if (this.shownColumns.length <= 3)
 					return new Toast(
@@ -1197,7 +1208,10 @@ export default {
 			let noWidthCount = 0;
 			let calculatedWidth = 0;
 			this.orderedColumns.forEach(column => {
-				if (this.shownColumns.indexOf(column.name) !== -1)
+				if (
+					this.shownColumns.indexOf(column.name) !== -1 &&
+					(column.name !== "select" || this.hasCheckboxes)
+				)
 					if (
 						Number.isFinite(column.width) &&
 						!Number.isFinite(column.calculatedWidth)
@@ -1286,6 +1300,70 @@ export default {
 };
 </script>
 
+<style lang="scss">
+.table-container .table tbody td .row-options {
+	display: flex;
+	justify-content: space-between;
+
+	.icon-with-button {
+		height: 30px;
+		width: 30px;
+	}
+}
+
+.bulk-popup {
+	display: flex;
+	position: fixed;
+	flex-direction: row;
+	width: 100%;
+	max-width: 400px;
+	line-height: 36px;
+	z-index: 5;
+	border: 1px solid var(--light-grey-3);
+	border-radius: 5px;
+	box-shadow: 0 14px 28px rgba(0, 0, 0, 0.25), 0 10px 10px rgba(0, 0, 0, 0.22);
+	background-color: var(--white);
+	color: var(--dark-grey);
+	padding: 5px;
+
+	.right {
+		display: flex;
+		flex-direction: row;
+		margin-left: auto;
+	}
+
+	.drag-icon {
+		position: relative;
+		top: 6px;
+		color: var(--dark-grey);
+		cursor: move;
+	}
+
+	.bulk-actions {
+		display: flex;
+		flex-direction: row;
+		width: 100%;
+		justify-content: space-evenly;
+
+		.material-icons {
+			position: relative;
+			top: 6px;
+			margin-left: 5px;
+			cursor: pointer;
+			color: var(--primary-color);
+
+			&:hover,
+			&:focus {
+				filter: brightness(90%);
+			}
+		}
+		.delete-icon {
+			color: var(--dark-red);
+		}
+	}
+}
+</style>
+
 <style lang="scss" scoped>
 .night-mode {
 	.table-outer-container {
@@ -1432,45 +1510,60 @@ export default {
 			}
 		}
 
-		table thead tr,
-		table tbody tr {
-			th,
-			td {
-				position: relative;
-				white-space: nowrap;
-				text-overflow: ellipsis;
-				overflow: hidden;
-
-				&:first-child {
-					position: sticky;
-					left: 0;
-					background-color: var(--white);
-					z-index: 2;
+		table {
+			thead tr,
+			tbody tr {
+				th,
+				td {
+					position: relative;
+					white-space: nowrap;
+					text-overflow: ellipsis;
+					overflow: hidden;
+
+					&:first-child {
+						display: none;
+					}
+
+					.resizer {
+						height: 100%;
+						width: 5px;
+						background-color: transparent;
+						cursor: col-resize;
+						position: absolute;
+						right: 0;
+						top: 0;
+					}
 				}
 
-				.resizer {
-					height: 100%;
-					width: 5px;
-					background-color: transparent;
-					cursor: col-resize;
-					position: absolute;
-					right: 0;
-					top: 0;
+				&:nth-child(even) td:first-child {
+					background-color: #fafafa;
 				}
-			}
 
-			&:nth-child(even) td:first-child {
-				background-color: #fafafa;
+				&:hover,
+				&:focus,
+				&.highlighted {
+					th,
+					td {
+						&,
+						&:first-child {
+							background-color: var(--light-grey);
+						}
+					}
+				}
 			}
 
-			&:hover,
-			&:focus,
-			&.highlighted {
-				th,
-				td {
-					&,
-					&:first-child {
-						background-color: var(--light-grey);
+			&.has-checkboxes {
+				thead tr,
+				tbody tr {
+					th,
+					td {
+						&:first-child {
+							display: table-cell;
+							position: sticky;
+							left: 0;
+							background-color: var(--white);
+							z-index: 2;
+						}
 					}
 				}
 			}
@@ -1614,33 +1707,4 @@ export default {
 		margin: 0 !important;
 	}
 }
-
-.bulk-popup {
-	display: flex;
-	position: fixed;
-	flex-direction: row;
-	width: 100%;
-	max-width: 400px;
-	line-height: 36px;
-	z-index: 5;
-	border: 1px solid var(--light-grey-3);
-	border-radius: 5px;
-	box-shadow: 0 14px 28px rgba(0, 0, 0, 0.25), 0 10px 10px rgba(0, 0, 0, 0.22);
-	background-color: var(--white);
-	color: var(--dark-grey);
-	padding: 5px;
-
-	.right {
-		display: flex;
-		flex-direction: row;
-		margin-left: auto;
-	}
-
-	.drag-icon {
-		position: relative;
-		top: 6px;
-		color: var(--dark-grey);
-		cursor: move;
-	}
-}
 </style>

+ 10 - 1
frontend/src/components/Modal.vue

@@ -21,7 +21,12 @@
 			<section class="modal-card-body">
 				<slot name="body" />
 			</section>
-			<footer class="modal-card-foot" v-if="$slots['footer'] != null">
+			<footer
+				:class="{
+					'modal-card-foot': true,
+					blank: $slots['footer'] == null
+				}"
+			>
 				<slot name="footer" />
 			</footer>
 		</div>
@@ -230,6 +235,10 @@ export default {
 				justify-content: flex-end;
 				column-gap: 16px;
 			}
+
+			&.blank {
+				padding: 10px;
+			}
 		}
 
 		.modal-card-body {

+ 31 - 66
frontend/src/pages/Admin/tabs/Playlists.vue

@@ -12,6 +12,23 @@
 				data-action="playlists.getData"
 				name="admin-playlists"
 			>
+				<template #column-options="slotProps">
+					<div class="row-options">
+						<button
+							class="
+								button
+								is-primary
+								icon-with-button
+								material-icons
+							"
+							@click="edit(slotProps.item._id)"
+							content="Edit Playlist"
+							v-tippy
+						>
+							edit
+						</button>
+					</div>
+				</template>
 				<template #column-displayName="slotProps">
 					<span :title="slotProps.item.displayName">{{
 						slotProps.item.displayName
@@ -65,30 +82,6 @@
 						slotProps.item._id
 					}}</span>
 				</template>
-				<template #bulk-actions="slotProps">
-					<div class="playlist-bulk-actions">
-						<i
-							class="material-icons edit-playlists-icon"
-							@click.prevent="editMany(slotProps.item)"
-							content="Edit Playlists"
-							v-tippy
-						>
-							edit
-						</i>
-						<quick-confirm
-							placement="left"
-							@confirm="deleteMany(slotProps.item)"
-						>
-							<i
-								class="material-icons delete-playlists-icon"
-								content="Delete Playlists"
-								v-tippy
-							>
-								delete_forever
-							</i>
-						</quick-confirm>
-					</div>
-				</template>
 			</advanced-table>
 		</div>
 
@@ -102,12 +95,9 @@
 import { mapState, mapActions, mapGetters } from "vuex";
 import { defineAsyncComponent } from "vue";
 
-import Toast from "toasters";
-
 import AdvancedTable from "@/components/AdvancedTable.vue";
 import RunJobDropdown from "@/components/RunJobDropdown.vue";
 import UserIdToUsername from "@/components/UserIdToUsername.vue";
-import QuickConfirm from "@/components/QuickConfirm.vue";
 
 import utils from "../../../../js/utils";
 
@@ -124,8 +114,7 @@ export default {
 		),
 		AdvancedTable,
 		RunJobDropdown,
-		UserIdToUsername,
-		QuickConfirm
+		UserIdToUsername
 	},
 	data() {
 		return {
@@ -140,6 +129,16 @@ export default {
 				maxWidth: 600
 			},
 			columns: [
+				{
+					name: "options",
+					displayName: "Edit",
+					properties: ["_id"],
+					sortable: false,
+					hidable: false,
+					resizable: false,
+					minWidth: 51,
+					defaultWidth: 51
+				},
 				{
 					name: "displayName",
 					displayName: "Display Name",
@@ -326,16 +325,9 @@ export default {
 		// );
 	},
 	methods: {
-		editMany(selectedRows) {
-			if (selectedRows.length === 1) {
-				this.editPlaylist(selectedRows[0]._id);
-				this.openModal("editPlaylist");
-			} else {
-				new Toast("Bulk editing not yet implemented.");
-			}
-		},
-		deleteMany() {
-			new Toast("Bulk deleting not yet implemented.");
+		edit(playlistId) {
+			this.editPlaylist(playlistId);
+			this.openModal("editPlaylist");
 		},
 		getDateFormatted(createdAt) {
 			const date = new Date(createdAt);
@@ -358,30 +350,3 @@ export default {
 	}
 };
 </script>
-
-<style lang="scss" scoped>
-.bulk-popup {
-	.playlist-bulk-actions {
-		display: flex;
-		flex-direction: row;
-		width: 100%;
-		justify-content: space-evenly;
-
-		.material-icons {
-			position: relative;
-			top: 6px;
-			margin-left: 5px;
-			cursor: pointer;
-			color: var(--primary-color);
-
-			&:hover,
-			&:focus {
-				filter: brightness(90%);
-			}
-		}
-		.delete-playlists-icon {
-			color: var(--dark-red);
-		}
-	}
-}
-</style>

+ 39 - 28
frontend/src/pages/Admin/tabs/Songs.vue

@@ -31,6 +31,23 @@
 				data-action="songs.getData"
 				name="admin-songs"
 			>
+				<template #column-options="slotProps">
+					<div class="row-options">
+						<button
+							class="
+								button
+								is-primary
+								icon-with-button
+								material-icons
+							"
+							@click="editOne(slotProps.item)"
+							content="Edit Song"
+							v-tippy
+						>
+							edit
+						</button>
+					</div>
+				</template>
 				<template #column-thumbnailImage="slotProps">
 					<img
 						class="song-thumbnail"
@@ -97,7 +114,7 @@
 					/>
 				</template>
 				<template #bulk-actions="slotProps">
-					<div class="song-bulk-actions">
+					<div class="bulk-actions">
 						<i
 							class="material-icons edit-songs-icon"
 							@click.prevent="editMany(slotProps.item)"
@@ -151,7 +168,7 @@
 							@confirm="deleteMany(slotProps.item)"
 						>
 							<i
-								class="material-icons delete-songs-icon"
+								class="material-icons delete-icon"
 								content="Delete Songs"
 								v-tippy
 							>
@@ -289,6 +306,16 @@ export default {
 				maxWidth: 600
 			},
 			columns: [
+				{
+					name: "options",
+					displayName: "Edit",
+					properties: [],
+					sortable: false,
+					hidable: false,
+					resizable: false,
+					minWidth: 51,
+					defaultWidth: 51
+				},
 				{
 					name: "thumbnailImage",
 					displayName: "Thumb",
@@ -527,6 +554,10 @@ export default {
 		});
 	},
 	methods: {
+		editOne(song) {
+			this.editSong(song);
+			this.openModal("editSong");
+		},
 		editMany(selectedRows) {
 			if (selectedRows.length === 1) {
 				this.editSong(selectedRows[0]);
@@ -608,32 +639,12 @@ export default {
 	margin: 0 auto;
 }
 
-.bulk-popup {
-	.song-bulk-actions {
-		display: flex;
-		flex-direction: row;
-		width: 100%;
-		justify-content: space-evenly;
-
-		.material-icons {
-			position: relative;
-			top: 6px;
-			margin-left: 5px;
-			cursor: pointer;
-			color: var(--primary-color);
-
-			&:hover,
-			&:focus {
-				filter: brightness(90%);
-			}
-		}
-		.verify-songs-icon {
-			color: var(--green);
-		}
-		.unverify-songs-icon,
-		.delete-songs-icon {
-			color: var(--dark-red);
-		}
+.bulk-popup .bulk-actions {
+	.verify-songs-icon {
+		color: var(--green);
+	}
+	.unverify-songs-icon {
+		color: var(--dark-red);
 	}
 }
 </style>

+ 30 - 72
frontend/src/pages/Admin/tabs/Stations.vue

@@ -18,6 +18,23 @@
 				data-action="stations.getData"
 				name="admin-stations"
 			>
+				<template #column-options="slotProps">
+					<div class="row-options">
+						<button
+							class="
+								button
+								is-primary
+								icon-with-button
+								material-icons
+							"
+							@click="edit(slotProps.item._id)"
+							content="Manage Station"
+							v-tippy
+						>
+							settings
+						</button>
+					</div>
+				</template>
 				<template #column-_id="slotProps">
 					<span :title="slotProps.item._id">{{
 						slotProps.item._id
@@ -53,30 +70,6 @@
 						:link="true"
 					/>
 				</template>
-				<template #bulk-actions="slotProps">
-					<div class="station-bulk-actions">
-						<i
-							class="material-icons edit-stations-icon"
-							@click.prevent="editMany(slotProps.item)"
-							content="Edit Stations"
-							v-tippy
-						>
-							edit
-						</i>
-						<quick-confirm
-							placement="left"
-							@confirm="deleteMany(slotProps.item)"
-						>
-							<i
-								class="material-icons delete-stations-icon"
-								content="Delete Stations"
-								v-tippy
-							>
-								delete_forever
-							</i>
-						</quick-confirm>
-					</div>
-				</template>
 			</advanced-table>
 		</div>
 
@@ -98,10 +91,8 @@
 import { mapState, mapActions, mapGetters } from "vuex";
 import { defineAsyncComponent } from "vue";
 
-import Toast from "toasters";
 import AdvancedTable from "@/components/AdvancedTable.vue";
 import UserIdToUsername from "@/components/UserIdToUsername.vue";
-import QuickConfirm from "@/components/QuickConfirm.vue";
 import RunJobDropdown from "@/components/RunJobDropdown.vue";
 
 export default {
@@ -129,7 +120,6 @@ export default {
 		),
 		AdvancedTable,
 		UserIdToUsername,
-		QuickConfirm,
 		RunJobDropdown
 	},
 	data() {
@@ -145,6 +135,16 @@ export default {
 				maxWidth: 600
 			},
 			columns: [
+				{
+					name: "options",
+					displayName: "Edit",
+					properties: ["_id"],
+					sortable: false,
+					hidable: false,
+					resizable: false,
+					minWidth: 51,
+					defaultWidth: 51
+				},
 				{
 					name: "_id",
 					displayName: "ID",
@@ -255,53 +255,11 @@ export default {
 		// );
 	},
 	methods: {
-		editMany(selectedRows) {
-			if (selectedRows.length === 1) {
-				this.editingStationId = selectedRows[0]._id;
-				this.openModal("manageStation");
-			} else {
-				new Toast("Bulk editing not yet implemented.");
-			}
-		},
-		deleteMany(selectedRows) {
-			if (selectedRows.length === 1) {
-				this.socket.dispatch(
-					"stations.remove",
-					selectedRows[0]._id,
-					res => new Toast(res.message)
-				);
-			} else {
-				new Toast("Bulk deleting not yet implemented.");
-			}
+		edit(stationId) {
+			this.editingStationId = stationId;
+			this.openModal("manageStation");
 		},
 		...mapActions("modalVisibility", ["openModal"])
 	}
 };
 </script>
-
-<style lang="scss" scoped>
-.bulk-popup {
-	.station-bulk-actions {
-		display: flex;
-		flex-direction: row;
-		width: 100%;
-		justify-content: space-evenly;
-
-		.material-icons {
-			position: relative;
-			top: 6px;
-			margin-left: 5px;
-			cursor: pointer;
-			color: var(--primary-color);
-
-			&:hover,
-			&:focus {
-				filter: brightness(90%);
-			}
-		}
-		.delete-stations-icon {
-			color: var(--dark-red);
-		}
-	}
-}
-</style>