Files
ERitors-Scribe-Desktop/components/rightbar/ComposerRightBar.tsx
natreex d4765e6576 Add foundational components and logic for migration, UI design, and input handling
- Introduced foundational UI components (`Badge`, `LockCard`, `SectionHeader`, `AvatarIcon`, etc.) for flexible layouts and consistent design.
- Added migration support with the `MigrationModal` component and backend integration for exporting/importing data between Electron and Tauri.
- Extended form components with `TextAreaInput`, `OrderInput`, `ToggleField`, and `ToolbarSelect` for improved input handling.
- Updated `ScribeShell` with migration popup logic to prompt users for data migration.
- Integrated `AlertStack` for better alert handling and notification management.
- Enhanced Rust/Tauri services with migration command implementations.
- Added translations and styles for new components.
2026-04-05 12:52:54 -04:00

251 lines
10 KiB
TypeScript

'use client'
import {ArrowRightLeft, ExternalLink, Feather, Globe, Info, MapPin, MessageCircle, Users, Wand2, X} from 'lucide-react';
import React, {lazy, Suspense, useContext, useEffect, useState} from "react";
import {BookContext, BookContextProps} from "@/context/BookContext";
import {PanelComponent} from "@/lib/types/editor";
import SectionHeader from "@/components/ui/SectionHeader";
import AboutEditors from "@/components/rightbar/AboutERitors";
import QuillSense from "@/components/quillsense/QuillSenseComponent";
import {useTranslations} from '@/lib/i18n';
import PulseLoader from '@/components/ui/PulseLoader';
import InsetPanel from "@/components/ui/InsetPanel";
import IconButton from "@/components/ui/IconButton";
import OfflineContext, {OfflineContextType} from "@/context/OfflineContext";
import {isDesktop} from '@/lib/configs';
import MigrationModal from '@/components/migration/MigrationModal';
// Lazy loaded Editor components
const WorldEditor = lazy(function () {
return import('@/components/book/settings/world/editor/WorldEditor');
});
const LocationEditor = lazy(function () {
return import('@/components/book/settings/locations/editor/LocationEditor');
});
const CharacterEditor = lazy(function () {
return import('@/components/book/settings/characters/editor/CharacterEditor');
});
const SpellEditor = lazy(function () {
return import('@/components/book/settings/spells/editor/SpellEditor');
});
interface PanelContentProps {
currentPanelId: number | undefined;
}
function PanelContent({currentPanelId}: PanelContentProps): React.JSX.Element {
return (
<Suspense fallback={<PulseLoader/>}>
{currentPanelId === 1 && <QuillSense/>}
{currentPanelId === 2 && <WorldEditor/>}
{currentPanelId === 3 && <LocationEditor/>}
{currentPanelId === 4 && <CharacterEditor/>}
{currentPanelId === 5 && <SpellEditor/>}
</Suspense>
);
}
export default function ComposerRightBar(): React.JSX.Element {
const {book}: BookContextProps = useContext<BookContextProps>(BookContext);
const {isCurrentlyOffline}: OfflineContextType = useContext<OfflineContextType>(OfflineContext);
const t = useTranslations();
const [panelHidden, setPanelHidden] = useState<boolean>(false);
const [currentPanel, setCurrentPanel] = useState<PanelComponent | undefined>();
const [showAbout, setShowAbout] = useState<boolean>(false);
const [showMigration, setShowMigration] = useState<boolean>(false);
const migrationDone: boolean = localStorage.getItem('electron_migration_done') === 'true';
const migrationDismissed: boolean = localStorage.getItem('electron_migration_dismissed') === 'true';
const showMigrationButton: boolean = isDesktop && !migrationDone;
function togglePanel(component: PanelComponent): void {
if (panelHidden) {
if (currentPanel?.id === component.id) {
setPanelHidden(!panelHidden);
return;
}
} else {
setPanelHidden(true);
}
}
const editorComponents: PanelComponent[] = [
{
id: 1,
title: t("composerRightBar.editorComponents.quillSense.title"),
description: t("composerRightBar.editorComponents.quillSense.description"),
badge: t("composerRightBar.editorComponents.quillSense.badge"),
icon: Feather
},
{
id: 2,
title: t("composerRightBar.editorComponents.worlds.title"),
description: t("composerRightBar.editorComponents.worlds.description"),
badge: t("composerRightBar.editorComponents.worlds.badge"),
icon: Globe
},
{
id: 3,
title: t("composerRightBar.editorComponents.locations.title"),
description: t("composerRightBar.editorComponents.locations.description"),
badge: t("composerRightBar.editorComponents.locations.badge"),
icon: MapPin
},
{
id: 4,
title: t("composerRightBar.editorComponents.characters.title"),
description: t("composerRightBar.editorComponents.characters.description"),
badge: t("composerRightBar.editorComponents.characters.badge"),
icon: Users
},
{
id: 5,
title: t("composerRightBar.editorComponents.spells.title"),
description: t("composerRightBar.editorComponents.spells.description"),
badge: t("composerRightBar.editorComponents.spells.badge"),
icon: Wand2
},
];
const homeComponents: PanelComponent[] = [
...(showMigrationButton ? [{
id: 0,
title: t("composerRightBar.homeComponents.migration.title"),
description: t("composerRightBar.homeComponents.migration.description"),
badge: 'IMPORT',
icon: ArrowRightLeft,
action: function (): void {
setShowMigration(true);
}
}] : []),
{
id: 1,
title: t("composerRightBar.homeComponents.about.title"),
description: t("composerRightBar.homeComponents.about.description"),
badge: t("composerRightBar.homeComponents.about.badge"),
icon: Info,
action: function (): void {
setShowAbout(true);
}
},
{
id: 2,
title: t("composerRightBar.homeComponents.facebook.title"),
description: t("composerRightBar.homeComponents.facebook.description"),
badge: t("composerRightBar.homeComponents.facebook.badge"),
icon: ExternalLink,
action: function (): void {
window.open('https://www.facebook.com/profile.php?id=61562628720878', '_blank');
}
},
{
id: 3,
title: t("composerRightBar.homeComponents.discord.title"),
description: t("composerRightBar.homeComponents.discord.description"),
badge: t("composerRightBar.homeComponents.discord.badge"),
icon: MessageCircle,
action: function (): void {
window.open('https://discord.gg/CHXRPvmaXm', '_blank');
}
}
];
function disabled(componentId: number): boolean {
return book === null;
}
useEffect(function (): void {
if (!book) {
setCurrentPanel(undefined);
setPanelHidden(false);
}
}, [book]);
async function handleClosePanel(): Promise<void> {
setPanelHidden(!panelHidden);
}
return (
<div id="right-panel-container" className="flex transition-all duration-300">
{panelHidden && (
<div id="right-panel"
className="bg-tertiary min-w-[450px] max-w-[450px] h-full transition-all duration-300 flex flex-col">
<InsetPanel>
<div className="p-4">
<SectionHeader title={currentPanel?.title ?? ''}
icon={currentPanel?.icon}
actions={<IconButton icon={X} variant="ghost"
onClick={handleClosePanel}/>}/>
</div>
<div className="flex-1 overflow-auto">
<PanelContent currentPanelId={currentPanel?.id}/>
</div>
</InsetPanel>
</div>
)}
<div className="bg-tertiary p-1 flex flex-col space-y-2">
{book ? editorComponents.filter(function (component: PanelComponent): boolean {
if ((isCurrentlyOffline() || book?.localBook) && component.id === 1) {
return false;
}
if (component.id === 1 && book.quillsenseEnabled === false) {
return false;
}
if (component.id === 2 && !book?.tools?.worlds) {
return false;
}
if (component.id === 3 && !book?.tools?.locations) {
return false;
}
if (component.id === 4 && !book?.tools?.characters) {
return false;
}
if (component.id === 5 && !book?.tools?.spells) {
return false;
}
return true;
}).map(function (component: PanelComponent) {
return (
<IconButton
key={component.id}
icon={component.icon}
variant="ghost"
shape="square"
tooltip={component.title}
disabled={disabled(component.id)}
selected={panelHidden && currentPanel?.id === component.id}
onClick={function (): void {
togglePanel(component);
setCurrentPanel(component);
}}
/>
);
}) : homeComponents.map(function (component: PanelComponent) {
return (
<IconButton
key={component.id}
icon={component.icon}
variant="ghost"
shape="square"
tooltip={component.title}
selected={panelHidden && currentPanel?.id === component.id}
onClick={component.action ?? function (): void {
}}
/>
);
})}
</div>
{showAbout && <AboutEditors onClose={function (): void {
setShowAbout(false);
}}/>}
{showMigration && <MigrationModal
onClose={function (): void { setShowMigration(false); }}
onSuccess={function (): void { setShowMigration(false); window.location.reload(); }}
/>}
</div>
);
}