Styling: style most buttons

This commit is contained in:
Pieter Vander Vennet 2023-05-14 03:24:13 +02:00
parent e04430b428
commit 83f3662b9a
46 changed files with 720 additions and 671 deletions

16
UI/Base/BackButton.svelte Normal file
View 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>

View file

@ -11,9 +11,10 @@
}
}
export let clss = ""
</script>
{#if src !== undefined}
<span bind:this={htmlElem}></span>
<span bind:this={htmlElem} class={clss}></span>
{/if}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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")
}
})

View file

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

View file

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

View file

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

View file

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

View file

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