Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions src/components/Item.ts
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,10 @@ export default class Item extends Element {

private bindEvents(): void {
this.on('click', (event: MouseEvent): void => {
if (this.#entry.placeholder) {
return;
}

if (event.ctrlKey || event.button === 1) {
window.open(this.#entry.fullPath);

Expand Down
14 changes: 6 additions & 8 deletions src/lib/DAV.ts
Original file line number Diff line number Diff line change
Expand Up @@ -300,17 +300,15 @@ export class DAV {
* @param path The path to upload the file to
* @param file The File object to upload
*/
async upload(path: string, file: File): Promise<Response> {
async upload(
path: string,
file: File,
onProgress: (uploadedBytes: number) => void = () => {}
): Promise<Response> {
const targetFile = joinPath(path, file.name);

return this.#toastOnFailure(
(): Promise<Response> =>
this.#http.PUT(targetFile, {
headers: {
'Content-Type': file.type,
},
body: file,
})
(): Promise<Response> => this.#http.PUT(targetFile, file, onProgress)
);
}
}
Expand Down
51 changes: 39 additions & 12 deletions src/lib/Entry.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import joinPath, { pathAndName, trailingSlash } from './joinPath';
import Collection from './Collection';
import EventEmitter from '@dom111/typed-event-emitter/EventEmitter';
import { t } from 'i18next';

type EntryArgs = {
copy?: boolean;
Expand All @@ -14,13 +15,31 @@ type EntryArgs = {
del?: boolean;
rename?: boolean;
placeholder?: boolean;
uploadedSize?: number;
collection?: Collection | null;
};

type EntryEvents = {
updated: [];
};

const sizeToDisplaySize = (size: number): string => {
return ['bytes', 'KiB', 'MiB', 'GiB', 'TiB', 'PiB'].reduce(
(size: string | number, label) => {
if (typeof size === 'string') {
return size;
}

if (size < 1024) {
return `${size.toFixed(2 * (label === 'bytes' ? 0 : 1))} ${label}`;
}

return size / 1024;
},
size
) as string;
};

export default class Entry extends EventEmitter<EntryEvents> {
#copy: boolean;
#del: boolean;
Expand All @@ -34,6 +53,7 @@ export default class Entry extends EventEmitter<EntryEvents> {
#name: string;
#path: string;
#placeholder: boolean;
#uploadedSize: number;
#rename: boolean;
#size: number;
#title: string;
Expand All @@ -51,6 +71,7 @@ export default class Entry extends EventEmitter<EntryEvents> {
modified,
move = true,
placeholder = false,
uploadedSize = 0,
rename = true,
size = 0,
title = '',
Expand All @@ -75,6 +96,7 @@ export default class Entry extends EventEmitter<EntryEvents> {
this.#del = del;
this.#rename = rename;
this.#placeholder = placeholder;
this.#uploadedSize = uploadedSize;
this.collection = collection;
}

Expand Down Expand Up @@ -131,20 +153,17 @@ export default class Entry extends EventEmitter<EntryEvents> {
}

if (!this.#displaySize) {
this.#displaySize = ['bytes', 'KiB', 'MiB', 'GiB', 'TiB', 'PiB'].reduce(
(size: string | number, label) => {
if (typeof size === 'string') {
return size;
}

if (size < 1024) {
return `${size.toFixed(2 * (label === 'bytes' ? 0 : 1))} ${label}`;
}
this.#displaySize = sizeToDisplaySize(this.#size);
}

return size / 1024;
if (this.placeholder) {
return t('uploadProgress', {
interpolation: {
escapeValue: false,
},
this.#size
) as string;
uploaded: sizeToDisplaySize(this.#uploadedSize),
total: this.#displaySize,
});
}

return this.#displaySize;
Expand Down Expand Up @@ -211,6 +230,14 @@ export default class Entry extends EventEmitter<EntryEvents> {
this.#placeholder = value;
}

get uploadedSize(): number {
return this.#uploadedSize;
}

set uploadedSize(value: number) {
this.#uploadedSize = value;
}

get rename(): boolean {
return this.#rename;
}
Expand Down
66 changes: 64 additions & 2 deletions src/lib/HTTP.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,54 @@ const method = async (
return response;
};

const methodXHR = async (
method: string,
url: RequestInfo,
body: Document | XMLHttpRequestBodyInit | null = null,
parameters: RequestInit = {},
onProgress: (loaded: number) => void = () => {}
): Promise<Response> => {
return new Promise<Response>(async (resolve, reject) => {
const request = new Request(url, {
...(defaultParams[method] || {}),
...parameters,
method,
});

const xhr = new XMLHttpRequest();

xhr.open(request.method, request.url, true);
request.headers.forEach((value, key) => xhr.setRequestHeader(key, value));
xhr.upload.addEventListener('progress', (e) => onProgress(e.loaded), false);
xhr.addEventListener('loadend', () => {
// NOTE: first argument to `new Response()` must be null, if second argument contains `status: 204`.
// But in XMLHttpRequest, `response` is always text, and in case of "204 No Content" response, it's empty string.
// Let's fix it manually here.
let xhr_response = xhr.response;
if (xhr.status === 204) {
xhr_response = null;
}
const response = new Response(xhr_response, {
headers: xhr
.getAllResponseHeaders()
.trim()
.split('\r\n')
.map((line) => line.split(': ', 2) as [string, string]),
status: xhr.status,
statusText: xhr.statusText,
});

if (!response.ok) {
reject(new RequestFailure(request, response));
}

resolve(response);
});

xhr.send(body);
});
};

export class HTTP {
GET(url: string, parameters: RequestInit = {}): Promise<Response> {
return method('GET', url, parameters);
Expand All @@ -60,8 +108,22 @@ export class HTTP {
return method('HEAD', url, parameters);
}

PUT(url: string, parameters: RequestInit = {}): Promise<Response> {
return method('PUT', url, parameters);
PUT(
url: string,
file: File,
onProgress: (uploadedBytes: number) => void = () => {},
parameters: RequestInit = {}
): Promise<Response> {
return methodXHR(
'PUT',
url,
file,
{
...parameters,
headers: [['Content-Type', file.type]],
},
onProgress
);
}

PROPFIND(url: string, parameters: RequestInit = {}): Promise<Response> {
Expand Down
9 changes: 8 additions & 1 deletion src/lib/handleFileUpload.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,14 @@ export const handleFileUpload = async (

collection.add(placeholder);

const result = await dav.upload(location.pathname, file);
const result = await dav.upload(
location.pathname,
file,
(uploaded: number) => {
placeholder.uploadedSize = uploaded;
placeholder.emit('updated');
}
);

if (!result.ok) {
collection.remove(placeholder);
Expand Down
41 changes: 21 additions & 20 deletions src/webdav-min.js

Large diffs are not rendered by default.

8 changes: 4 additions & 4 deletions src/webdav.js.map

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions translations/de.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
"alphabet": "Aa Bb Cc Dd Ee Ff Gg Hh Ii Jj Kk Ll Mm Nn Oo Pp Qq Rr Ss Tt Uu Vv Ww Xx Yy Zz Ää Öö Üü ẞß",
"dropFilesAnywhereToUpload": "Datei/en hineinverschieben um sie hochzuladen",
"uploadFiles": "Datei/en hochladen",
"uploadProgress": "{{uploaded}} von {{total}} hochgeladen",
Comment thread
dom111 marked this conversation as resolved.
"or": "oder",
"createNewDirectory": "Neuen Ordner erstellen",
"delete": "Löschen",
Expand Down
3 changes: 2 additions & 1 deletion translations/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
"successfullyRenamed": "'{{from}}' successfully renamed to '{{to}}'.",
"successfullyUploaded": "'{{file}}' has been successfully uploaded.",
"title": "{{path}} | WebDAV",
"uploadFiles": "upload files"
"uploadFiles": "upload files",
"uploadProgress": "{{uploaded}} of {{total}} uploaded..."
}
}
1 change: 1 addition & 0 deletions translations/pt.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
"alphabet": "Aa Bb Cc Dd Ee Ff Gg Hh Ii Jj Kk Ll Mm Nn Oo Pp Qq Rr Ss Tt Uu Vv Ww Xx Yy Zz Áá Ââ Ãã Àà Çç Éé Êê Íí Óó Ôô Õõ Úú",
"dropFilesAnywhereToUpload": "Mova o(s) arquivo(s) interno(s) para carregá-los",
"uploadFiles": "Carregar arquivo(s)",
"uploadProgress": "{{uploaded}} de {{total}} enviados",
"or": "ou",
"createNewDirectory": "Criar uma nova pasta",
"delete": "Eliminar",
Expand Down