diff --git a/InitUiElements.ts b/InitUiElements.ts index 5be908fad..b2f8fe09a 100644 --- a/InitUiElements.ts +++ b/InitUiElements.ts @@ -41,6 +41,7 @@ import FeatureSource from "./Logic/FeatureSource/FeatureSource"; import AllKnownLayers from "./Customizations/AllKnownLayers"; import LayerConfig from "./Customizations/JSON/LayerConfig"; import AvailableBaseLayers from "./Logic/Actors/AvailableBaseLayers"; +import FilterView from "./UI/BigComponents/FilterView"; export class InitUiElements { static InitAll( @@ -205,7 +206,7 @@ export class InitUiElements { Img.AsImageElement(Svg.plus_zoom, "", "width:1.25rem;height:1.25rem") ) ).onClick(() => { - State.state.locationControl.data.zoom++; + State.state.locationControl.data.zoom++; State.state.locationControl.ping(); }); @@ -353,6 +354,7 @@ export class InitUiElements { const layerControlPanel = new LayerControlPanel( State.state.layerControlIsOpened ).SetClass("block p-1 rounded-full"); + const layerControlButton = new Toggle( layerControlPanel, new MapControlButton(Svg.layers_svg()), @@ -365,7 +367,31 @@ export class InitUiElements { State.state.featureSwitchLayers ); - new Combine([copyrightButton, layerControl]).AttachTo("bottom-left"); + const filterView = new FilterView(State.state.FilterIsOpened).SetClass( + "block p-1 rounded-full" + ); + + const filterMapControlButton = new MapControlButton( + new CenterFlexedElement( + Img.AsImageElement(Svg.filter, "", "width:1.25rem;height:1.25rem") + ) + ); + + const filterButton = new Toggle( + filterView, + filterMapControlButton, + State.state.FilterIsOpened + ).ToggleOnClick(); + + const filterControl = new Toggle( + filterButton, + "", + State.state.featureSwitchFilter + ); + + new Combine([copyrightButton, layerControl, filterControl]).AttachTo( + "bottom-left" + ); State.state.locationControl.addCallback(() => { // Close the layer selection when the map is moved diff --git a/State.ts b/State.ts index 8e4322d65..c4169d50f 100644 --- a/State.ts +++ b/State.ts @@ -1,13 +1,13 @@ -import {Utils} from "./Utils"; -import {ElementStorage} from "./Logic/ElementStorage"; -import {Changes} from "./Logic/Osm/Changes"; -import {OsmConnection} from "./Logic/Osm/OsmConnection"; +import { Utils } from "./Utils"; +import { ElementStorage } from "./Logic/ElementStorage"; +import { Changes } from "./Logic/Osm/Changes"; +import { OsmConnection } from "./Logic/Osm/OsmConnection"; import Locale from "./UI/i18n/Locale"; -import {UIEventSource} from "./Logic/UIEventSource"; -import {LocalStorageSource} from "./Logic/Web/LocalStorageSource"; -import {QueryParameters} from "./Logic/Web/QueryParameters"; +import { UIEventSource } from "./Logic/UIEventSource"; +import { LocalStorageSource } from "./Logic/Web/LocalStorageSource"; +import { QueryParameters } from "./Logic/Web/QueryParameters"; import LayoutConfig from "./Customizations/JSON/LayoutConfig"; -import {MangroveIdentity} from "./Logic/Web/MangroveReviews"; +import { MangroveIdentity } from "./Logic/Web/MangroveReviews"; import InstalledThemes from "./Logic/Actors/InstalledThemes"; import BaseLayer from "./Models/BaseLayer"; import Loc from "./Models/Loc"; @@ -17,7 +17,7 @@ import OverpassFeatureSource from "./Logic/Actors/OverpassFeatureSource"; import LayerConfig from "./Customizations/JSON/LayerConfig"; import TitleHandler from "./Logic/Actors/TitleHandler"; import PendingChangesUploader from "./Logic/Actors/PendingChangesUploader"; -import {Relation} from "./Logic/Osm/ExtractRelations"; +import { Relation } from "./Logic/Osm/ExtractRelations"; import OsmApiFeatureSource from "./Logic/FeatureSource/OsmApiFeatureSource"; /** @@ -25,274 +25,402 @@ import OsmApiFeatureSource from "./Logic/FeatureSource/OsmApiFeatureSource"; */ export default class State { + // The singleton of the global state + public static state: State; - // The singleton of the global state - public static state: State; + public readonly layoutToUse = new UIEventSource(undefined); - - public readonly layoutToUse = new UIEventSource(undefined); - - /** + /** The mapping from id -> UIEventSource */ - public allElements: ElementStorage; - /** + public allElements: ElementStorage; + /** THe change handler */ - public changes: Changes; - /** + public changes: Changes; + /** The leaflet instance of the big basemap */ - public leafletMap = new UIEventSource(undefined); - /** - * Background layer id - */ - public availableBackgroundLayers: UIEventSource; - /** + public leafletMap = new UIEventSource(undefined); + /** + * Background layer id + */ + public availableBackgroundLayers: UIEventSource; + /** The user credentials */ - public osmConnection: OsmConnection; + public osmConnection: OsmConnection; - public mangroveIdentity: MangroveIdentity; + public mangroveIdentity: MangroveIdentity; - public favouriteLayers: UIEventSource; + public favouriteLayers: UIEventSource; - public layerUpdater: OverpassFeatureSource; - - public osmApiFeatureSource : OsmApiFeatureSource ; + public layerUpdater: OverpassFeatureSource; + public osmApiFeatureSource: OsmApiFeatureSource; - public filteredLayers: UIEventSource<{ - readonly isDisplayed: UIEventSource, - readonly layerDef: LayerConfig; - }[]> = new UIEventSource<{ - readonly isDisplayed: UIEventSource, - readonly layerDef: LayerConfig; - }[]>([]) + public filteredLayers: UIEventSource< + { + readonly isDisplayed: UIEventSource; + readonly layerDef: LayerConfig; + }[] + > = new UIEventSource< + { + readonly isDisplayed: UIEventSource; + readonly layerDef: LayerConfig; + }[] + >([]); - - /** + /** The latest element that was selected */ - public readonly selectedElement = new UIEventSource(undefined, "Selected element") + public readonly selectedElement = new UIEventSource( + undefined, + "Selected element" + ); - /** - * Keeps track of relations: which way is part of which other way? - * Set by the overpass-updater; used in the metatagging - */ - public readonly knownRelations = new UIEventSource>(undefined, "Relation memberships") + /** + * Keeps track of relations: which way is part of which other way? + * Set by the overpass-updater; used in the metatagging + */ + public readonly knownRelations = new UIEventSource< + Map + >(undefined, "Relation memberships"); - public readonly featureSwitchUserbadge: UIEventSource; - public readonly featureSwitchSearch: UIEventSource; - public readonly featureSwitchLayers: UIEventSource; - public readonly featureSwitchAddNew: UIEventSource; - public readonly featureSwitchWelcomeMessage: UIEventSource; - public readonly featureSwitchIframe: UIEventSource; - public readonly featureSwitchMoreQuests: UIEventSource; - public readonly featureSwitchShareScreen: UIEventSource; - public readonly featureSwitchGeolocation: UIEventSource; - public readonly featureSwitchIsTesting: UIEventSource; - public readonly featureSwitchIsDebugging: UIEventSource; - public readonly featureSwitchShowAllQuestions: UIEventSource; - public readonly featureSwitchApiURL: UIEventSource; + public readonly featureSwitchUserbadge: UIEventSource; + public readonly featureSwitchSearch: UIEventSource; + public readonly featureSwitchLayers: UIEventSource; + public readonly featureSwitchAddNew: UIEventSource; + public readonly featureSwitchWelcomeMessage: UIEventSource; + public readonly featureSwitchIframe: UIEventSource; + public readonly featureSwitchMoreQuests: UIEventSource; + public readonly featureSwitchShareScreen: UIEventSource; + public readonly featureSwitchGeolocation: UIEventSource; + public readonly featureSwitchIsTesting: UIEventSource; + public readonly featureSwitchIsDebugging: UIEventSource; + public readonly featureSwitchShowAllQuestions: UIEventSource; + public readonly featureSwitchApiURL: UIEventSource; + public readonly featureSwitchFilter: UIEventSource; + /** + * The map location: currently centered lat, lon and zoom + */ + public readonly locationControl = new UIEventSource(undefined); + public backgroundLayer; + public readonly backgroundLayerId: UIEventSource; - /** - * The map location: currently centered lat, lon and zoom - */ - public readonly locationControl = new UIEventSource(undefined); - public backgroundLayer; - public readonly backgroundLayerId: UIEventSource; + /* Last location where a click was registered + */ + public readonly LastClickLocation: UIEventSource<{ + lat: number; + lon: number; + }> = new UIEventSource<{ lat: number; lon: number }>(undefined); - /* Last location where a click was registered - */ - public readonly LastClickLocation: UIEventSource<{ lat: number, lon: number }> = new UIEventSource<{ lat: number, lon: number }>(undefined) + /** + * The location as delivered by the GPS + */ + public currentGPSLocation: UIEventSource<{ + latlng: { lat: number; lng: number }; + accuracy: number; + }> = new UIEventSource<{ + latlng: { lat: number; lng: number }; + accuracy: number; + }>(undefined); + public layoutDefinition: string; + public installedThemes: UIEventSource< + { layout: LayoutConfig; definition: string }[] + >; - /** - * The location as delivered by the GPS - */ - public currentGPSLocation: UIEventSource<{ - latlng: { lat: number, lng: number }, - accuracy: number - }> = new UIEventSource<{ latlng: { lat: number, lng: number }, accuracy: number }>(undefined); - public layoutDefinition: string; - public installedThemes: UIEventSource<{ layout: LayoutConfig; definition: string }[]>; - - public layerControlIsOpened: UIEventSource = - QueryParameters.GetQueryParameter("layer-control-toggle", "false", "Whether or not the layer control is shown") - .map((str) => str !== "false", [], b => "" + b) - - public welcomeMessageOpenedTab = QueryParameters.GetQueryParameter("tab", "0", `The tab that is shown in the welcome-message. 0 = the explanation of the theme,1 = OSM-credits, 2 = sharescreen, 3 = more themes, 4 = about mapcomplete (user must be logged in and have >${Constants.userJourney.mapCompleteHelpUnlock} changesets)`).map( - str => isNaN(Number(str)) ? 0 : Number(str), [], n => "" + n + public layerControlIsOpened: UIEventSource = + QueryParameters.GetQueryParameter( + "layer-control-toggle", + "false", + "Whether or not the layer control is shown" + ).map( + (str) => str !== "false", + [], + (b) => "" + b ); - - constructor(layoutToUse: LayoutConfig) { - const self = this; - this.layoutToUse.setData(layoutToUse); + public FilterIsOpened: UIEventSource = + QueryParameters.GetQueryParameter( + "filter-toggle", + "false", + "Whether or not the filter is shown" + ).map( + (str) => str !== "false", + [], + (b) => "" + b + ); - // -- Location control initialization - { - const zoom = State.asFloat( - QueryParameters.GetQueryParameter("z", "" + (layoutToUse?.startZoom ?? 1), "The initial/current zoom level") - .syncWith(LocalStorageSource.Get("zoom"))); - const lat = State.asFloat(QueryParameters.GetQueryParameter("lat", "" + (layoutToUse?.startLat ?? 0), "The initial/current latitude") - .syncWith(LocalStorageSource.Get("lat"))); - const lon = State.asFloat(QueryParameters.GetQueryParameter("lon", "" + (layoutToUse?.startLon ?? 0), "The initial/current longitude of the app") - .syncWith(LocalStorageSource.Get("lon"))); + public welcomeMessageOpenedTab = QueryParameters.GetQueryParameter( + "tab", + "0", + `The tab that is shown in the welcome-message. 0 = the explanation of the theme,1 = OSM-credits, 2 = sharescreen, 3 = more themes, 4 = about mapcomplete (user must be logged in and have >${Constants.userJourney.mapCompleteHelpUnlock} changesets)` + ).map( + (str) => (isNaN(Number(str)) ? 0 : Number(str)), + [], + (n) => "" + n + ); + constructor(layoutToUse: LayoutConfig) { + const self = this; - this.locationControl = new UIEventSource({ - zoom: Utils.asFloat(zoom.data), - lat: Utils.asFloat(lat.data), - lon: Utils.asFloat(lon.data), - }).addCallback((latlonz) => { - zoom.setData(latlonz.zoom); - lat.setData(latlonz.lat); - lon.setData(latlonz.lon); - }); + this.layoutToUse.setData(layoutToUse); - this.layoutToUse.addCallback(layoutToUse => { - const lcd = self.locationControl.data; - lcd.zoom = lcd.zoom ?? layoutToUse?.startZoom; - lcd.lat = lcd.lat ?? layoutToUse?.startLat; - lcd.lon = lcd.lon ?? layoutToUse?.startLon; - self.locationControl.ping(); - }); + // -- Location control initialization + { + const zoom = State.asFloat( + QueryParameters.GetQueryParameter( + "z", + "" + (layoutToUse?.startZoom ?? 1), + "The initial/current zoom level" + ).syncWith(LocalStorageSource.Get("zoom")) + ); + const lat = State.asFloat( + QueryParameters.GetQueryParameter( + "lat", + "" + (layoutToUse?.startLat ?? 0), + "The initial/current latitude" + ).syncWith(LocalStorageSource.Get("lat")) + ); + const lon = State.asFloat( + QueryParameters.GetQueryParameter( + "lon", + "" + (layoutToUse?.startLon ?? 0), + "The initial/current longitude of the app" + ).syncWith(LocalStorageSource.Get("lon")) + ); - } - - // Helper function to initialize feature switches - function featSw(key: string, deflt: (layout: LayoutConfig) => boolean, documentation: string): UIEventSource { - const queryParameterSource = QueryParameters.GetQueryParameter(key, undefined, documentation); - // I'm so sorry about someone trying to decipher this - - // It takes the current layout, extracts the default value for this query parameter. A query parameter event source is then retrieved and flattened - return UIEventSource.flatten( - self.layoutToUse.map((layout) => { - const defaultValue = deflt(layout); - const queryParam = QueryParameters.GetQueryParameter(key, "" + defaultValue, documentation) - return queryParam.map((str) => str === undefined ? defaultValue : (str !== "false")); - }), [queryParameterSource]); - } - - // Feature switch initialization - not as a function as the UIEventSources are readonly - { - - this.featureSwitchUserbadge = featSw("fs-userbadge", (layoutToUse) => layoutToUse?.enableUserBadge ?? true, - "Disables/Enables the user information pill (userbadge) at the top left. Disabling this disables logging in and thus disables editing all together, effectively putting MapComplete into read-only mode."); - this.featureSwitchSearch = featSw("fs-search", (layoutToUse) => layoutToUse?.enableSearch ?? true, - "Disables/Enables the search bar"); - this.featureSwitchLayers = featSw("fs-layers", (layoutToUse) => layoutToUse?.enableLayers ?? true, - "Disables/Enables the layer control"); - this.featureSwitchAddNew = featSw("fs-add-new", (layoutToUse) => layoutToUse?.enableAddNewPoints ?? true, - "Disables/Enables the 'add new feature'-popup. (A theme without presets might not have it in the first place)"); - this.featureSwitchWelcomeMessage = featSw("fs-welcome-message", () => true, - "Disables/enables the help menu or welcome message"); - this.featureSwitchIframe = featSw("fs-iframe", () => false, - "Disables/Enables the iframe-popup"); - this.featureSwitchMoreQuests = featSw("fs-more-quests", (layoutToUse) => layoutToUse?.enableMoreQuests ?? true, - "Disables/Enables the 'More Quests'-tab in the welcome message"); - this.featureSwitchShareScreen = featSw("fs-share-screen", (layoutToUse) => layoutToUse?.enableShareScreen ?? true, - "Disables/Enables the 'Share-screen'-tab in the welcome message"); - this.featureSwitchGeolocation = featSw("fs-geolocation", (layoutToUse) => layoutToUse?.enableGeolocation ?? true, - "Disables/Enables the geolocation button"); - this.featureSwitchShowAllQuestions = featSw("fs-all-questions", (layoutToUse) => layoutToUse?.enableShowAllQuestions ?? false, - "Always show all questions"); - - this.featureSwitchIsTesting = QueryParameters.GetQueryParameter("test", "false", - "If true, 'dryrun' mode is activated. The app will behave as normal, except that changes to OSM will be printed onto the console instead of actually uploaded to osm.org") - .map(str => str === "true", [], b => "" + b); - - this.featureSwitchIsDebugging = QueryParameters.GetQueryParameter("debug","false", - "If true, shows some extra debugging help such as all the available tags on every object") - .map(str => str === "true", [], b => "" + b) - - this.featureSwitchApiURL = QueryParameters.GetQueryParameter("backend","osm", - "The OSM backend to use - can be used to redirect mapcomplete to the testing backend when using 'osm-test'") - - } - { - // Some other feature switches - const customCssQP = QueryParameters.GetQueryParameter("custom-css", "", "If specified, the custom css from the given link will be loaded additionaly"); - if (customCssQP.data !== undefined && customCssQP.data !== "") { - Utils.LoadCustomCss(customCssQP.data); - } - - - this.backgroundLayerId = QueryParameters.GetQueryParameter("background", - layoutToUse?.defaultBackgroundId ?? "osm", - "The id of the background layer to start with") - - } - - - if(Utils.runningFromConsole){ - return; - } - - this.osmConnection = new OsmConnection( - this.featureSwitchIsTesting.data, - QueryParameters.GetQueryParameter("oauth_token", undefined, - "Used to complete the login"), - layoutToUse?.id, - true, - // @ts-ignore - this.featureSwitchApiURL.data - ); - - - this.allElements = new ElementStorage(); - this.changes = new Changes(); - this.osmApiFeatureSource = new OsmApiFeatureSource() - - new PendingChangesUploader(this.changes, this.selectedElement); - - this.mangroveIdentity = new MangroveIdentity( - this.osmConnection.GetLongPreference("identity", "mangrove") - ); - - - this.installedThemes = new InstalledThemes(this.osmConnection).installedThemes; - - // Important: the favourite layers are initialized _after_ the installed themes, as these might contain an installedTheme - this.favouriteLayers = LocalStorageSource.Get("favouriteLayers") - .syncWith(this.osmConnection.GetLongPreference("favouriteLayers")) - .map( - str => Utils.Dedup(str?.split(";")) ?? [], - [], layers => Utils.Dedup(layers)?.join(";") - ); - - Locale.language.syncWith(this.osmConnection.GetPreference("language")); - - - Locale.language.addCallback((currentLanguage) => { - const layoutToUse = self.layoutToUse.data; - if (layoutToUse === undefined) { - return; - } - if (this.layoutToUse.data.language.indexOf(currentLanguage) < 0) { - console.log("Resetting language to", layoutToUse.language[0], "as", currentLanguage, " is unsupported") - // The current language is not supported -> switch to a supported one - Locale.language.setData(layoutToUse.language[0]); - } - }).ping() - - new TitleHandler(this.layoutToUse, this.selectedElement, this.allElements); + this.locationControl = new UIEventSource({ + zoom: Utils.asFloat(zoom.data), + lat: Utils.asFloat(lat.data), + lon: Utils.asFloat(lon.data), + }).addCallback((latlonz) => { + zoom.setData(latlonz.zoom); + lat.setData(latlonz.lat); + lon.setData(latlonz.lon); + }); + this.layoutToUse.addCallback((layoutToUse) => { + const lcd = self.locationControl.data; + lcd.zoom = lcd.zoom ?? layoutToUse?.startZoom; + lcd.lat = lcd.lat ?? layoutToUse?.startLat; + lcd.lon = lcd.lon ?? layoutToUse?.startLon; + self.locationControl.ping(); + }); } - private static asFloat(source: UIEventSource): UIEventSource { - return source.map(str => { - let parsed = parseFloat(str); - return isNaN(parsed) ? undefined : parsed; - }, [], fl => { - if (fl === undefined || isNaN(fl)) { - return undefined; - } - return ("" + fl).substr(0, 8); - }) + // Helper function to initialize feature switches + function featSw( + key: string, + deflt: (layout: LayoutConfig) => boolean, + documentation: string + ): UIEventSource { + const queryParameterSource = QueryParameters.GetQueryParameter( + key, + undefined, + documentation + ); + // I'm so sorry about someone trying to decipher this + + // It takes the current layout, extracts the default value for this query parameter. A query parameter event source is then retrieved and flattened + return UIEventSource.flatten( + self.layoutToUse.map((layout) => { + const defaultValue = deflt(layout); + const queryParam = QueryParameters.GetQueryParameter( + key, + "" + defaultValue, + documentation + ); + return queryParam.map((str) => + str === undefined ? defaultValue : str !== "false" + ); + }), + [queryParameterSource] + ); } + // Feature switch initialization - not as a function as the UIEventSources are readonly + { + this.featureSwitchUserbadge = featSw( + "fs-userbadge", + (layoutToUse) => layoutToUse?.enableUserBadge ?? true, + "Disables/Enables the user information pill (userbadge) at the top left. Disabling this disables logging in and thus disables editing all together, effectively putting MapComplete into read-only mode." + ); + this.featureSwitchSearch = featSw( + "fs-search", + (layoutToUse) => layoutToUse?.enableSearch ?? true, + "Disables/Enables the search bar" + ); + this.featureSwitchLayers = featSw( + "fs-layers", + (layoutToUse) => layoutToUse?.enableLayers ?? true, + "Disables/Enables the layer control" + ); + this.featureSwitchFilter = featSw( + "fs-filter", + (layoutToUse) => layoutToUse?.enableLayers ?? true, + "Disables/Enables the filter" + ); + this.featureSwitchAddNew = featSw( + "fs-add-new", + (layoutToUse) => layoutToUse?.enableAddNewPoints ?? true, + "Disables/Enables the 'add new feature'-popup. (A theme without presets might not have it in the first place)" + ); + this.featureSwitchWelcomeMessage = featSw( + "fs-welcome-message", + () => true, + "Disables/enables the help menu or welcome message" + ); + this.featureSwitchIframe = featSw( + "fs-iframe", + () => false, + "Disables/Enables the iframe-popup" + ); + this.featureSwitchMoreQuests = featSw( + "fs-more-quests", + (layoutToUse) => layoutToUse?.enableMoreQuests ?? true, + "Disables/Enables the 'More Quests'-tab in the welcome message" + ); + this.featureSwitchShareScreen = featSw( + "fs-share-screen", + (layoutToUse) => layoutToUse?.enableShareScreen ?? true, + "Disables/Enables the 'Share-screen'-tab in the welcome message" + ); + this.featureSwitchGeolocation = featSw( + "fs-geolocation", + (layoutToUse) => layoutToUse?.enableGeolocation ?? true, + "Disables/Enables the geolocation button" + ); + this.featureSwitchShowAllQuestions = featSw( + "fs-all-questions", + (layoutToUse) => layoutToUse?.enableShowAllQuestions ?? false, + "Always show all questions" + ); + this.featureSwitchIsTesting = QueryParameters.GetQueryParameter( + "test", + "false", + "If true, 'dryrun' mode is activated. The app will behave as normal, except that changes to OSM will be printed onto the console instead of actually uploaded to osm.org" + ).map( + (str) => str === "true", + [], + (b) => "" + b + ); + + this.featureSwitchIsDebugging = QueryParameters.GetQueryParameter( + "debug", + "false", + "If true, shows some extra debugging help such as all the available tags on every object" + ).map( + (str) => str === "true", + [], + (b) => "" + b + ); + + this.featureSwitchApiURL = QueryParameters.GetQueryParameter( + "backend", + "osm", + "The OSM backend to use - can be used to redirect mapcomplete to the testing backend when using 'osm-test'" + ); + } + { + // Some other feature switches + const customCssQP = QueryParameters.GetQueryParameter( + "custom-css", + "", + "If specified, the custom css from the given link will be loaded additionaly" + ); + if (customCssQP.data !== undefined && customCssQP.data !== "") { + Utils.LoadCustomCss(customCssQP.data); + } + + this.backgroundLayerId = QueryParameters.GetQueryParameter( + "background", + layoutToUse?.defaultBackgroundId ?? "osm", + "The id of the background layer to start with" + ); + } + + if (Utils.runningFromConsole) { + return; + } + + this.osmConnection = new OsmConnection( + this.featureSwitchIsTesting.data, + QueryParameters.GetQueryParameter( + "oauth_token", + undefined, + "Used to complete the login" + ), + layoutToUse?.id, + true, + // @ts-ignore + this.featureSwitchApiURL.data + ); + + this.allElements = new ElementStorage(); + this.changes = new Changes(); + this.osmApiFeatureSource = new OsmApiFeatureSource(); + + new PendingChangesUploader(this.changes, this.selectedElement); + + this.mangroveIdentity = new MangroveIdentity( + this.osmConnection.GetLongPreference("identity", "mangrove") + ); + + this.installedThemes = new InstalledThemes( + this.osmConnection + ).installedThemes; + + // Important: the favourite layers are initialized _after_ the installed themes, as these might contain an installedTheme + this.favouriteLayers = LocalStorageSource.Get("favouriteLayers") + .syncWith(this.osmConnection.GetLongPreference("favouriteLayers")) + .map( + (str) => Utils.Dedup(str?.split(";")) ?? [], + [], + (layers) => Utils.Dedup(layers)?.join(";") + ); + + Locale.language.syncWith(this.osmConnection.GetPreference("language")); + + Locale.language + .addCallback((currentLanguage) => { + const layoutToUse = self.layoutToUse.data; + if (layoutToUse === undefined) { + return; + } + if (this.layoutToUse.data.language.indexOf(currentLanguage) < 0) { + console.log( + "Resetting language to", + layoutToUse.language[0], + "as", + currentLanguage, + " is unsupported" + ); + // The current language is not supported -> switch to a supported one + Locale.language.setData(layoutToUse.language[0]); + } + }) + .ping(); + + new TitleHandler(this.layoutToUse, this.selectedElement, this.allElements); + } + + private static asFloat(source: UIEventSource): UIEventSource { + return source.map( + (str) => { + let parsed = parseFloat(str); + return isNaN(parsed) ? undefined : parsed; + }, + [], + (fl) => { + if (fl === undefined || isNaN(fl)) { + return undefined; + } + return ("" + fl).substr(0, 8); + } + ); + } } diff --git a/Svg.ts b/Svg.ts index 0ebc3f924..22ea0e115 100644 --- a/Svg.ts +++ b/Svg.ts @@ -29,6 +29,11 @@ export default class Svg { public static arrow_left_smooth_svg() { return new Img(Svg.arrow_left_smooth, true);} public static arrow_left_smooth_ui() { return new FixedUiElement(Svg.arrow_left_smooth_img);} + public static arrow_left_thin = " " + public static arrow_left_thin_img = Img.AsImageElement(Svg.arrow_left_thin) + public static arrow_left_thin_svg() { return new Img(Svg.arrow_left_thin, true);} + public static arrow_left_thin_ui() { return new FixedUiElement(Svg.arrow_left_thin_img);} + public static arrow_right_smooth = " image/svg+xml " public static arrow_right_smooth_img = Img.AsImageElement(Svg.arrow_right_smooth) public static arrow_right_smooth_svg() { return new Img(Svg.arrow_right_smooth, true);} @@ -134,6 +139,11 @@ export default class Svg { public static envelope_svg() { return new Img(Svg.envelope, true);} public static envelope_ui() { return new FixedUiElement(Svg.envelope_img);} + public static filter = " " + public static filter_img = Img.AsImageElement(Svg.filter) + public static filter_svg() { return new Img(Svg.filter, true);} + public static filter_ui() { return new FixedUiElement(Svg.filter_img);} + public static floppy = " " public static floppy_img = Img.AsImageElement(Svg.floppy) public static floppy_svg() { return new Img(Svg.floppy, true);} @@ -349,4 +359,4 @@ export default class Svg { public static wikipedia_svg() { return new Img(Svg.wikipedia, true);} public static wikipedia_ui() { return new FixedUiElement(Svg.wikipedia_img);} -public static All = {"SocialImageForeground.svg": Svg.SocialImageForeground,"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,"back.svg": Svg.back,"bug.svg": Svg.bug,"camera-plus.svg": Svg.camera_plus,"checkmark.svg": Svg.checkmark,"circle.svg": Svg.circle,"clock.svg": Svg.clock,"close.svg": Svg.close,"compass.svg": Svg.compass,"cross_bottom_right.svg": Svg.cross_bottom_right,"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,"direction_masked.svg": Svg.direction_masked,"direction_outline.svg": Svg.direction_outline,"direction_stroke.svg": Svg.direction_stroke,"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,"location.svg": Svg.location,"logo.svg": Svg.logo,"logout.svg": Svg.logout,"mapcomplete_logo.svg": Svg.mapcomplete_logo,"mapillary.svg": Svg.mapillary,"mapillary_black.svg": Svg.mapillary_black,"min-zoom.svg": Svg.min_zoom,"min.svg": Svg.min,"no_checkmark.svg": Svg.no_checkmark,"or.svg": Svg.or,"osm-copyright.svg": Svg.osm_copyright,"osm-logo-us.svg": Svg.osm_logo_us,"osm-logo.svg": Svg.osm_logo,"pencil.svg": Svg.pencil,"phone.svg": Svg.phone,"pin.svg": Svg.pin,"plus-zoom.svg": Svg.plus_zoom,"plus.svg": Svg.plus,"pop-out.svg": Svg.pop_out,"reload.svg": Svg.reload,"ring.svg": Svg.ring,"search.svg": Svg.search,"send_email.svg": Svg.send_email,"share.svg": Svg.share,"square.svg": Svg.square,"star.svg": Svg.star,"star_half.svg": Svg.star_half,"star_outline.svg": Svg.star_outline,"star_outline_half.svg": Svg.star_outline_half,"statistics.svg": Svg.statistics,"translate.svg": Svg.translate,"up.svg": Svg.up,"wikidata.svg": Svg.wikidata,"wikimedia-commons-white.svg": Svg.wikimedia_commons_white,"wikipedia.svg": Svg.wikipedia};} +public static All = {"SocialImageForeground.svg": Svg.SocialImageForeground,"add.svg": Svg.add,"addSmall.svg": Svg.addSmall,"ampersand.svg": Svg.ampersand,"arrow-left-smooth.svg": Svg.arrow_left_smooth,"arrow-left-thin.svg": Svg.arrow_left_thin,"arrow-right-smooth.svg": Svg.arrow_right_smooth,"back.svg": Svg.back,"bug.svg": Svg.bug,"camera-plus.svg": Svg.camera_plus,"checkmark.svg": Svg.checkmark,"circle.svg": Svg.circle,"clock.svg": Svg.clock,"close.svg": Svg.close,"compass.svg": Svg.compass,"cross_bottom_right.svg": Svg.cross_bottom_right,"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,"direction_masked.svg": Svg.direction_masked,"direction_outline.svg": Svg.direction_outline,"direction_stroke.svg": Svg.direction_stroke,"down.svg": Svg.down,"envelope.svg": Svg.envelope,"filter.svg": Svg.filter,"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,"location.svg": Svg.location,"logo.svg": Svg.logo,"logout.svg": Svg.logout,"mapcomplete_logo.svg": Svg.mapcomplete_logo,"mapillary.svg": Svg.mapillary,"mapillary_black.svg": Svg.mapillary_black,"min-zoom.svg": Svg.min_zoom,"min.svg": Svg.min,"no_checkmark.svg": Svg.no_checkmark,"or.svg": Svg.or,"osm-copyright.svg": Svg.osm_copyright,"osm-logo-us.svg": Svg.osm_logo_us,"osm-logo.svg": Svg.osm_logo,"pencil.svg": Svg.pencil,"phone.svg": Svg.phone,"pin.svg": Svg.pin,"plus-zoom.svg": Svg.plus_zoom,"plus.svg": Svg.plus,"pop-out.svg": Svg.pop_out,"reload.svg": Svg.reload,"ring.svg": Svg.ring,"search.svg": Svg.search,"send_email.svg": Svg.send_email,"share.svg": Svg.share,"square.svg": Svg.square,"star.svg": Svg.star,"star_half.svg": Svg.star_half,"star_outline.svg": Svg.star_outline,"star_outline_half.svg": Svg.star_outline_half,"statistics.svg": Svg.statistics,"translate.svg": Svg.translate,"up.svg": Svg.up,"wikidata.svg": Svg.wikidata,"wikimedia-commons-white.svg": Svg.wikimedia_commons_white,"wikipedia.svg": Svg.wikipedia};} diff --git a/UI/BigComponents/FilterView.ts b/UI/BigComponents/FilterView.ts new file mode 100644 index 000000000..b2ee12c16 --- /dev/null +++ b/UI/BigComponents/FilterView.ts @@ -0,0 +1,39 @@ +import { FixedUiElement } from "./../Base/FixedUiElement"; +import { LayerConfigJson } from "./../../Customizations/JSON/LayerConfigJson"; +import { UIEventSource } from "../../Logic/UIEventSource"; +import { VariableUiElement } from "../Base/VariableUIElement"; +import State from "../../State"; +import Toggle from "../Input/Toggle"; +import Combine from "../Base/Combine"; +import Translations from "../i18n/Translations"; +import LayerConfig from "../../Customizations/JSON/LayerConfig"; +import BaseUIElement from "../BaseUIElement"; +import { Translation } from "../i18n/Translation"; +import ScrollableFullScreen from "../Base/ScrollableFullScreen"; + +/** + * Shows the filter + */ +export default class FilterView extends ScrollableFullScreen { + constructor(isShown: UIEventSource) { + super(FilterView.GenTitle, FilterView.Generatecontent, "filter", isShown); + } + + private static GenTitle(): BaseUIElement { + return new FixedUiElement(`Filter`).SetClass( + "text-2xl break-words font-bold p-2" + ); + } + + private static Generatecontent(): BaseUIElement { + let filterPanel: BaseUIElement = new FixedUiElement("more stuff"); + + if (State.state.filteredLayers.data.length > 1) { + let layers = State.state.filteredLayers; + console.log(layers); + filterPanel = new Combine(["layerssss", "
", filterPanel]); + } + + return filterPanel; + } +} diff --git a/assets/svg/arrow-left-thin.svg b/assets/svg/arrow-left-thin.svg new file mode 100644 index 000000000..e4329b615 --- /dev/null +++ b/assets/svg/arrow-left-thin.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/svg/filter.svg b/assets/svg/filter.svg new file mode 100644 index 000000000..a6783b248 --- /dev/null +++ b/assets/svg/filter.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/svg/license_info.json b/assets/svg/license_info.json index 41272b2d5..560b9d143 100644 --- a/assets/svg/license_info.json +++ b/assets/svg/license_info.json @@ -606,5 +606,11 @@ "path": "plus-zoom.svg", "license": "CC0", "sources": [] + }, + { + "authors": ["Hannah Declerck"], + "path": "filter.svg", + "license": "CC0", + "sources": [] } ] \ No newline at end of file