}
}
-class ValidationError {
+export class ValidationError {
constructor(
public readonly code: string,
public readonly message: string,
import "../File/woltlab-core-file";
import { CkeditorDropEvent } from "../File/Upload";
+import { formatFilesize } from "WoltLabSuite/Core/FileUtil";
type FileProcessorData = {
attachmentID: number;
const filename = document.createElement("div");
filename.classList.add("attachment__item__filename");
- filename.textContent = file.filename!;
+ filename.textContent = file.filename || file.dataset.filename!;
- element.append(fileWrapper, filename);
+ const fileSize = document.createElement("div");
+ fileSize.classList.add("attachment__item__fileSize");
+ fileSize.textContent = formatFilesize(file.fileSize || parseInt(file.dataset.fileSize!));
+
+ element.append(fileWrapper, filename, fileSize);
fileList.append(element);
- void file.ready.then(() => {
- const data = file.data;
- if (data === undefined) {
- // TODO: error handling
- return;
- }
+ void file.ready
+ .then(() => {
+ const data = file.data;
+ if (data === undefined) {
+ // TODO: error handling
+ return;
+ }
- const fileId = file.fileId;
- if (fileId === undefined) {
- // TODO: error handling
- return;
- }
+ const fileId = file.fileId;
+ if (fileId === undefined) {
+ // TODO: error handling
+ return;
+ }
- const buttonList = document.createElement("div");
- buttonList.classList.add("attachment__item__buttons");
- buttonList.append(
- getDeleteAttachButton(fileId, (data as FileProcessorData).attachmentID, editorId, element),
- getInsertAttachBbcodeButton(
- (data as FileProcessorData).attachmentID,
- file.isImage() && file.link ? file.link : "",
- editorId,
- ),
- );
-
- if (file.isImage()) {
- const thumbnail = file.thumbnails.find((thumbnail) => thumbnail.identifier === "tiny");
- if (thumbnail !== undefined) {
- file.thumbnail = thumbnail;
+ const buttonList = document.createElement("div");
+ buttonList.classList.add("attachment__item__buttons");
+ buttonList.append(
+ getDeleteAttachButton(fileId, (data as FileProcessorData).attachmentID, editorId, element),
+ getInsertAttachBbcodeButton(
+ (data as FileProcessorData).attachmentID,
+ file.isImage() && file.link ? file.link : "",
+ editorId,
+ ),
+ );
+
+ if (file.isImage()) {
+ const thumbnail = file.thumbnails.find((thumbnail) => thumbnail.identifier === "tiny");
+ if (thumbnail !== undefined) {
+ file.thumbnail = thumbnail;
+ }
+
+ const url = file.thumbnails.find((thumbnail) => thumbnail.identifier === "")?.link;
+ if (url !== undefined) {
+ buttonList.append(getInsertThumbnailButton((data as FileProcessorData).attachmentID, url, editorId));
+ }
}
- const url = file.thumbnails.find((thumbnail) => thumbnail.identifier === "")?.link;
- if (url !== undefined) {
- buttonList.append(getInsertThumbnailButton((data as FileProcessorData).attachmentID, url, editorId));
+ element.append(buttonList);
+ })
+ .catch(() => {
+ if (file.validationError === undefined) {
+ return;
}
- }
- element.append(buttonList);
- });
+ // TODO: Add a proper error message, this is for development purposes only.
+ element.append(JSON.stringify(file.validationError));
+ element.classList.add("attachment__item--error");
+ });
}
function getDeleteAttachButton(
const fileElement = document.createElement("woltlab-core-file");
fileElement.dataset.filename = file.name;
+ fileElement.dataset.fileSize = file.size.toString();
const event = new CustomEvent<WoltlabCoreFileElement>("uploadStart", { detail: fileElement });
element.dispatchEvent(event);
if (!response.ok) {
const validationError = response.error.getValidationError();
if (validationError === undefined) {
- fileElement.uploadFailed();
+ fileElement.uploadFailed(undefined);
throw response.error;
}
- console.log(validationError);
+ fileElement.uploadFailed(validationError);
return undefined;
}
const response = await uploadChunk(identifier, i, checksum, chunk);
if (!response.ok) {
- fileElement.uploadFailed();
+ fileElement.uploadFailed(undefined);
throw response.error;
}
+import type { ValidationError } from "WoltLabSuite/Core/Api/Error";
import { getExtensionByMimeType, getIconNameByFilename } from "WoltLabSuite/Core/FileUtil";
const enum State {
#data: Record<string, unknown> | undefined = undefined;
#filename: string = "";
#fileId: number | undefined = undefined;
+ #fileSize: number | undefined = undefined;
#link: string | undefined = undefined;
#mimeType: string | undefined = undefined;
#state: State = State.Initial;
+ #validationError: ValidationError | undefined = undefined;
readonly #thumbnails: Thumbnail[] = [];
#readyReject!: () => void;
// Files that exist at page load have a valid file id, otherwise a new
// file element can only be the result of an upload attempt.
if (this.#fileId === undefined) {
- this.#filename = this.dataset.filename || "bogus.bin";
+ this.#filename = this.dataset.filename || "unknown.bin";
delete this.dataset.filename;
+ this.#fileSize = parseInt(this.dataset.fileSize || "0");
+ delete this.dataset.fileSize;
+
this.#mimeType = this.dataset.mimeType || "application/octet-stream";
delete this.dataset.mimeType;
break;
case State.Failed:
- this.#replaceWithIcon("times");
+ this.#replaceWithIcon("triangle-exclamation");
break;
default:
return this.#filename;
}
+ get fileSize(): number | undefined {
+ return this.#fileSize;
+ }
+
get mimeType(): string | undefined {
return this.#mimeType;
}
}
}
- uploadFailed(): void {
+ uploadFailed(validationError: ValidationError | undefined): void {
if (this.#state !== State.Uploading) {
return;
}
this.#state = State.Failed;
+ this.#validationError = validationError;
this.#rebuildElement();
this.#readyReject();
get ready(): Promise<void> {
return this.#readyPromise;
}
+
+ get validationError(): ValidationError | undefined {
+ return this.#validationError;
+ }
}
export default WoltlabCoreFileElement;
define(["require", "exports"], function (require, exports) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
- exports.ApiError = void 0;
+ exports.ValidationError = exports.ApiError = void 0;
class ApiError {
type;
code;
this.param = param;
}
}
+ exports.ValidationError = ValidationError;
});
-define(["require", "exports", "WoltLabSuite/Core/Api/Files/DeleteFile", "../Ckeditor/Event", "../File/woltlab-core-file"], function (require, exports, DeleteFile_1, Event_1) {
+define(["require", "exports", "WoltLabSuite/Core/Api/Files/DeleteFile", "../Ckeditor/Event", "WoltLabSuite/Core/FileUtil", "../File/woltlab-core-file"], function (require, exports, DeleteFile_1, Event_1, FileUtil_1) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.setup = void 0;
fileWrapper.append(file);
const filename = document.createElement("div");
filename.classList.add("attachment__item__filename");
- filename.textContent = file.filename;
- element.append(fileWrapper, filename);
+ filename.textContent = file.filename || file.dataset.filename;
+ const fileSize = document.createElement("div");
+ fileSize.classList.add("attachment__item__fileSize");
+ fileSize.textContent = (0, FileUtil_1.formatFilesize)(file.fileSize || parseInt(file.dataset.fileSize));
+ element.append(fileWrapper, filename, fileSize);
fileList.append(element);
- void file.ready.then(() => {
+ void file.ready
+ .then(() => {
const data = file.data;
if (data === undefined) {
// TODO: error handling
}
}
element.append(buttonList);
+ })
+ .catch(() => {
+ if (file.validationError === undefined) {
+ return;
+ }
+ // TODO: Add a proper error message, this is for development purposes only.
+ element.append(JSON.stringify(file.validationError));
+ element.classList.add("attachment__item--error");
});
}
function getDeleteAttachButton(fileId, attachmentId, editorId, element) {
const fileHash = await getSha256Hash(await file.arrayBuffer());
const fileElement = document.createElement("woltlab-core-file");
fileElement.dataset.filename = file.name;
+ fileElement.dataset.fileSize = file.size.toString();
const event = new CustomEvent("uploadStart", { detail: fileElement });
element.dispatchEvent(event);
const response = await (0, Upload_1.upload)(file.name, file.size, fileHash, typeName, element.dataset.context || "");
if (!response.ok) {
const validationError = response.error.getValidationError();
if (validationError === undefined) {
- fileElement.uploadFailed();
+ fileElement.uploadFailed(undefined);
throw response.error;
}
- console.log(validationError);
+ fileElement.uploadFailed(validationError);
return undefined;
}
const { identifier, numberOfChunks } = response.value;
const checksum = await getSha256Hash(await chunk.arrayBuffer());
const response = await (0, Chunk_1.uploadChunk)(identifier, i, checksum, chunk);
if (!response.ok) {
- fileElement.uploadFailed();
+ fileElement.uploadFailed(undefined);
throw response.error;
}
await chunkUploadCompleted(fileElement, response.value);
#data = undefined;
#filename = "";
#fileId = undefined;
+ #fileSize = undefined;
#link = undefined;
#mimeType = undefined;
#state = 0 /* State.Initial */;
+ #validationError = undefined;
#thumbnails = [];
#readyReject;
#readyResolve;
// Files that exist at page load have a valid file id, otherwise a new
// file element can only be the result of an upload attempt.
if (this.#fileId === undefined) {
- this.#filename = this.dataset.filename || "bogus.bin";
+ this.#filename = this.dataset.filename || "unknown.bin";
delete this.dataset.filename;
+ this.#fileSize = parseInt(this.dataset.fileSize || "0");
+ delete this.dataset.fileSize;
this.#mimeType = this.dataset.mimeType || "application/octet-stream";
delete this.dataset.mimeType;
const fileId = parseInt(this.getAttribute("file-id") || "0");
}
break;
case 4 /* State.Failed */:
- this.#replaceWithIcon("times");
+ this.#replaceWithIcon("triangle-exclamation");
break;
default:
throw new Error("Unreachable", {
get filename() {
return this.#filename;
}
+ get fileSize() {
+ return this.#fileSize;
+ }
get mimeType() {
return this.#mimeType;
}
return false;
}
}
- uploadFailed() {
+ uploadFailed(validationError) {
if (this.#state !== 1 /* State.Uploading */) {
return;
}
this.#state = 4 /* State.Failed */;
+ this.#validationError = validationError;
this.#rebuildElement();
this.#readyReject();
}
get ready() {
return this.#readyPromise;
}
+ get validationError() {
+ return this.#validationError;
+ }
}
exports.WoltlabCoreFileElement = WoltlabCoreFileElement;
exports.default = WoltlabCoreFileElement;
<woltlab-core-file
file-id="%d"
data-filename="%s"
+ data-file-size="%s"
data-mime-type="%s"
data-thumbnails="%s"
data-meta-data="%s"
EOT,
$this->fileID,
StringUtil::encodeHTML($this->filename),
+ $this->fileSize,
StringUtil::encodeHTML($this->mimeType),
StringUtil::encodeHTML(\json_encode($thumbnails)),
StringUtil::encodeHTML(\json_encode($metaData)),
.attachment__list {
display: grid;
gap: 10px;
- grid-auto-flow: column;
+ grid-auto-flow: row;
// TODO: use container queries to make this more dynamic?
grid-template-columns: repeat(3, 1fr);
}
border-radius: var(--wcfBorderRadius);
box-shadow: var(--wcfBoxShadowCard);
display: grid;
- gap: 10px;
grid-template-areas:
"file filename"
+ "file fileSize"
"file buttons";
- grid-template-columns: 64px auto;
+ grid-template-columns: 80px auto;
padding: 10px;
}
+.attachment__item--error {
+ border-color: var(--wcfStatusErrorBorder);
+}
+
+.attachment__item--error .attachment__item__file {
+ color: var(--wcfStatusErrorText);
+}
+
.attachment__item__file {
+ display: flex;
grid-area: file;
+ justify-content: center;
+ margin-right: 10px;
}
.attachment__item__filename {
white-space: nowrap;
}
+.attachment__item__fileSize {
+ color: var(--wcfContentDimmedText);
+ font-size: 12px;
+ grid-area: fileSize;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+}
+
.attachment__item__buttons {
+ align-items: end;
column-gap: 5px;
display: flex;
grid-area: buttons;