mirror of https://github.com/qortal/qortal-ui
AlphaX-Projects
2 years ago
committed by
GitHub
6 changed files with 2502 additions and 0 deletions
@ -0,0 +1,626 @@
|
||||
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` |
||||
<div id="websitesWrapper" style="width:auto; padding:10px; background: var(--white);"> |
||||
<div class="layout horizontal center"> |
||||
<div class="address-bar"> |
||||
<mwc-button @click=${() => this.goBack()} title="${translate("general.back")}" class="address-bar-button"><mwc-icon>arrow_back_ios</mwc-icon></mwc-button> |
||||
<mwc-button @click=${() => this.goForward()} title="${translate("browserpage.bchange1")}" class="address-bar-button"><mwc-icon>arrow_forward_ios</mwc-icon></mwc-button> |
||||
<mwc-button @click=${() => this.refresh()} title="${translate("browserpage.bchange2")}" class="address-bar-button"><mwc-icon>refresh</mwc-icon></mwc-button> |
||||
<mwc-button @click=${() => this.goBackToList()} title="${translate("browserpage.bchange3")}" class="address-bar-button"><mwc-icon>home</mwc-icon></mwc-button> |
||||
<input disabled style="width: 550px; color: var(--black);" id="address" type="text" value="${this.displayUrl}"></input> |
||||
<mwc-button @click=${() => this.delete()} title="${translate("browserpage.bchange4")} ${this.service} ${this.name} ${translate("browserpage.bchange5")}" class="address-bar-button float-right"><mwc-icon>delete</mwc-icon></mwc-button> |
||||
${this.renderBlockUnblockButton()} |
||||
${this.renderFollowUnfollowButton()} |
||||
</div> |
||||
<div class="iframe-container"> |
||||
<iframe id="browser-iframe" src="${this.url}" sandbox="allow-scripts allow-forms allow-downloads"> |
||||
<span style="color: var(--black);">${translate("browserpage.bchange6")}</span> |
||||
</iframe> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
` |
||||
} |
||||
|
||||
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`<mwc-button @click=${() => this.follow()} title="${translate("browserpage.bchange7")} ${this.name}" class="address-bar-button float-right"><mwc-icon>add_to_queue</mwc-icon></mwc-button>` |
||||
} |
||||
else { |
||||
// render unfollow button
|
||||
return html`<mwc-button @click=${() => this.unfollow()} title="${translate("browserpage.bchange8")} ${this.name}" class="address-bar-button float-right"><mwc-icon>remove_from_queue</mwc-icon></mwc-button>` |
||||
} |
||||
} |
||||
|
||||
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`<mwc-button @click=${() => this.block()} title="${translate("browserpage.bchange9")} ${this.name}" class="address-bar-button float-right"><mwc-icon>block</mwc-icon></mwc-button>` |
||||
} |
||||
else { |
||||
// render unblock button
|
||||
return html`<mwc-button @click=${() => this.unblock()} title="${translate("browserpage.bchange10")} ${this.name}" class="address-bar-button float-right"><mwc-icon>radio_button_unchecked</mwc-icon></mwc-button>` |
||||
} |
||||
} |
||||
|
||||
|
||||
// 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) |
@ -0,0 +1,55 @@
|
||||
<!DOCTYPE html> |
||||
<html> |
||||
|
||||
<head> |
||||
<link rel="stylesheet" href="/font/material-icons.css"> |
||||
<link rel="stylesheet" href="/font/switch-theme.css"> |
||||
<script> |
||||
const checkBack = localStorage.getItem('qortalTheme') |
||||
if (checkBack === 'dark') { |
||||
newtheme = 'dark'; |
||||
} else { |
||||
newtheme = 'light'; |
||||
} |
||||
document.querySelector('html').setAttribute('theme', newtheme); |
||||
</script> |
||||
<style> |
||||
html { |
||||
--scrollbarBG: #a1a1a1; |
||||
--thumbBG: #6a6c75; |
||||
} |
||||
|
||||
*::-webkit-scrollbar { |
||||
width: 11px; |
||||
} |
||||
|
||||
* { |
||||
scrollbar-width: thin; |
||||
scrollbar-color: var(--thumbBG) var(--scrollbarBG); |
||||
} |
||||
|
||||
*::-webkit-scrollbar-track { |
||||
background: var(--scrollbarBG); |
||||
} |
||||
|
||||
*::-webkit-scrollbar-thumb { |
||||
background-color: var(--thumbBG); |
||||
border-radius: 6px; |
||||
border: 3px solid var(--scrollbarBG); |
||||
} |
||||
|
||||
html, |
||||
body { |
||||
margin: 0; |
||||
font-family: "Roboto", sans-serif; |
||||
background: var(--plugback); |
||||
} |
||||
</style> |
||||
</head> |
||||
|
||||
<body> |
||||
<app-browser></app-browser> |
||||
<script src="app-browser.js"></script> |
||||
</body> |
||||
|
||||
</html> |
@ -0,0 +1,55 @@
|
||||
<!DOCTYPE html> |
||||
<html> |
||||
|
||||
<head> |
||||
<link rel="stylesheet" href="/font/material-icons.css"> |
||||
<link rel="stylesheet" href="/font/switch-theme.css"> |
||||
<script> |
||||
const checkBack = localStorage.getItem('qortalTheme') |
||||
if (checkBack === 'dark') { |
||||
newtheme = 'dark'; |
||||
} else { |
||||
newtheme = 'light'; |
||||
} |
||||
document.querySelector('html').setAttribute('theme', newtheme); |
||||
</script> |
||||
<style> |
||||
html { |
||||
--scrollbarBG: #a1a1a1; |
||||
--thumbBG: #6a6c75; |
||||
} |
||||
|
||||
*::-webkit-scrollbar { |
||||
width: 11px; |
||||
} |
||||
|
||||
* { |
||||
scrollbar-width: thin; |
||||
scrollbar-color: var(--thumbBG) var(--scrollbarBG); |
||||
} |
||||
|
||||
*::-webkit-scrollbar-track { |
||||
background: var(--scrollbarBG); |
||||
} |
||||
|
||||
*::-webkit-scrollbar-thumb { |
||||
background-color: var(--thumbBG); |
||||
border-radius: 6px; |
||||
border: 3px solid var(--scrollbarBG); |
||||
} |
||||
|
||||
html, |
||||
body { |
||||
margin: 0; |
||||
font-family: "Roboto", sans-serif; |
||||
background: var(--plugback); |
||||
} |
||||
</style> |
||||
</head> |
||||
|
||||
<body> |
||||
<q-apps></q-apps> |
||||
<script src="q-apps.js"></script> |
||||
</body> |
||||
|
||||
</html> |
@ -0,0 +1,55 @@
|
||||
<!DOCTYPE html> |
||||
<html> |
||||
|
||||
<head> |
||||
<link rel="stylesheet" href="/font/material-icons.css"> |
||||
<link rel="stylesheet" href="/font/switch-theme.css"> |
||||
<script> |
||||
const checkBack = localStorage.getItem('qortalTheme') |
||||
if (checkBack === 'dark') { |
||||
newtheme = 'dark'; |
||||
} else { |
||||
newtheme = 'light'; |
||||
} |
||||
document.querySelector('html').setAttribute('theme', newtheme); |
||||
</script> |
||||
<style> |
||||
html { |
||||
--scrollbarBG: #a1a1a1; |
||||
--thumbBG: #6a6c75; |
||||
} |
||||
|
||||
*::-webkit-scrollbar { |
||||
width: 11px; |
||||
} |
||||
|
||||
* { |
||||
scrollbar-width: thin; |
||||
scrollbar-color: var(--thumbBG) var(--scrollbarBG); |
||||
} |
||||
|
||||
*::-webkit-scrollbar-track { |
||||
background: var(--scrollbarBG); |
||||
} |
||||
|
||||
*::-webkit-scrollbar-thumb { |
||||
background-color: var(--thumbBG); |
||||
border-radius: 6px; |
||||
border: 3px solid var(--scrollbarBG); |
||||
} |
||||
|
||||
html, |
||||
body { |
||||
margin: 0; |
||||
font-family: "Roboto", sans-serif; |
||||
background: var(--plugback); |
||||
} |
||||
</style> |
||||
</head> |
||||
|
||||
<body> |
||||
<publish-app></publish-app> |
||||
<script src="publish-app.js"></script> |
||||
</body> |
||||
|
||||
</html> |
@ -0,0 +1,670 @@
|
||||
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-textfield' |
||||
import '@material/mwc-select' |
||||
import '@material/mwc-list/mwc-list-item.js' |
||||
import '@polymer/paper-progress/paper-progress.js' |
||||
|
||||
const parentEpml = new Epml({ type: 'WINDOW', source: window.parent }) |
||||
|
||||
class PublishApp extends LitElement { |
||||
static get properties() { |
||||
return { |
||||
name: { type: String }, |
||||
service: { type: String }, |
||||
identifier: { type: String }, |
||||
category: { type: String }, |
||||
uploadType: { type: String }, |
||||
showName: { type: Boolean }, |
||||
showService: { type: Boolean }, |
||||
showIdentifier: { type: Boolean }, |
||||
showMetadata: { type: Boolean }, |
||||
tags: { type: Array }, |
||||
serviceLowercase: { type: String }, |
||||
metadata: { type: Array }, |
||||
categories: { type: Array }, |
||||
names: { type: Array }, |
||||
myRegisteredName: { type: String }, |
||||
selectedName: { type: String }, |
||||
path: { type: String }, |
||||
portForwardingEnabled: { type: Boolean }, |
||||
amount: { type: Number }, |
||||
generalMessage: { type: String }, |
||||
successMessage: { type: String }, |
||||
errorMessage: { type: String }, |
||||
loading: { type: Boolean }, |
||||
btnDisable: { type: Boolean }, |
||||
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); |
||||
--lumo-primary-text-color: rgb(0, 167, 245); |
||||
--lumo-primary-color-50pct: rgba(0, 167, 245, 0.5); |
||||
--lumo-primary-color-10pct: rgba(0, 167, 245, 0.1); |
||||
--lumo-primary-color: hsl(199, 100%, 48%); |
||||
--lumo-base-color: var(--white); |
||||
--lumo-body-text-color: var(--black); |
||||
--lumo-secondary-text-color: var(--sectxt); |
||||
--lumo-contrast-60pct: var(--vdicon); |
||||
--_lumo-grid-border-color: var(--border); |
||||
--_lumo-grid-secondary-border-color: var(--border2); |
||||
} |
||||
|
||||
|
||||
input[type=text] { |
||||
padding: 6px 6px 6px 6px; |
||||
color: var(--black); |
||||
} |
||||
|
||||
input[type=file]::file-selector-button { |
||||
border: 1px solid transparent; |
||||
padding: 6px 6px 6px 6px; |
||||
border-radius: 5px; |
||||
color: #fff; |
||||
background-color: var(--mdc-theme-primary); |
||||
transition: 1s; |
||||
} |
||||
|
||||
input[type=file]::file-selector-button:hover { |
||||
color: #000; |
||||
background-color: #81ecec; |
||||
border: 1px solid transparent; |
||||
} |
||||
|
||||
#publishWrapper paper-button { |
||||
float: right; |
||||
} |
||||
|
||||
#publishWrapper .buttons { |
||||
width: auto !important; |
||||
} |
||||
|
||||
mwc-textfield { |
||||
margin: 0; |
||||
} |
||||
|
||||
paper-progress { |
||||
--paper-progress-active-color: var(--mdc-theme-primary); |
||||
} |
||||
|
||||
.upload-text { |
||||
display: block; |
||||
font-size: 14px; |
||||
color: var(--black); |
||||
} |
||||
|
||||
.address-bar { |
||||
position: absolute; |
||||
top: 0; |
||||
left: 0; |
||||
right: 0; |
||||
height: 100px; |
||||
background-color: var(--white); |
||||
height: 36px; |
||||
} |
||||
|
||||
.address-bar-button mwc-icon { |
||||
width: 30px; |
||||
} |
||||
` |
||||
} |
||||
|
||||
constructor() { |
||||
super() |
||||
|
||||
this.showName = false; |
||||
this.showService = false |
||||
this.showIdentifier = false |
||||
this.showMetadata = false |
||||
|
||||
const urlParams = new URLSearchParams(window.location.search) |
||||
this.name = urlParams.get('name') |
||||
this.service = urlParams.get('service') |
||||
this.identifier = urlParams.get('identifier') |
||||
this.category = urlParams.get('category') |
||||
this.uploadType = urlParams.get('uploadType') !== "null" ? urlParams.get('uploadType') : "file" |
||||
|
||||
if (urlParams.get('showName') === "true") { |
||||
this.showName = true |
||||
} |
||||
|
||||
if (urlParams.get('showService') === "true") { |
||||
this.showService = true |
||||
} |
||||
|
||||
if (urlParams.get('showIdentifier') === "true") { |
||||
this.showIdentifier = true |
||||
} |
||||
|
||||
if (urlParams.get('showMetadata') === "true") { |
||||
this.showMetadata = true |
||||
} |
||||
|
||||
if (this.identifier != null) { |
||||
if (this.identifier === "null" || this.identifier.trim().length == 0) { |
||||
this.identifier = null |
||||
} |
||||
} |
||||
|
||||
// Default to true so the message doesn't appear and disappear quickly
|
||||
this.portForwardingEnabled = true |
||||
this.names = [] |
||||
this.myRegisteredName = '' |
||||
this.selectedName = 'invalid' |
||||
this.path = '' |
||||
this.successMessage = '' |
||||
this.generalMessage = '' |
||||
this.errorMessage = '' |
||||
this.loading = false |
||||
this.btnDisable = false |
||||
this.theme = localStorage.getItem('qortalTheme') ? localStorage.getItem('qortalTheme') : 'light' |
||||
|
||||
const fetchNames = () => { |
||||
parentEpml.request('apiCall', {url: `/names/address/${this.selectedAddress.address}?limit=0&reverse=true`}).then(res => { |
||||
setTimeout(() => { |
||||
this.names = res |
||||
if (res[0] != null) { |
||||
this.myRegisteredName = res[0].name; |
||||
} |
||||
}, 1) |
||||
}) |
||||
setTimeout(fetchNames, this.config.user.nodeSettings.pingInterval) |
||||
} |
||||
|
||||
const fetchCategories = () => { |
||||
parentEpml.request('apiCall', {url: `/arbitrary/categories`}).then(res => { |
||||
setTimeout(() => { |
||||
this.categories = res |
||||
}, 1) |
||||
}) |
||||
setTimeout(fetchCategories, this.config.user.nodeSettings.pingInterval) |
||||
} |
||||
|
||||
const fetchPeersSummary = () => { |
||||
parentEpml.request('apiCall', {url: `/peers/summary`}).then(res => { |
||||
setTimeout(() => { |
||||
this.portForwardingEnabled = (res.inboundConnections != null && res.inboundConnections > 0); |
||||
}, 1) |
||||
}) |
||||
setTimeout(fetchPeersSummary, this.config.user.nodeSettings.pingInterval) |
||||
} |
||||
|
||||
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 => { |
||||
if (!configLoaded) { |
||||
setTimeout(fetchNames, 1) |
||||
setTimeout(fetchCategories, 1) |
||||
setTimeout(fetchPeersSummary, 1) |
||||
configLoaded = true |
||||
} |
||||
this.config = JSON.parse(c) |
||||
}) |
||||
|
||||
parentEpml.subscribe('copy_menu_switch', async value => { |
||||
if (value === 'false' && window.getSelection().toString().length !== 0) { |
||||
this.clearSelection() |
||||
} |
||||
}) |
||||
}) |
||||
} |
||||
|
||||
render() { |
||||
return html` |
||||
<div id="publishWrapper" style="width: auto; padding:10px; background: var(--white); height: 100vh;"> |
||||
<div class="layout horizontal center" style=" padding:12px 15px;"> |
||||
<div class="address-bar"> |
||||
<mwc-button @click=${() => this.goBack()} class="address-bar-button"><mwc-icon>arrow_back_ios</mwc-icon> ${translate("general.back")}</mwc-button> |
||||
</div> |
||||
<paper-card style="width:100%; max-width:740px;"> |
||||
<div style="margin:0; margin-top:20px;"> |
||||
<h3 style="margin:0; padding:8px 0; text-transform: capitalize; color: var(--black);">${translate("publishpage.pchange1")} / ${translate("publishpage.pchange2")} Q-App</h3> |
||||
<p style="font-style: italic; font-size: 14px; color: var(--black);" ?hidden="${this.portForwardingEnabled}">${translate("publishpage.pchange3")}</p> |
||||
</div> |
||||
</paper-card> |
||||
<!-- TODO: adapt this dropdown to list all names on the account. Right now it's hardcoded to a single name --> |
||||
<p style="display: ${this.showName ? 'block' : 'none'}"> |
||||
<mwc-select id="registeredName" label="${translate("publishpage.pchange4")}" @selected=${(e) => this.selectName(e)} style="min-width: 130px; max-width:100%; width:100%;"> |
||||
<mwc-list-item selected value=""></mwc-list-item> |
||||
<mwc-list-item value="${this.myRegisteredName}">${this.myRegisteredName}</mwc-list-item> |
||||
</mwc-select> |
||||
</p> |
||||
<div style="display: ${this.showMetadata ? 'block' : 'none'}"> |
||||
<p> |
||||
<mwc-textfield style="width:100%;" label="${translate("publishpage.pchange5")}" id="title" type="text" value="${this.metadata != null && this.metadata.title != null ? this.metadata.title : ''}" maxLength="80"></mwc-textfield><!--charCounter="true"--> |
||||
</p> |
||||
<p> |
||||
<mwc-textfield style="width:100%;" label="${translate("publishpage.pchange6")}" id="description" type="text" value="${this.metadata != null && this.metadata.description != null ? this.metadata.description : ''}" maxLength="500"></mwc-textfield><!--charCounter="true"--> |
||||
</p> |
||||
<p> |
||||
<mwc-select id="category" label="${translate("publishpage.pchange7")}" index="0" style="min-width: 130px; max-width:100%; width:100%;"> |
||||
${this.categories.map((c, index) => html` |
||||
<mwc-list-item value="${c.id}">${c.name}</mwc-list-item> |
||||
`)}
|
||||
</mwc-select> |
||||
</p> |
||||
<p> |
||||
<mwc-textfield style="width:19.85%;" id="tag1" type="text" value="${this.metadata != null && this.metadata.tags != null && this.metadata.tags[0] != null ? this.metadata.tags[0] : ''}" placeholder="${translate("publishpage.pchange8")} 1" maxLength="20"></mwc-textfield> |
||||
<mwc-textfield style="width:19.85%;" id="tag2" type="text" value="${this.metadata != null && this.metadata.tags != null && this.metadata.tags[1] != null ? this.metadata.tags[1] : ''}" placeholder="${translate("publishpage.pchange8")} 2" maxLength="20"></mwc-textfield> |
||||
<mwc-textfield style="width:19.85%;" id="tag3" type="text" value="${this.metadata != null && this.metadata.tags != null && this.metadata.tags[2] != null ? this.metadata.tags[2] : ''}" placeholder="${translate("publishpage.pchange8")} 3" maxLength="20"></mwc-textfield> |
||||
<mwc-textfield style="width:19.85%;" id="tag4" type="text" value="${this.metadata != null && this.metadata.tags != null && this.metadata.tags[3] != null ? this.metadata.tags[3] : ''}" placeholder="${translate("publishpage.pchange8")} 4" maxLength="20"></mwc-textfield> |
||||
<mwc-textfield style="width:19.85%;" id="tag5" type="text" value="${this.metadata != null && this.metadata.tags != null && this.metadata.tags[4] != null ? this.metadata.tags[4] : ''}" placeholder="${translate("publishpage.pchange8")} 5" maxLength="20"></mwc-textfield> |
||||
</p> |
||||
</div> |
||||
${this.renderUploadField()} |
||||
<p style="display: ${this.showService ? 'block' : 'none'}"> |
||||
<mwc-textfield style="width:100%;" label="${translate("publishpage.pchange9")}" id="service" type="text" value="${this.service}"></mwc-textfield> |
||||
</p> |
||||
<p style="display: ${this.showIdentifier ? 'block' : 'none'}"> |
||||
<mwc-textfield style="width:100%;" label="${translate("publishpage.pchange10")}" id="identifier" type="text" value="${this.identifier != null ? this.identifier : ''}"></mwc-textfield> |
||||
</p> |
||||
<p style="break-word; color: var(--black);">${this.generalMessage}</p> |
||||
<p style="color:red">${this.errorMessage}</p> |
||||
<p style="color: green; word-break: break-word;">${this.successMessage}</p> |
||||
${this.loading ? html` <paper-progress indeterminate style="width:100%; margin:4px;"></paper-progress> ` : ''} |
||||
<div class="buttons"> |
||||
<div> |
||||
<mwc-button ?disabled=${this.btnDisable} style="width:100%;" raised icon="send" @click=${(e) => this.doPublish(e)}> ${translate("publishpage.pchange11")}</mwc-button> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
` |
||||
} |
||||
|
||||
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) |
||||
} |
||||
} |
||||
} |
||||
|
||||
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) |
||||
} |
||||
} |
||||
|
||||
// Navigation
|
||||
goBack() { |
||||
window.history.back(); |
||||
} |
||||
|
||||
|
||||
renderUploadField() { |
||||
if (this.uploadType === "file") { |
||||
return html` |
||||
<p> |
||||
<input style="width: 100%; background: var(--white); color: var(--black)" id="file" type="file"> |
||||
</p> |
||||
` |
||||
} |
||||
else if (this.uploadType === "zip") { |
||||
return html` |
||||
<p> |
||||
<span class="upload-text">${translate("publishpage.pchange12")}:</span><br /> |
||||
<input style="color: var(--black)" id="file" type="file" accept=".zip"> |
||||
</p> |
||||
` |
||||
} |
||||
else { |
||||
return html` |
||||
<p> |
||||
<mwc-textfield style="width:100%;" label="${translate("publishpage.pchange13")}" id="path" type="text" value="${this.path}"></mwc-textfield> |
||||
</p> |
||||
` |
||||
} |
||||
} |
||||
|
||||
|
||||
doPublish(e) { |
||||
let registeredName = this.shadowRoot.getElementById('registeredName').value |
||||
let service = this.shadowRoot.getElementById('service').value |
||||
let identifier = this.shadowRoot.getElementById('identifier').value |
||||
|
||||
// If name is hidden, use the value passed in via the name parameter
|
||||
if (!this.showName) { |
||||
registeredName = this.name |
||||
} |
||||
|
||||
let file; |
||||
let path; |
||||
|
||||
if (this.uploadType === "file" || this.uploadType === "zip") { |
||||
file = this.shadowRoot.getElementById('file').files[0] |
||||
} |
||||
else if (this.uploadType === "path") { |
||||
path = this.shadowRoot.getElementById('path').value |
||||
} |
||||
|
||||
this.generalMessage = '' |
||||
this.successMessage = '' |
||||
this.errorMessage = '' |
||||
|
||||
if (registeredName === '') { |
||||
this.showName = true |
||||
let err1string = get("publishpage.pchange14") |
||||
parentEpml.request('showSnackBar', `${err1string}`) |
||||
} |
||||
else if (this.uploadType === "file" && file == null) { |
||||
let err2string = get("publishpage.pchange15") |
||||
parentEpml.request('showSnackBar', `${err2string}`) |
||||
} |
||||
else if (this.uploadType === "zip" && file == null) { |
||||
let err3string = get("publishpage.pchange16") |
||||
parentEpml.request('showSnackBar', `${err3string}`) |
||||
} |
||||
else if (this.uploadType === "path" && path === '') { |
||||
let err4string = get("publishpage.pchange17") |
||||
parentEpml.request('showSnackBar', `${err4string}`) |
||||
} |
||||
else if (service === '') { |
||||
let err5string = get("publishpage.pchange18") |
||||
parentEpml.request('showSnackBar', `${err5string}`) |
||||
} |
||||
else { |
||||
this.publishData(registeredName, path, file, service, identifier) |
||||
} |
||||
} |
||||
|
||||
async publishData(registeredName, path, file, service, identifier) { |
||||
this.loading = true |
||||
this.btnDisable = true |
||||
|
||||
const validateName = async (receiverName) => { |
||||
let nameRes = await parentEpml.request('apiCall', { |
||||
type: 'api', |
||||
url: `/names/${receiverName}`, |
||||
}) |
||||
|
||||
return nameRes |
||||
} |
||||
|
||||
const showError = async (errorMessage) => { |
||||
this.loading = false |
||||
this.btnDisable = false |
||||
this.generalMessage = '' |
||||
this.successMessage = '' |
||||
console.error(errorMessage) |
||||
} |
||||
|
||||
const validate = async () => { |
||||
let validNameRes = await validateName(registeredName) |
||||
if (validNameRes.error) { |
||||
this.errorMessage = "Error: " + validNameRes.message |
||||
showError(this.errorMessage) |
||||
throw new Error(this.errorMessage); |
||||
} |
||||
|
||||
let err6string = get("publishpage.pchange19") |
||||
this.generalMessage = `${err6string}` |
||||
|
||||
let transactionBytes = await uploadData(registeredName, path, file) |
||||
if (transactionBytes.error) { |
||||
let err7string = get("publishpage.pchange20") |
||||
this.errorMessage = `${err7string}` + transactionBytes.message |
||||
showError(this.errorMessage) |
||||
throw new Error(this.errorMessage); |
||||
} |
||||
else if (transactionBytes.includes("Error 500 Internal Server Error")) { |
||||
let err8string = get("publishpage.pchange21") |
||||
this.errorMessage = `${err8string}` |
||||
showError(this.errorMessage) |
||||
throw new Error(this.errorMessage); |
||||
} |
||||
|
||||
let err9string = get("publishpage.pchange22") |
||||
this.generalMessage = `${err9string}` |
||||
|
||||
let signAndProcessRes = await signAndProcess(transactionBytes) |
||||
if (signAndProcessRes.error) { |
||||
let err10string = get("publishpage.pchange20") |
||||
this.errorMessage = `${err10string}` + signAndProcessRes.message |
||||
showError(this.errorMessage) |
||||
throw new Error(this.errorMessage); |
||||
} |
||||
|
||||
let err11string = get("publishpage.pchange23") |
||||
|
||||
this.btnDisable = false |
||||
this.loading = false |
||||
this.errorMessage = '' |
||||
this.generalMessage = '' |
||||
this.successMessage = `${err11string}` |
||||
} |
||||
|
||||
const uploadData = async (registeredName, path, file) => { |
||||
let postBody = path |
||||
let urlSuffix = "" |
||||
if (file != null) { |
||||
|
||||
// If we're sending zipped data, make sure to use the /zip version of the POST /arbitrary/* API
|
||||
if (this.uploadType === "zip") { |
||||
urlSuffix = "/zip" |
||||
} |
||||
// If we're sending file data, use the /base64 version of the POST /arbitrary/* API
|
||||
else if (this.uploadType === "file") { |
||||
urlSuffix = "/base64" |
||||
} |
||||
|
||||
// Base64 encode the file to work around compatibility issues between javascript and java byte arrays
|
||||
let fileBuffer = new Uint8Array(await file.arrayBuffer()) |
||||
postBody = Buffer.from(fileBuffer).toString('base64'); |
||||
} |
||||
|
||||
// Optional metadata
|
||||
let title = encodeURIComponent(this.shadowRoot.getElementById('title').value); |
||||
let description = encodeURIComponent(this.shadowRoot.getElementById('description').value); |
||||
let category = encodeURIComponent(this.shadowRoot.getElementById('category').value); |
||||
let tag1 = encodeURIComponent(this.shadowRoot.getElementById('tag1').value); |
||||
let tag2 = encodeURIComponent(this.shadowRoot.getElementById('tag2').value); |
||||
let tag3 = encodeURIComponent(this.shadowRoot.getElementById('tag3').value); |
||||
let tag4 = encodeURIComponent(this.shadowRoot.getElementById('tag4').value); |
||||
let tag5 = encodeURIComponent(this.shadowRoot.getElementById('tag5').value); |
||||
|
||||
let metadataQueryString = `title=${title}&description=${description}&category=${category}&tags=${tag1}&tags=${tag2}&tags=${tag3}&tags=${tag4}&tags=${tag5}` |
||||
|
||||
let uploadDataUrl = `/arbitrary/${this.service}/${registeredName}${urlSuffix}?${metadataQueryString}&apiKey=${this.getApiKey()}` |
||||
if (identifier != null && identifier.trim().length > 0) { |
||||
uploadDataUrl = `/arbitrary/${service}/${registeredName}/${this.identifier}${urlSuffix}?${metadataQueryString}&apiKey=${this.getApiKey()}` |
||||
} |
||||
|
||||
let uploadDataRes = await parentEpml.request('apiCall', { |
||||
type: 'api', |
||||
method: 'POST', |
||||
url: `${uploadDataUrl}`, |
||||
body: `${postBody}`, |
||||
}) |
||||
return uploadDataRes |
||||
} |
||||
|
||||
const convertBytesForSigning = async (transactionBytesBase58) => { |
||||
let convertedBytes = await parentEpml.request('apiCall', { |
||||
type: 'api', |
||||
method: 'POST', |
||||
url: `/transactions/convert`, |
||||
body: `${transactionBytesBase58}`, |
||||
}) |
||||
return convertedBytes |
||||
} |
||||
|
||||
const signAndProcess = async (transactionBytesBase58) => { |
||||
let convertedBytesBase58 = await convertBytesForSigning(transactionBytesBase58) |
||||
if (convertedBytesBase58.error) { |
||||
let err12string = get("publishpage.pchange20") |
||||
this.errorMessage = `${err12string}` + convertedBytesBase58.message |
||||
showError(this.errorMessage) |
||||
throw new Error(this.errorMessage); |
||||
} |
||||
|
||||
const convertedBytes = window.parent.Base58.decode(convertedBytesBase58); |
||||
const _convertedBytesArray = Object.keys(convertedBytes).map(function (key) { return convertedBytes[key]; }); |
||||
const convertedBytesArray = new Uint8Array(_convertedBytesArray) |
||||
const convertedBytesHash = new window.parent.Sha256().process(convertedBytesArray).finish().result |
||||
|
||||
const hashPtr = window.parent.sbrk(32, window.parent.heap); |
||||
const hashAry = new Uint8Array(window.parent.memory.buffer, hashPtr, 32); |
||||
hashAry.set(convertedBytesHash); |
||||
|
||||
const difficulty = 14; |
||||
const workBufferLength = 8 * 1024 * 1024; |
||||
const workBufferPtr = window.parent.sbrk(workBufferLength, window.parent.heap); |
||||
|
||||
this.errorMessage = ''; |
||||
this.successMessage = ''; |
||||
let nonce = window.parent.computePow(hashPtr, workBufferPtr, workBufferLength, difficulty) |
||||
|
||||
let response = await parentEpml.request('sign_arbitrary', { |
||||
nonce: this.selectedAddress.nonce, |
||||
arbitraryBytesBase58: transactionBytesBase58, |
||||
arbitraryBytesForSigningBase58: convertedBytesBase58, |
||||
arbitraryNonce: nonce |
||||
}) |
||||
|
||||
let myResponse = { error: '' } |
||||
if (response === false) { |
||||
let err13string = get("publishpage.pchange24") |
||||
myResponse.error = `${err13string}` |
||||
} |
||||
else { |
||||
myResponse = response |
||||
} |
||||
return myResponse |
||||
} |
||||
validate() |
||||
} |
||||
|
||||
_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() |
||||
} |
||||
|
||||
|
||||
fetchResourceMetadata() { |
||||
let identifier = this.identifier != null ? this.identifier : "default"; |
||||
|
||||
parentEpml.request('apiCall', { |
||||
url: `/arbitrary/metadata/${this.service}/${this.name}/${identifier}?apiKey=${this.getApiKey()}` |
||||
}).then(res => { |
||||
|
||||
setTimeout(() => { |
||||
this.metadata = res |
||||
if (this.metadata != null && this.metadata.category != null) { |
||||
this.shadowRoot.getElementById('category').value = this.metadata.category; |
||||
} |
||||
else { |
||||
this.shadowRoot.getElementById('category').value = ""; |
||||
} |
||||
}, 1) |
||||
}) |
||||
} |
||||
|
||||
selectName(e) { |
||||
let name = this.shadowRoot.getElementById('registeredName') |
||||
this.selectedName = (name.value) |
||||
// Update the current name if one has been selected
|
||||
if (name.value.length > 0) { |
||||
this.name = (name.value) |
||||
} |
||||
this.fetchResourceMetadata(); |
||||
} |
||||
|
||||
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('publish-app', PublishApp) |
Loading…
Reference in new issue