Compare commits

...

8 Commits

Author SHA1 Message Date
AlphaX
94835b5118
Merge pull request #365 from Qortal/feature/add-support-base64-field-qr
add support for base64 field
2025-04-27 10:10:05 +02:00
1287d0feed remove unused component 2025-04-14 22:36:38 +03:00
d51216516d fix display name 2025-04-12 07:40:41 +03:00
3c3e1d6e38 allow no name for qr- use default in ui 2025-04-12 07:14:15 +03:00
e9a0da9fda add support for base64 field 2025-04-10 00:45:29 +03:00
AlphaX
747b555edb
Merge pull request #364 from AlphaX-Qortal/master
Fix copy to clipboard in iframe
2025-03-20 11:52:49 +01:00
AlphaX
36e7bd8ba6
Fix copy to clipboard in iframe 2025-03-20 11:51:59 +01:00
AlphaX
29965500fc
Update browser.src.js 2025-03-20 11:28:17 +01:00
2 changed files with 34 additions and 309 deletions

View File

@ -1,299 +0,0 @@
import { html, LitElement } from 'lit'
import { Epml } from '../../../epml'
import { chatGroupStyles } from './plugins-css'
import './WrapperModal'
import '@material/mwc-button'
import '@material/mwc-dialog'
import '@material/mwc-icon'
import '@polymer/paper-spinner/paper-spinner-lite.js'
// Multi language support
import { translate } from '../../../../core/translate'
const parentEpml = new Epml({ type: 'WINDOW', source: window.parent })
class ChatGroupInvites extends LitElement {
static get properties() {
return {
isLoading: { type: Boolean },
isOpenLeaveModal: { type: Boolean },
leaveGroupObj: { type: Object },
error: { type: Boolean },
message: { type: String },
chatHeads: { type: Array },
groupAdmin: { attribute: false },
groupMembers: { attribute: false },
selectedHead: { type: Object }
}
}
static get styles() {
return [chatGroupStyles]
}
constructor() {
super()
this.isLoading = false
this.isOpenLeaveModal = false
this.leaveGroupObj = {}
this.leaveFee = 0.001
this.error = false
this.message = ''
this.chatHeads = []
this.groupAdmin = []
this.groupMembers = []
}
render() {
return html`
<vaadin-icon @click=${() => {this.isOpenLeaveModal = true}} class="top-bar-icon" style="margin: 0px 20px" icon="vaadin:users" slot="icon"></vaadin-icon>
<wrapper-modal
.removeImage=${() => {
if (this.isLoading) return
this.isOpenLeaveModal = false
}}
style=${this.isOpenLeaveModal ? "display: block" : "display: none"}
>
<div style="text-align:center">
<h1>${translate("grouppage.gchange35")}</h1>
<hr>
</div>
<button @click=${() => this._addAdmin(this.leaveGroupObj.groupId)}>Promote to Admin</button>
<button @click=${() => this._removeAdmin(this.leaveGroupObj.groupId)}>Remove as Admin</button>
<div style="text-align:right; height:36px;">
<span ?hidden="${!this.isLoading}">
<!-- loading message -->
${translate("grouppage.gchange36")} &nbsp;
<paper-spinner-lite style="margin-top:12px;" ?active="${this.isLoading}" alt="Leaving"></paper-spinner-lite>
</span>
<span ?hidden=${this.message === ""} style="${this.error ? "color:red;" : ""}">${this.message}</span>
</div>
<button @click=${() => {this.isOpenLeaveModal = false}} class="modal-button" ?disabled="${this.isLoading}">${translate("general.close")}</button>
</wrapper-modal>
`
}
firstUpdated() {
// ...
}
timeIsoString(timestamp) {
let myTimestamp = timestamp === undefined ? 1587560082346 : timestamp
let time = new Date(myTimestamp)
return time.toISOString()
}
resetDefaultSettings() {
this.error = false
this.message = ''
this.isLoading = false
}
renderErr9Text() {
return html`${translate("grouppage.gchange49")}`
}
async confirmRelationship(reference) {
let interval = null
let stop = false
const getAnswer = async () => {
if (!stop) {
stop = true
try {
let myRef = await parentEpml.request("apiCall", {
type: "api",
url: `/transactions/reference/${reference}`
})
if (myRef && myRef.type) {
clearInterval(interval)
this.isLoading = false
this.isOpenLeaveModal = false
}
} catch (error) { }
stop = false
}
}
interval = setInterval(getAnswer, 5000)
}
async getLastRef() {
return await parentEpml.request("apiCall", {
type: "api",
url: `/addresses/lastreference/${this.selectedAddress.address}`
})
}
getTxnRequestResponse(txnResponse, reference) {
if (txnResponse === true) {
this.message = this.renderErr9Text()
this.error = false
this.confirmRelationship(reference)
} else {
this.error = true
this.message = ''
throw new Error(txnResponse)
}
}
async convertBytesForSigning(transactionBytesBase58) {
return await parentEpml.request("apiCall", {
type: "api",
method: "POST",
url: `/transactions/convert`,
body: `${transactionBytesBase58}`
})
}
async signTx(body) {
return await parentEpml.request("apiCall", {
type: "api",
method: "POST",
url: `/transactions/sign`,
body: body,
headers: {
'Content-Type': 'application/json'
}
})
}
async process(body) {
return await parentEpml.request("apiCall", {
type: "api",
method: "POST",
url: `/transactions/process`,
body: body
})
}
async _addAdmin(groupId) {
// Reset Default Settings...
this.resetDefaultSettings()
const leaveFeeInput = this.leaveFee
this.isLoading = true
// Get Last Ref
const validateReceiver = async () => {
let lastRef = await this.getLastRef()
let myTransaction = await makeTransactionRequest(lastRef)
this.getTxnRequestResponse(myTransaction, lastRef)
}
// Make Transaction Request
const makeTransactionRequest = async (lastRef) => {
const body = {
timestamp: Date.now(),
reference: lastRef,
fee: leaveFeeInput,
ownerPublicKey: window.parent.Base58.encode(window.parent.reduxStore.getState().app.selectedAddress.keyPair.publicKey),
groupId: groupId,
member: this.selectedHead.address
}
const bodyToString = JSON.stringify(body)
let transactionBytes = await parentEpml.request("apiCall", {
type: "api",
method: "POST",
url: `/groups/addadmin`,
body: bodyToString,
headers: {
"Content-Type": "application/json"
}
})
const readforsign = await this.convertBytesForSigning(transactionBytes)
const body2 = {
privateKey: window.parent.Base58.encode(window.parent.reduxStore.getState().app.selectedAddress.keyPair.privateKey),
transactionBytes: readforsign
}
const bodyToString2 = JSON.stringify(body2)
let signTransaction = await this.signTx(bodyToString2)
return await this.process(signTransaction)
}
await validateReceiver()
}
async _removeAdmin(groupId) {
// Reset Default Settings...
this.resetDefaultSettings()
const leaveFeeInput = this.leaveFee
this.isLoading = true
// Get Last Ref
const validateReceiver = async () => {
let lastRef = await this.getLastRef()
let myTransaction = await makeTransactionRequest(lastRef)
this.getTxnRequestResponse(myTransaction, lastRef)
}
// Make Transaction Request
const makeTransactionRequest = async (lastRef) => {
const body = {
timestamp: Date.now(),
reference: lastRef,
fee: leaveFeeInput,
ownerPublicKey: window.parent.Base58.encode(window.parent.reduxStore.getState().app.selectedAddress.keyPair.publicKey),
groupId: groupId,
admin: this.selectedHead.address
}
const bodyToString = JSON.stringify(body)
let transactionBytes = await parentEpml.request("apiCall", {
type: "api",
method: "POST",
url: `/groups/removeadmin`,
body: bodyToString,
headers: {
"Content-Type": "application/json"
}
})
const readforsign = await this.convertBytesForSigning(
transactionBytes
)
const body2 = {
privateKey: window.parent.Base58.encode(window.parent.reduxStore.getState().app.selectedAddress.keyPair.privateKey),
transactionBytes: readforsign
}
const bodyToString2 = JSON.stringify(body2)
let signTransaction = await this.signTx(bodyToString2)
return await this.process(signTransaction)
}
await validateReceiver()
}
// Standard functions
getApiKey() {
const myNode = window.parent.reduxStore.getState().app.nodeConfig.knownNodes[window.parent.reduxStore.getState().app.nodeConfig.node]
return myNode.apiKey
}
isEmptyArray(arr) {
if (!arr) { return true }
return arr.length === 0
}
round(number) {
return (Math.round(parseFloat(number) * 1e8) / 1e8).toFixed(8)
}
}
window.customElements.define('chat-group-invites', ChatGroupInvites)

View File

@ -185,6 +185,7 @@ class WebBrowser extends LitElement {
)
}
const render = () => {
const myNode = window.parent.reduxStore.getState().app.nodeConfig.knownNodes[window.parent.reduxStore.getState().app.nodeConfig.node]
const nodeUrl = myNode.protocol + '://' + myNode.domain + ':' + myNode.port
@ -265,7 +266,7 @@ class WebBrowser extends LitElement {
${this.renderFollowUnfollowButton()}
</div>
<div class="iframe-container">
<iframe id="browser-iframe" src="${this.url}" sandbox="allow-scripts allow-same-origin allow-forms allow-downloads allow-modals" allow="fullscreen">
<iframe id="browser-iframe" src="${this.url}" sandbox="allow-scripts allow-same-origin allow-forms allow-downloads allow-modals" allow="fullscreen; clipboard-read; clipboard-write;">
<span style="color: var(--black);">${translate('browserpage.bchange6')}</span>
</iframe>
</div>
@ -1417,7 +1418,7 @@ class WebBrowser extends LitElement {
case actions.PUBLISH_QDN_RESOURCE: {
// optional fields: encrypt:boolean recipientPublicKey:string
const requiredFields = ['service', 'name']
const requiredFields = ['service']
const missingFields = []
let dataSentBack = {}
requiredFields.forEach((field) => {
@ -1439,7 +1440,7 @@ class WebBrowser extends LitElement {
response = JSON.stringify(dataSentBack)
break
}
if (!data.file && !data.data64) {
if (!data.file && !data.data64 && !data.base64) {
let myMsg1 = get("modals.mpchange22")
let myMsg2 = get("walletpage.wchange44")
await showErrorAndWait("ACTION_FAILED", { id1: myMsg1, id2: myMsg2 })
@ -1450,9 +1451,13 @@ class WebBrowser extends LitElement {
}
// Use "default" if user hasn't specified an identifer
const service = data.service
const name = data.name
const name = data.name || this.getMyName()
if(!name){
dataSentBack['error'] = `Missing name`
break
}
let identifier = data.identifier
let data64 = data.data64
let data64 = data.data64 || data.base64
const filename = data.filename
const title = data.title
const description = data.description
@ -1629,10 +1634,16 @@ class WebBrowser extends LitElement {
}
const getArbitraryFee = await this.getArbitraryFee()
feeAmount = getArbitraryFee.fee
const reformatResources = resources.map((resource)=> {
return {
...resource,
name: resource.name || this.getMyName()
}
})
const res2 = await showModalAndWait(
actions.PUBLISH_MULTIPLE_QDN_RESOURCES,
{
resources,
resources: reformatResources,
encrypt: data.encrypt,
feeAmount: getArbitraryFee.feeToShow
}
@ -1647,9 +1658,9 @@ class WebBrowser extends LitElement {
}
let failedPublishesIdentifiers = []
this.loader.show()
for (const resource of resources) {
for (const resource of reformatResources) {
try {
const requiredFields = ['service', 'name']
const requiredFields = ['service']
const missingFields = []
requiredFields.forEach((field) => {
if (!resource[field]) {
@ -1665,7 +1676,7 @@ class WebBrowser extends LitElement {
})
continue
}
if (!resource.file && !resource.data64) {
if (!resource.file && !resource.data64 && !resource.base64) {
const errorMsg = 'No data or file was submitted'
failedPublishesIdentifiers.push({
reason: errorMsg,
@ -1675,8 +1686,16 @@ class WebBrowser extends LitElement {
}
const service = resource.service
const name = resource.name
if(!name){
const errorMsg = `Missing name`
failedPublishesIdentifiers.push({
reason: errorMsg,
identifier: resource.identifier
})
continue
}
let identifier = resource.identifier
let data64 = resource.data64
let data64 = resource.data64 || resource.base64
const filename = resource.filename
const title = resource.title
const description = resource.description
@ -4407,6 +4426,11 @@ class WebBrowser extends LitElement {
}, 60000)
}
getMyName(){
const names = window.parent.reduxStore.getState().app.accountInfo.names
if(names.length === 0) return null
return names[0].name
}
renderFullScreen() {
if (window.innerHeight === screen.height) {
return html`