diff --git a/astro.config.mjs b/astro.config.mjs index 85512bf..4021635 100644 --- a/astro.config.mjs +++ b/astro.config.mjs @@ -1,21 +1,31 @@ -import { defineConfig } from 'astro/config' +import { defineConfig } from "astro/config"; -import react from '@astrojs/react' -import tailwind from '@astrojs/tailwind' -import optimizer from 'vite-plugin-optimizer' +import react from "@astrojs/react"; +import tailwind from "@astrojs/tailwind"; +import optimizer from "vite-plugin-optimizer"; +import * as fs from "node:fs"; // https://astro.build/config export default defineConfig({ - integrations: [react(), tailwind({ applyBaseStyles: false, })], + integrations: [react(), tailwind({ applyBaseStyles: false })], vite: { - build:{ - minify: false + server: + process.env.MODE === "development" + ? { + https: { + cert: fs.readFileSync("./.local-ssl/localhost.pem"), + key: fs.readFileSync("./.local-ssl/localhost-key.pem"), + }, + } + : {}, + build: { + minify: false, }, plugins: [ optimizer({ - 'node-fetch': - 'const e = undefined; export default e;export {e as Response, e as FormData, e as Blob};', + "node-fetch": + "const e = undefined; export default e;export {e as Response, e as FormData, e as Blob};", }), - ] - } -}) + ], + }, +}); diff --git a/package.json b/package.json index fe57a20..6a64fda 100644 --- a/package.json +++ b/package.json @@ -3,12 +3,13 @@ "type": "module", "version": "0.0.1", "scripts": { - "dev": "astro dev", + "dev": "MODE=development astro dev", "start": "astro dev", "prepare": "presetter bootstrap", "build": "run build", "preview": "astro preview", - "astro": "astro" + "astro": "astro", + "local-setup-https": "mkdir -p ./.local-ssl && mkcert -key-file ./.local-ssl/localhost-key.pem -cert-file ./.local-ssl/localhost.pem localhost" }, "dependencies": { "@astrojs/react": "^3.0.3", diff --git a/src/assets/lume-logo.png b/src/assets/lume-logo.png new file mode 100644 index 0000000..0a24ad8 Binary files /dev/null and b/src/assets/lume-logo.png differ diff --git a/src/components/App.tsx b/src/components/App.tsx index aac363a..6e7d594 100644 --- a/src/components/App.tsx +++ b/src/components/App.tsx @@ -10,26 +10,33 @@ import { Navigator, } from "@/components/Browser.tsx"; import Lume from "@/components/Lume.tsx"; +import LogoImg from "@/assets/lume-logo.png"; const App: React.FC = () => { return ( - - -
- -
+ + +
+
+
+ +

Web3 Browser

+
+
-
+
+ +
- -
-
+ + +
); -} +}; export default App; diff --git a/src/components/Browser.tsx b/src/components/Browser.tsx index 59b3255..2519863 100644 --- a/src/components/Browser.tsx +++ b/src/components/Browser.tsx @@ -20,7 +20,7 @@ import { import * as kernel from "@lumeweb/libkernel/kernel"; import { kernelLoaded } from "@lumeweb/libkernel/kernel"; import Arrow from "@/components/Arrow.tsx"; -import type React from "react"; +import React from "react"; import { Input } from "@/components/ui/input.tsx"; import { Button } from "@/components/ui/button.tsx"; import { @@ -35,6 +35,8 @@ let BOOT_FUNCTIONS: (() => Promise)[] = []; interface BrowserContextType { url: string; setUrl: React.Dispatch>; + isLoadingPage: boolean; + setIsLoadingPage: React.Dispatch>; } const BrowserStateContext = createContext( @@ -47,9 +49,12 @@ export function BrowserStateProvider({ children: React.ReactElement; }) { const [url, setUrl] = useState(""); + const [isLoadingPage, setIsLoadingPage] = useState(false); return ( - + {children} ); @@ -69,18 +74,22 @@ async function boot({ onInit, onAuth, onBoot, -}: {onInit: (inited: boolean) => Promise | void, onAuth: (authed: boolean) => Promise | void, onBoot: (booted: boolean) => Promise | void}) { +}: { + onInit: (inited: boolean) => Promise | void; + onAuth: (authed: boolean) => Promise | void; + onBoot: (booted: boolean) => Promise | void; +}) { const reg = await navigator.serviceWorker.register("/sw.js"); await reg.update(); await kernel.serviceWorkerReady(); await kernel.init().catch((err) => { - console.error("[Browser.tsx] Failed to init kernel", {error: err}); + console.error("[Browser.tsx] Failed to init kernel", { error: err }); }); await onInit(true); await kernelLoaded().catch((err) => { - console.error("[Browser.tsx] Failed to load kernel", {error: err}); + console.error("[Browser.tsx] Failed to load kernel", { error: err }); }); await onAuth(true); @@ -135,14 +144,16 @@ async function bootup() { } } -const NavInput = forwardRef((props: React.HTMLProps, ref) => { - return ; -}); +const NavInput = forwardRef( + (props: React.InputHTMLAttributes, ref) => { + return ; + }, +); export function Navigator() { const { url: contextUrl, setUrl } = useBrowserState(); const { ready } = useLumeStatus(); - const inputEl = useRef(); + const inputEl = useRef(); const browse = (inputValue: string) => { let input = inputValue.trim(); @@ -164,7 +175,7 @@ export function Navigator() { }; useEffect(() => { - if(inputEl.current) { + if (inputEl.current) { inputEl.current.value = contextUrl; } }, [contextUrl]); @@ -172,21 +183,29 @@ export function Navigator() { console.log("Navigator mounted"); return ( -
{ - e.preventDefault(); - const inputElement = e.target as HTMLFormElement; - const inputValue = inputElement?.elements.namedItem('url')?.value; - if (inputValue) { - browse(inputValue) - } - }} className="relative h-full w-full rounded-full bg-neutral-800 border border-neutral-700 flex items-center [&>input:focus]:ring-2 [&>input:focus]:ring-primary [&>input:focus+button]:ring-2 [&>input:focus+button]:ring-primary"> + { + e.preventDefault(); + const inputElement = e.target as HTMLFormElement; + const inputValue = ( + inputElement?.elements.namedItem("url") as HTMLInputElement + )?.value; + if (inputValue) { + browse(inputValue); + } + }} + className="relative h-full w-full rounded-full bg-neutral-800 border border-neutral-700 flex items-center [&>input:focus]:ring-2 [&>input:focus]:ring-primary [&>input:focus+button]:ring-2 [&>input:focus+button]:ring-primary" + > (inputEl.current = el)} disabled={!ready} className="rounded-l-full border-none focus-visible:ring-offset-0" name="url" /> - @@ -195,7 +214,7 @@ export function Navigator() { } export function Browser() { - const { url, setUrl } = useBrowserState(); + const { url, setUrl, isLoadingPage, setIsLoadingPage } = useBrowserState(); const status = useLumeStatus(); const auth = useAuth(); const iframeRef = useRef(null); @@ -203,15 +222,17 @@ export function Browser() { useEffect(() => { boot({ onAuth(authed) { - auth.setIsLoggedIn(authed) + auth.setIsLoggedIn(authed); }, onBoot(booted) { - status.setReady(booted) + status.setReady(booted); }, onInit(inited) { - status.setInited(inited) - } - }).catch((err) => console.error("[Browser.tsx] Failed to Boot Lume", {error: err})); + status.setInited(inited); + }, + }).catch((err) => + console.error("[Browser.tsx] Failed to Boot Lume", { error: err }), + ); }, []); const handleIframeLoad = () => { @@ -223,6 +244,7 @@ export function Browser() { .replace(/\/$/, ""); if (url !== realUrl) { setUrl(realUrl); + setIsLoadingPage(false); } } catch (e) { // This will catch errors related to cross-origin requests, in which case we can't access the iframe's contentWindow.location @@ -233,12 +255,40 @@ export function Browser() { } }; + useEffect(() => { + const iframe = iframeRef.current; + if (iframe) { + const observer = new MutationObserver((mutationsList, observer) => { + for (let mutation of mutationsList) { + if ( + mutation.type === "attributes" && + mutation.attributeName === "src" + ) { + setIsLoadingPage(true); + } + } + }); + + observer.observe(iframe, { attributes: true }); + return () => observer.disconnect(); // Clean up on unmount + } + }, []); + return ( - + <> + {isLoadingPage ? ( +
+ + Loading {url}... + +
+ ) : null} + + ); } diff --git a/src/pages/index.astro b/src/pages/index.astro index c6367c7..d652387 100644 --- a/src/pages/index.astro +++ b/src/pages/index.astro @@ -13,7 +13,7 @@ import "@/styles/globals.scss"; Astro -
+
diff --git a/src/pages/sw.js.ts b/src/pages/sw.js.ts new file mode 100644 index 0000000..9a81e24 --- /dev/null +++ b/src/pages/sw.js.ts @@ -0,0 +1,9 @@ +import type { APIRoute } from "astro"; +import * as fs from "node:fs"; +import * as path from "node:path"; + +export const GET: APIRoute = ({params, request}) => { + const filePath = path.resolve(process.cwd(), "dist/sw.js"); + const fileContents = fs.readFileSync(filePath); + return new Response(fileContents, { status: 200, headers: { 'Content-Type': 'application/javascript' } }); +} \ No newline at end of file diff --git a/tailwind.config.js b/tailwind.config.js index 0377ea1..b5bfccc 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -1,12 +1,7 @@ /** @type {import('tailwindcss').Config} */ module.exports = { darkMode: ["class"], - content: [ - './pages/**/*.{ts,tsx}', - './components/**/*.{ts,tsx}', - './app/**/*.{ts,tsx}', - './src/**/*.{ts,tsx}', - ], + content: ['./src/**/*.{astro,html,js,jsx,md,mdx,svelte,ts,tsx,vue}'], theme: { container: { center: true,