diff --git a/qortal-ui-plugins/build-config.js b/qortal-ui-plugins/build-config.js index 5d54f86d..63500fb3 100644 --- a/qortal-ui-plugins/build-config.js +++ b/qortal-ui-plugins/build-config.js @@ -91,10 +91,30 @@ const generateForPlugins = () => { in: "plugins/core/group-management/group-management.src.js", out: "plugins/core/group-management/group-management.js", }, + // { + // in: 'plugins/core/group-management/group-transaction/group-transaction.src.js', + // out: 'plugins/core/group-management/group-transaction/group-transaction.js' + // }, { in: "plugins/core/name-registration/name-registration.src.js", out: "plugins/core/name-registration/name-registration.js", }, + { + in: "plugins/core/qdn/websites.src.js", + out: "plugins/core/qdn/websites.js", + }, + { + in: "plugins/core/qdn/publish/publish.src.js", + out: "plugins/core/qdn/publish/publish.js", + }, + { + in: "plugins/core/qdn/browser/browser.src.js", + out: "plugins/core/qdn/browser/browser.js", + }, + { + in: "plugins/core/qdn/data-management/data-management.src.js", + out: "plugins/core/qdn/data-management/data-management.js", + }, { in: "plugins/core/messaging/messaging.src.js", out: "plugins/core/messaging/messaging.js", @@ -114,7 +134,7 @@ const generateForPlugins = () => { { in: "plugins/core/puzzles/puzzles.src.js", out: "plugins/core/puzzles/puzzles.js", - } + }, ].map((file) => { return generateRollupConfig( path.join(__dirname, file.in), diff --git a/qortal-ui-plugins/package.json b/qortal-ui-plugins/package.json index a2051a28..370d9435 100644 --- a/qortal-ui-plugins/package.json +++ b/qortal-ui-plugins/package.json @@ -11,18 +11,15 @@ "main": "default-plugins.js", "repository": { "type": "git", - "url": "https://github.com/Qortal/qortal-ui.git", + "url": "https://github.com/Qortal/UI.git", "directory": "qortal-ui-plugins" }, "author": "QORTAL ", - "license": { - "type": "GPL-3.0", - "url" : "https://opensource.org/licenses/GPL-3.0" - }, + "license": "GPL-3.0", "dependencies": { "@material/mwc-list": "^0.18.0", "@material/mwc-select": "^0.18.0", - "emoji-picker-js": "https://github.com/lotw7277/emoji-picker-js" + "emoji-picker-js": "https://github.com/Qortal/emoji-picker-js" }, "devDependencies": { "@babel/core": "^7.16.0", diff --git a/qortal-ui-plugins/plugins/core/components/ChatPage.js b/qortal-ui-plugins/plugins/core/components/ChatPage.js index fc238751..6c2d4567 100644 --- a/qortal-ui-plugins/plugins/core/components/ChatPage.js +++ b/qortal-ui-plugins/plugins/core/components/ChatPage.js @@ -80,6 +80,9 @@ class ChatPage extends LitElement { cursor: pointer; max-height: 40px; } + .float-left { + float: left; + } ` } @@ -219,6 +222,13 @@ class ChatPage extends LitElement { * @property sender and other info.. */ chatMessageTemplate(messageObj) { + let avatarImg = ''; + if (messageObj.senderName) { + const myNode = window.parent.reduxStore.getState().app.nodeConfig.knownNodes[window.parent.reduxStore.getState().app.nodeConfig.node]; + const nodeUrl = myNode.protocol + '://' + myNode.domain + ':' + myNode.port; + const avatarUrl = `${nodeUrl}/arbitrary/THUMBNAIL/${messageObj.senderName}/qortal_avatar?apiKey=${myNode.apiKey}`; + avatarImg = ``; + } return `
  • @@ -226,7 +236,8 @@ class ChatPage extends LitElement { ${messageObj.senderName ? messageObj.senderName : messageObj.sender} -
    ${this.emojiPicker.parse(escape(messageObj.decodedMessage))}
    +
    ${this.emojiPicker.parse(escape(messageObj.decodedMessage))}
    +
    ${avatarImg}
  • ` } diff --git a/qortal-ui-plugins/plugins/core/components/ChatScroller.js b/qortal-ui-plugins/plugins/core/components/ChatScroller.js index a0446f86..f445689d 100644 --- a/qortal-ui-plugins/plugins/core/components/ChatScroller.js +++ b/qortal-ui-plugins/plugins/core/components/ChatScroller.js @@ -123,6 +123,10 @@ class ChatScroller extends LitElement { text-align: right; } + .float-left { + float: left; + } + .float-right { float: right; } @@ -159,6 +163,13 @@ class ChatScroller extends LitElement { } chatMessageTemplate(messageObj) { + let avatarImg = ''; + if (messageObj.senderName) { + const myNode = window.parent.reduxStore.getState().app.nodeConfig.knownNodes[window.parent.reduxStore.getState().app.nodeConfig.node]; + const nodeUrl = myNode.protocol + '://' + myNode.domain + ':' + myNode.port; + const avatarUrl = `${nodeUrl}/arbitrary/THUMBNAIL/${messageObj.senderName}/qortal_avatar?apiKey=${myNode.apiKey}`; + avatarImg = ``; + } return `
  • @@ -166,7 +177,8 @@ class ChatScroller extends LitElement { ${messageObj.senderName ? messageObj.senderName : messageObj.sender} -
    ${this.emojiPicker.parse(this.escapeHTML(messageObj.decodedMessage))}
    +
    ${this.emojiPicker.parse(this.escapeHTML(messageObj.decodedMessage))}
    +
    ${avatarImg}
  • ` } diff --git a/qortal-ui-plugins/plugins/core/main.src.js b/qortal-ui-plugins/plugins/core/main.src.js index 3b5e0bf9..48d06dea 100644 --- a/qortal-ui-plugins/plugins/core/main.src.js +++ b/qortal-ui-plugins/plugins/core/main.src.js @@ -65,6 +65,24 @@ parentEpml.ready().then(() => { menus: [], parent: false }, + { + url: 'websites', + domain: 'core', + page: 'qdn/index.html', + title: 'Websites', + icon: 'computer', + menus: [], + parent: false + }, + { + url: 'data-management', + domain: 'core', + page: 'qdn/data-management/index.html', + title: 'Data Management', + icon: 'dns', + menus: [], + parent: false + }, { url: 'q-chat', domain: 'core', @@ -83,7 +101,7 @@ parentEpml.ready().then(() => { menus: [], parent: false }, - { + { url: 'puzzles', domain: 'core', page: 'puzzles/index.html', @@ -91,7 +109,7 @@ parentEpml.ready().then(() => { icon: 'extension', menus: [], parent: false - } + } ] const registerPlugins = (pluginInfo) => { diff --git a/qortal-ui-plugins/plugins/core/minting/index.html b/qortal-ui-plugins/plugins/core/minting/index.html index cd207174..afe19715 100644 --- a/qortal-ui-plugins/plugins/core/minting/index.html +++ b/qortal-ui-plugins/plugins/core/minting/index.html @@ -1,45 +1,45 @@ - - - - - - - - - - - - - - + + + + + + + + + + + + + + diff --git a/qortal-ui-plugins/plugins/core/minting/minting-info.src.js b/qortal-ui-plugins/plugins/core/minting/minting-info.src.js index 5462ee54..7c399910 100644 --- a/qortal-ui-plugins/plugins/core/minting/minting-info.src.js +++ b/qortal-ui-plugins/plugins/core/minting/minting-info.src.js @@ -1,733 +1,733 @@ -import { LitElement, html, css } from "lit-element"; -import { render } from "lit-html"; -import { Epml } from "../../../epml.js"; - -import "@material/mwc-icon"; -import "@material/mwc-button"; -import "@material/mwc-dialog"; -import "@material/mwc-textfield"; - -const parentEpml = new Epml({ type: 'WINDOW', source: window.parent }); - -class MintingInfo extends LitElement { - static get properties() { - return { - selectedAddress: { type: Object }, - loading: { type: Boolean }, - adminInfo: { type: Array }, - nodeInfo: { type: Array }, - sampleBlock: { type: Array }, - addressInfo: { type: Array }, - addressLevel: { type: Array }, - } - } - - static get styles() { - return css` - @keyframes moveInBottom { - 0% { - opacity: 0; - transform: translateY(30px); - } - - 100% { - opacity: 1; - transform: translate(0); - } - } - - [hidden] { - display: hidden !important; - visibility: none !important; - } - - h4 { - font-weight:600; - font-size:20px; - line-height: 28px; - color:#000000; - } - - .header-title { - display: block; - overflow: hidden; - font-size: 40px; - color: black; - font-weight: 400; - text-align: center; - white-space: pre-wrap; - overflow-wrap: break-word; - word-break: break-word; - cursor: inherit; - margin-top: 2rem; - } - - .level-black { - font-size: 32px; - color: black; - font-weight: 400; - text-align: center; - margin-top: 2rem; - } - - .level-blue { - display: inline-block; - font-size: 32px; - color: #03a9f4; - font-weight: 400; - text-align: center; - margin-top: 2rem; - } - - .sub-main { - position: relative; - text-align: center; - width: 100%; - } - - .center-box { - position: absolute; - width: 100%; - top: 50%; - left: 50%; - transform: translate(-50%, 0%); - text-align: center; - } - - .content-box { - border: 1px solid #a1a1a1; - border-radius: 10px; - padding: 10px 25px; - text-align: center; - display: inline-block; - min-width: 250px; - margin-left: 10px; - margin-bottom: 5px; - } - - .help-icon { - color: #f44336; - } - - .details { - display: flex; - font-size: 18px; - } - - .title { - font-weight:600; - font-size:20px; - line-height: 28px; - opacity: 0.66; - color: #03a9f4; - } - - .sub-title { - font-weight:600; - font-size:20px; - line-height: 24px; - opacity: 0.66; - } - - .input { - width: 90%; - border: none; - display: inline-block; - font-size: 20px; - padding: 10px 20px; - border-radius: 5px; - resize: none; - background: #eee; - } - - .textarea { - width: 90%; - border: none; - display: inline-block; - font-size: 16px; - padding: 10px 20px; - border-radius: 5px; - height: 120px; - resize: none; - background: #eee; - } - - .not-minter { - display: inline-block; - font-size: 32px; - color: #03a9f4; - font-weight: 400; - margin-top: 2rem; - } - - .blue { - color: #03a9f4; - } - - .black { - color: #000000; - } - - .red { - color: #f44336; - } - - .red-button { - --mdc-theme-primary: red; - --mdc-theme-on-primary: white; - } - - mwc-button.red-button { - --mdc-theme-primary: red; - --mdc-theme-on-primary: white; - } - ` - } - - constructor() { - super() - this.selectedAddress = window.parent.reduxStore.getState().app.selectedAddress.address - this.adminInfo = []; - this.nodeInfo = []; - this.sampleBlock = []; - this.addressInfo = []; - this.addressLevel = []; - } - - render() { - if (this.renderMintingPage() === "false") { - return html` -
    -
    - General Minting Details -
    -
    -
    -
    -
    - Blockchain Statistics -
    -

    -
    - Avg. Qortal Blocktime -
    -

    ${this._averageBlockTime()} Seconds

    -
    -
    - Avg. Blocks Per Day -
    -

    ${this._timeCalc()} Blocks

    -
    -
    - Avg. Created QORT Per Day -
    -

    ${this._dayReward()} QORT

    -



    -
    - ${this.renderActivateHelp()} -
    -

    -
    -
    - - - -
    -

    Activate Your Account

    -
    -
    -
    -

    Introduction


    - To activate your account you must have an transaction in your account. - You can ask within Q-Chat or wherever else and someone will happily send you some small QORT. - After somebody send you some QORT, logout and after login again. Your account is activated. -
    - Close -
    -
    - `} else { - return html` -
    -
    - General Minting Details -
    -
    -
    -
    -
    - Blockchain Statistics -
    -

    -
    - Avg. Qortal Blocktime -
    -

    ${this._averageBlockTime()} Seconds

    -
    -
    - Avg. Blocks Per Day -
    -

    ${this._timeCalc()} Blocks

    -
    -
    - Avg. Created QORT Per Day -
    -

    ${this._dayReward()} QORT

    -



    -
    - ${this.renderMintingHelp()} -
    -

    -
    - Current Status -
    -

    ${this._mintingStatus()}

    -
    -
    - Current Level -
    -

    Level ${this.addressInfo.level}

    -
    -
    - Blocks To Next Level -
    -

    ${this._levelUpBlocks()} Blocks

    -

    -
    - If you continue minting 24/7 you will reach level
    ${this._levelUp()}
    in
    ${this._levelUpDays()}
    days !
    -



    -
    - Minting Rewards Info -
    -

    -
    - Current Tier -
    -

    ${this._currentTier()}

    -
    -
    - Total Minters in The Tier -
    -

    ${this._countLevels()} Minters

    -
    -
    - Tier Share Per Block -
    -

    ${this._tierPercent()} %

    -
    -
    - Est. Reward Per Block -
    -

    ${this._countReward()} QORT

    -
    -
    - Est. Reward Per Day -
    -

    ${this._countRewardDay()} QORT

    -
    -
    -
    - - - -
    -

    Become A Minter

    -
    -
    -
    -

    Introduction


    - In Qortal, in order to become a minter and begin earning QORT rewards with your increase in Minter Level, you must first become ‘sponsored’. - A sponsor in Qortal is any other minter of level 5 or higher, or a Qortal Founder. You will obtain a sponsorship key from the sponsor, and use that key to get to level 1. - Once you have reached level 1, you will be able to create your own minting key and start earning rewards for helping secure the Qortal Blockchain. -
    -
    -

    Sponsorship


    - Your sponsor will issue you a ‘Sponsorship Key’ which you will use to add to your node, and begin minting (for no rewards until reaching level 1.) - Once you reach level 1, you create/assign your own ‘Minting Key’ and begin earning rewards. You have XXXX blocks remaining in your sponsorship period. -
    - Simply reach out to a minter in Qortal who is high enough level to issue a sponsorship key, obtain that key, then come back here and input the key to begin your minting journey ! -
    - Close -
    - -
    - `} - } - - firstUpdated() { - const getAdminInfo = () => { - parentEpml.request("apiCall", {url: `/admin/info`}).then((res) => { - setTimeout(() => {this.adminInfo = res;}, 1); - }); - setTimeout(getAdminInfo, 30000); - }; - - const getNodeInfo = () => { - parentEpml.request("apiCall", {url: `/admin/status`}).then((res) => { - this.nodeInfo = res; - // Now look up the sample block - getSampleBlock() - }); - setTimeout(getNodeInfo, 30000); - }; - - const getSampleBlock = () => { - let callBlock = parseFloat(this.nodeInfo.height) - 10000; - parentEpml.request("apiCall", {url: `/blocks/byheight/${callBlock}`}).then((res) => { - setTimeout(() => {this.sampleBlock = res;}, 1); - }); - }; - - const getAddressInfo = () => { - parentEpml.request('apiCall', {url: `/addresses/${window.parent.reduxStore.getState().app.selectedAddress.address}`}).then((res) => { - setTimeout(() => {this.addressInfo = res;}, 1); - }); - setTimeout(getAddressInfo, 30000); - }; - - const getAddressLevel = () => { - parentEpml.request('apiCall', {url: `/addresses/online/levels`}).then((res) => { - setTimeout(() => {this.addressLevel = res;}, 1); - }); - setTimeout(getAddressLevel, 30000); - }; - - 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.ready().then(() => { - parentEpml.subscribe("config", async c => { - if (!configLoaded) { - setTimeout(getAdminInfo, 1); - setTimeout(getNodeInfo, 1); - setTimeout(getAddressInfo, 1); - setTimeout(getAddressLevel, 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(); - }) - }); - - parentEpml.imReady(); - } - - renderMintingPage() { - if (this.addressInfo.error === 124) { - return "false" - } else { - return "true" - } - } - - renderActivateHelp() { - if (this.renderMintingPage() === "false") { - return html `Activate Account Details
    ==>
    Not Activated
    this.shadowRoot.querySelector("#activateAccountDialog").show()}>help_outline Press For Help`; - } else { - return "No Details"; - } - } - _averageBlockTime() { - let avgBlockString = (this.adminInfo.currentTimestamp - this.sampleBlock.timestamp).toString(); - let averageTimeString = ((avgBlockString / 1000) / 10000).toFixed(2); - let averageBlockTimeString = (averageTimeString).toString(); - return "" + averageBlockTimeString; - } - - _timeCalc() { - let timeString = (this.adminInfo.currentTimestamp - this.sampleBlock.timestamp).toString(); - let averageString = ((timeString / 1000) / 10000).toFixed(2); - let averageBlockDay = (86400 / averageString).toFixed(2); - let averageBlockDayString = (averageBlockDay).toString(); - return "" + averageBlockDayString; - } - - _dayReward() { - let rewardString = (this._timeCalc() * this._blockReward()).toFixed(2); - let rewardDayString = (rewardString).toString(); - return "" + rewardDayString ; - } - - _mintingStatus() { - if (this.nodeInfo.isMintingPossible === true && this.nodeInfo.isSynchronizing === true) { - this.cssMinting = "blue" - return "Minting" - } else if (this.nodeInfo.isMintingPossible === true && this.nodeInfo.isSynchronizing === false) { - this.cssMinting = "blue" - return "Minting" - } else if (this.nodeInfo.isMintingPossible === false && this.nodeInfo.isSynchronizing === true) { - this.cssMinting = "red" - return `(Synchronizing... ${this.nodeInfo.syncPercent !== undefined ? this.nodeInfo.syncPercent + '%' : ''})` - } else if (this.nodeInfo.isMintingPossible === false && this.nodeInfo.isSynchronizing === false) { - this.cssMinting = "red" - return "Not Minting" - } else { - return "No Status" - } - } - - renderMintingHelp() { - if (this._mintingStatus() === "Not Minting") { - return html `Minting Account Details
    ==>
    Not A Minter
    this.shadowRoot.querySelector("#becomeMinterDialog").show()}>help_outline Press For Help`; - } else { - return "Minting Account Details"; - } - } - - _levelUpDays() { - let countDays = ((this._blocksNeed() - (this.addressInfo.blocksMinted + this.addressInfo.blocksMintedAdjustment)) / this._timeCalc()).toFixed(2); - let countString = (countDays).toString(); - return "" + countString; - } - - _levelUpBlocks() { - let countBlocksString = (this._blocksNeed() - (this.addressInfo.blocksMinted + this.addressInfo.blocksMintedAdjustment)).toString(); - return "" + countBlocksString; - } - - _blocksNeed() { - if (this.addressInfo.level === 0) { - return "7200" - } else if (this.addressInfo.level === 1) { - return "72000" - } else if (this.addressInfo.level === 2) { - return "201600" - } else if (this.addressInfo.level === 3) { - return "374400" - } else if (this.addressInfo.level === 4) { - return "618400" - } else if (this.addressInfo.level === 5) { - return "964000" - } else if (this.addressInfo.level === 6) { - return "1482400" - } else if (this.addressInfo.level === 7) { - return "2173600" - } else if (this.addressInfo.level === 8) { - return "3037600" - } else if (this.addressInfo.level === 9) { - return "4074400" - } - } - - _levelUp() { - if (this.addressInfo.level === 0) { - return "1" - } else if (this.addressInfo.level === 1) { - return "2" - } else if (this.addressInfo.level === 2) { - return "3" - } else if (this.addressInfo.level === 3) { - return "4" - } else if (this.addressInfo.level === 4) { - return "5" - } else if (this.addressInfo.level === 5) { - return "6" - } else if (this.addressInfo.level === 6) { - return "7" - } else if (this.addressInfo.level === 7) { - return "8" - } else if (this.addressInfo.level === 8) { - return "9" - } else if (this.addressInfo.level === 9) { - return "10" - } - } - - _currentTier() { - if (this.addressInfo.level === 0) { - return "Tier 0 (Level 0)" - } else if (this.addressInfo.level === 1) { - return "Tier 1 (Level 1 + 2)" - } else if (this.addressInfo.level === 2) { - return "Tier 1 (Level 1 + 2)" - } else if (this.addressInfo.level === 3) { - return "Tier 2 (Level 3 + 4)" - } else if (this.addressInfo.level === 4) { - return "Tier 2 (Level 3 + 4)" - } else if (this.addressInfo.level === 5) { - return "Tier 3 (Level 5 + 6)" - } else if (this.addressInfo.level === 6) { - return "Tier 3 (Level 5 + 6)" - } else if (this.addressInfo.level === 7) { - return "Tier 4 (Level 7 + 8)" - } else if (this.addressInfo.level === 8) { - return "Tier 4 (Level 7 + 8)" - } else if (this.addressInfo.level === 9) { - return "Tier 5 (Level 9 + 10)" - } else if (this.addressInfo.level === 10) { - return "Tier 5 (Level 9 + 10)" - } - } - - _tierPercent() { - if (this.addressInfo.level === 0) { - return "0" - } else if (this.addressInfo.level === 1) { - return "5" - } else if (this.addressInfo.level === 2) { - return "5" - } else if (this.addressInfo.level === 3) { - return "10" - } else if (this.addressInfo.level === 4) { - return "10" - } else if (this.addressInfo.level === 5) { - return "15" - } else if (this.addressInfo.level === 6) { - return "15" - } else if (this.addressInfo.level === 7) { - return "20" - } else if (this.addressInfo.level === 8) { - return "20" - } else if (this.addressInfo.level === 9) { - return "25" - } else if (this.addressInfo.level === 10) { - return "25" - } - } - - _blockReward() { - if (this.nodeInfo.height < 259201) { - return "5.00" - } else if (this.nodeInfo.height < 518401) { - return "4.75" - } else if (this.nodeInfo.height < 777601) { - return "4.50" - } else if (this.nodeInfo.height < 1036801) { - return "4.25" - } else if (this.nodeInfo.height < 1296001) { - return "4.00" - } else if (this.nodeInfo.height < 1555201) { - return "3.75" - } else if (this.nodeInfo.height < 1814401) { - return "3.50" - } else if (this.nodeInfo.height < 2073601) { - return "3.25" - } else if (this.nodeInfo.height < 2332801) { - return "3.00" - } else if (this.nodeInfo.height < 2592001) { - return "2.75" - } else if (this.nodeInfo.height < 2851201) { - return "2.50" - } else if (this.nodeInfo.height < 3110401) { - return "2.25" - } else { - return "2.00" - } - } - - _countLevels() { - if (this.addressInfo.level === 0) { - return "0" - } else if (this.addressInfo.level === 1) { - let countTier10 = (this.addressLevel[0].count + this.addressLevel[1].count).toString(); - return "" + countTier10; - } else if (this.addressInfo.level === 2) { - let countTier11 = (this.addressLevel[0].count + this.addressLevel[1].count).toString(); - return "" + countTier11; - } else if (this.addressInfo.level === 3) { - let countTier20 = (this.addressLevel[2].count + this.addressLevel[3].count).toString(); - return "" + countTier20; - } else if (this.addressInfo.level === 4) { - let countTier21 = (this.addressLevel[2].count + this.addressLevel[3].count).toString(); - return "" + countTier21; - } else if (this.addressInfo.level === 5) { - let countTier30 = (this.addressLevel[4].count + this.addressLevel[5].count).toString(); - return "" + countTier30; - } else if (this.addressInfo.level === 6) { - let countTier31 = (this.addressLevel[4].count + this.addressLevel[5].count).toString(); - return "" + countTier31; - } else if (this.addressInfo.level === 10) { - let countTier101 = (this.addressLevel[6].count).toString(); - return "" + countTier101; - } - } - - _countReward() { - if (this.addressInfo.level === 0) { - return "0" - } else if (this.addressInfo.level === 1) { - let countReward10 = ((this._blockReward() / 100 * this._tierPercent()) / (this.addressLevel[0].count + this.addressLevel[1].count)).toFixed(8); - let countReward11 = (countReward10).toString(); - return "" + countReward11; - } else if (this.addressInfo.level === 2) { - let countReward20 = ((this._blockReward() / 100 * this._tierPercent()) / (this.addressLevel[0].count + this.addressLevel[1].count)).toFixed(8); - let countReward21 = (countReward20).toString(); - return "" + countReward21; - } else if (this.addressInfo.level === 3) { - let countReward30 = ((this._blockReward() / 100 * this._tierPercent()) / (this.addressLevel[2].count + this.addressLevel[3].count)).toFixed(8); - let countReward31 = (countReward30).toString(); - return "" + countReward31; - } else if (this.addressInfo.level === 4) { - let countReward40 = ((this._blockReward() / 100 * this._tierPercent()) / (this.addressLevel[2].count + this.addressLevel[3].count)).toFixed(8); - let countReward41 = (countReward40).toString(); - return "" + countReward41; - } else if (this.addressInfo.level === 5) { - let countReward50 = ((this._blockReward() / 100 * this._tierPercent()) / (this.addressLevel[4].count + this.addressLevel[5].count)).toFixed(8); - let countReward51 = (countReward50).toString(); - return "" + countReward51; - } else if (this.addressInfo.level === 6) { - let countReward60 = ((this._blockReward() / 100 * this._tierPercent()) / (this.addressLevel[4].count + this.addressLevel[5].count)).toFixed(8); - let countReward61 = (countReward60).toString(); - return "" + countReward61; - } else if (this.addressInfo.level === 10) { - let countReward100 = ((this._blockReward() / 100 * this._tierPercent()) / this.addressLevel[6].count).toFixed(8); - let countReward101 = (countReward100).toString(); - return "" + countReward101; - } - } - - _countRewardDay() { - if (this.addressInfo.level === 0) { - return "0" - } else if (this.addressInfo.level === 1) { - let countRewardDay10 = ((this._blockReward() / 100 * this._tierPercent()) / (this.addressLevel[0].count + this.addressLevel[1].count) * this._timeCalc()).toFixed(8); - let countRewardDay11 = (countRewardDay10).toString(); - return "" + countRewardDay11; - } else if (this.addressInfo.level === 2) { - let countRewardDay20 = ((this._blockReward() / 100 * this._tierPercent()) / (this.addressLevel[0].count + this.addressLevel[1].count) * this._timeCalc()).toFixed(8); - let countRewardDay21 = (countRewardDay20).toString(); - return "" + countRewardDay21; - } else if (this.addressInfo.level === 3) { - let countRewardDay30 = ((this._blockReward() / 100 * this._tierPercent()) / (this.addressLevel[2].count + this.addressLevel[3].count) * this._timeCalc()).toFixed(8); - let countRewardDay31 = (countRewardDay30).toString(); - return "" + countRewardDay31; - } else if (this.addressInfo.level === 4) { - let countRewardDay40 = ((this._blockReward() / 100 * this._tierPercent()) / (this.addressLevel[2].count + this.addressLevel[3].count) * this._timeCalc()).toFixed(8); - let countRewardDay41 = (countRewardDay40).toString(); - return "" + countRewardDay41; - } else if (this.addressInfo.level === 5) { - let countRewardDay50 = ((this._blockReward() / 100 * this._tierPercent()) / (this.addressLevel[4].count + this.addressLevel[5].count) * this._timeCalc()).toFixed(8); - let countRewardDay51 = (countRewardDay50).toString(); - return "" + countRewardDay51; - } else if (this.addressInfo.level === 6) { - let countRewardDay60 = ((this._blockReward() / 100 * this._tierPercent()) / (this.addressLevel[4].count + this.addressLevel[5].count) * this._timeCalc()).toFixed(8); - let countRewardDay61 = (countRewardDay60).toString(); - return "" + countRewardDay61; - } else if (this.addressInfo.level === 10) { - let countRewardDay100 = ((this._blockReward() / 100 * this._tierPercent()) / (this.addressLevel[6].count) * this._timeCalc()).toFixed(8); - let countRewardDay101 = (countRewardDay100).toString(); - return "" + countRewardDay101; - } - } - - clearSelection() { - window.getSelection().removeAllRanges() - window.parent.getSelection().removeAllRanges() - } - - isEmptyArray(arr) { - if (!arr) return true; - return arr.length === 0; - } -} - -window.customElements.define('minting-info', MintingInfo) +import { LitElement, html, css } from "lit-element"; +import { render } from "lit-html"; +import { Epml } from "../../../epml.js"; + +import "@material/mwc-icon"; +import "@material/mwc-button"; +import "@material/mwc-dialog"; +import "@material/mwc-textfield"; + +const parentEpml = new Epml({ type: 'WINDOW', source: window.parent }); + +class MintingInfo extends LitElement { + static get properties() { + return { + selectedAddress: { type: Object }, + loading: { type: Boolean }, + adminInfo: { type: Array }, + nodeInfo: { type: Array }, + sampleBlock: { type: Array }, + addressInfo: { type: Array }, + addressLevel: { type: Array }, + } + } + + static get styles() { + return css` + @keyframes moveInBottom { + 0% { + opacity: 0; + transform: translateY(30px); + } + + 100% { + opacity: 1; + transform: translate(0); + } + } + + [hidden] { + display: hidden !important; + visibility: none !important; + } + + h4 { + font-weight:600; + font-size:20px; + line-height: 28px; + color:#000000; + } + + .header-title { + display: block; + overflow: hidden; + font-size: 40px; + color: black; + font-weight: 400; + text-align: center; + white-space: pre-wrap; + overflow-wrap: break-word; + word-break: break-word; + cursor: inherit; + margin-top: 2rem; + } + + .level-black { + font-size: 32px; + color: black; + font-weight: 400; + text-align: center; + margin-top: 2rem; + } + + .level-blue { + display: inline-block; + font-size: 32px; + color: #03a9f4; + font-weight: 400; + text-align: center; + margin-top: 2rem; + } + + .sub-main { + position: relative; + text-align: center; + width: 100%; + } + + .center-box { + position: absolute; + width: 100%; + top: 50%; + left: 50%; + transform: translate(-50%, 0%); + text-align: center; + } + + .content-box { + border: 1px solid #a1a1a1; + border-radius: 10px; + padding: 10px 25px; + text-align: center; + display: inline-block; + min-width: 250px; + margin-left: 10px; + margin-bottom: 5px; + } + + .help-icon { + color: #f44336; + } + + .details { + display: flex; + font-size: 18px; + } + + .title { + font-weight:600; + font-size:20px; + line-height: 28px; + opacity: 0.66; + color: #03a9f4; + } + + .sub-title { + font-weight:600; + font-size:20px; + line-height: 24px; + opacity: 0.66; + } + + .input { + width: 90%; + border: none; + display: inline-block; + font-size: 20px; + padding: 10px 20px; + border-radius: 5px; + resize: none; + background: #eee; + } + + .textarea { + width: 90%; + border: none; + display: inline-block; + font-size: 16px; + padding: 10px 20px; + border-radius: 5px; + height: 120px; + resize: none; + background: #eee; + } + + .not-minter { + display: inline-block; + font-size: 32px; + color: #03a9f4; + font-weight: 400; + margin-top: 2rem; + } + + .blue { + color: #03a9f4; + } + + .black { + color: #000000; + } + + .red { + color: #f44336; + } + + .red-button { + --mdc-theme-primary: red; + --mdc-theme-on-primary: white; + } + + mwc-button.red-button { + --mdc-theme-primary: red; + --mdc-theme-on-primary: white; + } + ` + } + + constructor() { + super() + this.selectedAddress = window.parent.reduxStore.getState().app.selectedAddress.address + this.adminInfo = []; + this.nodeInfo = []; + this.sampleBlock = []; + this.addressInfo = []; + this.addressLevel = []; + } + + render() { + if (this.renderMintingPage() === "false") { + return html` +
    +
    + General Minting Details +
    +
    +
    +
    +
    + Blockchain Statistics +
    +

    +
    + Avg. Qortal Blocktime +
    +

    ${this._averageBlockTime()} Seconds

    +
    +
    + Avg. Blocks Per Day +
    +

    ${this._timeCalc()} Blocks

    +
    +
    + Avg. Created QORT Per Day +
    +

    ${this._dayReward()} QORT

    +



    +
    + ${this.renderActivateHelp()} +
    +

    +
    +
    + + + +
    +

    Activate Your Account

    +
    +
    +
    +

    Introduction


    + To activate your account you must have an transaction in your account. + You can ask within Q-Chat or wherever else and someone will happily send you some small QORT. + After somebody send you some QORT, logout and after login again. Your account is activated. +
    + Close +
    +
    + `} else { + return html` +
    +
    + General Minting Details +
    +
    +
    +
    +
    + Blockchain Statistics +
    +

    +
    + Avg. Qortal Blocktime +
    +

    ${this._averageBlockTime()} Seconds

    +
    +
    + Avg. Blocks Per Day +
    +

    ${this._timeCalc()} Blocks

    +
    +
    + Avg. Created QORT Per Day +
    +

    ${this._dayReward()} QORT

    +



    +
    + ${this.renderMintingHelp()} +
    +

    +
    + Current Status +
    +

    ${this._mintingStatus()}

    +
    +
    + Current Level +
    +

    Level ${this.addressInfo.level}

    +
    +
    + Blocks To Next Level +
    +

    ${this._levelUpBlocks()} Blocks

    +

    +
    + If you continue minting 24/7 you will reach level
    ${this._levelUp()}
    in
    ${this._levelUpDays()}
    days !
    +



    +
    + Minting Rewards Info +
    +

    +
    + Current Tier +
    +

    ${this._currentTier()}

    +
    +
    + Total Minters in The Tier +
    +

    ${this._countLevels()} Minters

    +
    +
    + Tier Share Per Block +
    +

    ${this._tierPercent()} %

    +
    +
    + Est. Reward Per Block +
    +

    ${this._countReward()} QORT

    +
    +
    + Est. Reward Per Day +
    +

    ${this._countRewardDay()} QORT

    +
    +
    +
    + + + +
    +

    Become A Minter

    +
    +
    +
    +

    Introduction


    + In Qortal, in order to become a minter and begin earning QORT rewards with your increase in Minter Level, you must first become ‘sponsored’. + A sponsor in Qortal is any other minter of level 5 or higher, or a Qortal Founder. You will obtain a sponsorship key from the sponsor, and use that key to get to level 1. + Once you have reached level 1, you will be able to create your own minting key and start earning rewards for helping secure the Qortal Blockchain. +
    +
    +

    Sponsorship


    + Your sponsor will issue you a ‘Sponsorship Key’ which you will use to add to your node, and begin minting (for no rewards until reaching level 1.) + Once you reach level 1, you create/assign your own ‘Minting Key’ and begin earning rewards. You have XXXX blocks remaining in your sponsorship period. +
    + Simply reach out to a minter in Qortal who is high enough level to issue a sponsorship key, obtain that key, then come back here and input the key to begin your minting journey ! +
    + Close +
    + +
    + `} + } + + firstUpdated() { + const getAdminInfo = () => { + parentEpml.request("apiCall", {url: `/admin/info`}).then((res) => { + setTimeout(() => {this.adminInfo = res;}, 1); + }); + setTimeout(getAdminInfo, 30000); + }; + + const getNodeInfo = () => { + parentEpml.request("apiCall", {url: `/admin/status`}).then((res) => { + this.nodeInfo = res; + // Now look up the sample block + getSampleBlock() + }); + setTimeout(getNodeInfo, 30000); + }; + + const getSampleBlock = () => { + let callBlock = parseFloat(this.nodeInfo.height) - 10000; + parentEpml.request("apiCall", {url: `/blocks/byheight/${callBlock}`}).then((res) => { + setTimeout(() => {this.sampleBlock = res;}, 1); + }); + }; + + const getAddressInfo = () => { + parentEpml.request('apiCall', {url: `/addresses/${window.parent.reduxStore.getState().app.selectedAddress.address}`}).then((res) => { + setTimeout(() => {this.addressInfo = res;}, 1); + }); + setTimeout(getAddressInfo, 30000); + }; + + const getAddressLevel = () => { + parentEpml.request('apiCall', {url: `/addresses/online/levels`}).then((res) => { + setTimeout(() => {this.addressLevel = res;}, 1); + }); + setTimeout(getAddressLevel, 30000); + }; + + 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.ready().then(() => { + parentEpml.subscribe("config", async c => { + if (!configLoaded) { + setTimeout(getAdminInfo, 1); + setTimeout(getNodeInfo, 1); + setTimeout(getAddressInfo, 1); + setTimeout(getAddressLevel, 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(); + }) + }); + + parentEpml.imReady(); + } + + renderMintingPage() { + if (this.addressInfo.error === 124) { + return "false" + } else { + return "true" + } + } + + renderActivateHelp() { + if (this.renderMintingPage() === "false") { + return html `Activate Account Details
    ==>
    Not Activated
    this.shadowRoot.querySelector("#activateAccountDialog").show()}>help_outline Press For Help`; + } else { + return "No Details"; + } + } + _averageBlockTime() { + let avgBlockString = (this.adminInfo.currentTimestamp - this.sampleBlock.timestamp).toString(); + let averageTimeString = ((avgBlockString / 1000) / 10000).toFixed(2); + let averageBlockTimeString = (averageTimeString).toString(); + return "" + averageBlockTimeString; + } + + _timeCalc() { + let timeString = (this.adminInfo.currentTimestamp - this.sampleBlock.timestamp).toString(); + let averageString = ((timeString / 1000) / 10000).toFixed(2); + let averageBlockDay = (86400 / averageString).toFixed(2); + let averageBlockDayString = (averageBlockDay).toString(); + return "" + averageBlockDayString; + } + + _dayReward() { + let rewardString = (this._timeCalc() * this._blockReward()).toFixed(2); + let rewardDayString = (rewardString).toString(); + return "" + rewardDayString ; + } + + _mintingStatus() { + if (this.nodeInfo.isMintingPossible === true && this.nodeInfo.isSynchronizing === true) { + this.cssMinting = "blue" + return "Minting" + } else if (this.nodeInfo.isMintingPossible === true && this.nodeInfo.isSynchronizing === false) { + this.cssMinting = "blue" + return "Minting" + } else if (this.nodeInfo.isMintingPossible === false && this.nodeInfo.isSynchronizing === true) { + this.cssMinting = "red" + return `(Synchronizing... ${this.nodeInfo.syncPercent !== undefined ? this.nodeInfo.syncPercent + '%' : ''})` + } else if (this.nodeInfo.isMintingPossible === false && this.nodeInfo.isSynchronizing === false) { + this.cssMinting = "red" + return "Not Minting" + } else { + return "No Status" + } + } + + renderMintingHelp() { + if (this._mintingStatus() === "Not Minting") { + return html `Minting Account Details
    ==>
    Not A Minter
    this.shadowRoot.querySelector("#becomeMinterDialog").show()}>help_outline Press For Help`; + } else { + return "Minting Account Details"; + } + } + + _levelUpDays() { + let countDays = ((this._blocksNeed() - (this.addressInfo.blocksMinted + this.addressInfo.blocksMintedAdjustment)) / this._timeCalc()).toFixed(2); + let countString = (countDays).toString(); + return "" + countString; + } + + _levelUpBlocks() { + let countBlocksString = (this._blocksNeed() - (this.addressInfo.blocksMinted + this.addressInfo.blocksMintedAdjustment)).toString(); + return "" + countBlocksString; + } + + _blocksNeed() { + if (this.addressInfo.level === 0) { + return "7200" + } else if (this.addressInfo.level === 1) { + return "72000" + } else if (this.addressInfo.level === 2) { + return "201600" + } else if (this.addressInfo.level === 3) { + return "374400" + } else if (this.addressInfo.level === 4) { + return "618400" + } else if (this.addressInfo.level === 5) { + return "964000" + } else if (this.addressInfo.level === 6) { + return "1482400" + } else if (this.addressInfo.level === 7) { + return "2173600" + } else if (this.addressInfo.level === 8) { + return "3037600" + } else if (this.addressInfo.level === 9) { + return "4074400" + } + } + + _levelUp() { + if (this.addressInfo.level === 0) { + return "1" + } else if (this.addressInfo.level === 1) { + return "2" + } else if (this.addressInfo.level === 2) { + return "3" + } else if (this.addressInfo.level === 3) { + return "4" + } else if (this.addressInfo.level === 4) { + return "5" + } else if (this.addressInfo.level === 5) { + return "6" + } else if (this.addressInfo.level === 6) { + return "7" + } else if (this.addressInfo.level === 7) { + return "8" + } else if (this.addressInfo.level === 8) { + return "9" + } else if (this.addressInfo.level === 9) { + return "10" + } + } + + _currentTier() { + if (this.addressInfo.level === 0) { + return "Tier 0 (Level 0)" + } else if (this.addressInfo.level === 1) { + return "Tier 1 (Level 1 + 2)" + } else if (this.addressInfo.level === 2) { + return "Tier 1 (Level 1 + 2)" + } else if (this.addressInfo.level === 3) { + return "Tier 2 (Level 3 + 4)" + } else if (this.addressInfo.level === 4) { + return "Tier 2 (Level 3 + 4)" + } else if (this.addressInfo.level === 5) { + return "Tier 3 (Level 5 + 6)" + } else if (this.addressInfo.level === 6) { + return "Tier 3 (Level 5 + 6)" + } else if (this.addressInfo.level === 7) { + return "Tier 4 (Level 7 + 8)" + } else if (this.addressInfo.level === 8) { + return "Tier 4 (Level 7 + 8)" + } else if (this.addressInfo.level === 9) { + return "Tier 5 (Level 9 + 10)" + } else if (this.addressInfo.level === 10) { + return "Tier 5 (Level 9 + 10)" + } + } + + _tierPercent() { + if (this.addressInfo.level === 0) { + return "0" + } else if (this.addressInfo.level === 1) { + return "5" + } else if (this.addressInfo.level === 2) { + return "5" + } else if (this.addressInfo.level === 3) { + return "10" + } else if (this.addressInfo.level === 4) { + return "10" + } else if (this.addressInfo.level === 5) { + return "15" + } else if (this.addressInfo.level === 6) { + return "15" + } else if (this.addressInfo.level === 7) { + return "20" + } else if (this.addressInfo.level === 8) { + return "20" + } else if (this.addressInfo.level === 9) { + return "25" + } else if (this.addressInfo.level === 10) { + return "25" + } + } + + _blockReward() { + if (this.nodeInfo.height < 259201) { + return "5.00" + } else if (this.nodeInfo.height < 518401) { + return "4.75" + } else if (this.nodeInfo.height < 777601) { + return "4.50" + } else if (this.nodeInfo.height < 1036801) { + return "4.25" + } else if (this.nodeInfo.height < 1296001) { + return "4.00" + } else if (this.nodeInfo.height < 1555201) { + return "3.75" + } else if (this.nodeInfo.height < 1814401) { + return "3.50" + } else if (this.nodeInfo.height < 2073601) { + return "3.25" + } else if (this.nodeInfo.height < 2332801) { + return "3.00" + } else if (this.nodeInfo.height < 2592001) { + return "2.75" + } else if (this.nodeInfo.height < 2851201) { + return "2.50" + } else if (this.nodeInfo.height < 3110401) { + return "2.25" + } else { + return "2.00" + } + } + + _countLevels() { + if (this.addressInfo.level === 0) { + return "0" + } else if (this.addressInfo.level === 1) { + let countTier10 = (this.addressLevel[0].count + this.addressLevel[1].count).toString(); + return "" + countTier10; + } else if (this.addressInfo.level === 2) { + let countTier11 = (this.addressLevel[0].count + this.addressLevel[1].count).toString(); + return "" + countTier11; + } else if (this.addressInfo.level === 3) { + let countTier20 = (this.addressLevel[2].count + this.addressLevel[3].count).toString(); + return "" + countTier20; + } else if (this.addressInfo.level === 4) { + let countTier21 = (this.addressLevel[2].count + this.addressLevel[3].count).toString(); + return "" + countTier21; + } else if (this.addressInfo.level === 5) { + let countTier30 = (this.addressLevel[4].count + this.addressLevel[5].count).toString(); + return "" + countTier30; + } else if (this.addressInfo.level === 6) { + let countTier31 = (this.addressLevel[4].count + this.addressLevel[5].count).toString(); + return "" + countTier31; + } else if (this.addressInfo.level === 10) { + let countTier101 = (this.addressLevel[6].count).toString(); + return "" + countTier101; + } + } + + _countReward() { + if (this.addressInfo.level === 0) { + return "0" + } else if (this.addressInfo.level === 1) { + let countReward10 = ((this._blockReward() / 100 * this._tierPercent()) / (this.addressLevel[0].count + this.addressLevel[1].count)).toFixed(8); + let countReward11 = (countReward10).toString(); + return "" + countReward11; + } else if (this.addressInfo.level === 2) { + let countReward20 = ((this._blockReward() / 100 * this._tierPercent()) / (this.addressLevel[0].count + this.addressLevel[1].count)).toFixed(8); + let countReward21 = (countReward20).toString(); + return "" + countReward21; + } else if (this.addressInfo.level === 3) { + let countReward30 = ((this._blockReward() / 100 * this._tierPercent()) / (this.addressLevel[2].count + this.addressLevel[3].count)).toFixed(8); + let countReward31 = (countReward30).toString(); + return "" + countReward31; + } else if (this.addressInfo.level === 4) { + let countReward40 = ((this._blockReward() / 100 * this._tierPercent()) / (this.addressLevel[2].count + this.addressLevel[3].count)).toFixed(8); + let countReward41 = (countReward40).toString(); + return "" + countReward41; + } else if (this.addressInfo.level === 5) { + let countReward50 = ((this._blockReward() / 100 * this._tierPercent()) / (this.addressLevel[4].count + this.addressLevel[5].count)).toFixed(8); + let countReward51 = (countReward50).toString(); + return "" + countReward51; + } else if (this.addressInfo.level === 6) { + let countReward60 = ((this._blockReward() / 100 * this._tierPercent()) / (this.addressLevel[4].count + this.addressLevel[5].count)).toFixed(8); + let countReward61 = (countReward60).toString(); + return "" + countReward61; + } else if (this.addressInfo.level === 10) { + let countReward100 = ((this._blockReward() / 100 * this._tierPercent()) / this.addressLevel[6].count).toFixed(8); + let countReward101 = (countReward100).toString(); + return "" + countReward101; + } + } + + _countRewardDay() { + if (this.addressInfo.level === 0) { + return "0" + } else if (this.addressInfo.level === 1) { + let countRewardDay10 = ((this._blockReward() / 100 * this._tierPercent()) / (this.addressLevel[0].count + this.addressLevel[1].count) * this._timeCalc()).toFixed(8); + let countRewardDay11 = (countRewardDay10).toString(); + return "" + countRewardDay11; + } else if (this.addressInfo.level === 2) { + let countRewardDay20 = ((this._blockReward() / 100 * this._tierPercent()) / (this.addressLevel[0].count + this.addressLevel[1].count) * this._timeCalc()).toFixed(8); + let countRewardDay21 = (countRewardDay20).toString(); + return "" + countRewardDay21; + } else if (this.addressInfo.level === 3) { + let countRewardDay30 = ((this._blockReward() / 100 * this._tierPercent()) / (this.addressLevel[2].count + this.addressLevel[3].count) * this._timeCalc()).toFixed(8); + let countRewardDay31 = (countRewardDay30).toString(); + return "" + countRewardDay31; + } else if (this.addressInfo.level === 4) { + let countRewardDay40 = ((this._blockReward() / 100 * this._tierPercent()) / (this.addressLevel[2].count + this.addressLevel[3].count) * this._timeCalc()).toFixed(8); + let countRewardDay41 = (countRewardDay40).toString(); + return "" + countRewardDay41; + } else if (this.addressInfo.level === 5) { + let countRewardDay50 = ((this._blockReward() / 100 * this._tierPercent()) / (this.addressLevel[4].count + this.addressLevel[5].count) * this._timeCalc()).toFixed(8); + let countRewardDay51 = (countRewardDay50).toString(); + return "" + countRewardDay51; + } else if (this.addressInfo.level === 6) { + let countRewardDay60 = ((this._blockReward() / 100 * this._tierPercent()) / (this.addressLevel[4].count + this.addressLevel[5].count) * this._timeCalc()).toFixed(8); + let countRewardDay61 = (countRewardDay60).toString(); + return "" + countRewardDay61; + } else if (this.addressInfo.level === 10) { + let countRewardDay100 = ((this._blockReward() / 100 * this._tierPercent()) / (this.addressLevel[6].count) * this._timeCalc()).toFixed(8); + let countRewardDay101 = (countRewardDay100).toString(); + return "" + countRewardDay101; + } + } + + clearSelection() { + window.getSelection().removeAllRanges() + window.parent.getSelection().removeAllRanges() + } + + isEmptyArray(arr) { + if (!arr) return true; + return arr.length === 0; + } +} + +window.customElements.define('minting-info', MintingInfo) diff --git a/qortal-ui-plugins/plugins/core/name-registration/name-registration.src.js b/qortal-ui-plugins/plugins/core/name-registration/name-registration.src.js index fefacde8..174605ec 100644 --- a/qortal-ui-plugins/plugins/core/name-registration/name-registration.src.js +++ b/qortal-ui-plugins/plugins/core/name-registration/name-registration.src.js @@ -3,6 +3,7 @@ // import '@webcomponents/webcomponentsjs/custom-elements-es5-adapter.js' import { LitElement, html, css } from 'lit-element' +import { render } from 'lit-html' import { Epml } from '../../../epml.js' import '@material/mwc-icon' @@ -81,9 +82,15 @@ class NameRegistration extends LitElement {

    Registered Names

    - + + { + render(html`${this.renderAvatar(data.item)}`, root) + }}> + { + render(html`${this.renderAvatarButton(data.item)}`, root) + }}> ${this.isEmptyArray(this.names) ? html` No names registered by this account! @@ -131,6 +138,23 @@ class NameRegistration extends LitElement { ` } + renderAvatar(nameObj) { + let name = nameObj.name + const myNode = window.parent.reduxStore.getState().app.nodeConfig.knownNodes[window.parent.reduxStore.getState().app.nodeConfig.node] + const nodeUrl = myNode.protocol + '://' + myNode.domain + ':' + myNode.port + const url = `${nodeUrl}/arbitrary/THUMBNAIL/${name}/qortal_avatar?apiKey=${this.getApiKey()}`; + return html`` + } + + renderAvatarButton(nameObj) { + return html` this.uploadAvatar(nameObj)}>perm_identity Set Avatar` + } + + async uploadAvatar(nameObj) { + let name = nameObj.name + window.location.href = `../qdn/publish/index.html?service=THUMBNAIL&identifier=qortal_avatar&name=${name}&uploadType=file&category=Avatar&showName=false&showService=false&showIdentifier=false` + } + // getNamesGrid() { // const myGrid = this.shadowRoot.querySelector('#namesGrid') @@ -207,6 +231,12 @@ class NameRegistration extends LitElement { parentEpml.imReady() } + 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() diff --git a/qortal-ui-plugins/plugins/core/node-management/node-management.src.js b/qortal-ui-plugins/plugins/core/node-management/node-management.src.js index 6f995616..bf5aae81 100644 --- a/qortal-ui-plugins/plugins/core/node-management/node-management.src.js +++ b/qortal-ui-plugins/plugins/core/node-management/node-management.src.js @@ -274,7 +274,7 @@ class NodeManagement extends LitElement { forceSyncPeer (peerAddress, rowIndex) { parentEpml .request("apiCall", { - url: `/admin/forcesync`, + url: `/admin/forcesync?apiKey=${this.getApiKey()}`, method: "POST", body: peerAddress, }) @@ -286,7 +286,7 @@ class NodeManagement extends LitElement { removePeer(peerAddress, rowIndex) { parentEpml .request("apiCall", { - url: `/peers`, + url: `/peers?apiKey=${this.getApiKey()}`, method: "DELETE", body: peerAddress, }) @@ -307,7 +307,7 @@ class NodeManagement extends LitElement { parentEpml .request("apiCall", { - url: `/peers`, + url: `/peers?apiKey=${this.getApiKey()}`, method: "POST", body: addPeerAddress, }) @@ -327,7 +327,7 @@ class NodeManagement extends LitElement { parentEpml .request("apiCall", { - url: `/admin/mintingaccounts`, + url: `/admin/mintingaccounts?apiKey=${this.getApiKey()}`, method: "POST", body: this.addMintingAccountKey, }) @@ -384,7 +384,7 @@ class NodeManagement extends LitElement { this.removeMintingAccountLoading = true; parentEpml.request("apiCall", { - url: `/admin/mintingaccounts`, + url: `/admin/mintingaccounts?apiKey=${this.getApiKey()}`, method: "DELETE", body: publicKey, }).then((res) => { @@ -492,6 +492,12 @@ class NodeManagement extends LitElement { parentEpml.imReady(); } + 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() diff --git a/qortal-ui-plugins/plugins/core/qdn/browser/browser.src.js b/qortal-ui-plugins/plugins/core/qdn/browser/browser.src.js new file mode 100644 index 00000000..4a83d477 --- /dev/null +++ b/qortal-ui-plugins/plugins/core/qdn/browser/browser.src.js @@ -0,0 +1,475 @@ +import { LitElement, html, css } from 'lit-element' +import { render } from 'lit-html' +import { Epml } from '../../../../epml' + +import '@material/mwc-button' +import '@material/mwc-textfield' +import '@material/mwc-select' +import '@material/mwc-list/mwc-list-item.js' +import '@material/mwc-slider' +import '@polymer/paper-progress/paper-progress.js' + +const parentEpml = new Epml({ type: 'WINDOW', source: window.parent }) + +class WebBrowser extends LitElement { + static get properties() { + return { + url: { type: String }, + name: { type: String }, + service: { type: String }, + identifier: { type: String }, + followedNames: { type: Array }, + blockedNames: { type: Array }, + } + } + + 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 { + /* --paper-button-ink-color: var(--paper-green-500); + color: var(--paper-green-500); */ + width: auto !important; + } + + .address-bar { + position: absolute; + top: 0; + left: 0; + right: 0; + height: 100px; + background-color: 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 #000000; + } + + .iframe-container iframe { + display: block; + width: 100%; + height: 100%; + border: none; + background-color: #ffffff; + } + + input[type=text] { + margin: 0; + padding: 2px 0 0 20px; + border: 0; + height: 34px; + font-size: 16px; + background-color: #ffffff; + } + + paper-progress { + --paper-progress-active-color: var(--mdc-theme-primary); + } + + .float-right { + float: right; + } + + ` + } + + render() { + return html` +
    +
    +
    + this.goBack()} title="Back" class="address-bar-button">arrow_back_ios + this.goForward()} title="Forward" class="address-bar-button">arrow_forward_ios + this.refresh()} title="Reload" class="address-bar-button">refresh + this.goBackToList()} title="Back to list" class="address-bar-button">home + + this.delete()} title="Delete ${this.service} ${this.name} from node" class="address-bar-button float-right">delete + ${this.renderBlockUnblockButton()} + ${this.renderFollowUnfollowButton()} +
    +
    + +
    +
    +
    + ` + } + + 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="Follow ${this.name}" class="address-bar-button float-right">add_to_queue` + } + else { + // render unfollow button + return html` this.unfollow()} title="Unfollow ${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="Block ${this.name}" class="address-bar-button float-right">block` + } + else { + // render unblock button + return html` this.unblock()} title="Unblock ${this.name}" class="address-bar-button float-right">radio_button_unchecked` + } + } + + + // Navigation + + goBack() { + window.history.back(); + } + + goForward() { + window.history.forward(); + } + + refresh() { + window.location.reload(); + } + + 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 { + parentEpml.request('showSnackBar', 'Error occurred when trying to follow this registered name. Please try again') + } + + 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 { + parentEpml.request('showSnackBar', 'Error occurred when trying to unfollow this registered name. Please try again') + } + + 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 { + parentEpml.request('showSnackBar', 'Error occurred when trying to block this registered name. Please try again') + } + + 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 { + parentEpml.request('showSnackBar', 'Error occurred when trying to unblock this registered name. Please try again') + } + + return ret + } + + async deleteCurrentResource() { + if (this.followedNames.indexOf(this.name) != -1) { + // Following name - so deleting won't work + parentEpml.request('showSnackBar', "Can't delete data from followed names. Please unfollow first."); + 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 { + parentEpml.request('showSnackBar', 'Error occurred when trying to delete this resource. Please try again') + } + + return ret + } + + + + // Helper Functions (Re-Used in Most part of the UI ) + + textColor(color) { + return color == 'light' ? 'rgba(255,255,255,0.7)' : 'rgba(0,0,0,0.87)' + } + + _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() + } + + 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 = null; // FUTURE: add support for identifiers + + this.followedNames = [] + this.blockedNames = [] + + + const getFollowedNames = async () => { + // this.followedNames = [] + + 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 () => { + // this.blockedNames = [] + + 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}`; + } + + const authorizeAndRender = () => { + parentEpml.request('apiCall', { + url: `/render/authorize/${this.name}?apiKey=${this.getApiKey()}`, + method: "POST" + }).then(res => { + console.log(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() + } + }) + }) + } + + firstUpdated() { + + window.addEventListener('contextmenu', (event) => { + event.preventDefault() + this._textMenu(event) + }) + + window.addEventListener('click', () => { + parentEpml.request('closeCopyTextMenu', null) + }) + + window.onkeyup = (e) => { + if (e.keyCode === 27) { + parentEpml.request('closeCopyTextMenu', null) + } + } + } + + 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('web-browser', WebBrowser) diff --git a/qortal-ui-plugins/plugins/core/qdn/browser/index.html b/qortal-ui-plugins/plugins/core/qdn/browser/index.html new file mode 100644 index 00000000..d8a117b9 --- /dev/null +++ b/qortal-ui-plugins/plugins/core/qdn/browser/index.html @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + diff --git a/qortal-ui-plugins/plugins/core/qdn/data-management/data-management.src.js b/qortal-ui-plugins/plugins/core/qdn/data-management/data-management.src.js new file mode 100644 index 00000000..f76c2882 --- /dev/null +++ b/qortal-ui-plugins/plugins/core/qdn/data-management/data-management.src.js @@ -0,0 +1,434 @@ +import { LitElement, html, css } from 'lit-element' +import { render } from 'lit-html' +import { Epml } from '../../../../epml' + +import '@material/mwc-icon' +import '@material/mwc-button' + +import '@vaadin/vaadin-grid/vaadin-grid.js' +import '@vaadin/vaadin-grid/theme/material/all-imports.js' + +const parentEpml = new Epml({ type: 'WINDOW', source: window.parent }) + +class DataManagement extends LitElement { + static get properties() { + return { + loading: { type: Boolean }, + resources: { type: Array }, + blockedNames: { type: Array }, + followedNames: { type: Array }, + } + } + + static get observers() { + return ['_kmxKeyUp(amount)'] + } + + static get styles() { + return css` + * { + --mdc-theme-primary: rgb(3, 169, 244); + --paper-input-container-focus-color: var(--mdc-theme-primary); + } + #websites-list-page { + background: #fff; + padding: 12px 24px; + } + + .divCard { + border: 1px solid #eee; + padding: 1em; + /** box-shadow: 0 1px 1px 0 rgba(0,0,0,0.14), 0 2px 1px -1px rgba(0,0,0,0.12), 0 1px 2px 0 rgba(0,0,0,0.20); **/ + box-shadow: 0 .3px 1px 0 rgba(0,0,0,0.14), 0 1px 1px -1px rgba(0,0,0,0.12), 0 1px 2px 0 rgba(0,0,0,0.20); + margin-bottom: 2em; + } + + h2 { + margin:0; + } + + h2, h3, h4, h5 { + color:#333; + font-weight: 400; + } + + a.visitSite { + color: #000; + text-decoration: none; + } + + [hidden] { + display: hidden !important; + visibility: none !important; + } + .details { + display: flex; + font-size: 18px; + } + span { + font-size: 18px; + word-break: break-all; + } + select { + padding: 13px 20px; + width: 100%; + font-size: 14px; + color: #555; + font-weight: 400; + } + .title { + font-weight:600; + font-size:12px; + line-height: 32px; + opacity: 0.66; + } + .itemList { + padding:0; + } + .default-identifier { + font-size: 14px; + font-style: italic; + } + ` + } + + render() { + return html` +
    +
    +

    Data Management

    +
    + +
    +

    Data hosted by this node

    + + + { + render(html`${this.renderName(data.item)}`, root) + }}> + { + render(html`${this.renderService(data.item)}`, root) + }}> + { + render(html`${this.renderIdentifier(data.item)}`, root) + }}> + { + render(html`${this.renderDeleteButton(data.item)}`, root); + }}> + { + render(html`${this.renderBlockUnblockButton(data.item)}`, root); + }}> + + ${this.renderDefaultText()} +
    +
    + ` + } + + goBack() { + window.history.back(); + } + + renderDefaultText() { + if (this.resources == null || !Array.isArray(this.resources)) { + return html`
    Couldn't fetch hosted data list from node` + } + if (this.isEmptyArray(this.resources)) { + return html`
    This node isn't hosting any data`; + } + return ''; + } + + renderName(resource) { + let name = resource.name + return html`${name}` + } + + renderService(resource) { + let service = resource.service + return html`${service}` + } + + renderIdentifier(resource) { + return resource.identifier == null ? html`default` : html`${resource.identifier}` + } + + renderDeleteButton(resource) { + let name = resource.name + + // Only show the block/unblock button if we have permission to modify the list on this node + // We can use the blocked names list for this, as it won't be a valid array if we have no access + if (this.blockedNames == null || !Array.isArray(this.blockedNames)) { + return html`` + } + + // We need to check if we are following this name, as if we are, there is no point in deleting anything + // as it will be re-fetched immediately. In these cases we should show an UNFOLLOW button. + if (this.followedNames.indexOf(name) != -1) { + // render unfollow button + return html` this.unfollowName(resource)}>remove_from_queue Unfollow` + } + + // render delete button + return html` this.deleteResource(resource)} onclick="this.blur();">delete Delete` + } + + renderBlockUnblockButton(resource) { + let name = resource.name + + // 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(name) === -1) { + // render block button + return html` this.blockName(resource)}>block Block` + } + else { + // render unblock button + return html` this.unblockName(resource)}>radio_button_unchecked Unblock` + } + } + + async blockName(resource) { + let name = resource.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 { + parentEpml.request('showSnackBar', 'Error occurred when trying to block this registered name. Please try again') + } + + return ret + } + + async unfollowName(websiteObj) { + let name = websiteObj.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 { + parentEpml.request('showSnackBar', 'Error occurred when trying to unfollow this registered name. Please try again') + } + + return ret + } + + async unblockName(resource) { + let name = resource.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 { + parentEpml.request('showSnackBar', 'Error occurred when trying to unblock this registered name. Please try again') + } + + return ret + } + + async deleteResource(resource) { + let identifier = resource.identifier == null ? "default" : resource.identifier; + + let ret = await parentEpml.request('apiCall', { + url: `/arbitrary/resource/${resource.service}/${resource.name}/${identifier}?apiKey=${this.getApiKey()}`, + method: 'DELETE' + }) + + if (ret === true) { + // Successfully deleted - so refresh the page + this.getArbitraryResources(); + } + else { + parentEpml.request('showSnackBar', 'Error occurred when trying to delete this resource. Please try again') + } + + return ret + } + + + // Helper Functions (Re-Used in Most part of the UI ) + + textColor(color) { + return color == 'light' ? 'rgba(255,255,255,0.7)' : 'rgba(0,0,0,0.87)' + } + + _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() + } + + constructor() { + super() + this.selectedAddress = {} + this.resources = [] + this.blockedNames = [] + this.followedNames = [] + this.isLoading = false + } + + getArbitraryResources = async () => { + // this.resources = [] + + let resources = await parentEpml.request('apiCall', { + url: `/arbitrary/hosted/resources?apiKey=${this.getApiKey()}` + }) + + this.resources = resources + } + + getBlockedNames = async () => { + // this.blockedNames = [] + + let blockedNames = await parentEpml.request('apiCall', { + url: `/lists/blockedNames?apiKey=${this.getApiKey()}` + }) + + this.blockedNames = blockedNames + } + + getFollowedNames = async () => { + // this.followedNames = [] + + let followedNames = await parentEpml.request('apiCall', { + url: `/lists/followedNames?apiKey=${this.getApiKey()}` + }) + + this.followedNames = followedNames + } + + firstUpdated() { + + window.addEventListener('contextmenu', (event) => { + event.preventDefault() + this._textMenu(event) + }) + + window.addEventListener('click', () => { + parentEpml.request('closeCopyTextMenu', null) + }) + + window.onkeyup = (e) => { + if (e.keyCode === 27) { + parentEpml.request('closeCopyTextMenu', null) + } + } + + 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) { + setTimeout(this.getArbitraryResources, 1) + setTimeout(this.getFollowedNames, 1) + setTimeout(this.getBlockedNames, 1) + setInterval(this.getArbitraryResources, 30*1000) + setInterval(this.getFollowedNames, 30*1000) + setInterval(this.getBlockedNames, 30*1000) + configLoaded = true + } + }) + parentEpml.subscribe('copy_menu_switch', async value => { + + if (value === 'false' && window.getSelection().toString().length !== 0) { + + this.clearSelection() + } + }) + }) + + parentEpml.imReady() + } + + 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() + } + + isEmptyArray(arr) { + if (!arr) { return true } + return arr.length === 0 + } +} + +window.customElements.define('data-management', DataManagement) diff --git a/qortal-ui-plugins/plugins/core/qdn/data-management/index.html b/qortal-ui-plugins/plugins/core/qdn/data-management/index.html new file mode 100644 index 00000000..24d49dbe --- /dev/null +++ b/qortal-ui-plugins/plugins/core/qdn/data-management/index.html @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + diff --git a/qortal-ui-plugins/plugins/core/qdn/index.html b/qortal-ui-plugins/plugins/core/qdn/index.html new file mode 100644 index 00000000..c9192227 --- /dev/null +++ b/qortal-ui-plugins/plugins/core/qdn/index.html @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + + diff --git a/qortal-ui-plugins/plugins/core/qdn/publish/index.html b/qortal-ui-plugins/plugins/core/qdn/publish/index.html new file mode 100644 index 00000000..484d8dca --- /dev/null +++ b/qortal-ui-plugins/plugins/core/qdn/publish/index.html @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + diff --git a/qortal-ui-plugins/plugins/core/qdn/publish/publish.src.js b/qortal-ui-plugins/plugins/core/qdn/publish/publish.src.js new file mode 100644 index 00000000..bb51dda3 --- /dev/null +++ b/qortal-ui-plugins/plugins/core/qdn/publish/publish.src.js @@ -0,0 +1,518 @@ +import { LitElement, html, css } from 'lit-element' +import { render } from 'lit-html' +import { Epml } from '../../../../epml' + +import '@material/mwc-button' +import '@material/mwc-textfield' +import '@material/mwc-select' +import '@material/mwc-list/mwc-list-item.js' +import '@material/mwc-slider' +import '@polymer/paper-progress/paper-progress.js' + +const parentEpml = new Epml({ type: 'WINDOW', source: window.parent }) + +class PublishData 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 }, + serviceLowercase: { type: String }, + names: { type: Array }, + registeredName: { type: String }, + selectedName: { type: String }, + path: { type: String }, + portForwardingEnabled: { type: Boolean }, + //selectedAddress: { type: Object }, + + amount: { type: Number }, + generalMessage: { type: String }, + successMessage: { type: String }, + errorMessage: { type: String }, + loading: { type: Boolean }, + btnDisable: { type: Boolean }, + } + } + + 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); + } + + #publishWrapper paper-button { + float: right; + } + + #publishWrapper .buttons { + /* --paper-button-ink-color: var(--paper-green-500); + color: var(--paper-green-500); */ + width: auto !important; + } + + mwc-textfield { + margin: 0; + } + + paper-progress { + --paper-progress-active-color: var(--mdc-theme-primary); + } + + .upload-text { + display: block; + font-size: 14px; + } + + .address-bar { + position: absolute; + top: 0; + left: 0; + right: 0; + height: 100px; + background-color: white; + height: 36px; + } + + .address-bar-button mwc-icon { + width: 30px; + } + ` + } + + render() { + return html` +
    +
    +
    + this.goBack()} class="address-bar-button">arrow_back_ios Back +
    + +
    +

    Publish / Update ${this.category}

    +

    Note: it is recommended that you set up port forwarding before hosting data, so that it can more easily accessed by peers on the network.

    +
    +
    + +

    + this.selectName(e)} style="min-width: 130px; max-width:100%; width:100%;"> + ${this.registeredName} + +

    + ${this.renderUploadField()} +

    + +

    +

    + +

    + +

    ${this.generalMessage}

    +

    ${this.errorMessage}

    +

    ${this.successMessage}

    + + ${this.loading ? html` ` : ''} + +
    +
    + this.doPublish(e)}>Publish   +
    +
    +
    +
    + ` + } + + // Navigation + + goBack() { + window.history.back(); + } + + + renderUploadField() { + if (this.uploadType === "file") { + return html`

    + +

    `; + } + else if (this.uploadType === "zip") { + return html`

    + Select zip file containing static content:
    + +

    `; + } + else { + return html`

    + +

    `; + } + } + + + 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 + parentEpml.request('showSnackBar', 'Please select a registered name to publish data for') + } + else if (this.uploadType === "file" && file == null) { + parentEpml.request('showSnackBar', 'Please select a file to host') + } + else if (this.uploadType === "zip" && file == null) { + parentEpml.request('showSnackBar', 'Please select a zip file to host') + } + else if (this.uploadType === "path" && path === '') { + parentEpml.request('showSnackBar', 'Please enter the directory path containing the static content') + } + else if (service === '') { + parentEpml.request('showSnackBar', 'Please enter a service name') + } + 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); + } + + this.generalMessage = "Processing data... this can take some time..."; + + let transactionBytes = await uploadData(registeredName, path, file) + if (transactionBytes.error) { + this.errorMessage = "Error: " + transactionBytes.message + showError(this.errorMessage) + throw new Error(this.errorMessage); + } + else if (transactionBytes.includes("Error 500 Internal Server Error")) { + this.errorMessage = "Internal Server Error when publishing data" + showError(this.errorMessage) + throw new Error(this.errorMessage); + } + + this.generalMessage = "Computing proof of work... this can take some time..."; + + let signAndProcessRes = await signAndProcess(transactionBytes) + if (signAndProcessRes.error) { + this.errorMessage = "Error: " + signAndProcessRes.message + showError(this.errorMessage) + throw new Error(this.errorMessage); + } + + this.btnDisable = false + this.loading = false + this.errorMessage = '' + this.generalMessage = '' + this.successMessage = 'Transaction successful!' + } + + 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'); + } + + let uploadDataUrl = `/arbitrary/${this.service}/${registeredName}${urlSuffix}` + if (identifier != null && identifier.trim().length > 0) { + uploadDataUrl = `/arbitrary/${service}/${registeredName}/${this.identifier}${urlSuffix}` + } + + let uploadDataRes = await parentEpml.request('apiCall', { + type: 'api', + method: 'POST', + url: `${uploadDataUrl}?apiKey=${this.getApiKey()}`, + 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) { + this.errorMessage = "Error: " + 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) { + myResponse.error = "Unable to sign and process transaction" + } + else { + myResponse = response + } + + return myResponse + } + + validate() + } + + + // Helper Functions (Re-Used in Most part of the UI ) + + textColor(color) { + return color == 'light' ? 'rgba(255,255,255,0.7)' : 'rgba(0,0,0,0.87)' + } + + _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() + } + + constructor() { + super() + + this.showName = false; + this.showService = false + this.showIdentifier = 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 (this.identifier != null) { + if (this.identifier === "null" || this.identifier.trim().length == 0) { + this.identifier = null + } + } + + this.portForwardingEnabled = true // Default to true so the message doesn't appear and disappear quickly + this.names = [] + this.registeredName = '' + this.selectedName = 'invalid' + this.path = '' + //this.selectedAddress = {} + this.successMessage = '' + this.generalMessage = '' + this.errorMessage = '' + this.loading = false + this.btnDisable = false + + const fetchNames = () => { + parentEpml.request('apiCall', { + url: `/names/address/${this.selectedAddress.address}?limit=0&reverse=true` + }).then(res => { + + setTimeout(() => { + this.names = res + this.registeredName = res[0].name; + }, 1) + }) + setTimeout(fetchNames, 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(fetchNames, 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(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() + } + }) + }) + } + + firstUpdated() { + + window.addEventListener('contextmenu', (event) => { + event.preventDefault() + this._textMenu(event) + }) + + window.addEventListener('click', () => { + parentEpml.request('closeCopyTextMenu', null) + }) + + window.onkeyup = (e) => { + if (e.keyCode === 27) { + parentEpml.request('closeCopyTextMenu', null) + } + } + } + + selectName(e) { + const name = this.shadowRoot.getElementById('registeredName').innerHTML + this.selectedName = name + } + + 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-data', PublishData) diff --git a/qortal-ui-plugins/plugins/core/qdn/websites.src.js b/qortal-ui-plugins/plugins/core/qdn/websites.src.js new file mode 100644 index 00000000..bd48c369 --- /dev/null +++ b/qortal-ui-plugins/plugins/core/qdn/websites.src.js @@ -0,0 +1,487 @@ + +import { LitElement, html, css } from 'lit-element' +import { render } from 'lit-html' +import { Epml } from '../../../epml.js' + +import '@material/mwc-icon' +import '@material/mwc-button' + +import '@vaadin/vaadin-grid/vaadin-grid.js' +import '@vaadin/vaadin-grid/theme/material/all-imports.js' + +const parentEpml = new Epml({ type: 'WINDOW', source: window.parent }) + +class Websites extends LitElement { + static get properties() { + return { + service: { type: String }, + identifier: { type: String }, + loading: { type: Boolean }, + resources: { type: Array }, + followedNames: { type: Array }, + blockedNames: { type: Array }, + relayMode: { type: Boolean }, + selectedAddress: { type: Object }, + } + } + + static get styles() { + return css` + * { + --mdc-theme-primary: rgb(3, 169, 244); + --paper-input-container-focus-color: var(--mdc-theme-primary); + } + #websites-list-page { + background: #fff; + padding: 12px 24px; + } + + .divCard { + border: 1px solid #eee; + padding: 1em; + /** box-shadow: 0 1px 1px 0 rgba(0,0,0,0.14), 0 2px 1px -1px rgba(0,0,0,0.12), 0 1px 2px 0 rgba(0,0,0,0.20); **/ + box-shadow: 0 .3px 1px 0 rgba(0,0,0,0.14), 0 1px 1px -1px rgba(0,0,0,0.12), 0 1px 2px 0 rgba(0,0,0,0.20); + margin-bottom: 2em; + } + + h2 { + margin:0; + } + + h2, h3, h4, h5 { + color:#333; + font-weight: 400; + } + + a.visitSite { + color: #000; + text-decoration: none; + } + + [hidden] { + display: hidden !important; + visibility: none !important; + } + .details { + display: flex; + font-size: 18px; + } + span { + font-size: 14px; + word-break: break-all; + } + select { + padding: 13px 20px; + width: 100%; + font-size: 14px; + color: #555; + font-weight: 400; + } + .title { + font-weight:600; + font-size:12px; + line-height: 32px; + opacity: 0.66; + } + .itemList { + padding:0; + } + /* .itemList > * { + padding-left:24px; + padding-right:24px; + } */ + .relay-mode-notice { + margin:auto; + text-align:center; + word-break:normal; + font-size:14px; + line-height:20px; + color:rgb(100,100,100); + } + ` + } + + constructor() { + super() + this.service = "WEBSITE" + this.identifier = null + this.selectedAddress = {} + this.resources = [] + this.followedNames = [] + this.blockedNames = [] + this.relayMode = null + this.isLoading = false + } + + render() { + return html` +
    +
    +

    Browse Websites

    + ${this.renderPublishButton()} +
    + +
    +

    Websites

    + + + { + render(html`${this.renderName(data.item)}`, root) + }}> + { + render(html`${this.renderStatus(data.item)}`, root) + }}> + { + render(html`${this.renderFollowUnfollowButton(data.item)}`, root); + }}> + { + render(html`${this.renderBlockUnblockButton(data.item)}`, root); + }}> + + + ${this.isEmptyArray(this.resources) ? html` + No websites available + `: ''} +
    + ${this.renderRelayModeText()} +
    + ` + } + + renderRelayModeText() { + if (this.relayMode === true) { + return html`
    Relay mode is enabled. This means that your node will help to transport encrypted data around the network when a peer requests it. You can opt out by setting "relayModeEnabled": false in settings.json
    `; + } + else if (this.relayMode === false) { + return html`
    Relay mode is disabled. You can enable it by setting "relayModeEnabled": true in settings.json
    `; + } + return html``; + } + + renderPublishButton() { + // Only show the publish button if we have admin permissions on this node + // We can check the followed names array to achieve this + if (this.followedNames == null || !Array.isArray(this.followedNames)) { + return html`` + } + + return html` this.publishWebsite()}>addPublish Website` + } + + publishWebsite() { + window.location.href = `publish/index.html?service=${this.service}&identifier=${this.identifier}&uploadType=zip&category=Website&showName=true&showService=false&showIdentifier=false` + } + + async followName(websiteObj) { + let name = websiteObj.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 { + parentEpml.request('showSnackBar', 'Error occurred when trying to follow this registered name. Please try again') + } + + return ret + } + + async unfollowName(websiteObj) { + let name = websiteObj.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 { + parentEpml.request('showSnackBar', 'Error occurred when trying to unfollow this registered name. Please try again') + } + + return ret + } + + async blockName(websiteObj) { + let name = websiteObj.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 { + parentEpml.request('showSnackBar', 'Error occurred when trying to block this registered name. Please try again') + } + + return ret + } + + async unblockName(websiteObj) { + let name = websiteObj.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 { + parentEpml.request('showSnackBar', 'Error occurred when trying to unblock this registered name. Please try again') + } + + return ret + } + + renderRole(groupObj) { + if (groupObj.owner === this.selectedAddress.address) { + return "Owner" + } else if (groupObj.isAdmin === true) { + return "Admin" + } else { + return "Member" + } + } + + renderName(websiteObj) { + let name = websiteObj.name + + return html`${name}` + } + + renderStatus(websiteObj) { + return html`${websiteObj.status.title}` + } + + renderFollowUnfollowButton(websiteObj) { + let name = websiteObj.name + + // 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(name) === -1) { + // render follow button + return html` this.followName(websiteObj)}>add_to_queue Follow` + } + else { + // render unfollow button + return html` this.unfollowName(websiteObj)}>remove_from_queue Unfollow` + } + } + + renderBlockUnblockButton(websiteObj) { + let name = websiteObj.name + + // 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(name) === -1) { + // render block button + return html` this.blockName(websiteObj)}>block Block` + } + else { + // render unblock button + return html` this.unblockName(websiteObj)}>radio_button_unchecked Unblock` + } + } + + _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() + } + + + firstUpdated() { + + const getArbitraryResources = async () => { + // this.resources = [] + + let resources = await parentEpml.request('apiCall', { + url: `/arbitrary/resources?service=${this.service}&default=true&limit=0&reverse=true&includestatus=true` + }) + + this.resources = resources + setTimeout(getArbitraryResources, this.config.user.nodeSettings.pingInterval) + } + + const getFollowedNames = async () => { + // this.followedNames = [] + + 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 () => { + // this.blockedNames = [] + + let blockedNames = await parentEpml.request('apiCall', { + url: `/lists/blockedNames?apiKey=${this.getApiKey()}` + }) + + this.blockedNames = blockedNames + setTimeout(getBlockedNames, this.config.user.nodeSettings.pingInterval) + } + + const getRelayMode = async () => { + + let relayMode = await parentEpml.request('apiCall', { + url: `/arbitrary/relaymode?apiKey=${this.getApiKey()}` + }) + this.relayMode = relayMode; + + setTimeout(getRelayMode, this.config.user.nodeSettings.pingInterval) + } + + + window.addEventListener("contextmenu", (event) => { + + event.preventDefault(); + this._textMenu(event) + }); + + window.addEventListener("click", () => { + + parentEpml.request('closeCopyTextMenu', null) + }); + + window.onkeyup = (e) => { + if (e.keyCode === 27) { + + parentEpml.request('closeCopyTextMenu', null) + } + } + + 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(getArbitraryResources, 1) + setTimeout(getFollowedNames, 1) + setTimeout(getBlockedNames, 1) + setTimeout(getRelayMode, 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() + } + }) + }) + + + parentEpml.imReady() + } + + 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() + } + + isEmptyArray(arr) { + if (!arr) { return true } + return arr.length === 0 + } +} + +window.customElements.define('websites-list', Websites) diff --git a/qortal-ui-plugins/plugins/core/send-coin/send-coin.src.js b/qortal-ui-plugins/plugins/core/send-coin/send-coin.src.js index b93e8f9c..978ac56c 100644 --- a/qortal-ui-plugins/plugins/core/send-coin/send-coin.src.js +++ b/qortal-ui-plugins/plugins/core/send-coin/send-coin.src.js @@ -174,12 +174,7 @@ class SendMoneyPage extends LitElement {

    - - +

    @@ -608,7 +603,7 @@ class SendMoneyPage extends LitElement { clearTimeout(this.updateAccountBalanceTimeout) parentEpml .request('apiCall', { - url: `/addresses/balance/${this.selectedAddress.address}`, + url: `/addresses/balance/${this.selectedAddress.address}?apiKey=${this.getApiKey()}`, }) .then((res) => { this.qortBalance = res @@ -807,7 +802,7 @@ class SendMoneyPage extends LitElement { updateBTCAccountBalance() { parentEpml .request('apiCall', { - url: `/crosschain/btc/walletbalance`, + url: `/crosschain/btc/walletbalance?apiKey=${this.getApiKey()}`, method: 'POST', body: window.parent.reduxStore.getState().app.selectedAddress.btcWallet.derivedMasterPublicKey, }) @@ -823,7 +818,7 @@ class SendMoneyPage extends LitElement { updateLTCAccountBalance() { parentEpml .request('apiCall', { - url: `/crosschain/ltc/walletbalance`, + url: `/crosschain/ltc/walletbalance?apiKey=${this.getApiKey()}`, method: 'POST', body: window.parent.reduxStore.getState().app.selectedAddress.ltcWallet.derivedMasterPublicKey, }) @@ -839,7 +834,7 @@ class SendMoneyPage extends LitElement { updateDOGEAccountBalance() { parentEpml .request('apiCall', { - url: `/crosschain/doge/walletbalance`, + url: `/crosschain/doge/walletbalance?apiKey=${this.getApiKey()}`, method: 'POST', body: window.parent.reduxStore.getState().app.selectedAddress.dogeWallet.derivedMasterPublicKey, }) @@ -852,6 +847,12 @@ class SendMoneyPage extends LitElement { }) } + 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() diff --git a/qortal-ui-plugins/plugins/core/streams/onNewBlock.js.old b/qortal-ui-plugins/plugins/core/streams/onNewBlock.js.old new file mode 100644 index 00000000..eaf4e934 --- /dev/null +++ b/qortal-ui-plugins/plugins/core/streams/onNewBlock.js.old @@ -0,0 +1,63 @@ +import { parentEpml } from '../connect.js' +import { EpmlStream } from 'epml' + +const BLOCK_CHECK_INTERVAL = 3000 // You should be runn off config.user.nodeSettings.pingInterval... +const BLOCK_CHECK_TIMEOUT = 3000 + +export const BLOCK_STREAM_NAME = 'new_block' + +const onNewBlockFunctions = [] + +let mostRecentBlock = { + height: -1 +} + +export const onNewBlock = newBlockFn => onNewBlockFunctions.push(newBlockFn) + +export const check = () => { + const c = doCheck() + c.then(() => { + setTimeout(() => check(), BLOCK_CHECK_INTERVAL) + }) + c.catch(() => { + setTimeout(() => check(), BLOCK_CHECK_INTERVAL) + }) +} + +const doCheck = async () => { + let timeout = setTimeout(() => { + throw new Error('Block check timed out') + }, BLOCK_CHECK_TIMEOUT) + + let _nodeStatus = {} + + const block = await parentEpml.request('apiCall', { + url: '/blocks/last' + }) + + const _nodeInfo = await parentEpml.request('apiCall', { + url: '/admin/info' + }) + + let nodeConfig = await parentEpml.request('getNodeConfig') + + if (nodeConfig.node === 0 || nodeConfig.node === 1) { + _nodeStatus = await parentEpml.request('apiCall', { + url: '/admin/status' + }) + } + + let appInfo = { + block: block, + nodeInfo: _nodeInfo, + nodeStatus: _nodeStatus + } + parentEpml.request('updateAppInfo', appInfo) + + clearTimeout(timeout) + + if (block.height > mostRecentBlock.height) { + mostRecentBlock = block + onNewBlockFunctions.forEach(fn => fn(mostRecentBlock)) + } +} diff --git a/qortal-ui-plugins/plugins/core/trade-portal/trade-portal.src.js b/qortal-ui-plugins/plugins/core/trade-portal/trade-portal.src.js index d129ef26..8ad72b1b 100644 --- a/qortal-ui-plugins/plugins/core/trade-portal/trade-portal.src.js +++ b/qortal-ui-plugins/plugins/core/trade-portal/trade-portal.src.js @@ -532,14 +532,14 @@ class TradePortal extends LitElement {
    - this.displayTabContent('buy')}> - this.displayTabContent('sell')}> + +
    - this.clearBuyForm()}> +

    You have: ${this.listedCoins.get(this.selectedCoin).balance} ${this.listedCoins.get(this.selectedCoin).coinCode}

    - this.buyAction(e)}> + ${this.isBuyLoading === false ? 'BUY' : html``}
    @@ -602,7 +602,7 @@ class TradePortal extends LitElement {
    - this.clearSellForm()}> +

    You have: ${this.listedCoins.get("QORTAL").balance} QORT

    - this.sellAction()}> + ${this.isSellLoading === false ? 'SELL' : html``}
    @@ -1644,7 +1644,7 @@ class TradePortal extends LitElement { updateAccountBalance() { clearTimeout(this.updateAccountBalanceTimeout) parentEpml.request('apiCall', { - url: `/addresses/balance/${this.selectedAddress.address}`, + url: `/addresses/balance/${this.selectedAddress.address}?apiKey=${this.getApiKey()}`, }) .then((res) => { this.listedCoins.get("QORTAL").balance = res @@ -1658,11 +1658,11 @@ class TradePortal extends LitElement { switch (this.selectedCoin) { case 'LITECOIN': - _url = `/crosschain/ltc/walletbalance` + _url = `/crosschain/ltc/walletbalance?apiKey=${this.getApiKey()}` _body = window.parent.reduxStore.getState().app.selectedAddress.ltcWallet.derivedMasterPublicKey break case 'DOGECOIN': - _url = `/crosschain/doge/walletbalance` + _url = `/crosschain/doge/walletbalance?apiKey=${this.getApiKey()}` _body = window.parent.reduxStore.getState().app.selectedAddress.dogeWallet.derivedMasterPublicKey break default: @@ -1824,6 +1824,12 @@ class TradePortal extends LitElement { } } + 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() diff --git a/qortal-ui-plugins/plugins/core/wallet/index.html b/qortal-ui-plugins/plugins/core/wallet/index.html index ac56f9ab..0a791185 100644 --- a/qortal-ui-plugins/plugins/core/wallet/index.html +++ b/qortal-ui-plugins/plugins/core/wallet/index.html @@ -1,40 +1,40 @@ - - - - + html, + body { + margin: 0; + background: white; + font-family: 'Roboto', sans-serif; + } + + diff --git a/qortal-ui-plugins/plugins/core/wallet/wallet-app.src.js b/qortal-ui-plugins/plugins/core/wallet/wallet-app.src.js index f2b9c35f..62967ba7 100644 --- a/qortal-ui-plugins/plugins/core/wallet/wallet-app.src.js +++ b/qortal-ui-plugins/plugins/core/wallet/wallet-app.src.js @@ -852,6 +852,12 @@ class MultiWallet extends LitElement { 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() @@ -946,7 +952,7 @@ async showWallet(){ //fetching the qort balance parentEpml .request('apiCall', { - url: `/addresses/balance/${this.wallets.get('qort').wallet.address}`, + url: `/addresses/balance/${this.wallets.get('qort').wallet.address}?apiKey=${this.getApiKey()}`, }) .then((res) => { if (isNaN(Number(res))) { @@ -973,7 +979,7 @@ async showWallet(){ const walletName = `${coin}Wallet` parentEpml .request('apiCall', { - url: `/crosschain/${coin}/walletbalance`, + url: `/crosschain/${coin}/walletbalance?apiKey=${this.getApiKey()}`, method: 'POST', body: `${window.parent.reduxStore.getState().app.selectedAddress[walletName].derivedMasterPublicKey}`, }) @@ -989,7 +995,7 @@ async showWallet(){ }) //fetching transactions const txs = await parentEpml.request('apiCall', { - url: `/crosschain/${coin}/wallettransactions`, + url: `/crosschain/${coin}/wallettransactions?apiKey=${this.getApiKey()}`, method: 'POST', body: `${window.parent.reduxStore.getState().app.selectedAddress[walletName].derivedMasterPublicKey}`, })