mirror of
https://github.com/MetaFam/TheGame.git
synced 2026-01-13 16:37:58 -05:00
241 lines
7.1 KiB
TypeScript
241 lines
7.1 KiB
TypeScript
import {
|
|
Box,
|
|
Button,
|
|
Center,
|
|
FormControl,
|
|
FormErrorMessage,
|
|
FormHelperText,
|
|
Image,
|
|
Input,
|
|
useToast,
|
|
} from '@metafam/ds';
|
|
import { Maybe, Optional } from '@metafam/utils';
|
|
import {
|
|
ChangeEvent,
|
|
DragEvent,
|
|
forwardRef,
|
|
useCallback,
|
|
useMemo,
|
|
useState,
|
|
} from 'react';
|
|
import { Controller, useFormContext } from 'react-hook-form';
|
|
|
|
import { Player } from '#graphql/autogen/hasura-sdk';
|
|
import { FileReaderData, useImageReader } from '#lib/hooks/useImageReader';
|
|
import { optimizedImage } from '#utils/imageHelpers';
|
|
|
|
import { Label } from './Label';
|
|
|
|
export type EditBackgroundImageProps = {
|
|
player: Player;
|
|
initialURL?: Maybe<string>;
|
|
onFilePicked: ({ file, dataURL }: FileReaderData) => void;
|
|
};
|
|
|
|
export const EditBackgroundImage = forwardRef<
|
|
HTMLImageElement,
|
|
EditBackgroundImageProps
|
|
>(({ player, initialURL, onFilePicked }, ref) => {
|
|
const toast = useToast();
|
|
const readFile = useImageReader();
|
|
|
|
const [active, setActive] = useState(false);
|
|
const [loading, setLoading] = useState(true);
|
|
const [url, setURL] = useState<Optional<string>>(
|
|
optimizedImage('backgroundImageURL', initialURL),
|
|
);
|
|
|
|
const metagamer = useMemo(
|
|
() =>
|
|
!!player?.guilds.some(
|
|
({ Guild: { guildname } }) => guildname === 'metafam',
|
|
),
|
|
[player],
|
|
);
|
|
|
|
const helperLabel =
|
|
// eslint-disable-next-line prefer-template
|
|
'An image with an ~1:1 aspect ratio to be the page background. 1MiB maximum size.' +
|
|
(metagamer ? '' : '\nOnly available to MetaGame players and patrons.');
|
|
|
|
const {
|
|
control,
|
|
formState: { errors },
|
|
} = useFormContext();
|
|
|
|
const onFileChange = useCallback(
|
|
async (file: File) => {
|
|
if (file.size === 0) {
|
|
toast({
|
|
title: 'Empty Image Error',
|
|
description:
|
|
'The selected image has a size of 0. Is it a symbolic link?',
|
|
status: 'error',
|
|
isClosable: true,
|
|
duration: 10000,
|
|
});
|
|
} else {
|
|
setLoading(true);
|
|
try {
|
|
const dataURL = await readFile(file);
|
|
setURL(dataURL);
|
|
onFilePicked({ file, dataURL });
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
}
|
|
},
|
|
[onFilePicked, readFile, toast],
|
|
);
|
|
|
|
// const onFileRemove = useCallback(() => {
|
|
// setURL(undefined);
|
|
// setLoading(true);
|
|
// setActive(false);
|
|
// }, []);
|
|
|
|
return (
|
|
<FormControl isInvalid={!!errors.backgroundImageURL} h="full">
|
|
<Label htmlFor="backgroundImageURL" userSelect="none" whiteSpace="nowrap">
|
|
Background Image
|
|
</Label>
|
|
<FormHelperText pb={3} color="white">
|
|
{helperLabel}
|
|
</FormHelperText>
|
|
{metagamer ? (
|
|
<Center
|
|
position="relative"
|
|
w="full"
|
|
h="full"
|
|
border="2px solid"
|
|
borderColor={active && !url ? 'blue.400' : 'transparent'}
|
|
>
|
|
<Image
|
|
ref={ref}
|
|
onLoad={() => {
|
|
setLoading(false);
|
|
}}
|
|
onError={() => {
|
|
setLoading(false);
|
|
}}
|
|
display={loading ? 'none' : 'inherit'}
|
|
src={url}
|
|
h="full"
|
|
w="full"
|
|
/>
|
|
{/* {loading &&
|
|
(url == null ? (
|
|
<Image w="5em" mx="2.5em" src={FileOpenIcon.src} opacity={0.5} />
|
|
) : (
|
|
<Spinner size="xl" color="purple.500" thickness="4px" />
|
|
))} */}
|
|
<Controller
|
|
control={control}
|
|
name="backgroundImageURL"
|
|
defaultValue={[]}
|
|
render={({ field: { onChange, value, ...props } }) => (
|
|
<>
|
|
<Box
|
|
as="label"
|
|
h="full"
|
|
w="full"
|
|
ml={[0, 3]}
|
|
htmlFor="upload-bg-image"
|
|
border="1px"
|
|
color="white"
|
|
borderColor="blue.600"
|
|
borderRadius="4px"
|
|
display="flex"
|
|
flexDirection={['column', 'row']}
|
|
alignItems="center"
|
|
justifyContent="center"
|
|
>
|
|
<Input
|
|
id="upload-bg-image"
|
|
h="full"
|
|
w="full"
|
|
position="absolute"
|
|
type="file"
|
|
opacity={0}
|
|
{...props}
|
|
value={value?.filename}
|
|
onChange={async (evt: ChangeEvent<HTMLInputElement>) => {
|
|
evt.preventDefault();
|
|
const file = evt.target.files?.[0];
|
|
if (file) {
|
|
onChange(file);
|
|
onFileChange(file);
|
|
}
|
|
}}
|
|
onDragOver={async (evt: DragEvent<HTMLInputElement>) => {
|
|
evt.preventDefault();
|
|
}}
|
|
onDrop={async (evt: DragEvent<HTMLInputElement>) => {
|
|
evt.preventDefault();
|
|
const file = evt.dataTransfer.files?.[0];
|
|
if (file) {
|
|
onChange(file);
|
|
onFileChange(file);
|
|
}
|
|
}}
|
|
onFocus={() => setActive(true)}
|
|
onBlur={() => setActive(false)}
|
|
accept="image/*"
|
|
/>
|
|
Drag and drop an image or
|
|
<Button
|
|
as="label"
|
|
htmlFor="upload-bg-image"
|
|
colorScheme="blue"
|
|
variant="outline"
|
|
borderColor="blue.600"
|
|
py={4}
|
|
px={8}
|
|
letterSpacing="0.1em"
|
|
size="md"
|
|
fontSize="sm"
|
|
ml={3}
|
|
mt={[3, 0]}
|
|
color="white"
|
|
rounded="md"
|
|
_hover={{ borderColor: 'blue.700' }}
|
|
_active={{ borderColor: 'blue.800' }}
|
|
cursor="pointer"
|
|
>
|
|
Upload Image
|
|
</Button>
|
|
</Box>
|
|
{/*
|
|
Disabled until we figure out a good way to clear
|
|
{url && !loading && (
|
|
<IconButton
|
|
zIndex="10"
|
|
position="absolute"
|
|
top={2}
|
|
right={2}
|
|
bg="purple.400"
|
|
borderRadius="50%"
|
|
aria-label={`remove backgroundImageURL`}
|
|
size="xs"
|
|
_hover={{ bg: 'purple.500' }}
|
|
icon={<CloseIcon boxSize="0.5rem" />}
|
|
onClick={() => {
|
|
onChange(null);
|
|
onFileRemove();
|
|
}}
|
|
onFocus={() => setActive(true)}
|
|
onBlur={() => setActive(false)}
|
|
/>
|
|
)} */}
|
|
</>
|
|
)}
|
|
/>
|
|
</Center>
|
|
) : null}
|
|
<FormErrorMessage>
|
|
{errors.backgroundImageURL?.message?.toString()}
|
|
</FormErrorMessage>
|
|
</FormControl>
|
|
);
|
|
});
|