diff --git a/core/language/us.json b/core/language/us.json index 128d1c66..cb820354 100644 --- a/core/language/us.json +++ b/core/language/us.json @@ -833,7 +833,9 @@ "cchange82": "This attachment has been deleted", "cchange90": "No messages", "cchange91": "Sending...", - "cchange92": "Unread messages below" + "cchange92": "Unread messages below", + "cchange93": "Image copied to clipboard", + "cchange94": "loaded" }, "welcomepage": { "wcchange1": "Welcome to Q-Chat", diff --git a/plugins/plugins/core/components/ChatImage.js b/plugins/plugins/core/components/ChatImage.js index a0f2abb0..238404ba 100644 --- a/plugins/plugins/core/components/ChatImage.js +++ b/plugins/plugins/core/components/ChatImage.js @@ -9,8 +9,11 @@ import { } from 'lit-translate'; import axios from 'axios' import { RequestQueueWithPromise } from '../../utils/queue'; - +import '@material/mwc-menu'; +import '@material/mwc-list/mwc-list-item.js' +import { Epml } from '../../../epml'; const requestQueue = new RequestQueueWithPromise(5); +const parentEpml = new Epml({ type: 'WINDOW', source: window.parent }) export class ChatImage extends LitElement { static get properties() { @@ -24,11 +27,15 @@ export class ChatImage extends LitElement { static get styles() { return css` + * { + --mdc-theme-text-primary-on-background: var(--black); + } img { max-width:45vh; max-height:40vh; border-radius: 5px; cursor: pointer; + position: relative; } .smallLoading, .smallLoading:after { @@ -54,6 +61,10 @@ export class ChatImage extends LitElement { height: 40vh; } + mwc-menu { + position: absolute; + } + @@ -96,6 +107,7 @@ export class ChatImage extends LitElement { this.nodeUrl = this.getNodeUrl() this.myNode = this.getMyNode() this.hasCalledWhenDownloaded = false + this.isFetching = false this.observer = new IntersectionObserver(entries => { for (const entry of entries) { @@ -130,16 +142,14 @@ getMyNode(){ async fetchResource() { try { - // await qortalRequest({ - // action: 'GET_QDN_RESOURCE_PROPERTIES', - // name, - // service, - // identifier - // }) + if(this.isFetching) return + this.isFetching = true await axios.get(`${this.nodeUrl}/arbitrary/resource/properties/${this.resource.service}/${this.resource.name}/${this.resource.identifier}?apiKey=${this.myNode.apiKey}`) + this.isFetching = false - - } catch (error) {} + } catch (error) { + this.isFetching = false + } } async fetchVideoUrl() { @@ -200,7 +210,9 @@ getMyNode(){ } this.status = res - + if(this.status.status === 'DOWNLOADED'){ + this.fetchResource() + } } // check if progress is 100% and clear interval if true @@ -237,14 +249,70 @@ getMyNode(){ return true } - async updated(changedProperties) { - if (changedProperties && changedProperties.has('status')) { - if(this.hasCalledWhenDownloaded === false && this.status.status === 'DOWNLOADED'){ - this.fetchResource() - this.hasCalledWhenDownloaded = true + + + + showContextMenu(e) { + e.preventDefault(); + e.stopPropagation(); + + const contextMenu = this.shadowRoot.getElementById('contextMenu'); + const containerRect = e.currentTarget.getBoundingClientRect(); + + // Adjusting the positions + const adjustedX = e.clientX - containerRect.left; + const adjustedY = e.clientY - containerRect.top; + + contextMenu.style.top = `${adjustedY}px`; + contextMenu.style.left = `${adjustedX}px`; + + contextMenu.open = true; + } + + + + + + async handleCopy(e) { + e.stopPropagation(); + const image = this.shadowRoot.querySelector('img'); + + // Create a canvas and draw the image on it. + const canvas = document.createElement('canvas'); + canvas.width = image.naturalWidth; + canvas.height = image.naturalHeight; + const ctx = canvas.getContext('2d'); + ctx.drawImage(image, 0, 0); + + // Convert canvas image to blob + canvas.toBlob(blob => { + try { + const clipboardItem = new ClipboardItem({ 'image/png': blob }); + navigator.clipboard.write([clipboardItem]).then(() => { + const msg = get("chatpage.cchange93") + parentEpml.request('showSnackBar', msg) + console.log('Image copied to clipboard'); + }).catch(err => { + console.error('Failed to copy image: ', err); + }); + } catch (error) { + console.error('Error copying the image: ', error); } - } + }, 'image/png'); } + + + + + handleMenuBlur() { + setTimeout(() => { + if (!this.isMenuItemClicked) { + const contextMenu = this.shadowRoot.getElementById('contextMenu'); + contextMenu.open = false; + } + }, 100); +} + render() { @@ -269,17 +337,23 @@ getMyNode(){
-

${`${Math.round(this.status.percentLoaded || 0 - ).toFixed(0)}% loaded`}

+

${`${Math.round(this.status.percentLoaded || 0 + ).toFixed(0)}% `}${translate('chatpage.cchange94')}

` : '' } ${this.status.status === 'READY' ? html` - this.setOpenDialogImage(true)} src=${this.url} /> +
+ this.setOpenDialogImage(true)} src=${this.url} /> + + Copy + +
` : ''} + ` diff --git a/plugins/plugins/core/components/ChatPage.js b/plugins/plugins/core/components/ChatPage.js index 8ccd94f0..b3bae116 100644 --- a/plugins/plugins/core/components/ChatPage.js +++ b/plugins/plugins/core/components/ChatPage.js @@ -2171,6 +2171,7 @@ class ChatPage extends LitElement { } } } + async pasteImage(e) { const event = e @@ -2185,7 +2186,7 @@ class ChatPage extends LitElement { if (event.clipboardData) { const blobFound = handleTransferIntoURL(event.clipboardData) if (blobFound) { - this.insertImage(blobFound) + this.insertFile(blobFound) return } else { const item_list = await navigator.clipboard.read() @@ -2204,7 +2205,7 @@ class ChatPage extends LitElement { let file = new File([blob], "name", { type: image_type }) - this.insertImage(file) + this.insertFile(file) } catch (error) { console.error(error) let errorMsg = get("chatpage.cchange81") diff --git a/plugins/plugins/core/components/ChatScroller-css.js b/plugins/plugins/core/components/ChatScroller-css.js index 7c337d70..2aad505d 100644 --- a/plugins/plugins/core/components/ChatScroller-css.js +++ b/plugins/plugins/core/components/ChatScroller-css.js @@ -756,9 +756,9 @@ export const chatStyles = css` .unread-divider { width: 100%; - background: #9B111E; padding: 5px; - color: #FAEBD7; + color: var(--black); + border-bottom: 1px solid var(--black); display: flex; justify-content: center; border-radius: 2px; diff --git a/plugins/plugins/core/components/ChatTextEditor.js b/plugins/plugins/core/components/ChatTextEditor.js index 85e6d749..4d4c6b75 100644 --- a/plugins/plugins/core/components/ChatTextEditor.js +++ b/plugins/plugins/core/components/ChatTextEditor.js @@ -502,7 +502,11 @@ mwc-checkbox::shadow .mdc-checkbox::after, mwc-checkbox::shadow .mdc-checkbox::b accept="image/*, .doc, .docx, .pdf, .zip, .pdf, .txt, .odt, .ods, .xls, .xlsx, .ppt, .pptx" /> - +
@@ -572,6 +576,29 @@ mwc-checkbox::shadow .mdc-checkbox::after, mwc-checkbox::shadow .mdc-checkbox::b } } + async handlePasteEvent(e) { + if (e.type === 'paste') { + e.preventDefault(); + const item_list = await navigator.clipboard.read(); + let image_type; // we will feed this later + const item = item_list.find( item => // choose the one item holding our image + item.types.some( type => { + if (type.startsWith( 'image/')) { + image_type = type; + return true; + } + }) + ); + if(item){ + const blob = item && await item.getType( image_type ); + var file = new File([blob], "name", { + type: image_type + }); + this.insertFile(file); + } + } + } + async firstUpdated() { window.addEventListener('storage', () => { const checkTheme = localStorage.getItem('qortalTheme'); diff --git a/plugins/plugins/core/messaging/q-chat/q-chat.src.js b/plugins/plugins/core/messaging/q-chat/q-chat.src.js index 9edcaa76..2e0d02f3 100644 --- a/plugins/plugins/core/messaging/q-chat/q-chat.src.js +++ b/plugins/plugins/core/messaging/q-chat/q-chat.src.js @@ -445,9 +445,24 @@ class Chat extends LitElement { if (!isElectron()) { } else { window.addEventListener('contextmenu', (event) => { - event.preventDefault() - window.parent.electronAPI.showMyMenu() - }) + // Check if the clicked element has the class + let target = event.target; + while (target !== null) { + if (target.classList && target.classList.contains('customContextMenuDiv')) { + // Your custom context menu logic + this.showContextMenu(event); + return; + } + target = target.parentNode; + } + + // If it doesn't, show the default Electron context menu + event.preventDefault(); + window.parent.electronAPI.showMyMenu(); + }); + + + } let configLoaded = false