forked from MapComplete/MapComplete
Merge pull request #1731 from pietervdvn/feature/favourites
Feature/favourites
This commit is contained in:
commit
4197ec0055
80 changed files with 2715 additions and 1059 deletions
|
|
@ -11,7 +11,7 @@
|
|||
|
||||
<button class={clss} on:click={() => osmConnection.AttemptLogin()}>
|
||||
<ToSvelte construct={Svg.login_svg().SetClass("w-12 m-1")} />
|
||||
<slot name="message">
|
||||
<slot>
|
||||
<Tr t={Translations.t.general.loginWithOpenStreetMap} />
|
||||
</slot>
|
||||
</button>
|
||||
|
|
|
|||
|
|
@ -4,12 +4,12 @@
|
|||
import Translations from "../i18n/Translations"
|
||||
import Tr from "./Tr.svelte"
|
||||
|
||||
export let osmConnection: OsmConnection
|
||||
export let osmConnection: OsmConnection;
|
||||
</script>
|
||||
|
||||
<button
|
||||
on:click={() => {
|
||||
state.osmConnection.LogOut()
|
||||
osmConnection.LogOut()
|
||||
}}
|
||||
>
|
||||
<Logout class="h-6 w-6" />
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@
|
|||
const uiElem = typeof construct === "function" ? construct() : construct
|
||||
html = uiElem?.ConstructElement()
|
||||
if (html !== undefined) {
|
||||
elem.replaceWith(html)
|
||||
elem?.replaceWith(html)
|
||||
}
|
||||
})
|
||||
|
||||
|
|
|
|||
|
|
@ -121,9 +121,9 @@ export default class UploadTraceToOsmUI extends LoginToggle {
|
|||
]).SetClass("flex p-2 rounded-xl border-2 subtle-border items-center"),
|
||||
new Toggle(
|
||||
confirmPanel,
|
||||
new SubtleButton(new SvelteUIElement(Upload), t.title).onClick(() =>
|
||||
clicked.setData(true)
|
||||
),
|
||||
new SubtleButton(new SvelteUIElement(Upload), t.title)
|
||||
.onClick(() => clicked.setData(true))
|
||||
.SetClass("w-full"),
|
||||
clicked
|
||||
),
|
||||
uploadFinished
|
||||
|
|
|
|||
83
src/UI/Favourites/FavouriteSummary.svelte
Normal file
83
src/UI/Favourites/FavouriteSummary.svelte
Normal file
|
|
@ -0,0 +1,83 @@
|
|||
<script lang="ts">
|
||||
|
||||
import type { SpecialVisualizationState } from "../SpecialVisualization";
|
||||
import TagRenderingAnswer from "../Popup/TagRendering/TagRenderingAnswer.svelte";
|
||||
import type { Feature } from "geojson";
|
||||
import { ImmutableStore } from "../../Logic/UIEventSource";
|
||||
import { GeoOperations } from "../../Logic/GeoOperations";
|
||||
import Center from "../../assets/svg/Center.svelte";
|
||||
|
||||
export let feature: Feature;
|
||||
let properties: Record<string, string> = feature.properties;
|
||||
export let state: SpecialVisualizationState;
|
||||
let tags = state.featureProperties.getStore(properties.id) ?? new ImmutableStore(properties);
|
||||
|
||||
const favLayer = state.layerState.filteredLayers.get("favourite");
|
||||
const favConfig = favLayer.layerDef;
|
||||
const titleConfig = favConfig.title;
|
||||
|
||||
function center() {
|
||||
const [lon, lat] = GeoOperations.centerpointCoordinates(feature);
|
||||
state.mapProperties.location.setData(
|
||||
{ lon, lat }
|
||||
);
|
||||
const z = state.mapProperties.zoom.data
|
||||
state.mapProperties.zoom.setData( Math.min(17, Math.max(12, z )) )
|
||||
state.guistate.menuIsOpened.setData(false);
|
||||
}
|
||||
|
||||
function select() {
|
||||
state.selectedLayer.setData(favConfig);
|
||||
state.selectedElement.setData(feature);
|
||||
center();
|
||||
}
|
||||
|
||||
const coord = GeoOperations.centerpointCoordinates(feature);
|
||||
const distance = state.mapProperties.location.stabilized(500).mapD(({ lon, lat }) => {
|
||||
let meters = Math.round(GeoOperations.distanceBetween(coord, [lon, lat]));
|
||||
|
||||
if (meters < 1000) {
|
||||
return meters + "m";
|
||||
}
|
||||
|
||||
meters = Math.round(meters / 100);
|
||||
const kmStr = "" + meters;
|
||||
|
||||
|
||||
return kmStr.substring(0, kmStr.length - 1) + "." + kmStr.substring(kmStr.length - 1) + "km";
|
||||
});
|
||||
const titleIconBlacklist = ["osmlink", "sharelink", "favourite_title_icon"];
|
||||
|
||||
</script>
|
||||
|
||||
<div class="px-1 my-1 border-2 border-dashed border-gray-300 rounded grid grid-cols-2 items-center no-weblate">
|
||||
<button class="cursor-pointer ml-1 m-0 link justify-self-start" on:click={() => select()}>
|
||||
<TagRenderingAnswer config={titleConfig} extraClasses="underline" layer={favConfig} selectedElement={feature}
|
||||
{tags} />
|
||||
</button>
|
||||
|
||||
|
||||
<div class="flex items-center justify-self-end title-icons links-as-button gap-x-0.5 p-1 pt-0.5 sm:pt-1">
|
||||
{#each favConfig.titleIcons as titleIconConfig}
|
||||
{#if (titleIconBlacklist.indexOf(titleIconConfig.id) < 0) && (titleIconConfig.condition?.matchesProperties(properties) ?? true) && (titleIconConfig.metacondition?.matchesProperties({ ...properties, ...state.userRelatedState.preferencesAsTags.data }) ?? true) && titleIconConfig.IsKnown(properties)}
|
||||
<div class={titleIconConfig.renderIconClass ?? "flex h-8 w-8 items-center"}>
|
||||
<TagRenderingAnswer
|
||||
config={titleIconConfig}
|
||||
{tags}
|
||||
selectedElement={feature}
|
||||
{state}
|
||||
layer={favLayer}
|
||||
extraClasses="h-full justify-center"
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
{/each}
|
||||
|
||||
<button class="p-1" on:click={() => center()}>
|
||||
<Center class="w-6 h-6" />
|
||||
</button>
|
||||
<div class="w-14">
|
||||
{$distance}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
68
src/UI/Favourites/Favourites.svelte
Normal file
68
src/UI/Favourites/Favourites.svelte
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
<script lang="ts">
|
||||
import type { SpecialVisualizationState } from "../SpecialVisualization";
|
||||
import FavouriteSummary from "./FavouriteSummary.svelte";
|
||||
import Translations from "../i18n/Translations";
|
||||
import Tr from "../Base/Tr.svelte";
|
||||
import { DownloadIcon } from "@rgossiaux/svelte-heroicons/solid";
|
||||
import { Utils } from "../../Utils";
|
||||
import { GeoOperations } from "../../Logic/GeoOperations";
|
||||
import type { Feature, LineString, Point } from "geojson";
|
||||
import LoginToggle from "../Base/LoginToggle.svelte";
|
||||
import LoginButton from "../Base/LoginButton.svelte";
|
||||
|
||||
/**
|
||||
* A panel showing all your favourites
|
||||
*/
|
||||
export let state: SpecialVisualizationState;
|
||||
let favourites = state.favourites.allFavourites;
|
||||
|
||||
function downloadGeojson() {
|
||||
const contents = { features: favourites.data, type: "FeatureCollection" };
|
||||
Utils.offerContentsAsDownloadableFile(
|
||||
JSON.stringify(contents),
|
||||
"mapcomplete-favourites-" + (new Date().toISOString()) + ".geojson",
|
||||
{
|
||||
mimetype: "application/vnd.geo+json"
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
function downloadGPX() {
|
||||
const gpx = GeoOperations.toGpxPoints(<Feature<Point>>favourites.data, "MapComplete favourites");
|
||||
Utils.offerContentsAsDownloadableFile(gpx,
|
||||
"mapcomplete-favourites-" + (new Date().toISOString()) + ".gpx",
|
||||
{
|
||||
mimetype: "{gpx=application/gpx+xml}"
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<LoginToggle {state}>
|
||||
<div slot="not-logged-in">
|
||||
<LoginButton osmConnection={state.osmConnection}>
|
||||
<Tr t={Translations.t.favouritePoi.loginToSeeList}/>
|
||||
</LoginButton>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col" on:keypress={(e) => console.log("Got keypress", e)}>
|
||||
<Tr t={Translations.t.favouritePoi.intro.Subs({length: $favourites?.length ?? 0})} />
|
||||
<Tr t={Translations.t.favouritePoi.privacy} />
|
||||
|
||||
{#each $favourites as feature (feature.properties.id)}
|
||||
<FavouriteSummary {feature} {state} />
|
||||
{/each}
|
||||
|
||||
<div class="mt-8">
|
||||
<button class="flex p-2" on:click={() => downloadGeojson()}>
|
||||
<DownloadIcon class="h-6 w-6" />
|
||||
<Tr t={Translations.t.favouritePoi.downloadGeojson} />
|
||||
</button>
|
||||
<button class="flex p-2" on:click={() => downloadGPX()}>
|
||||
<DownloadIcon class="h-6 w-6" />
|
||||
<Tr t={Translations.t.favouritePoi.downloadGpx} />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</LoginToggle>
|
||||
|
|
@ -1,27 +1,7 @@
|
|||
<script lang="ts">
|
||||
import { IconConfig } from "../../Models/ThemeConfig/PointRenderingConfig"
|
||||
import { Store } from "../../Logic/UIEventSource"
|
||||
import Pin from "../../assets/svg/Pin.svelte"
|
||||
import Square from "../../assets/svg/Square.svelte"
|
||||
import Circle from "../../assets/svg/Circle.svelte"
|
||||
import Checkmark from "../../assets/svg/Checkmark.svelte"
|
||||
import Clock from "../../assets/svg/Clock.svelte"
|
||||
import Close from "../../assets/svg/Close.svelte"
|
||||
import Crosshair from "../../assets/svg/Crosshair.svelte"
|
||||
import Help from "../../assets/svg/Help.svelte"
|
||||
import Home from "../../assets/svg/Home.svelte"
|
||||
import Invalid from "../../assets/svg/Invalid.svelte"
|
||||
import Location from "../../assets/svg/Location.svelte"
|
||||
import Location_empty from "../../assets/svg/Location_empty.svelte"
|
||||
import Location_locked from "../../assets/svg/Location_locked.svelte"
|
||||
import Note from "../../assets/svg/Note.svelte"
|
||||
import Resolved from "../../assets/svg/Resolved.svelte"
|
||||
import Ring from "../../assets/svg/Ring.svelte"
|
||||
import Scissors from "../../assets/svg/Scissors.svelte"
|
||||
import Teardrop from "../../assets/svg/Teardrop.svelte"
|
||||
import Teardrop_with_hole_green from "../../assets/svg/Teardrop_with_hole_green.svelte"
|
||||
import Triangle from "../../assets/svg/Triangle.svelte"
|
||||
import Icon from "./Icon.svelte"
|
||||
import { IconConfig } from "../../Models/ThemeConfig/PointRenderingConfig";
|
||||
import { Store } from "../../Logic/UIEventSource";
|
||||
import Icon from "./Icon.svelte";
|
||||
|
||||
/**
|
||||
* Renders a single icon.
|
||||
|
|
|
|||
|
|
@ -7,9 +7,9 @@
|
|||
/**
|
||||
* Renders a 'marker', which consists of multiple 'icons'
|
||||
*/
|
||||
export let marker: IconConfig[] = config?.marker
|
||||
export let marker: IconConfig[] = config?.marker;
|
||||
export let tags: Store<Record<string, string>>
|
||||
export let rotation: TagRenderingConfig
|
||||
export let rotation: TagRenderingConfig = undefined;
|
||||
let _rotation = rotation
|
||||
? tags.map((tags) => rotation.GetRenderValue(tags).Subs(tags).txt)
|
||||
: new ImmutableStore(0)
|
||||
|
|
@ -18,7 +18,9 @@
|
|||
{#if marker && marker}
|
||||
<div class="relative h-full w-full" style={`transform: rotate(${$_rotation})`}>
|
||||
{#each marker as icon}
|
||||
<DynamicIcon {icon} {tags} />
|
||||
<div class="absolute top-0 left-0 h-full w-full">
|
||||
<DynamicIcon {icon} {tags} />
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
|
|
|
|||
|
|
@ -1,27 +1,29 @@
|
|||
<script lang="ts">
|
||||
import Pin from "../../assets/svg/Pin.svelte"
|
||||
import Square from "../../assets/svg/Square.svelte"
|
||||
import Circle from "../../assets/svg/Circle.svelte"
|
||||
import Checkmark from "../../assets/svg/Checkmark.svelte"
|
||||
import Clock from "../../assets/svg/Clock.svelte"
|
||||
import Close from "../../assets/svg/Close.svelte"
|
||||
import Crosshair from "../../assets/svg/Crosshair.svelte"
|
||||
import Help from "../../assets/svg/Help.svelte"
|
||||
import Home from "../../assets/svg/Home.svelte"
|
||||
import Invalid from "../../assets/svg/Invalid.svelte"
|
||||
import Location from "../../assets/svg/Location.svelte"
|
||||
import Location_empty from "../../assets/svg/Location_empty.svelte"
|
||||
import Location_locked from "../../assets/svg/Location_locked.svelte"
|
||||
import Note from "../../assets/svg/Note.svelte"
|
||||
import Resolved from "../../assets/svg/Resolved.svelte"
|
||||
import Ring from "../../assets/svg/Ring.svelte"
|
||||
import Scissors from "../../assets/svg/Scissors.svelte"
|
||||
import Teardrop from "../../assets/svg/Teardrop.svelte"
|
||||
import Teardrop_with_hole_green from "../../assets/svg/Teardrop_with_hole_green.svelte"
|
||||
import Triangle from "../../assets/svg/Triangle.svelte"
|
||||
import Brick_wall_square from "../../assets/svg/Brick_wall_square.svelte"
|
||||
import Brick_wall_round from "../../assets/svg/Brick_wall_round.svelte"
|
||||
import Gps_arrow from "../../assets/svg/Gps_arrow.svelte"
|
||||
import Pin from "../../assets/svg/Pin.svelte";
|
||||
import Square from "../../assets/svg/Square.svelte";
|
||||
import Circle from "../../assets/svg/Circle.svelte";
|
||||
import Checkmark from "../../assets/svg/Checkmark.svelte";
|
||||
import Clock from "../../assets/svg/Clock.svelte";
|
||||
import Close from "../../assets/svg/Close.svelte";
|
||||
import Crosshair from "../../assets/svg/Crosshair.svelte";
|
||||
import Help from "../../assets/svg/Help.svelte";
|
||||
import Home from "../../assets/svg/Home.svelte";
|
||||
import Invalid from "../../assets/svg/Invalid.svelte";
|
||||
import Location from "../../assets/svg/Location.svelte";
|
||||
import Location_empty from "../../assets/svg/Location_empty.svelte";
|
||||
import Location_locked from "../../assets/svg/Location_locked.svelte";
|
||||
import Note from "../../assets/svg/Note.svelte";
|
||||
import Resolved from "../../assets/svg/Resolved.svelte";
|
||||
import Ring from "../../assets/svg/Ring.svelte";
|
||||
import Scissors from "../../assets/svg/Scissors.svelte";
|
||||
import Teardrop from "../../assets/svg/Teardrop.svelte";
|
||||
import Teardrop_with_hole_green from "../../assets/svg/Teardrop_with_hole_green.svelte";
|
||||
import Triangle from "../../assets/svg/Triangle.svelte";
|
||||
import Brick_wall_square from "../../assets/svg/Brick_wall_square.svelte";
|
||||
import Brick_wall_round from "../../assets/svg/Brick_wall_round.svelte";
|
||||
import Gps_arrow from "../../assets/svg/Gps_arrow.svelte";
|
||||
import { HeartIcon } from "@babeard/svelte-heroicons/solid";
|
||||
import { HeartIcon as HeartOutlineIcon } from "@babeard/svelte-heroicons/outline";
|
||||
|
||||
/**
|
||||
* Renders a single icon.
|
||||
|
|
@ -29,68 +31,72 @@
|
|||
* Icons -placed on top of each other- form a 'Marker' together
|
||||
*/
|
||||
|
||||
export let icon: string | undefined
|
||||
export let color: string | undefined
|
||||
export let icon: string | undefined;
|
||||
export let color: string | undefined = undefined
|
||||
export let clss: string | undefined = undefined
|
||||
</script>
|
||||
|
||||
{#if icon}
|
||||
<div class="absolute top-0 left-0 h-full w-full">
|
||||
{#if icon === "pin"}
|
||||
<Pin {color} />
|
||||
<Pin {color} class={clss}/>
|
||||
{:else if icon === "square"}
|
||||
<Square {color} />
|
||||
<Square {color} class={clss}/>
|
||||
{:else if icon === "circle"}
|
||||
<Circle {color} />
|
||||
<Circle {color} class={clss}/>
|
||||
{:else if icon === "checkmark"}
|
||||
<Checkmark {color} />
|
||||
<Checkmark {color} class={clss}/>
|
||||
{:else if icon === "clock"}
|
||||
<Clock {color} />
|
||||
<Clock {color} class={clss}/>
|
||||
{:else if icon === "close"}
|
||||
<Close {color} />
|
||||
<Close {color} class={clss}/>
|
||||
{:else if icon === "crosshair"}
|
||||
<Crosshair {color} />
|
||||
<Crosshair {color} class={clss}/>
|
||||
{:else if icon === "help"}
|
||||
<Help {color} />
|
||||
<Help {color} class={clss}/>
|
||||
{:else if icon === "home"}
|
||||
<Home {color} />
|
||||
<Home {color} class={clss}/>
|
||||
{:else if icon === "invalid"}
|
||||
<Invalid {color} />
|
||||
<Invalid {color} class={clss}/>
|
||||
{:else if icon === "location"}
|
||||
<Location {color} />
|
||||
<Location {color} class={clss}/>
|
||||
{:else if icon === "location_empty"}
|
||||
<Location_empty {color} />
|
||||
<Location_empty {color} class={clss}/>
|
||||
{:else if icon === "location_locked"}
|
||||
<Location_locked {color} />
|
||||
<Location_locked {color} class={clss}/>
|
||||
{:else if icon === "note"}
|
||||
<Note {color} />
|
||||
<Note {color} class={clss}/>
|
||||
{:else if icon === "resolved"}
|
||||
<Resolved {color} />
|
||||
<Resolved {color} class={clss}/>
|
||||
{:else if icon === "ring"}
|
||||
<Ring {color} />
|
||||
<Ring {color} class={clss}/>
|
||||
{:else if icon === "scissors"}
|
||||
<Scissors {color} />
|
||||
<Scissors {color} class={clss}/>
|
||||
{:else if icon === "teardrop"}
|
||||
<Teardrop {color} />
|
||||
<Teardrop {color} class={clss}/>
|
||||
{:else if icon === "teardrop_with_hole_green"}
|
||||
<Teardrop_with_hole_green {color} />
|
||||
<Teardrop_with_hole_green {color} class={clss}/>
|
||||
{:else if icon === "triangle"}
|
||||
<Triangle {color} />
|
||||
<Triangle {color} class={clss}/>
|
||||
{:else if icon === "brick_wall_square"}
|
||||
<Brick_wall_square {color} />
|
||||
<Brick_wall_square {color} class={clss}/>
|
||||
{:else if icon === "brick_wall_round"}
|
||||
<Brick_wall_round {color} />
|
||||
<Brick_wall_round {color} class={clss}/>
|
||||
{:else if icon === "gps_arrow"}
|
||||
<Gps_arrow {color} />
|
||||
<Gps_arrow {color} class={clss}/>
|
||||
{:else if icon === "checkmark"}
|
||||
<Checkmark {color} />
|
||||
<Checkmark {color} class={clss}/>
|
||||
{:else if icon === "help"}
|
||||
<Help {color} />
|
||||
<Help {color} class={clss}/>
|
||||
{:else if icon === "close"}
|
||||
<Close {color} />
|
||||
<Close {color} class={clss}/>
|
||||
{:else if icon === "invalid"}
|
||||
<Invalid {color} />
|
||||
<Invalid {color} class={clss}/>
|
||||
{:else if icon === "heart"}
|
||||
<HeartIcon class={clss}/>
|
||||
{:else if icon === "heart_outline"}
|
||||
<HeartOutlineIcon class={clss}/>
|
||||
{:else}
|
||||
<img class="h-full w-full" src={icon} />
|
||||
<img class={clss ?? "h-full w-full"} src={icon} aria-hidden="true"
|
||||
alt="" />
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
|
|
|
|||
|
|
@ -1,16 +1,18 @@
|
|||
<script lang="ts">
|
||||
import Icon from "./Icon.svelte"
|
||||
import Icon from "./Icon.svelte";
|
||||
|
||||
/**
|
||||
* Renders a 'marker', which consists of multiple 'icons'
|
||||
*/
|
||||
export let icons: { icon: string; color: string }[]
|
||||
export let icons: { icon: string; color: string }[];
|
||||
</script>
|
||||
|
||||
{#if icons !== undefined && icons.length > 0}
|
||||
<div class="relative h-full w-full">
|
||||
{#each icons as icon}
|
||||
<Icon icon={icon.icon} color={icon.color} />
|
||||
<div class="absolute top-0 left-0 h-full w-full">
|
||||
<Icon icon={icon.icon} color={icon.color} />
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
|
|
|
|||
|
|
@ -12,11 +12,9 @@ import { Feature, Point } from "geojson"
|
|||
import LineRenderingConfig from "../../Models/ThemeConfig/LineRenderingConfig"
|
||||
import { Utils } from "../../Utils"
|
||||
import * as range_layer from "../../../assets/layers/range/range.json"
|
||||
import { LayerConfigJson } from "../../Models/ThemeConfig/Json/LayerConfigJson"
|
||||
import PerLayerFeatureSourceSplitter from "../../Logic/FeatureSource/PerLayerFeatureSourceSplitter"
|
||||
import FilteredLayer from "../../Models/FilteredLayer"
|
||||
import SimpleFeatureSource from "../../Logic/FeatureSource/Sources/SimpleFeatureSource"
|
||||
import { CLIENT_RENEG_LIMIT } from "tls"
|
||||
|
||||
class PointRenderingLayer {
|
||||
private readonly _config: PointRenderingConfig
|
||||
|
|
|
|||
50
src/UI/OpeningHours/NextChangeViz.svelte
Normal file
50
src/UI/OpeningHours/NextChangeViz.svelte
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
<script lang="ts">/**
|
||||
* Simple visualisation which shows when the POI opens/closes next.
|
||||
*/
|
||||
import type { SpecialVisualizationState } from "../SpecialVisualization";
|
||||
import { Store, Stores } from "../../Logic/UIEventSource";
|
||||
import { OH } from "./OpeningHours";
|
||||
import opening_hours from "opening_hours";
|
||||
import Clock from "../../assets/svg/Clock.svelte";
|
||||
import { Utils } from "../../Utils";
|
||||
import Circle from "../../assets/svg/Circle.svelte";
|
||||
import Ring from "../../assets/svg/Ring.svelte";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
|
||||
export let state: SpecialVisualizationState;
|
||||
export let tags: Store<Record<string, string>>;
|
||||
export let keyToUse: string = "opening_hours";
|
||||
export let prefix: string = undefined;
|
||||
export let postfix: string = undefined;
|
||||
let oh: Store<opening_hours | "error" | undefined> = OH.CreateOhObjectStore(tags, keyToUse, prefix, postfix);
|
||||
|
||||
let currentState = oh.mapD(oh => typeof oh === "string" ? undefined : oh.getState());
|
||||
let tomorrow = new Date();
|
||||
tomorrow.setTime(tomorrow.getTime() + 24 * 60 * 60 * 1000);
|
||||
let nextChange = oh
|
||||
.mapD(oh => typeof oh === "string" ? undefined : oh.getNextChange(new Date(), tomorrow), [Stores.Chronic(5 * 60 * 1000)])
|
||||
.mapD(date => Utils.TwoDigits(date.getHours()) + ":" + Utils.TwoDigits(date.getMinutes()));
|
||||
|
||||
let size = nextChange.map(change => change === undefined ? "absolute h-7 w-7" : "absolute h-5 w-5 top-0 left-1/4");
|
||||
|
||||
</script>
|
||||
|
||||
{#if $currentState !== undefined}
|
||||
<div class="relative h-8 w-8">
|
||||
{#if $currentState === true}
|
||||
<Ring class={$size} color="#0f0" style="z-index: 0" />
|
||||
<Clock class={$size} color="#0f0" style="z-index: 0" />
|
||||
{:else if $currentState === false}
|
||||
<Circle class={$size} color="#f00" style="z-index: 0" />
|
||||
<Clock class={$size} color="#fff" style="z-index: 0" />
|
||||
{/if}
|
||||
|
||||
{#if $nextChange !== undefined}
|
||||
<span class="absolute bottom-0 font-bold text-sm" style="z-index: 1; background-color: #ffffff88; margin-top: 3px">
|
||||
{$nextChange}
|
||||
</span>
|
||||
{/if}
|
||||
|
||||
</div>
|
||||
|
||||
{/if}
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
import { Utils } from "../../Utils"
|
||||
import opening_hours from "opening_hours"
|
||||
import { Store } from "../../Logic/UIEventSource"
|
||||
|
||||
export interface OpeningHour {
|
||||
weekday: number // 0 is monday, 1 is tuesday, ...
|
||||
|
|
@ -494,10 +495,48 @@ This list will be sorted
|
|||
|
||||
return [changeHours, changeHourText]
|
||||
}
|
||||
public static CreateOhObjectStore(
|
||||
tags: Store<Record<string, string>>,
|
||||
key: string = "opening_hours",
|
||||
prefixToIgnore?: string,
|
||||
postfixToIgnore?: string
|
||||
): Store<opening_hours | undefined | "error"> {
|
||||
prefixToIgnore ??= ""
|
||||
postfixToIgnore ??= ""
|
||||
const country = tags.map((tags) => tags._country)
|
||||
return tags
|
||||
.mapD((tags) => {
|
||||
const value: string = tags[key]
|
||||
if (value === undefined) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
if (
|
||||
(prefixToIgnore || postfixToIgnore) &&
|
||||
value.startsWith(prefixToIgnore) &&
|
||||
value.endsWith(postfixToIgnore)
|
||||
) {
|
||||
return value
|
||||
.substring(prefixToIgnore.length, value.length - postfixToIgnore.length)
|
||||
.trim()
|
||||
}
|
||||
return value
|
||||
})
|
||||
.mapD(
|
||||
(ohtext) => {
|
||||
try {
|
||||
return OH.CreateOhObject(<any>tags.data, ohtext, country.data)
|
||||
} catch (e) {
|
||||
return "error"
|
||||
}
|
||||
},
|
||||
[country]
|
||||
)
|
||||
}
|
||||
public static CreateOhObject(
|
||||
tags: Record<string, string> & { _lat: number; _lon: number; _country?: string },
|
||||
textToParse: string
|
||||
textToParse: string,
|
||||
country?: string
|
||||
) {
|
||||
// noinspection JSPotentiallyInvalidConstructorUsage
|
||||
return new opening_hours(
|
||||
|
|
@ -506,7 +545,7 @@ This list will be sorted
|
|||
lat: tags._lat,
|
||||
lon: tags._lon,
|
||||
address: {
|
||||
country_code: tags._country?.toLowerCase(),
|
||||
country_code: country.toLowerCase(),
|
||||
state: undefined,
|
||||
},
|
||||
},
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ import Combine from "../Base/Combine"
|
|||
import { FixedUiElement } from "../Base/FixedUiElement"
|
||||
import { OH } from "./OpeningHours"
|
||||
import Translations from "../i18n/Translations"
|
||||
import Constants from "../../Models/Constants"
|
||||
import BaseUIElement from "../BaseUIElement"
|
||||
import Toggle from "../Input/Toggle"
|
||||
import { VariableUiElement } from "../Base/VariableUIElement"
|
||||
|
|
@ -30,48 +29,20 @@ export default class OpeningHoursVisualization extends Toggle {
|
|||
prefix = "",
|
||||
postfix = ""
|
||||
) {
|
||||
const country = tags.map((tags) => tags._country)
|
||||
const openingHoursStore = OH.CreateOhObjectStore(tags, key, prefix, postfix)
|
||||
const ohTable = new VariableUiElement(
|
||||
tags
|
||||
.map((tags) => {
|
||||
const value: string = tags[key]
|
||||
if (value === undefined) {
|
||||
return undefined
|
||||
}
|
||||
if (value.startsWith(prefix) && value.endsWith(postfix)) {
|
||||
return value.substring(prefix.length, value.length - postfix.length).trim()
|
||||
}
|
||||
return value
|
||||
}) // This mapping will absorb all other changes to tags in order to prevent regeneration
|
||||
.map(
|
||||
(ohtext) => {
|
||||
if (ohtext === undefined) {
|
||||
return new FixedUiElement(
|
||||
"No opening hours defined with key " + key
|
||||
).SetClass("alert")
|
||||
}
|
||||
try {
|
||||
return OpeningHoursVisualization.CreateFullVisualisation(
|
||||
OH.CreateOhObject(<any>tags.data, ohtext)
|
||||
)
|
||||
} catch (e) {
|
||||
console.warn(e, e.stack)
|
||||
return new Combine([
|
||||
Translations.t.general.opening_hours.error_loading,
|
||||
new Toggle(
|
||||
new FixedUiElement(e).SetClass("subtle"),
|
||||
undefined,
|
||||
state?.osmConnection?.userDetails.map(
|
||||
(userdetails) =>
|
||||
userdetails.csCount >=
|
||||
Constants.userJourney.tagsVisibleAndWikiLinked
|
||||
)
|
||||
),
|
||||
])
|
||||
}
|
||||
},
|
||||
[country]
|
||||
)
|
||||
openingHoursStore.map((opening_hours_obj) => {
|
||||
if (opening_hours_obj === undefined) {
|
||||
return new FixedUiElement("No opening hours defined with key " + key).SetClass(
|
||||
"alert"
|
||||
)
|
||||
}
|
||||
|
||||
if (opening_hours_obj === "error") {
|
||||
return Translations.t.general.opening_hours.error_loading
|
||||
}
|
||||
return OpeningHoursVisualization.CreateFullVisualisation(opening_hours_obj)
|
||||
})
|
||||
)
|
||||
|
||||
super(
|
||||
|
|
|
|||
|
|
@ -161,7 +161,7 @@
|
|||
2. What do we want to add?
|
||||
3. Are all elements of this category visible? (i.e. there are no filters possibly hiding this, is the data still loading, ...) -->
|
||||
<LoginButton osmConnection={state.osmConnection} slot="not-logged-in">
|
||||
<Tr slot="message" t={Translations.t.general.add.pleaseLogin} />
|
||||
<Tr t={Translations.t.general.add.pleaseLogin} />
|
||||
</LoginButton>
|
||||
<div class="h-full w-full">
|
||||
{#if $zoom < Constants.minZoomLevelToAddNewPoint}
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@
|
|||
...(state?.layoutToUse?.layers?.map((l) => l.calculatedTags?.map((c) => c[0]) ?? []) ?? [])
|
||||
)
|
||||
|
||||
const allTags = tags.map((tags) => {
|
||||
const allTags = tags.mapD((tags) => {
|
||||
const parts: (string | BaseUIElement)[][] = []
|
||||
for (const key in tags) {
|
||||
let v = tags[key]
|
||||
|
|
|
|||
|
|
@ -31,14 +31,16 @@ export class ExportAsGpxViz implements SpecialVisualization {
|
|||
t.downloadFeatureAsGpx.SetClass("font-bold text-lg"),
|
||||
t.downloadGpxHelper.SetClass("subtle"),
|
||||
]).SetClass("flex flex-col")
|
||||
).onClick(() => {
|
||||
console.log("Exporting as GPX!")
|
||||
const tags = tagSource.data
|
||||
const title = layer.title?.GetRenderValue(tags)?.Subs(tags)?.txt ?? "gpx_track"
|
||||
const gpx = GeoOperations.toGpx(<Feature<LineString>>feature, title)
|
||||
Utils.offerContentsAsDownloadableFile(gpx, title + "_mapcomplete_export.gpx", {
|
||||
mimetype: "{gpx=application/gpx+xml}",
|
||||
)
|
||||
.SetClass("w-full")
|
||||
.onClick(() => {
|
||||
console.log("Exporting as GPX!")
|
||||
const tags = tagSource.data
|
||||
const title = layer.title?.GetRenderValue(tags)?.Subs(tags)?.txt ?? "gpx_track"
|
||||
const gpx = GeoOperations.toGpx(<Feature<LineString>>feature, title)
|
||||
Utils.offerContentsAsDownloadableFile(gpx, title + "_mapcomplete_export.gpx", {
|
||||
mimetype: "{gpx=application/gpx+xml}",
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -28,7 +28,6 @@
|
|||
|
||||
const t = Translations.t.image.nearby
|
||||
const c = [lon, lat]
|
||||
console.log(">>>", image)
|
||||
let attributedImage = new AttributedImage({
|
||||
url: image.thumbUrl ?? image.pictureUrl,
|
||||
provider: AllImageProviders.byName(image.provider),
|
||||
|
|
@ -45,7 +44,7 @@
|
|||
const url = image.osmTags[key]
|
||||
if (isLinked) {
|
||||
const action = new LinkImageAction(currentTags.id, key, url, tags, {
|
||||
theme: state.layout.id,
|
||||
theme: tags.data._orig_theme ?? state.layout.id,
|
||||
changeType: "link-image",
|
||||
})
|
||||
state.changes.applyAction(action)
|
||||
|
|
@ -54,7 +53,7 @@
|
|||
const v = currentTags[k]
|
||||
if (v === url) {
|
||||
const action = new ChangeTagAction(currentTags.id, new Tag(k, ""), currentTags, {
|
||||
theme: state.layout.id,
|
||||
theme: tags.data._orig_theme ?? state.layout.id,
|
||||
changeType: "remove-image",
|
||||
})
|
||||
state.changes.applyAction(action)
|
||||
|
|
|
|||
48
src/UI/Popup/MarkAsFavourite.svelte
Normal file
48
src/UI/Popup/MarkAsFavourite.svelte
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
<script lang="ts">
|
||||
import type { SpecialVisualizationState } from "../SpecialVisualization";
|
||||
import { HeartIcon as HeartSolidIcon } from "@babeard/svelte-heroicons/solid";
|
||||
import { HeartIcon as HeartOutlineIcon } from "@babeard/svelte-heroicons/outline";
|
||||
import Tr from "../Base/Tr.svelte";
|
||||
import Translations from "../i18n/Translations";
|
||||
import LoginToggle from "../Base/LoginToggle.svelte";
|
||||
import type { Feature } from "geojson";
|
||||
import LayerConfig from "../../Models/ThemeConfig/LayerConfig";
|
||||
|
||||
/**
|
||||
* A full-blown 'mark as favourite'-button
|
||||
*/
|
||||
export let state: SpecialVisualizationState;
|
||||
export let feature: Feature
|
||||
export let tags: Record<string, string>;
|
||||
export let layer: LayerConfig
|
||||
let isFavourite = tags?.map(tags => tags._favourite === "yes");
|
||||
const t = Translations.t.favouritePoi;
|
||||
|
||||
function markFavourite(isFavourite: boolean) {
|
||||
state.favourites.markAsFavourite(feature, layer.id, state.layout.id, tags, isFavourite)
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<LoginToggle ignoreLoading={true} {state}>
|
||||
{#if $isFavourite}
|
||||
<div class="flex h-fit items-start">
|
||||
<HeartSolidIcon class="w-16 shrink-0 mr-2" on:click={() => markFavourite(false)} />
|
||||
<div class="flex flex-col w-full">
|
||||
<button class="flex flex-col items-start" on:click={() => markFavourite(false)}>
|
||||
<Tr t={t.button.unmark} />
|
||||
<Tr cls="normal-font subtle" t={t.button.unmarkNotDeleted}/>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<Tr cls="font-bold thanks m-2 p-2 block" t={t.button.isFavourite} />
|
||||
{:else}
|
||||
<div class="flex items-start">
|
||||
<HeartOutlineIcon class="w-16 shrink-0 mr-2" on:click={() => markFavourite(true)} />
|
||||
<button class="flex w-full flex-col items-start" on:click={() => markFavourite(true)}>
|
||||
<Tr t={t.button.markAsFavouriteTitle} />
|
||||
<Tr cls="normal-font subtle" t={t.button.markDescription}/>
|
||||
</button>
|
||||
</div>
|
||||
{/if}
|
||||
</LoginToggle>
|
||||
36
src/UI/Popup/MarkAsFavouriteMini.svelte
Normal file
36
src/UI/Popup/MarkAsFavouriteMini.svelte
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
<script lang="ts">
|
||||
import type { SpecialVisualizationState } from "../SpecialVisualization";
|
||||
import { HeartIcon as HeartSolidIcon } from "@babeard/svelte-heroicons/solid";
|
||||
import { HeartIcon as HeartOutlineIcon } from "@babeard/svelte-heroicons/outline";
|
||||
import Translations from "../i18n/Translations";
|
||||
import LoginToggle from "../Base/LoginToggle.svelte";
|
||||
import type { Feature } from "geojson";
|
||||
import LayerConfig from "../../Models/ThemeConfig/LayerConfig";
|
||||
|
||||
/**
|
||||
* A small 'mark as favourite'-button to serve as title-icon
|
||||
*/
|
||||
export let state: SpecialVisualizationState;
|
||||
export let feature: Feature;
|
||||
export let tags: Record<string, string>;
|
||||
export let layer: LayerConfig;
|
||||
let isFavourite = tags?.map(tags => tags._favourite === "yes");
|
||||
const t = Translations.t.favouritePoi;
|
||||
|
||||
function markFavourite(isFavourite: boolean) {
|
||||
state.favourites.markAsFavourite(feature, layer.id, state.layout.id, tags, isFavourite);
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<LoginToggle ignoreLoading={true} {state}>
|
||||
{#if $isFavourite}
|
||||
<button class="p-0 m-0 h-8 w-8" on:click={() => markFavourite(false)}>
|
||||
<HeartSolidIcon/>
|
||||
</button>
|
||||
{:else}
|
||||
<button class="p-0 m-0 h-8 w-8 no-image-background" on:click={() => markFavourite(true)} >
|
||||
<HeartOutlineIcon/>
|
||||
</button>
|
||||
{/if}
|
||||
</LoginToggle>
|
||||
|
|
@ -3,16 +3,15 @@
|
|||
* Shows all questions for which the answers are unknown.
|
||||
* The questions can either be shown all at once or one at a time (in which case they can be skipped)
|
||||
*/
|
||||
import TagRenderingConfig from "../../../Models/ThemeConfig/TagRenderingConfig"
|
||||
import { UIEventSource } from "../../../Logic/UIEventSource"
|
||||
import type { Feature } from "geojson"
|
||||
import type { SpecialVisualizationState } from "../../SpecialVisualization"
|
||||
import LayerConfig from "../../../Models/ThemeConfig/LayerConfig"
|
||||
import If from "../../Base/If.svelte"
|
||||
import TagRenderingQuestion from "./TagRenderingQuestion.svelte"
|
||||
import Tr from "../../Base/Tr.svelte"
|
||||
import Translations from "../../i18n/Translations.js"
|
||||
import { Utils } from "../../../Utils"
|
||||
import TagRenderingConfig from "../../../Models/ThemeConfig/TagRenderingConfig";
|
||||
import { UIEventSource } from "../../../Logic/UIEventSource";
|
||||
import type { Feature } from "geojson";
|
||||
import type { SpecialVisualizationState } from "../../SpecialVisualization";
|
||||
import LayerConfig from "../../../Models/ThemeConfig/LayerConfig";
|
||||
import TagRenderingQuestion from "./TagRenderingQuestion.svelte";
|
||||
import Tr from "../../Base/Tr.svelte";
|
||||
import Translations from "../../i18n/Translations.js";
|
||||
import { Utils } from "../../../Utils";
|
||||
|
||||
export let layer: LayerConfig
|
||||
export let tags: UIEventSource<Record<string, string>>
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@
|
|||
onDestroy(
|
||||
tags.addCallbackAndRun((tags) => {
|
||||
_tags = tags
|
||||
console.log("Getting render value for", _tags,config)
|
||||
trs = Utils.NoNull(config?.GetRenderValues(_tags))
|
||||
})
|
||||
)
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@
|
|||
import Translations from "../../i18n/Translations.js"
|
||||
import LayerConfig from "../../../Models/ThemeConfig/LayerConfig"
|
||||
import { Utils } from "../../../Utils"
|
||||
|
||||
import { twMerge } from "tailwind-merge"
|
||||
export let config: TagRenderingConfig
|
||||
export let tags: UIEventSource<Record<string, string>>
|
||||
export let selectedElement: Feature | undefined
|
||||
|
|
@ -71,7 +71,7 @@
|
|||
}
|
||||
</script>
|
||||
|
||||
<div bind:this={htmlElem} class={clss}>
|
||||
<div bind:this={htmlElem} class={twMerge(clss, "tr-"+config.id)}>
|
||||
{#if config.question && (!editingEnabled || $editingEnabled)}
|
||||
{#if editMode}
|
||||
<TagRenderingQuestion {config} {tags} {selectedElement} {state} {layer}>
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@
|
|||
import { UIEventSource } from "../../../Logic/UIEventSource"
|
||||
import LayerConfig from "../../../Models/ThemeConfig/LayerConfig"
|
||||
import { twJoin } from "tailwind-merge"
|
||||
import Icon from "../../Map/Icon.svelte";
|
||||
|
||||
export let selectedElement: Feature
|
||||
export let tags: UIEventSource<Record<string, string>>
|
||||
|
|
@ -27,13 +28,8 @@
|
|||
</script>
|
||||
|
||||
{#if mapping.icon !== undefined}
|
||||
<div class="inline-flex items-center">
|
||||
<img
|
||||
class={twJoin(`mapping-icon-${mapping.iconClass}`, "mr-1")}
|
||||
src={mapping.icon}
|
||||
aria-hidden="true"
|
||||
alt=""
|
||||
/>
|
||||
<div class="inline-flex">
|
||||
<Icon icon={mapping.icon} clss={twJoin(`mapping-icon-${mapping.iconClass}`, "mr-1")}/>
|
||||
<SpecialTranslation t={mapping.then} {tags} {state} {layer} feature={selectedElement} />
|
||||
</div>
|
||||
{:else if mapping.then !== undefined}
|
||||
|
|
|
|||
|
|
@ -1,188 +1,210 @@
|
|||
<script lang="ts">
|
||||
import { ImmutableStore, UIEventSource } from "../../../Logic/UIEventSource"
|
||||
import type { SpecialVisualizationState } from "../../SpecialVisualization"
|
||||
import Tr from "../../Base/Tr.svelte"
|
||||
import type { Feature } from "geojson"
|
||||
import type { Mapping } from "../../../Models/ThemeConfig/TagRenderingConfig"
|
||||
import TagRenderingConfig from "../../../Models/ThemeConfig/TagRenderingConfig"
|
||||
import { TagsFilter } from "../../../Logic/Tags/TagsFilter"
|
||||
import FreeformInput from "./FreeformInput.svelte"
|
||||
import Translations from "../../i18n/Translations.js"
|
||||
import ChangeTagAction from "../../../Logic/Osm/Actions/ChangeTagAction"
|
||||
import { createEventDispatcher, onDestroy } from "svelte"
|
||||
import LayerConfig from "../../../Models/ThemeConfig/LayerConfig"
|
||||
import SpecialTranslation from "./SpecialTranslation.svelte"
|
||||
import TagHint from "../TagHint.svelte"
|
||||
import LoginToggle from "../../Base/LoginToggle.svelte"
|
||||
import SubtleButton from "../../Base/SubtleButton.svelte"
|
||||
import Loading from "../../Base/Loading.svelte"
|
||||
import TagRenderingMappingInput from "./TagRenderingMappingInput.svelte"
|
||||
import { Translation } from "../../i18n/Translation"
|
||||
import Constants from "../../../Models/Constants"
|
||||
import { Unit } from "../../../Models/Unit"
|
||||
import UserRelatedState from "../../../Logic/State/UserRelatedState"
|
||||
import { twJoin } from "tailwind-merge"
|
||||
import { TagUtils } from "../../../Logic/Tags/TagUtils"
|
||||
import Search from "../../../assets/svg/Search.svelte"
|
||||
import Login from "../../../assets/svg/Login.svelte"
|
||||
import { ImmutableStore, UIEventSource } from "../../../Logic/UIEventSource";
|
||||
import type { SpecialVisualizationState } from "../../SpecialVisualization";
|
||||
import Tr from "../../Base/Tr.svelte";
|
||||
import type { Feature } from "geojson";
|
||||
import type { Mapping } from "../../../Models/ThemeConfig/TagRenderingConfig";
|
||||
import TagRenderingConfig from "../../../Models/ThemeConfig/TagRenderingConfig";
|
||||
import { TagsFilter } from "../../../Logic/Tags/TagsFilter";
|
||||
import FreeformInput from "./FreeformInput.svelte";
|
||||
import Translations from "../../i18n/Translations.js";
|
||||
import ChangeTagAction from "../../../Logic/Osm/Actions/ChangeTagAction";
|
||||
import { createEventDispatcher, onDestroy } from "svelte";
|
||||
import LayerConfig from "../../../Models/ThemeConfig/LayerConfig";
|
||||
import SpecialTranslation from "./SpecialTranslation.svelte";
|
||||
import TagHint from "../TagHint.svelte";
|
||||
import LoginToggle from "../../Base/LoginToggle.svelte";
|
||||
import SubtleButton from "../../Base/SubtleButton.svelte";
|
||||
import Loading from "../../Base/Loading.svelte";
|
||||
import TagRenderingMappingInput from "./TagRenderingMappingInput.svelte";
|
||||
import { Translation } from "../../i18n/Translation";
|
||||
import Constants from "../../../Models/Constants";
|
||||
import { Unit } from "../../../Models/Unit";
|
||||
import UserRelatedState from "../../../Logic/State/UserRelatedState";
|
||||
import { twJoin } from "tailwind-merge";
|
||||
import { TagUtils } from "../../../Logic/Tags/TagUtils";
|
||||
import Search from "../../../assets/svg/Search.svelte";
|
||||
import Login from "../../../assets/svg/Login.svelte";
|
||||
|
||||
export let config: TagRenderingConfig
|
||||
export let tags: UIEventSource<Record<string, string>>
|
||||
export let selectedElement: Feature
|
||||
export let state: SpecialVisualizationState
|
||||
export let layer: LayerConfig | undefined
|
||||
export let config: TagRenderingConfig;
|
||||
export let tags: UIEventSource<Record<string, string>>;
|
||||
export let selectedElement: Feature;
|
||||
export let state: SpecialVisualizationState;
|
||||
export let layer: LayerConfig | undefined;
|
||||
export let selectedTags: TagsFilter = undefined;
|
||||
|
||||
let feedback: UIEventSource<Translation> = new UIEventSource<Translation>(undefined)
|
||||
let feedback: UIEventSource<Translation> = new UIEventSource<Translation>(undefined);
|
||||
|
||||
let unit: Unit = layer?.units?.find((unit) => unit.appliesToKeys.has(config.freeform?.key))
|
||||
let unit: Unit = layer?.units?.find((unit) => unit.appliesToKeys.has(config.freeform?.key));
|
||||
|
||||
// Will be bound if a freeform is available
|
||||
let freeformInput = new UIEventSource<string>(tags?.[config.freeform?.key])
|
||||
let selectedMapping: number = undefined
|
||||
let checkedMappings: boolean[]
|
||||
// Will be bound if a freeform is available
|
||||
let freeformInput = new UIEventSource<string>(tags?.[config.freeform?.key]);
|
||||
let selectedMapping: number = undefined;
|
||||
let checkedMappings: boolean[];
|
||||
|
||||
/**
|
||||
* Prepares and fills the checkedMappings
|
||||
*/
|
||||
function initialize(tgs: Record<string, string>, confg: TagRenderingConfig) {
|
||||
mappings = confg.mappings?.filter((m) => {
|
||||
if (typeof m.hideInAnswer === "boolean") {
|
||||
return !m.hideInAnswer
|
||||
}
|
||||
return !m.hideInAnswer.matchesProperties(tgs)
|
||||
let mappings: Mapping[] = config?.mappings;
|
||||
let searchTerm: UIEventSource<string> = new UIEventSource("");
|
||||
|
||||
let dispatch = createEventDispatcher<{
|
||||
saved: {
|
||||
config: TagRenderingConfig
|
||||
applied: TagsFilter
|
||||
}
|
||||
}>();
|
||||
|
||||
/**
|
||||
* Prepares and fills the checkedMappings
|
||||
*/
|
||||
function initialize(tgs: Record<string, string>, confg: TagRenderingConfig) {
|
||||
mappings = confg.mappings?.filter((m) => {
|
||||
if (typeof m.hideInAnswer === "boolean") {
|
||||
return !m.hideInAnswer;
|
||||
}
|
||||
return !m.hideInAnswer.matchesProperties(tgs);
|
||||
});
|
||||
// We received a new config -> reinit
|
||||
unit = layer?.units?.find((unit) => unit.appliesToKeys.has(config.freeform?.key));
|
||||
|
||||
if (
|
||||
confg.mappings?.length > 0 &&
|
||||
confg.multiAnswer &&
|
||||
(checkedMappings === undefined ||
|
||||
checkedMappings?.length < confg.mappings.length + (confg.freeform ? 1 : 0))
|
||||
) {
|
||||
const seenFreeforms = [];
|
||||
TagUtils.FlattenMultiAnswer();
|
||||
checkedMappings = [
|
||||
...confg.mappings.map((mapping) => {
|
||||
const matches = TagUtils.MatchesMultiAnswer(mapping.if, tgs);
|
||||
if (matches && confg.freeform) {
|
||||
const newProps = TagUtils.changeAsProperties(mapping.if.asChange());
|
||||
seenFreeforms.push(newProps[confg.freeform.key]);
|
||||
}
|
||||
return matches;
|
||||
})
|
||||
// We received a new config -> reinit
|
||||
unit = layer?.units?.find((unit) => unit.appliesToKeys.has(config.freeform?.key))
|
||||
];
|
||||
|
||||
if (
|
||||
confg.mappings?.length > 0 &&
|
||||
confg.multiAnswer &&
|
||||
(checkedMappings === undefined ||
|
||||
checkedMappings?.length < confg.mappings.length + (confg.freeform ? 1 : 0))
|
||||
) {
|
||||
const seenFreeforms = []
|
||||
TagUtils.FlattenMultiAnswer()
|
||||
checkedMappings = [
|
||||
...confg.mappings.map((mapping) => {
|
||||
const matches = TagUtils.MatchesMultiAnswer(mapping.if, tgs)
|
||||
if (matches && confg.freeform) {
|
||||
const newProps = TagUtils.changeAsProperties(mapping.if.asChange())
|
||||
seenFreeforms.push(newProps[confg.freeform.key])
|
||||
}
|
||||
return matches
|
||||
}),
|
||||
]
|
||||
|
||||
if (tgs !== undefined && confg.freeform) {
|
||||
const unseenFreeformValues = tgs[confg.freeform.key]?.split(";") ?? []
|
||||
for (const seenFreeform of seenFreeforms) {
|
||||
if (!seenFreeform) {
|
||||
continue
|
||||
}
|
||||
const index = unseenFreeformValues.indexOf(seenFreeform)
|
||||
if (index < 0) {
|
||||
continue
|
||||
}
|
||||
unseenFreeformValues.splice(index, 1)
|
||||
}
|
||||
// TODO this has _to much_ values
|
||||
freeformInput.setData(unseenFreeformValues.join(";"))
|
||||
checkedMappings.push(unseenFreeformValues.length > 0)
|
||||
}
|
||||
if (tgs !== undefined && confg.freeform) {
|
||||
const unseenFreeformValues = tgs[confg.freeform.key]?.split(";") ?? [];
|
||||
for (const seenFreeform of seenFreeforms) {
|
||||
if (!seenFreeform) {
|
||||
continue;
|
||||
}
|
||||
const index = unseenFreeformValues.indexOf(seenFreeform);
|
||||
if (index < 0) {
|
||||
continue;
|
||||
}
|
||||
unseenFreeformValues.splice(index, 1);
|
||||
}
|
||||
if (confg.freeform?.key) {
|
||||
if (!confg.multiAnswer) {
|
||||
// Somehow, setting multi-answer freeform values is broken if this is not set
|
||||
freeformInput.setData(tgs[confg.freeform.key])
|
||||
}
|
||||
// TODO this has _to much_ values
|
||||
freeformInput.setData(unseenFreeformValues.join(";"));
|
||||
checkedMappings.push(unseenFreeformValues.length > 0);
|
||||
}
|
||||
}
|
||||
if (confg.freeform?.key) {
|
||||
if (!confg.multiAnswer) {
|
||||
// Somehow, setting multi-answer freeform values is broken if this is not set
|
||||
freeformInput.setData(tgs[confg.freeform.key]);
|
||||
}
|
||||
|
||||
} else {
|
||||
freeformInput.setData(undefined);
|
||||
}
|
||||
feedback.setData(undefined);
|
||||
}
|
||||
|
||||
$: {
|
||||
// Even though 'config' is not declared as a store, Svelte uses it as one to update the component
|
||||
// We want to (re)-initialize whenever the 'tags' or 'config' change - but not when 'checkedConfig' changes
|
||||
initialize($tags, config);
|
||||
}
|
||||
|
||||
$: {
|
||||
try {
|
||||
selectedTags = config?.constructChangeSpecification(
|
||||
$freeformInput,
|
||||
selectedMapping,
|
||||
checkedMappings,
|
||||
tags.data
|
||||
);
|
||||
} catch (e) {
|
||||
console.error("Could not calculate changeSpecification:", e);
|
||||
selectedTags = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function onSave() {
|
||||
if (selectedTags === undefined) {
|
||||
console.log("SelectedTags is undefined, ignoring 'onSave'-event");
|
||||
return;
|
||||
}
|
||||
if (layer === undefined || (layer?.source === null && layer.id !== "favourite")) {
|
||||
/**
|
||||
* This is a special, priviliged layer.
|
||||
* We simply apply the tags onto the records
|
||||
*/
|
||||
const kv = selectedTags.asChange(tags.data);
|
||||
for (const { k, v } of kv) {
|
||||
if (v === undefined || v === "") {
|
||||
delete tags.data[k];
|
||||
} else {
|
||||
freeformInput.setData(undefined)
|
||||
freeformInput.setData(undefined);
|
||||
}
|
||||
feedback.setData(undefined)
|
||||
feedback.setData(undefined);
|
||||
}
|
||||
}
|
||||
|
||||
$: {
|
||||
// Even though 'config' is not declared as a store, Svelte uses it as one to update the component
|
||||
// We want to (re)-initialize whenever the 'tags' or 'config' change - but not when 'checkedConfig' changes
|
||||
initialize($tags, config)
|
||||
dispatch("saved", { config, applied: selectedTags });
|
||||
const change = new ChangeTagAction(tags.data.id, selectedTags, tags.data, {
|
||||
theme: tags.data["_orig_theme"] ?? state.layout.id,
|
||||
changeType: "answer"
|
||||
});
|
||||
freeformInput.setData(undefined);
|
||||
selectedMapping = undefined;
|
||||
selectedTags = undefined;
|
||||
|
||||
change
|
||||
.CreateChangeDescriptions()
|
||||
.then((changes) => state.changes.applyChanges(changes))
|
||||
.catch(console.error);
|
||||
}
|
||||
|
||||
function onInputKeypress(e: Event) {
|
||||
if (e.key === "Enter") {
|
||||
onSave();
|
||||
}
|
||||
export let selectedTags: TagsFilter = undefined
|
||||
}
|
||||
|
||||
let mappings: Mapping[] = config?.mappings
|
||||
let searchTerm: UIEventSource<string> = new UIEventSource("")
|
||||
|
||||
$: {
|
||||
try {
|
||||
selectedTags = config?.constructChangeSpecification(
|
||||
$freeformInput,
|
||||
selectedMapping,
|
||||
checkedMappings,
|
||||
tags.data,
|
||||
)
|
||||
} catch (e) {
|
||||
console.error("Could not calculate changeSpecification:", e)
|
||||
selectedTags = undefined
|
||||
}
|
||||
$: {
|
||||
try {
|
||||
selectedTags = config?.constructChangeSpecification(
|
||||
$freeformInput,
|
||||
selectedMapping,
|
||||
checkedMappings,
|
||||
tags.data
|
||||
);
|
||||
} catch (e) {
|
||||
console.error("Could not calculate changeSpecification:", e);
|
||||
selectedTags = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
let dispatch = createEventDispatcher<{
|
||||
saved: {
|
||||
config: TagRenderingConfig
|
||||
applied: TagsFilter
|
||||
}
|
||||
}>()
|
||||
|
||||
function onSave() {
|
||||
if (selectedTags === undefined) {
|
||||
console.log("SelectedTags is undefined, ignoring 'onSave'-event")
|
||||
return
|
||||
}
|
||||
if (layer === undefined || layer?.source === null) {
|
||||
/**
|
||||
* This is a special, priviliged layer.
|
||||
* We simply apply the tags onto the records
|
||||
*/
|
||||
const kv = selectedTags.asChange(tags.data)
|
||||
for (const { k, v } of kv) {
|
||||
if (v === undefined || v === "") {
|
||||
delete tags.data[k]
|
||||
} else {
|
||||
tags.data[k] = v
|
||||
}
|
||||
}
|
||||
tags.ping()
|
||||
return
|
||||
}
|
||||
|
||||
dispatch("saved", { config, applied: selectedTags })
|
||||
const change = new ChangeTagAction(tags.data.id, selectedTags, tags.data, {
|
||||
theme: state.layout.id,
|
||||
changeType: "answer",
|
||||
})
|
||||
freeformInput.setData(undefined)
|
||||
selectedMapping = undefined
|
||||
selectedTags = undefined
|
||||
|
||||
change
|
||||
.CreateChangeDescriptions()
|
||||
.then((changes) => state.changes.applyChanges(changes))
|
||||
.catch(console.error)
|
||||
}
|
||||
|
||||
let featureSwitchIsTesting = state?.featureSwitchIsTesting ?? new ImmutableStore(false)
|
||||
let featureSwitchIsDebugging =
|
||||
state?.featureSwitches?.featureSwitchIsDebugging ?? new ImmutableStore(false)
|
||||
let showTags = state?.userRelatedState?.showTags ?? new ImmutableStore(undefined)
|
||||
let numberOfCs = state?.osmConnection?.userDetails?.data?.csCount ?? 0
|
||||
let question = config.question
|
||||
$: question = config.question
|
||||
if (state?.osmConnection) {
|
||||
onDestroy(
|
||||
state.osmConnection?.userDetails?.addCallbackAndRun((ud) => {
|
||||
numberOfCs = ud.csCount
|
||||
}),
|
||||
)
|
||||
}
|
||||
let featureSwitchIsTesting = state?.featureSwitchIsTesting ?? new ImmutableStore(false);
|
||||
let featureSwitchIsDebugging =
|
||||
state?.featureSwitches?.featureSwitchIsDebugging ?? new ImmutableStore(false);
|
||||
let showTags = state?.userRelatedState?.showTags ?? new ImmutableStore(undefined);
|
||||
let numberOfCs = state?.osmConnection?.userDetails?.data?.csCount ?? 0;
|
||||
let question = config.question;
|
||||
$: question = config.question;
|
||||
if (state?.osmConnection) {
|
||||
onDestroy(
|
||||
state.osmConnection?.userDetails?.addCallbackAndRun((ud) => {
|
||||
numberOfCs = ud.csCount;
|
||||
})
|
||||
);
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if question !== undefined}
|
||||
|
|
@ -246,9 +268,8 @@
|
|||
bind:group={selectedMapping}
|
||||
name={"mappings-radio-" + config.id}
|
||||
value={i}
|
||||
on:keypress={(e) => {
|
||||
if (e.key === "Enter") onSave()
|
||||
}}
|
||||
on:keypress={e => onInputKeypress(e)}
|
||||
|
||||
/>
|
||||
</TagRenderingMappingInput>
|
||||
{/each}
|
||||
|
|
@ -259,6 +280,7 @@
|
|||
bind:group={selectedMapping}
|
||||
name={"mappings-radio-" + config.id}
|
||||
value={config.mappings?.length}
|
||||
on:keypress={e => onInputKeypress(e)}
|
||||
/>
|
||||
<FreeformInput
|
||||
{config}
|
||||
|
|
@ -290,6 +312,7 @@
|
|||
type="checkbox"
|
||||
name={"mappings-checkbox-" + config.id + "-" + i}
|
||||
bind:checked={checkedMappings[i]}
|
||||
on:keypress={e => onInputKeypress(e)}
|
||||
/>
|
||||
</TagRenderingMappingInput>
|
||||
{/each}
|
||||
|
|
@ -299,6 +322,7 @@
|
|||
type="checkbox"
|
||||
name={"mappings-checkbox-" + config.id + "-" + config.mappings?.length}
|
||||
bind:checked={checkedMappings[config.mappings.length]}
|
||||
on:keypress={e => onInputKeypress(e)}
|
||||
/>
|
||||
<FreeformInput
|
||||
{config}
|
||||
|
|
@ -307,7 +331,6 @@
|
|||
{unit}
|
||||
feature={selectedElement}
|
||||
value={freeformInput}
|
||||
on:selected={() => (checkedMappings[config.mappings.length] = true)}
|
||||
on:submit={onSave}
|
||||
/>
|
||||
</label>
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ import OsmObjectDownloader from "../Logic/Osm/OsmObjectDownloader"
|
|||
import { RasterLayerPolygon } from "../Models/RasterLayers"
|
||||
import { ImageUploadManager } from "../Logic/ImageProviders/ImageUploadManager"
|
||||
import { OsmTags } from "../Models/OsmFeature"
|
||||
import FavouritesFeatureSource from "../Logic/FeatureSource/Sources/FavouritesFeatureSource"
|
||||
|
||||
/**
|
||||
* The state needed to render a special Visualisation.
|
||||
|
|
@ -33,7 +34,6 @@ export interface SpecialVisualizationState {
|
|||
}
|
||||
|
||||
readonly indexedFeatures: IndexedFeatureSource
|
||||
|
||||
/**
|
||||
* Some features will create a new element that should be displayed.
|
||||
* These can be injected by appending them to this featuresource (and pinging it)
|
||||
|
|
@ -59,6 +59,8 @@ export interface SpecialVisualizationState {
|
|||
readonly selectedLayer: UIEventSource<LayerConfig>
|
||||
readonly selectedElementAndLayer: Store<{ feature: Feature; layer: LayerConfig }>
|
||||
|
||||
readonly favourites: FavouritesFeatureSource
|
||||
|
||||
/**
|
||||
* If data is currently being fetched from external sources
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -79,6 +79,9 @@ import ThemeViewState from "../Models/ThemeViewState"
|
|||
import LanguagePicker from "./InputElement/LanguagePicker.svelte"
|
||||
import LogoutButton from "./Base/LogoutButton.svelte"
|
||||
import OpenJosm from "./Base/OpenJosm.svelte"
|
||||
import MarkAsFavourite from "./Popup/MarkAsFavourite.svelte"
|
||||
import MarkAsFavouriteMini from "./Popup/MarkAsFavouriteMini.svelte"
|
||||
import NextChangeViz from "./OpeningHours/NextChangeViz.svelte"
|
||||
|
||||
class NearbyImageVis implements SpecialVisualization {
|
||||
// Class must be in SpecialVisualisations due to weird cyclical import that breaks the tests
|
||||
|
|
@ -532,6 +535,9 @@ export default class SpecialVisualizations {
|
|||
feature: Feature,
|
||||
layer: LayerConfig
|
||||
): BaseUIElement {
|
||||
if (!layer.deletion) {
|
||||
return undefined
|
||||
}
|
||||
return new SvelteUIElement(DeleteWizard, {
|
||||
tags: tagSource,
|
||||
deleteConfig: layer.deletion,
|
||||
|
|
@ -822,6 +828,46 @@ export default class SpecialVisualizations {
|
|||
)
|
||||
},
|
||||
},
|
||||
{
|
||||
funcName: "opening_hours_state",
|
||||
docs: "A small element, showing if the POI is currently open and when the next change is",
|
||||
args: [
|
||||
{
|
||||
name: "key",
|
||||
defaultValue: "opening_hours",
|
||||
doc: "The tagkey from which the opening hours are read.",
|
||||
},
|
||||
{
|
||||
name: "prefix",
|
||||
defaultValue: "",
|
||||
doc: "Remove this string from the start of the value before parsing. __Note: use `&LPARENs` to indicate `(` if needed__",
|
||||
},
|
||||
{
|
||||
name: "postfix",
|
||||
defaultValue: "",
|
||||
doc: "Remove this string from the end of the value before parsing. __Note: use `&RPARENs` to indicate `)` if needed__",
|
||||
},
|
||||
],
|
||||
needsUrls: [],
|
||||
constr(
|
||||
state: SpecialVisualizationState,
|
||||
tags: UIEventSource<Record<string, string>>,
|
||||
args: string[],
|
||||
feature: Feature,
|
||||
layer: LayerConfig
|
||||
): BaseUIElement {
|
||||
const keyToUse = args[0]
|
||||
const prefix = args[1]
|
||||
const postfix = args[2]
|
||||
return new SvelteUIElement(NextChangeViz, {
|
||||
state,
|
||||
keyToUse,
|
||||
tags,
|
||||
prefix,
|
||||
postfix,
|
||||
})
|
||||
},
|
||||
},
|
||||
{
|
||||
funcName: "canonical",
|
||||
needsUrls: [],
|
||||
|
|
@ -872,20 +918,22 @@ export default class SpecialVisualizations {
|
|||
t.downloadFeatureAsGeojson.SetClass("font-bold text-lg"),
|
||||
t.downloadGeoJsonHelper.SetClass("subtle"),
|
||||
]).SetClass("flex flex-col")
|
||||
).onClick(() => {
|
||||
console.log("Exporting as Geojson")
|
||||
const tags = tagSource.data
|
||||
const title =
|
||||
layer?.title?.GetRenderValue(tags)?.Subs(tags)?.txt ?? "geojson"
|
||||
const data = JSON.stringify(feature, null, " ")
|
||||
Utils.offerContentsAsDownloadableFile(
|
||||
data,
|
||||
title + "_mapcomplete_export.geojson",
|
||||
{
|
||||
mimetype: "application/vnd.geo+json",
|
||||
}
|
||||
)
|
||||
})
|
||||
)
|
||||
.onClick(() => {
|
||||
console.log("Exporting as Geojson")
|
||||
const tags = tagSource.data
|
||||
const title =
|
||||
layer?.title?.GetRenderValue(tags)?.Subs(tags)?.txt ?? "geojson"
|
||||
const data = JSON.stringify(feature, null, " ")
|
||||
Utils.offerContentsAsDownloadableFile(
|
||||
data,
|
||||
title + "_mapcomplete_export.geojson",
|
||||
{
|
||||
mimetype: "application/vnd.geo+json",
|
||||
}
|
||||
)
|
||||
})
|
||||
.SetClass("w-full")
|
||||
},
|
||||
},
|
||||
{
|
||||
|
|
@ -1482,7 +1530,7 @@ export default class SpecialVisualizations {
|
|||
const tags = (<ThemeViewState>(
|
||||
state
|
||||
)).geolocation.currentUserLocation.features.map(
|
||||
(features) => features[0].properties
|
||||
(features) => features[0]?.properties
|
||||
)
|
||||
return new SvelteUIElement(AllTagsPanel, {
|
||||
state,
|
||||
|
|
@ -1490,6 +1538,46 @@ export default class SpecialVisualizations {
|
|||
})
|
||||
},
|
||||
},
|
||||
{
|
||||
funcName: "favourite_status",
|
||||
needsUrls: [],
|
||||
docs: "A button that allows a (logged in) contributor to mark a location as a favourite location",
|
||||
args: [],
|
||||
constr(
|
||||
state: SpecialVisualizationState,
|
||||
tagSource: UIEventSource<Record<string, string>>,
|
||||
argument: string[],
|
||||
feature: Feature,
|
||||
layer: LayerConfig
|
||||
): BaseUIElement {
|
||||
return new SvelteUIElement(MarkAsFavourite, {
|
||||
tags: tagSource,
|
||||
state,
|
||||
layer,
|
||||
feature,
|
||||
})
|
||||
},
|
||||
},
|
||||
{
|
||||
funcName: "favourite_icon",
|
||||
needsUrls: [],
|
||||
docs: "A small button that allows a (logged in) contributor to mark a location as a favourite location, sized to fit a title-icon",
|
||||
args: [],
|
||||
constr(
|
||||
state: SpecialVisualizationState,
|
||||
tagSource: UIEventSource<Record<string, string>>,
|
||||
argument: string[],
|
||||
feature: Feature,
|
||||
layer: LayerConfig
|
||||
): BaseUIElement {
|
||||
return new SvelteUIElement(MarkAsFavouriteMini, {
|
||||
tags: tagSource,
|
||||
state,
|
||||
layer,
|
||||
feature,
|
||||
})
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
specialVisualizations.push(new AutoApplyButton(specialVisualizations))
|
||||
|
|
|
|||
|
|
@ -1,30 +1,30 @@
|
|||
<script lang="ts">
|
||||
import { UIEventSource } from "../../Logic/UIEventSource"
|
||||
import { OsmConnection } from "../../Logic/Osm/OsmConnection"
|
||||
import Marker from "../Map/Marker.svelte"
|
||||
import NextButton from "../Base/NextButton.svelte"
|
||||
import { AllKnownLayouts } from "../../Customizations/AllKnownLayouts"
|
||||
import { AllSharedLayers } from "../../Customizations/AllSharedLayers"
|
||||
import { createEventDispatcher } from "svelte"
|
||||
import { UIEventSource } from "../../Logic/UIEventSource";
|
||||
import { OsmConnection } from "../../Logic/Osm/OsmConnection";
|
||||
import Marker from "../Map/Marker.svelte";
|
||||
import NextButton from "../Base/NextButton.svelte";
|
||||
import { AllKnownLayouts } from "../../Customizations/AllKnownLayouts";
|
||||
import { AllSharedLayers } from "../../Customizations/AllSharedLayers";
|
||||
import { createEventDispatcher } from "svelte";
|
||||
|
||||
export let info: { id: string; owner: number }
|
||||
export let category: "layers" | "themes"
|
||||
export let osmConnection: OsmConnection
|
||||
export let info: { id: string; owner: number };
|
||||
export let category: "layers" | "themes";
|
||||
export let osmConnection: OsmConnection;
|
||||
const dispatch = createEventDispatcher<{ layerSelected: string }>();
|
||||
|
||||
let displayName = UIEventSource.FromPromise(
|
||||
osmConnection.getInformationAboutUser(info.owner),
|
||||
).mapD((response) => response.display_name)
|
||||
let displayName = UIEventSource.FromPromise(
|
||||
osmConnection.getInformationAboutUser(info.owner)
|
||||
).mapD((response) => response.display_name);
|
||||
let selfId = osmConnection.userDetails.mapD((ud) => ud.uid);
|
||||
|
||||
let selfId = osmConnection.userDetails.mapD((ud) => ud.uid)
|
||||
|
||||
function fetchIconDescription(layerId): any {
|
||||
if (category === "themes") {
|
||||
return AllKnownLayouts.allKnownLayouts.get(layerId).icon
|
||||
}
|
||||
return AllSharedLayers.getSharedLayersConfigs().get(layerId)?._layerIcon
|
||||
function fetchIconDescription(layerId): any {
|
||||
if (category === "themes") {
|
||||
return AllKnownLayouts.allKnownLayouts.get(layerId).icon;
|
||||
}
|
||||
return AllSharedLayers.getSharedLayersConfigs().get(layerId)?._layerIcon;
|
||||
}
|
||||
|
||||
const dispatch = createEventDispatcher<{ layerSelected: string }>()
|
||||
</script>
|
||||
|
||||
<NextButton clss="small" on:click={() => dispatch("layerSelected", info)}>
|
||||
|
|
|
|||
|
|
@ -1,90 +1,91 @@
|
|||
<script lang="ts">
|
||||
import { Store, UIEventSource } from "../Logic/UIEventSource"
|
||||
import { Map as MlMap } from "maplibre-gl"
|
||||
import MaplibreMap from "./Map/MaplibreMap.svelte"
|
||||
import FeatureSwitchState from "../Logic/State/FeatureSwitchState"
|
||||
import MapControlButton from "./Base/MapControlButton.svelte"
|
||||
import ToSvelte from "./Base/ToSvelte.svelte"
|
||||
import If from "./Base/If.svelte"
|
||||
import { GeolocationControl } from "./BigComponents/GeolocationControl"
|
||||
import type { Feature } from "geojson"
|
||||
import SelectedElementView from "./BigComponents/SelectedElementView.svelte"
|
||||
import LayerConfig from "../Models/ThemeConfig/LayerConfig"
|
||||
import Filterview from "./BigComponents/Filterview.svelte"
|
||||
import ThemeViewState from "../Models/ThemeViewState"
|
||||
import type { MapProperties } from "../Models/MapProperties"
|
||||
import Geosearch from "./BigComponents/Geosearch.svelte"
|
||||
import Translations from "./i18n/Translations"
|
||||
import { CogIcon, EyeIcon, MenuIcon, XCircleIcon } from "@rgossiaux/svelte-heroicons/solid"
|
||||
import Tr from "./Base/Tr.svelte"
|
||||
import CommunityIndexView from "./BigComponents/CommunityIndexView.svelte"
|
||||
import FloatOver from "./Base/FloatOver.svelte"
|
||||
import PrivacyPolicy from "./BigComponents/PrivacyPolicy"
|
||||
import Constants from "../Models/Constants"
|
||||
import TabbedGroup from "./Base/TabbedGroup.svelte"
|
||||
import UserRelatedState from "../Logic/State/UserRelatedState"
|
||||
import LoginToggle from "./Base/LoginToggle.svelte"
|
||||
import LoginButton from "./Base/LoginButton.svelte"
|
||||
import CopyrightPanel from "./BigComponents/CopyrightPanel"
|
||||
import DownloadPanel from "./DownloadFlow/DownloadPanel.svelte"
|
||||
import ModalRight from "./Base/ModalRight.svelte"
|
||||
import { Utils } from "../Utils"
|
||||
import Hotkeys from "./Base/Hotkeys"
|
||||
import { VariableUiElement } from "./Base/VariableUIElement"
|
||||
import SvelteUIElement from "./Base/SvelteUIElement"
|
||||
import OverlayToggle from "./BigComponents/OverlayToggle.svelte"
|
||||
import LevelSelector from "./BigComponents/LevelSelector.svelte"
|
||||
import ExtraLinkButton from "./BigComponents/ExtraLinkButton"
|
||||
import SelectedElementTitle from "./BigComponents/SelectedElementTitle.svelte"
|
||||
import ThemeIntroPanel from "./BigComponents/ThemeIntroPanel.svelte"
|
||||
import type { RasterLayerPolygon } from "../Models/RasterLayers"
|
||||
import { AvailableRasterLayers } from "../Models/RasterLayers"
|
||||
import RasterLayerOverview from "./Map/RasterLayerOverview.svelte"
|
||||
import IfHidden from "./Base/IfHidden.svelte"
|
||||
import { onDestroy } from "svelte"
|
||||
import MapillaryLink from "./BigComponents/MapillaryLink.svelte"
|
||||
import OpenIdEditor from "./BigComponents/OpenIdEditor.svelte"
|
||||
import OpenBackgroundSelectorButton from "./BigComponents/OpenBackgroundSelectorButton.svelte"
|
||||
import StateIndicator from "./BigComponents/StateIndicator.svelte"
|
||||
import ShareScreen from "./BigComponents/ShareScreen.svelte"
|
||||
import UploadingImageCounter from "./Image/UploadingImageCounter.svelte"
|
||||
import PendingChangesIndicator from "./BigComponents/PendingChangesIndicator.svelte"
|
||||
import Cross from "../assets/svg/Cross.svelte"
|
||||
import Summary from "./BigComponents/Summary.svelte"
|
||||
import Mastodon from "../assets/svg/Mastodon.svelte"
|
||||
import Bug from "../assets/svg/Bug.svelte"
|
||||
import Liberapay from "../assets/svg/Liberapay.svelte"
|
||||
import Min from "../assets/svg/Min.svelte"
|
||||
import Plus from "../assets/svg/Plus.svelte"
|
||||
import Filter from "../assets/svg/Filter.svelte"
|
||||
import Add from "../assets/svg/Add.svelte"
|
||||
import Statistics from "../assets/svg/Statistics.svelte"
|
||||
import Community from "../assets/svg/Community.svelte"
|
||||
import Download from "../assets/svg/Download.svelte"
|
||||
import Share from "../assets/svg/Share.svelte"
|
||||
import LanguagePicker from "./InputElement/LanguagePicker.svelte"
|
||||
import OpenJosm from "./Base/OpenJosm.svelte"
|
||||
import { Store, UIEventSource } from "../Logic/UIEventSource";
|
||||
import { Map as MlMap } from "maplibre-gl";
|
||||
import MaplibreMap from "./Map/MaplibreMap.svelte";
|
||||
import FeatureSwitchState from "../Logic/State/FeatureSwitchState";
|
||||
import MapControlButton from "./Base/MapControlButton.svelte";
|
||||
import ToSvelte from "./Base/ToSvelte.svelte";
|
||||
import If from "./Base/If.svelte";
|
||||
import { GeolocationControl } from "./BigComponents/GeolocationControl";
|
||||
import type { Feature } from "geojson";
|
||||
import SelectedElementView from "./BigComponents/SelectedElementView.svelte";
|
||||
import LayerConfig from "../Models/ThemeConfig/LayerConfig";
|
||||
import Filterview from "./BigComponents/Filterview.svelte";
|
||||
import ThemeViewState from "../Models/ThemeViewState";
|
||||
import type { MapProperties } from "../Models/MapProperties";
|
||||
import Geosearch from "./BigComponents/Geosearch.svelte";
|
||||
import Translations from "./i18n/Translations";
|
||||
import { CogIcon, EyeIcon, HeartIcon, MenuIcon, XCircleIcon } from "@rgossiaux/svelte-heroicons/solid";
|
||||
import Tr from "./Base/Tr.svelte";
|
||||
import CommunityIndexView from "./BigComponents/CommunityIndexView.svelte";
|
||||
import FloatOver from "./Base/FloatOver.svelte";
|
||||
import PrivacyPolicy from "./BigComponents/PrivacyPolicy";
|
||||
import Constants from "../Models/Constants";
|
||||
import TabbedGroup from "./Base/TabbedGroup.svelte";
|
||||
import UserRelatedState from "../Logic/State/UserRelatedState";
|
||||
import LoginToggle from "./Base/LoginToggle.svelte";
|
||||
import LoginButton from "./Base/LoginButton.svelte";
|
||||
import CopyrightPanel from "./BigComponents/CopyrightPanel";
|
||||
import DownloadPanel from "./DownloadFlow/DownloadPanel.svelte";
|
||||
import ModalRight from "./Base/ModalRight.svelte";
|
||||
import { Utils } from "../Utils";
|
||||
import Hotkeys from "./Base/Hotkeys";
|
||||
import { VariableUiElement } from "./Base/VariableUIElement";
|
||||
import SvelteUIElement from "./Base/SvelteUIElement";
|
||||
import OverlayToggle from "./BigComponents/OverlayToggle.svelte";
|
||||
import LevelSelector from "./BigComponents/LevelSelector.svelte";
|
||||
import ExtraLinkButton from "./BigComponents/ExtraLinkButton";
|
||||
import SelectedElementTitle from "./BigComponents/SelectedElementTitle.svelte";
|
||||
import ThemeIntroPanel from "./BigComponents/ThemeIntroPanel.svelte";
|
||||
import type { RasterLayerPolygon } from "../Models/RasterLayers";
|
||||
import { AvailableRasterLayers } from "../Models/RasterLayers";
|
||||
import RasterLayerOverview from "./Map/RasterLayerOverview.svelte";
|
||||
import IfHidden from "./Base/IfHidden.svelte";
|
||||
import { onDestroy } from "svelte";
|
||||
import MapillaryLink from "./BigComponents/MapillaryLink.svelte";
|
||||
import OpenIdEditor from "./BigComponents/OpenIdEditor.svelte";
|
||||
import OpenBackgroundSelectorButton from "./BigComponents/OpenBackgroundSelectorButton.svelte";
|
||||
import StateIndicator from "./BigComponents/StateIndicator.svelte";
|
||||
import ShareScreen from "./BigComponents/ShareScreen.svelte";
|
||||
import UploadingImageCounter from "./Image/UploadingImageCounter.svelte";
|
||||
import PendingChangesIndicator from "./BigComponents/PendingChangesIndicator.svelte";
|
||||
import Cross from "../assets/svg/Cross.svelte";
|
||||
import Summary from "./BigComponents/Summary.svelte";
|
||||
import LanguagePicker from "./InputElement/LanguagePicker.svelte";
|
||||
import Mastodon from "../assets/svg/Mastodon.svelte";
|
||||
import Bug from "../assets/svg/Bug.svelte";
|
||||
import Liberapay from "../assets/svg/Liberapay.svelte";
|
||||
import OpenJosm from "./Base/OpenJosm.svelte";
|
||||
import Min from "../assets/svg/Min.svelte";
|
||||
import Plus from "../assets/svg/Plus.svelte";
|
||||
import Filter from "../assets/svg/Filter.svelte";
|
||||
import Add from "../assets/svg/Add.svelte";
|
||||
import Statistics from "../assets/svg/Statistics.svelte";
|
||||
import Community from "../assets/svg/Community.svelte";
|
||||
import Download from "../assets/svg/Download.svelte";
|
||||
import Share from "../assets/svg/Share.svelte";
|
||||
import Favourites from "./Favourites/Favourites.svelte";
|
||||
|
||||
export let state: ThemeViewState
|
||||
export let state: ThemeViewState
|
||||
let layout = state.layout
|
||||
|
||||
let maplibremap: UIEventSource<MlMap> = state.map
|
||||
let selectedElement: UIEventSource<Feature> = state.selectedElement
|
||||
let selectedLayer: UIEventSource<LayerConfig> = state.selectedLayer
|
||||
|
||||
let currentZoom = state.mapProperties.zoom
|
||||
let showCrosshair = state.userRelatedState.showCrosshair
|
||||
let arrowKeysWereUsed = state.mapProperties.lastKeyNavigation
|
||||
let centerFeatures = state.closestFeatures.features
|
||||
const selectedElementView = selectedElement.map(
|
||||
(selectedElement) => {
|
||||
// Svelte doesn't properly reload some of the legacy UI-elements
|
||||
// As such, we _reconstruct_ the selectedElementView every time a new feature is selected
|
||||
// This is a bit wasteful, but until everything is a svelte-component, this should do the trick
|
||||
const layer = selectedLayer.data
|
||||
if (selectedElement === undefined || layer === undefined) {
|
||||
return undefined
|
||||
}
|
||||
let currentZoom = state.mapProperties.zoom;
|
||||
let showCrosshair = state.userRelatedState.showCrosshair;
|
||||
let arrowKeysWereUsed = state.mapProperties.lastKeyNavigation;
|
||||
let centerFeatures = state.closestFeatures.features;
|
||||
const selectedElementView = selectedElement.map(
|
||||
(selectedElement) => {
|
||||
// Svelte doesn't properly reload some of the legacy UI-elements
|
||||
// As such, we _reconstruct_ the selectedElementView every time a new feature is selected
|
||||
// This is a bit wasteful, but until everything is a svelte-component, this should do the trick
|
||||
const layer = selectedLayer.data;
|
||||
if (selectedElement === undefined || layer === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (!(layer.tagRenderings?.length > 0) || layer.title === undefined) {
|
||||
return undefined
|
||||
|
|
@ -230,17 +231,15 @@
|
|||
</a>
|
||||
</div>
|
||||
</div>
|
||||
{#if $arrowKeysWereUsed !== undefined}
|
||||
{#if $centerFeatures.length > 0}
|
||||
<div class="interactive pointer-events-auto p-1">
|
||||
{#each $centerFeatures as feat, i (feat.properties.id)}
|
||||
<div class="flex">
|
||||
<b>{i + 1}.</b>
|
||||
<Summary {state} feature={feat} />
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if $arrowKeysWereUsed !== undefined && $centerFeatures?.length > 0}
|
||||
<div class="pointer-events-auto interactive p-1">
|
||||
{#each $centerFeatures as feat, i (feat.properties.id)}
|
||||
<div class="flex">
|
||||
<b>{i+1}.</b><Summary {state} feature={feat}/>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
<div class="flex flex-col items-end">
|
||||
<!-- bottom right elements -->
|
||||
|
|
@ -495,22 +494,31 @@
|
|||
</div>
|
||||
|
||||
<div class="flex" slot="title2">
|
||||
<HeartIcon class="h-6 w-6" />
|
||||
<Tr t={Translations.t.favouritePoi.tab}/>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col m-2" slot="content2">
|
||||
<h3> <Tr t={Translations.t.favouritePoi.title}/></h3>
|
||||
<Favourites {state}/>
|
||||
</div>
|
||||
<div class="flex" slot="title3">
|
||||
<Community class="h-6 w-6" />
|
||||
<Tr t={Translations.t.communityIndex.title} />
|
||||
</div>
|
||||
<div class="m-2" slot="content2">
|
||||
<div class="m-2" slot="content3">
|
||||
<CommunityIndexView location={state.mapProperties.location} />
|
||||
</div>
|
||||
<div class="flex" slot="title3">
|
||||
<div class="flex" slot="title4">
|
||||
<EyeIcon class="w-6" />
|
||||
<Tr t={Translations.t.privacy.title} />
|
||||
</div>
|
||||
<div class="m-2" slot="content3">
|
||||
<div class="m-2" slot="content4">
|
||||
<ToSvelte construct={() => new PrivacyPolicy()} />
|
||||
</div>
|
||||
|
||||
<Tr slot="title4" t={Translations.t.advanced.title} />
|
||||
<div class="m-2 flex flex-col" slot="content4">
|
||||
<Tr slot="title5" t={Translations.t.advanced.title} />
|
||||
<div class="m-2 flex flex-col" slot="content5">
|
||||
<If condition={featureSwitches.featureSwitchEnableLogin}>
|
||||
<OpenIdEditor mapProperties={state.mapProperties} />
|
||||
<OpenJosm {state} />
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue