mirror of
https://github.com/Qortal/qortal-ui.git
synced 2025-02-11 17:55:51 +00:00
improve loading of img in chat
This commit is contained in:
parent
2de6f4d25b
commit
5c6529d269
289
plugins/plugins/core/components/ChatImage.js
Normal file
289
plugins/plugins/core/components/ChatImage.js
Normal file
@ -0,0 +1,289 @@
|
||||
import { LitElement, html, css } from 'lit';
|
||||
import { render } from 'lit/html.js';
|
||||
import {
|
||||
use,
|
||||
get,
|
||||
translate,
|
||||
translateUnsafeHTML,
|
||||
registerTranslateConfig,
|
||||
} from 'lit-translate';
|
||||
import axios from 'axios'
|
||||
import { RequestQueueWithPromise } from '../../utils/queue';
|
||||
|
||||
const requestQueue = new RequestQueueWithPromise(5);
|
||||
|
||||
export class ChatImage extends LitElement {
|
||||
static get properties() {
|
||||
return {
|
||||
resource: { type: Object },
|
||||
isReady: { type: Boolean},
|
||||
status: {type: Object},
|
||||
setOpenDialogImage: { attribute: false}
|
||||
};
|
||||
}
|
||||
|
||||
static get styles() {
|
||||
return css`
|
||||
img {
|
||||
max-width:45vh;
|
||||
max-height:40vh;
|
||||
border-radius: 5px;
|
||||
cursor: pointer;
|
||||
}
|
||||
.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: 30px;
|
||||
position: relative;
|
||||
text-indent: -9999em;
|
||||
transform: translateZ(0px);
|
||||
animation: 1.1s linear 0s infinite normal none running loadingAnimation;
|
||||
}
|
||||
|
||||
.defaultSize {
|
||||
width: 45vh;
|
||||
height: 40vh;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@-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);
|
||||
}
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.resource = {
|
||||
identifier: "",
|
||||
name: "",
|
||||
service: ""
|
||||
}
|
||||
this.status = {
|
||||
status: ''
|
||||
}
|
||||
this.url = ""
|
||||
this.isReady = false
|
||||
this.nodeUrl = this.getNodeUrl()
|
||||
this.myNode = this.getMyNode()
|
||||
this.hasCalledWhenDownloaded = false
|
||||
|
||||
this.observer = new IntersectionObserver(entries => {
|
||||
for (const entry of entries) {
|
||||
if (entry.isIntersecting && this.status.status !== 'READY') {
|
||||
this._fetchImage();
|
||||
// Stop observing after the image has started loading
|
||||
this.observer.unobserve(this);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
getNodeUrl(){
|
||||
const myNode = window.parent.reduxStore.getState().app.nodeConfig.knownNodes[window.parent.reduxStore.getState().app.nodeConfig.node]
|
||||
|
||||
const nodeUrl = myNode.protocol + '://' + myNode.domain + ':' + myNode.port
|
||||
return nodeUrl
|
||||
}
|
||||
getMyNode(){
|
||||
const myNode = window.parent.reduxStore.getState().app.nodeConfig.knownNodes[window.parent.reduxStore.getState().app.nodeConfig.node]
|
||||
|
||||
return myNode
|
||||
}
|
||||
|
||||
getApiKey() {
|
||||
const myNode =
|
||||
window.parent.reduxStore.getState().app.nodeConfig.knownNodes[
|
||||
window.parent.reduxStore.getState().app.nodeConfig.node
|
||||
];
|
||||
let apiKey = myNode.apiKey;
|
||||
return apiKey;
|
||||
}
|
||||
|
||||
async fetchResource() {
|
||||
try {
|
||||
// await qortalRequest({
|
||||
// action: 'GET_QDN_RESOURCE_PROPERTIES',
|
||||
// name,
|
||||
// service,
|
||||
// identifier
|
||||
// })
|
||||
await axios.get(`${this.nodeUrl}/arbitrary/resource/properties/${this.resource.service}/${this.resource.name}/${this.resource.identifier}?apiKey=${this.myNode.apiKey}`)
|
||||
|
||||
|
||||
} catch (error) {}
|
||||
}
|
||||
|
||||
async fetchVideoUrl() {
|
||||
|
||||
this.fetchResource()
|
||||
this.url = `${this.nodeUrl}/arbitrary/${this.resource.service}/${this.resource.name}/${this.resource.identifier}?async=true&apiKey=${this.myNode.apiKey}`
|
||||
|
||||
}
|
||||
|
||||
async fetchStatus(){
|
||||
let isCalling = false
|
||||
let percentLoaded = 0
|
||||
let timer = 24
|
||||
const response = await axios.get(`${this.nodeUrl}/arbitrary/resource/status/${this.resource.service}/${this.resource.name}/${this.resource.identifier}?apiKey=${this.myNode.apiKey}`)
|
||||
if(response && response.data && response.data.status === 'READY'){
|
||||
this.status = response.data
|
||||
return
|
||||
}
|
||||
const intervalId = setInterval(async () => {
|
||||
if (isCalling) return
|
||||
isCalling = true
|
||||
|
||||
const data = await requestQueue.enqueue(() => {
|
||||
return axios.get(`${this.nodeUrl}/arbitrary/resource/status/${this.resource.service}/${this.resource.name}/${this.resource.identifier}?apiKey=${this.myNode.apiKey}`)
|
||||
});
|
||||
const res = data.data
|
||||
|
||||
isCalling = false
|
||||
if (res.localChunkCount) {
|
||||
if (res.percentLoaded) {
|
||||
if (
|
||||
res.percentLoaded === percentLoaded &&
|
||||
res.percentLoaded !== 100
|
||||
) {
|
||||
timer = timer - 5
|
||||
} else {
|
||||
timer = 24
|
||||
}
|
||||
if (timer < 0) {
|
||||
timer = 24
|
||||
isCalling = true
|
||||
this.status = {
|
||||
...res,
|
||||
status: 'REFETCHING'
|
||||
}
|
||||
|
||||
setTimeout(() => {
|
||||
isCalling = false
|
||||
this.fetchResource({
|
||||
name,
|
||||
service,
|
||||
identifier
|
||||
})
|
||||
}, 25000)
|
||||
return
|
||||
}
|
||||
percentLoaded = res.percentLoaded
|
||||
}
|
||||
|
||||
this.status = res
|
||||
|
||||
}
|
||||
|
||||
// check if progress is 100% and clear interval if true
|
||||
if (res?.status === 'READY') {
|
||||
clearInterval(intervalId)
|
||||
this.status = res
|
||||
this.isReady = true
|
||||
}
|
||||
}, 5000) // 1 second interval
|
||||
}
|
||||
|
||||
async _fetchImage() {
|
||||
try {
|
||||
this.fetchVideoUrl({
|
||||
name: this.resource.name,
|
||||
service: this.resource.service,
|
||||
identifier: this.resource.identifier
|
||||
})
|
||||
this.fetchStatus()
|
||||
} catch (error) {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
firstUpdated(){
|
||||
this.observer.observe(this);
|
||||
|
||||
}
|
||||
|
||||
shouldUpdate(changedProperties) {
|
||||
if (changedProperties.has('setOpenDialogImage') && changedProperties.size === 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
async updated(changedProperties) {
|
||||
if (changedProperties && changedProperties.has('status')) {
|
||||
if(this.hasCalledWhenDownloaded === false && this.status.status === 'DOWNLOADED'){
|
||||
this.fetchResource()
|
||||
this.hasCalledWhenDownloaded = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
|
||||
return html`
|
||||
<div
|
||||
class=${[
|
||||
`image-container`,
|
||||
this.status.status !== 'READY'
|
||||
? 'defaultSize'
|
||||
: '',
|
||||
this.status.status !== 'READY'
|
||||
? 'hideImg'
|
||||
: '',
|
||||
].join(' ')}
|
||||
>
|
||||
${
|
||||
this.status.status !== 'READY'
|
||||
? html`
|
||||
<div
|
||||
style="display:flex;flex-direction:column;width:100%;height:100%;justify-content:center;align-items:center;position:absolute;"
|
||||
>
|
||||
<div
|
||||
class=${`smallLoading`}
|
||||
></div>
|
||||
<p>${`${Math.round(this.status.percentLoaded || 0
|
||||
).toFixed(0)}% loaded`}</p>
|
||||
</div>
|
||||
`
|
||||
: ''
|
||||
}
|
||||
${this.status.status === 'READY' ? html`
|
||||
<img @click=${()=> this.setOpenDialogImage(true)} src=${this.url} />
|
||||
` : ''}
|
||||
|
||||
</div>
|
||||
`
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define('chat-image', ChatImage);
|
@ -3895,9 +3895,10 @@ class ChatPage extends LitElement {
|
||||
new Compressor(image, {
|
||||
quality: .6,
|
||||
maxWidth: 1200,
|
||||
mimeType: 'image/webp',
|
||||
success(result) {
|
||||
const file = new File([result], "name", {
|
||||
type: image.type
|
||||
type: 'image/webp'
|
||||
})
|
||||
compressedFile = file
|
||||
resolve()
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -1,126 +1,133 @@
|
||||
import { LitElement, html, css } from 'lit'
|
||||
import { render } from 'lit/html.js'
|
||||
import { use, get, translate, translateUnsafeHTML, registerTranslateConfig } from 'lit-translate'
|
||||
import { LitElement, html, css } from 'lit';
|
||||
import { render } from 'lit/html.js';
|
||||
import {
|
||||
use,
|
||||
get,
|
||||
translate,
|
||||
translateUnsafeHTML,
|
||||
registerTranslateConfig,
|
||||
} from 'lit-translate';
|
||||
|
||||
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()
|
||||
if (data.ok) {
|
||||
this.error = false
|
||||
this.gif = {
|
||||
...this.gif,
|
||||
url: data.src
|
||||
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 },
|
||||
};
|
||||
this.requestUpdate();
|
||||
} else if (!data.ok || data.error) {
|
||||
this.error = true
|
||||
} else {
|
||||
this.error = 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();
|
||||
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();
|
||||
}
|
||||
} 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` <img
|
||||
class=${this.class}
|
||||
src=${this.gif.url}
|
||||
alt=${this.alt}
|
||||
@click=${() => {
|
||||
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`
|
||||
<p class="gif-error-msg">${translate('gifs.gchange15')}</p>
|
||||
`
|
||||
: html`
|
||||
<p class="gif-error-msg">${translate('gifs.gchange16')}</p>
|
||||
`}`;
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
if (this.error && this.attempts <= this.maxAttempts) {
|
||||
setTimeout(() => {
|
||||
this._fetchImage()
|
||||
}, 1000)
|
||||
}
|
||||
return html`
|
||||
${this.gif && !this.error
|
||||
? html`
|
||||
<img
|
||||
class=${this.class}
|
||||
src=${this.gif.url}
|
||||
alt=${this.alt}
|
||||
@click=${() => {
|
||||
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`
|
||||
<p class='gif-error-msg'>${translate('gifs.gchange15')}</p>
|
||||
`
|
||||
: html`
|
||||
<p class='gif-error-msg'>${translate('gifs.gchange16')}</p>
|
||||
`
|
||||
}`
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define('image-component', ImageComponent)
|
||||
customElements.define('image-component', ImageComponent);
|
||||
|
@ -32,3 +32,40 @@ export class RequestQueue {
|
||||
}
|
||||
}
|
||||
|
||||
export class RequestQueueWithPromise {
|
||||
constructor(maxConcurrent = 5) {
|
||||
this.queue = [];
|
||||
this.maxConcurrent = maxConcurrent;
|
||||
this.currentlyProcessing = 0;
|
||||
}
|
||||
|
||||
// Add a request to the queue and return a promise
|
||||
enqueue(request) {
|
||||
return new Promise((resolve, reject) => {
|
||||
// Push the request and its resolve and reject callbacks to the queue
|
||||
this.queue.push({ request, resolve, reject });
|
||||
this.process();
|
||||
});
|
||||
}
|
||||
|
||||
// Process requests in the queue
|
||||
async process() {
|
||||
while (this.queue.length > 0 && this.currentlyProcessing < this.maxConcurrent) {
|
||||
this.currentlyProcessing++;
|
||||
const { request, resolve, reject } = this.queue.shift();
|
||||
try {
|
||||
const response = await request();
|
||||
resolve(response);
|
||||
} catch (error) {
|
||||
reject(error);
|
||||
} finally {
|
||||
this.currentlyProcessing--;
|
||||
this.process();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user