mirror of
https://github.com/stonith404/pingvin-share.git
synced 2025-02-19 01:55:48 -05:00
feat: Adding more informations on My Shares page (table and modal) (#174)
* Adding an information button to the shares and corrected MyShare interface * Adding other informations and disk usage * Adding description, disk usage * Add case if the expiration is never * Adding file size and better UI * UI changes to Information Modal * Adding description to the My Shares page * Ran format * Remove string type Co-authored-by: Elias Schneider <login@eliasschneider.com> * Remove string type check Co-authored-by: Elias Schneider <login@eliasschneider.com> * Remove string type conversion Co-authored-by: Elias Schneider <login@eliasschneider.com> * Variable name changes Co-authored-by: Elias Schneider <login@eliasschneider.com> * Remove color Co-authored-by: Elias Schneider <login@eliasschneider.com> * Requested changes made * Ran format * Adding MediaQuery --------- Co-authored-by: Elias Schneider <login@eliasschneider.com>
This commit is contained in:
parent
348852cfa4
commit
1466240461
5 changed files with 134 additions and 6 deletions
|
@ -1,7 +1,13 @@
|
||||||
import { Expose, plainToClass } from "class-transformer";
|
import { Expose, plainToClass, Type } from "class-transformer";
|
||||||
import { ShareDTO } from "./share.dto";
|
import { ShareDTO } from "./share.dto";
|
||||||
|
import {FileDTO} from "../../file/dto/file.dto";
|
||||||
|
import {OmitType} from "@nestjs/swagger";
|
||||||
|
|
||||||
export class MyShareDTO extends ShareDTO {
|
export class MyShareDTO extends OmitType(ShareDTO, [
|
||||||
|
"files",
|
||||||
|
"from",
|
||||||
|
"fromList",
|
||||||
|
] as const) {
|
||||||
@Expose()
|
@Expose()
|
||||||
views: number;
|
views: number;
|
||||||
|
|
||||||
|
@ -11,6 +17,10 @@ export class MyShareDTO extends ShareDTO {
|
||||||
@Expose()
|
@Expose()
|
||||||
recipients: string[];
|
recipients: string[];
|
||||||
|
|
||||||
|
@Expose()
|
||||||
|
@Type(() => OmitType(FileDTO, ["share", "from"] as const))
|
||||||
|
files: Omit<FileDTO, "share" | "from">[];
|
||||||
|
|
||||||
from(partial: Partial<MyShareDTO>) {
|
from(partial: Partial<MyShareDTO>) {
|
||||||
return plainToClass(MyShareDTO, partial, { excludeExtraneousValues: true });
|
return plainToClass(MyShareDTO, partial, { excludeExtraneousValues: true });
|
||||||
}
|
}
|
||||||
|
|
|
@ -195,7 +195,7 @@ export class ShareService {
|
||||||
orderBy: {
|
orderBy: {
|
||||||
expiration: "desc",
|
expiration: "desc",
|
||||||
},
|
},
|
||||||
include: { recipients: true },
|
include: { recipients: true, files: true },
|
||||||
});
|
});
|
||||||
|
|
||||||
return shares.map((share) => {
|
return shares.map((share) => {
|
||||||
|
|
|
@ -0,0 +1,85 @@
|
||||||
|
import { Text, Divider, Progress, Stack, Group, Flex } from "@mantine/core";
|
||||||
|
import { ModalsContextProps } from "@mantine/modals/lib/context";
|
||||||
|
import { MyShare } from "../../types/share.type";
|
||||||
|
import moment from "moment";
|
||||||
|
import { byteToHumanSizeString } from "../../utils/fileSize.util";
|
||||||
|
import CopyTextField from "../upload/CopyTextField";
|
||||||
|
import { FileMetaData } from "../../types/File.type";
|
||||||
|
|
||||||
|
const showShareInformationsModal = (
|
||||||
|
modals: ModalsContextProps,
|
||||||
|
share: MyShare,
|
||||||
|
appUrl: string,
|
||||||
|
maxShareSize: number
|
||||||
|
) => {
|
||||||
|
const link = `${appUrl}/share/${share.id}`;
|
||||||
|
|
||||||
|
let shareSize: number = 0;
|
||||||
|
for (let file of share.files as FileMetaData[])
|
||||||
|
shareSize += parseInt(file.size);
|
||||||
|
|
||||||
|
const formattedShareSize = byteToHumanSizeString(shareSize);
|
||||||
|
const formattedMaxShareSize = byteToHumanSizeString(maxShareSize);
|
||||||
|
const shareSizeProgress = (shareSize / maxShareSize) * 100;
|
||||||
|
|
||||||
|
const formattedCreatedAt = moment(share.createdAt).format("LLL");
|
||||||
|
const formattedExpiration =
|
||||||
|
moment(share.expiration).unix() === 0
|
||||||
|
? "Never"
|
||||||
|
: moment(share.expiration).format("LLL");
|
||||||
|
|
||||||
|
return modals.openModal({
|
||||||
|
title: "Share informations",
|
||||||
|
|
||||||
|
children: (
|
||||||
|
<Stack align="stretch" spacing="md">
|
||||||
|
<Text size="sm" color="lightgray">
|
||||||
|
<b>ID:</b> {share.id}
|
||||||
|
</Text>
|
||||||
|
|
||||||
|
<Text size="sm" color="lightgray">
|
||||||
|
<b>Description:</b> {share.description || "No description"}
|
||||||
|
</Text>
|
||||||
|
|
||||||
|
<Text size="sm" color="lightgray">
|
||||||
|
<b>Created at:</b> {formattedCreatedAt}
|
||||||
|
</Text>
|
||||||
|
|
||||||
|
<Text size="sm" color="lightgray">
|
||||||
|
<b>Expires at:</b> {formattedExpiration}
|
||||||
|
</Text>
|
||||||
|
|
||||||
|
<Divider />
|
||||||
|
|
||||||
|
<CopyTextField link={link} />
|
||||||
|
|
||||||
|
<Divider />
|
||||||
|
|
||||||
|
<Text size="sm" color="lightgray">
|
||||||
|
<b>Size:</b> {formattedShareSize} / {formattedMaxShareSize} (
|
||||||
|
{shareSizeProgress.toFixed(1)}%)
|
||||||
|
</Text>
|
||||||
|
|
||||||
|
<Flex align="center" justify="center">
|
||||||
|
{shareSize / maxShareSize < 0.1 && (
|
||||||
|
<Text size="xs" color="lightgray" style={{ marginRight: "4px" }}>
|
||||||
|
{formattedShareSize}
|
||||||
|
</Text>
|
||||||
|
)}
|
||||||
|
<Progress
|
||||||
|
value={shareSizeProgress}
|
||||||
|
label={shareSize / maxShareSize >= 0.1 ? formattedShareSize : ""}
|
||||||
|
style={{ width: shareSize / maxShareSize < 0.1 ? "70%" : "80%" }}
|
||||||
|
size="xl"
|
||||||
|
radius="xl"
|
||||||
|
/>
|
||||||
|
<Text size="xs" color="lightgray" style={{ marginLeft: "4px" }}>
|
||||||
|
{formattedMaxShareSize}
|
||||||
|
</Text>
|
||||||
|
</Flex>
|
||||||
|
</Stack>
|
||||||
|
),
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export default showShareInformationsModal;
|
|
@ -4,6 +4,7 @@ import {
|
||||||
Button,
|
Button,
|
||||||
Center,
|
Center,
|
||||||
Group,
|
Group,
|
||||||
|
MediaQuery,
|
||||||
Space,
|
Space,
|
||||||
Stack,
|
Stack,
|
||||||
Table,
|
Table,
|
||||||
|
@ -15,7 +16,7 @@ import { useModals } from "@mantine/modals";
|
||||||
import moment from "moment";
|
import moment from "moment";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { TbLink, TbTrash } from "react-icons/tb";
|
import { TbLink, TbTrash, TbInfoCircle } from "react-icons/tb";
|
||||||
import showShareLinkModal from "../../components/account/showShareLinkModal";
|
import showShareLinkModal from "../../components/account/showShareLinkModal";
|
||||||
import CenterLoader from "../../components/core/CenterLoader";
|
import CenterLoader from "../../components/core/CenterLoader";
|
||||||
import Meta from "../../components/Meta";
|
import Meta from "../../components/Meta";
|
||||||
|
@ -23,6 +24,7 @@ import useConfig from "../../hooks/config.hook";
|
||||||
import shareService from "../../services/share.service";
|
import shareService from "../../services/share.service";
|
||||||
import { MyShare } from "../../types/share.type";
|
import { MyShare } from "../../types/share.type";
|
||||||
import toast from "../../utils/toast.util";
|
import toast from "../../utils/toast.util";
|
||||||
|
import showShareInformationsModal from "../../components/account/showShareInformationsModal";
|
||||||
|
|
||||||
const MyShares = () => {
|
const MyShares = () => {
|
||||||
const modals = useModals();
|
const modals = useModals();
|
||||||
|
@ -60,6 +62,10 @@ const MyShares = () => {
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Name</th>
|
<th>Name</th>
|
||||||
|
<MediaQuery smallerThan="md" styles={{ display: "none" }}>
|
||||||
|
<th>Description</th>
|
||||||
|
</MediaQuery>
|
||||||
|
|
||||||
<th>Visitors</th>
|
<th>Visitors</th>
|
||||||
<th>Expires at</th>
|
<th>Expires at</th>
|
||||||
<th></th>
|
<th></th>
|
||||||
|
@ -69,6 +75,18 @@ const MyShares = () => {
|
||||||
{shares.map((share) => (
|
{shares.map((share) => (
|
||||||
<tr key={share.id}>
|
<tr key={share.id}>
|
||||||
<td>{share.id}</td>
|
<td>{share.id}</td>
|
||||||
|
<MediaQuery smallerThan="sm" styles={{ display: "none" }}>
|
||||||
|
<td
|
||||||
|
style={{
|
||||||
|
overflow: "hidden",
|
||||||
|
textOverflow: "ellipsis",
|
||||||
|
whiteSpace: "nowrap",
|
||||||
|
maxWidth: "300px",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{share.description || ""}
|
||||||
|
</td>
|
||||||
|
</MediaQuery>
|
||||||
<td>{share.views}</td>
|
<td>{share.views}</td>
|
||||||
<td>
|
<td>
|
||||||
{moment(share.expiration).unix() === 0
|
{moment(share.expiration).unix() === 0
|
||||||
|
@ -77,6 +95,21 @@ const MyShares = () => {
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<Group position="right">
|
<Group position="right">
|
||||||
|
<ActionIcon
|
||||||
|
color="blue"
|
||||||
|
variant="light"
|
||||||
|
size={25}
|
||||||
|
onClick={() => {
|
||||||
|
showShareInformationsModal(
|
||||||
|
modals,
|
||||||
|
share,
|
||||||
|
config.get("general.appUrl"),
|
||||||
|
parseInt(config.get("share.maxSize"))
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<TbInfoCircle />
|
||||||
|
</ActionIcon>
|
||||||
<ActionIcon
|
<ActionIcon
|
||||||
color="victoria"
|
color="victoria"
|
||||||
variant="light"
|
variant="light"
|
||||||
|
|
|
@ -24,7 +24,7 @@ export type ShareMetaData = {
|
||||||
|
|
||||||
export type MyShare = Share & {
|
export type MyShare = Share & {
|
||||||
views: number;
|
views: number;
|
||||||
cratedAt: Date;
|
createdAt: Date;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type MyReverseShare = {
|
export type MyReverseShare = {
|
||||||
|
|
Loading…
Add table
Reference in a new issue