Browse Source

feat: added more features to custom draggable like attribute binding and tag, and started converting AdvancedTable to custom draggable

Kristian Vos 2 years ago
parent
commit
a31cb6cb64
2 changed files with 200 additions and 205 deletions
  1. 178 201
      frontend/src/components/AdvancedTable.vue
  2. 22 4
      frontend/src/components/Draggable.vue

+ 178 - 201
frontend/src/components/AdvancedTable.vue

@@ -11,12 +11,12 @@ import {
 	nextTick
 } from "vue";
 import { useRoute, useRouter } from "vue-router";
-import { Sortable } from "sortablejs-vue3";
 import Toast from "toasters";
 import { storeToRefs } from "pinia";
 import { useWebsocketsStore } from "@/stores/websockets";
 import { useModalsStore } from "@/stores/modals";
 import keyboardShortcuts from "@/keyboardShortcuts";
+import draggable from "@/components/Draggable.vue";
 import ws from "@/ws";
 import { useDragBox } from "@/composables/useDragBox";
 import {
@@ -674,52 +674,8 @@ const applyFilterAndGetData = () => {
 	storeTableSettings();
 };
 
-const columnOrderChanged = ({ oldIndex, newIndex }) => {
-	if (columnOrderChangedDebounceTimeout.value)
-		clearTimeout(columnOrderChangedDebounceTimeout.value);
-
-	columnOrderChangedDebounceTimeout.value = setTimeout(() => {
-		if (oldIndex === newIndex) return;
-		orderedColumns.value.splice(
-			newIndex,
-			0,
-			orderedColumns.value.splice(oldIndex, 1)[0]
-		);
-		storeTableSettings();
-	}, 100);
-};
-
-const columnOrderChangedDropdown = event => {
-	const filteredOrderedColumns = orderedColumns.value.filter(
-		column =>
-			column.name !== "select" &&
-			column.name !== "placeholder" &&
-			column.name !== "updatedPlaceholder" &&
-			column.draggable
-	);
-	const oldColumn = filteredOrderedColumns[event.oldDraggableIndex];
-	const newColumn = filteredOrderedColumns[event.newDraggableIndex];
-
-	const oldIndex = orderedColumns.value.indexOf(oldColumn);
-	const newIndex = orderedColumns.value.indexOf(newColumn);
-
-	columnOrderChanged({ oldIndex, newIndex });
-};
-
-const columnOrderChangedHead = event => {
-	const filteredOrderedColumns = orderedColumns.value.filter(
-		column =>
-			shownColumns.value.indexOf(column.name) !== -1 &&
-			(column.name !== "updatedPlaceholder" || rows.value.length > 0) &&
-			column.draggable
-	);
-	const oldColumn = filteredOrderedColumns[event.oldDraggableIndex];
-	const newColumn = filteredOrderedColumns[event.newDraggableIndex];
-
-	const oldIndex = orderedColumns.value.indexOf(oldColumn);
-	const newIndex = orderedColumns.value.indexOf(newColumn);
-
-	columnOrderChanged({ oldIndex, newIndex });
+const columnOrderChanged = () => {
+	storeTableSettings();
 };
 
 const getTableSettings = () => {
@@ -1572,66 +1528,73 @@ watch(selectedRows, (newSelectedRows, oldSelectedRows) => {
 						</div>
 
 						<template #content>
-							<Sortable
-								:list="orderedColumns"
-								item-key="name"
-								:options="columnDragOptions"
-								class="nav-dropdown-items"
-								@update="columnOrderChangedDropdown"
-							>
-								<template #item="{ element: column }">
-									<button
-										v-if="
-											column.name !== 'select' &&
-											column.name !== 'placeholder' &&
-											column.name !== 'updatedPlaceholder'
-										"
-										:class="{
+							<div class="nav-dropdown-items">
+								<draggable
+									v-model:list="orderedColumns"
+									item-key="name"
+									:options="columnDragOptions"
+									@update="columnOrderChanged"
+									:attributes="{
+										class: column => ({
 											sortable: column.sortable,
 											'item-draggable': column.draggable,
 											'nav-item': true
-										}"
-										@click.prevent="
-											toggleColumnVisibility(column)
-										"
-									>
-										<p
-											class="control is-expanded checkbox-control"
+										})
+									}"
+									tag="button"
+								>
+									<template #item="{ element: column }">
+										<div
+											v-if="
+												column.name !== 'select' &&
+												column.name !== 'placeholder' &&
+												column.name !==
+													'updatedPlaceholder'
+											"
+											@click.prevent="
+												toggleColumnVisibility(column)
+											"
 										>
-											<label class="switch">
-												<input
-													type="checkbox"
-													:id="`column-dropdown-checkbox-${column.name}`"
-													:checked="
-														shownColumns.indexOf(
-															column.name
-														) !== -1
-													"
-													@click="
-														toggleColumnVisibility(
-															column
-														)
-													"
-												/>
-												<span
-													:class="{
-														slider: true,
-														round: true,
-														disabled:
-															!column.hidable
-													}"
-												></span>
-											</label>
-											<label
-												:for="`column-dropdown-checkbox-${column.name}`"
+											<p
+												class="control is-expanded checkbox-control"
 											>
-												<span></span>
-												<p>{{ column.displayName }}</p>
-											</label>
-										</p>
-									</button>
-								</template>
-							</Sortable>
+												<label class="switch">
+													<input
+														type="checkbox"
+														:id="`column-dropdown-checkbox-${column.name}`"
+														:checked="
+															shownColumns.indexOf(
+																column.name
+															) !== -1
+														"
+														@click="
+															toggleColumnVisibility(
+																column
+															)
+														"
+													/>
+													<span
+														:class="{
+															slider: true,
+															round: true,
+															disabled:
+																!column.hidable
+														}"
+													></span>
+												</label>
+												<label
+													:for="`column-dropdown-checkbox-${column.name}`"
+												>
+													<span></span>
+													<p>
+														{{ column.displayName }}
+													</p>
+												</label>
+											</p>
+										</div>
+									</template>
+								</draggable>
+							</div>
 						</template>
 					</tippy>
 				</div>
@@ -1644,29 +1607,18 @@ watch(selectedRows, (newSelectedRows, oldSelectedRows) => {
 					}"
 				>
 					<thead>
-						<Sortable
-							:list="orderedColumns"
-							item-key="name"
-							tag="tr"
-							:options="{
-								...columnDragOptions,
-								handle: '.handle'
-							}"
-							@update="columnOrderChangedHead"
-						>
-							<template #item="{ element: column }">
-								<th
-									v-if="
-										shownColumns.indexOf(column.name) !==
-											-1 &&
-										(column.name !== 'updatedPlaceholder' ||
-											rows.length > 0)
-									"
-									:class="{
-										sortable: column.sortable,
-										'item-draggable': column.draggable
-									}"
-									:style="{
+						<tr>
+							<draggable
+								v-model:list="orderedColumns"
+								item-key="name"
+								:options="{
+									...columnDragOptions,
+									handle: '.handle'
+								}"
+								@update="columnOrderChanged"
+								tag="th"
+								:attributes="{
+									style: column => ({
 										minWidth: Number.isNaN(column.minWidth)
 											? column.minWidth
 											: `${column.minWidth}px`,
@@ -1676,86 +1628,111 @@ watch(selectedRows, (newSelectedRows, oldSelectedRows) => {
 										maxWidth: Number.isNaN(column.maxWidth)
 											? column.maxWidth
 											: `${column.maxWidth}px`
-									}"
-								>
-									<div v-if="column.name === 'select'">
-										<p class="checkbox">
-											<input
-												v-if="rows.length === 0"
-												type="checkbox"
-												disabled
-											/>
-											<input
-												v-else
-												type="checkbox"
-												:checked="
-													rows.filter(
-														row => !row.removed
-													).length ===
-													selectedRows.length
-												"
-												@click="toggleAllRows()"
-											/>
-										</p>
-									</div>
-									<div v-else class="handle">
-										<span>
-											{{ column.displayName }}
-										</span>
-										<span
-											v-if="column.sortable"
-											:content="`Sort by ${column.displayName}`"
-											v-tippy
-										>
-											<span
-												v-if="
-													!sort[column.sortProperty]
-												"
-												class="material-icons"
-												@click="changeSort(column)"
-											>
-												unfold_more
-											</span>
-											<span
-												v-if="
-													sort[
-														column.sortProperty
-													] === 'ascending'
-												"
-												class="material-icons active"
-												@click="changeSort(column)"
-											>
-												expand_more
+									}),
+									class: column => ({
+										sortable: column.sortable,
+										'item-draggable': column.draggable
+									})
+								}"
+							>
+								<template #item="{ element: column }">
+									<template
+										v-if="
+											shownColumns.indexOf(
+												column.name
+											) !== -1 &&
+											(column.name !==
+												'updatedPlaceholder' ||
+												rows.length > 0)
+										"
+									>
+										<div v-if="column.name === 'select'">
+											<p class="checkbox">
+												<input
+													v-if="rows.length === 0"
+													type="checkbox"
+													disabled
+												/>
+												<input
+													v-else
+													type="checkbox"
+													:checked="
+														rows.filter(
+															row => !row.removed
+														).length ===
+														selectedRows.length
+													"
+													@click="toggleAllRows()"
+												/>
+											</p>
+										</div>
+										<div v-else class="handle">
+											<span>
+												{{ column.displayName }}
 											</span>
 											<span
-												v-if="
-													sort[
-														column.sortProperty
-													] === 'descending'
-												"
-												class="material-icons active"
-												@click="changeSort(column)"
+												v-if="column.sortable"
+												:content="`Sort by ${column.displayName}`"
+												v-tippy
 											>
-												expand_less
+												<span
+													v-if="
+														!sort[
+															column.sortProperty
+														]
+													"
+													class="material-icons"
+													@click="changeSort(column)"
+												>
+													unfold_more
+												</span>
+												<span
+													v-if="
+														sort[
+															column.sortProperty
+														] === 'ascending'
+													"
+													class="material-icons active"
+													@click="changeSort(column)"
+												>
+													expand_more
+												</span>
+												<span
+													v-if="
+														sort[
+															column.sortProperty
+														] === 'descending'
+													"
+													class="material-icons active"
+													@click="changeSort(column)"
+												>
+													expand_less
+												</span>
 											</span>
-										</span>
-									</div>
-									<div
-										class="resizer"
-										v-if="column.resizable"
-										@mousedown.prevent.stop="
-											columnResizingStart(column, $event)
-										"
-										@touchstart.prevent.stop="
-											columnResizingStart(column, $event)
-										"
-										@mouseup="columnResizingStop()"
-										@touchend="columnResizingStop()"
-										@dblclick="columnResetWidth(column)"
-									></div>
-								</th>
-							</template>
-						</Sortable>
+										</div>
+										<div
+											class="resizer"
+											v-if="column.resizable"
+											@mousedown.prevent.stop="
+												columnResizingStart(
+													column,
+													$event
+												)
+											"
+											@touchstart.prevent.stop="
+												columnResizingStart(
+													column,
+													$event
+												)
+											"
+											@mouseup="columnResizingStop()"
+											@touchend="columnResizingStop()"
+											@dblclick="columnResetWidth(column)"
+										></div>
+									</template>
+								</template>
+							</draggable>
+						</tr>
 					</thead>
 					<tbody>
 						<tr

+ 22 - 4
frontend/src/components/Draggable.vue

@@ -6,7 +6,10 @@ const props = defineProps({
 	itemKey: { type: String, default: "" },
 	list: { type: Array as PropType<any[]>, default: () => [] },
 	componentData: { type: Object, default: () => ({}) },
-	options: { type: Object, default: () => ({}) }
+	attributes: { type: Object, default: () => ({}) },
+	options: { type: Object, default: () => ({}) },
+	tag: { type: String, default: "div" },
+	class: { type: String, default: "" }
 });
 
 const mounted = ref(false);
@@ -19,6 +22,8 @@ const emit = defineEmits(["update:list", "start", "end", "update"]);
 
 // When an element starts being dragged
 const onDragStart = (itemIndex: number, event: DragEvent) => {
+	// console.log(111, event);
+
 	// Set the effect of moving an element, which by default is clone. Not being used right now
 	event.dataTransfer.dropEffect = "move";
 
@@ -39,6 +44,7 @@ const onDragStart = (itemIndex: number, event: DragEvent) => {
 
 // When a dragging element hovers over another draggable element, this gets triggered, usually many times in a second
 const onDragOver = (itemIndex: number) => {
+	// console.log(321, itemIndex);
 	// The index and list name of the item that is being dragged, stored in window since it can come from another list as well
 	const fromIndex = window.draggingItemIndex;
 	const fromList = window.draggingItemListName;
@@ -46,6 +52,8 @@ const onDragOver = (itemIndex: number) => {
 	const toIndex = itemIndex;
 	const toList = props.name;
 
+	// console.log(3211, fromIndex, fromList, toIndex, toList);
+
 	// If the item hasn't changed position in the same list, don't continue
 	if (fromIndex === toIndex && fromList === toList) return;
 
@@ -90,13 +98,22 @@ const onDrop = () => {
 	// Emits the update event to parent component, indicating that the order is now done and ordering/moving is done
 	emit("update");
 };
+
+// Function that gets called for each item and returns attributes
+const convertAttributes = item =>
+	Object.fromEntries(
+		Object.entries(props.attributes).map(([key, value]) => [
+			key,
+			typeof value === "function" ? value(item) : value
+		])
+	);
 </script>
 
 <template>
-	<div
+	<component
+		:is="tag"
 		v-for="(item, itemIndex) in list"
 		:key="item[itemKey]"
-		class="draggable-item"
 		draggable="true"
 		@dragstart="onDragStart(itemIndex, $event)"
 		@dragenter.prevent
@@ -105,7 +122,8 @@ const onDrop = () => {
 		@drop.prevent="onDrop()"
 		:data-index="itemIndex"
 		:data-list="name"
+		v-bind="convertAttributes(item)"
 	>
 		<slot name="item" :element="item"></slot>
-	</div>
+	</component>
 </template>