forked from MapComplete/MapComplete
		
	Add image delete button
This commit is contained in:
		
							parent
							
								
									f548ddea84
								
							
						
					
					
						commit
						0fe6b67976
					
				
					 13 changed files with 303 additions and 54 deletions
				
			
		|  | @ -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 | ||||
|          | ||||
|         ]; | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|  |  | |||
|  | @ -6,16 +6,16 @@ export class BikePumpsLayout extends Layout { | |||
|     constructor() { | ||||
|         super( | ||||
|             "pomp", | ||||
|             "Grb import fix tool", | ||||
|             "Cyclofix", | ||||
|             [new BikePumps()], | ||||
|             15, | ||||
|             51.2083, | ||||
|             3.2279, | ||||
| 
 | ||||
| 
 | ||||
|             "<h3>GRB Fix tool</h3>\n" + | ||||
|             "<h3>Open CycloFix</h3>\n" + | ||||
|             "\n" + | ||||
|             "Expert use only" | ||||
|             "Something something bikes" | ||||
| 
 | ||||
|             , | ||||
|             "", ""); | ||||
|  |  | |||
|  | @ -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 | ||||
|                 } | ||||
|             } | ||||
| 
 | ||||
|             const txt = choiceSubbed.txt; | ||||
|             if (alreadyUsedTexts.indexOf(txt) < 0) { | ||||
|                 choices.push(new FixedUiElement(txt)); | ||||
|                 alreadyUsedTexts.push(txt); | ||||
|             } | ||||
| 
 | ||||
| 
 | ||||
|             choices.push(new FixedUiElement(choiceSubbed.txt)); | ||||
|             this._mapping.push(choiceSubbed); | ||||
|             i++; | ||||
|         } | ||||
|  |  | |||
|  | @ -105,9 +105,8 @@ export class ImageSearcher extends UIEventSource<string[]> { | |||
|         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<string[]> { | |||
|             } | ||||
|         } | ||||
| 
 | ||||
|         const image0 = this._tags.data["image:0"]; | ||||
|         if (image0 !== undefined) { | ||||
|             this.AddImage(image0); | ||||
|         for (const key in this._tags.data) { | ||||
|             // @ts-ignore
 | ||||
|             if(key.startsWith("image:")){ | ||||
|                 this.AddImage(this._tags.data[key]); | ||||
|             } | ||||
|         let imageIndex = 1; | ||||
|         let imagei = this._tags.data["image:" + imageIndex]; | ||||
|         while (imagei !== undefined) { | ||||
|             this.AddImage(imagei); | ||||
|             imageIndex++; | ||||
|             imagei = this._tags.data["image:" + imageIndex]; | ||||
|         } | ||||
| 
 | ||||
|         const wdItem = this._tags.data.wikidata; | ||||
|  |  | |||
|  | @ -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 | ||||
|  |  | |||
							
								
								
									
										69
									
								
								UI/ConfirmDialog.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										69
									
								
								UI/ConfirmDialog.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -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<boolean> = new UIEventSource<boolean>(false); | ||||
| 
 | ||||
|     private _question: UIElement; | ||||
|     private _optionA: UIElement; | ||||
|     private _optionB: UIElement; | ||||
| 
 | ||||
|     constructor( | ||||
|         show: UIEventSource<boolean>, | ||||
|         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("<span class='ui-question'>" + question + "</span>") | ||||
|             .onClick(() => { | ||||
|                 self._showOptions.setData(!self._showOptions.data); | ||||
|             }); | ||||
|         this._optionA = new VariableUiElement( | ||||
|             this._showOptions.map( | ||||
|                 (show) => show ? "<div class='" + classA + "'>" + optionA + "</div>" : "")) | ||||
|             .onClick(() => { | ||||
|                     self._showOptions.setData(false); | ||||
|                     executeA(); | ||||
|                 } | ||||
|             ); | ||||
|         this._optionB = new VariableUiElement( | ||||
|             this._showOptions.map((show) => | ||||
|                 show ? "<div class='" + classB + "'>" + optionB + "</div>" : "")        ) | ||||
|             .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(); | ||||
|     } | ||||
| 
 | ||||
| } | ||||
|  | @ -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<UIElement[]>; | ||||
| 
 | ||||
|     private readonly _deleteButtonText = new UIEventSource<string>(""); | ||||
|     private readonly _deleteButton: UIElement; | ||||
| 
 | ||||
|     constructor(tags: UIEventSource<any>, 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, | ||||
|             "<img src='assets/delete.svg' alt='Afbeelding verwijderen' class='delete-image'>", | ||||
|             "<span>Afbeelding verwijderen</span>", | ||||
|             "<span>Terug</span>", | ||||
|             deleteCurrent, | ||||
|             () => {}, | ||||
|             'delete-image-confirm', | ||||
|             'delete-image-cancel'); | ||||
|     } | ||||
| 
 | ||||
|     InnerRender(): string { | ||||
|         return this.slideshow.Render() ; | ||||
|             // + this._deleteButton.Render();
 | ||||
|         return "<span class='image-carousel-container'>" + | ||||
|             "<div class='image-delete-container'>" + | ||||
|             this._deleteButton.Render() + | ||||
|             "</div>" + | ||||
|             this.slideshow.Render() + | ||||
|             "</span>"; | ||||
|     } | ||||
| 
 | ||||
|     InnerUpdate(htmlElement: HTMLElement) { | ||||
|  |  | |||
|  | @ -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); | ||||
|  |  | |||
|  | @ -27,16 +27,24 @@ export class UIEventSource<T>{ | |||
|         } | ||||
|     } | ||||
| 
 | ||||
|     public map<J>(f: ((T) => J)): UIEventSource<J> { | ||||
|     public map<J>(f: ((T) => J), | ||||
|                   extraSources : UIEventSource<any>[] = []): UIEventSource<J> { | ||||
|         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<J>( | ||||
|             f(this.data) | ||||
|         ); | ||||
|          | ||||
| 
 | ||||
|         return newSource; | ||||
| 
 | ||||
|     } | ||||
|  |  | |||
							
								
								
									
										55
									
								
								assets/delete.svg
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										55
									
								
								assets/delete.svg
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,55 @@ | |||
| <?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" | ||||
|    xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" | ||||
|    xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" | ||||
|    viewBox="0 -256 1792 1792" | ||||
|    id="svg3741" | ||||
|    version="1.1" | ||||
|    inkscape:version="0.92.4 (5da689c313, 2019-01-14)" | ||||
|    width="100%" | ||||
|    height="100%" | ||||
|    sodipodi:docname="delete.svg"> | ||||
|   <metadata | ||||
|      id="metadata3751"> | ||||
|     <rdf:RDF> | ||||
|       <cc:Work | ||||
|          rdf:about=""> | ||||
|         <dc:format>image/svg+xml</dc:format> | ||||
|         <dc:type | ||||
|            rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> | ||||
|       </cc:Work> | ||||
|     </rdf:RDF> | ||||
|   </metadata> | ||||
|   <defs | ||||
|      id="defs3749" /> | ||||
|   <sodipodi:namedview | ||||
|      pagecolor="#ffffff" | ||||
|      bordercolor="#666666" | ||||
|      borderopacity="1" | ||||
|      objecttolerance="10" | ||||
|      gridtolerance="10" | ||||
|      guidetolerance="10" | ||||
|      inkscape:pageopacity="0" | ||||
|      inkscape:pageshadow="2" | ||||
|      inkscape:window-width="1920" | ||||
|      inkscape:window-height="1001" | ||||
|      id="namedview3747" | ||||
|      showgrid="false" | ||||
|      inkscape:zoom="0.18624688" | ||||
|      inkscape:cx="795.91988" | ||||
|      inkscape:cy="822.60792" | ||||
|      inkscape:window-x="0" | ||||
|      inkscape:window-y="0" | ||||
|      inkscape:window-maximized="1" | ||||
|      inkscape:current-layer="svg3741" /> | ||||
|   <path | ||||
|      style="fill:#ff0000;fill-opacity:1" | ||||
|      inkscape:connector-curvature="0" | ||||
|      id="path3745" | ||||
|      d="m 709.42373,455.0508 v 576 q 0,14 -9,23 -9,9 -23,9 h -64 q -14,0 -23,-9 -9,-9 -9,-23 v -576 q 0,-14 9,-23 9,-9 23,-9 h 64 q 14,0 23,9 9,9 9,23 z m 256,0 v 576 q 0,14 -9,23 -9,9 -23,9 h -64 q -14,0 -23,-9 -9,-9 -9,-23 v -576 q 0,-14 9,-23 9,-9 23,-9 h 64 q 14,0 23,9 9,9 9,23 z m 255.99997,0 v 576 q 0,14 -9,23 -9,9 -23,9 h -64 q -14,0 -23,-9 -9,-9 -9,-23 v -576 q 0,-14 9,-23 9,-9 23,-9 h 64 q 14,0 23,9 9,9 9,23 z m 128,724 v -948 H 453.42373 v 948 q 0,22 7,40.5 7,18.5 14.5,27 7.5,8.5 10.5,8.5 h 831.99997 q 3,0 10.5,-8.5 7.5,-8.5 14.5,-27 7,-18.5 7,-40.5 z m -671.99997,-1076 h 447.99997 l -48,-117 q -7,-9 -17,-11 H 743.42373 q -10,2 -17,11 z m 927.99997,32 v 64 q 0,14 -9,23 -9,9 -23,9 h -96 v 948 q 0,83 -47,143.5 -47,60.5 -113,60.5 H 485.42373 q -66,0 -113,-58.5 -47,-58.5 -47,-141.5 v -952 h -96 q -14,0 -23,-9 -9,-9 -9,-23 v -64 q 0,-14 9,-23 9,-9 23,-9 h 309 l 70,-167 q 15,-37 54,-63 39,-26 79,-26 h 319.99997 q 40,0 79,26 39,26 54,63 l 70,167 h 309 q 14,0 23,9 9,9 9,23 z" /> | ||||
| </svg> | ||||
| After Width: | Height: | Size: 2.5 KiB | 
							
								
								
									
										78
									
								
								index.css
									
										
									
									
									
								
							
							
						
						
									
										78
									
								
								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 *****/ | ||||
| 
 | ||||
|  |  | |||
|  | @ -5,7 +5,9 @@ | |||
|     <link href="index.css" rel="stylesheet"/>    | ||||
| </head> | ||||
| <body> | ||||
| <span class="image-delete-container"> | ||||
| <div id="maindiv">'maindiv' not attached</div> | ||||
| </span> | ||||
| <div id="extradiv">'extradiv' not attached</div> | ||||
| <script src="./test.ts"></script> | ||||
| </body> | ||||
|  |  | |||
							
								
								
									
										28
									
								
								test.ts
									
										
									
									
									
								
							
							
						
						
									
										28
									
								
								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"); | ||||
| new ConfirmDialog(new UIEventSource<boolean>(true), | ||||
|     "<img src='assets/delete.svg' alt='Afbeelding verwijderen' class='delete-image'>", | ||||
|     "Deze afbeelding verwijderen", | ||||
|     "Terug", | ||||
| 
 | ||||
|     () => { | ||||
|         console.log("Verwijderen"); | ||||
|     }, | ||||
|     () => { | ||||
|         console.log("terug") | ||||
|     }, | ||||
|     'delete-image-confirm', | ||||
|     'delete-image-cancel') | ||||
|     .AttachTo("maindiv") | ||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue