Skip to content

Commit

Permalink
#2429 - File attachment in chat should show the size (#2435)
Browse files Browse the repository at this point in the history
* determine if an image needs compression or not

* implement compressImage() function

* write a function that checks if image/webp format is supported

* implement recursive compression logic in compressImage()

* optimise compression factors / some corrections on file details post compression

* update the minimum quality

* fix the typo iamge

* do not resize the image unless its physical size is too large

* attachment data in contracts needs size field

* file-size to a human readable string / display it in multiple UIs

* update according to feedback

* update maximum image dimension

* remove console.log / revert back webP format as default

* add a constant for kilo-byte

* updates for PR review

* file size update after image compression
  • Loading branch information
SebinSong authored Dec 3, 2024
1 parent 3996fa4 commit c9c47d6
Show file tree
Hide file tree
Showing 5 changed files with 64 additions and 18 deletions.
1 change: 1 addition & 0 deletions frontend/controller/actions/identity.js
Original file line number Diff line number Diff line change
Expand Up @@ -600,6 +600,7 @@ export default (sbp('sbp/selectors/register', {

attachment.mimeType = attachmentBlob.type
attachment.name = `${fileNameWithoutExtension}.${extension}`
attachment.size = attachmentBlob.size
}
const response = await sbp('chelonia/fileUpload', attachmentBlob, {
type: attachment.mimeType,
Expand Down
29 changes: 16 additions & 13 deletions frontend/model/contracts/shared/types.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import {
objectOf, objectMaybeOf, arrayOf, unionOf, boolean,
object, string, stringMax, optional, number, mapOf, literalOf
object, string, stringMax, optional, number, mapOf, literalOf, numberRange
} from '~/frontend/model/contracts/misc/flowTyper.js'
import {
CHATROOM_TYPES,
Expand Down Expand Up @@ -64,18 +64,21 @@ export const messageType: any = objectMaybeOf({
type: unionOf(...Object.values(MESSAGE_NOTIFICATIONS).map(v => literalOf(v))),
params: mapOf(string, string) // { username }
}),
attachments: arrayOf(objectOf({
name: string,
mimeType: string,
dimension: optional(objectOf({
width: number,
height: number
})),
downloadData: objectOf({
manifestCid: string,
downloadParams: optional(object)
})
})),
attachments: optional(
arrayOf(objectOf({
name: string,
mimeType: string,
size: numberRange(1, Number.MAX_SAFE_INTEGER),
dimension: optional(objectOf({
width: number,
height: number
})),
downloadData: objectOf({
manifestCid: string,
downloadParams: optional(object)
})
}))
),
replyingMessage: objectOf({
hash: string, // scroll to the original message and highlight
text: string // display text(if too long, truncate)
Expand Down
1 change: 1 addition & 0 deletions frontend/views/containers/chatroom/SendArea.vue
Original file line number Diff line number Diff line change
Expand Up @@ -750,6 +750,7 @@ export default ({
url: fileUrl,
name: file.name,
mimeType: file.type || '',
size: fileSize,
downloadData: null // NOTE: we can tell if the attachment has been uploaded by seeing if this field is non-null.
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@

.c-non-image-file-info
.c-file-name.has-ellipsis {{ entry.name }}
.c-file-ext {{ fileExt(entry) }}
.c-file-ext-and-size
.c-file-ext {{ fileExt(entry) }}
.c-file-size(v-if='entry.size') {{ fileSizeDisplay(entry) }}

.c-preview-img(v-else)
img(
Expand All @@ -36,7 +38,7 @@
.c-attachment-actions
tooltip(
direction='top'
:text='L("Download")'
:text='getDownloadTooltipText(entry)'
)
button.is-icon-small(
:aria-label='L("Download")'
Expand Down Expand Up @@ -91,9 +93,10 @@
import sbp from '@sbp/sbp'
import Tooltip from '@components/Tooltip.vue'
import { MESSAGE_VARIANTS } from '@model/contracts/shared/constants.js'
import { getFileExtension, getFileType } from '@view-utils/filters.js'
import { getFileExtension, getFileType, formatBytesDecimal } from '@view-utils/filters.js'
import { Secret } from '~/shared/domains/chelonia/Secret.js'
import { OPEN_MODAL } from '@utils/events.js'
import { L } from '@common/common.js'
import { randomHexString } from '@model/contracts/shared/giLodash.js'
export default {
Expand Down Expand Up @@ -155,6 +158,14 @@ export default {
fileExt ({ name }) {
return getFileExtension(name, true)
},
fileSizeDisplay ({ size }) {
return size ? formatBytesDecimal(size) : ''
},
getDownloadTooltipText ({ size }) {
return this.shouldPreviewImages
? `${L('Download ({size})', { size: formatBytesDecimal(size) })}`
: L('Download')
},
fileType ({ mimeType }) {
return getFileType(mimeType)
},
Expand Down Expand Up @@ -276,8 +287,22 @@ export default {
&.is-for-download {
padding: 0;
.c-preview-non-image .c-non-image-file-info {
width: calc(100% - 4rem);
.c-preview-non-image {
.c-non-image-file-info {
width: calc(100% - 4rem);
}
.c-file-ext-and-size {
display: flex;
align-items: flex-end;
flex-direction: row;
column-gap: 0.325rem;
}
.c-file-size {
color: $text_1;
font-size: 0.8em;
}
}
.c-attachment-actions-wrapper {
Expand Down Expand Up @@ -310,6 +335,8 @@ export default {
.is-download-item {
&:hover .c-attachment-actions-wrapper {
display: flex;
flex-direction: column;
align-items: flex-end;
}
.c-preview-non-image {
Expand Down
14 changes: 14 additions & 0 deletions frontend/views/utils/filters.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { L } from '@common/common.js'

export const toPercent = (decimal: number): number => Math.floor(decimal * 100)

export const getFileExtension = (
Expand All @@ -15,6 +17,18 @@ export const getFileType = (
return mimeType.match('image/') ? 'image' : 'non-image'
}

export const formatBytesDecimal = (bytes: number, decimals: number = 2): string => {
if (bytes < 0 || !Number.isFinite(bytes)) return L('Invalid size')
else if (bytes === 0) return L('0 Bytes')

const k = 1024 // Decimal base
const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']
const i = Math.floor(Math.log(bytes) / Math.log(k))

const formattedValue = parseFloat((bytes / Math.pow(k, i)).toFixed(decimals))
return `${formattedValue} ${sizes[i]}`
}

/**
* this function filters `list` by `keyword`
* `list` should be an array of objects and strings
Expand Down

0 comments on commit c9c47d6

Please sign in to comment.