atlas/web/_js/main/main.js

543 lines
13 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://2022.place-atlas.stefanocoding.me/license.txt)
*/
2022-05-06 09:41:22 +02:00
const innerContainer = document.getElementById("innerContainer")
const container = document.getElementById("container")
2023-04-07 11:44:33 +02:00
const highlightCanvas = document.getElementById("highlightCanvas")
const imageCanvas = document.getElementById('image')
const highlightContext = highlightCanvas.getContext("2d")
2023-04-07 11:44:33 +02:00
highlightCanvas.width = canvasSize.x
highlightCanvas.height = canvasSize.y
imageCanvas.width = canvasSize.x
imageCanvas.height = canvasSize.y
2022-05-06 09:41:22 +02:00
let zoom = 1
if (window.devicePixelRatio) {
2022-05-06 09:41:22 +02:00
zoom = 1 / window.devicePixelRatio
}
2022-05-06 09:41:22 +02:00
const maxZoom = 128
const minZoom = 0.1
2022-05-06 09:41:22 +02:00
let zoomOrigin = [0, 0]
let scaleZoomOrigin = [0, 0]
2022-05-06 09:41:22 +02:00
let dragging = false
let lastPosition = [0, 0]
2022-05-06 09:41:22 +02:00
const viewportSize = [0, 0]
2023-03-26 13:58:17 +02:00
// TODO Probably merge both functions
function applyView() {
2022-05-06 09:41:22 +02:00
//console.log(zoomOrigin, scaleZoomOrigin)
//console.log(scaleZoomOrigin[0])
2017-04-10 16:28:22 +02:00
2023-03-26 13:58:17 +02:00
scaleZoomOrigin[0] = Math.max(-canvasCenter.x, Math.min(canvasCenter.x, scaleZoomOrigin[0]))
scaleZoomOrigin[1] = Math.max(-canvasCenter.y, Math.min(canvasCenter.y, scaleZoomOrigin[1]))
2017-04-10 16:28:22 +02:00
2022-05-06 09:41:22 +02:00
zoomOrigin = [scaleZoomOrigin[0] * zoom, scaleZoomOrigin[1] * zoom]
2023-03-19 07:03:49 +01:00
innerContainer.style.height = (~~(zoom * canvasSize.x)) + "px"
innerContainer.style.width = (~~(zoom * canvasSize.y)) + "px"
2022-05-06 09:41:22 +02:00
innerContainer.style.left = ~~(container.clientWidth / 2 - innerContainer.clientWidth / 2 + zoomOrigin[0] + container.offsetLeft) + "px"
innerContainer.style.top = ~~(container.clientHeight / 2 - innerContainer.clientHeight / 2 + zoomOrigin[1] + container.offsetTop) + "px"
}
2023-03-26 13:58:17 +02:00
function setView(x, y, zoomN = zoom) {
zoom = zoomN
scaleZoomOrigin = [
canvasCenter.x - x,
canvasCenter.y - y
]
applyView()
}
2022-05-06 09:41:22 +02:00
let atlas = null
2022-04-10 11:11:34 +02:00
window.atlas = atlas
let atlasAll = null
2022-04-10 11:11:34 +02:00
window.atlasAll = atlasAll
2022-04-05 11:48:15 +02:00
2022-04-08 17:35:21 +02:00
if (document.location.host !== prodDomain) document.body.dataset.dev = ""
2022-05-06 09:41:22 +02:00
init()
async function init() {
const args = window.location.search
const params = new URLSearchParams(args)
2022-04-06 17:34:04 +02:00
// For Reviewing Reddit Changes
// const atlasRef = '../tools/temp-atlas.json'
const atlasRef = params.get('atlas') || './atlas.json'
const atlasResp = await fetch(atlasRef)
atlas = await atlasResp.json()
2023-04-16 19:12:02 +02:00
atlas.sort((a, b) => a.center[1] - b.center[1])
2022-05-06 09:41:22 +02:00
atlasAll = updateAtlasAll(atlas)
2022-05-06 09:41:22 +02:00
let mode = "view"
if (args) {
2023-04-07 11:44:33 +02:00
mode = params.get("mode") || "view"
2022-04-09 21:09:57 +02:00
// Backwards compatibility for old links using "search" id arg
2023-04-07 11:44:33 +02:00
if (params.has('id') && mode !== 'draw') {
2022-04-16 14:26:31 +02:00
const id = params.get('id')
params.delete('id')
2022-04-16 14:26:31 +02:00
const newLocation = new URL(window.location)
newLocation.hash = id
newLocation.search = params
window.history.replaceState({}, '', newLocation)
2022-04-09 21:09:57 +02:00
}
}
const hash = window.location.hash.substring(1)
2022-05-06 09:41:22 +02:00
const [, period] = hash.split('/')
if (period) {
2022-05-06 09:41:22 +02:00
const [, targetPeriod, targetVariation] = parsePeriod(period)
2023-04-07 04:44:06 +02:00
await updateTime(targetPeriod, targetVariation, true)
} else {
2023-04-07 04:44:06 +02:00
await updateTime(currentPeriod, currentVariation, true)
}
2022-05-06 09:41:22 +02:00
//console.log(document.documentElement.clientWidth, document.documentElement.clientHeight)
2022-05-06 09:41:22 +02:00
zoomOrigin = [0, 0]
applyView()
2022-05-06 09:41:22 +02:00
let initialPinchDistance = 0
let initialPinchZoom = 0
let initialPinchZoomOrigin = [0, 0]
2022-05-06 09:41:22 +02:00
let desiredZoom
let zoomAnimationFrame
document.body.dataset.mode = mode
2022-04-09 11:27:14 +02:00
initGlobal()
if (mode !== "draw") initViewGlobal()
if (mode === "draw") {
2022-05-06 09:41:22 +02:00
initDraw()
} else if (mode === "about") {
2022-05-06 09:41:22 +02:00
window.location = "./about.html"
} else if (mode === "overlap") {
if (initOverlap) {
2022-05-06 09:41:22 +02:00
initOverlap()
}
2023-04-07 11:44:33 +02:00
} else if (mode === "explore") {
initExplore()
} else if (mode.startsWith("diff")) {
2022-04-08 06:06:16 +02:00
try {
const liveAtlasRef = params.get('liveatlas') || `https://${prodDomain}/atlas.json`
const liveAtlasResp = await fetch(liveAtlasRef)
let liveAtlas = await liveAtlasResp.json()
liveAtlas = updateAtlasAll(liveAtlas)
const liveAtlasReduced = liveAtlas.reduce(function (a, c) {
2022-05-06 09:41:22 +02:00
a[c.id] = c
return a
}, {})
2022-04-08 22:02:14 +02:00
// Mark added/edited entries
atlasAll = atlasAll.map(function (entry) {
if (liveAtlasReduced[entry.id] === undefined) {
2022-05-06 09:41:22 +02:00
entry.diff = "add"
} else if (JSON.stringify(entry) !== JSON.stringify(liveAtlasReduced[entry.id])) {
2022-05-06 09:41:22 +02:00
entry.diff = "edit"
2022-04-08 06:06:16 +02:00
}
2022-05-06 09:41:22 +02:00
return entry
})
2022-04-08 22:02:14 +02:00
// Mark removed entries
const atlasReduced = atlasAll.reduce(function (a, c) {
2022-05-06 09:41:22 +02:00
a[c.id] = c
return a
}, {})
const removedEntries = liveAtlas.filter(entry =>
atlasReduced[entry.id] === undefined
).map(entry => {
entry.diff = "delete"
return entry
})
atlasAll.push(...removedEntries)
2022-04-08 22:02:14 +02:00
if (mode.includes("only")) {
atlasAll = atlasAll.filter(function (entry) {
2023-03-17 18:30:33 +01:00
return typeof entry.diff === "string"
2022-05-06 09:41:22 +02:00
})
2022-04-08 22:02:14 +02:00
}
2022-04-08 06:06:16 +02:00
} catch (error) {
2022-05-06 09:41:22 +02:00
console.warn("Diff mode failed to load, reverting to normal view.", error)
2022-04-08 06:06:16 +02:00
} finally {
2022-04-16 14:51:51 +02:00
await updateTime()
if (initOverlap && mode.includes("overlap")) {
2022-05-06 09:41:22 +02:00
initOverlap()
2022-04-08 22:02:14 +02:00
} else {
2022-05-06 09:41:22 +02:00
initView()
2022-04-08 22:02:14 +02:00
}
2022-04-08 06:06:16 +02:00
}
} else {
2022-05-06 09:41:22 +02:00
initView()
}
2017-04-10 17:57:13 +02:00
2022-05-06 09:41:22 +02:00
document.getElementById("loading").classList.add("d-none")
2017-04-10 17:57:13 +02:00
2023-03-18 02:05:40 +01:00
document.getElementById("zoomInButton").addEventListener("click", function () {
2017-04-10 12:25:04 +02:00
/*if(zoomAnimationFrame){
2022-05-06 09:41:22 +02:00
window.cancelAnimationFrame(zoomAnimationFrame)
2017-04-10 12:25:04 +02:00
}*/
2022-05-06 09:41:22 +02:00
const x = container.clientWidth / 2
const y = container.clientHeight / 2
initialPinchZoomOrigin = [
scaleZoomOrigin[0],
scaleZoomOrigin[1]
2022-05-06 09:41:22 +02:00
]
2022-05-06 09:41:22 +02:00
initialPinchZoom = zoom
2022-05-06 09:41:22 +02:00
lastPosition = [x, y]
2023-04-07 11:44:33 +02:00
zoom *= 2
2022-05-06 09:41:22 +02:00
zoom = Math.max(minZoom, Math.min(maxZoom, zoom))
2022-05-06 09:41:22 +02:00
applyZoom(x, y, zoom)
2022-05-06 09:41:22 +02:00
})
2017-04-05 00:53:10 +02:00
2023-03-18 02:05:40 +01:00
document.getElementById("zoomOutButton").addEventListener("click", function () {
2017-04-10 12:25:04 +02:00
/*if(zoomAnimationFrame){
2022-05-06 09:41:22 +02:00
window.cancelAnimationFrame(zoomAnimationFrame)
2017-04-10 12:25:04 +02:00
}*/
2022-05-06 09:41:22 +02:00
const x = container.clientWidth / 2
const y = container.clientHeight / 2
initialPinchZoomOrigin = [
scaleZoomOrigin[0],
scaleZoomOrigin[1]
2022-05-06 09:41:22 +02:00
]
2022-05-06 09:41:22 +02:00
initialPinchZoom = zoom
2022-05-06 09:41:22 +02:00
lastPosition = [x, y]
zoom /= 2
2022-05-06 09:41:22 +02:00
zoom = Math.max(minZoom, Math.min(maxZoom, zoom))
2022-05-06 09:41:22 +02:00
applyZoom(x, y, zoom)
})
2017-04-05 00:53:10 +02:00
2023-03-18 02:05:40 +01:00
document.getElementById("zoomResetButton").addEventListener("click", function () {
2022-05-06 09:41:22 +02:00
zoom = 1
zoomOrigin = [0, 0]
scaleZoomOrigin = [0, 0]
updateLines()
applyView()
})
2017-04-05 00:53:10 +02:00
2023-04-07 11:44:33 +02:00
container.addEventListener("dblclick", e => {
2017-04-10 12:25:04 +02:00
/*if(zoomAnimationFrame){
2022-05-06 09:41:22 +02:00
window.cancelAnimationFrame(zoomAnimationFrame)
2017-04-10 12:25:04 +02:00
}*/
2022-05-06 09:41:22 +02:00
const x = e.clientX - container.offsetLeft
const y = e.clientY - container.offsetTop
initialPinchZoomOrigin = [
scaleZoomOrigin[0],
scaleZoomOrigin[1]
2022-05-06 09:41:22 +02:00
]
2022-05-06 09:41:22 +02:00
initialPinchZoom = zoom
2022-05-06 09:41:22 +02:00
lastPosition = [x, y]
if (e.ctrlKey) zoom /= 2
else zoom *= 2
2022-05-06 09:41:22 +02:00
zoom = Math.max(minZoom, Math.min(maxZoom, zoom))
applyZoom(x, y, zoom)
2022-05-06 09:41:22 +02:00
e.preventDefault()
})
2023-04-07 11:44:33 +02:00
container.addEventListener("wheel", e => {
2017-04-10 12:25:04 +02:00
/*if(zoomAnimationFrame){
2022-05-06 09:41:22 +02:00
window.cancelAnimationFrame(zoomAnimationFrame)
2017-04-10 12:25:04 +02:00
}*/
2022-05-06 09:41:22 +02:00
const x = e.clientX - container.offsetLeft
const y = e.clientY - container.offsetTop
initialPinchZoomOrigin = [
scaleZoomOrigin[0],
scaleZoomOrigin[1]
2022-05-06 09:41:22 +02:00
]
2022-05-06 09:41:22 +02:00
initialPinchZoom = zoom
2022-05-06 09:41:22 +02:00
lastPosition = [x, y]
2022-04-06 22:42:48 +02:00
// Check if we are zooming by pixels
// https://developer.mozilla.org/en-US/docs/Web/API/WheelEvent/deltaMode
if (e.deltaMode === 0) {
// Scale the pixel delta by the current zoom factor
// We want to zoom faster when closer, and slower when further
// This creates a smoother experience
2022-05-06 09:41:22 +02:00
zoom -= e.deltaY * (0.001 * zoom)
2022-04-06 22:42:48 +02:00
} else {
2023-04-07 11:44:33 +02:00
if (e.deltaY > 0) zoom /= 2
else if (e.deltaY < 0) zoom *= 2
}
2022-05-06 09:41:22 +02:00
zoom = Math.max(minZoom, Math.min(maxZoom, zoom))
applyZoom(x, y, zoom)
}, { passive: true })
2017-04-10 12:25:04 +02:00
/*function setDesiredZoom(x, y, target){
2022-05-06 09:41:22 +02:00
zoom = (zoom*2 + target)/3
//console.log(zoom)
if(Math.abs(1 - zoom/target) <= 0.01){
2022-05-06 09:41:22 +02:00
zoom = target
}
2022-05-06 09:41:22 +02:00
applyZoom(x, y, zoom)
if(zoom != target){
zoomAnimationFrame = window.requestAnimationFrame(function(){
2022-05-06 09:41:22 +02:00
setDesiredZoom(x, y, target)
})
}
2017-04-10 12:25:04 +02:00
}*/
2023-04-07 11:44:33 +02:00
container.addEventListener("mousedown", e => {
2022-05-06 09:41:22 +02:00
mousedown(e.clientX, e.clientY)
e.preventDefault()
})
2023-04-07 11:44:33 +02:00
container.addEventListener("touchstart", e => {
2023-03-17 18:30:33 +01:00
if (e.touches.length === 2) {
2022-05-06 09:41:22 +02:00
e.preventDefault()
}
2022-05-06 09:41:22 +02:00
touchstart(e)
2022-05-06 09:41:22 +02:00
}, { passive: true })
function mousedown(x, y) {
2022-05-06 09:41:22 +02:00
lastPosition = [x, y]
dragging = true
}
function touchstart(e) {
2023-03-17 18:30:33 +01:00
if (e.touches.length === 1) {
2022-05-06 09:41:22 +02:00
mousedown(e.touches[0].clientX, e.touches[0].clientY)
2023-03-17 18:30:33 +01:00
} else if (e.touches.length === 2) {
initialPinchDistance = Math.sqrt(
Math.pow(e.touches[0].clientX - e.touches[1].clientX, 2)
+ Math.pow(e.touches[0].clientY - e.touches[1].clientY, 2)
2022-05-06 09:41:22 +02:00
)
2022-05-06 09:41:22 +02:00
initialPinchZoom = zoom
initialPinchZoomOrigin = [
scaleZoomOrigin[0],
scaleZoomOrigin[1]
2022-05-06 09:41:22 +02:00
]
mousedown(
(e.touches[0].clientX + e.touches[1].clientX) / 2,
(e.touches[0].clientY + e.touches[1].clientY) / 2
2022-05-06 09:41:22 +02:00
)
}
}
2023-04-07 11:44:33 +02:00
window.addEventListener("mousemove", e => {
2022-05-06 09:41:22 +02:00
updateLines()
mousemove(e.clientX, e.clientY)
if (dragging) {
2022-05-06 09:41:22 +02:00
e.preventDefault()
}
2022-05-06 09:41:22 +02:00
})
2023-04-07 11:44:33 +02:00
window.addEventListener("touchmove", e => {
2023-03-17 18:30:33 +01:00
if (e.touches.length === 2 || e.scale > 1) {
2022-05-06 09:41:22 +02:00
e.preventDefault()
}
2022-05-06 09:41:22 +02:00
touchmove(e)
},
{ passive: false }
2022-05-06 09:41:22 +02:00
)
function mousemove(x, y) {
if (!dragging) return
container.style.cursor = "move"
const deltaX = x - lastPosition[0]
const deltaY = y - lastPosition[1]
lastPosition = [x, y]
zoomOrigin[0] += deltaX
zoomOrigin[1] += deltaY
scaleZoomOrigin[0] += deltaX / zoom
scaleZoomOrigin[1] += deltaY / zoom
updateLines()
applyView()
}
function touchmove(e) {
2017-04-09 02:08:35 +02:00
2022-05-06 09:41:22 +02:00
updateLines()
2023-03-17 18:30:33 +01:00
if (e.touches.length === 1) {
2022-05-06 09:41:22 +02:00
mousemove(e.touches[0].clientX, e.touches[0].clientY)
2023-03-17 18:30:33 +01:00
} else if (e.touches.length === 2) {
const newPinchDistance = Math.sqrt(
Math.pow(e.touches[0].clientX - e.touches[1].clientX, 2)
+ Math.pow(e.touches[0].clientY - e.touches[1].clientY, 2)
2022-05-06 09:41:22 +02:00
)
2022-05-06 09:41:22 +02:00
zoom = initialPinchZoom * newPinchDistance / initialPinchDistance
2022-05-06 09:41:22 +02:00
const x = (e.touches[0].clientX + e.touches[1].clientX) / 2 - container.offsetLeft
const y = (e.touches[0].clientY + e.touches[1].clientY) / 2 - container.offsetTop
2022-05-06 09:41:22 +02:00
applyZoom(x, y, zoom)
}
}
function applyZoom(x, y, zoom) {
2022-05-06 09:41:22 +02:00
const deltaX = x - lastPosition[0]
const deltaY = y - lastPosition[1]
2022-05-06 09:41:22 +02:00
const pinchTranslateX = (x - container.clientWidth / 2 - deltaX)
const pinchTranslateY = (y - container.clientHeight / 2 - deltaY)
2022-05-06 09:41:22 +02:00
scaleZoomOrigin[0] = initialPinchZoomOrigin[0] + deltaX / zoom + pinchTranslateX / zoom - pinchTranslateX / initialPinchZoom
scaleZoomOrigin[1] = initialPinchZoomOrigin[1] + deltaY / zoom + pinchTranslateY / zoom - pinchTranslateY / initialPinchZoom
2022-05-06 09:41:22 +02:00
zoomOrigin[0] = scaleZoomOrigin[0] * zoom
zoomOrigin[1] = scaleZoomOrigin[1] * zoom
2022-05-06 09:41:22 +02:00
applyView()
updateLines()
}
2023-04-07 11:44:33 +02:00
window.addEventListener("mouseup", e => {
if (hovered.length > 0) {
container.style.cursor = "pointer"
2023-03-17 18:30:33 +01:00
} else if (drawing === true) {
container.style.cursor = "crosshair"
} else {
container.style.cursor = "default"
}
if (dragging) {
2022-05-06 09:41:22 +02:00
e.preventDefault()
}
2022-05-06 09:41:22 +02:00
mouseup(e.clientX, e.clientY)
})
window.addEventListener("touchend", touchend)
function mouseup(x, y) {
if (dragging) {
2022-05-06 09:41:22 +02:00
dragging = false
}
}
function touchend(e) {
2023-03-17 18:30:33 +01:00
if (e.touches.length === 0) {
2022-05-06 09:41:22 +02:00
mouseup()
2023-04-07 11:14:07 +02:00
setTimeout(() => updateLines(), 0)
2023-03-17 18:30:33 +01:00
} else if (e.touches.length === 1) {
2022-05-06 09:41:22 +02:00
initialPinchZoom = zoom
lastPosition = [e.touches[0].clientX, e.touches[0].clientY]
}
}
window.addEventListener("resize", function () {
2022-05-06 09:41:22 +02:00
//console.log(document.documentElement.clientWidth, document.documentElement.clientHeight)
2022-05-06 09:41:22 +02:00
applyView()
})
2022-04-12 14:18:53 +02:00
document.body.dataset.initDone = ''
}
function updateAtlasAll(atlas = atlasAll) {
for (const index in atlas) {
const entry = atlas[index]
entry._index = index
const currentLinks = entry.links
entry.links = {
website: [],
subreddit: [],
discord: [],
wiki: [],
...currentLinks
}
const currentPath = entry.path
const currentCenter = entry.center
for (const key in currentPath) {
currentPath[key] = currentPath[key].map(point => point.map(int => int + 0.5))
}
for (const key in currentCenter) {
currentCenter[key] = currentCenter[key].map(int => int + 0.5)
}
entry.path = currentPath
entry.center = currentCenter
}
return atlas
2023-07-23 16:08:09 +02:00
}
// Announcement system
const announcementEl = document.querySelector("#headerAnnouncement")
const announcementButton = announcementEl.querySelector('[role=button]')
const announcementText = announcementEl.querySelector('p').textContent.trim()
if (announcementText && announcementText !== window.localStorage.getItem('announcement-closed')) {
announcementButton.click()
document.querySelector('#objectsList').style.marginTop = '2.8rem'
}
announcementEl.querySelector('[role=button]').addEventListener('click', () => {
window.localStorage.setItem('announcement-closed', announcementText)
document.querySelector('#objectsList').style.marginTop = '0'
})