diff --git a/Logic/Actors/OverpassFeatureSource.ts b/Logic/Actors/OverpassFeatureSource.ts index 859b34efe..a630d9880 100644 --- a/Logic/Actors/OverpassFeatureSource.ts +++ b/Logic/Actors/OverpassFeatureSource.ts @@ -188,7 +188,7 @@ export default class OverpassFeatureSource implements FeatureSource { if (data === undefined) { return undefined } - data.features.forEach(feature => SimpleMetaTagger.objectMetaInfo.applyMetaTagsOnFeature(feature, date, undefined)); + data.features.forEach(feature => SimpleMetaTagger.objectMetaInfo.applyMetaTagsOnFeature(feature, date, undefined, this.state)); self.features.setData(data.features.map(f => ({feature: f, freshness: date}))); return [bounds, date, layersToDownload]; } catch (e) { diff --git a/Logic/FeatureSource/FeaturePipeline.ts b/Logic/FeatureSource/FeaturePipeline.ts index 6cb3cc110..43099c713 100644 --- a/Logic/FeatureSource/FeaturePipeline.ts +++ b/Logic/FeatureSource/FeaturePipeline.ts @@ -155,7 +155,8 @@ export default class FeaturePipeline { if (id === "current_view") { handlePriviligedFeatureSource(state.currentView) - state.currentView.features.map(ffs => ffs[0]?.feature?.properties?.id).withEqualityStabilized((x,y) => x === y).addCallbackAndRunD(_ => self.applyMetaTags(state.currentView)) + state.currentView.features.map(ffs => ffs[0]?.feature?.properties?.id).withEqualityStabilized((x,y) => x === y) + .addCallbackAndRunD(_ => self.applyMetaTags(state.currentView, state)) continue } @@ -291,7 +292,8 @@ export default class FeaturePipeline { // We don't bother to split them over tiles as it'll contain little features by default, so we simply add them like this perLayerHierarchy.get(perLayer.layer.layerDef.id).registerTile(perLayer) // AT last, we always apply the metatags whenever possible - perLayer.features.addCallbackAndRunD(_ => self.applyMetaTags(perLayer)) + // @ts-ignore + perLayer.features.addCallbackAndRunD(_ => self.applyMetaTags(perLayer, state)) perLayer.features.addCallbackAndRunD(_ => self.newDataLoadedSignal.setData(perLayer)) }, @@ -449,8 +451,11 @@ export default class FeaturePipeline { return updater; } - private applyMetaTags(src: FeatureSourceForLayer) { + private applyMetaTags(src: FeatureSourceForLayer, state: any) { const self = this + if(src === undefined){ + throw "Src is undefined" + } window.setTimeout( () => { const layerDef = src.layer.layerDef; @@ -462,6 +467,7 @@ export default class FeaturePipeline { getFeatureById: (id: string) => self.state.allElements.ContainingFeatures.get(id) }, layerDef, + state, { includeDates: true, // We assume that the non-dated metatags are already set by the cache generator @@ -479,10 +485,10 @@ export default class FeaturePipeline { console.debug("Updating the meta tagging of all tiles as new data got loaded") this.perLayerHierarchy.forEach(hierarchy => { hierarchy.loadedTiles.forEach(tile => { - self.applyMetaTags(tile) + self.applyMetaTags(tile, this.state) }) }) - this.applyMetaTags(this.state.currentView) + this.applyMetaTags(this.state.currentView, this.state) self.metataggingRecalculated.ping() } diff --git a/Logic/MetaTagging.ts b/Logic/MetaTagging.ts index 0203aa561..d1501109c 100644 --- a/Logic/MetaTagging.ts +++ b/Logic/MetaTagging.ts @@ -1,7 +1,6 @@ -import SimpleMetaTaggers from "./SimpleMetaTagger"; +import SimpleMetaTaggers, {SimpleMetaTagger} from "./SimpleMetaTagger"; import {ExtraFuncParams, ExtraFunctions} from "./ExtraFunctions"; import LayerConfig from "../Models/ThemeConfig/LayerConfig"; -import State from "../State"; /** @@ -24,6 +23,7 @@ export default class MetaTagging { public static addMetatags(features: { feature: any; freshness: Date }[], params: ExtraFuncParams, layer: LayerConfig, + state, options?: { includeDates?: true | boolean, includeNonDates?: true | boolean @@ -33,7 +33,7 @@ export default class MetaTagging { return; } - const metatagsToApply: SimpleMetaTaggers[] = [] + const metatagsToApply: SimpleMetaTagger[] = [] for (const metatag of SimpleMetaTaggers.metatags) { if (metatag.includesDates) { if (options.includeDates ?? true) { @@ -47,7 +47,7 @@ export default class MetaTagging { } // The calculated functions - per layer - which add the new keys - const layerFuncs = this.createRetaggingFunc(layer) + const layerFuncs = this.createRetaggingFunc(layer, state) let atLeastOneFeatureChanged = false; @@ -58,24 +58,17 @@ export default class MetaTagging { let somethingChanged = false for (const metatag of metatagsToApply) { try { - // @ts-ignore if (!metatag.keys.some(key => feature.properties[key] === undefined)) { // All keys are already defined, we probably already ran this one continue } - // @ts-ignore if (metatag.isLazy) { somethingChanged = true; - // @ts-ignore - metatag.applyMetaTagsOnFeature(feature, freshness, layer) - + metatag.applyMetaTagsOnFeature(feature, freshness, layer, state) } else { - - - // @ts-ignore - const newValueAdded = metatag.applyMetaTagsOnFeature(feature, freshness, layer) + const newValueAdded = metatag.applyMetaTagsOnFeature(feature, freshness, layer, state) /* Note that the expression: * `somethingChanged = newValueAdded || metatag.applyMetaTagsOnFeature(feature, freshness)` * Is WRONG @@ -86,7 +79,6 @@ export default class MetaTagging { somethingChanged = newValueAdded || somethingChanged } } catch (e) { - // @ts-ignore console.error("Could not calculate metatag for ", metatag.keys.join(","), ":", e, e.stack) } } @@ -101,7 +93,7 @@ export default class MetaTagging { } if (somethingChanged) { - State.state?.allElements?.getEventSourceById(feature.properties.id)?.ping() + state?.allElements?.getEventSourceById(feature.properties.id)?.ping() atLeastOneFeatureChanged = true } } @@ -170,7 +162,7 @@ export default class MetaTagging { private static retaggingFuncCache = new Map void)[]>() - private static createRetaggingFunc(layer: LayerConfig): + private static createRetaggingFunc(layer: LayerConfig, state): ((params: ExtraFuncParams, feature: any) => void) { const calculatedTags: [string, string, boolean][] = layer.calculatedTags; @@ -196,7 +188,7 @@ export default class MetaTagging { for (const f of functions) { f(feature); } - State.state?.allElements?.getEventSourceById(feature.properties.id)?.ping(); + state?.allElements?.getEventSourceById(feature.properties.id)?.ping(); } catch (e) { console.error("Invalid syntax in calculated tags or some other error: ", e) } diff --git a/Logic/SimpleMetaTagger.ts b/Logic/SimpleMetaTagger.ts index 20fa2d20a..9f7edbf47 100644 --- a/Logic/SimpleMetaTagger.ts +++ b/Logic/SimpleMetaTagger.ts @@ -1,5 +1,4 @@ import {GeoOperations} from "./GeoOperations"; -import State from "../State"; import {Utils} from "../Utils"; import opening_hours from "opening_hours"; import Combine from "../UI/Base/Combine"; @@ -15,7 +14,7 @@ export class SimpleMetaTagger { public readonly doc: string; public readonly isLazy: boolean; public readonly includesDates: boolean - public readonly applyMetaTagsOnFeature: (feature: any, freshness: Date, layer: LayerConfig) => boolean; + public readonly applyMetaTagsOnFeature: (feature: any, freshness: Date, layer: LayerConfig, state) => boolean; /*** * A function that adds some extra data to a feature @@ -23,7 +22,7 @@ export class SimpleMetaTagger { * @param f: apply the changes. Returns true if something changed */ constructor(docs: { keys: string[], doc: string, includesDates?: boolean, isLazy?: boolean, cleanupRetagger?: boolean }, - f: ((feature: any, freshness: Date, layer: LayerConfig) => boolean)) { + f: ((feature: any, freshness: Date, layer: LayerConfig, state) => boolean)) { this.keys = docs.keys; this.doc = docs.doc; this.isLazy = docs.isLazy @@ -54,7 +53,7 @@ export class CountryTagger extends SimpleMetaTagger { doc: "The country code of the property (with latlon2country)", includesDates: false }, - ((feature, _) => { + ((feature, _, __, state) => { let centerPoint: any = GeoOperations.centerpoint(feature); const lat = centerPoint.geometry.coordinates[1]; const lon = centerPoint.geometry.coordinates[0]; @@ -66,7 +65,7 @@ export class CountryTagger extends SimpleMetaTagger { const oldCountry = feature.properties["_country"]; feature.properties["_country"] = countries[0].trim().toLowerCase(); if (oldCountry !== feature.properties["_country"]) { - const tagsSource = State.state?.allElements?.getEventSourceById(feature.properties.id); + const tagsSource = state?.allElements?.getEventSourceById(feature.properties.id); tagsSource?.ping(); } } catch (e) { @@ -210,8 +209,8 @@ export default class SimpleMetaTaggers { keys: ["Theme-defined keys"], }, - (feature => { - const units = Utils.NoNull([].concat(...State.state?.layoutToUse?.layers?.map(layer => layer.units ?? []))); + ((feature, _, __, state) => { + const units = Utils.NoNull([].concat(...state?.layoutToUse?.layers?.map(layer => layer.units ?? []))); if (units.length == 0) { return; } @@ -279,7 +278,7 @@ export default class SimpleMetaTaggers { includesDates: true, isLazy: true }, - (feature => { + ((feature, _, __ ,state) => { if (Utils.runningFromConsole) { // We are running from console, thus probably creating a cache // isOpen is irrelevant @@ -292,7 +291,7 @@ export default class SimpleMetaTaggers { get: () => { delete feature.properties._isOpen feature.properties._isOpen = undefined - const tagsSource = State.state.allElements.getEventSourceById(feature.properties.id); + const tagsSource = state.allElements.getEventSourceById(feature.properties.id); tagsSource.addCallbackAndRunD(tags => { if (tags.opening_hours === undefined || tags._country === undefined) { return; diff --git a/Logic/State/ElementsState.ts b/Logic/State/ElementsState.ts index 345ada244..b0df7cdb8 100644 --- a/Logic/State/ElementsState.ts +++ b/Logic/State/ElementsState.ts @@ -11,7 +11,6 @@ import {Utils} from "../../Utils"; import ChangeToElementsActor from "../Actors/ChangeToElementsActor"; import PendingChangesUploader from "../Actors/PendingChangesUploader"; import TitleHandler from "../Actors/TitleHandler"; -import FeatureSource from "../FeatureSource/FeatureSource"; /** * The part of the state keeping track of where the elements, loading them, configuring the feature pipeline etc diff --git a/Logic/State/UserRelatedState.ts b/Logic/State/UserRelatedState.ts index 19d1eba85..cd8a6f1f6 100644 --- a/Logic/State/UserRelatedState.ts +++ b/Logic/State/UserRelatedState.ts @@ -9,9 +9,6 @@ import {Utils} from "../../Utils"; import Locale from "../../UI/i18n/Locale"; import ElementsState from "./ElementsState"; import SelectedElementTagsUpdater from "../Actors/SelectedElementTagsUpdater"; -import StaticFeatureSource from "../FeatureSource/Sources/StaticFeatureSource"; -import FeatureSource from "../FeatureSource/FeatureSource"; -import {Feature} from "@turf/turf"; /** * The part of the state which keeps track of user-related stuff, e.g. the OSM-connection, diff --git a/UI/AutomatonGui.ts b/UI/AutomatonGui.ts new file mode 100644 index 000000000..eb82d7a35 --- /dev/null +++ b/UI/AutomatonGui.ts @@ -0,0 +1,149 @@ +import BaseUIElement from "./BaseUIElement"; +import Combine from "./Base/Combine"; +import Svg from "../Svg"; +import Title from "./Base/Title"; +import Toggle from "./Input/Toggle"; +import {SubtleButton} from "./Base/SubtleButton"; +import LayoutConfig from "../Models/ThemeConfig/LayoutConfig"; +import UserRelatedState from "../Logic/State/UserRelatedState"; +import ValidatedTextField from "./Input/ValidatedTextField"; +import {Utils} from "../Utils"; +import {UIEventSource} from "../Logic/UIEventSource"; +import {VariableUiElement} from "./Base/VariableUIElement"; +import {FixedUiElement} from "./Base/FixedUiElement"; +import {Tiles} from "../Models/TileRange"; +import {LocalStorageSource} from "../Logic/Web/LocalStorageSource"; +import {DropDown} from "./Input/DropDown"; +import {AllKnownLayouts} from "../Customizations/AllKnownLayouts"; +import MinimapImplementation from "./Base/MinimapImplementation"; +import State from "../State"; +import {OsmConnection} from "../Logic/Osm/OsmConnection"; +import FeaturePipelineState from "../Logic/State/FeaturePipelineState"; + +export default class AutomatonGui extends Combine { + + constructor() { + + const osmConnection = new OsmConnection({ + allElements: undefined, + changes: undefined, + layoutName: "automaton", + singlePage: true + }); + + super([ + new Combine([Svg.robot_svg().SetClass("w-24 h-24 p-4 rounded-full subtle-background"), + new Combine([new Title("MapComplete Automaton", 1), + "This page helps to automate certain tasks for a theme. Expert use only." + ]).SetClass("flex flex-col m-4") + ]).SetClass("flex"), + new Toggle( + AutomatonGui.GenerateMainPanel(), + new SubtleButton(Svg.osm_logo_svg(), "Login to get started"), + osmConnection.isLoggedIn + )]) + } + + private static AutomationPanel(layoutToUse: LayoutConfig, tiles: UIEventSource): BaseUIElement { + const handledTiles = new UIEventSource(0) + + const state = new FeaturePipelineState(layoutToUse) + + + const nextTile = tiles.map(indices => { + if (indices === undefined) { + return "No tiles loaded - can not automate"; + } + const currentTile = handledTiles.data + const tileIndex = indices[currentTile] + if (tileIndex === undefined) { + return "All done!"; + } + + + return "" + tileIndex + }, [handledTiles]) + + return new Combine([ + new VariableUiElement(handledTiles.map(i => "" + i)), + new VariableUiElement(nextTile) + ]) + } + + private static GenerateMainPanel(): BaseUIElement { + + const themeSelect = new DropDown("Select a theme", + AllKnownLayouts.layoutsList.map(l => ({value: l.id, shown: l.id})) + ) + + LocalStorageSource.Get("automation-theme-id").syncWith(themeSelect.GetValue()) + + + const tilepath = ValidatedTextField.InputForType("url", { + placeholder: "Specifiy the path of the overview", + }) + tilepath.SetClass("w-full") + LocalStorageSource.Get("automation-tile_path").syncWith(tilepath.GetValue(), true) + + const tilesToRunOver = tilepath.GetValue().bind(path => { + if (path === undefined) { + return undefined + } + return UIEventSource.FromPromiseWithErr(Utils.downloadJson(path)) + }) + + const tilesPerIndex = tilesToRunOver.map(tiles => { + if (tiles === undefined || tiles["error"] !== undefined) { + return undefined + } + let indexes = []; + const tilesS = tiles["success"] + const z = Number(tilesS["zoom"]) + for (const key in tilesS) { + if (key === "zoom") { + continue + } + const x = Number(key) + const ys = tilesS[key] + indexes.push(...ys.map(y => Tiles.tile_index(z, x, y))) + } + return indexes + }) + + return new Combine([ + themeSelect, + "Specify the path to a tile overview. This is a hosted .json of the format {x : [y0, y1, y2], x1: [y0, ...]} where x is a string and y are numbers", + tilepath, + new VariableUiElement(tilesToRunOver.map(t => { + if (t === undefined) { + return "No path given or still loading..." + } + if (t["error"] !== undefined) { + return new FixedUiElement("Invalid URL or data: " + t["error"]).SetClass("alert") + } + + return new FixedUiElement("Loaded " + tilesPerIndex.data.length + " tiles to automated over").SetClass("thanks") + })), + new VariableUiElement(themeSelect.GetValue().map(id => AllKnownLayouts.allKnownLayouts.get(id)).map(layoutToUse => { + if (layoutToUse === undefined) { + return new FixedUiElement("Select a valid layout") + } + + return AutomatonGui.AutomationPanel(layoutToUse, tilesPerIndex) + + })) + + + ]).SetClass("flex flex-col") + + + } + +} + + +MinimapImplementation.initialize() + +new AutomatonGui() + .SetClass("block p-4") + .AttachTo("main") diff --git a/UI/Image/DeleteImage.ts b/UI/Image/DeleteImage.ts index eca3dc738..baa75d262 100644 --- a/UI/Image/DeleteImage.ts +++ b/UI/Image/DeleteImage.ts @@ -7,7 +7,6 @@ import Svg from "../../Svg"; import {Tag} from "../../Logic/Tags/Tag"; import ChangeTagAction from "../../Logic/Osm/Actions/ChangeTagAction"; - export default class DeleteImage extends Toggle { constructor(key: string, tags: UIEventSource) { diff --git a/assets/themes/grb_import/missing_streets.json b/assets/themes/grb_import/missing_streets.json index 1537abd31..e25efdeee 100644 --- a/assets/themes/grb_import/missing_streets.json +++ b/assets/themes/grb_import/missing_streets.json @@ -1,13 +1,13 @@ { "id": "missing_streets", "title": { - "nl": "GRB import helper" + "nl": "Fix ontbrekende straten" }, "shortDescription": { - "nl": "Grb import helper tool" + "nl": "Voegt ontbrekende straten toe aan gebouwen met huisnumer adhv CRAB" }, "description": { - "nl": "Dit thema voegt semi-automatisch straatnamen toe aan gebouwen met huisnummer en overeenkomstig CRAB-adres." + "nl": "Dit thema voegt automatisch straatnamen toe aan gebouwen met huisnummer en overeenkomstig CRAB-adres." }, "language": [ "nl" @@ -36,7 +36,7 @@ "point" ], "icon": { - "render": "./assets/themes/grb_import/robot.svg" + "render": "./assets/svg/robot.svg" }, "iconSize": "15,15,center" } diff --git a/css/index-tailwind-output.css b/css/index-tailwind-output.css index c650760df..bcc28e6b2 100644 --- a/css/index-tailwind-output.css +++ b/css/index-tailwind-output.css @@ -1052,6 +1052,10 @@ video { width: 100%; } +.w-24 { + width: 6rem; +} + .w-10 { width: 2.5rem; }