Merge pull request #219 from Philreact/feature/profile

Feature/profile
This commit is contained in:
AlphaX-Projects 2023-11-07 17:27:58 +01:00 committed by GitHub
commit 516804cf70
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 2616 additions and 68 deletions

View File

@ -7,24 +7,16 @@
"version": 1,
"updated": 1696646223261,
"title": "Q-Blog Post creations",
"description": "blablabla",
"description": "Get your friends Q-Blog posts on your feed",
"search": {
"query": "-post-",
"identifier": "q-blog-",
"service": "BLOG_POST",
"exactmatchnames": true
},
"click": "qortal://APP/Q-Blog/$${resource.name}$$/$${customParams.blogId}$$/$${customParams.shortIdentifier}$$",
"click": "qortal://APP/Q-Blog/$${resource.name}$$/blog/$${resource.identifier}$$",
"display": {
"title": "$${rawdata.title}$$"
},
"customParams": {
"blogId": "**methods.getBlogId(resource)**",
"shortIdentifier": "**methods.getShortId(resource)**"
},
"methods": {
"getShortId": "return resource.identifier.split('-post-')[1];",
"getBlogId": "const arr = resource.identifier.split('-post-'); const id = arr[0]; return id.startsWith('q-blog-') ? id.substring(7) : id;"
}
}
]

View File

@ -729,7 +729,10 @@
"bchange46": "Do you give this application permission to save the following file",
"bchange47": "Instant publish - requires",
"bchange48": "Do you give this application permission to send you notifications",
"bchange49": "Do you give this application permission to get your wallet information?"
"bchange49": "Do you grant this application permission to access the following private information from your profile?",
"bchange50": "This app has requested a change to your public profile. Property: ",
"bchange51": "To submit the changes don't forget to click on 'Update profile'",
"bchange52": "Do you give this application permission to get your wallet information?"
},
"datapage": {
"dchange1": "Data Management",
@ -1214,5 +1217,28 @@
"saving2": "Nothing to save",
"saving3": "Save unsaved changes",
"saving4": "Undo changes"
},
"profile": {
"profile1": "You do not have a name",
"profile2": "Go to name registration",
"profile3": "Update profile",
"profile4": "Tagline",
"profile5": "Bio",
"profile6": "Wallet Addresses",
"profile7": "Fill from UI",
"profile8": "Add custom property",
"profile9": "Property name",
"profile10": "Fields",
"profile11": "Add new field",
"profile12": "Field name",
"profile13": "Fill out",
"profile14": "Activity",
"profile15": "No value",
"profile16": "This name has no profile",
"profile17": "Unable to fetch profile",
"profile18": "Open profile",
"profile19": "Cannot fetch profile",
"profile20": "Register name",
"profile21": "Insert address from UI"
}
}

View File

@ -0,0 +1,5 @@
import WebWorker from 'web-worker:./computePowWorkerFile.js';
// You can add any initialization or configuration for the Web Worker here
export default WebWorker;

View File

@ -1,12 +1,12 @@
import {css, html, LitElement} from 'lit'
import {connect} from 'pwa-helpers'
import {store} from '../store.js'
import {Epml} from '../epml.js'
import {addTradeBotRoutes} from '../tradebot/addTradeBotRoutes.js'
import {get, translate} from 'lit-translate'
import { css, html, LitElement } from 'lit'
import { connect } from 'pwa-helpers'
import { store } from '../store.js'
import { Epml } from '../epml.js'
import { addTradeBotRoutes } from '../tradebot/addTradeBotRoutes.js'
import { get, translate } from 'lit-translate'
import localForage from 'localforage'
import {decryptData, encryptData} from '../lockScreen.js'
import {setChatLastSeen} from '../redux/app/app-actions.js'
import { decryptData, encryptData } from '../lockScreen.js'
import { setChatLastSeen } from '../redux/app/app-actions.js'
import isElectron from 'is-electron'
import '@material/mwc-button'
import '@material/mwc-icon'
@ -41,6 +41,7 @@ import './notification-view/notification-bell-general.js'
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 './controllers/coin-balances-controller.js'
const chatLastSeen = localForage.createInstance({
@ -564,7 +565,8 @@ class AppView extends connect(store)(LitElement) {
<img src="${this.config.coin.logo}" style="height:32px; padding-left:12px;">
</span>
</div>
<div style="display: flex; align-items: center; gap: 20px">
<div style="display:flex;align-items:center;gap:20px">
<profile-qdn></profile-qdn>
<friends-side-panel-parent></friends-side-panel-parent>
<notification-bell></notification-bell>
<notification-bell-general></notification-bell-general>
@ -705,11 +707,11 @@ class AppView extends connect(store)(LitElement) {
var drawerTog = this.shadowRoot.getElementById("mb")
var drawerOut = this.shadowRoot.getElementById("appsidebar")
drawerTog.addEventListener('mouseover', function() {
drawerTog.addEventListener('mouseover', function () {
drawerTog.click()
})
drawerOut.addEventListener('mouseleave', function() {
drawerOut.addEventListener('mouseleave', function () {
drawerTog.click()
})

View File

@ -0,0 +1,92 @@
import { Sha256 } from 'asmcrypto.js'
function sbrk(size, heap){
let brk = 512 * 1024 // stack top
let old = brk
brk += size
if (brk > heap.length)
throw new Error('heap exhausted')
return old
}
self.addEventListener('message', async e => {
const response = await computePow(e.data.convertedBytes, e.data.path)
postMessage(response)
})
const memory = new WebAssembly.Memory({ initial: 256, maximum: 256 })
const heap = new Uint8Array(memory.buffer)
const computePow = async (convertedBytes, path) => {
let response = null
await new Promise((resolve, reject)=> {
const _convertedBytesArray = Object.keys(convertedBytes).map(
function (key) {
return convertedBytes[key]
}
)
const convertedBytesArray = new Uint8Array(_convertedBytesArray)
const convertedBytesHash = new Sha256()
.process(convertedBytesArray)
.finish().result
const hashPtr = sbrk(32, heap)
const hashAry = new Uint8Array(
memory.buffer,
hashPtr,
32
)
hashAry.set(convertedBytesHash)
const difficulty = 14
const workBufferLength = 8 * 1024 * 1024
const workBufferPtr = sbrk(
workBufferLength,
heap
)
const importObject = {
env: {
memory: memory
},
};
function loadWebAssembly(filename, imports) {
return fetch(filename)
.then(response => response.arrayBuffer())
.then(buffer => WebAssembly.compile(buffer))
.then(module => {
return new WebAssembly.Instance(module, importObject);
});
}
loadWebAssembly(path)
.then(wasmModule => {
response = {
nonce : wasmModule.exports.compute2(hashPtr, workBufferPtr, workBufferLength, difficulty),
}
resolve()
});
})
return response
}

View File

@ -0,0 +1,310 @@
import { LitElement, html, css } from 'lit';
import { get, translate } from 'lit-translate';
import axios from 'axios';
import '@material/mwc-menu';
import '@material/mwc-list/mwc-list-item.js';
import { RequestQueueWithPromise } from '../../../../plugins/plugins/utils/queue';
import '../../../../plugins/plugins/core/components/TimeAgo';
import { connect } from 'pwa-helpers';
import { store } from '../../store';
import { setNewTab } from '../../redux/app/app-actions';
import ShortUniqueId from 'short-unique-id';
const requestQueue = new RequestQueueWithPromise(3);
const requestQueueRawData = new RequestQueueWithPromise(3);
const requestQueueStatus = new RequestQueueWithPromise(3);
export class AvatarComponent extends connect(store)(LitElement) {
static get properties() {
return {
resource: { type: Object },
isReady: { type: Boolean },
status: { type: Object },
name: { type: String },
};
}
static get styles() {
return css`
* {
--mdc-theme-text-primary-on-background: var(--black);
box-sizing: border-box;
}
:host {
width: 100%;
box-sizing: border-box;
}
img {
width: 100%;
max-height: 30vh;
border-radius: 5px;
cursor: pointer;
position: relative;
}
.smallLoading,
.smallLoading:after {
border-radius: 50%;
width: 2px;
height: 2px;
}
.defaultSize {
width: 100%;
height: 160px;
}
.parent-feed-item {
position: relative;
display: flex;
background-color: var(--chat-bubble-bg);
flex-grow: 0;
flex-direction: column;
align-items: flex-start;
justify-content: center;
border-radius: 5px;
padding: 12px 15px 4px 15px;
min-width: 150px;
width: 100%;
box-sizing: border-box;
cursor: pointer;
font-size: 16px;
}
.avatar {
width: 36px;
height: 36px;
border-radius: 50%;
overflow: hidden;
display: flex;
align-items: center;
}
.avatarApp {
width: 30px;
height: 30px;
border-radius: 50%;
overflow: hidden;
display: flex;
align-items: center;
}
.feed-item-name {
user-select: none;
color: #03a9f4;
margin-bottom: 5px;
}
.app-name {
display: flex;
justify-content: space-between;
align-items: center;
width: 100%;
}
mwc-menu {
position: absolute;
}
`;
}
constructor() {
super();
this.resource = {
identifier: '',
name: '',
service: '',
};
this.status = {
status: '',
};
this.isReady = false;
this.nodeUrl = this.getNodeUrl();
this.myNode = this.getMyNode();
this.isFetching = false;
this.uid = new ShortUniqueId();
}
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;
}
getApiKey() {
const myNode =
window.parent.reduxStore.getState().app.nodeConfig.knownNodes[
window.parent.reduxStore.getState().app.nodeConfig.node
];
let apiKey = myNode.apiKey;
return apiKey;
}
async fetchResource() {
try {
if (this.isFetching) return;
this.isFetching = true;
await axios.get(
`${this.nodeUrl}/arbitrary/resource/properties/${this.resource.service}/${this.resource.name}/${this.resource.identifier}?apiKey=${this.myNode.apiKey}`
);
this.isFetching = false;
} catch (error) {
this.isFetching = false;
}
}
async fetchVideoUrl() {
this.fetchResource();
}
async getRawData() {
const url = `${this.nodeUrl}/arbitrary/${this.resource.service}/${this.resource.name}/${this.resource.identifier}?apiKey=${this.myNode.apiKey}`;
return await requestQueueRawData.enqueue(() => {
return axios.get(url);
});
// const response2 = await fetch(url, {
// method: 'GET',
// headers: {
// 'Content-Type': 'application/json'
// }
// })
// const responseData2 = await response2.json()
// return responseData2
}
updateDisplayWithPlaceholders(display, resource, rawdata) {
const pattern = /\$\$\{([a-zA-Z0-9_\.]+)\}\$\$/g;
for (const key in display) {
const value = display[key];
display[key] = value.replace(pattern, (match, p1) => {
if (p1.startsWith('rawdata.')) {
const dataKey = p1.split('.')[1];
if (rawdata[dataKey] === undefined) {
console.error('rawdata key not found:', dataKey);
}
return rawdata[dataKey] || match;
} else if (p1.startsWith('resource.')) {
const resourceKey = p1.split('.')[1];
if (resource[resourceKey] === undefined) {
console.error('resource key not found:', resourceKey);
}
return resource[resourceKey] || match;
}
return match;
});
}
}
async fetchStatus() {
let isCalling = false;
let percentLoaded = 0;
let timer = 24;
const response = await requestQueueStatus.enqueue(() => {
return axios.get(
`${this.nodeUrl}/arbitrary/resource/status/${this.resource.service}/${this.resource.name}/${this.resource.identifier}?apiKey=${this.myNode.apiKey}`
);
});
if (response && response.data && response.data.status === 'READY') {
this.status = response.data;
return;
}
const intervalId = setInterval(async () => {
if (isCalling) return;
isCalling = true;
const data = await requestQueue.enqueue(() => {
return axios.get(
`${this.nodeUrl}/arbitrary/resource/status/${this.resource.service}/${this.resource.name}/${this.resource.identifier}?apiKey=${this.myNode.apiKey}`
);
});
const res = data.data;
isCalling = false;
if (res.localChunkCount) {
if (res.percentLoaded) {
if (
res.percentLoaded === percentLoaded &&
res.percentLoaded !== 100
) {
timer = timer - 5;
} else {
timer = 24;
}
if (timer < 0) {
clearInterval(intervalId);
}
percentLoaded = res.percentLoaded;
}
this.status = res;
if (this.status.status === 'DOWNLOADED') {
this.fetchResource();
}
}
// check if progress is 100% and clear interval if true
if (res.status === 'READY') {
clearInterval(intervalId);
this.status = res;
this.isReady = true;
}
}, 5000); // 1 second interval
}
async _fetchImage() {
try {
this.fetchVideoUrl();
this.fetchStatus();
} catch (error) {
/* empty */
}
}
firstUpdated() {
this._fetchImage();
}
render() {
console.log('hello', this.name, this.resource, this.status);
return html`
<div>
${this.status.status !== 'READY'
? html`
<mwc-icon style="user-select:none;"
>account_circle</mwc-icon
>
`
: ''}
${this.status.status === 'READY'
? html`
<div
style="height: 24px;width: 24px;overflow: hidden;"
>
<img
src="${this
.nodeUrl}/arbitrary/THUMBNAIL/${this
.name}/qortal_avatar?async=true&apiKey=${this
.myNode.apiKey}"
style="width:100%; height:100%;border-radius:50%"
onerror="this.onerror=null; this.src='/img/incognito.png';"
/>
</div>
`
: ''}
</div>
`;
}
}
customElements.define('avatar-component', AvatarComponent);

View File

@ -134,7 +134,7 @@ class FriendsFeed extends connect(store)(LitElement) {
this.endpointOffsets = Array(this.endpoints.length).fill(0);
return
}
const baseurl = `${this.nodeUrl}/arbitrary/resources/search?reverse=true&exactmatchnames=true&${names}`
const baseurl = `${this.nodeUrl}/arbitrary/resources/search?reverse=true&mode=ALL&exactmatchnames=true&${names}`
let formEndpoints = []
schemas.forEach((schema)=> {
const feedData = schema.feed[0]
@ -350,7 +350,7 @@ this.getFeedOnInterval()
// Merge new data with old data immutably
this.feed = [...enhancedNewData, ...this.feed];
this.feed = this.removeDuplicates(this.feed)
this.feed.sort((a, b) => new Date(b.created) - new Date(a.created)); // Sort by timestamp, most recent first
this.feed = this.trimDataToLimit(this.feed, maxResultsInMemory); // Trim to the maximum allowed in memory
this.feedToRender = this.feed.slice(0, 20);
@ -365,6 +365,17 @@ this.getFeedOnInterval()
}
removeDuplicates(array) {
const seenIds = new Set();
return array.filter(item => {
if (!seenIds.has(item.identifier)) {
seenIds.add(item.identifier);
return true;
}
return false;
});
}
async loadAndMergeData() {
let allData = this.feed
@ -373,6 +384,7 @@ this.getFeedOnInterval()
allData = this.mergeData(newData, allData);
allData.sort((a, b) => new Date(b.created) - new Date(a.created)); // Sort by timestamp, most recent first
allData = this.trimDataToLimit(allData, maxResultsInMemory); // Trim to the maximum allowed in memory
allData = this.removeDuplicates(allData)
this.feed = [...allData]
this.feedToRender = this.feed.slice(0,20)
this.hasInitialFetch = true

View File

@ -0,0 +1,725 @@
import { LitElement, html, css } from 'lit';
import { render } from 'lit/html.js';
import {
use,
get,
translate,
translateUnsafeHTML,
registerTranslateConfig,
} from 'lit-translate';
import '@material/mwc-button';
import '@material/mwc-icon';
import '@vaadin/tooltip';
import '@material/mwc-dialog';
import '@material/mwc-checkbox';
import { connect } from 'pwa-helpers';
import { store } from '../../store';
import '@polymer/paper-spinner/paper-spinner-lite.js';
import { parentEpml } from '../show-plugin';
class ProfileModalUpdate extends connect(store)(LitElement) {
static get properties() {
return {
isOpen: { type: Boolean },
setIsOpen: { attribute: false },
isLoading: { type: Boolean },
onSubmit: { attribute: false },
editContent: { type: Object },
onClose: { attribute: false },
tagline: { type: String },
bio: { type: String },
wallets: { type: Array },
hasFetchedArrr: { type: Boolean },
isOpenCustomDataModal: { type: Boolean },
customData: { type: Object },
newCustomDataField: {type: Object},
newFieldName: {type: String},
qortalRequestCustomData: {type: Object},
newCustomDataKey: {type: String},
isSaving: {type: Boolean}
};
}
constructor() {
super();
this.isOpen = false;
this.isLoading = false;
this.nodeUrl = this.getNodeUrl();
this.myNode = this.getMyNode();
this.tagline = '';
this.bio = '';
this.walletList = ['btc', 'ltc', 'doge', 'dgb', 'rvn', 'arrr'];
let wallets = {};
this.walletList.forEach((item) => {
wallets[item] = '';
});
this.wallets = wallets;
this.walletsUi = new Map();
let coinProp = {
wallet: null,
};
this.walletList.forEach((c, i) => {
this.walletsUi.set(c, { ...coinProp });
});
this.walletsUi.get('btc').wallet =
window.parent.reduxStore.getState().app.selectedAddress.btcWallet;
this.walletsUi.get('ltc').wallet =
window.parent.reduxStore.getState().app.selectedAddress.ltcWallet;
this.walletsUi.get('doge').wallet =
window.parent.reduxStore.getState().app.selectedAddress.dogeWallet;
this.walletsUi.get('dgb').wallet =
window.parent.reduxStore.getState().app.selectedAddress.dgbWallet;
this.walletsUi.get('rvn').wallet =
window.parent.reduxStore.getState().app.selectedAddress.rvnWallet;
this.hasFetchedArrr = false;
this.isOpenCustomDataModal = false;
this.customData = {};
this.newCustomDataKey = ""
this.newCustomDataField = {};
this.newFieldName = '';
this.isSaving = false
}
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);
--mdc-dialog-min-width: 400px;
--mdc-dialog-max-width: 1024px;
box-sizing: border-box;
}
.input {
width: 90%;
outline: 0;
border-width: 0 0 2px;
border-color: var(--mdc-theme-primary);
background-color: transparent;
padding: 10px;
font-family: Roboto, sans-serif;
font-size: 15px;
color: var(--chat-bubble-msg-color);
box-sizing: border-box;
}
.input::selection {
background-color: var(--mdc-theme-primary);
color: white;
}
.input::placeholder {
opacity: 0.6;
color: var(--black);
}
.modal-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;
}
.modal-button-red {
font-family: Roboto, sans-serif;
font-size: 16px;
color: #f44336;
background-color: transparent;
padding: 8px 10px;
border-radius: 5px;
border: none;
transition: all 0.3s ease-in-out;
}
.modal-button-red:hover {
cursor: pointer;
background-color: #f4433663;
}
.modal-button:hover {
cursor: pointer;
background-color: #03a8f475;
}
.checkbox-row {
position: relative;
display: flex;
align-items: center;
align-content: center;
font-family: Montserrat, sans-serif;
font-weight: 600;
color: var(--black);
}
.modal-overlay {
display: block;
position: fixed;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
background-color: rgba(
0,
0,
0,
0.5
); /* Semi-transparent backdrop */
z-index: 1000;
}
.modal-content {
position: fixed;
top: 50vh;
left: 50vw;
transform: translate(-50%, -50%);
background-color: var(--mdc-theme-surface);
width: 80vw;
max-width: 600px;
padding: 20px;
box-shadow: rgba(0, 0, 0, 0.1) 0px 4px 6px;
z-index: 1001;
border-radius: 5px;
display: flex;
flex-direction: column;
}
.modal-overlay.hidden {
display: none;
}
.avatar {
width: 36px;
height: 36px;
display: flex;
align-items: center;
}
.app-name {
display: flex;
gap: 20px;
align-items: center;
width: 100%;
cursor: pointer;
padding: 5px;
border-radius: 5px;
margin-bottom: 10px;
}
.inner-content {
display: flex;
flex-direction: column;
max-height: 75vh;
flex-grow: 1;
overflow: auto;
}
.inner-content::-webkit-scrollbar-track {
background-color: whitesmoke;
border-radius: 7px;
}
.inner-content::-webkit-scrollbar {
width: 12px;
border-radius: 7px;
background-color: whitesmoke;
}
.inner-content::-webkit-scrollbar-thumb {
background-color: rgb(180, 176, 176);
border-radius: 7px;
transition: all 0.3s ease-in-out;
}
`;
}
async updated(changedProperties) {
if (
changedProperties &&
changedProperties.has('editContent') &&
this.editContent
) {
const {bio, tagline, wallets, customData} = this.editContent
this.bio = bio ?? '';
this.tagline = tagline ?? '';
let formWallets = {...this.wallets}
if(wallets && Object.keys(wallets).length){
Object.keys(formWallets).forEach((key)=> {
if(wallets[key]){
formWallets[key] = wallets[key]
}
})
}
this.wallets = formWallets
this.customData = {...customData}
this.requestUpdate();
}
if (
changedProperties &&
changedProperties.has('qortalRequestCustomData') &&
this.qortalRequestCustomData
) {
this.isOpenCustomDataModal = true
this.newCustomDataField = {...this.qortalRequestCustomData.payload.customData}
this.newCustomDataKey = this.qortalRequestCustomData.property
this.requestUpdate();
}
}
async firstUpdated() {
try {
await this.fetchWalletAddress('arrr');
} catch (error) {
console.log({ error });
} finally {
}
}
async fetchWalletAddress(coin) {
switch (coin) {
case 'arrr':
const arrrWalletName = `${coin}Wallet`;
let res = await parentEpml.request('apiCall', {
url: `/crosschain/${coin}/walletaddress?apiKey=${this.myNode.apiKey}`,
method: 'POST',
body: `${
window.parent.reduxStore.getState().app.selectedAddress[
arrrWalletName
].seed58
}`,
});
if (res != null && res.error != 1201 && res.length === 78) {
this.arrrWalletAddress = res;
this.hasFetchedArrr = true;
}
break;
default:
// Not used for other coins yet
break;
}
}
async getSelectedWalletAddress(wallet) {
switch (wallet) {
case 'arrr':
if(!this.arrrWalletAddress){
try {
await this.fetchWalletAddress('arrr');
} catch (error) {
console.log({error})
}
}
// Use address returned by core API
return this.arrrWalletAddress;
default:
// Use locally derived address
return this.walletsUi.get(wallet).wallet.address;
}
}
getNodeUrl() {
const myNode =
store.getState().app.nodeConfig.knownNodes[
window.parent.reduxStore.getState().app.nodeConfig.node
];
const nodeUrl =
myNode.protocol + '://' + myNode.domain + ':' + myNode.port;
return nodeUrl;
}
getMyNode() {
const myNode =
store.getState().app.nodeConfig.knownNodes[
window.parent.reduxStore.getState().app.nodeConfig.node
];
return myNode;
}
clearFields() {
this.bio = '';
this.tagline = '';
}
fillAddress(coin) {
const address = this.getSelectedWalletAddress(coin);
if (address) {
this.wallets = {
...this.wallets,
[coin]: address,
};
}
}
async saveProfile() {
try {
const data = {
version: 1,
tagline: this.tagline,
bio: this.bio,
wallets: this.wallets,
customData: this.customData
};
this.isSaving = true
await this.onSubmit(data);
this.setIsOpen(false);
this.clearFields();
this.onClose('success');
} catch (error) {} finally {
this.isSaving = false
}
}
removeField(key){
const copyObj = {...this.newCustomDataField}
delete copyObj[key]
this.newCustomDataField = copyObj
}
addField(){
const copyObj = {...this.newCustomDataField}
copyObj[this.newFieldName] = ''
this.newCustomDataField = copyObj
this.newFieldName = ""
}
addCustomData(){
const copyObj = {...this.customData}
copyObj[this.newCustomDataKey] = this.newCustomDataField
this.customData = copyObj
this.newCustomDataKey = ""
this.newCustomDataField = {};
this.newFieldName = ''
this.isOpenCustomDataModal = false;
}
updateCustomData(key, data){
this.isOpenCustomDataModal = true
this.newCustomDataField = data
this.newCustomDataKey = key
}
removeCustomData(key){
const copyObj = {...this.customData}
delete copyObj[key]
this.customData = copyObj
}
render() {
return html`
<div class="modal-overlay ${this.isOpen ? '' : 'hidden'}">
<div class="modal-content">
<div class="inner-content">
<div style="height:15px"></div>
<div style="display: flex;flex-direction: column;">
<label
for="tagline"
id="taglineLabel"
style="color: var(--black);"
>
${translate('profile.profile4')}
</label>
<textarea
class="input"
@change=${(e) => {
this.tagline = e.target.value;
}}
.value=${this.tagline}
?disabled=${this.isLoading}
id="tagline"
placeholder="${translate('profile.profile4')}"
rows="3"
></textarea>
</div>
<div style="height:15px"></div>
<div style="display: flex;flex-direction: column;">
<label
for="bio"
id="bioLabel"
style="color: var(--black);"
>
${translate('profile.profile5')}
</label>
<textarea
class="input"
@change=${(e) => {
this.bio = e.target.value;
}}
.value=${this.bio}
?disabled=${this.isLoading}
id="bio"
placeholder="${translate('profile.profile5')}"
rows="3"
></textarea>
</div>
<div style="height:15px"></div>
<p>${translate('profile.profile6')}</p>
<div style="display: flex;flex-direction: column;">
${Object.keys(this.wallets).map((key) => {
return html`
<div
style="display:flex;justify-content:center;flex-direction:column"
>
<label
for=${key}
id="taglineLabel"
style="color: var(--black);font-size:16px"
>
${key}
</label>
<div
style="display:flex;gap:15px;align-items:center"
>
<input
id=${key}
placeholder=${key + ' ' + get('settings.address')}
class="input"
.value=${this.wallets[key]}
@change=${(e) => {
this.wallets = {
...this.wallets,
[key]: e.target.value,
};
}}
/>
<mwc-icon
id=${`${key}-upload`}
@click=${() =>
this.fillAddress(key)}
style="color:var(--black);cursor:pointer"
>upload_2</mwc-icon
>
<vaadin-tooltip
for=${`${key}-upload`}
position="bottom"
hover-delay=${200}
hide-delay=${1}
text=${translate('profile.profile21')}
>
</vaadin-tooltip>
</div>
</div>
`;
})}
</div>
<div style="display: flex;flex-direction: column;">
${Object.keys(this.customData).map((key) => {
return html`
<div
style="display:flex;justify-content:center;flex-direction:column;gap:25px"
>
<div
style="display:flex;gap:15px;align-items:center"
>
<p
style="color: var(--black);font-size:16px"
>
${key}
</p>
<mwc-icon
@click=${() =>
this.updateCustomData(key,this.customData[key])}
style="color:var(--black);cursor:pointer"
>edit</mwc-icon
>
<mwc-icon
@click=${() =>
this.removeCustomData(key)}
style="color:var(--black);cursor:pointer"
>remove</mwc-icon
>
</div>
</div>
`;
})}
</div>
</div>
<div
style="display:flex;justify-content:space-between;align-items:center;margin-top:20px"
>
<button
class="modal-button-red"
?disabled="${this.isLoading}"
@click="${() => {
this.setIsOpen(false);
this.clearFields();
this.onClose();
}}"
>
${translate('general.close')}
</button>
<div style="display:flex;gap:10px;align-items:center">
<button
?disabled="${this.isLoading}"
class="modal-button"
@click=${() => {
this.isOpenCustomDataModal = true;
}}
>
${translate('profile.profile8')}
</button>
<button
?disabled="${this.isLoading}"
class="modal-button"
@click=${() => {
if(this.isSaving) return
this.saveProfile();
}}
>
${this.isSaving ? html`
<paper-spinner-lite active></paper-spinner-lite>
` : ''}
${this.isSaving ? '' : translate('profile.profile3') }
</button>
</div>
</div>
</div>
</div>
<!-- add custom vars -->
<div
class="modal-overlay ${this.isOpenCustomDataModal
? ''
: 'hidden'}"
style="z-index:1001"
>
<div class="modal-content">
<div class="inner-content">
<div style="height:15px"></div>
<div
style="display:flex;justify-content:center;flex-direction:column"
>
<label
for="key-name"
id="taglineLabel"
style="color: var(--black);font-size:16px"
>
${translate('profile.profile9')}
</label>
<div
style="display:flex;gap:15px;align-items:center"
>
<input
id="key-name"
placeholder=${translate(
'profile.profile9'
)}
?disabled=${!!this.qortalRequestCustomData}
class="input"
.value=${this.newCustomDataKey}
@change=${(e) => {
this.newCustomDataKey = e.target.value
}}
/>
</div>
</div>
<div style="height:15px"></div>
<p>${translate('profile.profile10')}</p>
<div style="display: flex;flex-direction: column;">
${Object.keys(this.newCustomDataField).map((key) => {
return html`
<div
style="display:flex;justify-content:center;flex-direction:column"
>
<label
for=${key}
id="taglineLabel"
style="color: var(--black);font-size:16px"
>
${key}
</label>
<div
style="display:flex;gap:15px;align-items:center"
>
<input
id=${key}
placeholder=${translate('profile.profile13')}
class="input"
.value=${this.newCustomDataField[key]}
@change=${(e) => {
this.newCustomDataField = {
...this.newCustomDataField,
[key]: e.target.value,
};
}}
/>
<mwc-icon
@click=${() =>
this.removeField(key)}
style="color:var(--black);cursor:pointer"
>remove</mwc-icon
>
</div>
</div>
`;
})}
</div>
<div style="height:15px"></div>
<div style="width:100%;display:flex;justify-content:center;gap:10px;margin-top:30px">
<input
placeholder=${translate('profile.profile12')}
class="input"
.value=${this.newFieldName}
@change=${(e) => {
this.newFieldName = e.target.value
}}
/>
<button
class="modal-button"
@click=${() => {
this.addField();
}}
>
${translate('profile.profile11')}
</button>
</div>
</div>
<div
style="display:flex;justify-content:space-between;align-items:center;margin-top:20px"
>
<button
class="modal-button-red"
?disabled="${this.isLoading}"
@click="${() => {
this.isOpenCustomDataModal = false
this.newCustomDataKey = ""
this.newCustomDataField = {};
}}"
>
${translate('general.close')}
</button>
<button
?disabled="${this.isSaving}"
class="modal-button"
@click=${() => {
this.addCustomData();
}}
>
${translate('profile.profile8')}
</button>
</div>
</div>
</div>
`;
}
}
customElements.define('profile-modal-update', ProfileModalUpdate);

File diff suppressed because it is too large Load Diff

View File

@ -1,14 +1,17 @@
import {css, html, LitElement} from 'lit';
import '@material/mwc-icon';
import './friends-side-panel.js';
import {connect} from 'pwa-helpers';
import {store} from '../../store.js';
import WebWorker from 'web-worker:./computePowWorkerFile.src.js';
import { connect } from 'pwa-helpers';
import { store } from '../../store.js';
import WebWorker from '../WebWorkerFile.js';
import '@polymer/paper-spinner/paper-spinner-lite.js';
import '@vaadin/tooltip';
import {translate} from 'lit-translate';
import { get, translate } from 'lit-translate';
import ShortUniqueId from 'short-unique-id';
import {
decryptGroupData,
encryptDataGroup,
objectToBase64,
uint8ArrayToObject,
@ -16,6 +19,7 @@ import {
import {publishData} from '../../../../plugins/plugins/utils/publish-image.js';
import {parentEpml} from '../show-plugin.js';
import '../notification-view/popover.js';
import { setNewTab } from '../../redux/app/app-actions.js';
class SaveSettingsQdn extends connect(store)(LitElement) {
static get properties() {
@ -27,6 +31,9 @@ class SaveSettingsQdn extends connect(store)(LitElement) {
resourceExists: { type: Boolean },
isSaving: { type: Boolean },
fee: { type: Object },
hasName: {type: Boolean},
error: {type: String},
name: {type: String}
};
}
@ -47,6 +54,11 @@ class SaveSettingsQdn extends connect(store)(LitElement) {
this.valuesToBeSavedOnQdn = {};
this.isSaving = false;
this.fee = null;
this.hasName = false;
this.error = "";
this.uid = new ShortUniqueId();
this.name = undefined;
}
static styles = css`
:host {
@ -309,13 +321,26 @@ class SaveSettingsQdn extends connect(store)(LitElement) {
async getGeneralSettingsQdn() {
try {
this.error = ""
const arbFee = await this.getArbitraryFee();
this.fee = arbFee;
this.hasAttemptedToFetchResource = true;
let resource;
const nameObject = store.getState().app.accountInfo.names[0];
if (!nameObject) throw new Error('no name');
let nameObject
try {
nameObject = store.getState().app.accountInfo.names[0];
} catch (error) {
}
if (!nameObject) {
this.name = null;
this.error = 'no name'
throw new Error('no name');
}
const name = nameObject.name;
this.name = name;
this.hasName = true
this.error = '';
const url = `${this.nodeUrl}/arbitrary/resources/search?service=DOCUMENT_PRIVATE&identifier=qortal_general_settings&name=${name}&prefix=true&exactmatchnames=true&excludeblocked=true&limit=20`;
const res = await fetch(url);
@ -363,8 +388,6 @@ class SaveSettingsQdn extends connect(store)(LitElement) {
stateChanged(state) {
if (
state.app.accountInfo &&
state.app.accountInfo.names.length &&
state.app.nodeStatus &&
state.app.nodeStatus.syncPercent !== this.syncPercentage
) {
@ -377,6 +400,15 @@ class SaveSettingsQdn extends connect(store)(LitElement) {
this.getGeneralSettingsQdn();
}
}
if (
state.app.accountInfo &&
state.app.accountInfo.names.length &&
state.app.nodeStatus &&
state.app.nodeStatus.syncPercent === 100 && this.hasName === false && this.hasAttemptedToFetchResource && state.app.accountInfo && state.app.accountInfo.names && state.app.accountInfo.names.length > 0
) {
this.getGeneralSettingsQdn();
}
}
async getArbitraryFee() {
@ -529,7 +561,78 @@ class SaveSettingsQdn extends connect(store)(LitElement) {
style="display: block; margin: 0 auto;"
></paper-spinner-lite>
`
: html`
: !this.name ? html`
<mwc-icon
id="profile-icon"
class=${'notActive'}
@click=${() => {
const target = this.shadowRoot.getElementById(
'popover-notification'
);
const popover =
this.shadowRoot.querySelector(
'popover-component'
);
if (popover) {
popover.openPopover(target);
}
}}
style="user-select:none;cursor:pointer"
>save</mwc-icon
>
<vaadin-tooltip
for="profile-icon"
position="bottom"
hover-delay=${300}
hide-delay=${1}
text=${translate('profile.profile20')}
>
</vaadin-tooltip>
<popover-component for="profile-icon" message="">
<div style="margin-bottom:20px">
<p style="margin:10px 0px; font-size:16px">
${translate('profile.profile1')}
</p>
</div>
<div
style="display:flex;justify-content:center;gap:10px"
>
<div
class="accept-button"
@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,
})
);
const popover =
this.shadowRoot.querySelector(
'popover-component'
);
if (popover) {
popover.closePopover();
}
}}"
>
${translate('profile.profile2')}
</div>
</div>
</popover-component>
` : html`
<mwc-icon
id="save-icon"
class=${Object.values(this.valuesToBeSavedOnQdn)

View File

@ -9,6 +9,7 @@ const CHAT_HEADS_STREAM_NAME = 'chat_heads'
const NODE_CONFIG_STREAM_NAME = 'node_config'
const CHAT_LAST_SEEN = 'chat_last_seen'
const SIDE_EFFECT_ACTION = 'side_effect_action'
const PROFILE_DATA_ACTION = 'profile_data_action'
const COIN_BALANCES_ACTION = 'coin_balances'
export const loggedInStream = new EpmlStream(LOGIN_STREAM_NAME, () => store.getState().app.loggedIn)
@ -19,6 +20,7 @@ export const chatHeadsStateStream = new EpmlStream(CHAT_HEADS_STREAM_NAME, () =>
export const nodeConfigStream = new EpmlStream(NODE_CONFIG_STREAM_NAME, () => store.getState().app.nodeConfig)
export const chatLastSeenStream = new EpmlStream(CHAT_LAST_SEEN, () => store.getState().app.chatLastSeen)
export const sideEffectActionStream = new EpmlStream(SIDE_EFFECT_ACTION, () => store.getState().app.sideEffectAction)
export const profileDataActionStream = new EpmlStream(SIDE_EFFECT_ACTION, () => store.getState().app.profileData)
export const coinBalancesActionStream = new EpmlStream(COIN_BALANCES_ACTION, () => store.getState().app.coinBalances)
@ -65,6 +67,9 @@ store.subscribe(() => {
if (oldState.app.sideEffectAction !== state.app.sideEffectAction) {
sideEffectActionStream.emit(state.app.sideEffectAction)
}
if(oldState.app.profileDataActionStream !== state.app.profileDataActionStream){
profileDataActionStream.emit(state.app.profileData)
}
if (oldState.app.coinBalances !== state.app.coinBalances) {
coinBalancesActionStream.emit(state.app.coinBalances)
}

View File

@ -19,7 +19,8 @@ import {
SET_TAB_NOTIFICATIONS,
UPDATE_BLOCK_INFO,
UPDATE_NODE_INFO,
UPDATE_NODE_STATUS
UPDATE_NODE_STATUS,
SET_PROFILE_DATA
} from '../app-action-types.js'
export const doUpdateBlockInfo = (blockObj) => {
@ -179,6 +180,12 @@ export const setSideEffectAction = (payload)=> {
payload
}
}
export const setProfileData = (payload)=> {
return {
type: SET_PROFILE_DATA,
payload
}
}
export const setCoinBalances = (payload)=> {
return {

View File

@ -33,4 +33,5 @@ export const SET_TAB_NOTIFICATIONS = 'SET_TAB_NOTIFICATIONS'
export const IS_OPEN_DEV_DIALOG = 'IS_OPEN_DEV_DIALOG'
export const SET_NEW_NOTIFICATION = 'SET_NEW_NOTIFICATION'
export const SET_SIDE_EFFECT= 'SET_SIDE_EFFECT'
export const SET_PROFILE_DATA = 'SET_PROFILE_DATA'
export const SET_COIN_BALANCES= 'SET_COIN_BALANCES'

View File

@ -35,7 +35,8 @@ import {
SET_TAB_NOTIFICATIONS,
UPDATE_BLOCK_INFO,
UPDATE_NODE_INFO,
UPDATE_NODE_STATUS
UPDATE_NODE_STATUS,
SET_PROFILE_DATA
} from './app-action-types.js'
import {initWorkersReducer} from './reducers/init-workers.js'
import {loginReducer} from './reducers/login-reducer.js'
@ -90,6 +91,7 @@ const INITIAL_STATE = {
isOpenDevDialog: false,
newNotification: null,
sideEffectAction: null,
profileData: null,
coinBalances: {}
}
@ -331,6 +333,12 @@ export default (state = INITIAL_STATE, action) => {
sideEffectAction: action.payload
}
}
case SET_PROFILE_DATA: {
return {
...state,
profileData: action.payload
}
}
case SET_COIN_BALANCES: {
const copyBalances = {...state.coinBalances}
copyBalances[action.payload.type] = {

View File

@ -65,7 +65,6 @@ export class ChatGroupsModal extends LitElement {
render() {
console.log('hello')
return html`
<mwc-dialog

View File

@ -363,9 +363,7 @@ class ChatPage extends LitElement {
}
this.processMessages(getInitialMessages, true, false)
} catch (error) {
console.log
}
} catch (error) { /* empty */ }
}
async copyJoinGroupLinkToClipboard() {
@ -681,7 +679,10 @@ class ChatPage extends LitElement {
<br>
<h3 style="color: var(--black);">${translate("chatpage.cchange42")}</h3>
<div class="buttons">
<mwc-button @click=${() => this.sendMessage(this.myTrimmedMeassage)} dialog-confirm>${translate("transpage.tchange3")}</mwc-button>
<mwc-button @click=${() => {
this.sendMessage(this.myMessageUnder4Qort)
}} dialog-confirm>${translate("transpage.tchange3")}</mwc-button>
</div>
</paper-dialog>
<wrapper-modal
@ -1345,6 +1346,7 @@ class ChatPage extends LitElement {
const isRecipient = this.chatId.includes('direct') === true ? true : false
this.chatId.includes('direct') === true ? this.isReceipient = true : this.isReceipient = false
this._chatId = this.chatId.split('/')[1]
const mstring = get("chatpage.cchange8")
const placeholder = isRecipient === true ? `Message ${this._chatId}` : `${mstring}`
this.chatEditorPlaceholder = placeholder
@ -3113,8 +3115,8 @@ class ChatPage extends LitElement {
const stringifyMessageObject = JSON.stringify(messageObject)
if (this.balance < 4) {
this.myTrimmedMeassage = ''
this.myTrimmedMeassage = stringifyMessageObject
this.myMessageUnder4Qort = null
this.myMessageUnder4Qort = {messageText: stringifyMessageObject, typeMessage, chatReference: undefined, isForward: false, isReceipient, _chatId, _publicKey, messageQueue}
this.shadowRoot.getElementById('confirmDialog').open()
} else {
return this.sendMessage({messageText: stringifyMessageObject, typeMessage, chatReference: undefined, isForward: false, isReceipient, _chatId, _publicKey, messageQueue})
@ -3164,23 +3166,23 @@ class ChatPage extends LitElement {
})
return _computePow(chatResponse)
} else {
let groupResponse = await parentEpml.request('chat', {
type: 181,
nonce: this.selectedAddress.nonce,
params: {
timestamp: Date.now(),
groupID: Number(_chatId),
hasReceipient: 0,
hasChatReference: typeMessage === 'edit' ? 1 : 0,
chatReference: chatReference,
message: messageText,
lastReference: reference,
proofOfWorkNonce: 0,
isEncrypted: 0, // Set default to not encrypted for groups
isText: 1
}
})
return _computePow(groupResponse)
let groupResponse = await parentEpml.request('chat', {
type: 181,
nonce: this.selectedAddress.nonce,
params: {
timestamp: Date.now(),
groupID: Number(_chatId),
hasReceipient: 0,
hasChatReference: typeMessage === 'edit' ? 1 : 0,
chatReference: chatReference,
message: messageText,
lastReference: reference,
proofOfWorkNonce: 0,
isEncrypted: 0, // Set default to not encrypted for groups
isText: 1
}
})
return _computePow(groupResponse)
}
}

View File

@ -51,6 +51,16 @@ export class UserInfo extends LitElement {
return imageHTMLRes
}
openProfile(){
try {
const customEvent = new CustomEvent('open-visiting-profile', {
detail: this.userName
});
window.parent.dispatchEvent(customEvent);
} catch (error) { /* empty */ }
}
render() {
let avatarImg = ""
@ -84,7 +94,7 @@ export class UserInfo extends LitElement {
<img src="/img/incognito.png" alt="avatar" />
</div>`
: ""}
<div class="user-info-header">
<div class="user-info-header">
${this.selectedHead && this.selectedHead.name ? this.selectedHead.name : this.selectedHead ? cropAddress(this.selectedHead.address) : null}
</div>
<div class="send-message-button" @click="${() => {
@ -102,6 +112,19 @@ export class UserInfo extends LitElement {
}}>
${translate("chatpage.cchange59")}
</div>
${this.userName ? html`
<div style="margin-top: 5px;text-transform: uppercase;" class="send-message-button" @click=${() => {
setTimeout(() => {
this.openProfile()
}, 250);
this.setOpenUserInfo(false)
}}>
${translate("profile.profile18")}
</div>
` : ''}
</div>
`
}

View File

@ -70,5 +70,11 @@ export const VOTE_ON_POLL= 'VOTE_ON_POLL'
//CREATE_POLL
export const CREATE_POLL= 'CREATE_POLL'
//GET_PROFILE_DATA
export const GET_PROFILE_DATA = 'GET_PROFILE_DATA'
// SET_PROFILE_DATA
export const SET_PROFILE_DATA= 'SET_PROFILE_DATA'
//GET_DAY_SUMMARY
export const GET_DAY_SUMMARY = 'GET_DAY_SUMMARY'

View File

@ -481,11 +481,10 @@ class WebBrowser extends LitElement {
const arbitraryFee = (Number(data) / 1e8).toFixed(8)
return {
timestamp,
fee : Number(data),
fee: Number(data),
feeToShow: arbitraryFee
}
}
async sendQortFee() {
const myNode = window.parent.reduxStore.getState().app.nodeConfig.knownNodes[window.parent.reduxStore.getState().app.nodeConfig.node]
const nodeUrl = myNode.protocol + '://' + myNode.domain + ':' + myNode.port
@ -660,9 +659,9 @@ class WebBrowser extends LitElement {
}
const makeTransactionRequest = async (lastRef) => {
let votedialog1 = get("transactions.votedialog1")
let votedialog2 = get("transactions.votedialog2")
let feeDialog = get("walletpage.wchange12")
let votedialog1 = get("transactions.votedialog1")
let votedialog2 = get("transactions.votedialog2")
let feeDialog = get("walletpage.wchange12")
let myTxnrequest = await parentEpml.request('transaction', {
type: 9,
@ -1622,7 +1621,7 @@ class WebBrowser extends LitElement {
}
case actions.SEND_LOCAL_NOTIFICATION: {
const {title, url, icon, message} = data
const { title, url, icon, message } = data
try {
const id = `appNotificationList-${this.selectedAddress.address}`
const checkData = localStorage.getItem(id) ? JSON.parse(localStorage.getItem(id)) : null
@ -1641,7 +1640,7 @@ class WebBrowser extends LitElement {
this.updateLastNotification(id, this.name)
break
} else {
throw new Error(`duration until another notification can be sent: ${interval - timeDifference}`)
throw new Error(`invalid data`)
}
} else if(!lastNotification){
parentEpml.request('showNotification', {
@ -2064,6 +2063,199 @@ class WebBrowser extends LitElement {
break
}
case 'GET_PROFILE_DATA': {
const defaultProperties = ['tagline', 'bio', 'wallets']
const requiredFields = ['property'];
const missingFields = [];
requiredFields.forEach((field) => {
if (!data[field] && data[field] !== 0) {
missingFields.push(field);
}
});
if (missingFields.length > 0) {
const missingFieldsString = missingFields.join(', ');
const errorMsg = `Missing fields: ${missingFieldsString}`
let data = {};
data['error'] = errorMsg;
response = JSON.stringify(data);
break
}
try {
const profileData = window.parent.reduxStore.getState().app.profileData
if (!profileData) {
throw new Error('User does not have a profile')
}
const property = data.property
const propertyIndex = defaultProperties.indexOf(property)
if (propertyIndex !== -1) {
const requestedData = profileData[property]
if (requestedData) {
response = JSON.stringify(requestedData);
break
} else {
throw new Error('Cannot find requested data')
}
}
if (property.includes('-private')) {
const resPrivateProperty = await showModalAndWait(
actions.GET_PROFILE_DATA, {
property
}
);
if (resPrivateProperty.action === 'accept') {
const requestedData = profileData.customData[property]
if (requestedData) {
response = JSON.stringify(requestedData);
break
} else {
throw new Error('Cannot find requested data')
}
} else {
throw new Error('User denied permission for private property')
}
} else {
const requestedData = profileData.customData[property]
if (requestedData) {
response = JSON.stringify(requestedData);
break
} else {
throw new Error('Cannot find requested data')
}
}
} catch (error) {
const obj = {};
const errorMsg = error.message || 'Failed to join the group.';
obj['error'] = errorMsg;
response = JSON.stringify(obj);
} finally {
this.loader.hide();
}
break;
}
case 'SET_PROFILE_DATA': {
const requiredFields = ['property', 'data'];
const missingFields = [];
requiredFields.forEach((field) => {
if (!data[field] && data[field] !== 0) {
missingFields.push(field);
}
});
if (missingFields.length > 0) {
const missingFieldsString = missingFields.join(', ');
const errorMsg = `Missing fields: ${missingFieldsString}`
let data = {};
data['error'] = errorMsg;
response = JSON.stringify(data);
break
}
try {
const property = data.property
const payload = data.data
const uniqueId = this.uid.rnd()
const fee = await this.getArbitraryFee()
const resSetPrivateProperty = await showModalAndWait(
actions.SET_PROFILE_DATA, {
property,
fee: fee.feeToShow
}
);
if (resSetPrivateProperty.action !== 'accept') throw new Error('User declined permission')
//dispatch event and wait until I get a response to continue
// Create and dispatch custom event
const customEvent = new CustomEvent('qortal-request-set-profile-data', {
detail: {
property,
payload,
uniqueId
}
});
window.parent.dispatchEvent(customEvent);
// Wait for response event
const res = await new Promise((resolve, reject) => {
function handleResponseEvent(event) {
// Handle the data from the event, if any
const responseData = event.detail;
if(responseData && responseData.uniqueId !== uniqueId) return
// Clean up by removing the event listener once we've received the response
window.removeEventListener('qortal-request-set-profile-data-response', handleResponseEvent);
if (responseData.response === 'saved') {
resolve(responseData);
} else {
reject(new Error('not saved'));
}
}
// Set up an event listener to wait for the response
window.addEventListener('qortal-request-set-profile-data-response', handleResponseEvent);
});
if(!res.response) throw new Error('Failed to set property')
response = JSON.stringify(res.response);
} catch (error) {
const obj = {};
const errorMsg = error.message || 'Failed to set property.';
obj['error'] = errorMsg;
response = JSON.stringify(obj);
} finally {
this.loader.hide();
}
break;
}
case 'OPEN_PROFILE': {
const requiredFields = ['name'];
const missingFields = [];
requiredFields.forEach((field) => {
if (!data[field] && data[field] !== 0) {
missingFields.push(field);
}
});
if (missingFields.length > 0) {
const missingFieldsString = missingFields.join(', ');
const errorMsg = `Missing fields: ${missingFieldsString}`
let data = {};
data['error'] = errorMsg;
response = JSON.stringify(data);
break
}
try {
const customEvent = new CustomEvent('open-visiting-profile', {
detail: data.name
});
window.parent.dispatchEvent(customEvent);
response = JSON.stringify(true);
} catch (error) {
const obj = {};
const errorMsg = error.message || 'Failed to open profile';
obj['error'] = errorMsg;
response = JSON.stringify(obj);
}
break;
}
case actions.GET_USER_WALLET: {
const requiredFields = ['coin'];
const missingFields = [];
@ -3561,7 +3753,7 @@ async function showModalAndWait(type, data) {
${type === actions.GET_USER_WALLET ? `
<div class="modal-subcontainer">
<p class="modal-paragraph">${get("browserpage.bchange49")}</p>
<p class="modal-paragraph">${get("browserpage.bchange52")}</p>
</div>
` : ''}
${type === actions.GET_WALLET_BALANCE ? `
@ -3593,6 +3785,21 @@ async function showModalAndWait(type, data) {
<p class="modal-paragraph">${get("browserpage.bchange46")}: <span> ${data.filename}</span></p>
</div>
` : ''}
${type === actions.GET_PROFILE_DATA ? `
<div class="modal-subcontainer">
<p class="modal-paragraph">${get("browserpage.bchange49")}: <span style="font-weight: bold"> ${data.property}</span></p>
</div>
` : ''}
${type === actions.SET_PROFILE_DATA ? `
<div class="modal-subcontainer">
<p class="modal-paragraph">${get("browserpage.bchange50")} <span style="font-weight: bold"> ${data.property}</span></p>
<br>
<p style="font-size: 16px;overflow-wrap: anywhere;" class="modal-paragraph">${get('browserpage.bchange47')} <span style="font-weight: bold">${data.fee} QORT fee</span></p>
<br>
<p style="font-size: 16px;overflow-wrap: anywhere;" class="modal-paragraph">${get('browserpage.bchange51')} </p>
</div>
` : ''}
${type === actions.NOTIFICATIONS_PERMISSION ? `
<div class="modal-subcontainer">
<p class="modal-paragraph">${get("browserpage.bchange48")}</p>