atlas/web/_js/main/time.js

274 lines
9.1 KiB
JavaScript
Raw Normal View History

/*!
* The 2022 r/place Atlas
* Copyright (c) 2017 Roland Rytz <roland@draemm.li>
* Copyright (c) 2022 Place Atlas contributors
* Licensed under AGPL-3.0 (https://place-atlas.stefanocoding.me/license.txt)
*/
2022-04-16 14:51:51 +02:00
const codeReference = {}
const variantsEl = document.getElementById("variants")
2022-04-16 14:26:31 +02:00
for (const variation in variationsConfig) {
codeReference[variationsConfig[variation].code] = variation
const optionEl = document.createElement('option')
optionEl.value = variation
optionEl.textContent = variationsConfig[variation].name
variantsEl.appendChild(optionEl)
2022-04-16 14:51:51 +02:00
}
2022-05-06 09:41:22 +02:00
const timelineSlider = document.getElementById("timeControlsSlider")
const timelineList = document.getElementById("timeControlsList")
const tooltip = document.getElementById("timeControlsTooltip")
const image = document.getElementById("image")
let abortController = new AbortController()
let currentUpdateIndex = 0
let updateTimeout = setTimeout(null, 0)
let tooltipDelayHide = setTimeout(null, 0)
2022-04-05 20:17:39 +02:00
let currentVariation = "default"
2022-04-16 14:26:31 +02:00
const defaultPeriod = variationsConfig[currentVariation].default
const defaultVariation = currentVariation
let currentPeriod = defaultPeriod
window.currentPeriod = currentPeriod
window.currentVariation = currentVariation
2022-04-08 01:11:29 +02:00
2022-04-05 20:17:39 +02:00
// SETUP
if (variationsConfig[currentVariation].versions.length === 1) bottomBar.classList.add('no-time-slider')
2022-05-06 09:41:22 +02:00
timelineSlider.max = variationsConfig[currentVariation].versions.length - 1
timelineSlider.value = currentPeriod
timelineList.children[0].value = defaultPeriod
2022-04-05 20:17:39 +02:00
timelineSlider.addEventListener("input", (e) => timelineParser(e.target.value))
timelineSlider.addEventListener("wheel", function (e) {
if (e.deltaY < 0) {
this.valueAsNumber += 1;
timelineParser(this.value)
} else {
this.value -= 1;
timelineParser(this.value)
}
e.stopPropagation();
}, { passive: true })
function timelineParser(value) {
updateTooltip(parseInt(value), currentVariation)
clearTimeout(updateTimeout)
updateTimeout = setTimeout(() => {
updateTime(parseInt(timelineSlider.value), currentVariation)
setTimeout(() => {
2023-03-17 18:30:33 +01:00
if (timelineSlider.value !== currentPeriod && abortController.signal.aborted) {
2022-04-17 04:53:43 +02:00
updateTime(parseInt(timelineSlider.value), currentVariation)
}
}, 50)
}, 25)
}
2022-04-05 20:17:39 +02:00
2022-04-16 14:51:51 +02:00
variantsEl.addEventListener("input", (event) => {
updateTime(-1, event.target.value)
2022-04-10 09:03:08 +02:00
})
2022-04-05 20:17:39 +02:00
const dispatchTimeUpdateEvent = (period = currentPeriod, variation = currentVariation, atlas = atlas) => {
const timeUpdateEvent = new CustomEvent('timeupdate', {
detail: {
period: period,
variation: variation,
periodString: formatPeriod(period, period, variation),
atlas: atlas
}
2022-05-06 09:41:22 +02:00
})
document.dispatchEvent(timeUpdateEvent)
2022-04-10 11:11:34 +02:00
}
async function updateBackground(newPeriod = currentPeriod, newVariation = currentVariation) {
abortController.abort()
abortController = new AbortController()
currentUpdateIndex++
const myUpdateIndex = currentUpdateIndex
const variationConfig = variationsConfig[newVariation]
2022-04-24 09:51:41 +02:00
variantsEl.value = currentVariation
2022-05-11 08:09:17 +02:00
if (variationConfig.icon) {
variantsEl.previousElementSibling.innerHTML = variationConfig.icon
variantsEl.previousElementSibling.classList.remove('d-none')
variantsEl.parentElement.classList.add('input-group')
} else {
variantsEl.previousElementSibling.innerHTML = ""
variantsEl.previousElementSibling.classList.add('d-none')
variantsEl.parentElement.classList.remove('input-group')
}
2022-05-11 08:16:29 +02:00
2022-05-06 09:41:22 +02:00
const configObject = variationConfig.versions[currentPeriod]
if (typeof configObject.url === "string") {
image.src = configObject.url
} else {
const canvas = document.createElement('canvas')
const context = canvas.getContext('2d')
2023-03-19 07:03:49 +01:00
context.canvas.width = canvasSize.x
context.canvas.height = canvasSize.y
for await (const url of configObject.url) {
const imageLayer = new Image()
await new Promise(resolve => {
imageLayer.onload = () => {
context.drawImage(imageLayer, 0, 0)
resolve()
}
imageLayer.src = url
})
}
if (currentUpdateIndex !== myUpdateIndex) return [configObject, newPeriod, newVariation]
const blob = await new Promise(resolve => canvas.toBlob(resolve))
image.src = URL.createObjectURL(blob)
}
}
2023-03-26 13:58:17 +02:00
async function updateTime(newPeriod = currentPeriod, newVariation = currentVariation, forceLoad = false) {
document.body.dataset.canvasLoading = ""
2023-03-26 13:58:17 +02:00
const oldPeriod = currentPeriod
const oldVariation = currentVariation
if (!variationsConfig[newVariation]) newVariation = defaultVariation
const variationConfig = variationsConfig[newVariation]
if (newPeriod < 0) newPeriod = 0
else if (newPeriod > variationConfig.versions.length - 1) newPeriod = variationConfig.versions.length - 1
currentPeriod = newPeriod
2023-03-26 13:58:17 +02:00
currentVariation = newVariation
if (oldVariation !== newVariation) {
2022-05-06 09:41:22 +02:00
timelineSlider.max = variationConfig.versions.length - 1
2023-03-26 13:58:17 +02:00
if (!forceLoad) {
2022-05-06 09:41:22 +02:00
currentPeriod = variationConfig.default
newPeriod = currentPeriod
}
if (variationConfig.versions.length === 1) bottomBar.classList.add('no-time-slider')
else bottomBar.classList.remove('no-time-slider')
}
timelineSlider.value = currentPeriod
updateTooltip(newPeriod, newVariation)
await updateBackground(newPeriod, newVariation)
atlas = []
for (const atlasIndex in atlasAll) {
2022-04-23 15:29:22 +02:00
let chosenIndex
const validPeriods2 = Object.keys(atlasAll[atlasIndex].path)
for (const i in validPeriods2) {
const validPeriods = validPeriods2[i].split(', ')
for (const j in validPeriods) {
const [start, end, variation] = parsePeriod(validPeriods[j])
if (isOnPeriod(start, end, variation, newPeriod, newVariation)) {
chosenIndex = i
break
}
}
if (chosenIndex !== undefined) break
}
if (chosenIndex === undefined) continue
2022-04-23 15:29:22 +02:00
const pathChosen = Object.values(atlasAll[atlasIndex].path)[chosenIndex]
const centerChosen = Object.values(atlasAll[atlasIndex].center)[chosenIndex]
if (pathChosen === undefined) continue
atlas.push({
...atlasAll[atlasIndex],
path: pathChosen,
center: centerChosen,
})
}
dispatchTimeUpdateEvent(newPeriod, newVariation, atlas)
delete document.body.dataset.canvasLoading
tooltip.dataset.forceVisible = ""
clearTimeout(tooltipDelayHide)
tooltipDelayHide = setTimeout(() => {
delete tooltip.dataset.forceVisible
}, 1000)
}
2023-03-26 13:58:17 +02:00
function updateTooltip(period, variation) {
const configObject = variationsConfig[variation].versions[period]
// If timestap is a number return a UTC formatted date otherwise use exact timestap label
if (typeof configObject.timestamp === "number") tooltip.querySelector('div').textContent = new Date(configObject.timestamp * 1000).toUTCString()
2022-05-06 09:41:22 +02:00
else tooltip.querySelector('div').textContent = configObject.timestamp
// Clamps position of tooltip to prevent from going off screen
2022-05-06 09:41:22 +02:00
const timelineSliderRect = timelineSlider.getBoundingClientRect()
let min = -timelineSliderRect.left + 12
let max = (window.innerWidth - tooltip.offsetWidth) - timelineSliderRect.left + 4
tooltip.style.left = Math.min(Math.max((timelineSlider.offsetWidth) * (timelineSlider.value) / (timelineSlider.max) - tooltip.offsetWidth / 2, min), max) + "px"
2022-04-10 09:03:08 +02:00
}
tooltip.parentElement.addEventListener('mouseenter', () => updateTooltip(parseInt(timelineSlider.value), currentVariation))
2022-04-10 09:03:08 +02:00
window.addEventListener('resize', () => updateTooltip(parseInt(timelineSlider.value), currentVariation))
2022-04-10 09:03:08 +02:00
2022-04-16 14:51:51 +02:00
function isOnPeriod(start, end, variation, currentPeriod, currentVariation) {
2022-05-06 09:36:48 +02:00
if (start > end) [start, end] = [end, start]
return currentPeriod >= start && currentPeriod <= end && variation === currentVariation
2022-04-14 16:03:17 +02:00
}
function parsePeriod(periodString) {
let variation = defaultVariation
2022-04-14 16:03:17 +02:00
periodString = periodString + ""
if (periodString.split(':').length > 1) {
const split = periodString.split(':')
variation = codeReference[split[0]]
periodString = split[1]
}
2022-04-14 16:03:17 +02:00
if (periodString.search('-') + 1) {
2022-05-05 11:06:57 +02:00
let [start, end] = periodString.split('-').map(i => parseInt(i))
if (start > end) [start, end] = [end, start]
2022-04-16 14:51:51 +02:00
return [start, end, variation]
} else if (codeReference[periodString]) {
variation = codeReference[periodString]
const defaultPeriod = variationsConfig[variation].default
return [defaultPeriod, defaultPeriod, variation]
2022-04-14 16:03:17 +02:00
} else {
2022-04-16 14:26:31 +02:00
const periodNew = parseInt(periodString)
2022-04-16 14:51:51 +02:00
return [periodNew, periodNew, variation]
2022-04-14 16:03:17 +02:00
}
}
function formatPeriod(start, end, variation) {
start ??= currentPeriod
end ??= currentPeriod
variation ??= currentVariation
2022-04-30 06:28:20 +02:00
let periodString, variationString
variationString = variationsConfig[variation].code
2022-05-06 09:14:57 +02:00
if (start > end) [start, end] = [end, start]
if (start === end) {
2022-04-30 06:28:20 +02:00
if (start === variationsConfig[variation].default && variation !== defaultVariation) {
periodString = ""
2022-05-06 09:41:22 +02:00
}
else periodString = start
}
else periodString = start + "-" + end
2022-04-30 06:28:20 +02:00
if (periodString && variationString) return variationsConfig[variation].code + ":" + periodString
if (variationString) return variationString
return periodString
}
function formatHash(id, start, end, variation) {
start ??= currentPeriod
end ??= currentPeriod
variation ??= currentVariation
const result = [id]
const targetPeriod = formatPeriod(start, end, variation)
if (targetPeriod && targetPeriod !== defaultPeriod) result.push(targetPeriod)
if (!result.some(el => el || el === 0)) return ''
return '#' + result.join('/')
}