From 8fccc1e36bc36b3209e2c3b6677a5d0e9cfb21c1 Mon Sep 17 00:00:00 2001 From: Ku6epXBOCTuK Date: Wed, 18 Feb 2026 09:07:15 +0500 Subject: [PATCH] feat: update upload serive, start update image manager --- plans/FUNCTIONAL_ROADMAP.md | 2 +- plans/TECHNICAL_ROADMAP.md | 18 +- src/components/image/CropInline.svelte | 3 + src/components/image/ImageManager.svelte | 50 ++++- src/lib/constants.ts | 21 +- src/routes/+layout.svelte | 12 +- src/services/uploadService.ts | 51 +++++ src/states/image.svelte.ts | 50 +++++ src/states/imageConfig.svelte.ts | 98 -------- .../unit/__snapshots__/constants.test.ts.snap | 6 + tests/unit/states/imageConfig.test.ts | 212 +----------------- 11 files changed, 192 insertions(+), 331 deletions(-) create mode 100644 src/services/uploadService.ts create mode 100644 src/states/image.svelte.ts delete mode 100644 src/states/imageConfig.svelte.ts diff --git a/plans/FUNCTIONAL_ROADMAP.md b/plans/FUNCTIONAL_ROADMAP.md index cd52b0d..8b470c2 100644 --- a/plans/FUNCTIONAL_ROADMAP.md +++ b/plans/FUNCTIONAL_ROADMAP.md @@ -94,7 +94,7 @@ - **Контракт:** `filter: brightness(${brightness}%) contrast(${contrast}%)` 4. **Сохранить обрезанное изображение**: - - Метод `imageConfigState.applyCrop()` должен обновлять `image` с обрезанными данными + - Метод `imageState.applyCrop()` должен обновлять `image` с обрезанными данными - Использовать canvas для actual cropping ### 2.2 Валидация и состояния загрузки (Medium Priority) diff --git a/plans/TECHNICAL_ROADMAP.md b/plans/TECHNICAL_ROADMAP.md index 8d765ee..30919a0 100644 --- a/plans/TECHNICAL_ROADMAP.md +++ b/plans/TECHNICAL_ROADMAP.md @@ -52,18 +52,18 @@ export const textsState = createState(); export const konvaAllStagesState: Array = $state([]); // ПЛОХО - класс с $state полями, но без фабрики -export class ImageConfigState { +export class imageState { image = $state(undefined); imageLink = $state(""); // ... } -export const imageConfigState = new ImageConfigState(); +export const imageState = new imageState(); ``` **Проблемы:** - `konvaAllStagesState` - глобальный изменяемый массив, нет контроля над операциями -- `ImageConfigState` - класс с $state, но не фабрика, сложно тестировать +- `imageState` - класс с $state, но не фабрика, сложно тестировать - Нет единого подхода #### Паттерн C: Смешанный (⚠️ НЕПРАВИЛЬНО) @@ -151,14 +151,14 @@ export const imageConfigState = new ImageConfigState(); **Оценка:** ⚠️ **Частично реализовано (нефункционально)** - ImageManager.svelte — UI есть, но кнопки не работают (Upload, Edit, Reset) -- CropInline.svelte — визуальный crop box с handles, но нет интеграции с cropperjs и imageConfigState +- CropInline.svelte — визуальный crop box с handles, но нет интеграции с cropperjs и imageState - Настройки яркости/контраста — только UI, нет логики применения - **Вывод:** Компоненты выглядят готовыми, но функциональность отсутствует **Рекомендации (High Priority):** - Реализовать imageService.ts и интегрировать в ImageManager -- Добавить логику crop в CropInline (drag, resize, update imageConfigState) +- Добавить логику crop в CropInline (drag, resize, update imageState) - Применить фильтры яркости/контраста к Konva Image в Preview.svelte --- @@ -356,7 +356,7 @@ interface ImageService { **Архитектура:** ``` -textsState (массив текстов) + textConfigState (глобальные настройки) + imageConfigState (глобальное изображение) +textsState (массив текстов) + textConfigState (глобальные настройки) + imageState (глобальное изображение) ↓ PreviewManager (навигация по textsState) ↓ @@ -378,7 +378,7 @@ downloadService (экспорт) - `TextManager` → `textsState.addText()` / `removeText()` - `PreviewManager` → читает `textsState.texts` для навигации -- `Preview` → читает `textsState.texts[current]` + `textConfigState` + `imageConfigState` +- `Preview` → читает `textsState.texts[current]` + `textConfigState` + `imageState` Это **достаточно** для MVP. @@ -559,7 +559,7 @@ it("should create panel with text and export it", async () => { **2. Изображение flow:** ``` -ImageManager → imageService → imageConfigState → Preview → export +ImageManager → imageService → imageState → Preview → export ``` **Тест:** [`tests/integration/image-flow.test.ts`](tests/integration/image-flow.test.ts) @@ -567,7 +567,7 @@ ImageManager → imageService → imageConfigState → Preview → export ```typescript it("should upload image, crop it, and apply to panel", async () => { // 1. Загрузить изображение (mock file) - // 2. Проверить imageConfigState.image + // 2. Проверить imageState.image // 3. Установить crop values // 4. Проверить Preview отображает обрезанное изображение }); diff --git a/src/components/image/CropInline.svelte b/src/components/image/CropInline.svelte index 1e4e63a..e857aa8 100644 --- a/src/components/image/CropInline.svelte +++ b/src/components/image/CropInline.svelte @@ -1,8 +1,11 @@
+ Background for use
diff --git a/src/components/image/ImageManager.svelte b/src/components/image/ImageManager.svelte index 0996c3d..d078e70 100644 --- a/src/components/image/ImageManager.svelte +++ b/src/components/image/ImageManager.svelte @@ -4,17 +4,25 @@ import SettingsRow from "$components/layout/SettingsRow.svelte"; import Button from "$components/ui/Button.svelte"; import RangeSlider from "$components/ui/RangeSlider.svelte"; - // import { Pencil, RotateCcw as Reset, Upload } from "@lucide/svelte"; - // import Pencil from "@lucide/svelte/icons/pencil"; - // import Reset from "@lucide/svelte/icons/rotate-ccw"; - // import Upload from "@lucide/svelte/icons/upload"; + import { IMAGE_SETTINGS } from "$lib/constants"; + import { uploadService } from "$services/uploadService"; + import { imageState } from "$states/image.svelte"; import Pencil from "~icons/lucide/pencil"; import Reset from "~icons/lucide/rotate-ccw"; import Upload from "~icons/lucide/upload"; import CropInline from "./CropInline.svelte"; - let brightness = $state(100); - let contrast = $state(100); + let brightness = $state(IMAGE_SETTINGS.DEFAULT_BRIGHTNESS); + let contrast = $state(IMAGE_SETTINGS.DEFAULT_CONTRAST); + let hue = $state(IMAGE_SETTINGS.DEFAULT_HUE); + let chroma = $state(IMAGE_SETTINGS.DEFAULT_CHROMA); + + async function handlePaste(event: ClipboardEvent) { + let image = await uploadService.fromClipboard(event); + if (image.ok) { + imageState.fullImage = image.data; + } + } @@ -22,22 +30,42 @@
-
+ + + + + + - + - +
+ +