From c8e41679c6122ab38ef736354ca26bd1840e63eb Mon Sep 17 00:00:00 2001 From: alexa Date: Sun, 30 Jun 2024 01:30:24 +0200 Subject: [PATCH] send attachments --- ui/base.css | 1 + ui/classes.css | 50 ++++++++++++++++++--- ui/components/pages/chat.mjs | 84 +++++++++++++++++++++++++++++++----- 3 files changed, 119 insertions(+), 16 deletions(-) diff --git a/ui/base.css b/ui/base.css index 09d5ac5..2f2770b 100644 --- a/ui/base.css +++ b/ui/base.css @@ -11,6 +11,7 @@ --white-2: #e7ddda; --white-3: #ded2ce; --white-4: #d6c9c5; + --neutral: #7b7b7b; --blue: #16bac5ff; --green: #6a994eff; --red: #bc4749ff; diff --git a/ui/classes.css b/ui/classes.css index 6a64b47..2b4c12c 100644 --- a/ui/classes.css +++ b/ui/classes.css @@ -249,11 +249,15 @@ } .message-timestamp { - display: none; + color: var(--neutral); + font-size: 0.6em !important; + padding-left: calc(var(--message-padding-left) + 2em); + height: 0; + overflow: hidden; } .chat-message:hover .message-timestamp { - display: block; + height: 1em; } .message-note { @@ -292,7 +296,7 @@ .reaction-trigger { position: absolute; - left: 1.25em; + left: calc(var(--message-padding-left) - 1.5em); bottom: var(--gap-h); z-index: 999; max-width: max-content; @@ -511,7 +515,7 @@ height: var(--size); position: absolute; top: 0; - left: .5em; + left: calc(var(--message-padding-left) - 2.5em); } .small-avatar { @@ -670,4 +674,40 @@ top: 0; right: 0; transform: translateX(50%) translateY(-50%); -} \ No newline at end of file +} + +.attachments { + padding-left: calc(var(--message-padding-left) - 0.25em); +} + +.attachment-image { + max-height: 200px; + max-width: 200px; + height: auto; + width: auto; + border-radius: var(--input-border-radius); + cursor: zoom-in; +} + +.attachment-image:hover { + transform: scale(1.02); +} + +.full-image { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + object-fit: contain; + cursor: zoom-out; + z-index: 999; + background: var(--bg); +} + +.attachment-video, .attachment-audio { + max-height: 200px; + max-width: 200px; + height: auto; + width: auto; +} diff --git a/ui/components/pages/chat.mjs b/ui/components/pages/chat.mjs index 2fcfe25..4334a6e 100644 --- a/ui/components/pages/chat.mjs +++ b/ui/components/pages/chat.mjs @@ -174,6 +174,10 @@ export class ChatComponent { }), CommonTemplates.profileCard(message.sender, cardShown), ).build()), + create("span") + .classes("message-timestamp", "text-small") + .text(Time.ago(localTimestamp)) + .build(), create("div") .classes("message-content", "flex", "space-between", "full-width") .oncontextmenu((e) => { @@ -188,6 +192,11 @@ export class ChatComponent { .children( ifjs(menuShown, ChatComponent.messageMenu(message, messages, messageMenuPositionX, messageMenuPositionY)), ChatComponent.reactionTrigger(message, messages), + ifjs(message.attachments.length > 0, create("div") + .classes("flex", "align-center", "attachments", "full-width") + .children( + message.attachments.map(attachment => ChatComponent.attachment(attachment)), + ).build()), create("div") .classes("flex-v", "message-text") .children( @@ -203,12 +212,8 @@ export class ChatComponent { .classes("message-note") .text("edited " + Time.ago(new Date(message.updatedAt).getTime() + offset)) .build()), - create("span") - .classes("message-timestamp", "text-small") - .text(Time.ago(localTimestamp)) - .build(), ).build(), - ).build() + ).build(), ).build(); } @@ -387,10 +392,14 @@ export class ChatComponent { for (const file of input.files) { const reader = new FileReader(); reader.onload = () => { - const base64 = reader.result.split(',')[1]; + let base64 = reader.result.split(',')[1]; + if (base64.constructor.name === "Buffer") { + base64 = base64.toString("base64"); + } toBeSentAttachments.value = [...toBeSentAttachments.value, { + filename: file.name, type: file.type, - data: file.type.startsWith("image") ? `data:${file.type};base64,${base64}` : null, + data: base64, }]; }; reader.readAsDataURL(file); @@ -404,13 +413,66 @@ export class ChatComponent { return create("div") .classes("flex-v", "align-center", "relative") .children( - create("img") - .classes("attachment-preview-image") - .src(attachment.data) - .build(), + ChatComponent.attachmentPreviewContent(attachment), CommonTemplates.smallIconButton("delete", "Delete", () => { toBeSentAttachments.value = toBeSentAttachments.value.filter(a => a.filename !== attachment.filename); }, ["attachment-remove-button"]), ).build(); } + + static attachmentPreviewContent(attachment) { + let tag; + switch (attachment.type.split("/")[0]) { + case "image": + tag = "img"; + break; + case "video": + tag = "video"; + break; + case "audio": + tag = "audio"; + break; + default: + tag = "div"; + break; + } + return create(tag) + .classes(`attachment-preview-${attachment.type.split("/")[0]}`) + .src(`data:${attachment.type};base64,${attachment.data}`) + .build(); + } + + static attachment(attachment) { + const apiUrl = sessionStorage.getItem("apiUrl"); + const url = apiUrl + `/attachments/${encodeURIComponent(attachment.messageId)}/${encodeURIComponent(attachment.filename)}`; + let tag; + switch (attachment.type.split("/")[0]) { + case "image": + tag = "img"; + break; + case "video": + tag = "video"; + break; + case "audio": + tag = "audio"; + break; + default: + tag = "div"; + break; + } + const isFullImage = signal(false); + const imageClass = computedSignal(isFullImage, isFull => isFull ? "full-image" : `attachment-${attachment.type.split("/")[0]}`); + + return create("div") + .classes("attachment", attachment.type.split("/")[0]) + .onclick(() => { + isFullImage.value = !isFullImage.value; + }) + .children( + create("img") + .classes(imageClass) + .src(url) + .build() + ).build(); + } } \ No newline at end of file