import React, { useEffect, useState } from 'react'; import { createPortal } from 'react-dom'; import { ipcChannels } from '../../shared/ipc/contracts'; import type { AppToken } from '../../shared/types'; import { filterMaterialImagePaths, getDroppedFileEntries, pickFirstMaterialImagePath, useFileDropZone, } from '../editor/fileDrop'; import editorStyles from '../editor/EditorApp.module.css'; import matStyles from '../editor/MaterialsModals.module.css'; import { getDndApi } from '../shared/dndApi'; import { Button, Input } from '../shared/ui/controls'; import { useTokenImageUrl } from '../shared/tokens/useTokenImageUrl'; type Props = { open: boolean; initial: AppToken | null; existingNames: string[]; onClose: () => void; onSaved: (token: AppToken) => void; }; function normalizeName(input: string): string { return input.trim().toLowerCase(); } export function TokenEditModal({ open, initial, existingNames, onClose, onSaved }: Props) { const api = getDndApi(); const [name, setName] = useState(''); const [filePath, setFilePath] = useState(null); const [localPreviewUrl, setLocalPreviewUrl] = useState(null); const [saving, setSaving] = useState(false); const [error, setError] = useState(null); const existingUrl = useTokenImageUrl(initial?.id ?? null); useEffect(() => { if (!open) return; setName(initial?.name ?? ''); setFilePath(null); setLocalPreviewUrl(null); setSaving(false); setError(null); }, [open, initial]); useEffect(() => { if (!open) return; const onKey = (e: KeyboardEvent) => { if (e.key === 'Escape') onClose(); }; window.addEventListener('keydown', onKey); return () => window.removeEventListener('keydown', onKey); }, [onClose, open]); const setPreviewFromPathAndUrl = (path: string, previewUrl: string) => { setFilePath(path); setLocalPreviewUrl((prev) => { if (prev?.startsWith('blob:')) URL.revokeObjectURL(prev); return previewUrl || null; }); }; const drop = useFileDropZone({ onDropPaths: (paths) => { const picked = pickFirstMaterialImagePath(paths); if (!picked) return; setPreviewFromPathAndUrl(picked, ''); }, filterPaths: filterMaterialImagePaths, }); const trimmed = name.trim(); const nameOk = trimmed.length >= 1; const nameDup = nameOk && existingNames.some( (n) => normalizeName(n) === normalizeName(trimmed) && normalizeName(n) !== normalizeName(initial?.name ?? ''), ); const hasImage = Boolean(filePath) || Boolean(initial); const canSave = nameOk && !nameDup && hasImage && !saving; const previewSrc = localPreviewUrl || existingUrl; if (!open) return null; return createPortal( <>
Название
{!nameOk ?
Укажите название
: null} {nameDup ?
Такое название уже есть
: null}
Изображение
{ drop.onDrop(e); const entries = getDroppedFileEntries(e); const files = e.dataTransfer.files; for (let i = 0; i < entries.length; i += 1) { const entry = entries[i]; if (!entry || !pickFirstMaterialImagePath([entry.path])) continue; const file = files[i]; if (file) { setPreviewFromPathAndUrl(entry.path, URL.createObjectURL(file)); return; } setPreviewFromPathAndUrl(entry.path, ''); return; } }} > {drop.dragOver ? (
Отпустите, чтобы загрузить изображение
) : null} {previewSrc ? ( ) : (
Перетащите изображение или выберите файл
)}
{!hasImage ?
Нужно изображение
: null}
{error ?
{error}
: null}
, document.body, ); }