diff --git a/core/language/us.json b/core/language/us.json
index e53d4411..fea1be8b 100644
--- a/core/language/us.json
+++ b/core/language/us.json
@@ -1254,5 +1254,29 @@
"profile24": "You must fill out both field name and field value to add a custom property",
"profile25": "Is your friend",
"profile26": "Add as friend"
+ },
+ "tour": {
+ "tour1": "In order to use Qortal, the Core must be synced. This icon will be in blue when it's synced.",
+ "tour2": "Synced",
+ "tour3": "Synced and minting",
+ "tour4": "Syncing",
+ "tour5": "Sync your core",
+ "tour6": "The Unstoppable Force of Qortal",
+ "tour7": "Only you control your data on Qortal",
+ "tour8": "Qortal cannot be taken down",
+ "tour9": "Completely peer-to-peer with no centralized intermediaries",
+ "tour10": "This is the default tab view where you can access important Qortal settings and Q-apps such as Q-Tube.",
+ "tour11": "Get the full experience",
+ "tour12": "To get the full Qortal experience, we recommend following this checklist.",
+ "tour13": "You are fully synced! You can now experience the power of the Qortal blockchain.",
+ "tour14":"Let's try visiting Q-Tube!",
+ "tour15":"Visit Q-Tube",
+ "tour16": "Checklist",
+ "tour17": "Please start the Core to access the Qortal blockchain.",
+ "tour18": "Bootstrap",
+ "tour19":"Currently syncing... please wait to use Qortal to its full potential.",
+ "tour20": "blocks behind. Would you like to bootstrap to speed up the syncing process?",
+ "tour21": "blocks remaining.",
+ "tour22": "Bootstrap requested. Please wait."
}
}
diff --git a/core/src/components/app-view.js b/core/src/components/app-view.js
index 11c83388..afe909cd 100644
--- a/core/src/components/app-view.js
+++ b/core/src/components/app-view.js
@@ -42,6 +42,9 @@ import './friends-view/friends-side-panel-parent.js'
import './friends-view/save-settings-qdn.js'
import './friends-view/core-sync-status.js'
import './friends-view/profile.js'
+import './beginner-tour/tour-component.js'
+import './beginner-tour/sync-indicator.js'
+import './friends-view/beginner-checklist.js'
import './controllers/coin-balances-controller.js'
const chatLastSeen = localForage.createInstance({
@@ -540,6 +543,26 @@ class AppView extends connect(store)(LitElement) {
this.myLockScreenPass = ''
this.myLockScreenSet = ''
this.helperMessage = ''
+ this.getTourElements = this.getTourElements.bind(this)
+ }
+
+ getTourElements(){
+ let els = {}
+ console.log('this.shadowRoot.querySelector("core-sync-status")', this.shadowRoot.querySelector("core-sync-status"))
+ const el1 = this.shadowRoot.querySelector("core-sync-status").shadowRoot.getElementById("core-sync-status-id")
+ const el2 = this.shadowRoot.querySelector("show-plugin").shadowRoot.getElementById("showPluginId")
+ const el3 = this.shadowRoot.querySelector("beginner-checklist").shadowRoot.getElementById("popover-notification")
+ if(el1) {
+ els['core-sync-status-id'] = el1
+ }
+ if(el2) {
+ els['tab'] = el2
+ }
+ if(el3) {
+ els['checklist'] = el3
+ }
+
+ return els
}
render() {
@@ -570,6 +593,7 @@ class AppView extends connect(store)(LitElement) {
+
@@ -600,6 +624,8 @@ class AppView extends connect(store)(LitElement) {
+
+
diff --git a/core/src/components/beginner-tour/sync-indicator.js b/core/src/components/beginner-tour/sync-indicator.js
new file mode 100644
index 00000000..f4029800
--- /dev/null
+++ b/core/src/components/beginner-tour/sync-indicator.js
@@ -0,0 +1,281 @@
+import { LitElement, html, css } from 'lit';
+import { store } from '../../store';
+import { connect } from 'pwa-helpers';
+import '@material/mwc-icon';
+import { translate } from '../../../translate';
+import { parentEpml } from '../show-plugin';
+
+class SyncIndicator extends connect(store)(LitElement) {
+ static get properties() {
+ return {
+ isBehind: { type: Boolean },
+ blocksBehind: { type: Number },
+ isSynchronizing: { type: Boolean },
+ hasCoreRunning: { type: Boolean },
+ };
+ }
+
+ constructor() {
+ super();
+ this.isBehind = null;
+ this.blocksBehind = 0;
+ this.nodeUrl = this.getNodeUrl();
+ this.myNode = this.getMyNode();
+ this.interval = null;
+ this.hasCoreRunning = true;
+ this.seenWelcomeSync = false;
+ this.numberOfTries = 0;
+ }
+
+ static get styles() {
+ return css`
+ * {
+ --mdc-theme-text-primary-on-background: var(--black);
+ box-sizing: border-box;
+ }
+ :host {
+ box-sizing: border-box;
+ position: fixed;
+ bottom: 25px;
+ right: 25px;
+ z-index: 50000;
+ }
+ .parent {
+ width: 360px;
+ padding: 10px;
+ border-radius: 8px;
+ border: 1px solid var(--black);
+ display: flex;
+ align-items: center;
+ gap: 10px;
+ user-select: none;
+ background: var(--white);
+ }
+ .row {
+ display: flex;
+ gap: 10px;
+ width: 100%;
+ }
+ .column {
+ display: flex;
+ flex-direction: column;
+ gap: 10px;
+ width: 100%;
+ }
+ .bootstrap-button {
+ font-family: Roboto, sans-serif;
+ font-size: 16px;
+ color: var(--mdc-theme-primary);
+ background-color: transparent;
+ padding: 8px 10px;
+ border-radius: 5px;
+ border: none;
+ transition: all 0.3s ease-in-out;
+ }
+
+ .bootstrap-button:hover {
+ cursor: pointer;
+ background-color: #03a8f475;
+ }
+ `;
+ }
+ async firstUpdated() {
+ const seenWelcomeSync = JSON.parse(
+ localStorage.getItem('welcome-sync') || 'false'
+ );
+ this.seenWelcomeSync = seenWelcomeSync;
+ }
+
+ getNodeUrl() {
+ const myNode =
+ window.parent.reduxStore.getState().app.nodeConfig.knownNodes[
+ window.parent.reduxStore.getState().app.nodeConfig.node
+ ];
+
+ const nodeUrl =
+ myNode.protocol + '://' + myNode.domain + ':' + myNode.port;
+ return nodeUrl;
+ }
+ getMyNode() {
+ const myNode =
+ window.parent.reduxStore.getState().app.nodeConfig.knownNodes[
+ window.parent.reduxStore.getState().app.nodeConfig.node
+ ];
+
+ return myNode;
+ }
+
+ async getDaySummary() {
+ try {
+ const endpoint = `${this.nodeUrl}/admin/summary/?apiKey=${this.myNode.apiKey}`;
+ const res = await fetch(endpoint);
+ const data = await res.json();
+ let blockTimeInSeconds = null;
+ if (data.blockCount) {
+ const blockTime = 1440 / data.blockCount;
+ blockTimeInSeconds = blockTime * 60;
+ }
+
+ const endpointLastBlock = `${this.nodeUrl}/blocks/last`;
+ const resLastBlock = await fetch(endpointLastBlock);
+ const dataLastBlock = await resLastBlock.json();
+ const timestampNow = Date.now();
+ const currentBlockTimestamp = dataLastBlock.timestamp;
+ if (blockTimeInSeconds && currentBlockTimestamp < timestampNow) {
+ const diff = timestampNow - currentBlockTimestamp;
+ const inSeconds = diff / 1000; // millisecs to secs
+ const inBlocks = inSeconds / blockTimeInSeconds;
+ this.blocksBehind = parseInt(inBlocks);
+ if (inBlocks >= 1000) {
+ this.isBehind = true;
+ } else {
+ this.isBehind = false;
+ }
+ } else {
+ this.blocksBehind = 0;
+ this.isBehind = false;
+ }
+ } catch (error) {}
+ }
+
+ async checkHowManyBlocksBehind() {
+ try {
+ this.getDaySummary();
+ this.interval = setInterval(() => {
+ if (this.isBehind === false) {
+ this.isBehind = null;
+ clearInterval(this.interval);
+ }
+ this.getDaySummary();
+ }, 60000);
+ } catch (error) {}
+ }
+
+ stateChanged(state) {
+ console.log({state})
+ if (
+ state.app.nodeStatus &&
+ Object.keys(state.app.nodeStatus).length === 0
+ ) {
+ if (this.numberOfTries > 5) {
+ this.hasCoreRunning = false;
+ } else {
+ this.numberOfTries = this.numberOfTries + 1;
+ }
+ } else if (
+ state.app.nodeStatus &&
+ state.app.nodeStatus.syncPercent !== this.syncPercentage
+ ) {
+ this.syncPercentage = state.app.nodeStatus.syncPercent;
+
+ if (state.app.nodeStatus.syncPercent !== 100) {
+ this.isSynchronizing = true;
+ } else {
+ this.isSynchronizing = false;
+ }
+ if (
+ this.isBehind === null &&
+ state.app.nodeStatus.syncPercent === 100
+ ) {
+ this.isBehind = false;
+ this.blocksBehind = 0;
+ if (!this.seenWelcomeSync) {
+ this.dispatchEvent(
+ new CustomEvent('open-welcome-modal-sync', {
+ bubbles: true,
+ composed: true,
+ })
+ );
+ }
+ } else if (
+ !this.interval &&
+ this.isBehind === null &&
+ state.app.nodeStatus.isSynchronizing &&
+ state.app.nodeStatus.syncPercent !== 100
+ ) {
+ this.checkHowManyBlocksBehind();
+ }
+ } else {
+ this.hasCoreRunning = true;
+ }
+ }
+
+ async bootstrap(){
+ try {
+ const endpoint = `${this.nodeUrl}/admin/bootstrap/?apiKey=${this.myNode.apiKey}`;
+ const res = await fetch(endpoint);
+ const data = await res.json();
+ if(data === true){
+ parentEpml.request('showSnackBar', get('tour.tour22'));
+ }
+ console.log({data})
+ } catch (error) {
+
+ }
+ }
+
+ render() {
+ return html`
+ ${!this.hasCoreRunning
+ ? html`
+
+
priority_high
+
+ ${translate("tour.tour17")}
+
+
+ `
+ : (this.isBehind && this.isSynchronizing)
+ ? html`
+
+
+
+
+
+ ${this.blocksBehind} ${translate("tour.tour20")}
+
+
+
+ {
+ this.bootstrap()
+ }}"
+ >
+ ${translate("tour.tour18")}
+
+
+
+
+ `
+ : this.isSynchronizing
+ ? html`
+
+
+
+ ${translate("tour.tour19")} ${this.blocksBehind ? this.blocksBehind : ""} ${this.blocksBehind ? translate("tour.tour21"): ""}
+
+
+ `
+ : "" }
+ `;
+ }
+}
+customElements.define('sync-indicator', SyncIndicator);
diff --git a/core/src/components/beginner-tour/tour-component.js b/core/src/components/beginner-tour/tour-component.js
new file mode 100644
index 00000000..938cf393
--- /dev/null
+++ b/core/src/components/beginner-tour/tour-component.js
@@ -0,0 +1,396 @@
+import { LitElement, html, css } from 'lit';
+import { driver } from 'driver.js';
+import 'driver.js/dist/driver.css';
+import '@material/mwc-icon';
+import '@polymer/paper-spinner/paper-spinner-lite.js';
+import '@vaadin/tooltip';
+import '@material/mwc-button';
+import { get, translate } from '../../../translate/index.js';
+import '@polymer/paper-dialog/paper-dialog.js';
+import { setNewTab } from '../../redux/app/app-actions.js';
+import { store } from '../../store.js';
+import { connect } from 'pwa-helpers';
+import './tour.css';
+class TourComponent extends connect(store)(LitElement) {
+ static get properties() {
+ return {
+ getElements: { attribute: false },
+ dialogOpenedCongrats: { type: Boolean },
+ hasViewedTour: { type: Boolean },
+ };
+ }
+
+ constructor() {
+ super();
+ this.dialogOpenedCongrats = false;
+ this._controlOpenWelcomeModal =
+ this._controlOpenWelcomeModal.bind(this);
+ this.hasName = false;
+ this.nodeUrl = this.getNodeUrl();
+ this.myNode = this.getMyNode();
+ }
+
+ static get styles() {
+ return css`
+ * {
+ --mdc-theme-primary: rgb(3, 169, 244);
+ --mdc-theme-secondary: var(--mdc-theme-primary);
+ --mdc-theme-surface: var(--white);
+ --mdc-dialog-content-ink-color: var(--black);
+ box-sizing: border-box;
+ color: var(--black);
+ background: var(--white);
+ }
+
+ :host {
+ box-sizing: border-box;
+ position: fixed;
+ bottom: 25px;
+ right: 25px;
+ z-index: 50000;
+ }
+
+ .full-info-wrapper {
+ width: 100%;
+ min-width: 600px;
+ max-width: 600px;
+ text-align: center;
+ background: var(--white);
+ border: 1px solid var(--black);
+ border-radius: 15px;
+ padding: 25px;
+ box-shadow: 0px 10px 15px rgba(0, 0, 0, 0.1);
+ display: block !important;
+ }
+
+ .buttons {
+ display: inline;
+ }
+ .accept-button {
+ font-family: Roboto, sans-serif;
+ letter-spacing: 0.3px;
+ font-weight: 300;
+ padding: 8px 5px;
+ border-radius: 3px;
+ text-align: center;
+ color: var(--black);
+ transition: all 0.3s ease-in-out;
+ display: flex;
+ align-items: center;
+ gap: 10px;
+ font-size: 18px;
+ justify-content: center;
+ outline: 1px solid var(--black);
+ }
+
+ .accept-button:hover {
+ cursor: pointer;
+ background-color: #03a8f485;
+ }
+
+ .close-button {
+ font-family: Roboto, sans-serif;
+ letter-spacing: 0.3px;
+ font-weight: 300;
+ padding: 8px 5px;
+ border-radius: 3px;
+ text-align: center;
+ color: #f44336;
+ transition: all 0.3s ease-in-out;
+ display: flex;
+ align-items: center;
+ gap: 10px;
+ font-size: 18px;
+ width:auto;
+ }
+
+ .close-button:hover {
+ cursor: pointer;
+ background-color: #f4433663;
+ }
+ `;
+ }
+
+ _controlOpenWelcomeModal() {
+ this.isSynced = true
+
+ const seenWelcomeSync = JSON.parse(
+ localStorage.getItem('welcome-sync') || 'false'
+ );
+ if (this.hasName) return;
+ if (seenWelcomeSync) return;
+ if(!this.hasViewedTour) return
+ this.dialogOpenedCongrats = true;
+ }
+
+ openWelcomeModal() {
+ this.dispatchEvent(
+ new CustomEvent('send-tour-finished', {
+ bubbles: true,
+ composed: true,
+ })
+ );
+ const seenWelcomeSync = JSON.parse(
+ localStorage.getItem('welcome-sync') || 'false'
+ );
+ if (this.hasName) return;
+ if (seenWelcomeSync) return;
+ if(!this.isSynced) return
+ this.dialogOpenedCongrats = true;
+ }
+
+ connectedCallback() {
+ super.connectedCallback();
+ window.addEventListener(
+ 'open-welcome-modal-sync',
+ this._controlOpenWelcomeModal
+ );
+ }
+
+ disconnectedCallback() {
+ window.removeEventListener(
+ 'open-welcome-modal-sync',
+ this._controlOpenWelcomeModal
+ );
+
+ super.disconnectedCallback();
+ }
+
+ getNodeUrl() {
+ const myNode =
+ window.parent.reduxStore.getState().app.nodeConfig.knownNodes[
+ window.parent.reduxStore.getState().app.nodeConfig.node
+ ];
+
+ const nodeUrl =
+ myNode.protocol + '://' + myNode.domain + ':' + myNode.port;
+ return nodeUrl;
+ }
+ getMyNode() {
+ const myNode =
+ window.parent.reduxStore.getState().app.nodeConfig.knownNodes[
+ window.parent.reduxStore.getState().app.nodeConfig.node
+ ];
+
+ return myNode;
+ }
+
+ async getName(recipient) {
+ try {
+ const endpoint = `${this.nodeUrl}/names/address/${recipient}`;
+ const res = await fetch(endpoint);
+ const getNames = await res.json();
+
+ if (Array.isArray(getNames) && getNames.length > 0) {
+ return getNames[0].name;
+ } else {
+ return '';
+ }
+ } catch (error) {
+ return '';
+ }
+ }
+ async firstUpdated() {
+ const hasViewedTour = JSON.parse(
+ localStorage.getItem('hasViewedTour') || 'false'
+ );
+ const selectedAddress = store.getState().app.selectedAddress || '';
+ const name = await this.getName(selectedAddress.address);
+ if (name) {
+ this.hasName = true;
+ }
+ this.hasViewedTour = hasViewedTour;
+ if (!hasViewedTour) {
+ try {
+ if (name) {
+ this.hasViewedTour = true;
+ this.hasName = true;
+ localStorage.setItem("hasViewedTour", JSON.stringify(true))
+ }
+ } catch (error) {
+ console.log({ error });
+ }
+ }
+ await new Promise((res) => {
+ setTimeout(() => {
+ res();
+ }, 1000);
+ });
+ if (!this.hasViewedTour) {
+ const elements = this.getElements();
+ let steps = [
+ {
+ popover: {
+ title: get("tour.tour6"),
+ description: `
+
+
+
+
+
+
+ `,
+ // ... other options
+ },
+ },
+ ];
+ const step2 = elements['core-sync-status-id'];
+ const step3 = elements['tab'];
+ const step4 = elements['checklist'];
+
+ if (step2) {
+ steps.push({
+ element: step2,
+ popover: {
+ title: get("tour.tour5"),
+ description: `
+
+
+
+
${get("tour.tour2")}
+
+
+
+
${get("tour.tour3")}
+
+
+
+
${get("tour.tour4")}
+
+
+ `,
+ },
+ });
+ }
+ if (step3) {
+ steps.push({
+ element: step3,
+ popover: {
+ title: 'Tab View',
+ description: `
+
+
${get("tour.tour10")}
+
+
+
+
+
You can also bookmark other Q-Apps and Plugins by clicking on the ${get(
+ 'tabmenu.tm19'
+ )} button
+
+ `,
+ },
+ });
+ }
+ if (step4) {
+ steps.push(
+ {
+ element: step4,
+ popover: {
+ title: get("tour.tour11"),
+ description: get("tour.tour12"),
+ },
+ }
+ );this.hasViewedTour
+ }
+ let currentStepIndex = 0;
+ const driverObj = driver({
+ popoverClass: 'driverjs-theme',
+ showProgress: true,
+ showButtons: ['next', 'previous'],
+ steps: steps,
+ allowClose: false,
+ onDestroyed: () => {
+ localStorage.setItem("hasViewedTour", JSON.stringify(true))
+ this.hasViewedTour = true;
+ this.openWelcomeModal();
+ }
+ });
+
+ driverObj.drive();
+ } else {
+ this.dispatchEvent(
+ new CustomEvent('send-tour-finished', {
+ bubbles: true,
+ composed: true,
+ })
+ );
+ }
+ }
+
+ visitQtube() {
+ this.onClose();
+ const query = `?service=APP&name=Q-Tube`;
+ store.dispatch(
+ setNewTab({
+ url: `qdn/browser/index.html${query}`,
+ id: 'q-mail-notification',
+ myPlugObj: {
+ url: 'myapp',
+ domain: 'core',
+ page: `qdn/browser/index.html${query}`,
+ title: 'Q-Tube',
+ menus: [],
+ parent: false,
+ },
+ })
+ );
+ }
+
+ onClose() {
+ localStorage.setItem("welcome-sync", JSON.stringify(true))
+ this.dialogOpenedCongrats = false;
+ }
+
+ render() {
+ return html`
+
+ ${this.dialogOpenedCongrats && this.hasViewedTour
+ ? html`
+
+ Congratulations!
+
+ ${translate("tour.tour13")}
+
+
+ ${translate("tour.tour14")}
+
+
+
+ ${translate("tour.tour15")}
+
+
+
{
+ this.dialogOpenedCongrats = false
+ }}
+ >
+ ${translate("general.close")}
+
+
+
+ `
+ : ''}
+ `;
+ }
+}
+customElements.define('tour-component', TourComponent);
diff --git a/core/src/components/beginner-tour/tour.css b/core/src/components/beginner-tour/tour.css
new file mode 100644
index 00000000..9f9e971f
--- /dev/null
+++ b/core/src/components/beginner-tour/tour.css
@@ -0,0 +1,77 @@
+.driver-popover.driverjs-theme {
+ background-color: var(--white);
+ color: var(--black);
+ max-width: 500px;
+ width: auto;
+ }
+
+ .driver-popover.driverjs-theme .driver-popover-title {
+ font-size: 20px;
+ text-align: center;
+ }
+
+ .driver-popover.driverjs-theme .driver-popover-title,
+ .driver-popover.driverjs-theme .driver-popover-description,
+ .driver-popover.driverjs-theme .driver-popover-progress-text {
+ color: var(--black);
+ font-family: Roboto, sans-serif;
+ }
+ .driver-popover.driverjs-theme .driver-popover-description {
+ font-size: 16px;
+ }
+
+ .driver-popover.driverjs-theme button {
+ flex: 1;
+ text-align: center;
+ background-color: #000;
+ color: #ffffff;
+ border: 2px solid #000;
+ text-shadow: none;
+ font-size: 14px;
+ padding: 5px 8px;
+ border-radius: 6px;
+ }
+
+ .driver-popover.driverjs-theme .test-span {
+ color: green;
+ }
+
+ .driver-popover.driverjs-theme button:hover {
+ background-color: #000;
+ color: #ffffff;
+ }
+
+ .driver-popover.driverjs-theme .driver-popover-navigation-btns {
+ justify-content: space-between;
+ gap: 3px;
+ }
+
+ .driver-popover.driverjs-theme .driver-popover-close-btn {
+ color: #9b9b9b;
+ }
+
+ .driver-popover.driverjs-theme .driver-popover-close-btn:hover {
+ color: #000;
+ }
+ .driver-popover.driverjs-theme .driver-popover-footer {
+ gap: 20px;
+ }
+ .driver-popover.driverjs-theme .driver-popover-footer button {
+ background-color: #000 !important;
+ }
+
+ .driver-popover.driverjs-theme .driver-popover-arrow-side-left.driver-popover-arrow {
+ border-left-color: #fde047;
+ }
+
+ .driver-popover.driverjs-theme .driver-popover-arrow-side-right.driver-popover-arrow {
+ border-right-color: #fde047;
+ }
+
+ .driver-popover.driverjs-theme .driver-popover-arrow-side-top.driver-popover-arrow {
+ border-top-color: #fde047;
+ }
+
+ .driver-popover.driverjs-theme .driver-popover-arrow-side-bottom.driver-popover-arrow {
+ border-bottom-color: #fde047;
+ }
\ No newline at end of file
diff --git a/core/src/components/friends-view/beginner-checklist.js b/core/src/components/friends-view/beginner-checklist.js
new file mode 100644
index 00000000..6d77b016
--- /dev/null
+++ b/core/src/components/friends-view/beginner-checklist.js
@@ -0,0 +1,343 @@
+import { css, html, LitElement } from 'lit';
+import { connect } from 'pwa-helpers';
+
+import '@vaadin/item';
+import '@vaadin/list-box';
+import '@polymer/paper-icon-button/paper-icon-button.js';
+import '@polymer/iron-icons/iron-icons.js';
+import { store } from '../../store.js';
+import { setNewTab } from '../../redux/app/app-actions.js';
+import '@material/mwc-icon';
+import { get, translate } from '../../../translate/index.js';
+import { repeat } from 'lit/directives/repeat.js';
+import '../../../../plugins/plugins/core/components/TimeAgo.js';
+import '../notification-view/popover.js';
+import ShortUniqueId from 'short-unique-id';
+
+class BeginnerChecklist extends connect(store)(LitElement) {
+ static properties = {
+ notifications: { type: Array },
+ showChecklist: { type: Boolean },
+ theme: { type: String, reflect: true },
+ isSynced: { type: Boolean },
+ hasName: { type: Boolean },
+ hasTourFinished: { type: Boolean },
+ };
+
+ constructor() {
+ super();
+ this.showChecklist = false;
+ this.initialFetch = false;
+ this.theme = localStorage.getItem('qortalTheme')
+ ? localStorage.getItem('qortalTheme')
+ : 'light';
+ this.isSynced = false;
+ this.hasName = null;
+ this.nodeUrl = this.getNodeUrl();
+ this.myNode = this.getMyNode();
+ this.hasTourFinished = false;
+ this._controlTourFinished = this._controlTourFinished.bind(this);
+ this.uid = new ShortUniqueId();
+ }
+
+ _controlTourFinished() {
+ this.hasTourFinished = true;
+ }
+
+ connectedCallback() {
+ super.connectedCallback();
+ window.addEventListener(
+ 'send-tour-finished',
+ this._controlTourFinished
+ );
+ }
+
+ disconnectedCallback() {
+ window.removeEventListener(
+ 'send-tour-finished',
+ this._controlTourFinished
+ );
+
+ super.disconnectedCallback();
+ }
+
+ getNodeUrl() {
+ const myNode =
+ window.parent.reduxStore.getState().app.nodeConfig.knownNodes[
+ window.parent.reduxStore.getState().app.nodeConfig.node
+ ];
+
+ const nodeUrl =
+ myNode.protocol + '://' + myNode.domain + ':' + myNode.port;
+ return nodeUrl;
+ }
+ getMyNode() {
+ const myNode =
+ window.parent.reduxStore.getState().app.nodeConfig.knownNodes[
+ window.parent.reduxStore.getState().app.nodeConfig.node
+ ];
+
+ return myNode;
+ }
+
+ async getName(recipient) {
+ try {
+ const endpoint = `${this.nodeUrl}/names/address/${recipient}`;
+ const res = await fetch(endpoint);
+ const getNames = await res.json();
+
+ if (Array.isArray(getNames) && getNames.length > 0) {
+ this.hasName = true;
+ } else {
+ this.hasName = false;
+ }
+ } catch (error) {
+ return '';
+ }
+ }
+
+ stateChanged(state) {
+ if (
+ state.app.nodeStatus &&
+ state.app.nodeStatus.syncPercent !== this.syncPercentage
+ ) {
+ this.syncPercentage = state.app.nodeStatus.syncPercent;
+
+ if (
+ !this.hasAttempted &&
+ state.app.nodeStatus.syncPercent === 100
+ ) {
+ this.hasAttempted = true;
+ this.getName();
+ }
+ }
+ if (
+ state.app.accountInfo &&
+ state.app.accountInfo.names.length &&
+ state.app.nodeStatus &&
+ state.app.nodeStatus.syncPercent === 100 &&
+ this.hasName === false &&
+ this.hasAttempted &&
+ state.app.accountInfo &&
+ state.app.accountInfo.names &&
+ state.app.accountInfo.names.length > 0
+ ) {
+ this.hasName = true;
+ }
+ }
+
+ handleBlur() {
+ setTimeout(() => {
+ if (!this.shadowRoot.contains(document.activeElement)) {
+ this.showChecklist = false;
+ }
+ }, 0);
+ }
+
+
+
+ render() {
+
+ return this.hasName === false || !this.hasTourFinished
+ ? html`
+
+
+
this._toggleChecklist()}
+ >
+ checklist
+
+
+
+
+
+
+
+
+
Are you synced?
+ ${this.syncPercentage === 100
+ ? html`
+
task_alt
+ `
+ : html`
+
radio_button_unchecked
+ `}
+
+
+
{
+ store.dispatch(
+ setNewTab({
+ url: `group-management`,
+ id: this.uid.rnd(),
+ myPlugObj: {
+ url: 'name-registration',
+ domain: 'core',
+ page: 'name-registration/index.html',
+ title: 'Name Registration',
+ icon: 'vaadin:user-check',
+ mwcicon: 'manage_accounts',
+ pluginNumber:
+ 'plugin-qCmtXAQmtu',
+ menus: [],
+ parent: false,
+ },
+ openExisting: true,
+ })
+ );
+ this.handleBlur()
+ }}>
+
Do you have a name registered?
+ ${this.hasName
+ ? html`
+
task_alt
+ `
+ : html`
+
radio_button_unchecked
+ `}
+
+
+
+
+ `
+ : '';
+ }
+
+ _toggleChecklist() {
+ this.showChecklist = !this.showChecklist;
+ if (this.showChecklist) {
+ requestAnimationFrame(() => {
+ this.shadowRoot.getElementById('checklist-panel').focus();
+ });
+ }
+ }
+
+ static styles = css`
+ .layout {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ position: relative;
+ }
+
+ .count {
+ position: absolute;
+ top: -5px;
+ right: -5px;
+ font-size: 12px;
+ background-color: red;
+ color: white;
+ border-radius: 50%;
+ width: 16px;
+ height: 16px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ }
+
+ .nocount {
+ display: none;
+ }
+
+ .popover-panel {
+ position: absolute;
+ width: 200px;
+ padding: 10px;
+ background-color: var(--white);
+ border: 1px solid var(--black);
+ border-radius: 4px;
+ box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
+ top: 40px;
+ max-height: 350px;
+ overflow: auto;
+ scrollbar-width: thin;
+ scrollbar-color: #6a6c75 #a1a1a1;
+ }
+
+ .popover-panel::-webkit-scrollbar {
+ width: 11px;
+ }
+
+ .popover-panel::-webkit-scrollbar-track {
+ background: #a1a1a1;
+ }
+
+ .popover-panel::-webkit-scrollbar-thumb {
+ background-color: #6a6c75;
+ border-radius: 6px;
+ border: 3px solid #a1a1a1;
+ }
+
+ .list {
+ display: flex;
+ flex-direction: column;
+ gap: 15px;
+ }
+
+ .task-list-item {
+ display: flex;
+ gap: 15px;
+ justify-content: space-between;
+ align-items: center;
+ }
+
+ .checklist-item {
+ padding: 5px;
+ border-bottom: 1px solid;
+ display: flex;
+ justify-content: space-between;
+ cursor: pointer;
+ transition: 0.2s all;
+ }
+
+ .checklist-item:hover {
+ background: var(--nav-color-hover);
+ }
+
+ p {
+ font-size: 16px;
+ color: var(--black);
+ margin: 0px;
+ padding: 0px;
+ }
+ `;
+}
+
+customElements.define('beginner-checklist', BeginnerChecklist);
diff --git a/core/src/components/friends-view/core-sync-status.js b/core/src/components/friends-view/core-sync-status.js
index 4ec0d633..733123aa 100644
--- a/core/src/components/friends-view/core-sync-status.js
+++ b/core/src/components/friends-view/core-sync-status.js
@@ -81,7 +81,9 @@ class CoreSyncStatus extends connect(store)(LitElement) {
render() {
return html`
+
${this.renderSyncStatusIcon()}
+
`
}
diff --git a/core/src/components/friends-view/friends-view.js b/core/src/components/friends-view/friends-view.js
index be15c87f..148422c9 100644
--- a/core/src/components/friends-view/friends-view.js
+++ b/core/src/components/friends-view/friends-view.js
@@ -139,8 +139,8 @@ class FriendsView extends connect(store)(LitElement) {
disconnectedCallback() {
window.removeEventListener('friends-my-friend-list-event', this._updateFriends)
- window.addEventListener('friends-my-selected-feeds-event', this._updateFeed)
- window.addEventListener('add-friend', this._addFriend)
+ window.removeEventListener('friends-my-selected-feeds-event', this._updateFeed)
+ window.removeEventListener('add-friend', this._addFriend)
super.disconnectedCallback()
}
diff --git a/core/src/components/show-plugin.js b/core/src/components/show-plugin.js
index 44166dcb..824da3ab 100644
--- a/core/src/components/show-plugin.js
+++ b/core/src/components/show-plugin.js
@@ -19,6 +19,7 @@ import '@polymer/paper-dialog/paper-dialog.js'
import '@vaadin/grid'
import '@vaadin/text-field'
import '../custom-elements/frag-file-input.js'
+import { defaultQappsTabs } from '../data/defaultQapps.js'
registerTranslateConfig({
loader: lang => fetch(`/language/${lang}.json`).then(res => res.json())
@@ -322,6 +323,7 @@ class ShowPlugin extends connect(store)(LitElement) {
constructor() {
super()
this.registeredUrls = []
+ this.initialRegisteredUrls = []
this.currentTab = 0
this.tabs = []
this.uid = new ShortUniqueId()
@@ -339,6 +341,7 @@ class ShowPlugin extends connect(store)(LitElement) {
}
return html`
+
${this.tabs.map((tab, index) => {
let title = ''
@@ -1725,7 +1728,9 @@ class NavBar extends connect(store)(LitElement) {
if (localStorage.getItem("myMenuPlugs") === null) {
await appDelay(1000)
- const myObj = JSON.stringify(this.menuList)
+ const listOfPlugins = this.menuList.filter(plugin=> plugin.url !== "puzzles")
+ const addQapps = [...listOfPlugins, ...defaultQappsTabs]
+ const myObj = JSON.stringify(addQapps)
localStorage.setItem("myMenuPlugs", myObj)
this.myMenuPlugins = JSON.parse(localStorage.getItem("myMenuPlugs") || "[]")
} else {
diff --git a/core/src/data/defaultQapps.js b/core/src/data/defaultQapps.js
new file mode 100644
index 00000000..0378984a
--- /dev/null
+++ b/core/src/data/defaultQapps.js
@@ -0,0 +1,79 @@
+export const defaultQappsTabs = [
+ {
+ "url": "myapp",
+ "domain": "core",
+ "page": "qdn/browser/index.html?name=Q-Tube&service=APP",
+ "title": "Q-Tube",
+ "icon": "vaadin:external-browser",
+ "mwcicon": "apps",
+ "pluginNumber": "plugin-04tlGhUzzd",
+ "menus": [],
+ "parent": false
+ },
+ {
+ "url": "myapp",
+ "domain": "core",
+ "page": "qdn/browser/index.html?name=Q-Shop&service=APP",
+ "title": "Q-Shop",
+ "icon": "vaadin:external-browser",
+ "mwcicon": "apps",
+ "pluginNumber": "plugin-gAogkIS5z6",
+ "menus": [],
+ "parent": false
+ },
+ {
+ "url": "myapp",
+ "domain": "core",
+ "page": "qdn/browser/index.html?name=Q-Blog&service=APP",
+ "title": "Q-Blog",
+ "icon": "vaadin:external-browser",
+ "mwcicon": "rss_feed",
+ "pluginNumber": "plugin-4KX5PHKSbM",
+ "menus": [],
+ "parent": false
+ },
+ {
+ "url": "myapp",
+ "domain": "core",
+ "page": "qdn/browser/index.html?name=Q-Fund&service=APP",
+ "title": "Q-Fund",
+ "icon": "vaadin:external-browser",
+ "mwcicon": "apps",
+ "pluginNumber": "plugin-POw2jHDdHo",
+ "menus": [],
+ "parent": false
+ },
+ {
+ "url": "myapp",
+ "domain": "core",
+ "page": "qdn/browser/index.html?name=Q-Share&service=APP",
+ "title": "Q-Share",
+ "icon": "vaadin:external-browser",
+ "mwcicon": "apps",
+ "pluginNumber": "plugin-POw24oDdHo",
+ "menus": [],
+ "parent": false
+ },
+ {
+ "url": "myapp",
+ "domain": "core",
+ "page": "qdn/browser/index.html?name=Ear-Bump&service=APP",
+ "title": "Ear-Bump",
+ "icon": "vaadin:external-browser",
+ "mwcicon": "apps",
+ "pluginNumber": "plugin-POx8jHDdHo",
+ "menus": [],
+ "parent": false
+ },
+ {
+ "url": "myapp",
+ "domain": "core",
+ "page": "qdn/browser/index.html?name=Q-Mail&service=APP",
+ "title": "Q-Mail",
+ "icon": "vaadin:external-browser",
+ "mwcicon": "mail",
+ "pluginNumber": "plugin-GGHiHzW6pe",
+ "menus": [],
+ "parent": false
+ }
+]
\ No newline at end of file
diff --git a/core/src/styles/switch-theme.css b/core/src/styles/switch-theme.css
index b7a342ba..cd195572 100644
--- a/core/src/styles/switch-theme.css
+++ b/core/src/styles/switch-theme.css
@@ -136,4 +136,5 @@ html[theme="dark"] {
--app-hr: rgba(255, 255, 255, .3);
--code-block-text-color: #008fd5;
--noavatar: url("/img/noavatar_dark.png");
-}
\ No newline at end of file
+}
+
diff --git a/img/addplugin.webp b/img/addplugin.webp
new file mode 100644
index 00000000..13795259
Binary files /dev/null and b/img/addplugin.webp differ
diff --git a/package-lock.json b/package-lock.json
index 3dc571b8..7fdcc544 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -26,6 +26,7 @@
"buffer": "6.0.3",
"compressorjs": "1.2.1",
"crypto-js": "4.2.0",
+ "driver.js": "^1.3.1",
"electron-dl": "3.5.1",
"electron-log": "5.0.1",
"electron-store": "8.1.0",
@@ -5450,6 +5451,11 @@
"integrity": "sha512-YXQl1DSa4/PQyRfgrv6aoNjhasp/p4qs9FjJ4q4cQk+8m4r6k4ZSiEyytKG8f8W9gi8WsQtIObNmKd+tMzNTmA==",
"dev": true
},
+ "node_modules/driver.js": {
+ "version": "1.3.1",
+ "resolved": "https://registry.npmjs.org/driver.js/-/driver.js-1.3.1.tgz",
+ "integrity": "sha512-MvUdXbqSgEsgS/H9KyWb5Rxy0aE6BhOVT4cssi2x2XjmXea6qQfgdx32XKVLLSqTaIw7q/uxU5Xl3NV7+cN6FQ=="
+ },
"node_modules/ejs": {
"version": "3.1.9",
"resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.9.tgz",
diff --git a/package.json b/package.json
index ad4b1f4c..6f1f679f 100644
--- a/package.json
+++ b/package.json
@@ -48,6 +48,7 @@
"buffer": "6.0.3",
"compressorjs": "1.2.1",
"crypto-js": "4.2.0",
+ "driver.js": "^1.3.1",
"electron-dl": "3.5.1",
"electron-log": "5.0.1",
"electron-store": "8.1.0",
diff --git a/plugins/plugins/core/streams/onNewBlock.js b/plugins/plugins/core/streams/onNewBlock.js
index 495f920b..2aceab39 100644
--- a/plugins/plugins/core/streams/onNewBlock.js
+++ b/plugins/plugins/core/streams/onNewBlock.js
@@ -1,8 +1,9 @@
import {parentEpml} from '../connect.js'
+const MIN_RECONNECT_INTERVAL = 1000; // 1 second
+const MAX_RECONNECT_INTERVAL = 300000; // 5 minutes
let socketObject
let activeBlockSocketTimeout
-let initial = 0
let closeGracefully = false
let isCalled = false
let retryOnClose = false
@@ -10,11 +11,12 @@ let blockFirstCall = true
let nodeStatusSocketObject
let nodeStatusSocketTimeout
let nodeStatusSocketcloseGracefully = false
-let nodeStatusCount = 0
let nodeStatusRetryOnClose = false
let nodeStateCall = false
let isLoggedIn = false
let oldAccountInfo
+let blockSocketReconnectInterval = MIN_RECONNECT_INTERVAL;
+let nodeStatusSocketReconnectInterval = MIN_RECONNECT_INTERVAL;
parentEpml.subscribe('logged_in', loggedIn => {
if (loggedIn === 'true') {
@@ -24,6 +26,7 @@ parentEpml.subscribe('logged_in', loggedIn => {
}
})
+
const setAccountInfo = async (addr) => {
const names = await parentEpml.request('apiCall', {
url: `/names/address/${addr}`
@@ -115,6 +118,21 @@ const initNodeStatusCall = (nodeConfig) => {
}
}
+function attemptReconnectBlockSocket() {
+ setTimeout(() => {
+ initBlockSocket();
+ blockSocketReconnectInterval = Math.min(blockSocketReconnectInterval * 2, MAX_RECONNECT_INTERVAL);
+ }, blockSocketReconnectInterval);
+}
+
+function attemptReconnectNodeStatusSocket() {
+ setTimeout(() => {
+ initNodeStatusSocket();
+ nodeStatusSocketReconnectInterval = Math.min(nodeStatusSocketReconnectInterval * 2, MAX_RECONNECT_INTERVAL);
+ }, nodeStatusSocketReconnectInterval);
+}
+
+
const initBlockSocket = () => {
let myNode = window.parent.reduxStore.getState().app.nodeConfig.knownNodes[window.parent.reduxStore.getState().app.nodeConfig.node]
let nodeUrl = myNode.domain + ":" + myNode.port
@@ -127,9 +145,9 @@ const initBlockSocket = () => {
const activeBlockSocket = new WebSocket(activeBlockSocketLink)
// Open Connection
activeBlockSocket.onopen = (e) => {
+ blockSocketReconnectInterval = MIN_RECONNECT_INTERVAL;
closeGracefully = false
socketObject = activeBlockSocket
- initial = initial + 1
}
// Message Event
activeBlockSocket.onmessage = (e) => {
@@ -141,21 +159,10 @@ const initBlockSocket = () => {
}
// Closed Event
activeBlockSocket.onclose = () => {
- processBlock({})
- blockFirstCall = true
- clearInterval(activeBlockSocketTimeout)
-
- if (closeGracefully === false && initial <= 52) {
- if (initial <= 52) {
- retryOnClose = true
- setTimeout(pingactiveBlockSocket, 10000)
- initial = initial + 1
- } else {
- // ... Stop retrying...
- retryOnClose = false
- }
- }
- }
+ processBlock({});
+ blockFirstCall = true;
+ attemptReconnectBlockSocket();
+ };
// Error Event
activeBlockSocket.onerror = (e) => {
blockFirstCall = true
@@ -200,31 +207,26 @@ const initNodeStatusSocket = () => {
const activeNodeStatusSocket = new WebSocket(activeNodeStatusSocketLink)
// Open Connection
activeNodeStatusSocket.onopen = (e) => {
+ console.log('onopen')
+ nodeStatusSocketReconnectInterval = MIN_RECONNECT_INTERVAL;
+
nodeStatusSocketcloseGracefully = false
nodeStatusSocketObject = activeNodeStatusSocket
- nodeStatusCount = nodeStatusCount + 1
}
// Message Event
activeNodeStatusSocket.onmessage = (e) => {
+ console.log('onmessage')
doNodeStatus(JSON.parse(e.data))
}
// Closed Event
activeNodeStatusSocket.onclose = () => {
- doNodeStatus({})
- clearInterval(nodeStatusSocketTimeout)
- if (nodeStatusSocketcloseGracefully === false && nodeStatusCount <= 52) {
- if (nodeStatusCount <= 52) {
- nodeStatusRetryOnClose = true
- setTimeout(pingNodeStatusSocket, 10000)
- nodeStatusCount = nodeStatusCount + 1
- } else {
- // ... Stop retrying...
- nodeStatusRetryOnClose = false
- }
- }
- }
+ console.log('onclose')
+ doNodeStatus({});
+ attemptReconnectNodeStatusSocket();
+ };
// Error Event
activeNodeStatusSocket.onerror = (e) => {
+ console.log('onerror')
doNodeStatus({})
}
}