Compare commits

..

4 Commits

Author SHA1 Message Date
Elias Schneider
adf0f8d57e release: 0.16.0 2023-07-09 17:15:26 +02:00
Elias Schneider
447c86f1c9 chore: remove backend Dockerfile 2023-06-28 15:45:54 +02:00
pierrbt
1466240461 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>
2023-06-26 08:22:15 +02:00
pierrbt
348852cfa4 feat: Adding the possibility of copying the link by clicking text and icons (#171) 2023-06-23 20:07:49 +02:00
15 changed files with 204 additions and 79 deletions

View File

@@ -1,3 +1,11 @@
## [0.16.0](https://github.com/stonith404/pingvin-share/compare/v0.15.0...v0.16.0) (2023-07-09)
### Features
* Adding more informations on My Shares page (table and modal) ([#174](https://github.com/stonith404/pingvin-share/issues/174)) ([1466240](https://github.com/stonith404/pingvin-share/commit/14662404614f15bc25384d924d8cb0458ab06cd8))
* Adding the possibility of copying the link by clicking text and icons ([#171](https://github.com/stonith404/pingvin-share/issues/171)) ([348852c](https://github.com/stonith404/pingvin-share/commit/348852cfa4275f5c642669b43697f83c35333044))
## [0.15.0](https://github.com/stonith404/pingvin-share/compare/v0.14.1...v0.15.0) (2023-05-09)

View File

@@ -1,22 +0,0 @@
FROM node:18 AS deps
WORKDIR /opt/app
COPY package.json package-lock.json ./
COPY prisma ./prisma
RUN npm ci
RUN npx prisma generate
FROM node:18 As build
WORKDIR /opt/app
COPY . .
COPY --from=deps /opt/app/node_modules ./node_modules
RUN npm run build
FROM node:18 As runner
WORKDIR /opt/app
COPY --from=build /opt/app/node_modules ./node_modules
COPY --from=build /opt/app/dist ./dist
COPY --from=build /opt/app/prisma ./prisma
COPY --from=deps /opt/app/package.json ./
CMD npm run prod

View File

@@ -1,12 +1,12 @@
{
"name": "pingvin-share-backend",
"version": "0.15.0",
"version": "0.16.0",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "pingvin-share-backend",
"version": "0.15.0",
"version": "0.16.0",
"dependencies": {
"@nestjs/common": "^9.3.9",
"@nestjs/config": "^2.3.1",

View File

@@ -1,6 +1,6 @@
{
"name": "pingvin-share-backend",
"version": "0.15.0",
"version": "0.16.0",
"scripts": {
"build": "nest build",
"dev": "cross-env NODE_ENV=development nest start --watch",

View File

@@ -1,7 +1,13 @@
import { Expose, plainToClass } from "class-transformer";
import { Expose, plainToClass, Type } from "class-transformer";
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()
views: number;
@@ -11,6 +17,10 @@ export class MyShareDTO extends ShareDTO {
@Expose()
recipients: string[];
@Expose()
@Type(() => OmitType(FileDTO, ["share", "from"] as const))
files: Omit<FileDTO, "share" | "from">[];
from(partial: Partial<MyShareDTO>) {
return plainToClass(MyShareDTO, partial, { excludeExtraneousValues: true });
}
@@ -20,4 +30,4 @@ export class MyShareDTO extends ShareDTO {
plainToClass(MyShareDTO, part, { excludeExtraneousValues: true })
);
}
}
}

View File

@@ -195,7 +195,7 @@ export class ShareService {
orderBy: {
expiration: "desc",
},
include: { recipients: true },
include: { recipients: true, files: true },
});
return shares.map((share) => {

View File

@@ -1,12 +1,12 @@
{
"name": "pingvin-share-frontend",
"version": "0.15.0",
"version": "0.16.0",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "pingvin-share-frontend",
"version": "0.15.0",
"version": "0.16.0",
"dependencies": {
"@emotion/react": "^11.10.6",
"@emotion/server": "^11.10.0",

View File

@@ -1,6 +1,6 @@
{
"name": "pingvin-share-frontend",
"version": "0.15.0",
"version": "0.16.0",
"scripts": {
"dev": "next dev",
"build": "next build",

View File

@@ -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;

View File

@@ -1,9 +1,7 @@
import { ActionIcon, Button, Stack, TextInput } from "@mantine/core";
import { useClipboard } from "@mantine/hooks";
import { Button, Stack } from "@mantine/core";
import { useModals } from "@mantine/modals";
import { ModalsContextProps } from "@mantine/modals/lib/context";
import { TbCopy } from "react-icons/tb";
import toast from "../../../utils/toast.util";
import CopyTextField from "../../upload/CopyTextField";
const showCompletedReverseShareModal = (
modals: ModalsContextProps,
@@ -26,28 +24,11 @@ const Body = ({
link: string;
getReverseShares: () => void;
}) => {
const clipboard = useClipboard({ timeout: 500 });
const modals = useModals();
return (
<Stack align="stretch">
<TextInput
readOnly
variant="filled"
value={link}
rightSection={
window.isSecureContext && (
<ActionIcon
onClick={() => {
clipboard.copy(link);
toast.success("Your link was copied to the keyboard.");
}}
>
<TbCopy />
</ActionIcon>
)
}
/>
<CopyTextField link={link} />
<Button
onClick={() => {

View File

@@ -0,0 +1,48 @@
import { useRef, useState } from "react";
import toast from "../../utils/toast.util";
import { ActionIcon, TextInput } from "@mantine/core";
import { TbCheck, TbCopy } from "react-icons/tb";
import { useClipboard } from "@mantine/hooks";
function CopyTextField(props: { link: string }) {
const clipboard = useClipboard({ timeout: 500 });
const [checkState, setCheckState] = useState(false);
const [textClicked, setTextClicked] = useState(false);
const timerRef = useRef<number | ReturnType<typeof setTimeout> | undefined>(
undefined
);
const copyLink = () => {
clipboard.copy(props.link);
toast.success("Your link was copied to the keyboard.");
if (timerRef.current) clearTimeout(timerRef.current);
timerRef.current = setTimeout(() => {
setCheckState(false);
}, 1500);
setCheckState(true);
};
return (
<TextInput
readOnly
label="Link"
variant="filled"
value={props.link}
onClick={() => {
if (!textClicked) {
copyLink();
setTextClicked(true);
}
}}
rightSection={
window.isSecureContext && (
<ActionIcon onClick={copyLink}>
{checkState ? <TbCheck /> : <TbCopy />}
</ActionIcon>
)
}
/>
);
}
export default CopyTextField;

View File

@@ -1,12 +1,10 @@
import { ActionIcon, Button, Stack, Text, TextInput } from "@mantine/core";
import { useClipboard } from "@mantine/hooks";
import { Button, Stack, Text } from "@mantine/core";
import { useModals } from "@mantine/modals";
import { ModalsContextProps } from "@mantine/modals/lib/context";
import moment from "moment";
import { useRouter } from "next/router";
import { TbCopy } from "react-icons/tb";
import { Share } from "../../../types/share.type";
import toast from "../../../utils/toast.util";
import CopyTextField from "../CopyTextField";
const showCompletedUploadModal = (
modals: ModalsContextProps,
@@ -23,30 +21,14 @@ const showCompletedUploadModal = (
};
const Body = ({ share, appUrl }: { share: Share; appUrl: string }) => {
const clipboard = useClipboard({ timeout: 500 });
const modals = useModals();
const router = useRouter();
const link = `${appUrl}/share/${share.id}`;
return (
<Stack align="stretch">
<TextInput
readOnly
variant="filled"
value={link}
rightSection={
window.isSecureContext && (
<ActionIcon
onClick={() => {
clipboard.copy(link);
toast.success("Your link was copied to the keyboard.");
}}
>
<TbCopy />
</ActionIcon>
)
}
/>
<CopyTextField link={link} />
<Text
size="xs"
sx={(theme) => ({

View File

@@ -4,6 +4,7 @@ import {
Button,
Center,
Group,
MediaQuery,
Space,
Stack,
Table,
@@ -15,7 +16,7 @@ import { useModals } from "@mantine/modals";
import moment from "moment";
import Link from "next/link";
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 CenterLoader from "../../components/core/CenterLoader";
import Meta from "../../components/Meta";
@@ -23,6 +24,7 @@ import useConfig from "../../hooks/config.hook";
import shareService from "../../services/share.service";
import { MyShare } from "../../types/share.type";
import toast from "../../utils/toast.util";
import showShareInformationsModal from "../../components/account/showShareInformationsModal";
const MyShares = () => {
const modals = useModals();
@@ -60,6 +62,10 @@ const MyShares = () => {
<thead>
<tr>
<th>Name</th>
<MediaQuery smallerThan="md" styles={{ display: "none" }}>
<th>Description</th>
</MediaQuery>
<th>Visitors</th>
<th>Expires at</th>
<th></th>
@@ -69,6 +75,18 @@ const MyShares = () => {
{shares.map((share) => (
<tr key={share.id}>
<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>
{moment(share.expiration).unix() === 0
@@ -77,6 +95,21 @@ const MyShares = () => {
</td>
<td>
<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
color="victoria"
variant="light"

View File

@@ -24,7 +24,7 @@ export type ShareMetaData = {
export type MyShare = Share & {
views: number;
cratedAt: Date;
createdAt: Date;
};
export type MyReverseShare = {

View File

@@ -1,6 +1,6 @@
{
"name": "pingvin-share",
"version": "0.15.0",
"version": "0.16.0",
"scripts": {
"format": "cd frontend && npm run format && cd ../backend && npm run format",
"lint": "cd frontend && npm run lint && cd ../backend && npm run lint",