2023-03-22 16:38:18 +01:00
|
|
|
/*!
|
|
|
|
* The 2022 r/place Atlas
|
|
|
|
* Copyright (c) 2017 Roland Rytz <roland@draemm.li>
|
2024-01-12 10:27:25 +01:00
|
|
|
* Copyright (c) 2022 Place Atlas Initiative and contributors
|
2023-07-20 07:38:27 +02:00
|
|
|
* Licensed under AGPL-3.0 (https://2022.place-atlas.stefanocoding.me/license.txt)
|
2023-03-22 16:38:18 +01:00
|
|
|
*/
|
2022-04-09 04:45:47 +02:00
|
|
|
|
2022-04-16 14:51:51 +02:00
|
|
|
const codeReference = {}
|
2023-04-01 09:55:24 +02:00
|
|
|
let canvasUrl = ""
|
2022-04-16 14:51:51 +02:00
|
|
|
|
|
|
|
const variantsEl = document.getElementById("variants")
|
|
|
|
|
2022-04-16 14:26:31 +02:00
|
|
|
for (const variation in variationsConfig) {
|
2022-04-16 14:44:50 +02:00
|
|
|
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")
|
2022-04-16 04:48:12 +02:00
|
|
|
let abortController = new AbortController()
|
|
|
|
let currentUpdateIndex = 0
|
|
|
|
let updateTimeout = setTimeout(null, 0)
|
2022-04-29 06:29:58 +02:00
|
|
|
let tooltipDelayHide = setTimeout(null, 0)
|
2022-04-05 20:17:39 +02:00
|
|
|
|
2023-04-06 16:38:44 +02:00
|
|
|
let currentVariation = defaultVariation
|
2022-04-16 07:58:26 +02:00
|
|
|
let currentPeriod = defaultPeriod
|
2022-04-15 18:33:18 +02:00
|
|
|
window.currentVariation = currentVariation
|
2023-04-06 16:38:44 +02:00
|
|
|
window.currentPeriod = currentPeriod
|
2022-04-08 01:11:29 +02:00
|
|
|
|
2024-01-12 10:27:25 +01:00
|
|
|
let atlasDisplay = {}
|
|
|
|
window.atlasDisplay = atlasDisplay
|
|
|
|
|
|
|
|
const additionalLayers = []
|
|
|
|
const additionalLayerCanvas = document.createElement('canvas')
|
|
|
|
|
2022-04-05 20:17:39 +02:00
|
|
|
// SETUP
|
2023-03-21 13:31:59 +01:00
|
|
|
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
|
|
|
|
2023-04-07 11:44:33 +02:00
|
|
|
timelineSlider.addEventListener("input", e => timelineParser(e.target.value))
|
2022-05-10 08:03:23 +02:00
|
|
|
|
2023-04-07 11:44:33 +02:00
|
|
|
timelineSlider.addEventListener("wheel", e => {
|
2023-04-07 18:01:51 +02:00
|
|
|
if (e.deltaY < 0) e.target.valueAsNumber += 1
|
|
|
|
else e.target.value -= 1
|
|
|
|
timelineParser(e.target.value)
|
|
|
|
e.stopPropagation()
|
2022-05-10 08:03:23 +02:00
|
|
|
}, { passive: true })
|
|
|
|
|
|
|
|
function timelineParser(value) {
|
|
|
|
updateTooltip(parseInt(value), currentVariation)
|
2022-04-16 18:05:44 +02:00
|
|
|
clearTimeout(updateTimeout)
|
|
|
|
updateTimeout = setTimeout(() => {
|
|
|
|
updateTime(parseInt(timelineSlider.value), currentVariation)
|
2022-04-16 16:10:39 +02:00
|
|
|
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)
|
|
|
|
}
|
2022-04-16 18:05:44 +02:00
|
|
|
}, 50)
|
|
|
|
}, 25)
|
2022-05-10 08:03:23 +02:00
|
|
|
}
|
2022-04-05 20:17:39 +02:00
|
|
|
|
2023-04-07 11:44:33 +02:00
|
|
|
variantsEl.addEventListener("input", event => {
|
2022-04-29 08:41:25 +02:00
|
|
|
updateTime(-1, event.target.value)
|
2022-04-10 09:03:08 +02:00
|
|
|
})
|
2022-04-05 20:17:39 +02:00
|
|
|
|
2023-03-19 05:55:12 +01:00
|
|
|
const dispatchTimeUpdateEvent = (period = currentPeriod, variation = currentVariation, atlas = atlas) => {
|
2022-04-16 14:44:50 +02:00
|
|
|
const timeUpdateEvent = new CustomEvent('timeupdate', {
|
|
|
|
detail: {
|
|
|
|
period: period,
|
2023-03-19 05:55:12 +01:00
|
|
|
variation: variation,
|
2024-01-12 10:27:25 +01:00
|
|
|
periodString: formatPeriod(period, null, variation),
|
2022-04-16 14:44:50 +02:00
|
|
|
atlas: atlas
|
|
|
|
}
|
2022-05-06 09:41:22 +02:00
|
|
|
})
|
|
|
|
document.dispatchEvent(timeUpdateEvent)
|
2022-04-10 11:11:34 +02:00
|
|
|
}
|
|
|
|
|
2022-04-15 18:33:18 +02:00
|
|
|
async function updateBackground(newPeriod = currentPeriod, newVariation = currentVariation) {
|
2022-04-16 14:44:50 +02:00
|
|
|
abortController.abort()
|
2024-01-12 10:27:25 +01:00
|
|
|
const myAbortController = new AbortController()
|
2023-07-29 19:05:29 +02:00
|
|
|
abortController = myAbortController
|
2022-04-16 14:44:50 +02:00
|
|
|
currentUpdateIndex++
|
|
|
|
const myUpdateIndex = currentUpdateIndex
|
|
|
|
const variationConfig = variationsConfig[newVariation]
|
2022-04-24 09:51:41 +02:00
|
|
|
|
2022-04-16 14:44:50 +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
|
|
|
|
2024-01-12 10:27:25 +01:00
|
|
|
const configObject = variationConfig.versions[newPeriod]
|
2023-04-01 09:55:24 +02:00
|
|
|
let layerUrls = []
|
2023-04-06 17:08:53 +02:00
|
|
|
let layers = []
|
|
|
|
|
2022-04-16 14:44:50 +02:00
|
|
|
if (typeof configObject.url === "string") {
|
2023-04-01 09:55:24 +02:00
|
|
|
layerUrls.push(configObject.url)
|
2022-04-16 14:44:50 +02:00
|
|
|
} else {
|
2023-04-01 09:55:24 +02:00
|
|
|
layerUrls.push(...configObject.url)
|
|
|
|
}
|
|
|
|
const canvas = document.createElement('canvas')
|
|
|
|
const context = canvas.getContext('2d')
|
2023-06-01 05:16:30 +02:00
|
|
|
|
|
|
|
layers.length = layerUrls.length
|
|
|
|
await Promise.all(layerUrls.map(async (url, i) => {
|
2024-01-12 10:27:25 +01:00
|
|
|
try {
|
|
|
|
const imageBlob = await (await fetch(url, { signal: myAbortController.signal })).blob()
|
|
|
|
const imageLayer = new Image()
|
|
|
|
await new Promise(resolve => {
|
|
|
|
imageLayer.onload = () => {
|
|
|
|
context.canvas.width = Math.max(imageLayer.width, context.canvas.width)
|
|
|
|
context.canvas.height = Math.max(imageLayer.height, context.canvas.height)
|
|
|
|
layers[i] = imageLayer
|
|
|
|
resolve()
|
|
|
|
}
|
|
|
|
imageLayer.src = URL.createObjectURL(imageBlob)
|
|
|
|
})
|
|
|
|
} catch (e) {
|
|
|
|
const aborted = myAbortController.signal.aborted
|
|
|
|
if (!aborted) throw e
|
|
|
|
}
|
2023-06-01 05:16:30 +02:00
|
|
|
}))
|
2023-04-01 09:55:24 +02:00
|
|
|
|
2024-01-12 10:27:25 +01:00
|
|
|
if (myAbortController.signal.aborted || newPeriod !== currentPeriod || newVariation !== currentVariation || currentUpdateIndex !== myUpdateIndex) {
|
|
|
|
return false
|
2023-07-29 19:05:29 +02:00
|
|
|
}
|
2024-01-12 10:27:25 +01:00
|
|
|
|
2023-04-06 17:08:53 +02:00
|
|
|
for (const imageLayer of layers) {
|
|
|
|
context.drawImage(imageLayer, 0, 0)
|
|
|
|
}
|
|
|
|
|
2024-01-12 10:27:25 +01:00
|
|
|
context.drawImage(additionalLayerCanvas, 0, 0)
|
|
|
|
|
|
|
|
if (myAbortController.signal.aborted || newPeriod !== currentPeriod || newVariation !== currentVariation || currentUpdateIndex !== myUpdateIndex) {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
2023-04-01 09:55:24 +02:00
|
|
|
if (currentUpdateIndex !== myUpdateIndex) return [configObject, newPeriod, newVariation]
|
|
|
|
const blob = await new Promise(resolve => canvas.toBlob(resolve))
|
|
|
|
canvasUrl = URL.createObjectURL(blob)
|
|
|
|
image.src = canvasUrl
|
2024-01-12 10:27:25 +01:00
|
|
|
|
|
|
|
return true
|
|
|
|
|
2022-04-15 05:46:27 +02:00
|
|
|
}
|
|
|
|
|
2024-01-12 10:27:25 +01:00
|
|
|
let loadingTimeout = setTimeout(() => {}, 0)
|
|
|
|
|
2023-03-26 13:58:17 +02:00
|
|
|
async function updateTime(newPeriod = currentPeriod, newVariation = currentVariation, forceLoad = false) {
|
2024-01-12 10:27:25 +01:00
|
|
|
if (newPeriod === currentPeriod && newVariation === currentVariation && !forceLoad) {
|
|
|
|
return
|
2023-04-07 04:44:06 +02:00
|
|
|
}
|
2022-04-29 06:29:58 +02:00
|
|
|
document.body.dataset.canvasLoading = ""
|
2022-04-16 14:44:50 +02:00
|
|
|
|
2024-01-12 10:27:25 +01:00
|
|
|
const loadingEl = document.getElementById("loading")
|
|
|
|
const previouslyHidden = loadingEl.classList.contains("d-none")
|
|
|
|
|
|
|
|
if (previouslyHidden) loadingEl.classList.add("opacity-0", "transition-opacity")
|
|
|
|
clearTimeout(loadingTimeout)
|
|
|
|
loadingTimeout = setTimeout(() => {
|
|
|
|
loadingEl.classList.remove("d-none")
|
|
|
|
if (previouslyHidden) setTimeout(() => {
|
|
|
|
loadingEl.classList.remove("opacity-0")
|
|
|
|
}, 0)
|
|
|
|
}, 2000)
|
|
|
|
|
2023-07-29 19:05:29 +02:00
|
|
|
// const oldPeriod = currentPeriod
|
2023-03-26 13:58:17 +02:00
|
|
|
const oldVariation = currentVariation
|
|
|
|
|
2022-04-29 16:11:01 +02:00
|
|
|
if (!variationsConfig[newVariation]) newVariation = defaultVariation
|
2022-04-29 08:41:25 +02:00
|
|
|
const variationConfig = variationsConfig[newVariation]
|
2022-04-29 16:11:01 +02:00
|
|
|
|
|
|
|
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
|
2022-04-29 15:48:40 +02:00
|
|
|
newPeriod = currentPeriod
|
|
|
|
}
|
2022-05-09 21:57:23 +02:00
|
|
|
if (variationConfig.versions.length === 1) bottomBar.classList.add('no-time-slider')
|
|
|
|
else bottomBar.classList.remove('no-time-slider')
|
2022-04-29 08:41:25 +02:00
|
|
|
}
|
|
|
|
timelineSlider.value = currentPeriod
|
|
|
|
updateTooltip(newPeriod, newVariation)
|
|
|
|
|
2024-01-12 10:27:25 +01:00
|
|
|
const updateBackgroundResult = await updateBackground(newPeriod, newVariation)
|
2022-04-16 14:44:50 +02:00
|
|
|
|
2024-01-12 10:27:25 +01:00
|
|
|
if (!updateBackgroundResult) return
|
2023-07-29 19:05:29 +02:00
|
|
|
|
|
|
|
dispatchTimeUpdateEvent(newPeriod, newVariation, atlas)
|
|
|
|
delete document.body.dataset.canvasLoading
|
2024-01-12 10:27:25 +01:00
|
|
|
clearTimeout(loadingTimeout)
|
|
|
|
document.getElementById("loading").classList.add("d-none")
|
|
|
|
document.getElementById("loading").classList.remove("opacity-0", "opacity-100", "transition-opacity")
|
|
|
|
|
2023-07-29 19:05:29 +02:00
|
|
|
tooltip.dataset.forceVisible = ""
|
|
|
|
clearTimeout(tooltipDelayHide)
|
|
|
|
tooltipDelayHide = setTimeout(() => {
|
|
|
|
delete tooltip.dataset.forceVisible
|
|
|
|
}, 1000)
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2024-01-12 10:27:25 +01:00
|
|
|
function generateAtlasDisplay(prevAtlas, prevAtlasOrder, newPeriod = currentPeriod, newVariation = currentVariation) {
|
|
|
|
|
|
|
|
const newAtlas = {}
|
|
|
|
const newAtlasOrderDisplayed = []
|
|
|
|
const newAtlasOrderNotDisplayed = []
|
|
|
|
|
|
|
|
for (const id of prevAtlasOrder) {
|
|
|
|
|
|
|
|
newAtlasOrderNotDisplayed.push(id)
|
|
|
|
const entry = prevAtlas[id]
|
2023-07-29 19:05:29 +02:00
|
|
|
|
2022-04-23 15:29:22 +02:00
|
|
|
let chosenIndex
|
2022-04-16 14:44:50 +02:00
|
|
|
|
2023-04-08 12:32:30 +02:00
|
|
|
const validPeriods2 = Object.keys(entry.path)
|
2022-04-16 14:44:50 +02:00
|
|
|
|
2024-01-12 10:27:25 +01:00
|
|
|
periodCheck: for (const i in validPeriods2) {
|
2022-04-16 14:44:50 +02:00
|
|
|
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
|
2024-01-12 10:27:25 +01:00
|
|
|
break periodCheck
|
2022-04-16 14:44:50 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (chosenIndex === undefined) continue
|
2023-04-08 12:32:30 +02:00
|
|
|
const pathChosen = Object.values(entry.path)[chosenIndex]
|
|
|
|
const centerChosen = Object.values(entry.center)[chosenIndex]
|
2022-04-16 14:44:50 +02:00
|
|
|
|
|
|
|
if (pathChosen === undefined) continue
|
|
|
|
|
2024-01-12 10:27:25 +01:00
|
|
|
newAtlas[id] = {
|
2023-04-08 12:32:30 +02:00
|
|
|
...entry,
|
2022-04-16 14:44:50 +02:00
|
|
|
path: pathChosen,
|
|
|
|
center: centerChosen,
|
2024-01-12 10:27:25 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
newAtlasOrderNotDisplayed.pop()
|
|
|
|
newAtlasOrderDisplayed.push(id)
|
|
|
|
|
2022-04-16 14:44:50 +02:00
|
|
|
}
|
|
|
|
|
2024-01-12 10:27:25 +01:00
|
|
|
return [newAtlas, [...newAtlasOrderDisplayed, ...newAtlasOrderNotDisplayed]]
|
2022-04-29 06:29:58 +02:00
|
|
|
|
2022-04-16 04:48:12 +02:00
|
|
|
}
|
|
|
|
|
2023-03-26 13:58:17 +02:00
|
|
|
function updateTooltip(period, variation) {
|
|
|
|
const configObject = variationsConfig[variation].versions[period]
|
2022-04-27 21:40:27 +02:00
|
|
|
|
2023-04-07 10:29:34 +02:00
|
|
|
// If timestamp is a number return a UTC formatted date, otherwise use exact timestamp label
|
2023-04-06 17:31:45 +02:00
|
|
|
if (Array.isArray(configObject.timestamp)) {
|
|
|
|
tooltip.querySelector('div').textContent = ""
|
|
|
|
configObject.timestamp.forEach(timestamp => {
|
|
|
|
if (tooltip.querySelector('div').textContent) tooltip.querySelector('div').innerHTML += "<br />"
|
|
|
|
if (typeof timestamp === "number") tooltip.querySelector('div').innerHTML += new Date(timestamp * 1000).toUTCString()
|
|
|
|
else tooltip.querySelector('div').innerHTML += timestamp
|
|
|
|
})
|
|
|
|
} else 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
|
2022-04-27 21:40:27 +02:00
|
|
|
|
|
|
|
// 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
|
|
|
}
|
|
|
|
|
2022-04-16 07:58:26 +02:00
|
|
|
tooltip.parentElement.addEventListener('mouseenter', () => updateTooltip(parseInt(timelineSlider.value), currentVariation))
|
2022-04-10 09:03:08 +02:00
|
|
|
|
2022-04-16 04:48:12 +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]
|
2022-04-16 14:44:50 +02:00
|
|
|
return currentPeriod >= start && currentPeriod <= end && variation === currentVariation
|
2022-04-14 16:03:17 +02:00
|
|
|
}
|
2023-07-29 19:05:29 +02:00
|
|
|
window.isOnPeriod = isOnPeriod
|
2022-04-14 16:03:17 +02:00
|
|
|
|
|
|
|
function parsePeriod(periodString) {
|
2022-04-29 15:48:40 +02:00
|
|
|
let variation = defaultVariation
|
2022-04-14 16:03:17 +02:00
|
|
|
periodString = periodString + ""
|
2022-04-16 14:44:50 +02:00
|
|
|
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]
|
2022-04-29 15:48:40 +02:00
|
|
|
} 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
|
|
|
}
|
2022-04-16 04:48:12 +02:00
|
|
|
}
|
2022-04-29 15:48:40 +02:00
|
|
|
|
2023-07-29 19:05:29 +02:00
|
|
|
function formatPeriod(targetStart, targetEnd, targetVariation, forUrl = false) {
|
|
|
|
targetStart ??= currentPeriod
|
2024-01-12 10:27:25 +01:00
|
|
|
targetEnd ??= targetStart
|
2023-07-29 19:05:29 +02:00
|
|
|
targetVariation ??= currentVariation
|
2023-03-19 05:55:12 +01:00
|
|
|
|
2022-04-30 06:28:20 +02:00
|
|
|
let periodString, variationString
|
2023-07-29 19:05:29 +02:00
|
|
|
variationString = variationsConfig[targetVariation].code
|
|
|
|
if (targetStart > targetEnd) [targetStart, targetEnd] = [targetEnd, targetStart]
|
|
|
|
if (targetStart === targetEnd) {
|
|
|
|
if (forUrl && targetVariation === defaultVariation && targetStart === variationsConfig[defaultVariation].default) {
|
2022-04-30 06:28:20 +02:00
|
|
|
periodString = ""
|
2022-05-06 09:41:22 +02:00
|
|
|
}
|
2023-07-29 19:05:29 +02:00
|
|
|
else periodString = targetStart
|
2022-04-29 15:48:40 +02:00
|
|
|
}
|
2023-07-29 19:05:29 +02:00
|
|
|
else periodString = targetStart + "-" + targetEnd
|
|
|
|
if (periodString && variationString) return variationsConfig[targetVariation].code + ":" + periodString
|
2022-04-30 06:28:20 +02:00
|
|
|
if (variationString) return variationString
|
2023-07-29 19:05:29 +02:00
|
|
|
|
2022-04-29 15:48:40 +02:00
|
|
|
return periodString
|
2022-08-15 12:18:23 +02:00
|
|
|
}
|
2023-03-19 05:55:12 +01:00
|
|
|
|
2023-07-29 19:05:29 +02:00
|
|
|
function setReferenceVal(reference, newValue) {
|
|
|
|
if (reference === false || reference === "") return null
|
|
|
|
else return reference ?? newValue
|
|
|
|
}
|
|
|
|
|
2024-01-12 10:27:25 +01:00
|
|
|
function formatHash(targetEntry, targetPeriod, targetVariation, targetX, targetY, targetZoom) {
|
2023-07-29 19:05:29 +02:00
|
|
|
let hashData = window.location.hash.substring(1).split('/')
|
|
|
|
|
|
|
|
targetEntry = setReferenceVal(targetEntry, hashData[0])
|
2024-01-12 10:27:25 +01:00
|
|
|
targetPeriod = setReferenceVal(targetPeriod, currentPeriod)
|
2023-07-29 19:05:29 +02:00
|
|
|
targetVariation = setReferenceVal(targetVariation, currentVariation)
|
|
|
|
targetX = setReferenceVal(targetX, canvasCenter.x - scaleZoomOrigin[0])
|
|
|
|
targetY = setReferenceVal(targetY, canvasCenter.y - scaleZoomOrigin[1])
|
|
|
|
targetZoom = setReferenceVal(targetZoom, zoom)
|
2023-03-19 05:55:12 +01:00
|
|
|
|
2023-07-29 19:05:29 +02:00
|
|
|
if (targetX) targetX = Math.round(targetX)
|
|
|
|
if (targetY) targetY = Math.round(targetY)
|
|
|
|
if (targetZoom) targetZoom = targetZoom.toFixed(3).replace(/\.?0+$/, '')
|
|
|
|
|
|
|
|
const result = [targetEntry]
|
2024-01-12 10:27:25 +01:00
|
|
|
const targetPeriodFormat = formatPeriod(targetPeriod, null, targetVariation, true)
|
|
|
|
result.push(targetPeriodFormat, targetX, targetY, targetZoom)
|
2023-03-19 05:55:12 +01:00
|
|
|
if (!result.some(el => el || el === 0)) return ''
|
2023-07-29 19:05:29 +02:00
|
|
|
return '#' + result.join('/').replace(/\/+$/, '')
|
2023-03-19 05:55:12 +01:00
|
|
|
}
|
2023-04-01 09:55:24 +02:00
|
|
|
|
|
|
|
function downloadCanvas() {
|
|
|
|
const linkEl = document.createElement("a")
|
|
|
|
linkEl.download = "canvas.png"
|
|
|
|
linkEl.href = canvasUrl
|
|
|
|
linkEl.classList.add("d-none")
|
|
|
|
document.body.appendChild(linkEl)
|
|
|
|
linkEl.click()
|
|
|
|
document.body.removeChild(linkEl)
|
2024-01-12 10:27:25 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
function getNearestPeriod(entry, targetPeriod, targetVariation) {
|
|
|
|
|
|
|
|
const pathKeys = Object.keys(entry.path)
|
|
|
|
|
|
|
|
let nearestScore, nearestPeriod, nearestVariation, nearestKey
|
|
|
|
|
|
|
|
function updateNearest(newScore, newPeriod, newVariation, newKey) {
|
|
|
|
if (newScore >= nearestScore) return
|
|
|
|
nearestScore = newScore
|
|
|
|
nearestPeriod = newPeriod
|
|
|
|
nearestVariation = newVariation
|
|
|
|
nearestKey = newKey
|
|
|
|
}
|
|
|
|
|
|
|
|
checkPaths: for (const pathKey of pathKeys) {
|
|
|
|
const pathPeriods = pathKey.split(', ')
|
|
|
|
|
|
|
|
checkPathPeriod: for (const j in pathPeriods) {
|
|
|
|
const [pathStart, pathEnd, pathVariation] = parsePeriod(pathPeriods[j])
|
|
|
|
if (isOnPeriod(pathStart, pathEnd, pathVariation, targetPeriod, targetVariation)) {
|
|
|
|
updateNearest(0, targetPeriod, targetVariation)
|
|
|
|
break checkPaths
|
|
|
|
} else if (pathVariation !== targetVariation) {
|
|
|
|
updateNearest(Infinity, pathStart, pathVariation, pathKey)
|
|
|
|
continue checkPathPeriod
|
|
|
|
} else if (Math.abs(pathStart - targetPeriod) < Math.abs(pathEnd - targetPeriod)) {
|
|
|
|
updateNearest(Math.abs(pathStart - targetPeriod), pathStart, pathVariation, pathKey)
|
|
|
|
} else {
|
|
|
|
updateNearest(Math.abs(pathEnd - targetPeriod), pathStart, pathVariation, pathKey)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
return [ nearestPeriod, nearestVariation, nearestKey ]
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
const updateAdditionalLayer = () => {
|
|
|
|
const layers = additionalLayers
|
|
|
|
const canvas = additionalLayerCanvas
|
|
|
|
const context = additionalLayerCanvas.getContext('2d')
|
|
|
|
canvas.width = 0
|
|
|
|
canvas.height = 0
|
|
|
|
|
|
|
|
|
|
|
|
for (const layer of layers) {
|
|
|
|
if (!layer.imageLayer) continue
|
|
|
|
canvas.width = Math.max(layer.x + layer.imageLayer.width, canvas.width)
|
|
|
|
canvas.height = Math.max(layer.y + layer.imageLayer.height, canvas.height)
|
|
|
|
}
|
|
|
|
|
|
|
|
for (const layer of layers) {
|
|
|
|
if (!layer.imageLayer) continue
|
|
|
|
context.drawImage(layer.imageLayer, layer.x, layer.y)
|
|
|
|
console.log(layer.imageLayer)
|
|
|
|
}
|
|
|
|
}
|