mirror of
https://github.com/LBRYFoundation/Watch-on-LBRY.git
synced 2025-08-23 17:47:26 +00:00
commit
dfa1fd03a8
13 changed files with 443 additions and 92 deletions
97
package-lock.json
generated
97
package-lock.json
generated
|
@ -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",
|
||||||
|
|
|
@ -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",
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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>
|
|
||||||
}
|
|
|
@ -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
146
src/components/dialogs.tsx
Normal 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>
|
||||||
|
}
|
|
@ -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"
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
14
src/pages/import/index.html
Normal file
14
src/pages/import/index.html
Normal 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
47
src/pages/import/main.tsx
Normal 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')!)
|
35
src/pages/import/style.css
Normal file
35
src/pages/import/style.css
Normal 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;
|
||||||
|
}
|
|
@ -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>
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
Loading…
Add table
Reference in a new issue