Performance improvements, add clock

This commit is contained in:
Pieter Vander Vennet 2020-12-05 03:22:17 +01:00
parent c2b1f6643b
commit efd7631837
21 changed files with 2947 additions and 105 deletions

View file

@ -181,7 +181,7 @@ export class InitUiElements {
// This layer is the layer that gives the questions
const featureBox = new FeatureInfoBox(
State.state.allElements.getElement(data.id),
State.state.allElements.getEventSourceById(data.id),
layer
);

View file

@ -42,10 +42,14 @@ export class ElementStorage {
}
}
getElement(elementId): UIEventSource<any> {
getEventSourceById(elementId): UIEventSource<any> {
if (elementId in this._elements) {
return this._elements[elementId];
}
console.log("Can not find eventsource with id ", elementId);
console.error("Can not find eventsource with id ", elementId);
}
getEventSourceFor(feature): UIEventSource<any> {
return this.getEventSourceById(feature.properties.id);
}
}

View file

@ -7,6 +7,7 @@ import {UIElement} from "../UI/UIElement";
import State from "../State";
import LayerConfig from "../Customizations/JSON/LayerConfig";
import Hash from "./Web/Hash";
import LazyElement from "../UI/Base/LazyElement";
/***
* A filtered layer is a layer which offers a 'set-data' function
@ -78,7 +79,7 @@ export class FilteredLayer {
const tags = TagUtils.proprtiesToKV(feature.properties);
const matches = this.filters.matches(tags);
if (matches) {
selfFeatures.push(feature);
selfFeatures.push(feature);
}
if (!matches || this.layerDef.passAllFeatures) {
leftoverFeatures.push(feature);
@ -130,14 +131,13 @@ export class FilteredLayer {
let self = this;
this._geolayer = L.geoJSON(data, {
style: feature => {
const tagsSource = State.state.allElements.getElement(feature.properties.id);
const tagsSource = State.state.allElements.getEventSourceFor(feature);
return self.layerDef.GenerateLeafletStyle(tagsSource, self._showOnPopup !== undefined);
},
pointToLayer: function (feature, latLng) {
// Point to layer converts the 'point' to a layer object - as the geojson layer natively cannot handle points
// Click handling is done in the next step
const tagSource = State.state.allElements.getElement(feature.properties.id);
const tagSource = State.state.allElements.getEventSourceFor(feature);
const style = self.layerDef.GenerateLeafletStyle(tagSource, self._showOnPopup !== undefined);
let marker;
@ -169,10 +169,9 @@ export class FilteredLayer {
closeOnEscapeKey: true,
}, layer);
let uiElement: UIElement;
const eventSource = State.state.allElements.addOrGetElement(feature);
uiElement = self._showOnPopup(eventSource, feature);
const eventSource = State.state.allElements.getEventSourceFor(feature);
let uiElement: LazyElement = new LazyElement(() => self._showOnPopup(eventSource, feature));
popup.setContent(uiElement.Render());
layer.bindPopup(popup);
// We first render the UIelement (which'll still need an update later on...)
@ -181,11 +180,16 @@ export class FilteredLayer {
layer.on("click", (e) => {
// We set the element as selected...
uiElement.Activate();
State.state.selectedElement.setData(feature);
uiElement.Update();
});
if (feature.properties.id.replace(/\//g, "_") === Hash.Get().data) {
// This element is in the URL, so this is a share link
// We already open it
uiElement.Activate();
popup.setContent(uiElement.Render());
const center = GeoOperations.centerpoint(feature).geometry.coordinates;
popup.setLatLng({lat: center[1], lng: center[0]});
popup.openOn(State.state.bm.map);

View file

@ -1,11 +1,9 @@
import {GeoOperations} from "./GeoOperations";
import CodeGrid from "./Web/CodeGrid";
import State from "../State";
import opening_hours from "opening_hours";
import {And, Or, Tag} from "./Tags";
import {Utils} from "../Utils";
import CountryCoder from "latlon2country/lib/countryCoder";
import CountryCoder from "latlon2country"
class SimpleMetaTagger {
private _f: (feature: any, index: number) => void;
@ -62,57 +60,60 @@ export default class MetaTagging {
})
);
private static country = new SimpleMetaTagger(
["_country"], "",
((feature, index) => {
["_country"], "The country code of the property (with latlon2country)",
(feature, index) => {
const coder = new CountryCoder("https://pietervdvn.github.io/latlon2country/");
const centerPoint = GeoOperations.centerpoint(feature);
let centerPoint: any = GeoOperations.centerpoint(feature);
const lat = centerPoint.geometry.coordinates[1];
const lon = centerPoint.geometry.coordinates[0]
// But the codegrid SHOULD be a number!
coder.CountryCodeFor(lon, lat, (countries) => {
feature.properties["_country"] = countries[0];
console.log("Country is ",countries.join(";"))
coder.GetCountryCodeFor(lon, lat, (countries) => {
feature.properties["_country"] = countries[0].trim().toLowerCase();
const tagsSource = State.state.allElements.getEventSourceFor(feature);
tagsSource.ping();
});
})
}
)
private static isOpen = new SimpleMetaTagger(
["_isOpen", "_isOpen:description"],
"If 'opening_hours' is present, it will add the current state of the feature (being 'yes' or 'no')",
(feature => {
const tagsSource = State.state.allElements.addOrGetElement(feature);
tagsSource.addCallback(tags => {
if (tags["opening_hours"] !== undefined && tags["_country"] !== undefined) {
if (tags._isOpen !== undefined) {
// Already defined
return;
}
const oh = new opening_hours(tags["opening_hours"], {
lat: tags._lat,
lon: tags._lon,
address: {
country_code: tags._country.toLowerCase()
}
}, {tag_key: "opening_hours"});
const updateTags = () => {
tags["_isOpen"] = oh.getState() ? "yes" : "no";
const comment = oh.getComment();
if (comment) {
tags["_isOpen:description"] = comment;
}
const nextChange = oh.getNextChange() as Date;
if (nextChange !== undefined) {
window.setTimeout(
updateTags,
(nextChange.getTime() - (new Date()).getTime())
)
}
}
updateTags();
const tagsSource = State.state.allElements.getEventSourceFor(feature);
tagsSource.addCallbackAndRun(tags => {
if (tags.opening_hours === undefined || tags._country === undefined) {
return;
}
const oh = new opening_hours(tags["opening_hours"], {
lat: tags._lat,
lon: tags._lon,
address: {
country_code: tags._country.toLowerCase()
}
}, {tag_key: "opening_hours"});
// AUtomatically triggered on the next change
const updateTags = () => {
const oldValueIsOpen = tags["_isOpen"];
tags["_isOpen"] = oh.getState() ? "yes" : "no";
const comment = oh.getComment();
if (comment) {
tags["_isOpen:description"] = comment;
}
if (oldValueIsOpen !== tags._isOpen) {
tagsSource.ping();
}
const nextChange = oh.getNextChange() as Date;
if (nextChange !== undefined) {
window.setTimeout(
updateTags,
(nextChange.getTime() - (new Date()).getTime())
)
}
}
updateTags();
})
})

View file

@ -18,7 +18,7 @@ export class Changes {
if (changes.length == 0) {
return;
}
const eventSource = tags ?? State.state?.allElements.getElement(elementId);
const eventSource = tags ?? State.state?.allElements.getEventSourceById(elementId);
const elementTags = eventSource.data;
const pending : {elementId:string, key: string, value: string}[] = [];
for (const change of changes) {

View file

@ -181,7 +181,7 @@ export class ChangesetHandler {
continue;
}
console.log("Rewriting id: ", oldId, "-->", newId);
const element = allElements.getElement("node/" + oldId);
const element = allElements.getEventSourceById("node/" + oldId);
element.data.id = "node/" + newId;
allElements.addElementById("node/" + newId, element);
element.ping();

View file

@ -96,17 +96,20 @@ export class UpdateFromOverpass {
const self = this;
self.retries.setData(0);
let newIds = 1;
for (const feature of geojson.features) {
if(feature.properties.id === undefined){
feature.properties.id = "ext/"+newIds;
if (feature.properties.id === undefined) {
feature.properties.id = "ext/" + newIds;
newIds++;
}
}
geojson.features.forEach(feature => {
State.state.allElements.addElement(feature);
})
MetaTagging.addMetatags(geojson.features);
function renderLayers(layers: FilteredLayer[]) {
if (layers.length === 0) {
self.runningQuery.setData(false);
@ -125,6 +128,7 @@ export class UpdateFromOverpass {
}, 50)
}
renderLayers(State.state.filteredLayers.data);
}

View file

@ -262,4 +262,6 @@ https://commons.wikimedia.org/wiki/File:Shower_symbol.svg
Bench icons from StreetComplete: https://github.com/westnordost/StreetComplete/tree/v25.0-beta1/res/graphics/quest%20icons, GPLv3.0
Urinal icon: https://thenounproject.com/term/urinal/1307984/
Urinal icon: https://thenounproject.com/term/urinal/1307984/
24/7 icon: https://www.vecteezy.com/vector-art/1394992-24-7-service-and-support-icon-set

12
Svg.ts

File diff suppressed because one or more lines are too long

30
UI/Base/LazyElement.ts Normal file
View file

@ -0,0 +1,30 @@
import {UIElement} from "../UIElement";
export default class LazyElement extends UIElement {
private _content: UIElement = undefined;
public Activate: () => void;
constructor(content: (() => UIElement)) {
super();
this.dumbMode = false;
const self = this;
this.Activate = () => {
if (this._content === undefined) {
self._content = content();
}
self.Update();
}
}
InnerRender(): string {
if (this._content === undefined) {
return "Rendering...";
}
return this._content.InnerRender();
}
}

View file

@ -12,7 +12,7 @@ export class TextField extends InputElement<string> {
private readonly _htmlType: string;
private readonly _textAreaRows: number;
private readonly _isValid: (string, country) => boolean;
private readonly _isValid: (string,country) => boolean;
private _label: UIElement;
constructor(options?: {
@ -22,7 +22,7 @@ export class TextField extends InputElement<string> {
htmlType?: string,
label?: UIElement,
textAreaRows?: number,
isValid?: ((s: string, country?: string) => boolean)
isValid?: ((s: string, country?: () => string) => boolean)
}) {
super(undefined);
const self = this;

View file

@ -14,8 +14,8 @@ import DirectionInput from "./DirectionInput";
interface TextFieldDef {
name: string,
explanation: string,
isValid: ((s: string, country?: string) => boolean),
reformat?: ((s: string, country?: string) => string),
isValid: ((s: string, country?:() => string) => boolean),
reformat?: ((s: string, country?: () => string) => string),
inputHelper?: (value: UIEventSource<string>, options?: {
location: [number, number]
}) => InputElement<string>,
@ -26,8 +26,8 @@ export default class ValidatedTextField {
private static tp(name: string,
explanation: string,
isValid?: ((s: string, country?: string) => boolean),
reformat?: ((s: string, country?: string) => string),
isValid?: ((s: string, country?: () => string) => boolean),
reformat?: ((s: string, country?: () => string) => string),
inputHelper?: (value: UIEventSource<string>, options?:{
location: [number, number]
}) => InputElement<string>): TextFieldDef {
@ -154,13 +154,14 @@ export default class ValidatedTextField {
ValidatedTextField.tp(
"phone",
"A phone number",
(str, country: any) => {
(str, country: () => string) => {
if (str === undefined) {
return false;
}
return parsePhoneNumberFromString(str, country?.toUpperCase())?.isValid() ?? false
console.log("Validating phone number",str,"in country",country())
return parsePhoneNumberFromString(str, (country())?.toUpperCase() as any)?.isValid() ?? false
},
(str, country: any) => parsePhoneNumberFromString(str, country?.toUpperCase()).formatInternational()
(str, country: () => string) => parsePhoneNumberFromString(str, (country())?.toUpperCase() as any).formatInternational()
),
ValidatedTextField.tp(
"opening_hours",
@ -200,8 +201,8 @@ export default class ValidatedTextField {
value?: UIEventSource<string>,
textArea?: boolean,
textAreaRows?: number,
isValid?: ((s: string, country: string) => boolean),
country?: string,
isValid?: ((s: string, country: () => string) => boolean),
country?: () => string,
location?: [number /*lat*/, number /*lon*/]
}): InputElement<string> {
options = options ?? {};
@ -304,7 +305,7 @@ export default class ValidatedTextField {
textArea?: boolean,
textAreaRows?: number,
isValid?: ((string: string) => boolean),
country?: string
country?: () => string
}): InputElement<T> {
let textField: InputElement<string>;
if (options?.type) {

View file

@ -151,7 +151,7 @@ export default class OpeningHoursVisualization extends UIElement {
const tags = this._source.data;
if (tags._country === undefined) {
return "Loading...";
return "Loading country information...";
}
let oh = null;
@ -165,7 +165,7 @@ export default class OpeningHoursVisualization extends UIElement {
}, {tag_key: this._key});
} catch (e) {
console.log(e);
return "Error: could not visualize these opening hours"
return `Error: could not visualize these opening hours<br/><spann class='subtle'>${e}</spann>`
}
if (!oh.getState() && !oh.getUnknown()) {

View file

@ -251,7 +251,7 @@ export default class TagRenderingQuestion extends UIElement {
const textField = ValidatedTextField.InputForType(this._configuration.freeform.type, {
isValid: (str) => (str.length <= 255),
country: this._tags.data._country,
country: () => this._tags.data._country,
location: [this._tags.data._lat, this._tags.data._lon]
});

View file

@ -169,5 +169,14 @@ export class Utils {
}
static MatchKeys(object: any, prototype: any, context?: string){
for (const objectKey in object) {
if(prototype[objectKey] === undefined){
console.error("Key ", objectKey, "might be not supported (in context",context,")")
}
}
}
}

22
assets/svg/clock.svg Normal file
View file

@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
width="100"
height="100"
viewBox="0 0 100 100"
version="1.1"
style="fill:none">
<circle
cx="50"
cy="50"
r="45.0"
id="circle4"
style="fill:none;fill-opacity:1;stroke:#000000;stroke-width:10;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
<path
id="path823"
d="M 46,55.932203 H 66.550847"
style="fill:none;stroke:#000000;stroke-width:9;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
<path
id="path825"
d="m 46,55.533898 v -27.805085 0"
style="fill:none;stroke:#000000;stroke-width:9;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
</svg>

After

Width:  |  Height:  |  Size: 805 B

42
assets/svg/closed.svg Normal file
View file

@ -0,0 +1,42 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
id="svg3"
width="100"
height="100"
viewBox="0 0 100 100"
version="1.1"
style="fill:none">
<metadata
id="metadata9">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<defs
id="defs7" />
<circle
cx="50"
cy="50"
r="45.408772"
id="circle4"
style="fill:none;fill-opacity:1;stroke:#180000;stroke-width:7.5;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
<path
id="path823"
d="M 46,55.932203 H 66.550847"
style="fill:none;stroke:#180000;stroke-width:7;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
<path
id="path825"
d="m 46,55.533898 v -27.805085 0"
style="fill:none;stroke:#180000;stroke-width:7.38227367;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

View file

@ -73,6 +73,16 @@
},
"tagRenderings": [
"images",
{
"question": {
"en": "What is the name of this shop?",
"fr": "Qu'est-ce que le nom de ce magasin?"
},
"render": "This shop is called <i>{name}</i>",
"freeform": {
"key": "name"
}
},
{
"render": {
"en": "This shop sells {shop}",
@ -226,6 +236,28 @@
"icon": {
"render": "./assets/themes/shops/shop.svg"
},
"iconOverlays": [
{
"if": "_isOpen=yes",
"then": "clock:#0f0",
"badge": true
},
{
"if": "_isOpen=no",
"then": "circle:#f00;clock:#fff",
"badge": true
},
{
"if": {
"and": [
"_isOpen=",
"opening_hours~*"
]
},
"then": "clock:#ff0",
"badge": true
}
],
"width": {
"render": "8"
},

2714
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -20,9 +20,9 @@
"generate:translations": "ts-node scripts/generateTranslations.ts",
"generate:layouts": "ts-node scripts/createLayouts.ts",
"optimize-images": "cd assets/generated/ && find -name '*.png' -exec optipng '{}' \\; && echo 'PNGs are optimized'",
"generate": "npm run generate:images && npm run generate:translations && npm run generate:layouts && npm run generate:editor-layer-index",
"generate": "npm run generate:images && npm run generate:translations",
"build": "rm -rf dist/ && npm run generate && parcel build --public-url ./ *.html assets/** assets/**/** assets/**/**/** vendor/* vendor/*/*",
"prepare-deploy": "npm run generate && npm run build && rm -rf .cache",
"prepare-deploy": "npm run generate:editor-layer-index && npm run generate:layouts && npm run generate && npm run build && rm -rf .cache",
"deploy:staging": "npm run prepare-deploy && rm -rf /home/pietervdvn/git/pietervdvn.github.io/Staging/* && cp -r dist/* /home/pietervdvn/git/pietervdvn.github.io/Staging/ && cd /home/pietervdvn/git/pietervdvn.github.io/ && git add * && git commit -m 'New MapComplete Version' && git push && cd - && npm run clean",
"deploy:production": "rm -rf ./assets/generated && npm run prepare-deploy && npm run optimize-images && rm -rf /home/pietervdvn/git/pietervdvn.github.io/MapComplete/* && cp -r dist/* /home/pietervdvn/git/pietervdvn.github.io/MapComplete/ && cd /home/pietervdvn/git/pietervdvn.github.io/ && git add * && git commit -m 'New MapComplete Version' && git push && cd - && npm run clean",
"clean": "rm -rf .cache/ && (find *.html | grep -v \"\\(index\\|land\\|test\\|preferences\\|customGenerator\\).html\" | xargs rm) && (find *.webmanifest | xargs rm)"
@ -35,13 +35,12 @@
"license": "MIT",
"dependencies": {
"@types/leaflet-providers": "^1.2.0",
"codegrid-js": "git://github.com/hlaw/codegrid-js.git",
"country-language": "^0.1.7",
"email-validator": "^2.0.4",
"escape-html": "^1.0.3",
"i18next-client": "^1.11.4",
"jquery": "latest",
"latlon2country": "^1.0.3",
"latlon2country": "^1.0.7",
"leaflet": "^1.7.1",
"leaflet-providers": "^1.10.2",
"libphonenumber": "0.0.10",

14
test.ts
View file

@ -1,17 +1,5 @@
//*
/*
import CountryCoder from "latlon2country/lib/countryCoder";
f
unction pr(countries) {
console.log(">>>>>", countries.join(";"))
}
coder.CountryCodeFor(3.2, 51.2, pr)
coder.CountryCodeFor(4.2, 51.2, pr);
coder.CountryCodeFor(4.92119, 51.43995, pr)
coder.CountryCodeFor(4.93189, 51.43552, pr)
coder.CountryCodeFor(34.2581, 44.7536, pr)
/*/