From 16612b10ef9841da0ae59701abf5469d42c52ab0 Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Tue, 17 Nov 2020 02:22:48 +0100 Subject: [PATCH] I should have commited sooner... --- Customizations/JSON/LayerConfig.ts | 88 +++++++++++++++---- Customizations/JSON/LayerConfigJson.ts | 30 ++++++- Customizations/JSON/LayoutConfigJson.ts | 14 ++- Customizations/SharedLayers.ts | 2 + InitUiElements.ts | 69 +++++++++++---- Logic/FilteredLayer.ts | 86 +++++++++++------- Logic/Leaflet/GeoLocationHandler.ts | 18 ++-- Logic/UpdateFromOverpass.ts | 5 ++ Logic/Web/Hash.ts | 18 ++++ Logic/Web/QueryParameters.ts | 3 +- State.ts | 23 ++++- Svg.ts | 7 +- UI/FullScreenMessageBoxHandler.ts | 1 - UI/Input/DirectionInput.ts | 11 ++- UI/Input/ValidatedTextField.ts | 15 +++- UI/Popup/FeatureInfoBox.ts | 3 +- UI/Popup/TagRenderingAnswer.ts | 3 + UI/Popup/TagRenderingQuestion.ts | 3 +- UI/SpecialVisualizations.ts | 8 +- UI/UserBadge.ts | 9 -- Utils.ts | 3 + assets/layers/direction/direction.json | 32 +++++++ assets/svg/direction_gradient.svg | 54 ++++++++++++ .../surveillance_cameras/custom_theme.css | 14 +++ .../surveillance_cameras.json | 88 +++++++++++++++++-- css/mobile.css | 4 - css/tagrendering.css | 5 ++ index.css | 7 +- install.bat | 1 - package.json | 7 +- createLayouts.ts => scripts/createLayouts.ts | 26 +++--- .../generateIncludedImages.ts | 43 +-------- scripts/generateTranslations.ts | 40 +++++++++ test.html | 5 +- test.ts | 2 +- 35 files changed, 570 insertions(+), 177 deletions(-) create mode 100644 Logic/Web/Hash.ts create mode 100644 assets/layers/direction/direction.json create mode 100644 assets/svg/direction_gradient.svg delete mode 100644 install.bat rename createLayouts.ts => scripts/createLayouts.ts (92%) rename generateIncludedImages.ts => scripts/generateIncludedImages.ts (56%) create mode 100644 scripts/generateTranslations.ts diff --git a/Customizations/JSON/LayerConfig.ts b/Customizations/JSON/LayerConfig.ts index 268fa98..533658b 100644 --- a/Customizations/JSON/LayerConfig.ts +++ b/Customizations/JSON/LayerConfig.ts @@ -8,23 +8,33 @@ import {TagRenderingConfigJson} from "./TagRenderingConfigJson"; import {Translation} from "../../UI/i18n/Translation"; import {Img} from "../../UI/Img"; import Svg from "../../Svg"; +import {SubstitutedTranslation} from "../../UI/SpecialVisualizations"; +import {Utils} from "../../Utils"; +import Combine from "../../UI/Base/Combine"; +import {Browser} from "leaflet"; export default class LayerConfig { + + id: string; name: Translation description: Translation; overpassTags: TagsFilter; + doNotDownload: boolean; + + passAllFeatures: boolean; minzoom: number; - title: TagRenderingConfig; + title?: TagRenderingConfig; titleIcons: TagRenderingConfig[]; icon: TagRenderingConfig; iconSize: TagRenderingConfig; + rotation: TagRenderingConfig; color: TagRenderingConfig; width: TagRenderingConfig; dashArray: TagRenderingConfig; @@ -53,10 +63,11 @@ export default class LayerConfig { this.name = Translations.T(json.name); this.description = Translations.T(json.name); this.overpassTags = FromJSON.Tag(json.overpassTags, context + ".overpasstags"); + this.doNotDownload = json.doNotDownload ?? false, + this.passAllFeatures = json.passAllFeatures ?? false; this.minzoom = json.minzoom; this.wayHandling = json.wayHandling ?? 0; this.hideUnderlayingFeaturesMinPercentage = json.hideUnderlayingFeaturesMinPercentage ?? 0; - this.title = new TagRenderingConfig(json.title); this.presets = (json.presets ?? []).map(pr => ({ title: Translations.T(pr.title), @@ -93,7 +104,10 @@ export default class LayerConfig { function tr(key, deflt) { const v = json[key]; - if (v === undefined) { + if (v === undefined || v === null) { + if (deflt === undefined) { + return undefined; + } return new TagRenderingConfig(deflt); } if (typeof v === "string") { @@ -107,11 +121,19 @@ export default class LayerConfig { } - this.title = tr("title", ""); + this.title = tr("title", undefined); this.icon = tr("icon", Img.AsData(Svg.bug)); + const iconPath = this.icon.GetRenderValue({id: "node/-1"}).txt; + if (iconPath.startsWith(Utils.assets_path)) { + const iconKey = iconPath.substr(Utils.assets_path.length); + if (Svg.All[iconKey] === undefined) { + throw "Builtin SVG asset not found: " + iconPath + } + } this.iconSize = tr("iconSize", "40,40,center"); this.color = tr("color", "#0000ff"); this.width = tr("width", "7"); + this.rotation = tr("rotation", "0"); this.dashArray = tr("dashArray", ""); @@ -121,13 +143,16 @@ export default class LayerConfig { public GenerateLeafletStyle(tags: any): { color: string; - icon: { popupAnchor: [number, number]; iconAnchor: [number, number]; iconSize: [number, number]; iconUrl: string }; weight: number; dashArray: number[] + icon: { + iconUrl: string, + popupAnchor: [number, number]; + iconAnchor: [number, number]; + iconSize: [number, number]; + html: string; + rotation: number; + }; + weight: number; dashArray: number[] } { - const iconUrl = this.icon?.GetRenderValue(tags)?.txt; - const iconSize = (this.iconSize?.GetRenderValue(tags)?.txt ?? "40,40,center").split(","); - - - const dashArray = this.dashArray.GetRenderValue(tags)?.txt.split(" ").map(Number); function num(str, deflt = 40) { const n = Number(str); @@ -137,6 +162,33 @@ export default class LayerConfig { return n; } + function rendernum(tr: TagRenderingConfig, deflt: number) { + const str = Number(render(tr, "" + deflt)); + const n = Number(str); + if (isNaN(n)) { + return deflt; + } + return n; + } + + function render(tr: TagRenderingConfig, deflt?: string) { + const str = (tr?.GetRenderValue(tags)?.txt ?? deflt); + return SubstitutedTranslation.SubstituteKeys(str, tags); + } + + const iconUrl = render(this.icon); + const iconSize = render(this.iconSize, "40,40,center").split(","); + const dashArray = render(this.dashArray).split(" ").map(Number); + let color = render(this.color, "#00f"); + + if (color.startsWith("--")) { + color = getComputedStyle(document.body).getPropertyValue("--catch-detail-color") + } + + const weight = rendernum(this.width, 5); + const rotation = rendernum(this.rotation, 0); + + const iconW = num(iconSize[0]); const iconH = num(iconSize[1]); const mode = iconSize[2] ?? "center" @@ -157,16 +209,22 @@ export default class LayerConfig { anchorH = iconH; } - - const color = this.color?.GetRenderValue(tags)?.txt ?? "#00f"; - let weight = num(this.width?.GetRenderValue(tags)?.txt, 5); + let html = ``; + if (iconUrl.startsWith(Utils.assets_path)) { + const key = iconUrl.substr(Utils.assets_path.length); + html = new Combine([ + (Svg.All[key] as string).replace(/stop-color:#000000/g, 'stop-color:' + color) + ]).SetStyle(`width:100%;height:100%;rotate:${rotation}deg;display:block;`).Render(); + } return { icon: { - iconUrl: iconUrl, + html: html, iconSize: [iconW, iconH], iconAnchor: [anchorW, anchorH], - popupAnchor: [0, 3 - anchorH] + popupAnchor: [0, 3 - anchorH], + rotation: rotation, + iconUrl: iconUrl }, color: color, weight: weight, diff --git a/Customizations/JSON/LayerConfigJson.ts b/Customizations/JSON/LayerConfigJson.ts index be70638..b7371c5 100644 --- a/Customizations/JSON/LayerConfigJson.ts +++ b/Customizations/JSON/LayerConfigJson.ts @@ -29,6 +29,12 @@ export interface LayerConfigJson { */ overpassTags: AndOrTagConfigJson | string; + /** + * If set, this layer will not query overpass; but it'll still match the tags above which are by chance returned by other layers. + * Works well together with 'passAllFeatures', to add decoration + */ + doNotDownload?: boolean; + /** * The zoomlevel at which point the data is shown and loaded. */ @@ -39,8 +45,13 @@ export interface LayerConfigJson { /** * The title shown in a popup for elements of this layer. */ - title: string | TagRenderingConfigJson; - + title?: string | TagRenderingConfigJson; + + /** + * Small icons shown next to the title. + * If not specified, the OsmLink and wikipedia links will be used by default. + * Use an empty array to hide them + */ titleIcons?: (string | TagRenderingConfigJson)[]; /** @@ -54,9 +65,14 @@ export interface LayerConfigJson { * Default is '40,40,center' */ iconSize?: string | TagRenderingConfigJson; - /** - * The color for way-elements + * The rotation of an icon, useful for e.g. directions + */ + rotation?: string | TagRenderingConfigJson; + + /** + * The color for way-elements and SVG-elements. + * If the value starts with "--", the style of the body element will be queried for the corresponding variable instead */ color?: string | TagRenderingConfigJson; /** @@ -87,6 +103,11 @@ export interface LayerConfigJson { */ hideUnderlayingFeaturesMinPercentage?:number; + /** + * If set, this layer will pass all the features it receives onto the next layer + */ + passAllFeatures?:boolean + /** * Presets for this layer */ @@ -98,6 +119,7 @@ export interface LayerConfigJson { /** * All the tag renderings. + * A tag rendering is a block that either shows the known value or asks a question. */ tagRenderings?: (string | TagRenderingConfigJson) [] } \ No newline at end of file diff --git a/Customizations/JSON/LayoutConfigJson.ts b/Customizations/JSON/LayoutConfigJson.ts index 95c14c4..fe5451f 100644 --- a/Customizations/JSON/LayoutConfigJson.ts +++ b/Customizations/JSON/LayoutConfigJson.ts @@ -102,7 +102,19 @@ export interface LayoutConfigJson { /** - * The layers to display + * The layers to display. + * + * Every layer contains a description of which feature to display - the overpassTags which are queried. + * Instead of running one query for every layer, the query is fused. + * + * Afterwards, every layer is given the list of features. + * Every layer takes away the features that match with them*, and give the leftovers to the next layers. + * + * This implies that the _order_ of the layers is important in the case of features with the same tags; + * as the later layers might never receive their feature. + * + * *layers can also remove 'leftover'-features if the leftovers overlap with a feature in the layer itself + * */ layers: (LayerConfigJson | string)[], diff --git a/Customizations/SharedLayers.ts b/Customizations/SharedLayers.ts index 34dd9da..202b289 100644 --- a/Customizations/SharedLayers.ts +++ b/Customizations/SharedLayers.ts @@ -13,6 +13,7 @@ import * as bike_shops from "../assets/layers/bike_shop/bike_shop.json" import * as bike_cleaning from "../assets/layers/bike_cleaning/bike_cleaning.json" import * as maps from "../assets/layers/maps/maps.json" import * as information_boards from "../assets/layers/information_board/information_board.json" +import * as direction from "../assets/layers/direction/direction.json" import LayerConfig from "./JSON/LayerConfig"; export default class SharedLayers { @@ -37,6 +38,7 @@ export default class SharedLayers { new LayerConfig(bike_shops, "shared_layers"), new LayerConfig(bike_cleaning, "shared_layers"), new LayerConfig(maps, "shared_layers"), + new LayerConfig(direction, "shared_layers"), new LayerConfig(information_boards, "shared_layers") ]; diff --git a/InitUiElements.ts b/InitUiElements.ts index 20af0c8..e4ac702 100644 --- a/InitUiElements.ts +++ b/InitUiElements.ts @@ -35,6 +35,9 @@ import Svg from "./Svg"; import Link from "./UI/Base/Link"; import * as personal from "./assets/themes/personalLayout/personalLayout.json" import LayoutConfig from "./Customizations/JSON/LayoutConfig"; +import * as L from "leaflet"; +import {Img} from "./UI/Img"; +import {UserDetails} from "./Logic/Osm/OsmConnection"; export class InitUiElements { @@ -142,6 +145,7 @@ export class InitUiElements { } + if (layoutToUse.id === personal.id) { State.state.favouriteLayers.addCallback(updateFavs); State.state.installedThemes.addCallback(updateFavs); @@ -153,6 +157,10 @@ export class InitUiElements { * This is given to the div which renders fullscreen on mobile devices */ State.state.selectedElement.addCallback((feature) => { + + if (feature === undefined) { + State.state.fullScreenMessage.setData(undefined); + } if (feature?.properties === undefined) { return; } @@ -163,17 +171,22 @@ export class InitUiElements { continue; } const applicable = layer.overpassTags.matches(TagUtils.proprtiesToKV(data)); - if (applicable) { - // This layer is the layer that gives the questions - - const featureBox = new FeatureInfoBox( - State.state.allElements.getElement(data.id), - layer - ); - - State.state.fullScreenMessage.setData(featureBox); - break; + if (!applicable) { + continue; } + + if(layer.title === null && layer.tagRenderings.length === 0){ + continue; + } + + // This layer is the layer that gives the questions + const featureBox = new FeatureInfoBox( + State.state.allElements.getElement(data.id), + layer + ); + + State.state.fullScreenMessage.setData(featureBox); + break; } } ); @@ -204,6 +217,21 @@ export class InitUiElements { content.AttachTo("messagesbox"); } + State.state.osmConnection.userDetails.map((userDetails: UserDetails) => userDetails?.home) + .addCallbackAndRun(home => { + if (home === undefined) { + return; + } + const color = getComputedStyle(document.body).getPropertyValue("--subtle-detail-color") + const icon = L.icon({ + iconUrl: Img.AsData(Svg.home_white_bg.replace(/#ffffff/g, color)), + iconSize: [30, 30], + iconAnchor: [15, 15] + }); + const marker = L.marker([home.lat, home.lon], {icon: icon}) + marker.addTo(State.state.bm.map) + console.log(marker) + }); new GeoLocationHandler() .SetStyle(`position:relative;display:block;border: solid 2px #0005;cursor: pointer; z-index: 999; /*Just below leaflets zoom*/background-color: white;border-radius: 5px;width: 43px;height: 43px;`) @@ -327,6 +355,10 @@ export class InitUiElements { checkbox.isEnabled.setData(false); }) + State.state.selectedElement.addCallback(() => { + checkbox.isEnabled.setData(false); + }) + const fullOptions2 = this.CreateWelcomePane(); State.state.fullScreenMessage.setData(fullOptions2) @@ -435,13 +467,15 @@ export class InitUiElements { return new Combine([mapComplete, reportBug, " | ", stats, " | ", editHere, editWithJosm]).Render(); }, [State.state.osmConnection.userDetails]) - ).SetClass("map-attribution") } static InitBaseMap() { const bm = new Basemap("leafletDiv", State.state.locationControl, this.CreateAttribution()); State.state.bm = bm; + bm.map.on("popupclose", () => { + State.state.selectedElement.setData(undefined) + }) State.state.layerUpdater = new UpdateFromOverpass(State.state); State.state.availableBackgroundLayers = new AvailableBaseLayers(State.state).availableEditorLayers; @@ -475,13 +509,12 @@ export class InitUiElements { throw "Layer " + layer + " was not substituted"; } - const flayer: FilteredLayer = new FilteredLayer(layer, - (tagsES) => { - return new FeatureInfoBox( - tagsES, - layer, - ) - }); + let generateContents = (tags: UIEventSource) => new FeatureInfoBox(tags, layer); + if (layer.title === undefined && (layer.tagRenderings ?? []).length === 0) { + generateContents = undefined; + } + + const flayer: FilteredLayer = new FilteredLayer(layer, generateContents); flayers.push(flayer); QueryParameters.GetQueryParameter("layer-" + layer.id, "true", "Wehter or not layer " + layer.id + " is shown") diff --git a/Logic/FilteredLayer.ts b/Logic/FilteredLayer.ts index ac70ae5..bafdc28 100644 --- a/Logic/FilteredLayer.ts +++ b/Logic/FilteredLayer.ts @@ -6,6 +6,7 @@ import {GeoOperations} from "./GeoOperations"; import {UIElement} from "../UI/UIElement"; import State from "../State"; import LayerConfig from "../Customizations/JSON/LayerConfig"; +import Hash from "./Web/Hash"; /*** * A filtered layer is a layer which offers a 'set-data' function @@ -75,11 +76,13 @@ export class FilteredLayer { const selfFeatures = []; for (let feature of geojson.features) { const tags = TagUtils.proprtiesToKV(feature.properties); - if (!this.filters.matches(tags)) { - leftoverFeatures.push(feature); - continue; + const matches = this.filters.matches(tags); + if (matches) { + selfFeatures.push(feature); + } + if (!matches || this.layerDef.passAllFeatures) { + leftoverFeatures.push(feature); } - selfFeatures.push(feature); } this.RenderLayer(selfFeatures) @@ -117,7 +120,6 @@ export class FilteredLayer { // We fetch all the data we have to show: let fusedFeatures = this.ApplyWayHandling(this.FuseData(features)); - console.log("Fused:",fusedFeatures) // And we copy some features as points - if needed const data = { @@ -126,7 +128,6 @@ export class FilteredLayer { } let self = this; - console.log(data); this._geolayer = L.geoJSON(data, { style: feature => self.layerDef.GenerateLeafletStyle(feature.properties), @@ -147,19 +148,21 @@ export class FilteredLayer { color: style.color }); } else { - if (style.icon.iconSize === undefined) { - style.icon.iconSize = [50, 50] - } - marker = L.marker(latLng, { - icon: L.icon(style.icon) + icon: L.divIcon(style.icon) }); } return marker; }, onEachFeature: function (feature, layer: Layer) { - layer.on("click", (e) => { + if (self._showOnPopup === undefined) { + // No popup contents defined -> don't do anything + return; + } + + + function openPopup(latlng: any) { if (layer.getPopup() === undefined && (window.screen.availHeight > 600 || window.screen.availWidth > 600) // We DON'T trigger this code on small screens! No need to create a popup ) { @@ -168,30 +171,47 @@ export class FilteredLayer { closeOnEscapeKey: true, }, layer); - // @ts-ignore - popup.setLatLng(e.latlng) + popup.setLatLng(latlng) + + layer.bindPopup(popup); + const eventSource = State.state.allElements.addOrGetElement(feature); + const uiElement = self._showOnPopup(eventSource, feature); + // We first render the UIelement (which'll still need an update later on...) + // But at least it'll be visible already + popup.setContent(uiElement.Render()); + popup.openOn(State.state.bm.map); + // popup.openOn(State.state.bm.map); + // ANd we perform the pending update + uiElement.Update(); + // @ts-ignore + popup.Update = () => { + uiElement.Update(); + } + } else { + // @ts-ignore + layer.getPopup().Update(); + } + + + // We set the element as selected... + State.state.selectedElement.setData(feature); - layer.bindPopup(popup); - const eventSource = State.state.allElements.addOrGetElement(feature); - const uiElement = self._showOnPopup(eventSource, feature); - // We first render the UIelement (which'll still need an update later on...) - // But at least it'll be visible already - popup.setContent(uiElement.Render()); - popup.openOn(State.state.bm.map); - // popup.openOn(State.state.bm.map); - // ANd we perform the pending update - uiElement.Update(); } - // We set the element as selected... - State.state.selectedElement.setData(feature); - // We mark the event as consumed - L.DomEvent.stop(e); - }); - } - } - ) - ; + layer.on("click", (e) => { + // @ts-ignore + openPopup(e.latlng); + // We mark the event as consumed + L.DomEvent.stop(e); + }); + + if (feature.properties.id.replace(/\//g, "_") === Hash.Get().data) { + const center = GeoOperations.centerpoint(feature).geometry.coordinates; + openPopup({lat: center[1], lng: center[0]}) + } + + } + }); if (this.combinedIsDisplayed.data) { this._geolayer.addTo(State.state.bm.map); diff --git a/Logic/Leaflet/GeoLocationHandler.ts b/Logic/Leaflet/GeoLocationHandler.ts index 5975529..e94b23e 100644 --- a/Logic/Leaflet/GeoLocationHandler.ts +++ b/Logic/Leaflet/GeoLocationHandler.ts @@ -4,7 +4,6 @@ import {UIElement} from "../../UI/UIElement"; import State from "../../State"; import {Utils} from "../../Utils"; import {Basemap} from "./Basemap"; -import {FixedUiElement} from "../../UI/Base/FixedUiElement"; import Svg from "../../Svg"; import {Img} from "../../UI/Img"; @@ -48,15 +47,18 @@ export class GeoLocationHandler extends UIElement { map.on('accuratepositionfound', onAccuratePositionFound); map.on('accuratepositionerror', onAccuratePositionError); -FixedUiElement - const icon = L.icon( - { - iconUrl: Img.AsData(Svg.crosshair_blue), - iconSize: [40, 40], // size of the icon - iconAnchor: [20, 20], // point of the icon which will correspond to marker's location - }) + State.state.currentGPSLocation.addCallback((location) => { + + const color = getComputedStyle(document.body).getPropertyValue("--catch-detail-color") + const icon = L.icon( + { + iconUrl: Img.AsData(Svg.crosshair.replace(/#000000/g, color)), + iconSize: [40, 40], // size of the icon + iconAnchor: [20, 20], // point of the icon which will correspond to marker's location + }) + const newMarker = L.marker(location.latlng, {icon: icon}); newMarker.addTo(map); diff --git a/Logic/UpdateFromOverpass.ts b/Logic/UpdateFromOverpass.ts index 8cd6bbe..bbf83c4 100644 --- a/Logic/UpdateFromOverpass.ts +++ b/Logic/UpdateFromOverpass.ts @@ -62,6 +62,11 @@ export class UpdateFromOverpass { if (state.locationControl.data.zoom < layer.minzoom) { continue; } + if(layer.doNotDownload){ + continue; + } + + // Check if data for this layer has already been loaded let previouslyLoaded = false; for (let z = layer.minzoom; z < 25 && !previouslyLoaded; z++) { diff --git a/Logic/Web/Hash.ts b/Logic/Web/Hash.ts new file mode 100644 index 0000000..6ca2d99 --- /dev/null +++ b/Logic/Web/Hash.ts @@ -0,0 +1,18 @@ +import {UIEventSource} from "../UIEventSource"; + +export default class Hash { + + public static Get() : UIEventSource{ + const hash = new UIEventSource(window.location.hash.substr(1)); + hash.addCallback(h => { + h = h.replace(/\//g, "_"); + return window.location.hash = "#" + h; + }); + window.onhashchange = () => { + hash.setData(window.location.hash.substr(1)) + } + + return hash; + } + +} \ No newline at end of file diff --git a/Logic/Web/QueryParameters.ts b/Logic/Web/QueryParameters.ts index 821c163..dd8456e 100644 --- a/Logic/Web/QueryParameters.ts +++ b/Logic/Web/QueryParameters.ts @@ -2,6 +2,7 @@ * Wraps the query parameters into UIEventSources */ import {UIEventSource} from "../UIEventSource"; +import Hash from "./Hash"; export class QueryParameters { @@ -57,7 +58,7 @@ export class QueryParameters { parts.push(encodeURIComponent(key) + "=" + encodeURIComponent(QueryParameters.knownSources[key].data)) } - history.replaceState(null, "", "?" + parts.join("&")); + history.replaceState(null, "", "?" + parts.join("&") + "#" + Hash.Get().data); } diff --git a/State.ts b/State.ts index f9772fe..a1921d1 100644 --- a/State.ts +++ b/State.ts @@ -12,6 +12,7 @@ import {LocalStorageSource} from "./Logic/Web/LocalStorageSource"; import {QueryParameters} from "./Logic/Web/QueryParameters"; import {BaseLayer} from "./Logic/BaseLayer"; import LayoutConfig from "./Customizations/JSON/LayoutConfig"; +import Hash from "./Logic/Web/Hash"; /** * Contains the global state: a bunch of UI-event sources @@ -21,9 +22,9 @@ export default class State { // The singleton of the global state public static state: State; - - public static vNumber = "0.1.3-rc2+g"; - + + public static vNumber = "0.1.3-rc4"; + // The user journey states thresholds when a new feature gets unlocked public static userJourney = { addNewPointsUnlock: 0, @@ -209,6 +210,22 @@ export default class State { ); + const h = Hash.Get(); + this.selectedElement.addCallback(selected => { + if (selected === undefined) { + h.setData(""); + } else { + h.setData(selected.id) + } + } + ) + h.addCallbackAndRun(hash => { + if(hash === undefined || hash === ""){ + self.selectedElement.setData(undefined); + } + }) + + this.installedThemes = this.osmConnection.preferencesHandler.preferences.map<{ layout: LayoutConfig, definition: string }[]>(allPreferences => { const installedThemes: { layout: LayoutConfig, definition: string }[] = []; if (allPreferences === undefined) { diff --git a/Svg.ts b/Svg.ts index 1907610..e1a6704 100644 --- a/Svg.ts +++ b/Svg.ts @@ -79,6 +79,11 @@ export default class Svg { public static direction_svg() { return new FixedUiElement(Svg.direction);} public static direction_ui() { return new FixedUiElement(Svg.direction_img);} + public static direction_gradient = " image/svg+xml " + public static direction_gradient_img = Img.AsImageElement(Svg.direction_gradient) + public static direction_gradient_svg() { return new FixedUiElement(Svg.direction_gradient);} + public static direction_gradient_ui() { return new FixedUiElement(Svg.direction_gradient_img);} + public static down = " image/svg+xml " public static down_img = Img.AsImageElement(Svg.down) public static down_svg() { return new FixedUiElement(Svg.down);} @@ -214,4 +219,4 @@ export default class Svg { public static wikipedia_svg() { return new FixedUiElement(Svg.wikipedia);} public static wikipedia_ui() { return new FixedUiElement(Svg.wikipedia_img);} -} +public static All = {"add.svg": Svg.add,"addSmall.svg": Svg.addSmall,"ampersand.svg": Svg.ampersand,"arrow-left-smooth.svg": Svg.arrow_left_smooth,"arrow-right-smooth.svg": Svg.arrow_right_smooth,"bug.svg": Svg.bug,"camera-plus.svg": Svg.camera_plus,"checkmark.svg": Svg.checkmark,"close.svg": Svg.close,"compass.svg": Svg.compass,"crosshair-blue-center.svg": Svg.crosshair_blue_center,"crosshair-blue.svg": Svg.crosshair_blue,"crosshair.svg": Svg.crosshair,"delete_icon.svg": Svg.delete_icon,"direction.svg": Svg.direction,"direction_gradient.svg": Svg.direction_gradient,"down.svg": Svg.down,"envelope.svg": Svg.envelope,"floppy.svg": Svg.floppy,"gear.svg": Svg.gear,"help.svg": Svg.help,"home.svg": Svg.home,"home_white_bg.svg": Svg.home_white_bg,"josm_logo.svg": Svg.josm_logo,"layers.svg": Svg.layers,"layersAdd.svg": Svg.layersAdd,"logo.svg": Svg.logo,"logout.svg": Svg.logout,"mapillary.svg": Svg.mapillary,"no_checkmark.svg": Svg.no_checkmark,"or.svg": Svg.or,"osm-logo-us.svg": Svg.osm_logo_us,"osm-logo.svg": Svg.osm_logo,"pencil.svg": Svg.pencil,"pop-out.svg": Svg.pop_out,"reload.svg": Svg.reload,"search.svg": Svg.search,"share.svg": Svg.share,"star.svg": Svg.star,"statistics.svg": Svg.statistics,"up.svg": Svg.up,"wikimedia-commons-white.svg": Svg.wikimedia_commons_white,"wikipedia.svg": Svg.wikipedia};} diff --git a/UI/FullScreenMessageBoxHandler.ts b/UI/FullScreenMessageBoxHandler.ts index d1e9355..70d5d90 100644 --- a/UI/FullScreenMessageBoxHandler.ts +++ b/UI/FullScreenMessageBoxHandler.ts @@ -14,7 +14,6 @@ export class FullScreenMessageBox extends UIElement { constructor(onClear: (() => void)) { super(State.state.fullScreenMessage); this.HideOnEmpty(true); - const self = this; this.returnToTheMap = new Combine([Translations.t.general.returnToTheMap.Clone().SetStyle("font-size:xx-large")]) diff --git a/UI/Input/DirectionInput.ts b/UI/Input/DirectionInput.ts index 7188ad2..3fe8344 100644 --- a/UI/Input/DirectionInput.ts +++ b/UI/Input/DirectionInput.ts @@ -2,6 +2,10 @@ import {InputElement} from "./InputElement"; import {UIEventSource} from "../../Logic/UIEventSource"; import Combine from "../Base/Combine"; import Svg from "../../Svg"; +import * as L from "leaflet" +import * as X from "leaflet-providers" +import {Basemap} from "../../Logic/Leaflet/Basemap"; +import State from "../../State"; /** * Selects a direction in degrees @@ -34,8 +38,8 @@ export default class DirectionInput extends InputElement { } InnerRender(): string { - console.log("Inner render direction") return new Combine([ + `
`, Svg.direction_svg().SetStyle( `position: absolute;top: 0;left: 0;width: 100%;height: 100%;rotate:${this.value.data}deg;`) .SetClass("direction-svg"), @@ -47,8 +51,7 @@ export default class DirectionInput extends InputElement { } protected InnerUpdate(htmlElement: HTMLElement) { - console.log("Inner update direction") - super.InnerUpdate(htmlElement); + super.InnerUpdate(htmlElement); const self = this; function onPosChange(x: number, y: number) { @@ -57,7 +60,7 @@ export default class DirectionInput extends InputElement { const dy = (rect.top + rect.bottom) / 2 - y; const angle = 180 * Math.atan2(dy, dx) / Math.PI; const angleGeo = Math.floor((450 - angle) % 360); - self.value.setData(""+angleGeo) + self.value.setData("" + angleGeo) } diff --git a/UI/Input/ValidatedTextField.ts b/UI/Input/ValidatedTextField.ts index 9c9e23c..703ed19 100644 --- a/UI/Input/ValidatedTextField.ts +++ b/UI/Input/ValidatedTextField.ts @@ -16,7 +16,9 @@ interface TextFieldDef { explanation: string, isValid: ((s: string, country?: string) => boolean), reformat?: ((s: string, country?: string) => string), - inputHelper?: (value: UIEventSource) => InputElement, + inputHelper?: (value: UIEventSource, options?: { + location: [number, number] + }) => InputElement, } export default class ValidatedTextField { @@ -26,7 +28,9 @@ export default class ValidatedTextField { explanation: string, isValid?: ((s: string, country?: string) => boolean), reformat?: ((s: string, country?: string) => string), - inputHelper?: (value: UIEventSource) => InputElement): TextFieldDef { + inputHelper?: (value: UIEventSource, options?:{ + location: [number, number] + }) => InputElement): TextFieldDef { if (isValid === undefined) { isValid = () => true; @@ -197,7 +201,8 @@ export default class ValidatedTextField { textArea?: boolean, textAreaRows?: number, isValid?: ((s: string, country: string) => boolean), - country?: string + country?: string, + location?: [number /*lat*/, number /*lon*/] }): InputElement { options = options ?? {}; options.placeholder = options.placeholder ?? type; @@ -230,7 +235,9 @@ export default class ValidatedTextField { } if (tp.inputHelper) { - input = new CombinedInputElement(input, tp.inputHelper(input.GetValue())); + input = new CombinedInputElement(input, tp.inputHelper(input.GetValue(),{ + location: options.location + })); } return input; } diff --git a/UI/Popup/FeatureInfoBox.ts b/UI/Popup/FeatureInfoBox.ts index ed66eb2..71145f9 100644 --- a/UI/Popup/FeatureInfoBox.ts +++ b/UI/Popup/FeatureInfoBox.ts @@ -28,7 +28,8 @@ export class FeatureInfoBox extends UIElement { this._layerConfig = layerConfig; - this._title = new TagRenderingAnswer(tags, layerConfig.title) + this._title = layerConfig.title === undefined ? undefined : + new TagRenderingAnswer(tags, layerConfig.title) .SetClass("featureinfobox-title"); this._titleIcons = new Combine( layerConfig.titleIcons.map(icon => new TagRenderingAnswer(tags, icon))) diff --git a/UI/Popup/TagRenderingAnswer.ts b/UI/Popup/TagRenderingAnswer.ts index 9bafc24..d5e062d 100644 --- a/UI/Popup/TagRenderingAnswer.ts +++ b/UI/Popup/TagRenderingAnswer.ts @@ -15,6 +15,9 @@ export default class TagRenderingAnswer extends UIElement { super(tags); this._tags = tags; this._configuration = configuration; + if(configuration === undefined){ + throw "Trying to generate a tagRenderingAnswer without configuration..." + } } InnerRender(): string { diff --git a/UI/Popup/TagRenderingQuestion.ts b/UI/Popup/TagRenderingQuestion.ts index d803429..68f48f9 100644 --- a/UI/Popup/TagRenderingQuestion.ts +++ b/UI/Popup/TagRenderingQuestion.ts @@ -251,7 +251,8 @@ export default class TagRenderingQuestion extends UIElement { const textField = ValidatedTextField.InputForType(this._configuration.freeform.type, { isValid: (str) => (str.length <= 255), - country: this._tags.data._country + country: this._tags.data._country, + location: [this._tags.data._lat, this._tags.data._lon] }); textField.GetValue().setData(this._tags.data[this._configuration.freeform.key]); diff --git a/UI/SpecialVisualizations.ts b/UI/SpecialVisualizations.ts index 75f260f..8e1b31f 100644 --- a/UI/SpecialVisualizations.ts +++ b/UI/SpecialVisualizations.ts @@ -39,12 +39,16 @@ export class SubstitutedTranslation extends UIElement { return [] } const tags = this.tags.data; + txt = SubstitutedTranslation.SubstituteKeys(txt, tags); + return this.EvaluateSpecialComponents(txt); + } + + public static SubstituteKeys(txt: string, tags: any) { for (const key in tags) { // Poor mans replace all txt = txt.split("{" + key + "}").join(tags[key]); } - - return this.EvaluateSpecialComponents(txt); + return txt; } private EvaluateSpecialComponents(template: string): UIElement[] { diff --git a/UI/UserBadge.ts b/UI/UserBadge.ts index f733d9b..04ffcd9 100644 --- a/UI/UserBadge.ts +++ b/UI/UserBadge.ts @@ -94,15 +94,6 @@ export class UserBadge extends UIElement { dryrun = new FixedUiElement("TESTING").SetClass("alert"); } - if (user.home !== undefined) { - const icon = L.icon({ - iconUrl: Img.AsData(Svg.home_white_bg), - iconSize: [30, 30], - iconAnchor: [15, 15] - }); - L.marker([user.home.lat, user.home.lon], {icon: icon}).addTo(State.state.bm.map) - } - const settings = new Link(Svg.gear_svg(), `https://www.openstreetmap.org/user/${encodeURIComponent(user.name)}/account`, diff --git a/Utils.ts b/Utils.ts index 423ddb3..a0b9815 100644 --- a/Utils.ts +++ b/Utils.ts @@ -3,6 +3,7 @@ import * as $ from "jquery" export class Utils { + public static readonly assets_path = "./assets/svg/"; static EncodeXmlValue(str) { return str.replace(/&/g, '&') @@ -167,4 +168,6 @@ export class Utils { console.log("Added custom layout ",location) } + + } diff --git a/assets/layers/direction/direction.json b/assets/layers/direction/direction.json new file mode 100644 index 0000000..ac096e2 --- /dev/null +++ b/assets/layers/direction/direction.json @@ -0,0 +1,32 @@ +{ + "id": "direction", + "name": { + "en": "Direction visualization" + }, + "minzoom": 16, + "overpassTags": { + "or": ["camera:direction~*","direction~*"] + }, + "doNotDownload": true, + "passAllFeatures": true, + "title": null, + "description": { + "en": "This layer visualizes directions" + }, + "tagRenderings": [], + "icon": "./assets/svg/direction_gradient.svg", + "rotation": { + "render": "{camera:direction}", + "mappings": [ + { + "if": "direction~*", + "then": "{direction}" + } + ] + }, + "iconSize": "200,200,center", + "color": "--catch-detail-color", + "stroke": "0", + "presets": [], + "wayHandling": 2 +} \ No newline at end of file diff --git a/assets/svg/direction_gradient.svg b/assets/svg/direction_gradient.svg new file mode 100644 index 0000000..aa274b4 --- /dev/null +++ b/assets/svg/direction_gradient.svg @@ -0,0 +1,54 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + diff --git a/assets/themes/surveillance_cameras/custom_theme.css b/assets/themes/surveillance_cameras/custom_theme.css index d68e02a..97a49d8 100644 --- a/assets/themes/surveillance_cameras/custom_theme.css +++ b/assets/themes/surveillance_cameras/custom_theme.css @@ -10,3 +10,17 @@ html { --shadow-color: #0f0 !important; } +#innercolor { + stop-color:#ff0000 +} +.leaflet-div-icon svg { + width: calc(100% - 3px); + height: calc(100% + 3px); +} +/* +.leaflet-div-icon svg path { + fill: none !important; + stroke-width: 1px !important; + stroke: #0f0 !important; +} +*/ diff --git a/assets/themes/surveillance_cameras/surveillance_cameras.json b/assets/themes/surveillance_cameras/surveillance_cameras.json index c502120..3069b22 100644 --- a/assets/themes/surveillance_cameras/surveillance_cameras.json +++ b/assets/themes/surveillance_cameras/surveillance_cameras.json @@ -27,6 +27,7 @@ "customCss": "./assets/themes/surveillance_cameras/custom_theme.css", "defaultBackgroundId": "Stadia.AlidadeSmoothDark", "layers": [ + "direction", { "id": "cameras", "name": { @@ -56,6 +57,7 @@ "tagRenderings": [ "images", { + "#": "Camera type: fixed; panning; dome", "question": { "en": "What kind of camera is this?", "nl": "Wat voor soort camera is dit?" @@ -97,18 +99,32 @@ ] }, { + "#": "direction. We don't ask this for a dome on a pole or ceiling as it has a 360° view", "question": { "en": "In which geographical direction does this camera film?", "nl": "Naar welke geografische richting filmt deze camera?" }, "render": "Films to {camera:direction}", - "condition": "camera:type!=dome", + "condition": { + "not": { + "and": [ + "camera:type=dome", + { + "or": [ + "camera:mount=ceiling", + "camera:mount=pole" + ] + } + ] + } + }, "freeform": { "key": "camera:direction", "type": "direction" } }, { + "#": "Operator", "freeform": { "key": "operator" }, @@ -122,6 +138,7 @@ } }, { + "#": "Surveillance type: public, outdoor, indoor", "question": { "en": "What kind of surveillance is this camera", "nl": "Wat soort bewaking wordt hier uitgevoerd?" @@ -134,8 +151,8 @@ ] }, "then": { - "en": "A public area is surveilled, such as a street, a bridge, a square, a park, a train station...", - "nl": "Bewaking van de publieke ruilmte, dus een straat, een brug, een park, een plein, een stationsgebouw..." + "en": "A public area is surveilled, such as a street, a bridge, a square, a park, a train station, a public corridor or tunnel,...", + "nl": "Bewaking van de publieke ruilmte, dus een straat, een brug, een park, een plein, een stationsgebouw, een publiek toegankelijke gang of tunnel..." } }, { @@ -156,13 +173,67 @@ ] }, "then": { - "nl": "Een private binnenruimte wordt bewaakt, bv. een wiinkel, een parkeergarage, ...", + "nl": "Een private binnenruimte wordt bewaakt, bv. een winkel, een parkeergarage, ...", "en": "A private indoor area is surveilled, e.g. a shop, a private underground parking, ..." } } ] }, { + "#": "Indoor camera? This isn't clear for 'public'-cameras", + "question": { + "en": "Is the public space surveilled by this camera an indoor or outdoor space?", + "nl": "Bevindt de bewaakte publieke ruimte camera zich binnen of buiten?" + }, + "condition": { + "and": [ + "surveillance:type=public" + ] + }, + "mappings": [ + { + "if": "indoor=yes", + "then": { + "en": "This camera is located indoors", + "nl": "Deze camera bevindt zich binnen" + } + }, + { + "if": "indoor=no", + "then": { + "en": "This camera is located outdoors", + "nl": "Deze camera bevindt zich buiten" + } + }, + { + "if": "indoor=", + "then": { + "en": "This camera is probably located outdoors", + "nl": "Deze camera bevindt zich waarschijnlijk buiten" + }, + "hideInAnswer": true + } + ] + }, + { + "#": "Level", + "question": { + "en": "On which level is this camera located?", + "nl": "Op welke verdieping bevindt deze camera zich?" + }, + "freeform": { + "key": "level", + "type": "nat" + }, + "condition": { + "or": [ + "indoor=yes", + "surveillance:type=ye" + ] + } + }, + { + "#": "Surveillance:zone", "question": { "en": "What exactly is surveilled here?", "nl": "Wat wordt hier precies bewaakt?" @@ -244,6 +315,7 @@ ] }, { + "#": "camera:mount", "question": { "en": "How is this camera placed?", "nl": "Hoe is deze camera geplaatst?" @@ -267,10 +339,10 @@ } }, { - "if": "camera:mount=pole", + "if": "camera:mount=ceiling", "then": { - "en": "This camera is placed one a pole", - "nl": "Deze camera staat op een paal" + "en": "This camera is placed on the ceiling", + "nl": "Deze camera hangt aan het plafond" } } ] @@ -293,7 +365,7 @@ "render": "50,50,center" }, "color": { - "render": "#00f" + "render": "#f00" }, "presets": [ { diff --git a/css/mobile.css b/css/mobile.css index 6b1cde2..8525b13 100644 --- a/css/mobile.css +++ b/css/mobile.css @@ -45,10 +45,6 @@ Contains tweaks for small screens } .leaflet-popup { - transform: unset !important; - } - - .leaflet-popup-content { /* On mobile, the popups are shown as a full-screen element */ display: none; visibility: hidden; diff --git a/css/tagrendering.css b/css/tagrendering.css index 7694687..aa82b69 100644 --- a/css/tagrendering.css +++ b/css/tagrendering.css @@ -38,6 +38,11 @@ } +.question svg { + width: 100%; + height: 100%; +} + .question-text { font-size: larger; font-weight: bold; diff --git a/index.css b/index.css index 4fbd1ee..04b6382 100644 --- a/index.css +++ b/index.css @@ -255,7 +255,6 @@ a { position: absolute; z-index: 5000; transition: all 500ms linear; - overflow-x: hidden; pointer-events: none; /* Shadow offset */ padding: 0.5em 10px 0 0.5em; @@ -410,6 +409,12 @@ a { overflow-y: auto; overflow-x: hidden; } + +.leaflet-div-icon { + background-color: unset !important; + border: unset !important; +} + /****** ShareScreen *****/ .literal-code { diff --git a/install.bat b/install.bat deleted file mode 100644 index b66c116..0000000 --- a/install.bat +++ /dev/null @@ -1 +0,0 @@ -npm install \ No newline at end of file diff --git a/package.json b/package.json index a354cbc..5d40ea9 100644 --- a/package.json +++ b/package.json @@ -16,10 +16,11 @@ "start": "parcel *.html UI/** Logic/** assets/** assets/**/** assets/**/**/** vendor/* vendor/*/*", "test": "ts-node test/*", "generate:editor-layer-index": "cd assets/ && wget https://osmlab.github.io/editor-layer-index/imagery.geojson --output-document=editor-layer-index.json", - "generate:images": "ts-node generateIncludedImages.ts", - "generate:layouts": "ts-node createLayouts.ts", + "generate:images": "ts-node scripts/generateIncludedImages.ts", + "generate:translations": "ts-node scripts/generateTranslations.ts", + "generate:layouts": "ts-node scripts/createLayouts.ts", "optimize-images": "cd assets/generated/ && find -name '*.png' -exec optipng '{}' \\; && echo 'PNGs are optimized'", - "generate": "npm run generate:images && npm run generate:layouts && npm run generate:editor-layer-index", + "generate": "npm run generate:images && npm run generate:translations && npm run generate:layouts && npm run generate:editor-layer-index", "build": "rm -rf dist/ npm run generate && parcel build --public-url ./ *.html assets/** assets/**/** assets/**/**/** vendor/* vendor/*/*", "prepare-deploy": "npm run generate && npm run build && rm -rf .cache", "deploy:staging": "npm run prepare-deploy && rm -rf /home/pietervdvn/git/pietervdvn.github.io/Staging/* && cp -r dist/* /home/pietervdvn/git/pietervdvn.github.io/Staging/ && cd /home/pietervdvn/git/pietervdvn.github.io/ && git add * && git commit -m 'New MapComplete Version' && git push && cd - && npm run clean", diff --git a/createLayouts.ts b/scripts/createLayouts.ts similarity index 92% rename from createLayouts.ts rename to scripts/createLayouts.ts index 2b7c380..e596c09 100644 --- a/createLayouts.ts +++ b/scripts/createLayouts.ts @@ -1,16 +1,16 @@ -import {Img} from "./UI/Img" -import {UIElement} from "./UI/UIElement"; +import {Img} from "../UI/Img" +import {UIElement} from "../UI/UIElement"; Img.runningFromConsole = true; // We HAVE to mark this while importing UIElement.runningFromConsole = true; -import {AllKnownLayouts} from "./Customizations/AllKnownLayouts"; +import {AllKnownLayouts} from "../Customizations/AllKnownLayouts"; import {existsSync, mkdirSync, readFileSync, writeFile, writeFileSync} from "fs"; -import Locale from "./UI/i18n/Locale"; +import Locale from "../UI/i18n/Locale"; import svg2img from 'promise-svg2img'; -import Translations from "./UI/i18n/Translations"; -import {Translation} from "./UI/i18n/Translation"; -import LayoutConfig from "./Customizations/JSON/LayoutConfig"; +import Translations from "../UI/i18n/Translations"; +import {Translation} from "../UI/i18n/Translation"; +import LayoutConfig from "../Customizations/JSON/LayoutConfig"; function enc(str: string): string { @@ -100,7 +100,7 @@ const alreadyWritten = [] function createIcon(iconPath: string, size: number, layout: LayoutConfig) { let name = iconPath.split(".").slice(0, -1).join("."); - if (name.startsWith("./")) { + if (name.startsWith("../")) { name = name.substr(2) } const newname = `${name}${size}.png` @@ -151,7 +151,7 @@ function createManifest(layout: LayoutConfig, relativePath: string) { let path = layout.icon; if (layout.icon.startsWith("<")) { // THis is already the svg - path = "./assets/generated/" + layout.id + "_logo.svg" + path = "../assets/generated/" + layout.id + "_logo.svg" writeFileSync(path, layout.icon) } @@ -212,19 +212,19 @@ function createLandingPage(layout: LayoutConfig) { } const og = ` - + ` let icon = layout.icon; if (icon.startsWith("", og) .replace(/.+?<\/title>/, `<title>${ogTitle}`) .replace("Loading MapComplete, hang on...", `Loading MapComplete theme ${ogTitle}...`) @@ -251,7 +251,7 @@ let wikiPage = "{|class=\"wikitable sortable\"\n" + "|-"; -const generatedDir = "./assets/generated"; +const generatedDir = "../assets/generated"; if (! existsSync(generatedDir)) { mkdirSync(generatedDir) } diff --git a/generateIncludedImages.ts b/scripts/generateIncludedImages.ts similarity index 56% rename from generateIncludedImages.ts rename to scripts/generateIncludedImages.ts index 73a8251..75261b2 100644 --- a/generateIncludedImages.ts +++ b/scripts/generateIncludedImages.ts @@ -1,5 +1,5 @@ import * as fs from "fs"; -import {Utils} from "./Utils"; +import {Utils} from "../Utils"; function genImages() { @@ -7,6 +7,7 @@ function genImages() { const dir = fs.readdirSync("./assets/svg") let module = "import {Img} from \"./UI/Img\";\nimport {FixedUiElement} from \"./UI/Base/FixedUiElement\";\n\nexport default class Svg {\n\n\n"; + const allNames: string[] = []; for (const path of dir) { if (!path.endsWith(".svg")) { @@ -26,47 +27,11 @@ function genImages() { module += ` public static ${name}_img = Img.AsImageElement(Svg.${name})\n` module += ` public static ${name}_svg() { return new FixedUiElement(Svg.${name});}\n` module += ` public static ${name}_ui() { return new FixedUiElement(Svg.${name}_img);}\n\n` + allNames.push(`"${path}": Svg.${name}`) } + module += `public static All = {${allNames.join(",")}};` module += "}\n"; fs.writeFileSync("Svg.ts", module); console.log("Done") } - -function isTranslation(tr: any): boolean { - for (const key in tr) { - if (typeof tr[key] !== "string") { - return false; - } - } - return true; -} - -function transformTranslation(obj: any, depth = 1) { - - if (isTranslation(obj)) { - return `new Translation( ${JSON.stringify(obj)} )` - } - - let values = "" - for (const key in obj) { - values += (Utils.Times((_) => " ", depth)) + key + ": " + transformTranslation(obj[key], depth + 1) + ",\n" - } - return `{${values}}`; - -} - -function genTranslations() { - const translations = JSON.parse(fs.readFileSync("./assets/translations.json", "utf-8")) - const transformed = transformTranslation(translations); - - let module = `import {Translation} from "./UI/i18n/Translation"\n\nexport default class AllTranslationAssets {\n\n`; - module += " public static t = " + transformed; - module += "}" - - fs.writeFileSync("AllTranslationAssets.ts", module); - - -} - -genTranslations() genImages() \ No newline at end of file diff --git a/scripts/generateTranslations.ts b/scripts/generateTranslations.ts new file mode 100644 index 0000000..590cd08 --- /dev/null +++ b/scripts/generateTranslations.ts @@ -0,0 +1,40 @@ +import * as fs from "fs"; +import {Utils} from "../Utils"; + +function isTranslation(tr: any): boolean { + for (const key in tr) { + if (typeof tr[key] !== "string") { + return false; + } + } + return true; +} + +function transformTranslation(obj: any, depth = 1) { + + if (isTranslation(obj)) { + return `new Translation( ${JSON.stringify(obj)} )` + } + + let values = "" + for (const key in obj) { + values += (Utils.Times((_) => " ", depth)) + key + ": " + transformTranslation(obj[key], depth + 1) + ",\n" + } + return `{${values}}`; + +} + +function genTranslations() { + const translations = JSON.parse(fs.readFileSync("./assets/translations.json", "utf-8")) + const transformed = transformTranslation(translations); + + let module = `import {Translation} from "./UI/i18n/Translation"\n\nexport default class AllTranslationAssets {\n\n`; + module += " public static t = " + transformed; + module += "}" + + fs.writeFileSync("AllTranslationAssets.ts", module); + + +} + +genTranslations() \ No newline at end of file diff --git a/test.html b/test.html index fe0a777..521de22 100644 --- a/test.html +++ b/test.html @@ -25,7 +25,10 @@ -
'maindiv' not attached
+
+ +
'maindiv' not attached
+
'extradiv' not attached
diff --git a/test.ts b/test.ts index 87cb971..1a50ef3 100644 --- a/test.ts +++ b/test.ts @@ -6,7 +6,7 @@ import {UIEventSource} from "./Logic/UIEventSource"; import {VariableUiElement} from "./UI/Base/VariableUIElement"; const d = new UIEventSource("90"); -new Direction(d).AttachTo("maindiv") +new Direction(d, [51.21576,3.22001]).AttachTo("maindiv") new VariableUiElement(d.map(d => "" + d + "°")).AttachTo("extradiv") UIEventSource.Chronic(25, () => {