2024-12-28 13:46:08 +02:00
|
|
|
import FlexContainer from "../containers/flex_container.js";
|
|
|
|
|
2025-01-04 00:20:22 +02:00
|
|
|
const DRAG_STATE_NONE = 0;
|
|
|
|
const DRAG_STATE_INITIAL_DRAG = 1;
|
|
|
|
const DRAG_STATE_DRAGGING = 2;
|
|
|
|
|
2025-01-04 01:06:18 +02:00
|
|
|
/** Percentage of drag that the user has to do in order for the popup to open/close (0-100). */
|
|
|
|
const DRAG_THRESHOLD = 10;
|
|
|
|
|
2024-12-28 13:46:08 +02:00
|
|
|
export default class SidebarContainer extends FlexContainer {
|
|
|
|
|
|
|
|
constructor(screenName, direction) {
|
|
|
|
super(direction);
|
|
|
|
|
|
|
|
this.screenName = screenName;
|
2025-01-03 23:44:20 +02:00
|
|
|
this.currentTranslate = -100;
|
2025-01-04 00:20:22 +02:00
|
|
|
this.dragState = DRAG_STATE_NONE;
|
2024-12-28 13:46:08 +02:00
|
|
|
}
|
|
|
|
|
2024-12-28 13:56:20 +02:00
|
|
|
doRender() {
|
|
|
|
super.doRender();
|
|
|
|
|
|
|
|
this.$widget.on("click", () => {
|
2024-12-28 14:09:50 +02:00
|
|
|
this.triggerCommand('setActiveScreen', {
|
|
|
|
screen: "detail"
|
|
|
|
});
|
2024-12-28 13:56:20 +02:00
|
|
|
});
|
2025-01-03 23:44:20 +02:00
|
|
|
|
2025-01-04 00:22:16 +02:00
|
|
|
document.addEventListener("touchstart", (e) => this.#onDragStart(e));
|
|
|
|
document.addEventListener("touchmove", (e) => this.#onDragMove(e), { passive: false });
|
2025-01-03 23:44:20 +02:00
|
|
|
document.addEventListener("touchend", (e) => this.#onDragEnd(e));
|
|
|
|
}
|
|
|
|
|
|
|
|
#onDragStart(e) {
|
|
|
|
const x = e.touches ? e.touches[0].clientX : e.clientX;
|
2025-01-04 12:09:18 +02:00
|
|
|
this.startX = x;
|
2025-01-04 00:25:59 +02:00
|
|
|
|
2025-01-03 23:44:20 +02:00
|
|
|
if (x > 30 && this.currentTranslate === -100) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2025-01-04 01:54:01 +02:00
|
|
|
this.#setInitialState();
|
2025-01-04 00:20:22 +02:00
|
|
|
this.dragState = DRAG_STATE_INITIAL_DRAG;
|
2025-01-04 00:50:11 +02:00
|
|
|
this.translatePercentage = 0;
|
2025-01-03 23:44:20 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
#onDragMove(e) {
|
2025-01-04 00:20:22 +02:00
|
|
|
if (this.dragState === DRAG_STATE_NONE) {
|
2025-01-03 23:44:20 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
const x = e.touches ? e.touches[0].clientX : e.clientX;
|
|
|
|
const deltaX = x - this.startX;
|
2025-01-04 00:20:22 +02:00
|
|
|
if (this.dragState === DRAG_STATE_INITIAL_DRAG) {
|
2025-01-04 12:09:18 +02:00
|
|
|
if (Math.abs(deltaX) > 10) {
|
2025-01-04 00:50:11 +02:00
|
|
|
/* Disable the transitions since they affect performance, they are going to reenabled once drag ends. */
|
|
|
|
this.sidebarEl.style.transition = "none";
|
|
|
|
this.backdropEl.style.transition = "none";
|
|
|
|
|
2025-01-04 00:52:16 +02:00
|
|
|
this.backdropEl.style.opacity = (this.currentTranslate === -100 ? 0 : 1);
|
2025-01-04 00:50:11 +02:00
|
|
|
this.backdropEl.classList.add("show");
|
|
|
|
|
2025-01-04 00:20:22 +02:00
|
|
|
this.dragState = DRAG_STATE_DRAGGING;
|
|
|
|
}
|
|
|
|
} else if (this.dragState === DRAG_STATE_DRAGGING) {
|
2025-01-04 00:50:11 +02:00
|
|
|
const width = this.sidebarEl.offsetWidth;
|
2025-01-04 00:20:22 +02:00
|
|
|
const translatePercentage = Math.min(0, Math.max(this.currentTranslate + (deltaX / width) * 100, -100));
|
|
|
|
this.translatePercentage = translatePercentage;
|
2025-01-04 00:50:11 +02:00
|
|
|
this.sidebarEl.style.transform = `translateX(${translatePercentage}%)`;
|
|
|
|
this.backdropEl.style.opacity = Math.max(0, 1 + (translatePercentage / 100));
|
2025-01-04 00:20:22 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
e.preventDefault();
|
2025-01-03 23:44:20 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
#onDragEnd(e) {
|
2025-01-04 00:20:22 +02:00
|
|
|
if (this.dragState === DRAG_STATE_NONE) {
|
2025-01-03 23:44:20 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2025-01-04 12:09:18 +02:00
|
|
|
if (this.dragState === DRAG_STATE_INITIAL_DRAG) {
|
|
|
|
this.dragState = DRAG_STATE_NONE;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2025-01-04 01:21:53 +02:00
|
|
|
// If the sidebar is closed, snap the sidebar open only if the user swiped over a threshold.
|
|
|
|
// When the sidebar is open, always close for a smooth experience.
|
|
|
|
const isOpen = (this.currentTranslate === -100 && this.translatePercentage > -(100 - DRAG_THRESHOLD));
|
2025-01-04 12:18:23 +02:00
|
|
|
const screen = (isOpen ? "tree" : "detail");
|
|
|
|
this.triggerCommand("setActiveScreen", { screen });
|
2025-01-04 01:54:01 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
#setInitialState() {
|
|
|
|
if (this.sidebarEl) {
|
|
|
|
// Already initialized.
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
this.sidebarEl = document.getElementById("mobile-sidebar-wrapper");
|
|
|
|
this.backdropEl = document.getElementById("mobile-sidebar-container");
|
|
|
|
this.originalSidebarTransition = this.sidebarEl.style.transition;
|
|
|
|
this.originalBackdropTransition = this.backdropEl.style.transition;
|
|
|
|
}
|
|
|
|
|
|
|
|
#setSidebarOpen(isOpen) {
|
|
|
|
if (!this.sidebarEl) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2025-01-04 00:50:11 +02:00
|
|
|
this.sidebarEl.classList.toggle("show", isOpen);
|
|
|
|
this.sidebarEl.style.transform = isOpen ? 'translateX(0)' : 'translateX(-100%)';
|
|
|
|
this.sidebarEl.style.transition = this.originalSidebarTransition;
|
2025-01-03 23:44:20 +02:00
|
|
|
|
2025-01-04 01:54:01 +02:00
|
|
|
this.backdropEl.classList.toggle("show", isOpen);
|
|
|
|
this.backdropEl.style.transition = this.originalBackdropTransition;
|
|
|
|
this.backdropEl.style.opacity = isOpen ? 1 : 0;
|
2025-01-04 00:20:22 +02:00
|
|
|
|
2025-01-04 01:54:01 +02:00
|
|
|
this.currentTranslate = isOpen ? 0 : -100;
|
2025-01-04 00:20:22 +02:00
|
|
|
this.dragState = DRAG_STATE_NONE;
|
2024-12-28 13:56:20 +02:00
|
|
|
}
|
|
|
|
|
2024-12-28 13:46:08 +02:00
|
|
|
activeScreenChangedEvent({activeScreen}) {
|
2025-01-04 01:54:01 +02:00
|
|
|
this.#setInitialState();
|
|
|
|
this.#setSidebarOpen(activeScreen === this.screenName);
|
2024-12-28 13:46:08 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
}
|