Skip to content

Commit

Permalink
cleaner upload state handling
Browse files Browse the repository at this point in the history
  • Loading branch information
radek00 committed Dec 17, 2023
1 parent aded788 commit 3e263a5
Show file tree
Hide file tree
Showing 5 changed files with 59 additions and 70 deletions.
59 changes: 19 additions & 40 deletions SecureSend/ClientApp/src/components/FileUploadForm/FileInput.vue
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,11 @@ import { ref } from "vue";
import { useDropZone } from "@/utils/composables/useDropZone";
import TrashIcon from "@/assets/icons/TrashIcon.vue";
import PlusIcon from "@/assets/icons/PlusIcon.vue";
import { UploadStatus } from "@/models/enums/UploadStatus";
import { inject } from "vue";
import CloseIcon from "@/assets/icons/CloseIcon.vue";
import PauseIcon from "@/assets/icons/PauseIcon.vue";
import PlayIcon from "@/assets/icons/PlayIcon.vue";
import OptionsDropdown from "@/components/OptionsDropdown.vue";
import { UploadState, type UploadStateTuple } from "@/models/UploadStateTuple";
const emit = defineEmits<{
onFielsChange: [files: File[] | null];
Expand All @@ -23,7 +22,7 @@ const emit = defineEmits<{
}>();
defineProps<{
files: Map<File, number | string | boolean>;
files: Map<File, UploadStateTuple>;
isUploadSetup: boolean;
}>();
Expand All @@ -33,8 +32,6 @@ const onDrop = (files: File[] | null) => {
emit("onFielsChange", files);
};
const isLoading = inject<boolean>("isLoading");
const { isOverDropZone } = useDropZone(fileDropZone, { onDrop });
</script>

Expand Down Expand Up @@ -81,7 +78,7 @@ const { isOverDropZone } = useDropZone(fileDropZone, { onDrop });
>
<template #cardMiddle>
<button
v-if="!isLoading && value !== true"
v-if="value[1] === UploadState.NewFile"
@click="emit('onFileRemove', key)"
type="button"
class="hidden md:inline-flex m-0 border hover:enabled:bg-red-700 focus:ring-4 focus:outline-none font-medium rounded-lg text-sm p-2.5 text-center items-center mr-2 border-red-500 hover:enabled:text-white focus:ring-red-800 hover:bg-red-500 disabled:cursor-not-allowed disabled:bg-gray-600 disabled:border-gray-800"
Expand All @@ -90,15 +87,10 @@ const { isOverDropZone } = useDropZone(fileDropZone, { onDrop });
<span class="sr-only">Remove file</span>
</button>
<div
v-if="
isLoading &&
typeof value !== 'boolean' &&
value !== UploadStatus.error &&
value !== UploadStatus.cancelled
"
class="hidden md:flex gap-1 justify-between"
>
<button
v-if="value[1] === UploadState.InProgress"
@click="emit('onCancel', key)"
type="button"
:disabled="!isUploadSetup"
Expand All @@ -108,7 +100,7 @@ const { isOverDropZone } = useDropZone(fileDropZone, { onDrop });
<span class="sr-only">Cancel</span>
</button>
<button
v-if="value !== UploadStatus.paused"
v-if="value[1] === UploadState.InProgress"
@click="emit('onPause', key)"
type="button"
:disabled="!isUploadSetup"
Expand All @@ -118,7 +110,7 @@ const { isOverDropZone } = useDropZone(fileDropZone, { onDrop });
<span class="sr-only">Pause</span>
</button>
<button
v-if="value === UploadStatus.paused"
v-if="value[1] === UploadState.Paused"
@click="emit('onResume', key)"
type="button"
:disabled="!isUploadSetup"
Expand All @@ -131,66 +123,53 @@ const { isOverDropZone } = useDropZone(fileDropZone, { onDrop });

<OptionsDropdown
class="block md:hidden"
v-if="!isLoading && value !== true"
v-if="value[1] === UploadState.NewFile"
>
<li class="px-4 py-2 hover:bg-gray-600 hover:text-white">
<a href="#" @click="emit('onFileRemove', key)">Remove</a>
</li>
</OptionsDropdown>
<OptionsDropdown
class="block md:hidden"
v-if="
isLoading &&
typeof value !== 'boolean' &&
value !== UploadStatus.error &&
value !== UploadStatus.cancelled
"
>
<OptionsDropdown class="block md:hidden">
<li
class="px-4 py-2 hover:bg-gray-600 hover:text-white"
v-if="isUploadSetup"
v-if="isUploadSetup && value[1] === UploadState.InProgress"
>
<a href="#" @click="emit('onCancel', key)">Cancel</a>
</li>
<li
class="px-4 py-2 hover:bg-gray-600 hover:text-white"
v-if="isUploadSetup && value !== UploadStatus.paused"
v-if="isUploadSetup && value[1] === UploadState.InProgress"
>
<a href="#" @click="emit('onPause', key)">Pause</a>
</li>
<li
class="px-4 py-2 hover:bg-gray-600 hover:text-white"
v-if="isUploadSetup && value === UploadStatus.paused"
v-if="isUploadSetup && value[1] === UploadState.Paused"
>
<a href="#" @click="emit('onResume', key)">Resume</a>
</li>
</OptionsDropdown>

<LoadingIndicator
v-if="value === 100"
v-if="value[1] === UploadState.Merging"
class="w-8 h-5 mr-2"
></LoadingIndicator>
<CheckIcon v-if="value === true"></CheckIcon>
<CheckIcon v-if="value[1] === UploadState.Completed"></CheckIcon>
</template>
<template #cardBottom>
<div class="w-full rounded-full bg-gray-700 mt-2">
<div
class="bg-blue-600 text-xs font-medium text-blue-100 text-center p-0.5 leading-none rounded-full"
:style="{
width: `${
value === true ? 100 : typeof value === 'number' ? value : 100
}%`,
value[1] === UploadState.InProgress ||
value[1] === UploadState.NewFile
? value[0]
: `100%`
}`,
}"
>
{{
typeof value === "string"
? value
: typeof value === "number"
? value === 100
? UploadStatus.finishing
: `${value}%`
: UploadStatus.completed
}}
{{value[0] }}
</div>
</div>
</template>
Expand Down
11 changes: 11 additions & 0 deletions SecureSend/ClientApp/src/models/UploadStateTuple.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
export enum UploadState {
NewFile = 0,
InProgress = 1,
Completed = 2,
Failed = 3,
Paused = 4,
Cancelled = 5,
Merging = 6,
}

export type UploadStateTuple = [string, UploadState];
7 changes: 0 additions & 7 deletions SecureSend/ClientApp/src/models/enums/UploadStatus.ts

This file was deleted.

1 change: 0 additions & 1 deletion SecureSend/ClientApp/src/services/SecureSendService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@ export abstract class SecureSendService {
): Promise<void> => {
const formData = new FormData();
formData.append("chunk", new Blob([chunk], { type: fileType }), name);
chunkNumber = +chunkNumber + 1;
const requestOptions: RequestInit = {
method: "POST",
body: formData,
Expand Down
51 changes: 29 additions & 22 deletions SecureSend/ClientApp/src/views/FileUploadView.vue
Original file line number Diff line number Diff line change
Expand Up @@ -119,21 +119,19 @@ import SchemaInput from "@/components/SchemaInput.vue";
import { SecureSendService } from "@/services/SecureSendService";
import AuthenticatedSecretKeyCryptographyService from "@/utils/AuthenticatedSecretKeyCryptographyService";
import splitFile from "@/utils/splitFile";
import { ref, type Ref } from "vue";
import { computed, inject, ref, type Ref } from "vue";
import { useForm } from "vee-validate";
import { computed } from "vue";
import FileInput from "@/components/FileUploadForm/FileInput.vue";
import FormStepper from "@/components/FileUploadForm/FormStepper.vue";
import StyledButton from "@/components/StyledButton.vue";
import { ButtonType } from "@/models/enums/ButtonType";
import ConfirmModalVue from "@/components/ConfirmModal.vue";
import { useConfirmDialog } from "@/utils/composables/useConfirmDialog";
import SimpleInput from "@/components/SimpleInput.vue";
import { inject } from "vue";
import LoadingIndicator from "@/components/LoadingIndicator.vue";
import { useAlert } from "@/utils/composables/useAlert";
import { UploadStatus } from "@/models/enums/UploadStatus";
import CheckboxSchemaInput from "@/components/CheckboxSchemaInput.vue";
import { UploadState, type UploadStateTuple } from "@/models/UploadStateTuple";
interface IMappedFormValues {
expiryDate: string;
Expand All @@ -153,7 +151,7 @@ const step = ref<number>(0);
let uuid = self.crypto.randomUUID();
const files = ref(new Map<File, number | string | boolean>());
const files = ref(new Map<File, UploadStateTuple>());
const fileKeys = new Map<string, boolean>();
const pausedFiles = new Map<File, boolean | ((value?: string) => void)>();
const controllers = new Map<string, AbortController>();
Expand Down Expand Up @@ -208,37 +206,42 @@ const handleUploadCallback = async (
) => {
try {
await handlePause(file);
const currentChunk = num + 1;
await SecureSendService.uploadChunk(
uuid,
num,
currentChunk,
totalChunks,
file.name,
chunk,
file.type,
file.size,
controllers.get(file.name)?.signal
);
files.value.set(
file,
num + 1 === totalChunks
? true
: Math.ceil(((num + 1) / totalChunks) * 100)
);
const progress = Math.ceil((currentChunk / totalChunks) * 100);
const stateTuple: UploadStateTuple =
currentChunk === totalChunks
? ["Upload completed", UploadState.Completed]
: currentChunk === totalChunks - 1
? ["Finishing upload...", UploadState.Merging]
: [`${progress}%`, UploadState.InProgress];
files.value.set(file, stateTuple);
} catch (error: any) {
if (
error === UploadStatus.cancelled ||
error === UploadState.Cancelled ||
error.code === DOMException.ABORT_ERR
) {
files.value.set(file, UploadStatus.cancelled);
files.value.set(file, ["Upload cancelled.", UploadState.Cancelled]);
} else {
files.value.set(file, UploadStatus.error);
files.value.set(file, ["Error with uploading file.", UploadState.Failed]);
}
throw error;
}
};
const handleUploadResult = async () => {
if ([...files.value.values()].find((file) => file === true)) {
if (
[...files.value.values()].find((file) => file[1] === UploadState.Completed)
) {
const { data } = await reveal();
if (data) {
formReset();
Expand All @@ -260,7 +263,7 @@ const handlePause = async (file: File) => {
controllers.set(file.name, controller);
}
if (pausedFiles.get(file)) {
files.value.set(file, UploadStatus.paused);
files.value.set(file, ["Upload paused", UploadState.Paused]);
const promise = new Promise((res) => {
pausedFiles.set(file, res);
});
Expand All @@ -282,11 +285,15 @@ const onSubmit = handleSubmit(async () => {
isLoading!.value = false;
await handleUploadResult();
} catch (error) {
if (![...files.value.values()].find((file) => file === true)) {
if (
![...files.value.values()].find(
(file) => file[1] === UploadState.Completed
)
) {
openDanger("Upload failed, try again.");
formReset();
} else {
handleUploadResult();
await handleUploadResult();
}
}
} else {
Expand All @@ -296,7 +303,7 @@ const onSubmit = handleSubmit(async () => {
const onCancel = async (fileObj: File) => {
const controller = controllers.get(fileObj.name);
if (controller) {
controller.abort(UploadStatus.cancelled);
controller.abort(UploadState.Cancelled);
onResume(fileObj);
await SecureSendService.cancelUpload({ id: uuid, fileName: fileObj.name });
}
Expand All @@ -322,7 +329,7 @@ const onFilesChange = (formFiles: File[] | null) => {
for (let i = 0; i < formFiles.length; i++) {
const file = formFiles[i];
if (!fileKeys.has(file.name)) {
files.value.set(file, 0);
files.value.set(file, [`0%`, UploadState.NewFile]);
fileKeys.set(file.name, true);
}
}
Expand Down Expand Up @@ -350,7 +357,7 @@ const encryptFile = async () => {
(promise) =>
promise.status === "rejected" &&
promise.reason.code !== DOMException.ABORT_ERR &&
promise.reason !== UploadStatus.cancelled
promise.reason !== UploadState.Cancelled
)
) {
throw new Error("Upload error");
Expand Down

0 comments on commit 3e263a5

Please sign in to comment.