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": {
"type": "git",
"url": "https://github.com/bs-community/TwigBridge.git",
"reference": "72d1481a08c145ca3b0020ee6c07c771bb2ca3bf"
"reference": "61c2084d29bc63c3fa173fc631d8354fdf1c01fb"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/bs-community/TwigBridge/zipball/72d1481a08c145ca3b0020ee6c07c771bb2ca3bf",
"reference": "72d1481a08c145ca3b0020ee6c07c771bb2ca3bf",
"url": "https://api.github.com/repos/bs-community/TwigBridge/zipball/61c2084d29bc63c3fa173fc631d8354fdf1c01fb",
"reference": "61c2084d29bc63c3fa173fc631d8354fdf1c01fb",
"shasum": ""
},
"require": {
@ -5829,7 +5829,7 @@
"support": {
"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",
@ -9387,16 +9387,16 @@
},
{
"name": "barryvdh/reflection-docblock",
"version": "v2.3.0",
"version": "v2.3.1",
"source": {
"type": "git",
"url": "https://github.com/barryvdh/ReflectionDocBlock.git",
"reference": "818be8de6af4d16ef3ad51ea9234b3d37026ee5f"
"reference": "b6ff9f93603561f50e53b64310495d20b8dff5d8"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/barryvdh/ReflectionDocBlock/zipball/818be8de6af4d16ef3ad51ea9234b3d37026ee5f",
"reference": "818be8de6af4d16ef3ad51ea9234b3d37026ee5f",
"url": "https://api.github.com/repos/barryvdh/ReflectionDocBlock/zipball/b6ff9f93603561f50e53b64310495d20b8dff5d8",
"reference": "b6ff9f93603561f50e53b64310495d20b8dff5d8",
"shasum": ""
},
"require": {
@ -9433,9 +9433,9 @@
}
],
"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",

View File

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

View File

@ -1,9 +1,9 @@
type Properties = {
type Props = {
readonly title?: string;
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}>
<i className='fas fa-edit'/>
</a>

View File

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

View File

@ -1,11 +1,11 @@
import * as fetch from '@/scripts/net';
import {useState} from 'react';
type Properties = {
type Props = {
readonly initMode: boolean;
};
const DarkModeButton: React.FC<Properties> = ({initMode}) => {
const DarkModeButton: React.FC<Props> = ({initMode}) => {
const [darkMode, setDarkMode] = useState(initMode);
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']);
type Properties = Omit<React.InputHTMLAttributes<HTMLInputElement>, 'onChange'> & {
type Props = Omit<React.InputHTMLAttributes<HTMLInputElement>, 'onChange'> & {
onChange: (value: string) => void;
};
const EmailSuggestion: React.FC<Properties> = props => {
const EmailSuggestion: React.FC<Props> = props => {
useEffect(() => {
emit('emailDomainsSuggestion', domainNames);
}, []);

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -3,7 +3,7 @@ import {useEffect, useState} from 'react';
export type ToastType = 'success' | 'info' | 'warning' | 'error';
type Properties = {
type Props = {
readonly type: ToastType;
readonly distance: number;
onClose: () => void | Promise<void>;
@ -29,7 +29,7 @@ const shadow = css`
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);
useEffect(() => {
@ -40,9 +40,9 @@ const Toast: React.FC<Properties> = properties => {
return () => {
clearTimeout(timer);
};
}, [properties.onClose]);
}, [props.onClose]);
const type = properties.type === 'error' ? 'danger' : properties.type;
const type = props.type === 'error' ? 'danger' : props.type;
const classes = [
`alert alert-${type}`,
@ -56,16 +56,16 @@ const Toast: React.FC<Properties> = properties => {
const role = type === 'success' || type === 'info' ? 'status' : 'alert';
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}>
<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>{properties.children}</span>
<span>{props.children}</span>
<button
type='button'
className='mr-2 ml-1 close'
onClick={properties.onClose}
onClick={props.onClose}
>
&times;
</button>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -9,7 +9,7 @@ import {
verificationStatusText,
} from './utils';
type Properties = {
type Props = {
readonly user: User;
readonly currentUser: User;
onEmailChange: () => void;
@ -21,8 +21,8 @@ type Properties = {
onDelete: () => void;
};
const Row: React.FC<Properties> = properties => {
const {user, currentUser} = properties;
const Row: React.FC<Props> = props => {
const {user, currentUser} = props;
const canModify = canModifyUser(user, currentUser);
@ -35,7 +35,7 @@ const Row: React.FC<Properties> = properties => {
<span className='ml-1'>
<ButtonEdit
title={t('admin.changeEmail')}
onClick={properties.onEmailChange}
onClick={props.onEmailChange}
/>
</span>
)}
@ -46,7 +46,7 @@ const Row: React.FC<Properties> = properties => {
<span className='ml-1'>
<ButtonEdit
title={t('admin.changeNickName')}
onClick={properties.onNicknameChange}
onClick={props.onNicknameChange}
/>
</span>
)}
@ -57,7 +57,7 @@ const Row: React.FC<Properties> = properties => {
<span className='ml-1'>
<ButtonEdit
title={t('admin.changeScore')}
onClick={properties.onScoreChange}
onClick={props.onScoreChange}
/>
</span>
)}
@ -68,7 +68,7 @@ const Row: React.FC<Properties> = properties => {
<span className='ml-1'>
<ButtonEdit
title={t('admin.changePermission')}
onClick={properties.onPermissionChange}
onClick={props.onPermissionChange}
/>
</span>
)}
@ -80,7 +80,7 @@ const Row: React.FC<Properties> = properties => {
className='ml-1'
href='#'
title={t('admin.toggleVerification')}
onClick={properties.onVerificationToggle}
onClick={props.onVerificationToggle}
>
{user.verified
? <i className='fas fa-toggle-on'/>
@ -93,14 +93,14 @@ const Row: React.FC<Properties> = properties => {
<button
className='btn btn-default mr-2'
disabled={!canModify}
onClick={properties.onPasswordChange}
onClick={props.onPasswordChange}
>
{t('admin.changePassword')}
</button>
<button
className='btn btn-danger'
disabled={!canModify || user.uid === currentUser.uid}
onClick={properties.onDelete}
onClick={props.onDelete}
>
{t('admin.deleteUser')}
</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 useBlessingExtra from '@/scripts/hooks/useBlessingExtra';
import useIsLargeScreen from '@/scripts/hooks/useIsLargeScreen';
@ -159,7 +159,7 @@ function UsersManagement() {
};
const handlePermissionChange = async (user: User, index: number) => {
const permissions: ModalInputProperties['choices'] = [
const permissions: ModalInputProps['choices'] = [
{text: t('admin.banned'), value: '-1'},
{text: t('admin.normal'), value: '0'},
];

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -2,7 +2,7 @@ import {t} from '@/scripts/i18n';
import React from 'react';
import * as scoreUtils from './scoreUtils';
type Properties = {
type Props = {
readonly isLoading: boolean;
readonly lastSign: Date;
readonly canSignAfterZero: boolean;
@ -10,8 +10,8 @@ type Properties = {
readonly onClick: React.MouseEventHandler<HTMLButtonElement>;
};
const SignButton: React.FC<Properties> = properties => {
const {lastSign, signGap, canSignAfterZero} = properties;
const SignButton: React.FC<Props> = props => {
const {lastSign, signGap, canSignAfterZero} = props;
const remainingTime = scoreUtils.remainingTime(
lastSign,
signGap,
@ -24,8 +24,8 @@ const SignButton: React.FC<Properties> = properties => {
<button
className='btn bg-gradient-primary pl-4 pr-4'
role='button'
disabled={!canSign || properties.isLoading}
onClick={properties.onClick}
disabled={!canSign || props.isLoading}
onClick={props.onClick}
>
<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 {useState} from 'react';
type Properties = {
type Props = {
readonly show: boolean;
onCreate: (name: string, redirect: string) => Promise<void>;
onClose: () => void;
};
const ModalCreate: React.FC<Properties> = properties => {
const ModalCreate: React.FC<Props> = props => {
const [name, setName] = useState('');
const [url, setUrl] = useState('');
@ -21,7 +21,7 @@ const ModalCreate: React.FC<Properties> = properties => {
};
const handleComplete = () => {
properties.onCreate(name, url);
props.onCreate(name, url);
};
const handleDismiss = () => {
@ -31,10 +31,10 @@ const ModalCreate: React.FC<Properties> = properties => {
return (
<Modal
show={properties.show}
show={props.show}
onConfirm={handleComplete}
onDismiss={handleDismiss}
onClose={properties.onClose}
onClose={props.onClose}
>
<div className='form-group'>
<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 {t} from '@/scripts/i18n';
type Properties = {
type Props = {
readonly app: App;
readonly onEditName: React.MouseEventHandler<HTMLAnchorElement>;
readonly onEditRedirect: React.MouseEventHandler<HTMLAnchorElement>;
readonly onDelete: React.MouseEventHandler<HTMLButtonElement>;
};
const Row: React.FC<Properties> = properties => {
const {app} = properties;
const Row: React.FC<Props> = props => {
const {app} = props;
return (
<tr>
@ -20,7 +20,7 @@ const Row: React.FC<Properties> = properties => {
<span>{app.name}</span>
<ButtonEdit
title={t('user.oauth.modifyName')}
onClick={properties.onEditName}
onClick={props.onEditName}
/>
</td>
<td>{app.secret}</td>
@ -28,11 +28,11 @@ const Row: React.FC<Properties> = properties => {
<span>{app.redirect}</span>
<ButtonEdit
title={t('user.oauth.modifyUrl')}
onClick={properties.onEditRedirect}
onClick={props.onEditRedirect}
/>
</td>
<td>
<button className='btn btn-danger' onClick={properties.onDelete}>
<button className='btn btn-danger' onClick={props.onDelete}>
{t('report.delete')}
</button>
</td>

View File

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

View File

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

View File

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

View File

@ -4,7 +4,7 @@ import {t} from '@/scripts/i18n';
import * as cssUtils from '@/styles/utils';
import {css} from '@emotion/react';
type Properties = {
type Props = {
player: Player;
selected: boolean;
onClick: React.MouseEventHandler;
@ -13,19 +13,19 @@ type Properties = {
onDelete: (player: Player) => Promise<void>;
};
const Row: React.FC<Properties> = properties => {
const {player} = properties;
const Row: React.FC<Props> = props => {
const {player} = props;
const handleEdit = () => {
properties.onEditName(player);
props.onEditName(player);
};
const handleDelete = () => {
properties.onDelete(player);
props.onDelete(player);
};
const selected
= properties.selected
= props.selected
&& css`
background: #efefef;
.dark-mode & {
@ -34,14 +34,14 @@ const Row: React.FC<Properties> = properties => {
`;
return (
<tr css={[cssUtils.pointerCursor, selected]} onClick={properties.onClick}>
<tr css={[cssUtils.pointerCursor, selected]} onClick={props.onClick}>
<td>{player.pid}</td>
<td>
<span>{player.name}</span>
<ButtonEdit title={t('user.player.edit-pname')} onClick={handleEdit}/>
</td>
<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')}
</button>
<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 cape: string;
readonly children: React.ReactNode;
};
const Viewer2d: React.FC<Properties> = properties => (
const Viewer2d: React.FC<Props> = props => (
<div className='card'>
<div className='card-header'>
<h3 className='card-title'>{t('general.texturePreview')}</h3>
@ -27,18 +27,18 @@ const Viewer2d: React.FC<Properties> = properties => (
<div className='card-body'>
<TexturePreview className='mb-5'>
<span>{t('general.skin')}</span>
{properties.skin
? <img src={properties.skin} alt={t('general.skin')}/>
{props.skin
? <img src={props.skin} alt={t('general.skin')}/>
: <span>{t('user.player.texture-empty')}</span>}
</TexturePreview>
<TexturePreview className='mt-5'>
<span>{t('general.cape')}</span>
{properties.cape
? <img src={properties.cape} alt={t('general.cape')}/>
{props.cape
? <img src={props.cape} alt={t('general.cape')}/>
: <span>{t('user.player.texture-empty')}</span>}
</TexturePreview>
</div>
<div className='card-footer'>{properties.children}</div>
<div className='card-footer'>{props.children}</div>
</div>
);

View File

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

View File

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

View File

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

View File

@ -3,7 +3,7 @@
<a class="nav-link {{ item.active ? 'active' }}" href="#">
<i class="nav-icon fas {{ item.icon }}"></i>
<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>
<ul class="nav nav-treeview">
{% 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">
<span class="sidebar-brand brand-text font-weight-light">{{ site_name }}</span>
</a>
@ -7,8 +7,9 @@
<nav class="mt-2">
<ul
class="nav nav-pills nav-sidebar flex-column nav-child-indent sidebar-menu"
data-widget="treeview"
data-lte-toggle="treeview"
role="menu"
data-accordion="false"
>
{% if scope == 'user' %}

View File

@ -7,7 +7,7 @@
<body class="{{ dark_mode ? 'dark-mode' : '' }} hold-transition layout-top-nav">
<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="navbar-header">
<a href="{{ url('/') }}" class="navbar-brand">{{ site_name }}</a>

View File

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

View File

@ -1,4 +1,4 @@
<div class="card card-primary">
<div class="card card-primary mb-4">
<div class="card-header">
<h3 class="card-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">
<h3 class="card-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">
<h3 class="card-title">
{{ trans('user.profile.email.title') }}
</h3>
</div>
<div class="card-body">
<div class="form-group">
<div class="form-group mb-3">
<input type="email" class="form-control" name="email" required
value="{{ user.email }}"
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">
<h3 class="card-title">
{{ trans('user.profile.nickname.title') }}

View File

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

View File

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

View File

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