Added drag & drop for anime list status tabs (closes #56)

This commit is contained in:
Eduard Urbach 2018-11-12 19:48:54 +09:00
parent 9e3ec7656d
commit a18aa0095a
3 changed files with 161 additions and 59 deletions

View File

@ -8,7 +8,7 @@ component AnimeList(animeListItems []*arn.AnimeListItem, nextIndex int, viewUser
component AnimeListScrollable(animeListItems []*arn.AnimeListItem, viewUser *arn.User, user *arn.User) component AnimeListScrollable(animeListItems []*arn.AnimeListItem, viewUser *arn.User, user *arn.User)
each item in animeListItems 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 .anime-list-item-image-container
a.anime-list-item-image-link(href=item.Anime().Link()) 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)) 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))

View File

@ -1,4 +1,4 @@
component Tab(label string, icon string, url string) 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) Icon(icon)
span.tab-text= label span.tab-text= label

View File

@ -330,88 +330,190 @@ export default class AnimeNotifier {
} }
dragAndDrop() { dragAndDrop() {
for(let element of findAll("inventory-slot")) { if(location.pathname.includes("/animelist/")) {
// Skip elements that have their event listeners attached already for(let element of findAll("anime-list-item")) {
if(element["listeners-attached"]) { // Skip elements that have their event listeners attached already
continue 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 => { for(let element of findAll("tab")) {
if(!element.draggable) { // Skip elements that have their event listeners attached already
return if(element["drop-listeners-attached"]) {
continue
} }
e.dataTransfer.setData("text", element.dataset.index) element.addEventListener("drop", async e => {
}, false) let toElement = e.toElement as HTMLElement
element.addEventListener("dblclick", e => { // Find tab element
if(!element.draggable) { while(toElement && !toElement.classList.contains("tab")) {
return 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") { e.dataTransfer.setData("text", element.dataset.index)
return this.statusMessage.showError(itemName + " is not a consumable item.") }, false)
}
let apiEndpoint = this.findAPIEndpoint(element) element.addEventListener("dblclick", e => {
if(!element.draggable) {
return
}
this.post(apiEndpoint + "/use/" + element.dataset.index, "") let itemName = element.getAttribute("aria-label")
.then(() => this.reloadContent())
.then(() => this.statusMessage.showInfo(`You used ${itemName}.`))
.catch(err => this.statusMessage.showError(err))
}, false)
element.addEventListener("dragenter", e => { if(element.dataset.consumable !== "true") {
element.classList.add("drag-enter") return this.statusMessage.showError(itemName + " is not a consumable item.")
}, false) }
element.addEventListener("dragleave", e => { let apiEndpoint = this.findAPIEndpoint(element)
element.classList.remove("drag-enter")
}, false)
element.addEventListener("dragover", e => { this.post(apiEndpoint + "/use/" + element.dataset.index, "")
e.preventDefault() .then(() => this.reloadContent())
}, false) .then(() => this.statusMessage.showInfo(`You used ${itemName}.`))
.catch(err => this.statusMessage.showError(err))
}, false)
element.addEventListener("drop", e => { element.addEventListener("dragenter", e => {
let toElement = e.toElement as HTMLElement element.classList.add("drag-enter")
toElement.classList.remove("drag-enter") }, false)
e.stopPropagation() element.addEventListener("dragleave", e => {
e.preventDefault() element.classList.remove("drag-enter")
}, false)
let inventory = e.toElement.parentElement element.addEventListener("dragover", e => {
let fromIndex = e.dataTransfer.getData("text") e.preventDefault()
}, false)
if(!fromIndex) { element.addEventListener("drop", e => {
return 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) { if(!fromIndex) {
return return
} }
// Swap in database let fromElement = inventory.childNodes[fromIndex] as HTMLElement
let apiEndpoint = this.findAPIEndpoint(inventory)
this.post(apiEndpoint + "/swap/" + fromIndex + "/" + toIndex, "") let toIndex = toElement.dataset.index
.catch(err => this.statusMessage.showError(err))
// Swap in UI if(fromElement === toElement || fromIndex === toIndex) {
swapElements(fromElement, toElement) return
}
fromElement.dataset.index = toIndex // Swap in database
toElement.dataset.index = fromIndex let apiEndpoint = this.findAPIEndpoint(inventory)
}, false)
// Prevent re-attaching the same listeners this.post(apiEndpoint + "/swap/" + fromIndex + "/" + toIndex, "")
element["listeners-attached"] = true .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
}
} }
} }