diff --git a/qortal-ui-plugins/plugins/core/q-app/app-browser/app-browser.src.js b/qortal-ui-plugins/plugins/core/q-app/app-browser/app-browser.src.js
deleted file mode 100644
index b64c55bb..00000000
--- a/qortal-ui-plugins/plugins/core/q-app/app-browser/app-browser.src.js
+++ /dev/null
@@ -1,626 +0,0 @@
-import { LitElement, html, css } from 'lit'
-import { render } from 'lit/html.js'
-import { Epml } from '../../../../epml'
-import { use, get, translate, translateUnsafeHTML, registerTranslateConfig } from 'lit-translate'
-
-registerTranslateConfig({
- loader: lang => fetch(`/language/${lang}.json`).then(res => res.json())
-})
-
-import '@material/mwc-button'
-import '@material/mwc-icon'
-
-const parentEpml = new Epml({ type: 'WINDOW', source: window.parent })
-
-class AppBrowser extends LitElement {
- static get properties() {
- return {
- url: { type: String },
- name: { type: String },
- service: { type: String },
- identifier: { type: String },
- path: { type: String },
- displayUrl: {type: String },
- followedNames: { type: Array },
- blockedNames: { type: Array },
- theme: { type: String, reflect: true }
- }
- }
-
- static get observers() {
- return ['_kmxKeyUp(amount)']
- }
-
- static get styles() {
- return css`
- * {
- --mdc-theme-primary: rgb(3, 169, 244);
- --mdc-theme-secondary: var(--mdc-theme-primary);
- --paper-input-container-focus-color: var(--mdc-theme-primary);
- }
-
- #websitesWrapper paper-button {
- float: right;
- }
-
- #websitesWrapper .buttons {
- width: auto !important;
- }
-
- .address-bar {
- position: absolute;
- top: 0;
- left: 0;
- right: 0;
- height: 100px;
- background-color: var(--white);
- height: 36px;
- }
-
- .address-bar-button mwc-icon {
- width: 20px;
- }
-
- .iframe-container {
- position: absolute;
- top: 36px;
- left: 0;
- right: 0;
- bottom: 0;
- border-top: 1px solid var(--black);
- }
-
- .iframe-container iframe {
- display: block;
- width: 100%;
- height: 100%;
- border: none;
- background-color: var(--white);
- }
-
- input[type=text] {
- margin: 0;
- padding: 2px 0 0 20px;
- border: 0;
- height: 34px;
- font-size: 16px;
- background-color: var(--white);
- }
-
- paper-progress {
- --paper-progress-active-color: var(--mdc-theme-primary);
- }
-
- .float-right {
- float: right;
- }
-
- `
- }
-
- constructor() {
- super()
- this.url = 'about:blank'
-
- const urlParams = new URLSearchParams(window.location.search);
- this.name = urlParams.get('name');
- this.service = urlParams.get('service');
- this.identifier = urlParams.get('identifier') != null ? urlParams.get('identifier') : null;
- this.path = urlParams.get('path') != null ? ((urlParams.get('path').startsWith("/") ? "" : "/") + urlParams.get('path')) : "";
- this.followedNames = []
- this.blockedNames = []
- this.theme = localStorage.getItem('qortalTheme') ? localStorage.getItem('qortalTheme') : 'light'
-
- // Build initial display URL
- let displayUrl = "qortal://" + this.service + "/" + this.name;
- if (this.identifier != null && data.identifier != "" && this.identifier != "default") displayUrl = displayUrl.concat("/" + this.identifier);
- if (this.path != null && this.path != "/") displayUrl = displayUrl.concat(this.path);
- this.displayUrl = displayUrl;
-
- const getFollowedNames = async () => {
-
- let followedNames = await parentEpml.request('apiCall', {
- url: `/lists/followedNames?apiKey=${this.getApiKey()}`
- })
-
- this.followedNames = followedNames
- setTimeout(getFollowedNames, this.config.user.nodeSettings.pingInterval)
- }
-
- const getBlockedNames = async () => {
-
- let blockedNames = await parentEpml.request('apiCall', {
- url: `/lists/blockedNames?apiKey=${this.getApiKey()}`
- })
-
- this.blockedNames = blockedNames
- setTimeout(getBlockedNames, this.config.user.nodeSettings.pingInterval)
- }
-
- const render = () => {
- const myNode = window.parent.reduxStore.getState().app.nodeConfig.knownNodes[window.parent.reduxStore.getState().app.nodeConfig.node]
- const nodeUrl = myNode.protocol + '://' + myNode.domain + ':' + myNode.port
- this.url = `${nodeUrl}/render/${this.service}/${this.name}${this.path != null ? this.path : ""}?theme=${this.theme}&identifier=${this.identifier != null ? this.identifier : ""}`;
- }
-
- const authorizeAndRender = () => {
- parentEpml.request('apiCall', {
- url: `/render/authorize/${this.name}?apiKey=${this.getApiKey()}`,
- method: "POST"
- }).then(res => {
- if (res.error) {
- // Authorization problem - API key incorrect?
- }
- else {
- render()
- }
- })
- }
-
- let configLoaded = false
-
- parentEpml.ready().then(() => {
- parentEpml.subscribe('selected_address', async selectedAddress => {
- this.selectedAddress = {}
- selectedAddress = JSON.parse(selectedAddress)
- if (!selectedAddress || Object.entries(selectedAddress).length === 0) return
- this.selectedAddress = selectedAddress
- })
- parentEpml.subscribe('config', c => {
- this.config = JSON.parse(c)
- if (!configLoaded) {
- authorizeAndRender()
- setTimeout(getFollowedNames, 1)
- setTimeout(getBlockedNames, 1)
- configLoaded = true
- }
- })
- parentEpml.subscribe('copy_menu_switch', async value => {
-
- if (value === 'false' && window.getSelection().toString().length !== 0) {
-
- this.clearSelection()
- }
- })
- })
- }
-
- render() {
- return html`
-
-
-
- this.goBack()} title="${translate("general.back")}" class="address-bar-button">arrow_back_ios
- this.goForward()} title="${translate("browserpage.bchange1")}" class="address-bar-button">arrow_forward_ios
- this.refresh()} title="${translate("browserpage.bchange2")}" class="address-bar-button">refresh
- this.goBackToList()} title="${translate("browserpage.bchange3")}" class="address-bar-button">home
-
- this.delete()} title="${translate("browserpage.bchange4")} ${this.service} ${this.name} ${translate("browserpage.bchange5")}" class="address-bar-button float-right">delete
- ${this.renderBlockUnblockButton()}
- ${this.renderFollowUnfollowButton()}
-
-
-
-
-
-
- `
- }
-
- firstUpdated() {
-
- this.changeTheme()
- this.changeLanguage()
-
- window.addEventListener('contextmenu', (event) => {
- event.preventDefault()
- this._textMenu(event)
- })
-
- window.addEventListener('click', () => {
- parentEpml.request('closeCopyTextMenu', null)
- })
-
- 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)
- })
-
- window.onkeyup = (e) => {
- if (e.keyCode === 27) {
- parentEpml.request('closeCopyTextMenu', null)
- }
- }
-
- window.addEventListener("message", (event) => {
- if (event == null || event.data == null || event.data.length == 0 || event.data.action == null) {
- return;
- }
-
- let response = "{\"error\": \"Request could not be fulfilled\"}";
- let data = event.data;
- console.log("UI received event: " + JSON.stringify(data));
-
- switch (data.action) {
- case "GET_USER_ACCOUNT":
- // For now, we will return this without prompting the user, but we may need to add a prompt later
- let account = {};
- account["address"] = this.selectedAddress.address;
- account["publicKey"] = this.selectedAddress.base58PublicKey;
- response = JSON.stringify(account);
- break;
-
- case "LINK_TO_QDN_RESOURCE":
- case "QDN_RESOURCE_DISPLAYED":
- // Links are handled by the core, but the UI also listens for these actions in order to update the address bar.
- // Note: don't update this.url here, as we don't want to force reload the iframe each time.
- let url = "qortal://" + data.service + "/" + data.name;
- this.path = data.path != null ? ((data.path.startsWith("/") ? "" : "/") + data.path) : null;
- if (data.identifier != null && data.identifier != "" && data.identifier != "default") url = url.concat("/" + data.identifier);
- if (this.path != null && this.path != "/") url = url.concat(this.path);
- this.name = data.name;
- this.service = data.service;
- this.identifier = data.identifier;
- this.displayUrl = url;
- return;
-
- case "PUBLISH_QDN_RESOURCE":
- // Use "default" if user hasn't specified an identifer
- if (data.identifier == null) {
- data.identifier = "default";
- }
-
- // Params: data.service, data.name, data.identifier, data.data64,
- // TODO: prompt user for publish. If they confirm, call `POST /arbitrary/{service}/{name}/{identifier}/base64` and sign+process transaction
- // then set the response string from the core to the `response` variable (defined above)
- // If they decline, send back JSON that includes an `error` key, such as `{"error": "User declined request"}`
- break;
-
- case "SEND_CHAT_MESSAGE":
- // Params: data.groupId, data.destinationAddress, data.message
- // TODO: prompt user to send chat message. If they confirm, sign+process a CHAT transaction
- // then set the response string from the core to the `response` variable (defined above)
- // If they decline, send back JSON that includes an `error` key, such as `{"error": "User declined request"}`
- break;
-
- case "JOIN_GROUP":
- // Params: data.groupId
- // TODO: prompt user to join group. If they confirm, sign+process a JOIN_GROUP transaction
- // then set the response string from the core to the `response` variable (defined above)
- // If they decline, send back JSON that includes an `error` key, such as `{"error": "User declined request"}`
- break;
-
- case "DEPLOY_AT":
- // Params: data.creationBytes, data.name, data.description, data.type, data.tags, data.amount, data.assetId, data.fee
- // TODO: prompt user to deploy an AT. If they confirm, sign+process a DEPLOY_AT transaction
- // then set the response string from the core to the `response` variable (defined above)
- // If they decline, send back JSON that includes an `error` key, such as `{"error": "User declined request"}`
- break;
-
- case "GET_WALLET_BALANCE":
- // Params: data.coin (QORT / LTC / DOGE / DGB / RVN / ARRR)
- // TODO: prompt user to share wallet balance. If they confirm, call `GET /crosschain/:coin/walletbalance`, or for QORT, call `GET /addresses/balance/:address`
- // then set the response string from the core to the `response` variable (defined above)
- // If they decline, send back JSON that includes an `error` key, such as `{"error": "User declined request"}`
- break;
-
- case "SEND_COIN":
- // Params: data.coin, data.destinationAddress, data.amount, data.fee
- // TODO: prompt user to send. If they confirm, call `POST /crosschain/:coin/send`, or for QORT, broadcast a PAYMENT transaction
- // then set the response string from the core to the `response` variable (defined above)
- // If they decline, send back JSON that includes an `error` key, such as `{"error": "User declined request"}`
- break;
-
- default:
- console.log("Unhandled message: " + JSON.stringify(data));
- return;
- }
-
-
- // Parse response
- let responseObj;
- try {
- responseObj = JSON.parse(response);
- } catch (e) {
- // Not all responses will be JSON
- responseObj = response;
- }
-
- // Respond to app
- if (responseObj.error != null) {
- event.ports[0].postMessage({
- result: null,
- error: responseObj
- });
- }
- else {
- event.ports[0].postMessage({
- result: responseObj,
- error: null
- });
- }
-
- });
- }
-
- changeTheme() {
- const checkTheme = localStorage.getItem('qortalTheme')
- 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)
- }
- }
-
- renderFollowUnfollowButton() {
- // Only show the follow/unfollow button if we have permission to modify the list on this node
- if (this.followedNames == null || !Array.isArray(this.followedNames)) {
- return html``
- }
-
- if (this.followedNames.indexOf(this.name) === -1) {
- // render follow button
- return html` this.follow()} title="${translate("browserpage.bchange7")} ${this.name}" class="address-bar-button float-right">add_to_queue`
- }
- else {
- // render unfollow button
- return html` this.unfollow()} title="${translate("browserpage.bchange8")} ${this.name}" class="address-bar-button float-right">remove_from_queue`
- }
- }
-
- renderBlockUnblockButton() {
- // Only show the block/unblock button if we have permission to modify the list on this node
- if (this.blockedNames == null || !Array.isArray(this.blockedNames)) {
- return html``
- }
-
- if (this.blockedNames.indexOf(this.name) === -1) {
- // render block button
- return html` this.block()} title="${translate("browserpage.bchange9")} ${this.name}" class="address-bar-button float-right">block`
- }
- else {
- // render unblock button
- return html` this.unblock()} title="${translate("browserpage.bchange10")} ${this.name}" class="address-bar-button float-right">radio_button_unchecked`
- }
- }
-
-
- // Navigation
-
- goBack() {
- window.history.back();
- }
-
- goForward() {
- window.history.forward();
- }
-
- refresh() {
- const myNode = window.parent.reduxStore.getState().app.nodeConfig.knownNodes[window.parent.reduxStore.getState().app.nodeConfig.node]
- const nodeUrl = myNode.protocol + '://' + myNode.domain + ':' + myNode.port
- this.url = `${nodeUrl}/render/${this.service}/${this.name}${this.path != null ? this.path : ""}?theme=${this.theme}&identifier=${this.identifier != null ? this.identifier : ""}`;
- }
-
- goBackToList() {
- window.location = "../index.html";
- }
-
- follow() {
- this.followName(this.name);
- }
-
- unfollow() {
- this.unfollowName(this.name);
- }
-
- block() {
- this.blockName(this.name);
- }
-
- unblock() {
- this.unblockName(this.name);
- }
-
- delete() {
- this.deleteCurrentResource();
- }
-
-
- async followName(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) {
- // Successfully followed - add to local list
- // Remove it first by filtering the list - doing it this way ensures the UI updates
- // immediately, as apposed to only adding if it doesn't already exist
- this.followedNames = this.followedNames.filter(item => item != name);
- this.followedNames.push(name)
- }
- else {
- let err1string = get("browserpage.bchange11")
- parentEpml.request('showSnackBar', `${err1string}`)
- }
-
- return ret
- }
-
- async unfollowName(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) {
- // Successfully unfollowed - remove from local list
- this.followedNames = this.followedNames.filter(item => item != name);
- }
- else {
- let err2string = get("browserpage.bchange12")
- parentEpml.request('showSnackBar', `${err2string}`)
- }
-
- return ret
- }
-
- async blockName(name) {
- let items = [
- name
- ]
- let namesJsonString = JSON.stringify({ "items": items })
-
- let ret = await parentEpml.request('apiCall', {
- url: `/lists/blockedNames?apiKey=${this.getApiKey()}`,
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json'
- },
- body: `${namesJsonString}`
- })
-
- if (ret === true) {
- // Successfully blocked - add to local list
- // Remove it first by filtering the list - doing it this way ensures the UI updates
- // immediately, as apposed to only adding if it doesn't already exist
- this.blockedNames = this.blockedNames.filter(item => item != name);
- this.blockedNames.push(name)
- }
- else {
- let err3string = get("browserpage.bchange13")
- parentEpml.request('showSnackBar', `${err3string}`)
- }
-
- return ret
- }
-
- async unblockName(name) {
- let items = [
- name
- ]
- let namesJsonString = JSON.stringify({ "items": items })
-
- let ret = await parentEpml.request('apiCall', {
- url: `/lists/blockedNames?apiKey=${this.getApiKey()}`,
- method: 'DELETE',
- headers: {
- 'Content-Type': 'application/json'
- },
- body: `${namesJsonString}`
- })
-
- if (ret === true) {
- // Successfully unblocked - remove from local list
- this.blockedNames = this.blockedNames.filter(item => item != name);
- }
- else {
- let err4string = get("browserpage.bchange14")
- parentEpml.request('showSnackBar', `${err4string}`)
- }
-
- return ret
- }
-
- async deleteCurrentResource() {
- if (this.followedNames.indexOf(this.name) != -1) {
- // Following name - so deleting won't work
- let err5string = get("browserpage.bchange15")
- parentEpml.request('showSnackBar', `${err5string}`)
- return;
- }
-
- let identifier = this.identifier == null ? "default" : resource.identifier;
-
- let ret = await parentEpml.request('apiCall', {
- url: `/arbitrary/resource/${this.service}/${this.name}/${identifier}?apiKey=${this.getApiKey()}`,
- method: 'DELETE'
- })
-
- if (ret === true) {
- this.goBackToList();
- }
- else {
- let err6string = get("browserpage.bchange16")
- parentEpml.request('showSnackBar', `${err6string}`)
- }
-
- return ret
- }
-
- _textMenu(event) {
- const getSelectedText = () => {
- var text = ''
- if (typeof window.getSelection != 'undefined') {
- text = window.getSelection().toString()
- } else if (typeof this.shadowRoot.selection != 'undefined' && this.shadowRoot.selection.type == 'Text') {
- text = this.shadowRoot.selection.createRange().text
- }
- return text
- }
-
- const checkSelectedTextAndShowMenu = () => {
- let selectedText = getSelectedText()
- if (selectedText && typeof selectedText === 'string') {
- let _eve = { pageX: event.pageX, pageY: event.pageY, clientX: event.clientX, clientY: event.clientY }
- let textMenuObject = { selectedText: selectedText, eventObject: _eve, isFrame: true }
- parentEpml.request('openCopyTextMenu', textMenuObject)
- }
- }
- checkSelectedTextAndShowMenu()
- }
-
- getApiKey() {
- const myNode = window.parent.reduxStore.getState().app.nodeConfig.knownNodes[window.parent.reduxStore.getState().app.nodeConfig.node];
- let apiKey = myNode.apiKey;
- return apiKey;
- }
-
- clearSelection() {
- window.getSelection().removeAllRanges()
- window.parent.getSelection().removeAllRanges()
- }
-}
-
-window.customElements.define('app-browser', AppBrowser)
diff --git a/qortal-ui-plugins/plugins/core/q-app/app-browser/index.html b/qortal-ui-plugins/plugins/core/q-app/app-browser/index.html
deleted file mode 100644
index 03cf4220..00000000
--- a/qortal-ui-plugins/plugins/core/q-app/app-browser/index.html
+++ /dev/null
@@ -1,55 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-