Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

IPFS: Attach files and images in the chat #628

Open
wants to merge 124 commits into
base: dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
124 commits
Select commit Hold shift + click to select a range
0b4628f
feat: preview image
juliahermak Mar 28, 2024
a255a60
fix: delete img and scroll element
juliahermak Mar 30, 2024
67bbc12
feat: image modal
juliahermak Apr 6, 2024
151c74f
fix: images in chat grid css
juliahermak Apr 9, 2024
d2c6031
fix: icon file in chat preview
juliahermak Apr 10, 2024
cb40c0d
fix: classes names
juliahermak Apr 10, 2024
cc1fcc3
fix: styles, max files, new component upload file
juliahermak Apr 17, 2024
3e0b5a0
fix: upload a file and photo with a limited selection
juliahermak Apr 17, 2024
be7f839
feat(AChatAttachment): basic logic for interaction with Helia nodes. …
RealGoodProgrammer Apr 19, 2024
fb81a5c
feat(normalizeMessage): reply with image. Work in progress
RealGoodProgrammer Apr 19, 2024
042b201
feat(QuotedMessage): rewritten to TS. Ready to interact with attachme…
RealGoodProgrammer Apr 20, 2024
172141d
fix(services): build fix. Work in progress
RealGoodProgrammer Apr 20, 2024
660352a
chore(services): cleanup
RealGoodProgrammer Apr 20, 2024
b4bc311
Merge pull request #617 from Adamant-im/feat/user-interface-upload-file
juliahermak Apr 20, 2024
5ec4a53
fix(chat): fix file transaction normalization
bludnic Apr 21, 2024
5fa189a
refactor(QuotedMessage): simplify code
bludnic Apr 21, 2024
15e00f3
refactor(AChatAttachment): use AChatMessage as a template
bludnic Apr 21, 2024
6492414
chore(nodes): preparation for IPFS infoService health check
RealGoodProgrammer Apr 23, 2024
d801998
fix: number of files in chat preview and quoted message
juliahermak Apr 25, 2024
1eb3e78
feat(IpfsNodesTable): table on a new tab (work in progress)
RealGoodProgrammer Apr 28, 2024
03f6816
Merge remote-tracking branch 'refs/remotes/origin/feat/user-interface…
RealGoodProgrammer Apr 28, 2024
e220b4f
Merge remote-tracking branch 'refs/remotes/origin/dev' into feat/info…
RealGoodProgrammer May 2, 2024
321298e
feat(AChatAttachment): download and upload files (work in progress)
RealGoodProgrammer May 2, 2024
e4ce4b6
Merge branch 'refs/heads/dev' into Feat/user-interface-file-in-chat
bludnic May 2, 2024
6c7f7d2
Merge branch 'refs/heads/feat/user-interface-file-in-chat' into feat/…
RealGoodProgrammer May 3, 2024
b29ebd2
Merge pull request #625 from Adamant-im/feat/download_and_upload_files
bludnic May 6, 2024
47c550a
Merge branch 'refs/heads/dev' into feat/ipfs
bludnic May 7, 2024
97d13b3
chore: regenerate `adamant-wallets`
bludnic May 7, 2024
584f97e
fix(healtcheck): fix IPFS service path
bludnic May 7, 2024
a2f5bac
fix(nodes, storage): fix undefined error
bludnic May 7, 2024
4b4d8a4
feat(IpfsNodesTableItem): rewritten healthcheck (work in progress)
RealGoodProgrammer May 14, 2024
abfea27
Merge remote-tracking branch 'refs/remotes/origin/dev' into feat/ipfs
RealGoodProgrammer May 14, 2024
05f2f57
fix(AChatAttachment): the AIP changed
bludnic May 20, 2024
bcd7578
fix: photo display in chat and image modal
juliahermak May 27, 2024
95a6cc9
refactor: remove legacy README.md
bludnic Jun 3, 2024
3af78a5
Update src/components/nodes/ipfs/IpfsNodesTable.vue
RealGoodProgrammer Jul 13, 2024
84eafb5
Merge branch 'refs/heads/dev' into feat/ipfs
RealGoodProgrammer Jul 19, 2024
2c5da6a
Merge remote-tracking branch 'origin/feat/ipfs' into feat/ipfs
bludnic Sep 11, 2024
fe8bf86
chore: fix merge conflicts
bludnic Sep 11, 2024
0420727
fix(AChatAttachment): fix status icon
bludnic Sep 11, 2024
c4e202a
fix(vuex): commit is not defined in attachment module
bludnic Sep 11, 2024
12d06d8
fix(UploadFile.vue): convert File to Uint8Array and refactor to TS
bludnic Sep 13, 2024
65cdac4
refactor(Chat.vue): refactor to Composition API
bludnic Sep 14, 2024
603e479
fix(UploadFile): remove extra symbols
bludnic Sep 14, 2024
4de5a55
feat(asset): describe `FileAsset` according to AIP-18
bludnic Sep 14, 2024
dc66f53
feat(adamant-api): add `encodeFile` helper
bludnic Sep 14, 2024
0514253
feat(IpfsClient): add `upload` method
bludnic Sep 14, 2024
863b6dc
feat(AChatFile): fix file type
bludnic Sep 14, 2024
a24c05e
refactor(UploadFile): type it properly
bludnic Sep 15, 2024
6da8c2d
feat(adamant-api): add `createAttachment` util
bludnic Sep 15, 2024
d5b331d
feat(store, chat): add `sendAttachment` action
bludnic Sep 15, 2024
c312db7
chore(Vue): add JSX support
bludnic Sep 16, 2024
3e2b236
fix(i18n): use tFunction
bludnic Sep 16, 2024
885d662
fix(lib/adamant): senderPublicKey can be either HEX string or Uint8Array
bludnic Sep 17, 2024
a257cdd
feat(IpfsClient): add `downloadFile` method
bludnic Sep 17, 2024
37ba567
feat(IPFS): add file loader component
bludnic Sep 17, 2024
e76475a
feat(AChatFileLoader): handle local files
bludnic Sep 17, 2024
ad14420
fix(adamant-api, encodeFile): fix `file` param type
bludnic Sep 17, 2024
74b0689
refactor(UploadFile.vue): move `readFileAsDataURL` to util
bludnic Sep 17, 2024
0eea02a
fix(Vuex, chat, sendAttachment): fix `files` param type
bludnic Sep 17, 2024
32a0bc3
feat(IPFS): upload file functionality
bludnic Sep 17, 2024
1376527
chore(config): use prod IPFS nodes
bludnic Sep 17, 2024
43df190
fix(Vuex): attachment not cached
bludnic Sep 17, 2024
77cd1a6
feat(lib/file, IPFS): add `computeCID` helper
bludnic Sep 17, 2024
84660ac
feat(sendAttachment): compute CIDs on the client side
bludnic Sep 17, 2024
d16e134
feat(UploadFile): save to File API right after file is selected
bludnic Sep 17, 2024
1eb6d3c
fix(`lib/asset`): use CID from file data
bludnic Sep 17, 2024
56d1cbd
refactor(AChatTransaction): remove `crypto` prop & fix TS error
bludnic Sep 18, 2024
23f1afc
fix(i18n): use `t()` instead of legacy `$t()`
bludnic Sep 18, 2024
ad5dcd1
fix(TS): fix build errors
bludnic Sep 18, 2024
b1333ba
fix(IPFS): update production config
bludnic Sep 18, 2024
8bcf319
fix(IPFS): increase threshold up to 50 seconds due to Sync state
bludnic Sep 18, 2024
02f4dc3
fix(UploadFile): CID of the file is different from the one returned b…
bludnic Sep 18, 2024
5380e0c
chore: regenerate wallets
bludnic Sep 26, 2024
f5c7b7c
chore: migrate to new adamant-wallets structure
bludnic Sep 26, 2024
89b60ee
feat(AChatAttachment): restyle image grid layout to show 2/3 images p…
bludnic Sep 27, 2024
fb69d48
feat(AChatAttachment): add inline layout
bludnic Sep 27, 2024
6f3b964
fix(Chat): attachment does not require a message
bludnic Sep 27, 2024
a72366a
fix(Chat): reset preview attachments after message was send
bludnic Sep 27, 2024
e2665c5
feat(AChatAttachment): hide avatar
bludnic Sep 28, 2024
ca4bdcd
Merge branch 'refs/heads/dev' into feat/ipfs
bludnic Sep 30, 2024
da180d2
feat(AChatAttachment): align inline attachments to right
bludnic Sep 30, 2024
5eff429
fix(AChatAttachment): open images gallery when inline layout
bludnic Sep 30, 2024
3cec833
feat(attachment): handle error state
bludnic Oct 4, 2024
b7d292d
chore: add VueUse
bludnic Oct 10, 2024
688889f
feat(AChatImageModal): restyle dialog
bludnic Oct 10, 2024
0e2b6f8
feat(AChatImageModal): slider navigation using arrows
bludnic Oct 10, 2024
d573973
feat(AChatImageModal): slider navigation using arrows
bludnic Oct 10, 2024
fa68c6c
fix(AChatModalItem): refactor component
bludnic Oct 10, 2024
cd770dd
feat(AChatImageModal): close slider when click outside
bludnic Oct 10, 2024
cc5537c
refactor(AChatImageModalItem): fix types
bludnic Oct 10, 2024
373510f
fix(AChatImageModal): fix `files` prop type
bludnic Oct 10, 2024
1fd6b26
fix(AChatImageModal): download file by URL
bludnic Oct 10, 2024
2add32b
feat(ImageLayout): improve image grid layout
bludnic Oct 10, 2024
46b0d8d
feat(AChatImage): show error icon when failed to load the image
bludnic Oct 10, 2024
a353538
fix(AChatImage): don't open image slider while the image is loading
bludnic Oct 11, 2024
e37f425
fix(AChatImageModal): hide arrow on mobile or when only one image att…
bludnic Oct 11, 2024
68c615d
feat(ImageModal): layout for files
bludnic Oct 11, 2024
8062fa9
feat(attachments): restyle inline layout
bludnic Oct 12, 2024
0c88442
feat(attachment): add box-shadow to ImageLayout
bludnic Oct 12, 2024
86eb3af
fix(attachments): reset the input value to allow selecting the same f…
bludnic Oct 12, 2024
314ec03
refactor(UploadFile): to Composition API
bludnic Oct 12, 2024
b5a8f6a
feat(AChatFile): show error icon when failed to load the image
bludnic Oct 12, 2024
b77ca53
fix(AChatFile, AChatImage): don't open file slider until image previe…
bludnic Oct 12, 2024
eac60d9
fix(AChatFileLoader): don't retry or refetch
bludnic Oct 12, 2024
f4f7c06
fix(lib/nodes): export `ipfs` client
bludnic Oct 18, 2024
acaa64f
fix(attachments): revoke objects URLs when logout
bludnic Oct 18, 2024
35640d1
Merge branch 'refs/heads/dev' into feat/ipfs
bludnic Oct 28, 2024
b0bfc55
refactor(constants): merge JS and DTS files
bludnic Oct 28, 2024
5856bae
feat(attachments): set max 10 files
bludnic Oct 28, 2024
f11d83f
feat(IconFile): improve file icon
bludnic Nov 5, 2024
7f24a40
fix(AChatFile): uppercase file extension
bludnic Nov 5, 2024
d5d73cc
refactor(IconFile): wrap in `defineComponent`
bludnic Nov 5, 2024
a6cf06e
fix(AChatModalFile): display file extension
bludnic Nov 5, 2024
58b5642
refactor: decompose files preview components
bludnic Nov 5, 2024
0f46571
fix(AChatModalFile): width/height props should be type number
bludnic Nov 5, 2024
122a882
fix(AChatImageModal): disable carousel continuous
bludnic Nov 5, 2024
14ce69b
feat(attachments): show upload progress
bludnic Nov 10, 2024
6f5e535
feat(adamant-api): update attachmentAsset params
bludnic Nov 10, 2024
50b1d6e
feat: add `cropImage` helper
bludnic Nov 10, 2024
d28fad8
feat(attachments): upload previews
bludnic Nov 10, 2024
ba69d63
fix(attachments): upload previews
bludnic Nov 10, 2024
e289e8b
chore(adamant-api, asset): DTS to TS
bludnic Nov 10, 2024
4b03ca6
feat(attachments): show prompt if files are uploading
bludnic Nov 26, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
336 changes: 310 additions & 26 deletions package-lock.json

Large diffs are not rendered by default.

3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
"@mdi/font": "^7.4.47",
"@stablelib/utf8": "^2.0.0",
"@tanstack/vue-query": "^5.52.2",
"@vueuse/core": "^11.1.0",
"@zxing/browser": "^0.1.5",
"@zxing/library": "^0.21.3",
"assert": "^2.1.0",
Expand Down Expand Up @@ -75,6 +76,7 @@
"lodash": "^4.17.21",
"marked": "^14.1.0",
"mitt": "^3.0.1",
"multiformats": "^13.3.0",
"notifyjs": "^3.0.0",
"npm": "^10.8.3",
"os-browserify": "^0.3.0",
Expand Down Expand Up @@ -133,6 +135,7 @@
"@typescript-eslint/eslint-plugin": "^8.3.0",
"@typescript-eslint/parser": "^8.3.0",
"@vitejs/plugin-vue": "^5.1.2",
"@vitejs/plugin-vue-jsx": "^4.0.1",
"@vue/eslint-config-prettier": "^9.0.0",
"@vue/test-utils": "^2.4.6",
"autoprefixer": "^10.4.20",
Expand Down
5 changes: 4 additions & 1 deletion src/App.vue
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
<template>
<v-app :theme="themeName" class="application--linear-gradient">
<UploadAttachmentExitPrompt />
<warning-on-addresses-dialog v-model="showWarningOnAddressesDialog" />

<component :is="layout">
Expand All @@ -12,12 +13,14 @@
import { defineComponent } from 'vue'
import dayjs from 'dayjs'
import WarningOnAddressesDialog from '@/components/WarningOnAddressesDialog.vue'
import UploadAttachmentExitPrompt from '@/components/UploadAttachmentExitPrompt.vue'
import Notifications from '@/lib/notifications'
import { ThemeName } from './plugins/vuetify'

export default defineComponent({
components: {
WarningOnAddressesDialog
WarningOnAddressesDialog,
UploadAttachmentExitPrompt
},
data: () => ({
showWarningOnAddressesDialog: false,
Expand Down
1 change: 1 addition & 0 deletions src/assets/styles/settings/_colors.scss
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ $adm-colors: (
'secondary2': #f5f5f5,
'secondary2-transparent': rgba(245, 245, 245, 0.3),
'secondary2-slightly-transparent': rgba(245, 245, 245, 0.13),
'secondary2-slightly-transparent2': rgba(245, 245, 245, 0.1),
'grey': #9e9e9e,
'grey-transparent': rgba(255, 255, 255, 0.7),
);
289 changes: 289 additions & 0 deletions src/components/AChat/AChatAttachment/AChatAttachment.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,289 @@
<template>
<v-row
class="a-chat__message-container"
:class="{
'a-chat__message-container--right': isStringEqualCI(transaction.senderId, userId),
'a-chat__message-container--transition': elementLeftOffset === 0,
'a-chat__message-container--disable-max-width': disableMaxWidth
}"
v-touch="{
move: onMove,
end: onSwipeEnd
}"
:style="{
left: swipeDisabled ? '0px' : `${elementLeftOffset}px`
}"
v-longpress="onLongPress"
>
<div
class="a-chat__message"
:class="{
'a-chat__message--flashing': flashing,
'elevation-9': elevation
}"
:data-id="dataId"
>
<div class="a-chat__message-card">
<div class="a-chat__message-card-header mt-1">
<div v-if="transaction.status === 'CONFIRMED'" class="a-chat__blockchain-status">
&#x26AD;
</div>
<div class="a-chat__timestamp">
{{ time }}
</div>
<div v-if="isOutgoingMessage" class="a-chat__status">
<v-icon
v-if="transaction.status === 'REJECTED'"
:icon="statusIcon"
:title="t('chats.retry_message')"
size="15"
color="red"
@click="$emit('resend')"
/>
<v-icon v-else :icon="statusIcon" size="13" />
</div>
</div>

<div v-if="transaction.isReply" class="a-chat__quoted-message">
<quoted-message
:message-id="transaction.asset.replyto_id"
@click="$emit('click:quotedMessage', transaction.asset.replyto_id)"
/>
</div>

<div class="a-chat__message-card-body">
<!-- eslint-disable vue/no-v-html -- Safe with DOMPurify.sanitize() content -->
<!-- AChatMessage :message <- Chat.vue :message="formatMessage(message)" <- formatMessage <- DOMPurify.sanitize() -->
<div
v-if="html"
class="a-chat__message-text a-text-regular-enlarged"
v-html="formattedMessage"
/>
<!-- eslint-enable vue/no-v-html -->
<div
v-else
class="a-chat__message-text a-text-regular-enlarged"
v-text="formattedMessage"
/>
</div>
</div>
</div>

<div
:class="{
'a-chat__attachments': true,
'a-chat__attachments--inline': !hasImagesOnly
}"
>
<ImageLayout
v-if="hasImagesOnly"
:partnerId="partnerId"
:images="transaction.localFiles || transaction.asset.files"
:transaction="transaction"
@click:image="openModal"
/>
<InlineLayout
v-else
:partnerId="partnerId"
:files="transaction.localFiles || transaction.asset.files"
:transaction="transaction"
@click:file="openModal"
/>

<AChatImageModal
:files="transaction.asset.files"
:transaction="transaction"
:index="currentIndex"
:modal="isModalOpen"
v-if="isModalOpen"
@close="closeModal"
@update:modal="closeModal"
/>
</div>

<slot name="actions" />
</v-row>
</template>

<script lang="ts">
import { FileAsset } from '@/lib/adamant-api/asset'
import { ref, computed, defineComponent, PropType } from 'vue'
import { useI18n } from 'vue-i18n'
import { useStore } from 'vuex'

import { useFormatMessage } from '../hooks/useFormatMessage'
import { usePartnerId } from '../hooks/usePartnerId'
import { useTransactionTime } from '../hooks/useTransactionTime'
import { LocalFile, NormalizedChatMessageTransaction } from '@/lib/chat/helpers'
import { downloadFile, isStringEqualCI } from '@/lib/textHelpers'
import { tsIcon } from '@/lib/constants'
import QuotedMessage from '../QuotedMessage.vue'
import { useSwipeLeft } from '@/hooks/useSwipeLeft'
import formatDate from '@/filters/date'
import { isWelcomeChat } from '@/lib/chat/meta/utils'
import ImageLayout from './ImageLayout.vue'
import InlineLayout from './InlineLayout.vue'
import AChatImageModal from './AChatImageModal.vue'

function isLocalFile(file: FileAsset | LocalFile): file is LocalFile {
return 'file' in file && file.file?.file instanceof File
}

export default defineComponent({
methods: { downloadFile },
components: {
InlineLayout,
ImageLayout,
QuotedMessage,
AChatImageModal
},
props: {
transaction: {
type: Object as PropType<NormalizedChatMessageTransaction>,
required: true
},
partnerId: {
type: String,
required: true
},
dataId: {
type: String
},
html: {
type: Boolean,
default: false
},
/**
* Highlight the message by applying a background flash effect
*/
flashing: {
type: Boolean,
default: false
},
disableMaxWidth: {
type: Boolean
},
elevation: {
type: Boolean
},
swipeDisabled: {
type: Boolean
}
},
emits: ['resend', 'click:quotedMessage', 'swipe:left', 'longpress'],
setup(props, { emit }) {
const { t } = useI18n()
const store = useStore()

const userId = computed(() => store.state.address)
const partnerId = usePartnerId(props.transaction)
const currentIndex = ref(0)
const isModalOpen = ref(false)

const openModal = (index: number) => {
currentIndex.value = index
isModalOpen.value = true
}

const closeModal = () => {
isModalOpen.value = false
}
const showAvatar = computed(() => !isWelcomeChat(partnerId.value))

const statusIcon = computed(() => tsIcon(props.transaction.status))
const isOutgoingMessage = computed(() =>
isStringEqualCI(props.transaction.senderId, userId.value)
)
const formattedMessage = useFormatMessage(props.transaction)
const time = useTransactionTime(props.transaction)

const { onMove, onSwipeEnd, elementLeftOffset } = useSwipeLeft(() => {
emit('swipe:left')
})

const onLongPress = () => {
emit('longpress')
}

const hasImagesOnly = computed(() => {
const files = props.transaction.localFiles || (props.transaction.asset.files as FileAsset[])

return files.every((file) => {
if (isLocalFile(file)) {
return file.file.isImage
}

return ['jpg', 'jpeg', 'png'].includes(file.extension!)
})
})

return {
t,
userId,
statusIcon,
isOutgoingMessage,
formattedMessage,
showAvatar,
onMove,
onSwipeEnd,
elementLeftOffset,
isStringEqualCI,
onLongPress,
formatDate,
time,
currentIndex,
isModalOpen,
openModal,
closeModal,
hasImagesOnly
}
}
})
</script>
<style lang="scss" scoped>
@import '@/assets/styles/settings/_colors.scss';
@import '@/assets/styles/themes/adamant/_mixins.scss';

.a-chat__attachments {
width: 500px;
max-width: 100%;
margin-top: 4px;

&--inline {
width: auto;
}
}

.a-chat_file-container {
max-width: 420px;
}

.a-chat_fileContainerWithElement {
display: grid;
gap: 2px;
width: 80vw;
max-width: 200px;
grid-template-columns: repeat(auto-fit, minmax(98px, 1fr));
}

.a-chat_file-img {
width: 100%;
height: auto;
display: block;
object-fit: cover;
}

.v-theme--light {
.a-chat-file {
background-color: map-get($adm-colors, 'secondary');
color: map-get($adm-colors, 'regular');
}
}

.v-theme--dark {
.a-chat-file {
background-color: rgba(245, 245, 245, 0.1); // @todo const
color: #fff;
}
}
</style>
Loading