Merge branch 'project/natuurpunt' of https://github.com/pietervdvn/MapComplete into project/natuurpunt

This commit is contained in:
karelleketers 2021-07-20 16:23:56 +02:00
commit 9b2e93ddb0
7 changed files with 460 additions and 245 deletions

View file

@ -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(
@ -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

612
State.ts
View file

@ -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<LayoutConfig>(undefined);
public readonly layoutToUse = new UIEventSource<LayoutConfig>(undefined);
/**
/**
The mapping from id -> UIEventSource<properties>
*/
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<L.Map>(undefined);
/**
* Background layer id
*/
public availableBackgroundLayers: UIEventSource<BaseLayer[]>;
/**
public leafletMap = new UIEventSource<L.Map>(undefined);
/**
* Background layer id
*/
public availableBackgroundLayers: UIEventSource<BaseLayer[]>;
/**
The user credentials
*/
public osmConnection: OsmConnection;
public osmConnection: OsmConnection;
public mangroveIdentity: MangroveIdentity;
public mangroveIdentity: MangroveIdentity;
public favouriteLayers: UIEventSource<string[]>;
public favouriteLayers: UIEventSource<string[]>;
public layerUpdater: OverpassFeatureSource;
public layerUpdater: OverpassFeatureSource;
public osmApiFeatureSource : OsmApiFeatureSource ;
public osmApiFeatureSource: OsmApiFeatureSource;
public filteredLayers: UIEventSource<
{
readonly isDisplayed: UIEventSource<boolean>;
readonly layerDef: LayerConfig;
}[]
> = new UIEventSource<
{
readonly isDisplayed: UIEventSource<boolean>;
readonly layerDef: LayerConfig;
}[]
>([]);
public filteredLayers: UIEventSource<{
readonly isDisplayed: UIEventSource<boolean>,
readonly layerDef: LayerConfig;
}[]> = new UIEventSource<{
readonly isDisplayed: UIEventSource<boolean>,
readonly layerDef: LayerConfig;
}[]>([])
/**
/**
The latest element that was selected
*/
public readonly selectedElement = new UIEventSource<any>(undefined, "Selected element")
public readonly selectedElement = new UIEventSource<any>(
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<Map<string, {role: string, relation: Relation}[]>>(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<string, { role: string; relation: Relation }[]>
>(undefined, "Relation memberships");
public readonly featureSwitchUserbadge: UIEventSource<boolean>;
public readonly featureSwitchSearch: UIEventSource<boolean>;
public readonly featureSwitchLayers: UIEventSource<boolean>;
public readonly featureSwitchAddNew: UIEventSource<boolean>;
public readonly featureSwitchWelcomeMessage: UIEventSource<boolean>;
public readonly featureSwitchIframe: UIEventSource<boolean>;
public readonly featureSwitchMoreQuests: UIEventSource<boolean>;
public readonly featureSwitchShareScreen: UIEventSource<boolean>;
public readonly featureSwitchGeolocation: UIEventSource<boolean>;
public readonly featureSwitchIsTesting: UIEventSource<boolean>;
public readonly featureSwitchIsDebugging: UIEventSource<boolean>;
public readonly featureSwitchShowAllQuestions: UIEventSource<boolean>;
public readonly featureSwitchApiURL: UIEventSource<string>;
public readonly featureSwitchUserbadge: UIEventSource<boolean>;
public readonly featureSwitchSearch: UIEventSource<boolean>;
public readonly featureSwitchLayers: UIEventSource<boolean>;
public readonly featureSwitchAddNew: UIEventSource<boolean>;
public readonly featureSwitchWelcomeMessage: UIEventSource<boolean>;
public readonly featureSwitchIframe: UIEventSource<boolean>;
public readonly featureSwitchMoreQuests: UIEventSource<boolean>;
public readonly featureSwitchShareScreen: UIEventSource<boolean>;
public readonly featureSwitchGeolocation: UIEventSource<boolean>;
public readonly featureSwitchIsTesting: UIEventSource<boolean>;
public readonly featureSwitchIsDebugging: UIEventSource<boolean>;
public readonly featureSwitchShowAllQuestions: UIEventSource<boolean>;
public readonly featureSwitchApiURL: UIEventSource<string>;
public readonly featureSwitchFilter: UIEventSource<boolean>;
/**
* The map location: currently centered lat, lon and zoom
*/
public readonly locationControl = new UIEventSource<Loc>(undefined);
public backgroundLayer;
public readonly backgroundLayerId: UIEventSource<string>;
/**
* The map location: currently centered lat, lon and zoom
*/
public readonly locationControl = new UIEventSource<Loc>(undefined);
public backgroundLayer;
public readonly backgroundLayerId: UIEventSource<string>;
/* 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<boolean> =
QueryParameters.GetQueryParameter("layer-control-toggle", "false", "Whether or not the layer control is shown")
.map<boolean>((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<number>(
str => isNaN(Number(str)) ? 0 : Number(str), [], n => "" + n
public layerControlIsOpened: UIEventSource<boolean> =
QueryParameters.GetQueryParameter(
"layer-control-toggle",
"false",
"Whether or not the layer control is shown"
).map<boolean>(
(str) => str !== "false",
[],
(b) => "" + b
);
constructor(layoutToUse: LayoutConfig) {
const self = this;
public FilterIsOpened: UIEventSource<boolean> =
QueryParameters.GetQueryParameter(
"filter-toggle",
"false",
"Whether or not the filter is shown"
).map<boolean>(
(str) => str !== "false",
[],
(b) => "" + b
);
this.layoutToUse.setData(layoutToUse);
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<number>(
(str) => (isNaN(Number(str)) ? 0 : Number(str)),
[],
(n) => "" + n
);
// -- 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")));
constructor(layoutToUse: LayoutConfig) {
const self = this;
this.layoutToUse.setData(layoutToUse);
this.locationControl = new UIEventSource<Loc>({
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);
});
// -- 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"))
);
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();
});
}
// Helper function to initialize feature switches
function featSw(key: string, deflt: (layout: LayoutConfig) => boolean, documentation: string): UIEventSource<boolean> {
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<Loc>({
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<string>): UIEventSource<number> {
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<boolean> {
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<string>): UIEventSource<number> {
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);
}
);
}
}

12
Svg.ts
View file

@ -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 = "<svg width=\"16\" height=\"16\" viewBox=\"0 0 16 16\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\"> <path d=\"M16 7H3.83L9.42 1.41L8 0L0 8L8 16L9.41 14.59L3.83 9H16V7Z\" fill=\"#007759\"/> </svg> "
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 = " <!-- Created with Inkscape (http://www.inkscape.org/) --> <svg xmlns:dc=\"http://purl.org/dc/elements/1.1/\" xmlns:cc=\"http://creativecommons.org/ns#\" xmlns:rdf=\"http://www.w3.org/1999/02/22-rdf-syntax-ns#\" xmlns:svg=\"http://www.w3.org/2000/svg\" xmlns=\"http://www.w3.org/2000/svg\" xmlns:sodipodi=\"http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd\" xmlns:inkscape=\"http://www.inkscape.org/namespaces/inkscape\" width=\"100\" height=\"100\" viewBox=\"0 0 26.458333 26.458334\" version=\"1.1\" id=\"svg8\" sodipodi:docname=\"arrow-right-smooth.svg\" inkscape:version=\"0.92.4 (5da689c313, 2019-01-14)\"> <defs id=\"defs2\" /> <sodipodi:namedview id=\"base\" pagecolor=\"#ffffff\" bordercolor=\"#666666\" borderopacity=\"1.0\" inkscape:pageopacity=\"0.0\" inkscape:pageshadow=\"2\" inkscape:zoom=\"4\" inkscape:cx=\"-22.237738\" inkscape:cy=\"36.323203\" inkscape:document-units=\"px\" inkscape:current-layer=\"layer1\" showgrid=\"false\" units=\"px\" showguides=\"true\" inkscape:guide-bbox=\"true\" inkscape:window-width=\"1920\" inkscape:window-height=\"1001\" inkscape:window-x=\"0\" inkscape:window-y=\"0\" inkscape:window-maximized=\"1\"> <sodipodi:guide position=\"13.229167,23.859748\" orientation=\"1,0\" id=\"guide815\" inkscape:locked=\"false\" /> <sodipodi:guide position=\"14.944824,13.229167\" orientation=\"0,1\" id=\"guide817\" inkscape:locked=\"false\" /> </sodipodi:namedview> <metadata id=\"metadata5\"> <rdf:RDF> <cc:Work rdf:about=\"\"> <dc:format>image/svg+xml</dc:format> <dc:type rdf:resource=\"http://purl.org/dc/dcmitype/StillImage\" /> <dc:title /> </cc:Work> </rdf:RDF> </metadata> <g inkscape:label=\"Layer 1\" inkscape:groupmode=\"layer\" id=\"layer1\" transform=\"translate(0,-270.54165)\"> <path style=\"fill: none !important;stroke:#ffffff;stroke-width:3.59588718;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1\" d=\"m 6.3128214,273.41335 c 0,0 13.7995296,7.53922 13.8484366,10.36091 0.04891,2.82169 -13.8484366,10.38607 -13.8484366,10.38607\" id=\"path821\" inkscape:connector-curvature=\"0\" /> </g> </svg> "
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 = "<svg width=\"14\" height=\"16\" viewBox=\"0 0 14 16\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\"> <path d=\"M8.71478 7.99533V15.0032C8.73645 15.138 8.7246 15.2762 8.68028 15.4051C8.63597 15.534 8.56056 15.6497 8.46103 15.7414C8.29711 15.907 8.07541 16 7.84428 16C7.61315 16 7.39145 15.907 7.22753 15.7414L5.47778 13.9627C5.38296 13.8675 5.31072 13.7513 5.26675 13.6234C5.22277 13.4954 5.2082 13.3591 5.22403 13.2245V8.00402L0.18473 1.44083C0.0426575 1.25543 -0.0214882 1.02037 0.0063802 0.78707C0.0342486 0.553773 0.151904 0.341292 0.333498 0.195845C0.487423 0.0703057 0.678576 0.00116361 0.875917 0H13.1242C13.3215 0.00116361 13.5127 0.0703057 13.6666 0.195845C13.8482 0.341292 13.9658 0.553773 13.9936 0.78707C14.0215 1.02037 13.9573 1.25543 13.8153 1.44083L8.77597 8.00402L8.71478 7.99533Z\" fill=\"white\"/> </svg> "
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 = " <svg xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" width=\"48\" height=\"48\"> <defs> <linearGradient id=\"d\"> <stop offset=\"0\"/> <stop offset=\"1\" stop-opacity=\"0\"/> </linearGradient> <linearGradient id=\"c\"> <stop offset=\"0\" stop-color=\"#858585\"/> <stop offset=\".5\" stop-color=\"#cbcbcb\"/> <stop offset=\"1\" stop-color=\"#6b6b6b\"/> </linearGradient> <linearGradient id=\"b\"> <stop offset=\"0\" stop-color=\"#fff\"/> <stop offset=\"1\" stop-color=\"#fff\" stop-opacity=\"0\"/> </linearGradient> <linearGradient id=\"a\"> <stop offset=\"0\" stop-color=\"#1e2d69\"/> <stop offset=\"1\" stop-color=\"#78a7e0\"/> </linearGradient> <linearGradient id=\"f\" x1=\"40.885\" x2=\"16.88\" y1=\"71.869\" y2=\"-.389\" gradientTransform=\"matrix(.97661 0 0 1.13979 .564 -3.271)\" gradientUnits=\"userSpaceOnUse\" xlink:href=\"#a\"/> <linearGradient id=\"g\" x1=\"13.784\" x2=\"33.075\" y1=\"-.997\" y2=\"55.702\" gradientTransform=\"matrix(.98543 0 0 1.14818 .641 -2.934)\" gradientUnits=\"userSpaceOnUse\" xlink:href=\"#b\"/> <linearGradient id=\"h\" x1=\"20.125\" x2=\"28.563\" y1=\"21.844\" y2=\"42.469\" gradientTransform=\"matrix(1.0677 0 0 1.12153 -1.369 -5.574)\" gradientUnits=\"userSpaceOnUse\" xlink:href=\"#c\"/> <radialGradient id=\"e\" cx=\"24.313\" cy=\"41.156\" r=\"22.875\" fx=\"24.313\" fy=\"41.156\" gradientTransform=\"matrix(1 0 0 .26913 0 30.08)\" gradientUnits=\"userSpaceOnUse\" xlink:href=\"#d\"/> </defs> <path fill=\"url(#e)\" d=\"M47.188 41.156a22.875 6.156 0 1 1-45.75 0 22.875 6.156 0 1 1 45.75 0z\" opacity=\".506\" transform=\"matrix(.91803 0 0 .98122 1.68 .648)\"/> <path fill=\"url(#f)\" stroke=\"#25375f\" stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M4.558 3.568h38.89c.59 0 1.064.474 1.064 1.063v37.765c0 .59-.475 1.064-1.064 1.064H6.558l-3.064-3.064V4.631a1.06 1.06 0 0 1 1.064-1.063z\"/> <path fill=\"#fff\" d=\"M9 4h30v23H9z\"/> <rect width=\"30\" height=\"4\" x=\"9\" y=\"4\" fill=\"#d31c00\" rx=\".126\" ry=\".126\"/> <rect width=\"2\" height=\"2\" x=\"6\" y=\"6\" opacity=\".739\" rx=\".126\" ry=\".126\"/> <path stroke=\"#000\" d=\"M11 12.5h26m-26 5h26m-26 5h26\" opacity=\".131\"/> <path fill=\"none\" stroke=\"url(#g)\" stroke-linecap=\"round\" d=\"M4.619 4.528h38.768c.07 0 .127.056.127.126v37.648c0 .07-.057.126-.127.126H6.928l-2.435-2.391V4.654c0-.07.056-.126.126-.126z\" opacity=\".597\"/> <path fill=\"url(#h)\" stroke=\"#525252\" d=\"M14.114 28.562h19.75c.888 0 1.603.751 1.603 1.684v13.201H12.51V30.246c0-.933.715-1.684 1.603-1.684z\"/> <rect width=\"5.03\" height=\"10.066\" x=\"16.464\" y=\"30.457\" fill=\"#4967a2\" stroke=\"#525252\" rx=\".751\" ry=\".751\"/> </svg>"
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};}

View file

@ -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<boolean>) {
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", "<br/>", filterPanel]);
}
return filterPanel;
}
}

View file

@ -0,0 +1,3 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M16 7H3.83L9.42 1.41L8 0L0 8L8 16L9.41 14.59L3.83 9H16V7Z" fill="#007759"/>
</svg>

After

Width:  |  Height:  |  Size: 188 B

3
assets/svg/filter.svg Normal file
View file

@ -0,0 +1,3 @@
<svg width="14" height="16" viewBox="0 0 14 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M8.71478 7.99533V15.0032C8.73645 15.138 8.7246 15.2762 8.68028 15.4051C8.63597 15.534 8.56056 15.6497 8.46103 15.7414C8.29711 15.907 8.07541 16 7.84428 16C7.61315 16 7.39145 15.907 7.22753 15.7414L5.47778 13.9627C5.38296 13.8675 5.31072 13.7513 5.26675 13.6234C5.22277 13.4954 5.2082 13.3591 5.22403 13.2245V8.00402L0.18473 1.44083C0.0426575 1.25543 -0.0214882 1.02037 0.0063802 0.78707C0.0342486 0.553773 0.151904 0.341292 0.333498 0.195845C0.487423 0.0703057 0.678576 0.00116361 0.875917 0H13.1242C13.3215 0.00116361 13.5127 0.0703057 13.6666 0.195845C13.8482 0.341292 13.9658 0.553773 13.9936 0.78707C14.0215 1.02037 13.9573 1.25543 13.8153 1.44083L8.77597 8.00402L8.71478 7.99533Z" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 813 B

View file

@ -606,5 +606,11 @@
"path": "plus-zoom.svg",
"license": "CC0",
"sources": []
},
{
"authors": ["Hannah Declerck"],
"path": "filter.svg",
"license": "CC0",
"sources": []
}
]