refactoring: move logic of lastclick into special layer, fix labels, fix anchoring

This commit is contained in:
Pieter Vander Vennet 2023-04-02 02:59:20 +02:00
parent 25a98af057
commit 52a0810ea9
47 changed files with 682 additions and 197 deletions

11
UI/Base/FloatOver.svelte Normal file
View file

@ -0,0 +1,11 @@
<script lang="ts">
/**
* The slotted element will be shown on top, with a lower-opacity border
*/
</script>
<div class="absolute top-0 right-0 w-screen h-screen overflow-auto" style="background-color: #00000088">
<div class="flex flex-col m-4 sm:m-6 md:m-8 p-4 sm:p-6 md:m-8 normal-background rounded normal-background">
<slot></slot>
</div>
</div>

View file

@ -1,7 +1,7 @@
/**
* Asks to add a feature at the last clicked location, at least if zoom is sufficient
*/
import { ImmutableStore, Store, UIEventSource } from "../../Logic/UIEventSource"
import { ImmutableStore, UIEventSource } from "../../Logic/UIEventSource"
import Svg from "../../Svg"
import { SubtleButton } from "../Base/SubtleButton"
import Combine from "../Base/Combine"
@ -16,18 +16,14 @@ import CreateNewNodeAction from "../../Logic/Osm/Actions/CreateNewNodeAction"
import { OsmObject, OsmWay } from "../../Logic/Osm/OsmObject"
import PresetConfig from "../../Models/ThemeConfig/PresetConfig"
import FilteredLayer from "../../Models/FilteredLayer"
import Loc from "../../Models/Loc"
import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig"
import { Changes } from "../../Logic/Osm/Changes"
import FeaturePipeline from "../../Logic/FeatureSource/FeaturePipeline"
import { ElementStorage } from "../../Logic/ElementStorage"
import ConfirmLocationOfPoint from "../NewPoint/ConfirmLocationOfPoint"
import Loading from "../Base/Loading"
import Hash from "../../Logic/Web/Hash"
import { WayId } from "../../Models/OsmFeature"
import { Tag } from "../../Logic/Tags/Tag"
import { LoginToggle } from "../Popup/LoginButton"
import { GlobalFilter } from "../../Models/GlobalFilter"
import { SpecialVisualizationState } from "../SpecialVisualization"
import { Feature } from "geojson"
/*
* The SimpleAddUI is a single panel, which can have multiple states:
@ -47,34 +43,8 @@ export interface PresetInfo extends PresetConfig {
export default class SimpleAddUI extends LoginToggle {
/**
*
* @param isShown
* @param resetScrollSignal
* @param filterViewIsOpened
* @param state
* @param takeLocationFrom: defaults to state.lastClickLocation. Take this location to add the new point around
*/
constructor(
isShown: UIEventSource<boolean>,
resetScrollSignal: UIEventSource<void>,
filterViewIsOpened: UIEventSource<boolean>,
state: {
featureSwitchIsTesting: UIEventSource<boolean>
featureSwitchUserbadge: Store<boolean>
layoutToUse: LayoutConfig
osmConnection: OsmConnection
changes: Changes
allElements: ElementStorage
LastClickLocation: UIEventSource<{ lat: number; lon: number }>
featurePipeline: FeaturePipeline
selectedElement: UIEventSource<any>
locationControl: UIEventSource<Loc>
filteredLayers: UIEventSource<FilteredLayer[]>
featureSwitchFilter: UIEventSource<boolean>
backgroundLayer: UIEventSource<BaseLayer>
globalFilters: UIEventSource<GlobalFilter[]>
},
takeLocationFrom?: UIEventSource<{ lat: number; lon: number }>
) {
constructor(state: SpecialVisualizationState) {
const readYourMessages = new Combine([
Translations.t.general.readYourMessages.Clone().SetClass("alert"),
new SubtleButton(Svg.envelope_ui(), Translations.t.general.goToInbox, {
@ -83,13 +53,10 @@ export default class SimpleAddUI extends LoginToggle {
}),
])
takeLocationFrom = takeLocationFrom ?? state.LastClickLocation
const filterViewIsOpened = state.guistate.filterViewIsOpened
const takeLocationFrom = state.mapProperties.lastClickLocation
const selectedPreset = new UIEventSource<PresetInfo>(undefined)
selectedPreset.addCallback((_) => {
resetScrollSignal.ping()
})
isShown.addCallback((_) => selectedPreset.setData(undefined)) // Clear preset selection when the UI is closed/opened
takeLocationFrom.addCallback((_) => selectedPreset.setData(undefined))
const presetsOverview = SimpleAddUI.CreateAllPresetsPanel(selectedPreset, state)
@ -104,14 +71,13 @@ export default class SimpleAddUI extends LoginToggle {
tags.push(new Tag("_referencing_ways", "way/" + snapOntoWay.id))
}
const newElementAction = new CreateNewNodeAction(tags, location.lat, location.lon, {
theme: state.layoutToUse?.id ?? "unkown",
theme: state.layout?.id ?? "unkown",
changeType: "create",
snapOnto: snapOntoWay,
})
await state.changes.applyAction(newElementAction)
selectedPreset.setData(undefined)
isShown.setData(false)
const selectedFeature = state.allElements.ContainingFeatures.get(
const selectedFeature: Feature = state.indexedFeatures.featuresById.data.get(
newElementAction.newElementId
)
state.selectedElement.setData(selectedFeature)
@ -156,7 +122,7 @@ export default class SimpleAddUI extends LoginToggle {
confirm,
cancel,
() => {
isShown.setData(false)
selectedPreset.setData(undefined)
},
{
cancelIcon: Svg.back_svg(),
@ -172,11 +138,11 @@ export default class SimpleAddUI extends LoginToggle {
new Toggle(
new Loading(Translations.t.general.add.stillLoading).SetClass("alert"),
addUi,
state.featurePipeline.runningQuery
state.dataIsLoading
),
Translations.t.general.add.zoomInFurther.Clone().SetClass("alert"),
state.locationControl.map(
(loc) => loc.zoom >= Constants.userJourney.minZoomLevelToAddNewPoints
state.mapProperties.zoom.map(
(zoom) => zoom >= Constants.minZoomLevelToAddNewPoint
)
),
readYourMessages,
@ -222,12 +188,7 @@ export default class SimpleAddUI extends LoginToggle {
private static CreateAllPresetsPanel(
selectedPreset: UIEventSource<PresetInfo>,
state: {
featureSwitchIsTesting: UIEventSource<boolean>
filteredLayers: UIEventSource<FilteredLayer[]>
featureSwitchFilter: UIEventSource<boolean>
osmConnection: OsmConnection
}
state: SpecialVisualizationState
): BaseUIElement {
const presetButtons = SimpleAddUI.CreatePresetButtons(state, selectedPreset)
let intro: BaseUIElement = Translations.t.general.add.intro
@ -260,18 +221,14 @@ export default class SimpleAddUI extends LoginToggle {
/*
* Generates the list with all the buttons.*/
private static CreatePresetButtons(
state: {
filteredLayers: UIEventSource<FilteredLayer[]>
featureSwitchFilter: UIEventSource<boolean>
osmConnection: OsmConnection
},
state: SpecialVisualizationState,
selectedPreset: UIEventSource<PresetInfo>
): BaseUIElement {
const allButtons = []
for (const layer of state.filteredLayers.data) {
for (const layer of Array.from(state.layerState.filteredLayers.values())) {
if (layer.isDisplayed.data === false) {
// The layer is not displayed...
if (!state.featureSwitchFilter.data) {
if (!state.featureSwitches.featureSwitchFilter.data) {
// ...and we cannot enable the layer control -> we skip, as these presets can never be shown anyway
continue
}

View file

@ -48,7 +48,6 @@ export default class DefaultGUI {
public setup() {
this.SetupUIElements()
this.SetupMap()
ScrollableFullScreen.ActivateCurrent()
if (
this.state.layoutToUse.customCss !== undefined &&
@ -168,13 +167,6 @@ export default class DefaultGUI {
features: state.selectedElementsLayer,
state,
})
state.leafletMap.addCallbackAndRunD((_) => {
// Lets assume that all showDataLayers are initialized at this point
state.selectedElement.ping()
State.state.locationControl.ping()
return true
})
}
private SetupUIElements() {

View file

@ -20,11 +20,12 @@ import StaticFeatureSource from "../../Logic/FeatureSource/Sources/StaticFeature
class PointRenderingLayer {
private readonly _config: PointRenderingConfig
private readonly _visibility?: Store<boolean>
private readonly _fetchStore?: (id: string) => Store<Record<string, string>>
private readonly _map: MlMap
private readonly _onClick: (feature: Feature) => void
private readonly _allMarkers: Map<string, Marker> = new Map<string, Marker>()
private _dirty = false
constructor(
map: MlMap,
features: FeatureSource,
@ -33,6 +34,7 @@ class PointRenderingLayer {
fetchStore?: (id: string) => Store<Record<string, string>>,
onClick?: (feature: Feature) => void
) {
this._visibility = visibility
this._config = config
this._map = map
this._fetchStore = fetchStore
@ -40,10 +42,20 @@ class PointRenderingLayer {
const self = this
features.features.addCallbackAndRunD((features) => self.updateFeatures(features))
visibility?.addCallbackAndRunD((visible) => self.setVisibility(visible))
visibility?.addCallbackAndRunD((visible) => {
if (visible === true && self._dirty) {
self.updateFeatures(features.features.data)
}
self.setVisibility(visible)
})
}
private updateFeatures(features: Feature[]) {
if (this._visibility?.data === false) {
this._dirty = true
return
}
this._dirty = false
const cache = this._allMarkers
const unseenKeys = new Set(cache.keys())
for (const location of this._config.location) {
@ -58,6 +70,9 @@ class PointRenderingLayer {
this._config
)
}
const id = feature.properties.id + "-" + location
unseenKeys.delete(id)
const loc = GeoOperations.featureToCoordinateWithRenderingType(
<any>feature,
location
@ -65,8 +80,6 @@ class PointRenderingLayer {
if (loc === undefined) {
continue
}
const id = feature.properties.id + "-" + location
unseenKeys.delete(id)
if (cache.has(id)) {
const cached = cache.get(id)
@ -357,10 +370,12 @@ export default class ShowDataLayer {
private initDrawFeatures(map: MlMap) {
let { features, doShowLayer, fetchStore, selectedElement, selectedLayer } = this._options
const onClick = (feature: Feature) => {
selectedElement?.setData(feature)
selectedLayer?.setData(this._options.layer)
}
const onClick =
this._options.onClick ??
((feature: Feature) => {
selectedElement?.setData(feature)
selectedLayer?.setData(this._options.layer)
})
for (let i = 0; i < this._options.layer.lineRendering.length; i++) {
const lineRenderingConfig = this._options.layer.lineRendering[i]
new LineRenderingLayer(

View file

@ -33,4 +33,6 @@ export interface ShowDataLayerOptions {
* If given, the map will update when a property is changed
*/
fetchStore?: (id: string) => Store<Record<string, string>>
onClick?: (feature: Feature) => void
}

View file

@ -18,6 +18,8 @@ import Toggle from "../Input/Toggle"
import Title from "../Base/Title"
import { MapillaryLinkVis } from "./MapillaryLinkVis"
import { SpecialVisualization, SpecialVisualizationState } from "../SpecialVisualization"
import LayerConfig from "../../Models/ThemeConfig/LayerConfig"
import { Feature } from "geojson"
export class NearbyImageVis implements SpecialVisualization {
args: { name: string; defaultValue?: string; doc: string; required?: boolean }[] = [
@ -39,11 +41,12 @@ export class NearbyImageVis implements SpecialVisualization {
constr(
state: SpecialVisualizationState,
tagSource: UIEventSource<Record<string, string>>,
args: string[]
args: string[],
feature: Feature,
layer: LayerConfig
): BaseUIElement {
const t = Translations.t.image.nearbyPictures
const mode: "open" | "expandable" | "collapsable" = <any>args[0]
const feature = state.indexedFeatures.featuresById.data.get(tagSource.data.id)
const [lon, lat] = GeoOperations.centerpointCoordinates(feature)
const id: string = tagSource.data["id"]
const canBeEdited: boolean = !!id?.match("(node|way|relation)/-?[0-9]+")
@ -128,7 +131,7 @@ export class NearbyImageVis implements SpecialVisualization {
slideshow,
controls,
saveButton,
new MapillaryLinkVis().constr(state, tagSource, []).SetClass("mt-6"),
new MapillaryLinkVis().constr(state, tagSource, [], feature).SetClass("mt-6"),
])
})

View file

@ -4,6 +4,7 @@
import { onDestroy } from "svelte";
import { Translation } from "../../i18n/Translation";
import Locale from "../../i18n/Locale";
import FromHtml from "../../Base/FromHtml.svelte";
export let template: Translation;
let _template: string
@ -20,7 +21,7 @@
</script>
<span>
{Utils.SubstituteKeys(before, _tags)}
<FromHtml src={Utils.SubstituteKeys(before, _tags)}/>
<slot />
{Utils.SubstituteKeys(after, _tags)}
<FromHtml src={Utils.SubstituteKeys(after, _tags)}/>
</span>

View file

@ -41,10 +41,7 @@
return true;
}
console.log("Got layer", layer, onlyForLabels, notForLabels);
const baseQuestions = (layer.tagRenderings ?? [])?.filter(tr => allowed(tr.labels) && tr.question !== undefined);
console.log("BaseQuestions are", baseQuestions);
let skippedQuestions = new UIEventSource<Set<string>>(new Set<string>());
let questionsToAsk = tags.map(tags => {

View file

@ -15,6 +15,7 @@
import { createEventDispatcher } from "svelte";
import LayerConfig from "../../../Models/ThemeConfig/LayerConfig";
import { ExclamationIcon } from "@rgossiaux/svelte-heroicons/solid";
import SpecialTranslation from "./SpecialTranslation.svelte";
export let config: TagRenderingConfig;
export let tags: UIEventSource<Record<string, string>>;
@ -86,15 +87,15 @@
<div class="border border-black subtle-background flex flex-col">
<If condition={state.featureSwitchIsTesting}>
<div class="flex justify-between">
<Tr t={config.question}></Tr>
<SpecialTranslation t={config.question} {tags} {state} {layer} feature={selectedElement}></SpecialTranslation>
<span class="alert">{config.id}</span>
</div>
<Tr slot="else" t={config.question}></Tr>
<SpecialTranslation slot="else" t={config.question} {tags} {state} {layer} feature={selectedElement}></SpecialTranslation>
</If>
{#if config.questionhint}
<div class="subtle">
<Tr t={config.questionHint}></Tr>
<SpecialTranslation t={config.questionhint} {tags} {state} {layer} feature={selectedElement}></SpecialTranslation>
</div>
{/if}

View file

@ -12,6 +12,7 @@ import FullNodeDatabaseSource from "../Logic/FeatureSource/TiledFeatureSource/Fu
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"
/**
* The state needed to render a special Visualisation.
@ -19,6 +20,7 @@ import LayerConfig from "../Models/ThemeConfig/LayerConfig"
export interface SpecialVisualizationState {
readonly guistate: DefaultGuiState
readonly layout: LayoutConfig
readonly featureSwitches: FeatureSwitchState
readonly layerState: LayerState
readonly featureProperties: { getStore(id: string): UIEventSource<Record<string, string>> }

View file

@ -56,6 +56,7 @@ import Maproulette from "../Logic/Maproulette"
import SvelteUIElement from "./Base/SvelteUIElement"
import { BBoxFeatureSourceForLayer } from "../Logic/FeatureSource/Sources/TouchesBboxFeatureSource"
import QuestionViz from "./Popup/QuestionViz"
import SimpleAddUI from "./BigComponents/SimpleAddUI"
export default class SpecialVisualizations {
public static specialVisualizations: SpecialVisualization[] = SpecialVisualizations.initList()
@ -232,6 +233,15 @@ export default class SpecialVisualizations {
private static initList(): SpecialVisualization[] {
const specialVisualizations: SpecialVisualization[] = [
new QuestionViz(),
{
funcName: "add_new_point",
docs: "An element which allows to add a new point on the 'last_click'-location. Only makes sense in the layer `last_click`",
args: [],
constr(state: SpecialVisualizationState): BaseUIElement {
return new SimpleAddUI(state)
},
},
new HistogramViz(),
new StealViz(),
new MinimapViz(),
@ -670,7 +680,9 @@ export default class SpecialVisualizations {
if (title === undefined) {
return undefined
}
return new SubstitutedTranslation(title, tagsSource, state)
return new SubstitutedTranslation(title, tagsSource, state).RemoveClass(
"w-full"
)
})
),
},

View file

@ -11,6 +11,7 @@ import LinkToWeblate from "./Base/LinkToWeblate"
import { SpecialVisualization, SpecialVisualizationState } from "./SpecialVisualization"
import SpecialVisualizations from "./SpecialVisualizations"
import { Feature } from "geojson"
import LayerConfig from "../Models/ThemeConfig/LayerConfig"
export class SubstitutedTranslation extends VariableUiElement {
public constructor(
@ -24,7 +25,8 @@ export class SubstitutedTranslation extends VariableUiElement {
state: SpecialVisualizationState,
tagSource: UIEventSource<Record<string, string>>,
argument: string[],
feature: Feature
feature: Feature,
layer: LayerConfig
) => BaseUIElement)
> = undefined
) {
@ -85,7 +87,7 @@ export class SubstitutedTranslation extends VariableUiElement {
tagsSource.data.id
)
return viz.func
.constr(state, tagsSource, proto.args, feature)
.constr(state, tagsSource, proto.args, feature, undefined)
?.SetStyle(proto.style)
} catch (e) {
console.error("SPECIALRENDERING FAILED for", tagsSource.data?.id, e)

View file

@ -1,5 +1,5 @@
<script lang="ts">
import { Store, UIEventSource } from "../Logic/UIEventSource";
import { ImmutableStore, Store, UIEventSource } from "../Logic/UIEventSource";
import { Map as MlMap } from "maplibre-gl";
import MaplibreMap from "./Map/MaplibreMap.svelte";
import LayoutConfig from "../Models/ThemeConfig/LayoutConfig";
@ -22,6 +22,7 @@
import { MenuIcon } from "@rgossiaux/svelte-heroicons/solid";
import Tr from "./Base/Tr.svelte";
import CommunityIndexView from "./BigComponents/CommunityIndexView.svelte";
import FloatOver from "./Base/FloatOver.svelte";
export let layout: LayoutConfig;
const state = new ThemeViewState(layout);
@ -57,6 +58,11 @@
<MapControlButton on:click={() =>state.guistate.menuIsOpened.setData(true)}>
<MenuIcon class="w-8 h-8"></MenuIcon>
</MapControlButton>
<If condition={state.featureSwitchIsTesting}>
<span class="alert">
Testmode
</span>
</If>
</div>
<div class="absolute bottom-0 left-0 mb-4 ml-4">
@ -87,8 +93,7 @@
<If condition={state.guistate.welcomeMessageIsOpened}>
<!-- Theme page -->
<div class="absolute top-0 left-0 w-screen h-screen" style="background-color: #00000088">
<div class="flex flex-col m-4 sm:m-6 md:m-8 p-4 sm:p-6 md:m-8 normal-background rounded">
<FloatOver>
<div on:click={() => state.guistate.welcomeMessageIsOpened.setData(false)}>Close</div>
<TabGroup>
<TabList>
@ -136,15 +141,13 @@
<TabPanel>Content 3</TabPanel>
</TabPanels>
</TabGroup>
</div>
</div>
</FloatOver>
</If>
<If condition={state.guistate.menuIsOpened}>
<!-- Menu page -->
<div class="absolute top-0 left-0 w-screen h-screen overflow-auto" style="background-color: #00000088">
<div class="flex flex-col m-4 sm:m-6 md:m-8 p-4 sm:p-6 md:m-8 normal-background rounded">
<FloatOver>
<div on:click={() => state.guistate.menuIsOpened.setData(false)}>Close</div>
<TabGroup>
<TabList>
@ -174,20 +177,15 @@
<TabPanel>Privacy</TabPanel>
</TabPanels>
</TabGroup>
</div>
</div>
</FloatOver>
</If>
{#if $selectedElement !== undefined && $selectedLayer !== undefined}
<div class="absolute top-0 right-0 w-screen h-screen overflow-auto" style="background-color: #00000088">
<FloatOver>
<SelectedElementView layer={$selectedLayer} selectedElement={$selectedElement}
tags={$selectedElementTags} state={state}></SelectedElementView>
</FloatOver>
<div class="flex flex-col m-4 sm:m-6 md:m-8 p-4 sm:p-6 md:m-8 normal-background rounded normal-background">
<SelectedElementView layer={$selectedLayer} selectedElement={$selectedElement}
tags={$selectedElementTags} state={state}></SelectedElementView>
</div>
</div>
{/if}
<style>
/* WARNING: This is just for demonstration.