Merge pull request #1492 from Hans5958/sync/240112

Update codebase
This commit is contained in:
Hans5958 2024-01-24 23:20:04 +07:00 committed by GitHub
commit f836e06071
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
23 changed files with 761 additions and 632 deletions

View File

@ -121,6 +121,14 @@ Hereforth is an example of the structured entry data. The example has been expan
## Development ## Development
> [!IMPORTANT]
>
> Instead of here, consider directing your contributions to [The 2023 r/place Atlas](https://github.com/placeAtlas/atlas-2023).
>
> Updates from the latest Atlas would be backported occasionally for the benefit of the other instances (e.g. the 2017 Atlas and the 2022 Atlas). Furthermore, there is a great chance that what you want to do would also benefit the other instances.
>
> There may some cases where what you want to do only apply on this specific instance. In that case, you may proceed. When in doubt, don't hesitate to contact us.
Other than contributing to the Atlas data, code contributions are also accepted. Here are some information regarding some aspects on the repository. Other than contributing to the Atlas data, code contributions are also accepted. Here are some information regarding some aspects on the repository.
### Web interface ### Web interface

View File

@ -1,20 +1,20 @@
[![Entry count](https://img.shields.io/badge/dynamic/json?color=blue&label=entries&query=%24.length&url=https%3A%2F%2Fgithub.com%2FplaceAtlas%2Fatlas%2Fblob%2Fmaster%2Fweb%2Fatlas.json%3Fraw%3Dtrue)](https://2022.place-atlas.stefanocoding.me/) [![Entry count](https://img.shields.io/badge/dynamic/json?color=blue&label=entries&query=%24.length&url=https%3A%2F%2Fgithub.com%2FplaceAtlas%2Fatlas-2022%2Fblob%2Fmaster%2Fweb%2Fatlas.json%3Fraw%3Dtrue)](https://2022.place-atlas.stefanocoding.me/)
![Commit activity](https://img.shields.io/github/commit-activity/w/placeAtlas/atlas-2022) ![Commit activity](https://img.shields.io/github/commit-activity/w/placeAtlas/atlas-2022)
[![Netlify](https://img.shields.io/netlify/1e7291ce-0680-45ed-9843-47a32a992bbb?logo=netlify&logoColor=white)](https://app.netlify.com/sites/place-atlas/deploys) [![Netlify](https://img.shields.io/netlify/1e7291ce-0680-45ed-9843-47a32a992bbb?logo=netlify&logoColor=white)](https://app.netlify.com/sites/place-atlas-2022/deploys)
[![License](https://img.shields.io/github/license/placeAtlas/atlas-2022)](https://github.com/placeAtlas/atlas-2022/blob/master/LICENSE) [![License](https://img.shields.io/github/license/placeAtlas/atlas)](https://github.com/placeAtlas/atlas-2022/blob/master/LICENSE)
[![Discord](https://img.shields.io/discord/960791635342524496?color=%235865F2&logo=discord&logoColor=white)](https://discord.gg/pJkm23b2nA) [![Discord](https://img.shields.io/discord/960791635342524496?color=%235865F2&logo=discord&logoColor=white)](https://discord.gg/pJkm23b2nA)
[![Subreddit subscribers](https://img.shields.io/reddit/subreddit-subscribers/placeAtlas2?color=%23FF4500&label=r%2FplaceAtlas2&logo=reddit&logoColor=white)](https://www.reddit.com/r/placeAtlas2/) [![Subreddit subscribers](https://img.shields.io/reddit/subreddit-subscribers/placeAtlas2?color=%23FF4500&label=r%2FplaceAtlas2&logo=reddit&logoColor=white)](https://www.reddit.com/r/placeAtlas2/)
[![Website](https://img.shields.io/static/v1?label=website&message=2022.place-atlas.stefanocoding.me&color=blue)](https://2022.place-atlas.stefanocoding.me/) [![Website](https://img.shields.io/static/v1?label=website&message=2022.place-atlas.stefanocoding.me&color=blue)](https://2022.place-atlas.stefanocoding.me/)
# The 2022 r/place Atlas # The 2022 r/place Atlas
The 2022 r/place Atlas is a project aiming to chart all the artworks created during the r/place April Fools event on Reddit in 2022. It is made with information to each artwork of the canvas provided by the community. The 2022 r/place Atlas is an interactive map aiming to chart all the artworks created during the r/place April Fools event on Reddit in 2022. It is made with information to each artwork of the canvas provided by the community.
This project was established by Roland Rytz for the event in 2017, and further developed and maintained by the Place Atlas team and contributors. This project was established by Roland Rytz for the event in 2017, and further developed and maintained by the [Place Atlas Initiative](https://place-atlas.stefanocoding.me) and [contributors](https://github.com/placeAtlas/atlas-2022/graphs/contributors).
This project is licensed under the [GNU Affero General Public License v3.0](LICENSE). This project is licensed under the [GNU Affero General Public License v3.0](LICENSE).
You can check out the website by visiting [2022.place-atlas.stefanocoding.me](https://2022.place-atlas.stefanocoding.me/). If you want to keep distance from GitHub, you may visit [r/placeAtlas2](https://www.reddit.com/r/placeAtlas2/). You can check out the website by visiting [2022.place-atlas.stefanocoding.me](https://2022.place-atlas.stefanocoding.me/). The subreddit, [r/placeAtlas2](https://www.reddit.com/r/placeAtlas2/), is available for communications via Reddit.
## Contributing ## Contributing
@ -22,15 +22,18 @@ This project is open source, and contributions are welcome. In fact, the Atlas r
Please read [CONTRIBUTING.md](CONTRIBUTING.md) to learn how to submit a new entry, edit existing entries, or contribute to the development of the codebase. Please read [CONTRIBUTING.md](CONTRIBUTING.md) to learn how to submit a new entry, edit existing entries, or contribute to the development of the codebase.
[The r/placeAlas2 subreddit](https://reddit.com/r/placeAtlas2/) and [the Discord server](https://discord.gg/pJkm23b2nA) is also the place to submit all bug reports, feature requests, or questions. [The r/placeAtlas2 subreddit](https://reddit.com/r/placeAtlas2/) and [the Discord server](https://discord.gg/pJkm23b2nA) is also the place to submit all bug reports, feature requests, or questions.
## Contributors ## Contributors
> [!NOTE]
> For more credits, including entry contributors, please see [the Credits sections on the About page](https://2022.place-atlas.stefanocoding.me/about#credits).
<!-- ALL-CONTRIBUTORS-BADGE:START - Do not remove or modify this section --> <!-- ALL-CONTRIBUTORS-BADGE:START - Do not remove or modify this section -->
[![All Contributors](https://img.shields.io/badge/all_contributors-7-orange.svg?style=flat)](#contributors) [![All Contributors](https://img.shields.io/badge/all_contributors-7-orange.svg?style=flat)](#contributors)
<!-- ALL-CONTRIBUTORS-BADGE:END --> <!-- ALL-CONTRIBUTORS-BADGE:END -->
Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key))): Many thanks to these wonderful people for contributing on the scope of this repository. ([emoji key](https://allcontributors.org/docs/en/emoji-key)).
<!-- ALL-CONTRIBUTORS-LIST:START - Do not remove or modify this section --> <!-- ALL-CONTRIBUTORS-LIST:START - Do not remove or modify this section -->
<!-- prettier-ignore-start --> <!-- prettier-ignore-start -->

91
package-lock.json generated
View File

@ -14,7 +14,7 @@
"parcel": "^2.8.3", "parcel": "^2.8.3",
"parcel-namer-rewrite": "^2.0.0-rc.3", "parcel-namer-rewrite": "^2.0.0-rc.3",
"parcel-resolver-ignore": "^2.1.3", "parcel-resolver-ignore": "^2.1.3",
"postcss": "^8.4.31" "postcss": "^8.4.21"
} }
}, },
"node_modules/@babel/code-frame": { "node_modules/@babel/code-frame": {
@ -1926,9 +1926,9 @@
} }
}, },
"node_modules/all-contributors-cli": { "node_modules/all-contributors-cli": {
"version": "6.26.1", "version": "6.24.0",
"resolved": "https://registry.npmjs.org/all-contributors-cli/-/all-contributors-cli-6.26.1.tgz", "resolved": "https://registry.npmjs.org/all-contributors-cli/-/all-contributors-cli-6.24.0.tgz",
"integrity": "sha512-Ymgo3FJACRBEd1eE653FD1J/+uD0kqpUNYfr9zNC1Qby0LgbhDBzB3EF6uvkAbYpycStkk41J+0oo37Lc02yEw==", "integrity": "sha512-7oSKr2PnqxsOotuSwciltcFTS1eVRdjR0cn99hbElfff7gRQBShVhsf/XBprY41sLcgqTk0l0MKgKv6QNgZdMg==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"@babel/runtime": "^7.7.6", "@babel/runtime": "^7.7.6",
@ -1947,9 +1947,6 @@
}, },
"engines": { "engines": {
"node": ">=4" "node": ">=4"
},
"optionalDependencies": {
"prettier": "^2"
} }
}, },
"node_modules/all-contributors-cli/node_modules/ansi-styles": { "node_modules/all-contributors-cli/node_modules/ansi-styles": {
@ -3762,16 +3759,10 @@
"dev": true "dev": true
}, },
"node_modules/nanoid": { "node_modules/nanoid": {
"version": "3.3.6", "version": "3.3.4",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.6.tgz", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.4.tgz",
"integrity": "sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==", "integrity": "sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==",
"dev": true, "dev": true,
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/ai"
}
],
"bin": { "bin": {
"nanoid": "bin/nanoid.cjs" "nanoid": "bin/nanoid.cjs"
}, },
@ -3974,9 +3965,9 @@
} }
}, },
"node_modules/parcel-resolver-ignore": { "node_modules/parcel-resolver-ignore": {
"version": "2.1.5", "version": "2.1.3",
"resolved": "https://registry.npmjs.org/parcel-resolver-ignore/-/parcel-resolver-ignore-2.1.5.tgz", "resolved": "https://registry.npmjs.org/parcel-resolver-ignore/-/parcel-resolver-ignore-2.1.3.tgz",
"integrity": "sha512-/2zgQw3J/2YA7L6JXg4XKBWT/SXDZx+PfweWcCsllchNVwFvK7jDJhG6h+puy+e15Rm9A/ubuuHYwANQHVXp2A==", "integrity": "sha512-C8uLvR4o7SPRSsQ/Nylm1/PdsLwn/Z9bzCs66qT3XIebJC7ojaFFF3MDl/mie5audngjcFF8wzU0AoEQkZq2pA==",
"dev": true, "dev": true,
"engines": { "engines": {
"parcel": ">=2.0.0" "parcel": ">=2.0.0"
@ -4137,9 +4128,9 @@
} }
}, },
"node_modules/postcss": { "node_modules/postcss": {
"version": "8.4.31", "version": "8.4.21",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.21.tgz",
"integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==", "integrity": "sha512-tP7u/Sn/dVxK2NnruI4H9BG+x+Wxz6oeZ1cJ8P6G/PZY0IKk4k/63TDsQf2kQq3+qoJeLm2kIBUNlZe3zgb4Zg==",
"dev": true, "dev": true,
"funding": [ "funding": [
{ {
@ -4149,14 +4140,10 @@
{ {
"type": "tidelift", "type": "tidelift",
"url": "https://tidelift.com/funding/github/npm/postcss" "url": "https://tidelift.com/funding/github/npm/postcss"
},
{
"type": "github",
"url": "https://github.com/sponsors/ai"
} }
], ],
"dependencies": { "dependencies": {
"nanoid": "^3.3.6", "nanoid": "^3.3.4",
"picocolors": "^1.0.0", "picocolors": "^1.0.0",
"source-map-js": "^1.0.2" "source-map-js": "^1.0.2"
}, },
@ -4691,22 +4678,6 @@
"node": ">=12" "node": ">=12"
} }
}, },
"node_modules/prettier": {
"version": "2.8.8",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz",
"integrity": "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==",
"dev": true,
"optional": true,
"bin": {
"prettier": "bin-prettier.js"
},
"engines": {
"node": ">=10.13.0"
},
"funding": {
"url": "https://github.com/prettier/prettier?sponsor=1"
}
},
"node_modules/purgecss": { "node_modules/purgecss": {
"version": "5.0.0", "version": "5.0.0",
"resolved": "https://registry.npmjs.org/purgecss/-/purgecss-5.0.0.tgz", "resolved": "https://registry.npmjs.org/purgecss/-/purgecss-5.0.0.tgz",
@ -6550,9 +6521,9 @@
"dev": true "dev": true
}, },
"all-contributors-cli": { "all-contributors-cli": {
"version": "6.26.1", "version": "6.24.0",
"resolved": "https://registry.npmjs.org/all-contributors-cli/-/all-contributors-cli-6.26.1.tgz", "resolved": "https://registry.npmjs.org/all-contributors-cli/-/all-contributors-cli-6.24.0.tgz",
"integrity": "sha512-Ymgo3FJACRBEd1eE653FD1J/+uD0kqpUNYfr9zNC1Qby0LgbhDBzB3EF6uvkAbYpycStkk41J+0oo37Lc02yEw==", "integrity": "sha512-7oSKr2PnqxsOotuSwciltcFTS1eVRdjR0cn99hbElfff7gRQBShVhsf/XBprY41sLcgqTk0l0MKgKv6QNgZdMg==",
"dev": true, "dev": true,
"requires": { "requires": {
"@babel/runtime": "^7.7.6", "@babel/runtime": "^7.7.6",
@ -6564,7 +6535,6 @@
"lodash": "^4.11.2", "lodash": "^4.11.2",
"node-fetch": "^2.6.0", "node-fetch": "^2.6.0",
"pify": "^5.0.0", "pify": "^5.0.0",
"prettier": "^2",
"yargs": "^15.0.1" "yargs": "^15.0.1"
}, },
"dependencies": { "dependencies": {
@ -7853,9 +7823,9 @@
"dev": true "dev": true
}, },
"nanoid": { "nanoid": {
"version": "3.3.6", "version": "3.3.4",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.6.tgz", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.4.tgz",
"integrity": "sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==", "integrity": "sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==",
"dev": true "dev": true
}, },
"node-addon-api": { "node-addon-api": {
@ -8045,9 +8015,9 @@
} }
}, },
"parcel-resolver-ignore": { "parcel-resolver-ignore": {
"version": "2.1.5", "version": "2.1.3",
"resolved": "https://registry.npmjs.org/parcel-resolver-ignore/-/parcel-resolver-ignore-2.1.5.tgz", "resolved": "https://registry.npmjs.org/parcel-resolver-ignore/-/parcel-resolver-ignore-2.1.3.tgz",
"integrity": "sha512-/2zgQw3J/2YA7L6JXg4XKBWT/SXDZx+PfweWcCsllchNVwFvK7jDJhG6h+puy+e15Rm9A/ubuuHYwANQHVXp2A==", "integrity": "sha512-C8uLvR4o7SPRSsQ/Nylm1/PdsLwn/Z9bzCs66qT3XIebJC7ojaFFF3MDl/mie5audngjcFF8wzU0AoEQkZq2pA==",
"dev": true, "dev": true,
"requires": {} "requires": {}
}, },
@ -8103,12 +8073,12 @@
"dev": true "dev": true
}, },
"postcss": { "postcss": {
"version": "8.4.31", "version": "8.4.21",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.21.tgz",
"integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==", "integrity": "sha512-tP7u/Sn/dVxK2NnruI4H9BG+x+Wxz6oeZ1cJ8P6G/PZY0IKk4k/63TDsQf2kQq3+qoJeLm2kIBUNlZe3zgb4Zg==",
"dev": true, "dev": true,
"requires": { "requires": {
"nanoid": "^3.3.6", "nanoid": "^3.3.4",
"picocolors": "^1.0.0", "picocolors": "^1.0.0",
"source-map-js": "^1.0.2" "source-map-js": "^1.0.2"
} }
@ -8473,13 +8443,6 @@
"is-json": "^2.0.1" "is-json": "^2.0.1"
} }
}, },
"prettier": {
"version": "2.8.8",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz",
"integrity": "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==",
"dev": true,
"optional": true
},
"purgecss": { "purgecss": {
"version": "5.0.0", "version": "5.0.0",
"resolved": "https://registry.npmjs.org/purgecss/-/purgecss-5.0.0.tgz", "resolved": "https://registry.npmjs.org/purgecss/-/purgecss-5.0.0.tgz",

View File

@ -7,7 +7,7 @@
"parcel": "^2.8.3", "parcel": "^2.8.3",
"parcel-namer-rewrite": "^2.0.0-rc.3", "parcel-namer-rewrite": "^2.0.0-rc.3",
"parcel-resolver-ignore": "^2.1.3", "parcel-resolver-ignore": "^2.1.3",
"postcss": "^8.4.31", "postcss": "^8.4.21",
"all-contributors-cli": "^6.24.0" "all-contributors-cli": "^6.24.0"
}, },
"parcel-namer-rewrite": { "parcel-namer-rewrite": {
@ -18,7 +18,8 @@
}, },
"parcelIgnore": [ "parcelIgnore": [
"sw.js", "sw.js",
"_img/.+" "_img/.+",
"atlas.json"
], ],
"browserslist": [ "browserslist": [
">= 0.5%", ">= 0.5%",

View File

@ -20,6 +20,5 @@ cp -r web/_img/ dist/
cp web/atlas.json dist/ cp web/atlas.json dist/
cp web/*.txt dist/ cp web/*.txt dist/
cp web/_headers dist/ cp web/_headers dist/
cp web/_redirects dist/
cp web/favicon.ico dist/ cp web/favicon.ico dist/
cp web/sw.js dist/ cp web/sw.js dist/

View File

@ -19,6 +19,5 @@ cp -r web/_img/ dist/
cp web/atlas.json dist/ cp web/atlas.json dist/
cp web/*.txt dist/ cp web/*.txt dist/
cp web/_headers dist/ cp web/_headers dist/
cp web/_redirects dist/
cp web/favicon.ico dist/ cp web/favicon.ico dist/
cp web/sw.js dist/ cp web/sw.js dist/

View File

@ -1,8 +1,7 @@
<!-- <!--
The 2022 r/place Atlas The 2022 r/place Atlas
Copyright (c) 2017 Roland Rytz <roland@draemm.li> Copyright (c) 2017 Roland Rytz <roland@draemm.li>
Copyright (c) 2022 Place Atlas contributors Copyright (c) 2022 Place Atlas Initiative and contributors
Licensed under AGPL-3.0 (https://2022.place-atlas.stefanocoding.me/license.txt) Licensed under AGPL-3.0 (https://2022.place-atlas.stefanocoding.me/license.txt)
--> -->

View File

@ -1,7 +1,7 @@
/*! /*!
* The 2022 r/place Atlas * The 2022 r/place Atlas
* Copyright (c) 2017 Roland Rytz <roland@draemm.li> * Copyright (c) 2017 Roland Rytz <roland@draemm.li>
* Copyright (c) 2022 Place Atlas contributors * Copyright (c) 2022 Place Atlas Initiative and contributors
* Licensed under AGPL-3.0 (https://2022.place-atlas.stefanocoding.me/license.txt) * Licensed under AGPL-3.0 (https://2022.place-atlas.stefanocoding.me/license.txt)
*/ */
@ -52,6 +52,14 @@ body[data-mode] {
padding-left: env(safe-area-inset-left); 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()) { @supports (backdrop-filter: blur()) or (-webkit-backdrop-filter: blur()) {
.navbar, .offcanvas { .navbar, .offcanvas {
-webkit-backdrop-filter: saturate(180%) blur(15px); -webkit-backdrop-filter: saturate(180%) blur(15px);
@ -258,12 +266,12 @@ body[data-init-done] .listTransitioning #innerContainer {
} }
#variantControls { #variantControls {
flex: 1 0 auto; flex: 2 0 auto;
} }
#timeControls { #timeControls {
position: relative; position: relative;
flex: 6 0 300px; flex: 12 0 300px;
} }
#bottomBar.no-time-slider #timeControls { #bottomBar.no-time-slider #timeControls {
@ -440,13 +448,25 @@ body:not([data-dev]) .show-only-on-dev {
display: none !important display: none !important
} }
#objectsList,
#offcanvasList,
#offcanvasDraw,
#closeObjectsListButton {
margin-top: var(--global-top-padding);
}
.copyleft {
display: inline-block;
transform: rotateY(180deg);
}
/* about.html */ /* about.html */
#credits a { #entry-contributors-wrapper a {
text-decoration: none; text-decoration: none;
} }
#credits a:hover { #entry-contributors-wrapper a:hover {
text-decoration: underline; text-decoration: underline;
} }

View File

@ -1,8 +1,21 @@
/* /*
Access-Control-Allow-Origin: * Access-Control-Allow-Origin: *
/_img/canvas/*/*.png # Hashed resources: 1 year and immutable
cache-control: public, max-age=604800
/_js/*
Cache-Control: public, immutable, max-age=31536000
/_css/*
Cache-Control: public, immutable, max-age=31536000
# Canvas images: 1 year
/_img/canvas/*
Cache-Control: public, max-age=31536000
/_img/canvas/*.png /_img/canvas/*.png
cache-control: public, max-age=604800 Cache-Control: public, max-age=31536000
/_img/canvas/*/*.png
Cache-Control: public, max-age=31536000

View File

@ -1,11 +1,11 @@
/*! /*!
* The 2022 r/place Atlas * The 2022 r/place Atlas
* Copyright (c) 2017 Roland Rytz <roland@draemm.li> * Copyright (c) 2017 Roland Rytz <roland@draemm.li>
* Copyright (c) 2022 Place Atlas contributors * Copyright (c) 2022 Place Atlas Initiative and contributors
* Licensed under AGPL-3.0 (https://2022.place-atlas.stefanocoding.me/license.txt) * Licensed under AGPL-3.0 (https://2022.place-atlas.stefanocoding.me/license.txt)
*/ */
const contributorsEl = document.querySelector('#contributors-wrapper') const contributorsEl = document.querySelector('#entry-contributors-wrapper')
// <i aria-label="GitHub" class="bi bi-github"></i> // <i aria-label="GitHub" class="bi bi-github"></i>
const gitHubEl = document.createElement("i") const gitHubEl = document.createElement("i")
@ -34,6 +34,8 @@ fetch('all-authors.txt')
userEl.href = 'https://reddit.com/user/' + contributor userEl.href = 'https://reddit.com/user/' + contributor
userEl.textContent = contributor userEl.textContent = contributor
} }
userEl.target = '_blank'
userEl.rel = 'noreferrer'
contributorsEl.appendChild(userEl) contributorsEl.appendChild(userEl)
contributorsEl.appendChild(document.createTextNode(' ')) contributorsEl.appendChild(document.createTextNode(' '))
} }

View File

@ -265,8 +265,8 @@ window.useNumericalId = useNumericalId
console.info(`%cThe 2022 r/place Atlas console.info(`%cThe 2022 r/place Atlas
%cCopyright (c) 2017 Roland Rytz <roland@draemm.li> %cCopyright (c) 2017 Roland Rytz <roland@draemm.li>
Copyright (c) 2022 Place Atlas contributors Copyright (c) 2022 Place Atlas Initiative and contributors
Licensed under AGPL-3.0 (https://2022.place-atlas.stefanocoding.me/license.txt) Licensed under AGPL-3.0 (https://2023.place-atlas.stefanocoding.me/license.txt)
https://2022.place-atlas.stefanocoding.me/ https://2022.place-atlas.stefanocoding.me/
https://discord.gg/pJkm23b2nA https://discord.gg/pJkm23b2nA
@ -274,4 +274,4 @@ https://reddit.com/r/placeatlas2
https://github.com/placeAtlas/atlas-2022 https://github.com/placeAtlas/atlas-2022
To get the image of the canvas, use downloadCanvas(). To get the image of the canvas, use downloadCanvas().
`, 'font-size: 150%; line-height: 150%', '') `, 'font-size: 150%; line-height: 150%', '')

View File

@ -1,7 +1,7 @@
/*! /*!
* The 2022 r/place Atlas * The 2022 r/place Atlas
* Copyright (c) 2017 Roland Rytz <roland@draemm.li> * Copyright (c) 2017 Roland Rytz <roland@draemm.li>
* Copyright (c) 2022 Place Atlas contributors * Copyright (c) 2022 Place Atlas Initiative and contributors
* Licensed under AGPL-3.0 (https://2022.place-atlas.stefanocoding.me/license.txt) * Licensed under AGPL-3.0 (https://2022.place-atlas.stefanocoding.me/license.txt)
*/ */

View File

@ -1,7 +1,7 @@
/*! /*!
* The 2022 r/place Atlas * The 2022 r/place Atlas
* Copyright (c) 2017 Roland Rytz <roland@draemm.li> * Copyright (c) 2017 Roland Rytz <roland@draemm.li>
* Copyright (c) 2022 Place Atlas contributors * Copyright (c) 2022 Place Atlas Initiative and contributors
* Licensed under AGPL-3.0 (https://2022.place-atlas.stefanocoding.me/license.txt) * Licensed under AGPL-3.0 (https://2022.place-atlas.stefanocoding.me/license.txt)
*/ */
@ -69,7 +69,7 @@ const periodClipboard = {
const drawBackButton = document.createElement("a") const drawBackButton = document.createElement("a")
drawBackButton.className = "btn btn-outline-primary" drawBackButton.className = "btn btn-outline-primary"
drawBackButton.id = "drawBackButton" drawBackButton.id = "drawBackButton"
drawBackButton.textContent = "Exit Draw Mode" drawBackButton.textContent = "Exit Drawing"
const baseInputAddon = document.createElement("span") const baseInputAddon = document.createElement("span")
baseInputAddon.className = "input-group-text" 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.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.parentElement.appendChild(drawBackButton)
showListButton.remove() showListButton.remove()
drawButton.remove()
// Opens draw menu // Opens draw menu
wrapper.classList.remove('listHidden') wrapper.classList.remove('listHidden')
bsOffcanvasDraw.show() bsOffcanvasDraw.show()
window.render = render window.renderHighlight = renderHighlight
window.renderBackground = renderBackground window.renderBackground = renderBackground
window.updateHovering = updateHovering window.updateHovering = updateHovering
@ -125,13 +126,18 @@ function initDraw() {
let highlightUncharted = highlightUnchartedEl.checked let highlightUncharted = highlightUnchartedEl.checked
renderBackground(atlas) window.updateAtlas = updateAtlas
updateAtlas()
document.addEventListener('timeupdate', () => {
updateAtlas()
})
applyView() applyView()
container.style.cursor = "crosshair" container.style.cursor = "crosshair"
render(path)
container.addEventListener("mousedown", e => { container.addEventListener("mousedown", e => {
lastPos = [ lastPos = [
e.clientX, e.clientX,
@ -168,7 +174,7 @@ function initDraw() {
const coords = getCanvasCoords(e.clientX, e.clientY) const coords = getCanvasCoords(e.clientX, e.clientY)
path.push(coords) path.push(coords)
render(path) renderHighlight(path)
undoHistory = [] undoHistory = []
redoButton.disabled = true redoButton.disabled = true
@ -185,13 +191,13 @@ function initDraw() {
container.addEventListener("mousemove", e => { container.addEventListener("mousemove", e => {
if (!dragging && drawing && path.length > 0) { if (!dragging && drawing && path.length > 0) {
const coords = getCanvasCoords(e.clientX, e.clientY) const coords = getCanvasCoords(e.clientX, e.clientY)
render([...path, coords]) renderHighlight([...path, coords])
} }
}) })
container.addEventListener("mouseout", function () { container.addEventListener("mouseout", function () {
if (!dragging && drawing && path.length > 0) { if (!dragging && drawing && path.length > 0) {
render(path) renderHighlight(path)
} }
}) })
@ -228,19 +234,19 @@ function initDraw() {
undoButton.addEventListener("click", e => { undoButton.addEventListener("click", e => {
undo() undo()
const coords = getCanvasCoords(e.clientX, e.clientY) const coords = getCanvasCoords(e.clientX, e.clientY)
render([...path, coords]) renderHighlight([...path, coords])
}) })
redoButton.addEventListener("click", e => { redoButton.addEventListener("click", e => {
redo() redo()
const coords = getCanvasCoords(e.clientX, e.clientY) const coords = getCanvasCoords(e.clientX, e.clientY)
render([...path, coords]) renderHighlight([...path, coords])
}) })
resetButton.addEventListener("click", e => { resetButton.addEventListener("click", e => {
reset() reset()
const coords = getCanvasCoords(e.clientX, e.clientY) const coords = getCanvasCoords(e.clientX, e.clientY)
render([...path, coords]) renderHighlight([...path, coords])
}) })
resetButton.addEventListener("blur", function () { resetButton.addEventListener("blur", function () {
@ -269,7 +275,7 @@ function initDraw() {
highlightUnchartedEl.addEventListener("click", function () { highlightUnchartedEl.addEventListener("click", function () {
highlightUncharted = this.checked highlightUncharted = this.checked
render(path) renderHighlight(path)
}) })
function generateExportObject() { function generateExportObject() {
@ -332,7 +338,7 @@ function initDraw() {
if (exportArea.value > 40000) { if (exportArea.value > 40000) {
exportArea.value = " " + miniJsonString exportArea.value = " " + miniJsonString
} }
// Reddit // Reddit
let redditPostJsonString = " " + prettyJsonString.split("\n").join("\n ") let redditPostJsonString = " " + prettyJsonString.split("\n").join("\n ")
@ -385,13 +391,11 @@ function initDraw() {
} }
githubPostButton.href = githubPostUrl githubPostButton.href = githubPostUrl
console.log(githubPostUrl)
exportModal.show() exportModal.show()
} }
function preview() { function preview() {
let infoElement = createInfoBlock(generateExportObject(), true) let infoElement = createInfoBlock(generateExportObject(), 2)
objectsContainer.replaceChildren() objectsContainer.replaceChildren()
objectsContainer.appendChild(infoElement) objectsContainer.appendChild(infoElement)
closeObjectsListButton.classList.remove("d-none") closeObjectsListButton.classList.remove("d-none")
@ -483,16 +487,16 @@ function initDraw() {
closeObjectsListButton.classList.add("d-none") closeObjectsListButton.classList.add("d-none")
} }
function renderBackground() { function renderBackground(atlas) {
backgroundContext.clearRect(0, 0, highlightCanvas.width, highlightCanvas.height) backgroundContext.clearRect(0, 0, highlightCanvas.width, highlightCanvas.height)
backgroundContext.fillStyle = "rgba(0, 0, 0, 1)" 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++) { for (const entry of Object.values(atlas)) {
const path = atlas[i].path const path = entry.path
backgroundContext.beginPath() backgroundContext.beginPath()
@ -510,7 +514,7 @@ function initDraw() {
} }
} }
function render(path) { function renderHighlight(path) {
if (!Array.isArray(path)) return if (!Array.isArray(path)) return
@ -553,10 +557,16 @@ function initDraw() {
updateCoordsDisplay(e) updateCoordsDisplay(e)
} }
function updateAtlas() {
;[atlas, atlasOrder] = filterAtlas(atlasAll)
;[atlasDisplay, atlasOrder] = generateAtlasDisplay(atlas, atlasOrder, currentPeriod, currentVariation)
renderBackground(atlasDisplay)
renderHighlight(atlasDisplay)
}
const getEntry = id => { const getEntry = id => {
if (!id) return if (!id) return
const entries = atlasAll.filter(entry => entry.id.toString() === id.toString()) return atlasAll[id]
if (entries.length === 1) return entries[0]
} }
function addFieldButton(inputButton, inputGroup, array, index, name) { function addFieldButton(inputButton, inputGroup, array, index, name) {
@ -790,7 +800,7 @@ function initDraw() {
} else { } else {
document.getElementById("offcanvasDrawLabel").textContent = "New Entry" document.getElementById("offcanvasDrawLabel").textContent = "New Entry"
pathWithPeriods.push([formatPeriod(currentPeriod, currentPeriod, currentVariation), []]) pathWithPeriods.push([formatPeriod(currentPeriod, null, currentVariation), []])
// Builds multi-input list // Builds multi-input list
addWebsiteFields("", 0, [0]) addWebsiteFields("", 0, [0])
@ -805,25 +815,25 @@ function initDraw() {
const [,, hashX, hashY, hashZoom] = hash.split('/') const [,, hashX, hashY, hashZoom] = hash.split('/')
setView( setView(
(isNaN(hashX) || hashX === '') ? center[0] : Number(hashX), (isNaN(hashX) || hashX === '') ? center[0] : Number(hashX),
(isNaN(hashY) || hashY === '') ? center[1] : Number(hashY), (isNaN(hashY) || hashY === '') ? center[1] : Number(hashY),
(isNaN(hashZoom) || hashZoom === '') ? 4 : Number(hashZoom) (isNaN(hashZoom) || hashZoom === '') ? 4 : Number(hashZoom)
) )
document.addEventListener('timeupdate', () => { document.addEventListener('timeupdate', () => {
renderBackground(atlas) renderBackground(atlasDisplay)
updatePeriodGroups() updatePeriodGroups()
}) })
periodsAdd.addEventListener('click', () => { periodsAdd.addEventListener('click', () => {
pathWithPeriods.push([formatPeriod(currentPeriod, currentPeriod, currentVariation), []]) pathWithPeriods.push([formatPeriod(currentPeriod, null, currentVariation), []])
initPeriodGroups() initPeriodGroups()
}) })
drawBackButton.href = "./" + formatHash(entry?.id) drawBackButton.href = "./" + formatHash(entry?.id)
document.addEventListener('timeupdate', event => { 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)
}) })
} }
@ -862,7 +872,7 @@ function initPeriodGroups() {
const periodCopyEl = periodGroupEl.querySelector('.period-copy') const periodCopyEl = periodGroupEl.querySelector('.period-copy')
const periodDuplicateEl = periodGroupEl.querySelector('.period-duplicate') const periodDuplicateEl = periodGroupEl.querySelector('.period-duplicate')
const periodDeleteEl = periodGroupEl.querySelector('.period-delete') const periodDeleteEl = periodGroupEl.querySelector('.period-delete')
const periodVariationEl = periodGroupEl.querySelector('.period-variation') const periodVariationEl = periodGroupEl.querySelector('.period-variation')
const periodStatusEl = periodGroupEl.querySelector('.period-status') const periodStatusEl = periodGroupEl.querySelector('.period-status')
@ -926,19 +936,19 @@ function initPeriodGroups() {
}) })
startPeriodViewEl.addEventListener('click', () => { startPeriodViewEl.addEventListener('click', () => {
updateTime(parseInt(startPeriodEl.value), getCurrentVariation()) updateTime(parseInt(startPeriodEl.value), getCurrentVariation())
// Set zoom view // Set zoom view
periodCenter = calculateCenter(path) periodCenter = calculateCenter(path)
setView(periodCenter[0], periodCenter[1], setZoomByPath(path)) setView(periodCenter[0], periodCenter[1], setZoomByPath(path))
}) })
function getCurrentVariation() { function getCurrentVariation() {
return periodVariationEl[periodVariationEl.selectedIndex].value return periodVariationEl[periodVariationEl.selectedIndex].value
} }
function startPeriodUpdate(value) { function startPeriodUpdate(value) {
endPeriodListEl.innerHTML = '<option value="' + (parseInt(value) + 1) + '"></option>' endPeriodListEl.innerHTML = '<option value="' + (parseInt(value) + 1) + '"></option>'
// Update time only when value changes // Update time only when value changes
if (startPeriodEl.value !== timelineSlider.value) { if (startPeriodEl.value !== timelineSlider.value) {
timelineSlider.value = value timelineSlider.value = value
@ -1224,7 +1234,7 @@ function updatePeriodGroups() {
function updatePath(newPath, newUndoHistory) { function updatePath(newPath, newUndoHistory) {
path = newPath || path path = newPath || path
if (path.length > 3) center = calculateCenter(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 undoButton.disabled = path.length === 0; // Maybe make it undo the cancel action in the future
undoHistory = newUndoHistory || [] undoHistory = newUndoHistory || []
redoButton.disabled = (!undoHistory.length) redoButton.disabled = (!undoHistory.length)

View File

@ -1,7 +1,7 @@
/*! /*!
* The 2022 r/place Atlas * The 2022 r/place Atlas
* Copyright (c) 2017 Roland Rytz <roland@draemm.li> * Copyright (c) 2017 Roland Rytz <roland@draemm.li>
* Copyright (c) 2022 Place Atlas contributors * Copyright (c) 2022 Place Atlas Initiative and contributors
* Licensed under AGPL-3.0 (https://2022.place-atlas.stefanocoding.me/license.txt) * Licensed under AGPL-3.0 (https://2022.place-atlas.stefanocoding.me/license.txt)
*/ */
@ -31,7 +31,10 @@ function createInfoListItem(name, value) {
return entryInfoListElement 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") const element = document.createElement("div")
element.className = "card mb-2 overflow-hidden shadow" element.className = "card mb-2 overflow-hidden shadow"
@ -40,21 +43,31 @@ function createInfoBlock(entry, isPreview) {
const linkElement = document.createElement("a") const linkElement = document.createElement("a")
linkElement.className = "text-decoration-none d-flex justify-content-between text-body" linkElement.className = "text-decoration-none d-flex justify-content-between text-body"
if (isPreview) linkElement.href = "#"
else { let nearestPeriod = currentPeriod
linkElement.href = formatHash(entry.id, null, null, null, false, false, false) let nearestVariation = currentVariation
linkElement.addEventListener('click', e => { 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() e.preventDefault()
location.hash = formatHash(entry.id, null, null, null, false, false, false) location.hash = hash
window.dispatchEvent(new HashChangeEvent("hashchange")) window.dispatchEvent(new HashChangeEvent("hashchange"))
}) })
} }
const linkNameElement = document.createElement("span") const linkNameElement = document.createElement("span")
linkNameElement.className = "flex-grow-1 text-break" linkNameElement.className = "flex-grow-1 text-break"
linkNameElement.textContent = entry.name linkNameElement.textContent = entry.name
headerElement.appendChild(linkElement) headerElement.appendChild(linkElement)
linkElement.appendChild(linkNameElement) 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) element.appendChild(headerElement)
const bodyElement = document.createElement("div") const bodyElement = document.createElement("div")
@ -92,7 +105,7 @@ function createInfoBlock(entry, isPreview) {
} }
// Entry data submitted to preview does not include center or path // Entry data submitted to preview does not include center or path
if (!isPreview) { if (mode === 0) {
const [x, y] = entry?.center const [x, y] = entry?.center
listElement.appendChild(createInfoListItem("Position: ", `${Math.floor(x)}, ${Math.floor(y)}`)) listElement.appendChild(createInfoListItem("Position: ", `${Math.floor(x)}, ${Math.floor(y)}`))
@ -170,11 +183,11 @@ function createInfoBlock(entry, isPreview) {
element.appendChild(idElementContainer) element.appendChild(idElementContainer)
// Adds edit button only if element is not deleted // 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") 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.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 editElement.title = "Edit " + entry.name
idElementContainer.appendChild(editElement) idElementContainer.appendChild(editElement)
} }

View File

@ -1,7 +1,7 @@
/*! /*!
* The 2022 r/place Atlas * The 2022 r/place Atlas
* Copyright (c) 2017 Roland Rytz <roland@draemm.li> * Copyright (c) 2017 Roland Rytz <roland@draemm.li>
* Copyright (c) 2022 Place Atlas contributors * Copyright (c) 2022 Place Atlas Initiative and contributors
* Licensed under AGPL-3.0 (https://2022.place-atlas.stefanocoding.me/license.txt) * Licensed under AGPL-3.0 (https://2022.place-atlas.stefanocoding.me/license.txt)
*/ */
@ -26,7 +26,7 @@ const maxZoom = 128
const minZoom = 0.125 const minZoom = 0.125
let zoomOrigin = [0, 0] let zoomOrigin = [0, 0]
let scaleZoomOrigin = [0, 0] let scaleZoomOrigin = [canvasCenter.x, canvasCenter.y]
let dragging = false let dragging = false
let lastPosition = [0, 0] let lastPosition = [0, 0]
@ -54,7 +54,7 @@ function applyView() {
} }
function setView(targetX, targetY, targetZoom) { function setView(targetX, targetY, targetZoom) {
if (isNaN(targetX)) targetX = null if (isNaN(targetX)) targetX = null
if (isNaN(targetY)) targetY = null if (isNaN(targetY)) targetY = null
@ -72,8 +72,6 @@ function updateHash(...args) {
if (location.hash !== newLocation.hash) history.replaceState({}, "", newLocation) if (location.hash !== newLocation.hash) history.replaceState({}, "", newLocation)
} }
let atlas = null
window.atlas = atlas
let atlasAll = null let atlasAll = null
window.atlasAll = atlasAll window.atlasAll = atlasAll
@ -102,13 +100,24 @@ async function init() {
} }
} }
// Experimental: TemplateManager support
// Add a .json file of TemplateManager on the "template" URL param.
// e.g. ?template=https://osu.place/e/osuplace2023.json
// CORS bypass is required (e.g. a proxy, CORS Anywhere).
if (params.get("template")) {
const [ templateDatas ] = await loadTemplateData(params.get("template"))
const templateLayers = await loadTemplateImages(templateDatas)
additionalLayers.push(...templateLayers)
updateAdditionalLayer(additionalLayers)
}
if (mode === "about") window.location.replace("./about.html") if (mode === "about") window.location.replace("./about.html")
// For Reviewing Reddit Changes // For Reviewing Reddit Changes
// const atlasRef = '../tools/temp-atlas.json' // const atlasRef = '../tools/temp-atlas.json'
const atlasRef = params.get('atlas') || './atlas.json' const atlasAllUrl = params.get('atlas') || './atlas.json'
const atlasResp = await fetch(atlasRef) atlasAll = generateAtlasAll(await (await fetch(atlasAllUrl)).json())
atlasAll = updateAtlasAll(await atlasResp.json()) // console.log(atlas, atlasOrder)
const hash = window.location.hash.substring(1) const hash = window.location.hash.substring(1)
const [, hashPeriod, hashX, hashY, hashZoom] = hash.split('/') const [, hashPeriod, hashX, hashY, hashZoom] = hash.split('/')
@ -148,45 +157,33 @@ async function init() {
initExplore() initExplore()
} else if (mode.startsWith("diff")) { } else if (mode.startsWith("diff")) {
try { try {
const liveAtlasRef = params.get('liveatlas') || `https://${prodDomain}/atlas.json` const liveAtlasUrl = params.get('liveatlas') || `https://${prodDomain}/atlas.json`
const liveAtlasResp = await fetch(liveAtlasRef) let liveAtlasAll = generateAtlasAll(await (await fetch(liveAtlasUrl)).json())
let liveAtlas = await liveAtlasResp.json()
liveAtlas = updateAtlasAll(liveAtlas)
const liveAtlasReduced = liveAtlas.reduce((atlas, entry) => {
delete entry._index
atlas[entry.id] = entry
return atlas
}, {})
// Mark added/edited entries // Mark added/edited entries
atlasAll = atlasAll.map(function (entry) { for (const entry of Object.values(atlasAll)) {
delete entry._index if (!liveAtlasAll[entry.id]) {
if (!liveAtlasReduced[entry.id]) {
entry.diff = "add" 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" 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) { } catch (error) {
console.warn("Diff mode failed to load, reverting to normal view.", error) console.warn("Diff mode failed to load, reverting to normal view.", error)
@ -255,7 +252,7 @@ async function init() {
zoom = 1 zoom = 1
zoomOrigin = [0, 0] zoomOrigin = [0, 0]
scaleZoomOrigin = [0, 0] scaleZoomOrigin = [0, 0]
updateLines() renderLines()
applyView() applyView()
}) })
@ -384,7 +381,6 @@ async function init() {
} }
window.addEventListener("mousemove", e => { window.addEventListener("mousemove", e => {
// updateLines()
mousemove(e.clientX, e.clientY) mousemove(e.clientX, e.clientY)
if (dragging) { if (dragging) {
e.preventDefault() e.preventDefault()
@ -417,7 +413,7 @@ async function init() {
scaleZoomOrigin[0] += deltaX / zoom scaleZoomOrigin[0] += deltaX / zoom
scaleZoomOrigin[1] += deltaY / zoom scaleZoomOrigin[1] += deltaY / zoom
updateLines() renderLines()
applyView() applyView()
} }
@ -460,7 +456,7 @@ async function init() {
zoomOrigin[1] = scaleZoomOrigin[1] * zoom zoomOrigin[1] = scaleZoomOrigin[1] * zoom
applyView() applyView()
updateLines() renderLines()
} }
window.addEventListener("mouseup", e => { window.addEventListener("mouseup", e => {
@ -478,7 +474,7 @@ async function init() {
}) })
window.addEventListener("touchend", touchend) window.addEventListener("touchend", touchend)
function mouseup(x, y) { function mouseup() {
dragging = false dragging = false
updateHash() updateHash()
} }
@ -486,7 +482,7 @@ async function init() {
function touchend(e) { function touchend(e) {
if (e.touches.length === 0) { if (e.touches.length === 0) {
mouseup() mouseup()
setTimeout(() => updateLines(), 0) renderLines()
dragging = false dragging = false
} else if (e.touches.length === 1) { } else if (e.touches.length === 1) {
@ -506,7 +502,8 @@ async function init() {
} }
function updateAtlasAll(atlas = atlasAll) { function generateAtlasAll(atlas = atlasAll) {
const newAtlas = {}
for (const index in atlas) { for (const index in atlas) {
const entry = atlas[index] const entry = atlas[index]
entry._index = index entry._index = index
@ -528,22 +525,37 @@ function updateAtlasAll(atlas = atlasAll) {
} }
entry.path = currentPath entry.path = currentPath
entry.center = currentCenter entry.center = currentCenter
newAtlas[entry.id] = entry
} }
return atlas // console.log(newAtlas)
return newAtlas
} }
// Announcement system // Notice system
const announcementEl = document.querySelector("#headerAnnouncement") const noticeEl = document.querySelector("#headerNotice")
const announcementButton = announcementEl.querySelector('[role=button]') const noticeButton = noticeEl.querySelector('[role=button]')
const announcementText = announcementEl.querySelector('p').textContent.trim() const noticeText = noticeEl.querySelector('p').textContent.trim()
if (announcementText && announcementText !== window.localStorage.getItem('announcement-closed')) { const resizeGlobalTopPadding = () => {
announcementButton.click() document.body.style.setProperty("--global-top-padding", noticeEl.offsetHeight + 'px')
document.querySelector('#objectsList').style.marginTop = '2.8rem'
} }
announcementEl.querySelector('[role=button]').addEventListener('click', () => { if (window.localStorage.getItem('announcement-closed')) {
window.localStorage.setItem('announcement-closed', announcementText) window.localStorage.setItem('closed-notice', window.localStorage.getItem('announcement-closed'))
document.querySelector('#objectsList').style.marginTop = '0' window.localStorage.removeItem('announcement-closed')
}
if (noticeText && noticeText !== window.localStorage.getItem('closed-notice')) {
noticeButton.click()
setTimeout(() => {
document.body.style.setProperty("--global-top-padding", noticeEl.offsetHeight + 'px')
}, 500)
window.addEventListener('resize', resizeGlobalTopPadding)
}
noticeEl.querySelector('[role=button]').addEventListener('click', () => {
window.localStorage.setItem('closed-notice', noticeText)
window.removeEventListener('resize', resizeGlobalTopPadding)
document.body.style.setProperty("--global-top-padding", null)
}) })

View File

@ -1,7 +1,7 @@
/*! /*!
* The 2022 r/place Atlas * The 2022 r/place Atlas
* Copyright (c) 2017 Roland Rytz <roland@draemm.li> * Copyright (c) 2017 Roland Rytz <roland@draemm.li>
* Copyright (c) 2022 Place Atlas contributors * Copyright (c) 2022 Place Atlas Initiative and contributors
* Licensed under AGPL-3.0 (https://2022.place-atlas.stefanocoding.me/license.txt) * Licensed under AGPL-3.0 (https://2022.place-atlas.stefanocoding.me/license.txt)
*/ */
@ -9,22 +9,16 @@ function initOverlap() {
window.renderBackground = renderBackground window.renderBackground = renderBackground
updateAtlas()
// const hovered = [] // const hovered = []
resetEntriesList()
renderBackground(atlas)
render()
document.addEventListener('timeupdate', () => { document.addEventListener('timeupdate', () => {
atlasDisplay = atlas.slice() updateAtlas()
resetEntriesList()
renderBackground(atlasDisplay)
render()
}) })
applyView() applyView()
render() renderLines()
updateLines()
if (window.location.hash) { if (window.location.hash) {
updateViewFromHash() updateViewFromHash()
@ -37,7 +31,7 @@ function initOverlap() {
backgroundContext.fillStyle = "rgba(255, 255, 255, 1)" backgroundContext.fillStyle = "rgba(255, 255, 255, 1)"
backgroundContext.fillRect(0, 0, highlightCanvas.width, highlightCanvas.height) backgroundContext.fillRect(0, 0, highlightCanvas.width, highlightCanvas.height)
for (const entry of atlas) { for (const entry of Object.values(atlas)) {
const path = entry.path const path = entry.path

View File

@ -1,7 +1,7 @@
/*! /*!
* The 2022 r/place Atlas * The 2022 r/place Atlas
* Copyright (c) 2017 Roland Rytz <roland@draemm.li> * Copyright (c) 2017 Roland Rytz <roland@draemm.li>
* Copyright (c) 2022 Place Atlas contributors * Copyright (c) 2022 Place Atlas Initiative and contributors
* Licensed under AGPL-3.0 (https://2022.place-atlas.stefanocoding.me/license.txt) * Licensed under AGPL-3.0 (https://2022.place-atlas.stefanocoding.me/license.txt)
*/ */
@ -203,7 +203,7 @@ console.info("The " + topCount + " largest entries:")
let outstring = "" let outstring = ""
for (let i = 0; i < topCount; i++) { for (let i = 0; i < topCount; i++) {
outstring += ((i + 1) + "|[" + atlas[atlas.length - i - 1].name + "](http://2022.place-atlas.stefanocoding.me/?id=" + atlas[atlas.length - i - 1].id + ")|" + ~~atlas[atlas.length - i - 1].area + "|" + Math.round(atlas[atlas.length - i - 1].area / 100) / 100 + "%\n") outstring += ((i + 1) + "|[" + atlas[atlas.length - i - 1].name + "](http://2023.place-atlas.stefanocoding.me/?id=" + atlas[atlas.length - i - 1].id + ")|" + ~~atlas[atlas.length - i - 1].area + "|" + Math.round(atlas[atlas.length - i - 1].area / 100) / 100 + "%\n")
} }
console.info(outstring) console.info(outstring)

View File

@ -1,7 +1,7 @@
/*! /*!
* The 2022 r/place Atlas * The 2022 r/place Atlas
* Copyright (c) 2017 Roland Rytz <roland@draemm.li> * Copyright (c) 2017 Roland Rytz <roland@draemm.li>
* Copyright (c) 2022 Place Atlas contributors * Copyright (c) 2022 Place Atlas Initiative and contributors
* Licensed under AGPL-3.0 (https://2022.place-atlas.stefanocoding.me/license.txt) * Licensed under AGPL-3.0 (https://2022.place-atlas.stefanocoding.me/license.txt)
*/ */
@ -32,6 +32,12 @@ let currentPeriod = defaultPeriod
window.currentVariation = currentVariation window.currentVariation = currentVariation
window.currentPeriod = currentPeriod window.currentPeriod = currentPeriod
let atlasDisplay = {}
window.atlasDisplay = atlasDisplay
const additionalLayers = []
const additionalLayerCanvas = document.createElement('canvas')
// SETUP // SETUP
if (variationsConfig[currentVariation].versions.length === 1) bottomBar.classList.add('no-time-slider') if (variationsConfig[currentVariation].versions.length === 1) bottomBar.classList.add('no-time-slider')
@ -70,7 +76,7 @@ const dispatchTimeUpdateEvent = (period = currentPeriod, variation = currentVari
detail: { detail: {
period: period, period: period,
variation: variation, variation: variation,
periodString: formatPeriod(period, period, variation), periodString: formatPeriod(period, null, variation),
atlas: atlas atlas: atlas
} }
}) })
@ -79,7 +85,7 @@ const dispatchTimeUpdateEvent = (period = currentPeriod, variation = currentVari
async function updateBackground(newPeriod = currentPeriod, newVariation = currentVariation) { async function updateBackground(newPeriod = currentPeriod, newVariation = currentVariation) {
abortController.abort() abortController.abort()
myAbortController = new AbortController() const myAbortController = new AbortController()
abortController = myAbortController abortController = myAbortController
currentUpdateIndex++ currentUpdateIndex++
const myUpdateIndex = currentUpdateIndex const myUpdateIndex = currentUpdateIndex
@ -96,7 +102,7 @@ async function updateBackground(newPeriod = currentPeriod, newVariation = curren
variantsEl.parentElement.classList.remove('input-group') variantsEl.parentElement.classList.remove('input-group')
} }
const configObject = variationConfig.versions[currentPeriod] const configObject = variationConfig.versions[newPeriod]
let layerUrls = [] let layerUrls = []
let layers = [] let layers = []
@ -110,39 +116,67 @@ async function updateBackground(newPeriod = currentPeriod, newVariation = curren
layers.length = layerUrls.length layers.length = layerUrls.length
await Promise.all(layerUrls.map(async (url, i) => { await Promise.all(layerUrls.map(async (url, i) => {
const imageBlob = await (await fetch(url, { signal: myAbortController.signal })).blob() try {
const imageLayer = new Image() const imageBlob = await (await fetch(url, { signal: myAbortController.signal })).blob()
await new Promise(resolve => { const imageLayer = new Image()
imageLayer.onload = () => { await new Promise(resolve => {
context.canvas.width = Math.max(imageLayer.width, context.canvas.width) imageLayer.onload = () => {
context.canvas.height = Math.max(imageLayer.height, context.canvas.height) context.canvas.width = Math.max(imageLayer.width, context.canvas.width)
layers[i] = imageLayer context.canvas.height = Math.max(imageLayer.height, context.canvas.height)
resolve() layers[i] = imageLayer
} resolve()
imageLayer.src = URL.createObjectURL(imageBlob) }
}) imageLayer.src = URL.createObjectURL(imageBlob)
})
} catch (e) {
const aborted = myAbortController.signal.aborted
if (!aborted) throw e
}
})) }))
if (myAbortController.signal.aborted || newPeriod !== currentPeriod || newVariation !== currentVariation) { if (myAbortController.signal.aborted || newPeriod !== currentPeriod || newVariation !== currentVariation || currentUpdateIndex !== myUpdateIndex) {
return return false
} }
for (const imageLayer of layers) { for (const imageLayer of layers) {
context.drawImage(imageLayer, 0, 0) context.drawImage(imageLayer, 0, 0)
} }
context.drawImage(additionalLayerCanvas, 0, 0)
if (myAbortController.signal.aborted || newPeriod !== currentPeriod || newVariation !== currentVariation || currentUpdateIndex !== myUpdateIndex) {
return false
}
if (currentUpdateIndex !== myUpdateIndex) return [configObject, newPeriod, newVariation] if (currentUpdateIndex !== myUpdateIndex) return [configObject, newPeriod, newVariation]
const blob = await new Promise(resolve => canvas.toBlob(resolve)) const blob = await new Promise(resolve => canvas.toBlob(resolve))
canvasUrl = URL.createObjectURL(blob) canvasUrl = URL.createObjectURL(blob)
image.src = canvasUrl image.src = canvasUrl
return true
} }
let loadingTimeout = setTimeout(() => {}, 0)
async function updateTime(newPeriod = currentPeriod, newVariation = currentVariation, forceLoad = false) { async function updateTime(newPeriod = currentPeriod, newVariation = currentVariation, forceLoad = false) {
if (newPeriod === currentPeriod && !forceLoad) { if (newPeriod === currentPeriod && newVariation === currentVariation && !forceLoad) {
return; return
} }
document.body.dataset.canvasLoading = "" 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 oldPeriod = currentPeriod
const oldVariation = currentVariation const oldVariation = currentVariation
@ -167,12 +201,16 @@ async function updateTime(newPeriod = currentPeriod, newVariation = currentVaria
timelineSlider.value = currentPeriod timelineSlider.value = currentPeriod
updateTooltip(newPeriod, newVariation) updateTooltip(newPeriod, newVariation)
await updateBackground(newPeriod, newVariation) const updateBackgroundResult = await updateBackground(newPeriod, newVariation)
atlas = generateAtlasForPeriod(newPeriod, newVariation) if (!updateBackgroundResult) return
dispatchTimeUpdateEvent(newPeriod, newVariation, atlas) dispatchTimeUpdateEvent(newPeriod, newVariation, atlas)
delete document.body.dataset.canvasLoading 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 = "" tooltip.dataset.forceVisible = ""
clearTimeout(tooltipDelayHide) clearTimeout(tooltipDelayHide)
tooltipDelayHide = setTimeout(() => { tooltipDelayHide = setTimeout(() => {
@ -181,24 +219,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 let chosenIndex
const validPeriods2 = Object.keys(entry.path) const validPeriods2 = Object.keys(entry.path)
for (const i in validPeriods2) { periodCheck: for (const i in validPeriods2) {
const validPeriods = validPeriods2[i].split(', ') const validPeriods = validPeriods2[i].split(', ')
for (const j in validPeriods) { for (const j in validPeriods) {
const [start, end, variation] = parsePeriod(validPeriods[j]) const [start, end, variation] = parsePeriod(validPeriods[j])
if (isOnPeriod(start, end, variation, newPeriod, newVariation)) { if (isOnPeriod(start, end, variation, newPeriod, newVariation)) {
chosenIndex = i chosenIndex = i
break break periodCheck
} }
} }
if (chosenIndex !== undefined) break
} }
if (chosenIndex === undefined) continue if (chosenIndex === undefined) continue
@ -207,14 +251,18 @@ function generateAtlasForPeriod(newPeriod = currentPeriod, newVariation = curren
if (pathChosen === undefined) continue if (pathChosen === undefined) continue
atlas.push({ newAtlas[id] = {
...entry, ...entry,
path: pathChosen, path: pathChosen,
center: centerChosen, center: centerChosen,
}) }
newAtlasOrderNotDisplayed.pop()
newAtlasOrderDisplayed.push(id)
} }
return atlas return [newAtlas, [...newAtlasOrderDisplayed, ...newAtlasOrderNotDisplayed]]
} }
@ -273,7 +321,7 @@ function parsePeriod(periodString) {
function formatPeriod(targetStart, targetEnd, targetVariation, forUrl = false) { function formatPeriod(targetStart, targetEnd, targetVariation, forUrl = false) {
targetStart ??= currentPeriod targetStart ??= currentPeriod
targetEnd ??= currentPeriod targetEnd ??= targetStart
targetVariation ??= currentVariation targetVariation ??= currentVariation
let periodString, variationString let periodString, variationString
@ -297,12 +345,11 @@ function setReferenceVal(reference, newValue) {
else return 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('/') let hashData = window.location.hash.substring(1).split('/')
targetEntry = setReferenceVal(targetEntry, hashData[0]) targetEntry = setReferenceVal(targetEntry, hashData[0])
targetPeriodStart = setReferenceVal(targetPeriodStart, currentPeriod) targetPeriod = setReferenceVal(targetPeriod, currentPeriod)
targetPeriodEnd = setReferenceVal(targetPeriodEnd, currentPeriod)
targetVariation = setReferenceVal(targetVariation, currentVariation) targetVariation = setReferenceVal(targetVariation, currentVariation)
targetX = setReferenceVal(targetX, canvasCenter.x - scaleZoomOrigin[0]) targetX = setReferenceVal(targetX, canvasCenter.x - scaleZoomOrigin[0])
targetY = setReferenceVal(targetY, canvasCenter.y - scaleZoomOrigin[1]) targetY = setReferenceVal(targetY, canvasCenter.y - scaleZoomOrigin[1])
@ -313,8 +360,8 @@ function formatHash(targetEntry, targetPeriodStart, targetPeriodEnd, targetVaria
if (targetZoom) targetZoom = targetZoom.toFixed(3).replace(/\.?0+$/, '') if (targetZoom) targetZoom = targetZoom.toFixed(3).replace(/\.?0+$/, '')
const result = [targetEntry] const result = [targetEntry]
const targetPeriod = formatPeriod(targetPeriodStart, targetPeriodEnd, targetVariation, true) const targetPeriodFormat = formatPeriod(targetPeriod, null, targetVariation, true)
result.push(targetPeriod, targetX, targetY, targetZoom) result.push(targetPeriodFormat, targetX, targetY, targetZoom)
if (!result.some(el => el || el === 0)) return '' if (!result.some(el => el || el === 0)) return ''
return '#' + result.join('/').replace(/\/+$/, '') return '#' + result.join('/').replace(/\/+$/, '')
} }
@ -327,4 +374,63 @@ function downloadCanvas() {
document.body.appendChild(linkEl) document.body.appendChild(linkEl)
linkEl.click() linkEl.click()
document.body.removeChild(linkEl) 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 ]
}
const updateAdditionalLayer = () => {
const layers = additionalLayers
const canvas = additionalLayerCanvas
const context = additionalLayerCanvas.getContext('2d')
canvas.width = 0
canvas.height = 0
for (const layer of layers) {
if (!layer.imageLayer) continue
canvas.width = Math.max(layer.x + layer.imageLayer.width, canvas.width)
canvas.height = Math.max(layer.y + layer.imageLayer.height, canvas.height)
}
for (const layer of layers) {
if (!layer.imageLayer) continue
context.drawImage(layer.imageLayer, layer.x, layer.y)
console.log(layer.imageLayer)
}
}

View File

@ -1,7 +1,7 @@
/*! /*!
* The 2022 r/place Atlas * The 2022 r/place Atlas
* Copyright (c) 2017 Roland Rytz <roland@draemm.li> * Copyright (c) 2017 Roland Rytz <roland@draemm.li>
* Copyright (c) 2022 Place Atlas contributors * Copyright (c) 2022 Place Atlas Initiative and contributors
* Licensed under AGPL-3.0 (https://2022.place-atlas.stefanocoding.me/license.txt) * Licensed under AGPL-3.0 (https://2022.place-atlas.stefanocoding.me/license.txt)
*/ */
@ -43,7 +43,11 @@ objectEditNav.className = "btn btn-outline-primary"
objectEditNav.id = "objectEditNav" objectEditNav.id = "objectEditNav"
objectEditNav.textContent = "Edit" objectEditNav.textContent = "Edit"
let atlasDisplay let atlas = null
window.atlas = atlas
let atlasOrder = []
window.atlasOrder = atlasOrder
const entriesLimit = 50 const entriesLimit = 50
let entriesOffset = 0 let entriesOffset = 0
@ -65,18 +69,18 @@ const moreEntriesObserver = new IntersectionObserver(entries => {
moreEntriesObserver.observe(moreEntriesButton) moreEntriesObserver.observe(moreEntriesButton)
let defaultSort = sortInput.value let defaultSort = sortInput.value
let lastPos = [0, 0] let lastPos = [0, 0]
let fixed = false; // Fix hovered items in place, so that clicking on links is possible let fixed = false; // Fix hovered items in place, so that clicking on links is possible
searchInput.addEventListener("input", function () { searchInput.addEventListener("input", function () {
resetEntriesList() updateAtlas()
}) })
sortInput.addEventListener("input", function () { sortInput.addEventListener("input", function () {
resetEntriesList() updateAtlas()
}) })
offcanvasDraw.addEventListener('show.bs.offcanvas', () => { offcanvasDraw.addEventListener('show.bs.offcanvas', () => {
@ -112,8 +116,8 @@ offcanvasList.addEventListener('shown.bs.offcanvas', e => {
wrapper.classList.remove('listTransitioning') wrapper.classList.remove('listTransitioning')
updateHovering(e) updateHovering(e)
applyView() applyView()
render() renderHighlight()
updateLines() renderLines()
}) })
offcanvasList.addEventListener('hide.bs.offcanvas', () => { offcanvasList.addEventListener('hide.bs.offcanvas', () => {
@ -127,63 +131,59 @@ offcanvasList.addEventListener('hidden.bs.offcanvas', e => {
wrapper.classList.remove('listTransitioning') wrapper.classList.remove('listTransitioning')
updateHovering(e) updateHovering(e)
applyView() applyView()
render() renderHighlight()
updateLines() renderLines()
}) })
closeObjectsListButton.addEventListener("click", clearObjectsList) closeObjectsListButton.addEventListener("click", clearObjectsList)
bottomBar.addEventListener("mouseover", () => {
if (!fixed) clearObjectsList()
})
function clearObjectsList() { function clearObjectsList() {
hovered = []
fixed = false
renderLines()
renderHighlight()
document.title = pageTitle
closeObjectsListButton.classList.add("d-none") closeObjectsListButton.classList.add("d-none")
objectsListOverflowNotice.classList.add("d-none") objectsListOverflowNotice.classList.add("d-none")
entriesList.classList.remove("disableHover") entriesList.classList.remove("disableHover")
hovered = []
objectsContainer.replaceChildren() objectsContainer.replaceChildren()
updateLines()
fixed = false
render()
objectEditNav.remove() objectEditNav.remove()
updateHash(false) updateHash(false)
document.title = pageTitle
} }
function toggleFixed(e, tapped) { function toggleFixed(e, tapped) {
if (!fixed && hovered.length === 0) { if (!fixed && hovered.length === 0) {
entriesList.classList.remove("disableHover") entriesList.classList.remove("disableHover")
return 0 return
} }
fixed = !fixed fixed = !fixed
if (!fixed) { if (!fixed) {
updateHovering(e, tapped) updateHovering(e, tapped)
render() renderHighlight()
} }
entriesList.classList.add("disableHover") entriesList.classList.add("disableHover")
objectsListOverflowNotice.classList.add("d-none") objectsListOverflowNotice.classList.add("d-none")
} }
window.addEventListener("resize", updateLines) window.addEventListener("dblClick", renderLines)
window.addEventListener("mousemove", updateLines) window.addEventListener("wheel", renderLines)
window.addEventListener("dblClick", updateLines)
window.addEventListener("wheel", updateLines)
objectsContainer.addEventListener("scroll", () => { objectsContainer.addEventListener("scroll", () => {
updateLines() renderLines()
}) })
window.addEventListener("resize", () => { window.addEventListener("resize", () => {
applyView() applyView()
render() renderHighlight()
updateLines() renderLines()
}) })
function updateLines() { async function renderLines() {
if (hovered.length === 0) {
linesContext.clearRect(0, 0, linesCanvas.width, linesCanvas.height)
return
}
// Line border // Line border
linesCanvas.width = linesCanvas.clientWidth linesCanvas.width = linesCanvas.clientWidth
linesCanvas.height = linesCanvas.clientHeight linesCanvas.height = linesCanvas.clientHeight
@ -260,9 +260,9 @@ function renderBackground(atlas) {
backgroundContext.fillStyle = "rgba(0, 0, 0, 0.6)" backgroundContext.fillStyle = "rgba(0, 0, 0, 0.6)"
backgroundContext.fillRect(0, 0, backgroundCanvas.width, backgroundCanvas.height) 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() backgroundContext.beginPath()
@ -279,7 +279,7 @@ function renderBackground(atlas) {
backgroundContext.closePath() backgroundContext.closePath()
let bgStrokeStyle let bgStrokeStyle
switch (atlas[i].diff) { switch (entry.diff) {
case "add": case "add":
bgStrokeStyle = "rgba(0, 255, 0, 1)" bgStrokeStyle = "rgba(0, 255, 0, 1)"
backgroundContext.lineWidth = 2 backgroundContext.lineWidth = 2
@ -299,45 +299,39 @@ function renderBackground(atlas) {
backgroundContext.strokeStyle = bgStrokeStyle backgroundContext.strokeStyle = bgStrokeStyle
backgroundContext.stroke() backgroundContext.stroke()
backgroundContext.lineWidth = 1 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) { document.getElementById("atlasSize").innerHTML = ""
atlasDisplay = atlas.filter(entry => {
return ( if (search) {
entry.name.toLowerCase().includes(filter.toLowerCase()) for (const [id, entry] of Object.entries(prevAtlas)) {
|| entry.description?.toLowerCase().includes(filter.toLowerCase()) if (!(
|| Object.values(entry.links).flat().some(str => str.toLowerCase().includes(filter)) entry.name.toLowerCase().includes(search.toLowerCase()) ||
|| entry.id.toString() === filter entry.description?.toLowerCase().includes(search.toLowerCase()) ||
) Object.values(entry.links).flat().some(str => str.toLowerCase().includes(search)) ||
}) id.toString() === search
document.getElementById("atlasSize").innerHTML = "Found " + atlasDisplay.length + " entries." )) delete newAtlas[id]
} else { }
document.getElementById("atlasSize").innerHTML = "The Atlas contains " + atlasDisplay.length + " entries."
} }
renderBackground(atlasDisplay) // document.getElementById("sort").value = sort
render()
sort ||= defaultSort
document.getElementById("sort").value = sort
//console.log(sort)
let sortFunction let sortFunction
//console.log(sort)
switch (sort) { switch (sort) {
case "shuffle": case "shuffle":
sortFunction = null sortFunction = () => Math.random() - 0.5
if (entriesOffset === 0) {
shuffle()
}
break break
case "alphaAsc": case "alphaAsc":
sortFunction = (a, b) => a.name.toLowerCase().localeCompare(b.name.toLowerCase()) sortFunction = (a, b) => a.name.toLowerCase().localeCompare(b.name.toLowerCase())
@ -369,100 +363,130 @@ function buildObjectsList(filter, sort) {
break break
} }
newAtlasOrder = Object.keys(newAtlas)
if (sortFunction) { 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) moreEntriesButton.removeEventListener('click', showMoreEntries)
showMoreEntries = () => { showMoreEntries = () => {
if (entriesList.contains(moreEntriesButton)) { if (entriesList.contains(moreEntriesButton)) {
entriesList.removeChild(moreEntriesButton) entriesList.removeChild(moreEntriesButton)
}
for (let i = entriesOffset; i < entriesOffset + entriesLimit; i++) {
if (i >= atlasDisplay.length) break
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()
})
entriesList.appendChild(element)
} }
entriesOffset += entriesLimit let entriesLeft = entriesLimit
let element
if (atlasDisplay.length > entriesOffset) {
moreEntriesButton.innerHTML = "Show " + Math.min(entriesLimit, atlasDisplay.length - entriesOffset) + " more" while (entriesLeft > 0 && atlasOrder.length > entriesOffset) {
if (atlasDisplay[atlasOrder[entriesOffset]]) {
// console.log(i, entriesLeft)
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)
}
if (atlasOrder.length > entriesOffset) {
moreEntriesButton.innerHTML = "Show " + Math.min(entriesLimit, atlasOrder.length - entriesOffset) + " more"
entriesList.appendChild(moreEntriesButton) entriesList.appendChild(moreEntriesButton)
} }
} }
moreEntriesButton.addEventListener('click', showMoreEntries) moreEntriesButton.addEventListener('click', showMoreEntries)
showMoreEntries() showMoreEntries()
} }
function shuffle() { async function renderHighlight() {
//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() {
highlightContext.clearRect(0, 0, highlightCanvas.width, highlightCanvas.height) highlightContext.clearRect(0, 0, highlightCanvas.width, highlightCanvas.height)
@ -478,10 +502,8 @@ async function render() {
container.style.cursor = "default" container.style.cursor = "default"
} }
for (let i = 0; i < hovered.length; i++) { for (let i = 0; i < hovered.length; i++) {
const path = hovered[i].path const path = hovered[i].path
highlightContext.beginPath() highlightContext.beginPath()
@ -507,28 +529,6 @@ async function render() {
highlightContext.globalCompositeOperation = "source-out" highlightContext.globalCompositeOperation = "source-out"
highlightContext.drawImage(backgroundCanvas, 0, 0) 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++) { for (let i = 0; i < hovered.length; i++) {
const path = hovered[i].path const path = hovered[i].path
@ -591,12 +591,16 @@ function updateHovering(e, tapped) {
const pos = updateCoordsDisplay(e) const pos = updateCoordsDisplay(e)
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 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 = [] let newHovered = []
for (const entry of atlasDisplay) { for (const entry of Object.values(atlasDisplay)) {
if (pointIsInPolygon(pos, entry.path)) newHovered.push(entry) if (pointIsInPolygon(pos, entry.path)) newHovered.push(entry)
} }
newHovered = newHovered.sort(function (a, b) {
return calcPolygonArea(a.path) - calcPolygonArea(b.path)
})
let changed = false let changed = false
if (hovered.length === newHovered.length) { if (hovered.length === newHovered.length) {
@ -612,9 +616,7 @@ function updateHovering(e, tapped) {
if (!changed) return if (!changed) return
hovered = newHovered.sort(function (a, b) { hovered = newHovered
return calcPolygonArea(a.path) - calcPolygonArea(b.path)
})
objectsContainer.replaceChildren() objectsContainer.replaceChildren()
@ -639,12 +641,13 @@ function updateHovering(e, tapped) {
objectsListOverflowNotice.classList.add("d-none") objectsListOverflowNotice.classList.add("d-none")
entriesList.classList.remove("disableHover") entriesList.classList.remove("disableHover")
} }
render() renderLines()
renderHighlight()
} }
window.addEventListener("hashchange", updateViewFromHash) window.addEventListener("hashchange", updateViewFromHash)
function updateViewFromHash() { async function updateViewFromHash() {
const hash = window.location.hash.substring(1); //Remove hash prefix const hash = window.location.hash.substring(1); //Remove hash prefix
let [hashEntryId, hashPeriod, hashX, hashY, hashZoom] = hash.split('/') let [hashEntryId, hashPeriod, hashX, hashY, hashZoom] = hash.split('/')
@ -666,11 +669,11 @@ function updateViewFromHash() {
targetPeriod = defaultPeriod targetPeriod = defaultPeriod
targetVariation = defaultVariation targetVariation = defaultVariation
} }
updateTime(targetPeriod, targetVariation, true) await updateTime(targetPeriod, targetVariation)
setView( setView(
(isNaN(hashX) || hashX === '') ? undefined : Number(hashX), (isNaN(hashX) || hashX === '') ? undefined : Number(hashX),
(isNaN(hashY) || hashY === '') ? undefined : Number(hashY), (isNaN(hashY) || hashY === '') ? undefined : Number(hashY),
(isNaN(hashZoom) || hashZoom === '') ? undefined : Number(hashZoom) (isNaN(hashZoom) || hashZoom === '') ? undefined : Number(hashZoom)
) )
@ -678,13 +681,8 @@ function updateViewFromHash() {
// Highlight entry from hash // Highlight entry from hash
const entries = atlas.filter(e => { const entry = atlasDisplay[hashEntryId]
return e.id.toString() === hashEntryId if (!entry) return
})
if (entries.length !== 1) return
const entry = entries[0]
document.title = entry.name + " on " + pageTitle document.title = entry.name + " on " + pageTitle
@ -702,24 +700,24 @@ function updateViewFromHash() {
objectsContainer.replaceChildren() objectsContainer.replaceChildren()
objectsContainer.appendChild(infoElement) objectsContainer.appendChild(infoElement)
renderBackground(atlas)
setView( setView(
(isNaN(hashX) || hashX === '') ? entry.center[0] : Number(hashX), (isNaN(hashX) || hashX === '') ? entry.center[0] : Number(hashX),
(isNaN(hashY) || hashY === '') ? entry.center[1] : Number(hashY), (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") closeObjectsListButton.classList.remove("d-none")
entriesList.classList.add("disableHover") entriesList.classList.add("disableHover")
hovered = [entry] hovered = [{...entry, element: infoElement}]
render() renderBackground(atlasDisplay)
hovered[0].element = infoElement renderHighlight(atlasDisplay)
updateLines() renderLines()
} }
function setZoomByPath(path) { function calculateZoomFromPath(path) {
let zoom
let boundingBox = [canvasSize.x + canvasOffset.x, canvasOffset.x, canvasSize.y + canvasOffset.y, canvasOffset.y] let boundingBox = [canvasSize.x + canvasOffset.x, canvasOffset.x, canvasSize.y + canvasOffset.y, canvasOffset.y]
path?.forEach(([x, y]) => { path?.forEach(([x, y]) => {
boundingBox[0] = Math.min(boundingBox[0], x) boundingBox[0] = Math.min(boundingBox[0], x)
@ -741,13 +739,10 @@ function setZoomByPath(path) {
function initView() { function initView() {
buildObjectsList(null, null) updateAtlas()
renderBackground(atlas)
render()
document.addEventListener('timeupdate', () => { document.addEventListener('timeupdate', () => {
atlasDisplay = atlas.slice() updateAtlas()
resetEntriesList()
}) })
// parse linked atlas entry id from link hash // parse linked atlas entry id from link hash
@ -758,22 +753,20 @@ function initView() {
}*/ }*/
applyView() applyView()
render() renderLines()
updateLines()
} }
function initExplore() { function initExplore() {
window.updateHovering = updateHovering window.updateHovering = updateHovering
window.render = () => { } window.renderHighlight = () => { }
function updateHovering(e, tapped) { function updateHovering(e, tapped) {
if (dragging || (fixed && !tapped)) return if (dragging || (fixed && !tapped)) return
updateCoordsDisplay(e) updateCoordsDisplay(e)
} }
renderBackground(atlas) renderBackground({})
applyView() applyView()
@ -790,7 +783,7 @@ function initGlobal() {
} }
}) })
document.addEventListener('timeupdate', event => { document.addEventListener('timeupdate', () => {
updateHash() updateHash()
}) })
} }
@ -837,6 +830,84 @@ function initViewGlobal() {
} }
document.addEventListener('timeupdate', event => { 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)
})
document.addEventListener("mouseleave", () => {
if (!fixed) clearObjectsList()
}) })
} }
async function loadTemplateData(initUrl, datas, blacklistUrls, level = 0) {
datas ??= {}
blacklistUrls ??= new Set()
if (datas[initUrl] || blacklistUrls.has(initUrl)) return [ datas, blacklistUrls ]
datas[initUrl] = {}
try {
const data = await (await fetch(initUrl)).json()
datas[initUrl] = data
for (const blacklisted of data?.blacklist) {
blacklistUrls.add(blacklisted.url)
}
await Promise.all(data?.whitelist.map(async wl => {
const [ wlDatas, wlBlacklistUrls ] = await loadTemplateData(wl.url, datas, blacklistUrls, level + 1)
Object.assign(datas, wlDatas)
blacklistUrls.add(...wlBlacklistUrls)
}))
} catch (e) {}
return [ datas, [...blacklistUrls] ]
}
async function loadTemplateImages(datas) {
const templates = []
for (const data of Object.values(datas)) {
if (!data?.templates) continue
for (const template of data?.templates) {
templates.push(template)
}
}
await Promise.all(templates.map(async (template, i) => {
if (!template.sources) return
for (const source of template.sources) {
try {
const sourceResponse = await (await fetch(source)).blob()
template.blob = URL.createObjectURL(sourceResponse)
break
} catch (e) {}
}
delete template.sources
if (!template.blob) return
const imageLayer = new Image()
await new Promise(resolve => {
imageLayer.onload = () => {
template.imageLayer = imageLayer
delete template.blob
resolve()
}
imageLayer.onerror = () => {
delete template
resolve()
}
imageLayer.src = template.blob
})
}))
for (const layer of templates) {
if (!layer.imageLayer) delete layer
}
return templates
}

View File

@ -1,7 +1,7 @@
<!-- <!--
The 2022 r/place Atlas The 2022 r/place Atlas
Copyright (c) 2017 Roland Rytz <roland@draemm.li> Copyright (c) 2017 Roland Rytz <roland@draemm.li>
Copyright (c) 2022 Place Atlas contributors Copyright (c) 2022 Place Atlas Initiative and contributors
Licensed under AGPL-3.0 (https://2022.place-atlas.stefanocoding.me/license.txt) Licensed under AGPL-3.0 (https://2022.place-atlas.stefanocoding.me/license.txt)
--> -->
@ -10,8 +10,8 @@
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<title>About - The 2022 r/place Atlas</title> <title>About - The 2022 r/place Atlas</title>
<meta name="description" content="This is an atlas aiming to chart all the artworks created during the r/place April Fools event on Reddit in 2022. It is made with information to each artwork of the canvas provided by the community."> <meta name="description" content="This is an interactive map aiming to chart all the artworks created during the r/place April Fools event on Reddit in 2022. It is made 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="author" content="Place Atlas Initiative and contributors (original by Roland Rytz)">
<meta name="application-name" content="2022 r/place Atlas"> <meta name="application-name" content="2022 r/place Atlas">
<meta name="robots" content="index, follow"> <meta name="robots" content="index, follow">
@ -23,18 +23,19 @@
<meta property="og:image:width" content="512"> <meta property="og:image:width" content="512">
<meta property="og:image:height" content="512"> <meta property="og:image:height" content="512">
<meta property="og:image:alt" content="The r/place Atlas logo"> <meta property="og:image:alt" content="The r/place Atlas logo">
<meta property="og:description" content="This is an atlas aiming to chart all the artworks created during the r/place April Fools event on Reddit in 2022. It is made with information to each artwork of the canvas provided by the community."> <meta property="og:description" content="This is an interactive map aiming to chart all the artworks created during the r/place April Fools event on Reddit in 2022. It is made with information to each artwork of the canvas provided by the community.">
<meta name="twitter:card" content="summary"> <meta name="twitter:card" content="summary">
<meta name="twitter:creator" content="Place Atlas contributors (original by Roland Rytz)">
<meta name="twitter:url" content="https://2022.place-atlas.stefanocoding.me/"> <meta name="twitter:url" content="https://2022.place-atlas.stefanocoding.me/">
<meta name="twitter:title" content="About - The 2022 r/place Atlas"> <meta name="twitter:title" content="About - The 2022 r/place Atlas">
<meta name="twitter:description" content="This is an atlas aiming to chart all the artworks created during the r/place April Fools event on Reddit in 2022. It is made with information to each artwork of the canvas provided by the community."> <meta name="twitter:description" content="This is an interactive map aiming to chart all the artworks created during the r/place April Fools event on Reddit in 2022. It is made with information to each artwork of the canvas provided by the community.">
<meta name="twitter:image" content="https://2022.place-atlas.stefanocoding.me/_img/logo.png"> <meta name="twitter:image" content="https://2022.place-atlas.stefanocoding.me/_img/logo.png">
<meta name="twitter:image:alt" content="The 2022 r/place Atlas logo"> <meta name="twitter:image:alt" content="The 2022 r/place Atlas logo">
<meta name="viewport" content="width=device-width, height=device-height, initial-scale=1, viewport-fit=cover"> <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="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="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 alternate" href="_img/favicon.png" type="image/png" class="js-site-favicon">
@ -60,8 +61,8 @@
<i class="bi bi-list" aria-hidden="true"></i> <i class="bi bi-list" aria-hidden="true"></i>
</button> </button>
<div class="navbar-collapse collapse" id="navbarCollapse"> <div class="navbar-collapse collapse" id="navbarCollapse">
<ul class="navbar-nav ms-auto pt-2 pt-md-0"> <ul class="navbar-nav flex-row flex-wrap ms-auto pt-2 pt-md-0">
<li class="nav-item dropdown"> <li class="nav-item col-6 col-md-auto dropdown">
<a class="nav-link dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown" aria-expanded="false" aria-current="page"> <a class="nav-link dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown" aria-expanded="false" aria-current="page">
Atlas Map Atlas Map
</a> </a>
@ -72,9 +73,12 @@
</ul> </ul>
</li> </li>
<li class="nav-item col-6 col-md-auto"> <li class="nav-item col-6 col-md-auto">
<a class="nav-link" href="https://place-wiki.stefanocoding.me/">Place Wiki</a> <a class="nav-link" href="https://place-wiki.stefanocoding.me/" target="_blank">Place Wiki</a>
</li> </li>
<li class="nav-item"> <li class="nav-item col-6 col-md-auto">
<a class="nav-link" href="https://github.com/placeAtlas/atlas-2022" target="_blank">Source</a>
</li>
<li class="nav-item col-6 col-md-auto">
<a class="nav-link active" href="#" aria-current="page">About</a> <a class="nav-link active" href="#" aria-current="page">About</a>
</li> </li>
</ul> </ul>
@ -86,119 +90,35 @@
<div class="row g-5"> <div class="row g-5">
<div class="col-md-7 col-xl-8"> <div class="col-md-7 col-xl-8">
<h1 class="display-5 fw-bold mb-4">The 2022 r/place Atlas</h1> <h1 class="display-5 fw-bold mb-4">The 2022 r/place Atlas</h1>
<p>This is an atlas aiming to chart all the artworks created during the <a href="https://www.reddit.com/r/place/">r/place</a> April Fools event on Reddit in 2022. It is made with information to each artwork of the canvas provided by the community.</p> <p>This is an interactive map aiming to chart all the artworks created during the <a href="https://www.reddit.com/r/place/" target="_blank" rel="noreferrer">r/place</a> April Fools event on Reddit in 2022. It is made with information to each artwork of the canvas provided by the community.</p>
<p>The original code was developed by <a href="https://draemm.li/various/place-atlas/">Roland Rytz</a> <a href="mailto:roland.rytz@gmail.com"><i aria-label="Mail" class="bi bi-envelope"></i></a> <a href="https://reddit.com/user/draemmli/"><i aria-label="Reddit" class="bi bi-reddit"></i></a> and is available under the free <a href="https://www.gnu.org/licenses/agpl-3.0.en.html">AGPL license</a> on <a href="https://github.com/RolandR/place-atlas">GitHub</a>.</p> <p>The original code was developed by <a href="https://draemm.li/various/place-atlas/" target="_blank" rel="noreferrer">Roland Rytz</a> <a href="mailto:roland.rytz@gmail.com"><i aria-label="Mail" class="bi bi-envelope"></i></a> <a href="https://reddit.com/user/draemmli/" target="_blank" rel="noreferrer"><i aria-label="Reddit" class="bi bi-reddit"></i></a> and is available under the free <a href="https://www.gnu.org/licenses/agpl-3.0.en.html" target="_blank" rel="noreferrer">AGPL license</a> on <a href="https://github.com/RolandR/place-atlas" target="_blank">GitHub</a>.</p>
<p>The currently maintained version of the website is managed by <a href="https://place-atlas.stefanocoding.me" target="_blank">Place Atlas Initiative</a>, led by Stefano Haagmans and is obtainable under the same license within a <a href="https://github.com/placeAtlas/atlas-2022" target="_blank">GitHub fork</a>.</p>
<hr> <hr>
<p>The currently maintained version of the website is managed by <a href="https://github.com/placeAtlas">the Place Atlas team</a>, led by Stefano Haagmans and is obtainable under the same license within a <a href="https://github.com/placeAtlas/atlas-2022">GitHub fork</a>. Initial images are provided by <a href="https://place.thatguyalex.com/">Alex Tsernoh</a>.</p> <p>Maintaining and updating the website takes work, but I enjoy doing it for free and giving this to people. But if you would like to support me, or the people who helped me with contributions to this project. You're free to support us though <a href="https://paypal.me/placeAtlas/5" target="_blank" rel="noreferrer">PayPal</a>, <a href="https://www.patreon.com/placeAtlas" target="_blank" rel="noreferrer">Patreon</a>, or <a href="https://ko-fi.com/placeatlas" target="_blank" rel="noreferrer">Ko-Fi</a>.</p>
<p>Maintaining and updating the website takes work, but I enjoy doing it for free and giving this to people. But if you would like to support me, or the people who helped me with contributions to this project. You're free to support us though <a href="https://paypal.me/placeAtlas/5">PayPal</a>, <a href="https://www.patreon.com/placeAtlas">Patreon</a>, or <a href="https://ko-fi.com/placeatlas">Ko-Fi</a>.</p>
<p>To maintain the same tradition, I will also be offering stickers to anyone donating more then 20$ (Due to the size increase). But, you're not forced to do anything! This only shows appreciation to us and the people who've worked on this project</p> <p>To maintain the same tradition, I will also be offering stickers to anyone donating more then 20$ (Due to the size increase). But, you're not forced to do anything! This only shows appreciation to us and the people who've worked on this project</p>
<p>Roland Rytz worked on the Atlas full-time (and more!) for over two weeks during the 2017 r/place event. If you'd like to support Roland, you can do so by <a href="https://paypal.me/draemmli">PayPal</a>. If you donate more than 10(€/$/CHF/mBTC), He'll (hopefully) send you a nice sticker of the 2017 Place canvas!</p> <p>Roland Rytz worked on the Atlas full-time (and more!) for over two weeks during the 2017 r/place event. If you'd like to support Roland, you can do so by <a href="https://paypal.me/draemmli" target="_blank" rel="noreferrer">PayPal</a>. If you donate more than 10(€/$/CHF/mBTC), He'll (hopefully) send you a nice sticker of the 2017 Place canvas!</p>
<p class="blockquote-footer mt-0">Stefano Haagmans</p>
<h2>Contributing</h2> <h2>Contributing</h2>
<p>This project is open source, and contributions are welcome. In fact, the Atlas relies on user contributions.</p> <p>This project is open source, and contributions are welcome. In fact, the Atlas relies on user contributions.</p>
<p>To contribute a label for an artwork, please read <a href="https://github.com/placeAtlas/atlas-2022/blob/master/CONTRIBUTING.md">this guide</a> to learn how to submit a new entry, edit existing entries, or contribute to the development of the codebase.</p> <p>To contribute a label for an artwork, please read <a href="https://github.com/placeAtlas/atlas-2022/blob/master/CONTRIBUTING.md" target="_blank">the contributing guide</a> to learn how to submit a new entry, edit existing entries, or contribute to the development of the codebase.</p>
<p><a href="https://reddit.com/r/placeAtlas2/">The r/placeAtlas2 subreddit</a> and <a href="https://discord.gg/pJkm23b2nA">the Discord server</a> is also the place to submit all bug reports, feature requests, or questions.</p> <p><a href="https://reddit.com/r/placeAtlas2/" target="_blank" rel="noreferrer">The r/placeAtlas2 subreddit</a> and <a href="https://discord.gg/pJkm23b2nA" target="_blank" rel="noreferrer">the Discord server</a> is also the place to submit all bug reports, feature requests, or questions.</p>
<div id="credits"> <div id="credits">
<h2>Credit</h2> <h2 class="mb-2">Credits</h2>
<h3>Project Contributors</h3> <h3>Project Contributors</h3>
<p>This section is focused towards the project development as a whole, excluding entry contributions, but including, and not limited to, code contributions, toolset, documentation, etc.</p>
<ul> <ul>
<li>Project Lead: <li><b>Place Atlas Initiative</b> is the primary maintainer of the project. Please see <a href="https://place-atlas.stefanocoding.me/#team" target="_blank">the Team section</a> for the current list.</li>
<a href="https://reddit.com/user/TCOOfficiall">TCOOfficiall</a> (Stefano Haagmans) <li>All contributors of this repository are listed on <a href="https://github.com/placeAtlas/atlas-2022/graphs/contributors" target="_blank">the Contributors page on GitHub</a>. The Contributors section with <a href="https://allcontributors.org/" target="_blank" rel="noreferrer">All Contributors</a> spec is also available on <a href="https://github.com/placeAtlas/atlas-2022#contributors" target="_blank">the README</a>.</li>
<a href="https://stefanocoding.me/"><i aria-label="Website" class="bi bi-globe"></i></a> <li>Thank you to those who are contributing in other channels, such as Discord and Reddit (such as reporting bugs), and those who have supported the project in general.</li>
<a href="https://github.com/Codixer"><i aria-label="GitHub" class="bi bi-github"></i></a> <li>Thank you to <a href="https://www.reddit.com/r/thefinalclean/" target="_blank" rel="noreferrer">The Final Clean community</a> (Pixel Janitors) for "The Final Clean" canvas.</li>
</li> <li>Thank you to <a href="https://place.thatguyalex.com/" target="_blank" rel="noreferrer">Alex Tsernoh</a> for providing the initial canvas images.</li>
<li>Archival Team:
<ul>
<li>
<a href="https://github.com/electric-blue">electric-blue</a> (tycho)
<a href="https://github.com/ab-gh"><i aria-label="GitHub" class="bi bi-github"></i></a>
</li>
<li>
Nick12
<a href="https://github.com/nico-abram"><i aria-label="GitHub" class="bi bi-github"></i></a>
</li>
<li>
<a href="https://reddit.com/user/m654zy">m654zy</a>
</li>
<li>
<a href="https://reddit.com/user/AnonymousRandPerson">AnonymousRandPerson</a>
<a href="https://github.com/AnonymousRandomPerson"><i aria-label="GitHub" class="bi bi-github"></i></a>
</li>
</ul>
</li>
<li>Development Team:
<ul>
<li>skyyc</li>
<li>
<a href="https://reddit.com/user/prosto_sanja">prosto_sanja</a> (Alex Tsernoh)
<a href="https://place.thatguyalex.com/"><i aria-label="Website" class="bi bi-globe"></i></a>
<a href="https://github.com/ProstoSanja"><i aria-label="GitHub" class="bi bi-github"></i></a>
</li>
<li>jso</li>
<li>
<a href="https://reddit.com/user/Hans5958_">Hans5958_</a>
<a href="https://hans5958.github.io/"><i aria-label="Website" class="bi bi-globe"></i></a>
<a href="https://github.com/Hans5958"><i aria-label="GitHub" class="bi bi-github"></i></a>
</li>
<li>
<a href="https://reddit.com/user/mxdanger">mxdanger</a>
<a href="https://github.com/mxdanger"><i aria-label="GitHub" class="bi bi-github"></i></a>
</li>
</ul>
</li>
<li>Wiki Team:
<ul>
<li>
<a href="https://reddit.com/user/xXLInkster17Xx">xXLInkster17Xx</a> (Aeywoo)
<a href="https://github.com/Aeywoo"><i aria-label="GitHub" class="bi bi-github"></i></a>
</li>
<li>
Nick12
<a href="https://github.com/nico-abram"><i aria-label="GitHub" class="bi bi-github"></i></a>
</li>
<li>pax</li>
<li>Artillect</li>
<li>axollyon</li>
<li>
<a href="https://reddit.com/user/Crkza">Crzka</a> (Cappy)
<a href="https://github.com/korewaChino"><i aria-label="GitHub" class="bi bi-github"></i></a>
</li>
<li>CodeDev</li>
<li>dntk</li>
<li>Eurobat</li>
<li>FuriousChocolate</li>
<li>
<a href="https://reddit.com/geekahedron">geekahedron</a>
</li>
<li>Jim Milton</li>
<li>
Noah
<a href="https://github.com/NoahS04"><i aria-label="GitHub" class="bi bi-github"></i></a>
</li>
<li>Saya</li>
<li>
Sodapone
<a href="https://github.com/Sodapone"><i aria-label="GitHub" class="bi bi-github"></i></a>
</li>
<li>
tea
<a href="https://github.com/dreamingkills"><i aria-label="GitHub" class="bi bi-github"></i></a>
</li>
<li>Zurke</li>
</ul>
</li>
<li>Others:
<ul>
<li>"The Final Clean" canvas: r/TheFinalClean community</li>
<li>Everyone who contributes from <a href="https://github.com/placeAtlas/atlas-2022/graphs/contributors">GitHub</a>, <a href="https://discord.gg/pJkm23b2nA">Discord</a>, or other channels.</li>
</ul>
</li>
</ul> </ul>
<h3>Contributors</h3> <h3 class="mt-4">Entry Contributors</h3>
<p>The 2022 Atlas would not have been possible without the help of our <span id="contributors-count"></span> contributors.</p> <p>The 2022 Atlas would not have been possible without the help of our <span id="contributors-count"></span> entry contributors.</p>
<p>Thank you to everyone who submitted new entries, amended existing ones, reported bugs and just supported the project in general.</p> <p>Thank you to everyone who submitted new entries and amended existing ones.</p>
<div id="contributors-wrapper" style="text-align: justify;"></div> <div id="entry-contributors-wrapper" style="text-align: justify;"></div>
</div> </div>
</div> </div>
<div class="col-md-5 col-xl-4"> <div class="col-md-5 col-xl-4">
@ -218,9 +138,9 @@
<h4>Sponsor this project</h4> <h4>Sponsor this project</h4>
<p>Current 2022 Atlas Maintainers:</p> <p>Current 2022 Atlas Maintainers:</p>
<ul class="mb-0"> <ul class="mb-0">
<li><a href="https://paypal.me/placeAtlas/5">PayPal</a></li> <li><a href="https://paypal.me/placeAtlas/5" target="_blank" rel="noreferrer">PayPal</a></li>
<li><a href="https://www.patreon.com/placeAtlas">Patreon</a></li> <li><a href="https://www.patreon.com/placeAtlas" target="_blank" rel="noreferrer">Patreon</a></li>
<li><a href="https://ko-fi.com/placeatlas">Ko-Fi</a></li> <li><a href="https://ko-fi.com/placeatlas" target="_blank" rel="noreferrer">Ko-Fi</a></li>
</ul> </ul>
</div> </div>
<div class="p-4"> <div class="p-4">
@ -235,7 +155,7 @@
</div> </div>
</div> </div>
<footer class="pt-3 mt-4 text-muted border-top"> <footer class="pt-3 mt-4 text-muted border-top">
Original by <a href="https://draemm.li/various/place-atlas/" target="_blank" rel="noopener noreferrer author">Roland Rytz</a> (<a href="https://github.com/RolandR/place-atlas">source</a>). Maintained by <a href="https://github.com/placeAtlas/atlas-2022/contributors">Place Atlas contributors</a> (<a href="https://github.com/placeAtlas/atlas-2022">source</a>). Original by <a href="https://draemm.li/various/place-atlas/" target="_blank" rel="noreferrer">Roland Rytz</a> (<a href="https://github.com/RolandR/place-atlas" target="_blank">source</a>). Maintained by <a href="https://place-atlas.stefanocoding.me" target="_blank">Place Atlas Initiative</a> and <a href="https://github.com/placeAtlas/atlas-2022/graphs/contributors" target="_blank">contributors</a> (<a href="https://github.com/placeAtlas/atlas-2022" target="_blank">source</a>).
</footer> </footer>
</div> </div>
</main> </main>

View File

@ -1,7 +1,7 @@
<!-- <!--
The 2022 r/place Atlas The 2022 r/place Atlas
Copyright (c) 2017 Roland Rytz <roland@draemm.li> Copyright (c) 2017 Roland Rytz <roland@draemm.li>
Copyright (c) 2022 Place Atlas contributors Copyright (c) 2022 Place Atlas Initiative and contributors
Licensed under AGPL-3.0 (https://2022.place-atlas.stefanocoding.me/license.txt) Licensed under AGPL-3.0 (https://2022.place-atlas.stefanocoding.me/license.txt)
--> -->
@ -11,10 +11,10 @@
<meta charset="UTF-8"> <meta charset="UTF-8">
<title>The 2022 r/place Atlas</title> <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."> <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.">
<meta name="author" content="Place Atlas contributors (original by Roland Rytz)"> <meta name="author" content="Place Atlas Initiative and contributors (original by Roland Rytz)">
<meta name="application-name" content="The r/place Atlas 2022"> <meta name="application-name" content="The 2022 r/place Atlas">
<meta name="robots" content="index, follow"> <meta name="robots" content="index, follow">
<meta property="og:title" content="The 2022 r/place Atlas"> <meta property="og:title" content="The 2022 r/place Atlas">
<meta property="og:type" content="website"> <meta property="og:type" content="website">
<meta property="og:url" content="https://2022.place-atlas.stefanocoding.me/"> <meta property="og:url" content="https://2022.place-atlas.stefanocoding.me/">
@ -22,11 +22,10 @@
<meta property="og:image:type" content="image/png"> <meta property="og:image:type" content="image/png">
<meta property="og:image:width" content="512"> <meta property="og:image:width" content="512">
<meta property="og:image:height" content="512"> <meta property="og:image:height" content="512">
<meta property="og:image:alt" content="The r/place Atlas logo"> <meta property="og:image:alt" content="The 2022 r/place Atlas logo">
<meta property="og:description" content="An interactive map of Reddit's 2022 r/place, with information to each artwork of the canvas provided by the community."> <meta property="og:description" content="An interactive map of Reddit's 2022 r/place, with information to each artwork of the canvas provided by the community.">
<meta name="twitter:card" content="summary"> <meta name="twitter:card" content="summary">
<meta name="twitter:creator" content="Place Atlas contributors (original by Roland Rytz)">
<meta name="twitter:url" content="https://2022.place-atlas.stefanocoding.me/"> <meta name="twitter:url" content="https://2022.place-atlas.stefanocoding.me/">
<meta name="twitter:title" content="The 2022 r/place Atlas"> <meta name="twitter:title" content="The 2022 r/place Atlas">
<meta name="twitter:description" content="An interactive map of Reddit's 2022 r/place, with information to each artwork of the canvas provided by the community."> <meta name="twitter:description" content="An interactive map of Reddit's 2022 r/place, with information to each artwork of the canvas provided by the community.">
@ -34,22 +33,21 @@
<meta name="twitter:image:alt" content="The 2022 r/place Atlas logo"> <meta name="twitter:image:alt" content="The 2022 r/place Atlas logo">
<!-- <meta name="google-site-verification" content="gZGHpBSMzffAbIn0qB8b00We6EwSGkDTfDoQVv-NWss"/> --> <!-- <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="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="mobile-web-app-capable" content="yes">
<meta name="color-scheme" content="light dark"> <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: light)" content="white">
<meta name="theme-color" media="(prefers-color-scheme: dark)" content="#222"> <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="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 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="icon" href="_img/favicon.svg" type="image/svg+xml" class="js-site-favicon">
<link rel="stylesheet" href="./_css/style.css"> <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-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"> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.8.1/font/bootstrap-icons.css">
<link rel="manifest" href="./manifest.webmanifest"> <link rel="manifest" href="./manifest.webmanifest">
<link rel="me" href="https://enshittification.social/@Codixer">
<script type="module" src="https://cdn.jsdelivr.net/npm/@pwabuilder/pwaupdate/dist/pwa-update.js"></script> <script type="module" src="https://cdn.jsdelivr.net/npm/@pwabuilder/pwaupdate/dist/pwa-update.js"></script>
<script src="./_js/favicon.js" defer></script> <script src="./_js/favicon.js" defer></script>
<script type="application/ld+json"> <script type="application/ld+json">
@ -83,10 +81,10 @@
}, },
{ {
"@type": "Organization", "@type": "Organization",
"name": "Place Atlas", "name": "Place Atlas Initiative",
"alternateName": "r/placeatlas2", "alternateName": "r/placeAtlas2023",
"url": "https://github.com/placeAtlas", "url": "https://place-atlas.stefanocoding.me",
"image": "http://2022.place-atlas.stefanocoding.me/_img/logo.png", "image": "https://place-atlas.stefanocoding.me/assets/logo.png",
"founder": { "founder": {
"@type": "Person", "@type": "Person",
"@id": "#Codixer", "@id": "#Codixer",
@ -115,14 +113,14 @@
<body> <body>
<div id="wrapper" class="listHidden"> <div id="wrapper" class="listHidden">
<header class="fixed-top"> <header class="fixed-top">
<aside class="container-fluid alert-primary collapse" id="headerAnnouncement"> <aside class="container-fluid alert-primary collapse" id="headerNotice">
<div class="d-flex w-100 py-2 align-items-center"> <div class="d-flex w-100 py-2 align-items-center">
<i class="bi bi-info-circle-fill fs-5"></i> <i class="bi bi-info-circle-fill fs-5"></i>
<p class="mb-0 flex-grow-1 px-2"> This is the 2022 edition of the r/place Atlas. Check out <a class="alert-link" href="https://2023.place-atlas.stefanocoding.me">the 2023 r/place Atlas</a>!</p> <p class="mb-0 flex-grow-1 px-2">This is the 2022 edition of the r/place Atlas. For the latest one, check out <a class="alert-link" href="https://2023.place-atlas.stefanocoding.me">the 2023 r/place Atlas</a>.</p>
<a class="fs-5" data-bs-toggle="collapse" href="#headerAnnouncement" role="button" aria-expanded="false" aria-controls="headerAnnouncement"><i class="bi bi-x-lg"></i></a> <a class="fs-5" data-bs-toggle="collapse" href="#headerNotice" role="button" aria-expanded="false" aria-controls="headerNotice"><i class="bi bi-x-lg"></i></a>
</div> </div>
</aside> </aside>
<nav id="main-navbar" class="navbar navbar-expand-md bg-body border-bottom"> <nav id="main-navbar" class="navbar navbar-expand-lg bg-body border-bottom">
<div class="container-fluid"> <div class="container-fluid">
<a class="navbar-brand text-body d-flex align-items-center" href="./"> <a class="navbar-brand text-body d-flex align-items-center" href="./">
<picture> <picture>
@ -135,21 +133,22 @@
<i class="bi bi-list" aria-hidden="true"></i> <i class="bi bi-list" aria-hidden="true"></i>
</button> </button>
<div class="collapse navbar-collapse" id="navbarCollapse"> <div class="collapse navbar-collapse" id="navbarCollapse">
<ul class="navbar-nav me-auto my-2 my-md-0"> <ul class="navbar-nav me-auto my-2 my-lg-0">
<li class="nav-item"> <li class="nav-item">
<div class="btn-group" role="group"> <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> </div>
</li> </li>
</ul> </ul>
<ul class="navbar-nav mx-auto d-block d-md-none d-lg-block"> <ul class="navbar-nav mx-auto d-block">
<li class="nav-item d-flex align-items-center gap-2"> <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-lg-2">Coordinates: <span class="badge bg-secondary" id="coords_p">0, 0</span></span>
</li> </li>
</ul> </ul>
<hr class="d-md-none"> <hr class="d-lg-none">
<ul class="navbar-nav flex-row flex-wrap ms-auto"> <ul class="navbar-nav flex-row flex-wrap ms-auto">
<li class="nav-item dropdown"> <li class="nav-item col-6 col-lg-auto dropdown">
<a class="nav-link dropdown-toggle active" href="#" role="button" data-bs-toggle="dropdown" aria-expanded="false" aria-current="page"> <a class="nav-link dropdown-toggle active" href="#" role="button" data-bs-toggle="dropdown" aria-expanded="false" aria-current="page">
Atlas Map Atlas Map
</a> </a>
@ -159,10 +158,13 @@
<li><a class="dropdown-item" href="https://2023.place-atlas.stefanocoding.me/">2023</a></li> <li><a class="dropdown-item" href="https://2023.place-atlas.stefanocoding.me/">2023</a></li>
</ul> </ul>
</li> </li>
<li class="nav-item col-6 col-md-auto"> <li class="nav-item col-6 col-lg-auto">
<a class="nav-link" href="https://place-wiki.stefanocoding.me/">Place Wiki</a> <a class="nav-link" href="https://place-wiki.stefanocoding.me/" target="_blank">Place Wiki</a>
</li> </li>
<li class="nav-item col-6 col-md-auto"> <li class="nav-item col-6 col-lg-auto">
<a class="nav-link" href="https://github.com/placeAtlas/atlas-2022" target="_blank">Source</a>
</li>
<li class="nav-item col-6 col-lg-auto">
<a class="nav-link" href="./about.html">About</a> <a class="nav-link" href="./about.html">About</a>
</li> </li>
</ul> </ul>
@ -179,8 +181,8 @@
<h4 class="mb-3">Hang on…</h4> <h4 class="mb-3">Hang on…</h4>
<noscript> <noscript>
<p>Sorry, you need JavaScript to view the Atlas.</p> <p>Sorry, you need JavaScript to view the Atlas.</p>
<p>All JavaScript on this site is licensed under either the MIT or AGPL license.</p> <p>All JS scripts on this project is under a free license.</p>
<p><a href="https://github.com/placeAtlas/atlas-2022">See the source on GitHub</a></p> <p><a href="https://github.com/placeAtlas/atlas-2022" target="_blank">See the source on GitHub.</a></p>
</noscript> </noscript>
</div> </div>
</div> </div>
@ -199,8 +201,7 @@
</header> </header>
<div class="py-3 mx-3 border-bottom"> <div class="py-3 mx-3 border-bottom">
<div class="d-flex gap-2 mb-2"> <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-100" id="dropdownModes" data-bs-toggle="dropdown" aria-expanded="false">Layer Views</button>
<button type="button" class="btn btn-primary dropdown-toggle w-50" id="dropdownModes" data-bs-toggle="dropdown" aria-expanded="false">Modes</button>
<ul class="dropdown-menu dropdown-menu-end" aria-labelledby="dropdownModes"> <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="./">Normal</a></li>
<li><a class="dropdown-item" href="./?mode=explore">Explore</a></li> <li><a class="dropdown-item" href="./?mode=explore">Explore</a></li>
@ -211,11 +212,11 @@
</ul> </ul>
</div> </div>
<div class="btn-group w-100 mb-3" role="group" aria-label="Social links and donate button"> <div class="btn-group w-100 mb-3" role="group" aria-label="Social links and donate button">
<a class="btn btn-outline-primary" href="https://discord.gg/pJkm23b2nA" target="_blank" rel="noopener noreferrer"> <a class="btn btn-outline-primary" href="https://discord.gg/pJkm23b2nA" target="_blank" rel="noreferrer">
<i class="bi bi-discord" aria-hidden="true"></i> <i class="bi bi-discord" aria-hidden="true"></i>
Discord Discord
</a> </a>
<a class="btn btn-outline-primary" href="https://www.reddit.com/r/placeAtlas2/" target="_blank" rel="noopener noreferrer"> <a class="btn btn-outline-primary" href="https://www.reddit.com/r/placeAtlas2022/" target="_blank" rel="noreferrer">
<i class="bi bi-reddit" aria-hidden="true"></i> <i class="bi bi-reddit" aria-hidden="true"></i>
Reddit Reddit
</a> </a>
@ -272,7 +273,7 @@
</datalist> </datalist>
</div> </div>
<div id="author" class="bg-body d-flex align-items-center justify-content-center py-1 px-2 rounded shadow" style="--bs-bg-opacity: .9;"> <div id="author" class="bg-body d-flex align-items-center justify-content-center py-1 px-2 rounded shadow" style="--bs-bg-opacity: .9;">
<span style="font-size: 0.7rem;">Code by <a href="https://github.com/placeAtlas" target="_blank" rel="noopener noreferrer">Place Atlas</a>. Source on <a href="https://github.com/placeAtlas/atlas-2022" target="_blank" rel="noopener noreferrer">GitHub</a>. Site powered by <a href="https://www.netlify.com" target="_blank" rel="noopener noreferrer">Netlify</a>.</span> <span style="font-size: 0.7rem;">AGPL-3.0 <span class="copyleft">©</span> <a href="https://place-atlas.stefanocoding.me" target="_blank">Place Atlas Initiative</a> and <a href="https://github.com/placeAtlas/atlas-2022/graphs/contributors" target="_blank">contributors</a>. Powered by <a href="https://www.netlify.com" target="_blank" rel="noreferrer">Netlify</a>.</span>
</div> </div>
</div> </div>
<div class="offcanvas offcanvas-start bg-body" data-bs-scroll="true" data-bs-backdrop="false" tabindex="-1" id="offcanvasDraw" aria-labelledby="offcanvasDrawLabel"> <div class="offcanvas offcanvas-start bg-body" data-bs-scroll="true" data-bs-backdrop="false" tabindex="-1" id="offcanvasDraw" aria-labelledby="offcanvasDrawLabel">
@ -295,7 +296,7 @@
Redo Redo
</button> </button>
</div> </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> <button type="button" class="btn btn-secondary" id="resetButton">Reset</button>
<div class="form-check" id="highlightUnchartedLabel"> <div class="form-check" id="highlightUnchartedLabel">
@ -312,7 +313,7 @@
<button type="button" class="btn btn-primary d-block mx-auto" id="periodsAdd">Add Period</button> <button type="button" class="btn btn-primary d-block mx-auto" id="periodsAdd">Add Period</button>
</div> </div>
<div id="hint"> <div id="hint">
<p class="text-center">Please read <a href="https://github.com/placeAtlas/atlas-2022/blob/master/CONTRIBUTING.md" target="_blank" rel="noopener noreferrer">this guide</a> for instructions.</p> <p class="text-center">Please read <a href="https://github.com/placeAtlas/atlas-2022/blob/master/CONTRIBUTING.md" target="_blank">the contributing guide</a> for instructions.</p>
<hr> <hr>
<p>You can suggest new entries to the Atlas for art that isn't mapped yet, or update entries by editing it.</p> <p>You can suggest new entries to the Atlas for art that isn't mapped yet, or update entries by editing it.</p>
<p>Click anywhere on the image to start drawing a shape. Switch between periods by adding a period, and/or seek through the timeline.</p> <p>Click anywhere on the image to start drawing a shape. Switch between periods by adding a period, and/or seek through the timeline.</p>
@ -320,7 +321,7 @@
<hr> <hr>
<div class="text-white p-3 rounded shadow" style="background-color: #5865F2;"> <div class="text-white p-3 rounded shadow" style="background-color: #5865F2;">
<h6>Need Help?</h6> <h6>Need Help?</h6>
<p class="mb-0">You can ask for help on <a class="link-light" href="https://discord.gg/pJkm23b2nA" target="_blank" rel="noopener noreferrer">our Discord server</a>!</p> <p class="mb-0">You can ask for help on <a class="link-light" href="https://discord.gg/pJkm23b2nA" target="_blank" rel="noreferrer">our Discord server</a>!</p>
</div> </div>
</div> </div>
</div> </div>
@ -328,11 +329,11 @@
<form id="objectInfo" class="d-flex flex-column p-3 bg-secondary bg-opacity-10 rounded"> <form id="objectInfo" class="d-flex flex-column p-3 bg-secondary bg-opacity-10 rounded">
<div class="mb-3"> <div class="mb-3">
<label for="nameField" class="form-label">Title</label> <label for="nameField" class="form-label">Title</label>
<input type="text" id="nameField" class="form-control" placeholder="A short title" pattern="(.|\s)*\S(.|\s)*" title="Cannot be blank" required> <input type="text" id="nameField" class="form-control" placeholder="A short title" pattern="(.|\s)*\S(.|\s)*" required>
</div> </div>
<div class="mb-3"> <div class="mb-3">
<label for="descriptionField" class="form-label">Description</label> <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> </div>
<label id="websiteLabel" class="form-label">Website</label> <label id="websiteLabel" class="form-label">Website</label>
<div id="websiteGroup" class="mb-3 d-flex flex-column gap-2"></div> <div id="websiteGroup" class="mb-3 d-flex flex-column gap-2"></div>
@ -370,10 +371,10 @@
</div> </div>
<div class="modal-body d-flex flex-column"> <div class="modal-body d-flex flex-column">
<p> <p>
If you want to use Reddit, use the <span class="badge bg-primary">Post Direct to Reddit</span> button or manually copy the text below and submit it as a new text post to <a href="https://www.reddit.com/r/placeAtlas2/" target="_blank" rel="noopener noreferrer">r/placeAtlas2</a> on Reddit. If you want to use Reddit, use the <span class="badge bg-primary">Post Direct to Reddit</span> button or manually copy the text below and submit it as a new text post to <a href="https://www.reddit.com/r/placeAtlas2/" target="_blank" rel="noreferrer">r/placeAtlas2</a> on Reddit.
Don't forget to flair it with the <span class="badge rounded-pill bg-primary"><i class="bi bi-tag" aria-hidden="true"></i> <span id="redditFlair">New Entry</span></span> flair.</p> Don't forget to flair it with the <span class="badge rounded-pill bg-primary"><i class="bi bi-tag" aria-hidden="true"></i> <span id="redditFlair">New Entry</span></span> flair.</p>
<p> <p>
If you want to use GitHub, use the <span class="badge bg-primary">Submit Direct to GitHub</span> button, or read <a href="https://github.com/placeAtlas/atlas-2022/blob/master/CONTRIBUTING.md#through-github" target="_blank" rel="noopener noreferrer">the contributing guide</a> to submit a patch. If you want to use GitHub, use the <span class="badge bg-primary">Submit Direct to GitHub</span> button, or read <a href="https://github.com/placeAtlas/atlas-2022/blob/master/CONTRIBUTING.md#through-github" target="_blank">the contributing guide</a> to submit a patch.
</p> </p>
<p>We will then check it and add it to the Atlas.</p> <p>We will then check it and add it to the Atlas.</p>
<textarea class="form-control flex-grow-1" cols="40" rows="20" id="exportString" title="Raw JSON string" readonly></textarea> <textarea class="form-control flex-grow-1" cols="40" rows="20" id="exportString" title="Raw JSON string" readonly></textarea>
@ -381,8 +382,8 @@
<div class="modal-footer"> <div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Done</button> <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Done</button>
<button id="exportCopy" type="button" class="btn btn-secondary">Copy</button> <button id="exportCopy" type="button" class="btn btn-secondary">Copy</button>
<a id="exportGithubPost" class="btn btn-primary" href="#" target="_blank" rel="noopener noreferrer">Submit Direct on GitHub</a> <a id="exportGithubPost" class="btn btn-primary" href="#" target="_blank" rel="noreferrer">Submit Direct on GitHub</a>
<a id="exportRedditPost" class="btn btn-primary" href="#" target="_blank" rel="noopener noreferrer">Post Direct on Reddit</a> <a id="exportRedditPost" class="btn btn-primary" href="#" target="_blank" rel="noreferrer">Post Direct on Reddit</a>
</div> </div>
</div> </div>
</div> </div>
@ -396,21 +397,21 @@
</div> </div>
<div class="modal-body"> <div class="modal-body">
<p>Current 2022 Atlas Maintainers:</p> <p>Current 2022 Atlas Maintainers:</p>
<a class="btn btn-primary" target="_blank" rel="noopener noreferrer" href="https://paypal.me/placeAtlas/5"> <a class="btn btn-primary" target="_blank" rel="noreferrer" href="https://paypal.me/placeAtlas/5">
<i class="bi bi-paypal" aria-hidden="true"></i> <i class="bi bi-paypal" aria-hidden="true"></i>
PayPal PayPal
</a> </a>
<a class="btn btn-primary" target="_blank" rel="noopener noreferrer" href="https://www.patreon.com/placeAtlas"> <a class="btn btn-primary" target="_blank" rel="noreferrer" href="https://www.patreon.com/placeAtlas">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="icon" viewBox="0 0 24 24" aria-hidden="true"><path d="M0 .48v23.04h4.22V.48zm15.385 0c-4.764 0-8.641 3.88-8.641 8.65 0 4.755 3.877 8.623 8.641 8.623 4.75 0 8.615-3.868 8.615-8.623C24 4.36 20.136.48 15.385.48z"/></svg> <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="icon" viewBox="0 0 24 24" aria-hidden="true"><path d="M0 .48v23.04h4.22V.48zm15.385 0c-4.764 0-8.641 3.88-8.641 8.65 0 4.755 3.877 8.623 8.641 8.623 4.75 0 8.615-3.868 8.615-8.623C24 4.36 20.136.48 15.385.48z"/></svg>
Patreon Patreon
</a> </a>
<a class="btn btn-primary" target="_blank" rel="noopener noreferrer" href="https://ko-fi.com/placeatlas"> <a class="btn btn-primary" target="_blank" rel="noreferrer" href="https://ko-fi.com/placeatlas">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="icon" viewBox="0 0 24 24" aria-hidden="true"><path d="M23.881 8.948c-.773-4.085-4.859-4.593-4.859-4.593H.723c-.604 0-.679.798-.679.798s-.082 7.324-.022 11.822c.164 2.424 2.586 2.672 2.586 2.672s8.267-.023 11.966-.049c2.438-.426 2.683-2.566 2.658-3.734 4.352.24 7.422-2.831 6.649-6.916zm-11.062 3.511c-1.246 1.453-4.011 3.976-4.011 3.976s-.121.119-.31.023c-.076-.057-.108-.09-.108-.09-.443-.441-3.368-3.049-4.034-3.954-.709-.965-1.041-2.7-.091-3.71.951-1.01 3.005-1.086 4.363.407 0 0 1.565-1.782 3.468-.963 1.904.82 1.832 3.011.723 4.311zm6.173.478c-.928.116-1.682.028-1.682.028V7.284h1.77s1.971.551 1.971 2.638c0 1.913-.985 2.667-2.059 3.015z"/></svg> <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="icon" viewBox="0 0 24 24" aria-hidden="true"><path d="M23.881 8.948c-.773-4.085-4.859-4.593-4.859-4.593H.723c-.604 0-.679.798-.679.798s-.082 7.324-.022 11.822c.164 2.424 2.586 2.672 2.586 2.672s8.267-.023 11.966-.049c2.438-.426 2.683-2.566 2.658-3.734 4.352.24 7.422-2.831 6.649-6.916zm-11.062 3.511c-1.246 1.453-4.011 3.976-4.011 3.976s-.121.119-.31.023c-.076-.057-.108-.09-.108-.09-.443-.441-3.368-3.049-4.034-3.954-.709-.965-1.041-2.7-.091-3.71.951-1.01 3.005-1.086 4.363.407 0 0 1.565-1.782 3.468-.963 1.904.82 1.832 3.011.723 4.311zm6.173.478c-.928.116-1.682.028-1.682.028V7.284h1.77s1.971.551 1.971 2.638c0 1.913-.985 2.667-2.059 3.015z"/></svg>
Ko-Fi Ko-Fi
</a> </a>
<hr> <hr>
<p>Original 2017 Atlas Developer (draemmli aka Roland Rytz): </p> <p>Original 2017 Atlas Developer (draemmli aka Roland Rytz): </p>
<a class="btn btn-primary" target="_blank" rel="noopener noreferrer" href="https://paypal.me/draemmli"> <a class="btn btn-primary" target="_blank" rel="noreferrer" href="https://paypal.me/draemmli">
<i class="bi bi-paypal" aria-hidden="true"></i> <i class="bi bi-paypal" aria-hidden="true"></i>
PayPal PayPal
</a> </a>

View File

@ -3,9 +3,9 @@
"display": "standalone", "display": "standalone",
"scope": "/", "scope": "/",
"start_url": "/", "start_url": "/",
"name": "The r/place Atlas", "name": "The 2022 r/place Atlas",
"short_name": "r/placeAtlas2", "short_name": "r/placeAtlas2",
"description": "The atlas for the r/place event of 2022 hosted on Reddit.", "description": "The atlas for Reddit's r/place event of 2022.",
"icons": [ "icons": [
{ {
"src": "./_img/pwa/logo-round-192x192.png", "src": "./_img/pwa/logo-round-192x192.png",

View File

@ -1,5 +1,3 @@
// This is the "Offline copy of assets" service worker
importScripts('https://cdn.jsdelivr.net/npm/workbox-sw@6.5.4/build/workbox-sw.js'); importScripts('https://cdn.jsdelivr.net/npm/workbox-sw@6.5.4/build/workbox-sw.js');
self.addEventListener("message", event => { self.addEventListener("message", event => {
@ -9,13 +7,15 @@ self.addEventListener("message", event => {
}); });
workbox.routing.registerRoute( workbox.routing.registerRoute(
({ url }) => {!url.pathname.startsWith('/_img/canvas/')}, ({ url }) => !url.pathname.startsWith('/_img/canvas/'),
// `workbox.strategies.StaleWhileRevalidate` is used to reduce server contact.
// Change to `workbox.strategies.NetworkFirst` when updating is required.
new workbox.strategies.NetworkFirst({ new workbox.strategies.NetworkFirst({
cacheName: "main", cacheName: "main",
plugins: [ plugins: [
new workbox.backgroundSync.BackgroundSyncPlugin( new workbox.backgroundSync.BackgroundSyncPlugin(
"main-queue", { "main-queue", {
maxRetentionTime: 24 * 60 // 24 hours (in minutes) maxRetentionTime: 4 * 7 * 24 * 60 // 4 weeks (in minutes)
} }
) )
] ]
@ -24,14 +24,9 @@ workbox.routing.registerRoute(
workbox.routing.registerRoute( workbox.routing.registerRoute(
({ url }) => url.pathname.startsWith('/_img/canvas/'), ({ url }) => url.pathname.startsWith('/_img/canvas/'),
// `workbox.strategies.CacheFirst` is used to minimize server contact, due to their size.
// Change to `workbox.strategies.StateWhileRevalidate` when updating is required.
new workbox.strategies.CacheFirst({ new workbox.strategies.CacheFirst({
cacheName: "canvas", cacheName: "canvas"
plugins: [
new workbox.backgroundSync.BackgroundSyncPlugin(
"canvas-queue", {
maxRetentionTime: 4 * 7 * 24 * 60 // 4 weeks (in minutes)
}
)
]
}) })
); );