Add environment variables for presets and local storage configuration (#167)

This commit is contained in:
Estee Tey 2025-06-08 09:58:50 +08:00 committed by GitHub
parent 2fe35fafcc
commit c5a9bf91c8
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 117 additions and 31 deletions

View File

@ -8,6 +8,9 @@ CROWDIN_PROJECT_ID="your_project_id"
# Default: / # Default: /
BASE_PATH=/ BASE_PATH=/
# Hide credits in the footer VITE_HIDE_CREDITS=false
# Default: false VITE_DEFAULT_PRESET=plain
VITE_HIDE_CREDITS=false VITE_QR_CODE_PRESETS=
VITE_FRAME_PRESET=
VITE_FRAME_PRESETS=
VITE_DISABLE_LOCAL_STORAGE=false

View File

@ -83,7 +83,7 @@ The project is a modern Vite-powered Vue.js 3 application with TypeScript suppor
**Utilities (`src/utils/`):** **Utilities (`src/utils/`):**
- `presets.ts`: QR code style presets and preset management - `qrCodePresets.ts`: QR code style presets and preset management
- `dataEncoding.ts`: Data encoding/decoding utilities for different QR code types - `dataEncoding.ts`: Data encoding/decoding utilities for different QR code types
- `convertToImage.ts`: Image conversion and export utilities (PNG, JPG, SVG) - `convertToImage.ts`: Image conversion and export utilities (PNG, JPG, SVG)
- `csv.ts`: CSV parsing and validation for batch operations - `csv.ts`: CSV parsing and validation for batch operations
@ -210,7 +210,7 @@ const yourNewPreset = {
} }
``` ```
Then add it to the `allPresets` array in `src/utils/presets.ts`. New presets should always be added as the last item in the array. Then add it to the `allQrCodePresets` array in `src/utils/qrCodePresets.ts`. New presets should always be added as the last item in the array.
You will then see your new preset in the Presets dropdown in the website. You will then see your new preset in the Presets dropdown in the website.

View File

@ -138,32 +138,42 @@ docker run -d -p 8081:8080 mini-qr
### Customization ### Customization
#### Environment Variables
| Variable | Description | Default |
| ---------------------------- | ---------------------------------------------------------------------------------- | --------- |
| `BASE_PATH` | Base path for deployment | `/` |
| `VITE_HIDE_CREDITS` | Set to `"true"` to hide credits in the footer | `"false"` |
| `VITE_DEFAULT_PRESET` | Name of the default QR code preset to load (e.g., `"lyqht"`) | `""` |
| `VITE_QR_CODE_PRESETS` | JSON string defining custom QR code presets. E.g., `'[{"name":"c1","data":"hi"}]'` | `"[]"` |
| `VITE_FRAME_PRESET` | Name of the default frame preset to load (e.g., `"default"`) | `""` |
| `VITE_FRAME_PRESETS` | JSON string defining custom frame presets. E.g., `'[{"name":"fA","text":"QR"}]'` | `"[]"` |
| `VITE_DISABLE_LOCAL_STORAGE` | Set to `"true"` to disable loading saved settings from local storage on startup | `"false"` |
### Docker configuration
- You can edit `nginx.conf` or mount your own static files by uncommenting the `volumes` section in `docker-compose.yml`. - You can edit `nginx.conf` or mount your own static files by uncommenting the `volumes` section in `docker-compose.yml`.
- The production image uses Nginx for optimal static file serving. - The production image uses Nginx for optimal static file serving.
- The `.dockerignore` file is included for smaller, faster builds. - The `.dockerignore` file is included for smaller, faster builds.
- Set `HIDE_CREDITS=1` to remove the maintainer credit from the footer.
- Set `BASE_PATH=/your-path` to deploy the app under a subdirectory (e.g., for hosting at `domain.com/your-path`). - Set `BASE_PATH=/your-path` to deploy the app under a subdirectory (e.g., for hosting at `domain.com/your-path`).
- If you want to have a default preset to be fixed, you should set `VITE_DISABLE_LOCAL_STORAGE=true`
#### Environment Variables
| Variable | Description | Default |
|----------|-------------|---------|
| `HIDE_CREDITS` | Hide credits in the footer | `false` |
| `BASE_PATH` | Base path for deployment | `/` |
#### Examples #### Examples
Deploy at root path (default): Deploy at root path (default):
```bash ```bash
docker compose up -d docker compose up -d
``` ```
Deploy at subdirectory `/mini-qr`: Deploy at subdirectory `/mini-qr`:
```bash ```bash
BASE_PATH=/mini-qr docker compose up -d BASE_PATH=/mini-qr docker compose up -d
``` ```
For custom builds with specific BASE_PATH: For custom builds with specific BASE_PATH:
```bash ```bash
docker build --build-arg BASE_PATH=/mini-qr -t mini-qr . docker build --build-arg BASE_PATH=/mini-qr -t mini-qr .
docker run -d -p 8081:8080 mini-qr docker run -d -p 8081:8080 mini-qr

View File

@ -7,6 +7,11 @@ services:
environment: environment:
- VITE_HIDE_CREDITS=${HIDE_CREDITS:-false} - VITE_HIDE_CREDITS=${HIDE_CREDITS:-false}
- BASE_PATH=${BASE_PATH:-/} - BASE_PATH=${BASE_PATH:-/}
- VITE_DEFAULT_PRESET=${DEFAULT_PRESET:-}
- VITE_QR_CODE_PRESETS=${PRESETS:-}
- VITE_FRAME_PRESET=${FRAME_PRESET:-}
- VITE_FRAME_PRESETS=${FRAME_PRESETS:-}
- VITE_DISABLE_LOCAL_STORAGE=${DISABLE_LOCAL_STORAGE:-false}
# Uncomment the following lines to build locally instead of pulling from ghcr.io # Uncomment the following lines to build locally instead of pulling from ghcr.io
build: build:
context: . context: .

7
env.d.ts vendored
View File

@ -1,8 +1,13 @@
/// <reference types="vite/client" /> /// <reference types="vite/client" />
interface ImportMetaEnv { interface ImportMetaEnv {
readonly VITE_HIDE_CREDITS?: string
readonly BASE_PATH?: string readonly BASE_PATH?: string
readonly VITE_HIDE_CREDITS?: string
readonly VITE_DEFAULT_PRESET?: string
readonly VITE_QR_CODE_PRESETS?: string
readonly VITE_FRAME_PRESET?: string
readonly VITE_FRAME_PRESETS?: string
readonly VITE_DISABLE_LOCAL_STORAGE?: string
} }
interface ImportMeta { interface ImportMeta {

View File

@ -26,8 +26,8 @@ import {
import { parseCSV, validateCSVData } from '@/utils/csv' import { parseCSV, validateCSVData } from '@/utils/csv'
import { generateVCardData } from '@/utils/dataEncoding' import { generateVCardData } from '@/utils/dataEncoding'
import { getNumericCSSValue } from '@/utils/formatting' import { getNumericCSSValue } from '@/utils/formatting'
import { allPresets, type Preset } from '@/utils/presets' import { allQrCodePresets, defaultPreset, type Preset } from '@/utils/qrCodePresets'
import { allFramePresets, type FramePreset } from '@/utils/framePresets' import { allFramePresets, defaultFramePreset, type FramePreset } from '@/utils/framePresets'
import { useMediaQuery } from '@vueuse/core' import { useMediaQuery } from '@vueuse/core'
import JSZip from 'jszip' import JSZip from 'jszip'
import { import {
@ -65,7 +65,6 @@ const { t } = useI18n()
//#endregion //#endregion
//#region /* QR code style settings */ //#region /* QR code style settings */
const defaultPreset = allPresets[0]
const data = ref(props.initialData || '') const data = ref(props.initialData || '')
const debouncedData = ref(data.value) const debouncedData = ref(data.value)
let dataDebounceTimer: ReturnType<typeof setTimeout> let dataDebounceTimer: ReturnType<typeof setTimeout>
@ -207,8 +206,8 @@ function uploadImage() {
const isPresetSelectOpen = ref(false) const isPresetSelectOpen = ref(false)
const allPresetOptions = computed(() => { const allPresetOptions = computed(() => {
const options = lastCustomLoadedPreset.value const options = lastCustomLoadedPreset.value
? [lastCustomLoadedPreset.value, ...allPresets] ? [lastCustomLoadedPreset.value, ...allQrCodePresets]
: allPresets : allQrCodePresets
return options.map((preset) => ({ value: preset.name, label: t(preset.name) })) return options.map((preset) => ({ value: preset.name, label: t(preset.name) }))
}) })
const selectedPreset = ref< const selectedPreset = ref<
@ -244,19 +243,29 @@ watch(selectedPreset, () => {
const LAST_LOADED_LOCALLY_PRESET_KEY = 'Last saved locally' const LAST_LOADED_LOCALLY_PRESET_KEY = 'Last saved locally'
const LOADED_FROM_FILE_PRESET_KEY = 'Loaded from file' const LOADED_FROM_FILE_PRESET_KEY = 'Loaded from file'
const CUSTOM_LOADED_PRESET_KEYS = [LAST_LOADED_LOCALLY_PRESET_KEY, LOADED_FROM_FILE_PRESET_KEY] const CUSTOM_LOADED_PRESET_KEYS = [LAST_LOADED_LOCALLY_PRESET_KEY, LOADED_FROM_FILE_PRESET_KEY]
const selectedPresetKey = ref<string>(LAST_LOADED_LOCALLY_PRESET_KEY) const selectedPresetKey = ref<string>(
import.meta.env.VITE_DISABLE_LOCAL_STORAGE === 'true'
? defaultPreset.name
: localStorage.getItem('qrCodeConfig')
? LAST_LOADED_LOCALLY_PRESET_KEY
: defaultPreset.name
)
const lastCustomLoadedPreset = ref<Preset>() const lastCustomLoadedPreset = ref<Preset>()
watch( watch(
selectedPresetKey, selectedPresetKey,
(newKey, prevKey) => { (newKey, prevKey) => {
if (newKey === prevKey || !newKey) return if (newKey === prevKey || !newKey) return
if (CUSTOM_LOADED_PRESET_KEYS.includes(newKey) && lastCustomLoadedPreset.value) { if (
import.meta.env.VITE_DISABLE_LOCAL_STORAGE !== 'true' &&
CUSTOM_LOADED_PRESET_KEYS.includes(newKey) &&
lastCustomLoadedPreset.value
) {
selectedPreset.value = lastCustomLoadedPreset.value selectedPreset.value = lastCustomLoadedPreset.value
return return
} }
const updatedPreset = allPresets.find((preset) => preset.name === newKey) const updatedPreset = allQrCodePresets.find((preset) => preset.name === newKey)
if (updatedPreset) { if (updatedPreset) {
selectedPreset.value = updatedPreset selectedPreset.value = updatedPreset
} }
@ -301,7 +310,6 @@ const frameStyle = ref<FrameStyle>({
borderRadius: '8px', borderRadius: '8px',
padding: '16px' padding: '16px'
}) })
const defaultFramePreset = allFramePresets[0]
const selectedFramePresetKey = ref<string>(defaultFramePreset.name) const selectedFramePresetKey = ref<string>(defaultFramePreset.name)
const lastCustomLoadedFramePreset = ref<FramePreset>() const lastCustomLoadedFramePreset = ref<FramePreset>()
const CUSTOM_LOADED_FRAME_PRESET_KEYS = [ const CUSTOM_LOADED_FRAME_PRESET_KEYS = [
@ -327,7 +335,11 @@ watch(
(newKey, prevKey) => { (newKey, prevKey) => {
if (newKey === prevKey || !newKey) return if (newKey === prevKey || !newKey) return
if (CUSTOM_LOADED_FRAME_PRESET_KEYS.includes(newKey) && lastCustomLoadedFramePreset.value) { if (
import.meta.env.VITE_DISABLE_LOCAL_STORAGE !== 'true' &&
CUSTOM_LOADED_FRAME_PRESET_KEYS.includes(newKey) &&
lastCustomLoadedFramePreset.value
) {
applyFramePreset(lastCustomLoadedFramePreset.value) applyFramePreset(lastCustomLoadedFramePreset.value)
return return
} }
@ -588,7 +600,23 @@ watch(
) )
onMounted(() => { onMounted(() => {
loadQRConfigFromLocalStorage() if (import.meta.env.VITE_DISABLE_LOCAL_STORAGE !== 'true') {
const qrCodeConfigString = localStorage.getItem('qrCodeConfig')
if (qrCodeConfigString) {
loadQRConfig(qrCodeConfigString, LAST_LOADED_LOCALLY_PRESET_KEY)
} else {
// No localStorage data found, use the environment variable default preset
selectedPreset.value = { ...defaultPreset }
selectedPresetKey.value = defaultPreset.name
}
// No separate frameConfig loading from localStorage noted,
// assuming selectedFramePresetKey watcher handles it if lastCustomLoadedFramePreset was populated by loadQRConfig
}
// Set initial data if provided through props
if (props.initialData) {
data.value = props.initialData
}
}) })
//#endregion //#endregion

View File

@ -14,7 +14,7 @@ export interface FramePreset {
position?: 'top' | 'bottom' | 'left' | 'right' position?: 'top' | 'bottom' | 'left' | 'right'
} }
export const defaultFramePreset: FramePreset = { export const plainFramePreset: FramePreset = {
name: 'Default Frame', name: 'Default Frame',
style: { style: {
textColor: '#000000', textColor: '#000000',
@ -50,8 +50,24 @@ export const borderlessFramePreset: FramePreset = {
} }
} }
export const allFramePresets: FramePreset[] = [ export const builtInFramePresets: FramePreset[] = [
defaultFramePreset, plainFramePreset,
darkFramePreset, darkFramePreset,
borderlessFramePreset borderlessFramePreset
] ]
function parseFramePresetsFromEnv(envVal?: string): FramePreset[] | undefined {
if (!envVal) return undefined
try {
return JSON.parse(envVal) as FramePreset[]
} catch (err) {
console.error('Failed to parse VITE_FRAME_PRESETS', err)
return undefined
}
}
const envFramePresets = parseFramePresetsFromEnv(import.meta.env.VITE_FRAME_PRESETS)
export const allFramePresets: FramePreset[] = envFramePresets ?? builtInFramePresets
export const defaultFramePreset: FramePreset =
allFramePresets.find((p) => p.name === import.meta.env.VITE_FRAME_PRESET) ?? allFramePresets[0]

View File

@ -244,7 +244,7 @@ export const vueJsPreset: Preset = {
// Individual presets // Individual presets
export const defaultPreset: Preset = { export const lyqhtPreset: Preset = {
...defaultPresetOptions, ...defaultPresetOptions,
name: 'Default (lyqht)', name: 'Default (lyqht)',
data: 'https://github.com/lyqht', data: 'https://github.com/lyqht',
@ -316,8 +316,8 @@ export const hackomania2025Preset = {
style: Hackomania2025Config.style style: Hackomania2025Config.style
} as Preset } as Preset
export const allPresets: Preset[] = [ export const builtInPresets: Preset[] = [
defaultPreset, lyqhtPreset,
plainPreset, plainPreset,
...[ ...[
padletPreset, padletPreset,
@ -336,3 +336,22 @@ export const allPresets: Preset[] = [
hackomania2025Preset hackomania2025Preset
].sort((a, b) => a.name.localeCompare(b.name)) ].sort((a, b) => a.name.localeCompare(b.name))
] ]
function parsePresetsFromEnv(envVal?: string): Preset[] | undefined {
if (!envVal) return undefined
try {
return JSON.parse(envVal) as Preset[]
} catch (err) {
console.error('Failed to parse VITE_QR_CODE_PRESETS', err)
return undefined
}
}
const envPresets = parsePresetsFromEnv(import.meta.env.VITE_QR_CODE_PRESETS)
export const allQrCodePresets: Preset[] = envPresets ?? builtInPresets
export const defaultPreset: Preset =
import.meta.env.VITE_DEFAULT_PRESET
? allQrCodePresets.find((p) => p.name === import.meta.env.VITE_DEFAULT_PRESET) ??
allQrCodePresets[0]
: allQrCodePresets[0]