/**
* Uploads file via AJAX.
*
- * @author Joshua Ruesweg, Matthias Schmidt
- * @copyright 2001-2019 WoltLab GmbH
- * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module WoltLabSuite/Core/Ui/File/Upload
- * @since 5.2
+ * @author Joshua Ruesweg, Matthias Schmidt
+ * @copyright 2001-2019 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @module WoltLabSuite/Core/Ui/File/Upload
+ * @since 5.2
*/
-define(['Core', 'Language', 'Dom/Util', 'WoltLabSuite/Core/Ui/File/Delete', 'Upload'], function (Core, Language, DomUtil, DeleteHandler, CoreUpload) {
+define(["require", "exports", "tslib", "../../Core", "./Delete", "../../Dom/Util", "../../Language", "../../Upload"], function (require, exports, tslib_1, Core, Delete_1, Util_1, Language, Upload_1) {
"use strict";
- /**
- * @constructor
- */
- function Upload(buttonContainerId, targetId, options) {
- options = options || {};
- if (options.internalId === undefined) {
- throw new Error("Missing internal id.");
- }
- // set default options
- this._options = Core.extend({
- // name if the upload field
- name: '__files[]',
- // is true if every file from a multi-file selection is uploaded in its own request
- singleFileRequests: false,
- // url for uploading file
- url: 'index.php?ajax-file-upload/&t=' + SECURITY_TOKEN,
- // image preview
- imagePreview: false,
- // max files
- maxFiles: null,
- // array of acceptable file types, null if any file type is acceptable
- acceptableFiles: null,
- }, options);
- this._options.multiple = this._options.maxFiles === null || this._options.maxFiles > 1;
- if (this._options.url.indexOf('index.php') === 0) {
- this._options.url = WSC_API_URL + this._options.url;
- }
- this._buttonContainer = elById(buttonContainerId);
- if (this._buttonContainer === null) {
- throw new Error("Element id '" + buttonContainerId + "' is unknown.");
- }
- this._target = elById(targetId);
- if (targetId === null) {
- throw new Error("Element id '" + targetId + "' is unknown.");
- }
- if (options.multiple && this._target.nodeName !== 'UL' && this._target.nodeName !== 'OL') {
- throw new Error("Target element has to be list or table body if uploading multiple files is supported.");
+ Core = tslib_1.__importStar(Core);
+ Delete_1 = tslib_1.__importDefault(Delete_1);
+ Util_1 = tslib_1.__importDefault(Util_1);
+ Language = tslib_1.__importStar(Language);
+ Upload_1 = tslib_1.__importDefault(Upload_1);
+ class FileUpload extends Upload_1.default {
+ constructor(buttonContainerId, targetId, options) {
+ options = options || {};
+ if (options.internalId === undefined) {
+ throw new Error("Missing internal id.");
+ }
+ // set default options
+ options = Core.extend({
+ // image preview
+ imagePreview: false,
+ // max files
+ maxFiles: null,
+ }, options);
+ options.multiple = options.maxFiles === null || options.maxFiles > 1;
+ super(buttonContainerId, targetId, options);
+ this.checkMaxFiles();
+ this._deleteHandler = new Delete_1.default(buttonContainerId, targetId, this._options.imagePreview, this);
}
- this._fileElements = [];
- this._internalFileId = 0;
- // upload ids that belong to an upload of multiple files at once
- this._multiFileUploadIds = [];
- this._createButton();
- this.checkMaxFiles();
- this._deleteHandler = new DeleteHandler(buttonContainerId, targetId, this._options.imagePreview, this);
- }
- Core.inherit(Upload, CoreUpload, {
- _createFileElement: function (file) {
- var element = Upload._super.prototype._createFileElement.call(this, file);
- element.classList.add('box64', 'uploadedFile');
- var progress = elBySel('progress', element);
- var icon = elCreate('span');
- icon.className = 'icon icon64 fa-spinner';
- var fileName = element.textContent;
+ _createFileElement(file) {
+ const element = super._createFileElement(file);
+ element.classList.add("box64", "uploadedFile");
+ const progress = element.querySelector("progress");
+ const icon = document.createElement("span");
+ icon.className = "icon icon64 fa-spinner";
+ const fileName = element.textContent;
element.textContent = "";
element.append(icon);
- var innerDiv = elCreate('div');
- var fileNameP = elCreate('p');
+ const innerDiv = document.createElement("div");
+ const fileNameP = document.createElement("p");
fileNameP.textContent = fileName; // file.name
- var smallProgress = elCreate('small');
+ const smallProgress = document.createElement("small");
smallProgress.appendChild(progress);
innerDiv.appendChild(fileNameP);
innerDiv.appendChild(smallProgress);
- var div = elCreate('div');
+ const div = document.createElement("div");
div.appendChild(innerDiv);
- var ul = elCreate('ul');
- ul.className = 'buttonGroup';
+ const ul = document.createElement("ul");
+ ul.className = "buttonGroup";
div.appendChild(ul);
// reset element textContent and replace with own element style
element.append(div);
return element;
- },
- _failure: function (uploadId, data, responseText, xhr, requestOptions) {
- for (var i = 0, length = this._fileElements[uploadId].length; i < length; i++) {
- this._fileElements[uploadId][i].classList.add('uploadFailed');
- elBySel('small', this._fileElements[uploadId][i]).innerHTML = '';
- var icon = elBySel('.icon', this._fileElements[uploadId][i]);
- icon.classList.remove('fa-spinner');
- icon.classList.add('fa-ban');
- var innerError = elCreate('span');
- innerError.className = 'innerError';
- innerError.textContent = Language.get('wcf.upload.error.uploadFailed');
- DomUtil.insertAfter(innerError, elBySel('small', this._fileElements[uploadId][i]));
+ }
+ _failure(uploadId, data) {
+ this._fileElements[uploadId].forEach((fileElement) => {
+ fileElement.classList.add("uploadFailed");
+ const small = fileElement.querySelector("small");
+ small.innerHTML = "";
+ const icon = fileElement.querySelector(".icon");
+ icon.classList.remove("fa-spinner");
+ icon.classList.add("fa-ban");
+ const innerError = document.createElement("span");
+ innerError.className = "innerError";
+ innerError.textContent = Language.get("wcf.upload.error.uploadFailed");
+ small.insertAdjacentElement("afterend", innerError);
+ });
+ throw new Error(`Upload failed: ${data.message}`);
+ }
+ _upload(event, file, blob) {
+ const parent = this._buttonContainer.parentElement;
+ const innerError = parent.querySelector("small.innerError:not(.innerFileError)");
+ if (innerError) {
+ innerError.remove();
}
- throw new Error("Upload failed: " + data.message);
- },
- _upload: function (event, file, blob) {
- var innerError = elBySel('small.innerError:not(.innerFileError)', this._buttonContainer.parentNode);
- if (innerError)
- elRemove(innerError);
- return Upload._super.prototype._upload.call(this, event, file, blob);
- },
- _success: function (uploadId, data, responseText, xhr, requestOptions) {
- for (var i = 0, length = this._fileElements[uploadId].length; i < length; i++) {
- if (data['files'][i] !== undefined) {
+ return super._upload(event, file, blob);
+ }
+ _success(uploadId, data) {
+ this._fileElements[uploadId].forEach((fileElement, index) => {
+ if (data.files[index] !== undefined) {
+ const fileData = data.files[index];
if (this._options.imagePreview) {
- if (data['files'][i].image === null) {
+ if (fileData.image === null) {
throw new Error("Expect image for uploaded file. None given.");
}
- elRemove(this._fileElements[uploadId][i]);
- if (elBySel('img.previewImage', this._target) !== null) {
- elBySel('img.previewImage', this._target).setAttribute('src', data['files'][i].image);
+ fileElement.remove();
+ const previewImage = this._target.querySelector("img.previewImage");
+ if (previewImage !== null) {
+ previewImage.src = fileData.image;
}
else {
- var image = elCreate('img');
- image.classList.add('previewImage');
- image.setAttribute('src', data['files'][i].image);
- image.setAttribute('style', "max-width: 100%;");
- elData(image, 'unique-file-id', data['files'][i].uniqueFileId);
+ const image = document.createElement("img");
+ image.classList.add("previewImage");
+ image.src = fileData.image;
+ image.style.setProperty("max-width", "100%", "");
+ image.dataset.uniqueFileId = fileData.uniqueFileId;
this._target.appendChild(image);
}
}
else {
- elData(this._fileElements[uploadId][i], 'unique-file-id', data['files'][i].uniqueFileId);
- elBySel('small', this._fileElements[uploadId][i]).textContent = data['files'][i].filesize;
- var icon = elBySel('.icon', this._fileElements[uploadId][i]);
- icon.classList.remove('fa-spinner');
- icon.classList.add('fa-' + data['files'][i].icon);
+ fileElement.dataset.uniqueFileId = fileData.uniqueFileId;
+ fileElement.querySelector("small").textContent = fileData.filesize.toString();
+ const icon = fileElement.querySelector(".icon");
+ icon.classList.remove("fa-spinner");
+ icon.classList.add(`fa-${fileData.icon}`);
}
}
- else if (data['error'][i] !== undefined) {
- this._fileElements[uploadId][i].classList.add('uploadFailed');
- elBySel('small', this._fileElements[uploadId][i]).innerHTML = '';
- var icon = elBySel('.icon', this._fileElements[uploadId][i]);
- icon.classList.remove('fa-spinner');
- icon.classList.add('fa-ban');
- if (elBySel('.innerError', this._fileElements[uploadId][i]) === null) {
- var innerError = elCreate('span');
- innerError.className = 'innerError';
- innerError.textContent = data['error'][i].errorMessage;
- DomUtil.insertAfter(innerError, elBySel('small', this._fileElements[uploadId][i]));
+ else if (data.error[index] !== undefined) {
+ const errorData = data["error"][index];
+ fileElement.classList.add("uploadFailed");
+ const small = fileElement.querySelector("small");
+ small.innerHTML = "";
+ const icon = fileElement.querySelector(".icon");
+ icon.classList.remove("fa-spinner");
+ icon.classList.add("fa-ban");
+ let innerError = fileElement.querySelector(".innerError");
+ if (innerError === null) {
+ innerError = document.createElement("span");
+ innerError.className = "innerError";
+ innerError.textContent = errorData.errorMessage;
+ small.insertAdjacentElement("afterend", innerError);
}
else {
- elBySel('.innerError', this._fileElements[uploadId][i]).textContent = data['error'][i].errorMessage;
+ innerError.textContent = errorData.errorMessage;
}
}
else {
- throw new Error('Unknown uploaded file for uploadId ' + uploadId + '.');
+ throw new Error(`Unknown uploaded file for uploadId ${uploadId}.`);
}
- }
+ });
// create delete buttons
this._deleteHandler.rebuild();
this.checkMaxFiles();
- Core.triggerEvent(this._target, 'change');
- },
- _getFormData: function () {
+ Core.triggerEvent(this._target, "change");
+ }
+ _getFormData() {
return {
- internalId: this._options.internalId
+ internalId: this._options.internalId,
};
- },
- validateUpload: function (files) {
+ }
+ validateUpload(files) {
if (this._options.maxFiles === null || files.length + this.countFiles() <= this._options.maxFiles) {
return true;
}
else {
- var innerError = elBySel('small.innerError:not(.innerFileError)', this._buttonContainer.parentNode);
+ const parent = this._buttonContainer.parentElement;
+ let innerError = parent.querySelector("small.innerError:not(.innerFileError)");
if (innerError === null) {
- innerError = elCreate('small');
- innerError.className = 'innerError';
- DomUtil.insertAfter(innerError, this._buttonContainer);
+ innerError = document.createElement("small");
+ innerError.className = "innerError";
+ this._buttonContainer.insertAdjacentElement("afterend", innerError);
}
- innerError.textContent = Language.get('wcf.upload.error.reachedRemainingLimit', {
- maxFiles: this._options.maxFiles - this.countFiles()
+ innerError.textContent = Language.get("wcf.upload.error.reachedRemainingLimit", {
+ maxFiles: this._options.maxFiles - this.countFiles(),
});
return false;
}
- },
+ }
/**
* Returns the count of the uploaded images.
- *
- * @return {int}
*/
- countFiles: function () {
+ countFiles() {
if (this._options.imagePreview) {
- return elBySel('img', this._target) !== null ? 1 : 0;
+ return this._target.querySelector("img") !== null ? 1 : 0;
}
else {
return this._target.childElementCount;
}
- },
+ }
/**
* Checks the maximum number of files and enables or disables the upload button.
*/
- checkMaxFiles: function () {
+ checkMaxFiles() {
if (this._options.maxFiles !== null && this.countFiles() >= this._options.maxFiles) {
- elHide(this._button);
+ Util_1.default.hide(this._button);
}
else {
- elShow(this._button);
+ Util_1.default.show(this._button);
}
}
- });
- return Upload;
+ }
+ Core.enableLegacyInheritance(FileUpload);
+ return FileUpload;
});
_success(_uploadId, _data, _responseText, _xhr, _requestOptions) {
// This should be an abstract method, but cannot be marked as such for backwards compatibility.
}
+ // This duplication is on purpose, the signature below is implementation private.
_upload(event, file, blob) {
// remove failed upload elements first
this._target.querySelectorAll(".uploadFailed").forEach((el) => el.remove());
--- /dev/null
+define(["require", "exports"], function (require, exports) {
+ "use strict";
+ Object.defineProperty(exports, "__esModule", { value: true });
+});
+++ /dev/null
-/**
- * Uploads file via AJAX.
- *
- * @author Joshua Ruesweg, Matthias Schmidt
- * @copyright 2001-2019 WoltLab GmbH
- * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module WoltLabSuite/Core/Ui/File/Upload
- * @since 5.2
- */
-define(['Core', 'Language', 'Dom/Util', 'WoltLabSuite/Core/Ui/File/Delete', 'Upload'], function(Core, Language, DomUtil, DeleteHandler, CoreUpload) {
- "use strict";
-
- /**
- * @constructor
- */
- function Upload(buttonContainerId, targetId, options) {
- options = options || {};
-
- if (options.internalId === undefined) {
- throw new Error("Missing internal id.");
- }
-
- // set default options
- this._options = Core.extend({
- // name if the upload field
- name: '__files[]',
- // is true if every file from a multi-file selection is uploaded in its own request
- singleFileRequests: false,
- // url for uploading file
- url: 'index.php?ajax-file-upload/&t=' + SECURITY_TOKEN,
- // image preview
- imagePreview: false,
- // max files
- maxFiles: null,
- // array of acceptable file types, null if any file type is acceptable
- acceptableFiles: null,
- }, options);
-
- this._options.multiple = this._options.maxFiles === null || this._options.maxFiles > 1;
-
- if (this._options.url.indexOf('index.php') === 0) {
- this._options.url = WSC_API_URL + this._options.url;
- }
-
- this._buttonContainer = elById(buttonContainerId);
- if (this._buttonContainer === null) {
- throw new Error("Element id '" + buttonContainerId + "' is unknown.");
- }
-
- this._target = elById(targetId);
- if (targetId === null) {
- throw new Error("Element id '" + targetId + "' is unknown.");
- }
-
- if (options.multiple && this._target.nodeName !== 'UL' && this._target.nodeName !== 'OL') {
- throw new Error("Target element has to be list or table body if uploading multiple files is supported.");
- }
-
- this._fileElements = [];
- this._internalFileId = 0;
-
- // upload ids that belong to an upload of multiple files at once
- this._multiFileUploadIds = [];
-
- this._createButton();
- this.checkMaxFiles();
-
- this._deleteHandler = new DeleteHandler(buttonContainerId, targetId, this._options.imagePreview, this);
- }
-
- Core.inherit(Upload, CoreUpload, {
- _createFileElement: function(file) {
- var element = Upload._super.prototype._createFileElement.call(this, file);
- element.classList.add('box64', 'uploadedFile');
-
- var progress = elBySel('progress', element);
-
- var icon = elCreate('span');
- icon.className = 'icon icon64 fa-spinner';
-
- var fileName = element.textContent;
- element.textContent = "";
- element.append(icon);
-
- var innerDiv = elCreate('div');
- var fileNameP = elCreate('p');
- fileNameP.textContent = fileName; // file.name
-
- var smallProgress = elCreate('small');
- smallProgress.appendChild(progress);
-
- innerDiv.appendChild(fileNameP);
- innerDiv.appendChild(smallProgress);
-
- var div = elCreate('div');
- div.appendChild(innerDiv);
-
- var ul = elCreate('ul');
- ul.className = 'buttonGroup';
- div.appendChild(ul);
-
- // reset element textContent and replace with own element style
- element.append(div);
-
- return element;
- },
-
- _failure: function(uploadId, data, responseText, xhr, requestOptions) {
- for (var i = 0, length = this._fileElements[uploadId].length; i < length; i++) {
- this._fileElements[uploadId][i].classList.add('uploadFailed');
-
- elBySel('small', this._fileElements[uploadId][i]).innerHTML = '';
- var icon = elBySel('.icon', this._fileElements[uploadId][i]);
- icon.classList.remove('fa-spinner');
- icon.classList.add('fa-ban');
-
- var innerError = elCreate('span');
- innerError.className = 'innerError';
- innerError.textContent = Language.get('wcf.upload.error.uploadFailed');
- DomUtil.insertAfter(innerError, elBySel('small', this._fileElements[uploadId][i]));
- }
-
- throw new Error("Upload failed: " + data.message);
- },
-
- _upload: function(event, file, blob) {
- var innerError = elBySel('small.innerError:not(.innerFileError)', this._buttonContainer.parentNode);
- if (innerError) elRemove(innerError);
-
- return Upload._super.prototype._upload.call(this, event, file, blob);
- },
-
- _success: function(uploadId, data, responseText, xhr, requestOptions) {
- for (var i = 0, length = this._fileElements[uploadId].length; i < length; i++) {
- if (data['files'][i] !== undefined) {
- if (this._options.imagePreview) {
- if (data['files'][i].image === null) {
- throw new Error("Expect image for uploaded file. None given.");
- }
-
- elRemove(this._fileElements[uploadId][i]);
-
- if (elBySel('img.previewImage', this._target) !== null) {
- elBySel('img.previewImage', this._target).setAttribute('src', data['files'][i].image);
- }
- else {
- var image = elCreate('img');
- image.classList.add('previewImage');
- image.setAttribute('src', data['files'][i].image);
- image.setAttribute('style', "max-width: 100%;");
- elData(image, 'unique-file-id', data['files'][i].uniqueFileId);
- this._target.appendChild(image);
- }
- }
- else {
- elData(this._fileElements[uploadId][i], 'unique-file-id', data['files'][i].uniqueFileId);
- elBySel('small', this._fileElements[uploadId][i]).textContent = data['files'][i].filesize;
- var icon = elBySel('.icon', this._fileElements[uploadId][i]);
- icon.classList.remove('fa-spinner');
- icon.classList.add('fa-' + data['files'][i].icon);
- }
- }
- else if (data['error'][i] !== undefined) {
- this._fileElements[uploadId][i].classList.add('uploadFailed');
-
- elBySel('small', this._fileElements[uploadId][i]).innerHTML = '';
- var icon = elBySel('.icon', this._fileElements[uploadId][i]);
- icon.classList.remove('fa-spinner');
- icon.classList.add('fa-ban');
-
- if (elBySel('.innerError', this._fileElements[uploadId][i]) === null) {
- var innerError = elCreate('span');
- innerError.className = 'innerError';
- innerError.textContent = data['error'][i].errorMessage;
- DomUtil.insertAfter(innerError, elBySel('small', this._fileElements[uploadId][i]));
- }
- else {
- elBySel('.innerError', this._fileElements[uploadId][i]).textContent = data['error'][i].errorMessage;
- }
- }
- else {
- throw new Error('Unknown uploaded file for uploadId ' + uploadId + '.');
- }
- }
-
- // create delete buttons
- this._deleteHandler.rebuild();
- this.checkMaxFiles();
- Core.triggerEvent(this._target, 'change');
- },
-
- _getFormData: function() {
- return {
- internalId: this._options.internalId
- };
- },
-
- validateUpload: function(files) {
- if (this._options.maxFiles === null || files.length + this.countFiles() <= this._options.maxFiles) {
- return true;
- }
- else {
- var innerError = elBySel('small.innerError:not(.innerFileError)', this._buttonContainer.parentNode);
-
- if (innerError === null) {
- innerError = elCreate('small');
- innerError.className = 'innerError';
- DomUtil.insertAfter(innerError, this._buttonContainer);
- }
-
- innerError.textContent = Language.get('wcf.upload.error.reachedRemainingLimit', {
- maxFiles: this._options.maxFiles - this.countFiles()
- });
-
- return false;
- }
- },
-
- /**
- * Returns the count of the uploaded images.
- *
- * @return {int}
- */
- countFiles: function() {
- if (this._options.imagePreview) {
- return elBySel('img', this._target) !== null ? 1 : 0;
- }
- else {
- return this._target.childElementCount;
- }
- },
-
- /**
- * Checks the maximum number of files and enables or disables the upload button.
- */
- checkMaxFiles: function() {
- if (this._options.maxFiles !== null && this.countFiles() >= this._options.maxFiles) {
- elHide(this._button);
- }
- else {
- elShow(this._button);
- }
- }
- });
-
- return Upload;
-});
--- /dev/null
+/**
+ * Uploads file via AJAX.
+ *
+ * @author Joshua Ruesweg, Matthias Schmidt
+ * @copyright 2001-2019 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @module WoltLabSuite/Core/Ui/File/Upload
+ * @since 5.2
+ */
+
+import { ResponseData } from "../../Ajax/Data";
+import * as Core from "../../Core";
+import { FileCollection, FileLikeObject, UploadId, UploadOptions } from "../../Upload/Data";
+import { default as DeleteHandler } from "./Delete";
+import DomUtil from "../../Dom/Util";
+import * as Language from "../../Language";
+import Upload from "../../Upload";
+
+interface FileUploadOptions extends UploadOptions {
+ // image preview
+ imagePreview: boolean;
+ // max files
+ maxFiles: number | null;
+
+ internalId: string;
+}
+
+interface FileData {
+ filesize: number;
+ icon: string;
+ image: string | null;
+ uniqueFileId: string;
+}
+
+interface ErrorData {
+ errorMessage: string;
+}
+
+interface AjaxResponse {
+ error: ErrorData[];
+ files: FileData[];
+}
+
+class FileUpload extends Upload<FileUploadOptions> {
+ protected readonly _deleteHandler: DeleteHandler;
+
+ constructor(buttonContainerId: string, targetId: string, options: Partial<FileUploadOptions>) {
+ options = options || {};
+
+ if (options.internalId === undefined) {
+ throw new Error("Missing internal id.");
+ }
+
+ // set default options
+ options = Core.extend(
+ {
+ // image preview
+ imagePreview: false,
+ // max files
+ maxFiles: null,
+ },
+ options,
+ );
+
+ options.multiple = options.maxFiles === null || (options.maxFiles as number) > 1;
+
+ super(buttonContainerId, targetId, options);
+
+ this.checkMaxFiles();
+
+ this._deleteHandler = new DeleteHandler(buttonContainerId, targetId, this._options.imagePreview, this);
+ }
+
+ protected _createFileElement(file: File | FileLikeObject): HTMLElement {
+ const element = super._createFileElement(file);
+ element.classList.add("box64", "uploadedFile");
+
+ const progress = element.querySelector("progress") as HTMLProgressElement;
+
+ const icon = document.createElement("span");
+ icon.className = "icon icon64 fa-spinner";
+
+ const fileName = element.textContent;
+ element.textContent = "";
+ element.append(icon);
+
+ const innerDiv = document.createElement("div");
+ const fileNameP = document.createElement("p");
+ fileNameP.textContent = fileName; // file.name
+
+ const smallProgress = document.createElement("small");
+ smallProgress.appendChild(progress);
+
+ innerDiv.appendChild(fileNameP);
+ innerDiv.appendChild(smallProgress);
+
+ const div = document.createElement("div");
+ div.appendChild(innerDiv);
+
+ const ul = document.createElement("ul");
+ ul.className = "buttonGroup";
+ div.appendChild(ul);
+
+ // reset element textContent and replace with own element style
+ element.append(div);
+
+ return element;
+ }
+
+ protected _failure(uploadId: number, data: ResponseData): boolean {
+ this._fileElements[uploadId].forEach((fileElement) => {
+ fileElement.classList.add("uploadFailed");
+
+ const small = fileElement.querySelector("small") as HTMLElement;
+ small.innerHTML = "";
+
+ const icon = fileElement.querySelector(".icon") as HTMLElement;
+ icon.classList.remove("fa-spinner");
+ icon.classList.add("fa-ban");
+
+ const innerError = document.createElement("span");
+ innerError.className = "innerError";
+ innerError.textContent = Language.get("wcf.upload.error.uploadFailed");
+ small.insertAdjacentElement("afterend", innerError);
+ });
+
+ throw new Error(`Upload failed: ${data.message as string}`);
+ }
+
+ protected _upload(event: Event): UploadId;
+ protected _upload(event: null, file: File): UploadId;
+ protected _upload(event: null, file: null, blob: Blob): UploadId;
+ protected _upload(event: Event | null, file?: File | null, blob?: Blob | null): UploadId {
+ const parent = this._buttonContainer.parentElement!;
+ const innerError = parent.querySelector("small.innerError:not(.innerFileError)");
+ if (innerError) {
+ innerError.remove();
+ }
+
+ return super._upload(event, file, blob);
+ }
+
+ protected _success(uploadId: number, data: AjaxResponse): void {
+ this._fileElements[uploadId].forEach((fileElement, index) => {
+ if (data.files[index] !== undefined) {
+ const fileData = data.files[index];
+
+ if (this._options.imagePreview) {
+ if (fileData.image === null) {
+ throw new Error("Expect image for uploaded file. None given.");
+ }
+
+ fileElement.remove();
+
+ const previewImage = this._target.querySelector("img.previewImage") as HTMLImageElement;
+ if (previewImage !== null) {
+ previewImage.src = fileData.image;
+ } else {
+ const image = document.createElement("img");
+ image.classList.add("previewImage");
+ image.src = fileData.image;
+ image.style.setProperty("max-width", "100%", "");
+ image.dataset.uniqueFileId = fileData.uniqueFileId;
+ this._target.appendChild(image);
+ }
+ } else {
+ fileElement.dataset.uniqueFileId = fileData.uniqueFileId;
+ fileElement.querySelector("small")!.textContent = fileData.filesize.toString();
+
+ const icon = fileElement.querySelector(".icon") as HTMLElement;
+ icon.classList.remove("fa-spinner");
+ icon.classList.add(`fa-${fileData.icon}`);
+ }
+ } else if (data.error[index] !== undefined) {
+ const errorData = data["error"][index];
+
+ fileElement.classList.add("uploadFailed");
+
+ const small = fileElement.querySelector("small") as HTMLElement;
+ small.innerHTML = "";
+
+ const icon = fileElement.querySelector(".icon") as HTMLElement;
+ icon.classList.remove("fa-spinner");
+ icon.classList.add("fa-ban");
+
+ let innerError = fileElement.querySelector(".innerError") as HTMLElement;
+ if (innerError === null) {
+ innerError = document.createElement("span");
+ innerError.className = "innerError";
+ innerError.textContent = errorData.errorMessage;
+
+ small.insertAdjacentElement("afterend", innerError);
+ } else {
+ innerError.textContent = errorData.errorMessage;
+ }
+ } else {
+ throw new Error(`Unknown uploaded file for uploadId ${uploadId}.`);
+ }
+ });
+
+ // create delete buttons
+ this._deleteHandler.rebuild();
+ this.checkMaxFiles();
+ Core.triggerEvent(this._target, "change");
+ }
+
+ protected _getFormData(): ArbitraryObject {
+ return {
+ internalId: this._options.internalId,
+ };
+ }
+
+ validateUpload(files: FileCollection): boolean {
+ if (this._options.maxFiles === null || files.length + this.countFiles() <= this._options.maxFiles) {
+ return true;
+ } else {
+ const parent = this._buttonContainer.parentElement!;
+
+ let innerError = parent.querySelector("small.innerError:not(.innerFileError)");
+ if (innerError === null) {
+ innerError = document.createElement("small");
+ innerError.className = "innerError";
+ this._buttonContainer.insertAdjacentElement("afterend", innerError);
+ }
+
+ innerError.textContent = Language.get("wcf.upload.error.reachedRemainingLimit", {
+ maxFiles: this._options.maxFiles - this.countFiles(),
+ });
+
+ return false;
+ }
+ }
+
+ /**
+ * Returns the count of the uploaded images.
+ */
+ countFiles(): number {
+ if (this._options.imagePreview) {
+ return this._target.querySelector("img") !== null ? 1 : 0;
+ } else {
+ return this._target.childElementCount;
+ }
+ }
+
+ /**
+ * Checks the maximum number of files and enables or disables the upload button.
+ */
+ checkMaxFiles(): void {
+ if (this._options.maxFiles !== null && this.countFiles() >= this._options.maxFiles) {
+ DomUtil.hide(this._button);
+ } else {
+ DomUtil.show(this._button);
+ }
+ }
+}
+
+Core.enableLegacyInheritance(FileUpload);
+
+export = FileUpload;
this.userId = userId;
}
- protected _getParameters(): object {
+ protected _getParameters(): ArbitraryObject {
return {
userID: this.userId,
};
import * as Core from "./Core";
import DomChangeListener from "./Dom/Change/Listener";
import * as Language from "./Language";
+import { FileCollection, FileElements, FileLikeObject, UploadId, UploadOptions } from "./Upload/Data";
-interface UploadOptions {
- // name of the PHP action
- action: string;
- className: string;
- // is true if multiple files can be uploaded at once
- multiple: boolean;
- // array of acceptable file types, null if any file type is acceptable
- acceptableFiles: string[] | null;
- // name of the upload field
- name: string;
- // is true if every file from a multi-file selection is uploaded in its own request
- singleFileRequests: boolean;
- // url for uploading file
- url: string;
-}
-
-type FileElements = HTMLElement[];
-
-type FileLikeObject = { name: string };
-
-type FileCollection = File[] | FileLikeObject[] | FileList;
-
-type UploadId = number | number[] | null;
-
-abstract class Upload {
+abstract class Upload<TOptions extends UploadOptions = UploadOptions> {
protected readonly _button = document.createElement("p");
protected readonly _buttonContainer: HTMLElement;
protected readonly _fileElements: FileElements[] = [];
protected readonly _fileUpload = document.createElement("input");
protected _internalFileId = 0;
protected readonly _multiFileUploadIds: unknown[] = [];
- protected readonly _options: UploadOptions;
+ protected readonly _options: TOptions;
protected readonly _target: HTMLElement;
- protected constructor(buttonContainerId: string, targetId: string, options: Partial<UploadOptions>) {
+ protected constructor(buttonContainerId: string, targetId: string, options: Partial<TOptions>) {
options = options || {};
if (!options.className) {
throw new Error("Missing class name.");
url: `index.php?ajax-upload/&t=${window.SECURITY_TOKEN}`,
},
options,
- ) as UploadOptions;
+ ) as TOptions;
this._options.url = Core.convertLegacyUrl(this._options.url);
if (this._options.url.indexOf("index.php") === 0) {
/**
* Return additional parameters for upload requests.
*/
- protected _getParameters(): object {
+ protected _getParameters(): ArbitraryObject {
return {};
}
*
* @since 5.2
*/
- protected _getFormData(): object {
+ protected _getFormData(): ArbitraryObject {
return {};
}
protected _upload(event: Event): UploadId;
protected _upload(event: null, file: File): UploadId;
protected _upload(event: null, file: null, blob: Blob): UploadId;
+ protected _upload(event: Event | null, file?: File | null, blob?: Blob | null): UploadId;
+ // This duplication is on purpose, the signature below is implementation private.
protected _upload(event: Event | null, file?: File | null, blob?: Blob | null): UploadId {
// remove failed upload elements first
this._target.querySelectorAll(".uploadFailed").forEach((el) => el.remove());
*
* @since 5.2
*/
- protected validateUpload(_files: FileCollection): boolean {
+ validateUpload(_files: FileCollection): boolean {
// This should be an abstract method, but cannot be marked as such for backwards compatibility.
return true;
--- /dev/null
+export interface UploadOptions {
+ // name of the PHP action
+ action: string;
+ className: string;
+ // is true if multiple files can be uploaded at once
+ multiple: boolean;
+ // array of acceptable file types, null if any file type is acceptable
+ acceptableFiles: string[] | null;
+ // name of the upload field
+ name: string;
+ // is true if every file from a multi-file selection is uploaded in its own request
+ singleFileRequests: boolean;
+ // url for uploading file
+ url: string;
+}
+
+export type FileElements = HTMLElement[];
+
+export type FileLikeObject = { name: string };
+
+export type FileCollection = File[] | FileLikeObject[] | FileList;
+
+export type UploadId = number | number[] | null;