Merge pull request #127 from PhillipLangMartinez/feature/new-editor-file-import

Feature/new editor file import
This commit is contained in:
AlphaX-Projects 2023-02-14 16:27:57 +01:00 committed by GitHub
commit a43dc63a7b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 638 additions and 148 deletions

BIN
img/attachment-icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

View File

@ -14,9 +14,9 @@
@font-face { @font-face {
font-family: 'Material Symbols Outlined'; font-family: 'Material Symbols Outlined';
font-style: normal; font-style: normal;
src: local('MaterialSymbolsOutlined'), src: local('MaterialSymbolsOutlined'),
url(MaterialSymbolsOutlined.ttf) format('truetype'), url(MaterialSymbolsOutlined.ttf) format('truetype'),
url(MaterialSymbolsOutlined.woff2) format('woff2') url(MaterialSymbolsOutlined.woff2) format('woff2')
} }
@font-face { @font-face {
@ -26,6 +26,13 @@
url(Montserrat.ttf) format('truetype'); url(Montserrat.ttf) format('truetype');
} }
@font-face {
font-family: 'WorkSans';
src: local('WorkSans'),
local('WorkSans'),
url(WorkSans.ttf) format('truetype');
}
@font-face { @font-face {
font-family: 'Raleway'; font-family: 'Raleway';
src: local('Raleway'), src: local('Raleway'),
@ -77,7 +84,8 @@
font-family: 'Material Symbols Outlined'; font-family: 'Material Symbols Outlined';
font-weight: normal; font-weight: normal;
font-style: normal; font-style: normal;
font-size: 24px; /* Preferred icon size */ font-size: 24px;
/* Preferred icon size */
display: inline-block; display: inline-block;
line-height: 1; line-height: 1;
text-transform: none; text-transform: none;

View File

@ -581,6 +581,14 @@
"cchange72": "other", "cchange72": "other",
"cchange73": "s", "cchange73": "s",
"cchange74": "reacted with", "cchange74": "reacted with",
"cchange75": "Uploading attachment. This may take up to one minute.",
"cchange76": "Deleting attachment. This may take up to one minute.",
"cchange77": "Attachment size exceeds 1 MB",
"cchange78": "Are you sure you want to delete this image?",
"cchange79": "Are you sure you want to delete this attachment?",
"cchange80": "This image has been deleted",
"cchange81": "This image type is not supported",
"cchange82": "This attachment has been deleted",
"cchange90": "No messages" "cchange90": "No messages"
}, },
"welcomepage": { "welcomepage": {

View File

@ -86,7 +86,8 @@
"rollup": "3.15.0", "rollup": "3.15.0",
"rollup-plugin-node-globals": "1.4.0", "rollup-plugin-node-globals": "1.4.0",
"rollup-plugin-progress": "1.1.2", "rollup-plugin-progress": "1.1.2",
"rollup-plugin-web-worker-loader": "1.6.1" "rollup-plugin-web-worker-loader": "1.6.1",
"axios": "1.3.2"
}, },
"engines": { "engines": {

View File

@ -39,7 +39,7 @@ import { replaceMessagesEdited } from '../../utils/replace-messages-edited.js';
import { publishData } from '../../utils/publish-image.js'; import { publishData } from '../../utils/publish-image.js';
import { EmojiPicker } from 'emoji-picker-js'; import { EmojiPicker } from 'emoji-picker-js';
import WebWorker from 'web-worker:./computePowWorker.js'; import WebWorker from 'web-worker:./computePowWorker.js';
import WebWorkerImage from 'web-worker:./computePowWorkerImage.js'; import WebWorkerFile from 'web-worker:./computePowWorkerFile.js';
import '@polymer/paper-dialog/paper-dialog.js' import '@polymer/paper-dialog/paper-dialog.js'
const chatLastSeen = localForage.createInstance({ const chatLastSeen = localForage.createInstance({
@ -77,7 +77,11 @@ class ChatPage extends LitElement {
editedMessageObj: { type: Object }, editedMessageObj: { type: Object },
iframeHeight: { type: Number }, iframeHeight: { type: Number },
imageFile: { type: Object }, imageFile: { type: Object },
attachment: { type: Object },
isUploadingImage: { type: Boolean }, isUploadingImage: { type: Boolean },
isDeletingImage: { type: Boolean },
isUploadingAttachment: { type: Boolean },
isDeletingAttachment: { type: Boolean },
userLanguage: { type: String }, userLanguage: { type: String },
lastMessageRefVisible: { type: Boolean }, lastMessageRefVisible: { type: Boolean },
isLoadingOldMessages: { type: Boolean }, isLoadingOldMessages: { type: Boolean },
@ -94,7 +98,7 @@ class ChatPage extends LitElement {
userFound: { type: Array }, userFound: { type: Array },
userFoundModalOpen: { type: Boolean }, userFoundModalOpen: { type: Boolean },
webWorker: { type: Object }, webWorker: { type: Object },
webWorkerImage: { type: Object }, webWorkerFile: { type: Object },
myTrimmedMeassage: { type: String }, myTrimmedMeassage: { type: String },
editor: {type: Object}, editor: {type: Object},
currentEditor: {type: String}, currentEditor: {type: String},
@ -823,6 +827,30 @@ class ChatPage extends LitElement {
cursor: pointer; cursor: pointer;
color: #4e5054; color: #4e5054;
} }
.attachment-icon-container {
display: flex;
align-items: center;
justify-content: center;
height: 120px;
width: 120px;
border-radius: 50%;
border: none;
background-color: var(--mdc-theme-primary);
}
.attachment-icon {
width: 70%;
}
.attachment-name {
font-family: Work Sans, sans-serif;
font-size: 20px;
color: var(--chat-bubble-msg-color);
margin: 0px;
letter-spacing: 1px;
padding: 5px 0px;
}
` `
} }
@ -830,7 +858,7 @@ class ChatPage extends LitElement {
super() super()
this.getOldMessage = this.getOldMessage.bind(this) this.getOldMessage = this.getOldMessage.bind(this)
this._sendMessage = this._sendMessage.bind(this) this._sendMessage = this._sendMessage.bind(this)
this.insertImage = this.insertImage.bind(this) this.insertFile = this.insertFile.bind(this)
this.pasteImage = this.pasteImage.bind(this) this.pasteImage = this.pasteImage.bind(this)
this.toggleEnableChatEnter = this.toggleEnableChatEnter.bind(this) this.toggleEnableChatEnter = this.toggleEnableChatEnter.bind(this)
this._downObserverhandler = this._downObserverhandler.bind(this) this._downObserverhandler = this._downObserverhandler.bind(this)
@ -861,6 +889,7 @@ class ChatPage extends LitElement {
this.editedMessageObj = null this.editedMessageObj = null
this.iframeHeight = 42 this.iframeHeight = 42
this.imageFile = null this.imageFile = null
this.attachment = null
this.uid = new ShortUniqueId() this.uid = new ShortUniqueId()
this.userLanguage = "" this.userLanguage = ""
this.lastMessageRefVisible = false this.lastMessageRefVisible = false
@ -888,7 +917,7 @@ class ChatPage extends LitElement {
selected: false selected: false
} }
this.webWorker = null; this.webWorker = null;
this.webWorkerImage = null; this.webWorkerFile = null;
this.currentEditor = '_chatEditorDOM' this.currentEditor = '_chatEditorDOM'
this.initialChat = this.initialChat.bind(this) this.initialChat = this.initialChat.bind(this)
this.isEnabledChatEnter = true this.isEnabledChatEnter = true
@ -1030,8 +1059,8 @@ class ChatPage extends LitElement {
class="close-icon" class="close-icon"
icon="vaadin:close-big" icon="vaadin:close-big"
slot="icon" slot="icon"
@click=${() => this.closeEditMessageContainer()} @click=${() => this.closeEditMessageContainer()}>
></vaadin-icon> </vaadin-icon>
</div> </div>
</div> </div>
`} `}
@ -1042,7 +1071,7 @@ class ChatPage extends LitElement {
placeholder=${this.chatEditorPlaceholder} placeholder=${this.chatEditorPlaceholder}
._sendMessage=${this._sendMessage} ._sendMessage=${this._sendMessage}
.imageFile=${this.imageFile} .imageFile=${this.imageFile}
.insertImage=${this.insertImage} .insertFile=${this.insertFile}
.editedMessageObj=${this.editedMessageObj} .editedMessageObj=${this.editedMessageObj}
?isLoading=${this.isLoading} ?isLoading=${this.isLoading}
?isLoadingMessages=${this.isLoadingMessages} ?isLoadingMessages=${this.isLoadingMessages}
@ -1060,25 +1089,39 @@ class ChatPage extends LitElement {
</div> </div>
</div> </div>
${(this.isUploadingImage || this.isDeletingImage) ? html` ${(this.isUploadingImage || this.isDeletingImage) ? html`
<div class="dialogCustom"> <div class="dialogCustom">
<div class="dialogCustomInner"> <div class="dialogCustomInner">
<div class="dialog-container-loader"> <div class="dialog-container-loader">
<div class=${`smallLoading marginLoader`}></div> <div class=${`smallLoading marginLoader`}></div>
<p> <p>
${this.isDeletingImage ? ${this.isDeletingImage ?
translate("chatpage.cchange31") : translate("chatpage.cchange30")} translate("chatpage.cchange31") : translate("chatpage.cchange30")}
</p> </p>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
`: ''} `: ''}
${(this.isUploadingAttachment || this.isDeletingAttachment) ? html`
<div class="dialogCustom">
<div class="dialogCustomInner">
<div class="dialog-container-loader">
<div class=${`smallLoading marginLoader`}></div>
<p>
${this.isDeletingAttachment ?
translate("chatpage.cchange76") : translate("chatpage.cchange75")}
</p>
</div>
</div>
</div>
</div>
`: ''}
<wrapper-modal <wrapper-modal
.onClickFunc=${() => { .onClickFunc=${() => {
this.removeImage(); this.removeImage();
}} }}
style=${(this.imageFile && !this.isUploadingImage) ? "visibility:visible;z-index:50" : "visibility: hidden;z-index:-100"}> style=${(this.imageFile && !this.isUploadingImage) ? "visibility:visible; z-index:50" : "visibility: hidden;z-index:-100"}>
<div> <div>
<div class="dialog-container"> <div class="dialog-container">
${this.imageFile && html` ${this.imageFile && html`
@ -1091,7 +1134,7 @@ class ChatPage extends LitElement {
placeholder=${this.chatEditorPlaceholder} placeholder=${this.chatEditorPlaceholder}
._sendMessage=${this._sendMessage} ._sendMessage=${this._sendMessage}
.imageFile=${this.imageFile} .imageFile=${this.imageFile}
.insertImage=${this.insertImage} .insertFile=${this.insertFile}
.editedMessageObj=${this.editedMessageObj} .editedMessageObj=${this.editedMessageObj}
?isLoading=${this.isLoading} ?isLoading=${this.isLoading}
?isLoadingMessages=${this.isLoadingMessages} ?isLoadingMessages=${this.isLoadingMessages}
@ -1114,7 +1157,6 @@ class ChatPage extends LitElement {
const chatTextEditor = this.shadowRoot.getElementById('chatTextCaption') const chatTextEditor = this.shadowRoot.getElementById('chatTextCaption')
chatTextEditor.sendMessageFunc({ chatTextEditor.sendMessageFunc({
type: 'image', type: 'image',
imageFile: this.imageFile,
}) })
}} }}
> >
@ -1124,6 +1166,58 @@ class ChatPage extends LitElement {
</div> </div>
</div> </div>
</wrapper-modal> </wrapper-modal>
<wrapper-modal
.onClickFunc=${() => {
this.removeAttachment();
}}
style=${this.attachment && !this.isUploadingAttachment ? "visibility: visible; z-index: 50" : "visibility: hidden; z-index: -100"}>
<div>
<div class="dialog-container">
${this.attachment && html`
<div class="attachment-icon-container">
<img src="/img/attachment-icon.png" alt="attachment-icon" class="attachment-icon" />
</div>
`}
<p class="attachment-name">${this.attachment && this.attachment.name}</p>
<div class="caption-container">
<chat-text-editor
iframeId="newAttachmentChat"
?hasGlobalEvents=${false}
placeholder=${this.chatEditorPlaceholder}
._sendMessage=${this._sendMessage}
.imageFile=${this.imageFile}
.attachment=${this.attachment}
.insertFile=${this.insertFile}
.editedMessageObj=${this.editedMessageObj}
?isLoading=${this.isLoading}
?isLoadingMessages=${this.isLoadingMessages}
id="chatAttachmentId"
.editor=${this.editorAttachment}
.updatePlaceholder=${(editor, value)=> this.updatePlaceholder(editor, value)}
>
</chat-text-editor>
</div>
<div class="modal-button-row">
<button class="modal-button-red" @click=${() => {
this.removeAttachment();
}}>
${translate("chatpage.cchange33")}
</button>
<button
class="modal-button"
@click=${() => {
const chatTextEditor = this.shadowRoot.getElementById('chatAttachmentId');
chatTextEditor.sendMessageFunc({
type: 'attachment',
})
}}
>
${translate("chatpage.cchange9")}
</button>
</div>
</div>
</div>
</wrapper-modal>
<paper-dialog class="warning" id="confirmDialog" modal> <paper-dialog class="warning" id="confirmDialog" modal>
<h2 style="color: var(--black);">${translate("chatpage.cchange41")}</h2> <h2 style="color: var(--black);">${translate("chatpage.cchange41")}</h2>
<hr> <hr>
@ -1346,11 +1440,12 @@ class ChatPage extends LitElement {
super.connectedCallback(); super.connectedCallback();
await this.initUpdate() await this.initUpdate()
this.webWorker = new WebWorker(); this.webWorker = new WebWorker();
this.webWorkerImage = new WebWorkerImage(); this.webWorkerFile = new WebWorkerFile();
await this.getUpdateCompleteTextEditor(); await this.getUpdateCompleteTextEditor();
const elementChatId = this.shadowRoot.getElementById('_chatEditorDOM').shadowRoot.getElementById('_chatEditorDOM') const elementChatId = this.shadowRoot.getElementById('_chatEditorDOM').shadowRoot.getElementById('_chatEditorDOM');
const elementChatImageId = this.shadowRoot.getElementById('chatTextCaption').shadowRoot.getElementById('newChat') const elementChatImageId = this.shadowRoot.getElementById('chatTextCaption').shadowRoot.getElementById('newChat');
const elementChatAttachmentId = this.shadowRoot.getElementById('chatAttachmentId').shadowRoot.getElementById('newAttachmentChat');
this.editor = new Editor({ this.editor = new Editor({
onUpdate: ()=> { onUpdate: ()=> {
this.shadowRoot.getElementById('_chatEditorDOM').getMessageSize(this.editor.getJSON()) this.shadowRoot.getElementById('_chatEditorDOM').getMessageSize(this.editor.getJSON())
@ -1407,11 +1502,37 @@ class ChatPage extends LitElement {
Extension.create({ Extension.create({
addKeyboardShortcuts:()=> { addKeyboardShortcuts:()=> {
return { return {
'Enter':()=> { 'Enter':() => {
const chatTextEditor = this.shadowRoot.getElementById('chatTextCaption') const chatTextEditor = this.shadowRoot.getElementById('chatTextCaption')
chatTextEditor.sendMessageFunc({ chatTextEditor.sendMessageFunc({
type: 'image', type: 'image'
imageFile: this.imageFile, })
return true
}
}
}})
]
})
this.editorAttachment = new Editor({
onUpdate: () => {
this.shadowRoot.getElementById('chatAttachmentId').getMessageSize(this.editorAttachment.getJSON())
},
element: elementChatAttachmentId,
extensions: [
StarterKit,
Underline,
Highlight,
Placeholder.configure({
placeholder: 'Write something …',
}),
Extension.create({
addKeyboardShortcuts:()=> {
return {
'Enter':()=> {
const chatTextEditor = this.shadowRoot.getElementById('chatAttachmentId')
chatTextEditor.sendMessageFunc({
type: 'attachment'
}) })
return true return true
} }
@ -1461,7 +1582,7 @@ class ChatPage extends LitElement {
} }
initialChat(e) { initialChat(e) {
if (this.editor && !this.editor.isFocused && this.currentEditor === '_chatEditorDOM' && !this.openForwardOpen && !this.openTipUser) { if (this.editor && !this.editor.isFocused && this.currentEditor === '_chatEditorDOM' && !this.openForwardOpen && !this.openTipUser) {
// WARNING: Deprecated methods from KeyBoard Event // WARNING: Deprecated methods from KeyBoard Event
if (e.code === "Space" || e.keyCode === 32 || e.which === 32) { if (e.code === "Space" || e.keyCode === 32 || e.which === 32) {
} else if (inputKeyCodes.includes(e.keyCode)) { } else if (inputKeyCodes.includes(e.keyCode)) {
@ -1510,7 +1631,7 @@ class ChatPage extends LitElement {
this.insertImage(file); this.insertImage(file);
} catch (error) { } catch (error) {
console.error(error); console.error(error);
let errorMsg = get("chatpage.cchange70") let errorMsg = get("chatpage.cchange81")
parentEpml.request('showSnackBar', `${errorMsg}`) parentEpml.request('showSnackBar', `${errorMsg}`)
} }
} else { } else {
@ -1648,19 +1769,29 @@ class ChatPage extends LitElement {
} }
insertImage(file) { insertFile(file) {
if (file.type.includes('image')) { if (file.type.includes('image')) {
this.imageFile = file; this.imageFile = file;
this.currentEditor = 'newChat' this.currentEditor = 'newChat';
return; return;
} } else {
parentEpml.request('showSnackBar', get("chatpage.cchange28")); this.attachment = file;
this.currentEditor = "newAttachmentChat";
return;
}
// parentEpml.request('showSnackBar', get("chatpage.cchange28"));
} }
removeImage() { removeImage() {
this.imageFile = null; this.imageFile = null;
this.resetChatEditor() this.resetChatEditor();
this.currentEditor = '_chatEditorDOM' this.currentEditor = '_chatEditorDOM';
}
removeAttachment() {
this.attachment = null;
this.resetChatEditor();
this.currentEditor = '_chatEditorDOM';
} }
changeMsgInput(id) { changeMsgInput(id) {
@ -1817,7 +1948,6 @@ class ChatPage extends LitElement {
this.editor.setEditable(true) this.editor.setEditable(true)
} }
} }
} }
async getName (recipient) { async getName (recipient) {
@ -2519,6 +2649,9 @@ class ChatPage extends LitElement {
if(this.currentEditor === 'newChat'){ if(this.currentEditor === 'newChat'){
this.editorImage.commands.setContent('') this.editorImage.commands.setContent('')
} }
if(this.currentEditor === 'newAttachmentChat'){
this.editorAttachment.commands.setContent('')
}
} }
async _sendMessage(outSideMsg, msg) { async _sendMessage(outSideMsg, msg) {
@ -2561,6 +2694,7 @@ class ChatPage extends LitElement {
// find specific object property in local // find specific object property in local
let typeMessage = 'regular'; let typeMessage = 'regular';
let workerImage; let workerImage;
let workerAttachment;
this.isLoading = true; this.isLoading = true;
const trimmedMessage = msg const trimmedMessage = msg
@ -2589,62 +2723,152 @@ class ChatPage extends LitElement {
let compressedFile = '' let compressedFile = ''
var str = "iVBORw0KGgoAAAANSUhEUgAAAsAAAAGMAQMAAADuk4YmAAAAA1BMVEX///+nxBvIAAAAAXRSTlMAQObYZgAAADlJREFUeF7twDEBAAAAwiD7p7bGDlgYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwAGJrAABgPqdWQAAAABJRU5ErkJggg=="; var str = "iVBORw0KGgoAAAANSUhEUgAAAsAAAAGMAQMAAADuk4YmAAAAA1BMVEX///+nxBvIAAAAAXRSTlMAQObYZgAAADlJREFUeF7twDEBAAAAwiD7p7bGDlgYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwAGJrAABgPqdWQAAAABJRU5ErkJggg==";
if (this.webWorkerImage) { if (this.webWorkerFile) {
workerImage = this.webWorkerImage; workerImage = this.webWorkerFile;
} else { } else {
this.webWorkerImage = new WebWorkerImage(); this.webWorkerFile = new WebWorkerFile();
} }
const b64toBlob = (b64Data, contentType='', sliceSize=512) => { const b64toBlob = (b64Data, contentType='', sliceSize=512) => {
const byteCharacters = atob(b64Data); const byteCharacters = atob(b64Data);
const byteArrays = []; const byteArrays = [];
for (let offset = 0; offset < byteCharacters.length; offset += sliceSize) { for (let offset = 0; offset < byteCharacters.length; offset += sliceSize) {
const slice = byteCharacters.slice(offset, offset + sliceSize); const slice = byteCharacters.slice(offset, offset + sliceSize);
const byteNumbers = new Array(slice.length); const byteNumbers = new Array(slice.length);
for (let i = 0; i < slice.length; i++) { for (let i = 0; i < slice.length; i++) {
byteNumbers[i] = slice.charCodeAt(i); byteNumbers[i] = slice.charCodeAt(i);
}
const byteArray = new Uint8Array(byteNumbers);
byteArrays.push(byteArray);
}
const blob = new Blob(byteArrays, {type: contentType});
return blob;
}
const blob = b64toBlob(str, 'image/png');
await new Promise(resolve => {
new Compressor(blob, {
quality: 0.6,
maxWidth: 500,
success(result) {
const file = new File([result], "name", {
type: 'image/png'
});
compressedFile = file;
resolve();
},
error(err) {
console.log(err.message);
},
})
})
try {
await publishData({
registeredName: userName,
file : compressedFile,
service: 'QCHAT_IMAGE',
identifier: identifier,
parentEpml,
metaData: undefined,
uploadType: 'file',
selectedAddress: this.selectedAddress,
worker: workerImage
})
this.isDeletingImage = false
} catch (error) {
this.isLoading = false;
return
}
typeMessage = 'edit';
let chatReference = outSideMsg.editedMessageObj.reference;
if(outSideMsg.editedMessageObj.chatReference){
chatReference = outSideMsg.editedMessageObj.chatReference;
} }
const byteArray = new Uint8Array(byteNumbers); let message = "";
byteArrays.push(byteArray); try {
const parsedMessageObj = JSON.parse(outSideMsg.editedMessageObj.decodedMessage);
message = parsedMessageObj;
} catch (error) {
message = outSideMsg.editedMessageObj.decodedMessage;
} }
const messageObject = {
const blob = new Blob(byteArrays, {type: contentType}); ...message,
return blob; isImageDeleted: true
} }
const blob = b64toBlob(str, 'image/png'); const stringifyMessageObject = JSON.stringify(messageObject);
await new Promise(resolve => { this.sendMessage(stringifyMessageObject, typeMessage, chatReference);
new Compressor(blob, { } else if (outSideMsg && outSideMsg.type === 'deleteAttachment') {
quality: 0.6, this.isDeletingAttachment = true;
maxWidth: 500, let compressedFile = ''
success(result) { var str = "iVBORw0KGgoAAAANSUhEUgAAAsAAAAGMAQMAAADuk4YmAAAAA1BMVEX///+nxBvIAAAAAXRSTlMAQObYZgAAADlJREFUeF7twDEBAAAAwiD7p7bGDlgYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwAGJrAABgPqdWQAAAABJRU5ErkJggg==";
const file = new File([result], "name", { const userName = outSideMsg.name;
type: 'image/png' const identifier = outSideMsg.identifier;
});
if (this.webWorkerFile) {
compressedFile = file; workerAttachment = this.webWorkerFile;
resolve(); } else {
}, this.webWorkerFile = new WebWorkerFile();
error(err) { }
console.log(err.message);
}, const b64toBlob = (b64Data, contentType='', sliceSize=512) => {
const byteCharacters = atob(b64Data);
const byteArrays = [];
for (let offset = 0; offset < byteCharacters.length; offset += sliceSize) {
const slice = byteCharacters.slice(offset, offset + sliceSize);
const byteNumbers = new Array(slice.length);
for (let i = 0; i < slice.length; i++) {
byteNumbers[i] = slice.charCodeAt(i);
}
const byteArray = new Uint8Array(byteNumbers);
byteArrays.push(byteArray);
}
const blob = new Blob(byteArrays, {type: contentType});
return blob;
}
const blob = b64toBlob(str, 'image/png');
await new Promise(resolve => {
new Compressor(blob, {
quality: 0.6,
maxWidth: 500,
success(result) {
const file = new File([result], "name", {
type: 'image/png'
});
compressedFile = file;
resolve();
},
error(err) {
console.log(err.message);
},
})
}) })
})
try { try {
await publishData({ await publishData({
registeredName: userName, registeredName: userName,
file : compressedFile, file: compressedFile,
service: 'QCHAT_IMAGE', service: 'QCHAT_ATTACHMENT',
identifier: identifier, identifier: identifier,
parentEpml, parentEpml,
metaData: undefined, metaData: undefined,
uploadType: 'file', uploadType: 'file',
selectedAddress: this.selectedAddress, selectedAddress: this.selectedAddress,
worker: workerImage worker: workerAttachment
}) })
this.isDeletingImage = false this.isDeletingAttachment = false
} catch (error) { } catch (error) {
this.isLoading = false; this.isLoading = false;
return return
@ -2655,7 +2879,7 @@ class ChatPage extends LitElement {
if(outSideMsg.editedMessageObj.chatReference){ if(outSideMsg.editedMessageObj.chatReference){
chatReference = outSideMsg.editedMessageObj.chatReference; chatReference = outSideMsg.editedMessageObj.chatReference;
} }
let message = ""; let message = "";
try { try {
const parsedMessageObj = JSON.parse(outSideMsg.editedMessageObj.decodedMessage); const parsedMessageObj = JSON.parse(outSideMsg.editedMessageObj.decodedMessage);
@ -2666,14 +2890,11 @@ class ChatPage extends LitElement {
} }
const messageObject = { const messageObject = {
...message, ...message,
isImageDeleted: true isAttachmentDeleted: true
} }
const stringifyMessageObject = JSON.stringify(messageObject); const stringifyMessageObject = JSON.stringify(messageObject);
this.sendMessage(stringifyMessageObject, typeMessage, chatReference); this.sendMessage(stringifyMessageObject, typeMessage, chatReference);
} else if (outSideMsg && outSideMsg.type === 'image') {
}
else if (outSideMsg && outSideMsg.type === 'image') {
this.isUploadingImage = true; this.isUploadingImage = true;
const userName = await getName(this.selectedAddress.address); const userName = await getName(this.selectedAddress.address);
if (!userName) { if (!userName) {
@ -2684,10 +2905,10 @@ class ChatPage extends LitElement {
return; return;
} }
if (this.webWorkerImage) { if (this.webWorkerFile) {
workerImage = this.webWorkerImage; workerImage = this.webWorkerFile;
} else { } else {
this.webWorkerImage = new WebWorkerImage(); this.webWorkerFile = new WebWorkerFile();
} }
const image = this.imageFile const image = this.imageFile
@ -2717,41 +2938,101 @@ class ChatPage extends LitElement {
this.isUploadingImage = false; this.isUploadingImage = false;
return; return;
} }
try { try {
await publishData({ await publishData({
registeredName: userName, registeredName: userName,
file : compressedFile, file : compressedFile,
service: 'QCHAT_IMAGE', service: 'QCHAT_IMAGE',
identifier : identifier, identifier : identifier,
parentEpml, parentEpml,
metaData: undefined, metaData: undefined,
uploadType: 'file', uploadType: 'file',
selectedAddress: this.selectedAddress, selectedAddress: this.selectedAddress,
worker: workerImage worker: workerImage
}); });
this.isUploadingImage = false;
this.removeImage();
} catch (error) {
console.error(error);
this.isLoading = false;
this.isUploadingImage = false; this.isUploadingImage = false;
this.removeImage() return;
} catch (error) { }
this.isLoading = false;
this.isUploadingImage = false;
return;
}
const messageObject = {
messageText: trimmedMessage,
const messageObject = { images: [{
messageText: trimmedMessage, service: "QCHAT_IMAGE",
images: [{ name: userName,
service: "QCHAT_IMAGE", identifier: identifier
name: userName, }],
identifier: identifier isImageDeleted: false,
}], repliedTo: '',
isImageDeleted: false, version: 2
repliedTo: '', };
version: 2 const stringifyMessageObject = JSON.stringify(messageObject);
}; this.sendMessage(stringifyMessageObject, typeMessage);
const stringifyMessageObject = JSON.stringify(messageObject); }
this.sendMessage(stringifyMessageObject, typeMessage); else if (outSideMsg && outSideMsg.type === 'attachment') {
this.isUploadingAttachment = true;
const userName = await getName(this.selectedAddress.address);
if (!userName) {
parentEpml.request('showSnackBar', get("chatpage.cchange27"));
this.isLoading = false;
return;
}
if (this.webWorkerFile) {
workerAttachment = this.webWorkerFile;
} else {
this.webWorkerFile = new WebWorkerFile();
}
const attachment = this.attachment;
const id = this.uid();
const identifier = `qchat_${id}`;
const fileSize = attachment.size;
if (fileSize > 1000000) {
parentEpml.request('showSnackBar', get("chatpage.cchange77"));
this.isLoading = false;
this.isUploadingAttachment = false;
return;
}
try {
await publishData({
registeredName: userName,
file : attachment,
service: 'QCHAT_ATTACHMENT',
identifier : identifier,
parentEpml,
metaData: undefined,
uploadType: 'file',
selectedAddress: this.selectedAddress,
worker: workerAttachment
});
this.isUploadingAttachment = false;
this.removeAttachment();
} catch (error) {
console.error(error);
this.isLoading = false;
this.isUploadingAttachment = false;
return;
}
const messageObject = {
messageText: trimmedMessage,
attachments: [{
service: 'QCHAT_ATTACHMENT',
name: userName,
identifier: identifier,
attachmentName: attachment.name,
attachmentSize: attachment.size
}],
isAttachmentDeleted: false,
repliedTo: '',
version: 2
};
const stringifyMessageObject = JSON.stringify(messageObject);
this.sendMessage(stringifyMessageObject, typeMessage);
} else if (outSideMsg && outSideMsg.type === 'reaction') { } else if (outSideMsg && outSideMsg.type === 'reaction') {
const userName = await getName(this.selectedAddress.address); const userName = await getName(this.selectedAddress.address);
typeMessage = 'edit'; typeMessage = 'edit';

View File

@ -86,6 +86,7 @@ export const chatStyles = css`
font-size: 13px; font-size: 13px;
user-select: none; user-select: none;
display: flex; display: flex;
justify-content: space-between;
width: 100%; width: 100%;
padding-top: 2px; padding-top: 2px;
} }
@ -97,6 +98,7 @@ export const chatStyles = css`
font-size: 13px; font-size: 13px;
user-select: none; user-select: none;
display: flex; display: flex;
justify-content: space-between;
width: 100%; width: 100%;
padding-top: 2px; padding-top: 2px;
} }
@ -644,6 +646,7 @@ export const chatStyles = css`
white-space: pre-wrap; white-space: pre-wrap;
margin: 0px; margin: 0px;
} }
.replied-message pre code { .replied-message pre code {
color: inherit; color: inherit;
padding: 0; padding: 0;
@ -651,12 +654,10 @@ export const chatStyles = css`
font-size: 0.8rem; font-size: 0.8rem;
} }
.replied-message img { .replied-message img {
width: 1.7em; width: 1.7em;
height: 1.5em; height: 1.5em;
margin: 0px; margin: 0px;
} }
.replied-message blockquote { .replied-message blockquote {
@ -670,6 +671,79 @@ export const chatStyles = css`
margin: 2rem 0; margin: 2rem 0;
} }
.attachment-container {
display: flex;
align-items: center;
justify-content: space-evenly;
padding: 5px 0 10px 0;
gap: 20px;
cursor: pointer;
}
.attachment-icon-container {
display: flex;
align-items: center;
justify-content: center;
height: 50px;
width: 50px;
border-radius: 50%;
border: none;
background-color: var(--mdc-theme-primary);
}
.attachment-icon {
width: 70%;
}
.attachment-info {
display: flex;
flex-direction: column;
gap: 5px;
}
.attachment-name {
font-family: Work Sans, sans-serif;
font-size: 16px;
color: var(--chat-bubble-msg-color);
margin: 0;
letter-spacing: 0.4px;
padding: 5px 0;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.attachment-size {
font-family: Roboto, sans-serif;
font-size: 16px;
color: var(--chat-bubble-msg-color);
margin: 0;
letter-spacing: 0.3px;
font-weight: 300;
}
.download-icon {
position: relative;
color: var(--chat-bubble-msg-color);
width: 19px;
background-color: transparent;
}
.download-icon:hover::before {
background-color: rgb(161 158 158 / 41%);
}
.download-icon::before {
content: "";
position: absolute;
border-radius: 50%;
padding: 18px;
background-color: transparent;
transition: all 0.3s ease-in-out;
}
.edited-message-style { .edited-message-style {
font-family: "Work Sans", sans-serif; font-family: "Work Sans", sans-serif;
font-style: italic; font-style: italic;

View File

@ -6,6 +6,7 @@ import {unsafeHTML} from 'lit/directives/unsafe-html.js';
import { chatStyles } from './ChatScroller-css.js' import { chatStyles } from './ChatScroller-css.js'
import { Epml } from "../../../epml"; import { Epml } from "../../../epml";
import { cropAddress } from "../../utils/cropAddress"; import { cropAddress } from "../../utils/cropAddress";
import { roundToNearestDecimal } from '../../utils/roundToNearestDecimal.js';
import './LevelFounder.js'; import './LevelFounder.js';
import './NameMenu.js'; import './NameMenu.js';
import './ChatModals.js'; import './ChatModals.js';
@ -18,7 +19,9 @@ import '@material/mwc-button';
import '@material/mwc-dialog'; import '@material/mwc-dialog';
import '@material/mwc-icon'; import '@material/mwc-icon';
import { EmojiPicker } from 'emoji-picker-js'; import { EmojiPicker } from 'emoji-picker-js';
import { generateHTML } from '@tiptap/core' import { generateHTML } from '@tiptap/core';
import { saveAs } from 'file-saver';
import axios from "axios";
import StarterKit from '@tiptap/starter-kit' import StarterKit from '@tiptap/starter-kit'
import Underline from '@tiptap/extension-underline'; import Underline from '@tiptap/extension-underline';
import Highlight from '@tiptap/extension-highlight' import Highlight from '@tiptap/extension-highlight'
@ -82,7 +85,6 @@ class ChatScroller extends LitElement {
let timestamp; let timestamp;
let sender; let sender;
let repliedToData; let repliedToData;
let firstMessageInChat; let firstMessageInChat;
if (index === 0) { if (index === 0) {
@ -281,6 +283,7 @@ class MessageTemplate extends LitElement {
sendMessageForward: { attribute: false }, sendMessageForward: { attribute: false },
openDialogImage: { attribute: false }, openDialogImage: { attribute: false },
openDeleteImage: { type: Boolean }, openDeleteImage: { type: Boolean },
openDeleteAttachment: { type: Boolean },
isImageLoaded: { type: Boolean }, isImageLoaded: { type: Boolean },
isFirstMessage: { type: Boolean }, isFirstMessage: { type: Boolean },
isSingleMessageInGroup: { type: Boolean }, isSingleMessageInGroup: { type: Boolean },
@ -344,6 +347,22 @@ class MessageTemplate extends LitElement {
} }
} }
async downloadAttachment(attachment) {
const myNode = window.parent.reduxStore.getState().app.nodeConfig.knownNodes[window.parent.reduxStore.getState().app.nodeConfig.node];
const nodeUrl = myNode.protocol + '://' + myNode.domain + ':' + myNode.port;
try{
axios.get(`${nodeUrl}/arbitrary/QCHAT_ATTACHMENT/${attachment.name}/${attachment.identifier}?apiKey=${myNode.apiKey}`, { responseType: 'blob'})
.then(response =>{
let filename = attachment.attachmentName;
let blob = new Blob([response.data], { type:"application/octet-stream" });
saveAs(blob , filename);
})
} catch (error) {
console.error(error);
}
}
firstUpdated(){ firstUpdated(){
const autoSeeChatList = window.parent.reduxStore.getState().app?.autoLoadImageChats const autoSeeChatList = window.parent.reduxStore.getState().app?.autoLoadImageChats
if(autoSeeChatList.includes(this.chatId) || this.listSeenMessages.includes(this.messageObj.reference)){ if(autoSeeChatList.includes(this.chatId) || this.listSeenMessages.includes(this.messageObj.reference)){
@ -366,9 +385,11 @@ class MessageTemplate extends LitElement {
let repliedToData = null; let repliedToData = null;
let image = null; let image = null;
let isImageDeleted = false; let isImageDeleted = false;
let isAttachmentDeleted = false;
let version = 0; let version = 0;
let isForwarded = false let isForwarded = false
let isEdited = false let isEdited = false
let attachment = null;
try { try {
const parsedMessageObj = JSON.parse(this.messageObj.decodedMessage); const parsedMessageObj = JSON.parse(this.messageObj.decodedMessage);
if(parsedMessageObj.version.toString() === '2'){ if(parsedMessageObj.version.toString() === '2'){
@ -383,10 +404,14 @@ class MessageTemplate extends LitElement {
message = parsedMessageObj.messageText; message = parsedMessageObj.messageText;
repliedToData = this.messageObj.repliedToData; repliedToData = this.messageObj.repliedToData;
isImageDeleted = parsedMessageObj.isImageDeleted; isImageDeleted = parsedMessageObj.isImageDeleted;
isAttachmentDeleted = parsedMessageObj.isAttachmentDeleted;
reactions = parsedMessageObj.reactions || []; reactions = parsedMessageObj.reactions || [];
version = parsedMessageObj.version; version = parsedMessageObj.version;
isForwarded = parsedMessageObj.type === 'forward'; isForwarded = parsedMessageObj.type === 'forward';
isEdited = parsedMessageObj.isEdited && true; isEdited = parsedMessageObj.isEdited && true;
if (parsedMessageObj.attachments && Array.isArray(parsedMessageObj.attachments) && parsedMessageObj.attachments.length > 0) {
attachment = parsedMessageObj.attachments[0];
}
if (parsedMessageObj.images && Array.isArray(parsedMessageObj.images) && parsedMessageObj.images.length > 0) { if (parsedMessageObj.images && Array.isArray(parsedMessageObj.images) && parsedMessageObj.images.length > 0) {
image = parsedMessageObj.images[0]; image = parsedMessageObj.images[0];
} }
@ -443,12 +468,11 @@ class MessageTemplate extends LitElement {
const nodeUrl = myNode.protocol + '://' + myNode.domain + ':' + myNode.port; const nodeUrl = myNode.protocol + '://' + myNode.domain + ':' + myNode.port;
imageUrl = `${nodeUrl}/arbitrary/${image.service}/${image.name}/${image.identifier}?async=true&apiKey=${myNode.apiKey}`; imageUrl = `${nodeUrl}/arbitrary/${image.service}/${image.name}/${image.identifier}?async=true&apiKey=${myNode.apiKey}`;
if(this.viewImage || this.myAddress === this.messageObj.sender){ if (this.viewImage || this.myAddress === this.messageObj.sender) {
imageHTML = createImage(imageUrl); imageHTML = createImage(imageUrl);
imageHTMLDialog = createImage(imageUrl) imageHTMLDialog = createImage(imageUrl)
imageHTMLDialog.style= "height: auto; max-height: 80vh; width: auto; max-width: 80vw; object-fit: contain; border-radius: 5px"; imageHTMLDialog.style= "height: auto; max-height: 80vh; width: auto; max-width: 80vw; object-fit: contain; border-radius: 5px";
} }
} }
nameMenu = html` nameMenu = html`
@ -594,10 +618,9 @@ class MessageTemplate extends LitElement {
}} }}
class=${[`image-container`, !this.isImageLoaded ? 'defaultSize' : ''].join(' ')} class=${[`image-container`, !this.isImageLoaded ? 'defaultSize' : ''].join(' ')}
style=${this.isFirstMessage && "margin-top: 10px;"}> style=${this.isFirstMessage && "margin-top: 10px;"}>
<div style="display:flex;width:100%;height:100%;justify-content:center;align-items:center;cursor:pointer;color:var(--black)"> <div style="display:flex;width:100%;height:100%;justify-content:center;align-items:center;cursor:pointer;color:var(--black)">
${translate("chatpage.cchange40")} ${translate("chatpage.cchange40")}
</div> </div>
</div> </div>
` : html``} ` : html``}
${!this.isImageLoaded && image && this.viewImage ? html` ${!this.isImageLoaded && image && this.viewImage ? html`
@ -621,8 +644,53 @@ class MessageTemplate extends LitElement {
</div> </div>
` : image && isImageDeleted ? html` ` : image && isImageDeleted ? html`
<p class="image-deleted-msg">This image has been deleted</p> <p class="image-deleted-msg">${translate("chatpage.cchange80")}</p>
` : html``} ` : html``}
${attachment && !isAttachmentDeleted ?
html`
<div @click=${async () => await this.downloadAttachment(attachment)} class="attachment-container">
<div class="attachment-icon-container">
<img
src="/img/attachment-icon.png"
alt="attachment-icon"
class="attachment-icon" />
</div>
<div class="attachment-info">
<p class="attachment-name">
${attachment && attachment.attachmentName}
</p>
<p class="attachment-size">
${roundToNearestDecimal(attachment.attachmentSize)} mb
</p>
</div>
<vaadin-icon
icon="vaadin:download-alt"
slot="icon"
class="download-icon">
</vaadin-icon>
${this.myAddress === this.messageObj.sender
? html`
<vaadin-icon
@click=${(e) => {
e.stopPropagation();
this.openDeleteAttachment = true;
}}
class="image-delete-icon" icon="vaadin:close" slot="icon">
</vaadin-icon>
` : html``}
</div>
`
: attachment && isAttachmentDeleted ?
html`
<div class="attachment-container">
<div class="attachment-info">
<p style=${"font-style: italic;"} class="attachment-name">
${translate("chatpage.cchange82")}
</p>
</div>
</div>
`
: html``}
<div <div
id="messageContent" id="messageContent"
class="message" class="message"
@ -794,7 +862,7 @@ class MessageTemplate extends LitElement {
this.openDeleteImage = false; this.openDeleteImage = false;
}}> }}>
<div class="delete-image-msg"> <div class="delete-image-msg">
<p>Are you sure you want to delete this image?</p> <p>${translate("chatpage.cchange78")}</p>
</div> </div>
<div class="modal-button-row" @click=${() => this.openDeleteImage = false}> <div class="modal-button-row" @click=${() => this.openDeleteImage = false}>
<button class="modal-button-red"> <button class="modal-button-red">
@ -812,6 +880,34 @@ class MessageTemplate extends LitElement {
</button> </button>
</div> </div>
</mwc-dialog> </mwc-dialog>
<mwc-dialog
hideActions
?open=${this.openDeleteAttachment}
@closed=${()=> {
this.openDeleteAttachment = false;
}}>
<div class="delete-image-msg">
<p>${translate("chatpage.cchange79")}</p>
</div>
<div class="modal-button-row" @click=${() => this.openDeleteAttachment = false}>
<button class="modal-button-red">
Cancel
</button>
<button
class="modal-button"
@click=${() => {
this.sendMessage({
type: 'deleteAttachment',
attachment: attachment,
name: attachment.name,
identifier: attachment.identifier,
editedMessageObj: this.messageObj,
})}
}>
Yes
</button>
</div>
</mwc-dialog>
` `
} }
} }

View File

@ -15,6 +15,8 @@ class ChatTextEditor extends LitElement {
isLoadingMessages: { type: Boolean }, isLoadingMessages: { type: Boolean },
_sendMessage: { attribute: false }, _sendMessage: { attribute: false },
placeholder: { type: String }, placeholder: { type: String },
attachment: { type: Object },
insertFile: { attribute: false },
imageFile: { type: Object }, imageFile: { type: Object },
insertImage: { attribute: false }, insertImage: { attribute: false },
iframeHeight: { type: Number }, iframeHeight: { type: Number },
@ -481,16 +483,19 @@ mwc-checkbox::shadow .mdc-checkbox::after, mwc-checkbox::shadow .mdc-checkbox::b
</vaadin-icon> </vaadin-icon>
<div class="file-picker-input-container"> <div class="file-picker-input-container">
<input <input
@change="${e => { @change="${e => {
this.insertImage(e.target.files[0]); this.insertFile(e.target.files[0]);
const filePickerInput = this.shadowRoot.getElementById('file-picker') const filePickerInput = this.shadowRoot.getElementById('file-picker');
if(filePickerInput){ if (filePickerInput) {
filePickerInput.value = "" filePickerInput.value = "";
} }
} }
}" }"
id="file-picker" id="file-picker"
class="file-picker-input" type="file" name="myImage" accept="image/*" /> class="file-picker-input"
type="file"
name="myImage"
accept="image/*, .doc, .docx, .pdf, .zip, .pdf, .txt, .odt, .ods, .xls, .xlsx, .ppt, .pptx" />
</div> </div>
</div> </div>
<textarea style="color: var(--black);" tabindex='1' ?autofocus=${true} ?disabled=${this.isLoading || this.isLoadingMessages} id="messageBox" rows="1"></textarea> <textarea style="color: var(--black);" tabindex='1' ?autofocus=${true} ?disabled=${this.isLoading || this.isLoadingMessages} id="messageBox" rows="1"></textarea>
@ -523,7 +528,7 @@ mwc-checkbox::shadow .mdc-checkbox::after, mwc-checkbox::shadow .mdc-checkbox::b
html` html`
<div <div
style="margin-bottom: 10px; style="margin-bottom: 10px;
${this.iframeId === 'newChat' ${(this.iframeId === 'newChat' || this.iframeId === "newAttachmentChat")
? 'display: none;' ? 'display: none;'
: 'display: flex;'}"> : 'display: flex;'}">
${this.isLoading === false ? html` ${this.isLoading === false ? html`
@ -692,7 +697,20 @@ mwc-checkbox::shadow .mdc-checkbox::after, mwc-checkbox::shadow .mdc-checkbox::b
repliedTo: '', repliedTo: '',
version: 2 version: 2
}; };
} else { } else if (this.attachment && this.iframeId === 'newAttachmentChat') {
messageObject = {
messageText: trimmedMessage,
attachments: [{
service: "QCHAT_ATTACHMENT",
name: '123456789123456789123456789',
identifier: '123456',
attachmentName: "123456789123456789123456789",
attachmentSize: "123456"
}],
repliedTo: '',
version: 2
};
} else {
messageObject = { messageObject = {
messageText: trimmedMessage, messageText: trimmedMessage,
images: [''], images: [''],
@ -713,4 +731,4 @@ mwc-checkbox::shadow .mdc-checkbox::after, mwc-checkbox::shadow .mdc-checkbox::b
} }
window.customElements.define("chat-text-editor", ChatTextEditor) window.customElements.define("chat-text-editor", ChatTextEditor)

View File

@ -0,0 +1,4 @@
export function roundToNearestDecimal(num) {
const mb = num / 1000000;
return Math.round(mb * 10) / 10;
}