From aea4c876b86d01c3ffee29774b92edb47f891d96 Mon Sep 17 00:00:00 2001 From: Phillip Date: Fri, 20 Jan 2023 15:07:17 +0200 Subject: [PATCH 01/16] able to save zip file to qdn --- qortal-ui-plugins/package.json | 7 +- .../plugins/core/components/ChatPage.js | 94 ++++++++++++++++++- .../plugins/utils/publish-image.js | 11 ++- 3 files changed, 106 insertions(+), 6 deletions(-) diff --git a/qortal-ui-plugins/package.json b/qortal-ui-plugins/package.json index 7e6583d1..5100b14e 100644 --- a/qortal-ui-plugins/package.json +++ b/qortal-ui-plugins/package.json @@ -21,15 +21,17 @@ "@material/mwc-list": "0.27.0", "@material/mwc-select": "0.27.0", "@tiptap/core": "2.0.0-beta.209", + "@tiptap/extension-highlight": "2.0.0-beta.209", "@tiptap/extension-image": "2.0.0-beta.209", "@tiptap/extension-placeholder": "2.0.0-beta.209", "@tiptap/extension-underline": "2.0.0-beta.209", - "@tiptap/extension-highlight": "2.0.0-beta.209", "@tiptap/html": "2.0.0-beta.209", "@tiptap/starter-kit": "2.0.0-beta.209", + "@zip.js/zip.js": "2.6.62", "asmcrypto.js": "2.3.2", "compressorjs": "1.1.1", "emoji-picker-js": "https://github.com/Qortal/emoji-picker-js", + "localforage": "1.10.0", "prosemirror-commands": "1.5.0", "prosemirror-dropcursor": "1.6.1", "prosemirror-gapcursor": "1.3.1", @@ -40,7 +42,6 @@ "prosemirror-state": "1.4.2", "prosemirror-transform": "1.7.0", "prosemirror-view": "1.29.1", - "localforage": "1.10.0", "short-unique-id": "4.4.4" }, "devDependencies": { @@ -88,4 +89,4 @@ "engines": { "node": ">=16.17.1" } -} \ No newline at end of file +} diff --git a/qortal-ui-plugins/plugins/core/components/ChatPage.js b/qortal-ui-plugins/plugins/core/components/ChatPage.js index 1274da7e..0ade923f 100644 --- a/qortal-ui-plugins/plugins/core/components/ChatPage.js +++ b/qortal-ui-plugins/plugins/core/components/ChatPage.js @@ -9,6 +9,8 @@ import Placeholder from '@tiptap/extension-placeholder' import Highlight from '@tiptap/extension-highlight' import {unsafeHTML} from 'lit/directives/unsafe-html.js'; import { Editor, Extension } from '@tiptap/core' +import * as zip from "@zip.js/zip.js"; +import { saveAs } from 'file-saver'; // import localForage from "localforage"; registerTranslateConfig({ @@ -103,7 +105,8 @@ class ChatPage extends LitElement { openUserInfo: { type: Boolean }, selectedHead: { type: Object }, userName: { type: String }, - goToRepliedMessage: {attribute: false} + goToRepliedMessage: {attribute: false}, + gifsToBeAdded: { type: Array} } } @@ -891,6 +894,7 @@ class ChatPage extends LitElement { this.currentEditor = '_chatEditorDOM' this.initialChat = this.initialChat.bind(this) this.isEnabledChatEnter = true + this.gifsToBeAdded = [] } _toggle(value) { @@ -923,6 +927,66 @@ class ChatPage extends LitElement { localStorage.setItem('isEnabledChatEnter', !this.isEnabledChatEnter ) this.isEnabledChatEnter = !this.isEnabledChatEnter } + + addGifs(gifs){ + console.log('gifs', gifs) + this.gifsToBeAdded = [...this.gifsToBeAdded, ...gifs] + console.log('this.gifsToBeAdded', this.gifsToBeAdded) + } + + async uploadGifCollection(){ + try { + function blobToBase64(blob) { + return new Promise((resolve, _) => { + const reader = new FileReader(); + reader.onloadend = () => resolve(reader.result); + reader.readAsDataURL(blob); + }); + } + const zipFileWriter = new zip.BlobWriter("application/zip"); +// Creates a TextReader object storing the text of the entry to add in the zip +// (i.e. "Hello world!"). +const helloWorldReader = new zip.TextReader("Hello world!"); + +// Creates a ZipWriter object writing data via `zipFileWriter`, adds the entry +// "hello.txt" containing the text "Hello world!" via `helloWorldReader`, and +// closes the writer. +const file = this.gifsToBeAdded[0] +const file2 = this.gifsToBeAdded[1] +const zipWriter = new zip.ZipWriter(zipFileWriter, { bufferedWrite: true }); +await zipWriter.add(file.name, new zip.BlobReader(file)); +await zipWriter.add(file2.name, new zip.BlobReader(file2)); + +await zipWriter.close(); +const zipFileBlob = await zipFileWriter.getData() +const blobTobase = await blobToBase64(zipFileBlob) +console.log({blobTobase}) +const userName = await this.getName(this.selectedAddress.address); + if (!userName) { + parentEpml.request('showSnackBar', get("chatpage.cchange27")); + this.isLoading = false; + return; + } + const id = this.uid(); + const identifier = `gif_${id}`; +await publishData({ + registeredName: userName, + file : blobTobase.split(',')[1], + service: 'GIF_REPOSITORY', + identifier: identifier, + parentEpml, + metaData: undefined, + uploadType: 'zip', + selectedAddress: this.selectedAddress, + worker: this.webWorkerImage, + isBase64: true + }) +saveAs(zipFileBlob, 'zipfile'); +console.log({zipFileBlob}) + } catch (error) { + console.log(error) + } + } render() { return html` @@ -1110,6 +1174,34 @@ class ChatPage extends LitElement { + + { + // this.removeImage(); + }} + style=${true ? "visibility:visible;z-index:50" : "visibility: hidden;z-index:-100"}> +
+
+ + + + +
+
+

${translate("chatpage.cchange41")}


diff --git a/qortal-ui-plugins/plugins/utils/publish-image.js b/qortal-ui-plugins/plugins/utils/publish-image.js index 05594d1c..5835aa33 100644 --- a/qortal-ui-plugins/plugins/utils/publish-image.js +++ b/qortal-ui-plugins/plugins/utils/publish-image.js @@ -16,7 +16,8 @@ export const publishData = async ({ parentEpml, uploadType, selectedAddress, - worker + worker, + isBase64 }) => { const validateName = async (receiverName) => { let nameRes = await parentEpml.request("apiCall", { @@ -115,11 +116,17 @@ export const publishData = async ({ } // Base64 encode the file to work around compatibility issues between javascript and java byte arrays + if(isBase64){ + postBody = file + } + if(!isBase64){ let fileBuffer = new Uint8Array(await file.arrayBuffer()) postBody = Buffer.from(fileBuffer).toString("base64") + } + } - + console.log({postBody}) let uploadDataUrl = `/arbitrary/${service}/${registeredName}${urlSuffix}?apiKey=${getApiKey()}` if (identifier != null && identifier.trim().length > 0) { From 281e063365eea9af7973343174a53674afe3dcc7 Mon Sep 17 00:00:00 2001 From: Phillip Date: Sat, 21 Jan 2023 16:55:48 +0200 Subject: [PATCH 02/16] create separate component --- .../plugins/core/components/ChatGifs.js | 276 ++++++++++++++++++ .../plugins/core/components/ChatPage.js | 52 ++-- .../plugins/core/components/ChatTextEditor.js | 10 +- .../plugins/utils/publish-image.js | 9 +- 4 files changed, 313 insertions(+), 34 deletions(-) create mode 100644 qortal-ui-plugins/plugins/core/components/ChatGifs.js diff --git a/qortal-ui-plugins/plugins/core/components/ChatGifs.js b/qortal-ui-plugins/plugins/core/components/ChatGifs.js new file mode 100644 index 00000000..3ddb8017 --- /dev/null +++ b/qortal-ui-plugins/plugins/core/components/ChatGifs.js @@ -0,0 +1,276 @@ +import { LitElement, html, css } from 'lit' +import { render } from 'lit/html.js' +import { Epml } from '../../../epml.js' +import * as zip from "@zip.js/zip.js"; +import { saveAs } from 'file-saver'; +import '@material/mwc-icon' +import ShortUniqueId from 'short-unique-id'; +import { publishData } from '../../utils/publish-image.js'; + +const parentEpml = new Epml({ type: 'WINDOW', source: window.parent }) + +class ChatGifs extends LitElement { + static get properties() { + return { + selectedAddress: { type: Object }, + myGifCollections: { type: Array }, + gifsToBeAdded: { type: Array}, + webWorkerImage: {type: Object}, + mode: {type: String}, + currentCollection: {type: String} + } + } + + static get styles() { + return css` + + ` + } + + constructor() { + super() + this.uid = new ShortUniqueId() + this.selectedAddress = window.parent.reduxStore.getState().app.selectedAddress + this.myGifCollections = [] + this.myAccountName = '' + this.gifsToBeAdded = [] + // mode can be 'myCollection', 'newCollection', 'explore', 'subscribedCollection' + this.mode = "myCollection" + this.currentCollection = null + } + + async firstUpdated() { + + + try { + 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 userName = await this.getName(this.selectedAddress.address); + this.myAccountName = userName + if(this.myAccountName){ + const getMyGifColloctions = await parentEpml.request('apiCall', { + url: `/arbitrary/resources?service=GIF_REPOSITORY&limit=0&name=${this.myAccountName}` + }) + + + const getMetaDataGifs = (getMyGifColloctions || []).map(async (collection) => { + let collectionObj = collection + try { + const metaData = await parentEpml.request('apiCall', { + url: `/arbitrary/metadata/GIF_REPOSITORY/${this.myAccountName}/${collection.identifier}` + }) + + collectionObj = { + ...collection, + gifUrls: [] + } + if(metaData.description){ + const metaDataArray = metaData.description.split(';').map((data)=> { + return `${nodeUrl}/arbitrary/GIF_REPOSITORY/${this.myAccountName}/${collection.identifier}?filepath=${data}` + }) + const listOfGifs = [`${nodeUrl}/arbitrary/GIF_REPOSITORY/Phil/gif_pmBEwm?filepath=giphy (1).gif`, `${nodeUrl}/arbitrary/GIF_REPOSITORY/Phil/gif_pmBEwm?filepath=giphy (3).gif`] + + collectionObj = { + ...collection, + gifUrls: metaDataArray + } + + } + + + + } catch (error) { + console.log(error) + } + + return collectionObj + }) + const gifCollectionWithMetaData = await Promise.all(getMetaDataGifs) + console.log({gifCollectionWithMetaData}) + this.myGifCollections = gifCollectionWithMetaData + } + + } catch (error) { + + } + } + + async getName (recipient) { + try { + const getNames = await parentEpml.request("apiCall", { + type: "api", + url: `/names/address/${recipient}`, + }); + + if (Array.isArray(getNames) && getNames.length > 0 ) { + return getNames[0].name + } else { + return '' + } + + } catch (error) { + return "" + } + } + + addGifs(gifs){ + console.log('gifs', gifs) + const mapGifs = gifs.map((file)=> { + return { + file, + name: file.name + } + }) + console.log({mapGifs}) + this.gifsToBeAdded = [...this.gifsToBeAdded, ...mapGifs] + console.log('this.gifsToBeAdded', this.gifsToBeAdded) + } + + async uploadGifCollection(){ + try { + function blobToBase64(blob) { + return new Promise((resolve, _) => { + const reader = new FileReader(); + reader.onloadend = () => resolve(reader.result); + reader.readAsDataURL(blob); + }); + } + const zipFileWriter = new zip.BlobWriter("application/zip"); +// Creates a TextReader object storing the text of the entry to add in the zip +// (i.e. "Hello world!"). +const helloWorldReader = new zip.TextReader("Hello world!"); + +// Creates a ZipWriter object writing data via `zipFileWriter`, adds the entry +// "hello.txt" containing the text "Hello world!" via `helloWorldReader`, and +// closes the writer. + +const zipWriter = new zip.ZipWriter(zipFileWriter, { bufferedWrite: true }); + + +for (let i = 0; i < this.gifsToBeAdded.length; i++) { + await zipWriter.add(this.gifsToBeAdded[i].name, new zip.BlobReader(this.gifsToBeAdded[i].file)); + } + + +await zipWriter.close(); +const zipFileBlob = await zipFileWriter.getData() +const blobTobase = await blobToBase64(zipFileBlob) +console.log({blobTobase}) +const userName = await this.getName(this.selectedAddress.address); + if (!userName) { + parentEpml.request('showSnackBar', get("chatpage.cchange27")); + this.isLoading = false; + return; + } + const id = this.uid(); + const identifier = `gif_${id}`; +await publishData({ + registeredName: userName, + file : blobTobase.split(',')[1], + service: 'GIF_REPOSITORY', + identifier: identifier, + parentEpml, + metaData: undefined, + uploadType: 'zip', + selectedAddress: this.selectedAddress, + worker: this.webWorkerImage, + isBase64: true, + metaData: `description=${this.gifsToBeAdded.map((gif)=> gif.name).join(';')}` + }) +saveAs(zipFileBlob, 'zipfile'); +console.log({zipFileBlob}) + } catch (error) { + console.log(error) + } + } + + render() { + + return html` +
+
+ + + + + + +${this.mode === "myCollection" && !this.currentCollection ? html` + ${this.myGifCollections.map((collection)=> { + return html` +
+

{ + this.currentCollection = collection + }}>${collection.identifier}

+ +
+ ` + })} + ` : ''} + ${this.currentCollection ? html` + + ${this.currentCollection.gifUrls.map((gif)=> { + console.log({gif}) + + return html` + { + e.target.src = gif + }} src=${gif} style="width: 50px; height: 50px" /> + ` + })} + ` : ''} + ${this.mode === "newCollection" ? html` + + + +
+ ${this.gifsToBeAdded.map((gif, i)=> { + console.log({gif}) + return html` +
+ + { + this.gifsToBeAdded[i] = { + ...gif, + name: e.target.value + } + })} /> +
+ + ` + })} +
+ ` : ''} + +
+
+ ` + } + + +} + +window.customElements.define('chat-gifs', ChatGifs) diff --git a/qortal-ui-plugins/plugins/core/components/ChatPage.js b/qortal-ui-plugins/plugins/core/components/ChatPage.js index 0ade923f..5ebc0247 100644 --- a/qortal-ui-plugins/plugins/core/components/ChatPage.js +++ b/qortal-ui-plugins/plugins/core/components/ChatPage.js @@ -11,7 +11,7 @@ import {unsafeHTML} from 'lit/directives/unsafe-html.js'; import { Editor, Extension } from '@tiptap/core' import * as zip from "@zip.js/zip.js"; import { saveAs } from 'file-saver'; - +import './ChatGifs' // import localForage from "localforage"; registerTranslateConfig({ loader: lang => fetch(`/language/${lang}.json`).then(res => res.json()) @@ -106,7 +106,7 @@ class ChatPage extends LitElement { selectedHead: { type: Object }, userName: { type: String }, goToRepliedMessage: {attribute: false}, - gifsToBeAdded: { type: Array} + openGifModal: {type: Boolean} } } @@ -894,7 +894,12 @@ class ChatPage extends LitElement { this.currentEditor = '_chatEditorDOM' this.initialChat = this.initialChat.bind(this) this.isEnabledChatEnter = true - this.gifsToBeAdded = [] + this.openGifModal = false + } + + setOpenGifModal(value){ + console.log({value}) + this.openGifModal = value } _toggle(value) { @@ -1104,6 +1109,7 @@ console.log({zipFileBlob}) .repliedToMessageObj=${this.repliedToMessageObj} .toggleEnableChatEnter=${this.toggleEnableChatEnter} ?isEnabledChatEnter=${this.isEnabledChatEnter} + .setOpenGifModal=${(val)=> this.setOpenGifModal(val)} > @@ -1124,6 +1130,15 @@ console.log({zipFileBlob}) `: ''} + + { + this.setOpenGifModal(false) + // this.removeImage(); + }} + style=${this.openGifModal ? "visibility:visible;z-index:50" : "visibility: hidden;z-index:-100"}> + + { this.removeImage(); @@ -1174,34 +1189,7 @@ console.log({zipFileBlob}) - - { - // this.removeImage(); - }} - style=${true ? "visibility:visible;z-index:50" : "visibility: hidden;z-index:-100"}> -
-
- - - - -
-
-
+

${translate("chatpage.cchange41")}


@@ -1511,7 +1499,7 @@ console.log({zipFileBlob}) } 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 &&!this.openGifModal) { // WARNING: Deprecated methods from KeyBoard Event if (e.code === "Space" || e.keyCode === 32 || e.which === 32) { } else if (inputKeyCodes.includes(e.keyCode)) { diff --git a/qortal-ui-plugins/plugins/core/components/ChatTextEditor.js b/qortal-ui-plugins/plugins/core/components/ChatTextEditor.js index cc371fa4..70a7d068 100644 --- a/qortal-ui-plugins/plugins/core/components/ChatTextEditor.js +++ b/qortal-ui-plugins/plugins/core/components/ChatTextEditor.js @@ -29,7 +29,8 @@ class ChatTextEditor extends LitElement { reflect: true }, toggleEnableChatEnter: {attribute: false}, - isEnabledChatEnter: {type: Boolean} + isEnabledChatEnter: {type: Boolean}, + setOpenGifModal: {attribute: false} } } @@ -461,6 +462,13 @@ class ChatTextEditor extends LitElement { + ${this.setOpenGifModal ? + html` + + ` + : ''} ${this.editedMessageObj ? ( html`
diff --git a/qortal-ui-plugins/plugins/utils/publish-image.js b/qortal-ui-plugins/plugins/utils/publish-image.js index 5835aa33..ed8e2a64 100644 --- a/qortal-ui-plugins/plugins/utils/publish-image.js +++ b/qortal-ui-plugins/plugins/utils/publish-image.js @@ -17,7 +17,8 @@ export const publishData = async ({ uploadType, selectedAddress, worker, - isBase64 + isBase64, + metaData }) => { const validateName = async (receiverName) => { let nameRes = await parentEpml.request("apiCall", { @@ -131,7 +132,13 @@ export const publishData = async ({ let uploadDataUrl = `/arbitrary/${service}/${registeredName}${urlSuffix}?apiKey=${getApiKey()}` if (identifier != null && identifier.trim().length > 0) { uploadDataUrl = `/arbitrary/${service}/${registeredName}/${identifier}${urlSuffix}?apiKey=${getApiKey()}` + + if(metaData){ + uploadDataUrl = `/arbitrary/${service}/${registeredName}/${identifier}${urlSuffix}?${metaData}&apiKey=${getApiKey()}` + + } } + let uploadDataRes = await parentEpml.request("apiCall", { type: "api", method: "POST", From deb0d531f9d6c22f032afd08ee779fd44d9565b4 Mon Sep 17 00:00:00 2001 From: Phillip Date: Sun, 22 Jan 2023 13:59:47 +0200 Subject: [PATCH 03/16] added gif explore --- .../plugins/core/components/ChatGifs.js | 262 +++++++++++++++--- .../core/components/ChatGifsExplore.js | 97 +++++++ 2 files changed, 318 insertions(+), 41 deletions(-) create mode 100644 qortal-ui-plugins/plugins/core/components/ChatGifsExplore.js diff --git a/qortal-ui-plugins/plugins/core/components/ChatGifs.js b/qortal-ui-plugins/plugins/core/components/ChatGifs.js index 3ddb8017..9ee79fab 100644 --- a/qortal-ui-plugins/plugins/core/components/ChatGifs.js +++ b/qortal-ui-plugins/plugins/core/components/ChatGifs.js @@ -6,7 +6,7 @@ import { saveAs } from 'file-saver'; import '@material/mwc-icon' import ShortUniqueId from 'short-unique-id'; import { publishData } from '../../utils/publish-image.js'; - +import './ChatGifsExplore' const parentEpml = new Epml({ type: 'WINDOW', source: window.parent }) class ChatGifs extends LitElement { @@ -14,6 +14,7 @@ class ChatGifs extends LitElement { return { selectedAddress: { type: Object }, myGifCollections: { type: Array }, + exploreCollections: { type: Array }, gifsToBeAdded: { type: Array}, webWorkerImage: {type: Object}, mode: {type: String}, @@ -32,19 +33,172 @@ class ChatGifs extends LitElement { this.uid = new ShortUniqueId() this.selectedAddress = window.parent.reduxStore.getState().app.selectedAddress this.myGifCollections = [] + this.exploreCollections = [] this.myAccountName = '' this.gifsToBeAdded = [] // mode can be 'myCollection', 'newCollection', 'explore', 'subscribedCollection' this.mode = "myCollection" this.currentCollection = null + this.pageNumber = 1 + this.downObserverElement = '' + this.viewElement = '' + } + + async structureCollections(gifCollections){ + try { + 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 getMetaDataGifs = (gifCollections || []).map(async (collection) => { + let collectionObj = collection + try { + const metaData = await parentEpml.request('apiCall', { + url: `/arbitrary/metadata/GIF_REPOSITORY/${this.myAccountName}/${collection.identifier}` + }) + + collectionObj = { + ...collection, + gifUrls: [] + } + if(metaData.description){ + const metaDataArray = metaData.description.split(';').map((data)=> { + return `${nodeUrl}/arbitrary/GIF_REPOSITORY/${this.myAccountName}/${collection.identifier}?filepath=${data}` + }) + + + collectionObj = { + ...collection, + gifUrls: metaDataArray + } + + } + + + + } catch (error) { + console.log(error) + } + + return collectionObj + }) + return await Promise.all(getMetaDataGifs) + } catch (error) { + + } + } + + elementObserver() { + const options = { + root: this.viewElement, + rootMargin: '0px', + threshold: 1 + } + // identify an element to observe + const elementToObserve = this.downObserverElement; + // passing it a callback function + const observer = new IntersectionObserver(this.observerHandler, options); + // call `observe()` on that MutationObserver instance, + // passing it the element to observe, and the options object + observer.observe(elementToObserve); + } + + observerHandler(entries) { + if (!entries[0].isIntersecting) { + return + } else { + if(this.exploreCollections.length < 20){ + return + } + + this.getMoreExploreGifs() + } + } + + async getMoreExploreGifs(){ + try { + + const getAllGifCollections = await parentEpml.request("apiCall", { + type: "api", + url: `/arbitrary/resources?service=GIF_REPOSITORY&limit=20&offset=${this.pageNumber * 20}`, + }); + + const gifCollectionWithMetaData = await this.structureCollections(getAllGifCollections) + this.exploreCollections = [...this.exploreCollections, ...gifCollectionWithMetaData] + + this.pageNumber = this.pageNumber + 1 + } catch (error) { + console.error(error) + } + } + + async getCollectionList(){ + try { + await parentEpml.request("apiCall", { + type: "api", + url: `/lists/gifSubscribedRepos`, + }); + + } catch (error) { + + } + } + + async addCollectionToList(collection){ + try { + + const body = { + + "items": [ + collection + ] + + } + const bodyToString = JSON.stringify(body) + await parentEpml.request("apiCall", { + type: "api", + method: "POST", + url: `/lists/gifSubscribedRepos`, + body: bodyToString, + headers: { + 'Content-Type': 'application/json' + } + }) + } catch (error) { + + } + } + + async removeCollectionFromList(collection){ + try { + + const body = { + + "items": [ + collection + ] + + } + const bodyToString = JSON.stringify(body) + await parentEpml.request("apiCall", { + type: "api", + method: 'DELETE', + url: `/lists/gifSubscribedRepos`, + body: bodyToString, + headers: { + 'Content-Type': 'application/json' + } + }) + } catch (error) { + + } } async firstUpdated() { - + this.viewElement = this.shadowRoot.getElementById('viewElement'); + this.downObserverElement = this.shadowRoot.getElementById('downObserver'); + this.elementObserver(); try { - 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 userName = await this.getName(this.selectedAddress.address); this.myAccountName = userName if(this.myAccountName){ @@ -52,44 +206,43 @@ class ChatGifs extends LitElement { url: `/arbitrary/resources?service=GIF_REPOSITORY&limit=0&name=${this.myAccountName}` }) - - const getMetaDataGifs = (getMyGifColloctions || []).map(async (collection) => { - let collectionObj = collection - try { - const metaData = await parentEpml.request('apiCall', { - url: `/arbitrary/metadata/GIF_REPOSITORY/${this.myAccountName}/${collection.identifier}` - }) - - collectionObj = { - ...collection, - gifUrls: [] - } - if(metaData.description){ - const metaDataArray = metaData.description.split(';').map((data)=> { - return `${nodeUrl}/arbitrary/GIF_REPOSITORY/${this.myAccountName}/${collection.identifier}?filepath=${data}` - }) - const listOfGifs = [`${nodeUrl}/arbitrary/GIF_REPOSITORY/Phil/gif_pmBEwm?filepath=giphy (1).gif`, `${nodeUrl}/arbitrary/GIF_REPOSITORY/Phil/gif_pmBEwm?filepath=giphy (3).gif`] - - collectionObj = { - ...collection, - gifUrls: metaDataArray - } - - } - - - - } catch (error) { - console.log(error) - } - - return collectionObj - }) - const gifCollectionWithMetaData = await Promise.all(getMetaDataGifs) + const gifCollectionWithMetaData = await this.structureCollections(getMyGifColloctions) + console.log({gifCollectionWithMetaData}) this.myGifCollections = gifCollectionWithMetaData } - + // for the explore section + const getAllGifCollections = await parentEpml.request("apiCall", { + type: "api", + url: `/arbitrary/resources?service=GIF_REPOSITORY&limit=20&offset=${this.pageNumber * 20}`, + }); + console.log({getAllGifCollections}) + const gifCollectionWithMetaData = await this.structureCollections(getAllGifCollections) + this.exploreCollections = gifCollectionWithMetaData + this.pageNumber = this.pageNumber + 1 + + const getCollectionList = await this.getCollectionList() + + let savedCollections = [] + + const getSavedGifRepos = (!Array.isArray(getCollectionList) || []).map(async (collection) => { + let collectionObj = collection + try { + const data = await parentEpml.request('apiCall', { + url: `/arbitrary/GIF_REPOSITORY/collection` + }) + savedCollections.push(data) + + } catch (error) { + console.log(error) + } + + return collectionObj + }) + await Promise.all(getSavedGifRepos) + + console.log({savedCollections}) + } catch (error) { } @@ -184,6 +337,10 @@ console.log({zipFileBlob}) } } + setCurrentCollection(val){ + this.currentCollection = val + } + render() { return html` @@ -201,7 +358,7 @@ console.log({zipFileBlob}) - + ${this.mode === "myCollection" && !this.currentCollection ? html` ${this.myGifCollections.map((collection)=> { @@ -215,7 +372,13 @@ ${this.mode === "myCollection" && !this.currentCollection ? html` ` })} ` : ''} - ${this.currentCollection ? html` + ${this.mode === "explore" && !this.currentCollection ? html` + this.getMoreExploreGifs(val)} .exploreCollections=${this.exploreCollections} + .setCurrentCollection=${(val)=> this.setCurrentCollection(val)} + > + + ` : ''} + ${this.currentCollection && this.mode === "myCollection" ? html` @@ -229,6 +392,23 @@ ${this.mode === "myCollection" && !this.currentCollection ? html` ` })} ` : ''} + ${this.currentCollection && this.mode === "explore" ? html` + + + ${this.currentCollection.gifUrls.map((gif)=> { + console.log({gif}) + + return html` + { + e.target.src = gif + }} src=${gif} style="width: 50px; height: 50px" /> + ` + })} + ` : ''} ${this.mode === "newCollection" ? html` + ${this.exploreCollections.map((collection)=> { + return html` +
+

{ + this.setCurrentCollection(collection) + }}>${collection.identifier}

+ +
+ ` + })} +
+
+ + ` + } + + +} + +window.customElements.define('chat-gifs-explore', ChatGifsExplore) From 047d5aa7d47f445928e2ce1f2331897964c8cc29 Mon Sep 17 00:00:00 2001 From: Phillip Date: Thu, 26 Jan 2023 18:23:45 +0200 Subject: [PATCH 04/16] added fetch saved collections --- .../plugins/core/components/ChatGifs.js | 300 +++++++++++++----- 1 file changed, 214 insertions(+), 86 deletions(-) diff --git a/qortal-ui-plugins/plugins/core/components/ChatGifs.js b/qortal-ui-plugins/plugins/core/components/ChatGifs.js index 9ee79fab..48afaae5 100644 --- a/qortal-ui-plugins/plugins/core/components/ChatGifs.js +++ b/qortal-ui-plugins/plugins/core/components/ChatGifs.js @@ -6,7 +6,8 @@ import { saveAs } from 'file-saver'; import '@material/mwc-icon' import ShortUniqueId from 'short-unique-id'; import { publishData } from '../../utils/publish-image.js'; -import './ChatGifsExplore' +import './ChatGifsExplore.js' +// import isAlphanumeric from 'validator/lib/isAlphanumeric'/ const parentEpml = new Epml({ type: 'WINDOW', source: window.parent }) class ChatGifs extends LitElement { @@ -14,11 +15,14 @@ class ChatGifs extends LitElement { return { selectedAddress: { type: Object }, myGifCollections: { type: Array }, + mySubscribedCollections: {type: Array}, exploreCollections: { type: Array }, gifsToBeAdded: { type: Array}, webWorkerImage: {type: Object}, mode: {type: String}, - currentCollection: {type: String} + currentCollection: {type: String}, + isLoading: {type: String}, + newCollectionName: {type: String} } } @@ -33,15 +37,16 @@ class ChatGifs extends LitElement { this.uid = new ShortUniqueId() this.selectedAddress = window.parent.reduxStore.getState().app.selectedAddress this.myGifCollections = [] + this.mySubscribedCollections = [] this.exploreCollections = [] this.myAccountName = '' this.gifsToBeAdded = [] // mode can be 'myCollection', 'newCollection', 'explore', 'subscribedCollection' this.mode = "myCollection" this.currentCollection = null - this.pageNumber = 1 - this.downObserverElement = '' - this.viewElement = '' + this.pageNumber = 0 + this.isLoading = false + this.newCollectionName = "" } async structureCollections(gifCollections){ @@ -86,32 +91,8 @@ class ChatGifs extends LitElement { } } - elementObserver() { - const options = { - root: this.viewElement, - rootMargin: '0px', - threshold: 1 - } - // identify an element to observe - const elementToObserve = this.downObserverElement; - // passing it a callback function - const observer = new IntersectionObserver(this.observerHandler, options); - // call `observe()` on that MutationObserver instance, - // passing it the element to observe, and the options object - observer.observe(elementToObserve); - } - observerHandler(entries) { - if (!entries[0].isIntersecting) { - return - } else { - if(this.exploreCollections.length < 20){ - return - } - - this.getMoreExploreGifs() - } - } + async getMoreExploreGifs(){ try { @@ -132,7 +113,7 @@ class ChatGifs extends LitElement { async getCollectionList(){ try { - await parentEpml.request("apiCall", { + return await parentEpml.request("apiCall", { type: "api", url: `/lists/gifSubscribedRepos`, }); @@ -192,61 +173,125 @@ class ChatGifs extends LitElement { } } - async firstUpdated() { - this.viewElement = this.shadowRoot.getElementById('viewElement'); - this.downObserverElement = this.shadowRoot.getElementById('downObserver'); - this.elementObserver(); - - try { - - const userName = await this.getName(this.selectedAddress.address); - this.myAccountName = userName - if(this.myAccountName){ - const getMyGifColloctions = await parentEpml.request('apiCall', { - url: `/arbitrary/resources?service=GIF_REPOSITORY&limit=0&name=${this.myAccountName}` - }) - - const gifCollectionWithMetaData = await this.structureCollections(getMyGifColloctions) - - console.log({gifCollectionWithMetaData}) - this.myGifCollections = gifCollectionWithMetaData - } - // for the explore section - const getAllGifCollections = await parentEpml.request("apiCall", { - type: "api", - url: `/arbitrary/resources?service=GIF_REPOSITORY&limit=20&offset=${this.pageNumber * 20}`, - }); - console.log({getAllGifCollections}) - const gifCollectionWithMetaData = await this.structureCollections(getAllGifCollections) - this.exploreCollections = gifCollectionWithMetaData - this.pageNumber = this.pageNumber + 1 - - const getCollectionList = await this.getCollectionList() - - let savedCollections = [] - - const getSavedGifRepos = (!Array.isArray(getCollectionList) || []).map(async (collection) => { - let collectionObj = collection - try { - const data = await parentEpml.request('apiCall', { - url: `/arbitrary/GIF_REPOSITORY/collection` - }) - savedCollections.push(data) - - } catch (error) { - console.log(error) - } - - return collectionObj + async getMyGifCollections(){ + const userName = await this.getName(this.selectedAddress.address); + this.myAccountName = userName + if(this.myAccountName){ + const getMyGifColloctions = await parentEpml.request('apiCall', { + url: `/arbitrary/resources?service=GIF_REPOSITORY&limit=0&name=${this.myAccountName}` }) - await Promise.all(getSavedGifRepos) - console.log({savedCollections}) - - } catch (error) { + const gifCollectionWithMetaData = await this.structureCollections(getMyGifColloctions) + console.log({gifCollectionWithMetaData}) + this.myGifCollections = gifCollectionWithMetaData } } + async getAllCollections(){ + this.pageNumber = 0 + // for the explore section + const getAllGifCollections = await parentEpml.request("apiCall", { + type: "api", + url: `/arbitrary/resources?service=GIF_REPOSITORY&limit=20&offset=${this.pageNumber * 20}`, + }); + const gifCollectionWithMetaData = await this.structureCollections(getAllGifCollections) + this.exploreCollections = gifCollectionWithMetaData + this.pageNumber = this.pageNumber + 1 + } + + async getSavedCollections(){ + const getCollectionList = await this.getCollectionList() + + let savedCollections = [] + const getSavedGifRepos = (getCollectionList || []).map(async (collection) => { + let splitCollection = collection.split('/') + const name = splitCollection[0] + const identifier = splitCollection[1] + try { + console.log({collection}) + const data = await parentEpml.request('apiCall', { + url: `/arbitrary/resources?service=GIF_REPOSITORY&limit=0&name=${name}&identifier=${identifier}` + }) + if(data.length > 0){ + savedCollections.push(data[0]) + } + + + + } catch (error) { + console.log(error) + } + + return collection + }) + await Promise.all(getSavedGifRepos) + const savedCollectionsWithMetaData = await this.structureCollections(savedCollections) + this.mySubscribedCollections = savedCollectionsWithMetaData + } + + + async firstUpdated() { + + + try { + this.isLoading = true + await this.getMyGifCollections() + await this.getAllCollections() + await this.getSavedCollections() + this.isLoading = false + + + } catch (error) { + this.isLoading = false + console.error(error) + } + } + + async updated(changedProperties) { + console.log({changedProperties}) + if (changedProperties && changedProperties.has('mode')) { + const mode = this.mode + console.log({mode}) + if (mode === 'myCollection') { + try { + this.isLoading = true + + await this.getMyGifCollections() + this.isLoading = false + } catch (error) { + this.isLoading = false + } + + } + if (mode === 'explore') { + try { + this.isLoading = true + + await this.getAllCollections() + this.isLoading = false + } catch (error) { + this.isLoading = false + } + + } + if (mode === 'subscribedCollection') { + try { + this.isLoading = true + + await this.getSavedCollections() + this.isLoading = false + } catch (error) { + this.isLoading = false + } + + } + } + + + + } + + async getName (recipient) { try { @@ -280,7 +325,25 @@ class ChatGifs extends LitElement { } async uploadGifCollection(){ + if(!this.newCollectionName){ + parentEpml.request('showSnackBar', get("chatpage.cchange27")); + return + } + + // if(!isAlphanumeric(this.newCollectionName)){ + // parentEpml.request('showSnackBar', get("chatpage.cchange27")); + // return + // } try { + const userName = await this.getName(this.selectedAddress.address); + const doesNameExist = await parentEpml.request('apiCall', { + url: `/arbitrary/resources?service=GIF_REPOSITORY&limit=0&name=${userName}&identifier=${this.newCollectionName}` + }) + + if(doesNameExist.length !== 0){ + parentEpml.request('showSnackBar', get("chatpage.cchange27")); + return + } function blobToBase64(blob) { return new Promise((resolve, _) => { const reader = new FileReader(); @@ -309,7 +372,7 @@ await zipWriter.close(); const zipFileBlob = await zipFileWriter.getData() const blobTobase = await blobToBase64(zipFileBlob) console.log({blobTobase}) -const userName = await this.getName(this.selectedAddress.address); + if (!userName) { parentEpml.request('showSnackBar', get("chatpage.cchange27")); this.isLoading = false; @@ -321,7 +384,7 @@ await publishData({ registeredName: userName, file : blobTobase.split(',')[1], service: 'GIF_REPOSITORY', - identifier: identifier, + identifier: this.newCollectionName, parentEpml, metaData: undefined, uploadType: 'zip', @@ -330,6 +393,29 @@ await publishData({ isBase64: true, metaData: `description=${this.gifsToBeAdded.map((gif)=> gif.name).join(';')}` }) + + await new Promise((res)=> { + let interval = null + let stop = false + const getAnswer = async () => { + + + if (!stop) { + stop = true + try { + let myCollection = await parentEpml.request('apiCall', { + url: `/arbitrary/resources?service=GIF_REPOSITORY&limit=0&name=${userName}&identifier=${this.newCollectionName}` + }) + if (myCollection.length > 0) { + clearInterval(interval) + res() + } + } catch (error) {} + stop = false + } + } + interval = setInterval(getAnswer, 5000) + }) saveAs(zipFileBlob, 'zipfile'); console.log({zipFileBlob}) } catch (error) { @@ -342,25 +428,32 @@ console.log({zipFileBlob}) } render() { - + console.log('this.currentCollection', this.currentCollection) return html`
+ }}>Subscribed colloctions ${this.mode === "myCollection" && !this.currentCollection ? html` + ${this.isLoading === true ? html` +

Loading...

+ ` : ''} ${this.myGifCollections.map((collection)=> { return html`
@@ -372,7 +465,25 @@ ${this.mode === "myCollection" && !this.currentCollection ? html` ` })} ` : ''} + ${this.mode === "subscribedCollection" && !this.currentCollection ? html` + ${this.isLoading === true ? html` +

Loading...

+ ` : ''} + ${this.mySubscribedCollections.map((collection)=> { + return html` +
+

{ + this.currentCollection = collection + }}>${collection.identifier}

+ +
+ ` + })} + ` : ''} ${this.mode === "explore" && !this.currentCollection ? html` + ${this.isLoading === true ? html` +

Loading...

+ ` : ''} this.getMoreExploreGifs(val)} .exploreCollections=${this.exploreCollections} .setCurrentCollection=${(val)=> this.setCurrentCollection(val)} > @@ -392,12 +503,26 @@ ${this.mode === "myCollection" && !this.currentCollection ? html` ` })} ` : ''} + ${this.currentCollection && this.mode === "subscribedCollection" ? html` + + ${this.currentCollection.gifUrls.map((gif)=> { + console.log({gif}) + + return html` + { + e.target.src = gif + }} src=${gif} style="width: 50px; height: 50px" /> + ` + })} + ` : ''} ${this.currentCollection && this.mode === "explore" ? html` ${this.currentCollection.gifUrls.map((gif)=> { console.log({gif}) @@ -426,6 +551,9 @@ ${this.mode === "myCollection" && !this.currentCollection ? html` + { + this.newCollectionName = e.target.value + })} />
${this.gifsToBeAdded.map((gif, i)=> { console.log({gif}) From f229ead7aeaacf1af5e96717d03033e5944fdc12 Mon Sep 17 00:00:00 2001 From: Phillip Date: Fri, 27 Jan 2023 13:37:44 +0200 Subject: [PATCH 05/16] switch meta to files --- qortal-ui-core/package.json | 20 ++++++------ qortal-ui-plugins/package.json | 31 ++++++++++--------- .../plugins/core/components/ChatGifs.js | 9 +++--- 3 files changed, 31 insertions(+), 29 deletions(-) diff --git a/qortal-ui-core/package.json b/qortal-ui-core/package.json index e98948c6..5515942d 100644 --- a/qortal-ui-core/package.json +++ b/qortal-ui-core/package.json @@ -1,6 +1,6 @@ { "name": "qortal-ui-core", - "version": "2.2.5", + "version": "3.0.0", "description": "Qortal Project - decentralize the world - Data storage, communications, web hosting, decentralized trading, complete infrastructure for the future blockchain-based Internet", "keywords": [ "QORT", @@ -17,7 +17,7 @@ "author": "QORTAL ", "license": "GPL-3.0", "dependencies": { - "@hapi/hapi": "21.1.0", + "@hapi/hapi": "21.2.0", "@hapi/inert": "7.0.0", "sass": "1.57.1" }, @@ -53,17 +53,17 @@ "@polymer/paper-spinner": "3.0.2", "@polymer/paper-toast": "3.0.1", "@polymer/paper-tooltip": "3.0.1", - "@rollup/plugin-alias": "4.0.2", + "@rollup/plugin-alias": "4.0.3", "@rollup/plugin-babel": "6.0.3", - "@rollup/plugin-commonjs": "24.0.0", + "@rollup/plugin-commonjs": "24.0.1", "@rollup/plugin-node-resolve": "15.0.1", "@rollup/plugin-replace": "5.0.2", "@rollup/plugin-terser": "0.3.0", - "@vaadin/button": "23.3.3", - "@vaadin/grid": "23.3.3", - "@vaadin/icons": "23.3.3", - "@vaadin/password-field": "23.3.3", - "@vaadin/tooltip": "23.3.3", + "@vaadin/button": "23.3.5", + "@vaadin/grid": "23.3.5", + "@vaadin/icons": "23.3.5", + "@vaadin/password-field": "23.3.5", + "@vaadin/tooltip": "23.3.5", "asmcrypto.js": "2.3.2", "bcryptjs": "2.4.3", "epml": "0.3.3", @@ -73,7 +73,7 @@ "pwa-helpers": "0.9.1", "redux": "4.2.0", "redux-thunk": "2.4.2", - "rollup": "3.10.0", + "rollup": "3.10.1", "rollup-plugin-node-globals": "1.4.0", "rollup-plugin-progress": "1.1.2", "rollup-plugin-scss": "3.0.0", diff --git a/qortal-ui-plugins/package.json b/qortal-ui-plugins/package.json index 5100b14e..f41d6d38 100644 --- a/qortal-ui-plugins/package.json +++ b/qortal-ui-plugins/package.json @@ -1,6 +1,6 @@ { "name": "qortal-ui-plugins", - "version": "2.2.5", + "version": "3.0.0", "description": "Qortal Project - decentralize the world - Data storage, communications, web hosting, decentralized trading, complete infrastructure for the future blockchain-based Internet", "keywords": [ "QORT", @@ -21,17 +21,15 @@ "@material/mwc-list": "0.27.0", "@material/mwc-select": "0.27.0", "@tiptap/core": "2.0.0-beta.209", - "@tiptap/extension-highlight": "2.0.0-beta.209", "@tiptap/extension-image": "2.0.0-beta.209", "@tiptap/extension-placeholder": "2.0.0-beta.209", "@tiptap/extension-underline": "2.0.0-beta.209", + "@tiptap/extension-highlight": "2.0.0-beta.209", "@tiptap/html": "2.0.0-beta.209", "@tiptap/starter-kit": "2.0.0-beta.209", - "@zip.js/zip.js": "2.6.62", "asmcrypto.js": "2.3.2", "compressorjs": "1.1.1", "emoji-picker-js": "https://github.com/Qortal/emoji-picker-js", - "localforage": "1.10.0", "prosemirror-commands": "1.5.0", "prosemirror-dropcursor": "1.6.1", "prosemirror-gapcursor": "1.3.1", @@ -42,6 +40,7 @@ "prosemirror-state": "1.4.2", "prosemirror-transform": "1.7.0", "prosemirror-view": "1.29.1", + "localforage": "1.10.0", "short-unique-id": "4.4.4" }, "devDependencies": { @@ -64,29 +63,31 @@ "@polymer/paper-slider": "3.0.1", "@polymer/paper-spinner": "3.0.2", "@polymer/paper-tooltip": "3.0.1", - "@rollup/plugin-alias": "4.0.2", + "@rollup/plugin-alias": "4.0.3", "@rollup/plugin-babel": "6.0.3", - "@rollup/plugin-commonjs": "24.0.0", + "@rollup/plugin-commonjs": "24.0.1", "@rollup/plugin-node-resolve": "15.0.1", "@rollup/plugin-replace": "5.0.2", "@rollup/plugin-terser": "0.3.0", - "@vaadin/avatar": "23.3.3", - "@vaadin/button": "23.3.3", - "@vaadin/grid": "23.3.3", - "@vaadin/icons": "23.3.3", - "@vaadin/tooltip": "23.3.3", + "@vaadin/avatar": "23.3.5", + "@vaadin/button": "23.3.5", + "@vaadin/grid": "23.3.5", + "@vaadin/icons": "23.3.5", + "@vaadin/tooltip": "23.3.5", "epml": "0.3.3", "file-saver": "2.0.5", - "highcharts": "10.3.2", + "highcharts": "10.3.3", "html-escaper": "3.0.3", "lit": "2.6.1", "lit-translate": "2.0.1", - "rollup": "3.10.0", + "rollup": "3.10.1", "rollup-plugin-node-globals": "1.4.0", "rollup-plugin-progress": "1.1.2", - "rollup-plugin-web-worker-loader": "1.6.1" + "rollup-plugin-web-worker-loader": "1.6.1", + "@zip.js/zip.js": "2.6.62", + "validator": "13.7.0" }, "engines": { "node": ">=16.17.1" } -} +} \ No newline at end of file diff --git a/qortal-ui-plugins/plugins/core/components/ChatGifs.js b/qortal-ui-plugins/plugins/core/components/ChatGifs.js index 48afaae5..9a97986e 100644 --- a/qortal-ui-plugins/plugins/core/components/ChatGifs.js +++ b/qortal-ui-plugins/plugins/core/components/ChatGifs.js @@ -6,6 +6,8 @@ import { saveAs } from 'file-saver'; import '@material/mwc-icon' import ShortUniqueId from 'short-unique-id'; import { publishData } from '../../utils/publish-image.js'; +import { get } from 'lit-translate'; + import './ChatGifsExplore.js' // import isAlphanumeric from 'validator/lib/isAlphanumeric'/ const parentEpml = new Epml({ type: 'WINDOW', source: window.parent }) @@ -64,8 +66,8 @@ class ChatGifs extends LitElement { ...collection, gifUrls: [] } - if(metaData.description){ - const metaDataArray = metaData.description.split(';').map((data)=> { + if(metaData.files){ + const metaDataArray = metaData.files.split(';').map((data)=> { return `${nodeUrl}/arbitrary/GIF_REPOSITORY/${this.myAccountName}/${collection.identifier}?filepath=${data}` }) @@ -390,8 +392,7 @@ await publishData({ uploadType: 'zip', selectedAddress: this.selectedAddress, worker: this.webWorkerImage, - isBase64: true, - metaData: `description=${this.gifsToBeAdded.map((gif)=> gif.name).join(';')}` + isBase64: true }) await new Promise((res)=> { From 4cab597afafcbb85d95781537f79a4fbe8a0f673 Mon Sep 17 00:00:00 2001 From: Justin Ferrari Date: Wed, 1 Feb 2023 23:51:13 +0200 Subject: [PATCH 06/16] Initial UI done for the gif explorer --- package.json | 8 +- qortal-ui-core/font/MavenPro.ttf | Bin 0 -> 89520 bytes qortal-ui-core/font/material-icons.css | 16 +- qortal-ui-core/font/switch-theme.css | 4 +- qortal-ui-core/language/us.json | 5 +- qortal-ui-core/package.json | 16 +- qortal-ui-core/src/styles/switch-theme.css | 4 +- qortal-ui-plugins/package.json | 26 +- .../plugins/core/components/ChatGifs.js | 585 ------- .../core/components/ChatGifs/ChatGifs-css.js | 41 + .../core/components/ChatGifs/ChatGifs.js | 594 +++++++ .../{ => ChatGifs}/ChatGifsExplore.js | 4 +- .../plugins/core/components/ChatPage.js | 1485 +++++++++-------- .../plugins/core/components/ChatTextEditor.js | 119 +- .../core/messaging/q-chat/q-chat.src.js | 2 - 15 files changed, 1495 insertions(+), 1414 deletions(-) create mode 100644 qortal-ui-core/font/MavenPro.ttf delete mode 100644 qortal-ui-plugins/plugins/core/components/ChatGifs.js create mode 100644 qortal-ui-plugins/plugins/core/components/ChatGifs/ChatGifs-css.js create mode 100644 qortal-ui-plugins/plugins/core/components/ChatGifs/ChatGifs.js rename qortal-ui-plugins/plugins/core/components/{ => ChatGifs}/ChatGifsExplore.js (96%) diff --git a/package.json b/package.json index fad3cfe8..1afc154b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "qortal-ui", - "version": "2.2.4", + "version": "3.0.1", "description": "Qortal Project - decentralize the world - Data storage, communications, web hosting, decentralized trading, complete infrastructure for the future blockchain-based Internet", "keywords": [ "QORT", @@ -37,14 +37,12 @@ "os-locale": "3.0.0" }, "devDependencies": { - "electron": "22.0.2", + "electron": "22.1.0", "electron-builder": "23.6.0", "electron-packager": "17.1.1", - "eslint-plugin-lit": "1.8.0", - "eslint-plugin-wc": "1.4.0", "shelljs": "0.8.5" }, "engines": { "node": ">=16.17.1" } -} +} \ No newline at end of file diff --git a/qortal-ui-core/font/MavenPro.ttf b/qortal-ui-core/font/MavenPro.ttf new file mode 100644 index 0000000000000000000000000000000000000000..ab146a80512bca8bc38e491987c476944706de9b GIT binary patch literal 89520 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", "license": "GPL-3.0", "dependencies": { - "@hapi/hapi": "21.2.0", + "@hapi/hapi": "21.2.1", "@hapi/inert": "7.0.0", "sass": "1.57.1" }, @@ -58,12 +58,12 @@ "@rollup/plugin-commonjs": "24.0.1", "@rollup/plugin-node-resolve": "15.0.1", "@rollup/plugin-replace": "5.0.2", - "@rollup/plugin-terser": "0.3.0", - "@vaadin/button": "23.3.5", - "@vaadin/grid": "23.3.5", - "@vaadin/icons": "23.3.5", - "@vaadin/password-field": "23.3.5", - "@vaadin/tooltip": "23.3.5", + "@rollup/plugin-terser": "0.4.0", + "@vaadin/button": "23.3.6", + "@vaadin/grid": "23.3.6", + "@vaadin/icons": "23.3.6", + "@vaadin/password-field": "23.3.6", + "@vaadin/tooltip": "23.3.6", "asmcrypto.js": "2.3.2", "bcryptjs": "2.4.3", "epml": "0.3.3", @@ -73,7 +73,7 @@ "pwa-helpers": "0.9.1", "redux": "4.2.0", "redux-thunk": "2.4.2", - "rollup": "3.10.1", + "rollup": "3.12.0", "rollup-plugin-node-globals": "1.4.0", "rollup-plugin-progress": "1.1.2", "rollup-plugin-scss": "3.0.0", diff --git a/qortal-ui-core/src/styles/switch-theme.css b/qortal-ui-core/src/styles/switch-theme.css index 72dfa151..47162c6b 100644 --- a/qortal-ui-core/src/styles/switch-theme.css +++ b/qortal-ui-core/src/styles/switch-theme.css @@ -48,6 +48,7 @@ html { --chatHeadTextActive: #080808; --group-header: #929292; --group-drop-shadow: rgb(17 17 26 / 10%) 0px 1px 0px; + --gifs-drop-shadow: #32326926 0px 2px 5px 0px, #0000000d 0px 1px 1px 0px; } html[theme="dark"] { @@ -99,5 +100,6 @@ html[theme="dark"] { --chatHeadText: #ffffff; --chatHeadTextActive: #ffffff; --group-header: #c8c8c8; - --group-drop-shadow: rgb(191 191 191 / 32%) 0px 1px 0px + --group-drop-shadow: rgb(191 191 191 / 32%) 0px 1px 0px; + --gifs-drop-shadow: 0px 2px 2px 0px hsla(0, 0%, 0%, 0.14), 0px 3px 1px -2px hsla(0, 0%, 0%, 0.12), 0px 1px 5px 0px hsla(0, 0%, 0%, 0.2); } \ No newline at end of file diff --git a/qortal-ui-plugins/package.json b/qortal-ui-plugins/package.json index f41d6d38..198b0be6 100644 --- a/qortal-ui-plugins/package.json +++ b/qortal-ui-plugins/package.json @@ -21,15 +21,16 @@ "@material/mwc-list": "0.27.0", "@material/mwc-select": "0.27.0", "@tiptap/core": "2.0.0-beta.209", + "@tiptap/extension-highlight": "2.0.0-beta.209", "@tiptap/extension-image": "2.0.0-beta.209", "@tiptap/extension-placeholder": "2.0.0-beta.209", "@tiptap/extension-underline": "2.0.0-beta.209", - "@tiptap/extension-highlight": "2.0.0-beta.209", "@tiptap/html": "2.0.0-beta.209", "@tiptap/starter-kit": "2.0.0-beta.209", "asmcrypto.js": "2.3.2", "compressorjs": "1.1.1", "emoji-picker-js": "https://github.com/Qortal/emoji-picker-js", + "localforage": "1.10.0", "prosemirror-commands": "1.5.0", "prosemirror-dropcursor": "1.6.1", "prosemirror-gapcursor": "1.3.1", @@ -40,7 +41,6 @@ "prosemirror-state": "1.4.2", "prosemirror-transform": "1.7.0", "prosemirror-view": "1.29.1", - "localforage": "1.10.0", "short-unique-id": "4.4.4" }, "devDependencies": { @@ -48,6 +48,7 @@ "@material/mwc-button": "0.27.0", "@material/mwc-checkbox": "0.27.0", "@material/mwc-dialog": "0.27.0", + "@material/mwc-fab": "0.27.0", "@material/mwc-formfield": "0.27.0", "@material/mwc-icon": "0.27.0", "@material/mwc-icon-button": "0.27.0", @@ -68,26 +69,27 @@ "@rollup/plugin-commonjs": "24.0.1", "@rollup/plugin-node-resolve": "15.0.1", "@rollup/plugin-replace": "5.0.2", - "@rollup/plugin-terser": "0.3.0", - "@vaadin/avatar": "23.3.5", - "@vaadin/button": "23.3.5", - "@vaadin/grid": "23.3.5", - "@vaadin/icons": "23.3.5", - "@vaadin/tooltip": "23.3.5", + "@rollup/plugin-terser": "0.4.0", + "@vaadin/avatar": "23.3.6", + "@vaadin/button": "23.3.6", + "@vaadin/grid": "23.3.6", + "@vaadin/icons": "23.3.6", + "@vaadin/tooltip": "23.3.6", + "@zip.js/zip.js": "^2.6.62", "epml": "0.3.3", "file-saver": "2.0.5", "highcharts": "10.3.3", "html-escaper": "3.0.3", "lit": "2.6.1", "lit-translate": "2.0.1", - "rollup": "3.10.1", + "passive-events-support": "1.0.33", + "rollup": "3.12.0", "rollup-plugin-node-globals": "1.4.0", "rollup-plugin-progress": "1.1.2", "rollup-plugin-web-worker-loader": "1.6.1", - "@zip.js/zip.js": "2.6.62", - "validator": "13.7.0" + "validator": "^13.7.0" }, "engines": { "node": ">=16.17.1" } -} \ No newline at end of file +} diff --git a/qortal-ui-plugins/plugins/core/components/ChatGifs.js b/qortal-ui-plugins/plugins/core/components/ChatGifs.js deleted file mode 100644 index 9a97986e..00000000 --- a/qortal-ui-plugins/plugins/core/components/ChatGifs.js +++ /dev/null @@ -1,585 +0,0 @@ -import { LitElement, html, css } from 'lit' -import { render } from 'lit/html.js' -import { Epml } from '../../../epml.js' -import * as zip from "@zip.js/zip.js"; -import { saveAs } from 'file-saver'; -import '@material/mwc-icon' -import ShortUniqueId from 'short-unique-id'; -import { publishData } from '../../utils/publish-image.js'; -import { get } from 'lit-translate'; - -import './ChatGifsExplore.js' -// import isAlphanumeric from 'validator/lib/isAlphanumeric'/ -const parentEpml = new Epml({ type: 'WINDOW', source: window.parent }) - -class ChatGifs extends LitElement { - static get properties() { - return { - selectedAddress: { type: Object }, - myGifCollections: { type: Array }, - mySubscribedCollections: {type: Array}, - exploreCollections: { type: Array }, - gifsToBeAdded: { type: Array}, - webWorkerImage: {type: Object}, - mode: {type: String}, - currentCollection: {type: String}, - isLoading: {type: String}, - newCollectionName: {type: String} - } - } - - static get styles() { - return css` - - ` - } - - constructor() { - super() - this.uid = new ShortUniqueId() - this.selectedAddress = window.parent.reduxStore.getState().app.selectedAddress - this.myGifCollections = [] - this.mySubscribedCollections = [] - this.exploreCollections = [] - this.myAccountName = '' - this.gifsToBeAdded = [] - // mode can be 'myCollection', 'newCollection', 'explore', 'subscribedCollection' - this.mode = "myCollection" - this.currentCollection = null - this.pageNumber = 0 - this.isLoading = false - this.newCollectionName = "" - } - - async structureCollections(gifCollections){ - try { - 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 getMetaDataGifs = (gifCollections || []).map(async (collection) => { - let collectionObj = collection - try { - const metaData = await parentEpml.request('apiCall', { - url: `/arbitrary/metadata/GIF_REPOSITORY/${this.myAccountName}/${collection.identifier}` - }) - - collectionObj = { - ...collection, - gifUrls: [] - } - if(metaData.files){ - const metaDataArray = metaData.files.split(';').map((data)=> { - return `${nodeUrl}/arbitrary/GIF_REPOSITORY/${this.myAccountName}/${collection.identifier}?filepath=${data}` - }) - - - collectionObj = { - ...collection, - gifUrls: metaDataArray - } - - } - - - - } catch (error) { - console.log(error) - } - - return collectionObj - }) - return await Promise.all(getMetaDataGifs) - } catch (error) { - - } - } - - - - - async getMoreExploreGifs(){ - try { - - const getAllGifCollections = await parentEpml.request("apiCall", { - type: "api", - url: `/arbitrary/resources?service=GIF_REPOSITORY&limit=20&offset=${this.pageNumber * 20}`, - }); - - const gifCollectionWithMetaData = await this.structureCollections(getAllGifCollections) - this.exploreCollections = [...this.exploreCollections, ...gifCollectionWithMetaData] - - this.pageNumber = this.pageNumber + 1 - } catch (error) { - console.error(error) - } - } - - async getCollectionList(){ - try { - return await parentEpml.request("apiCall", { - type: "api", - url: `/lists/gifSubscribedRepos`, - }); - - } catch (error) { - - } - } - - async addCollectionToList(collection){ - try { - - const body = { - - "items": [ - collection - ] - - } - const bodyToString = JSON.stringify(body) - await parentEpml.request("apiCall", { - type: "api", - method: "POST", - url: `/lists/gifSubscribedRepos`, - body: bodyToString, - headers: { - 'Content-Type': 'application/json' - } - }) - } catch (error) { - - } - } - - async removeCollectionFromList(collection){ - try { - - const body = { - - "items": [ - collection - ] - - } - const bodyToString = JSON.stringify(body) - await parentEpml.request("apiCall", { - type: "api", - method: 'DELETE', - url: `/lists/gifSubscribedRepos`, - body: bodyToString, - headers: { - 'Content-Type': 'application/json' - } - }) - } catch (error) { - - } - } - - async getMyGifCollections(){ - const userName = await this.getName(this.selectedAddress.address); - this.myAccountName = userName - if(this.myAccountName){ - const getMyGifColloctions = await parentEpml.request('apiCall', { - url: `/arbitrary/resources?service=GIF_REPOSITORY&limit=0&name=${this.myAccountName}` - }) - - const gifCollectionWithMetaData = await this.structureCollections(getMyGifColloctions) - - console.log({gifCollectionWithMetaData}) - this.myGifCollections = gifCollectionWithMetaData - } - } - async getAllCollections(){ - this.pageNumber = 0 - // for the explore section - const getAllGifCollections = await parentEpml.request("apiCall", { - type: "api", - url: `/arbitrary/resources?service=GIF_REPOSITORY&limit=20&offset=${this.pageNumber * 20}`, - }); - const gifCollectionWithMetaData = await this.structureCollections(getAllGifCollections) - this.exploreCollections = gifCollectionWithMetaData - this.pageNumber = this.pageNumber + 1 - } - - async getSavedCollections(){ - const getCollectionList = await this.getCollectionList() - - let savedCollections = [] - const getSavedGifRepos = (getCollectionList || []).map(async (collection) => { - let splitCollection = collection.split('/') - const name = splitCollection[0] - const identifier = splitCollection[1] - try { - console.log({collection}) - const data = await parentEpml.request('apiCall', { - url: `/arbitrary/resources?service=GIF_REPOSITORY&limit=0&name=${name}&identifier=${identifier}` - }) - if(data.length > 0){ - savedCollections.push(data[0]) - } - - - - } catch (error) { - console.log(error) - } - - return collection - }) - await Promise.all(getSavedGifRepos) - const savedCollectionsWithMetaData = await this.structureCollections(savedCollections) - this.mySubscribedCollections = savedCollectionsWithMetaData - } - - - async firstUpdated() { - - - try { - this.isLoading = true - await this.getMyGifCollections() - await this.getAllCollections() - await this.getSavedCollections() - this.isLoading = false - - - } catch (error) { - this.isLoading = false - console.error(error) - } - } - - async updated(changedProperties) { - console.log({changedProperties}) - if (changedProperties && changedProperties.has('mode')) { - const mode = this.mode - console.log({mode}) - if (mode === 'myCollection') { - try { - this.isLoading = true - - await this.getMyGifCollections() - this.isLoading = false - } catch (error) { - this.isLoading = false - } - - } - if (mode === 'explore') { - try { - this.isLoading = true - - await this.getAllCollections() - this.isLoading = false - } catch (error) { - this.isLoading = false - } - - } - if (mode === 'subscribedCollection') { - try { - this.isLoading = true - - await this.getSavedCollections() - this.isLoading = false - } catch (error) { - this.isLoading = false - } - - } - } - - - - } - - - - async getName (recipient) { - try { - const getNames = await parentEpml.request("apiCall", { - type: "api", - url: `/names/address/${recipient}`, - }); - - if (Array.isArray(getNames) && getNames.length > 0 ) { - return getNames[0].name - } else { - return '' - } - - } catch (error) { - return "" - } - } - - addGifs(gifs){ - console.log('gifs', gifs) - const mapGifs = gifs.map((file)=> { - return { - file, - name: file.name - } - }) - console.log({mapGifs}) - this.gifsToBeAdded = [...this.gifsToBeAdded, ...mapGifs] - console.log('this.gifsToBeAdded', this.gifsToBeAdded) - } - - async uploadGifCollection(){ - if(!this.newCollectionName){ - parentEpml.request('showSnackBar', get("chatpage.cchange27")); - return - } - - // if(!isAlphanumeric(this.newCollectionName)){ - // parentEpml.request('showSnackBar', get("chatpage.cchange27")); - // return - // } - try { - const userName = await this.getName(this.selectedAddress.address); - const doesNameExist = await parentEpml.request('apiCall', { - url: `/arbitrary/resources?service=GIF_REPOSITORY&limit=0&name=${userName}&identifier=${this.newCollectionName}` - }) - - if(doesNameExist.length !== 0){ - parentEpml.request('showSnackBar', get("chatpage.cchange27")); - return - } - function blobToBase64(blob) { - return new Promise((resolve, _) => { - const reader = new FileReader(); - reader.onloadend = () => resolve(reader.result); - reader.readAsDataURL(blob); - }); - } - const zipFileWriter = new zip.BlobWriter("application/zip"); -// Creates a TextReader object storing the text of the entry to add in the zip -// (i.e. "Hello world!"). -const helloWorldReader = new zip.TextReader("Hello world!"); - -// Creates a ZipWriter object writing data via `zipFileWriter`, adds the entry -// "hello.txt" containing the text "Hello world!" via `helloWorldReader`, and -// closes the writer. - -const zipWriter = new zip.ZipWriter(zipFileWriter, { bufferedWrite: true }); - - -for (let i = 0; i < this.gifsToBeAdded.length; i++) { - await zipWriter.add(this.gifsToBeAdded[i].name, new zip.BlobReader(this.gifsToBeAdded[i].file)); - } - - -await zipWriter.close(); -const zipFileBlob = await zipFileWriter.getData() -const blobTobase = await blobToBase64(zipFileBlob) -console.log({blobTobase}) - - if (!userName) { - parentEpml.request('showSnackBar', get("chatpage.cchange27")); - this.isLoading = false; - return; - } - const id = this.uid(); - const identifier = `gif_${id}`; -await publishData({ - registeredName: userName, - file : blobTobase.split(',')[1], - service: 'GIF_REPOSITORY', - identifier: this.newCollectionName, - parentEpml, - metaData: undefined, - uploadType: 'zip', - selectedAddress: this.selectedAddress, - worker: this.webWorkerImage, - isBase64: true - }) - - await new Promise((res)=> { - let interval = null - let stop = false - const getAnswer = async () => { - - - if (!stop) { - stop = true - try { - let myCollection = await parentEpml.request('apiCall', { - url: `/arbitrary/resources?service=GIF_REPOSITORY&limit=0&name=${userName}&identifier=${this.newCollectionName}` - }) - if (myCollection.length > 0) { - clearInterval(interval) - res() - } - } catch (error) {} - stop = false - } - } - interval = setInterval(getAnswer, 5000) - }) -saveAs(zipFileBlob, 'zipfile'); -console.log({zipFileBlob}) - } catch (error) { - console.log(error) - } - } - - setCurrentCollection(val){ - this.currentCollection = val - } - - render() { - console.log('this.currentCollection', this.currentCollection) - return html` -

-
- - - - - - -${this.mode === "myCollection" && !this.currentCollection ? html` - ${this.isLoading === true ? html` -

Loading...

- ` : ''} - ${this.myGifCollections.map((collection)=> { - return html` -
-

{ - this.currentCollection = collection - }}>${collection.identifier}

- -
- ` - })} - ` : ''} - ${this.mode === "subscribedCollection" && !this.currentCollection ? html` - ${this.isLoading === true ? html` -

Loading...

- ` : ''} - ${this.mySubscribedCollections.map((collection)=> { - return html` -
-

{ - this.currentCollection = collection - }}>${collection.identifier}

- -
- ` - })} - ` : ''} - ${this.mode === "explore" && !this.currentCollection ? html` - ${this.isLoading === true ? html` -

Loading...

- ` : ''} - this.getMoreExploreGifs(val)} .exploreCollections=${this.exploreCollections} - .setCurrentCollection=${(val)=> this.setCurrentCollection(val)} - > - - ` : ''} - ${this.currentCollection && this.mode === "myCollection" ? html` - - ${this.currentCollection.gifUrls.map((gif)=> { - console.log({gif}) - - return html` - { - e.target.src = gif - }} src=${gif} style="width: 50px; height: 50px" /> - ` - })} - ` : ''} - ${this.currentCollection && this.mode === "subscribedCollection" ? html` - - ${this.currentCollection.gifUrls.map((gif)=> { - console.log({gif}) - - return html` - { - e.target.src = gif - }} src=${gif} style="width: 50px; height: 50px" /> - ` - })} - ` : ''} - ${this.currentCollection && this.mode === "explore" ? html` - - - ${this.currentCollection.gifUrls.map((gif)=> { - console.log({gif}) - - return html` - { - e.target.src = gif - }} src=${gif} style="width: 50px; height: 50px" /> - ` - })} - ` : ''} - ${this.mode === "newCollection" ? html` - - - - { - this.newCollectionName = e.target.value - })} /> -
- ${this.gifsToBeAdded.map((gif, i)=> { - console.log({gif}) - return html` -
- - { - this.gifsToBeAdded[i] = { - ...gif, - name: e.target.value - } - })} /> -
- - ` - })} -
- ` : ''} - -
-
- ` - } - - -} - -window.customElements.define('chat-gifs', ChatGifs) diff --git a/qortal-ui-plugins/plugins/core/components/ChatGifs/ChatGifs-css.js b/qortal-ui-plugins/plugins/core/components/ChatGifs/ChatGifs-css.js new file mode 100644 index 00000000..11c2272b --- /dev/null +++ b/qortal-ui-plugins/plugins/core/components/ChatGifs/ChatGifs-css.js @@ -0,0 +1,41 @@ +import { css } from 'lit' + +export const gifExplorerStyles = css` + .gif-explorer-container { + display: flex; + flex-direction: column; + justify-content: center; + width: 100%; + align-items: center; + gap: 15px; + } + + .title-row { + display: flex; + justify-content: center; + align-items: center; + width: 100%; + } + + .gif-explorer-title { + flex: 1; + text-align: center; + font-family: Roboto, sans-serif; + letter-spacing: 0.8px; + font-size: 25px; + color: var(--chat-bubble-msg-color); + margin: 0; + } + + .explore-collections-icon { + margin-left: auto; + text-align: right; + font-size: 20px; + color: var(--chat-bubble-msg-color); + box-shadow: rgba(0, 0, 0, 0.1) 0px 20px 25px -5px, rgba(0, 0, 0, 0.04) 0px 10px 10px -5px; + padding: 5px; + background-color: var(--chat-menu-bg); + border: none; + border-radius: 12px; + } +` diff --git a/qortal-ui-plugins/plugins/core/components/ChatGifs/ChatGifs.js b/qortal-ui-plugins/plugins/core/components/ChatGifs/ChatGifs.js new file mode 100644 index 00000000..4b41e11f --- /dev/null +++ b/qortal-ui-plugins/plugins/core/components/ChatGifs/ChatGifs.js @@ -0,0 +1,594 @@ +import { LitElement, html, css } from 'lit' +import { render } from 'lit/html.js' +import { Epml } from '../../../../epml.js' +import * as zip from "@zip.js/zip.js"; +import { saveAs } from 'file-saver'; +import '@material/mwc-icon' +import ShortUniqueId from 'short-unique-id'; +import { publishData } from '../../../utils/publish-image.js'; +import { translate, get } from 'lit-translate'; +import { gifExplorerStyles } from "./ChatGifs-css.js"; +import './ChatGifsExplore.js'; +import '@vaadin/tooltip'; + +const parentEpml = new Epml({ type: 'WINDOW', source: window.parent }) + +class ChatGifs extends LitElement { + static get properties() { + return { + selectedAddress: { type: Object }, + myGifCollections: { type: Array }, + mySubscribedCollections: {type: Array}, + exploreCollections: { type: Array }, + gifsToBeAdded: { type: Array}, + webWorkerImage: {type: Object}, + mode: {type: String}, + currentCollection: {type: String}, + isLoading: {type: String}, + newCollectionName: {type: String} + } + } + + static styles = [gifExplorerStyles] + + constructor() { + super() + this.uid = new ShortUniqueId() + this.selectedAddress = window.parent.reduxStore.getState().app.selectedAddress + this.myGifCollections = [] + this.mySubscribedCollections = [] + this.exploreCollections = [] + this.myAccountName = '' + this.gifsToBeAdded = [] + // mode can be 'myCollection', 'newCollection', 'explore', 'subscribedCollection' + this.mode = "myCollection" + this.currentCollection = null + this.pageNumber = 0 + this.isLoading = false + this.newCollectionName = "" + } + + async structureCollections(gifCollections){ + try { + 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 getMetaDataGifs = (gifCollections || []).map(async (collection) => { + let collectionObj = collection + try { + const metaData = await parentEpml.request('apiCall', { + url: `/arbitrary/metadata/GIF_REPOSITORY/${this.myAccountName}/${collection.identifier}` + }) + + collectionObj = { + ...collection, + gifUrls: [] + } + if(metaData.files){ + const metaDataArray = metaData.files.map((data)=> { + return `${nodeUrl}/arbitrary/GIF_REPOSITORY/${this.myAccountName}/${collection.identifier}?filepath=${data}` + }) + + + collectionObj = { + ...collection, + gifUrls: metaDataArray + } + + } + + } catch (error) { + console.log(error) + } + + return collectionObj + }) + return await Promise.all(getMetaDataGifs) + } catch (error) { + + } + } + + async getMoreExploreGifs(){ + try { + const getAllGifCollections = await parentEpml.request("apiCall", { + type: "api", + url: `/arbitrary/resources?service=GIF_REPOSITORY&limit=20&offset=${this.pageNumber * 20}`, + }); + + const gifCollectionWithMetaData = await this.structureCollections(getAllGifCollections) + this.exploreCollections = [...this.exploreCollections, ...gifCollectionWithMetaData] + + this.pageNumber = this.pageNumber + 1 + } catch (error) { + console.error(error) + } + } + + async getCollectionList(){ + try { + return await parentEpml.request("apiCall", { + type: "api", + url: `/lists/gifSubscribedRepos`, + }); + + } catch (error) { + + } + } + + async addCollectionToList(collection){ + try { + const body = { + + "items": [ + collection + ] + + } + + const bodyToString = JSON.stringify(body) + await parentEpml.request("apiCall", { + type: "api", + method: "POST", + url: `/lists/gifSubscribedRepos`, + body: bodyToString, + headers: { + 'Content-Type': 'application/json' + } + }) + } catch (error) { + + } + } + + async removeCollectionFromList(collection){ + try { + + const body = { + + "items": [ + collection + ] + + } + const bodyToString = JSON.stringify(body) + await parentEpml.request("apiCall", { + type: "api", + method: 'DELETE', + url: `/lists/gifSubscribedRepos`, + body: bodyToString, + headers: { + 'Content-Type': 'application/json' + } + }) + } catch (error) { + + } + } + + async getMyGifCollections(){ + const userName = await this.getName(this.selectedAddress.address); + this.myAccountName = userName + if(this.myAccountName){ + const getMyGifColloctions = await parentEpml.request('apiCall', { + url: `/arbitrary/resources?service=GIF_REPOSITORY&limit=0&name=${this.myAccountName}` + }) + const gifCollectionWithMetaData = await this.structureCollections(getMyGifColloctions) + + console.log({gifCollectionWithMetaData}) + this.myGifCollections = gifCollectionWithMetaData + } + } + async getAllCollections(){ + this.pageNumber = 0 + // for the explore section + const getAllGifCollections = await parentEpml.request("apiCall", { + type: "api", + url: `/arbitrary/resources?service=GIF_REPOSITORY&limit=20&offset=${this.pageNumber * 20}`, + }); + const gifCollectionWithMetaData = await this.structureCollections(getAllGifCollections) + this.exploreCollections = gifCollectionWithMetaData + this.pageNumber = this.pageNumber + 1 + } + + async getSavedCollections(){ + const getCollectionList = await this.getCollectionList() + let savedCollections = [] + const getSavedGifRepos = (getCollectionList || []).map(async (collection) => { + let splitCollection = collection.split('/') + const name = splitCollection[0] + const identifier = splitCollection[1] + try { + console.log({collection}) + const data = await parentEpml.request('apiCall', { + url: `/arbitrary/resources?service=GIF_REPOSITORY&limit=0&name=${name}&identifier=${identifier}` + }) + if(data.length > 0){ + savedCollections.push(data[0]) + } + } catch (error) { + console.log(error) + } + return collection + }) + await Promise.all(getSavedGifRepos) + const savedCollectionsWithMetaData = await this.structureCollections(savedCollections) + this.mySubscribedCollections = savedCollectionsWithMetaData + } + + async firstUpdated() { + const tooltip = this.shadowRoot.querySelector('vaadin-tooltip'); + const overlay = tooltip.shadowRoot.querySelector('vaadin-tooltip-overlay'); + overlay.shadowRoot.getElementById("overlay").style.cssText = "background-color: transparent; box-shadow: rgb(50 50 93 / 25%) 0px 2px 5px -1px, rgb(0 0 0 / 30%) 0px 1px 3px -1px"; + overlay.shadowRoot.getElementById('content').style.cssText = "background-color: var(--reactions-tooltip-bg); color: var(--chat-bubble-msg-color); text-align: center; padding: 20px 10px; border-radius: 8px; font-family: Roboto, sans-serif; letter-spacing: 0.3px; font-weight: 300; font-size: 13.5px; transition: all 0.3s ease-in-out;"; + + try { + this.isLoading = true + await this.getMyGifCollections() + await this.getAllCollections() + await this.getSavedCollections() + this.isLoading = false + } catch (error) { + this.isLoading = false + console.error(error) + } + } + + async updated(changedProperties) { + console.log({changedProperties}) + if (changedProperties && changedProperties.has('mode')) { + const mode = this.mode + console.log({mode}) + if (mode === 'myCollection') { + try { + this.isLoading = true + + await this.getMyGifCollections() + this.isLoading = false + } catch (error) { + this.isLoading = false + } + + } + + if (mode === 'explore') { + try { + this.isLoading = true + + await this.getAllCollections() + this.isLoading = false + } catch (error) { + this.isLoading = false + } + + } + if (mode === 'subscribedCollection') { + try { + this.isLoading = true + + await this.getSavedCollections() + this.isLoading = false + } catch (error) { + this.isLoading = false + } + + } + } + + + + } + + async getName (recipient) { + try { + const getNames = await parentEpml.request("apiCall", { + type: "api", + url: `/names/address/${recipient}`, + }); + + if (Array.isArray(getNames) && getNames.length > 0 ) { + return getNames[0].name + } else { + return '' + } + + } catch (error) { + return "" + } + } + + addGifs(gifs){ + console.log('gifs', gifs) + const mapGifs = gifs.map((file)=> { + return { + file, + name: file.name + } + }) + console.log({mapGifs}) + this.gifsToBeAdded = [...this.gifsToBeAdded, ...mapGifs] + console.log('this.gifsToBeAdded', this.gifsToBeAdded) + } + + async uploadGifCollection(){ + if(!this.newCollectionName){ + parentEpml.request('showSnackBar', get("chatpage.cchange27")); + return + } + + // if(!isAlphanumeric(this.newCollectionName)){ + // parentEpml.request('showSnackBar', get("chatpage.cchange27")); + // return + // } + try { + const userName = await this.getName(this.selectedAddress.address); + const doesNameExist = await parentEpml.request('apiCall', { + url: `/arbitrary/resources?service=GIF_REPOSITORY&limit=0&name=${userName}&identifier=${this.newCollectionName}` + }) + + if(doesNameExist.length !== 0){ + parentEpml.request('showSnackBar', get("chatpage.cchange27")); + return + } + function blobToBase64(blob) { + return new Promise((resolve, _) => { + const reader = new FileReader(); + reader.onloadend = () => resolve(reader.result); + reader.readAsDataURL(blob); + }); + } + const zipFileWriter = new zip.BlobWriter("application/zip"); +// Creates a TextReader object storing the text of the entry to add in the zip +// (i.e. "Hello world!"). + const helloWorldReader = new zip.TextReader("Hello world!"); + +// Creates a ZipWriter object writing data via `zipFileWriter`, adds the entry +// "hello.txt" containing the text "Hello world!" via `helloWorldReader`, and +// closes the writer. + + const zipWriter = new zip.ZipWriter(zipFileWriter, { bufferedWrite: true }); + + + for (let i = 0; i < this.gifsToBeAdded.length; i++) { + await zipWriter.add(this.gifsToBeAdded[i].name, new zip.BlobReader(this.gifsToBeAdded[i].file)); + } + + + await zipWriter.close(); + const zipFileBlob = await zipFileWriter.getData() + const blobTobase = await blobToBase64(zipFileBlob) + console.log({blobTobase}) + + if (!userName) { + parentEpml.request('showSnackBar', get("chatpage.cchange27")); + this.isLoading = false; + return; + } + const id = this.uid(); + const identifier = `gif_${id}`; + await publishData({ + registeredName: userName, + file : blobTobase.split(',')[1], + service: 'GIF_REPOSITORY', + identifier: this.newCollectionName, + parentEpml, + metaData: undefined, + uploadType: 'zip', + selectedAddress: this.selectedAddress, + worker: this.webWorkerImage, + isBase64: true + }) + + await new Promise((res)=> { + let interval = null + let stop = false + const getAnswer = async () => { + if (!stop) { + stop = true + try { + let myCollection = await parentEpml.request('apiCall', { + url: `/arbitrary/resources?service=GIF_REPOSITORY&limit=0&name=${userName}&identifier=${this.newCollectionName}` + }) + if (myCollection.length > 0) { + clearInterval(interval) + res() + } + } catch (error) {} + stop = false + } + } + interval = setInterval(getAnswer, 5000) + }) + + saveAs(zipFileBlob, 'zipfile'); + console.log({zipFileBlob}) + } catch (error) { + console.log(error) + } + } + + setCurrentCollection(val){ + this.currentCollection = val + } + + render() { + console.log('this.currentCollection', this.currentCollection) + console.log(3, "chat gifs here") + return html` +
+
+

${translate("chatpage.cchange80")}

+ { + if(this.isLoading) return; + this.mode = "explore"; + }} + icon="vaadin:search" + slot="icon"> + + + +
+
+ + + + ${this.mode === "myCollection" && !this.currentCollection ? html` + ${this.isLoading === true ? html` +

Loading...

+ ` : ''} + ${this.myGifCollections.map((collection)=> { + return html` +
+

{ + this.currentCollection = collection + }}>${collection.identifier}

+ +
+ ` + })} + ` : ''} + ${this.mode === "subscribedCollection" && !this.currentCollection ? html` + ${this.isLoading === true ? html` +

Loading...

+ ` : ''} + ${this.mySubscribedCollections.map((collection)=> { + return html` +
+

{ + this.currentCollection = collection + }}>${collection.identifier}

+ +
+ ` + })} + ` : ''} + ${this.mode === "explore" && !this.currentCollection ? html` + ${this.isLoading === true ? html` +

Loading...

+ ` : ''} + this.getMoreExploreGifs(val)} .exploreCollections=${this.exploreCollections} + .setCurrentCollection=${(val)=> this.setCurrentCollection(val)} + > + + ` : ''} + ${this.currentCollection && this.mode === "myCollection" ? html` + + ${this.currentCollection.gifUrls.map((gif)=> { + console.log({gif}) + + return html` + { + e.target.src = gif + }} src=${gif} style="width: 50px; height: 50px" /> + ` + })} + ` : ''} + ${this.currentCollection && this.mode === "subscribedCollection" ? html` + + ${this.currentCollection.gifUrls.map((gif)=> { + console.log({gif}) + + return html` + { + e.target.src = gif + }} src=${gif} style="width: 50px; height: 50px" /> + ` + })} + ` : ''} + ${this.currentCollection && this.mode === "explore" ? html` + + + ${this.currentCollection.gifUrls.map((gif)=> { + console.log({gif}) + + return html` + { + e.target.src = gif + }} src=${gif} style="width: 50px; height: 50px" /> + ` + })} + ` : ''} + ${this.mode === "newCollection" ? html` + + + + { + this.newCollectionName = e.target.value + })} /> +
+ ${this.gifsToBeAdded.map((gif, i)=> { + console.log({gif}) + return html` +
+ + { + this.gifsToBeAdded[i] = { + ...gif, + name: e.target.value + } + })} /> +
+ + ` + })} +
+ ` : ''} + +
+
+ ` + } + + +} + +window.customElements.define('chat-gifs', ChatGifs) diff --git a/qortal-ui-plugins/plugins/core/components/ChatGifsExplore.js b/qortal-ui-plugins/plugins/core/components/ChatGifs/ChatGifsExplore.js similarity index 96% rename from qortal-ui-plugins/plugins/core/components/ChatGifsExplore.js rename to qortal-ui-plugins/plugins/core/components/ChatGifs/ChatGifsExplore.js index bbd7fc3d..b5cca445 100644 --- a/qortal-ui-plugins/plugins/core/components/ChatGifsExplore.js +++ b/qortal-ui-plugins/plugins/core/components/ChatGifs/ChatGifsExplore.js @@ -1,11 +1,11 @@ import { LitElement, html, css } from 'lit' import { render } from 'lit/html.js' -import { Epml } from '../../../epml.js' +import { Epml } from '../../../../epml.js' import * as zip from "@zip.js/zip.js"; import { saveAs } from 'file-saver'; import '@material/mwc-icon' import ShortUniqueId from 'short-unique-id'; -import { publishData } from '../../utils/publish-image.js'; +import { publishData } from '../../../utils/publish-image.js'; const parentEpml = new Epml({ type: 'WINDOW', source: window.parent }) diff --git a/qortal-ui-plugins/plugins/core/components/ChatPage.js b/qortal-ui-plugins/plugins/core/components/ChatPage.js index 5ebc0247..e3bf62c3 100644 --- a/qortal-ui-plugins/plugins/core/components/ChatPage.js +++ b/qortal-ui-plugins/plugins/core/components/ChatPage.js @@ -11,7 +11,7 @@ import {unsafeHTML} from 'lit/directives/unsafe-html.js'; import { Editor, Extension } from '@tiptap/core' import * as zip from "@zip.js/zip.js"; import { saveAs } from 'file-saver'; -import './ChatGifs' +import './ChatGifs/ChatGifs.js'; // import localForage from "localforage"; registerTranslateConfig({ loader: lang => fetch(`/language/${lang}.json`).then(res => res.json()) @@ -44,10 +44,6 @@ import WebWorker from 'web-worker:./computePowWorker.js'; import WebWorkerImage from 'web-worker:./computePowWorkerImage.js'; import '@polymer/paper-dialog/paper-dialog.js' -// const messagesCache = localForage.createInstance({ -// name: "messages-cache", -// }); - const parentEpml = new Epml({ type: 'WINDOW', source: window.parent }) class ChatPage extends LitElement { @@ -111,724 +107,748 @@ class ChatPage extends LitElement { } static get styles() { - return css` - html { - scroll-behavior: smooth; - } + return css` + html { + scroll-behavior: smooth; + } + + .chat-head-container { + display: flex; + justify-content: flex-start; + flex-direction: column; + height: 50vh; + overflow-y: auto; + overflow-x: hidden; + width: 100%; + } - .chat-head-container { + .repliedTo-container { + display: flex; + flex-direction: row; + justify-content: space-between; + align-items: center; + padding: 10px 10px 8px 10px; + } + + .senderName { + margin: 0; + color: var(--mdc-theme-primary); + font-weight: bold; + user-select: none; + } + + .original-message { + color: var(--chat-bubble-msg-color); + text-overflow: ellipsis; + overflow: hidden; + white-space: nowrap; + margin: 0; + width: 800px; + } + + + .close-icon { + color: #676b71; + width: 18px; + transition: all 0.1s ease-in-out; + } + + .close-icon:hover { + cursor: pointer; + color: #494c50; + } + + .chat-text-area .typing-area .chatbar { + position: relative; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + height: auto; + padding: 5px 5px 5px 7px; + overflow: hidden; + } + + .chat-text-area .typing-area .emoji-button { + width: 45px; + height: 40px; + padding-top: 4px; + border: none; + outline: none; + background: transparent; + cursor: pointer; + max-height: 40px; + 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; + background-color: var(--white); + padding: 5px; + border-radius: 1px; + } + + .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; + width: 100%; + } + + .message-size { + font-family: Roboto, sans-serif; + font-size: 12px; + color: black; + } + + .lds-grid { + width: 120px; + height: 120px; + position: absolute; + left: 50%; + top: 40%; + } + + img { + border-radius: 25%; + } + + .dialogCustom { + position: fixed; + z-index: 10000; + display: flex; + justify-content: center; + flex-direction: column; + align-items: center; + top: 10px; + right: 20px; + user-select: none; + } + + .dialogCustomInner { + min-width: 300px; + height: 40px; + background-color: var(--white); + box-shadow: rgb(119 119 119 / 32%) 0px 4px 12px; + padding: 10px; + border-radius: 4px; + } + + .dialogCustomInner ul { + padding-left: 0px + } + + .dialogCustomInner li { + margin-bottom: 10px; + } + + .marginLoader { + margin-right: 8px; + } + + .last-message-ref { + position: absolute; + font-size: 18px; + top: -40px; + right: 30px; + width: 50; + height: 50; + z-index: 5; + color: black; + background-color: white; + border-radius: 50%; + transition: all 0.1s ease-in-out; + } + + .last-message-ref:hover { + cursor: pointer; + transform: scale(1.1); + } + + .arrow-down-icon { + transform: scale(1.15); + } + + .chat-container { + display: grid; + max-height: 100%; + } + + .chat-text-area { + display: flex; + position: relative; + justify-content: center; + min-height: 60px; + max-height: 100%; + } + + .chat-text-area .typing-area { + display: flex; + flex-direction: column; + width: 98%; + box-sizing: border-box; + margin-bottom: 8px; + border: 1px solid var(--chat-bubble-bg); + border-radius: 10px; + background: var(--chat-bubble-bg); + } + + .chat-text-area .typing-area textarea { + display: none; + } + + .chat-text-area .typing-area .chat-editor { + display: flex; + max-height: -webkit-fill-available; + width: 100%; + border-color: transparent; + margin: 0; + padding: 0; + border: none; + } + + .repliedTo-container { + display: flex; + flex-direction: row; + justify-content: space-between; + align-items: center; + padding: 10px 10px 8px 10px; + } + + .repliedTo-subcontainer { + display: flex; + flex-direction: row; + align-items: center; + gap: 15px; + width: 100%; + } + + .repliedTo-message { + display: flex; + flex-direction: column; + gap: 5px; + width: 100%; + word-break: break-all; + text-overflow: ellipsis; + overflow: hidden; + max-height: 60px; + } + .repliedTo-message p { + margin: 0px; + padding: 0px; + } + + .repliedTo-message pre { + white-space: pre-wrap; + } + + .repliedTo-message p mark { + background-color: #ffe066; + border-radius: 0.25em; + box-decoration-break: clone; + padding: 0.125em 0; + } + + .reply-icon { + width: 20px; + color: var(--mdc-theme-primary); + } + + .close-icon { + color: #676b71; + width: 18px; + transition: all 0.1s ease-in-out; + } + + .close-icon:hover { + cursor: pointer; + color: #494c50; + } + + .chatbar-container { + width: 100%; + display: flex; + height: auto; + overflow: hidden; + } + + .lds-grid { + width: 120px; + height: 120px; + position: absolute; + left: 50%; + top: 40%; + } + + .lds-grid div { + position: absolute; + width: 34px; + height: 34px; + border-radius: 50%; + background: #03a9f4; + animation: lds-grid 1.2s linear infinite; + } + + .lds-grid div:nth-child(1) { + top: 4px; + left: 4px; + animation-delay: 0s; + } + + .lds-grid div:nth-child(2) { + top: 4px; + left: 48px; + animation-delay: -0.4s; + } + + .lds-grid div:nth-child(3) { + top: 4px; + left: 90px; + animation-delay: -0.8s; + } + + .lds-grid div:nth-child(4) { + top: 50px; + left: 4px; + animation-delay: -0.4s; + } + + .lds-grid div:nth-child(5) { + top: 50px; + left: 48px; + animation-delay: -0.8s; + } + + .lds-grid div:nth-child(6) { + top: 50px; + left: 90px; + animation-delay: -1.2s; + } + + .lds-grid div:nth-child(7) { + top: 95px; + left: 4px; + animation-delay: -0.8s; + } + + .lds-grid div:nth-child(8) { + top: 95px; + left: 48px; + animation-delay: -1.2s; + } + + .lds-grid div:nth-child(9) { + top: 95px; + left: 90px; + animation-delay: -1.6s; + } + + @keyframes lds-grid { + 0%, 100% { + opacity: 1; + } + 50% { + opacity: 0.5; + } + } + + .float-left { + float: left; + } + + img { + border-radius: 25%; + } + + paper-dialog.warning { + width: 50%; + max-width: 50vw; + height: 30%; + max-height: 30vh; + text-align: center; + background-color: var(--white); + color: var(--black); + border: 1px solid var(--black); + border-radius: 15px; + line-height: 1.6; + overflow-y: auto; + } + .buttons { + text-align:right; + } + + .dialogCustom { + position: fixed; + z-index: 10000; + display: flex; + justify-content: center; + flex-direction: column; + align-items: center; + top: 10px; + right: 20px; + user-select: none; + } + + .dialogCustom p { + color: var(--black) + } + + .dialogCustomInner { + min-width: 300px; + height: 40px; + background-color: var(--white); + box-shadow: rgb(119 119 119 / 32%) 0px 4px 12px; + padding: 10px; + border-radius: 4px; + } + + .dialogCustomInner ul { + padding-left: 0px + } + + .dialogCustomInner li { + margin-bottom: 10px; + } + + .marginLoader { + margin-right: 8px; + } + + .smallLoading, + .smallLoading:after { + border-radius: 50%; + width: 2px; + height: 2px; + } + + .smallLoading { + border-width: 0.8em; + border-style: solid; + border-color: rgba(3, 169, 244, 0.2) rgba(3, 169, 244, 0.2) + rgba(3, 169, 244, 0.2) rgb(3, 169, 244); + font-size: 10px; + position: relative; + text-indent: -9999em; + transform: translateZ(0px); + animation: 1.1s linear 0s infinite normal none running loadingAnimation; + } + + @-webkit-keyframes loadingAnimation { + 0% { + -webkit-transform: rotate(0deg); + transform: rotate(0deg); + } + 100% { + -webkit-transform: rotate(360deg); + transform: rotate(360deg); + } + } + + @keyframes loadingAnimation { + 0% { + -webkit-transform: rotate(0deg); + transform: rotate(0deg); + } + 100% { + -webkit-transform: rotate(360deg); + transform: rotate(360deg); + } + } + + /* Add Image Modal Dialog Styling */ + + .dialog-container { + position: relative; + display: flex; + align-items: center; + flex-direction: column; + padding: 0 10px; + gap: 10px; + height: 100%; + } + + .dialog-container-title { + font-family: Montserrat; + color: var(--black); + font-size: 20px; + margin: 15px 0 0 0; + } + + .divider { + height: 1px; + background-color: var(--chat-bubble-msg-color); + user-select: none; + width: 70%; + margin-bottom: 20px; + } + + .dialog-container-loader { + position: relative; + display: flex; + align-items: center; + padding: 0 10px; + gap: 10px; + height: 100%; + } + + .dialog-image { + width: 100%; + max-height: 300px; + border-radius: 0; + object-fit: contain; + } + + .chat-right-panel { + flex: 0; + border-left: 3px solid rgb(221, 221, 221); + height: 100%; + overflow-y: auto; + background: transparent; + } + + .movedin { + flex: 1 !important; + background: transparent; + } + + .main-container { + display: flex; + height: 100%; + } + + .group-nav-container { + display: flex; + height: 40px; + padding: 25px 5px 25px 20px; + margin: 0px; + background-color: var(--chat-bubble-bg); + box-sizing: border-box; + align-items: center; + justify-content: space-between; + box-shadow: var(--group-drop-shadow); + } + + .top-bar-icon { + border-radius: 50%; + color: var(--chat-bubble-msg-color); + transition: 0.3s all ease-in-out; + padding: 5px; + background-color: transparent; + } + + .top-bar-icon:hover { + background-color: #e6e6e69b; + cursor: pointer; + color: var(--black) + } + + .group-name { + font-family: Raleway, sans-serif; + font-size: 16px; + color: var(--black); + margin:0px; + padding:0px; + } + + .modal-button-row { + display: flex; + align-items: center; + justify-content: space-between; + width: 100%; + } + + .modal-button { + font-family: Roboto, sans-serif; + font-size: 16px; + color: var(--mdc-theme-primary); + background-color: transparent; + padding: 8px 10px; + border-radius: 5px; + border: none; + transition: all 0.3s ease-in-out; + } + + .modal-button-red { + font-family: Roboto, sans-serif; + font-size: 16px; + color: #F44336; + background-color: transparent; + padding: 8px 10px; + border-radius: 5px; + border: none; + transition: all 0.3s ease-in-out; + } + + .modal-button-red:hover { + cursor: pointer; + background-color: #f4433663; + } + + .modal-button:hover { + cursor: pointer; + background-color: #03a8f475; + } + + .name-input { + width: 100%; + margin-bottom: 15px; + outline: 0; + border-width: 0 0 2px; + border-color: var(--mdc-theme-primary); + background-color: transparent; + padding: 10px; + font-family: Roboto, sans-serif; + font-size: 15px; + color: var(--chat-bubble-msg-color); + box-sizing: border-box; + } + + .name-input::selection { + background-color: var(--mdc-theme-primary); + color: white; + } + + .name-input::placeholder { + opacity: 0.9; + color: var(--black); + } + + .search-results-div { + position: absolute; + top: 25px; + right: 25px; + } + + .search-field { + width: 100%; + position: relative; + margin-bottom: 5px; + } + + .search-icon { + position: absolute; + right: 3px; + top: 0; + color: var(--chat-bubble-msg-color); + transition: all 0.3s ease-in-out; + background: none; + border-radius: 50%; + padding: 6px 3px; + font-size: 21px; + } + + .search-icon:hover { + cursor: pointer; + background: #d7d7d75c; + } + + .user-verified { + position: absolute; + top: 0; + right: 5px; display: flex; - justify-content: flex-start; - flex-direction: column; - height: 50vh; - overflow-y: auto; - overflow-x: hidden; - width: 100%; - } - - .repliedTo-container { + align-items: center; + gap: 10px; + color: #04aa2e; + font-size: 13px; + } + + .user-selected { display: flex; - flex-direction: row; justify-content: space-between; align-items: center; - padding: 10px 10px 8px 10px; - } - - .senderName { margin: 0; - color: var(--mdc-theme-primary); - font-weight: bold; - user-select: none; - } - - .original-message { + box-shadow: rgb(0 0 0 / 16%) 0px 3px 6px, rgb(0 0 0 / 23%) 0px 3px 6px; + padding: 18px 20px; color: var(--chat-bubble-msg-color); - text-overflow: ellipsis; - overflow: hidden; - white-space: nowrap; - margin: 0; - width: 800px; - } - - - .close-icon { - color: #676b71; - width: 18px; - transition: all 0.1s ease-in-out; - } - - .close-icon:hover { - cursor: pointer; - color: #494c50; - } + border-radius: 5px; + background-color: #ececec96; + } - .chat-text-area .typing-area .chatbar { + .user-selected-name { + font-family: Roboto, sans-serif; + margin: 0; + font-size: 16px; + } + + .forwarding-container { + display: flex; + gap: 15px; + } + + .user-selected-forwarding { + font-family: Livvic, sans-serif; + margin: 0; + font-size: 16px; + } + + .close-forwarding { + color: #676b71; + width: 14px; + transition: all 0.1s ease-in-out; + } + + .close-forwarding:hover { + cursor: pointer; + color: #4e5054; + } + + .gifs-backdrop { + height: 100vh; + width: 100vw; + background: transparent; + position: fixed; + } + + .gifs-container { position: relative; display: flex; - flex-direction: column; - justify-content: center; + padding: 10px 15px; + border-radius: 12px; + box-shadow: rgba(0, 0, 0, 0.09) 0px 3px 12px; + background-color: var(--chat-menu-bg); align-items: center; - height: auto; - padding: 5px 5px 5px 7px; - overflow: hidden; - } - - .chat-text-area .typing-area .emoji-button { - width: 45px; - height: 40px; - padding-top: 4px; - border: none; - outline: none; - background: transparent; - cursor: pointer; - max-height: 40px; - 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; - background-color: var(--white); - padding: 5px; - border-radius: 1px; - } - - .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; - width: 100%; - } - - .message-size { - font-family: Roboto, sans-serif; - font-size: 12px; - color: black; - } - - .lds-grid { - width: 120px; - height: 120px; - position: absolute; - left: 50%; - top: 40%; - } - - img { - border-radius: 25%; - } - - .dialogCustom { - position: fixed; - z-index: 10000; - display: flex; - justify-content: center; - flex-direction: column; - align-items: center; - top: 10px; - right: 20px; - user-select: none; - } - - .dialogCustomInner { - min-width: 300px; - height: 40px; - background-color: var(--white); - box-shadow: rgb(119 119 119 / 32%) 0px 4px 12px; - padding: 10px; - border-radius: 4px; - } - - .dialogCustomInner ul { - padding-left: 0px - } - - .dialogCustomInner li { - margin-bottom: 10px; - } - - .marginLoader { - margin-right: 8px; - } - - .last-message-ref { - position: absolute; - font-size: 18px; - top: -40px; - right: 30px; - width: 50; - height: 50; + width: fit-content; + justify-self: flex-end; + margin-bottom: 8px; + margin-right: 5px; + box-shadow: var(--gifs-drop-shadow); z-index: 5; - color: black; - background-color: white; - border-radius: 50%; - transition: all 0.1s ease-in-out; } - - .last-message-ref:hover { - cursor: pointer; - transform: scale(1.1); + ` } - .arrow-down-icon { - transform: scale(1.15); - } - - .chat-container { - display: grid; - max-height: 100%; - } - - .chat-text-area { - display: flex; - position: relative; - justify-content: center; - min-height: 60px; - max-height: 100%; - } - - .chat-text-area .typing-area { - display: flex; - flex-direction: column; - width: 98%; - box-sizing: border-box; - margin-bottom: 8px; - border: 1px solid var(--chat-bubble-bg); - border-radius: 10px; - background: var(--chat-bubble-bg); - } - - .chat-text-area .typing-area textarea { - display: none; - } - - .chat-text-area .typing-area .chat-editor { - display: flex; - max-height: -webkit-fill-available; - width: 100%; - border-color: transparent; - margin: 0; - padding: 0; - border: none; - } - - .repliedTo-container { - display: flex; - flex-direction: row; - justify-content: space-between; - align-items: center; - padding: 10px 10px 8px 10px; - } - - .repliedTo-subcontainer { - display: flex; - flex-direction: row; - align-items: center; - gap: 15px; - width: 100%; - } - - .repliedTo-message { - display: flex; - flex-direction: column; - gap: 5px; - width: 100%; - word-break: break-all; - text-overflow: ellipsis; - overflow: hidden; - max-height: 60px; - } - .repliedTo-message p { - margin: 0px; - padding: 0px; - } - - .repliedTo-message pre { - white-space: pre-wrap; - } - - .repliedTo-message p mark { - background-color: #ffe066; - border-radius: 0.25em; - box-decoration-break: clone; - padding: 0.125em 0; - } - - .reply-icon { - width: 20px; - color: var(--mdc-theme-primary); - } - - .close-icon { - color: #676b71; - width: 18px; - transition: all 0.1s ease-in-out; - } - - .close-icon:hover { - cursor: pointer; - color: #494c50; - } - - .chatbar-container { - width: 100%; - display: flex; - height: auto; - overflow: hidden; - } - - .lds-grid { - width: 120px; - height: 120px; - position: absolute; - left: 50%; - top: 40%; - } - - .lds-grid div { - position: absolute; - width: 34px; - height: 34px; - border-radius: 50%; - background: #03a9f4; - animation: lds-grid 1.2s linear infinite; - } - - .lds-grid div:nth-child(1) { - top: 4px; - left: 4px; - animation-delay: 0s; - } - - .lds-grid div:nth-child(2) { - top: 4px; - left: 48px; - animation-delay: -0.4s; - } - - .lds-grid div:nth-child(3) { - top: 4px; - left: 90px; - animation-delay: -0.8s; - } - - .lds-grid div:nth-child(4) { - top: 50px; - left: 4px; - animation-delay: -0.4s; - } - - .lds-grid div:nth-child(5) { - top: 50px; - left: 48px; - animation-delay: -0.8s; - } - - .lds-grid div:nth-child(6) { - top: 50px; - left: 90px; - animation-delay: -1.2s; - } - - .lds-grid div:nth-child(7) { - top: 95px; - left: 4px; - animation-delay: -0.8s; - } - - .lds-grid div:nth-child(8) { - top: 95px; - left: 48px; - animation-delay: -1.2s; - } - - .lds-grid div:nth-child(9) { - top: 95px; - left: 90px; - animation-delay: -1.6s; - } - - @keyframes lds-grid { - 0%, 100% { - opacity: 1; - } - 50% { - opacity: 0.5; - } -} - - .float-left { - float: left; - } - - img { - border-radius: 25%; - } - - paper-dialog.warning { - width: 50%; - max-width: 50vw; - height: 30%; - max-height: 30vh; - text-align: center; - background-color: var(--white); - color: var(--black); - border: 1px solid var(--black); - border-radius: 15px; - line-height: 1.6; - overflow-y: auto; - } - .buttons { - text-align:right; - } - - .dialogCustom { - position: fixed; - z-index: 10000; - display: flex; - justify-content: center; - flex-direction: column; - align-items: center; - top: 10px; - right: 20px; - user-select: none; - } - - .dialogCustom p { - color: var(--black) - } - - .dialogCustomInner { - min-width: 300px; - height: 40px; - background-color: var(--white); - box-shadow: rgb(119 119 119 / 32%) 0px 4px 12px; - padding: 10px; - border-radius: 4px; - } - - .dialogCustomInner ul { - padding-left: 0px - } - - .dialogCustomInner li { - margin-bottom: 10px; - } - - .marginLoader { - margin-right: 8px; - } - - .smallLoading, - .smallLoading:after { - border-radius: 50%; - width: 2px; - height: 2px; - } - - .smallLoading { - border-width: 0.8em; - border-style: solid; - border-color: rgba(3, 169, 244, 0.2) rgba(3, 169, 244, 0.2) - rgba(3, 169, 244, 0.2) rgb(3, 169, 244); - font-size: 10px; - position: relative; - text-indent: -9999em; - transform: translateZ(0px); - animation: 1.1s linear 0s infinite normal none running loadingAnimation; - } - - @-webkit-keyframes loadingAnimation { - 0% { - -webkit-transform: rotate(0deg); - transform: rotate(0deg); - } - 100% { - -webkit-transform: rotate(360deg); - transform: rotate(360deg); - } - } - - @keyframes loadingAnimation { - 0% { - -webkit-transform: rotate(0deg); - transform: rotate(0deg); - } - 100% { - -webkit-transform: rotate(360deg); - transform: rotate(360deg); - } - } - - /* Add Image Modal Dialog Styling */ - - .dialog-container { - position: relative; - display: flex; - align-items: center; - flex-direction: column; - padding: 0 10px; - gap: 10px; - height: 100%; - } - - .dialog-container-title { - font-family: Montserrat; - color: var(--black); - font-size: 20px; - margin: 15px 0 0 0; - } - - .divider { - height: 1px; - background-color: var(--chat-bubble-msg-color); - user-select: none; - width: 70%; - margin-bottom: 20px; - } - - .dialog-container-loader { - position: relative; - display: flex; - align-items: center; - padding: 0 10px; - gap: 10px; - height: 100%; - } - - .dialog-image { - width: 100%; - max-height: 300px; - border-radius: 0; - object-fit: contain; - } - - .chat-right-panel { - flex: 0; - border-left: 3px solid rgb(221, 221, 221); - height: 100%; - overflow-y: auto; - background: transparent; - } - - .movedin { - flex: 1 !important; - background: transparent; - } - - .main-container { - display: flex; - height: 100%; - } - - .group-nav-container { - display: flex; - height: 40px; - padding: 25px 5px 25px 20px; - margin: 0px; - background-color: var(--chat-bubble-bg); - box-sizing: border-box; - align-items: center; - justify-content: space-between; - box-shadow: var(--group-drop-shadow); - } - - .top-bar-icon { - border-radius: 50%; - color: var(--chat-bubble-msg-color); - transition: 0.3s all ease-in-out; - padding: 5px; - background-color: transparent; - } - - .top-bar-icon:hover { - background-color: #e6e6e69b; - cursor: pointer; - color: var(--black) - } - - .group-name { - font-family: Raleway, sans-serif; - font-size: 16px; - color: var(--black); - margin:0px; - padding:0px; - } - - .modal-button-row { - display: flex; - align-items: center; - justify-content: space-between; - width: 100%; - } - - .modal-button { - font-family: Roboto, sans-serif; - font-size: 16px; - color: var(--mdc-theme-primary); - background-color: transparent; - padding: 8px 10px; - border-radius: 5px; - border: none; - transition: all 0.3s ease-in-out; - } - - .modal-button-red { - font-family: Roboto, sans-serif; - font-size: 16px; - color: #F44336; - background-color: transparent; - padding: 8px 10px; - border-radius: 5px; - border: none; - transition: all 0.3s ease-in-out; - } - - .modal-button-red:hover { - cursor: pointer; - background-color: #f4433663; - } - - .modal-button:hover { - cursor: pointer; - background-color: #03a8f475; - } - - .name-input { - width: 100%; - margin-bottom: 15px; - outline: 0; - border-width: 0 0 2px; - border-color: var(--mdc-theme-primary); - background-color: transparent; - padding: 10px; - font-family: Roboto, sans-serif; - font-size: 15px; - color: var(--chat-bubble-msg-color); - box-sizing: border-box; - } - - .name-input::selection { - background-color: var(--mdc-theme-primary); - color: white; - } - - .name-input::placeholder { - opacity: 0.9; - color: var(--black); - } - - .search-results-div { - position: absolute; - top: 25px; - right: 25px; - } - - .search-field { - width: 100%; - position: relative; - margin-bottom: 5px; - } - - .search-icon { - position: absolute; - right: 3px; - top: 0; - color: var(--chat-bubble-msg-color); - transition: all 0.3s ease-in-out; - background: none; - border-radius: 50%; - padding: 6px 3px; - font-size: 21px; - } - - .search-icon:hover { - cursor: pointer; - background: #d7d7d75c; - } - - .user-verified { - position: absolute; - top: 0; - right: 5px; - display: flex; - align-items: center; - gap: 10px; - color: #04aa2e; - font-size: 13px; - } - - .user-selected { - display: flex; - justify-content: space-between; - align-items: center; - margin: 0; - box-shadow: rgb(0 0 0 / 16%) 0px 3px 6px, rgb(0 0 0 / 23%) 0px 3px 6px; - padding: 18px 20px; - color: var(--chat-bubble-msg-color); - border-radius: 5px; - background-color: #ececec96; - } - - .user-selected-name { - font-family: Roboto, sans-serif; - margin: 0; - font-size: 16px; - } - - .forwarding-container { - display: flex; - gap: 15px; - } - - .user-selected-forwarding { - font-family: Livvic, sans-serif; - margin: 0; - font-size: 16px; - } - - .close-forwarding { - color: #676b71; - width: 14px; - transition: all 0.1s ease-in-out; - } - - .close-forwarding:hover { - cursor: pointer; - color: #4e5054; - } -` -} - constructor() { super() this.getOldMessage = this.getOldMessage.bind(this) @@ -897,8 +917,8 @@ class ChatPage extends LitElement { this.openGifModal = false } + setOpenGifModal(value){ - console.log({value}) this.openGifModal = value } @@ -994,6 +1014,7 @@ console.log({zipFileBlob}) } render() { + console.log(23, "chat page here"); return html`
+ +
{ + this.editor.commands.focus("end"); + this.setOpenGifModal(false); + }} style=${this.openGifModal ? "visibility: visible; z-index: 4" : "visibility: hidden; z-index: -100"}> +
+
+ + +
+
-
@@ -1053,12 +1087,11 @@ console.log({zipFileBlob}) ` : ''} ${this.repliedToMessageObj.toString() === '2' ? html` ${unsafeHTML(generateHTML(this.repliedToMessageObj.message, [ - StarterKit, - Underline, - Highlight - // other extensions … - ]))} ` : ''} - + StarterKit, + Underline, + Highlight + // other extensions … + ]))} ` : ''}

${translate("chatpage.cchange25")}

${unsafeHTML(generateHTML(this.editedMessageObj.message, [ - StarterKit, - Underline, - Highlight - // other extensions … - ]))} + StarterKit, + Underline, + Highlight + // other extensions … + ]))}
this.setOpenGifModal(val)} > @@ -1130,15 +1164,6 @@ console.log({zipFileBlob})
`: ''} - - { - this.setOpenGifModal(false) - // this.removeImage(); - }} - style=${this.openGifModal ? "visibility:visible;z-index:50" : "visibility: hidden;z-index:-100"}> - - { this.removeImage(); @@ -1499,7 +1524,7 @@ console.log({zipFileBlob}) } initialChat(e) { - if (this.editor && !this.editor.isFocused && this.currentEditor === '_chatEditorDOM' && !this.openForwardOpen && !this.openTipUser &&!this.openGifModal) { + if (this.editor && !this.editor.isFocused && this.currentEditor === '_chatEditorDOM' && !this.openForwardOpen && !this.openTipUser && !this.openGifModal) { // WARNING: Deprecated methods from KeyBoard Event if (e.code === "Space" || e.keyCode === 32 || e.which === 32) { } else if (inputKeyCodes.includes(e.keyCode)) { diff --git a/qortal-ui-plugins/plugins/core/components/ChatTextEditor.js b/qortal-ui-plugins/plugins/core/components/ChatTextEditor.js index 70a7d068..5393dfc2 100644 --- a/qortal-ui-plugins/plugins/core/components/ChatTextEditor.js +++ b/qortal-ui-plugins/plugins/core/components/ChatTextEditor.js @@ -30,6 +30,7 @@ class ChatTextEditor extends LitElement { }, toggleEnableChatEnter: {attribute: false}, isEnabledChatEnter: {type: Boolean}, + openGifModal: { type: Boolean }, setOpenGifModal: {attribute: false} } } @@ -361,69 +362,63 @@ class ChatTextEditor extends LitElement { return html`
- + class=${["chatbar-container", "chatbar-buttons", this.iframeId !=="_chatEditorDOM" && 'hide-styling'].join(" ")} + style="align-items: center;"> - - - - - - - + @click=${() => this.editor.chain().focus().toggleBold().run()} + ?disabled=${ + this.editor && + !this.editor.can() + .chain() + .focus() + .toggleBold() + .run() + } + class=${["chatbar-button-single", (this.editedMessageObj || this.repliedToMessageObj || this.openGifModal) && 'show-chatbar-buttons', this.editor && this.editor.isActive('bold') ? 'is-active' : ''].join(" ")}> + + + + + + +
From b7327bafc369205f2087bc4b9fccc08f555fcacc Mon Sep 17 00:00:00 2001 From: Justin Ferrari Date: Fri, 3 Feb 2023 13:01:33 +0200 Subject: [PATCH 07/16] Further added new UI features --- qortal-ui-core/font/switch-theme.css | 12 ++ qortal-ui-core/language/us.json | 6 +- qortal-ui-core/src/styles/switch-theme.css | 12 ++ .../core/components/ChatGifs/ChatGifs-css.js | 138 ++++++++++++++- .../core/components/ChatGifs/ChatGifs.js | 164 +++++++++++------- .../plugins/core/components/ChatPage.js | 67 +++---- 6 files changed, 299 insertions(+), 100 deletions(-) diff --git a/qortal-ui-core/font/switch-theme.css b/qortal-ui-core/font/switch-theme.css index ee0a4f91..8e56fbea 100644 --- a/qortal-ui-core/font/switch-theme.css +++ b/qortal-ui-core/font/switch-theme.css @@ -51,7 +51,13 @@ html { --lightChatHeadHover: #1e1f201a; --group-header: #929292; --group-drop-shadow: rgb(17 17 26 / 10%) 0px 1px 0px; + --reactions-tooltip-bg: #ffffff; --gifs-drop-shadow: #32326926 0px 2px 5px 0px, #0000000d 0px 1px 1px 0px; + --gif-tooltip-bg: #dad7ef; + --gif-search-icon-bs: rgb(17 17 26 / 10%) 0px 4px 16px, rgb(17 17 26 / 5%) 0px 8px 32px; + --gif-search-icon: #ffffff; + --gif-button-row-bg: #eaeaef; + --gif-button-row-color: #464040; } html[theme="dark"] { @@ -107,5 +113,11 @@ html[theme="dark"] { --lightChatHeadHover: #e0e1e31a; --group-header: #c8c8c8; --group-drop-shadow: rgb(191 191 191 / 32%) 0px 1px 0px; + --reactions-tooltip-bg: #161515; --gifs-drop-shadow: 0px 2px 2px 0px hsla(0, 0%, 0%, 0.14), 0px 3px 1px -2px hsla(0, 0%, 0%, 0.12), 0px 1px 5px 0px hsla(0, 0%, 0%, 0.2); + --gif-tooltip-bg: #586b8d; + --gif-search-icon-bs: 0px 8px 10px 1px hsla(0, 0%, 0%, 0.14), 0px 3px 14px 2px hsla(0, 0%, 0%, 0.12), 0px 5px 5px -3px hsla(0, 0%, 0%, 0.2); + --gif-search-icon: #586b8d; + --gif-button-row-bg: #82899c; + --gif-button-row-color: #151212; } \ No newline at end of file diff --git a/qortal-ui-core/language/us.json b/qortal-ui-core/language/us.json index a3287762..b95a9b43 100644 --- a/qortal-ui-core/language/us.json +++ b/qortal-ui-core/language/us.json @@ -574,7 +574,11 @@ "cchange65": "Please enter a recipient", "cchange66": "Cannot fetch replied-to message. Message is too old.", "cchange80": "Gif Explorer", - "cchange81": "Explore Collections" + "cchange81": "Explore Collections", + "cchange82": "My Collections", + "cchange83": "Subscribed Collections", + "cchange84": "Upload your gif files", + "cchange85": "File should be .Gif" }, "welcomepage": { "wcchange1": "Welcome to Q-Chat", diff --git a/qortal-ui-core/src/styles/switch-theme.css b/qortal-ui-core/src/styles/switch-theme.css index 47162c6b..20ed00ef 100644 --- a/qortal-ui-core/src/styles/switch-theme.css +++ b/qortal-ui-core/src/styles/switch-theme.css @@ -48,7 +48,13 @@ html { --chatHeadTextActive: #080808; --group-header: #929292; --group-drop-shadow: rgb(17 17 26 / 10%) 0px 1px 0px; + --reactions-tooltip-bg: #ffffff; --gifs-drop-shadow: #32326926 0px 2px 5px 0px, #0000000d 0px 1px 1px 0px; + --gif-tooltip-bg: #dad7ef; + --gif-search-icon-bs: rgb(17 17 26 / 10%) 0px 4px 16px, rgb(17 17 26 / 5%) 0px 8px 32px; + --gif-search-icon: #ffffff; + --gif-button-row-bg: #eaeaef; + --gif-button-row-color: #464040; } html[theme="dark"] { @@ -101,5 +107,11 @@ html[theme="dark"] { --chatHeadTextActive: #ffffff; --group-header: #c8c8c8; --group-drop-shadow: rgb(191 191 191 / 32%) 0px 1px 0px; + --reactions-tooltip-bg: #161515; --gifs-drop-shadow: 0px 2px 2px 0px hsla(0, 0%, 0%, 0.14), 0px 3px 1px -2px hsla(0, 0%, 0%, 0.12), 0px 1px 5px 0px hsla(0, 0%, 0%, 0.2); + --gif-tooltip-bg: #586b8d; + --gif-search-icon-bs: 0px 8px 10px 1px hsla(0, 0%, 0%, 0.14), 0px 3px 14px 2px hsla(0, 0%, 0%, 0.12), 0px 5px 5px -3px hsla(0, 0%, 0%, 0.2); + --gif-search-icon: #586b8d; + --gif-button-row-bg: #82899c; + --gif-button-row-color: #151212; } \ No newline at end of file diff --git a/qortal-ui-plugins/plugins/core/components/ChatGifs/ChatGifs-css.js b/qortal-ui-plugins/plugins/core/components/ChatGifs/ChatGifs-css.js index 11c2272b..c975016f 100644 --- a/qortal-ui-plugins/plugins/core/components/ChatGifs/ChatGifs-css.js +++ b/qortal-ui-plugins/plugins/core/components/ChatGifs/ChatGifs-css.js @@ -1,10 +1,28 @@ import { css } from 'lit' export const gifExplorerStyles = css` + .gifs-container { + position: relative; + display: flex; + padding: 10px 15px; + border-radius: 12px; + box-shadow: rgba(0, 0, 0, 0.09) 0px 3px 12px; + background-color: var(--chat-menu-bg); + width: fit-content; + justify-self: flex-end; + place-self: end flex-end; + min-height: 400px; + min-width: 370px; + margin-bottom: 8px; + margin-right: 5px; + box-shadow: var(--gifs-drop-shadow); + } + .gif-explorer-container { + min-height: 400px; display: flex; flex-direction: column; - justify-content: center; + justify-content: flex-start; width: 100%; align-items: center; gap: 15px; @@ -25,17 +43,125 @@ export const gifExplorerStyles = css` font-size: 25px; color: var(--chat-bubble-msg-color); margin: 0; + user-select: none; } .explore-collections-icon { margin-left: auto; text-align: right; font-size: 20px; - color: var(--chat-bubble-msg-color); - box-shadow: rgba(0, 0, 0, 0.1) 0px 20px 25px -5px, rgba(0, 0, 0, 0.04) 0px 10px 10px -5px; - padding: 5px; - background-color: var(--chat-menu-bg); + color: var(--chat-group); + box-shadow: var(--gif-search-icon-bs); + padding: 7px; + background-color: var(--gif-search-icon); border: none; - border-radius: 12px; + border-radius: 8px; + cursor: pointer; + } + + .create-collections-icon { + position: absolute; + bottom: 5px; + left: 50%; + transform: translateX(-50%); + padding: 4px; + font-size: 22px; + background-color: var(--mdc-theme-primary); + color: white; + border-radius: 8px; + box-shadow: 0 0 0 rgba(0, 0, 0, 0.2); + transition: all 0.3s ease-in-out; + } + + .create-collections-icon:hover { + cursor: pointer; + box-shadow: 0px 4px 5px 0px hsla(0,0%,0%,0.14), 0px 1px 10px 0px hsla(0,0%,0%,0.12), 0px 2px 4px -1px hsla(0,0%,0%,0.2); + } + + .collections-button-row { + width: auto; + background-color: var(--gif-button-row-bg); + border-radius: 35px; + padding: 2px; + margin-top: 10px; + } + + .collections-button-innerrow { + display: flex; + flex-direction: row; + align-items: center; + } + + .my-collections-button { + font-size: 16px; + font-family: "Maven Pro", sans-serif; + letter-spacing: 0.5px; + color: var(--gif-button-row-color); + border-radius: 35px; + padding: 8px 20px; + margin: 2px 0; + cursor: pointer; + user-select: none; + } + + .subscribed-collections-button { + font-size: 16px; + font-family: "Maven Pro", sans-serif; + letter-spacing: 0.5px; + color: var(--gif-button-row-color); + border-radius: 35px; + padding: 8px 20px; + margin: 2px 0; + cursor: pointer; + user-select: none; + } + + .collections-button-active { + display: flex; + align-items: center; + justify-content: center; + background-color: white; + color: var(--mdc-theme-primary); + border-radius: 25px; + padding: 8px 20px; + margin: 2px 0; + box-shadow: rgb(0 0 0 / 14%) 0px 1px 1px 0px, rgb(0 0 0 / 12%) 0px 2px 1px -1px, rgb(0 0 0 / 20%) 0px 1px 3px 0px; + /* box-shadow: rgb(99 99 99 / 20%) 0px 2px 8px 0px; */ + transition: all 0.3s ease-in-out; + cursor: auto; + } + + .new-collection-row { + display: flex; + flex-direction: column; + justify-content: center; + } + + .new-collection-subrow { + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + gap: 5px; + } + + .new-collection-title { + font-family: Maven Pro, sans-serif; + color: var(--chat-bubble-msg-color); + font-size: 18px; + letter-spacing: 0.6px; + margin: 0; + user-select: none; + } + + .new-collection-subtitle { + font-family: Roboto, sans-serif; + color: var(--chat-bubble-msg-color); + font-weight: 300; + opacity: 0.9; + font-size: 14px; + letter-spacing: 0.3px; + margin: 0; + user-select: none; } ` diff --git a/qortal-ui-plugins/plugins/core/components/ChatGifs/ChatGifs.js b/qortal-ui-plugins/plugins/core/components/ChatGifs/ChatGifs.js index 4b41e11f..cd5b291c 100644 --- a/qortal-ui-plugins/plugins/core/components/ChatGifs/ChatGifs.js +++ b/qortal-ui-plugins/plugins/core/components/ChatGifs/ChatGifs.js @@ -18,14 +18,15 @@ class ChatGifs extends LitElement { return { selectedAddress: { type: Object }, myGifCollections: { type: Array }, - mySubscribedCollections: {type: Array}, + mySubscribedCollections: { type: Array }, exploreCollections: { type: Array }, - gifsToBeAdded: { type: Array}, - webWorkerImage: {type: Object}, - mode: {type: String}, - currentCollection: {type: String}, - isLoading: {type: String}, - newCollectionName: {type: String} + gifsToBeAdded: { type: Array }, + webWorkerImage: { type: Object }, + mode: { type: String }, + currentCollection: { type: String }, + isLoading: { type: String }, + newCollectionName: { type: String }, + editor: { type: Object }, } } @@ -219,8 +220,8 @@ class ChatGifs extends LitElement { async firstUpdated() { const tooltip = this.shadowRoot.querySelector('vaadin-tooltip'); const overlay = tooltip.shadowRoot.querySelector('vaadin-tooltip-overlay'); - overlay.shadowRoot.getElementById("overlay").style.cssText = "background-color: transparent; box-shadow: rgb(50 50 93 / 25%) 0px 2px 5px -1px, rgb(0 0 0 / 30%) 0px 1px 3px -1px"; - overlay.shadowRoot.getElementById('content').style.cssText = "background-color: var(--reactions-tooltip-bg); color: var(--chat-bubble-msg-color); text-align: center; padding: 20px 10px; border-radius: 8px; font-family: Roboto, sans-serif; letter-spacing: 0.3px; font-weight: 300; font-size: 13.5px; transition: all 0.3s ease-in-out;"; + overlay.shadowRoot.getElementById("overlay").style.cssText = "background-color: transparent; border-radius: 10px; box-shadow: rgb(50 50 93 / 25%) 0px 2px 5px -1px, rgb(0 0 0 / 30%) 0px 1px 3px -1px"; + overlay.shadowRoot.getElementById('content').style.cssText = "background-color: var(--gif-tooltip-bg); color: var(--chat-bubble-msg-color); text-align: center; padding: 20px 10px; font-family: Roboto, sans-serif; letter-spacing: 0.3px; font-weight: 300; font-size: 13.5px; transition: all 0.3s ease-in-out;"; try { this.isLoading = true @@ -398,31 +399,47 @@ class ChatGifs extends LitElement { } } interval = setInterval(getAnswer, 5000) - }) - - saveAs(zipFileBlob, 'zipfile'); - console.log({zipFileBlob}) - } catch (error) { - console.log(error) - } + }) + saveAs(zipFileBlob, 'zipfile'); + console.log({zipFileBlob}) + } catch (error) { + console.log(error) } + } setCurrentCollection(val){ this.currentCollection = val } + clearGifSelections() { + this.mode = "myCollection"; + this.gifsToBeAdded = []; + } + render() { console.log('this.currentCollection', this.currentCollection) - console.log(3, "chat gifs here") + console.log(9, "chat gifs here") return html` -
+
+
+ { + if (this.isLoading) return; + this.mode = "newCollection"; + }} + icon="vaadin:plus" + slot="icon"> +

${translate("chatpage.cchange80")}

{ - if(this.isLoading) return; + @click=${()=> { + if (this.isLoading) return; this.mode = "explore"; }} icon="vaadin:search" @@ -436,30 +453,43 @@ class ChatGifs extends LitElement { text=${get("chatpage.cchange81")}>
-
- - - +
+
+
{ + if (this.isLoading) return; + if (this.mode === "myCollection") return; + this.mode = "myCollection"; + }} + > + ${translate("chatpage.cchange82")} +
+
{ + if(this.isLoading) return; + if (this.mode === "subscribedCollection") return; + this.mode = "subscribedCollection"; + }} + > + ${translate("chatpage.cchange83")} +
+
+
+
${this.mode === "myCollection" && !this.currentCollection ? html` ${this.isLoading === true ? html`

Loading...

@@ -545,25 +575,40 @@ class ChatGifs extends LitElement { })} ` : ''} ${this.mode === "newCollection" ? html` +
+
+

+ ${translate("chatpage.cchange84")} +

+

+ ${translate("chatpage.cchange85")} +

+
- - - { + class="file-picker-input-gif" type="file" name="myGif" accept="image/gif" + /> + + { this.newCollectionName = e.target.value - })} /> + })} + /> +
${this.gifsToBeAdded.map((gif, i)=> { console.log({gif}) @@ -576,19 +621,16 @@ class ChatGifs extends LitElement { name: e.target.value } })} /> -
- +
` })}
` : ''} -
-
+
+
` } - - } window.customElements.define('chat-gifs', ChatGifs) diff --git a/qortal-ui-plugins/plugins/core/components/ChatPage.js b/qortal-ui-plugins/plugins/core/components/ChatPage.js index e3bf62c3..6d8a1d90 100644 --- a/qortal-ui-plugins/plugins/core/components/ChatPage.js +++ b/qortal-ui-plugins/plugins/core/components/ChatPage.js @@ -823,29 +823,29 @@ class ChatPage extends LitElement { color: #4e5054; } - .gifs-backdrop { - height: 100vh; - width: 100vw; - background: transparent; - position: fixed; - } + .chat-gifs { + position: relative; + justify-self: flex-end; + width: fit-content; + height: auto; + transform: translateY(30%); + animation: smooth-appear 0.5s ease forwards; + z-index: 5; + } - .gifs-container { - position: relative; - display: flex; - padding: 10px 15px; - border-radius: 12px; - box-shadow: rgba(0, 0, 0, 0.09) 0px 3px 12px; - background-color: var(--chat-menu-bg); - align-items: center; - justify-content: flex-end; - width: fit-content; - justify-self: flex-end; - margin-bottom: 8px; - margin-right: 5px; - box-shadow: var(--gifs-drop-shadow); - z-index: 5; - } + @keyframes smooth-appear { + to { + transform: translateY(0); + } + } + + .gifs-backdrop { + top: 0; + height: 100vh; + width: 100vw; + background: transparent; + position: fixed; + } ` } @@ -913,6 +913,7 @@ class ChatPage extends LitElement { this.webWorkerImage = null; this.currentEditor = '_chatEditorDOM' this.initialChat = this.initialChat.bind(this) + this.setOpenGifModal = this.setOpenGifModal.bind(this) this.isEnabledChatEnter = true this.openGifModal = false } @@ -1050,18 +1051,20 @@ console.log({zipFileBlob}) this.renderChatScroller()}
-
{ - this.editor.commands.focus("end"); - this.setOpenGifModal(false); - }} style=${this.openGifModal ? "visibility: visible; z-index: 4" : "visibility: hidden; z-index: -100"}> -
-
+
{ + this.setOpenGifModal(false); + this.editor.commands.focus("end"); + this.shadowRoot.querySelector("chat-gifs").clearGifSelections(); + }} + style=${this.openGifModal ? "visibility: visible; z-index: 4" : "visibility: hidden; z-index: -100"}> +
+ class="chat-gifs" + style=${this.openGifModal ? "display: flex;" : "display: none;"} + .webWorkerImage=${this.webWorkerImage}> -
Date: Sat, 4 Feb 2023 00:28:33 +0200 Subject: [PATCH 08/16] Continued UI of gifs --- qortal-ui-core/language/us.json | 3 +- .../core/components/ChatGifs/ChatGifs-css.js | 435 ++++-- .../core/components/ChatGifs/ChatGifs.js | 1234 +++++++++-------- .../components/ChatGifs/ChatGifsExplore.js | 163 ++- .../plugins/core/components/ChatPage.js | 34 +- .../core/components/UserInfo/UserInfo-css.js | 120 +- 6 files changed, 1135 insertions(+), 854 deletions(-) diff --git a/qortal-ui-core/language/us.json b/qortal-ui-core/language/us.json index b95a9b43..2499a3a1 100644 --- a/qortal-ui-core/language/us.json +++ b/qortal-ui-core/language/us.json @@ -578,7 +578,8 @@ "cchange82": "My Collections", "cchange83": "Subscribed Collections", "cchange84": "Upload your gif files", - "cchange85": "File should be .Gif" + "cchange85": "File should be .Gif", + "cchange86": "Upload Collection" }, "welcomepage": { "wcchange1": "Welcome to Q-Chat", diff --git a/qortal-ui-plugins/plugins/core/components/ChatGifs/ChatGifs-css.js b/qortal-ui-plugins/plugins/core/components/ChatGifs/ChatGifs-css.js index c975016f..6144b149 100644 --- a/qortal-ui-plugins/plugins/core/components/ChatGifs/ChatGifs-css.js +++ b/qortal-ui-plugins/plugins/core/components/ChatGifs/ChatGifs-css.js @@ -1,167 +1,302 @@ -import { css } from 'lit' +import { css } from 'lit'; export const gifExplorerStyles = css` - .gifs-container { - position: relative; - display: flex; - padding: 10px 15px; - border-radius: 12px; - box-shadow: rgba(0, 0, 0, 0.09) 0px 3px 12px; - background-color: var(--chat-menu-bg); - width: fit-content; - justify-self: flex-end; - place-self: end flex-end; - min-height: 400px; - min-width: 370px; - margin-bottom: 8px; - margin-right: 5px; - box-shadow: var(--gifs-drop-shadow); - } +.gifs-container { +position: relative; +display: flex; +padding: 10px 15px; +border-radius: 12px; +box-shadow: rgba(0, 0, 0, 0.09) 0px 3px 12px; +background-color: var(--chat-menu-bg); +width: fit-content; +justify-self: flex-end; +place-self: end flex-end; +min-height: 400px; +max-height: 95vh; +min-width: 370px; +box-shadow: var(--gifs-drop-shadow); +} - .gif-explorer-container { - min-height: 400px; - display: flex; - flex-direction: column; - justify-content: flex-start; - width: 100%; - align-items: center; - gap: 15px; - } +.gif-explorer-container { +min-height: 400px; +display: flex; +flex-direction: column; +justify-content: flex-start; +width: 100%; +align-items: center; +gap: 15px; +} - .title-row { - display: flex; - justify-content: center; - align-items: center; - width: 100%; - } +.title-row { +display: flex; +justify-content: center; +align-items: center; +width: 100%; +} - .gif-explorer-title { - flex: 1; - text-align: center; - font-family: Roboto, sans-serif; - letter-spacing: 0.8px; - font-size: 25px; - color: var(--chat-bubble-msg-color); - margin: 0; - user-select: none; - } +.gif-explorer-title { +flex: 1; +text-align: center; +font-family: Roboto, sans-serif; +letter-spacing: 0.8px; +font-size: 25px; +color: var(--chat-bubble-msg-color); +margin: 0; +user-select: none; +} - .explore-collections-icon { - margin-left: auto; - text-align: right; - font-size: 20px; - color: var(--chat-group); - box-shadow: var(--gif-search-icon-bs); - padding: 7px; - background-color: var(--gif-search-icon); - border: none; - border-radius: 8px; - cursor: pointer; - } +.explore-collections-icon { +margin-left: auto; +text-align: right; +font-size: 20px; +color: var(--chat-group); +box-shadow: var(--gif-search-icon-bs); +padding: 7px; +background-color: var(--gif-search-icon); +border: none; +border-radius: 8px; +cursor: pointer; +} - .create-collections-icon { - position: absolute; - bottom: 5px; - left: 50%; - transform: translateX(-50%); - padding: 4px; - font-size: 22px; - background-color: var(--mdc-theme-primary); - color: white; - border-radius: 8px; - box-shadow: 0 0 0 rgba(0, 0, 0, 0.2); - transition: all 0.3s ease-in-out; - } +.create-collections-icon { +position: absolute; +bottom: 5px; +left: 50%; +transform: translateX(-50%); +padding: 4px; +font-size: 22px; +background-color: var(--mdc-theme-primary); +color: white; +border-radius: 8px; +box-shadow: 0 0 0 rgba(0, 0, 0, 0.2); +transition: all 0.3s ease-in-out; +} - .create-collections-icon:hover { - cursor: pointer; - box-shadow: 0px 4px 5px 0px hsla(0,0%,0%,0.14), 0px 1px 10px 0px hsla(0,0%,0%,0.12), 0px 2px 4px -1px hsla(0,0%,0%,0.2); - } +.create-collections-icon:hover { +cursor: pointer; +box-shadow: 0px 4px 5px 0px hsla(0, 0%, 0%, 0.14), +0px 1px 10px 0px hsla(0, 0%, 0%, 0.12), +0px 2px 4px -1px hsla(0, 0%, 0%, 0.2); +} - .collections-button-row { - width: auto; - background-color: var(--gif-button-row-bg); - border-radius: 35px; - padding: 2px; - margin-top: 10px; - } +.collections-button-row { +width: auto; +background-color: var(--gif-button-row-bg); +border-radius: 35px; +padding: 2px; +margin-top: 10px; +} - .collections-button-innerrow { - display: flex; - flex-direction: row; - align-items: center; - } +.collections-button-innerrow { +display: flex; +flex-direction: row; +align-items: center; +} - .my-collections-button { - font-size: 16px; - font-family: "Maven Pro", sans-serif; - letter-spacing: 0.5px; - color: var(--gif-button-row-color); - border-radius: 35px; - padding: 8px 20px; - margin: 2px 0; - cursor: pointer; - user-select: none; - } +.my-collections-button { +font-size: 16px; +font-family: 'Maven Pro', sans-serif; +letter-spacing: 0.5px; +color: var(--gif-button-row-color); +border-radius: 35px; +padding: 8px 20px; +margin: 2px 0; +cursor: pointer; +user-select: none; +} - .subscribed-collections-button { - font-size: 16px; - font-family: "Maven Pro", sans-serif; - letter-spacing: 0.5px; - color: var(--gif-button-row-color); - border-radius: 35px; - padding: 8px 20px; - margin: 2px 0; - cursor: pointer; - user-select: none; - } +.subscribed-collections-button { +font-size: 16px; +font-family: 'Maven Pro', sans-serif; +letter-spacing: 0.5px; +color: var(--gif-button-row-color); +border-radius: 35px; +padding: 8px 20px; +margin: 2px 0; +cursor: pointer; +user-select: none; +} - .collections-button-active { - display: flex; - align-items: center; - justify-content: center; - background-color: white; - color: var(--mdc-theme-primary); - border-radius: 25px; - padding: 8px 20px; - margin: 2px 0; - box-shadow: rgb(0 0 0 / 14%) 0px 1px 1px 0px, rgb(0 0 0 / 12%) 0px 2px 1px -1px, rgb(0 0 0 / 20%) 0px 1px 3px 0px; - /* box-shadow: rgb(99 99 99 / 20%) 0px 2px 8px 0px; */ - transition: all 0.3s ease-in-out; - cursor: auto; - } +.collections-button-active { +display: flex; +align-items: center; +justify-content: center; +background-color: white; +color: var(--mdc-theme-primary); +border-radius: 25px; +padding: 8px 20px; +margin: 2px 0; +box-shadow: rgb(0 0 0 / 14%) 0px 1px 1px 0px, +rgb(0 0 0 / 12%) 0px 2px 1px -1px, rgb(0 0 0 / 20%) 0px 1px 3px 0px; +transition: all 0.3s ease-in-out; +cursor: auto; +} - .new-collection-row { - display: flex; - flex-direction: column; - justify-content: center; - } +.collection-wrapper { +display: flex; +flex-direction: column; +width: 100%; +height: 100%; +} - .new-collection-subrow { - display: flex; - flex-direction: column; - justify-content: center; - align-items: center; - gap: 5px; - } +.new-collection-row { +display: flex; +flex-direction: column; +justify-content: center; +height: 100%; +} - .new-collection-title { - font-family: Maven Pro, sans-serif; - color: var(--chat-bubble-msg-color); - font-size: 18px; - letter-spacing: 0.6px; - margin: 0; - user-select: none; - } +.new-collection-subrow { +display: flex; +flex-direction: column; +justify-content: center; +align-items: center; +gap: 5px; +} - .new-collection-subtitle { - font-family: Roboto, sans-serif; - color: var(--chat-bubble-msg-color); - font-weight: 300; - opacity: 0.9; - font-size: 14px; - letter-spacing: 0.3px; - margin: 0; - user-select: none; - } -` +.new-collection-title { +font-family: Maven Pro, sans-serif; +color: var(--chat-bubble-msg-color); +font-size: 18px; +letter-spacing: 0.6px; +margin: 0; +user-select: none; +} + +.new-collection-subtitle { +font-family: Roboto, sans-serif; +color: var(--chat-bubble-msg-color); +font-weight: 300; +opacity: 0.9; +font-size: 14px; +letter-spacing: 0.3px; +margin: 0; +user-select: none; +} + +.new-collection-container { +display: flex; +margin: 15px 20px; +border: 3.5px dashed #b898c1; +border-radius: 10px; +background-color: #d7d3db2e; +align-items: center; +justify-content: center; +cursor: pointer; +} + +.new-collection-icon { +font-size: 30px; +color: var(--mdc-theme-primary); +} + +.gifs-added-col { +flex-direction: column; +justify-content: space-between; +flex: 1 1 0%; +margin-top: 10px; +overflow-y: auto; +max-height: 300px; +} + +.gifs-added-col::-webkit-scrollbar-track { +background-color: whitesmoke; +border-radius: 7px; +} + +.gifs-added-col::-webkit-scrollbar { +width: 6px; +border-radius: 7px; +background-color: whitesmoke; +} + +.gifs-added-col::-webkit-scrollbar-thumb { +background-color: rgb(180, 176, 176); +border-radius: 7px; +transition: all 0.3s ease-in-out; +} + +.gifs-added-row { +display: flex; +flex-direction: column; +gap: 5px; +overflow-y: auto; +} + +.gifs-added-row .gif-input:last-child { +border-bottom: none; +} + +.gif-input { +display: flex; +flex-direction: row; +align-items: center; +gap: 10px; +background-color: transparent; +padding: 15px 5px; +border-bottom: 1px solid #7b787888; +} + +.gif-input-img { +width: 70px; +height: 70px; +border-radius: 10px; +} + +.gif-input-field { +height: 30px; +background-color: transparent; +border: none; +color: var(--chat-bubble-msg-color); +border-bottom: 1px solid var(--chat-bubble-msg-color); +width: 100%; +padding: 0; +margin: 0; +outline: 0; +font-size: 16px; +font-family: Roboto, sans-serif; +letter-spacing: 0.3px; +font-weight: 300; +} + +.upload-collection-row { +display: flex; +align-items: center; +justify-content: space-between; +width: 100%; +margin-top: 10px; +} + +.upload-button { +font-family: Roboto, sans-serif; +font-size: 16px; +color: var(--mdc-theme-primary); +background-color: transparent; +padding: 8px 10px; +border-radius: 5px; +border: none; +transition: all 0.3s ease-in-out; +} + +.upload-back-button { +font-family: Roboto, sans-serif; +font-size: 16px; +color: #f44336; +background-color: transparent; +padding: 8px 10px; +border-radius: 5px; +border: none; +transition: all 0.3s ease-in-out; +} + +.upload-back-button:hover { +cursor: pointer; +background-color: #f4433663; +} + +.upload-button:hover { +cursor: pointer; +background-color: #03a8f475; +} + +`; diff --git a/qortal-ui-plugins/plugins/core/components/ChatGifs/ChatGifs.js b/qortal-ui-plugins/plugins/core/components/ChatGifs/ChatGifs.js index cd5b291c..a6315fcc 100644 --- a/qortal-ui-plugins/plugins/core/components/ChatGifs/ChatGifs.js +++ b/qortal-ui-plugins/plugins/core/components/ChatGifs/ChatGifs.js @@ -1,636 +1,784 @@ -import { LitElement, html, css } from 'lit' -import { render } from 'lit/html.js' -import { Epml } from '../../../../epml.js' -import * as zip from "@zip.js/zip.js"; -import { saveAs } from 'file-saver'; -import '@material/mwc-icon' +import {LitElement, html, css} from 'lit'; +import {render} from 'lit/html.js'; +import {Epml} from '../../../../epml.js'; +import * as zip from '@zip.js/zip.js'; +import {saveAs} from 'file-saver'; +import '@material/mwc-icon'; import ShortUniqueId from 'short-unique-id'; -import { publishData } from '../../../utils/publish-image.js'; -import { translate, get } from 'lit-translate'; -import { gifExplorerStyles } from "./ChatGifs-css.js"; +import {publishData} from '../../../utils/publish-image.js'; +import {translate, get} from 'lit-translate'; +import {gifExplorerStyles} from './ChatGifs-css.js'; import './ChatGifsExplore.js'; import '@vaadin/tooltip'; -const parentEpml = new Epml({ type: 'WINDOW', source: window.parent }) +const parentEpml = new Epml({type: 'WINDOW', source: window.parent}); class ChatGifs extends LitElement { - static get properties() { - return { - selectedAddress: { type: Object }, - myGifCollections: { type: Array }, - mySubscribedCollections: { type: Array }, - exploreCollections: { type: Array }, - gifsToBeAdded: { type: Array }, - webWorkerImage: { type: Object }, - mode: { type: String }, - currentCollection: { type: String }, - isLoading: { type: String }, - newCollectionName: { type: String }, - editor: { type: Object }, - } - } +static get properties() { +return { +selectedAddress: {type: Object}, +myGifCollections: {type: Array}, +mySubscribedCollections: {type: Array}, +exploreCollections: {type: Array}, +gifsToBeAdded: {type: Array}, +webWorkerImage: {type: Object}, +mode: {type: String}, +currentCollection: {type: String}, +isLoading: {type: String}, +newCollectionName: {type: String}, +editor: {type: Object}, +}; +} - static styles = [gifExplorerStyles] + static styles = [gifExplorerStyles]; constructor() { - super() - this.uid = new ShortUniqueId() - this.selectedAddress = window.parent.reduxStore.getState().app.selectedAddress - this.myGifCollections = [] - this.mySubscribedCollections = [] - this.exploreCollections = [] - this.myAccountName = '' - this.gifsToBeAdded = [] - // mode can be 'myCollection', 'newCollection', 'explore', 'subscribedCollection' - this.mode = "myCollection" - this.currentCollection = null - this.pageNumber = 0 - this.isLoading = false - this.newCollectionName = "" + super(); + this.uid = new ShortUniqueId(); + this.selectedAddress = + window.parent.reduxStore.getState().app.selectedAddress; + this.myGifCollections = []; + this.mySubscribedCollections = []; + this.exploreCollections = []; + this.myAccountName = ''; + this.gifsToBeAdded = []; + // mode can be 'myCollection', 'newCollection', 'explore', 'subscribedCollection' + this.mode = 'myCollection'; + this.currentCollection = null; + this.pageNumber = 0; + this.isLoading = false; + this.newCollectionName = ''; } - async structureCollections(gifCollections){ - try { - 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 getMetaDataGifs = (gifCollections || []).map(async (collection) => { - let collectionObj = collection - try { - const metaData = await parentEpml.request('apiCall', { - url: `/arbitrary/metadata/GIF_REPOSITORY/${this.myAccountName}/${collection.identifier}` - }) + async structureCollections(gifCollections) { + try { + 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 getMetaDataGifs = (gifCollections || []).map( + async (collection) => { + let collectionObj = collection; + try { + const metaData = await parentEpml.request('apiCall', { + url: `/arbitrary/metadata/GIF_REPOSITORY/${this.myAccountName}/${collection.identifier}`, + }); - collectionObj = { - ...collection, - gifUrls: [] - } - if(metaData.files){ - const metaDataArray = metaData.files.map((data)=> { - return `${nodeUrl}/arbitrary/GIF_REPOSITORY/${this.myAccountName}/${collection.identifier}?filepath=${data}` - }) - - - collectionObj = { - ...collection, - gifUrls: metaDataArray - } - - } + collectionObj = { + ...collection, + gifUrls: [], + }; + if (metaData.files) { + const metaDataArray = metaData.files.map((data) => { + return `${nodeUrl}/arbitrary/GIF_REPOSITORY/${this.myAccountName}/${collection.identifier}?filepath=${data}`; + }); - } catch (error) { - console.log(error) - } - - return collectionObj - }) - return await Promise.all(getMetaDataGifs) - } catch (error) { - - } + collectionObj = { + ...collection, + gifUrls: metaDataArray, + }; + } + } catch (error) { + console.log(error); + } + + return collectionObj; + } + ); + return await Promise.all(getMetaDataGifs); + } catch (error) {} } - async getMoreExploreGifs(){ - try { - const getAllGifCollections = await parentEpml.request("apiCall", { - type: "api", - url: `/arbitrary/resources?service=GIF_REPOSITORY&limit=20&offset=${this.pageNumber * 20}`, - }); + async getMoreExploreGifs() { + try { + const getAllGifCollections = await parentEpml.request('apiCall', { + type: 'api', + url: `/arbitrary/resources?service=GIF_REPOSITORY&limit=20&offset=${ + this.pageNumber * 20 + }`, + }); - const gifCollectionWithMetaData = await this.structureCollections(getAllGifCollections) - this.exploreCollections = [...this.exploreCollections, ...gifCollectionWithMetaData] + const gifCollectionWithMetaData = await this.structureCollections( + getAllGifCollections + ); + this.exploreCollections = [ + ...this.exploreCollections, + ...gifCollectionWithMetaData, + ]; - this.pageNumber = this.pageNumber + 1 - } catch (error) { - console.error(error) - } + this.pageNumber = this.pageNumber + 1; + } catch (error) { + console.error(error); + } } - async getCollectionList(){ - try { - return await parentEpml.request("apiCall", { - type: "api", - url: `/lists/gifSubscribedRepos`, - }); - - } catch (error) { - - } + async getCollectionList() { + try { + return await parentEpml.request('apiCall', { + type: 'api', + url: `/lists/gifSubscribedRepos`, + }); + } catch (error) {} } - async addCollectionToList(collection){ - try { - const body = { - - "items": [ - collection - ] - - } + async addCollectionToList(collection) { + try { + const body = { + items: [collection], + }; - const bodyToString = JSON.stringify(body) - await parentEpml.request("apiCall", { - type: "api", - method: "POST", - url: `/lists/gifSubscribedRepos`, - body: bodyToString, - headers: { - 'Content-Type': 'application/json' - } - }) - } catch (error) { - - } + const bodyToString = JSON.stringify(body); + await parentEpml.request('apiCall', { + type: 'api', + method: 'POST', + url: `/lists/gifSubscribedRepos`, + body: bodyToString, + headers: { + 'Content-Type': 'application/json', + }, + }); + } catch (error) {} } - async removeCollectionFromList(collection){ - try { - - const body = { - - "items": [ - collection - ] - - } - const bodyToString = JSON.stringify(body) - await parentEpml.request("apiCall", { - type: "api", - method: 'DELETE', - url: `/lists/gifSubscribedRepos`, - body: bodyToString, - headers: { - 'Content-Type': 'application/json' - } - }) - } catch (error) { - - } + async removeCollectionFromList(collection) { + try { + const body = { + items: [collection], + }; + const bodyToString = JSON.stringify(body); + await parentEpml.request('apiCall', { + type: 'api', + method: 'DELETE', + url: `/lists/gifSubscribedRepos`, + body: bodyToString, + headers: { + 'Content-Type': 'application/json', + }, + }); + } catch (error) {} } - async getMyGifCollections(){ - const userName = await this.getName(this.selectedAddress.address); - this.myAccountName = userName - if(this.myAccountName){ - const getMyGifColloctions = await parentEpml.request('apiCall', { - url: `/arbitrary/resources?service=GIF_REPOSITORY&limit=0&name=${this.myAccountName}` - }) - const gifCollectionWithMetaData = await this.structureCollections(getMyGifColloctions) - - console.log({gifCollectionWithMetaData}) - this.myGifCollections = gifCollectionWithMetaData - } + async getMyGifCollections() { + const userName = await this.getName(this.selectedAddress.address); + this.myAccountName = userName; + if (this.myAccountName) { + const getMyGifColloctions = await parentEpml.request('apiCall', { + url: `/arbitrary/resources?service=GIF_REPOSITORY&limit=0&name=${this.myAccountName}`, + }); + const gifCollectionWithMetaData = await this.structureCollections( + getMyGifColloctions + ); + + console.log({gifCollectionWithMetaData}); + this.myGifCollections = gifCollectionWithMetaData; + } } - async getAllCollections(){ - this.pageNumber = 0 - // for the explore section - const getAllGifCollections = await parentEpml.request("apiCall", { - type: "api", - url: `/arbitrary/resources?service=GIF_REPOSITORY&limit=20&offset=${this.pageNumber * 20}`, - }); - const gifCollectionWithMetaData = await this.structureCollections(getAllGifCollections) - this.exploreCollections = gifCollectionWithMetaData - this.pageNumber = this.pageNumber + 1 + async getAllCollections() { + this.pageNumber = 0; + // for the explore section + const getAllGifCollections = await parentEpml.request('apiCall', { + type: 'api', + url: `/arbitrary/resources?service=GIF_REPOSITORY&limit=20&offset=${ + this.pageNumber * 20 + }`, + }); + const gifCollectionWithMetaData = await this.structureCollections( + getAllGifCollections + ); + this.exploreCollections = gifCollectionWithMetaData; + this.pageNumber = this.pageNumber + 1; } - async getSavedCollections(){ - const getCollectionList = await this.getCollectionList() - let savedCollections = [] - const getSavedGifRepos = (getCollectionList || []).map(async (collection) => { - let splitCollection = collection.split('/') - const name = splitCollection[0] - const identifier = splitCollection[1] - try { - console.log({collection}) - const data = await parentEpml.request('apiCall', { - url: `/arbitrary/resources?service=GIF_REPOSITORY&limit=0&name=${name}&identifier=${identifier}` - }) - if(data.length > 0){ - savedCollections.push(data[0]) - } - } catch (error) { - console.log(error) - } - return collection - }) - await Promise.all(getSavedGifRepos) - const savedCollectionsWithMetaData = await this.structureCollections(savedCollections) - this.mySubscribedCollections = savedCollectionsWithMetaData - } + async getSavedCollections() { + const getCollectionList = await this.getCollectionList(); + let savedCollections = []; + const getSavedGifRepos = (getCollectionList || []).map( + async (collection) => { + let splitCollection = collection.split('/'); + const name = splitCollection[0]; + const identifier = splitCollection[1]; + try { + console.log({collection}); + const data = await parentEpml.request('apiCall', { + url: `/arbitrary/resources?service=GIF_REPOSITORY&limit=0&name=${name}&identifier=${identifier}`, + }); + if (data.length > 0) { + savedCollections.push(data[0]); + } + } catch (error) { + console.log(error); + } + return collection; + } + ); + await Promise.all(getSavedGifRepos); + const savedCollectionsWithMetaData = await this.structureCollections( + savedCollections + ); + this.mySubscribedCollections = savedCollectionsWithMetaData; + } async firstUpdated() { - const tooltip = this.shadowRoot.querySelector('vaadin-tooltip'); - const overlay = tooltip.shadowRoot.querySelector('vaadin-tooltip-overlay'); - overlay.shadowRoot.getElementById("overlay").style.cssText = "background-color: transparent; border-radius: 10px; box-shadow: rgb(50 50 93 / 25%) 0px 2px 5px -1px, rgb(0 0 0 / 30%) 0px 1px 3px -1px"; - overlay.shadowRoot.getElementById('content').style.cssText = "background-color: var(--gif-tooltip-bg); color: var(--chat-bubble-msg-color); text-align: center; padding: 20px 10px; font-family: Roboto, sans-serif; letter-spacing: 0.3px; font-weight: 300; font-size: 13.5px; transition: all 0.3s ease-in-out;"; + const tooltip = this.shadowRoot.querySelector('vaadin-tooltip'); + const overlay = tooltip.shadowRoot.querySelector( + 'vaadin-tooltip-overlay' + ); + overlay.shadowRoot.getElementById('overlay').style.cssText = + 'background-color: transparent; border-radius: 10px; box-shadow: rgb(50 50 93 / 25%) 0px 2px 5px -1px, rgb(0 0 0 / 30%) 0px 1px 3px -1px'; + overlay.shadowRoot.getElementById('content').style.cssText = + 'background-color: var(--gif-tooltip-bg); color: var(--chat-bubble-msg-color); text-align: center; padding: 20px 10px; font-family: Roboto, sans-serif; letter-spacing: 0.3px; font-weight: 300; font-size: 13.5px; transition: all 0.3s ease-in-out;'; - try { - this.isLoading = true - await this.getMyGifCollections() - await this.getAllCollections() - await this.getSavedCollections() - this.isLoading = false - } catch (error) { - this.isLoading = false - console.error(error) - } + try { + this.isLoading = true; + await this.getMyGifCollections(); + await this.getAllCollections(); + await this.getSavedCollections(); + this.isLoading = false; + } catch (error) { + this.isLoading = false; + console.error(error); + } } async updated(changedProperties) { - console.log({changedProperties}) - if (changedProperties && changedProperties.has('mode')) { - const mode = this.mode - console.log({mode}) - if (mode === 'myCollection') { - try { - this.isLoading = true + console.log({changedProperties}); + if (changedProperties && changedProperties.has('mode')) { + const mode = this.mode; + console.log({mode}); + if (mode === 'myCollection') { + try { + this.isLoading = true; - await this.getMyGifCollections() - this.isLoading = false - } catch (error) { - this.isLoading = false - } - - } + await this.getMyGifCollections(); + this.isLoading = false; + } catch (error) { + this.isLoading = false; + } + } - if (mode === 'explore') { - try { - this.isLoading = true - - await this.getAllCollections() - this.isLoading = false - } catch (error) { - this.isLoading = false - } - - } - if (mode === 'subscribedCollection') { - try { - this.isLoading = true - - await this.getSavedCollections() - this.isLoading = false - } catch (error) { - this.isLoading = false - } - - } - } + if (mode === 'explore') { + try { + this.isLoading = true; - - + await this.getAllCollections(); + this.isLoading = false; + } catch (error) { + this.isLoading = false; + } + } + if (mode === 'subscribedCollection') { + try { + this.isLoading = true; + + await this.getSavedCollections(); + this.isLoading = false; + } catch (error) { + this.isLoading = false; + } + } + } } - async getName (recipient) { - try { - const getNames = await parentEpml.request("apiCall", { - type: "api", - url: `/names/address/${recipient}`, - }); + async getName(recipient) { + try { + const getNames = await parentEpml.request('apiCall', { + type: 'api', + url: `/names/address/${recipient}`, + }); - if (Array.isArray(getNames) && getNames.length > 0 ) { - return getNames[0].name - } else { - return '' - } - - } catch (error) { - return "" - } + if (Array.isArray(getNames) && getNames.length > 0) { + return getNames[0].name; + } else { + return ''; + } + } catch (error) { + return ''; + } } - addGifs(gifs){ - console.log('gifs', gifs) - const mapGifs = gifs.map((file)=> { - return { - file, - name: file.name - } - }) - console.log({mapGifs}) - this.gifsToBeAdded = [...this.gifsToBeAdded, ...mapGifs] - console.log('this.gifsToBeAdded', this.gifsToBeAdded) + addGifs(gifs) { + console.log('gifs', gifs); + const mapGifs = gifs.map((file) => { + return { + file, + name: file.name, + }; + }); + console.log({mapGifs}); + this.gifsToBeAdded = [...this.gifsToBeAdded, ...mapGifs]; + console.log('this.gifsToBeAdded', this.gifsToBeAdded); } - async uploadGifCollection(){ - if(!this.newCollectionName){ - parentEpml.request('showSnackBar', get("chatpage.cchange27")); - return - } + async uploadGifCollection() { + if (!this.newCollectionName) { + parentEpml.request('showSnackBar', get('chatpage.cchange27')); + return; + } - // if(!isAlphanumeric(this.newCollectionName)){ - // parentEpml.request('showSnackBar', get("chatpage.cchange27")); - // return - // } - try { - const userName = await this.getName(this.selectedAddress.address); - const doesNameExist = await parentEpml.request('apiCall', { - url: `/arbitrary/resources?service=GIF_REPOSITORY&limit=0&name=${userName}&identifier=${this.newCollectionName}` - }) + // if(!isAlphanumeric(this.newCollectionName)){ + // parentEpml.request('showSnackBar', get("chatpage.cchange27")); + // return + // } + try { + const userName = await this.getName(this.selectedAddress.address); + const doesNameExist = await parentEpml.request('apiCall', { + url: `/arbitrary/resources?service=GIF_REPOSITORY&limit=0&name=${userName}&identifier=${this.newCollectionName}`, + }); - if(doesNameExist.length !== 0){ - parentEpml.request('showSnackBar', get("chatpage.cchange27")); - return - } - function blobToBase64(blob) { - return new Promise((resolve, _) => { - const reader = new FileReader(); - reader.onloadend = () => resolve(reader.result); - reader.readAsDataURL(blob); - }); - } - const zipFileWriter = new zip.BlobWriter("application/zip"); -// Creates a TextReader object storing the text of the entry to add in the zip -// (i.e. "Hello world!"). - const helloWorldReader = new zip.TextReader("Hello world!"); + if (doesNameExist.length !== 0) { + parentEpml.request('showSnackBar', get('chatpage.cchange27')); + return; + } + function blobToBase64(blob) { + return new Promise((resolve, _) => { + const reader = new FileReader(); + reader.onloadend = () => resolve(reader.result); + reader.readAsDataURL(blob); + }); + } + const zipFileWriter = new zip.BlobWriter('application/zip'); + // Creates a TextReader object storing the text of the entry to add in the zip + // (i.e. "Hello world!"). + const helloWorldReader = new zip.TextReader('Hello world!'); -// Creates a ZipWriter object writing data via `zipFileWriter`, adds the entry -// "hello.txt" containing the text "Hello world!" via `helloWorldReader`, and -// closes the writer. + // Creates a ZipWriter object writing data via `zipFileWriter`, adds the entry + // "hello.txt" containing the text "Hello world!" via `helloWorldReader`, and + // closes the writer. - const zipWriter = new zip.ZipWriter(zipFileWriter, { bufferedWrite: true }); + const zipWriter = new zip.ZipWriter(zipFileWriter, { + bufferedWrite: true, + }); + for (let i = 0; i < this.gifsToBeAdded.length; i++) { + await zipWriter.add( + this.gifsToBeAdded[i].name, + new zip.BlobReader(this.gifsToBeAdded[i].file) + ); + } - for (let i = 0; i < this.gifsToBeAdded.length; i++) { - await zipWriter.add(this.gifsToBeAdded[i].name, new zip.BlobReader(this.gifsToBeAdded[i].file)); + await zipWriter.close(); + const zipFileBlob = await zipFileWriter.getData(); + const blobTobase = await blobToBase64(zipFileBlob); + console.log({blobTobase}); + + if (!userName) { + parentEpml.request('showSnackBar', get('chatpage.cchange27')); + this.isLoading = false; + return; + } + const id = this.uid(); + const identifier = `gif_${id}`; + await publishData({ + registeredName: userName, + file: blobTobase.split(',')[1], + service: 'GIF_REPOSITORY', + identifier: this.newCollectionName, + parentEpml, + metaData: undefined, + uploadType: 'zip', + selectedAddress: this.selectedAddress, + worker: this.webWorkerImage, + isBase64: true, + }); + + await new Promise((res) => { + let interval = null; + let stop = false; + const getAnswer = async () => { + if (!stop) { + stop = true; + try { + let myCollection = await parentEpml.request( + 'apiCall', + { + url: `/arbitrary/resources?service=GIF_REPOSITORY&limit=0&name=${userName}&identifier=${this.newCollectionName}`, + } + ); + if (myCollection.length > 0) { + clearInterval(interval); + res(); + } + } catch (error) {} + stop = false; + } + }; + interval = setInterval(getAnswer, 5000); + }); + saveAs(zipFileBlob, 'zipfile'); + console.log({zipFileBlob}); + } catch (error) { + console.log(error); + } } - - await zipWriter.close(); - const zipFileBlob = await zipFileWriter.getData() - const blobTobase = await blobToBase64(zipFileBlob) - console.log({blobTobase}) - - if (!userName) { - parentEpml.request('showSnackBar', get("chatpage.cchange27")); - this.isLoading = false; - return; - } - const id = this.uid(); - const identifier = `gif_${id}`; - await publishData({ - registeredName: userName, - file : blobTobase.split(',')[1], - service: 'GIF_REPOSITORY', - identifier: this.newCollectionName, - parentEpml, - metaData: undefined, - uploadType: 'zip', - selectedAddress: this.selectedAddress, - worker: this.webWorkerImage, - isBase64: true - }) - - await new Promise((res)=> { - let interval = null - let stop = false - const getAnswer = async () => { - if (!stop) { - stop = true - try { - let myCollection = await parentEpml.request('apiCall', { - url: `/arbitrary/resources?service=GIF_REPOSITORY&limit=0&name=${userName}&identifier=${this.newCollectionName}` - }) - if (myCollection.length > 0) { - clearInterval(interval) - res() - } - } catch (error) {} - stop = false - } - } - interval = setInterval(getAnswer, 5000) - }) - saveAs(zipFileBlob, 'zipfile'); - console.log({zipFileBlob}) - } catch (error) { - console.log(error) - } - } - - setCurrentCollection(val){ - this.currentCollection = val + setCurrentCollection(val) { + this.currentCollection = val; } clearGifSelections() { - this.mode = "myCollection"; - this.gifsToBeAdded = []; + this.mode = 'myCollection'; + this.gifsToBeAdded = []; } render() { - console.log('this.currentCollection', this.currentCollection) - console.log(9, "chat gifs here") - return html` + console.log('this.currentCollection', this.currentCollection); + console.log(12, 'chat gifs here'); + return html`
- { - if (this.isLoading) return; - this.mode = "newCollection"; - }} - icon="vaadin:plus" - slot="icon"> - -
-

${translate("chatpage.cchange80")}

- { - if (this.isLoading) return; - this.mode = "explore"; - }} - icon="vaadin:search" + if (this.isLoading) return; + this.mode = 'newCollection'; + }} + icon="vaadin:plus" slot="icon"> - +

${translate( + 'chatpage.cchange80' + )}

+ { + if (this.isLoading) return; + this.mode = 'explore'; + }} + icon="vaadin:search" + slot="icon"> + + + text=${get('chatpage.cchange81')}>
-
+
{ - if (this.isLoading) return; - if (this.mode === "myCollection") return; - this.mode = "myCollection"; - }} + if (this.isLoading) return; + if (this.mode === 'myCollection') return; + this.mode = 'myCollection'; + }} > - ${translate("chatpage.cchange82")} + ${translate('chatpage.cchange82')}
{ - if(this.isLoading) return; - if (this.mode === "subscribedCollection") return; - this.mode = "subscribedCollection"; - }} + if (this.isLoading) return; + if (this.mode === 'subscribedCollection') return; + this.mode = 'subscribedCollection'; + }} > - ${translate("chatpage.cchange83")} + ${translate('chatpage.cchange83')}
-
- ${this.mode === "myCollection" && !this.currentCollection ? html` - ${this.isLoading === true ? html` -

Loading...

- ` : ''} - ${this.myGifCollections.map((collection)=> { - return html` -
-

{ - this.currentCollection = collection - }}>${collection.identifier}

- -
- ` - })} - ` : ''} - ${this.mode === "subscribedCollection" && !this.currentCollection ? html` - ${this.isLoading === true ? html` -

Loading...

- ` : ''} - ${this.mySubscribedCollections.map((collection)=> { - return html` -
-

{ - this.currentCollection = collection - }}>${collection.identifier}

- -
- ` - })} - ` : ''} - ${this.mode === "explore" && !this.currentCollection ? html` - ${this.isLoading === true ? html` -

Loading...

- ` : ''} - this.getMoreExploreGifs(val)} .exploreCollections=${this.exploreCollections} - .setCurrentCollection=${(val)=> this.setCurrentCollection(val)} - > - - ` : ''} - ${this.currentCollection && this.mode === "myCollection" ? html` - - ${this.currentCollection.gifUrls.map((gif)=> { - console.log({gif}) +
+ ${this.mode === 'myCollection' && !this.currentCollection + ? html` + ${this.isLoading === true + ? html`

Loading...

` + : ''} + ${this.myGifCollections.map((collection) => { + return html` +
+

{ + this.currentCollection = + collection; + }} + > + ${collection.identifier} +

+
+ `; + })} + ` + : '' + } + ${this.mode === 'subscribedCollection' && + !this.currentCollection + ? html` + ${this.isLoading === true + ? html`

Loading...

` + : ''} + ${this.mySubscribedCollections.map( + (collection) => { + return html` +
+

{ + this.currentCollection = + collection; + }} + > + ${collection.identifier} +

+
+ `; + } + )} + ` + : '' + } + ${this.mode === 'explore' && !this.currentCollection + ? html` + ${this.isLoading === true + ? html`

Loading...

` + : ''} + + this.getMoreExploreGifs(val)} + .exploreCollections=${this + .exploreCollections} + .setCurrentCollection=${(val) => + this.setCurrentCollection(val)} + > + ` + : '' + } + ${this.currentCollection && this.mode === 'myCollection' + ? html` + + ${this.currentCollection.gifUrls.map((gif) => { + console.log({gif}); - return html` - { - e.target.src = gif - }} src=${gif} style="width: 50px; height: 50px" /> - ` - })} - ` : ''} - ${this.currentCollection && this.mode === "subscribedCollection" ? html` - - ${this.currentCollection.gifUrls.map((gif)=> { - console.log({gif}) + return html` + { + e.target.src = gif; + }} + src=${gif} + style="width: 50px; height: 50px" + /> + `; + })} + ` + : '' + } + ${this.currentCollection && + this.mode === 'subscribedCollection' + ? html` + + ${this.currentCollection.gifUrls.map((gif) => { + console.log({gif}); - return html` - { - e.target.src = gif - }} src=${gif} style="width: 50px; height: 50px" /> - ` - })} - ` : ''} - ${this.currentCollection && this.mode === "explore" ? html` - - - ${this.currentCollection.gifUrls.map((gif)=> { - console.log({gif}) + return html` + { + e.target.src = gif; + }} + src=${gif} + style="width: 50px; height: 50px" + /> + `; + })} + ` + : '' + } + ${this.currentCollection && this.mode === 'explore' + ? html` + + + ${this.currentCollection.gifUrls.map((gif) => { + console.log({gif}); - return html` - { - e.target.src = gif - }} src=${gif} style="width: 50px; height: 50px" /> - ` - })} - ` : ''} - ${this.mode === "newCollection" ? html` -
-
-

- ${translate("chatpage.cchange84")} -

-

- ${translate("chatpage.cchange85")} -

-
- - - { - this.newCollectionName = e.target.value - })} - /> -
-
- ${this.gifsToBeAdded.map((gif, i)=> { - console.log({gif}) - return html` -
- - { - this.gifsToBeAdded[i] = { - ...gif, - name: e.target.value - } - })} /> -
- ` - })} -
- ` : ''} + return html` + { + e.target.src = gif; + }} + src=${gif} + style="width: 50px; height: 50px" + /> + `; + })} + ` + : '' + } + ${this.mode === 'newCollection' + ? html` +
+
+

+ ${translate('chatpage.cchange84')} +

+

+ ${translate('chatpage.cchange85')} +

+
+
+ this.shadowRoot + .getElementById( + 'file-picker-gif' + ) + .click()} + class="new-collection-container" + style=${this.gifsToBeAdded.length > 0 ? "padding: 10px 0;" : "padding: 60px 0;"} + > + + + +
+ + { + this.newCollectionName = + e.target.value; + }} + /> +
+
+ ${this.gifsToBeAdded.map((gif, i) => { + console.log({gif}); + return html` +
+ + { + this.gifsToBeAdded[i] = { + ...gif, + name: e.target + .value, + }; + }} + /> +
+ `; + })} +
+
+ + +
+
+
+ ` + : '' + }
-
+
- ` +
+ `; } + } -window.customElements.define('chat-gifs', ChatGifs) +window.customElements.define('chat-gifs', ChatGifs); diff --git a/qortal-ui-plugins/plugins/core/components/ChatGifs/ChatGifsExplore.js b/qortal-ui-plugins/plugins/core/components/ChatGifs/ChatGifsExplore.js index b5cca445..73e5c872 100644 --- a/qortal-ui-plugins/plugins/core/components/ChatGifs/ChatGifsExplore.js +++ b/qortal-ui-plugins/plugins/core/components/ChatGifs/ChatGifsExplore.js @@ -1,97 +1,92 @@ -import { LitElement, html, css } from 'lit' -import { render } from 'lit/html.js' -import { Epml } from '../../../../epml.js' -import * as zip from "@zip.js/zip.js"; -import { saveAs } from 'file-saver'; -import '@material/mwc-icon' +import {LitElement, html, css} from 'lit'; +import {render} from 'lit/html.js'; +import {Epml} from '../../../../epml.js'; +import * as zip from '@zip.js/zip.js'; +import {saveAs} from 'file-saver'; +import '@material/mwc-icon'; import ShortUniqueId from 'short-unique-id'; -import { publishData } from '../../../utils/publish-image.js'; +import {publishData} from '../../../utils/publish-image.js'; -const parentEpml = new Epml({ type: 'WINDOW', source: window.parent }) +const parentEpml = new Epml({type: 'WINDOW', source: window.parent}); class ChatGifsExplore extends LitElement { - static get properties() { - return { - currentCollection: {type: String}, - getMoreExploreGifs: {attribute: false}, - exploreCollections: {type: Array}, - setCurrentCollection: {attribute: false} - } - } + static get properties() { + return { + currentCollection: {type: String}, + getMoreExploreGifs: {attribute: false}, + exploreCollections: {type: Array}, + setCurrentCollection: {attribute: false}, + }; + } - static get styles() { - return css` - - ` - } + static get styles() { + return css``; + } - constructor() { - super() - this.downObserverElement = '' - this.viewElement = '' - this.exploreCollections = [] - } + constructor() { + super(); + this.downObserverElement = ''; + this.viewElement = ''; + this.exploreCollections = []; + } + elementObserver() { + const options = { + root: this.viewElement, + rootMargin: '0px', + threshold: 1, + }; + // identify an element to observe + const elementToObserve = this.downObserverElement; + // passing it a callback function + const observer = new IntersectionObserver( + this.observerHandler, + options + ); + // call `observe()` on that MutationObserver instance, + // passing it the element to observe, and the options object + observer.observe(elementToObserve); + } + observerHandler(entries) { + if (!entries[0].isIntersecting) { + return; + } else { + if (this.exploreCollections.length < 20) { + return; + } - elementObserver() { - const options = { - root: this.viewElement, - rootMargin: '0px', - threshold: 1 - } - // identify an element to observe - const elementToObserve = this.downObserverElement; - // passing it a callback function - const observer = new IntersectionObserver(this.observerHandler, options); - // call `observe()` on that MutationObserver instance, - // passing it the element to observe, and the options object - observer.observe(elementToObserve); - } + this.getMoreExploreGifs(); + } + } - observerHandler(entries) { - if (!entries[0].isIntersecting) { - return - } else { - if(this.exploreCollections.length < 20){ - return - } - - this.getMoreExploreGifs() - } - } + async firstUpdated() { + this.viewElement = this.shadowRoot.getElementById('viewElement'); + this.downObserverElement = + this.shadowRoot.getElementById('downObserver'); + this.elementObserver(); + } - - - async firstUpdated() { - this.viewElement = this.shadowRoot.getElementById('viewElement'); - this.downObserverElement = this.shadowRoot.getElementById('downObserver'); - this.elementObserver(); - } - - - - render() { - - return html` -
- ${this.exploreCollections.map((collection)=> { - return html` -
-

{ - this.setCurrentCollection(collection) - }}>${collection.identifier}

- -
- ` - })} -
-
- - ` - } - - + render() { + return html` +
+ ${this.exploreCollections.map((collection) => { + return html` +
+

{ + this.setCurrentCollection(collection); + }} + > + ${collection.identifier} +

+
+ `; + })} +
+
+ `; + } } -window.customElements.define('chat-gifs-explore', ChatGifsExplore) +window.customElements.define('chat-gifs-explore', ChatGifsExplore); diff --git a/qortal-ui-plugins/plugins/core/components/ChatPage.js b/qortal-ui-plugins/plugins/core/components/ChatPage.js index 6d8a1d90..bbb1e90f 100644 --- a/qortal-ui-plugins/plugins/core/components/ChatPage.js +++ b/qortal-ui-plugins/plugins/core/components/ChatPage.js @@ -824,7 +824,9 @@ class ChatPage extends LitElement { } .chat-gifs { - position: relative; + position: absolute; + right: 15px; + bottom: 100px; justify-self: flex-end; width: fit-content; height: auto; @@ -1050,26 +1052,26 @@ console.log({zipFileBlob}) ` : this.renderChatScroller()}
- -
{ - this.setOpenGifModal(false); - this.editor.commands.focus("end"); - this.shadowRoot.querySelector("chat-gifs").clearGifSelections(); - }} +
{ + this.setOpenGifModal(false); + this.editor.commands.focus("end"); + this.shadowRoot.querySelector("chat-gifs").clearGifSelections(); + }} style=${this.openGifModal ? "visibility: visible; z-index: 4" : "visibility: hidden; z-index: -100"}> -
- - +
+ + +
+ style=${(this.lastMessageRefVisible && !this.imageFile && !this.openGifModal) ? 'opacity: 1;' : 'opacity: 0;'}> { this.shadowRoot.querySelector("chat-scroller").shadowRoot.getElementById("downObserver") .scrollIntoView({ diff --git a/qortal-ui-plugins/plugins/core/components/UserInfo/UserInfo-css.js b/qortal-ui-plugins/plugins/core/components/UserInfo/UserInfo-css.js index d22025ca..465f75da 100644 --- a/qortal-ui-plugins/plugins/core/components/UserInfo/UserInfo-css.js +++ b/qortal-ui-plugins/plugins/core/components/UserInfo/UserInfo-css.js @@ -1,69 +1,69 @@ import { css } from 'lit' export const userInfoStyles = css` - .user-info-header { - font-family: Montserrat, sans-serif; - text-align: center; - font-size: 28px; - color: var(--chat-bubble-msg-color); - margin-bottom: 10px; - padding: 10px 0; - user-select: none; - } +.user-info-header { +font-family: Montserrat, sans-serif; +text-align: center; +font-size: 28px; +color: var(--chat-bubble-msg-color); +margin-bottom: 10px; +padding: 10px 0; +user-select: none; +} - .avatar-container { - display: flex; - justify-content: center; - } +.avatar-container { +display: flex; +justify-content: center; +} - .user-info-avatar { - width: 100px; - height: 100px; - border-radius: 50%; - margin: 10px 0; - } +.user-info-avatar { +width: 100px; +height: 100px; +border-radius: 50%; +margin: 10px 0; +} - .user-info-no-avatar { - display: flex; - justify-content: center; - align-items: center; - text-transform: capitalize; - font-size: 50px; - font-family: Roboto, sans-serif; - width: 100px; - height: 100px; - border-radius:50%; - background: var(--chatHeadBg); - color: var(--chatHeadText); - } - - .send-message-button { - font-family: Roboto, sans-serif; - letter-spacing: 0.3px; - font-weight: 300; - padding: 8px 5px; - border-radius: 3px; - text-align: center; - color: var(--mdc-theme-primary); - transition: all 0.3s ease-in-out; - } +.user-info-no-avatar { +display: flex; +justify-content: center; +align-items: center; +text-transform: capitalize; +font-size: 50px; +font-family: Roboto, sans-serif; +width: 100px; +height: 100px; +border-radius:50%; +background: var(--chatHeadBg); +color: var(--chatHeadText); +} - .send-message-button:hover { - cursor: pointer; - background-color: #03a8f485; - } +.send-message-button { +font-family: Roboto, sans-serif; +letter-spacing: 0.3px; +font-weight: 300; +padding: 8px 5px; +border-radius: 3px; +text-align: center; +color: var(--mdc-theme-primary); +transition: all 0.3s ease-in-out; +} - .close-icon { - position: absolute; - top: 3px; - right: 5px; - color: #676b71; - width: 14px; - transition: all 0.1s ease-in-out; - } +.send-message-button:hover { +cursor: pointer; +background-color: #03a8f485; +} - .close-icon:hover { - cursor: pointer; - color: #494c50; - } -` \ No newline at end of file +.close-icon { +position: absolute; +top: 3px; +right: 5px; +color: #676b71; +width: 14px; +transition: all 0.1s ease-in-out; +} + +.close-icon:hover { +cursor: pointer; +color: #494c50; +} +` From 013b97ca51de2086b975f263ca9494f7923c09b8 Mon Sep 17 00:00:00 2001 From: Justin Ferrari Date: Mon, 6 Feb 2023 23:18:51 +0200 Subject: [PATCH 09/16] Fixed metadata issue + continued styling --- qortal-ui-core/language/us.json | 6 +- .../core/components/ChatGifs/ChatGifs-css.js | 144 +++++++++++++++--- .../core/components/ChatGifs/ChatGifs.js | 80 ++++++---- .../plugins/core/components/ChatPage.js | 14 +- 4 files changed, 190 insertions(+), 54 deletions(-) diff --git a/qortal-ui-core/language/us.json b/qortal-ui-core/language/us.json index 2499a3a1..77f635b5 100644 --- a/qortal-ui-core/language/us.json +++ b/qortal-ui-core/language/us.json @@ -579,7 +579,11 @@ "cchange83": "Subscribed Collections", "cchange84": "Upload your gif files", "cchange85": "File should be .Gif", - "cchange86": "Upload Collection" + "cchange86": "Upload Collection", + "cchange87": "A collection name is required!", + "cchange88": "Collection Name", + "cchange89": "Gif Collection Uploaded Successfully!", + "cchange90": "Gifs uploading, please wait..." }, "welcomepage": { "wcchange1": "Welcome to Q-Chat", diff --git a/qortal-ui-plugins/plugins/core/components/ChatGifs/ChatGifs-css.js b/qortal-ui-plugins/plugins/core/components/ChatGifs/ChatGifs-css.js index 6144b149..1911758d 100644 --- a/qortal-ui-plugins/plugins/core/components/ChatGifs/ChatGifs-css.js +++ b/qortal-ui-plugins/plugins/core/components/ChatGifs/ChatGifs-css.js @@ -191,31 +191,15 @@ color: var(--mdc-theme-primary); } .gifs-added-col { +display: flex; flex-direction: column; -justify-content: space-between; +justify-content: flex-end; flex: 1 1 0%; margin-top: 10px; overflow-y: auto; max-height: 300px; } -.gifs-added-col::-webkit-scrollbar-track { -background-color: whitesmoke; -border-radius: 7px; -} - -.gifs-added-col::-webkit-scrollbar { -width: 6px; -border-radius: 7px; -background-color: whitesmoke; -} - -.gifs-added-col::-webkit-scrollbar-thumb { -background-color: rgb(180, 176, 176); -border-radius: 7px; -transition: all 0.3s ease-in-out; -} - .gifs-added-row { display: flex; flex-direction: column; @@ -227,6 +211,23 @@ overflow-y: auto; border-bottom: none; } +.gifs-added-row::-webkit-scrollbar-track { +background-color: whitesmoke; +border-radius: 7px; +} + +.gifs-added-row::-webkit-scrollbar { +width: 6px; +border-radius: 7px; +background-color: whitesmoke; +} + +.gifs-added-row::-webkit-scrollbar-thumb { +background-color: rgb(180, 176, 176); +border-radius: 7px; +transition: all 0.3s ease-in-out; +} + .gif-input { display: flex; flex-direction: row; @@ -267,6 +268,72 @@ width: 100%; margin-top: 10px; } +.upload-collection-name { +display: block; +padding: 8px 10px; +font-size: 16px; +font-family: Montserrat, sans-serif; +font-weight: 600; +background-color: #ebeaea21; +border: 1px solid var(--mdc-theme-primary); +border-radius: 5px; +color: var(--chat-bubble-msg-color); +outline: none; +} + +.upload-collection-name::placeholder { +font-size: 16px; +font-family: Montserrat, sans-serif; +font-weight: 600; +opacity: 0.6; +color: var(--chat-bubble-msg-color); +} + +.collection-back-button { + display: flex; + font-family: Roboto, sans-serif; + font-weight: 300; + letter-spacing: 0.3px; + font-size: 16px; + color: var(--chat-bubble-msg-color); + flex-direction: row; + align-items: center; + transition: box-shadow 0.2s ease-in-out; + background-color: rgb(111, 116, 129); + border-radius: 10px; + box-shadow: rgb(0 0 0 / 15%) 1.95px 1.95px 2.6px; + padding: 8px 15px; + cursor: pointer; +} + +.collection-back-button:hover { + border: none; + box-sizing: border-box; + box-shadow: rgba(50, 50, 93, 0.25) 0px 6px 12px -2px, rgba(0, 0, 0, 0.3) 0px 3px 7px -3px; +} + +.collection-card { + display: flex; + font-family: Roboto, sans-serif; + font-weight: 300; + letter-spacing: 0.3px; + font-size: 19px; + color: var(--chat-bubble-msg-color); + flex-direction: row; + align-items: center; + transition: box-shadow 0.2s ease-in-out; + box-shadow: none; + padding: 10px; + cursor: pointer; +} + +.collection-card:hover { + border: none; + border-radius: 4px; + box-sizing: border-box; + box-shadow: rgb(0 0 0 / 14%) 0px 4px 5px 0px, rgb(0 0 0 / 12%) 0px 1px 10px 0px, rgb(0 0 0 / 20%) 0px 2px 4px -1px; +} + .upload-button { font-family: Roboto, sans-serif; font-size: 16px; @@ -299,4 +366,45 @@ cursor: pointer; background-color: #03a8f475; } +.lds-circle { + display: flex; + align-items: center; + justify-content: center; + margin-top: 70px; +} + +.lds-circle > div { + display: inline-block; + width: 80px; + height: 80px; + margin: 8px; + border-radius: 50%; + background: var(--mdc-theme-primary); + animation: lds-circle 2.4s cubic-bezier(0, 0.2, 0.8, 1) infinite; +} + +@keyframes lds-circle { + 0%, 100% { + animation-timing-function: cubic-bezier(0.5, 0, 1, 0.5); + } + 0% { + transform: rotateY(0deg); + } + 50% { + transform: rotateY(1800deg); + animation-timing-function: cubic-bezier(0, 0.5, 0.5, 1); + } + 100% { + transform: rotateY(3600deg); + } +} + +.gifs-loading-message { + font-family: Montserrat, sans-serif; + font-size: 20px; + color: var(--chat-bubble-msg-color); + margin: 0 0 10px 0; + text-align: center; +} + `; diff --git a/qortal-ui-plugins/plugins/core/components/ChatGifs/ChatGifs.js b/qortal-ui-plugins/plugins/core/components/ChatGifs/ChatGifs.js index a6315fcc..3a79c903 100644 --- a/qortal-ui-plugins/plugins/core/components/ChatGifs/ChatGifs.js +++ b/qortal-ui-plugins/plugins/core/components/ChatGifs/ChatGifs.js @@ -66,6 +66,8 @@ editor: {type: Object}, url: `/arbitrary/metadata/GIF_REPOSITORY/${this.myAccountName}/${collection.identifier}`, }); + console.log({metaData}); + collectionObj = { ...collection, gifUrls: [], @@ -164,11 +166,11 @@ editor: {type: Object}, const userName = await this.getName(this.selectedAddress.address); this.myAccountName = userName; if (this.myAccountName) { - const getMyGifColloctions = await parentEpml.request('apiCall', { + const getMyGifCollections = await parentEpml.request('apiCall', { url: `/arbitrary/resources?service=GIF_REPOSITORY&limit=0&name=${this.myAccountName}`, }); const gifCollectionWithMetaData = await this.structureCollections( - getMyGifColloctions + getMyGifCollections ); console.log({gifCollectionWithMetaData}); @@ -313,22 +315,21 @@ editor: {type: Object}, async uploadGifCollection() { if (!this.newCollectionName) { - parentEpml.request('showSnackBar', get('chatpage.cchange27')); + parentEpml.request('showSnackBar', get('chatpage.cchange87')); return; } - - // if(!isAlphanumeric(this.newCollectionName)){ - // parentEpml.request('showSnackBar', get("chatpage.cchange27")); - // return - // } try { + this.setGifsLoading(true); + this.isLoading = true; const userName = await this.getName(this.selectedAddress.address); const doesNameExist = await parentEpml.request('apiCall', { url: `/arbitrary/resources?service=GIF_REPOSITORY&limit=0&name=${userName}&identifier=${this.newCollectionName}`, }); if (doesNameExist.length !== 0) { - parentEpml.request('showSnackBar', get('chatpage.cchange27')); + parentEpml.request('showSnackBar', get('chatpage.cchange87')); + this.isLoading = false; + this.setGifsLoading(false); return; } function blobToBase64(blob) { @@ -365,6 +366,7 @@ editor: {type: Object}, if (!userName) { parentEpml.request('showSnackBar', get('chatpage.cchange27')); + this.setGifsLoading(false); this.isLoading = false; return; } @@ -376,7 +378,7 @@ editor: {type: Object}, service: 'GIF_REPOSITORY', identifier: this.newCollectionName, parentEpml, - metaData: undefined, + metaData: `title=${this.newCollectionName}`, uploadType: 'zip', selectedAddress: this.selectedAddress, worker: this.webWorkerImage, @@ -406,7 +408,13 @@ editor: {type: Object}, }; interval = setInterval(getAnswer, 5000); }); - saveAs(zipFileBlob, 'zipfile'); + // saveAs(zipFileBlob, 'zipfile'); + this.isLoading = false; + this.setGifsLoading(false); + this.mode = 'myCollection'; + this.gifsToBeAdded = []; + this.newCollectionName = ''; + parentEpml.request('showSnackBar', get('chatpage.cchange89')); console.log({zipFileBlob}); } catch (error) { console.log(error); @@ -424,7 +432,7 @@ editor: {type: Object}, render() { console.log('this.currentCollection', this.currentCollection); - console.log(12, 'chat gifs here'); + console.log(13, 'chat gifs here'); return html`
@@ -514,19 +522,15 @@ editor: {type: Object}, ${this.mode === 'myCollection' && !this.currentCollection ? html` ${this.isLoading === true - ? html`

Loading...

` + ? html`
` : ''} ${this.myGifCollections.map((collection) => { return html` -
-

{ +

{ this.currentCollection = collection; - }} - > + }} class='collection-card'> ${collection.identifier} -

`; })} @@ -577,13 +581,15 @@ editor: {type: Object}, } ${this.currentCollection && this.mode === 'myCollection' ? html` - + + ${translate('general.back')} +
${this.currentCollection.gifUrls.map((gif) => { console.log({gif}); @@ -660,9 +666,9 @@ editor: {type: Object}, ` : '' } - ${this.mode === 'newCollection' + ${this.mode === 'newCollection' && this.isLoading === false ? html` -
+

${translate('chatpage.cchange84')} @@ -679,7 +685,7 @@ editor: {type: Object}, ) .click()} class="new-collection-container" - style=${this.gifsToBeAdded.length > 0 ? "padding: 10px 0;" : "padding: 60px 0;"} + style=${this.gifsToBeAdded.length > 0 ? "padding: 10px 0;" : "padding: 60px 0;"} > { this.newCollectionName = @@ -720,21 +728,20 @@ editor: {type: Object}, />

-
+
${this.gifsToBeAdded.map((gif, i) => { - console.log({gif}); return html`
{ this.gifsToBeAdded[i] = { @@ -747,7 +754,7 @@ editor: {type: Object},
`; })} -
+
` - : '' + : this.mode === 'newCollection' && this.isLoading === true ? ( + html` +
+

${translate("chatpage.cchange90")}

+
+
+ ` + ) + : '' }
diff --git a/qortal-ui-plugins/plugins/core/components/ChatPage.js b/qortal-ui-plugins/plugins/core/components/ChatPage.js index bbb1e90f..61b2a1a8 100644 --- a/qortal-ui-plugins/plugins/core/components/ChatPage.js +++ b/qortal-ui-plugins/plugins/core/components/ChatPage.js @@ -101,8 +101,9 @@ class ChatPage extends LitElement { openUserInfo: { type: Boolean }, selectedHead: { type: Object }, userName: { type: String }, - goToRepliedMessage: {attribute: false}, - openGifModal: {type: Boolean} + goToRepliedMessage: { attribute: false }, + openGifModal: { type: Boolean }, + gifsLoading: { type: Boolean }, } } @@ -862,6 +863,7 @@ class ChatPage extends LitElement { this.setOpenUserInfo = this.setOpenUserInfo.bind(this) this.setUserName = this.setUserName.bind(this) this.setSelectedHead = this.setSelectedHead.bind(this) + this.setGifsLoading = this.setGifsLoading.bind(this) this.selectedAddress = {} this.userName = "" this.chatId = '' @@ -962,6 +964,10 @@ class ChatPage extends LitElement { console.log('this.gifsToBeAdded', this.gifsToBeAdded) } + setGifsLoading(props) { + this.gifsLoading = props; + } + async uploadGifCollection(){ try { function blobToBase64(blob) { @@ -1055,6 +1061,7 @@ console.log({zipFileBlob})
{ + if (this.gifsLoading) return; this.setOpenGifModal(false); this.editor.commands.focus("end"); this.shadowRoot.querySelector("chat-gifs").clearGifSelections(); @@ -1067,7 +1074,8 @@ console.log({zipFileBlob}) + .webWorkerImage=${this.webWorkerImage} + .setGifsLoading=${(val) => this.setGifsLoading(val)}>
Date: Wed, 8 Feb 2023 23:00:50 +0200 Subject: [PATCH 10/16] Continued UI styling and fixed img bug --- qortal-ui-core/font/PaytoneOne.ttf | Bin 0 -> 100576 bytes qortal-ui-core/font/material-icons.css | 9 +- qortal-ui-core/language/us.json | 7 +- .../core/components/ChatGifs/ChatGifs-css.js | 73 +++++- .../core/components/ChatGifs/ChatGifs.js | 207 +++++++++++------- .../plugins/core/components/ImageComponent.js | 80 +++++++ 6 files changed, 287 insertions(+), 89 deletions(-) create mode 100644 qortal-ui-core/font/PaytoneOne.ttf create mode 100644 qortal-ui-plugins/plugins/core/components/ImageComponent.js diff --git a/qortal-ui-core/font/PaytoneOne.ttf b/qortal-ui-core/font/PaytoneOne.ttf new file mode 100644 index 0000000000000000000000000000000000000000..5cb0bbb8ba8da3de2df608a9e4dec5e4642c8d31 GIT binary patch literal 100576 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 literal 0 HcmV?d00001 diff --git a/qortal-ui-core/font/material-icons.css b/qortal-ui-core/font/material-icons.css index bf6891fc..d2bfeaae 100644 --- a/qortal-ui-core/font/material-icons.css +++ b/qortal-ui-core/font/material-icons.css @@ -54,12 +54,19 @@ url(Livvic.ttf) format('truetype'); } +@font-face { + font-family: 'Paytone One', sans-serif; + src: local('PaytoneOne'), + local('PaytoneOne'), + url(PaytoneOne.ttf) format('truetype'); +} + .material-icons { font-family: 'Material Icons'; font-weight: normal; font-style: normal; font-size: 24px; - /* Preferred icon size */ + /* Preferred icon size */ display: inline-block; line-height: 1; text-transform: none; diff --git a/qortal-ui-core/language/us.json b/qortal-ui-core/language/us.json index 77f635b5..1b3e31f7 100644 --- a/qortal-ui-core/language/us.json +++ b/qortal-ui-core/language/us.json @@ -583,7 +583,12 @@ "cchange87": "A collection name is required!", "cchange88": "Collection Name", "cchange89": "Gif Collection Uploaded Successfully!", - "cchange90": "Gifs uploading, please wait..." + "cchange90": "Gifs uploading, please wait...", + "cchange91": "Something went wrong! Please try changing tabs and coming back.", + "cchange92": "You currently have no collections.", + "cchange93": "You currently have no subscribed collections.", + "cchange94": "Error fetching image. Retrying...", + "cchange95": "Failed to fetch image! Please visit another collection and try again!" }, "welcomepage": { "wcchange1": "Welcome to Q-Chat", diff --git a/qortal-ui-plugins/plugins/core/components/ChatGifs/ChatGifs-css.js b/qortal-ui-plugins/plugins/core/components/ChatGifs/ChatGifs-css.js index 1911758d..b4607b9f 100644 --- a/qortal-ui-plugins/plugins/core/components/ChatGifs/ChatGifs-css.js +++ b/qortal-ui-plugins/plugins/core/components/ChatGifs/ChatGifs-css.js @@ -12,8 +12,9 @@ width: fit-content; justify-self: flex-end; place-self: end flex-end; min-height: 400px; -max-height: 95vh; +max-height: calc(95vh - 90px); min-width: 370px; +max-width: 370px; box-shadow: var(--gifs-drop-shadow); } @@ -137,6 +138,49 @@ display: flex; flex-direction: column; width: 100%; height: 100%; +overflow: hidden; +} + +.collection-gifs { +display: grid; +grid-template-columns: repeat(2, 1fr); +grid-gap: 10px; +margin-top: 10px; +overflow-y: auto; +overflow-x: hidden; +} + +.collection-gifs::-webkit-scrollbar-track { +background-color: whitesmoke; +border-radius: 7px; +} + +.collection-gifs::-webkit-scrollbar { +width: 6px; +border-radius: 7px; +background-color: whitesmoke; +} + +.collection-gifs::-webkit-scrollbar-thumb { +background-color: rgb(180, 176, 176); +border-radius: 7px; +transition: all 0.3s ease-in-out; +} + +.collection-gif { +border-radius: 15px; +background-color: transparent; +cursor: pointer; +width: 100%; +height: 150px; +object-fit: cover; +border: 1px solid transparent; +transition: all 0.2s cubic-bezier(0, 0.55, 0.45, 1); +box-shadow: rgb(50 50 93 / 25%) 0px 6px 12px -2px, rgb(0 0 0 / 30%) 0px 3px 7px -3px; +} + +.collection-gif:hover { +border: 1px solid var(--mdc-theme-primary ); } .new-collection-row { @@ -295,21 +339,39 @@ color: var(--chat-bubble-msg-color); font-weight: 300; letter-spacing: 0.3px; font-size: 16px; + width: fit-content; + gap: 10px; color: var(--chat-bubble-msg-color); flex-direction: row; align-items: center; transition: box-shadow 0.2s ease-in-out; - background-color: rgb(111, 116, 129); + background-color: var(--gif-button-row-bg); border-radius: 10px; - box-shadow: rgb(0 0 0 / 15%) 1.95px 1.95px 2.6px; - padding: 8px 15px; + box-shadow: rgb(0 0 0 / 20%) 0px 0px 0px; + padding: 8px 10px; cursor: pointer; } .collection-back-button:hover { border: none; box-sizing: border-box; - box-shadow: rgba(50, 50, 93, 0.25) 0px 6px 12px -2px, rgba(0, 0, 0, 0.3) 0px 3px 7px -3px; + box-shadow: rgb(0 0 0 / 14%) 0px 4px 5px 0px, rgb(0 0 0 / 12%) 0px 1px 10px 0px, rgb(0 0 0 / 20%) 0px 2px 4px -1px; +} + +.collection-back-button-arrow { + font-size: 10px; +} + +.no-collections { + display: flex; + align-items: center; + justify-content: center; + text-align: center; + color: var(--chat-bubble-msg-color); + font-size: 20px; + font-family: Paytone One, sans-serif; + margin-top: 20px; + user-select: none; } .collection-card { @@ -405,6 +467,7 @@ background-color: #03a8f475; color: var(--chat-bubble-msg-color); margin: 0 0 10px 0; text-align: center; + user-select: none; } `; diff --git a/qortal-ui-plugins/plugins/core/components/ChatGifs/ChatGifs.js b/qortal-ui-plugins/plugins/core/components/ChatGifs/ChatGifs.js index 3a79c903..b6ae1475 100644 --- a/qortal-ui-plugins/plugins/core/components/ChatGifs/ChatGifs.js +++ b/qortal-ui-plugins/plugins/core/components/ChatGifs/ChatGifs.js @@ -9,6 +9,7 @@ import {publishData} from '../../../utils/publish-image.js'; import {translate, get} from 'lit-translate'; import {gifExplorerStyles} from './ChatGifs-css.js'; import './ChatGifsExplore.js'; +import '../ImageComponent.js'; import '@vaadin/tooltip'; const parentEpml = new Epml({type: 'WINDOW', source: window.parent}); @@ -66,7 +67,7 @@ editor: {type: Object}, url: `/arbitrary/metadata/GIF_REPOSITORY/${this.myAccountName}/${collection.identifier}`, }); - console.log({metaData}); + console.log({metaData}); collectionObj = { ...collection, @@ -173,10 +174,12 @@ editor: {type: Object}, getMyGifCollections ); - console.log({gifCollectionWithMetaData}); - this.myGifCollections = gifCollectionWithMetaData; - } + return gifCollectionWithMetaData; + } else { + return []; + } } + async getAllCollections() { this.pageNumber = 0; // for the explore section @@ -202,7 +205,6 @@ editor: {type: Object}, const name = splitCollection[0]; const identifier = splitCollection[1]; try { - console.log({collection}); const data = await parentEpml.request('apiCall', { url: `/arbitrary/resources?service=GIF_REPOSITORY&limit=0&name=${name}&identifier=${identifier}`, }); @@ -219,7 +221,7 @@ editor: {type: Object}, const savedCollectionsWithMetaData = await this.structureCollections( savedCollections ); - this.mySubscribedCollections = savedCollectionsWithMetaData; + return savedCollectionsWithMetaData; } async firstUpdated() { @@ -233,10 +235,23 @@ editor: {type: Object}, 'background-color: var(--gif-tooltip-bg); color: var(--chat-bubble-msg-color); text-align: center; padding: 20px 10px; font-family: Roboto, sans-serif; letter-spacing: 0.3px; font-weight: 300; font-size: 13.5px; transition: all 0.3s ease-in-out;'; try { - this.isLoading = true; - await this.getMyGifCollections(); + this.isLoading = true; + const myCollections = await this.getMyGifCollections(); + const savedCollections = await this.getSavedCollections(); + + if (!Array.isArray(myCollections) && !Array.isArray(savedCollections)) { + parentEpml.request('showSnackBar', get('chatpage.cchange91')); + return; + } + + await new Promise((res) => { + setTimeout(() => { + res(); + }, 1000) + }); + this.myGifCollections = myCollections; + this.mySubscribedCollections = savedCollections; await this.getAllCollections(); - await this.getSavedCollections(); this.isLoading = false; } catch (error) { this.isLoading = false; @@ -245,16 +260,20 @@ editor: {type: Object}, } async updated(changedProperties) { - console.log({changedProperties}); if (changedProperties && changedProperties.has('mode')) { const mode = this.mode; - console.log({mode}); if (mode === 'myCollection') { try { + this.myGifCollections = []; this.isLoading = true; - - await this.getMyGifCollections(); - this.isLoading = false; + const collections = await this.getMyGifCollections(); + await new Promise((res) => { + setTimeout(() => { + res(); + }, 1000) + }); + this.myGifCollections = collections; + this.isLoading = false; } catch (error) { this.isLoading = false; } @@ -272,10 +291,16 @@ editor: {type: Object}, } if (mode === 'subscribedCollection') { try { + this.mySubscribedCollections = []; this.isLoading = true; - - await this.getSavedCollections(); - this.isLoading = false; + const savedCollections = await this.getSavedCollections(); + await new Promise((res) => { + setTimeout(() => { + res(); + }, 1000) + }); + this.mySubscribedCollections = savedCollections; + this.isLoading = false; } catch (error) { this.isLoading = false; } @@ -301,16 +326,13 @@ editor: {type: Object}, } addGifs(gifs) { - console.log('gifs', gifs); const mapGifs = gifs.map((file) => { return { file, name: file.name, }; }); - console.log({mapGifs}); this.gifsToBeAdded = [...this.gifsToBeAdded, ...mapGifs]; - console.log('this.gifsToBeAdded', this.gifsToBeAdded); } async uploadGifCollection() { @@ -362,7 +384,6 @@ editor: {type: Object}, await zipWriter.close(); const zipFileBlob = await zipFileWriter.getData(); const blobTobase = await blobToBase64(zipFileBlob); - console.log({blobTobase}); if (!userName) { parentEpml.request('showSnackBar', get('chatpage.cchange27')); @@ -411,12 +432,11 @@ editor: {type: Object}, // saveAs(zipFileBlob, 'zipfile'); this.isLoading = false; this.setGifsLoading(false); - this.mode = 'myCollection'; - this.gifsToBeAdded = []; - this.newCollectionName = ''; + this.mode = 'myCollection'; + this.gifsToBeAdded = []; + this.newCollectionName = ''; parentEpml.request('showSnackBar', get('chatpage.cchange89')); - console.log({zipFileBlob}); - } catch (error) { + } catch (error) { console.log(error); } } @@ -432,7 +452,7 @@ editor: {type: Object}, render() { console.log('this.currentCollection', this.currentCollection); - console.log(13, 'chat gifs here'); + console.log(27, 'chat gifs here'); return html`
@@ -464,9 +484,9 @@ editor: {type: Object}, id="explore-collections-icon" class="explore-collections-icon" @click=${() => { - if (this.isLoading) return; - this.mode = 'explore'; - }} + if (this.isLoading) return; + this.mode = 'explore'; + }} icon="vaadin:search" slot="icon"> @@ -496,6 +516,7 @@ editor: {type: Object}, if (this.isLoading) return; if (this.mode === 'myCollection') return; this.mode = 'myCollection'; + this.currentCollection = null; }} > ${translate('chatpage.cchange82')} @@ -512,6 +533,7 @@ editor: {type: Object}, if (this.isLoading) return; if (this.mode === 'subscribedCollection') return; this.mode = 'subscribedCollection'; + this.currentCollection = null; }} > ${translate('chatpage.cchange83')} @@ -524,48 +546,60 @@ editor: {type: Object}, ${this.isLoading === true ? html`
` : ''} - ${this.myGifCollections.map((collection) => { - return html` -
{ - this.currentCollection = - collection; - }} class='collection-card'> - ${collection.identifier} -
- `; - })} + ${(this.myGifCollections.length === 0 && !this.isLoading) ? ( + html` +
${translate('chatpage.cchange92')}
+ ` + ) : ( + html` + ${(this.myGifCollections || []).map((collection) => { + return html` +
{ + this.currentCollection = + collection; + }} class='collection-card'> + ${collection.identifier} +
+ `; + })} + ` + )} ` : '' } - ${this.mode === 'subscribedCollection' && + ${this.mode === 'subscribedCollection' && !this.currentCollection ? html` ${this.isLoading === true - ? html`

Loading...

` + ? html`
` : ''} - ${this.mySubscribedCollections.map( - (collection) => { - return html` -
-

{ + ${(this.mySubscribedCollections.length === 0 && !this.isLoading) ? ( + html` +

${translate('chatpage.cchange93')}
+ ` + ) : ( + html` + ${this.mySubscribedCollections.map( + (collection) => { + return html` +
{ this.currentCollection = collection; - }} - > - ${collection.identifier} -

-
- `; - } - )} + }} class='collection-card'> + ${collection.identifier} +
+ `; + } + )} + ` + )} ` : '' } ${this.mode === 'explore' && !this.currentCollection - ? html` + ? html` ${this.isLoading === true - ? html`

Loading...

` + ? html`
` : ''} { this.currentCollection = null; }} > - + ${translate('general.back')}
- ${this.currentCollection.gifUrls.map((gif) => { - console.log({gif}); - - return html` - { - e.target.src = gif; - }} - src=${gif} - style="width: 50px; height: 50px" - /> - `; - })} +
+ ${this.currentCollection.gifUrls.map((gif) => { + console.log({gif}); + return html` + + + `; + })} +
` : '' } ${this.currentCollection && this.mode === 'subscribedCollection' ? html` - + + + ${translate('general.back')} +
+
${this.currentCollection.gifUrls.map((gif) => { - console.log({gif}); - return html` { e.target.src = gif; }} src=${gif} - style="width: 50px; height: 50px" /> `; })} +
` : '' } @@ -651,7 +695,6 @@ editor: {type: Object}, Subscribe to this collection ${this.currentCollection.gifUrls.map((gif) => { - console.log({gif}); return html`
${this.gifsToBeAdded.map((gif, i) => { @@ -766,7 +809,7 @@ editor: {type: Object}, ${translate('general.back')}
+ ` + : html` + + this.getAllCollections(val)} .getMoreExploreGifs=${(val) => this.getMoreExploreGifs(val)} .exploreCollections=${this @@ -625,6 +734,8 @@ editor: {type: Object}, .setCurrentCollection=${(val) => this.setCurrentCollection(val)} > + ` + } ` : '' } @@ -634,8 +745,10 @@ editor: {type: Object}, ${this.currentCollection.gifUrls.map((gif) => { return html` this.sendMessage(val)} + .setOpenGifModal=${(val) => this.setOpenGifModal(val)} .class=${'gif-image'} - .url=${gif} + .gif=${gif} .alt=${'gif-image'}> `; @@ -651,8 +764,10 @@ editor: {type: Object}, ${this.currentCollection.gifUrls.map((gif) => { return html` this.sendMessage(val)} + .setOpenGifModal=${(val) => this.setOpenGifModal(val)} .class=${'gif-image'} - .url=${gif} + .gif=${gif} .alt=${'gif-image'}> `; @@ -662,28 +777,38 @@ editor: {type: Object}, : '' } ${this.currentCollection && this.mode === 'explore' - ? html` - + ? html` +
${this.currentCollection.gifUrls.map((gif) => { - return html` - { - e.target.src = gif; - }} - src=${gif} - style="width: 50px; height: 50px" - /> + this.sendMessage(val)} + .setOpenGifModal=${(val) => this.setOpenGifModal(val)} + .class=${'gif-image'} + .gif=${gif} + .alt=${'gif-image'}> + `; })} +
+ ${this.isSubscribed ? ( + html` + + ` + ) : ( + html` + + ` + )} ` : '' } @@ -692,10 +817,10 @@ editor: {type: Object},

- ${translate('chatpage.cchange84')} + ${translate('gifs.gchange5')}

- ${translate('chatpage.cchange85')} + ${translate('gifs.gchange6')}

{ this.newCollectionName = @@ -793,7 +918,7 @@ editor: {type: Object}, this.uploadGifCollection(); }} > - ${translate('chatpage.cchange86')} + ${translate('gifs.gchange7')}
@@ -802,7 +927,7 @@ editor: {type: Object}, : this.mode === 'newCollection' && this.isLoading === true ? ( html`
-

${translate("chatpage.cchange90")}

+

${translate("gifs.gchange11")}

` diff --git a/qortal-ui-plugins/plugins/core/components/ChatGifs/ChatGifsExplore-css.js b/qortal-ui-plugins/plugins/core/components/ChatGifs/ChatGifsExplore-css.js index ce80afc1..2ce632ba 100644 --- a/qortal-ui-plugins/plugins/core/components/ChatGifs/ChatGifsExplore-css.js +++ b/qortal-ui-plugins/plugins/core/components/ChatGifs/ChatGifsExplore-css.js @@ -61,4 +61,77 @@ opacity: 0.6; color: var(--chat-bubble-msg-color); } +.search-collection-wrapper { +display: flex; +justify-content: center; +align-items: center; +width: 100%; +position: relative; +} + +.explore-collections-icon { +position: absolute; +right: 20px; +font-size: 13px; +color: var(--chat-group); +cursor: pointer; +} + +.clear-search-icon { +position: absolute; +right: 15px; +font-size: 16px; +color: var(--chat-group); +padding: 1px; +border-radius: 50%; +background-color: transparent; +transition: all 0.3s ease-in-out; +} + +.clear-search-icon:hover { +cursor: pointer; +background-color: #e4e3e389 +} + +.gifs-loading-message { +font-family: Montserrat, sans-serif; +font-size: 20px; +font-weight: 600; +color: var(--chat-bubble-msg-color); +margin: 0 0 10px 0; +text-align: center; +user-select: none; +} + +.lds-circle { +display: flex; +align-items: center; +justify-content: center; +} + +.lds-circle > div { +display: inline-block; +width: 80px; +height: 80px; +margin: 8px; +border-radius: 50%; +background: var(--mdc-theme-primary); +animation: lds-circle 2.4s cubic-bezier(0, 0.2, 0.8, 1) infinite; +} + +@keyframes lds-circle { +0%, 100% { + animation-timing-function: cubic-bezier(0.5, 0, 1, 0.5); +} +0% { + transform: rotateY(0deg); +} +50% { + transform: rotateY(1800deg); + animation-timing-function: cubic-bezier(0, 0.5, 0.5, 1); +} +100% { + transform: rotateY(3600deg); +} +} ` diff --git a/qortal-ui-plugins/plugins/core/components/ChatGifs/ChatGifsExplore.js b/qortal-ui-plugins/plugins/core/components/ChatGifs/ChatGifsExplore.js index 1c1eb3a9..9db3f797 100644 --- a/qortal-ui-plugins/plugins/core/components/ChatGifs/ChatGifsExplore.js +++ b/qortal-ui-plugins/plugins/core/components/ChatGifs/ChatGifsExplore.js @@ -7,15 +7,18 @@ import '@material/mwc-icon'; const parentEpml = new Epml({ type: 'WINDOW', source: window.parent }); class ChatGifsExplore extends LitElement { -static get properties() { -return { -currentCollection: { type: String }, -searchCollectionName: {type: String}, -getMoreExploreGifs: { attribute: false }, -exploreCollections: { type: Array }, -setCurrentCollection: { attribute: false }, -}; -} + static get properties() { + return { + currentCollection: { type: String }, + searchCollectionName: {type: String}, + getMoreExploreGifs: { attribute: false }, + exploreCollections: { type: Array }, + setCurrentCollection: { attribute: false }, + isLoading: { type: Boolean }, + isSearched: { type: Boolean }, + getAllCollections: { attribute: false } + }; + } static styles = [chatGifsExploreStyles]; @@ -25,6 +28,8 @@ setCurrentCollection: { attribute: false }, this.downObserverElement = ''; this.viewElement = ''; this.exploreCollections = []; + this.isLoading = false; + this.isSearched = false; } elementObserver() { @@ -64,21 +69,87 @@ setCurrentCollection: { attribute: false }, this.elementObserver(); } + async searchCollections() { + this.isSearched = true; + try { + this.exploreCollections = []; + this.isLoading = true; + const response = await parentEpml.request('apiCall', { + url: `/arbitrary/resources/search?service=GIF_REPOSITORY&query=${this.searchCollectionName}&limit=0 + `, + }); + await new Promise((res) => { + setTimeout(() => { + res(); + }, 1000) + }); + this.exploreCollections = response; + } catch (error) { + console.error(error); + } finally { + this.isLoading = false; + } + } + render() { - console.log(6, "chat-gifs-explore-here"); + console.log(18, "chat-gifs-explore-here"); + console.log(this.searchCollectionName, "search collection name"); return html` -
- { - this.searchCollectionName = - e.target.value; - }} - /> -
- ${this.exploreCollections.map((collection) => { +
+
+ { + this.searchCollectionName = + e.target.value; + }} + @keyup=${async (e) => { + console.log(e.key); + if (e.key === 'Enter' && this.searchCollectionName) { + await this.searchCollections() + } + }} + /> + ${this.isSearched ? ( + html` + { + if (this.isLoading) return; + const latestCollections = await this.getAllCollections(); + this.exploreCollections = latestCollections; + this.searchCollectionName = ''; + this.isSearched = false; + }} + icon='vaadin:close-small' + slot='icon'> + + ` + ) : html` + { + if (this.isLoading || !this.searchCollectionName) return; + await this.searchCollections(); + }} + icon='vaadin:search' + slot='icon'> + + `} +
+
+ ${this.isLoading ? html` +
+

${translate('gifs.gchange18')} +

+
+
` + : this.isSearched && this.exploreCollections.length === 0 ? ( + html`

${translate('gifs.gchange19')}

` + ) : ( + html`${this.exploreCollections.map((collection) => { return html`
{ this.setCurrentCollection(collection); @@ -86,9 +157,10 @@ setCurrentCollection: { attribute: false }, ${collection.identifier}
`; - })} -
-
+ })}` + )} +
+
`; } diff --git a/qortal-ui-plugins/plugins/core/components/ChatPage.js b/qortal-ui-plugins/plugins/core/components/ChatPage.js index 61b2a1a8..25561083 100644 --- a/qortal-ui-plugins/plugins/core/components/ChatPage.js +++ b/qortal-ui-plugins/plugins/core/components/ChatPage.js @@ -1023,7 +1023,6 @@ console.log({zipFileBlob}) } render() { - console.log(23, "chat page here"); return html`
this.setGifsLoading(val)}> + .setGifsLoading=${(val) => this.setGifsLoading(val)} + .sendMessage=${(val) => this._sendMessage(val)} + .setOpenGifModal=${(val)=> this.setOpenGifModal(val)}>
{ const lastGroupedMessage = messageArray[messageArray.length - 1]; let timestamp; @@ -269,9 +270,11 @@ class MessageTemplate extends LitElement { setEditedMessageObj: { attribute: false }, sendMessage: { attribute: false }, sendMessageForward: { attribute: false }, - openDialogImage: { attribute: false }, + openDialogImage: { type: Boolean }, + openDialogGif: { type: Boolean }, openDeleteImage: { type: Boolean }, isImageLoaded: { type: Boolean }, + isGifLoaded: { type: Boolean }, isFirstMessage: { type: Boolean }, isSingleMessageInGroup: { type: Boolean }, isLastMessageInGroup: { type: Boolean }, @@ -295,8 +298,11 @@ class MessageTemplate extends LitElement { this.showBlockAddressIcon = false this.myAddress = window.parent.reduxStore.getState().app.selectedAddress.address this.imageFetches = 0 + this.gifFetches = 0 this.openDialogImage = false + this.openDialogGif = false this.isImageLoaded = false + this.isGifLoaded = false this.isFirstMessage = false this.isSingleMessageInGroup = false this.isLastMessageInGroup = false @@ -338,19 +344,23 @@ class MessageTemplate extends LitElement { let reactions = []; let repliedToData = null; let image = null; + let gif = null; let isImageDeleted = false; let version = 0; let isForwarded = false try { const parsedMessageObj = JSON.parse(this.messageObj.decodedMessage); - if(parsedMessageObj.version.toString() === '2'){ - - messageVersion2 = generateHTML(parsedMessageObj.messageText, [ - StarterKit, - Underline, - Highlight - // other extensions … - ]) + if (parsedMessageObj.version.toString() === '2' && parsedMessageObj.messageText) { + try { + messageVersion2 = generateHTML(parsedMessageObj.messageText, [ + StarterKit, + Underline, + Highlight + // other extensions … + ]) + } catch (error) { + console.error(error); + } } message = parsedMessageObj.messageText; repliedToData = this.messageObj.repliedToData; @@ -361,13 +371,20 @@ class MessageTemplate extends LitElement { if (parsedMessageObj.images && Array.isArray(parsedMessageObj.images) && parsedMessageObj.images.length > 0) { image = parsedMessageObj.images[0]; } + if (parsedMessageObj.gifs && Array.isArray(parsedMessageObj.gifs) && parsedMessageObj.gifs.length > 0) { + gif = parsedMessageObj.gifs[0]; + } } catch (error) { + console.error(error); message = this.messageObj.decodedMessage; } let avatarImg = ''; let imageHTML = ''; let imageHTMLDialog = ''; let imageUrl = ''; + let gifHTML = ''; + let gifHTMLDialog = ''; + let gifUrl = ''; let nameMenu = ''; let levelFounder = ''; let hideit = hidemsg.includes(this.messageObj.sender); @@ -411,6 +428,33 @@ class MessageTemplate extends LitElement { }; return imageHTMLRes; } + + const createGif = (gif) => { + const gifHTMLRes = new Image(); + gifHTMLRes.src = gif; + gifHTMLRes.style= "max-width:45vh; max-height:40vh; border-radius: 5px; cursor: pointer"; + gifHTMLRes.onclick= () => { + this.openDialogGif = true; + } + gifHTMLRes.onload = () => { + this.isGifLoaded = true; + } + gifHTMLRes.onerror = () => { + if (this.gifFetches < 4) { + setTimeout(() => { + this.gifFetches = this.gifFetches + 1; + gifHTMLRes.src = gif; + }, 500); + } else { + gifHTMLRes.src = '/img/chain.png'; + gifHTMLRes.style= "max-width:45vh; max-height:20vh; border-radius: 5px; filter: opacity(0.5)"; + gifHTMLRes.onclick= () => {} + this.isGifLoaded = true + } + }; + return gifHTMLRes; + } + 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; @@ -421,7 +465,17 @@ class MessageTemplate extends LitElement { imageHTMLDialog = createImage(imageUrl) imageHTMLDialog.style= "height: auto; max-height: 80vh; width: auto; max-width: 80vw; object-fit: contain; border-radius: 5px"; } - + } + + if (gif) { + const myNode = window.parent.reduxStore.getState().app.nodeConfig.knownNodes[window.parent.reduxStore.getState().app.nodeConfig.node]; + const nodeUrl = myNode.protocol + '://' + myNode.domain + ':' + myNode.port; + gifUrl = `${nodeUrl}/arbitrary/${gif.service}/${gif.name}/${gif.identifier}?filepath=${gif.filePath}&apiKey=${myNode.apiKey}`; + if (this.viewImage || this.myAddress === this.messageObj.sender){ + gifHTML = createGif(gifUrl); + gifHTMLDialog = createGif(gifUrl) + gifHTMLDialog.style= "height: auto; max-height: 80vh; width: auto; max-width: 80vw; object-fit: contain; border-radius: 5px"; + } } nameMenu = html` @@ -560,10 +614,9 @@ class MessageTemplate extends LitElement { }} class=${[`image-container`, !this.isImageLoaded ? 'defaultSize' : ''].join(' ')} style=${this.isFirstMessage && "margin-top: 10px;"}> -
- ${translate("chatpage.cchange40")} -
- +
+ ${translate("chatpage.cchange40")} +
` : html``} ${image && !isImageDeleted && (this.viewImage || this.myAddress === this.messageObj.sender) ? html` @@ -580,6 +633,25 @@ class MessageTemplate extends LitElement { ` : image && isImageDeleted ? html`

This image has been deleted

` : html``} + ${gif && !this.viewImage && this.myAddress !== this.messageObj.sender ? html` +
{ + this.viewImage = true + }} + class=${[`image-container`, !this.isImageLoaded ? 'defaultSize' : ''].join(' ')} + style=${this.isFirstMessage && "margin-top: 10px;"}> +
+ ${translate("gifs.gchange25")} +
+
+ ` : html``} + ${gif && (this.viewImage || this.myAddress === this.messageObj.sender) ? html` +
+ ${gifHTML} +
+ ` : html``}
+ { + this.openDialogGif = false + }}> +
+
+ ${gifHTMLDialog} +
+ { + + this.openDialogGif = false + }} + > + ${translate("general.close")} + +
${this.setOpenGifModal ? html` - ` @@ -527,14 +535,7 @@ class ChatTextEditor extends LitElement { } } - - - - - async firstUpdated() { - - - + async firstUpdated() { window.addEventListener('storage', () => { const checkTheme = localStorage.getItem('qortalTheme'); const chatbar = this.shadowRoot.querySelector('.element') diff --git a/qortal-ui-plugins/plugins/core/components/ImageComponent.js b/qortal-ui-plugins/plugins/core/components/ImageComponent.js index 7a3ae908..78f37603 100644 --- a/qortal-ui-plugins/plugins/core/components/ImageComponent.js +++ b/qortal-ui-plugins/plugins/core/components/ImageComponent.js @@ -7,11 +7,13 @@ export class ImageComponent extends LitElement { static get properties() { return { class: { type: String }, -url: { type: String }, +gif: { type: Object }, alt: { type: String }, attempts: { type: Number }, maxAttempts: { type: Number }, -error: { type: Boolean } +error: { type: Boolean }, +sendMessage: { attribute: false }, +setOpenGifModal: { attribute: false } } } @@ -54,14 +56,21 @@ constructor() { async _fetchImage() { this.attempts++; if (this.attempts > this.maxAttempts) return; - + await new Promise((res) => { + setTimeout(() => { + res(); + }, 1000) + }); try { - const response = await fetch(this.url); + const response = await fetch(this.gif.url); const data = await response.json(); console.log({data}); if (data.ok) { this.error = false; - this.url = data.src; + this.gif = { + ...this.gif, + url: data.src + }; this.requestUpdate(); } else if (!data.ok || data.error) { this.error = true; @@ -76,27 +85,35 @@ async _fetchImage() { } render() { -console.log(5, "Image Component here"); -console.log(this.url) if (this.error && this.attempts <= this.maxAttempts) { setTimeout(() => { this._fetchImage(); }, 1000); } return html` -${this.url && !this.error +${this.gif && !this.error ? html` ${this.alt} { + this.sendMessage({ + type: 'gif', + identifier: this.gif.identifier, + name: this.gif.name, + filePath: this.gif.filePath, + service: "GIF_REPOSITORY" + }) + this.setOpenGifModal(false); +}} @error=${this._fetchImage} />` : this.error && this.attempts <= this.maxAttempts ? html` -

${translate('chatpage.cchange94')}

+

${translate('gifs.gchange15')}

` : html` -

${translate('chatpage.cchange95')}

+

${translate('gifs.gchange16')}

` }` } From 07878c454e4c21c47a8c5a6def64a1d61e76d5e5 Mon Sep 17 00:00:00 2001 From: Justin Ferrari Date: Wed, 15 Feb 2023 11:09:37 +0200 Subject: [PATCH 13/16] Remove edit feature from gif --- .../plugins/core/components/ChatScroller.js | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/qortal-ui-plugins/plugins/core/components/ChatScroller.js b/qortal-ui-plugins/plugins/core/components/ChatScroller.js index dec6466c..633896dc 100644 --- a/qortal-ui-plugins/plugins/core/components/ChatScroller.js +++ b/qortal-ui-plugins/plugins/core/components/ChatScroller.js @@ -70,7 +70,7 @@ class ChatScroller extends LitElement { } render() { - console.log(4, "chat scroller here"); + console.log(5, "chat scroller here"); let formattedMessages = this.messages.reduce((messageArray, message, index) => { const lastGroupedMessage = messageArray[messageArray.length - 1]; let timestamp; @@ -699,6 +699,7 @@ class MessageTemplate extends LitElement { .setOpenPrivateMessage=${(val) => this.setOpenPrivateMessage(val)} .setOpenTipUser=${(val) => this.setOpenTipUser(val)} .setUserName=${(val) => this.setUserName(val)} + .gif=${!!gif} >
@@ -828,6 +829,7 @@ class ChatMenu extends LitElement { setOpenPrivateMessage: { attribute: false }, setOpenTipUser: { attribute: false }, setUserName: { attribute: false }, + gif: { type: Boolean }, } } @@ -957,13 +959,14 @@ class ChatMenu extends LitElement {
- ${this.myAddress === this.originalMessage.sender ? ( + ${((this.myAddress === this.originalMessage.sender) && ( + !this.gif)) ? ( html`
{ - if(this.version === '0'){ + if (this.version === '0'){ this.versionErrorSnack() return } From f925f8bf433ccdab64a3be8d8ec63cbc284cc204 Mon Sep 17 00:00:00 2001 From: Phillip Date: Fri, 17 Feb 2023 00:36:53 +0200 Subject: [PATCH 14/16] fix string literal bug --- qortal-ui-plugins/plugins/core/components/ChatPage.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/qortal-ui-plugins/plugins/core/components/ChatPage.js b/qortal-ui-plugins/plugins/core/components/ChatPage.js index e3d53560..edb4869e 100644 --- a/qortal-ui-plugins/plugins/core/components/ChatPage.js +++ b/qortal-ui-plugins/plugins/core/components/ChatPage.js @@ -3423,7 +3423,7 @@ console.log({zipFileBlob}) const image = this.imageFile const id = this.uid(); - const identifier = qchat_${id}; + const identifier = `qchat_${id}`; let compressedFile = ''; await new Promise(resolve => { new Compressor( image, { @@ -3520,7 +3520,7 @@ console.log({zipFileBlob}) const attachment = this.attachment; const id = this.uid(); - const identifier = qchat_${id}; + const identifier = `qchat_${id}`; const fileSize = attachment.size; if (fileSize > 1000000) { parentEpml.request('showSnackBar', get("chatpage.cchange77")); From 035c3c5e6de07b13a975f0c25eda9e2e19ad8f9b Mon Sep 17 00:00:00 2001 From: Phillip Date: Fri, 17 Feb 2023 14:03:06 +0200 Subject: [PATCH 15/16] fix apiKey error --- .../core/components/ChatGifs/ChatGifs.js | 38 +++++++----- .../components/ChatGifs/ChatGifsExplore.js | 7 ++- .../plugins/core/components/ChatPage.js | 62 ++----------------- .../plugins/core/components/ChatScroller.js | 1 - .../plugins/core/components/ChatTextEditor.js | 1 - .../plugins/core/components/ImageComponent.js | 7 ++- 6 files changed, 37 insertions(+), 79 deletions(-) diff --git a/qortal-ui-plugins/plugins/core/components/ChatGifs/ChatGifs.js b/qortal-ui-plugins/plugins/core/components/ChatGifs/ChatGifs.js index 3fa66eba..3d8f2757 100644 --- a/qortal-ui-plugins/plugins/core/components/ChatGifs/ChatGifs.js +++ b/qortal-ui-plugins/plugins/core/components/ChatGifs/ChatGifs.js @@ -2,7 +2,6 @@ import {LitElement, html, css} from 'lit'; import {render} from 'lit/html.js'; import {Epml} from '../../../../epml.js'; import * as zip from '@zip.js/zip.js'; -import {saveAs} from 'file-saver'; import '@material/mwc-icon'; import ShortUniqueId from 'short-unique-id'; import {publishData} from '../../../utils/publish-image.js'; @@ -150,9 +149,7 @@ setOpenGifModal: { attribute: false } if (changedProperties && changedProperties.has('currentCollection')) { if (this.mode === 'explore') { - console.log(this.mySubscribedCollections, "subbed collections here"); const subbedCollection = this.mySubscribedCollections.find((collection) => ((collection.name === this.currentCollection.name) && (collection.identifier === this.currentCollection.identifier))); - console.log({subbedCollection}); if (subbedCollection) { this.isSubscribed = true; } else { @@ -179,7 +176,7 @@ setOpenGifModal: { attribute: false } let collectionObj = collection; try { const metaData = await parentEpml.request('apiCall', { - url: `/arbitrary/metadata/GIF_REPOSITORY/${this.myAccountName}/${collection.identifier}`, + url: `/arbitrary/metadata/GIF_REPOSITORY/${this.myAccountName}/${collection.identifier}?apiKey=${this.getApiKey()}`, }); collectionObj = { @@ -212,13 +209,19 @@ setOpenGifModal: { attribute: false } } catch (error) {} } + getApiKey() { + const myNode = window.parent.reduxStore.getState().app.nodeConfig.knownNodes[window.parent.reduxStore.getState().app.nodeConfig.node]; + let apiKey = myNode.apiKey; + return apiKey; + } + async getMoreExploreGifs() { try { const getAllGifCollections = await parentEpml.request('apiCall', { type: 'api', url: `/arbitrary/resources?service=GIF_REPOSITORY&limit=20&offset=${ this.pageNumber * 20 - }`, + }&apiKey=${this.getApiKey()}`, }); const gifCollectionWithMetaData = await this.structureCollections( @@ -239,7 +242,7 @@ setOpenGifModal: { attribute: false } try { return await parentEpml.request('apiCall', { type: 'api', - url: `/lists/gifSubscribedRepos`, + url: `/lists/gifSubscribedRepos?apiKey=${this.getApiKey()}`, }); } catch (error) {} } @@ -254,7 +257,7 @@ setOpenGifModal: { attribute: false } await parentEpml.request('apiCall', { type: 'api', method: 'POST', - url: `/lists/gifSubscribedRepos`, + url: `/lists/gifSubscribedRepos?apiKey=${this.getApiKey()}`, body: bodyToString, headers: { 'Content-Type': 'application/json', @@ -272,7 +275,7 @@ setOpenGifModal: { attribute: false } await parentEpml.request('apiCall', { type: 'api', method: 'DELETE', - url: `/lists/gifSubscribedRepos`, + url: `/lists/gifSubscribedRepos?apiKey=${this.getApiKey()}`, body: bodyToString, headers: { 'Content-Type': 'application/json', @@ -286,7 +289,7 @@ setOpenGifModal: { attribute: false } this.myAccountName = userName; if (this.myAccountName) { const getMyGifCollections = await parentEpml.request('apiCall', { - url: `/arbitrary/resources?service=GIF_REPOSITORY&limit=0&name=${this.myAccountName}`, + url: `/arbitrary/resources?service=GIF_REPOSITORY&limit=0&name=${this.myAccountName}&apiKey=${this.getApiKey()}`, }); const gifCollectionWithMetaData = await this.structureCollections( getMyGifCollections @@ -305,7 +308,7 @@ setOpenGifModal: { attribute: false } type: 'api', url: `/arbitrary/resources?service=GIF_REPOSITORY&limit=20&offset=${ this.pageNumber * 20 - }`, + }&apiKey=${this.getApiKey()}`, }); const gifCollectionWithMetaData = await this.structureCollections( getAllGifCollections @@ -324,7 +327,7 @@ setOpenGifModal: { attribute: false } const identifier = splitCollection[1]; try { const data = await parentEpml.request('apiCall', { - url: `/arbitrary/resources?service=GIF_REPOSITORY&limit=0&name=${name}&identifier=${identifier}`, + url: `/arbitrary/resources?service=GIF_REPOSITORY&limit=0&name=${name}&identifier=${identifier}&apiKey=${this.getApiKey()}`, }); if (data.length > 0) { savedCollections.push(data[0]); @@ -346,7 +349,7 @@ setOpenGifModal: { attribute: false } try { const getNames = await parentEpml.request('apiCall', { type: 'api', - url: `/names/address/${recipient}`, + url: `/names/address/${recipient}?apiKey=${this.getApiKey()}`, }); if (Array.isArray(getNames) && getNames.length > 0) { @@ -400,7 +403,7 @@ setOpenGifModal: { attribute: false } this.isLoading = true; const userName = await this.getName(this.selectedAddress.address); const doesNameExist = await parentEpml.request('apiCall', { - url: `/arbitrary/resources?service=GIF_REPOSITORY&limit=0&name=${userName}&identifier=${this.newCollectionName}`, + url: `/arbitrary/resources?service=GIF_REPOSITORY&limit=0&name=${userName}&identifier=${this.newCollectionName}&apiKey=${this.getApiKey()}`, }); if (!userName) { @@ -465,6 +468,7 @@ setOpenGifModal: { attribute: false } const blobTobase = await blobToBase64(zipFileBlob); + await publishData({ registeredName: userName, file: blobTobase.split(',')[1], @@ -488,7 +492,7 @@ setOpenGifModal: { attribute: false } let myCollection = await parentEpml.request( 'apiCall', { - url: `/arbitrary/resources?service=GIF_REPOSITORY&limit=0&name=${userName}&identifier=${this.newCollectionName}`, + url: `/arbitrary/resources?service=GIF_REPOSITORY&limit=0&name=${userName}&identifier=${this.newCollectionName}&apiKey=${this.getApiKey()}`, } ); if (myCollection.length > 0) { @@ -509,7 +513,7 @@ setOpenGifModal: { attribute: false } }; interval = setInterval(getAnswer, 5000); }); - saveAs(zipFileBlob, 'zipfile'); + this.isLoading = false; this.setGifsLoading(false); this.mode = 'myCollection'; @@ -519,6 +523,8 @@ setOpenGifModal: { attribute: false } } catch (error) { console.log(error); parentEpml.request('showSnackBar', get('gifs.gchange12')); + this.setGifsLoading(false); + this.isLoading = false; } } @@ -552,8 +558,6 @@ setOpenGifModal: { attribute: false } } render() { - console.log(8, "chat gifs here"); - console.log('this.currentCollection', this.currentCollection); return html`
diff --git a/qortal-ui-plugins/plugins/core/components/ChatGifs/ChatGifsExplore.js b/qortal-ui-plugins/plugins/core/components/ChatGifs/ChatGifsExplore.js index 9db3f797..df825b74 100644 --- a/qortal-ui-plugins/plugins/core/components/ChatGifs/ChatGifsExplore.js +++ b/qortal-ui-plugins/plugins/core/components/ChatGifs/ChatGifsExplore.js @@ -68,6 +68,11 @@ class ChatGifsExplore extends LitElement { this.shadowRoot.getElementById('downObserver'); this.elementObserver(); } + getApiKey() { + const myNode = window.parent.reduxStore.getState().app.nodeConfig.knownNodes[window.parent.reduxStore.getState().app.nodeConfig.node]; + let apiKey = myNode.apiKey; + return apiKey; + } async searchCollections() { this.isSearched = true; @@ -75,7 +80,7 @@ class ChatGifsExplore extends LitElement { this.exploreCollections = []; this.isLoading = true; const response = await parentEpml.request('apiCall', { - url: `/arbitrary/resources/search?service=GIF_REPOSITORY&query=${this.searchCollectionName}&limit=0 + url: `/arbitrary/resources/search?service=GIF_REPOSITORY&query=${this.searchCollectionName}&limit=0&apiKey=${this.getApiKey()} `, }); await new Promise((res) => { diff --git a/qortal-ui-plugins/plugins/core/components/ChatPage.js b/qortal-ui-plugins/plugins/core/components/ChatPage.js index edb4869e..050f89da 100644 --- a/qortal-ui-plugins/plugins/core/components/ChatPage.js +++ b/qortal-ui-plugins/plugins/core/components/ChatPage.js @@ -1386,68 +1386,14 @@ class ChatPage extends LitElement { } addGifs(gifs){ - console.log('gifs', gifs) this.gifsToBeAdded = [...this.gifsToBeAdded, ...gifs] - console.log('this.gifsToBeAdded', this.gifsToBeAdded) } setGifsLoading(props) { this.gifsLoading = props; } - async uploadGifCollection(){ - try { - function blobToBase64(blob) { - return new Promise((resolve, _) => { - const reader = new FileReader(); - reader.onloadend = () => resolve(reader.result); - reader.readAsDataURL(blob); - }); - } - const zipFileWriter = new zip.BlobWriter("application/zip"); -// Creates a TextReader object storing the text of the entry to add in the zip -// (i.e. "Hello world!"). -const helloWorldReader = new zip.TextReader("Hello world!"); - -// Creates a ZipWriter object writing data via `zipFileWriter`, adds the entry -// "hello.txt" containing the text "Hello world!" via `helloWorldReader`, and -// closes the writer. -const file = this.gifsToBeAdded[0] -const file2 = this.gifsToBeAdded[1] -const zipWriter = new zip.ZipWriter(zipFileWriter, { bufferedWrite: true }); -await zipWriter.add(file.name, new zip.BlobReader(file)); -await zipWriter.add(file2.name, new zip.BlobReader(file2)); - -await zipWriter.close(); -const zipFileBlob = await zipFileWriter.getData() -const blobTobase = await blobToBase64(zipFileBlob) -console.log({blobTobase}) -const userName = await this.getName(this.selectedAddress.address); - if (!userName) { - parentEpml.request('showSnackBar', get("chatpage.cchange27")); - this.isLoading = false; - return; - } - const id = this.uid(); - const identifier = `gif_${id}`; -await publishData({ - registeredName: userName, - file : blobTobase.split(',')[1], - service: 'GIF_REPOSITORY', - identifier: identifier, - parentEpml, - metaData: undefined, - uploadType: 'zip', - selectedAddress: this.selectedAddress, - worker: this.webWorkerImage, - isBase64: true - }) -saveAs(zipFileBlob, 'zipfile'); -console.log({zipFileBlob}) - } catch (error) { - console.log(error) - } - } + render() { return html` @@ -1502,7 +1448,7 @@ console.log({zipFileBlob}) this.setGifsLoading(val)} .sendMessage=${(val) => this._sendMessage(val)} .setOpenGifModal=${(val)=> this.setOpenGifModal(val)}> @@ -2069,8 +2015,8 @@ console.log({zipFileBlob}) if(this.webWorker){ this.webWorker.terminate(); } - if(this.webWorkerImage){ - this.webWorkerImage.terminate(); + if(this.webWorkerFile){ + this.webWorkerFile.terminate(); } if(this.editor){ this.editor.destroy() diff --git a/qortal-ui-plugins/plugins/core/components/ChatScroller.js b/qortal-ui-plugins/plugins/core/components/ChatScroller.js index 64a4d0ee..a6ba4105 100644 --- a/qortal-ui-plugins/plugins/core/components/ChatScroller.js +++ b/qortal-ui-plugins/plugins/core/components/ChatScroller.js @@ -80,7 +80,6 @@ class ChatScroller extends LitElement { } render() { - console.log(5, "chat scroller here"); let formattedMessages = this.messages.reduce((messageArray, message, index) => { const lastGroupedMessage = messageArray[messageArray.length - 1]; let timestamp; diff --git a/qortal-ui-plugins/plugins/core/components/ChatTextEditor.js b/qortal-ui-plugins/plugins/core/components/ChatTextEditor.js index 9ed0fe25..bfb929d0 100644 --- a/qortal-ui-plugins/plugins/core/components/ChatTextEditor.js +++ b/qortal-ui-plugins/plugins/core/components/ChatTextEditor.js @@ -455,7 +455,6 @@ mwc-checkbox::shadow .mdc-checkbox::after, mwc-checkbox::shadow .mdc-checkbox::b >${translate('chatpage.cchange69')} { - console.log(e.target.checked) if(e.target.checked){ window.parent.reduxStore.dispatch( window.parent.reduxAction.removeAutoLoadImageChat(this.chatId)) return diff --git a/qortal-ui-plugins/plugins/core/components/ImageComponent.js b/qortal-ui-plugins/plugins/core/components/ImageComponent.js index 78f37603..b113d75a 100644 --- a/qortal-ui-plugins/plugins/core/components/ImageComponent.js +++ b/qortal-ui-plugins/plugins/core/components/ImageComponent.js @@ -52,6 +52,11 @@ constructor() { this.attempts = 0; this.maxAttempts = 5; } +getApiKey() { + const myNode = window.parent.reduxStore.getState().app.nodeConfig.knownNodes[window.parent.reduxStore.getState().app.nodeConfig.node]; + let apiKey = myNode.apiKey; + return apiKey; +} async _fetchImage() { this.attempts++; @@ -62,7 +67,7 @@ async _fetchImage() { }, 1000) }); try { - const response = await fetch(this.gif.url); + const response = await fetch(this.gif.url + `&apiKey=${this.getApiKey()}`); const data = await response.json(); console.log({data}); if (data.ok) { From e64ed9be4de06d825c6d8620632579ca1e3c3045 Mon Sep 17 00:00:00 2001 From: Phillip Date: Fri, 17 Feb 2023 15:31:40 +0200 Subject: [PATCH 16/16] fix fetching errors --- qortal-ui-plugins/plugins/core/components/ChatGifs/ChatGifs.js | 3 ++- qortal-ui-plugins/plugins/core/components/ImageComponent.js | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/qortal-ui-plugins/plugins/core/components/ChatGifs/ChatGifs.js b/qortal-ui-plugins/plugins/core/components/ChatGifs/ChatGifs.js index 3d8f2757..7b02b003 100644 --- a/qortal-ui-plugins/plugins/core/components/ChatGifs/ChatGifs.js +++ b/qortal-ui-plugins/plugins/core/components/ChatGifs/ChatGifs.js @@ -186,7 +186,7 @@ setOpenGifModal: { attribute: false } if (metaData.files) { const metaDataArray = metaData.files.map((data) => { return { - url: `${nodeUrl}/arbitrary/GIF_REPOSITORY/${this.myAccountName}/${collection.identifier}?filepath=${data}`, + url: `${nodeUrl}/arbitrary/GIF_REPOSITORY/${this.myAccountName}/${collection.identifier}?filepath=${data}&apiKey=${this.getApiKey()}`, filePath: data, identifier: collection.identifier, name: this.myAccountName @@ -612,6 +612,7 @@ setOpenGifModal: { attribute: false } @click=${() => { if (this.isLoading) return; this.mode = 'explore'; + this.currentCollection = null; }} icon="vaadin:search" slot="icon"> diff --git a/qortal-ui-plugins/plugins/core/components/ImageComponent.js b/qortal-ui-plugins/plugins/core/components/ImageComponent.js index b113d75a..af03aadc 100644 --- a/qortal-ui-plugins/plugins/core/components/ImageComponent.js +++ b/qortal-ui-plugins/plugins/core/components/ImageComponent.js @@ -67,7 +67,7 @@ async _fetchImage() { }, 1000) }); try { - const response = await fetch(this.gif.url + `&apiKey=${this.getApiKey()}`); + const response = await fetch(this.gif.url); const data = await response.json(); console.log({data}); if (data.ok) {