870 lines
28 KiB
HTML
870 lines
28 KiB
HTML
<!DOCTYPE html>
|
|
<?xml version="1.0" encoding="UTF-8"?>
|
|
<html lang="en" dir="ltr">
|
|
|
|
<head>
|
|
<meta charset="utf-8" />
|
|
<title>CLIENT</title>
|
|
|
|
<link rel="stylesheet" href="./css/client.css" />
|
|
|
|
<!-- Initialize theme BEFORE any components load -->
|
|
<script>
|
|
(function () {
|
|
const savedTheme = localStorage.getItem('theme');
|
|
if (savedTheme === 'dark') {
|
|
document.documentElement.classList.add('dark-mode');
|
|
if (document.body) {
|
|
document.body.classList.add('dark-mode');
|
|
}
|
|
}
|
|
})();
|
|
</script>
|
|
<link rel="shortcut icon" type="image/x-icon" href="./favicon/favicon-dots2.ico" />
|
|
|
|
<!-- SVG.js library (required by HamburgerMorphing) -->
|
|
<script src="./js/vendor/svg.min.js"></script>
|
|
|
|
|
|
<style media="screen">
|
|
#divBody {
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
flex-direction: row;
|
|
justify-content: space-around;
|
|
aligncontent: space-around;
|
|
}
|
|
|
|
#divKeyScreen {
|
|
padding-top: 20px;
|
|
display: flex;
|
|
flex-direction: column;
|
|
justify-content: space-around;
|
|
}
|
|
|
|
.inpSeed {
|
|
/* border: 1px solid red; */
|
|
padding-top: 20px;
|
|
display: flex;
|
|
flex-direction: row;
|
|
justify-content: space-around;
|
|
flex-wrap: wrap;
|
|
width: 400px;
|
|
}
|
|
|
|
.inpStyle {
|
|
border: var(--header-border);
|
|
width: 400px;
|
|
border-radius: 15px;
|
|
padding: 10px;
|
|
font-size: 100%;
|
|
}
|
|
|
|
.inpStyle2 {
|
|
border: var(--header-border);
|
|
/* width: 400px; */
|
|
border-radius: 15px;
|
|
padding: 10px;
|
|
font-size: 100%;
|
|
}
|
|
|
|
.inpKey {
|
|
text-align: left;
|
|
overflow: hidden;
|
|
white-space: nowrap;
|
|
}
|
|
|
|
.inpKey:focus {
|
|
outline: none;
|
|
}
|
|
|
|
.inpKeyError {
|
|
border-color: var(--button-hover-color);
|
|
}
|
|
|
|
.inpKeyButton {
|
|
text-align: center;
|
|
}
|
|
|
|
.inpKeyButton:hover {
|
|
background-color: var(--button-hover-color);
|
|
cursor: pointer;
|
|
}
|
|
|
|
.inpKeyButton:active {
|
|
background-color: var(--button-click-color);
|
|
cursor: pointer;
|
|
}
|
|
|
|
.inpCheckBox {
|
|
text-align: center;
|
|
}
|
|
|
|
.inpCheckBox:hover {
|
|
background-color: var(--button-hover-color);
|
|
cursor: pointer;
|
|
}
|
|
|
|
.divAppButtons {
|
|
|
|
border: var(--border-width2) solid var(--primary-color);
|
|
min-width: 200px;
|
|
width: 200px;
|
|
height: 270px;
|
|
margin-top: 20px;
|
|
border-radius: 20px;
|
|
font-size: 200%;
|
|
font-weight: bold;
|
|
text-align: center;
|
|
margin: 1px;
|
|
}
|
|
|
|
.divAppButtons:hover {
|
|
background-color: var(--button-hover-color);
|
|
cursor: pointer;
|
|
}
|
|
|
|
a.divAppButtons {
|
|
margin: 10px;
|
|
text-decoration: none;
|
|
color: inherit;
|
|
display: flex;
|
|
flex-direction: column;
|
|
justify-content: center;
|
|
align-items: center;
|
|
}
|
|
|
|
.svgAppButtons {
|
|
fill: none;
|
|
stroke: var(--button-color);
|
|
stroke-width: 1.98;
|
|
stroke-linecap: round;
|
|
stroke-linejoin: round;
|
|
}
|
|
|
|
#divNpub {
|
|
padding-top: 12px;
|
|
}
|
|
|
|
#divNpubTitle {
|
|
padding-top: 10px;
|
|
}
|
|
|
|
.svgBtn {
|
|
fill: none;
|
|
stroke-width: 1.98;
|
|
stroke-linecap: round;
|
|
stroke-linejoin: round;
|
|
transition: 0.2s;
|
|
height: 1.2em;
|
|
padding-top: 0.2em;
|
|
}
|
|
|
|
.svgBtn:hover {
|
|
cursor: pointer;
|
|
stroke: var(--button-hover-color);
|
|
}
|
|
|
|
.svgBtn_unsel {
|
|
stroke: #eee;
|
|
}
|
|
|
|
.svgBtn_sel {
|
|
stroke: var(--button-color);
|
|
fill: black;
|
|
}
|
|
|
|
#tblBootstrap {}
|
|
|
|
.clsBootstrap_enabled {
|
|
pointer-events: all;
|
|
opacity: 1;
|
|
}
|
|
|
|
.clsBootstrap_disabled {
|
|
pointer-events: none;
|
|
opacity: 0.4;
|
|
}
|
|
|
|
.tdBootstrap {
|
|
/* border: 0.1px solid black; */
|
|
/* line-height: 1.5; */
|
|
/* display: inline-block; */
|
|
/* vertical-align: middle; */
|
|
}
|
|
|
|
#tblSeed {
|
|
border: 1px solid red;
|
|
width: 100%;
|
|
empty-cells: show;
|
|
border-collapse: collapse;
|
|
}
|
|
|
|
.tdSeed {
|
|
border: 0.1px solid black;
|
|
/* display: inline-block; */
|
|
/* vertical-align: middle; */
|
|
/* min-width: 5em; */
|
|
/* margin-right: .1em; */
|
|
}
|
|
|
|
.divSeed {
|
|
border: 2px solid black;
|
|
/* border-style: solid;
|
|
border-width: 1px; */
|
|
padding: 10px;
|
|
margin: 1px;
|
|
border-radius: 10px;
|
|
position: relative;
|
|
min-width: 120px;
|
|
}
|
|
|
|
.borderBlack {
|
|
border-color: black;
|
|
outline: black;
|
|
}
|
|
|
|
.borderRed {
|
|
border-color: red;
|
|
outline: red;
|
|
}
|
|
|
|
.borderGreen {
|
|
border-color: green;
|
|
outline: green;
|
|
}
|
|
|
|
#tblIdent {
|
|
/* border: 1px solid red; */
|
|
width: 100%;
|
|
empty-cells: show;
|
|
border-collapse: collapse;
|
|
}
|
|
|
|
.tdIdent {
|
|
/* border: 0.1px solid black; */
|
|
padding-left: 3px;
|
|
margin-left: 3px;
|
|
text-align: center;
|
|
}
|
|
|
|
.trHover {}
|
|
|
|
.trHover:hover {
|
|
cursor: pointer;
|
|
background-color: var(--muted-color);
|
|
}
|
|
|
|
.DivHorizontal {
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
flex-direction: row;
|
|
justify-content: space-between;
|
|
/* aligncontent: space-around; */
|
|
padding-bottom: 10px;
|
|
}
|
|
</style>
|
|
</head>
|
|
|
|
<body>
|
|
<!-- ================================================================
|
|
HAMBURGER BUTTON (Fixed, separate from header)
|
|
================================================================ -->
|
|
<div id="divSvgHam" class="divHeaderButtons">
|
|
<!-- HamburgerMorphing will be injected here -->
|
|
</div>
|
|
|
|
<!-- ================================================================
|
|
HEADER
|
|
================================================================ -->
|
|
<div id="divHeader">
|
|
<div id="divHeaderFlexLeft">
|
|
<!-- Hamburger is now separate fixed element -->
|
|
</div>
|
|
|
|
<div id="divHeaderFlexCenter">
|
|
<!-- <div class="divHeaderText">CLIENT</div> -->
|
|
</div>
|
|
|
|
<div id="divHeaderFlexRight">
|
|
<!-- No button in header right - logout is in sidenav footer -->
|
|
</div>
|
|
</div>
|
|
|
|
<!-- ================================================================
|
|
BODY
|
|
================================================================ -->
|
|
<div id="divBody" class="clsBodyFull"></div>
|
|
|
|
<!-- ================================================================
|
|
FOOTER
|
|
================================================================ -->
|
|
<div id="divFooter">
|
|
<div id="divFooterLeft" class="divFooterBox"></div>
|
|
<div id="divFooterCenter" class="divFooterBox"></div>
|
|
<div id="divFooterRight" class="divFooterBox"></div>
|
|
<div id="divFooterBalance" class="divFooterBox">0 sats</div>
|
|
</div>
|
|
|
|
<!-- ================================================================
|
|
SIDENAV
|
|
================================================================ -->
|
|
<div id="divSideNav">
|
|
<div id="divSideNavHeader">
|
|
<!-- No close button - use main hamburger to close -->
|
|
</div>
|
|
|
|
<div id="divSideNavBody">
|
|
<div id="divFiles"></div>
|
|
</div>
|
|
|
|
<div id="divAiSection" class="sidenavSection">
|
|
<div id="divAiSectionTitle" class="sidenavSectionTitle">AI</div>
|
|
<div id="divAiList" class="sidenavSectionList">
|
|
<div id="divAiProvidersList">No saved providers yet.</div>
|
|
</div>
|
|
</div>
|
|
|
|
|
|
<div id="divRelaySection">
|
|
<div id="divRelaySectionTitle">
|
|
リレー
|
|
</div>
|
|
<div id="divRelayList">
|
|
Loading relays...
|
|
</div>
|
|
</div>
|
|
|
|
<div id="divBlossomSection">
|
|
|
|
<div id="divBlossomSectionTitle">ブロッサム</div>
|
|
|
|
<div id="divBlossomList">Loading blossom servers...</div>
|
|
|
|
</div>
|
|
|
|
|
|
<div id="divVersionBar">
|
|
<span id="versionDisplay">v0.0.1</span>
|
|
<div id="divVersionBarButtons">
|
|
<button id="themeToggleButton" class="btn" title="Toggle Dark/Light Mode"
|
|
style="width: 32px; height: 32px; padding: 0;">
|
|
<div id="themeToggleHamburgerContainer"></div>
|
|
</button>
|
|
<button id="logoutButton" class="btn" title="Logout" style="width: 32px; height: 32px; padding: 0;">
|
|
<div id="logoutHamburgerContainer"></div>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- ================================================================
|
|
REQUIRED SCRIPTS
|
|
================================================================ -->
|
|
<script type="text/javascript" src="./js/qrcode-svg.min.js"></script>
|
|
<script src="./nostr.bundle.js"></script>
|
|
<script src="/nostr-login-lite/nostr-lite.js"></script>
|
|
|
|
<script type="module">
|
|
/* ================================================================
|
|
IMPORTS
|
|
================================================================ */
|
|
import { initNDKPage, getPubkey, injectHeaderAvatar, disconnect, getVersion, updateVersionDisplay } from './js/init-ndk.mjs';
|
|
import { HamburgerMorphing } from "./hamburger_morphing/hamburger.mjs";
|
|
import { initFooterRelayStatus, updateFooterRelayStatus, initSidenavRelaySection, updateSidenavRelaySection, setRelayActivityState } from './js/relay-ui.mjs';
|
|
|
|
import { initBlossomSection, updateBlossomSection } from './js/blossom-ui.mjs';
|
|
|
|
import { initAiSectionWithLocalConfig } from './js/ai-ui.mjs';
|
|
// Version will be loaded asynchronously
|
|
const versionInfo = await getVersion();
|
|
const VERSION = versionInfo.VERSION;
|
|
console.log(`[index.html ${VERSION}] Loading...`);
|
|
|
|
/* ================================================================
|
|
GLOBAL VARIABLES
|
|
================================================================ */
|
|
let updateIntervalId = null;
|
|
let currentPubkey = null;
|
|
let startupStatusMessage = '';
|
|
let startupStatusUntil = 0;
|
|
let walletFooterStatusMessage = '';
|
|
let walletFooterStatusUntil = 0;
|
|
|
|
// Hamburger menu
|
|
let hamburgerInstance = null;
|
|
let isNavOpen = false;
|
|
|
|
// Version bar buttons
|
|
let logoutHamburger = null;
|
|
let themeToggleHamburger = null;
|
|
let isDarkMode = false;
|
|
|
|
/* ================================================================
|
|
DOM VARIABLES
|
|
================================================================ */
|
|
const divBody = document.getElementById("divBody");
|
|
const divSideNav = document.getElementById("divSideNav");
|
|
const divSideNavBody = document.getElementById("divSideNavBody");
|
|
const divFooterCenter = document.getElementById("divFooterCenter");
|
|
const divFooterRight = document.getElementById("divFooterRight");
|
|
|
|
/* ================================================================
|
|
APP DEFINITIONS
|
|
================================================================ */
|
|
const arrApps = [
|
|
{
|
|
id: `relays`,
|
|
svg: `<circle r="1" cx="7" cy="3" /> <circle r="1" cx="3" cy="3" /> <circle r="1" cx="3" cy="7" /> <circle r="1" cx="7" cy="7" />`,
|
|
name: `RELAYS`,
|
|
},
|
|
{
|
|
id: `db`,
|
|
svg: `<line x1="2" y1="2" x2="8" y2="2" /> <line x1="2" y1="5" x2="8" y2="5" /> <line x1="2" y1="8" x2="8" y2="8" />`,
|
|
name: `DB`
|
|
},
|
|
{
|
|
id: `tools`,
|
|
svg: `<path d="M5,8 L5,3 L8,4 L8,2 L5,3 L2,3 "/>`,
|
|
name: `TOOLS`,
|
|
},
|
|
|
|
// {
|
|
// id: `files`,
|
|
// svg: `<path d="M2,2 L2,8 "/> <path d="M4,2 L4,8 "/><path d="M6,2 L6,8"/><path d="M8,2 L8,8"/> `,
|
|
// name: `FILES`,
|
|
// },
|
|
|
|
{
|
|
id: `links`,
|
|
svg: `<path d="M2,2 L2,2 M2,5 L2,5 M2,8 L2,8 M5,2 L5,2 M5,5 L5,5 M5,8 L5,8 M8,2 L8,2 M8,5 L8,5 M8,8 L8,8 "/>`,
|
|
name: `LINKS`,
|
|
},
|
|
{
|
|
id: `profile`,
|
|
svg: `<circle cx=5 cy=5 r=3 fill="none" />`,
|
|
name: `P-FILE`,
|
|
},
|
|
{
|
|
id: `ws`,
|
|
svg: `<circle r="2.8284271247461903" cx="5" cy="5" />
|
|
<line x1="6" y1="6" x2="6" y2="6" />
|
|
<line x1="4" y1="4" x2="4" y2="4" /> `,
|
|
name: `WS`,
|
|
},
|
|
{
|
|
id: `post`,
|
|
svg: `<path d="M2,8 L8,2 L5,2 L8,2 L8,5 " /> `,
|
|
name: `POST`,
|
|
},
|
|
{
|
|
id: `feed`,
|
|
svg: `<path d="M2,5 L2,8 L5,8 L2,8 L8,2 " />`,
|
|
name: `FEED`,
|
|
},
|
|
{
|
|
id: `view`,
|
|
svg: `<path d="M2,5 L2,8 L5,8 L2,8 L8,2 " />`,
|
|
name: `VIEW`,
|
|
},
|
|
{
|
|
id: `love`,
|
|
svg: `<circle cx="3.5" cy="5" r="1.5" /><circle cx="7" cy="5" r="1.5" />`,
|
|
name: `LOVE`,
|
|
},
|
|
{
|
|
id: `msg`,
|
|
svg: `<path d="M5,2 L8,2 L8,5 L8,2 L2,8 L2,5 L2,8 L5,8 " /> `,
|
|
name: `MSG`,
|
|
},
|
|
{
|
|
id: `event`,
|
|
svg: `<circle cx=5 cy=5 r=3 fill="none" />`,
|
|
name: `EVENTS`,
|
|
},
|
|
{
|
|
id: `note`,
|
|
svg: `<polygon fill="none" points="6,2 6,4 8,4 8,8 2,8 2,2 " />`,
|
|
name: `NOTE`,
|
|
},
|
|
{ id: `todo`, svg: `<path d=" M2,6 L4,8 L8,2"/>`, name: `TODO` },
|
|
{ id: `map`, svg: `<path d="M2,8 L2,2 L8,2 L8,8 L4,8 L4,4 L6,4 L6,6 "/>`, name: `MAP` },
|
|
{
|
|
id: `people`,
|
|
svg: `<path d="M2,2 L6,2 L8,6 L7,6 L7,8 L2,8 L2,2 " /> `,
|
|
name: `PEOPLE`,
|
|
},
|
|
{
|
|
id: `cal`,
|
|
svg: `<polygon points="2,3 3,6 3,8 7,8 7,6 8,3 6,8 5,8 " />
|
|
<line x1="6" y1="3" x2="6" y2="3" />
|
|
<line x1="4" y1="2" x2="4" y2="2" />
|
|
<line x1="5" y1="5" x2="5" y2="5" />
|
|
<rect x="3" y="6" width="4" height="2" />`,
|
|
name: `CAL`,
|
|
},
|
|
{
|
|
id: `block`,
|
|
svg: `<circle cx="5" cy="5" r="3" fill="none" /> <path d="M3,3 L7,7" />`,
|
|
name: `BLOCK`,
|
|
},
|
|
{
|
|
id: `conway`,
|
|
svg: `<circle cx="5" cy="2.45" r="0.85" fill="currentColor" stroke="none" />
|
|
<circle cx="7.55" cy="5" r="0.85" fill="currentColor" stroke="none" />
|
|
<circle cx="2.45" cy="7.55" r="0.85" fill="currentColor" stroke="none" />
|
|
<circle cx="5" cy="7.55" r="0.85" fill="currentColor" stroke="none" />
|
|
<circle cx="7.55" cy="7.55" r="0.85" fill="currentColor" stroke="none" />`,
|
|
name: `CONWAY`,
|
|
},
|
|
{
|
|
id: `slide-show`,
|
|
svg: `<circle cx="2" cy="2" r="1.3" fill="currentColor" stroke="none"/>
|
|
<circle cx="5" cy="2" r="1.3" fill="currentColor" stroke="none"/>
|
|
<circle cx="8" cy="2" r="1.3" fill="currentColor" stroke="none"/>
|
|
<circle cx="2" cy="5" r="1.3" fill="currentColor" stroke="none"/>
|
|
<circle cx="5" cy="5" r="1.3" fill="currentColor" stroke="none"/>
|
|
<circle cx="8" cy="5" r="1.3" fill="currentColor" stroke="none"/>
|
|
<circle cx="2" cy="8" r="1.3" fill="currentColor" stroke="none"/>
|
|
<circle cx="5" cy="8" r="1.3" fill="currentColor" stroke="none"/>
|
|
<circle cx="8" cy="8" r="1.3" fill="currentColor" stroke="none"/>`,
|
|
name: `SLIDES`,
|
|
},
|
|
{
|
|
id: `blobs`,
|
|
|
|
// svg: `<rect fill="none" x="2" y="2" width="6" height="6" />`,
|
|
svg: `<rect fill="none" x="4" y="4" width="2" height="2" />`,
|
|
name: `BLOBS`,
|
|
},
|
|
{ id: `ai`, svg: `<rect x="2" y="2" width="6" height="6" rx="1" transform="rotate(90,5,5)"/><path d="M4 4h2M4 6h2" transform="rotate(90,5,5)"/>`, name: `AI` },
|
|
{ id: `html-tv`, svg: `<rect x="2" y="3" width="6" height="4" rx="1"/><path d="M3.5 5l.7.7-.7.7M6.5 5l-.7.7.7.7"/>`, name: `HTML TV` },
|
|
{ id: `skills-edit`, svg: `<path d="M5 2l3 6H2z"/>`, name: `SKILLS EDIT` },
|
|
{ id: `cashu`, svg: `<ellipse cx="5" cy="3" rx="2.5" ry="1"/><ellipse cx="5" cy="5" rx="2.5" ry="1"/><ellipse cx="5" cy="7" rx="2.5" ry="1"/>`, name: `CASHU` },
|
|
{ id: `didactyl`, svg: `<path stroke-width="3.98" d="M3,3 L3,7 L7,7 L7,3" />`, name: `AGENT` },
|
|
{ id: `strudel`, svg: `<circle cx="4" cy="7" r="1" /><line x1="5" y1="7" x2="5" y2="2" /><path d="M5,2 C6,2 7,3 7,4" />`, name: `STRUDEL` },
|
|
{ id: `music`, svg: `<path d="M2,6 C2,3 3,2 5,2 C7,2 8,3 8,6" /><rect x="2" y="6" width="1" height="2" /><rect x="7" y="6" width="1" height="2" />`, name: `MUSIC` },
|
|
{ id: `keep-alive`, svg: `<path d="M2 5a3 3 0 1 0 6 0 3 3 0 1 0-6 0M2 5h6M5 2v6"/>`, name: `KEEP ALIVE` },
|
|
{ id: `vj`, svg: `<line x1="3" y1="8" x2="3" y2="4" /><line x1="4.5" y1="8" x2="4.5" y2="2" /><line x1="6" y1="8" x2="6" y2="5" /><line x1="7.5" y1="8" x2="7.5" y2="3" />`, name: `VJ` },
|
|
{ id: `ai-tv`, svg: `<rect x="2" y="2" width="6" height="6" rx="1"/><path d="M5 2v6M2 5h6"/>`, name: `AI TV` },
|
|
];
|
|
|
|
/* ================================================================
|
|
HAMBURGER MENU
|
|
================================================================ */
|
|
function initHamburgerMenu() {
|
|
hamburgerInstance = new HamburgerMorphing('#divSvgHam', {
|
|
foreground: 'var(--primary-color)',
|
|
background: 'var(--secondary-color)',
|
|
hover: 'var(--accent-color)'
|
|
});
|
|
hamburgerInstance.animateTo('burger');
|
|
}
|
|
|
|
/* ================================================================
|
|
SIDENAV FUNCTIONS
|
|
================================================================ */
|
|
function openNav() {
|
|
divSideNav.style.zIndex = 3;
|
|
divSideNav.style.width = "clamp(400px, 50vw, 600px)";
|
|
isNavOpen = true;
|
|
if (hamburgerInstance) {
|
|
hamburgerInstance.animateTo('arrow_left');
|
|
}
|
|
divSideNavBody.innerHTML = "Settings and options coming soon...";
|
|
|
|
// Initialize version bar buttons when sidenav opens (lazy load)
|
|
if (!logoutHamburger) {
|
|
logoutHamburger = new HamburgerMorphing('#logoutHamburgerContainer', {
|
|
size: 24,
|
|
foreground: 'var(--primary-color)',
|
|
background: 'var(--secondary-color)',
|
|
hover: 'var(--accent-color)'
|
|
});
|
|
logoutHamburger.animateTo('x');
|
|
}
|
|
|
|
if (!themeToggleHamburger) {
|
|
themeToggleHamburger = new HamburgerMorphing('#themeToggleHamburgerContainer', {
|
|
size: 24,
|
|
foreground: 'var(--primary-color)',
|
|
background: 'var(--secondary-color)',
|
|
hover: 'var(--accent-color)'
|
|
});
|
|
|
|
// Determine current theme
|
|
const savedTheme = localStorage.getItem('theme');
|
|
isDarkMode = savedTheme === 'dark' || document.body.classList.contains('dark-mode');
|
|
const initialShape = isDarkMode ? 'moon' : 'circle';
|
|
themeToggleHamburger.animateTo(initialShape);
|
|
}
|
|
}
|
|
|
|
function closeNav() {
|
|
divSideNav.style.width = "0vw";
|
|
divSideNav.style.zIndex = -1;
|
|
isNavOpen = false;
|
|
if (hamburgerInstance) {
|
|
hamburgerInstance.animateTo('burger');
|
|
}
|
|
}
|
|
|
|
function toggleNav() {
|
|
if (isNavOpen) {
|
|
closeNav();
|
|
} else {
|
|
openNav();
|
|
}
|
|
}
|
|
|
|
/* ================================================================
|
|
LOAD APP SCREEN
|
|
================================================================ */
|
|
const LoadAppScreen = async (providedPubkey = null) => {
|
|
const xmlns = "http://www.w3.org/2000/svg";
|
|
|
|
divBody.innerHTML = "";
|
|
|
|
const pubkey = providedPubkey || currentPubkey || null;
|
|
|
|
let divTitle = document.createElement(`div`);
|
|
divTitle.setAttribute(`id`, `divNpubTitle`);
|
|
divTitle.innerHTML = "NPUB";
|
|
|
|
let div = document.createElement(`a`);
|
|
div.setAttribute(`id`, `divNpub`);
|
|
div.setAttribute(`class`, `divAppButtons`);
|
|
|
|
if (pubkey) {
|
|
const computedTheme = getComputedStyle(document.documentElement);
|
|
const primaryColor = computedTheme.getPropertyValue('--primary-color').trim() || '#000000';
|
|
const secondaryColor = computedTheme.getPropertyValue('--secondary-color').trim() || '#ffffff';
|
|
const svgNpub = QRCode({ msg: pubkey, dim: 170, ecl: 'L', pal: [primaryColor, secondaryColor] });
|
|
svgNpub.style.background = secondaryColor;
|
|
svgNpub.style.padding = '6px';
|
|
svgNpub.style.borderRadius = '8px';
|
|
div.setAttribute(`href`, `./npub.html?npub=${pubkey}`);
|
|
div.setAttribute(`target`, `_blank`);
|
|
div.setAttribute(`rel`, `noopener noreferrer`);
|
|
div.appendChild(svgNpub);
|
|
div.appendChild(divTitle);
|
|
}
|
|
divBody.appendChild(div);
|
|
|
|
let htmlOut = "";
|
|
for (let Each of arrApps) {
|
|
const url = Each.id === 'feed'
|
|
? './feed.html'
|
|
: Each.id === 'view'
|
|
? './post.html?follows=true&post=false&viewed=true'
|
|
: Each.id === 'love'
|
|
? './notifications.html'
|
|
: Each.id === 'event'
|
|
? './event-management.html'
|
|
: `./${Each.id}.html`;
|
|
htmlOut += `<a id="div${Each.id}" class="divAppButtons" href="${url}" target="_blank" rel="noopener noreferrer">
|
|
<svg id="${Each.id}" class="svgAppButtons" viewBox="0 0 10 10" >
|
|
${Each.svg}
|
|
</svg>
|
|
${Each.name}
|
|
</a>`;
|
|
}
|
|
|
|
divBody.innerHTML = divBody.innerHTML + htmlOut;
|
|
divBody.style.justifyContent = `space-around`;
|
|
divBody.style.alignContent = `space-around`;
|
|
divBody.style.alignItems = ``;
|
|
divBody.style.height = "";
|
|
};
|
|
|
|
/* ================================================================
|
|
UPDATE FOOTER
|
|
================================================================ */
|
|
const UpdateFooter = async () => {
|
|
try {
|
|
// Update relay status visuals in footer and sidenav
|
|
await updateFooterRelayStatus();
|
|
await updateSidenavRelaySection();
|
|
|
|
await updateBlossomSection();
|
|
// Keep startup/wallet status visible in footer center while active
|
|
const now = Date.now();
|
|
if (now < walletFooterStatusUntil && walletFooterStatusMessage) {
|
|
divFooterCenter.textContent = walletFooterStatusMessage;
|
|
} else if (now < startupStatusUntil && startupStatusMessage) {
|
|
divFooterCenter.textContent = startupStatusMessage;
|
|
} else {
|
|
divFooterCenter.innerHTML = '';
|
|
}
|
|
divFooterRight.innerHTML = '';
|
|
} catch (error) {
|
|
console.error('[index.html] Error updating footer:', error);
|
|
}
|
|
};
|
|
|
|
/* ================================================================
|
|
LOGOUT
|
|
================================================================ */
|
|
const Logout = async () => {
|
|
console.log("[index.html] Starting logout process...");
|
|
|
|
// Stop the update loop
|
|
if (updateIntervalId) {
|
|
clearInterval(updateIntervalId);
|
|
updateIntervalId = null;
|
|
}
|
|
|
|
// Disconnect from worker
|
|
disconnect();
|
|
|
|
// Logout from nostr-login-lite
|
|
if (window.NOSTR_LOGIN_LITE && window.NOSTR_LOGIN_LITE.logout) {
|
|
await window.NOSTR_LOGIN_LITE.logout();
|
|
}
|
|
|
|
// Clear all storage
|
|
localStorage.clear();
|
|
sessionStorage.clear();
|
|
|
|
// Clear IndexedDB
|
|
if (window.indexedDB) {
|
|
const databases = await window.indexedDB.databases();
|
|
for (const db of databases) {
|
|
if (db.name) {
|
|
window.indexedDB.deleteDatabase(db.name);
|
|
}
|
|
}
|
|
}
|
|
|
|
console.log("[index.html] Logged out, reloading page");
|
|
location.reload(true);
|
|
};
|
|
|
|
/* ================================================================
|
|
EVENT LISTENERS
|
|
================================================================ */
|
|
|
|
/* ================================================================
|
|
INITIALIZATION
|
|
================================================================ */
|
|
(async function main() {
|
|
console.log("[index.html] Starting initialization...");
|
|
|
|
try {
|
|
// Initialize hamburger menu first
|
|
initHamburgerMenu();
|
|
|
|
// Add click handler to hamburger
|
|
const divSvgHam = document.getElementById('divSvgHam');
|
|
if (divSvgHam) {
|
|
divSvgHam.addEventListener('click', toggleNav);
|
|
}
|
|
|
|
// Initialize version bar buttons
|
|
const themeToggleButton = document.getElementById('themeToggleButton');
|
|
const logoutButton = document.getElementById('logoutButton');
|
|
|
|
if (themeToggleButton) {
|
|
themeToggleButton.addEventListener('click', () => {
|
|
isDarkMode = !isDarkMode;
|
|
localStorage.setItem('theme', isDarkMode ? 'dark' : 'light');
|
|
document.documentElement.classList.toggle('dark-mode', isDarkMode);
|
|
document.body.classList.toggle('dark-mode', isDarkMode);
|
|
if (themeToggleHamburger) {
|
|
themeToggleHamburger.animateTo(isDarkMode ? 'moon' : 'circle');
|
|
}
|
|
LoadAppScreen(currentPubkey);
|
|
});
|
|
}
|
|
|
|
if (logoutButton) {
|
|
logoutButton.addEventListener('click', async () => {
|
|
try {
|
|
await Logout();
|
|
} catch (error) {
|
|
console.error('Logout failed:', error);
|
|
}
|
|
});
|
|
}
|
|
|
|
// Render app shell immediately so worker startup does not block first paint
|
|
await LoadAppScreen();
|
|
|
|
// Initialize NDK (handles authentication automatically)
|
|
await initNDKPage();
|
|
|
|
// Get authenticated pubkey
|
|
currentPubkey = await getPubkey();
|
|
await injectHeaderAvatar(currentPubkey);
|
|
console.log("[index.html] Authenticated as:", currentPubkey);
|
|
|
|
// Initialize relay UI components
|
|
initFooterRelayStatus();
|
|
initSidenavRelaySection();
|
|
|
|
await initBlossomSection();
|
|
initAiSectionWithLocalConfig();
|
|
|
|
// Listen for relay activity broadcasts from worker
|
|
window.addEventListener('ndkRelayActivity', (event) => {
|
|
const { relayUrl, activity, stats } = event.detail;
|
|
console.log(`[index.html] Relay activity: ${relayUrl} - ${activity}`, stats);
|
|
setRelayActivityState(relayUrl, activity);
|
|
});
|
|
|
|
// Show startup progress messages from worker in center footer section
|
|
window.addEventListener('ndkStartupStatus', (event) => {
|
|
const msg = String(event?.detail?.message || '').trim();
|
|
if (!msg) return;
|
|
console.log(`[index.html] Startup status: ${msg}`, event?.detail || {});
|
|
startupStatusMessage = msg;
|
|
startupStatusUntil = Date.now() + 12000;
|
|
divFooterCenter.textContent = msg;
|
|
});
|
|
|
|
// Show wallet balance/status updates from worker in center footer section
|
|
window.addEventListener('ndkWalletBalance', (event) => {
|
|
const balance = Number(event?.detail?.balance || 0);
|
|
const msg = `Wallet balance: ${balance.toLocaleString()} sats`;
|
|
walletFooterStatusMessage = msg;
|
|
walletFooterStatusUntil = Date.now() + 12000;
|
|
console.log(`[index.html] ${msg}`, event?.detail || {});
|
|
divFooterCenter.textContent = msg;
|
|
});
|
|
|
|
window.addEventListener('ndkWalletStatus', (event) => {
|
|
const status = String(event?.detail?.status || '').trim();
|
|
if (!status) return;
|
|
const msg = `Wallet status: ${status}`;
|
|
walletFooterStatusMessage = msg;
|
|
walletFooterStatusUntil = Date.now() + 12000;
|
|
console.log(`[index.html] ${msg}`, event?.detail || {});
|
|
divFooterCenter.textContent = msg;
|
|
});
|
|
|
|
// Hydrate app screen with authenticated data (NPUB QR)
|
|
await LoadAppScreen(currentPubkey);
|
|
|
|
|
|
// Start update loop (updates footer every second)
|
|
updateIntervalId = setInterval(UpdateFooter, 1000);
|
|
|
|
// Update version display
|
|
await updateVersionDisplay();
|
|
|
|
console.log('[index.html] Initialization complete');
|
|
} catch (error) {
|
|
console.error('[index.html] Initialization failed:', error);
|
|
divBody.innerHTML = `<div style="text-align: center; padding: 50px;">
|
|
<div style="font-size: 24px; margin-bottom: 20px; color: red;">❌ Authentication Error</div>
|
|
<div style="font-size: 16px; color: #666;">${error.message}</div>
|
|
<div style="margin-top: 20px;">
|
|
<button onclick="location.reload()" style="padding: 10px 20px; font-size: 16px;">Retry</button>
|
|
</div>
|
|
</div>`;
|
|
}
|
|
})();
|
|
</script>
|
|
</body>
|
|
|
|
</html> |