Skip to content

Commit

Permalink
#2445 - add Download/Delete buttons and display image size in the ima…
Browse files Browse the repository at this point in the history
…ge viewer (#2453)

* display file-size in the image viewer

* add downloadHelper invisible link

* add cta buttons for the images in the viewer

* style the cta buttons

* conditionally show/hide delete button

* implement download / delete functionalities

* logic after the image deletion is done

* implement loader for deleting-image status

* setup event handling for DELETE_ATTACHMENT_FEEDBACK

* add some comments

* remove debugging console.log

* move image-compression logic to ChatMain.vue
  • Loading branch information
SebinSong authored Dec 18, 2024
1 parent 6c7be9e commit 8aad62e
Show file tree
Hide file tree
Showing 6 changed files with 222 additions and 42 deletions.
21 changes: 5 additions & 16 deletions frontend/controller/actions/identity.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import {
import { cloneDeep, has, omit } from '@model/contracts/shared/giLodash.js'
import { SETTING_CHELONIA_STATE } from '@model/database.js'
import sbp from '@sbp/sbp'
import { imageUpload, objectURLtoBlob, compressImage } from '@utils/image.js'
import { imageUpload, objectURLtoBlob } from '@utils/image.js'
import { SETTING_CURRENT_USER } from '~/frontend/model/database.js'
import { JOINED_CHATROOM, KV_QUEUE, LOGIN, LOGOUT } from '~/frontend/utils/events.js'
import { GIMessage } from '~/shared/domains/chelonia/GIMessage.js'
Expand Down Expand Up @@ -596,28 +596,17 @@ export default (sbp('sbp/selectors/register', {
const { identityContractID } = sbp('state/vuex/state').loggedIn
try {
const attachmentsData = await Promise.all(attachments.map(async (attachment) => {
const { url, needsImageCompression } = attachment
const { url, compressedBlob } = attachment
// url here is an instance of URL.createObjectURL(), which needs to be converted to a 'Blob'
const attachmentBlob = needsImageCompression
? await compressImage(url)
: await objectURLtoBlob(url)

if (needsImageCompression) {
// Update the attachment details to reflect the compressed image.
const fileNameWithoutExtension = attachment.name.split('.').slice(0, -1).join('.')
const extension = attachmentBlob.type.split('/')[1]

attachment.mimeType = attachmentBlob.type
attachment.name = `${fileNameWithoutExtension}.${extension}`
attachment.size = attachmentBlob.size
}
const attachmentBlob = compressedBlob || await objectURLtoBlob(url)

const response = await sbp('chelonia/fileUpload', attachmentBlob, {
type: attachment.mimeType,
cipher: 'aes256gcm'
}, { billableContractID })
const { delete: token, download: downloadData } = response
return {
attributes: omit(attachment, ['url', 'needsImageCompression']),
attributes: omit(attachment, ['url', 'compressedBlob', 'needsImageCompression']),
downloadData,
deleteData: { token }
}
Expand Down
2 changes: 2 additions & 0 deletions frontend/utils/events.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ export const LEFT_GROUP = 'left-group'
export const JOINED_CHATROOM = 'joined-chatroom'
export const LEFT_CHATROOM = 'left-chatroom'
export const DELETED_CHATROOM = 'deleted-chatroom'
export const DELETE_ATTACHMENT = 'delete-attachment'
export const DELETE_ATTACHMENT_FEEDBACK = 'delete-attachment-complete'

export const REPLACED_STATE = 'replaced-state'

Expand Down
39 changes: 35 additions & 4 deletions frontend/views/containers/chatroom/ChatMain.vue
Original file line number Diff line number Diff line change
Expand Up @@ -138,11 +138,12 @@ import Emoticons from './Emoticons.vue'
import TouchLinkHelper from './TouchLinkHelper.vue'
import DragActiveOverlay from './file-attachment/DragActiveOverlay.vue'
import { MESSAGE_TYPES, MESSAGE_VARIANTS, CHATROOM_ACTIONS_PER_PAGE } from '@model/contracts/shared/constants.js'
import { CHATROOM_EVENTS, NEW_CHATROOM_UNREAD_POSITION } from '@utils/events.js'
import { CHATROOM_EVENTS, NEW_CHATROOM_UNREAD_POSITION, DELETE_ATTACHMENT_FEEDBACK } from '@utils/events.js'
import { findMessageIdx } from '@model/contracts/shared/functions.js'
import { proximityDate, MINS_MILLIS } from '@model/contracts/shared/time.js'
import { cloneDeep, debounce, throttle, delay } from '@model/contracts/shared/giLodash.js'
import { EVENT_HANDLED } from '~/shared/domains/chelonia/events.js'
import { compressImage } from '@utils/image.js'
const ignorableScrollDistanceInPixel = 500
Expand Down Expand Up @@ -455,6 +456,7 @@ export default ({
}
const uploadAttachments = async () => {
try {
attachments = await this.checkAndCompressImages(attachments)
data.attachments = await sbp('gi.actions/identity/uploadFiles', {
attachments,
billableContractID: contractID
Expand Down Expand Up @@ -509,6 +511,26 @@ export default ({
})
}
},
checkAndCompressImages (attachments) {
return Promise.all(
attachments.map(async attachment => {
if (attachment.needsImageCompression) {
const compressedImageBlob = await compressImage(attachment.url)
const fileNameWithoutExtension = attachment.name.split('.').slice(0, -1).join('.')
const extension = compressedImageBlob.type.split('/')[1]
return {
...attachment,
mimeType: compressedImageBlob.type,
name: `${fileNameWithoutExtension}.${extension}`,
size: compressedImageBlob.size,
url: URL.createObjectURL(compressedImageBlob),
compressedBlob: compressedImageBlob
}
} else { return attachment }
})
)
},
async scrollToMessage (messageHash, effect = true) {
if (!messageHash || !this.messages.length) {
return
Expand Down Expand Up @@ -685,12 +707,21 @@ export default ({
}
const primaryButtonSelected = await sbp('gi.ui/prompt', promptConfig)
const sendDeleteAttachmentFeedback = (action) => {
// Delete attachment action can lead to 'success', 'error' or can be cancelled by user.
sbp('okTurtles.events/emit', DELETE_ATTACHMENT_FEEDBACK, { action, manifestCid })
}
if (primaryButtonSelected) {
const data = { hash, manifestCid, messageSender: from }
sbp('gi.actions/chatroom/deleteAttachment', { contractID, data }).catch((e) => {
console.error(`Error while deleting attachment(${manifestCid}) of message(${hash}) for chatroom(${contractID})`, e)
})
sbp('gi.actions/chatroom/deleteAttachment', { contractID, data })
.then(() => sendDeleteAttachmentFeedback('complete'))
.catch((e) => {
console.error(`Error while deleting attachment(${manifestCid}) of message(${hash}) for chatroom(${contractID})`, e)
sendDeleteAttachmentFeedback('error')
})
} else {
sendDeleteAttachmentFeedback('cancel')
}
},
changeDay (index) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@
)
button.is-icon-small(
:aria-label='L("Delete")'
@click='deleteAttachment(entryIndex)'
@click='deleteAttachment({ index: entryIndex })'
)
i.icon-trash-alt

Expand Down Expand Up @@ -95,9 +95,8 @@ import Tooltip from '@components/Tooltip.vue'
import { MESSAGE_VARIANTS } from '@model/contracts/shared/constants.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 { OPEN_MODAL, DELETE_ATTACHMENT } from '@utils/events.js'
import { L } from '@common/common.js'
import { randomHexString } from '@model/contracts/shared/giLodash.js'
export default {
name: 'ChatAttachmentPreview',
Expand Down Expand Up @@ -152,6 +151,13 @@ export default {
return this.getStretchedDimension(attachment.dimension)
})
}
sbp('okTurtles.events/on', DELETE_ATTACHMENT, this.deleteAttachment)
}
},
beforeDestroy () {
if (this.shouldPreviewImages) {
sbp('okTurtles.events/off', DELETE_ATTACHMENT, this.deleteAttachment)
}
},
methods: {
Expand All @@ -169,10 +175,16 @@ export default {
fileType ({ mimeType }) {
return getFileType(mimeType)
},
deleteAttachment (index) {
const attachment = this.attachmentList[index]
if (attachment.downloadData) {
this.$emit('delete-attachment', attachment.downloadData.manifestCid)
deleteAttachment ({ index, url }) {
if (url) {
index = this.objectURLList.indexOf(url)
}
if (index >= 0) {
const attachment = this.attachmentList[index]
if (attachment.downloadData) {
this.$emit('delete-attachment', attachment.downloadData.manifestCid)
}
}
},
async getAttachmentObjectURL (attachment) {
Expand Down Expand Up @@ -229,12 +241,15 @@ export default {
const allImageAttachments = this.attachmentList.filter(entry => this.fileType(entry) === 'image')
.map((entry, index) => {
const imgUrl = entry.url || this.objectURLList[index] || ''
return {
name: entry.name,
ownerID: this.ownerID,
imgUrl: entry.url || this.objectURLList[index] || '',
createdAt: this.createdAt || new Date(),
id: randomHexString(12)
size: entry.size,
id: imgUrl,
imgUrl,
manifestCid: entry.downloadData?.manifestCid
}
})
const initialIndex = allImageAttachments.findIndex(attachment => attachment.imgUrl === objectURL)
Expand All @@ -244,7 +259,8 @@ export default {
null,
{
images: allImageAttachments,
initialIndex: initialIndex === -1 ? 0 : initialIndex
initialIndex: initialIndex === -1 ? 0 : initialIndex,
canDelete: this.isMsgSender || this.isGroupCreator // delete-attachment action can only be performed by the sender or the group creator
}
)
}
Expand Down
Loading

0 comments on commit 8aad62e

Please sign in to comment.