pridana ikonka, screenshot a popis do README.md
1
.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
/dist
|
||||
230
README.md
@ -1,2 +1,232 @@
|
||||
# Mozaic
|
||||
|
||||

|
||||
|
||||
Mozaic je webová aplikácia na prácu s fotografiami mozaík, maľovaných predlôh a ďalších obrazov rozdelených na samostatné farebné plochy. Používateľ kliknutím označuje jednotlivé uzavreté plochy a aplikácia pre každú z nich nájde súvislú oblasť, vypočíta reprezentatívnu farbu a priradí ju do farebnej skupiny.
|
||||
|
||||
Cieľom je uľahčiť identifikáciu podobných farieb, zoradiť ich od najtmavšej po najsvetlejšiu a očíslovať príslušné plochy. Takýto prehľad môže pomôcť pri plánovaní miešania farieb a pri systematickom maľovaní alebo skladaní mozaiky po jednotlivých farebných skupinách.
|
||||
|
||||
Aplikácia je určená najmä autorom mozaík, maliarom a každému, kto potrebuje z členitého farebného návrhu pripraviť praktickú paletu a očíslovanú pracovnú predlohu. Aktuálna verzia je lokálne fungujúce MVP: plochy sa označujú jednotlivo kliknutím, nejde o automatické rozpoznanie všetkých plôch celého obrázka naraz.
|
||||
|
||||
## Ukážka rozhrania
|
||||
|
||||

|
||||
|
||||
## Na čo Mozaic slúži
|
||||
|
||||
Typický pracovný postup začína načítaním fotografie alebo návrhu mozaiky. Používateľ klikne dovnútra uzavretej farebnej plochy a Mozaic pomocou flood fill algoritmu vyhľadá jej súvislé pixely. Z viacerých vnútorných pixelov určí reprezentatívnu farbu, porovná ju s už nájdenými farbami a zaradí plochu do zodpovedajúcej skupiny.
|
||||
|
||||
Farebné skupiny sa automaticky číslujú a zoraďujú podľa svetlosti. Výberom skupiny možno zvýrazniť všetky jej plochy a ostatné plochy ponechať, stlmiť alebo skryť. Používateľ tak môže pri práci s fyzickou mozaikou alebo obrazom spracovať všetky plochy jedného čísla, potom prejsť k svetlejšej farbe a zvolenú farbu postupne meniť alebo zosvetľovať. Aplikácia samotná nesleduje stav vymaľovania jednotlivých plôch.
|
||||
|
||||
Výsledok možno upraviť, exportovať ako očíslovaný obrázok a uložiť spolu s farebnou paletou alebo projektovými dátami na neskoršie pokračovanie.
|
||||
|
||||
## Hlavné funkcie
|
||||
|
||||
- **Načítanie obrázka:** podporované sú súbory PNG, JPEG a WebP vybrané cez tlačidlo alebo pretiahnuté do vyznačenej plochy.
|
||||
- **Canvas editor:** obrázok sa zobrazí so zachovaným pomerom strán; koliesko myši mení priblíženie, ťahanie posúva pohľad a tlačidlo `Prispôsobiť` obnoví vhodné zobrazenie.
|
||||
- **Označenie plochy kliknutím:** kliknutie do obrázka spustí iteratívny flood fill nad pixelmi pôvodného rozlíšenia.
|
||||
- **Nastaviteľný výber:** používateľ môže meniť toleranciu výberu, 4- alebo 8-smerovú susednosť a prah tmavosti obrysov.
|
||||
- **Reprezentatívna farba:** farba nevychádza iba z kliknutého pixelu; počíta sa medián vzoriek z vnútra nájdenej plochy.
|
||||
- **Zoskupovanie podobných farieb:** plochy sa porovnávajú v priestore CIE Lab pomocou farebnej vzdialenosti Delta E 1976. Prah podobnosti je nastaviteľný.
|
||||
- **Zoradenie a číslovanie:** skupiny sa automaticky zoradia podľa hodnoty svetlosti `L` od najtmavšej po najsvetlejšiu a ich čísla sa zobrazia na označených plochách.
|
||||
- **Zvýraznenie skupiny:** vybraná skupina sa na obrázku zvýrazní; ostatné skupiny možno zobraziť normálne, stlmiť alebo skryť.
|
||||
- **Manuálne opravy:** plochu možno odstrániť, presunúť do inej skupiny alebo oddeliť do novej skupiny. Skupiny možno zlúčiť a ich reprezentatívnu farbu zmeniť.
|
||||
- **Prepočítanie skupín:** tlačidlo `Prepočítať` znovu vytvorí skupiny podľa aktuálnej tolerancie podobnosti.
|
||||
- **Undo a redo:** história obsahuje najviac 30 krokov pre pridanie alebo odstránenie plochy, presuny, rozdelenie a zlúčenie skupín a vymazanie výsledkov.
|
||||
- **Export PNG:** vytvorí obrázok v pôvodnom rozlíšení s číslami skupín a voliteľným polopriehľadným farebným prekrytím.
|
||||
- **Export CSV:** uloží zoradenú paletu s HEX, RGB a Lab hodnotami, počtom plôch a celkovým počtom pixelov.
|
||||
- **Export a import JSON:** uloží nastavenia, plochy, skupiny, rozmery a komprimované masky projektu. Import sa spúšťa tlačidlom `Import JSON`.
|
||||
- **Automatická obnova:** posledná rozpracovaná práca sa priebežne ukladá do IndexedDB a po obnovení stránky ju aplikácia ponúkne na pokračovanie.
|
||||
|
||||
## Ako aplikáciu používať
|
||||
|
||||
1. **Otvorte aplikáciu.** Pri lokálnom vývoji ju sprístupní Vite na adrese uvedenej v termináli.
|
||||
2. **Načítajte obrázok.** Použite `Načítať obrázok`, `Vybrať obrázok` alebo presuňte súbor PNG, JPEG či WebP do editora.
|
||||
3. **Nastavte toleranciu výberu.** Začnite s predvolenou hodnotou. Ak výber uniká cez hranice, hodnotu znížte; ak sa plocha vyberie iba čiastočne, zvýšte ju. V rozšírených nastaveniach možno upraviť aj prah tmavosti obrysu.
|
||||
4. **Označujte plochy.** Klikajte dovnútra jednotlivých uzavretých plôch. Ťahaním obrázok posúvajte a kolieskom ho približujte alebo odďaľujte.
|
||||
5. **Skontrolujte farebné skupiny.** Pravý panel zobrazuje HEX, RGB, svetlosť a počet plôch každej skupiny. Zoradenie od najtmavšej po najsvetlejšiu prebieha automaticky.
|
||||
6. **Upravte podobnosť farieb.** Posuvník `Podobnosť farieb (ΔE)` určuje, ktoré farby sa spoja. Po jeho zmene sa skupiny prepočítajú.
|
||||
7. **Zvýraznite pracovnú skupinu.** Kliknite na farebnú skupinu a v nastavení `Ostatné skupiny` vyberte normálne zobrazenie, stlmenie alebo skrytie.
|
||||
8. **Opravte nepresnosti.** V zozname plôch možno plochu preradiť, rozdeliť do novej skupiny alebo odstrániť. V detaile skupiny možno zmeniť farbu alebo skupinu zlúčiť s inou.
|
||||
9. **Exportujte výsledok.** Použite `Očíslované PNG`, `Paleta CSV` alebo `Projekt JSON`. Pred PNG exportom možno zapnúť možnosť `Zahrnúť farebné prekrytie do PNG`.
|
||||
|
||||
Projekt JSON možno neskôr otvoriť cez `Import JSON`. Ak súbor neobsahuje pôvodný obrázok, aplikácia požiada o jeho opätovný výber; rozmery sa musia zhodovať s uloženým projektom.
|
||||
|
||||
### Klávesové skratky
|
||||
|
||||
| Skratka | Funkcia |
|
||||
| --- | --- |
|
||||
| `Ctrl+Z` | krok späť |
|
||||
| `Ctrl+Y` alebo `Ctrl+Shift+Z` | krok znova |
|
||||
| `Delete` | odstránenie vybratej plochy |
|
||||
| `Escape` | zrušenie výberu plochy a skupiny |
|
||||
|
||||
## Ako aplikácia určuje farby
|
||||
|
||||
Po kliknutí Mozaic začne farbou daného pixelu a hľadá susedné pixely s dostatočne podobnou RGB farbou. Tmavé pixely pod nastaveným prahom slúžia pri farebnom výbere ako bariéra, čo pomáha zabrániť pretečeniu cez tmavé obrysové čiary. Už označené pixely sa do ďalšej plochy nezaradia.
|
||||
|
||||
Reprezentatívna farba sa neurčuje z jediného bodu. Algoritmus zbiera vzorky z vnútorných častí scanline segmentov, obmedzuje ich počet a pre každý RGB kanál vypočíta medián. Tým sa znižuje vplyv okrajov, drobného šumu a časti kompresných artefaktov.
|
||||
|
||||
Na porovnanie plôch sa RGB farby prevedú cez XYZ do priestoru CIE Lab. V ňom sa vypočíta Delta E 1976 (`ΔE76`), ktorá vyjadruje vzdialenosť dvoch farieb. Plochy pod nastaveným prahom sa zoskupia a skupiny sa zoradia podľa zložky `L`, teda približnej svetlosti.
|
||||
|
||||
## Ochrana súkromia
|
||||
|
||||
Obrázky sa načítavajú a analyzujú výhradne lokálne v prehliadači. Projekt nemá backend, databázový server ani cloudové API a obrázky nikam neodosiela.
|
||||
|
||||
Rozpracovaný stav sa môže ukladať iba do lokálneho IndexedDB úložiska daného prehliadača. Exportované PNG, CSV a JSON súbory sa vytvárajú v prehliadači a sťahujú priamo do zariadenia používateľa.
|
||||
|
||||
## Tipy pre kvalitný výsledok
|
||||
|
||||
- Použite ostrú fotografiu s dostatočným rozlíšením a jasne uzavretými hranicami plôch.
|
||||
- Fotografujte čo najkolmejšie na obraz, aby sa obmedzilo perspektívne skreslenie.
|
||||
- Uprednostnite rovnomerné neutrálne osvetlenie bez tieňov, farebných odrazov a lesklých odleskov.
|
||||
- Ak výber preteká cez obrys, znížte toleranciu výberu alebo upravte prah tmavosti obrysu.
|
||||
- Ak sa jedna plocha rozdelí na viac výberov, toleranciu mierne zvýšte alebo použite kvalitnejší zdrojový obrázok.
|
||||
- Automaticky vytvorené farebné skupiny pred exportom skontrolujte a podľa potreby manuálne opravte.
|
||||
- Pri farebne kritickej práci používajte fotografiu so správnym vyvážením bielej; aplikácia nevykonáva kalibráciu fotoaparátu ani monitora.
|
||||
|
||||
## Aktuálne obmedzenia
|
||||
|
||||
- Všetky plochy sa označujú postupne kliknutím. Automatická segmentácia celého obrázka nie je implementovaná.
|
||||
- Flood fill funguje najlepšie na uzavretých plochách s výraznými hranicami. Medzera v obryse môže spôsobiť pretečenie výberu.
|
||||
- Jemné prechody, textúry, tiene, odlesky a JPEG artefakty môžu jednu plochu rozdeliť alebo ovplyvniť jej farbu.
|
||||
- Tmavá hranica sa rozpoznáva iba podľa nastavenej svetlosti; zložité alebo farebné obrysy môžu vyžadovať inú toleranciu.
|
||||
- Analýza prebieha na hlavnom vlákne bez Web Workera. Veľké súvislé plochy preto môžu dočasne znížiť odozvu rozhrania.
|
||||
- Pri obrázkoch nad 24 miliónov pixelov aplikácia zobrazí výkonnostné upozornenie. Obrázky nad 80 miliónov pixelov odmietne.
|
||||
- Výber pokrývajúci viac než 92 % obrázka a plochy menšie než 12 pixelov sa považujú za pravdepodobnú chybu a neuložia sa.
|
||||
- Výsledná farba závisí od kvality a osvetlenia fotografie; nejde o fyzicky kalibrované meranie farby.
|
||||
- Projektový JSON obsahuje pôvodný obrázok iba vtedy, keď má najviac 5 MiB. Pri väčšom obrázku ho treba pri importe znovu vybrať v rovnakých rozmeroch.
|
||||
|
||||
---
|
||||
|
||||
# Technické informácie
|
||||
|
||||
## Použité technológie
|
||||
|
||||
- [Vue 3](https://vuejs.org/) s Composition API a komponentmi `<script setup lang="ts">`
|
||||
- TypeScript
|
||||
- Vite
|
||||
- Pinia pre globálny stav editora
|
||||
- Vue Router; projekt ho inicializuje, aktuálne však nemá definované aplikačné trasy
|
||||
- HTML Canvas API a natívne browser API (`ImageData`, `createImageBitmap`, `IndexedDB`, `FileReader`, Blob a Object URL)
|
||||
- ESLint, Oxlint a Prettier pre kontrolu a formátovanie kódu
|
||||
|
||||
Aplikácia nepoužíva backend, OpenCV ani externú knižnicu na spracovanie obrázkov.
|
||||
|
||||
## Požiadavky
|
||||
|
||||
- Node.js presne podľa `engines` v `frontend/package.json`: `^22.18.0 || >=24.12.0`
|
||||
- npm nie je v projekte pevne pripnuté; použite verziu dodanú s podporovanou verziou Node.js
|
||||
- moderný prehliadač s podporou Canvas 2D, `createImageBitmap`, IndexedDB a ES modulov
|
||||
|
||||
Ďalšie systémové služby ani databázový server nie sú potrebné.
|
||||
|
||||
## Inštalácia a lokálne spustenie
|
||||
|
||||
Po klonovaní alebo otvorení repozitára prejdite do frontendového projektu, nainštalujte závislosti a spustite vývojový server:
|
||||
|
||||
```bash
|
||||
cd frontend
|
||||
npm install
|
||||
npm run dev
|
||||
```
|
||||
|
||||
Vite štandardne sprístupní aplikáciu na `http://localhost:5173/`. Ak je port obsadený, zvolí iný a vypíše jeho adresu v termináli.
|
||||
|
||||
## Produkčný build
|
||||
|
||||
```bash
|
||||
cd frontend
|
||||
npm run build
|
||||
```
|
||||
|
||||
Skript spustí TypeScript kontrolu a produkčný Vite build paralelne. Výsledné statické súbory sa vytvoria v `frontend/dist/`.
|
||||
|
||||
Lokálny náhľad hotového buildu:
|
||||
|
||||
```bash
|
||||
npm run preview
|
||||
```
|
||||
|
||||
## Kontrola kvality kódu
|
||||
|
||||
Všetky dostupné kontrolné skripty sú definované v `frontend/package.json`:
|
||||
|
||||
```bash
|
||||
npm run type-check
|
||||
npm run lint
|
||||
npm run format
|
||||
```
|
||||
|
||||
- `type-check` spúšťa `vue-tsc --build`.
|
||||
- `lint` postupne spúšťa Oxlint a ESLint. Obe lint úlohy používajú automatické opravy, takže môžu zmeniť kontrolované súbory.
|
||||
- `format` formátuje obsah `frontend/src/` pomocou Prettieru.
|
||||
|
||||
Projekt momentálne nemá nakonfigurovaný testovací runner ani npm skript `test`.
|
||||
|
||||
## Štruktúra projektu
|
||||
|
||||
```text
|
||||
Mozaic/
|
||||
├── frontend/
|
||||
│ ├── public/ # verejné favicony a ikony
|
||||
│ ├── src/
|
||||
│ │ ├── assets/ # zdrojové vizuálne súbory
|
||||
│ │ ├── components/ # editor, toolbar a pravý ovládací panel
|
||||
│ │ ├── composables/ # ovládanie canvas viewportu
|
||||
│ │ ├── router/ # inicializácia Vue Routera
|
||||
│ │ ├── stores/ # Pinia store a stav projektu
|
||||
│ │ ├── types/ # TypeScript dátové modely
|
||||
│ │ ├── utils/ # farby, flood fill, masky, export a IndexedDB
|
||||
│ │ ├── views/ # hlavné rozloženie editora
|
||||
│ │ ├── App.vue
|
||||
│ │ └── main.ts
|
||||
│ ├── index.html
|
||||
│ ├── package.json
|
||||
│ ├── tsconfig.app.json
|
||||
│ └── vite.config.ts
|
||||
├── LICENSE
|
||||
└── README.md
|
||||
```
|
||||
|
||||
Najdôležitejšie implementačné body:
|
||||
|
||||
- `src/stores/mosaic.ts` koordinuje obrázok, plochy, skupiny, výber, históriu, import/export a automatické uloženie.
|
||||
- `src/components/MosaicCanvas.vue` vykresľuje obrázok, prekrytia a čísla a prepočítava udalosti ukazovateľa na obrazové súradnice.
|
||||
- `src/utils/floodFill.ts` obsahuje iteratívny scanline flood fill.
|
||||
- `src/utils/colors.ts` obsahuje prevody RGB → XYZ → Lab, `ΔE76`, svetlosť a HEX formátovanie.
|
||||
- `src/utils/colorGroups.ts` zoskupuje plochy a zoraďuje skupiny podľa svetlosti.
|
||||
- `src/utils/regionMask.ts` pracuje s pamäťovo úspornou RLE reprezentáciou masiek.
|
||||
- `src/utils/projectExport.ts` a `src/utils/projectStorage.ts` zabezpečujú exporty a lokálne ukladanie.
|
||||
|
||||
## Technický princíp fungovania
|
||||
|
||||
1. Prehliadač dekóduje súbor pomocou `createImageBitmap` a vykreslí ho do interného canvasu v pôvodnom rozlíšení.
|
||||
2. Store načíta `ImageData` raz a uchová ho mimo hlbokej reaktivity. Zobrazený canvas používa samostatný viewport pre zoom a posun.
|
||||
3. Kliknutie sa prepočíta zo súradníc obrazovky na súradnice pôvodného obrázka.
|
||||
4. Iteratívny scanline flood fill vyhľadá súvislú oblasť podľa RGB tolerancie, zvolenej susednosti, prahu tmavého obrysu a masiek už označených plôch.
|
||||
5. Výsledok sa uloží ako RLE riadky v `Uint32Array`. Zároveň sa vypočíta počet pixelov, bounding box a vnútorná pozícia čísla.
|
||||
6. Z najviac 8 000 vnútorných vzoriek sa vypočíta medián RGB kanálov reprezentatívnej farby.
|
||||
7. RGB farba sa prevedie do CIE Lab a porovná s existujúcimi skupinami pomocou `ΔE76`.
|
||||
8. Skupiny dostanú váženú reprezentatívnu farbu, zoradia sa podľa Lab `L` a prečíslujú sa.
|
||||
9. Canvas vykreslí pôvodný obrázok, polopriehľadné RLE prekrytia, zvýraznenie výberu a čísla skupín.
|
||||
10. Export vytvorí výsledné dáta priamo v prehliadači bez serverovej komunikácie.
|
||||
|
||||
## Dáta a ukladanie
|
||||
|
||||
Aktívny stav spravuje Pinia store. Veľké objekty `HTMLCanvasElement`, `ImageData`, `Blob`, masky a zoznam plôch sa ukladajú cez `shallowRef` alebo `markRaw`, aby ich Vue zbytočne neobalilo hlbokou reaktivitou.
|
||||
|
||||
Po načítaní obrázka a po zmenách výsledkov sa projekt s krátkym oneskorením uloží do IndexedDB databázy `mozaic-analyzer`, úložiska `projects`, pod kľúčom `last-project`. Automatická záloha obsahuje serializovaný projekt aj pôvodný obrazový Blob. Pri ďalšom otvorení aplikácia ponúkne obnovenie alebo zahodenie poslednej práce. Ak prehliadač IndexedDB nepovolí, chyba automatického uloženia neblokuje editor.
|
||||
|
||||
Exportovaný JSON obsahuje verziu formátu, dátum vytvorenia, názov a rozmery obrázka, nastavenia, farebné skupiny a plochy vrátane RLE masiek, farieb, počtu pixelov, bounding boxov a pozícií čísiel. Obrázok do 5 MiB sa vloží ako data URL. Pri väčšom obrázku JSON obsahuje iba projektové dáta a pri importe treba znovu vybrať pôvodný obrázok s rovnakými rozmermi.
|
||||
|
||||
História undo/redo je iba v pamäti aktuálnej relácie a nie je súčasťou exportovaného projektu.
|
||||
|
||||
## Vývojové poznámky
|
||||
|
||||
- Analýza vždy používa pixely pôvodného rozlíšenia; zoom ovplyvňuje iba zobrazenie.
|
||||
- RLE maska ukladá trojice `y`, `xStart`, `xEnd` namiesto objektu pre každý pixel.
|
||||
- Masky sú po vytvorení nemenné, preto ich história môže bezpečne zdieľať bez kopírovania veľkých binárnych polí.
|
||||
- Výpočtová logika je oddelená od Vue komponentov v `src/utils/`, čo umožňuje jej neskoršie samostatné testovanie.
|
||||
- Flood fill aktuálne beží na hlavnom vlákne. Pri prípadnom presune do Web Workera treba zachovať prácu s pôvodným `ImageData` a minimalizovať kopírovanie veľkých polí.
|
||||
- Router je zapojený, no hlavná aplikácia sa momentálne vykresľuje priamo cez `App.vue` a nevyužíva samostatné trasy.
|
||||
|
||||
122
build.bat
Normal file
@ -0,0 +1,122 @@
|
||||
@echo off
|
||||
setlocal EnableExtensions
|
||||
|
||||
set "ROOT=%~dp0"
|
||||
if "%ROOT:~-1%"=="\" set "ROOT=%ROOT:~0,-1%"
|
||||
|
||||
set "DIST_DIR=%ROOT%\dist"
|
||||
set "DIST_PUBLIC=%DIST_DIR%\public"
|
||||
set "DIST_APP=%DIST_PUBLIC%"
|
||||
set "FRONTEND_DIR=%ROOT%\frontend"
|
||||
set "BACKEND_DIR=%ROOT%\backend"
|
||||
|
||||
echo [1/8] Cleaning dist...
|
||||
if exist "%DIST_DIR%" (
|
||||
rmdir /S /Q "%DIST_DIR%"
|
||||
if exist "%DIST_DIR%" (
|
||||
echo ERROR: Failed to remove "%DIST_DIR%".
|
||||
exit /b 1
|
||||
)
|
||||
)
|
||||
|
||||
mkdir "%DIST_DIR%" >nul 2>&1 || (
|
||||
echo ERROR: Failed to create "%DIST_DIR%".
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
echo [2/8] Installing backend dependencies... SKIPED
|
||||
@REM pushd "%BACKEND_DIR%" >nul 2>&1 || (
|
||||
@REM echo ERROR: Backend directory not found: "%BACKEND_DIR%".
|
||||
@REM exit /b 1
|
||||
@REM )
|
||||
|
||||
@REM call composer install --no-dev --optimize-autoloader
|
||||
@REM set "COMPOSER_EXIT=%ERRORLEVEL%"
|
||||
@REM popd >nul
|
||||
@REM if not "%COMPOSER_EXIT%"=="0" (
|
||||
@REM echo ERROR: composer install failed.
|
||||
@REM exit /b 1
|
||||
@REM )
|
||||
|
||||
echo [3/8] Regenerating frontend API client... SKIPED
|
||||
@REM call php "%BACKEND_DIR%\scripts\buildTypeScript.php"
|
||||
@REM if not "%ERRORLEVEL%"=="0" (
|
||||
@REM echo ERROR: TypeScript API client generation failed.
|
||||
@REM exit /b 1
|
||||
@REM )
|
||||
|
||||
echo [4/8] Building frontend...
|
||||
pushd "%FRONTEND_DIR%" >nul 2>&1 || (
|
||||
echo ERROR: Frontend directory not found: "%FRONTEND_DIR%".
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
if exist "%FRONTEND_DIR%\node_modules" (
|
||||
echo - npm install
|
||||
call npm.cmd install
|
||||
) else (
|
||||
echo - npm ci
|
||||
call npm.cmd ci
|
||||
)
|
||||
set "NPM_INSTALL_EXIT=%ERRORLEVEL%"
|
||||
if not "%NPM_INSTALL_EXIT%"=="0" (
|
||||
popd >nul
|
||||
echo ERROR: npm dependency install failed.
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
echo - npm run build
|
||||
call npm.cmd run build
|
||||
set "NPM_BUILD_EXIT=%ERRORLEVEL%"
|
||||
if not "%NPM_BUILD_EXIT%"=="0" (
|
||||
popd >nul
|
||||
echo ERROR: npm run build failed.
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
if not exist "%FRONTEND_DIR%\dist" (
|
||||
popd >nul
|
||||
echo ERROR: Frontend build output not found at "%FRONTEND_DIR%\dist".
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
popd >nul
|
||||
|
||||
echo [5/8] Copy backend root to dist/... SKIPED
|
||||
@REM call :RunRobocopy "%BACKEND_DIR%" "%DIST_DIR%" /E /R:2 /W:1 /NFL /NDL /NJH /NJS /NP ^
|
||||
@REM /XD "%BACKEND_DIR%\.git" "%BACKEND_DIR%\.vscode" "%BACKEND_DIR%\tests" "%BACKEND_DIR%\node_modules" "%BACKEND_DIR%\frontend" "%BACKEND_DIR%\dist" ^
|
||||
@REM /XF ".env" ".env.*"
|
||||
@REM if errorlevel 1 exit /b 1
|
||||
|
||||
echo [6/8] Copy frontend/dist to dist/public...
|
||||
call :RunRobocopy "%FRONTEND_DIR%\dist" "%DIST_APP%" /E /R:2 /W:1 /NFL /NDL /NJH /NJS /NP
|
||||
if errorlevel 1 exit /b 1
|
||||
|
||||
echo [7/8] Applying .env.production (if present)...
|
||||
set "ENV_NOTE=No backend/.env.production found"
|
||||
if exist "%BACKEND_DIR%\.env.production" (
|
||||
copy /Y "%BACKEND_DIR%\.env.production" "%DIST_DIR%\.env" >nul
|
||||
if errorlevel 1 (
|
||||
echo ERROR: Failed to copy backend/.env.production to dist/.env.
|
||||
exit /b 1
|
||||
)
|
||||
set "ENV_NOTE=Copied backend/.env.production to dist/.env"
|
||||
)
|
||||
|
||||
echo [8/8] Summary
|
||||
echo - Frontend build: OK
|
||||
echo - Backend root copied to: "%DIST_DIR%" (excluding excluded dirs)
|
||||
echo - Frontend assets copied to: "%DIST_APP%"
|
||||
echo - Env: %ENV_NOTE%
|
||||
echo - DocumentRoot should be: "%DIST_PUBLIC%"
|
||||
echo - Frontend app is served from: "%DIST_APP%"
|
||||
echo DONE "%DIST_DIR%"
|
||||
exit /b 0
|
||||
|
||||
:RunRobocopy
|
||||
robocopy %*
|
||||
if errorlevel 8 (
|
||||
echo ERROR: robocopy failed with exit code %ERRORLEVEL%.
|
||||
exit /b 1
|
||||
)
|
||||
exit /b 0
|
||||
9
deploy.bat
Normal file
@ -0,0 +1,9 @@
|
||||
@echo off
|
||||
|
||||
call php d:\www\sftpsync\src\sftpsync.php --host vihorlat.tpsoft.org --user igor ^
|
||||
--delete-dir /storage/tpsoft.org/mozaic/dist ^
|
||||
--sync d:/www/Mozaic/dist /storage/tpsoft.org/mozaic/dist ^
|
||||
--skip .git ^
|
||||
--print-relative
|
||||
|
||||
echo ✔️ Done.
|
||||
BIN
doc/Screenshot-2026-08-01-100022.png
Normal file
|
After Width: | Height: | Size: 1.7 MiB |
@ -2,11 +2,14 @@
|
||||
<html lang="sk">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<link rel="icon" href="/favicon.ico">
|
||||
<link rel="icon" type="image/svg+xml" href="/favicon.svg">
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png">
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png">
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta name="theme-color" content="#f8fafc">
|
||||
<meta name="description" content="Lokálna analýza farebných plôch a palety mozaiky.">
|
||||
<title>Mozaic – analýza farebných plôch</title>
|
||||
<title>Mozaic</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
|
||||
BIN
frontend/public/apple-touch-icon.png
Normal file
|
After Width: | Height: | Size: 35 KiB |
BIN
frontend/public/favicon-16x16.png
Normal file
|
After Width: | Height: | Size: 1.9 KiB |
BIN
frontend/public/favicon-32x32.png
Normal file
|
After Width: | Height: | Size: 4.7 KiB |
15
frontend/public/favicon.svg
Normal file
@ -0,0 +1,15 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" role="img" aria-label="Mozaic">
|
||||
<defs>
|
||||
<clipPath id="tile-frame">
|
||||
<rect x="36" y="36" width="440" height="440" rx="88"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
<rect x="16" y="16" width="480" height="480" rx="112" fill="#172033"/>
|
||||
<g clip-path="url(#tile-frame)" stroke="#172033" stroke-width="24" stroke-linejoin="round">
|
||||
<path d="M24 24h226l-24 210L24 202Z" fill="#F16C5B"/>
|
||||
<path d="M250 24h238v184L226 234Z" fill="#36A99A"/>
|
||||
<path d="M24 202l202 32-24 254H24Z" fill="#4386D8"/>
|
||||
<path d="M226 234l104-34 70 126-142 80-56-92Z" fill="#F2C14E"/>
|
||||
<path d="M330 200l158 8v280H202l56-82 142-80Z" fill="#8A6FBC"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 709 B |
BIN
frontend/public/icon-192.png
Normal file
|
After Width: | Height: | Size: 38 KiB |
BIN
frontend/public/icon-512.png
Normal file
|
After Width: | Height: | Size: 41 KiB |
15
frontend/src/assets/mozaic-logo.svg
Normal file
@ -0,0 +1,15 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" role="img" aria-label="Mozaic">
|
||||
<defs>
|
||||
<clipPath id="tile-frame">
|
||||
<rect x="36" y="36" width="440" height="440" rx="88"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
<rect x="16" y="16" width="480" height="480" rx="112" fill="#172033"/>
|
||||
<g clip-path="url(#tile-frame)" stroke="#172033" stroke-width="24" stroke-linejoin="round">
|
||||
<path d="M24 24h226l-24 210L24 202Z" fill="#F16C5B"/>
|
||||
<path d="M250 24h238v184L226 234Z" fill="#36A99A"/>
|
||||
<path d="M24 202l202 32-24 254H24Z" fill="#4386D8"/>
|
||||
<path d="M226 234l104-34 70 126-142 80-56-92Z" fill="#F2C14E"/>
|
||||
<path d="M330 200l158 8v280H202l56-82 142-80Z" fill="#8A6FBC"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 709 B |
@ -2,6 +2,7 @@
|
||||
import { ref } from 'vue'
|
||||
import { storeToRefs } from 'pinia'
|
||||
import { useMosaicStore } from '@/stores/mosaic'
|
||||
import mozaicLogo from '@/assets/mozaic-logo.svg'
|
||||
|
||||
defineEmits<{ resetViewport: [] }>()
|
||||
const store = useMosaicStore()
|
||||
@ -22,7 +23,7 @@ function importProject(files: FileList | null): void {
|
||||
|
||||
<template>
|
||||
<header class="toolbar">
|
||||
<div class="brand"><span class="brand-mark">M</span><span>Mozaic</span></div>
|
||||
<div class="brand"><img class="brand-mark" :src="mozaicLogo" alt="" width="32" height="32" /><span>Mozaic</span></div>
|
||||
<div class="toolbar-actions">
|
||||
<button class="button primary" type="button" @click="imageInput?.click()">Načítať obrázok</button>
|
||||
<button class="icon-button" type="button" :disabled="!canUndo" title="Späť (Ctrl+Z)" @click="store.undo">↶</button>
|
||||
@ -40,7 +41,7 @@ function importProject(files: FileList | null): void {
|
||||
<style scoped>
|
||||
.toolbar { position: sticky; top: 0; z-index: 10; display: flex; min-height: 58px; align-items: center; gap: 24px; padding: 8px 16px; border-bottom: 1px solid #dbe1e8; background: rgba(255,255,255,.96); }
|
||||
.brand { display: flex; align-items: center; gap: 9px; color: #172033; font-weight: 760; letter-spacing: -.02em; }
|
||||
.brand-mark { display: grid; width: 30px; height: 30px; place-items: center; border-radius: 8px; background: #1e293b; color: white; }
|
||||
.brand-mark { display: block; width: 32px; height: 32px; flex: 0 0 32px; }
|
||||
.toolbar-actions { display: flex; min-width: 0; align-items: center; gap: 7px; overflow-x: auto; }
|
||||
.divider { width: 1px; height: 26px; margin: 0 2px; background: #dbe1e8; }
|
||||
@media (max-width: 700px) { .toolbar { align-items: flex-start; gap: 10px; padding: 8px 10px; } .brand span:last-child { display: none; } }
|
||||
|
||||