forked from MapComplete/MapComplete
		
	UX+Refactoring: use side-drawer for menu, reorder menu structure
This commit is contained in:
		
							parent
							
								
									8465b59c7f
								
							
						
					
					
						commit
						124e816abe
					
				
					 25 changed files with 645 additions and 1059 deletions
				
			
		| 
						 | 
				
			
			@ -1,48 +0,0 @@
 | 
			
		|||
<script lang="ts">
 | 
			
		||||
  import { Store } from "../../Logic/UIEventSource"
 | 
			
		||||
  import { onDestroy, onMount } from "svelte"
 | 
			
		||||
 | 
			
		||||
  let elem: HTMLElement
 | 
			
		||||
  let targetOuter: HTMLElement
 | 
			
		||||
  export let isOpened: Store<boolean>
 | 
			
		||||
  export let moveTo: Store<HTMLElement>
 | 
			
		||||
 | 
			
		||||
  export let debug: string
 | 
			
		||||
  function copySizeOf(htmlElem: HTMLElement) {
 | 
			
		||||
    const target = htmlElem.getBoundingClientRect()
 | 
			
		||||
    elem.style.left = target.x + "px"
 | 
			
		||||
    elem.style.top = target.y + "px"
 | 
			
		||||
    elem.style.width = target.width + "px"
 | 
			
		||||
    elem.style.height = target.height + "px"
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  function animate(opened: boolean) {
 | 
			
		||||
    const moveToElem = moveTo.data
 | 
			
		||||
    if (opened) {
 | 
			
		||||
      copySizeOf(targetOuter)
 | 
			
		||||
      elem.style.background = "var(--background-color)"
 | 
			
		||||
    } else if (moveToElem !== undefined) {
 | 
			
		||||
      copySizeOf(moveToElem)
 | 
			
		||||
      elem.style.background = "#ffffff00"
 | 
			
		||||
    } else {
 | 
			
		||||
      elem.style.left = "0px"
 | 
			
		||||
      elem.style.top = "0px"
 | 
			
		||||
      elem.style.background = "#ffffff00"
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  onDestroy(isOpened.addCallback((opened) => animate(opened)))
 | 
			
		||||
  onMount(() => requestAnimationFrame(() => animate(isOpened.data)))
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<div class={"pointer-events-none invisible absolute bottom-0 right-0 h-full w-screen p-4 md:p-6"}>
 | 
			
		||||
  <div class="content h-full" bind:this={targetOuter} style="background: red" />
 | 
			
		||||
</div>
 | 
			
		||||
 | 
			
		||||
<div
 | 
			
		||||
  bind:this={elem}
 | 
			
		||||
  class="low-interaction pointer-events-none absolute bottom-0 right-0 rounded-2xl"
 | 
			
		||||
  style="transition: all 0.5s ease-out, background-color 1.4s ease-out; background: var(--background-color);"
 | 
			
		||||
>
 | 
			
		||||
  <!-- Classes should be the same as the 'floatoaver' -->
 | 
			
		||||
</div>
 | 
			
		||||
							
								
								
									
										20
									
								
								src/UI/Base/DrawerLeft.svelte
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								src/UI/Base/DrawerLeft.svelte
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,20 @@
 | 
			
		|||
<script lang="ts">
 | 
			
		||||
  import { Drawer } from "flowbite-svelte"
 | 
			
		||||
  import { sineIn } from "svelte/easing"
 | 
			
		||||
  import { UIEventSource } from "../../Logic/UIEventSource.js"
 | 
			
		||||
 | 
			
		||||
  export let shown: UIEventSource<boolean>;
 | 
			
		||||
  let transitionParams = {
 | 
			
		||||
    x: -320,
 | 
			
		||||
    duration: 200,
 | 
			
		||||
    easing: sineIn
 | 
			
		||||
  };
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
<Drawer placement="left" transitionType="fly" transitionParams={transitionParams} hidden={!$shown} on:close={() => shown.set(false)}>
 | 
			
		||||
  <slot>
 | 
			
		||||
    CONTENTS
 | 
			
		||||
  </slot>
 | 
			
		||||
</Drawer>
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -14,15 +14,10 @@
 | 
			
		|||
  export let arialabel: Translation = undefined
 | 
			
		||||
  export let arialabelDynamic: Store<Translation> = new ImmutableStore(arialabel)
 | 
			
		||||
  let arialabelString = arialabelDynamic.bind((tr) => tr?.current)
 | 
			
		||||
  export let htmlElem: UIEventSource<HTMLElement> = undefined
 | 
			
		||||
  let _htmlElem: HTMLElement
 | 
			
		||||
  $: {
 | 
			
		||||
    htmlElem?.setData(_htmlElem)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<button
 | 
			
		||||
  bind:this={_htmlElem}
 | 
			
		||||
  on:click={(e) => dispatch("click", e)}
 | 
			
		||||
  on:keydown
 | 
			
		||||
  use:ariaLabelStore={arialabelString}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										38
									
								
								src/UI/Base/Page.svelte
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								src/UI/Base/Page.svelte
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,38 @@
 | 
			
		|||
<script lang="ts">
 | 
			
		||||
  // A fake 'page' which can be shown; kind of a modal
 | 
			
		||||
  import { UIEventSource } from "../../Logic/UIEventSource"
 | 
			
		||||
  import { Modal } from "flowbite-svelte"
 | 
			
		||||
 | 
			
		||||
  export let shown: UIEventSource<boolean>
 | 
			
		||||
  let _shown = false
 | 
			
		||||
  export let onlyLink: boolean = false
 | 
			
		||||
  shown.addCallbackAndRun(sh => {
 | 
			
		||||
    _shown = sh
 | 
			
		||||
  })
 | 
			
		||||
  export let fullscreen: boolean = false
 | 
			
		||||
 | 
			
		||||
  const shared = "defaultClass normal-background dark:bg-gray-800 rounded-lg border-gray-200 dark:border-gray-700 border-gray-200 dark:border-gray-700 divide-gray-200 dark:divide-gray-700 shadow-md"
 | 
			
		||||
  let defaultClass = "relative flex flex-col mx-auto w-full divide-y " + shared
 | 
			
		||||
  if (fullscreen) {
 | 
			
		||||
    defaultClass = shared
 | 
			
		||||
  }
 | 
			
		||||
  let dialogClass = "fixed top-0 start-0 end-0 h-modal inset-0 z-50 w-full p-4 flex";
 | 
			
		||||
  if(fullscreen){
 | 
			
		||||
    dialogClass += " h-full-child"
 | 
			
		||||
  }
 | 
			
		||||
  let bodyClass = "h-full p-4 md:p-5 space-y-4 flex-1 overflow-y-auto overscroll-contain"
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
{#if !onlyLink}
 | 
			
		||||
  <Modal open={_shown} on:close={() => shown.set(false)} size="xl" {defaultClass} {bodyClass} {dialogClass} color="none">
 | 
			
		||||
    <slot name="header" slot="header" />
 | 
			
		||||
    <slot />
 | 
			
		||||
    {#if $$slots.footer}
 | 
			
		||||
      <slot name="footer" />
 | 
			
		||||
    {/if}
 | 
			
		||||
  </Modal>
 | 
			
		||||
{:else}
 | 
			
		||||
  <button class="as-link" on:click={() => shown.setData(true)}>
 | 
			
		||||
    <slot name="header" />
 | 
			
		||||
  </button>
 | 
			
		||||
{/if}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,197 +0,0 @@
 | 
			
		|||
<script lang="ts">
 | 
			
		||||
  /**
 | 
			
		||||
   * Thin wrapper around 'TabGroup' which binds the state
 | 
			
		||||
   */
 | 
			
		||||
 | 
			
		||||
  import { Tab, TabGroup, TabList, TabPanel, TabPanels } from "@rgossiaux/svelte-headlessui"
 | 
			
		||||
  import { ImmutableStore, Store, UIEventSource } from "../../Logic/UIEventSource"
 | 
			
		||||
  import { twJoin } from "tailwind-merge"
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * If a condition is given for a certain tab, it will only be shown if this condition is true.
 | 
			
		||||
   * E.g.
 | 
			
		||||
   * condition3 = new ImmutableStore(false) will always hide tab3 (the fourth tab)
 | 
			
		||||
   */
 | 
			
		||||
  const tr = new ImmutableStore(true)
 | 
			
		||||
  export let condition0: Store<boolean> = tr
 | 
			
		||||
  export let condition1: Store<boolean> = tr
 | 
			
		||||
  export let condition2: Store<boolean> = tr
 | 
			
		||||
  export let condition3: Store<boolean> = tr
 | 
			
		||||
  export let condition4: Store<boolean> = tr
 | 
			
		||||
  export let condition5: Store<boolean> = tr
 | 
			
		||||
  export let condition6: Store<boolean> = tr
 | 
			
		||||
  export let tab: UIEventSource<number> = new UIEventSource<number>(0)
 | 
			
		||||
  let tabElements: HTMLElement[] = []
 | 
			
		||||
  $: tabElements[$tab]?.click()
 | 
			
		||||
  $: {
 | 
			
		||||
    if (tabElements[tab.data]) {
 | 
			
		||||
      window.setTimeout(() => tabElements[tab.data].click(), 50)
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  export function getTab() {
 | 
			
		||||
    return tab
 | 
			
		||||
  }
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<div class="tabbedgroup flex h-full w-full">
 | 
			
		||||
  <TabGroup
 | 
			
		||||
    class="flex h-full w-full flex-col"
 | 
			
		||||
    defaultIndex={1}
 | 
			
		||||
    on:change={(e) => {
 | 
			
		||||
      if (e.detail >= 0) {
 | 
			
		||||
        tab.setData(e.detail)
 | 
			
		||||
      }
 | 
			
		||||
    }}
 | 
			
		||||
  >
 | 
			
		||||
    <div class="interactive sticky top-0 flex items-center justify-between">
 | 
			
		||||
      <TabList class="flex flex-wrap">
 | 
			
		||||
        {#if $$slots.title0}
 | 
			
		||||
          <Tab
 | 
			
		||||
            class={({ selected }) => twJoin("tab", selected && "primary", !$condition0 && "hidden")}
 | 
			
		||||
          >
 | 
			
		||||
            <div bind:this={tabElements[0]} class="flex">
 | 
			
		||||
              <slot name="title0">Tab 0</slot>
 | 
			
		||||
            </div>
 | 
			
		||||
          </Tab>
 | 
			
		||||
        {/if}
 | 
			
		||||
        {#if $$slots.title1}
 | 
			
		||||
          <Tab
 | 
			
		||||
            class={({ selected }) => twJoin("tab", selected && "primary", !$condition1 && "hidden")}
 | 
			
		||||
          >
 | 
			
		||||
            <div bind:this={tabElements[1]} class="flex">
 | 
			
		||||
              <slot name="title1" />
 | 
			
		||||
            </div>
 | 
			
		||||
          </Tab>
 | 
			
		||||
        {/if}
 | 
			
		||||
        {#if $$slots.title2}
 | 
			
		||||
          <Tab
 | 
			
		||||
            class={({ selected }) => twJoin("tab", selected && "primary", !$condition2 && "hidden")}
 | 
			
		||||
          >
 | 
			
		||||
            <div bind:this={tabElements[2]} class="flex">
 | 
			
		||||
              <slot name="title2" />
 | 
			
		||||
            </div>
 | 
			
		||||
          </Tab>
 | 
			
		||||
        {/if}
 | 
			
		||||
        {#if $$slots.title3}
 | 
			
		||||
          <Tab
 | 
			
		||||
            class={({ selected }) => twJoin("tab", selected && "primary", !$condition3 && "hidden")}
 | 
			
		||||
          >
 | 
			
		||||
            <div bind:this={tabElements[3]} class="flex">
 | 
			
		||||
              <slot name="title3" />
 | 
			
		||||
            </div>
 | 
			
		||||
          </Tab>
 | 
			
		||||
        {/if}
 | 
			
		||||
        {#if $$slots.title4}
 | 
			
		||||
          <Tab
 | 
			
		||||
            class={({ selected }) => twJoin("tab", selected && "primary", !$condition4 && "hidden")}
 | 
			
		||||
          >
 | 
			
		||||
            <div bind:this={tabElements[4]} class="flex">
 | 
			
		||||
              <slot name="title4" />
 | 
			
		||||
            </div>
 | 
			
		||||
          </Tab>
 | 
			
		||||
        {/if}
 | 
			
		||||
        {#if $$slots.title5}
 | 
			
		||||
          <Tab
 | 
			
		||||
            class={({ selected }) => twJoin("tab", selected && "primary", !$condition5 && "hidden")}
 | 
			
		||||
          >
 | 
			
		||||
            <div bind:this={tabElements[5]} class="flex">
 | 
			
		||||
              <slot name="title5" />
 | 
			
		||||
            </div>
 | 
			
		||||
          </Tab>
 | 
			
		||||
        {/if}
 | 
			
		||||
        {#if $$slots.title6}
 | 
			
		||||
          <Tab
 | 
			
		||||
            class={({ selected }) => twJoin("tab", selected && "primary", !$condition6 && "hidden")}
 | 
			
		||||
          >
 | 
			
		||||
            <div bind:this={tabElements[6]} class="flex">
 | 
			
		||||
              <slot name="title6" />
 | 
			
		||||
            </div>
 | 
			
		||||
          </Tab>
 | 
			
		||||
        {/if}
 | 
			
		||||
      </TabList>
 | 
			
		||||
      <slot name="post-tablist" />
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="normal-background h-full overflow-y-auto">
 | 
			
		||||
      <TabPanels class="tabpanels" defaultIndex={$tab}>
 | 
			
		||||
        <TabPanel class="tabpanel">
 | 
			
		||||
          <slot name="content0">
 | 
			
		||||
            <div>Empty</div>
 | 
			
		||||
          </slot>
 | 
			
		||||
        </TabPanel>
 | 
			
		||||
        <TabPanel class="tabpanel">
 | 
			
		||||
          <slot name="content1">
 | 
			
		||||
            <div />
 | 
			
		||||
          </slot>
 | 
			
		||||
        </TabPanel>
 | 
			
		||||
        <TabPanel class="tabpanel">
 | 
			
		||||
          <slot name="content2">
 | 
			
		||||
            <div />
 | 
			
		||||
          </slot>
 | 
			
		||||
        </TabPanel>
 | 
			
		||||
        <TabPanel class="tabpanel">
 | 
			
		||||
          <slot name="content3">
 | 
			
		||||
            <div />
 | 
			
		||||
          </slot>
 | 
			
		||||
        </TabPanel>
 | 
			
		||||
        <TabPanel class="tabpanel">
 | 
			
		||||
          <slot name="content4">
 | 
			
		||||
            <div />
 | 
			
		||||
          </slot>
 | 
			
		||||
        </TabPanel>
 | 
			
		||||
        <TabPanel class="tabpanel">
 | 
			
		||||
          <slot name="content5">
 | 
			
		||||
            <div />
 | 
			
		||||
          </slot>
 | 
			
		||||
        </TabPanel>
 | 
			
		||||
        <TabPanel class="tabpanel">
 | 
			
		||||
          <slot name="content6">
 | 
			
		||||
            <div />
 | 
			
		||||
          </slot>
 | 
			
		||||
        </TabPanel>
 | 
			
		||||
      </TabPanels>
 | 
			
		||||
    </div>
 | 
			
		||||
  </TabGroup>
 | 
			
		||||
</div>
 | 
			
		||||
 | 
			
		||||
<style>
 | 
			
		||||
  .tabbedgroup {
 | 
			
		||||
    max-height: 100vh;
 | 
			
		||||
    height: 100%;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  :global(.tabpanel) {
 | 
			
		||||
    height: 100%;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  :global(.tabpanels) {
 | 
			
		||||
    height: calc(100% - 2rem);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  :global(.tab) {
 | 
			
		||||
    margin: 0.25rem;
 | 
			
		||||
    padding: 0.25rem;
 | 
			
		||||
    padding-left: 0.75rem;
 | 
			
		||||
    padding-right: 0.75rem;
 | 
			
		||||
    border-radius: 1rem;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  :global(.tab .flex) {
 | 
			
		||||
    align-items: center;
 | 
			
		||||
    gap: 0.25rem;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  :global(.tab span|div) {
 | 
			
		||||
    align-items: center;
 | 
			
		||||
    gap: 0.25rem;
 | 
			
		||||
    display: flex;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  :global(.tab-selected svg) {
 | 
			
		||||
    fill: var(--catch-detail-color-contrast);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  :global(.tab-unselected) {
 | 
			
		||||
    background-color: var(--background-color) !important;
 | 
			
		||||
    color: var(--foreground-color) !important;
 | 
			
		||||
  }
 | 
			
		||||
</style>
 | 
			
		||||
| 
						 | 
				
			
			@ -6,4 +6,7 @@
 | 
			
		|||
  <div class="flex h-full flex-col overflow-auto border-b-2 p-4">
 | 
			
		||||
    <slot />
 | 
			
		||||
  </div>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
  <slot class="border-t-gray-300 mt-1" name="footer" />
 | 
			
		||||
</div>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,103 +0,0 @@
 | 
			
		|||
<script lang="ts">
 | 
			
		||||
  import Translations from "../i18n/Translations"
 | 
			
		||||
  import { Utils } from "../../Utils"
 | 
			
		||||
  import Constants from "../../Models/Constants"
 | 
			
		||||
  import Tr from "../Base/Tr.svelte"
 | 
			
		||||
  import Add from "../../assets/svg/Add.svelte"
 | 
			
		||||
  import Github from "../../assets/svg/Github.svelte"
 | 
			
		||||
  import Mastodon from "../../assets/svg/Mastodon.svelte"
 | 
			
		||||
  import Liberapay from "../../assets/svg/Liberapay.svelte"
 | 
			
		||||
  import { EyeIcon } from "@rgossiaux/svelte-heroicons/solid"
 | 
			
		||||
  import MapillaryLink from "./MapillaryLink.svelte"
 | 
			
		||||
  import OpenJosm from "../Base/OpenJosm.svelte"
 | 
			
		||||
  import OpenIdEditor from "./OpenIdEditor.svelte"
 | 
			
		||||
  import If from "../Base/If.svelte"
 | 
			
		||||
  import Community from "../../assets/svg/Community.svelte"
 | 
			
		||||
  import Bug from "../../assets/svg/Bug.svelte"
 | 
			
		||||
  import ThemeViewState from "../../Models/ThemeViewState"
 | 
			
		||||
  import DocumentChartBar from "@babeard/svelte-heroicons/outline/DocumentChartBar"
 | 
			
		||||
  import DocumentMagnifyingGlass from "@babeard/svelte-heroicons/outline/DocumentMagnifyingGlass"
 | 
			
		||||
 | 
			
		||||
  export let state: ThemeViewState
 | 
			
		||||
 | 
			
		||||
  let layout = state.layout
 | 
			
		||||
  let featureSwitches = state.featureSwitches
 | 
			
		||||
  let showHome = featureSwitches.featureSwitchBackToThemeOverview
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<div class="link-underline links-w-full m-2 flex flex-col gap-y-1">
 | 
			
		||||
  <Tr t={Translations.t.general.aboutMapComplete.intro} />
 | 
			
		||||
 | 
			
		||||
  {#if $showHome}
 | 
			
		||||
    <a class="flex" href={Utils.HomepageLink()}>
 | 
			
		||||
      <Add class="h-6 w-6" />
 | 
			
		||||
      {#if Utils.isIframe}
 | 
			
		||||
        <Tr t={Translations.t.general.seeIndex} />
 | 
			
		||||
      {:else}
 | 
			
		||||
        <Tr t={Translations.t.general.backToIndex} />
 | 
			
		||||
      {/if}
 | 
			
		||||
    </a>
 | 
			
		||||
  {/if}
 | 
			
		||||
 | 
			
		||||
  <a class="flex" href="https://github.com/pietervdvn/MapComplete/" target="_blank">
 | 
			
		||||
    <Github class="h-6 w-6" />
 | 
			
		||||
    <Tr t={Translations.t.general.attribution.gotoSourceCode} />
 | 
			
		||||
  </a>
 | 
			
		||||
 | 
			
		||||
  <a class="flex" href="https://github.com/pietervdvn/MapComplete/issues" target="_blank">
 | 
			
		||||
    <Bug class="h-6 w-6" />
 | 
			
		||||
    <Tr t={Translations.t.general.attribution.openIssueTracker} />
 | 
			
		||||
  </a>
 | 
			
		||||
 | 
			
		||||
  {#if layout.official}
 | 
			
		||||
    <a
 | 
			
		||||
      class="flex"
 | 
			
		||||
      href={"https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Themes/" +
 | 
			
		||||
        layout.id +
 | 
			
		||||
        ".md"}
 | 
			
		||||
      target="_blank"
 | 
			
		||||
    >
 | 
			
		||||
      <DocumentMagnifyingGlass class="h-6 w-6" />
 | 
			
		||||
      <Tr
 | 
			
		||||
        t={Translations.t.general.attribution.openThemeDocumentation.Subs({
 | 
			
		||||
          name: layout.title,
 | 
			
		||||
        })}
 | 
			
		||||
      />
 | 
			
		||||
    </a>
 | 
			
		||||
 | 
			
		||||
    <a class="flex" href={Utils.OsmChaLinkFor(31, layout.id)}>
 | 
			
		||||
      <DocumentChartBar class="h-6 w-6" />
 | 
			
		||||
      <Tr t={Translations.t.general.attribution.openOsmcha.Subs({ theme: layout.title })} />
 | 
			
		||||
    </a>
 | 
			
		||||
  {/if}
 | 
			
		||||
 | 
			
		||||
  <a class="flex" href="https://en.osm.town/@MapComplete" target="_blank">
 | 
			
		||||
    <Mastodon class="h-6 w-6" />
 | 
			
		||||
    <Tr t={Translations.t.general.attribution.followOnMastodon} />
 | 
			
		||||
  </a>
 | 
			
		||||
 | 
			
		||||
  <a class="flex" href="https://liberapay.com/pietervdvn/" target="_blank">
 | 
			
		||||
    <Liberapay class="h-6 w-6" />
 | 
			
		||||
    <Tr t={Translations.t.general.attribution.donate} />
 | 
			
		||||
  </a>
 | 
			
		||||
 | 
			
		||||
  <button class="as-link" on:click={() => state.guistate.communityIndexPanelIsOpened.setData(true)}>
 | 
			
		||||
    <Community class="h-6 w-6" />
 | 
			
		||||
    <Tr t={Translations.t.communityIndex.title} />
 | 
			
		||||
  </button>
 | 
			
		||||
 | 
			
		||||
  <If condition={featureSwitches.featureSwitchEnableLogin}>
 | 
			
		||||
    <OpenIdEditor mapProperties={state.mapProperties} />
 | 
			
		||||
    <OpenJosm {state} />
 | 
			
		||||
    <MapillaryLink large={false} mapProperties={state.mapProperties} />
 | 
			
		||||
  </If>
 | 
			
		||||
 | 
			
		||||
  <button class="as-link" on:click={() => state.guistate.privacyPanelIsOpened.setData(true)}>
 | 
			
		||||
    <EyeIcon class="h-6 w-6 pr-1" />
 | 
			
		||||
    <Tr t={Translations.t.privacy.title} />
 | 
			
		||||
  </button>
 | 
			
		||||
 | 
			
		||||
  <div class="subtle">
 | 
			
		||||
    {Constants.vNumber}
 | 
			
		||||
  </div>
 | 
			
		||||
</div>
 | 
			
		||||
							
								
								
									
										22
									
								
								src/UI/BigComponents/CopyrightAllIcons.svelte
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								src/UI/BigComponents/CopyrightAllIcons.svelte
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,22 @@
 | 
			
		|||
<script lang="ts">
 | 
			
		||||
  import type { SpecialVisualizationState } from "../SpecialVisualization"
 | 
			
		||||
  import IconCopyrightPanel from "./CopyrightSingleIcon.svelte"
 | 
			
		||||
  import licenses from "../../assets/generated/license_info.json"
 | 
			
		||||
  import type SmallLicense from "../../Models/smallLicense"
 | 
			
		||||
 | 
			
		||||
  export let state: SpecialVisualizationState
 | 
			
		||||
 | 
			
		||||
  let layoutToUse = state.layout
 | 
			
		||||
  let iconAttributions: string[] = layoutToUse.getUsedImages()
 | 
			
		||||
 | 
			
		||||
  const allLicenses = {}
 | 
			
		||||
  for (const key in licenses) {
 | 
			
		||||
    const license: SmallLicense = licenses[key]
 | 
			
		||||
    allLicenses[license.path] = license
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
{#each iconAttributions as iconAttribution}
 | 
			
		||||
  <IconCopyrightPanel iconPath={iconAttribution} license={allLicenses[iconAttribution]} />
 | 
			
		||||
{/each}
 | 
			
		||||
| 
						 | 
				
			
			@ -4,11 +4,7 @@
 | 
			
		|||
  import contributors from "../../assets/contributors.json"
 | 
			
		||||
  import translators from "../../assets/translators.json"
 | 
			
		||||
  import { Translation, TypedTranslation } from "../i18n/Translation"
 | 
			
		||||
  import AccordionSingle from "../Flowbite/AccordionSingle.svelte"
 | 
			
		||||
  import Tr from "../Base/Tr.svelte"
 | 
			
		||||
  import IconCopyrightPanel from "./IconCopyrightPanel.svelte"
 | 
			
		||||
  import licenses from "../../assets/generated/license_info.json"
 | 
			
		||||
  import type SmallLicense from "../../Models/smallLicense"
 | 
			
		||||
  import Constants from "../../Models/Constants"
 | 
			
		||||
  import ContributorCount from "../../Logic/ContributorCount"
 | 
			
		||||
  import BaseUIElement from "../BaseUIElement"
 | 
			
		||||
| 
						 | 
				
			
			@ -24,7 +20,6 @@
 | 
			
		|||
  const t = Translations.t.general.attribution
 | 
			
		||||
  const layoutToUse = state.layout
 | 
			
		||||
 | 
			
		||||
  const iconAttributions: string[] = layoutToUse.getUsedImages()
 | 
			
		||||
 | 
			
		||||
  let maintainer: Translation = undefined
 | 
			
		||||
  if (layoutToUse.credits !== undefined && layoutToUse.credits !== "") {
 | 
			
		||||
| 
						 | 
				
			
			@ -53,11 +48,7 @@
 | 
			
		|||
    return Translations.t.general.attribution.attributionBackgroundLayer.Subs(props)
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
  const allLicenses = {}
 | 
			
		||||
  for (const key in licenses) {
 | 
			
		||||
    const license: SmallLicense = licenses[key]
 | 
			
		||||
    allLicenses[license.path] = license
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
  function calculateDataContributions(contributions: Map<string, number>): Translation {
 | 
			
		||||
    if (contributions === undefined) {
 | 
			
		||||
| 
						 | 
				
			
			@ -121,9 +112,6 @@
 | 
			
		|||
</script>
 | 
			
		||||
 | 
			
		||||
<div class="link-underline flex flex-col gap-y-4">
 | 
			
		||||
  <h3>
 | 
			
		||||
    <Tr t={t.attributionTitle} />
 | 
			
		||||
  </h3>
 | 
			
		||||
  <div class="flex items-center gap-x-2">
 | 
			
		||||
    <Osm_logo class="h-8 w-8 shrink-0" />
 | 
			
		||||
    <Tr t={t.attributionContent} />
 | 
			
		||||
| 
						 | 
				
			
			@ -159,14 +147,6 @@
 | 
			
		|||
    <Tr t={codeContributors(translators, t.translatedBy)} />
 | 
			
		||||
  </div>
 | 
			
		||||
 | 
			
		||||
  <AccordionSingle>
 | 
			
		||||
    <div slot="header">
 | 
			
		||||
      <Tr t={t.iconAttribution.title} />
 | 
			
		||||
    </div>
 | 
			
		||||
    {#each iconAttributions as iconAttribution}
 | 
			
		||||
      <IconCopyrightPanel iconPath={iconAttribution} license={allLicenses[iconAttribution]} />
 | 
			
		||||
    {/each}
 | 
			
		||||
  </AccordionSingle>
 | 
			
		||||
 | 
			
		||||
  <div class="self-end">
 | 
			
		||||
    MapComplete {Constants.vNumber}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -9,9 +9,11 @@
 | 
			
		|||
  import Translations from "../i18n/Translations"
 | 
			
		||||
  import Tr from "../Base/Tr.svelte"
 | 
			
		||||
  import Filter from "../../assets/svg/Filter.svelte"
 | 
			
		||||
  import TitledPanel from "../Base/TitledPanel.svelte"
 | 
			
		||||
  import Page from "../Base/Page.svelte"
 | 
			
		||||
 | 
			
		||||
  export let state: ThemeViewState
 | 
			
		||||
  export let onlyLink: boolean
 | 
			
		||||
 | 
			
		||||
  let layout = state.layout
 | 
			
		||||
 | 
			
		||||
  let allEnabled: boolean
 | 
			
		||||
| 
						 | 
				
			
			@ -47,8 +49,8 @@
 | 
			
		|||
  }
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<TitledPanel>
 | 
			
		||||
  <div class="mr-10 flex w-full flex-wrap items-center justify-between" slot="title">
 | 
			
		||||
<Page {onlyLink} shown={state.guistate.pageStates.filter}>
 | 
			
		||||
  <div class="mr-10 flex w-full flex-wrap items-center justify-between" slot="header">
 | 
			
		||||
    <div class="flex">
 | 
			
		||||
      <Filter class="h-6 w-6 pr-2" />
 | 
			
		||||
      <Tr t={Translations.t.general.menu.filter} />
 | 
			
		||||
| 
						 | 
				
			
			@ -80,4 +82,4 @@
 | 
			
		|||
      zoomlevel={state.mapProperties.zoom}
 | 
			
		||||
    />
 | 
			
		||||
  {/each}
 | 
			
		||||
</TitledPanel>
 | 
			
		||||
</Page>
 | 
			
		||||
| 
						 | 
				
			
			@ -15,10 +15,6 @@
 | 
			
		|||
  }
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<AccordionSingle>
 | 
			
		||||
  <div slot="header">
 | 
			
		||||
    <Tr t={t.title} />
 | 
			
		||||
  </div>
 | 
			
		||||
  <Tr t={t.intro} />
 | 
			
		||||
  <table>
 | 
			
		||||
    <tr>
 | 
			
		||||
| 
						 | 
				
			
			@ -47,4 +43,3 @@
 | 
			
		|||
      </tr>
 | 
			
		||||
    {/each}
 | 
			
		||||
  </table>
 | 
			
		||||
</AccordionSingle>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										296
									
								
								src/UI/BigComponents/MenuDrawer.svelte
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										296
									
								
								src/UI/BigComponents/MenuDrawer.svelte
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,296 @@
 | 
			
		|||
<script lang="ts">
 | 
			
		||||
 | 
			
		||||
  // All the relevant links
 | 
			
		||||
  import ThemeViewState from "../../Models/ThemeViewState"
 | 
			
		||||
  import Translations from "../i18n/Translations"
 | 
			
		||||
  import { CogIcon, EyeIcon, HeartIcon } from "@rgossiaux/svelte-heroicons/solid"
 | 
			
		||||
  import Page from "../Base/Page.svelte"
 | 
			
		||||
  import PrivacyPolicy from "./PrivacyPolicy.svelte"
 | 
			
		||||
  import Tr from "../Base/Tr.svelte"
 | 
			
		||||
  import If from "../Base/If.svelte"
 | 
			
		||||
  import CommunityIndexView from "./CommunityIndexView.svelte"
 | 
			
		||||
  import Community from "../../assets/svg/Community.svelte"
 | 
			
		||||
  import LoginToggle from "../Base/LoginToggle.svelte"
 | 
			
		||||
  import { Avatar, Sidebar, SidebarWrapper } from "flowbite-svelte"
 | 
			
		||||
  import HotkeyTable from "./HotkeyTable.svelte"
 | 
			
		||||
  import { Utils } from "../../Utils"
 | 
			
		||||
  import Constants from "../../Models/Constants"
 | 
			
		||||
  import Mastodon from "../../assets/svg/Mastodon.svelte"
 | 
			
		||||
  import Liberapay from "../../assets/svg/Liberapay.svelte"
 | 
			
		||||
  import DocumentMagnifyingGlass from "@babeard/svelte-heroicons/outline/DocumentMagnifyingGlass"
 | 
			
		||||
  import DocumentChartBar from "@babeard/svelte-heroicons/outline/DocumentChartBar"
 | 
			
		||||
  import OpenIdEditor from "./OpenIdEditor.svelte"
 | 
			
		||||
  import OpenJosm from "../Base/OpenJosm.svelte"
 | 
			
		||||
  import MapillaryLink from "./MapillaryLink.svelte"
 | 
			
		||||
  import Github from "../../assets/svg/Github.svelte"
 | 
			
		||||
  import Bug from "../../assets/svg/Bug.svelte"
 | 
			
		||||
  import Add from "../../assets/svg/Add.svelte"
 | 
			
		||||
  import CopyrightPanel from "./CopyrightPanel.svelte"
 | 
			
		||||
  import CopyrightAllIcons from "./CopyrightAllIcons.svelte"
 | 
			
		||||
  import LanguagePicker from "../InputElement/LanguagePicker.svelte"
 | 
			
		||||
  import LoginButton from "../Base/LoginButton.svelte"
 | 
			
		||||
  import SelectedElementView from "./SelectedElementView.svelte"
 | 
			
		||||
  import LayerConfig from "../../Models/ThemeConfig/LayerConfig"
 | 
			
		||||
  import type { LayerConfigJson } from "../../Models/ThemeConfig/Json/LayerConfigJson"
 | 
			
		||||
  import usersettings from "../../assets/generated/layers/usersettings.json"
 | 
			
		||||
  import UserRelatedState from "../../Logic/State/UserRelatedState"
 | 
			
		||||
  import ArrowDownTray from "@babeard/svelte-heroicons/mini/ArrowDownTray"
 | 
			
		||||
  import DownloadPanel from "../DownloadFlow/DownloadPanel.svelte"
 | 
			
		||||
  import Favourites from "../Favourites/Favourites.svelte"
 | 
			
		||||
  import ReviewsOverview from "../Reviews/ReviewsOverview.svelte"
 | 
			
		||||
  import Share from "@babeard/svelte-heroicons/solid/Share"
 | 
			
		||||
  import ShareScreen from "./ShareScreen.svelte"
 | 
			
		||||
  import FilterPage from "./FilterPage.svelte"
 | 
			
		||||
  import RasterLayerOverview from "../Map/RasterLayerOverview.svelte"
 | 
			
		||||
  import ThemeIntroPanel from "./ThemeIntroPanel.svelte"
 | 
			
		||||
  import Marker from "../Map/Marker.svelte"
 | 
			
		||||
  import LogoutButton from "../Base/LogoutButton.svelte"
 | 
			
		||||
 | 
			
		||||
  export let state: ThemeViewState
 | 
			
		||||
  let userdetails = state.osmConnection.userDetails
 | 
			
		||||
 | 
			
		||||
  let usersettingslayer = new LayerConfig(<LayerConfigJson>usersettings, "usersettings", true)
 | 
			
		||||
 | 
			
		||||
  let layout = state.layout
 | 
			
		||||
  let featureSwitches = state.featureSwitches
 | 
			
		||||
  let showHome = featureSwitches.featureSwitchBackToThemeOverview
 | 
			
		||||
  let pg = state.guistate.pageStates
 | 
			
		||||
  export let onlyLink: boolean
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<Sidebar>
 | 
			
		||||
  <SidebarWrapper divClass="link-underline">
 | 
			
		||||
 | 
			
		||||
    <!-- User related: avatar, settings, favourits, logout -->
 | 
			
		||||
    <div>
 | 
			
		||||
      <LoginToggle {state} >
 | 
			
		||||
        <LoginButton osmConnection={state.osmConnection} slot="not-logged-in"></LoginButton>
 | 
			
		||||
        <div class="flex">
 | 
			
		||||
 | 
			
		||||
          <Avatar src={$userdetails.img} rounded />
 | 
			
		||||
          Welcome <b>{$userdetails.name}</b>
 | 
			
		||||
        </div>
 | 
			
		||||
        <LogoutButton osmConnection={state.osmConnection}/>
 | 
			
		||||
      </LoginToggle>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
      <Page {onlyLink} shown={pg.usersettings}>
 | 
			
		||||
        <div class="flex" slot="header">
 | 
			
		||||
          <CogIcon class="h-6 w-6" />
 | 
			
		||||
          <Tr t={UserRelatedState.usersettingsConfig.title.GetRenderValue({})} />
 | 
			
		||||
        </div>
 | 
			
		||||
 | 
			
		||||
        <!-- All shown components are set by 'usersettings.json', which happily uses some special visualisations created specifically for it -->
 | 
			
		||||
        <LoginToggle {state}>
 | 
			
		||||
          <div class="flex flex-col" slot="not-logged-in">
 | 
			
		||||
            <LanguagePicker availableLanguages={layout.language} />
 | 
			
		||||
            <Tr cls="alert" t={Translations.t.userinfo.notLoggedIn} />
 | 
			
		||||
            <LoginButton clss="primary" osmConnection={state.osmConnection} />
 | 
			
		||||
          </div>
 | 
			
		||||
          <SelectedElementView
 | 
			
		||||
            highlightedRendering={state.guistate.highlightedUserSetting}
 | 
			
		||||
            layer={usersettingslayer}
 | 
			
		||||
            selectedElement={{
 | 
			
		||||
                type: "Feature",
 | 
			
		||||
                properties: { id: "settings" },
 | 
			
		||||
                geometry: { type: "Point", coordinates: [0, 0] },
 | 
			
		||||
              }}
 | 
			
		||||
 | 
			
		||||
            {state}
 | 
			
		||||
            tags={state.userRelatedState.preferencesAsTags}
 | 
			
		||||
          />
 | 
			
		||||
        </LoginToggle>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
      </Page>
 | 
			
		||||
 | 
			
		||||
      <LoginToggle {state}>
 | 
			
		||||
      <Page {onlyLink} shown={pg.favourites}>
 | 
			
		||||
 | 
			
		||||
        <div class="flex" slot="header">
 | 
			
		||||
          <HeartIcon class="h-6 w-6" />
 | 
			
		||||
          <Tr t={Translations.t.favouritePoi.tab} />
 | 
			
		||||
        </div>
 | 
			
		||||
        <h3>
 | 
			
		||||
 | 
			
		||||
          <Tr t={Translations.t.favouritePoi.title} />
 | 
			
		||||
        </h3>
 | 
			
		||||
        <div>
 | 
			
		||||
          <Favourites {state} />
 | 
			
		||||
          <h3>
 | 
			
		||||
            <Tr t={Translations.t.reviews.your_reviews} />
 | 
			
		||||
          </h3>
 | 
			
		||||
          <ReviewsOverview {state} />
 | 
			
		||||
        </div>
 | 
			
		||||
      </Page>
 | 
			
		||||
      </LoginToggle>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    <!-- Theme related: documentation links, download, ... -->
 | 
			
		||||
    <div>
 | 
			
		||||
      <h3>
 | 
			
		||||
        About {layout.id}
 | 
			
		||||
      </h3>
 | 
			
		||||
 | 
			
		||||
      <Page {onlyLink} shown={pg.about_theme}>
 | 
			
		||||
        <div class="flex" slot="header">
 | 
			
		||||
          <Marker icons={layout.icon} size="h-4 w-4" />
 | 
			
		||||
          <Tr t={layout.title} />
 | 
			
		||||
        </div>
 | 
			
		||||
        <ThemeIntroPanel {state} />
 | 
			
		||||
      </Page>
 | 
			
		||||
 | 
			
		||||
      <FilterPage {onlyLink} {state} />
 | 
			
		||||
 | 
			
		||||
      <RasterLayerOverview {onlyLink} {state} />
 | 
			
		||||
 | 
			
		||||
      <Page {onlyLink} shown={pg.share}>
 | 
			
		||||
        <div class="flex" slot="header">
 | 
			
		||||
          <Share class="h-4 w-4" />
 | 
			
		||||
          <Tr t={Translations.t.general.sharescreen.title} />
 | 
			
		||||
        </div>
 | 
			
		||||
        <ShareScreen {state} />
 | 
			
		||||
      </Page>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
      {#if state.featureSwitches.featureSwitchEnableExport}
 | 
			
		||||
        <Page {onlyLink} shown={pg.download}>
 | 
			
		||||
          <div slot="header" class="flex">
 | 
			
		||||
            <ArrowDownTray class="h-4 w-4" />
 | 
			
		||||
            <Tr t={Translations.t.general.download.title} />
 | 
			
		||||
          </div>
 | 
			
		||||
          <DownloadPanel {state} />
 | 
			
		||||
        </Page>
 | 
			
		||||
      {/if}
 | 
			
		||||
 | 
			
		||||
      {#if layout.official}
 | 
			
		||||
        <a
 | 
			
		||||
          class="flex"
 | 
			
		||||
          href={"https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Themes/" +
 | 
			
		||||
        layout.id +
 | 
			
		||||
        ".md"}
 | 
			
		||||
          target="_blank"
 | 
			
		||||
        >
 | 
			
		||||
          <DocumentMagnifyingGlass class="h-6 w-6" />
 | 
			
		||||
          <Tr
 | 
			
		||||
            t={Translations.t.general.attribution.openThemeDocumentation.Subs({
 | 
			
		||||
          name: layout.title,
 | 
			
		||||
        })}
 | 
			
		||||
          />
 | 
			
		||||
        </a>
 | 
			
		||||
 | 
			
		||||
        <a class="flex" href={Utils.OsmChaLinkFor(31, layout.id)} target="_blank">
 | 
			
		||||
          <DocumentChartBar class="h-6 w-6" />
 | 
			
		||||
          <Tr t={Translations.t.general.attribution.openOsmcha.Subs({ theme: layout.title })} />
 | 
			
		||||
        </a>
 | 
			
		||||
      {/if}
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
    <!-- Legal info: privacy policy, map attribution, icon attribution -->
 | 
			
		||||
    <div>
 | 
			
		||||
      <h3>Legal</h3>
 | 
			
		||||
 | 
			
		||||
      <Page {onlyLink} shown={pg.copyright}>
 | 
			
		||||
        <Tr slot="header" t={Translations.t.general.attribution.attributionTitle} />
 | 
			
		||||
        <CopyrightPanel {state} />
 | 
			
		||||
      </Page>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
      <Page {onlyLink} shown={pg.copyright_icons}>
 | 
			
		||||
        <div slot="header">
 | 
			
		||||
          <Tr t={ Translations.t.general.attribution.iconAttribution.title} />
 | 
			
		||||
        </div>
 | 
			
		||||
        <CopyrightAllIcons {state} />
 | 
			
		||||
 | 
			
		||||
      </Page>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
      <Page {onlyLink} shown={pg.privacy}>
 | 
			
		||||
        <div class="flex gap-x-2" slot="header">
 | 
			
		||||
          <EyeIcon class="w-6 pr-2" />
 | 
			
		||||
          <Tr t={Translations.t.privacy.title} />
 | 
			
		||||
        </div>
 | 
			
		||||
        <PrivacyPolicy {state} />
 | 
			
		||||
      </Page>
 | 
			
		||||
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
    <!-- Other links and tools for the given location: open iD/JOSM; community index, ... -->
 | 
			
		||||
    <div>
 | 
			
		||||
 | 
			
		||||
      <h3>
 | 
			
		||||
        Discover more
 | 
			
		||||
      </h3>
 | 
			
		||||
 | 
			
		||||
      <Page {onlyLink} shown={pg.community_index}>
 | 
			
		||||
        <div class="flex gap-x-2" slot="header">
 | 
			
		||||
          <Community class="h-6 w-6" />
 | 
			
		||||
          <Tr t={Translations.t.communityIndex.title} />
 | 
			
		||||
        </div>
 | 
			
		||||
        <CommunityIndexView location={state.mapProperties.location} />
 | 
			
		||||
      </Page>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
      <If condition={featureSwitches.featureSwitchEnableLogin}>
 | 
			
		||||
        <OpenIdEditor mapProperties={state.mapProperties} />
 | 
			
		||||
        <OpenJosm {state} />
 | 
			
		||||
        <MapillaryLink large={false} mapProperties={state.mapProperties} />
 | 
			
		||||
      </If>
 | 
			
		||||
 | 
			
		||||
    </div>
 | 
			
		||||
    <!-- About MC: various hints -->
 | 
			
		||||
    <div>
 | 
			
		||||
 | 
			
		||||
      <h3>
 | 
			
		||||
        <Tr t={Translations.t.general.menu.aboutMapComplete} />
 | 
			
		||||
      </h3>
 | 
			
		||||
 | 
			
		||||
      {#if $showHome}
 | 
			
		||||
        <a class="flex" href={Utils.HomepageLink()}>
 | 
			
		||||
          <Add class="h-6 w-6" />
 | 
			
		||||
          {#if Utils.isIframe}
 | 
			
		||||
            <Tr t={Translations.t.general.seeIndex} />
 | 
			
		||||
          {:else}
 | 
			
		||||
            <Tr t={Translations.t.general.backToIndex} />
 | 
			
		||||
          {/if}
 | 
			
		||||
        </a>
 | 
			
		||||
      {/if}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
      <Page {onlyLink} shown={pg.hotkeys}>
 | 
			
		||||
        <Tr t={ Translations.t.hotkeyDocumentation.title} slot="header" />
 | 
			
		||||
        <HotkeyTable />
 | 
			
		||||
      </Page>
 | 
			
		||||
 | 
			
		||||
      <a class="flex" href="https://github.com/pietervdvn/MapComplete/" target="_blank">
 | 
			
		||||
        <Github class="h-6 w-6" />
 | 
			
		||||
        <Tr t={Translations.t.general.attribution.gotoSourceCode} />
 | 
			
		||||
      </a>
 | 
			
		||||
 | 
			
		||||
      <a class="flex" href="https://github.com/pietervdvn/MapComplete/issues" target="_blank">
 | 
			
		||||
        <Bug class="h-6 w-6" />
 | 
			
		||||
        <Tr t={Translations.t.general.attribution.openIssueTracker} />
 | 
			
		||||
      </a>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
      <a class="flex" href="https://en.osm.town/@MapComplete" target="_blank">
 | 
			
		||||
        <Mastodon class="h-6 w-6" />
 | 
			
		||||
        <Tr t={Translations.t.general.attribution.followOnMastodon} />
 | 
			
		||||
      </a>
 | 
			
		||||
 | 
			
		||||
      <a class="flex" href="https://liberapay.com/pietervdvn/" target="_blank">
 | 
			
		||||
        <Liberapay class="h-6 w-6" />
 | 
			
		||||
        <Tr t={Translations.t.general.attribution.donate} />
 | 
			
		||||
      </a>
 | 
			
		||||
 | 
			
		||||
      <div class="subtle">
 | 
			
		||||
        {Constants.vNumber}
 | 
			
		||||
      </div>
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
  </SidebarWrapper>
 | 
			
		||||
</Sidebar>
 | 
			
		||||
| 
						 | 
				
			
			@ -20,7 +20,7 @@
 | 
			
		|||
 | 
			
		||||
<MapControlButton
 | 
			
		||||
  arialabel={Translations.t.general.labels.background}
 | 
			
		||||
  on:click={() => state.guistate.backgroundLayerSelectionIsOpened.setData(true)}
 | 
			
		||||
  on:click={() => state.guistate.pageStates.background.setData(true)}
 | 
			
		||||
  {htmlElem}
 | 
			
		||||
>
 | 
			
		||||
  <StyleLoadingIndicator map={map ?? state.map} rasterLayer={state.mapProperties.rasterLayer}>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -12,8 +12,6 @@
 | 
			
		|||
  import { GeoLocationState } from "../../Logic/State/GeoLocationState"
 | 
			
		||||
  import If from "../Base/If.svelte"
 | 
			
		||||
  import { ExclamationTriangleIcon } from "@babeard/svelte-heroicons/mini"
 | 
			
		||||
  import Location_refused from "../../assets/svg/Location_refused.svelte"
 | 
			
		||||
  import Location from "../../assets/svg/Location.svelte"
 | 
			
		||||
  import ChevronDoubleLeft from "@babeard/svelte-heroicons/solid/ChevronDoubleLeft"
 | 
			
		||||
  import GeolocationIndicator from "./GeolocationIndicator.svelte"
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -38,7 +36,7 @@
 | 
			
		|||
    const glstate = state.geolocation.geolocationState
 | 
			
		||||
    if (glstate.currentGPSLocation.data !== undefined) {
 | 
			
		||||
      const c: GeolocationCoordinates = glstate.currentGPSLocation.data
 | 
			
		||||
      state.guistate.themeIsOpened.setData(false)
 | 
			
		||||
      state.guistate.pageStates.about_theme.setData(false)
 | 
			
		||||
      const coor = { lon: c.longitude, lat: c.latitude }
 | 
			
		||||
      state.mapProperties.location.setData(coor)
 | 
			
		||||
    }
 | 
			
		||||
| 
						 | 
				
			
			@ -65,7 +63,7 @@
 | 
			
		|||
    <Tr t={layout.descriptionTail} />
 | 
			
		||||
 | 
			
		||||
    <!-- Buttons: open map, go to location, search -->
 | 
			
		||||
    <NextButton clss="primary w-full" on:click={() => state.guistate.themeIsOpened.setData(false)}>
 | 
			
		||||
    <NextButton clss="primary w-full" on:click={() => state.guistate.pageStates.about_theme.setData(false)}>
 | 
			
		||||
      <div class="flex w-full flex-col items-center">
 | 
			
		||||
        <div class="flex w-full justify-center text-2xl">
 | 
			
		||||
          <Tr t={Translations.t.general.openTheMap} />
 | 
			
		||||
| 
						 | 
				
			
			@ -96,7 +94,7 @@
 | 
			
		|||
          <div style="min-width: 16rem; " class="grow">
 | 
			
		||||
            <Geosearch
 | 
			
		||||
              bounds={state.mapProperties.bounds}
 | 
			
		||||
              on:searchCompleted={() => state.guistate.themeIsOpened.setData(false)}
 | 
			
		||||
              on:searchCompleted={() => state.guistate.pageStates.about_theme.setData(false)}
 | 
			
		||||
              on:searchIsValid={(event) => {
 | 
			
		||||
                searchEnabled = event.detail
 | 
			
		||||
              }}
 | 
			
		||||
| 
						 | 
				
			
			@ -138,20 +136,10 @@
 | 
			
		|||
    {/if}
 | 
			
		||||
  </div>
 | 
			
		||||
 | 
			
		||||
  {#if Utils.isIframe}
 | 
			
		||||
    <div class="link-underline flex justify-end">
 | 
			
		||||
      <a href="https://mapcomplete.org" target="_blank">
 | 
			
		||||
        <Tr t={Translations.t.general.poweredByMapComplete} />
 | 
			
		||||
      </a>
 | 
			
		||||
    </div>
 | 
			
		||||
  {:else}
 | 
			
		||||
    <If condition={state.featureSwitches.featureSwitchBackToThemeOverview}>
 | 
			
		||||
      <div class="link-underline m-2 mx-4 flex w-full">
 | 
			
		||||
        <a class="flex w-fit items-center justify-end" href={Utils.HomepageLink()}>
 | 
			
		||||
          <ChevronDoubleLeft class="h-4 w-4" />
 | 
			
		||||
          <Tr t={Translations.t.general.backToIndex} />
 | 
			
		||||
        </a>
 | 
			
		||||
      </div>
 | 
			
		||||
    </If>
 | 
			
		||||
  {/if}
 | 
			
		||||
  <div class="link-underline flex justify-end text-sm mt-8">
 | 
			
		||||
    <a href="https://mapcomplete.org" target="_blank">
 | 
			
		||||
      <Tr t={Translations.t.general.poweredByMapComplete} />
 | 
			
		||||
    </a>
 | 
			
		||||
  </div>
 | 
			
		||||
 | 
			
		||||
</div>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -65,10 +65,6 @@
 | 
			
		|||
  <Tr cls="alert" t={Translations.t.general.download.toMuch} />
 | 
			
		||||
{:else}
 | 
			
		||||
  <div class="flex w-full flex-col" />
 | 
			
		||||
  <h3>
 | 
			
		||||
    <Tr t={t.title} />
 | 
			
		||||
  </h3>
 | 
			
		||||
 | 
			
		||||
  <DownloadButton
 | 
			
		||||
    {state}
 | 
			
		||||
    extension="geojson"
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -61,7 +61,6 @@ export class MapLibreAdaptor implements MapProperties, ExportableMap {
 | 
			
		|||
        if (!MapLibreAdaptor.pmtilesInited) {
 | 
			
		||||
            maplibregl.addProtocol("pmtiles", new Protocol().tile)
 | 
			
		||||
            MapLibreAdaptor.pmtilesInited = true
 | 
			
		||||
            console.log("PM-tiles protocol added" + "")
 | 
			
		||||
        }
 | 
			
		||||
        this._maplibreMap = maplibreMap
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -4,32 +4,29 @@
 | 
			
		|||
   */
 | 
			
		||||
  import { Store, UIEventSource } from "../../Logic/UIEventSource"
 | 
			
		||||
  import type { RasterLayerPolygon } from "../../Models/RasterLayers"
 | 
			
		||||
  import type { MapProperties } from "../../Models/MapProperties"
 | 
			
		||||
  import { Map as MlMap } from "maplibre-gl"
 | 
			
		||||
  import RasterLayerPicker from "./RasterLayerPicker.svelte"
 | 
			
		||||
  import type { EliCategory } from "../../Models/RasterLayerProperties"
 | 
			
		||||
  import UserRelatedState from "../../Logic/State/UserRelatedState"
 | 
			
		||||
  import Translations from "../i18n/Translations"
 | 
			
		||||
  import Tr from "../Base/Tr.svelte"
 | 
			
		||||
  import TitledPanel from "../Base/TitledPanel.svelte"
 | 
			
		||||
  import Loading from "../Base/Loading.svelte"
 | 
			
		||||
  import Page from "../Base/Page.svelte"
 | 
			
		||||
  import ThemeViewState from "../../Models/ThemeViewState"
 | 
			
		||||
 | 
			
		||||
  export let availableLayers: { store: Store<RasterLayerPolygon[]> }
 | 
			
		||||
  export let mapproperties: MapProperties
 | 
			
		||||
  export let userstate: UserRelatedState
 | 
			
		||||
  export let map: Store<MlMap>
 | 
			
		||||
  export let state: ThemeViewState
 | 
			
		||||
 | 
			
		||||
  let map = state.map
 | 
			
		||||
  let mapproperties = state.mapProperties
 | 
			
		||||
  let userstate = state.userRelatedState
 | 
			
		||||
  let shown = state.guistate.pageStates.background
 | 
			
		||||
  let availableLayers: { store: Store<RasterLayerPolygon[]> } = state.availableLayers
 | 
			
		||||
  let _availableLayers = availableLayers.store
 | 
			
		||||
  /**
 | 
			
		||||
   * Used to toggle the background layers on/off
 | 
			
		||||
   */
 | 
			
		||||
  export let visible: UIEventSource<boolean> = undefined
 | 
			
		||||
 | 
			
		||||
  type CategoryType = "photo" | "map" | "other" | "osmbasedmap"
 | 
			
		||||
  const categories: Record<CategoryType, EliCategory[]> = {
 | 
			
		||||
    photo: ["photo", "historicphoto"],
 | 
			
		||||
    map: ["map", "historicmap"],
 | 
			
		||||
    other: ["other", "elevation"],
 | 
			
		||||
    osmbasedmap: ["osmbasedmap"],
 | 
			
		||||
    osmbasedmap: ["osmbasedmap"]
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  function availableForCategory(type: CategoryType): Store<RasterLayerPolygon[]> {
 | 
			
		||||
| 
						 | 
				
			
			@ -45,27 +42,31 @@
 | 
			
		|||
  const otherLayers = availableForCategory("other")
 | 
			
		||||
 | 
			
		||||
  function onApply() {
 | 
			
		||||
    visible.setData(false)
 | 
			
		||||
    shown.setData(false)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  function getPref(type: CategoryType): undefined | UIEventSource<string> {
 | 
			
		||||
    return userstate?.osmConnection?.GetPreference("preferred-layer-" + type)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  export let onlyLink: boolean
 | 
			
		||||
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<TitledPanel>
 | 
			
		||||
  <Tr slot="title" t={Translations.t.general.backgroundMap} />
 | 
			
		||||
<Page {onlyLink} shown={shown} fullscreen={true}>
 | 
			
		||||
  <Tr slot="header" t={Translations.t.general.backgroundMap} />
 | 
			
		||||
  {#if $_availableLayers?.length < 1}
 | 
			
		||||
    <Loading />
 | 
			
		||||
  {:else}
 | 
			
		||||
    <div class="grid h-full w-full grid-cols-1 gap-2 md:grid-cols-2">
 | 
			
		||||
 | 
			
		||||
    <div class="flex gap-x-2 flex-col sm:flex-row gap-y-2" style="height: calc( 100% - 5rem)">
 | 
			
		||||
      <RasterLayerPicker
 | 
			
		||||
        availableLayers={$photoLayers}
 | 
			
		||||
        favourite={getPref("photo")}
 | 
			
		||||
        {map}
 | 
			
		||||
        {mapproperties}
 | 
			
		||||
        on:appliedLayer={onApply}
 | 
			
		||||
        {visible}
 | 
			
		||||
        {shown}
 | 
			
		||||
      />
 | 
			
		||||
      <RasterLayerPicker
 | 
			
		||||
        availableLayers={$mapLayers}
 | 
			
		||||
| 
						 | 
				
			
			@ -73,7 +74,7 @@
 | 
			
		|||
        {map}
 | 
			
		||||
        {mapproperties}
 | 
			
		||||
        on:appliedLayer={onApply}
 | 
			
		||||
        {visible}
 | 
			
		||||
        {shown}
 | 
			
		||||
      />
 | 
			
		||||
      <RasterLayerPicker
 | 
			
		||||
        availableLayers={$osmbasedmapLayers}
 | 
			
		||||
| 
						 | 
				
			
			@ -81,7 +82,7 @@
 | 
			
		|||
        {map}
 | 
			
		||||
        {mapproperties}
 | 
			
		||||
        on:appliedLayer={onApply}
 | 
			
		||||
        {visible}
 | 
			
		||||
        {shown}
 | 
			
		||||
      />
 | 
			
		||||
      <RasterLayerPicker
 | 
			
		||||
        availableLayers={$otherLayers}
 | 
			
		||||
| 
						 | 
				
			
			@ -89,8 +90,8 @@
 | 
			
		|||
        {map}
 | 
			
		||||
        {mapproperties}
 | 
			
		||||
        on:appliedLayer={onApply}
 | 
			
		||||
        {visible}
 | 
			
		||||
        {shown}
 | 
			
		||||
      />
 | 
			
		||||
    </div>
 | 
			
		||||
  {/if}
 | 
			
		||||
</TitledPanel>
 | 
			
		||||
</Page>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -13,7 +13,7 @@
 | 
			
		|||
  export let mapproperties: MapProperties
 | 
			
		||||
  export let map: Store<MlMap>
 | 
			
		||||
 | 
			
		||||
  export let visible: Store<boolean> = undefined
 | 
			
		||||
  export let shown: Store<boolean> = undefined
 | 
			
		||||
 | 
			
		||||
  let dispatch = createEventDispatcher<{ appliedLayer }>()
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -48,10 +48,10 @@
 | 
			
		|||
 | 
			
		||||
  let rasterLayerOnMap = UIEventSource.feedFrom(rasterLayer)
 | 
			
		||||
 | 
			
		||||
  if (visible) {
 | 
			
		||||
  if (shown) {
 | 
			
		||||
    onDestroy(
 | 
			
		||||
      visible?.addCallbackAndRunD((visible) => {
 | 
			
		||||
        if (visible) {
 | 
			
		||||
      shown?.addCallbackAndRunD((shown) => {
 | 
			
		||||
        if (shown) {
 | 
			
		||||
          rasterLayerOnMap.setData(rasterLayer.data ?? availableLayers[0])
 | 
			
		||||
        } else {
 | 
			
		||||
          rasterLayerOnMap.setData(undefined)
 | 
			
		||||
| 
						 | 
				
			
			@ -85,7 +85,7 @@
 | 
			
		|||
          rasterLayer={rasterLayerOnMap}
 | 
			
		||||
          placedOverMap={map}
 | 
			
		||||
          placedOverMapProperties={mapproperties}
 | 
			
		||||
          {visible}
 | 
			
		||||
          visible={shown}
 | 
			
		||||
        />
 | 
			
		||||
      </span>
 | 
			
		||||
    </button>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -13,65 +13,40 @@
 | 
			
		|||
  import type { MapProperties } from "../Models/MapProperties"
 | 
			
		||||
  import Geosearch from "./BigComponents/Geosearch.svelte"
 | 
			
		||||
  import Translations from "./i18n/Translations"
 | 
			
		||||
  import usersettings from "../assets/generated/layers/usersettings.json"
 | 
			
		||||
  import {
 | 
			
		||||
    CogIcon,
 | 
			
		||||
    EyeIcon,
 | 
			
		||||
    HeartIcon,
 | 
			
		||||
    MenuIcon,
 | 
			
		||||
    XCircleIcon,
 | 
			
		||||
    MenuIcon
 | 
			
		||||
  } from "@rgossiaux/svelte-heroicons/solid"
 | 
			
		||||
  import Tr from "./Base/Tr.svelte"
 | 
			
		||||
  import CommunityIndexView from "./BigComponents/CommunityIndexView.svelte"
 | 
			
		||||
  import FloatOver from "./Base/FloatOver.svelte"
 | 
			
		||||
  import Constants from "../Models/Constants"
 | 
			
		||||
  import TabbedGroup from "./Base/TabbedGroup.svelte"
 | 
			
		||||
  import UserRelatedState from "../Logic/State/UserRelatedState"
 | 
			
		||||
  import LoginToggle from "./Base/LoginToggle.svelte"
 | 
			
		||||
  import LoginButton from "./Base/LoginButton.svelte"
 | 
			
		||||
  import CopyrightPanel from "./BigComponents/CopyrightPanel.svelte"
 | 
			
		||||
  import DownloadPanel from "./DownloadFlow/DownloadPanel.svelte"
 | 
			
		||||
  import ModalRight from "./Base/ModalRight.svelte"
 | 
			
		||||
  import LevelSelector from "./BigComponents/LevelSelector.svelte"
 | 
			
		||||
  import ThemeIntroPanel from "./BigComponents/ThemeIntroPanel.svelte"
 | 
			
		||||
  import type { RasterLayerPolygon } from "../Models/RasterLayers"
 | 
			
		||||
  import { AvailableRasterLayers } from "../Models/RasterLayers"
 | 
			
		||||
  import RasterLayerOverview from "./Map/RasterLayerOverview.svelte"
 | 
			
		||||
  import IfHidden from "./Base/IfHidden.svelte"
 | 
			
		||||
  import { onDestroy } from "svelte"
 | 
			
		||||
  import OpenBackgroundSelectorButton from "./BigComponents/OpenBackgroundSelectorButton.svelte"
 | 
			
		||||
  import StateIndicator from "./BigComponents/StateIndicator.svelte"
 | 
			
		||||
  import ShareScreen from "./BigComponents/ShareScreen.svelte"
 | 
			
		||||
  import UploadingImageCounter from "./Image/UploadingImageCounter.svelte"
 | 
			
		||||
  import PendingChangesIndicator from "./BigComponents/PendingChangesIndicator.svelte"
 | 
			
		||||
  import Cross from "../assets/svg/Cross.svelte"
 | 
			
		||||
  import LanguagePicker from "./InputElement/LanguagePicker.svelte"
 | 
			
		||||
  import Min from "../assets/svg/Min.svelte"
 | 
			
		||||
  import Plus from "../assets/svg/Plus.svelte"
 | 
			
		||||
  import Filter from "../assets/svg/Filter.svelte"
 | 
			
		||||
  import Community from "../assets/svg/Community.svelte"
 | 
			
		||||
  import Favourites from "./Favourites/Favourites.svelte"
 | 
			
		||||
  import ImageOperations from "./Image/ImageOperations.svelte"
 | 
			
		||||
  import VisualFeedbackPanel from "./BigComponents/VisualFeedbackPanel.svelte"
 | 
			
		||||
  import { Orientation } from "../Sensors/Orientation"
 | 
			
		||||
  import GeolocationIndicator from "./BigComponents/GeolocationIndicator.svelte"
 | 
			
		||||
  import Compass_arrow from "../assets/svg/Compass_arrow.svelte"
 | 
			
		||||
  import ReverseGeocoding from "./BigComponents/ReverseGeocoding.svelte"
 | 
			
		||||
  import FilterPanel from "./BigComponents/FilterPanel.svelte"
 | 
			
		||||
  import PrivacyPolicy from "./BigComponents/PrivacyPolicy.svelte"
 | 
			
		||||
  import { BBox } from "../Logic/BBox"
 | 
			
		||||
  import ReviewsOverview from "./Reviews/ReviewsOverview.svelte"
 | 
			
		||||
  import ExtraLinkButton from "./BigComponents/ExtraLinkButton.svelte"
 | 
			
		||||
  import CloseAnimation from "./Base/CloseAnimation.svelte"
 | 
			
		||||
  import { LastClickFeatureSource } from "../Logic/FeatureSource/Sources/LastClickFeatureSource"
 | 
			
		||||
  import ArrowDownTray from "@babeard/svelte-heroicons/mini/ArrowDownTray"
 | 
			
		||||
  import Share from "@babeard/svelte-heroicons/solid/Share"
 | 
			
		||||
  import ChevronRight from "@babeard/svelte-heroicons/solid/ChevronRight"
 | 
			
		||||
  import Marker from "./Map/Marker.svelte"
 | 
			
		||||
  import AboutMapComplete from "./BigComponents/AboutMapComplete.svelte"
 | 
			
		||||
  import HotkeyTable from "./BigComponents/HotkeyTable.svelte"
 | 
			
		||||
  import SelectedElementPanel from "./Base/SelectedElementPanel.svelte"
 | 
			
		||||
  import type { LayerConfigJson } from "../Models/ThemeConfig/Json/LayerConfigJson"
 | 
			
		||||
  import MenuDrawer from "./BigComponents/MenuDrawer.svelte"
 | 
			
		||||
  import DrawerLeft from "./Base/DrawerLeft.svelte"
 | 
			
		||||
 | 
			
		||||
  export let state: ThemeViewState
 | 
			
		||||
  let layout = state.layout
 | 
			
		||||
| 
						 | 
				
			
			@ -120,7 +95,6 @@
 | 
			
		|||
  let visualFeedback = state.visualFeedback
 | 
			
		||||
  let viewport: UIEventSource<HTMLDivElement> = new UIEventSource<HTMLDivElement>(undefined)
 | 
			
		||||
  let mapproperties: MapProperties = state.mapProperties
 | 
			
		||||
  let usersettingslayer = new LayerConfig(<LayerConfigJson>usersettings, "usersettings", true)
 | 
			
		||||
  state.mapProperties.installCustomKeyboardHandler(viewport)
 | 
			
		||||
  let canZoomIn = mapproperties.maxzoom.map(
 | 
			
		||||
    (mz) => mapproperties.zoom.data < mz,
 | 
			
		||||
| 
						 | 
				
			
			@ -144,7 +118,7 @@
 | 
			
		|||
    const bottomRight = mlmap.unproject([rect.right, rect.bottom])
 | 
			
		||||
    const bbox = new BBox([
 | 
			
		||||
      [topLeft.lng, topLeft.lat],
 | 
			
		||||
      [bottomRight.lng, bottomRight.lat],
 | 
			
		||||
      [bottomRight.lng, bottomRight.lat]
 | 
			
		||||
    ])
 | 
			
		||||
    state.visualFeedbackViewportBounds.setData(bbox)
 | 
			
		||||
  }
 | 
			
		||||
| 
						 | 
				
			
			@ -156,7 +130,6 @@
 | 
			
		|||
    updateViewport()
 | 
			
		||||
  })
 | 
			
		||||
  let featureSwitches: FeatureSwitchState = state.featureSwitches
 | 
			
		||||
  let availableLayers = state.availableLayers
 | 
			
		||||
  let currentViewLayer: LayerConfig = layout.layers.find((l) => l.id === "current_view")
 | 
			
		||||
  let rasterLayer: Store<RasterLayerPolygon> = state.mapProperties.rasterLayer
 | 
			
		||||
  let rasterLayerName =
 | 
			
		||||
| 
						 | 
				
			
			@ -168,8 +141,13 @@
 | 
			
		|||
    })
 | 
			
		||||
  )
 | 
			
		||||
  let previewedImage = state.previewedImage
 | 
			
		||||
 | 
			
		||||
  let addNewFeatureMode = state.userRelatedState.addNewFeatureMode
 | 
			
		||||
  let gpsAvailable = state.geolocation.geolocationState.gpsAvailable
 | 
			
		||||
  let gpsButtonAriaLabel = state.geolocation.geolocationState.gpsStateExplanation
 | 
			
		||||
  let debug = state.featureSwitches.featureSwitchIsDebugging
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
  debug.addCallbackAndRun((dbg) => {
 | 
			
		||||
    if (dbg) {
 | 
			
		||||
      document.body.classList.add("debug")
 | 
			
		||||
| 
						 | 
				
			
			@ -190,26 +168,7 @@
 | 
			
		|||
    animation?.cameraAnimation(mlmap)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Needed for the animations
 | 
			
		||||
   */
 | 
			
		||||
  let openMapButton: UIEventSource<HTMLElement> = new UIEventSource<HTMLElement>(undefined)
 | 
			
		||||
  let openMenuButton: UIEventSource<HTMLElement> = new UIEventSource<HTMLElement>(undefined)
 | 
			
		||||
  let openCurrentViewLayerButton: UIEventSource<HTMLElement> = new UIEventSource<HTMLElement>(
 | 
			
		||||
    undefined
 | 
			
		||||
  )
 | 
			
		||||
  let _openNewElementButton: HTMLButtonElement
 | 
			
		||||
  let openNewElementButton: UIEventSource<HTMLElement> = new UIEventSource<HTMLElement>(undefined)
 | 
			
		||||
 | 
			
		||||
  $: {
 | 
			
		||||
    openNewElementButton.setData(_openNewElementButton)
 | 
			
		||||
  }
 | 
			
		||||
  let openFilterButton: UIEventSource<HTMLElement> = new UIEventSource<HTMLElement>(undefined)
 | 
			
		||||
  let openBackgroundButton: UIEventSource<HTMLElement> = new UIEventSource<HTMLElement>(undefined)
 | 
			
		||||
  let addNewFeatureMode = state.userRelatedState.addNewFeatureMode
 | 
			
		||||
 | 
			
		||||
  let gpsAvailable = state.geolocation.geolocationState.gpsAvailable
 | 
			
		||||
  let gpsButtonAriaLabel = state.geolocation.geolocationState.gpsStateExplanation
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<main>
 | 
			
		||||
| 
						 | 
				
			
			@ -255,9 +214,8 @@
 | 
			
		|||
    <div class="float-left m-1 flex flex-col sm:mt-2">
 | 
			
		||||
      <If condition={state.featureSwitches.featureSwitchWelcomeMessage}>
 | 
			
		||||
        <MapControlButton
 | 
			
		||||
          on:click={() => state.guistate.themeIsOpened.setData(true)}
 | 
			
		||||
          on:click={() => state.guistate.pageStates.about_theme.set(true)}
 | 
			
		||||
          on:keydown={forwardEventToMap}
 | 
			
		||||
          htmlElem={openMapButton}
 | 
			
		||||
        >
 | 
			
		||||
          <div
 | 
			
		||||
            class="m-0.5 mx-1 flex cursor-pointer items-center max-[480px]:w-full sm:mx-1 md:mx-2"
 | 
			
		||||
| 
						 | 
				
			
			@ -272,9 +230,8 @@
 | 
			
		|||
 | 
			
		||||
        <MapControlButton
 | 
			
		||||
          arialabel={Translations.t.general.labels.menu}
 | 
			
		||||
          on:click={() => state.guistate.menuIsOpened.setData(true)}
 | 
			
		||||
          on:click={() => {console.log("Opening...."); state.guistate.menuIsOpened.setData(true)}}
 | 
			
		||||
          on:keydown={forwardEventToMap}
 | 
			
		||||
          htmlElem={openMenuButton}
 | 
			
		||||
        >
 | 
			
		||||
          <MenuIcon class="h-8 w-8 cursor-pointer" />
 | 
			
		||||
        </MapControlButton>
 | 
			
		||||
| 
						 | 
				
			
			@ -285,7 +242,6 @@
 | 
			
		|||
            state.selectCurrentView()
 | 
			
		||||
          }}
 | 
			
		||||
          on:keydown={forwardEventToMap}
 | 
			
		||||
          htmlElem={openCurrentViewLayerButton}
 | 
			
		||||
        >
 | 
			
		||||
          <div class="h-8 w-8 cursor-pointer">
 | 
			
		||||
            <ToSvelte construct={() => currentViewLayer.defaultIcon()} />
 | 
			
		||||
| 
						 | 
				
			
			@ -322,7 +278,6 @@
 | 
			
		|||
            <button
 | 
			
		||||
              class="low-interaction pointer-events-auto w-fit"
 | 
			
		||||
              class:disabled={$currentZoom < Constants.minZoomLevelToAddNewPoint}
 | 
			
		||||
              bind:this={_openNewElementButton}
 | 
			
		||||
              on:click={() => {
 | 
			
		||||
                state.openNewDialog()
 | 
			
		||||
              }}
 | 
			
		||||
| 
						 | 
				
			
			@ -346,7 +301,6 @@
 | 
			
		|||
              arialabel={Translations.t.general.labels.filter}
 | 
			
		||||
              on:click={() => state.guistate.openFilterView()}
 | 
			
		||||
              on:keydown={forwardEventToMap}
 | 
			
		||||
              htmlElem={openFilterButton}
 | 
			
		||||
            >
 | 
			
		||||
              <Filter class="h-6 w-6" />
 | 
			
		||||
            </MapControlButton>
 | 
			
		||||
| 
						 | 
				
			
			@ -355,25 +309,17 @@
 | 
			
		|||
            <OpenBackgroundSelectorButton
 | 
			
		||||
              hideTooltip={true}
 | 
			
		||||
              {state}
 | 
			
		||||
              htmlElem={openBackgroundButton}
 | 
			
		||||
            />
 | 
			
		||||
          </If>
 | 
			
		||||
          <a
 | 
			
		||||
            class="bg-black-transparent pointer-events-auto ml-1 h-fit max-h-12 cursor-pointer self-end self-center overflow-hidden rounded-2xl px-1 text-white opacity-50 hover:opacity-100"
 | 
			
		||||
            on:click={() => {
 | 
			
		||||
              if (featureSwitches.featureSwitchWelcomeMessage.data) {
 | 
			
		||||
                state.guistate.themeViewTab.setData("copyright")
 | 
			
		||||
                state.guistate.themeIsOpened.setData(true)
 | 
			
		||||
              } else {
 | 
			
		||||
                state.guistate.copyrightPanelIsOpened.setData(true)
 | 
			
		||||
              }
 | 
			
		||||
            }}
 | 
			
		||||
          <button
 | 
			
		||||
            class="as-link bg-black-transparent pointer-events-auto ml-1 h-fit max-h-12 cursor-pointer self-end self-center overflow-hidden rounded-2xl px-1 text-white opacity-50 hover:opacity-100"
 | 
			
		||||
            on:click={() => {state.guistate.pageStates.copyright.set(true)}}
 | 
			
		||||
          >
 | 
			
		||||
            © <span class="hidden sm:inline sm:pr-2">
 | 
			
		||||
              OpenStreetMap
 | 
			
		||||
              <span class="hidden w-24 md:inline md:pr-2">, {rasterLayerName}</span>
 | 
			
		||||
            </span>
 | 
			
		||||
          </a>
 | 
			
		||||
          </button>
 | 
			
		||||
        </div>
 | 
			
		||||
      </div>
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -443,6 +389,11 @@
 | 
			
		|||
    <svelte:fragment slot="error" />
 | 
			
		||||
  </LoginToggle>
 | 
			
		||||
 | 
			
		||||
  <DrawerLeft shown={state.guistate.menuIsOpened}>
 | 
			
		||||
    <MenuDrawer onlyLink={true} {state} />
 | 
			
		||||
  </DrawerLeft>
 | 
			
		||||
  <MenuDrawer onlyLink={false} {state} />
 | 
			
		||||
 | 
			
		||||
  {#if $selectedElement !== undefined && $selectedLayer !== undefined && !$selectedLayer.popupInFloatover}
 | 
			
		||||
    <!-- right modal with the selected element view -->
 | 
			
		||||
    <ModalRight
 | 
			
		||||
| 
						 | 
				
			
			@ -485,225 +436,5 @@
 | 
			
		|||
    </FloatOver>
 | 
			
		||||
  </If>
 | 
			
		||||
 | 
			
		||||
  <!-- big theme menu -->
 | 
			
		||||
  <If condition={state.guistate.themeIsOpened}>
 | 
			
		||||
    <FloatOver on:close={() => state.guistate.themeIsOpened.setData(false)}>
 | 
			
		||||
      <span slot="close-button"><!-- Disable the close button --></span>
 | 
			
		||||
      <TabbedGroup
 | 
			
		||||
        condition1={state.featureSwitches.featureSwitchEnableExport}
 | 
			
		||||
        tab={state.guistate.themeViewTabIndex}
 | 
			
		||||
      >
 | 
			
		||||
        <div slot="post-tablist">
 | 
			
		||||
          <XCircleIcon
 | 
			
		||||
            class="mr-2 h-8 w-8"
 | 
			
		||||
            on:click={() => state.guistate.themeIsOpened.setData(false)}
 | 
			
		||||
          />
 | 
			
		||||
        </div>
 | 
			
		||||
 | 
			
		||||
        <div class="flex" slot="title0">
 | 
			
		||||
          <Marker icons={layout.icon} size="h-4 w-4" />
 | 
			
		||||
          <Tr t={layout.title} />
 | 
			
		||||
        </div>
 | 
			
		||||
 | 
			
		||||
        <div class="m-4 h-full" slot="content0">
 | 
			
		||||
          <ThemeIntroPanel {state} />
 | 
			
		||||
        </div>
 | 
			
		||||
 | 
			
		||||
        <div class="flex" slot="title1">
 | 
			
		||||
          <If condition={state.featureSwitches.featureSwitchEnableExport}>
 | 
			
		||||
            <ArrowDownTray class="h-4 w-4" />
 | 
			
		||||
            <Tr t={Translations.t.general.download.title} />
 | 
			
		||||
          </If>
 | 
			
		||||
        </div>
 | 
			
		||||
        <div class="m-4" slot="content1">
 | 
			
		||||
          <DownloadPanel {state} />
 | 
			
		||||
        </div>
 | 
			
		||||
 | 
			
		||||
        <div slot="title2">
 | 
			
		||||
          <Tr t={Translations.t.general.attribution.title} />
 | 
			
		||||
        </div>
 | 
			
		||||
 | 
			
		||||
        <div slot="content2" class="m-2 flex flex-col">
 | 
			
		||||
          <CopyrightPanel {state} />
 | 
			
		||||
        </div>
 | 
			
		||||
 | 
			
		||||
        <div class="flex" slot="title3">
 | 
			
		||||
          <Share class="h-4 w-4" />
 | 
			
		||||
          <Tr t={Translations.t.general.sharescreen.title} />
 | 
			
		||||
        </div>
 | 
			
		||||
        <div class="m-2" slot="content3">
 | 
			
		||||
          <ShareScreen {state} />
 | 
			
		||||
        </div>
 | 
			
		||||
      </TabbedGroup>
 | 
			
		||||
    </FloatOver>
 | 
			
		||||
  </If>
 | 
			
		||||
 | 
			
		||||
  <!-- Filterpane -->
 | 
			
		||||
  <If condition={state.guistate.filtersPanelIsOpened}>
 | 
			
		||||
    <FloatOver on:close={() => state.guistate.filtersPanelIsOpened.setData(false)}>
 | 
			
		||||
      <FilterPanel {state} />
 | 
			
		||||
    </FloatOver>
 | 
			
		||||
  </If>
 | 
			
		||||
 | 
			
		||||
  <!-- background layer selector -->
 | 
			
		||||
  <IfHidden condition={state.guistate.backgroundLayerSelectionIsOpened}>
 | 
			
		||||
    <FloatOver
 | 
			
		||||
      on:close={() => {
 | 
			
		||||
        state.guistate.backgroundLayerSelectionIsOpened.setData(false)
 | 
			
		||||
      }}
 | 
			
		||||
    >
 | 
			
		||||
      <RasterLayerOverview
 | 
			
		||||
        {availableLayers}
 | 
			
		||||
        map={state.map}
 | 
			
		||||
        mapproperties={state.mapProperties}
 | 
			
		||||
        userstate={state.userRelatedState}
 | 
			
		||||
        visible={state.guistate.backgroundLayerSelectionIsOpened}
 | 
			
		||||
      />
 | 
			
		||||
    </FloatOver>
 | 
			
		||||
  </IfHidden>
 | 
			
		||||
 | 
			
		||||
  <!-- Menu page -->
 | 
			
		||||
  <If condition={state.guistate.menuIsOpened}>
 | 
			
		||||
    <FloatOver on:close={() => state.guistate.menuIsOpened.setData(false)}>
 | 
			
		||||
      <span slot="close-button"><!-- Hide the default close button --></span>
 | 
			
		||||
      <TabbedGroup
 | 
			
		||||
        condition1={featureSwitches.featureSwitchEnableLogin}
 | 
			
		||||
        condition2={state.featureSwitches.featureSwitchCommunityIndex}
 | 
			
		||||
        tab={state.guistate.menuViewTabIndex}
 | 
			
		||||
      >
 | 
			
		||||
        <div slot="post-tablist">
 | 
			
		||||
          <XCircleIcon
 | 
			
		||||
            class="mr-2 h-8 w-8"
 | 
			
		||||
            on:click={() => state.guistate.menuIsOpened.setData(false)}
 | 
			
		||||
          />
 | 
			
		||||
        </div>
 | 
			
		||||
        <div class="flex" slot="title0">
 | 
			
		||||
          <Tr t={Translations.t.general.menu.aboutMapComplete} />
 | 
			
		||||
        </div>
 | 
			
		||||
 | 
			
		||||
        <div slot="content0" class="flex flex-col">
 | 
			
		||||
          <AboutMapComplete {state} />
 | 
			
		||||
          <div class="m-2 flex flex-col">
 | 
			
		||||
            <HotkeyTable />
 | 
			
		||||
          </div>
 | 
			
		||||
        </div>
 | 
			
		||||
 | 
			
		||||
        <div class="flex" slot="title1">
 | 
			
		||||
          <CogIcon class="h-6 w-6" />
 | 
			
		||||
          <Tr t={UserRelatedState.usersettingsConfig.title.GetRenderValue({})} />
 | 
			
		||||
        </div>
 | 
			
		||||
 | 
			
		||||
        <div class="links-as-button py-8" slot="content1">
 | 
			
		||||
          <!-- All shown components are set by 'usersettings.json', which happily uses some special visualisations created specifically for it -->
 | 
			
		||||
          <LoginToggle {state}>
 | 
			
		||||
            <div class="flex flex-col" slot="not-logged-in">
 | 
			
		||||
              <LanguagePicker availableLanguages={layout.language} />
 | 
			
		||||
              <Tr cls="alert" t={Translations.t.userinfo.notLoggedIn} />
 | 
			
		||||
              <LoginButton clss="primary" osmConnection={state.osmConnection} />
 | 
			
		||||
            </div>
 | 
			
		||||
            <SelectedElementView
 | 
			
		||||
              highlightedRendering={state.guistate.highlightedUserSetting}
 | 
			
		||||
              layer={usersettingslayer}
 | 
			
		||||
              selectedElement={{
 | 
			
		||||
                type: "Feature",
 | 
			
		||||
                properties: { id: "settings" },
 | 
			
		||||
                geometry: { type: "Point", coordinates: [0, 0] },
 | 
			
		||||
              }}
 | 
			
		||||
              {state}
 | 
			
		||||
              tags={state.userRelatedState.preferencesAsTags}
 | 
			
		||||
            />
 | 
			
		||||
          </LoginToggle>
 | 
			
		||||
        </div>
 | 
			
		||||
 | 
			
		||||
        <div class="flex" slot="title2">
 | 
			
		||||
          <HeartIcon class="h-6 w-6" />
 | 
			
		||||
          <Tr t={Translations.t.favouritePoi.tab} />
 | 
			
		||||
        </div>
 | 
			
		||||
 | 
			
		||||
        <div class="m-2 flex flex-col" slot="content2">
 | 
			
		||||
          <h3>
 | 
			
		||||
            <Tr t={Translations.t.favouritePoi.title} />
 | 
			
		||||
          </h3>
 | 
			
		||||
          <Favourites {state} />
 | 
			
		||||
          <h3>
 | 
			
		||||
            <Tr t={Translations.t.reviews.your_reviews} />
 | 
			
		||||
          </h3>
 | 
			
		||||
          <ReviewsOverview {state} />
 | 
			
		||||
        </div>
 | 
			
		||||
      </TabbedGroup>
 | 
			
		||||
    </FloatOver>
 | 
			
		||||
  </If>
 | 
			
		||||
 | 
			
		||||
  <!-- Privacy policy -->
 | 
			
		||||
  <If condition={state.guistate.privacyPanelIsOpened}>
 | 
			
		||||
    <FloatOver on:close={() => state.guistate.privacyPanelIsOpened.setData(false)}>
 | 
			
		||||
      <div class="flex h-full flex-col overflow-hidden">
 | 
			
		||||
        <h2 class="low-interaction m-0 flex items-center p-4 drop-shadow-md">
 | 
			
		||||
          <EyeIcon class="w-6 pr-2" />
 | 
			
		||||
          <Tr t={Translations.t.privacy.title} />
 | 
			
		||||
        </h2>
 | 
			
		||||
        <div class="overflow-auto p-4">
 | 
			
		||||
          <PrivacyPolicy {state} />
 | 
			
		||||
        </div>
 | 
			
		||||
      </div>
 | 
			
		||||
    </FloatOver>
 | 
			
		||||
  </If>
 | 
			
		||||
 | 
			
		||||
  <!-- Attribution, copyright and about MapComplete (no menu case) -->
 | 
			
		||||
  <If condition={state.guistate.copyrightPanelIsOpened}>
 | 
			
		||||
    <FloatOver on:close={() => state.guistate.copyrightPanelIsOpened.setData(false)}>
 | 
			
		||||
      <div class="flex h-full flex-col overflow-hidden">
 | 
			
		||||
        <h1 class="low-interaction m-0 flex items-center p-4 drop-shadow-md">
 | 
			
		||||
          <Tr t={Translations.t.general.attribution.title} />
 | 
			
		||||
        </h1>
 | 
			
		||||
        <div class="overflow-auto p-4">
 | 
			
		||||
          <h2>
 | 
			
		||||
            <Tr t={Translations.t.general.menu.aboutMapComplete} />
 | 
			
		||||
          </h2>
 | 
			
		||||
          <AboutMapComplete {state} />
 | 
			
		||||
          <CopyrightPanel {state} />
 | 
			
		||||
        </div>
 | 
			
		||||
      </div>
 | 
			
		||||
    </FloatOver>
 | 
			
		||||
  </If>
 | 
			
		||||
 | 
			
		||||
  <!-- Community index -->
 | 
			
		||||
  <If condition={state.guistate.communityIndexPanelIsOpened}>
 | 
			
		||||
    <FloatOver on:close={() => state.guistate.communityIndexPanelIsOpened.setData(false)}>
 | 
			
		||||
      <div class="flex h-full flex-col overflow-hidden">
 | 
			
		||||
        <h2 class="low-interaction m-0 flex items-center p-4">
 | 
			
		||||
          <Community class="h-6 w-6" />
 | 
			
		||||
          <Tr t={Translations.t.communityIndex.title} />
 | 
			
		||||
        </h2>
 | 
			
		||||
        <div class="overflow-auto p-4">
 | 
			
		||||
          <CommunityIndexView location={state.mapProperties.location} />
 | 
			
		||||
        </div>
 | 
			
		||||
      </div>
 | 
			
		||||
    </FloatOver>
 | 
			
		||||
  </If>
 | 
			
		||||
 | 
			
		||||
  <CloseAnimation isOpened={state.guistate.themeIsOpened} moveTo={openMapButton} debug="theme" />
 | 
			
		||||
  <CloseAnimation isOpened={state.guistate.menuIsOpened} moveTo={openMenuButton} debug="menu" />
 | 
			
		||||
  <CloseAnimation
 | 
			
		||||
    isOpened={selectedLayer.map((sl) => sl !== undefined && sl === currentViewLayer)}
 | 
			
		||||
    moveTo={openCurrentViewLayerButton}
 | 
			
		||||
    debug="currentViewLayer"
 | 
			
		||||
  />
 | 
			
		||||
  <CloseAnimation
 | 
			
		||||
    isOpened={selectedElement.map(
 | 
			
		||||
      (sl) => sl !== undefined && sl?.properties?.id === LastClickFeatureSource.newPointElementId
 | 
			
		||||
    )}
 | 
			
		||||
    moveTo={openNewElementButton}
 | 
			
		||||
    debug="newElement"
 | 
			
		||||
  />
 | 
			
		||||
  <CloseAnimation
 | 
			
		||||
    isOpened={state.guistate.filtersPanelIsOpened}
 | 
			
		||||
    moveTo={openFilterButton}
 | 
			
		||||
    debug="filter"
 | 
			
		||||
  />
 | 
			
		||||
  <CloseAnimation
 | 
			
		||||
    isOpened={state.guistate.backgroundLayerSelectionIsOpened}
 | 
			
		||||
    moveTo={openBackgroundButton}
 | 
			
		||||
    debug="bg"
 | 
			
		||||
  />
 | 
			
		||||
</main>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue