From 1cd4745c3a216e16c624a634f965bdddb2a0eb2d Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Tue, 7 Jul 2020 15:08:52 +0200 Subject: [PATCH 1/9] Preparation for delete button --- Logic/FilteredLayer.ts | 9 ++++--- Logic/ImageSearcher.ts | 57 +++++++++++++++++++++++++++++++-------- UI/FeatureInfoBox.ts | 2 +- UI/Image/ImageCarousel.ts | 45 ++++++++++++++++++++++++++----- UI/SlideShow.ts | 4 ++- UI/UIElement.ts | 1 - index.css | 9 ++++--- index.html | 1 - 8 files changed, 98 insertions(+), 30 deletions(-) diff --git a/Logic/FilteredLayer.ts b/Logic/FilteredLayer.ts index 3d66d03..87ba7c6 100644 --- a/Logic/FilteredLayer.ts +++ b/Logic/FilteredLayer.ts @@ -190,15 +190,16 @@ export class FilteredLayer { }); - const uiElement = self._showOnPopup(eventSource); - layer.bindPopup(uiElement.Render()); layer.on("click", function (e) { console.log("Selected ", feature) self._selectedElement.setData(feature.properties); - + const uiElement = self._showOnPopup(eventSource); + const popup = L.popup() + .setContent(uiElement.Render()) + .setLatLng(e.latlng) + .openOn(self._map.map); uiElement.Update(); uiElement.Activate(); - L.DomEvent.stop(e); // Marks the event as consumed }); diff --git a/Logic/ImageSearcher.ts b/Logic/ImageSearcher.ts index 08a630e..7e7b5dd 100644 --- a/Logic/ImageSearcher.ts +++ b/Logic/ImageSearcher.ts @@ -3,6 +3,7 @@ import {ImagesInCategory, Wikidata, Wikimedia} from "./Wikimedia"; import {WikimediaImage} from "../UI/Image/WikimediaImage"; import {SimpleImageElement} from "../UI/Image/SimpleImageElement"; import {UIElement} from "../UI/UIElement"; +import {Changes} from "./Changes"; /** @@ -15,18 +16,20 @@ export class ImageSearcher extends UIEventSource { private readonly _wdItem = new UIEventSource(""); private readonly _commons = new UIEventSource(""); private _activated: boolean = false; - - constructor(tags: UIEventSource) { + private _changes: Changes; + + constructor(tags: UIEventSource, + changes: Changes) { super([]); this._tags = tags; - + this._changes = changes; const self = this; this._wdItem.addCallback(() => { - // Load the wikidata item, then detect usage on 'commons' - let wikidataId = self._wdItem.data; - // @ts-ignore + // Load the wikidata item, then detect usage on 'commons' + let wikidataId = self._wdItem.data; + // @ts-ignore if (wikidataId.startsWith("Q")) { wikidataId = wikidataId.substr(1); } @@ -34,6 +37,7 @@ export class ImageSearcher extends UIEventSource { self.AddImage(wd.image); Wikimedia.GetCategoryFiles(wd.commonsWiki, (images: ImagesInCategory) => { for (const image of images.images) { + // @ts-ignore if (image.startsWith("File:")) { self.AddImage(image); } @@ -67,17 +71,48 @@ export class ImageSearcher extends UIEventSource { } private AddImage(url: string) { - if(url === undefined || url === null){ + if (url === undefined || url === null) { return; } - if (this.data.indexOf(url) < 0) { - this.data.push(url); - this.ping(); + + for (const el of this.data) { + if (el === url) { + return; + } } + + this.data.push(url); + this.ping(); + } + + private ImageKey(url: string): string { + const tgs = this._tags.data; + for (const key in tgs) { + if (tgs[key] === url) { + return key; + } + } + return undefined; + } + + public IsDeletable(url: string): boolean { + return this.ImageKey(url) !== undefined; + } + + public Delete(url: string): void { + + const key = this.ImageKey(url); + if(key === undefined){ + return; + } + console.log("Deleting image..."); + + // this._changes.addChange(this._tags.data.id, key, ""); + } public Activate() { - if(this._activated){ + if (this._activated) { return; } this._activated = true; diff --git a/UI/FeatureInfoBox.ts b/UI/FeatureInfoBox.ts index b2fa05c..fbd42ac 100644 --- a/UI/FeatureInfoBox.ts +++ b/UI/FeatureInfoBox.ts @@ -44,7 +44,7 @@ export class FeatureInfoBox extends UIElement { this._userDetails = userDetails; this.ListenTo(userDetails); - this._imageElement = new ImageCarousel(this._tagsES); + this._imageElement = new ImageCarousel(this._tagsES, changes); this._infoboxes = []; for (const tagRenderingOption of elementsToShow) { diff --git a/UI/Image/ImageCarousel.ts b/UI/Image/ImageCarousel.ts index 372488d..05682c4 100644 --- a/UI/Image/ImageCarousel.ts +++ b/UI/Image/ImageCarousel.ts @@ -3,6 +3,9 @@ import {ImageSearcher} from "../../Logic/ImageSearcher"; import {UIEventSource} from "../UIEventSource"; import {SlideShow} from "../SlideShow"; import {FixedUiElement} from "../Base/FixedUiElement"; +import {VerticalCombine} from "../Base/VerticalCombine"; +import {Changes} from "../../Logic/Changes"; +import {VariableUiElement} from "../Base/VariableUIElement"; export class ImageCarousel extends UIElement { /** @@ -20,26 +23,54 @@ export class ImageCarousel extends UIElement { public readonly slideshow: SlideShow; - constructor(tags: UIEventSource) { + private readonly _uiElements: UIEventSource; + + private readonly _deleteButtonText = new UIEventSource(""); + private readonly _deleteButton: UIElement; + + constructor(tags: UIEventSource, changes: Changes) { super(tags); - this.searcher = new ImageSearcher(tags); + const self = this; + this.searcher = new ImageSearcher(tags, changes); - let uiElements = this.searcher.map((imageURLS: string[]) => { + this._uiElements = this.searcher.map((imageURLS: string[]) => { const uiElements: UIElement[] = []; for (const url of imageURLS) { - uiElements.push(ImageSearcher.CreateImageElement(url)); + const image = ImageSearcher.CreateImageElement(url); + uiElements.push(image); } return uiElements; }); - + this.slideshow = new SlideShow( - uiElements, + this._uiElements, new FixedUiElement("")).HideOnEmpty(true); + + + this._deleteButtonText = this.slideshow._currentSlide.map((i) => { + if(self.searcher.IsDeletable(self.searcher.data[i])){ + return "DELETE"; + }else{ + return ""; + } + }); + + this._deleteButton = new VariableUiElement(this._deleteButtonText) + .HideOnEmpty(true) + .onClick(() => { + self.searcher.Delete(self.searcher.data[self.slideshow._currentSlide.data]); + }); } InnerRender(): string { - return this.slideshow.Render(); + return this.slideshow.Render() ; + // + this._deleteButton.Render(); + } + + InnerUpdate(htmlElement: HTMLElement) { + super.InnerUpdate(htmlElement); + this._deleteButton.Update(); } diff --git a/UI/SlideShow.ts b/UI/SlideShow.ts index 2be2312..8602d4c 100644 --- a/UI/SlideShow.ts +++ b/UI/SlideShow.ts @@ -6,7 +6,7 @@ export class SlideShow extends UIElement { private readonly _embeddedElements: UIEventSource - private readonly _currentSlide: UIEventSource = new UIEventSource(0); + public readonly _currentSlide: UIEventSource = new UIEventSource(0); private readonly _noimages: UIElement; private _prev: FixedUiElement; private _next: FixedUiElement; @@ -84,6 +84,8 @@ export class SlideShow extends UIElement { for (const embeddedElement of this._embeddedElements.data) { embeddedElement.Activate(); } + this._next.Update(); + this._prev.Update(); } } \ No newline at end of file diff --git a/UI/UIElement.ts b/UI/UIElement.ts index f1bb779..5a72043 100644 --- a/UI/UIElement.ts +++ b/UI/UIElement.ts @@ -1,5 +1,4 @@ import {UIEventSource} from "./UIEventSource"; -import {Playground} from "../Layers/Playground"; export abstract class UIElement { diff --git a/index.css b/index.css index 6a0e9c5..f95ddb0 100644 --- a/index.css +++ b/index.css @@ -457,14 +457,15 @@ body { text-align: center; } - - +.slide > span { + max-height: 40vh; +} .slide > span img { height: auto; width: auto; max-width: 100%; - max-height: 50vh; + max-height: 30vh; border-radius: 1em; } @@ -497,7 +498,7 @@ body { position: absolute; bottom: 0; - left: 5em; /* Offset for the go left button*/ + left: 6em; /* Offset for the go left button*/ padding: 0.25em; margin-bottom: 0.25em; border-radius: 0.5em; diff --git a/index.html b/index.html index 8e1ed55..52e1f10 100644 --- a/index.html +++ b/index.html @@ -9,7 +9,6 @@ integrity="sha512-xwE/Az9zrjBIphAcBb3F6JVqxf46+CDLwfLMHloNu6KEQCAWi6HcDUbeOfBIptF7tcCzusKFjFw2yuvEpDL9wQ==" crossorigin=""/> - From cc0b0f52c1866d3bd5da99ec3d3a425e4b416685 Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Tue, 7 Jul 2020 16:00:39 +0200 Subject: [PATCH 2/9] Add comments --- Customizations/LayerDefinition.ts | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/Customizations/LayerDefinition.ts b/Customizations/LayerDefinition.ts index bd64aa4..945f299 100644 --- a/Customizations/LayerDefinition.ts +++ b/Customizations/LayerDefinition.ts @@ -11,13 +11,22 @@ import {TagRenderingOptions} from "./TagRendering"; export class LayerDefinition { + /** + * This name is shown in the 'add XXX button' + */ name: string; newElementTags: Tag[] icon: string; minzoom: number; overpassFilter: TagsFilter; + /** + * This UIElement is rendered as title element in the popup + */ title: TagRenderingOptions; + /** + * These are the questions/shown attributes in the popup + */ elementsToShow: TagRenderingOptions[]; style: (tags: any) => { color: string, icon: any }; From 89c7f6ad19508269c92b61ad83e2cd7c1c7d3e11 Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Tue, 7 Jul 2020 16:42:25 +0200 Subject: [PATCH 3/9] Add docs --- Customizations/TagRendering.ts | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/Customizations/TagRendering.ts b/Customizations/TagRendering.ts index 2158baa..8c11961 100644 --- a/Customizations/TagRendering.ts +++ b/Customizations/TagRendering.ts @@ -23,9 +23,25 @@ export class TagRenderingOptions { constructor(options: { + + /** + * What is the priority of the question. + * By default, in the popup of a feature, only one question is shown at the same time. If multiple questions are unanswered, the question with the highest priority is asked first + */ priority?: number + /** + * This is the string that is shown in the popup if this tag is missing. + * + * If 'question' is undefined, then the question is never asked at all + * If the question is "" (empty string) then the question is + */ question?: string, + + /** + * Optional: + * if defined, this a common piece of tag that is shown in front of every mapping (except freeform) + */ primer?: string, tagsPreprocessor?: ((tags: any) => any), freeform?: { @@ -34,6 +50,18 @@ export class TagRenderingOptions { placeholder?: string, extraTags?: TagsFilter, }, + /** + * Mappings convert a well-known tag combination into a user friendly text. + * It converts e.g. 'access=yes' into 'this area can be accessed' + * + * If there are multiple tags that should be matched, And can be used. All tags in AND will be added when the question is picked (and the corresponding text will only be shown if all tags are present). + * If AND is used, it is best practice to make sure every used tag is in every option (with empty string) to erase extra tags. + * + * If a 'k' is null, then this one is shown by default. It can be used to force a default value, e.g. to show that the name of a POI is not (yet) known . + * A mapping where 'k' is null will not be shown as option in the radio buttons. + * + * + */ mappings?: { k: TagsFilter, txt: string, priority?: number, substitute?: boolean }[] }) { this.options = options; From 0fe6b67976908f9dcb876e46f171f0cec6a7469f Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Wed, 8 Jul 2020 11:23:36 +0200 Subject: [PATCH 4/9] Add image delete button --- Customizations/Layers/Park.ts | 17 ++++++- Customizations/Layouts/BikePumps.ts | 6 +-- Customizations/TagRendering.ts | 15 ++++-- Logic/ImageSearcher.ts | 20 +++----- README.md | 3 +- UI/ConfirmDialog.ts | 69 +++++++++++++++++++++++++ UI/Image/ImageCarousel.ts | 39 +++++++++------ UI/UIElement.ts | 11 ++++ UI/UIEventSource.ts | 14 ++++-- assets/delete.svg | 55 ++++++++++++++++++++ index.css | 78 +++++++++++++++++++++++++++++ test.html | 2 + test.ts | 28 ++++++----- 13 files changed, 303 insertions(+), 54 deletions(-) create mode 100644 UI/ConfirmDialog.ts create mode 100644 assets/delete.svg diff --git a/Customizations/Layers/Park.ts b/Customizations/Layers/Park.ts index 3736435..e46dc25 100644 --- a/Customizations/Layers/Park.ts +++ b/Customizations/Layers/Park.ts @@ -9,6 +9,18 @@ import {NameInline} from "../Questions/NameInline"; export class Park extends LayerDefinition { + + private accessByDefault = new TagRenderingOptions({ + question: "Is dit park publiek toegankelijk?", + mappings: [ + {k: new Tag("access","yes"), txt: "Publiek toegankelijk"}, + {k: new Tag("access",""), txt: "Publiek toegankelijk"}, + {k: new Tag("access","no"), txt: "Niet-publiek toegankelijk park"}, + {k: new Tag("access","guided"), txt: "Enkel toegankelijk met een gids of op een activiteit"} + ] + }) + + constructor() { super(); this.name = "park"; @@ -22,7 +34,10 @@ export class Park extends LayerDefinition { this.minzoom = 13; this.style = this.generateStyleFunction(); this.title = new NameInline("park"); - this.elementsToShow = [new NameQuestion()]; + this.elementsToShow = [new NameQuestion(), + this.accessByDefault + + ]; } diff --git a/Customizations/Layouts/BikePumps.ts b/Customizations/Layouts/BikePumps.ts index a797305..a3902fb 100644 --- a/Customizations/Layouts/BikePumps.ts +++ b/Customizations/Layouts/BikePumps.ts @@ -6,16 +6,16 @@ export class BikePumpsLayout extends Layout { constructor() { super( "pomp", - "Grb import fix tool", + "Cyclofix", [new BikePumps()], 15, 51.2083, 3.2279, - "

GRB Fix tool

\n" + + "

Open CycloFix

\n" + "\n" + - "Expert use only" + "Something something bikes" , "", ""); diff --git a/Customizations/TagRendering.ts b/Customizations/TagRendering.ts index 8c11961..f94b112 100644 --- a/Customizations/TagRendering.ts +++ b/Customizations/TagRendering.ts @@ -65,7 +65,6 @@ export class TagRenderingOptions { mappings?: { k: TagsFilter, txt: string, priority?: number, substitute?: boolean }[] }) { this.options = options; - } @@ -150,6 +149,7 @@ export class TagRendering extends UIElement { // Prepare the choices for the Radio buttons let i = 0; const choices: UIElement[] = []; + const alreadyUsedTexts: string[] = []; for (const choice of options.mappings ?? []) { if (choice.k === null) { @@ -159,16 +159,21 @@ export class TagRendering extends UIElement { let choiceSubbed = choice; if (choice.substitute) { choiceSubbed = { - k : choice.k.substituteValues( + k: choice.k.substituteValues( options.tagsPreprocessor(this._source.data)), - txt : this.ApplyTemplate(choice.txt), + txt: this.ApplyTemplate(choice.txt), substitute: false, priority: choice.priority } } - - choices.push(new FixedUiElement(choiceSubbed.txt)); + const txt = choiceSubbed.txt; + if (alreadyUsedTexts.indexOf(txt) < 0) { + choices.push(new FixedUiElement(txt)); + alreadyUsedTexts.push(txt); + } + + this._mapping.push(choiceSubbed); i++; } diff --git a/Logic/ImageSearcher.ts b/Logic/ImageSearcher.ts index 7e7b5dd..dc48153 100644 --- a/Logic/ImageSearcher.ts +++ b/Logic/ImageSearcher.ts @@ -105,9 +105,8 @@ export class ImageSearcher extends UIEventSource { if(key === undefined){ return; } - console.log("Deleting image..."); - - // this._changes.addChange(this._tags.data.id, key, ""); + console.log("Deleting image...", key, " --> ", url); + this._changes.addChange(this._tags.data.id, key, ""); } @@ -133,16 +132,11 @@ export class ImageSearcher extends UIEventSource { } } - const image0 = this._tags.data["image:0"]; - if (image0 !== undefined) { - this.AddImage(image0); - } - let imageIndex = 1; - let imagei = this._tags.data["image:" + imageIndex]; - while (imagei !== undefined) { - this.AddImage(imagei); - imageIndex++; - imagei = this._tags.data["image:" + imageIndex]; + for (const key in this._tags.data) { + // @ts-ignore + if(key.startsWith("image:")){ + this.AddImage(this._tags.data[key]); + } } const wdItem = this._tags.data.wikidata; diff --git a/README.md b/README.md index 5cd3832..448373b 100644 --- a/README.md +++ b/README.md @@ -78,7 +78,8 @@ Camera Icon, Dave Gandy, CC-BY-SA 3.0 https://commons.wikimedia.org/wiki/File:OOjs_UI_indicator_search-rtl.svg Search Icon, MIT - +https://commons.wikimedia.org/wiki/File:Trash_font_awesome.svg +Trash icon by Dave Gandy, CC-BY-SA https://commons.wikimedia.org/wiki/File:Home-icon.svg Home icon by Timothy Miller, CC-BY-SA 3.0 diff --git a/UI/ConfirmDialog.ts b/UI/ConfirmDialog.ts new file mode 100644 index 0000000..8395401 --- /dev/null +++ b/UI/ConfirmDialog.ts @@ -0,0 +1,69 @@ +import {UIElement} from "./UIElement"; +import {UIEventSource} from "./UIEventSource"; +import {FixedUiElement} from "./Base/FixedUiElement"; +import {VariableUiElement} from "./Base/VariableUIElement"; + + +export class ConfirmDialog extends UIElement { + private _showOptions: UIEventSource = new UIEventSource(false); + + private _question: UIElement; + private _optionA: UIElement; + private _optionB: UIElement; + + constructor( + show: UIEventSource, + question: string, + optionA: string, optionB: string, + executeA: () => void, + executeB: () => void, + classA: string = "", + classB: string = "") { + super(show); + this.ListenTo(this._showOptions); + const self = this; + show.addCallback(() => { + self._showOptions.setData(false); + }) + this._question = new FixedUiElement("" + question + "") + .onClick(() => { + self._showOptions.setData(!self._showOptions.data); + }); + this._optionA = new VariableUiElement( + this._showOptions.map( + (show) => show ? "
" + optionA + "
" : "")) + .onClick(() => { + self._showOptions.setData(false); + executeA(); + } + ); + this._optionB = new VariableUiElement( + this._showOptions.map((show) => + show ? "
" + optionB + "
" : "") ) + .onClick(() => { + self._showOptions.setData(false); + executeB(); + }); + + + + } + + protected InnerRender(): string { + if (!this._source.data) { + return ""; + } + + return this._question.Render() + + this._optionA.Render() + + this._optionB.Render(); + } + + Update() { + super.Update(); + this._question.Update(); + this._optionA.Update(); + this._optionB.Update(); + } + +} \ No newline at end of file diff --git a/UI/Image/ImageCarousel.ts b/UI/Image/ImageCarousel.ts index 05682c4..21d36f6 100644 --- a/UI/Image/ImageCarousel.ts +++ b/UI/Image/ImageCarousel.ts @@ -6,6 +6,7 @@ import {FixedUiElement} from "../Base/FixedUiElement"; import {VerticalCombine} from "../Base/VerticalCombine"; import {Changes} from "../../Logic/Changes"; import {VariableUiElement} from "../Base/VariableUIElement"; +import {ConfirmDialog} from "../ConfirmDialog"; export class ImageCarousel extends UIElement { /** @@ -25,7 +26,6 @@ export class ImageCarousel extends UIElement { private readonly _uiElements: UIEventSource; - private readonly _deleteButtonText = new UIEventSource(""); private readonly _deleteButton: UIElement; constructor(tags: UIEventSource, changes: Changes) { @@ -48,24 +48,33 @@ export class ImageCarousel extends UIElement { new FixedUiElement("")).HideOnEmpty(true); - this._deleteButtonText = this.slideshow._currentSlide.map((i) => { - if(self.searcher.IsDeletable(self.searcher.data[i])){ - return "DELETE"; - }else{ - return ""; - } - }); + const showDeleteButton = this.slideshow._currentSlide.map((i) => { + return self.searcher.IsDeletable(self.searcher.data[i]); + }, [this.searcher]); + this.slideshow._currentSlide.addCallback(() => { + showDeleteButton.ping(); // This pings the showDeleteButton, which indicates that it has to hide it's subbuttons + }) - this._deleteButton = new VariableUiElement(this._deleteButtonText) - .HideOnEmpty(true) - .onClick(() => { - self.searcher.Delete(self.searcher.data[self.slideshow._currentSlide.data]); - }); + + const deleteCurrent = () => self.searcher.Delete(self.searcher.data[self.slideshow._currentSlide.data]); + + this._deleteButton = new ConfirmDialog(showDeleteButton, + "Afbeelding verwijderen", + "Afbeelding verwijderen", + "Terug", + deleteCurrent, + () => {}, + 'delete-image-confirm', + 'delete-image-cancel'); } InnerRender(): string { - return this.slideshow.Render() ; - // + this._deleteButton.Render(); + return "" + + "
" + + this._deleteButton.Render() + + "
" + + this.slideshow.Render() + + "
"; } InnerUpdate(htmlElement: HTMLElement) { diff --git a/UI/UIElement.ts b/UI/UIElement.ts index 5a72043..57f1427 100644 --- a/UI/UIElement.ts +++ b/UI/UIElement.ts @@ -58,6 +58,17 @@ export abstract class UIElement { } element.style.pointerEvents = "all"; element.style.cursor = "pointer"; + /* + const childs = element.children; + for (let i = 0; i < childs.length; i++) { + const ch = childs[i]; + console.log(ch); + ch.style.cursor = "pointer"; + ch.onclick = () => { + self._onClick(); + } + ch.style.pointerEvents = "all"; + }*/ } this.InnerUpdate(element); diff --git a/UI/UIEventSource.ts b/UI/UIEventSource.ts index f1a9909..eae2151 100644 --- a/UI/UIEventSource.ts +++ b/UI/UIEventSource.ts @@ -27,15 +27,23 @@ export class UIEventSource{ } } - public map(f: ((T) => J)): UIEventSource { + public map(f: ((T) => J), + extraSources : UIEventSource[] = []): UIEventSource { const self = this; - this.addCallback(function () { + + const update = function () { newSource.setData(f(self.data)); newSource.ping(); - }); + } + + this.addCallback(update); + for (const extraSource of extraSources) { + extraSource.addCallback(update); + } const newSource = new UIEventSource( f(this.data) ); + return newSource; diff --git a/assets/delete.svg b/assets/delete.svg new file mode 100644 index 0000000..60bf193 --- /dev/null +++ b/assets/delete.svg @@ -0,0 +1,55 @@ + + + + + + image/svg+xml + + + + + + + + diff --git a/index.css b/index.css index f95ddb0..f61ecac 100644 --- a/index.css +++ b/index.css @@ -682,6 +682,84 @@ body { display: inline-block } +/******* THe remove image buttons ****/ + +.image-carousel-container { + position: relative; +} + +.image-delete-container { + position: absolute; + left: 6em; + top: 1.5em; + display: inline-block; + z-index: 7000; + +} + +.delete-image { + width: 1.5em; + height: 1.5em; + padding: 0.5em; + border-radius: 3em; + background-color: black; +} + +.delete-image-confirm { + position: absolute; + display: inline-block; + left: 0; + top: 2.5em; + + padding: 0.5em; + padding-left: 0.75em; + + z-index: -1; + height: 3em; + width: 14em; + border-radius: 1em; + border-top-left-radius: 0; + border-top-right-radius: 0; + background-color: #ff8c8c; + + color: white; + height: 1.5em; /* same as .delete-image */ + + z-index: 7000; +} + +.delete-image-confirm span { + font-size: larger; + font-weight: bold; +} + + +.delete-image-cancel { + display: inline-block; + position: absolute; + + left: 0em; + padding: 0.5em; + padding-left: 0.75em; + + border-radius: 1em; + border-bottom-right-radius: 0; + border-bottom-left-radius: 0; + + height: 1.5em; /* same as .delete-image */ + width: 14em; /* Same as delete-image-confirm */ + + + background-color: black; + color: white; + z-index: 7000; +} + +.delete-image-cancel span { + font-size: larger; + font-weight: bold; + +} /**** The save button *****/ diff --git a/test.html b/test.html index b5f19fb..ad22b5d 100644 --- a/test.html +++ b/test.html @@ -5,7 +5,9 @@ +
'maindiv' not attached
+
'extradiv' not attached
diff --git a/test.ts b/test.ts index 9079435..f006400 100644 --- a/test.ts +++ b/test.ts @@ -4,18 +4,20 @@ import {OsmConnection} from "./Logic/OsmConnection"; import {ElementStorage} from "./Logic/ElementStorage"; import {WikipediaLink} from "./Customizations/Questions/WikipediaLink"; import {OsmLink} from "./Customizations/Questions/OsmLink"; - -const tags = {name: "Test", - wikipedia: "nl:Pieter", - id: "node/-1"}; -const tagsES = new UIEventSource(tags); - -const login = new OsmConnection(true); - -const allElements = new ElementStorage(); -allElements.addElementById(tags.id, tagsES); - -const changes = new Changes("Test", login, allElements) +import {ConfirmDialog} from "./UI/ConfirmDialog"; -new OsmLink(tagsES, changes).AttachTo("maindiv"); \ No newline at end of file +new ConfirmDialog(new UIEventSource(true), + "Afbeelding verwijderen", + "Deze afbeelding verwijderen", + "Terug", + + () => { + console.log("Verwijderen"); + }, + () => { + console.log("terug") + }, + 'delete-image-confirm', + 'delete-image-cancel') + .AttachTo("maindiv") \ No newline at end of file From 31a64887a1646a8a89cba3f95cb2fccc9d8d203a Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Wed, 8 Jul 2020 13:12:23 +0200 Subject: [PATCH 5/9] Fixes to removing images --- Logic/ImageSearcher.ts | 26 +++++++++++++++++------ UI/Image/ImageCarousel.ts | 43 +++++++++++++++++++++++++++------------ UI/UIElement.ts | 2 +- index.css | 21 ++++++++++++++++++- 4 files changed, 71 insertions(+), 21 deletions(-) diff --git a/Logic/ImageSearcher.ts b/Logic/ImageSearcher.ts index dc48153..1a07957 100644 --- a/Logic/ImageSearcher.ts +++ b/Logic/ImageSearcher.ts @@ -5,7 +5,17 @@ import {SimpleImageElement} from "../UI/Image/SimpleImageElement"; import {UIElement} from "../UI/UIElement"; import {Changes} from "./Changes"; - +/** + * There are multiple way to fetch images for an object + * 1) There is an image tag + * 2) There is an image tag, the image tag contains multiple ';'-seperated URLS + * 3) there are multiple image tags, e.g. 'image', 'image:0', 'image:1', and 'image_0', 'image_1' - however, these are pretty rare so we are gonna ignore them + * 4) There is a wikimedia_commons-tag, which either has a 'File': or a 'category:' containing images + * 5) There is a wikidata-tag, and the wikidata item either has an 'image' attribute or has 'a link to a wikimedia commons category' + * 6) There is a wikipedia article, from which we can deduct the wikidata item + * + * For some images, author and license should be shown + */ /** * Class which search for all the possible locations for images and which builds a list of UI-elements for it. * Note that this list is embedded into an UIEVentSource, ready to put it into a carousel @@ -17,6 +27,8 @@ export class ImageSearcher extends UIEventSource { private readonly _commons = new UIEventSource(""); private _activated: boolean = false; private _changes: Changes; + public _deletedImages = new UIEventSource([]); + constructor(tags: UIEventSource, changes: Changes) { @@ -71,7 +83,7 @@ export class ImageSearcher extends UIEventSource { } private AddImage(url: string) { - if (url === undefined || url === null) { + if (url === undefined || url === null || url === "") { return; } @@ -102,12 +114,13 @@ export class ImageSearcher extends UIEventSource { public Delete(url: string): void { const key = this.ImageKey(url); - if(key === undefined){ + if (key === undefined) { return; } console.log("Deleting image...", key, " --> ", url); this._changes.addChange(this._tags.data.id, key, ""); - + this._deletedImages.data.push(url); + this._deletedImages.ping(); } public Activate() { @@ -134,8 +147,9 @@ export class ImageSearcher extends UIEventSource { for (const key in this._tags.data) { // @ts-ignore - if(key.startsWith("image:")){ - this.AddImage(this._tags.data[key]); + if (key.startsWith("image:")) { + const url = this._tags.data[key] + this.AddImage(url); } } diff --git a/UI/Image/ImageCarousel.ts b/UI/Image/ImageCarousel.ts index 21d36f6..82627f1 100644 --- a/UI/Image/ImageCarousel.ts +++ b/UI/Image/ImageCarousel.ts @@ -9,17 +9,7 @@ import {VariableUiElement} from "../Base/VariableUIElement"; import {ConfirmDialog} from "../ConfirmDialog"; export class ImageCarousel extends UIElement { - /** - * There are multiple way to fetch images for an object - * 1) There is an image tag - * 2) There is an image tag, the image tag contains multiple ';'-seperated URLS - * 3) there are multiple image tags, e.g. 'image', 'image:0', 'image:1', and 'image_0', 'image_1' - however, these are pretty rare so we are gonna ignore them - * 4) There is a wikimedia_commons-tag, which either has a 'File': or a 'category:' containing images - * 5) There is a wikidata-tag, and the wikidata item either has an 'image' attribute or has 'a link to a wikimedia commons category' - * 6) There is a wikipedia article, from which we can deduct the wikidata item - * - * For some images, author and license should be shown - */ + private readonly searcher: ImageSearcher; public readonly slideshow: SlideShow; @@ -27,6 +17,7 @@ export class ImageCarousel extends UIElement { private readonly _uiElements: UIEventSource; private readonly _deleteButton: UIElement; + private readonly _isDeleted: UIElement; constructor(tags: UIEventSource, changes: Changes) { super(tags); @@ -56,22 +47,47 @@ export class ImageCarousel extends UIElement { }) - const deleteCurrent = () => self.searcher.Delete(self.searcher.data[self.slideshow._currentSlide.data]); + const deleteCurrent = () => { + self.searcher.Delete(self.searcher.data[self.slideshow._currentSlide.data]); + } + this._deleteButton = new ConfirmDialog(showDeleteButton, "Afbeelding verwijderen", "Afbeelding verwijderen", "Terug", deleteCurrent, - () => {}, + () => { + }, 'delete-image-confirm', 'delete-image-cancel'); + + + const mapping = this.slideshow._currentSlide.map((i) => { + if (this.searcher._deletedImages.data.indexOf( + this.searcher.data[i] + ) >= 0) { + return "
Deze afbeelding is verwijderd
" + } + + return ""; + }); + this._isDeleted = new VariableUiElement( + mapping + ) + // .HideOnEmpty(true); + + this.searcher._deletedImages.addCallback(() => { + this.slideshow._currentSlide.ping(); + }) + } InnerRender(): string { return "" + "
" + this._deleteButton.Render() + + this._isDeleted.Render() + "
" + this.slideshow.Render() + "
"; @@ -80,6 +96,7 @@ export class ImageCarousel extends UIElement { InnerUpdate(htmlElement: HTMLElement) { super.InnerUpdate(htmlElement); this._deleteButton.Update(); + this._isDeleted.Update(); } diff --git a/UI/UIElement.ts b/UI/UIElement.ts index 57f1427..efd574f 100644 --- a/UI/UIElement.ts +++ b/UI/UIElement.ts @@ -17,7 +17,7 @@ export abstract class UIElement { } - protected ListenTo(source: UIEventSource) { + public ListenTo(source: UIEventSource) { if (source === undefined) { return; } diff --git a/index.css b/index.css index f61ecac..1d39a9c 100644 --- a/index.css +++ b/index.css @@ -688,6 +688,26 @@ body { position: relative; } +.image-is-removed{ + display: inline-block; + left: 0; + top: 2.5em; + + padding: 0.5em; + padding-left: 0.75em; + + height: 3em; + width: 14em; + border-radius: 1em; + background-color: black; + + color: white; + font-weight: bold; + height: 1.5em; /* same as .delete-image */ + + z-index: 7000; +} + .image-delete-container { position: absolute; left: 6em; @@ -714,7 +734,6 @@ body { padding: 0.5em; padding-left: 0.75em; - z-index: -1; height: 3em; width: 14em; border-radius: 1em; From 9e9a6004881712fc312a640eed3570eb5800669f Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Wed, 8 Jul 2020 15:09:34 +0200 Subject: [PATCH 6/9] Add imgur attribution --- Logic/ImageSearcher.ts | 8 +++--- Logic/Imgur.ts | 45 +++++++++++++++++++++++++++++++ UI/Image/ImgurImage.ts | 54 ++++++++++++++++++++++++++++++++++++++ UI/Image/WikimediaImage.ts | 9 ++++--- test.ts | 20 +++++--------- 5 files changed, 115 insertions(+), 21 deletions(-) create mode 100644 UI/Image/ImgurImage.ts diff --git a/Logic/ImageSearcher.ts b/Logic/ImageSearcher.ts index 1a07957..3d47b67 100644 --- a/Logic/ImageSearcher.ts +++ b/Logic/ImageSearcher.ts @@ -4,6 +4,7 @@ import {WikimediaImage} from "../UI/Image/WikimediaImage"; import {SimpleImageElement} from "../UI/Image/SimpleImageElement"; import {UIElement} from "../UI/UIElement"; import {Changes} from "./Changes"; +import {ImgurImage} from "../UI/Image/ImgurImage"; /** * There are multiple way to fetch images for an object @@ -170,12 +171,13 @@ export class ImageSearcher extends UIEventSource { * @constructor */ static CreateImageElement(url: string): UIElement { - const urlSource = new UIEventSource(url); // @ts-ignore if (url.startsWith("File:")) { - return new WikimediaImage(urlSource.data); + return new WikimediaImage(url); + }else if(url.startsWith("https://i.imgur.com/")){ + return new ImgurImage(url); } else { - return new SimpleImageElement(urlSource); + return new SimpleImageElement(new UIEventSource(url)); } } diff --git a/Logic/Imgur.ts b/Logic/Imgur.ts index 71118e5..bfba380 100644 --- a/Logic/Imgur.ts +++ b/Logic/Imgur.ts @@ -1,4 +1,5 @@ import $ from "jquery" +import {LicenseInfo} from "./Wikimedia"; export class Imgur { @@ -27,6 +28,50 @@ export class Imgur { ); + } + static getDescriptionOfImage(url: string, + handleDescription: ((license: LicenseInfo) => void)) { + + const hash = url.substr("https://i.imgur.com/".length).split(".jpg")[0]; + + const apiUrl = 'https://api.imgur.com/3/image/'+hash; + const apiKey = '7070e7167f0a25a'; + + var settings = { + async: true, + crossDomain: true, + processData: false, + contentType: false, + type: 'GET', + url: apiUrl, + headers: { + Authorization: 'Client-ID ' + apiKey, + Accept: 'application/json', + }, + }; + $.ajax(settings).done(function (response) { + const descr : string= response.data.description; + const data : any = {}; + for (const tag of descr.split("\n")) { + const kv = tag.split(":"); + const k = kv[0]; + const v = kv[1].replace("\r", ""); + data[k] = v; + } + + + console.log(data); + const licenseInfo = new LicenseInfo(); + + licenseInfo.licenseShortName = data.license; + licenseInfo.artist = data.author; + + handleDescription(licenseInfo); + + }).fail((reason) => { + console.log("Getting metadata from to IMGUR failed", reason) + }); + } static uploadImage(title: string, description: string, blob, diff --git a/UI/Image/ImgurImage.ts b/UI/Image/ImgurImage.ts new file mode 100644 index 0000000..cf0e07d --- /dev/null +++ b/UI/Image/ImgurImage.ts @@ -0,0 +1,54 @@ +import {UIEventSource} from "../UIEventSource"; +import {UIElement} from "../UIElement"; +import {LicenseInfo} from "../../Logic/Wikimedia"; +import {Imgur} from "../../Logic/Imgur"; + + +export class ImgurImage extends UIElement { + + + /*** + * Dictionary from url to alreayd known license info + */ + static allLicenseInfos: any = {}; + private _imageMeta: UIEventSource; + private _imageLocation: string; + + constructor(source: string) { + super(undefined) + this._imageLocation = source; + if (ImgurImage.allLicenseInfos[source] !== undefined) { + this._imageMeta = ImgurImage.allLicenseInfos[source]; + } else { + this._imageMeta = new UIEventSource(null); + ImgurImage.allLicenseInfos[source] = this._imageMeta; + const self = this; + Imgur.getDescriptionOfImage(source, (license) => { + self._imageMeta.setData(license) + }) + } + + this.ListenTo(this._imageMeta); + + } + + protected InnerRender(): string { + const image = ""; + + if(this._imageMeta.data === null){ + return image; + } + + const attribution = + "" + (this._imageMeta.data.artist ?? "") + "" + " " + (this._imageMeta.data.licenseShortName ?? "") + ""; + + return "
" + + image + + "
" + + attribution + + "
" + + "
"; + } + + +} \ No newline at end of file diff --git a/UI/Image/WikimediaImage.ts b/UI/Image/WikimediaImage.ts index 9b5c589..abdf132 100644 --- a/UI/Image/WikimediaImage.ts +++ b/UI/Image/WikimediaImage.ts @@ -18,14 +18,15 @@ export class WikimediaImage extends UIElement { } else { this._imageMeta = new UIEventSource(new LicenseInfo()); WikimediaImage.allLicenseInfos[source] = this._imageMeta; + const self = this; + Wikimedia.LicenseData(source, (info) => { + self._imageMeta.setData(info); + }) } this.ListenTo(this._imageMeta); - const self = this; - Wikimedia.LicenseData(source, (info) => { - self._imageMeta.setData(info); - }) + } protected InnerRender(): string { diff --git a/test.ts b/test.ts index f006400..cd6a746 100644 --- a/test.ts +++ b/test.ts @@ -5,19 +5,11 @@ import {ElementStorage} from "./Logic/ElementStorage"; import {WikipediaLink} from "./Customizations/Questions/WikipediaLink"; import {OsmLink} from "./Customizations/Questions/OsmLink"; import {ConfirmDialog} from "./UI/ConfirmDialog"; +import {Imgur} from "./Logic/Imgur"; +console.log("Hello world") -new ConfirmDialog(new UIEventSource(true), - "Afbeelding verwijderen", - "Deze afbeelding verwijderen", - "Terug", - - () => { - console.log("Verwijderen"); - }, - () => { - console.log("terug") - }, - 'delete-image-confirm', - 'delete-image-cancel') - .AttachTo("maindiv") \ No newline at end of file +Imgur.getDescriptionOfImage("https://i.imgur.com/pJfQYsj.jpg", + (info) => { + console.log(info) + }) \ No newline at end of file From e3e6566c12ac8243958962b130c196dac2a8fedd Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Wed, 8 Jul 2020 16:07:16 +0200 Subject: [PATCH 7/9] Small fixes --- Customizations/AllKnownLayouts.ts | 3 ++- Customizations/Layers/Bookcases.ts | 33 +++++++++++++++++++++------ Customizations/Layers/Park.ts | 24 +++++++++++++++++++- Customizations/Layouts/Groen.ts | 2 +- Customizations/TagRendering.ts | 36 +++++++++++++++++++----------- UI/Base/UIRadioButton.ts | 8 ++++--- index.css | 9 ++++++++ index.ts | 4 ++-- test.ts | 14 ------------ 9 files changed, 91 insertions(+), 42 deletions(-) diff --git a/Customizations/AllKnownLayouts.ts b/Customizations/AllKnownLayouts.ts index 8fea419..fe6b632 100644 --- a/Customizations/AllKnownLayouts.ts +++ b/Customizations/AllKnownLayouts.ts @@ -14,9 +14,10 @@ export class AllKnownLayouts { new Groen(), new GRB(), new BikePumpsLayout(), + new Bookcases() /*new Toilets(), new Statues(), - new Bookcases()*/ + */ ]; const allSets = {}; for (const layout of layouts) { diff --git a/Customizations/Layers/Bookcases.ts b/Customizations/Layers/Bookcases.ts index 81015f1..fcd621c 100644 --- a/Customizations/Layers/Bookcases.ts +++ b/Customizations/Layers/Bookcases.ts @@ -3,6 +3,7 @@ import L from "leaflet"; import {Tag} from "../../Logic/TagsFilter"; import {QuestionDefinition} from "../../Logic/Question"; import {TagRenderingOptions} from "../TagRendering"; +import {NameInline} from "../Questions/NameInline"; export class Bookcases extends LayerDefinition { @@ -10,21 +11,38 @@ export class Bookcases extends LayerDefinition { super(); this.name = "boekenkast"; - this.newElementTags = [new Tag( "amenity", "public_bookcase")]; + this.newElementTags = [new Tag("amenity", "public_bookcase")]; this.icon = "./assets/bookcase.svg"; - this.overpassFilter = new Tag("amenity","public_bookcase"); + this.overpassFilter = new Tag("amenity", "public_bookcase"); this.minzoom = 13; + this.title = new NameInline("ruilboekenkastje"); + this.elementsToShow = [ - this.questions = [ + new TagRenderingOptions( + { + question: "Hoeveel boeken passen in dit boekenruilkastje?", + freeform: { + renderTemplate: "Er passen {capacity} boeken in dit boekenruilkastje", + template: "Er passen $$$ boeken in dit boekenruilkastje", + key: "capacity", + placeholder: "aantal" + }, + priority: 15 + } + ) + + ]; + + /* this.questions = [ QuestionDefinition.noNameOrNameQuestion("Wat is de naam van dit boekenruilkastje?", "Dit boekenruilkastje heeft niet echt een naam", 20), - QuestionDefinition.textQuestion("Hoeveel boeken kunnen er in?", "capacity", 15), QuestionDefinition.textQuestion("Heeft dit boekenkastje een peter, meter of voogd?", "operator", 10), - // QuestionDefinition.textQuestion("Wie kunnen we (per email) contacteren voor dit boekenruilkastje?", "email", 5), + // QuestionDefinition.textQuestion("Wie kunnen we (per email) contacteren voor dit boekenruilkastje?", "email", 5), - ] + ] ; + */ this.style = function (tags) { return { @@ -36,6 +54,7 @@ export class Bookcases extends LayerDefinition { }; } + /* this.elementsToShow = [ @@ -58,7 +77,7 @@ export class Bookcases extends LayerDefinition { new TagMappingOptions({key: "description", template: "Extra beschrijving:

{description}

"}), ] - ; + ;*/ } diff --git a/Customizations/Layers/Park.ts b/Customizations/Layers/Park.ts index 3736435..0ad8801 100644 --- a/Customizations/Layers/Park.ts +++ b/Customizations/Layers/Park.ts @@ -22,7 +22,29 @@ export class Park extends LayerDefinition { this.minzoom = 13; this.style = this.generateStyleFunction(); this.title = new NameInline("park"); - this.elementsToShow = [new NameQuestion()]; + this.elementsToShow = [new NameQuestion(), + new TagRenderingOptions({ + question: "Is dit park publiek toegankelijk?", + mappings: [ + {k: new Tag("access", "yes"), txt: "Publiek toegankelijk"}, + {k: new Tag("access", ""), txt: "Publiek toegankelijk"}, + {k: new Tag("access", "no"), txt: "Niet publiek toegankelijk"}, + {k: new Tag("access", "guided"), txt: "Enkel met een gids of tijdens activiteiten"}, + ] + }), + new TagRenderingOptions({ + question: "Wie beheert dit park?", + freeform: { + key: "operator", + renderTemplate: "Dit park wordt beheerd door {operator}", + template: "$$$", + }, + mappings: [{ + k: null, txt: "De gemeente beheert dit park" + }] + }) + + ]; } diff --git a/Customizations/Layouts/Groen.ts b/Customizations/Layouts/Groen.ts index 2432869..f27da74 100644 --- a/Customizations/Layouts/Groen.ts +++ b/Customizations/Layouts/Groen.ts @@ -6,7 +6,7 @@ import {Layout} from "../Layout"; export class Groen extends Layout { constructor() { - super("groen", + super("buurtnatuur", "Buurtnatuur", [new NatureReserves(), new Park(), new Bos()], 10, diff --git a/Customizations/TagRendering.ts b/Customizations/TagRendering.ts index 8c11961..397e0a1 100644 --- a/Customizations/TagRendering.ts +++ b/Customizations/TagRendering.ts @@ -73,7 +73,7 @@ export class TagRenderingOptions { const tagsKV = TagUtils.proprtiesToKV(tags); for (const oneOnOneElement of this.options.mappings) { - if (oneOnOneElement.k.matches(tagsKV)) { + if (oneOnOneElement.k === null || oneOnOneElement.k.matches(tagsKV)) { return false; } } @@ -148,9 +148,10 @@ export class TagRendering extends UIElement { this.elementPriority = options.priority ?? 0; // Prepare the choices for the Radio buttons - let i = 0; const choices: UIElement[] = []; - + + const usedChoices: string [] = []; + for (const choice of options.mappings ?? []) { if (choice.k === null) { this._mapping.push(choice); @@ -159,18 +160,24 @@ export class TagRendering extends UIElement { let choiceSubbed = choice; if (choice.substitute) { choiceSubbed = { - k : choice.k.substituteValues( + k: choice.k.substituteValues( options.tagsPreprocessor(this._source.data)), - txt : this.ApplyTemplate(choice.txt), + txt: this.ApplyTemplate(choice.txt), substitute: false, priority: choice.priority } } - - choices.push(new FixedUiElement(choiceSubbed.txt)); - this._mapping.push(choiceSubbed); - i++; + + const txt = choiceSubbed.txt + // Choices is what is shown in the radio buttons + if (usedChoices.indexOf(txt) < 0) { + + choices.push(new FixedUiElement(txt)); + usedChoices.push(txt); + // This is used to convert the radio button index into tags needed to add + this._mapping.push(choiceSubbed); + } } // Map radiobutton choice and textfield answer onto tagfilter. That tagfilter will be pushed into the changes later on @@ -200,6 +207,7 @@ export class TagRendering extends UIElement { // Prepare the actual input element -> pick an appropriate implementation let inputElement: UIInputElement; + if (this._freeform !== undefined && this._mapping !== undefined) { // Radio buttons with 'other' inputElement = new UIRadioButtonWithOther( @@ -210,14 +218,15 @@ export class TagRendering extends UIElement { pickString ); this._questionElement = inputElement; - } else if (this._mapping !== undefined) { + } else if (this._mapping !== [] && this._mapping.length > 0) { // This is a classic radio selection element - inputElement = new UIRadioButton(new UIEventSource(choices), pickChoice) + inputElement = new UIRadioButton(new UIEventSource(choices), pickChoice, false) this._questionElement = inputElement; } else if (this._freeform !== undefined) { this._textField = new TextField(new UIEventSource(this._freeform.placeholder), pickString); inputElement = this._textField; - this._questionElement = new FixedUiElement(this._freeform.template.replace("$$$", inputElement.Render())) + this._questionElement = new FixedUiElement( + "
" + this._freeform.template.replace("$$$", inputElement.Render()) + "
") } else { throw "Invalid questionRendering, expected at least choices or a freeform" } @@ -234,6 +243,7 @@ export class TagRendering extends UIElement { const cancel = () => { self._questionSkipped.setData(true); self._editMode.setData(false); + self._source.ping(); // Send a ping upstream to render the next question } // Setup the save button and it's action @@ -352,7 +362,7 @@ export class TagRendering extends UIElement { return "
" + - this._question + + "" + this._question + "" + (this._question !== "" ? "
" : "") + this._questionElement.Render() + this._skipButton.Render() + diff --git a/UI/Base/UIRadioButton.ts b/UI/Base/UIRadioButton.ts index d368dc6..2413073 100644 --- a/UI/Base/UIRadioButton.ts +++ b/UI/Base/UIRadioButton.ts @@ -74,9 +74,11 @@ export class UIRadioButton extends UIInputElement { if (this.SelectedElementIndex.data == null) { if (this._selectFirstAsDefault) { const el = document.getElementById(this.IdFor(0)); - // @ts-ignore - el.checked = true; - checkButtons(); + if (el) { + // @ts-ignore + el.checked = true; + checkButtons(); + } } } else { diff --git a/index.css b/index.css index f95ddb0..6d4f2f2 100644 --- a/index.css +++ b/index.css @@ -8,6 +8,10 @@ body { font-family: 'Helvetica Neue', Arial, sans-serif; } +form { + display: inline; +} + #leafletDiv { height: 100%; } @@ -670,6 +674,11 @@ body { } +.question-text{ + font-size: larger; + font-weight: bold; +} + .answer { display: inline-block; margin: 0.1em; diff --git a/index.ts b/index.ts index d9bb317..d05afba 100644 --- a/index.ts +++ b/index.ts @@ -38,7 +38,7 @@ if (location.hostname === "localhost" || location.hostname === "127.0.0.1") { // ----------------- SELECT THE RIGHT QUESTSET ----------------- -let defaultQuest = "groen" +let defaultQuest = "buurtnatuur" if (window.location.search) { const params = window.location.search.substr(1).split("&"); const paramDict: any = {}; @@ -88,7 +88,7 @@ const saveTimeout = 30000; // After this many milliseconds without changes, save const allElements = new ElementStorage(); const osmConnection = new OsmConnection(dryRun); const changes = new Changes( - "Beantwoorden van vragen met MapComplete voor vragenset #" + questSetToRender.name, + "Beantwoorden van vragen met #MapComplete voor vragenset #" + questSetToRender.name, osmConnection, allElements); const bm = new Basemap("leafletDiv", locationControl, new VariableUiElement( locationControl.map((location) => { diff --git a/test.ts b/test.ts index 9079435..c44fb88 100644 --- a/test.ts +++ b/test.ts @@ -5,17 +5,3 @@ import {ElementStorage} from "./Logic/ElementStorage"; import {WikipediaLink} from "./Customizations/Questions/WikipediaLink"; import {OsmLink} from "./Customizations/Questions/OsmLink"; -const tags = {name: "Test", - wikipedia: "nl:Pieter", - id: "node/-1"}; -const tagsES = new UIEventSource(tags); - -const login = new OsmConnection(true); - -const allElements = new ElementStorage(); -allElements.addElementById(tags.id, tagsES); - -const changes = new Changes("Test", login, allElements) - - -new OsmLink(tagsES, changes).AttachTo("maindiv"); \ No newline at end of file From 9308eeaeabed3296d528d180c8b87deb163c19e1 Mon Sep 17 00:00:00 2001 From: Pieter Fiers Date: Wed, 8 Jul 2020 16:08:24 +0200 Subject: [PATCH 8/9] Change cyclofix quest description --- Customizations/Layouts/BikePumps.ts | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/Customizations/Layouts/BikePumps.ts b/Customizations/Layouts/BikePumps.ts index a3902fb..2283b8e 100644 --- a/Customizations/Layouts/BikePumps.ts +++ b/Customizations/Layouts/BikePumps.ts @@ -13,10 +13,14 @@ export class BikePumpsLayout extends Layout { 3.2279, - "

Open CycloFix

\n" + + "

Cyclofix bicycle infrastructure

\n" + "\n" + - "Something something bikes" - + "

EN> On this map we want to collect data about the whereabouts of bicycle pumps and public racks in Brussels." + + "As a result, cyclists will be able to quickly find the nearest infrastructure for their needs.

" + + "

NL> Op deze kaart willen we gegevens verzamelen over de locatie van fietspompen en openbare stelplaatsen in Brussel." + + "Hierdoor kunnen fietsers snel de dichtstbijzijnde infrastructuur vinden die voldoet aan hun behoeften.

" + + "

FR> Sur cette carte, nous voulons collecter des données sur la localisation des pompes à vélo et des supports publics à Bruxelles." + + "Les cyclistes pourront ainsi trouver rapidement l'infrastructure la plus proche de leurs besoins.

" , "", ""); } From 811a98ebd3e63c9115a0b91ec6c68c9f758fda50 Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Wed, 8 Jul 2020 16:24:12 +0200 Subject: [PATCH 9/9] Small change: defaults show up now --- Customizations/TagRendering.ts | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/Customizations/TagRendering.ts b/Customizations/TagRendering.ts index 695c122..d299e9d 100644 --- a/Customizations/TagRendering.ts +++ b/Customizations/TagRendering.ts @@ -98,6 +98,8 @@ export class TagRendering extends UIElement { private _question: string; private _primer: string; private _mapping: { k: TagsFilter, txt: string, priority?: number, substitute?: boolean }[]; + private _renderMapping: { k: TagsFilter, txt: string, priority?: number, substitute?: boolean }[]; + private _tagsPreprocessor?: ((tags: any) => any); private _freeform: { key: string, template: string, @@ -143,6 +145,7 @@ export class TagRendering extends UIElement { this._primer = options.primer ?? ""; this._tagsPreprocessor = options.tagsPreprocessor; this._mapping = []; + this._renderMapping = []; this._freeform = options.freeform; this.elementPriority = options.priority ?? 0; @@ -175,6 +178,8 @@ export class TagRendering extends UIElement { usedChoices.push(txt); // This is used to convert the radio button index into tags needed to add this._mapping.push(choiceSubbed); + } else { + this._renderMapping.push(choiceSubbed); // only used while rendering } } @@ -289,11 +294,12 @@ export class TagRendering extends UIElement { IsKnown(): boolean { const tags = TagUtils.proprtiesToKV(this._source.data); - for (const oneOnOneElement of this._mapping) { + for (const oneOnOneElement of this._mapping.concat(this._renderMapping)) { if (oneOnOneElement.k === null || oneOnOneElement.k.matches(tags)) { return true; } } + return this._freeform !== undefined && this._source.data[this._freeform.key] !== undefined; } @@ -322,11 +328,10 @@ export class TagRendering extends UIElement { freeformScore = 0; } - if (this._mapping !== undefined) { let highestScore = -100; let highestTemplate = undefined; - for (const oneOnOneElement of this._mapping) { + for (const oneOnOneElement of this._mapping.concat(this._renderMapping)) { if (oneOnOneElement.k == null || oneOnOneElement.k.matches(tags)) { // We have found a matching key -> we use the template, but only if it scores better @@ -347,9 +352,7 @@ export class TagRendering extends UIElement { // we render the found template return this._primer + this.ApplyTemplate(highestTemplate); } - } else { - return freeform; - } + }