/* ======================================================================== 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 Copyright (c) 2022 Place Atlas 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) 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("loading").style.display = "none"; 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 }