Browse Source

added tour and checklist

master
PhilReact 9 months ago
parent
commit
eb22b0cda6
  1. 24
      core/language/us.json
  2. 26
      core/src/components/app-view.js
  3. 281
      core/src/components/beginner-tour/sync-indicator.js
  4. 396
      core/src/components/beginner-tour/tour-component.js
  5. 77
      core/src/components/beginner-tour/tour.css
  6. 343
      core/src/components/friends-view/beginner-checklist.js
  7. 2
      core/src/components/friends-view/core-sync-status.js
  8. 4
      core/src/components/friends-view/friends-view.js
  9. 7
      core/src/components/show-plugin.js
  10. 79
      core/src/data/defaultQapps.js
  11. 3
      core/src/styles/switch-theme.css
  12. BIN
      img/addplugin.webp
  13. 6
      package-lock.json
  14. 1
      package.json
  15. 66
      plugins/plugins/core/streams/onNewBlock.js

24
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."
}
}

26
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) {
</span>
</div>
<div style="display:flex;align-items:center;gap:20px">
<beginner-checklist></beginner-checklist>
<profile-qdn></profile-qdn>
<friends-side-panel-parent></friends-side-panel-parent>
<notification-bell></notification-bell>
@ -600,6 +624,8 @@ class AppView extends connect(store)(LitElement) {
</app-toolbar>
</app-header>
<show-plugin></show-plugin>
<tour-component .getElements=${this.getTourElements}></tour-component>
<sync-indicator ></sync-indicator>
</app-header-layout>
</app-drawer-layout>
<user-info-view></user-info-view>

281
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`
<div class="parent">
<span
><mwc-icon
id="notification-general-icon"
style="color: red; cursor:pointer;user-select:none"
>priority_high</mwc-icon
></span
>
<p>
${translate("tour.tour17")}
</p>
</div>
`
: (this.isBehind && this.isSynchronizing)
? html`
<div class="parent">
<div class="column">
<div class="row">
<span
><img
src="/img/syncing.png"
style="height: 24px; width: 24px;"
/></span>
<p>
${this.blocksBehind} ${translate("tour.tour20")}
</p>
</div>
<div
class="row"
style="justify-content: center"
>
<button
class="bootstrap-button"
@click="${() => {
this.bootstrap()
}}"
>
${translate("tour.tour18")}
</button>
</div>
</div>
</div>
`
: this.isSynchronizing
? html`
<div class="parent">
<span
><img
src="/img/syncing.png"
style="height: 24px; width: 24px;"
/></span>
<p>
${translate("tour.tour19")} ${this.blocksBehind ? this.blocksBehind : ""} ${this.blocksBehind ? translate("tour.tour21"): ""}
</p>
</div>
`
: "" }
`;
}
}
customElements.define('sync-indicator', SyncIndicator);

396
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: `
<div style="display:flex;justify-content:center;gap:15px">
<img style="height:40px;width:auto;margin:15px 0px;" src="/img/qort.png" />
</div>
<div style="display:flex;gap:15px;align-items:center;margin-top:15px;">
<div style="height:6px;width:6px;border-radius:50%;background:var(--black)"></div> <p style="margin:0px;padding:0px">${get("tour.tour7")}</p>
</div>
<div style="display:flex;gap:15px;align-items:center;margin-top:15px;">
<div style="height:6px;width:6px;border-radius:50%;background:var(--black)"></div> <p style="margin:0px;padding:0px">${get("tour.tour8")}</p>
</div>
<div style="display:flex;gap:15px;align-items:center;margin-top:15px;margin-bottom:30px">
<div style="height:6px;width:6px;border-radius:50%;background:var(--black)"></div> <p style="margin:0px;padding:0px">${get("tour.tour9")}</p>
</div>
`,
// ... 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: `
<div style="display:flex;gap:15px;align-items:center;margin-top:15px;margin-bottom:30px">
<p style="margin:0px;padding:0px">${get("tour.tour1")}</p>
</div>
<div style="display:flex;gap:15px;align-items:center;margin-top:15px;">
<span><img src="/img/synced.png" style="height: 24px; width: 24px; padding-top: 4px;" /></span>
<p style="margin:0px;padding:0px">${get("tour.tour2")}</p>
</div>
<div style="display:flex;gap:15px;align-items:center;margin-top:15px;">
<span><img src="/img/synced_minting.png" style="height: 24px; width: 24px; padding-top: 4px;" /></span>
<p style="margin:0px;padding:0px">${get("tour.tour3")}</p>
</div>
<div style="display:flex;gap:15px;align-items:center;margin-top:15px;margin-bottom:30px">
<span><img src="/img/syncing.png" style="height: 24px; width: 24px; padding-top: 4px;" /></span>
<p style="margin:0px;padding:0px">${get("tour.tour4")}</p>
</div>
`,
},
});
}
if (step3) {
steps.push({
element: step3,
popover: {
title: 'Tab View',
description: `
<div style="display:flex;gap:15px;align-items:center;margin-top:15px;margin-bottom:30px">
<p style="margin:0px;padding:0px">${get("tour.tour10")}
</p>
</div>
<div style="display:flex;gap:15px;align-items:center;margin-top:15px;">
<span><img src="/img/addplugin.webp" style="height: 36px; width: 36px; padding-top: 4px;" /></span>
<p style="margin:0px;padding:0px">You can also bookmark other Q-Apps and Plugins by clicking on the ${get(
'tabmenu.tm19'
)} button</p>
</div>
`,
},
});
}
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`
<!-- Profile read-view -->
${this.dialogOpenedCongrats && this.hasViewedTour
? html`
<paper-dialog
class="full-info-wrapper"
?opened="${this.dialogOpenedCongrats}"
>
<h3>Congratulations!</h3>
<div
style="display:flex;gap:15px;justify-content:center;margin-top:10px"
>
${translate("tour.tour13")}
</div>
<div
style="display:flex;gap:15px;justify-content:center;margin-top:10px"
>
${translate("tour.tour14")}
</div>
<div
class="accept-button"
@click=${this.visitQtube}
>
${translate("tour.tour15")}
</div>
<div style="width:100%;display:flex;justify-content:center;margin-top:10px">
<div
class="close-button"
@click=${()=> {
this.dialogOpenedCongrats = false
}}
>
${translate("general.close")}
</div>
</div>
</paper-dialog>
`
: ''}
`;
}
}
customElements.define('tour-component', TourComponent);

77
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;
}

343
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`
<div class="layout">
<popover-component
for="popover-checklist"
message=${get('tour.tour16')}
></popover-component>
<div
id="popover-checklist"
@click=${() => this._toggleChecklist()}
>
<mwc-icon
id="checklist-general-icon"
style=${`color: ${!this.hasName ? 'red' : 'var(--black)'}; cursor:pointer;user-select:none`}
>checklist</mwc-icon
>
<vaadin-tooltip
for="checklist-general-icon"
position="bottom"
hover-delay=${400}
hide-delay=${1}
text=${get('tour.tour16')}
>
</vaadin-tooltip>
</div>
<div
id="checklist-panel"
class="popover-panel"
style="visibility:${this.showChecklist
? 'visibile'
: 'hidden'}"
tabindex="0"
@blur=${this.handleBlur}
>
<div class="list">
<div class="task-list-item">
<p>Are you synced?</p>
${this.syncPercentage === 100
? html`
<mwc-icon
id="checklist-general-icon"
style="color: green; user-select:none"
>task_alt</mwc-icon
>
`
: html`
<mwc-icon
id="checklist-general-icon"
style="color: red; user-select:none"
>radio_button_unchecked</mwc-icon
>
`}
</div>
<div class="task-list-item" style="cursor:pointer" @click=${()=> {
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()
}}>
<p>Do you have a name registered?</p>
${this.hasName
? html`
<mwc-icon
id="checklist-general-icon"
style="color: green; user-select:none"
>task_alt</mwc-icon
>
`
: html`
<mwc-icon
id="checklist-general-icon"
style="color: red; user-select:none"
>radio_button_unchecked</mwc-icon
>
`}
</div>
</div>
</div>
</div>
`
: '';
}
_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);

2
core/src/components/friends-view/core-sync-status.js

@ -81,7 +81,9 @@ class CoreSyncStatus extends connect(store)(LitElement) {
render() {
return html`
<div id="core-sync-status-id">
${this.renderSyncStatusIcon()}
</div>
`
}

4
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()
}

7
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`
<div id="showPluginId" style="width:0px"></div>
<div class="tabs">
${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 {

79
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
}
]

3
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");
}
}

BIN
img/addplugin.webp

Binary file not shown.

After

Width:  |  Height:  |  Size: 572 B

6
package-lock.json generated

@ -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",

1
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",

66
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({})
}
}

Loading…
Cancel
Save