diff --git a/i18n/template-ssr.pot b/i18n/template-ssr.pot index 404fd90..780bdb6 100644 --- a/i18n/template-ssr.pot +++ b/i18n/template-ssr.pot @@ -3,53 +3,53 @@ msgstr "" "Content-Type: text/plain; charset=utf-8\n" "Plural-Forms: nplurals=2; plural=(n!=1);\n" -#: src/core/ChatProvider.js:350 +#: src/core/ChatProvider.js:370 msgid "You can not send chat messages with proxy" msgstr "" -#: src/core/ChatProvider.js:364 +#: src/core/ChatProvider.js:384 #, javascript-format msgid "You are sending messages too fast, you have to wait ${ waitTime }s :(" msgstr "" -#: src/core/ChatProvider.js:368 +#: src/core/ChatProvider.js:388 msgid "You don't have access to this channel" msgstr "" -#: src/core/ChatProvider.js:384 +#: src/core/ChatProvider.js:404 msgid "Your mail has to be verified in order to chat" msgstr "" -#: src/core/ChatProvider.js:389 +#: src/core/ChatProvider.js:409 msgid "You are permanently muted, join our guilded to apppeal the mute" msgstr "" -#: src/core/ChatProvider.js:394 +#: src/core/ChatProvider.js:414 #, javascript-format msgid "You are muted for another ${ timeMin } minutes" msgstr "" -#: src/core/ChatProvider.js:396 +#: src/core/ChatProvider.js:416 msgid "You are muted for another ${ muted } seconds" msgstr "" -#: src/core/ChatProvider.js:404 +#: src/core/ChatProvider.js:424 msgid "Ow no! Spam protection decided to mute you" msgstr "" -#: src/core/ChatProvider.js:415 +#: src/core/ChatProvider.js:435 msgid "You can't send a message this long :(" msgstr "" -#: src/core/ChatProvider.js:419 +#: src/core/ChatProvider.js:439 msgid "Please use int channel" msgstr "" -#: src/core/ChatProvider.js:423 +#: src/core/ChatProvider.js:443 msgid "Your country is temporary muted from chat" msgstr "" -#: src/core/ChatProvider.js:431 +#: src/core/ChatProvider.js:451 msgid "Stop flooding." msgstr "" @@ -245,15 +245,15 @@ msgstr "" msgid "Server error occured" msgstr "" -#: src/routes/api/modtools.js:50 +#: src/routes/api/modtools.js:52 msgid "You are not logged in" msgstr "" -#: src/routes/api/modtools.js:62 +#: src/routes/api/modtools.js:64 msgid "You are not allowed to access this page" msgstr "" -#: src/routes/api/modtools.js:128 +#: src/routes/api/modtools.js:166 msgid "Just admins can do that" msgstr "" @@ -282,7 +282,7 @@ msgid "Name can't be empty." msgstr "" #: src/utils/validation.js:31 -msgid "Name must be at least 4 characters long" +msgid "Name must be at least 2 characters long" msgstr "" #: src/utils/validation.js:32 diff --git a/i18n/template.pot b/i18n/template.pot index 0caee60..8e5fdf4 100644 --- a/i18n/template.pot +++ b/i18n/template.pot @@ -3,6 +3,48 @@ msgstr "" "Content-Type: text/plain; charset=utf-8\n" "Plural-Forms: nplurals=2; plural=(n!=1);\n" +#: src/controls/keypress.js:41 +#, javascript-format +msgid "Switched to ${ canvasName }" +msgstr "" + +#: src/controls/keypress.js:64 +msgid "Grid ON" +msgstr "" + +#: src/controls/keypress.js:65 +msgid "Grid OFF" +msgstr "" + +#: src/controls/keypress.js:75 +msgid "Pixel Notify ON" +msgstr "" + +#: src/controls/keypress.js:76 +msgid "Pixel Notify OFF" +msgstr "" + +#: src/controls/keypress.js:81 +msgid "Muted Sound" +msgstr "" + +#: src/controls/keypress.js:82 +msgid "Unmuted Sound" +msgstr "" + +#: src/components/CoordinatesBox.jsx:29 +#: src/controls/keypress.js:88 +msgid "Copied!" +msgstr "" + +#: src/controls/keypress.js:94 +msgid "Show Hidden Canvases" +msgstr "" + +#: src/controls/keypress.js:95 +msgid "Hide Hidden Canvases" +msgstr "" + #: src/ui/placePixel.js:53 msgid "Error :(" msgstr "" @@ -104,48 +146,6 @@ msgstr "" msgid "Error ${ retCode }" msgstr "" -#: src/controls/keypress.js:41 -#, javascript-format -msgid "Switched to ${ canvasName }" -msgstr "" - -#: src/controls/keypress.js:64 -msgid "Grid ON" -msgstr "" - -#: src/controls/keypress.js:65 -msgid "Grid OFF" -msgstr "" - -#: src/controls/keypress.js:75 -msgid "Pixel Notify ON" -msgstr "" - -#: src/controls/keypress.js:76 -msgid "Pixel Notify OFF" -msgstr "" - -#: src/controls/keypress.js:81 -msgid "Muted Sound" -msgstr "" - -#: src/controls/keypress.js:82 -msgid "Unmuted Sound" -msgstr "" - -#: src/components/CoordinatesBox.jsx:29 -#: src/controls/keypress.js:88 -msgid "Copied!" -msgstr "" - -#: src/controls/keypress.js:94 -msgid "Show Hidden Canvases" -msgstr "" - -#: src/controls/keypress.js:95 -msgid "Hide Hidden Canvases" -msgstr "" - #: src/ui/renderer.js:36 msgid "Canvas Error" msgstr "" @@ -170,6 +170,11 @@ msgstr "" msgid "Look at past Canvases" msgstr "" +#: src/components/Converter.jsx:559 +#: src/components/CoordinatesBox.jsx:32 +msgid "Copy to Clipboard" +msgstr "" + #: src/components/OnlineBox.jsx:41 msgid "Online Users on Canvas" msgstr "" @@ -182,13 +187,8 @@ msgstr "" msgid "Pixels placed" msgstr "" -#: src/components/Converter.jsx:559 -#: src/components/CoordinatesBox.jsx:32 -msgid "Copy to Clipboard" -msgstr "" - #: src/components/ModalRoot.jsx:69 -#: src/components/Modtools.jsx:224 +#: src/components/Modtools.jsx:318 #: src/components/Window.jsx:138 #: src/components/contextmenus/ChannelContextMenu.jsx:67 msgid "Close" @@ -198,27 +198,6 @@ msgstr "" msgid "Restore" msgstr "" -#: src/components/buttons/ChatButton.jsx:92 -msgid "Close Chat" -msgstr "" - -#: src/components/buttons/ChatButton.jsx:92 -msgid "Open Chat" -msgstr "" - -#: src/components/buttons/CanvasSwitchButton.jsx:22 -#: src/components/windows/index.js:19 -msgid "Canvas Selection" -msgstr "" - -#: src/components/buttons/ExpandMenuButton.jsx:23 -msgid "Close Menu" -msgstr "" - -#: src/components/buttons/ExpandMenuButton.jsx:23 -msgid "Open Menu" -msgstr "" - #: src/actions/fetch.js:39 msgid "You made too many requests" msgstr "" @@ -248,8 +227,25 @@ msgstr "" msgid "Server answered with gibberish :(" msgstr "" -#: src/components/buttons/GlobeButton.jsx:35 -msgid "Globe View" +#: src/components/buttons/ChatButton.jsx:92 +msgid "Close Chat" +msgstr "" + +#: src/components/buttons/ChatButton.jsx:92 +msgid "Open Chat" +msgstr "" + +#: src/components/buttons/CanvasSwitchButton.jsx:22 +#: src/components/windows/index.js:19 +msgid "Canvas Selection" +msgstr "" + +#: src/components/buttons/ExpandMenuButton.jsx:23 +msgid "Close Menu" +msgstr "" + +#: src/components/buttons/ExpandMenuButton.jsx:23 +msgid "Open Menu" msgstr "" #: src/components/HistorySelect.jsx:144 @@ -260,33 +256,6 @@ msgstr "" msgid "Select Date above" msgstr "" -#: src/components/buttons/PalselButton.jsx:31 -msgid "Close Palette" -msgstr "" - -#: src/components/buttons/PalselButton.jsx:31 -msgid "Open Palette" -msgstr "" - -#: src/components/buttons/HelpButton.jsx:23 -#: src/components/windows/index.js:13 -msgid "Help" -msgstr "" - -#: src/components/buttons/SettingsButton.jsx:23 -#: src/components/windows/index.js:14 -msgid "Settings" -msgstr "" - -#: src/components/buttons/DownloadButton.jsx:37 -msgid "Make Screenshot" -msgstr "" - -#: src/components/buttons/LogInButton.jsx:23 -#: src/components/windows/index.js:15 -msgid "User Area" -msgstr "" - #: src/components/Window.jsx:117 msgid "Clone" msgstr "" @@ -303,22 +272,53 @@ msgstr "" msgid "Resize" msgstr "" -#: src/components/contextmenus/UserContextMenu.jsx:53 -msgid "Ping" +#: src/components/buttons/SettingsButton.jsx:23 +#: src/components/windows/index.js:14 +msgid "Settings" msgstr "" -#: src/components/contextmenus/UserContextMenu.jsx:78 -msgid "DM" +#: src/components/buttons/HelpButton.jsx:23 +#: src/components/windows/index.js:13 +msgid "Help" msgstr "" -#: src/components/contextmenus/UserContextMenu.jsx:88 -msgid "Block" +#: src/components/buttons/LogInButton.jsx:23 +#: src/components/windows/index.js:15 +msgid "User Area" +msgstr "" + +#: src/components/buttons/DownloadButton.jsx:37 +msgid "Make Screenshot" +msgstr "" + +#: src/components/buttons/GlobeButton.jsx:35 +msgid "Globe View" +msgstr "" + +#: src/components/buttons/PalselButton.jsx:31 +msgid "Close Palette" +msgstr "" + +#: src/components/buttons/PalselButton.jsx:31 +msgid "Open Palette" msgstr "" #: src/components/contextmenus/ChannelContextMenu.jsx:55 msgid "Mute" msgstr "" +#: src/components/contextmenus/UserContextMenu.jsx:55 +msgid "Ping" +msgstr "" + +#: src/components/contextmenus/UserContextMenu.jsx:80 +msgid "DM" +msgstr "" + +#: src/components/contextmenus/UserContextMenu.jsx:90 +msgid "Block" +msgstr "" + #: src/components/windows/index.js:16 msgid "Registration" msgstr "" @@ -631,6 +631,73 @@ msgstr "" msgid "Select Language" msgstr "" +#: src/components/windows/UserArea.jsx:27 +msgid "Profile" +msgstr "" + +#: src/components/windows/UserArea.jsx:30 +msgid "Ranking" +msgstr "" + +#: src/components/windows/UserArea.jsx:33 +msgid "Converter" +msgstr "" + +#: src/components/windows/UserArea.jsx:39 +msgid "Modtools" +msgstr "" + +#: src/components/windows/UserArea.jsx:40 +msgid "Loading..." +msgstr "" + +#: src/components/windows/UserArea.jsx:47 +msgid "Consider joining us on Guilded:" +msgstr "" + +#: src/components/windows/Register.jsx:85 +msgid "Register new account here" +msgstr "" + +#: src/components/windows/Register.jsx:90 +#: src/components/windows/Register.jsx:96 +msgid "Name" +msgstr "" + +#: src/components/windows/ForgotPassword.jsx:82 +#: src/components/windows/Register.jsx:98 +#: src/components/windows/Register.jsx:104 +msgid "Email" +msgstr "" + +#: src/components/ChangeMail.jsx:80 +#: src/components/DeleteAccount.jsx:62 +#: src/components/LogInForm.jsx:83 +#: src/components/windows/Register.jsx:106 +#: src/components/windows/Register.jsx:112 +msgid "Password" +msgstr "" + +#: src/components/windows/Register.jsx:114 +#: src/components/windows/Register.jsx:120 +msgid "Confirm Password" +msgstr "" + +#: src/components/windows/Register.jsx:122 +msgid "Captcha" +msgstr "" + +#: src/components/Modtools.jsx:405 +#: src/components/Modtools.jsx:486 +#: src/components/Modtools.jsx:561 +#: src/components/Modtools.jsx:653 +#: src/components/Modtools.jsx:715 +#: src/components/Modtools.jsx:798 +#: src/components/windows/ForgotPassword.jsx:86 +#: src/components/windows/Register.jsx:125 +msgid "Submit" +msgstr "" + #: src/components/windows/CanvasSelect.jsx:33 msgid "" "Select the canvas you want to use. Every canvas is unique and has " @@ -683,88 +750,22 @@ msgstr "" msgid "Enter your mail address and we will send you a new password:" msgstr "" -#: src/components/windows/ForgotPassword.jsx:82 -#: src/components/windows/Register.jsx:98 -#: src/components/windows/Register.jsx:104 -msgid "Email" -msgstr "" - -#: src/components/Modtools.jsx:311 -#: src/components/Modtools.jsx:392 -#: src/components/Modtools.jsx:467 -#: src/components/Modtools.jsx:512 -#: src/components/Modtools.jsx:595 -#: src/components/windows/ForgotPassword.jsx:86 -#: src/components/windows/Register.jsx:125 -msgid "Submit" -msgstr "" - -#: src/components/windows/Chat.jsx:146 +#: src/components/windows/Chat.jsx:133 msgid "Channel settings" msgstr "" -#: src/components/windows/Chat.jsx:161 +#: src/components/windows/Chat.jsx:150 msgid "Start chatting here" msgstr "" -#: src/components/windows/Chat.jsx:198 +#: src/components/windows/Chat.jsx:192 msgid "Chat here" msgstr "" -#: src/components/windows/Chat.jsx:221 +#: src/components/windows/Chat.jsx:214 msgid "You must be logged in to chat" msgstr "" -#: src/components/windows/UserArea.jsx:27 -msgid "Profile" -msgstr "" - -#: src/components/windows/UserArea.jsx:30 -msgid "Ranking" -msgstr "" - -#: src/components/windows/UserArea.jsx:33 -msgid "Converter" -msgstr "" - -#: src/components/windows/UserArea.jsx:39 -msgid "Modtools" -msgstr "" - -#: src/components/windows/UserArea.jsx:40 -msgid "Loading..." -msgstr "" - -#: src/components/windows/UserArea.jsx:47 -msgid "Consider joining us on Guilded:" -msgstr "" - -#: src/components/windows/Register.jsx:85 -msgid "Register new account here" -msgstr "" - -#: src/components/windows/Register.jsx:90 -#: src/components/windows/Register.jsx:96 -msgid "Name" -msgstr "" - -#: src/components/ChangeMail.jsx:80 -#: src/components/DeleteAccount.jsx:62 -#: src/components/LogInForm.jsx:83 -#: src/components/windows/Register.jsx:106 -#: src/components/windows/Register.jsx:112 -msgid "Password" -msgstr "" - -#: src/components/windows/Register.jsx:114 -#: src/components/windows/Register.jsx:120 -msgid "Confirm Password" -msgstr "" - -#: src/components/windows/Register.jsx:122 -msgid "Captcha" -msgstr "" - #: src/components/Captcha.jsx:50 #: src/components/Captcha.jsx:105 msgid "Could not load captcha" @@ -823,7 +824,7 @@ msgid "Name can't be empty." msgstr "" #: src/utils/validation.js:31 -msgid "Name must be at least 4 characters long" +msgid "Name must be at least 2 characters long" msgstr "" #: src/utils/validation.js:32 @@ -853,49 +854,28 @@ msgstr "" msgid "Save" msgstr "" -#: src/components/CanvasItem.jsx:30 -msgid "Online Users" +#: src/components/LogInArea.jsx:21 +msgid "Login to access more features and stats." msgstr "" -#: src/components/CanvasItem.jsx:35 -msgid "Cooldown" +#: src/components/LogInArea.jsx:23 +msgid "Login with Name or Mail:" msgstr "" -#: src/components/CanvasItem.jsx:41 -msgid "Stacking till" +#: src/components/LogInArea.jsx:30 +msgid "I forgot my Password." msgstr "" -#: src/components/CanvasItem.jsx:43 -msgid "Ranked" +#: src/components/LogInArea.jsx:31 +msgid "or login with:" msgstr "" -#: src/components/CanvasItem.jsx:45 -msgid "Yes" +#: src/components/LogInArea.jsx:72 +msgid "or register here:" msgstr "" -#: src/components/CanvasItem.jsx:45 -msgid "No" -msgstr "" - -#: src/components/CanvasItem.jsx:51 -msgid "Requirements" -msgstr "" - -#: src/components/CanvasItem.jsx:54 -msgid "User Account" -msgstr "" - -#: src/components/CanvasItem.jsx:56 -#, javascript-format -msgid "and ${ canvas.req } Pixels set" -msgstr "" - -#: src/components/CanvasItem.jsx:59 -msgid "Top 10 Daily Ranking" -msgstr "" - -#: src/components/CanvasItem.jsx:65 -msgid "Dimensions" +#: src/components/LogInArea.jsx:79 +msgid "Register" msgstr "" #: src/components/UserAreaContent.jsx:63 @@ -955,81 +935,126 @@ msgstr "" msgid "Ranking updates every 5 min. Daily rankings get reset at midnight UTC." msgstr "" -#: src/components/Modtools.jsx:184 +#: src/components/Modtools.jsx:246 msgid "Build image on canvas." msgstr "" -#: src/components/Modtools.jsx:187 +#: src/components/Modtools.jsx:249 msgid "Build image and set it to protected." msgstr "" -#: src/components/Modtools.jsx:190 +#: src/components/Modtools.jsx:252 msgid "Build image, but reset cooldown to unset-pixel cd." msgstr "" -#: src/components/Modtools.jsx:253 +#: src/components/Modtools.jsx:262 +msgid "Clean spare pixels that are surrounded by unset pixels" +msgstr "" + +#: src/components/Modtools.jsx:266 +msgid "" +"Clean spare pixels that are surrounded by unset pixels and up to 1 other " +"set pixels" +msgstr "" + +#: src/components/Modtools.jsx:270 +msgid "" +"Clean spare pixels that are surrounded by a single other color or unset " +"pixels (VERY AGGRESSIVE ON CANVASES THAT ALLOW UNSET PIXELS (where there " +"are two cooldowns)!)" +msgstr "" + +#: src/components/Modtools.jsx:292 +msgid "Status: Not running" +msgstr "" + +#: src/components/Modtools.jsx:347 msgid "Image Upload" msgstr "" -#: src/components/Modtools.jsx:254 +#: src/components/Modtools.jsx:348 msgid "Upload images to canvas" msgstr "" -#: src/components/Modtools.jsx:256 +#: src/components/Modtools.jsx:350 msgid "File" msgstr "" -#: src/components/Modtools.jsx:276 +#: src/components/Modtools.jsx:370 msgid "Coordinates in X_Y format:" msgstr "" -#: src/components/Modtools.jsx:316 +#: src/components/Modtools.jsx:410 msgid "Pixel Protection" msgstr "" -#: src/components/Modtools.jsx:318 +#: src/components/Modtools.jsx:412 msgid "" "Set protection of areas (if you need finer grained control, " "use protect with image upload and alpha layers)" msgstr "" -#: src/components/Modtools.jsx:398 +#: src/components/Modtools.jsx:432 +#: src/components/Modtools.jsx:507 +#: src/components/Modtools.jsx:591 +msgid "Top-left corner" +msgstr "" + +#: src/components/Modtools.jsx:450 +#: src/components/Modtools.jsx:525 +#: src/components/Modtools.jsx:609 +msgid "Bottom-right corner" +msgstr "" + +#: src/components/Modtools.jsx:492 msgid "Rollback to Date" msgstr "" -#: src/components/Modtools.jsx:400 +#: src/components/Modtools.jsx:494 msgid "Rollback an area of the canvas to a set date (00:00 UTC)" msgstr "" -#: src/components/Modtools.jsx:475 +#: src/components/Modtools.jsx:567 +msgid "Canvas Cleaner" +msgstr "" + +#: src/components/Modtools.jsx:569 +msgid "Apply a filter to clean trash in large canvas areas." +msgstr "" + +#: src/components/Modtools.jsx:671 +msgid "Stop Cleaner" +msgstr "" + +#: src/components/Modtools.jsx:678 msgid "IP Actions" msgstr "" -#: src/components/Modtools.jsx:477 +#: src/components/Modtools.jsx:680 msgid "Do stuff with IPs (one IP per line)" msgstr "" -#: src/components/Modtools.jsx:516 +#: src/components/Modtools.jsx:719 msgid "Manage Moderators" msgstr "" -#: src/components/Modtools.jsx:518 +#: src/components/Modtools.jsx:721 msgid "Remove Moderator" msgstr "" -#: src/components/Modtools.jsx:550 +#: src/components/Modtools.jsx:753 msgid "There are no mods" msgstr "" -#: src/components/Modtools.jsx:555 +#: src/components/Modtools.jsx:758 msgid "Assign new Mod" msgstr "" -#: src/components/Modtools.jsx:558 +#: src/components/Modtools.jsx:761 msgid "Enter UserName of new Mod" msgstr "" -#: src/components/Modtools.jsx:567 +#: src/components/Modtools.jsx:770 msgid "User Name" msgstr "" @@ -1111,42 +1136,49 @@ msgstr "" msgid "Download Template" msgstr "" -#: src/components/LogInArea.jsx:21 -msgid "Login to access more features and stats." +#: src/components/CanvasItem.jsx:30 +msgid "Online Users" msgstr "" -#: src/components/LogInArea.jsx:23 -msgid "Login with Name or Mail:" +#: src/components/CanvasItem.jsx:35 +msgid "Cooldown" msgstr "" -#: src/components/LogInArea.jsx:30 -msgid "I forgot my Password." +#: src/components/CanvasItem.jsx:41 +msgid "Stacking till" msgstr "" -#: src/components/LogInArea.jsx:31 -msgid "or login with:" +#: src/components/CanvasItem.jsx:43 +msgid "Ranked" msgstr "" -#: src/components/LogInArea.jsx:72 -msgid "or register here:" +#: src/components/CanvasItem.jsx:45 +msgid "Yes" msgstr "" -#: src/components/LogInArea.jsx:79 -msgid "Register" +#: src/components/CanvasItem.jsx:45 +msgid "No" msgstr "" -#: src/components/UserMessages.jsx:28 -msgid "" -"Please verify your mail address \n" -"or your account could get deleted after a few days." +#: src/components/CanvasItem.jsx:51 +msgid "Requirements" msgstr "" -#: src/components/UserMessages.jsx:49 -msgid "A new verification mail is getting sent to you." +#: src/components/CanvasItem.jsx:54 +msgid "User Account" msgstr "" -#: src/components/UserMessages.jsx:53 -msgid "Click here to request a new verification mail." +#: src/components/CanvasItem.jsx:56 +#, javascript-format +msgid "and ${ canvas.req } Pixels set" +msgstr "" + +#: src/components/CanvasItem.jsx:59 +msgid "Top 10 Daily Ranking" +msgstr "" + +#: src/components/CanvasItem.jsx:65 +msgid "Dimensions" msgstr "" #: src/components/ChangePassword.jsx:22 @@ -1173,6 +1205,28 @@ msgstr "" msgid "New Username" msgstr "" +#: src/components/UserMessages.jsx:28 +msgid "" +"Please verify your mail address \n" +"or your account could get deleted after a few days." +msgstr "" + +#: src/components/UserMessages.jsx:49 +msgid "A new verification mail is getting sent to you." +msgstr "" + +#: src/components/UserMessages.jsx:53 +msgid "Click here to request a new verification mail." +msgstr "" + +#: src/components/LogInForm.jsx:76 +msgid "Name or Email" +msgstr "" + +#: src/components/LogInForm.jsx:87 +msgid "LogIn" +msgstr "" + #: src/components/ChangeMail.jsx:59 msgid "" "Changed Mail successfully. We sent you a verification mail, " @@ -1183,6 +1237,10 @@ msgstr "" msgid "New Mail" msgstr "" +#: src/components/DeleteAccount.jsx:66 +msgid "Yes, Delete My Account!" +msgstr "" + #: src/components/SocialSettings.jsx:38 msgid "Block all Private Messages" msgstr "" @@ -1195,18 +1253,6 @@ msgstr "" msgid "You have no users blocked" msgstr "" -#: src/components/DeleteAccount.jsx:66 -msgid "Yes, Delete My Account!" -msgstr "" - -#: src/components/LogInForm.jsx:76 -msgid "Name or Email" -msgstr "" - -#: src/components/LogInForm.jsx:87 -msgid "LogIn" -msgstr "" - #: src/components/windows/Help.jsx:14 #: src/components/windows/Settings.jsx:134 msgctxt "keybinds" diff --git a/src/core/Tile.js b/src/core/Tile.js index ddda0b5..fcf407a 100644 --- a/src/core/Tile.js +++ b/src/core/Tile.js @@ -1,14 +1,17 @@ /* - * * basic functions for creating zommed tiles + * basic functions for creating zommed tiles + * Used by tilewriter worker thread, so dont import too much. + * * */ +/* eslint-disable no-console */ + // Tile creation is allowed to be slow /* eslint-disable no-await-in-loop */ import sharp from 'sharp'; import fs from 'fs'; -import logger from './logger'; import { getMaxTiledZoom } from './utils'; import { TILE_SIZE, TILE_ZOOM_LEVEL } from './constants'; @@ -171,7 +174,7 @@ export async function createZoomTileFromChunk( .resize(TILE_SIZE) .png({ options: { compressionLevel: 6 } }) .toFile(filename); - logger.info( + console.log( // eslint-disable-next-line max-len `Tiling: Created Tile ${filename} with ${na.length} empty chunks in ${Date.now() - startTime}ms`, ); @@ -228,7 +231,7 @@ export async function createZoomedTile( }, }, ).resize(TILE_SIZE).toFile(filename); - logger.info( + console.log( // eslint-disable-next-line max-len `Tiling: Created tile ${filename} with ${na.length} empty subtiles in ${Date.now() - startTime}ms.`, ); @@ -269,7 +272,7 @@ export async function createEmptyTile( }) .png({ options: { compressionLevel: 6 } }) .toFile(filename); - logger.info(`Tiling: Created empty tile at ${filename}`); + console.log(`Tiling: Created empty tile at ${filename}`); } /* @@ -343,7 +346,7 @@ export async function createTexture( }, }, ).toFile(filename); - logger.info( + console.log( `Tiling: Created texture in ${(Date.now() - startTime) / 1000}s.`, ); } @@ -365,7 +368,7 @@ export async function initializeTiles( palette, force: boolean = false, ) { - logger.info( + console.log( `Tiling: Initializing tiles in ${canvasTileFolder}, forceint = ${force}`, ); const startTime = Date.now(); @@ -375,7 +378,7 @@ export async function initializeTiles( // base zoomlevel let zoom = maxTiledZoom - 1; let zoomDir = `${canvasTileFolder}/${zoom}`; - logger.info(`Tiling: Checking zoomlevel ${zoomDir}`); + console.log(`Tiling: Checking zoomlevel ${zoomDir}`); if (!fs.existsSync(zoomDir)) fs.mkdirSync(zoomDir); let cnt = 0; let cnts = 0; @@ -399,7 +402,7 @@ export async function initializeTiles( } } } - logger.info( + console.log( `Tiling: Created ${cnts} / ${cnt} tiles for basezoom of canvas${canvasId}`, ); // zoomlevels that are created from other zoomlevels @@ -407,7 +410,7 @@ export async function initializeTiles( cnt = 0; cnts = 0; zoomDir = `${canvasTileFolder}/${zoom}`; - logger.info(`Tiling: Checking zoomlevel ${zoomDir}`); + console.log(`Tiling: Checking zoomlevel ${zoomDir}`); if (!fs.existsSync(zoomDir)) fs.mkdirSync(zoomDir); const maxZ = TILE_ZOOM_LEVEL ** zoom; for (let cx = 0; cx < maxZ; cx += 1) { @@ -426,7 +429,7 @@ export async function initializeTiles( } } } - logger.info( + console.log( // eslint-disable-next-line max-len `Tiling: Created ${cnts} / ${cnt} tiles for zoom ${zoom} for canvas${canvasId}`, ); @@ -440,7 +443,7 @@ export async function initializeTiles( palette, ); //-- - logger.info( + console.log( // eslint-disable-next-line max-len `Tiling: Elapsed Time: ${Math.round((Date.now() - startTime) / 1000)} for canvas${canvasId}`, ); diff --git a/src/core/draw.js b/src/core/draw.js index a1d36b4..8288131 100644 --- a/src/core/draw.js +++ b/src/core/draw.js @@ -1,9 +1,7 @@ /* * draw pixel on canvas */ -import { using } from 'bluebird'; -import { redlock } from '../data/redis'; import { getPixelFromChunkOffset, } from './utils'; @@ -375,76 +373,3 @@ export async function drawByCoords( coolDownSeconds: coolDown / 1000, }; } - - -/** - * This function is a wrapper for draw. It fixes race condition exploits - * It permits just placing one pixel at a time per user. - * - * @param user - * @param canvasId - * @param color - * @param x - * @param y - * @param z (optional for 3d canvas) - */ -export function drawSafeByCoords( - user, - canvasId, - color, - x, - y, - z = null, -) { - // can just check for one unique occurence, - // we use ip, because id for logged out users is - // always null - const userId = user.ip; - - return new Promise((resolve) => { - using( - redlock.disposer(`locks:${userId}`, 5000, logger.error), - async () => { - const ret = await drawByCoords(user, canvasId, color, x, y, z); - resolve(ret); - }, - ); // <-- unlock is automatically handled by bluebird - }); -} - - -/** - * This function is a wrapper for draw. It fixes race condition exploits - * It permits just placing one pixel at a time per user. - * - * @param user - * @param canvasId - * @param i Chunk coordinates - * @param j - * @param pixels Array of indiviual pixels within the chunk, with: - * [[offset, color], [offset2, color2],...] - * Offset is the offset of the pixel within the chunk - * @return Promise - */ -export function drawSafeByOffsets( - user, - canvasId, - i, - j, - pixels, -) { - // can just check for one unique occurence, - // we use ip, because id for logged out users is - // always null - const userId = user.ip; - - return new Promise((resolve) => { - using( - redlock.disposer(`locks:${userId}`, 5000, logger.error), - async () => { - const ret = await drawByOffsets(user, canvasId, i, j, pixels); - resolve(ret); - }, - ); // <-- unlock is automatically handled by bluebird - }); -} diff --git a/src/core/tileserver.js b/src/core/tileserver.js index 2f72f3a..55dbb7f 100644 --- a/src/core/tileserver.js +++ b/src/core/tileserver.js @@ -1,5 +1,5 @@ /* - * creation of tiles + * creation of zoom tiles * */ @@ -16,18 +16,16 @@ import { TILE_SIZE, TILE_ZOOM_LEVEL, } from './constants'; -import { - createZoomTileFromChunk, - createZoomedTile, - createTexture, - initializeTiles, -} from './Tile'; import { mod, getMaxTiledZoom } from './utils'; -// Array that holds cells of all changed base zoomlevel tiles const CanvasUpdaters = {}; +/* + * worker thread + */ +const worker = new Worker('./workers/tilewriter.js'); + class CanvasUpdater { TileLoadingQueues; palette; @@ -63,30 +61,37 @@ class CanvasUpdater { const cy = Math.floor(tile / width); if (zoom === this.maxTiledZoom - 1) { - await createZoomTileFromChunk( - RedisCanvas, - this.canvas.size, - this.id, - this.canvasTileFolder, - this.palette, - [cx, cy], - ); + worker.postMessage({ + task: 'createZoomTileFromChunk', + args: [ + this.canvas.size, + this.id, + this.canvasTileFolder, + this.palette, + [cx, cy], + ], + }); } else if (zoom !== this.maxTiledZoom) { - await createZoomedTile( - this.canvasTileFolder, - this.palette, - [zoom, cx, cy], - ); + worker.postMessage({ + task: 'createZoomedTile', + args: [ + this.canvasTileFolder, + this.palette, + [zoom, cx, cy], + ], + }); } if (zoom === 0) { - createTexture( - RedisCanvas, - this.id, - this.canvas.size, - this.canvasTileFolder, - this.palette, - ); + worker.postMessage({ + task: 'createTexture', + args: [ + this.id, + this.canvas.size, + this.canvasTileFolder, + this.palette, + ], + }); } else { const [ucx, ucy] = [cx, cy].map((z) => Math.floor(z / 4)); const upperTile = ucx + ucy * (TILE_ZOOM_LEVEL ** (zoom - 1)); @@ -115,6 +120,28 @@ class CanvasUpdater { ); } + initializeTiles() { + return new Promise((resolve) => { + worker.postMessage({ + task: 'initializeTiles', + args: [ + this.canvas.size, + this.id, + this.canvasTileFolder, + this.palette, + false, + ], + }); + worker.once('message', (msg) => { + logger.info( + // eslint-disable-next-line max-len + `Tiling: Worker thread finished initializing Tiles with message ${msg}`, + ); + resolve(); + }); + }); + } + /* * initialize queues and start loops for updating tiles */ @@ -127,14 +154,7 @@ class CanvasUpdater { logger.warn( 'Tiling: tiledir empty, will initialize it, this can take some time', ); - await initializeTiles( - RedisCanvas, - this.canvas.size, - this.id, - this.canvasTileFolder, - this.palette, - false, - ); + await this.initializeTiles(); } for (let c = 0; c < this.maxTiledZoom; c += 1) { this.TileLoadingQueues.push([]); diff --git a/src/data/models/RedisCanvas.js b/src/data/models/RedisCanvas.js index c19e0c2..00eae7e 100644 --- a/src/data/models/RedisCanvas.js +++ b/src/data/models/RedisCanvas.js @@ -8,7 +8,6 @@ import { } from '../../core/constants'; // eslint-disable-next-line import/no-unresolved import canvases from './canvases.json'; -import logger from '../../core/logger'; import redis from '../redis'; @@ -48,7 +47,8 @@ class RedisCanvas { static async setChunk(i: number, j: number, chunk: Uint8Array, canvasId: number) { if (chunk.length !== TILE_SIZE * TILE_SIZE) { - logger.error(`Tried to set chunk with invalid length ${chunk.length}!`); + // eslint-disable-next-line no-console + console.error(`Tried to set chunk with invalid length ${chunk.length}!`); return false; } const key = `ch:${canvasId}:${i}:${j}`; diff --git a/src/workers/tilewriter.js b/src/workers/tilewriter.js new file mode 100644 index 0000000..dab48ba --- /dev/null +++ b/src/workers/tilewriter.js @@ -0,0 +1,42 @@ +/* + * worker thread for ..core/tileserver.js + */ + +/* eslint-disable no-console */ + +import { isMainThread, parentPort } from 'worker_threads'; +import RedisCanvas from '../data/models/RedisCanvas'; + +import { + createZoomTileFromChunk, + createZoomedTile, + createTexture, + initializeTiles, +} from '../core/Tile'; + +if (isMainThread) { + throw new Error( + 'Tilewriter is run as a worker thread, not as own process', + ); +} + +parentPort.on('message', async (msg) => { + const { task, args } = msg; + switch (task) { + case 'createZoomTileFromChunk': + createZoomTileFromChunk(RedisCanvas, ...args); + break; + case 'createZoomedTile': + createZoomedTile(...args); + break; + case 'createTexture': + createTexture(RedisCanvas, ...args); + break; + case 'initializeTiles': + await initializeTiles(RedisCanvas, ...args); + parentPort.postMessage('Done!'); + break; + default: + console.warn(`Tiling: Main thread requested unknown task ${task}`); + } +}); diff --git a/webpack.config.server.babel.js b/webpack.config.server.babel.js index 3ee18dc..1949e1a 100644 --- a/webpack.config.server.babel.js +++ b/webpack.config.server.babel.js @@ -51,6 +51,7 @@ export default ({ entry: { server: [path.resolve(__dirname, 'src', 'server.js')], backup: [path.resolve(__dirname, 'src', 'backup.js')], + 'workers/tilewriter': [path.resolve(__dirname, 'src', 'workers', 'tilewriter.js')], captchaserver: [path.resolve(__dirname, 'src', 'captchaserver.js')], },