Merge pull request #118 from DeepDoge/master

Bugfixes
This commit is contained in:
kodxana 2022-05-02 10:01:13 +02:00 committed by GitHub
commit dfa1fd03a8
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 443 additions and 92 deletions

97
package-lock.json generated
View file

@ -4826,6 +4826,91 @@
"timsort": "^0.3.0"
}
},
"css-loader": {
"version": "6.7.1",
"resolved": "https://registry.npmjs.org/css-loader/-/css-loader-6.7.1.tgz",
"integrity": "sha512-yB5CNFa14MbPJcomwNh3wLThtkZgcNyI2bNMRt8iE5Z8Vwl7f8vQXFAzn2HDOJvtDq2NTZBUGMSUNNyrv3/+cw==",
"dev": true,
"requires": {
"icss-utils": "^5.1.0",
"postcss": "^8.4.7",
"postcss-modules-extract-imports": "^3.0.0",
"postcss-modules-local-by-default": "^4.0.0",
"postcss-modules-scope": "^3.0.0",
"postcss-modules-values": "^4.0.0",
"postcss-value-parser": "^4.2.0",
"semver": "^7.3.5"
},
"dependencies": {
"nanoid": {
"version": "3.3.3",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.3.tgz",
"integrity": "sha512-p1sjXuopFs0xg+fPASzQ28agW1oHD7xDsd9Xkf3T15H3c/cifrFHVwrh74PdoklAPi+i7MdRsE47vm2r6JoB+w==",
"dev": true
},
"postcss": {
"version": "8.4.13",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.13.tgz",
"integrity": "sha512-jtL6eTBrza5MPzy8oJLFuUscHDXTV5KcLlqAWHl5q5WYRfnNRGSmOZmOZ1T6Gy7A99mOZfqungmZMpMmCVJ8ZA==",
"dev": true,
"requires": {
"nanoid": "^3.3.3",
"picocolors": "^1.0.0",
"source-map-js": "^1.0.2"
}
},
"postcss-modules-extract-imports": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.0.0.tgz",
"integrity": "sha512-bdHleFnP3kZ4NYDhuGlVK+CMrQ/pqUm8bx/oGL93K6gVwiclvX5x0n76fYMKuIGKzlABOy13zsvqjb0f92TEXw==",
"dev": true
},
"postcss-modules-local-by-default": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.0.0.tgz",
"integrity": "sha512-sT7ihtmGSF9yhm6ggikHdV0hlziDTX7oFoXtuVWeDd3hHObNkcHRo9V3yg7vCAY7cONyxJC/XXCmmiHHcvX7bQ==",
"dev": true,
"requires": {
"icss-utils": "^5.0.0",
"postcss-selector-parser": "^6.0.2",
"postcss-value-parser": "^4.1.0"
}
},
"postcss-modules-scope": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-3.0.0.tgz",
"integrity": "sha512-hncihwFA2yPath8oZ15PZqvWGkWf+XUfQgUGamS4LqoP1anQLOsOJw0vr7J7IwLpoY9fatA2qiGUGmuZL0Iqlg==",
"dev": true,
"requires": {
"postcss-selector-parser": "^6.0.4"
}
},
"postcss-modules-values": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/postcss-modules-values/-/postcss-modules-values-4.0.0.tgz",
"integrity": "sha512-RDxHkAiEGI78gS2ofyvCsu7iycRv7oqw5xMWn9iMoR0N/7mf9D50ecQqUo5BZ9Zh2vH4bCUR/ktCqbB9m8vJjQ==",
"dev": true,
"requires": {
"icss-utils": "^5.0.0"
}
},
"postcss-value-parser": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz",
"integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==",
"dev": true
},
"semver": {
"version": "7.3.7",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz",
"integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==",
"dev": true,
"requires": {
"lru-cache": "^6.0.0"
}
}
}
},
"css-modules-loader-core": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/css-modules-loader-core/-/css-modules-loader-core-1.1.0.tgz",
@ -7838,6 +7923,12 @@
"integrity": "sha1-Bupvg2ead0njhs/h/oEq5dsiPe0=",
"dev": true
},
"icss-utils": {
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/icss-utils/-/icss-utils-5.1.0.tgz",
"integrity": "sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==",
"dev": true
},
"ieee754": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
@ -14874,6 +14965,12 @@
"integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=",
"dev": true
},
"source-map-js": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz",
"integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==",
"dev": true
},
"source-map-resolve": {
"version": "0.5.3",
"resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.3.tgz",

View file

@ -5,8 +5,8 @@
"scripts": {
"build:assets": "cpx \"src/**/*.{json,png,svg}\" dist/",
"watch:assets": "cpx --watch -v \"src/**/*.{json,png,svg}\" dist/",
"build:parcel": "cross-env NODE_ENV=production parcel build --no-source-maps --no-minify \"src/**/*.{js,jsx,ts,tsx}\" \"src/**/*.html\"",
"watch:parcel": "parcel watch --no-hmr --no-source-maps \"src/**/*.{js,jsx,ts,tsx}\" \"src/**/*.html\"",
"build:parcel": "cross-env NODE_ENV=production parcel build --no-source-maps --no-minify \"src/**/*.{js,jsx,ts,tsx}\" \"src/**/*.html\" \"src/**/*.md\"",
"watch:parcel": "parcel watch --no-hmr --no-source-maps \"src/**/*.{js,jsx,ts,tsx}\" \"src/**/*.html\" \"src/**/*.md\"",
"build:webext": "web-ext build --source-dir ./dist --overwrite-dest",
"build": "npm-run-all -l -p build:parcel build:assets",
"watch": "npm-run-all -l -p watch:parcel watch:assets",

View file

@ -9,8 +9,8 @@
--gradient-animation: gradient-animation 5s linear infinite alternate;
}
:root {
font-size: .95rem;
body {
font-size: .75rem;
font-family: Arial, -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen, Ubuntu, Cantarell, Open Sans, Helvetica Neue, sans-serif;
letter-spacing: .2ch;
}
@ -59,13 +59,17 @@ h6 {
margin: 0;
}
pre {
margin: 0;
white-space: break-spaces;
}
.options {
display: grid;
width: 100%;
grid-template-columns: repeat(auto-fit, minmax(3em, 1fr));
justify-content: center;
gap: .25em;
padding: 0 1.5em;
}
.button {
@ -81,7 +85,8 @@ h6 {
border-radius: .5em;
border: unset;
font-size: inherit;
font: inherit;
cursor: pointer;
}
.button.active {

View file

@ -1,33 +0,0 @@
import { ComponentChildren, h } from 'preact'
import './style.css'
/*
Re-implementation of https://github.com/DeepDoge/svelte-responsive-row
*/
export function Row(params: {
children: ComponentChildren
type?: "fit" | "fill",
idealSize?: string,
gap?: string,
maxColumnCount?: number,
justifyItems?: "center" | "start" | "end" | "stretch"
}) {
if (!params.type) params.type = 'fill'
if (!params.gap) params.gap = '0'
if (!params.idealSize) params.idealSize = '100%'
if (!params.maxColumnCount) params.maxColumnCount = Number.MAX_SAFE_INTEGER
if (!params.justifyItems) params.justifyItems = 'center'
return <div className='responsive-row'
style={{
'--ideal-size': params.idealSize,
'--gap': params.gap === "0" ? "0px" : params.gap,
'--type': `auto-${params.type}`,
'--max-column-count': params.maxColumnCount,
'--justify-items': params.justifyItems
}}
>
{params.children}
</div>
}

View file

@ -1,6 +0,0 @@
.responsive-row {
display: grid;
grid-template-columns: repeat(var(--type), minmax(min(max(100% / var(--max-column-count) - var(--gap), var(--ideal-size)), 100%), 1fr));
gap: var(--gap);
justify-items: var(--justify-items);
}

146
src/components/dialogs.tsx Normal file
View file

@ -0,0 +1,146 @@
import { h } from 'preact'
import { useEffect, useRef, useState } from 'preact/hooks'
type Message = string
type Alert = { type: 'alert' | 'prompt' | 'confirm', message: Message, resolve: (data: string | boolean | null) => void }
export type DialogManager = ReturnType<typeof createDialogManager>
export function createDialogManager() {
const [alerts, setAlerts] = useState({} as Record<string, Alert>)
const id = crypto.randomUUID()
function add(alert: Alert) {
setAlerts({ ...alerts, alert })
}
function remove() {
delete alerts[id]
setAlerts({ ...alerts })
}
return {
useAlerts() { return alerts },
async alert(message: Message) {
return await new Promise<void>((resolve) => add({
message, type: 'alert', resolve: () => {
resolve()
remove()
}
}))
},
async prompt(message: Message) {
return await new Promise<string | null>((resolve) => add({
message, type: 'prompt', resolve: (data) => {
resolve(data?.toString() ?? null)
remove()
}
}))
},
async confirm(message: Message) {
return await new Promise<boolean>((resolve) => add({
message, type: 'confirm', resolve: (data) => {
resolve(!!data)
remove()
}
}))
}
}
}
interface DialogElement extends HTMLDivElement {
open: boolean
showModal(): void
}
export function Dialogs(params: { manager: ReturnType<typeof createDialogManager> }) {
const alerts = params.manager.useAlerts()
let currentAlert = Object.values(alerts)[0]
if (!currentAlert) return <noscript></noscript>
const [value, setValue] = useState(null as Parameters<typeof currentAlert['resolve']>[0])
let cancelled = false
const dialog = useRef(null as any as DialogElement)
useEffect(() => {
if (!dialog.current) return
if (!dialog.current.open) dialog.current.showModal()
const onClose = () => currentAlert.resolve(null)
dialog.current.addEventListener('close', onClose)
return dialog.current.removeEventListener('close', onClose)
})
return <dialog class="alert-dialog" ref={dialog}>
<style>
{`
.alert-dialog
{
border: none;
background: var(--color-dark);
color: var(--color-light);
margin-bottom: 0;
width: 100%;
max-width: unset;
padding: 1.5em;
}
.alert-dialog::before {
content: "";
display: block;
background: var(--color-gradient-0);
height: 0.1em;
width: 100%;
position: absolute;
left: 0;
top: 0;
}
.alert-dialog form {
display: grid;
gap: 2em
}
.alert-dialog form .fields {
display: grid;
gap: .5em
}
.alert-dialog form .fields pre {
font: inherit;
}
.alert-dialog form .actions {
display: flex;
gap: .5em;
font-size: 1.1em;
font-weight: bold;
}
.alert-dialog form .actions::before {
content: "";
flex-grow: 1111111111111;
}`}
</style>
<form method='dialog' onSubmit={(event) => {
event.preventDefault()
currentAlert.resolve(cancelled ? null : currentAlert.type === 'confirm' ? true : value)
}}>
<div class="fields">
<pre>{currentAlert.message}</pre>
{currentAlert.type === 'prompt' && <input type='text' onInput={(event) => setValue(event.currentTarget.value)} />}
</div>
<div class="actions">
{/* This is here to capture, return key */}
<button style="position:0;opacity:0;pointer-events:none"></button>
{currentAlert.type !== 'alert' && <button className='button' onClick={() => cancelled = true}>Cancel</button>}
<button className='button active'>
{
currentAlert.type === 'alert' ? 'Ok'
: currentAlert.type === 'confirm' ? 'Confirm'
: currentAlert.type === 'prompt' ? 'Apply'
: 'Ok'
}
</button>
</div>
</form>
</dialog>
}

View file

@ -43,6 +43,7 @@
"web_accessible_resources": [
"pages/popup/index.html",
"pages/YTtoLBRY/index.html",
"pages/import/index.html",
"assets/icons/lbry/lbry-logo.svg",
"assets/icons/lbry/odysee-logo.svg",
"assets/icons/lbry/madiator-logo.svg"

View file

@ -1,5 +1,7 @@
import path from 'path'
import { DialogManager } from '../../components/dialogs'
import { getExtensionSettingsAsync, setExtensionSetting, ytUrlResolversSettings } from "../../settings"
import { getFileContent } from '../file'
async function generateKeys() {
const keys = await window.crypto.subtle.generateKey(
@ -91,15 +93,15 @@ async function apiRequest<T extends object>(method: 'GET' | 'POST', pathname: st
throw new Error((await respond.json()).message)
}
export async function generateProfileAndSetNickname(overwrite = false) {
export async function generateProfileAndSetNickname(dialogManager: DialogManager, overwrite = false) {
let { publicKey, privateKey } = await getExtensionSettingsAsync()
let nickname
while (true) {
nickname = prompt("Pick a nickname")
nickname = await dialogManager.prompt("Pick a nickname")
if (nickname) break
if (nickname === null) return
alert("Invalid nickname")
await dialogManager.alert("Invalid nickname")
}
try {
@ -113,21 +115,21 @@ export async function generateProfileAndSetNickname(overwrite = false) {
setExtensionSetting('privateKey', privateKey)
}
await apiRequest('POST', '/profile', { nickname })
alert(`Your nickname has been set to ${nickname}`)
await dialogManager.alert(`Your nickname has been set to ${nickname}`)
} catch (error: any) {
resetProfileSettings()
alert(error.message)
await dialogManager.alert(error.message)
}
}
export async function purgeProfile() {
export async function purgeProfile(dialogManager: DialogManager) {
try {
if (!confirm("This will purge all of your online and offline profile data.\nStill wanna continue?")) return
if (!await dialogManager.confirm("This will purge all of your online and offline profile data.\nStill wanna continue?")) return
await apiRequest('POST', '/profile/purge', {})
resetProfileSettings()
alert(`Your profile has been purged`)
await dialogManager.alert(`Your profile has been purged`)
} catch (error: any) {
alert(error.message)
await dialogManager.alert(error.message)
}
}
@ -155,22 +157,13 @@ function download(data: string, filename: string, type: string) {
})
}
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.readAsText(myFile)
})
})
// Using callback here because there is no good solution for detecting cancel event
export function inputKeyFile(callback: (file: File | null) => void) {
const input = document.createElement("input")
input.type = 'file'
input.accept = '.wol-keys.json'
input.click()
input.addEventListener("change", () => callback(input.files?.[0] ?? null))
}
interface ExportedProfileKeysFile {
@ -189,14 +182,23 @@ export async function exportProfileKeysAsFile() {
download(json, `watch-on-lbry-profile-export-${friendlyPublicKey(publicKey)}.wol-keys.json`, 'application/json')
}
export async function importProfileKeysFromFile() {
export async function importProfileKeysFromFile(dialogManager: DialogManager, file: File) {
try {
const json = await readFile()
if (!json) throw new Error("Invalid")
let settings = await getExtensionSettingsAsync()
if (settings.publicKey && !await dialogManager.confirm(
"This will overwrite your old keypair." +
"\nStill wanna continue?\n\n" +
"NOTE: Without keypair you can't purge your data online.\n" +
"So if you wish to purge, please use purging instead."
)) return false
const json = await getFileContent(file)
if (!json) return false
const { publicKey, privateKey } = JSON.parse(json) as ExportedProfileKeysFile
setExtensionSetting('publicKey', publicKey)
setExtensionSetting('privateKey', privateKey)
return true
} catch (error: any) {
alert(error.message)
await dialogManager.alert(error.message)
return false
}
}

View file

@ -0,0 +1,14 @@
<!DOCTYPE html>
<html lang="en">
<head>
<link rel="stylesheet" href="../../assets/styles/common.css" />
<link rel="stylesheet" href="style.css" />
<script src="main.tsx" defer></script>
</head>
<body>
<div id="root" />
</body>
</html>

47
src/pages/import/main.tsx Normal file
View file

@ -0,0 +1,47 @@
import { h, render } from 'preact'
import { useState } from 'preact/hooks'
import { createDialogManager, Dialogs } from '../../components/dialogs'
import { importProfileKeysFromFile, inputKeyFile } from '../../modules/crypto'
function ImportPage() {
const [loading, updateLoading] = useState(() => false)
async function loads<T>(operation: Promise<T>) {
try {
updateLoading(true)
await operation
} catch (error) {
console.error(error)
}
finally {
updateLoading(false)
}
}
function importProfile() {
inputKeyFile(async (file) => file && await loads(
importProfileKeysFromFile(dialogManager, file)
.then((success) => success && (location.pathname = '/pages/popup/index.html'))
))
}
const dialogManager = createDialogManager()
return <div id='popup'>
<Dialogs manager={dialogManager} />
<main>
<section>
<label>Import your profile</label>
<p>Import your unique keypair.</p>
<div className='options'>
<a onClick={() => importProfile()} className={`button`}>Import</a>
</div>
</section>
</main>
{loading && <div class="overlay">
<span>Loading...</span>
</div>}
</div>
}
render(<ImportPage />, document.getElementById('root')!)

View file

@ -0,0 +1,35 @@
header {
display: grid;
gap: .5em;
padding: .75em;
position: sticky;
top: 0;
background: rgba(19, 19, 19, 0.5);
justify-items: center;
}
main {
display: grid;
gap: 2em;
padding: 1.5em 0.5em;
}
section {
display: grid;
justify-items: center;
text-align: center;
gap: .75em;
}
section label {
font-size: 1.75em;
font-weight: bold;
text-align: center;
}
#popup {
width: 35em;
max-width: 100%;
overflow: hidden;
margin: auto;
}

View file

@ -1,6 +1,7 @@
import { h, render } from 'preact'
import { useState } from 'preact/hooks'
import { exportProfileKeysAsFile, friendlyPublicKey, generateProfileAndSetNickname, getProfile, importProfileKeysFromFile, purgeProfile, resetProfileSettings } from '../../modules/crypto'
import { createDialogManager, Dialogs } from '../../components/dialogs'
import { exportProfileKeysAsFile, friendlyPublicKey, generateProfileAndSetNickname, getProfile, purgeProfile, resetProfileSettings } from '../../modules/crypto'
import { LbryPathnameCache } from '../../modules/yt/urlCache'
import { getTargetPlatfromSettingsEntiries, getYtUrlResolversSettingsEntiries, setExtensionSetting, useExtensionSettings } from '../../settings'
@ -12,11 +13,13 @@ const ytUrlResolverOptions = getYtUrlResolversSettingsEntiries()
function WatchOnLbryPopup(params: { profile: Awaited<ReturnType<typeof getProfile>> | null }) {
const { redirect, targetPlatform, urlResolver, privateKey, publicKey } = useExtensionSettings()
let [loading, updateLoading] = useState(() => false)
let [popupRoute, updateRoute] = useState<string | null>(() => null)
let [route, updateRoute] = useState<string | null>(() => null)
const dialogManager = createDialogManager()
const nickname = params.profile ? params.profile.nickname ?? 'No Nickname' : '...'
async function startAsyncOperation<T>(operation: Promise<T>) {
async function loads<T>(operation: Promise<T>) {
try {
updateLoading(true)
await operation
@ -28,8 +31,22 @@ function WatchOnLbryPopup(params: { profile: Awaited<ReturnType<typeof getProfil
}
}
return <div id='popup'>
async function importButtonClick() {
const importPopupWindow = open(
'/pages/import/index.html',
'Import Profile',
[
`height=${Math.max(document.body.clientHeight, screen.height * .5)}`,
`width=${document.body.clientWidth}`,
`toolbar=0,menubar=0,location=0`,
`top=${screenY}`,
`left=${screenX}`
].join(','))
importPopupWindow?.focus()
}
return <div id='popup'>
<Dialogs manager={dialogManager} />
{
publicKey
? <header>
@ -41,7 +58,7 @@ function WatchOnLbryPopup(params: { profile: Awaited<ReturnType<typeof getProfil
</section>
<section>
{
popupRoute === 'profile'
route === 'profile'
? <a onClick={() => updateRoute('')} className="filled"> Back</a>
: <a className='filled' onClick={() => updateRoute('profile')} href="#profile">Profile Settings</a>
}
@ -49,22 +66,28 @@ function WatchOnLbryPopup(params: { profile: Awaited<ReturnType<typeof getProfil
</header>
: <header>
{
popupRoute === 'profile'
route === 'profile'
? <a onClick={() => updateRoute('')} className="filled"> Back</a>
: <a className='filled' onClick={() => updateRoute('profile')} href="#profile">Profile Settings</a>
}
</header>
}
{
popupRoute === 'profile' ?
route === 'profile' ?
publicKey ?
<main>
<section>
<div className='options'>
<a onClick={() => startAsyncOperation(generateProfileAndSetNickname()).then(() => renderPopup())} className={`button active`}>
<a onClick={() => loads(generateProfileAndSetNickname(dialogManager)).then(() => renderPopup())} className={`button active`}>
Change Nickname
</a>
<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`}>
<a onClick={async () =>
await dialogManager.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`}
>
Forget/Logout
</a>
</div>
@ -76,7 +99,9 @@ function WatchOnLbryPopup(params: { profile: Awaited<ReturnType<typeof getProfil
<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`}>
<a onClick={() => importButtonClick()}
className={`button`}
>
Import
</a>
</div>
@ -85,8 +110,10 @@ function WatchOnLbryPopup(params: { profile: Awaited<ReturnType<typeof getProfil
<label>Purge your profile and data!</label>
<p>Purge your profile data online and offline.</p>
<div className='options'>
<span className="filled button">(°° </span>
<a onClick={() => startAsyncOperation(purgeProfile()).then(() => renderPopup())} className={`button`}>
<div className="purge-aaaaaaa">
<span className='filled'>(°° </span>
</div>
<a onClick={() => loads(purgeProfile(dialogManager)).then(() => renderPopup())} className={`button`}>
Purge Everything!!
</a>
</div>
@ -95,7 +122,11 @@ function WatchOnLbryPopup(params: { profile: Awaited<ReturnType<typeof getProfil
<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`}>
<a onClick={async () => await dialogManager.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.")
&& loads(generateProfileAndSetNickname(dialogManager, true)).then(() => renderPopup())
}
className={`button`}
>
Generate New Account
</a>
</div>
@ -107,10 +138,10 @@ function WatchOnLbryPopup(params: { profile: Awaited<ReturnType<typeof getProfil
<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`}>
<a onClick={() => importButtonClick()} className={`button`}>
Import
</a>
<a onClick={() => startAsyncOperation(generateProfileAndSetNickname()).then(() => renderPopup())} className={`button active`}>
<a onClick={() => loads(generateProfileAndSetNickname(dialogManager)).then(() => renderPopup())} className={`button active`}>
Generate
</a>
</div>
@ -148,13 +179,13 @@ function WatchOnLbryPopup(params: { profile: Awaited<ReturnType<typeof getProfil
</a>
)}
</div>
<a onClick={() => startAsyncOperation(LbryPathnameCache.clearAll()).then(() => alert("Cleared Cache!"))} className={`button active`}>
<a onClick={() => loads(LbryPathnameCache.clearAll().then(() => dialogManager.alert("Cleared Cache!")))} className={`button active`}>
Clear Resolver Cache
</a>
</section>
<section>
<label>Tools</label>
<a target='_blank' href='/tools/YTtoLBRY/index.html' className={`filled`}>
<a target='_blank' href='/pages/YTtoLBRY/index.html' className={`filled`}>
Subscription Converter
</a>
</section>

View file

@ -5,6 +5,7 @@ header {
position: sticky;
top: 0;
background: rgba(19, 19, 19, 0.5);
justify-items: center;
}
main {
@ -16,17 +17,28 @@ main {
section {
display: grid;
justify-items: center;
text-align: center;
gap: .75em;
}
section label {
section>label {
font-size: 1.75em;
font-weight: bold;
text-align: center;
}
section>.options {
padding: 0 1.5em;
}
#popup {
width: 35em;
max-width: 100%;
overflow: hidden;
margin: auto;
}
.purge-aaaaaaa {
display: grid;
justify-items: center;
}