Skip to content

Commit

Permalink
refactor(mapper): feature legend & layer-switcher (#2107)
Browse files Browse the repository at this point in the history
* fix(bottom-sheet): adjust z-index

* refactor(main): seperate map control symbol and component

* refactor(legend): show feature level legend and refactor accordingly

* refactor(layer-switcher): remove basemap toggle icon, refactor accordingly

* fix(legend): update legend heading
  • Loading branch information
NSUWAL123 authored Jan 24, 2025
1 parent 14559e1 commit 23f6c39
Show file tree
Hide file tree
Showing 4 changed files with 120 additions and 107 deletions.
2 changes: 1 addition & 1 deletion src/mapper/src/lib/components/bottom-sheet.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@
<!-- sheet container -->
<div
bind:this={bottomSheetRef}
class={`z-10 bottom-sheet fixed w-[100vw] left-0 bottom-0 flex items-center flex-col justify-end duration-100 ease-linear z-20 ${
class={`z-30 bottom-sheet fixed w-[100vw] left-0 bottom-0 flex items-center flex-col justify-end duration-100 ease-linear z-20 ${
!show ? 'opacity-0 pointer-events-none' : 'opacity-100 pointer-events-none'
}`}
>
Expand Down
70 changes: 25 additions & 45 deletions src/mapper/src/lib/components/map/layer-switcher.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ map = new Map({

<script lang="ts">
import { onDestroy } from 'svelte';
import { clickOutside } from '$lib/utils/clickOutside';
type MapLibreStylePlusMetadata = maplibregl.StyleSpecification & {
metadata: {
Expand All @@ -37,15 +36,17 @@ map = new Map({
selectedStyleName?: string | undefined;
map: maplibregl.Map | undefined;
sourcesIdToReAdd: string[];
selectedStyleUrl: string | undefined;
setSelectedStyleUrl: (url: string | undefined) => void;
isOpen: boolean;
};
const { styles, selectedStyleName, map, sourcesIdToReAdd }: Props = $props();
const { styles, selectedStyleName, map, sourcesIdToReAdd, selectedStyleUrl, setSelectedStyleUrl, isOpen }: Props =
$props();
let allStyles: MapLibreStylePlusMetadata[] | [] = $state([]);
let selectedStyleUrl: string | undefined = $state(undefined);
// This variable is used for updating the prop selectedStyleName dynamically
let reactiveStyleSelection: MapLibreStylePlusMetadata | undefined = $state(undefined);
let isOpen = $state(false);
// Get style info when styles are updated
$effect(() => {
Expand All @@ -60,7 +61,7 @@ map = new Map({
$effect(() => {
// Set initial selected style
reactiveStyleSelection = allStyles.find((style) => style.name === selectedStyleName) || allStyles[0];
selectedStyleUrl = reactiveStyleSelection?.metadata?.thumbnail;
setSelectedStyleUrl(reactiveStyleSelection?.metadata?.thumbnail);
});
// Update the map when a new style is selected
Expand Down Expand Up @@ -143,7 +144,7 @@ map = new Map({
if (style.name === currentMapStyle.name) return;
selectedStyleUrl = style.metadata.thumbnail;
setSelectedStyleUrl(style.metadata.thumbnail);
// Apply the selected style to the map
// being sure to save sources and layers to add back on top
Expand Down Expand Up @@ -173,48 +174,27 @@ map = new Map({
}
onDestroy(() => {
allStyles = [];
selectedStyleUrl = undefined;
setSelectedStyleUrl(undefined);
});
</script>

<div class="relative font-barlow" use:clickOutside onclick_outside={() => (isOpen = false)}>
<div
onclick={() => (isOpen = !isOpen)}
role="button"
onkeydown={(e) => {
if (e.key === 'Enter') {
isOpen = !isOpen;
}
}}
tabindex="0"
>
<img
style="border: 1px solid #d73f3f;"
class="w-[2.25rem] h-[2.25rem] rounded-full"
src={selectedStyleUrl}
alt="Basemap Icon"
/>
</div>
<div
class={`absolute bottom-0 right-11 bg-white rounded-md p-4 duration-200 ${isOpen ? 'opacity-100' : 'opacity-0 pointer-events-none'}`}
>
<p class="font-semibold text-lg mb-2">Base Maps</p>
<div class="grid grid-cols-2 w-[212px] gap-3">
{#each allStyles as style, _}
<div
class={`layer-card ${selectedStyleUrl === style.metadata.thumbnail ? 'active' : ''} h-[3.75rem] relative overflow-hidden rounded-md cursor-pointer hover:border-red-600`}
onclick={() => selectStyle(style)}
role="button"
onkeydown={(e) => {
if (e.key === 'Enter') selectStyle(style);
}}
tabindex="0"
>
<img src={style.metadata.thumbnail} alt="Style Thumbnail" class="w-full h-full object-cover" />
<span class="absolute top-0 left-0 bg-white bg-opacity-80 px-1 rounded-br">{style.name}</span>
</div>
{/each}
</div>
<div class={`${isOpen ? 'block' : 'hidden'}`}>
<p class="font-semibold text-lg mb-2">Base Maps</p>
<div class="grid grid-cols-3 w-full gap-3">
{#each allStyles as style, _}
<div
class={`layer-card ${selectedStyleUrl === style.metadata.thumbnail ? 'active' : ''} h-[3.75rem] relative overflow-hidden rounded-md cursor-pointer hover:border-red-600`}
onclick={() => selectStyle(style)}
role="button"
onkeydown={(e) => {
if (e.key === 'Enter') selectStyle(style);
}}
tabindex="0"
>
<img src={style.metadata.thumbnail} alt="Style Thumbnail" class="w-full h-full object-cover" />
<span class="absolute top-0 left-0 bg-white bg-opacity-80 px-1 rounded-br text-sm">{style.name}</span>
</div>
{/each}
</div>
</div>

Expand Down
73 changes: 20 additions & 53 deletions src/mapper/src/lib/components/map/legend.svelte
Original file line number Diff line number Diff line change
@@ -1,62 +1,29 @@
<script lang="ts">
import '$styles/page.css';
import { clickOutside } from '$lib/utils/clickOutside.ts';
import LockImg from '$assets/images/black-lock.png';
type taskStatusesType = { status: string; color?: string; icon?: string };
let isOpen = $state(false);
type taskStatusesType = { status: string; color: string };
type Props = {
isOpen: boolean;
};
const { isOpen }: Props = $props();
const taskStatuses: taskStatusesType[] = [
{ status: 'Ready', color: '#ffffff' },
{ status: 'Locked For Mapping', color: '#008099' },
{ status: 'Ready For Validation', color: '#ade6ef' },
{ status: 'Locked For Validation', color: '#fceca4' },
{ status: 'Validated', color: '#40ac8c' },
{ status: 'More Mapping Needed', color: '#d73f3e' },
{ status: 'Locked', icon: LockImg },
{ status: 'Ready', color: '#9c9a9a' },
{ status: 'Opened in ODK', color: '#fae15f' },
{ status: 'Survey Submitted', color: '#71bf86' },
{ status: 'Marked Bad', color: '#fa1100' },
];
</script>

<div use:clickOutside onclick_outside={() => (isOpen = false)} class="relative font-barlow">
<div
aria-label="toggle legend"
class="group text-nowrap cursor-pointer"
onclick={() => (isOpen = !isOpen)}
role="button"
onkeydown={(e) => {
if (e.key === 'Enter') {
isOpen = !isOpen;
}
}}
tabindex="0"
>
<hot-icon
style="border: 1px solid #D7D7D7;"
name="legend-toggle"
class={`!text-[1.7rem] text-[#333333] bg-white p-1 rounded-full group-hover:text-red-600 duration-200 ${isOpen && 'text-red-600'}`}
></hot-icon>
</div>
<div
class={`absolute bottom-0 right-11 bg-white rounded-md p-4 duration-200 ${isOpen ? 'opacity-100' : 'opacity-0 pointer-events-none'} overflow-hidden flex flex-col gap-2`}
>
<p class="font-semibold leading-0 text-lg mb-3">Legend</p>
{#each taskStatuses as taskStatus}
<div class="flex items-center gap-2">
{#if !taskStatus.color}
<div class="w-5 h-5 flex justify-center">
<img src={taskStatus.icon} class="w-4" alt="Lock Icon" />
</div>
{:else}
<div
style="background-color: {taskStatus.color}; border: 1px solid #D0D0D0;"
class={`w-5 h-5 opacity-40`}
></div>
{/if}
<p class="font-regular text-[#494949] text-nowrap leading-0">{taskStatus?.status}</p>
</div>
{/each}
</div>
<div class={`${isOpen ? 'flex' : 'hidden'} flex-col gap-2`}>
<p class="font-semibold leading-0 text-lg mb-3">Legend (Features)</p>
{#each taskStatuses as taskStatus}
<div class="flex items-center gap-2">
<div
style="background-color: {taskStatus.color};"
class="w-5 h-5 opacity-40 border-solid border-[1px] border-[#D0D0D0]"
></div>
<p class="font-regular text-[#494949] text-nowrap leading-0">{taskStatus?.status}</p>
</div>
{/each}
</div>

<style></style>
82 changes: 74 additions & 8 deletions src/mapper/src/lib/components/map/main.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
import { projectSetupStep as projectSetupStepEnum, NewGeomTypes } from '$constants/enums.ts';
import { baseLayers, osmStyle, pmtilesStyle } from '$constants/baseLayers.ts';
import { getEntitiesStatusStore } from '$store/entities.svelte.ts';
import { clickOutside } from '$lib/utils/clickOutside.ts';
type bboxType = [number, number, number, number];
Expand Down Expand Up @@ -80,6 +81,8 @@
let projectSetupStep: number | null = $state(null);
let lineWidth = $state(1); // Initial line width of the rejected entities
let expanding = true; // Whether the line is expanding
let selectedControl: 'layer-switcher' | 'legend' | null = $state(null);
let selectedStyleUrl: string | undefined = $state(undefined);
// Trigger adding the PMTiles layer to baselayers, if PmtilesUrl is set
let allBaseLayers: maplibregl.StyleSpecification[] = $derived(
Expand Down Expand Up @@ -371,14 +374,44 @@
tabindex="0"
></sl-icon-button>
</div>

<LayerSwitcher
{map}
styles={allBaseLayers}
sourcesIdToReAdd={['tasks', 'entities', 'geolocation']}
selectedStyleName={selectedBaselayer}
></LayerSwitcher>
<Legend />
<div
aria-label="layer switcher"
onclick={() => {
selectedControl = 'layer-switcher';
}}
role="button"
onkeydown={(e) => {
if (e.key === 'Enter') {
selectedControl = 'layer-switcher';
}
}}
tabindex="0"
>
<img
style="border: 1px solid #d73f3f;"
class="w-[2.25rem] h-[2.25rem] rounded-full"
src={selectedStyleUrl}
alt="Basemap Icon"
/>
</div>
<div
aria-label="toggle legend"
class="group text-nowrap cursor-pointer"
onclick={() => (selectedControl = 'legend')}
role="button"
onkeydown={(e) => {
if (e.key === 'Enter') {
selectedControl = 'legend';
}
}}
tabindex="0"
>
<hot-icon
style="border: 1px solid #D7D7D7;"
name="legend-toggle"
class="!text-[1.7rem] text-[#333333] bg-white p-1 rounded-full group-hover:text-red-600 duration-200"
></hot-icon>
</div>
</Control>
<!-- Add the Geolocation GeoJSON layer to the map -->
<Geolocation {map}></Geolocation>
Expand Down Expand Up @@ -537,3 +570,36 @@
</div>
{/if}
</MapLibre>

<div
use:clickOutside
onclick_outside={() => (selectedControl = null)}
class={`font-barlow flex justify-center !w-[100vw] absolute left-0 z-20 duration-400 ${selectedControl ? 'bottom-[4rem]' : '-bottom-[100%] pointer-events-none'}`}
>
<div class="bg-white w-full font-regular md:max-w-[580px] px-4 py-3 sm:py-4 rounded-t-3xl">
<div class="flex justify-end">
<hot-icon
name="close"
class="!text-[1.5rem] text-[#52525B] cursor-pointer hover:text-red-600 duration-200"
onclick={() => (selectedControl = null)}
onkeydown={(e: KeyboardEvent) => {
if (e.key === 'Enter') {
selectedControl = null;
}
}}
role="button"
tabindex="0"
></hot-icon>
</div>
<LayerSwitcher
{map}
styles={allBaseLayers}
sourcesIdToReAdd={['tasks', 'entities', 'geolocation']}
selectedStyleName={selectedBaselayer}
{selectedStyleUrl}
setSelectedStyleUrl={(style) => (selectedStyleUrl = style)}
isOpen={selectedControl === 'layer-switcher'}
></LayerSwitcher>
<Legend isOpen={selectedControl === 'legend'} />
</div>
</div>

0 comments on commit 23f6c39

Please sign in to comment.