Files
ERitors-Scribe-Desktop/components/layout/ScribeFooterBar.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

97 lines
4.4 KiB
TypeScript

import {ChapterContext, ChapterContextProps} from "@/context/ChapterContext";
import {EditorContext, EditorContextProps} from "@/context/EditorContext";
import React, {useContext, useEffect, useState} from "react";
import {BookOpen, ChevronRight, FileText, Pilcrow, Type} from "lucide-react";
import IconLabel from "@/components/ui/IconLabel";
import {useTranslations} from '@/lib/i18n';
import {AlertContext, AlertContextProps} from "@/context/AlertContext";
import {BookContext, BookContextProps} from "@/context/BookContext";
import {chapterVersions} from "@/lib/constants/chapter";
export default function ScribeFooterBar() {
const t = useTranslations();
const {chapter}: ChapterContextProps = useContext<ChapterContextProps>(ChapterContext);
const {editor}: EditorContextProps = useContext<EditorContextProps>(EditorContext);
const {errorMessage}: AlertContextProps = useContext<AlertContextProps>(AlertContext);
const {book}: BookContextProps = useContext<BookContextProps>(BookContext);
const [wordsCount, setWordsCount] = useState<number>(0);
const [paragraphCount, setParagraphCount] = useState<number>(0);
useEffect((): void => {
getWordCount();
}, [editor?.state.doc.textContent]);
function getWordCount(): void {
if (editor) {
try {
const content: string = editor.state.doc.textContent;
const texteNormalise: string = content
.replace(/'/g, ' ')
.replace(/-/g, ' ')
.replace(/\s+/g, ' ')
.trim();
const mots: string[] = texteNormalise.split(' ');
const wordCount: number = mots.filter(
(mot: string): boolean => mot.length > 0,
).length;
setWordsCount(wordCount);
let paragraphs: number = 0;
editor.state.doc.descendants(function (node): void {
if (node.type.name === 'paragraph' && node.textContent.trim().length > 0) {
paragraphs++;
}
});
setParagraphCount(paragraphs);
} catch (e: unknown) {
if (e instanceof Error) {
errorMessage(t('errors.wordCountError') + ` (${e.message})`);
} else {
errorMessage(t('errors.wordCountError'));
}
}
}
}
function getVersionLabel(): string {
if (!chapter) return '';
const version = chapterVersions.find(function (v) {
return v.value === chapter.chapterContent.version.toString();
});
return version ? t(version.label) : '';
}
return (
<div className="px-5 py-2 bg-tertiary text-text-secondary flex justify-between items-center text-xs">
{/* Gauche : Breadcrumb */}
<div className="flex items-center gap-1.5">
{book ? (
<>
<BookOpen className="w-3 h-3 text-muted" strokeWidth={1.75}/>
<span className="text-text-primary font-medium">{book.title}</span>
{chapter && (
<>
<ChevronRight className="w-3 h-3 text-muted" strokeWidth={1.75}/>
<span>{chapter.title}</span>
<ChevronRight className="w-3 h-3 text-muted" strokeWidth={1.75}/>
<span className="text-primary">{getVersionLabel()}</span>
</>
)}
</>
) : (
<span className="text-text-dimmed">{t('scribeFooterBar.madeWith')} ERitors</span>
)}
</div>
{/* Droite : Stats */}
{(chapter || book) && (
<div className="flex items-center gap-4">
<IconLabel icon={Type}>{wordsCount} {t('scribeFooterBar.words')}</IconLabel>
<IconLabel icon={FileText}>{Math.ceil(wordsCount / 300)} {t('scribeFooterBar.pages')}</IconLabel>
<IconLabel icon={Pilcrow}>{paragraphCount} {t('scribeFooterBar.paragraphs')}</IconLabel>
</div>
)}
</div>
);
}