Merge master

This commit is contained in:
Pieter Vander Vennet 2022-06-19 18:23:43 +02:00
commit 470e9acc64
66 changed files with 10798 additions and 414 deletions

View file

@ -9,7 +9,6 @@ import BaseUIElement from "../UI/BaseUIElement";
import {UIEventSource} from "./UIEventSource";
import {LocalStorageSource} from "./Web/LocalStorageSource";
import LZString from "lz-string";
import * as personal from "../assets/themes/personal/personal.json";
import {FixLegacyTheme} from "../Models/ThemeConfig/Conversion/LegacyJsonConvert";
import {LayerConfigJson} from "../Models/ThemeConfig/Json/LayerConfigJson";
import SharedTagRenderings from "../Customizations/SharedTagRenderings";
@ -52,17 +51,7 @@ export default class DetermineLayout {
console.log("Using layout", layoutId);
}
layoutId = QueryParameters.GetQueryParameter("layout", layoutId, "The layout to load into MapComplete").data;
const layoutToUse: LayoutConfig = AllKnownLayouts.allKnownLayouts.get(layoutId?.toLowerCase());
if (layoutToUse?.id === personal.id) {
layoutToUse.layers = AllKnownLayouts.AllPublicLayers()
for (const layer of layoutToUse.layers) {
layer.minzoomVisible = Math.max(layer.minzoomVisible, layer.minzoom)
layer.minzoom = Math.max(16, layer.minzoom)
}
}
return layoutToUse
return AllKnownLayouts.allKnownLayouts.get(layoutId?.toLowerCase())
}
public static LoadLayoutFromHash(
@ -161,7 +150,8 @@ export default class DetermineLayout {
}
const converState = {
tagRenderings: SharedTagRenderings.SharedTagRenderingJson,
sharedLayers: knownLayersDict
sharedLayers: knownLayersDict,
publicLayers: new Set<string>()
}
json = new FixLegacyTheme().convertStrict(json, "While loading a dynamic theme")
const raw = json;

View file

@ -79,7 +79,7 @@ export default class GeoJsonSource implements FeatureSourceForLayer, Tiled {
private LoadJSONFrom(url: string) {
const eventSource = this.features;
const self = this;
Utils.downloadJson(url)
Utils.downloadJsonCached(url, 60 * 60)
.then(json => {
self.state.setData("loaded")
if (json.features === undefined || json.features === null) {

View file

@ -34,7 +34,7 @@ export default class GenericImageProvider extends ImageProvider {
return undefined;
}
protected DownloadAttribution(url: string) {
public DownloadAttribution(url: string) {
return undefined
}

View file

@ -1,4 +1,4 @@
import {Store, Stores, UIEventSource} from "../UIEventSource";
import {Store, UIEventSource} from "../UIEventSource";
import BaseUIElement from "../../UI/BaseUIElement";
import {LicenseInfo} from "./LicenseInfo";
import {Utils} from "../../Utils";
@ -12,19 +12,7 @@ export interface ProvidedImage {
export default abstract class ImageProvider {
public abstract readonly defaultKeyPrefixes: string[]
private _cache = new Map<string, Store<LicenseInfo>>()
GetAttributionFor(url: string): Store<LicenseInfo> {
const cached = this._cache.get(url);
if (cached !== undefined) {
return cached;
}
const src = Stores.FromPromise(this.DownloadAttribution(url))
this._cache.set(url, src)
return src;
}
public abstract SourceIcon(backlinkSource?: string): BaseUIElement;
/**
@ -75,6 +63,6 @@ export default abstract class ImageProvider {
public abstract ExtractUrls(key: string, value: string): Promise<Promise<ProvidedImage>[]>;
protected abstract DownloadAttribution(url: string): Promise<LicenseInfo>;
public abstract DownloadAttribution(url: string): Promise<LicenseInfo>;
}

View file

@ -99,11 +99,23 @@ export class Imgur extends ImageProvider {
return []
}
protected DownloadAttribution: (url: string) => Promise<LicenseInfo> = async (url: string) => {
/**
* Download the attribution from attribution
*
* const data = {"data":{"id":"I9t6B7B","title":"Station Knokke","description":"author:Pieter Vander Vennet\r\nlicense:CC-BY 4.0\r\nosmid:node\/9812712386","datetime":1655052078,"type":"image\/jpeg","animated":false,"width":2400,"height":1795,"size":910872,"views":2,"bandwidth":1821744,"vote":null,"favorite":false,"nsfw":false,"section":null,"account_url":null,"account_id":null,"is_ad":false,"in_most_viral":false,"has_sound":false,"tags":[],"ad_type":0,"ad_url":"","edited":"0","in_gallery":false,"link":"https:\/\/i.imgur.com\/I9t6B7B.jpg","ad_config":{"safeFlags":["not_in_gallery","share"],"highRiskFlags":[],"unsafeFlags":["sixth_mod_unsafe"],"wallUnsafeFlags":[],"showsAds":false,"showAdLevel":1}},"success":true,"status":200}
* Utils.injectJsonDownloadForTests("https://api.imgur.com/3/image/E0RuAK3", data)
* const licenseInfo = await Imgur.singleton.DownloadAttribution("https://i.imgur.com/E0RuAK3.jpg")
* const expected = new LicenseInfo()
* expected.licenseShortName = "CC-BY 4.0"
* expected.artist = "Pieter Vander Vennet"
* licenseInfo // => expected
*/
public async DownloadAttribution (url: string) : Promise<LicenseInfo> {
const hash = url.substr("https://i.imgur.com/".length).split(".jpg")[0];
const apiUrl = 'https://api.imgur.com/3/image/' + hash;
const response = await Utils.downloadJson(apiUrl, {Authorization: 'Client-ID ' + Constants.ImgurApiKey})
const response = await Utils.downloadJsonCached(apiUrl, 365*24*60*60,
{Authorization: 'Client-ID ' + Constants.ImgurApiKey})
const descr: string = response.data.description ?? "";
const data: any = {};

View file

@ -1,7 +1,7 @@
export class LicenseInfo {
title: string = ""
artist: string = "";
license: string = "";
license: string = undefined;
licenseShortName: string = "";
usageTerms: string = "";
attributionRequired: boolean = false;

View file

@ -4,7 +4,6 @@ import Svg from "../../Svg";
import {Utils} from "../../Utils";
import {LicenseInfo} from "./LicenseInfo";
import Constants from "../../Models/Constants";
import * as Console from "console";
export class Mapillary extends ImageProvider {
@ -82,7 +81,7 @@ export class Mapillary extends ImageProvider {
return [this.PrepareUrlAsync(key, value)]
}
protected async DownloadAttribution(url: string): Promise<LicenseInfo> {
public async DownloadAttribution(url: string): Promise<LicenseInfo> {
const license = new LicenseInfo()
license.artist = "Contributor name unavailable";
license.license = "CC BY-SA 4.0";
@ -98,7 +97,7 @@ export class Mapillary extends ImageProvider {
}
const metadataUrl = 'https://graph.mapillary.com/' + mapillaryId + '?fields=thumb_1024_url&&access_token=' + Constants.mapillary_client_token_v4;
const response = await Utils.downloadJson(metadataUrl)
const response = await Utils.downloadJsonCached(metadataUrl,60*60)
const url = <string>response["thumb_1024_url"];
return {
url: url,

View file

@ -46,7 +46,7 @@ export class WikidataImageProvider extends ImageProvider {
return allImages
}
protected DownloadAttribution(url: string): Promise<any> {
public DownloadAttribution(url: string): Promise<any> {
throw new Error("Method not implemented; shouldn't be needed!");
}

View file

@ -112,7 +112,7 @@ export class WikimediaImageProvider extends ImageProvider {
return [Promise.resolve(this.UrlForImage("File:" + value))]
}
protected async DownloadAttribution(filename: string): Promise<LicenseInfo> {
public async DownloadAttribution(filename: string): Promise<LicenseInfo> {
filename = WikimediaImageProvider.ExtractFileName(filename)
if (filename === "") {
@ -123,7 +123,7 @@ export class WikimediaImageProvider extends ImageProvider {
"api.php?action=query&prop=imageinfo&iiprop=extmetadata&" +
"titles=" + filename +
"&format=json&origin=*";
const data = await Utils.downloadJson(url)
const data = await Utils.downloadJsonCached(url,365*24*60*60)
const licenseInfo = new LicenseInfo();
const pageInfo = data.query.pages[-1]
if (pageInfo === undefined) {

View file

@ -222,6 +222,11 @@ export class Changes {
}
console.log("Got the fresh objects!", osmObjects, "pending: ", pending)
if(pending.length == 0){
console.log("No pending changes...")
return true;
}
const perType = Array.from(
Utils.Hist(pending.filter(descr => descr.meta.changeType !== undefined && descr.meta.changeType !== null)
.map(descr => descr.meta.changeType)), ([key, count]) => (

View file

@ -216,11 +216,19 @@ export class TagUtils {
*
* TagUtils.Tag("xyz<5").matchesProperties({xyz: 4}) // => true
* TagUtils.Tag("xyz<5").matchesProperties({xyz: 5}) // => false
<<<<<<< HEAD
*
* // RegexTags must match values with newlines
* TagUtils.Tag("note~.*aed.*").matchesProperties({note: "Hier bevindt zich wss een defibrillator. \\n\\n De aed bevindt zich op de 5de verdieping"}) // => true
* TagUtils.Tag("note~i~.*aed.*").matchesProperties({note: "Hier bevindt zich wss een defibrillator. \\n\\n De AED bevindt zich op de 5de verdieping"}) // => true
*
=======
*
* // RegexTags must match values with newlines
* TagUtils.Tag("note~.*aed.*").matchesProperties({note: "Hier bevindt zich wss een defibrillator. \\n\\n De aed bevindt zich op de 5de verdieping"}) // => true
* TagUtils.Tag("note~i~.*aed.*").matchesProperties({note: "Hier bevindt zich wss een defibrillator. \\n\\n De AED bevindt zich op de 5de verdieping"}) // => true
*
>>>>>>> master
* // Must match case insensitive
* TagUtils.Tag("name~i~somename").matchesProperties({name: "SoMeName"}) // => true
*/
@ -286,7 +294,7 @@ export class TagUtils {
private static TagUnsafe(json: AndOrTagConfigJson | string, context: string = ""): TagsFilter {
if (json === undefined) {
throw `Error while parsing a tag: 'json' is undefined in ${context}. Make sure all the tags are defined and at least one tag is present in a complex expression`
throw new Error(`Error while parsing a tag: 'json' is undefined in ${context}. Make sure all the tags are defined and at least one tag is present in a complex expression`)
}
if (typeof (json) != "string") {
if (json.and !== undefined && json.or !== undefined) {
@ -335,7 +343,7 @@ export class TagUtils {
return new ComparingTag(split[0], f, operator + val)
}
}
if (tag.indexOf("~~") >= 0) {
const split = Utils.SplitFirst(tag, "~~");
if (split[1] === "*") {
@ -346,9 +354,8 @@ export class TagUtils {
new RegExp("^" + split[1] + "$", "s")
);
}
const withRegex = TagUtils.parseRegexOperator(tag)
if(withRegex != null) {
if (withRegex != null) {
if (withRegex.value === "*" && withRegex.invert) {
throw `Don't use 'key!~*' - use 'key=' instead (empty string as value (in the tag ${tag} while parsing ${context})`
}
@ -362,7 +369,7 @@ export class TagUtils {
}
return new RegexTag(
withRegex.key,
new RegExp("^"+value+"$", "s"+withRegex.modifier),
new RegExp("^" + value + "$", "s" + withRegex.modifier),
withRegex.invert
);
}
@ -459,35 +466,35 @@ export class TagUtils {
/**
* Returns 'true' is opposite tags are detected.
* Note that this method will never work perfectly
*
*
* // should be false for some simple cases
* TagUtils.ContainsOppositeTags([new Tag("key", "value"), new Tag("key0", "value")]) // => false
* TagUtils.ContainsOppositeTags([new Tag("key", "value"), new Tag("key", "value0")]) // => false
*
*
* // should detect simple cases
* TagUtils.ContainsOppositeTags([new Tag("key", "value"), new RegexTag("key", "value", true)]) // => true
* TagUtils.ContainsOppositeTags([new Tag("key", "value"), new RegexTag("key", /value/, true)]) // => true
*/
public static ContainsOppositeTags(tags: (TagsFilter)[]) : boolean{
for (let i = 0; i < tags.length; i++){
public static ContainsOppositeTags(tags: (TagsFilter)[]): boolean {
for (let i = 0; i < tags.length; i++) {
const tag = tags[i];
if(!(tag instanceof Tag || tag instanceof RegexTag)){
if (!(tag instanceof Tag || tag instanceof RegexTag)) {
continue
}
for (let j = i + 1; j < tags.length; j++){
for (let j = i + 1; j < tags.length; j++) {
const guard = tags[j];
if(!(guard instanceof Tag || guard instanceof RegexTag)){
if (!(guard instanceof Tag || guard instanceof RegexTag)) {
continue
}
if(guard.key !== tag.key) {
if (guard.key !== tag.key) {
// Different keys: they can _never_ be opposites
continue
}
if((guard.value["source"] ?? guard.value) !== (tag.value["source"] ?? tag.value)){
if ((guard.value["source"] ?? guard.value) !== (tag.value["source"] ?? tag.value)) {
// different values: the can _never_ be opposites
continue
}
if( (guard["invert"] ?? false) !== (tag["invert"] ?? false) ) {
if ((guard["invert"] ?? false) !== (tag["invert"] ?? false)) {
// The 'invert' flags are opposite, the key and value is the same for both
// This means we have found opposite tags!
return true
@ -502,28 +509,28 @@ export class TagUtils {
* Returns a filtered version of 'listToFilter'.
* For a list [t0, t1, t2], If `blackList` contains an equivalent (or broader) match of any `t`, this respective `t` is dropped from the returned list
* Ignores nested ORS and ANDS
*
*
* TagUtils.removeShadowedElementsFrom([new Tag("key","value")], [new Tag("key","value"), new Tag("other_key","value")]) // => [new Tag("other_key","value")]
*/
public static removeShadowedElementsFrom(blacklist: TagsFilter[], listToFilter: TagsFilter[] ) : TagsFilter[] {
public static removeShadowedElementsFrom(blacklist: TagsFilter[], listToFilter: TagsFilter[]): TagsFilter[] {
return listToFilter.filter(tf => !blacklist.some(guard => guard.shadows(tf)))
}
/**
* Returns a filtered version of 'listToFilter', where no duplicates and no equivalents exists.
*
*
* TagUtils.removeEquivalents([new RegexTag("key", /^..*$/), new Tag("key","value")]) // => [new Tag("key", "value")]
*/
public static removeEquivalents( listToFilter: (Tag | RegexTag)[]) : TagsFilter[] {
public static removeEquivalents(listToFilter: (Tag | RegexTag)[]): TagsFilter[] {
const result: TagsFilter[] = []
outer: for (let i = 0; i < listToFilter.length; i++){
outer: for (let i = 0; i < listToFilter.length; i++) {
const tag = listToFilter[i];
for (let j = 0; j < listToFilter.length; j++){
if(i === j){
for (let j = 0; j < listToFilter.length; j++) {
if (i === j) {
continue
}
const guard = listToFilter[j];
if(guard.shadows(tag)) {
if (guard.shadows(tag)) {
// the guard 'kills' the tag: we continue the outer loop without adding the tag
continue outer;
}
@ -532,7 +539,7 @@ export class TagUtils {
}
return result
}
/**
* Returns `true` if at least one element of the 'guards' shadows one element of the 'listToFilter'.
*
@ -540,7 +547,7 @@ export class TagUtils {
* TagUtils.containsEquivalents([new Tag("key","value")], [ new Tag("other_key","value")]) // => false
* TagUtils.containsEquivalents([new Tag("key","value")], [ new Tag("key","other_value")]) // => false
*/
public static containsEquivalents( guards: TagsFilter[], listToFilter: TagsFilter[] ) : boolean {
public static containsEquivalents(guards: TagsFilter[], listToFilter: TagsFilter[]): boolean {
return listToFilter.some(tf => guards.some(guard => guard.shadows(tf)))
}

View file

@ -8,7 +8,7 @@ import {Utils} from "../../Utils";
export class QueryParameters {
static defaults = {}
static documentation = {}
static documentation: Map<string, string> = new Map<string, string>()
private static order: string [] = ["layout", "test", "z", "lat", "lon"];
private static _wasInitialized: Set<string> = new Set()
private static knownSources = {};
@ -18,7 +18,7 @@ export class QueryParameters {
if (!this.initialized) {
this.init();
}
QueryParameters.documentation[key] = documentation;
QueryParameters.documentation.set(key, documentation);
if (deflt !== undefined) {
QueryParameters.defaults[key] = deflt;
}

View file

@ -121,13 +121,14 @@ export default class Wikipedia {
* @param searchTerm
*/
public async searchViaIndex(searchTerm: string): Promise<{ title: string, snippet: string, url: string } []> {
const url = `${this.backend}/w/index.php?search=${encodeURIComponent(searchTerm)}`
const url = `${this.backend}/w/index.php?search=${encodeURIComponent(searchTerm)}&ns0=1`
const result = await Utils.downloadAdvanced(url);
if(result["redirect"] ){
const targetUrl = result["redirect"]
// This is an exact match
return [{
title: this.extractPageName(result["redirect"]).trim(),
url: result["redirect"],
title: this.extractPageName(targetUrl)?.trim(),
url: targetUrl,
snippet: ""
}]
}