diff --git a/Logic/FeatureSource/Sources/LayoutSource.ts b/Logic/FeatureSource/Sources/LayoutSource.ts index 822534b555..53626bc14a 100644 --- a/Logic/FeatureSource/Sources/LayoutSource.ts +++ b/Logic/FeatureSource/Sources/LayoutSource.ts @@ -18,6 +18,11 @@ import StaticFeatureSource from "./StaticFeatureSource" * Note that special layers (with `source=null` will be ignored) */ export default class LayoutSource extends FeatureSourceMerger { + /** + * Indicates if a data source is loading something + * TODO fixme + */ + public readonly isLoading: Store = new ImmutableStore(false) constructor( layers: LayerConfig[], featureSwitches: FeatureSwitchState, diff --git a/Models/FilteredLayer.ts b/Models/FilteredLayer.ts index 9eca426588..ea6f4173bf 100644 --- a/Models/FilteredLayer.ts +++ b/Models/FilteredLayer.ts @@ -31,6 +31,7 @@ export default class FilteredLayer { * Contains the current properties a feature should fulfill in order to match the filter */ readonly currentFilter: Store + constructor( layer: LayerConfig, appliedFilters?: Map>, @@ -41,104 +42,34 @@ export default class FilteredLayer { this.appliedFilters = appliedFilters ?? new Map>() - const hasFilter = new UIEventSource(false) const self = this const currentTags = new UIEventSource(undefined) - this.appliedFilters.forEach((filterSrc) => { - filterSrc.addCallbackAndRun((filter) => { - if ((filter ?? 0) !== 0) { - hasFilter.setData(true) - currentTags.setData(self.calculateCurrentTags()) - return - } - - const hf = Array.from(self.appliedFilters.values()).some((f) => (f.data ?? 0) !== 0) - if (hf) { - currentTags.setData(self.calculateCurrentTags()) - } else { - currentTags.setData(undefined) - } - hasFilter.setData(hf) + filterSrc.addCallbackAndRun((_) => { + currentTags.setData(self.calculateCurrentTags()) }) }) - - currentTags.addCallbackAndRunD((t) => console.log("Current filter is", t)) - + this.hasFilter = currentTags.map((ct) => ct !== undefined) this.currentFilter = currentTags } - private calculateCurrentTags(): TagsFilter { - let needed: TagsFilter[] = [] - for (const filter of this.layerDef.filters) { - const state = this.appliedFilters.get(filter.id) - if (state.data === undefined) { - continue - } - if (filter.options[0].fields.length > 0) { - const fieldProperties = FilteredLayer.stringToFieldProperties(state.data) - const asTags = FilteredLayer.fieldsToTags(filter.options[0], fieldProperties) - needed.push(asTags) - continue - } - needed.push(filter.options[state.data].osmTags) - } - needed = Utils.NoNull(needed) - if (needed.length == 0) { - return undefined - } - let tags: TagsFilter - - if (needed.length == 1) { - tags = needed[1] - } else { - tags = new And(needed) - } - let optimized = tags.optimize() - if (optimized === true) { - return undefined - } - if (optimized === false) { - return tags - } - return optimized - } - public static fieldsToString(values: Record): string { + for (const key in values) { + if (values[key] === "") { + delete values[key] + } + } return JSON.stringify(values) } public static stringToFieldProperties(value: string): Record { - return JSON.parse(value) - } - - private static fieldsToTags( - option: FilterConfigOption, - fieldstate: string | Record - ): TagsFilter { - let properties: Record - if (typeof fieldstate === "string") { - properties = FilteredLayer.stringToFieldProperties(fieldstate) - } else { - properties = fieldstate + const values = JSON.parse(value) + for (const key in values) { + if (values[key] === "") { + delete values[key] + } } - console.log("Building tagsspec with properties", properties) - const tagsSpec = Utils.WalkJson(option.originalTagsSpec, (v) => { - if (typeof v !== "string") { - return v - } - - for (const key in properties) { - v = (v).replace("{" + key + "}", properties[key]) - } - - return v - }) - return TagUtils.Tag(tagsSpec) - } - - public disableAllFilters(): void { - this.appliedFilters.forEach((value) => value.setData(undefined)) + return values } /** @@ -177,10 +108,42 @@ export default class FilteredLayer { const appliedFilters = new Map>() for (const subfilter of layer.filters) { - appliedFilters.set(subfilter.id, subfilter.initState()) + appliedFilters.set(subfilter.id, subfilter.initState(layer.id)) } return new FilteredLayer(layer, appliedFilters, isDisplayed) } + + private static fieldsToTags( + option: FilterConfigOption, + fieldstate: string | Record + ): TagsFilter | undefined { + let properties: Record + if (typeof fieldstate === "string") { + properties = FilteredLayer.stringToFieldProperties(fieldstate) + } else { + properties = fieldstate + } + console.log("Building tagsspec with properties", properties) + const missingKeys = option.fields + .map((f) => f.name) + .filter((key) => properties[key] === undefined) + if (missingKeys.length > 0) { + return undefined + } + const tagsSpec = Utils.WalkJson(option.originalTagsSpec, (v) => { + if (typeof v !== "string") { + return v + } + + for (const key in properties) { + v = (v).replace("{" + key + "}", properties[key]) + } + + return v + }) + return TagUtils.Tag(tagsSpec) + } + private static getPref( osmConnection: OsmConnection, key: string, @@ -202,4 +165,49 @@ export default class FilteredLayer { } ) } + + public disableAllFilters(): void { + this.appliedFilters.forEach((value) => value.setData(undefined)) + } + + private calculateCurrentTags(): TagsFilter { + let needed: TagsFilter[] = [] + for (const filter of this.layerDef.filters) { + const state = this.appliedFilters.get(filter.id) + if (state.data === undefined) { + continue + } + if (filter.options[0].fields.length > 0) { + // This is a filter with fields + // We calculate the fields + const fieldProperties = FilteredLayer.stringToFieldProperties(state.data) + const asTags = FilteredLayer.fieldsToTags(filter.options[0], fieldProperties) + console.log("Current field properties:", state.data, fieldProperties, asTags) + if (asTags) { + needed.push(asTags) + } + continue + } + needed.push(filter.options[state.data].osmTags) + } + needed = Utils.NoNull(needed) + if (needed.length == 0) { + return undefined + } + let tags: TagsFilter + + if (needed.length == 1) { + tags = needed[0] + } else { + tags = new And(needed) + } + let optimized = tags.optimize() + if (optimized === true) { + return undefined + } + if (optimized === false) { + return tags + } + return optimized + } } diff --git a/Models/ThemeConfig/FilterConfig.ts b/Models/ThemeConfig/FilterConfig.ts index 3e6db82502..f23e9943ca 100644 --- a/Models/ThemeConfig/FilterConfig.ts +++ b/Models/ThemeConfig/FilterConfig.ts @@ -144,10 +144,12 @@ export default class FilterConfig { }) } - public initState(): UIEventSource { + public initState(layerId: string): UIEventSource { let defaultValue = "" if (this.options.length > 1) { defaultValue = "" + (this.defaultSelection ?? 0) + } else if (this.options[0].fields?.length > 0) { + defaultValue = "{}" } else { // Only a single option if (this.defaultSelection === 0) { @@ -157,7 +159,7 @@ export default class FilterConfig { } } const qp = QueryParameters.GetQueryParameter( - "filter-" + this.id, + `filter-${layerId}-${this.id}`, defaultValue, "State of filter " + this.id ) diff --git a/Models/ThemeViewState.ts b/Models/ThemeViewState.ts index 4bc4a70937..76d362de24 100644 --- a/Models/ThemeViewState.ts +++ b/Models/ThemeViewState.ts @@ -116,7 +116,7 @@ export default class ThemeViewState implements SpecialVisualizationState { const self = this this.layerState = new LayerState(this.osmConnection, layout.layers, layout.id) this.newFeatures = new SimpleFeatureSource(undefined) - this.indexedFeatures = new LayoutSource( + const layoutSource = new LayoutSource( layout.layers, this.featureSwitches, this.newFeatures, @@ -124,6 +124,8 @@ export default class ThemeViewState implements SpecialVisualizationState { this.osmConnection.Backend(), (id) => self.layerState.filteredLayers.get(id).isDisplayed ) + this.indexedFeatures = layoutSource + this.dataIsLoading = layoutSource.isLoading const lastClick = (this.lastClickObject = new LastClickFeatureSource( this.mapProperties.lastClickLocation, this.layout diff --git a/UI/BigComponents/NewPointLocationInput.svelte b/UI/BigComponents/NewPointLocationInput.svelte index e97107d9b0..9d9a98c0ae 100644 --- a/UI/BigComponents/NewPointLocationInput.svelte +++ b/UI/BigComponents/NewPointLocationInput.svelte @@ -45,7 +45,7 @@ /*If no snapping needed: the value is simply the map location; * If snapping is needed: the value will be set later on by the snapping feature source * */ - location: snapToLayers.length === 0 ? value : new UIEventSource<{ lon: number; lat: number }>(coordinate), + location: snapToLayers?.length > 0 ? new UIEventSource<{ lon: number; lat: number }>(coordinate) :value, bounds: new UIEventSource(undefined), allowMoving: new UIEventSource(true), allowZooming: new UIEventSource(true), diff --git a/UI/BigComponents/SimpleAddUI.ts b/UI/BigComponents/SimpleAddUI.ts index 66d31899e8..0caa5f8cb4 100644 --- a/UI/BigComponents/SimpleAddUI.ts +++ b/UI/BigComponents/SimpleAddUI.ts @@ -17,6 +17,7 @@ import { Tag } from "../../Logic/Tags/Tag" import { SpecialVisualizationState } from "../SpecialVisualization" import { Feature } from "geojson" import { FixedUiElement } from "../Base/FixedUiElement" +import Combine from "../Base/Combine" /* * The SimpleAddUI is a single panel, which can have multiple states: @@ -33,83 +34,8 @@ export interface PresetInfo extends PresetConfig { boundsFactor?: 0.25 | number } -export default class SimpleAddUI extends Toggle { +export default class SimpleAddUI extends Combine { constructor(state: SpecialVisualizationState) { - const takeLocationFrom = state.mapProperties.lastClickLocation - const selectedPreset = new UIEventSource(undefined) - - takeLocationFrom.addCallback((_) => selectedPreset.setData(undefined)) - - async function createNewPoint( - tags: Tag[], - location: { lat: number; lon: number }, - snapOntoWay?: OsmWay - ): Promise { - if (snapOntoWay) { - tags.push(new Tag("_referencing_ways", "way/" + snapOntoWay.id)) - } - const newElementAction = new CreateNewNodeAction(tags, location.lat, location.lon, { - theme: state.layout?.id ?? "unkown", - changeType: "create", - snapOnto: snapOntoWay, - }) - await state.changes.applyAction(newElementAction) - selectedPreset.setData(undefined) - const selectedFeature: Feature = state.indexedFeatures.featuresById.data.get( - newElementAction.newElementId - ) - state.selectedElement.setData(selectedFeature) - Hash.hash.setData(newElementAction.newElementId) - } - - const addUi = new VariableUiElement( - selectedPreset.map((preset) => { - function confirm( - tags: any[], - location: { lat: number; lon: number }, - snapOntoWayId?: WayId - ) { - if (snapOntoWayId === undefined) { - createNewPoint(tags, location, undefined) - } else { - OsmObject.DownloadObject(snapOntoWayId).addCallbackAndRunD((way) => { - createNewPoint(tags, location, way) - return true - }) - } - } - - function cancel() { - selectedPreset.setData(undefined) - } - - const message = Translations.t.general.add.addNew.Subs( - { category: preset.name }, - preset.name["context"] - ) - return new FixedUiElement("ConfirmLocationOfPoint...") /*ConfirmLocationOfPoint( - state, - filterViewIsOpened, - preset, - message, - takeLocationFrom.data, - confirm, - cancel, - () => { - selectedPreset.setData(undefined) - }, - { - cancelIcon: Svg.back_svg(), - cancelText: Translations.t.general.add.backToSelect, - } - )*/ - }) - ) - - super( - new Loading(Translations.t.general.add.stillLoading).SetClass("alert"), - addUi, - state.dataIsLoading - ) + super([]) } } diff --git a/UI/NewPoint/ConfirmLocationOfPoint.ts b/UI/NewPoint/ConfirmLocationOfPoint.ts index 266adae669..3d03da8864 100644 --- a/UI/NewPoint/ConfirmLocationOfPoint.ts +++ b/UI/NewPoint/ConfirmLocationOfPoint.ts @@ -1,23 +1,16 @@ import { UIEventSource } from "../../Logic/UIEventSource" import BaseUIElement from "../BaseUIElement" -import LocationInput from "../Input/LocationInput" -import { BBox } from "../../Logic/BBox" -import { TagUtils } from "../../Logic/Tags/TagUtils" import { SubtleButton } from "../Base/SubtleButton" import Combine from "../Base/Combine" import Translations from "../i18n/Translations" import Svg from "../../Svg" import Toggle from "../Input/Toggle" import { PresetInfo } from "../BigComponents/SimpleAddUI" -import Title from "../Base/Title" import { VariableUiElement } from "../Base/VariableUIElement" import { Tag } from "../../Logic/Tags/Tag" import { WayId } from "../../Models/OsmFeature" import { Translation } from "../i18n/Translation" -import { Feature } from "geojson" -import { AvailableRasterLayers } from "../../Models/RasterLayers" import { SpecialVisualizationState } from "../SpecialVisualization" -import ClippedFeatureSource from "../../Logic/FeatureSource/Sources/ClippedFeatureSource" export default class ConfirmLocationOfPoint extends Combine { constructor( @@ -38,76 +31,6 @@ export default class ConfirmLocationOfPoint extends Combine { cancelText?: string | Translation } ) { - let preciseInput: LocationInput = undefined - if (preset.preciseInput !== undefined) { - // Create location input - - // We uncouple the event source - const zloc = { ...loc, zoom: 19 } - const locationSrc = new UIEventSource(zloc) - - let backgroundLayer = new UIEventSource( - state?.mapProperties.rasterLayer?.data ?? AvailableRasterLayers.osmCarto - ) - if (preset.preciseInput.preferredBackground) { - const defaultBackground = AvailableRasterLayers.SelectBestLayerAccordingTo( - locationSrc, - new UIEventSource(preset.preciseInput.preferredBackground) - ) - // Note that we _break the link_ here, as the minimap will take care of the switching! - backgroundLayer.setData(defaultBackground.data) - } - - let snapToFeatures: UIEventSource = undefined - let mapBounds: UIEventSource = undefined - if (preset.preciseInput.snapToLayers && preset.preciseInput.snapToLayers.length > 0) { - snapToFeatures = new UIEventSource([]) - mapBounds = new UIEventSource(undefined) - } - - const tags = TagUtils.KVtoProperties(preset.tags ?? []) - preciseInput = new LocationInput({ - mapBackground: backgroundLayer, - centerLocation: locationSrc, - snapTo: snapToFeatures, - renderLayerForSnappedPoint: preset.layerToAddTo.layerDef, - snappedPointTags: tags, - maxSnapDistance: preset.preciseInput.maxSnapDistance, - bounds: mapBounds, - state: state, - }) - preciseInput.installBounds(preset.boundsFactor ?? 0.25, true) - preciseInput - .SetClass("rounded-xl overflow-hidden border border-gray") - .SetStyle("height: 18rem; max-height: 50vh") - - if (preset.preciseInput.snapToLayers && preset.preciseInput.snapToLayers.length > 0) { - // We have to snap to certain layers. - // Lets fetch them - - let loadedBbox: BBox = undefined - mapBounds?.addCallbackAndRunD((bbox) => { - if (loadedBbox !== undefined && bbox.isContainedIn(loadedBbox)) { - // All is already there - // return; - } - - bbox = bbox.pad( - Math.max(preset.boundsFactor ?? 0.25, 2), - Math.max(preset.boundsFactor ?? 0.25, 2) - ) - loadedBbox = bbox - const sources = preset.preciseInput.snapToLayers.map( - (layerId) => - new ClippedFeatureSource( - state.perLayer.get(layerId), - bbox.asGeoJson({}) - ) - ) - }) - } - } - let confirmButton: BaseUIElement = new SubtleButton( preset.icon(), new Combine([confirmText]).SetClass("flex flex-col") @@ -119,38 +42,12 @@ export default class ConfirmLocationOfPoint extends Combine { .map((gf) => gf.onNewPoint.tags) const globalTags: Tag[] = [].concat(...globalFilterTagsToAdd) console.log("Global tags to add are: ", globalTags) - confirm( - [...preset.tags, ...globalTags], - preciseInput?.GetValue()?.data ?? loc, - preciseInput?.snappedOnto?.data?.properties?.id - ) }) - if (preciseInput !== undefined) { - confirmButton = new Combine([preciseInput, confirmButton]) - } else { - confirmButton = new Combine([confirmButton]) - } + confirmButton = new Combine([confirmButton]) let openLayerOrConfirm = confirmButton - const disableFilter = new SubtleButton( - new Combine([ - Svg.filter_ui().SetClass("absolute w-full"), - Svg.cross_bottom_right_svg().SetClass("absolute red-svg"), - ]).SetClass("relative"), - new Combine([ - Translations.t.general.add.disableFiltersExplanation.Clone(), - Translations.t.general.add.disableFilters.Clone().SetClass("text-xl"), - ]).SetClass("flex flex-col") - ).onClick(() => { - const appliedFilters = preset.layerToAddTo.appliedFilters - appliedFilters.data.forEach((_, k) => appliedFilters.data.set(k, undefined)) - appliedFilters.ping() - cancel() - closePopup() - }) - // We assume the number of global filters won't change during the run of the program for (let i = 0; i < state.globalFilters.data.length; i++) { const hasBeenCheckedOf = new UIEventSource(false) @@ -178,31 +75,6 @@ export default class ConfirmLocationOfPoint extends Combine { ) } - // If at least one filter is active which _might_ hide a newly added item, this blocks the preset and requests the filter to be disabled - const disableFiltersOrConfirm = new Toggle(openLayerOrConfirm, disableFilter) - - const cancelButton = new SubtleButton( - options?.cancelIcon ?? Svg.close_ui(), - options?.cancelText ?? Translations.t.general.cancel - ).onClick(cancel) - - let examples: BaseUIElement = undefined - if (preset.exampleImages !== undefined && preset.exampleImages.length > 0) { - examples = new Combine([new Title()]) - } - - super([ - new Toggle( - Translations.t.general.testing.SetClass("alert"), - undefined, - state.featureSwitchIsTesting - ), - disableFiltersOrConfirm, - cancelButton, - preset.description, - examples, - ]) - - this.SetClass("flex flex-col") + super([openLayerOrConfirm]) } } diff --git a/UI/Popup/AddNewPoint/AddNewPoint.svelte b/UI/Popup/AddNewPoint/AddNewPoint.svelte index efc4b730d0..75b35a0d73 100644 --- a/UI/Popup/AddNewPoint/AddNewPoint.svelte +++ b/UI/Popup/AddNewPoint/AddNewPoint.svelte @@ -35,7 +35,7 @@ let confirmedCategory = false; $: if (selectedPreset === undefined) { confirmedCategory = false; - creating = false + creating = false; } let flayer: FilteredLayer = undefined; @@ -51,6 +51,7 @@ const zoom = state.mapProperties.zoom; + const isLoading = state.dataIsLoading; let preciseCoordinate: UIEventSource<{ lon: number, lat: number }> = new UIEventSource(undefined); let snappedToObject: UIEventSource = new UIEventSource(undefined); @@ -95,7 +96,6 @@ } }); state.newFeatures.features.ping(); - console.log("New features:", state.newFeatures.features.data ) { // Set some metainfo const tagsStore = state.featureProperties.getStore(newId); @@ -114,7 +114,7 @@ abort(); state.selectedElement.setData(feature); state.selectedLayer.setData(selectedPreset.layer); - + } @@ -123,8 +123,13 @@ - - {#if $zoom < Constants.minZoomLevelToAddNewPoint} + {#if $isLoading} +
+ + + +
+ {:else if $zoom < Constants.minZoomLevelToAddNewPoint}
diff --git a/test.ts b/test.ts index 5bbbac83ab..488e19a817 100644 --- a/test.ts +++ b/test.ts @@ -2,7 +2,7 @@ import SvelteUIElement from "./UI/Base/SvelteUIElement" import ThemeViewGUI from "./UI/ThemeViewGUI.svelte" import { FixedUiElement } from "./UI/Base/FixedUiElement" import LayoutConfig from "./Models/ThemeConfig/LayoutConfig" -import * as theme from "./assets/generated/themes/aed.json" +import * as theme from "./assets/generated/themes/shops.json" import ThemeViewState from "./Models/ThemeViewState" import Combine from "./UI/Base/Combine" import SpecialVisualizations from "./UI/SpecialVisualizations"