Custom themes now stick to the user account and can be revisited, small improvements
This commit is contained in:
parent
bf6eae9af1
commit
4a0970a71f
23 changed files with 556 additions and 1748 deletions
|
@ -1,7 +1,6 @@
|
|||
import {LayerDefinition} from "./LayerDefinition";
|
||||
import {Layout} from "./Layout";
|
||||
import {All} from "./Layouts/All";
|
||||
import {CustomLayout} from "../Logic/CustomLayers";
|
||||
import {Groen} from "./Layouts/Groen";
|
||||
import Cyclofix from "./Layouts/Cyclofix";
|
||||
import {StreetWidth} from "./Layouts/StreetWidth";
|
||||
|
@ -16,13 +15,14 @@ import * as bookcases from "../assets/themes/bookcases/Bookcases.json";
|
|||
import * as aed from "../assets/themes/aed/aed.json";
|
||||
import * as toilets from "../assets/themes/toilets/toilets.json";
|
||||
import * as artworks from "../assets/themes/artwork/artwork.json";
|
||||
import {PersonalLayout} from "../Logic/PersonalLayout";
|
||||
|
||||
export class AllKnownLayouts {
|
||||
|
||||
public static allLayers: Map<string, LayerDefinition> = undefined;
|
||||
|
||||
public static layoutsList: Layout[] = [
|
||||
new CustomLayout(),
|
||||
new PersonalLayout(),
|
||||
new Natuurpunt(),
|
||||
new GRB(),
|
||||
new Cyclofix(),
|
||||
|
|
|
@ -10,6 +10,7 @@ import {UIEventSource} from "../../Logic/UIEventSource";
|
|||
import {TagDependantUIElementConstructor} from "../UIElementConstructor";
|
||||
import {Map} from "../Layers/Map";
|
||||
import {UIElement} from "../../UI/UIElement";
|
||||
import Translations from "../../UI/i18n/Translations";
|
||||
|
||||
|
||||
export interface TagRenderingConfigJson {
|
||||
|
@ -246,7 +247,7 @@ export class CustomLayoutFromJSON {
|
|||
json.id,
|
||||
{
|
||||
description: t(json.description),
|
||||
name: t(json.title.render),
|
||||
name: Translations.WT(t(json.title.render)).txt.replace(/[^a-zA-Z0-9-_]/g, ''),
|
||||
icon: icon,
|
||||
minzoom: parseInt(""+json.minzoom),
|
||||
title: tr(json.title),
|
||||
|
|
|
@ -14,6 +14,7 @@ export class Layout {
|
|||
public icon: string = "./assets/logo.svg";
|
||||
public title: UIElement;
|
||||
public maintainer: string;
|
||||
public version: string;
|
||||
public description: string | UIElement;
|
||||
public socialImage: string = "";
|
||||
|
||||
|
|
|
@ -20,13 +20,13 @@ 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";
|
||||
import {CustomLayout} from "./Logic/CustomLayers";
|
||||
import {Preset} from "./Customizations/LayerDefinition";
|
||||
import {VariableUiElement} from "./UI/Base/VariableUIElement";
|
||||
import {LayerUpdater} from "./Logic/LayerUpdater";
|
||||
import {UIEventSource} from "./Logic/UIEventSource";
|
||||
import {QueryParameters} from "./Logic/Web/QueryParameters";
|
||||
import {PersonalLayout} from "./Logic/PersonalLayout";
|
||||
import {PersonalLayersPanel} from "./Logic/PersonalLayersPanel";
|
||||
|
||||
export class InitUiElements {
|
||||
|
||||
|
@ -50,8 +50,8 @@ export class InitUiElements {
|
|||
|
||||
const layoutToUse = State.state.layoutToUse.data;
|
||||
let welcome: UIElement = new WelcomeMessage();
|
||||
if (layoutToUse.name === CustomLayout.NAME) {
|
||||
welcome = new CustomLayersPanel();
|
||||
if (layoutToUse.name === PersonalLayout.NAME) {
|
||||
welcome = new PersonalLayersPanel();
|
||||
}
|
||||
|
||||
const tabs = [
|
||||
|
|
|
@ -1,88 +0,0 @@
|
|||
import {State} from "../State";
|
||||
|
||||
export class CustomLayersState {
|
||||
static RemoveFavouriteLayer(layer: string) {
|
||||
|
||||
State.state.GetFilteredLayerFor(layer)?.isDisplayed?.setData(false);
|
||||
|
||||
const favs = State.state.favourteLayers.data;
|
||||
const ind = favs.indexOf(layer);
|
||||
if (ind < 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
favs.splice(ind, 1);
|
||||
|
||||
|
||||
const osmConnection = State.state.osmConnection;
|
||||
const count = osmConnection.GetPreference("mapcomplete-custom-layer-count");
|
||||
for (let i = 0; i < favs.length; i++) {
|
||||
const layerIDescr = osmConnection.GetPreference("mapcomplete-custom-layer-" + i);
|
||||
layerIDescr.setData(favs[i]);
|
||||
}
|
||||
count.setData("" + favs.length)
|
||||
}
|
||||
|
||||
static AddFavouriteLayer(layer: string) {
|
||||
State.state.GetFilteredLayerFor(layer)?.isDisplayed?.setData(true);
|
||||
|
||||
const favs = State.state.favourteLayers.data;
|
||||
const ind = favs.indexOf(layer);
|
||||
if (ind >= 0) {
|
||||
return;
|
||||
}
|
||||
console.log("Adding fav layer", layer);
|
||||
favs.push(layer);
|
||||
|
||||
|
||||
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 InitFavouriteLayers(state: State) {
|
||||
const osmConnection = state.osmConnection;
|
||||
const count = osmConnection.GetPreference("mapcomplete-custom-layer-count");
|
||||
const favs = state.favourteLayers.data;
|
||||
let changed = false;
|
||||
count.addCallback((countStr) => {
|
||||
console.log("Updating favourites")
|
||||
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.favourteLayers.data.push(layerId);
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
if (changed) {
|
||||
state.favourteLayers.ping();
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
}
|
131
Logic/Osm/ChangesetHandler.ts
Normal file
131
Logic/Osm/ChangesetHandler.ts
Normal file
|
@ -0,0 +1,131 @@
|
|||
import {State} from "../../State";
|
||||
import {UserDetails} from "./OsmConnection";
|
||||
import {UIEventSource} from "../UIEventSource";
|
||||
|
||||
export class ChangesetHandler {
|
||||
|
||||
private _dryRun: boolean;
|
||||
private userDetails: UIEventSource<UserDetails>;
|
||||
private auth: any;
|
||||
|
||||
constructor(dryRun: boolean, userDetails: UIEventSource<UserDetails>, auth) {
|
||||
this._dryRun = dryRun;
|
||||
this.userDetails = userDetails;
|
||||
this.auth = auth;
|
||||
|
||||
if (dryRun) {
|
||||
console.log("DRYRUN ENABLED");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public UploadChangeset(generateChangeXML: (csid: string) => string,
|
||||
handleMapping: (idMapping: any) => void,
|
||||
continuation: () => void) {
|
||||
|
||||
if (this._dryRun) {
|
||||
console.log("NOT UPLOADING as dryrun is true");
|
||||
var changesetXML = generateChangeXML("123456");
|
||||
console.log(changesetXML);
|
||||
continuation();
|
||||
return;
|
||||
}
|
||||
|
||||
const self = this;
|
||||
this.OpenChangeset(
|
||||
function (csId) {
|
||||
var changesetXML = generateChangeXML(csId);
|
||||
self.AddChange(csId, changesetXML,
|
||||
function (csId, mapping) {
|
||||
self.CloseChangeset(csId, continuation);
|
||||
handleMapping(mapping);
|
||||
}
|
||||
);
|
||||
|
||||
}
|
||||
);
|
||||
|
||||
this.userDetails.data.csCount++;
|
||||
this.userDetails.ping();
|
||||
}
|
||||
|
||||
|
||||
private OpenChangeset(continuation: (changesetId: string) => void) {
|
||||
|
||||
const layout = State.state.layoutToUse.data;
|
||||
|
||||
this.auth.xhr({
|
||||
method: 'PUT',
|
||||
path: '/api/0.6/changeset/create',
|
||||
options: {header: {'Content-Type': 'text/xml'}},
|
||||
content: [`<osm><changeset>`,
|
||||
`<tag k="created_by" v="MapComplete ${State.vNumber}" />`,
|
||||
`<tag k="comment" v="Adding data with #MapComplete"/>`,
|
||||
`<tag k="theme" v="${layout.name}"/>`,
|
||||
layout.maintainer !== undefined ? `<tag k="theme-creator" v="${layout.maintainer}"/>` : "",
|
||||
`</changeset></osm>`].join("")
|
||||
}, function (err, response) {
|
||||
if (response === undefined) {
|
||||
console.log("err", err);
|
||||
alert("Could not upload change (opening failed). Please file a bug report")
|
||||
return;
|
||||
} else {
|
||||
continuation(response);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private AddChange(changesetId: string,
|
||||
changesetXML: string,
|
||||
continuation: ((changesetId: string, idMapping: any) => void)) {
|
||||
this.auth.xhr({
|
||||
method: 'POST',
|
||||
options: {header: {'Content-Type': 'text/xml'}},
|
||||
path: '/api/0.6/changeset/' + changesetId + '/upload',
|
||||
content: changesetXML
|
||||
}, function (err, response) {
|
||||
if (response == null) {
|
||||
console.log("err", err);
|
||||
return;
|
||||
}
|
||||
const mapping = ChangesetHandler.parseUploadChangesetResponse(response);
|
||||
console.log("Uploaded changeset ", changesetId);
|
||||
continuation(changesetId, mapping);
|
||||
});
|
||||
}
|
||||
|
||||
private CloseChangeset(changesetId: string, continuation: (() => void)) {
|
||||
console.log("closing");
|
||||
this.auth.xhr({
|
||||
method: 'PUT',
|
||||
path: '/api/0.6/changeset/' + changesetId + '/close',
|
||||
}, function (err, response) {
|
||||
if (response == null) {
|
||||
|
||||
console.log("err", err);
|
||||
}
|
||||
console.log("Closed changeset ", changesetId);
|
||||
|
||||
if (continuation !== undefined) {
|
||||
continuation();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private static parseUploadChangesetResponse(response: XMLDocument) {
|
||||
const nodes = response.getElementsByTagName("node");
|
||||
const mapping = {};
|
||||
// @ts-ignore
|
||||
for (const node of nodes) {
|
||||
const oldId = parseInt(node.attributes.old_id.value);
|
||||
const newId = parseInt(node.attributes.new_id.value);
|
||||
if (oldId !== undefined && newId !== undefined &&
|
||||
!isNaN(oldId) && !isNaN(newId)) {
|
||||
mapping["node/" + oldId] = "node/" + newId;
|
||||
}
|
||||
}
|
||||
return mapping;
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -1,8 +1,10 @@
|
|||
// @ts-ignore
|
||||
import osmAuth from "osm-auth";
|
||||
import {UIEventSource} from "../UIEventSource";
|
||||
import {CustomLayersState} from "../CustomLayersState";
|
||||
import {State} from "../../State";
|
||||
import {All} from "../../Customizations/Layouts/All";
|
||||
import {OsmPreferences} from "./OsmPreferences";
|
||||
import {ChangesetHandler} from "./ChangesetHandler";
|
||||
|
||||
export class UserDetails {
|
||||
|
||||
|
@ -22,6 +24,11 @@ export class OsmConnection {
|
|||
public userDetails: UIEventSource<UserDetails>;
|
||||
private _dryRun: boolean;
|
||||
|
||||
public _preferencesHandler: OsmPreferences;
|
||||
private _changesetHandler: ChangesetHandler;
|
||||
|
||||
private _onLoggedIn : ((userDetails: UserDetails) => void)[] = [];
|
||||
|
||||
constructor(dryRun: boolean, oauth_token: UIEventSource<string>, singlePage: boolean = true) {
|
||||
|
||||
let pwaStandAloneMode = false;
|
||||
|
@ -61,16 +68,18 @@ export class OsmConnection {
|
|||
this.userDetails.data.dryRun = dryRun;
|
||||
this._dryRun = dryRun;
|
||||
|
||||
|
||||
this._preferencesHandler = new OsmPreferences(this.auth, this);
|
||||
|
||||
this._changesetHandler = new ChangesetHandler(dryRun, this.userDetails, this.auth);
|
||||
if (oauth_token.data !== undefined) {
|
||||
console.log(oauth_token.data)
|
||||
const self = this;
|
||||
this.auth.bootstrapToken(oauth_token.data,
|
||||
this.auth.bootstrapToken(oauth_token.data,
|
||||
(x) => {
|
||||
console.log("Called back: ", x)
|
||||
self.AttemptLogin();
|
||||
}, this.auth);
|
||||
|
||||
|
||||
oauth_token.setData(undefined);
|
||||
|
||||
}
|
||||
|
@ -79,15 +88,27 @@ export class OsmConnection {
|
|||
} else {
|
||||
console.log("Not authenticated");
|
||||
}
|
||||
|
||||
|
||||
if (dryRun) {
|
||||
console.log("DRYRUN ENABLED");
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
public UploadChangeset(generateChangeXML: (csid: string) => string,
|
||||
handleMapping: (idMapping: any) => void,
|
||||
continuation: () => void) {
|
||||
this._changesetHandler.UploadChangeset(generateChangeXML, handleMapping, continuation);
|
||||
}
|
||||
|
||||
public GetPreference(key: string, prefix: string = "mapcomplete-"): UIEventSource<string> {
|
||||
return this._preferencesHandler.GetPreference(key, prefix);
|
||||
}
|
||||
|
||||
public GetLongPreference(key: string, prefix: string = "mapcomplete-"): UIEventSource<string> {
|
||||
return this._preferencesHandler.GetLongPreference(key, prefix);
|
||||
}
|
||||
|
||||
public OnLoggedIn(action: (userDetails: UserDetails) => void){
|
||||
this._onLoggedIn.push(action);
|
||||
}
|
||||
|
||||
public LogOut() {
|
||||
this.auth.logout();
|
||||
this.userDetails.data.loggedIn = false;
|
||||
|
@ -112,7 +133,6 @@ export class OsmConnection {
|
|||
return;
|
||||
}
|
||||
|
||||
self.UpdatePreferences();
|
||||
self.CheckForMessagesContinuously();
|
||||
|
||||
// details is an XML DOM of user details
|
||||
|
@ -143,8 +163,12 @@ export class OsmConnection {
|
|||
const messages = userInfo.getElementsByTagName("messages")[0].getElementsByTagName("received")[0];
|
||||
data.unreadMessages = parseInt(messages.getAttribute("unread"));
|
||||
data.totalMessages = parseInt(messages.getAttribute("count"));
|
||||
self.userDetails.ping();
|
||||
|
||||
for (const action of self._onLoggedIn) {
|
||||
action(self.userDetails.data);
|
||||
}
|
||||
|
||||
self.userDetails.ping();
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -159,208 +183,5 @@ export class OsmConnection {
|
|||
}, 5 * 60 * 1000);
|
||||
}
|
||||
|
||||
public preferences = new UIEventSource<any>({});
|
||||
public preferenceSources : any = {}
|
||||
|
||||
public GetPreference(key: string, prefix : string = "mapcomplete-") : UIEventSource<string>{
|
||||
key = prefix+key;
|
||||
if (this.preferenceSources[key] !== undefined) {
|
||||
return this.preferenceSources[key];
|
||||
}
|
||||
if (this.userDetails.data.loggedIn && this.preferences.data[key] === undefined) {
|
||||
this.UpdatePreferences();
|
||||
}
|
||||
const pref = new UIEventSource<string>(this.preferences.data[key]);
|
||||
pref.addCallback((v) => {
|
||||
this.SetPreference(key, v);
|
||||
});
|
||||
|
||||
this.preferences.addCallback((prefs) => {
|
||||
if (prefs[key] !== undefined) {
|
||||
pref.setData(prefs[key]);
|
||||
}
|
||||
});
|
||||
|
||||
this.preferenceSources[key] = pref;
|
||||
return pref;
|
||||
}
|
||||
|
||||
private UpdatePreferences() {
|
||||
const self = this;
|
||||
this.auth.xhr({
|
||||
method: 'GET',
|
||||
path: '/api/0.6/user/preferences'
|
||||
}, function (error, value: XMLDocument) {
|
||||
if(error){
|
||||
console.log("Could not load preferences", error);
|
||||
return;
|
||||
}
|
||||
const prefs = value.getElementsByTagName("preference");
|
||||
for (let i = 0; i < prefs.length; i++) {
|
||||
const pref = prefs[i];
|
||||
const k = pref.getAttribute("k");
|
||||
const v = pref.getAttribute("v");
|
||||
self.preferences.data[k] = v;
|
||||
}
|
||||
self.preferences.ping();
|
||||
});
|
||||
}
|
||||
|
||||
private SetPreference(k:string, v:string) {
|
||||
if(!this.userDetails.data.loggedIn){
|
||||
console.log("Not saving preference: user not logged in");
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.preferences.data[k] === v) {
|
||||
console.log("Not updating preference", k, " to ", v, "not changed");
|
||||
return;
|
||||
}
|
||||
console.log("Updating preference", k, " to ", v);
|
||||
|
||||
this.preferences.data[k] = v;
|
||||
this.preferences.ping();
|
||||
|
||||
if(v === ""){
|
||||
this.auth.xhr({
|
||||
method: 'DELETE',
|
||||
path: '/api/0.6/user/preferences/' + k,
|
||||
options: {header: {'Content-Type': 'text/plain'}},
|
||||
}, function (error, result) {
|
||||
if (error) {
|
||||
console.log("Could not remove preference", error);
|
||||
return;
|
||||
}
|
||||
|
||||
console.log("Preference removed!", result == "" ? "OK" : result);
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
this.auth.xhr({
|
||||
method: 'PUT',
|
||||
path: '/api/0.6/user/preferences/' + k,
|
||||
options: {header: {'Content-Type': 'text/plain'}},
|
||||
content: v
|
||||
}, function (error, result) {
|
||||
if (error) {
|
||||
console.log("Could not set preference", error);
|
||||
return;
|
||||
}
|
||||
|
||||
console.log("Preference written!", result == "" ? "OK" : result);
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
private static parseUploadChangesetResponse(response: XMLDocument) {
|
||||
const nodes = response.getElementsByTagName("node");
|
||||
const mapping = {};
|
||||
// @ts-ignore
|
||||
for (const node of nodes) {
|
||||
const oldId = parseInt(node.attributes.old_id.value);
|
||||
const newId = parseInt(node.attributes.new_id.value);
|
||||
if (oldId !== undefined && newId !== undefined &&
|
||||
!isNaN(oldId) && !isNaN(newId)) {
|
||||
mapping["node/" + oldId] = "node/" + newId;
|
||||
}
|
||||
}
|
||||
return mapping;
|
||||
}
|
||||
|
||||
|
||||
public UploadChangeset(generateChangeXML: (csid: string) => string,
|
||||
handleMapping: (idMapping: any) => void,
|
||||
continuation: () => void) {
|
||||
|
||||
if (this._dryRun) {
|
||||
console.log("NOT UPLOADING as dryrun is true");
|
||||
var changesetXML = generateChangeXML("123456");
|
||||
console.log(changesetXML);
|
||||
continuation();
|
||||
return;
|
||||
}
|
||||
|
||||
const self = this;
|
||||
this.OpenChangeset(
|
||||
function (csId) {
|
||||
var changesetXML = generateChangeXML(csId);
|
||||
self.AddChange(csId, changesetXML,
|
||||
function (csId, mapping) {
|
||||
self.CloseChangeset(csId, continuation);
|
||||
handleMapping(mapping);
|
||||
}
|
||||
);
|
||||
|
||||
}
|
||||
);
|
||||
|
||||
this.userDetails.data.csCount++;
|
||||
this.userDetails.ping();
|
||||
}
|
||||
|
||||
|
||||
private OpenChangeset(continuation: (changesetId: string) => void) {
|
||||
|
||||
const layout = State.state.layoutToUse.data;
|
||||
|
||||
this.auth.xhr({
|
||||
method: 'PUT',
|
||||
path: '/api/0.6/changeset/create',
|
||||
options: {header: {'Content-Type': 'text/xml'}},
|
||||
content: [`<osm><changeset>`,
|
||||
`<tag k="created_by" v="MapComplete ${State.vNumber}" />`,
|
||||
`<tag k="comment" v="Adding data with #MapComplete"/>`,
|
||||
`<tag k="theme" v="${layout.name}"/>`,
|
||||
layout.maintainer !== undefined ? `<tag k="theme-creator" v="${layout.maintainer}"/>` : "",
|
||||
`</changeset></osm>`].join("")
|
||||
}, function (err, response) {
|
||||
if (response === undefined) {
|
||||
console.log("err", err);
|
||||
alert("Could not upload change (opening failed). Please file a bug report")
|
||||
return;
|
||||
} else {
|
||||
continuation(response);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private AddChange(changesetId: string,
|
||||
changesetXML: string,
|
||||
continuation: ((changesetId: string, idMapping: any) => void)){
|
||||
this.auth.xhr({
|
||||
method: 'POST',
|
||||
options: { header: { 'Content-Type': 'text/xml' } },
|
||||
path: '/api/0.6/changeset/'+changesetId+'/upload',
|
||||
content: changesetXML
|
||||
}, function (err, response) {
|
||||
if (response == null) {
|
||||
console.log("err", err);
|
||||
return;
|
||||
}
|
||||
const mapping = OsmConnection.parseUploadChangesetResponse(response);
|
||||
console.log("Uploaded changeset ", changesetId);
|
||||
continuation(changesetId, mapping);
|
||||
});
|
||||
}
|
||||
|
||||
private CloseChangeset(changesetId: string, continuation : (() => void)) {
|
||||
console.log("closing");
|
||||
this.auth.xhr({
|
||||
method: 'PUT',
|
||||
path: '/api/0.6/changeset/'+changesetId+'/close',
|
||||
}, function (err, response) {
|
||||
if (response == null) {
|
||||
|
||||
console.log("err", err);
|
||||
}
|
||||
console.log("Closed changeset ", changesetId);
|
||||
|
||||
if(continuation !== undefined){
|
||||
continuation();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
176
Logic/Osm/OsmPreferences.ts
Normal file
176
Logic/Osm/OsmPreferences.ts
Normal file
|
@ -0,0 +1,176 @@
|
|||
import {UIEventSource} from "../UIEventSource";
|
||||
import {OsmConnection, UserDetails} from "./OsmConnection";
|
||||
import {All} from "../../Customizations/Layouts/All";
|
||||
import {Utils} from "../../Utils";
|
||||
|
||||
export class OsmPreferences {
|
||||
|
||||
private auth: any;
|
||||
private userDetails: UIEventSource<UserDetails>;
|
||||
|
||||
public preferences = new UIEventSource<any>({});
|
||||
public preferenceSources: any = {}
|
||||
|
||||
constructor(auth, osmConnection: OsmConnection) {
|
||||
this.auth = auth;
|
||||
this.userDetails = osmConnection.userDetails;
|
||||
const self = this;
|
||||
osmConnection.OnLoggedIn(() => self.UpdatePreferences());
|
||||
}
|
||||
|
||||
/**
|
||||
* OSM preferences can be at most 255 chars
|
||||
* @param key
|
||||
* @param prefix
|
||||
* @constructor
|
||||
*/
|
||||
public GetLongPreference(key: string, prefix: string = "mapcomplete-"): UIEventSource<string> {
|
||||
const source = new UIEventSource<string>(undefined);
|
||||
|
||||
const allStartWith = prefix + key + "-combined";
|
||||
// Gives the number of combined preferences
|
||||
const length = this.GetPreference(allStartWith + "-length", "");
|
||||
|
||||
console.log("Getting long pref " + prefix + key);
|
||||
const self = this;
|
||||
source.addCallback(str => {
|
||||
if (str === undefined) {
|
||||
for (const prefKey in self.preferenceSources) {
|
||||
if (prefKey.startsWith(allStartWith)) {
|
||||
self.GetPreference(prefKey, "").setData(undefined);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
let i = 0;
|
||||
while (str !== "") {
|
||||
self.GetPreference(allStartWith + "-" + i, "").setData(str.substr(0, 255));
|
||||
str = str.substr(255);
|
||||
i++;
|
||||
}
|
||||
length.setData("" + i);
|
||||
});
|
||||
|
||||
|
||||
function updateData(l: number) {
|
||||
if (l === undefined) {
|
||||
source.setData(undefined);
|
||||
return;
|
||||
}
|
||||
const length = Number(l);
|
||||
let str = "";
|
||||
for (let i = 0; i < length; i++) {
|
||||
str += self.GetPreference(allStartWith + "-" + i, "").data;
|
||||
}
|
||||
source.setData(str);
|
||||
source.ping();
|
||||
console.log("Long preference ", key, " has ", str.length, " chars");
|
||||
}
|
||||
|
||||
length.addCallback(l => {
|
||||
updateData(Number(l));
|
||||
});
|
||||
updateData(Number(length.data));
|
||||
|
||||
return source;
|
||||
}
|
||||
|
||||
public GetPreference(key: string, prefix: string = "mapcomplete-"): UIEventSource<string> {
|
||||
key = prefix + key;
|
||||
if(key.length >= 255){
|
||||
throw "Preferences: key length to big";
|
||||
}
|
||||
if (this.preferenceSources[key] !== undefined) {
|
||||
return this.preferenceSources[key];
|
||||
}
|
||||
if (this.userDetails.data.loggedIn && this.preferences.data[key] === undefined) {
|
||||
this.UpdatePreferences();
|
||||
}
|
||||
const pref = new UIEventSource<string>(this.preferences.data[key]);
|
||||
pref.addCallback((v) => {
|
||||
this.SetPreference(key, v);
|
||||
});
|
||||
|
||||
this.preferences.addCallback((prefs) => {
|
||||
if (prefs[key] !== undefined) {
|
||||
pref.setData(prefs[key]);
|
||||
}
|
||||
});
|
||||
|
||||
this.preferenceSources[key] = pref;
|
||||
return pref;
|
||||
}
|
||||
|
||||
private UpdatePreferences() {
|
||||
const self = this;
|
||||
this.auth.xhr({
|
||||
method: 'GET',
|
||||
path: '/api/0.6/user/preferences'
|
||||
}, function (error, value: XMLDocument) {
|
||||
if (error) {
|
||||
console.log("Could not load preferences", error);
|
||||
return;
|
||||
}
|
||||
const prefs = value.getElementsByTagName("preference");
|
||||
for (let i = 0; i < prefs.length; i++) {
|
||||
const pref = prefs[i];
|
||||
const k = pref.getAttribute("k");
|
||||
const v = pref.getAttribute("v");
|
||||
self.preferences.data[k] = v;
|
||||
}
|
||||
self.preferences.ping();
|
||||
});
|
||||
}
|
||||
|
||||
private SetPreference(k: string, v: string) {
|
||||
if (!this.userDetails.data.loggedIn) {
|
||||
console.log("Not saving preference: user not logged in");
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.preferences.data[k] === v) {
|
||||
console.log("Not updating preference", k, " to ", v, "not changed");
|
||||
return;
|
||||
}
|
||||
console.log("Updating preference", k, " to ", Utils.EllipsesAfter(v, 15));
|
||||
|
||||
this.preferences.data[k] = v;
|
||||
this.preferences.ping();
|
||||
|
||||
if (v === "") {
|
||||
this.auth.xhr({
|
||||
method: 'DELETE',
|
||||
path: '/api/0.6/user/preferences/' + k,
|
||||
options: {header: {'Content-Type': 'text/plain'}},
|
||||
}, function (error, result) {
|
||||
if (error) {
|
||||
console.log("Could not remove preference", error);
|
||||
return;
|
||||
}
|
||||
|
||||
console.log("Preference removed!", result == "" ? "OK" : result);
|
||||
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
this.auth.xhr({
|
||||
method: 'PUT',
|
||||
path: '/api/0.6/user/preferences/' + k,
|
||||
options: {header: {'Content-Type': 'text/plain'}},
|
||||
content: v
|
||||
}, function (error, result) {
|
||||
if (error) {
|
||||
console.log("Could not set preference", error);
|
||||
return;
|
||||
}
|
||||
|
||||
console.log("Preference written!", result == "" ? "OK" : result);
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -6,40 +6,38 @@ 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";
|
||||
import {CustomLayout} from "./CustomLayers";
|
||||
import {SubtleButton} from "../UI/Base/SubtleButton";
|
||||
import {PersonalLayout} from "./PersonalLayout";
|
||||
|
||||
export class CustomLayersPanel extends UIElement {
|
||||
export class PersonalLayersPanel extends UIElement {
|
||||
private checkboxes: UIElement[] = [];
|
||||
|
||||
private updateButton : UIElement;
|
||||
|
||||
private updateButton: UIElement;
|
||||
|
||||
constructor() {
|
||||
super(State.state.favourteLayers);
|
||||
super(State.state.favouriteLayers);
|
||||
|
||||
this.ListenTo(State.state.osmConnection.userDetails);
|
||||
|
||||
|
||||
const t = Translations.t.favourite;
|
||||
const favs = State.state.favourteLayers.data;
|
||||
const favs = State.state.favouriteLayers.data ?? [];
|
||||
|
||||
this.updateButton = new SubtleButton("./assets/reload.svg", t.reload)
|
||||
.onClick(() => {
|
||||
State.state.layerUpdater.ForceRefresh();
|
||||
CustomLayersState.InitFavouriteLayers(State.state);
|
||||
State.state.layoutToUse.ping();
|
||||
})
|
||||
|
||||
const controls = new Map<string, UIEventSource<boolean>>();
|
||||
for (const layout of AllKnownLayouts.layoutsList) {
|
||||
|
||||
if(layout.name === CustomLayout.NAME){
|
||||
if (layout.name === PersonalLayout.NAME) {
|
||||
continue;
|
||||
}
|
||||
if (layout.hideFromOverview &&
|
||||
if (layout.hideFromOverview &&
|
||||
State.state.osmConnection.userDetails.data.name !== "Pieter Vander Vennet") {
|
||||
continue
|
||||
}
|
||||
|
@ -86,18 +84,20 @@ export class CustomLayersPanel extends UIElement {
|
|||
controls[layer.id] = cb.isEnabled;
|
||||
|
||||
cb.isEnabled.addCallback((isEnabled) => {
|
||||
const favs = State.state.favouriteLayers;
|
||||
if (isEnabled) {
|
||||
CustomLayersState.AddFavouriteLayer(layer.id)
|
||||
favs.data.push(layer.id);
|
||||
} else {
|
||||
CustomLayersState.RemoveFavouriteLayer(layer.id);
|
||||
favs.data.splice(favs.data.indexOf(layer.id), 1);
|
||||
}
|
||||
favs.ping();
|
||||
})
|
||||
|
||||
this.checkboxes.push(cb);
|
||||
|
||||
}
|
||||
|
||||
State.state.favourteLayers.addCallback((layers) => {
|
||||
State.state.favouriteLayers.addCallback((layers) => {
|
||||
for (const layerId of layers) {
|
||||
controls[layerId]?.setData(true);
|
||||
}
|
|
@ -1,13 +1,13 @@
|
|||
import {Layout} from "../Customizations/Layout";
|
||||
import Translations from "../UI/i18n/Translations";
|
||||
|
||||
export class CustomLayout extends Layout {
|
||||
export class PersonalLayout extends Layout {
|
||||
|
||||
public static NAME: string = "personal";
|
||||
|
||||
constructor() {
|
||||
super(
|
||||
CustomLayout.NAME,
|
||||
PersonalLayout.NAME,
|
||||
["en"],
|
||||
Translations.t.favourite.title,
|
||||
[],
|
||||
|
@ -20,7 +20,4 @@ export class CustomLayout extends Layout {
|
|||
this.icon = "./assets/star.svg"
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
|
@ -68,9 +68,9 @@ A typical user journey would be:
|
|||
|
||||
## License
|
||||
|
||||
GPL + pingback.
|
||||
GPLv3.0 + recommended pingback.
|
||||
|
||||
I love it to see where the project ends up. You are free to reuse the software (under GPL) but, when you have made your own change and are using it, I would like to know about it. Drop me a line, give a pingback in the issues, ...
|
||||
I love it to see where the project ends up. You are free to reuse the software (under GPL) but, when you have made your own change and are using it, I would like to know about it. Drop me a line, give a pingback in the issues,...
|
||||
|
||||
## Dev
|
||||
|
||||
|
|
98
State.ts
98
State.ts
|
@ -8,7 +8,6 @@ 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";
|
||||
import {FilteredLayer} from "./Logic/FilteredLayer";
|
||||
import {LayerUpdater} from "./Logic/LayerUpdater";
|
||||
import {UIEventSource} from "./Logic/UIEventSource";
|
||||
|
@ -24,7 +23,7 @@ export class State {
|
|||
// The singleton of the global state
|
||||
public static state: State;
|
||||
|
||||
public static vNumber = "0.0.6c";
|
||||
public static vNumber = "0.0.6d";
|
||||
|
||||
// The user journey states thresholds when a new feature gets unlocked
|
||||
public static userJourney = {
|
||||
|
@ -38,11 +37,7 @@ export class State {
|
|||
|
||||
public static runningFromConsole: boolean = false;
|
||||
|
||||
/**
|
||||
THe layout to use
|
||||
*/
|
||||
public readonly layoutToUse = new UIEventSource<Layout>(undefined);
|
||||
public layoutDefinition : string;
|
||||
|
||||
/**
|
||||
The mapping from id -> UIEventSource<properties>
|
||||
|
@ -60,13 +55,15 @@ export class State {
|
|||
The user credentials
|
||||
*/
|
||||
public osmConnection: OsmConnection;
|
||||
|
||||
public layerUpdater : LayerUpdater;
|
||||
|
||||
|
||||
|
||||
public favouriteLayers: UIEventSource<string[]>;
|
||||
|
||||
public layerUpdater: LayerUpdater;
|
||||
|
||||
|
||||
public filteredLayers: UIEventSource<FilteredLayer[]> = new UIEventSource<FilteredLayer[]>([])
|
||||
public presets: UIEventSource<Preset[]> = new UIEventSource<Preset[]>([])
|
||||
|
||||
|
||||
/**
|
||||
* The message that should be shown at the center of the screen
|
||||
*/
|
||||
|
@ -123,77 +120,96 @@ 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[]>([])
|
||||
public layoutDefinition: string;
|
||||
|
||||
|
||||
constructor(layoutToUse: Layout) {
|
||||
this.layoutToUse = new UIEventSource<Layout>(layoutToUse);
|
||||
const self = this;
|
||||
this.layoutToUse.setData(layoutToUse)
|
||||
this.locationControl = new UIEventSource<{ lat: number, lon: number, zoom: number }>({
|
||||
zoom: Utils.asFloat(this.zoom.data) ?? layoutToUse.startzoom,
|
||||
lat: Utils.asFloat(this.lat.data) ?? layoutToUse.startLat,
|
||||
lon: Utils.asFloat(this.lon.data) ?? layoutToUse.startLon
|
||||
zoom: Utils.asFloat(this.zoom.data),
|
||||
lat: Utils.asFloat(this.lat.data),
|
||||
lon: Utils.asFloat(this.lon.data),
|
||||
}).addCallback((latlonz) => {
|
||||
this.zoom.setData(latlonz.zoom.toString());
|
||||
this.lat.setData(latlonz.lat.toString().substr(0, 6));
|
||||
this.lon.setData(latlonz.lon.toString().substr(0, 6));
|
||||
})
|
||||
});
|
||||
|
||||
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();
|
||||
});
|
||||
|
||||
const self = this;
|
||||
|
||||
function featSw(key: string, deflt: (layout: Layout) => boolean): UIEventSource<boolean> {
|
||||
const queryParameterSource = QueryParameters.GetQueryParameter(key, undefined);
|
||||
// I'm so sorry about someone trying to decipher this
|
||||
|
||||
|
||||
// It takes the current layout, extracts the default value for this query paramter. A query parameter event source is then retreived and flattened
|
||||
return UIEventSource.flatten(
|
||||
self.layoutToUse.map((layout) =>
|
||||
QueryParameters.GetQueryParameter(key, "" + deflt(layout)).map((str) => str === undefined ? deflt(layout) : str !== "false")), [queryParameterSource]);
|
||||
self.layoutToUse.map((layout) => {
|
||||
const defaultValue = deflt(layout);
|
||||
const queryParam = QueryParameters.GetQueryParameter(key, "" + defaultValue)
|
||||
return queryParam.map((str) => str === undefined ? defaultValue : (str !== "false"));
|
||||
}), [queryParameterSource]);
|
||||
}
|
||||
|
||||
|
||||
this.featureSwitchUserbadge = featSw("fs-userbadge", (layoutToUse) => layoutToUse?.enableUserBadge);
|
||||
this.featureSwitchSearch = featSw("fs-search", (layoutToUse) => layoutToUse?.enableSearch);
|
||||
this.featureSwitchLayers = featSw("fs-layers", (layoutToUse) => layoutToUse?.enableLayers);
|
||||
this.featureSwitchAddNew = featSw("fs-add-new", (layoutToUse) => layoutToUse?.enableAdd);
|
||||
this.featureSwitchUserbadge = featSw("fs-userbadge", (layoutToUse) => layoutToUse?.enableUserBadge ?? true);
|
||||
this.featureSwitchSearch = featSw("fs-search", (layoutToUse) => layoutToUse?.enableSearch ?? true);
|
||||
this.featureSwitchLayers = featSw("fs-layers", (layoutToUse) => layoutToUse?.enableLayers ?? true);
|
||||
this.featureSwitchAddNew = featSw("fs-add-new", (layoutToUse) => layoutToUse?.enableAdd ?? true);
|
||||
this.featureSwitchWelcomeMessage = featSw("fs-welcome-message", () => true);
|
||||
this.featureSwitchIframe = featSw("fs-iframe", () => false);
|
||||
this.featureSwitchMoreQuests = featSw("fs-more-quests", () => layoutToUse?.enableMoreQuests);
|
||||
this.featureSwitchShareScreen = featSw("fs-share-screen", () => layoutToUse?.enableShareScreen);
|
||||
this.featureSwitchGeolocation = featSw("fs-geolocation", () => layoutToUse?.enableGeolocation);
|
||||
this.featureSwitchMoreQuests = featSw("fs-more-quests", (layoutToUse) => layoutToUse?.enableMoreQuests ?? true);
|
||||
this.featureSwitchShareScreen = featSw("fs-share-screen", (layoutToUse) => layoutToUse?.enableShareScreen ?? true);
|
||||
this.featureSwitchGeolocation = featSw("fs-geolocation", (layoutToUse) => layoutToUse?.enableGeolocation ?? true);
|
||||
|
||||
this.osmConnection = new OsmConnection(
|
||||
QueryParameters.GetQueryParameter("test", "false").data === "true",
|
||||
QueryParameters.GetQueryParameter("oauth_token", undefined)
|
||||
);
|
||||
|
||||
CustomLayersState.InitFavouriteLayers(this);
|
||||
|
||||
|
||||
|
||||
this.favouriteLayers = 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) => {
|
||||
if (layoutToUse.supportedLanguages.indexOf(currentLanguage) < 0) {
|
||||
const layoutToUse = self.layoutToUse.data;
|
||||
if (layoutToUse === undefined) {
|
||||
return;
|
||||
}
|
||||
if (this.layoutToUse.data.supportedLanguages.indexOf(currentLanguage) < 0) {
|
||||
console.log("Resetting language 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.layoutToUse.map((layoutToUse) => {
|
||||
if (layoutToUse === undefined) {
|
||||
return "MapComplete";
|
||||
}
|
||||
return Translations.W(layoutToUse.title).InnerRender()
|
||||
}, [Locale.language]
|
||||
).addCallback((title) => {
|
||||
document.title = title
|
||||
});
|
||||
|
||||
|
||||
this.allElements = new ElementStorage();
|
||||
this.changes = new Changes(this);
|
||||
|
||||
if(State.runningFromConsole){
|
||||
if (State.runningFromConsole) {
|
||||
console.warn("running from console - not initializing map. Assuming test.html");
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -13,7 +13,7 @@ export class VerticalCombine extends UIElement {
|
|||
InnerRender(): string {
|
||||
let html = "";
|
||||
for (const element of this._elements) {
|
||||
if (!element.IsEmpty()) {
|
||||
if (element!== undefined && !element.IsEmpty()) {
|
||||
html += "<div>" + element.Render() + "</div>";
|
||||
}
|
||||
}
|
||||
|
|
114
UI/MoreScreen.ts
114
UI/MoreScreen.ts
|
@ -5,8 +5,12 @@ import {AllKnownLayouts} from "../Customizations/AllKnownLayouts";
|
|||
import Combine from "./Base/Combine";
|
||||
import {SubtleButton} from "./Base/SubtleButton";
|
||||
import {State} from "../State";
|
||||
import {CustomLayout} from "../Logic/CustomLayers";
|
||||
import {VariableUiElement} from "./Base/VariableUIElement";
|
||||
import {PersonalLayout} from "../Logic/PersonalLayout";
|
||||
import {FixedUiElement} from "./Base/FixedUiElement";
|
||||
import {Layout} from "../Customizations/Layout";
|
||||
import {CustomLayoutFromJSON} from "../Customizations/JSON/CustomLayoutFromJSON";
|
||||
import {All} from "../Customizations/Layouts/All";
|
||||
|
||||
|
||||
export class MoreScreen extends UIElement {
|
||||
|
@ -14,6 +18,48 @@ export class MoreScreen extends UIElement {
|
|||
constructor() {
|
||||
super(State.state.locationControl);
|
||||
this.ListenTo(State.state.osmConnection.userDetails);
|
||||
this.ListenTo(State.state.osmConnection._preferencesHandler.preferences);
|
||||
|
||||
}
|
||||
|
||||
private createLinkButton(layout: Layout, customThemeDefinition: string = undefined) {
|
||||
if (layout.hideFromOverview && State.state.osmConnection.userDetails.data.name !== "Pieter Vander Vennet") {
|
||||
return undefined;
|
||||
}
|
||||
if (layout.name === State.state.layoutToUse.data.name) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (layout.name === PersonalLayout.NAME) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const currentLocation = State.state.locationControl.data;
|
||||
let linkText =
|
||||
`./${layout.name}.html?z=${currentLocation.zoom}&lat=${currentLocation.lat}&lon=${currentLocation.lon}`
|
||||
|
||||
if (location.hostname === "localhost" || location.hostname === "127.0.0.1") {
|
||||
linkText = `./index.html?layout=${layout.name}&z=${currentLocation.zoom}&lat=${currentLocation.lat}&lon=${currentLocation.lon}`
|
||||
}
|
||||
|
||||
if (customThemeDefinition) {
|
||||
linkText = `./index.html?userlayout=${layout.name}&z=${currentLocation.zoom}&lat=${currentLocation.lat}&lon=${currentLocation.lon}#${customThemeDefinition}`
|
||||
|
||||
}
|
||||
|
||||
let description = Translations.W(layout.description);
|
||||
if (description !== undefined) {
|
||||
description = new Combine(["<br/>", description]);
|
||||
}
|
||||
const link =
|
||||
new SubtleButton(layout.icon,
|
||||
new Combine([
|
||||
"<b>",
|
||||
Translations.W(layout.title),
|
||||
"</b>",
|
||||
description ?? "",
|
||||
]), {url: linkText, newTab: false})
|
||||
return link;
|
||||
}
|
||||
|
||||
InnerRender(): string {
|
||||
|
@ -28,7 +74,7 @@ export class MoreScreen extends UIElement {
|
|||
return tr.requestATheme.Render();
|
||||
}
|
||||
return new SubtleButton("./assets/pencil.svg", tr.createYourOwnTheme, {
|
||||
url: "https://pietervdvn.github.io/MapComplete/customGenerator.html",
|
||||
url: "./customGenerator.html",
|
||||
newTab: false
|
||||
}).Render();
|
||||
})
|
||||
|
@ -53,40 +99,44 @@ export class MoreScreen extends UIElement {
|
|||
|
||||
|
||||
for (const k in AllKnownLayouts.allSets) {
|
||||
const layout = AllKnownLayouts.allSets[k]
|
||||
if (layout.hideFromOverview && State.state.osmConnection.userDetails.data.name !== "Pieter Vander Vennet") {
|
||||
continue
|
||||
els.push(this.createLinkButton(AllKnownLayouts.allSets[k]));
|
||||
}
|
||||
|
||||
const installedThemes = State.state.osmConnection._preferencesHandler.preferences.map(allPreferences => {
|
||||
const installedThemes = [];
|
||||
if(allPreferences === undefined){
|
||||
return installedThemes;
|
||||
}
|
||||
if (layout.name === State.state.layoutToUse.data.name) {
|
||||
|
||||
for (const allPreferencesKey in allPreferences) {
|
||||
"mapcomplete-installed-theme-Superficie-combined-length"
|
||||
const themename = allPreferencesKey.match(/^mapcomplete-installed-theme-(.*)-combined-length$/);
|
||||
if(themename){
|
||||
installedThemes.push(themename[1]);
|
||||
}
|
||||
}
|
||||
|
||||
return installedThemes;
|
||||
|
||||
})
|
||||
const customThemesNames = installedThemes.data ?? [];
|
||||
if (customThemesNames !== []) {
|
||||
els.push(Translations.t.general.customThemeIntro)
|
||||
}
|
||||
|
||||
console.log(customThemesNames);
|
||||
for (const installedThemeName of customThemesNames) {
|
||||
if(installedThemeName === ""){
|
||||
continue;
|
||||
}
|
||||
|
||||
if (layout.name === CustomLayout.NAME) {
|
||||
continue;
|
||||
const customThemeDefinition = State.state.osmConnection.GetLongPreference("installed-theme-" + installedThemeName);
|
||||
try {
|
||||
const layout = CustomLayoutFromJSON.FromQueryParam(customThemeDefinition.data);
|
||||
els.push(this.createLinkButton(layout, customThemeDefinition.data));
|
||||
} catch (e) {
|
||||
console.log(customThemeDefinition.data);
|
||||
console.warn("Could not parse custom layout from preferences: ", installedThemeName, e);
|
||||
}
|
||||
|
||||
const currentLocation = State.state.locationControl.data;
|
||||
let linkText =
|
||||
`./${layout.name}.html?z=${currentLocation.zoom}&lat=${currentLocation.lat}&lon=${currentLocation.lon}`
|
||||
|
||||
if (location.hostname === "localhost" || location.hostname === "127.0.0.1") {
|
||||
linkText = `./index.html?layout=${layout.name}&z=${currentLocation.zoom}&lat=${currentLocation.lat}&lon=${currentLocation.lon}`
|
||||
}
|
||||
|
||||
let description = Translations.W(layout.description);
|
||||
if (description !== undefined) {
|
||||
description = new Combine(["<br/>", description]);
|
||||
}
|
||||
const link =
|
||||
new SubtleButton(layout.icon,
|
||||
new Combine([
|
||||
"<b>",
|
||||
Translations.W(layout.title),
|
||||
"</b>",
|
||||
description ?? "",
|
||||
]), {url: linkText, newTab: false});
|
||||
|
||||
els.push(link)
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -22,11 +22,11 @@ export class WelcomeMessage extends UIElement {
|
|||
|
||||
constructor() {
|
||||
super(State.state.osmConnection.userDetails);
|
||||
this.languagePicker = Utils.CreateLanguagePicker(Translations.t.general.pickLanguage);
|
||||
this.ListenTo(Locale.language);
|
||||
this.languagePicker = Utils.CreateLanguagePicker(Translations.t.general.pickLanguage);
|
||||
|
||||
function fromLayout(f: (layout: Layout) => (string | UIElement)): UIElement {
|
||||
return Translations.W(f(State.state.layoutToUse.data))
|
||||
return Translations.W(f(State.state.layoutToUse.data));
|
||||
}
|
||||
|
||||
this.description = fromLayout((layout) => layout.welcomeMessage);
|
||||
|
|
|
@ -901,7 +901,8 @@ export default class Translations {
|
|||
nl: " of <a href='https://www.openstreetmap.org/user/new' target='_blank'>maak een nieuwe account aan</a> ",
|
||||
fr: " ou <a href='https://www.openstreetmap.org/user/new' target='_blank'>registrez vous</a>"
|
||||
}),
|
||||
noTagsSelected: new T({en: "No tags selected"})
|
||||
noTagsSelected: new T({en: "No tags selected"}),
|
||||
customThemeIntro: new T({en:"<h3>Custom themes</h3>These are previously visited user-generated themes."})
|
||||
|
||||
},
|
||||
favourite: {
|
||||
|
|
13
Utils.ts
13
Utils.ts
|
@ -72,5 +72,18 @@ export class Utils {
|
|||
}
|
||||
return str.substr(0, l - 3)+"...";
|
||||
}
|
||||
|
||||
public static Dedup(arr: string[]):string[]{
|
||||
if(arr === undefined){
|
||||
return undefined;
|
||||
}
|
||||
const newArr = [];
|
||||
for (const string of arr) {
|
||||
if(newArr.indexOf(string) < 0){
|
||||
newArr.push(string);
|
||||
}
|
||||
}
|
||||
return newArr;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
#! /bin/bash
|
||||
|
||||
mkdir assets/generated
|
||||
ts-node createLayouts.ts
|
||||
find -name '*.png' | parallel optipng '{}'
|
||||
npm run build
|
||||
rm -rf /home/pietervdvn/git/pietervdvn.github.io/MapComplete/*
|
||||
cp -r dist/* /home/pietervdvn/git/pietervdvn.github.io/MapComplete/
|
||||
|
|
16
index.ts
16
index.ts
|
@ -13,10 +13,11 @@ import {InitUiElements} from "./InitUiElements";
|
|||
import {StrayClickHandler} from "./Logic/Leaflet/StrayClickHandler";
|
||||
import {GeoLocationHandler} from "./Logic/Leaflet/GeoLocationHandler";
|
||||
import {State} from "./State";
|
||||
import {CustomLayout} from "./Logic/CustomLayers";
|
||||
import {CustomLayoutFromJSON} from "./Customizations/JSON/CustomLayoutFromJSON";
|
||||
import {QueryParameters} from "./Logic/Web/QueryParameters";
|
||||
import {LocalStorageSource} from "./Logic/Web/LocalStorageSource";
|
||||
import {PersonalLayout} from "./Logic/PersonalLayout";
|
||||
import {OsmConnection} from "./Logic/Osm/OsmConnection";
|
||||
|
||||
TagRendering.injectFunction();
|
||||
|
||||
|
@ -109,7 +110,9 @@ console.log("Using layout: ", layoutToUse.name);
|
|||
State.state = new State(layoutToUse);
|
||||
if (layoutFromBase64 !== "false") {
|
||||
State.state.layoutDefinition = hash.substr(1);
|
||||
console.log(State.state.layoutDefinition)
|
||||
State.state.osmConnection.OnLoggedIn(() => {
|
||||
State.state.osmConnection.GetLongPreference("installed-theme-"+layoutToUse.name).setData(State.state.layoutDefinition);
|
||||
})
|
||||
}
|
||||
InitUiElements.InitBaseMap();
|
||||
|
||||
|
@ -152,8 +155,8 @@ function setupAllLayerElements() {
|
|||
|
||||
setupAllLayerElements();
|
||||
|
||||
if (layoutToUse === AllKnownLayouts.allSets[CustomLayout.NAME]) {
|
||||
State.state.favourteLayers.addCallback((favs) => {
|
||||
if (layoutToUse === AllKnownLayouts.allSets[PersonalLayout.NAME]) {
|
||||
State.state.favouriteLayers.addCallback((favs: string[]) => {
|
||||
layoutToUse.layers = [];
|
||||
for (const fav of favs) {
|
||||
const layer = AllKnownLayouts.allLayers[fav];
|
||||
|
@ -161,9 +164,10 @@ if (layoutToUse === AllKnownLayouts.allSets[CustomLayout.NAME]) {
|
|||
layoutToUse.layers.push(layer);
|
||||
}
|
||||
setupAllLayerElements();
|
||||
};
|
||||
}
|
||||
;
|
||||
State.state.locationControl.ping();
|
||||
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
|
|
1323
package-lock.json
generated
1323
package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
@ -39,6 +39,7 @@
|
|||
"@babel/polyfill": "^7.10.4",
|
||||
"@types/node": "^7.0.5",
|
||||
"assert": "^2.0.0",
|
||||
"canvas": "^2.6.1",
|
||||
"chai": "^4.2.0",
|
||||
"fs": "0.0.1-security",
|
||||
"marked": "^1.1.1",
|
||||
|
|
|
@ -57,5 +57,5 @@ function createTable(preferences: any) {
|
|||
el.AttachTo("maindiv");
|
||||
}
|
||||
|
||||
connection.preferences.addCallback((prefs) => createTable(prefs))
|
||||
connection._preferencesHandler.preferences.addCallback((prefs) => createTable(prefs))
|
||||
|
||||
|
|
23
test.ts
23
test.ts
|
@ -1,10 +1,15 @@
|
|||
import {TextField, ValidatedTextField} from "./UI/Input/TextField";
|
||||
import {CustomLayoutFromJSON} from "./Customizations/JSON/CustomLayoutFromJSON";
|
||||
import {And} from "./Logic/TagsFilter";
|
||||
import {OsmConnection} from "./Logic/Osm/OsmConnection";
|
||||
import {UIEventSource} from "./Logic/UIEventSource";
|
||||
|
||||
const tags = CustomLayoutFromJSON.TagsFromJson("indoor=yes&access!=private");
|
||||
console.log(tags);
|
||||
const m0 = new And(tags).matches([{k:"indoor",v:"yes"}, {k:"access",v: "yes"}]);
|
||||
console.log("Matches 0", m0)
|
||||
const m1 = new And(tags).matches([{k:"indoor",v:"yes"}, {k:"access",v: "private"}]);
|
||||
console.log("Matches 1", m1)
|
||||
const conn = new OsmConnection(true, new UIEventSource<string>(undefined));
|
||||
conn.AttemptLogin();
|
||||
|
||||
conn.userDetails.addCallback(userDetails => {
|
||||
if (!userDetails.loggedIn) {
|
||||
return;
|
||||
}
|
||||
const str = "01234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789";
|
||||
console.log(str.length);
|
||||
conn.GetLongPreference("test").setData(str);
|
||||
// console.log(got.length)
|
||||
});
|
||||
|
|
Loading…
Reference in a new issue