mirror of
https://github.com/LBRYFoundation/Watch-on-LBRY.git
synced 2025-08-23 17:47:26 +00:00
🔥 New UI and Madiator Finder Features
This commit is contained in:
parent
81f1742289
commit
3ee7e530d6
6 changed files with 403 additions and 83 deletions
|
@ -1,6 +1,6 @@
|
||||||
:root {
|
:root {
|
||||||
--color-master: #488e77;
|
--color-master: #499375;
|
||||||
--color-slave: #458593;
|
--color-slave: #43889d;
|
||||||
--color-error: rgb(245, 81, 69);
|
--color-error: rgb(245, 81, 69);
|
||||||
--color-gradient-0: linear-gradient(130deg, var(--color-master), var(--color-slave));
|
--color-gradient-0: linear-gradient(130deg, var(--color-master), var(--color-slave));
|
||||||
--color-gradient-1: linear-gradient(130deg, #ff7a18, #af002d 41.07%, #319197 76.05%);
|
--color-gradient-1: linear-gradient(130deg, #ff7a18, #af002d 41.07%, #319197 76.05%);
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { getExtensionSettingsAsync } from "./settings"
|
import { getExtensionSettingsAsync } from "./settings"
|
||||||
import { setSetting } from "./useSettings"
|
import { setSetting } from "./useSettings"
|
||||||
|
|
||||||
export async function generateKeys() {
|
async function generateKeys() {
|
||||||
const keys = await window.crypto.subtle.generateKey(
|
const keys = await window.crypto.subtle.generateKey(
|
||||||
{
|
{
|
||||||
name: "RSASSA-PKCS1-v1_5",
|
name: "RSASSA-PKCS1-v1_5",
|
||||||
|
@ -58,31 +58,154 @@ export async function sign(data: string, privateKey: string) {
|
||||||
)).toString('base64')
|
)).toString('base64')
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function loginAndSetNickname() {
|
export function resetProfileSettings() {
|
||||||
const settings = await getExtensionSettingsAsync()
|
setSetting('publicKey', null)
|
||||||
|
setSetting('privateKey', null)
|
||||||
|
}
|
||||||
|
|
||||||
let nickname;
|
export async function generateProfileAndSetNickname(overwrite = false) {
|
||||||
while (true)
|
let { publicKey, privateKey } = await getExtensionSettingsAsync()
|
||||||
{
|
|
||||||
|
let nickname
|
||||||
|
while (true) {
|
||||||
nickname = prompt("Pick a nickname")
|
nickname = prompt("Pick a nickname")
|
||||||
if (nickname) break
|
if (nickname) break
|
||||||
if (nickname === null) return
|
if (nickname === null) return
|
||||||
alert("Invalid nickname")
|
alert("Invalid nickname")
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!settings.privateKey || !settings.publicKey)
|
if (overwrite || !privateKey || !publicKey) {
|
||||||
|
resetProfileSettings()
|
||||||
await generateKeys().then((keys) => {
|
await generateKeys().then((keys) => {
|
||||||
setSetting('publicKey', keys.publicKey)
|
publicKey = keys.publicKey
|
||||||
setSetting('privateKey', keys.privateKey)
|
privateKey = keys.privateKey
|
||||||
})
|
})
|
||||||
|
}
|
||||||
|
|
||||||
const url = new URL('https://finder.madiator.com/api/v1/profile')
|
const url = new URL('https://finder.madiator.com/api/v1/profile')
|
||||||
url.searchParams.set('data', JSON.stringify({ nickname }))
|
url.searchParams.set('data', JSON.stringify({ nickname }))
|
||||||
url.searchParams.set('keys', JSON.stringify({
|
url.searchParams.set('keys', JSON.stringify({
|
||||||
signature: await sign(url.searchParams.toString(), settings.privateKey!),
|
signature: await sign(url.searchParams.toString(), privateKey!),
|
||||||
publicKey: settings.publicKey
|
publicKey
|
||||||
}))
|
}))
|
||||||
const respond = await fetch(url.href, { method: "POST" })
|
const respond = await fetch(url.href, { method: "POST" })
|
||||||
if (respond.ok) alert(`Your nickname has been set to ${nickname}`)
|
if (respond.ok) {
|
||||||
|
setSetting('publicKey', publicKey)
|
||||||
|
setSetting('privateKey', privateKey)
|
||||||
|
alert(`Your nickname has been set to ${nickname}`)
|
||||||
|
}
|
||||||
else alert((await respond.json()).message)
|
else alert((await respond.json()).message)
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function purgeProfile() {
|
||||||
|
try {
|
||||||
|
if (!confirm("This will purge all of your online and offline profile data.\nStill wanna continue?")) return
|
||||||
|
const settings = await getExtensionSettingsAsync()
|
||||||
|
|
||||||
|
if (!settings.privateKey || !settings.publicKey)
|
||||||
|
throw new Error('There is no profile to be purged.')
|
||||||
|
|
||||||
|
const url = new URL('https://finder.madiator.com/api/v1/profile/purge')
|
||||||
|
url.searchParams.set('keys', JSON.stringify({
|
||||||
|
signature: await sign(url.searchParams.toString(), settings.privateKey!),
|
||||||
|
publicKey: settings.publicKey
|
||||||
|
}))
|
||||||
|
const respond = await fetch(url.href, { method: "POST" })
|
||||||
|
if (respond.ok) {
|
||||||
|
resetProfileSettings()
|
||||||
|
alert(`Your profile has been purged`)
|
||||||
|
}
|
||||||
|
else throw new Error((await respond.json()).message)
|
||||||
|
} catch (error: any) {
|
||||||
|
alert(error.message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getProfile() {
|
||||||
|
try {
|
||||||
|
const settings = await getExtensionSettingsAsync()
|
||||||
|
|
||||||
|
if (!settings.privateKey || !settings.publicKey)
|
||||||
|
throw new Error('There is no profile.')
|
||||||
|
|
||||||
|
const url = new URL('https://finder.madiator.com/api/v1/profile')
|
||||||
|
url.searchParams.set('data', JSON.stringify({ publicKey: settings.publicKey }))
|
||||||
|
url.searchParams.set('keys', JSON.stringify({
|
||||||
|
signature: await sign(url.searchParams.toString(), settings.privateKey!),
|
||||||
|
publicKey: settings.publicKey
|
||||||
|
}))
|
||||||
|
const respond = await fetch(url.href, { method: "GET" })
|
||||||
|
if (respond.ok) {
|
||||||
|
const profile = await respond.json() as { nickname: string, score: number, publickKey: string }
|
||||||
|
return profile
|
||||||
|
}
|
||||||
|
else throw new Error((await respond.json()).message)
|
||||||
|
} catch (error: any) {
|
||||||
|
console.error(error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function friendlyPublicKey(publicKey: string | null) {
|
||||||
|
// This is copy paste of Madiator Finder's friendly public key
|
||||||
|
return publicKey?.substring(publicKey.length - 64, publicKey.length - 32)
|
||||||
|
}
|
||||||
|
|
||||||
|
function download(data: string, filename: string, type: string) {
|
||||||
|
const file = new Blob([data], { type: type })
|
||||||
|
const a = document.createElement("a")
|
||||||
|
const url = URL.createObjectURL(file)
|
||||||
|
a.href = url
|
||||||
|
a.download = filename
|
||||||
|
document.body.appendChild(a)
|
||||||
|
a.click()
|
||||||
|
setTimeout(() => {
|
||||||
|
document.body.removeChild(a)
|
||||||
|
window.URL.revokeObjectURL(url)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async function readFile() {
|
||||||
|
return await new Promise<string | null>((resolve) => {
|
||||||
|
const input = document.createElement("input")
|
||||||
|
input.type = 'file'
|
||||||
|
input.accept = '.wol-keys.json'
|
||||||
|
|
||||||
|
input.click()
|
||||||
|
input.addEventListener("change", () => {
|
||||||
|
if (!input.files?.[0]) return
|
||||||
|
const myFile = input.files[0]
|
||||||
|
const reader = new FileReader()
|
||||||
|
|
||||||
|
reader.addEventListener('load', () => resolve(reader.result?.toString() ?? null))
|
||||||
|
reader.readAsBinaryString(myFile)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ExportedProfileKeysFile {
|
||||||
|
publicKey: string
|
||||||
|
privateKey: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function exportProfileKeysAsFile() {
|
||||||
|
const { publicKey, privateKey } = await getExtensionSettingsAsync()
|
||||||
|
|
||||||
|
const json = JSON.stringify({
|
||||||
|
publicKey,
|
||||||
|
privateKey
|
||||||
|
})
|
||||||
|
|
||||||
|
download(json, `watch-on-lbry-profile-export-${friendlyPublicKey(publicKey)}.wol-keys.json`, 'application/json')
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function importProfileKeysFromFile() {
|
||||||
|
try {
|
||||||
|
const json = await readFile()
|
||||||
|
if (!json) throw new Error("Invalid")
|
||||||
|
const { publicKey, privateKey } = JSON.parse(json) as ExportedProfileKeysFile
|
||||||
|
setSetting('publicKey', publicKey)
|
||||||
|
setSetting('privateKey', privateKey)
|
||||||
|
} catch (error: any) {
|
||||||
|
alert(error.message)
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -1,5 +1,4 @@
|
||||||
import { useEffect, useReducer } from 'preact/hooks'
|
import { useEffect, useReducer } from 'preact/hooks'
|
||||||
import { generateKeys } from './crypto'
|
|
||||||
import { DEFAULT_SETTINGS, ExtensionSettings } from './settings'
|
import { DEFAULT_SETTINGS, ExtensionSettings } from './settings'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -7,7 +6,7 @@ import { DEFAULT_SETTINGS, ExtensionSettings } from './settings'
|
||||||
*
|
*
|
||||||
* @param defaultSettings the default value. Must have all relevant keys present and should not change
|
* @param defaultSettings the default value. Must have all relevant keys present and should not change
|
||||||
*/
|
*/
|
||||||
export function useSettings(defaultSettings: ExtensionSettings) {
|
function useSettings(defaultSettings: ExtensionSettings) {
|
||||||
const [state, dispatch] = useReducer((state, nstate: Partial<ExtensionSettings>) => ({ ...state, ...nstate }), defaultSettings)
|
const [state, dispatch] = useReducer((state, nstate: Partial<ExtensionSettings>) => ({ ...state, ...nstate }), defaultSettings)
|
||||||
const settingsKeys = Object.keys(defaultSettings)
|
const settingsKeys = Object.keys(defaultSettings)
|
||||||
// register change listeners, gets current values, and cleans up the listeners on unload
|
// register change listeners, gets current values, and cleans up the listeners on unload
|
||||||
|
|
|
@ -1,15 +1,55 @@
|
||||||
.popup {
|
:root {
|
||||||
width: 35em;
|
--color-master: #499375;
|
||||||
max-width: 100%;
|
--color-slave: #43889d;
|
||||||
overflow: hidden;
|
--color-error: rgb(245, 81, 69);
|
||||||
|
--color-gradient-0: linear-gradient(130deg, var(--color-master), var(--color-slave));
|
||||||
|
--color-gradient-1: linear-gradient(130deg, #ff7a18, #af002d 41.07%, #319197 76.05%);
|
||||||
|
--color-dark: #0e1117;
|
||||||
|
--color-light: rgb(235, 237, 241);
|
||||||
|
--gradient-animation: gradient-animation 5s linear infinite alternate;
|
||||||
|
}
|
||||||
|
|
||||||
|
:root {
|
||||||
|
letter-spacing: .2ch;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
background: linear-gradient(to left top, var(--color-master), var(--color-slave));
|
||||||
|
background-attachment: fixed;
|
||||||
|
color: var(--color-light);
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
body::before {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
background: rgba(19, 19, 19, 0.75);
|
||||||
|
}
|
||||||
|
|
||||||
|
*,
|
||||||
|
*::before,
|
||||||
|
*::after {
|
||||||
|
box-sizing: border-box;
|
||||||
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
label {
|
label {
|
||||||
font-size: 2em;
|
font-size: 1.75em;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
margin: 0;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
header {
|
header {
|
||||||
display: grid;
|
display: grid;
|
||||||
position: sticky;
|
position: sticky;
|
||||||
|
@ -19,27 +59,99 @@ header {
|
||||||
padding: .5em .2em;
|
padding: .5em .2em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.button {
|
||||||
|
display: inline-flex;
|
||||||
|
place-items: center;
|
||||||
|
text-align: center;
|
||||||
|
padding: .5em 1em;
|
||||||
|
|
||||||
|
background: var(--color-dark);
|
||||||
|
color: var(--color-light);
|
||||||
|
|
||||||
|
cursor: pointer;
|
||||||
|
border-radius: .5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filled {
|
||||||
|
background: var(--color-gradient-0);
|
||||||
|
background-clip: text;
|
||||||
|
-webkit-background-clip: text;
|
||||||
|
font-weight: bold;
|
||||||
|
color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button.active {
|
||||||
|
background: var(--color-gradient-0);
|
||||||
|
}
|
||||||
|
|
||||||
|
.button.active::before {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
background: var(--color-gradient-0);
|
||||||
|
filter: blur(.5em);
|
||||||
|
z-index: -1;
|
||||||
|
border-radius: .5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button.disabled {
|
||||||
|
filter: saturate(0);
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.go-back {
|
||||||
|
color: currentColor;
|
||||||
|
text-decoration: none;
|
||||||
|
font-size: 1.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.popup {
|
||||||
|
width: 35em;
|
||||||
|
max-width: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.overlay {
|
||||||
|
display: grid;
|
||||||
|
place-items: center;
|
||||||
|
position: fixed;
|
||||||
|
inset: 0;
|
||||||
|
font-size: 2em;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.overlay::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
background-color: #0e1117;
|
||||||
|
opacity: .75;
|
||||||
|
}
|
||||||
|
|
||||||
.profile {
|
.profile {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: auto auto;
|
grid-template-columns: auto auto;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: .25em;
|
gap: .25em;
|
||||||
|
font-size: 1.5em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.profile .name {
|
.profile .name {
|
||||||
display: inline-block;
|
display: inline-flex;
|
||||||
width: 10em;
|
gap: .5em;
|
||||||
max-width: 100%;
|
place-items: center;
|
||||||
|
max-width: min(10em, 100%);
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
|
color: currentColor;
|
||||||
}
|
}
|
||||||
|
|
||||||
.profile::before {
|
.profile .name::before {
|
||||||
content: '';
|
content: '';
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
width: 4ch;
|
width: 1ch;
|
||||||
aspect-ratio: 1/1;
|
aspect-ratio: 1/1;
|
||||||
background: var(--color-gradient-0);
|
background: var(--color-gradient-0);
|
||||||
border-radius: 100000%;
|
border-radius: 100000%;
|
||||||
|
@ -47,17 +159,14 @@ header {
|
||||||
|
|
||||||
main {
|
main {
|
||||||
display: grid;
|
display: grid;
|
||||||
gap: 1em;
|
gap: 2em;
|
||||||
padding: .25em;
|
padding: 1.5em 0.5em;
|
||||||
}
|
}
|
||||||
|
|
||||||
section {
|
section {
|
||||||
display: grid;
|
display: grid;
|
||||||
justify-items: center;
|
justify-items: center;
|
||||||
gap: .75em;
|
gap: .75em;
|
||||||
background: rgba(19, 19, 19, 0.75);
|
|
||||||
border-radius: 1em;
|
|
||||||
padding: .5em;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.options {
|
.options {
|
||||||
|
@ -66,4 +175,5 @@ section {
|
||||||
grid-template-columns: repeat(auto-fit, minmax(3em, 1fr));
|
grid-template-columns: repeat(auto-fit, minmax(3em, 1fr));
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
gap: .25em;
|
gap: .25em;
|
||||||
|
padding: 0 1.5em;
|
||||||
}
|
}
|
|
@ -1,72 +1,162 @@
|
||||||
import { h, render } from 'preact'
|
import { h, render } from 'preact'
|
||||||
import { useState } from 'preact/hooks'
|
import { useState } from 'preact/hooks'
|
||||||
|
import '../common/common.css'
|
||||||
|
import { exportProfileKeysAsFile, friendlyPublicKey, generateProfileAndSetNickname, getProfile, importProfileKeysFromFile, purgeProfile, resetProfileSettings } from '../common/crypto'
|
||||||
import { getTargetPlatfromSettingsEntiries, getYtUrlResolversSettingsEntiries } from '../common/settings'
|
import { getTargetPlatfromSettingsEntiries, getYtUrlResolversSettingsEntiries } from '../common/settings'
|
||||||
import { setSetting, useLbrySettings } from '../common/useSettings'
|
import { setSetting, useLbrySettings } from '../common/useSettings'
|
||||||
import '../common/common.css'
|
|
||||||
import './popup.css'
|
|
||||||
import { LbryPathnameCache } from '../common/yt/urlCache'
|
import { LbryPathnameCache } from '../common/yt/urlCache'
|
||||||
import { loginAndSetNickname } from '../common/crypto'
|
import './popup.css'
|
||||||
|
|
||||||
|
|
||||||
/** Gets all the options for redirect destinations as selection options */
|
/** Gets all the options for redirect destinations as selection options */
|
||||||
const targetPlatforms = getTargetPlatfromSettingsEntiries()
|
const targetPlatforms = getTargetPlatfromSettingsEntiries()
|
||||||
|
|
||||||
const ytUrlResolverOptions = getYtUrlResolversSettingsEntiries()
|
const ytUrlResolverOptions = getYtUrlResolversSettingsEntiries()
|
||||||
|
|
||||||
function WatchOnLbryPopup() {
|
function WatchOnLbryPopup(params: { profile: Awaited<ReturnType<typeof getProfile>> | null }) {
|
||||||
const { redirect, targetPlatform, urlResolver, privateKey, publicKey } = useLbrySettings()
|
const { redirect, targetPlatform, urlResolver, privateKey, publicKey } = useLbrySettings()
|
||||||
let [clearingCache, updateClearingCache] = useState(() => false)
|
let [loading, updateLoading] = useState(() => false)
|
||||||
|
let [popupRoute, updateRoute] = useState<string | null>(() => null)
|
||||||
|
|
||||||
|
const nickname = params.profile ? params.profile.nickname ?? 'No Nickname' : '...'
|
||||||
|
|
||||||
|
async function startAsyncOperation<T>(operation: Promise<T>) {
|
||||||
|
try {
|
||||||
|
updateLoading(true)
|
||||||
|
await operation
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error)
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
updateLoading(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return <div className='popup'>
|
return <div className='popup'>
|
||||||
<header>
|
<header>
|
||||||
<div className='profile'>
|
<div className='profile'>
|
||||||
{publicKey
|
{publicKey
|
||||||
? <span className='name'>{publicKey}</span>
|
? <span><b>Using As:</b> <a onClick={() => updateRoute('profile')} className='name link'>{nickname}</a></span>
|
||||||
: <a className='button' onClick={() => loginAndSetNickname()}>Login</a>}
|
: <a className='button filled' onClick={() => updateRoute('profile')} href="#profile">Your Profile</a>}
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
<main>
|
{
|
||||||
<section>
|
popupRoute === 'profile' ?
|
||||||
<label>Pick a mode !</label>
|
publicKey ?
|
||||||
<div className='options'>
|
<main>
|
||||||
<a onClick={() => setSetting('redirect', true)} className={`button ${redirect ? 'active' : ''}`}>
|
<a onClick={() => updateRoute('')} className="go-back link">⇐ Back</a>
|
||||||
Redirect
|
<section>
|
||||||
</a>
|
<label>{nickname}</label>
|
||||||
<a onClick={() => setSetting('redirect', false)} className={`button ${redirect ? '' : 'active'}`}>
|
<p>{friendlyPublicKey(publicKey)}</p>
|
||||||
Show a button
|
<span><b>Score: {params.profile?.score ?? '...'}</b> - <a target='_blank' href="https://finder.madiator.com/leaderboard" class="filled">🔗Leaderboard</a></span>
|
||||||
</a>
|
<div className='options'>
|
||||||
</div>
|
<a onClick={() => startAsyncOperation(generateProfileAndSetNickname()).then(() => renderPopup())} className={`button active`}>
|
||||||
</section>
|
Change Nickname
|
||||||
<section>
|
</a>
|
||||||
<label>Where would you like to redirect ?</label>
|
<a onClick={() => confirm("This will delete your keypair from this device.\nStill wanna continue?\n\nNOTE: Without keypair you can't purge your data online.\nSo if you wish to purge, please use purging instead.") && resetProfileSettings() && renderPopup()} className={`button`}>
|
||||||
<div className='options'>
|
Forget/Logout
|
||||||
{targetPlatforms.map(([name, value]) =>
|
</a>
|
||||||
<a onClick={() => setSetting('targetPlatform', name)} className={`button ${targetPlatform === name ? 'active' : ''}`}>
|
</div>
|
||||||
{value.displayName}
|
</section>
|
||||||
|
<section>
|
||||||
|
<label>Backup your account</label>
|
||||||
|
<p>Import and export your unique keypair.</p>
|
||||||
|
<div className='options'>
|
||||||
|
<a onClick={() => exportProfileKeysAsFile()} className={`button active`}>
|
||||||
|
Export
|
||||||
|
</a>
|
||||||
|
<a onClick={() => confirm("This will overwrite your old keypair.\nStill wanna continue?\n\nNOTE: Without keypair you can't purge your data online.\nSo if you wish to purge, please use purging instead.") && startAsyncOperation(importProfileKeysFromFile()).then(() => renderPopup())} className={`button`}>
|
||||||
|
Import
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
<section>
|
||||||
|
<label>Purge your profile and data!</label>
|
||||||
|
<p>Purge your profile data online and offline.</p>
|
||||||
|
<div className='options'>
|
||||||
|
<a className="button filled">(╯°□°)╯︵ ┻━┻</a>
|
||||||
|
<a onClick={() => startAsyncOperation(purgeProfile()).then(() => renderPopup())} className={`button`}>
|
||||||
|
Purge Everything!!
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
<section>
|
||||||
|
<label>Generate new profile</label>
|
||||||
|
<p>Generate a new keypair.</p>
|
||||||
|
<div className='options'>
|
||||||
|
<a onClick={() => confirm("This will overwrite your old keypair.\nStill wanna continue?\n\nNOTE: Without keypair you can't purge your data online.\nSo if you wish to purge, please use purging instead.") && startAsyncOperation(generateProfileAndSetNickname(true)).then(() => renderPopup())} className={`button`}>
|
||||||
|
Generate New Account
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</main>
|
||||||
|
:
|
||||||
|
<main>
|
||||||
|
<section>
|
||||||
|
<label>You don't have a profile.</label>
|
||||||
|
<p>You can either import keypair for an existing profile or generate a new profile keypair.</p>
|
||||||
|
<div className='options'>
|
||||||
|
<a onClick={() => startAsyncOperation(importProfileKeysFromFile()).then(() => renderPopup())} className={`button`}>
|
||||||
|
Import
|
||||||
|
</a>
|
||||||
|
<a onClick={() => startAsyncOperation(generateProfileAndSetNickname()).then(() => renderPopup())} className={`button active`}>
|
||||||
|
Generate
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</main>
|
||||||
|
:
|
||||||
|
<main>
|
||||||
|
<section>
|
||||||
|
<label>Pick a mode:</label>
|
||||||
|
<div className='options'>
|
||||||
|
<a onClick={() => setSetting('redirect', true)} className={`button ${redirect ? 'active' : ''}`}>
|
||||||
|
Redirect
|
||||||
|
</a>
|
||||||
|
<a onClick={() => setSetting('redirect', false)} className={`button ${redirect ? '' : 'active'}`}>
|
||||||
|
Show a button
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
<section>
|
||||||
|
<label>Which platform you would like to redirect?</label>
|
||||||
|
<div className='options'>
|
||||||
|
{targetPlatforms.map(([name, value]) =>
|
||||||
|
<a onClick={() => setSetting('targetPlatform', name)} className={`button ${targetPlatform === name ? 'active' : ''}`}>
|
||||||
|
{value.displayName}
|
||||||
|
</a>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
<section>
|
||||||
|
<label>Which resolver API you want to use?</label>
|
||||||
|
<div className='options'>
|
||||||
|
{ytUrlResolverOptions.map(([name, value]) =>
|
||||||
|
<a onClick={() => setSetting('urlResolver', name)} className={`button ${urlResolver === name ? 'active' : ''}`}>
|
||||||
|
{value.name}
|
||||||
|
</a>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<a onClick={() => startAsyncOperation(LbryPathnameCache.clearAll()).then(() => alert("Cleared Cache!"))} className={`button active`}>
|
||||||
|
Clear Resolver Cache
|
||||||
</a>
|
</a>
|
||||||
)}
|
</section>
|
||||||
</div>
|
<section>
|
||||||
</section>
|
<label>Tools</label>
|
||||||
<section>
|
<a target='_blank' href='/tools/YTtoLBRY.html' className={`button filled`}>
|
||||||
<label>Which resolver API you want to use ?</label>
|
Subscription Converter
|
||||||
<div className='options'>
|
|
||||||
{ytUrlResolverOptions.map(([name, value]) =>
|
|
||||||
<a onClick={() => setSetting('urlResolver', name)} className={`button ${urlResolver === name ? 'active' : ''}`}>
|
|
||||||
{value.name}
|
|
||||||
</a>
|
</a>
|
||||||
)}
|
</section>
|
||||||
</div>
|
</main>
|
||||||
<a onClick={async () => {
|
}
|
||||||
updateClearingCache(true)
|
{loading && <div class="overlay">
|
||||||
await LbryPathnameCache.clearAll()
|
<span>Loading...</span>
|
||||||
updateClearingCache(false)
|
</div>}
|
||||||
alert("Cleared Cache!")
|
|
||||||
}} className={`button active ${clearingCache ? 'disabled' : ''}`}>
|
|
||||||
Clear Resolver Cache
|
|
||||||
</a>
|
|
||||||
</section>
|
|
||||||
</main>
|
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
render(<WatchOnLbryPopup />, document.getElementById('root')!)
|
function renderPopup() {
|
||||||
|
render(<WatchOnLbryPopup profile={null} />, document.getElementById('root')!)
|
||||||
|
getProfile().then((profile) => render(<WatchOnLbryPopup profile={profile} />, document.getElementById('root')!))
|
||||||
|
}
|
||||||
|
|
||||||
|
renderPopup()
|
|
@ -1,6 +1,4 @@
|
||||||
import { generateKeys } from '../common/crypto'
|
|
||||||
import { DEFAULT_SETTINGS, ExtensionSettings, getExtensionSettingsAsync } from '../common/settings'
|
import { DEFAULT_SETTINGS, ExtensionSettings, getExtensionSettingsAsync } from '../common/settings'
|
||||||
import { setSetting } from '../common/useSettings'
|
|
||||||
|
|
||||||
/** Reset settings to default value and update the browser badge text */
|
/** Reset settings to default value and update the browser badge text */
|
||||||
async function initSettings() {
|
async function initSettings() {
|
||||||
|
|
Loading…
Add table
Reference in a new issue