Browse Source

UI Done for the image caption modal

pull/127/head
Justin Ferrari 2 years ago
parent
commit
9c021cca5c
  1. 3
      qortal-ui-core/language/us.json
  2. 241
      qortal-ui-plugins/plugins/core/components/ChatPage.js
  3. 112
      qortal-ui-plugins/plugins/core/components/ChatScroller.js
  4. 5
      qortal-ui-plugins/plugins/utils/publish-image.js

3
qortal-ui-core/language/us.json

@ -487,7 +487,8 @@
"cchange25": "Edit Message",
"cchange26": "File size exceeds 5 MB",
"cchange27": "A registered name is required to send images",
"cchange28": "This file is not an image"
"cchange28": "This file is not an image",
"cchange29": "Cancel"
},
"welcomepage": {
"wcchange1": "Welcome to Q-Chat",

241
qortal-ui-plugins/plugins/core/components/ChatPage.js

@ -56,12 +56,19 @@ class ChatPage extends LitElement {
editedMessageObj: { type: Object },
iframeHeight: { type: Number },
chatMessageSize: { type: Number },
imageFile: {type: Object}
imageFile: { type: Object },
caption: { type: String }
}
}
static get styles() {
return css`
* {
/* Styling mdc dialog native props */
--mdc-dialog-min-width: 300px;
--mdc-dialog-box-shadow:rgba(60, 64, 67, 0.3) 0px 1px 2px 0px, rgba(60, 64, 67, 0.15) 0px 2px 6px 2px;
}
html {
scroll-behavior: smooth;
}
@ -194,6 +201,41 @@ class ChatPage extends LitElement {
color: var(--black);
}
.emoji-button-caption {
width: 45px;
height: 40px;
padding-top: 4px;
border: none;
outline: none;
background: transparent;
cursor: pointer;
max-height: 40px;
color: var(--black);
}
.caption-container {
width: 100%;
display: flex;
height: auto;
overflow: hidden;
justify-content: center;
}
.chatbar-caption {
font-family: Roboto, sans-serif;
width: 70%;
margin-right: 10px;
outline: none;
align-items: center;
font-size: 18px;
resize: none;
border-top: 0;
border-right: 0;
border-left: 0;
border-bottom: 1px solid #cac8c8;
padding: 3px;
}
.message-size-container {
display: flex;
justify-content: flex-end;
@ -335,12 +377,45 @@ class ChatPage extends LitElement {
cursor: pointer;
}
.mdc-dialog .mdc-dialog__surface {
border-radius: 10px;
}
/* Add Image Modal Dialog Styling */
.dialog-container {
position: relative;
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
height: 100%;
padding: 18px 18px 0 18px;
gap: 15px;
}
.dialog-container:after {
position: absolute;
content: "";
bottom: -15px;
height: 1px;
width: 95%;
background: #dddddd;
}
.dialog-image {
width: 100%;
border-radius: 0;
}
.red {
--mdc-theme-primary: #F44336;
}
`
}
constructor() {
super()
this.getMessageConfig = this.getMessageConfig.bind(this)
this.getOldMessage = this.getOldMessage.bind(this)
this._sendMessage = this._sendMessage.bind(this)
this.insertImage = this.insertImage.bind(this)
@ -371,6 +446,7 @@ class ChatPage extends LitElement {
this.chatMessageSize = 0
this.imageFile = null
this.uid = new ShortUniqueId()
this.caption = ""
}
render() {
@ -392,39 +468,69 @@ class ChatPage extends LitElement {
</div>
` :
this.renderChatScroller(this._initialMessages)}
<mwc-dialog id="showDialogPublicKey" ?open=${this.imageFile} @closed=${()=> {
this.imageFile = null
<mwc-dialog
id="showDialogPublicKey"
?open=${this.imageFile}
@closed=${()=> {
this.chatEditor.enable();
this.caption = "";
this.imageFile = null;
}}>
<div class="dialog-header"></div>
<div class="dialog-container">
<div class="dialog-container mdc-dialog mdc-dialog__surface">
${this.imageFile && html`
<img src=${URL.createObjectURL(this.imageFile)} />
<img src=${URL.createObjectURL(this.imageFile)} alt="dialog-img" class="dialog-image" />
`}
<!-- Replace by reusable chatbar component -->
<div class="caption-container">
<textarea @change=${(e) => this.onCaptionChange(e.target.value)} .value=${this.caption} placeholder="Caption" class="chatbar-caption" tabindex='1' rows="1"></textarea>
<div style="display:flex; ${this.chatMessageInput && this.chatMessageInput.contentDocument.body.scrollHeight > 60 ? 'margin-bottom: 5px' : "margin-bottom: 0"}">
${this.isLoading === false ? html`
<img
src="/img/qchat-send-message-icon.svg"
alt="send-icon"
class="send-icon"
@click=${() => this._sendMessage()} />
` :
html`
<paper-spinner-lite active></paper-spinner-lite>
`}
</div>
</div>
${this.chatMessageSize >= 750 ?
html`
<div class="message-size-container">
<div class="message-size" style="${this.chatMessageSize >= 1000 && 'color: #bd1515'}">
${`Your message size is of ${this.chatMessageSize} bytes out of a maximum of 1000`}
</div>
</div>
` :
html``}
</div>
<mwc-button
slot="primaryAction"
dialogAction="cancel"
class="red"
@click=${()=>{
this._sendMessage({
type: 'image',
imageFile: this.imageFile,
caption: 'This is a caption'
})
this.imageFile = null
}}
>
send
${translate("chatpage.cchange29")}
</mwc-button>
<mwc-button
slot="primaryAction"
dialogAction="cancel"
class="red"
@click=${()=> {
this.imageFile = null
// this._sendMessage({
// type: 'image',
// imageFile: this.imageFile,
// caption: 'This is a caption'
// })
console.log(this.caption);
}}
>
${translate("general.close")}
${translate("chatpage.cchange9")}
</mwc-button>
</mwc-dialog>
</div>
@ -483,7 +589,7 @@ class ChatPage extends LitElement {
</div>
</div>
<textarea style="color: var(--black);" tabindex='1' ?autofocus=${true} ?disabled=${this.isLoading || this.isLoadingMessages} id="messageBox" rows="1"></textarea>
<iframe style="${`height: ${this.iframeHeight}px`}" class="chat-editor" id="_chatEditorDOM" tabindex="-1" height=${this.iframeHeight}>
<iframe id="_chatEditorDOM" style="${`height: ${this.iframeHeight}px`}" class="chat-editor" id="_chatEditorDOM" tabindex="-1" height=${this.iframeHeight}>
</iframe>
<button class="emoji-button" ?disabled=${this.isLoading || this.isLoadingMessages}>
${html`<img class="emoji" draggable="false" alt="😀" src="/emoji/svg/1f600.svg" />`}
@ -539,13 +645,11 @@ class ChatPage extends LitElement {
`
}
insertImage(file){
if(file.type.includes('image')){
this.imageFile = file
this.chatEditor.disable();
return
}
@ -553,8 +657,6 @@ class ChatPage extends LitElement {
}
async firstUpdated() {
// TODO: Load and fetch messages from localstorage (maybe save messages to localstorage...)
@ -562,8 +664,6 @@ class ChatPage extends LitElement {
this.emojiPickerHandler = this.shadowRoot.querySelector('.emoji-button');
this.mirrorChatInput = this.shadowRoot.getElementById('messageBox');
this.chatMessageInput = this.shadowRoot.getElementById('_chatEditorDOM');
console.log(this.chatMessageInput);
console.log(this.mirrorChatInput.clientHeight);
document.addEventListener('keydown', (e) => {
if (!this.chatEditor.content.body.matches(':focus')) {
// WARNING: Deprecated methods from KeyBoard Event
@ -681,12 +781,20 @@ class ChatPage extends LitElement {
if (changedProperties && changedProperties.has('chatMessageSize')) {
console.log(this.chatMessageSize, "Chat Message Size");
}
if(changedProperties && changedProperties.has("imageFile")) {
this.chatbarCaption = this.shadowRoot.querySelector('.chatbar-caption');
this.chatbarCaption.focus();
}
}
calculateIFrameHeight(height) {
this.iframeHeight = height;
}
onCaptionChange(e) {
this.caption = e;
}
changeLanguage() {
const checkLanguage = localStorage.getItem('qortalLanguage')
@ -803,8 +911,6 @@ class ChatPage extends LitElement {
if (isInitial) {
const replacedMessages = await replaceMessagesEdited({
decodedMessages: decodedMessages,
parentEpml,
@ -818,17 +924,11 @@ class ChatPage extends LitElement {
- b.timestamp
})
// TODO: Determine number of initial messages by screen height...
this._initialMessages = this._messages
this.messagesRendered = this._initialMessages
this.isLoadingMessages = false
setTimeout(() => this.downElementObserver(), 500)
this._initialMessages = this._messages;
this.messagesRendered = this._initialMessages;
this.isLoadingMessages = false;
setTimeout(() => this.downElementObserver(), 500);
} else {
const replacedMessages = await replaceMessagesEdited({
decodedMessages: decodedMessages,
@ -838,28 +938,19 @@ class ChatPage extends LitElement {
_publicKey: this._publicKey
})
const renderEachMessage = replacedMessages.map(async(msg)=> {
await this.renderNewMessage(msg)
})
await Promise.all(renderEachMessage)
// this.newMessages = this.newMessages.concat(_newMessages)
this.messagesRendered = [...this.messagesRendered].sort(function (a, b) {
return a.timestamp
- b.timestamp
})
}
}
getMessageConfig() {
const textAreaElement = this.shadowRoot.getElementById('messageBox');
return textAreaElement.value;
}
getMessageSize(message){
try {
const messageText = message;
@ -1031,41 +1122,39 @@ class ChatPage extends LitElement {
*
*/
decodeMessage(encodedMessageObj, isReceipient, _publicKey ) {
let isReceipientVar
let _publicKeyVar
let isReceipientVar;
let _publicKeyVar;
try {
isReceipientVar = this.isReceipient === undefined ? isReceipient : this.isReceipient;
_publicKeyVar = this._publicKey === undefined ? _publicKey : this._publicKey
_publicKeyVar = this._publicKey === undefined ? _publicKey : this._publicKey;
} catch (error) {
isReceipientVar = isReceipient
_publicKeyVar = _publicKey
isReceipientVar = isReceipient;
_publicKeyVar = _publicKey;
}
let decodedMessageObj = {}
let decodedMessageObj = {};
if (isReceipientVar === true) {
// direct chat
if (encodedMessageObj.isEncrypted === true && _publicKeyVar.hasPubKey === true && encodedMessageObj.data) {
let decodedMessage = window.parent.decryptChatMessage(encodedMessageObj.data, window.parent.reduxStore.getState().app.selectedAddress.keyPair.privateKey, _publicKeyVar.key, encodedMessageObj.reference)
decodedMessageObj = { ...encodedMessageObj, decodedMessage }
let decodedMessage = window.parent.decryptChatMessage(encodedMessageObj.data, window.parent.reduxStore.getState().app.selectedAddress.keyPair.privateKey, _publicKeyVar.key, encodedMessageObj.reference);
decodedMessageObj = { ...encodedMessageObj, decodedMessage };
} else if (encodedMessageObj.isEncrypted === false && encodedMessageObj.data) {
let bytesArray = window.parent.Base58.decode(encodedMessageObj.data)
let decodedMessage = new TextDecoder('utf-8').decode(bytesArray)
decodedMessageObj = { ...encodedMessageObj, decodedMessage }
let bytesArray = window.parent.Base58.decode(encodedMessageObj.data);
let decodedMessage = new TextDecoder('utf-8').decode(bytesArray);
decodedMessageObj = { ...encodedMessageObj, decodedMessage };
} else {
decodedMessageObj = { ...encodedMessageObj, decodedMessage: "Cannot Decrypt Message!" }
decodedMessageObj = { ...encodedMessageObj, decodedMessage: "Cannot Decrypt Message!" };
}
} else {
// group chat
let bytesArray = window.parent.Base58.decode(encodedMessageObj.data)
let decodedMessage = new TextDecoder('utf-8').decode(bytesArray)
decodedMessageObj = { ...encodedMessageObj, decodedMessage }
let bytesArray = window.parent.Base58.decode(encodedMessageObj.data);
let decodedMessage = new TextDecoder('utf-8').decode(bytesArray);
decodedMessageObj = { ...encodedMessageObj, decodedMessage };
}
return decodedMessageObj
return decodedMessageObj;
}
async fetchChatMessages(chatId) {
@ -1353,13 +1442,13 @@ class ChatPage extends LitElement {
selectedAddress: this.selectedAddress
})
} catch (error) {
console.error(error)
console.error(error);
}
typeMessage = 'edit';
let chatReference = outSideMsg.editedMessageObj.reference;
if(outSideMsg.editedMessageObj.chatReference){
chatReference = outSideMsg.editedMessageObj.chatReference
chatReference = outSideMsg.editedMessageObj.chatReference;
}
let message = "";
@ -1389,8 +1478,8 @@ class ChatPage extends LitElement {
return;
}
const id = this.uid();
const identifier = `qchat_${id}`
let compressedFile = ''
const identifier = `qchat_${id}`;
let compressedFile = '';
await new Promise(resolve => {
new Compressor( outSideMsg.imageFile, {
quality: .6,
@ -1408,15 +1497,13 @@ class ChatPage extends LitElement {
})
})
const fileSize = compressedFile.size
const fileSize = compressedFile.size;
if (fileSize > 5000000) {
parentEpml.request('showSnackBar', get("chatpage.cchange26"))
parentEpml.request('showSnackBar', get("chatpage.cchange26"));
this.isLoading = false;
this.chatEditor.enable();
return
return;
}
console.log({userName, identifier })
await publishData({
registeredName: userName,
file : compressedFile,
@ -1427,6 +1514,7 @@ class ChatPage extends LitElement {
uploadType: 'file',
selectedAddress: this.selectedAddress
})
const messageObject = {
messageText: outSideMsg.caption,
images: [{
@ -1441,8 +1529,6 @@ class ChatPage extends LitElement {
const stringifyMessageObject = JSON.stringify(messageObject)
this.sendMessage(stringifyMessageObject, typeMessage);
} else if(outSideMsg && outSideMsg.type === 'reaction'){
typeMessage = 'edit'
let chatReference = outSideMsg.editedMessageObj.reference
@ -1857,7 +1943,6 @@ class ChatPage extends LitElement {
for (let i = 0; i < events.length; i++) {
const event = events[i]
editor.content.body.addEventListener(event, async function (e) {
console.log("hello world12")
if (e.type === 'click') {
e.preventDefault();
e.stopPropagation();
@ -1900,12 +1985,9 @@ class ChatPage extends LitElement {
}
if (e.type === 'keydown') {
console.log("key pressed");
console.log(editorConfig.getMessageConfig(), "this is the chat input value");
console.log(editorConfig.editableElement.contentDocument.body.scrollHeight, "scroll height")
console.log(editorConfig.mirrorElement.clientHeight, "client height")
editorConfig.calculateIFrameHeight(editorConfig.editableElement.contentDocument.body.scrollHeight);
editorConfig.getMessageSize(editorConfig.editableElement.contentDocument.body.innerHTML);
// Handle Enter
if (e.keyCode === 13 && !e.shiftKey) {
@ -1985,7 +2067,6 @@ class ChatPage extends LitElement {
};
const editorConfig = {
getMessageConfig: this.getMessageConfig,
getMessageSize: this.getMessageSize,
calculateIFrameHeight: this.calculateIFrameHeight,
mirrorElement: this.mirrorChatInput,

112
qortal-ui-plugins/plugins/core/components/ChatScroller.js

@ -210,41 +210,39 @@ class MessageTemplate extends LitElement {
render() {
const hidemsg = this.hideMessages
let message = ""
let reactions = []
let repliedToData = null
let image = null
let isImageDeleted = false
const hidemsg = this.hideMessages;
let message = "";
let reactions = [];
let repliedToData = null;
let image = null;
let isImageDeleted = false;
try {
const parsedMessageObj = JSON.parse(this.messageObj.decodedMessage)
message = parsedMessageObj.messageText
repliedToData = this.messageObj.repliedToData
isImageDeleted = parsedMessageObj.isImageDeleted
reactions = parsedMessageObj.reactions || []
const parsedMessageObj = JSON.parse(this.messageObj.decodedMessage);
message = parsedMessageObj.messageText;
repliedToData = this.messageObj.repliedToData;
isImageDeleted = parsedMessageObj.isImageDeleted;
reactions = parsedMessageObj.reactions || [];
if(parsedMessageObj.images && Array.isArray(parsedMessageObj.images) && parsedMessageObj.images.length > 0){
image = parsedMessageObj.images[0]
image = parsedMessageObj.images[0];
}
} catch (error) {
message = this.messageObj.decodedMessage
message = this.messageObj.decodedMessage;
}
let avatarImg = ''
let imageHTML = ''
let imageHTMLDialog = ''
let imageUrl = ''
let nameMenu = ''
let levelFounder = ''
let hideit = hidemsg.includes(this.messageObj.sender)
let avatarImg = '';
let imageHTML = '';
let imageHTMLDialog = '';
let imageUrl = '';
let nameMenu = '';
let levelFounder = '';
let hideit = hidemsg.includes(this.messageObj.sender);
levelFounder = html`<level-founder checkleveladdress="${this.messageObj.sender}"></level-founder>`
levelFounder = html`<level-founder checkleveladdress="${this.messageObj.sender}"></level-founder>`;
if (this.messageObj.senderName) {
const myNode = window.parent.reduxStore.getState().app.nodeConfig.knownNodes[window.parent.reduxStore.getState().app.nodeConfig.node]
const nodeUrl = myNode.protocol + '://' + myNode.domain + ':' + myNode.port
const avatarUrl = `${nodeUrl}/arbitrary/THUMBNAIL/${this.messageObj.senderName}/qortal_avatar?async=true&apiKey=${myNode.apiKey}`
avatarImg = html`<img src="${avatarUrl}" style="max-width:100%; max-height:100%;" onerror="this.onerror=null; this.src='/img/incognito.png';" />`
const myNode = window.parent.reduxStore.getState().app.nodeConfig.knownNodes[window.parent.reduxStore.getState().app.nodeConfig.node];
const nodeUrl = myNode.protocol + '://' + myNode.domain + ':' + myNode.port;
const avatarUrl = `${nodeUrl}/arbitrary/THUMBNAIL/${this.messageObj.senderName}/qortal_avatar?async=true&apiKey=${myNode.apiKey}`;
avatarImg = html`<img src="${avatarUrl}" style="max-width:100%; max-height:100%;" onerror="this.onerror=null; this.src='/img/incognito.png';" />`;
} else {
avatarImg = html`<img src='/img/incognito.png' style="max-width:100%; max-height:100%;" onerror="this.onerror=null;" />`
}
@ -252,58 +250,47 @@ class MessageTemplate extends LitElement {
const createImage=(imageUrl)=>{
const imageHTMLRes = new Image();
imageHTMLRes.src = imageUrl;
imageHTMLRes.style= "max-width:45vh; max-height:40vh; border-radius: 5px; cursor: pointer"
imageHTMLRes.style= "max-width:45vh; max-height:40vh; border-radius: 5px; cursor: pointer";
imageHTMLRes.onclick= () => {
this.openDialogImage = true
}
};
imageHTMLRes.onerror = () => {
console.log('inputRef', this.imageFetches)
if (this.imageFetches < 4) {
setTimeout(()=> {
this.imageFetches = this.imageFetches + 1
imageHTMLRes.src = imageUrl
}, 500)
this.imageFetches = this.imageFetches + 1;
imageHTMLRes.src = imageUrl;
}, 500);
} else {
imageHTMLRes.src = '/img/chain.png'
imageHTMLRes.src = '/img/chain.png';
imageHTMLRes.style= "max-width:45vh; max-height:20vh; border-radius: 5px; filter: opacity(0.5)";
imageHTMLRes.onclick= () => {
}
}
}
return imageHTMLRes
};
return imageHTMLRes;
}
if (image) {
const myNode = window.parent.reduxStore.getState().app.nodeConfig.knownNodes[window.parent.reduxStore.getState().app.nodeConfig.node]
const nodeUrl = myNode.protocol + '://' + myNode.domain + ':' + myNode.port
imageUrl = `${nodeUrl}/arbitrary/${image.service}/${image.name}/${image.identifier}?async=true&apiKey=${myNode.apiKey}`
imageHTML = createImage(imageUrl)
imageHTMLDialog = createImage(imageUrl)
imageHTMLDialog.style= "height: auto; max-height: 80vh; width: auto; max-width: 80vw; object-fit: contain; border-radius: 5px"
const myNode = window.parent.reduxStore.getState().app.nodeConfig.knownNodes[window.parent.reduxStore.getState().app.nodeConfig.node];
const nodeUrl = myNode.protocol + '://' + myNode.domain + ':' + myNode.port;
imageUrl = `${nodeUrl}/arbitrary/${image.service}/${image.name}/${image.identifier}?async=true&apiKey=${myNode.apiKey}`;
imageHTML = createImage(imageUrl);
imageHTMLDialog = createImage(imageUrl);
imageHTMLDialog.style= "height: auto; max-height: 80vh; width: auto; max-width: 80vw; object-fit: contain; border-radius: 5px";
}
if (this.messageObj.sender === this.myAddress) {
nameMenu = html`<span style="color: #03a9f4;">${this.messageObj.senderName ? this.messageObj.senderName : this.messageObj.sender}</span>`
nameMenu = html`<span style="color: #03a9f4;">${this.messageObj.senderName ? this.messageObj.senderName : this.messageObj.sender}</span>`;
} else {
nameMenu = html`<span>${this.messageObj.senderName ? this.messageObj.senderName : this.messageObj.sender}</span>`
nameMenu = html`<span>${this.messageObj.senderName ? this.messageObj.senderName : this.messageObj.sender}</span>`;
}
if (repliedToData) {
try {
const parsedMsg = JSON.parse(repliedToData.decodedMessage)
repliedToData.decodedMessage = parsedMsg
const parsedMsg = JSON.parse(repliedToData.decodedMessage);
repliedToData.decodedMessage = parsedMsg;
} catch (error) {
console.error(error);
}
}
return hideit ? html`<li class="clearfix"></li>` : html`
@ -332,7 +319,6 @@ class MessageTemplate extends LitElement {
name: image.name,
identifier: image.identifier,
editedMessageObj: this.messageObj,
})}
class="image-delete-icon" icon="vaadin:close" slot="icon"></vaadin-icon>
</div>
@ -385,16 +371,16 @@ class MessageTemplate extends LitElement {
toblockaddress=${this.messageObj.sender}
>
</chat-modals>
<mwc-dialog id="showDialogPublicKey" ?open=${this.openDialogImage} @closed=${()=> {
<mwc-dialog
id="showDialogPublicKey"
?open=${this.openDialogImage}
@closed=${()=> {
this.openDialogImage = false
}}>
<div class="dialog-header" >
</div>
<div class="dialog-header"></div>
<div class="dialog-container imageContainer">
${imageHTMLDialog}
</div>
<mwc-button
slot="primaryAction"
dialogAction="cancel"

5
qortal-ui-plugins/plugins/utils/publish-image.js

@ -156,11 +156,6 @@ export const publishData = async ({
})
return uploadDataRes
}
}
await validate()
}

Loading…
Cancel
Save