diff --git a/Customizations/JSON/LayerConfig.ts b/Customizations/JSON/LayerConfig.ts index edcb072..511744f 100644 --- a/Customizations/JSON/LayerConfig.ts +++ b/Customizations/JSON/LayerConfig.ts @@ -24,6 +24,7 @@ export default class LayerConfig { static WAYHANDLING_DEFAULT = 0; static WAYHANDLING_CENTER_ONLY = 1; static WAYHANDLING_CENTER_AND_WAY = 2; + id: string; name: Translation description: Translation; @@ -31,6 +32,7 @@ export default class LayerConfig { calculatedTags: [string, string][] doNotDownload: boolean; passAllFeatures: boolean; + isShown: TagRenderingConfig; minzoom: number; maxzoom: number; title?: TagRenderingConfig; @@ -205,6 +207,7 @@ export default class LayerConfig { throw "Builtin SVG asset not found: " + iconPath } } + this.isShown = tr("isShown", "yes"); this.iconSize = tr("iconSize", "40,40,center"); this.color = tr("color", "#0000ff"); this.width = tr("width", "7"); diff --git a/Customizations/JSON/LayerConfigJson.ts b/Customizations/JSON/LayerConfigJson.ts index d009038..8f236b8 100644 --- a/Customizations/JSON/LayerConfigJson.ts +++ b/Customizations/JSON/LayerConfigJson.ts @@ -1,5 +1,6 @@ import {TagRenderingConfigJson} from "./TagRenderingConfigJson"; import {AndOrTagConfigJson} from "./TagConfigJson"; +import TagRenderingConfig from "./TagRenderingConfig"; /** * Configuration for a single layer @@ -51,7 +52,15 @@ export interface LayerConfigJson { * Works well together with 'passAllFeatures', to add decoration */ doNotDownload?: boolean; - + + /** + * This tagrendering should either be 'yes' or 'no'. If 'no' is returned, then the feature will be hidden from view. + * This is useful to hide certain features from view + * The default value is 'yes' + */ + isShown?: TagRenderingConfigJson; + + /** * The zoomlevel at which point the data is shown and loaded. * Default: 0 diff --git a/InitUiElements.ts b/InitUiElements.ts index cf049a4..3036984 100644 --- a/InitUiElements.ts +++ b/InitUiElements.ts @@ -384,26 +384,7 @@ export class InitUiElements { State.state.layerUpdater = updater; const source = new FeaturePipeline(state.filteredLayers, updater, state.layoutToUse, state.changes, state.locationControl); - - source.features.addCallbackAndRun((featuresFreshness: { feature: any, freshness: Date }[]) => { - if (featuresFreshness === undefined) { - return; - } - featuresFreshness.forEach(featureFresh => { - const feature = featureFresh.feature; - State.state.allElements.addOrGetElement(feature); - - if (Hash.hash.data === feature.properties.id) { - State.state.selectedElement.setData(feature); - } - - }) - - MetaTagging.addMetatags(featuresFreshness, state.layoutToUse.data.layers); - }) - - new ShowDataLayer(source.features, State.state.leafletMap, - State.state.layoutToUse); + new ShowDataLayer(source.features, State.state.leafletMap, State.state.layoutToUse); new SelectedFeatureHandler(Hash.hash, State.state.selectedElement, source); diff --git a/Logic/Actors/TitleHandler.ts b/Logic/Actors/TitleHandler.ts index 83ae076..93bb4bb 100644 --- a/Logic/Actors/TitleHandler.ts +++ b/Logic/Actors/TitleHandler.ts @@ -43,7 +43,7 @@ class TitleElement extends UIElement { } if (layer.source.osmTags.matchesProperties(properties)) { const title = new TagRenderingAnswer( - this._allElementsStorage.getEventSourceFor(feature), + this._allElementsStorage.addOrGetElement(feature), layer.title ) return new Combine([defaultTitle, " | ", title]).Render(); diff --git a/Logic/ElementStorage.ts b/Logic/ElementStorage.ts index d300ec0..d301d79 100644 --- a/Logic/ElementStorage.ts +++ b/Logic/ElementStorage.ts @@ -15,30 +15,42 @@ export class ElementStorage { this._elements[id] = eventSource; } - addElement(element): UIEventSource { - const eventSource = new UIEventSource(element.properties, "tags of "+element.properties.id); - this._elements[element.properties.id] = eventSource; - return eventSource; - } - - addOrGetElement(element: any) : UIEventSource{ - const elementId = element.properties.id; + /** + * Creates a UIEventSource for the tags of the given feature. + * If an UIEventsource has been created previously, the same UIEventSource will be returned + * + * Note: it will cleverly merge the tags, if needed + */ + addOrGetElement(feature: any): UIEventSource { + const elementId = feature.properties.id; if (elementId in this._elements) { const es = this._elements[elementId]; + if (es.data == feature.properties) { + // Reference comparison gives the same object! we can just return the event source + return es; + } + + const keptKeys = es.data; // The element already exists // We add all the new keys to the old keys - for (const k in element.properties) { - const v = element.properties[k]; + let somethingChanged = false; + for (const k in feature.properties) { + const v = feature.properties[k]; if (keptKeys[k] !== v) { keptKeys[k] = v; - es.ping(); + somethingChanged = true; } } + if (somethingChanged) { + es.ping(); + } return es; - }else{ - return this.addElement(element); + } else { + const eventSource = new UIEventSource(feature.properties, "tags of " + feature.properties.id); + this._elements[feature.properties.id] = eventSource; + return eventSource; } } @@ -48,8 +60,4 @@ export class ElementStorage { } console.error("Can not find eventsource with id ", elementId); } - - getEventSourceFor(feature): UIEventSource { - return this.getEventSourceById(feature.properties.id); - } } \ No newline at end of file diff --git a/Logic/ExtraFunction.ts b/Logic/ExtraFunction.ts new file mode 100644 index 0000000..deb66bb --- /dev/null +++ b/Logic/ExtraFunction.ts @@ -0,0 +1,81 @@ +import {GeoOperations} from "./GeoOperations"; +import {UIElement} from "../UI/UIElement"; +import Combine from "../UI/Base/Combine"; + +export class ExtraFunction { + + + private static DistanceToFunc = new ExtraFunction( + "distanceTo", + "Calculates the distance between the feature and a specified point", + ["longitude", "latitude"], + (feature) => { + return (lon, lat) => { + // Feature._lon and ._lat is conveniently place by one of the other metatags + return GeoOperations.distanceBetween([lon, lat], [feature._lon, feature._lat]); + } + } + ) + private static readonly allFuncs: ExtraFunction[] = [ExtraFunction.DistanceToFunc]; + private readonly _name: string; + private readonly _args: string[]; + private readonly _doc: string; + private readonly _f: (feat: any) => any; + + constructor(name: string, doc: string, args: string[], f: ((feat: any) => any)) { + this._name = name; + this._doc = doc; + this._args = args; + this._f = f; + + } + + public static FullPatchFeature(feature) { + for (const func of ExtraFunction.allFuncs) { + func.PatchFeature(feature); + } + } + + public static HelpText(): UIElement { + return new Combine([ + ExtraFunction.intro, + ...ExtraFunction.allFuncs.map(func => + new Combine([ + "

" + func._name + "

", + func._doc, + "
    ", + ...func._args.map(arg => "
  • " + arg + "
  • "), + "
" + ]) + ) + ]); + } + + public PatchFeature(feature: any) { + feature[this._name] = this._f(feature); + } + + static readonly intro = `

Calculating tags with Javascript

+ +

In some cases, it is useful to have some tags calculated based on other properties. Some useful tags are available by default (e.g. _lat, lon, _country), as detailed above.

+ +

It is also possible to calculate your own tags - but this requires some javascript knowledge.

+ +Before proceeding, some warnings: + +
    +
  • DO NOT DO THIS AS BEGINNER
  • +
  • Only do this if all other techniques fail. This should not be done to create a rendering effect, only to calculate a specific value
  • +
  • THIS MIGHT BE DISABLED WITHOUT ANY NOTICE ON UNOFFICIAL THEMES. As unofficial themes might be loaded from the internet, this is the equivalent of injecting arbitrary code into the client. It'll be disabled if abuse occurs.
  • +
+In the layer object, add a field calculatedTags, e.g.: + +
+ "calculatedTags": { + "_someKey": "javascript-expression", + "name": "feat.properties.name ?? feat.properties.ref ?? feat.properties.operator", + "_distanceCloserThen3Km": "feat.distanceTo( some_lon, some_lat) < 3 ? 'yes' : 'no'" + } +
+` +} \ No newline at end of file diff --git a/Logic/FeatureSource/FeatureDuplicatorPerLayer.ts b/Logic/FeatureSource/FeatureDuplicatorPerLayer.ts index f69d817..a58534c 100644 --- a/Logic/FeatureSource/FeatureDuplicatorPerLayer.ts +++ b/Logic/FeatureSource/FeatureDuplicatorPerLayer.ts @@ -6,7 +6,7 @@ import LayerConfig from "../../Customizations/JSON/LayerConfig"; /** * In some rare cases, some elements are shown on multiple layers (when 'passthrough' is enabled) * If this is the case, multiple objects with a different _matching_layer_id are generated. - * If not, the _feature_layter_id is added + * In any case, this featureSource marks the objects with _matching_layer_id */ export default class FeatureDuplicatorPerLayer implements FeatureSource { public readonly features: UIEventSource<{ feature: any; freshness: Date }[]>; diff --git a/Logic/FeatureSource/FeaturePipeline.ts b/Logic/FeatureSource/FeaturePipeline.ts index 688f4fc..fb8d21f 100644 --- a/Logic/FeatureSource/FeaturePipeline.ts +++ b/Logic/FeatureSource/FeaturePipeline.ts @@ -12,6 +12,7 @@ import LocalStorageSource from "./LocalStorageSource"; import LayoutConfig from "../../Customizations/JSON/LayoutConfig"; import Loc from "../../Models/Loc"; import GeoJsonSource from "./GeoJsonSource"; +import MetaTaggingFeatureSource from "./MetaTaggingFeatureSource"; export default class FeaturePipeline implements FeatureSource { @@ -25,42 +26,42 @@ export default class FeaturePipeline implements FeatureSource { const amendedOverpassSource = new RememberingSource( - new WayHandlingApplyingFeatureSource(flayers, - new NoOverlapSource(flayers, new FeatureDuplicatorPerLayer(flayers, - new LocalStorageSaver(updater, layout))) - ) + new NoOverlapSource(flayers, new FeatureDuplicatorPerLayer(flayers, + new LocalStorageSaver(updater, layout))) ); const geojsonSources: GeoJsonSource [] = [] for (const flayer of flayers.data) { const sourceUrl = flayer.layerDef.source.geojsonSource if (sourceUrl !== undefined) { - geojsonSources.push(new WayHandlingApplyingFeatureSource(flayers, - new GeoJsonSource(flayer.layerDef.id, sourceUrl))) + geojsonSources.push( + new GeoJsonSource(flayer.layerDef.id, sourceUrl)) } } const amendedLocalStorageSource = new RememberingSource( - new WayHandlingApplyingFeatureSource(flayers, - new NoOverlapSource(flayers, new FeatureDuplicatorPerLayer(flayers, new LocalStorageSource(layout))) - )); + new NoOverlapSource(flayers, new FeatureDuplicatorPerLayer(flayers, new LocalStorageSource(layout))) + ); newPoints = new FeatureDuplicatorPerLayer(flayers, newPoints); - const merged = new FeatureSourceMerger([ - amendedOverpassSource, - amendedLocalStorageSource, - newPoints, - ...geojsonSources - ]); + const merged = + new MetaTaggingFeatureSource( + new FeatureSourceMerger([ + amendedOverpassSource, + amendedLocalStorageSource, + newPoints, + ...geojsonSources + ])); const source = - new FilteringFeatureSource( - flayers, - locationControl, - merged - ); + new WayHandlingApplyingFeatureSource(flayers, + new FilteringFeatureSource( + flayers, + locationControl, + merged + )); this.features = source.features; } diff --git a/Logic/FeatureSource/FilteringFeatureSource.ts b/Logic/FeatureSource/FilteringFeatureSource.ts index b5f6a37..2ac54ef 100644 --- a/Logic/FeatureSource/FilteringFeatureSource.ts +++ b/Logic/FeatureSource/FilteringFeatureSource.ts @@ -29,6 +29,7 @@ export default class FilteringFeatureSource implements FeatureSource { const newFeatures = features.filter(f => { const layerId = f.feature._matching_layer_id; + if (layerId !== undefined) { const layer: { isDisplayed: UIEventSource, @@ -38,6 +39,17 @@ export default class FilteringFeatureSource implements FeatureSource { console.error("No layer found with id ", layerId); return true; } + + const isShown = layer.layerDef.isShown + const tags = f.feature.properties; + console.log("Is shown: ", isShown," known? ", isShown.IsKnown(tags), " result: ", isShown.GetRenderValue(tags).txt) + if(isShown.IsKnown(tags)){ + const result = layer.layerDef.isShown.GetRenderValue(f.feature.properties).txt; + if(result !== "yes"){ + return false; + } + } + if (FilteringFeatureSource.showLayer(layer, location)) { return true; } diff --git a/Logic/FeatureSource/MetaTaggingFeatureSource.ts b/Logic/FeatureSource/MetaTaggingFeatureSource.ts new file mode 100644 index 0000000..fd24722 --- /dev/null +++ b/Logic/FeatureSource/MetaTaggingFeatureSource.ts @@ -0,0 +1,30 @@ +import FeatureSource from "./FeatureSource"; +import {UIEventSource} from "../UIEventSource"; +import State from "../../State"; +import Hash from "../Web/Hash"; +import MetaTagging from "../MetaTagging"; + +export default class MetaTaggingFeatureSource implements FeatureSource { + features: UIEventSource<{ feature: any; freshness: Date }[]> = new UIEventSource<{feature: any; freshness: Date}[]>(undefined); + + constructor(source: FeatureSource) { + const self = this; + source.features.addCallbackAndRun((featuresFreshness: { feature: any, freshness: Date }[]) => { + if (featuresFreshness === undefined) { + return; + } + featuresFreshness.forEach(featureFresh => { + const feature = featureFresh.feature; + State.state.allElements.addOrGetElement(feature); + + if (Hash.hash.data === feature.properties.id) { + State.state.selectedElement.setData(feature); + } + }) + + MetaTagging.addMetatags(featuresFreshness, State.state.layoutToUse.data.layers); + self.features.setData(featuresFreshness); + }); + } + +} \ No newline at end of file diff --git a/Logic/FeatureSource/WayHandlingApplyingFeatureSource.ts b/Logic/FeatureSource/WayHandlingApplyingFeatureSource.ts index 97f340d..95b9ede 100644 --- a/Logic/FeatureSource/WayHandlingApplyingFeatureSource.ts +++ b/Logic/FeatureSource/WayHandlingApplyingFeatureSource.ts @@ -3,6 +3,9 @@ import {UIEventSource} from "../UIEventSource"; import LayerConfig from "../../Customizations/JSON/LayerConfig"; import {GeoOperations} from "../GeoOperations"; +/** + * This is the part of the pipeline which introduces extra points at the center of an area (but only if this is demanded by the wayhandling) + */ export default class WayHandlingApplyingFeatureSource implements FeatureSource { features: UIEventSource<{ feature: any; freshness: Date }[]>; @@ -46,6 +49,7 @@ export default class WayHandlingApplyingFeatureSource implements FeatureSource { continue; } + // Create the copy const centerPoint = GeoOperations.centerpoint(feat); centerPoint._matching_layer_id = feat._matching_layer_id; newFeatures.push({feature: centerPoint, freshness: f.freshness}); diff --git a/Logic/MetaTagging.ts b/Logic/MetaTagging.ts index 2249471..46cf5bb 100644 --- a/Logic/MetaTagging.ts +++ b/Logic/MetaTagging.ts @@ -1,85 +1,6 @@ -import {GeoOperations} from "./GeoOperations"; import LayerConfig from "../Customizations/JSON/LayerConfig"; import SimpleMetaTagger from "./SimpleMetaTagger"; -import {UIElement} from "../UI/UIElement"; -import Combine from "../UI/Base/Combine"; - -export class ExtraFunction { - - static readonly intro = `

Calculating tags with Javascript

- -

In some cases, it is useful to have some tags calculated based on other properties. Some useful tags are available by default (e.g. _lat, lon, _country), as detailed above.

- -

It is also possible to calculate your own tags - but this requires some javascript knowledge.

- -Before proceeding, some warnings: - -
    -
  • DO NOT DO THIS AS BEGINNER
  • -
  • Only do this if all other techniques fail. This should not be done to create a rendering effect, only to calculate a specific value
  • -
  • THIS MIGHT BE DISABLED WITHOUT ANY NOTICE ON UNOFFICIAL THEMES. As unofficial themes might be loaded from the internet, this is the equivalent of injecting arbitrary code into the client. It'll be disabled if abuse occurs.
  • -
-In the layer object, add a field calculatedTags, e.g.: - -
- "calculatedTags": { - "_someKey": "javascript-expression", - "name": "feat.properties.name ?? feat.properties.ref ?? feat.properties.operator", - "_distanceCloserThen3Km": "feat.distanceTo( some_lon, some_lat) < 3 ? 'yes' : 'no'" - } -
-` - - private static DistanceToFunc = new ExtraFunction( - "distanceTo", - "Calculates the distance between the feature and a specified point", - ["longitude", "latitude"], - (feature) => { - return (lon, lat) => { - // Feature._lon and ._lat is conveniently place by one of the other metatags - return GeoOperations.distanceBetween([lon, lat], [feature._lon, feature._lat]); - } - } - ) - private static readonly allFuncs: ExtraFunction[] = [ExtraFunction.DistanceToFunc]; - private readonly _name: string; - private readonly _args: string[]; - private readonly _doc: string; - private readonly _f: (feat: any) => any; - - constructor(name: string, doc: string, args: string[], f: ((feat: any) => any)) { - this._name = name; - this._doc = doc; - this._args = args; - this._f = f; - - } - - public static FullPatchFeature(feature) { - for (const func of ExtraFunction.allFuncs) { - func.PatchFeature(feature); - } - } - - public static HelpText(): UIElement { - return new Combine([ - ExtraFunction.intro, - ...ExtraFunction.allFuncs.map(func => - new Combine([ - "

" + func._name + "

", - func._doc, - "
    ", - ...func._args.map(arg => "
  • " + arg + "
  • "), - "
" - ]) - ) - ]); - } - - public PatchFeature(feature: any) { - feature[this._name] = this._f(feature); - } -} +import {ExtraFunction} from "./ExtraFunction"; /** * Metatagging adds various tags to the elements, e.g. lat, lon, surface area, ... @@ -163,4 +84,6 @@ export default class MetaTagging { } } } + + } diff --git a/Logic/Osm/Overpass.ts b/Logic/Osm/Overpass.ts index c597d84..401d96b 100644 --- a/Logic/Osm/Overpass.ts +++ b/Logic/Osm/Overpass.ts @@ -7,30 +7,15 @@ import {TagsFilter} from "../TagsFilter"; * Interfaces overpass to get all the latest data */ export class Overpass { - private _filter: TagsFilter public static testUrl: string = null - private readonly _extraScripts: string[]; + private _filter: TagsFilter + private readonly _extraScripts: string[]; constructor(filter: TagsFilter, extraScripts: string[]) { this._filter = filter this._extraScripts = extraScripts; } - - private buildQuery(bbox: string): string { - const filters = this._filter.asOverpass() - let filter = "" - for (const filterOr of filters) { - filter += 'nwr' + filterOr + ';' - } - for (const extraScript of this._extraScripts){ - filter += '('+extraScript+');'; - } - const query = - '[out:json][timeout:25]' + bbox + ';(' + filter + ');out body;>;out skel qt;' - return "https://overpass-api.de/api/interpreter?data=" + encodeURIComponent(query) - } - queryGeoJson(bounds: Bounds, continuation: ((any, date: Date) => void), onFail: ((reason) => void)): void { let query = this.buildQuery("[bbox:" + bounds.south + "," + bounds.west + "," + bounds.north + "," + bounds.east + "]") @@ -52,11 +37,27 @@ export class Overpass { onFail("Runtime error (timeout)") return; } + // @ts-ignore const geojson = OsmToGeoJson.default(json); console.log("Received geojson", geojson) const osmTime = new Date(json.osm3s.timestamp_osm_base); continuation(geojson, osmTime); + }).fail(onFail) } + + private buildQuery(bbox: string): string { + const filters = this._filter.asOverpass() + let filter = "" + for (const filterOr of filters) { + filter += 'nwr' + filterOr + ';' + } + for (const extraScript of this._extraScripts) { + filter += '(' + extraScript + ');'; + } + const query = + '[out:json][timeout:25]' + bbox + ';(' + filter + ');out body;>;out skel qt;' + return "https://overpass-api.de/api/interpreter?data=" + encodeURIComponent(query) + } } diff --git a/Logic/SimpleMetaTagger.ts b/Logic/SimpleMetaTagger.ts index d5ae1bd..b9f0b30 100644 --- a/Logic/SimpleMetaTagger.ts +++ b/Logic/SimpleMetaTagger.ts @@ -64,7 +64,7 @@ export default class SimpleMetaTagger { SimpleMetaTagger.GetCountryCodeFor(lon, lat, (countries) => { try { feature.properties["_country"] = countries[0].trim().toLowerCase(); - const tagsSource = State.state.allElements.getEventSourceFor(feature); + const tagsSource = State.state.allElements.addOrGetElement(feature); tagsSource.ping(); } catch (e) { console.warn(e) @@ -77,7 +77,7 @@ export default class SimpleMetaTagger { "If 'opening_hours' is present, it will add the current state of the feature (being 'yes' or 'no')", (feature => { - const tagsSource = State.state.allElements.getEventSourceFor(feature); + const tagsSource = State.state.allElements.addOrGetElement(feature); tagsSource.addCallbackAndRun(tags => { if (tags.opening_hours === undefined || tags._country === undefined) { return; diff --git a/UI/ShowDataLayer.ts b/UI/ShowDataLayer.ts index 6014f74..9abc487 100644 --- a/UI/ShowDataLayer.ts +++ b/UI/ShowDataLayer.ts @@ -86,7 +86,7 @@ export default class ShowDataLayer { marker.openPopup(); const popup = marker.getPopup(); - const tags = State.state.allElements.getEventSourceFor(selected); + const tags = State.state.allElements.addOrGetElement(selected); const layer: LayerConfig = this._layerDict[selected._matching_layer_id]; const infoBox = FeatureInfoBox.construct(tags, layer); @@ -105,7 +105,7 @@ export default class ShowDataLayer { private createStyleFor(feature) { - const tagsSource = State.state.allElements.getEventSourceFor(feature); + const tagsSource = State.state.allElements.addOrGetElement(feature); // Every object is tied to exactly one layer const layer = this._layerDict[feature._matching_layer_id]; return layer?.GenerateLeafletStyle(tagsSource, layer._showOnPopup !== undefined); diff --git a/assets/layers/public_bookcases/public_bookcases.json b/assets/layers/public_bookcases/public_bookcases.json index e4dd0af..c159920 100644 --- a/assets/layers/public_bookcases/public_bookcases.json +++ b/assets/layers/public_bookcases/public_bookcases.json @@ -15,9 +15,6 @@ "source": { "osmTags": "amenity=public_bookcase" }, - "calculatedTags": { - "_distanceToPietervdn": "feat.distanceTo(3.704388, 51.05281) < 1 ? 'closeby' : 'faraway'" - }, "minzoom": 12, "wayHandling": 2, "title": { diff --git a/assets/layers/slow_roads/slow_roads.json b/assets/layers/slow_roads/slow_roads.json index 1720e21..1c72ea0 100644 --- a/assets/layers/slow_roads/slow_roads.json +++ b/assets/layers/slow_roads/slow_roads.json @@ -6,13 +6,19 @@ "minzoom": 16, "source": { "osmTags": { - "or": [ - "highway=pedestrian", - "highway=footway", - "highway=path", - "highway=bridleway", - "highway=living_street", - "highway=track" + "and": [ + { + "or": [ + "highway=pedestrian", + "highway=footway", + "highway=path", + "highway=bridleway", + "highway=living_street", + "highway=track" + ] + }, + "access!=no", + "access!=private" ] } }, @@ -51,7 +57,8 @@ "nl": "Woonerf" } }, - {"if": "highway=path", + { + "if": "highway=path", "then": "Klein pad" } ] diff --git a/scripts/generateDocs.ts b/scripts/generateDocs.ts index 4f29928..e371eaa 100644 --- a/scripts/generateDocs.ts +++ b/scripts/generateDocs.ts @@ -4,8 +4,8 @@ import SpecialVisualizations from "../UI/SpecialVisualizations"; import {writeFileSync} from "fs"; import {UIElement} from "../UI/UIElement"; import SimpleMetaTagger from "../Logic/SimpleMetaTagger"; -import {ExtraFunction} from "../Logic/MetaTagging"; import Combine from "../UI/Base/Combine"; +import {ExtraFunction} from "../Logic/ExtraFunction";