cleanup: wip 6.1

This commit is contained in:
Zephyr Lykos 2025-01-19 15:25:15 +08:00
parent 590ed9ce73
commit d48d332c83
No known key found for this signature in database
GPG Key ID: D3E9D31E2F77F04D
60 changed files with 348 additions and 357 deletions

20
composer.lock generated
View File

@ -5767,12 +5767,12 @@
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/bs-community/TwigBridge.git", "url": "https://github.com/bs-community/TwigBridge.git",
"reference": "72d1481a08c145ca3b0020ee6c07c771bb2ca3bf" "reference": "61c2084d29bc63c3fa173fc631d8354fdf1c01fb"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/bs-community/TwigBridge/zipball/72d1481a08c145ca3b0020ee6c07c771bb2ca3bf", "url": "https://api.github.com/repos/bs-community/TwigBridge/zipball/61c2084d29bc63c3fa173fc631d8354fdf1c01fb",
"reference": "72d1481a08c145ca3b0020ee6c07c771bb2ca3bf", "reference": "61c2084d29bc63c3fa173fc631d8354fdf1c01fb",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -5829,7 +5829,7 @@
"support": { "support": {
"source": "https://github.com/bs-community/TwigBridge/tree/blessing" "source": "https://github.com/bs-community/TwigBridge/tree/blessing"
}, },
"time": "2025-01-18T08:54:48+00:00" "time": "2025-01-18T13:54:09+00:00"
}, },
{ {
"name": "sanmai/hoa-protocol", "name": "sanmai/hoa-protocol",
@ -9387,16 +9387,16 @@
}, },
{ {
"name": "barryvdh/reflection-docblock", "name": "barryvdh/reflection-docblock",
"version": "v2.3.0", "version": "v2.3.1",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/barryvdh/ReflectionDocBlock.git", "url": "https://github.com/barryvdh/ReflectionDocBlock.git",
"reference": "818be8de6af4d16ef3ad51ea9234b3d37026ee5f" "reference": "b6ff9f93603561f50e53b64310495d20b8dff5d8"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/barryvdh/ReflectionDocBlock/zipball/818be8de6af4d16ef3ad51ea9234b3d37026ee5f", "url": "https://api.github.com/repos/barryvdh/ReflectionDocBlock/zipball/b6ff9f93603561f50e53b64310495d20b8dff5d8",
"reference": "818be8de6af4d16ef3ad51ea9234b3d37026ee5f", "reference": "b6ff9f93603561f50e53b64310495d20b8dff5d8",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -9433,9 +9433,9 @@
} }
], ],
"support": { "support": {
"source": "https://github.com/barryvdh/ReflectionDocBlock/tree/v2.3.0" "source": "https://github.com/barryvdh/ReflectionDocBlock/tree/v2.3.1"
}, },
"time": "2024-12-30T10:35:04+00:00" "time": "2025-01-18T19:26:32+00:00"
}, },
{ {
"name": "clue/ndjson-react", "name": "clue/ndjson-react",

View File

@ -7,12 +7,12 @@ const icons = new Map<AlertType, string>([
['danger', 'times-circle'], ['danger', 'times-circle'],
]); ]);
type Properties = { type Props = {
readonly type: AlertType; readonly type: AlertType;
readonly children?: React.ReactNode; readonly children?: React.ReactNode;
}; };
const Alert: React.FC<Properties> = ({type, children}) => { const Alert: React.FC<Props> = ({type, children}) => {
const icon = icons.get(type); const icon = icons.get(type);
return children === '' return children === ''

View File

@ -1,9 +1,9 @@
type Properties = { type Props = {
readonly title?: string; readonly title?: string;
readonly onClick: React.MouseEventHandler<HTMLAnchorElement>; readonly onClick: React.MouseEventHandler<HTMLAnchorElement>;
}; };
const ButtonEdit: React.FC<Properties> = ({title, onClick}) => ( const ButtonEdit: React.FC<Props> = ({title, onClick}) => (
<a href='#' title={title} className='ml-2' onClick={onClick}> <a href='#' title={title} className='ml-2' onClick={onClick}>
<i className='fas fa-edit'/> <i className='fas fa-edit'/>
</a> </a>

View File

@ -18,8 +18,8 @@ class Captcha extends React.Component<Record<string, unknown>, State> {
// eslint-disable-next-line ts/no-restricted-types // eslint-disable-next-line ts/no-restricted-types
ref: React.RefObject<Reaptcha | null>; ref: React.RefObject<Reaptcha | null>;
constructor(properties: Record<string, unknown>) { constructor(props: Record<string, unknown>) {
super(properties); super(props);
this.state = { this.state = {
value: '', value: '',
time: Date.now(), time: Date.now(),

View File

@ -1,11 +1,11 @@
import * as fetch from '@/scripts/net'; import * as fetch from '@/scripts/net';
import {useState} from 'react'; import {useState} from 'react';
type Properties = { type Props = {
readonly initMode: boolean; readonly initMode: boolean;
}; };
const DarkModeButton: React.FC<Properties> = ({initMode}) => { const DarkModeButton: React.FC<Props> = ({initMode}) => {
const [darkMode, setDarkMode] = useState(initMode); const [darkMode, setDarkMode] = useState(initMode);
const icon = darkMode ? 'moon' : 'sun'; const icon = darkMode ? 'moon' : 'sun';

View File

@ -13,11 +13,11 @@ const styles = css`
const domainNames = new Set(['qq.com', '163.com', 'gmail.com', 'hotmail.com']); const domainNames = new Set(['qq.com', '163.com', 'gmail.com', 'hotmail.com']);
type Properties = Omit<React.InputHTMLAttributes<HTMLInputElement>, 'onChange'> & { type Props = Omit<React.InputHTMLAttributes<HTMLInputElement>, 'onChange'> & {
onChange: (value: string) => void; onChange: (value: string) => void;
}; };
const EmailSuggestion: React.FC<Properties> = props => { const EmailSuggestion: React.FC<Props> = props => {
useEffect(() => { useEffect(() => {
emit('emailDomainsSuggestion', domainNames); emit('emailDomainsSuggestion', domainNames);
}, []); }, []);

View File

@ -8,13 +8,13 @@ const hideRawBrowseButton = css`
} }
`; `;
type Properties = { type Props = {
file: File | undefined; file: File | undefined;
accept?: string; accept?: string;
onChange: (event: React.ChangeEvent<HTMLInputElement>) => void; onChange: (event: React.ChangeEvent<HTMLInputElement>) => void;
}; };
const FileInput: React.FC<Properties> = properties => { const FileInput: React.FC<Props> = props => {
const reference = useRef<HTMLInputElement>(null); const reference = useRef<HTMLInputElement>(null);
const handleClick = () => { const handleClick = () => {
@ -31,12 +31,12 @@ const FileInput: React.FC<Properties> = properties => {
type='file' type='file'
className='custom-file-input' className='custom-file-input'
id='select-file' id='select-file'
accept={properties.accept} accept={props.accept}
title={t('skinlib.upload.select-file')} title={t('skinlib.upload.select-file')}
onChange={properties.onChange} onChange={props.onChange}
/> />
<label className='custom-file-label' css={hideRawBrowseButton}> <label className='custom-file-label' css={hideRawBrowseButton}>
{properties.file?.name} {props.file?.name}
</label> </label>
</div> </div>
<div className='input-group-append'> <div className='input-group-append'>

View File

@ -2,9 +2,9 @@ import {Modal as BootstrapModal} from 'bootstrap';
import clsx from 'clsx'; import clsx from 'clsx';
import {useEffect, useRef, useState} from 'react'; import {useEffect, useRef, useState} from 'react';
import {t} from '../scripts/i18n'; import {t} from '../scripts/i18n';
import ModalBody, {type Props as BodyProperties} from './ModalBody'; import ModalBody, {type Props as BodyProps} from './ModalBody';
import ModalFooter, {type Props as FooterProperties} from './ModalFooter'; import ModalFooter, {type Props as FooterProps} from './ModalFooter';
import ModalHeader, {type Props as HeaderProperties} from './ModalHeader'; import ModalHeader, {type Props as HeaderProps} from './ModalHeader';
type BasicOptions = { type BasicOptions = {
readonly mode?: 'alert' | 'confirm' | 'prompt'; readonly mode?: 'alert' | 'confirm' | 'prompt';
@ -17,9 +17,9 @@ type BasicOptions = {
children?: React.ReactNode; children?: React.ReactNode;
}; };
export type ModalOptions = BasicOptions & HeaderProperties & BodyProperties & FooterProperties; export type ModalOptions = BasicOptions & HeaderProps & BodyProps & FooterProps;
type Properties = { type Props = {
readonly id?: string; readonly id?: string;
readonly children?: React.ReactNode; readonly children?: React.ReactNode;
readonly footer?: React.ReactNode; readonly footer?: React.ReactNode;
@ -32,7 +32,7 @@ export type ModalResult = {
value: string; value: string;
}; };
const Modal: React.FC<ModalOptions & Properties> = properties => { const Modal: React.FC<ModalOptions & Props> = props => {
const { const {
mode = 'confirm', mode = 'confirm',
title = t('general.tip'), title = t('general.tip'),
@ -59,7 +59,7 @@ const Modal: React.FC<ModalOptions & Properties> = properties => {
children, children,
choices, choices,
dangerousHTML: html, dangerousHTML: html,
} = properties; } = props;
const [value, setValue] = useState(input); const [value, setValue] = useState(input);
const [valid, setValid] = useState(true); const [valid, setValid] = useState(true);

View File

@ -1,24 +1,24 @@
import ModalContent, {type Props as ContentProperties} from './ModalContent'; import ModalContent, {type Props as ContentProps} from './ModalContent';
import ModalInput, { import ModalInput, {
type type
InternalProps as InputInteralProperties, InternalProps as InputInteralProps,
type type
Props as InputProperties, Props as InputProps,
} from './ModalInput'; } from './ModalInput';
type InternalProperties = { type InternalProps = {
readonly showInput: boolean; readonly showInput: boolean;
}; };
export type Props = ContentProperties & InputProperties; export type Props = ContentProps & InputProps;
const ModalBody: React.FC<InternalProperties & InputInteralProperties & Props> = properties => ( const ModalBody: React.FC<InternalProps & InputInteralProps & Props> = props => (
<div className='modal-body'> <div className='modal-body'>
<ModalContent text={properties.text} dangerousHTML={properties.dangerousHTML}> <ModalContent text={props.text} dangerousHTML={props.dangerousHTML}>
{properties.children} {props.children}
</ModalContent> </ModalContent>
{properties.showInput && <ModalInput {...properties}/>} {props.showInput && <ModalInput {...props}/>}
</div> </div>
); );

View File

@ -5,22 +5,22 @@ export type Props = {
readonly children?: React.ReactNode; readonly children?: React.ReactNode;
}; };
const ModalContent: React.FC<Props> = properties => { const ModalContent: React.FC<Props> = props => {
if (properties.children) { if (props.children) {
return <>{properties.children}</>; return <>{props.children}</>;
} }
if (properties.text) { if (props.text) {
return ( return (
<> <>
{properties.text.split(/\r?\n/).map((line, i) => {props.text.split(/\r?\n/).map((line, i) =>
<p key={i}>{line}</p>)} <p key={i}>{line}</p>)}
</> </>
); );
} }
if (properties.dangerousHTML) { if (props.dangerousHTML) {
return <div dangerouslySetInnerHTML={{__html: properties.dangerousHTML}}/>; return <div dangerouslySetInnerHTML={{__html: props.dangerousHTML}}/>;
} }
return <></>; return <></>;

View File

@ -8,40 +8,40 @@ export type Props = {
readonly children?: React.ReactNode; readonly children?: React.ReactNode;
}; };
type InternalProperties = { type InternalProps = {
readonly showCancelButton: boolean; readonly showCancelButton: boolean;
onConfirm?: () => void; onConfirm?: () => void;
onDismiss?: () => void; onDismiss?: () => void;
}; };
const ModalFooter: React.FC<InternalProperties & Props> = properties => { const ModalFooter: React.FC<InternalProps & Props> = props => {
const classes = ['modal-footer']; const classes = ['modal-footer'];
if (properties.flexFooter) { if (props.flexFooter) {
classes.push('d-flex', 'justify-content-between'); classes.push('d-flex', 'justify-content-between');
} }
const footerClass = classes.join(' '); const footerClass = classes.join(' ');
return properties.children return props.children
? <div className={footerClass}>{properties.children}</div> ? <div className={footerClass}>{props.children}</div>
: ( : (
<div className={footerClass}> <div className={footerClass}>
{properties.showCancelButton && ( {props.showCancelButton && (
<button <button
type='button' type='button'
className={`btn btn-${properties.cancelButtonType}`} className={`btn btn-${props.cancelButtonType}`}
data-dismiss='modal' data-dismiss='modal'
onClick={properties.onDismiss} onClick={props.onDismiss}
> >
{properties.cancelButtonText} {props.cancelButtonText}
</button> </button>
)} )}
<button <button
type='button' type='button'
className={`btn btn-${properties.okButtonType}`} className={`btn btn-${props.okButtonType}`}
onClick={properties.onConfirm} onClick={props.onConfirm}
> >
{properties.okButtonText} {props.okButtonText}
</button> </button>
</div> </div>
); );

View File

@ -3,12 +3,12 @@ export type Props = {
readonly title?: string; readonly title?: string;
}; };
type InternalProperties = { type InternalProps = {
onDismiss?: () => void; onDismiss?: () => void;
readonly show?: boolean; readonly show?: boolean;
}; };
const ModalHeader: React.FC<Props & InternalProperties> = ({show, title, onDismiss}) => const ModalHeader: React.FC<Props & InternalProps> = ({show, title, onDismiss}) =>
show show
? ( ? (
<div className='modal-header'> <div className='modal-header'>

View File

@ -13,20 +13,20 @@ export type InternalProps = {
readonly onChange?: React.ChangeEventHandler<HTMLInputElement>; readonly onChange?: React.ChangeEventHandler<HTMLInputElement>;
}; };
const ModalInput: React.FC<InternalProps & Props> = properties => ( const ModalInput: React.FC<InternalProps & Props> = props => (
<> <>
{properties.inputType === 'radios' && properties.choices {props.inputType === 'radios' && props.choices
? ( ? (
<> <>
{properties.choices.map(choice => ( {props.choices.map(choice => (
<div key={choice.value}> <div key={choice.value}>
<input <input
type='radio' type='radio'
name='modal-radios' name='modal-radios'
id={`modal-radio-${choice.value}`} id={`modal-radio-${choice.value}`}
value={choice.value} value={choice.value}
checked={choice.value === properties.value} checked={choice.value === props.value}
onChange={properties.onChange} onChange={props.onChange}
/> />
<label htmlFor={`modal-radio-${choice.value}`} className='ml-1'> <label htmlFor={`modal-radio-${choice.value}`} className='ml-1'>
{choice.text} {choice.text}
@ -38,19 +38,19 @@ const ModalInput: React.FC<InternalProps & Props> = properties => (
: ( : (
<div className='form-group'> <div className='form-group'>
<input <input
value={properties.value} value={props.value}
type={properties.inputType} type={props.inputType}
inputMode={properties.inputMode} inputMode={props.inputMode}
className='form-control' className='form-control'
placeholder={properties.placeholder} placeholder={props.placeholder}
onChange={properties.onChange} onChange={props.onChange}
/> />
</div> </div>
)} )}
{properties.invalid && ( {props.invalid && (
<div className='alert alert-danger'> <div className='alert alert-danger'>
<i className='icon far fa-times-circle'/> <i className='icon far fa-times-circle'/>
<span className='ml-1'>{properties.validatorMessage}</span> <span className='ml-1'>{props.validatorMessage}</span>
</div> </div>
)} )}
</> </>

View File

@ -2,7 +2,7 @@
import {t} from '@/scripts/i18n'; import {t} from '@/scripts/i18n';
import PaginationItem from './PaginationItem'; import PaginationItem from './PaginationItem';
type Properties = { type Props = {
readonly page: number; readonly page: number;
readonly totalPages: number; readonly totalPages: number;
onChange: (page: number) => void | Promise<void>; onChange: (page: number) => void | Promise<void>;
@ -13,8 +13,8 @@ const labels = {
next: '', next: '',
}; };
const Pagination: React.FC<Properties> = properties => { const Pagination: React.FC<Props> = props => {
const {page, totalPages, onChange} = properties; const {page, totalPages, onChange} = props;
if (totalPages < 1) { if (totalPages < 1) {
return null; return null;

View File

@ -1,5 +1,5 @@
type Properties = { type Props = {
readonly disabled?: boolean; readonly disabled?: boolean;
readonly active?: boolean; readonly active?: boolean;
readonly title?: string; readonly title?: string;
@ -8,31 +8,31 @@ type Properties = {
readonly children?: React.ReactNode; readonly children?: React.ReactNode;
}; };
const PaginationItem: React.FC<Properties> = properties => { const PaginationItem: React.FC<Props> = props => {
const classes = ['page-item']; const classes = ['page-item'];
if (properties.active) { if (props.active) {
classes.push('active'); classes.push('active');
} }
if (properties.disabled) { if (props.disabled) {
classes.push('disabled'); classes.push('disabled');
} }
if (properties.className) { if (props.className) {
classes.push(properties.className); classes.push(props.className);
} }
const handleClick = (event: React.MouseEvent) => { const handleClick = (event: React.MouseEvent) => {
event.preventDefault(); event.preventDefault();
if (!properties.disabled && properties.onClick) { if (!props.disabled && props.onClick) {
properties.onClick(); props.onClick();
} }
}; };
return ( return (
<li className={classes.join(' ')} title={properties.title} onClick={handleClick}> <li className={classes.join(' ')} title={props.title} onClick={handleClick}>
<a href='#' className='page-link' aria-disabled={properties.disabled}> <a href='#' className='page-link' aria-disabled={props.disabled}>
{properties.children} {props.children}
</a> </a>
</li> </li>
); );

View File

@ -3,7 +3,7 @@ import {useEffect, useState} from 'react';
export type ToastType = 'success' | 'info' | 'warning' | 'error'; export type ToastType = 'success' | 'info' | 'warning' | 'error';
type Properties = { type Props = {
readonly type: ToastType; readonly type: ToastType;
readonly distance: number; readonly distance: number;
onClose: () => void | Promise<void>; onClose: () => void | Promise<void>;
@ -29,7 +29,7 @@ const shadow = css`
box-shadow: 0 0.25rem 0.75rem rgba(0, 0, 0, 0.1); box-shadow: 0 0.25rem 0.75rem rgba(0, 0, 0, 0.1);
`; `;
const Toast: React.FC<Properties> = properties => { const Toast: React.FC<Props> = props => {
const [show, setShow] = useState(false); const [show, setShow] = useState(false);
useEffect(() => { useEffect(() => {
@ -40,9 +40,9 @@ const Toast: React.FC<Properties> = properties => {
return () => { return () => {
clearTimeout(timer); clearTimeout(timer);
}; };
}, [properties.onClose]); }, [props.onClose]);
const type = properties.type === 'error' ? 'danger' : properties.type; const type = props.type === 'error' ? 'danger' : props.type;
const classes = [ const classes = [
`alert alert-${type}`, `alert alert-${type}`,
@ -56,16 +56,16 @@ const Toast: React.FC<Properties> = properties => {
const role = type === 'success' || type === 'info' ? 'status' : 'alert'; const role = type === 'success' || type === 'info' ? 'status' : 'alert';
return ( return (
<div css={wrapper} style={{top: `${properties.distance}px`}}> <div css={wrapper} style={{top: `${props.distance}px`}}>
<div className={classes.join(' ')} css={shadow} role={role}> <div className={classes.join(' ')} css={shadow} role={role}>
<span className='mr-1 d-flex align-items-center'> <span className='mr-1 d-flex align-items-center'>
<i className={`icon fas fa-${icons.get(properties.type)}`}/> <i className={`icon fas fa-${icons.get(props.type)}`}/>
</span> </span>
<span>{properties.children}</span> <span>{props.children}</span>
<button <button
type='button' type='button'
className='mr-2 ml-1 close' className='mr-2 ml-1 close'
onClick={properties.onClose} onClick={props.onClose}
> >
&times; &times;
</button> </button>

View File

@ -18,7 +18,7 @@ import SkinSteve from '../../../misc/textures/steve.png';
const backgrounds = [bg1, bg2, bg3, bg4, bg5, bg6, bg7]; const backgrounds = [bg1, bg2, bg3, bg4, bg5, bg6, bg7];
export const PICTURES_COUNT = backgrounds.length; export const PICTURES_COUNT = backgrounds.length;
type Properties = { type Props = {
readonly skin?: string; readonly skin?: string;
readonly cape?: string; readonly cape?: string;
readonly children?: React.ReactNode; readonly children?: React.ReactNode;
@ -58,8 +58,8 @@ const cssViewer = css`
} }
`; `;
const Viewer: React.FC<Properties> = properties => { const Viewer: React.FC<Props> = props => {
const {initPositionZ = 70} = properties; const {initPositionZ = 70} = props;
const viewReference: React.MutableRefObject<skinview3d.SkinViewer> = useRef(null!); const viewReference: React.MutableRefObject<skinview3d.SkinViewer> = useRef(null!);
const containerReference = useRef<HTMLCanvasElement>(null); const containerReference = useRef<HTMLCanvasElement>(null);
@ -69,7 +69,7 @@ const Viewer: React.FC<Properties> = properties => {
const [bgPicture, setBgPicture] = useState(-1); const [bgPicture, setBgPicture] = useState(-1);
const indicator = (() => { const indicator = (() => {
const {skin, cape} = properties; const {skin, cape} = props;
if (skin && cape) { if (skin && cape) {
return `${t('general.skin')} & ${t('general.cape')}`; return `${t('general.skin')} & ${t('general.cape')}`;
} }
@ -91,9 +91,9 @@ const Viewer: React.FC<Properties> = properties => {
canvas: container, canvas: container,
width: container.clientWidth, width: container.clientWidth,
height: container.clientHeight, height: container.clientHeight,
skin: properties.skin || SkinSteve, skin: props.skin || SkinSteve,
cape: properties.cape || undefined, cape: props.cape || undefined,
model: properties.isAlex ? 'slim' : 'default', model: props.isAlex ? 'slim' : 'default',
zoom: initPositionZ / 100, zoom: initPositionZ / 100,
}); });
viewer.autoRotate = true; viewer.autoRotate = true;
@ -117,19 +117,19 @@ const Viewer: React.FC<Properties> = properties => {
useEffect(() => { useEffect(() => {
const viewer = viewReference.current; const viewer = viewReference.current;
viewer.loadSkin(properties.skin || SkinSteve, { viewer.loadSkin(props.skin || SkinSteve, {
model: properties.isAlex ? 'slim' : 'default', model: props.isAlex ? 'slim' : 'default',
}); });
}, [properties.skin, properties.isAlex]); }, [props.skin, props.isAlex]);
useEffect(() => { useEffect(() => {
const viewer = viewReference.current; const viewer = viewReference.current;
if (properties.cape) { if (props.cape) {
viewer.loadCape(properties.cape); viewer.loadCape(props.cape);
} else { } else {
viewer.resetCape(); viewer.resetCape();
} }
}, [properties.cape]); }, [props.cape]);
useEffect(() => { useEffect(() => {
const viewer = viewReference.current; const viewer = viewReference.current;
@ -225,12 +225,12 @@ const Viewer: React.FC<Properties> = properties => {
<div className='d-flex justify-content-between'> <div className='d-flex justify-content-between'>
<h3 className='card-title'> <h3 className='card-title'>
<span>{t('general.texturePreview')}</span> <span>{t('general.texturePreview')}</span>
{properties.showIndicator {props.showIndicator
&& <span className='badge bg-olive ml-1'>{indicator}</span>} && <span className='badge bg-olive ml-1'>{indicator}</span>}
</h3> </h3>
<div> <div>
<ActionButton <ActionButton
className={`fas fa-tablet ${properties.cape ? '' : 'd-none'}`} className={`fas fa-tablet ${props.cape ? '' : 'd-none'}`}
data-toggle='tooltip' data-toggle='tooltip'
data-placement='bottom' data-placement='bottom'
title={t('general.switchCapeElytra')} title={t('general.switchCapeElytra')}
@ -301,7 +301,7 @@ const Viewer: React.FC<Properties> = properties => {
<i className='fas fa-arrow-right'/> <i className='fas fa-arrow-right'/>
</div> </div>
</div> </div>
{properties.children} {props.children}
</div> </div>
</div> </div>
); );

View File

@ -26,10 +26,9 @@ if (route) {
const root = createRoot(container!); const root = createRoot(container!);
root.render( root.render(
<React.StrictMode> <React.StrictMode>
<React.Suspense fallback={route.frame?.() ?? ''}> <React.Suspense fallback={route.frame?.() ?? ''}>
<Component/> <Component/>
</React.Suspense> </React.Suspense>
</React.StrictMode> </React.StrictMode>,);
);
} }
} }

View File

@ -5,7 +5,7 @@ import {showModal} from '@/scripts/notify';
import clsx from 'clsx'; import clsx from 'clsx';
import {Box} from './styles'; import {Box} from './styles';
type Properties = { type Props = {
readonly player: Player; readonly player: Player;
onUpdateName: () => void; onUpdateName: () => void;
onUpdateOwner: () => void; onUpdateOwner: () => void;
@ -13,8 +13,8 @@ type Properties = {
onDelete: () => void; onDelete: () => void;
}; };
const Card: React.FC<Properties> = properties => { const Card: React.FC<Props> = props => {
const {player} = properties; const {player} = props;
const handlePreviewTextures = () => { const handlePreviewTextures = () => {
const skinPreview = `${blessing.base_url}/preview/${player.tid_skin}`; const skinPreview = `${blessing.base_url}/preview/${player.tid_skin}`;
@ -109,7 +109,7 @@ const Card: React.FC<Properties> = properties => {
<a <a
href='#' href='#'
className='dropdown-item' className='dropdown-item'
onClick={properties.onUpdateName} onClick={props.onUpdateName}
> >
<i className='fas fa-signature mr-2'/> <i className='fas fa-signature mr-2'/>
{t('admin.changePlayerName')} {t('admin.changePlayerName')}
@ -117,7 +117,7 @@ const Card: React.FC<Properties> = properties => {
<a <a
href='#' href='#'
className='dropdown-item' className='dropdown-item'
onClick={properties.onUpdateOwner} onClick={props.onUpdateOwner}
> >
<i className='fas fa-user-edit mr-2'/> <i className='fas fa-user-edit mr-2'/>
{t('admin.changeOwner')} {t('admin.changeOwner')}
@ -125,7 +125,7 @@ const Card: React.FC<Properties> = properties => {
<a <a
href='#' href='#'
className='dropdown-item' className='dropdown-item'
onClick={properties.onUpdateTexture} onClick={props.onUpdateTexture}
> >
<i className='fas fa-tshirt mr-2'/> <i className='fas fa-tshirt mr-2'/>
{t('admin.changeTexture')} {t('admin.changeTexture')}
@ -134,7 +134,7 @@ const Card: React.FC<Properties> = properties => {
<a <a
href='#' href='#'
className='dropdown-item dropdown-item-danger' className='dropdown-item dropdown-item-danger'
onClick={properties.onDelete} onClick={props.onDelete}
> >
<i className='fas fa-trash mr-2'/> <i className='fas fa-trash mr-2'/>
{t('admin.deletePlayer')} {t('admin.deletePlayer')}

View File

@ -6,7 +6,7 @@ import {Box} from './styles';
const isDarkMode = document.body.classList.contains('dark-mode'); const isDarkMode = document.body.classList.contains('dark-mode');
const ShrinkedSkeleton = styled(Skeleton)<{width?: string}>` const ShrinkedSkeleton = styled(Skeleton)<{width?: string}>`
width: ${properties => properties.width}; width: ${props => props.width};
`; `;
export default function LoadingCard() { export default function LoadingCard() {

View File

@ -3,13 +3,13 @@ import {t} from '@/scripts/i18n';
import {TextureType} from '@/scripts/types'; import {TextureType} from '@/scripts/types';
import {useState} from 'react'; import {useState} from 'react';
type Properties = { type Props = {
readonly open: boolean; readonly open: boolean;
onSubmit: (type: 'skin' | 'cape', tid: number) => void; onSubmit: (type: 'skin' | 'cape', tid: number) => void;
onClose: () => void; onClose: () => void;
}; };
const ModalUpdateTexture: React.FC<Properties> = properties => { const ModalUpdateTexture: React.FC<Props> = props => {
const [type, setType] = useState<'skin' | 'cape'>('skin'); const [type, setType] = useState<'skin' | 'cape'>('skin');
const [tid, setTid] = useState(''); const [tid, setTid] = useState('');
@ -22,7 +22,7 @@ const ModalUpdateTexture: React.FC<Properties> = properties => {
}; };
const handleConfirm = () => { const handleConfirm = () => {
properties.onSubmit(type, Number.parseInt(tid)); props.onSubmit(type, Number.parseInt(tid));
setType('skin'); setType('skin');
setTid(''); setTid('');
}; };
@ -30,13 +30,13 @@ const ModalUpdateTexture: React.FC<Properties> = properties => {
const handleClose = () => { const handleClose = () => {
setType('skin'); setType('skin');
setTid(''); setTid('');
properties.onClose(); props.onClose();
}; };
return ( return (
<Modal <Modal
center center
show={properties.open} show={props.open}
title={t('admin.changeTexture')} title={t('admin.changeTexture')}
onConfirm={handleConfirm} onConfirm={handleConfirm}
onClose={handleClose} onClose={handleClose}

View File

@ -3,7 +3,7 @@ import type {Player} from '@/scripts/types';
import ButtonEdit from '@/components/ButtonEdit'; import ButtonEdit from '@/components/ButtonEdit';
import {t} from '@/scripts/i18n'; import {t} from '@/scripts/i18n';
type Properties = { type Props = {
readonly player: Player; readonly player: Player;
onUpdateName: () => void; onUpdateName: () => void;
onUpdateOwner: () => void; onUpdateOwner: () => void;
@ -11,8 +11,8 @@ type Properties = {
onDelete: () => void; onDelete: () => void;
}; };
const Row: React.FC<Properties> = properties => { const Row: React.FC<Props> = props => {
const {player} = properties; const {player} = props;
return ( return (
<tr> <tr>
@ -22,7 +22,7 @@ const Row: React.FC<Properties> = properties => {
<span className='ml-1'> <span className='ml-1'>
<ButtonEdit <ButtonEdit
title={t('admin.changePlayerName')} title={t('admin.changePlayerName')}
onClick={properties.onUpdateName} onClick={props.onUpdateName}
/> />
</span> </span>
</td> </td>
@ -31,7 +31,7 @@ const Row: React.FC<Properties> = properties => {
<span className='ml-1'> <span className='ml-1'>
<ButtonEdit <ButtonEdit
title={t('admin.changeOwner')} title={t('admin.changeOwner')}
onClick={properties.onUpdateOwner} onClick={props.onUpdateOwner}
/> />
</span> </span>
</td> </td>
@ -68,11 +68,11 @@ const Row: React.FC<Properties> = properties => {
<td className='d-flex flex-wrap'> <td className='d-flex flex-wrap'>
<button <button
className='btn btn-default mr-2' className='btn btn-default mr-2'
onClick={properties.onUpdateTexture} onClick={props.onUpdateTexture}
> >
{t('admin.changeTexture')} {t('admin.changeTexture')}
</button> </button>
<button className='btn btn-danger' onClick={properties.onDelete}> <button className='btn btn-danger' onClick={props.onDelete}>
{t('admin.deletePlayer')} {t('admin.deletePlayer')}
</button> </button>
</td> </td>

View File

@ -39,7 +39,7 @@ const Description = styled.div`
font-size: 14px; font-size: 14px;
`; `;
type Properties = { type Props = {
readonly plugin: Plugin; readonly plugin: Plugin;
onEnable: (plugin: Plugin) => void; onEnable: (plugin: Plugin) => void;
onDisable: (plugin: Plugin) => void; onDisable: (plugin: Plugin) => void;
@ -47,21 +47,21 @@ type Properties = {
readonly baseUrl: string; readonly baseUrl: string;
}; };
const InfoBox: React.FC<Properties> = properties => { const InfoBox: React.FC<Props> = props => {
const {plugin} = properties; const {plugin} = props;
const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => { const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
event.preventDefault(); event.preventDefault();
if (event.target.checked) { if (event.target.checked) {
properties.onEnable(plugin); props.onEnable(plugin);
} else { } else {
properties.onDisable(plugin); props.onDisable(plugin);
} }
}; };
const handleDelete = () => { const handleDelete = () => {
properties.onDelete(plugin); props.onDelete(plugin);
}; };
const isDarkMode = document.body.classList.contains('dark-mode'); const isDarkMode = document.body.classList.contains('dark-mode');
@ -96,7 +96,7 @@ const InfoBox: React.FC<Properties> = properties => {
<div> <div>
{plugin.readme && ( {plugin.readme && (
<ActionButton <ActionButton
href={`${properties.baseUrl}/admin/plugins/readme/${plugin.name}`} href={`${props.baseUrl}/admin/plugins/readme/${plugin.name}`}
title={t('admin.pluginReadme')} title={t('admin.pluginReadme')}
> >
<i className='fas fa-question'/> <i className='fas fa-question'/>
@ -104,7 +104,7 @@ const InfoBox: React.FC<Properties> = properties => {
)} )}
{plugin.enabled && plugin.config && ( {plugin.enabled && plugin.config && (
<ActionButton <ActionButton
href={`${properties.baseUrl}/admin/plugins/config/${plugin.name}`} href={`${props.baseUrl}/admin/plugins/config/${plugin.name}`}
title={t('admin.configurePlugin')} title={t('admin.configurePlugin')}
> >
<i className='fas fa-cog'/> <i className='fas fa-cog'/>

View File

@ -2,15 +2,15 @@
import type {Plugin} from './types'; import type {Plugin} from './types';
import {t} from '@/scripts/i18n'; import {t} from '@/scripts/i18n';
type Properties = { type Props = {
readonly plugin: Plugin; readonly plugin: Plugin;
readonly isInstalling: boolean; readonly isInstalling: boolean;
onInstall: () => void; onInstall: () => void;
onUpdate: () => void; onUpdate: () => void;
}; };
const Row: React.FC<Properties> = properties => { const Row: React.FC<Props> = props => {
const {plugin, isInstalling} = properties; const {plugin, isInstalling} = props;
const allDeps = Object.entries(plugin.dependencies.all); const allDeps = Object.entries(plugin.dependencies.all);
const unsatisfied = Object.keys(plugin.dependencies.unsatisfied); const unsatisfied = Object.keys(plugin.dependencies.unsatisfied);
@ -54,7 +54,7 @@ const Row: React.FC<Properties> = properties => {
<button <button
className='btn btn-success' className='btn btn-success'
disabled={isInstalling} disabled={isInstalling}
onClick={properties.onUpdate} onClick={props.onUpdate}
> >
{isInstalling {isInstalling
? ( ? (
@ -74,8 +74,8 @@ const Row: React.FC<Properties> = properties => {
: ( : (
<button <button
className='btn btn-default' className='btn btn-default'
disabled={properties.isInstalling || Boolean(plugin.installed)} disabled={props.isInstalling || Boolean(plugin.installed)}
onClick={properties.onInstall} onClick={props.onInstall}
> >
{isInstalling {isInstalling
? ( ? (

View File

@ -29,7 +29,7 @@ const Card = styled.div`
} }
`; `;
type Properties = { type Props = {
readonly report: Report; readonly report: Report;
onClick: (texture: Texture | undefined) => void; onClick: (texture: Texture | undefined) => void;
onBan: () => void; onBan: () => void;
@ -37,13 +37,13 @@ type Properties = {
onReject: () => void; onReject: () => void;
}; };
const ImageBox: React.FC<Properties> = properties => { const ImageBox: React.FC<Props> = props => {
const {report} = properties; const {report} = props;
const preview = `${blessing.base_url}/preview/${report.tid}?height=150`; const preview = `${blessing.base_url}/preview/${report.tid}?height=150`;
const previewPNG = `${preview}&png`; const previewPNG = `${preview}&png`;
const handleImageClick = () => { const handleImageClick = () => {
properties.onClick(report.texture); props.onClick(report.texture);
}; };
return ( return (
@ -102,19 +102,19 @@ const ImageBox: React.FC<Properties> = properties => {
<i className='fas fa-share-square mr-2'/> <i className='fas fa-share-square mr-2'/>
{t('user.viewInSkinlib')} {t('user.viewInSkinlib')}
</a> </a>
<a href='#' className='dropdown-item' onClick={properties.onBan}> <a href='#' className='dropdown-item' onClick={props.onBan}>
<i className='fas fa-user-slash mr-2'/> <i className='fas fa-user-slash mr-2'/>
{t('report.ban')} {t('report.ban')}
</a> </a>
<a <a
href='#' href='#'
className='dropdown-item dropdown-item-danger' className='dropdown-item dropdown-item-danger'
onClick={properties.onDelete} onClick={props.onDelete}
> >
<i className='fas fa-trash mr-2'/> <i className='fas fa-trash mr-2'/>
{t('skinlib.show.delete-texture')} {t('skinlib.show.delete-texture')}
</a> </a>
<a href='#' className='dropdown-item' onClick={properties.onReject}> <a href='#' className='dropdown-item' onClick={props.onReject}>
<i className='fas fa-thumbs-down mr-2'/> <i className='fas fa-thumbs-down mr-2'/>
{t('report.reject')} {t('report.reject')}
</a> </a>

View File

@ -12,14 +12,14 @@ const Operations = styled.td`
width: 25%; width: 25%;
`; `;
type Properties = { type Props = {
readonly line: Line; readonly line: Line;
onEdit: (line: Line) => void; onEdit: (line: Line) => void;
onRemove: (line: Line) => void; onRemove: (line: Line) => void;
}; };
const Row: React.FC<Properties> = properties => { const Row: React.FC<Props> = props => {
const {line, onEdit, onRemove} = properties; const {line, onEdit, onRemove} = props;
const text = line.text[blessing.locale]; const text = line.text[blessing.locale];
const handleEditClick = () => { const handleEditClick = () => {

View File

@ -10,7 +10,7 @@ import {
verificationStatusText, verificationStatusText,
} from './utils'; } from './utils';
type Properties = { type Props = {
readonly user: User; readonly user: User;
readonly currentUser: User; readonly currentUser: User;
onEmailChange: () => void; onEmailChange: () => void;
@ -22,8 +22,8 @@ type Properties = {
onDelete: () => void; onDelete: () => void;
}; };
const Card: React.FC<Properties> = properties => { const Card: React.FC<Props> = props => {
const {user, currentUser} = properties; const {user, currentUser} = props;
const isDarkMode = document.body.classList.contains('dark-mode'); const isDarkMode = document.body.classList.contains('dark-mode');
@ -59,7 +59,7 @@ const Card: React.FC<Properties> = properties => {
<a <a
href='#' href='#'
className='dropdown-item' className='dropdown-item'
onClick={properties.onEmailChange} onClick={props.onEmailChange}
> >
<i className='fas fa-at mr-2'/> <i className='fas fa-at mr-2'/>
{t('admin.changeEmail')} {t('admin.changeEmail')}
@ -67,7 +67,7 @@ const Card: React.FC<Properties> = properties => {
<a <a
href='#' href='#'
className='dropdown-item' className='dropdown-item'
onClick={properties.onNicknameChange} onClick={props.onNicknameChange}
> >
<i className='fas fa-signature mr-2'/> <i className='fas fa-signature mr-2'/>
{t('admin.changeNickName')} {t('admin.changeNickName')}
@ -75,7 +75,7 @@ const Card: React.FC<Properties> = properties => {
<a <a
href='#' href='#'
className='dropdown-item' className='dropdown-item'
onClick={properties.onPasswordChange} onClick={props.onPasswordChange}
> >
<i className='fas fa-asterisk mr-2'/> <i className='fas fa-asterisk mr-2'/>
{t('admin.changePassword')} {t('admin.changePassword')}
@ -84,7 +84,7 @@ const Card: React.FC<Properties> = properties => {
<a <a
href='#' href='#'
className='dropdown-item' className='dropdown-item'
onClick={properties.onScoreChange} onClick={props.onScoreChange}
> >
<i className='fas fa-coins mr-2'/> <i className='fas fa-coins mr-2'/>
{t('admin.changeScore')} {t('admin.changeScore')}
@ -93,7 +93,7 @@ const Card: React.FC<Properties> = properties => {
<a <a
href='#' href='#'
className='dropdown-item' className='dropdown-item'
onClick={properties.onPermissionChange} onClick={props.onPermissionChange}
> >
<i className='fas fa-user-secret mr-2'/> <i className='fas fa-user-secret mr-2'/>
{t('admin.changePermission')} {t('admin.changePermission')}
@ -102,7 +102,7 @@ const Card: React.FC<Properties> = properties => {
<a <a
href='#' href='#'
className='dropdown-item' className='dropdown-item'
onClick={properties.onVerificationToggle} onClick={props.onVerificationToggle}
> >
<i className='fas fa-user-check mr-2'/> <i className='fas fa-user-check mr-2'/>
{t('admin.toggleVerification')} {t('admin.toggleVerification')}
@ -112,7 +112,7 @@ const Card: React.FC<Properties> = properties => {
<a <a
href='#' href='#'
className='dropdown-item dropdown-item-danger' className='dropdown-item dropdown-item-danger'
onClick={properties.onDelete} onClick={props.onDelete}
> >
<i className='fas fa-trash mr-2'/> <i className='fas fa-trash mr-2'/>
{t('admin.deleteUser')} {t('admin.deleteUser')}

View File

@ -5,7 +5,7 @@ import Skeleton from 'react-loading-skeleton';
import {Box, Icon, InfoTable} from './styles'; import {Box, Icon, InfoTable} from './styles';
const ShrinkedSkeleton = styled(Skeleton)<{width?: string}>` const ShrinkedSkeleton = styled(Skeleton)<{width?: string}>`
width: ${properties => properties.width}; width: ${props => props.width};
`; `;
const isDarkMode = document.body.classList.contains('dark-mode'); const isDarkMode = document.body.classList.contains('dark-mode');

View File

@ -9,7 +9,7 @@ import {
verificationStatusText, verificationStatusText,
} from './utils'; } from './utils';
type Properties = { type Props = {
readonly user: User; readonly user: User;
readonly currentUser: User; readonly currentUser: User;
onEmailChange: () => void; onEmailChange: () => void;
@ -21,8 +21,8 @@ type Properties = {
onDelete: () => void; onDelete: () => void;
}; };
const Row: React.FC<Properties> = properties => { const Row: React.FC<Props> = props => {
const {user, currentUser} = properties; const {user, currentUser} = props;
const canModify = canModifyUser(user, currentUser); const canModify = canModifyUser(user, currentUser);
@ -35,7 +35,7 @@ const Row: React.FC<Properties> = properties => {
<span className='ml-1'> <span className='ml-1'>
<ButtonEdit <ButtonEdit
title={t('admin.changeEmail')} title={t('admin.changeEmail')}
onClick={properties.onEmailChange} onClick={props.onEmailChange}
/> />
</span> </span>
)} )}
@ -46,7 +46,7 @@ const Row: React.FC<Properties> = properties => {
<span className='ml-1'> <span className='ml-1'>
<ButtonEdit <ButtonEdit
title={t('admin.changeNickName')} title={t('admin.changeNickName')}
onClick={properties.onNicknameChange} onClick={props.onNicknameChange}
/> />
</span> </span>
)} )}
@ -57,7 +57,7 @@ const Row: React.FC<Properties> = properties => {
<span className='ml-1'> <span className='ml-1'>
<ButtonEdit <ButtonEdit
title={t('admin.changeScore')} title={t('admin.changeScore')}
onClick={properties.onScoreChange} onClick={props.onScoreChange}
/> />
</span> </span>
)} )}
@ -68,7 +68,7 @@ const Row: React.FC<Properties> = properties => {
<span className='ml-1'> <span className='ml-1'>
<ButtonEdit <ButtonEdit
title={t('admin.changePermission')} title={t('admin.changePermission')}
onClick={properties.onPermissionChange} onClick={props.onPermissionChange}
/> />
</span> </span>
)} )}
@ -80,7 +80,7 @@ const Row: React.FC<Properties> = properties => {
className='ml-1' className='ml-1'
href='#' href='#'
title={t('admin.toggleVerification')} title={t('admin.toggleVerification')}
onClick={properties.onVerificationToggle} onClick={props.onVerificationToggle}
> >
{user.verified {user.verified
? <i className='fas fa-toggle-on'/> ? <i className='fas fa-toggle-on'/>
@ -93,14 +93,14 @@ const Row: React.FC<Properties> = properties => {
<button <button
className='btn btn-default mr-2' className='btn btn-default mr-2'
disabled={!canModify} disabled={!canModify}
onClick={properties.onPasswordChange} onClick={props.onPasswordChange}
> >
{t('admin.changePassword')} {t('admin.changePassword')}
</button> </button>
<button <button
className='btn btn-danger' className='btn btn-danger'
disabled={!canModify || user.uid === currentUser.uid} disabled={!canModify || user.uid === currentUser.uid}
onClick={properties.onDelete} onClick={props.onDelete}
> >
{t('admin.deleteUser')} {t('admin.deleteUser')}
</button> </button>

View File

@ -1,4 +1,4 @@
import type {Props as ModalInputProperties} from '@/components/ModalInput'; import type {Props as ModalInputProps} from '@/components/ModalInput';
import Pagination from '@/components/Pagination'; import Pagination from '@/components/Pagination';
import useBlessingExtra from '@/scripts/hooks/useBlessingExtra'; import useBlessingExtra from '@/scripts/hooks/useBlessingExtra';
import useIsLargeScreen from '@/scripts/hooks/useIsLargeScreen'; import useIsLargeScreen from '@/scripts/hooks/useIsLargeScreen';
@ -159,7 +159,7 @@ function UsersManagement() {
}; };
const handlePermissionChange = async (user: User, index: number) => { const handlePermissionChange = async (user: User, index: number) => {
const permissions: ModalInputProperties['choices'] = [ const permissions: ModalInputProps['choices'] = [
{text: t('admin.banned'), value: '-1'}, {text: t('admin.banned'), value: '-1'},
{text: t('admin.normal'), value: '0'}, {text: t('admin.normal'), value: '0'},
]; ];

View File

@ -1,5 +1,5 @@
type Properties = { type Props = {
readonly active?: boolean; readonly active?: boolean;
readonly bg?: string; readonly bg?: string;
}; };
@ -9,21 +9,21 @@ type Attributes = React.DetailedHTMLProps<
HTMLButtonElement HTMLButtonElement
>; >;
const Button: React.FC<Properties & Attributes> = properties => { const Button: React.FC<Props & Attributes> = props => {
const classes = [properties.className ?? '']; const classes = [props.className ?? ''];
if (properties.bg) { if (props.bg) {
classes.push('btn', `bg-${properties.bg}`); classes.push('btn', `bg-${props.bg}`);
} }
if (properties.active) { if (props.active) {
classes.push('active'); classes.push('active');
} }
const rest = {...properties, active: undefined, bg: undefined}; const rest = {...props, active: undefined, bg: undefined};
return ( return (
<button {...rest} className={classes.join(' ')}> <button {...rest} className={classes.join(' ')}>
{properties.children} {props.children}
</button> </button>
); );
}; };

View File

@ -5,13 +5,13 @@ import {TextureType} from '@/scripts/types';
import Button from './Button'; import Button from './Button';
import {humanizeType} from './utils'; import {humanizeType} from './utils';
type Properties = { type Props = {
readonly filter: Filter; readonly filter: Filter;
onChange: (filter: Filter) => void; onChange: (filter: Filter) => void;
}; };
const FilterSelector: React.FC<Properties> = properties => { const FilterSelector: React.FC<Props> = props => {
const {filter, onChange} = properties; const {filter, onChange} = props;
const handleSkinClick = () => { const handleSkinClick = () => {
onChange('skin'); onChange('skin');

View File

@ -35,21 +35,21 @@ const NickNameBadge = styled(Badge)`
max-width: 100px; max-width: 100px;
`; `;
type ButtonLikeProperties = { type ButtonLikeProps = {
liked: boolean; liked: boolean;
}; };
const ButtonLike = styled.a<ButtonLikeProperties>` const ButtonLike = styled.a<ButtonLikeProps>`
${cssUtils.pointerCursor} ${cssUtils.pointerCursor}
i, span { i, span {
color: ${properties => properties.liked ? '#dc3545' : '#6c757d'}; color: ${props => props.liked ? '#dc3545' : '#6c757d'};
&:hover { &:hover {
color: ${properties => properties.liked ? '#dc3545' : '#343a40'}; color: ${props => props.liked ? '#dc3545' : '#343a40'};
} }
} }
`; `;
type Properties = { type Props = {
readonly item: LibraryItem; readonly item: LibraryItem;
readonly liked: boolean; readonly liked: boolean;
onAdd: (texture: LibraryItem) => Promise<void>; onAdd: (texture: LibraryItem) => Promise<void>;
@ -57,8 +57,8 @@ type Properties = {
onUploaderClick: (uploader: number) => void; onUploaderClick: (uploader: number) => void;
}; };
const Item: React.FC<Properties> = properties => { const Item: React.FC<Props> = props => {
const {item} = properties; const {item} = props;
const link = `${blessing.base_url}/skinlib/show/${item.tid}`; const link = `${blessing.base_url}/skinlib/show/${item.tid}`;
const preview = `${blessing.base_url}/preview/${item.tid}?height=150`; const preview = `${blessing.base_url}/preview/${item.tid}?height=150`;
@ -66,12 +66,12 @@ const Item: React.FC<Properties> = properties => {
const handleUploaderClick = (event: React.MouseEvent) => { const handleUploaderClick = (event: React.MouseEvent) => {
event.preventDefault(); event.preventDefault();
properties.onUploaderClick(item.uploader); props.onUploaderClick(item.uploader);
}; };
const handleHeartClick = (event: React.MouseEvent) => { const handleHeartClick = (event: React.MouseEvent) => {
event.preventDefault(); event.preventDefault();
properties.liked ? properties.onRemove(item) : properties.onAdd(item); props.liked ? props.onRemove(item) : props.onAdd(item);
}; };
return ( return (
@ -111,7 +111,7 @@ const Item: React.FC<Properties> = properties => {
</NickNameBadge> </NickNameBadge>
</div> </div>
<ButtonLike <ButtonLike
liked={properties.liked} liked={props.liked}
tabIndex={-1} tabIndex={-1}
onClick={handleHeartClick} onClick={handleHeartClick}
> >

View File

@ -4,7 +4,7 @@ import {t} from '@/scripts/i18n';
import setAsAvatar from './setAsAvatar'; import setAsAvatar from './setAsAvatar';
import {Card, DropdownButton} from './styles'; import {Card, DropdownButton} from './styles';
type Properties = { type Props = {
readonly item: ClosetItemType; readonly item: ClosetItemType;
readonly selected: boolean; readonly selected: boolean;
onClick: (item: ClosetItemType) => void; onClick: (item: ClosetItemType) => void;
@ -12,19 +12,19 @@ type Properties = {
onRemove: () => void; onRemove: () => void;
}; };
const ClosetItem: React.FC<Properties> = properties => { const ClosetItem: React.FC<Props> = props => {
const {item} = properties; const {item} = props;
const preview = `${blessing.base_url}/preview/${item.tid}?height=150`; const preview = `${blessing.base_url}/preview/${item.tid}?height=150`;
const previewPNG = `${preview}&png`; const previewPNG = `${preview}&png`;
const handleItemClick = () => { const handleItemClick = () => {
properties.onClick(item); props.onClick(item);
}; };
const handleSetAsAvatar = async () => setAsAvatar(item.tid); const handleSetAsAvatar = async () => setAsAvatar(item.tid);
return ( return (
<Card className={`card mr-3 mb-3 ${properties.selected ? 'shadow' : ''}`}> <Card className={`card mr-3 mb-3 ${props.selected ? 'shadow' : ''}`}>
<div className='card-body' onClick={handleItemClick}> <div className='card-body' onClick={handleItemClick}>
<picture> <picture>
<source srcSet={preview} type='image/webp'/> <source srcSet={preview} type='image/webp'/>
@ -49,10 +49,10 @@ const ClosetItem: React.FC<Properties> = properties => {
<i className='fas fa-cog'/> <i className='fas fa-cog'/>
</DropdownButton> </DropdownButton>
<div className='dropdown-menu'> <div className='dropdown-menu'>
<a href='#' className='dropdown-item' onClick={properties.onRename}> <a href='#' className='dropdown-item' onClick={props.onRename}>
{t('user.renameItem')} {t('user.renameItem')}
</a> </a>
<a href='#' className='dropdown-item' onClick={properties.onRemove}> <a href='#' className='dropdown-item' onClick={props.onRemove}>
{t('user.removeItem')} {t('user.removeItem')}
</a> </a>
<a <a

View File

@ -10,7 +10,7 @@ import {useEffect, useState} from 'react';
const baseUrl = blessing.base_url; const baseUrl = blessing.base_url;
type Properties = { type Props = {
readonly show: boolean; readonly show: boolean;
readonly canAdd: boolean; readonly canAdd: boolean;
readonly skin?: number; readonly skin?: number;
@ -18,13 +18,13 @@ type Properties = {
onClose: () => void; onClose: () => void;
}; };
const ModalApply: React.FC<Properties> = properties => { const ModalApply: React.FC<Props> = props => {
const [players, setPlayers] = useState<Player[]>([]); const [players, setPlayers] = useState<Player[]>([]);
const [search, setSearch] = useState(''); const [search, setSearch] = useState('');
const [isLoading, setIsLoading] = useState(false); const [isLoading, setIsLoading] = useState(false);
useEffect(() => { useEffect(() => {
if (!properties.show) { if (!props.show) {
return; return;
} }
@ -36,7 +36,7 @@ const ModalApply: React.FC<Properties> = properties => {
}; };
getPlayers(); getPlayers();
}, [properties.show]); }, [props.show]);
const handleSearch = (event: React.ChangeEvent<HTMLInputElement>) => { const handleSearch = (event: React.ChangeEvent<HTMLInputElement>) => {
setSearch(event.target.value); setSearch(event.target.value);
@ -46,8 +46,8 @@ const ModalApply: React.FC<Properties> = properties => {
const {code, message} = await fetch.put<fetch.ResponseBody>( const {code, message} = await fetch.put<fetch.ResponseBody>(
urls.user.player.set(player.pid), urls.user.player.set(player.pid),
{ {
skin: properties.skin, skin: props.skin,
cape: properties.cape, cape: props.cape,
}, },
); );
if (code === 0) { if (code === 0) {
@ -61,11 +61,11 @@ const ModalApply: React.FC<Properties> = properties => {
return ( return (
<Modal <Modal
flexFooter flexFooter
show={properties.show} show={props.show}
id='modal-apply' id='modal-apply'
title={t('user.closet.use-as.title')} title={t('user.closet.use-as.title')}
footer={<></>} footer={<></>}
onClose={properties.onClose} onClose={props.onClose}
> >
{isLoading {isLoading
? <Loading/> ? <Loading/>

View File

@ -5,25 +5,25 @@ import ReactDOM from 'react-dom';
const Viewer = React.lazy(async () => import('@/components/Viewer')); const Viewer = React.lazy(async () => import('@/components/Viewer'));
type Properties = { type Props = {
skin?: string; skin?: string;
cape?: string; cape?: string;
children: React.ReactNode; children: React.ReactNode;
isAlex: boolean; isAlex: boolean;
}; };
const Previewer: React.FC<Properties> = properties => { const Previewer: React.FC<Props> = props => {
const container = useMount('#previewer'); const container = useMount('#previewer');
const skin = properties.skin ? `${blessing.base_url}/textures/${properties.skin}` : ''; const skin = props.skin ? `${blessing.base_url}/textures/${props.skin}` : '';
const cape = properties.cape ? `${blessing.base_url}/textures/${properties.cape}` : ''; const cape = props.cape ? `${blessing.base_url}/textures/${props.cape}` : '';
return ( return (
container container
&& ReactDOM.createPortal( && ReactDOM.createPortal(
<React.Suspense fallback={<ViewerSkeleton/>}> <React.Suspense fallback={<ViewerSkeleton/>}>
<Viewer showIndicator skin={skin} cape={cape} isAlex={properties.isAlex}> <Viewer showIndicator skin={skin} cape={cape} isAlex={props.isAlex}>
{properties.children} {props.children}
</Viewer> </Viewer>
</React.Suspense>, </React.Suspense>,
container, container,

View File

@ -1,6 +1,6 @@
import React from 'react'; import React from 'react';
type Properties = { type Props = {
readonly name: string; readonly name: string;
readonly icon: string; readonly icon: string;
readonly color: string; readonly color: string;
@ -9,24 +9,24 @@ type Properties = {
readonly unit: string; readonly unit: string;
}; };
const InfoBox: React.FC<Properties> = properties => { const InfoBox: React.FC<Props> = props => {
const total = Math.trunc(properties.used + properties.unused); const total = Math.trunc(props.used + props.unused);
const percentage = (properties.used / total) * 100; const percentage = (props.used / total) * 100;
return ( return (
<div className={`info-box bg-${properties.color}`}> <div className={`info-box bg-${props.color}`}>
<span className='info-box-icon'> <span className='info-box-icon'>
<i className={`fas fa-${properties.icon}`}/> <i className={`fas fa-${props.icon}`}/>
</span> </span>
<div className='info-box-content'> <div className='info-box-content'>
<span className='info-box-text'>{properties.name}</span> <span className='info-box-text'>{props.name}</span>
<span className='info-box-number'> <span className='info-box-number'>
<b>{properties.used}</b> <b>{props.used}</b>
{' '} {' '}
/ /
{total} {total}
{' '} {' '}
{properties.unit} {props.unit}
</span> </span>
<div className='progress'> <div className='progress'>
<div className='progress-bar' style={{width: `${percentage}%`}}/> <div className='progress-bar' style={{width: `${percentage}%`}}/>

View File

@ -2,7 +2,7 @@ import {t} from '@/scripts/i18n';
import React from 'react'; import React from 'react';
import * as scoreUtils from './scoreUtils'; import * as scoreUtils from './scoreUtils';
type Properties = { type Props = {
readonly isLoading: boolean; readonly isLoading: boolean;
readonly lastSign: Date; readonly lastSign: Date;
readonly canSignAfterZero: boolean; readonly canSignAfterZero: boolean;
@ -10,8 +10,8 @@ type Properties = {
readonly onClick: React.MouseEventHandler<HTMLButtonElement>; readonly onClick: React.MouseEventHandler<HTMLButtonElement>;
}; };
const SignButton: React.FC<Properties> = properties => { const SignButton: React.FC<Props> = props => {
const {lastSign, signGap, canSignAfterZero} = properties; const {lastSign, signGap, canSignAfterZero} = props;
const remainingTime = scoreUtils.remainingTime( const remainingTime = scoreUtils.remainingTime(
lastSign, lastSign,
signGap, signGap,
@ -24,8 +24,8 @@ const SignButton: React.FC<Properties> = properties => {
<button <button
className='btn bg-gradient-primary pl-4 pr-4' className='btn bg-gradient-primary pl-4 pr-4'
role='button' role='button'
disabled={!canSign || properties.isLoading} disabled={!canSign || props.isLoading}
onClick={properties.onClick} onClick={props.onClick}
> >
<i className='far fa-calendar-check' aria-hidden='true'/> <i className='far fa-calendar-check' aria-hidden='true'/>
{' '} {' '}

View File

@ -2,13 +2,13 @@ import Modal from '@/components/Modal';
import {t} from '@/scripts/i18n'; import {t} from '@/scripts/i18n';
import {useState} from 'react'; import {useState} from 'react';
type Properties = { type Props = {
readonly show: boolean; readonly show: boolean;
onCreate: (name: string, redirect: string) => Promise<void>; onCreate: (name: string, redirect: string) => Promise<void>;
onClose: () => void; onClose: () => void;
}; };
const ModalCreate: React.FC<Properties> = properties => { const ModalCreate: React.FC<Props> = props => {
const [name, setName] = useState(''); const [name, setName] = useState('');
const [url, setUrl] = useState(''); const [url, setUrl] = useState('');
@ -21,7 +21,7 @@ const ModalCreate: React.FC<Properties> = properties => {
}; };
const handleComplete = () => { const handleComplete = () => {
properties.onCreate(name, url); props.onCreate(name, url);
}; };
const handleDismiss = () => { const handleDismiss = () => {
@ -31,10 +31,10 @@ const ModalCreate: React.FC<Properties> = properties => {
return ( return (
<Modal <Modal
show={properties.show} show={props.show}
onConfirm={handleComplete} onConfirm={handleComplete}
onDismiss={handleDismiss} onDismiss={handleDismiss}
onClose={properties.onClose} onClose={props.onClose}
> >
<div className='form-group'> <div className='form-group'>
<label htmlFor='new-app-name'>{t('user.oauth.name')}</label> <label htmlFor='new-app-name'>{t('user.oauth.name')}</label>

View File

@ -3,15 +3,15 @@ import type {App} from './types';
import ButtonEdit from '@/components/ButtonEdit'; import ButtonEdit from '@/components/ButtonEdit';
import {t} from '@/scripts/i18n'; import {t} from '@/scripts/i18n';
type Properties = { type Props = {
readonly app: App; readonly app: App;
readonly onEditName: React.MouseEventHandler<HTMLAnchorElement>; readonly onEditName: React.MouseEventHandler<HTMLAnchorElement>;
readonly onEditRedirect: React.MouseEventHandler<HTMLAnchorElement>; readonly onEditRedirect: React.MouseEventHandler<HTMLAnchorElement>;
readonly onDelete: React.MouseEventHandler<HTMLButtonElement>; readonly onDelete: React.MouseEventHandler<HTMLButtonElement>;
}; };
const Row: React.FC<Properties> = properties => { const Row: React.FC<Props> = props => {
const {app} = properties; const {app} = props;
return ( return (
<tr> <tr>
@ -20,7 +20,7 @@ const Row: React.FC<Properties> = properties => {
<span>{app.name}</span> <span>{app.name}</span>
<ButtonEdit <ButtonEdit
title={t('user.oauth.modifyName')} title={t('user.oauth.modifyName')}
onClick={properties.onEditName} onClick={props.onEditName}
/> />
</td> </td>
<td>{app.secret}</td> <td>{app.secret}</td>
@ -28,11 +28,11 @@ const Row: React.FC<Properties> = properties => {
<span>{app.redirect}</span> <span>{app.redirect}</span>
<ButtonEdit <ButtonEdit
title={t('user.oauth.modifyUrl')} title={t('user.oauth.modifyUrl')}
onClick={properties.onEditRedirect} onClick={props.onEditRedirect}
/> />
</td> </td>
<td> <td>
<button className='btn btn-danger' onClick={properties.onDelete}> <button className='btn btn-danger' onClick={props.onDelete}>
{t('report.delete')} {t('report.delete')}
</button> </button>
</td> </td>

View File

@ -13,13 +13,13 @@ type Extra = {
length: string; length: string;
}; };
type Properties = { type Props = {
readonly show: boolean; readonly show: boolean;
onAdd: (player: Player) => void; onAdd: (player: Player) => void;
onClose: () => void; onClose: () => void;
}; };
const ModalAddPlayer: React.FC<Properties> = properties => { const ModalAddPlayer: React.FC<Props> = props => {
const [name, setName] = useState(''); const [name, setName] = useState('');
const handleNameChange = (event: React.ChangeEvent<HTMLInputElement>) => { const handleNameChange = (event: React.ChangeEvent<HTMLInputElement>) => {
@ -36,7 +36,7 @@ const ModalAddPlayer: React.FC<Properties> = properties => {
}); });
if (code === 0) { if (code === 0) {
toast.success(message); toast.success(message);
properties.onAdd(player); props.onAdd(player);
} else { } else {
toast.error(message); toast.error(message);
} }
@ -44,7 +44,7 @@ const ModalAddPlayer: React.FC<Properties> = properties => {
const handleClose = () => { const handleClose = () => {
setName(''); setName('');
properties.onClose(); props.onClose();
}; };
const {score, cost, rule, length} = blessing.extra as Extra; const {score, cost, rule, length} = blessing.extra as Extra;
@ -52,7 +52,7 @@ const ModalAddPlayer: React.FC<Properties> = properties => {
return ( return (
<Modal <Modal
show={properties.show} show={props.show}
title={t('user.player.add-player')} title={t('user.player.add-player')}
onConfirm={handleConfirm} onConfirm={handleConfirm}
onClose={handleClose} onClose={handleClose}

View File

@ -2,13 +2,13 @@ import Modal from '@/components/Modal';
import {t} from '@/scripts/i18n'; import {t} from '@/scripts/i18n';
import {useState} from 'react'; import {useState} from 'react';
type Properties = { type Props = {
readonly show: boolean; readonly show: boolean;
onSubmit: (skin: boolean, cape: boolean) => Promise<void>; onSubmit: (skin: boolean, cape: boolean) => Promise<void>;
onClose: () => void; onClose: () => void;
}; };
const ModalReset: React.FC<Properties> = properties => { const ModalReset: React.FC<Props> = props => {
const [skin, setSkin] = useState(false); const [skin, setSkin] = useState(false);
const [cape, setCape] = useState(false); const [cape, setCape] = useState(false);
@ -21,18 +21,18 @@ const ModalReset: React.FC<Properties> = properties => {
}; };
const handleConfirm = () => { const handleConfirm = () => {
properties.onSubmit(skin, cape); props.onSubmit(skin, cape);
}; };
const handleClose = () => { const handleClose = () => {
setSkin(false); setSkin(false);
setCape(false); setCape(false);
properties.onClose(); props.onClose();
}; };
return ( return (
<Modal <Modal
show={properties.show} show={props.show}
title={t('user.chooseClearTexture')} title={t('user.chooseClearTexture')}
onConfirm={handleConfirm} onConfirm={handleConfirm}
onClose={handleClose} onClose={handleClose}

View File

@ -7,13 +7,13 @@ import Viewer2d from './Viewer2d';
const Viewer3d = React.lazy(async () => import('@/components/Viewer')); const Viewer3d = React.lazy(async () => import('@/components/Viewer'));
type Properties = { type Props = {
skin: string; skin: string;
cape: string; cape: string;
isAlex: boolean; isAlex: boolean;
}; };
const Previewer: React.FC<Properties> = properties => { const Previewer: React.FC<Props> = props => {
const [is3d, setIs3d] = useState(true); const [is3d, setIs3d] = useState(true);
const container = useMount('#previewer'); const container = useMount('#previewer');
@ -28,7 +28,7 @@ const Previewer: React.FC<Properties> = properties => {
</button> </button>
); );
const {skin, cape, isAlex} = properties; const {skin, cape, isAlex} = props;
return ( return (
container container

View File

@ -4,7 +4,7 @@ import {t} from '@/scripts/i18n';
import * as cssUtils from '@/styles/utils'; import * as cssUtils from '@/styles/utils';
import {css} from '@emotion/react'; import {css} from '@emotion/react';
type Properties = { type Props = {
player: Player; player: Player;
selected: boolean; selected: boolean;
onClick: React.MouseEventHandler; onClick: React.MouseEventHandler;
@ -13,19 +13,19 @@ type Properties = {
onDelete: (player: Player) => Promise<void>; onDelete: (player: Player) => Promise<void>;
}; };
const Row: React.FC<Properties> = properties => { const Row: React.FC<Props> = props => {
const {player} = properties; const {player} = props;
const handleEdit = () => { const handleEdit = () => {
properties.onEditName(player); props.onEditName(player);
}; };
const handleDelete = () => { const handleDelete = () => {
properties.onDelete(player); props.onDelete(player);
}; };
const selected const selected
= properties.selected = props.selected
&& css` && css`
background: #efefef; background: #efefef;
.dark-mode & { .dark-mode & {
@ -34,14 +34,14 @@ const Row: React.FC<Properties> = properties => {
`; `;
return ( return (
<tr css={[cssUtils.pointerCursor, selected]} onClick={properties.onClick}> <tr css={[cssUtils.pointerCursor, selected]} onClick={props.onClick}>
<td>{player.pid}</td> <td>{player.pid}</td>
<td> <td>
<span>{player.name}</span> <span>{player.name}</span>
<ButtonEdit title={t('user.player.edit-pname')} onClick={handleEdit}/> <ButtonEdit title={t('user.player.edit-pname')} onClick={handleEdit}/>
</td> </td>
<td className='d-flex'> <td className='d-flex'>
<button className='btn btn-warning' onClick={properties.onReset}> <button className='btn btn-warning' onClick={props.onReset}>
{t('user.player.delete-texture')} {t('user.player.delete-texture')}
</button> </button>
<button className='btn btn-danger ml-2' onClick={handleDelete}> <button className='btn btn-danger ml-2' onClick={handleDelete}>

View File

@ -13,13 +13,13 @@ const TexturePreview = styled.div`
} }
`; `;
type Properties = { type Props = {
readonly skin: string; readonly skin: string;
readonly cape: string; readonly cape: string;
readonly children: React.ReactNode; readonly children: React.ReactNode;
}; };
const Viewer2d: React.FC<Properties> = properties => ( const Viewer2d: React.FC<Props> = props => (
<div className='card'> <div className='card'>
<div className='card-header'> <div className='card-header'>
<h3 className='card-title'>{t('general.texturePreview')}</h3> <h3 className='card-title'>{t('general.texturePreview')}</h3>
@ -27,18 +27,18 @@ const Viewer2d: React.FC<Properties> = properties => (
<div className='card-body'> <div className='card-body'>
<TexturePreview className='mb-5'> <TexturePreview className='mb-5'>
<span>{t('general.skin')}</span> <span>{t('general.skin')}</span>
{properties.skin {props.skin
? <img src={properties.skin} alt={t('general.skin')}/> ? <img src={props.skin} alt={t('general.skin')}/>
: <span>{t('user.player.texture-empty')}</span>} : <span>{t('user.player.texture-empty')}</span>}
</TexturePreview> </TexturePreview>
<TexturePreview className='mt-5'> <TexturePreview className='mt-5'>
<span>{t('general.cape')}</span> <span>{t('general.cape')}</span>
{properties.cape {props.cape
? <img src={properties.cape} alt={t('general.cape')}/> ? <img src={props.cape} alt={t('general.cape')}/>
: <span>{t('user.player.texture-empty')}</span>} : <span>{t('user.player.texture-empty')}</span>}
</TexturePreview> </TexturePreview>
</div> </div>
<div className='card-footer'>{properties.children}</div> <div className='card-footer'>{props.children}</div>
</div> </div>
); );

View File

@ -6,12 +6,11 @@ import {expect} from 'vitest';
it('click to select file', () => { it('click to select file', () => {
const {getAllByText} = render( const {getAllByText} = render(
<FileInput <FileInput
file={null} file={null}
onChange={() => { onChange={() => {
/* */ /* */
}} }}
/> />,);
);
fireEvent.click(getAllByText(t('skinlib.upload.select-file'))[1]); fireEvent.click(getAllByText(t('skinlib.upload.select-file'))[1]);
}); });
@ -20,12 +19,11 @@ it('display file name', () => {
const file = new File([], 'f.txt'); const file = new File([], 'f.txt');
const {queryByText} = render( const {queryByText} = render(
<FileInput <FileInput
file={file} file={file}
onChange={() => { onChange={() => {
/* */ /* */
}} }}
/> />,);
);
expect(queryByText('f.txt')).toBeInTheDocument(); expect(queryByText('f.txt')).toBeInTheDocument();
}); });

View File

@ -85,12 +85,11 @@ describe('modal body', () => {
it('input control type', () => { it('input control type', () => {
const {getByPlaceholderText} = render( const {getByPlaceholderText} = render(
<Modal <Modal
show show
mode='prompt' mode='prompt'
placeholder='password' placeholder='password'
inputType='password' inputType='password'
/> />,);
);
expect(getByPlaceholderText('password')).toHaveAttribute( expect(getByPlaceholderText('password')).toHaveAttribute(
'type', 'type',
'password', 'password',
@ -169,13 +168,12 @@ describe('"prompt" mode', () => {
const reject = vi.fn(); const reject = vi.fn();
const {getByPlaceholderText, getByText} = render( const {getByPlaceholderText, getByText} = render(
<Modal <Modal
show show
mode='prompt' mode='prompt'
placeholder='hint' placeholder='hint'
onConfirm={resolve} onConfirm={resolve}
onDismiss={reject} onDismiss={reject}
/> />,);
);
fireEvent.change(getByPlaceholderText('hint'), { fireEvent.change(getByPlaceholderText('hint'), {
target: {value: 'my'}, target: {value: 'my'},
}); });
@ -201,13 +199,12 @@ describe('"prompt" mode', () => {
const resolve = vi.fn(); const resolve = vi.fn();
const {getByText, getByLabelText} = render( const {getByText, getByLabelText} = render(
<Modal <Modal
show show
mode='prompt' mode='prompt'
inputType='radios' inputType='radios'
choices={choices} choices={choices}
onConfirm={resolve} onConfirm={resolve}
/> />,);
);
fireEvent.click(getByLabelText('B')); fireEvent.click(getByLabelText('B'));
fireEvent.click(getByText(t('general.confirm'))); fireEvent.click(getByText(t('general.confirm')));
expect(resolve).toBeCalledWith({value: 'b'}); expect(resolve).toBeCalledWith({value: 'b'});
@ -219,14 +216,13 @@ describe('"prompt" mode', () => {
const validator = vi.fn().mockReturnValue(true); const validator = vi.fn().mockReturnValue(true);
const {getByText} = render( const {getByText} = render(
<Modal <Modal
show show
mode='prompt' mode='prompt'
input='val' input='val'
validator={validator} validator={validator}
onConfirm={resolve} onConfirm={resolve}
onDismiss={reject} onDismiss={reject}
/> />,);
);
fireEvent.click(getByText(t('general.confirm'))); fireEvent.click(getByText(t('general.confirm')));
expect(resolve).toBeCalledWith({value: 'val'}); expect(resolve).toBeCalledWith({value: 'val'});
expect(reject).not.toBeCalled(); expect(reject).not.toBeCalled();
@ -239,14 +235,13 @@ describe('"prompt" mode', () => {
const validator = vi.fn().mockReturnValue(message); const validator = vi.fn().mockReturnValue(message);
const {getByText, queryByText} = render( const {getByText, queryByText} = render(
<Modal <Modal
show show
mode='prompt' mode='prompt'
input='val' input='val'
validator={validator} validator={validator}
onConfirm={resolve} onConfirm={resolve}
onDismiss={reject} onDismiss={reject}
/> />,);
);
expect(queryByText(message)).not.toBeInTheDocument(); expect(queryByText(message)).not.toBeInTheDocument();
fireEvent.click(getByText(t('general.confirm'))); fireEvent.click(getByText(t('general.confirm')));

View File

@ -1,4 +1,4 @@
<div class="card card-primary"> <div class="card card-primary mb-4">
<div class="card-header"> <div class="card-header">
<h3 class="card-title">{{ trans('admin.index.overview') }}</h3> <h3 class="card-title">{{ trans('admin.index.overview') }}</h3>
</div> </div>

View File

@ -1,27 +1,27 @@
<div class="row"> <div class="row">
<div class="col-md-6"> <div class="col-md-6">
<div class="small-box bg-info"> <div class="small-box text-bg-info">
<div class="inner"> <div class="inner">
<h3 class="stats">{{ sum.users }}</h3> <h3 class="stats">{{ sum.users }}</h3>
<p>{{ trans('admin.index.total-users') }}</p> <p>{{ trans('admin.index.total-users') }}</p>
</div> </div>
<div class="icon"><i class="fas fa-users"></i></div> <div class="small-box-icon"><i class="fas fa-users"></i></div>
<a href="{{ url('admin/users') }}" class="small-box-footer"> <a href="{{ url('admin/users') }}" class="small-box-footer link-light link-underline-opacity-0 link-underline-opacity-50-hover">
{{ trans('general.user-manage') }}&nbsp; {{ trans('general.user-manage') }}
<i class="fa fa-arrow-circle-right"></i> <i class="fa fa-arrow-circle-right"></i>
</a> </a>
</div> </div>
</div> </div>
<div class="col-md-6"> <div class="col-md-6">
<div class="small-box bg-green"> <div class="small-box text-bg-success">
<div class="inner"> <div class="inner">
<h3 class="stats">{{ sum.players }}</h3> <h3 class="stats">{{ sum.players }}</h3>
<p>{{ trans('admin.index.total-players') }}</p> <p>{{ trans('admin.index.total-players') }}</p>
</div> </div>
<div class="icon"><i class="fas fa-gamepad"></i></div> <div class="small-box-icon"><i class="fas fa-gamepad"></i></div>
<a href="{{ url('admin/players') }}" class="small-box-footer"> <a href="{{ url('admin/players') }}" class="small-box-footer link-light link-underline-opacity-0 link-underline-opacity-50-hover">
{{ trans('general.player-manage') }}&nbsp; {{ trans('general.player-manage') }}
<i class="fa fa-arrow-circle-right"></i> <i class="fa fa-arrow-circle-right"></i>
</a> </a>
</div> </div>
@ -30,17 +30,17 @@
<div class="row"> <div class="row">
<div class="col-md-6"> <div class="col-md-6">
<div class="small-box bg-purple"> <div class="small-box text-bg-warning">
<div class="inner"> <div class="inner">
<h3 class="stats">{{ sum.textures }}</h3> <h3 class="stats">{{ sum.textures }}</h3>
<p>{{ trans('admin.index.total-textures') }}</p> <p>{{ trans('admin.index.total-textures') }}</p>
</div> </div>
<div class="icon"><i class="fas fa-file"></i></div> <div class="small-box-icon"><i class="fas fa-file"></i></div>
</div> </div>
</div> </div>
<div class="col-md-6"> <div class="col-md-6">
<div class="small-box bg-yellow"> <div class="small-box text-bg-danger">
<div class="inner"> <div class="inner">
{% if sum.storage > 1024 %} {% if sum.storage > 1024 %}
<h3 class="stats">{{ (sum.storage / 1024)|round(1) }}MB</h3> <h3 class="stats">{{ (sum.storage / 1024)|round(1) }}MB</h3>
@ -49,7 +49,7 @@
{% endif %} {% endif %}
<p>{{ trans('admin.index.disk-usage') }}</p> <p>{{ trans('admin.index.disk-usage') }}</p>
</div> </div>
<div class="icon"><i class="fas fa-hdd"></i></div> <div class="small-box-icon"><i class="fas fa-hdd"></i></div>
</div> </div>
</div> </div>
</div> </div>

View File

@ -3,7 +3,7 @@
<a class="nav-link {{ item.active ? 'active' }}" href="#"> <a class="nav-link {{ item.active ? 'active' }}" href="#">
<i class="nav-icon fas {{ item.icon }}"></i> <i class="nav-icon fas {{ item.icon }}"></i>
<p class="ml-1">{{ trans(item.title) }}</p> <p class="ml-1">{{ trans(item.title) }}</p>
<i class="right fas fa-angle-left"></i> <i class="nav-arrow right fas fa-angle-right"></i>
</a> </a>
<ul class="nav nav-treeview"> <ul class="nav nav-treeview">
{% for child in item.children %} {% for child in item.children %}

View File

@ -1,4 +1,4 @@
<aside class="app-sidebar bg-body shadow elevation-3 layout-fixed" data-bs-theme="{{ color_mode }}"> <aside class="app-sidebar bg-body shadow elevation-3 sidebar-expand-lg" data-bs-theme="{{ color_mode }}">
<a href="{{ url('/') }}" class="brand-link text-center"> <a href="{{ url('/') }}" class="brand-link text-center">
<span class="sidebar-brand brand-text font-weight-light">{{ site_name }}</span> <span class="sidebar-brand brand-text font-weight-light">{{ site_name }}</span>
</a> </a>
@ -7,8 +7,9 @@
<nav class="mt-2"> <nav class="mt-2">
<ul <ul
class="nav nav-pills nav-sidebar flex-column nav-child-indent sidebar-menu" class="nav nav-pills nav-sidebar flex-column nav-child-indent sidebar-menu"
data-widget="treeview" data-lte-toggle="treeview"
role="menu" role="menu"
data-accordion="false"
> >
{% if scope == 'user' %} {% if scope == 'user' %}

View File

@ -7,7 +7,7 @@
<body class="{{ dark_mode ? 'dark-mode' : '' }} hold-transition layout-top-nav"> <body class="{{ dark_mode ? 'dark-mode' : '' }} hold-transition layout-top-nav">
<div class="wrapper"> <div class="wrapper">
<nav class="main-header navbar navbar-expand navbar-{{ navbar_color }} navbar-{{ color_mode }} ml-0"> <nav class="app-header navbar navbar-expand bg-primary-subtle ml-0" data-bs-theme="{{ color_mode }}">
<div class="container"> <div class="container">
<div class="navbar-header"> <div class="navbar-header">
<a href="{{ url('/') }}" class="navbar-brand">{{ site_name }}</a> <a href="{{ url('/') }}" class="navbar-brand">{{ site_name }}</a>

View File

@ -5,12 +5,12 @@
<title>{% block title %}{% endblock %} - {{ site_name }}</title> <title>{% block title %}{% endblock %} - {{ site_name }}</title>
</head> </head>
<body class="{{ dark_mode ? 'dark-mode' : '' }} hold-transition"> <body class="{{ dark_mode ? 'dark-mode' : '' }} hold-transition layout-fixed">
<div class="app-wrapper"> <div class="app-wrapper">
{{ include('shared.header') }} {{ include('shared.header') }}
{{ include('shared.sidebar', {scope: 'user'}) }} {{ include('shared.sidebar', {scope: 'user'}) }}
<div class="content-wrapper"> <main class="app-main">
<div class="content-header"> <div class="app-content-header">
<div class="container-fluid"> <div class="container-fluid">
<div class="d-flex justify-content-between flex-wrap"> <div class="d-flex justify-content-between flex-wrap">
<div> <div>
@ -27,7 +27,7 @@
{% block content %}{% endblock %} {% block content %}{% endblock %}
</div> </div>
</section> </section>
</div> </main>
<footer class="app-footer"> <footer class="app-footer">
{{ include('shared.copyright') }} {{ include('shared.copyright') }}
</footer> </footer>

View File

@ -1,4 +1,4 @@
<div class="card card-primary"> <div class="card card-primary mb-4">
<div class="card-header"> <div class="card-header">
<h3 class="card-title"> <h3 class="card-title">
{{ trans('user.profile.avatar.title') }} {{ trans('user.profile.avatar.title') }}

View File

@ -1,4 +1,4 @@
<div class="card card-danger"> <div class="card card-danger mb-4">
<div class="card-header"> <div class="card-header">
<h3 class="card-title"> <h3 class="card-title">
{{ trans('user.profile.delete.title') }} {{ trans('user.profile.delete.title') }}

View File

@ -1,11 +1,11 @@
<form class="card card-warning" method="post" id="change-email"> <form class="card card-warning mb-4" method="post" id="change-email">
<div class="card-header"> <div class="card-header">
<h3 class="card-title"> <h3 class="card-title">
{{ trans('user.profile.email.title') }} {{ trans('user.profile.email.title') }}
</h3> </h3>
</div> </div>
<div class="card-body"> <div class="card-body">
<div class="form-group"> <div class="form-group mb-3">
<input type="email" class="form-control" name="email" required <input type="email" class="form-control" name="email" required
value="{{ user.email }}" value="{{ user.email }}"
placeholder="{{ trans('user.profile.email.new') }}"> placeholder="{{ trans('user.profile.email.new') }}">

View File

@ -1,4 +1,4 @@
<form class="card card-primary" method="post" id="change-nickname"> <form class="card card-primary mb-4" method="post" id="change-nickname">
<div class="card-header"> <div class="card-header">
<h3 class="card-title"> <h3 class="card-title">
{{ trans('user.profile.nickname.title') }} {{ trans('user.profile.nickname.title') }}

View File

@ -5,13 +5,13 @@
</h3> </h3>
</div> </div>
<div class="card-body"> <div class="card-body">
<div class="form-group"> <div class="form-group mb-3">
<label>{{ trans('user.profile.password.old') }}</label> <label>{{ trans('user.profile.password.old') }}</label>
<input type="password" class="form-control" name="oldPassword" <input type="password" class="form-control" name="oldPassword"
required autocomplete="current-password"> required autocomplete="current-password">
</div> </div>
<div class="form-group"> <div class="form-group mb-3">
<label>{{ trans('user.profile.password.new') }}</label> <label>{{ trans('user.profile.password.new') }}</label>
<input type="password" class="form-control" name="newPassword" required <input type="password" class="form-control" name="newPassword" required
minlength="8" maxlength="32" autocomplete="new-password"> minlength="8" maxlength="32" autocomplete="new-password">

View File

@ -1,6 +1,7 @@
{ {
"extends": "@tsconfig/vite-react", "extends": "@tsconfig/vite-react",
"compilerOptions": { "compilerOptions": {
"jsxImportSource": "@emotion/react",
"baseUrl": ".", "baseUrl": ".",
"paths": { "paths": {
"@/*": [ "@/*": [
@ -9,8 +10,7 @@
}, },
"allowJs": true, "allowJs": true,
"checkJs": true, "checkJs": true,
"skipLibCheck": false, "skipLibCheck": false
"jsxImportSource": "@emotion/react"
}, },
"include": [ "include": [
"resources/assets/src", "resources/assets/src",

View File

@ -1,6 +1,4 @@
import type {GetModuleInfo} from 'rollup'; import type {GetModuleInfo} from 'rollup';
import {execSync} from 'node:child_process';
import {env} from 'node:process';
import react from '@vitejs/plugin-react-swc'; import react from '@vitejs/plugin-react-swc';
import browserslistToEsbuild from 'browserslist-to-esbuild'; import browserslistToEsbuild from 'browserslist-to-esbuild';
import laravel from 'laravel-vite-plugin'; import laravel from 'laravel-vite-plugin';