- Introduced new translations for terms of use in French and English locales. - Added sync status detection logic for books in `BookList` and `BookCard` components. - Refactored `BookCard` to handle additional props and improve layout flexibility. - Enhanced `TermsOfUse` component with complete localization support and refuse functionality. - Updated data decryption logic in Rust services to handle optional fields and additional metadata consistently. - Improved offline/online synchronization workflows with extended context properties.
230 lines
9.4 KiB
TypeScript
230 lines
9.4 KiB
TypeScript
'use client'
|
|
import {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";
|
|
|
|
// 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);
|
|
|
|
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[] = [
|
|
{
|
|
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);
|
|
}}/>}
|
|
</div>
|
|
);
|
|
}
|