Add environment variables for presets and local storage configuration (#167)
This commit is contained in:
parent
2fe35fafcc
commit
c5a9bf91c8
@ -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
|
@ -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.
|
||||||
|
|
||||||
|
26
README.md
26
README.md
@ -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
|
||||||
|
@ -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
7
env.d.ts
vendored
@ -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 {
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
@ -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]
|
||||||
|
@ -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]
|
Loading…
x
Reference in New Issue
Block a user