Merge remote-tracking branch 'origin/ditorodev' into ditorodev

This commit is contained in:
Derrick Hammer 2023-11-04 03:23:25 -04:00
commit d43436382f
Signed by: pcfreak30
GPG Key ID: C997C339BE476FF2
5 changed files with 243 additions and 31 deletions

View File

@ -25,26 +25,40 @@ const App: React.FC = () => {
return ( return (
<header className="relative border rounded-md m-4 border-neutral-800 px-3 py-4 w-[calc(100%-32px)] bg-neutral-900 flex flex-col"> <header className="relative border rounded-md m-4 border-neutral-800 px-3 py-4 w-[calc(100%-32px)] bg-neutral-900 flex flex-col">
<div className="relative px-2 my-2 mb-4 pl-2 pb-2 w-full flex justify-between"> <div className="relative px-2 my-2 mb-4 pl-2 pb-2 w-full flex justify-between">
<div className="flex items-center gap-x-2 text-zinc-500"> <a href="https://lumeweb.com">
<img src={LogoImg.src} className="w-20 h-7" /> <div className="flex items-center gap-x-2 text-zinc-500">
<h2 className="border-l border-current pl-2">Web3 Browser</h2> <img src={LogoImg.src} className="w-20 h-7" />
</div> <h2 className="border-l border-current pl-2">Web3 Browser</h2>
</div>
</a>
<div className="w-32 flex justify-end h-10"> <div className="w-32 flex justify-end h-10">
<Lume /> <Lume />
</div> </div>
</div> </div>
<Navigator /> <Navigator />
{ethStatus?.syncState === "syncing" || {true || ethStatus?.syncState === "syncing" ||
handshakeStatus?.syncState === "syncing" ? ( handshakeStatus?.syncState === "syncing" ? (
<div className="py-4 -mb-4 flex flex-row gap-x-3"> <div className="py-4 -mb-4 flex flex-row gap-x-3">
{ethStatus?.syncState === "syncing" ? ( {ethStatus?.syncState === "syncing" ? (
<span className="rounded-full bg-neutral-800 text-white p-1 px-4"> <span className="flex items-center gap-x-2 rounded-full bg-neutral-800 text-white p-1 px-4 bg">
<span className="font-mono mr-2 font-bold">{ethStatus.sync.toFixed(0)}%</span> Syncing Ethereum Network <CircleProgressBar radius={5} strokeWidth={3} percentage={Math.ceil(ethStatus.sync)} />
<span className="font-bold font-mono text-orange-400 mr-2">{ethStatus.sync.toFixed(1)}%</span> Syncing Ethereum Network
</span>
) : ethStatus?.syncState === "done" ? (
<span className="flex items-center gap-x-2 rounded-full bg-neutral-800 text-white p-1 px-4 bg">
<CircleProgressBar radius={5} strokeWidth={3} percentage={100} />
{" "} Ethereum Synced
</span> </span>
) : null} ) : null}
{handshakeStatus?.syncState === "syncing" ? ( {handshakeStatus?.syncState === "syncing" ? (
<span className="rounded-full bg-neutral-800 text-white p-1 px-4"> <span className="flex items-center gap-x-2 rounded-full bg-neutral-800 text-white p-1 px-4 bg">
<span className="font-bold font-mono mr-2">{handshakeStatus.sync.toFixed(1)}%</span> Syncing Handshake Network <CircleProgressBar radius={5} strokeWidth={3} percentage={Math.ceil(handshakeStatus.sync)} />
<span className="font-bold font-mono text-orange-400 mr-2">{handshakeStatus.sync.toFixed(1)}%</span> Syncing Ethereum Network
</span>
) : handshakeStatus?.syncState === "done" ? (
<span className="flex items-center gap-x-2 rounded-full bg-neutral-800 text-white p-1 px-4 bg">
<CircleProgressBar radius={5} strokeWidth={3} percentage={100} />
{" "} Handshake Synced
</span> </span>
) : null} ) : null}
</div> </div>
@ -53,6 +67,46 @@ const App: React.FC = () => {
); );
}; };
const CircleProgressBar = ({ radius, strokeWidth, textSize, percentage } : {radius: number, strokeWidth: number, textSize?: number, percentage: number}) => {
const circumference = 2 * Math.PI * radius;
const offset = circumference - (percentage / 100) * circumference;
const color = Math.ceil(percentage) >= 100 ? "green-500" : "orange-400"
return (
<svg width={radius * 2 + strokeWidth} height={radius * 2 + strokeWidth}>
<circle
className="stroke-neutral-700"
fill="transparent"
r={radius}
cx={radius + strokeWidth / 2}
cy={radius + strokeWidth / 2}
strokeWidth={strokeWidth}
/>
<circle
className={`stroke-${color}`}
fill="transparent"
r={radius}
cx={radius + strokeWidth / 2}
cy={radius + strokeWidth / 2}
strokeWidth={strokeWidth}
strokeDasharray={circumference}
strokeDashoffset={offset}
strokeLinecap="round"
/>
{textSize ? <text
x="50%"
className={`fill-${color}`}
y="50%"
textAnchor="middle"
dy=".3em"
fontSize={textSize}
>
{`${percentage}%`}
</text> : null }
</svg>
);
};
const Root = () => { const Root = () => {
return ( return (
<BrowserStateProvider> <BrowserStateProvider>

View File

@ -29,6 +29,7 @@ import {
useAuth, useAuth,
useLumeStatus, useLumeStatus,
} from "@lumeweb/sdk"; } from "@lumeweb/sdk";
import StartPage from "./StartPage";
let BOOT_FUNCTIONS: (() => Promise<any>)[] = []; let BOOT_FUNCTIONS: (() => Promise<any>)[] = [];
@ -163,22 +164,29 @@ const NavInput = forwardRef<HTMLInputElement>(
}, },
); );
function parseUrl(url: string) {
let input = url.trim();
// If the input doesn't contain a protocol, assume it's http
if (!input?.match(/^https?:\/\//)) {
input = `http://${input}`;
}
return new URL(input);
}
export function Navigator() { export function Navigator() {
const { url: contextUrl, setUrl } = useBrowserState(); const { url: contextUrl, setUrl } = useBrowserState();
const { ready } = useLumeStatus(); const { ready } = useLumeStatus();
const inputEl = useRef<HTMLInputElement | null>(); const inputEl = useRef<HTMLInputElement | null>();
const browse = (inputValue: string) => { const browse = (inputValue: string) => {
let input = inputValue.trim();
// If the input doesn't contain a protocol, assume it's http
if (!input?.match(/^https?:\/\//)) {
input = `http://${input}`;
}
try { try {
if(inputValue === "") {
setUrl("about:blank")
}
// Try to parse it as a URL // Try to parse it as a URL
const url = new URL(input); const url = parseUrl(inputValue);
setUrl(url.toString() || "about:blank"); setUrl(url.toString() || "about:blank");
} catch (e) { } catch (e) {
@ -248,7 +256,7 @@ export function Browser() {
); );
}, []); }, []);
const handleIframeLoad = () => { const handleIframeLoad = (event: React.SyntheticEvent<HTMLIFrameElement, Event>) => {
try { try {
const newUrl = iframeRef?.current?.contentWindow?.location.href as string; const newUrl = iframeRef?.current?.contentWindow?.location.href as string;
const urlObj = new URL(newUrl); const urlObj = new URL(newUrl);
@ -258,7 +266,11 @@ export function Browser() {
if (url !== realUrl) { if (url !== realUrl) {
setUrl(realUrl); setUrl(realUrl);
} }
setIsLoadingPage(false); const readyState = event.currentTarget.contentDocument?.readyState;
console.log("[debug]",{readyState});
if(readyState === 'interactive') {
setIsLoadingPage(false);
}
} catch (e) { } catch (e) {
// This will catch errors related to cross-origin requests, in which case we can't access the iframe's contentWindow.location // This will catch errors related to cross-origin requests, in which case we can't access the iframe's contentWindow.location
console.warn( console.warn(
@ -273,6 +285,7 @@ export function Browser() {
if (iframe) { if (iframe) {
const observer = new MutationObserver((mutationsList, observer) => { const observer = new MutationObserver((mutationsList, observer) => {
for (let mutation of mutationsList) { for (let mutation of mutationsList) {
console.log("[debug] Mutated ", {mutation})
if ( if (
mutation.type === "attributes" && mutation.type === "attributes" &&
mutation.attributeName === "src" mutation.attributeName === "src"
@ -287,6 +300,8 @@ export function Browser() {
} }
}, []); }, []);
const shouldRenderStartPage = !url || url === "about:blank";
return ( return (
<> <>
{isLoadingPage ? ( {isLoadingPage ? (
@ -296,12 +311,21 @@ export function Browser() {
</span> </span>
</div> </div>
) : null} ) : null}
<iframe {shouldRenderStartPage ? (
ref={iframeRef} <StartPage
onLoad={handleIframeLoad} setUrl={(url) => {
src={url ? `/browse/${url}` : "about:blank"} const _url = parseUrl(url);
className="w-full h-full" setUrl(_url.toString() || "about:blank");
></iframe> }}
/>
) : null}
<iframe
ref={iframeRef}
onLoad={handleIframeLoad}
src={url ? `/browse/${url}` : "about:blank"}
className={`${shouldRenderStartPage ? "hidden": ""} w-full h-full`}
></iframe>
</> </>
); );
} }

View File

@ -6,7 +6,7 @@ import {
useLumeStatus, useLumeStatus,
} from "@lumeweb/sdk"; } from "@lumeweb/sdk";
const Lume: React.FC = () => { const Lume: React.FC = () => {
const { isLoggedIn } = useAuth(); const { isLoggedIn } = useAuth();
const { ready, inited } = useLumeStatus(); const { ready, inited } = useLumeStatus();
@ -14,21 +14,19 @@ const Lume: React.FC = () => {
<> <>
{!isLoggedIn && ( {!isLoggedIn && (
<LumeIdentity> <LumeIdentity>
<LumeIdentityTrigger asChild disabled={!inited}> <LumeIdentityTrigger asChild>
{
<button <button
className="ml-2 w-full rounded-full bg-[hsl(113,49%,55%)] text-black disabled:pointer-events-none disabled:opacity-50" className="ml-2 w-full rounded-full bg-[hsl(113,49%,55%)] text-black disabled:pointer-events-none disabled:opacity-50"
disabled={!inited} disabled={!inited}
> >
Login Login
</button> </button>
}
</LumeIdentityTrigger> </LumeIdentityTrigger>
</LumeIdentity> </LumeIdentity>
)} )}
{isLoggedIn && <LumeDashboard disabled={!ready} />} {isLoggedIn && <LumeDashboard disabled={!ready} />}
</> </>
); );
} };
export default Lume; export default Lume;

View File

@ -0,0 +1,135 @@
import { useAuth, useLumeStatus } from "@lumeweb/sdk";
import React from "react";
type Props = {
setUrl: (url: string) => void;
};
const AVAILABLE_PAGES = [
"blockranger.eth",
"esteroids.eth",
"ens.eth",
"sogola.eth",
"vitalik.eth",
];
const StartPage = ({ setUrl }: Props) => {
const { ready, inited } = useLumeStatus();
const { isLoggedIn } = useAuth();
return (
<div className="mx-4 relative border rounded-md mt-2 border-neutral-800 p-10 w-[calc(100%-32px)] bg-neutral-900 flex flex-col">
<h2 className="font-bold text-2xl text-white">
Welcome to the Lume Browser
</h2>
<p className="text-gray-400 my-4">
This browser will let you trustessly access websites with domain names
from the Ethereum Name Service (ENS) and Handshake protocol, providing a
secure and decentralized browsing experience.
</p>
{/* TODO: Add the lume loading indicators for the networks. */}
{/* <CircleProgressBar radius={20} strokeWidth={4} textSize={12} percentage={75} /> */}
{inited && ready ? (
<div>
<hr className="my-3 border-neutral-700" />
<h3 className="text-white text-lg font-bold mt-4">
Currently Accessible Websites:
</h3>
<p className="text-gray-400 my-4">
To come back to the roots of the web, we have to change a lot of
behavior on how browsers resolve assets and make them safe by
checking their hashes in a trustlessly way. The sites listed here
are the ones we've successfully integrated with our technology.
We're working on complex tasks to ensure that file serving is
trustless and decentralized, which involves reimplementing many
functionalities that current DNSs and CDNs already provide.
</p>
<ul className="flex gap-2 flex-row flex-wrap py-3">
{AVAILABLE_PAGES.map((url, index) => (
<button
key={`AvailableSites_${index}`}
disabled={!ready}
className={`w-[calc(33%-16px)] border rounded-md py-2 text-white ${
ready
? "bg-zinc-900 border-zinc-800 hover:shadow-md hover:ring-1 hover:ring-green-400/20 hover:shadow-green-400/20 hover:transform-gpu hover:-translate-y-[3px] transition-all duration-150"
: "bg-zinc-950 border-zinc-900 cursor-not-allowed opacity-30"
}`}
onClick={() => ready && setUrl(`http://${url}`)}
>
<div className="w-full">{url}</div>
</button>
))}
</ul>
</div>
) : null}
{inited && !ready && isLoggedIn ? (
<div
className="bg-yellow-800/40 rounded-md border border-yellow-500 text-yellow-500 p-4"
role="alert"
>
<p className="font-bold">Be patient</p>
<p>We are starting the engines.</p>
</div>
): null}
{!isLoggedIn ? (
<div
className="bg-blue-800/40 rounded-md border border-blue-500 text-blue-500 p-4"
role="alert"
>
<p className="font-bold">Attention</p>
<p>Please click login to start using the browser.</p>
</div>
) : null}
</div>
);
};
export default StartPage;
const CircleProgressBar = ({
radius,
strokeWidth,
textSize,
percentage,
}: {
radius: number;
strokeWidth: number;
textSize: number;
percentage: number;
}) => {
const circumference = 2 * Math.PI * radius;
const offset = circumference - (percentage / 100) * circumference;
return (
<svg width={radius * 2 + strokeWidth} height={radius * 2 + strokeWidth}>
<circle
className="stroke-neutral-700"
fill="transparent"
r={radius}
cx={radius + strokeWidth / 2}
cy={radius + strokeWidth / 2}
strokeWidth={strokeWidth}
/>
<circle
className="stroke-primary"
fill="transparent"
r={radius}
cx={radius + strokeWidth / 2}
cy={radius + strokeWidth / 2}
strokeWidth={strokeWidth}
strokeDasharray={circumference}
strokeDashoffset={offset}
strokeLinecap="round"
/>
<text
x="50%"
className="fill-primary"
y="50%"
textAnchor="middle"
dy=".3em"
fontSize={textSize}
>
{`${percentage}%`}
</text>
</svg>
);
};

View File

@ -1,6 +1,7 @@
/** @type {import('tailwindcss').Config} */ /** @type {import('tailwindcss').Config} */
module.exports = { module.exports = {
darkMode: ["class"], darkMode: ["class"],
safelist: ["fill-green-500", "stroke-green-500", "fill-orange-400", "stroke-orange-400"],
content: ['./src/**/*.{astro,html,js,jsx,md,mdx,svelte,ts,tsx,vue}'], content: ['./src/**/*.{astro,html,js,jsx,md,mdx,svelte,ts,tsx,vue}'],
theme: { theme: {
container: { container: {