mirror of
https://github.com/storytold/artcraft.git
synced 2026-11-09 03:21:23 +00:00
compacted the timeline (#478)
only height and padding styling updates to the timeline tracks and clips timeline now more compact and takes less space. 
This commit is contained in:
1 parent
16ade492b5
commit
b15a18fb47
16 files changed
+66
-64
No files matched your search
@@ -31,7 +31,7 @@ export const PageEditor = () => {
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
timelineHeight.value = window.innerHeight * 0.25;
|
||||
timelineHeight.value = 208;
|
||||
sidePanelWidth.value = 340;
|
||||
window.onbeforeunload = () => {
|
||||
return "You may have unsaved changes.";
|
||||
|
||||
@@ -28,7 +28,7 @@ export const DragComponent = () => {
|
||||
id={`ani-dnd-${dragItem.value.media_id}`}
|
||||
className={[
|
||||
"absolute p-2",
|
||||
"rounded-lg",
|
||||
"rounded-md",
|
||||
!canDrop.value
|
||||
? "bg-brand-primary"
|
||||
: "border border-dashed border-dnd-canDropBorder bg-dnd-canDrop",
|
||||
@@ -39,7 +39,7 @@ export const DragComponent = () => {
|
||||
left: currX + 1,
|
||||
zIndex: 10000,
|
||||
width: (dragItem.value.length ?? 0) * 4 * scale.value,
|
||||
height: 32,
|
||||
height: 30,
|
||||
}}
|
||||
/>
|
||||
<div
|
||||
|
||||
@@ -19,7 +19,7 @@ export const Audio = () => {
|
||||
return (
|
||||
<div
|
||||
id="track-global-audio"
|
||||
className="relative flex h-[35px] items-center justify-end rounded-r-lg bg-global_audio-groupBg pr-4"
|
||||
className="relative flex h-[30px] items-center justify-end rounded-r-lg bg-global_audio-groupBg pr-4"
|
||||
style={{ width: fullWidth.value + 16 }}
|
||||
>
|
||||
<button
|
||||
@@ -42,7 +42,7 @@ export const Audio = () => {
|
||||
return (
|
||||
<div
|
||||
id="track-global-audio"
|
||||
className="relative block rounded-r-lg bg-global_audio-groupBg pb-5 pr-4"
|
||||
className="relative block rounded-r-lg bg-global_audio-groupBg pb-2 pr-4"
|
||||
style={{ width: fullWidth.value + 16 }}
|
||||
>
|
||||
<button
|
||||
@@ -58,7 +58,7 @@ export const Audio = () => {
|
||||
>
|
||||
<FontAwesomeIcon icon={faAngleUp} />
|
||||
</button>
|
||||
<div className="pt-[47px]">
|
||||
<div className="pt-[30px]">
|
||||
<TrackClips
|
||||
id={audioGroup.value.id}
|
||||
clips={clips}
|
||||
|
||||
@@ -19,7 +19,7 @@ export const Camera = () => {
|
||||
return (
|
||||
<div
|
||||
id="track-camera"
|
||||
className="relative flex h-[35px] items-center justify-end rounded-r-lg bg-camera-groupBg pr-4"
|
||||
className="relative flex h-[30px] items-center justify-end rounded-r-lg bg-camera-groupBg pr-4"
|
||||
style={{ width: fullWidth.value + 16 }}
|
||||
>
|
||||
<button
|
||||
@@ -42,7 +42,7 @@ export const Camera = () => {
|
||||
return (
|
||||
<div
|
||||
id="track-camera"
|
||||
className="relative block rounded-r-lg bg-camera-groupBg pb-5 pr-4"
|
||||
className="relative block rounded-r-lg bg-camera-groupBg pb-2 pr-4"
|
||||
style={{ width: fullWidth.value + 16 }}
|
||||
>
|
||||
<div className="flex justify-end">
|
||||
@@ -60,7 +60,7 @@ export const Camera = () => {
|
||||
<FontAwesomeIcon icon={faAngleUp} />
|
||||
</button>
|
||||
</div>
|
||||
<div className="pt-[47px]">
|
||||
<div className="pt-[30px]">
|
||||
<TrackKeyFrames
|
||||
id={cameraGroup.value.id}
|
||||
keyframes={keyframes}
|
||||
|
||||
@@ -88,7 +88,7 @@ export const Character = ({ character }: Props) => {
|
||||
return (
|
||||
<div
|
||||
id={`track-character-${character.object_uuid}`}
|
||||
className="relative flex h-[35px] items-center justify-end rounded-r-lg bg-character-groupBg pr-4"
|
||||
className="relative flex h-[30px] items-center justify-end rounded-r-lg bg-character-groupBg pr-4"
|
||||
style={{ width: fullWidth.value + 16 }}
|
||||
>
|
||||
<button
|
||||
@@ -111,10 +111,10 @@ export const Character = ({ character }: Props) => {
|
||||
return (
|
||||
<div
|
||||
id={`track-character-${character.object_uuid}`}
|
||||
className="relative block rounded-r-lg bg-character-groupBg pb-5 pr-4"
|
||||
className="relative block rounded-r-lg bg-character-groupBg pb-2 pr-4"
|
||||
style={{ width: fullWidth.value + 16 }}
|
||||
>
|
||||
<div className="flex h-[35px] items-center justify-end">
|
||||
<div className="flex h-[30px] items-center justify-end">
|
||||
<button
|
||||
className="absolute"
|
||||
style={{
|
||||
@@ -129,7 +129,7 @@ export const Character = ({ character }: Props) => {
|
||||
<FontAwesomeIcon icon={faAngleUp} />
|
||||
</button>
|
||||
</div>
|
||||
<div className="flex flex-col gap-3 pt-[12px]">
|
||||
<div className="flex flex-col">
|
||||
<TrackClips
|
||||
id={character.object_uuid}
|
||||
clips={animationClips}
|
||||
|
||||
@@ -7,7 +7,7 @@ export const Characters = () => {
|
||||
return (
|
||||
<>
|
||||
{characterGroup.value.characters.map((character) => (
|
||||
<div key={character.object_uuid} className="pb-4 pr-4">
|
||||
<div key={character.object_uuid} className="mb-1 pr-4">
|
||||
<Character character={character} />
|
||||
</div>
|
||||
))}
|
||||
|
||||
@@ -21,7 +21,7 @@ export const ObjectGroups = () => {
|
||||
return (
|
||||
<div
|
||||
id="track-objects"
|
||||
className="relative mb-4 flex h-[35px] items-center justify-end rounded-r-lg bg-object-groupBg pr-4"
|
||||
className="relative mb-4 flex h-[30px] items-center justify-end rounded-r-lg bg-object-groupBg pr-4"
|
||||
style={{ width: fullWidth.value + 16 }}
|
||||
>
|
||||
<button
|
||||
@@ -44,7 +44,7 @@ export const ObjectGroups = () => {
|
||||
return (
|
||||
<div
|
||||
id="track-objects"
|
||||
className="relative mb-5 block rounded-r-lg bg-object-groupBg pr-4"
|
||||
className="relative mb-5 block rounded-r-lg bg-object-groupBg pb-2 pr-4"
|
||||
style={{ width: fullWidth.value + 16 }}
|
||||
>
|
||||
<button
|
||||
@@ -60,11 +60,11 @@ export const ObjectGroups = () => {
|
||||
>
|
||||
<FontAwesomeIcon icon={faAngleUp} />
|
||||
</button>
|
||||
<div className="pt-[47px]">
|
||||
<div className="pt-[30px]">
|
||||
{objectGroup.value.objects.map((object) => (
|
||||
<div
|
||||
key={object.object_uuid}
|
||||
className="pb-4 pr-4"
|
||||
className="mb-1"
|
||||
id={`track-object-${selectedObject.value?.id}`}
|
||||
>
|
||||
<ObjectTrackComponent object={object} />
|
||||
|
||||
@@ -13,7 +13,7 @@ export const LipSyncSubHeader = ({
|
||||
const editorEngine = useContext(EngineContext);
|
||||
if (editorEngine && editorEngine.isObjectLipsync(character.object_uuid)) {
|
||||
return (
|
||||
<div className="mb-3 flex h-[36px] flex-col justify-center pl-[22px] text-xs font-medium text-white opacity-80">
|
||||
<div className="mb-1 flex h-[30px] flex-col justify-center pl-[22px] text-xs font-medium text-white opacity-80">
|
||||
<div className="flex gap-3">
|
||||
Lip Sync
|
||||
<button
|
||||
|
||||
@@ -23,9 +23,9 @@ export const RowHeaders = () => {
|
||||
useSignals();
|
||||
|
||||
const compressedHeaderClasses =
|
||||
"flex h-[35px] items-center gap-2 pl-2 text-xs rounded-tl-lg font-medium text-white";
|
||||
"flex h-[30px] items-center gap-2 pl-2 text-xs rounded-tl-lg font-medium text-white w-32";
|
||||
const uncompressedHeaderClasses =
|
||||
"flex h-[35px] items-center gap-2 rounded-br-lg rounded-tl-lg pl-2 text-xs font-medium text-white";
|
||||
"flex h-[30px] items-center gap-2 rounded-br-lg rounded-tl-lg pl-2 text-xs font-medium text-white w-32";
|
||||
|
||||
return (
|
||||
<div className="relative">
|
||||
@@ -38,7 +38,7 @@ export const RowHeaders = () => {
|
||||
return (
|
||||
<div
|
||||
key={character.object_uuid}
|
||||
className="mb-4 h-[35px] w-full rounded-l-lg bg-character-groupBg"
|
||||
className="mb-1 h-[30px] w-full rounded-l-lg bg-character-groupBg"
|
||||
>
|
||||
<div
|
||||
className={[
|
||||
@@ -54,12 +54,12 @@ export const RowHeaders = () => {
|
||||
return (
|
||||
<div
|
||||
key={character.object_uuid}
|
||||
className="mb-4 w-full rounded-l-lg bg-character-groupBg pb-2"
|
||||
className="mb-1 w-full rounded-l-lg bg-character-groupBg pb-2"
|
||||
// style={{
|
||||
// height: environmentVariables.value.EXPRESSIONS ? 247 : 199,
|
||||
// }}
|
||||
>
|
||||
<div className="h-[47px] text-xs font-medium text-white">
|
||||
<div className="h-[30px] text-xs font-medium text-white">
|
||||
<div
|
||||
className={[
|
||||
uncompressedHeaderClasses,
|
||||
@@ -69,14 +69,14 @@ export const RowHeaders = () => {
|
||||
<CharacterHeader name={character.name} />
|
||||
</div>
|
||||
</div>
|
||||
<div className="mb-3 flex h-[36px] flex-col justify-center pl-[22px] text-xs font-medium text-white opacity-80">
|
||||
<div className="mb-1 flex h-[30px] flex-col justify-center pl-[22px] text-xs font-medium text-white opacity-80">
|
||||
Animation
|
||||
</div>
|
||||
<div className="mb-3 flex h-[36px] flex-col justify-center pl-[22px] text-xs font-medium text-white opacity-80">
|
||||
<div className="mb-1 flex h-[30px] flex-col justify-center pl-[22px] text-xs font-medium text-white opacity-80">
|
||||
Position
|
||||
</div>
|
||||
{environmentVariables.value.EXPRESSIONS && (
|
||||
<div className="mb-3 flex h-[36px] flex-col justify-center pl-[22px] text-xs font-medium text-white opacity-80">
|
||||
<div className="mb-1 flex h-[30px] flex-col justify-center pl-[22px] text-xs font-medium text-white opacity-80">
|
||||
Expression
|
||||
</div>
|
||||
)}
|
||||
@@ -86,7 +86,7 @@ export const RowHeaders = () => {
|
||||
})}
|
||||
|
||||
{cameraMinimized.value ? (
|
||||
<div className="mb-4 h-[35px] w-full rounded-l-lg bg-camera-groupBg">
|
||||
<div className="mb-1 h-[30px] w-full rounded-l-lg bg-camera-groupBg">
|
||||
<div
|
||||
className={[compressedHeaderClasses, "bg-camera-titleBg"].join(
|
||||
" ",
|
||||
@@ -96,8 +96,8 @@ export const RowHeaders = () => {
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="mb-4 h-[103px] w-full rounded-l-lg bg-camera-groupBg">
|
||||
<div className="h-[47px] text-xs font-medium text-white">
|
||||
<div className="mb-1 h-[72px] w-full rounded-l-lg bg-camera-groupBg">
|
||||
<div className="h-[30px] text-xs font-medium text-white">
|
||||
<div
|
||||
className={[
|
||||
uncompressedHeaderClasses,
|
||||
@@ -107,14 +107,14 @@ export const RowHeaders = () => {
|
||||
<CameraHeader />
|
||||
</div>
|
||||
</div>
|
||||
<div className="mb-3 flex h-[36px] flex-col justify-center pl-[22px] text-xs font-medium text-white opacity-80">
|
||||
<div className="flex h-[30px] flex-col justify-center pl-[22px] text-xs font-medium text-white opacity-80">
|
||||
Position
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{audioMinimized.value ? (
|
||||
<div className="mb-4 h-[35px] w-full rounded-l-lg bg-global_audio-groupBg">
|
||||
<div className="mb-1 h-[30px] w-full rounded-l-lg bg-global_audio-groupBg">
|
||||
<div
|
||||
className={[
|
||||
compressedHeaderClasses,
|
||||
@@ -125,8 +125,8 @@ export const RowHeaders = () => {
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="mb-4 h-[103px] w-full rounded-l-lg bg-global_audio-groupBg">
|
||||
<div className="h-[47px] text-xs font-medium text-white">
|
||||
<div className="mb-1 h-[72px] w-full rounded-l-lg bg-global_audio-groupBg">
|
||||
<div className="h-[30px] text-xs font-medium text-white">
|
||||
<div
|
||||
className={[
|
||||
uncompressedHeaderClasses,
|
||||
@@ -136,7 +136,7 @@ export const RowHeaders = () => {
|
||||
<GlobalAudioHeader />
|
||||
</div>
|
||||
</div>
|
||||
<div className="mb-3 flex h-[36px] flex-col justify-center pl-[22px] text-xs font-medium text-white opacity-80">
|
||||
<div className="flex h-[30px] flex-col justify-center pl-[22px] text-xs font-medium text-white opacity-80">
|
||||
<div className="flex gap-3">
|
||||
Track 1
|
||||
<button
|
||||
@@ -159,7 +159,7 @@ export const RowHeaders = () => {
|
||||
{objectGroup.value.objects.length > 0 && (
|
||||
<>
|
||||
{objectsMinimized.value ? (
|
||||
<div className="mb-4 h-[35px] w-full rounded-l-lg bg-object-groupBg">
|
||||
<div className="h-[30px] w-full rounded-l-lg bg-object-groupBg">
|
||||
<div
|
||||
className={[
|
||||
compressedHeaderClasses,
|
||||
@@ -171,10 +171,12 @@ export const RowHeaders = () => {
|
||||
</div>
|
||||
) : (
|
||||
<div
|
||||
className="mb-4 h-[103px] w-full rounded-l-lg bg-object-groupBg"
|
||||
style={{ height: 48 + objectGroup.value.objects.length * 52 }}
|
||||
className="mb-1 h-[72px] w-full rounded-l-lg bg-object-groupBg"
|
||||
style={{
|
||||
height: `${38 + objectGroup.value.objects.length * 34}px`,
|
||||
}}
|
||||
>
|
||||
<div className="h-[47px] text-xs font-medium text-white">
|
||||
<div className="h-[30px] text-xs font-medium text-white">
|
||||
<div
|
||||
className={[
|
||||
uncompressedHeaderClasses,
|
||||
@@ -187,7 +189,7 @@ export const RowHeaders = () => {
|
||||
{objectGroup.value.objects.map((obj) => (
|
||||
<div
|
||||
key={obj.object_uuid}
|
||||
className="mb-4 flex h-[36px] flex-col justify-center pl-[22px] text-xs font-medium text-white opacity-80"
|
||||
className="mb-1 flex h-[30px] flex-col justify-center pl-[22px] text-xs font-medium text-white opacity-80"
|
||||
>
|
||||
{obj.name}
|
||||
</div>
|
||||
|
||||
@@ -28,7 +28,7 @@ export const Scrubber = ({ page }: Props) => {
|
||||
<div
|
||||
className="absolute z-20 flex cursor-ew-resize flex-col items-center"
|
||||
style={{
|
||||
top: 16,
|
||||
top: 8,
|
||||
left: displayTime * 4 * scale.value + 199 - scrollX,
|
||||
}}
|
||||
onPointerDown={onPointerDown}
|
||||
@@ -54,7 +54,7 @@ export const Scrubber = ({ page }: Props) => {
|
||||
style={{
|
||||
width: 2,
|
||||
marginTop: -5,
|
||||
height: timelineHeight.value - 48,
|
||||
height: timelineHeight.value - 36,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -90,7 +90,7 @@ export const Timeline = () => {
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
timelineHeight.value = window.outerHeight * 0.25;
|
||||
timelineHeight.value = 208;
|
||||
}, []);
|
||||
|
||||
const onDelete = useCallback(() => {
|
||||
@@ -143,7 +143,7 @@ export const Timeline = () => {
|
||||
<div
|
||||
className="ml-[60px] mt-2 w-[144px] overflow-hidden"
|
||||
style={{
|
||||
height: timelineHeight.value - 54,
|
||||
height: timelineHeight.value - 36,
|
||||
}}
|
||||
>
|
||||
<RowHeaders />
|
||||
@@ -153,7 +153,7 @@ export const Timeline = () => {
|
||||
onScroll={onScroll}
|
||||
style={{
|
||||
width: pageWidth.value - 204,
|
||||
height: timelineHeight.value - 54,
|
||||
height: timelineHeight.value - 36,
|
||||
}}
|
||||
>
|
||||
<div
|
||||
@@ -162,10 +162,10 @@ export const Timeline = () => {
|
||||
>
|
||||
<PremiumLockTimeline locked={false} />
|
||||
<Characters />
|
||||
<div className="pb-4 pr-8">
|
||||
<div className="pb-1 pr-8">
|
||||
<Camera />
|
||||
</div>
|
||||
<div className="pb-4 pr-8">
|
||||
<div className="pb-1 pr-8">
|
||||
<Audio />
|
||||
</div>
|
||||
<ObjectGroups />
|
||||
|
||||
@@ -21,8 +21,8 @@ export const TimerGrid = ({ page }: Props) => {
|
||||
return (
|
||||
<div
|
||||
className={[
|
||||
"prevent-select ml-[204px] mt-4",
|
||||
"relative flex h-7 overflow-hidden",
|
||||
"prevent-select ml-[204px] mt-2",
|
||||
"relative flex h-5 overflow-hidden",
|
||||
"border-t border-t-ui-panel-border",
|
||||
"text-xs text-white opacity-75",
|
||||
].join(" ")}
|
||||
@@ -33,13 +33,13 @@ export const TimerGrid = ({ page }: Props) => {
|
||||
.map((_, index) => (
|
||||
<Fragment key={index}>
|
||||
<div
|
||||
className="absolute ps-1 pt-2"
|
||||
className="absolute ps-0.5 pt-1.5 text-[11px]"
|
||||
style={{ left: index * sectionWidth + 4 }}
|
||||
>
|
||||
00:{index < 10 ? "0" + index.toString() : index.toString()}
|
||||
</div>
|
||||
<div
|
||||
className="absolute block bg-ui-divider"
|
||||
className="absolute mt-0.5 block bg-ui-divider"
|
||||
style={{
|
||||
width: 1,
|
||||
left: index * sectionWidth,
|
||||
@@ -51,7 +51,7 @@ export const TimerGrid = ({ page }: Props) => {
|
||||
.map((_, ind) => (
|
||||
<div
|
||||
key={ind}
|
||||
className="absolute block h-2 bg-ui-divider"
|
||||
className="absolute mt-0.5 block h-1 bg-ui-divider"
|
||||
style={{
|
||||
width: 1,
|
||||
top: 0,
|
||||
@@ -63,7 +63,7 @@ export const TimerGrid = ({ page }: Props) => {
|
||||
</Fragment>
|
||||
))}
|
||||
<div
|
||||
className="absolute pt-2"
|
||||
className="absolute ps-0.5 pt-1.5 text-[11px]"
|
||||
style={{ left: filmLength.value * sectionWidth + 4 }}
|
||||
>
|
||||
00:
|
||||
|
||||
@@ -47,7 +47,7 @@ export const TrackClip = ({ clip, min, max, group, updateClip }: Props) => {
|
||||
<button
|
||||
className={[
|
||||
...classes,
|
||||
"w-[15px] rounded-l-lg",
|
||||
"w-[15px] rounded-l-md border-2 border-transparent",
|
||||
"block h-full cursor-ew-resize",
|
||||
clip.clip_uuid === selectedClipId
|
||||
? "border border-b-2 border-l-2 border-r-0 border-t-2"
|
||||
@@ -69,8 +69,8 @@ export const TrackClip = ({ clip, min, max, group, updateClip }: Props) => {
|
||||
selectedItem.value = clip;
|
||||
}}
|
||||
>
|
||||
<div className="flex h-full items-center justify-end">
|
||||
<div className="block h-[10px] w-[2px] bg-white opacity-60" />
|
||||
<div className="flex h-full items-center justify-start">
|
||||
<div className="ml-1.5 block h-[10px] w-[2px] bg-white opacity-60" />
|
||||
</div>
|
||||
</button>
|
||||
<button
|
||||
@@ -109,7 +109,7 @@ export const TrackClip = ({ clip, min, max, group, updateClip }: Props) => {
|
||||
<button
|
||||
className={[
|
||||
...classes,
|
||||
"w-[15px] rounded-r-lg",
|
||||
"w-[15px] rounded-r-md border-2 border-transparent",
|
||||
"block h-full cursor-ew-resize",
|
||||
clip.clip_uuid === selectedClipId
|
||||
? "border border-b-2 border-l-0 border-r-2 border-t-2"
|
||||
@@ -131,8 +131,8 @@ export const TrackClip = ({ clip, min, max, group, updateClip }: Props) => {
|
||||
selectedItem.value = clip;
|
||||
}}
|
||||
>
|
||||
<div className="flex h-full items-center">
|
||||
<div className="block h-[10px] w-[2px] bg-white opacity-60" />
|
||||
<div className="flex h-full items-center justify-end">
|
||||
<div className="mr-1.5 block h-[10px] w-[2px] bg-white opacity-60" />
|
||||
</div>
|
||||
</button>
|
||||
</>
|
||||
|
||||
@@ -152,14 +152,14 @@ export const TrackClips = ({ id, clips, updateClip, group, type }: Props) => {
|
||||
<div
|
||||
id={`track-${trackType}-${id}`}
|
||||
className={[
|
||||
"relative block h-9 w-full rounded-lg",
|
||||
"relative mb-1 block h-[30px] w-full rounded-md",
|
||||
`bg-${group}-unselected`,
|
||||
clips.length === 0 ? "border-2 border-dashed border-white/30" : "",
|
||||
clips.length === 0 ? "border border-dashed border-white/30" : "",
|
||||
].join(" ")}
|
||||
>
|
||||
<div
|
||||
className={[
|
||||
"absolute inset-0 rounded-lg",
|
||||
"absolute inset-0 rounded-md",
|
||||
canDropAsset
|
||||
? "animate-pulse bg-white/30 duration-[750ms]"
|
||||
: "opacity-0",
|
||||
|
||||
@@ -26,7 +26,7 @@ export const TrackKeyFrame = ({ keyframe, updateKeyframe }: Props) => {
|
||||
return (
|
||||
<button
|
||||
className={[
|
||||
"top-[11px] block h-[14px] w-[14px] rotate-45 cursor-ew-resize",
|
||||
"top-[9px] block h-[12px] w-[12px] rotate-45 cursor-ew-resize",
|
||||
"absolute",
|
||||
keyframe.keyframe_uuid === selectedKeyframeId
|
||||
? "bg-keyframe-selected"
|
||||
|
||||
@@ -67,7 +67,7 @@ export const TrackKeyFrames = ({
|
||||
return (
|
||||
<div
|
||||
id={`track-${group}-${id}`}
|
||||
className={`relative block h-9 w-full rounded-lg bg-${group}-unselected`}
|
||||
className={`relative mb-1 block h-[30px] w-full rounded-md bg-${group}-unselected`}
|
||||
onPointerMove={onPointerMove}
|
||||
>
|
||||
{keyframes.map((keyframe) => (
|
||||
|
||||
Reference in new issue
Block a user