Skip to content

Commit

Permalink
Refresh file list when there are queued snapshots (#697)
Browse files Browse the repository at this point in the history
* add destroy hook

* refresh details modal in interval

* refactor to refresh assets list

* disable create snapshot button when there is a pending snapshot
  • Loading branch information
sissbruecker committed Apr 14, 2024
1 parent df9f009 commit 1b7731e
Show file tree
Hide file tree
Showing 16 changed files with 266 additions and 84 deletions.
14 changes: 8 additions & 6 deletions bookmarks/frontend/behaviors/bookmark-page.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { registerBehavior } from "./index";
import { Behavior, registerBehavior } from "./index";

class BookmarkItem {
class BookmarkItem extends Behavior {
constructor(element) {
this.element = element;
super(element);

// Toggle notes
const notesToggle = element.querySelector(".toggle-notes");
Expand All @@ -13,9 +13,11 @@ class BookmarkItem {
// Add tooltip to title if it is truncated
const titleAnchor = element.querySelector(".title > a");
const titleSpan = titleAnchor.querySelector("span");
if (titleSpan.offsetWidth > titleAnchor.offsetWidth) {
titleAnchor.dataset.tooltip = titleSpan.textContent;
}
requestAnimationFrame(() => {
if (titleSpan.offsetWidth > titleAnchor.offsetWidth) {
titleAnchor.dataset.tooltip = titleSpan.textContent;
}
});
}

onToggleNotes(event) {
Expand Down
7 changes: 4 additions & 3 deletions bookmarks/frontend/behaviors/bulk-edit.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { registerBehavior } from "./index";
import { Behavior, registerBehavior } from "./index";

class BulkEdit {
class BulkEdit extends Behavior {
constructor(element) {
this.element = element;
super(element);

this.active = false;

this.onToggleActive = this.onToggleActive.bind(this);
Expand Down
49 changes: 27 additions & 22 deletions bookmarks/frontend/behaviors/confirm-button.js
Original file line number Diff line number Diff line change
@@ -1,25 +1,29 @@
import { registerBehavior } from "./index";
import { Behavior, registerBehavior } from "./index";

class ConfirmButtonBehavior {
class ConfirmButtonBehavior extends Behavior {
constructor(element) {
const button = element;
button.dataset.type = button.type;
button.dataset.name = button.name;
button.dataset.value = button.value;
button.removeAttribute("type");
button.removeAttribute("name");
button.removeAttribute("value");
button.addEventListener("click", this.onClick.bind(this));
this.button = button;
super(element);
element.dataset.type = element.type;
element.dataset.name = element.name;
element.dataset.value = element.value;
element.removeAttribute("type");
element.removeAttribute("name");
element.removeAttribute("value");
element.addEventListener("click", this.onClick.bind(this));
}

destroy() {
Behavior.interacting = false;
}

onClick(event) {
event.preventDefault();
Behavior.interacting = true;

const container = document.createElement("span");
container.className = "confirmation";

const icon = this.button.getAttribute("ld-confirm-icon");
const icon = this.element.getAttribute("ld-confirm-icon");
if (icon) {
const iconElement = document.createElementNS(
"http://www.w3.org/2000/svg",
Expand All @@ -31,42 +35,43 @@ class ConfirmButtonBehavior {
container.append(iconElement);
}

const question = this.button.getAttribute("ld-confirm-question");
const question = this.element.getAttribute("ld-confirm-question");
if (question) {
const questionElement = document.createElement("span");
questionElement.innerText = question;
container.append(question);
}

const buttonClasses = Array.from(this.button.classList.values())
const buttonClasses = Array.from(this.element.classList.values())
.filter((cls) => cls.startsWith("btn"))
.join(" ");

const cancelButton = document.createElement(this.button.nodeName);
const cancelButton = document.createElement(this.element.nodeName);
cancelButton.type = "button";
cancelButton.innerText = question ? "No" : "Cancel";
cancelButton.className = `${buttonClasses} mr-1`;
cancelButton.addEventListener("click", this.reset.bind(this));

const confirmButton = document.createElement(this.button.nodeName);
confirmButton.type = this.button.dataset.type;
confirmButton.name = this.button.dataset.name;
confirmButton.value = this.button.dataset.value;
const confirmButton = document.createElement(this.element.nodeName);
confirmButton.type = this.element.dataset.type;
confirmButton.name = this.element.dataset.name;
confirmButton.value = this.element.dataset.value;
confirmButton.innerText = question ? "Yes" : "Confirm";
confirmButton.className = buttonClasses;
confirmButton.addEventListener("click", this.reset.bind(this));

container.append(cancelButton, confirmButton);
this.container = container;

this.button.before(container);
this.button.classList.add("d-none");
this.element.before(container);
this.element.classList.add("d-none");
}

reset() {
setTimeout(() => {
Behavior.interacting = false;
this.container.remove();
this.button.classList.remove("d-none");
this.element.classList.remove("d-none");
});
}
}
Expand Down
6 changes: 3 additions & 3 deletions bookmarks/frontend/behaviors/dropdown.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { registerBehavior } from "./index";
import { Behavior, registerBehavior } from "./index";

class DropdownBehavior {
class DropdownBehavior extends Behavior {
constructor(element) {
this.element = element;
super(element);
this.opened = false;
this.onOutsideClick = this.onOutsideClick.bind(this);

Expand Down
35 changes: 29 additions & 6 deletions bookmarks/frontend/behaviors/fetch.js
Original file line number Diff line number Diff line change
@@ -1,15 +1,31 @@
import { fireEvents, registerBehavior, swap } from "./index";
import { Behavior, fireEvents, registerBehavior, swap } from "./index";

class FetchBehavior {
class FetchBehavior extends Behavior {
constructor(element) {
this.element = element;
super(element);

const eventName = element.getAttribute("ld-on");
const interval = parseInt(element.getAttribute("ld-interval")) * 1000;

this.onFetch = this.onFetch.bind(this);
this.onInterval = this.onInterval.bind(this);

element.addEventListener(eventName, this.onFetch);
if (interval) {
this.intervalId = setInterval(this.onInterval, interval);
}
}

element.addEventListener(eventName, this.onFetch.bind(this));
destroy() {
if (this.intervalId) {
clearInterval(this.intervalId);
}
}

async onFetch(event) {
event.preventDefault();
async onFetch(maybeEvent) {
if (maybeEvent) {
maybeEvent.preventDefault();
}
const url = this.element.getAttribute("ld-fetch");
const html = await fetch(url).then((response) => response.text());

Expand All @@ -20,6 +36,13 @@ class FetchBehavior {
const events = this.element.getAttribute("ld-fire");
fireEvents(events);
}

onInterval() {
if (Behavior.interacting) {
return;
}
this.onFetch();
}
}

registerBehavior("ld-fetch", FetchBehavior);
11 changes: 7 additions & 4 deletions bookmarks/frontend/behaviors/form.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { fireEvents, registerBehavior } from "./index";
import { Behavior, fireEvents, registerBehavior } from "./index";

class FormBehavior {
class FormBehavior extends Behavior {
constructor(element) {
this.element = element;
super(element);

element.addEventListener("submit", this.onSubmit.bind(this));
}

Expand All @@ -28,8 +29,10 @@ class FormBehavior {
}
}

class AutoSubmitBehavior {
class AutoSubmitBehavior extends Behavior {
constructor(element) {
super(element);

element.addEventListener("change", () => {
const form = element.closest("form");
form.dispatchEvent(new Event("submit", { cancelable: true }));
Expand Down
8 changes: 5 additions & 3 deletions bookmarks/frontend/behaviors/global-shortcuts.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import { registerBehavior } from "./index";
import { Behavior, registerBehavior } from "./index";

class GlobalShortcuts extends Behavior {
constructor(element) {
super(element);

class GlobalShortcuts {
constructor() {
document.addEventListener("keydown", this.onKeyDown.bind(this));
}

Expand Down
64 changes: 62 additions & 2 deletions bookmarks/frontend/behaviors/index.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,35 @@
const behaviorRegistry = {};
const debug = false;

const mutationObserver = new MutationObserver((mutations) => {
mutations.forEach((mutation) => {
mutation.removedNodes.forEach((node) => {
if (node instanceof HTMLElement && !node.isConnected) {
destroyBehaviors(node);
}
});
mutation.addedNodes.forEach((node) => {
if (node instanceof HTMLElement && node.isConnected) {
applyBehaviors(node);
}
});
});
});

mutationObserver.observe(document.body, {
childList: true,
subtree: true,
});

export class Behavior {
constructor(element) {
this.element = element;
}

destroy() {}
}

Behavior.interacting = false;

export function registerBehavior(name, behavior) {
behaviorRegistry[name] = behavior;
Expand Down Expand Up @@ -33,6 +64,34 @@ export function applyBehaviors(container, behaviorNames = null) {

const behaviorInstance = new behavior(element);
element.__behaviors.push(behaviorInstance);
if (debug) {
console.log(
`[Behavior] ${behaviorInstance.constructor.name} initialized`,
);
}
});
});
}

export function destroyBehaviors(element) {
const behaviorNames = Object.keys(behaviorRegistry);

behaviorNames.forEach((behaviorName) => {
const elements = Array.from(element.querySelectorAll(`[${behaviorName}]`));
elements.push(element);

elements.forEach((element) => {
if (!element.__behaviors) {
return;
}

element.__behaviors.forEach((behavior) => {
behavior.destroy();
if (debug) {
console.log(`[Behavior] ${behavior.constructor.name} destroyed`);
}
});
delete element.__behaviors;
});
});
}
Expand Down Expand Up @@ -63,10 +122,11 @@ export function swap(element, html, options) {
break;
case "innerHTML":
default:
targetElement.innerHTML = "";
Array.from(targetElement.children).forEach((child) => {
child.remove();
});
targetElement.append(...contents);
}
contents.forEach((content) => applyBehaviors(content));
}

export function fireEvents(events) {
Expand Down
6 changes: 3 additions & 3 deletions bookmarks/frontend/behaviors/modal.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { registerBehavior } from "./index";
import { Behavior, registerBehavior } from "./index";

class ModalBehavior {
class ModalBehavior extends Behavior {
constructor(element) {
this.element = element;
super(element);

const modalOverlay = element.querySelector(".modal-overlay");
const closeButton = element.querySelector("button.close");
Expand Down
5 changes: 3 additions & 2 deletions bookmarks/frontend/behaviors/tag-autocomplete.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import { registerBehavior } from "./index";
import { Behavior, registerBehavior } from "./index";
import TagAutoCompleteComponent from "../components/TagAutocomplete.svelte";
import { ApiClient } from "../api";

class TagAutocomplete {
class TagAutocomplete extends Behavior {
constructor(element) {
super(element);
const wrapper = document.createElement("div");
const apiBaseUrl = document.documentElement.dataset.apiBaseUrl || "";
const apiClient = new ApiClient(apiBaseUrl);
Expand Down

0 comments on commit 1b7731e

Please sign in to comment.