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.
This commit is contained in:
96
components/layout/ScribeFooterBar.tsx
Normal file
96
components/layout/ScribeFooterBar.tsx
Normal file
@@ -0,0 +1,96 @@
|
||||
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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user