소스 검색

feat: AdvancedTable improvements

Owen Diffey 3 년 전
부모
커밋
745b063db1
4개의 변경된 파일228개의 추가작업 그리고 109개의 파일을 삭제
  1. 18 14
      backend/logic/actions/songs.js
  2. 10 10
      backend/logic/songs.js
  3. 193 84
      frontend/src/components/AdvancedTable.vue
  4. 7 1
      frontend/src/pages/Admin/tabs/Test.vue

+ 18 - 14
backend/logic/actions/songs.js

@@ -212,23 +212,27 @@ export default {
 	 * @param filter - the filter object
 	 * @param cb
 	 */
-	 getData: isAdminRequired(async function getSet(session, page, pageSize, properties, sort, filter, cb) {
+	getData: isAdminRequired(async function getSet(session, page, pageSize, properties, sort, filter, cb) {
 		async.waterfall(
 			[
 				next => {
-					SongsModule.runJob("GET_DATA", {
-						page,
-						pageSize,
-						properties,
-						sort,
-						filter
-					}, this)
-					.then(response => {
-						next(null, response);
-					})
-					.catch(err => {
-						next(err);
-					});
+					SongsModule.runJob(
+						"GET_DATA",
+						{
+							page,
+							pageSize,
+							properties,
+							sort,
+							filter
+						},
+						this
+					)
+						.then(response => {
+							next(null, response);
+						})
+						.catch(err => {
+							next(err);
+						});
 				}
 			],
 			async (err, response) => {

+ 10 - 10
backend/logic/songs.js

@@ -217,7 +217,7 @@ class _SongsModule extends CoreClass {
 	 * @param {string} payload.filter - the filter object
 	 * @returns {Promise} - returns a promise (resolve, reject)
 	 */
-	 GET_DATA(payload) {
+	GET_DATA(payload) {
 		return new Promise((resolve, reject) => {
 			const { page, pageSize, properties, sort, filter } = payload;
 
@@ -225,7 +225,10 @@ class _SongsModule extends CoreClass {
 
 			const regexFilter = {};
 			for (const [filterKey, filterValue] of Object.entries(filter)) {
-				const isRegex = filterValue.length > 2 && filterValue.indexOf("/") === 0 && filterValue.lastIndexOf("/") === filterValue.length - 1;
+				const isRegex =
+					filterValue.length > 2 &&
+					filterValue.indexOf("/") === 0 &&
+					filterValue.lastIndexOf("/") === filterValue.length - 1;
 				let query;
 				if (isRegex) query = filterValue.slice(1, filterValue.length - 1);
 				else query = filterValue.replaceAll(/[.*+?^${}()|[\]\\]/g, "\\$&");
@@ -236,16 +239,13 @@ class _SongsModule extends CoreClass {
 			async.waterfall(
 				[
 					next => {
-						SongsModule.SongModel
-							.find({ ...regexFilter })
-							.count((err, count) => {
-								next(err, count);
-							});
+						SongsModule.SongModel.find({ ...regexFilter }).count((err, count) => {
+							next(err, count);
+						});
 					},
 
 					(count, next) => {
-						SongsModule.SongModel
-							.find({ ...regexFilter })
+						SongsModule.SongModel.find({ ...regexFilter })
 							.sort(sort)
 							.skip(pageSize * (page - 1))
 							.limit(pageSize)
@@ -259,7 +259,7 @@ class _SongsModule extends CoreClass {
 					if (err && err !== true) return reject(new Error(err));
 					return resolve({ data: songs, count });
 				}
-			)
+			);
 		});
 	}
 

+ 193 - 84
frontend/src/components/AdvancedTable.vue

@@ -2,7 +2,9 @@
 	<div>
 		<div>
 			<button
-				v-for="column in orderedColumns"
+				v-for="column in orderedColumns.filter(
+					c => c.name !== 'select'
+				)"
 				:key="column.name"
 				class="button"
 				@click="toggleColumnVisibility(column)"
@@ -16,61 +18,69 @@
 				}}
 			</button>
 		</div>
-		<table class="table">
-			<thead>
-				<draggable
-					item-key="name"
-					v-model="orderedColumns"
-					v-bind="columnDragOptions"
-					tag="tr"
-				>
-					<template #item="{ element: column }">
-						<th
-							:class="{ sortable: column.sortable }"
-							v-if="enabledColumns.indexOf(column.name) !== -1"
-							@click="changeSort(column)"
-						>
-							{{ column.displayName }}
-							<span
+		<div class="table-container">
+			<table class="table">
+				<thead>
+					<draggable
+						item-key="name"
+						v-model="orderedColumns"
+						v-bind="columnDragOptions"
+						tag="tr"
+						draggable=".item-draggable"
+					>
+						<template #item="{ element: column }">
+							<th
+								:class="{
+									sortable: column.sortable,
+									'item-draggable': column.name !== 'select'
+								}"
 								v-if="
-									column.sortable && sort[column.sortProperty]
+									enabledColumns.indexOf(column.name) !== -1
 								"
-								>({{ sort[column.sortProperty] }})</span
+								@click="changeSort(column)"
 							>
-							<input
-								v-if="column.sortable"
-								placeholder="Filter"
-								@click.stop
-								@keyup.enter="changeFilter(column, $event)"
-							/>
-						</th>
-					</template>
-				</draggable>
-			</thead>
-			<tbody>
-				<tr
-					v-for="(item, itemIndex) in data"
-					:key="item._id"
-					:class="{
-						selected: item.selected,
-						highlighted: item.highlighted
-					}"
-					@click="clickItem(itemIndex, $event)"
-				>
-					<td
-						v-for="column in sortedFilteredColumns"
-						:key="`${item._id}-${column.name}`"
+								{{ column.displayName }}
+								<span
+									v-if="
+										column.sortable &&
+										sort[column.sortProperty]
+									"
+									>({{ sort[column.sortProperty] }})</span
+								>
+								<input
+									v-if="column.sortable"
+									placeholder="Filter"
+									@click.stop
+									@keyup.enter="changeFilter(column, $event)"
+								/>
+							</th>
+						</template>
+					</draggable>
+				</thead>
+				<tbody>
+					<tr
+						v-for="(item, itemIndex) in data"
+						:key="item._id"
+						:class="{
+							selected: item.selected,
+							highlighted: item.highlighted
+						}"
+						@click="clickItem(itemIndex, $event)"
 					>
-						<slot
-							:name="`column-${column.name}`"
-							:item="item"
-						></slot>
-					</td>
-				</tr>
-			</tbody>
-		</table>
-		<br />
-		<div class="control">
+						<td
+							v-for="column in sortedFilteredColumns"
+							:key="`${item._id}-${column.name}`"
+						>
+							<slot
+								:name="`column-${column.name}`"
+								:item="item"
+							></slot>
+						</td>
+					</tr>
+				</tbody>
+			</table>
+		</div>
+		<div class="row control">
 			<label class="label">Items per page</label>
 			<p class="control select">
 				<select v-model.number="pageSize" @change="getData()">
@@ -84,21 +94,47 @@
 				</select>
 			</p>
 		</div>
-		<br />
-		<p>Page {{ page }} / {{ lastPage }}</p>
-		<br />
-		<button class="button is-primary" @click="changePage(page - 1)">
-			Go to previous page</button
-		>&nbsp;
-		<button class="button is-primary" @click="changePage(page + 1)">
-			Go to next page</button
-		>&nbsp;
-		<button class="button is-primary" @click="changePage(1)">
-			Go to first page</button
-		>&nbsp;
-		<button class="button is-primary" @click="changePage(lastPage)">
-			Go to last page
-		</button>
+		<div class="row">
+			<button
+				v-if="page > 1"
+				class="button is-primary material-icons"
+				@click="changePage(1)"
+				content="First Page"
+				v-tippy
+			>
+				skip_previous
+			</button>
+			<button
+				v-if="page > 1"
+				class="button is-primary material-icons"
+				@click="changePage(page - 1)"
+				content="Previous Page"
+				v-tippy
+			>
+				fast_rewind
+			</button>
+
+			<p>Page {{ page }} / {{ lastPage }}</p>
+
+			<button
+				v-if="page < lastPage"
+				class="button is-primary material-icons"
+				@click="changePage(page + 1)"
+				content="Next Page"
+				v-tippy
+			>
+				fast_forward
+			</button>
+			<button
+				v-if="page < lastPage"
+				class="button is-primary material-icons"
+				@click="changePage(lastPage)"
+				content="Last Page"
+				v-tippy
+			>
+				skip_next
+			</button>
+		</div>
 	</div>
 </template>
 
@@ -166,8 +202,18 @@ export default {
 		})
 	},
 	mounted() {
-		this.orderedColumns = this.columns;
-		this.enabledColumns = this.columns.map(column => column.name);
+		const columns = [
+			{
+				name: "select",
+				displayName: "",
+				properties: [],
+				sortable: false,
+				filterable: false
+			},
+			...this.columns
+		];
+		this.orderedColumns = columns;
+		this.enabledColumns = columns.map(column => column.name);
 
 		ws.onConnect(this.init);
 	},
@@ -288,31 +334,94 @@ export default {
 </script>
 
 <style lang="scss" scoped>
-.table {
-	thead {
+.night-mode {
+	.table {
+		background-color: var(--dark-grey-3);
+		color: var(--light-grey-2);
+
 		tr {
-			th {
-				&.sortable {
-					cursor: pointer;
-				}
+			&:nth-child(even) {
+				background-color: var(--dark-grey-2);
+			}
+
+			&:hover,
+			&:focus,
+			&.highlighted {
+				background-color: var(--dark-grey);
 			}
 		}
 	}
+}
 
-	tbody {
-		tr {
-			&.selected {
-				outline: 1px solid red;
-			}
+.table-container {
+	border-radius: 5px;
+	box-shadow: 0 2px 3px rgba(10, 10, 10, 0.1), 0 0 0 1px rgba(10, 10, 10, 0.1);
+	overflow-x: auto;
+	margin: 10px 0;
 
-			&.highlighted {
-				outline: 1px solid blue;
+	table {
+		border-collapse: separate;
+
+		thead {
+			tr {
+				th {
+					&.sortable {
+						cursor: pointer;
+					}
+				}
 			}
+		}
+
+		tbody {
+			tr {
+				&.selected td:first-child {
+					border-left: 5px solid var(--primary-color);
+					padding-left: 0;
+				}
+
+				&.highlighted {
+					background-color: var(--light-grey);
 
-			&.selected.highlighted {
-				outline: 1px solid green;
+					td:first-child {
+						border-left: 5px solid var(--red);
+						padding-left: 0;
+					}
+				}
+
+				&.selected.highlighted td:first-child {
+					border-left: 5px solid var(--green);
+					padding-left: 0;
+				}
 			}
 		}
 	}
+
+	table thead tr th:first-child,
+	table tbody tr td:first-child {
+		position: sticky;
+		left: 0;
+		z-index: 2;
+		padding: 0;
+		padding-left: 5px;
+	}
+}
+
+.row {
+	display: flex;
+	flex-direction: row;
+	flex-wrap: wrap;
+	justify-content: center;
+	margin: 10px;
+
+	button {
+		font-size: 22px;
+		margin: auto 5px;
+	}
+
+	p,
+	label {
+		font-size: 18px;
+		margin: auto 5px;
+	}
 }
 </style>

+ 7 - 1
frontend/src/pages/Admin/tabs/Test.vue

@@ -1,7 +1,7 @@
 <template>
 	<div>
 		<page-metadata title="Admin | Test" />
-		<div class="container">
+		<div class="admin-container">
 			<advanced-table :columns="columns" data-action="songs.getData">
 				<template #column-thumbnailImage="slotProps">
 					<img
@@ -90,6 +90,12 @@ export default {
 </script>
 
 <style lang="scss" scoped>
+.admin-container {
+	max-width: 1900px;
+	margin: 0 auto;
+	padding: 0 10px;
+}
+
 .song-thumbnail {
 	display: block;
 	max-width: 50px;