From f1f2e74842c0a8c252270df096d09ca8127573e5 Mon Sep 17 00:00:00 2001 From: Ku6epXBOCTuK Date: Wed, 4 Feb 2026 09:26:47 +0500 Subject: [PATCH] feat: move to new workflow, create workflow and mvp plan --- PROJECT_PLAN.md | 189 +++++++++++++++++++++++++++++ src/components/PanelPreview.svelte | 46 +------ src/components/TextManager.svelte | 84 ++++++++++++- src/routes/+page.svelte | 42 +++++-- 4 files changed, 303 insertions(+), 58 deletions(-) create mode 100644 PROJECT_PLAN.md diff --git a/PROJECT_PLAN.md b/PROJECT_PLAN.md new file mode 100644 index 0000000..d2ff11b --- /dev/null +++ b/PROJECT_PLAN.md @@ -0,0 +1,189 @@ +# Twitch Panels Creator - Project Plan + +## MVP (Minimum Viable Product) + +### Цель MVP +Создать инструмент для быстрого создания нескольких Twitch панелей в едином стиле с разными текстами. + +### Функциональность MVP + +#### 1. Загрузка фонового изображения +- Автоматическая загрузка фонового изображения по умолчанию при старте +- Возможность загрузки собственного фонового изображения +- Обрезка изображения до нужных пропорций + +#### 2. Создание панелей +- Ввод текстов для создания отдельных панелей +- Каждый текст создает отдельную панель +- Все панели используют одно фоновое изображение +- Настройки текста применяются ко всем панелям + +#### 3. Настройки текста +- Размер шрифта (10-72px) +- Выбор шрифта из списка (Arial, Verdana, Georgia и др.) +- Цвет текста +- Выравнивание (лево, центр, право) +- Боковые отступы +- Вертикальное смещение от центра + +#### 4. Управление текстами +- Список всех созданных текстов +- Редактирование текста прямо в списке +- Удаление текста кнопкой "×" +- Автоматическое обновление панелей при изменении текстов + +#### 5. Просмотр панелей +- Превью всех созданных панелей в боковой панели +- Отображение финального результата с текстом +- Превью только для просмотра +- Скачивание отдельной панели +- Массовое скачивание всех панелей + +#### 6. Экспорт панелей +- Скачивание панелей в формате PNG +- Размер панелей: 320px ширина, настраиваемая высота (по умолчанию 100px) + +## Workflow работы программы (MVP) + +### Шаг 1: Запуск приложения +1. Пользователь открывает приложение +2. Автоматически загружается фоновое изображение по умолчанию +3. Пользователь видит экран добавления текстов + +### Шаг 2: Настройка фона (опционально) +1. Пользователь может загрузить собственное фоновое изображение +2. При необходимости обрезает изображение +3. Фон применяется ко всем создаваемым панелям + +### Шаг 3: Создание и редактирование текстов +1. Пользователь вводит текст для первой панели (например: "links") +2. Нажимает кнопку "Добавить" +3. Текст добавляется в список текстов +4. В боковой панели автоматически отображается превью созданной панели +5. Пользователь повторяет процесс для других текстов ("about me", "projects", "memes") +6. Все тексты отображаются в списке ниже поля ввода +7. Каждый текст в списке можно редактировать прямо в поле ввода +8. Любой текст можно удалить кнопкой "×" +9. При редактировании или удалении текста автоматически обновляется соответствующая панель + +### Шаг 4: Настройка внешнего вида (опционально) +1. Пользователь настраивает параметры текста: + - Размер шрифта + - Шрифт + - Цвет + - Выравнивание + - Отступы +2. Настройки применяются ко всем создаваемым панелям +3. Превью панелей в боковой панели обновляется в реальном времени + +### Шаг 5: Просмотр панелей +1. В боковой панели отображаются превью всех созданных панелей +2. Каждая панель показывает финальный результат с текстом +3. Превью только для просмотра, редактирование происходит в списке текстов + +### Шаг 6: Экспорт панелей +1. Пользователь скачивает отдельные панели или все сразу +2. Панели сохраняются в формате PNG +3. Готовые панели загружаются в Twitch + +## Технические требования MVP + +### Frontend +- Svelte 5 с runes +- TypeScript +- SvelteKit +- Tailwind CSS (опционально для стилей) + +### Основные компоненты +- ImageUpload - загрузка изображений +- ImageCropper - обрезка изображений +- TextManager - добавление, редактирование и удаление текстов, настройки текста +- PanelPreview - превью отдельной панели (только для просмотра) +- PanelList - список превью всех созданных панелей + +### Хранилище данных +- panelStore - хранение состояния панелей и функций для их создания +- uiStore - хранение состояния UI (текущий шаг, загрузка и т.д.) +- texts - массив текстов для создания панелей (хранится в компоненте страницы) + +### Сервисы +- exportService - экспорт панелей в PNG + +## Будущее развитие (Future Roadmap) + +### Фаза 2: Расширение функциональности + +#### 1. Дополнительные настройки +- Добавление иконок к панелям +- Настройка тени текста +- Градиентный текст +- Несколько текстовых элементов на одной панели +- Настройка прозрачности фона + +#### 2. Библиотека шаблонов +- Предустановленные шаблоны панелей +- Возможность сохранения собственных шаблонов +- Категории шаблонов (игры, технологии, искусство и т.д.) + +#### 3. Пакетная обработка +- Импорт списка текстов из файла +- Автоматическое создание панелей из списка +- Пакетное переименование панелей + +### Фаза 3: Социальные функции + +#### 1. Облачное хранилище +- Сохранение проектов в облаке +- Синхронизация между устройствами +- История версий + +#### 2. Шаринг +- Возможность делиться проектами +- Публичная библиотека панелей +- Рейтинг и отзывы + +#### 3. Интеграции +- Интеграция с Twitch API +- Автоматическая загрузка панелей в Twitch +- Интеграция с другими платформами (YouTube, Discord) + +### Фаза 4: Монетизация + +#### 1. Premium функции +- Дополнительные шаблоны +- Расширенные настройки +- Приоритетная поддержка + +#### 2. Маркетплейс +- Продажа премиальных шаблонов +- Комиссионные продажи + +## Метрики успеха MVP + +- Время создания одной панели: < 30 секунд +- Время создания набора из 5 панелей: < 2 минут +- Успешное скачивание панелей: 100% +- Положительный фидбек от первых пользователей + +## Ограничения MVP + +- Один фоновый рисунок для всех панелей +- Один текстовый элемент на панель +- Фиксированная ширина панели (320px) +- Только экспорт в PNG +- Работа в браузере без сохранения на сервере +- Редактирование текстов происходит в списке, превью только для просмотра + +## Следующие шаги + +1. ✅ Реализовать базовую структуру приложения +2. ✅ Реализовать загрузку и обрезку изображений +3. ✅ Реализовать создание панелей из текстов +4. ✅ Реализовать настройки текста +5. ✅ Реализовать управление текстами (редактирование, удаление) +6. ✅ Реализовать превью панелей (только для просмотра) +7. ✅ Реализовать экспорт панелей +8. ✅ Разделить редактирование текстов и превью панелей +9. 🔄 Тестирование и исправление багов +10. ⏳ Сборка фидбека от пользователей +11. ⏳ Планирование Фазы 2 на основе фидбека diff --git a/src/components/PanelPreview.svelte b/src/components/PanelPreview.svelte index 62355ec..cfeed44 100644 --- a/src/components/PanelPreview.svelte +++ b/src/components/PanelPreview.svelte @@ -7,40 +7,17 @@ let { panel, onDownload, - onUpdate, - onDelete, }: { panel: Panel; onDownload?: () => void; - onUpdate?: (text: string) => void; - onDelete?: () => void; } = $props(); let backgroundImage: HTMLImageElement | undefined = $state(undefined); - let editingText = $state(false); - let editText = $state(""); function handleUploadNewImage() { setCurrentStep("upload"); } - function handleEdit() { - editText = panel.texts[0]?.text || ""; - editingText = true; - } - - function handleSaveEdit() { - if (onUpdate && editText.trim()) { - onUpdate(editText.trim()); - } - editingText = false; - } - - function handleCancelEdit() { - editingText = false; - editText = ""; - } - $effect(() => { if (panel?.backgroundImage && panel.backgroundImage !== backgroundImage?.src) { loadImage(panel.backgroundImage); @@ -81,27 +58,8 @@
- {#if editingText} -
- e.key === "Enter" && handleSaveEdit()} - /> - - -
- {:else} -
{panel.texts[0]?.text || "Без названия"}
-
- - - {#if onDelete} - 🗑 - {/if} -
- {/if} +
{panel.texts[0]?.text || "Без названия"}
+
diff --git a/src/components/TextManager.svelte b/src/components/TextManager.svelte index de0a33b..5ee906c 100644 --- a/src/components/TextManager.svelte +++ b/src/components/TextManager.svelte @@ -2,8 +2,11 @@
@@ -78,6 +93,34 @@ {/if}
+ + {#if texts.length > 0} +
+

Созданные тексты ({texts.length})

+
+ {#each texts as textItem (textItem.id)} +
+ handleUpdateText(textItem.id, e.target.value)} + class="text-edit-input" + maxlength="100" + /> + +
+ {/each} +
+
+ {/if} +

Общие настройки текста

@@ -272,6 +315,45 @@ margin-bottom: 1rem; } + .texts-list { + display: flex; + flex-direction: column; + gap: 0.75rem; + margin-bottom: 1.5rem; + } + + .texts-list h3 { + margin: 0; + color: #333; + font-size: 1rem; + font-weight: 600; + } + + .texts-container { + display: flex; + flex-direction: column; + gap: 0.5rem; + } + + .text-item { + display: flex; + gap: 0.5rem; + align-items: center; + } + + .text-edit-input { + flex: 1; + padding: 0.5rem 0.75rem; + border: 1px solid #ddd; + border-radius: 4px; + font-size: 0.95rem; + } + + .text-edit-input:focus { + outline: none; + border-color: #007bff; + } + .input-group { display: flex; gap: 0.5rem; diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte index e7a5b5b..751d59b 100644 --- a/src/routes/+page.svelte +++ b/src/routes/+page.svelte @@ -14,6 +14,7 @@ let uploadedImage = $state(null); let panels = $state([]); + let texts = $state>([]); let errorMessage = $state(null); let backgroundImage = $state(null); @@ -62,20 +63,32 @@ } function handleAddText(text: string) { + texts = [...texts, { id: crypto.randomUUID(), text }]; + updatePanelsFromTexts(); + } + + function handleUpdateText(id: string, newText: string) { + texts = texts.map(t => t.id === id ? { ...t, text: newText } : t); + updatePanelsFromTexts(); + } + + function handleDeleteText(id: string) { + texts = texts.filter(t => t.id !== id); + updatePanelsFromTexts(); + } + + function updatePanelsFromTexts() { if (!backgroundImage) return; - const newPanel = createPanelFromText(backgroundImage, text); - panels = [...panels, newPanel]; + panels = texts.map(textItem => { + const existingPanel = panels.find(p => p.texts[0]?.text === textItem.text); + if (existingPanel) { + return updatePanelText(existingPanel, textItem.text); + } + return createPanelFromText(backgroundImage, textItem.text); + }); } - function handleUpdateText(panelId: string, text: string) { - panels = panels.map(panel => - panel.id === panelId ? updatePanelText(panel, text) : panel - ); - } - function handleDeletePanel(panelId: string) { - panels = panels.filter(panel => panel.id !== panelId); - } async function handleDownload(panel: Panel) { try { @@ -128,7 +141,12 @@ {:else if $uiStore.currentStep === "text"}

Добавьте тексты для панелей

- +
{/if}
@@ -146,8 +164,6 @@ handleDownload(panel)} - onUpdate={(text) => handleUpdateText(panel.id, text)} - onDelete={() => handleDeletePanel(panel.id)} />
{/each}