Skip to content

Commit

Permalink
Add drag and drop for table columns
Browse files Browse the repository at this point in the history
  • Loading branch information
jwbonner committed Sep 14, 2024
1 parent a05d538 commit 3eeb7b9
Show file tree
Hide file tree
Showing 2 changed files with 99 additions and 15 deletions.
3 changes: 1 addition & 2 deletions src/hub/SourceList.ts
Original file line number Diff line number Diff line change
Expand Up @@ -193,10 +193,10 @@ export default class SourceList {
if (index === -1) return;

// Update drag item
let element = this.LIST.children[index];
while (this.DRAG_ITEM.firstChild) {
this.DRAG_ITEM.removeChild(this.DRAG_ITEM.firstChild);
}
let element = this.LIST.children[index];
let dragContainer = document.createElement("div");
dragContainer.style.position = "absolute";
dragContainer.style.width = element.clientWidth.toString() + "px";
Expand All @@ -222,7 +222,6 @@ export default class SourceList {
});
});
}

dragContainer.appendChild(elementClone);
this.DRAG_ITEM.appendChild(dragContainer);

Expand Down
111 changes: 98 additions & 13 deletions src/hub/controllers/TableController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,14 @@ import TabController from "./TabController";
export default class TableController implements TabController {
UUID = createUUID();

private DRAG_THRESHOLD_PX = 5;

private ROOT: HTMLElement;
private TABLE_CONTAINER: HTMLElement;
private TABLE_BODY: HTMLElement;
private HEADER: HTMLElement;
private DRAG_HIGHLIGHT: HTMLElement;
private DRAG_ITEM = document.getElementById("dragItem") as HTMLElement;

private fields: string[] = [];
private ranges: { [key: string]: [number, number] } = {};
Expand All @@ -19,6 +23,7 @@ export default class TableController implements TabController {
this.ROOT = root;
this.TABLE_CONTAINER = root.getElementsByClassName("data-table-container")[0] as HTMLElement;
this.TABLE_BODY = root.getElementsByClassName("data-table")[0].firstElementChild as HTMLElement;
this.HEADER = this.TABLE_BODY.firstElementChild as HTMLElement;
this.DRAG_HIGHLIGHT = root.getElementsByClassName("data-table-drag-highlight")[0] as HTMLElement;

// Drag handling
Expand All @@ -33,6 +38,71 @@ export default class TableController implements TabController {
this.fields.splice(index, 1);
}
});

// Column dragging
let mouseDownInfo: [number, number] | null = null;
root.addEventListener("mousedown", (event) => {
if (event.clientY < this.HEADER.firstElementChild!.getBoundingClientRect().bottom) {
mouseDownInfo = [event.clientX, event.clientY];
}
});
root.addEventListener("mouseup", () => {
mouseDownInfo = null;
});
root.addEventListener("mousemove", (event) => {
// Start drag
if (
mouseDownInfo !== null &&
(Math.abs(event.clientX - mouseDownInfo[0]) >= this.DRAG_THRESHOLD_PX ||
Math.abs(event.clientY - mouseDownInfo[1]) >= this.DRAG_THRESHOLD_PX)
) {
// Find item
let index = -1;
Array.from(this.HEADER.children).forEach((element, i) => {
if (i === 0) return;
let rect = element.getBoundingClientRect();
if (
mouseDownInfo![0] >= rect.left &&
mouseDownInfo![0] <= rect.right &&
mouseDownInfo![1] >= rect.top &&
mouseDownInfo![1] <= rect.bottom
) {
index = i;
}
});
mouseDownInfo = null;
if (index === -1) return;

// Update drag item
while (this.DRAG_ITEM.firstChild) {
this.DRAG_ITEM.removeChild(this.DRAG_ITEM.firstChild);
}
let element = this.HEADER.children[index];
let keyContainer = element.firstElementChild as HTMLElement;
let dragContainer = document.createElement("div");
dragContainer.style.position = "absolute";
dragContainer.style.width =
Math.min(keyContainer.clientWidth, (keyContainer.firstElementChild as HTMLElement).offsetWidth).toString() +
"px";
dragContainer.style.height = "30px";
dragContainer.style.left = "0px";
dragContainer.style.top = "0px";
dragContainer.style.margin = "none";
dragContainer.style.padding = "none";
let keyContainerClone = keyContainer.cloneNode(true) as HTMLElement;
keyContainerClone.style.width = "100%";
keyContainerClone.style.fontWeight = "bold";
keyContainerClone.style.fontSize = "14px";
dragContainer.appendChild(keyContainerClone);
this.DRAG_ITEM.appendChild(dragContainer);

// Start drag
let itemRect = element.getBoundingClientRect();
window.startDrag(event.clientX, event.clientY, event.clientX - itemRect.left, event.clientY - itemRect.top, {
tableIndex: index - 1
});
}
});
}

saveState(): unknown {
Expand All @@ -47,32 +117,37 @@ export default class TableController implements TabController {

/** Processes a drag event, including adding a field if necessary. */
private handleDrag(dragData: any) {
if (this.ROOT.hidden || !("fields" in dragData.data)) return;
if (this.ROOT.hidden) return;
const isField = "fields" in dragData.data;
const isColumn = "tableIndex" in dragData.data;
if (!isField && !isColumn) return;

// Remove empty fields
let dragFields = dragData.data.fields as string[];
dragFields = dragFields.filter((field) => window.log.getType(field) !== LoggableType.Empty);
if (dragFields.length === 0) return;
let dragFields: string[] = [];
if (isField) {
dragFields = dragData.data.fields;
dragFields = dragFields.filter((field) => window.log.getType(field) !== LoggableType.Empty);
if (dragFields.length === 0) return;
}

// Find selected section
let header = this.TABLE_BODY.firstElementChild as HTMLElement;
let tableBox = this.TABLE_CONTAINER.getBoundingClientRect();
let selected: number | null = null;
let selectedX: number | null = null;
if (dragData.y > tableBox.y) {
for (let i = 0; i < header.childElementCount; i++) {
for (let i = 0; i < this.HEADER.childElementCount; i++) {
let targetX = 0;
if (i === 0 && this.fields.length > 0) {
targetX = header.children[1].getBoundingClientRect().left;
targetX = this.HEADER.children[1].getBoundingClientRect().left;
} else {
targetX = header.children[i].getBoundingClientRect().right;
targetX = this.HEADER.children[i].getBoundingClientRect().right;
}
if (targetX < (header.firstElementChild as HTMLElement).getBoundingClientRect().right) continue;
let leftBound = i === 0 ? tableBox.x : targetX - header.children[i].getBoundingClientRect().width / 2;
if (targetX < (this.HEADER.firstElementChild as HTMLElement).getBoundingClientRect().right) continue;
let leftBound = i === 0 ? tableBox.x : targetX - this.HEADER.children[i].getBoundingClientRect().width / 2;
let rightBound =
i === header.childElementCount - 1
i === this.HEADER.childElementCount - 1
? Infinity
: targetX + header.children[i + 1].getBoundingClientRect().width / 2;
: targetX + this.HEADER.children[i + 1].getBoundingClientRect().width / 2;
if (leftBound < dragData.x && rightBound > dragData.x) {
selected = i;
selectedX = targetX;
Expand All @@ -84,7 +159,17 @@ export default class TableController implements TabController {
if (dragData.end) {
this.DRAG_HIGHLIGHT.hidden = true;
if (selected !== null) {
this.fields.splice(selected, 0, ...dragFields);
if (isField) {
this.fields.splice(selected, 0, ...dragFields);
} else if (isColumn) {
let sourceIndex = dragData.data.tableIndex;
let fields = this.fields.splice(sourceIndex, 1)[0];
if (selected <= sourceIndex) {
this.fields.splice(selected, 0, fields);
} else {
this.fields.splice(selected - 1, 0, fields);
}
}
}
} else {
this.DRAG_HIGHLIGHT.hidden = selected === null;
Expand Down

0 comments on commit 3eeb7b9

Please sign in to comment.