add styles
This commit is contained in:
parent
9ab31e6c35
commit
534e185c55
@ -7,8 +7,8 @@
|
|||||||
transition: opacity 1300ms ease-in;
|
transition: opacity 1300ms ease-in;
|
||||||
|
|
||||||
.Card {
|
.Card {
|
||||||
width: 100px;
|
width: 80px;
|
||||||
height: 100px;
|
height: 80px;
|
||||||
font-family: 'Pacifico', sans-serif;
|
font-family: 'Pacifico', sans-serif;
|
||||||
font-size: 48px;
|
font-size: 48px;
|
||||||
display: flex;
|
display: flex;
|
||||||
@ -56,3 +56,13 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@media(max-width: 400px) {
|
||||||
|
.CardWrapper {
|
||||||
|
.Card {
|
||||||
|
width: 50px;
|
||||||
|
height: 50px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
@ -3,15 +3,8 @@
|
|||||||
position: relative;
|
position: relative;
|
||||||
border: 2px solid #fff;
|
border: 2px solid #fff;
|
||||||
border-radius: 10px;
|
border-radius: 10px;
|
||||||
width: 500px;
|
min-height: 500px;
|
||||||
height: 500px;
|
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 1fr 1fr 1fr;
|
gap: 15px;
|
||||||
grid-template-rows: 1fr 1fr 1fr 1fr;
|
padding: 20px;
|
||||||
grid-auto-flow: row;
|
|
||||||
grid-template-areas:
|
|
||||||
". . ."
|
|
||||||
". . ."
|
|
||||||
". . ."
|
|
||||||
". . .";
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
import { DiffLevel, FieldSize } from 'pages/Options/Options';
|
||||||
import React, { useCallback, useEffect, useState } from 'react';
|
import React, { useCallback, useEffect, useState } from 'react';
|
||||||
import { v4 as uuidv4 } from 'uuid';
|
import { v4 as uuidv4 } from 'uuid';
|
||||||
|
|
||||||
@ -34,7 +35,7 @@ const CardsField: React.FC<CardsFieldProps> = ({cards, isGameStarted = false, fi
|
|||||||
const [cardsState, setCardsState] = useState<CardComponentProps[]>(getInitialConfig(isNewGame));
|
const [cardsState, setCardsState] = useState<CardComponentProps[]>(getInitialConfig(isNewGame));
|
||||||
const [currentCardPare, setCurrentCardPare] = useState<CurrentCardPareType>({first: null, second: null})
|
const [currentCardPare, setCurrentCardPare] = useState<CurrentCardPareType>({first: null, second: null})
|
||||||
|
|
||||||
const {addMistakesCount} = useMatchGame();
|
const {addMistakesCount, filedSize, diffLevel} = useMatchGame();
|
||||||
|
|
||||||
function getInitialConfig(isNewGame: boolean): CardComponentProps[] {
|
function getInitialConfig(isNewGame: boolean): CardComponentProps[] {
|
||||||
if (!isNewGame) {
|
if (!isNewGame) {
|
||||||
@ -142,8 +143,25 @@ const CardsField: React.FC<CardsFieldProps> = ({cards, isGameStarted = false, fi
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getStyles(): React.CSSProperties {
|
||||||
|
const size = {
|
||||||
|
gridTemplateColumns: '',
|
||||||
|
gridTemplateRows: '',
|
||||||
|
};
|
||||||
|
|
||||||
|
if (diffLevel === DiffLevel.Easy) size.gridTemplateColumns = 'repeat(3, 1fr)';
|
||||||
|
if (diffLevel === DiffLevel.Normal) size.gridTemplateColumns = 'repeat(4, 1fr)';
|
||||||
|
if (diffLevel === DiffLevel.Hard) size.gridTemplateColumns = 'repeat(5, 1fr)';
|
||||||
|
|
||||||
|
if (filedSize === FieldSize.Small) size.gridTemplateRows = 'repeat(2, 1fr)';
|
||||||
|
if (filedSize === FieldSize.Normal) size.gridTemplateRows = 'repeat(4, 1fr)';
|
||||||
|
if (filedSize === FieldSize.Big) size.gridTemplateRows = 'repeat(6, 1fr)';
|
||||||
|
|
||||||
|
return size;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={cls.CardsField}>
|
<div className={cls.CardsField} style={getStyles()}>
|
||||||
{renderCards()}
|
{renderCards()}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
4
src/components/GoToMenuButton/GoToMenuButton.module.scss
Normal file
4
src/components/GoToMenuButton/GoToMenuButton.module.scss
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
.Button {
|
||||||
|
width: 45px;
|
||||||
|
height: 45px;
|
||||||
|
}
|
||||||
20
src/components/GoToMenuButton/GoToMenuButton.tsx
Normal file
20
src/components/GoToMenuButton/GoToMenuButton.tsx
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import Button from 'components/UI/Button/Button';
|
||||||
|
import { useMatchGame } from 'context/MatchGameContext';
|
||||||
|
|
||||||
|
import cls from './GoToMenuButton.module.scss';
|
||||||
|
|
||||||
|
const GoToMenuButton = () => {
|
||||||
|
const {setCurrentMenuItem, setIsGameStarted} = useMatchGame();
|
||||||
|
|
||||||
|
function onClickHandler() {
|
||||||
|
setIsGameStarted?.(false);
|
||||||
|
setCurrentMenuItem?.('menu')
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Button className={cls.Button} onClick={onClickHandler}>⇦</Button>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default GoToMenuButton;
|
||||||
@ -7,5 +7,6 @@
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
border: 2px solid #fff;
|
border: 2px solid #fff;
|
||||||
border-radius: 10px;
|
border-radius: 10px;
|
||||||
|
max-width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -2,11 +2,13 @@ import React from 'react';
|
|||||||
|
|
||||||
import cls from './BottomPanel.module.scss';
|
import cls from './BottomPanel.module.scss';
|
||||||
|
|
||||||
type BottomPanelProps = {}
|
type BottomPanelProps = {
|
||||||
|
style?: React.CSSProperties;
|
||||||
|
}
|
||||||
|
|
||||||
const BottomPanel: React.FC<BottomPanelProps> = ({children}) => {
|
const BottomPanel: React.FC<BottomPanelProps> = ({children, style}) => {
|
||||||
return (
|
return (
|
||||||
<div className={cls.BottomPanel}>
|
<div className={cls.BottomPanel} style={style}>
|
||||||
{ children }
|
{ children }
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -9,18 +9,32 @@
|
|||||||
font-size: 22px;
|
font-size: 22px;
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
letter-spacing: 3px;
|
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
color: #524f4e;
|
color: #524f4e;
|
||||||
background: white;
|
background: white;
|
||||||
box-shadow: 0 8px 15px rgba(0, 0, 0, .1);
|
box-shadow: 0 8px 15px rgba(0, 0, 0, .1);
|
||||||
transition: .3s;
|
transition: .3s;
|
||||||
|
|
||||||
&:hover, &:focus {
|
&:hover {
|
||||||
background: #7986cb;
|
background: #7986cb;
|
||||||
box-shadow: 0 15px 20px rgba(121, 134, 203, 0.4);
|
box-shadow: 0 15px 20px rgba(121, 134, 203, 0.4);
|
||||||
color: white;
|
color: white;
|
||||||
transform: translateY(-7px);
|
transform: translateY(-7px);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
&:active {
|
||||||
|
color: #524f4e;
|
||||||
|
transform: translateY(-5px);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 400px) {
|
||||||
|
.Button {
|
||||||
|
width: 120px;
|
||||||
|
margin: 5px;
|
||||||
|
font-size: 20px;
|
||||||
|
letter-spacing: normal;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
@ -3,12 +3,13 @@ import cls from './Button.module.scss';
|
|||||||
|
|
||||||
type ButtonProps = {
|
type ButtonProps = {
|
||||||
className?: string,
|
className?: string,
|
||||||
onClick: (e: React.MouseEvent<HTMLButtonElement>) => void
|
onClick: (e: React.MouseEvent<HTMLButtonElement>) => void;
|
||||||
|
style?: React.CSSProperties;
|
||||||
}
|
}
|
||||||
|
|
||||||
const Button: React.FC<ButtonProps> = ({children, className, onClick}) => {
|
const Button: React.FC<ButtonProps> = ({children, className, style, onClick}) => {
|
||||||
return (
|
return (
|
||||||
<button className={[cls.Button, className].join(' ')} onClick={onClick}>
|
<button className={[cls.Button, className].join(' ')} style={style} onClick={onClick}>
|
||||||
{children}
|
{children}
|
||||||
</button>
|
</button>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -22,21 +22,43 @@ interface MatchGameContextType {
|
|||||||
const MatchGameContext = React.createContext<MatchGameContextType>({});
|
const MatchGameContext = React.createContext<MatchGameContextType>({});
|
||||||
export const useMatchGame = () => useContext(MatchGameContext);
|
export const useMatchGame = () => useContext(MatchGameContext);
|
||||||
|
|
||||||
|
function getInitialSize() {
|
||||||
|
const sizeFromLocalStorage = localStorage.getItem('match-game:size');
|
||||||
|
return sizeFromLocalStorage ? JSON.parse(sizeFromLocalStorage) : FieldSize.Small;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getInitialDiffLevel() {
|
||||||
|
const diffLevelFromLocalStorage = localStorage.getItem('match-game:diffLevel');
|
||||||
|
return diffLevelFromLocalStorage ? JSON.parse(diffLevelFromLocalStorage) : DiffLevel.Easy;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getInitialTime() {
|
||||||
|
const timeFromLocalStorage = localStorage.getItem('match-game:time');
|
||||||
|
return timeFromLocalStorage ? JSON.parse(timeFromLocalStorage) : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getInitialMistakesCount() {
|
||||||
|
const mistakesCountFromLocalStorage = localStorage.getItem('match-game:mistakesCount');
|
||||||
|
return mistakesCountFromLocalStorage ? JSON.parse(mistakesCountFromLocalStorage) : 0;
|
||||||
|
}
|
||||||
|
|
||||||
export const MatchGameProvider: React.FC = ({children}) => {
|
export const MatchGameProvider: React.FC = ({children}) => {
|
||||||
const [mistakesCount, setMistakesCount] = useState(0)
|
const [mistakesCount, setMistakesCount] = useState<number>(getInitialMistakesCount)
|
||||||
const [gameTime, setGameTime] = useState(0);
|
const [gameTime, setGameTime] = useState<number>(getInitialTime);
|
||||||
const [currentMenuItem, setCurrentMenuItem] = useState<MenuItem>('menu');
|
const [currentMenuItem, setCurrentMenuItem] = useState<MenuItem>('menu');
|
||||||
const [isGameStarted, setIsGameStarted] = useState(false);
|
const [isGameStarted, setIsGameStarted] = useState(false);
|
||||||
const [isGameFinished, setIsGameFinished] = useState(false);
|
const [isGameFinished, setIsGameFinished] = useState(false);
|
||||||
const [filedSize, setFieldSize] = useState(FieldSize.Small);
|
const [filedSize, setFieldSize] = useState<number>(getInitialSize);
|
||||||
const [diffLevel, setDiffLevel] = useState(DiffLevel.Easy);
|
const [diffLevel, setDiffLevel] = useState<number>(getInitialDiffLevel);
|
||||||
|
|
||||||
const addMistakesCount = () => {
|
const addMistakesCount = () => {
|
||||||
|
localStorage.setItem('match-game:mistakesCount', JSON.stringify(mistakesCount + 1));
|
||||||
return setMistakesCount(prev => prev + 1)
|
return setMistakesCount(prev => prev + 1)
|
||||||
};
|
};
|
||||||
|
|
||||||
const changeGameTime = (time: number) => {
|
const changeGameTime = (time: number) => {
|
||||||
setGameTime(time);
|
setGameTime(time);
|
||||||
|
localStorage.setItem('match-game:time', JSON.stringify(time));
|
||||||
}
|
}
|
||||||
|
|
||||||
const resetGameProgress = () => {
|
const resetGameProgress = () => {
|
||||||
@ -44,12 +66,18 @@ export const MatchGameProvider: React.FC = ({children}) => {
|
|||||||
setGameTime(0);
|
setGameTime(0);
|
||||||
setIsGameStarted(false);
|
setIsGameStarted(false);
|
||||||
setIsGameFinished(false);
|
setIsGameFinished(false);
|
||||||
|
|
||||||
localStorage.removeItem('match-game:cards');
|
localStorage.removeItem('match-game:cards');
|
||||||
|
localStorage.removeItem('match-game:time');
|
||||||
|
localStorage.removeItem('match-game:mistakesCount');
|
||||||
}
|
}
|
||||||
|
|
||||||
const saveOptions = (options: OptionsType) => {
|
const saveOptions = (options: OptionsType) => {
|
||||||
setFieldSize(options.size);
|
setFieldSize(options.size);
|
||||||
setDiffLevel(options.diffLevel);
|
setDiffLevel(options.diffLevel);
|
||||||
|
|
||||||
|
localStorage.setItem('match-game:size', JSON.stringify(options.size));
|
||||||
|
localStorage.setItem('match-game:diffLevel', JSON.stringify(options.diffLevel));
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@ -0,0 +1,4 @@
|
|||||||
|
.MatchGameField {
|
||||||
|
width: 500px;
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
@ -1,3 +1,4 @@
|
|||||||
|
import GoToMenuButton from 'components/GoToMenuButton/GoToMenuButton';
|
||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import BottomPanel from 'components/UI/BottomPanel/BottomPanel';
|
import BottomPanel from 'components/UI/BottomPanel/BottomPanel';
|
||||||
import Button from 'components/UI/Button/Button';
|
import Button from 'components/UI/Button/Button';
|
||||||
@ -7,6 +8,8 @@ import Timer from 'components/UI/Timer/Timer';
|
|||||||
import { createCards, randomizeElementInArray } from 'src/utils';
|
import { createCards, randomizeElementInArray } from 'src/utils';
|
||||||
import { useMatchGame } from 'context/MatchGameContext';
|
import { useMatchGame } from 'context/MatchGameContext';
|
||||||
|
|
||||||
|
import cls from './MatchGame.module.scss';
|
||||||
|
|
||||||
interface MatchGameProps {
|
interface MatchGameProps {
|
||||||
isNewGame: boolean;
|
isNewGame: boolean;
|
||||||
}
|
}
|
||||||
@ -22,7 +25,6 @@ const MatchGame: React.FC<MatchGameProps> = ({isNewGame}) => {
|
|||||||
isGameFinished,
|
isGameFinished,
|
||||||
isGameStarted,
|
isGameStarted,
|
||||||
changeGameTime,
|
changeGameTime,
|
||||||
setCurrentMenuItem,
|
|
||||||
diffLevel,
|
diffLevel,
|
||||||
filedSize
|
filedSize
|
||||||
} = useMatchGame();
|
} = useMatchGame();
|
||||||
@ -51,12 +53,10 @@ const MatchGame: React.FC<MatchGameProps> = ({isNewGame}) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function retryGame() {
|
function retryGame() {
|
||||||
setIsGameFinished?.(false);
|
|
||||||
setIsGameStarted?.(false);
|
|
||||||
setIsTimerStarted?.(false);
|
setIsTimerStarted?.(false);
|
||||||
setCards(prev => randomizeElementInArray(prev.map(el => ({...el, disabled: true, isMatched: false, toggled: true}))))
|
|
||||||
|
|
||||||
resetGameProgress?.();
|
resetGameProgress?.();
|
||||||
|
|
||||||
|
setCards(prev => randomizeElementInArray(prev.map(el => ({...el, disabled: true, isMatched: false, toggled: true}))))
|
||||||
}
|
}
|
||||||
|
|
||||||
function finishGame() {
|
function finishGame() {
|
||||||
@ -68,15 +68,10 @@ const MatchGame: React.FC<MatchGameProps> = ({isNewGame}) => {
|
|||||||
setCards(randomizeElementInArray([...cards]));
|
setCards(randomizeElementInArray([...cards]));
|
||||||
}
|
}
|
||||||
|
|
||||||
function goToMenu() {
|
|
||||||
setIsGameStarted?.(false);
|
|
||||||
setCurrentMenuItem?.('menu');
|
|
||||||
}
|
|
||||||
|
|
||||||
function renderRetryStatePanel() {
|
function renderRetryStatePanel() {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Button onClick={goToMenu}>⇦</Button>
|
<GoToMenuButton/>
|
||||||
<Button onClick={retryGame}>RETRY</Button>
|
<Button onClick={retryGame}>RETRY</Button>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
@ -85,12 +80,12 @@ const MatchGame: React.FC<MatchGameProps> = ({isNewGame}) => {
|
|||||||
function renderStartStatePanel() {
|
function renderStartStatePanel() {
|
||||||
return isTimerStarted
|
return isTimerStarted
|
||||||
? <>
|
? <>
|
||||||
<Button onClick={goToMenu}>⇦</Button>
|
<GoToMenuButton/>
|
||||||
<Timer isStarted={isTimerStarted} startTime={gameTime || 0} isFinished={isTimerStopped} changeTime={changeGameTime}/>
|
<Timer isStarted={isTimerStarted} startTime={gameTime || 0} isFinished={isTimerStopped} changeTime={changeGameTime}/>
|
||||||
</>
|
</>
|
||||||
: <>
|
: <>
|
||||||
<Button onClick={goToMenu}>⇦</Button>
|
<GoToMenuButton/>
|
||||||
<Button onClick={startGame}>{isNewGame ? 'START' : 'Continue'}</Button>
|
<Button style={{width: 'auto', minWidth: '120px'}} onClick={startGame}>{isNewGame ? 'START' : 'CONTINUE'}</Button>
|
||||||
{isNewGame && <Button onClick={shuffleCards}>SHUFFLE</Button>}
|
{isNewGame && <Button onClick={shuffleCards}>SHUFFLE</Button>}
|
||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
@ -109,7 +104,7 @@ const MatchGame: React.FC<MatchGameProps> = ({isNewGame}) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<div className={cls.MatchGameField}>
|
||||||
<div style={{marginBottom: 20}}>
|
<div style={{marginBottom: 20}}>
|
||||||
{
|
{
|
||||||
isGameFinished
|
isGameFinished
|
||||||
@ -117,10 +112,10 @@ const MatchGame: React.FC<MatchGameProps> = ({isNewGame}) => {
|
|||||||
: <CardsField cards={cards} isGameStarted={isGameStarted} finishGameCallback={finishGame} isNewGame={isNewGame}/>
|
: <CardsField cards={cards} isGameStarted={isGameStarted} finishGameCallback={finishGame} isNewGame={isNewGame}/>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
<BottomPanel>
|
<BottomPanel style={{width: '100%'}}>
|
||||||
{ renderPanelByState(panelState) }
|
{ renderPanelByState(panelState) }
|
||||||
</BottomPanel>
|
</BottomPanel>
|
||||||
</>
|
</div>
|
||||||
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -9,6 +9,7 @@
|
|||||||
ul {
|
ul {
|
||||||
list-style: none;
|
list-style: none;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
li {
|
li {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
|||||||
@ -25,6 +25,9 @@ const MatchMenu: React.FC<GameMenuProps> = () => {
|
|||||||
|
|
||||||
function clickItemHandler(item: MenuItem) {
|
function clickItemHandler(item: MenuItem) {
|
||||||
setCurrentMenuItem?.(item);
|
setCurrentMenuItem?.(item);
|
||||||
|
if (item === 'exit') {
|
||||||
|
window.location = 'https://google.com' as any;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function canContinueGame() {
|
function canContinueGame() {
|
||||||
|
|||||||
@ -9,10 +9,12 @@
|
|||||||
border-radius: 10px;
|
border-radius: 10px;
|
||||||
color: #ffffff;
|
color: #ffffff;
|
||||||
margin-bottom: 20px;
|
margin-bottom: 20px;
|
||||||
|
max-width: 100%;
|
||||||
|
|
||||||
ul {
|
ul {
|
||||||
list-style: none;
|
list-style: none;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
h2 {
|
h2 {
|
||||||
@ -24,7 +26,7 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
font-size: 30px;
|
font-size: 1.5rem;
|
||||||
|
|
||||||
.button {
|
.button {
|
||||||
width: 45px;
|
width: 45px;
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
import GoToMenuButton from 'components/GoToMenuButton/GoToMenuButton';
|
||||||
import BottomPanel from 'components/UI/BottomPanel/BottomPanel';
|
import BottomPanel from 'components/UI/BottomPanel/BottomPanel';
|
||||||
import Button from 'components/UI/Button/Button';
|
import Button from 'components/UI/Button/Button';
|
||||||
import { useMatchGame } from 'context/MatchGameContext';
|
import { useMatchGame } from 'context/MatchGameContext';
|
||||||
@ -62,7 +63,7 @@ const Options: React.FC = () => {
|
|||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<BottomPanel>
|
<BottomPanel>
|
||||||
<Button onClick={() => setCurrentMenuItem?.('menu')}>⇦</Button>
|
<GoToMenuButton/>
|
||||||
<Button onClick={saveOptionHandler}>Save</Button>
|
<Button onClick={saveOptionHandler}>Save</Button>
|
||||||
</BottomPanel>
|
</BottomPanel>
|
||||||
</>
|
</>
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user