Frontend: Better file uploads
This commit is contained in:
parent
491f1b1b4c
commit
1a1f2ee4af
@ -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}`)
|
||||
}
|
||||
}
|
||||
}
|
@ -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)
|
||||
|
21
frontend/src/components/inputs/file-input.tsx
Normal file
21
frontend/src/components/inputs/file-input.tsx
Normal 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;
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
@ -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];
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -62,3 +62,7 @@ nav[role='menu'] {
|
||||
display: block; /* For Firefox */
|
||||
color: var(--muted);
|
||||
}
|
||||
|
||||
::file-selector-button {
|
||||
display: none;
|
||||
}
|
||||
|
@ -1024,6 +1024,9 @@ video {
|
||||
.w-\[480px\] {
|
||||
width: 480px;
|
||||
}
|
||||
.w-1\/4 {
|
||||
width: 25%;
|
||||
}
|
||||
.max-w-full {
|
||||
max-width: 100%;
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user