1
Fork 1

Compare commits

...

31 Commits

Author SHA1 Message Date
HF 45fc4b4d9c final commit v1.0 2023-12-21 19:01:52 +01:00
HF 18d9828b2b silently fail when pixellog file doesn't exist
fix #76
2023-12-13 12:15:17 +01:00
HF 9c5596d86a only extract langs from server when full build 2023-12-13 11:25:24 +01:00
HF 8d41c6533d don't require server restart on client changes,
by watching the asset files and reloading if changed
2023-12-13 11:11:25 +01:00
HF 058290aa16 do minifyCss right after cleaning assets
Use hashes in css filenames again
2023-12-13 11:10:07 +01:00
HF 0a4199e633 make example ecosystem.yml watch the server script for changes 2023-12-13 10:58:13 +01:00
HF 05298604b1 fix image link in mail 2023-12-13 10:57:12 +01:00
HF 260f6a6210 fix typo 2023-12-13 04:05:16 +01:00
HF fb87a18bd7 update deployment scripts 2023-12-13 04:01:34 +01:00
HF 1304dfb4ba change all logger prefixes 2023-12-13 03:45:11 +01:00
HF eb6c76c1b6 add shard names to logfile names 2023-12-13 03:18:14 +01:00
HF 6ecffafd00 remove assets.json from deployement scripts 2023-12-13 02:07:46 +01:00
HF d8042552d8 move langage-code-to-country-code mapping into ssr filename 2023-12-13 02:04:55 +01:00
HF 3895bf292a only extract languages on full builds 2023-12-13 01:41:32 +01:00
hf dddc0d0823 Merge pull request 'Add ge translation' (#91) from sallbet/pixelplanet-fork:translations into master
Reviewed-on: ppfun/pixelplanet#91
2023-12-13 00:36:09 +00:00
HF b5ccda251c use multiple processes while building
extract langs on production builds not on dev
update webpack
2023-12-13 01:30:02 +01:00
HF ab840c6f23 default to english only production build in webpack config 2023-12-12 20:33:05 +01:00
HF 3aad00dca7 allow building of only specfic languages,
english is not a required locale anymore
2023-12-12 20:30:39 +01:00
HF 872c3a5659 clean webpack cache on full build
add option to clena assets
2023-12-12 18:58:53 +01:00
HF 997d887cfd dont generate styleassets.json 2023-12-12 18:58:34 +01:00
HF b48821e92e no-cache on http requests depending on cookies
(etag reevaluation still works)
2023-12-12 18:57:40 +01:00
HF a3edd2cb44 use sting 'en' for english translation instead of 'default' 2023-12-12 17:58:59 +01:00
HF 6d3b6edd8a cache all language builds in the same folder
write custom webpack loader to invalidate language specific files
choose most recent asset based on mtime rather than birthtime
2023-12-12 17:58:06 +01:00
HF 22b9cf2612 make asset detection smarter 2023-12-11 22:26:16 +01:00
HF b5d48be01f renaming entry points fixes different three.js compilations weirdly 2023-12-11 22:11:55 +01:00
HF af97cf7ec8 don't clean dist directory before build 2023-12-11 22:11:39 +01:00
HF bcc489eff9 fix etag caching when run on localhost
(languages were not possible to be selected if run locally, cause of
aggressive caching)
2023-12-11 20:56:26 +01:00
HF 6b0404b66d THREE.js doesn't like the timestamp name,
cause every language bundle builds its own three.js
change order of script insertion (vendor before client)
2023-12-11 20:54:52 +01:00
HF d2fa61f3c6 use port 5000 on example ecosystem.yml 2023-12-11 16:49:27 +01:00
HF fe8541bcbf save cookie by hostname instead of host, because cookies don't like
ports
2023-12-11 16:48:47 +01:00
HF 8544c42e7b deprecate assets.json by reading the assets directory ourselves,
use build timestamp instead of hash in filename
fix #92
2023-12-11 16:48:26 +01:00
39 changed files with 5025 additions and 4305 deletions

1
.gitignore vendored
View File

@ -10,6 +10,7 @@ logs
*.log
npm-debug.log*
*.tmp
records.json
pids
*.pid

View File

@ -1,3 +1,5 @@
> **Currently pixelplanet is being developed in a another private repository. This development version diverted and is incompatible with the stable version here. Until it is ready, this public repository will be frozen.**
# PixelPlanet.fun
@ -7,7 +9,7 @@ Official repository of [pixelplanet.fun](http://www.pixelplanet.fun).
![videothumb](promotion/videothumb.gif)
> **TRANSLATORS NEEDED** If you want to help us translate pixelplanet.fun, look into [i18n](./i18n)
> If you want to help us translate pixelplanet.fun, look into [i18n](./i18n)
To the 2nd anniversary of r/space, pixelplanet takes pixelgames to a new level. Place pixels, create pixelart and fight faction wars on pixelplanet.fun.
Pixelplanet is a 65k x 65k large canvas that is a map of the world and can also be seen as 3d globe, you can place pixels where ever you want, build an island, take over another country with a flag or just create pixelart.
@ -256,11 +258,19 @@ If v is set and true for a canvas in the canvas.json, it will be a 3D voxel canv
Run `npm run lint:src` to check for code errors and warnings or `npm run lint -- ./your/file.js` to check a single file.
[ttag](https://github.com/ttag-org/ttag/) is used for handling translations. For server-side rendering the `Accept-Language` header gets checked and the first locale used and on-the-fly translated (`src/core/ttag.js` provides the functions for it). On the client-side a seperate bundle for every language gets provided.
The language definitions in `i18n/template.pot` and `i18n/template-ssr.pot` get updated when doing a dev build with
Compile with source-maps and debug options (but only english language) with
```
npm run build:dev
```
which also only builds the default local in a development environment for debugging.
[ttag](https://github.com/ttag-org/ttag/) is used for handling translations. For server-side rendering the `Accept-Language` header gets checked and the first locale used and on-the-fly translated (`src/core/ttag.js` provides the functions for it). On the client-side a seperate bundle for every language gets provided.
The language definitions in `i18n/template.pot` and `i18n/template-ssr.pot` get updated when doing a full production build with all languages (`npm run build`)-
To build only specific languages, you can define them with the `--langs` flag:
```
npm run build -- --langs de,gr
```
You can use `npm run babel-node ./utils/script.js` to execute a script with local babel (path always relative to the root directory).

View File

@ -1,9 +1,11 @@
apps:
- script : ./server.js
name : 'ppfun-server'
node_args: --nouse-idle-notification --expose-gc
- script : ./server.js
name : 'ppfun'
node_args : --nouse-idle-notification --expose-gc
watch : [ 'server.js' ]
watch_delay: 5000
env:
PORT: 80
PORT: 5000
HOST: "localhost"
REDIS_URL: 'redis://localhost:6379'
MYSQL_HOST: "localhost"

View File

@ -1,15 +1,13 @@
#!/bin/bash
# This hook builds pixelplanet after a push, and deploys it, it should be ron post-receive
# If it is the master branch, it will deploy it on the life system, and other branch will get deployed to the dev-canvas (a second canvas that is running on the server)
# This hook builds pixelplanet after a push to a development branch,
# and starts the dev-canvas
#
# To set up a server to use this, you have to go through the building steps manually first.
#
#folder for building the canvas (the git repository will get checkout there and the canvas will get buil thtere)
#folder for building the canvas (the git repository will get checkout there and the canvas will get built thtere)
BUILDDIR="/home/pixelpla/pixelplanet-build"
#folder for dev canvas
DEVFOLDER="/home/pixelpla/pixelplanet-dev"
#folder for production canvas
PFOLDER="/home/pixelpla/pixelplanet"
should_reinstall () {
local TMPFILE="${BUILDDIR}/package.json.${1}.tmp"
@ -33,14 +31,12 @@ npm_reinstall () {
copy () {
local TARGETDIR="${1}"
local REINSTALL="${2}"
cp -r dist/*.js "${TARGETDIR}/"
cp -r dist/workers "${TARGETDIR}/"
cp -r "${BUILDDIR}"/dist/*.js "${TARGETDIR}/"
cp -r "${BUILDDIR}"/dist/workers "${TARGETDIR}/"
rm -rf "${TARGETDIR}/public/assets"
cp -r dist/public "${TARGETDIR}/"
cp -r dist/captchaFonts "${TARGETDIR}/"
cp -r dist/package.json "${TARGETDIR}/"
cp -r dist/assets.json "${TARGETDIR}/"
cp -r dist/styleassets.json "${TARGETDIR}/"
cp -r "${BUILDDIR}"/dist/public "${TARGETDIR}/"
cp -r "${BUILDDIR}"/dist/captchaFonts "${TARGETDIR}/"
cp -r "${BUILDDIR}"/dist/package.json "${TARGETDIR}/"
mkdir -p "${TARGETDIR}/log"
cd "${TARGETDIR}"
[ $REINSTALL -eq 0 ] && npm_reinstall
@ -53,28 +49,10 @@ do
GIT_WORK_TREE="$BUILDDIR" GIT_DIR="${BUILDDIR}/.git" git fetch --all
cd "$BUILDDIR"
branch=$(git rev-parse --symbolic --abbrev-ref $refname)
if [ "master" == "$branch" ]; then
echo "---UPDATING REPO ON PRODUCTION SERVER---"
GIT_WORK_TREE="$BUILDDIR" GIT_DIR="${BUILDDIR}/.git" git reset --hard "origin/$branch"
COMMITS=`git log --pretty=format:'- %s%b' $newrev ^$oldrev`
COMMITS=`echo "$COMMITS" | sed ':a;N;$!ba;s/\n/\\\n/g'`
echo "---BUILDING pixelplanet---"
should_reinstall master
DO_REINSTALL=$?
[ $DO_REINSTALL -eq 0 ] && npm_reinstall
npm run build
echo "---RESTARTING CANVAS---"
pm2 stop ppfun-server
pm2 stop ppfun-backups
copy "${PFOLDER}" "${DO_REINSTALL}"
cd "$PFOLDER"
pm2 start ecosystem-backup.yml
else
if [ "test" == "$branch" ] || [ "devel" == "$branch" ]; then
echo "---UPDATING REPO ON DEV SERVER---"
pm2 stop ppfun-server-dev
GIT_WORK_TREE="$BUILDDIR" GIT_DIR="${BUILDDIR}/.git" git reset --hard "origin/$branch"
COMMITS=`git log --pretty=format:'- %s%b' $newrev ^$oldrev`
COMMITS=`echo "$COMMITS" | sed ':a;N;$!ba;s/\n/\\\n/g'`
echo "---BUILDING pixelplanet---"
should_reinstall dev
DO_REINSTALL=$?

View File

@ -1,18 +1,10 @@
#!/bin/bash
# Rebuild from master branch and restart pixelplanet
# Rebuild and Restert pixelplanet
#folder for building the canvas (the git repository will get checkout there and the canvas will get buil thtere)
BUILDDIR="/home/pixelpla/pixelplanet-build"
#folder for dev canvas
DEVFOLDER="/home/pixelpla/pixelplanet-dev"
#folder for shards
SCBFOLDER="/home/pixelpla/pixelplanet-scb"
SCCFOLDER="/home/pixelpla/pixelplanet-scc"
SCDFOLDER="/home/pixelpla/pixelplanet-scd"
SCEFOLDER="/home/pixelpla/pixelplanet-sce"
SCFFOLDER="/home/pixelpla/pixelplanet-scf"
SCGFOLDER="/home/pixelpla/pixelplanet-scg"
SCHFOLDER="/home/pixelpla/pixelplanet-sch"
#folder for production canvas
PFOLDER="/home/pixelpla/pixelplanet"
#which branch to use
@ -40,23 +32,20 @@ npm_reinstall () {
copy () {
local TARGETDIR="${1}"
local REINSTALL="${2}"
cp -r dist/*.js "${TARGETDIR}/"
cp -r dist/workers "${TARGETDIR}/"
cp -r "${BUILDDIR}"/dist/*.js "${TARGETDIR}/"
cp -r "${BUILDDIR}"/dist/workers "${TARGETDIR}/"
rm -rf "${TARGETDIR}/public/assets"
cp -r dist/public "${TARGETDIR}/"
cp -r dist/captchaFonts "${TARGETDIR}/"
cp -r dist/package.json "${TARGETDIR}/"
cp -r dist/assets.json "${TARGETDIR}/"
cp -r dist/styleassets.json "${TARGETDIR}/"
cp -r "${BUILDDIR}"/dist/public "${TARGETDIR}/"
cp -r "${BUILDDIR}"/dist/captchaFonts "${TARGETDIR}/"
cp -r "${BUILDDIR}"/dist/package.json "${TARGETDIR}/"
mkdir -p "${TARGETDIR}/log"
cd "${TARGETDIR}"
[ $REINSTALL -eq 0 ] && npm_reinstall
pm2 start ecosystem.yml
[ ${REINSTALL} -eq 0 ] && npm_reinstall
cd -
}
GIT_WORK_TREE="$BUILDDIR" GIT_DIR="${BUILDDIR}/.git" git fetch --all
cd "$BUILDDIR"
GIT_WORK_TREE="$BUILDDIR" GIT_DIR="${BUILDDIR}/.git" git fetch --all
echo "---UPDATING REPO ON PRODUCTION SERVER---"
GIT_WORK_TREE="$BUILDDIR" GIT_DIR="${BUILDDIR}/.git" git reset --hard "origin/${BRANCH}"
echo "---BUILDING pixelplanet---"
@ -65,22 +54,10 @@ DO_REINSTALL=$?
[ $DO_REINSTALL -eq 0 ] && npm_reinstall
npm run build
echo "---RESTARTING CANVAS---"
pm2 stop ppfun-server
pm2 stop ppfun-backups
pm2 stop ppfun-scb
pm2 stop ppfun-scc
pm2 stop ppfun-scd
pm2 stop ppfun-sce
pm2 stop ppfun-scf
pm2 stop ppfun-scg
pm2 stop ppfun-sch
copy "${PFOLDER}" "${DO_REINSTALL}"
copy "${SCBFOLDER}" 1
copy "${SCCFOLDER}" 1
copy "${SCDFOLDER}" 1
copy "${SCEFOLDER}" 1
copy "${SCFFOLDER}" 1
copy "${SCGFOLDER}" 1
copy "${SCHFOLDER}" 1
cp dist/canvases.json ~/
cd "$PFOLDER"
pm2 stop ppfun-backups
pm2 stop ecosystem.config.js
copy "${PFOLDER}" "${DO_REINSTALL}"
pm2 start ecosystem-backup.yml
pm2 start ecosystem.config.js

View File

@ -6,6 +6,8 @@ Translation files can be created out of the templates [template.pot](https://git
All translated languages get an own chat channel that just people who use this language can access.
If a language code differs from the country code of a wanted flag, it can be defined in the ssr filename, like `ssr-en-gb.po` would be the english language, with the flag of Great Britain.
## With poedit
### Create new translation

View File

@ -29,6 +29,7 @@ msgid "You are muted for another ${ timeMin } minutes"
msgstr ""
#: src/core/ChatProvider.js:450
#, javascript-format
msgid "You are muted for another ${ ttl } seconds"
msgstr ""
@ -81,35 +82,35 @@ msgstr ""
msgid "Invalid url :( Please check your mail again."
msgstr ""
#: src/ssr/Globe.jsx:32
#: src/ssr/Globe.jsx:37
msgid "PixelPlanet.Fun 3DGlobe"
msgstr ""
#: src/ssr/Globe.jsx:33
#: src/ssr/Globe.jsx:38
msgid "A 3D globe of our whole map"
msgstr ""
#: src/ssr/Globe.jsx:46
#: src/ssr/Globe.jsx:51
msgid "Double click on globe to go back."
msgstr ""
#: src/ssr/Globe.jsx:47
#: src/ssr/Globe.jsx:52
msgid "Loading..."
msgstr ""
#: src/ssr/PopUp.jsx:58
#: src/ssr/PopUp.jsx:55
msgid "ppfun"
msgstr ""
#: src/ssr/PopUp.jsx:59
#: src/ssr/PopUp.jsx:56
msgid "PixelPlanet.Fun PopUp"
msgstr ""
#: src/ssr/Main.jsx:67
#: src/ssr/Main.jsx:64
msgid "PixelPlanet.Fun"
msgstr ""
#: src/ssr/Main.jsx:68
#: src/ssr/Main.jsx:65
msgid "Place color pixels on an map styled canvas with other players online"
msgstr ""
@ -219,31 +220,7 @@ msgstr ""
msgid "You are not banned"
msgstr ""
#: src/routes/api/auth/logout.js:11
msgid "You are not even logged in."
msgstr ""
#: src/routes/api/auth/delete_account.js:55
#: src/routes/api/auth/logout.js:20
msgid "Server error when logging out."
msgstr ""
#: src/routes/api/auth/verify.js:26
#: src/routes/api/auth/verify.js:35
msgid "Mail verification"
msgstr ""
#: src/routes/api/auth/verify.js:27
msgid "You are now verified :)"
msgstr ""
#: src/routes/api/auth/verify.js:35
msgid ""
"Your mail verification code is invalid or already expired :(, please "
"request a new one."
msgstr ""
#: src/routes/api/auth/change_mail.js:21
#: src/routes/api/auth/change_mail.js:22
#: src/routes/api/auth/register.js:24
msgid "This email provider is not allowed"
msgstr ""
@ -280,18 +257,50 @@ msgstr ""
msgid "Failed to establish session after register :("
msgstr ""
#: src/routes/api/auth/change_mail.js:43
#: src/routes/api/auth/verify.js:26
#: src/routes/api/auth/verify.js:35
msgid "Mail verification"
msgstr ""
#: src/routes/api/auth/verify.js:27
msgid "You are now verified :)"
msgstr ""
#: src/routes/api/auth/verify.js:35
msgid ""
"Your mail verification code is invalid or already expired :(, please "
"request a new one."
msgstr ""
#: src/routes/api/auth/logout.js:11
msgid "You are not even logged in."
msgstr ""
#: src/routes/api/auth/delete_account.js:65
#: src/routes/api/auth/logout.js:20
msgid "Server error when logging out."
msgstr ""
#: src/routes/api/auth/change_mail.js:44
#: src/routes/api/auth/change_passwd.js:34
#: src/routes/api/auth/delete_account.js:35
#: src/routes/api/auth/delete_account.js:36
msgid "You are not authenticated."
msgstr ""
#: src/routes/api/auth/change_mail.js:52
#: src/routes/api/auth/change_mail.js:53
#: src/routes/api/auth/change_passwd.js:43
#: src/routes/api/auth/delete_account.js:45
#: src/routes/api/auth/delete_account.js:55
msgid "Incorrect password!"
msgstr ""
#: src/routes/api/auth/delete_account.js:46
msgid "Muted users can not delete their account."
msgstr ""
#: src/routes/api/auth/change_mail.js:62
msgid "Muted users can not do this."
msgstr ""
#: src/ssr/RedirectionPage.jsx:19
msgid "PixelPlanet.fun Accounts"
msgstr ""
@ -337,48 +346,59 @@ msgstr ""
msgid "Top10"
msgstr ""
#: src/canvasesDesc.js:29
msgid "Our main canvas, a huge map of the world. Place everywhere you like"
#: src/canvasesDesc.js:27
msgid "Thoia"
msgstr ""
#: src/canvasesDesc.js:30
msgid "Our main canvas, a huge map of the world. Place everywhere you like"
msgstr ""
#: src/canvasesDesc.js:31
msgid ""
"Moon canvas. Safe space for art. No flags or large text (unless part of "
"art) or art larger than 1.5k x 1.5k pixels."
msgstr ""
#: src/canvasesDesc.js:31
#: src/canvasesDesc.js:32
msgid "Place Voxels on a 3D canvas with others"
msgstr ""
#: src/canvasesDesc.js:32
#: src/canvasesDesc.js:33
msgid "Special canvas to spread awareness of SARS-CoV2"
msgstr ""
#: src/canvasesDesc.js:33
#: src/canvasesDesc.js:34
msgid "Mirror of PixelZone"
msgstr ""
#: src/canvasesDesc.js:34
#: src/canvasesDesc.js:35
msgid "Mirror of PixelCanvas"
msgstr ""
#: src/canvasesDesc.js:35
#: src/canvasesDesc.js:36
msgid "Black and White canvas"
msgstr ""
#: src/canvasesDesc.js:36
#: src/canvasesDesc.js:37
msgid ""
"A canvas for the most active players from the the previous day. Daily "
"ranking updates at 00:00 UTC."
msgstr ""
#: src/canvasesDesc.js:38
msgid ""
"Thoia World Canvas. Advanced fictional worldbuilding and arts. Abandon the "
"old world and all it entails."
msgstr ""
#: src/core/MailProvider.js:66
#, javascript-format
msgid "Welcome ${ name } to PixelPlanet, please verify your mail"
msgstr ""
#: src/core/MailProvider.js:67
#, javascript-format
msgid "Hello ${ name }"
msgstr ""
@ -446,4 +466,4 @@ msgstr ""
#: src/core/MailProvider.js:130
msgid "Couldn't find this mail in our database"
msgstr ""
msgstr ""

View File

@ -194,6 +194,7 @@ msgid "try again after ${ ti }min"
msgstr ""
#: src/store/actions/fetch.js:70
#, javascript-format
msgid "Connection error ${ code } :("
msgstr ""
@ -230,12 +231,6 @@ msgstr ""
msgid "You have new messages in chat"
msgstr ""
#: src/components/Converter.jsx:561
#: src/components/CoordinatesBox.jsx:31
#: src/components/ModWatchtools.jsx:371
msgid "Copy to Clipboard"
msgstr ""
#: src/components/OnlineBox.jsx:40
msgid "Online Users on Canvas"
msgstr ""
@ -248,6 +243,13 @@ msgstr ""
msgid "Pixels placed"
msgstr ""
#: src/components/Converter.jsx:565
#: src/components/CoordinatesBox.jsx:31
#: src/components/ModWatchtools.jsx:378
#: src/components/ModWatchtools.jsx:396
msgid "Copy to Clipboard"
msgstr ""
#: src/components/buttons/CanvasSwitchButton.jsx:20
#: src/components/windows/index.js:19
msgid "Canvas Selection"
@ -279,7 +281,7 @@ msgstr ""
#: src/components/Admintools.jsx:103
#: src/components/ModCanvastools.jsx:222
#: src/components/ModWatchtools.jsx:118
#: src/components/ModWatchtools.jsx:120
#: src/components/Window.jsx:157
#: src/components/Window.jsx:260
#: src/components/contextmenus/ChannelContextMenu.jsx:59
@ -310,6 +312,26 @@ msgstr ""
msgid "Resize"
msgstr ""
#: src/components/BanInfo.jsx:75
#: src/components/buttons/HelpButton.jsx:20
#: src/components/windows/index.js:13
msgid "Help"
msgstr ""
#: src/components/buttons/SettingsButton.jsx:21
#: src/components/windows/index.js:14
msgid "Settings"
msgstr ""
#: src/components/buttons/LogInButton.jsx:20
#: src/components/windows/index.js:15
msgid "User Area"
msgstr ""
#: src/components/buttons/DownloadButton.jsx:36
msgid "Make Screenshot"
msgstr ""
#: src/components/buttons/GlobeButton.jsx:34
msgid "Globe View"
msgstr ""
@ -322,26 +344,6 @@ msgstr ""
msgid "Open Palette"
msgstr ""
#: src/components/buttons/SettingsButton.jsx:21
#: src/components/windows/index.js:14
msgid "Settings"
msgstr ""
#: src/components/BanInfo.jsx:75
#: src/components/buttons/HelpButton.jsx:20
#: src/components/windows/index.js:13
msgid "Help"
msgstr ""
#: src/components/buttons/LogInButton.jsx:20
#: src/components/windows/index.js:15
msgid "User Area"
msgstr ""
#: src/components/buttons/DownloadButton.jsx:36
msgid "Make Screenshot"
msgstr ""
#: src/components/windows/index.js:16
msgid "Registration"
msgstr ""
@ -447,99 +449,6 @@ msgstr ""
msgid "Why?"
msgstr ""
#: src/components/windows/Settings.jsx:86
msgid "Show Grid"
msgstr ""
#: src/components/windows/Settings.jsx:91
msgid "Turn on grid to highlight pixel borders."
msgstr ""
#: src/components/windows/Settings.jsx:94
msgid "Show Pixel Activity"
msgstr ""
#: src/components/windows/Settings.jsx:99
msgid "Show circles where pixels are placed."
msgstr ""
#: src/components/windows/Settings.jsx:102
msgid "Disable Game Sounds"
msgstr ""
#: src/components/windows/Settings.jsx:108
msgid "All sound effects will be disabled."
msgstr ""
#: src/components/windows/Settings.jsx:112
msgid ""
"Your Browser doesn't allow us to use AudioContext to play sounds. Do you "
"have some privacy feature blocking us?"
msgstr ""
#: src/components/windows/Settings.jsx:118
msgid "Enable chat notifications"
msgstr ""
#: src/components/windows/Settings.jsx:122
msgid "Play a sound when new chat messages arrive"
msgstr ""
#: src/components/windows/Settings.jsx:125
msgid "Auto Zoom In"
msgstr ""
#: src/components/windows/Settings.jsx:130
msgid ""
"Zoom in instead of placing a pixel when you tap the canvas and your zoom is "
"small."
msgstr ""
#: src/components/windows/Settings.jsx:133
msgid "Compact Palette"
msgstr ""
#: src/components/windows/Settings.jsx:138
msgid "Display Palette in a compact form that takes less screen space."
msgstr ""
#: src/components/windows/Settings.jsx:141
msgid "Potato Mode"
msgstr ""
#: src/components/windows/Settings.jsx:145
msgid "For when you are playing on a potato."
msgstr ""
#: src/components/Converter.jsx:376
#: src/components/windows/Settings.jsx:148
msgid "Light Grid"
msgstr ""
#: src/components/windows/Settings.jsx:152
msgid "Show Grid in white instead of black."
msgstr ""
#: src/components/windows/Settings.jsx:156
msgid "Historical View"
msgstr ""
#: src/components/windows/Settings.jsx:161
msgid "Check out past versions of the canvas."
msgstr ""
#: src/components/windows/Settings.jsx:166
msgid "Themes"
msgstr ""
#: src/components/windows/Settings.jsx:171
msgid "How pixelplanet should look like."
msgstr ""
#: src/components/windows/Settings.jsx:178
msgid "Select Language"
msgstr ""
#: src/components/windows/Help.jsx:42
msgid "Place color pixels on a large canvas with other players online!"
msgstr ""
@ -609,6 +518,7 @@ msgid "Press ${ bindG } to toggle grid"
msgstr ""
#: src/components/windows/Help.jsx:64
#, javascript-format
msgid "Press ${ bindX } to toggle showing of pixel activity"
msgstr ""
@ -618,6 +528,7 @@ msgid "Press ${ bindH } to toggle historical view"
msgstr ""
#: src/components/windows/Help.jsx:66
#, javascript-format
msgid "Press ${ bindR } to copy coordinates"
msgstr ""
@ -639,6 +550,7 @@ msgid "Press ${ bindAUp }, ${ bindALeft }, ${ bindADown }, ${ bindARight } to mo
msgstr ""
#: src/components/windows/Help.jsx:70
#, javascript-format
msgid "Drag ${ mouseSymbol } mouse or ${ touchSymbol } pan to move"
msgstr ""
@ -648,6 +560,7 @@ msgid "Scroll ${ mouseSymbol } mouse wheel or ${ touchSymbol } pinch to zoom"
msgstr ""
#: src/components/windows/Help.jsx:72
#, javascript-format
msgid "Hold left ${ bindShift } for placing while moving mouse"
msgstr ""
@ -666,12 +579,14 @@ msgstr ""
#: src/components/windows/Help.jsx:75
#: src/components/windows/Help.jsx:87
#, javascript-format
msgid ""
"Click ${ mouseSymbol } middle mouse button or ${ touchSymbol } long-tap to "
"select current hovering color"
msgstr ""
#: src/components/windows/Help.jsx:81
#, javascript-format
msgid "Press ${ bindE } and ${ bindC } to fly up and down"
msgstr ""
@ -688,6 +603,7 @@ msgid ""
msgstr ""
#: src/components/windows/Help.jsx:84
#, javascript-format
msgid "${ mouseSymbol } Right click and drag mouse to pan"
msgstr ""
@ -710,9 +626,103 @@ msgid "Credit for the Palette of the Moon goes to ${ starhouseLink }."
msgstr ""
#: src/components/windows/Help.jsx:97
#, javascript-format
msgid "Credit for the Palette of the Top10 canvas goes to ${ vinikLink }."
msgstr ""
#: src/components/windows/Settings.jsx:86
msgid "Show Grid"
msgstr ""
#: src/components/windows/Settings.jsx:91
msgid "Turn on grid to highlight pixel borders."
msgstr ""
#: src/components/windows/Settings.jsx:94
msgid "Show Pixel Activity"
msgstr ""
#: src/components/windows/Settings.jsx:99
msgid "Show circles where pixels are placed."
msgstr ""
#: src/components/windows/Settings.jsx:102
msgid "Disable Game Sounds"
msgstr ""
#: src/components/windows/Settings.jsx:108
msgid "All sound effects will be disabled."
msgstr ""
#: src/components/windows/Settings.jsx:112
msgid ""
"Your Browser doesn't allow us to use AudioContext to play sounds. Do you "
"have some privacy feature blocking us?"
msgstr ""
#: src/components/windows/Settings.jsx:118
msgid "Enable chat notifications"
msgstr ""
#: src/components/windows/Settings.jsx:122
msgid "Play a sound when new chat messages arrive"
msgstr ""
#: src/components/windows/Settings.jsx:125
msgid "Auto Zoom In"
msgstr ""
#: src/components/windows/Settings.jsx:130
msgid ""
"Zoom in instead of placing a pixel when you tap the canvas and your zoom is "
"small."
msgstr ""
#: src/components/windows/Settings.jsx:133
msgid "Compact Palette"
msgstr ""
#: src/components/windows/Settings.jsx:138
msgid "Display Palette in a compact form that takes less screen space."
msgstr ""
#: src/components/windows/Settings.jsx:141
msgid "Potato Mode"
msgstr ""
#: src/components/windows/Settings.jsx:145
msgid "For when you are playing on a potato."
msgstr ""
#: src/components/Converter.jsx:380
#: src/components/windows/Settings.jsx:148
msgid "Light Grid"
msgstr ""
#: src/components/windows/Settings.jsx:152
msgid "Show Grid in white instead of black."
msgstr ""
#: src/components/windows/Settings.jsx:156
msgid "Historical View"
msgstr ""
#: src/components/windows/Settings.jsx:161
msgid "Check out past versions of the canvas."
msgstr ""
#: src/components/windows/Settings.jsx:166
msgid "Themes"
msgstr ""
#: src/components/windows/Settings.jsx:171
msgid "How pixelplanet should look like."
msgstr ""
#: src/components/windows/Settings.jsx:178
msgid "Select Language"
msgstr ""
#: src/components/windows/UserArea.jsx:36
#: src/components/windows/UserArea.jsx:56
msgid "Profile"
@ -826,14 +836,6 @@ msgid ""
"how the canvas was at that time."
msgstr ""
#: src/components/windows/ForgotPassword.jsx:58
msgid "Sent you a mail with instructions to reset your password."
msgstr ""
#: src/components/windows/ForgotPassword.jsx:69
msgid "Enter your mail address and we will send you a new password:"
msgstr ""
#: src/components/windows/Chat.jsx:180
msgid "Start chatting here"
msgstr ""
@ -850,6 +852,14 @@ msgstr ""
msgid "Channel settings"
msgstr ""
#: src/components/windows/ForgotPassword.jsx:58
msgid "Sent you a mail with instructions to reset your password."
msgstr ""
#: src/components/windows/ForgotPassword.jsx:69
msgid "Enter your mail address and we will send you a new password:"
msgstr ""
#: src/components/Captcha.jsx:51
#: src/components/Captcha.jsx:105
msgid "Could not load captcha"
@ -931,13 +941,6 @@ msgstr ""
msgid "Password must be shorter than 60 characters."
msgstr ""
#: src/components/ChangeMail.jsx:91
#: src/components/ChangeName.jsx:68
#: src/components/ChangePassword.jsx:109
#: src/components/LanguageSelect.jsx:80
msgid "Save"
msgstr ""
#: src/components/GetIID.jsx:44
msgid "Get IID"
msgstr ""
@ -946,6 +949,13 @@ msgstr ""
msgid "Copy"
msgstr ""
#: src/components/ChangeMail.jsx:91
#: src/components/ChangeName.jsx:68
#: src/components/ChangePassword.jsx:109
#: src/components/LanguageSelect.jsx:80
msgid "Save"
msgstr ""
#: src/components/LogInArea.jsx:19
msgid "Login to access more features and stats."
msgstr ""
@ -1020,77 +1030,77 @@ msgstr ""
msgid "Choose Canvas"
msgstr ""
#: src/components/Converter.jsx:216
#: src/components/Converter.jsx:217
msgid "Palette Download"
msgstr ""
#: src/components/Converter.jsx:218
#: src/components/Converter.jsx:219
#, javascript-format
msgid "Palette for ${ gimpLink }"
msgstr ""
#: src/components/Converter.jsx:236
#: src/components/Converter.jsx:240
msgid "Image Converter"
msgstr ""
#: src/components/Converter.jsx:237
#: src/components/Converter.jsx:241
msgid "Convert an image to canvas colors"
msgstr ""
#: src/components/Converter.jsx:256
#: src/components/Converter.jsx:260
msgid "Choose Strategy"
msgstr ""
#: src/components/Converter.jsx:294
#: src/components/Converter.jsx:298
msgid "Serpentine"
msgstr ""
#: src/components/Converter.jsx:296
#: src/components/Converter.jsx:300
msgid "Minimum Color Distance"
msgstr ""
#: src/components/Converter.jsx:323
#: src/components/Converter.jsx:327
msgid "Calculate like GIMP"
msgstr ""
#: src/components/Converter.jsx:327
#: src/components/Converter.jsx:331
msgid "Choose Color Mode"
msgstr ""
#: src/components/Converter.jsx:356
#: src/components/Converter.jsx:360
msgid "Add Grid (uncheck if you need a 1:1 template)"
msgstr ""
#: src/components/Converter.jsx:378
#: src/components/Converter.jsx:394
#: src/components/Converter.jsx:382
#: src/components/Converter.jsx:398
msgid "Offset"
msgstr ""
#: src/components/Converter.jsx:423
#: src/components/Converter.jsx:427
msgid "Scale Image"
msgstr ""
#: src/components/Converter.jsx:434
#: src/components/Converter.jsx:438
msgid "Width"
msgstr ""
#: src/components/Converter.jsx:465
#: src/components/Converter.jsx:469
msgid "Height"
msgstr ""
#: src/components/Converter.jsx:504
#: src/components/Converter.jsx:508
msgid "Keep Ratio"
msgstr ""
#: src/components/Converter.jsx:517
#: src/components/Converter.jsx:521
msgid "Anti Aliasing"
msgstr ""
#: src/components/Converter.jsx:531
#: src/components/Converter.jsx:535
msgid "Reset"
msgstr ""
#: src/components/Converter.jsx:549
#: src/components/Converter.jsx:553
msgid "Download Template"
msgstr ""
@ -1189,7 +1199,7 @@ msgstr ""
msgid "Countries by Pixels Today"
msgstr ""
#: src/core/chartSettings.js:352
#: src/core/chartSettings.js:351
msgid "Total Pixels placed per day"
msgstr ""
@ -1215,10 +1225,6 @@ msgstr ""
msgid "Click here to request a new verification mail."
msgstr ""
#: src/components/ChangeName.jsx:64
msgid "New Username"
msgstr ""
#: src/components/ChangePassword.jsx:21
msgid "Passwords do not match."
msgstr ""
@ -1239,6 +1245,10 @@ msgstr ""
msgid "Confirm New Password"
msgstr ""
#: src/components/ChangeName.jsx:64
msgid "New Username"
msgstr ""
#: src/components/ChangeMail.jsx:59
msgid ""
"Changed Mail successfully. We sent you a verification mail, "
@ -1339,14 +1349,14 @@ msgstr ""
#: src/components/ModCanvastools.jsx:330
#: src/components/ModCanvastools.jsx:403
#: src/components/ModCanvastools.jsx:486
#: src/components/ModWatchtools.jsx:174
#: src/components/ModWatchtools.jsx:176
msgid "Top-left corner"
msgstr ""
#: src/components/ModCanvastools.jsx:347
#: src/components/ModCanvastools.jsx:420
#: src/components/ModCanvastools.jsx:503
#: src/components/ModWatchtools.jsx:191
#: src/components/ModWatchtools.jsx:193
msgid "Bottom-right corner"
msgstr ""
@ -1370,54 +1380,6 @@ msgstr ""
msgid "Stop Cleaner"
msgstr ""
#: src/components/ModWatchtools.jsx:48
msgid "Interval is invalid"
msgstr ""
#: src/components/ModWatchtools.jsx:122
msgid "Check who placed in an area"
msgstr ""
#: src/components/ModWatchtools.jsx:123
msgid "Canvas"
msgstr ""
#: src/components/ModWatchtools.jsx:142
msgid "Interval"
msgstr ""
#: src/components/ModWatchtools.jsx:157
msgid "IID (optional)"
msgstr ""
#: src/components/ModWatchtools.jsx:236
msgid "Get Pixels"
msgstr ""
#: src/components/ModWatchtools.jsx:267
msgid "Get Users"
msgstr ""
#: src/components/ModIIDtools.jsx:20
msgid "You must enter a duration"
msgstr ""
#: src/components/ModIIDtools.jsx:24
msgid "You must enter an IID"
msgstr ""
#: src/components/ModIIDtools.jsx:53
msgid "IID Actions"
msgstr ""
#: src/components/ModIIDtools.jsx:80
msgid "Enter Reason"
msgstr ""
#: src/components/ModIIDtools.jsx:97
msgid "(0 = infinite)"
msgstr ""
#: src/components/Admintools.jsx:109
msgid "IP Actions"
msgstr ""
@ -1450,8 +1412,52 @@ msgstr ""
msgid "User Name"
msgstr ""
#: src/components/contextmenus/ChannelContextMenu.jsx:46
msgid "Mute"
#: src/components/ModWatchtools.jsx:48
msgid "Interval is invalid"
msgstr ""
#: src/components/ModWatchtools.jsx:124
msgid "Check who placed in an area"
msgstr ""
#: src/components/ModWatchtools.jsx:125
msgid "Canvas"
msgstr ""
#: src/components/ModWatchtools.jsx:144
msgid "Interval"
msgstr ""
#: src/components/ModWatchtools.jsx:159
msgid "IID (optional)"
msgstr ""
#: src/components/ModWatchtools.jsx:239
msgid "Get Pixels"
msgstr ""
#: src/components/ModWatchtools.jsx:271
msgid "Get Users"
msgstr ""
#: src/components/ModIIDtools.jsx:20
msgid "You must enter a duration"
msgstr ""
#: src/components/ModIIDtools.jsx:24
msgid "You must enter an IID"
msgstr ""
#: src/components/ModIIDtools.jsx:53
msgid "IID Actions"
msgstr ""
#: src/components/ModIIDtools.jsx:80
msgid "Enter Reason"
msgstr ""
#: src/components/ModIIDtools.jsx:97
msgid "(0 = infinite)"
msgstr ""
#: src/components/contextmenus/UserContextMenu.jsx:49
@ -1466,6 +1472,10 @@ msgstr ""
msgid "Block"
msgstr ""
#: src/components/contextmenus/ChannelContextMenu.jsx:46
msgid "Mute"
msgstr ""
#: src/components/windows/Help.jsx:15
#: src/components/windows/Settings.jsx:87
msgctxt "keybinds"
@ -1478,11 +1488,6 @@ msgctxt "keybinds"
msgid "X"
msgstr ""
#: src/components/windows/Settings.jsx:103
msgctxt "keybinds"
msgid "M"
msgstr ""
#: src/components/windows/Help.jsx:17
#: src/components/windows/Settings.jsx:158
msgctxt "keybinds"
@ -1532,4 +1537,9 @@ msgstr ""
#: src/components/windows/Help.jsx:32
msgctxt "keybinds"
msgid "C"
msgstr ""
msgstr ""
#: src/components/windows/Settings.jsx:103
msgctxt "keybinds"
msgid "M"
msgstr ""

7649
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -8,8 +8,11 @@
"description": "Unlimited planet canvas for placing pixels",
"main": "server.js",
"scripts": {
"build": "node scripts/build.js && npm run minify-css",
"build:dev": "webpack --env extract --config ./webpack.config.server.js && webpack --env extract --env development --config ./webpack.config.client.js && npm run minify-css",
"build": "node scripts/build.js",
"build:server": "webpack --config ./webpack.config.server.js",
"build:client": "node scripts/build.js --client",
"build:dev": "webpack --config ./webpack.config.server.js && webpack --env development --config ./webpack.config.client.js && npm run minify-css",
"update-browserlist": "browserslist --update-db",
"deploy": "ssh pixelplanet /home/pixelpla/rebuild.sh",
"minify-css": "node scripts/minifyCss.js",
"babel-node": "babel-node",
@ -23,11 +26,11 @@
"not IE_Mob 11"
],
"dependencies": {
"bcrypt": "^5.0.1",
"bcrypt": "^5.1.1",
"chart.js": "^3.9.1",
"compression": "^1.7.3",
"cookie": "^0.5.0",
"core-js": "^3.23.4",
"core-js": "^3.34.0",
"etag": "^1.8.1",
"express": "^4.17.2",
"express-session": "^1.17.2",
@ -36,7 +39,7 @@
"morgan": "^1.10.0",
"multer": "^1.4.5-lts.1",
"mysql2": "^2.3.3",
"nodemailer": "^6.7.8",
"nodemailer": "^6.9.7",
"passport": "^0.6.0",
"passport-discord": "^0.1.4",
"passport-facebook": "^3.0.0",
@ -48,57 +51,56 @@
"react": "^18.2.0",
"react-chartjs-2": "^4.3.1",
"react-dom": "^18.2.0",
"react-icons": "^4.3.1",
"react-icons": "^4.12.0",
"react-redux": "^8.0.2",
"react-stay-scrolled": "^8.0.0",
"react-toggle": "^4.1.3",
"redis": "^4.3.1",
"redis": "^4.6.11",
"redux": "^4.1.2",
"redux-persist": "^6.0.0",
"redux-thunk": "^2.4.1",
"reselect": "^4.1.6",
"sequelize": "^6.21.6",
"sequelize": "^6.35.2",
"sharp": "^0.31.0",
"startaudiocontext": "^1.2.1",
"three": "^0.143.0",
"three-trackballcontrols": "^0.9.0",
"ttag": "^1.7.24",
"url-search-params-polyfill": "^8.1.1",
"winston": "^3.8.2",
"ttag": "^1.8.3",
"url-search-params-polyfill": "^8.2.5",
"winston": "^3.11.0",
"winston-daily-rotate-file": "^4.5.5",
"ws": "^8.4.0"
"ws": "^8.15.1"
},
"devDependencies": {
"@babel/cli": "^7.21.0",
"@babel/core": "^7.21.0",
"@babel/eslint-parser": "^7.19.1",
"@babel/node": "^7.20.7",
"@babel/plugin-transform-react-constant-elements": "^7.20.2",
"@babel/plugin-transform-react-inline-elements": "^7.21.0",
"@babel/preset-env": "^7.20.2",
"@babel/preset-react": "^7.18.6",
"assets-webpack-plugin": "^7.1.1",
"babel-loader": "^8.2.3",
"@babel/cli": "^7.23.4",
"@babel/core": "^7.23.6",
"@babel/eslint-parser": "^7.23.3",
"@babel/node": "^7.22.19",
"@babel/plugin-transform-react-constant-elements": "^7.23.3",
"@babel/plugin-transform-react-inline-elements": "^7.23.3",
"@babel/preset-env": "^7.23.6",
"@babel/preset-react": "^7.23.3",
"babel-loader": "^9.1.3",
"babel-plugin-transform-react-pure-class-to-function": "^1.0.1",
"babel-plugin-transform-react-remove-prop-types": "^0.4.24",
"babel-plugin-ttag": "^1.8.5",
"clean-css": "^5.2.2",
"clean-css-loader": "^4.1.1",
"clean-css-loader": "^4.2.1",
"copy-webpack-plugin": "^11.0.0",
"css-loader": "^6.5.1",
"eslint": "^8.36.0",
"css-loader": "^6.8.1",
"eslint": "^8.55.0",
"eslint-config-airbnb": "^19.0.4",
"eslint-config-airbnb-base": "^15.0.0",
"eslint-plugin-import": "^2.27.5",
"eslint-plugin-jsx-a11y": "^6.7.1",
"eslint-plugin-react": "^7.32.2",
"eslint-plugin-import": "^2.29.0",
"eslint-plugin-jsx-a11y": "^6.8.0",
"eslint-plugin-react": "^7.33.2",
"eslint-plugin-react-hooks": "^4.6.0",
"generate-package-json-webpack-plugin": "^2.6.0",
"ttag-cli": "^1.9.3",
"ttag-cli": "^1.10.9",
"ttag-po-loader": "0.0.2",
"webpack": "^5.67.0",
"webpack-bundle-analyzer": "^4.5.0",
"webpack-cli": "^4.9.2",
"webpack": "^5.89.0",
"webpack-bundle-analyzer": "^4.10.1",
"webpack-cli": "^5.1.4",
"webpack-node-externals": "^3.0.0"
},
"optionalDependencies": {

View File

@ -0,0 +1,20 @@
/*
* webpack loader that
* marks modules that include ttag as non-cachable
*/
const filtered = {};
module.exports = function (source) {
if (filtered.hasOwnProperty(this.resourcePath)) {
if (filtered[this.resourcePath]) {
this.cacheable(false);
}
return source;
}
const hasTtag = source.slice(0, 400).includes('ttag');
filtered[this.resourcePath] = hasTtag;
if (hasTtag) {
this.cacheable(false);
}
return source;
}

View File

@ -3,14 +3,52 @@
* Lets split that here
*/
const path = require('path');
const fs = require('fs');
const { spawn } = require('child_process');
const webpack = require('webpack');
const minifyCss = require('./minifyCss');
const serverConfig = require('../webpack.config.server.js');
const clientConfig = require('../webpack.config.client.js');
const { getAllAvailableLocals } = clientConfig;
let langs = 'all';
let doBuildServer = false;
let doBuildClient = false;
let parallel = false;
let recursion = false;
for (let i = 0; i < process.argv.length; i += 1) {
switch (process.argv[i]) {
case '--langs': {
const newLangs = process.argv[++i];
if (newLangs) langs = newLangs;
break;
}
case '--client':
doBuildClient = true;
break;
case `--server`:
doBuildServer = true;
break;
case '--parallel':
parallel = true;
break;
case '--recursion':
recursion = true;
break;
default:
// nothing
}
}
if (!doBuildServer && !doBuildClient) {
doBuildServer = true;
doBuildClient = true;
}
function compile(webpackConfig) {
return new Promise((resolve, reject) => {
webpack(webpackConfig).run((err, stats) => {
webpack(webpackConfig, (err, stats) => {
if (err) {
return reject(err);
}
@ -21,31 +59,146 @@ function compile(webpackConfig) {
});
}
async function buildProduction() {
// server files
function buildServer() {
console.log('-----------------------------');
console.log(`Build server...`);
console.log('-----------------------------');
await compile(serverConfig({
development: false,
extract: false,
}));
// client files
const langs = getAllAvailableLocals();
console.log('Available locales:', langs);
const st = Date.now();
for(let i = 0; i < langs.length; i += 1) {
const lang = langs[i];
const ts = Date.now();
return new Promise((resolve, reject) => {
const argsc = (langs === 'all')
? ['webpack', '--env', 'extract', '--config', './webpack.config.server.js']
: ['webpack', '--config', './webpack.config.server.js']
const serverCompile = spawn('npx', argsc);
serverCompile.stdout.on('data', (data) => {
console.log(data.toString());
});
serverCompile.stderr.on('data', (data) => {
console.error(data.toString());
});
serverCompile.on('close', (code) => {
if (code) {
reject(new Error('Server compilation failed!'));
} else {
console.log('---------------------------------------');
console.log(`Server Compilation finished in ${Math.floor((Date.now() - ts) / 1000)}s`);
console.log('---------------------------------------');
resolve();
}
});
});
}
function buildClients(slangs) {
return new Promise((resolve, reject) => {
const clientCompile = spawn('npm', ['run', 'build', '--', '--client', '--recursion', '--langs', slangs.join(',')]);
clientCompile.stdout.on('data', (data) => {
console.log(data.toString());
});
clientCompile.stderr.on('data', (data) => {
console.error(data.toString());
});
clientCompile.on('close', (code) => {
if (code) {
reject(new Error('Client compilation failed!'));
} else {
resolve();
}
});
});
}
async function buildClientsSync(avlangs) {
for(let i = 0; i < avlangs.length; i += 1) {
const lang = avlangs[i];
console.log(`Build client for locale ${lang}...`);
console.log('-----------------------------');
await compile(clientConfig({
development: false,
analyze: false,
extract: false,
locale: lang,
clean: false,
readonly: recursion,
}));
}
console.log(`Finished building in ${(Date.now() - st) / 1000}s`);
}
function buildClientsParallel(avlangs) {
const st = Date.now();
const numProc = 3;
let nump = Math.floor(avlangs.length / numProc);
if (!nump) nump = 1;
const promises = [];
while (avlangs.length >= nump) {
const slangs = avlangs.splice(0, nump);
promises.push(buildClients(slangs));
}
if (avlangs.length) {
promises.push(buildClientsSync(avlangs));
}
return Promise.all(promises);
}
async function buildProduction() {
const st = Date.now();
// cleanup old files
if (!recursion) {
fs.rmSync(path.resolve(__dirname, '..', 'node_modules', '.cache', 'webpack'), { recursive: true, force: true });
}
// decide which languages to build
let avlangs = getAllAvailableLocals();
if (langs !== 'all') {
avlangs = langs.split(',').map((l) => l.trim())
.filter((l) => avlangs.includes(l));
if (!avlangs.length) {
console.error(`ERROR: language ${langs} not available`);
process.exit(1);
return;
}
}
console.log('Building locales:', avlangs);
const promises = [];
if (doBuildServer) {
promises.push(buildServer());
}
if (doBuildClient) {
if (!recursion) {
console.log(
'Building one package seperately to populate cache and possibly extract langs...',
);
await compile(clientConfig({
development: false,
analyze: false,
extract: (langs === 'all'),
locale: avlangs.shift(),
clean: true,
readonly: false,
}));
console.log('-----------------------------');
console.log(`Minify CSS assets...`);
console.log('-----------------------------');
await minifyCss();
}
if (parallel) {
promises.push(buildClientsParallel(avlangs));
} else {
promises.push(buildClientsSync(avlangs));
}
}
await Promise.all(promises);
if (!recursion) {
console.log(`Finished building in ${(Date.now() - st) / 1000}s`);
} else {
console.log(`Worker done in ${(Date.now() - st) / 1000}s`);
}
}
buildProduction();

View File

@ -14,6 +14,7 @@ const path = require('path');
const CleanCSS = require('clean-css');
const crypto = require('crypto');
const buildTs = Date.now();
const assetdir = path.resolve(__dirname, '..', 'dist', 'public', 'assets');
const builddir = path.resolve(__dirname, '..', 'dist');
@ -23,7 +24,6 @@ FILES.push('default.css');
async function minifyCss() {
console.log('Minifying css');
const assets = {};
FILES.forEach((file) => {
const input = fs.readFileSync(path.resolve(FOLDER, file), 'utf8');
const options = {};
@ -48,10 +48,7 @@ async function minifyCss() {
}
const filename = `${key}.${hash.substr(0, 8)}.css`;
fs.writeFileSync(path.resolve(assetdir, filename), output.styles, 'utf8');
assets[key] = `/assets/${filename}`;
});
const json = JSON.stringify(assets);
fs.writeFileSync(path.resolve(builddir, 'styleassets.json'), json);
}
async function doMinifyCss() {

View File

@ -4,6 +4,7 @@
*
*/
import assetWatcher from './core/fsWatcher';
import canvases from './core/canvases';
import ttag from './core/ttag';
@ -58,16 +59,21 @@ function getCanvases(t) {
return localizedCanvases;
}
const lCanvases = {};
(() => {
function translateCanvases() {
const parsedCanvases = {};
const langs = Object.keys(ttag);
langs.forEach((lang) => {
lCanvases[lang] = getCanvases(ttag[lang].t);
parsedCanvases[lang] = getCanvases(ttag[lang].t);
});
})();
export function getLocalizedCanvases(lang) {
return lCanvases[lang] || lCanvases.default;
return parsedCanvases;
}
export default lCanvases;
let lCanvases = translateCanvases();
// reload on asset change
assetWatcher.onChange(() => {
lCanvases = translateCanvases();
});
export default function getLocalizedCanvases(lang = 'en') {
return lCanvases[lang] || lCanvases.en;
}

View File

@ -66,14 +66,14 @@ function LanguageSelect() {
/* set with selected language */
const d = new Date();
d.setTime(d.getTime() + 24 * MONTH);
let { host } = window.location;
if (host.lastIndexOf('.') !== host.indexOf('.')) {
host = host.slice(host.indexOf('.'));
let { hostname } = window.location;
if (hostname.lastIndexOf('.') !== hostname.indexOf('.')) {
hostname = hostname.slice(hostname.indexOf('.'));
} else {
host = `.${host}`;
hostname = `.${hostname}`;
}
// eslint-disable-next-line max-len
document.cookie = `plang=${langSel};expires=${d.toUTCString()};path=/;domain=${host}`;
document.cookie = `plang=${langSel};expires=${d.toUTCString()};path=/;domain=${hostname}`;
window.location.reload();
}}
>

View File

@ -192,7 +192,7 @@ export class ChatProvider {
getDefaultChannels(lang) {
const langChannel = {};
if (lang && lang !== 'default') {
if (lang && lang !== 'en') {
const { langChannels } = this;
if (langChannels[lang]) {
const {

View File

@ -68,7 +68,7 @@ export class MailProvider {
${t`welcome to our little community of pixelplacers, to use your account, you have to verify your mail. You can do that here: `} <a href="${verifyUrl}">${t`Click to Verify`}</a>. ${t`Or by copying following url:`}<br />${verifyUrl}\n<br />
${t`Have fun and don't hesitate to contact us if you encounter any problems :)`}<br />
${t`Thanks`}<br /><br />
<img alt="" src="https://assets.pixelplanet.fun/tile.png" style="height:64px; width:64px" />`;
<img alt="" src="https://pixelplanet.fun/tile.png" style="height:64px; width:64px" />`;
this.sendMail(to, subject, html);
}
@ -104,7 +104,7 @@ export class MailProvider {
const html = `<em>${t`Hello`}</em>,<br />
${t`You requested to get a new password. You can change your password within the next 30min here: `} <a href="${restoreUrl}">${t`Reset Password`}</a>. ${t`Or by copying following url:`}<br />${restoreUrl}\n<br />
${t`If you did not request this mail, please just ignore it (the ip that requested this mail was ${ip}).`}<br />
${t`Thanks`}<br /><br />\n<img alt="" src="https://assets.pixelplanet.fun/tile.png" style="height:64px; width:64px" />`;
${t`Thanks`}<br /><br />\n<img alt="" src="https://pixelplanet.fun/tile.png" style="height:64px; width:64px" />`;
this.sendMail(to, subject, html);
}

View File

@ -268,14 +268,18 @@ export async function executeImageAction(
* register responses on socket for Watch Actions
*/
socketEvents.onReq('watch', (action, ...args) => {
if (action === 'getIIDSummary') {
return getIIDSummary(...args);
} if (action === 'getIIDPixels') {
return getIIDPixels(...args);
} if (action === 'getSummaryFromArea') {
return getSummaryFromArea(...args);
} if (action === 'getPixelsFromArea') {
return getPixelsFromArea(...args);
try {
if (action === 'getIIDSummary') {
return getIIDSummary(...args);
} if (action === 'getIIDPixels') {
return getIIDPixels(...args);
} if (action === 'getSummaryFromArea') {
return getSummaryFromArea(...args);
} if (action === 'getPixelsFromArea') {
return getPixelsFromArea(...args);
}
} catch {
// silently fail when file couldn't be parsed
}
return null;
});

View File

@ -1,9 +1,133 @@
import path from 'path';
import { readFileSync } from 'fs';
/*
* Provide css and js asset files for client
*/
export const assets = JSON.parse(readFileSync(
path.resolve(__dirname, './assets.json'),
));
export const styleassets = JSON.parse(readFileSync(
path.resolve(__dirname, './styleassets.json'),
));
import fs from 'fs';
import path from 'path';
import assetWatcher from './fsWatcher';
import { ASSET_DIR } from './config';
const assetDir = path.join(__dirname, 'public', ASSET_DIR);
/*
* {
* js:
* client:
* en: "/assets/client.defult.134234.js",
* de: "/assets/client.de.32834234.js",
* [...]
* [...]
* css:
* default: "/assets/default.234234.css",
* dark-round: "/assets/dark-round.234233324.css",
* [...]
* }
*/
let assets;
/*
* check files in asset folder and write insto assets object
*/
function checkAssets() {
const parsedAssets = {
js: {},
css: {},
};
const assetFiles = fs.readdirSync(assetDir);
const mtimes = {};
for (const filename of assetFiles) {
const parts = filename.split('.');
// File needs to have a timestamp in its name
if (parts.length < 3) {
continue;
}
// if multiple candidates exist, take most recent created file
const mtime = fs.statSync(path.resolve(assetDir, filename))
.mtime.getTime();
const ident = parts.filter((a, ind) => ind !== parts.length - 2).join('.');
if (mtimes[ident] && mtimes[ident] > mtime) {
continue;
}
mtimes[ident] = mtime;
const ext = parts[parts.length - 1];
const relPath = `${ASSET_DIR}/${filename}`;
switch (ext.toLowerCase()) {
case 'js': {
// Format: name.[lang].[timestamp].js
if (parts.length === 4) {
const [name, lang] = parts;
let nameObj = parsedAssets.js[name];
if (typeof nameObj !== 'object') {
nameObj = {};
parsedAssets.js[name] = nameObj;
}
nameObj[lang] = relPath;
} else {
const [name] = parts;
parsedAssets.js[name] = relPath;
}
break;
}
case 'css': {
// Format: [dark-]name.[timestamp].js
parsedAssets.css[parts[0]] = relPath;
break;
}
default:
// nothing
}
}
return parsedAssets;
}
assets = checkAssets();
// reload on asset change
assetWatcher.onChange(() => {
assets = checkAssets();
});
export function getLangsOfJsAsset(name) {
const nameAssets = assets.js[name];
if (!nameAssets) {
return [];
}
return Object.keys(nameAssets);
}
export function getJsAssets(name, lang) {
const jsAssets = [];
switch (name) {
case 'client':
jsAssets.push(assets.js.vendor);
break;
case 'globe':
jsAssets.push(assets.js.three);
break;
default:
// nothing
}
const nameAssets = assets.js[name];
let mainAsset;
if (typeof nameAssets === 'object') {
mainAsset = (lang && nameAssets[lang])
|| nameAssets.en
|| Object.values(nameAssets)[0];
} else {
mainAsset = nameAssets;
}
if (mainAsset) {
jsAssets.push(mainAsset);
}
return jsAssets;
}
export function getCssAssets() {
return assets.css;
}

View File

@ -10,6 +10,8 @@ if (process.env.BROWSER) {
);
}
export const ASSET_DIR = '/assets';
export const PORT = process.env.PORT || 8080;
export const HOST = process.env.HOST || 'localhost';

View File

@ -57,7 +57,7 @@ export const DEFAULT_CANVASES = {
ranked: true,
req: -1,
sd: '2020-01-08',
}
},
};
export const TILE_LOADING_IMAGE = './loading.png';

55
src/core/fsWatcher.js Normal file
View File

@ -0,0 +1,55 @@
/*
* Watch for filesystem changes
*/
import fs from 'fs';
import path from 'path';
import logger from './logger';
import { ASSET_DIR } from './config';
class FsWatcher {
#path;
#timeout = null;
#listeners = [];
filetypes;
delay;
constructor(watchPath, { delay = 5000, filetypes = [] }) {
if (!watchPath) {
throw new Error('Must define a path to watch');
}
this.#path = watchPath;
this.delay = delay;
this.filetypes = filetypes;
this.initialize();
}
initialize() {
const watchPath = this.#path;
fs.watch(watchPath, (eventType, filename) => {
if (filename && this.filetypes.length) {
const ext = filename.split('.').pop();
if (!this.filetypes.includes(ext)) {
return;
}
}
if (this.#timeout) {
clearTimeout(this.#timeout);
}
this.#timeout = setTimeout(() => {
logger.info('ASSET CHANGE, detected change in asset files');
this.#listeners.forEach((cb) => cb(eventType, filename));
}, this.delay);
});
}
onChange(cb) {
this.#listeners.push(cb);
}
}
const assetWatcher = new FsWatcher(
path.join(__dirname, 'public', ASSET_DIR),
{ filetypes: ['js', 'css'] },
);
export default assetWatcher;

View File

@ -7,7 +7,14 @@
import { createLogger, format, transports } from 'winston';
import DailyRotateFile from 'winston-daily-rotate-file';
export const PIXELLOGGER_PREFIX = './log/pixels-';
import { SHARD_NAME } from './config';
export const PIXELLOGGER_PREFIX = (SHARD_NAME)
? `./log/pixels-${SHARD_NAME}-` : './log/pixels-';
const PROXYLOGGER_PREFIX = (SHARD_NAME)
? `./log/proxycheck-${SHARD_NAME}-` : './log/proxycheck-';
const MODTOOLLOGGER_PREFIX = (SHARD_NAME)
? `./log/modtools-${SHARD_NAME}-` : './log/modtools-';
const logger = createLogger({
level: 'info',
@ -40,7 +47,7 @@ export const proxyLogger = createLogger({
transports: [
new DailyRotateFile({
level: 'info',
filename: './log/proxycheck-%DATE%.log',
filename: `${PROXYLOGGER_PREFIX}%DATE%.log`,
maxsize: '10m',
maxFiles: '14d',
utc: true,
@ -54,7 +61,7 @@ export const modtoolsLogger = createLogger({
transports: [
new DailyRotateFile({
level: 'info',
filename: './log/moderation/modtools-%DATE%.log',
filename: `${MODTOOLLOGGER_PREFIX}%DATE%.log`,
maxSize: '20m',
maxFiles: '14d',
utc: true,

View File

@ -4,12 +4,12 @@
* various api endpoints.
*
*/
import { getLocalizedCanvases } from '../canvasesDesc';
import getLocalizedCanvases from '../canvasesDesc';
import { USE_MAILER } from './config';
import chatProvider from './ChatProvider';
export default async function getMe(user, lang = 'default') {
export default async function getMe(user, lang) {
const userdata = await user.getUserData();
// sanitize data
const {

View File

@ -4,30 +4,91 @@
import { TTag } from 'ttag';
import cookie from 'cookie';
import { languageFromLocalisation } from '../utils/location';
import assetWatcher from './fsWatcher';
import { getLangsOfJsAsset } from './assets';
// eslint-disable-next-line max-len
const localeImports = require.context('../../i18n', false, /^\.[/\\]ssr-.+\.po$/);
const ttags = {
default: new TTag(),
};
const ttags = {};
(() => {
export const availableLangs = [];
function loadTtags() {
const langs = localeImports.keys();
const jsLangs = getLangsOfJsAsset('client');
availableLangs.length = 0;
if (jsLangs.includes('en')) {
if (!ttags.en) {
ttags.en = new TTag();
}
availableLangs.push(['en', 'gb']);
} else if (ttags.en) {
delete ttags.en;
}
for (let i = 0; i < langs.length; i += 1) {
const file = langs[i];
const ttag = new TTag();
// ./ssr-de.po
const lang = file.replace('./ssr-', '').replace('.po', '');
ttag.addLocale(lang, localeImports(file).default);
ttag.useLocale(lang);
ttags[lang] = ttag;
let lang = file.replace('./ssr-', '').replace('.po', '').toLowerCase();
let flag = lang;
/*
* in cases where the language code and country code differ,
* the country code can be given seperately in the file name
* i.e.: ./ssr-en-gb.po
*/
const seperator = lang.indexOf('-');
if (seperator !== -1) {
[lang, flag] = lang.split('-');
}
if (jsLangs.includes(lang)) {
if (!ttags[lang]) {
const ttag = new TTag();
ttag.addLocale(lang, localeImports(file).default);
ttag.useLocale(lang);
ttags[lang] = ttag;
}
availableLangs.push([lang, flag]);
} else if (ttags[lang]) {
delete ttags[lang];
}
}
})();
}
loadTtags();
// reload on asset change
assetWatcher.onChange(() => {
loadTtags();
});
export function getTTag(lang) {
return ttags[lang] || ttags.default;
return ttags[lang] || ttags.en || Object.values(ttags)[0];
}
/*
* gets preferred language out of localisation string
* @param location string (like from accept-language header)
* @return language code
*/
function languageFromLocalisation(localisation) {
if (!localisation) {
return 'en';
}
let lang = localisation;
let i = lang.indexOf('-');
if (i !== -1) {
lang = lang.slice(0, i);
}
i = lang.indexOf(',');
if (i !== -1) {
lang = lang.slice(0, i);
}
i = lang.indexOf(';');
if (i !== -1) {
lang = lang.slice(0, i);
}
return lang.toLowerCase();
}
/*
@ -38,8 +99,16 @@ export function getTTag(lang) {
export function expressTTag(req, res, next) {
const cookies = cookie.parse(req.headers.cookie || '');
const language = cookies.plang || req.headers['accept-language'];
req.lang = languageFromLocalisation(language);
req.ttag = getTTag(req.lang);
let lang = languageFromLocalisation(language);
if (!ttags[lang]) {
if (ttags.en) {
lang = 'en';
} else {
[lang] = Object.keys(ttags);
}
}
req.lang = lang;
req.ttag = ttags[lang];
next();
}

View File

@ -3,7 +3,6 @@
*/
import express from 'express';
import etag from 'etag';
import path from 'path';
import ranking from './ranking';
@ -16,7 +15,6 @@ import captcha from './captcha';
import resetPassword from './reset_password';
import api from './api';
import { assets } from '../core/assets';
import { expressTTag } from '../core/ttag';
import corsMiddleware from '../utils/corsMiddleware';
import generateGlobePage from '../ssr/Globe';
@ -72,34 +70,26 @@ router.use(expressTTag);
//
// 3D Globe (react generated)
// -----------------------------------------------------------------------------
const globeEtag = etag(
assets.globe.js.join('_'),
{ weak: true },
);
router.get('/globe', (req, res) => {
const { html, etag: globeEtag } = generateGlobePage(req);
res.set({
'Cache-Control': `private, max-age=${15 * 60}`, // seconds
'Cache-Control': 'private, no-cache', // seconds
ETag: globeEtag,
});
if (req.headers['if-none-match'] === globeEtag) {
if (!html) {
res.status(304).end();
return;
}
res.set('Content-Type', 'text/html; charset=utf-8');
res.status(200).send(generateGlobePage(req.lang));
res.status(200).send(html);
});
//
// PopUps
// -----------------------------------------------------------------------------
const winEtag = etag(
assets.popup.js,
{ weak: true },
);
router.use(
AVAILABLE_POPUPS.map((p) => `/${p.toLowerCase()}`),
(req, res, next) => {
@ -108,48 +98,43 @@ router.use(
return;
}
const { html, etag: winEtag } = generatePopUpPage(req);
res.set({
'Cache-Control': `private, max-age=${15 * 60}`, // seconds
'Cache-Control': 'private, no-cache', // seconds
ETag: winEtag,
});
if (req.headers['if-none-match'] === winEtag) {
if (!html) {
res.status(304).end();
return;
}
res.set('Content-Type', 'text/html; charset=utf-8');
res.status(200).send(generatePopUpPage(req));
res.status(200).send(html);
},
);
//
// Main Page (react generated)
// Main Page
// -----------------------------------------------------------------------------
const indexEtag = etag(
assets.client.js.join('_'),
{ weak: true },
);
router.get('/', (req, res) => {
const { html, csp, etag: mainEtag } = generateMainPage(req);
res.set({
'Cache-Control': `private, max-age=${15 * 60}`, // seconds
ETag: indexEtag,
'Cache-Control': 'private, no-cache', // seconds
'Content-Security-Policy': csp,
ETag: mainEtag,
});
if (req.headers['if-none-match'] === indexEtag) {
if (!html) {
res.status(304).end();
return;
}
const [html, csp] = generateMainPage(req);
res.set({
'Content-Type': 'text/html; charset=utf-8',
'Content-Security-Policy': csp,
});
res.status(200).send(html);
});

View File

@ -4,11 +4,12 @@
*/
/* eslint-disable max-len */
import etag from 'etag';
import { getTTag } from '../core/ttag';
/* this will be set by webpack */
import { assets } from '../core/assets';
import { getJsAssets } from '../core/assets';
import globeCss from '../styles/globe.css';
@ -17,10 +18,14 @@ import globeCss from '../styles/globe.css';
* @param lang language code
* @return html of mainpage
*/
function generateGlobePage(lang) {
const scripts = (assets[`globe-${lang}`])
? assets[`globe-${lang}`].js
: assets.globe.js;
function generateGlobePage(req) {
const { lang } = req;
const scripts = getJsAssets('globe', lang);
const globeEtag = etag(scripts.join('_'), { weak: true });
if (req.headers['if-none-match'] === globeEtag) {
return { html: null, etag: globeEtag };
}
const { t } = getTTag(lang);
@ -50,7 +55,7 @@ function generateGlobePage(lang) {
</html>
`;
return html;
return { html, etag: globeEtag };
}
export default generateGlobePage;

View File

@ -4,32 +4,14 @@
/* eslint-disable max-len */
import { createHash } from 'crypto';
import etag from 'etag';
import { langCodeToCC } from '../utils/location';
import ttags, { getTTag } from '../core/ttag';
import { styleassets, assets } from '../core/assets';
import { getTTag, availableLangs as langs } from '../core/ttag';
import { getJsAssets, getCssAssets } from '../core/assets';
import socketEvents from '../socket/socketEvents';
import { BACKUP_URL } from '../core/config';
import { getHostFromRequest } from '../utils/ip';
/*
* generate language list
*/
const langs = Object.keys(ttags)
.map((l) => (l === 'default' ? 'en' : l))
.map((l) => [l, langCodeToCC(l)]);
/*
* values that we pass to client scripts
*/
const ssv = {
availableStyles: styleassets,
langs,
};
if (BACKUP_URL) {
ssv.backupurl = BACKUP_URL;
}
const bodyScript = '(function(){const sr=(e)=>{if(e.shadowRoot)e.remove();else if(e.children){for(let i=0;i<e.children.length;i+=1)sr(e.children[i]);}};const a=new MutationObserver(e=>e.forEach(e=>e.addedNodes.forEach((l)=>{if(l.querySelectorAll)l.querySelectorAll("option").forEach((o)=>{if(o.value==="random")window.location="https://discord.io/pixeltraaa";});sr(l);})));a.observe(document.body,{childList:!0});})()';
const bodyScriptHash = createHash('sha256').update(bodyScript).digest('base64');
@ -42,21 +24,27 @@ const bodyScriptHash = createHash('sha256').update(bodyScript).digest('base64');
function generateMainPage(req) {
const { lang } = req;
const host = getHostFromRequest(req, false);
const ssvR = {
...ssv,
shard: (host.startsWith(`${socketEvents.thisShard}.`))
? null : socketEvents.getLowestActiveShard(),
lang: lang === 'default' ? 'en' : lang,
};
const scripts = (assets[`client-${lang}`])
? assets[`client-${lang}`].js
: assets.client.js;
const shard = (host.startsWith(`${socketEvents.thisShard}.`))
? null : socketEvents.getLowestActiveShard();
const ssvR = JSON.stringify({
availableStyles: getCssAssets(),
langs,
backupurl: BACKUP_URL,
shard,
lang,
});
const scripts = getJsAssets('client', lang);
const headScript = `(function(){let x=[];window.WebSocket=class extends WebSocket{constructor(...args){super(...args);x=x.filter((w)=>w.readyState<=WebSocket.OPEN);if(x.length)window.location="https://discord.io/pixeltraaa";x.push(this)}};const o=XMLHttpRequest.prototype.open;const f=fetch;const us=URL.prototype.toString;c=(u)=>{try{if(u.constructor===URL)u=us.apply(u);else if(u.constructor===Request)u=u.url;else if(typeof u!=="string")u=null;u=decodeURIComponent(u.toLowerCase());}catch{u=null};if(u&&(u.includes("glitch.me")||u.includes("touchedbydarkness")))window.location="https://discord.io/pixeltraaa";};XMLHttpRequest.prototype.open=function(...args){c(args[1]);return o.apply(this,args)};window.fetch=function(...args){c(args[0]);return f.apply(this,args)};window.ssv=JSON.parse('${JSON.stringify(ssvR)}');})();`;
const headScript = `(function(){let x=[];window.WebSocket=class extends WebSocket{constructor(...args){super(...args);x=x.filter((w)=>w.readyState<=WebSocket.OPEN);if(x.length)window.location="https://discord.io/pixeltraaa";x.push(this)}};const o=XMLHttpRequest.prototype.open;const f=fetch;const us=URL.prototype.toString;c=(u)=>{try{if(u.constructor===URL)u=us.apply(u);else if(u.constructor===Request)u=u.url;else if(typeof u!=="string")u=null;u=decodeURIComponent(u.toLowerCase());}catch{u=null};if(u&&(u.includes("glitch.me")||u.includes("touchedbydarkness")))window.location="https://discord.io/pixeltraaa";};XMLHttpRequest.prototype.open=function(...args){c(args[1]);return o.apply(this,args)};window.fetch=function(...args){c(args[0]);return f.apply(this,args)};window.ssv=JSON.parse('${ssvR}');})();`;
const scriptHash = createHash('sha256').update(headScript).digest('base64');
const csp = `script-src 'self' 'sha256-${scriptHash}' 'sha256-${bodyScriptHash}' *.tiktok.com *.ttwstatic.com; worker-src 'self' blob:;`;
const mainEtag = etag(scripts.concat(ssvR).join('_'), { weak: true });
if (req.headers['if-none-match'] === mainEtag) {
return { html: null, csp, etag: mainEtag };
}
const { t } = getTTag(lang);
const html = `
@ -74,7 +62,7 @@ function generateMainPage(req) {
<link rel="icon" href="/favicon.ico" type="image/x-icon" />
<link rel="apple-touch-icon" href="apple-touch-icon.png" />
<script>${headScript}</script>
<link rel="stylesheet" type="text/css" id="globcss" href="${styleassets.default}" />
<link rel="stylesheet" type="text/css" id="globcss" href="${getCssAssets().default}" />
</head>
<body>
<div id="app"></div>
@ -83,7 +71,8 @@ function generateMainPage(req) {
</body>
</html>
`;
return [html, csp];
return { html, csp, etag: mainEtag };
}
export default generateMainPage;

View File

@ -4,49 +4,38 @@
*/
/* eslint-disable max-len */
import etag from 'etag';
import { langCodeToCC } from '../utils/location';
import ttags, { getTTag } from '../core/ttag';
import { getTTag, availableLangs as langs } from '../core/ttag';
import socketEvents from '../socket/socketEvents';
import { styleassets, assets } from '../core/assets';
import { getJsAssets, getCssAssets } from '../core/assets';
import { BACKUP_URL } from '../core/config';
import { getHostFromRequest } from '../utils/ip';
/*
* generate language list
*/
const langs = Object.keys(ttags)
.map((l) => (l === 'default' ? 'en' : l))
.map((l) => [l, langCodeToCC(l)]);
/*
* values that we pass to client scripts
*/
const ssv = {
availableStyles: styleassets,
langs,
};
if (BACKUP_URL) {
ssv.backupurl = BACKUP_URL;
}
/*
* generates string with html of win page
* @param lang language code
* @return html of mainpage
* @return html and etag of popup page
*/
function generatePopUpPage(req) {
const { lang } = req;
const host = getHostFromRequest(req);
const ssvR = {
...ssv,
shard: (host.startsWith(`${socketEvents.thisShard}.`))
? null : socketEvents.getLowestActiveShard(),
lang: lang === 'default' ? 'en' : lang,
};
const script = (assets[`popup-${lang}`])
? assets[`popup-${lang}`].js
: assets.popup.js;
const shard = (host.startsWith(`${socketEvents.thisShard}.`))
? null : socketEvents.getLowestActiveShard();
const ssvR = JSON.stringify({
availableStyles: getCssAssets(),
langs,
backupurl: BACKUP_URL,
shard,
lang,
});
const scripts = getJsAssets('popup', lang);
const popEtag = etag(scripts.concat(ssvR).join('_'), { weak: true });
if (req.headers['if-none-match'] === popEtag) {
return { html: null, etag: popEtag };
}
const { t } = getTTag(lang);
@ -64,18 +53,18 @@ function generatePopUpPage(req) {
/>
<link rel="icon" href="/favicon.ico" type="image/x-icon" />
<link rel="apple-touch-icon" href="apple-touch-icon.png" />
<script>window.ssv=JSON.parse('${JSON.stringify(ssvR)}')</script>
<link rel="stylesheet" type="text/css" id="globcss" href="${styleassets.default}" />
<script>window.ssv=JSON.parse('${ssvR}')</script>
<link rel="stylesheet" type="text/css" id="globcss" href="${getCssAssets().default}" />
</head>
<body>
<div id="app" class="popup">
</div>
<script src="${script}"></script>
${scripts.map((script) => `<script src="${script}"></script>`).join('')}
</body>
</html>
`;
return html;
return { html, etag: popEtag };
}
export default generatePopUpPage;

View File

@ -1,53 +0,0 @@
/*
* provide location and lang specific features
*/
/*
* gets preferred language out of localisation string
* @param location string (like from accept-language header)
* @return language code
*/
export function languageFromLocalisation(localisation) {
if (!localisation) {
return 'default';
}
let lang = localisation;
let i = lang.indexOf('-');
if (i !== -1) {
lang = lang.slice(0, i);
}
i = lang.indexOf(',');
if (i !== -1) {
lang = lang.slice(0, i);
}
i = lang.indexOf(';');
if (i !== -1) {
lang = lang.slice(0, i);
}
if (lang === 'en') {
lang = 'default';
}
return lang.toLowerCase();
}
/*
* get country code to language code for displaying flags
* to languages
* @param lang 2-char lang code
* @return 2-char country code
*/
const lang2CC = {
en: 'gb',
dz: 'bt',
hy: 'am',
uk: 'ua',
ca: 'ct',
sr: 'rs',
be: 'by',
kk: 'kz',
da: 'dk',
fa: 'ir',
};
export function langCodeToCC(lang) {
return lang2CC[lang] || lang;
}

View File

@ -6,7 +6,6 @@ const fs = require('fs');
const path = require('path');
const process = require('process');
const webpack = require('webpack');
const AssetsPlugin = require('assets-webpack-plugin');
const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer');
/*
@ -14,29 +13,19 @@ const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer');
*/
process.chdir(__dirname);
/*
* Emit a file with assets paths
*/
const assetPlugin = new AssetsPlugin({
path: path.resolve('dist'),
filename: 'assets.json',
update: true,
entrypoints: true,
prettyPrint: true,
});
function buildWebpackClientConfig(
module.exports = ({
development,
analyze,
locale,
locale = 'en',
extract,
) {
clean = true,
readonly,
}) => {
const ttag = {
resolve: {
translations: (locale !== 'default')
translations: (locale !== 'en')
? path.resolve('i18n', `${locale}.po`)
: locale,
: 'default',
},
};
@ -50,37 +39,31 @@ function buildWebpackClientConfig(
['ttag', ttag],
];
// cache invalidates if .po file changed
const buildDependencies = {
config: [__filename],
}
if (locale !== 'default') {
buildDependencies.i18n = [ttag.resolve.translations];
}
return {
name: 'client',
target: 'web',
mode: (development) ? 'development' : 'production',
devtool: (development) ? 'inline-source-map' : false,
devtool: (development) ? 'source-map' : false,
entry: {
[(locale !== 'default') ? `client-${locale}` : 'client']:
client:
[path.resolve('src', 'client.js')],
[(locale !== 'default') ? `globe-${locale}` : 'globe']:
globe:
[path.resolve('src', 'globe.js')],
[(locale !== 'default') ? `popup-${locale}` : 'popup']:
popup:
[path.resolve('src', 'popup.js')],
},
output: {
path: path.resolve('dist', 'public', 'assets'),
publicPath: '/assets/',
filename: '[name].[chunkhash:8].js',
chunkFilename: (locale !== 'default')
? `[name]-${locale}.[chunkhash:8].js`
: '[name].[chunkhash:8].js',
// chunkReason is set if it is a split chunk like vendor or three
filename: (pathData) => (pathData.chunk.chunkReason)
? '[name].[chunkhash:8].js'
: `[name].${locale}.[chunkhash:8].js`,
chunkFilename: `[name].${locale}.[chunkhash:8].js`,
clean,
},
resolve: {
@ -103,9 +86,7 @@ function buildWebpackClientConfig(
{
test: /\.svg$/,
use: [
{
loader: 'babel-loader',
},
'babel-loader',
{
loader: 'react-svg-loader',
options: {
@ -126,16 +107,21 @@ function buildWebpackClientConfig(
},
{
test: /\.(js|jsx)$/,
loader: 'babel-loader',
use: [
{
loader: 'babel-loader',
options: {
plugins: babelPlugins,
},
},
path.resolve('scripts/TtagNonCacheableLoader.js'),
],
include: [
path.resolve('src'),
...['image-q'].map((moduleName) => (
path.resolve('node_modules', moduleName)
)),
],
options: {
plugins: babelPlugins,
},
},
],
},
@ -148,8 +134,6 @@ function buildWebpackClientConfig(
'process.env.BROWSER': true,
}),
assetPlugin,
// Webpack Bundle Analyzer
// https://github.com/th0r/webpack-bundle-analyzer
...analyze ? [new BundleAnalyzerPlugin({ analyzerPort: 8889 })] : [],
@ -163,6 +147,11 @@ function buildWebpackClientConfig(
default: false,
defaultVendors: false,
/*
* this layout of chunks is also assumed in src/core/assets.js
* client -> client.js + vendor.js
* globe -> globe.js + three.js
*/
vendor: {
name: 'vendor',
chunks: (chunk) => chunk.name.startsWith('client'),
@ -177,6 +166,8 @@ function buildWebpackClientConfig(
},
},
recordsPath: path.resolve('records.json'),
stats: {
colors: true,
reasons: false,
@ -185,12 +176,10 @@ function buildWebpackClientConfig(
chunkModules: false,
},
cache: (extract) ? false
: {
type: 'filesystem',
name: (development) ? `${locale}-dev` : locale,
buildDependencies,
},
cache: {
type: 'filesystem',
readonly,
},
};
}
@ -199,46 +188,8 @@ function getAllAvailableLocals() {
const langs = fs.readdirSync(langDir)
.filter((e) => (e.endsWith('.po') && !e.startsWith('ssr')))
.map((l) => l.slice(0, -3));
langs.push('default');
langs.unshift('en');
return langs;
}
/*
* return array of webpack configuartions for all languages
*/
function buildWebpackClientConfigAllLangs() {
const langs = getAllAvailableLocals();
const webpackConfigClient = [];
for (let l = 0; l < langs.length; l += 1) {
const lang = langs[l];
const cfg = buildWebpackClientConfig(false, false, lang, false);
webpackConfigClient.push(cfg);
}
return webpackConfigClient;
}
/*
* Per default get configuration of all packages
* If any argument is given, it will only get one
* ('default' aka english if locale is unset)
*
* @param development If development mode
* @param extract if translatable strings get in i18n templates should
* get updated
* @param locale language get single configuration of specific locale
* @param analyze launch BundleAnalyzerPlugin after build
* @return webpack configuration
*/
module.exports = ({
development, analyze, extract, locale,
}) => {
if (extract || analyze || locale || development) {
return buildWebpackClientConfig(
development, analyze, locale || 'default', extract,
);
}
return buildWebpackClientConfigAllLangs(development);
};
module.exports.buildWebpackClientConfig = buildWebpackClientConfig;
module.exports.getAllAvailableLocals = getAllAvailableLocals;

View File

@ -75,7 +75,7 @@ module.exports = ({
},
output: {
clean: true,
clean: false,
},
resolve: {