Refactored tag list in their own component
This commit is contained in:
@@ -28,6 +28,8 @@
|
|||||||
--blue-700: #0284c7;
|
--blue-700: #0284c7;
|
||||||
--orange-500: #f97316;
|
--orange-500: #f97316;
|
||||||
--orange-700: #c2410c;
|
--orange-700: #c2410c;
|
||||||
|
--purple-500: #8b5cf6;
|
||||||
|
--purple-700: #6d28d9;
|
||||||
}
|
}
|
||||||
|
|
||||||
html {
|
html {
|
||||||
|
|||||||
@@ -44,6 +44,8 @@ const inertiaDeceleration = 2000
|
|||||||
const maxBoundsPadding = 10
|
const maxBoundsPadding = 10
|
||||||
const maxBoundsViscosity = .75
|
const maxBoundsViscosity = .75
|
||||||
|
|
||||||
|
const mapEl = document.getElementById('world')
|
||||||
|
|
||||||
// Initializes the map
|
// Initializes the map
|
||||||
const map = L.map('world', {
|
const map = L.map('world', {
|
||||||
crs: L.CRS.Simple,
|
crs: L.CRS.Simple,
|
||||||
@@ -296,6 +298,10 @@ let customMarkersList = []
|
|||||||
|
|
||||||
function saveCustomMarkers() {
|
function saveCustomMarkers() {
|
||||||
localStorage.setItem('custom-markers', JSON.stringify(customMarkersList))
|
localStorage.setItem('custom-markers', JSON.stringify(customMarkersList))
|
||||||
|
|
||||||
|
const refreshCustomMarkersEvent = new CustomEvent('refresh-custom-markers', { bubbles: true })
|
||||||
|
|
||||||
|
mapEl.dispatchEvent(refreshCustomMarkersEvent)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Stores last map coords on context
|
// Stores last map coords on context
|
||||||
|
|||||||
@@ -260,7 +260,7 @@ dialog {
|
|||||||
}
|
}
|
||||||
|
|
||||||
&::backdrop {
|
&::backdrop {
|
||||||
background-color: rgba(0, 0, 0, 0.15);
|
backdrop-filter: blur(1px);
|
||||||
}
|
}
|
||||||
|
|
||||||
&.open {
|
&.open {
|
||||||
|
|||||||
@@ -1,25 +1,26 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { MapMarker, MapMarkerGroup, PlayerMarker } from '@/types/Leaflet';
|
import type { MapMarker, MapMarkerGroup, PlayerMarker } from '@/types/Leaflet';
|
||||||
import { useFocus, useMagicKeys, whenever } from '@vueuse/core';
|
import type { SearchMode } from '@/types/Search';
|
||||||
|
import { useFocus, useLocalStorage, useMagicKeys, whenever } from '@vueuse/core';
|
||||||
import { computed, onMounted, ref, onUpdated, watch } from 'vue';
|
import { computed, onMounted, ref, onUpdated, watch } from 'vue';
|
||||||
|
import SearchMarkersTags from './SearchMarkersTags.vue';
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
markers: MapMarker[],
|
markers: MapMarker[],
|
||||||
players: PlayerMarker,
|
players: PlayerMarker,
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
const markers = props.markers
|
const fixedMarkers = props.markers
|
||||||
|
const customMarkersData = useLocalStorage('custom-markers', [])
|
||||||
|
|
||||||
|
const allMarkers = computed(() => {
|
||||||
|
return [...fixedMarkers, ...customMarkersData.value]
|
||||||
|
})
|
||||||
|
|
||||||
// Search functions
|
// Search functions
|
||||||
const qInput = ref()
|
const qInput = ref()
|
||||||
const { focused: isFocused } = useFocus(qInput)
|
const { focused: isFocused } = useFocus(qInput)
|
||||||
|
|
||||||
/**
|
|
||||||
* Available advanced search modes
|
|
||||||
* Currently only groups MapMarkerGroup and string "query"
|
|
||||||
*/
|
|
||||||
type SearchMode = "query" | MapMarkerGroup
|
|
||||||
|
|
||||||
const currentSearchMode = ref<SearchMode>('query')
|
const currentSearchMode = ref<SearchMode>('query')
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
@@ -39,7 +40,7 @@ const unifier = new RegExp(/[^a-zA-Z0-9\-\'']/g)
|
|||||||
*/
|
*/
|
||||||
const filteredMarkers = computed(() => {
|
const filteredMarkers = computed(() => {
|
||||||
if (currentSearchMode.value === "query") {
|
if (currentSearchMode.value === "query") {
|
||||||
return markers?.filter(m => {
|
return allMarkers.value?.filter(m => {
|
||||||
const queryString = new String(q.value).replace(unifier, "").toLocaleLowerCase()
|
const queryString = new String(q.value).replace(unifier, "").toLocaleLowerCase()
|
||||||
const hitTitle = m.title.replace(unifier, "").toLocaleLowerCase().includes(queryString)
|
const hitTitle = m.title.replace(unifier, "").toLocaleLowerCase().includes(queryString)
|
||||||
const hitDesc = m.description?.replace(unifier, "").toLocaleLowerCase().includes(queryString)
|
const hitDesc = m.description?.replace(unifier, "").toLocaleLowerCase().includes(queryString)
|
||||||
@@ -48,7 +49,7 @@ const filteredMarkers = computed(() => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
return markers?.filter(m => {
|
return allMarkers.value?.filter(m => {
|
||||||
return m.group === currentSearchMode.value
|
return m.group === currentSearchMode.value
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@@ -115,14 +116,11 @@ onUpdated(() => {
|
|||||||
* Advanced Search Mode
|
* Advanced Search Mode
|
||||||
* Uses filters to show specific kinds of markers in the search results
|
* Uses filters to show specific kinds of markers in the search results
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// TODO: This should be refactored with better type checking
|
|
||||||
// It does the job for now but will be a pain if I had another feature that uses group sorting
|
|
||||||
const hasGroupFilter = computed(() => {
|
const hasGroupFilter = computed(() => {
|
||||||
return currentSearchMode.value !== 'query'
|
return currentSearchMode.value !== 'query'
|
||||||
})
|
})
|
||||||
|
|
||||||
function setActiveCategory(g: MapMarkerGroup) {
|
function handleCategorySwitch(g: MapMarkerGroup) {
|
||||||
resetQueryValue()
|
resetQueryValue()
|
||||||
|
|
||||||
if (currentSearchMode.value !== g) {
|
if (currentSearchMode.value !== g) {
|
||||||
@@ -166,6 +164,17 @@ function resetAllFields(actionAfter?: "focusAfter") {
|
|||||||
|
|
||||||
if (actionAfter === "focusAfter") isFocused.value = true
|
if (actionAfter === "focusAfter") isFocused.value = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* CUSTOM MARKERS HANDLING
|
||||||
|
* This is really dirty (to me at least)
|
||||||
|
* It should probably be handled by a store system, but nanostores doesn't mesh well
|
||||||
|
*/
|
||||||
|
onMounted(() => {
|
||||||
|
document.addEventListener('refresh-custom-markers', () => {
|
||||||
|
customMarkersData.value = useLocalStorage('custom-markers', []).value
|
||||||
|
})
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -209,61 +218,11 @@ function resetAllFields(actionAfter?: "focusAfter") {
|
|||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<menu class="tag-list">
|
<SearchMarkersTags
|
||||||
<li>
|
:current-search-mode="currentSearchMode"
|
||||||
<button
|
:custom-markers="customMarkersData"
|
||||||
@click="setActiveCategory('quests')"
|
@on-category-switch="handleCategorySwitch"
|
||||||
class="red"
|
/>
|
||||||
:class="{
|
|
||||||
'active': currentSearchMode === 'quests'
|
|
||||||
}"
|
|
||||||
>
|
|
||||||
<span class="icon">
|
|
||||||
<i v-if="currentSearchMode === 'quests'" class="ph-bold ph-check"></i>
|
|
||||||
<i v-else class="ph-fill ph-flag-banner"></i>
|
|
||||||
</span>
|
|
||||||
<span class="label">
|
|
||||||
Quêtes
|
|
||||||
</span>
|
|
||||||
</button>
|
|
||||||
</li>
|
|
||||||
|
|
||||||
<li>
|
|
||||||
<button
|
|
||||||
@click="setActiveCategory('capitals')"
|
|
||||||
class="blue"
|
|
||||||
:class="{
|
|
||||||
'active': currentSearchMode === 'capitals'
|
|
||||||
}"
|
|
||||||
>
|
|
||||||
<span class="icon">
|
|
||||||
<i v-if="currentSearchMode === 'capitals'" class="ph-bold ph-check"></i>
|
|
||||||
<i v-else class="ph-fill ph-castle-turret"></i>
|
|
||||||
</span>
|
|
||||||
<span class="label">
|
|
||||||
Capitales
|
|
||||||
</span>
|
|
||||||
</button>
|
|
||||||
</li>
|
|
||||||
|
|
||||||
<li>
|
|
||||||
<button
|
|
||||||
@click="setActiveCategory('landmarks')"
|
|
||||||
class="orange"
|
|
||||||
:class="{
|
|
||||||
'active': currentSearchMode === 'landmarks'
|
|
||||||
}"
|
|
||||||
>
|
|
||||||
<span class="icon">
|
|
||||||
<i v-if="currentSearchMode === 'landmarks'" class="ph-bold ph-check"></i>
|
|
||||||
<i v-else class="ph-fill ph-lighthouse"></i>
|
|
||||||
</span>
|
|
||||||
<span class="label">
|
|
||||||
Points d'intérêt
|
|
||||||
</span>
|
|
||||||
</button>
|
|
||||||
</li>
|
|
||||||
</menu>
|
|
||||||
</nav>
|
</nav>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -491,75 +450,5 @@ function resetAllFields(actionAfter?: "focusAfter") {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* TAG LIST
|
|
||||||
*/
|
|
||||||
.tag-list {
|
|
||||||
display: flex;
|
|
||||||
gap: .5rem;
|
|
||||||
margin-top: .5rem;
|
|
||||||
|
|
||||||
button {
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: .5ch;
|
|
||||||
padding-block: .25rem;
|
|
||||||
padding-inline: .8rem;
|
|
||||||
font-weight: 600;
|
|
||||||
font-size: .85em;
|
|
||||||
background: var(--white);
|
|
||||||
border: 1px solid var(--slate-400);
|
|
||||||
border-radius: 100vmax;
|
|
||||||
box-shadow: rgba(0, 0, 0, 0.1) 0px 4px 12px;
|
|
||||||
pointer-events: all;
|
|
||||||
cursor: pointer;
|
|
||||||
outline: .2rem solid transparent;
|
|
||||||
transition-property: color, background-color, border-color, outline-color;
|
|
||||||
transition-duration: .15s;
|
|
||||||
transition-timing-function: cubic-bezier(0.445, 0.05, 0.55, 0.95);
|
|
||||||
|
|
||||||
@media screen and (width < 900px) {
|
|
||||||
font-size: .76em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.icon {
|
|
||||||
font-size: 1.1em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.label {
|
|
||||||
text-underline-offset: .15rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
$colors: 'red', 'blue', 'orange';
|
|
||||||
|
|
||||||
@each $c in $colors {
|
|
||||||
&.#{$c} {
|
|
||||||
&.active {
|
|
||||||
color: var(--white);
|
|
||||||
background-color: var(--#{$c}-500);
|
|
||||||
border-color: var(--#{$c}-700);
|
|
||||||
}
|
|
||||||
|
|
||||||
&:not(.active) {
|
|
||||||
&:hover,
|
|
||||||
&:focus-visible {
|
|
||||||
color: var(--#{$c}-500);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&:hover,
|
|
||||||
&:focus-visible {
|
|
||||||
outline-color: color-mix(in srgb, var(--#{$c}-500) 20%, transparent);
|
|
||||||
border-color: var(--#{$c}-500);
|
|
||||||
|
|
||||||
.label {
|
|
||||||
text-decoration: underline;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
182
src/components/maps/overlay/SearchMarkersTags.vue
Normal file
182
src/components/maps/overlay/SearchMarkersTags.vue
Normal file
@@ -0,0 +1,182 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import type { MapMarker, MapMarkerGroup } from '@/types/Leaflet';
|
||||||
|
import type { SearchMode } from '@/types/Search';
|
||||||
|
|
||||||
|
defineProps<{
|
||||||
|
currentSearchMode: SearchMode,
|
||||||
|
customMarkers: MapMarker[]
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
(e: 'on-category-switch', cat: MapMarkerGroup): void
|
||||||
|
}>()
|
||||||
|
|
||||||
|
function emitCategorySwitch(newCategory: MapMarkerGroup) {
|
||||||
|
emit('on-category-switch', newCategory)
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<menu class="tag-list">
|
||||||
|
<TransitionGroup name="tag-menu">
|
||||||
|
<li>
|
||||||
|
<button
|
||||||
|
@click="emitCategorySwitch('quests')"
|
||||||
|
class="red"
|
||||||
|
:class="{
|
||||||
|
'active': currentSearchMode === 'quests'
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<span class="icon">
|
||||||
|
<i v-if="currentSearchMode === 'quests'" class="ph-bold ph-check"></i>
|
||||||
|
<i v-else class="ph-fill ph-flag-banner"></i>
|
||||||
|
</span>
|
||||||
|
<span class="label">
|
||||||
|
Quêtes
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li>
|
||||||
|
<button
|
||||||
|
@click="emitCategorySwitch('capitals')"
|
||||||
|
class="blue"
|
||||||
|
:class="{
|
||||||
|
'active': currentSearchMode === 'capitals'
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<span class="icon">
|
||||||
|
<i v-if="currentSearchMode === 'capitals'" class="ph-bold ph-check"></i>
|
||||||
|
<i v-else class="ph-fill ph-castle-turret"></i>
|
||||||
|
</span>
|
||||||
|
<span class="label">
|
||||||
|
Capitales
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li>
|
||||||
|
<button
|
||||||
|
@click="emitCategorySwitch('landmarks')"
|
||||||
|
class="orange"
|
||||||
|
:class="{
|
||||||
|
'active': currentSearchMode === 'landmarks'
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<span class="icon">
|
||||||
|
<i v-if="currentSearchMode === 'landmarks'" class="ph-bold ph-check"></i>
|
||||||
|
<i v-else class="ph-fill ph-lighthouse"></i>
|
||||||
|
</span>
|
||||||
|
<span class="label">
|
||||||
|
Points d'intérêt
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li v-if="customMarkers.length > 0">
|
||||||
|
<button
|
||||||
|
@click="emitCategorySwitch('custom')"
|
||||||
|
class="purple"
|
||||||
|
:class="{
|
||||||
|
'active': currentSearchMode === 'custom'
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<span class="icon">
|
||||||
|
<i v-if="currentSearchMode === 'custom'" class="ph-bold ph-check"></i>
|
||||||
|
<i v-else class="ph-fill ph-user-circle"></i>
|
||||||
|
</span>
|
||||||
|
<span class="label">
|
||||||
|
Personnels
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
</TransitionGroup>
|
||||||
|
</menu>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.tag-list {
|
||||||
|
display: flex;
|
||||||
|
gap: .5rem;
|
||||||
|
margin-top: .5rem;
|
||||||
|
|
||||||
|
.tag-menu-move, /* apply transition to moving elements */
|
||||||
|
.tag-menu-enter-active,
|
||||||
|
.tag-menu-leave-active {
|
||||||
|
transition: all 0.5s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tag-menu-enter-from,
|
||||||
|
.tag-menu-leave-to {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateX(30px);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ensure leaving items are taken out of layout flow so that moving
|
||||||
|
animations can be calculated correctly. */
|
||||||
|
.tag-menu-leave-active {
|
||||||
|
position: absolute;
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: .5ch;
|
||||||
|
padding-block: .25rem;
|
||||||
|
padding-inline: .8rem;
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: .85em;
|
||||||
|
background: var(--white);
|
||||||
|
border: 1px solid var(--slate-400);
|
||||||
|
border-radius: 100vmax;
|
||||||
|
box-shadow: rgba(0, 0, 0, 0.1) 0px 4px 12px;
|
||||||
|
pointer-events: all;
|
||||||
|
cursor: pointer;
|
||||||
|
outline: .2rem solid transparent;
|
||||||
|
transition-property: color, background-color, border-color, outline-color;
|
||||||
|
transition-duration: .15s;
|
||||||
|
transition-timing-function: cubic-bezier(0.445, 0.05, 0.55, 0.95);
|
||||||
|
|
||||||
|
@media screen and (width < 900px) {
|
||||||
|
font-size: .76em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon {
|
||||||
|
font-size: 1.1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.label {
|
||||||
|
text-underline-offset: .15rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
$colors: 'red', 'blue', 'orange', 'purple';
|
||||||
|
|
||||||
|
@each $c in $colors {
|
||||||
|
&.#{$c} {
|
||||||
|
&.active {
|
||||||
|
color: var(--white);
|
||||||
|
background-color: var(--#{$c}-500);
|
||||||
|
border-color: var(--#{$c}-700);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:not(.active) {
|
||||||
|
&:hover,
|
||||||
|
&:focus-visible {
|
||||||
|
color: var(--#{$c}-500);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover,
|
||||||
|
&:focus-visible {
|
||||||
|
outline-color: color-mix(in srgb, var(--#{$c}-500) 20%, transparent);
|
||||||
|
border-color: var(--#{$c}-500);
|
||||||
|
|
||||||
|
.label {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
export type MapMarkerGroup = "capitals" | "cities" | "towns" | "landmarks" | "quests";
|
export type MapMarkerGroup = "capitals" | "cities" | "towns" | "landmarks" | "quests" | "custom";
|
||||||
|
|
||||||
export type MapCoords = {
|
export type MapCoords = {
|
||||||
x: number,
|
x: number,
|
||||||
y: number,
|
y: number,
|
||||||
@@ -18,3 +19,10 @@ export type PlayerMarker = {
|
|||||||
description?: string,
|
description?: string,
|
||||||
link?: string,
|
link?: string,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type CustomMarker = {
|
||||||
|
lat: number,
|
||||||
|
lon: number,
|
||||||
|
title: string,
|
||||||
|
icon: Object
|
||||||
|
}
|
||||||
|
|||||||
8
src/types/Search.ts
Normal file
8
src/types/Search.ts
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
import type { MapMarkerGroup } from "./Leaflet";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Available advanced search modes for the filter functions
|
||||||
|
*
|
||||||
|
* Currently only groups MapMarkerGroup and string "query"
|
||||||
|
*/
|
||||||
|
export type SearchMode = "query" | MapMarkerGroup
|
||||||
Reference in New Issue
Block a user