Refactored tag list in their own component

This commit is contained in:
Alexis
2024-01-10 21:03:37 +01:00
parent 68eb0d165f
commit d680cae84d
7 changed files with 236 additions and 141 deletions

View File

@@ -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 {

View File

@@ -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

View File

@@ -260,7 +260,7 @@ dialog {
} }
&::backdrop { &::backdrop {
background-color: rgba(0, 0, 0, 0.15); backdrop-filter: blur(1px);
} }
&.open { &.open {

View File

@@ -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>

View 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>

View File

@@ -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
View 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