2022-08-02 10:49:34 +00:00
/ *
* ModCanvastools
* /
import React , { useState , useEffect } from 'react' ;
import { useSelector , shallowEqual } from 'react-redux' ;
import { t } from 'ttag' ;
import useInterval from './hooks/interval' ;
2024-02-13 14:08:27 +00:00
import { getToday , dateToString , coordsFromString } from '../core/utils' ;
2022-09-09 22:35:28 +00:00
import { shardOrigin } from '../store/actions/fetch' ;
2022-08-02 10:49:34 +00:00
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 ) ;
2022-09-09 22:35:28 +00:00
const resp = await fetch ( ` ${ shardOrigin } /api/modtools ` , {
2022-08-02 10:49:34 +00:00
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 ) ;
2022-09-09 22:35:28 +00:00
const resp = await fetch ( ` ${ shardOrigin } /api/modtools ` , {
2022-08-02 10:49:34 +00:00
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 ) ;
2022-09-09 22:35:28 +00:00
const resp = await fetch ( ` ${ shardOrigin } /api/modtools ` , {
2022-08-02 10:49:34 +00:00
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 ) ;
2022-09-09 22:35:28 +00:00
const resp = await fetch ( ` ${ shardOrigin } /api/modtools ` , {
2022-08-02 10:49:34 +00:00
credentials : 'include' ,
method : 'POST' ,
body : data ,
} ) ;
callback ( await resp . text ( ) ) ;
}
async function getCleanerStats (
callback ,
) {
const data = new FormData ( ) ;
data . append ( 'cleanerstat' , true ) ;
2022-09-09 22:35:28 +00:00
const resp = await fetch ( ` ${ shardOrigin } /api/modtools ` , {
2022-08-02 10:49:34 +00:00
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 ) ;
2022-09-09 22:35:28 +00:00
const resp = await fetch ( ` ${ shardOrigin } /api/modtools ` , {
2022-08-02 10:49:34 +00:00
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 [ 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 ;
2024-02-23 21:27:45 +00:00
case 'makenull' :
// eslint-disable-next-line max-len
descCleanAction = t ` Turn every pixel in area to 0 (YOU REALLY SHOULDN'T DO THAT ON ANY AREA THAT ISN'T ALREADY MOSTLY 0) ` ;
2024-02-24 10:12:26 +00:00
break ;
2022-08-02 10:49:34 +00:00
default :
// nothing
}
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 (
2022-08-19 13:23:01 +00:00
< div className = "content" >
2022-08-02 10:49:34 +00:00
{ resp && (
2022-08-05 20:56:02 +00:00
< div className = "respbox" >
2022-08-02 10:49:34 +00:00
{ resp . split ( '\n' ) . map ( ( line ) => (
2022-08-15 20:28:41 +00:00
< p key = { line . slice ( 0 , 3 ) } >
2022-08-02 10:49:34 +00:00
{ line }
< / p >
) ) }
< span
role = "button"
tabIndex = { - 1 }
className = "modallink"
onClick = { ( ) => setResp ( null ) }
>
{ t ` Close ` }
< / span >
< / div >
) }
2022-08-15 20:28:41 +00:00
< p > { t ` Choose Canvas ` } : & nbsp ;
2022-08-02 10:49:34 +00:00
< select
value = { selectedCanvas }
onChange = { ( e ) => {
const sel = e . target ;
selectCanvas ( sel . options [ sel . selectedIndex ] . value ) ;
} }
>
2022-08-05 14:56:31 +00:00
{ Object . keys ( canvases ) . filter ( ( c ) => ! canvases [ c ] . v ) . map ( ( canvas ) => (
< option
key = { canvas }
value = { canvas }
>
{ canvases [ canvas ] . title }
< / option >
) ) }
2022-08-02 10:49:34 +00:00
< / select >
< / p >
< div className = "modaldivider" / >
2022-08-15 19:56:11 +00:00
< h3 > { t ` Image Upload ` } < / h3 >
2022-08-15 20:28:41 +00:00
< p > { t ` Upload images to canvas ` } < / p >
< p >
2022-08-02 10:49:34 +00:00
{ t ` File ` } : & nbsp ;
< input type = "file" name = "image" id = "imgfile" / >
< / p >
< select
value = { imageAction }
onChange = { ( e ) => {
const sel = e . target ;
selectImageAction ( sel . options [ sel . selectedIndex ] . value ) ;
} }
>
{ [ 'build' , 'protect' , 'wipe' ] . map ( ( opt ) => (
< option
2022-08-12 11:27:06 +00:00
key = { opt }
2022-08-02 10:49:34 +00:00
value = { opt }
>
{ opt }
< / option >
) ) }
< / select >
2022-08-15 20:28:41 +00:00
< p > { descAction } < / p >
< p >
2024-01-04 14:41:23 +00:00
{ t ` Coordinates: ` } & nbsp ;
2022-08-02 10:49:34 +00:00
< input
2022-08-19 13:34:56 +00:00
defaultValue = { keptState . coords }
2022-08-02 10:49:34 +00:00
style = { {
display : 'inline-block' ,
width : '100%' ,
maxWidth : '15em' ,
} }
type = "text"
2023-12-30 11:22:05 +00:00
placeholder = "X_Y or URL"
2022-08-02 10:49:34 +00:00
onChange = { ( evt ) => {
2023-12-30 11:22:05 +00:00
let co = evt . target . value . trim ( ) ;
2024-02-13 14:08:27 +00:00
co = coordsFromString ( co ) ;
if ( co ) {
co = co . join ( '_' ) ;
evt . target . value = co ;
}
2023-12-30 11:22:05 +00:00
keptState . coords = co ;
2022-08-02 10:49:34 +00:00
} }
/ >
< / p >
< button
type = "button"
onClick = { ( ) => {
if ( submitting ) {
return ;
}
setSubmitting ( true ) ;
submitImageAction (
imageAction ,
selectedCanvas ,
2022-08-19 13:34:56 +00:00
keptState . coords ,
2022-08-02 10:49:34 +00:00
( ret ) => {
setSubmitting ( false ) ;
setResp ( ret ) ;
} ,
) ;
} }
>
{ ( submitting ) ? '...' : t ` Submit ` }
< / button >
< br / >
< div className = "modaldivider" / >
2022-08-15 19:56:11 +00:00
< h3 > { t ` Pixel Protection ` } < / h3 >
2022-08-15 20:28:41 +00:00
< p >
2023-12-27 14:42:54 +00:00
{
// eslint-disable-next-line max-len
t ` Set protection of areas (if you need finer grained control, use protect with image upload and alpha layers) `
}
2022-08-02 10:49:34 +00:00
< / p >
< select
value = { protAction }
onChange = { ( e ) => {
const sel = e . target ;
selectProtAction ( sel . options [ sel . selectedIndex ] . value ) ;
} }
>
{ [ 'protect' , 'unprotect' ] . map ( ( opt ) => (
< option
2022-08-12 11:27:06 +00:00
key = { opt }
2022-08-02 10:49:34 +00:00
value = { opt }
>
{ opt }
< / option >
) ) }
< / select >
2022-08-15 20:28:41 +00:00
< p >
2023-12-30 11:22:05 +00:00
{ t ` Top-left corner ` } : & nbsp ;
2022-08-02 10:49:34 +00:00
< input
2022-08-19 13:34:56 +00:00
defaultValue = { keptState . tlcoords }
2022-08-02 10:49:34 +00:00
style = { {
display : 'inline-block' ,
width : '100%' ,
maxWidth : '15em' ,
} }
type = "text"
2023-12-30 11:22:05 +00:00
placeholder = "X_Y or URL"
2022-08-02 10:49:34 +00:00
onChange = { ( evt ) => {
2023-12-30 11:22:05 +00:00
let co = evt . target . value . trim ( ) ;
2024-02-13 14:08:27 +00:00
co = coordsFromString ( co ) ;
if ( co ) {
co = co . join ( '_' ) ;
evt . target . value = co ;
}
2022-08-02 10:49:34 +00:00
keptState . tlcoords = co ;
} }
/ >
< / p >
2022-08-15 20:28:41 +00:00
< p >
2023-12-30 11:22:05 +00:00
{ t ` Bottom-right corner ` } : & nbsp ;
2022-08-02 10:49:34 +00:00
< input
2022-08-19 13:34:56 +00:00
defaultValue = { keptState . brcoords }
2022-08-02 10:49:34 +00:00
style = { {
display : 'inline-block' ,
width : '100%' ,
maxWidth : '15em' ,
} }
type = "text"
2023-12-30 11:22:05 +00:00
placeholder = "X_Y or URL"
2022-08-02 10:49:34 +00:00
onChange = { ( evt ) => {
2023-12-30 11:22:05 +00:00
let co = evt . target . value . trim ( ) ;
2024-02-13 14:08:27 +00:00
co = coordsFromString ( co ) ;
if ( co ) {
co = co . join ( '_' ) ;
evt . target . value = co ;
}
2022-08-02 10:49:34 +00:00
keptState . brcoords = co ;
} }
/ >
< / p >
< button
type = "button"
onClick = { ( ) => {
if ( submitting ) {
return ;
}
setSubmitting ( true ) ;
submitProtAction (
protAction ,
selectedCanvas ,
2022-08-19 13:34:56 +00:00
keptState . tlcoords ,
keptState . brcoords ,
2022-08-02 10:49:34 +00:00
( ret ) => {
setSubmitting ( false ) ;
setResp ( ret ) ;
} ,
) ;
} }
>
{ ( submitting ) ? '...' : t ` Submit ` }
< / button >
{ ( window . ssv && window . ssv . backupurl ) && (
< div >
< br / >
< div className = "modaldivider" / >
2022-08-15 19:56:11 +00:00
< h3 > { t ` Rollback to Date ` } < / h3 >
2022-08-15 20:28:41 +00:00
< p >
2022-08-02 10:49:34 +00:00
{ t ` Rollback an area of the canvas to a set date (00:00 UTC) ` }
< / p >
< input
type = "date"
value = { date }
pattern = "\d{4}-\d{2}-\d{2}"
min = { canvases [ selectedCanvas ] . sd }
max = { maxDate }
onChange = { ( evt ) => {
selectDate ( evt . target . value ) ;
} }
/ >
2022-08-15 20:28:41 +00:00
< p >
2023-12-30 11:22:05 +00:00
{ t ` Top-left corner ` } : & nbsp ;
2022-08-02 10:49:34 +00:00
< input
2022-08-19 13:34:56 +00:00
defaultValue = { keptState . tlrcoords }
2022-08-02 10:49:34 +00:00
style = { {
display : 'inline-block' ,
width : '100%' ,
maxWidth : '15em' ,
} }
type = "text"
2023-12-30 11:22:05 +00:00
placeholder = "X_Y or URL"
2022-08-02 10:49:34 +00:00
onChange = { ( evt ) => {
2023-12-30 11:22:05 +00:00
let co = evt . target . value . trim ( ) ;
2024-02-13 14:08:27 +00:00
co = coordsFromString ( co ) ;
if ( co ) {
co = co . join ( '_' ) ;
evt . target . value = co ;
}
2022-08-02 10:49:34 +00:00
keptState . tlrcoords = co ;
} }
/ >
< / p >
2022-08-15 20:28:41 +00:00
< p >
2023-12-30 11:22:05 +00:00
{ t ` Bottom-right corner ` } : & nbsp ;
2022-08-02 10:49:34 +00:00
< input
2022-08-19 13:34:56 +00:00
defaultValue = { keptState . brrcoords }
2022-08-02 10:49:34 +00:00
style = { {
display : 'inline-block' ,
width : '100%' ,
maxWidth : '15em' ,
} }
type = "text"
2023-12-30 11:22:05 +00:00
placeholder = "X_Y or URL"
2022-08-02 10:49:34 +00:00
onChange = { ( evt ) => {
2023-12-30 11:22:05 +00:00
let co = evt . target . value . trim ( ) ;
2024-02-13 14:08:27 +00:00
co = coordsFromString ( co ) ;
if ( co ) {
co = co . join ( '_' ) ;
evt . target . value = co ;
}
2022-08-02 10:49:34 +00:00
keptState . brrcoords = co ;
} }
/ >
< / p >
< button
type = "button"
onClick = { ( ) => {
if ( submitting ) {
return ;
}
setSubmitting ( true ) ;
submitRollback (
date ,
selectedCanvas ,
2022-08-19 13:34:56 +00:00
keptState . tlrcoords ,
keptState . brrcoords ,
2022-08-02 10:49:34 +00:00
( ret ) => {
setSubmitting ( false ) ;
setResp ( ret ) ;
} ,
) ;
} }
>
{ ( submitting ) ? '...' : t ` Submit ` }
< / button >
< / div >
) }
< br / >
< div className = "modaldivider" / >
2022-08-15 19:56:11 +00:00
< h3 > { t ` Canvas Cleaner ` } < / h3 >
2022-08-15 20:28:41 +00:00
< p >
2022-08-02 10:49:34 +00:00
{ t ` Apply a filter to clean trash in large canvas areas. ` }
< / p >
< select
value = { cleanAction }
onChange = { ( e ) => {
const sel = e . target ;
selectCleanAction ( sel . options [ sel . selectedIndex ] . value ) ;
} }
>
2024-02-23 21:27:45 +00:00
{ [ 'spare' , 'spareext' , 'spareextu' , 'makenull' ] . map ( ( opt ) => (
2022-08-02 10:49:34 +00:00
< option
2022-08-12 11:27:06 +00:00
key = { opt }
2022-08-02 10:49:34 +00:00
value = { opt }
>
{ opt }
< / option >
) ) }
< / select >
2022-08-15 20:28:41 +00:00
< p > { descCleanAction } < / p >
< p style = { { fontWeight : 'bold' } } >
2022-08-02 10:49:34 +00:00
{ cleanerStatusString }
< / p >
2022-08-15 20:28:41 +00:00
< p >
2023-12-30 11:22:05 +00:00
{ t ` Top-left corner ` } : & nbsp ;
2022-08-02 10:49:34 +00:00
< input
2022-08-19 13:34:56 +00:00
defaultValue = { keptState . tlccoords }
2022-08-02 10:49:34 +00:00
style = { {
display : 'inline-block' ,
width : '100%' ,
maxWidth : '15em' ,
} }
type = "text"
2023-12-30 11:22:05 +00:00
placeholder = "X_Y or URL"
2022-08-02 10:49:34 +00:00
onChange = { ( evt ) => {
2023-12-30 11:22:05 +00:00
let co = evt . target . value . trim ( ) ;
2024-02-13 14:08:27 +00:00
co = coordsFromString ( co ) ;
if ( co ) {
co = co . join ( '_' ) ;
evt . target . value = co ;
}
2022-08-02 10:49:34 +00:00
keptState . tlccoords = co ;
} }
/ >
< / p >
2022-08-15 20:28:41 +00:00
< p >
2023-12-30 11:22:05 +00:00
{ t ` Bottom-right corner ` } : & nbsp ;
2022-08-02 10:49:34 +00:00
< input
2022-08-19 13:34:56 +00:00
defaultValue = { keptState . brccoords }
2022-08-02 10:49:34 +00:00
style = { {
display : 'inline-block' ,
width : '100%' ,
maxWidth : '15em' ,
} }
type = "text"
2023-12-30 11:22:05 +00:00
placeholder = "X_Y or URL"
2022-08-02 10:49:34 +00:00
onChange = { ( evt ) => {
2023-12-30 11:22:05 +00:00
let co = evt . target . value . trim ( ) ;
2024-02-13 14:08:27 +00:00
co = coordsFromString ( co ) ;
if ( co ) {
co = co . join ( '_' ) ;
evt . target . value = co ;
}
2022-08-02 10:49:34 +00:00
keptState . brccoords = co ;
} }
/ >
< / p >
< button
type = "button"
onClick = { ( ) => {
if ( submitting ) {
return ;
}
setSubmitting ( true ) ;
submitCanvasCleaner (
cleanAction ,
selectedCanvas ,
2022-08-19 13:34:56 +00:00
keptState . tlccoords ,
keptState . brccoords ,
2022-08-02 10:49:34 +00:00
( ret ) => {
setCleanerStats ( {
running : true ,
percent : 'N/A' ,
method : cleanAction ,
2022-08-19 13:34:56 +00:00
tl : keptState . tlccoords ,
br : keptState . brccoords ,
2022-08-02 10:49:34 +00:00
canvasId : selectedCanvas ,
} ) ;
setSubmitting ( false ) ;
setResp ( ret ) ;
} ,
) ;
} }
>
{ ( submitting ) ? '...' : t ` Submit ` }
< / button >
< button
type = "button"
onClick = { ( ) => {
if ( submitting ) {
return ;
}
setSubmitting ( true ) ;
getCleanerCancel (
( ret ) => {
setCleanerStats ( { } ) ;
setSubmitting ( false ) ;
setResp ( ret ) ;
} ,
) ;
} }
>
{ ( submitting ) ? '...' : t ` Stop Cleaner ` }
< / button >
< / div >
) ;
}
export default React . memo ( ModCanvastools ) ;