Add PWA support for desktop and mobile installation (#96)
26
README.md
@ -30,6 +30,32 @@ An app to create beautiful QR codes and scan various QR code types.
|
|||||||
- 🛡️ Error correction level: affects the size of the QR code and logo within. Use lower correction levels for bigger pieces of data to ensure that it can be read.
|
- 🛡️ Error correction level: affects the size of the QR code and logo within. Use lower correction levels for bigger pieces of data to ensure that it can be read.
|
||||||
- 📱 QR Code Scanner: Scan QR codes using your camera or by uploading images, with intelligent detection for URLs, emails, phone numbers, WiFi credentials, and more
|
- 📱 QR Code Scanner: Scan QR codes using your camera or by uploading images, with intelligent detection for URLs, emails, phone numbers, WiFi credentials, and more
|
||||||
- 📦 Batch data export: Import a CSV file with multiple data strings and export QR codes for them all at once.
|
- 📦 Batch data export: Import a CSV file with multiple data strings and export QR codes for them all at once.
|
||||||
|
- 📲 PWA Support: Install MiniQR as a desktop or mobile app
|
||||||
|
|
||||||
|
### Installation as PWA
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>MiniQR can also be installed as a Progressive Web App (PWA) on your device</summary>
|
||||||
|
|
||||||
|
1. **Desktop (Chrome/Edge)**:
|
||||||
|
- Visit [mini-qr.vercel.app](https://mini-qr.vercel.app)
|
||||||
|
- Click the install icon (➕) in the address bar
|
||||||
|
- Click "Install" in the prompt
|
||||||
|
|
||||||
|
2. **Mobile (Android)**:
|
||||||
|
- Visit [mini-qr.vercel.app](https://mini-qr.vercel.app)
|
||||||
|
- Tap the "Add to Home Screen" option in your browser menu
|
||||||
|
- Tap "Install" or "Add"
|
||||||
|
|
||||||
|
3. **iOS (Safari)**:
|
||||||
|
- Visit [mini-qr.vercel.app](https://mini-qr.vercel.app)
|
||||||
|
- Tap the Share button
|
||||||
|
- Scroll down and tap "Add to Home Screen"
|
||||||
|
- Tap "Add"
|
||||||
|
|
||||||
|
Once installed, MiniQR will work offline and provide a native app-like experience.
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
## Demo
|
## Demo
|
||||||
|
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
||||||
|
<link rel="apple-touch-icon" href="/apple-touch-icon.png">
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<meta http-equiv="X-UA-Compatible" content="ie=edge">
|
<meta http-equiv="X-UA-Compatible" content="ie=edge">
|
||||||
|
@ -10,7 +10,8 @@
|
|||||||
"type-check": "vue-tsc --noEmit",
|
"type-check": "vue-tsc --noEmit",
|
||||||
"lint": "eslint --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .gitignore --ignore-pattern \"*.json\"",
|
"lint": "eslint --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .gitignore --ignore-pattern \"*.json\"",
|
||||||
"format": "prettier --write",
|
"format": "prettier --write",
|
||||||
"postinstall": "husky install"
|
"postinstall": "husky install",
|
||||||
|
"generate-splash": "node scripts/generate-splash-screens.js"
|
||||||
},
|
},
|
||||||
"lint-staged": {
|
"lint-staged": {
|
||||||
"*.{vue,js,jsx,ts,tsx,json}": [
|
"*.{vue,js,jsx,ts,tsx,json}": [
|
||||||
@ -57,7 +58,9 @@
|
|||||||
"lint-staged": "^15.4.3",
|
"lint-staged": "^15.4.3",
|
||||||
"postcss": "^8.5.3",
|
"postcss": "^8.5.3",
|
||||||
"prettier": "^3.5.3",
|
"prettier": "^3.5.3",
|
||||||
|
"sharp": "^0.33.5",
|
||||||
"typescript": "^5.8.2",
|
"typescript": "^5.8.2",
|
||||||
|
"vite-plugin-pwa": "^0.21.1",
|
||||||
"vue-eslint-parser": "^9.4.3"
|
"vue-eslint-parser": "^9.4.3"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
2886
pnpm-lock.yaml
generated
BIN
public/app_icons/android/play_store_512.png
Normal file
After Width: | Height: | Size: 31 KiB |
@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<background android:drawable="@mipmap/ic_launcher_background"/>
|
||||||
|
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
|
||||||
|
<monochrome android:drawable="@mipmap/ic_launcher_monochrome"/>
|
||||||
|
</adaptive-icon>
|
BIN
public/app_icons/android/res/mipmap-hdpi/ic_launcher.png
Normal file
After Width: | Height: | Size: 5.5 KiB |
After Width: | Height: | Size: 852 B |
After Width: | Height: | Size: 7.0 KiB |
After Width: | Height: | Size: 7.0 KiB |
BIN
public/app_icons/android/res/mipmap-mdpi/ic_launcher.png
Normal file
After Width: | Height: | Size: 3.2 KiB |
After Width: | Height: | Size: 459 B |
After Width: | Height: | Size: 4.0 KiB |
After Width: | Height: | Size: 4.0 KiB |
BIN
public/app_icons/android/res/mipmap-xhdpi/ic_launcher.png
Normal file
After Width: | Height: | Size: 7.4 KiB |
After Width: | Height: | Size: 1.3 KiB |
After Width: | Height: | Size: 9.8 KiB |
After Width: | Height: | Size: 9.8 KiB |
BIN
public/app_icons/android/res/mipmap-xxhdpi/ic_launcher.png
Normal file
After Width: | Height: | Size: 12 KiB |
After Width: | Height: | Size: 2.9 KiB |
After Width: | Height: | Size: 16 KiB |
After Width: | Height: | Size: 16 KiB |
BIN
public/app_icons/android/res/mipmap-xxxhdpi/ic_launcher.png
Normal file
After Width: | Height: | Size: 18 KiB |
After Width: | Height: | Size: 4.1 KiB |
After Width: | Height: | Size: 24 KiB |
After Width: | Height: | Size: 24 KiB |
BIN
public/app_icons/ios/AppIcon-20@2x.png
Normal file
After Width: | Height: | Size: 1.5 KiB |
BIN
public/app_icons/ios/AppIcon-20@2x~ipad.png
Normal file
After Width: | Height: | Size: 1.5 KiB |
BIN
public/app_icons/ios/AppIcon-20@3x.png
Normal file
After Width: | Height: | Size: 2.4 KiB |
BIN
public/app_icons/ios/AppIcon-20~ipad.png
Normal file
After Width: | Height: | Size: 636 B |
BIN
public/app_icons/ios/AppIcon-29.png
Normal file
After Width: | Height: | Size: 1.0 KiB |
BIN
public/app_icons/ios/AppIcon-29@2x.png
Normal file
After Width: | Height: | Size: 2.3 KiB |
BIN
public/app_icons/ios/AppIcon-29@2x~ipad.png
Normal file
After Width: | Height: | Size: 2.3 KiB |
BIN
public/app_icons/ios/AppIcon-29@3x.png
Normal file
After Width: | Height: | Size: 3.6 KiB |
BIN
public/app_icons/ios/AppIcon-29~ipad.png
Normal file
After Width: | Height: | Size: 1.0 KiB |
BIN
public/app_icons/ios/AppIcon-40@2x.png
Normal file
After Width: | Height: | Size: 3.5 KiB |
BIN
public/app_icons/ios/AppIcon-40@2x~ipad.png
Normal file
After Width: | Height: | Size: 3.5 KiB |
BIN
public/app_icons/ios/AppIcon-40@3x.png
Normal file
After Width: | Height: | Size: 5.5 KiB |
BIN
public/app_icons/ios/AppIcon-40~ipad.png
Normal file
After Width: | Height: | Size: 1.5 KiB |
BIN
public/app_icons/ios/AppIcon-60@2x~car.png
Normal file
After Width: | Height: | Size: 5.5 KiB |
BIN
public/app_icons/ios/AppIcon-60@3x~car.png
Normal file
After Width: | Height: | Size: 8.7 KiB |
BIN
public/app_icons/ios/AppIcon-83.5@2x~ipad.png
Normal file
After Width: | Height: | Size: 7.7 KiB |
BIN
public/app_icons/ios/AppIcon@2x.png
Normal file
After Width: | Height: | Size: 5.5 KiB |
BIN
public/app_icons/ios/AppIcon@2x~ipad.png
Normal file
After Width: | Height: | Size: 6.9 KiB |
BIN
public/app_icons/ios/AppIcon@3x.png
Normal file
After Width: | Height: | Size: 8.7 KiB |
BIN
public/app_icons/ios/AppIcon~ios-marketing.png
Normal file
After Width: | Height: | Size: 79 KiB |
BIN
public/app_icons/ios/AppIcon~ipad.png
Normal file
After Width: | Height: | Size: 3.3 KiB |
134
public/app_icons/ios/Contents.json
Normal file
@ -0,0 +1,134 @@
|
|||||||
|
{
|
||||||
|
"images": [
|
||||||
|
{
|
||||||
|
"filename": "AppIcon@2x.png",
|
||||||
|
"idiom": "iphone",
|
||||||
|
"scale": "2x",
|
||||||
|
"size": "60x60"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename": "AppIcon@3x.png",
|
||||||
|
"idiom": "iphone",
|
||||||
|
"scale": "3x",
|
||||||
|
"size": "60x60"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename": "AppIcon~ipad.png",
|
||||||
|
"idiom": "ipad",
|
||||||
|
"scale": "1x",
|
||||||
|
"size": "76x76"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename": "AppIcon@2x~ipad.png",
|
||||||
|
"idiom": "ipad",
|
||||||
|
"scale": "2x",
|
||||||
|
"size": "76x76"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename": "AppIcon-83.5@2x~ipad.png",
|
||||||
|
"idiom": "ipad",
|
||||||
|
"scale": "2x",
|
||||||
|
"size": "83.5x83.5"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename": "AppIcon-40@2x.png",
|
||||||
|
"idiom": "iphone",
|
||||||
|
"scale": "2x",
|
||||||
|
"size": "40x40"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename": "AppIcon-40@3x.png",
|
||||||
|
"idiom": "iphone",
|
||||||
|
"scale": "3x",
|
||||||
|
"size": "40x40"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename": "AppIcon-40~ipad.png",
|
||||||
|
"idiom": "ipad",
|
||||||
|
"scale": "1x",
|
||||||
|
"size": "40x40"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename": "AppIcon-40@2x~ipad.png",
|
||||||
|
"idiom": "ipad",
|
||||||
|
"scale": "2x",
|
||||||
|
"size": "40x40"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename": "AppIcon-20@2x.png",
|
||||||
|
"idiom": "iphone",
|
||||||
|
"scale": "2x",
|
||||||
|
"size": "20x20"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename": "AppIcon-20@3x.png",
|
||||||
|
"idiom": "iphone",
|
||||||
|
"scale": "3x",
|
||||||
|
"size": "20x20"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename": "AppIcon-20~ipad.png",
|
||||||
|
"idiom": "ipad",
|
||||||
|
"scale": "1x",
|
||||||
|
"size": "20x20"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename": "AppIcon-20@2x~ipad.png",
|
||||||
|
"idiom": "ipad",
|
||||||
|
"scale": "2x",
|
||||||
|
"size": "20x20"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename": "AppIcon-29.png",
|
||||||
|
"idiom": "iphone",
|
||||||
|
"scale": "1x",
|
||||||
|
"size": "29x29"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename": "AppIcon-29@2x.png",
|
||||||
|
"idiom": "iphone",
|
||||||
|
"scale": "2x",
|
||||||
|
"size": "29x29"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename": "AppIcon-29@3x.png",
|
||||||
|
"idiom": "iphone",
|
||||||
|
"scale": "3x",
|
||||||
|
"size": "29x29"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename": "AppIcon-29~ipad.png",
|
||||||
|
"idiom": "ipad",
|
||||||
|
"scale": "1x",
|
||||||
|
"size": "29x29"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename": "AppIcon-29@2x~ipad.png",
|
||||||
|
"idiom": "ipad",
|
||||||
|
"scale": "2x",
|
||||||
|
"size": "29x29"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename": "AppIcon-60@2x~car.png",
|
||||||
|
"idiom": "car",
|
||||||
|
"scale": "2x",
|
||||||
|
"size": "60x60"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename": "AppIcon-60@3x~car.png",
|
||||||
|
"idiom": "car",
|
||||||
|
"scale": "3x",
|
||||||
|
"size": "60x60"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename": "AppIcon~ios-marketing.png",
|
||||||
|
"idiom": "ios-marketing",
|
||||||
|
"scale": "1x",
|
||||||
|
"size": "1024x1024"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info": {
|
||||||
|
"author": "iconkitchen",
|
||||||
|
"version": 1
|
||||||
|
}
|
||||||
|
}
|
BIN
public/app_icons/web/favicon.ico
Normal file
After Width: | Height: | Size: 5.1 KiB |
BIN
public/app_icons/web/icon-192-maskable.png
Normal file
After Width: | Height: | Size: 9.4 KiB |
BIN
public/app_icons/web/icon-192.png
Normal file
After Width: | Height: | Size: 11 KiB |
BIN
public/app_icons/web/icon-512-maskable.png
Normal file
After Width: | Height: | Size: 31 KiB |
BIN
public/app_icons/web/icon-512.png
Normal file
After Width: | Height: | Size: 34 KiB |
BIN
public/app_icons/web/screenshot-narrow.png
Normal file
After Width: | Height: | Size: 108 KiB |
BIN
public/app_icons/web/screenshot-wide.png
Normal file
After Width: | Height: | Size: 111 KiB |
BIN
public/app_icons/web/splash-1170x2532.png
Normal file
After Width: | Height: | Size: 57 KiB |
BIN
public/app_icons/web/splash-1170x2532@3x.png
Normal file
After Width: | Height: | Size: 108 KiB |
BIN
public/app_icons/web/splash-1290x2796.png
Normal file
After Width: | Height: | Size: 55 KiB |
BIN
public/app_icons/web/splash-1290x2796@3x.png
Normal file
After Width: | Height: | Size: 127 KiB |
BIN
public/app_icons/web/splash-2048x2732.png
Normal file
After Width: | Height: | Size: 108 KiB |
BIN
public/app_icons/web/splash-2048x2732@2x.png
Normal file
After Width: | Height: | Size: 128 KiB |
BIN
public/app_icons/web/splash-750x1334.png
Normal file
After Width: | Height: | Size: 32 KiB |
BIN
public/app_icons/web/splash-750x1334@2x.png
Normal file
After Width: | Height: | Size: 27 KiB |
BIN
public/apple-touch-icon.png
Normal file
After Width: | Height: | Size: 8.7 KiB |
34
scripts/generate-pwa-icons.js
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
import sharp from 'sharp'
|
||||||
|
import fs from 'fs'
|
||||||
|
import path from 'path'
|
||||||
|
import process from 'process'
|
||||||
|
|
||||||
|
const sizes = [192, 512]
|
||||||
|
const inputFile = 'public/favicon.svg'
|
||||||
|
const outputDir = 'public'
|
||||||
|
|
||||||
|
async function generateIcons() {
|
||||||
|
try {
|
||||||
|
for (const size of sizes) {
|
||||||
|
const outputFile = path.join(outputDir, `pwa-${size}x${size}.png`)
|
||||||
|
await sharp(inputFile).resize(size, size).png().toFile(outputFile)
|
||||||
|
console.log(`Generated ${outputFile}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate apple touch icon
|
||||||
|
await sharp(inputFile)
|
||||||
|
.resize(180, 180)
|
||||||
|
.png()
|
||||||
|
.toFile(path.join(outputDir, 'apple-touch-icon.png'))
|
||||||
|
console.log('Generated apple-touch-icon.png')
|
||||||
|
|
||||||
|
// Copy favicon.svg to mask-icon.svg
|
||||||
|
fs.copyFileSync(inputFile, path.join(outputDir, 'mask-icon.svg'))
|
||||||
|
console.log('Generated mask-icon.svg')
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error generating icons:', error)
|
||||||
|
process.exit(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
generateIcons()
|
102
scripts/generate-splash-screens.js
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
import sharp from 'sharp'
|
||||||
|
import path from 'path'
|
||||||
|
import process from 'process'
|
||||||
|
|
||||||
|
const SOURCE_ICON = 'public/app_icons/web/icon-512.png'
|
||||||
|
const OUTPUT_DIR = 'public/app_icons/web'
|
||||||
|
|
||||||
|
// Common sizes for iOS and Android splash screens with device pixel ratios
|
||||||
|
const SPLASH_SCREENS = [
|
||||||
|
// iPhone SE, 4.7" iPhone (2x)
|
||||||
|
{ width: 750 * 2, height: 1334 * 2, name: '750x1334@2x' },
|
||||||
|
// iPhone 14, 13 (3x)
|
||||||
|
{ width: 1170 * 3, height: 2532 * 3, name: '1170x2532@3x' },
|
||||||
|
// iPhone Pro Max (3x)
|
||||||
|
{ width: 1290 * 3, height: 2796 * 3, name: '1290x2796@3x' },
|
||||||
|
// iPad Pro 12.9" (2x)
|
||||||
|
{ width: 2048 * 2, height: 2732 * 2, name: '2048x2732@2x' }
|
||||||
|
]
|
||||||
|
|
||||||
|
async function generateSplashScreens() {
|
||||||
|
try {
|
||||||
|
// Create light version splash screens
|
||||||
|
for (const size of SPLASH_SCREENS) {
|
||||||
|
const outputFile = path.join(OUTPUT_DIR, `splash-${size.name}.png`)
|
||||||
|
|
||||||
|
// Calculate icon size (40% of the smallest dimension)
|
||||||
|
const minDimension = Math.min(size.width, size.height)
|
||||||
|
const iconSize = Math.floor(minDimension * 0.4)
|
||||||
|
|
||||||
|
// Create a new image with white background
|
||||||
|
await sharp({
|
||||||
|
create: {
|
||||||
|
width: size.width,
|
||||||
|
height: size.height,
|
||||||
|
channels: 4,
|
||||||
|
background: { r: 255, g: 255, b: 255, alpha: 1 }
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.composite([
|
||||||
|
{
|
||||||
|
input: await sharp(SOURCE_ICON)
|
||||||
|
.resize({
|
||||||
|
width: iconSize,
|
||||||
|
height: iconSize,
|
||||||
|
fit: 'contain',
|
||||||
|
background: { r: 255, g: 255, b: 255, alpha: 0 },
|
||||||
|
kernel: 'lanczos3' // Use high-quality resampling
|
||||||
|
})
|
||||||
|
.toBuffer(),
|
||||||
|
gravity: 'center'
|
||||||
|
}
|
||||||
|
])
|
||||||
|
.png({ quality: 100 }) // Use maximum PNG quality
|
||||||
|
.toFile(outputFile)
|
||||||
|
|
||||||
|
console.log(`Generated ${outputFile}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create screenshots with proper background
|
||||||
|
const screenshotSizes = [
|
||||||
|
{ name: 'narrow', width: 1170 * 3, height: 2532 * 3 }, // 3x for modern devices
|
||||||
|
{ name: 'wide', width: 2532 * 3, height: 1170 * 3 }
|
||||||
|
]
|
||||||
|
|
||||||
|
for (const size of screenshotSizes) {
|
||||||
|
const iconSize = Math.min(size.width, size.height) * 0.4 // Reduced to 40% for better proportion
|
||||||
|
|
||||||
|
// Create a new image with white background
|
||||||
|
await sharp({
|
||||||
|
create: {
|
||||||
|
width: size.width,
|
||||||
|
height: size.height,
|
||||||
|
channels: 4,
|
||||||
|
background: { r: 255, g: 255, b: 255, alpha: 1 }
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.composite([
|
||||||
|
{
|
||||||
|
input: await sharp(SOURCE_ICON)
|
||||||
|
.resize({
|
||||||
|
width: Math.floor(iconSize),
|
||||||
|
height: Math.floor(iconSize),
|
||||||
|
fit: 'contain',
|
||||||
|
background: { r: 255, g: 255, b: 255, alpha: 0 },
|
||||||
|
kernel: 'lanczos3' // Use high-quality resampling
|
||||||
|
})
|
||||||
|
.toBuffer(),
|
||||||
|
gravity: 'center'
|
||||||
|
}
|
||||||
|
])
|
||||||
|
.png({ quality: 100 }) // Use maximum PNG quality
|
||||||
|
.toFile(path.join(OUTPUT_DIR, `screenshot-${size.name}.png`))
|
||||||
|
|
||||||
|
console.log(`Generated screenshot-${size.name}.png`)
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error generating splash screens:', error)
|
||||||
|
process.exit(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
generateSplashScreens()
|
@ -3,10 +3,77 @@ import { fileURLToPath, URL } from 'node:url'
|
|||||||
import { defineConfig } from 'vite'
|
import { defineConfig } from 'vite'
|
||||||
import vue from '@vitejs/plugin-vue'
|
import vue from '@vitejs/plugin-vue'
|
||||||
import vueJsx from '@vitejs/plugin-vue-jsx'
|
import vueJsx from '@vitejs/plugin-vue-jsx'
|
||||||
|
import { VitePWA } from 'vite-plugin-pwa'
|
||||||
|
|
||||||
// https://vitejs.dev/config/
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
plugins: [vue(), vueJsx()],
|
plugins: [
|
||||||
|
vue(),
|
||||||
|
vueJsx(),
|
||||||
|
VitePWA({
|
||||||
|
registerType: 'autoUpdate',
|
||||||
|
includeAssets: [
|
||||||
|
'app_icons/web/favicon.ico',
|
||||||
|
'app_icons/web/splash-750x1334@2x.png',
|
||||||
|
'app_icons/web/splash-1170x2532@3x.png',
|
||||||
|
'app_icons/web/splash-1290x2796@3x.png',
|
||||||
|
'app_icons/web/splash-2048x2732@2x.png'
|
||||||
|
],
|
||||||
|
manifest: {
|
||||||
|
name: 'MiniQR',
|
||||||
|
short_name: 'MiniQR',
|
||||||
|
description: 'A minimal QR code generator and scanner',
|
||||||
|
theme_color: '#ffffff',
|
||||||
|
background_color: '#ffffff',
|
||||||
|
display: 'standalone',
|
||||||
|
orientation: 'portrait',
|
||||||
|
icons: [
|
||||||
|
{
|
||||||
|
src: 'app_icons/web/icon-192.png',
|
||||||
|
sizes: '192x192',
|
||||||
|
type: 'image/png'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
src: 'app_icons/web/icon-192-maskable.png',
|
||||||
|
sizes: '192x192',
|
||||||
|
type: 'image/png',
|
||||||
|
purpose: 'maskable'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
src: 'app_icons/web/icon-512.png',
|
||||||
|
sizes: '512x512',
|
||||||
|
type: 'image/png'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
src: 'app_icons/web/icon-512-maskable.png',
|
||||||
|
sizes: '512x512',
|
||||||
|
type: 'image/png',
|
||||||
|
purpose: 'maskable'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
screenshots: [
|
||||||
|
{
|
||||||
|
src: 'app_icons/web/screenshot-narrow.png',
|
||||||
|
sizes: '3510x7596',
|
||||||
|
type: 'image/png',
|
||||||
|
form_factor: 'narrow'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
src: 'app_icons/web/screenshot-wide.png',
|
||||||
|
sizes: '7596x3510',
|
||||||
|
type: 'image/png',
|
||||||
|
form_factor: 'wide'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
workbox: {
|
||||||
|
navigateFallback: 'index.html'
|
||||||
|
},
|
||||||
|
devOptions: {
|
||||||
|
enabled: true,
|
||||||
|
type: 'module'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
],
|
||||||
resolve: {
|
resolve: {
|
||||||
alias: {
|
alias: {
|
||||||
'@': fileURLToPath(new URL('./src', import.meta.url))
|
'@': fileURLToPath(new URL('./src', import.meta.url))
|
||||||
|