atlas/web/_js/main.js

547 lines
No EOL
13 KiB
JavaScript

/*
========================================================================
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>
Copyright (c) 2022 r/placeAtlas2 contributors
Licensed under the GNU Affero General Public License Version 3
https://place-atlas.stefanocoding.me/license.txt
========================================================================
*/
const prodDomain = "place-atlas.stefanocoding.me"
const innerContainer = document.getElementById("innerContainer");
const container = document.getElementById("container");
const canvas = document.getElementById("highlightCanvas");
const context = canvas.getContext("2d");
let zoom = 1;
if (window.devicePixelRatio) {
zoom = 1 / window.devicePixelRatio;
}
const maxZoom = 128;
const minZoom = 0.1;
let zoomOrigin = [0, 0];
let scaleZoomOrigin = [0, 0];
let dragging = false;
let lastPosition = [0, 0];
const viewportSize = [0, 0];
document.getElementById("entriesListDonate").addEventListener("click", function (e) {
document.getElementById("donateOverlay").style.display = "flex";
});
document.getElementById("closeDonateButton").addEventListener("click", function (e) {
document.getElementById("donateOverlay").style.display = "none";
});
function applyView() {
//console.log(zoomOrigin, scaleZoomOrigin);
//console.log(scaleZoomOrigin[0]);
scaleZoomOrigin[0] = Math.max(-1000, Math.min(1000, scaleZoomOrigin[0]));
scaleZoomOrigin[1] = Math.max(-1000, Math.min(1000, scaleZoomOrigin[1]));
zoomOrigin = [scaleZoomOrigin[0] * zoom, scaleZoomOrigin[1] * zoom];
innerContainer.style.height = (~~(zoom * 2000)) + "px";
innerContainer.style.width = (~~(zoom * 2000)) + "px";
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";
}
let atlas = null;
window.atlas = atlas
let atlasAll = null
window.atlasAll = atlasAll
if (document.location.host !== prodDomain) document.body.dataset.dev = ""
init();
async function init() {
// For Reviewing Reddit Changes
//let resp = await fetch("../tools/temp_atlas.json");
const resp = await fetch("./atlas.json");
atlas = await resp.json();
atlas.sort(function (a, b) {
if (a.center[1] < b.center[1]) {
return -1;
}
if (a.center[1] > b.center[1]) {
return 1;
}
// a must be equal to b
return 0;
});
atlasAll = updateAtlasAll(atlas);
await updateTime(currentPeriod, currentVariation)
//console.log(document.documentElement.clientWidth, document.documentElement.clientHeight);
zoomOrigin = [0, 0];
applyView();
let initialPinchDistance = 0;
let initialPinchZoom = 0;
let initialPinchZoomOrigin = [0, 0];
let desiredZoom;
let zoomAnimationFrame;
let mode = "view";
const args = window.location.search;
const params = new URLSearchParams(args)
if (args) {
mode = params.get("mode")
if (!mode) {
mode = "view";
}
// Backwards compatibility for old links using "search" id arg
if (params.has('id') && params.get('mode') !== 'draw') {
const id = params.get('id')
params.delete('id')
const newLocation = new URL(window.location)
newLocation.hash = id
newLocation.search = params
window.history.replaceState({}, '', newLocation)
}
}
document.body.dataset.mode = mode
initGlobal()
if (mode !== "draw") initViewGlobal()
if (mode === "draw") {
initDraw();
} else if (mode === "about") {
window.location = "./about.html";
} else if (mode === "overlap") {
if (initOverlap) {
initOverlap();
}
} else if (mode.startsWith("diff")) {
try {
const liveResp = await fetch("https://place-atlas.stefanocoding.me/atlas.json");
let liveJson = await liveResp.json();
liveJson = updateAtlasAll(liveJson)
// console.log(liveJson)
const liveAtlasReduced = liveJson.reduce(function (a, c) {
a[c.id] = c;
return a;
}, {});
// Mark added/edited entries
atlasAll = atlasAll.map(function (entry) {
if (liveAtlasReduced[entry.id] === undefined) {
entry.diff = "add";
} else if (JSON.stringify(entry) !== JSON.stringify(liveAtlasReduced[entry.id])) {
entry.diff = "edit";
}
return entry;
});
// Mark removed entries
const atlasReduced = atlasAll.reduce(function (a, c) {
a[c.id] = c;
return a;
}, {});
const removedEntries = liveJson.filter(entry =>
atlasReduced[entry.id] === undefined
).map(entry => {
entry.diff = "delete"
return entry
})
atlasAll.push(...removedEntries)
if (mode.includes("only")) {
atlasAll = atlasAll.filter(function (entry) {
return typeof entry.diff == "string"
});
}
} catch (error) {
console.warn("Diff mode failed to load, reverting to normal view.", error);
} finally {
await updateTime()
if (initOverlap && mode.includes("overlap")) {
initOverlap();
} else {
initView();
}
}
} else if (mode === "explore") {
initExplore();
} else {
initView();
}
document.getElementById("zoomInButton").addEventListener("click", function (e) {
/*if(zoomAnimationFrame){
window.cancelAnimationFrame(zoomAnimationFrame);
}*/
const x = container.clientWidth / 2;
const y = container.clientHeight / 2;
initialPinchZoomOrigin = [
scaleZoomOrigin[0],
scaleZoomOrigin[1]
];
initialPinchZoom = zoom;
lastPosition = [x, y];
zoom = zoom * 2;
zoom = Math.max(minZoom, Math.min(maxZoom, zoom));
applyZoom(x, y, zoom);
});
document.getElementById("zoomOutButton").addEventListener("click", function (e) {
/*if(zoomAnimationFrame){
window.cancelAnimationFrame(zoomAnimationFrame);
}*/
const x = container.clientWidth / 2;
const y = container.clientHeight / 2;
initialPinchZoomOrigin = [
scaleZoomOrigin[0],
scaleZoomOrigin[1]
];
initialPinchZoom = zoom;
lastPosition = [x, y];
zoom = zoom / 2;
zoom = Math.max(minZoom, Math.min(maxZoom, zoom));
applyZoom(x, y, zoom);
});
document.getElementById("zoomResetButton").addEventListener("click", function (e) {
zoom = 1;
zoomOrigin = [0, 0];
scaleZoomOrigin = [0, 0];
updateLines();
applyView();
});
container.addEventListener("dblclick", function (e) {
/*if(zoomAnimationFrame){
window.cancelAnimationFrame(zoomAnimationFrame);
}*/
const x = e.clientX - container.offsetLeft;
const y = e.clientY - container.offsetTop;
initialPinchZoomOrigin = [
scaleZoomOrigin[0],
scaleZoomOrigin[1]
];
initialPinchZoom = zoom;
lastPosition = [x, y];
if (e.ctrlKey) {
zoom = zoom / 2;
} else {
zoom = zoom * 2;
}
zoom = Math.max(minZoom, Math.min(maxZoom, zoom));
applyZoom(x, y, zoom);
e.preventDefault();
});
container.addEventListener("wheel", function (e) {
/*if(zoomAnimationFrame){
window.cancelAnimationFrame(zoomAnimationFrame);
}*/
const x = e.clientX - container.offsetLeft;
const y = e.clientY - container.offsetTop;
initialPinchZoomOrigin = [
scaleZoomOrigin[0],
scaleZoomOrigin[1]
];
initialPinchZoom = zoom;
lastPosition = [x, y];
// 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
zoom -= e.deltaY * (0.001 * zoom);
} else {
if (e.deltaY > 0) {
zoom = zoom / 2;
} else if (e.deltaY < 0) {
zoom = zoom * 2;
}
}
zoom = Math.max(minZoom, Math.min(maxZoom, zoom));
applyZoom(x, y, zoom);
e.preventDefault();
}, { passive: true });
/*function setDesiredZoom(x, y, target){
zoom = (zoom*2 + target)/3;
//console.log(zoom);
if(Math.abs(1 - zoom/target) <= 0.01){
zoom = target;
}
applyZoom(x, y, zoom);
if(zoom != target){
zoomAnimationFrame = window.requestAnimationFrame(function(){
setDesiredZoom(x, y, target);
});
}
}*/
container.addEventListener("mousedown", function (e) {
mousedown(e.clientX, e.clientY);
e.preventDefault();
});
container.addEventListener("touchstart", function (e) {
if (e.touches.length == 2) {
e.preventDefault();
}
touchstart(e);
}, { passive: true });
function mousedown(x, y) {
lastPosition = [x, y];
dragging = true;
}
function touchstart(e) {
if (e.touches.length == 1) {
mousedown(e.touches[0].clientX, e.touches[0].clientY);
} 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)
);
initialPinchZoom = zoom;
initialPinchZoomOrigin = [
scaleZoomOrigin[0],
scaleZoomOrigin[1]
];
mousedown(
(e.touches[0].clientX + e.touches[1].clientX) / 2,
(e.touches[0].clientY + e.touches[1].clientY) / 2
);
}
}
window.addEventListener("mousemove", function (e) {
updateLines();
mousemove(e.clientX, e.clientY);
if (dragging) {
e.preventDefault();
}
});
window.addEventListener("touchmove", function (e) {
if (e.touches.length == 2 || e.scale > 1) {
e.preventDefault();
}
touchmove(e);
},
{ passive: false }
);
function mousemove(x, y) {
if (dragging) {
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;
previousZoomOrigin = [zoomOrigin[0], zoomOrigin[1]];
previousScaleZoomOrigin = [scaleZoomOrigin[0], scaleZoomOrigin[1]];
updateLines();
applyView();
}
}
function touchmove(e) {
updateLines();
if (e.touches.length == 1) {
mousemove(e.touches[0].clientX, e.touches[0].clientY);
} 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)
);
zoom = initialPinchZoom * newPinchDistance / initialPinchDistance;
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;
applyZoom(x, y, zoom);
}
}
function applyZoom(x, y, zoom) {
const deltaX = x - lastPosition[0];
const deltaY = y - lastPosition[1];
const pinchTranslateX = (x - container.clientWidth / 2 - deltaX);
const pinchTranslateY = (y - container.clientHeight / 2 - deltaY);
scaleZoomOrigin[0] = initialPinchZoomOrigin[0] + deltaX / zoom + pinchTranslateX / zoom - pinchTranslateX / initialPinchZoom;
scaleZoomOrigin[1] = initialPinchZoomOrigin[1] + deltaY / zoom + pinchTranslateY / zoom - pinchTranslateY / initialPinchZoom;
zoomOrigin[0] = scaleZoomOrigin[0] * zoom;
zoomOrigin[1] = scaleZoomOrigin[1] * zoom;
applyView();
updateLines();
}
window.addEventListener("mouseup", function (e) {
if (dragging) {
e.preventDefault();
}
mouseup(e.clientX, e.clientY);
});
window.addEventListener("touchend", touchend);
function mouseup(x, y) {
if (dragging) {
dragging = false;
}
}
function touchend(e) {
if (e.touches.length == 0) {
mouseup();
} else if (e.touches.length == 1) {
initialPinchZoom = zoom;
lastPosition = [e.touches[0].clientX, e.touches[0].clientY];
}
}
window.addEventListener("resize", function () {
//console.log(document.documentElement.clientWidth, document.documentElement.clientHeight);
applyView();
});
document.body.dataset.initDone = ''
}
function updateAtlasAll(atlas) {
if (!atlas) atlas = atlasAll
for (const atlasIndex in atlas) {
if (Array.isArray(atlas[atlasIndex].path)) {
const currentPath = atlas[atlasIndex].path
atlas[atlasIndex].path = {}
atlas[atlasIndex].path[defaultPeriod] = currentPath
}
if (Array.isArray(atlas[atlasIndex].center)) {
const currentCenter = atlas[atlasIndex].center
atlas[atlasIndex].center = {}
atlas[atlasIndex].center[defaultPeriod] = currentCenter
}
if (atlas[atlasIndex].links) {
const currentLinks = atlas[atlasIndex].links
atlas[atlasIndex].links = {
website: [],
subreddit: [],
discord: [],
wiki: [],
...currentLinks
}
} else {
atlas[atlasIndex].links = {
website: [],
subreddit: [],
discord: [],
wiki: []
}
if (atlas[atlasIndex].website) atlas[atlasIndex].links.website = [atlas[atlasIndex].website]
if (atlas[atlasIndex].subreddit) atlas[atlasIndex].links.subreddit = atlas[atlasIndex].subreddit.split(',').map(subreddit => subreddit.trim().replace(/^\/r\//, ''))
delete atlas[atlasIndex].website
delete atlas[atlasIndex].subreddit
}
}
return atlas
}