changes for: `1.2.4`
This commit is contained in:
parent
0d02b1b440
commit
a3f92bc3e8
|
@ -34,3 +34,4 @@ npm-debug.log*
|
||||||
/src/extension/webInject.js
|
/src/extension/webInject.js
|
||||||
releases
|
releases
|
||||||
rules.json
|
rules.json
|
||||||
|
docs
|
||||||
|
|
|
@ -1,5 +1,11 @@
|
||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## Manifest Version 1.2.4
|
||||||
|
|
||||||
|
- updated showing assets page to use new api
|
||||||
|
- removed yup score from assets page
|
||||||
|
- change the info modal in settings
|
||||||
|
|
||||||
## Manifest Version 1.2.3
|
## Manifest Version 1.2.3
|
||||||
|
|
||||||
- injected stub with chrome feature available in chrome 103 ( register world ) to bypass CSP
|
- injected stub with chrome feature available in chrome 103 ( register world ) to bypass CSP
|
||||||
|
|
|
@ -3,8 +3,8 @@
|
||||||
"name": "__MSG_appName__",
|
"name": "__MSG_appName__",
|
||||||
"description": "__MSG_appDesc__",
|
"description": "__MSG_appDesc__",
|
||||||
"default_locale": "en",
|
"default_locale": "en",
|
||||||
"version": "1.2.2",
|
"version": "1.2.4",
|
||||||
"version_name": "1.2.2",
|
"version_name": "1.2.4",
|
||||||
"icons": {
|
"icons": {
|
||||||
"16": "assets/extension-icon/wallet_16.png",
|
"16": "assets/extension-icon/wallet_16.png",
|
||||||
"32": "assets/extension-icon/wallet_32.png",
|
"32": "assets/extension-icon/wallet_32.png",
|
||||||
|
|
|
@ -6,7 +6,6 @@
|
||||||
</ion-toolbar>
|
</ion-toolbar>
|
||||||
</ion-header>
|
</ion-header>
|
||||||
<ion-content class="ion-padding">
|
<ion-content class="ion-padding">
|
||||||
|
|
||||||
<ion-loading
|
<ion-loading
|
||||||
:is-open="loading"
|
:is-open="loading"
|
||||||
cssClass="my-custom-class"
|
cssClass="my-custom-class"
|
||||||
|
@ -30,19 +29,12 @@
|
||||||
<ion-icon style="margin-left: 0.5rem" :icon="copyOutline"></ion-icon>
|
<ion-icon style="margin-left: 0.5rem" :icon="copyOutline"></ion-icon>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
<template v-if="isError">
|
<template v-if="isError">
|
||||||
Assets info could not be retrieved because of an http error, API down or conectivity issues.
|
Assets info could not be retrieved because of an http error, API down or
|
||||||
</template>
|
conectivity issues.
|
||||||
<template v-else-if="noAssets">
|
|
||||||
No assets found for this wallet address.
|
|
||||||
</template>
|
</template>
|
||||||
|
<template v-else-if="noAssets"> No assets found for this wallet address. </template>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<ion-item v-if="assets.yupScore">
|
<template v-if="ethTokens.length || polyTokens.length">
|
||||||
<span style="font-size: 0.9rem">YUP Score:</span> <span style="font-size: 1.1rem; margin-left: 0.5rem">{{ assets.yupScore.toFixed(2) }}</span>
|
|
||||||
</ion-item>
|
|
||||||
<ion-item>
|
|
||||||
<p style="font-size: 0.7rem">YUP score is a score of your wallet based on assets and transactions. </p>
|
|
||||||
</ion-item>
|
|
||||||
<template v-if="assets.tokens">
|
|
||||||
<template v-if="ethTokens.length">
|
<template v-if="ethTokens.length">
|
||||||
<ion-item>Ethereum Tokens</ion-item>
|
<ion-item>Ethereum Tokens</ion-item>
|
||||||
<ion-list>
|
<ion-list>
|
||||||
|
@ -57,7 +49,9 @@
|
||||||
@error="token.image = getUrl('assets/randomGrad.svg')"
|
@error="token.image = getUrl('assets/randomGrad.svg')"
|
||||||
/>
|
/>
|
||||||
</ion-avatar>
|
</ion-avatar>
|
||||||
<ion-label><b>{{ token?.symbol }}:</b> {{ token?.balance }}</ion-label>
|
<ion-label
|
||||||
|
><b>{{ token?.symbol }}:</b> {{ token?.balance }}</ion-label
|
||||||
|
>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
<ion-item v-if="hasMore.ethTokens">
|
<ion-item v-if="hasMore.ethTokens">
|
||||||
<ion-button @click="loadMore('ethTokens')">Load More</ion-button>
|
<ion-button @click="loadMore('ethTokens')">Load More</ion-button>
|
||||||
|
@ -79,17 +73,17 @@
|
||||||
@error="token.image = getUrl('assets/randomGrad.svg')"
|
@error="token.image = getUrl('assets/randomGrad.svg')"
|
||||||
/>
|
/>
|
||||||
</ion-avatar>
|
</ion-avatar>
|
||||||
<ion-label><b>{{ token?.symbol }}:</b> {{ token?.balance }}</ion-label>
|
<ion-label
|
||||||
|
><b>{{ token?.symbol }}:</b> {{ token?.balance }}</ion-label
|
||||||
|
>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
<ion-item v-if="hasMore.polyTokens">
|
<ion-item v-if="hasMore.polyTokens">
|
||||||
<ion-button @click="loadMore('polyTokens')">Load More</ion-button>
|
<ion-button @click="loadMore('polyTokens')">Load More</ion-button>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
</ion-list>
|
</ion-list>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
</template>
|
</template>
|
||||||
<template v-if="assets.nfts">
|
<template v-if="ethNfts.length || polyNfts.length">
|
||||||
|
|
||||||
<template v-if="ethNfts.length">
|
<template v-if="ethNfts.length">
|
||||||
<ion-item>Ethereum NFTs</ion-item>
|
<ion-item>Ethereum NFTs</ion-item>
|
||||||
<ion-list>
|
<ion-list>
|
||||||
|
@ -104,7 +98,9 @@
|
||||||
@error="nft.imageURI = getUrl('assets/randomGrad.svg')"
|
@error="nft.imageURI = getUrl('assets/randomGrad.svg')"
|
||||||
/>
|
/>
|
||||||
</ion-avatar>
|
</ion-avatar>
|
||||||
<ion-label><b>{{ nft?.collectionName }}</b></ion-label>
|
<ion-label
|
||||||
|
><b>{{ nft?.collectionName }}</b></ion-label
|
||||||
|
>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
<ion-item v-if="hasMore.ethNfts">
|
<ion-item v-if="hasMore.ethNfts">
|
||||||
<ion-button @click="loadMore('ethNfts')">Load More</ion-button>
|
<ion-button @click="loadMore('ethNfts')">Load More</ion-button>
|
||||||
|
@ -126,16 +122,16 @@
|
||||||
@error="nft.imageURI = getUrl('assets/randomGrad.svg')"
|
@error="nft.imageURI = getUrl('assets/randomGrad.svg')"
|
||||||
/>
|
/>
|
||||||
</ion-avatar>
|
</ion-avatar>
|
||||||
<ion-label><b>{{ nft?.collectionName }}</b></ion-label>
|
<ion-label
|
||||||
|
><b>{{ nft?.collectionName }}</b></ion-label
|
||||||
|
>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
<ion-item v-if="hasMore.polyNfts">
|
<ion-item v-if="hasMore.polyNfts">
|
||||||
<ion-button @click="loadMore('polyNfts')">Load More</ion-button>
|
<ion-button @click="loadMore('polyNfts')">Load More</ion-button>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
</ion-list>
|
</ion-list>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
</template>
|
</template>
|
||||||
<template v-if="assets.poaps">
|
|
||||||
<template v-if="poaps.length">
|
<template v-if="poaps.length">
|
||||||
<ion-item>POAPs</ion-item>
|
<ion-item>POAPs</ion-item>
|
||||||
<ion-list>
|
<ion-list>
|
||||||
|
@ -150,7 +146,9 @@
|
||||||
@error="nft.image = getUrl('assets/randomGrad.svg')"
|
@error="nft.image = getUrl('assets/randomGrad.svg')"
|
||||||
/>
|
/>
|
||||||
</ion-avatar>
|
</ion-avatar>
|
||||||
<ion-label><b>{{ nft?.title }}</b></ion-label>
|
<ion-label
|
||||||
|
><b>{{ nft?.title }}</b></ion-label
|
||||||
|
>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
<ion-item v-if="hasMore.poaps">
|
<ion-item v-if="hasMore.poaps">
|
||||||
<ion-button @click="loadMore('poaps')">Load More</ion-button>
|
<ion-button @click="loadMore('poaps')">Load More</ion-button>
|
||||||
|
@ -158,163 +156,407 @@
|
||||||
</ion-list>
|
</ion-list>
|
||||||
</template>
|
</template>
|
||||||
</template>
|
</template>
|
||||||
</template>
|
|
||||||
</ion-content>
|
</ion-content>
|
||||||
</ion-page>
|
</ion-page>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent, Ref, ref, reactive } from "vue";
|
import { defineComponent, Ref, ref, reactive } from "vue";
|
||||||
import { IonContent, IonHeader, IonPage, IonTitle, IonToolbar, onIonViewWillEnter, IonItem, IonLabel, IonAvatar, IonList, IonButton, IonToast, IonLoading, IonIcon } from "@ionic/vue";
|
import {
|
||||||
import { getSelectedAccount, copyAddress, getUrl } from "@/utils/platform"
|
IonContent,
|
||||||
import type { Account } from "@/extension/types"
|
IonHeader,
|
||||||
|
IonPage,
|
||||||
|
IonTitle,
|
||||||
|
IonToolbar,
|
||||||
|
onIonViewWillEnter,
|
||||||
|
IonItem,
|
||||||
|
IonLabel,
|
||||||
|
IonAvatar,
|
||||||
|
IonList,
|
||||||
|
IonButton,
|
||||||
|
IonToast,
|
||||||
|
IonLoading,
|
||||||
|
IonIcon,
|
||||||
|
} from "@ionic/vue";
|
||||||
|
import { getSelectedAccount, copyAddress, getUrl } from "@/utils/platform";
|
||||||
|
import type { Account } from "@/extension/types";
|
||||||
|
|
||||||
import { copyOutline } from "ionicons/icons";
|
import { copyOutline } from "ionicons/icons";
|
||||||
|
|
||||||
|
|
||||||
const yupAssetsApi = 'https://api.yup.io/profile'
|
|
||||||
|
|
||||||
interface IProfileToken {
|
interface IProfileToken {
|
||||||
address: string
|
address: string;
|
||||||
balance: number
|
balance: number;
|
||||||
image: string
|
image: string;
|
||||||
name: string
|
name: string;
|
||||||
symbol: string
|
symbol: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IProfileNFT {
|
interface IProfileNFT {
|
||||||
address: string
|
address: string;
|
||||||
collectionImageURI: string
|
collectionImageURI: string;
|
||||||
collectionName: string
|
collectionName: string;
|
||||||
imageURI: string
|
imageURI: string;
|
||||||
link: string
|
link: string;
|
||||||
tokenId: number
|
tokenId: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IProfilePOAP {
|
interface IProfilePOAP {
|
||||||
description: string
|
description: string;
|
||||||
eventId: string
|
eventId: string;
|
||||||
image: string
|
image: string;
|
||||||
link: string
|
link: string;
|
||||||
title: string
|
title: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
components: { IonContent, IonHeader, IonPage, IonTitle, IonToolbar, IonItem, IonLabel, IonAvatar, IonList, IonButton, IonToast, IonLoading, IonIcon },
|
components: {
|
||||||
|
IonContent,
|
||||||
|
IonHeader,
|
||||||
|
IonPage,
|
||||||
|
IonTitle,
|
||||||
|
IonToolbar,
|
||||||
|
IonItem,
|
||||||
|
IonLabel,
|
||||||
|
IonAvatar,
|
||||||
|
IonList,
|
||||||
|
IonButton,
|
||||||
|
IonToast,
|
||||||
|
IonLoading,
|
||||||
|
IonIcon,
|
||||||
|
},
|
||||||
setup: () => {
|
setup: () => {
|
||||||
const selectedAccount = ref({}) as Ref<Account>
|
const selectedAccount = ref({}) as Ref<Account>;
|
||||||
const assets = ref({}) as Ref< {
|
const loading = ref(true);
|
||||||
yupScore?: number
|
const isError = ref(false);
|
||||||
tokens?: any[]
|
const noAssets = ref(false);
|
||||||
nfts?: any[]
|
|
||||||
poaps?: any[]
|
|
||||||
}>
|
|
||||||
const loading = ref(true)
|
|
||||||
const isError = ref(false)
|
|
||||||
const noAssets = ref(false)
|
|
||||||
const toastState = ref(false);
|
const toastState = ref(false);
|
||||||
const ethTokens = ref([]) as Ref< IProfileToken[]>
|
const ethTokens = ref([]) as Ref<IProfileToken[]>;
|
||||||
const polyTokens = ref([]) as Ref< IProfileToken[]>
|
const polyTokens = ref([]) as Ref<IProfileToken[]>;
|
||||||
const ethNfts = ref([]) as Ref< IProfileNFT[]>
|
const ethNfts = ref([]) as Ref<IProfileNFT[]>;
|
||||||
const polyNfts = ref([]) as Ref< IProfileNFT[]>
|
const polyNfts = ref([]) as Ref<IProfileNFT[]>;
|
||||||
const poaps = ref([]) as Ref< IProfilePOAP[]>
|
const poaps = ref([]) as Ref<IProfilePOAP[]>;
|
||||||
const hasMore = reactive({
|
const hasMore = reactive({
|
||||||
poaps: true,
|
poaps: true,
|
||||||
ethTokens: true,
|
ethTokens: true,
|
||||||
polyTokens: true,
|
polyTokens: true,
|
||||||
ethNfts: true,
|
ethNfts: true,
|
||||||
polyNfts: true,
|
polyNfts: true,
|
||||||
})
|
});
|
||||||
const getToastRef = () => toastState;
|
const getToastRef = () => toastState;
|
||||||
|
|
||||||
onIonViewWillEnter(async () => {
|
const resources = ["nfts", "poaps", "tokens"];
|
||||||
selectedAccount.value = await getSelectedAccount()
|
const chains = ["ethereum", "polygon"];
|
||||||
const req = await fetch(`${yupAssetsApi}/${selectedAccount.value.address}`)
|
|
||||||
if(req.ok) {
|
const fetchFromWallet = async ({
|
||||||
assets.value = (await req.json()) ?? {}
|
apiBase = "https://api.yup.io",
|
||||||
if(!('poaps' in assets.value) && !('tokens' in assets.value) && !('nfts' in assets.value)) {
|
address,
|
||||||
noAssets.value = true
|
resource = resources[0],
|
||||||
}
|
chain = chains[0],
|
||||||
if ('poaps' in assets.value) {
|
start = 0,
|
||||||
poaps.value = assets.value?.poaps?.slice(0, 10) ?? []
|
limit = 10,
|
||||||
if(poaps.value.length >= (assets.value?.poaps?.length ?? 0)) {
|
}: {
|
||||||
hasMore.poaps = false
|
apiBase?: string;
|
||||||
}
|
address: string;
|
||||||
}
|
resource?: string;
|
||||||
if ('nfts' in assets.value) {
|
chain?: string;
|
||||||
ethNfts.value = assets.value?.nfts?.filter(n => n.chain === 'ethereum').slice(0, 10) ?? []
|
start?: number;
|
||||||
if(ethNfts.value.length >= (assets.value?.nfts?.filter(n => n.chain === 'ethereum').length ?? 0)) {
|
limit?: number;
|
||||||
hasMore.ethNfts = false
|
}) => {
|
||||||
}
|
try {
|
||||||
polyNfts.value = assets.value?.nfts?.filter(n => n.chain === 'polygon').slice(0, 10) ?? []
|
const res = await fetch(
|
||||||
if(polyNfts.value.length >= (assets.value?.nfts?.filter(n => n.chain === 'polygon').length ?? 0)) {
|
`${apiBase}/web3-profiles/${resource}/${address}?chain=${chain}&start=${start}&limit=${limit}`
|
||||||
hasMore.polyNfts = false
|
);
|
||||||
}
|
const req = await res.json();
|
||||||
}
|
if (res.ok) {
|
||||||
if ('tokens' in assets.value) {
|
return req;
|
||||||
ethTokens.value = assets.value?.tokens?.filter(n => n.chain === 'ethereum').slice(0, 10) ?? []
|
|
||||||
if(ethTokens.value.length >= (assets.value?.tokens?.filter(n => n.chain === 'ethereum').length ?? 0)) {
|
|
||||||
hasMore.ethTokens = false
|
|
||||||
}
|
|
||||||
polyTokens.value = assets.value?.tokens?.filter(n => n.chain === 'polygon').slice(0, 10) ?? []
|
|
||||||
if(polyTokens.value.length >= (assets.value?.tokens?.filter(n => n.chain === 'polygon').length ?? 0)) {
|
|
||||||
hasMore.polyTokens = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
isError.value = true
|
return null;
|
||||||
}
|
}
|
||||||
loading .value = false
|
} catch (error) {
|
||||||
|
console.error("Failed to fetch web3 profiles", error);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const walletLoadArgs = {
|
||||||
|
address: "",
|
||||||
|
start: 0,
|
||||||
|
limit: 11,
|
||||||
|
res: resources,
|
||||||
|
ch: chains,
|
||||||
|
apiBase: "https://api.yup.io",
|
||||||
|
};
|
||||||
|
|
||||||
|
const getProfileWallet = async ({
|
||||||
|
address,
|
||||||
|
start,
|
||||||
|
limit,
|
||||||
|
res,
|
||||||
|
ch,
|
||||||
|
apiBase,
|
||||||
|
}: {
|
||||||
|
address: string;
|
||||||
|
start: number;
|
||||||
|
limit: number;
|
||||||
|
res: string[];
|
||||||
|
ch: string[];
|
||||||
|
apiBase: string;
|
||||||
|
}) => {
|
||||||
|
const r = {
|
||||||
|
poaps: [] as IProfilePOAP[],
|
||||||
|
ethNfts: [] as IProfileNFT[],
|
||||||
|
polyNfts: [] as IProfileNFT[],
|
||||||
|
ethTokens: [] as IProfileToken[],
|
||||||
|
polyTokens: [] as IProfileToken[],
|
||||||
|
};
|
||||||
|
try {
|
||||||
|
const promises = [];
|
||||||
|
|
||||||
|
if (res.includes("poaps")) {
|
||||||
|
promises.push(
|
||||||
|
fetchFromWallet({
|
||||||
|
apiBase,
|
||||||
|
address,
|
||||||
|
start,
|
||||||
|
limit,
|
||||||
|
resource: "poaps",
|
||||||
|
chain: "ethereum",
|
||||||
|
}).then((rz) => {
|
||||||
|
r.poaps = rz ?? [];
|
||||||
})
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (res.includes("nfts")) {
|
||||||
|
if (ch.includes("ethereum")) {
|
||||||
|
promises.push(
|
||||||
|
fetchFromWallet({
|
||||||
|
apiBase,
|
||||||
|
address,
|
||||||
|
start,
|
||||||
|
limit,
|
||||||
|
resource: "nfts",
|
||||||
|
chain: "ethereum",
|
||||||
|
}).then((rz) => {
|
||||||
|
r.ethNfts = rz ?? [];
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (ch.includes("polygon")) {
|
||||||
|
promises.push(
|
||||||
|
fetchFromWallet({
|
||||||
|
apiBase,
|
||||||
|
address,
|
||||||
|
start,
|
||||||
|
limit,
|
||||||
|
resource: "nfts",
|
||||||
|
chain: "polygon",
|
||||||
|
}).then((rz) => {
|
||||||
|
r.polyNfts = rz ?? [];
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (res.includes("tokens")) {
|
||||||
|
if (ch.includes("ethereum")) {
|
||||||
|
promises.push(
|
||||||
|
fetchFromWallet({
|
||||||
|
apiBase,
|
||||||
|
address,
|
||||||
|
start,
|
||||||
|
limit,
|
||||||
|
resource: "tokens",
|
||||||
|
chain: "ethereum",
|
||||||
|
}).then((rz) => {
|
||||||
|
r.ethTokens = rz ?? [];
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (ch.includes("polygon")) {
|
||||||
|
promises.push(
|
||||||
|
fetchFromWallet({
|
||||||
|
apiBase,
|
||||||
|
address,
|
||||||
|
start,
|
||||||
|
limit,
|
||||||
|
resource: "tokens",
|
||||||
|
chain: "polygon",
|
||||||
|
}).then((rz) => {
|
||||||
|
r.polyTokens = rz ?? [];
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const loadMore = (type: string) => {
|
await Promise.all(promises);
|
||||||
|
return r;
|
||||||
|
} catch {
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
onIonViewWillEnter(async () => {
|
||||||
|
selectedAccount.value = await getSelectedAccount();
|
||||||
|
walletLoadArgs.address = selectedAccount.value.address;
|
||||||
|
const r = await getProfileWallet(walletLoadArgs);
|
||||||
|
ethNfts.value = r.ethNfts.slice(0, 10);
|
||||||
|
if (r.ethNfts.length !== walletLoadArgs.limit) {
|
||||||
|
hasMore.ethNfts = false;
|
||||||
|
}
|
||||||
|
polyNfts.value = r.polyNfts.slice(0, 10);
|
||||||
|
if (r.polyNfts.length !== walletLoadArgs.limit) {
|
||||||
|
hasMore.polyNfts = false;
|
||||||
|
}
|
||||||
|
ethTokens.value = r.ethTokens.slice(0, 10);
|
||||||
|
if (r.ethTokens.length !== walletLoadArgs.limit) {
|
||||||
|
hasMore.ethTokens = false;
|
||||||
|
}
|
||||||
|
polyTokens.value = r.polyTokens.slice(0, 10);
|
||||||
|
if (r.polyTokens.length !== walletLoadArgs.limit) {
|
||||||
|
hasMore.polyTokens = false;
|
||||||
|
}
|
||||||
|
poaps.value = r.poaps.slice(0, -1);
|
||||||
|
if (r.poaps.length !== walletLoadArgs.limit) {
|
||||||
|
hasMore.poaps = false;
|
||||||
|
}
|
||||||
|
noAssets.value =
|
||||||
|
poaps.value.length &&
|
||||||
|
ethNfts.value.length &&
|
||||||
|
polyNfts.value.length &&
|
||||||
|
ethTokens.value.length &&
|
||||||
|
polyTokens.value.length
|
||||||
|
? false
|
||||||
|
: true;
|
||||||
|
loading.value = false;
|
||||||
|
|
||||||
|
// const req = await fetch(`${yupAssetsApi}/${selectedAccount.value.address}`);
|
||||||
|
// if (req.ok) {
|
||||||
|
// assets.value = (await req.json()) ?? {};
|
||||||
|
// if (
|
||||||
|
// !("poaps" in assets.value) &&
|
||||||
|
// !("tokens" in assets.value) &&
|
||||||
|
// !("nfts" in assets.value)
|
||||||
|
// ) {
|
||||||
|
// noAssets.value = true;
|
||||||
|
// }
|
||||||
|
// if ("poaps" in assets.value) {
|
||||||
|
// poaps.value = assets.value?.poaps?.slice(0, 10) ?? [];
|
||||||
|
// if (poaps.value.length >= (assets.value?.poaps?.length ?? 0)) {
|
||||||
|
// hasMore.poaps = false;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// if ("nfts" in assets.value) {
|
||||||
|
// ethNfts.value =
|
||||||
|
// assets.value?.nfts?.filter((n) => n.chain === "ethereum").slice(0, 10) ?? [];
|
||||||
|
// if (
|
||||||
|
// ethNfts.value.length >=
|
||||||
|
// (assets.value?.nfts?.filter((n) => n.chain === "ethereum").length ?? 0)
|
||||||
|
// ) {
|
||||||
|
// hasMore.ethNfts = false;
|
||||||
|
// }
|
||||||
|
// polyNfts.value =
|
||||||
|
// assets.value?.nfts?.filter((n) => n.chain === "polygon").slice(0, 10) ?? [];
|
||||||
|
// if (
|
||||||
|
// polyNfts.value.length >=
|
||||||
|
// (assets.value?.nfts?.filter((n) => n.chain === "polygon").length ?? 0)
|
||||||
|
// ) {
|
||||||
|
// hasMore.polyNfts = false;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// if ("tokens" in assets.value) {
|
||||||
|
// ethTokens.value =
|
||||||
|
// assets.value?.tokens?.filter((n) => n.chain === "ethereum").slice(0, 10) ??
|
||||||
|
// [];
|
||||||
|
// if (
|
||||||
|
// ethTokens.value.length >=
|
||||||
|
// (assets.value?.tokens?.filter((n) => n.chain === "ethereum").length ?? 0)
|
||||||
|
// ) {
|
||||||
|
// hasMore.ethTokens = false;
|
||||||
|
// }
|
||||||
|
// polyTokens.value =
|
||||||
|
// assets.value?.tokens?.filter((n) => n.chain === "polygon").slice(0, 10) ?? [];
|
||||||
|
// if (
|
||||||
|
// polyTokens.value.length >=
|
||||||
|
// (assets.value?.tokens?.filter((n) => n.chain === "polygon").length ?? 0)
|
||||||
|
// ) {
|
||||||
|
// hasMore.polyTokens = false;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// } else {
|
||||||
|
// isError.value = true;
|
||||||
|
// }
|
||||||
|
// loading.value = false;
|
||||||
|
});
|
||||||
|
|
||||||
|
const loadMore = async (type: string) => {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case 'ethTokens': {
|
case "ethTokens": {
|
||||||
ethTokens.value = assets.value?.tokens?.filter(n => n.chain === 'ethereum').slice(0, ethTokens.value.length + 10) ?? []
|
walletLoadArgs.start = ethTokens.value.length;
|
||||||
if(ethTokens.value.length >= (assets.value?.tokens?.filter(n => n.chain === 'ethereum').length ?? 0)) {
|
walletLoadArgs.res = ["tokens"];
|
||||||
hasMore.ethTokens = false
|
walletLoadArgs.ch = ["ethereum"];
|
||||||
|
const r = await getProfileWallet(walletLoadArgs);
|
||||||
|
if (r.ethTokens.length !== walletLoadArgs.limit) {
|
||||||
|
hasMore.ethTokens = false;
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
break
|
ethTokens.value = [...ethTokens.value, ...r.ethTokens.slice(0, 10)];
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
case 'polyTokens': {
|
case "polyTokens": {
|
||||||
polyTokens.value = assets.value?.tokens?.filter(n => n.chain === 'polygon').slice(0, polyTokens.value.length + 10) ?? []
|
walletLoadArgs.start = polyTokens.value.length;
|
||||||
if(polyTokens.value.length >= (assets.value?.tokens?.filter(n => n.chain === 'polygon').length ?? 0)) {
|
walletLoadArgs.res = ["tokens"];
|
||||||
hasMore.polyTokens = false
|
walletLoadArgs.ch = ["polygon"];
|
||||||
|
const r = await getProfileWallet(walletLoadArgs);
|
||||||
|
if (r.polyTokens.length !== walletLoadArgs.limit) {
|
||||||
|
hasMore.polyTokens = false;
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
break
|
polyTokens.value = [...polyTokens.value, ...r.polyTokens.slice(0, 10)];
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
case 'ethNfts': {
|
case "ethNfts": {
|
||||||
ethNfts.value = assets.value?.nfts?.filter(n => n.chain === 'ethereum').slice(0, ethNfts.value.length + 10) ?? []
|
walletLoadArgs.start = ethNfts.value.length;
|
||||||
if(ethNfts.value.length >= (assets.value?.nfts?.filter(n => n.chain === 'ethereum').length ?? 0)) {
|
walletLoadArgs.res = ["nfts"];
|
||||||
hasMore.ethNfts = false
|
walletLoadArgs.ch = ["ethereum"];
|
||||||
|
const r = await getProfileWallet(walletLoadArgs);
|
||||||
|
if (r.ethNfts.length !== walletLoadArgs.limit) {
|
||||||
|
hasMore.ethNfts = false;
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
break
|
ethNfts.value = [...ethNfts.value, ...r.ethNfts.slice(0, 10)];
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
case 'polyNfts': {
|
case "polyNfts": {
|
||||||
polyNfts.value = assets.value?.nfts?.filter(n => n.chain === 'polygon').slice(0, polyNfts.value.length + 10) ?? []
|
walletLoadArgs.start = polyNfts.value.length;
|
||||||
if(polyNfts.value.length >= (assets.value?.nfts?.filter(n => n.chain === 'polygon').length ?? 0)) {
|
walletLoadArgs.res = ["nfts"];
|
||||||
hasMore.polyNfts = false
|
walletLoadArgs.ch = ["polygon"];
|
||||||
|
const r = await getProfileWallet(walletLoadArgs);
|
||||||
|
if (r.polyNfts.length !== walletLoadArgs.limit) {
|
||||||
|
hasMore.polyNfts = false;
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
break
|
polyNfts.value = [...polyNfts.value, ...r.polyNfts.slice(0, 10)];
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
case 'poaps': {
|
case "poaps": {
|
||||||
poaps.value = assets.value?.poaps?.slice(0, poaps.value.length + 10) ?? []
|
walletLoadArgs.start = poaps.value.length;
|
||||||
if(poaps.value.length >= (assets.value?.poaps?.length ?? 0)) {
|
walletLoadArgs.res = ["poaps"];
|
||||||
hasMore.poaps = false
|
walletLoadArgs.ch = ["ethereum"];
|
||||||
|
const r = await getProfileWallet(walletLoadArgs);
|
||||||
|
if (r.poaps.length !== walletLoadArgs.limit) {
|
||||||
|
hasMore.poaps = false;
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
break
|
poaps.value = [...poaps.value, ...r.poaps.slice(0, 10)];
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
selectedAccount,
|
selectedAccount,
|
||||||
loading,
|
loading,
|
||||||
isError,
|
isError,
|
||||||
noAssets,
|
noAssets,
|
||||||
assets,
|
|
||||||
getToastRef,
|
getToastRef,
|
||||||
copyAddress,
|
copyAddress,
|
||||||
copyOutline,
|
copyOutline,
|
||||||
|
@ -326,9 +568,8 @@ export default defineComponent({
|
||||||
polyNfts,
|
polyNfts,
|
||||||
loadMore,
|
loadMore,
|
||||||
toastState,
|
toastState,
|
||||||
getUrl
|
getUrl,
|
||||||
}
|
};
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -13,11 +13,18 @@
|
||||||
</ion-item>
|
</ion-item>
|
||||||
<div class="ion-padding" slot="content">
|
<div class="ion-padding" slot="content">
|
||||||
<ion-list>
|
<ion-list>
|
||||||
<ion-item v-if="noAccounts">You need at least one account to touch this settings</ion-item>
|
<ion-item v-if="noAccounts"
|
||||||
|
>You need at least one account to touch this settings</ion-item
|
||||||
|
>
|
||||||
<ion-list :disabled="noAccounts">
|
<ion-list :disabled="noAccounts">
|
||||||
<ion-item>
|
<ion-item>
|
||||||
<ion-label>Enable Storage Encryption</ion-label>
|
<ion-label>Enable Storage Encryption</ion-label>
|
||||||
<ion-toggle :key="updateKey" @ion-change="changeEncryption" slot="end" :checked="settings.s.enableStorageEnctyption"></ion-toggle>
|
<ion-toggle
|
||||||
|
:key="updateKey"
|
||||||
|
@ion-change="changeEncryption"
|
||||||
|
slot="end"
|
||||||
|
:checked="settings.s.enableStorageEnctyption"
|
||||||
|
></ion-toggle>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
<ion-item>
|
<ion-item>
|
||||||
This will require to input an encrypto key when storage is locked.
|
This will require to input an encrypto key when storage is locked.
|
||||||
|
@ -25,25 +32,54 @@
|
||||||
</ion-list>
|
</ion-list>
|
||||||
<ion-item :disabled="!settings.s.enableStorageEnctyption">
|
<ion-item :disabled="!settings.s.enableStorageEnctyption">
|
||||||
<ion-label>Enable Auto Lock</ion-label>
|
<ion-label>Enable Auto Lock</ion-label>
|
||||||
<ion-toggle :key="updateKey" @ion-change="changeAutoLock" slot="end" :checked="settings.s.lockOutEnabled"></ion-toggle>
|
<ion-toggle
|
||||||
|
:key="updateKey"
|
||||||
|
@ion-change="changeAutoLock"
|
||||||
|
slot="end"
|
||||||
|
:checked="settings.s.lockOutEnabled"
|
||||||
|
></ion-toggle>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
<ion-list>
|
<ion-list>
|
||||||
<ion-item :disabled="!settings.s.enableStorageEnctyption || !settings.s.lockOutEnabled">
|
<ion-item
|
||||||
|
:disabled="
|
||||||
|
!settings.s.enableStorageEnctyption || !settings.s.lockOutEnabled
|
||||||
|
"
|
||||||
|
>
|
||||||
<ion-label>Auto-lock Period: (2-120) minutes</ion-label>
|
<ion-label>Auto-lock Period: (2-120) minutes</ion-label>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
<ion-item :disabled="!settings.s.enableStorageEnctyption || !settings.s.lockOutEnabled">
|
<ion-item
|
||||||
<ion-input :key="updateKey" v-model="settings.s.lockOutPeriod" type="number"></ion-input>
|
:disabled="
|
||||||
|
!settings.s.enableStorageEnctyption || !settings.s.lockOutEnabled
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<ion-input
|
||||||
|
:key="updateKey"
|
||||||
|
v-model="settings.s.lockOutPeriod"
|
||||||
|
type="number"
|
||||||
|
></ion-input>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
<ion-item :disabled="!settings.s.enableStorageEnctyption || !settings.s.lockOutEnabled">
|
<ion-item
|
||||||
|
:disabled="
|
||||||
|
!settings.s.enableStorageEnctyption || !settings.s.lockOutEnabled
|
||||||
|
"
|
||||||
|
>
|
||||||
<ion-button @click="setTime">Set Auto-lock</ion-button>
|
<ion-button @click="setTime">Set Auto-lock</ion-button>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
</ion-list>
|
</ion-list>
|
||||||
<ion-list>
|
<ion-list>
|
||||||
<ion-item>
|
<ion-item>
|
||||||
<ion-label>Permanent Lock</ion-label>
|
<ion-label>Permanent Lock</ion-label>
|
||||||
<ion-toggle @ion-change="changePermaLock" :key="updateKey" slot="end" :disabled="!settings.s.enableStorageEnctyption" :checked="settings.s.encryptAfterEveryTx"></ion-toggle>
|
<ion-toggle
|
||||||
|
@ion-change="changePermaLock"
|
||||||
|
:key="updateKey"
|
||||||
|
slot="end"
|
||||||
|
:disabled="!settings.s.enableStorageEnctyption"
|
||||||
|
:checked="settings.s.encryptAfterEveryTx"
|
||||||
|
></ion-toggle>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
<ion-item>Will require decrypt pass before any sign or transaction</ion-item>
|
<ion-item
|
||||||
|
>Will require decrypt pass before any sign or transaction</ion-item
|
||||||
|
>
|
||||||
</ion-list>
|
</ion-list>
|
||||||
</ion-list>
|
</ion-list>
|
||||||
</div>
|
</div>
|
||||||
|
@ -56,27 +92,15 @@
|
||||||
<ion-list>
|
<ion-list>
|
||||||
<ion-radio-group :value="radioTheme">
|
<ion-radio-group :value="radioTheme">
|
||||||
<ion-item>
|
<ion-item>
|
||||||
<ion-radio
|
<ion-radio slot="start" value="system" @click="changeTheme('system')" />
|
||||||
slot="start"
|
|
||||||
value="system"
|
|
||||||
@click="changeTheme('system')"
|
|
||||||
/>
|
|
||||||
<ion-label>System Default</ion-label>
|
<ion-label>System Default</ion-label>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
<ion-item>
|
<ion-item>
|
||||||
<ion-radio
|
<ion-radio slot="start" value="dark" @click="changeTheme('dark')" />
|
||||||
slot="start"
|
|
||||||
value="dark"
|
|
||||||
@click="changeTheme('dark')"
|
|
||||||
/>
|
|
||||||
<ion-label>Dark</ion-label>
|
<ion-label>Dark</ion-label>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
<ion-item>
|
<ion-item>
|
||||||
<ion-radio
|
<ion-radio slot="start" value="light" @click="changeTheme('light')" />
|
||||||
slot="start"
|
|
||||||
value="light"
|
|
||||||
@click="changeTheme('light')"
|
|
||||||
/>
|
|
||||||
<ion-label>Light</ion-label>
|
<ion-label>Light</ion-label>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
</ion-radio-group>
|
</ion-radio-group>
|
||||||
|
@ -88,16 +112,43 @@
|
||||||
<ion-label>About</ion-label>
|
<ion-label>About</ion-label>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
<div class="ion-padding" slot="content">
|
<div class="ion-padding" slot="content">
|
||||||
<p>Clear EVM Wallet (CLW) is a fully open-source wallet built with Vue, Ionic, and Ethers.</p>
|
<p>
|
||||||
<p>It emulates Metamask Wallet and can be used as a drop-in replacement, right now if you have both extensions, CLW will overwrite Metamask.</p>
|
Clear EVM Wallet (CLW) is a fully open-source wallet built with Vue, Ionic,
|
||||||
<p>Main philosophy of the wallet is: no trackers, full control, export/import JSONs with accounts, fast generate new accounts, and wipe everything with one click.</p>
|
and Ethers.
|
||||||
<p>Github Repo: <a href="#" @click="openTab('https://github.com/andrei0x309/clear-wallet')">LINK</a></p>
|
</p>
|
||||||
|
<p>
|
||||||
|
It emulates Metamask Wallet and can be used as a drop-in replacement, right
|
||||||
|
now if you have both extensions, CLW will overwrite Metamask.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Main philosophy of the wallet is: no trackers, full control, export/import
|
||||||
|
JSONs with accounts, fast generate new accounts, and wipe everything with
|
||||||
|
one click.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Github Repo:
|
||||||
|
<a href="#" @click="openTab('https://github.com/andrei0x309/clear-wallet')"
|
||||||
|
>LINK</a
|
||||||
|
>
|
||||||
|
</p>
|
||||||
<br />
|
<br />
|
||||||
<p style="margin-bottom: 0.2rem">Some Web3 Projects I personally appreciate:</p>
|
<p style="margin-bottom: 0.2rem">Places you can check me out:</p>
|
||||||
<p>YUP - web3 social platform <a href="#" @click="openTab('https://app.yup.io')">LINK</a></p>
|
<p>
|
||||||
<p>Crypto-Leftists: web3 left-wing crypto community <a href="#" @click="openTab('https://discord.gg/gzA4bTCdhb')">LINK</a></p>
|
Github andrei0x309 -
|
||||||
<p>Idena: web3 fully private identity provider blockchain <a href="#" @click="openTab('https://www.idena.io/')">LINK</a></p>
|
<a href="#" @click="openTab('https://github.com/andrei0x309')">LINK</a>
|
||||||
<p>Mirror: web3 publishing platform <a href="#" @click="openTab('https://mirror.xyz')">LINK</a></p>
|
</p>
|
||||||
|
<p>
|
||||||
|
Mirror Profile
|
||||||
|
<a href="#" @click="openTab('https://mirror.xyz/andrei0x309.eth')">LINK</a>
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Blog Flashsoft
|
||||||
|
<a href="#" @click="openTab('https://blog.flashsoft.eu')">LINK</a>
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Crypto-Leftists Discord
|
||||||
|
<a href="#" @click="openTab('https://discord.gg/gzA4bTCdhb')">LINK</a>
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</ion-accordion>
|
</ion-accordion>
|
||||||
<ion-accordion value="4">
|
<ion-accordion value="4">
|
||||||
|
@ -144,14 +195,29 @@
|
||||||
</ion-loading>
|
</ion-loading>
|
||||||
<ion-modal
|
<ion-modal
|
||||||
:is-open="mpModal"
|
:is-open="mpModal"
|
||||||
@did-dismiss="mpModal=false;modalDismiss()"
|
@did-dismiss="
|
||||||
|
mpModal = false;
|
||||||
|
modalDismiss();
|
||||||
|
"
|
||||||
>
|
>
|
||||||
<ion-header>
|
<ion-header>
|
||||||
<ion-toolbar>
|
<ion-toolbar>
|
||||||
<ion-buttons slot="start">
|
<ion-buttons slot="start">
|
||||||
<ion-button @click="modalGetPassword?.reject ? (() => { modalGetPassword.reject(); modalGetPassword = null })() : mpModal=false">Close</ion-button>
|
<ion-button
|
||||||
|
@click="
|
||||||
|
modalGetPassword?.reject
|
||||||
|
? (() => {
|
||||||
|
modalGetPassword.reject();
|
||||||
|
modalGetPassword = null;
|
||||||
|
})()
|
||||||
|
: (mpModal = false)
|
||||||
|
"
|
||||||
|
>Close</ion-button
|
||||||
|
>
|
||||||
</ion-buttons>
|
</ion-buttons>
|
||||||
<ion-title v-if="!settings.s.enableStorageEnctyption">Create Encryption Password</ion-title>
|
<ion-title v-if="!settings.s.enableStorageEnctyption"
|
||||||
|
>Create Encryption Password</ion-title
|
||||||
|
>
|
||||||
<ion-title v-else>Enter Encryption Password</ion-title>
|
<ion-title v-else>Enter Encryption Password</ion-title>
|
||||||
</ion-toolbar>
|
</ion-toolbar>
|
||||||
</ion-header>
|
</ion-header>
|
||||||
|
@ -159,7 +225,8 @@
|
||||||
<ion-list v-if="settings.s.enableStorageEnctyption">
|
<ion-list v-if="settings.s.enableStorageEnctyption">
|
||||||
<ion-item>
|
<ion-item>
|
||||||
<ion-label>Old Password</ion-label>
|
<ion-label>Old Password</ion-label>
|
||||||
</ion-item> <ion-item>
|
</ion-item>
|
||||||
|
<ion-item>
|
||||||
<ion-input v-model="mpPass" type="password"></ion-input>
|
<ion-input v-model="mpPass" type="password"></ion-input>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
</ion-list>
|
</ion-list>
|
||||||
|
@ -167,20 +234,32 @@
|
||||||
<ion-list>
|
<ion-list>
|
||||||
<ion-item>
|
<ion-item>
|
||||||
<ion-label>New Password</ion-label>
|
<ion-label>New Password</ion-label>
|
||||||
</ion-item> <ion-item>
|
</ion-item>
|
||||||
|
<ion-item>
|
||||||
<ion-input v-model="mpPass" type="password"></ion-input>
|
<ion-input v-model="mpPass" type="password"></ion-input>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
</ion-list>
|
</ion-list>
|
||||||
<ion-list>
|
<ion-list>
|
||||||
<ion-item>
|
<ion-item>
|
||||||
<ion-label>Confirm</ion-label>
|
<ion-label>Confirm</ion-label>
|
||||||
</ion-item> <ion-item>
|
</ion-item>
|
||||||
|
<ion-item>
|
||||||
<ion-input v-model="mpConfirm" type="password"></ion-input>
|
<ion-input v-model="mpConfirm" type="password"></ion-input>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
</ion-list>
|
</ion-list>
|
||||||
</div>
|
</div>
|
||||||
<ion-item>
|
<ion-item>
|
||||||
<ion-button @click="modalGetPassword?.resolve ? (() => { modalGetPassword.resolve(); modalGetPassword = null })() : confirmModal()">Confirm</ion-button>
|
<ion-button
|
||||||
|
@click="
|
||||||
|
modalGetPassword?.resolve
|
||||||
|
? (() => {
|
||||||
|
modalGetPassword.resolve();
|
||||||
|
modalGetPassword = null;
|
||||||
|
})()
|
||||||
|
: confirmModal()
|
||||||
|
"
|
||||||
|
>Confirm</ion-button
|
||||||
|
>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
</ion-content>
|
</ion-content>
|
||||||
</ion-modal>
|
</ion-modal>
|
||||||
|
@ -197,11 +276,19 @@
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent, ref, reactive, Ref } from "vue";
|
import { defineComponent, ref, reactive, Ref } from "vue";
|
||||||
import { storageWipe, getSettings, setSettings, getAccounts, saveSelectedAccount, replaceAccounts, openTab } from "@/utils/platform";
|
import {
|
||||||
import { decrypt, encrypt, getCryptoParams } from "@/utils/webCrypto"
|
storageWipe,
|
||||||
import { Account } from '@/extension/types'
|
getSettings,
|
||||||
import { exportFile } from '@/utils/misc'
|
setSettings,
|
||||||
import type { Settings } from "@/extension/types"
|
getAccounts,
|
||||||
|
saveSelectedAccount,
|
||||||
|
replaceAccounts,
|
||||||
|
openTab,
|
||||||
|
} from "@/utils/platform";
|
||||||
|
import { decrypt, encrypt, getCryptoParams } from "@/utils/webCrypto";
|
||||||
|
import { Account } from "@/extension/types";
|
||||||
|
import { exportFile } from "@/utils/misc";
|
||||||
|
import type { Settings } from "@/extension/types";
|
||||||
import {
|
import {
|
||||||
IonContent,
|
IonContent,
|
||||||
IonHeader,
|
IonHeader,
|
||||||
|
@ -223,7 +310,7 @@ import {
|
||||||
IonRadio,
|
IonRadio,
|
||||||
IonButtons,
|
IonButtons,
|
||||||
IonAlert,
|
IonAlert,
|
||||||
IonToast
|
IonToast,
|
||||||
} from "@ionic/vue";
|
} from "@ionic/vue";
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
|
@ -247,25 +334,28 @@ export default defineComponent({
|
||||||
IonRadio,
|
IonRadio,
|
||||||
IonButtons,
|
IonButtons,
|
||||||
IonAlert,
|
IonAlert,
|
||||||
IonToast
|
IonToast,
|
||||||
},
|
},
|
||||||
setup() {
|
setup() {
|
||||||
const loading = ref(true);
|
const loading = ref(true);
|
||||||
const mpModal = ref(false);
|
const mpModal = ref(false);
|
||||||
const mpPass = ref('');
|
const mpPass = ref("");
|
||||||
const mpConfirm = ref('');
|
const mpConfirm = ref("");
|
||||||
const updateKey = ref(0);
|
const updateKey = ref(0);
|
||||||
const alertOpen = ref(false);
|
const alertOpen = ref(false);
|
||||||
const alertMsg = ref('');
|
const alertMsg = ref("");
|
||||||
const toastState = ref(false);
|
const toastState = ref(false);
|
||||||
const toastMsg = ref('');
|
const toastMsg = ref("");
|
||||||
const alertHeader = ref('Error')
|
const alertHeader = ref("Error");
|
||||||
const importFile = ref(null) as unknown as Ref<HTMLInputElement>
|
const importFile = (ref(null) as unknown) as Ref<HTMLInputElement>;
|
||||||
type ModalPromisePassword = null | { resolve: ((p?: unknown) => void), reject: ((p?: unknown) => void)}
|
type ModalPromisePassword = null | {
|
||||||
const modalGetPassword = ref(null) as Ref<ModalPromisePassword>
|
resolve: (p?: unknown) => void;
|
||||||
const noAccounts = ref(true)
|
reject: (p?: unknown) => void;
|
||||||
const defaultAccordionOpen = ref("0")
|
};
|
||||||
const radioTheme = ref('system') as Ref<'system' | 'light' | 'dark'>
|
const modalGetPassword = ref(null) as Ref<ModalPromisePassword>;
|
||||||
|
const noAccounts = ref(true);
|
||||||
|
const defaultAccordionOpen = ref("0");
|
||||||
|
const radioTheme = ref("system") as Ref<"system" | "light" | "dark">;
|
||||||
|
|
||||||
const wipeStorage = async () => {
|
const wipeStorage = async () => {
|
||||||
loading.value = true;
|
loading.value = true;
|
||||||
|
@ -273,280 +363,296 @@ export default defineComponent({
|
||||||
loading.value = false;
|
loading.value = false;
|
||||||
};
|
};
|
||||||
const settings = reactive({
|
const settings = reactive({
|
||||||
s: null as unknown as Settings
|
s: (null as unknown) as Settings,
|
||||||
}) as { s: Settings}
|
}) as { s: Settings };
|
||||||
|
|
||||||
const saveSettings = async () => {
|
const saveSettings = async () => {
|
||||||
loading.value = true
|
loading.value = true;
|
||||||
await setSettings(settings.s)
|
await setSettings(settings.s);
|
||||||
loading.value = false
|
loading.value = false;
|
||||||
}
|
};
|
||||||
|
|
||||||
const setEncryptToggle = (state: boolean) => {
|
const setEncryptToggle = (state: boolean) => {
|
||||||
settings.s.enableStorageEnctyption = state
|
settings.s.enableStorageEnctyption = state;
|
||||||
updateKey.value++
|
updateKey.value++;
|
||||||
defaultAccordionOpen.value = "1"
|
defaultAccordionOpen.value = "1";
|
||||||
}
|
};
|
||||||
|
|
||||||
const changeAutoLock = async () => {
|
const changeAutoLock = async () => {
|
||||||
settings.s.lockOutEnabled = !settings.s.lockOutEnabled
|
settings.s.lockOutEnabled = !settings.s.lockOutEnabled;
|
||||||
updateKey.value++
|
updateKey.value++;
|
||||||
await saveSettings()
|
await saveSettings();
|
||||||
defaultAccordionOpen.value = "1"
|
defaultAccordionOpen.value = "1";
|
||||||
}
|
};
|
||||||
|
|
||||||
const changePermaLock = async () => {
|
const changePermaLock = async () => {
|
||||||
settings.s.encryptAfterEveryTx = !settings.s.encryptAfterEveryTx
|
settings.s.encryptAfterEveryTx = !settings.s.encryptAfterEveryTx;
|
||||||
updateKey.value++
|
updateKey.value++;
|
||||||
await saveSettings()
|
await saveSettings();
|
||||||
defaultAccordionOpen.value = "1"
|
defaultAccordionOpen.value = "1";
|
||||||
}
|
};
|
||||||
|
|
||||||
const changeTheme = async (theme: 'system' | 'light' | 'dark') => {
|
const changeTheme = async (theme: "system" | "light" | "dark") => {
|
||||||
document.body.classList.remove(radioTheme.value)
|
document.body.classList.remove(radioTheme.value);
|
||||||
document.body.classList.add(theme)
|
document.body.classList.add(theme);
|
||||||
radioTheme.value = theme
|
radioTheme.value = theme;
|
||||||
settings.s.theme = theme
|
settings.s.theme = theme;
|
||||||
await saveSettings()
|
await saveSettings();
|
||||||
defaultAccordionOpen.value = "2"
|
defaultAccordionOpen.value = "2";
|
||||||
}
|
};
|
||||||
|
|
||||||
const changeEncryption = async () => {
|
const changeEncryption = async () => {
|
||||||
loading.value = true
|
loading.value = true;
|
||||||
mpModal.value = true
|
mpModal.value = true;
|
||||||
loading.value = false
|
loading.value = false;
|
||||||
}
|
};
|
||||||
|
|
||||||
const confirmModal = async () => {
|
const confirmModal = async () => {
|
||||||
loading.value = true
|
loading.value = true;
|
||||||
if (mpPass.value.length < 3) {
|
if (mpPass.value.length < 3) {
|
||||||
loading.value = false
|
loading.value = false;
|
||||||
alertHeader.value = 'Error'
|
alertHeader.value = "Error";
|
||||||
alertMsg.value = 'Password is too short. More than 3 characters are required.';
|
alertMsg.value = "Password is too short. More than 3 characters are required.";
|
||||||
alertOpen.value = true
|
alertOpen.value = true;
|
||||||
setEncryptToggle(settings.s.enableStorageEnctyption)
|
setEncryptToggle(settings.s.enableStorageEnctyption);
|
||||||
return
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!settings.s.enableStorageEnctyption) {
|
if (!settings.s.enableStorageEnctyption) {
|
||||||
if (mpPass.value !== mpConfirm.value) {
|
if (mpPass.value !== mpConfirm.value) {
|
||||||
loading.value = false
|
loading.value = false;
|
||||||
alertHeader.value = 'Error'
|
alertHeader.value = "Error";
|
||||||
alertMsg.value = 'Password and confirm password do not match';
|
alertMsg.value = "Password and confirm password do not match";
|
||||||
alertOpen.value = true
|
alertOpen.value = true;
|
||||||
setEncryptToggle(settings.s.enableStorageEnctyption)
|
setEncryptToggle(settings.s.enableStorageEnctyption);
|
||||||
return
|
return;
|
||||||
}
|
}
|
||||||
let accounts = await getAccounts()
|
let accounts = await getAccounts();
|
||||||
const cryptoParams = await getCryptoParams(mpPass.value)
|
const cryptoParams = await getCryptoParams(mpPass.value);
|
||||||
const accProm = accounts.map(async a => {
|
const accProm = accounts.map(async (a) => {
|
||||||
a.encPk = await encrypt(a.pk, cryptoParams)
|
a.encPk = await encrypt(a.pk, cryptoParams);
|
||||||
a.pk = ''
|
a.pk = "";
|
||||||
return a
|
return a;
|
||||||
})
|
});
|
||||||
accounts = await Promise.all(accProm)
|
accounts = await Promise.all(accProm);
|
||||||
await replaceAccounts(accounts)
|
await replaceAccounts(accounts);
|
||||||
await saveSelectedAccount(accounts[0])
|
await saveSelectedAccount(accounts[0]);
|
||||||
setEncryptToggle(true)
|
setEncryptToggle(true);
|
||||||
await setSettings(settings.s)
|
await setSettings(settings.s);
|
||||||
mpPass.value = ''
|
mpPass.value = "";
|
||||||
mpConfirm.value = ''
|
mpConfirm.value = "";
|
||||||
mpModal.value = false
|
mpModal.value = false;
|
||||||
} else {
|
} else {
|
||||||
try {
|
try {
|
||||||
let accounts = await getAccounts()
|
let accounts = await getAccounts();
|
||||||
const cryptoParams = await getCryptoParams(mpPass.value)
|
const cryptoParams = await getCryptoParams(mpPass.value);
|
||||||
const accProm = accounts.map(async a => {
|
const accProm = accounts.map(async (a) => {
|
||||||
if (a.encPk) {
|
if (a.encPk) {
|
||||||
a.pk = await decrypt(a.encPk, cryptoParams)
|
a.pk = await decrypt(a.encPk, cryptoParams);
|
||||||
}
|
}
|
||||||
return a
|
return a;
|
||||||
})
|
});
|
||||||
accProm.forEach( a => a.catch(e => console.log(e)) )
|
accProm.forEach((a) => a.catch((e) => console.log(e)));
|
||||||
accounts = await Promise.all(accProm)
|
accounts = await Promise.all(accProm);
|
||||||
await replaceAccounts(accounts)
|
await replaceAccounts(accounts);
|
||||||
await saveSelectedAccount(accounts[0])
|
await saveSelectedAccount(accounts[0]);
|
||||||
setEncryptToggle(false)
|
setEncryptToggle(false);
|
||||||
settings.s.lockOutEnabled = false
|
settings.s.lockOutEnabled = false;
|
||||||
settings.s.encryptAfterEveryTx = false
|
settings.s.encryptAfterEveryTx = false;
|
||||||
await setSettings(settings.s)
|
await setSettings(settings.s);
|
||||||
mpPass.value = ''
|
mpPass.value = "";
|
||||||
mpConfirm.value = ''
|
mpConfirm.value = "";
|
||||||
mpModal.value = false
|
mpModal.value = false;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(error)
|
console.log(error);
|
||||||
loading.value = false
|
loading.value = false;
|
||||||
alertHeader.value = 'Error'
|
alertHeader.value = "Error";
|
||||||
alertMsg.value = 'Decryption failed, password is not correct.';
|
alertMsg.value = "Decryption failed, password is not correct.";
|
||||||
alertOpen.value = true
|
alertOpen.value = true;
|
||||||
setEncryptToggle(settings.s.enableStorageEnctyption)
|
setEncryptToggle(settings.s.enableStorageEnctyption);
|
||||||
return
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
loading.value = false
|
loading.value = false;
|
||||||
}
|
};
|
||||||
|
|
||||||
const validateFile = () => {
|
const validateFile = () => {
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
try {
|
try {
|
||||||
if (!importFile.value?.value?.length) {
|
if (!importFile.value?.value?.length) {
|
||||||
return resolve({
|
return resolve({
|
||||||
error: 'Import json file is missing'
|
error: "Import json file is missing",
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
const reader = new FileReader();
|
const reader = new FileReader();
|
||||||
reader.onload = (event) => {
|
reader.onload = (event) => {
|
||||||
const json = JSON.parse(event?.target?.result as string)
|
const json = JSON.parse(event?.target?.result as string);
|
||||||
if (!json.length) {
|
if (!json.length) {
|
||||||
return resolve({ error: 'JSON format is wrong. Corrrect JSON format is: [{ "name": "Account Name", "pk": "Private Key", "address": "0x..." },{...}]' })
|
return resolve({
|
||||||
|
error:
|
||||||
|
'JSON format is wrong. Corrrect JSON format is: [{ "name": "Account Name", "pk": "Private Key", "address": "0x..." },{...}]',
|
||||||
|
});
|
||||||
}
|
}
|
||||||
const test = json.some((e:any) => ( !('pk' in e ) || !('name' in e) || !('address' in e) || !(e.pk.length === 66 || e.pk.length === 64)))
|
const test = json.some(
|
||||||
|
(e: any) =>
|
||||||
|
!("pk" in e) ||
|
||||||
|
!("name" in e) ||
|
||||||
|
!("address" in e) ||
|
||||||
|
!(e.pk.length === 66 || e.pk.length === 64)
|
||||||
|
);
|
||||||
if (test) {
|
if (test) {
|
||||||
return resolve({ error: 'JSON format is wrong. Corrrect JSON format is: [{ "name": "Account Name", "pk": "Private Key", "address": "0x..." },{...}], Also PK must be valid (66 || 64 length) !' })
|
return resolve({
|
||||||
}
|
error:
|
||||||
return resolve({ error: false, json })
|
'JSON format is wrong. Corrrect JSON format is: [{ "name": "Account Name", "pk": "Private Key", "address": "0x..." },{...}], Also PK must be valid (66 || 64 length) !',
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
return resolve({ error: false, json });
|
||||||
|
};
|
||||||
reader.readAsText(importFile.value?.files?.[0] as File);
|
reader.readAsText(importFile.value?.files?.[0] as File);
|
||||||
|
|
||||||
} catch {
|
} catch {
|
||||||
return resolve(
|
return resolve({
|
||||||
{
|
error: "Parsing JSON file",
|
||||||
error: 'Parsing JSON file'
|
});
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
const getPassword = () => {
|
const getPassword = () => {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
modalGetPassword.value = { resolve, reject }
|
modalGetPassword.value = { resolve, reject };
|
||||||
mpModal.value = true
|
mpModal.value = true;
|
||||||
})
|
});
|
||||||
}
|
};
|
||||||
|
|
||||||
const promptForPassword = async (accounts: Account[]) => {
|
const promptForPassword = async (accounts: Account[]) => {
|
||||||
let isCorectPass = false
|
let isCorectPass = false;
|
||||||
do {
|
do {
|
||||||
try {
|
try {
|
||||||
await getPassword()
|
await getPassword();
|
||||||
modalGetPassword.value = null
|
modalGetPassword.value = null;
|
||||||
} catch {
|
} catch {
|
||||||
alertHeader.value = 'Error'
|
alertHeader.value = "Error";
|
||||||
alertMsg.value = "Password is required!"
|
alertMsg.value = "Password is required!";
|
||||||
alertOpen.value = true
|
alertOpen.value = true;
|
||||||
mpModal.value = false
|
mpModal.value = false;
|
||||||
return false
|
return false;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
const cryptoParams = await getCryptoParams(mpPass.value)
|
const cryptoParams = await getCryptoParams(mpPass.value);
|
||||||
if (accounts?.[0]?.encPk) {
|
if (accounts?.[0]?.encPk) {
|
||||||
await decrypt(accounts[0].encPk, cryptoParams)
|
await decrypt(accounts[0].encPk, cryptoParams);
|
||||||
}
|
}
|
||||||
isCorectPass = true
|
isCorectPass = true;
|
||||||
} catch {
|
} catch {
|
||||||
isCorectPass = false
|
isCorectPass = false;
|
||||||
alertHeader.value = 'Error'
|
alertHeader.value = "Error";
|
||||||
alertMsg.value = "Password is wrong!"
|
alertMsg.value = "Password is wrong!";
|
||||||
alertOpen.value = true
|
alertOpen.value = true;
|
||||||
}
|
}
|
||||||
} while (!isCorectPass);
|
} while (!isCorectPass);
|
||||||
return true
|
return true;
|
||||||
}
|
};
|
||||||
|
|
||||||
const importAcc = async () => {
|
const importAcc = async () => {
|
||||||
const validation = await validateFile() as { error: any }
|
const validation = (await validateFile()) as { error: any };
|
||||||
if (validation.error) {
|
if (validation.error) {
|
||||||
alertMsg.value = validation.error
|
alertMsg.value = validation.error;
|
||||||
alertOpen.value = true
|
alertOpen.value = true;
|
||||||
return
|
return;
|
||||||
}
|
}
|
||||||
const accounts = await getAccounts()
|
const accounts = await getAccounts();
|
||||||
const newAccounts = (validation as unknown as { json: Account[] }).json
|
const newAccounts = ((validation as unknown) as { json: Account[] }).json;
|
||||||
if (settings.s.enableStorageEnctyption) {
|
if (settings.s.enableStorageEnctyption) {
|
||||||
const hasPass = await promptForPassword(accounts)
|
const hasPass = await promptForPassword(accounts);
|
||||||
if (hasPass) {
|
if (hasPass) {
|
||||||
const cryptoParams = await getCryptoParams(mpPass.value)
|
const cryptoParams = await getCryptoParams(mpPass.value);
|
||||||
const accProm = newAccounts.map(async a => {
|
const accProm = newAccounts.map(async (a) => {
|
||||||
if (a.pk.length === 64) {
|
if (a.pk.length === 64) {
|
||||||
a.pk = `0x${a.pk}`
|
a.pk = `0x${a.pk}`;
|
||||||
}
|
}
|
||||||
a.encPk = await encrypt(a.pk, cryptoParams)
|
a.encPk = await encrypt(a.pk, cryptoParams);
|
||||||
return a
|
return a;
|
||||||
})
|
});
|
||||||
const encNewAccounts = await Promise.all(accProm)
|
const encNewAccounts = await Promise.all(accProm);
|
||||||
await replaceAccounts([...accounts, ...encNewAccounts])
|
await replaceAccounts([...accounts, ...encNewAccounts]);
|
||||||
alertHeader.value = 'Success'
|
alertHeader.value = "Success";
|
||||||
alertMsg.value = "Successfully imported new accounts."
|
alertMsg.value = "Successfully imported new accounts.";
|
||||||
alertOpen.value = true
|
alertOpen.value = true;
|
||||||
noAccounts.value = false
|
noAccounts.value = false;
|
||||||
}
|
}
|
||||||
return false
|
return false;
|
||||||
} else {
|
} else {
|
||||||
await replaceAccounts([...accounts, ...newAccounts.map( a => { a.encPk = ''; return a })])
|
await replaceAccounts([
|
||||||
alertHeader.value = 'Success'
|
...accounts,
|
||||||
alertMsg.value = "Successfully imported new accounts."
|
...newAccounts.map((a) => {
|
||||||
alertOpen.value = true
|
a.encPk = "";
|
||||||
noAccounts.value = false
|
return a;
|
||||||
}
|
}),
|
||||||
|
]);
|
||||||
|
alertHeader.value = "Success";
|
||||||
|
alertMsg.value = "Successfully imported new accounts.";
|
||||||
|
alertOpen.value = true;
|
||||||
|
noAccounts.value = false;
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const exportAcc = async () => {
|
const exportAcc = async () => {
|
||||||
const accounts = await getAccounts()
|
const accounts = await getAccounts();
|
||||||
if (!accounts.length) {
|
if (!accounts.length) {
|
||||||
alertMsg.value = "You need at least one account to export."
|
alertMsg.value = "You need at least one account to export.";
|
||||||
alertOpen.value = true
|
alertOpen.value = true;
|
||||||
}
|
}
|
||||||
if (settings.s.enableStorageEnctyption) {
|
if (settings.s.enableStorageEnctyption) {
|
||||||
const hasPass = await promptForPassword(accounts)
|
const hasPass = await promptForPassword(accounts);
|
||||||
if (hasPass) {
|
if (hasPass) {
|
||||||
const cryptoParams = await getCryptoParams(mpPass.value)
|
const cryptoParams = await getCryptoParams(mpPass.value);
|
||||||
const accProm = accounts.map(async a => {
|
const accProm = accounts.map(async (a) => {
|
||||||
a.pk = await decrypt(a.encPk, cryptoParams)
|
a.pk = await decrypt(a.encPk, cryptoParams);
|
||||||
return a
|
return a;
|
||||||
})
|
});
|
||||||
const encNewAccounts = await Promise.all(accProm)
|
const encNewAccounts = await Promise.all(accProm);
|
||||||
exportFile('wallet_export.json', JSON.stringify(encNewAccounts, null, 2))
|
exportFile("wallet_export.json", JSON.stringify(encNewAccounts, null, 2));
|
||||||
}
|
}
|
||||||
return false
|
return false;
|
||||||
} else {
|
} else {
|
||||||
exportFile('wallet_export.json', JSON.stringify(accounts, null, 2))
|
exportFile("wallet_export.json", JSON.stringify(accounts, null, 2));
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
|
|
||||||
onIonViewWillEnter(async () => {
|
onIonViewWillEnter(async () => {
|
||||||
await Promise.all([getSettings().then((storeSettings) =>
|
await Promise.all([
|
||||||
{
|
getSettings().then((storeSettings) => {
|
||||||
settings.s = storeSettings
|
settings.s = storeSettings;
|
||||||
radioTheme.value = settings.s.theme
|
radioTheme.value = settings.s.theme;
|
||||||
}),
|
}),
|
||||||
getAccounts().then((accounts) => {
|
getAccounts().then((accounts) => {
|
||||||
if (accounts.length) {
|
if (accounts.length) {
|
||||||
noAccounts.value = false
|
noAccounts.value = false;
|
||||||
}
|
}
|
||||||
})])
|
}),
|
||||||
loading.value = false
|
]);
|
||||||
})
|
loading.value = false;
|
||||||
|
});
|
||||||
|
|
||||||
const setTime = async () => {
|
const setTime = async () => {
|
||||||
loading.value = true
|
loading.value = true;
|
||||||
if (settings.s.lockOutPeriod < 2 || settings.s.lockOutPeriod > 120) {
|
if (settings.s.lockOutPeriod < 2 || settings.s.lockOutPeriod > 120) {
|
||||||
loading.value = false
|
loading.value = false;
|
||||||
alertMsg.value = 'Auto-lock period must be between 2 and 120';
|
alertMsg.value = "Auto-lock period must be between 2 and 120";
|
||||||
alertOpen.value = true
|
alertOpen.value = true;
|
||||||
return
|
return;
|
||||||
}
|
|
||||||
settings.s.lockOutPeriod = Math.trunc(settings.s.lockOutPeriod)
|
|
||||||
await saveSettings()
|
|
||||||
loading.value = false
|
|
||||||
toastMsg.value = 'Auto-lock period was set';
|
|
||||||
toastState.value = true
|
|
||||||
}
|
}
|
||||||
|
settings.s.lockOutPeriod = Math.trunc(settings.s.lockOutPeriod);
|
||||||
|
await saveSettings();
|
||||||
|
loading.value = false;
|
||||||
|
toastMsg.value = "Auto-lock period was set";
|
||||||
|
toastState.value = true;
|
||||||
|
};
|
||||||
|
|
||||||
const modalDismiss = () => {
|
const modalDismiss = () => {
|
||||||
setEncryptToggle(settings.s.enableStorageEnctyption)
|
setEncryptToggle(settings.s.enableStorageEnctyption);
|
||||||
}
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
wipeStorage,
|
wipeStorage,
|
||||||
|
@ -576,7 +682,7 @@ export default defineComponent({
|
||||||
changeTheme,
|
changeTheme,
|
||||||
openTab,
|
openTab,
|
||||||
radioTheme,
|
radioTheme,
|
||||||
changePermaLock
|
changePermaLock,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in New Issue