Chore: formatting

This commit is contained in:
Pieter Vander Vennet 2023-09-28 23:50:27 +02:00
parent 8ef9b48e2b
commit 8a3f7a012d
97 changed files with 3350 additions and 2136 deletions

View file

@ -1,13 +1,14 @@
<script lang="ts">
import type { Writable } from "svelte/store";
import type { Writable } from "svelte/store"
/**
* For some stupid reason, it is very hard to bind inputs
*/
export let selected: Writable<boolean>;
let _c: boolean = selected.data ?? true;
$: selected.set(_c);
export let selected: Writable<boolean>
let _c: boolean = selected.data ?? true
$: selected.set(_c)
</script>
<label class="no-image-background flex gap-1">
<input bind:checked={_c} type="checkbox" />
<slot />

View file

@ -1,40 +1,48 @@
<script lang="ts">
import { createEventDispatcher } from "svelte"
import { twMerge } from "tailwind-merge"
import { createEventDispatcher } from "svelte";
import { twMerge } from "tailwind-merge";
export let accept: string
export let multiple: boolean = true
export let accept: string;
export let multiple: boolean = true;
const dispatcher = createEventDispatcher<{ submit: FileList }>();
export let cls: string = "";
let drawAttention = false;
let inputElement: HTMLInputElement;
let id = Math.random() * 1000000000 + "";
const dispatcher = createEventDispatcher<{ submit: FileList }>()
export let cls: string = ""
let drawAttention = false
let inputElement: HTMLInputElement
let id = Math.random() * 1000000000 + ""
</script>
<form>
<label class={twMerge(cls, drawAttention ? "glowing-shadow" : "")} for={"fileinput"+id}>
<label class={twMerge(cls, drawAttention ? "glowing-shadow" : "")} for={"fileinput" + id}>
<slot />
</label>
<input {accept} bind:this={inputElement} class="hidden" id={"fileinput" + id} {multiple} name="file-input"
on:change|preventDefault={() => {
drawAttention = false;
dispatcher("submit", inputElement.files)}}
on:dragend={ () => {drawAttention = false}}
on:dragover|preventDefault|stopPropagation={(e) => {
console.log("Dragging over!")
drawAttention = true
e.dataTransfer.drop = "copy"
}}
on:dragstart={ () => {drawAttention = false}}
on:drop|preventDefault|stopPropagation={(e) => {
console.log("Got a 'drop'")
drawAttention = false
dispatcher("submit", e.dataTransfer.files)
}}
type="file"
>
<input
{accept}
bind:this={inputElement}
class="hidden"
id={"fileinput" + id}
{multiple}
name="file-input"
on:change|preventDefault={() => {
drawAttention = false
dispatcher("submit", inputElement.files)
}}
on:dragend={() => {
drawAttention = false
}}
on:dragover|preventDefault|stopPropagation={(e) => {
console.log("Dragging over!")
drawAttention = true
e.dataTransfer.drop = "copy"
}}
on:dragstart={() => {
drawAttention = false
}}
on:drop|preventDefault|stopPropagation={(e) => {
console.log("Got a 'drop'")
drawAttention = false
dispatcher("submit", e.dataTransfer.files)
}}
type="file"
/>
</form>

View file

@ -2,7 +2,7 @@
/**
* Given an HTML string, properly shows this
*/
import { Utils } from "../../Utils";
import { Utils } from "../../Utils"
export let src: string

View file

@ -1,12 +1,12 @@
<script lang="ts">
import ToSvelte from "./ToSvelte.svelte";
import Svg from "../../Svg";
import { twMerge } from "tailwind-merge";
import ToSvelte from "./ToSvelte.svelte"
import Svg from "../../Svg"
import { twMerge } from "tailwind-merge"
export let cls : string = undefined
export let cls: string = undefined
</script>
<div class={twMerge( "flex p-1 pl-2", cls)}>
<div class={twMerge("flex p-1 pl-2", cls)}>
<div class="min-w-6 h-6 w-6 animate-spin self-center">
<ToSvelte construct={Svg.loading_svg()} />
</div>

View file

@ -55,26 +55,26 @@
{#if filteredLayer.layerDef.name}
<div bind:this={mainElem} class="mb-1.5">
<Checkbox selected={isDisplayed} >
<If condition={filteredLayer.isDisplayed}>
<ToSvelte
construct={() => layer.defaultIcon()?.SetClass("block h-6 w-6 no-image-background")}
/>
<ToSvelte
slot="else"
construct={() =>
<Checkbox selected={isDisplayed}>
<If condition={filteredLayer.isDisplayed}>
<ToSvelte
construct={() => layer.defaultIcon()?.SetClass("block h-6 w-6 no-image-background")}
/>
<ToSvelte
slot="else"
construct={() =>
layer.defaultIcon()?.SetClass("block h-6 w-6 no-image-background opacity-50")}
/>
</If>
/>
</If>
{filteredLayer.layerDef.name}
{filteredLayer.layerDef.name}
{#if $zoomlevel < layer.minzoom}
{#if $zoomlevel < layer.minzoom}
<span class="alert">
<Tr t={Translations.t.general.layerSelection.zoomInToSeeThisLayer} />
</span>
{/if}
</Checkbox>
{/if}
</Checkbox>
{#if $isDisplayed && filteredLayer.layerDef.filters?.length > 0}
<div id="subfilters" class="ml-4 flex flex-col gap-y-1">
@ -82,9 +82,9 @@
<div>
<!-- There are three (and a half) modes of filters: a single checkbox, a radio button/dropdown or with searchable fields -->
{#if filter.options.length === 1 && filter.options[0].fields.length === 0}
<Checkbox selected={getBooleanStateFor(filter)} >
{filter.options[0].question}
</Checkbox>
<Checkbox selected={getBooleanStateFor(filter)}>
{filter.options[0].question}
</Checkbox>
{/if}
{#if filter.options.length === 1 && filter.options[0].fields.length > 0}

View file

@ -1,44 +1,45 @@
<script lang="ts">
import Translations from "../i18n/Translations";
import Svg from "../../Svg";
import Tr from "../Base/Tr.svelte";
import NextButton from "../Base/NextButton.svelte";
import Geosearch from "./Geosearch.svelte";
import ToSvelte from "../Base/ToSvelte.svelte";
import ThemeViewState from "../../Models/ThemeViewState";
import { Store, UIEventSource } from "../../Logic/UIEventSource";
import { SearchIcon } from "@rgossiaux/svelte-heroicons/solid";
import { twJoin } from "tailwind-merge";
import { Utils } from "../../Utils";
import type { GeolocationPermissionState } from "../../Logic/State/GeoLocationState";
import Translations from "../i18n/Translations"
import Svg from "../../Svg"
import Tr from "../Base/Tr.svelte"
import NextButton from "../Base/NextButton.svelte"
import Geosearch from "./Geosearch.svelte"
import ToSvelte from "../Base/ToSvelte.svelte"
import ThemeViewState from "../../Models/ThemeViewState"
import { Store, UIEventSource } from "../../Logic/UIEventSource"
import { SearchIcon } from "@rgossiaux/svelte-heroicons/solid"
import { twJoin } from "tailwind-merge"
import { Utils } from "../../Utils"
import type { GeolocationPermissionState } from "../../Logic/State/GeoLocationState"
/**
* The theme introduction panel
*/
export let state: ThemeViewState;
let layout = state.layout;
let selectedElement = state.selectedElement;
let selectedLayer = state.selectedLayer;
export let state: ThemeViewState
let layout = state.layout
let selectedElement = state.selectedElement
let selectedLayer = state.selectedLayer
let triggerSearch: UIEventSource<any> = new UIEventSource<any>(undefined);
let searchEnabled = false;
let triggerSearch: UIEventSource<any> = new UIEventSource<any>(undefined)
let searchEnabled = false
let geopermission: Store<GeolocationPermissionState> = state.geolocation.geolocationState.permission;
let currentGPSLocation = state.geolocation.geolocationState.currentGPSLocation;
let geopermission: Store<GeolocationPermissionState> =
state.geolocation.geolocationState.permission
let currentGPSLocation = state.geolocation.geolocationState.currentGPSLocation
geopermission.addCallback(perm => console.log(">>>> Permission", perm));
geopermission.addCallback((perm) => console.log(">>>> Permission", perm))
function jumpToCurrentLocation() {
const glstate = state.geolocation.geolocationState;
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);
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;
glstate.requestPermission()
return
}
}
</script>
@ -69,22 +70,29 @@
</button>
<!-- No geolocation granted - we don't show the button -->
{:else if $geopermission === "requested"}
<button class="flex w-full items-center gap-x-2 disabled" on:click={jumpToCurrentLocation}>
<button class="disabled flex w-full items-center gap-x-2" on:click={jumpToCurrentLocation}>
<!-- Even though disabled, when clicking we request the location again in case the contributor dismissed the location popup -->
<ToSvelte construct={Svg.crosshair_svg().SetClass("w-8 h-8").SetStyle("animation: 3s linear 0s infinite normal none running spin;")} />
<ToSvelte
construct={Svg.crosshair_svg()
.SetClass("w-8 h-8")
.SetStyle("animation: 3s linear 0s infinite normal none running spin;")}
/>
<Tr t={Translations.t.general.waitingForGeopermission} />
</button>
{:else if $geopermission === "denied"}
<button class="flex w-full items-center gap-x-2 disabled">
<button class="disabled flex w-full items-center gap-x-2">
<ToSvelte construct={Svg.location_refused_svg().SetClass("w-8 h-8")} />
<Tr t={Translations.t.general.geopermissionDenied} />
</button>
{:else }
<button class="flex w-full items-center gap-x-2 disabled">
<ToSvelte construct={Svg.crosshair_svg().SetClass("w-8 h-8").SetStyle("animation: 3s linear 0s infinite normal none running spin;")} />
{:else}
<button class="disabled flex w-full items-center gap-x-2">
<ToSvelte
construct={Svg.crosshair_svg()
.SetClass("w-8 h-8")
.SetStyle("animation: 3s linear 0s infinite normal none running spin;")}
/>
<Tr t={Translations.t.general.waitingForLocation} />
</button>
{/if}
<div class=".button low-interaction m-1 flex w-full items-center gap-x-2 rounded border p-2">

View file

@ -1,63 +1,63 @@
<script lang="ts">/**
* Shows an 'upload'-button which will start the upload for this feature
*/
<script lang="ts">
/**
* Shows an 'upload'-button which will start the upload for this feature
*/
import type { SpecialVisualizationState } from "../SpecialVisualization";
import { Store } from "../../Logic/UIEventSource";
import type { OsmTags } from "../../Models/OsmFeature";
import LoginToggle from "../Base/LoginToggle.svelte";
import Translations from "../i18n/Translations";
import Tr from "../Base/Tr.svelte";
import UploadingImageCounter from "./UploadingImageCounter.svelte";
import FileSelector from "../Base/FileSelector.svelte";
import ToSvelte from "../Base/ToSvelte.svelte";
import Svg from "../../Svg";
import type { SpecialVisualizationState } from "../SpecialVisualization"
import { Store } from "../../Logic/UIEventSource"
import type { OsmTags } from "../../Models/OsmFeature"
import LoginToggle from "../Base/LoginToggle.svelte"
import Translations from "../i18n/Translations"
import Tr from "../Base/Tr.svelte"
import UploadingImageCounter from "./UploadingImageCounter.svelte"
import FileSelector from "../Base/FileSelector.svelte"
import ToSvelte from "../Base/ToSvelte.svelte"
import Svg from "../../Svg"
export let state: SpecialVisualizationState;
export let state: SpecialVisualizationState
export let tags: Store<OsmTags>;
/**
* Image to show in the button
* NOT the image to upload!
*/
export let image: string = undefined;
if (image === "") {
image = undefined;
}
export let labelText: string = undefined;
const t = Translations.t.image;
export let tags: Store<OsmTags>
/**
* Image to show in the button
* NOT the image to upload!
*/
export let image: string = undefined
if (image === "") {
image = undefined
}
export let labelText: string = undefined
const t = Translations.t.image
let licenseStore = state.userRelatedState.imageLicense;
let licenseStore = state.userRelatedState.imageLicense
function handleFiles(files: FileList) {
for (let i = 0; i < files.length; i++) {
const file = files.item(i);
console.log("Got file", file.name)
try {
state.imageUploadManager.uploadImageAndApply(file, tags);
} catch (e) {
alert(e);
function handleFiles(files: FileList) {
for (let i = 0; i < files.length; i++) {
const file = files.item(i)
console.log("Got file", file.name)
try {
state.imageUploadManager.uploadImageAndApply(file, tags)
} catch (e) {
alert(e)
}
}
}
}
</script>
<LoginToggle {state}>
<Tr slot="not-logged-in" t={t.pleaseLogin} />
<div class="flex flex-col">
<UploadingImageCounter {state} {tags} />
<FileSelector accept="image/*" cls="button border-2 text-2xl" multiple={true}
on:submit={e => handleFiles(e.detail)}>
<FileSelector
accept="image/*"
cls="button border-2 text-2xl"
multiple={true}
on:submit={(e) => handleFiles(e.detail)}
>
<div class="flex items-center">
{#if image !== undefined}
<img src={image} />
{:else}
<ToSvelte construct={ Svg.camera_plus_svg().SetClass("block w-12 h-12 p-1 text-4xl ")} />
<ToSvelte construct={Svg.camera_plus_svg().SetClass("block w-12 h-12 p-1 text-4xl ")} />
{/if}
{#if labelText}
{labelText}
@ -68,10 +68,14 @@ function handleFiles(files: FileList) {
</FileSelector>
<div class="text-sm">
<Tr t={t.respectPrivacy} />
<a class="cursor-pointer" on:click={() => {state.guistate.openUsersettings("picture-license")}}>
<Tr t={t.currentLicense.Subs({license: $licenseStore})} />
<a
class="cursor-pointer"
on:click={() => {
state.guistate.openUsersettings("picture-license")
}}
>
<Tr t={t.currentLicense.Subs({ license: $licenseStore })} />
</a>
</div>
</div>
</LoginToggle>

View file

@ -1,32 +1,28 @@
<script lang="ts">/**
* Shows information about how much images are uploaded for the given feature
*/
<script lang="ts">
/**
* Shows information about how much images are uploaded for the given feature
*/
import type { SpecialVisualizationState } from "../SpecialVisualization";
import { Store } from "../../Logic/UIEventSource";
import type { OsmTags } from "../../Models/OsmFeature";
import Translations from "../i18n/Translations";
import Tr from "../Base/Tr.svelte";
import Loading from "../Base/Loading.svelte";
export let state: SpecialVisualizationState;
export let tags: Store<OsmTags>;
const featureId = tags.data.id;
const {
uploadStarted,
uploadFinished,
retried,
failed
} = state.imageUploadManager.getCountsFor(featureId);
const t = Translations.t.image;
import type { SpecialVisualizationState } from "../SpecialVisualization"
import { Store } from "../../Logic/UIEventSource"
import type { OsmTags } from "../../Models/OsmFeature"
import Translations from "../i18n/Translations"
import Tr from "../Base/Tr.svelte"
import Loading from "../Base/Loading.svelte"
export let state: SpecialVisualizationState
export let tags: Store<OsmTags>
const featureId = tags.data.id
const { uploadStarted, uploadFinished, retried, failed } =
state.imageUploadManager.getCountsFor(featureId)
const t = Translations.t.image
</script>
{#if $uploadStarted == 1}
{#if $uploadFinished == 1 }
{#if $uploadFinished == 1}
<Tr cls="thanks" t={t.upload.one.done} />
{:else if $failed == 1}
<div class="flex flex-col alert">
<div class="alert flex flex-col">
<Tr cls="self-center" t={t.upload.one.failed} />
<Tr t={t.upload.failReasons} />
<Tr t={t.upload.failReasonsAdvanced} />
@ -35,30 +31,34 @@ const t = Translations.t.image;
<Loading cls="alert">
<Tr t={t.upload.one.retrying} />
</Loading>
{:else }
{:else}
<Loading cls="alert">
<Tr t={t.upload.one.uploading} />
</Loading>
{/if}
{:else if $uploadStarted > 1}
{#if ($uploadFinished + $failed) == $uploadStarted && $uploadFinished > 0}
<Tr cls="thanks" t={t.upload.multiple.done.Subs({count: $uploadFinished})} />
{#if $uploadFinished + $failed == $uploadStarted && $uploadFinished > 0}
<Tr cls="thanks" t={t.upload.multiple.done.Subs({ count: $uploadFinished })} />
{:else if $uploadFinished == 0}
<Loading cls="alert">
<Tr t={t.upload.multiple.uploading.Subs({count: $uploadStarted})} />
<Tr t={t.upload.multiple.uploading.Subs({ count: $uploadStarted })} />
</Loading>
{:else if $uploadFinished > 0}
<Loading cls="alert">
<Tr t={t.upload.multiple.partiallyDone.Subs({count: $uploadStarted - $uploadFinished, done: $uploadFinished})} />
<Tr
t={t.upload.multiple.partiallyDone.Subs({
count: $uploadStarted - $uploadFinished,
done: $uploadFinished,
})}
/>
</Loading>
{/if}
{#if $failed > 0}
<div class="flex flex-col alert">
<div class="alert flex flex-col">
{#if failed === 1}
<Tr cls="self-center" t={t.upload.one.failed} />
{:else}
<Tr cls="self-center" t={t.upload.multiple.someFailed.Subs({count: $failed})} />
<Tr cls="self-center" t={t.upload.multiple.someFailed.Subs({ count: $failed })} />
{/if}
<Tr t={t.upload.failReasons} />
<Tr t={t.upload.failReasonsAdvanced} />

View file

@ -432,7 +432,7 @@ export class MapLibreAdaptor implements MapProperties, ExportableMap {
)
}
await this.awaitStyleIsLoaded()
if(this._currentRasterLayer !== background?.id){
if (this._currentRasterLayer !== background?.id) {
this.removeCurrentLayer(map)
}
this._currentRasterLayer = background?.id
@ -459,10 +459,10 @@ export class MapLibreAdaptor implements MapProperties, ExportableMap {
map.rotateTo(0, { duration: 0 })
map.setPitch(0)
map.dragRotate.disable()
map.touchZoomRotate.disableRotation();
map.touchZoomRotate.disableRotation()
} else {
map.dragRotate.enable()
map.touchZoomRotate.enableRotation();
map.touchZoomRotate.enableRotation()
}
}

View file

@ -1,76 +1,79 @@
<script lang="ts">
import Translations from "../i18n/Translations";
import Tr from "../Base/Tr.svelte";
import PlantNetSpeciesList from "./PlantNetSpeciesList.svelte";
import { ImmutableStore, Store, UIEventSource } from "../../Logic/UIEventSource";
import type { PlantNetSpeciesMatch } from "../../Logic/Web/PlantNet";
import PlantNet from "../../Logic/Web/PlantNet";
import { XCircleIcon } from "@babeard/svelte-heroicons/solid";
import BackButton from "../Base/BackButton.svelte";
import NextButton from "../Base/NextButton.svelte";
import WikipediaPanel from "../Wikipedia/WikipediaPanel.svelte";
import { createEventDispatcher } from "svelte";
import ToSvelte from "../Base/ToSvelte.svelte";
import Svg from "../../Svg";
import Translations from "../i18n/Translations"
import Tr from "../Base/Tr.svelte"
import PlantNetSpeciesList from "./PlantNetSpeciesList.svelte"
import { ImmutableStore, Store, UIEventSource } from "../../Logic/UIEventSource"
import type { PlantNetSpeciesMatch } from "../../Logic/Web/PlantNet"
import PlantNet from "../../Logic/Web/PlantNet"
import { XCircleIcon } from "@babeard/svelte-heroicons/solid"
import BackButton from "../Base/BackButton.svelte"
import NextButton from "../Base/NextButton.svelte"
import WikipediaPanel from "../Wikipedia/WikipediaPanel.svelte"
import { createEventDispatcher } from "svelte"
import ToSvelte from "../Base/ToSvelte.svelte"
import Svg from "../../Svg"
/**
* The main entry point for the plantnet wizard
*/
const t = Translations.t.plantDetection;
const t = Translations.t.plantDetection
/**
* All the URLs pointing to images of the selected feature.
* We need to feed them into Plantnet when applicable
*/
export let imageUrls: Store<string[]>;
export let onConfirm: (wikidataId: string) => void;
const dispatch = createEventDispatcher<{ selected: string }>();
let collapsedMode = true;
let options: UIEventSource<PlantNetSpeciesMatch[]> = new UIEventSource<PlantNetSpeciesMatch[]>(undefined);
export let imageUrls: Store<string[]>
export let onConfirm: (wikidataId: string) => void
const dispatch = createEventDispatcher<{ selected: string }>()
let collapsedMode = true
let options: UIEventSource<PlantNetSpeciesMatch[]> = new UIEventSource<PlantNetSpeciesMatch[]>(
undefined
)
let error: string = undefined;
let error: string = undefined
/**
* The Wikidata-id of the species to apply
*/
let selectedOption: string;
let selectedOption: string
let done = false;
let done = false
function speciesSelected(species: PlantNetSpeciesMatch) {
console.log("Selected:", species);
selectedOption = species;
console.log("Selected:", species)
selectedOption = species
}
async function detectSpecies() {
collapsedMode = false;
collapsedMode = false
try {
const result = await PlantNet.query(imageUrls.data.slice(0, 5));
options.set(result.results.filter(r => r.score > 0.005).slice(0, 8));
const result = await PlantNet.query(imageUrls.data.slice(0, 5))
options.set(result.results.filter((r) => r.score > 0.005).slice(0, 8))
} catch (e) {
error = e;
error = e
}
}
</script>
<div class="flex flex-col">
{#if collapsedMode}
<button class="w-full" on:click={detectSpecies}>
<Tr t={t.button} />
</button>
{:else if $error !== undefined}
<Tr cls="alert" t={t.error.Subs({error})} />
<Tr cls="alert" t={t.error.Subs({ error })} />
{:else if $imageUrls.length === 0}
<!-- No urls are available, show the explanation instead-->
<div class=" border-region p-2 mb-1 relative">
<XCircleIcon class="absolute top-0 right-0 w-8 h-8 m-4 cursor-pointer"
on:click={() => {collapsedMode = true}}></XCircleIcon>
<div class=" border-region relative mb-1 p-2">
<XCircleIcon
class="absolute top-0 right-0 m-4 h-8 w-8 cursor-pointer"
on:click={() => {
collapsedMode = true
}}
/>
<Tr t={t.takeImages} />
<Tr t={ t.howTo.intro} />
<Tr t={t.howTo.intro} />
<ul>
<li>
<Tr t={t.howTo.li0} />
@ -87,23 +90,39 @@
</ul>
</div>
{:else if selectedOption === undefined}
<PlantNetSpeciesList {options} numberOfImages={$imageUrls.length}
on:selected={(species) => speciesSelected(species.detail)}>
<XCircleIcon slot="upper-right" class="w-8 h-8 m-4 cursor-pointer"
on:click={() => {collapsedMode = true}}></XCircleIcon>
<PlantNetSpeciesList
{options}
numberOfImages={$imageUrls.length}
on:selected={(species) => speciesSelected(species.detail)}
>
<XCircleIcon
slot="upper-right"
class="m-4 h-8 w-8 cursor-pointer"
on:click={() => {
collapsedMode = true
}}
/>
</PlantNetSpeciesList>
{:else if !done}
<div class="flex flex-col border-interactive">
<div class="border-interactive flex flex-col">
<div class="m-2">
<WikipediaPanel wikiIds={new ImmutableStore([selectedOption])} />
</div>
<div class="flex flex-col items-stretch">
<BackButton on:click={() => {selectedOption = undefined}}>
<BackButton
on:click={() => {
selectedOption = undefined
}}
>
<Tr t={t.back} />
</BackButton>
<NextButton clss="primary" on:click={() => { done = true; onConfirm(selectedOption); }} >
<NextButton
clss="primary"
on:click={() => {
done = true
onConfirm(selectedOption)
}}
>
<Tr t={t.confirm} />
</NextButton>
</div>
@ -111,13 +130,21 @@
{:else}
<!-- done ! -->
<Tr t={t.done} cls="thanks w-full" />
<BackButton imageClass="w-6 h-6 shrink-0" clss="p-1 m-0" on:click={() => {done = false; selectedOption = undefined}}>
<BackButton
imageClass="w-6 h-6 shrink-0"
clss="p-1 m-0"
on:click={() => {
done = false
selectedOption = undefined
}}
>
<Tr t={t.tryAgain} />
</BackButton>
{/if}
<div class="flex p-2 low-interaction rounded-xl self-end">
<ToSvelte construct={Svg.plantnet_logo_svg().SetClass("w-8 h-8 p-1 mr-1 bg-white rounded-full")} />
<div class="low-interaction flex self-end rounded-xl p-2">
<ToSvelte
construct={Svg.plantnet_logo_svg().SetClass("w-8 h-8 p-1 mr-1 bg-white rounded-full")}
/>
<Tr t={t.poweredByPlantnet} />
</div>
</div>

View file

@ -1,29 +1,28 @@
<script lang="ts">/**
* Show the list of options to choose from
*/
import type { PlantNetSpeciesMatch } from "../../Logic/Web/PlantNet";
import { Store } from "../../Logic/UIEventSource";
import Translations from "../i18n/Translations";
import Tr from "../Base/Tr.svelte";
import Loading from "../Base/Loading.svelte";
import SpeciesButton from "./SpeciesButton.svelte";
<script lang="ts">
/**
* Show the list of options to choose from
*/
import type { PlantNetSpeciesMatch } from "../../Logic/Web/PlantNet"
import { Store } from "../../Logic/UIEventSource"
import Translations from "../i18n/Translations"
import Tr from "../Base/Tr.svelte"
import Loading from "../Base/Loading.svelte"
import SpeciesButton from "./SpeciesButton.svelte"
const t = Translations.t.plantDetection;
export let options: Store<PlantNetSpeciesMatch[]>;
export let numberOfImages: number;
const t = Translations.t.plantDetection
export let options: Store<PlantNetSpeciesMatch[]>
export let numberOfImages: number
</script>
{#if $options === undefined}
<Loading>
<Tr t={t.querying.Subs({length: numberOfImages})} />
<Tr t={t.querying.Subs({ length: numberOfImages })} />
</Loading>
{:else}
<div class="low-interaction border-interactive flex p-2 flex-col relative">
<div class="absolute top-0 right-0" >
<slot name="upper-right"/>
<div class="low-interaction border-interactive relative flex flex-col p-2">
<div class="absolute top-0 right-0">
<slot name="upper-right" />
</div>
<h3>
<Tr t={t.overviewTitle} />
@ -31,7 +30,7 @@ export let numberOfImages: number;
<Tr t={t.overviewIntro} />
<Tr cls="font-bold" t={t.overviewVerify} />
{#each $options as species}
<SpeciesButton {species} on:selected/>
{/each}
<SpeciesButton {species} on:selected />
{/each}
</div>
{/if}

View file

@ -1,54 +1,61 @@
<script lang="ts">/**
* A button to select a single species
*/
import { createEventDispatcher } from "svelte";
import type { PlantNetSpeciesMatch } from "../../Logic/Web/PlantNet";
import { UIEventSource } from "../../Logic/UIEventSource";
import Wikidata from "../../Logic/Web/Wikidata";
import NextButton from "../Base/NextButton.svelte";
import Loading from "../Base/Loading.svelte";
import WikidataPreviewBox from "../Wikipedia/WikidataPreviewBox";
import Tr from "../Base/Tr.svelte";
import Translations from "../i18n/Translations";
import ToSvelte from "../Base/ToSvelte.svelte";
<script lang="ts">
/**
* A button to select a single species
*/
import { createEventDispatcher } from "svelte"
import type { PlantNetSpeciesMatch } from "../../Logic/Web/PlantNet"
import { UIEventSource } from "../../Logic/UIEventSource"
import Wikidata from "../../Logic/Web/Wikidata"
import NextButton from "../Base/NextButton.svelte"
import Loading from "../Base/Loading.svelte"
import WikidataPreviewBox from "../Wikipedia/WikidataPreviewBox"
import Tr from "../Base/Tr.svelte"
import Translations from "../i18n/Translations"
import ToSvelte from "../Base/ToSvelte.svelte"
export let species: PlantNetSpeciesMatch;
let wikidata = UIEventSource.FromPromise(
Wikidata.Sparql<{ species }>(
["?species", "?speciesLabel"],
["?species wdt:P846 \"" + species.gbif.id + "\""]
export let species: PlantNetSpeciesMatch
let wikidata = UIEventSource.FromPromise(
Wikidata.Sparql<{ species }>(
["?species", "?speciesLabel"],
['?species wdt:P846 "' + species.gbif.id + '"']
)
)
);
const dispatch = createEventDispatcher<{ selected: string /* wikidata-id*/ }>();
const t = Translations.t.plantDetection;
const dispatch = createEventDispatcher<{ selected: string /* wikidata-id*/ }>()
const t = Translations.t.plantDetection
/**
* PlantNet give us a GBIF-id, but we want the Wikidata-id instead.
* We look this up in wikidata
*/
const wikidataId: Store<string> = UIEventSource.FromPromise(
Wikidata.Sparql<{ species }>(
["?species", "?speciesLabel"],
["?species wdt:P846 \"" + species.gbif.id + "\""]
)
).mapD(wd => wd[0]?.species?.value);
/**
* PlantNet give us a GBIF-id, but we want the Wikidata-id instead.
* We look this up in wikidata
*/
const wikidataId: Store<string> = UIEventSource.FromPromise(
Wikidata.Sparql<{ species }>(
["?species", "?speciesLabel"],
['?species wdt:P846 "' + species.gbif.id + '"']
)
).mapD((wd) => wd[0]?.species?.value)
</script>
<NextButton on:click={() => dispatch("selected", $wikidataId)}>
{#if $wikidata === undefined}
<Loading>
<Tr t={ t.loadingWikidata.Subs({
species: species.species.scientificNameWithoutAuthor,
})} />
<Tr
t={t.loadingWikidata.Subs({
species: species.species.scientificNameWithoutAuthor,
})}
/>
</Loading>
{:else}
<ToSvelte construct={() => new WikidataPreviewBox(wikidataId,
{ imageStyle: "max-width: 8rem; width: unset; height: 8rem",
extraItems: [t.matchPercentage
.Subs({ match: Math.round(species.score * 100) })
.SetClass("thanks w-fit self-center")]
}).SetClass("w-full")}></ToSvelte>
<ToSvelte
construct={() =>
new WikidataPreviewBox(wikidataId, {
imageStyle: "max-width: 8rem; width: unset; height: 8rem",
extraItems: [
t.matchPercentage
.Subs({ match: Math.round(species.score * 100) })
.SetClass("thanks w-fit self-center"),
],
}).SetClass("w-full")}
/>
{/if}
</NextButton>

View file

@ -3,109 +3,109 @@
* 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";
import ToSvelte from "../../Base/ToSvelte.svelte";
import Svg from "../../../Svg";
import OpenBackgroundSelectorButton from "../../BigComponents/OpenBackgroundSelectorButton.svelte";
import { twJoin } from "tailwind-merge";
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"
import ToSvelte from "../../Base/ToSvelte.svelte"
import Svg from "../../../Svg"
import OpenBackgroundSelectorButton from "../../BigComponents/OpenBackgroundSelectorButton.svelte"
import { twJoin } from "tailwind-merge"
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;
} = undefined
let checkedOfGlobalFilters: number = 0
let confirmedCategory = false
$: if (selectedPreset === undefined) {
confirmedCategory = false;
creating = false;
checkedOfGlobalFilters = 0;
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[] = [];
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 ?? [];
console.log("Global filters are", globalFilter)
_globalFilter = globalFilter ?? []
})
);
)
$: {
flayer = state.layerState.filteredLayers.get(selectedPreset?.layer?.id);
layerIsDisplayed = flayer?.isDisplayed;
layerHasFilters = flayer?.hasFilter;
flayer = state.layerState.filteredLayers.get(selectedPreset?.layer?.id)
layerIsDisplayed = flayer?.isDisplayed
layerHasFilters = flayer?.hasFilter
}
const t = Translations.t.general.add;
const t = Translations.t.general.add
const zoom = state.mapProperties.zoom;
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);
const isLoading = state.dataIsLoading
let preciseCoordinate: UIEventSource<{ lon: number; lat: number }> = new UIEventSource(undefined)
let snappedToObject: UIEventSource<string> = new UIEventSource<string>(undefined)
// Small helper variable: if the map is tapped, we should let the 'Next'-button grab some attention as users have to click _that_ to continue, not the map
let preciseInputIsTapped = false;
let preciseInputIsTapped = false
let creating = false;
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);
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([]);
preciseInputIsTapped = false;
state.lastClickObject.features.setData([])
preciseInputIsTapped = false
}
async function confirm() {
creating = true;
const location: { lon: number; lat: number } = preciseCoordinate.data;
const snapTo: WayId | undefined = <WayId>snappedToObject.data;
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);
)
console.log("Creating new point at", location, "snapped to", snapTo, "with tags", tags)
let snapToWay: undefined | OsmWay = undefined;
let snapToWay: undefined | OsmWay = undefined
if (snapTo !== undefined && snapTo !== null) {
const downloaded = await state.osmObjectDownloader.DownloadObjectAsync(snapTo, 0);
const downloaded = await state.osmObjectDownloader.DownloadObjectAsync(snapTo, 0)
if (downloaded !== "deleted") {
snapToWay = downloaded;
snapToWay = downloaded
}
}
@ -113,42 +113,44 @@
theme: state.layout?.id ?? "unkown",
changeType: "create",
snapOnto: snapToWay,
reusePointWithinMeters: 1
});
await state.changes.applyAction(newElementAction);
state.newFeatures.features.ping();
reusePointWithinMeters: 1,
})
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);
const newId = newElementAction.newElementId
console.log("Applied pending changes, fetching store for", newId)
const tagsStore = state.featureProperties.getStore(newId)
if (!tagsStore) {
console.error("Bug: no tagsStore found for", newId);
console.error("Bug: no tagsStore found for", newId)
}
{
// Set some metainfo
const properties = tagsStore.data;
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}"]`;
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();
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);
console.log("Selecting feature", feature, "and opening their popup");
abort();
state.selectedLayer.setData(selectedPreset.layer);
state.selectedElement.setData(feature);
tagsStore.ping();
const feature = state.indexedFeatures.featuresById.data.get(newId)
console.log("Selecting feature", feature, "and opening their popup")
abort()
state.selectedLayer.setData(selectedPreset.layer)
state.selectedElement.setData(feature)
tagsStore.ping()
}
function confirmSync() {
confirm().then(_ => console.debug("New point successfully handled")).catch(e => console.error("Handling the new point went wrong due to", e));
confirm()
.then((_) => console.debug("New point successfully handled"))
.catch((e) => console.error("Handling the new point went wrong due to", e))
}
</script>

View file

@ -62,7 +62,7 @@
state.newFeatures.features.data.push(feature)
state.newFeatures.features.ping()
state.selectedElement?.setData(feature)
if(state.featureProperties.trackFeature){
if (state.featureProperties.trackFeature) {
state.featureProperties.trackFeature(feature)
}
comment.setData("")

View file

@ -23,18 +23,20 @@
export let feature: Feature
export let layer: LayerConfig
export let linkable = true;
let isLinked = Object.values(tags.data).some(v => image.pictureUrl === v);
export let linkable = true
let isLinked = Object.values(tags.data).some((v) => image.pictureUrl === v)
const t = Translations.t.image.nearby;
const c = [lon, lat];
const t = Translations.t.image.nearby
const c = [lon, lat]
let attributedImage = new AttributedImage({
url: image.thumbUrl ?? image.pictureUrl,
provider: AllImageProviders.byName(image.provider),
date: new Date(image.date)
});
let distance = Math.round(GeoOperations.distanceBetween([image.coordinates.lng, image.coordinates.lat], c));
date: new Date(image.date),
})
let distance = Math.round(
GeoOperations.distanceBetween([image.coordinates.lng, image.coordinates.lat], c)
)
$: {
const currentTags = tags.data
const key = Object.keys(image.osmTags)[0]

View file

@ -1,41 +1,40 @@
<script lang="ts">
import FeatureReviews from "../../Logic/Web/MangroveReviews";
import SingleReview from "./SingleReview.svelte";
import { Utils } from "../../Utils";
import StarsBar from "./StarsBar.svelte";
import ReviewForm from "./ReviewForm.svelte";
import Translations from "../i18n/Translations";
import Tr from "../Base/Tr.svelte";
import type { SpecialVisualizationState } from "../SpecialVisualization";
import { UIEventSource } from "../../Logic/UIEventSource";
import type { Feature } from "geojson";
import LayerConfig from "../../Models/ThemeConfig/LayerConfig";
import ToSvelte from "../Base/ToSvelte.svelte";
import Svg from "../../Svg";
import FeatureReviews from "../../Logic/Web/MangroveReviews"
import SingleReview from "./SingleReview.svelte"
import { Utils } from "../../Utils"
import StarsBar from "./StarsBar.svelte"
import ReviewForm from "./ReviewForm.svelte"
import Translations from "../i18n/Translations"
import Tr from "../Base/Tr.svelte"
import type { SpecialVisualizationState } from "../SpecialVisualization"
import { UIEventSource } from "../../Logic/UIEventSource"
import type { Feature } from "geojson"
import LayerConfig from "../../Models/ThemeConfig/LayerConfig"
import ToSvelte from "../Base/ToSvelte.svelte"
import Svg from "../../Svg"
/**
* An element showing all reviews
*/
export let reviews: FeatureReviews;
export let state: SpecialVisualizationState;
export let tags: UIEventSource<Record<string, string>>;
export let feature: Feature;
export let layer: LayerConfig;
let average = reviews.average;
let _reviews = [];
reviews.reviews.addCallbackAndRunD(r => {
_reviews = Utils.NoNull(r);
});
export let reviews: FeatureReviews
export let state: SpecialVisualizationState
export let tags: UIEventSource<Record<string, string>>
export let feature: Feature
export let layer: LayerConfig
let average = reviews.average
let _reviews = []
reviews.reviews.addCallbackAndRunD((r) => {
_reviews = Utils.NoNull(r)
})
</script>
<div class="border-gray-300 border-dashed border-2">
<div class="border-2 border-dashed border-gray-300">
{#if _reviews.length > 1}
<StarsBar score={$average}></StarsBar>
<StarsBar score={$average} />
{/if}
{#if _reviews.length > 0}
{#each _reviews as review}
<SingleReview {review}></SingleReview>
<SingleReview {review} />
{/each}
{:else}
<Tr t={Translations.t.reviews.no_reviews_yet} />

View file

@ -1,60 +1,61 @@
<script lang="ts">
import FeatureReviews from "../../Logic/Web/MangroveReviews";
import StarsBar from "./StarsBar.svelte";
import SpecialTranslation from "../Popup/TagRendering/SpecialTranslation.svelte";
import type { SpecialVisualizationState } from "../SpecialVisualization";
import { UIEventSource } from "../../Logic/UIEventSource";
import type { Feature } from "geojson";
import LayerConfig from "../../Models/ThemeConfig/LayerConfig";
import Translations from "../i18n/Translations";
import Checkbox from "../Base/Checkbox.svelte";
import Tr from "../Base/Tr.svelte";
import If from "../Base/If.svelte";
import Loading from "../Base/Loading.svelte";
import { Review } from "mangrove-reviews-typescript";
import { Utils } from "../../Utils";
import FeatureReviews from "../../Logic/Web/MangroveReviews"
import StarsBar from "./StarsBar.svelte"
import SpecialTranslation from "../Popup/TagRendering/SpecialTranslation.svelte"
import type { SpecialVisualizationState } from "../SpecialVisualization"
import { UIEventSource } from "../../Logic/UIEventSource"
import type { Feature } from "geojson"
import LayerConfig from "../../Models/ThemeConfig/LayerConfig"
import Translations from "../i18n/Translations"
import Checkbox from "../Base/Checkbox.svelte"
import Tr from "../Base/Tr.svelte"
import If from "../Base/If.svelte"
import Loading from "../Base/Loading.svelte"
import { Review } from "mangrove-reviews-typescript"
import { Utils } from "../../Utils"
export let state: SpecialVisualizationState;
export let tags: UIEventSource<Record<string, string>>;
export let feature: Feature;
export let layer: LayerConfig;
export let state: SpecialVisualizationState
export let tags: UIEventSource<Record<string, string>>
export let feature: Feature
export let layer: LayerConfig
/**
* The form to create a new review.
* This is multi-stepped.
*/
export let reviews: FeatureReviews;
export let reviews: FeatureReviews
let score = 0;
let confirmedScore = undefined;
let isAffiliated = new UIEventSource(false);
let opinion = new UIEventSource<string>(undefined);
let score = 0
let confirmedScore = undefined
let isAffiliated = new UIEventSource(false)
let opinion = new UIEventSource<string>(undefined)
const t = Translations.t.reviews;
const t = Translations.t.reviews
let _state: "ask" | "saving" | "done" = "ask";
let _state: "ask" | "saving" | "done" = "ask"
const connection = state.osmConnection;
const connection = state.osmConnection
async function save() {
_state = "saving";
let nickname = undefined;
_state = "saving"
let nickname = undefined
if (connection.isLoggedIn.data) {
nickname = connection.userDetails.data.name;
nickname = connection.userDetails.data.name
}
const review: Omit<Review, "sub"> = {
rating: confirmedScore,
opinion: opinion.data,
metadata: { nickname, is_affiliated: isAffiliated.data }
};
if (state.featureSwitchIsTesting.data) {
console.log("Testing - not actually saving review", review);
await Utils.waitFor(1000);
} else {
await reviews.createReview(review);
metadata: { nickname, is_affiliated: isAffiliated.data },
}
_state = "done";
if (state.featureSwitchIsTesting.data) {
console.log("Testing - not actually saving review", review)
await Utils.waitFor(1000)
} else {
await reviews.createReview(review)
}
_state = "done"
}
</script>
{#if _state === "done"}
<Tr cls="thanks w-full" t={t.saved} />
{:else if _state === "saving"}
@ -64,24 +65,34 @@
{:else}
<div class="interactive border-interactive p-1">
<div class="font-bold">
<SpecialTranslation {feature} {layer} {state} t={Translations.t.reviews.question} {tags}></SpecialTranslation>
<SpecialTranslation {feature} {layer} {state} t={Translations.t.reviews.question} {tags} />
</div>
<StarsBar on:click={e => {confirmedScore = e.detail.score}} on:hover={e => {score = e.detail.score}}
on:mouseout={e => {score = null}} score={score ?? confirmedScore ?? 0}
starSize="w-8 h-8"></StarsBar>
<StarsBar
on:click={(e) => {
confirmedScore = e.detail.score
}}
on:hover={(e) => {
score = e.detail.score
}}
on:mouseout={(e) => {
score = null
}}
score={score ?? confirmedScore ?? 0}
starSize="w-8 h-8"
/>
{#if confirmedScore !== undefined}
<Tr cls="font-bold mt-2" t={t.question_opinion} />
<textarea bind:value={$opinion} inputmode="text" rows="3" class="w-full mb-1" />
<textarea bind:value={$opinion} inputmode="text" rows="3" class="mb-1 w-full" />
<Checkbox selected={isAffiliated}>
<div class="flex flex-col">
<Tr t={t.i_am_affiliated} />
<Tr cls="subtle" t={t.i_am_affiliated_explanation} />
</div>
</Checkbox>
<div class="flex w-full justify-between flex-wrap items-center">
<div class="flex w-full flex-wrap items-center justify-between">
<If condition={state.osmConnection.isLoggedIn}>
<Tr t={t.reviewing_as.Subs({nickname: state.osmConnection.userDetails.data.name})} />
<Tr t={t.reviewing_as.Subs({ nickname: state.osmConnection.userDetails.data.name })} />
<Tr slot="else" t={t.reviewing_as_anonymous} />
</If>
<button class="primary" on:click={save}>
@ -90,8 +101,6 @@
</div>
<Tr cls="subtle mt-4" t={t.tos} />
{/if}
</div>
{/if}

View file

@ -1,32 +1,32 @@
<script lang="ts">
import { Review } from "mangrove-reviews-typescript";
import { Store } from "../../Logic/UIEventSource";
import StarsBar from "./StarsBar.svelte";
import Translations from "../i18n/Translations";
import Tr from "../Base/Tr.svelte";
import { Review } from "mangrove-reviews-typescript"
import { Store } from "../../Logic/UIEventSource"
import StarsBar from "./StarsBar.svelte"
import Translations from "../i18n/Translations"
import Tr from "../Base/Tr.svelte"
export let review: Review & { madeByLoggedInUser: Store<boolean> };
let name = review.metadata.nickname;
name ??= (review.metadata.given_name ?? "") + " " + (review.metadata.family_name ?? "").trim();
export let review: Review & { madeByLoggedInUser: Store<boolean> }
let name = review.metadata.nickname
name ??= (review.metadata.given_name ?? "") + " " + (review.metadata.family_name ?? "").trim()
if (name.length === 0) {
name = "Anonymous";
name = "Anonymous"
}
let d = new Date();
d.setTime(review.iat * 1000);
let date = d.toDateString();
let byLoggedInUser = review.madeByLoggedInUser;
let d = new Date()
d.setTime(review.iat * 1000)
let date = d.toDateString()
let byLoggedInUser = review.madeByLoggedInUser
</script>
<div class={"low-interaction p-1 px-2 rounded-lg "+ ($byLoggedInUser ? "border-interactive" : "")}>
<div class="flex justify-between items-center">
<StarsBar score={review.rating}></StarsBar>
<div class={"low-interaction rounded-lg p-1 px-2 " + ($byLoggedInUser ? "border-interactive" : "")}>
<div class="flex items-center justify-between">
<StarsBar score={review.rating} />
<div class="flex flex-wrap space-x-2">
<div class="font-bold">
{name}
</div>
<span class="subtle">
{date}
</span>
<span class="subtle">
{date}
</span>
</div>
</div>
{#if review.opinion}

View file

@ -1,27 +1,27 @@
<script lang="ts">
import ToSvelte from "../Base/ToSvelte.svelte"
import Svg from "../../Svg"
import { createEventDispatcher } from "svelte"
import ToSvelte from "../Base/ToSvelte.svelte";
import Svg from "../../Svg";
import { createEventDispatcher } from "svelte";
export let score: number
export let cutoff: number
export let starSize = "w-h h-4"
export let score: number;
export let cutoff: number;
export let starSize = "w-h h-4";
let dispatch = createEventDispatcher<{ hover: { score: number } }>();
let container: HTMLElement;
let dispatch = createEventDispatcher<{ hover: { score: number } }>()
let container: HTMLElement
function getScore(e: MouseEvent): number {
const x = e.clientX - e.target.getBoundingClientRect().x;
const w = container.getClientRects()[0]?.width;
return (x / w) < 0.5 ? cutoff - 10 : cutoff;
const x = e.clientX - e.target.getBoundingClientRect().x
const w = container.getClientRects()[0]?.width
return x / w < 0.5 ? cutoff - 10 : cutoff
}
</script>
<div bind:this={container} on:click={(e) => dispatch("click", {score: getScore(e)})}
on:mousemove={(e) => dispatch("hover", { score: getScore(e) })}>
<div
bind:this={container}
on:click={(e) => dispatch("click", { score: getScore(e) })}
on:mousemove={(e) => dispatch("hover", { score: getScore(e) })}
>
{#if score >= cutoff}
<ToSvelte construct={Svg.star_svg().SetClass(starSize)} />
{:else if score + 10 >= cutoff}

View file

@ -1,21 +1,21 @@
<script lang="ts">
import { createEventDispatcher } from "svelte";
import StarElement from "./StarElement.svelte";
import { createEventDispatcher } from "svelte"
import StarElement from "./StarElement.svelte"
/**
* Number between 0 and 100. Every 10 points, another half star is added
*/
export let score: number;
let dispatch = createEventDispatcher<{ hover: number, click: number }>();
export let score: number
let dispatch = createEventDispatcher<{ hover: number; click: number }>()
let cutoffs = [20,40,60,80,100]
let cutoffs = [20, 40, 60, 80, 100]
export let starSize = "w-h h-4"
</script>
{#if score !== undefined}
<div class="flex" on:mouseout>
{#each cutoffs as cutoff}
<StarElement {score} {cutoff} {starSize} on:hover on:click/>
<div class="flex" on:mouseout>
{#each cutoffs as cutoff}
<StarElement {score} {cutoff} {starSize} on:hover on:click />
{/each}
</div>
{/if}
</div>
{/if}

View file

@ -1,9 +1,8 @@
<script lang="ts">
import { Store } from "../../Logic/UIEventSource"
import StarsBar from "./StarsBar.svelte"
import { Store } from "../../Logic/UIEventSource";
import StarsBar from "./StarsBar.svelte";
export let score: Store<number>;
export let score: Store<number>
</script>
{#if $score !== undefined && $score !== null}

View file

@ -1,118 +1,121 @@
import { Store, UIEventSource } from "../Logic/UIEventSource";
import BaseUIElement from "./BaseUIElement";
import LayoutConfig from "../Models/ThemeConfig/LayoutConfig";
import { IndexedFeatureSource, WritableFeatureSource } from "../Logic/FeatureSource/FeatureSource";
import { OsmConnection } from "../Logic/Osm/OsmConnection";
import { Changes } from "../Logic/Osm/Changes";
import { ExportableMap, MapProperties } from "../Models/MapProperties";
import LayerState from "../Logic/State/LayerState";
import { Feature, Geometry, Point } from "geojson";
import FullNodeDatabaseSource from "../Logic/FeatureSource/TiledFeatureSource/FullNodeDatabaseSource";
import { MangroveIdentity } from "../Logic/Web/MangroveReviews";
import { GeoIndexedStoreForLayer } from "../Logic/FeatureSource/Actors/GeoIndexedStore";
import LayerConfig from "../Models/ThemeConfig/LayerConfig";
import FeatureSwitchState from "../Logic/State/FeatureSwitchState";
import { MenuState } from "../Models/MenuState";
import OsmObjectDownloader from "../Logic/Osm/OsmObjectDownloader";
import { RasterLayerPolygon } from "../Models/RasterLayers";
import { ImageUploadManager } from "../Logic/ImageProviders/ImageUploadManager";
import { OsmTags } from "../Models/OsmFeature";
import { Store, UIEventSource } from "../Logic/UIEventSource"
import BaseUIElement from "./BaseUIElement"
import LayoutConfig from "../Models/ThemeConfig/LayoutConfig"
import { IndexedFeatureSource, WritableFeatureSource } from "../Logic/FeatureSource/FeatureSource"
import { OsmConnection } from "../Logic/Osm/OsmConnection"
import { Changes } from "../Logic/Osm/Changes"
import { ExportableMap, MapProperties } from "../Models/MapProperties"
import LayerState from "../Logic/State/LayerState"
import { Feature, Geometry, Point } from "geojson"
import FullNodeDatabaseSource from "../Logic/FeatureSource/TiledFeatureSource/FullNodeDatabaseSource"
import { MangroveIdentity } from "../Logic/Web/MangroveReviews"
import { GeoIndexedStoreForLayer } from "../Logic/FeatureSource/Actors/GeoIndexedStore"
import LayerConfig from "../Models/ThemeConfig/LayerConfig"
import FeatureSwitchState from "../Logic/State/FeatureSwitchState"
import { MenuState } from "../Models/MenuState"
import OsmObjectDownloader from "../Logic/Osm/OsmObjectDownloader"
import { RasterLayerPolygon } from "../Models/RasterLayers"
import { ImageUploadManager } from "../Logic/ImageProviders/ImageUploadManager"
import { OsmTags } from "../Models/OsmFeature"
/**
* The state needed to render a special Visualisation.
*/
export interface SpecialVisualizationState {
readonly guistate: MenuState;
readonly layout: LayoutConfig;
readonly featureSwitches: FeatureSwitchState;
readonly guistate: MenuState
readonly layout: LayoutConfig
readonly featureSwitches: FeatureSwitchState
readonly layerState: LayerState;
readonly featureProperties: { getStore(id: string): UIEventSource<Record<string, string>>, trackFeature?(feature: { properties: OsmTags }) };
readonly layerState: LayerState
readonly featureProperties: {
getStore(id: string): UIEventSource<Record<string, string>>
trackFeature?(feature: { properties: OsmTags })
}
readonly indexedFeatures: IndexedFeatureSource;
readonly indexedFeatures: IndexedFeatureSource
/**
* Some features will create a new element that should be displayed.
* These can be injected by appending them to this featuresource (and pinging it)
*/
readonly newFeatures: WritableFeatureSource;
/**
* Some features will create a new element that should be displayed.
* These can be injected by appending them to this featuresource (and pinging it)
*/
readonly newFeatures: WritableFeatureSource
readonly historicalUserLocations: WritableFeatureSource<Feature<Point>>;
readonly historicalUserLocations: WritableFeatureSource<Feature<Point>>
readonly osmConnection: OsmConnection;
readonly featureSwitchUserbadge: Store<boolean>;
readonly featureSwitchIsTesting: Store<boolean>;
readonly changes: Changes;
readonly osmObjectDownloader: OsmObjectDownloader;
/**
* State of the main map
*/
readonly mapProperties: MapProperties & ExportableMap;
readonly osmConnection: OsmConnection
readonly featureSwitchUserbadge: Store<boolean>
readonly featureSwitchIsTesting: Store<boolean>
readonly changes: Changes
readonly osmObjectDownloader: OsmObjectDownloader
/**
* State of the main map
*/
readonly mapProperties: MapProperties & ExportableMap
readonly selectedElement: UIEventSource<Feature>;
/**
* Works together with 'selectedElement' to indicate what properties should be displayed
*/
readonly selectedLayer: UIEventSource<LayerConfig>;
readonly selectedElementAndLayer: Store<{ feature: Feature; layer: LayerConfig }>;
readonly selectedElement: UIEventSource<Feature>
/**
* Works together with 'selectedElement' to indicate what properties should be displayed
*/
readonly selectedLayer: UIEventSource<LayerConfig>
readonly selectedElementAndLayer: Store<{ feature: Feature; layer: LayerConfig }>
/**
* If data is currently being fetched from external sources
*/
readonly dataIsLoading: Store<boolean>;
/**
* Only needed for 'ReplaceGeometryAction'
*/
readonly fullNodeDatabase?: FullNodeDatabaseSource;
/**
* If data is currently being fetched from external sources
*/
readonly dataIsLoading: Store<boolean>
/**
* Only needed for 'ReplaceGeometryAction'
*/
readonly fullNodeDatabase?: FullNodeDatabaseSource
readonly perLayer: ReadonlyMap<string, GeoIndexedStoreForLayer>;
readonly userRelatedState: {
readonly imageLicense: UIEventSource<string>;
readonly showTags: UIEventSource<"no" | undefined | "always" | "yes" | "full">
readonly mangroveIdentity: MangroveIdentity
readonly showAllQuestionsAtOnce: UIEventSource<boolean>
readonly preferencesAsTags: Store<Record<string, string>>
readonly language: UIEventSource<string>
};
readonly lastClickObject: WritableFeatureSource;
readonly perLayer: ReadonlyMap<string, GeoIndexedStoreForLayer>
readonly userRelatedState: {
readonly imageLicense: UIEventSource<string>
readonly showTags: UIEventSource<"no" | undefined | "always" | "yes" | "full">
readonly mangroveIdentity: MangroveIdentity
readonly showAllQuestionsAtOnce: UIEventSource<boolean>
readonly preferencesAsTags: Store<Record<string, string>>
readonly language: UIEventSource<string>
}
readonly lastClickObject: WritableFeatureSource
readonly availableLayers: Store<RasterLayerPolygon[]>;
readonly availableLayers: Store<RasterLayerPolygon[]>
readonly imageUploadManager: ImageUploadManager;
readonly imageUploadManager: ImageUploadManager
}
export interface SpecialVisualization {
readonly funcName: string;
readonly docs: string | BaseUIElement;
readonly example?: string;
readonly funcName: string
readonly docs: string | BaseUIElement
readonly example?: string
/**
* Indicates that this special visualisation will make requests to the 'alLNodesDatabase' and that it thus should be included
*/
readonly needsNodeDatabase?: boolean;
readonly args: {
name: string
defaultValue?: string
doc: string
required?: false | boolean
}[];
readonly getLayerDependencies?: (argument: string[]) => string[];
/**
* Indicates that this special visualisation will make requests to the 'alLNodesDatabase' and that it thus should be included
*/
readonly needsNodeDatabase?: boolean
readonly args: {
name: string
defaultValue?: string
doc: string
required?: false | boolean
}[]
readonly getLayerDependencies?: (argument: string[]) => string[]
structuredExamples?(): { feature: Feature<Geometry, Record<string, string>>; args: string[] }[];
structuredExamples?(): { feature: Feature<Geometry, Record<string, string>>; args: string[] }[]
constr(
state: SpecialVisualizationState,
tagSource: UIEventSource<Record<string, string>>,
argument: string[],
feature: Feature,
layer: LayerConfig
): BaseUIElement;
constr(
state: SpecialVisualizationState,
tagSource: UIEventSource<Record<string, string>>,
argument: string[],
feature: Feature,
layer: LayerConfig
): BaseUIElement
}
export type RenderingSpecification =
| string
| {
func: SpecialVisualization
args: string[]
style: string
}
| string
| {
func: SpecialVisualization
args: string[]
style: string
}

View file

@ -1,72 +1,76 @@
import Combine from "./Base/Combine";
import { FixedUiElement } from "./Base/FixedUiElement";
import BaseUIElement from "./BaseUIElement";
import Title from "./Base/Title";
import Table from "./Base/Table";
import { RenderingSpecification, SpecialVisualization, SpecialVisualizationState } from "./SpecialVisualization";
import { HistogramViz } from "./Popup/HistogramViz";
import { MinimapViz } from "./Popup/MinimapViz";
import { ShareLinkViz } from "./Popup/ShareLinkViz";
import { UploadToOsmViz } from "./Popup/UploadToOsmViz";
import { MultiApplyViz } from "./Popup/MultiApplyViz";
import { AddNoteCommentViz } from "./Popup/AddNoteCommentViz";
import { PlantNetDetectionViz } from "./Popup/PlantNetDetectionViz";
import TagApplyButton from "./Popup/TagApplyButton";
import { CloseNoteButton } from "./Popup/CloseNoteButton";
import { MapillaryLinkVis } from "./Popup/MapillaryLinkVis";
import { Store, Stores, UIEventSource } from "../Logic/UIEventSource";
import AllTagsPanel from "./Popup/AllTagsPanel.svelte";
import AllImageProviders from "../Logic/ImageProviders/AllImageProviders";
import { ImageCarousel } from "./Image/ImageCarousel";
import { VariableUiElement } from "./Base/VariableUIElement";
import { Utils } from "../Utils";
import Wikidata, { WikidataResponse } from "../Logic/Web/Wikidata";
import { Translation } from "./i18n/Translation";
import Translations from "./i18n/Translations";
import OpeningHoursVisualization from "./OpeningHours/OpeningHoursVisualization";
import LiveQueryHandler from "../Logic/Web/LiveQueryHandler";
import { SubtleButton } from "./Base/SubtleButton";
import Svg from "../Svg";
import NoteCommentElement from "./Popup/NoteCommentElement";
import { SubstitutedTranslation } from "./SubstitutedTranslation";
import List from "./Base/List";
import StatisticsPanel from "./BigComponents/StatisticsPanel";
import AutoApplyButton from "./Popup/AutoApplyButton";
import { LanguageElement } from "./Popup/LanguageElement";
import FeatureReviews from "../Logic/Web/MangroveReviews";
import Maproulette from "../Logic/Maproulette";
import SvelteUIElement from "./Base/SvelteUIElement";
import { BBoxFeatureSourceForLayer } from "../Logic/FeatureSource/Sources/TouchesBboxFeatureSource";
import QuestionViz from "./Popup/QuestionViz";
import { Feature, Point } from "geojson";
import { GeoOperations } from "../Logic/GeoOperations";
import CreateNewNote from "./Popup/CreateNewNote.svelte";
import AddNewPoint from "./Popup/AddNewPoint/AddNewPoint.svelte";
import UserProfile from "./BigComponents/UserProfile.svelte";
import LanguagePicker from "./LanguagePicker";
import Link from "./Base/Link";
import LayerConfig from "../Models/ThemeConfig/LayerConfig";
import TagRenderingConfig from "../Models/ThemeConfig/TagRenderingConfig";
import { OsmTags, WayId } from "../Models/OsmFeature";
import MoveWizard from "./Popup/MoveWizard";
import SplitRoadWizard from "./Popup/SplitRoadWizard";
import { ExportAsGpxViz } from "./Popup/ExportAsGpxViz";
import WikipediaPanel from "./Wikipedia/WikipediaPanel.svelte";
import TagRenderingEditable from "./Popup/TagRendering/TagRenderingEditable.svelte";
import { PointImportButtonViz } from "./Popup/ImportButtons/PointImportButtonViz";
import WayImportButtonViz from "./Popup/ImportButtons/WayImportButtonViz";
import ConflateImportButtonViz from "./Popup/ImportButtons/ConflateImportButtonViz";
import DeleteWizard from "./Popup/DeleteFlow/DeleteWizard.svelte";
import { OpenJosm } from "./BigComponents/OpenJosm";
import OpenIdEditor from "./BigComponents/OpenIdEditor.svelte";
import FediverseValidator from "./InputElement/Validators/FediverseValidator";
import SendEmail from "./Popup/SendEmail.svelte";
import NearbyImages from "./Popup/NearbyImages.svelte";
import NearbyImagesCollapsed from "./Popup/NearbyImagesCollapsed.svelte";
import UploadImage from "./Image/UploadImage.svelte";
import AllReviews from "./Reviews/AllReviews.svelte";
import StarsBarIcon from "./Reviews/StarsBarIcon.svelte";
import ReviewForm from "./Reviews/ReviewForm.svelte";
import Combine from "./Base/Combine"
import { FixedUiElement } from "./Base/FixedUiElement"
import BaseUIElement from "./BaseUIElement"
import Title from "./Base/Title"
import Table from "./Base/Table"
import {
RenderingSpecification,
SpecialVisualization,
SpecialVisualizationState,
} from "./SpecialVisualization"
import { HistogramViz } from "./Popup/HistogramViz"
import { MinimapViz } from "./Popup/MinimapViz"
import { ShareLinkViz } from "./Popup/ShareLinkViz"
import { UploadToOsmViz } from "./Popup/UploadToOsmViz"
import { MultiApplyViz } from "./Popup/MultiApplyViz"
import { AddNoteCommentViz } from "./Popup/AddNoteCommentViz"
import { PlantNetDetectionViz } from "./Popup/PlantNetDetectionViz"
import TagApplyButton from "./Popup/TagApplyButton"
import { CloseNoteButton } from "./Popup/CloseNoteButton"
import { MapillaryLinkVis } from "./Popup/MapillaryLinkVis"
import { Store, Stores, UIEventSource } from "../Logic/UIEventSource"
import AllTagsPanel from "./Popup/AllTagsPanel.svelte"
import AllImageProviders from "../Logic/ImageProviders/AllImageProviders"
import { ImageCarousel } from "./Image/ImageCarousel"
import { VariableUiElement } from "./Base/VariableUIElement"
import { Utils } from "../Utils"
import Wikidata, { WikidataResponse } from "../Logic/Web/Wikidata"
import { Translation } from "./i18n/Translation"
import Translations from "./i18n/Translations"
import OpeningHoursVisualization from "./OpeningHours/OpeningHoursVisualization"
import LiveQueryHandler from "../Logic/Web/LiveQueryHandler"
import { SubtleButton } from "./Base/SubtleButton"
import Svg from "../Svg"
import NoteCommentElement from "./Popup/NoteCommentElement"
import { SubstitutedTranslation } from "./SubstitutedTranslation"
import List from "./Base/List"
import StatisticsPanel from "./BigComponents/StatisticsPanel"
import AutoApplyButton from "./Popup/AutoApplyButton"
import { LanguageElement } from "./Popup/LanguageElement"
import FeatureReviews from "../Logic/Web/MangroveReviews"
import Maproulette from "../Logic/Maproulette"
import SvelteUIElement from "./Base/SvelteUIElement"
import { BBoxFeatureSourceForLayer } from "../Logic/FeatureSource/Sources/TouchesBboxFeatureSource"
import QuestionViz from "./Popup/QuestionViz"
import { Feature, Point } from "geojson"
import { GeoOperations } from "../Logic/GeoOperations"
import CreateNewNote from "./Popup/CreateNewNote.svelte"
import AddNewPoint from "./Popup/AddNewPoint/AddNewPoint.svelte"
import UserProfile from "./BigComponents/UserProfile.svelte"
import LanguagePicker from "./LanguagePicker"
import Link from "./Base/Link"
import LayerConfig from "../Models/ThemeConfig/LayerConfig"
import TagRenderingConfig from "../Models/ThemeConfig/TagRenderingConfig"
import { OsmTags, WayId } from "../Models/OsmFeature"
import MoveWizard from "./Popup/MoveWizard"
import SplitRoadWizard from "./Popup/SplitRoadWizard"
import { ExportAsGpxViz } from "./Popup/ExportAsGpxViz"
import WikipediaPanel from "./Wikipedia/WikipediaPanel.svelte"
import TagRenderingEditable from "./Popup/TagRendering/TagRenderingEditable.svelte"
import { PointImportButtonViz } from "./Popup/ImportButtons/PointImportButtonViz"
import WayImportButtonViz from "./Popup/ImportButtons/WayImportButtonViz"
import ConflateImportButtonViz from "./Popup/ImportButtons/ConflateImportButtonViz"
import DeleteWizard from "./Popup/DeleteFlow/DeleteWizard.svelte"
import { OpenJosm } from "./BigComponents/OpenJosm"
import OpenIdEditor from "./BigComponents/OpenIdEditor.svelte"
import FediverseValidator from "./InputElement/Validators/FediverseValidator"
import SendEmail from "./Popup/SendEmail.svelte"
import NearbyImages from "./Popup/NearbyImages.svelte"
import NearbyImagesCollapsed from "./Popup/NearbyImagesCollapsed.svelte"
import UploadImage from "./Image/UploadImage.svelte"
import AllReviews from "./Reviews/AllReviews.svelte"
import StarsBarIcon from "./Reviews/StarsBarIcon.svelte"
import ReviewForm from "./Reviews/ReviewForm.svelte"
class NearbyImageVis implements SpecialVisualization {
// Class must be in SpecialVisualisations due to weird cyclical import that breaks the tests
@ -265,7 +269,6 @@ export default class SpecialVisualizations {
SpecialVisualizations.specialVisualizations
.map((sp) => sp.funcName + "()")
.join(", ")
}
}
@ -610,17 +613,20 @@ export default class SpecialVisualizations {
{
name: "image-key",
doc: "Image tag to add the URL to (or image-tag:0, image-tag:1 when multiple images are added)",
required: false
required: false,
},
{
name: "label",
doc: "The text to show on the button",
required: false
required: false,
},
],
constr: (state, tags, args) => {
return new SvelteUIElement(UploadImage, {
state,tags, labelText: args[1], image: args[0]
state,
tags,
labelText: args[1],
image: args[0],
})
},
},
@ -642,15 +648,22 @@ export default class SpecialVisualizations {
const nameKey = args[0] ?? "name"
let fallbackName = args[1]
const reviews = FeatureReviews.construct(
feature,
tags,
state.userRelatedState.mangroveIdentity,
{
nameKey: nameKey,
fallbackName,
}
feature,
tags,
state.userRelatedState.mangroveIdentity,
{
nameKey: nameKey,
fallbackName,
}
)
return new SvelteUIElement(StarsBarIcon, {score:reviews.average, reviews, state, tags, feature, layer})
return new SvelteUIElement(StarsBarIcon, {
score: reviews.average,
reviews,
state,
tags,
feature,
layer,
})
},
},
@ -672,15 +685,15 @@ export default class SpecialVisualizations {
const nameKey = args[0] ?? "name"
let fallbackName = args[1]
const reviews = FeatureReviews.construct(
feature,
tags,
state.userRelatedState.mangroveIdentity,
{
nameKey: nameKey,
fallbackName,
}
feature,
tags,
state.userRelatedState.mangroveIdentity,
{
nameKey: nameKey,
fallbackName,
}
)
return new SvelteUIElement(ReviewForm, {reviews, state, tags, feature, layer})
return new SvelteUIElement(ReviewForm, { reviews, state, tags, feature, layer })
},
},
{
@ -711,7 +724,7 @@ export default class SpecialVisualizations {
fallbackName,
}
)
return new SvelteUIElement(AllReviews, {reviews, state, tags, feature, layer})
return new SvelteUIElement(AllReviews, { reviews, state, tags, feature, layer })
},
},
{
@ -920,8 +933,8 @@ export default class SpecialVisualizations {
const id = tags.data[args[0] ?? "id"]
tags = state.featureProperties.getStore(id)
console.log("Id is", id)
return new SvelteUIElement(UploadImage, {state, tags})
}
return new SvelteUIElement(UploadImage, { state, tags })
},
},
{
funcName: "title",

View file

@ -32,7 +32,7 @@
<div class="border-interactive interactive">
Highly interactive area (mostly: active question)
</div>
<div class="flex">
<button class="primary">
<ToSvelte construct={Svg.community_svg().SetClass("w-6 h-6")} />

View file

@ -33,22 +33,22 @@
<Tr t={Translations.t.general.wikipedia.loading} />
</Loading>
{:else}
<span class="wikipedia-article">
<FromHtml src={$wikipediaDetails.firstParagraph} />
<Disclosure let:open>
<DisclosureButton>
<span class="flex">
<ChevronRightIcon
style={(open ? "transform: rotate(90deg); " : "") +
" transition: all .25s linear; width: 1.5rem; height: 1.5rem"}
/>
<Tr t={Translations.t.general.wikipedia.readMore}/>
</span>
</DisclosureButton>
<DisclosurePanel>
<FromHtml src={$wikipediaDetails.restOfArticle} />
</DisclosurePanel>
</Disclosure>
</span>
<span class="wikipedia-article">
<FromHtml src={$wikipediaDetails.firstParagraph} />
<Disclosure let:open>
<DisclosureButton>
<span class="flex">
<ChevronRightIcon
style={(open ? "transform: rotate(90deg); " : "") +
" transition: all .25s linear; width: 1.5rem; height: 1.5rem"}
/>
<Tr t={Translations.t.general.wikipedia.readMore} />
</span>
</DisclosureButton>
<DisclosurePanel>
<FromHtml src={$wikipediaDetails.restOfArticle} />
</DisclosurePanel>
</Disclosure>
</span>
{/if}
{/if}

View file

@ -17,7 +17,7 @@
export let wikiIds: Store<string[]>
let wikipediaStores: Store<Store<FullWikipediaDetails>[]> = Locale.language.bind((language) =>
wikiIds?.map((wikiIds) => wikiIds?.map((id) => Wikipedia.fetchArticleAndWikidata(id, language)))
);
)
let _wikipediaStores
onDestroy(
wikipediaStores.addCallbackAndRunD((wikipediaStores) => {