From 0ed8f0d80dce9ed1ec3e1ed636925bc934810d58 Mon Sep 17 00:00:00 2001 From: Juan Di Toro Date: Wed, 1 Nov 2023 23:01:55 +0100 Subject: [PATCH] feat: add loading status to the browser --- astro.config.mjs | 34 +++++++---- package.json | 5 +- src/assets/lume-logo.png | Bin 0 -> 2592 bytes src/components/App.tsx | 27 +++++---- src/components/Browser.tsx | 114 ++++++++++++++++++++++++++----------- src/pages/index.astro | 2 +- src/pages/sw.js.ts | 9 +++ tailwind.config.js | 7 +-- 8 files changed, 135 insertions(+), 63 deletions(-) create mode 100644 src/assets/lume-logo.png create mode 100644 src/pages/sw.js.ts 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 0000000000000000000000000000000000000000..0a24ad80ef777350f33dab8041c0dc6557dc1034 GIT binary patch literal 2592 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 { 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,