5.4.0: Картинка желания по ссылке и единая высота инпутов
All checks were successful
Build and Push Docker Image / build-and-push (push) Successful in 1m3s

This commit is contained in:
poignatov
2026-02-24 17:06:44 +03:00
parent ad52cf93ea
commit 7bbd732d72
4 changed files with 215 additions and 12 deletions

View File

@@ -35,6 +35,8 @@ function WishlistForm({ onNavigate, wishlistId, editConditionIndex, newTaskId, b
const [fetchingMetadata, setFetchingMetadata] = useState(false)
const [restoredFromSession, setRestoredFromSession] = useState(false) // Флаг восстановления из sessionStorage
const [loadedWishlistData, setLoadedWishlistData] = useState(null) // Данные желания для последующего маппинга условий
const [imageUrlInput, setImageUrlInput] = useState('') // Ссылка на картинку для загрузки по URL
const [loadingImageFromUrl, setLoadingImageFromUrl] = useState(false)
const fileInputRef = useRef(null)
// Загрузка задач, проектов и саджестов групп
@@ -237,6 +239,8 @@ function WishlistForm({ onNavigate, wishlistId, editConditionIndex, newTaskId, b
setCrop({ x: 0, y: 0 })
setZoom(1)
setCroppedAreaPixels(null)
setImageUrlInput('')
setLoadingImageFromUrl(false)
setShowConditionForm(false)
setEditingConditionIndex(null)
setToastMessage(null)
@@ -300,6 +304,8 @@ function WishlistForm({ onNavigate, wishlistId, editConditionIndex, newTaskId, b
setImageUrl(null)
setImageFile(null)
setImageRemoved(false)
setImageUrlInput('')
setLoadingImageFromUrl(false)
setUnlockConditions([])
setGroupName('')
setError('')
@@ -434,6 +440,53 @@ function WishlistForm({ onNavigate, wishlistId, editConditionIndex, newTaskId, b
reader.readAsDataURL(file)
}
// Загрузка картинки по ссылке с последующим кропом
const loadImageFromUrl = async () => {
const url = imageUrlInput?.trim()
if (!url) {
setToastMessage({ text: 'Введите ссылку на картинку', type: 'error' })
return
}
try {
new URL(url)
} catch {
setToastMessage({ text: 'Некорректная ссылка на картинку', type: 'error' })
return
}
setLoadingImageFromUrl(true)
try {
const proxyUrl = `${API_URL}/proxy-image?url=${encodeURIComponent(url)}`
const imgResponse = await authFetch(proxyUrl)
if (!imgResponse.ok) {
const errData = await imgResponse.json().catch(() => ({}))
throw new Error(errData.message || errData.error || 'Не удалось загрузить картинку')
}
const blob = await imgResponse.blob()
if (blob.size > 5 * 1024 * 1024) {
setToastMessage({ text: 'Картинка слишком большая (максимум 5MB)', type: 'error' })
return
}
if (!blob.type.startsWith('image/')) {
setToastMessage({ text: 'По ссылке не изображение', type: 'error' })
return
}
const reader = new FileReader()
reader.onload = () => {
setImageUrl(reader.result)
setImageFile(blob)
setImageRemoved(false)
setImageUrlInput('')
setShowCropper(true)
}
reader.readAsDataURL(blob)
} catch (err) {
setToastMessage({ text: err.message || 'Ошибка при загрузке картинки по ссылке', type: 'error' })
} finally {
setLoadingImageFromUrl(false)
}
}
const onCropComplete = (croppedArea, croppedAreaPixels) => {
setCroppedAreaPixels(croppedAreaPixels)
}
@@ -788,14 +841,51 @@ function WishlistForm({ onNavigate, wishlistId, editConditionIndex, newTaskId, b
</button>
</div>
)}
<input
ref={fileInputRef}
type="file"
accept="image/*"
onChange={handleImageSelect}
className="form-input"
style={{ display: imageUrl ? 'none' : 'block' }}
/>
{!imageUrl && (
<>
<div className="image-url-row">
<span className="image-url-label">Файл:</span>
<label className="file-input-label">
<input
ref={fileInputRef}
type="file"
accept="image/*"
onChange={handleImageSelect}
className="file-input-hidden"
/>
<span className="file-input-button">Выбрать</span>
</label>
</div>
<div className="image-url-row">
<span className="image-url-label">Ссылка:</span>
<input
type="url"
value={imageUrlInput}
onChange={(e) => setImageUrlInput(e.target.value)}
placeholder="https://..."
className="form-input image-url-input"
disabled={loadingImageFromUrl}
/>
<button
type="button"
className="image-url-load-button"
onClick={loadImageFromUrl}
disabled={loadingImageFromUrl || !imageUrlInput.trim()}
title="Загрузить картинку по ссылке и обрезать"
>
{loadingImageFromUrl ? (
<div className="mini-spinner"></div>
) : (
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/>
<polyline points="7 10 12 15 17 10"/>
<line x1="12" y1="15" x2="12" y2="3"/>
</svg>
)}
</button>
</div>
</>
)}
</div>
{showCropper && (