Add custom theme for advanced users
This commit is contained in:
parent
004eead4ee
commit
9c42839f01
44 changed files with 635 additions and 326 deletions
|
@ -11,12 +11,14 @@ import {StreetWidth} from "./Layouts/StreetWidth";
|
|||
import {Natuurpunt} from "./Layouts/Natuurpunt";
|
||||
import {ClimbingTrees} from "./Layouts/ClimbingTrees";
|
||||
import {Smoothness} from "./Layouts/Smoothness";
|
||||
import {LayerDefinition} from "./LayerDefinition";
|
||||
import {CustomLayers} from "../Logic/CustomLayers";
|
||||
|
||||
export class AllKnownLayouts {
|
||||
public static allSets = AllKnownLayouts.AllLayouts();
|
||||
|
||||
private static AllLayouts(): Map<string, Layout> {
|
||||
const layouts: Layout[] = [
|
||||
public static allLayers: Map<string, LayerDefinition> = undefined;
|
||||
|
||||
public static layoutsList: Layout[] = [
|
||||
new Groen(),
|
||||
new GRB(),
|
||||
new Cyclofix(),
|
||||
|
@ -27,39 +29,35 @@ export class AllKnownLayouts {
|
|||
new Natuurpunt(),
|
||||
new ClimbingTrees(),
|
||||
new Artworks(),
|
||||
new Smoothness()
|
||||
new Smoothness(),
|
||||
new CustomLayers()
|
||||
/*new Toilets(),
|
||||
*/
|
||||
];
|
||||
|
||||
public static allSets: Map<string, Layout> = AllKnownLayouts.AllLayouts();
|
||||
|
||||
private static AllLayouts(): Map<string, Layout> {
|
||||
|
||||
const all = new All();
|
||||
const knownKeys = []
|
||||
for (const layout of layouts) {
|
||||
this.allLayers = new Map<string, LayerDefinition>();
|
||||
for (const layout of this.layoutsList) {
|
||||
for (const layer of layout.layers) {
|
||||
const key = layer.overpassFilter.asOverpass().join("");
|
||||
if (knownKeys.indexOf(key) >= 0) {
|
||||
const key = layer.id;
|
||||
if (this.allLayers[layer.id] !== undefined) {
|
||||
continue;
|
||||
}
|
||||
knownKeys.push(key);
|
||||
this.allLayers[layer.id] = layer;
|
||||
all.layers.push(layer);
|
||||
}
|
||||
}
|
||||
layouts.push(all)
|
||||
|
||||
const allSets: Map<string, Layout> = new Map();
|
||||
for (const layout of layouts) {
|
||||
for (const layout of this.layoutsList) {
|
||||
allSets[layout.name] = layout;
|
||||
}
|
||||
allSets[all.name] = all;
|
||||
return allSets;
|
||||
}
|
||||
|
||||
public static GetSets(layoutNames): any {
|
||||
const all = new All();
|
||||
for (const name of layoutNames) {
|
||||
all.layers = all.layers.concat(AllKnownLayouts.allSets[name].layers);
|
||||
}
|
||||
|
||||
return all;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -51,6 +51,7 @@ export class LayerDefinition {
|
|||
* ])
|
||||
*/
|
||||
overpassFilter: TagsFilter;
|
||||
public readonly id: string;
|
||||
|
||||
/**
|
||||
* This UIElement is rendered as title element in the popup
|
||||
|
@ -90,7 +91,7 @@ export class LayerDefinition {
|
|||
static WAYHANDLING_CENTER_ONLY = 1;
|
||||
static WAYHANDLING_CENTER_AND_WAY = 2;
|
||||
|
||||
constructor(options: {
|
||||
constructor(id: string, options: {
|
||||
name: string,
|
||||
description: string | UIElement,
|
||||
presets: {
|
||||
|
@ -111,6 +112,7 @@ export class LayerDefinition {
|
|||
icon: any
|
||||
}
|
||||
} = undefined) {
|
||||
this.id = id;
|
||||
if (options === undefined) {
|
||||
return;
|
||||
}
|
||||
|
@ -127,6 +129,4 @@ export class LayerDefinition {
|
|||
this.wayHandling = options.wayHandling ?? LayerDefinition.WAYHANDLING_DEFAULT;
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
|
@ -10,7 +10,7 @@ import FixedText from "../Questions/FixedText";
|
|||
export class Artwork extends LayerDefinition {
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
super("artwork");
|
||||
this.name = "artwork";
|
||||
const t = Translations.t.artwork;
|
||||
this.title = t.title;
|
||||
|
|
|
@ -16,7 +16,7 @@ export default class BikeCafes extends LayerDefinition {
|
|||
private readonly to = Translations.t.cyclofix.cafe
|
||||
|
||||
constructor() {
|
||||
super()
|
||||
super("bikecafe")
|
||||
this.name = this.to.name
|
||||
this.icon = "./assets/bike/cafe.svg"
|
||||
this.overpassFilter = new And([
|
||||
|
|
|
@ -20,7 +20,7 @@ export default class BikeOtherShops extends LayerDefinition {
|
|||
private readonly to = Translations.t.cyclofix.nonBikeShop
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
super("bikeOtherShop");
|
||||
this.name = this.to.name
|
||||
this.icon = "./assets/bike/non_bike_repair_shop.svg"
|
||||
this.overpassFilter = new And([
|
||||
|
|
|
@ -15,7 +15,7 @@ export default class BikeParkings extends LayerDefinition {
|
|||
private readonly accessCargoDesignated = new Tag("cargo_bike", "designated");
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
super("bikeparking");
|
||||
this.name = Translations.t.cyclofix.parking.name;
|
||||
this.icon = "./assets/bike/parking.svg";
|
||||
this.overpassFilter = new Tag("amenity", "bicycle_parking");
|
||||
|
|
|
@ -21,7 +21,7 @@ export default class BikeShops extends LayerDefinition {
|
|||
private readonly repairsBikes = new Tag("service:bicycle:repair", "yes")
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
super("bikeshop");
|
||||
this.name = Translations.t.cyclofix.shop.name
|
||||
this.icon = "./assets/bike/repair_shop.svg"
|
||||
this.overpassFilter = new Tag("shop", "bicycle");
|
||||
|
|
|
@ -27,7 +27,7 @@ export default class BikeStations extends LayerDefinition {
|
|||
private readonly to = Translations.t.cyclofix.station
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
super("bikestation");
|
||||
this.name = Translations.t.cyclofix.station.name;
|
||||
this.icon = "./assets/bike/repair_station_pump.svg";
|
||||
|
||||
|
|
|
@ -10,7 +10,7 @@ export class Birdhide extends LayerDefinition {
|
|||
|
||||
|
||||
constructor() {
|
||||
super({
|
||||
super("birdhide",{
|
||||
name: "vogelkijkplaats",
|
||||
description: "Een plaats om vogels te kijken, zoals een vogelkijkhut of kijkwand",
|
||||
overpassFilter: Birdhide.birdhide,
|
||||
|
|
|
@ -9,7 +9,7 @@ import T from "../../UI/i18n/Translation";
|
|||
export class Bookcases extends LayerDefinition {
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
super("bookcases");
|
||||
|
||||
this.name = "boekenkast";
|
||||
this.presets = [{
|
||||
|
|
|
@ -10,7 +10,7 @@ import {ImageCarouselWithUploadConstructor} from "../../UI/Image/ImageCarouselWi
|
|||
export class Bos extends LayerDefinition {
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
super("bos");
|
||||
this.name = "Bos";
|
||||
this.icon = "";
|
||||
|
||||
|
|
|
@ -8,7 +8,7 @@ export class ClimbingTree extends LayerDefinition {
|
|||
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
super("climbingtree");
|
||||
const t = Translations.t.climbingTrees.layer;
|
||||
this.title = new FixedText(t.title);
|
||||
const icon = "assets/walkbybrussels/tree.svg";
|
||||
|
|
|
@ -10,7 +10,7 @@ import Translations from "../../UI/i18n/Translations";
|
|||
export class DrinkingWater extends LayerDefinition {
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
super("drinkingwater");
|
||||
this.name = Translations.t.cyclofix.drinking_water.title;
|
||||
this.icon = "./assets/bike/drinking_water.svg";
|
||||
|
||||
|
|
|
@ -8,7 +8,7 @@ import L from "leaflet";
|
|||
|
||||
export class GhostBike extends LayerDefinition {
|
||||
constructor() {
|
||||
super();
|
||||
super("ghost bike");
|
||||
this.name = "ghost bike";
|
||||
this.overpassFilter = new Tag("memorial", "ghost_bike")
|
||||
this.title = new FixedText("Ghost bike");
|
||||
|
|
|
@ -5,7 +5,7 @@ import {TagRenderingOptions} from "../TagRendering";
|
|||
export class GrbToFix extends LayerDefinition {
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
super("grb");
|
||||
|
||||
this.name = "grb";
|
||||
this.presets = [];
|
||||
|
|
|
@ -6,7 +6,7 @@ import {And, Tag} from "../../Logic/TagsFilter";
|
|||
|
||||
export class InformationBoard extends LayerDefinition {
|
||||
constructor() {
|
||||
super({
|
||||
super("informationBoard",{
|
||||
name: "Informatiebord",
|
||||
description: "Een informatiebord of kaart",
|
||||
minzoom: 12,
|
||||
|
|
|
@ -6,7 +6,7 @@ import {And, Tag} from "../../Logic/TagsFilter";
|
|||
|
||||
export class Map extends LayerDefinition {
|
||||
constructor() {
|
||||
super();
|
||||
super("map");
|
||||
this.name = "Map";
|
||||
this.title = new FixedText("Map");
|
||||
this.minzoom = 12;
|
||||
|
|
|
@ -11,7 +11,7 @@ import {ImageCarouselWithUploadConstructor} from "../../UI/Image/ImageCarouselWi
|
|||
export class NatureReserves extends LayerDefinition {
|
||||
|
||||
constructor(moreQuests: boolean = false) {
|
||||
super();
|
||||
super("natureReserve");
|
||||
this.name = "Natuurgebied";
|
||||
this.icon = "";
|
||||
this.overpassFilter =
|
||||
|
|
|
@ -45,7 +45,7 @@ export class Park extends LayerDefinition {
|
|||
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
super("park");
|
||||
this.name = "Park";
|
||||
this.icon = undefined;
|
||||
this.overpassFilter =
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import {LayerDefinition} from "../LayerDefinition";
|
||||
import {Quests} from "../../Quests";
|
||||
import {FixedUiElement} from "../../UI/Base/FixedUiElement";
|
||||
import L from "leaflet";
|
||||
import {Tag} from "../../Logic/TagsFilter";
|
||||
|
@ -7,7 +6,7 @@ import {Tag} from "../../Logic/TagsFilter";
|
|||
export class Toilets extends LayerDefinition{
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
super("toilets");
|
||||
|
||||
this.name="toilet";
|
||||
this.newElementTags = [new Tag( "amenity", "toilets")];
|
||||
|
|
|
@ -8,7 +8,7 @@ import {TagRenderingOptions} from "../TagRendering";
|
|||
export class Viewpoint extends LayerDefinition {
|
||||
|
||||
constructor() {
|
||||
super({
|
||||
super("viewpoint",{
|
||||
name: "Bezienswaardigheid",
|
||||
description: "Wil je een foto toevoegen van iets dat geen park, bos of natuurgebied is? Dit kan hiermee",
|
||||
presets: [{
|
||||
|
|
|
@ -113,7 +113,7 @@ export class Widths extends LayerDefinition {
|
|||
constructor(carWidth: number,
|
||||
cyclistWidth: number,
|
||||
pedestrianWidth: number) {
|
||||
super();
|
||||
super("width");
|
||||
this.carWidth = carWidth;
|
||||
this.cyclistWidth = cyclistWidth;
|
||||
this.pedestrianWidth = pedestrianWidth;
|
||||
|
|
93
Helpers.ts
93
Helpers.ts
|
@ -1,93 +0,0 @@
|
|||
import {UIEventSource} from "./UI/UIEventSource";
|
||||
import {Changes} from "./Logic/Osm/Changes";
|
||||
import {State} from "./State";
|
||||
|
||||
export class Helpers {
|
||||
|
||||
static DoEvery(millis: number, f: (() => void)) {
|
||||
window.setTimeout(
|
||||
function () {
|
||||
f();
|
||||
Helpers.DoEvery(millis, f);
|
||||
}
|
||||
, millis)
|
||||
}
|
||||
|
||||
|
||||
static SetupAutoSave() {
|
||||
|
||||
const changes = State.state.changes;
|
||||
const millisTillChangesAreSaved = State.state.secondsTillChangesAreSaved;
|
||||
const saveAfterXMillis = State.state.secondsTillChangesAreSaved.data * 1000;
|
||||
changes.pendingChangesES.addCallback(function () {
|
||||
|
||||
var c = changes.pendingChangesES.data;
|
||||
if (c > 10) {
|
||||
millisTillChangesAreSaved.setData(0);
|
||||
changes.uploadAll(undefined);
|
||||
return;
|
||||
}
|
||||
|
||||
if (c > 0) {
|
||||
millisTillChangesAreSaved.setData(saveAfterXMillis);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
millisTillChangesAreSaved.addCallback((time) => {
|
||||
if (time <= 0 && changes.pendingChangesES.data > 0) {
|
||||
changes.uploadAll(undefined);
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
Helpers.DoEvery(
|
||||
1000,
|
||||
() => {
|
||||
millisTillChangesAreSaved
|
||||
.setData(millisTillChangesAreSaved.data - 1000)
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Registers an action that:
|
||||
* -> Upload everything to OSM
|
||||
* -> Asks the user not to close. The 'not to close' dialog should profide enough time to upload
|
||||
* -> WHen uploading is done, the window is closed anyway
|
||||
*/
|
||||
static LastEffortSave() {
|
||||
const changes = State.state.changes;
|
||||
window.addEventListener("beforeunload", function (e) {
|
||||
// Quickly save everyting!
|
||||
if (changes.pendingChangesES.data == 0) {
|
||||
return "";
|
||||
}
|
||||
|
||||
changes.uploadAll(function () {
|
||||
window.close()
|
||||
});
|
||||
var confirmationMessage = "Nog even geduld - je laatset wijzigingen worden opgeslaan!";
|
||||
|
||||
(e || window.event).returnValue = confirmationMessage; //Gecko + IE
|
||||
return confirmationMessage; //Webkit, Safari, Chrome
|
||||
});
|
||||
|
||||
|
||||
document.addEventListener('visibilitychange',() => {
|
||||
if(document.visibilityState === "visible"){
|
||||
return;
|
||||
}
|
||||
if (changes.pendingChangesES.data == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
console.log("Upmoading: loss of focus")
|
||||
changes.uploadAll(function () {
|
||||
window.close()
|
||||
});
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -16,9 +16,13 @@ import {ElementStorage} from "./Logic/ElementStorage";
|
|||
import {Preset} from "./UI/SimpleAddUI";
|
||||
import {Changes} from "./Logic/Osm/Changes";
|
||||
import {OsmConnection} from "./Logic/Osm/OsmConnection";
|
||||
import {Basemap} from "./Logic/Leaflet/Basemap";
|
||||
import {BaseLayers, Basemap} from "./Logic/Leaflet/Basemap";
|
||||
import {State} from "./State";
|
||||
import {WelcomeMessage} from "./UI/WelcomeMessage";
|
||||
import {Img} from "./UI/Img";
|
||||
import {DropDown} from "./UI/Input/DropDown";
|
||||
import {LayerSelection} from "./UI/LayerSelection";
|
||||
import {CustomLayersPanel} from "./Logic/CustomLayersPanel";
|
||||
|
||||
export class InitUiElements {
|
||||
|
||||
|
@ -47,7 +51,8 @@ export class InitUiElements {
|
|||
{header: `<img src='${layoutToUse.icon}'>`, content: welcome},
|
||||
{header: `<img src='${'./assets/osm-logo.svg'}'>`, content: Translations.t.general.openStreetMapIntro},
|
||||
{header: `<img src='${'./assets/share.svg'}'>`, content: new ShareScreen()},
|
||||
{header: `<img src='${'./assets/add.svg'}'>`, content: new MoreScreen()}
|
||||
{header: `<img src='${'./assets/add.svg'}'>`, content: new MoreScreen()},
|
||||
{header: `<img src='${'./assets/star.svg'}'>`, content: new CustomLayersPanel()},
|
||||
])
|
||||
|
||||
return fullOptions;
|
||||
|
@ -89,14 +94,38 @@ export class InitUiElements {
|
|||
|
||||
}
|
||||
|
||||
static InitLayerSelection(layerSetup) {
|
||||
const closedFilterButton = `<button id="filter__button" class="filter__button shadow">${Img.closedFilterButton}</button>`;
|
||||
|
||||
const openFilterButton = `<button id="filter__button" class="filter__button">${Img.openFilterButton}</button>`;
|
||||
|
||||
let baseLayerOptions = BaseLayers.baseLayers.map((layer) => {
|
||||
return {value: layer, shown: layer.name}
|
||||
});
|
||||
const backgroundMapPicker = new Combine([new DropDown(`Background map`, baseLayerOptions, State.state.bm.CurrentLayer), openFilterButton]);
|
||||
const layerSelection = new Combine([`<p class="filter__label">Maplayers</p>`, new LayerSelection(layerSetup.flayers)]);
|
||||
let layerControl = backgroundMapPicker;
|
||||
if (layerSetup.flayers.length > 1) {
|
||||
layerControl = new Combine([layerSelection, backgroundMapPicker]);
|
||||
}
|
||||
|
||||
InitUiElements.OnlyIf(State.state.featureSwitchLayers, () => {
|
||||
|
||||
const checkbox = new CheckBox(layerControl, closedFilterButton);
|
||||
checkbox.AttachTo("filter__selection");
|
||||
State.state.bm.Location.addCallback(() => {
|
||||
checkbox.isEnabled.setData(false);
|
||||
});
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
static InitLayers(): {
|
||||
minZoom: number
|
||||
flayers: FilteredLayer[],
|
||||
presets: Preset[]
|
||||
} {
|
||||
const addButtons: Preset[]
|
||||
= [];
|
||||
const addButtons: Preset[] = [];
|
||||
|
||||
const flayers: FilteredLayer[] = []
|
||||
|
||||
|
|
26
Logic/CustomLayers.ts
Normal file
26
Logic/CustomLayers.ts
Normal file
|
@ -0,0 +1,26 @@
|
|||
import {Layout} from "../Customizations/Layout";
|
||||
import Translations from "../UI/i18n/Translations";
|
||||
|
||||
export class CustomLayers extends Layout {
|
||||
|
||||
public static NAME: string = "personal";
|
||||
|
||||
constructor() {
|
||||
super(
|
||||
CustomLayers.NAME,
|
||||
["en"],
|
||||
Translations.t.favourite.title,
|
||||
[],
|
||||
12,
|
||||
0,
|
||||
0,
|
||||
Translations.t.favourite.description,
|
||||
);
|
||||
|
||||
this.icon = "./assets/star.svg"
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
96
Logic/CustomLayersPanel.ts
Normal file
96
Logic/CustomLayersPanel.ts
Normal file
|
@ -0,0 +1,96 @@
|
|||
import {UIElement} from "../UI/UIElement";
|
||||
import {State} from "../State";
|
||||
import Translations from "../UI/i18n/Translations";
|
||||
import {UIEventSource} from "../UI/UIEventSource";
|
||||
import {AllKnownLayouts} from "../Customizations/AllKnownLayouts";
|
||||
import Combine from "../UI/Base/Combine";
|
||||
import {Img} from "../UI/Img";
|
||||
import {CheckBox} from "../UI/Input/CheckBox";
|
||||
import {CustomLayersState} from "./CustomLayersState";
|
||||
import {VerticalCombine} from "../UI/Base/VerticalCombine";
|
||||
import {FixedUiElement} from "../UI/Base/FixedUiElement";
|
||||
|
||||
export class CustomLayersPanel extends UIElement {
|
||||
private checkboxes: UIElement[];
|
||||
|
||||
constructor() {
|
||||
super(State.state.favourteLayers);
|
||||
this.ListenTo(State.state.osmConnection.userDetails);
|
||||
|
||||
|
||||
const t = Translations.t.favourite;
|
||||
|
||||
this.checkboxes = [];
|
||||
const controls = new Map<string, UIEventSource<boolean>>();
|
||||
const favs = State.state.favourteLayers.data;
|
||||
for (const layout of AllKnownLayouts.layoutsList) {
|
||||
|
||||
const header =
|
||||
new Combine([
|
||||
`<div class="custom-layer-panel-header-img"><img src='${layout.icon}'></div>`,
|
||||
"<span><b>",
|
||||
layout.title,
|
||||
"</b><br/>",
|
||||
layout.description ?? "",
|
||||
"</span>",
|
||||
], 'custom-layer-panel-header')
|
||||
this.checkboxes.push(header);
|
||||
|
||||
for (const layer of layout.layers) {
|
||||
const image = (layer.icon ? `<img src='${layer.icon}'>` : Img.checkmark);
|
||||
const cb = new CheckBox(
|
||||
new Combine([
|
||||
image,
|
||||
"<b>", layer.name ?? "", "</b> ", layer.description ?? ""
|
||||
]),
|
||||
new Combine([
|
||||
"<span style='opacity: 0'>",
|
||||
image, "</span>", "<b>", layer.name ?? "", "</b> ", layer.description ?? ""
|
||||
]),
|
||||
controls[layer.id] ?? (favs.indexOf(layer.id) >= 0)
|
||||
);
|
||||
cb.clss = "custom-layer-checkbox"
|
||||
controls[layer.id] = cb.isEnabled;
|
||||
|
||||
cb.isEnabled.addCallback((isEnabled) => {
|
||||
if (isEnabled) {
|
||||
CustomLayersState.AddFavouriteLayer(layer.id)
|
||||
} else {
|
||||
CustomLayersState.RemoveFavouriteLayer(layer.id);
|
||||
}
|
||||
})
|
||||
|
||||
this.checkboxes.push(cb);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
State.state.favourteLayers.addCallback((layers) => {
|
||||
for (const layerId of layers) {
|
||||
controls[layerId].setData(true);
|
||||
}
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
InnerRender(): string {
|
||||
const t = Translations.t.favourite;
|
||||
const userDetails = State.state.osmConnection.userDetails.data;
|
||||
if(!userDetails.loggedIn){
|
||||
return "";
|
||||
}
|
||||
|
||||
if(userDetails.csCount <= 100){
|
||||
return "";
|
||||
}
|
||||
|
||||
return new VerticalCombine([
|
||||
t.panelIntro,
|
||||
new FixedUiElement("<a href='./index.html?layout=personal'>GO</a>"),
|
||||
...this.checkboxes
|
||||
], "custom-layer-panel").Render();
|
||||
}
|
||||
|
||||
|
||||
}
|
94
Logic/CustomLayersState.ts
Normal file
94
Logic/CustomLayersState.ts
Normal file
|
@ -0,0 +1,94 @@
|
|||
import {State} from "../State";
|
||||
|
||||
export class CustomLayersState {
|
||||
static RemoveFavouriteLayer(layer: string) {
|
||||
|
||||
const favs = State.state.favourteLayers.data;
|
||||
const ind = favs.indexOf(layer);
|
||||
if (ind < 0) {
|
||||
return;
|
||||
}
|
||||
console.log("REmovign fav layer", layer);
|
||||
favs.splice(ind, 1);
|
||||
State.state.favourteLayers.ping();
|
||||
|
||||
const osmConnection = State.state.osmConnection;
|
||||
const count = osmConnection.GetPreference("mapcomplete-custom-layer-count");
|
||||
if (favs.length === 0) {
|
||||
count.setData("0")
|
||||
} else if (count.data === undefined || isNaN(Number(count.data))) {
|
||||
count.data = "0";
|
||||
}
|
||||
const lastId = Number(count.data);
|
||||
|
||||
for (let i = 0; i < lastId; i++) {
|
||||
const layerIDescr = osmConnection.GetPreference("mapcomplete-custom-layer-" + i);
|
||||
if (layerIDescr.data === layer) {
|
||||
// We found the value to remove - mark with a tombstone
|
||||
layerIDescr.setData("-");
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static AddFavouriteLayer(layer: string) {
|
||||
const favs = State.state.favourteLayers.data;
|
||||
const ind = favs.indexOf(layer);
|
||||
if (ind >= 0) {
|
||||
return;
|
||||
}
|
||||
console.log("Adding fav layer", layer);
|
||||
favs.push(layer);
|
||||
State.state.favourteLayers.ping();
|
||||
|
||||
|
||||
const osmConnection = State.state.osmConnection;
|
||||
const count = osmConnection.GetPreference("mapcomplete-custom-layer-count");
|
||||
if (count.data === undefined || isNaN(Number(count.data))) {
|
||||
count.data = "0";
|
||||
}
|
||||
const lastId = Number(count.data);
|
||||
|
||||
for (let i = 0; i < lastId; i++) {
|
||||
const layerIDescr = osmConnection.GetPreference("mapcomplete-custom-layer-" + i);
|
||||
if (layerIDescr.data === undefined || layerIDescr.data === "-") {
|
||||
// An earlier item was removed -> overwrite it
|
||||
layerIDescr.setData(layer);
|
||||
count.ping();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// No empty slot found -> create a new one
|
||||
const layerIDescr = osmConnection.GetPreference("mapcomplete-custom-layer-" + lastId);
|
||||
layerIDescr.setData(layer);
|
||||
count.setData((lastId + 1) + "");
|
||||
}
|
||||
|
||||
static InitFavouriteLayer() {
|
||||
const osmConnection = State.state.osmConnection;
|
||||
const count = osmConnection.GetPreference("mapcomplete-custom-layer-count");
|
||||
const favs = State.state.favourteLayers.data;
|
||||
let changed = false;
|
||||
count.addCallback((countStr) => {
|
||||
if (countStr === undefined) {
|
||||
return;
|
||||
}
|
||||
let countI = Number(countStr);
|
||||
if (isNaN(countI)) {
|
||||
countI = 999;
|
||||
}
|
||||
for (let i = 0; i < countI; i++) {
|
||||
const layerId = osmConnection.GetPreference("mapcomplete-custom-layer-" + i).data;
|
||||
if (layerId !== undefined && layerId !== "-" && favs.indexOf(layerId) < 0) {
|
||||
State.state.favourteLayers.data.push(layerId);
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
if (changed) {
|
||||
State.state.favourteLayers.ping();
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
}
|
|
@ -1,8 +1,8 @@
|
|||
import L from "leaflet";
|
||||
import {UIEventSource} from "../../UI/UIEventSource";
|
||||
import {UIElement} from "../../UI/UIElement";
|
||||
import {Helpers} from "../../Helpers";
|
||||
import {State} from "../../State";
|
||||
import {Utils} from "../../Utils";
|
||||
|
||||
export class GeoLocationHandler extends UIElement {
|
||||
|
||||
|
@ -108,7 +108,7 @@ export class GeoLocationHandler extends UIElement {
|
|||
|
||||
if (!self._isActive.data) {
|
||||
self._isActive.setData(true);
|
||||
Helpers.DoEvery(60000, () => {
|
||||
Utils.DoEvery(60000, () => {
|
||||
|
||||
if (document.visibilityState !== "visible") {
|
||||
console.log("Not starting gps: document not visible")
|
||||
|
|
|
@ -8,6 +8,7 @@ import {OsmNode, OsmObject} from "./OsmObject";
|
|||
import {And, Tag, TagsFilter} from "../TagsFilter";
|
||||
import {ElementStorage} from "../ElementStorage";
|
||||
import {State} from "../../State";
|
||||
import {Utils} from "../../Utils";
|
||||
|
||||
export class Changes {
|
||||
|
||||
|
@ -22,9 +23,11 @@ export class Changes {
|
|||
|
||||
constructor(
|
||||
changesetComment: string,
|
||||
login: OsmConnection,
|
||||
allElements: ElementStorage) {
|
||||
state: State) {
|
||||
this._changesetComment = changesetComment;
|
||||
|
||||
this.SetupAutoSave(state);
|
||||
this.LastEffortSave();
|
||||
}
|
||||
|
||||
addTag(elementId: string, tagsFilter : TagsFilter){
|
||||
|
@ -52,7 +55,6 @@ export class Changes {
|
|||
* @param value
|
||||
*/
|
||||
addChange(elementId: string, key: string, value: string) {
|
||||
console.log("Received change",key, value)
|
||||
if (key === undefined || key === null) {
|
||||
console.log("Invalid key");
|
||||
return;
|
||||
|
@ -257,4 +259,77 @@ console.log("Received change",key, value)
|
|||
});
|
||||
}
|
||||
|
||||
/*
|
||||
* Registers an action that:
|
||||
* -> Upload everything to OSM
|
||||
* -> Asks the user not to close. The 'not to close' dialog should profide enough time to upload
|
||||
* -> WHen uploading is done, the window is closed anyway
|
||||
*/
|
||||
private LastEffortSave() {
|
||||
const self = this;
|
||||
window.addEventListener("beforeunload", function (e) {
|
||||
// Quickly save everyting!
|
||||
if (self.pendingChangesES.data == 0) {
|
||||
return "";
|
||||
}
|
||||
|
||||
self.uploadAll(function () {
|
||||
window.close()
|
||||
});
|
||||
var confirmationMessage = "Nog even geduld - je laatset wijzigingen worden opgeslaan!";
|
||||
|
||||
(e || window.event).returnValue = confirmationMessage; //Gecko + IE
|
||||
return confirmationMessage; //Webkit, Safari, Chrome
|
||||
});
|
||||
|
||||
|
||||
document.addEventListener('visibilitychange', () => {
|
||||
if (document.visibilityState === "visible") {
|
||||
return;
|
||||
}
|
||||
if (this.pendingChangesES.data == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
console.log("Upmoading: loss of focus")
|
||||
this.uploadAll(function () {
|
||||
window.close()
|
||||
});
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
private SetupAutoSave(state: State) {
|
||||
|
||||
const millisTillChangesAreSaved = state.secondsTillChangesAreSaved;
|
||||
const saveAfterXMillis = state.secondsTillChangesAreSaved.data * 1000;
|
||||
this.pendingChangesES.addCallback(function () {
|
||||
|
||||
var c = this.pendingChangesES.data;
|
||||
if (c > 10) {
|
||||
millisTillChangesAreSaved.setData(0);
|
||||
this.uploadAll(undefined);
|
||||
return;
|
||||
}
|
||||
|
||||
if (c > 0) {
|
||||
millisTillChangesAreSaved.setData(saveAfterXMillis);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
millisTillChangesAreSaved.addCallback((time) => {
|
||||
if (time <= 0 && this.pendingChangesES.data > 0) {
|
||||
this.uploadAll(undefined);
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
Utils.DoEvery(
|
||||
1000,
|
||||
() => {
|
||||
millisTillChangesAreSaved
|
||||
.setData(millisTillChangesAreSaved.data - 1000)
|
||||
});
|
||||
}
|
||||
}
|
|
@ -1,6 +1,7 @@
|
|||
// @ts-ignore
|
||||
import osmAuth from "osm-auth";
|
||||
import {UIEventSource} from "../../UI/UIEventSource";
|
||||
import {CustomLayersState} from "../CustomLayersState";
|
||||
|
||||
export class UserDetails {
|
||||
|
||||
|
@ -215,6 +216,7 @@ export class OsmConnection {
|
|||
self.preferences.data[k] = v;
|
||||
}
|
||||
self.preferences.ping();
|
||||
CustomLayersState.InitFavouriteLayer();
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -52,7 +52,6 @@ export class QueryParameters {
|
|||
|
||||
public static GetQueryParameter(key: string, deflt: string): UIEventSource<string> {
|
||||
if (deflt !== undefined) {
|
||||
console.log(key, "-->", deflt)
|
||||
QueryParameters.defaults[key] = deflt;
|
||||
}
|
||||
if (QueryParameters.knownSources[key] !== undefined) {
|
||||
|
|
61
State.ts
61
State.ts
|
@ -9,6 +9,10 @@ import {ElementStorage} from "./Logic/ElementStorage";
|
|||
import {Changes} from "./Logic/Osm/Changes";
|
||||
import {Basemap} from "./Logic/Leaflet/Basemap";
|
||||
import {OsmConnection} from "./Logic/Osm/OsmConnection";
|
||||
import Locale from "./UI/i18n/Locale";
|
||||
import {VariableUiElement} from "./UI/Base/VariableUIElement";
|
||||
import Translations from "./UI/i18n/Translations";
|
||||
import {CustomLayersState} from "./Logic/CustomLayersState";
|
||||
|
||||
/**
|
||||
* Contains the global state: a bunch of UI-event sources
|
||||
|
@ -95,6 +99,11 @@ export class State {
|
|||
// After this many milliseconds without changes, saves are sent of to OSM
|
||||
public readonly saveTimeout = new UIEventSource<number>(30 * 1000);
|
||||
|
||||
/**
|
||||
* Layers can be marked as favourites, they show up in a custom layout
|
||||
*/
|
||||
public favourteLayers: UIEventSource<string[]> = new UIEventSource<string[]>([])
|
||||
|
||||
|
||||
constructor(layoutToUse: Layout) {
|
||||
this.layoutToUse = new UIEventSource<Layout>(layoutToUse);
|
||||
|
@ -130,5 +139,57 @@ export class State {
|
|||
this.featureSwitchIframe = featSw("fs-iframe", () => false);
|
||||
|
||||
|
||||
this.osmConnection = new OsmConnection(
|
||||
QueryParameters.GetQueryParameter("test", "false").data === "true",
|
||||
QueryParameters.GetQueryParameter("oauth_token", undefined)
|
||||
);
|
||||
|
||||
|
||||
Locale.language.syncWith(this.osmConnection.GetPreference("language"));
|
||||
|
||||
|
||||
Locale.language.addCallback((currentLanguage) => {
|
||||
if (layoutToUse.supportedLanguages.indexOf(currentLanguage) < 0) {
|
||||
console.log("Resetting languate to", layoutToUse.supportedLanguages[0], "as", currentLanguage, " is unsupported")
|
||||
// The current language is not supported -> switch to a supported one
|
||||
Locale.language.setData(layoutToUse.supportedLanguages[0]);
|
||||
}
|
||||
}).ping()
|
||||
|
||||
document.title = Translations.W(layoutToUse.title).InnerRender();
|
||||
Locale.language.addCallback(e => {
|
||||
document.title = Translations.W(layoutToUse.title).InnerRender();
|
||||
})
|
||||
|
||||
|
||||
this.allElements = new ElementStorage();
|
||||
this.changes = new Changes(
|
||||
"Beantwoorden van vragen met #MapComplete voor vragenset #" + this.layoutToUse.data.name,
|
||||
this);
|
||||
|
||||
|
||||
|
||||
if (document.getElementById("leafletDiv") === null) {
|
||||
console.warn("leafletDiv not found - not initializing map. Assuming test.html");
|
||||
return;
|
||||
}
|
||||
|
||||
this.bm = new Basemap("leafletDiv", this.locationControl, new VariableUiElement(
|
||||
this.locationControl.map((location) => {
|
||||
const mapComplete = "<a href='https://github.com/pietervdvn/MapComplete' target='_blank'>Mapcomple</a> " +
|
||||
" " +
|
||||
"<a href='https://github.com/pietervdvn/MapComplete/issues' target='_blank'><img src='./assets/bug.svg' alt='Report bug' class='small-userbadge-icon'></a>";
|
||||
let editHere = "";
|
||||
if (location !== undefined) {
|
||||
editHere = " | " +
|
||||
"<a href='https://www.openstreetmap.org/edit?editor=id#map=" + location.zoom + "/" + location.lat + "/" + location.lon + "' target='_blank'>" +
|
||||
"<img src='./assets/pencil.svg' alt='edit here' class='small-userbadge-icon'>" +
|
||||
"</a>"
|
||||
}
|
||||
return mapComplete + editHere;
|
||||
|
||||
})
|
||||
));
|
||||
|
||||
}
|
||||
}
|
|
@ -3,10 +3,12 @@ import Translations from "../i18n/Translations";
|
|||
|
||||
export default class Combine extends UIElement {
|
||||
private uiElements: (string | UIElement)[];
|
||||
private className: string = undefined;
|
||||
private clas: string = undefined;
|
||||
|
||||
constructor(uiElements: (string | UIElement)[]) {
|
||||
constructor(uiElements: (string | UIElement)[], className: string = undefined) {
|
||||
super(undefined);
|
||||
this.className = className;
|
||||
this.uiElements = uiElements;
|
||||
}
|
||||
|
||||
|
@ -19,6 +21,10 @@ export default class Combine extends UIElement {
|
|||
elements += element;
|
||||
}
|
||||
}
|
||||
if(this.className !== undefined){
|
||||
elements = `<span class='${this.className}'>${elements}</span>`;
|
||||
}
|
||||
|
||||
return elements;
|
||||
}
|
||||
|
||||
|
|
20
UI/Base/Image.ts
Normal file
20
UI/Base/Image.ts
Normal file
|
@ -0,0 +1,20 @@
|
|||
import {UIElement} from "../UIElement";
|
||||
|
||||
|
||||
export class Image extends UIElement{
|
||||
private src: string;
|
||||
private style: string = "";
|
||||
constructor(src: string, style: string = "") {
|
||||
super(undefined);
|
||||
this.style = style;
|
||||
this.src = src;
|
||||
}
|
||||
|
||||
InnerRender(): string {
|
||||
if(this.src === undefined){
|
||||
return "";
|
||||
}
|
||||
return `<img src='${this.src}' style='${this.style}'>`;
|
||||
}
|
||||
|
||||
}
|
|
@ -27,9 +27,11 @@ export class TabbedComponent extends UIElement {
|
|||
for (let i = 0; i < this.headers.length; i++) {
|
||||
let header = this.headers[i];
|
||||
|
||||
if (!this.content[i].IsEmpty()) {
|
||||
headerBar += `<div class=\'tab-single-header ${i == this._source.data ? 'tab-active' : 'tab-non-active'}\'>` +
|
||||
header.Render() + "</div>"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
headerBar = "<div class='tabs-header-bar'>" + headerBar + "</div>"
|
||||
|
|
|
@ -92,7 +92,7 @@ export class SimpleAddUI extends UIElement {
|
|||
const self = this;
|
||||
return () => {
|
||||
|
||||
const loc = State.state.bm.lastClickLocation.data;
|
||||
const loc = State.state.bm.LastClickLocation.data;
|
||||
let feature = State.state.changes.createElement(option.tags, loc.lat, loc.lon);
|
||||
option.layerToAddTo.AddNewElement(feature);
|
||||
State.state.selectedElement.setData({feature: feature});
|
||||
|
@ -107,7 +107,7 @@ export class SimpleAddUI extends UIElement {
|
|||
|
||||
if(userDetails.data.dryRun){
|
||||
this.CreatePoint(this._confirmPreset.data)();
|
||||
return;
|
||||
return "";
|
||||
}
|
||||
|
||||
return new Combine([
|
||||
|
|
|
@ -9,6 +9,7 @@ import {Basemap} from "../Logic/Leaflet/Basemap";
|
|||
import {State} from "../State";
|
||||
import {PendingChanges} from "./PendingChanges";
|
||||
import Locale from "./i18n/Locale";
|
||||
import {Utils} from "../Utils";
|
||||
|
||||
/**
|
||||
* Handles and updates the user badge
|
||||
|
@ -25,7 +26,7 @@ export class UserBadge extends UIElement {
|
|||
super(State.state.osmConnection.userDetails);
|
||||
this._userDetails = State.state.osmConnection.userDetails;
|
||||
this._pendingChanges = new PendingChanges();
|
||||
this._languagePicker = Locale.CreateLanguagePicker();
|
||||
this._languagePicker = Utils.CreateLanguagePicker();
|
||||
|
||||
this._logout = new FixedUiElement("<img src='assets/logout.svg' class='small-userbadge-icon' alt='logout'>")
|
||||
.onClick(() => {
|
||||
|
|
|
@ -6,6 +6,7 @@ import {State} from "../State";
|
|||
import {Layout} from "../Customizations/Layout";
|
||||
import Translations from "./i18n/Translations";
|
||||
import {VariableUiElement} from "./Base/VariableUIElement";
|
||||
import {Utils} from "../Utils";
|
||||
|
||||
export class WelcomeMessage extends UIElement {
|
||||
private readonly layout: Layout;
|
||||
|
@ -20,7 +21,7 @@ export class WelcomeMessage extends UIElement {
|
|||
|
||||
constructor() {
|
||||
super(State.state.osmConnection.userDetails);
|
||||
this.languagePicker = Locale.CreateLanguagePicker(Translations.t.general.pickLanguage);
|
||||
this.languagePicker = Utils.CreateLanguagePicker(Translations.t.general.pickLanguage);
|
||||
this.ListenTo(Locale.language);
|
||||
|
||||
function fromLayout(f: (layout: Layout) => (string | UIElement)): UIElement {
|
||||
|
|
|
@ -7,15 +7,17 @@ import {State} from "../../State";
|
|||
|
||||
|
||||
export default class Locale {
|
||||
public static language: UIEventSource<string> = LocalStorageSource.Get('language', "en");
|
||||
|
||||
public static CreateLanguagePicker(label: string | UIElement = "") {
|
||||
public static language: UIEventSource<string> = Locale.setup();
|
||||
private static setup() {
|
||||
const source = LocalStorageSource.Get('language', "en");
|
||||
// @ts-ignore
|
||||
window.setLanguage = function (language: string) {
|
||||
source.setData(language)
|
||||
}
|
||||
return source;
|
||||
}
|
||||
|
||||
return new DropDown(label, State.state.layoutToUse.data.supportedLanguages.map(lang => {
|
||||
return {value: lang, shown: lang}
|
||||
}
|
||||
), Locale.language);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -793,8 +793,8 @@ export default class Translations {
|
|||
|
||||
}),
|
||||
header: new T({
|
||||
en: "<h2>No data</h2>You clicked somewhere where no data is known yet.<br/>",
|
||||
nl: "<h2>Geen selectie</h2>Je klikte ergens waar er nog geen data is.<br/>",
|
||||
en: "<h2>Add a point?</h2>You clicked somewhere where no data is known yet.<br/>",
|
||||
nl: "<h2>Punt toevoegen?</h2>Je klikte ergens waar er nog geen data is.<br/>",
|
||||
fr: "<h2>Pas de données</h2> vous avez cliqué sur un endroit ou il n'y a pas encore de données. <br/>"
|
||||
|
||||
}),
|
||||
|
@ -814,8 +814,8 @@ export default class Translations {
|
|||
fr: "Chargement des donnés. Patientez un instant avant d'ajouter un nouveau point"
|
||||
}),
|
||||
confirmIntro: new T({
|
||||
en: "<h3>Add a {title} here?</h3>The point you create here will be visible for everyone. Please, only add things on to the map if they truly exist. A lot of applications use this data.",
|
||||
nl: "<h3>Voeg hier een {title} toe?</h3>Het punt dat je hier toevoegt, is zichtbaar voor iedereen. Veel applicaties gebruiken deze data, voeg dus enkel punten toe die echt bestaan.",
|
||||
en: "<h3>Add a {title} here?</h3>The point you create here will be <b>visible for everyone</b>. Please, only add things on to the map if they truly exist. A lot of applications use this data.",
|
||||
nl: "<h3>Voeg hier een {title} toe?</h3>Het punt dat je hier toevoegt, is <b>zichtbaar voor iedereen</b>. Veel applicaties gebruiken deze data, voeg dus enkel punten toe die echt bestaan.",
|
||||
fr: "<h3>Ajouter un/une {title} ici?</h3>Le point que vous ajouter sera visible par tout le monde. Merci d'etre sûr que ce point existe réellement. Beaucoup d'autres applications reposent sur ces données.",
|
||||
|
||||
})
|
||||
|
@ -952,6 +952,16 @@ export default class Translations {
|
|||
nl: "Ga naar de berichten",
|
||||
fr: "Ouvrir les messages"
|
||||
})
|
||||
},
|
||||
favourite: {
|
||||
title: "Custom",
|
||||
description: new T({
|
||||
en: "<h3>Your custom theme</h3>In your custom theme, you can add some favourite layers from other themes to create a custom theme."
|
||||
}),
|
||||
panelIntro: new T({
|
||||
en:"<h3>Your custom theme</h3>Create your own theme here by picking your favourite layers"
|
||||
})
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
|
23
Utils.ts
23
Utils.ts
|
@ -1,3 +1,8 @@
|
|||
import {UIElement} from "./UI/UIElement";
|
||||
import {DropDown} from "./UI/Input/DropDown";
|
||||
import {State} from "./State";
|
||||
import Locale from "./UI/i18n/Locale";
|
||||
|
||||
export class Utils {
|
||||
|
||||
/**
|
||||
|
@ -18,4 +23,22 @@ export class Utils {
|
|||
public static Upper(str : string){
|
||||
return str.substr(0,1).toUpperCase() + str.substr(1);
|
||||
}
|
||||
|
||||
static DoEvery(millis: number, f: (() => void)) {
|
||||
window.setTimeout(
|
||||
function () {
|
||||
f();
|
||||
Utils.DoEvery(millis, f);
|
||||
}
|
||||
, millis)
|
||||
}
|
||||
|
||||
public static CreateLanguagePicker(label: string | UIElement = "") {
|
||||
|
||||
return new DropDown(label, State.state.layoutToUse.data.supportedLanguages.map(lang => {
|
||||
return {value: lang, shown: lang}
|
||||
}
|
||||
), Locale.language);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
61
index.css
61
index.css
|
@ -1242,3 +1242,64 @@ form {
|
|||
.add-ui {
|
||||
font-size: large;
|
||||
}
|
||||
|
||||
|
||||
.custom-layer-panel {
|
||||
|
||||
}
|
||||
|
||||
|
||||
.custom-layer-panel-header {
|
||||
display: flex;
|
||||
flex-wrap: nowrap;
|
||||
flex-direction: row;
|
||||
font-size: large;
|
||||
margin: 0.5em;
|
||||
background-color: white;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
text-decoration: none;
|
||||
color: black;
|
||||
}
|
||||
|
||||
.custom-layer-panel-header-img img {
|
||||
max-width: 3em;
|
||||
width: 100%;
|
||||
max-height: 3em;
|
||||
padding: 0.5em;
|
||||
}
|
||||
|
||||
.custom-layer-panel-header-img {
|
||||
width: 4em;
|
||||
height: 4em;
|
||||
|
||||
}
|
||||
|
||||
.custom-layer-checkbox {
|
||||
font-size: larger;
|
||||
height: 2em;
|
||||
background-color: #e5f5ff;
|
||||
margin:0.3em;
|
||||
margin-left: 2em;
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
align-items: stretch;
|
||||
text-decoration: none;
|
||||
padding: 0.5em;
|
||||
border-radius: 1em;
|
||||
}
|
||||
.custom-layer-checkbox img {
|
||||
max-width: 1.5em;
|
||||
max-height: 1.5em;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
padding: 0.2em;
|
||||
padding-right: 0.5em;
|
||||
}
|
||||
|
||||
.custom-layer-checkbox svg {
|
||||
max-width: 1.5em;
|
||||
max-height: 1.5em;
|
||||
padding: 0.2em;
|
||||
padding-right: 0.5em;
|
||||
}
|
142
index.ts
142
index.ts
|
@ -1,37 +1,21 @@
|
|||
import {ElementStorage} from "./Logic/ElementStorage";
|
||||
import {UIEventSource} from "./UI/UIEventSource";
|
||||
import {UserBadge} from "./UI/UserBadge";
|
||||
import {PendingChanges} from "./UI/PendingChanges";
|
||||
import {CenterMessageBox} from "./UI/CenterMessageBox";
|
||||
import {Helpers} from "./Helpers";
|
||||
import {TagUtils} from "./Logic/TagsFilter";
|
||||
import {LayerUpdater} from "./Logic/LayerUpdater";
|
||||
import {UIElement} from "./UI/UIElement";
|
||||
import {FullScreenMessageBoxHandler} from "./UI/FullScreenMessageBoxHandler";
|
||||
import {FeatureInfoBox} from "./UI/FeatureInfoBox";
|
||||
import {SimpleAddUI} from "./UI/SimpleAddUI";
|
||||
import {VariableUiElement} from "./UI/Base/VariableUIElement";
|
||||
import {SearchAndGo} from "./UI/SearchAndGo";
|
||||
import {AllKnownLayouts} from "./Customizations/AllKnownLayouts";
|
||||
import {CheckBox} from "./UI/Input/CheckBox";
|
||||
import Translations from "./UI/i18n/Translations";
|
||||
import Locale from "./UI/i18n/Locale";
|
||||
import {Layout} from "./Customizations/Layout";
|
||||
import {DropDown} from "./UI/Input/DropDown";
|
||||
import {FixedUiElement} from "./UI/Base/FixedUiElement";
|
||||
import {LayerSelection} from "./UI/LayerSelection";
|
||||
import Combine from "./UI/Base/Combine";
|
||||
import {Img} from "./UI/Img";
|
||||
import {QueryParameters} from "./Logic/QueryParameters";
|
||||
import {Utils} from "./Utils";
|
||||
import {LocalStorageSource} from "./Logic/LocalStorageSource";
|
||||
import {InitUiElements} from "./InitUiElements";
|
||||
import {StrayClickHandler} from "./Logic/Leaflet/StrayClickHandler";
|
||||
import {BaseLayers, Basemap} from "./Logic/Leaflet/Basemap";
|
||||
import {GeoLocationHandler} from "./Logic/Leaflet/GeoLocationHandler";
|
||||
import {OsmConnection} from "./Logic/Osm/OsmConnection";
|
||||
import {Changes} from "./Logic/Osm/Changes";
|
||||
import {State} from "./State";
|
||||
import {All} from "./Customizations/Layouts/All";
|
||||
import {CustomLayers} from "./Logic/CustomLayers";
|
||||
|
||||
|
||||
// --------------------- Special actions based on the parameters -----------------
|
||||
|
@ -79,105 +63,29 @@ for (const k in AllKnownLayouts.allSets) {
|
|||
defaultLayout = QueryParameters.GetQueryParameter("layout", defaultLayout).data;
|
||||
|
||||
const layoutToUse: Layout = AllKnownLayouts.allSets[defaultLayout] ?? AllKnownLayouts["all"];
|
||||
console.log("Using layout: ", layoutToUse.name);
|
||||
if (layoutToUse === undefined) {
|
||||
console.log("Incorrect layout")
|
||||
new FixedUiElement("Error: incorrect layout " + defaultLayout + "<a href='https://pietervdvn.github.io/MapComplete/index.html'>Go to MapComplete</a>").AttachTo("centermessage").onClick(() => {
|
||||
});
|
||||
throw "Incorrect layout"
|
||||
}
|
||||
|
||||
// Setup the global state
|
||||
console.log("Using layout: ", layoutToUse.name);
|
||||
|
||||
State.state = new State(layoutToUse);
|
||||
const state = State.state;
|
||||
|
||||
|
||||
// ----------------- Prepare the important objects -----------------
|
||||
state.osmConnection = new OsmConnection(
|
||||
QueryParameters.GetQueryParameter("test", "false").data === "true",
|
||||
QueryParameters.GetQueryParameter("oauth_token", undefined)
|
||||
);
|
||||
|
||||
|
||||
Locale.language.syncWith(state.osmConnection.GetPreference("language"));
|
||||
|
||||
// @ts-ignore
|
||||
window.setLanguage = function (language: string) {
|
||||
Locale.language.setData(language)
|
||||
}
|
||||
|
||||
Locale.language.addCallback((currentLanguage) => {
|
||||
if (layoutToUse.supportedLanguages.indexOf(currentLanguage) < 0) {
|
||||
console.log("Resetting languate to", layoutToUse.supportedLanguages[0], "as", currentLanguage, " is unsupported")
|
||||
// The current language is not supported -> switch to a supported one
|
||||
Locale.language.setData(layoutToUse.supportedLanguages[0]);
|
||||
}
|
||||
}).ping()
|
||||
|
||||
|
||||
state.allElements = new ElementStorage();
|
||||
state.changes = new Changes(
|
||||
"Beantwoorden van vragen met #MapComplete voor vragenset #" + state.layoutToUse.data.name,
|
||||
state.osmConnection, state.allElements);
|
||||
state.bm = new Basemap("leafletDiv", state.locationControl, new VariableUiElement(
|
||||
state.locationControl.map((location) => {
|
||||
const mapComplete = "<a href='https://github.com/pietervdvn/MapComplete' target='_blank'>Mapcomple</a> " +
|
||||
" " +
|
||||
"<a href='https://github.com/pietervdvn/MapComplete/issues' target='_blank'><img src='./assets/bug.svg' alt='Report bug' class='small-userbadge-icon'></a>";
|
||||
let editHere = "";
|
||||
if (location !== undefined) {
|
||||
editHere = " | " +
|
||||
"<a href='https://www.openstreetmap.org/edit?editor=id#map=" + location.zoom + "/" + location.lat + "/" + location.lon + "' target='_blank'>" +
|
||||
"<img src='./assets/pencil.svg' alt='edit here' class='small-userbadge-icon'>" +
|
||||
"</a>"
|
||||
}
|
||||
return mapComplete + editHere;
|
||||
|
||||
})
|
||||
));
|
||||
|
||||
|
||||
function setupAllLayerElements() {
|
||||
|
||||
// ------------- Setup the layers -------------------------------
|
||||
|
||||
const layerSetup = InitUiElements.InitLayers();
|
||||
|
||||
const layerUpdater = new LayerUpdater(layerSetup.minZoom, layoutToUse.widenFactor, layerSetup.flayers);
|
||||
|
||||
|
||||
// --------------- Setting up layer selection ui --------
|
||||
|
||||
const closedFilterButton = `<button id="filter__button" class="filter__button shadow">${Img.closedFilterButton}</button>`;
|
||||
|
||||
const openFilterButton = `
|
||||
<button id="filter__button" class="filter__button">${Img.openFilterButton}</button>`;
|
||||
|
||||
let baseLayerOptions = BaseLayers.baseLayers.map((layer) => {
|
||||
return {value: layer, shown: layer.name}
|
||||
});
|
||||
const backgroundMapPicker = new Combine([new DropDown(`Background map`, baseLayerOptions, State.state.bm.CurrentLayer), openFilterButton]);
|
||||
const layerSelection = new Combine([`<p class="filter__label">Maplayers</p>`, new LayerSelection(layerSetup.flayers)]);
|
||||
let layerControl = backgroundMapPicker;
|
||||
if (layerSetup.flayers.length > 1) {
|
||||
layerControl = new Combine([layerSelection, backgroundMapPicker]);
|
||||
}
|
||||
|
||||
InitUiElements.OnlyIf(State.state.featureSwitchLayers, () => {
|
||||
|
||||
const checkbox = new CheckBox(layerControl, closedFilterButton);
|
||||
checkbox.AttachTo("filter__selection");
|
||||
State.state.bm.Location.addCallback(() => {
|
||||
checkbox.isEnabled.setData(false);
|
||||
});
|
||||
|
||||
});
|
||||
InitUiElements.InitLayerSelection(layerSetup)
|
||||
|
||||
|
||||
// ------------------ Setup various other UI elements ------------
|
||||
|
||||
document.title = Translations.W(layoutToUse.title).InnerRender();
|
||||
|
||||
Locale.language.addCallback(e => {
|
||||
document.title = Translations.W(layoutToUse.title).InnerRender();
|
||||
})
|
||||
|
||||
|
||||
InitUiElements.OnlyIf(State.state.featureSwitchAddNew, () => {
|
||||
new StrayClickHandler(() => {
|
||||
|
@ -188,6 +96,27 @@ InitUiElements.OnlyIf(State.state.featureSwitchAddNew, () => {
|
|||
);
|
||||
});
|
||||
|
||||
new CenterMessageBox(
|
||||
layerSetup.minZoom,
|
||||
layerUpdater.runningQuery)
|
||||
.AttachTo("centermessage");
|
||||
|
||||
}
|
||||
|
||||
setupAllLayerElements();
|
||||
|
||||
if (layoutToUse === AllKnownLayouts.allSets[CustomLayers.NAME]) {
|
||||
State.state.favourteLayers.addCallback((favs) => {
|
||||
for (const fav of favs) {
|
||||
const layer = AllKnownLayouts.allLayers[fav];
|
||||
if (!!layer) {
|
||||
layoutToUse.layers.push(layer);
|
||||
}
|
||||
setupAllLayerElements();
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Show the questions and information for the selected element
|
||||
|
@ -219,7 +148,6 @@ State.state.selectedElement.addCallback((feature) => {
|
|||
}
|
||||
);
|
||||
|
||||
console.log("Enable new:",State.state.featureSwitchAddNew.data,"deafult", layoutToUse.enableAdd)
|
||||
InitUiElements.OnlyIf(State.state.featureSwitchUserbadge, () => {
|
||||
new UserBadge().AttachTo('userbadge');
|
||||
});
|
||||
|
@ -241,16 +169,6 @@ if ((window != window.top && !State.state.featureSwitchWelcomeMessage) || State.
|
|||
}
|
||||
|
||||
|
||||
new CenterMessageBox(
|
||||
layerSetup.minZoom,
|
||||
layerUpdater.runningQuery)
|
||||
.AttachTo("centermessage");
|
||||
|
||||
|
||||
Helpers.SetupAutoSave();
|
||||
Helpers.LastEffortSave();
|
||||
|
||||
|
||||
|
||||
new GeoLocationHandler().AttachTo("geolocate-button");
|
||||
|
||||
|
|
37
test.ts
37
test.ts
|
@ -1,32 +1,11 @@
|
|||
import {ImageUploadFlow} from "./UI/ImageUploadFlow";
|
||||
import {OsmConnection, UserDetails} from "./Logic/Osm/OsmConnection";
|
||||
import {OsmImageUploadHandler} from "./Logic/Osm/OsmImageUploadHandler";
|
||||
import {UIEventSource} from "./UI/UIEventSource";
|
||||
import {Changes} from "./Logic/Osm/Changes";
|
||||
import {SlideShow} from "./UI/SlideShow";
|
||||
import {ElementStorage} from "./Logic/ElementStorage";
|
||||
import {isNullOrUndefined} from "util";
|
||||
import Locale from "./UI/i18n/Locale";
|
||||
import {State} from "./State";
|
||||
import Cyclofix from "./Customizations/Layouts/Cyclofix";
|
||||
import {CustomLayersPanel} from "./Logic/CustomLayersPanel";
|
||||
|
||||
const osmConnection = new OsmConnection(true, new UIEventSource<string>(undefined));
|
||||
const uploadHandler = new OsmImageUploadHandler(
|
||||
new UIEventSource<any>({}),
|
||||
osmConnection.userDetails,
|
||||
new UIEventSource<string>("cc0"),
|
||||
new Changes("blabla", osmConnection, new ElementStorage()),
|
||||
undefined);
|
||||
State.state= new State(new Cyclofix());
|
||||
|
||||
new ImageUploadFlow(
|
||||
osmConnection.userDetails,
|
||||
new UIEventSource<string>("cc0"),
|
||||
(license: string) => {
|
||||
return {
|
||||
title: "test",
|
||||
description: "test",
|
||||
handleURL: console.log,
|
||||
allDone: () => {
|
||||
}
|
||||
}
|
||||
}).AttachTo("maindiv")
|
||||
new CustomLayersPanel().AttachTo("maindiv");
|
||||
|
||||
Locale.language.setData("nl")
|
||||
State.state.osmConnection.GetPreference("mapcomplete-custom-layer-count").addCallback((count) => console.log("Count: ", count))
|
||||
|
||||
State.state.favourteLayers.addCallback(console.log)
|
Loading…
Reference in a new issue