diff --git a/TODO.md b/TODO.md index d7a8624..c7aaca0 100644 --- a/TODO.md +++ b/TODO.md @@ -3,7 +3,7 @@ ## Known Issues ### RTL Support in Rich Text -**Priority**: Medium +**Priority**: Medium **File**: `src/components/nostr/RichText/Text.tsx` Current RTL implementation is partial and has limitations: @@ -11,7 +11,7 @@ Current RTL implementation is partial and has limitations: - RTL text alignment (right-align) doesn't work properly with inline elements - Mixed LTR/RTL content with inline elements (hashtags, mentions) creates layout conflicts -**The core problem**: +**The core problem**: - Inline elements (hashtags, mentions) need inline flow to stay on same line - RTL alignment requires block-level containers - These two requirements conflict diff --git a/src/components/Home.tsx b/src/components/Home.tsx index 8e342b8..05743ad 100644 --- a/src/components/Home.tsx +++ b/src/components/Home.tsx @@ -1,21 +1,11 @@ import { useState, useEffect } from "react"; import { useGrimoire } from "@/core/state"; import { useAccountSync } from "@/hooks/useAccountSync"; -import Feed from "./nostr/Feed"; -import { WinViewer } from "./WinViewer"; -import { WindowToolbar } from "./WindowToolbar"; import { TabBar } from "./TabBar"; import { Mosaic, MosaicWindow, MosaicBranch } from "react-mosaic-component"; -import { NipRenderer } from "./NipRenderer"; -import ManPage from "./ManPage"; import CommandLauncher from "./CommandLauncher"; -import ReqViewer from "./ReqViewer"; -import { EventDetailViewer } from "./EventDetailViewer"; -import { ProfileViewer } from "./ProfileViewer"; -import EncodeViewer from "./EncodeViewer"; -import DecodeViewer from "./DecodeViewer"; -import { RelayViewer } from "./RelayViewer"; -import KindRenderer from "./KindRenderer"; +import { WindowToolbar } from "./WindowToolbar"; +import { WindowTile } from "./WindowTitle"; import { Terminal } from "lucide-react"; import UserMenu from "./nostr/user-menu"; import { GrimoireWelcome } from "./GrimoireWelcome"; @@ -62,68 +52,13 @@ export default function Home() { ); } - // Render based on appId - let content; - switch (window.appId) { - case "nip": - content = ; - break; - case "feed": - content = ; - break; - case "win": - content = ; - break; - case "kind": - content = ; - break; - case "man": - content = ; - break; - case "req": - content = ( - - ); - break; - case "open": - content = ; - break; - case "profile": - content = ; - break; - case "encode": - content = ; - break; - case "decode": - content = ; - break; - case "relay": - content = ; - break; - default: - content = ( -
- Unknown app: {window.appId} -
- ); - } - return ( - handleRemoveWindow(id)} /> - } - > -
{content}
-
+ onClose={handleRemoveWindow} + /> ); }; diff --git a/src/components/WindowRenderer.tsx b/src/components/WindowRenderer.tsx new file mode 100644 index 0000000..e2b9278 --- /dev/null +++ b/src/components/WindowRenderer.tsx @@ -0,0 +1,166 @@ +import { Component, ReactNode } from "react"; +import { AlertCircle } from "lucide-react"; +import { Button } from "@/components/ui/button"; +import { WindowInstance } from "@/types/app"; +import { NipRenderer } from "./NipRenderer"; +import ManPage from "./ManPage"; +import ReqViewer from "./ReqViewer"; +import { EventDetailViewer } from "./EventDetailViewer"; +import { ProfileViewer } from "./ProfileViewer"; +import EncodeViewer from "./EncodeViewer"; +import DecodeViewer from "./DecodeViewer"; +import { RelayViewer } from "./RelayViewer"; +import KindRenderer from "./KindRenderer"; +import Feed from "./nostr/Feed"; +import { WinViewer } from "./WinViewer"; + +interface WindowRendererProps { + window: WindowInstance; + onClose: () => void; +} + +interface WindowErrorBoundaryState { + hasError: boolean; + error?: Error; +} + +class WindowErrorBoundary extends Component< + { children: ReactNode; windowTitle: string; onClose: () => void }, + WindowErrorBoundaryState +> { + constructor(props: { + children: ReactNode; + windowTitle: string; + onClose: () => void; + }) { + super(props); + this.state = { hasError: false }; + } + + static getDerivedStateFromError(error: Error): WindowErrorBoundaryState { + return { hasError: true, error }; + } + + componentDidCatch(error: Error, errorInfo: React.ErrorInfo) { + console.error( + `Window "${this.props.windowTitle}" crashed:`, + error, + errorInfo, + ); + } + + render() { + if (this.state.hasError) { + return ( +
+
+
+ +
+

+ Window Crashed +

+

+ {this.state.error?.message || + "An unexpected error occurred in this window."} +

+ +
+
+
+
+ ); + } + + return this.props.children; + } +} + +export function WindowRenderer({ window, onClose }: WindowRendererProps) { + let content: ReactNode; + + try { + switch (window.appId) { + case "nip": + content = ; + break; + case "feed": + content = ; + break; + case "win": + content = ; + break; + case "kind": + content = ; + break; + case "man": + content = ; + break; + case "req": + content = ( + + ); + break; + case "open": + content = ; + break; + case "profile": + content = ; + break; + case "encode": + content = ; + break; + case "decode": + content = ; + break; + case "relay": + content = ; + break; + default: + content = ( +
+ Unknown app: {window.appId} +
+ ); + } + } catch (error) { + content = ( +
+
+
+ +
+

+ Failed to render window +

+

+ {error instanceof Error + ? error.message + : "An unexpected error occurred"} +

+
+
+
+
+ ); + } + + return ( + +
{content}
+
+ ); +} diff --git a/src/components/WindowTitle.tsx b/src/components/WindowTitle.tsx new file mode 100644 index 0000000..64a1342 --- /dev/null +++ b/src/components/WindowTitle.tsx @@ -0,0 +1,23 @@ +import { MosaicWindow, MosaicBranch } from "react-mosaic-component"; +import { WindowInstance } from "@/types/app"; +import { WindowToolbar } from "./WindowToolbar"; +import { WindowRenderer } from "./WindowRenderer"; + +interface WindowTileProps { + id: string; + window: WindowInstance; + path: MosaicBranch[]; + onClose: (id: string) => void; +} + +export function WindowTile({ id, window, path, onClose }: WindowTileProps) { + return ( + onClose(id)} />} + > + onClose(id)} /> + + ); +}