mirror of
https://github.com/Theodor-Springmann-Stiftung/musenalm.git
synced 2026-02-04 10:35:30 +00:00
+Reihen relations, small UX stuff
This commit is contained in:
289
views/transform/relations-editor.js
Normal file
289
views/transform/relations-editor.js
Normal file
@@ -0,0 +1,289 @@
|
||||
const ROLE_ADD_TOGGLE = "[data-role='relation-add-toggle']";
|
||||
const ROLE_ADD_PANEL = "[data-role='relation-add-panel']";
|
||||
const ROLE_ADD_CLOSE = "[data-role='relation-add-close']";
|
||||
const ROLE_ADD_APPLY = "[data-role='relation-add-apply']";
|
||||
const ROLE_ADD_ERROR = "[data-role='relation-add-error']";
|
||||
const ROLE_ADD_ROW = "[data-role='relation-add-row']";
|
||||
const ROLE_ADD_SELECT = "[data-role='relation-add-select']";
|
||||
const ROLE_TYPE_SELECT = "[data-role='relation-type-select']";
|
||||
const ROLE_UNCERTAIN = "[data-role='relation-uncertain']";
|
||||
const ROLE_NEW_TEMPLATE = "template[data-role='relation-new-template']";
|
||||
const ROLE_NEW_DELETE = "[data-role='relation-new-delete']";
|
||||
const ROLE_REL_ROW = "[data-rel-row]";
|
||||
const ROLE_REL_STRIKE = "[data-rel-strike]";
|
||||
|
||||
export class RelationsEditor extends HTMLElement {
|
||||
constructor() {
|
||||
super();
|
||||
this._pendingItem = null;
|
||||
this._pendingApply = false;
|
||||
}
|
||||
|
||||
connectedCallback() {
|
||||
this._prefix = this.getAttribute("data-prefix") || "";
|
||||
this._linkBase = this.getAttribute("data-link-base") || "";
|
||||
this._newLabel = this.getAttribute("data-new-label") || "(Neu)";
|
||||
this._addToggleId = this.getAttribute("data-add-toggle-id") || "";
|
||||
this._setupAddPanel();
|
||||
this._setupDeleteToggles();
|
||||
}
|
||||
|
||||
_setupAddPanel() {
|
||||
this._addToggle = this.querySelector(ROLE_ADD_TOGGLE);
|
||||
if (this._addToggleId) {
|
||||
const externalToggle = document.getElementById(this._addToggleId);
|
||||
if (externalToggle) {
|
||||
this._addToggle = externalToggle;
|
||||
}
|
||||
}
|
||||
this._addPanel = this.querySelector(ROLE_ADD_PANEL);
|
||||
this._addClose = this.querySelector(ROLE_ADD_CLOSE);
|
||||
this._addApply = this.querySelector(ROLE_ADD_APPLY);
|
||||
this._addError = this.querySelector(ROLE_ADD_ERROR);
|
||||
this._addRow = this.querySelector(ROLE_ADD_ROW);
|
||||
this._addSelect = this.querySelector(ROLE_ADD_SELECT);
|
||||
this._typeSelect = this.querySelector(ROLE_TYPE_SELECT);
|
||||
this._uncertain = this.querySelector(ROLE_UNCERTAIN);
|
||||
this._template = this.querySelector(ROLE_NEW_TEMPLATE);
|
||||
this._addInput = this._addSelect ? this._addSelect.querySelector(".ssr-input") : null;
|
||||
|
||||
if (!this._addPanel || !this._addRow || !this._addSelect || !this._typeSelect || !this._uncertain || !this._template) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this._addToggle) {
|
||||
this._addToggle.addEventListener("click", () => {
|
||||
this._addPanel.classList.toggle("hidden");
|
||||
});
|
||||
}
|
||||
|
||||
if (this._addClose) {
|
||||
this._addClose.addEventListener("click", () => {
|
||||
this._addPanel.classList.add("hidden");
|
||||
});
|
||||
}
|
||||
|
||||
if (this._addInput) {
|
||||
this._addInput.addEventListener("keydown", (event) => {
|
||||
if (event.key === "Enter") {
|
||||
this._pendingApply = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (this._addApply) {
|
||||
this._addApply.addEventListener("click", () => {
|
||||
this._pendingApply = false;
|
||||
const idInput = this._addPanel.querySelector(`input[name='${this._prefix}_new_id']`);
|
||||
const hasSelection = idInput && idInput.value.trim().length > 0;
|
||||
if (!hasSelection) {
|
||||
if (this._addError) {
|
||||
this._addError.classList.remove("hidden");
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (this._addError) {
|
||||
this._addError.classList.add("hidden");
|
||||
}
|
||||
if (!this._pendingItem) {
|
||||
return;
|
||||
}
|
||||
this._insertNewRow();
|
||||
});
|
||||
}
|
||||
|
||||
this._addSelect.addEventListener("ssrchange", (event) => {
|
||||
this._pendingItem = event.detail?.item || null;
|
||||
if (this._pendingItem && this._addError) {
|
||||
this._addError.classList.add("hidden");
|
||||
}
|
||||
if (this._pendingApply && this._pendingItem && this._addApply) {
|
||||
this._pendingApply = false;
|
||||
this._addApply.click();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
_clearAddPanel() {
|
||||
if (this._addSelect) {
|
||||
const clearButton = this._addSelect.querySelector(".ssr-clear-button");
|
||||
if (clearButton) {
|
||||
clearButton.click();
|
||||
}
|
||||
}
|
||||
if (this._typeSelect) {
|
||||
this._typeSelect.selectedIndex = 0;
|
||||
}
|
||||
if (this._uncertain) {
|
||||
this._uncertain.checked = false;
|
||||
}
|
||||
if (this._addError) {
|
||||
this._addError.classList.add("hidden");
|
||||
}
|
||||
}
|
||||
|
||||
_insertNewRow() {
|
||||
const fragment = this._template.content.cloneNode(true);
|
||||
const row = fragment.querySelector(ROLE_REL_ROW) || fragment.firstElementChild;
|
||||
if (!row) {
|
||||
return;
|
||||
}
|
||||
|
||||
const link = fragment.querySelector("[data-rel-link]");
|
||||
if (link) {
|
||||
link.setAttribute("href", `${this._linkBase}${this._pendingItem.id}`);
|
||||
}
|
||||
|
||||
const nameEl = fragment.querySelector("[data-rel-name]");
|
||||
if (nameEl) {
|
||||
nameEl.textContent = this._pendingItem.name || "";
|
||||
}
|
||||
|
||||
const detailEl = fragment.querySelector("[data-rel-detail]");
|
||||
const detailContainer = fragment.querySelector("[data-rel-detail-container]");
|
||||
const detailText = this._pendingItem.detail || this._pendingItem.bio || "";
|
||||
if (detailEl && detailText) {
|
||||
detailEl.textContent = detailText;
|
||||
} else if (detailContainer) {
|
||||
detailContainer.remove();
|
||||
}
|
||||
|
||||
const newBadge = fragment.querySelector("[data-rel-new]");
|
||||
if (newBadge) {
|
||||
newBadge.textContent = this._newLabel;
|
||||
}
|
||||
|
||||
const typeSelect = fragment.querySelector("[data-rel-input='type']");
|
||||
if (typeSelect && this._typeSelect) {
|
||||
typeSelect.innerHTML = this._typeSelect.innerHTML;
|
||||
typeSelect.value = this._typeSelect.value;
|
||||
typeSelect.name = `${this._prefix}_new_type`;
|
||||
}
|
||||
|
||||
const uncertain = fragment.querySelector("[data-rel-input='uncertain']");
|
||||
if (uncertain && this._uncertain) {
|
||||
uncertain.checked = this._uncertain.checked;
|
||||
uncertain.name = `${this._prefix}_new_uncertain`;
|
||||
const uncertainId = `${this._prefix}_new_uncertain_row`;
|
||||
uncertain.id = uncertainId;
|
||||
const uncertainLabel = fragment.querySelector("[data-rel-uncertain-label]");
|
||||
if (uncertainLabel) {
|
||||
uncertainLabel.setAttribute("for", uncertainId);
|
||||
}
|
||||
}
|
||||
|
||||
const hiddenId = fragment.querySelector("[data-rel-input='id']");
|
||||
if (hiddenId) {
|
||||
hiddenId.name = `${this._prefix}_new_id`;
|
||||
hiddenId.value = this._pendingItem.id;
|
||||
}
|
||||
|
||||
const deleteButton = fragment.querySelector(ROLE_NEW_DELETE);
|
||||
if (deleteButton) {
|
||||
deleteButton.addEventListener("click", () => {
|
||||
this._addRow.innerHTML = "";
|
||||
this._pendingItem = null;
|
||||
this._clearAddPanel();
|
||||
if (this._addPanel) {
|
||||
this._addPanel.classList.add("hidden");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
this._addRow.innerHTML = "";
|
||||
this._addRow.appendChild(fragment);
|
||||
this._pendingItem = null;
|
||||
this._clearAddPanel();
|
||||
if (this._addPanel) {
|
||||
this._addPanel.classList.add("hidden");
|
||||
}
|
||||
}
|
||||
|
||||
_setupDeleteToggles() {
|
||||
this.querySelectorAll("[data-delete-toggle]").forEach((button) => {
|
||||
button.addEventListener("click", () => {
|
||||
const targetId = button.getAttribute("data-delete-toggle");
|
||||
const checkbox = this.querySelector(`#${CSS.escape(targetId)}`);
|
||||
if (!checkbox) {
|
||||
return;
|
||||
}
|
||||
checkbox.checked = !checkbox.checked;
|
||||
button.classList.toggle("opacity-60", checkbox.checked);
|
||||
|
||||
const row = button.closest(ROLE_REL_ROW);
|
||||
if (row) {
|
||||
row.classList.toggle("bg-red-50", checkbox.checked);
|
||||
row.querySelectorAll(ROLE_REL_STRIKE).forEach((el) => {
|
||||
el.classList.toggle("is-removed", checkbox.checked);
|
||||
el.classList.toggle("text-gray-500", checkbox.checked);
|
||||
});
|
||||
}
|
||||
|
||||
const label = button.querySelector("[data-delete-label]");
|
||||
if (label) {
|
||||
label.textContent = checkbox.checked
|
||||
? label.getAttribute("data-delete-active") || "Wird entfernt"
|
||||
: label.getAttribute("data-delete-default") || "Entfernen";
|
||||
}
|
||||
|
||||
const icon = button.querySelector("i");
|
||||
if (icon) {
|
||||
if (checkbox.checked) {
|
||||
icon.classList.add("hidden");
|
||||
icon.classList.remove("ri-delete-bin-line", "ri-arrow-go-back-line");
|
||||
} else {
|
||||
icon.classList.remove("hidden");
|
||||
icon.classList.add("ri-delete-bin-line");
|
||||
icon.classList.remove("ri-arrow-go-back-line");
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
button.addEventListener("mouseenter", () => {
|
||||
const targetId = button.getAttribute("data-delete-toggle");
|
||||
const checkbox = this.querySelector(`#${CSS.escape(targetId)}`);
|
||||
if (!checkbox || !checkbox.checked) {
|
||||
return;
|
||||
}
|
||||
const label = button.querySelector("[data-delete-label]");
|
||||
if (label) {
|
||||
label.textContent = label.getAttribute("data-delete-hover") || "Rückgängig";
|
||||
label.classList.add("text-orange-700");
|
||||
label.classList.remove("text-gray-500");
|
||||
}
|
||||
const icon = button.querySelector("i");
|
||||
if (icon) {
|
||||
icon.classList.remove("hidden");
|
||||
icon.classList.add("ri-arrow-go-back-line");
|
||||
icon.classList.remove("ri-delete-bin-line");
|
||||
}
|
||||
});
|
||||
|
||||
button.addEventListener("mouseleave", () => {
|
||||
const targetId = button.getAttribute("data-delete-toggle");
|
||||
const checkbox = this.querySelector(`#${CSS.escape(targetId)}`);
|
||||
const label = button.querySelector("[data-delete-label]");
|
||||
if (!label) {
|
||||
return;
|
||||
}
|
||||
label.classList.remove("text-orange-700");
|
||||
if (checkbox && checkbox.checked) {
|
||||
label.textContent = label.getAttribute("data-delete-active") || "Wird entfernt";
|
||||
} else {
|
||||
label.textContent = label.getAttribute("data-delete-default") || "Entfernen";
|
||||
}
|
||||
const icon = button.querySelector("i");
|
||||
if (icon) {
|
||||
if (checkbox && checkbox.checked) {
|
||||
icon.classList.add("hidden");
|
||||
icon.classList.remove("ri-delete-bin-line", "ri-arrow-go-back-line");
|
||||
} else {
|
||||
icon.classList.remove("hidden");
|
||||
icon.classList.add("ri-delete-bin-line");
|
||||
icon.classList.remove("ri-arrow-go-back-line");
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user