import { html, LitElement } from 'lit' import { render } from 'lit/html.js' import { repeat } from 'lit/directives/repeat.js' import { connect } from 'pwa-helpers' import { store } from '../store' import { Epml } from '../epml' import { addPluginRoutes } from '../plugins/addPluginRoutes' import { setIsOpenDevDialog, setNewTab } from '../redux/app/app-actions' import { defaultQappsTabs } from '../data/defaultQapps' import { showPluginStyles, navBarStyles, appAvatarStyles } from '../styles/core-css' import FileSaver from 'file-saver' import ShortUniqueId from 'short-unique-id' import '../functional-components/frag-file-input' import '@material/mwc-button' import '@material/mwc-dialog' import '@material/mwc-icon' import '@material/mwc-textfield' import '@polymer/iron-icons/iron-icons.js' import '@polymer/paper-dialog/paper-dialog.js' import '@polymer/paper-icon-button/paper-icon-button.js' import '@vaadin/grid' import '@vaadin/text-field' // Multi language support import { get, registerTranslateConfig, translate, use } from '../../translate' registerTranslateConfig({ loader: lang => fetch(`/language/${lang}.json`).then(res => res.json()) }) export const parentEpml = new Epml({ type: 'WINDOW', source: window.parent }) class ShowPlugin extends connect(store)(LitElement) { static get properties() { return { app: { type: Object }, pluginConfig: { type: Object }, url: { type: String }, linkParam: { type: String }, registeredUrls: { type: Array }, currentTab: { type: Number }, tabs: { type: Array }, theme: { type: String, reflect: true }, tabInfo: { type: Object }, chatLastSeen: { type: Array }, chatHeads: { type: Array }, proxyPort: { type: Number }, isOpenDevDialog: { type: Boolean } } } static get styles() { return [showPluginStyles] } constructor() { super() this.registeredUrls = [] this.initialRegisteredUrls = [] this.currentTab = 0 this.tabs = [] this.uid = new ShortUniqueId() this.tabInfo = {} this.chatLastSeen = [] this.chatHeads = [] this.proxyPort = 0 this.isOpenDevDialog = false this.theme = localStorage.getItem('qortalTheme') ? localStorage.getItem('qortalTheme') : 'light' } render() { const plugSrc = (myPlug) => { return myPlug === undefined ? 'about:blank' : `${window.location.origin}/plugin/${myPlug.domain}/${myPlug.page}${this.linkParam}` } return html`
${this.tabs.map((tab, index) => { let title = '' let icon = '' let count = 0 if (tab.myPlugObj && tab.myPlugObj.title === "Overview Page") { title = html`${translate('tabmenu.tm28')}` } else if (tab.myPlugObj && tab.myPlugObj.title === "Minting Details") { title = html`${translate('tabmenu.tm1')}` } else if (tab.myPlugObj && tab.myPlugObj.title === "Become a Minter") { title = html`${translate('tabmenu.tm2')}` } else if (tab.myPlugObj && tab.myPlugObj.title === "Sponsorship List") { title = html`${translate('tabmenu.tm3')}` } else if (tab.myPlugObj && tab.myPlugObj.title === "Wallets") { title = html`${translate('tabmenu.tm4')}` } else if (tab.myPlugObj && tab.myPlugObj.title === "Trade Portal") { title = html`${translate('tabmenu.tm5')}` } else if (tab.myPlugObj && tab.myPlugObj.title === "Auto Buy") { title = html`${translate('tabmenu.tm6')}` } else if (tab.myPlugObj && tab.myPlugObj.title === "Reward Share") { title = html`${translate('tabmenu.tm7')}` } else if (tab.myPlugObj && tab.myPlugObj.title === "Q-Chat") { title = html`${translate('tabmenu.tm8')}` } else if (tab.myPlugObj && tab.myPlugObj.title === "Name Registration") { title = html`${translate('tabmenu.tm9')}` } else if (tab.myPlugObj && tab.myPlugObj.title === "Names Market") { title = html`${translate('tabmenu.tm10')}` } else if (tab.myPlugObj && tab.myPlugObj.title === "Websites") { title = html`${translate('tabmenu.tm11')}` } else if (tab.myPlugObj && tab.myPlugObj.title === "Q-Apps") { title = html`${translate('tabmenu.tm12')}` } else if (tab.myPlugObj && tab.myPlugObj.title === "Group Management") { title = html`${translate('tabmenu.tm13')}` } else if (tab.myPlugObj && tab.myPlugObj.title === "Data Management") { title = html`${translate('tabmenu.tm14')}` } else if (tab.myPlugObj && tab.myPlugObj.title === "Puzzles") { title = html`${translate('tabmenu.tm15')}` } else if (tab.myPlugObj && tab.myPlugObj.title === "Node Management") { title = html`${translate('tabmenu.tm16')}` } else if (tab.myPlugObj && tab.myPlugObj.title === "Qortal Lottery") { title = html`${translate('tabmenu.tm42')}` } else if (tab.myPlugObj && tab.myPlugObj.url === "myapp") { title = tab.myPlugObj && tab.myPlugObj.title } else if (tab.myPlugObj && tab.myPlugObj.url === "devmode") { title = html`${translate('tabmenu.tm38')}` } else { title = html`${translate('tabmenu.tm17')}` } if (tab.myPlugObj && tab.myPlugObj.mwcicon) { icon = tab.myPlugObj.mwcicon } else { icon = 'tab' } if (tab.myPlugObj && (tab.myPlugObj.url === 'myapp') && this.tabInfo[tab.id]) { title = this.tabInfo[tab.id].name } if (tab.myPlugObj && (tab.myPlugObj.url === 'myapp') && this.tabInfo[tab.id]) { count = this.tabInfo[tab.id].count } if (tab.myPlugObj && tab.myPlugObj.url === 'q-chat') { for (const chat of this.chatHeads) { const lastReadMessage = this.chatLastSeen.find((ch) => { let id if (chat.groupId === 0) { id = chat.groupId } else if (chat.groupId) { id = chat.groupId } else { id = chat.address } return ch.key.includes(id) }) if (lastReadMessage && lastReadMessage.timestamp < chat.timestamp) { count = count + 1 } } } return html`
${tab.myPlugObj && tab.myPlugObj.url === "myapp" ? html` ` : html` ${icon} ` }
${count ? html` ${title} ${count} { event.stopPropagation(); this.removeTab(index, tab.id)}}> close ` : html` ${title} { event.stopPropagation(); this.removeTab(index, tab.id)}}> close ` }
` })}
${repeat(this.tabs, (tab) => tab.id, (tab, index) => html`
this.changePage(val)} >
` )} { this.shadowRoot.getElementById('domainInput').value = '' this.shadowRoot.getElementById('portInput').value = '' this.isOpenDevDialog = false store.dispatch(setIsOpenDevDialog(false)) }} >

${translate('tabmenu.tm39')}


${translate("general.close")} ${translate('tabmenu.tm40')}
` } firstUpdated() { this.changeLanguage() this.tabs.forEach((tab, index) => { const frame = this.shadowRoot.getElementById(`showPluginFrame${index}`) this.createEpmlInstance(frame, index) }) window.addEventListener('storage', () => { const checkLanguage = localStorage.getItem('qortalLanguage') const checkTheme = localStorage.getItem('qortalTheme') use(checkLanguage) if (checkTheme === 'dark') { this.theme = 'dark' } else { this.theme = 'light' } document.querySelector('html').setAttribute('theme', this.theme) }) } changeLanguage() { const checkLanguage = localStorage.getItem('qortalLanguage') if (checkLanguage === null || checkLanguage.length === 0) { localStorage.setItem('qortalLanguage', 'us') use('us') } else { use(checkLanguage) } } async getUpdateComplete() { await super.getUpdateComplete() return true } async getProxyPort() { this.proxyPort = 0 let framework = '' const domain = this.shadowRoot.getElementById('domainInput').value const port = this.shadowRoot.getElementById('portInput').value if (domain.length >= 3 && port.length >= 2) { framework = domain + ':' + port } else { let errorString = get("tabmenu.tm41") parentEpml.request('showSnackBar', `${errorString}`) return } let framePort = await parentEpml.request('apiCall', { url: `/developer/proxy/start`, method: 'POST', headers: { 'Content-Type': 'text/plain' }, body: `${framework}` }) this.createUrl(framePort) } createUrl(framePort) { this.proxyPort = framePort const myFrameNode = store.getState().app.nodeConfig.knownNodes[store.getState().app.nodeConfig.node] const myFrameNodeUrl = myFrameNode.protocol + '://' + myFrameNode.domain + ':' + this.proxyPort this.changePage({ "url": "devmode", "domain": "core", "page": `qdn/browser/index.html?link=${myFrameNodeUrl}&dev=FRAMEWORK`, "title": "Dev Server", "icon": "vaadin:desktop", "mwcicon": "api", "menus": [], "parent": false }) this.shadowRoot.querySelector("#addDevDialog").close() } async addTab(tab) { if (this.tabs == []) { // ...Nothing to do } else { this.tabs.forEach((rac, index) => { let racId = '' let tabRacId = '' let frameRacId = '' let plugRacId = '' let iconRacId = '' racId = rac.id tabRacId = 'tab-' + racId frameRacId = 'frame-' + racId plugRacId = 'plug-' + racId iconRacId = 'icon-' + racId const plugObjRac = rac.url var tabActiveRac = this.shadowRoot.getElementById(tabRacId) var frameActiveRac = this.shadowRoot.getElementById(frameRacId) var plugActiveRac = this.shadowRoot.getElementById(plugRacId) var iconActiveRac = this.shadowRoot.getElementById(iconRacId) if (plugObjRac === undefined || '') { tabActiveRac.classList.remove('active') iconActiveRac.classList.remove('iconActive') iconActiveRac.classList.add('iconInactive') plugActiveRac.classList.remove('showIframe') plugActiveRac.classList.add('hideIframe') } else { tabActiveRac.classList.remove('active') iconActiveRac.classList.remove('iconActive') iconActiveRac.classList.add('iconInactive') frameActiveRac.classList.remove('showIframe') frameActiveRac.classList.add('hideIframe') } }) } this.tabs = [...this.tabs, tab] await this.getUpdateComplete() // add the new tab to the tabs array const newIndex = this.tabs.length - 1 // render the tab and wait for it to be added to the DOM const frame = this.shadowRoot.getElementById(`showPluginFrame${newIndex}`) this.createEpmlInstance(frame, newIndex) } removeTab(index, tabA) { const tabB = this.tabs.length - 1 const tabC = this.tabs[tabB].id if (tabC === tabA) { let theId = '' let tabId = '' let frameId = '' let plugId = '' let iconId = '' this.tabs = this.tabs.filter((tab, tIndex) => tIndex !== index) this.currentTab = this.tabs.length - 1 const tabD = this.tabs.length - 1 if (tabD < 0) { const lengthOfTabs = this.tabs.length this.addTab({ url: '', id: this.uid.rnd() }) this.currentTab = lengthOfTabs } else { const plugObj = this.tabs[tabD].url theId = this.tabs[tabD].id tabId = 'tab-' + theId frameId = 'frame-' + theId plugId = 'plug-' + theId iconId = 'icon-' + theId var tabActive = this.shadowRoot.getElementById(tabId) var frameActive = this.shadowRoot.getElementById(frameId) var plugActive = this.shadowRoot.getElementById(plugId) var iconActive = this.shadowRoot.getElementById(iconId) if (plugObj === undefined || '') { tabActive.classList.add('active') iconActive.classList.remove('iconInactive') iconActive.classList.add('iconActive') plugActive.classList.remove('hideIframe') plugActive.classList.add('showIframe') } else { tabActive.classList.add('active') iconActive.classList.remove('iconInactive') iconActive.classList.add('iconActive') frameActive.classList.remove('hideIframe') frameActive.classList.add('showIframe') } this.requestUpdate() } } else { // Remove tab from array this.tabs = this.tabs.filter((tab, tIndex) => tIndex !== index) if (this.tabs.length !== 0) { this.currentTab = 0 } this.requestUpdate() } } createEpmlInstance(frame, index) { const showingPluginEpml = new Epml({ type: 'WINDOW', source: frame.contentWindow }) addPluginRoutes(showingPluginEpml) showingPluginEpml.imReady() // store Epml instance in tab for later use this.tabs[index].epmlInstance = showingPluginEpml // Register each instance with a unique identifier Epml.registerProxyInstance(`visible-plugin-${index}`, showingPluginEpml) } updated(changedProps) { if (changedProps.has('url') || changedProps.has('registeredUrls')) { const plugArr = [] this.registeredUrls.forEach(myPlugArr => { myPlugArr.menus.length === 0 ? plugArr.push(myPlugArr) : myPlugArr.menus.forEach(i => plugArr.push(myPlugArr, i)) }) const myPlugObj = plugArr.find(pagePlug => { return pagePlug.url === this.url }) if (this.tabs.length === 0) { this.addTab({ url: '', id: this.uid.rnd() }) } else { const copiedTabs = [...this.tabs] copiedTabs[this.currentTab] = { ...copiedTabs[this.currentTab], url: this.url, myPlugObj } this.tabs = copiedTabs } this.requestUpdate() } if (changedProps.has('computerUrl')) { if (this.computedUrl !== 'about:blank') { this.loading = true } } } changePage(page) { const copiedTabs = [...this.tabs] copiedTabs[this.currentTab] = { ...copiedTabs[this.currentTab], myPlugObj: page, url: page.url } this.tabs = copiedTabs } async stateChanged(state) { const split = state.app.url.split('/') const newRegisteredUrls = state.app.registeredUrls let newUrl, newLinkParam if (newRegisteredUrls !== this.registeredUrls) { this.registeredUrls = newRegisteredUrls } if (split[0] === '' && split[1] === 'app' && split[2] === undefined) { newUrl = '' newLinkParam = '' } else if (split.length === 5 && split[1] === 'app') { newUrl = split[2] newLinkParam = split[3] === undefined ? '' : '?' + split[3] + '/' + split[4] } else if (split[1] === 'app') { newUrl = split[2] newLinkParam = '' } else { newUrl = '404' newLinkParam = '' } if (newUrl !== this.url) { this.url = newUrl } if (newLinkParam !== this.linkParam) { this.linkParam = newLinkParam } if (this.tabInfo !== state.app.tabInfo) { this.tabInfo = state.app.tabInfo } if (this.chatLastSeen !== state.app.chatLastSeen) { this.chatLastSeen = state.app.chatLastSeen } if (state.app.chatHeads !== this.unModifiedChatHeads) { let chatHeads = [] if (state.app.chatHeads && state.app.chatHeads.groups) { chatHeads = [...chatHeads, ...state.app.chatHeads.groups] } if (state.app.chatHeads && state.app.chatHeads.direct) { chatHeads = [...chatHeads, ...state.app.chatHeads.direct] } this.chatHeads = chatHeads this.unModifiedChatHeads = state.app.chatHeads } if (state.app.newTab) { const newTab = state.app.newTab if (newTab.openExisting && this.tabs.find((tab) => tab.url === newTab.url)) { const findIndex = this.tabs.findIndex((tab) => tab.url === newTab.url) if (findIndex !== -1) { this.currentTab = findIndex } store.dispatch(setNewTab(null)) } else if (!this.tabs.find((tab) => tab.id === newTab.id)) { await this.addTab(newTab) this.currentTab = this.tabs.length - 1 //clear newTab store.dispatch(setNewTab(null)) } else { const findIndex = this.tabs.findIndex((tab) => tab.id === newTab.id) if (findIndex !== -1) { const copiedTabs = [...this.tabs] copiedTabs[findIndex] = newTab this.tabs = copiedTabs this.currentTab = findIndex } //clear newTab store.dispatch(setNewTab(null)) } } if (state.app.isOpenDevDialog) { this.isOpenDevDialog = state.app.isOpenDevDialog } } } window.customElements.define('show-plugin', ShowPlugin) class NavBar extends connect(store)(LitElement) { static get properties() { return { menuList: { type: Array }, newMenuList: { type: Array }, myMenuList: { type: Array }, myMenuPlugins: { type: Array }, myApps: { type: Array }, changePage: { attribute: false }, pluginName: { type: String }, pluginType: { type: String }, pluginPage: { type: String }, mwcIcon: { type: String }, pluginNameToDelete: { type: String }, pluginNumberToDelete: { type: String }, textFieldDisabled: { type: Boolean }, initialName: { type: String }, newId: { type: String }, removeTitle: { type: String }, myFollowedNames: { type: Array }, myFollowedNamesList: { type: Array }, searchNameContentString: { type: String }, searchNameResources: { type: Array }, addressInfo: { type: Object } } } static get styles() { return [navBarStyles] } constructor() { super() this.menuList = [] this.newMenuList = [] this.myMenuList = [] this.myMenuPlugins = [] this.pluginName = '' this.pluginType = '' this.pluginPage = '' this.myApps = '' this.mwcIcon = '' this.pluginNameToDelete = '' this.pluginNumberToDelete = '' this.textFieldDisabled = false this.initialName = '' this.newId = '' this.removeTitle = '' this.myFollowedNames = [] this.myFollowedNamesList = [] this.searchContentString = '' this.searchNameResources = [] this.addressInfo = store.getState().app.accountInfo.addressInfo this._updateMyMenuPlugins = this._updateMyMenuPlugins.bind(this) } render() { return html`
reset_tv person_search upload download
${repeat(this.myMenuList, (plugin) => plugin.url, (plugin, index) => html`
${this.renderRemoveIcon(plugin.url, plugin.mwcicon, plugin.title, plugin.pluginNumber, plugin)}
${this.renderTitle(plugin.url, plugin.title)}
`)}
add
${translate("tabmenu.tm19")}

${translate("tabmenu.tm26")}



${translate("tabmenu.tm24")}

${translate("tabmenu.tm19")} ${translate("general.close")}

${translate("tabmenu.tm27")}



${translate("tabmenu.tm23")}

${this.removeTitle}

${translate("general.yes")} ${translate("general.no")}

{ render(html`${this.renderNameAvatar(data.item)}`, root) }} > { render(html`${data.item.name}`, root) }} > { render(html`${this.renderMyFollowUnfollowButton(data.item)}`, root) }} > ${this.isEmptyArray(this.searchNameResources) ? html` ${translate("login.entername")} `: ''}

${translate("tabmenu.tm31")}


{ render(html`${this.renderNameAvatar(data.item)}`, root) }} > { render(html`${data.item.name}`, root) }} > { render(html`${this.renderMyFollowUnfollowButton(data.item)}`, root) }} > ${this.isEmptyArray(this.myFollowedNamesList) ? html` ${translate("tabmenu.tm32")} `: ''}

${translate("tabmenu.tm33")}



${translate("walletpage.wchange56")}

${translate("tabmenu.tm35")}
${translate("general.close")}
` } async firstUpdated() { addPluginRoutes(parentEpml) parentEpml.imReady() this.addressInfo = store.getState().app.accountInfo.addressInfo this.menuList = store.getState().app.registeredUrls const addressInfo = this.addressInfo const isMinter = addressInfo?.error !== 124 && +addressInfo?.level > 0 const isSponsor = +addressInfo?.level >= 5 const appDelay = ms => new Promise(res => setTimeout(res, ms)) await this.checkMyMenuPlugins() await appDelay(250) if (!isMinter) { this.newMenuList = this.myMenuPlugins.filter((minter) => { return minter.url !== 'minting' }) } else { this.newMenuList = this.myMenuPlugins.filter((minter) => { return minter.url !== 'become-minter' }) } if (!isSponsor) { this.myMenuList = this.newMenuList.filter((sponsor) => { return sponsor.url !== 'sponsorship-list' }) } else { this.myMenuList = this.newMenuList } await appDelay(250) await this.getMyFollowedNames() await this.getMyFollowedNamesList() } async _updateMyMenuPlugins(event) { await new Promise((res) => { setTimeout(() => { res() }, 500) }) this.myMenuPlugins = event.detail const addressInfo = this.addressInfo const isMinter = addressInfo?.error !== 124 && +addressInfo?.level > 0 const isSponsor = +addressInfo?.level >= 5 if (!isMinter) { this.newMenuList = this.myMenuPlugins.filter((minter) => { return minter.url !== 'minting' }) } else { this.newMenuList = this.myMenuPlugins.filter((minter) => { return minter.url !== 'become-minter' }) } if (!isSponsor) { this.myMenuList = this.newMenuList.filter((sponsor) => { return sponsor.url !== 'sponsorship-list' }) } else { this.myMenuList = this.newMenuList } this.requestUpdate() } connectedCallback() { super.connectedCallback() window.addEventListener('myMenuPlugs-event', this._updateMyMenuPlugins) } disconnectedCallback() { window.removeEventListener('myMenuPlugs-event', this._updateMyMenuPlugins) super.disconnectedCallback() } openImportDialog() { this.shadowRoot.getElementById('importTabMenutDialog').show() } importTabMenu(file) { let myFile = '' myFile = file this.myMenuPlugins = [] localStorage.removeItem('myMenuPlugs') const newTabMenu = JSON.parse((myFile) || '[]') const copyPayload = [...newTabMenu] localStorage.setItem('myMenuPlugs', JSON.stringify(newTabMenu)) this.saveSettingToTemp(copyPayload) this.shadowRoot.getElementById('importTabMenutDialog').close() this.myMenuPlugins = JSON.parse(localStorage.getItem('myMenuPlugs') || '[]') this.firstUpdated() let success5string = get('tabmenu.tm36') parentEpml.request('showSnackBar', `${success5string}`) } exportTabMenu() { let tabMenu = '' const qortalTabMenu = JSON.stringify(localStorage.getItem('myMenuPlugs')) const qortalTabMenuSave = JSON.parse((qortalTabMenu) || '[]') const blob = new Blob([qortalTabMenuSave], { type: 'text/plain;charset=utf-8' }) tabMenu = 'qortal.tabmenu' this.saveFileToDisk(blob, tabMenu) } async saveFileToDisk(blob, fileName) { try { const fileHandle = await self.showSaveFilePicker({ suggestedName: fileName, types: [{ description: 'File' }] }) const writeFile = async (fileHandle, contents) => { const writable = await fileHandle.createWritable() await writable.write(contents) await writable.close() } writeFile(fileHandle, blob).then(() => console.log('FILE SAVED')) let snack4string = get('tabmenu.tm37') parentEpml.request('showSnackBar', `${snack4string} ${fileName}`) } catch (error) { if (error.name === 'AbortError') { return } FileSaver.saveAs(blob, fileName) let snack4string = get('tabmenu.tm37') parentEpml.request('showSnackBar', `${snack4string} ${fileName}`) } } openNameSearch() { this.searchNameResources = [] this.shadowRoot.getElementById('searchNameContent').value = '' this.shadowRoot.getElementById('myFollowedNamesDialog').close() this.shadowRoot.getElementById('searchNameDialog').open() } closeNameSearch() { this.shadowRoot.getElementById('searchNameDialog').close() } openMyFollowedNames() { this.shadowRoot.getElementById('searchNameDialog').close() this.shadowRoot.getElementById('myFollowedNamesDialog').open() this.getMyFollowedNamesList() } closeMyFollowedNames() { this.shadowRoot.getElementById('myFollowedNamesDialog').close() } async getMyFollowedNames() { this.myFollowedNames = await parentEpml.request('apiCall', { url: `/lists/followedNames?apiKey=${this.getApiKey()}` }) } searchNameKeyListener(e) { if (e.key === 'Enter') { this.searchNameResult() } } async getMyFollowedNamesList() { const myNode = store.getState().app.nodeConfig.knownNodes[store.getState().app.nodeConfig.node] const myNodeUrl = myNode.protocol + '://' + myNode.domain + ':' + myNode.port const followedNamesUrl = `${myNodeUrl}/lists/followedNames?apiKey=${this.getApiKey()}` var myFollowedNamesNew = [] this.myFollowedNamesList = [] await fetch(followedNamesUrl).then(response => { return response.json() }).then(data => { return data.map(item => { const addListName = { name: item } myFollowedNamesNew.push(addListName) }) }) this.myFollowedNamesList = myFollowedNamesNew if (this.shadowRoot.getElementById('myFollowedNamesDialog').opened) { this.shadowRoot.getElementById('myFollowedNamesDialog').notifyResize() } } async searchNameResult() { let searchMyName = this.shadowRoot.getElementById('searchNameContent').value if (searchMyName.length === 0) { let err1string = get('appspage.schange34') parentEpml.request('showSnackBar', `${err1string}`) } else { let searchNameResources = await parentEpml.request('apiCall', { url: `/names/search?query=${searchMyName}&prefix=true&limit=0&reverse=true` }) if (this.isEmptyArray(searchNameResources)) { let err2string = get('appspage.schange17') parentEpml.request('showSnackBar', `${err2string}`) } else { this.searchNameResources = searchNameResources if (this.shadowRoot.getElementById('searchNameDialog').opened) { this.shadowRoot.getElementById('searchNameDialog').notifyResize() } } } } renderNameAvatar(nameObj) { let myName = nameObj.name const myNameNode = store.getState().app.nodeConfig.knownNodes[store.getState().app.nodeConfig.node] const myNameNodeUrl = myNameNode.protocol + '://' + myNameNode.domain + ':' + myNameNode.port const nameUrl = `${myNameNodeUrl}/arbitrary/THUMBNAIL/${myName}/qortal_avatar?async=true` return html`` } renderMyFollowUnfollowButton(nameObj) { let name = nameObj.name if (this.myFollowedNames == null || !Array.isArray(this.myFollowedNames)) { return html`` } if (this.myFollowedNames.indexOf(name) === -1) { return html` this.myFollowName(nameObj)}>add_to_queue ${translate("appspage.schange29")}` } else { return html` this.myUnfollowName(nameObj)}>remove_from_queue ${translate("appspage.schange30")}` } } async myFollowName(nameObj) { let name = nameObj.name let items = [ name ] let namesJsonString = JSON.stringify({ 'items': items }) let ret = await parentEpml.request('apiCall', { url: `/lists/followedNames?apiKey=${this.getApiKey()}`, method: 'POST', headers: { 'Content-Type': 'application/json' }, body: `${namesJsonString}` }) if (ret === true) { this.myFollowedNames = this.myFollowedNames.filter(item => item != name) this.myFollowedNames.push(name) } else { let err3string = get('appspage.schange22') parentEpml.request('showSnackBar', `${err3string}`) } await this.getMyFollowedNamesList() return ret } async myUnfollowName(nameObj) { let name = nameObj.name let items = [ name ] let namesJsonString = JSON.stringify({ 'items': items }) let ret = await parentEpml.request('apiCall', { url: `/lists/followedNames?apiKey=${this.getApiKey()}`, method: 'DELETE', headers: { 'Content-Type': 'application/json' }, body: `${namesJsonString}` }) if (ret === true) { this.myFollowedNames = this.myFollowedNames.filter(item => item != name) } else { let err4string = get('appspage.schange23') parentEpml.request('showSnackBar', `${err4string}`) } await this.getMyFollowedNamesList() return ret } async checkMyMenuPlugins() { const appDelay = ms => new Promise(res => setTimeout(res, ms)) if (localStorage.getItem('myMenuPlugs') === null) { await appDelay(500) const listOfPlugins = this.menuList.filter(plugin => plugin.url !== 'puzzles') const addQapps = [...listOfPlugins, ...defaultQappsTabs] const myObj = JSON.stringify(addQapps) localStorage.setItem('myMenuPlugs', myObj) await appDelay(250) this.myMenuPlugins = JSON.parse(localStorage.getItem('myMenuPlugs') || '[]') } else { let newPluginMenu = JSON.parse(localStorage.getItem('myMenuPlugs') || '[]') const oldQchat = 'messaging/q-chat/index.html' const newQchat = 'q-chat/index.html' const oldMinting = 'minting/index.html' const newMinting = 'minting-info/index.html' const oldWebsites = 'qdn/index.html' const newWebsites = 'q-website/index.html' const oldDatamanager = 'qdn/data-management/index.html' const newDatamanager = 'data-management/index.html' // Check if local storage have broken links and replace them newPluginMenu.find(a => { if (a.page === oldQchat) { a.page = newQchat } else if (a.page === oldMinting) { a.page = newMinting } else if (a.page === oldWebsites) { a.page = newWebsites } else if (a.page === oldDatamanager) { a.page = newDatamanager } else {} }) localStorage.setItem('myMenuPlugs', JSON.stringify(newPluginMenu)) await appDelay(250) this.myMenuPlugins = JSON.parse(localStorage.getItem('myMenuPlugs') || '[]') } } resetMenu() { localStorage.removeItem('myMenuPlugs') this.firstUpdated() } val() { const theValue = this.shadowRoot.getElementById('pluginTypeInput').value if (theValue === 'reject') { this.textFieldDisabled = false this.initialName = '' this.mwcIcon = '' } else if (theValue === '0') { this.textFieldDisabled = false this.initialName = '' this.mwcIcon = '' } else if (theValue === '1') { this.textFieldDisabled = false this.initialName = '' this.mwcIcon = '' } else if (theValue === 'overview-page') { this.mwcIcon = '' this.initialName = '' this.textFieldDisabled = true this.initialName = 'Overview Page' this.mwcIcon = 'home' } else if (theValue === 'minting') { this.mwcIcon = '' this.initialName = '' this.textFieldDisabled = true this.initialName = 'Minting Details' this.mwcIcon = 'info_outline' } else if (theValue === 'become-minter') { this.mwcIcon = '' this.initialName = '' this.textFieldDisabled = true this.initialName = 'Become a Minter' this.mwcIcon = 'thumb_up' } else if (theValue === 'sponsorship-list') { this.mwcIcon = '' this.initialName = '' this.textFieldDisabled = true this.initialName = 'Sponsorship List' this.mwcIcon = 'format_list_numbered' } else if (theValue === 'wallet') { this.mwcIcon = '' this.initialName = '' this.textFieldDisabled = true this.initialName = 'Wallets' this.mwcIcon = 'account_balance_wallet' } else if (theValue === 'trade-portal') { this.mwcIcon = '' this.initialName = '' this.textFieldDisabled = true this.initialName = 'Trade Portal' this.mwcIcon = 'format_list_bulleted' } else if (theValue === 'trade-bot-portal') { this.mwcIcon = '' this.initialName = '' this.textFieldDisabled = true this.initialName = 'Auto Buy' this.mwcIcon = 'shop' } else if (theValue === 'reward-share') { this.mwcIcon = '' this.initialName = '' this.textFieldDisabled = true this.initialName = 'Reward Share' this.mwcIcon = 'ios_share' } else if (theValue === 'q-chat') { this.mwcIcon = '' this.initialName = '' this.textFieldDisabled = true this.initialName = 'Q-Chat' this.mwcIcon = 'forum' } else if (theValue === 'name-registration') { this.mwcIcon = '' this.initialName = '' this.textFieldDisabled = true this.initialName = 'Name Registration' this.mwcIcon = 'manage_accounts' } else if (theValue === 'names-market') { this.mwcIcon = '' this.initialName = '' this.textFieldDisabled = true this.initialName = 'Names Market' this.mwcIcon = 'store' } else if (theValue === 'websites') { this.mwcIcon = '' this.initialName = '' this.textFieldDisabled = true this.initialName = 'Websites' this.mwcIcon = 'desktop_mac' } else if (theValue === 'qapps') { this.mwcIcon = '' this.initialName = '' this.textFieldDisabled = true this.initialName = 'Q-Apps' this.mwcIcon = 'apps' } else if (theValue === 'group-management') { this.mwcIcon = '' this.initialName = '' this.textFieldDisabled = true this.initialName = 'Group Management' this.mwcIcon = 'group' } else if (theValue === 'data-management') { this.mwcIcon = '' this.initialName = '' this.textFieldDisabled = true this.initialName = 'Data Management' this.mwcIcon = 'storage' } else if (theValue === 'puzzles') { this.mwcIcon = '' this.initialName = '' this.textFieldDisabled = true this.initialName = 'Puzzles' this.mwcIcon = 'extension' } else if (theValue === 'node-management') { this.mwcIcon = '' this.initialName = '' this.textFieldDisabled = true this.initialName = 'Node Management' this.mwcIcon = 'cloud' } else if (theValue === 'lottery') { this.mwcIcon = '' this.initialName = '' this.textFieldDisabled = true this.initialName = 'Qortal Lottery' this.mwcIcon = 'token' } } filterSelectMenu() { const addressInfoSelect = this.addressInfo const isMinterSelect = addressInfoSelect?.error !== 124 && +addressInfoSelect?.level > 0 const isSponsorSelect = +addressInfoSelect?.level >= 5 if (!isMinterSelect) { return html` ` } else if (isMinterSelect && isSponsorSelect) { return html` ` } else { return html` ` } } openAddNewPlugin() { this.shadowRoot.getElementById('pluginTypeInput').value = 'reject' this.shadowRoot.getElementById('pluginNameInput').value = '' this.initialName = '' this.textFieldDisabled = false this.shadowRoot.querySelector('#addNewPlugin').show() } async addToMyMenuPlugins() { this.newId = '' const newUid = new ShortUniqueId({ length: 10 }) this.newId = 'plugin-' + newUid.rnd() this.pluginType = this.shadowRoot.getElementById('pluginTypeInput').value if (this.pluginType === 'reject') { let myplugerr = get('tabmenu.tm25') parentEpml.request('showSnackBar', `${myplugerr}`) return false } else if (this.pluginType === '0') { this.mwcIcon = '' this.pluginName = this.shadowRoot.getElementById('pluginNameInput').value if (this.pluginName === 'Q-Blog') { this.mwcIcon = 'rss_feed' } else if (this.pluginName === 'Q-Mail') { this.mwcIcon = 'mail' } else { this.mwcIcon = 'apps' } var oldMenuPlugs = JSON.parse(localStorage.getItem('myMenuPlugs') || '[]') const newMenuPlugsItem = { 'url': 'myapp', 'domain': 'core', 'page': `qdn/browser/index.html?name=${this.pluginName}&service=APP`, 'title': this.pluginName, 'icon': 'vaadin:external-browser', 'mwcicon': this.mwcIcon, 'pluginNumber': this.newId, 'menus': [], 'parent': false } const validatePluginName = async () => { if (this.pluginType === '0' && this.pluginName.length == 0) { let myplugstring1 = get('walletpage.wchange50') parentEpml.request('showSnackBar', `${myplugstring1}`) return false } let myPluginName = false this.myPluginNameRes = [] await parentEpml.request('apiCall', { url: `/arbitrary/resources/search?service=APP&query=${this.pluginName}&exactmatchnames=true&limit=1` }).then(res => { this.myPluginNameRes = res }) myPluginName = !(this.myPluginNameRes === undefined || this.myPluginNameRes.length == 0) return myPluginName } let myNameRes = await validatePluginName() if (myNameRes !== false) { oldMenuPlugs.push(newMenuPlugsItem) const copyPayload = [...oldMenuPlugs] localStorage.setItem('myMenuPlugs', JSON.stringify(oldMenuPlugs)) this.saveSettingToTemp(copyPayload) let myplugstring2 = get('walletpage.wchange52') parentEpml.request('showSnackBar', `${myplugstring2}`) this.closeAddNewPlugin() this.myMenuPlugins = JSON.parse(localStorage.getItem('myMenuPlugs') || '[]') this.firstUpdated() } else { let myplugstring3 = get('websitespage.schange17') parentEpml.request('showSnackBar', `${myplugstring3}`) return false } } else if (this.pluginType === '1') { this.mwcIcon = '' this.pluginName = this.shadowRoot.getElementById('pluginNameInput').value this.mwcIcon = 'web' var oldMenuPlugs = JSON.parse(localStorage.getItem('myMenuPlugs') || '[]') const newMenuPlugsItem = { 'url': 'myapp', 'domain': 'core', 'page': `qdn/browser/index.html?name=${this.pluginName}&service=WEBSITE`, 'title': this.pluginName, 'icon': 'vaadin:external-browser', 'mwcicon': this.mwcIcon, 'pluginNumber': this.newId, 'menus': [], 'parent': false } const validatePluginName = async () => { if (this.pluginType === '1' && this.pluginName.length == 0) { let myplugstring1 = get('walletpage.wchange50') parentEpml.request('showSnackBar', `${myplugstring1}`) return false } let myPluginName = false this.myPluginNameRes = [] await parentEpml.request('apiCall', { url: `/arbitrary/resources/search?service=WEBSITE&query=${this.pluginName}&exactmatchnames=true&limit=1` }).then(res => { this.myPluginNameRes = res }) myPluginName = !(this.myPluginNameRes === undefined || this.myPluginNameRes.length == 0) return myPluginName } let myNameRes = await validatePluginName() if (myNameRes !== false) { oldMenuPlugs.push(newMenuPlugsItem) const copyPayload = [...oldMenuPlugs] localStorage.setItem('myMenuPlugs', JSON.stringify(oldMenuPlugs)) this.saveSettingToTemp(copyPayload) let myplugstring2 = get('walletpage.wchange52') parentEpml.request('showSnackBar', `${myplugstring2}`) this.closeAddNewPlugin() this.myMenuPlugins = JSON.parse(localStorage.getItem('myMenuPlugs') || '[]') this.firstUpdated() } else { let myplugstring3 = get('websitespage.schange17') parentEpml.request('showSnackBar', `${myplugstring3}`) return false } } else { this.pluginPage = '' if (this.pluginType === 'overview-page') { this.pluginPage = 'overview-page/index.html' } else if (this.pluginType === 'minting') { this.pluginPage = 'minting-info/index.html' } else if (this.pluginType === 'become-minter') { this.pluginPage = 'become-minter/index.html' } else if (this.pluginType === 'sponsorship-list') { this.pluginPage = 'sponsorship-list/index.html' } else if (this.pluginType === 'wallet') { this.pluginPage = 'wallet/index.html' } else if (this.pluginType === 'trade-portal') { this.pluginPage = 'trade-portal/index.html' } else if (this.pluginType === 'trade-bot-portal') { this.pluginPage = 'trade-bot/index.html' } else if (this.pluginType === 'reward-share') { this.pluginPage = 'reward-share/index.html' } else if (this.pluginType === 'q-chat') { this.pluginPage = 'q-chat/index.html' } else if (this.pluginType === 'name-registration') { this.pluginPage = 'name-registration/index.html' } else if (this.pluginType === 'names-market') { this.pluginPage = 'names-market/index.html' } else if (this.pluginType === 'websites') { this.pluginPage = 'q-website/index.html' } else if (this.pluginType === 'qapps') { this.pluginPage = 'q-app/index.html' } else if (this.pluginType === 'group-management') { this.pluginPage = 'group-management/index.html' } else if (this.pluginType === 'data-management') { this.pluginPage = 'data-management/index.html' } else if (this.pluginType === 'puzzles') { this.pluginPage = 'puzzles/index.html' } else if (this.pluginType === 'node-management') { this.pluginPage = 'node-management/index.html' } else if (this.pluginType === 'lottery') { this.pluginPage = 'qortal-lottery/index.html' } var oldMenuPlugs = JSON.parse(localStorage.getItem('myMenuPlugs') || '[]') const newMenuPlugsItem = { 'url': this.pluginType, 'domain': 'core', 'page': this.pluginPage, 'title': this.initialName, 'icon': 'vaadin:external-browser', 'mwcicon': this.mwcIcon, 'pluginNumber': this.newId, 'menus': [], 'parent': false } oldMenuPlugs.push(newMenuPlugsItem) const copyPayload = [...oldMenuPlugs] localStorage.setItem('myMenuPlugs', JSON.stringify(oldMenuPlugs)) this.saveSettingToTemp(copyPayload) let myplugstring2 = get('walletpage.wchange52') parentEpml.request('showSnackBar', `${myplugstring2}`) this.closeAddNewPlugin() this.myMenuPlugins = JSON.parse(localStorage.getItem('myMenuPlugs') || '[]') this.firstUpdated() } } closeAddNewPlugin() { this.shadowRoot.querySelector('#addNewPlugin').close() this.shadowRoot.getElementById('pluginTypeInput').value = 'reject' this.shadowRoot.getElementById('pluginNameInput').value = '' this.initialName = '' this.textFieldDisabled = false } renderTitle(theUrl, theName) { if (theUrl === 'overview-page') { return html`${translate('tabmenu.tm28')}` } else if (theUrl === 'minting') { return html`${translate('tabmenu.tm1')}` } else if (theUrl === 'become-minter') { return html`${translate('tabmenu.tm2')}` } else if (theUrl === 'sponsorship-list') { return html`${translate('tabmenu.tm3')}` } else if (theUrl === 'wallet') { return html`${translate('tabmenu.tm4')}` } else if (theUrl === 'trade-portal') { return html`${translate('tabmenu.tm5')}` } else if (theUrl === 'trade-bot-portal') { return html`${translate('tabmenu.tm6')}` } else if (theUrl === 'reward-share') { return html`${translate('tabmenu.tm7')}` } else if (theUrl === 'q-chat') { return html`${translate('tabmenu.tm8')}` } else if (theUrl === 'name-registration') { return html`${translate('tabmenu.tm9')}` } else if (theUrl === 'names-market') { return html`${translate('tabmenu.tm10')}` } else if (theUrl === 'websites') { return html`${translate('tabmenu.tm11')}` } else if (theUrl === 'qapps') { return html`${translate('tabmenu.tm12')}` } else if (theUrl === 'group-management') { return html`${translate('tabmenu.tm13')}` } else if (theUrl === 'data-management') { return html`${translate('tabmenu.tm14')}` } else if (theUrl === 'puzzles') { return html`${translate('tabmenu.tm15')}` } else if (theUrl === 'node-management') { return html`${translate('tabmenu.tm16')}` } else if (theUrl === 'lottery') { return html`${translate('tabmenu.tm42')}` } else { return html`${theName}` } } renderRemoveIcon(appurl, appicon, appname, appid, appplugin) { return html` ` } openRemoveApp(pluginNameTD, pluginNumberTD, pluginUrlTD) { this.pluginNameToDelete = '' this.pluginNameToDelete = pluginNameTD this.pluginNumberToDelete = '' this.pluginNumberToDelete = pluginNumberTD this.removeTitle = '' if (pluginUrlTD === 'overview-page') { this.removeTitle = html`${translate('tabmenu.tm28')}` } else if (pluginUrlTD === 'minting') { this.removeTitle = html`${translate('tabmenu.tm1')}` } else if (pluginUrlTD === 'become-minter') { this.removeTitle = html`${translate('tabmenu.tm2')}` } else if (pluginUrlTD === 'sponsorship-list') { this.removeTitle = html`${translate('tabmenu.tm3')}` } else if (pluginUrlTD === 'wallet') { this.removeTitle = html`${translate('tabmenu.tm4')}` } else if (pluginUrlTD === 'trade-portal') { this.removeTitle = html`${translate('tabmenu.tm5')}` } else if (pluginUrlTD === 'trade-bot-portal') { this.removeTitle = html`${translate('tabmenu.tm6')}` } else if (pluginUrlTD === 'reward-share') { this.removeTitle = html`${translate('tabmenu.tm7')}` } else if (pluginUrlTD === 'q-chat') { this.removeTitle = html`${translate('tabmenu.tm8')}` } else if (pluginUrlTD === 'name-registration') { this.removeTitle = html`${translate('tabmenu.tm9')}` } else if (pluginUrlTD === 'names-market') { this.removeTitle = html`${translate('tabmenu.tm10')}` } else if (pluginUrlTD === 'websites') { this.removeTitle = html`${translate('tabmenu.tm11')}` } else if (pluginUrlTD === 'qapps') { this.removeTitle = html`${translate('tabmenu.tm12')}` } else if (pluginUrlTD === 'group-management') { this.removeTitle = html`${translate('tabmenu.tm13')}` } else if (pluginUrlTD === 'data-management') { this.removeTitle = html`${translate('tabmenu.tm14')}` } else if (pluginUrlTD === 'puzzles') { this.removeTitle = html`${translate('tabmenu.tm15')}` } else if (pluginUrlTD === 'node-management') { this.removeTitle = html`${translate('tabmenu.tm16')}` } else if (pluginUrlTD === 'lottery') { this.removeTitle = html`${translate('tabmenu.tm42')}` } else { this.removeTitle = html`${pluginNameTD}` } this.shadowRoot.querySelector('#removePlugin').show() } removeAppFromArray() { const pluginToRemove = this.pluginNumberToDelete this.newMenuFilter = [] this.newMenuFilter = this.myMenuList.filter((item) => item.pluginNumber !== pluginToRemove) const copyPayload = [...this.newMenuFilter] const myNewObj = JSON.stringify(this.newMenuFilter) localStorage.removeItem('myMenuPlugs') localStorage.setItem('myMenuPlugs', myNewObj) this.saveSettingToTemp(copyPayload) this.myMenuPlugins = JSON.parse(localStorage.getItem('myMenuPlugs') || '[]') this.firstUpdated() this.closeRemoveApp() } saveSettingToTemp(data) { const tempSettingsData = JSON.parse(localStorage.getItem('temp-settings-data') || '{}') const newTemp = { ...tempSettingsData, myMenuPlugs: { data: data, timestamp: Date.now() } } localStorage.setItem('temp-settings-data', JSON.stringify(newTemp)) this.dispatchEvent( new CustomEvent('temp-settings-data-event', { bubbles: true, composed: true }) ) } closeRemoveApp() { this.shadowRoot.querySelector('#removePlugin').close() this.pluginNameToDelete = '' this.pluginNumberToDelete = '' } async extractComponents(url) { if (!url.startsWith('qortal://')) { return null } url = url.replace(/^(qortal\:\/\/)/, '') if (url.includes('/')) { let parts = url.split('/') const service = parts[0].toUpperCase() parts.shift() const name = parts[0] parts.shift() let identifier if (parts.length > 0) { // Do not shift yet identifier = parts[0] // Check if a resource exists with this service, name and identifier combination const myNode = store.getState().app.nodeConfig.knownNodes[store.getState().app.nodeConfig.node] const nodeUrl = myNode.protocol + '://' + myNode.domain + ':' + myNode.port const url = `${nodeUrl}/arbitrary/resource/status/${service}/${name}/${identifier}?apiKey=${myNode.apiKey}}` const res = await fetch(url) const data = await res.json() if (data.totalChunkCount > 0) { // Identifier exists, so don't include it in the path parts.shift() } else { identifier = null } } const path = parts.join('/') const components = {} components['service'] = service components['name'] = name components['identifier'] = identifier components['path'] = path return components } return null } async getQuery(value) { let newQuery = value if (newQuery.endsWith('/')) { newQuery = newQuery.slice(0, -1) } const res = await this.extractComponents(newQuery) if (!res) return const { service, name, identifier, path } = res let query = `?service=${service}` if (name) { query = query + `&name=${name}` } if (identifier) { query = query + `&identifier=${identifier}` } if (path) { query = query + `&path=${path}` } if (service === 'APP') { this.changePage({ 'url': 'myapp', 'domain': 'core', 'page': `qdn/browser/index.html${query}`, 'title': name || 'Q-App', 'icon': 'vaadin:external-browser', 'mwcicon': 'open_in_browser', 'menus': [], 'parent': false }) } else if (service === 'WEBSITE') { this.changePage({ 'url': 'myapp', 'domain': 'core', 'page': `qdn/browser/index.html${query}`, 'title': name || 'Website', 'icon': 'vaadin:desktop', 'mwcicon': 'desktop_mac', 'menus': [], 'parent': false }) } } async handlePasteLink(e) { try { const value = this.shadowRoot.getElementById('linkInput').value await this.getQuery(value) } catch (error) { } } async _handleKeyDown(e) { if (e.key === 'Enter') { try { const value = this.shadowRoot.getElementById('linkInput').value await this.getQuery(value) } catch (error) { } } } getApiKey() { const apiNode = store.getState().app.nodeConfig.knownNodes[store.getState().app.nodeConfig.node] return apiNode.apiKey } isEmptyArray(arr) { if (!arr) { return true } return arr.length === 0 } stateChanged(state) { this.menuList = state.app.registeredUrls this.addressInfo = state.app.accountInfo.addressInfo } } window.customElements.define('nav-bar', NavBar) class AppAvatar extends connect(store)(LitElement) { static get properties() { return { hasAvatar: { type: Boolean }, isImageLoaded: { type: Boolean }, appicon: { type: String }, appname: { type: String } } } static get styles() { return [appAvatarStyles] } constructor() { super() this.hasAvatar = false this.isImageLoaded = false this.imageFetches = 0 } render() { let appAvatarImg = '' if (this.appname) { const appAvatarNode = store.getState().app.nodeConfig.knownNodes[store.getState().app.nodeConfig.node] const appAvatarNodeUrl = appAvatarNode.protocol + '://' + appAvatarNode.domain + ':' + appAvatarNode.port const appAvatarUrl = `${appAvatarNodeUrl}/arbitrary/THUMBNAIL/${this.appname}/qortal_avatar?async=true` appAvatarImg = this.createImage(appAvatarUrl) } return html` ${this.isImageLoaded ? html` ${appAvatarImg} ` : html` ${this.appicon} ` } ` } firstUpdated() { // ... } createImage(imageUrl) { const imageHTMLRes = new Image() imageHTMLRes.src = imageUrl imageHTMLRes.style = "border-radius:10px; font-size:14px; object-fit: fill;height:60px;width:60px;" imageHTMLRes.onload = () => { this.isImageLoaded = true } imageHTMLRes.onerror = () => { if (this.imageFetches < 1) { setTimeout(() => { this.imageFetches = this.imageFetches + 1 imageHTMLRes.src = imageUrl }, 5000) } else { this.isImageLoaded = false } } return imageHTMLRes } } window.customElements.define('app-avatar', AppAvatar) class TabAvatar extends connect(store)(LitElement) { static get properties() { return { hasAvatar: { type: Boolean }, isImageLoaded: { type: Boolean }, appicon: { type: String }, appname: { type: String } } } constructor() { super() this.hasAvatar = false this.isImageLoaded = false this.imageFetches = 0 } render() { let tabAvatarImg = '' if (this.appname) { const tabAvatarNode = store.getState().app.nodeConfig.knownNodes[store.getState().app.nodeConfig.node] const tabAvatarNodeUrl = tabAvatarNode.protocol + '://' + tabAvatarNode.domain + ':' + tabAvatarNode.port const tabAvatarUrl = `${tabAvatarNodeUrl}/arbitrary/THUMBNAIL/${this.appname}/qortal_avatar?async=true` tabAvatarImg = this.createImage(tabAvatarUrl) } return html` ${this.isImageLoaded ? html` ${tabAvatarImg} ` : html` ${this.appicon} ` } ` } firstUpdated() { // ... } createImage(imageUrl) { const imageHTMLRes = new Image() imageHTMLRes.src = imageUrl imageHTMLRes.style = "border-radius:4px; font-size:14px; object-fit: fill;height:24px;width:24px;" imageHTMLRes.onload = () => { this.isImageLoaded = true } imageHTMLRes.onerror = () => { if (this.imageFetches < 1) { setTimeout(() => { this.imageFetches = this.imageFetches + 1 imageHTMLRes.src = imageUrl }, 5000) } else { this.isImageLoaded = false } } return imageHTMLRes } } window.customElements.define('tab-avatar', TabAvatar)