new sortable list implementation
This commit is contained in:
@@ -287,8 +287,7 @@ export class BadgeEditorForm {
|
||||
return;
|
||||
}
|
||||
if (this.#badgeTemplate === undefined){
|
||||
this.#badgeTemplate = document.getElementById('badge-editor-template').content;
|
||||
console.log(this.#badgeTemplate);
|
||||
this.#badgeTemplate = document.getElementById('badge-editor-template').content.firstElementChild.outerHTML;
|
||||
}
|
||||
el.replaceChildren();
|
||||
const addButton = `<button data-disable-if-max="1" data-receive="updateBadgeCount" DISABLE_IF_MAX type="button" data-send="addBadge">Add badge</button>`;
|
||||
@@ -300,14 +299,18 @@ export class BadgeEditorForm {
|
||||
['DISABLE_IF_MAX', badgeCount === 10 ? 'disabled' : ''],
|
||||
];
|
||||
el.appendChild(this.api.makeHTML(controls, subs));
|
||||
el.appendChild(badges.value);
|
||||
|
||||
const listTemplate = document.getElementById('badges-list-template').content.firstElementChild.outerHTML;
|
||||
const list = this.api.makeHTML(listTemplate).firstElementChild;
|
||||
list.appendChild(badges.value);
|
||||
el.appendChild(list);
|
||||
}
|
||||
|
||||
addBadge(ev, el) {
|
||||
if (this.#badgeTemplate === undefined) {
|
||||
return;
|
||||
}
|
||||
const badge = this.#badgeTemplate.cloneNode(true);
|
||||
const badge = this.api.makeHTML(this.#badgeTemplate).firstElementChild;
|
||||
el.appendChild(badge);
|
||||
this.api.localTrigger('updateBadgeCount');
|
||||
}
|
||||
@@ -341,9 +344,7 @@ export class BadgeEditorForm {
|
||||
noUploads.forEach(e => {
|
||||
e.value = null;
|
||||
})
|
||||
// console.log(noUploads);
|
||||
el.submit();
|
||||
// console.log('would submit now');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -463,3 +464,80 @@ export class BadgeEditorBadge {
|
||||
el.setCustomValidity('');
|
||||
}
|
||||
}
|
||||
|
||||
const getCollectionDataForEl = el => {
|
||||
const nameInput = el.querySelector(".collection-name");
|
||||
const collectionId = el.dataset.collectionId;
|
||||
|
||||
return {
|
||||
id: collectionId,
|
||||
name: nameInput.value,
|
||||
is_new: !('collectionId' in el.dataset),
|
||||
};
|
||||
}
|
||||
|
||||
export class CollectionsEditor {
|
||||
#collectionTemplate = undefined;
|
||||
#collectionsData = [];
|
||||
#removedCollections = [];
|
||||
#valid = true;
|
||||
|
||||
addCollection(ev, el) {
|
||||
if (this.#collectionTemplate === undefined) {
|
||||
this.#collectionTemplate = document.getElementById('new-collection-template').content;
|
||||
}
|
||||
// interesting
|
||||
const newCollection = this.api.makeHTML(this.#collectionTemplate.firstElementChild.outerHTML);
|
||||
el.appendChild(newCollection);
|
||||
}
|
||||
|
||||
deleteCollection(ev, el) {
|
||||
if (!el.contains(ev.sender)) {
|
||||
return;
|
||||
}
|
||||
if ('collectionId' in el.dataset) {
|
||||
this.#removedCollections.push(el.dataset.collectionId);
|
||||
}
|
||||
el.remove();
|
||||
}
|
||||
|
||||
async saveCollections(ev, el) {
|
||||
this.#valid = true;
|
||||
this.api.localTrigger('testValidity');
|
||||
if (!this.#valid) {
|
||||
return;
|
||||
}
|
||||
this.#collectionsData = [];
|
||||
this.api.localTrigger('getCollectionData');
|
||||
|
||||
const data = {
|
||||
collections: this.#collectionsData,
|
||||
removed_collections: this.#removedCollections,
|
||||
};
|
||||
|
||||
const res = await this.api.getJSON(el.prop('submitHref'), [], {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(data),
|
||||
});
|
||||
if (res.error) {
|
||||
return;
|
||||
}
|
||||
|
||||
window.location.reload();
|
||||
}
|
||||
|
||||
getCollectionData(ev, el) {
|
||||
this.#collectionsData.push(getCollectionDataForEl(el));
|
||||
}
|
||||
|
||||
testValidity(ev, el) {
|
||||
const input = el.querySelector('input');
|
||||
if (!input.validity.valid) {
|
||||
input.reportValidity();
|
||||
this.#valid = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,128 +0,0 @@
|
||||
let removedCollections = [];
|
||||
|
||||
document.getElementById("add-collection-button").addEventListener("click", () => {
|
||||
const container = document.getElementById("collections-container");
|
||||
const currentCount = container.querySelectorAll(".draggable-collection").length;
|
||||
|
||||
const newId = `new-${Date.now()}`
|
||||
const collectionHtml = `
|
||||
<div class="draggable-collection"
|
||||
data-collection-id="${newId}"
|
||||
draggable="true"
|
||||
ondragover="dragOver(event)"
|
||||
ondragstart="dragStart(event)"
|
||||
ondragend="dragEnd()">
|
||||
<input type="text" class="collection-name" value="" required placeholder="Enter collection name" autocomplete="off" maxlength="60"><br>
|
||||
<div>0 threads, 0 posts</div>
|
||||
<button type="button" class="delete-button critical">Delete</button>
|
||||
</div>
|
||||
`;
|
||||
container.insertAdjacentHTML('beforeend', collectionHtml);
|
||||
})
|
||||
|
||||
document.addEventListener("click", e => {
|
||||
if (!e.target.classList.contains("delete-button")) {
|
||||
return;
|
||||
}
|
||||
const collectionDiv = e.target.closest(".draggable-collection");
|
||||
const collectionId = collectionDiv.dataset.collectionId;
|
||||
|
||||
if (!collectionId.startsWith("new-")) {
|
||||
removedCollections.push(collectionId);
|
||||
}
|
||||
|
||||
collectionDiv.remove();
|
||||
})
|
||||
|
||||
document.getElementById("save-button").addEventListener("click", async () => {
|
||||
const collections = [];
|
||||
const collectionDivs = document.querySelectorAll(".draggable-collection");
|
||||
let isValid = true;
|
||||
collectionDivs.forEach((collection, index) => {
|
||||
const collectionId = collection.dataset.collectionId;
|
||||
const nameInput = collection.querySelector(".collection-name");
|
||||
|
||||
if (!nameInput.reportValidity()) {
|
||||
isValid = false;
|
||||
return;
|
||||
}
|
||||
|
||||
collections.push({
|
||||
id: collectionId,
|
||||
name: nameInput.value,
|
||||
is_new: collectionId.startsWith("new-"),
|
||||
});
|
||||
})
|
||||
|
||||
if (!isValid) {
|
||||
return;
|
||||
}
|
||||
|
||||
const data = {
|
||||
collections: collections,
|
||||
removed_collections: removedCollections,
|
||||
};
|
||||
|
||||
try {
|
||||
const saveHref = document.getElementById('save-button').dataset.submitHref;
|
||||
const response = await fetch(saveHref, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify(data),
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
window.location.reload();
|
||||
} else {
|
||||
console.error("Error saving collections");
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error("Error saving collections: ", error);
|
||||
}
|
||||
})
|
||||
|
||||
// drag logic
|
||||
// https://codepen.io/crouchingtigerhiddenadam/pen/qKXgap
|
||||
|
||||
let selected = null;
|
||||
const container = document.getElementById("collections-container");
|
||||
function isBefore(el1, el2) {
|
||||
let cur;
|
||||
if (el2.parentNode === el1.parentNode) {
|
||||
for (cur = el1.previousSibling; cur; cur = cur.previousSibling) {
|
||||
if (cur === el2) return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function dragOver(e) {
|
||||
let target = e.target.closest(".draggable-collection")
|
||||
|
||||
if (!target || target === selected) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (isBefore(selected, target)) {
|
||||
container.insertBefore(selected, target)
|
||||
} else {
|
||||
container.insertBefore(selected, target.nextSibling)
|
||||
}
|
||||
}
|
||||
|
||||
function dragEnd() {
|
||||
if (!selected) return;
|
||||
|
||||
selected.classList.remove("dragged")
|
||||
selected = null;
|
||||
}
|
||||
|
||||
function dragStart(e) {
|
||||
e.dataTransfer.effectAllowed = 'move'
|
||||
e.dataTransfer.setData('text/plain', "")
|
||||
selected = e.target
|
||||
selected.classList.add("dragged")
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
// https://codepen.io/crouchingtigerhiddenadam/pen/qKXgap
|
||||
let selected = null;
|
||||
let container = document.getElementById("topics-container")
|
||||
|
||||
function isBefore(el1, el2) {
|
||||
let cur
|
||||
if (el2.parentNode === el1.parentNode) {
|
||||
for (cur = el1.previousSibling; cur; cur = cur.previousSibling) {
|
||||
if (cur === el2) return true
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function dragOver(e) {
|
||||
let target = e.target.closest(".draggable-topic")
|
||||
|
||||
if (!target || target === selected) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (isBefore(selected, target)) {
|
||||
container.insertBefore(selected, target)
|
||||
} else {
|
||||
container.insertBefore(selected, target.nextSibling)
|
||||
}
|
||||
}
|
||||
|
||||
function dragEnd() {
|
||||
if (!selected) return;
|
||||
|
||||
selected.classList.remove("dragged")
|
||||
selected = null;
|
||||
for (let i = 0; i < container.childElementCount - 1; i++) {
|
||||
let input = container.children[i].querySelector(".topic-input");
|
||||
input.value = i + 1;
|
||||
}
|
||||
}
|
||||
|
||||
function dragStart(e) {
|
||||
e.dataTransfer.effectAllowed = 'move'
|
||||
e.dataTransfer.setData('text/plain', null)
|
||||
selected = e.target
|
||||
selected.classList.add("dragged")
|
||||
}
|
||||
@@ -90,21 +90,16 @@ document.addEventListener("DOMContentLoaded", () => {
|
||||
minWidth: origMinWidth,
|
||||
minHeight: origMinHeight,
|
||||
} = getComputedStyle(img);
|
||||
console.log(img, img.naturalWidth, img.naturalHeight, origMinWidth, origMinHeight, origMaxWidth, origMaxHeight)
|
||||
if (img.naturalWidth < parseInt(origMinWidth)) {
|
||||
console.log(1)
|
||||
img.style.minWidth = img.naturalWidth + "px";
|
||||
}
|
||||
if (img.naturalHeight < parseInt(origMinHeight)) {
|
||||
console.log(2)
|
||||
img.style.minHeight = img.naturalHeight + "px";
|
||||
}
|
||||
if (img.naturalWidth < parseInt(origMaxWidth)) {
|
||||
console.log(3)
|
||||
img.style.maxWidth = img.naturalWidth + "px";
|
||||
}
|
||||
if (img.naturalHeight < parseInt(origMaxHeight)) {
|
||||
console.log(4)
|
||||
img.style.maxHeight = img.naturalHeight + "px";
|
||||
}
|
||||
}
|
||||
@@ -133,3 +128,108 @@ document.addEventListener("DOMContentLoaded", () => {
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
|
||||
{
|
||||
function isBefore(el1, el2) {
|
||||
if (el2.parentNode === el1.parentNode) {
|
||||
for (let cur = el1.previousSibling; cur; cur = cur.previousSibling) {
|
||||
if (cur === el2) return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
let draggedItem = null;
|
||||
|
||||
function sortableItemDragStart(e, item) {
|
||||
const box = item.getBoundingClientRect();
|
||||
const oX = e.clientX - box.left;
|
||||
const oY = e.clientY - box.top;
|
||||
draggedItem = item;
|
||||
item.classList.add('dragged');
|
||||
e.dataTransfer.setDragImage(item, oX, oY);
|
||||
e.dataTransfer.effectAllowed = 'move';
|
||||
}
|
||||
|
||||
function sortableItemDragEnd(e, item) {
|
||||
draggedItem = null;
|
||||
item.classList.remove('dragged');
|
||||
}
|
||||
|
||||
function sortableItemDragOver(e, item) {
|
||||
const target = e.target.closest('.sortable-item');
|
||||
if (!target || target === draggedItem) {
|
||||
return;
|
||||
}
|
||||
const inSameList = draggedItem.dataset.sortableListKey === target.dataset.sortableListKey;
|
||||
if (!inSameList) {
|
||||
return;
|
||||
}
|
||||
const targetList = draggedItem.closest('.sortable-list');
|
||||
if (isBefore(draggedItem, target)) {
|
||||
targetList.insertBefore(draggedItem, target);
|
||||
} else {
|
||||
targetList.insertBefore(draggedItem, target.nextSibling);
|
||||
}
|
||||
}
|
||||
|
||||
const listItemsHandled = new Map();
|
||||
|
||||
const getListItemsHandled = (list) => {
|
||||
return listItemsHandled.get(list) || new Set();
|
||||
}
|
||||
|
||||
function registerSortableList(list) {
|
||||
list.querySelectorAll('li:not(.immovable)').forEach(item => {
|
||||
const listItems = getListItemsHandled(list);
|
||||
listItems.add(item);
|
||||
listItemsHandled.set(list, listItems);
|
||||
const dragger = item.querySelector('.dragger');
|
||||
dragger.addEventListener('dragstart', e => {sortableItemDragStart(e, item)});
|
||||
dragger.addEventListener('dragend', e => {sortableItemDragEnd(e, item)});
|
||||
item.addEventListener('dragover', e => {sortableItemDragOver(e, item)});
|
||||
});
|
||||
|
||||
const obs = new MutationObserver(records => {
|
||||
for (const mutation of records) {
|
||||
mutation.addedNodes.forEach(node => {
|
||||
if (!(node instanceof HTMLElement)) {
|
||||
return;
|
||||
}
|
||||
if (!node.classList.contains('sortable-item')) {
|
||||
return;
|
||||
}
|
||||
const listItems = getListItemsHandled(list)
|
||||
if (listItems.has(node)) {
|
||||
return;
|
||||
}
|
||||
const dragger = node.querySelector('.dragger');
|
||||
dragger.addEventListener('dragstart', e => {sortableItemDragStart(e, node)});
|
||||
dragger.addEventListener('dragend', e => {sortableItemDragEnd(e, node)});
|
||||
node.addEventListener('dragover', e => {sortableItemDragOver(e, node)});
|
||||
listItems.add(node);
|
||||
listItemsHandled.set(list, listItems);
|
||||
});
|
||||
}
|
||||
});
|
||||
obs.observe(list, {childList: true});
|
||||
}
|
||||
|
||||
document.querySelectorAll('.sortable-list').forEach(registerSortableList);
|
||||
|
||||
listsObs = new MutationObserver(records => {
|
||||
for (const mutation of records) {
|
||||
mutation.addedNodes.forEach(node => {
|
||||
if (!(node instanceof HTMLElement)) {
|
||||
return;
|
||||
}
|
||||
if (!node.classList.contains('sortable-list')) {
|
||||
return;
|
||||
}
|
||||
registerSortableList(node);
|
||||
})
|
||||
}
|
||||
})
|
||||
listsObs.observe(document.body, {childList: true, subtree: true});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user