Frontend: Better file uploads

This commit is contained in:
ADAMJR 2021-12-30 06:15:26 +00:00
parent 491f1b1b4c
commit 1a1f2ee4af
8 changed files with 65 additions and 19 deletions

View File

@ -13,7 +13,7 @@ export default class Themes extends DBWrapper<string, ThemeDocument> {
}
public async create(options: Partial<Entity.Theme>) {
parseCSS(options.styles);
this.parse(options.styles!);
return await Theme.create(options);
}
@ -38,4 +38,11 @@ export default class Themes extends DBWrapper<string, ThemeDocument> {
send: { unlockedThemeIds: user.unlockedThemeIds },
});
}
public parse(styles: string) {
try { parseCSS(styles) }
catch (error: any) {
throw new TypeError(`CSS Error: ${styles}`)
}
}
}

View File

@ -33,7 +33,7 @@ router.get('/:id', async (req, res) => {
router.patch('/:id', updateUser, validateUser, async (req, res) => {
const { name, styles } = req.body;
parseCSS(styles);
deps.themes.parse(styles);
const theme = await deps.themes.get(req.params.id);
if (res.locals.user.id !== theme.creatorId)

View File

@ -0,0 +1,21 @@
import { ChangeEventHandler } from 'react';
import Input, { InputProps } from './input';
type FileInputProps = {
onChange: ChangeEventHandler<HTMLInputElement>;
} & InputProps;
const FileInput: React.FunctionComponent<FileInputProps> = (props) => {
return (
<Input
accept="image/*"
className="pt-5"
label="Avatar Image"
register={(): any => {}}
type="file"
{...props}
onChange={props.onChange} />
);
}
export default FileInput;

View File

@ -37,7 +37,10 @@ const Input: React.FunctionComponent<InputProps & React.AllHTMLAttributes<HTMLIn
style={{ color: "var(--muted)" }}
className="cursor-pointer ml-2"
icon={faQuestionCircle} />
<ReactTooltip id={id + 'Tooltip'} effect="solid" backgroundColor="var(--bg-tertiary)">
<ReactTooltip
backgroundColor="var(--bg-tertiary)"
id={id + 'Tooltip'}
effect="solid">
<span>{tooltip}</span>
</ReactTooltip>
</>)}
@ -49,10 +52,8 @@ const Input: React.FunctionComponent<InputProps & React.AllHTMLAttributes<HTMLIn
disabled={disabled}
{...filterProps(props)}
{...register?.(name, { ...options })}
className={classNames(
'block bg-bg-secondary rounded focus:outline-none w-full h-10 p-2 mt-2',
{ 'h-12': type === 'file' },
)} />
size={60}
className={classNames('block bg-bg-secondary rounded focus:outline-none w-full p-2 h-10 mt-2')} />
</div>
);
}

View File

@ -8,6 +8,7 @@ import Category from '../../utils/category';
import Input from '../../inputs/input';
import Toggle from '../../inputs/toggle';
import SaveChanges from '../../utils/save-changes';
import FileInput from '../../inputs/file-input';
const UserSettingsOverview: React.FunctionComponent = () => {
const dispatch = useDispatch();
@ -44,13 +45,8 @@ const UserSettingsOverview: React.FunctionComponent = () => {
register={register}
options={{ value: user.email }}
className="pt-5" />
<Input
type="file"
accept="image/*"
label="Avatar Image"
className="pt-5"
<FileInput
name="avatarURL"
register={(): any => {}}
options={{ value: user.avatarURL }}
onChange={(e) => {
const file = e.currentTarget?.files?.[0];

View File

@ -3,9 +3,11 @@ import classNames from 'classnames';
import React, { useEffect, useState } from 'react';
import { useForm } from 'react-hook-form';
import { useDispatch, useSelector } from 'react-redux';
import { uploadFile } from '../../../store/api';
import { createTheme, deleteTheme, getTheme, updateTheme } from '../../../store/themes';
import { openSaveChanges } from '../../../store/ui';
import { updateSelf } from '../../../store/users';
import FileInput from '../../inputs/file-input';
import Input from '../../inputs/input';
import SidebarIcon from '../../navigation/sidebar/sidebar-icon';
import CircleButton from '../../utils/buttons/circle-button';
@ -14,9 +16,9 @@ import SaveChanges from '../../utils/save-changes';
const UserSettingsThemes: React.FunctionComponent = () => {
const dispatch = useDispatch();
const selfUser = useSelector((s: Store.AppState) => s.auth.user);
const themes = useSelector((s: Store.AppState) => s.entities.themes);
const user = useSelector((s: Store.AppState) => s.auth.user);
const [themeId, setTab] = useState(user.activeThemeId);
const [themeId, setTab] = useState(selfUser.activeThemeId);
useEffect(() => {
const theme = getTheme(themeId, themes);
@ -80,11 +82,21 @@ const UserSettingsThemes: React.FunctionComponent = () => {
options={{ value: theme.name }} />
<Input
tooltip="The code that is used to share themes."
className="w-1/3"
className="w-1/3 mr-5"
label="Code"
name="code"
register={register}
options={{ value: theme.code }} />
<FileInput
className="w-1/3"
name="iconURL"
options={{ value: theme.iconURL }}
onChange={(e) => {
const file = e.currentTarget?.files?.[0];
if (!file) return;
dispatch(uploadFile(file, ({ url }) => setValue('iconURL', url)));
}} />
</div>
<textarea
@ -101,9 +113,11 @@ const UserSettingsThemes: React.FunctionComponent = () => {
<NormalButton
className="bg-success dark mt-5"
onClick={onApply}>Apply</NormalButton>
<NormalButton
className="bg-danger dark mt-5 ml-2"
onClick={onDelete}>Delete</NormalButton>
{(selfUser.id === theme.creatorId) && (
<NormalButton
className="bg-danger dark mt-5 ml-2"
onClick={onDelete}>Delete</NormalButton>
)}
</div>
) : null;
}

View File

@ -62,3 +62,7 @@ nav[role='menu'] {
display: block; /* For Firefox */
color: var(--muted);
}
::file-selector-button {
display: none;
}

View File

@ -1024,6 +1024,9 @@ video {
.w-\[480px\] {
width: 480px;
}
.w-1\/4 {
width: 25%;
}
.max-w-full {
max-width: 100%;
}