mirror of
https://github.com/invoke-ai/InvokeAI
synced 2024-08-30 20:32:17 +00:00
[Code Splitting] Bounding Box Options
Isolated all bounding box components to trigger unnecessary re-renders. Still need to fix bounding box triggering re-renders on the control panel inside the canvas itself. But the options panel should be a good to go with this change.
This commit is contained in:
parent
2e562742c1
commit
868e4b2db8
File diff suppressed because one or more lines are too long
2
frontend/dist/index.html
vendored
2
frontend/dist/index.html
vendored
@ -6,7 +6,7 @@
|
|||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title>InvokeAI - A Stable Diffusion Toolkit</title>
|
<title>InvokeAI - A Stable Diffusion Toolkit</title>
|
||||||
<link rel="shortcut icon" type="icon" href="./assets/favicon.0d253ced.ico" />
|
<link rel="shortcut icon" type="icon" href="./assets/favicon.0d253ced.ico" />
|
||||||
<script type="module" crossorigin src="./assets/index.f51a2644.js"></script>
|
<script type="module" crossorigin src="./assets/index.9ad2f384.js"></script>
|
||||||
<link rel="stylesheet" href="./assets/index.14c578ee.css">
|
<link rel="stylesheet" href="./assets/index.14c578ee.css">
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
|
@ -1,209 +0,0 @@
|
|||||||
import { Flex } from '@chakra-ui/react';
|
|
||||||
import { createSelector } from '@reduxjs/toolkit';
|
|
||||||
import _ from 'lodash';
|
|
||||||
|
|
||||||
import { BiHide, BiReset, BiShow } from 'react-icons/bi';
|
|
||||||
|
|
||||||
import {
|
|
||||||
RootState,
|
|
||||||
useAppDispatch,
|
|
||||||
useAppSelector,
|
|
||||||
} from '../../../../app/store';
|
|
||||||
import IAICheckbox from '../../../../common/components/IAICheckbox';
|
|
||||||
import IAIIconButton from '../../../../common/components/IAIIconButton';
|
|
||||||
|
|
||||||
import IAINumberInput from '../../../../common/components/IAINumberInput';
|
|
||||||
import IAISlider from '../../../../common/components/IAISlider';
|
|
||||||
import { roundDownToMultiple } from '../../../../common/util/roundDownToMultiple';
|
|
||||||
import {
|
|
||||||
InpaintingState,
|
|
||||||
setBoundingBoxDimensions,
|
|
||||||
setShouldLockBoundingBox,
|
|
||||||
setShouldShowBoundingBox,
|
|
||||||
setShouldShowBoundingBoxFill,
|
|
||||||
} from '../../../tabs/Inpainting/inpaintingSlice';
|
|
||||||
|
|
||||||
const boundingBoxDimensionsSelector = createSelector(
|
|
||||||
(state: RootState) => state.inpainting,
|
|
||||||
(inpainting: InpaintingState) => {
|
|
||||||
const {
|
|
||||||
canvasDimensions,
|
|
||||||
boundingBoxDimensions,
|
|
||||||
shouldShowBoundingBox,
|
|
||||||
shouldShowBoundingBoxFill,
|
|
||||||
shouldLockBoundingBox,
|
|
||||||
} = inpainting;
|
|
||||||
return {
|
|
||||||
canvasDimensions,
|
|
||||||
boundingBoxDimensions,
|
|
||||||
shouldShowBoundingBox,
|
|
||||||
shouldShowBoundingBoxFill,
|
|
||||||
shouldLockBoundingBox,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
{
|
|
||||||
memoizeOptions: {
|
|
||||||
resultEqualityCheck: _.isEqual,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
const BoundingBoxSettings = () => {
|
|
||||||
const dispatch = useAppDispatch();
|
|
||||||
const {
|
|
||||||
canvasDimensions,
|
|
||||||
boundingBoxDimensions,
|
|
||||||
shouldShowBoundingBox,
|
|
||||||
shouldShowBoundingBoxFill,
|
|
||||||
shouldLockBoundingBox,
|
|
||||||
} = useAppSelector(boundingBoxDimensionsSelector);
|
|
||||||
|
|
||||||
const handleChangeBoundingBoxWidth = (v: number) => {
|
|
||||||
dispatch(
|
|
||||||
setBoundingBoxDimensions({
|
|
||||||
...boundingBoxDimensions,
|
|
||||||
width: Math.floor(v),
|
|
||||||
})
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleChangeBoundingBoxHeight = (v: number) => {
|
|
||||||
dispatch(
|
|
||||||
setBoundingBoxDimensions({
|
|
||||||
...boundingBoxDimensions,
|
|
||||||
height: Math.floor(v),
|
|
||||||
})
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleChangeShouldShowBoundingBoxFill = () => {
|
|
||||||
dispatch(setShouldShowBoundingBoxFill(!shouldShowBoundingBoxFill));
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleChangeShouldLockBoundingBox = () => {
|
|
||||||
dispatch(setShouldLockBoundingBox(!shouldLockBoundingBox));
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleResetWidth = () => {
|
|
||||||
dispatch(
|
|
||||||
setBoundingBoxDimensions({
|
|
||||||
...boundingBoxDimensions,
|
|
||||||
width: Math.floor(canvasDimensions.width),
|
|
||||||
})
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleResetHeight = () => {
|
|
||||||
dispatch(
|
|
||||||
setBoundingBoxDimensions({
|
|
||||||
...boundingBoxDimensions,
|
|
||||||
height: Math.floor(canvasDimensions.height),
|
|
||||||
})
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleShowBoundingBox = () =>
|
|
||||||
dispatch(setShouldShowBoundingBox(!shouldShowBoundingBox));
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="inpainting-bounding-box-settings">
|
|
||||||
<div className="inpainting-bounding-box-header">
|
|
||||||
<p>Inpaint Box</p>
|
|
||||||
<IAIIconButton
|
|
||||||
aria-label="Toggle Bounding Box Visibility"
|
|
||||||
icon={
|
|
||||||
shouldShowBoundingBox ? <BiShow size={22} /> : <BiHide size={22} />
|
|
||||||
}
|
|
||||||
onClick={handleShowBoundingBox}
|
|
||||||
background={'none'}
|
|
||||||
padding={0}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="inpainting-bounding-box-settings-items">
|
|
||||||
<div className="inpainting-bounding-box-dimensions-slider-numberinput">
|
|
||||||
<IAISlider
|
|
||||||
isDisabled={shouldLockBoundingBox}
|
|
||||||
label="Box W"
|
|
||||||
min={64}
|
|
||||||
max={roundDownToMultiple(canvasDimensions.width, 64)}
|
|
||||||
step={64}
|
|
||||||
value={boundingBoxDimensions.width}
|
|
||||||
onChange={handleChangeBoundingBoxWidth}
|
|
||||||
width={'5rem'}
|
|
||||||
/>
|
|
||||||
<IAINumberInput
|
|
||||||
isDisabled={shouldLockBoundingBox}
|
|
||||||
value={boundingBoxDimensions.width}
|
|
||||||
onChange={handleChangeBoundingBoxWidth}
|
|
||||||
min={64}
|
|
||||||
max={roundDownToMultiple(canvasDimensions.width, 64)}
|
|
||||||
step={64}
|
|
||||||
width={'5rem'}
|
|
||||||
/>
|
|
||||||
<IAIIconButton
|
|
||||||
size={'sm'}
|
|
||||||
aria-label={'Reset Width'}
|
|
||||||
tooltip={'Reset Width'}
|
|
||||||
onClick={handleResetWidth}
|
|
||||||
icon={<BiReset />}
|
|
||||||
styleClass="inpainting-bounding-box-reset-icon-btn"
|
|
||||||
isDisabled={
|
|
||||||
shouldLockBoundingBox ||
|
|
||||||
canvasDimensions.width === boundingBoxDimensions.width
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="inpainting-bounding-box-dimensions-slider-numberinput">
|
|
||||||
<IAISlider
|
|
||||||
isDisabled={shouldLockBoundingBox}
|
|
||||||
label="Box H"
|
|
||||||
min={64}
|
|
||||||
max={roundDownToMultiple(canvasDimensions.height, 64)}
|
|
||||||
step={64}
|
|
||||||
value={boundingBoxDimensions.height}
|
|
||||||
onChange={handleChangeBoundingBoxHeight}
|
|
||||||
width={'5rem'}
|
|
||||||
/>
|
|
||||||
<IAINumberInput
|
|
||||||
isDisabled={shouldLockBoundingBox}
|
|
||||||
value={boundingBoxDimensions.height}
|
|
||||||
onChange={handleChangeBoundingBoxHeight}
|
|
||||||
min={64}
|
|
||||||
max={roundDownToMultiple(canvasDimensions.height, 64)}
|
|
||||||
step={64}
|
|
||||||
padding="0"
|
|
||||||
width={'5rem'}
|
|
||||||
/>
|
|
||||||
<IAIIconButton
|
|
||||||
size={'sm'}
|
|
||||||
aria-label={'Reset Height'}
|
|
||||||
tooltip={'Reset Height'}
|
|
||||||
onClick={handleResetHeight}
|
|
||||||
icon={<BiReset />}
|
|
||||||
styleClass="inpainting-bounding-box-reset-icon-btn"
|
|
||||||
isDisabled={
|
|
||||||
shouldLockBoundingBox ||
|
|
||||||
canvasDimensions.height === boundingBoxDimensions.height
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<Flex alignItems={'center'} justifyContent={'space-between'}>
|
|
||||||
<IAICheckbox
|
|
||||||
label="Darken Outside Box"
|
|
||||||
isChecked={shouldShowBoundingBoxFill}
|
|
||||||
onChange={handleChangeShouldShowBoundingBoxFill}
|
|
||||||
styleClass="inpainting-bounding-box-darken"
|
|
||||||
/>
|
|
||||||
<IAICheckbox
|
|
||||||
label="Lock Bounding Box"
|
|
||||||
isChecked={shouldLockBoundingBox}
|
|
||||||
onChange={handleChangeShouldLockBoundingBox}
|
|
||||||
styleClass="inpainting-bounding-box-darken"
|
|
||||||
/>
|
|
||||||
</Flex>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default BoundingBoxSettings;
|
|
@ -0,0 +1,28 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import {
|
||||||
|
RootState,
|
||||||
|
useAppDispatch,
|
||||||
|
useAppSelector,
|
||||||
|
} from '../../../../../app/store';
|
||||||
|
import IAICheckbox from '../../../../../common/components/IAICheckbox';
|
||||||
|
import { setShouldShowBoundingBoxFill } from '../../../../tabs/Inpainting/inpaintingSlice';
|
||||||
|
|
||||||
|
export default function BoundingBoxDarkenOutside() {
|
||||||
|
const dispatch = useAppDispatch();
|
||||||
|
const shouldShowBoundingBoxFill = useAppSelector(
|
||||||
|
(state: RootState) => state.inpainting.shouldShowBoundingBoxFill
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleChangeShouldShowBoundingBoxFill = () => {
|
||||||
|
dispatch(setShouldShowBoundingBoxFill(!shouldShowBoundingBoxFill));
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<IAICheckbox
|
||||||
|
label="Darken Outside Box"
|
||||||
|
isChecked={shouldShowBoundingBoxFill}
|
||||||
|
onChange={handleChangeShouldShowBoundingBoxFill}
|
||||||
|
styleClass="inpainting-bounding-box-darken"
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
@ -0,0 +1,128 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import IAISlider from '../../../../../common/components/IAISlider';
|
||||||
|
import IAINumberInput from '../../../../../common/components/IAINumberInput';
|
||||||
|
import IAIIconButton from '../../../../../common/components/IAIIconButton';
|
||||||
|
import { BiReset } from 'react-icons/bi';
|
||||||
|
|
||||||
|
import {
|
||||||
|
RootState,
|
||||||
|
useAppDispatch,
|
||||||
|
useAppSelector,
|
||||||
|
} from '../../../../../app/store';
|
||||||
|
import { createSelector } from '@reduxjs/toolkit';
|
||||||
|
import {
|
||||||
|
InpaintingState,
|
||||||
|
setBoundingBoxDimensions,
|
||||||
|
} from '../../../../tabs/Inpainting/inpaintingSlice';
|
||||||
|
|
||||||
|
import { roundDownToMultiple } from '../../../../../common/util/roundDownToMultiple';
|
||||||
|
import _ from 'lodash';
|
||||||
|
|
||||||
|
const boundingBoxDimensionsSelector = createSelector(
|
||||||
|
(state: RootState) => state.inpainting,
|
||||||
|
(inpainting: InpaintingState) => {
|
||||||
|
const { canvasDimensions, boundingBoxDimensions, shouldLockBoundingBox } =
|
||||||
|
inpainting;
|
||||||
|
return {
|
||||||
|
canvasDimensions,
|
||||||
|
boundingBoxDimensions,
|
||||||
|
shouldLockBoundingBox,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
{
|
||||||
|
memoizeOptions: {
|
||||||
|
resultEqualityCheck: _.isEqual,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
type BoundingBoxDimensionSlidersType = {
|
||||||
|
dimension: 'width' | 'height';
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function BoundingBoxDimensionSlider(
|
||||||
|
props: BoundingBoxDimensionSlidersType
|
||||||
|
) {
|
||||||
|
const { dimension } = props;
|
||||||
|
const dispatch = useAppDispatch();
|
||||||
|
const { shouldLockBoundingBox, canvasDimensions, boundingBoxDimensions } =
|
||||||
|
useAppSelector(boundingBoxDimensionsSelector);
|
||||||
|
|
||||||
|
const canvasDimension = canvasDimensions[dimension];
|
||||||
|
const boundingBoxDimension = boundingBoxDimensions[dimension];
|
||||||
|
|
||||||
|
const handleBoundingBoxDimension = (v: number) => {
|
||||||
|
if (dimension == 'width') {
|
||||||
|
dispatch(
|
||||||
|
setBoundingBoxDimensions({
|
||||||
|
...boundingBoxDimensions,
|
||||||
|
width: Math.floor(v),
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dimension == 'height') {
|
||||||
|
dispatch(
|
||||||
|
setBoundingBoxDimensions({
|
||||||
|
...boundingBoxDimensions,
|
||||||
|
height: Math.floor(v),
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleResetDimension = () => {
|
||||||
|
if (dimension == 'width') {
|
||||||
|
dispatch(
|
||||||
|
setBoundingBoxDimensions({
|
||||||
|
...boundingBoxDimensions,
|
||||||
|
width: Math.floor(canvasDimension),
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (dimension == 'height') {
|
||||||
|
dispatch(
|
||||||
|
setBoundingBoxDimensions({
|
||||||
|
...boundingBoxDimensions,
|
||||||
|
height: Math.floor(canvasDimension),
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="inpainting-bounding-box-dimensions-slider-numberinput">
|
||||||
|
<IAISlider
|
||||||
|
isDisabled={shouldLockBoundingBox}
|
||||||
|
label="Box H"
|
||||||
|
min={64}
|
||||||
|
max={roundDownToMultiple(canvasDimension, 64)}
|
||||||
|
step={64}
|
||||||
|
value={boundingBoxDimension}
|
||||||
|
onChange={handleBoundingBoxDimension}
|
||||||
|
width={'5rem'}
|
||||||
|
/>
|
||||||
|
<IAINumberInput
|
||||||
|
isDisabled={shouldLockBoundingBox}
|
||||||
|
value={boundingBoxDimension}
|
||||||
|
onChange={handleBoundingBoxDimension}
|
||||||
|
min={64}
|
||||||
|
max={roundDownToMultiple(canvasDimension, 64)}
|
||||||
|
step={64}
|
||||||
|
padding="0"
|
||||||
|
width={'5rem'}
|
||||||
|
/>
|
||||||
|
<IAIIconButton
|
||||||
|
size={'sm'}
|
||||||
|
aria-label={'Reset Height'}
|
||||||
|
tooltip={'Reset Height'}
|
||||||
|
onClick={handleResetDimension}
|
||||||
|
icon={<BiReset />}
|
||||||
|
styleClass="inpainting-bounding-box-reset-icon-btn"
|
||||||
|
isDisabled={
|
||||||
|
shouldLockBoundingBox || canvasDimension === boundingBoxDimension
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
@ -0,0 +1,27 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import {
|
||||||
|
RootState,
|
||||||
|
useAppDispatch,
|
||||||
|
useAppSelector,
|
||||||
|
} from '../../../../../app/store';
|
||||||
|
import IAICheckbox from '../../../../../common/components/IAICheckbox';
|
||||||
|
import { setShouldLockBoundingBox } from '../../../../tabs/Inpainting/inpaintingSlice';
|
||||||
|
|
||||||
|
export default function BoundingBoxLock() {
|
||||||
|
const shouldLockBoundingBox = useAppSelector(
|
||||||
|
(state: RootState) => state.inpainting.shouldLockBoundingBox
|
||||||
|
);
|
||||||
|
const dispatch = useAppDispatch();
|
||||||
|
|
||||||
|
const handleChangeShouldLockBoundingBox = () => {
|
||||||
|
dispatch(setShouldLockBoundingBox(!shouldLockBoundingBox));
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<IAICheckbox
|
||||||
|
label="Lock Bounding Box"
|
||||||
|
isChecked={shouldLockBoundingBox}
|
||||||
|
onChange={handleChangeShouldLockBoundingBox}
|
||||||
|
styleClass="inpainting-bounding-box-darken"
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
@ -0,0 +1,26 @@
|
|||||||
|
import { Flex } from '@chakra-ui/react';
|
||||||
|
import BoundingBoxDarkenOutside from './BoundingBoxDarkenOutside';
|
||||||
|
import BoundingBoxDimensionSlider from './BoundingBoxDimensionSlider';
|
||||||
|
import BoundingBoxLock from './BoundingBoxLock';
|
||||||
|
import BoundingBoxVisibility from './BoundingBoxVisibility';
|
||||||
|
|
||||||
|
const BoundingBoxSettings = () => {
|
||||||
|
return (
|
||||||
|
<div className="inpainting-bounding-box-settings">
|
||||||
|
<div className="inpainting-bounding-box-header">
|
||||||
|
<p>Inpaint Box</p>
|
||||||
|
<BoundingBoxVisibility />
|
||||||
|
</div>
|
||||||
|
<div className="inpainting-bounding-box-settings-items">
|
||||||
|
<BoundingBoxDimensionSlider dimension="width" />
|
||||||
|
<BoundingBoxDimensionSlider dimension="height" />
|
||||||
|
<Flex alignItems={'center'} justifyContent={'space-between'}>
|
||||||
|
<BoundingBoxDarkenOutside />
|
||||||
|
<BoundingBoxLock />
|
||||||
|
</Flex>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default BoundingBoxSettings;
|
@ -0,0 +1,28 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { BiHide, BiShow } from 'react-icons/bi';
|
||||||
|
import {
|
||||||
|
RootState,
|
||||||
|
useAppDispatch,
|
||||||
|
useAppSelector,
|
||||||
|
} from '../../../../../app/store';
|
||||||
|
import IAIIconButton from '../../../../../common/components/IAIIconButton';
|
||||||
|
import { setShouldShowBoundingBox } from '../../../../tabs/Inpainting/inpaintingSlice';
|
||||||
|
|
||||||
|
export default function BoundingBoxVisibility() {
|
||||||
|
const shouldShowBoundingBox = useAppSelector(
|
||||||
|
(state: RootState) => state.inpainting.shouldShowBoundingBox
|
||||||
|
);
|
||||||
|
const dispatch = useAppDispatch();
|
||||||
|
|
||||||
|
const handleShowBoundingBox = () =>
|
||||||
|
dispatch(setShouldShowBoundingBox(!shouldShowBoundingBox));
|
||||||
|
return (
|
||||||
|
<IAIIconButton
|
||||||
|
aria-label="Toggle Bounding Box Visibility"
|
||||||
|
icon={shouldShowBoundingBox ? <BiShow size={22} /> : <BiHide size={22} />}
|
||||||
|
onClick={handleShowBoundingBox}
|
||||||
|
background={'none'}
|
||||||
|
padding={0}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
@ -1,4 +1,4 @@
|
|||||||
import BoundingBoxSettings from './BoundingBoxSettings';
|
import BoundingBoxSettings from './BoundingBoxSettings/BoundingBoxSettings';
|
||||||
import InpaintReplace from './InpaintReplace';
|
import InpaintReplace from './InpaintReplace';
|
||||||
import ClearBrushHistory from './ClearBrushHistory';
|
import ClearBrushHistory from './ClearBrushHistory';
|
||||||
|
|
||||||
|
@ -170,7 +170,6 @@ const InvokeOptionsPanel = (props: Props) => {
|
|||||||
</h1>
|
</h1>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{children}
|
{children}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -23,7 +23,7 @@
|
|||||||
@use '../features/options/MainOptions/MainOptions.scss';
|
@use '../features/options/MainOptions/MainOptions.scss';
|
||||||
@use '../features/options/AccordionItems/AdvancedSettings.scss';
|
@use '../features/options/AccordionItems/AdvancedSettings.scss';
|
||||||
@use '../features/options/AdvancedOptions/Upscale/UpscaleOptions.scss';
|
@use '../features/options/AdvancedOptions/Upscale/UpscaleOptions.scss';
|
||||||
@use '../features/options/AdvancedOptions/Inpainting/BoundingBoxSettings.scss';
|
@use '../features/options/AdvancedOptions/Inpainting/BoundingBoxSettings/BoundingBoxSettings.scss';
|
||||||
@use '../features/system/ProgressBar.scss';
|
@use '../features/system/ProgressBar.scss';
|
||||||
|
|
||||||
// gallery
|
// gallery
|
||||||
|
Loading…
Reference in New Issue
Block a user