127 lines
3.5 KiB
TypeScript
127 lines
3.5 KiB
TypeScript
import React, { useState, useRef, useCallback } from "react";
|
|
import { EntityMapping } from "@/app/config/entityLabels";
|
|
import { useTextParsing } from "./hooks/useTextParsing";
|
|
import { useContextMenu } from "./hooks/useContextMenu";
|
|
import { useColorMapping } from "./hooks/useColorMapping";
|
|
import { TextDisplay } from "./TextDisplay";
|
|
import { ContextMenu } from "./ContextMenu";
|
|
import { InstructionsPanel } from "./InstructionsPanel";
|
|
|
|
interface InteractiveTextEditorProps {
|
|
text: string;
|
|
entityMappings: EntityMapping[];
|
|
onUpdateMapping: (
|
|
originalValue: string,
|
|
newLabel: string,
|
|
entityType: string,
|
|
applyToAllOccurrences?: boolean,
|
|
customColor?: string // Ajouter ce paramètre
|
|
) => void;
|
|
onRemoveMapping?: (originalValue: string) => void;
|
|
}
|
|
|
|
export const InteractiveTextEditor: React.FC<InteractiveTextEditorProps> = ({
|
|
text,
|
|
entityMappings,
|
|
onUpdateMapping,
|
|
onRemoveMapping,
|
|
}) => {
|
|
const [selectedWords, setSelectedWords] = useState<Set<number>>(new Set());
|
|
const [hoveredWord, setHoveredWord] = useState<number | null>(null);
|
|
const containerRef = useRef<HTMLDivElement>(null);
|
|
|
|
const { words } = useTextParsing(text, entityMappings);
|
|
const { getCurrentColor } = useColorMapping(entityMappings); // CORRECTION: Passer entityMappings
|
|
const {
|
|
contextMenu,
|
|
showContextMenu,
|
|
applyLabel,
|
|
applyColorDirectly,
|
|
removeLabel,
|
|
getExistingLabels,
|
|
} = useContextMenu({
|
|
entityMappings,
|
|
words, // NOUVEAU: passer les mots
|
|
onUpdateMapping,
|
|
onRemoveMapping,
|
|
getCurrentColor,
|
|
setSelectedWords,
|
|
});
|
|
|
|
const handleWordClick = useCallback(
|
|
(index: number, event: React.MouseEvent) => {
|
|
event.preventDefault();
|
|
event.stopPropagation();
|
|
|
|
// Support multi-sélection avec Ctrl, Cmd et Shift
|
|
const isMultiSelect = event.ctrlKey || event.metaKey || event.shiftKey;
|
|
|
|
if (isMultiSelect) {
|
|
setSelectedWords((prev) => {
|
|
const newSet = new Set(prev);
|
|
if (newSet.has(index)) {
|
|
newSet.delete(index);
|
|
} else {
|
|
newSet.add(index);
|
|
}
|
|
return newSet;
|
|
});
|
|
} else {
|
|
setSelectedWords(new Set([index]));
|
|
}
|
|
},
|
|
[]
|
|
);
|
|
|
|
const handleContextMenu = useCallback(
|
|
(event: React.MouseEvent) => {
|
|
event.preventDefault();
|
|
if (selectedWords.size === 0) return;
|
|
|
|
const selectedText = Array.from(selectedWords)
|
|
.map((index) => {
|
|
const word = words[index];
|
|
return word?.isEntity ? word.text : word?.text;
|
|
})
|
|
.filter(Boolean)
|
|
.join(" ");
|
|
|
|
showContextMenu({
|
|
x: event.clientX,
|
|
y: event.clientY,
|
|
selectedText,
|
|
wordIndices: Array.from(selectedWords),
|
|
});
|
|
},
|
|
[selectedWords, words, showContextMenu]
|
|
);
|
|
|
|
return (
|
|
<div ref={containerRef} className="relative">
|
|
<InstructionsPanel />
|
|
|
|
<TextDisplay
|
|
words={words}
|
|
text={text}
|
|
selectedWords={selectedWords}
|
|
hoveredWord={hoveredWord}
|
|
onWordClick={handleWordClick}
|
|
onContextMenu={handleContextMenu}
|
|
onWordHover={setHoveredWord}
|
|
/>
|
|
|
|
{contextMenu.visible && (
|
|
<ContextMenu
|
|
contextMenu={contextMenu}
|
|
existingLabels={getExistingLabels()}
|
|
// entityMappings={entityMappings} // SUPPRIMER cette ligne
|
|
onApplyLabel={applyLabel}
|
|
onApplyColor={applyColorDirectly}
|
|
onRemoveLabel={removeLabel}
|
|
getCurrentColor={getCurrentColor}
|
|
/>
|
|
)}
|
|
</div>
|
|
);
|
|
};
|