mirror of
https://github.com/placeAtlas/atlas.git
synced 2024-11-15 14:33:36 +01:00
parent
39440f263e
commit
b6d39f7e39
12 changed files with 424 additions and 338 deletions
|
@ -52,6 +52,14 @@ .offcanvas {
|
|||
padding-left: env(safe-area-inset-left);
|
||||
}
|
||||
|
||||
.opacity-0 {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.transition-opacity {
|
||||
transition: opacity .15s linear;
|
||||
}
|
||||
|
||||
@supports (backdrop-filter: blur()) or (-webkit-backdrop-filter: blur()) {
|
||||
.navbar, .offcanvas {
|
||||
-webkit-backdrop-filter: saturate(180%) blur(15px);
|
||||
|
|
|
@ -69,7 +69,7 @@ const periodClipboard = {
|
|||
const drawBackButton = document.createElement("a")
|
||||
drawBackButton.className = "btn btn-outline-primary"
|
||||
drawBackButton.id = "drawBackButton"
|
||||
drawBackButton.textContent = "Exit Draw Mode"
|
||||
drawBackButton.textContent = "Exit Drawing"
|
||||
|
||||
const baseInputAddon = document.createElement("span")
|
||||
baseInputAddon.className = "input-group-text"
|
||||
|
@ -106,12 +106,13 @@ function initDraw() {
|
|||
showListButton.insertAdjacentHTML("afterend", '<button class="btn btn-outline-primary" type="button" data-bs-toggle="offcanvas" data-bs-target="#offcanvasDraw" aria-controls="offcanvasDraw">Menu</button>')
|
||||
showListButton.parentElement.appendChild(drawBackButton)
|
||||
showListButton.remove()
|
||||
|
||||
drawButton.remove()
|
||||
|
||||
// Opens draw menu
|
||||
wrapper.classList.remove('listHidden')
|
||||
bsOffcanvasDraw.show()
|
||||
|
||||
window.render = render
|
||||
window.renderHighlight = renderHighlight
|
||||
window.renderBackground = renderBackground
|
||||
window.updateHovering = updateHovering
|
||||
|
||||
|
@ -125,13 +126,18 @@ function initDraw() {
|
|||
|
||||
let highlightUncharted = highlightUnchartedEl.checked
|
||||
|
||||
renderBackground(atlas)
|
||||
window.updateAtlas = updateAtlas
|
||||
|
||||
updateAtlas()
|
||||
|
||||
document.addEventListener('timeupdate', () => {
|
||||
updateAtlas()
|
||||
})
|
||||
|
||||
applyView()
|
||||
|
||||
container.style.cursor = "crosshair"
|
||||
|
||||
render(path)
|
||||
|
||||
container.addEventListener("mousedown", e => {
|
||||
lastPos = [
|
||||
e.clientX,
|
||||
|
@ -168,7 +174,7 @@ function initDraw() {
|
|||
const coords = getCanvasCoords(e.clientX, e.clientY)
|
||||
|
||||
path.push(coords)
|
||||
render(path)
|
||||
renderHighlight(path)
|
||||
|
||||
undoHistory = []
|
||||
redoButton.disabled = true
|
||||
|
@ -185,13 +191,13 @@ function initDraw() {
|
|||
container.addEventListener("mousemove", e => {
|
||||
if (!dragging && drawing && path.length > 0) {
|
||||
const coords = getCanvasCoords(e.clientX, e.clientY)
|
||||
render([...path, coords])
|
||||
renderHighlight([...path, coords])
|
||||
}
|
||||
})
|
||||
|
||||
container.addEventListener("mouseout", function () {
|
||||
if (!dragging && drawing && path.length > 0) {
|
||||
render(path)
|
||||
renderHighlight(path)
|
||||
}
|
||||
})
|
||||
|
||||
|
@ -228,19 +234,19 @@ function initDraw() {
|
|||
undoButton.addEventListener("click", e => {
|
||||
undo()
|
||||
const coords = getCanvasCoords(e.clientX, e.clientY)
|
||||
render([...path, coords])
|
||||
renderHighlight([...path, coords])
|
||||
})
|
||||
|
||||
redoButton.addEventListener("click", e => {
|
||||
redo()
|
||||
const coords = getCanvasCoords(e.clientX, e.clientY)
|
||||
render([...path, coords])
|
||||
renderHighlight([...path, coords])
|
||||
})
|
||||
|
||||
resetButton.addEventListener("click", e => {
|
||||
reset()
|
||||
const coords = getCanvasCoords(e.clientX, e.clientY)
|
||||
render([...path, coords])
|
||||
renderHighlight([...path, coords])
|
||||
})
|
||||
|
||||
resetButton.addEventListener("blur", function () {
|
||||
|
@ -269,7 +275,7 @@ function initDraw() {
|
|||
|
||||
highlightUnchartedEl.addEventListener("click", function () {
|
||||
highlightUncharted = this.checked
|
||||
render(path)
|
||||
renderHighlight(path)
|
||||
})
|
||||
|
||||
function generateExportObject() {
|
||||
|
@ -385,13 +391,11 @@ function initDraw() {
|
|||
}
|
||||
githubPostButton.href = githubPostUrl
|
||||
|
||||
console.log(githubPostUrl)
|
||||
|
||||
exportModal.show()
|
||||
}
|
||||
|
||||
function preview() {
|
||||
let infoElement = createInfoBlock(generateExportObject(), true)
|
||||
let infoElement = createInfoBlock(generateExportObject(), 2)
|
||||
objectsContainer.replaceChildren()
|
||||
objectsContainer.appendChild(infoElement)
|
||||
closeObjectsListButton.classList.remove("d-none")
|
||||
|
@ -483,17 +487,17 @@ function initDraw() {
|
|||
closeObjectsListButton.classList.add("d-none")
|
||||
}
|
||||
|
||||
function renderBackground() {
|
||||
function renderBackground(atlas) {
|
||||
|
||||
backgroundContext.clearRect(0, 0, highlightCanvas.width, highlightCanvas.height)
|
||||
|
||||
backgroundContext.fillStyle = "rgba(0, 0, 0, 1)"
|
||||
//backgroundContext.fillRect(0, 0, canvas.width, canvas.height)
|
||||
//backgroundContext.fillRect(0, 0, canvas.width, renderBackgroundcanvas.height)
|
||||
|
||||
for (let i = 0; i < atlas.length; i++) {
|
||||
|
||||
const path = atlas[i].path
|
||||
for (const entry of Object.values(atlas)) {
|
||||
|
||||
const path = entry.path
|
||||
|
||||
backgroundContext.beginPath()
|
||||
|
||||
if (path[0]) {
|
||||
|
@ -510,7 +514,7 @@ function initDraw() {
|
|||
}
|
||||
}
|
||||
|
||||
function render(path) {
|
||||
function renderHighlight(path) {
|
||||
|
||||
if (!Array.isArray(path)) return
|
||||
|
||||
|
@ -553,10 +557,16 @@ function initDraw() {
|
|||
updateCoordsDisplay(e)
|
||||
}
|
||||
|
||||
function updateAtlas() {
|
||||
;[atlas, atlasOrder] = filterAtlas(atlasAll)
|
||||
;[atlasDisplay, atlasOrder] = generateAtlasDisplay(atlas, atlasOrder, currentPeriod, currentVariation)
|
||||
renderBackground(atlasDisplay)
|
||||
renderHighlight(atlasDisplay)
|
||||
}
|
||||
|
||||
const getEntry = id => {
|
||||
if (!id) return
|
||||
const entries = atlasAll.filter(entry => entry.id.toString() === id.toString())
|
||||
if (entries.length === 1) return entries[0]
|
||||
return atlasAll[id]
|
||||
}
|
||||
|
||||
function addFieldButton(inputButton, inputGroup, array, index, name) {
|
||||
|
@ -790,7 +800,7 @@ function initDraw() {
|
|||
|
||||
} else {
|
||||
document.getElementById("offcanvasDrawLabel").textContent = "New Entry"
|
||||
pathWithPeriods.push([formatPeriod(currentPeriod, currentPeriod, currentVariation), []])
|
||||
pathWithPeriods.push([formatPeriod(currentPeriod, null, currentVariation), []])
|
||||
|
||||
// Builds multi-input list
|
||||
addWebsiteFields("", 0, [0])
|
||||
|
@ -811,19 +821,19 @@ function initDraw() {
|
|||
)
|
||||
|
||||
document.addEventListener('timeupdate', () => {
|
||||
renderBackground(atlas)
|
||||
renderBackground(atlasDisplay)
|
||||
updatePeriodGroups()
|
||||
})
|
||||
|
||||
periodsAdd.addEventListener('click', () => {
|
||||
pathWithPeriods.push([formatPeriod(currentPeriod, currentPeriod, currentVariation), []])
|
||||
pathWithPeriods.push([formatPeriod(currentPeriod, null, currentVariation), []])
|
||||
initPeriodGroups()
|
||||
})
|
||||
|
||||
drawBackButton.href = "./" + formatHash(entry?.id)
|
||||
|
||||
document.addEventListener('timeupdate', event => {
|
||||
drawBackButton.href = "./" + formatHash(entry?.id, event.detail.period, event.detail.period, event.detail.variation)
|
||||
drawBackButton.href = "./" + formatHash(entry?.id, event.detail.period, event.detail.variation)
|
||||
})
|
||||
|
||||
}
|
||||
|
@ -1224,7 +1234,7 @@ function updatePeriodGroups() {
|
|||
function updatePath(newPath, newUndoHistory) {
|
||||
path = newPath || path
|
||||
if (path.length > 3) center = calculateCenter(path)
|
||||
render(path)
|
||||
renderHighlight(path)
|
||||
undoButton.disabled = path.length === 0; // Maybe make it undo the cancel action in the future
|
||||
undoHistory = newUndoHistory || []
|
||||
redoButton.disabled = (!undoHistory.length)
|
||||
|
|
|
@ -31,7 +31,10 @@ function createInfoListItem(name, value) {
|
|||
return entryInfoListElement
|
||||
}
|
||||
|
||||
function createInfoBlock(entry, isPreview) {
|
||||
// mode 0 = normal
|
||||
// mode 1 = entry list but none on atlas
|
||||
// mode 2 = preview
|
||||
function createInfoBlock(entry, mode = 0) {
|
||||
const element = document.createElement("div")
|
||||
element.className = "card mb-2 overflow-hidden shadow"
|
||||
|
||||
|
@ -40,21 +43,31 @@ function createInfoBlock(entry, isPreview) {
|
|||
|
||||
const linkElement = document.createElement("a")
|
||||
linkElement.className = "text-decoration-none d-flex justify-content-between text-body"
|
||||
if (isPreview) linkElement.href = "#"
|
||||
else {
|
||||
linkElement.href = formatHash(entry.id, null, null, null, false, false, false)
|
||||
linkElement.addEventListener('click', e => {
|
||||
|
||||
let nearestPeriod = currentPeriod
|
||||
let nearestVariation = currentVariation
|
||||
if (!atlasDisplay[entry.id]) {
|
||||
[nearestPeriod, nearestVariation] = getNearestPeriod(entry, currentPeriod, currentVariation)
|
||||
}
|
||||
|
||||
if (mode === 2) {
|
||||
linkElement.href = "#"
|
||||
} else {
|
||||
const hash = formatHash(entry.id, nearestPeriod, nearestVariation, false, false, false)
|
||||
linkElement.href = hash
|
||||
if (mode === 0) linkElement.addEventListener('click', e => {
|
||||
e.preventDefault()
|
||||
location.hash = formatHash(entry.id, null, null, null, false, false, false)
|
||||
location.hash = hash
|
||||
window.dispatchEvent(new HashChangeEvent("hashchange"))
|
||||
})
|
||||
}
|
||||
|
||||
const linkNameElement = document.createElement("span")
|
||||
linkNameElement.className = "flex-grow-1 text-break"
|
||||
linkNameElement.textContent = entry.name
|
||||
headerElement.appendChild(linkElement)
|
||||
linkElement.appendChild(linkNameElement)
|
||||
linkElement.insertAdjacentHTML("beforeend", '<i class="bi bi-link-45deg align-self-center link-primary" aria-hidden="true"></i>')
|
||||
linkElement.insertAdjacentHTML("beforeend", '<i class="bi bi-link-45deg align-self-center link-primary" aria-hidden="true" title="Copy direct link"></i>')
|
||||
element.appendChild(headerElement)
|
||||
|
||||
const bodyElement = document.createElement("div")
|
||||
|
@ -92,7 +105,7 @@ function createInfoBlock(entry, isPreview) {
|
|||
}
|
||||
|
||||
// Entry data submitted to preview does not include center or path
|
||||
if (!isPreview) {
|
||||
if (mode === 0) {
|
||||
const [x, y] = entry?.center
|
||||
listElement.appendChild(createInfoListItem("Position: ", `${Math.floor(x)}, ${Math.floor(y)}`))
|
||||
|
||||
|
@ -170,11 +183,11 @@ function createInfoBlock(entry, isPreview) {
|
|||
element.appendChild(idElementContainer)
|
||||
|
||||
// Adds edit button only if element is not deleted
|
||||
if (!isPreview && (!entry.diff || entry.diff !== "delete")) {
|
||||
if (mode < 2 && (!entry.diff || entry.diff !== "delete")) {
|
||||
const editElement = document.createElement("a")
|
||||
editElement.textContent = "Edit"
|
||||
editElement.innerHTML = '<i class="bi bi-pencil-fill" aria-hidden="true"></i> Edit'
|
||||
editElement.className = "btn btn-sm btn-outline-primary"
|
||||
editElement.href = "./?mode=draw&id=" + entry.id + formatHash(false)
|
||||
editElement.href = "./?mode=draw&id=" + entry.id + formatHash(false, nearestPeriod, nearestVariation, false, false, false)
|
||||
editElement.title = "Edit " + entry.name
|
||||
idElementContainer.appendChild(editElement)
|
||||
}
|
||||
|
|
|
@ -26,7 +26,7 @@ const maxZoom = 128
|
|||
const minZoom = 0.125
|
||||
|
||||
let zoomOrigin = [0, 0]
|
||||
let scaleZoomOrigin = [0, 0]
|
||||
let scaleZoomOrigin = [canvasCenter.x, canvasCenter.y]
|
||||
|
||||
let dragging = false
|
||||
let lastPosition = [0, 0]
|
||||
|
@ -54,7 +54,7 @@ function applyView() {
|
|||
}
|
||||
|
||||
function setView(targetX, targetY, targetZoom) {
|
||||
|
||||
|
||||
if (isNaN(targetX)) targetX = null
|
||||
if (isNaN(targetY)) targetY = null
|
||||
|
||||
|
@ -72,8 +72,6 @@ function updateHash(...args) {
|
|||
if (location.hash !== newLocation.hash) history.replaceState({}, "", newLocation)
|
||||
}
|
||||
|
||||
let atlas = null
|
||||
window.atlas = atlas
|
||||
let atlasAll = null
|
||||
window.atlasAll = atlasAll
|
||||
|
||||
|
@ -106,9 +104,9 @@ async function init() {
|
|||
|
||||
// For Reviewing Reddit Changes
|
||||
// const atlasRef = '../tools/temp-atlas.json'
|
||||
const atlasRef = params.get('atlas') || './atlas.json'
|
||||
const atlasResp = await fetch(atlasRef)
|
||||
atlasAll = updateAtlasAll(await atlasResp.json())
|
||||
const atlasAllUrl = params.get('atlas') || './atlas.json'
|
||||
atlasAll = generateAtlasAll(await (await fetch(atlasAllUrl)).json())
|
||||
// console.log(atlas, atlasOrder)
|
||||
|
||||
const hash = window.location.hash.substring(1)
|
||||
const [, hashPeriod, hashX, hashY, hashZoom] = hash.split('/')
|
||||
|
@ -148,45 +146,33 @@ async function init() {
|
|||
initExplore()
|
||||
} else if (mode.startsWith("diff")) {
|
||||
try {
|
||||
const liveAtlasRef = params.get('liveatlas') || `https://${prodDomain}/atlas.json`
|
||||
const liveAtlasResp = await fetch(liveAtlasRef)
|
||||
let liveAtlas = await liveAtlasResp.json()
|
||||
liveAtlas = updateAtlasAll(liveAtlas)
|
||||
const liveAtlasUrl = params.get('liveatlas') || `https://${prodDomain}/atlas.json`
|
||||
let liveAtlasAll = generateAtlasAll(await (await fetch(liveAtlasUrl)).json())
|
||||
|
||||
const liveAtlasReduced = liveAtlas.reduce((atlas, entry) => {
|
||||
delete entry._index
|
||||
atlas[entry.id] = entry
|
||||
return atlas
|
||||
}, {})
|
||||
// Mark added/edited entries
|
||||
atlasAll = atlasAll.map(function (entry) {
|
||||
delete entry._index
|
||||
if (!liveAtlasReduced[entry.id]) {
|
||||
for (const entry of Object.values(atlasAll)) {
|
||||
if (!liveAtlasAll[entry.id]) {
|
||||
entry.diff = "add"
|
||||
} else if (JSON.stringify(entry) !== JSON.stringify(liveAtlasReduced[entry.id])) {
|
||||
} else {
|
||||
if (JSON.stringify({ ...entry, _index: undefined }) === JSON.stringify({ ...liveAtlasAll[entry.id], _index: undefined })) continue
|
||||
entry.diff = "edit"
|
||||
}
|
||||
return entry
|
||||
})
|
||||
|
||||
// Mark removed entries
|
||||
const atlasReduced = atlasAll.reduce((atlas, entry) => {
|
||||
delete entry._index
|
||||
atlas[entry.id] = entry
|
||||
return atlas
|
||||
}, {})
|
||||
const removedEntries = liveAtlas.filter(entry => !atlasReduced[entry.id]).map(entry => {
|
||||
delete entry._index
|
||||
entry.diff = "delete"
|
||||
return entry
|
||||
})
|
||||
atlasAll.push(...removedEntries)
|
||||
|
||||
if (mode.includes("only")) {
|
||||
atlasAll = atlasAll.filter(entry => entry.diff)
|
||||
}
|
||||
|
||||
atlas = generateAtlasForPeriod()
|
||||
// Mark removed entries
|
||||
for (const entry of Object.values(liveAtlasAll)) {
|
||||
if (!atlasAll[entry.id]) {
|
||||
entry.diff = "delete"
|
||||
atlasAll[entry.id] = entry
|
||||
}
|
||||
}
|
||||
|
||||
if (mode.includes('only')) {
|
||||
for (const key of Object.keys(atlasAll)) {
|
||||
if (atlasAll[key].diff) continue
|
||||
delete atlasAll[key]
|
||||
}
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.warn("Diff mode failed to load, reverting to normal view.", error)
|
||||
|
@ -255,7 +241,7 @@ async function init() {
|
|||
zoom = 1
|
||||
zoomOrigin = [0, 0]
|
||||
scaleZoomOrigin = [0, 0]
|
||||
updateLines()
|
||||
renderLines()
|
||||
applyView()
|
||||
})
|
||||
|
||||
|
@ -384,7 +370,6 @@ async function init() {
|
|||
}
|
||||
|
||||
window.addEventListener("mousemove", e => {
|
||||
// updateLines()
|
||||
mousemove(e.clientX, e.clientY)
|
||||
if (dragging) {
|
||||
e.preventDefault()
|
||||
|
@ -417,7 +402,7 @@ async function init() {
|
|||
scaleZoomOrigin[0] += deltaX / zoom
|
||||
scaleZoomOrigin[1] += deltaY / zoom
|
||||
|
||||
updateLines()
|
||||
renderLines()
|
||||
applyView()
|
||||
}
|
||||
|
||||
|
@ -460,7 +445,7 @@ async function init() {
|
|||
zoomOrigin[1] = scaleZoomOrigin[1] * zoom
|
||||
|
||||
applyView()
|
||||
updateLines()
|
||||
renderLines()
|
||||
}
|
||||
|
||||
window.addEventListener("mouseup", e => {
|
||||
|
@ -478,7 +463,7 @@ async function init() {
|
|||
})
|
||||
window.addEventListener("touchend", touchend)
|
||||
|
||||
function mouseup(x, y) {
|
||||
function mouseup() {
|
||||
dragging = false
|
||||
updateHash()
|
||||
}
|
||||
|
@ -486,7 +471,7 @@ async function init() {
|
|||
function touchend(e) {
|
||||
if (e.touches.length === 0) {
|
||||
mouseup()
|
||||
setTimeout(() => updateLines(), 0)
|
||||
renderLines()
|
||||
dragging = false
|
||||
|
||||
} else if (e.touches.length === 1) {
|
||||
|
@ -506,7 +491,8 @@ async function init() {
|
|||
|
||||
}
|
||||
|
||||
function updateAtlasAll(atlas = atlasAll) {
|
||||
function generateAtlasAll(atlas = atlasAll) {
|
||||
const newAtlas = {}
|
||||
for (const index in atlas) {
|
||||
const entry = atlas[index]
|
||||
entry._index = index
|
||||
|
@ -528,8 +514,10 @@ function updateAtlasAll(atlas = atlasAll) {
|
|||
}
|
||||
entry.path = currentPath
|
||||
entry.center = currentCenter
|
||||
newAtlas[entry.id] = entry
|
||||
}
|
||||
return atlas
|
||||
// console.log(newAtlas)
|
||||
return newAtlas
|
||||
}
|
||||
|
||||
// Announcement system
|
||||
|
|
|
@ -9,22 +9,16 @@ function initOverlap() {
|
|||
|
||||
window.renderBackground = renderBackground
|
||||
|
||||
updateAtlas()
|
||||
|
||||
// const hovered = []
|
||||
|
||||
resetEntriesList()
|
||||
renderBackground(atlas)
|
||||
render()
|
||||
|
||||
document.addEventListener('timeupdate', () => {
|
||||
atlasDisplay = atlas.slice()
|
||||
resetEntriesList()
|
||||
renderBackground(atlasDisplay)
|
||||
render()
|
||||
updateAtlas()
|
||||
})
|
||||
|
||||
applyView()
|
||||
render()
|
||||
updateLines()
|
||||
renderLines()
|
||||
|
||||
if (window.location.hash) {
|
||||
updateViewFromHash()
|
||||
|
@ -37,7 +31,7 @@ function initOverlap() {
|
|||
backgroundContext.fillStyle = "rgba(255, 255, 255, 1)"
|
||||
backgroundContext.fillRect(0, 0, highlightCanvas.width, highlightCanvas.height)
|
||||
|
||||
for (const entry of atlas) {
|
||||
for (const entry of Object.values(atlas)) {
|
||||
|
||||
const path = entry.path
|
||||
|
||||
|
|
|
@ -32,6 +32,9 @@ let currentPeriod = defaultPeriod
|
|||
window.currentVariation = currentVariation
|
||||
window.currentPeriod = currentPeriod
|
||||
|
||||
let atlasDisplay = {}
|
||||
window.atlasDisplay = atlasDisplay
|
||||
|
||||
// SETUP
|
||||
if (variationsConfig[currentVariation].versions.length === 1) bottomBar.classList.add('no-time-slider')
|
||||
|
||||
|
@ -70,7 +73,7 @@ const dispatchTimeUpdateEvent = (period = currentPeriod, variation = currentVari
|
|||
detail: {
|
||||
period: period,
|
||||
variation: variation,
|
||||
periodString: formatPeriod(period, period, variation),
|
||||
periodString: formatPeriod(period, null, variation),
|
||||
atlas: atlas
|
||||
}
|
||||
})
|
||||
|
@ -79,7 +82,7 @@ const dispatchTimeUpdateEvent = (period = currentPeriod, variation = currentVari
|
|||
|
||||
async function updateBackground(newPeriod = currentPeriod, newVariation = currentVariation) {
|
||||
abortController.abort()
|
||||
myAbortController = new AbortController()
|
||||
const myAbortController = new AbortController()
|
||||
abortController = myAbortController
|
||||
currentUpdateIndex++
|
||||
const myUpdateIndex = currentUpdateIndex
|
||||
|
@ -96,7 +99,7 @@ async function updateBackground(newPeriod = currentPeriod, newVariation = curren
|
|||
variantsEl.parentElement.classList.remove('input-group')
|
||||
}
|
||||
|
||||
const configObject = variationConfig.versions[currentPeriod]
|
||||
const configObject = variationConfig.versions[newPeriod]
|
||||
let layerUrls = []
|
||||
let layers = []
|
||||
|
||||
|
@ -110,39 +113,59 @@ async function updateBackground(newPeriod = currentPeriod, newVariation = curren
|
|||
|
||||
layers.length = layerUrls.length
|
||||
await Promise.all(layerUrls.map(async (url, i) => {
|
||||
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)
|
||||
})
|
||||
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
|
||||
}
|
||||
}))
|
||||
|
||||
if (myAbortController.signal.aborted || newPeriod !== currentPeriod || newVariation !== currentVariation) {
|
||||
return
|
||||
if (currentUpdateIndex !== myUpdateIndex) {
|
||||
return false
|
||||
}
|
||||
|
||||
for (const imageLayer of layers) {
|
||||
context.drawImage(imageLayer, 0, 0)
|
||||
}
|
||||
|
||||
if (currentUpdateIndex !== myUpdateIndex) return [configObject, newPeriod, newVariation]
|
||||
const blob = await new Promise(resolve => canvas.toBlob(resolve))
|
||||
canvasUrl = URL.createObjectURL(blob)
|
||||
image.src = canvasUrl
|
||||
|
||||
return true
|
||||
|
||||
}
|
||||
|
||||
let loadingTimeout = setTimeout(() => {}, 0)
|
||||
|
||||
async function updateTime(newPeriod = currentPeriod, newVariation = currentVariation, forceLoad = false) {
|
||||
if (newPeriod === currentPeriod && !forceLoad) {
|
||||
return;
|
||||
if (newPeriod === currentPeriod && newVariation === currentVariation && !forceLoad) {
|
||||
return
|
||||
}
|
||||
document.body.dataset.canvasLoading = ""
|
||||
|
||||
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)
|
||||
|
||||
// const oldPeriod = currentPeriod
|
||||
const oldVariation = currentVariation
|
||||
|
||||
|
@ -167,12 +190,16 @@ async function updateTime(newPeriod = currentPeriod, newVariation = currentVaria
|
|||
timelineSlider.value = currentPeriod
|
||||
updateTooltip(newPeriod, newVariation)
|
||||
|
||||
await updateBackground(newPeriod, newVariation)
|
||||
const updateBackgroundResult = await updateBackground(newPeriod, newVariation)
|
||||
|
||||
atlas = generateAtlasForPeriod(newPeriod, newVariation)
|
||||
if (!updateBackgroundResult) return
|
||||
|
||||
dispatchTimeUpdateEvent(newPeriod, newVariation, atlas)
|
||||
delete document.body.dataset.canvasLoading
|
||||
clearTimeout(loadingTimeout)
|
||||
document.getElementById("loading").classList.add("d-none")
|
||||
document.getElementById("loading").classList.remove("opacity-0", "opacity-100", "transition-opacity")
|
||||
|
||||
tooltip.dataset.forceVisible = ""
|
||||
clearTimeout(tooltipDelayHide)
|
||||
tooltipDelayHide = setTimeout(() => {
|
||||
|
@ -181,24 +208,30 @@ async function updateTime(newPeriod = currentPeriod, newVariation = currentVaria
|
|||
|
||||
}
|
||||
|
||||
function generateAtlasForPeriod(newPeriod = currentPeriod, newVariation = currentVariation) {
|
||||
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]
|
||||
|
||||
const atlas = []
|
||||
for (const entry of atlasAll) {
|
||||
let chosenIndex
|
||||
|
||||
const validPeriods2 = Object.keys(entry.path)
|
||||
|
||||
for (const i in validPeriods2) {
|
||||
periodCheck: 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
|
||||
break periodCheck
|
||||
}
|
||||
}
|
||||
if (chosenIndex !== undefined) break
|
||||
}
|
||||
|
||||
if (chosenIndex === undefined) continue
|
||||
|
@ -207,14 +240,18 @@ function generateAtlasForPeriod(newPeriod = currentPeriod, newVariation = curren
|
|||
|
||||
if (pathChosen === undefined) continue
|
||||
|
||||
atlas.push({
|
||||
newAtlas[id] = {
|
||||
...entry,
|
||||
path: pathChosen,
|
||||
center: centerChosen,
|
||||
})
|
||||
}
|
||||
|
||||
newAtlasOrderNotDisplayed.pop()
|
||||
newAtlasOrderDisplayed.push(id)
|
||||
|
||||
}
|
||||
|
||||
return atlas
|
||||
return [newAtlas, [...newAtlasOrderDisplayed, ...newAtlasOrderNotDisplayed]]
|
||||
|
||||
}
|
||||
|
||||
|
@ -273,7 +310,7 @@ function parsePeriod(periodString) {
|
|||
|
||||
function formatPeriod(targetStart, targetEnd, targetVariation, forUrl = false) {
|
||||
targetStart ??= currentPeriod
|
||||
targetEnd ??= currentPeriod
|
||||
targetEnd ??= targetStart
|
||||
targetVariation ??= currentVariation
|
||||
|
||||
let periodString, variationString
|
||||
|
@ -297,12 +334,11 @@ function setReferenceVal(reference, newValue) {
|
|||
else return reference ?? newValue
|
||||
}
|
||||
|
||||
function formatHash(targetEntry, targetPeriodStart, targetPeriodEnd, targetVariation, targetX, targetY, targetZoom) {
|
||||
function formatHash(targetEntry, targetPeriod, targetVariation, targetX, targetY, targetZoom) {
|
||||
let hashData = window.location.hash.substring(1).split('/')
|
||||
|
||||
targetEntry = setReferenceVal(targetEntry, hashData[0])
|
||||
targetPeriodStart = setReferenceVal(targetPeriodStart, currentPeriod)
|
||||
targetPeriodEnd = setReferenceVal(targetPeriodEnd, currentPeriod)
|
||||
targetPeriod = setReferenceVal(targetPeriod, currentPeriod)
|
||||
targetVariation = setReferenceVal(targetVariation, currentVariation)
|
||||
targetX = setReferenceVal(targetX, canvasCenter.x - scaleZoomOrigin[0])
|
||||
targetY = setReferenceVal(targetY, canvasCenter.y - scaleZoomOrigin[1])
|
||||
|
@ -313,8 +349,8 @@ function formatHash(targetEntry, targetPeriodStart, targetPeriodEnd, targetVaria
|
|||
if (targetZoom) targetZoom = targetZoom.toFixed(3).replace(/\.?0+$/, '')
|
||||
|
||||
const result = [targetEntry]
|
||||
const targetPeriod = formatPeriod(targetPeriodStart, targetPeriodEnd, targetVariation, true)
|
||||
result.push(targetPeriod, targetX, targetY, targetZoom)
|
||||
const targetPeriodFormat = formatPeriod(targetPeriod, null, targetVariation, true)
|
||||
result.push(targetPeriodFormat, targetX, targetY, targetZoom)
|
||||
if (!result.some(el => el || el === 0)) return ''
|
||||
return '#' + result.join('/').replace(/\/+$/, '')
|
||||
}
|
||||
|
@ -327,4 +363,42 @@ function downloadCanvas() {
|
|||
document.body.appendChild(linkEl)
|
||||
linkEl.click()
|
||||
document.body.removeChild(linkEl)
|
||||
}
|
||||
}
|
||||
|
||||
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 ]
|
||||
|
||||
}
|
||||
|
|
|
@ -43,7 +43,11 @@ objectEditNav.className = "btn btn-outline-primary"
|
|||
objectEditNav.id = "objectEditNav"
|
||||
objectEditNav.textContent = "Edit"
|
||||
|
||||
let atlasDisplay
|
||||
let atlas = null
|
||||
window.atlas = atlas
|
||||
|
||||
let atlasOrder = []
|
||||
window.atlasOrder = atlasOrder
|
||||
|
||||
const entriesLimit = 50
|
||||
let entriesOffset = 0
|
||||
|
@ -72,11 +76,11 @@ let lastPos = [0, 0]
|
|||
let fixed = false; // Fix hovered items in place, so that clicking on links is possible
|
||||
|
||||
searchInput.addEventListener("input", function () {
|
||||
resetEntriesList()
|
||||
updateAtlas()
|
||||
})
|
||||
|
||||
sortInput.addEventListener("input", function () {
|
||||
resetEntriesList()
|
||||
updateAtlas()
|
||||
})
|
||||
|
||||
offcanvasDraw.addEventListener('show.bs.offcanvas', () => {
|
||||
|
@ -112,8 +116,8 @@ offcanvasList.addEventListener('shown.bs.offcanvas', e => {
|
|||
wrapper.classList.remove('listTransitioning')
|
||||
updateHovering(e)
|
||||
applyView()
|
||||
render()
|
||||
updateLines()
|
||||
renderHighlight()
|
||||
renderLines()
|
||||
})
|
||||
|
||||
offcanvasList.addEventListener('hide.bs.offcanvas', () => {
|
||||
|
@ -127,8 +131,8 @@ offcanvasList.addEventListener('hidden.bs.offcanvas', e => {
|
|||
wrapper.classList.remove('listTransitioning')
|
||||
updateHovering(e)
|
||||
applyView()
|
||||
render()
|
||||
updateLines()
|
||||
renderHighlight()
|
||||
renderLines()
|
||||
})
|
||||
|
||||
closeObjectsListButton.addEventListener("click", clearObjectsList)
|
||||
|
@ -138,52 +142,52 @@ bottomBar.addEventListener("mouseover", () => {
|
|||
})
|
||||
|
||||
function clearObjectsList() {
|
||||
hovered = []
|
||||
fixed = false
|
||||
renderLines()
|
||||
renderHighlight()
|
||||
document.title = pageTitle
|
||||
closeObjectsListButton.classList.add("d-none")
|
||||
objectsListOverflowNotice.classList.add("d-none")
|
||||
entriesList.classList.remove("disableHover")
|
||||
hovered = []
|
||||
objectsContainer.replaceChildren()
|
||||
updateLines()
|
||||
fixed = false
|
||||
render()
|
||||
objectEditNav.remove()
|
||||
updateHash(false)
|
||||
document.title = pageTitle
|
||||
}
|
||||
|
||||
function toggleFixed(e, tapped) {
|
||||
if (!fixed && hovered.length === 0) {
|
||||
entriesList.classList.remove("disableHover")
|
||||
return 0
|
||||
return
|
||||
}
|
||||
fixed = !fixed
|
||||
if (!fixed) {
|
||||
updateHovering(e, tapped)
|
||||
render()
|
||||
renderHighlight()
|
||||
}
|
||||
entriesList.classList.add("disableHover")
|
||||
objectsListOverflowNotice.classList.add("d-none")
|
||||
}
|
||||
|
||||
window.addEventListener("resize", updateLines)
|
||||
window.addEventListener("mousemove", updateLines)
|
||||
window.addEventListener("dblClick", updateLines)
|
||||
window.addEventListener("wheel", updateLines)
|
||||
window.addEventListener("dblClick", renderLines)
|
||||
window.addEventListener("wheel", renderLines)
|
||||
|
||||
objectsContainer.addEventListener("scroll", () => {
|
||||
updateLines()
|
||||
renderLines()
|
||||
})
|
||||
|
||||
window.addEventListener("resize", () => {
|
||||
|
||||
applyView()
|
||||
render()
|
||||
updateLines()
|
||||
renderHighlight()
|
||||
renderLines()
|
||||
|
||||
})
|
||||
|
||||
function updateLines() {
|
||||
|
||||
async function renderLines() {
|
||||
if (hovered.length === 0) {
|
||||
linesContext.clearRect(0, 0, linesCanvas.width, linesCanvas.height)
|
||||
return
|
||||
}
|
||||
// Line border
|
||||
linesCanvas.width = linesCanvas.clientWidth
|
||||
linesCanvas.height = linesCanvas.clientHeight
|
||||
|
@ -260,9 +264,9 @@ function renderBackground(atlas) {
|
|||
backgroundContext.fillStyle = "rgba(0, 0, 0, 0.6)"
|
||||
backgroundContext.fillRect(0, 0, backgroundCanvas.width, backgroundCanvas.height)
|
||||
|
||||
for (let i = 0; i < atlas.length; i++) {
|
||||
for (const entry of Object.values(atlas)) {
|
||||
|
||||
const path = atlas[i].path
|
||||
const path = entry.path
|
||||
|
||||
backgroundContext.beginPath()
|
||||
|
||||
|
@ -279,7 +283,7 @@ function renderBackground(atlas) {
|
|||
backgroundContext.closePath()
|
||||
|
||||
let bgStrokeStyle
|
||||
switch (atlas[i].diff) {
|
||||
switch (entry.diff) {
|
||||
case "add":
|
||||
bgStrokeStyle = "rgba(0, 255, 0, 1)"
|
||||
backgroundContext.lineWidth = 2
|
||||
|
@ -299,45 +303,39 @@ function renderBackground(atlas) {
|
|||
backgroundContext.strokeStyle = bgStrokeStyle
|
||||
backgroundContext.stroke()
|
||||
backgroundContext.lineWidth = 1
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function buildObjectsList(filter, sort) {
|
||||
function filterAtlas(prevAtlas) {
|
||||
|
||||
atlasDisplay = atlas.slice()
|
||||
const sort = sortInput.value || defaultSort
|
||||
const search = searchInput?.value.toLowerCase()
|
||||
let newAtlas = Object.assign({}, prevAtlas)
|
||||
let newAtlasOrder = []
|
||||
|
||||
if (filter) {
|
||||
atlasDisplay = atlas.filter(entry => {
|
||||
return (
|
||||
entry.name.toLowerCase().includes(filter.toLowerCase())
|
||||
|| entry.description?.toLowerCase().includes(filter.toLowerCase())
|
||||
|| Object.values(entry.links).flat().some(str => str.toLowerCase().includes(filter))
|
||||
|| entry.id.toString() === filter
|
||||
)
|
||||
})
|
||||
document.getElementById("atlasSize").innerHTML = "Found " + atlasDisplay.length + " entries."
|
||||
} else {
|
||||
document.getElementById("atlasSize").innerHTML = "The Atlas contains " + atlasDisplay.length + " entries."
|
||||
document.getElementById("atlasSize").innerHTML = ""
|
||||
|
||||
if (search) {
|
||||
for (const [id, entry] of Object.entries(prevAtlas)) {
|
||||
if (!(
|
||||
entry.name.toLowerCase().includes(search.toLowerCase()) ||
|
||||
entry.description?.toLowerCase().includes(search.toLowerCase()) ||
|
||||
Object.values(entry.links).flat().some(str => str.toLowerCase().includes(search)) ||
|
||||
id.toString() === search
|
||||
)) delete newAtlas[id]
|
||||
}
|
||||
}
|
||||
|
||||
renderBackground(atlasDisplay)
|
||||
render()
|
||||
|
||||
sort ||= defaultSort
|
||||
document.getElementById("sort").value = sort
|
||||
|
||||
//console.log(sort)
|
||||
// document.getElementById("sort").value = sort
|
||||
|
||||
let sortFunction
|
||||
|
||||
//console.log(sort)
|
||||
|
||||
switch (sort) {
|
||||
case "shuffle":
|
||||
sortFunction = null
|
||||
if (entriesOffset === 0) {
|
||||
shuffle()
|
||||
}
|
||||
sortFunction = () => Math.random() - 0.5
|
||||
break
|
||||
case "alphaAsc":
|
||||
sortFunction = (a, b) => a.name.toLowerCase().localeCompare(b.name.toLowerCase())
|
||||
|
@ -369,10 +367,37 @@ function buildObjectsList(filter, sort) {
|
|||
break
|
||||
}
|
||||
|
||||
newAtlasOrder = Object.keys(newAtlas)
|
||||
if (sortFunction) {
|
||||
atlasDisplay.sort(sortFunction)
|
||||
newAtlasOrder = newAtlasOrder.sort((a, b) => sortFunction(prevAtlas[a], prevAtlas[b]))
|
||||
}
|
||||
|
||||
// console.log(newAtlas, newAtlasOrder)
|
||||
|
||||
return [newAtlas, newAtlasOrder]
|
||||
|
||||
}
|
||||
|
||||
function updateAtlas() {
|
||||
;[atlas, atlasOrder] = filterAtlas(atlasAll)
|
||||
;[atlasDisplay, atlasOrder] = generateAtlasDisplay(atlas, atlasOrder, currentPeriod, currentVariation)
|
||||
const atlasSizeEl = document.getElementById("atlasSize")
|
||||
if (Object.keys(atlas).length === Object.keys(atlasAll).length) {
|
||||
atlasSizeEl.innerHTML = Object.keys(atlasAll).length + " entries in total."
|
||||
} else {
|
||||
atlasSizeEl.innerHTML = "Found " + Object.keys(atlas).length + " entries."
|
||||
}
|
||||
atlasSizeEl.innerHTML += " Displaying " + Object.keys(atlasDisplay).length + " entries."
|
||||
resetEntriesList()
|
||||
renderBackground(atlasDisplay)
|
||||
renderHighlight(atlasDisplay)
|
||||
}
|
||||
|
||||
async function resetEntriesList() {
|
||||
entriesOffset = 0
|
||||
entriesList.replaceChildren()
|
||||
entriesList.appendChild(moreEntriesButton)
|
||||
|
||||
moreEntriesButton.removeEventListener('click', showMoreEntries)
|
||||
showMoreEntries = () => {
|
||||
|
||||
|
@ -380,89 +405,92 @@ function buildObjectsList(filter, sort) {
|
|||
entriesList.removeChild(moreEntriesButton)
|
||||
}
|
||||
|
||||
for (let i = entriesOffset; i < entriesOffset + entriesLimit; i++) {
|
||||
let entriesLeft = entriesLimit
|
||||
let element
|
||||
|
||||
if (i >= atlasDisplay.length) break
|
||||
while (entriesLeft > 0 && atlasOrder.length > entriesOffset) {
|
||||
|
||||
if (atlasDisplay[atlasOrder[entriesOffset]]) {
|
||||
// console.log(i, entriesLeft)
|
||||
|
||||
const element = createInfoBlock(atlasDisplay[i])
|
||||
const entry = atlasDisplay[i]
|
||||
|
||||
element.addEventListener("mouseenter", function () {
|
||||
if (fixed || dragging) return
|
||||
objectsContainer.replaceChildren()
|
||||
|
||||
previousScaleZoomOrigin ??= [...scaleZoomOrigin]
|
||||
previousZoom ??= zoom
|
||||
setView(entry.center[0], entry.center[1], setZoomByPath(entry.path))
|
||||
|
||||
hovered = [entry]
|
||||
render()
|
||||
hovered[0].element = this
|
||||
updateLines()
|
||||
|
||||
})
|
||||
|
||||
element.addEventListener("click", e => {
|
||||
toggleFixed(e)
|
||||
if (!fixed) return
|
||||
previousScaleZoomOrigin ??= [...scaleZoomOrigin]
|
||||
previousZoom ??= zoom
|
||||
applyView()
|
||||
})
|
||||
|
||||
element.addEventListener("mouseleave", () => {
|
||||
if (fixed || dragging) return
|
||||
|
||||
scaleZoomOrigin = [...previousScaleZoomOrigin]
|
||||
zoom = previousZoom
|
||||
previousScaleZoomOrigin = undefined
|
||||
previousZoom = undefined
|
||||
applyView()
|
||||
|
||||
hovered = []
|
||||
updateLines()
|
||||
render()
|
||||
})
|
||||
let entry = atlasDisplay[atlasOrder[entriesOffset]]
|
||||
element = createInfoBlock(entry)
|
||||
|
||||
element.addEventListener("mouseenter", function () {
|
||||
if (fixed || dragging) return
|
||||
objectsContainer.replaceChildren()
|
||||
|
||||
previousScaleZoomOrigin ??= [...scaleZoomOrigin]
|
||||
previousZoom ??= zoom
|
||||
setView(entry.center[0], entry.center[1], calculateZoomFromPath(entry.path))
|
||||
|
||||
hovered = [entry]
|
||||
renderHighlight()
|
||||
hovered[0].element = this
|
||||
renderLines()
|
||||
|
||||
})
|
||||
|
||||
element.addEventListener("click", e => {
|
||||
fixed = true
|
||||
previousScaleZoomOrigin ??= [...scaleZoomOrigin]
|
||||
previousZoom ??= zoom
|
||||
applyView()
|
||||
})
|
||||
|
||||
element.addEventListener("mouseleave", () => {
|
||||
if (fixed || dragging) return
|
||||
|
||||
scaleZoomOrigin = [...previousScaleZoomOrigin]
|
||||
zoom = previousZoom
|
||||
previousScaleZoomOrigin = undefined
|
||||
previousZoom = undefined
|
||||
applyView()
|
||||
|
||||
hovered = []
|
||||
renderLines()
|
||||
renderHighlight()
|
||||
})
|
||||
} else {
|
||||
let entry = atlas[atlasOrder[entriesOffset]]
|
||||
element = createInfoBlock(entry, 1)
|
||||
|
||||
element.addEventListener("click", async e => {
|
||||
e.preventDefault()
|
||||
const [nearestPeriod, nearestVariation] = getNearestPeriod(entry, currentPeriod, currentVariation)
|
||||
|
||||
await updateTime(nearestPeriod, nearestVariation, true)
|
||||
|
||||
entry = atlasDisplay[entry.id]
|
||||
element = createInfoBlock(entry)
|
||||
hovered = [{ ...entry, element }]
|
||||
fixed = true
|
||||
previousScaleZoomOrigin = undefined
|
||||
previousZoom = undefined
|
||||
|
||||
const hash = formatHash(entry.id, nearestPeriod, nearestVariation, entry.center[0], entry.center[1], calculateZoomFromPath(entry.path))
|
||||
location.hash = hash
|
||||
})
|
||||
}
|
||||
|
||||
entriesOffset += 1
|
||||
entriesLeft -= 1
|
||||
|
||||
entriesList.appendChild(element)
|
||||
|
||||
}
|
||||
|
||||
entriesOffset += entriesLimit
|
||||
|
||||
if (atlasDisplay.length > entriesOffset) {
|
||||
moreEntriesButton.innerHTML = "Show " + Math.min(entriesLimit, atlasDisplay.length - entriesOffset) + " more"
|
||||
if (atlasOrder.length > entriesOffset) {
|
||||
moreEntriesButton.innerHTML = "Show " + Math.min(entriesLimit, atlasOrder.length - entriesOffset) + " more"
|
||||
entriesList.appendChild(moreEntriesButton)
|
||||
}
|
||||
|
||||
}
|
||||
moreEntriesButton.addEventListener('click', showMoreEntries)
|
||||
showMoreEntries()
|
||||
|
||||
}
|
||||
|
||||
function shuffle() {
|
||||
//console.log("shuffled atlas")
|
||||
for (let i = atlasDisplay.length - 1; i > 0; i--) {
|
||||
const j = Math.floor(Math.random() * (i + 1))
|
||||
const temp = atlasDisplay[i]
|
||||
atlasDisplay[i] = atlasDisplay[j]
|
||||
atlasDisplay[j] = temp
|
||||
}
|
||||
}
|
||||
|
||||
function resetEntriesList() {
|
||||
entriesOffset = 0
|
||||
entriesList.replaceChildren()
|
||||
entriesList.appendChild(moreEntriesButton)
|
||||
|
||||
const sort = sortInput.value || defaultSort
|
||||
const search = searchInput?.value.toLowerCase()
|
||||
|
||||
buildObjectsList(search, sort)
|
||||
}
|
||||
|
||||
async function render() {
|
||||
async function renderHighlight() {
|
||||
|
||||
highlightContext.clearRect(0, 0, highlightCanvas.width, highlightCanvas.height)
|
||||
|
||||
|
@ -478,10 +506,8 @@ async function render() {
|
|||
container.style.cursor = "default"
|
||||
}
|
||||
|
||||
|
||||
for (let i = 0; i < hovered.length; i++) {
|
||||
|
||||
|
||||
const path = hovered[i].path
|
||||
|
||||
highlightContext.beginPath()
|
||||
|
@ -507,28 +533,6 @@ async function render() {
|
|||
highlightContext.globalCompositeOperation = "source-out"
|
||||
highlightContext.drawImage(backgroundCanvas, 0, 0)
|
||||
|
||||
if (hovered.length === 1 && hovered[0].path.length && hovered[0].overrideImage) {
|
||||
const undisputableHovered = hovered[0]
|
||||
// Find the left-topmost point of all the paths
|
||||
const entryPosition = getPositionOfEntry(undisputableHovered)
|
||||
if (entryPosition) {
|
||||
const [startX, startY] = entryPosition
|
||||
const overrideImage = new Image()
|
||||
const loadingPromise = new Promise((res, rej) => {
|
||||
overrideImage.onerror = rej
|
||||
overrideImage.onload = res
|
||||
})
|
||||
overrideImage.src = "imageOverrides/" + undisputableHovered.overrideImage
|
||||
try {
|
||||
await loadingPromise
|
||||
highlightContext.globalCompositeOperation = "source-over"
|
||||
highlightContext.drawImage(overrideImage, startX, startY)
|
||||
} catch (ex) {
|
||||
console.error("Cannot override image.", ex)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (let i = 0; i < hovered.length; i++) {
|
||||
|
||||
const path = hovered[i].path
|
||||
|
@ -592,11 +596,15 @@ function updateHovering(e, tapped) {
|
|||
|
||||
if (!(pos[0] <= canvasSize.x + canvasOffset.x + 200 && pos[0] >= canvasOffset.x - 200 && pos[1] <= canvasSize.y + canvasOffset.y + 200 && pos[1] >= canvasOffset.x - 200)) return
|
||||
|
||||
const newHovered = []
|
||||
for (const entry of atlasDisplay) {
|
||||
let newHovered = []
|
||||
for (const entry of Object.values(atlasDisplay)) {
|
||||
if (pointIsInPolygon(pos, entry.path)) newHovered.push(entry)
|
||||
}
|
||||
|
||||
newHovered = newHovered.sort(function (a, b) {
|
||||
return calcPolygonArea(a.path) - calcPolygonArea(b.path)
|
||||
})
|
||||
|
||||
let changed = false
|
||||
|
||||
if (hovered.length === newHovered.length) {
|
||||
|
@ -612,9 +620,7 @@ function updateHovering(e, tapped) {
|
|||
|
||||
if (!changed) return
|
||||
|
||||
hovered = newHovered.sort(function (a, b) {
|
||||
return calcPolygonArea(a.path) - calcPolygonArea(b.path)
|
||||
})
|
||||
hovered = newHovered
|
||||
|
||||
objectsContainer.replaceChildren()
|
||||
|
||||
|
@ -639,12 +645,13 @@ function updateHovering(e, tapped) {
|
|||
objectsListOverflowNotice.classList.add("d-none")
|
||||
entriesList.classList.remove("disableHover")
|
||||
}
|
||||
render()
|
||||
renderLines()
|
||||
renderHighlight()
|
||||
}
|
||||
|
||||
window.addEventListener("hashchange", updateViewFromHash)
|
||||
|
||||
function updateViewFromHash() {
|
||||
async function updateViewFromHash() {
|
||||
|
||||
const hash = window.location.hash.substring(1); //Remove hash prefix
|
||||
let [hashEntryId, hashPeriod, hashX, hashY, hashZoom] = hash.split('/')
|
||||
|
@ -666,7 +673,7 @@ function updateViewFromHash() {
|
|||
targetPeriod = defaultPeriod
|
||||
targetVariation = defaultVariation
|
||||
}
|
||||
updateTime(targetPeriod, targetVariation, true)
|
||||
await updateTime(targetPeriod, targetVariation)
|
||||
|
||||
setView(
|
||||
(isNaN(hashX) || hashX === '') ? undefined : Number(hashX),
|
||||
|
@ -678,13 +685,8 @@ function updateViewFromHash() {
|
|||
|
||||
// Highlight entry from hash
|
||||
|
||||
const entries = atlas.filter(e => {
|
||||
return e.id.toString() === hashEntryId
|
||||
})
|
||||
|
||||
if (entries.length !== 1) return
|
||||
|
||||
const entry = entries[0]
|
||||
const entry = atlasDisplay[hashEntryId]
|
||||
if (!entry) return
|
||||
|
||||
document.title = entry.name + " on " + pageTitle
|
||||
|
||||
|
@ -702,24 +704,24 @@ function updateViewFromHash() {
|
|||
objectsContainer.replaceChildren()
|
||||
objectsContainer.appendChild(infoElement)
|
||||
|
||||
renderBackground(atlas)
|
||||
setView(
|
||||
(isNaN(hashX) || hashX === '') ? entry.center[0] : Number(hashX),
|
||||
(isNaN(hashY) || hashY === '') ? entry.center[1] : Number(hashY),
|
||||
(isNaN(hashZoom) || hashZoom === '') ? setZoomByPath(entry.path) : Number(hashZoom)
|
||||
(isNaN(hashZoom) || hashZoom === '') ? calculateZoomFromPath(entry.path) : Number(hashZoom)
|
||||
)
|
||||
|
||||
closeObjectsListButton.classList.remove("d-none")
|
||||
entriesList.classList.add("disableHover")
|
||||
|
||||
hovered = [entry]
|
||||
render()
|
||||
hovered[0].element = infoElement
|
||||
updateLines()
|
||||
hovered = [{...entry, element: infoElement}]
|
||||
renderBackground(atlasDisplay)
|
||||
renderHighlight(atlasDisplay)
|
||||
renderLines()
|
||||
}
|
||||
|
||||
function setZoomByPath(path) {
|
||||
function calculateZoomFromPath(path) {
|
||||
|
||||
let zoom
|
||||
let boundingBox = [canvasSize.x + canvasOffset.x, canvasOffset.x, canvasSize.y + canvasOffset.y, canvasOffset.y]
|
||||
path?.forEach(([x, y]) => {
|
||||
boundingBox[0] = Math.min(boundingBox[0], x)
|
||||
|
@ -741,13 +743,10 @@ function setZoomByPath(path) {
|
|||
|
||||
function initView() {
|
||||
|
||||
buildObjectsList(null, null)
|
||||
renderBackground(atlas)
|
||||
render()
|
||||
|
||||
updateAtlas()
|
||||
|
||||
document.addEventListener('timeupdate', () => {
|
||||
atlasDisplay = atlas.slice()
|
||||
resetEntriesList()
|
||||
updateAtlas()
|
||||
})
|
||||
|
||||
// parse linked atlas entry id from link hash
|
||||
|
@ -758,22 +757,20 @@ function initView() {
|
|||
}*/
|
||||
|
||||
applyView()
|
||||
render()
|
||||
updateLines()
|
||||
renderLines()
|
||||
|
||||
}
|
||||
|
||||
function initExplore() {
|
||||
|
||||
window.updateHovering = updateHovering
|
||||
window.render = () => { }
|
||||
window.renderHighlight = () => { }
|
||||
|
||||
function updateHovering(e, tapped) {
|
||||
if (dragging || (fixed && !tapped)) return
|
||||
updateCoordsDisplay(e)
|
||||
}
|
||||
|
||||
renderBackground(atlas)
|
||||
renderBackground({})
|
||||
|
||||
applyView()
|
||||
|
||||
|
@ -837,6 +834,6 @@ function initViewGlobal() {
|
|||
}
|
||||
|
||||
document.addEventListener('timeupdate', event => {
|
||||
drawButton.href = "./?mode=draw" + formatHash(null, event.detail.period, event.detail.period, event.detail.variation)
|
||||
drawButton.href = "./?mode=draw" + formatHash(null, event.detail.period, event.detail.variation)
|
||||
})
|
||||
}
|
||||
|
|
|
@ -35,6 +35,8 @@
|
|||
|
||||
<meta name="viewport" content="width=device-width, height=device-height, initial-scale=1, viewport-fit=cover">
|
||||
<meta name="color-scheme" content="light dark">
|
||||
<meta name="theme-color" media="(prefers-color-scheme: light)" content="white">
|
||||
<meta name="theme-color" media="(prefers-color-scheme: dark)" content="#222">
|
||||
|
||||
<link rel="apple-touch-icon" href="_img/apple-touch-icon.png" sizes="180x180">
|
||||
<link rel="icon alternate" href="_img/favicon.png" type="image/png" class="js-site-favicon">
|
||||
|
|
|
@ -9,16 +9,16 @@
|
|||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>The 2022 r/place Atlas</title>
|
||||
<meta name="description" content="An interactive map of Reddit's 2022 r/place, with information to each artwork of the canvas provided by the community.">
|
||||
<title>The 2023 r/place Atlas</title>
|
||||
<meta name="description" content="An interactive map of Reddit's 2023 r/place, with information to each artwork of the canvas provided by the community.">
|
||||
<meta name="author" content="Place Atlas contributors (original by Roland Rytz)">
|
||||
<meta name="application-name" content="The r/place Atlas 2022">
|
||||
<meta name="application-name" content="The r/place Atlas 2023">
|
||||
<meta name="robots" content="index, follow">
|
||||
|
||||
<meta property="og:title" content="The 2022 r/place Atlas">
|
||||
|
||||
<meta property="og:title" content="The 2023 r/place Atlas">
|
||||
<meta property="og:type" content="website">
|
||||
<meta property="og:url" content="https://2022.place-atlas.stefanocoding.me/">
|
||||
<meta property="og:image" content="https://2022.place-atlas.stefanocoding.me/_img/logo.png">
|
||||
<meta property="og:url" content="https://2023.place-atlas.stefanocoding.me/">
|
||||
<meta property="og:image" content="https://2023.place-atlas.stefanocoding.me/_img/logo.png">
|
||||
<meta property="og:image:type" content="image/png">
|
||||
<meta property="og:image:width" content="512">
|
||||
<meta property="og:image:height" content="512">
|
||||
|
@ -34,17 +34,17 @@
|
|||
<meta name="twitter:image:alt" content="The 2022 r/place Atlas logo">
|
||||
|
||||
<!-- <meta name="google-site-verification" content="gZGHpBSMzffAbIn0qB8b00We6EwSGkDTfDoQVv-NWss"/> -->
|
||||
|
||||
|
||||
<meta name="viewport" content="width=device-width, height=device-height, initial-scale=1, minimum-scale=1, maximum-scale=1, shrink-to-fit=no, viewport-fit=cover"> <!-- user-scalable=no -->
|
||||
<meta name="mobile-web-app-capable" content="yes">
|
||||
<meta name="color-scheme" content="light dark">
|
||||
<meta name="theme-color" media="(prefers-color-scheme: light)" content="white">
|
||||
<meta name="theme-color" media="(prefers-color-scheme: dark)" content="#222">
|
||||
|
||||
|
||||
<link rel="apple-touch-icon" href="_img/apple-touch-icon.png" sizes="180x180">
|
||||
<link rel="icon alternate" href="_img/favicon.png" type="image/png" class="js-site-favicon">
|
||||
<link rel="icon" href="_img/favicon.svg" type="image/svg+xml" class="js-site-favicon">
|
||||
|
||||
|
||||
<link rel="stylesheet" href="./_css/style.css">
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-dark-5@1.1.3/dist/css/bootstrap-dark.min.css">
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.8.1/font/bootstrap-icons.css">
|
||||
|
@ -138,18 +138,19 @@
|
|||
<ul class="navbar-nav me-auto my-2 my-md-0">
|
||||
<li class="nav-item">
|
||||
<div class="btn-group" role="group">
|
||||
<button id="showListButton" class="btn btn-outline-primary" type="button" data-bs-toggle="offcanvas" data-bs-target="#offcanvasList" aria-controls="offcanvasList">Entries List</button>
|
||||
<button id="showListButton" class="btn btn-outline-primary" type="button" data-bs-toggle="offcanvas" data-bs-target="#offcanvasList" aria-controls="offcanvasList">Menu</button>
|
||||
<a class="btn btn-outline-primary" id="drawLink" href="./?mode=draw">Draw</a>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
<ul class="navbar-nav mx-auto d-block d-md-none d-lg-block">
|
||||
<li class="nav-item d-flex align-items-center gap-2">
|
||||
<span class="p-2">Coordinates: <span class="badge bg-secondary" id="coords_p">0, 0</span></span>
|
||||
<span class="p-md-2">Coordinates: <span class="badge bg-secondary" id="coords_p">0, 0</span></span>
|
||||
</li>
|
||||
</ul>
|
||||
<hr class="d-md-none">
|
||||
<ul class="navbar-nav flex-row flex-wrap ms-auto">
|
||||
<li class="nav-item dropdown">
|
||||
<li class="nav-item col-6 col-md-auto dropdown">
|
||||
<a class="nav-link dropdown-toggle active" href="#" role="button" data-bs-toggle="dropdown" aria-expanded="false" aria-current="page">
|
||||
Atlas Map
|
||||
</a>
|
||||
|
@ -199,8 +200,7 @@ <h5 class="offcanvas-title" id="offcanvasListLabel">Atlas Entries List</h5>
|
|||
</header>
|
||||
<div class="py-3 mx-3 border-bottom">
|
||||
<div class="d-flex gap-2 mb-2">
|
||||
<a class="btn btn-primary w-50" id="drawLink" href="./?mode=draw">Draw</a>
|
||||
<button type="button" class="btn btn-primary dropdown-toggle w-50" id="dropdownModes" data-bs-toggle="dropdown" aria-expanded="false">Modes</button>
|
||||
<button type="button" class="btn btn-primary dropdown-toggle w-100" id="dropdownModes" data-bs-toggle="dropdown" aria-expanded="false">Layer Views</button>
|
||||
<ul class="dropdown-menu dropdown-menu-end" aria-labelledby="dropdownModes">
|
||||
<li><a class="dropdown-item" href="./">Normal</a></li>
|
||||
<li><a class="dropdown-item" href="./?mode=explore">Explore</a></li>
|
||||
|
@ -295,7 +295,7 @@ <h5>Draw</h5>
|
|||
Redo
|
||||
</button>
|
||||
</div>
|
||||
<button type="button" class="btn btn-primary" id="finishButton" title="Finish drawing" disabled>Finish</button>
|
||||
<button type="button" class="btn btn-primary" id="finishButton" title="Finish drawing" disabled>Next</button>
|
||||
<button type="button" class="btn btn-secondary" id="resetButton">Reset</button>
|
||||
|
||||
<div class="form-check" id="highlightUnchartedLabel">
|
||||
|
@ -332,7 +332,7 @@ <h6>Need Help?</h6>
|
|||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="descriptionField" class="form-label">Description</label>
|
||||
<textarea id="descriptionField" class="form-control overflow-hidden" placeholder="A short description to be understood by everyone"></textarea>
|
||||
<textarea id="descriptionField" class="form-control overflow-hidden" placeholder="A short description to be understood by everyone."></textarea>
|
||||
</div>
|
||||
<label id="websiteLabel" class="form-label">Website</label>
|
||||
<div id="websiteGroup" class="mb-3 d-flex flex-column gap-2"></div>
|
||||
|
|
|
@ -3,9 +3,9 @@
|
|||
"display": "standalone",
|
||||
"scope": "/",
|
||||
"start_url": "/",
|
||||
"name": "The r/place Atlas",
|
||||
"short_name": "r/placeAtlas2",
|
||||
"description": "The atlas for the r/place event of 2022 hosted on Reddit.",
|
||||
"name": "The 2022 r/place Atlas",
|
||||
"short_name": "r/placeAtlas2022",
|
||||
"description": "The atlas for Reddit's r/place event of 2022.",
|
||||
"icons": [
|
||||
{
|
||||
"src": "./_img/pwa/logo-round-192x192.png",
|
||||
|
|
|
@ -24,7 +24,7 @@ workbox.routing.registerRoute(
|
|||
|
||||
workbox.routing.registerRoute(
|
||||
({ url }) => url.pathname.startsWith('/_img/canvas/'),
|
||||
new workbox.strategies.CacheFirst({
|
||||
new workbox.strategies.StaleWhileRevalidate({
|
||||
cacheName: "canvas",
|
||||
plugins: [
|
||||
new workbox.backgroundSync.BackgroundSyncPlugin(
|
||||
|
|
Loading…
Reference in a new issue