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" "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": { "css-modules-loader-core": {
"version": "1.1.0", "version": "1.1.0",
"resolved": "https://registry.npmjs.org/css-modules-loader-core/-/css-modules-loader-core-1.1.0.tgz", "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=", "integrity": "sha1-Bupvg2ead0njhs/h/oEq5dsiPe0=",
"dev": true "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": { "ieee754": {
"version": "1.2.1", "version": "1.2.1",
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
@ -14874,6 +14965,12 @@
"integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=",
"dev": true "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": { "source-map-resolve": {
"version": "0.5.3", "version": "0.5.3",
"resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.3.tgz", "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.3.tgz",

View file

@ -5,8 +5,8 @@
"scripts": { "scripts": {
"build:assets": "cpx \"src/**/*.{json,png,svg}\" dist/", "build:assets": "cpx \"src/**/*.{json,png,svg}\" dist/",
"watch:assets": "cpx --watch -v \"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\"", "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\"", "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:webext": "web-ext build --source-dir ./dist --overwrite-dest",
"build": "npm-run-all -l -p build:parcel build:assets", "build": "npm-run-all -l -p build:parcel build:assets",
"watch": "npm-run-all -l -p watch:parcel watch: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; --gradient-animation: gradient-animation 5s linear infinite alternate;
} }
:root { body {
font-size: .95rem; font-size: .75rem;
font-family: Arial, -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen, Ubuntu, Cantarell, Open Sans, Helvetica Neue, sans-serif; font-family: Arial, -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen, Ubuntu, Cantarell, Open Sans, Helvetica Neue, sans-serif;
letter-spacing: .2ch; letter-spacing: .2ch;
} }
@ -59,13 +59,17 @@ h6 {
margin: 0; margin: 0;
} }
pre {
margin: 0;
white-space: break-spaces;
}
.options { .options {
display: grid; display: grid;
width: 100%; width: 100%;
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;
} }
.button { .button {
@ -81,7 +85,8 @@ h6 {
border-radius: .5em; border-radius: .5em;
border: unset; border: unset;
font-size: inherit; font: inherit;
cursor: pointer;
} }
.button.active { .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": [ "web_accessible_resources": [
"pages/popup/index.html", "pages/popup/index.html",
"pages/YTtoLBRY/index.html", "pages/YTtoLBRY/index.html",
"pages/import/index.html",
"assets/icons/lbry/lbry-logo.svg", "assets/icons/lbry/lbry-logo.svg",
"assets/icons/lbry/odysee-logo.svg", "assets/icons/lbry/odysee-logo.svg",
"assets/icons/lbry/madiator-logo.svg" "assets/icons/lbry/madiator-logo.svg"

View file

@ -1,5 +1,7 @@
import path from 'path' import path from 'path'
import { DialogManager } from '../../components/dialogs'
import { getExtensionSettingsAsync, setExtensionSetting, ytUrlResolversSettings } from "../../settings" import { getExtensionSettingsAsync, setExtensionSetting, ytUrlResolversSettings } from "../../settings"
import { getFileContent } from '../file'
async function generateKeys() { async function generateKeys() {
const keys = await window.crypto.subtle.generateKey( 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) 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 { publicKey, privateKey } = await getExtensionSettingsAsync()
let nickname let nickname
while (true) { while (true) {
nickname = prompt("Pick a nickname") nickname = await dialogManager.prompt("Pick a nickname")
if (nickname) break if (nickname) break
if (nickname === null) return if (nickname === null) return
alert("Invalid nickname") await dialogManager.alert("Invalid nickname")
} }
try { try {
@ -113,21 +115,21 @@ export async function generateProfileAndSetNickname(overwrite = false) {
setExtensionSetting('privateKey', privateKey) setExtensionSetting('privateKey', privateKey)
} }
await apiRequest('POST', '/profile', { nickname }) 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) { } catch (error: any) {
resetProfileSettings() resetProfileSettings()
alert(error.message) await dialogManager.alert(error.message)
} }
} }
export async function purgeProfile() { export async function purgeProfile(dialogManager: DialogManager) {
try { 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', {}) await apiRequest('POST', '/profile/purge', {})
resetProfileSettings() resetProfileSettings()
alert(`Your profile has been purged`) await dialogManager.alert(`Your profile has been purged`)
} catch (error: any) { } 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() { // Using callback here because there is no good solution for detecting cancel event
return await new Promise<string | null>((resolve) => { export function inputKeyFile(callback: (file: File | null) => void) {
const input = document.createElement("input") const input = document.createElement("input")
input.type = 'file' input.type = 'file'
input.accept = '.wol-keys.json' input.accept = '.wol-keys.json'
input.click()
input.click() input.addEventListener("change", () => callback(input.files?.[0] ?? null))
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)
})
})
} }
interface ExportedProfileKeysFile { interface ExportedProfileKeysFile {
@ -189,14 +182,23 @@ export async function exportProfileKeysAsFile() {
download(json, `watch-on-lbry-profile-export-${friendlyPublicKey(publicKey)}.wol-keys.json`, 'application/json') 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 { try {
const json = await readFile() let settings = await getExtensionSettingsAsync()
if (!json) throw new Error("Invalid") 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 const { publicKey, privateKey } = JSON.parse(json) as ExportedProfileKeysFile
setExtensionSetting('publicKey', publicKey) setExtensionSetting('publicKey', publicKey)
setExtensionSetting('privateKey', privateKey) setExtensionSetting('privateKey', privateKey)
return true
} catch (error: any) { } 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 { h, render } from 'preact'
import { useState } from 'preact/hooks' 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 { LbryPathnameCache } from '../../modules/yt/urlCache'
import { getTargetPlatfromSettingsEntiries, getYtUrlResolversSettingsEntiries, setExtensionSetting, useExtensionSettings } from '../../settings' import { getTargetPlatfromSettingsEntiries, getYtUrlResolversSettingsEntiries, setExtensionSetting, useExtensionSettings } from '../../settings'
@ -12,11 +13,13 @@ const ytUrlResolverOptions = getYtUrlResolversSettingsEntiries()
function WatchOnLbryPopup(params: { profile: Awaited<ReturnType<typeof getProfile>> | null }) { function WatchOnLbryPopup(params: { profile: Awaited<ReturnType<typeof getProfile>> | null }) {
const { redirect, targetPlatform, urlResolver, privateKey, publicKey } = useExtensionSettings() const { redirect, targetPlatform, urlResolver, privateKey, publicKey } = useExtensionSettings()
let [loading, updateLoading] = useState(() => false) 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' : '...' const nickname = params.profile ? params.profile.nickname ?? 'No Nickname' : '...'
async function startAsyncOperation<T>(operation: Promise<T>) {
async function loads<T>(operation: Promise<T>) {
try { try {
updateLoading(true) updateLoading(true)
await operation 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 publicKey
? <header> ? <header>
@ -41,7 +58,7 @@ function WatchOnLbryPopup(params: { profile: Awaited<ReturnType<typeof getProfil
</section> </section>
<section> <section>
{ {
popupRoute === 'profile' route === 'profile'
? <a onClick={() => updateRoute('')} className="filled"> Back</a> ? <a onClick={() => updateRoute('')} className="filled"> Back</a>
: <a className='filled' onClick={() => updateRoute('profile')} href="#profile">Profile Settings</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>
: <header> : <header>
{ {
popupRoute === 'profile' route === 'profile'
? <a onClick={() => updateRoute('')} className="filled"> Back</a> ? <a onClick={() => updateRoute('')} className="filled"> Back</a>
: <a className='filled' onClick={() => updateRoute('profile')} href="#profile">Profile Settings</a> : <a className='filled' onClick={() => updateRoute('profile')} href="#profile">Profile Settings</a>
} }
</header> </header>
} }
{ {
popupRoute === 'profile' ? route === 'profile' ?
publicKey ? publicKey ?
<main> <main>
<section> <section>
<div className='options'> <div className='options'>
<a onClick={() => startAsyncOperation(generateProfileAndSetNickname()).then(() => renderPopup())} className={`button active`}> <a onClick={() => loads(generateProfileAndSetNickname(dialogManager)).then(() => renderPopup())} className={`button active`}>
Change Nickname Change Nickname
</a> </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 Forget/Logout
</a> </a>
</div> </div>
@ -76,7 +99,9 @@ function WatchOnLbryPopup(params: { profile: Awaited<ReturnType<typeof getProfil
<a onClick={() => exportProfileKeysAsFile()} className={`button active`}> <a onClick={() => exportProfileKeysAsFile()} className={`button active`}>
Export Export
</a> </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 Import
</a> </a>
</div> </div>
@ -85,8 +110,10 @@ function WatchOnLbryPopup(params: { profile: Awaited<ReturnType<typeof getProfil
<label>Purge your profile and data!</label> <label>Purge your profile and data!</label>
<p>Purge your profile data online and offline.</p> <p>Purge your profile data online and offline.</p>
<div className='options'> <div className='options'>
<span className="filled button">(°° </span> <div className="purge-aaaaaaa">
<a onClick={() => startAsyncOperation(purgeProfile()).then(() => renderPopup())} className={`button`}> <span className='filled'>(°° </span>
</div>
<a onClick={() => loads(purgeProfile(dialogManager)).then(() => renderPopup())} className={`button`}>
Purge Everything!! Purge Everything!!
</a> </a>
</div> </div>
@ -95,7 +122,11 @@ function WatchOnLbryPopup(params: { profile: Awaited<ReturnType<typeof getProfil
<label>Generate new profile</label> <label>Generate new profile</label>
<p>Generate a new keypair.</p> <p>Generate a new keypair.</p>
<div className='options'> <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 Generate New Account
</a> </a>
</div> </div>
@ -107,10 +138,10 @@ function WatchOnLbryPopup(params: { profile: Awaited<ReturnType<typeof getProfil
<label>You don't have a profile.</label> <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> <p>You can either import keypair for an existing profile or generate a new profile keypair.</p>
<div className='options'> <div className='options'>
<a onClick={() => startAsyncOperation(importProfileKeysFromFile()).then(() => renderPopup())} className={`button`}> <a onClick={() => importButtonClick()} className={`button`}>
Import Import
</a> </a>
<a onClick={() => startAsyncOperation(generateProfileAndSetNickname()).then(() => renderPopup())} className={`button active`}> <a onClick={() => loads(generateProfileAndSetNickname(dialogManager)).then(() => renderPopup())} className={`button active`}>
Generate Generate
</a> </a>
</div> </div>
@ -148,13 +179,13 @@ function WatchOnLbryPopup(params: { profile: Awaited<ReturnType<typeof getProfil
</a> </a>
)} )}
</div> </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 Clear Resolver Cache
</a> </a>
</section> </section>
<section> <section>
<label>Tools</label> <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 Subscription Converter
</a> </a>
</section> </section>

View file

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