From 670d2f0b323f7c216ec0ec37c92a6bc41777629f Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Thu, 13 Apr 2023 22:44:35 +0200 Subject: [PATCH] Refactoring: fix tests --- UI/Popup/NearbyImageVis.ts | 153 ------------------------- UI/Popup/StealViz.ts | 70 ------------ UI/SpecialVisualization.ts | 8 +- UI/SpecialVisualizations.ts | 216 +++++++++++++++++++++++++++++++++++- 4 files changed, 213 insertions(+), 234 deletions(-) delete mode 100644 UI/Popup/NearbyImageVis.ts delete mode 100644 UI/Popup/StealViz.ts diff --git a/UI/Popup/NearbyImageVis.ts b/UI/Popup/NearbyImageVis.ts deleted file mode 100644 index 194a058ac..000000000 --- a/UI/Popup/NearbyImageVis.ts +++ /dev/null @@ -1,153 +0,0 @@ -import { UIEventSource } from "../../Logic/UIEventSource" -import BaseUIElement from "../BaseUIElement" -import Translations from "../i18n/Translations" -import { GeoOperations } from "../../Logic/GeoOperations" -import NearbyImages, { NearbyImageOptions, P4CPicture, SelectOneNearbyImage } from "./NearbyImages" -import { SubstitutedTranslation } from "../SubstitutedTranslation" -import { Tag } from "../../Logic/Tags/Tag" -import ChangeTagAction from "../../Logic/Osm/Actions/ChangeTagAction" -import { And } from "../../Logic/Tags/And" -import { SaveButton } from "./SaveButton" -import Lazy from "../Base/Lazy" -import { CheckBox } from "../Input/Checkboxes" -import Slider from "../Input/Slider" -import AllImageProviders from "../../Logic/ImageProviders/AllImageProviders" -import Combine from "../Base/Combine" -import { VariableUiElement } from "../Base/VariableUIElement" -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 }[] = [ - { - name: "mode", - defaultValue: "expandable", - doc: "Indicates how this component is initialized. Options are: \n\n- `open`: always show and load the pictures\n- `collapsable`: show the pictures, but a user can collapse them\n- `expandable`: shown by default; but a user can collapse them.", - }, - { - name: "mapillary", - defaultValue: "true", - doc: "If 'true', includes a link to mapillary on this location.", - }, - ] - docs = - "A component showing nearby images loaded from various online services such as Mapillary. In edit mode and when used on a feature, the user can select an image to add to the feature" - funcName = "nearby_images" - - constr( - state: SpecialVisualizationState, - tagSource: UIEventSource>, - args: string[], - feature: Feature, - layer: LayerConfig - ): BaseUIElement { - const t = Translations.t.image.nearbyPictures - const mode: "open" | "expandable" | "collapsable" = args[0] - const [lon, lat] = GeoOperations.centerpointCoordinates(feature) - const id: string = tagSource.data["id"] - const canBeEdited: boolean = !!id?.match("(node|way|relation)/-?[0-9]+") - const selectedImage = new UIEventSource(undefined) - - let saveButton: BaseUIElement = undefined - if (canBeEdited) { - const confirmText: BaseUIElement = new SubstitutedTranslation( - t.confirm, - tagSource, - state - ) - - const onSave = async () => { - console.log("Selected a picture...", selectedImage.data) - const osmTags = selectedImage.data.osmTags - const tags: Tag[] = [] - for (const key in osmTags) { - tags.push(new Tag(key, osmTags[key])) - } - await state?.changes?.applyAction( - new ChangeTagAction(id, new And(tags), tagSource.data, { - theme: state?.layout.id, - changeType: "link-image", - }) - ) - } - saveButton = new SaveButton( - selectedImage, - state.osmConnection, - confirmText, - t.noImageSelected - ) - .onClick(onSave) - .SetClass("flex justify-end") - } - - const nearby = new Lazy(() => { - const towardsCenter = new CheckBox(t.onlyTowards, false) - - const maxSearchRadius = 100 - const stepSize = 10 - const defaultValue = Math.floor(maxSearchRadius / (2 * stepSize)) * stepSize - const fromOsmPreferences = state?.osmConnection - ?.GetPreference("nearby-images-radius", "" + defaultValue) - .sync( - (s) => Number(s), - [], - (i) => "" + i - ) - const radiusValue = new UIEventSource(fromOsmPreferences.data) - radiusValue.addCallbackAndRunD((v) => fromOsmPreferences.setData(v)) - - const radius = new Slider(stepSize, maxSearchRadius, { - value: radiusValue, - step: 10, - }) - const alreadyInTheImage = AllImageProviders.LoadImagesFor(tagSource) - const options: NearbyImageOptions & { value } = { - lon, - lat, - searchRadius: maxSearchRadius, - shownRadius: radius.GetValue(), - value: selectedImage, - blacklist: alreadyInTheImage, - towardscenter: towardsCenter.GetValue(), - maxDaysOld: 365 * 3, - } - const slideshow = canBeEdited - ? new SelectOneNearbyImage(options, state.indexedFeatures) - : new NearbyImages(options, state.indexedFeatures) - const controls = new Combine([ - towardsCenter, - new Combine([ - new VariableUiElement( - radius.GetValue().map((radius) => t.withinRadius.Subs({ radius })) - ), - radius, - ]).SetClass("flex justify-between"), - ]).SetClass("flex flex-col") - return new Combine([ - slideshow, - controls, - saveButton, - new MapillaryLinkVis().constr(state, tagSource, [], feature).SetClass("mt-6"), - ]) - }) - - let withEdit: BaseUIElement = nearby - if (canBeEdited) { - withEdit = new Combine([t.hasMatchingPicture, nearby]).SetClass("flex flex-col") - } - - if (mode === "open") { - return withEdit - } - const toggleState = new UIEventSource(mode === "collapsable") - return new Toggle( - new Combine([new Title(t.title), withEdit]), - new Title(t.browseNearby).onClick(() => toggleState.setData(true)), - toggleState - ) - } -} diff --git a/UI/Popup/StealViz.ts b/UI/Popup/StealViz.ts deleted file mode 100644 index 20e74ebca..000000000 --- a/UI/Popup/StealViz.ts +++ /dev/null @@ -1,70 +0,0 @@ -import LayerConfig from "../../Models/ThemeConfig/LayerConfig" -import TagRenderingConfig from "../../Models/ThemeConfig/TagRenderingConfig" -import { VariableUiElement } from "../Base/VariableUIElement" -import BaseUIElement from "../BaseUIElement" -import EditableTagRendering from "./EditableTagRendering" -import Combine from "../Base/Combine" -import { SpecialVisualization, SpecialVisualizationState } from "../SpecialVisualization" - -export class StealViz implements SpecialVisualization { - funcName = "steal" - docs = "Shows a tagRendering from a different object as if this was the object itself" - args = [ - { - name: "featureId", - doc: "The key of the attribute which contains the id of the feature from which to use the tags", - required: true, - }, - { - name: "tagRenderingId", - doc: "The layer-id and tagRenderingId to render. Can be multiple value if ';'-separated (in which case every value must also contain the layerId, e.g. `layerId.tagRendering0; layerId.tagRendering1`). Note: this can cause layer injection", - required: true, - }, - ] - constr(state: SpecialVisualizationState, featureTags, args) { - const [featureIdKey, layerAndtagRenderingIds] = args - const tagRenderings: [LayerConfig, TagRenderingConfig][] = [] - for (const layerAndTagRenderingId of layerAndtagRenderingIds.split(";")) { - const [layerId, tagRenderingId] = layerAndTagRenderingId.trim().split(".") - const layer = state.layout.layers.find((l) => l.id === layerId) - const tagRendering = layer.tagRenderings.find((tr) => tr.id === tagRenderingId) - tagRenderings.push([layer, tagRendering]) - } - if (tagRenderings.length === 0) { - throw "Could not create stolen tagrenddering: tagRenderings not found" - } - return new VariableUiElement( - featureTags.map((tags) => { - const featureId = tags[featureIdKey] - if (featureId === undefined) { - return undefined - } - const otherTags = state.featureProperties.getStore(featureId) - const elements: BaseUIElement[] = [] - for (const [layer, tagRendering] of tagRenderings) { - const el = new EditableTagRendering( - otherTags, - tagRendering, - layer.units, - state, - {} - ) - elements.push(el) - } - if (elements.length === 1) { - return elements[0] - } - return new Combine(elements).SetClass("flex flex-col") - }) - ) - } - - getLayerDependencies(args): string[] { - const [_, tagRenderingId] = args - if (tagRenderingId.indexOf(".") < 0) { - throw "Error: argument 'layerId.tagRenderingId' of special visualisation 'steal' should contain a dot" - } - const [layerId, __] = tagRenderingId.split(".") - return [layerId] - } -} diff --git a/UI/SpecialVisualization.ts b/UI/SpecialVisualization.ts index a08efe696..a43e382fe 100644 --- a/UI/SpecialVisualization.ts +++ b/UI/SpecialVisualization.ts @@ -1,12 +1,7 @@ import { Store, UIEventSource } from "../Logic/UIEventSource" import BaseUIElement from "./BaseUIElement" -import { DefaultGuiState } from "./DefaultGuiState" import LayoutConfig from "../Models/ThemeConfig/LayoutConfig" -import { - FeatureSource, - IndexedFeatureSource, - WritableFeatureSource, -} from "../Logic/FeatureSource/FeatureSource" +import { IndexedFeatureSource, WritableFeatureSource } from "../Logic/FeatureSource/FeatureSource" import { OsmConnection } from "../Logic/Osm/OsmConnection" import { Changes } from "../Logic/Osm/Changes" import { MapProperties } from "../Models/MapProperties" @@ -17,7 +12,6 @@ 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 SimpleFeatureSource from "../Logic/FeatureSource/Sources/SimpleFeatureSource" import { MenuState } from "../Models/MenuState" /** diff --git a/UI/SpecialVisualizations.ts b/UI/SpecialVisualizations.ts index 77ac68c26..4d83b5870 100644 --- a/UI/SpecialVisualizations.ts +++ b/UI/SpecialVisualizations.ts @@ -9,7 +9,6 @@ import { SpecialVisualizationState, } from "./SpecialVisualization" import { HistogramViz } from "./Popup/HistogramViz" -import { StealViz } from "./Popup/StealViz" import { MinimapViz } from "./Popup/MinimapViz" import { ShareLinkViz } from "./Popup/ShareLinkViz" import { UploadToOsmViz } from "./Popup/UploadToOsmViz" @@ -20,7 +19,6 @@ import { PlantNetDetectionViz } from "./Popup/PlantNetDetectionViz" import { ConflateButton, ImportPointButton, ImportWayButton } from "./Popup/ImportButton" import TagApplyButton from "./Popup/TagApplyButton" import { CloseNoteButton } from "./Popup/CloseNoteButton" -import { NearbyImageVis } from "./Popup/NearbyImageVis" import { MapillaryLinkVis } from "./Popup/MapillaryLinkVis" import { Stores, UIEventSource } from "../Logic/UIEventSource" import AllTagsPanel from "./Popup/AllTagsPanel.svelte" @@ -63,7 +61,218 @@ 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 EditableTagRendering from "./Popup/EditableTagRendering" +import NearbyImages, { + NearbyImageOptions, + P4CPicture, + SelectOneNearbyImage, +} from "./Popup/NearbyImages" +import { Tag } from "../Logic/Tags/Tag" +import ChangeTagAction from "../Logic/Osm/Actions/ChangeTagAction" +import { And } from "../Logic/Tags/And" +import { SaveButton } from "./Popup/SaveButton" +import Lazy from "./Base/Lazy" +import { CheckBox } from "./Input/Checkboxes" +import Slider from "./Input/Slider" +class NearbyImageVis implements SpecialVisualization { + // Class must be in SpecialVisualisations due to weird cyclical import that breaks the tests + args: { name: string; defaultValue?: string; doc: string; required?: boolean }[] = [ + { + name: "mode", + defaultValue: "expandable", + doc: "Indicates how this component is initialized. Options are: \n\n- `open`: always show and load the pictures\n- `collapsable`: show the pictures, but a user can collapse them\n- `expandable`: shown by default; but a user can collapse them.", + }, + { + name: "mapillary", + defaultValue: "true", + doc: "If 'true', includes a link to mapillary on this location.", + }, + ] + docs = + "A component showing nearby images loaded from various online services such as Mapillary. In edit mode and when used on a feature, the user can select an image to add to the feature" + funcName = "nearby_images" + + constr( + state: SpecialVisualizationState, + tagSource: UIEventSource>, + args: string[], + feature: Feature, + layer: LayerConfig + ): BaseUIElement { + const t = Translations.t.image.nearbyPictures + const mode: "open" | "expandable" | "collapsable" = args[0] + const [lon, lat] = GeoOperations.centerpointCoordinates(feature) + const id: string = tagSource.data["id"] + const canBeEdited: boolean = !!id?.match("(node|way|relation)/-?[0-9]+") + const selectedImage = new UIEventSource(undefined) + + let saveButton: BaseUIElement = undefined + if (canBeEdited) { + const confirmText: BaseUIElement = new SubstitutedTranslation( + t.confirm, + tagSource, + state + ) + + const onSave = async () => { + console.log("Selected a picture...", selectedImage.data) + const osmTags = selectedImage.data.osmTags + const tags: Tag[] = [] + for (const key in osmTags) { + tags.push(new Tag(key, osmTags[key])) + } + await state?.changes?.applyAction( + new ChangeTagAction(id, new And(tags), tagSource.data, { + theme: state?.layout.id, + changeType: "link-image", + }) + ) + } + saveButton = new SaveButton( + selectedImage, + state.osmConnection, + confirmText, + t.noImageSelected + ) + .onClick(onSave) + .SetClass("flex justify-end") + } + + const nearby = new Lazy(() => { + const towardsCenter = new CheckBox(t.onlyTowards, false) + + const maxSearchRadius = 100 + const stepSize = 10 + const defaultValue = Math.floor(maxSearchRadius / (2 * stepSize)) * stepSize + const fromOsmPreferences = state?.osmConnection + ?.GetPreference("nearby-images-radius", "" + defaultValue) + .sync( + (s) => Number(s), + [], + (i) => "" + i + ) + const radiusValue = new UIEventSource(fromOsmPreferences.data) + radiusValue.addCallbackAndRunD((v) => fromOsmPreferences.setData(v)) + + const radius = new Slider(stepSize, maxSearchRadius, { + value: radiusValue, + step: 10, + }) + const alreadyInTheImage = AllImageProviders.LoadImagesFor(tagSource) + const options: NearbyImageOptions & { value } = { + lon, + lat, + searchRadius: maxSearchRadius, + shownRadius: radius.GetValue(), + value: selectedImage, + blacklist: alreadyInTheImage, + towardscenter: towardsCenter.GetValue(), + maxDaysOld: 365 * 3, + } + const slideshow = canBeEdited + ? new SelectOneNearbyImage(options, state.indexedFeatures) + : new NearbyImages(options, state.indexedFeatures) + const controls = new Combine([ + towardsCenter, + new Combine([ + new VariableUiElement( + radius.GetValue().map((radius) => t.withinRadius.Subs({ radius })) + ), + radius, + ]).SetClass("flex justify-between"), + ]).SetClass("flex flex-col") + return new Combine([ + slideshow, + controls, + saveButton, + new MapillaryLinkVis().constr(state, tagSource, [], feature).SetClass("mt-6"), + ]) + }) + + let withEdit: BaseUIElement = nearby + if (canBeEdited) { + withEdit = new Combine([t.hasMatchingPicture, nearby]).SetClass("flex flex-col") + } + + if (mode === "open") { + return withEdit + } + const toggleState = new UIEventSource(mode === "collapsable") + return new Toggle( + new Combine([new Title(t.title), withEdit]), + new Title(t.browseNearby).onClick(() => toggleState.setData(true)), + toggleState + ) + } +} + +class StealViz implements SpecialVisualization { + // Class must be in SpecialVisualisations due to weird cyclical import that breaks the tests + + funcName = "steal" + docs = "Shows a tagRendering from a different object as if this was the object itself" + args = [ + { + name: "featureId", + doc: "The key of the attribute which contains the id of the feature from which to use the tags", + required: true, + }, + { + name: "tagRenderingId", + doc: "The layer-id and tagRenderingId to render. Can be multiple value if ';'-separated (in which case every value must also contain the layerId, e.g. `layerId.tagRendering0; layerId.tagRendering1`). Note: this can cause layer injection", + required: true, + }, + ] + constr(state: SpecialVisualizationState, featureTags, args) { + const [featureIdKey, layerAndtagRenderingIds] = args + const tagRenderings: [LayerConfig, TagRenderingConfig][] = [] + for (const layerAndTagRenderingId of layerAndtagRenderingIds.split(";")) { + const [layerId, tagRenderingId] = layerAndTagRenderingId.trim().split(".") + const layer = state.layout.layers.find((l) => l.id === layerId) + const tagRendering = layer.tagRenderings.find((tr) => tr.id === tagRenderingId) + tagRenderings.push([layer, tagRendering]) + } + if (tagRenderings.length === 0) { + throw "Could not create stolen tagrenddering: tagRenderings not found" + } + return new VariableUiElement( + featureTags.map((tags) => { + const featureId = tags[featureIdKey] + if (featureId === undefined) { + return undefined + } + const otherTags = state.featureProperties.getStore(featureId) + const elements: BaseUIElement[] = [] + for (const [layer, tagRendering] of tagRenderings) { + const el = new EditableTagRendering( + otherTags, + tagRendering, + layer.units, + state, + {} + ) + elements.push(el) + } + if (elements.length === 1) { + return elements[0] + } + return new Combine(elements).SetClass("flex flex-col") + }) + ) + } + + getLayerDependencies(args): string[] { + const [_, tagRenderingId] = args + if (tagRenderingId.indexOf(".") < 0) { + throw "Error: argument 'layerId.tagRenderingId' of special visualisation 'steal' should contain a dot" + } + const [layerId, __] = tagRenderingId.split(".") + return [layerId] + } +} export default class SpecialVisualizations { public static specialVisualizations: SpecialVisualization[] = SpecialVisualizations.initList() @@ -987,8 +1196,7 @@ export default class SpecialVisualizations { constr( state: SpecialVisualizationState, tagSource: UIEventSource>, - args: string[], - feature: Feature + args: string[] ): BaseUIElement { const [text, href] = args return new VariableUiElement(