Refactor Docker setup and enhance development environment
- Updated docker-compose.dev.yml to use Dockerfile.dev for backend builds and added HOST environment variable for frontend. - Introduced Dockerfile.dev for streamlined backend development with Go. - Enhanced development documentation to reflect changes in local setup and API proxying. - Removed outdated frontend Dockerfile and adjusted frontend configuration for improved development experience.
This commit is contained in:
@@ -1,16 +0,0 @@
|
||||
# Use Node 20+ for build (required by Tailwind/PostCSS toolchain)
|
||||
FROM node:20-alpine AS builder
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY package.json package-lock.json* ./
|
||||
RUN npm ci
|
||||
|
||||
COPY . .
|
||||
RUN npm run generate
|
||||
|
||||
# Output: .output/public is the static site root (for Go http.Dir("frontend"))
|
||||
FROM alpine:3.19
|
||||
RUN apk add --no-cache bash
|
||||
COPY --from=builder /app/.output/public /frontend
|
||||
# Optional: when integrating with main Dockerfile, copy /frontend into the image
|
||||
@@ -1,66 +1,66 @@
|
||||
# HnH Map – Nuxt 3 frontend
|
||||
|
||||
Nuxt 3 + Tailwind + DaisyUI frontend for the HnH map. Served by the Go backend under `/map/`.
|
||||
|
||||
In dev mode the app is available at the path with baseURL **`/map/`** (e.g. `http://localhost:3000/map/`). The Go backend must be reachable (directly or via the dev proxy in `nuxt.config.ts`).
|
||||
|
||||
## Project structure
|
||||
|
||||
- **pages/** — route pages (e.g. map view, profile, login)
|
||||
- **components/** — Vue components
|
||||
- **composables/** — shared composition functions
|
||||
- **layouts/** — layout components
|
||||
- **server/** — Nitro server (if used)
|
||||
- **plugins/** — Nuxt plugins
|
||||
- **public/gfx/** — static assets (sprites, terrain, etc.)
|
||||
|
||||
## Requirements
|
||||
|
||||
- **Node.js 20+** (required for build; `engines` in package.json). Use `nvm use` if you have `.nvmrc`, or build via Docker (see below).
|
||||
- npm
|
||||
|
||||
## Setup
|
||||
|
||||
```bash
|
||||
npm install
|
||||
```
|
||||
|
||||
## Development
|
||||
|
||||
```bash
|
||||
npm run dev
|
||||
```
|
||||
|
||||
Then open the app at the path shown (e.g. `http://localhost:3000/map/`). Ensure the Go backend is running and proxying or serving this app if needed.
|
||||
|
||||
## Build
|
||||
|
||||
```bash
|
||||
npm run build
|
||||
```
|
||||
|
||||
Static export (for Go `http.Dir`):
|
||||
|
||||
```bash
|
||||
npm run generate
|
||||
```
|
||||
|
||||
Output is in `.output/public`. To serve from the existing `frontend` directory, copy contents to `../frontend` after generate, or set `nitro.output.dir` in `nuxt.config.ts` and build from the repo root.
|
||||
|
||||
## Build with Docker (Node 20)
|
||||
|
||||
Build requires Node 20+. If your host has an older version (e.g. Node 18), build the frontend in Docker:
|
||||
|
||||
```bash
|
||||
docker build -t frontend-nuxt .
|
||||
docker create --name fn frontend-nuxt
|
||||
docker cp fn:/frontend ./output-public
|
||||
docker rm fn
|
||||
# Copy output-public/* into repo frontend/ and run Go server
|
||||
```
|
||||
|
||||
## Cutover from Vue 2 frontend
|
||||
|
||||
1. Build this app (`npm run generate`).
|
||||
2. Copy `.output/public/*` into the repo’s `frontend` directory (or point Go at the Nuxt output directory).
|
||||
3. Restart the Go server. The same `/map/` routes and API remain.
|
||||
# HnH Map – Nuxt 3 frontend
|
||||
|
||||
Nuxt 3 + Tailwind + DaisyUI frontend for the HnH map. Served by the Go backend under `/map/`.
|
||||
|
||||
In dev mode the app is available at the path with baseURL **`/map/`** (e.g. `http://localhost:3000/map/`). The Go backend must be reachable (directly or via the dev proxy in `nuxt.config.ts`).
|
||||
|
||||
## Project structure
|
||||
|
||||
- **pages/** — route pages (e.g. map view, profile, login)
|
||||
- **components/** — Vue components
|
||||
- **composables/** — shared composition functions
|
||||
- **layouts/** — layout components
|
||||
- **server/** — Nitro server (if used)
|
||||
- **plugins/** — Nuxt plugins
|
||||
- **public/gfx/** — static assets (sprites, terrain, etc.)
|
||||
|
||||
## Requirements
|
||||
|
||||
- **Node.js 20+** (required for build; `engines` in package.json). Use `nvm use` if you have `.nvmrc`, or build via Docker (see below).
|
||||
- npm
|
||||
|
||||
## Setup
|
||||
|
||||
```bash
|
||||
npm install
|
||||
```
|
||||
|
||||
## Development
|
||||
|
||||
```bash
|
||||
npm run dev
|
||||
```
|
||||
|
||||
Then open the app at the path shown (e.g. `http://localhost:3000/map/`). Ensure the Go backend is running and proxying or serving this app if needed.
|
||||
|
||||
## Build
|
||||
|
||||
```bash
|
||||
npm run build
|
||||
```
|
||||
|
||||
Static export (for Go `http.Dir`):
|
||||
|
||||
```bash
|
||||
npm run generate
|
||||
```
|
||||
|
||||
Output is in `.output/public`. To serve from the existing `frontend` directory, copy contents to `../frontend` after generate, or set `nitro.output.dir` in `nuxt.config.ts` and build from the repo root.
|
||||
|
||||
## Build with Docker (Node 20)
|
||||
|
||||
Build requires Node 20+. If your host has an older version (e.g. Node 18), build the frontend in Docker:
|
||||
|
||||
```bash
|
||||
docker build -t frontend-nuxt .
|
||||
docker create --name fn frontend-nuxt
|
||||
docker cp fn:/frontend ./output-public
|
||||
docker rm fn
|
||||
# Copy output-public/* into repo frontend/ and run Go server
|
||||
```
|
||||
|
||||
## Cutover from Vue 2 frontend
|
||||
|
||||
1. Build this app (`npm run generate`).
|
||||
2. Copy `.output/public/*` into the repo’s `frontend` directory (or point Go at the Nuxt output directory).
|
||||
3. Restart the Go server. The same `/map/` routes and API remain.
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,63 +1,65 @@
|
||||
<template>
|
||||
<!-- Context menu (tile) -->
|
||||
<div
|
||||
v-show="contextMenu.tile.show"
|
||||
class="fixed z-[1000] bg-base-100/95 backdrop-blur-xl shadow-xl rounded-lg border border-base-300 py-1 min-w-[180px] transition-opacity duration-150"
|
||||
:style="{ left: contextMenu.tile.x + 'px', top: contextMenu.tile.y + 'px' }"
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-ghost btn-sm w-full justify-start hover:bg-base-200 transition-colors"
|
||||
@click="onWipeTile(contextMenu.tile.data?.coords)"
|
||||
>
|
||||
Wipe tile {{ contextMenu.tile.data?.coords?.x }}, {{ contextMenu.tile.data?.coords?.y }}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-ghost btn-sm w-full justify-start hover:bg-base-200 transition-colors"
|
||||
@click="onRewriteCoords(contextMenu.tile.data?.coords)"
|
||||
>
|
||||
Rewrite tile coords
|
||||
</button>
|
||||
</div>
|
||||
<!-- Context menu (marker) -->
|
||||
<div
|
||||
v-show="contextMenu.marker.show"
|
||||
class="fixed z-[1000] bg-base-100/95 backdrop-blur-xl shadow-xl rounded-lg border border-base-300 py-1 min-w-[180px] transition-opacity duration-150"
|
||||
:style="{ left: contextMenu.marker.x + 'px', top: contextMenu.marker.y + 'px' }"
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-ghost btn-sm w-full justify-start hover:bg-base-200 transition-colors"
|
||||
@click="onHideMarker(contextMenu.marker.data?.id)"
|
||||
>
|
||||
Hide marker {{ contextMenu.marker.data?.name }}
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { ContextMenuState } from '~/composables/useMapLogic'
|
||||
|
||||
defineProps<{
|
||||
contextMenu: ContextMenuState
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
wipeTile: [coords: { x: number; y: number } | undefined]
|
||||
rewriteCoords: [coords: { x: number; y: number } | undefined]
|
||||
hideMarker: [id: number | undefined]
|
||||
}>()
|
||||
|
||||
function onWipeTile(coords: { x: number; y: number } | undefined) {
|
||||
if (coords) emit('wipeTile', coords)
|
||||
}
|
||||
|
||||
function onRewriteCoords(coords: { x: number; y: number } | undefined) {
|
||||
if (coords) emit('rewriteCoords', coords)
|
||||
}
|
||||
|
||||
function onHideMarker(id: number | undefined) {
|
||||
if (id != null) emit('hideMarker', id)
|
||||
}
|
||||
</script>
|
||||
<template>
|
||||
<!-- Context menu (tile) — Teleport so it is not clipped by map/overflow -->
|
||||
<Teleport to="body">
|
||||
<div
|
||||
v-show="contextMenu.tile.show"
|
||||
class="fixed z-[9999] bg-base-100/95 backdrop-blur-xl shadow-xl rounded-lg border border-base-300 py-1 min-w-[180px] transition-opacity duration-150"
|
||||
:style="{ left: contextMenu.tile.x + 'px', top: contextMenu.tile.y + 'px' }"
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-ghost btn-sm w-full justify-start hover:bg-base-200 transition-colors"
|
||||
@click="onWipeTile(contextMenu.tile.data?.coords)"
|
||||
>
|
||||
Wipe tile {{ contextMenu.tile.data?.coords?.x }}, {{ contextMenu.tile.data?.coords?.y }}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-ghost btn-sm w-full justify-start hover:bg-base-200 transition-colors"
|
||||
@click="onRewriteCoords(contextMenu.tile.data?.coords)"
|
||||
>
|
||||
Rewrite tile coords
|
||||
</button>
|
||||
</div>
|
||||
<!-- Context menu (marker) -->
|
||||
<div
|
||||
v-show="contextMenu.marker.show"
|
||||
class="fixed z-[9999] bg-base-100/95 backdrop-blur-xl shadow-xl rounded-lg border border-base-300 py-1 min-w-[180px] transition-opacity duration-150"
|
||||
:style="{ left: contextMenu.marker.x + 'px', top: contextMenu.marker.y + 'px' }"
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-ghost btn-sm w-full justify-start hover:bg-base-200 transition-colors"
|
||||
@click="onHideMarker(contextMenu.marker.data?.id)"
|
||||
>
|
||||
Hide marker {{ contextMenu.marker.data?.name }}
|
||||
</button>
|
||||
</div>
|
||||
</Teleport>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { ContextMenuState } from '~/composables/useMapLogic'
|
||||
|
||||
defineProps<{
|
||||
contextMenu: ContextMenuState
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
wipeTile: [coords: { x: number; y: number } | undefined]
|
||||
rewriteCoords: [coords: { x: number; y: number } | undefined]
|
||||
hideMarker: [id: number | undefined]
|
||||
}>()
|
||||
|
||||
function onWipeTile(coords: { x: number; y: number } | undefined) {
|
||||
if (coords) emit('wipeTile', coords)
|
||||
}
|
||||
|
||||
function onRewriteCoords(coords: { x: number; y: number } | undefined) {
|
||||
if (coords) emit('rewriteCoords', coords)
|
||||
}
|
||||
|
||||
function onHideMarker(id: number | undefined) {
|
||||
if (id != null) emit('hideMarker', id)
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -101,6 +101,7 @@ export function useMapLogic() {
|
||||
}
|
||||
|
||||
function openTileContextMenu(clientX: number, clientY: number, coords: { x: number; y: number }) {
|
||||
closeContextMenus()
|
||||
contextMenu.tile.show = true
|
||||
contextMenu.tile.x = clientX
|
||||
contextMenu.tile.y = clientY
|
||||
@@ -108,6 +109,7 @@ export function useMapLogic() {
|
||||
}
|
||||
|
||||
function openMarkerContextMenu(clientX: number, clientY: number, id: number, name: string) {
|
||||
closeContextMenus()
|
||||
contextMenu.marker.show = true
|
||||
contextMenu.marker.x = clientX
|
||||
contextMenu.marker.y = clientY
|
||||
|
||||
@@ -1,128 +1,128 @@
|
||||
<template>
|
||||
<div class="h-screen flex flex-col bg-base-100 overflow-hidden">
|
||||
<header class="navbar bg-base-100/80 backdrop-blur-xl border-b border-base-300/50 px-4 gap-2 shrink-0">
|
||||
<NuxtLink to="/" class="text-lg font-semibold hover:opacity-80 transition-all duration-200">{{ title }}</NuxtLink>
|
||||
<div class="flex-1" />
|
||||
<NuxtLink
|
||||
v-if="!isLogin"
|
||||
to="/"
|
||||
class="btn btn-ghost btn-sm gap-1.5 transition-all duration-200 hover:scale-105"
|
||||
:class="route.path === '/' ? 'btn-primary' : ''"
|
||||
>
|
||||
<icons-icon-map />
|
||||
Map
|
||||
</NuxtLink>
|
||||
<NuxtLink
|
||||
v-if="!isLogin"
|
||||
to="/profile"
|
||||
class="btn btn-ghost btn-sm gap-1.5 transition-all duration-200 hover:scale-105"
|
||||
:class="route.path === '/profile' ? 'btn-primary' : ''"
|
||||
>
|
||||
<icons-icon-user />
|
||||
Profile
|
||||
</NuxtLink>
|
||||
<NuxtLink
|
||||
v-if="!isLogin && isAdmin"
|
||||
to="/admin"
|
||||
class="btn btn-ghost btn-sm gap-1.5 transition-all duration-200 hover:scale-105"
|
||||
:class="route.path.startsWith('/admin') ? 'btn-primary' : ''"
|
||||
>
|
||||
<icons-icon-shield />
|
||||
Admin
|
||||
</NuxtLink>
|
||||
<button
|
||||
v-if="!isLogin && me"
|
||||
type="button"
|
||||
class="btn btn-ghost btn-sm btn-error btn-outline gap-1.5 transition-all duration-200 hover:scale-105"
|
||||
@click="doLogout"
|
||||
>
|
||||
<icons-icon-logout />
|
||||
Logout
|
||||
</button>
|
||||
<label class="swap swap-rotate btn btn-ghost btn-sm transition-all duration-200 hover:scale-105">
|
||||
<input type="checkbox" v-model="dark" @change="toggleTheme" />
|
||||
<span class="swap-off"><icons-icon-sun /></span>
|
||||
<span class="swap-on"><icons-icon-moon /></span>
|
||||
</label>
|
||||
<span v-if="live" class="badge badge-success badge-sm">Live</span>
|
||||
</header>
|
||||
<main class="flex-1 min-h-0 overflow-y-auto relative">
|
||||
<slot />
|
||||
</main>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
const THEME_KEY = 'hnh-map-theme'
|
||||
|
||||
function getInitialDark(): boolean {
|
||||
if (import.meta.client) {
|
||||
const stored = localStorage.getItem(THEME_KEY)
|
||||
if (stored === 'dark') return true
|
||||
if (stored === 'light') return false
|
||||
return window.matchMedia('(prefers-color-scheme: dark)').matches
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
const title = ref('HnH Map')
|
||||
const dark = ref(false)
|
||||
const live = ref(false)
|
||||
const me = ref<{ username?: string; auths?: string[] } | null>(null)
|
||||
|
||||
const { isLoginPath } = useAppPaths()
|
||||
const isLogin = computed(() => isLoginPath(route.path))
|
||||
const isAdmin = computed(() => !!me.value?.auths?.includes('admin'))
|
||||
|
||||
async function loadMe() {
|
||||
if (isLogin.value) return
|
||||
try {
|
||||
me.value = await useMapApi().me()
|
||||
} catch {
|
||||
me.value = null
|
||||
}
|
||||
}
|
||||
|
||||
async function loadConfig() {
|
||||
if (isLogin.value) return
|
||||
try {
|
||||
const config = await useMapApi().getConfig()
|
||||
if (config?.title) title.value = config.title
|
||||
} catch (_) {}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
dark.value = getInitialDark()
|
||||
const html = document.documentElement
|
||||
html.setAttribute('data-theme', dark.value ? 'dark' : 'light')
|
||||
})
|
||||
|
||||
watch(
|
||||
() => route.path,
|
||||
(path) => {
|
||||
if (!isLoginPath(path)) loadMe().then(loadConfig)
|
||||
},
|
||||
{ immediate: true }
|
||||
)
|
||||
|
||||
function toggleTheme() {
|
||||
const html = document.documentElement
|
||||
if (dark.value) {
|
||||
html.setAttribute('data-theme', 'dark')
|
||||
localStorage.setItem(THEME_KEY, 'dark')
|
||||
} else {
|
||||
html.setAttribute('data-theme', 'light')
|
||||
localStorage.setItem(THEME_KEY, 'light')
|
||||
}
|
||||
}
|
||||
|
||||
async function doLogout() {
|
||||
await useMapApi().logout()
|
||||
await router.push('/login')
|
||||
me.value = null
|
||||
}
|
||||
|
||||
defineExpose({ setLive: (v: boolean) => { live.value = v } })
|
||||
</script>
|
||||
<template>
|
||||
<div class="h-screen flex flex-col bg-base-100 overflow-hidden">
|
||||
<header class="navbar bg-base-100/80 backdrop-blur-xl border-b border-base-300/50 px-4 gap-2 shrink-0">
|
||||
<NuxtLink to="/" class="text-lg font-semibold hover:opacity-80 transition-all duration-200">{{ title }}</NuxtLink>
|
||||
<div class="flex-1" />
|
||||
<NuxtLink
|
||||
v-if="!isLogin"
|
||||
to="/"
|
||||
class="btn btn-ghost btn-sm gap-1.5 transition-all duration-200 hover:scale-105"
|
||||
:class="route.path === '/' ? 'btn-primary' : ''"
|
||||
>
|
||||
<icons-icon-map />
|
||||
Map
|
||||
</NuxtLink>
|
||||
<NuxtLink
|
||||
v-if="!isLogin"
|
||||
to="/profile"
|
||||
class="btn btn-ghost btn-sm gap-1.5 transition-all duration-200 hover:scale-105"
|
||||
:class="route.path === '/profile' ? 'btn-primary' : ''"
|
||||
>
|
||||
<icons-icon-user />
|
||||
Profile
|
||||
</NuxtLink>
|
||||
<NuxtLink
|
||||
v-if="!isLogin && isAdmin"
|
||||
to="/admin"
|
||||
class="btn btn-ghost btn-sm gap-1.5 transition-all duration-200 hover:scale-105"
|
||||
:class="route.path.startsWith('/admin') ? 'btn-primary' : ''"
|
||||
>
|
||||
<icons-icon-shield />
|
||||
Admin
|
||||
</NuxtLink>
|
||||
<button
|
||||
v-if="!isLogin && me"
|
||||
type="button"
|
||||
class="btn btn-ghost btn-sm btn-error btn-outline gap-1.5 transition-all duration-200 hover:scale-105"
|
||||
@click="doLogout"
|
||||
>
|
||||
<icons-icon-logout />
|
||||
Logout
|
||||
</button>
|
||||
<label class="swap swap-rotate btn btn-ghost btn-sm transition-all duration-200 hover:scale-105">
|
||||
<input type="checkbox" v-model="dark" @change="toggleTheme" />
|
||||
<span class="swap-off"><icons-icon-sun /></span>
|
||||
<span class="swap-on"><icons-icon-moon /></span>
|
||||
</label>
|
||||
<span v-if="live" class="badge badge-success badge-sm">Live</span>
|
||||
</header>
|
||||
<main class="flex-1 min-h-0 overflow-y-auto relative">
|
||||
<slot />
|
||||
</main>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
const THEME_KEY = 'hnh-map-theme'
|
||||
|
||||
function getInitialDark(): boolean {
|
||||
if (import.meta.client) {
|
||||
const stored = localStorage.getItem(THEME_KEY)
|
||||
if (stored === 'dark') return true
|
||||
if (stored === 'light') return false
|
||||
return window.matchMedia('(prefers-color-scheme: dark)').matches
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
const title = ref('HnH Map')
|
||||
const dark = ref(false)
|
||||
const live = ref(false)
|
||||
const me = ref<{ username?: string; auths?: string[] } | null>(null)
|
||||
|
||||
const { isLoginPath } = useAppPaths()
|
||||
const isLogin = computed(() => isLoginPath(route.path))
|
||||
const isAdmin = computed(() => !!me.value?.auths?.includes('admin'))
|
||||
|
||||
async function loadMe() {
|
||||
if (isLogin.value) return
|
||||
try {
|
||||
me.value = await useMapApi().me()
|
||||
} catch {
|
||||
me.value = null
|
||||
}
|
||||
}
|
||||
|
||||
async function loadConfig() {
|
||||
if (isLogin.value) return
|
||||
try {
|
||||
const config = await useMapApi().getConfig()
|
||||
if (config?.title) title.value = config.title
|
||||
} catch (_) {}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
dark.value = getInitialDark()
|
||||
const html = document.documentElement
|
||||
html.setAttribute('data-theme', dark.value ? 'dark' : 'light')
|
||||
})
|
||||
|
||||
watch(
|
||||
() => route.path,
|
||||
(path) => {
|
||||
if (!isLoginPath(path)) loadMe().then(loadConfig)
|
||||
},
|
||||
{ immediate: true }
|
||||
)
|
||||
|
||||
function toggleTheme() {
|
||||
const html = document.documentElement
|
||||
if (dark.value) {
|
||||
html.setAttribute('data-theme', 'dark')
|
||||
localStorage.setItem(THEME_KEY, 'dark')
|
||||
} else {
|
||||
html.setAttribute('data-theme', 'light')
|
||||
localStorage.setItem(THEME_KEY, 'light')
|
||||
}
|
||||
}
|
||||
|
||||
async function doLogout() {
|
||||
await useMapApi().logout()
|
||||
await router.push('/login')
|
||||
me.value = null
|
||||
}
|
||||
|
||||
defineExpose({ setLive: (v: boolean) => { live.value = v } })
|
||||
</script>
|
||||
|
||||
@@ -1,54 +1,54 @@
|
||||
// https://nuxt.com/docs/api/configuration/nuxt-config
|
||||
import { viteUriGuard } from './vite/vite-uri-guard'
|
||||
|
||||
export default defineNuxtConfig({
|
||||
compatibilityDate: '2024-11-01',
|
||||
devtools: { enabled: true },
|
||||
|
||||
app: {
|
||||
baseURL: '/',
|
||||
pageTransition: { name: 'page', mode: 'out-in' },
|
||||
head: {
|
||||
title: 'HnH Map',
|
||||
meta: [{ charset: 'utf-8' }, { name: 'viewport', content: 'width=device-width, initial-scale=1' }],
|
||||
link: [
|
||||
{ rel: 'preconnect', href: 'https://fonts.googleapis.com' },
|
||||
{ rel: 'preconnect', href: 'https://fonts.gstatic.com', crossorigin: '' },
|
||||
{ rel: 'stylesheet', href: 'https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap' },
|
||||
],
|
||||
},
|
||||
},
|
||||
|
||||
ssr: false,
|
||||
|
||||
runtimeConfig: {
|
||||
public: {
|
||||
apiBase: '/map/api',
|
||||
},
|
||||
},
|
||||
|
||||
modules: ['@nuxtjs/tailwindcss'],
|
||||
tailwindcss: {
|
||||
cssPath: '~/assets/css/app.css',
|
||||
},
|
||||
css: ['~/assets/css/app.css', 'leaflet/dist/leaflet.css', '~/assets/css/leaflet-overrides.css'],
|
||||
|
||||
vite: {
|
||||
plugins: [viteUriGuard()],
|
||||
optimizeDeps: {
|
||||
include: ['leaflet'],
|
||||
},
|
||||
},
|
||||
|
||||
// Dev: proxy /map API, SSE and grids to Go backend (e.g. docker compose -f docker-compose.dev.yml)
|
||||
nitro: {
|
||||
devProxy: {
|
||||
'/map/api': { target: 'http://backend:3080', changeOrigin: true },
|
||||
'/map/updates': { target: 'http://backend:3080', changeOrigin: true },
|
||||
'/map/grids': { target: 'http://backend:3080', changeOrigin: true },
|
||||
},
|
||||
},
|
||||
|
||||
// For cutover: set nitro.preset to 'static' and optionally copy .output/public to ../frontend
|
||||
// nitro: { output: { dir: '../frontend' } },
|
||||
})
|
||||
// https://nuxt.com/docs/api/configuration/nuxt-config
|
||||
import { viteUriGuard } from './vite/vite-uri-guard'
|
||||
|
||||
export default defineNuxtConfig({
|
||||
compatibilityDate: '2024-11-01',
|
||||
devtools: { enabled: true },
|
||||
|
||||
app: {
|
||||
baseURL: '/',
|
||||
pageTransition: { name: 'page', mode: 'out-in' },
|
||||
head: {
|
||||
title: 'HnH Map',
|
||||
meta: [{ charset: 'utf-8' }, { name: 'viewport', content: 'width=device-width, initial-scale=1' }],
|
||||
link: [
|
||||
{ rel: 'preconnect', href: 'https://fonts.googleapis.com' },
|
||||
{ rel: 'preconnect', href: 'https://fonts.gstatic.com', crossorigin: '' },
|
||||
{ rel: 'stylesheet', href: 'https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap' },
|
||||
],
|
||||
},
|
||||
},
|
||||
|
||||
ssr: false,
|
||||
|
||||
runtimeConfig: {
|
||||
public: {
|
||||
apiBase: '/map/api',
|
||||
},
|
||||
},
|
||||
|
||||
modules: ['@nuxtjs/tailwindcss'],
|
||||
tailwindcss: {
|
||||
cssPath: '~/assets/css/app.css',
|
||||
},
|
||||
css: ['~/assets/css/app.css', 'leaflet/dist/leaflet.css', '~/assets/css/leaflet-overrides.css'],
|
||||
|
||||
vite: {
|
||||
plugins: [viteUriGuard()],
|
||||
optimizeDeps: {
|
||||
include: ['leaflet'],
|
||||
},
|
||||
},
|
||||
|
||||
// Dev: proxy /map API, SSE and grids to Go backend (e.g. docker compose -f docker-compose.dev.yml)
|
||||
nitro: {
|
||||
devProxy: {
|
||||
'/map/api': { target: 'http://backend:3080/map/api', changeOrigin: true },
|
||||
'/map/updates': { target: 'http://backend:3080/map/updates', changeOrigin: true },
|
||||
'/map/grids': { target: 'http://backend:3080/map/grids', changeOrigin: true },
|
||||
},
|
||||
},
|
||||
|
||||
// For cutover: set nitro.preset to 'static' and optionally copy .output/public to ../frontend
|
||||
// nitro: { output: { dir: '../frontend' } },
|
||||
})
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1.0">
|
||||
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
|
||||
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.4.0/dist/leaflet.css"
|
||||
integrity="sha512-puBpdR0798OZvTTbP4A8Ix/l+A4dHDD0DGqYW6RQ+9jxkRFclaxxQb/SJAWZfWAkuyeQUytO7+7N4QKrDh+drA=="
|
||||
crossorigin=""/>
|
||||
<title></title>
|
||||
</head>
|
||||
<body>
|
||||
<noscript>
|
||||
<strong>We're sorry but map-frontend doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
|
||||
</noscript>
|
||||
<div id="app"></div>
|
||||
<!-- built files will be auto injected -->
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user