Skip to content

Commit 4cd03c6

Browse files
Merge pull request #34 from faisalsayed10/v1.3.0
2 parents 5b1ce5d + 3c9c0ae commit 4cd03c6

29 files changed

+899
-190
lines changed

apps/web/components/ProgressBar.tsx

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import Router from "next/router";
2+
import NProgress from "nprogress";
3+
import "nprogress/nprogress.css";
4+
5+
NProgress.configure({
6+
minimum: 0.3,
7+
easing: "ease",
8+
speed: 500,
9+
showSpinner: false,
10+
});
11+
12+
Router.events.on("routeChangeStart", () => NProgress.start());
13+
Router.events.on("routeChangeComplete", () => NProgress.done());
14+
Router.events.on("routeChangeError", () => NProgress.done());
15+
16+
export default function () {
17+
return null;
18+
}

apps/web/components/files/UploadProgress.tsx

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import { Box, Flex, IconButton, Progress, Text } from "@chakra-ui/react";
22
import useBucket from "@hooks/useBucket";
3-
import useKeys from "@hooks/useKeys";
4-
import { Provider, UploadingFile } from "@util/types";
3+
import { UploadingFile } from "@util/types";
54
import React from "react";
65
import toast from "react-hot-toast";
76
import { PlayerPause, PlayerPlay, X } from "tabler-icons-react";

apps/web/components/ui/VideoModal.tsx

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -31,14 +31,11 @@ const VideoModal: React.FC<Props> = ({ src }) => {
3131
How do I get the credentials?
3232
</Button>
3333

34-
<Modal isOpen={isOpen} onClose={onClose} isCentered>
34+
<Modal isOpen={isOpen} onClose={onClose} isCentered size="2xl">
3535
<ModalOverlay />
3636
<ModalContent>
37-
<ModalHeader py="2" px="4">
38-
Here's How:
39-
</ModalHeader>
4037
<ModalCloseButton _focus={{ outline: "none" }} />
41-
<ModalBody p="0">
38+
<ModalBody p={0}>
4239
<Player playsInline src={src} />
4340
</ModalBody>
4441
</ModalContent>

apps/web/hooks/useBucket.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ export type ContextValue = {
1212
setUploadingFiles: React.Dispatch<React.SetStateAction<UploadingFile[]>>;
1313
addFolder: (name: string) => void;
1414
removeFolder: (folder: DriveFolder) => Promise<void>;
15-
addFile: (files: File[] | FileList) => Promise<void>;
15+
addFile: (files: File[] | FileList) => Promise<any>;
1616
removeFile: (file: DriveFile) => Promise<boolean>;
1717
};
1818

@@ -31,6 +31,8 @@ export default function useBucket(): ContextValue {
3131
return useFirebase();
3232
} else if ((Provider[keys.type] as Provider) === Provider.s3) {
3333
return useS3();
34+
} else if ((Provider[keys.type] as Provider) === Provider.backblaze) {
35+
return useS3();
3436
}
3537

3638
return null;

apps/web/hooks/useS3.tsx

Lines changed: 130 additions & 110 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,16 @@ import {
33
DeleteObjectsCommand,
44
GetObjectCommand,
55
ListObjectsV2Command,
6-
S3Client,
6+
S3Client
77
} from "@aws-sdk/client-s3";
88
import { getSignedUrl } from "@aws-sdk/s3-request-presigner";
99
import { Drive } from "@prisma/client";
10-
import { cryptoHexEncodedHash256, cryptoMd5Method, signRequest } from "@util/helpers/s3-helpers";
11-
import { DriveFile, DriveFolder, UploadingFile } from "@util/types";
12-
import Evaporate from "evaporate";
10+
import { calculateVariablePartSize } from "@util/helpers/s3-helpers";
11+
import { DriveFile, DriveFolder, Provider, UploadingFile } from "@util/types";
12+
import { Upload } from "@util/upload";
1313
import mime from "mime-types";
1414
import { nanoid } from "nanoid";
15-
import { createContext, useContext, useEffect, useState } from "react";
15+
import { createContext, useContext, useEffect, useRef, useState } from "react";
1616
import toast from "react-hot-toast";
1717
import { ContextValue, ROOT_FOLDER } from "./useBucket";
1818
import useUser from "./useUser";
@@ -31,6 +31,7 @@ export const S3Provider: React.FC<Props> = ({ data, fullPath, children }) => {
3131
region: data.keys.region,
3232
maxAttempts: 1,
3333
credentials: { accessKeyId: data.keys.accessKey, secretAccessKey: data.keys.secretKey },
34+
...(data.keys?.endpoint ? { endpoint: data.keys.endpoint } : {}),
3435
})
3536
);
3637
const [loading, setLoading] = useState(false);
@@ -40,6 +41,24 @@ export const S3Provider: React.FC<Props> = ({ data, fullPath, children }) => {
4041
const [folders, setFolders] = useState<DriveFolder[]>(null);
4142
const [uploadingFiles, setUploadingFiles] = useState<UploadingFile[]>([]);
4243
const [files, setFiles] = useState<DriveFile[]>(null);
44+
const isMounted = useRef(false);
45+
46+
// Fallback for old buckets not already having the bucketUrl.
47+
useEffect(() => {
48+
if (isMounted.current || !data?.keys) return;
49+
isMounted.current = true;
50+
if (data.keys.bucketUrl) return;
51+
52+
if ((Provider[data.type] as Provider) === Provider.s3) {
53+
data.keys.bucketUrl = `https://${data.keys.Bucket}.s3.${data.keys.region}.amazonaws.com`;
54+
} else if ((Provider[data.type] as Provider) === Provider.backblaze) {
55+
data.keys.bucketUrl = `https://${data.keys.Bucket}.s3.${data.keys.region}.backblazeb2.com`;
56+
}
57+
58+
return () => {
59+
isMounted.current = false;
60+
};
61+
}, [data]);
4362

4463
const addFolder = (name: string) => {
4564
const path =
@@ -53,7 +72,7 @@ export const S3Provider: React.FC<Props> = ({ data, fullPath, children }) => {
5372
parent: currentFolder.fullPath,
5473
createdAt: new Date().toISOString(),
5574
bucketName: data.keys.Bucket,
56-
bucketUrl: `https://${data.keys.Bucket}.s3.${data.keys.region}.amazonaws.com`,
75+
bucketUrl: data.keys.bucketUrl,
5776
};
5877

5978
setFolders((folders) => [...folders, newFolder]);
@@ -79,112 +98,113 @@ export const S3Provider: React.FC<Props> = ({ data, fullPath, children }) => {
7998
};
8099

81100
const addFile = async (filesToUpload: File[] | FileList) => {
82-
const evaporate = await Evaporate.create({
83-
bucket: data.keys.Bucket,
84-
awsRegion: data.keys.region,
85-
aws_key: data.keys.accessKey,
86-
computeContentMd5: true,
87-
cryptoMd5Method,
88-
cryptoHexEncodedHash256,
89-
customAuthMethod: (_, __, stringToSign) => signRequest(stringToSign, data.keys.secretKey),
90-
logging: false,
91-
});
101+
Array.from(filesToUpload).forEach(async (file) => {
102+
if (/[#\$\[\]\*/]/.test(file.name))
103+
return toast.error("File name cannot contain special characters (#$[]*/).");
92104

93-
Array.from(filesToUpload).forEach(async (toUpload) => {
94-
const id = nanoid();
95-
if (/[#\$\[\]\*/]/.test(toUpload.name)) {
96-
toast.error("File name cannot contain special characters (#$[]*/).");
97-
return;
98-
}
99-
100-
if (files?.filter((f) => f.name === toUpload.name).length > 0) {
101-
toast.error("File with same name already exists.");
102-
return;
103-
}
105+
if (files?.filter((f) => f.name === file.name).length > 0)
106+
return toast.error("File with same name already exists.");
104107

105-
const filePath =
108+
const id = nanoid();
109+
const Key =
106110
currentFolder === ROOT_FOLDER
107-
? toUpload.name
108-
: `${decodeURIComponent(currentFolder.fullPath)}${toUpload.name}`;
109-
110-
evaporate.add({
111-
name: filePath,
112-
file: toUpload,
113-
contentType: mime.lookup(toUpload.name) || "application/octet-stream",
114-
uploadInitiated: () => {
115-
setUploadingFiles((prev) =>
116-
prev.concat([
117-
{
118-
id,
119-
name: toUpload.name,
120-
key: `${data.keys.Bucket}/${filePath}`,
121-
task: evaporate,
122-
state: "running",
123-
progress: 0,
124-
error: false,
125-
},
126-
])
127-
);
128-
},
129-
progress: (_, stats) => {
130-
setUploadingFiles((prevUploadingFiles) =>
131-
prevUploadingFiles.map((uploadFile) => {
132-
return uploadFile.id === id
133-
? {
134-
...uploadFile,
135-
state: "running",
136-
progress: Math.round((stats.totalUploaded / stats.fileSize) * 100),
137-
}
138-
: uploadFile;
139-
})
140-
);
141-
},
142-
paused: () => {
143-
setUploadingFiles((prevUploadingFiles) =>
144-
prevUploadingFiles.map((uploadFile) => {
145-
return uploadFile.id === id ? { ...uploadFile, state: "paused" } : uploadFile;
146-
})
147-
);
148-
},
149-
resumed: () => {
150-
setUploadingFiles((prevUploadingFiles) =>
151-
prevUploadingFiles.map((uploadFile) => {
152-
return uploadFile.id === id ? { ...uploadFile, state: "running" } : uploadFile;
153-
})
154-
);
111+
? file.name
112+
: `${decodeURIComponent(currentFolder.fullPath)}${file.name}`;
113+
114+
const upload = new Upload({
115+
client: s3Client,
116+
params: {
117+
Key,
118+
Body: file,
119+
Bucket: data.keys.Bucket,
120+
ContentType: mime.lookup(file.name) || "application/octet-stream",
155121
},
156-
error: (_) => {
157-
setUploadingFiles((prevUploadingFiles) => {
158-
return prevUploadingFiles.map((uploadFile) => {
159-
if (uploadFile.id === id) return { ...uploadFile, error: true };
160-
return uploadFile;
161-
});
122+
partSize: calculateVariablePartSize(file.size),
123+
});
124+
125+
upload.on("initiated", () => {
126+
setUploadingFiles((prev) =>
127+
prev.concat([
128+
{
129+
id,
130+
name: file.name,
131+
key: Key,
132+
task: upload,
133+
state: "running",
134+
progress: 0,
135+
error: false,
136+
},
137+
])
138+
);
139+
});
140+
141+
upload.on("progress", (progress) => {
142+
setUploadingFiles((prevUploadingFiles) =>
143+
prevUploadingFiles.map((uploadFile) => {
144+
return uploadFile.id === id
145+
? {
146+
...uploadFile,
147+
state: "running",
148+
progress: Number(
149+
parseFloat(((progress.loaded / progress.total) * 100).toString()).toFixed(2)
150+
),
151+
}
152+
: uploadFile;
153+
})
154+
);
155+
});
156+
157+
upload.on("paused", () => {
158+
setUploadingFiles((prevUploadingFiles) =>
159+
prevUploadingFiles.map((uploadFile) => {
160+
return uploadFile.id === id ? { ...uploadFile, state: "paused" } : uploadFile;
161+
})
162+
);
163+
});
164+
165+
upload.on("resumed", () => {
166+
setUploadingFiles((prevUploadingFiles) =>
167+
prevUploadingFiles.map((uploadFile) => {
168+
return uploadFile.id === id ? { ...uploadFile, state: "running" } : uploadFile;
169+
})
170+
);
171+
});
172+
173+
upload.on("error", (err) => {
174+
toast.error(err.message);
175+
setUploadingFiles((prevUploadingFiles) => {
176+
return prevUploadingFiles.map((uploadFile) => {
177+
if (uploadFile.id === id) return { ...uploadFile, error: true };
178+
return uploadFile;
162179
});
163-
},
164-
complete: async (_xhr, file_key) => {
165-
console.log("complete", decodeURIComponent(file_key));
166-
setUploadingFiles((prevUploadingFiles) =>
167-
prevUploadingFiles.filter((uploadFile) => uploadFile.id !== id)
168-
);
169-
const newFile: DriveFile = {
170-
fullPath: filePath,
171-
name: toUpload.name,
172-
parent: currentFolder.fullPath,
173-
size: toUpload.size.toString(),
174-
createdAt: new Date().toISOString(),
175-
contentType: mime.lookup(toUpload.name) || "application/octet-stream",
176-
bucketName: data.keys.Bucket,
177-
bucketUrl: `https://${data.keys.Bucket}.s3.${data.keys.region}.amazonaws.com`,
178-
url: await getSignedUrl(
179-
s3Client,
180-
new GetObjectCommand({ Bucket: data.keys.Bucket, Key: decodeURIComponent(file_key) }),
181-
{ expiresIn: 3600 * 24 }
182-
),
183-
};
184-
setFiles((files) => (files ? [...files, newFile] : [newFile]));
185-
toast.success("File uploaded successfully.");
186-
},
180+
});
181+
});
182+
183+
upload.on("completed", async () => {
184+
setUploadingFiles((prevUploadingFiles) =>
185+
prevUploadingFiles.filter((uploadFile) => uploadFile.id !== id)
186+
);
187+
const newFile: DriveFile = {
188+
fullPath: Key,
189+
name: file.name,
190+
parent: currentFolder.fullPath,
191+
size: file.size.toString(),
192+
createdAt: new Date().toISOString(),
193+
contentType: mime.lookup(file.name) || "application/octet-stream",
194+
bucketName: data.keys.Bucket,
195+
bucketUrl: `https://${data.keys.Bucket}.s3.${data.keys.region}.amazonaws.com`,
196+
url: await getSignedUrl(
197+
s3Client,
198+
new GetObjectCommand({ Bucket: data.keys.Bucket, Key: Key }),
199+
{ expiresIn: 3600 * 24 }
200+
),
201+
};
202+
203+
setFiles((files) => (files ? [...files, newFile] : [newFile]));
204+
toast.success("File uploaded successfully.");
187205
});
206+
207+
await upload.start();
188208
});
189209
};
190210

@@ -210,7 +230,7 @@ export const S3Provider: React.FC<Props> = ({ data, fullPath, children }) => {
210230
name: fullPath.split("/").pop(),
211231
bucketName: data.keys.Bucket,
212232
parent: fullPath.split("/").shift() + "/",
213-
bucketUrl: `https://${data.keys.Bucket}.s3.${data.keys.region}.amazonaws.com`,
233+
bucketUrl: data.keys.bucketUrl,
214234
});
215235
}, [fullPath, user]);
216236

@@ -240,7 +260,7 @@ export const S3Provider: React.FC<Props> = ({ data, fullPath, children }) => {
240260
size: result.Size.toString(),
241261
contentType: mime.lookup(result.Key) || "",
242262
bucketName: results.Name,
243-
bucketUrl: `https://${results.Name}.s3.${data.keys.region}.amazonaws.com`,
263+
bucketUrl: data.keys.bucketUrl,
244264
url: await getSignedUrl(
245265
s3Client,
246266
new GetObjectCommand({ Bucket: results.Name, Key: result.Key }),
@@ -269,7 +289,7 @@ export const S3Provider: React.FC<Props> = ({ data, fullPath, children }) => {
269289
name: results.CommonPrefixes[i].Prefix.slice(0, -1).split("/").pop(),
270290
bucketName: results.Name,
271291
parent: currentFolder.fullPath,
272-
bucketUrl: `https://${results.Name}.s3.${data.keys.region}.amazonaws.com`,
292+
bucketUrl: data.keys.bucketUrl,
273293
};
274294
setFolders((folders) => [...folders, driveFolder]);
275295
}
@@ -295,7 +315,7 @@ export const S3Provider: React.FC<Props> = ({ data, fullPath, children }) => {
295315
size: result.Size.toString(),
296316
contentType: mime.lookup(result.Key) || "",
297317
bucketName: results.Name,
298-
bucketUrl: `https://${results.Name}.s3.${data.keys.region}.amazonaws.com`,
318+
bucketUrl: data.keys.bucketUrl,
299319
url: await getSignedUrl(
300320
s3Client,
301321
new GetObjectCommand({ Bucket: results.Name, Key: result.Key }),

0 commit comments

Comments
 (0)