2022-04-09 04:45:47 +02:00
|
|
|
/*
|
|
|
|
========================================================================
|
|
|
|
The 2022 /r/place Atlas
|
|
|
|
|
|
|
|
An Atlas of Reddit's 2022 /r/place, with information to each
|
|
|
|
artwork of the canvas provided by the community.
|
|
|
|
|
|
|
|
Copyright (c) 2017 Roland Rytz <roland@draemm.li>
|
2022-04-23 15:30:48 +02:00
|
|
|
Copyright (c) 2022 Place Atlas contributors
|
2022-04-09 04:45:47 +02:00
|
|
|
|
|
|
|
Licensed under the GNU Affero General Public License Version 3
|
|
|
|
https://place-atlas.stefanocoding.me/license.txt
|
|
|
|
========================================================================
|
|
|
|
*/
|
|
|
|
|
2022-04-16 14:51:51 +02:00
|
|
|
const variationsConfig = {
|
2022-04-16 14:44:50 +02:00
|
|
|
default: {
|
|
|
|
name: "r/place",
|
|
|
|
code: "",
|
2022-04-16 18:05:44 +02:00
|
|
|
default: 164,
|
2022-04-16 18:24:41 +02:00
|
|
|
versions: [{ "timestamp": 1648818000, "url": ["./_img/canvas/place30ex/start.png"] }, { "timestamp": 1648819800, "url": ["./_img/canvas/place30/005.png", "./_img/canvas/place30/000_005.png"] }, { "timestamp": 1648821600, "url": ["./_img/canvas/place30/005.png", "./_img/canvas/place30/001_005.png"] }, { "timestamp": 1648823400, "url": ["./_img/canvas/place30/005.png", "./_img/canvas/place30/002_005.png"] }, { "timestamp": 1648825200, "url": ["./_img/canvas/place30/005.png", "./_img/canvas/place30/003_005.png"] }, { "timestamp": 1648827000, "url": ["./_img/canvas/place30/005.png", "./_img/canvas/place30/004_005.png"] }, { "timestamp": 1648828800, "url": "./_img/canvas/place30/005.png" }, { "timestamp": 1648830600, "url": ["./_img/canvas/place30/005.png", "./_img/canvas/place30/006_005.png"] }, { "timestamp": 1648832400, "url": ["./_img/canvas/place30/005.png", "./_img/canvas/place30/007_005.png"] }, { "timestamp": 1648834200, "url": ["./_img/canvas/place30/005.png", "./_img/canvas/place30/008_005.png"] }, { "timestamp": 1648836000, "url": ["./_img/canvas/place30/005.png", "./_img/canvas/place30/009_005.png"] }, { "timestamp": 1648837800, "url": ["./_img/canvas/place30/005.png", "./_img/canvas/place30/010_005.png"] }, { "timestamp": 1648839600, "url": ["./_img/canvas/place30/016.png", "./_img/canvas/place30/011_016.png"] }, { "timestamp": 1648841400, "url": ["./_img/canvas/place30/016.png", "./_img/canvas/place30/012_016.png"] }, { "timestamp": 1648843200, "url": ["./_img/canvas/place30/016.png", "./_img/canvas/place30/013_016.png"] }, { "timestamp": 1648845000, "url": ["./_img/canvas/place30/016.png", "./_img/canvas/place30/014_016.png"] }, { "timestamp": 1648846800, "url": ["./_img/canvas/place30/016.png", "./_img/canvas/place30/015_016.png"] }, { "timestamp": 1648848600, "url": "./_img/canvas/place30/016.png" }, { "timestamp": 1648850400, "url": ["./_img/canvas/place30/016.png", "./_img/canvas/place30/017_016.png"] }, { "timestamp": 1648852200, "url": ["./_img/canvas/place30/016.png", "./_img/canvas/place30/018_016.png"] }, { "timestamp": 1648854000, "url": ["./_img/canvas/place30/016.png", "./_img/canvas/place30/019_016.png"] }, { "timestamp": 1648855800, "url": ["./_img/canvas/place30/016.png", "./_img/canvas/place30/020_016.png"] }, { "timestamp": 1648857600, "url": ["./_img/canvas/place30/016.png", "./_img/canvas/place30/021_016.png"] }, { "timestamp": 1648859400, "url": ["./_img/canvas/place30/027.png", "./_img/canvas/place30/022_027.png"] }, { "timestamp": 1648861200, "url": ["./_img/canvas/place30/027.png", "./_img/canvas/place30/023_027.png"] }, { "timestamp": 1648863000, "url": ["./_img/canvas/place30/027.png", "./_img/canvas/place30/024_027.png"] }, { "timestamp": 1648864800, "url": ["./_img/canvas/place30/027.png", "./_img/canvas/place30/025_027.png"] }, { "timestamp": 1648866600, "url": ["./_img/canvas/place30/027.png", "./_img/canvas/place30/026_027.png"] }, { "timestamp": 1648868400, "url": "./_img/canvas/place30/027.png" }, { "timestamp": 1648870200, "url": ["./_img/canvas/place30/027.png", "./_img/canvas/place30/028_027.png"] }, { "timestamp": 1648872000, "url": ["./_img/canvas/place30/027.png", "./_img/canvas/place30/029_027.png"] }, { "timestamp": 1648873800, "url": ["./_img/canvas/place30/027.png", "./_img/canvas/place30/030_027.png"] }, { "timestamp": 1648875600, "url": ["./_img/canvas/place30/027.png", "./_img/canvas/place30/031_027.png"] }, { "timestamp": 1648877400, "url": ["./_img/canvas/place30/027.png", "./_img/canvas/place30/032_027.png"] }, { "timestamp": 1648879200, "url": ["./_img/canvas/place30/038.png", "./_img/canvas/place30/033_038.png"] }, { "timestamp": 1648881000, "url": ["./_img/canvas/place30/038.png", "./_img/canvas/place30/034_038.png"] }, { "timestamp": 1648882800, "url": ["./_img/canvas/place30/038.png", "./_img/canvas/place30/035_038.png"] }, { "timestamp": 1648884600, "url": ["./_img/canvas/place30/038.png", "./_img/canvas/place30/036_038.png"] }, { "timestamp": 1648886400, "url": ["./_img/canvas/place30/038.png", "./_img/canvas/place30/037_038.png"] }, { "timestamp": 16488
|
2022-04-16 14:52:04 +02:00
|
|
|
},
|
|
|
|
tfc: {
|
|
|
|
name: "The Final Clean",
|
|
|
|
code: "T",
|
2022-04-23 15:49:22 +02:00
|
|
|
default: 0,
|
2022-04-16 14:52:04 +02:00
|
|
|
versions: [
|
|
|
|
{
|
|
|
|
timestamp: "Final",
|
|
|
|
url: "./_img/canvas/tfc/final.png",
|
|
|
|
},
|
|
|
|
]
|
2022-04-16 14:44:50 +02:00
|
|
|
}
|
2022-04-16 14:51:51 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
const codeReference = {}
|
2022-04-16 07:58:26 +02:00
|
|
|
const imageCache = {}
|
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
|
|
|
}
|
|
|
|
|
|
|
|
const timelineSlider = document.getElementById("timeControlsSlider");
|
|
|
|
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-05 20:17:39 +02:00
|
|
|
|
2022-04-15 18:33:18 +02:00
|
|
|
let currentVariation = "default"
|
2022-04-16 14:26:31 +02:00
|
|
|
const defaultPeriod = variationsConfig[currentVariation].default
|
2022-04-16 07:58:26 +02:00
|
|
|
let currentPeriod = defaultPeriod
|
2022-04-15 18:33:18 +02:00
|
|
|
window.currentPeriod = currentPeriod
|
|
|
|
window.currentVariation = currentVariation
|
2022-04-08 01:11:29 +02:00
|
|
|
|
2022-04-05 20:17:39 +02:00
|
|
|
// SETUP
|
2022-04-16 07:58:26 +02:00
|
|
|
timelineSlider.max = variationsConfig[currentVariation].versions.length - 1;
|
2022-04-15 18:33:18 +02:00
|
|
|
timelineSlider.value = currentPeriod;
|
2022-04-05 20:17:39 +02:00
|
|
|
|
2022-04-14 10:37:29 +02:00
|
|
|
timelineSlider.addEventListener("input", (event) => {
|
2022-04-16 18:05:44 +02:00
|
|
|
updateTooltip(parseInt(event.target.value), currentVariation)
|
|
|
|
clearTimeout(updateTimeout)
|
|
|
|
updateTimeout = setTimeout(() => {
|
|
|
|
updateTime(parseInt(timelineSlider.value), currentVariation)
|
2022-04-16 16:10:39 +02:00
|
|
|
setTimeout(() => {
|
2022-04-17 04:53:43 +02:00
|
|
|
if (timelineSlider.value != currentPeriod && abortController.signal.aborted) {
|
|
|
|
updateTime(parseInt(timelineSlider.value), currentVariation)
|
|
|
|
}
|
2022-04-16 18:05:44 +02:00
|
|
|
}, 50)
|
|
|
|
}, 25)
|
2022-04-16 14:51:51 +02:00
|
|
|
})
|
|
|
|
|
|
|
|
variantsEl.addEventListener("input", (event) => {
|
2022-04-16 14:44:50 +02:00
|
|
|
updateTime(currentPeriod, event.target.value)
|
2022-04-10 09:03:08 +02:00
|
|
|
})
|
2022-04-05 20:17:39 +02:00
|
|
|
|
2022-04-14 10:37:29 +02:00
|
|
|
const dispatchTimeUpdateEvent = (period = timelineSlider.value, atlas = atlas) => {
|
2022-04-16 14:44:50 +02:00
|
|
|
const timeUpdateEvent = new CustomEvent('timeupdate', {
|
|
|
|
detail: {
|
|
|
|
period: period,
|
|
|
|
atlas: atlas
|
|
|
|
}
|
|
|
|
});
|
|
|
|
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()
|
|
|
|
abortController = new AbortController()
|
|
|
|
currentUpdateIndex++
|
|
|
|
const myUpdateIndex = currentUpdateIndex
|
|
|
|
currentPeriod = newPeriod
|
|
|
|
const variationConfig = variationsConfig[newVariation]
|
|
|
|
if (currentVariation !== newVariation) {
|
|
|
|
currentVariation = newVariation
|
|
|
|
timelineSlider.max = variationConfig.versions.length - 1;
|
|
|
|
currentPeriod = variationConfig.default;
|
|
|
|
newPeriod = currentPeriod
|
2022-04-23 15:49:22 +02:00
|
|
|
if (variationConfig.versions.length === 1) timelineSlider.parentElement.classList.add('no-time-slider')
|
|
|
|
else timelineSlider.parentElement.classList.remove('no-time-slider')
|
|
|
|
|
2022-04-16 14:44:50 +02:00
|
|
|
}
|
|
|
|
timelineSlider.value = currentPeriod
|
|
|
|
variantsEl.value = currentVariation
|
|
|
|
const configObject = variationConfig.versions[currentPeriod];
|
|
|
|
if (typeof configObject.url === "string") {
|
|
|
|
if (imageCache[configObject.url] === undefined) {
|
|
|
|
const fetchResult = await fetch(configObject.url, {
|
|
|
|
signal: abortController.signal
|
|
|
|
});
|
|
|
|
if (currentUpdateIndex !== myUpdateIndex) {
|
2022-04-16 15:18:40 +02:00
|
|
|
return [configObject, newPeriod, newVariation]
|
2022-04-16 14:44:50 +02:00
|
|
|
}
|
|
|
|
const imageBlob = await fetchResult.blob()
|
|
|
|
imageCache[configObject.url] = URL.createObjectURL(imageBlob)
|
|
|
|
}
|
|
|
|
image.src = imageCache[configObject.url]
|
|
|
|
} else {
|
|
|
|
const canvas = document.createElement('canvas')
|
|
|
|
const context = canvas.getContext('2d')
|
|
|
|
context.canvas.width = 2000
|
|
|
|
context.canvas.height = 2000
|
2022-04-16 18:58:13 +02:00
|
|
|
await Promise.all(configObject.url.map(async url => {
|
2022-04-16 14:44:50 +02:00
|
|
|
if (imageCache[url] === undefined) {
|
|
|
|
const fetchResult = await fetch(url, {
|
|
|
|
signal: abortController.signal
|
|
|
|
});
|
|
|
|
if (currentUpdateIndex !== myUpdateIndex) {
|
2022-04-16 16:10:39 +02:00
|
|
|
return
|
2022-04-16 14:44:50 +02:00
|
|
|
}
|
|
|
|
const imageBlob = await fetchResult.blob()
|
|
|
|
imageCache[url] = URL.createObjectURL(imageBlob)
|
|
|
|
}
|
2022-04-16 18:58:13 +02:00
|
|
|
}))
|
|
|
|
for await (const url of configObject.url) {
|
2022-04-16 14:44:50 +02:00
|
|
|
const imageLayer = new Image()
|
|
|
|
await new Promise(resolve => {
|
|
|
|
imageLayer.onload = () => {
|
|
|
|
context.drawImage(imageLayer, 0, 0)
|
|
|
|
resolve()
|
|
|
|
}
|
|
|
|
imageLayer.src = imageCache[url]
|
|
|
|
})
|
|
|
|
}
|
2022-04-16 18:58:13 +02:00
|
|
|
|
2022-04-16 15:18:40 +02:00
|
|
|
if (currentUpdateIndex !== myUpdateIndex) return [configObject, newPeriod, newVariation]
|
2022-04-16 14:44:50 +02:00
|
|
|
const blob = await new Promise(resolve => canvas.toBlob(resolve))
|
|
|
|
image.src = URL.createObjectURL(blob)
|
|
|
|
}
|
|
|
|
|
|
|
|
return [configObject, newPeriod, newVariation]
|
2022-04-15 05:46:27 +02:00
|
|
|
}
|
|
|
|
|
2022-04-15 18:33:18 +02:00
|
|
|
async function updateTime(newPeriod = currentPeriod, newVariation = currentVariation) {
|
2022-04-16 14:44:50 +02:00
|
|
|
document.body.dataset.canvasLoading = true
|
|
|
|
|
|
|
|
let configObject
|
|
|
|
[configObject, newPeriod, newVariation] = await updateBackground(newPeriod, newVariation)
|
|
|
|
|
|
|
|
atlas = []
|
|
|
|
for (const atlasIndex in atlasAll) {
|
2022-04-23 15:29:22 +02:00
|
|
|
let chosenIndex
|
2022-04-16 14:44:50 +02:00
|
|
|
|
|
|
|
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]
|
2022-04-16 14:44:50 +02:00
|
|
|
|
|
|
|
if (pathChosen === undefined) continue
|
|
|
|
|
|
|
|
atlas.push({
|
|
|
|
...atlasAll[atlasIndex],
|
|
|
|
path: pathChosen,
|
|
|
|
center: centerChosen,
|
|
|
|
})
|
|
|
|
}
|
2022-04-23 15:49:22 +02:00
|
|
|
|
2022-04-16 14:44:50 +02:00
|
|
|
dispatchTimeUpdateEvent(newPeriod, atlas)
|
|
|
|
document.body.dataset.canvasLoading = false
|
2022-04-16 04:48:12 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
function updateTooltip(newPeriod, newVariation) {
|
2022-04-16 14:44:50 +02:00
|
|
|
const configObject = variationsConfig[newVariation].versions[newPeriod]
|
|
|
|
if (typeof configObject.timestamp === "number") tooltip.querySelector('p').textContent = new Date(configObject.timestamp * 1000).toUTCString()
|
|
|
|
else tooltip.querySelector('p').textContent = configObject.timestamp
|
2022-04-17 07:27:53 +02:00
|
|
|
if (timelineSlider.max === 1) {
|
|
|
|
tooltip.style.left = (0 - tooltip.offsetWidth/2) + "px"
|
|
|
|
} else {
|
|
|
|
tooltip.style.left = (((timelineSlider.offsetWidth)*(timelineSlider.value)/(timelineSlider.max)) - tooltip.offsetWidth/2) + "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 11:11:34 +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-04-16 14:44:50 +02:00
|
|
|
return currentPeriod >= start && currentPeriod <= end && variation === currentVariation
|
2022-04-14 16:03:17 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
function parsePeriod(periodString) {
|
2022-04-16 14:44:50 +02:00
|
|
|
let variation = "default"
|
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-04-16 14:44:50 +02:00
|
|
|
const [start, end] = periodString.split('-').map(i => parseInt(i))
|
2022-04-16 14:51:51 +02:00
|
|
|
return [start, end, 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
|
|
|
}
|