forked from akai/readest
feat(annotator): support vertical layout for annotation editor (#2882)
This commit is contained in:
@@ -874,5 +874,6 @@
|
||||
"Annotations": "التعليقات التوضيحية",
|
||||
"Show Results": "عرض النتائج",
|
||||
"Clear search": "مسح البحث",
|
||||
"Clear search history": "مسح سجل البحث"
|
||||
"Clear search history": "مسح سجل البحث",
|
||||
"Quick action disabled": "الإجراء السريع معطل"
|
||||
}
|
||||
|
||||
@@ -834,5 +834,6 @@
|
||||
"Annotations": "টীকা",
|
||||
"Show Results": "ফলাফল দেখুন",
|
||||
"Clear search": "অনুসন্ধান সাফ করুন",
|
||||
"Clear search history": "অনুসন্ধান ইতিহাস সাফ করুন"
|
||||
"Clear search history": "অনুসন্ধান ইতিহাস সাফ করুন",
|
||||
"Quick action disabled": "দ্রুত কার্যক্রম অক্ষম"
|
||||
}
|
||||
|
||||
@@ -824,5 +824,6 @@
|
||||
"Annotations": "མཆན།",
|
||||
"Show Results": "འབྲས་བུ་སྟོན།",
|
||||
"Clear search": "འཚོལ་བཤེར་གཙང་སེལ།",
|
||||
"Clear search history": "འཚོལ་བཤེར་ལོ་རྒྱུས་གཙང་སེལ།"
|
||||
"Clear search history": "འཚོལ་བཤེར་ལོ་རྒྱུས་གཙང་སེལ།",
|
||||
"Quick action disabled": "མྱུར་མགྱོགས་བྱ་འགུལ་བཀག་འགོག"
|
||||
}
|
||||
|
||||
@@ -834,5 +834,6 @@
|
||||
"Annotations": "Anmerkungen",
|
||||
"Show Results": "Ergebnisse anzeigen",
|
||||
"Clear search": "Suche löschen",
|
||||
"Clear search history": "Suchverlauf löschen"
|
||||
"Clear search history": "Suchverlauf löschen",
|
||||
"Quick action disabled": "Schnellaktion deaktiviert"
|
||||
}
|
||||
|
||||
@@ -834,5 +834,6 @@
|
||||
"Annotations": "Σχολιασμοί",
|
||||
"Show Results": "Εμφάνιση αποτελεσμάτων",
|
||||
"Clear search": "Εκκαθάριση αναζήτησης",
|
||||
"Clear search history": "Εκκαθάριση ιστορικού αναζήτησης"
|
||||
"Clear search history": "Εκκαθάριση ιστορικού αναζήτησης",
|
||||
"Quick action disabled": "Η γρήγορη ενέργεια απενεργοποιήθηκε"
|
||||
}
|
||||
|
||||
@@ -844,5 +844,6 @@
|
||||
"Annotations": "Anotaciones",
|
||||
"Show Results": "Mostrar resultados",
|
||||
"Clear search": "Borrar búsqueda",
|
||||
"Clear search history": "Borrar historial de búsqueda"
|
||||
"Clear search history": "Borrar historial de búsqueda",
|
||||
"Quick action disabled": "Acción rápida desactivada"
|
||||
}
|
||||
|
||||
@@ -834,5 +834,6 @@
|
||||
"Annotations": "یادداشتها",
|
||||
"Show Results": "نمایش نتایج",
|
||||
"Clear search": "پاک کردن جستجو",
|
||||
"Clear search history": "پاک کردن تاریخچه جستجو"
|
||||
"Clear search history": "پاک کردن تاریخچه جستجو",
|
||||
"Quick action disabled": "عملکرد سریع غیرفعال است"
|
||||
}
|
||||
|
||||
@@ -844,5 +844,6 @@
|
||||
"Annotations": "Annotations",
|
||||
"Show Results": "Afficher les résultats",
|
||||
"Clear search": "Effacer la recherche",
|
||||
"Clear search history": "Effacer l'historique de recherche"
|
||||
"Clear search history": "Effacer l'historique de recherche",
|
||||
"Quick action disabled": "Action rapide désactivée"
|
||||
}
|
||||
|
||||
@@ -834,5 +834,6 @@
|
||||
"Annotations": "टिप्पणियाँ",
|
||||
"Show Results": "परिणाम दिखाएं",
|
||||
"Clear search": "खोज साफ़ करें",
|
||||
"Clear search history": "खोज इतिहास साफ़ करें"
|
||||
"Clear search history": "खोज इतिहास साफ़ करें",
|
||||
"Quick action disabled": "त्वरित क्रिया अक्षम"
|
||||
}
|
||||
|
||||
@@ -824,5 +824,6 @@
|
||||
"Annotations": "Anotasi",
|
||||
"Show Results": "Tampilkan Hasil",
|
||||
"Clear search": "Hapus pencarian",
|
||||
"Clear search history": "Hapus riwayat pencarian"
|
||||
"Clear search history": "Hapus riwayat pencarian",
|
||||
"Quick action disabled": "Tindakan cepat dinonaktifkan"
|
||||
}
|
||||
|
||||
@@ -844,5 +844,6 @@
|
||||
"Annotations": "Annotazioni",
|
||||
"Show Results": "Mostra risultati",
|
||||
"Clear search": "Cancella ricerca",
|
||||
"Clear search history": "Cancella cronologia ricerche"
|
||||
"Clear search history": "Cancella cronologia ricerche",
|
||||
"Quick action disabled": "Azione rapida disattivata"
|
||||
}
|
||||
|
||||
@@ -824,5 +824,6 @@
|
||||
"Annotations": "注釈",
|
||||
"Show Results": "結果を表示",
|
||||
"Clear search": "検索をクリア",
|
||||
"Clear search history": "検索履歴をクリア"
|
||||
"Clear search history": "検索履歴をクリア",
|
||||
"Quick action disabled": "クイックアクション無効"
|
||||
}
|
||||
|
||||
@@ -824,5 +824,6 @@
|
||||
"Annotations": "주석",
|
||||
"Show Results": "결과 표시",
|
||||
"Clear search": "검색 지우기",
|
||||
"Clear search history": "검색 기록 지우기"
|
||||
"Clear search history": "검색 기록 지우기",
|
||||
"Quick action disabled": "빠른 작업 비활성화됨"
|
||||
}
|
||||
|
||||
@@ -824,5 +824,6 @@
|
||||
"Annotations": "Anotasi",
|
||||
"Show Results": "Tunjukkan Hasil",
|
||||
"Clear search": "Kosongkan carian",
|
||||
"Clear search history": "Kosongkan sejarah carian"
|
||||
"Clear search history": "Kosongkan sejarah carian",
|
||||
"Quick action disabled": "Tindakan pantas dilumpuhkan"
|
||||
}
|
||||
|
||||
@@ -834,5 +834,6 @@
|
||||
"Annotations": "Aantekeningen",
|
||||
"Show Results": "Resultaten tonen",
|
||||
"Clear search": "Zoekopdracht wissen",
|
||||
"Clear search history": "Zoekgeschiedenis wissen"
|
||||
"Clear search history": "Zoekgeschiedenis wissen",
|
||||
"Quick action disabled": "Snelle actie uitgeschakeld"
|
||||
}
|
||||
|
||||
@@ -854,5 +854,6 @@
|
||||
"Annotations": "Adnotacje",
|
||||
"Show Results": "Pokaż wyniki",
|
||||
"Clear search": "Wyczyść wyszukiwanie",
|
||||
"Clear search history": "Wyczyść historię wyszukiwania"
|
||||
"Clear search history": "Wyczyść historię wyszukiwania",
|
||||
"Quick action disabled": "Szybka akcja wyłączona"
|
||||
}
|
||||
|
||||
@@ -844,5 +844,6 @@
|
||||
"Annotations": "Anotações",
|
||||
"Show Results": "Mostrar resultados",
|
||||
"Clear search": "Limpar pesquisa",
|
||||
"Clear search history": "Limpar histórico de pesquisa"
|
||||
"Clear search history": "Limpar histórico de pesquisa",
|
||||
"Quick action disabled": "Ação rápida desativada"
|
||||
}
|
||||
|
||||
@@ -854,5 +854,6 @@
|
||||
"Annotations": "Аннотации",
|
||||
"Show Results": "Показать результаты",
|
||||
"Clear search": "Очистить поиск",
|
||||
"Clear search history": "Очистить историю поиска"
|
||||
"Clear search history": "Очистить историю поиска",
|
||||
"Quick action disabled": "Быстрое действие отключено"
|
||||
}
|
||||
|
||||
@@ -834,5 +834,6 @@
|
||||
"Annotations": "අනුසටහන්",
|
||||
"Show Results": "ප්රතිඵල පෙන්වන්න",
|
||||
"Clear search": "සෙවුම හිස් කරන්න",
|
||||
"Clear search history": "සෙවුම් ඉතිහාසය හිස් කරන්න"
|
||||
"Clear search history": "සෙවුම් ඉතිහාසය හිස් කරන්න",
|
||||
"Quick action disabled": "ඉක්මන් ක්රියාව අක්රීයයි"
|
||||
}
|
||||
|
||||
@@ -834,5 +834,6 @@
|
||||
"Annotations": "Anteckningar",
|
||||
"Show Results": "Visa resultat",
|
||||
"Clear search": "Rensa sökning",
|
||||
"Clear search history": "Rensa sökhistorik"
|
||||
"Clear search history": "Rensa sökhistorik",
|
||||
"Quick action disabled": "Snabbåtgärd inaktiverad"
|
||||
}
|
||||
|
||||
@@ -834,5 +834,6 @@
|
||||
"Annotations": "சிறுகுறிப்புகள்",
|
||||
"Show Results": "முடிவுகளைக் காட்டு",
|
||||
"Clear search": "தேடலை அழி",
|
||||
"Clear search history": "தேடல் வரலாற்றை அழி"
|
||||
"Clear search history": "தேடல் வரலாற்றை அழி",
|
||||
"Quick action disabled": "விரைவு செயல் முடக்கப்பட்டது"
|
||||
}
|
||||
|
||||
@@ -824,5 +824,6 @@
|
||||
"Annotations": "คำอธิบายประกอบ",
|
||||
"Show Results": "แสดงผลลัพธ์",
|
||||
"Clear search": "ล้างการค้นหา",
|
||||
"Clear search history": "ล้างประวัติการค้นหา"
|
||||
"Clear search history": "ล้างประวัติการค้นหา",
|
||||
"Quick action disabled": "ปิดใช้งานการดำเนินการด่วน"
|
||||
}
|
||||
|
||||
@@ -834,5 +834,6 @@
|
||||
"Annotations": "Açıklamalar",
|
||||
"Show Results": "Sonuçları Göster",
|
||||
"Clear search": "Aramayı temizle",
|
||||
"Clear search history": "Arama geçmişini temizle"
|
||||
"Clear search history": "Arama geçmişini temizle",
|
||||
"Quick action disabled": "Hızlı eylem devre dışı"
|
||||
}
|
||||
|
||||
@@ -854,5 +854,6 @@
|
||||
"Annotations": "Анотації",
|
||||
"Show Results": "Показати результати",
|
||||
"Clear search": "Очистити пошук",
|
||||
"Clear search history": "Очистити історію пошуку"
|
||||
"Clear search history": "Очистити історію пошуку",
|
||||
"Quick action disabled": "Швидку дію вимкнено"
|
||||
}
|
||||
|
||||
@@ -824,5 +824,6 @@
|
||||
"Annotations": "Chú thích",
|
||||
"Show Results": "Hiển thị kết quả",
|
||||
"Clear search": "Xóa tìm kiếm",
|
||||
"Clear search history": "Xóa lịch sử tìm kiếm"
|
||||
"Clear search history": "Xóa lịch sử tìm kiếm",
|
||||
"Quick action disabled": "Đã tắt thao tác nhanh"
|
||||
}
|
||||
|
||||
@@ -824,5 +824,6 @@
|
||||
"Annotations": "注释",
|
||||
"Show Results": "显示结果",
|
||||
"Clear search": "清除搜索",
|
||||
"Clear search history": "清除搜索历史"
|
||||
"Clear search history": "清除搜索历史",
|
||||
"Quick action disabled": "快捷操作已禁用"
|
||||
}
|
||||
|
||||
@@ -824,5 +824,6 @@
|
||||
"Annotations": "註解",
|
||||
"Show Results": "顯示結果",
|
||||
"Clear search": "清除搜尋",
|
||||
"Clear search history": "清除搜尋歷史"
|
||||
"Clear search history": "清除搜尋歷史",
|
||||
"Quick action disabled": "快捷操作已停用"
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import clsx from 'clsx';
|
||||
import dayjs from 'dayjs';
|
||||
import React, { useMemo } from 'react';
|
||||
import { BookNote } from '@/types/book';
|
||||
import { useEnv } from '@/context/EnvContext';
|
||||
@@ -11,6 +12,7 @@ interface AnnotationNotesProps {
|
||||
bookKey: string;
|
||||
isVertical: boolean;
|
||||
notes: BookNote[];
|
||||
toolsVisible: boolean;
|
||||
triangleDir: 'up' | 'down' | 'left' | 'right';
|
||||
popupWidth: number;
|
||||
popupHeight: number;
|
||||
@@ -21,6 +23,7 @@ const AnnotationNotes: React.FC<AnnotationNotesProps> = ({
|
||||
bookKey,
|
||||
isVertical,
|
||||
notes,
|
||||
toolsVisible,
|
||||
triangleDir,
|
||||
popupWidth,
|
||||
popupHeight,
|
||||
@@ -31,7 +34,7 @@ const AnnotationNotes: React.FC<AnnotationNotesProps> = ({
|
||||
const { setHoveredBookKey } = useReaderStore();
|
||||
const { setSideBarVisible } = useSidebarStore();
|
||||
const config = getConfig(bookKey);
|
||||
const maxSize = useResponsiveSize(150);
|
||||
const maxSize = useResponsiveSize(250);
|
||||
|
||||
const sortedNotes = useMemo(() => {
|
||||
return [...notes].sort((a, b) => b.updatedAt - a.updatedAt);
|
||||
@@ -59,15 +62,15 @@ const AnnotationNotes: React.FC<AnnotationNotesProps> = ({
|
||||
style={{
|
||||
...(isVertical
|
||||
? {
|
||||
right: `${triangleDir === 'left' ? `${popupWidth + 16}px` : undefined}`,
|
||||
left: `${triangleDir === 'right' ? `${popupWidth + 16}px` : undefined}`,
|
||||
right: triangleDir === 'left' ? `${toolsVisible ? popupWidth + 16 : 0}px` : undefined,
|
||||
left: triangleDir === 'right' ? `${toolsVisible ? popupWidth + 16 : 0}px` : undefined,
|
||||
height: `${popupHeight}px`,
|
||||
maxWidth: `${maxSize}px`,
|
||||
overflowX: 'auto',
|
||||
}
|
||||
: {
|
||||
top: triangleDir === 'up' ? undefined : `${popupHeight + 16}px`,
|
||||
bottom: triangleDir === 'up' ? `${popupHeight + 16}px` : undefined,
|
||||
top: triangleDir === 'down' ? `${toolsVisible ? popupHeight + 16 : 0}px` : undefined,
|
||||
bottom: triangleDir === 'up' ? `${toolsVisible ? popupHeight + 16 : 0}px` : undefined,
|
||||
width: `${popupWidth}px`,
|
||||
maxHeight: `${maxSize}px`,
|
||||
overflowY: 'auto',
|
||||
@@ -76,7 +79,7 @@ const AnnotationNotes: React.FC<AnnotationNotesProps> = ({
|
||||
}}
|
||||
>
|
||||
<div
|
||||
className={clsx('flex gap-4', isVertical ? 'h-full flex-row' : 'flex-col')}
|
||||
className={clsx('flex gap-4', isVertical ? 'h-full flex-row' : 'w-full flex-col')}
|
||||
style={
|
||||
isVertical
|
||||
? {
|
||||
@@ -122,7 +125,12 @@ const AnnotationNotes: React.FC<AnnotationNotesProps> = ({
|
||||
: {}
|
||||
}
|
||||
>
|
||||
{note.note}
|
||||
<div className={clsx('flex flex-col justify-between gap-2')}>
|
||||
{note.note}
|
||||
<span className='text-sm text-white/50 sm:text-xs'>
|
||||
{dayjs(note.createdAt).fromNow()}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -54,7 +54,10 @@ const AnnotationPopup: React.FC<AnnotationPopupProps> = ({
|
||||
minHeight={isVertical ? popupWidth : popupHeight}
|
||||
position={position}
|
||||
trianglePosition={trianglePosition}
|
||||
className='selection-popup bg-gray-600 text-white'
|
||||
className={clsx(
|
||||
'selection-popup bg-gray-600 text-white',
|
||||
notes.length > 0 && 'bg-transparent',
|
||||
)}
|
||||
triangleClassName='text-gray-600'
|
||||
onDismiss={onDismiss}
|
||||
>
|
||||
@@ -63,6 +66,7 @@ const AnnotationPopup: React.FC<AnnotationPopupProps> = ({
|
||||
className={clsx(
|
||||
'selection-buttons flex h-full w-full items-center justify-between p-2',
|
||||
isVertical ? 'flex-col overflow-y-auto' : 'flex-row overflow-x-auto',
|
||||
notes.length > 0 && 'hidden',
|
||||
)}
|
||||
style={{ scrollbarWidth: 'none', msOverflowStyle: 'none' }}
|
||||
>
|
||||
@@ -85,6 +89,7 @@ const AnnotationPopup: React.FC<AnnotationPopupProps> = ({
|
||||
bookKey={bookKey}
|
||||
isVertical={isVertical}
|
||||
notes={notes}
|
||||
toolsVisible={false}
|
||||
triangleDir={trianglePosition.dir!}
|
||||
popupWidth={isVertical ? popupHeight : popupWidth}
|
||||
popupHeight={isVertical ? popupWidth : popupHeight}
|
||||
|
||||
@@ -4,11 +4,13 @@ import React, { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import { BookNote, HighlightColor } from '@/types/book';
|
||||
import { Point, TextSelection } from '@/utils/sel';
|
||||
import { useSettingsStore } from '@/store/settingsStore';
|
||||
import { useResponsiveSize } from '@/hooks/useResponsiveSize';
|
||||
import { useAnnotationEditor } from '../../hooks/useAnnotationEditor';
|
||||
import { getHighlightColorHex } from '../../utils/annotatorUtil';
|
||||
|
||||
interface HandleProps {
|
||||
position: Point;
|
||||
isVertical: boolean;
|
||||
type: 'start' | 'end';
|
||||
color: string;
|
||||
onDragStart: () => void;
|
||||
@@ -18,6 +20,7 @@ interface HandleProps {
|
||||
|
||||
const Handle: React.FC<HandleProps> = ({
|
||||
position,
|
||||
isVertical,
|
||||
type,
|
||||
color,
|
||||
onDragStart,
|
||||
@@ -25,6 +28,9 @@ const Handle: React.FC<HandleProps> = ({
|
||||
onDragEnd,
|
||||
}) => {
|
||||
const isDragging = useRef(false);
|
||||
const size = useResponsiveSize(24);
|
||||
const circleRadius = useResponsiveSize(8);
|
||||
const stemHeight = useResponsiveSize(12);
|
||||
|
||||
const handlePointerDown = useCallback(
|
||||
(e: React.PointerEvent) => {
|
||||
@@ -59,16 +65,22 @@ const Handle: React.FC<HandleProps> = ({
|
||||
[onDragEnd],
|
||||
);
|
||||
|
||||
const size = 24;
|
||||
const circleRadius = 8;
|
||||
const stemHeight = 8;
|
||||
|
||||
return (
|
||||
<div
|
||||
className='pointer-events-auto absolute z-50 cursor-grab touch-none active:cursor-grabbing'
|
||||
style={{
|
||||
left: position.x - size / 2,
|
||||
top: type === 'start' ? position.y - size : position.y - size / 2,
|
||||
left: isVertical
|
||||
? type === 'start'
|
||||
? position.x - size / 2 + stemHeight / 4
|
||||
: position.x - size / 2
|
||||
: position.x - size / 2,
|
||||
top: isVertical
|
||||
? type === 'start'
|
||||
? position.y - size + stemHeight / 2
|
||||
: position.y - size / 2 - stemHeight / 2
|
||||
: type === 'start'
|
||||
? position.y - size
|
||||
: position.y - size / 2 - stemHeight / 8,
|
||||
width: size,
|
||||
height: size + stemHeight,
|
||||
}}
|
||||
@@ -82,7 +94,15 @@ const Handle: React.FC<HandleProps> = ({
|
||||
height={size + stemHeight}
|
||||
viewBox={`0 0 ${size} ${size + stemHeight}`}
|
||||
className={clsx(type === 'start' && 'rotate-180')}
|
||||
style={{ transform: type === 'start' ? 'rotate(180deg)' : undefined }}
|
||||
style={{
|
||||
transform: isVertical
|
||||
? type === 'start'
|
||||
? 'rotate(270deg)'
|
||||
: 'rotate(90deg)'
|
||||
: type === 'start'
|
||||
? 'rotate(180deg)'
|
||||
: undefined,
|
||||
}}
|
||||
>
|
||||
{/* Stem/line */}
|
||||
<line
|
||||
@@ -103,6 +123,7 @@ const Handle: React.FC<HandleProps> = ({
|
||||
|
||||
interface AnnotationRangeEditorProps {
|
||||
bookKey: string;
|
||||
isVertical: boolean;
|
||||
annotation: BookNote;
|
||||
selection: TextSelection;
|
||||
handleColor: HighlightColor;
|
||||
@@ -113,6 +134,7 @@ interface AnnotationRangeEditorProps {
|
||||
|
||||
const AnnotationRangeEditor: React.FC<AnnotationRangeEditorProps> = ({
|
||||
bookKey,
|
||||
isVertical,
|
||||
annotation,
|
||||
selection,
|
||||
handleColor,
|
||||
@@ -137,7 +159,7 @@ const AnnotationRangeEditor: React.FC<AnnotationRangeEditorProps> = ({
|
||||
initializedRef.current = true;
|
||||
|
||||
const range = selection.range;
|
||||
const positions = getHandlePositionsFromRange(range);
|
||||
const positions = getHandlePositionsFromRange(range, isVertical);
|
||||
if (positions) {
|
||||
setTimeout(() => {
|
||||
setCurrentStart(positions.start);
|
||||
@@ -146,7 +168,7 @@ const AnnotationRangeEditor: React.FC<AnnotationRangeEditorProps> = ({
|
||||
startRef.current = positions.start;
|
||||
endRef.current = positions.end;
|
||||
}
|
||||
}, [annotation, selection, getHandlePositionsFromRange]);
|
||||
}, [annotation, selection, isVertical, getHandlePositionsFromRange]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!handlePositions || draggingRef.current) return;
|
||||
@@ -172,24 +194,24 @@ const AnnotationRangeEditor: React.FC<AnnotationRangeEditorProps> = ({
|
||||
(point: Point) => {
|
||||
setCurrentStart(point);
|
||||
startRef.current = point;
|
||||
handleAnnotationRangeChange(point, endRef.current, true);
|
||||
handleAnnotationRangeChange(point, endRef.current, isVertical, true);
|
||||
},
|
||||
[handleAnnotationRangeChange],
|
||||
[isVertical, handleAnnotationRangeChange],
|
||||
);
|
||||
|
||||
const handleEndDrag = useCallback(
|
||||
(point: Point) => {
|
||||
setCurrentEnd(point);
|
||||
endRef.current = point;
|
||||
handleAnnotationRangeChange(startRef.current, point, true);
|
||||
handleAnnotationRangeChange(startRef.current, point, isVertical, true);
|
||||
},
|
||||
[handleAnnotationRangeChange],
|
||||
[isVertical, handleAnnotationRangeChange],
|
||||
);
|
||||
|
||||
const handleDragEnd = useCallback(() => {
|
||||
draggingRef.current = null;
|
||||
handleAnnotationRangeChange(startRef.current, endRef.current, false);
|
||||
}, [handleAnnotationRangeChange]);
|
||||
handleAnnotationRangeChange(startRef.current, endRef.current, isVertical, false);
|
||||
}, [isVertical, handleAnnotationRangeChange]);
|
||||
|
||||
if (currentStart.x === 0 && currentStart.y === 0) {
|
||||
return null;
|
||||
@@ -199,6 +221,7 @@ const AnnotationRangeEditor: React.FC<AnnotationRangeEditorProps> = ({
|
||||
<div className='pointer-events-none fixed inset-0 z-40'>
|
||||
<Handle
|
||||
position={currentStart}
|
||||
isVertical={isVertical}
|
||||
type='start'
|
||||
color={handleColorHex}
|
||||
onDragStart={handleStartDragStart}
|
||||
@@ -207,6 +230,7 @@ const AnnotationRangeEditor: React.FC<AnnotationRangeEditorProps> = ({
|
||||
/>
|
||||
<Handle
|
||||
position={currentEnd}
|
||||
isVertical={isVertical}
|
||||
type='end'
|
||||
color={handleColorHex}
|
||||
onDragStart={handleEndDragStart}
|
||||
|
||||
@@ -939,6 +939,7 @@ const Annotator: React.FC<{ bookKey: string }> = ({ bookKey }) => {
|
||||
{editingAnnotation && editingAnnotation.color && selection && (
|
||||
<AnnotationRangeEditor
|
||||
bookKey={bookKey}
|
||||
isVertical={viewSettings.vertical}
|
||||
annotation={editingAnnotation}
|
||||
selection={selection}
|
||||
handleColor={selectedColor}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import clsx from 'clsx';
|
||||
import React from 'react';
|
||||
import { MdCheck } from 'react-icons/md';
|
||||
|
||||
import { AnnotationToolType } from '@/types/annotator';
|
||||
import { useTranslation } from '@/hooks/useTranslation';
|
||||
import { annotationToolQuickActions } from './AnnotationTools';
|
||||
import { eventDispatcher } from '@/utils/event';
|
||||
import MenuItem from '@/components/MenuItem';
|
||||
import Menu from '@/components/Menu';
|
||||
|
||||
@@ -23,6 +23,20 @@ const QuickActionMenu: React.FC<QuickActionMenuProps> = ({
|
||||
|
||||
const handleActionClick = (action: AnnotationToolType) => {
|
||||
onActionSelect(action);
|
||||
if (selectedAction === action) {
|
||||
eventDispatcher.dispatch('toast', {
|
||||
type: 'info',
|
||||
timeout: 2000,
|
||||
message: _('Quick action disabled'),
|
||||
});
|
||||
} else {
|
||||
eventDispatcher.dispatch('toast', {
|
||||
type: 'info',
|
||||
timeout: 2000,
|
||||
message: _(annotationToolQuickActions.find((btn) => btn.type === action)?.tooltip || ''),
|
||||
});
|
||||
}
|
||||
setIsDropdownOpen?.(false);
|
||||
};
|
||||
|
||||
return (
|
||||
@@ -41,7 +55,8 @@ const QuickActionMenu: React.FC<QuickActionMenuProps> = ({
|
||||
key={button.type}
|
||||
label={_(button.label)}
|
||||
tooltip={_(button.tooltip)}
|
||||
Icon={selectedAction === button.type ? MdCheck : button.Icon}
|
||||
buttonClass={selectedAction === button.type ? 'bg-base-300/85' : ''}
|
||||
Icon={button.Icon}
|
||||
onClick={() => handleActionClick(button.type)}
|
||||
/>
|
||||
))}
|
||||
|
||||
@@ -34,7 +34,7 @@ export const useAnnotationEditor = ({
|
||||
const [handlePositions, setHandlePositions] = useState<HandlePositions | null>(null);
|
||||
|
||||
const getHandlePositionsFromRange = useCallback(
|
||||
(range: Range): HandlePositions | null => {
|
||||
(range: Range, isVertical: boolean): HandlePositions | null => {
|
||||
const gridFrame = document.querySelector(`#gridcell-${bookKey}`);
|
||||
if (!gridFrame) return null;
|
||||
|
||||
@@ -48,11 +48,11 @@ export const useAnnotationEditor = ({
|
||||
|
||||
return {
|
||||
start: {
|
||||
x: frameRect.left + firstRect.left,
|
||||
x: frameRect.left + (isVertical ? firstRect.right : firstRect.left),
|
||||
y: frameRect.top + firstRect.top,
|
||||
},
|
||||
end: {
|
||||
x: frameRect.left + lastRect.right,
|
||||
x: frameRect.left + (isVertical ? lastRect.left : lastRect.right),
|
||||
y: frameRect.top + lastRect.bottom,
|
||||
},
|
||||
};
|
||||
@@ -61,7 +61,7 @@ export const useAnnotationEditor = ({
|
||||
);
|
||||
|
||||
const handleAnnotationRangeChange = useCallback(
|
||||
async (startPoint: Point, endPoint: Point, isDragging: boolean) => {
|
||||
async (startPoint: Point, endPoint: Point, isVertical: boolean, isDragging: boolean) => {
|
||||
if (!editingAnnotationRef.current || !view) return;
|
||||
|
||||
const contents = view.renderer.getContents();
|
||||
@@ -132,7 +132,7 @@ export const useAnnotationEditor = ({
|
||||
return;
|
||||
}
|
||||
|
||||
const newPositions = getHandlePositionsFromRange(newRange);
|
||||
const newPositions = getHandlePositionsFromRange(newRange, isVertical);
|
||||
if (newPositions) {
|
||||
setHandlePositions(newPositions);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user