From a18aa0095a8ee54cdb7ba7719eb36a7bddf08dc9 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Mon, 12 Nov 2018 19:48:54 +0900 Subject: [PATCH] Added drag & drop for anime list status tabs (closes #56) --- mixins/AnimeList.pixy | 2 +- mixins/Tab.pixy | 2 +- scripts/AnimeNotifier.ts | 216 ++++++++++++++++++++++++++++----------- 3 files changed, 161 insertions(+), 59 deletions(-) diff --git a/mixins/AnimeList.pixy b/mixins/AnimeList.pixy index 51511034..53ed5097 100644 --- a/mixins/AnimeList.pixy +++ b/mixins/AnimeList.pixy @@ -8,7 +8,7 @@ component AnimeList(animeListItems []*arn.AnimeListItem, nextIndex int, viewUser component AnimeListScrollable(animeListItems []*arn.AnimeListItem, viewUser *arn.User, user *arn.User) each item in animeListItems - .anime-list-item.mountable(title=item.Notes, data-api="/api/animelist/" + viewUser.ID + "/field/Items[AnimeID=\"" + item.AnimeID + "\"]") + .anime-list-item.mountable(title=item.Notes, data-api="/api/animelist/" + viewUser.ID + "/field/Items[AnimeID=\"" + item.AnimeID + "\"]", draggable="true") .anime-list-item-image-container a.anime-list-item-image-link(href=item.Anime().Link()) img.anime-list-item-image.lazy(data-src=item.Anime().ImageLink("small"), data-webp="true", data-color=item.Anime().AverageColor(), alt=item.Anime().Title.ByUser(user)) diff --git a/mixins/Tab.pixy b/mixins/Tab.pixy index c4582768..9b1faac4 100644 --- a/mixins/Tab.pixy +++ b/mixins/Tab.pixy @@ -1,4 +1,4 @@ component Tab(label string, icon string, url string) - a.tab.action(href=url, data-action="diff", data-trigger="click", aria-label=label) + a.tab.action(href=url, data-action="diff", data-trigger="click", aria-label=label, dropzone="move") Icon(icon) span.tab-text= label \ No newline at end of file diff --git a/scripts/AnimeNotifier.ts b/scripts/AnimeNotifier.ts index 2d58b5f1..63d4521a 100644 --- a/scripts/AnimeNotifier.ts +++ b/scripts/AnimeNotifier.ts @@ -330,88 +330,190 @@ export default class AnimeNotifier { } dragAndDrop() { - for(let element of findAll("inventory-slot")) { - // Skip elements that have their event listeners attached already - if(element["listeners-attached"]) { - continue + if(location.pathname.includes("/animelist/")) { + for(let element of findAll("anime-list-item")) { + // Skip elements that have their event listeners attached already + if(element["drag-listeners-attached"]) { + continue + } + + element.addEventListener("dragstart", e => { + if(!element.draggable) { + return + } + + let image = element.getElementsByClassName("anime-list-item-image")[0] + e.dataTransfer.setDragImage(image, 0, 0) + + let name = element.getElementsByClassName("anime-list-item-name")[0] + + e.dataTransfer.setData("text/plain", JSON.stringify({ + api: element.dataset.api, + animeTitle: name.textContent + })) + e.dataTransfer.effectAllowed = "move" + }, false) + + // Prevent re-attaching the same listeners + element["drag-listeners-attached"] = true } - element.addEventListener("dragstart", e => { - if(!element.draggable) { - return + for(let element of findAll("tab")) { + // Skip elements that have their event listeners attached already + if(element["drop-listeners-attached"]) { + continue } - e.dataTransfer.setData("text", element.dataset.index) - }, false) + element.addEventListener("drop", async e => { + let toElement = e.toElement as HTMLElement - element.addEventListener("dblclick", e => { - if(!element.draggable) { - return + // Find tab element + while(toElement && !toElement.classList.contains("tab")) { + toElement = toElement.parentElement + } + + // Ignore a drop on the current status tab + if(!toElement || toElement.classList.contains("active")) { + return + } + + let data = e.dataTransfer.getData("text/plain") + let json = null + + try { + json = JSON.parse(data) + } catch { + return + } + + if(!json || !json.api) { + return + } + + e.stopPropagation() + e.preventDefault() + + let tabText = toElement.textContent + let newStatus = tabText.toLowerCase() + + if(newStatus === "on hold") { + newStatus = "hold" + } + + try { + await this.post(json.api, { + Status: newStatus + }) + await this.reloadContent() + + this.statusMessage.showInfo(`Moved "${json.animeTitle}" to "${tabText}".`) + } catch(err) { + this.statusMessage.showError(err) + } + + }, false) + + element.addEventListener("dragenter", e => { + e.preventDefault() + }, false) + + element.addEventListener("dragleave", e => { + e.preventDefault() + }, false) + + element.addEventListener("dragover", e => { + e.preventDefault() + }, false) + + // Prevent re-attaching the same listeners + element["drop-listeners-attached"] = true + } + } + + if(location.pathname.startsWith("/inventory")) { + for(let element of findAll("inventory-slot")) { + // Skip elements that have their event listeners attached already + if(element["drag-listeners-attached"]) { + continue } - let itemName = element.getAttribute("aria-label") + element.addEventListener("dragstart", e => { + if(!element.draggable) { + return + } - if(element.dataset.consumable !== "true") { - return this.statusMessage.showError(itemName + " is not a consumable item.") - } + e.dataTransfer.setData("text", element.dataset.index) + }, false) - let apiEndpoint = this.findAPIEndpoint(element) + element.addEventListener("dblclick", e => { + if(!element.draggable) { + return + } - this.post(apiEndpoint + "/use/" + element.dataset.index, "") - .then(() => this.reloadContent()) - .then(() => this.statusMessage.showInfo(`You used ${itemName}.`)) - .catch(err => this.statusMessage.showError(err)) - }, false) + let itemName = element.getAttribute("aria-label") - element.addEventListener("dragenter", e => { - element.classList.add("drag-enter") - }, false) + if(element.dataset.consumable !== "true") { + return this.statusMessage.showError(itemName + " is not a consumable item.") + } - element.addEventListener("dragleave", e => { - element.classList.remove("drag-enter") - }, false) + let apiEndpoint = this.findAPIEndpoint(element) - element.addEventListener("dragover", e => { - e.preventDefault() - }, false) + this.post(apiEndpoint + "/use/" + element.dataset.index, "") + .then(() => this.reloadContent()) + .then(() => this.statusMessage.showInfo(`You used ${itemName}.`)) + .catch(err => this.statusMessage.showError(err)) + }, false) - element.addEventListener("drop", e => { - let toElement = e.toElement as HTMLElement - toElement.classList.remove("drag-enter") + element.addEventListener("dragenter", e => { + element.classList.add("drag-enter") + }, false) - e.stopPropagation() - e.preventDefault() + element.addEventListener("dragleave", e => { + element.classList.remove("drag-enter") + }, false) - let inventory = e.toElement.parentElement - let fromIndex = e.dataTransfer.getData("text") + element.addEventListener("dragover", e => { + e.preventDefault() + }, false) - if(!fromIndex) { - return - } + element.addEventListener("drop", e => { + let toElement = e.toElement as HTMLElement + toElement.classList.remove("drag-enter") - let fromElement = inventory.childNodes[fromIndex] as HTMLElement + e.stopPropagation() + e.preventDefault() - let toIndex = toElement.dataset.index + let inventory = e.toElement.parentElement + let fromIndex = e.dataTransfer.getData("text") - if(fromElement === toElement || fromIndex === toIndex) { - return - } + if(!fromIndex) { + return + } - // Swap in database - let apiEndpoint = this.findAPIEndpoint(inventory) + let fromElement = inventory.childNodes[fromIndex] as HTMLElement - this.post(apiEndpoint + "/swap/" + fromIndex + "/" + toIndex, "") - .catch(err => this.statusMessage.showError(err)) + let toIndex = toElement.dataset.index - // Swap in UI - swapElements(fromElement, toElement) + if(fromElement === toElement || fromIndex === toIndex) { + return + } - fromElement.dataset.index = toIndex - toElement.dataset.index = fromIndex - }, false) + // Swap in database + let apiEndpoint = this.findAPIEndpoint(inventory) - // Prevent re-attaching the same listeners - element["listeners-attached"] = true + this.post(apiEndpoint + "/swap/" + fromIndex + "/" + toIndex, "") + .catch(err => this.statusMessage.showError(err)) + + // Swap in UI + swapElements(fromElement, toElement) + + fromElement.dataset.index = toIndex + toElement.dataset.index = fromIndex + }, false) + + // Prevent re-attaching the same listeners + element["drag-listeners-attached"] = true + } } }