feat: v3.2.2 new file management & viewing

This commit is contained in:
diced 2021-09-12 21:17:27 -07:00
parent ece3e16459
commit 4728258750
No known key found for this signature in database
GPG key ID: 85AB64C74535D76E
15 changed files with 114 additions and 99 deletions

View file

@ -1,3 +1,5 @@
prisma/migrations
prisma
node_modules
.next
.next
uploads
.git

View file

@ -1,6 +1,6 @@
{
"name": "zip3",
"version": "3.2.1",
"version": "3.2.2",
"scripts": {
"prepare": "husky install",
"dev": "NODE_ENV=development node server",
@ -17,10 +17,9 @@
"@emotion/styled": "^11.3.0",
"@iarna/toml": "2.2.5",
"@material-ui/core": "^5.0.0-alpha.37",
"@material-ui/data-grid": "^4.0.0-alpha.32",
"@material-ui/icons": "^5.0.0-alpha.37",
"@material-ui/styles": "^5.0.0-alpha.35",
"@prisma/client": "^2.30.3",
"@prisma/client": "^3.0.2",
"@reduxjs/toolkit": "^1.6.0",
"argon2": "^0.28.2",
"colorette": "^1.2.2",
@ -30,7 +29,7 @@
"formik": "^2.2.9",
"multer": "^1.4.2",
"next": "11.1.1",
"prisma": "^2.30.3",
"prisma": "^3.0.2",
"react": "17.0.2",
"react-dom": "17.0.2",
"react-dropzone": "^11.3.2",

View file

@ -123,6 +123,7 @@ function shouldUseYarn() {
});
srv.on('listening', () => {
Logger.get('server').info(`listening on ${config.core.host}:${config.core.port}`);
if (process.platform === 'linux' && dev) execSync(`xdg-open ${config.core.secure ? 'https' : 'http'}://${config.core.host === '0.0.0.0' ? 'localhost' : config.core.host}:${config.core.port}`);
});
srv.listen(config.core.port, config.core.host ?? '0.0.0.0');

View file

@ -4,64 +4,80 @@ import {
Card,
CardMedia,
CardActionArea,
Popover,
Button,
ButtonGroup
Dialog,
DialogTitle,
DialogActions,
DialogContent
} from '@material-ui/core';
import AudioIcon from '@material-ui/icons/Audiotrack';
import copy from 'copy-to-clipboard';
import useFetch from '../lib/hooks/useFetch';
import useFetch from 'hooks/useFetch';
export default function Image({ image, updateImages }) {
const [anchorEl, setAnchorEl] = useState(null);
const [open, setOpen] = useState(false);
const [t,] = useState(image.mimetype.split('/')[0]);
const handleDelete = async () => {
const res = await useFetch('/api/user/images', 'DELETE', { id: image.id });
const res = await useFetch('/api/user/files', 'DELETE', { id: image.id });
if (!res.error) updateImages(true);
setAnchorEl(null);
setOpen(false);
};
const handleCopy = () => {
copy(`${window.location.protocol}//${window.location.host}${image.url}`);
setAnchorEl(null);
setOpen(false);
};
const handleFavorite = async () => {
const data = await useFetch('/api/user/images', 'PATCH', { id: image.id, favorite: !image.favorite });
const data = await useFetch('/api/user/files', 'PATCH', { id: image.id, favorite: !image.favorite });
if (!data.error) updateImages(true);
};
const Type = (props) => {
return {
'video': <video controls {...props} />,
// eslint-disable-next-line jsx-a11y/alt-text
'image': <img {...props} />,
'audio': <audio controls {...props} />
}[t];
};
return (
<>
<Card sx={{ maxWidth: '100%' }}>
<CardActionArea>
<CardActionArea sx={t === 'audio' ? { justifyContent: 'center', display: 'flex', alignItems: 'center' } : {}}>
<CardMedia
sx={{ height: 320 }}
sx={{ height: 320, fontSize: 70, width: '100%' }}
image={image.url}
title={image.file}
onClick={e => setAnchorEl(e.currentTarget)}
component={t === 'audio' ? AudioIcon : t} // this is done because audio without controls is hidden
onClick={() => setOpen(true)}
/>
</CardActionArea>
</Card>
<Popover
open={Boolean(anchorEl)}
anchorEl={anchorEl}
onClose={() => setAnchorEl(null)}
anchorOrigin={{
vertical: 'center',
horizontal: 'center',
}}
transformOrigin={{
vertical: 'center',
horizontal: 'center',
}}
<Dialog
open={open}
onClose={() => setOpen(false)}
>
<ButtonGroup variant='contained'>
<Button onClick={handleDelete} color='primary'>Delete</Button>
<Button onClick={handleCopy} color='primary'>Copy URL</Button>
<Button onClick={handleFavorite} color='primary'>{image.favorite ? 'Unfavorite' : 'Favorite'}</Button>
</ButtonGroup>
</Popover>
<DialogTitle id='alert-dialog-title'>
{image.file}
</DialogTitle>
<DialogContent>
<Type
style={{ width: '100%' }}
src={image.url}
alt={image.url}
/>
</DialogContent>
<DialogActions>
<Button onClick={handleDelete} color='inherit'>Delete</Button>
<Button onClick={handleCopy} color='inherit'>Copy URL</Button>
<Button onClick={handleFavorite} color='inherit'>{image.favorite ? 'Unfavorite' : 'Favorite'}</Button>
</DialogActions>
</Dialog>
</>
);
}

View file

@ -1,6 +1,5 @@
import React, { useState } from 'react';
import Link from 'next/link';
import useFetch from '../lib/hooks/useFetch';
import {
AppBar,
@ -29,13 +28,13 @@ import {
Menu as MenuIcon,
Home as HomeIcon,
AccountCircle as AccountIcon,
Image as ImageIcon,
Folder as FolderIcon,
Upload as UploadIcon,
ContentCopy as CopyIcon,
Autorenew as ResetIcon,
Logout as LogoutIcon,
PeopleAlt as UsersIcon,
Brush as BrushIcon
Brush as BrushIcon,
} from '@material-ui/icons';
import copy from 'copy-to-clipboard';
import Backdrop from './Backdrop';
@ -43,6 +42,7 @@ import { friendlyThemeName, themes } from './Theming';
import { useRouter } from 'next/router';
import { useStoreDispatch } from 'lib/redux/store';
import { updateUser } from 'lib/redux/reducers/user';
import useFetch from 'hooks/useFetch';
const items = [
{
@ -51,9 +51,9 @@ const items = [
link: '/dashboard'
},
{
icon: <ImageIcon />,
text: 'Images',
link: '/dashboard/images'
icon: <FolderIcon />,
text: 'Files',
link: '/dashboard/files'
},
{
icon: <UploadIcon />,

View file

@ -1,6 +1,8 @@
import React from 'react';
import { ThemeProvider } from '@emotion/react';
import { CssBaseline } from '@material-ui/core';
// themes
import dark_blue from 'lib/themes/dark_blue';
import dark from 'lib/themes/dark';
import ayu_dark from 'lib/themes/ayu_dark';
@ -9,6 +11,7 @@ import ayu_light from 'lib/themes/ayu_light';
import nord from 'lib/themes/nord';
import polar from 'lib/themes/polar';
import dracula from 'lib/themes/dracula';
import { useStoreSelector } from 'lib/redux/store';
import createTheme from 'lib/themes';

View file

@ -16,6 +16,7 @@ import {
CardMedia,
Card as MuiCard
} from '@material-ui/core';
import AudioIcon from '@material-ui/icons/Audiotrack';
import Link from 'components/Link';
import Card from 'components/Card';
@ -61,18 +62,19 @@ function StatTable({ rows, columns }) {
<TableHead>
<TableRow>
{columns.map(col => (
<TableCell key={col.name}>{col.name}</TableCell>
<TableCell key={col.name} sx={{ borderColor: t => t.palette.divider }}>{col.name}</TableCell>
))}
</TableRow>
</TableHead>
<TableBody>
{rows.map((row, i) => (
{rows.map(row => (
<TableRow
hover
key={row.username}
sx={{ '&:last-child td, &:last-child th': { border: 0 } }}
>
{columns.map(col => (
<TableCell key={col.id}>
<TableCell key={col.id} sx={{ borderColor: t => t.palette.divider }}>
{col.format ? col.format(row[col.id]) : row[col.id]}
</TableCell>
))}
@ -94,8 +96,8 @@ export default function Dashboard() {
const [rowsPerPage, setRowsPerPage] = useState(10);
const updateImages = async () => {
const imgs = await useFetch('/api/user/images');
const recent = await useFetch('/api/user/recent');
const imgs = await useFetch('/api/user/files');
const recent = await useFetch('/api/user/recent?filter=media');
const stts = await useFetch('/api/stats');
setImages(imgs);
setStats(stts);
@ -106,13 +108,13 @@ export default function Dashboard() {
setPage(newPage);
};
const handleChangeRowsPerPage = (event) => {
const handleChangeRowsPerPage = event => {
setRowsPerPage(+event.target.value);
setPage(0);
};
const handleDelete = async image => {
const res = await useFetch('/api/user/images', 'DELETE', { id: image.id });
const res = await useFetch('/api/user/files', 'DELETE', { id: image.id });
if (!res.error) updateImages();
};
@ -135,6 +137,8 @@ export default function Dashboard() {
sx={{ height: 220 }}
image={image.url}
title={image.file}
controls
component={image.mimetype.split('/')[0] === 'audio' ? AudioIcon : image.mimetype.split('/')[0]} // this is done because audio without controls is hidden
/>
</CardActionArea>
</MuiCard>
@ -173,16 +177,16 @@ export default function Dashboard() {
<Table size='small'>
<TableHead>
<TableRow>
{columns.map((column) => (
{columns.map(column => (
<TableCell
key={column.id}
align={column.align}
sx={{ minWidth: column.minWidth }}
sx={{ minWidth: column.minWidth, borderColor: t => t.palette.divider }}
>
{column.label}
</TableCell>
))}
<TableCell sx={{ minWidth: 200 }} align='right'>
<TableCell sx={{ minWidth: 200, borderColor: t => t.palette.divider }} align='right'>
Actions
</TableCell>
</TableRow>
@ -190,18 +194,18 @@ export default function Dashboard() {
<TableBody>
{images
.slice(page * rowsPerPage, page * rowsPerPage + rowsPerPage)
.map((row) => {
.map(row => {
return (
<TableRow hover role='checkbox' tabIndex={-1} key={row.id}>
{columns.map((column) => {
{columns.map(column => {
const value = row[column.id];
return (
<TableCell key={column.id} align={column.align}>
<TableCell key={column.id} align={column.align} sx={{ borderColor: t => t.palette.divider }}>
{column.format ? column.format(value) : value}
</TableCell>
);
})}
<TableCell align='right'>
<TableCell align='right' sx={{ borderColor: t => t.palette.divider }}>
<ButtonGroup variant='outlined'>
<Button onClick={() => handleDelete(row)} color='error' size='small'>Delete</Button>
</ButtonGroup>

View file

@ -1,12 +1,12 @@
import React, { useEffect, useState } from 'react';
import { Grid, Pagination, Box, Typography, Accordion, AccordionSummary, AccordionDetails } from '@material-ui/core';
import { ExpandMore } from '@material-ui/icons';
import Backdrop from 'components/Backdrop';
import ZiplineImage from 'components/Image';
import useFetch from 'hooks/useFetch';
import { ExpandMore } from '@material-ui/icons';
export default function Upload() {
export default function Files() {
const [pages, setPages] = useState([]);
const [page, setPage] = useState(1);
const [favoritePages, setFavoritePages] = useState([]);
@ -15,9 +15,9 @@ export default function Upload() {
const updatePages = async favorite => {
setLoading(true);
const pages = await useFetch('/api/user/images?paged=true&filter=image');
const pages = await useFetch('/api/user/files?paged=true&filter=media');
if (favorite) {
const fPages = await useFetch('/api/user/images?paged=true&favorite=true');
const fPages = await useFetch('/api/user/files?paged=true&favorite=media');
setFavoritePages(fPages);
}
setPages(pages);
@ -39,13 +39,13 @@ export default function Upload() {
pt={2}
pb={3}
>
<Typography variant='h4'>No Images</Typography>
<Typography variant='h4'>No Files</Typography>
</Box>
) : <Typography variant='h4'>Images</Typography>}
) : <Typography variant='h4'>Files</Typography>}
{favoritePages.length ? (
<Accordion sx={{ my: 2, border: 1, borderColor: t => t.palette.divider }} elevation={0}>
<AccordionSummary expandIcon={<ExpandMore />}>
<Typography variant='h4'>Favorite Images</Typography>
<Typography variant='h4'>Favorite Files</Typography>
</AccordionSummary>
<AccordionDetails>
<Grid container spacing={2}>

View file

@ -167,7 +167,6 @@ export default function Manage() {
validationSchema: themeValidationSchema,
onSubmit: async values => {
setLoading(true);
const newUser = await useFetch('/api/user', 'PATCH', { customTheme: values });
if (newUser.error) {

View file

@ -18,7 +18,7 @@ export default function Upload({ route }) {
const [open, setOpen] = useState(false);
const [severity, setSeverity] = useState('success');
const [message, setMessage] = useState('Saved');
console.log(files);
const handleUpload = async () => {
const body = new FormData();

View file

@ -48,7 +48,7 @@ export default function createTheme(o: ThemeOptions) {
backgroundColor: o.border
}
}
}
}
},
},
});
}

View file

@ -55,7 +55,7 @@ async function handler(req: NextApiReq, res: NextApiRes) {
// @ts-ignore
images.map(image => image.url = `/r/${image.file}`);
if (req.query.filter && req.query.filter === 'image') images = images.filter(x => x.mimetype.startsWith('image'));
if (req.query.filter && req.query.filter === 'media') images = images.filter(x => /^(video|audio|image)/.test(x.mimetype));
return res.json(req.query.paged ? chunk(images, 16) : images);
}

View file

@ -9,7 +9,7 @@ async function handler(req: NextApiReq, res: NextApiRes) {
if (take > 50) return res.error('take can\'t be more than 50');
const images = await prisma.image.findMany({
let images = await prisma.image.findMany({
take,
orderBy: {
created_at: 'desc'
@ -23,6 +23,7 @@ async function handler(req: NextApiReq, res: NextApiRes) {
// @ts-ignore
images.map(image => image.url = `/r/${image.file}`);
if (req.query.filter && req.query.filter === 'media') images = images.filter(x => /^(video|audio|image)/.test(x.mimetype));
return res.json(images);
}

View file

@ -1,7 +1,7 @@
import React from 'react';
import useLogin from 'hooks/useLogin';
import Layout from 'components/Layout';
import Images from 'components/pages/Images';
import Files from 'components/pages/Files';
export default function ImagesPage() {
const { user, loading } = useLogin();
@ -14,7 +14,7 @@ export default function ImagesPage() {
loading={loading}
noPaper={false}
>
<Images />
<Files />
</Layout>
);
}

View file

@ -399,16 +399,6 @@
react-is "^17.0.0"
react-transition-group "^4.4.0"
"@material-ui/data-grid@^4.0.0-alpha.32":
version "4.0.0-alpha.32"
resolved "https://registry.yarnpkg.com/@material-ui/data-grid/-/data-grid-4.0.0-alpha.32.tgz#72952d1dea9a9440a02827dd66996a8e94d3f28f"
integrity sha512-yEmQ8OGGHCB9fUx6f6/ncIxAQpH/U/295EqqocCsbIjLJA1rUYF5eseo2/jnW1Wd69o3aTsXozdlKV8tQNit2Q==
dependencies:
"@material-ui/utils" "^5.0.0-alpha.14"
clsx "^1.0.4"
prop-types "^15.7.2"
reselect "^4.0.0"
"@material-ui/icons@^5.0.0-alpha.37":
version "5.0.0-alpha.37"
resolved "https://registry.yarnpkg.com/@material-ui/icons/-/icons-5.0.0-alpha.37.tgz#8579986e0a3b4586dc7fbf23234d8bfe909b6c3c"
@ -487,7 +477,7 @@
prop-types "^15.7.2"
react-is "^17.0.0"
"@material-ui/utils@5.0.0-alpha.35", "@material-ui/utils@^5.0.0-alpha.14":
"@material-ui/utils@5.0.0-alpha.35":
version "5.0.0-alpha.35"
resolved "https://registry.yarnpkg.com/@material-ui/utils/-/utils-5.0.0-alpha.35.tgz#89078592c42bca5db712e82e12d56cd4be737c01"
integrity sha512-Msu+zIXd7Y2JrTU9JIf0xjjjAMdWEIdlj2Tmj9bSYFF6bgStrQ1WXXZxxFz5GmdzT7FcLi5U3PqBynSNX/QDGA==
@ -612,22 +602,22 @@
resolved "https://registry.yarnpkg.com/@popperjs/core/-/core-2.9.2.tgz#adea7b6953cbb34651766b0548468e743c6a2353"
integrity sha512-VZMYa7+fXHdwIq1TDhSXoVmSPEGM/aa+6Aiq3nVVJ9bXr24zScr+NlKFKC3iPljA7ho/GAZr+d2jOf5GIRC30Q==
"@prisma/client@^2.30.3":
version "2.30.3"
resolved "https://registry.yarnpkg.com/@prisma/client/-/client-2.30.3.tgz#49c1015e2cec26a44b20c62eb2fd738cb0bb043b"
integrity sha512-Ey2miZ+Hne12We3rA8XrlPoAF0iuKEhw5IK2nropaelSt0Ju3b2qSz9Qt50a/1Mx3+7yRSu/iSXt8y9TUMl/Yw==
"@prisma/client@^3.0.2":
version "3.0.2"
resolved "https://registry.yarnpkg.com/@prisma/client/-/client-3.0.2.tgz#f04d9b252f3d0c6918df43ad228eac27d03f6db1"
integrity sha512-6SrDYY2Yr5AmYpVB3XAXFqfzxKMdDTemXR7FmfXthnxWhQHoBwRLNZ3B3GyI/MmWa5tr+kaaGDJjp1LU0vuYvQ==
dependencies:
"@prisma/engines-version" "2.30.1-2.b8c35d44de987a9691890b3ddf3e2e7effb9bf20"
"@prisma/engines-version" "2.31.0-32.2452cc6313d52b8b9a96999ac0e974d0aedf88db"
"@prisma/engines-version@2.30.1-2.b8c35d44de987a9691890b3ddf3e2e7effb9bf20":
version "2.30.1-2.b8c35d44de987a9691890b3ddf3e2e7effb9bf20"
resolved "https://registry.yarnpkg.com/@prisma/engines-version/-/engines-version-2.30.1-2.b8c35d44de987a9691890b3ddf3e2e7effb9bf20.tgz#d5ef55c92beeba56e52bba12b703af0bfd30530d"
integrity sha512-/iDRgaoSQC77WN2oDsOM8dn61fykm6tnZUAClY+6p+XJbOEgZ9gy4CKuKTBgrjSGDVjtQ/S2KGcYd3Ring8xaw==
"@prisma/engines-version@2.31.0-32.2452cc6313d52b8b9a96999ac0e974d0aedf88db":
version "2.31.0-32.2452cc6313d52b8b9a96999ac0e974d0aedf88db"
resolved "https://registry.yarnpkg.com/@prisma/engines-version/-/engines-version-2.31.0-32.2452cc6313d52b8b9a96999ac0e974d0aedf88db.tgz#c45323e420f47dd950b22c873bdcf38f75e65779"
integrity sha512-iArSApZZImVmT9oC/rGOjzvpG2AOqlIeqYcVnop9poA3FxD4zfVPbNPH9DTgOWhc06OkBHujJZeAcsNddVabIQ==
"@prisma/engines@2.30.1-2.b8c35d44de987a9691890b3ddf3e2e7effb9bf20":
version "2.30.1-2.b8c35d44de987a9691890b3ddf3e2e7effb9bf20"
resolved "https://registry.yarnpkg.com/@prisma/engines/-/engines-2.30.1-2.b8c35d44de987a9691890b3ddf3e2e7effb9bf20.tgz#2df768aa7c9f84acaa1f35c970417822233a9fb1"
integrity sha512-WPnA/IUrxDihrRhdP6+8KAVSwsc0zsh8ioPYsLJjOhzVhwpRbuFH2tJDRIAbc+qFh+BbTIZbeyBYt8fpNXaYQQ==
"@prisma/engines@2.31.0-32.2452cc6313d52b8b9a96999ac0e974d0aedf88db":
version "2.31.0-32.2452cc6313d52b8b9a96999ac0e974d0aedf88db"
resolved "https://registry.yarnpkg.com/@prisma/engines/-/engines-2.31.0-32.2452cc6313d52b8b9a96999ac0e974d0aedf88db.tgz#b6cf70bc05dd2a62168a16f3ea58a1b011074621"
integrity sha512-Q9CwN6e5E5Abso7J3A1fHbcF4NXGRINyMnf7WQ07fXaebxTTARY5BNUzy2Mo5uH82eRVO5v7ImNuR044KTjLJg==
"@reduxjs/toolkit@^1.6.0":
version "1.6.0"
@ -4444,12 +4434,12 @@ prepend-http@^1.0.1:
resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-1.0.4.tgz#d4f4562b0ce3696e41ac52d0e002e57a635dc6dc"
integrity sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw=
prisma@^2.30.3:
version "2.30.3"
resolved "https://registry.yarnpkg.com/prisma/-/prisma-2.30.3.tgz#e4a770e1f52151e72c1c5be0aa2e75222a0135c4"
integrity sha512-48qYba2BIyUmXuosBZs0g3kYGrxKvo4VkSHYOuLlDdDirmKyvoY2hCYMUYHSx3f++8ovfgs+MX5KmNlP+iAZrQ==
prisma@^3.0.2:
version "3.0.2"
resolved "https://registry.yarnpkg.com/prisma/-/prisma-3.0.2.tgz#e86cb6abf4a815c7ac97b9d0ed383f01c253ce34"
integrity sha512-TyOCbtWGDVdWvsM1RhUzJXoGClXGalHhyYWIc5eizSF8T1ScGiOa34asBUdTnXOUBFSErbsqMNw40DHAteBm1A==
dependencies:
"@prisma/engines" "2.30.1-2.b8c35d44de987a9691890b3ddf3e2e7effb9bf20"
"@prisma/engines" "2.31.0-32.2452cc6313d52b8b9a96999ac0e974d0aedf88db"
process-nextick-args@~2.0.0:
version "2.0.1"