diff --git a/.gitignore b/.gitignore
index 0caf84d0..dd42adfe 100644
--- a/.gitignore
+++ b/.gitignore
@@ -4,7 +4,7 @@ output.webm
utils/ocean-tiles/ocean
utils/osm-tiles/osm
i18n/*.mo
-test.js
+test*.js
logs
*.log
diff --git a/src/components/Admintools.jsx b/src/components/Admintools.jsx
new file mode 100644
index 00000000..ff019fd1
--- /dev/null
+++ b/src/components/Admintools.jsx
@@ -0,0 +1,244 @@
+/*
+ * Admintools
+ */
+
+import React, { useState, useEffect } from 'react';
+import { t } from 'ttag';
+
+async function submitIPAction(
+ action,
+ callback,
+) {
+ const data = new FormData();
+ const iplist = document.getElementById('iparea').value;
+ data.append('ip', iplist);
+ data.append('ipaction', action);
+ const resp = await fetch('./api/modtools', {
+ credentials: 'include',
+ method: 'POST',
+ body: data,
+ });
+ callback(await resp.text());
+}
+
+async function getModList(
+ callback,
+) {
+ const data = new FormData();
+ data.append('modlist', true);
+ const resp = await fetch('./api/modtools', {
+ credentials: 'include',
+ method: 'POST',
+ body: data,
+ });
+ if (resp.ok) {
+ callback(await resp.json());
+ } else {
+ callback([]);
+ }
+}
+
+async function submitRemMod(
+ userId,
+ callback,
+) {
+ const data = new FormData();
+ data.append('remmod', userId);
+ const resp = await fetch('./api/modtools', {
+ credentials: 'include',
+ method: 'POST',
+ body: data,
+ });
+ callback(resp.ok, await resp.text());
+}
+
+async function submitMakeMod(
+ userName,
+ callback,
+) {
+ const data = new FormData();
+ data.append('makemod', userName);
+ const resp = await fetch('./api/modtools', {
+ credentials: 'include',
+ method: 'POST',
+ body: data,
+ });
+ if (resp.ok) {
+ callback(await resp.json());
+ } else {
+ callback(await resp.text());
+ }
+}
+
+
+function Admintools() {
+ const [iPAction, selectIPAction] = useState('ban');
+ const [modName, selectModName] = useState('');
+ const [resp, setResp] = useState(null);
+ const [modlist, setModList] = useState([]);
+ const [submitting, setSubmitting] = useState(false);
+
+ useEffect(() => {
+ getModList((mods) => setModList(mods));
+ }, []);
+
+ return (
+
+ {resp && (
+
+ {resp.split('\n').map((line) => (
+
+ {line}
+
+ ))}
+
setResp(null)}
+ >
+ {t`Close`}
+
+
+ )}
+
+
+
+
{t`IP Actions`}
+
+ {t`Do stuff with IPs (one IP per line)`}
+
+
+
+
+
+
+
+
{t`Manage Moderators`}
+
+ {t`Remove Moderator`}
+
+ {(modlist.length) ? (
+
+ {modlist.map((mod) => (
+ {
+ if (submitting) {
+ return;
+ }
+ setSubmitting(true);
+ submitRemMod(mod[0], (success, ret) => {
+ if (success) {
+ setModList(
+ modlist.filter((modl) => (modl[0] !== mod[0])),
+ );
+ }
+ setSubmitting(false);
+ setResp(ret);
+ });
+ }}
+ >
+ {`⦸ ${mod[0]} ${mod[1]}`}
+
+ ))}
+
+ )
+ : (
+
{t`There are no mods`}
+ )}
+
+
+
+ {t`Assign new Mod`}
+
+
+ {t`Enter UserName of new Mod`}:
+ {
+ const co = evt.target.value.trim();
+ selectModName(co);
+ }}
+ />
+
+
+
+
+
+
+
+ );
+}
+
+export default React.memo(Admintools);
diff --git a/src/components/ModCanvastools.jsx b/src/components/ModCanvastools.jsx
new file mode 100644
index 00000000..1a95dae5
--- /dev/null
+++ b/src/components/ModCanvastools.jsx
@@ -0,0 +1,598 @@
+/*
+ * ModCanvastools
+ */
+
+import React, { useState, useEffect } from 'react';
+import { useSelector, shallowEqual } from 'react-redux';
+import { t } from 'ttag';
+
+import useInterval from './hooks/interval';
+import { getToday, dateToString } from '../core/utils';
+
+const keptState = {
+ coords: '',
+ tlcoords: '',
+ brcoords: '',
+ tlrcoords: '',
+ brrcoords: '',
+ tlccoords: '',
+ brccoords: '',
+};
+
+async function submitImageAction(
+ action,
+ canvas,
+ coords,
+ callback,
+) {
+ const data = new FormData();
+ const fileSel = document.getElementById('imgfile');
+ const file = (!fileSel.files || !fileSel.files[0])
+ ? null : fileSel.files[0];
+ data.append('imageaction', action);
+ data.append('image', file);
+ data.append('canvasid', canvas);
+ data.append('coords', coords);
+ const resp = await fetch('./api/modtools', {
+ credentials: 'include',
+ method: 'POST',
+ body: data,
+ });
+ callback(await resp.text());
+}
+
+async function submitProtAction(
+ action,
+ canvas,
+ tlcoords,
+ brcoords,
+ callback,
+) {
+ const data = new FormData();
+ data.append('protaction', action);
+ data.append('canvasid', canvas);
+ data.append('ulcoor', tlcoords);
+ data.append('brcoor', brcoords);
+ const resp = await fetch('./api/modtools', {
+ credentials: 'include',
+ method: 'POST',
+ body: data,
+ });
+ callback(await resp.text());
+}
+
+async function submitRollback(
+ date,
+ canvas,
+ tlcoords,
+ brcoords,
+ callback,
+) {
+ const data = new FormData();
+ const timeString = dateToString(date);
+ data.append('rollback', timeString);
+ data.append('canvasid', canvas);
+ data.append('ulcoor', tlcoords);
+ data.append('brcoor', brcoords);
+ const resp = await fetch('./api/modtools', {
+ credentials: 'include',
+ method: 'POST',
+ body: data,
+ });
+ callback(await resp.text());
+}
+
+async function submitCanvasCleaner(
+ action,
+ canvas,
+ tlcoords,
+ brcoords,
+ callback,
+) {
+ const data = new FormData();
+ data.append('cleaneraction', action);
+ data.append('canvasid', canvas);
+ data.append('ulcoor', tlcoords);
+ data.append('brcoor', brcoords);
+ const resp = await fetch('./api/modtools', {
+ credentials: 'include',
+ method: 'POST',
+ body: data,
+ });
+ callback(await resp.text());
+}
+
+async function getCleanerStats(
+ callback,
+) {
+ const data = new FormData();
+ data.append('cleanerstat', true);
+ const resp = await fetch('./api/modtools', {
+ credentials: 'include',
+ method: 'POST',
+ body: data,
+ });
+ if (resp.ok) {
+ callback(await resp.json());
+ } else {
+ callback({
+ });
+ }
+}
+
+async function getCleanerCancel(
+ callback,
+) {
+ const data = new FormData();
+ data.append('cleanercancel', true);
+ const resp = await fetch('./api/modtools', {
+ credentials: 'include',
+ method: 'POST',
+ body: data,
+ });
+ if (resp.ok) {
+ callback(await resp.text());
+ } else {
+ callback('');
+ }
+}
+
+function ModCanvastools() {
+ const maxDate = getToday();
+
+ const [selectedCanvas, selectCanvas] = useState(0);
+ const [imageAction, selectImageAction] = useState('build');
+ const [cleanAction, selectCleanAction] = useState('spare');
+ const [protAction, selectProtAction] = useState('protect');
+ const [date, selectDate] = useState(maxDate);
+ const [coords, selectCoords] = useState(keptState.coords);
+ const [tlcoords, selectTLCoords] = useState(keptState.tlcoords);
+ const [brcoords, selectBRCoords] = useState(keptState.brcoords);
+ const [tlrcoords, selectTLRCoords] = useState(keptState.tlrcoords);
+ const [brrcoords, selectBRRCoords] = useState(keptState.brrcoords);
+ const [tlccoords, selectTLCCoords] = useState(keptState.tlrcoords);
+ const [brccoords, selectBRCCoords] = useState(keptState.brrcoords);
+ const [resp, setResp] = useState(null);
+ const [cleanerstats, setCleanerStats] = useState({});
+ const [submitting, setSubmitting] = useState(false);
+
+ const [
+ canvasId,
+ canvases,
+ ] = useSelector((state) => [
+ state.canvas.canvasId,
+ state.canvas.canvases,
+ ], shallowEqual);
+
+ useEffect(() => {
+ selectCanvas(canvasId);
+ }, [canvasId]);
+
+ let descAction;
+ switch (imageAction) {
+ case 'build':
+ descAction = t`Build image on canvas.`;
+ break;
+ case 'protect':
+ descAction = t`Build image and set it to protected.`;
+ break;
+ case 'wipe':
+ descAction = t`Build image, but reset cooldown to unset-pixel cd.`;
+ break;
+ default:
+ // nothing
+ }
+
+ let descCleanAction;
+ switch (cleanAction) {
+ case 'spare':
+ // eslint-disable-next-line max-len
+ descCleanAction = t`Clean spare pixels that are surrounded by unset pixels`;
+ break;
+ case 'spareext':
+ // eslint-disable-next-line max-len
+ descCleanAction = t`Clean spare pixels that are surrounded by unset pixels and up to 1 other set pixels`;
+ break;
+ case 'spareextu':
+ // eslint-disable-next-line max-len
+ descCleanAction = t`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)!)`;
+ break;
+ default:
+ // nothing
+ }
+
+ useEffect(() => {
+ getCleanerStats((stats) => setCleanerStats(stats));
+ }, []);
+
+ useInterval(() => {
+ getCleanerStats((stats) => setCleanerStats(stats));
+ }, 10000);
+
+ const cleanerStatusString = (!cleanerstats.running)
+ ? t`Status: Not running`
+ // eslint-disable-next-line max-len
+ : `Status: ${cleanerstats.method} from ${cleanerstats.tl} to ${cleanerstats.br} on canvas ${canvases[cleanerstats.canvasId].ident} to ${cleanerstats.percent} done`;
+
+ return (
+
+ {resp && (
+
+ {resp.split('\n').map((line) => (
+
+ {line}
+
+ ))}
+
setResp(null)}
+ >
+ {t`Close`}
+
+
+ )}
+
Choose Canvas:
+
+
+
+
+
{t`Image Upload`}
+
{t`Upload images to canvas`}
+
+ {t`File`}:
+
+
+
+
{descAction}
+
+ {t`Coordinates in X_Y format:`}
+ {
+ const co = evt.target.value.trim();
+ selectCoords(co);
+ keptState.coords = co;
+ }}
+ />
+
+
+
+
+
+
{t`Pixel Protection`}
+
+ {t`Set protection of areas \
+ (if you need finer grained control, \
+ use protect with image upload and alpha layers)`}
+
+
+
+ {t`Top-left corner`} (X_Y):
+ {
+ const co = evt.target.value.trim();
+ selectTLCoords(co);
+ keptState.tlcoords = co;
+ }}
+ />
+
+
+ {t`Bottom-right corner`} (X_Y):
+ {
+ const co = evt.target.value.trim();
+ selectBRCoords(co);
+ keptState.brcoords = co;
+ }}
+ />
+
+
+ {(window.ssv && window.ssv.backupurl) && (
+
+
+
+
{t`Rollback to Date`}
+
+ {t`Rollback an area of the canvas to a set date (00:00 UTC)`}
+
+
{
+ selectDate(evt.target.value);
+ }}
+ />
+
+ {t`Top-left corner`} (X_Y):
+ {
+ const co = evt.target.value.trim();
+ selectTLRCoords(co);
+ keptState.tlrcoords = co;
+ }}
+ />
+
+
+ {t`Bottom-right corner`} (X_Y):
+ {
+ const co = evt.target.value.trim();
+ selectBRRCoords(co);
+ keptState.brrcoords = co;
+ }}
+ />
+
+
+
+ )}
+
+
+
{t`Canvas Cleaner`}
+
+ {t`Apply a filter to clean trash in large canvas areas.`}
+
+
+
{descCleanAction}
+
+ {cleanerStatusString}
+
+
+ {t`Top-left corner`} (X_Y):
+ {
+ const co = evt.target.value.trim();
+ selectTLCCoords(co);
+ keptState.tlccoords = co;
+ }}
+ />
+
+
+ {t`Bottom-right corner`} (X_Y):
+ {
+ const co = evt.target.value.trim();
+ selectBRCCoords(co);
+ keptState.brccoords = co;
+ }}
+ />
+
+
+
+
+ );
+}
+
+export default React.memo(ModCanvastools);
diff --git a/src/components/Modtools.jsx b/src/components/Modtools.jsx
index 81c042ed..2da77687 100644
--- a/src/components/Modtools.jsx
+++ b/src/components/Modtools.jsx
@@ -2,806 +2,47 @@
* Modtools
*/
-import React, { useState, useEffect } from 'react';
-import { useSelector, shallowEqual } from 'react-redux';
-import { t } from 'ttag';
+import React, { useState } from 'react';
+import { useSelector } from 'react-redux';
-import useInterval from './hooks/interval';
-import { getToday, dateToString } from '../core/utils';
+import ModCanvastools from './ModCanvastools';
+import Admintools from './Admintools';
-const keptState = {
- coords: '',
- tlcoords: '',
- brcoords: '',
- tlrcoords: '',
- brrcoords: '',
- tlccoords: '',
- brccoords: '',
+
+const CONTENT = {
+ Canvas: ModCanvastools,
+ Admin: Admintools,
};
-async function submitImageAction(
- action,
- canvas,
- coords,
- callback,
-) {
- const data = new FormData();
- const fileSel = document.getElementById('imgfile');
- const file = (!fileSel.files || !fileSel.files[0])
- ? null : fileSel.files[0];
- data.append('imageaction', action);
- data.append('image', file);
- data.append('canvasid', canvas);
- data.append('coords', coords);
- const resp = await fetch('./api/modtools', {
- credentials: 'include',
- method: 'POST',
- body: data,
- });
- callback(await resp.text());
-}
-
-async function submitProtAction(
- action,
- canvas,
- tlcoords,
- brcoords,
- callback,
-) {
- const data = new FormData();
- data.append('protaction', action);
- data.append('canvasid', canvas);
- data.append('ulcoor', tlcoords);
- data.append('brcoor', brcoords);
- const resp = await fetch('./api/modtools', {
- credentials: 'include',
- method: 'POST',
- body: data,
- });
- callback(await resp.text());
-}
-
-async function submitRollback(
- date,
- canvas,
- tlcoords,
- brcoords,
- callback,
-) {
- const data = new FormData();
- const timeString = dateToString(date);
- data.append('rollback', timeString);
- data.append('canvasid', canvas);
- data.append('ulcoor', tlcoords);
- data.append('brcoor', brcoords);
- const resp = await fetch('./api/modtools', {
- credentials: 'include',
- method: 'POST',
- body: data,
- });
- callback(await resp.text());
-}
-
-async function submitCanvasCleaner(
- action,
- canvas,
- tlcoords,
- brcoords,
- callback,
-) {
- const data = new FormData();
- data.append('cleaneraction', action);
- data.append('canvasid', canvas);
- data.append('ulcoor', tlcoords);
- data.append('brcoor', brcoords);
- const resp = await fetch('./api/modtools', {
- credentials: 'include',
- method: 'POST',
- body: data,
- });
- callback(await resp.text());
-}
-
-async function submitIPAction(
- action,
- callback,
-) {
- const data = new FormData();
- const iplist = document.getElementById('iparea').value;
- data.append('ip', iplist);
- data.append('ipaction', action);
- const resp = await fetch('./api/modtools', {
- credentials: 'include',
- method: 'POST',
- body: data,
- });
- callback(await resp.text());
-}
-
-async function getModList(
- callback,
-) {
- const data = new FormData();
- data.append('modlist', true);
- const resp = await fetch('./api/modtools', {
- credentials: 'include',
- method: 'POST',
- body: data,
- });
- if (resp.ok) {
- callback(await resp.json());
- } else {
- callback([]);
- }
-}
-
-async function getCleanerStats(
- callback,
-) {
- const data = new FormData();
- data.append('cleanerstat', true);
- const resp = await fetch('./api/modtools', {
- credentials: 'include',
- method: 'POST',
- body: data,
- });
- if (resp.ok) {
- callback(await resp.json());
- } else {
- callback({
- });
- }
-}
-
-async function getCleanerCancel(
- callback,
-) {
- const data = new FormData();
- data.append('cleanercancel', true);
- const resp = await fetch('./api/modtools', {
- credentials: 'include',
- method: 'POST',
- body: data,
- });
- if (resp.ok) {
- callback(await resp.text());
- } else {
- callback('');
- }
-}
-
-async function submitRemMod(
- userId,
- callback,
-) {
- const data = new FormData();
- data.append('remmod', userId);
- const resp = await fetch('./api/modtools', {
- credentials: 'include',
- method: 'POST',
- body: data,
- });
- callback(resp.ok, await resp.text());
-}
-
-async function submitMakeMod(
- userName,
- callback,
-) {
- const data = new FormData();
- data.append('makemod', userName);
- const resp = await fetch('./api/modtools', {
- credentials: 'include',
- method: 'POST',
- body: data,
- });
- if (resp.ok) {
- callback(await resp.json());
- } else {
- callback(await resp.text());
- }
-}
-
-
function Modtools() {
- const maxDate = getToday();
+ const [selectedPart, selectPart] = useState('canvas');
- const [selectedCanvas, selectCanvas] = useState(0);
- const [imageAction, selectImageAction] = useState('build');
- const [cleanAction, selectCleanAction] = useState('spare');
- const [iPAction, selectIPAction] = useState('ban');
- const [protAction, selectProtAction] = useState('protect');
- const [date, selectDate] = useState(maxDate);
- const [coords, selectCoords] = useState(keptState.coords);
- const [tlcoords, selectTLCoords] = useState(keptState.tlcoords);
- const [brcoords, selectBRCoords] = useState(keptState.brcoords);
- const [tlrcoords, selectTLRCoords] = useState(keptState.tlrcoords);
- const [brrcoords, selectBRRCoords] = useState(keptState.brrcoords);
- const [tlccoords, selectTLCCoords] = useState(keptState.tlrcoords);
- const [brccoords, selectBRCCoords] = useState(keptState.brrcoords);
- const [modName, selectModName] = useState('');
- const [resp, setResp] = useState(null);
- const [modlist, setModList] = useState([]);
- const [cleanerstats, setCleanerStats] = useState({});
- const [submitting, setSubmitting] = useState(false);
+ const userlvl = useSelector((state) => state.user.userlvl);
- const [
- canvasId,
- canvases,
- userlvl,
- ] = useSelector((state) => [
- state.canvas.canvasId,
- state.canvas.canvases,
- state.user.userlvl,
- ], shallowEqual);
-
- useEffect(() => {
- selectCanvas(canvasId);
- }, [canvasId]);
-
- let descAction;
- switch (imageAction) {
- case 'build':
- descAction = t`Build image on canvas.`;
- break;
- case 'protect':
- descAction = t`Build image and set it to protected.`;
- break;
- case 'wipe':
- descAction = t`Build image, but reset cooldown to unset-pixel cd.`;
- break;
- default:
- // nothing
- }
-
- let descCleanAction;
- switch (cleanAction) {
- case 'spare':
- // eslint-disable-next-line max-len
- descCleanAction = t`Clean spare pixels that are surrounded by unset pixels`;
- break;
- case 'spareext':
- // eslint-disable-next-line max-len
- descCleanAction = t`Clean spare pixels that are surrounded by unset pixels and up to 1 other set pixels`;
- break;
- case 'spareextu':
- // eslint-disable-next-line max-len
- descCleanAction = t`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)!)`;
- break;
- default:
- // nothing
- }
-
- useEffect(() => {
- if (userlvl === 1) {
- getModList((mods) => setModList(mods));
- }
- if (userlvl > 0) {
- getCleanerStats((stats) => setCleanerStats(stats));
- }
- }, []);
-
- useInterval(() => {
- if (userlvl > 0) {
- getCleanerStats((stats) => setCleanerStats(stats));
- }
- }, 10000);
-
- const cleanerStatusString = (!cleanerstats.running)
- ? t`Status: Not running`
- // eslint-disable-next-line max-len
- : `Status: ${cleanerstats.method} from ${cleanerstats.tl} to ${cleanerstats.br} on canvas ${canvases[cleanerstats.canvasId].ident} to ${cleanerstats.percent} done`;
+ const Content = CONTENT[selectedPart];
return (
-
- {resp && (
-
- {resp.split('\n').map((line) => (
-
- {line}
-
- ))}
-
setResp(null)}
- >
- {t`Close`}
-
-
- )}
-
Choose Canvas:
-
-
-
-
-
{t`Image Upload`}
-
{t`Upload images to canvas`}
-
- {t`File`}:
-
-
-
-
{descAction}
-
- {t`Coordinates in X_Y format:`}
- {
- const co = evt.target.value.trim();
- selectCoords(co);
- keptState.coords = co;
- }}
- />
-
-
-
-
-
-
{t`Pixel Protection`}
-
- {t`Set protection of areas \
- (if you need finer grained control, \
- use protect with image upload and alpha layers)`}
-
-
-
- {t`Top-left corner`} (X_Y):
- {
- const co = evt.target.value.trim();
- selectTLCoords(co);
- keptState.tlcoords = co;
- }}
- />
-
-
- {t`Bottom-right corner`} (X_Y):
- {
- const co = evt.target.value.trim();
- selectBRCoords(co);
- keptState.brcoords = co;
- }}
- />
-
-
- {(window.ssv && window.ssv.backupurl) && (
-
-
-
-
{t`Rollback to Date`}
-
- {t`Rollback an area of the canvas to a set date (00:00 UTC)`}
-
-
{
- selectDate(evt.target.value);
- }}
- />
-
- {t`Top-left corner`} (X_Y):
- {
- const co = evt.target.value.trim();
- selectTLRCoords(co);
- keptState.tlrcoords = co;
- }}
- />
-
-
- {t`Bottom-right corner`} (X_Y):
- {
- const co = evt.target.value.trim();
- selectBRRCoords(co);
- keptState.brrcoords = co;
- }}
- />
-
-
-
- )}
-
-
-
{t`Canvas Cleaner`}
-
- {t`Apply a filter to clean trash in large canvas areas.`}
-
-
-
{descCleanAction}
-
- {cleanerStatusString}
-
-
- {t`Top-left corner`} (X_Y):
- {
- const co = evt.target.value.trim();
- selectTLCCoords(co);
- keptState.tlccoords = co;
- }}
- />
-
-
- {t`Bottom-right corner`} (X_Y):
- {
- const co = evt.target.value.trim();
- selectBRCCoords(co);
- keptState.brccoords = co;
- }}
- />
-
-
-
-
- {(userlvl === 1) && (
-
-
-
-
{t`IP Actions`}
-
- {t`Do stuff with IPs (one IP per line)`}
-
-
-
-
-
-
-
-
{t`Manage Moderators`}
-
- {t`Remove Moderator`}
-
- {(modlist.length) ? (
+ <>
+
+ {Object.keys(CONTENT)
+ .filter((part) => part !== 'Admin' || userlvl === 1)
+ .map((part) => (
- {modlist.map((mod) => (
- {
- if (submitting) {
- return;
- }
- setSubmitting(true);
- submitRemMod(mod[0], (success, ret) => {
- if (success) {
- setModList(
- modlist.filter((modl) => (modl[0] !== mod[0])),
- );
- }
- setSubmitting(false);
- setResp(ret);
- });
- }}
- >
- {`⦸ ${mod[0]} ${mod[1]}`}
-
- ))}
-
- )
- : (
-
{t`There are no mods`}
- )}
-
-
-
- {t`Assign new Mod`}
-
-
- {t`Enter UserName of new Mod`}:
- {
- const co = evt.target.value.trim();
- selectModName(co);
- }}
- />
-
-
-
-
-
-
- )}
-
+ onClick={() => selectPart(part)}
+ >{part}
+ ),
+ )}
+
+
+ {Content && }
+ >
);
}