mirror of
https://github.com/purrgrammer/grimoire.git
synced 2026-04-09 15:07:10 +02:00
feat: isolate crashes, refactor window rendering
This commit is contained in:
4
TODO.md
4
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
|
||||
|
||||
@@ -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 = <NipRenderer nipId={window.props.number} />;
|
||||
break;
|
||||
case "feed":
|
||||
content = <Feed className="h-full w-full overflow-auto" />;
|
||||
break;
|
||||
case "win":
|
||||
content = <WinViewer />;
|
||||
break;
|
||||
case "kind":
|
||||
content = <KindRenderer kind={parseInt(window.props.number)} />;
|
||||
break;
|
||||
case "man":
|
||||
content = <ManPage cmd={window.props.cmd} />;
|
||||
break;
|
||||
case "req":
|
||||
content = (
|
||||
<ReqViewer
|
||||
filter={window.props.filter}
|
||||
relays={window.props.relays}
|
||||
closeOnEose={window.props.closeOnEose}
|
||||
nip05Authors={window.props.nip05Authors}
|
||||
nip05PTags={window.props.nip05PTags}
|
||||
/>
|
||||
);
|
||||
break;
|
||||
case "open":
|
||||
content = <EventDetailViewer pointer={window.props.pointer} />;
|
||||
break;
|
||||
case "profile":
|
||||
content = <ProfileViewer pubkey={window.props.pubkey} />;
|
||||
break;
|
||||
case "encode":
|
||||
content = <EncodeViewer args={window.props.args} />;
|
||||
break;
|
||||
case "decode":
|
||||
content = <DecodeViewer args={window.props.args} />;
|
||||
break;
|
||||
case "relay":
|
||||
content = <RelayViewer url={window.props.url} />;
|
||||
break;
|
||||
default:
|
||||
content = (
|
||||
<div className="p-4 text-muted-foreground">
|
||||
Unknown app: {window.appId}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<MosaicWindow
|
||||
<WindowTile
|
||||
id={id}
|
||||
window={window}
|
||||
path={path}
|
||||
title={window.title}
|
||||
toolbarControls={
|
||||
<WindowToolbar onClose={() => handleRemoveWindow(id)} />
|
||||
}
|
||||
>
|
||||
<div className="h-full w-full overflow-auto">{content}</div>
|
||||
</MosaicWindow>
|
||||
onClose={handleRemoveWindow}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
166
src/components/WindowRenderer.tsx
Normal file
166
src/components/WindowRenderer.tsx
Normal file
@@ -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 (
|
||||
<div className="p-4">
|
||||
<div className="border border-red-500 bg-red-50 dark:bg-red-950 rounded-md p-4">
|
||||
<div className="flex items-start gap-3">
|
||||
<AlertCircle className="h-5 w-5 text-red-600 dark:text-red-400 flex-shrink-0 mt-0.5" />
|
||||
<div className="flex-1 space-y-2">
|
||||
<h3 className="font-semibold text-red-900 dark:text-red-100">
|
||||
Window Crashed
|
||||
</h3>
|
||||
<p className="text-sm text-red-800 dark:text-red-200">
|
||||
{this.state.error?.message ||
|
||||
"An unexpected error occurred in this window."}
|
||||
</p>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={this.props.onClose}
|
||||
className="mt-2"
|
||||
>
|
||||
Close Window
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return this.props.children;
|
||||
}
|
||||
}
|
||||
|
||||
export function WindowRenderer({ window, onClose }: WindowRendererProps) {
|
||||
let content: ReactNode;
|
||||
|
||||
try {
|
||||
switch (window.appId) {
|
||||
case "nip":
|
||||
content = <NipRenderer nipId={window.props.number} />;
|
||||
break;
|
||||
case "feed":
|
||||
content = <Feed className="h-full w-full overflow-auto" />;
|
||||
break;
|
||||
case "win":
|
||||
content = <WinViewer />;
|
||||
break;
|
||||
case "kind":
|
||||
content = <KindRenderer kind={parseInt(window.props.number)} />;
|
||||
break;
|
||||
case "man":
|
||||
content = <ManPage cmd={window.props.cmd} />;
|
||||
break;
|
||||
case "req":
|
||||
content = (
|
||||
<ReqViewer
|
||||
filter={window.props.filter}
|
||||
relays={window.props.relays}
|
||||
closeOnEose={window.props.closeOnEose}
|
||||
nip05Authors={window.props.nip05Authors}
|
||||
nip05PTags={window.props.nip05PTags}
|
||||
/>
|
||||
);
|
||||
break;
|
||||
case "open":
|
||||
content = <EventDetailViewer pointer={window.props.pointer} />;
|
||||
break;
|
||||
case "profile":
|
||||
content = <ProfileViewer pubkey={window.props.pubkey} />;
|
||||
break;
|
||||
case "encode":
|
||||
content = <EncodeViewer args={window.props.args} />;
|
||||
break;
|
||||
case "decode":
|
||||
content = <DecodeViewer args={window.props.args} />;
|
||||
break;
|
||||
case "relay":
|
||||
content = <RelayViewer url={window.props.url} />;
|
||||
break;
|
||||
default:
|
||||
content = (
|
||||
<div className="p-4 text-muted-foreground">
|
||||
Unknown app: {window.appId}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
content = (
|
||||
<div className="p-4">
|
||||
<div className="border border-red-500 bg-red-50 dark:bg-red-950 rounded-md p-4">
|
||||
<div className="flex items-start gap-3">
|
||||
<AlertCircle className="h-5 w-5 text-red-600 dark:text-red-400" />
|
||||
<div className="flex-1">
|
||||
<h3 className="font-semibold text-red-900 dark:text-red-100">
|
||||
Failed to render window
|
||||
</h3>
|
||||
<p className="text-sm text-red-800 dark:text-red-200 mt-1">
|
||||
{error instanceof Error
|
||||
? error.message
|
||||
: "An unexpected error occurred"}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<WindowErrorBoundary windowTitle={window.title} onClose={onClose}>
|
||||
<div className="h-full w-full overflow-auto">{content}</div>
|
||||
</WindowErrorBoundary>
|
||||
);
|
||||
}
|
||||
23
src/components/WindowTitle.tsx
Normal file
23
src/components/WindowTitle.tsx
Normal file
@@ -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 (
|
||||
<MosaicWindow
|
||||
path={path}
|
||||
title={window.title}
|
||||
toolbarControls={<WindowToolbar onClose={() => onClose(id)} />}
|
||||
>
|
||||
<WindowRenderer window={window} onClose={() => onClose(id)} />
|
||||
</MosaicWindow>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user