mirror of
https://github.com/Ku6epXBOCTuK/easy-png-tools.git
synced 2026-09-14 13:36:36 +00:00
docs: update docs - plan, archive
This commit is contained in:
@@ -0,0 +1,109 @@
|
||||
# План: единая дизайн-система
|
||||
|
||||
> **СТАТУС: ВЫПОЛНЕН 2026-08-22.**
|
||||
|
||||
## 1. Цели и принципы
|
||||
|
||||
1. **Один источник правды** — все визуальные константы живут в токенах `web/src/app.css`; ни один компонент не пишет «магические» цвета/размеры напрямую.
|
||||
2. **Никакого дублирования** — повторяющийся визуальный рецепт оформляется один раз: либо токен, либо глобальный утилитарный класс, либо компонент в `ui/`.
|
||||
3. **Простые компоненты важнее умных** — если проще сделать отдельные `SliderField` и `TextField`, делаем два маленьких компонента, а не один конфигурируемый монстр.
|
||||
4. **Scoped-стили — только для раскладки** конкретного компонента (сетка, отступы между блоками). Внешний вид контролов и текстов — из дизайн-системы.
|
||||
|
||||
## 2. Аудит: что дублируется прямо сейчас
|
||||
|
||||
- **«Приглушённый текст»** (`--text-muted` + размер 0.85–0.95rem) — повторён в семи местах: `ToolPage` (.description, .hint), главная (.lead, .card-desc), лейаут (.nav-link, footer), `Preview` (.dims), `ParamForm` (label), `InfoPanel` (dt).
|
||||
- **«Заголовок секции капсом»** (uppercase, letter-spacing, muted) — точная копипаста в `ToolPage` и на главной.
|
||||
- **Базовые инпуты** (padding, border, radius) — живут только внутри `ParamForm`; любые новые типы контролов (слайдеры, пипетка цвета) пришлось бы копировать вручную.
|
||||
- **Блок «label + отступы»** — дублировался бы в каждом `*Field`; решается обёрткой `Field.svelte`.
|
||||
- **«Панель»** (surface + border + radius) — три ручные реализации одного паттерна: `DropZone`, карточки главной, строки `InfoPanel`.
|
||||
- **Кнопки** — глобальные классы `.primary`/`.secondary` без компонента; состояния «занят/недоступен» каждый описывает заново.
|
||||
- **Ошибка-баннер** — только в `ToolPage`, а понадобится минимум ещё раз в workspace.
|
||||
|
||||
## 3. Токены (расширение `app.css`)
|
||||
|
||||
Существующие остаются; добавляются недостающие группы:
|
||||
|
||||
```css
|
||||
:root {
|
||||
/* типографика */
|
||||
--text-xs: 0.8rem;
|
||||
--text-s: 0.85rem;
|
||||
--text-m: 0.95rem;
|
||||
--text-l: 1.1rem;
|
||||
--text-xl: 1.25rem;
|
||||
--text-xxl: 1.75rem;
|
||||
--leading-tight: 1.25;
|
||||
|
||||
/* тени */
|
||||
--shadow-card: 0 4px 14px rgb(37 99 235 / 12%);
|
||||
|
||||
/* фокус */
|
||||
--focus-ring: 2px solid var(--accent);
|
||||
|
||||
/* контрольные размеры контролов */
|
||||
--control-height: 2.4rem;
|
||||
--control-max-width: 16rem;
|
||||
}
|
||||
```
|
||||
|
||||
Правило именования: семантика, не значение (`--text-muted`, а не `--gray-500`).
|
||||
|
||||
## 4. Глобальные утилиты (`app.css`)
|
||||
|
||||
Только то, что не требует JS и не является интерактивным:
|
||||
|
||||
- `.text-muted` — приглушённый текст (размер наследуется);
|
||||
- `.text-caption` — мелкий текст `--text-s`;
|
||||
- `.heading-section` — заголовок секции капсом (вместо двух копипаст);
|
||||
- `.panel` — поверхность с бордером/радиусом (DropZone, карточки, InfoPanel-строки);
|
||||
- `.error-banner` — баннер ошибки;
|
||||
- `.visually-hidden` — скрытый, но читаемый скринридерами.
|
||||
|
||||
Элементные стили из текущего `app.css` (голые `input, select { ... }`) убрать — внешний вид контролов переезжает в компоненты `ui/`.
|
||||
|
||||
## 5. Состав UI-компонентов (`web/src/lib/components/ui/`)
|
||||
|
||||
Все — тонкие обёртки над нативными элементами, без состояний кроме своих:
|
||||
|
||||
| Компонент | Пропы | Заменяет |
|
||||
| ---------------------- | -------------------------------------------------------------------------------- | ---------------------------------------------------------------------- |
|
||||
| `Field.svelte` | `id`, `label`, `hint?`, слот контрола | label + раскладка поля; единая точка для hint'ов и ошибок валидации |
|
||||
| `Button.svelte` | `variant: 'primary' \| 'secondary'`, `disabled`, `busy`, слот текста | глобальные `.primary`/`.secondary`, локальный busy в `DownloadButton` |
|
||||
| `TextField.svelte` | `id`, `label`, `type: 'number' \| 'text'`, `min/max/step`, `value = $bindable()` | number/text ветки `ParamForm` |
|
||||
| `SliderField.svelte` | `id`, `label`, `min/max/step`, `value = $bindable()`, показ значения рядом | ничего — готовая база для будущих слайдеров (яркость, качество, порог) |
|
||||
| `SelectField.svelte` | `id`, `label`, `options`, `value = $bindable()` | select-ветка `ParamForm` |
|
||||
| `CheckboxField.svelte` | `id`, `label`, `checked = $bindable()` | чекбокс `ParamForm` |
|
||||
| `ColorField.svelte` | `id`, `label`, `value = $bindable()` | color-ветка `ParamForm`; точка будущего расширения пипеткой |
|
||||
| `EmptyState.svelte` | `title`, `hint`, слот действия | «Обработка…», будущие пустые панели исходника/результата |
|
||||
|
||||
`Field` — чисто раскладочная обёртка (`<div class="field"><label for>…</label><slot /></div>`), ничего не знает о типе контрола. На ней построены `TextField`, `SliderField`, `SelectField`, `ColorField`. Исключение — `CheckboxField`: у чекбокса label оборачивает контрол (кликабельная строка), поэтому у него своя раскладка.
|
||||
|
||||
## 6. Миграция существующего
|
||||
|
||||
1. `ParamForm` — переписывается на `Field` + `*Field`-компоненты; собственная стилизация контролов и label удаляется.
|
||||
2. `DownloadButton` и кнопки `ToolPage` — на `Button`.
|
||||
3. `DropZone`, карточки главной, строки `InfoPanel` — на `.panel` + свои раскладочные стили.
|
||||
4. `ToolPage`, главная, лейаут — приглушённые тексты на `.text-*`, заголовки секций на `.heading-section`, баннер на `.error-banner`.
|
||||
5. Из `app.css` удалить глобальные классы кнопок и голые стили инпутов после перехода.
|
||||
|
||||
## 7. Порядок работ
|
||||
|
||||
| Этап | Содержимое | Проверка |
|
||||
| ------------ | ----------------------------------------------------- | -------------------------------------------------------------------- |
|
||||
| A. Фундамент | Токены + утилиты в `app.css` | `pnpm check` |
|
||||
| B. Примитивы | Восемь компонентов в `components/ui/` | `pnpm check`; каждый используется хотя бы раз после этапа C |
|
||||
| C. Миграция | Переделка пяти файлов из §6 | Визуально: страницы выглядят как раньше; `test/check/build` зелёные |
|
||||
| D. Чистка | Удаление устаревших классов и дублей из scoped-стилей | grep: в `components/` нет hex-цветов и `var(--space…)` вне раскладки |
|
||||
|
||||
## 8. Критерии готовности
|
||||
|
||||
- Ни один компонент не задаёт цвет/радиус/шрифт мимо токенов или классов системы.
|
||||
- Все интерактивные контролы на страницах — экземпляры `ui/*`.
|
||||
- `grep -E '#[0-9a-f]{3,6}' web/src/lib/components` не находит ничего, кроме токенов в `app.css`.
|
||||
- Визуальной регрессии нет (ручная сверка всех страниц до/после), тесты и билд зелёные.
|
||||
|
||||
## 9. Что сознательно не делаем
|
||||
|
||||
- Никаких CSS-фреймворков и препроцессоров — обычный CSS, как решено на старте.
|
||||
- Никаких тем/тёмного режима — но токены сразу семантические, чтобы тема добавилась заменой значений `:root`.
|
||||
- Никаких сложных составных компонентов (модалки, таблицы) — они появятся, когда потребуется реальная задача.
|
||||
@@ -1,8 +1,9 @@
|
||||
# План начальной реализации: минимальный рабочий MVP
|
||||
|
||||
> **СТАТУС: ВЫПОЛНЕН И ЗАКРЫТ 2026-08-22. Документ перенесён в архив** (`docs/archive/`) и больше не развивается.
|
||||
> **СТАТУС: ВЫПОЛНЕН 2026-08-22.**
|
||||
>
|
||||
> **Что сделано по плану:**
|
||||
>
|
||||
> - Шаг 0 — каркас: SvelteKit + adapter-static, design-токены на CSS, лейаут с навигацией по категориям ✔
|
||||
> - Шаг 1 — ядро: `PixelImage`, декодирование файлов, кодирование PNG/JPEG/WebP, скачивание ✔
|
||||
> - Шаг 2 — операции ядра: color / geometry / alpha / analyze, чистые функции без DOM ✔
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
# План: раскладка «исходник слева — результат справа» + вставка изображения
|
||||
|
||||
> **СТАТУС: ВЫПОЛНЕН 2026-08-22.**
|
||||
>
|
||||
> **Что сделано по этапам:**
|
||||
>
|
||||
> - A — общая MIME-валидация `isSupportedImage` + `unsupportedImageMessage` в `core/io.ts`; `DropZone` переведён на них ✔
|
||||
> - B — постоянная двухпанельная раскладка: одна карточка-«сцена» с равными половинами «Исходник | Результат», кнопки внутри своих половин, параметры — отдельной карточкой ниже; контент сайта растянут на всю ширину окна ✔
|
||||
> - C — Ctrl+V: `<svelte:window onpaste>`, поиск image-item в буфере, общая валидация; текстовые вставки не затрагиваются ✔
|
||||
> - D — drag-замена: компонент `DropOverlay` с вуалью-подсказкой и счётчиком `dragenter/dragleave` против мерцания; тот же счётчик в `DropZone` ✔
|
||||
> - E — верификация: тесты 85/85, svelte-check 0 ошибок, статический билд с 11 страницами; ручной смоук-чеклист §5 пройден ✔
|
||||
>
|
||||
> **Отступления от исходного плана в ходе реализации:** вместо пропа `overlay` у `DropZone` сделан отдельный компонент-обёртка `DropOverlay` (drag-события ловит контент под курсором); раскладка доработана по ревью — одна карточка равных половин вместо двух независимых колонок, сайт без ограничения ширины.
|
||||
|
||||
## 1. Целевое поведение
|
||||
|
||||
1. **Постоянная двухпанельная раскладка** страницы инструмента — отдельного состояния «страница загрузки» больше нет:
|
||||
- **Слева — исходник**, панель есть всегда. Не загружено → пустое дроп-поле (drag&drop или клик). Загружено → превью исходника.
|
||||
- **Справа — результат**: до загрузки — `EmptyState` «Результат появится здесь»; во время обработки — `EmptyState` «Обработка…»; после — превью результата (для info-инструмента — `InfoPanel`).
|
||||
2. **Ctrl+V** — картинка из буфера обмена вставляется как исходник с любого места страницы.
|
||||
3. **Замена перетаскиванием** — новый файл, брошенный на левую панель с загруженной картинкой, заменяет исходник (с оверлеем-подсказкой во время перетаскивания).
|
||||
4. **Кнопка «Заменить»** под превью исходника (переносится туда текущая «Загрузить другое изображение»).
|
||||
|
||||
На мобильной ширине панели складываются в столбец: исходник сверху, результат снизу.
|
||||
|
||||
## 2. Изменения по файлам
|
||||
|
||||
Новые компоненты не вводим — `EmptyState`, `Preview`, `DropZone` покрывают всё. Меняем три файла:
|
||||
|
||||
- **`core/io.ts`** — экспорт `isSupportedImage(file): boolean`: общая MIME-проверка на базе `ACCEPTED_IMAGE_TYPES`.
|
||||
- **`DropZone.svelte`** — переходит на `isSupportedImage`; новый опциональный проп `overlay` — режим «невидимая зона приёма поверх контента» для замены перетаскиванием.
|
||||
- **`ToolPage.svelte`** — постоянная сетка из двух панелей; `<svelte:window onpaste>`; drag-оверлей левой панели; кнопки замены/сброса переезжают в левую панель; правая панель — три состояния через `EmptyState`.
|
||||
|
||||
## 3. Этапы и проверки
|
||||
|
||||
- **A. Общая валидация** — `isSupportedImage` в `io.ts`, `DropZone` переведён на неё. Проверка: `pnpm check`.
|
||||
- **B. Раскладка** — две постоянные колонки в ToolPage; правая панель через EmptyState; кнопки уходят в левую панель; обёртка `{#if !source}` на всю страницу убирается. Проверка: визуальный осмотр всех инструментов, `check/build`.
|
||||
- **C. Ctrl+V** — `onpaste` на окне → поиск image-item в `clipboardData` → тот же `handleFile`; не-изображения игнорируются молча. Проверка: ручной смоук со скриншотом из буфера.
|
||||
- **D. Drag-замена** — оверлей DropZone(overlay) при dragover на левой панели; drop → `handleFile` нового файла. Проверка: ручной смоук «бросить файл на загруженный исходник».
|
||||
- **E. Итог** — полный ручной смоук-чеклист (§5). Проверка: `test/check/build` зелёные.
|
||||
|
||||
## 4. Нюансы и решения
|
||||
|
||||
- **Мерцание dragleave** — классическая проблема оверлеев: используем счётчик `dragenter/dragleave`, оверлей скрывается только когда счётчик обнулился и произошёл `drop`/`dragend`.
|
||||
- **Paste при фокусе в поле параметров** — обрабатываем всегда: если в буфере именно изображение, пользователь очевидно хочет заменить исходник; текстовые вставки обработчик не трогает.
|
||||
- **Форматы буфера** — скриншоты приходят как PNG, часть приложений отдаёт JPEG/WebP; все проходят общую `isSupportedImage`. GIF/BMP из буфера редки, но тоже допустимы.
|
||||
- **Info-инструменты** — ведут себя так же: слева исходник, справа после загрузки появляется `InfoPanel`.
|
||||
- **Состояние ошибки** — баннер остаётся сверху страницы; неудачная замена не сбрасывает старый исходник.
|
||||
|
||||
## 5. Чеклист ручного смоука
|
||||
|
||||
1. Пустые обе панели → drag&drop файла слева → появился исходник и результат.
|
||||
2. Клик по пустой левой панели → выбор файла.
|
||||
3. Ctrl+V со скриншотом → исходник заменился, результат пересчитался.
|
||||
4. Drag файла на загруженную левую панель → оверлей виден, после drop — замена.
|
||||
5. Отмена перетаскивания (Esc / мимо) → оверлей скрылся, исходник не изменился.
|
||||
6. Бросок неподдерживаемого файла (например .txt) → баннер ошибки, старый исходник цел.
|
||||
7. Кнопка «Заменить» открывает выбор файла.
|
||||
8. png-info: работает та же схема, справа сводка.
|
||||
9. Мобильная ширина: панели в столбец, порядок исходник → результат.
|
||||
10. Все 11 инструментов отвечают на замену исходника корректным пересчётом.
|
||||
|
||||
## 6. Критерии готовности
|
||||
|
||||
- На странице инструмента нет состояния «только дропзона на всю страницу» — раскладка двухпанельная всегда.
|
||||
- Загрузка работает тремя способами: клик, drag&drop (включая замену), Ctrl+V.
|
||||
- Ошибочные файлы и отмена перетаскивания не портят текущий исходник.
|
||||
- `test/check/build` зелёные; чеклист §5 пройден.
|
||||
|
||||
## 7. Что сознательно не делаем (граница скоупа этого плана)
|
||||
|
||||
- Множественная загрузка / пакетная обработка — вне скоупа этого плана.
|
||||
- Камера/скриншот-инструменты захвата — только буфер обмена.
|
||||
- Прогресс-бары декодирования — файлы читаются быстро, достаточно индикации «Обработка…».
|
||||
Reference in New Issue
Block a user