forked from MapComplete/MapComplete
Styling: style most buttons
This commit is contained in:
parent
e04430b428
commit
83f3662b9a
46 changed files with 720 additions and 671 deletions
16
UI/Base/BackButton.svelte
Normal file
16
UI/Base/BackButton.svelte
Normal file
|
@ -0,0 +1,16 @@
|
|||
<script lang="ts">
|
||||
/**
|
||||
* Wrapper around 'subtleButton' with an arrow pointing to the right
|
||||
*/
|
||||
import SubtleButton from "./SubtleButton.svelte";
|
||||
import {ChevronLeftIcon} from "@rgossiaux/svelte-heroicons/solid";
|
||||
import {createEventDispatcher} from "svelte";
|
||||
|
||||
const dispatch = createEventDispatcher<{ click }>()
|
||||
export let clss = ""
|
||||
</script>
|
||||
|
||||
<SubtleButton on:click={() => dispatch("click")} options={{extraClasses:clss+ " flex items-center"}}>
|
||||
<ChevronLeftIcon class="w-12 h-12" slot="image"/>
|
||||
<slot name="message" slot="message"/>
|
||||
</SubtleButton>
|
|
@ -11,9 +11,10 @@
|
|||
}
|
||||
}
|
||||
|
||||
export let clss = ""
|
||||
</script>
|
||||
|
||||
{#if src !== undefined}
|
||||
<span bind:this={htmlElem}></span>
|
||||
<span bind:this={htmlElem} class={clss}></span>
|
||||
{/if}
|
||||
|
||||
|
|
|
@ -1,15 +1,17 @@
|
|||
<script lang="ts">
|
||||
import { OsmConnection } from "../../Logic/Osm/OsmConnection";
|
||||
import SubtleButton from "./SubtleButton.svelte";
|
||||
import Translations from "../i18n/Translations.js";
|
||||
import Tr from "./Tr.svelte";
|
||||
import {OsmConnection} from "../../Logic/Osm/OsmConnection";
|
||||
import Translations from "../i18n/Translations.js";
|
||||
import Tr from "./Tr.svelte";
|
||||
import ToSvelte from "./ToSvelte.svelte";
|
||||
import Svg from "../../Svg";
|
||||
|
||||
export let osmConnection: OsmConnection
|
||||
export let osmConnection: OsmConnection
|
||||
export let clss = ""
|
||||
</script>
|
||||
|
||||
<SubtleButton on:click={() => osmConnection.AttemptLogin()}>
|
||||
<img slot="image" src="./assets/svg/login.svg" class="w-8"/>
|
||||
<slot name="message" slot="message">
|
||||
<Tr t={Translations.t.general.loginWithOpenStreetMap}/>
|
||||
</slot>
|
||||
</SubtleButton>
|
||||
<button class={clss} on:click={() => osmConnection.AttemptLogin()}>
|
||||
<ToSvelte construct={Svg.login_svg().SetClass("w-12 m-1")}/>
|
||||
<slot name="message">
|
||||
<Tr t={Translations.t.general.loginWithOpenStreetMap}/>
|
||||
</slot>
|
||||
</button>
|
||||
|
|
|
@ -8,6 +8,6 @@
|
|||
</script>
|
||||
|
||||
|
||||
<button on:click={e => dispatch("click", e)} class="secondary rounded-full h-fit w-fit m-0.5 md:m-1 p-0.5 sm:p-1">
|
||||
<button on:click={e => dispatch("click", e)} class="rounded-full h-fit w-fit m-0.5 md:m-1 p-0.5 sm:p-1">
|
||||
<slot/>
|
||||
</button>
|
||||
|
|
20
UI/Base/NextButton.svelte
Normal file
20
UI/Base/NextButton.svelte
Normal file
|
@ -0,0 +1,20 @@
|
|||
<script lang="ts">
|
||||
/**
|
||||
* Wrapper around 'subtleButton' with an arrow pointing to the right
|
||||
*/
|
||||
import SubtleButton from "./SubtleButton.svelte";
|
||||
import {ChevronRightIcon} from "@rgossiaux/svelte-heroicons/solid";
|
||||
import {createEventDispatcher} from "svelte";
|
||||
|
||||
const dispatch = createEventDispatcher<{ click }>()
|
||||
|
||||
export let clss : string= ""
|
||||
</script>
|
||||
|
||||
<SubtleButton on:click={() => dispatch("click")} options={{extraClasses: clss+" flex items-center"}}>
|
||||
<slot name="image" slot="image"/>
|
||||
<div class="w-full flex justify-between items-center" slot="message">
|
||||
<slot/>
|
||||
<ChevronRightIcon class="w-12 h-12"/>
|
||||
</div>
|
||||
</SubtleButton>
|
|
@ -12,11 +12,10 @@
|
|||
|
||||
let imgClasses = "block justify-center shrink-0 mr-4 " + (options?.imgSize ?? "h-11 w-11");
|
||||
const dispatch = createEventDispatcher<{click}>()
|
||||
console.log("Slots:", $$slots)
|
||||
</script>
|
||||
|
||||
<button
|
||||
class={(options.extraClasses??"") + 'flex hover:shadow-xl transition-[color,background-color,box-shadow] hover:bg-unsubtle cursor-pointer'}
|
||||
class={(options.extraClasses??"") + ' secondary no-image-background'}
|
||||
target={options?.newTab ? "_blank" : ""}
|
||||
on:click={(e) => dispatch("click", e)}
|
||||
>
|
||||
|
@ -30,16 +29,3 @@
|
|||
|
||||
<slot name="message"/>
|
||||
</button>
|
||||
|
||||
<style lang="scss">
|
||||
span,
|
||||
a {
|
||||
@apply flex p-3 my-2 py-4 rounded-lg shrink-0;
|
||||
@apply items-center w-full no-underline;
|
||||
@apply bg-subtle text-black;
|
||||
|
||||
:global(span) {
|
||||
@apply block text-ellipsis;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -22,7 +22,7 @@
|
|||
<div class="interactive flex items-center justify-between sticky top-0">
|
||||
<TabList class="flex flex-wrap">
|
||||
{#if $$slots.title1}
|
||||
<Tab class={({selected}) => "tab "+(selected ? "selected" : "secondary")}>
|
||||
<Tab class={({selected}) => "tab "+(selected ? "primary" : "")}>
|
||||
<div bind:this={tabElements[0]} class="flex">
|
||||
<slot name="title0">
|
||||
Tab 0
|
||||
|
@ -31,28 +31,28 @@
|
|||
</Tab>
|
||||
{/if}
|
||||
{#if $$slots.title1}
|
||||
<Tab class={({selected}) => "tab "+(selected ? "selected" : "secondary")}>
|
||||
<Tab class={({selected}) => "tab "+(selected ? "primary" : "")}>
|
||||
<div bind:this={tabElements[1]} class="flex">
|
||||
<slot name="title1"/>
|
||||
</div>
|
||||
</Tab>
|
||||
{/if}
|
||||
{#if $$slots.title2}
|
||||
<Tab class={({selected}) => "tab "+(selected ? "selected" : "secondary")}>
|
||||
<Tab class={({selected}) => "tab "+(selected ? "primary" : "")}>
|
||||
<div bind:this={tabElements[2]} class="flex">
|
||||
<slot name="title2"/>
|
||||
</div>
|
||||
</Tab>
|
||||
{/if}
|
||||
{#if $$slots.title3}
|
||||
<Tab class={({selected}) => "tab "+(selected ? "selected" : "secondary")}>
|
||||
<Tab class={({selected}) => "tab "+(selected ? "primary" : "")}>
|
||||
<div bind:this={tabElements[3]} class="flex">
|
||||
<slot name="title3"/>
|
||||
</div>
|
||||
</Tab>
|
||||
{/if}
|
||||
{#if $$slots.title4}
|
||||
<Tab class={({selected}) => "tab "+(selected ? "selected" : "secondary")}>
|
||||
<Tab class={({selected}) => "tab "+(selected ? "primary" : "")}>
|
||||
<div bind:this={tabElements[4]} class="flex">
|
||||
<slot name="title4"/>
|
||||
</div>
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
import WeblateLink from "./WeblateLink.svelte";
|
||||
|
||||
export let t: Translation;
|
||||
export let cls: string = ""
|
||||
export let tags: Record<string, string> | undefined = undefined;
|
||||
// Text for the current language
|
||||
let txt: string | undefined;
|
||||
|
@ -29,7 +30,7 @@
|
|||
</script>
|
||||
|
||||
{#if t}
|
||||
<span class="inline-flex items-center">
|
||||
<span class={"inline-flex items-center "+cls}>
|
||||
<FromHtml src={txt}></FromHtml>
|
||||
<WeblateLink context={t.context}></WeblateLink>
|
||||
</span>
|
||||
|
|
|
@ -40,9 +40,9 @@
|
|||
</a>
|
||||
{resource.resolved?.description}
|
||||
{#if resource.languageCodes?.indexOf($language) >= 0}
|
||||
<span class="border-2 rounded-full border-lime-500 text-sm w-fit px-2">
|
||||
<div class="thanks w-fit">
|
||||
<ToSvelte construct={() => availableTranslation.Clone()} />
|
||||
</span>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -111,17 +111,3 @@ $: onDestroy(
|
|||
</div>
|
||||
{/if}
|
||||
|
||||
<style>
|
||||
|
||||
:global(.no-image-background * img) {
|
||||
background: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
:global(.no-image-background * svg) {
|
||||
background: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
import { Geocoding } from "../../Logic/Osm/Geocoding";
|
||||
import { BBox } from "../../Logic/BBox";
|
||||
import { GeoIndexedStoreForLayer } from "../../Logic/FeatureSource/Actors/GeoIndexedStore";
|
||||
import {createEventDispatcher} from "svelte";
|
||||
|
||||
export let perLayer: ReadonlyMap<string, GeoIndexedStoreForLayer> | undefined = undefined;
|
||||
export let bounds: UIEventSource<BBox>;
|
||||
|
@ -34,6 +35,8 @@
|
|||
}
|
||||
);
|
||||
|
||||
const dispatch = createEventDispatcher<{searchCompleted}>()
|
||||
|
||||
async function performSearch() {
|
||||
try {
|
||||
isRunning = true;
|
||||
|
@ -59,6 +62,7 @@
|
|||
|
||||
}
|
||||
}
|
||||
dispatch("searchCompleted")
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
feedback = Translations.t.general.search.error.txt;
|
||||
|
|
|
@ -4,7 +4,6 @@
|
|||
import LayerConfig from "../../Models/ThemeConfig/LayerConfig";
|
||||
import type {SpecialVisualizationState} from "../SpecialVisualization";
|
||||
import TagRenderingAnswer from "../Popup/TagRendering/TagRenderingAnswer.svelte";
|
||||
import TagRenderingEditable from "../Popup/TagRendering/TagRenderingEditable.svelte";
|
||||
import {onDestroy} from "svelte";
|
||||
import Translations from "../i18n/Translations";
|
||||
import Tr from "../Base/Tr.svelte";
|
||||
|
@ -31,31 +30,37 @@
|
|||
{#if _tags._deleted === "yes"}
|
||||
<Tr t={ Translations.t.delete.isDeleted}/>
|
||||
{:else}
|
||||
<div class="flex border-b-2 border-black drop-shadow-md justify-between items-center low-interaction px-3 active-links">
|
||||
<div class="flex flex-col">
|
||||
<div class="flex border-b-2 border-black drop-shadow-md justify-between items-center low-interaction px-3">
|
||||
<div class="flex flex-col">
|
||||
|
||||
<!-- Title element-->
|
||||
<h3>
|
||||
<TagRenderingAnswer config={layer.title} {selectedElement} {state} {tags}
|
||||
{layer}></TagRenderingAnswer>
|
||||
</h3>
|
||||
<!-- Title element-->
|
||||
<h3>
|
||||
<TagRenderingAnswer config={layer.title} {selectedElement} {state} {tags}
|
||||
{layer}></TagRenderingAnswer>
|
||||
</h3>
|
||||
|
||||
<div class="flex flex-row flex-wrap pt-0.5 sm:pt-1 items-center mr-2 gap-x-0.5 p-1">
|
||||
{#each layer.titleIcons as titleIconConfig}
|
||||
{#if (titleIconConfig.condition?.matchesProperties(_tags) ?? true) && (titleIconConfig.metacondition?.matchesProperties({..._metatags, ..._tags}) ?? true) && titleIconConfig.IsKnown(_tags)}
|
||||
<div class="w-8 h-8 flex items-center">
|
||||
<TagRenderingAnswer config={titleIconConfig} {tags} {selectedElement} {state}
|
||||
{layer} extraClasses="h-full justify-center" ></TagRenderingAnswer>
|
||||
</div>
|
||||
{/if}
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
<div class="title-icons flex flex-row flex-wrap pt-0.5 sm:pt-1 items-center mr-2 gap-x-0.5 p-1 links-as-button">
|
||||
{#each layer.titleIcons as titleIconConfig}
|
||||
{#if (titleIconConfig.condition?.matchesProperties(_tags) ?? true) && (titleIconConfig.metacondition?.matchesProperties({..._metatags, ..._tags}) ?? true) && titleIconConfig.IsKnown(_tags)}
|
||||
<div class="w-8 h-8 flex items-center">
|
||||
<TagRenderingAnswer config={titleIconConfig} {tags} {selectedElement} {state}
|
||||
{layer} extraClasses="h-full justify-center"></TagRenderingAnswer>
|
||||
</div>
|
||||
{/if}
|
||||
{/each}
|
||||
</div>
|
||||
<XCircleIcon class="w-8 h-8 cursor-pointer" on:click={() => state.selectedElement.setData(undefined)}/>
|
||||
|
||||
</div>
|
||||
<XCircleIcon class="w-8 h-8 cursor-pointer" on:click={() => state.selectedElement.setData(undefined)}/>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<style>
|
||||
|
||||
.title-icons {
|
||||
|
||||
}
|
||||
|
||||
:global(.title-icons a) {
|
||||
display: block !important;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -23,7 +23,7 @@
|
|||
}));
|
||||
</script>
|
||||
|
||||
<div class="flex border border-gray-300 border-dashed m-1 p-1 rounded-md link-underline">
|
||||
<div class="flex border border-gray-600 border-dashed m-1 p-1 rounded-md link-underline">
|
||||
{#if $userdetails.img}
|
||||
<img src={$userdetails.img} class="rounded-full w-12 h-12 m-4">
|
||||
{:else}
|
||||
|
@ -32,15 +32,15 @@
|
|||
<div class="flex flex-col">
|
||||
<h3>{$userdetails.name}</h3>
|
||||
{#if description}
|
||||
<FromHtml src={description} />
|
||||
<a href={osmConnection.Backend() + "/profile/edit"} target="_blank" class="link-no-underline flex subtle-background items-center w-fit self-end">
|
||||
<FromHtml src={description}/>
|
||||
<a href={osmConnection.Backend() + "/profile/edit"} target="_blank" class="link-no-underline flex items-center self-end">
|
||||
<PencilAltIcon slot="image" class="p-2 w-8 h-8" />
|
||||
<Tr slot="message" t={Translations.t.userinfo.editDescription} />
|
||||
</a>
|
||||
|
||||
{:else}
|
||||
<Tr t={Translations.t. userinfo.noDescription} />
|
||||
<a href={osmConnection.Backend() + "/profile/edit"} target="_blank" class="flex subtle-background items-center">
|
||||
<a href={osmConnection.Backend() + "/profile/edit"} target="_blank" class="flex items-center">
|
||||
<PencilAltIcon slot="image" class="p-2 w-8 h-8" />
|
||||
<Tr slot="message" t={Translations.t.userinfo.noDescriptionCallToAction} />
|
||||
</a>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { InputElement } from "./InputElement"
|
||||
import { UIEventSource } from "../../Logic/UIEventSource"
|
||||
import { Utils } from "../../Utils"
|
||||
import {InputElement} from "./InputElement"
|
||||
import {UIEventSource} from "../../Logic/UIEventSource"
|
||||
import {Utils} from "../../Utils"
|
||||
import BaseUIElement from "../BaseUIElement"
|
||||
import InputElementMap from "./InputElementMap"
|
||||
import Translations from "../i18n/Translations"
|
||||
|
@ -68,18 +68,15 @@ export default class CheckBoxes extends InputElement<number[]> {
|
|||
label.appendChild(inputI.ConstructElement())
|
||||
label.classList.add("block", "w-full", "p-2", "cursor-pointer", "bg-red")
|
||||
|
||||
const wrapper = document.createElement("div")
|
||||
wrapper.classList.add("wrapper", "flex", "w-full", "border", "border-gray-400", "mb-1")
|
||||
wrapper.appendChild(label)
|
||||
formTag.appendChild(wrapper)
|
||||
formTag.appendChild(label)
|
||||
|
||||
value.addCallbackAndRunD((selectedValues) => {
|
||||
input.checked = selectedValues.indexOf(i) >= 0
|
||||
|
||||
if (input.checked) {
|
||||
wrapper.classList.add("checked")
|
||||
label.classList.add("checked")
|
||||
} else {
|
||||
wrapper.classList.remove("checked")
|
||||
label.classList.remove("checked")
|
||||
}
|
||||
})
|
||||
|
||||
|
|
|
@ -1,272 +1,291 @@
|
|||
<script lang="ts">
|
||||
/**
|
||||
* This component ties together all the steps that are needed to create a new point.
|
||||
* There are many subcomponents which help with that
|
||||
*/
|
||||
import type { SpecialVisualizationState } from "../../SpecialVisualization";
|
||||
import PresetList from "./PresetList.svelte";
|
||||
import type PresetConfig from "../../../Models/ThemeConfig/PresetConfig";
|
||||
import LayerConfig from "../../../Models/ThemeConfig/LayerConfig";
|
||||
import Tr from "../../Base/Tr.svelte";
|
||||
import SubtleButton from "../../Base/SubtleButton.svelte";
|
||||
import FromHtml from "../../Base/FromHtml.svelte";
|
||||
import Translations from "../../i18n/Translations.js";
|
||||
import TagHint from "../TagHint.svelte";
|
||||
import { And } from "../../../Logic/Tags/And.js";
|
||||
import LoginToggle from "../../Base/LoginToggle.svelte";
|
||||
import Constants from "../../../Models/Constants.js";
|
||||
import FilteredLayer from "../../../Models/FilteredLayer";
|
||||
import { Store, UIEventSource } from "../../../Logic/UIEventSource";
|
||||
import { EyeIcon, EyeOffIcon } from "@rgossiaux/svelte-heroicons/solid";
|
||||
import LoginButton from "../../Base/LoginButton.svelte";
|
||||
import NewPointLocationInput from "../../BigComponents/NewPointLocationInput.svelte";
|
||||
import CreateNewNodeAction from "../../../Logic/Osm/Actions/CreateNewNodeAction";
|
||||
import { OsmWay } from "../../../Logic/Osm/OsmObject";
|
||||
import { Tag } from "../../../Logic/Tags/Tag";
|
||||
import type { WayId } from "../../../Models/OsmFeature";
|
||||
import Loading from "../../Base/Loading.svelte";
|
||||
import type { GlobalFilter } from "../../../Models/GlobalFilter";
|
||||
import { onDestroy } from "svelte";
|
||||
/**
|
||||
* This component ties together all the steps that are needed to create a new point.
|
||||
* There are many subcomponents which help with that
|
||||
*/
|
||||
import type {SpecialVisualizationState} from "../../SpecialVisualization";
|
||||
import PresetList from "./PresetList.svelte";
|
||||
import type PresetConfig from "../../../Models/ThemeConfig/PresetConfig";
|
||||
import LayerConfig from "../../../Models/ThemeConfig/LayerConfig";
|
||||
import Tr from "../../Base/Tr.svelte";
|
||||
import SubtleButton from "../../Base/SubtleButton.svelte";
|
||||
import FromHtml from "../../Base/FromHtml.svelte";
|
||||
import Translations from "../../i18n/Translations.js";
|
||||
import TagHint from "../TagHint.svelte";
|
||||
import {And} from "../../../Logic/Tags/And.js";
|
||||
import LoginToggle from "../../Base/LoginToggle.svelte";
|
||||
import Constants from "../../../Models/Constants.js";
|
||||
import FilteredLayer from "../../../Models/FilteredLayer";
|
||||
import {Store, UIEventSource} from "../../../Logic/UIEventSource";
|
||||
import {EyeIcon, EyeOffIcon} from "@rgossiaux/svelte-heroicons/solid";
|
||||
import LoginButton from "../../Base/LoginButton.svelte";
|
||||
import NewPointLocationInput from "../../BigComponents/NewPointLocationInput.svelte";
|
||||
import CreateNewNodeAction from "../../../Logic/Osm/Actions/CreateNewNodeAction";
|
||||
import {OsmWay} from "../../../Logic/Osm/OsmObject";
|
||||
import {Tag} from "../../../Logic/Tags/Tag";
|
||||
import type {WayId} from "../../../Models/OsmFeature";
|
||||
import Loading from "../../Base/Loading.svelte";
|
||||
import type {GlobalFilter} from "../../../Models/GlobalFilter";
|
||||
import {onDestroy} from "svelte";
|
||||
import NextButton from "../../Base/NextButton.svelte";
|
||||
import BackButton from "../../Base/BackButton.svelte";
|
||||
|
||||
export let coordinate: { lon: number, lat: number };
|
||||
export let state: SpecialVisualizationState;
|
||||
export let coordinate: { lon: number, lat: number };
|
||||
export let state: SpecialVisualizationState;
|
||||
|
||||
let selectedPreset: {
|
||||
preset: PresetConfig,
|
||||
layer: LayerConfig,
|
||||
icon: string,
|
||||
tags: Record<string, string>
|
||||
} = undefined;
|
||||
let checkedOfGlobalFilters : number = 0
|
||||
let confirmedCategory = false;
|
||||
$: if (selectedPreset === undefined) {
|
||||
confirmedCategory = false;
|
||||
creating = false;
|
||||
checkedOfGlobalFilters = 0
|
||||
|
||||
}
|
||||
let selectedPreset: {
|
||||
preset: PresetConfig,
|
||||
layer: LayerConfig,
|
||||
icon: string,
|
||||
tags: Record<string, string>
|
||||
} = undefined;
|
||||
let checkedOfGlobalFilters: number = 0
|
||||
let confirmedCategory = false;
|
||||
$: if (selectedPreset === undefined) {
|
||||
confirmedCategory = false;
|
||||
creating = false;
|
||||
checkedOfGlobalFilters = 0
|
||||
|
||||
let flayer: FilteredLayer = undefined;
|
||||
let layerIsDisplayed: UIEventSource<boolean> | undefined = undefined;
|
||||
let layerHasFilters: Store<boolean> | undefined = undefined;
|
||||
let globalFilter: UIEventSource<GlobalFilter[]> = state.layerState.globalFilters;
|
||||
let _globalFilter: GlobalFilter[] = [];
|
||||
onDestroy(globalFilter.addCallbackAndRun(globalFilter => {
|
||||
console.log("Global filters are", globalFilter);
|
||||
_globalFilter = globalFilter ?? [];
|
||||
}));
|
||||
$:{
|
||||
flayer = state.layerState.filteredLayers.get(selectedPreset?.layer?.id);
|
||||
layerIsDisplayed = flayer?.isDisplayed;
|
||||
layerHasFilters = flayer?.hasFilter;
|
||||
}
|
||||
const t = Translations.t.general.add;
|
||||
|
||||
const zoom = state.mapProperties.zoom;
|
||||
|
||||
const isLoading = state.dataIsLoading;
|
||||
let preciseCoordinate: UIEventSource<{ lon: number, lat: number }> = new UIEventSource(undefined);
|
||||
let snappedToObject: UIEventSource<string> = new UIEventSource<string>(undefined);
|
||||
|
||||
let creating = false;
|
||||
|
||||
/**
|
||||
* Call when the user should restart the flow by clicking on the map, e.g. because they disabled filters.
|
||||
* Will delete the lastclick-location
|
||||
*/
|
||||
function abort() {
|
||||
state.selectedElement.setData(undefined);
|
||||
// When aborted, we force the contributors to place the pin _again_
|
||||
// This is because there might be a nearby object that was disabled; this forces them to re-evaluate the map
|
||||
state.lastClickObject.features.setData([]);
|
||||
}
|
||||
|
||||
async function confirm() {
|
||||
creating = true;
|
||||
const location: { lon: number; lat: number } = preciseCoordinate.data;
|
||||
const snapTo: WayId | undefined = <WayId>snappedToObject.data;
|
||||
const tags: Tag[] = selectedPreset.preset.tags.concat(..._globalFilter.map(f => f?.onNewPoint?.tags ?? []));
|
||||
console.log("Creating new point at", location, "snapped to", snapTo, "with tags", tags);
|
||||
|
||||
let snapToWay: undefined | OsmWay = undefined;
|
||||
if (snapTo !== undefined) {
|
||||
const downloaded = await state.osmObjectDownloader.DownloadObjectAsync(snapTo, 0);
|
||||
if (downloaded !== "deleted") {
|
||||
snapToWay = downloaded;
|
||||
}
|
||||
}
|
||||
|
||||
const newElementAction = new CreateNewNodeAction(tags, location.lat, location.lon,
|
||||
{
|
||||
theme: state.layout?.id ?? "unkown",
|
||||
changeType: "create",
|
||||
snapOnto: snapToWay
|
||||
});
|
||||
await state.changes.applyAction(newElementAction);
|
||||
state.newFeatures.features.ping();
|
||||
// The 'changes' should have created a new point, which added this into the 'featureProperties'
|
||||
const newId = newElementAction.newElementId;
|
||||
console.log("Applied pending changes, fetching store for", newId);
|
||||
const tagsStore = state.featureProperties.getStore(newId);
|
||||
{
|
||||
// Set some metainfo
|
||||
const properties = tagsStore.data;
|
||||
if (snapTo) {
|
||||
// metatags (starting with underscore) are not uploaded, so we can safely mark this
|
||||
delete properties["_referencing_ways"];
|
||||
properties["_referencing_ways"] = `["${snapTo}"]`;
|
||||
}
|
||||
properties["_backend"] = state.osmConnection.Backend();
|
||||
properties["_last_edit:timestamp"] = new Date().toISOString();
|
||||
const userdetails = state.osmConnection.userDetails.data;
|
||||
properties["_last_edit:contributor"] = userdetails.name;
|
||||
properties["_last_edit:uid"] = "" + userdetails.uid;
|
||||
tagsStore.ping();
|
||||
let flayer: FilteredLayer = undefined;
|
||||
let layerIsDisplayed: UIEventSource<boolean> | undefined = undefined;
|
||||
let layerHasFilters: Store<boolean> | undefined = undefined;
|
||||
let globalFilter: UIEventSource<GlobalFilter[]> = state.layerState.globalFilters;
|
||||
let _globalFilter: GlobalFilter[] = [];
|
||||
onDestroy(globalFilter.addCallbackAndRun(globalFilter => {
|
||||
console.log("Global filters are", globalFilter);
|
||||
_globalFilter = globalFilter ?? [];
|
||||
}));
|
||||
$:{
|
||||
flayer = state.layerState.filteredLayers.get(selectedPreset?.layer?.id);
|
||||
layerIsDisplayed = flayer?.isDisplayed;
|
||||
layerHasFilters = flayer?.hasFilter;
|
||||
}
|
||||
const feature = state.indexedFeatures.featuresById.data.get(newId);
|
||||
abort();
|
||||
state.selectedLayer.setData(selectedPreset.layer);
|
||||
state.selectedElement.setData(feature);
|
||||
tagsStore.ping();
|
||||
const t = Translations.t.general.add;
|
||||
|
||||
}
|
||||
const zoom = state.mapProperties.zoom;
|
||||
|
||||
const isLoading = state.dataIsLoading;
|
||||
let preciseCoordinate: UIEventSource<{ lon: number, lat: number }> = new UIEventSource(undefined);
|
||||
let snappedToObject: UIEventSource<string> = new UIEventSource<string>(undefined);
|
||||
|
||||
let creating = false;
|
||||
|
||||
/**
|
||||
* Call when the user should restart the flow by clicking on the map, e.g. because they disabled filters.
|
||||
* Will delete the lastclick-location
|
||||
*/
|
||||
function abort() {
|
||||
state.selectedElement.setData(undefined);
|
||||
// When aborted, we force the contributors to place the pin _again_
|
||||
// This is because there might be a nearby object that was disabled; this forces them to re-evaluate the map
|
||||
state.lastClickObject.features.setData([]);
|
||||
}
|
||||
|
||||
async function confirm() {
|
||||
creating = true;
|
||||
const location: { lon: number; lat: number } = preciseCoordinate.data;
|
||||
const snapTo: WayId | undefined = <WayId>snappedToObject.data;
|
||||
const tags: Tag[] = selectedPreset.preset.tags.concat(..._globalFilter.map(f => f?.onNewPoint?.tags ?? []));
|
||||
console.log("Creating new point at", location, "snapped to", snapTo, "with tags", tags);
|
||||
|
||||
let snapToWay: undefined | OsmWay = undefined;
|
||||
if (snapTo !== undefined) {
|
||||
const downloaded = await state.osmObjectDownloader.DownloadObjectAsync(snapTo, 0);
|
||||
if (downloaded !== "deleted") {
|
||||
snapToWay = downloaded;
|
||||
}
|
||||
}
|
||||
|
||||
const newElementAction = new CreateNewNodeAction(tags, location.lat, location.lon,
|
||||
{
|
||||
theme: state.layout?.id ?? "unkown",
|
||||
changeType: "create",
|
||||
snapOnto: snapToWay
|
||||
});
|
||||
await state.changes.applyAction(newElementAction);
|
||||
state.newFeatures.features.ping();
|
||||
// The 'changes' should have created a new point, which added this into the 'featureProperties'
|
||||
const newId = newElementAction.newElementId;
|
||||
console.log("Applied pending changes, fetching store for", newId);
|
||||
const tagsStore = state.featureProperties.getStore(newId);
|
||||
{
|
||||
// Set some metainfo
|
||||
const properties = tagsStore.data;
|
||||
if (snapTo) {
|
||||
// metatags (starting with underscore) are not uploaded, so we can safely mark this
|
||||
delete properties["_referencing_ways"];
|
||||
properties["_referencing_ways"] = `["${snapTo}"]`;
|
||||
}
|
||||
properties["_backend"] = state.osmConnection.Backend();
|
||||
properties["_last_edit:timestamp"] = new Date().toISOString();
|
||||
const userdetails = state.osmConnection.userDetails.data;
|
||||
properties["_last_edit:contributor"] = userdetails.name;
|
||||
properties["_last_edit:uid"] = "" + userdetails.uid;
|
||||
tagsStore.ping();
|
||||
}
|
||||
const feature = state.indexedFeatures.featuresById.data.get(newId);
|
||||
abort();
|
||||
state.selectedLayer.setData(selectedPreset.layer);
|
||||
state.selectedElement.setData(feature);
|
||||
tagsStore.ping();
|
||||
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<LoginToggle ignoreLoading={true} {state}>
|
||||
<!-- This component is basically one big if/then/else flow checking for many conditions and edge cases that (in some cases) have to be handled;
|
||||
1. the first (and outermost) is of course: are we logged in?
|
||||
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} />
|
||||
</LoginButton>
|
||||
{#if $isLoading}
|
||||
<div class="alert">
|
||||
<Loading>
|
||||
<Tr t={Translations.t.general.add.stillLoading} />
|
||||
</Loading>
|
||||
</div>
|
||||
{:else if $zoom < Constants.minZoomLevelToAddNewPoint}
|
||||
<div class="alert">
|
||||
<Tr t={Translations.t.general.add.zoomInFurther}></Tr>
|
||||
</div>
|
||||
{:else if selectedPreset === undefined}
|
||||
<!-- First, select the correct preset -->
|
||||
<PresetList {state} on:select={event => {selectedPreset = event.detail}}></PresetList>
|
||||
<!-- This component is basically one big if/then/else flow checking for many conditions and edge cases that (in some cases) have to be handled;
|
||||
1. the first (and outermost) is of course: are we logged in?
|
||||
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}/>
|
||||
</LoginButton>
|
||||
{#if $isLoading}
|
||||
<div class="alert">
|
||||
<Loading>
|
||||
<Tr t={Translations.t.general.add.stillLoading}/>
|
||||
</Loading>
|
||||
</div>
|
||||
{:else if $zoom < Constants.minZoomLevelToAddNewPoint}
|
||||
<div class="alert">
|
||||
<Tr t={Translations.t.general.add.zoomInFurther}></Tr>
|
||||
</div>
|
||||
{:else if selectedPreset === undefined}
|
||||
<!-- First, select the correct preset -->
|
||||
<PresetList {state} on:select={event => {selectedPreset = event.detail}}></PresetList>
|
||||
|
||||
|
||||
{:else if !$layerIsDisplayed}
|
||||
<!-- Check that the layer is enabled, so that we don't add a duplicate -->
|
||||
<div class="alert flex justify-center items-center">
|
||||
<EyeOffIcon class="w-8" />
|
||||
<Tr t={Translations.t.general.add.layerNotEnabled
|
||||
{:else if !$layerIsDisplayed}
|
||||
<!-- Check that the layer is enabled, so that we don't add a duplicate -->
|
||||
<div class="alert flex justify-center items-center">
|
||||
<EyeOffIcon class="w-8"/>
|
||||
<Tr t={Translations.t.general.add.layerNotEnabled
|
||||
.Subs({ layer: selectedPreset.layer.name })
|
||||
} />
|
||||
</div>
|
||||
}/>
|
||||
</div>
|
||||
|
||||
<SubtleButton on:click={() => {
|
||||
<SubtleButton on:click={() => {
|
||||
layerIsDisplayed.setData(true)
|
||||
abort()
|
||||
}}>
|
||||
<EyeIcon slot="image" class="w-8" />
|
||||
<Tr slot="message" t={Translations.t.general.add.enableLayer.Subs({name: selectedPreset.layer.name})} />
|
||||
</SubtleButton>
|
||||
<SubtleButton on:click={() => {
|
||||
<EyeIcon slot="image" class="w-8"/>
|
||||
<Tr slot="message" t={Translations.t.general.add.enableLayer.Subs({name: selectedPreset.layer.name})}/>
|
||||
</SubtleButton>
|
||||
|
||||
<SubtleButton on:click={() => {
|
||||
abort()
|
||||
state.guistate.openFilterView(selectedPreset.layer) } }>
|
||||
<img src="./assets/svg/layers.svg" slot="image" class="w-6">
|
||||
<Tr slot="message" t={Translations.t.general.add.openLayerControl}></Tr>
|
||||
</SubtleButton>
|
||||
<img src="./assets/svg/layers.svg" slot="image" class="w-6">
|
||||
<Tr slot="message" t={Translations.t.general.add.openLayerControl}></Tr>
|
||||
</SubtleButton>
|
||||
|
||||
|
||||
{:else if $layerHasFilters}
|
||||
<!-- Some filters are enabled. The feature to add might already be mapped, but hidden -->
|
||||
<div class="alert flex justify-center items-center">
|
||||
<EyeOffIcon class="w-8" />
|
||||
<Tr t={Translations.t.general.add.disableFiltersExplanation} />
|
||||
</div>
|
||||
{:else if $layerHasFilters}
|
||||
<!-- Some filters are enabled. The feature to add might already be mapped, but hidden -->
|
||||
<div class="alert flex justify-center items-center">
|
||||
<EyeOffIcon class="w-8"/>
|
||||
<Tr t={Translations.t.general.add.disableFiltersExplanation}/>
|
||||
</div>
|
||||
|
||||
<SubtleButton on:click={() => {
|
||||
<SubtleButton on:click={() => {
|
||||
abort()
|
||||
const flayer = state.layerState.filteredLayers.get(selectedPreset.layer.id)
|
||||
flayer.disableAllFilters()
|
||||
}
|
||||
}>
|
||||
<EyeOffIcon class="w-8" />
|
||||
<Tr slot="message" t={Translations.t.general.add.disableFilters}></Tr>
|
||||
</SubtleButton>
|
||||
<EyeOffIcon class="w-8"/>
|
||||
<Tr slot="message" t={Translations.t.general.add.disableFilters}></Tr>
|
||||
</SubtleButton>
|
||||
|
||||
|
||||
<SubtleButton on:click={() => {
|
||||
<SubtleButton options={{extraClasses:"secondary"}} on:click={() => {
|
||||
abort()
|
||||
state.guistate.openFilterView(selectedPreset.layer)
|
||||
}
|
||||
}>
|
||||
<img src="./assets/svg/layers.svg" slot="image" class="w-6">
|
||||
<Tr slot="message" t={Translations.t.general.add.openLayerControl}></Tr>
|
||||
</SubtleButton>
|
||||
<img src="./assets/svg/layers.svg" slot="image" class="w-6">
|
||||
<Tr slot="message" t={Translations.t.general.add.openLayerControl}></Tr>
|
||||
</SubtleButton>
|
||||
|
||||
{:else if !confirmedCategory }
|
||||
<!-- Second, confirm the category -->
|
||||
<Tr t={Translations.t.general.add.confirmIntro.Subs({title: selectedPreset.preset.title})}></Tr>
|
||||
{:else if !confirmedCategory }
|
||||
<!-- Second, confirm the category -->
|
||||
<h2>
|
||||
<Tr t={Translations.t.general.add.confirmTitle.Subs({title: selectedPreset.preset.title})}/>
|
||||
</h2>
|
||||
|
||||
<Tr t={Translations.t.general.add.confirmIntro}/>
|
||||
|
||||
|
||||
{#if selectedPreset.preset.description}
|
||||
<Tr t={selectedPreset.preset.description} />
|
||||
{/if}
|
||||
|
||||
{#if selectedPreset.preset.exampleImages}
|
||||
<h4>
|
||||
{#if selectedPreset.preset.exampleImages.length == 1}
|
||||
<Tr t={Translations.t.general.example} />
|
||||
{:else}
|
||||
<Tr t={Translations.t.general.examples } />
|
||||
{#if selectedPreset.preset.description}
|
||||
<Tr t={selectedPreset.preset.description}/>
|
||||
{/if}
|
||||
</h4>
|
||||
<span class="flex flex-wrap items-stretch">
|
||||
|
||||
{#if selectedPreset.preset.exampleImages}
|
||||
<h3>
|
||||
{#if selectedPreset.preset.exampleImages.length === 1}
|
||||
<Tr t={Translations.t.general.example}/>
|
||||
{:else}
|
||||
<Tr t={Translations.t.general.examples }/>
|
||||
{/if}
|
||||
</h3>
|
||||
<span class="flex flex-wrap items-stretch">
|
||||
{#each selectedPreset.preset.exampleImages as src}
|
||||
<img {src} class="h-64 m-1 w-auto rounded-lg">
|
||||
{/each}
|
||||
</span>
|
||||
{/if}
|
||||
<TagHint embedIn={tags => t.presetInfo.Subs({tags})} {state}
|
||||
tags={new And(selectedPreset.preset.tags)}></TagHint>
|
||||
|
||||
<div class="flex w-full">
|
||||
|
||||
<BackButton on:click={() => selectedPreset = undefined} clss="w-full">
|
||||
<Tr slot="message" t={t.backToSelect}/>
|
||||
</BackButton>
|
||||
|
||||
<NextButton on:click={() => confirmedCategory = true} clss="primary w-full">
|
||||
<div slot="image" class="relative">
|
||||
<FromHtml src={selectedPreset.icon}></FromHtml>
|
||||
<img class="absolute bottom-0 right-0 w-4 h-4" src="./assets/svg/confirm.svg">
|
||||
</div>
|
||||
<div class="w-full">
|
||||
<Tr t={selectedPreset.text}></Tr>
|
||||
</div>
|
||||
</NextButton>
|
||||
</div>
|
||||
|
||||
{:else if _globalFilter?.length > 0 && _globalFilter?.length > checkedOfGlobalFilters}
|
||||
<Tr t={_globalFilter[checkedOfGlobalFilters].onNewPoint?.safetyCheck}/>
|
||||
<SubtleButton on:click={() => {checkedOfGlobalFilters = checkedOfGlobalFilters + 1}}>
|
||||
<img slot="image" src={_globalFilter[checkedOfGlobalFilters].onNewPoint?.icon ?? "./assets/svg/confirm.svg"}
|
||||
class="w-12 h-12">
|
||||
<Tr slot="message"
|
||||
t={_globalFilter[checkedOfGlobalFilters].onNewPoint?.confirmAddNew.Subs({preset: selectedPreset.preset})}/>
|
||||
</SubtleButton>
|
||||
<SubtleButton on:click={() => {globalFilter.setData([]); abort()}}>
|
||||
<img slot="image" src="./assets/svg/close.svg" class="w-8 h-8"/>
|
||||
<Tr slot="message" t={Translations.t.general.cancel}/>
|
||||
</SubtleButton>
|
||||
{:else if !creating}
|
||||
<NewPointLocationInput value={preciseCoordinate} snappedTo={snappedToObject} {state} {coordinate}
|
||||
targetLayer={selectedPreset.layer}
|
||||
snapToLayers={selectedPreset.preset.preciseInput.snapToLayers}></NewPointLocationInput>
|
||||
<div class="flex">
|
||||
|
||||
<BackButton on:click={() => selectedPreset = undefined} clss="w-full">
|
||||
<Tr slot="message" t={t.backToSelect}/>
|
||||
</BackButton>
|
||||
|
||||
<NextButton on:click={confirm} clss="primary w-full">
|
||||
<div class="w-full flex justify-end gap-x-2">
|
||||
<Tr t={Translations.t.general.add.confirmLocation}/>
|
||||
</div>
|
||||
</NextButton>
|
||||
</div>
|
||||
{:else}
|
||||
<Loading>Creating point...</Loading>
|
||||
{/if}
|
||||
<TagHint embedIn={tags => t.presetInfo.Subs({tags})} {state}
|
||||
tags={new And(selectedPreset.preset.tags)}></TagHint>
|
||||
|
||||
|
||||
<SubtleButton on:click={() => confirmedCategory = true}>
|
||||
<div slot="image" class="relative">
|
||||
<FromHtml src={selectedPreset.icon}></FromHtml>
|
||||
<img class="absolute bottom-0 right-0 w-4 h-4" src="./assets/svg/confirm.svg">
|
||||
</div>
|
||||
<div slot="message">
|
||||
<Tr t={selectedPreset.text}></Tr>
|
||||
</div>
|
||||
</SubtleButton>
|
||||
<SubtleButton on:click={() => selectedPreset = undefined}>
|
||||
<img src="./assets/svg/back.svg" class="w-8 h-8" slot="image">
|
||||
<div slot="message">
|
||||
<Tr t={t.backToSelect} />
|
||||
</div>
|
||||
</SubtleButton>
|
||||
{:else if _globalFilter?.length > 0 && _globalFilter?.length > checkedOfGlobalFilters}
|
||||
<Tr t={_globalFilter[checkedOfGlobalFilters].onNewPoint?.safetyCheck} />
|
||||
<SubtleButton on:click={() => {checkedOfGlobalFilters = checkedOfGlobalFilters + 1}}>
|
||||
<img slot="image" src={_globalFilter[checkedOfGlobalFilters].onNewPoint?.icon ?? "./assets/svg/confirm.svg"} class="w-12 h-12">
|
||||
<Tr slot="message" t={_globalFilter[checkedOfGlobalFilters].onNewPoint?.confirmAddNew.Subs({preset: selectedPreset.preset})} />
|
||||
</SubtleButton>
|
||||
<SubtleButton on:click={() => {globalFilter.setData([]); abort()}}>
|
||||
<img slot="image" src="./assets/svg/close.svg" class="w-8 h-8"/>
|
||||
<Tr slot="message" t={Translations.t.general.cancel}/>
|
||||
</SubtleButton>
|
||||
{:else if !creating}
|
||||
<NewPointLocationInput value={preciseCoordinate} snappedTo={snappedToObject} {state} {coordinate}
|
||||
targetLayer={selectedPreset.layer}
|
||||
snapToLayers={selectedPreset.preset.preciseInput.snapToLayers}></NewPointLocationInput>
|
||||
<SubtleButton on:click={confirm}>
|
||||
<span slot="message">Confirm location</span>
|
||||
</SubtleButton>
|
||||
{:else}
|
||||
<Loading>Creating point...</Loading>
|
||||
{/if}
|
||||
</LoginToggle>
|
||||
|
|
|
@ -1,88 +1,93 @@
|
|||
<script lang="ts">
|
||||
import LayoutConfig from "../../../Models/ThemeConfig/LayoutConfig";
|
||||
import { createEventDispatcher } from "svelte";
|
||||
import type PresetConfig from "../../../Models/ThemeConfig/PresetConfig";
|
||||
import Tr from "../../Base/Tr.svelte";
|
||||
import Translations from "../../i18n/Translations.js";
|
||||
import SubtleButton from "../../Base/SubtleButton.svelte";
|
||||
import { Translation } from "../../i18n/Translation";
|
||||
import type { SpecialVisualizationState } from "../../SpecialVisualization";
|
||||
import { ImmutableStore } from "../../../Logic/UIEventSource";
|
||||
import { TagUtils } from "../../../Logic/Tags/TagUtils";
|
||||
import LayerConfig from "../../../Models/ThemeConfig/LayerConfig";
|
||||
import FromHtml from "../../Base/FromHtml.svelte";
|
||||
import LayoutConfig from "../../../Models/ThemeConfig/LayoutConfig";
|
||||
import {createEventDispatcher} from "svelte";
|
||||
import type PresetConfig from "../../../Models/ThemeConfig/PresetConfig";
|
||||
import Tr from "../../Base/Tr.svelte";
|
||||
import Translations from "../../i18n/Translations.js";
|
||||
import SubtleButton from "../../Base/SubtleButton.svelte";
|
||||
import {Translation} from "../../i18n/Translation";
|
||||
import type {SpecialVisualizationState} from "../../SpecialVisualization";
|
||||
import {ImmutableStore} from "../../../Logic/UIEventSource";
|
||||
import {TagUtils} from "../../../Logic/Tags/TagUtils";
|
||||
import LayerConfig from "../../../Models/ThemeConfig/LayerConfig";
|
||||
import FromHtml from "../../Base/FromHtml.svelte";
|
||||
import NextButton from "../../Base/NextButton.svelte";
|
||||
|
||||
/**
|
||||
* This component lists all the presets and allows the user to select one
|
||||
*/
|
||||
export let state: SpecialVisualizationState;
|
||||
let layout: LayoutConfig = state.layout;
|
||||
let presets: {
|
||||
preset: PresetConfig,
|
||||
layer: LayerConfig,
|
||||
text: Translation,
|
||||
icon: string,
|
||||
tags: Record<string, string>
|
||||
}[] = [];
|
||||
/**
|
||||
* This component lists all the presets and allows the user to select one
|
||||
*/
|
||||
export let state: SpecialVisualizationState;
|
||||
let layout: LayoutConfig = state.layout;
|
||||
let presets: {
|
||||
preset: PresetConfig,
|
||||
layer: LayerConfig,
|
||||
text: Translation,
|
||||
icon: string,
|
||||
tags: Record<string, string>
|
||||
}[] = [];
|
||||
|
||||
for (const layer of layout.layers) {
|
||||
const flayer = state.layerState.filteredLayers.get(layer.id);
|
||||
if (flayer.isDisplayed.data === false) {
|
||||
// The layer is not displayed...
|
||||
if (!state.featureSwitches.featureSwitchFilter.data) {
|
||||
// ...and we cannot enable the layer control -> we skip, as these presets can never be shown anyway
|
||||
continue;
|
||||
}
|
||||
for (const layer of layout.layers) {
|
||||
const flayer = state.layerState.filteredLayers.get(layer.id);
|
||||
if (flayer.isDisplayed.data === false) {
|
||||
// The layer is not displayed...
|
||||
if (!state.featureSwitches.featureSwitchFilter.data) {
|
||||
// ...and we cannot enable the layer control -> we skip, as these presets can never be shown anyway
|
||||
continue;
|
||||
}
|
||||
|
||||
if (layer.name === undefined) {
|
||||
// this layer can never be toggled on in any case, so we skip the presets
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
for (const preset of layer.presets) {
|
||||
const tags = TagUtils.KVtoProperties(preset.tags ?? []);
|
||||
|
||||
const icon: string =
|
||||
layer.mapRendering[0]
|
||||
.RenderIcon(new ImmutableStore<any>(tags), false)
|
||||
.html.SetClass("w-12 h-12 block relative")
|
||||
.ConstructElement().innerHTML;
|
||||
|
||||
const description = preset.description?.FirstSentence();
|
||||
|
||||
const simplified = {
|
||||
preset,
|
||||
layer,
|
||||
icon,
|
||||
description,
|
||||
tags,
|
||||
text: Translations.t.general.add.addNew.Subs({category: preset.title}, preset.title["context"])
|
||||
};
|
||||
presets.push(simplified);
|
||||
}
|
||||
|
||||
if (layer.name === undefined) {
|
||||
// this layer can never be toggled on in any case, so we skip the presets
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
for (const preset of layer.presets) {
|
||||
const tags = TagUtils.KVtoProperties(preset.tags ?? []);
|
||||
|
||||
const icon: string =
|
||||
layer.mapRendering[0]
|
||||
.RenderIcon(new ImmutableStore<any>(tags), false)
|
||||
.html.SetClass("w-12 h-12 block relative")
|
||||
.ConstructElement().innerHTML;
|
||||
|
||||
const description = preset.description?.FirstSentence();
|
||||
|
||||
const simplified = {
|
||||
preset,
|
||||
layer,
|
||||
icon,
|
||||
description,
|
||||
tags,
|
||||
text: Translations.t.general.add.addNew.Subs({ category: preset.title }, preset.title["context"])
|
||||
};
|
||||
presets.push(simplified);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
const dispatch = createEventDispatcher<{ select: {preset: PresetConfig, layer: LayerConfig, icon: string, tags: Record<string, string>} }>();
|
||||
const dispatch = createEventDispatcher<{
|
||||
select: { preset: PresetConfig, layer: LayerConfig, icon: string, tags: Record<string, string> }
|
||||
}>();
|
||||
|
||||
</script>
|
||||
|
||||
<div>
|
||||
<Tr t={Translations.t.general.add.intro} />
|
||||
{#each presets as preset}
|
||||
<SubtleButton on:click={() => dispatch("select", preset)}>
|
||||
<FromHtml slot="image" src={preset.icon}></FromHtml>
|
||||
<div slot="message">
|
||||
<div class="flex flex-col w-full">
|
||||
<h2>
|
||||
<Tr t={Translations.t.general.add.intro}/>
|
||||
</h2>
|
||||
|
||||
{#each presets as preset}
|
||||
<NextButton on:click={() => dispatch("select", preset)}>
|
||||
<FromHtml slot="image" src={preset.icon}></FromHtml>
|
||||
<div class="flex flex-col">
|
||||
<b class="w-fit">
|
||||
<Tr t={preset.text}/>
|
||||
</b>
|
||||
{#if preset.description}
|
||||
<Tr t={preset.description} cls="font-normal"/>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<b>
|
||||
<Tr t={preset.text} />
|
||||
</b>
|
||||
{#if preset.description}
|
||||
<Tr t={preset.description}/>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
</SubtleButton>
|
||||
{/each}
|
||||
</NextButton>
|
||||
{/each}
|
||||
</div>
|
||||
|
|
|
@ -201,7 +201,7 @@
|
|||
|
||||
<slot name="cancel"></slot>
|
||||
|
||||
<button on:click={onSave} class={selectedTags === undefined ? "disabled" : "button-shadow"}>
|
||||
<button on:click={onSave} class={(selectedTags === undefined ? "disabled" : "button-shadow")+" primary"}>
|
||||
<Tr t={Translations.t.general.save}></Tr>
|
||||
</button>
|
||||
</div>
|
||||
|
|
|
@ -31,21 +31,21 @@
|
|||
</p>
|
||||
|
||||
<div class="flex">
|
||||
<button>
|
||||
<button class="primary">
|
||||
<ToSvelte construct={Svg.community_svg().SetClass("w-6 h-6")}/>
|
||||
Main action
|
||||
</button>
|
||||
<button class="disabled">
|
||||
<button class="primary disabled">
|
||||
<ToSvelte construct={Svg.community_svg().SetClass("w-6 h-6")}/>
|
||||
Main action (disabled)
|
||||
</button>
|
||||
</div>
|
||||
<div class="flex">
|
||||
<button class="secondary">
|
||||
<button>
|
||||
<ToSvelte construct={Svg.community_svg().SetClass("w-6 h-6")}/>
|
||||
Secondary action
|
||||
</button>
|
||||
<button class="secondary disabled">
|
||||
<button class="disabled">
|
||||
<ToSvelte construct={Svg.community_svg().SetClass("w-6 h-6")}/>
|
||||
Secondary action (disabled)
|
||||
</button>
|
||||
|
@ -79,21 +79,21 @@
|
|||
</p>
|
||||
|
||||
<div class="flex">
|
||||
<button>
|
||||
<button class="primary">
|
||||
<ToSvelte construct={Svg.community_svg().SetClass("w-6 h-6")}/>
|
||||
Main action
|
||||
</button>
|
||||
<button class="disabled">
|
||||
<button class="primary disabled">
|
||||
<ToSvelte construct={Svg.community_svg().SetClass("w-6 h-6")}/>
|
||||
Main action (disabled)
|
||||
</button>
|
||||
</div>
|
||||
<div class="flex">
|
||||
<button class="secondary">
|
||||
<button>
|
||||
<ToSvelte construct={Svg.community_svg().SetClass("w-6 h-6")}/>
|
||||
Secondary action
|
||||
</button>
|
||||
<button class="secondary disabled">
|
||||
<button class="disabled">
|
||||
<ToSvelte construct={Svg.community_svg().SetClass("w-6 h-6")}/>
|
||||
Secondary action (disabled)
|
||||
</button>
|
||||
|
|
|
@ -39,6 +39,8 @@
|
|||
import SelectedElementTitle from "./BigComponents/SelectedElementTitle.svelte";
|
||||
import Svg from "../Svg";
|
||||
import {ShareScreen} from "./BigComponents/ShareScreen";
|
||||
import NextButton from "./Base/NextButton.svelte";
|
||||
import IfNot from "./Base/IfNot.svelte";
|
||||
|
||||
export let state: ThemeViewState;
|
||||
let layout = state.layout;
|
||||
|
@ -83,6 +85,21 @@
|
|||
let availableLayers = state.availableLayers;
|
||||
let userdetails = state.osmConnection.userDetails;
|
||||
let currentViewLayer = layout.layers.find(l => l.id === "current_view")
|
||||
|
||||
function jumpToCurrentLocation() {
|
||||
const glstate = state.geolocation.geolocationState
|
||||
if (glstate.currentGPSLocation.data !== undefined) {
|
||||
const c: GeolocationCoordinates = glstate.currentGPSLocation.data
|
||||
state.guistate.themeIsOpened.setData(false)
|
||||
const coor = {lon: c.longitude, lat: c.latitude}
|
||||
state.mapProperties.location.setData(coor)
|
||||
}
|
||||
if (glstate.permission.data !== "granted") {
|
||||
glstate.requestPermission()
|
||||
return
|
||||
}
|
||||
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
|
@ -95,7 +112,7 @@
|
|||
<If condition={state.featureSwitches.featureSwitchSearch}>
|
||||
<div class="max-[480px]:w-full float-right mt-1 px-1 sm:m-2">
|
||||
<Geosearch bounds={state.mapProperties.bounds} perLayer={state.perLayer} {selectedElement}
|
||||
{selectedLayer}></Geosearch>
|
||||
{selectedLayer}/>
|
||||
</div>
|
||||
</If>
|
||||
<div class="float-left m-1 sm:mt-2">
|
||||
|
@ -184,7 +201,7 @@
|
|||
<Tr t={layout.title}/>
|
||||
</div>
|
||||
|
||||
<div slot="content0">
|
||||
<div class="m-4" slot="content0">
|
||||
|
||||
<Tr t={layout.description}></Tr>
|
||||
<Tr t={Translations.t.general.welcomeExplanation.general}/>
|
||||
|
@ -198,11 +215,30 @@
|
|||
loginStatus.SetClass("block mt-6 pt-2 md:border-t-2 border-dotted border-gray-400"),
|
||||
-->
|
||||
<Tr t={layout.descriptionTail}></Tr>
|
||||
<div class="m-x-8">
|
||||
<button class="subtle-background rounded w-full p-4"
|
||||
on:click={() => state.guistate.themeIsOpened.setData(false)}>
|
||||
<NextButton clss="primary w-full" on:click={() => state.guistate.themeIsOpened.setData(false)}>
|
||||
<div class="flex justify-center w-full text-2xl">
|
||||
<Tr t={Translations.t.general.openTheMap}/>
|
||||
</button>
|
||||
</div>
|
||||
</NextButton>
|
||||
|
||||
<div class="flex w-full">
|
||||
<IfNot condition={state.geolocation.geolocationState.permission.map(p => p === "denied")}>
|
||||
<button class="flex w-full gap-x-2 items-center" on:click={jumpToCurrentLocation}>
|
||||
<ToSvelte construct={Svg.crosshair_svg().SetClass("w-8 h-8")}/>
|
||||
<span>
|
||||
Jump to your location
|
||||
</span>
|
||||
</button>
|
||||
</IfNot>
|
||||
|
||||
<div class="flex flex-col w-full border rounded low-interactive">
|
||||
Search for a location:
|
||||
<Geosearch bounds={state.mapProperties.bounds}
|
||||
on:searchCompleted={() => state.guistate.themeIsOpened.setData(false)}
|
||||
perLayer={state.perLayer}
|
||||
{selectedElement}
|
||||
{selectedLayer}/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
@ -238,7 +274,7 @@
|
|||
<Tr t={Translations.t.general.download.title}/>
|
||||
</If>
|
||||
</div>
|
||||
<div slot="content2">
|
||||
<div class="m-4" slot="content2">
|
||||
<ToSvelte construct={() => new DownloadPanel(state)}/>
|
||||
</div>
|
||||
|
||||
|
@ -251,8 +287,8 @@
|
|||
<div slot="title4">
|
||||
<Tr t={Translations.t.general.sharescreen.title}/>
|
||||
</div>
|
||||
<ToSvelte construct={() => new ShareScreen(state)} slot="content4"/>
|
||||
|
||||
<ToSvelte construct={() => new ShareScreen(state)} slot="content4"/>
|
||||
|
||||
</TabbedGroup>
|
||||
</FloatOver>
|
||||
</If>
|
||||
|
@ -270,7 +306,7 @@
|
|||
<Tr t={Translations.t.general.menu.aboutMapComplete}/>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col" slot="content0">
|
||||
<div class="flex flex-col m-2 links-as-button links-w-full gap-y-1" slot="content0">
|
||||
|
||||
<Tr t={Translations.t.general.aboutMapComplete.intro}/>
|
||||
|
||||
|
@ -308,12 +344,12 @@
|
|||
<Tr t={UserRelatedState.usersettingsConfig.title.GetRenderValue({})}/>
|
||||
</div>
|
||||
|
||||
<div slot="content1">
|
||||
<div class="links-as-button" slot="content1">
|
||||
<!-- All shown components are set by 'usersettings.json', which happily uses some special visualisations created specifically for it -->
|
||||
<LoginToggle {state}>
|
||||
<div slot="not-logged-in">
|
||||
<div slot="not-logged-in" class="flex flex-col">
|
||||
<Tr class="alert" t={Translations.t.userinfo.notLoggedIn}/>
|
||||
<LoginButton osmConnection={state.osmConnection}></LoginButton>
|
||||
<LoginButton osmConnection={state.osmConnection} clss="primary"/>
|
||||
</div>
|
||||
<SelectedElementView
|
||||
highlightedRendering={state.guistate.highlightedUserSetting}
|
||||
|
@ -330,16 +366,20 @@
|
|||
<ToSvelte construct={Svg.community_svg().SetClass("w-6 h-6")}/>
|
||||
Get in touch with others
|
||||
</div>
|
||||
<CommunityIndexView location={state.mapProperties.location} slot="content2"></CommunityIndexView>
|
||||
<div class="m-2" slot="content2">
|
||||
<CommunityIndexView location={state.mapProperties.location}></CommunityIndexView>
|
||||
</div>
|
||||
|
||||
<div class="flex" slot="title3">
|
||||
<EyeIcon class="w-6"/>
|
||||
<Tr t={Translations.t.privacy.title}></Tr>
|
||||
</div>
|
||||
<ToSvelte construct={() => new PrivacyPolicy()} slot="content3"></ToSvelte>
|
||||
<div class="m-2" slot="content3">
|
||||
<ToSvelte construct={() => new PrivacyPolicy()}/>
|
||||
</div>
|
||||
|
||||
<Tr slot="title4" t={Translations.t.advanced.title}/>
|
||||
<div class="flex flex-col" slot="content4">
|
||||
<div class="flex flex-col m-2" slot="content4">
|
||||
<ToSvelte construct={Hotkeys.generateDocumentationDynamic}></ToSvelte>
|
||||
|
||||
</div>
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue