diff --git a/qortal-ui-core/font/MavenPro.ttf b/qortal-ui-core/font/MavenPro.ttf new file mode 100644 index 00000000..ab146a80 Binary files /dev/null and b/qortal-ui-core/font/MavenPro.ttf differ diff --git a/qortal-ui-core/font/PaytoneOne.ttf b/qortal-ui-core/font/PaytoneOne.ttf new file mode 100644 index 00000000..5cb0bbb8 Binary files /dev/null and b/qortal-ui-core/font/PaytoneOne.ttf differ diff --git a/qortal-ui-core/font/material-icons.css b/qortal-ui-core/font/material-icons.css index 881a68be..6340d60f 100644 --- a/qortal-ui-core/font/material-icons.css +++ b/qortal-ui-core/font/material-icons.css @@ -26,6 +26,12 @@ url(Montserrat.ttf) format('truetype'); } +@font-face { + font-family: 'MavenPro'; + src: local('MavenPro'), + local('MavenPro'), + url(Montserrat.ttf) format('truetype'); +} @font-face { font-family: 'WorkSans'; src: local('WorkSans'), @@ -54,12 +60,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/font/switch-theme.css b/qortal-ui-core/font/switch-theme.css index c8b139c3..13f38f37 100644 --- a/qortal-ui-core/font/switch-theme.css +++ b/qortal-ui-core/font/switch-theme.css @@ -53,6 +53,13 @@ html { --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; + --gif-collection-hover-bg: #eaeaefa3; } html[theme="dark"] { @@ -110,4 +117,11 @@ html[theme="dark"] { --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; + --gif-collection-hover-bg: #ffffff26; } \ No newline at end of file diff --git a/qortal-ui-core/language/us.json b/qortal-ui-core/language/us.json index 008bee19..088c7d54 100644 --- a/qortal-ui-core/language/us.json +++ b/qortal-ui-core/language/us.json @@ -165,6 +165,34 @@ "balances": "YOUR WALLET BALANCES", "update": "UPDATE WALLET BALANCES" }, + "gifs": { + "gchange1": "Gif Explorer", + "gchange2": "Explore Collections", + "gchange3": "My Collections", + "gchange4": "Subscribed Collections", + "gchange5": "Upload your gif files", + "gchange6": "File should be .Gif", + "gchange7": "Upload Collection", + "gchange8": "A collection name is required!", + "gchange9": "Collection Name", + "gchange10": "Gif Collection Uploaded Successfully!", + "gchange11": "Gifs uploading, please wait...", + "gchange12": "Something went wrong! Please try changing tabs and coming back.", + "gchange13": "You currently have no collections.", + "gchange14": "You currently have no subscribed collections.", + "gchange15": "Error fetching GIF. Retrying...", + "gchange16": "Failed to fetch GIF! Please visit another collection and try again!", + "gchange17": "Subscribe to this collection", + "gchange18": "Searching for collections...", + "gchange19": "No collections found!", + "gchange20": "Subscribed to collection successfully!", + "gchange21": "Unsubscribed to collection successfully!", + "gchange22": "Unsubscribe from this collection", + "gchange23": "Your gif collection cannot contain two gifs with the same name!", + "gchange24": "This collection name is already taken. Try another name!", + "gchange25": "GIF (click to view)", + "gchange26": "A name is needed to access and send GIF files" + }, "startminting": { "smchange1": "Cannot fetch minting accounts", "smchange2": "Failed to remove key", diff --git a/qortal-ui-core/src/styles/switch-theme.css b/qortal-ui-core/src/styles/switch-theme.css index 130e8b8d..2a700a48 100644 --- a/qortal-ui-core/src/styles/switch-theme.css +++ b/qortal-ui-core/src/styles/switch-theme.css @@ -50,6 +50,13 @@ html { --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; + --gif-collection-hover-bg: #eaeaefa3; } html[theme="dark"] { @@ -104,4 +111,11 @@ html[theme="dark"] { --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; + --gif-collection-hover-bg: #ffffff26; } \ No newline at end of file diff --git a/qortal-ui-plugins/package.json b/qortal-ui-plugins/package.json index 759a24e6..6121f786 100644 --- a/qortal-ui-plugins/package.json +++ b/qortal-ui-plugins/package.json @@ -77,6 +77,7 @@ "@vaadin/icons": "23.3.7", "@vaadin/tooltip": "23.3.7", "axios": "1.3.3", + "@zip.js/zip.js": "^2.6.62", "epml": "0.3.3", "file-saver": "2.0.5", "highcharts": "10.3.3", @@ -93,4 +94,4 @@ "engines": { "node": ">=18.12.1" } -} \ 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 new file mode 100644 index 00000000..7f68de4f --- /dev/null +++ b/qortal-ui-plugins/plugins/core/components/ChatGifs/ChatGifs-css.js @@ -0,0 +1,515 @@ +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; +max-height: calc(95vh - 90px); +min-width: 370px; +max-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; +} + +.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; +} + +.explore-collections-icon { +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: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; +transition: all 0.3s ease-in-out; +cursor: auto; +} + +.collection-wrapper { +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 { +display: flex; +flex-direction: column; +justify-content: center; +height: 100%; +} + +.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; +} + +.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 { +display: flex; +flex-direction: column; +justify-content: flex-end; +flex: 1 1 0%; +margin-top: 10px; +overflow-y: auto; +max-height: 300px; +} + +.gifs-added-row { +display: flex; +flex-direction: column; +gap: 5px; +overflow-y: auto; +} + +.gifs-added-row .gif-input:last-child { +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; +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-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; + 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: var(--gif-button-row-bg); + border-radius: 3px; + 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: 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 { + 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: all 0.3s ease-in-out; + box-shadow: none; + padding: 10px; + cursor: pointer; +} + +.collection-card:hover { + border: none; + border-radius: 5px; + background-color: var(--gif-collection-hover-bg); +} + +.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.4s 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; +} + +.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; + font-weight: 600; + color: var(--chat-bubble-msg-color); + margin: 0 0 10px 0; + text-align: center; + user-select: none; +} + +.subscribe-button { + position: absolute; + bottom: 3px; + left: 50%; + transform: translateX(-50%); + font-family: Raleway, sans-serif; + font-weight: 500; + font-size: 14px; + background-color: var(--mdc-theme-primary); + border: none; + border-radius: 8px; + outline: none; + padding: 5px 10px; + transition: all 0.3s cubic-bezier(0.5, 1, 0.89, 1); +} + +.subscribe-button:hover { + cursor: pointer; + box-shadow: 0px 3px 4px 0px hsla(0,0%,0%,0.14), 0px 3px 3px -2px hsla(0,0%,0%,0.12), 0px 1px 8px 0px hsla(0,0%,0%,0.2); +} + +.unsubscribe-button { + position: absolute; + width: max-content; + bottom: 3px; + left: 50%; + transform: translateX(-50%); + font-family: Raleway, sans-serif; + font-weight: 500; + font-size: 14px; + background-color: #f44336; + border: none; + border-radius: 8px; + outline: none; + padding: 5px 10px; + transition: all 0.3s cubic-bezier(0.5, 1, 0.89, 1); +} + +.unsubscribe-button:hover { + cursor: pointer; + box-shadow: 0px 3px 4px 0px hsla(0,0%,0%,0.14), 0px 3px 3px -2px hsla(0,0%,0%,0.12), 0px 1px 8px 0px hsla(0,0%,0%,0.2); +} + +`; 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..7b02b003 --- /dev/null +++ b/qortal-ui-plugins/plugins/core/components/ChatGifs/ChatGifs.js @@ -0,0 +1,951 @@ +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 '@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 '../ImageComponent.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}, +editor: {type: Object}, +isSubscribed: { type: Boolean }, +setGifsLoading: { attribute: false }, +sendMessage: { attribute: false }, +setOpenGifModal: { attribute: false } +}; +} + + 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 = []; + this.mode = 'myCollection'; + this.currentCollection = null; + this.pageNumber = 0; + this.isLoading = false; + this.isSubscribed = false; + this.newCollectionName = ''; + this.getAllCollections = this.getAllCollections.bind(this); + } + + 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;'; + + try { + this.isLoading = true; + const myCollections = await this.getMyGifCollections(); + const savedCollections = await this.getSavedCollections(); + const allCollections = await this.getAllCollections(); + + if (!Array.isArray(myCollections) && !Array.isArray(savedCollections)) { + parentEpml.request('showSnackBar', get('gifs.gchange12')); + return; + } + + await new Promise((res) => { + setTimeout(() => { + res(); + }, 1000) + }); + this.myGifCollections = myCollections; + this.mySubscribedCollections = savedCollections; + this.exploreCollections = allCollections; + } catch (error) { + console.error(error); + } finally { + this.isLoading = false; + } + } + + async updated(changedProperties) { + if (changedProperties && changedProperties.has('mode')) { + const mode = this.mode; + if (mode === 'myCollection') { + try { + this.myGifCollections = []; + this.isLoading = true; + const collections = await this.getMyGifCollections(); + await new Promise((res) => { + setTimeout(() => { + res(); + }, 1000) + }); + this.myGifCollections = collections; + } catch (error) { + console.error(error); + } finally { + this.isLoading = false; + } + } + + if (mode === 'explore') { + try { + this.exploreCollections = []; + this.isLoading = true; + const allCollections = await this.getAllCollections(); + await new Promise((res) => { + setTimeout(() => { + res(); + }, 1000) + }); + this.exploreCollections = allCollections; + } catch (error) { + console.error(error); + } finally { + this.isLoading = false; + } + } + if (mode === 'subscribedCollection') { + try { + this.mySubscribedCollections = []; + this.isLoading = true; + const savedCollections = await this.getSavedCollections(); + await new Promise((res) => { + setTimeout(() => { + res(); + }, 1000) + }); + this.mySubscribedCollections = savedCollections; + } catch (error) { + console.error(error); + } finally { + this.isLoading = false; + } + } + } + + if (changedProperties && changedProperties.has('currentCollection')) { + if (this.mode === 'explore') { + const subbedCollection = this.mySubscribedCollections.find((collection) => ((collection.name === this.currentCollection.name) && (collection.identifier === this.currentCollection.identifier))); + if (subbedCollection) { + this.isSubscribed = true; + } else { + this.isSubscribed = false; + } + } + } + } + + async structureCollections(gifCollections) { + const userName = await this.getName(this.selectedAddress.address); + if (!userName) { + return; + } + 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}?apiKey=${this.getApiKey()}`, + }); + + collectionObj = { + ...collection, + gifUrls: [], + }; + if (metaData.files) { + const metaDataArray = metaData.files.map((data) => { + return { + url: `${nodeUrl}/arbitrary/GIF_REPOSITORY/${this.myAccountName}/${collection.identifier}?filepath=${data}&apiKey=${this.getApiKey()}`, + filePath: data, + identifier: collection.identifier, + name: this.myAccountName + }; + }); + + collectionObj = { + ...collection, + gifUrls: metaDataArray, + }; + } + } catch (error) { + console.log(error); + } + + return collectionObj; + } + ); + return await Promise.all(getMetaDataGifs); + } 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( + 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?apiKey=${this.getApiKey()}`, + }); + } 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?apiKey=${this.getApiKey()}`, + 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?apiKey=${this.getApiKey()}`, + 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 getMyGifCollections = await parentEpml.request('apiCall', { + url: `/arbitrary/resources?service=GIF_REPOSITORY&limit=0&name=${this.myAccountName}&apiKey=${this.getApiKey()}`, + }); + const gifCollectionWithMetaData = await this.structureCollections( + getMyGifCollections + ); + + return gifCollectionWithMetaData; + } else { + return []; + } + } + + 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 + }&apiKey=${this.getApiKey()}`, + }); + const gifCollectionWithMetaData = await this.structureCollections( + getAllGifCollections + ); + this.pageNumber = this.pageNumber + 1; + return gifCollectionWithMetaData; + } + + 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 { + const data = await parentEpml.request('apiCall', { + url: `/arbitrary/resources?service=GIF_REPOSITORY&limit=0&name=${name}&identifier=${identifier}&apiKey=${this.getApiKey()}`, + }); + 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 + ); + return savedCollectionsWithMetaData; + } + + async getName(recipient) { + try { + const getNames = await parentEpml.request('apiCall', { + type: 'api', + url: `/names/address/${recipient}?apiKey=${this.getApiKey()}`, + }); + + if (Array.isArray(getNames) && getNames.length > 0) { + return getNames[0].name; + } else { + return ''; + } + } catch (error) { + return ''; + } + } + + removeDotGIF(arr) { + return arr.map(obj => { + const newObj = { ...obj }; + if (newObj.hasOwnProperty('name') && newObj.name.endsWith('.gif')) { + newObj.name = newObj.name.slice(0, -4); + } + return newObj; + }); + } + + addDotGIF(arr) { + return arr.map(obj => { + const newObj = { ...obj }; + if (newObj.hasOwnProperty('name') && !newObj.name.endsWith('.gif')) { + newObj.name += '.gif'; + } + return newObj; + }); + } + + addGifs(gifs) { + const mapGifs = gifs.map((file) => { + return { + file, + name: file.name, + }; + }); + const removedExtensions = this.removeDotGIF(mapGifs); + this.gifsToBeAdded = [...this.gifsToBeAdded, ...removedExtensions]; + } + + async uploadGifCollection() { + if (!this.newCollectionName) { + parentEpml.request('showSnackBar', get('gifs.gchange8')); + 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}&apiKey=${this.getApiKey()}`, + }); + + if (!userName) { + parentEpml.request('showSnackBar', get('chatpage.cchange27')); + this.setGifsLoading(false); + this.isLoading = false; + return; + } + + if (doesNameExist.length !== 0) { + parentEpml.request('showSnackBar', get('gifs.gchange24')); + this.isLoading = false; + this.setGifsLoading(false); + return; + } + + function validateDuplicateGifNames(arr) { + let names = []; + for (let i = 0; i < arr.length; i++) { + if (names.includes(arr[i].name)) { + return false; + } + names.push(arr[i].name); + } + return true; + } + + let result = validateDuplicateGifNames(this.gifsToBeAdded); + + if (!result) { + parentEpml.request('showSnackBar', get('gifs.gchange23')); + this.isLoading = false; + this.setGifsLoading(false); + return; + } + + const addedGifExtensionsArr = this.addDotGIF(this.gifsToBeAdded); + + 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'); + + const zipWriter = new zip.ZipWriter(zipFileWriter, { + bufferedWrite: true, + }); + + for (let i = 0; i < addedGifExtensionsArr.length; i++) { + await zipWriter.add( + addedGifExtensionsArr[i].name, + new zip.BlobReader(addedGifExtensionsArr[i].file) + ); + } + + await zipWriter.close(); + + const zipFileBlob = await zipFileWriter.getData(); + + const blobTobase = await blobToBase64(zipFileBlob); + + + await publishData({ + registeredName: userName, + file: blobTobase.split(',')[1], + service: 'GIF_REPOSITORY', + identifier: this.newCollectionName, + parentEpml, + metaData: `title=${this.newCollectionName}`, + 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}&apiKey=${this.getApiKey()}`, + } + ); + if (myCollection.length > 0) { + clearInterval(interval); + res(); + } + } catch (error) { + console.error(error); + this.isLoading = false; + this.setGifsLoading(false); + this.mode = 'myCollection'; + this.gifsToBeAdded = []; + this.newCollectionName = ''; + parentEpml.request('showSnackBar', get('gifs.gchange12')); + } + stop = false; + } + }; + interval = setInterval(getAnswer, 5000); + }); + + this.isLoading = false; + this.setGifsLoading(false); + this.mode = 'myCollection'; + this.gifsToBeAdded = []; + this.newCollectionName = ''; + parentEpml.request('showSnackBar', get('gifs.gchange10')); + } catch (error) { + console.log(error); + parentEpml.request('showSnackBar', get('gifs.gchange12')); + this.setGifsLoading(false); + this.isLoading = false; + } + } + + setCurrentCollection(val) { + this.currentCollection = val; + } + + clearGifSelections() { + this.mode = 'myCollection'; + this.gifsToBeAdded = []; + } + + async subscribeToCollection() { + await this.addCollectionToList( + `${this.currentCollection.name}/${this.currentCollection.identifier}` + ); + parentEpml.request('showSnackBar', get('gifs.gchange20')); + this.isSubscribed = true; + const savedCollections = await this.getSavedCollections(); + this.mySubscribedCollections = savedCollections; + } + + async unsubscribeToCollection() { + await this.removeCollectionFromList( + `${this.currentCollection.name}/${this.currentCollection.identifier}` + ); + parentEpml.request('showSnackBar', get('gifs.gchange21')); + this.isSubscribed = false; + const savedCollections = await this.getSavedCollections(); + this.mySubscribedCollections = savedCollections; + } + + render() { + return html` +
+
+ { + if (this.isLoading) return; + this.mode = 'newCollection'; + }} + icon="vaadin:plus" + slot="icon"> + +
+
{ + if (this.mode === 'explore' && !this.currentCollection) { + this.mode = 'myCollection'; + this.currentCollection = null; + } else if (this.mode === 'explore' && this.currentCollection) { + this.mode = 'explore'; + this.currentCollection = null; + this.isSubscribed = false; + } else { + this.currentCollection = null; + } + }} + > + +
+

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

+ { + if (this.isLoading) return; + this.mode = 'explore'; + this.currentCollection = null; + }} + icon="vaadin:search" + slot="icon"> + + + +
+
+
+
{ + if (this.isLoading) return; + if (this.mode === 'myCollection') return; + this.mode = 'myCollection'; + this.currentCollection = null; + }}> + ${translate('gifs.gchange3')} +
+
{ + if (this.isLoading) return; + if (this.mode === 'subscribedCollection') return; + this.mode = 'subscribedCollection'; + this.currentCollection = null; + }} + > + ${translate('gifs.gchange4')} +
+
+
+
+ ${this.mode === 'myCollection' && !this.currentCollection + ? html` + ${this.isLoading === true + ? html`
` + : ''} + ${(this.myGifCollections.length === 0 && !this.isLoading) ? ( + html` +
${translate('gifs.gchange13')}
+ ` + ) : ( + html` + ${(this.myGifCollections || []).map((collection) => { + return html` +
{ + this.currentCollection = + collection; + }} class='collection-card'> + ${collection.identifier} +
+ `; + })} + ` + )} + ` + : '' + } + ${this.mode === 'subscribedCollection' && + !this.currentCollection + ? html` + ${this.isLoading === true + ? html`
` + : ''} + ${(this.mySubscribedCollections.length === 0 && !this.isLoading) ? ( + html` +
${translate('gifs.gchange14')}
+ ` + ) : ( + html` + ${this.mySubscribedCollections.map( + (collection) => { + return html` +
{ + this.currentCollection = + collection; + }} class='collection-card'> + ${collection.identifier} +
+ `; + } + )} + ` + )} + ` + : '' + } + ${this.mode === 'explore' && !this.currentCollection + ? html` + ${this.isLoading === true + ? html` +
+ ` + : html` + + this.getAllCollections(val)} + .getMoreExploreGifs=${(val) => + this.getMoreExploreGifs(val)} + .exploreCollections=${this + .exploreCollections} + .setCurrentCollection=${(val) => + this.setCurrentCollection(val)} + > + ` + } + ` + : '' + } + ${this.currentCollection && this.mode === 'myCollection' + ? html` +
+ ${this.currentCollection.gifUrls.map((gif) => { + return html` + this.sendMessage(val)} + .setOpenGifModal=${(val) => this.setOpenGifModal(val)} + .class=${'gif-image'} + .gif=${gif} + .alt=${'gif-image'}> + + `; + })} +
+ ` + : '' + } + ${this.currentCollection && + this.mode === 'subscribedCollection' + ? html` +
+ ${this.currentCollection.gifUrls.map((gif) => { + return html` + this.sendMessage(val)} + .setOpenGifModal=${(val) => this.setOpenGifModal(val)} + .class=${'gif-image'} + .gif=${gif} + .alt=${'gif-image'}> + + `; + })} +
+ ` + : '' + } + ${this.currentCollection && this.mode === 'explore' + ? html` +
+ ${this.currentCollection.gifUrls.map((gif) => { + return html` + this.sendMessage(val)} + .setOpenGifModal=${(val) => this.setOpenGifModal(val)} + .class=${'gif-image'} + .gif=${gif} + .alt=${'gif-image'}> + + `; + })} +
+ ${this.isSubscribed ? ( + html` + + ` + ) : ( + html` + + ` + )} + ` + : '' + } + ${this.mode === 'newCollection' && this.isLoading === false + ? html` +
+
+

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

+

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

+
+
+ 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) => { + return html` +
+ + { + this.gifsToBeAdded[i] = { + ...gif, + name: e.target + .value, + }; + }} + /> +
+ `; + })} +
+
+ + +
+
+
+ ` + : this.mode === 'newCollection' && this.isLoading === true ? ( + html` +
+

${translate("gifs.gchange11")}

+
+
+ ` + ) + : '' + } +
+
+
+ + `; + } + +} + +window.customElements.define('chat-gifs', ChatGifs); diff --git a/qortal-ui-plugins/plugins/core/components/ChatGifs/ChatGifsExplore-css.js b/qortal-ui-plugins/plugins/core/components/ChatGifs/ChatGifsExplore-css.js new file mode 100644 index 00000000..2ce632ba --- /dev/null +++ b/qortal-ui-plugins/plugins/core/components/ChatGifs/ChatGifsExplore-css.js @@ -0,0 +1,137 @@ +import { css } from 'lit'; + +export const chatGifsExploreStyles = css` +.container-body { +display: flex; +flex-direction: column; +align-items: center; +max-width: 100%; +height: 100%; +} + +.collection-wrapper { +display: flex; +flex-direction: column; +width: 100%; +height: 100%; +overflow: hidden; +} + +.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: all 0.3s ease-in-out; +box-shadow: none; +padding: 10px; +cursor: pointer; +} + +.collection-card:hover { +border: none; +border-radius: 5px; +background-color: var(--gif-collection-hover-bg); +} + +.search-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); +width: 90%; +margin: 10px 0; +outline: none; +} + +.search-collection-name::placeholder { +font-size: 16px; +font-family: Montserrat, sans-serif; +font-weight: 600; +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 new file mode 100644 index 00000000..df825b74 --- /dev/null +++ b/qortal-ui-plugins/plugins/core/components/ChatGifs/ChatGifsExplore.js @@ -0,0 +1,175 @@ +import { LitElement, html, css } from 'lit'; +import { Epml } from '../../../../epml.js'; +import { chatGifsExploreStyles } from './ChatGifsExplore-css.js'; +import { translate, get } from 'lit-translate'; +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 }, + isLoading: { type: Boolean }, + isSearched: { type: Boolean }, + getAllCollections: { attribute: false } + }; + } + + static styles = [chatGifsExploreStyles]; + + constructor() { + super(); + this.searchCollectionName = ''; + this.downObserverElement = ''; + this.viewElement = ''; + this.exploreCollections = []; + this.isLoading = false; + this.isSearched = false; + } + + 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 firstUpdated() { + this.viewElement = this.shadowRoot.getElementById('viewElement'); + this.downObserverElement = + 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; + try { + this.exploreCollections = []; + this.isLoading = true; + const response = await parentEpml.request('apiCall', { + url: `/arbitrary/resources/search?service=GIF_REPOSITORY&query=${this.searchCollectionName}&limit=0&apiKey=${this.getApiKey()} + `, + }); + await new Promise((res) => { + setTimeout(() => { + res(); + }, 1000) + }); + this.exploreCollections = response; + } catch (error) { + console.error(error); + } finally { + this.isLoading = false; + } + } + + render() { + console.log(18, "chat-gifs-explore-here"); + console.log(this.searchCollectionName, "search collection name"); + return html` +
+
+ { + 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); + }}> + ${collection.identifier} +
+ `; + })}` + )} +
+
+
+ `; + } + +} + +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 32f5840c..d6311b28 100644 --- a/qortal-ui-plugins/plugins/core/components/ChatPage.js +++ b/qortal-ui-plugins/plugins/core/components/ChatPage.js @@ -9,6 +9,9 @@ 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 './ChatGifs/ChatGifs.js'; import localForage from "localforage"; registerTranslateConfig({ @@ -107,6 +110,8 @@ class ChatPage extends LitElement { openUserInfo: { type: Boolean }, selectedHead: { type: Object }, userName: { type: String }, + openGifModal: { type: Boolean }, + gifsLoading: { type: Boolean }, goToRepliedMessage: {attribute: false}, isLoadingGoToRepliedMessage: {type: Object} } @@ -507,183 +512,730 @@ class ChatPage extends LitElement { border-radius: 15px; line-height: 1.6; overflow-y: auto; + overflow-x: hidden; + width: 100%; } - .buttons { - text-align:right; + + .repliedTo-container { + display: flex; + flex-direction: row; + justify-content: space-between; + align-items: center; + padding: 10px 10px 8px 10px; } - - .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); + + .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%; } - 100% { - -webkit-transform: rotate(360deg); - transform: rotate(360deg); + + .chat-text-area { + display: flex; + position: relative; + justify-content: center; + min-height: 60px; + max-height: 100%; } - } - - @keyframes loadingAnimation { - 0% { - -webkit-transform: rotate(0deg); - transform: rotate(0deg); + + .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); } - 100% { - -webkit-transform: rotate(360deg); - transform: rotate(360deg); + + .chat-text-area .typing-area textarea { + display: none; } - } - - /* 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; + + .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; - 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; + 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 { + 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; - color: var(--black) - } - - .group-name { - font-family: Raleway, sans-serif; + 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; - color: var(--black); - margin:0px; - padding:0px; - } + } + + .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; + } + + .chat-gifs { + position: absolute; + right: 15px; + bottom: 100px; + justify-self: flex-end; + width: fit-content; + height: auto; + transform: translateY(30%); + animation: smooth-appear 0.5s ease forwards; + z-index: 5; + } + + @keyframes smooth-appear { + to { + transform: translateY(0); + } + } + + .gifs-backdrop { + top: 0; + height: 100vh; + width: 100vw; + background: transparent; + position: fixed; + } .modal-button-row { display: flex; @@ -692,141 +1244,6 @@ class ChatPage extends LitElement { 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; - } .attachment-icon-container { display: flex; @@ -866,6 +1283,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 = '' @@ -920,7 +1338,9 @@ class ChatPage extends LitElement { this.webWorkerFile = null; this.currentEditor = '_chatEditorDOM' this.initialChat = this.initialChat.bind(this) + this.setOpenGifModal = this.setOpenGifModal.bind(this) this.isEnabledChatEnter = true + this.openGifModal = false this.isLoadingGoToRepliedMessage = { isLoading: false, top: 0, @@ -929,6 +1349,11 @@ class ChatPage extends LitElement { } } + + setOpenGifModal(value){ + this.openGifModal = value + } + _toggle(value) { this.shifted = value === (false || true) ? value : !this.shifted; this.requestUpdate() @@ -959,6 +1384,16 @@ class ChatPage extends LitElement { localStorage.setItem('isEnabledChatEnter', !this.isEnabledChatEnter ) this.isEnabledChatEnter = !this.isEnabledChatEnter } + + addGifs(gifs){ + this.gifsToBeAdded = [...this.gifsToBeAdded, ...gifs] + } + + setGifsLoading(props) { + this.gifsLoading = props; + } + + render() { return html` @@ -994,14 +1429,33 @@ class ChatPage extends LitElement { ` : this.renderChatScroller()} +
{ + if (this.gifsLoading) return; + 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"}> +
+ ${this.isLoadingGoToRepliedMessage && this.isLoadingGoToRepliedMessage.loading ? html`
` : ''}
- + + this.setGifsLoading(val)} + .sendMessage=${(val) => this._sendMessage(val)} + .setOpenGifModal=${(val)=> this.setOpenGifModal(val)}> +
+ style=${(this.lastMessageRefVisible && !this.imageFile && !this.openGifModal) ? 'opacity: 1;' : 'opacity: 0;'}> { this.shadowRoot.querySelector("chat-scroller").shadowRoot.getElementById("downObserver") .scrollIntoView({ @@ -1082,6 +1536,8 @@ class ChatPage extends LitElement { .repliedToMessageObj=${this.repliedToMessageObj} .toggleEnableChatEnter=${this.toggleEnableChatEnter} ?isEnabledChatEnter=${this.isEnabledChatEnter} + ?openGifModal=${this.openGifModal} + .setOpenGifModal=${(val)=> this.setOpenGifModal(val)} chatId=${this.chatId} > @@ -1559,8 +2015,8 @@ class ChatPage extends LitElement { if(this.webWorker){ this.webWorker.terminate(); } - if(this.webWorkerImage){ - this.webWorkerImage.terminate(); + if(this.webWorkerFile){ + this.webWorkerFile.terminate(); } if(this.editor){ this.editor.destroy() @@ -1582,7 +2038,7 @@ class ChatPage extends LitElement { } 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)) { @@ -2972,8 +3428,28 @@ class ChatPage extends LitElement { }; const stringifyMessageObject = JSON.stringify(messageObject); this.sendMessage(stringifyMessageObject, typeMessage); - } - else if (outSideMsg && outSideMsg.type === 'attachment') { + } else if (outSideMsg && outSideMsg.type === 'gif') { + const userName = await getName(this.selectedAddress.address); + if (!userName) { + parentEpml.request('showSnackBar', get("chatpage.cchange27")); + this.isLoading = false; + return; + } + + const messageObject = { + messageText: '', + gifs: [{ + service: outSideMsg.service, + name: outSideMsg.name, + identifier: outSideMsg.identifier, + filePath: outSideMsg.filePath + }], + repliedTo: '', + version: 2 + }; + const stringifyMessageObject = JSON.stringify(messageObject); + this.sendMessage(stringifyMessageObject, typeMessage); + } else if (outSideMsg && outSideMsg.type === 'attachment') { this.isUploadingAttachment = true; const userName = await getName(this.selectedAddress.address); if (!userName) { diff --git a/qortal-ui-plugins/plugins/core/components/ChatScroller.js b/qortal-ui-plugins/plugins/core/components/ChatScroller.js index 7ed44c52..ab4ab7ed 100644 --- a/qortal-ui-plugins/plugins/core/components/ChatScroller.js +++ b/qortal-ui-plugins/plugins/core/components/ChatScroller.js @@ -281,10 +281,12 @@ 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 }, openDeleteAttachment: { type: Boolean }, isImageLoaded: { type: Boolean }, + isGifLoaded: { type: Boolean }, isFirstMessage: { type: Boolean }, isSingleMessageInGroup: { type: Boolean }, isLastMessageInGroup: { type: Boolean }, @@ -311,8 +313,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 @@ -384,6 +389,7 @@ class MessageTemplate extends LitElement { let reactions = []; let repliedToData = null; let image = null; + let gif = null; let isImageDeleted = false; let isAttachmentDeleted = false; let version = 0; @@ -392,8 +398,7 @@ class MessageTemplate extends LitElement { let attachment = null; try { const parsedMessageObj = JSON.parse(this.messageObj.decodedMessage); - if(+parsedMessageObj.version > 1){ - + if(+parsedMessageObj.version > 1 && parsedMessageObj.messageText){ messageVersion2 = generateHTML(parsedMessageObj.messageText, [ StarterKit, Underline, @@ -415,13 +420,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); @@ -463,6 +475,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; @@ -475,6 +514,17 @@ class MessageTemplate extends LitElement { } } + 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` ${this.messageObj.senderName ? this.messageObj.senderName : cropAddress(this.messageObj.sender)} @@ -646,6 +696,25 @@ class MessageTemplate extends LitElement { ` : image && isImageDeleted ? html`

${translate("chatpage.cchange80")}

` : 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``} ${attachment && !isAttachmentDeleted ? html`
await this.downloadAttachment(attachment)} class="attachment-container"> @@ -753,6 +822,7 @@ class MessageTemplate extends LitElement { .setOpenPrivateMessage=${(val) => this.setOpenPrivateMessage(val)} .setOpenTipUser=${(val) => this.setOpenTipUser(val)} .setUserName=${(val) => this.setUserName(val)} + .gif=${!!gif} >
@@ -855,6 +925,28 @@ class MessageTemplate extends LitElement { ${translate("general.close")} + { + this.openDialogGif = false + }}> +
+
+ ${gifHTMLDialog} +
+ { + + this.openDialogGif = false + }} + > + ${translate("general.close")} + +
- ${this.myAddress === this.originalMessage.sender ? ( + ${((this.myAddress === this.originalMessage.sender) && ( + !this.gif)) ? ( html`
{ - if(this.version === '0'){ + if (this.version === '0'){ this.versionErrorSnack() return } diff --git a/qortal-ui-plugins/plugins/core/components/ChatTextEditor.js b/qortal-ui-plugins/plugins/core/components/ChatTextEditor.js index cb09cf94..98e1ae89 100644 --- a/qortal-ui-plugins/plugins/core/components/ChatTextEditor.js +++ b/qortal-ui-plugins/plugins/core/components/ChatTextEditor.js @@ -34,6 +34,8 @@ class ChatTextEditor extends LitElement { }, toggleEnableChatEnter: {attribute: false}, isEnabledChatEnter: {type: Boolean}, + openGifModal: { type: Boolean }, + setOpenGifModal: { attribute: false }, chatId: {type: String} } } @@ -392,7 +394,7 @@ mwc-checkbox::shadow .mdc-checkbox::after, mwc-checkbox::shadow .mdc-checkbox::b .toggleBold() .run() } - class=${["chatbar-button-single", (this.editedMessageObj || this.repliedToMessageObj) && 'show-chatbar-buttons', this.editor && this.editor.isActive('bold') ? 'is-active' : ''].join(" ")} + class=${["chatbar-button-single", (this.editedMessageObj || this.repliedToMessageObj || this.openGifModal) && 'show-chatbar-buttons', this.editor && this.editor.isActive('bold') ? 'is-active' : ''].join(" ")} > @@ -406,33 +408,33 @@ mwc-checkbox::shadow .mdc-checkbox::after, mwc-checkbox::shadow .mdc-checkbox::b .toggleItalic() .run() } - class=${["chatbar-button-single", (this.editedMessageObj || this.repliedToMessageObj) && 'show-chatbar-buttons', this.editor && this.editor.isActive('italic') ? 'is-active' : ''].join(' ')} + class=${["chatbar-button-single", (this.editedMessageObj || this.repliedToMessageObj || this.openGifModal) && 'show-chatbar-buttons', this.editor && this.editor.isActive('italic') ? 'is-active' : ''].join(' ')} > + ${this.setOpenGifModal ? + html` + + ` + : ''} ${this.editedMessageObj ? ( html`
@@ -568,14 +584,7 @@ mwc-checkbox::shadow .mdc-checkbox::after, mwc-checkbox::shadow .mdc-checkbox::b } } - - - - - 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 new file mode 100644 index 00000000..af03aadc --- /dev/null +++ b/qortal-ui-plugins/plugins/core/components/ImageComponent.js @@ -0,0 +1,127 @@ +import { LitElement, html, css } from 'lit'; +import { translate, get } from 'lit-translate'; +import { render } from 'lit/html.js'; + +export class ImageComponent extends LitElement { + +static get properties() { +return { +class: { type: String }, +gif: { type: Object }, +alt: { type: String }, +attempts: { type: Number }, +maxAttempts: { type: Number }, +error: { type: Boolean }, +sendMessage: { attribute: false }, +setOpenGifModal: { attribute: false } +} +} + +static get styles() { +return css` +.gif-error-msg { +margin: 0; +font-family: Roboto, sans-serif; +font-size: 17px; +letter-spacing: 0.3px; +color: var(--chat-bubble-msg-color); +font-weight: 300; +padding: 10px 10px; +} + +.gif-image { +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; +} + +.gif-image:hover { +border: 1px solid var(--mdc-theme-primary ); +} +` +} + +constructor() { + super(); + 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++; + if (this.attempts > this.maxAttempts) return; + await new Promise((res) => { + setTimeout(() => { + res(); + }, 1000) + }); + try { + const response = await fetch(this.gif.url); + const data = await response.json(); + console.log({data}); + if (data.ok) { + this.error = false; + this.gif = { + ...this.gif, + url: data.src + }; + this.requestUpdate(); + } else if (!data.ok || data.error) { + this.error = true; + } else { + this.error = false; + } + } catch (error) { + this.error = true; + console.error(error); + this._fetchImage(); + } +} + +render() { +if (this.error && this.attempts <= this.maxAttempts) { + setTimeout(() => { + this._fetchImage(); + }, 1000); +} +return html` +${this.gif && !this.error + ? html` + { + 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('gifs.gchange15')}

+` + : html` +

${translate('gifs.gchange16')}

+ ` +}` +} +} + +customElements.define('image-component', ImageComponent); 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; +} +` diff --git a/qortal-ui-plugins/plugins/utils/publish-image.js b/qortal-ui-plugins/plugins/utils/publish-image.js index 05594d1c..9f03644a 100644 --- a/qortal-ui-plugins/plugins/utils/publish-image.js +++ b/qortal-ui-plugins/plugins/utils/publish-image.js @@ -16,7 +16,9 @@ export const publishData = async ({ parentEpml, uploadType, selectedAddress, - worker + worker, + isBase64, + metaData }) => { const validateName = async (receiverName) => { let nameRes = await parentEpml.request("apiCall", { @@ -115,16 +117,26 @@ 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") + } + } - - 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",