Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
584 changes: 194 additions & 390 deletions README.md

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions components.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ declare module 'vue' {
export interface GlobalComponents {
ActiveFeatureProperties: typeof import('./src/components/ActiveFeatureProperties.vue')['default']
AreaMenu: typeof import('./src/components/AreaMenu.vue')['default']
FlashHighlight: typeof import('./src/components/FlashHighlight.vue')['default']
LayerLegend: typeof import('./src/components/LayerLegend.vue')['default']
LayerList: typeof import('./src/components/LayerList.vue')['default']
MapComponent: typeof import('./src/components/MapComponent.vue')['default']
Expand Down
79 changes: 46 additions & 33 deletions src/components/ActiveFeatureProperties.vue
Original file line number Diff line number Diff line change
@@ -1,48 +1,55 @@
<template>
<v-card
variant="flat"
rounded="xl"
class="bg-grey-lighten-3 px-3 py-0 mx-3"
<flash-highlight
:enabled="isReadyForFeatureSelection"
:flash-when-enabled="flashWhenEnabled"
>
<v-card-title v-if="hasActiveFeature" class="d-flex justify-space-between align-center card-title-compact">
<span>{{ cardTitle }}</span>
<v-btn
v-if="hasActiveFeature"
variant="outlined"
rounded="xl"
size="small"
class="bg-grey-lighten-2 clear-btn"
@click="clear"
>
Clear
</v-btn>
</v-card-title>
<v-card-text class="pa-0">
<div v-if="!hasActiveFeature" class="empty-state text-grey font-italic">
Click on a feature on the map
</div>
<v-list v-else density="compact">
<v-list-item
v-for="(value, label) in propertyDisplay"
:key="label"
class="item-compact"
<v-card
variant="flat"
rounded="xl"
class="bg-grey-lighten-3 px-3 py-0 mx-3 my-1"
>
<v-card-title v-if="hasActiveFeature" class="d-flex justify-space-between align-center card-title-compact">
<span>{{ cardTitle }}</span>
<v-btn
v-if="hasActiveFeature"
variant="outlined"
rounded="xl"
size="small"
class="bg-grey-lighten-2 clear-btn"
@click="clear"
>
<v-list-item-title class="item-title-compact">
<b>{{ label }}</b>: {{ value }}
</v-list-item-title>
</v-list-item>
</v-list>
</v-card-text>
</v-card>
Clear
</v-btn>
</v-card-title>
<v-card-text class="pa-0">
<div v-if="!hasActiveFeature" class="empty-state text-grey font-italic">
Click on a feature on the map
</div>
<v-list v-else density="compact">
<v-list-item
v-for="(value, label) in propertyDisplay"
:key="label"
class="item-compact"
>
<v-list-item-title class="item-title-compact">
<b>{{ label }}</b>: {{ value }}
</v-list-item-title>
</v-list-item>
</v-list>
</v-card-text>
</v-card>
</flash-highlight>
</template>

<script setup>
import { computed } from 'vue'
import { useMapStore } from '@/stores/map'
import FlashHighlight from '@/components/FlashHighlight.vue'

const props = defineProps({
layerId: { type: String, required: true },
propertiesBoxType: { type: String, required: true },
flashWhenEnabled: { type: Boolean, default: false },
})

const mapStore = useMapStore()
Expand Down Expand Up @@ -75,6 +82,12 @@
return mapStore.activeRegion?.layerId === props.layerId
})

const isReadyForFeatureSelection = computed(() => {
const isVisible = mapStore.layerVisibility[props.layerId] === true
const isClickable = mapStore.isLayerClickable(props.layerId)
return isVisible && isClickable && !hasActiveFeature.value
})

const propertyDisplay = computed(() => {
if (!hasActiveFeature.value) {
return {}
Expand Down
106 changes: 106 additions & 0 deletions src/components/FlashHighlight.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
<template>
<div
class="flash-highlight"
:class="{ 'flash-highlight--active': isFlashing }"
:style="{ '--flash-count': String(flashCountValue) }"
@animationend="onAnimationEnd"
>
<slot />
</div>
</template>

<script setup>
import { computed, nextTick, onBeforeUnmount, ref, watch } from 'vue'

const props = defineProps({
enabled: { type: Boolean, default: false },
flashWhenEnabled: { type: Boolean, default: false },
flashCount: { type: Number, default: 2 },
})

const isFlashing = ref(false)
const safeFlashCount = computed(() => Math.max(1, Number(props.flashCount) || 2))
let loopTimer = null

function triggerFlash () {
isFlashing.value = false
nextTick(() => {
isFlashing.value = true
})
}

function stopLoop () {
if (loopTimer != null) {
clearInterval(loopTimer)
loopTimer = null
}
isFlashing.value = false
}

function startLoop () {
stopLoop()
triggerFlash()
loopTimer = setInterval(() => {
triggerFlash()
}, 5000)
}

watch(
() => props.enabled,
(enabled) => {
if (!props.flashWhenEnabled) return
if (enabled) {
startLoop()
} else {
stopLoop()
}
},
{ immediate: true },
)

const flashCountValue = computed(() => safeFlashCount.value)

function onAnimationEnd () {
isFlashing.value = false
}

onBeforeUnmount(() => {
stopLoop()
})
</script>

<style scoped>
.flash-highlight {
border-radius: 8px;
}

.flash-highlight--active {
animation-name: flash-highlight-pulse;
animation-duration: 700ms;
animation-iteration-count: var(--flash-count, 2);
animation-timing-function: ease-in-out;
}

@keyframes flash-highlight-pulse {
0% {
background-color: rgba(255, 221, 87, 0);
box-shadow: 0 0 0 0 rgba(255, 221, 87, 0);
}
50% {
background-color: rgba(255, 221, 87, 0.32);
box-shadow: 0 0 0 2px rgba(255, 221, 87, 0.55);
}
100% {
background-color: rgba(255, 221, 87, 0);
box-shadow: 0 0 0 0 rgba(255, 221, 87, 0);
}
}

@media (prefers-reduced-motion: reduce) {
.flash-highlight--active {
animation: none;
box-shadow: 0 0 0 2px rgba(255, 221, 87, 0.55);
background-color: rgba(255, 221, 87, 0.2);
}
}
</style>
10 changes: 8 additions & 2 deletions src/components/LayerList.vue
Original file line number Diff line number Diff line change
Expand Up @@ -25,14 +25,15 @@
v-if="layer.propertiesBox"
:layer-id="layer.id"
:properties-box-type="layer.propertiesBox"
:flash-when-enabled="layer.flashWhenEnabled ?? false"
class="mt-0"
/>
</template>
</div>
</template>

<script setup>
import { computed, onMounted, watch } from 'vue'
import { computed, inject, onMounted, onUnmounted, watch } from 'vue'
import { useAppStore } from '@/stores/app'
import { useMapStore } from '@/stores/map'
import ActiveFeatureProperties from '@/components/ActiveFeatureProperties.vue'
Expand All @@ -44,6 +45,7 @@

const appStore = useAppStore()
const mapStore = useMapStore()
const stepId = inject('stepId', null)

const filteredLayers = computed(() => {
if (!props.conditionSource) {
Expand All @@ -68,10 +70,14 @@

onMounted(() => {
mapStore.initializeLayerVisibility(props.layers)
mapStore.initializeLayerClickable(props.layers)
mapStore.registerStepClickability(stepId, props.layers)
syncVisibilityToFilter()
})

onUnmounted(() => {
mapStore.unregisterStepClickability(stepId)
})

watch(filteredLayers, syncVisibilityToFilter)
</script>

Expand Down
Loading
Loading