From 0650c40199b23d43f2a1292bcc066ffb890d081e Mon Sep 17 00:00:00 2001 From: sHa Date: Wed, 11 Jun 2025 22:36:48 +0300 Subject: [PATCH] feat: Add PWA support with service worker and caching - Implemented service worker registration in main.js - Added icons for PWA in manifest.json - Created a basic service worker (sw.js) for caching static assets - Generated a list of files to cache using a Node.js script (generate-pwa-cache-list.js) - Added icon images (icon-192.png and icon-512.png) for PWA - Defined PWA manifest with app details and icon references --- .github/workflows/gh-pages.yml | 4 +++ .gitignore | 1 + Makefile | 14 +++++--- ToDo.md | 14 ++++---- package.json | 3 +- public/icon-192.png | Bin 0 -> 3412 bytes public/icon-512.png | Bin 0 -> 10006 bytes public/index.html | 14 ++++++++ public/manifest.json | 20 +++++++++++ public/sw.js | 54 +++++++++++++++++++++++++++++ scripts/generate-pwa-cache-list.js | 45 ++++++++++++++++++++++++ scripts/generateFavicons.js | 14 +++++++- src/main.js | 6 ++++ 13 files changed, 175 insertions(+), 14 deletions(-) create mode 100644 public/icon-192.png create mode 100644 public/icon-512.png create mode 100644 public/manifest.json create mode 100644 public/sw.js create mode 100644 scripts/generate-pwa-cache-list.js diff --git a/.github/workflows/gh-pages.yml b/.github/workflows/gh-pages.yml index 392205a..8376b5f 100644 --- a/.github/workflows/gh-pages.yml +++ b/.github/workflows/gh-pages.yml @@ -37,6 +37,9 @@ jobs: - name: Generate SVG variants run: node scripts/generate-svg-variants.js + - name: Generate PWA cache list + run: node scripts/generate-pwa-cache-list.js + - name: Prepare Pages artifact run: | mkdir -p ./gh-pages-artifact/build @@ -50,6 +53,7 @@ jobs: cp -r public/data ./gh-pages-artifact/ cp -r public/logos ./gh-pages-artifact/ cp -r public/logos_gen ./gh-pages-artifact/ + cp public/pwa-files-to-cache.json ./gh-pages-artifact/ if [ -f public/CNAME ]; then cp public/CNAME ./gh-pages-artifact/CNAME fi diff --git a/.gitignore b/.gitignore index fde3786..3c95cd1 100644 --- a/.gitignore +++ b/.gitignore @@ -8,6 +8,7 @@ yarn.lock # Build output public/build/ +public/pwa-files-to-cache.json # macOS specific files .DS_Store diff --git a/Makefile b/Makefile index 1720ee6..5115b14 100644 --- a/Makefile +++ b/Makefile @@ -6,21 +6,19 @@ CONTAINER_NAME = slogos-dev DEV_PORT = 5006 # Main targets -.PHONY: all build start stop restart logs clean scan-logos dev rebuild favicon deps-favicon build-with-favicons sync-packages convert-colors generate-svg-variants +.PHONY: all build start stop restart logs clean scan-logos dev rebuild favicon build-with-favicons generate-svg-variants pwa-cache-list run update-lock all: build start -# Development mode with hot reloading -dev: +dev: pwa-cache-list $(DOCKER_COMPOSE) -f compose.dev.yml up --build -# Build the Docker container build: @echo "Building the Logo Gallery container..." $(DOCKER_COMPOSE) -f compose.dev.yml build # Start the application in the background -start: +start: pwa-cache-list @echo "Starting Logo Gallery application on port $(DEV_PORT)..." $(DOCKER_COMPOSE) -f compose.dev.yml up -d @echo "Application is running at http://localhost:$(DEV_PORT)" @@ -81,3 +79,9 @@ generate-svg-variants: @echo "Generating SVG variants with color sets..." $(DOCKER_COMPOSE) -f compose.dev.yml run --rm $(CONTAINER_NAME) node scripts/generate-svg-variants.js @echo "SVG variants have been generated" + +# Generate PWA cache list +pwa-cache-list: + @echo "Generating PWA cache list..." + $(DOCKER_COMPOSE) -f compose.dev.yml run --rm $(CONTAINER_NAME) npm run pwa-cache-list + diff --git a/ToDo.md b/ToDo.md index 1842fc2..45359db 100644 --- a/ToDo.md +++ b/ToDo.md @@ -1,7 +1,7 @@ -[] Improove: To the tiny card add the color chooser. It should be one circle that display current color, on click it should open color picker. -[] Improove: In the preview page, add full header. -[] Improove: Split header into two parts: static top and dynamic bottom. -[] Improove: In the preview page, add possibility select custom color for each target. -[] Strategy: Add differents base/collections of images: Flags, -[] Strategy: WebApp, PWA -[] Improove: Add to filter Image variants - just logo, line logo, square logo +[ ] Improove: To the tiny card add the color chooser. It should be one circle that display current color, on click it should open color picker. +[ ] Improove: In the preview page, add full header. +[ ] Improove: Split header into two parts: static top and dynamic bottom. +[ ] Improove: In the preview page, add possibility select custom color for each target. +[ ] Strategy: Add differents base/collections of images: Flags, +[v] Strategy: WebApp, PWA +[ ] Improove: Add to filter Image variants - just logo, line logo, square logo diff --git a/package.json b/package.json index b166d2e..94860b7 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,8 @@ "scan-logos": "node scripts/scanLogos.js", "generate-favicons": "node scripts/generateFavicons.js", "generate-variants": "node scripts/generate-svg-variants.js", - "variants": "npm run generate-variants" + "variants": "npm run generate-variants", + "pwa-cache-list": "node scripts/generate-pwa-cache-list.js" }, "devDependencies": { "@rollup/plugin-commonjs": "^17.0.0", diff --git a/public/icon-192.png b/public/icon-192.png new file mode 100644 index 0000000000000000000000000000000000000000..15f7b7de7e74f4444643e3077a1dbffe1c725282 GIT binary patch literal 3412 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 literal 0 HcmV?d00001 diff --git a/public/icon-512.png b/public/icon-512.png new file mode 100644 index 0000000000000000000000000000000000000000..88452e2651f16df40803978f71eac70081e633fd GIT binary patch literal 10006 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