Refactor frontend components for improved functionality and accessibility
- Consolidated global error handling in app.vue to redirect users to the login page on API authentication failure. - Enhanced MapView component by reintroducing event listeners for selected map and marker updates, improving interactivity. - Updated PasswordInput and various modal components to ensure proper input handling and accessibility compliance. - Refactored MapControls and MapControlsContent to streamline prop management and enhance user experience. - Improved error handling in local storage operations within useMapBookmarks and useRecentLocations composables. - Standardized input elements across forms for consistency in user interaction.
This commit is contained in:
@@ -4,6 +4,16 @@
|
|||||||
</NuxtLayout>
|
</NuxtLayout>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
// Global error handling: on API auth failure, redirect to login
|
||||||
|
const { onApiError } = useMapApi()
|
||||||
|
const { fullUrl } = useAppPaths()
|
||||||
|
const unsubscribe = onApiError(() => {
|
||||||
|
if (import.meta.client) window.location.href = fullUrl('/login')
|
||||||
|
})
|
||||||
|
onUnmounted(() => unsubscribe())
|
||||||
|
</script>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.page-enter-active,
|
.page-enter-active,
|
||||||
.page-leave-active {
|
.page-leave-active {
|
||||||
@@ -14,13 +24,3 @@
|
|||||||
opacity: 0;
|
opacity: 0;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
// Global error handling: on API auth failure, redirect to login
|
|
||||||
const { onApiError } = useMapApi()
|
|
||||||
const { fullUrl } = useAppPaths()
|
|
||||||
const unsubscribe = onApiError(() => {
|
|
||||||
if (import.meta.client) window.location.href = fullUrl('/login')
|
|
||||||
})
|
|
||||||
onUnmounted(() => unsubscribe())
|
|
||||||
</script>
|
|
||||||
|
|||||||
@@ -88,15 +88,10 @@
|
|||||||
</div>
|
</div>
|
||||||
<MapControls
|
<MapControls
|
||||||
:hide-markers="mapLogic.state.hideMarkers.value"
|
:hide-markers="mapLogic.state.hideMarkers.value"
|
||||||
@update:hide-markers="(v) => (mapLogic.state.hideMarkers.value = v)"
|
|
||||||
:selected-map-id="mapLogic.state.selectedMapId.value"
|
:selected-map-id="mapLogic.state.selectedMapId.value"
|
||||||
@update:selected-map-id="(v) => (mapLogic.state.selectedMapId.value = v)"
|
|
||||||
:overlay-map-id="mapLogic.state.overlayMapId.value"
|
:overlay-map-id="mapLogic.state.overlayMapId.value"
|
||||||
@update:overlay-map-id="(v) => (mapLogic.state.overlayMapId.value = v)"
|
|
||||||
:selected-marker-id="mapLogic.state.selectedMarkerId.value"
|
:selected-marker-id="mapLogic.state.selectedMarkerId.value"
|
||||||
@update:selected-marker-id="(v) => (mapLogic.state.selectedMarkerId.value = v)"
|
|
||||||
:selected-player-id="mapLogic.state.selectedPlayerId.value"
|
:selected-player-id="mapLogic.state.selectedPlayerId.value"
|
||||||
@update:selected-player-id="(v) => (mapLogic.state.selectedPlayerId.value = v)"
|
|
||||||
:maps="maps"
|
:maps="maps"
|
||||||
:quest-givers="questGivers"
|
:quest-givers="questGivers"
|
||||||
:players="players"
|
:players="players"
|
||||||
@@ -105,6 +100,11 @@
|
|||||||
:current-map-id="mapLogic.state.mapid.value"
|
:current-map-id="mapLogic.state.mapid.value"
|
||||||
:current-coords="mapLogic.state.displayCoords.value"
|
:current-coords="mapLogic.state.displayCoords.value"
|
||||||
:selected-marker-for-bookmark="selectedMarkerForBookmark"
|
:selected-marker-for-bookmark="selectedMarkerForBookmark"
|
||||||
|
@update:hide-markers="(v) => (mapLogic.state.hideMarkers.value = v)"
|
||||||
|
@update:selected-map-id="(v) => (mapLogic.state.selectedMapId.value = v)"
|
||||||
|
@update:overlay-map-id="(v) => (mapLogic.state.overlayMapId.value = v)"
|
||||||
|
@update:selected-marker-id="(v) => (mapLogic.state.selectedMarkerId.value = v)"
|
||||||
|
@update:selected-player-id="(v) => (mapLogic.state.selectedPlayerId.value = v)"
|
||||||
@zoom-in="mapLogic.zoomIn(leafletMap)"
|
@zoom-in="mapLogic.zoomIn(leafletMap)"
|
||||||
@zoom-out="mapLogic.zoomOutControl(leafletMap)"
|
@zoom-out="mapLogic.zoomOutControl(leafletMap)"
|
||||||
@reset-view="mapLogic.resetView(leafletMap)"
|
@reset-view="mapLogic.resetView(leafletMap)"
|
||||||
|
|||||||
@@ -15,7 +15,7 @@
|
|||||||
:readonly="readonly"
|
:readonly="readonly"
|
||||||
:aria-describedby="ariaDescribedby"
|
:aria-describedby="ariaDescribedby"
|
||||||
@input="emit('update:modelValue', ($event.target as HTMLInputElement).value)"
|
@input="emit('update:modelValue', ($event.target as HTMLInputElement).value)"
|
||||||
/>
|
>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
class="absolute right-2 top-1/2 -translate-y-1/2 btn btn-ghost btn-sm btn-square min-h-9 min-w-9 touch-manipulation"
|
class="absolute right-2 top-1/2 -translate-y-1/2 btn btn-ghost btn-sm btn-square min-h-9 min-w-9 touch-manipulation"
|
||||||
@@ -41,7 +41,14 @@ const props = withDefaults(
|
|||||||
inputId?: string
|
inputId?: string
|
||||||
ariaDescribedby?: string
|
ariaDescribedby?: string
|
||||||
}>(),
|
}>(),
|
||||||
{ required: false, autocomplete: 'off', inputId: undefined, ariaDescribedby: undefined }
|
{
|
||||||
|
required: false,
|
||||||
|
autocomplete: 'off',
|
||||||
|
inputId: undefined,
|
||||||
|
ariaDescribedby: undefined,
|
||||||
|
label: undefined,
|
||||||
|
placeholder: undefined,
|
||||||
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
const emit = defineEmits<{ 'update:modelValue': [value: string] }>()
|
const emit = defineEmits<{ 'update:modelValue': [value: string] }>()
|
||||||
|
|||||||
@@ -6,5 +6,5 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
defineOptions({ inheritAttrs: false })
|
defineOptions({ name: 'AppSkeleton', inheritAttrs: false })
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ const props = withDefaults(
|
|||||||
email?: string
|
email?: string
|
||||||
size?: number
|
size?: number
|
||||||
}>(),
|
}>(),
|
||||||
{ size: 32 }
|
{ size: 32, email: undefined }
|
||||||
)
|
)
|
||||||
|
|
||||||
const gravatarError = ref(false)
|
const gravatarError = ref(false)
|
||||||
|
|||||||
@@ -18,7 +18,7 @@
|
|||||||
class="input input-bordered w-full"
|
class="input input-bordered w-full"
|
||||||
placeholder="Bookmark name"
|
placeholder="Bookmark name"
|
||||||
@keydown.enter.prevent="onSubmit"
|
@keydown.enter.prevent="onSubmit"
|
||||||
/>
|
>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-action">
|
<div class="modal-action">
|
||||||
<form method="dialog" @submit.prevent="onSubmit">
|
<form method="dialog" @submit.prevent="onSubmit">
|
||||||
|
|||||||
@@ -39,13 +39,9 @@
|
|||||||
<MapControlsContent
|
<MapControlsContent
|
||||||
v-model:hide-markers="hideMarkers"
|
v-model:hide-markers="hideMarkers"
|
||||||
:selected-map-id-select="selectedMapIdSelect"
|
:selected-map-id-select="selectedMapIdSelect"
|
||||||
@update:selected-map-id-select="(v) => (selectedMapIdSelect = v)"
|
|
||||||
:overlay-map-id="overlayMapId"
|
:overlay-map-id="overlayMapId"
|
||||||
@update:overlay-map-id="(v) => (overlayMapId = v)"
|
|
||||||
:selected-marker-id-select="selectedMarkerIdSelect"
|
:selected-marker-id-select="selectedMarkerIdSelect"
|
||||||
@update:selected-marker-id-select="(v) => (selectedMarkerIdSelect = v)"
|
|
||||||
:selected-player-id-select="selectedPlayerIdSelect"
|
:selected-player-id-select="selectedPlayerIdSelect"
|
||||||
@update:selected-player-id-select="(v) => (selectedPlayerIdSelect = v)"
|
|
||||||
:maps="maps"
|
:maps="maps"
|
||||||
:quest-givers="questGivers"
|
:quest-givers="questGivers"
|
||||||
:players="players"
|
:players="players"
|
||||||
@@ -54,6 +50,10 @@
|
|||||||
:current-map-id="currentMapId ?? undefined"
|
:current-map-id="currentMapId ?? undefined"
|
||||||
:current-coords="currentCoords"
|
:current-coords="currentCoords"
|
||||||
:selected-marker-for-bookmark="selectedMarkerForBookmark"
|
:selected-marker-for-bookmark="selectedMarkerForBookmark"
|
||||||
|
@update:selected-map-id-select="(v) => (selectedMapIdSelect = v)"
|
||||||
|
@update:overlay-map-id="(v) => (overlayMapId = v)"
|
||||||
|
@update:selected-marker-id-select="(v) => (selectedMarkerIdSelect = v)"
|
||||||
|
@update:selected-player-id-select="(v) => (selectedPlayerIdSelect = v)"
|
||||||
@zoom-in="$emit('zoomIn')"
|
@zoom-in="$emit('zoomIn')"
|
||||||
@zoom-out="$emit('zoomOut')"
|
@zoom-out="$emit('zoomOut')"
|
||||||
@reset-view="$emit('resetView')"
|
@reset-view="$emit('resetView')"
|
||||||
@@ -133,13 +133,9 @@
|
|||||||
<MapControlsContent
|
<MapControlsContent
|
||||||
v-model:hide-markers="hideMarkers"
|
v-model:hide-markers="hideMarkers"
|
||||||
:selected-map-id-select="selectedMapIdSelect"
|
:selected-map-id-select="selectedMapIdSelect"
|
||||||
@update:selected-map-id-select="(v) => (selectedMapIdSelect = v)"
|
|
||||||
:overlay-map-id="overlayMapId"
|
:overlay-map-id="overlayMapId"
|
||||||
@update:overlay-map-id="(v) => (overlayMapId = v)"
|
|
||||||
:selected-marker-id-select="selectedMarkerIdSelect"
|
:selected-marker-id-select="selectedMarkerIdSelect"
|
||||||
@update:selected-marker-id-select="(v) => (selectedMarkerIdSelect = v)"
|
|
||||||
:selected-player-id-select="selectedPlayerIdSelect"
|
:selected-player-id-select="selectedPlayerIdSelect"
|
||||||
@update:selected-player-id-select="(v) => (selectedPlayerIdSelect = v)"
|
|
||||||
:maps="maps"
|
:maps="maps"
|
||||||
:quest-givers="questGivers"
|
:quest-givers="questGivers"
|
||||||
:players="players"
|
:players="players"
|
||||||
@@ -149,6 +145,10 @@
|
|||||||
:current-coords="currentCoords"
|
:current-coords="currentCoords"
|
||||||
:selected-marker-for-bookmark="selectedMarkerForBookmark"
|
:selected-marker-for-bookmark="selectedMarkerForBookmark"
|
||||||
:touch-friendly="true"
|
:touch-friendly="true"
|
||||||
|
@update:selected-map-id-select="(v) => (selectedMapIdSelect = v)"
|
||||||
|
@update:overlay-map-id="(v) => (overlayMapId = v)"
|
||||||
|
@update:selected-marker-id-select="(v) => (selectedMarkerIdSelect = v)"
|
||||||
|
@update:selected-player-id-select="(v) => (selectedPlayerIdSelect = v)"
|
||||||
@zoom-in="$emit('zoomIn')"
|
@zoom-in="$emit('zoomIn')"
|
||||||
@zoom-out="$emit('zoomOut')"
|
@zoom-out="$emit('zoomOut')"
|
||||||
@reset-view="$emit('resetView')"
|
@reset-view="$emit('resetView')"
|
||||||
@@ -191,9 +191,9 @@ interface Player {
|
|||||||
|
|
||||||
const props = withDefaults(
|
const props = withDefaults(
|
||||||
defineProps<{
|
defineProps<{
|
||||||
maps: MapInfo[]
|
maps?: MapInfo[]
|
||||||
questGivers: QuestGiver[]
|
questGivers?: QuestGiver[]
|
||||||
players: Player[]
|
players?: Player[]
|
||||||
markers?: ApiMarker[]
|
markers?: ApiMarker[]
|
||||||
currentZoom?: number
|
currentZoom?: number
|
||||||
currentMapId?: number | null
|
currentMapId?: number | null
|
||||||
|
|||||||
@@ -71,7 +71,7 @@
|
|||||||
Display
|
Display
|
||||||
</h3>
|
</h3>
|
||||||
<label class="label cursor-pointer justify-start gap-2 py-0 hover:bg-base-200/50 rounded-lg px-2 -mx-2 touch-manipulation" :class="touchFriendly ? 'min-h-11' : ''">
|
<label class="label cursor-pointer justify-start gap-2 py-0 hover:bg-base-200/50 rounded-lg px-2 -mx-2 touch-manipulation" :class="touchFriendly ? 'min-h-11' : ''">
|
||||||
<input v-model="hideMarkers" type="checkbox" class="checkbox checkbox-sm" />
|
<input v-model="hideMarkers" type="checkbox" class="checkbox checkbox-sm" >
|
||||||
<span>Hide markers</span>
|
<span>Hide markers</span>
|
||||||
</label>
|
</label>
|
||||||
</section>
|
</section>
|
||||||
|
|||||||
@@ -11,8 +11,8 @@
|
|||||||
<h3 id="coord-set-modal-title" class="font-bold text-lg">Rewrite tile coords</h3>
|
<h3 id="coord-set-modal-title" class="font-bold text-lg">Rewrite tile coords</h3>
|
||||||
<p class="py-2">From ({{ coordSetFrom.x }}, {{ coordSetFrom.y }}) to:</p>
|
<p class="py-2">From ({{ coordSetFrom.x }}, {{ coordSetFrom.y }}) to:</p>
|
||||||
<div class="flex gap-2">
|
<div class="flex gap-2">
|
||||||
<input ref="firstInputRef" v-model.number="localTo.x" type="number" class="input flex-1" placeholder="X" />
|
<input ref="firstInputRef" v-model.number="localTo.x" type="number" class="input flex-1" placeholder="X" >
|
||||||
<input v-model.number="localTo.y" type="number" class="input flex-1" placeholder="Y" />
|
<input v-model.number="localTo.y" type="number" class="input flex-1" placeholder="Y" >
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-action">
|
<div class="modal-action">
|
||||||
<form method="dialog" @submit.prevent="onSubmit">
|
<form method="dialog" @submit.prevent="onSubmit">
|
||||||
|
|||||||
@@ -22,7 +22,7 @@
|
|||||||
@keydown.enter="onEnter"
|
@keydown.enter="onEnter"
|
||||||
@keydown.down.prevent="moveHighlight(1)"
|
@keydown.down.prevent="moveHighlight(1)"
|
||||||
@keydown.up.prevent="moveHighlight(-1)"
|
@keydown.up.prevent="moveHighlight(-1)"
|
||||||
/>
|
>
|
||||||
<button
|
<button
|
||||||
v-if="query"
|
v-if="query"
|
||||||
type="button"
|
type="button"
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import { describe, it, expect, vi, beforeEach } from 'vitest'
|
import { describe, it, expect, vi, beforeEach } from 'vitest'
|
||||||
|
|
||||||
|
import { useAppPaths } from '../useAppPaths'
|
||||||
|
|
||||||
const useRuntimeConfigMock = vi.fn()
|
const useRuntimeConfigMock = vi.fn()
|
||||||
vi.stubGlobal('useRuntimeConfig', useRuntimeConfigMock)
|
vi.stubGlobal('useRuntimeConfig', useRuntimeConfigMock)
|
||||||
|
|
||||||
import { useAppPaths } from '../useAppPaths'
|
|
||||||
|
|
||||||
describe('useAppPaths with default base /', () => {
|
describe('useAppPaths with default base /', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
useRuntimeConfigMock.mockReturnValue({ app: { baseURL: '/' } })
|
useRuntimeConfigMock.mockReturnValue({ app: { baseURL: '/' } })
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'
|
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'
|
||||||
|
|
||||||
|
import { useMapApi } from '../useMapApi'
|
||||||
|
|
||||||
vi.stubGlobal('useRuntimeConfig', () => ({
|
vi.stubGlobal('useRuntimeConfig', () => ({
|
||||||
app: { baseURL: '/' },
|
app: { baseURL: '/' },
|
||||||
public: { apiBase: '/map/api' },
|
public: { apiBase: '/map/api' },
|
||||||
}))
|
}))
|
||||||
|
|
||||||
import { useMapApi } from '../useMapApi'
|
|
||||||
|
|
||||||
function mockFetch(status: number, body: unknown, contentType = 'application/json') {
|
function mockFetch(status: number, body: unknown, contentType = 'application/json') {
|
||||||
return vi.fn().mockResolvedValue({
|
return vi.fn().mockResolvedValue({
|
||||||
ok: status >= 200 && status < 300,
|
ok: status >= 200 && status < 300,
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
import { describe, it, expect, vi, beforeEach } from 'vitest'
|
import { describe, it, expect, vi, beforeEach } from 'vitest'
|
||||||
import { ref } from 'vue'
|
import { ref } from 'vue'
|
||||||
|
|
||||||
|
import { useMapBookmarks } from '../useMapBookmarks'
|
||||||
|
|
||||||
const stateByKey: Record<string, ReturnType<typeof ref>> = {}
|
const stateByKey: Record<string, ReturnType<typeof ref>> = {}
|
||||||
const useStateMock = vi.fn((key: string, init: () => unknown) => {
|
const useStateMock = vi.fn((key: string, init: () => unknown) => {
|
||||||
if (!stateByKey[key]) {
|
if (!stateByKey[key]) {
|
||||||
@@ -18,15 +20,13 @@ const localStorageMock = {
|
|||||||
storage[key] = value
|
storage[key] = value
|
||||||
}),
|
}),
|
||||||
clear: vi.fn(() => {
|
clear: vi.fn(() => {
|
||||||
for (const k of Object.keys(storage)) delete storage[k]
|
delete storage['hnh-map-bookmarks']
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
vi.stubGlobal('localStorage', localStorageMock)
|
vi.stubGlobal('localStorage', localStorageMock)
|
||||||
vi.stubGlobal('import.meta.server', false)
|
vi.stubGlobal('import.meta.server', false)
|
||||||
vi.stubGlobal('import.meta.client', true)
|
vi.stubGlobal('import.meta.client', true)
|
||||||
|
|
||||||
import { useMapBookmarks } from '../useMapBookmarks'
|
|
||||||
|
|
||||||
describe('useMapBookmarks', () => {
|
describe('useMapBookmarks', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
storage['hnh-map-bookmarks'] = '[]'
|
storage['hnh-map-bookmarks'] = '[]'
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
import { describe, it, expect, vi, beforeEach } from 'vitest'
|
import { describe, it, expect, vi, beforeEach } from 'vitest'
|
||||||
import { ref, reactive } from 'vue'
|
import { ref, reactive } from 'vue'
|
||||||
|
import type { Map } from 'leaflet'
|
||||||
|
|
||||||
|
import { useMapLogic } from '../useMapLogic'
|
||||||
|
|
||||||
vi.stubGlobal('ref', ref)
|
vi.stubGlobal('ref', ref)
|
||||||
vi.stubGlobal('reactive', reactive)
|
vi.stubGlobal('reactive', reactive)
|
||||||
|
|
||||||
import { useMapLogic } from '../useMapLogic'
|
|
||||||
|
|
||||||
describe('useMapLogic', () => {
|
describe('useMapLogic', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
vi.clearAllMocks()
|
vi.clearAllMocks()
|
||||||
@@ -27,7 +28,7 @@ describe('useMapLogic', () => {
|
|||||||
it('zoomIn calls map.zoomIn', () => {
|
it('zoomIn calls map.zoomIn', () => {
|
||||||
const { zoomIn } = useMapLogic()
|
const { zoomIn } = useMapLogic()
|
||||||
const mockMap = { zoomIn: vi.fn() }
|
const mockMap = { zoomIn: vi.fn() }
|
||||||
zoomIn(mockMap as unknown as import('leaflet').Map)
|
zoomIn(mockMap as unknown as Map)
|
||||||
expect(mockMap.zoomIn).toHaveBeenCalled()
|
expect(mockMap.zoomIn).toHaveBeenCalled()
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -39,7 +40,7 @@ describe('useMapLogic', () => {
|
|||||||
it('zoomOutControl calls map.zoomOut', () => {
|
it('zoomOutControl calls map.zoomOut', () => {
|
||||||
const { zoomOutControl } = useMapLogic()
|
const { zoomOutControl } = useMapLogic()
|
||||||
const mockMap = { zoomOut: vi.fn() }
|
const mockMap = { zoomOut: vi.fn() }
|
||||||
zoomOutControl(mockMap as unknown as import('leaflet').Map)
|
zoomOutControl(mockMap as unknown as Map)
|
||||||
expect(mockMap.zoomOut).toHaveBeenCalled()
|
expect(mockMap.zoomOut).toHaveBeenCalled()
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -47,7 +48,7 @@ describe('useMapLogic', () => {
|
|||||||
const { state, resetView } = useMapLogic()
|
const { state, resetView } = useMapLogic()
|
||||||
state.trackingCharacterId.value = 42
|
state.trackingCharacterId.value = 42
|
||||||
const mockMap = { setView: vi.fn() }
|
const mockMap = { setView: vi.fn() }
|
||||||
resetView(mockMap as unknown as import('leaflet').Map)
|
resetView(mockMap as unknown as Map)
|
||||||
expect(state.trackingCharacterId.value).toBe(-1)
|
expect(state.trackingCharacterId.value).toBe(-1)
|
||||||
expect(mockMap.setView).toHaveBeenCalledWith([0, 0], 1, { animate: false })
|
expect(mockMap.setView).toHaveBeenCalledWith([0, 0], 1, { animate: false })
|
||||||
})
|
})
|
||||||
@@ -59,7 +60,7 @@ describe('useMapLogic', () => {
|
|||||||
getCenter: vi.fn(() => ({ lat: 0, lng: 0 })),
|
getCenter: vi.fn(() => ({ lat: 0, lng: 0 })),
|
||||||
getZoom: vi.fn(() => 3),
|
getZoom: vi.fn(() => 3),
|
||||||
}
|
}
|
||||||
updateDisplayCoords(mockMap as unknown as import('leaflet').Map)
|
updateDisplayCoords(mockMap as unknown as Map)
|
||||||
expect(state.displayCoords.value).toEqual({ x: 5, y: 3, z: 3 })
|
expect(state.displayCoords.value).toEqual({ x: 5, y: 3, z: 3 })
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -72,7 +73,7 @@ describe('useMapLogic', () => {
|
|||||||
it('toLatLng calls map.unproject', () => {
|
it('toLatLng calls map.unproject', () => {
|
||||||
const { toLatLng } = useMapLogic()
|
const { toLatLng } = useMapLogic()
|
||||||
const mockMap = { unproject: vi.fn(() => ({ lat: 1, lng: 2 })) }
|
const mockMap = { unproject: vi.fn(() => ({ lat: 1, lng: 2 })) }
|
||||||
const result = toLatLng(mockMap as unknown as import('leaflet').Map, 100, 200)
|
const result = toLatLng(mockMap as unknown as Map, 100, 200)
|
||||||
expect(mockMap.unproject).toHaveBeenCalledWith([100, 200], 6)
|
expect(mockMap.unproject).toHaveBeenCalledWith([100, 200], 6)
|
||||||
expect(result).toEqual({ lat: 1, lng: 2 })
|
expect(result).toEqual({ lat: 1, lng: 2 })
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
import { describe, it, expect, vi, beforeEach } from 'vitest'
|
import { describe, it, expect, vi, beforeEach } from 'vitest'
|
||||||
import { ref } from 'vue'
|
import { ref } from 'vue'
|
||||||
|
|
||||||
|
import { useToast } from '../useToast'
|
||||||
|
|
||||||
const stateByKey: Record<string, ReturnType<typeof ref>> = {}
|
const stateByKey: Record<string, ReturnType<typeof ref>> = {}
|
||||||
const useStateMock = vi.fn((key: string, init: () => unknown) => {
|
const useStateMock = vi.fn((key: string, init: () => unknown) => {
|
||||||
if (!stateByKey[key]) {
|
if (!stateByKey[key]) {
|
||||||
@@ -10,8 +12,6 @@ const useStateMock = vi.fn((key: string, init: () => unknown) => {
|
|||||||
})
|
})
|
||||||
vi.stubGlobal('useState', useStateMock)
|
vi.stubGlobal('useState', useStateMock)
|
||||||
|
|
||||||
import { useToast } from '../useToast'
|
|
||||||
|
|
||||||
describe('useToast', () => {
|
describe('useToast', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
stateByKey['hnh-map-toasts'] = ref([])
|
stateByKey['hnh-map-toasts'] = ref([])
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ function saveBookmarks(bookmarks: MapBookmark[]) {
|
|||||||
if (import.meta.server) return
|
if (import.meta.server) return
|
||||||
try {
|
try {
|
||||||
localStorage.setItem(STORAGE_KEY, JSON.stringify(bookmarks.slice(0, MAX_BOOKMARKS)))
|
localStorage.setItem(STORAGE_KEY, JSON.stringify(bookmarks.slice(0, MAX_BOOKMARKS)))
|
||||||
} catch (_) {}
|
} catch { /* ignore */ }
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useMapBookmarks() {
|
export function useMapBookmarks() {
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import {
|
|||||||
import type { SmartTileLayer } from '~/lib/SmartTileLayer'
|
import type { SmartTileLayer } from '~/lib/SmartTileLayer'
|
||||||
import type { Marker as ApiMarker, Character as ApiCharacter } from '~/types/api'
|
import type { Marker as ApiMarker, Character as ApiCharacter } from '~/types/api'
|
||||||
|
|
||||||
|
type LeafletModule = L
|
||||||
type SmartTileLayerInstance = InstanceType<typeof SmartTileLayer>
|
type SmartTileLayerInstance = InstanceType<typeof SmartTileLayer>
|
||||||
|
|
||||||
function escapeHtml(s: string): string {
|
function escapeHtml(s: string): string {
|
||||||
@@ -25,7 +26,7 @@ function escapeHtml(s: string): string {
|
|||||||
|
|
||||||
export interface MapLayersOptions {
|
export interface MapLayersOptions {
|
||||||
/** Leaflet API (from dynamic import). Required for creating markers and characters without static leaflet import. */
|
/** Leaflet API (from dynamic import). Required for creating markers and characters without static leaflet import. */
|
||||||
L: typeof import('leaflet')
|
L: LeafletModule
|
||||||
map: L.Map
|
map: L.Map
|
||||||
markerLayer: L.LayerGroup
|
markerLayer: L.LayerGroup
|
||||||
layer: SmartTileLayerInstance
|
layer: SmartTileLayerInstance
|
||||||
@@ -33,7 +34,7 @@ export interface MapLayersOptions {
|
|||||||
getCurrentMapId: () => number
|
getCurrentMapId: () => number
|
||||||
setCurrentMapId: (id: number) => void
|
setCurrentMapId: (id: number) => void
|
||||||
setSelectedMapId: (id: number) => void
|
setSelectedMapId: (id: number) => void
|
||||||
getAuths: () => string[]
|
getAuths?: () => string[]
|
||||||
getTrackingCharacterId: () => number
|
getTrackingCharacterId: () => number
|
||||||
setTrackingCharacterId: (id: number) => void
|
setTrackingCharacterId: (id: number) => void
|
||||||
onMarkerContextMenu: (clientX: number, clientY: number, id: number, name: string) => void
|
onMarkerContextMenu: (clientX: number, clientY: number, id: number, name: string) => void
|
||||||
@@ -69,7 +70,7 @@ export function createMapLayers(options: MapLayersOptions): MapLayersManager {
|
|||||||
getCurrentMapId,
|
getCurrentMapId,
|
||||||
setCurrentMapId,
|
setCurrentMapId,
|
||||||
setSelectedMapId,
|
setSelectedMapId,
|
||||||
getAuths,
|
getAuths: _getAuths,
|
||||||
getTrackingCharacterId,
|
getTrackingCharacterId,
|
||||||
setTrackingCharacterId,
|
setTrackingCharacterId,
|
||||||
onMarkerContextMenu,
|
onMarkerContextMenu,
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ function saveRecent(list: RecentLocation[]) {
|
|||||||
if (import.meta.server) return
|
if (import.meta.server) return
|
||||||
try {
|
try {
|
||||||
localStorage.setItem(STORAGE_KEY, JSON.stringify(list.slice(0, MAX_RECENT)))
|
localStorage.setItem(STORAGE_KEY, JSON.stringify(list.slice(0, MAX_RECENT)))
|
||||||
} catch (_) {}
|
} catch { /* ignore */ }
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useRecentLocations() {
|
export function useRecentLocations() {
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
type="checkbox"
|
type="checkbox"
|
||||||
class="drawer-toggle"
|
class="drawer-toggle"
|
||||||
@change="onDrawerChange"
|
@change="onDrawerChange"
|
||||||
/>
|
>
|
||||||
<div class="drawer-content flex flex-col h-screen overflow-hidden">
|
<div class="drawer-content flex flex-col h-screen overflow-hidden">
|
||||||
<header class="navbar relative z-[1100] bg-base-100/80 backdrop-blur-xl border-b border-base-300/50 px-4 gap-2 shrink-0">
|
<header class="navbar relative z-[1100] bg-base-100/80 backdrop-blur-xl border-b border-base-300/50 px-4 gap-2 shrink-0">
|
||||||
<NuxtLink to="/" class="flex items-center gap-2 text-lg font-semibold hover:opacity-80 transition-all duration-200">
|
<NuxtLink to="/" class="flex items-center gap-2 text-lg font-semibold hover:opacity-80 transition-all duration-200">
|
||||||
@@ -87,7 +87,7 @@
|
|||||||
class="toggle toggle-sm toggle-primary shrink-0"
|
class="toggle toggle-sm toggle-primary shrink-0"
|
||||||
:checked="dark"
|
:checked="dark"
|
||||||
@change="onThemeToggle"
|
@change="onThemeToggle"
|
||||||
/>
|
>
|
||||||
</label>
|
</label>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
@@ -177,7 +177,7 @@
|
|||||||
class="toggle toggle-sm toggle-primary shrink-0"
|
class="toggle toggle-sm toggle-primary shrink-0"
|
||||||
:checked="dark"
|
:checked="dark"
|
||||||
@change="onThemeToggle"
|
@change="onThemeToggle"
|
||||||
/>
|
>
|
||||||
</label>
|
</label>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
@@ -296,7 +296,7 @@ async function loadConfig(loadToken: number) {
|
|||||||
const config = await useMapApi().getConfig()
|
const config = await useMapApi().getConfig()
|
||||||
if (loadToken !== loadId) return
|
if (loadToken !== loadId) return
|
||||||
if (config?.title) title.value = config.title
|
if (config?.title) title.value = config.title
|
||||||
} catch (_) {}
|
} catch { /* ignore */ }
|
||||||
}
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import type L from 'leaflet'
|
|||||||
import { getColorForCharacterId, type CharacterColors } from '~/lib/characterColors'
|
import { getColorForCharacterId, type CharacterColors } from '~/lib/characterColors'
|
||||||
import { HnHMaxZoom } from '~/lib/LeafletCustomTypes'
|
import { HnHMaxZoom } from '~/lib/LeafletCustomTypes'
|
||||||
|
|
||||||
export type LeafletApi = typeof import('leaflet')
|
export type LeafletApi = L
|
||||||
|
|
||||||
function buildCharacterIconUrl(colors: CharacterColors): string {
|
function buildCharacterIconUrl(colors: CharacterColors): string {
|
||||||
const svg =
|
const svg =
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ export interface MarkerIconOptions {
|
|||||||
fallbackIconUrl?: string
|
fallbackIconUrl?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export type LeafletApi = typeof import('leaflet')
|
export type LeafletApi = L
|
||||||
|
|
||||||
export function createMarker(
|
export function createMarker(
|
||||||
data: MarkerData,
|
data: MarkerData,
|
||||||
|
|||||||
@@ -39,7 +39,10 @@ export function uniqueListUpdate<T extends Identifiable>(
|
|||||||
if (addCallback) {
|
if (addCallback) {
|
||||||
elementsToAdd.forEach((it) => addCallback(it))
|
elementsToAdd.forEach((it) => addCallback(it))
|
||||||
}
|
}
|
||||||
elementsToRemove.forEach((it) => delete list.elements[String(it.id)])
|
const toRemove = new Set(elementsToRemove.map((it) => String(it.id)))
|
||||||
|
list.elements = Object.fromEntries(
|
||||||
|
Object.entries(list.elements).filter(([id]) => !toRemove.has(id))
|
||||||
|
) as Record<string, T>
|
||||||
elementsToAdd.forEach((it) => (list.elements[String(it.id)] = it))
|
elementsToAdd.forEach((it) => (list.elements[String(it.id)] = it))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,9 @@
|
|||||||
import { describe, it, expect, vi, beforeEach } from 'vitest'
|
import { describe, it, expect, vi, beforeEach } from 'vitest'
|
||||||
|
|
||||||
|
import type L from 'leaflet'
|
||||||
|
import type { Map, LayerGroup } from 'leaflet'
|
||||||
|
import { createCharacter, type CharacterData, type CharacterMapViewRef } from '../Character'
|
||||||
|
|
||||||
vi.mock('leaflet', () => {
|
vi.mock('leaflet', () => {
|
||||||
const markerMock = {
|
const markerMock = {
|
||||||
on: vi.fn().mockReturnThis(),
|
on: vi.fn().mockReturnThis(),
|
||||||
@@ -21,9 +25,6 @@ vi.mock('~/lib/LeafletCustomTypes', () => ({
|
|||||||
HnHMaxZoom: 6,
|
HnHMaxZoom: 6,
|
||||||
}))
|
}))
|
||||||
|
|
||||||
import type L from 'leaflet'
|
|
||||||
import { createCharacter, type CharacterData, type CharacterMapViewRef } from '../Character'
|
|
||||||
|
|
||||||
function getL(): L {
|
function getL(): L {
|
||||||
return require('leaflet').default
|
return require('leaflet').default
|
||||||
}
|
}
|
||||||
@@ -44,12 +45,12 @@ function makeMapViewRef(mapid = 1): CharacterMapViewRef {
|
|||||||
map: {
|
map: {
|
||||||
unproject: vi.fn(() => ({ lat: 0, lng: 0 })),
|
unproject: vi.fn(() => ({ lat: 0, lng: 0 })),
|
||||||
removeLayer: vi.fn(),
|
removeLayer: vi.fn(),
|
||||||
} as unknown as import('leaflet').Map,
|
} as unknown as Map,
|
||||||
mapid,
|
mapid,
|
||||||
markerLayer: {
|
markerLayer: {
|
||||||
removeLayer: vi.fn(),
|
removeLayer: vi.fn(),
|
||||||
addLayer: vi.fn(),
|
addLayer: vi.fn(),
|
||||||
} as unknown as import('leaflet').LayerGroup,
|
} as unknown as LayerGroup,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,9 @@
|
|||||||
import { describe, it, expect, vi, beforeEach } from 'vitest'
|
import { describe, it, expect, vi, beforeEach } from 'vitest'
|
||||||
|
|
||||||
|
import L from 'leaflet'
|
||||||
|
import type { Map, LayerGroup } from 'leaflet'
|
||||||
|
import { createMarker, type MarkerData, type MapViewRef } from '../Marker'
|
||||||
|
|
||||||
vi.mock('leaflet', () => {
|
vi.mock('leaflet', () => {
|
||||||
const markerMock = {
|
const markerMock = {
|
||||||
on: vi.fn().mockReturnThis(),
|
on: vi.fn().mockReturnThis(),
|
||||||
@@ -14,24 +18,19 @@ vi.mock('leaflet', () => {
|
|||||||
return {
|
return {
|
||||||
default: {
|
default: {
|
||||||
marker: vi.fn(() => markerMock),
|
marker: vi.fn(() => markerMock),
|
||||||
Icon: class {},
|
Icon: vi.fn(),
|
||||||
},
|
},
|
||||||
marker: vi.fn(() => markerMock),
|
marker: vi.fn(() => markerMock),
|
||||||
Icon: class {},
|
Icon: vi.fn(),
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
vi.mock('~/lib/LeafletCustomTypes', () => ({
|
vi.mock('~/lib/LeafletCustomTypes', () => ({
|
||||||
HnHMaxZoom: 6,
|
HnHMaxZoom: 6,
|
||||||
TileSize: 100,
|
TileSize: 100,
|
||||||
ImageIcon: class {
|
ImageIcon: vi.fn(),
|
||||||
constructor(_opts: Record<string, unknown>) {}
|
|
||||||
},
|
|
||||||
}))
|
}))
|
||||||
|
|
||||||
import L from 'leaflet'
|
|
||||||
import { createMarker, type MarkerData, type MapViewRef } from '../Marker'
|
|
||||||
|
|
||||||
function makeMarkerData(overrides: Partial<MarkerData> = {}): MarkerData {
|
function makeMarkerData(overrides: Partial<MarkerData> = {}): MarkerData {
|
||||||
return {
|
return {
|
||||||
id: 1,
|
id: 1,
|
||||||
@@ -48,12 +47,12 @@ function makeMapViewRef(): MapViewRef {
|
|||||||
return {
|
return {
|
||||||
map: {
|
map: {
|
||||||
unproject: vi.fn(() => ({ lat: 0, lng: 0 })),
|
unproject: vi.fn(() => ({ lat: 0, lng: 0 })),
|
||||||
} as unknown as import('leaflet').Map,
|
} as unknown as Map,
|
||||||
mapid: 1,
|
mapid: 1,
|
||||||
markerLayer: {
|
markerLayer: {
|
||||||
removeLayer: vi.fn(),
|
removeLayer: vi.fn(),
|
||||||
addLayer: vi.fn(),
|
addLayer: vi.fn(),
|
||||||
} as unknown as import('leaflet').LayerGroup,
|
} as unknown as LayerGroup,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -58,7 +58,7 @@
|
|||||||
placeholder="Search users…"
|
placeholder="Search users…"
|
||||||
class="input input-sm input-bordered w-full min-h-11 touch-manipulation"
|
class="input input-sm input-bordered w-full min-h-11 touch-manipulation"
|
||||||
aria-label="Search users"
|
aria-label="Search users"
|
||||||
/>
|
>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-col gap-2 max-h-[60vh] overflow-y-auto">
|
<div class="flex flex-col gap-2 max-h-[60vh] overflow-y-auto">
|
||||||
<div
|
<div
|
||||||
@@ -101,7 +101,7 @@
|
|||||||
placeholder="Search maps…"
|
placeholder="Search maps…"
|
||||||
class="input input-sm input-bordered w-full min-h-11 touch-manipulation"
|
class="input input-sm input-bordered w-full min-h-11 touch-manipulation"
|
||||||
aria-label="Search maps"
|
aria-label="Search maps"
|
||||||
/>
|
>
|
||||||
</div>
|
</div>
|
||||||
<div class="overflow-x-auto max-h-[60vh] overflow-y-auto">
|
<div class="overflow-x-auto max-h-[60vh] overflow-y-auto">
|
||||||
<table class="table table-sm table-zebra min-w-[32rem]">
|
<table class="table table-sm table-zebra min-w-[32rem]">
|
||||||
@@ -121,7 +121,7 @@
|
|||||||
</th>
|
</th>
|
||||||
<th scope="col">Hidden</th>
|
<th scope="col">Hidden</th>
|
||||||
<th scope="col">Priority</th>
|
<th scope="col">Priority</th>
|
||||||
<th scope="col" class="text-right"></th>
|
<th scope="col" class="text-right"/>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
@@ -159,7 +159,7 @@
|
|||||||
v-model="settings.prefix"
|
v-model="settings.prefix"
|
||||||
type="text"
|
type="text"
|
||||||
class="input input-sm w-full min-h-11 touch-manipulation"
|
class="input input-sm w-full min-h-11 touch-manipulation"
|
||||||
/>
|
>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
<fieldset class="fieldset w-full max-w-xs">
|
<fieldset class="fieldset w-full max-w-xs">
|
||||||
<label class="label" for="admin-settings-title">Title</label>
|
<label class="label" for="admin-settings-title">Title</label>
|
||||||
@@ -168,7 +168,7 @@
|
|||||||
v-model="settings.title"
|
v-model="settings.title"
|
||||||
type="text"
|
type="text"
|
||||||
class="input input-sm w-full min-h-11 touch-manipulation"
|
class="input input-sm w-full min-h-11 touch-manipulation"
|
||||||
/>
|
>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
<fieldset class="fieldset">
|
<fieldset class="fieldset">
|
||||||
<label class="label gap-2 cursor-pointer justify-start min-h-11 touch-manipulation" for="admin-settings-default-hide">
|
<label class="label gap-2 cursor-pointer justify-start min-h-11 touch-manipulation" for="admin-settings-default-hide">
|
||||||
@@ -177,7 +177,7 @@
|
|||||||
v-model="settings.defaultHide"
|
v-model="settings.defaultHide"
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
class="checkbox checkbox-sm"
|
class="checkbox checkbox-sm"
|
||||||
/>
|
>
|
||||||
Default hide new maps
|
Default hide new maps
|
||||||
</label>
|
</label>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
@@ -211,7 +211,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="flex flex-col gap-2">
|
<div class="flex flex-col gap-2">
|
||||||
<div class="flex flex-wrap items-center gap-2">
|
<div class="flex flex-wrap items-center gap-2">
|
||||||
<input ref="mergeFileRef" type="file" accept=".zip" class="hidden" @change="onMergeFile" />
|
<input ref="mergeFileRef" type="file" accept=".zip" class="hidden" @change="onMergeFile" >
|
||||||
<button type="button" class="btn btn-sm min-h-11 touch-manipulation" @click="mergeFileRef?.click()">
|
<button type="button" class="btn btn-sm min-h-11 touch-manipulation" @click="mergeFileRef?.click()">
|
||||||
Choose merge file
|
Choose merge file
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -2,20 +2,20 @@
|
|||||||
<div class="container mx-auto p-4 max-w-2xl min-w-0">
|
<div class="container mx-auto p-4 max-w-2xl min-w-0">
|
||||||
<h1 class="text-2xl font-bold mb-6">Edit map {{ id }}</h1>
|
<h1 class="text-2xl font-bold mb-6">Edit map {{ id }}</h1>
|
||||||
|
|
||||||
<form v-if="map" @submit.prevent="submit" class="flex flex-col gap-4">
|
<form v-if="map" class="flex flex-col gap-4" @submit.prevent="submit">
|
||||||
<fieldset class="fieldset">
|
<fieldset class="fieldset">
|
||||||
<label class="label" for="name">Name</label>
|
<label class="label" for="name">Name</label>
|
||||||
<input id="name" v-model="form.name" type="text" class="input min-h-11 touch-manipulation" required />
|
<input id="name" v-model="form.name" type="text" class="input min-h-11 touch-manipulation" required >
|
||||||
</fieldset>
|
</fieldset>
|
||||||
<fieldset class="fieldset">
|
<fieldset class="fieldset">
|
||||||
<label class="label cursor-pointer gap-2">
|
<label class="label cursor-pointer gap-2">
|
||||||
<input v-model="form.hidden" type="checkbox" class="checkbox" />
|
<input v-model="form.hidden" type="checkbox" class="checkbox" >
|
||||||
<span>Hidden</span>
|
<span>Hidden</span>
|
||||||
</label>
|
</label>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
<fieldset class="fieldset">
|
<fieldset class="fieldset">
|
||||||
<label class="label cursor-pointer gap-2">
|
<label class="label cursor-pointer gap-2">
|
||||||
<input v-model="form.priority" type="checkbox" class="checkbox" />
|
<input v-model="form.priority" type="checkbox" class="checkbox" >
|
||||||
<span>Priority</span>
|
<span>Priority</span>
|
||||||
</label>
|
</label>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
<div class="container mx-auto p-4 max-w-2xl min-w-0">
|
<div class="container mx-auto p-4 max-w-2xl min-w-0">
|
||||||
<h1 class="text-2xl font-bold mb-6">{{ isNew ? 'New user' : `Edit ${username}` }}</h1>
|
<h1 class="text-2xl font-bold mb-6">{{ isNew ? 'New user' : `Edit ${username}` }}</h1>
|
||||||
|
|
||||||
<form @submit.prevent="submit" class="flex flex-col gap-4">
|
<form class="flex flex-col gap-4" @submit.prevent="submit">
|
||||||
<fieldset class="fieldset">
|
<fieldset class="fieldset">
|
||||||
<label class="label" for="user">Username</label>
|
<label class="label" for="user">Username</label>
|
||||||
<input
|
<input
|
||||||
@@ -12,7 +12,7 @@
|
|||||||
class="input min-h-11 touch-manipulation"
|
class="input min-h-11 touch-manipulation"
|
||||||
required
|
required
|
||||||
:readonly="!isNew"
|
:readonly="!isNew"
|
||||||
/>
|
>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
<p id="admin-user-password-hint" class="text-sm text-base-content/60 mb-1">Leave blank to keep current password.</p>
|
<p id="admin-user-password-hint" class="text-sm text-base-content/60 mb-1">Leave blank to keep current password.</p>
|
||||||
<PasswordInput
|
<PasswordInput
|
||||||
@@ -25,7 +25,7 @@
|
|||||||
<label class="label">Auths</label>
|
<label class="label">Auths</label>
|
||||||
<div class="flex flex-wrap gap-2">
|
<div class="flex flex-wrap gap-2">
|
||||||
<label v-for="a of authOptions" :key="a" class="label cursor-pointer gap-2" :for="`auth-${a}`">
|
<label v-for="a of authOptions" :key="a" class="label cursor-pointer gap-2" :for="`auth-${a}`">
|
||||||
<input :id="`auth-${a}`" v-model="form.auths" type="checkbox" :value="a" class="checkbox checkbox-sm" />
|
<input :id="`auth-${a}`" v-model="form.auths" type="checkbox" :value="a" class="checkbox checkbox-sm" >
|
||||||
<span>{{ a }}</span>
|
<span>{{ a }}</span>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -14,18 +14,18 @@
|
|||||||
</a>
|
</a>
|
||||||
<div class="divider text-sm">or</div>
|
<div class="divider text-sm">or</div>
|
||||||
</div>
|
</div>
|
||||||
<form @submit.prevent="submit" class="flex flex-col gap-4">
|
<form class="flex flex-col gap-4" @submit.prevent="submit">
|
||||||
<fieldset class="fieldset">
|
<fieldset class="fieldset">
|
||||||
<label class="label" for="user">User</label>
|
<label class="label" for="user">User</label>
|
||||||
<input
|
<input
|
||||||
ref="userInputRef"
|
|
||||||
id="user"
|
id="user"
|
||||||
|
ref="userInputRef"
|
||||||
v-model="user"
|
v-model="user"
|
||||||
type="text"
|
type="text"
|
||||||
class="input min-h-11 touch-manipulation"
|
class="input min-h-11 touch-manipulation"
|
||||||
required
|
required
|
||||||
autocomplete="username"
|
autocomplete="username"
|
||||||
/>
|
>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
<PasswordInput
|
<PasswordInput
|
||||||
v-model="pass"
|
v-model="pass"
|
||||||
|
|||||||
@@ -115,7 +115,7 @@
|
|||||||
<icons-icon-settings />
|
<icons-icon-settings />
|
||||||
Change password
|
Change password
|
||||||
</h2>
|
</h2>
|
||||||
<form @submit.prevent="changePass" class="flex flex-col gap-2">
|
<form class="flex flex-col gap-2" @submit.prevent="changePass">
|
||||||
<PasswordInput
|
<PasswordInput
|
||||||
v-model="newPass"
|
v-model="newPass"
|
||||||
placeholder="New password"
|
placeholder="New password"
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
This is the first run. Create the administrator account using the bootstrap password
|
This is the first run. Create the administrator account using the bootstrap password
|
||||||
from the server configuration (e.g. <code class="text-xs">HNHMAP_BOOTSTRAP_PASSWORD</code>).
|
from the server configuration (e.g. <code class="text-xs">HNHMAP_BOOTSTRAP_PASSWORD</code>).
|
||||||
</p>
|
</p>
|
||||||
<form @submit.prevent="submit" class="flex flex-col gap-4">
|
<form class="flex flex-col gap-4" @submit.prevent="submit">
|
||||||
<PasswordInput
|
<PasswordInput
|
||||||
v-model="pass"
|
v-model="pass"
|
||||||
label="Bootstrap password"
|
label="Bootstrap password"
|
||||||
|
|||||||
Reference in New Issue
Block a user