2017-06-19 14:49:24 +00:00
|
|
|
import { Application } from "./Application"
|
2017-06-21 16:44:20 +00:00
|
|
|
import { Diff } from "./Diff"
|
2017-06-20 10:41:26 +00:00
|
|
|
import { findAll } from "./utils"
|
|
|
|
import * as actions from "./actions"
|
2017-06-19 14:49:24 +00:00
|
|
|
|
|
|
|
export class AnimeNotifier {
|
|
|
|
app: Application
|
2017-06-24 14:17:38 +00:00
|
|
|
visibilityObserver: IntersectionObserver
|
2017-06-19 14:49:24 +00:00
|
|
|
|
|
|
|
constructor(app: Application) {
|
|
|
|
this.app = app
|
2017-06-24 14:28:16 +00:00
|
|
|
|
|
|
|
if("IntersectionObserver" in window) {
|
|
|
|
// Enable lazy load
|
|
|
|
this.visibilityObserver = new IntersectionObserver(
|
|
|
|
entries => {
|
|
|
|
for(let entry of entries) {
|
|
|
|
if(entry.intersectionRatio > 0) {
|
|
|
|
entry.target["became visible"]()
|
|
|
|
this.visibilityObserver.unobserve(entry.target)
|
|
|
|
}
|
2017-06-24 14:17:38 +00:00
|
|
|
}
|
2017-06-24 14:28:16 +00:00
|
|
|
},
|
|
|
|
{}
|
|
|
|
)
|
|
|
|
} else {
|
|
|
|
// Disable lazy load feature
|
|
|
|
this.visibilityObserver = {
|
|
|
|
disconnect: () => {},
|
|
|
|
observe: (elem: HTMLElement) => {
|
|
|
|
elem["became visible"]()
|
|
|
|
},
|
|
|
|
unobserve: (elem: HTMLElement) => {}
|
|
|
|
} as IntersectionObserver
|
|
|
|
}
|
2017-06-19 14:49:24 +00:00
|
|
|
}
|
|
|
|
|
2017-06-19 15:45:27 +00:00
|
|
|
onReadyStateChange() {
|
|
|
|
if(document.readyState !== "interactive") {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
this.run()
|
|
|
|
}
|
|
|
|
|
2017-06-19 14:49:24 +00:00
|
|
|
run() {
|
|
|
|
this.app.content = this.app.find("content")
|
|
|
|
this.app.loading = this.app.find("loading")
|
|
|
|
this.app.run()
|
|
|
|
}
|
2017-06-19 15:45:27 +00:00
|
|
|
|
2017-06-24 14:17:38 +00:00
|
|
|
onContentLoaded() {
|
|
|
|
this.visibilityObserver.disconnect()
|
|
|
|
|
|
|
|
// Update each of these asynchronously
|
2017-06-26 01:57:29 +00:00
|
|
|
Promise.resolve().then(() => this.mountMountables())
|
|
|
|
Promise.resolve().then(() => this.assignActions())
|
2017-06-24 14:17:38 +00:00
|
|
|
Promise.resolve().then(() => this.lazyLoadImages())
|
|
|
|
}
|
|
|
|
|
2017-06-21 16:44:20 +00:00
|
|
|
reloadContent() {
|
|
|
|
return fetch("/_" + this.app.currentPath, {
|
|
|
|
credentials: "same-origin"
|
|
|
|
})
|
|
|
|
.then(response => response.text())
|
|
|
|
.then(html => Diff.innerHTML(this.app.content, html))
|
2017-06-24 00:10:04 +00:00
|
|
|
.then(() => this.app.emit("DOMContentLoaded"))
|
2017-06-21 16:44:20 +00:00
|
|
|
}
|
|
|
|
|
2017-06-20 10:41:26 +00:00
|
|
|
loading(isLoading: boolean) {
|
|
|
|
if(isLoading) {
|
|
|
|
this.app.loading.classList.remove(this.app.fadeOutClass)
|
|
|
|
} else {
|
|
|
|
this.app.loading.classList.add(this.app.fadeOutClass)
|
|
|
|
}
|
|
|
|
}
|
2017-06-21 12:00:52 +00:00
|
|
|
|
2017-06-26 01:57:29 +00:00
|
|
|
assignActions() {
|
2017-06-21 13:29:06 +00:00
|
|
|
for(let element of findAll("action")) {
|
2017-06-24 00:10:04 +00:00
|
|
|
if(element["action assigned"]) {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2017-06-20 10:41:26 +00:00
|
|
|
let actionName = element.dataset.action
|
|
|
|
|
2017-06-20 20:54:45 +00:00
|
|
|
element.addEventListener(element.dataset.trigger, e => {
|
|
|
|
actions[actionName](this, element, e)
|
2017-06-26 01:57:29 +00:00
|
|
|
|
|
|
|
e.stopPropagation()
|
|
|
|
e.preventDefault()
|
2017-06-20 20:54:45 +00:00
|
|
|
})
|
|
|
|
|
2017-06-24 15:38:10 +00:00
|
|
|
// Use "action assigned" flag instead of removing the class.
|
|
|
|
// This will make sure that DOM diffs which restore the class name
|
|
|
|
// will not assign the action multiple times to the same element.
|
2017-06-24 00:10:04 +00:00
|
|
|
element["action assigned"] = true
|
2017-06-20 10:41:26 +00:00
|
|
|
}
|
2017-06-19 15:45:27 +00:00
|
|
|
}
|
|
|
|
|
2017-06-24 14:17:38 +00:00
|
|
|
lazyLoadImages() {
|
2017-06-24 15:38:10 +00:00
|
|
|
for(let element of findAll("lazy")) {
|
2017-06-24 14:17:38 +00:00
|
|
|
this.lazyLoadImage(element as HTMLImageElement)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
lazyLoadImage(img: HTMLImageElement) {
|
|
|
|
// Once the image becomes visible, load it
|
|
|
|
img["became visible"] = () => {
|
2017-06-24 15:38:10 +00:00
|
|
|
img.src = img.dataset.src
|
2017-06-19 15:45:27 +00:00
|
|
|
|
|
|
|
if(img.naturalWidth === 0) {
|
|
|
|
img.onload = function() {
|
2017-06-24 15:38:10 +00:00
|
|
|
this.classList.add("image-found")
|
2017-06-19 15:45:27 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
img.onerror = function() {
|
2017-06-24 15:38:10 +00:00
|
|
|
this.classList.add("image-not-found")
|
2017-06-19 15:45:27 +00:00
|
|
|
}
|
|
|
|
} else {
|
2017-06-24 15:38:10 +00:00
|
|
|
img.classList.add("image-found")
|
2017-06-19 15:45:27 +00:00
|
|
|
}
|
|
|
|
}
|
2017-06-24 14:17:38 +00:00
|
|
|
|
|
|
|
this.visibilityObserver.observe(img)
|
2017-06-19 15:45:27 +00:00
|
|
|
}
|
2017-06-20 12:16:23 +00:00
|
|
|
|
2017-06-26 01:57:29 +00:00
|
|
|
mountMountables() {
|
|
|
|
this.modifyDelayed("mountable", element => element.classList.add("mounted"))
|
|
|
|
}
|
|
|
|
|
|
|
|
unmountMountables() {
|
2017-06-26 10:32:07 +00:00
|
|
|
for(let element of findAll("mountable")) {
|
2017-06-26 01:57:29 +00:00
|
|
|
element.classList.remove("mounted")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
modifyDelayed(className: string, func: (element: HTMLElement) => void) {
|
2017-06-20 18:13:04 +00:00
|
|
|
const delay = 20
|
2017-06-26 13:17:53 +00:00
|
|
|
const maxDelay = 500
|
2017-06-20 18:13:04 +00:00
|
|
|
|
|
|
|
let time = 0
|
|
|
|
|
2017-06-26 01:57:29 +00:00
|
|
|
for(let element of findAll(className)) {
|
2017-06-20 18:13:04 +00:00
|
|
|
time += delay
|
|
|
|
|
|
|
|
if(time > maxDelay) {
|
2017-06-26 13:17:53 +00:00
|
|
|
func(element)
|
|
|
|
} else {
|
|
|
|
setTimeout(() => {
|
|
|
|
window.requestAnimationFrame(() => func(element))
|
|
|
|
}, time)
|
2017-06-20 18:13:04 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-06-21 12:00:52 +00:00
|
|
|
onPopState(e: PopStateEvent) {
|
|
|
|
if(e.state) {
|
|
|
|
this.app.load(e.state, {
|
|
|
|
addToHistory: false
|
|
|
|
})
|
|
|
|
} else if(this.app.currentPath !== this.app.originalPath) {
|
|
|
|
this.app.load(this.app.originalPath, {
|
|
|
|
addToHistory: false
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
onKeyDown(e: KeyboardEvent) {
|
2017-06-24 14:41:22 +00:00
|
|
|
// Ignore hotkeys on input elements
|
|
|
|
switch(document.activeElement.tagName) {
|
|
|
|
case "INPUT":
|
|
|
|
case "TEXTAREA":
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2017-06-24 14:31:54 +00:00
|
|
|
// F = Search
|
|
|
|
if(e.keyCode == 70) {
|
2017-06-21 12:00:52 +00:00
|
|
|
let search = this.app.find("search") as HTMLInputElement
|
|
|
|
|
|
|
|
search.focus()
|
|
|
|
search.select()
|
|
|
|
|
|
|
|
e.preventDefault()
|
|
|
|
e.stopPropagation()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-06-20 12:16:23 +00:00
|
|
|
// onResize(e: UIEvent) {
|
|
|
|
// let hasScrollbar = this.app.content.clientHeight === this.app.content.scrollHeight
|
|
|
|
|
|
|
|
// if(hasScrollbar) {
|
|
|
|
// this.app.content.classList.add("has-scrollbar")
|
|
|
|
// } else {
|
|
|
|
// this.app.content.classList.remove("has-scrollbar")
|
|
|
|
// }
|
|
|
|
// }
|
2017-06-19 14:49:24 +00:00
|
|
|
}
|