mirror of
https://github.com/nexu-io/open-design.git
synced 2026-06-01 03:14:35 +07:00
fix(web): replace SketchEditor text prompt with modal
Replace the SketchEditor text tool's window.prompt flow with an in-app modal so it works in Electron desktop.
This commit is contained in:
parent
0b2b456694
commit
01c22a87bd
18 changed files with 91 additions and 7 deletions
|
|
@ -73,6 +73,13 @@ export function SketchEditor({
|
|||
const [size, setSize] = useState(2);
|
||||
const drawingRef = useRef<SketchItem | null>(null);
|
||||
const [, force] = useState(0);
|
||||
// Text-tool modal. Replaces window.prompt() because Electron 28+
|
||||
// disables that API by default and silently returns null, making
|
||||
// the text tool a no-op in the desktop app. Same root cause as
|
||||
// issue #723 (FileViewer's Save-as-template flow).
|
||||
const [textModalOpen, setTextModalOpen] = useState(false);
|
||||
const [textModalValue, setTextModalValue] = useState('');
|
||||
const textAnchorRef = useRef<{ x: number; y: number } | null>(null);
|
||||
|
||||
// Resize canvas to its container while keeping a high DPR for crisp lines.
|
||||
useEffect(() => {
|
||||
|
|
@ -126,13 +133,11 @@ export function SketchEditor({
|
|||
const pos = pointerPos(e);
|
||||
|
||||
if (tool === 'text') {
|
||||
const text = window.prompt(t('sketch.textPrompt'));
|
||||
if (text) {
|
||||
onItemsChange([
|
||||
...items,
|
||||
{ kind: 'text', x: pos.x, y: pos.y, text, color, size: 16 + size * 4 },
|
||||
]);
|
||||
}
|
||||
// Stash the click position and open the modal. The actual TextItem is
|
||||
// appended in submitTextModal, once the user confirms.
|
||||
textAnchorRef.current = pos;
|
||||
setTextModalValue('');
|
||||
setTextModalOpen(true);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -189,6 +194,28 @@ export function SketchEditor({
|
|||
onItemsChange([]);
|
||||
}
|
||||
|
||||
function submitTextModal() {
|
||||
const text = textModalValue.trim();
|
||||
const anchor = textAnchorRef.current;
|
||||
if (!text || !anchor) {
|
||||
cancelTextModal();
|
||||
return;
|
||||
}
|
||||
onItemsChange([
|
||||
...items,
|
||||
{ kind: 'text', x: anchor.x, y: anchor.y, text, color, size: 16 + size * 4 },
|
||||
]);
|
||||
setTextModalOpen(false);
|
||||
setTextModalValue('');
|
||||
textAnchorRef.current = null;
|
||||
}
|
||||
|
||||
function cancelTextModal() {
|
||||
setTextModalOpen(false);
|
||||
setTextModalValue('');
|
||||
textAnchorRef.current = null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="sketch-editor">
|
||||
<div className="sketch-toolbar">
|
||||
|
|
@ -250,6 +277,46 @@ export function SketchEditor({
|
|||
style={{ touchAction: 'none' }}
|
||||
/>
|
||||
</div>
|
||||
{textModalOpen ? (
|
||||
<div className="modal-backdrop" role="presentation">
|
||||
<div className="modal" role="dialog" aria-modal="true">
|
||||
<div className="modal-head">
|
||||
<h2>{t('sketch.textModalTitle')}</h2>
|
||||
</div>
|
||||
<label>
|
||||
<span>{t('sketch.textPrompt')}</span>
|
||||
<input
|
||||
type="text"
|
||||
value={textModalValue}
|
||||
autoFocus
|
||||
onChange={(e) => setTextModalValue(e.target.value)}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === 'Enter' && textModalValue.trim()) {
|
||||
e.preventDefault();
|
||||
submitTextModal();
|
||||
} else if (e.key === 'Escape') {
|
||||
e.preventDefault();
|
||||
cancelTextModal();
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</label>
|
||||
<div className="modal-foot">
|
||||
<button type="button" className="ghost" onClick={cancelTextModal}>
|
||||
{t('common.cancel')}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="primary"
|
||||
disabled={!textModalValue.trim()}
|
||||
onClick={submitTextModal}
|
||||
>
|
||||
{t('common.save')}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -818,6 +818,7 @@ export const ar: Dict = {
|
|||
'sketch.clear': 'مسح',
|
||||
'sketch.close': 'إغلاق',
|
||||
'sketch.textPrompt': 'النص:',
|
||||
'sketch.textModalTitle': 'إضافة نص',
|
||||
|
||||
'pet.title': 'الحيوانات الأليفة',
|
||||
'pet.subtitle': 'تبنَّ رفيقاً صغيراً يطفو فوق مساحة عملك.',
|
||||
|
|
|
|||
|
|
@ -774,6 +774,7 @@ export const de: Dict = {
|
|||
'sketch.clear': 'Leeren',
|
||||
'sketch.close': 'Schließen',
|
||||
'sketch.textPrompt': 'Text:',
|
||||
'sketch.textModalTitle': 'Text hinzufügen',
|
||||
|
||||
'pet.title': 'Haustiere',
|
||||
'pet.tabBuiltIn': 'Vorgegeben',
|
||||
|
|
|
|||
|
|
@ -851,6 +851,7 @@ export const en: Dict = {
|
|||
'sketch.clear': 'Clear',
|
||||
'sketch.close': 'Close',
|
||||
'sketch.textPrompt': 'Text:',
|
||||
'sketch.textModalTitle': 'Add text',
|
||||
|
||||
'pet.title': 'Pets',
|
||||
'pet.subtitle': 'Adopt a tiny companion that floats over your workspace.',
|
||||
|
|
|
|||
|
|
@ -775,6 +775,7 @@ export const esES: Dict = {
|
|||
'sketch.clear': 'Limpiar',
|
||||
'sketch.close': 'Cerrar',
|
||||
'sketch.textPrompt': 'Texto:',
|
||||
'sketch.textModalTitle': 'Añadir texto',
|
||||
|
||||
'pet.title': 'Mascotas',
|
||||
'pet.tabBuiltIn': 'Integradas',
|
||||
|
|
|
|||
|
|
@ -852,6 +852,7 @@ export const fa: Dict = {
|
|||
'sketch.clear': 'پاک کردن',
|
||||
'sketch.close': 'بستن',
|
||||
'sketch.textPrompt': 'متن:',
|
||||
'sketch.textModalTitle': 'افزودن متن',
|
||||
|
||||
'pet.title': 'حیوان خانگی',
|
||||
'pet.tabBuiltIn': 'پیشفرض',
|
||||
|
|
|
|||
|
|
@ -818,6 +818,7 @@ export const fr: Dict = {
|
|||
'sketch.clear': 'Effacer',
|
||||
'sketch.close': 'Fermer',
|
||||
'sketch.textPrompt': 'Texte :',
|
||||
'sketch.textModalTitle': 'Ajouter du texte',
|
||||
|
||||
'pet.title': 'Compagnons',
|
||||
'pet.subtitle': 'Adoptez un petit compagnon qui flotte au-dessus de votre espace de travail.',
|
||||
|
|
|
|||
|
|
@ -820,6 +820,7 @@ export const hu: Dict = {
|
|||
'sketch.clear': 'Törlés',
|
||||
'sketch.close': 'Bezárás',
|
||||
'sketch.textPrompt': 'Szöveg:',
|
||||
'sketch.textModalTitle': 'Szöveg hozzáadása',
|
||||
|
||||
'pet.title': 'Háziállatok',
|
||||
'pet.subtitle': 'Fogadj be egy apró társat, aki a munkaterületed felett lebeg.',
|
||||
|
|
|
|||
|
|
@ -773,6 +773,7 @@ export const ja: Dict = {
|
|||
'sketch.clear': 'クリア',
|
||||
'sketch.close': '閉じる',
|
||||
'sketch.textPrompt': 'テキスト:',
|
||||
'sketch.textModalTitle': 'テキストを追加',
|
||||
|
||||
'pet.title': 'ペット',
|
||||
'pet.tabBuiltIn': '組み込み',
|
||||
|
|
|
|||
|
|
@ -820,6 +820,7 @@ export const ko: Dict = {
|
|||
'sketch.clear': '모두 지우기',
|
||||
'sketch.close': '닫기',
|
||||
'sketch.textPrompt': '텍스트:',
|
||||
'sketch.textModalTitle': '텍스트 추가',
|
||||
|
||||
'pet.title': '펫',
|
||||
'pet.tabBuiltIn': '내장',
|
||||
|
|
|
|||
|
|
@ -820,6 +820,7 @@ export const pl: Dict = {
|
|||
'sketch.clear': 'Wyczyść',
|
||||
'sketch.close': 'Zamknij',
|
||||
'sketch.textPrompt': 'Tekst:',
|
||||
'sketch.textModalTitle': 'Dodaj tekst',
|
||||
|
||||
'pet.title': 'Pupile',
|
||||
'pet.tabBuiltIn': 'Wbudowane',
|
||||
|
|
|
|||
|
|
@ -850,6 +850,7 @@ export const ptBR: Dict = {
|
|||
'sketch.clear': 'Limpar',
|
||||
'sketch.close': 'Fechar',
|
||||
'sketch.textPrompt': 'Texto:',
|
||||
'sketch.textModalTitle': 'Adicionar texto',
|
||||
|
||||
'pet.title': 'Bichinhos',
|
||||
'pet.tabBuiltIn': 'Inclusos',
|
||||
|
|
|
|||
|
|
@ -850,6 +850,7 @@ export const ru: Dict = {
|
|||
'sketch.clear': 'Очистить',
|
||||
'sketch.close': 'Закрыть',
|
||||
'sketch.textPrompt': 'Текст:',
|
||||
'sketch.textModalTitle': 'Добавить текст',
|
||||
|
||||
'pet.title': 'Питомцы',
|
||||
'pet.tabBuiltIn': 'Встроенные',
|
||||
|
|
|
|||
|
|
@ -812,6 +812,7 @@ export const tr: Dict = {
|
|||
'sketch.clear': 'Temizle',
|
||||
'sketch.close': 'Kapat',
|
||||
'sketch.textPrompt': 'Metin:',
|
||||
'sketch.textModalTitle': 'Metin ekle',
|
||||
|
||||
'pet.title': 'Evcil Dostlar',
|
||||
'pet.tabBuiltIn': 'Yerleşik',
|
||||
|
|
|
|||
|
|
@ -851,6 +851,7 @@ export const uk: Dict = {
|
|||
'sketch.clear': 'Очистити',
|
||||
'sketch.close': 'Закрити',
|
||||
'sketch.textPrompt': 'Текст:',
|
||||
'sketch.textModalTitle': 'Додати текст',
|
||||
|
||||
'pet.title': 'Маскоти',
|
||||
'pet.subtitle': 'Встановити крихітного супутника, який пливе над вашим робочим простором.',
|
||||
|
|
|
|||
|
|
@ -832,6 +832,7 @@ export const zhCN: Dict = {
|
|||
'sketch.clear': '清空',
|
||||
'sketch.close': '关闭',
|
||||
'sketch.textPrompt': '请输入文本:',
|
||||
'sketch.textModalTitle': '添加文本',
|
||||
|
||||
'pet.title': '宠物',
|
||||
'pet.tabBuiltIn': '内置',
|
||||
|
|
|
|||
|
|
@ -832,6 +832,7 @@ export const zhTW: Dict = {
|
|||
'sketch.clear': '清除',
|
||||
'sketch.close': '關閉',
|
||||
'sketch.textPrompt': '請輸入文字:',
|
||||
'sketch.textModalTitle': '新增文字',
|
||||
|
||||
'pet.title': '寵物',
|
||||
'pet.tabBuiltIn': '內建',
|
||||
|
|
|
|||
|
|
@ -1024,4 +1024,5 @@ export interface Dict {
|
|||
'sketch.clear': string;
|
||||
'sketch.close': string;
|
||||
'sketch.textPrompt': string;
|
||||
'sketch.textModalTitle': string;
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue