forked from MapComplete/MapComplete
		
	Performance: trim svg.ts, use Svelte-SVG-componenets where possible (WIP)
This commit is contained in:
		
							parent
							
								
									ac5c60c3f0
								
							
						
					
					
						commit
						c13d80f062
					
				
					 37 changed files with 367 additions and 378 deletions
				
			
		|  | @ -160,7 +160,7 @@ | |||
|               "craft=key_cutter" | ||||
|             ] | ||||
|           }, | ||||
|           "then": "./assets/layers/id_presets/fas-key.svg" | ||||
|           "then": "circle:white;./assets/layers/id_presets/fas-key.svg" | ||||
|         } | ||||
|       ], | ||||
|       "label": { | ||||
|  |  | |||
|  | @ -1,4 +0,0 @@ | |||
| <?xml version="1.0" encoding="UTF-8" standalone="yes"?> | ||||
| <svg xmlns="http://www.w3.org/2000/svg" width="374px" height="259px" viewBox="0 0 374 259" version="1.1"> | ||||
|   <g id="surface1"/> | ||||
| </svg> | ||||
| Before Width: | Height: | Size: 189 B | 
|  | @ -1,4 +0,0 @@ | |||
| <?xml version="1.0" encoding="UTF-8" standalone="yes"?> | ||||
| <svg xmlns="http://www.w3.org/2000/svg" width="375px" height="375px" viewBox="0 0 375 375" version="1.1"> | ||||
|   <g id="surface1"/> | ||||
| </svg> | ||||
| Before Width: | Height: | Size: 189 B | 
|  | @ -1,11 +1,47 @@ | |||
| import * as fs from "fs" | ||||
| 
 | ||||
| function genImages(dryrun = false) { | ||||
|     console.log("Generating images") | ||||
|     // These images are not referenced via 'Svg.ts' anymore and can be ignored
 | ||||
|     const blacklist: string[] = [ | ||||
|         "SocialImageForeground", | ||||
|         "wikipedia", | ||||
|         "Upload", | ||||
|         "pin", | ||||
|         "mapillary_black", | ||||
|         "plantnet_logo", | ||||
|         "mastodon", | ||||
|         "move-arrows", | ||||
|         "mapcomplete_logo", | ||||
|         "logo", | ||||
|         "logout", | ||||
|         "hand", | ||||
|         "help", | ||||
|         "home", | ||||
|         "reload", | ||||
|         "min", | ||||
|         "plus", | ||||
|         "not_found", | ||||
|         "osm_logo_us", | ||||
|         "party", | ||||
|         "filter", | ||||
|         "filter_disable", | ||||
|         "floppy", | ||||
|         "eye", | ||||
|         "gear", | ||||
|         "gender_bi", | ||||
|         "compass", | ||||
|         "blocked", | ||||
|         "brick_wall", | ||||
|         "brick_wall_raw", | ||||
|         "brick_wall_round", | ||||
|         "bug", | ||||
|         "back", | ||||
|     ].map((s) => s.toLowerCase()) | ||||
|     const dir = fs.readdirSync("./assets/svg") | ||||
| 
 | ||||
|     let module = | ||||
|         'import Img from "./UI/Base/Img";\nimport {FixedUiElement} from "./UI/Base/FixedUiElement";\n\n/* @deprecated */\nexport default class Svg {\n\n\n' | ||||
|     const allNames: string[] = [] | ||||
|     for (const path of dir) { | ||||
|         if (path.endsWith("license_info.json")) { | ||||
|             continue | ||||
|  | @ -21,6 +57,7 @@ function genImages(dryrun = false) { | |||
|         let svg: string = fs | ||||
|             .readFileSync("./assets/svg/" + path, "utf-8") | ||||
|             .replace(/<\?xml.*?>/, "") | ||||
|             .replace(/<!DOCTYPE [^>]*>/, "") | ||||
|             .replace(/fill: ?none;/g, "fill: none !important;") // This is such a brittle hack...
 | ||||
|             .replace(/\n/g, " ") | ||||
|             .replace(/\r/g, "") | ||||
|  | @ -37,18 +74,6 @@ function genImages(dryrun = false) { | |||
|         } | ||||
|         const name = path.substring(0, path.length - 4).replace(/[ -]/g, "_") | ||||
| 
 | ||||
|         if (dryrun) { | ||||
|             svg = "<omitting svg - dryrun>" | ||||
|         } | ||||
| 
 | ||||
|         let rawName = name | ||||
| 
 | ||||
|         module += `    public static ${name} = "${svg}"\n` | ||||
|         module += `    public static ${name}_svg() { return new Img(Svg.${rawName}, true);}\n` | ||||
|         if (!dryrun) { | ||||
|             allNames.push(`"${path}": Svg.${name}`) | ||||
|         } | ||||
| 
 | ||||
|         const nameUC = name.toUpperCase().at(0) + name.substring(1) | ||||
|         const svelteCode = | ||||
|             '<script>\nexport let color = "#000000"\n</script>\n' + | ||||
|  | @ -60,8 +85,23 @@ function genImages(dryrun = false) { | |||
|                 .replace(/\\"/g, '"') | ||||
|                 .replace(/(rgb\(0%,0%,0%\)|#000000|#000)/g, "{color}") | ||||
|         fs.writeFileSync("./src/assets/svg/" + nameUC + ".svelte", svelteCode, "utf8") | ||||
| 
 | ||||
|         if (blacklist.some((item) => path.toLowerCase().endsWith(item + ".svg"))) { | ||||
|             continue | ||||
|         } | ||||
|         if (dryrun) { | ||||
|             svg = "<omitting svg - dryrun>" | ||||
|         } | ||||
| 
 | ||||
|         let rawName = name | ||||
| 
 | ||||
|         module += `    public static ${name} = "${svg}"\n` | ||||
|         if (!dryrun) { | ||||
|             module += `    public static ${name}_svg() { return new Img(Svg.${rawName}, true);}\n` | ||||
|         } else { | ||||
|             module += `    public static ${name}_svg() { return new Img("", true);}\n` | ||||
|         } | ||||
|     } | ||||
|     module += `public static All = {${allNames.join(",")}};` | ||||
|     module += "}\n" | ||||
|     fs.writeFileSync("src/Svg.ts", module) | ||||
|     console.log("Done") | ||||
|  |  | |||
|  | @ -92,7 +92,7 @@ export class DoesImageExist extends DesugaringStep<string> { | |||
|             return image | ||||
|         } | ||||
|         if (image.match(/[a-z]*/)) { | ||||
|             if (Svg.All[image + ".svg"] !== undefined) { | ||||
|             if (Constants.defaultPinIcons.indexOf(image) >= 0) { | ||||
|                 // This is a builtin img, e.g. 'checkmark' or 'crosshair'
 | ||||
|                 return image | ||||
|             } | ||||
|  |  | |||
|  | @ -14,6 +14,7 @@ import { VariableUiElement } from "../../UI/Base/VariableUIElement" | |||
| import { TagRenderingConfigJson } from "./Json/TagRenderingConfigJson" | ||||
| import SvelteUIElement from "../../UI/Base/SvelteUIElement" | ||||
| import DynamicMarker from "../../UI/Map/DynamicMarker.svelte" | ||||
| import { html } from "svelte/types/compiler/utils/namespaces" | ||||
| 
 | ||||
| export class IconConfig extends WithContextLoader { | ||||
|     public static readonly defaultIcon = new IconConfig({ icon: "pin", color: "#ff9939" }) | ||||
|  | @ -133,71 +134,23 @@ export default class PointRenderingConfig extends WithContextLoader { | |||
|             context + ".rotationAlignment" | ||||
|         ) | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Given a single HTML spec (either a single image path OR "image_path_to_known_svg:fill-colour", returns a fixedUIElement containing that | ||||
|      * The element will fill 100% and be positioned absolutely with top:0 and left: 0 | ||||
|      */ | ||||
|     private static FromHtmlSpec(htmlSpec: string, style: string, isBadge = false): BaseUIElement { | ||||
|         if (htmlSpec === undefined) { | ||||
|             return undefined | ||||
|     private static FromHtmlMulti(multiSpec: string, tags: Store<Record<string, string>>) { | ||||
|         const icons: IconConfig[] = [] | ||||
|         for (const subspec of multiSpec.split(";")) { | ||||
|             const [icon, color] = subspec.split(":") | ||||
|             icons.push(new IconConfig({ icon, color })) | ||||
|         } | ||||
|         const match = htmlSpec.match(/([a-zA-Z0-9_]*):([^;]*)/) | ||||
|         if (match !== null && Svg.All[match[1] + ".svg"] !== undefined) { | ||||
|             const svg = Svg.All[match[1] + ".svg"] as string | ||||
|             const targetColor = match[2] | ||||
|             const img = new Img( | ||||
|                 svg.replace(/(rgb\(0%,0%,0%\)|#000000|#000)/g, targetColor), | ||||
|                 true | ||||
|             ).SetStyle(style) | ||||
|             if (isBadge) { | ||||
|                 img.SetClass("badge") | ||||
|             } | ||||
|             return img | ||||
|         } else if (Svg.All[htmlSpec + ".svg"] !== undefined) { | ||||
|             const svg = Svg.All[htmlSpec + ".svg"] as string | ||||
|             const img = new Img(svg, true).SetStyle(style) | ||||
|             if (isBadge) { | ||||
|                 img.SetClass("badge") | ||||
|             } | ||||
|             return img | ||||
|         } else { | ||||
|             return new FixedUiElement(`<img src="${htmlSpec}" style="${style}" />`) | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     private static FromHtmlMulti( | ||||
|         multiSpec: string, | ||||
|         rotation: string, | ||||
|         isBadge: boolean, | ||||
|         defaultElement: BaseUIElement = undefined, | ||||
|         options?: { | ||||
|             noFullWidth?: boolean | ||||
|         } | ||||
|     ) { | ||||
|         if (multiSpec === undefined) { | ||||
|             return defaultElement | ||||
|         } | ||||
|         const style = `width:100%;height:100%;transform: rotate( ${rotation} );display:block;position: absolute; top: 0; left: 0` | ||||
| 
 | ||||
|         const htmlDefs = multiSpec.trim()?.split(";") ?? [] | ||||
|         const elements = Utils.NoEmpty(htmlDefs).map((def) => | ||||
|             PointRenderingConfig.FromHtmlSpec(def, style, isBadge) | ||||
|         return new SvelteUIElement(DynamicMarker, { marker: icons, tags }).SetClass( | ||||
|             "w-full h-full block absolute top-0 left-0" | ||||
|         ) | ||||
|         if (elements.length === 0) { | ||||
|             return defaultElement | ||||
|         } else { | ||||
|             const combine = new Combine(elements).SetClass("relative block") | ||||
|             if (options?.noFullWidth) { | ||||
|                 return combine | ||||
|             } | ||||
|             combine.SetClass("w-full h-full") | ||||
|             return combine | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     public GetBaseIcon(tags?: Record<string, string>): BaseUIElement { | ||||
|         return new SvelteUIElement(DynamicMarker, { config: this, tags: new ImmutableStore(tags) }) | ||||
|         return new SvelteUIElement(DynamicMarker, { | ||||
|             marker: this.marker, | ||||
|             rotation: this.rotation, | ||||
|             tags: new ImmutableStore(tags), | ||||
|         }) | ||||
|     } | ||||
| 
 | ||||
|     public RenderIcon( | ||||
|  | @ -249,9 +202,11 @@ export default class PointRenderingConfig extends WithContextLoader { | |||
|             anchorH = -iconH / 2 | ||||
|         } | ||||
| 
 | ||||
|         const icon = new SvelteUIElement(DynamicMarker, { config: this, tags }).SetClass( | ||||
|             "w-full h-full" | ||||
|         ) | ||||
|         const icon = new SvelteUIElement(DynamicMarker, { | ||||
|             marker: this.marker, | ||||
|             rotation: this.rotation, | ||||
|             tags, | ||||
|         }).SetClass("w-full h-full") | ||||
|         let badges = undefined | ||||
|         if (options?.includeBadges ?? true) { | ||||
|             badges = this.GetBadges(tags, options?.metatags) | ||||
|  | @ -264,7 +219,6 @@ export default class PointRenderingConfig extends WithContextLoader { | |||
|         tags.map((tags) => this.iconSize.GetRenderValue(tags).Subs(tags).txt ?? "[40,40]").map( | ||||
|             (size) => { | ||||
|                 const [iconW, iconH] = size.split(",").map((x) => num(x)) | ||||
|                 console.log("Setting size to", iconW, iconH) | ||||
|                 iconAndBadges.SetStyle(`width: ${iconW}px; height: ${iconH}px`) | ||||
|             } | ||||
|         ) | ||||
|  | @ -307,9 +261,9 @@ export default class PointRenderingConfig extends WithContextLoader { | |||
|         } | ||||
|         return new VariableUiElement( | ||||
|             tags.map( | ||||
|                 (tags) => { | ||||
|                 (tagsData) => { | ||||
|                     const badgeElements = this.iconBadges.map((badge) => { | ||||
|                         if (!badge.if.matchesProperties(tags)) { | ||||
|                         if (!badge.if.matchesProperties(tagsData)) { | ||||
|                             // Doesn't match...
 | ||||
|                             return undefined | ||||
|                         } | ||||
|  | @ -324,19 +278,23 @@ export default class PointRenderingConfig extends WithContextLoader { | |||
|                         } | ||||
| 
 | ||||
|                         const htmlDefs = Utils.SubstituteKeys( | ||||
|                             badge.then.GetRenderValue(tags)?.txt, | ||||
|                             tags | ||||
|                             badge.then.GetRenderValue(tagsData)?.txt, | ||||
|                             tagsData | ||||
|                         ) | ||||
|                         if (htmlDefs.startsWith("<") && htmlDefs.endsWith(">")) { | ||||
|                             // This is probably an HTML-element
 | ||||
|                             return new FixedUiElement(Utils.SubstituteKeys(htmlDefs, tags)) | ||||
|                             return new FixedUiElement(Utils.SubstituteKeys(htmlDefs, tagsData)) | ||||
|                                 .SetStyle("width: 1.5rem") | ||||
|                                 .SetClass("block") | ||||
|                         } | ||||
| 
 | ||||
|                         if (!htmlDefs) { | ||||
|                             return undefined | ||||
|                         } | ||||
| 
 | ||||
|                         const badgeElement = PointRenderingConfig.FromHtmlMulti( | ||||
|                             htmlDefs, | ||||
|                             "0", | ||||
|                             true | ||||
|                             tags | ||||
|                         )?.SetClass("block relative") | ||||
|                         if (badgeElement === undefined) { | ||||
|                             return undefined | ||||
|  |  | |||
|  | @ -19,6 +19,7 @@ import { Paragraph } from "../../UI/Base/Paragraph" | |||
| import Svg from "../../Svg" | ||||
| import Validators, { ValidatorType } from "../../UI/InputElement/Validators" | ||||
| import { TagRenderingConfigJson } from "./Json/TagRenderingConfigJson" | ||||
| import Constants from "../Constants" | ||||
| 
 | ||||
| export interface Icon {} | ||||
| 
 | ||||
|  | @ -362,7 +363,7 @@ export default class TagRenderingConfig { | |||
|                 if (stripped.endsWith(".svg")) { | ||||
|                     stripped = stripped.substring(0, stripped.length - 4) | ||||
|                 } | ||||
|                 if (Svg.All[stripped + ".svg"] !== undefined) { | ||||
|                 if (Constants.defaultPinIcons.indexOf(stripped) >= 0) { | ||||
|                     icon = "./assets/svg/" + mapping.icon | ||||
|                     if (!icon.endsWith(".svg")) { | ||||
|                         icon += ".svg" | ||||
|  |  | |||
|  | @ -5,6 +5,7 @@ | |||
|    */ | ||||
|   import { Store } from "../../Logic/UIEventSource" | ||||
|   import { onDestroy } from "svelte" | ||||
|   import Hand from "../../assets/svg/Hand.svelte"; | ||||
| 
 | ||||
|   let mainElem: HTMLElement | ||||
|   export let hideSignal: Store<any> | ||||
|  | @ -34,7 +35,7 @@ | |||
| 
 | ||||
| <div bind:this={mainElem} class="pointer-events-none absolute bottom-0 right-0 h-full w-full"> | ||||
|   <div id="hand-container"> | ||||
|     <img src="./assets/svg/hand.svg" /> | ||||
|     <Hand /> | ||||
|   </div> | ||||
| </div> | ||||
| 
 | ||||
|  |  | |||
|  | @ -6,6 +6,7 @@ | |||
|   import Tr from "./Tr.svelte" | ||||
|   import { OsmConnection } from "../../Logic/Osm/OsmConnection" | ||||
|   import { ImmutableStore, UIEventSource } from "../../Logic/UIEventSource" | ||||
|   import Invalid from "../../assets/svg/Invalid.svelte"; | ||||
| 
 | ||||
|   export let state: { | ||||
|     osmConnection: OsmConnection | ||||
|  | @ -35,7 +36,7 @@ | |||
|     </slot> | ||||
|   {:else if $loadingStatus === "error"} | ||||
|     <div class="alert max-w-64 flex items-center"> | ||||
|       <img src="./assets/svg/invalid.svg" class="m-2 h-8 w-8 shrink-0" /> | ||||
|       <Invalid class="m-2 h-8 w-8 shrink-0" /> | ||||
|       <Tr t={offlineModes[$apiState]} /> | ||||
|     </div> | ||||
|   {:else if $loadingStatus === "logged-in"} | ||||
|  |  | |||
							
								
								
									
										15
									
								
								src/UI/Base/LogoutButton.svelte
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								src/UI/Base/LogoutButton.svelte
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,15 @@ | |||
| <script lang="ts"> | ||||
|   import { OsmConnection } from "../../Logic/Osm/OsmConnection"; | ||||
|   import Logout from "../../assets/svg/Logout.svelte"; | ||||
|   import Translations from "../i18n/Translations"; | ||||
|   import Tr from "./Tr.svelte"; | ||||
| 
 | ||||
|   export let osmConnection: OsmConnection | ||||
| </script> | ||||
| 
 | ||||
| <button on:click={() => { | ||||
|                         state.osmConnection.LogOut() | ||||
|                     }}> | ||||
|   <Logout class="w-6 h-6"/> | ||||
|   <Tr t={Translations.t.general.logout}/> | ||||
| </button> | ||||
							
								
								
									
										48
									
								
								src/UI/Base/OpenJosm.svelte
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										48
									
								
								src/UI/Base/OpenJosm.svelte
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,48 @@ | |||
| <script lang="ts"> | ||||
|   import { UIEventSource } from "../../Logic/UIEventSource"; | ||||
|   import Translations from "../i18n/Translations"; | ||||
|   import Tr from "./Tr.svelte"; | ||||
|   import Josm_logo from "../../assets/svg/Josm_logo.svelte"; | ||||
|   import Constants from "../../Models/Constants"; | ||||
|   import type { SpecialVisualizationState } from "../SpecialVisualization"; | ||||
| 
 | ||||
|   export let state : SpecialVisualizationState | ||||
|   const t = Translations.t.general.attribution; | ||||
|   const josmState = new UIEventSource<"OK" | string>(undefined); | ||||
|   // Reset after 15s | ||||
|   josmState.stabilized(15000).addCallbackD(() => josmState.setData(undefined)); | ||||
| 
 | ||||
|   const showButton = state.osmConnection.userDetails.map( | ||||
|     (ud) => ud.loggedIn && ud.csCount >= Constants.userJourney.historyLinkVisible | ||||
|   ); | ||||
| 
 | ||||
|   function openJosm() { | ||||
|     const bbox = state.mapProperties. bounds.data; | ||||
|     if (bbox === undefined) { | ||||
|       return; | ||||
|     } | ||||
|     const top = bbox.getNorth(); | ||||
|     const bottom = bbox.getSouth(); | ||||
|     const right = bbox.getEast(); | ||||
|     const left = bbox.getWest(); | ||||
|     const josmLink = `http://127.0.0.1:8111/load_and_zoom?left=${left}&right=${right}&top=${top}&bottom=${bottom}`; | ||||
|     Utils.download(josmLink) | ||||
|       .then((answer) => josmState.setData(answer.replace(/\n/g, "").trim())) | ||||
|       .catch(() => josmState.setData("ERROR")); | ||||
|   } | ||||
| 
 | ||||
| </script> | ||||
| {#if $showButton} | ||||
|   {#if $josmState === undefined} | ||||
|     <!-- empty --> | ||||
|   {:else if state === "OK"} | ||||
|     <Tr cls="thanks" t={t.josmOpened} /> | ||||
|   {:else} | ||||
|     <Tr cls="alert" t={t.josmNotOpened} /> | ||||
|   {/if} | ||||
| 
 | ||||
|   <button class="flex items-center" on:click={openJosm}> | ||||
|     <Josm_logo class="h-12 w-12 p-2 pr-4" /> | ||||
|     <Tr t={t.editJosm} /> | ||||
|   </button> | ||||
| {/if} | ||||
|  | @ -1,6 +1,7 @@ | |||
| <script lang="ts"> | ||||
|   import ToSvelte from "./ToSvelte.svelte" | ||||
|   import Svg from "../../Svg" | ||||
|   import Share from "../../assets/svg/Share.svelte"; | ||||
| 
 | ||||
|   export let generateShareData: () => { | ||||
|     text: string | ||||
|  | @ -25,6 +26,6 @@ | |||
| 
 | ||||
| <button on:click={share} class="secondary m-0 h-8 w-8 p-0"> | ||||
|   <slot name="content"> | ||||
|     <ToSvelte construct={Svg.share_svg().SetClass("w-7 h-7 p-1")} /> | ||||
|     <Share class="w-7 h-7 p-1"/> | ||||
|   </slot> | ||||
| </button> | ||||
|  |  | |||
|  | @ -23,10 +23,10 @@ export default class SvelteUIElement< | |||
|     private readonly _events: Events | ||||
|     private readonly _slots: Slots | ||||
| 
 | ||||
|     constructor(svelteElement, props: Props, events?: Events, slots?: Slots) { | ||||
|     constructor(svelteElement, props?: Props, events?: Events, slots?: Slots) { | ||||
|         super() | ||||
|         this._svelteComponent = svelteElement | ||||
|         this._props = props | ||||
|         this._props = props ?? <Props>{} | ||||
|         this._events = events | ||||
|         this._slots = slots | ||||
|     } | ||||
|  |  | |||
|  | @ -1,6 +1,7 @@ | |||
| <script lang="ts"> | ||||
|   import Locale from "../i18n/Locale" | ||||
|   import LinkToWeblate from "./LinkToWeblate" | ||||
|   import Translate from "../../assets/svg/Translate.svelte"; | ||||
| 
 | ||||
|   /** | ||||
|    * Shows a small icon which will open up weblate; a contributor can translate the item for 'context' there | ||||
|  | @ -19,7 +20,7 @@ | |||
|       target="_blank" | ||||
|       class="weblate-link mx-1" | ||||
|     > | ||||
|       <img src="./assets/svg/translate.svg" class="font-gray" /> | ||||
|       <Translate class="font-gray" /> | ||||
|     </a> | ||||
|   {:else if $linkToWeblate} | ||||
|     <a | ||||
|  | @ -27,7 +28,7 @@ | |||
|       class="weblate-link hidden-on-mobile mx-1" | ||||
|       target="_blank" | ||||
|     > | ||||
|       <img src="./assets/svg/translate.svg" class="font-gray inline-block" /> | ||||
|       <Translate class="font-gray inline-block" /> | ||||
|     </a> | ||||
|   {/if} | ||||
| {/if} | ||||
|  |  | |||
|  | @ -4,6 +4,7 @@ | |||
|   import { Store } from "../../Logic/UIEventSource" | ||||
|   import Tr from "../Base/Tr.svelte" | ||||
|   import ToSvelte from "../Base/ToSvelte.svelte" | ||||
|   import Mapillary_black from "../../assets/svg/Mapillary_black.svelte"; | ||||
| 
 | ||||
|   /* | ||||
|     A subtleButton which opens mapillary in a new tab at the current location | ||||
|  | @ -21,7 +22,7 @@ | |||
| </script> | ||||
| 
 | ||||
| <a class="button flex items-center" href={mapillaryLink} target="_blank"> | ||||
|   <ToSvelte construct={() => Svg.mapillary_black_svg().SetClass("w-12 h-12 m-2 mr-4 shrink-0")} /> | ||||
|   <Mapillary_black class="w-12 h-12 m-2 mr-4 shrink-0"/> | ||||
|   <div class="flex flex-col"> | ||||
|     <Tr t={Translations.t.general.attribution.openMapillary} /> | ||||
|     <Tr cls="subtle" t={Translations.t.general.attribution.mapillaryHelp} /> | ||||
|  |  | |||
|  | @ -16,6 +16,7 @@ | |||
|   import LayerConfig from "../../Models/ThemeConfig/LayerConfig" | ||||
|   import { Utils } from "../../Utils" | ||||
|   import { createEventDispatcher } from "svelte" | ||||
|   import Move_arrows from "../../assets/svg/Move_arrows.svelte"; | ||||
| 
 | ||||
|   /** | ||||
|    * An advanced location input, which has support to: | ||||
|  | @ -125,6 +126,6 @@ | |||
|   maxDistanceInMeters="50" | ||||
| > | ||||
|   <slot name="image" slot="image"> | ||||
|     <img class="h-full max-h-24" src="./assets/svg/move-arrows.svg" /> | ||||
|     <Move_arrows  class="h-full max-h-24" /> | ||||
|   </slot> | ||||
| </LocationInput> | ||||
|  |  | |||
|  | @ -1,60 +0,0 @@ | |||
| import Combine from "../Base/Combine" | ||||
| import { OsmConnection } from "../../Logic/Osm/OsmConnection" | ||||
| import { Store, UIEventSource } from "../../Logic/UIEventSource" | ||||
| import { BBox } from "../../Logic/BBox" | ||||
| import Translations from "../i18n/Translations" | ||||
| import { VariableUiElement } from "../Base/VariableUIElement" | ||||
| import Toggle from "../Input/Toggle" | ||||
| import { SubtleButton } from "../Base/SubtleButton" | ||||
| import Svg from "../../Svg" | ||||
| import { Utils } from "../../Utils" | ||||
| import Constants from "../../Models/Constants" | ||||
| 
 | ||||
| export class OpenJosm extends Combine { | ||||
|     public static readonly needsUrls = ["http://127.0.0.1:8111/load_and_zoom"] | ||||
|     constructor(osmConnection: OsmConnection, bounds: Store<BBox>, iconStyle?: string) { | ||||
|         const t = Translations.t.general.attribution | ||||
| 
 | ||||
|         const josmState = new UIEventSource<string>(undefined) | ||||
|         // Reset after 15s
 | ||||
|         josmState.stabilized(15000).addCallbackD(() => josmState.setData(undefined)) | ||||
| 
 | ||||
|         const stateIndication = new VariableUiElement( | ||||
|             josmState.map((state) => { | ||||
|                 if (state === undefined) { | ||||
|                     return undefined | ||||
|                 } | ||||
|                 state = state.toUpperCase() | ||||
|                 if (state === "OK") { | ||||
|                     return t.josmOpened.SetClass("thanks") | ||||
|                 } | ||||
|                 return t.josmNotOpened.SetClass("alert") | ||||
|             }) | ||||
|         ) | ||||
| 
 | ||||
|         const toggle = new Toggle( | ||||
|             new SubtleButton(Svg.josm_logo_svg().SetStyle(iconStyle), t.editJosm) | ||||
|                 .onClick(() => { | ||||
|                     const bbox = bounds.data | ||||
|                     if (bbox === undefined) { | ||||
|                         return | ||||
|                     } | ||||
|                     const top = bbox.getNorth() | ||||
|                     const bottom = bbox.getSouth() | ||||
|                     const right = bbox.getEast() | ||||
|                     const left = bbox.getWest() | ||||
|                     const josmLink = `http://127.0.0.1:8111/load_and_zoom?left=${left}&right=${right}&top=${top}&bottom=${bottom}` | ||||
|                     Utils.download(josmLink) | ||||
|                         .then((answer) => josmState.setData(answer.replace(/\n/g, "").trim())) | ||||
|                         .catch(() => josmState.setData("ERROR")) | ||||
|                 }) | ||||
|                 .SetClass("w-full"), | ||||
|             undefined, | ||||
|             osmConnection.userDetails.map( | ||||
|                 (ud) => ud.loggedIn && ud.csCount >= Constants.userJourney.historyLinkVisible | ||||
|             ) | ||||
|         ) | ||||
| 
 | ||||
|         super([stateIndication, toggle]) | ||||
|     } | ||||
| } | ||||
|  | @ -15,6 +15,7 @@ | |||
|   import If from "../Base/If.svelte" | ||||
|   import { ExclamationTriangleIcon } from "@babeard/svelte-heroicons/mini" | ||||
|   import type { Readable } from "svelte/store" | ||||
|   import Add from "../../assets/svg/Add.svelte"; | ||||
| 
 | ||||
|   /** | ||||
|    * The theme introduction panel | ||||
|  | @ -156,7 +157,7 @@ | |||
|   <div class="links-as-button links-w-full m-2 flex flex-col gap-y-1"> | ||||
|     <!-- bottom buttons, a bit hidden away: switch layout --> | ||||
|     <a class="flex" href={Utils.HomepageLink()}> | ||||
|       <img class="h-6 w-6" src="./assets/svg/add.svg" /> | ||||
|       <Add  class="h-6 w-6"/> | ||||
|       <Tr t={Translations.t.general.backToIndex} /> | ||||
|     </a> | ||||
|   </div> | ||||
|  |  | |||
|  | @ -12,6 +12,8 @@ import { OsmConnection } from "../../Logic/Osm/OsmConnection" | |||
| import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig" | ||||
| import { Translation } from "../i18n/Translation" | ||||
| import { LoginToggle } from "../Popup/LoginButton" | ||||
| import SvelteUIElement from "../Base/SvelteUIElement" | ||||
| import Upload from "../../assets/svg/Upload.svelte" | ||||
| 
 | ||||
| export default class UploadTraceToOsmUI extends LoginToggle { | ||||
|     constructor( | ||||
|  | @ -83,7 +85,7 @@ export default class UploadTraceToOsmUI extends LoginToggle { | |||
|                         clicked.setData(false) | ||||
|                     }) | ||||
|                     .SetClass(""), | ||||
|                 new SubtleButton(Svg.upload_svg(), t.confirm).OnClickWithLoading( | ||||
|                 new SubtleButton(new SvelteUIElement(Upload, {}), t.confirm).OnClickWithLoading( | ||||
|                     t.uploading, | ||||
|                     async () => { | ||||
|                         const titleStr = UploadTraceToOsmUI.createDefault( | ||||
|  | @ -119,7 +121,7 @@ export default class UploadTraceToOsmUI extends LoginToggle { | |||
|                     ]).SetClass("flex p-2 rounded-xl border-2 subtle-border items-center"), | ||||
|                     new Toggle( | ||||
|                         confirmPanel, | ||||
|                         new SubtleButton(Svg.upload_svg(), t.title).onClick(() => | ||||
|                         new SubtleButton(new SvelteUIElement(Upload), t.title).onClick(() => | ||||
|                             clicked.setData(true) | ||||
|                         ), | ||||
|                         clicked | ||||
|  |  | |||
|  | @ -3,16 +3,15 @@ | |||
|    * Shows an 'upload'-button which will start the upload for this feature | ||||
|    */ | ||||
| 
 | ||||
|   import type { SpecialVisualizationState } from "../SpecialVisualization" | ||||
|   import { ImmutableStore, Store } from "../../Logic/UIEventSource" | ||||
|   import type { OsmTags } from "../../Models/OsmFeature" | ||||
|   import LoginToggle from "../Base/LoginToggle.svelte" | ||||
|   import Translations from "../i18n/Translations" | ||||
|   import Tr from "../Base/Tr.svelte" | ||||
|   import UploadingImageCounter from "./UploadingImageCounter.svelte" | ||||
|   import FileSelector from "../Base/FileSelector.svelte" | ||||
|   import ToSvelte from "../Base/ToSvelte.svelte" | ||||
|   import Svg from "../../Svg" | ||||
|   import type { SpecialVisualizationState } from "../SpecialVisualization"; | ||||
|   import { ImmutableStore, Store } from "../../Logic/UIEventSource"; | ||||
|   import type { OsmTags } from "../../Models/OsmFeature"; | ||||
|   import LoginToggle from "../Base/LoginToggle.svelte"; | ||||
|   import Translations from "../i18n/Translations"; | ||||
|   import Tr from "../Base/Tr.svelte"; | ||||
|   import UploadingImageCounter from "./UploadingImageCounter.svelte"; | ||||
|   import FileSelector from "../Base/FileSelector.svelte"; | ||||
|   import Camera_plus from "../../assets/svg/Camera_plus.svelte"; | ||||
| 
 | ||||
|   export let state: SpecialVisualizationState | ||||
| 
 | ||||
|  | @ -58,7 +57,7 @@ | |||
|         {#if image !== undefined} | ||||
|           <img src={image} /> | ||||
|         {:else} | ||||
|           <ToSvelte construct={Svg.camera_plus_svg().SetClass("block w-12 h-12 p-1 text-4xl ")} /> | ||||
|           <Camera_plus class="block w-12 h-12 p-1 text-4xl"/> | ||||
|         {/if} | ||||
|         {#if labelText} | ||||
|           {labelText} | ||||
|  |  | |||
|  | @ -12,6 +12,7 @@ | |||
|   import * as turf from "@turf/turf" | ||||
|   import LayerConfig from "../../../Models/ThemeConfig/LayerConfig" | ||||
|   import { createEventDispatcher, onDestroy } from "svelte" | ||||
|   import Move_arrows from "../../../assets/svg/Move_arrows.svelte"; | ||||
| 
 | ||||
|   /** | ||||
|    * A visualisation to pick a location on a map background | ||||
|  | @ -90,7 +91,7 @@ | |||
|     class="pointer-events-none absolute top-0 left-0 flex h-full w-full items-center p-8 opacity-50" | ||||
|   > | ||||
|     <slot name="image"> | ||||
|       <img class="h-full max-h-24" src="./assets/svg/move-arrows.svg" /> | ||||
|       <Move_arrows class="h-full max-h-24"/> | ||||
|     </slot> | ||||
|   </div> | ||||
| 
 | ||||
|  |  | |||
|  | @ -1,21 +1,21 @@ | |||
| <script lang="ts"> | ||||
|   import PointRenderingConfig, { IconConfig } from "../../Models/ThemeConfig/PointRenderingConfig" | ||||
|   import { Store } from "../../Logic/UIEventSource" | ||||
|   import DynamicIcon from "./DynamicIcon.svelte" | ||||
|   import { IconConfig } from "../../Models/ThemeConfig/PointRenderingConfig"; | ||||
|   import { ImmutableStore, Store } from "../../Logic/UIEventSource"; | ||||
|   import DynamicIcon from "./DynamicIcon.svelte"; | ||||
|   import TagRenderingConfig from "../../Models/ThemeConfig/TagRenderingConfig"; | ||||
| 
 | ||||
|   /** | ||||
|    * Renders a 'marker', which consists of multiple 'icons' | ||||
|    */ | ||||
|   export let config: PointRenderingConfig | ||||
|   let icons: IconConfig[] = config.marker | ||||
|   export let tags: Store<Record<string, string>> | ||||
|   let rotation = tags.map(tags => config.rotation.GetRenderValue(tags).Subs(tags).txt) | ||||
| 
 | ||||
|   export let marker: IconConfig[] = config?.marker; | ||||
|   export let rotation: TagRenderingConfig | ||||
|   export let tags: Store<Record<string, string>>; | ||||
|   let _rotation = rotation ? tags.map(tags => rotation.GetRenderValue(tags).Subs(tags).txt) : new ImmutableStore(0); | ||||
| </script> | ||||
| 
 | ||||
| {#if config !== undefined} | ||||
|   <div class="relative h-full w-full" style={`transform: rotate(${$rotation})`}> | ||||
|     {#each icons as icon} | ||||
| {#if marker && marker} | ||||
|   <div class="relative h-full w-full" style={`transform: rotate(${$_rotation})`}> | ||||
|     {#each marker as icon} | ||||
|       <DynamicIcon {icon} {tags} /> | ||||
|     {/each} | ||||
|   </div> | ||||
|  |  | |||
|  | @ -12,6 +12,7 @@ | |||
|   import { createEventDispatcher } from "svelte" | ||||
|   import ToSvelte from "../Base/ToSvelte.svelte" | ||||
|   import Svg from "../../Svg" | ||||
|   import Plantnet_logo from "../../assets/svg/Plantnet_logo.svelte"; | ||||
| 
 | ||||
|   /** | ||||
|    * The main entry point for the plantnet wizard | ||||
|  | @ -142,9 +143,7 @@ | |||
|     </BackButton> | ||||
|   {/if} | ||||
|   <div class="low-interaction flex self-end rounded-xl p-2"> | ||||
|     <ToSvelte | ||||
|       construct={Svg.plantnet_logo_svg().SetClass("w-8 h-8 p-1 mr-1 bg-white rounded-full")} | ||||
|     /> | ||||
|     <Plantnet_logo class="w-8 h-8 p-1 mr-1 bg-white rounded-full"/> | ||||
|     <Tr t={t.poweredByPlantnet} /> | ||||
|   </div> | ||||
| </div> | ||||
|  |  | |||
|  | @ -3,109 +3,110 @@ | |||
|    * This component ties together all the steps that are needed to create a new point. | ||||
|    * There are many subcomponents which help with that | ||||
|    */ | ||||
|   import type { SpecialVisualizationState } from "../../SpecialVisualization" | ||||
|   import PresetList from "./PresetList.svelte" | ||||
|   import type PresetConfig from "../../../Models/ThemeConfig/PresetConfig" | ||||
|   import LayerConfig from "../../../Models/ThemeConfig/LayerConfig" | ||||
|   import Tr from "../../Base/Tr.svelte" | ||||
|   import SubtleButton from "../../Base/SubtleButton.svelte" | ||||
|   import FromHtml from "../../Base/FromHtml.svelte" | ||||
|   import Translations from "../../i18n/Translations.js" | ||||
|   import TagHint from "../TagHint.svelte" | ||||
|   import { And } from "../../../Logic/Tags/And.js" | ||||
|   import LoginToggle from "../../Base/LoginToggle.svelte" | ||||
|   import Constants from "../../../Models/Constants.js" | ||||
|   import FilteredLayer from "../../../Models/FilteredLayer" | ||||
|   import { Store, UIEventSource } from "../../../Logic/UIEventSource" | ||||
|   import { EyeIcon, EyeOffIcon } from "@rgossiaux/svelte-heroicons/solid" | ||||
|   import LoginButton from "../../Base/LoginButton.svelte" | ||||
|   import NewPointLocationInput from "../../BigComponents/NewPointLocationInput.svelte" | ||||
|   import CreateNewNodeAction from "../../../Logic/Osm/Actions/CreateNewNodeAction" | ||||
|   import { OsmWay } from "../../../Logic/Osm/OsmObject" | ||||
|   import { Tag } from "../../../Logic/Tags/Tag" | ||||
|   import type { WayId } from "../../../Models/OsmFeature" | ||||
|   import Loading from "../../Base/Loading.svelte" | ||||
|   import type { GlobalFilter } from "../../../Models/GlobalFilter" | ||||
|   import { onDestroy } from "svelte" | ||||
|   import NextButton from "../../Base/NextButton.svelte" | ||||
|   import BackButton from "../../Base/BackButton.svelte" | ||||
|   import ToSvelte from "../../Base/ToSvelte.svelte" | ||||
|   import Svg from "../../../Svg" | ||||
|   import OpenBackgroundSelectorButton from "../../BigComponents/OpenBackgroundSelectorButton.svelte" | ||||
|   import { twJoin } from "tailwind-merge" | ||||
|   import type { SpecialVisualizationState } from "../../SpecialVisualization"; | ||||
|   import PresetList from "./PresetList.svelte"; | ||||
|   import type PresetConfig from "../../../Models/ThemeConfig/PresetConfig"; | ||||
|   import LayerConfig from "../../../Models/ThemeConfig/LayerConfig"; | ||||
|   import Tr from "../../Base/Tr.svelte"; | ||||
|   import SubtleButton from "../../Base/SubtleButton.svelte"; | ||||
|   import Translations from "../../i18n/Translations.js"; | ||||
|   import TagHint from "../TagHint.svelte"; | ||||
|   import { And } from "../../../Logic/Tags/And.js"; | ||||
|   import LoginToggle from "../../Base/LoginToggle.svelte"; | ||||
|   import Constants from "../../../Models/Constants.js"; | ||||
|   import FilteredLayer from "../../../Models/FilteredLayer"; | ||||
|   import { Store, UIEventSource } from "../../../Logic/UIEventSource"; | ||||
|   import { EyeIcon, EyeOffIcon } from "@rgossiaux/svelte-heroicons/solid"; | ||||
|   import LoginButton from "../../Base/LoginButton.svelte"; | ||||
|   import NewPointLocationInput from "../../BigComponents/NewPointLocationInput.svelte"; | ||||
|   import CreateNewNodeAction from "../../../Logic/Osm/Actions/CreateNewNodeAction"; | ||||
|   import { OsmWay } from "../../../Logic/Osm/OsmObject"; | ||||
|   import { Tag } from "../../../Logic/Tags/Tag"; | ||||
|   import type { WayId } from "../../../Models/OsmFeature"; | ||||
|   import Loading from "../../Base/Loading.svelte"; | ||||
|   import type { GlobalFilter } from "../../../Models/GlobalFilter"; | ||||
|   import { onDestroy } from "svelte"; | ||||
|   import NextButton from "../../Base/NextButton.svelte"; | ||||
|   import BackButton from "../../Base/BackButton.svelte"; | ||||
|   import ToSvelte from "../../Base/ToSvelte.svelte"; | ||||
|   import Svg from "../../../Svg"; | ||||
|   import OpenBackgroundSelectorButton from "../../BigComponents/OpenBackgroundSelectorButton.svelte"; | ||||
|   import { twJoin } from "tailwind-merge"; | ||||
|   import Confirm from "../../../assets/svg/Confirm.svelte"; | ||||
|   import Close from "../../../assets/svg/Close.svelte"; | ||||
| 
 | ||||
|   export let coordinate: { lon: number; lat: number } | ||||
|   export let state: SpecialVisualizationState | ||||
|   export let coordinate: { lon: number; lat: number }; | ||||
|   export let state: SpecialVisualizationState; | ||||
| 
 | ||||
|   let selectedPreset: { | ||||
|     preset: PresetConfig | ||||
|     layer: LayerConfig | ||||
|     icon: string | ||||
|     tags: Record<string, string> | ||||
|   } = undefined | ||||
|   let checkedOfGlobalFilters: number = 0 | ||||
|   let confirmedCategory = false | ||||
|   } = undefined; | ||||
|   let checkedOfGlobalFilters: number = 0; | ||||
|   let confirmedCategory = false; | ||||
|   $: if (selectedPreset === undefined) { | ||||
|     confirmedCategory = false | ||||
|     creating = false | ||||
|     checkedOfGlobalFilters = 0 | ||||
|     confirmedCategory = false; | ||||
|     creating = false; | ||||
|     checkedOfGlobalFilters = 0; | ||||
|   } | ||||
| 
 | ||||
|   let flayer: FilteredLayer = undefined | ||||
|   let layerIsDisplayed: UIEventSource<boolean> | undefined = undefined | ||||
|   let layerHasFilters: Store<boolean> | undefined = undefined | ||||
|   let globalFilter: UIEventSource<GlobalFilter[]> = state.layerState.globalFilters | ||||
|   let _globalFilter: GlobalFilter[] = [] | ||||
|   let flayer: FilteredLayer = undefined; | ||||
|   let layerIsDisplayed: UIEventSource<boolean> | undefined = undefined; | ||||
|   let layerHasFilters: Store<boolean> | undefined = undefined; | ||||
|   let globalFilter: UIEventSource<GlobalFilter[]> = state.layerState.globalFilters; | ||||
|   let _globalFilter: GlobalFilter[] = []; | ||||
|   onDestroy( | ||||
|     globalFilter.addCallbackAndRun((globalFilter) => { | ||||
|       console.log("Global filters are", globalFilter) | ||||
|       _globalFilter = globalFilter ?? [] | ||||
|       console.log("Global filters are", globalFilter); | ||||
|       _globalFilter = globalFilter ?? []; | ||||
|     }) | ||||
|   ) | ||||
|   ); | ||||
|   $: { | ||||
|     flayer = state.layerState.filteredLayers.get(selectedPreset?.layer?.id) | ||||
|     layerIsDisplayed = flayer?.isDisplayed | ||||
|     layerHasFilters = flayer?.hasFilter | ||||
|     flayer = state.layerState.filteredLayers.get(selectedPreset?.layer?.id); | ||||
|     layerIsDisplayed = flayer?.isDisplayed; | ||||
|     layerHasFilters = flayer?.hasFilter; | ||||
|   } | ||||
|   const t = Translations.t.general.add | ||||
|   const t = Translations.t.general.add; | ||||
| 
 | ||||
|   const zoom = state.mapProperties.zoom | ||||
|   const zoom = state.mapProperties.zoom; | ||||
| 
 | ||||
|   const isLoading = state.dataIsLoading | ||||
|   let preciseCoordinate: UIEventSource<{ lon: number; lat: number }> = new UIEventSource(undefined) | ||||
|   let snappedToObject: UIEventSource<string> = new UIEventSource<string>(undefined) | ||||
|   const isLoading = state.dataIsLoading; | ||||
|   let preciseCoordinate: UIEventSource<{ lon: number; lat: number }> = new UIEventSource(undefined); | ||||
|   let snappedToObject: UIEventSource<string> = new UIEventSource<string>(undefined); | ||||
| 
 | ||||
|   // Small helper variable: if the map is tapped, we should let the 'Next'-button grab some attention as users have to click _that_ to continue, not the map | ||||
|   let preciseInputIsTapped = false | ||||
|   let preciseInputIsTapped = false; | ||||
| 
 | ||||
|   let creating = false | ||||
|   let creating = false; | ||||
| 
 | ||||
|   /** | ||||
|    * Call when the user should restart the flow by clicking on the map, e.g. because they disabled filters. | ||||
|    * Will delete the lastclick-location | ||||
|    */ | ||||
|   function abort() { | ||||
|     state.selectedElement.setData(undefined) | ||||
|     state.selectedElement.setData(undefined); | ||||
|     // When aborted, we force the contributors to place the pin _again_ | ||||
|     // This is because there might be a nearby object that was disabled; this forces them to re-evaluate the map | ||||
|     state.lastClickObject.features.setData([]) | ||||
|     preciseInputIsTapped = false | ||||
|     state.lastClickObject.features.setData([]); | ||||
|     preciseInputIsTapped = false; | ||||
|   } | ||||
| 
 | ||||
|   async function confirm() { | ||||
|     creating = true | ||||
|     const location: { lon: number; lat: number } = preciseCoordinate.data | ||||
|     const snapTo: WayId | undefined = <WayId>snappedToObject.data | ||||
|     creating = true; | ||||
|     const location: { lon: number; lat: number } = preciseCoordinate.data; | ||||
|     const snapTo: WayId | undefined = <WayId>snappedToObject.data; | ||||
|     const tags: Tag[] = selectedPreset.preset.tags.concat( | ||||
|       ..._globalFilter.map((f) => f?.onNewPoint?.tags ?? []) | ||||
|     ) | ||||
|     console.log("Creating new point at", location, "snapped to", snapTo, "with tags", tags) | ||||
|     ); | ||||
|     console.log("Creating new point at", location, "snapped to", snapTo, "with tags", tags); | ||||
| 
 | ||||
|     let snapToWay: undefined | OsmWay = undefined | ||||
|     let snapToWay: undefined | OsmWay = undefined; | ||||
|     if (snapTo !== undefined && snapTo !== null) { | ||||
|       const downloaded = await state.osmObjectDownloader.DownloadObjectAsync(snapTo, 0) | ||||
|       const downloaded = await state.osmObjectDownloader.DownloadObjectAsync(snapTo, 0); | ||||
|       if (downloaded !== "deleted") { | ||||
|         snapToWay = downloaded | ||||
|         snapToWay = downloaded; | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|  | @ -113,44 +114,44 @@ | |||
|       theme: state.layout?.id ?? "unkown", | ||||
|       changeType: "create", | ||||
|       snapOnto: snapToWay, | ||||
|       reusePointWithinMeters: 1, | ||||
|     }) | ||||
|     await state.changes.applyAction(newElementAction) | ||||
|     state.newFeatures.features.ping() | ||||
|       reusePointWithinMeters: 1 | ||||
|     }); | ||||
|     await state.changes.applyAction(newElementAction); | ||||
|     state.newFeatures.features.ping(); | ||||
|     // The 'changes' should have created a new point, which added this into the 'featureProperties' | ||||
|     const newId = newElementAction.newElementId | ||||
|     console.log("Applied pending changes, fetching store for", newId) | ||||
|     const tagsStore = state.featureProperties.getStore(newId) | ||||
|     const newId = newElementAction.newElementId; | ||||
|     console.log("Applied pending changes, fetching store for", newId); | ||||
|     const tagsStore = state.featureProperties.getStore(newId); | ||||
|     if (!tagsStore) { | ||||
|       console.error("Bug: no tagsStore found for", newId) | ||||
|       console.error("Bug: no tagsStore found for", newId); | ||||
|     } | ||||
|     { | ||||
|       // Set some metainfo | ||||
|       const properties = tagsStore.data | ||||
|       const properties = tagsStore.data; | ||||
|       if (snapTo) { | ||||
|         // metatags (starting with underscore) are not uploaded, so we can safely mark this | ||||
|         delete properties["_referencing_ways"] | ||||
|         properties["_referencing_ways"] = `["${snapTo}"]` | ||||
|         delete properties["_referencing_ways"]; | ||||
|         properties["_referencing_ways"] = `["${snapTo}"]`; | ||||
|       } | ||||
|       properties["_backend"] = state.osmConnection.Backend() | ||||
|       properties["_last_edit:timestamp"] = new Date().toISOString() | ||||
|       const userdetails = state.osmConnection.userDetails.data | ||||
|       properties["_last_edit:contributor"] = userdetails.name | ||||
|       properties["_last_edit:uid"] = "" + userdetails.uid | ||||
|       tagsStore.ping() | ||||
|       properties["_backend"] = state.osmConnection.Backend(); | ||||
|       properties["_last_edit:timestamp"] = new Date().toISOString(); | ||||
|       const userdetails = state.osmConnection.userDetails.data; | ||||
|       properties["_last_edit:contributor"] = userdetails.name; | ||||
|       properties["_last_edit:uid"] = "" + userdetails.uid; | ||||
|       tagsStore.ping(); | ||||
|     } | ||||
|     const feature = state.indexedFeatures.featuresById.data.get(newId) | ||||
|     console.log("Selecting feature", feature, "and opening their popup") | ||||
|     abort() | ||||
|     state.selectedLayer.setData(selectedPreset.layer) | ||||
|     state.selectedElement.setData(feature) | ||||
|     tagsStore.ping() | ||||
|     const feature = state.indexedFeatures.featuresById.data.get(newId); | ||||
|     console.log("Selecting feature", feature, "and opening their popup"); | ||||
|     abort(); | ||||
|     state.selectedLayer.setData(selectedPreset.layer); | ||||
|     state.selectedElement.setData(feature); | ||||
|     tagsStore.ping(); | ||||
|   } | ||||
| 
 | ||||
|   function confirmSync() { | ||||
|     confirm() | ||||
|       .then((_) => console.debug("New point successfully handled")) | ||||
|       .catch((e) => console.error("Handling the new point went wrong due to", e)) | ||||
|       .catch((e) => console.error("Handling the new point went wrong due to", e)); | ||||
|   } | ||||
| </script> | ||||
| 
 | ||||
|  | @ -285,7 +286,7 @@ | |||
|         <NextButton on:click={() => (confirmedCategory = true)} clss="primary w-full"> | ||||
|           <div slot="image" class="relative"> | ||||
|             <ToSvelte construct={selectedPreset.icon} /> | ||||
|             <img class="absolute bottom-0 right-0 h-4 w-4" src="./assets/svg/confirm.svg" /> | ||||
|             <Confirm class="absolute bottom-0 right-0 h-4 w-4" /> | ||||
|           </div> | ||||
|           <div class="w-full"> | ||||
|             <Tr t={selectedPreset.text} /> | ||||
|  | @ -299,11 +300,7 @@ | |||
|           checkedOfGlobalFilters = checkedOfGlobalFilters + 1 | ||||
|         }} | ||||
|       > | ||||
|         <img | ||||
|           slot="image" | ||||
|           src={_globalFilter[checkedOfGlobalFilters].onNewPoint?.icon ?? "./assets/svg/confirm.svg"} | ||||
|           class="h-12 w-12" | ||||
|         /> | ||||
|         <Confirm slot="image" class="h-12 w-12" /> | ||||
|         <Tr | ||||
|           slot="message" | ||||
|           t={_globalFilter[checkedOfGlobalFilters].onNewPoint?.confirmAddNew.Subs({ | ||||
|  | @ -317,7 +314,7 @@ | |||
|           abort() | ||||
|         }} | ||||
|       > | ||||
|         <img slot="image" src="./assets/svg/close.svg" class="h-8 w-8" /> | ||||
|         <Close slot="image" class="h-8 w-8" /> | ||||
|         <Tr slot="message" t={Translations.t.general.cancel} /> | ||||
|       </SubtleButton> | ||||
|     {:else if !creating} | ||||
|  |  | |||
|  | @ -15,6 +15,8 @@ | |||
|   import NewPointLocationInput from "../BigComponents/NewPointLocationInput.svelte" | ||||
|   import ToSvelte from "../Base/ToSvelte.svelte" | ||||
|   import Svg from "../../Svg" | ||||
|   import Layers from "../../assets/svg/Layers.svelte"; | ||||
|   import AddSmall from "../../assets/svg/AddSmall.svelte"; | ||||
| 
 | ||||
|   export let coordinate: UIEventSource<{ lon: number; lat: number }> | ||||
|   export let state: SpecialVisualizationState | ||||
|  | @ -97,7 +99,7 @@ | |||
|           <Tr t={Translations.t.notes.noteLayerHasFilters} /> | ||||
|         </div> | ||||
|         <SubtleButton on:click={() => notelayer.disableAllFilters()}> | ||||
|           <img slot="image" src="./assets/svg/filter.svg" class="mr-4 h-8 w-8" /> | ||||
|           <Layers class="mr-4 h-8 w-8"/> | ||||
|           <Tr slot="message" t={Translations.t.notes.disableAllNoteFilters} /> | ||||
|         </SubtleButton> | ||||
|       </div> | ||||
|  | @ -126,7 +128,7 @@ | |||
| 
 | ||||
|           {#if $comment?.length >= 3} | ||||
|             <SubtleButton on:click={uploadNote}> | ||||
|               <img slot="image" src="./assets/svg/addSmall.svg" class="mr-4 h-8 w-8" /> | ||||
|               <AddSmall slot="image" class="mr-4 h-8 w-8" /> | ||||
|               <Tr slot="message" t={Translations.t.notes.createNote} /> | ||||
|             </SubtleButton> | ||||
|           {:else} | ||||
|  | @ -143,7 +145,7 @@ | |||
|         <Tr t={Translations.t.notes.noteLayerNotEnabled} /> | ||||
|       </div> | ||||
|       <SubtleButton on:click={enableNoteLayer}> | ||||
|         <img slot="image" src="./assets/svg/layers.svg" class="mr-4 h-8 w-8" /> | ||||
|         <Layers slot="image" class="mr-4 h-8 w-8" /> | ||||
|         <Tr slot="message" t={Translations.t.notes.noteLayerDoEnable} /> | ||||
|       </SubtleButton> | ||||
|     </div> | ||||
|  |  | |||
|  | @ -11,6 +11,7 @@ | |||
|   import ToSvelte from "../Base/ToSvelte.svelte" | ||||
|   import { XCircleIcon } from "@babeard/svelte-heroicons/solid" | ||||
|   import exp from "constants" | ||||
|   import Camera_plus from "../../assets/svg/Camera_plus.svelte"; | ||||
| 
 | ||||
|   export let tags: Store<OsmTags> | ||||
|   export let state: SpecialVisualizationState | ||||
|  | @ -42,7 +43,7 @@ | |||
|       expanded = true | ||||
|     }} | ||||
|   > | ||||
|     <ToSvelte construct={Svg.camera_plus_svg().SetClass("block w-8 h-8 p-1 mr-2 ")} /> | ||||
|     <Camera_plus class="block w-8 h-8 p-1 mr-2"/> | ||||
|     <Tr t={t.seeNearby} /> | ||||
|   </button> | ||||
| {/if} | ||||
|  |  | |||
|  | @ -23,6 +23,8 @@ | |||
|   import UserRelatedState from "../../../Logic/State/UserRelatedState" | ||||
|   import { twJoin } from "tailwind-merge" | ||||
|   import { TagUtils } from "../../../Logic/Tags/TagUtils" | ||||
|   import Search from "../../../assets/svg/Search.svelte"; | ||||
|   import Login from "../../../assets/svg/Login.svelte"; | ||||
| 
 | ||||
|   export let config: TagRenderingConfig | ||||
|   export let tags: UIEventSource<Record<string, string>> | ||||
|  | @ -217,7 +219,7 @@ | |||
| 
 | ||||
|     {#if config.mappings?.length >= 8} | ||||
|       <div class="sticky flex w-full"> | ||||
|         <img src="./assets/svg/search.svg" class="h-6 w-6" /> | ||||
|         <Search class="h-6 w-6"/> | ||||
|         <input type="text" bind:value={$searchTerm} class="w-full" /> | ||||
|       </div> | ||||
|     {/if} | ||||
|  | @ -324,7 +326,7 @@ | |||
|     <LoginToggle {state}> | ||||
|       <Loading slot="loading" /> | ||||
|       <SubtleButton slot="not-logged-in" on:click={() => state?.osmConnection?.AttemptLogin()}> | ||||
|         <img slot="image" src="./assets/svg/login.svg" class="h-8 w-8" /> | ||||
|         <Login slot="image" class="h-8 w-8" /> | ||||
|         <Tr t={Translations.t.general.loginToStart} slot="message" /> | ||||
|       </SubtleButton> | ||||
|       {#if $feedback !== undefined} | ||||
|  |  | |||
|  | @ -12,6 +12,7 @@ | |||
|   import LayerConfig from "../../Models/ThemeConfig/LayerConfig" | ||||
|   import ToSvelte from "../Base/ToSvelte.svelte" | ||||
|   import Svg from "../../Svg" | ||||
|   import Mangrove_logo from "../../assets/svg/Mangrove_logo.svelte"; | ||||
| 
 | ||||
|   /** | ||||
|    * An element showing all reviews | ||||
|  | @ -40,7 +41,7 @@ | |||
|     <Tr t={Translations.t.reviews.no_reviews_yet} /> | ||||
|   {/if} | ||||
|   <div class="flex justify-end"> | ||||
|     <ToSvelte construct={Svg.mangrove_logo_svg().SetClass("w-12 h-12 shrink-0 p-1 ")} /> | ||||
|     <Mangrove_logo class="w-12 h-12 shrink-0 p-1"/> | ||||
|     <Tr cls="text-sm subtle" t={Translations.t.reviews.attribution} /> | ||||
|   </div> | ||||
| </div> | ||||
|  |  | |||
|  | @ -58,7 +58,6 @@ import { PointImportButtonViz } from "./Popup/ImportButtons/PointImportButtonViz | |||
| import WayImportButtonViz from "./Popup/ImportButtons/WayImportButtonViz" | ||||
| import ConflateImportButtonViz from "./Popup/ImportButtons/ConflateImportButtonViz" | ||||
| import DeleteWizard from "./Popup/DeleteFlow/DeleteWizard.svelte" | ||||
| import { OpenJosm } from "./BigComponents/OpenJosm" | ||||
| import OpenIdEditor from "./BigComponents/OpenIdEditor.svelte" | ||||
| import FediverseValidator from "./InputElement/Validators/FediverseValidator" | ||||
| import SendEmail from "./Popup/SendEmail.svelte" | ||||
|  | @ -78,6 +77,8 @@ import { TagUtils } from "../Logic/Tags/TagUtils" | |||
| import Giggity from "./BigComponents/Giggity.svelte" | ||||
| import ThemeViewState from "../Models/ThemeViewState" | ||||
| import LanguagePicker from "./InputElement/LanguagePicker.svelte" | ||||
| import LogoutButton from "./Base/LogoutButton.svelte" | ||||
| import OpenJosm from "./Base/OpenJosm.svelte" | ||||
| 
 | ||||
| class NearbyImageVis implements SpecialVisualization { | ||||
|     // Class must be in SpecialVisualisations due to weird cyclical import that breaks the tests
 | ||||
|  | @ -465,11 +466,7 @@ export default class SpecialVisualizations { | |||
|                 needsUrls: [Constants.osmAuthConfig.url], | ||||
|                 docs: "Shows a button where the user can log out", | ||||
|                 constr(state: SpecialVisualizationState): BaseUIElement { | ||||
|                     return new SubtleButton(Svg.logout_svg(), Translations.t.general.logout, { | ||||
|                         imgSize: "w-6 h-6", | ||||
|                     }).onClick(() => { | ||||
|                         state.osmConnection.LogOut() | ||||
|                     }) | ||||
|                     return new SvelteUIElement(LogoutButton, { osmConnection: state.osmConnection }) | ||||
|                 }, | ||||
|             }, | ||||
|             new HistogramViz(), | ||||
|  | @ -903,10 +900,10 @@ export default class SpecialVisualizations { | |||
|                 funcName: "open_in_josm", | ||||
|                 docs: "Opens the current view in the JOSM-editor", | ||||
|                 args: [], | ||||
|                 needsUrls: OpenJosm.needsUrls, | ||||
|                 needsUrls: ["http://127.0.0.1:8111/load_and_zoom"], | ||||
| 
 | ||||
|                 constr: (state) => { | ||||
|                     return new OpenJosm(state.osmConnection, state.mapProperties.bounds) | ||||
|                     return new SvelteUIElement(OpenJosm, { state }) | ||||
|                 }, | ||||
|             }, | ||||
|             { | ||||
|  | @ -1099,12 +1096,6 @@ export default class SpecialVisualizations { | |||
|                     if (maproulette_id_key === "" || maproulette_id_key === undefined) { | ||||
|                         maproulette_id_key = "mr_taskId" | ||||
|                     } | ||||
|                     if (Svg.All[image] !== undefined || Svg.All[image + ".svg"] !== undefined) { | ||||
|                         if (image.endsWith(".svg")) { | ||||
|                             image = image.substring(0, image.length - 4) | ||||
|                         } | ||||
|                         image = Svg[image + "_svg"]() | ||||
|                     } | ||||
|                     const failed = new UIEventSource(false) | ||||
| 
 | ||||
|                     const closeButton = new SubtleButton(image, message).OnClickWithLoading( | ||||
|  |  | |||
|  | @ -29,6 +29,7 @@ | |||
|   import { Utils } from "../Utils"; | ||||
|   import Translations from "./i18n/Translations"; | ||||
|   import Tr from "./Base/Tr.svelte"; | ||||
|   import Add from "../assets/svg/Add.svelte"; | ||||
| 
 | ||||
|   export let studioUrl = | ||||
|     window.location.hostname === "127.0.0.2" | ||||
|  | @ -197,7 +198,7 @@ | |||
|             Show the introduction again | ||||
|           </button> | ||||
|           <a class="flex button" href={Utils.HomepageLink()}> | ||||
|             <img class="h-6 w-6" src="./assets/svg/add.svg" /> | ||||
|             <Add class="h-6 w-6" /> | ||||
|             <Tr t={Translations.t.general.backToIndex} /> | ||||
|           </a> | ||||
|         </div> | ||||
|  |  | |||
|  | @ -43,18 +43,25 @@ | |||
|   import RasterLayerOverview from "./Map/RasterLayerOverview.svelte"; | ||||
|   import IfHidden from "./Base/IfHidden.svelte"; | ||||
|   import { onDestroy } from "svelte"; | ||||
|   import { OpenJosm } from "./BigComponents/OpenJosm"; | ||||
|   import MapillaryLink from "./BigComponents/MapillaryLink.svelte"; | ||||
|   import OpenIdEditor from "./BigComponents/OpenIdEditor.svelte"; | ||||
|   import OpenBackgroundSelectorButton from "./BigComponents/OpenBackgroundSelectorButton.svelte"; | ||||
|   import StateIndicator from "./BigComponents/StateIndicator.svelte"; | ||||
|   import Locale from "./i18n/Locale"; | ||||
|   import ShareScreen from "./BigComponents/ShareScreen.svelte"; | ||||
|   import UploadingImageCounter from "./Image/UploadingImageCounter.svelte"; | ||||
|   import PendingChangesIndicator from "./BigComponents/PendingChangesIndicator.svelte"; | ||||
|   import Cross from "../assets/svg/Cross.svelte"; | ||||
|   import Summary from "./BigComponents/Summary.svelte"; | ||||
|   import LanguagePicker from "./InputElement/LanguagePicker.svelte"; | ||||
|   import Mastodon from "../assets/svg/Mastodon.svelte"; | ||||
|   import Bug from "../assets/svg/Bug.svelte"; | ||||
|   import Liberapay from "../assets/svg/Liberapay.svelte"; | ||||
|   import OpenJosm from "./Base/OpenJosm.svelte"; | ||||
|   import Min from "../assets/svg/Min.svelte"; | ||||
|   import Plus from "../assets/svg/Plus.svelte"; | ||||
|   import Filter from "../assets/svg/Filter.svelte"; | ||||
|   import Add from "../assets/svg/Add.svelte"; | ||||
|   import Statistics from "../assets/svg/Statistics.svelte"; | ||||
| 
 | ||||
|   export let state: ThemeViewState; | ||||
|   let layout = state.layout; | ||||
|  | @ -205,7 +212,7 @@ | |||
|         <!-- bottom left elements --> | ||||
|         <If condition={state.featureSwitches.featureSwitchFilter}> | ||||
|           <MapControlButton on:click={() => state.guistate.openFilterView()}> | ||||
|             <ToSvelte construct={Svg.filter_svg().SetClass("h-6 w-6")} /> | ||||
|             <Filter class="h-6 w-6"/> | ||||
|           </MapControlButton> | ||||
|         </If> | ||||
|         <If condition={state.featureSwitches.featureSwitchBackgroundSelection}> | ||||
|  | @ -244,10 +251,10 @@ | |||
|         </div> | ||||
|       </If> | ||||
|       <MapControlButton on:click={() => mapproperties.zoom.update((z) => z + 1)}> | ||||
|         <ToSvelte construct={Svg.plus_svg().SetClass("w-8 h-8")} /> | ||||
|         <Plus class="w-8 h-8" /> | ||||
|       </MapControlButton> | ||||
|       <MapControlButton on:click={() => mapproperties.zoom.update((z) => z - 1)}> | ||||
|         <ToSvelte construct={Svg.min_svg().SetClass("w-8 h-8")} /> | ||||
|         <Min class="w-8 h-8"/> | ||||
|       </MapControlButton> | ||||
|       <If condition={featureSwitches.featureSwitchGeolocation}> | ||||
|         <MapControlButton> | ||||
|  | @ -340,7 +347,7 @@ | |||
|       </div> | ||||
| 
 | ||||
|       <div class="flex" slot="title1"> | ||||
|         <ToSvelte construct={Svg.filter_svg().SetClass("w-4 h-4")} /> | ||||
|         <Filter class="w-4 h-4"/> | ||||
|         <Tr t={Translations.t.general.menu.filter} /> | ||||
|       </div> | ||||
| 
 | ||||
|  | @ -431,27 +438,27 @@ | |||
|         <Tr t={Translations.t.general.aboutMapComplete.intro} /> | ||||
| 
 | ||||
|         <a class="flex" href={Utils.HomepageLink()}> | ||||
|           <img class="h-6 w-6" src="./assets/svg/add.svg" /> | ||||
|           <Add class="h-6 w-6"/> | ||||
|           <Tr t={Translations.t.general.backToIndex} /> | ||||
|         </a> | ||||
| 
 | ||||
|         <a class="flex" href="https://github.com/pietervdvn/MapComplete/issues" target="_blank"> | ||||
|           <img class="h-6 w-6" src="./assets/svg/bug.svg" /> | ||||
|           <Bug class="h-6 w-6"/> | ||||
|           <Tr t={Translations.t.general.attribution.openIssueTracker} /> | ||||
|         </a> | ||||
| 
 | ||||
|         <a class="flex" href="https://en.osm.town/@MapComplete" target="_blank"> | ||||
|           <img class="h-6 w-6" src="./assets/svg/mastodon.svg" /> | ||||
|           <Mastodon class="w-6 h-6" /> | ||||
|           <Tr t={Translations.t.general.attribution.followOnMastodon} /> | ||||
|         </a> | ||||
| 
 | ||||
|         <a class="flex" href="https://liberapay.com/pietervdvn/" target="_blank"> | ||||
|           <img class="h-6 w-6" src="./assets/svg/liberapay.svg" /> | ||||
|           <Liberapay class="h-6 w-6" /> | ||||
|           <Tr t={Translations.t.general.attribution.donate} /> | ||||
|         </a> | ||||
| 
 | ||||
|         <a class="flex" href={Utils.OsmChaLinkFor(7)} target="_blank"> | ||||
|           <img class="h-6 w-6" src="./assets/svg/statistics.svg" /> | ||||
|           <Statistics class="h-6 w-6" /> | ||||
|           <Tr t={Translations.t.general.attribution.openOsmcha.Subs({ theme: "MapComplete" })} /> | ||||
|         </a> | ||||
|         {Constants.vNumber} | ||||
|  | @ -503,10 +510,7 @@ | |||
|       <div class="m-2 flex flex-col" slot="content4"> | ||||
|         <If condition={featureSwitches.featureSwitchEnableLogin}> | ||||
|           <OpenIdEditor mapProperties={state.mapProperties} /> | ||||
|           <ToSvelte | ||||
|             construct={() => | ||||
|               new OpenJosm(state.osmConnection, state.mapProperties.bounds).SetClass("w-full")} | ||||
|           /> | ||||
|           <OpenJosm {state}/> | ||||
|           <MapillaryLink mapProperties={state.mapProperties} /> | ||||
|         </If> | ||||
| 
 | ||||
|  |  | |||
|  | @ -9,6 +9,7 @@ | |||
|   import WikidataPreviewBox from "./WikidataPreviewBox" | ||||
|   import Tr from "../Base/Tr.svelte" | ||||
|   import Translations from "../i18n/Translations" | ||||
|   import Wikipedia from "../../assets/svg/Wikipedia.svelte"; | ||||
| 
 | ||||
|   /** | ||||
|    * Shows a wikipedia-article + wikidata preview for the given item | ||||
|  | @ -18,7 +19,7 @@ | |||
| 
 | ||||
| {#if $wikipediaDetails.articleUrl} | ||||
|   <a class="flex" href={$wikipediaDetails.articleUrl} rel="noreferrer" target="_blank"> | ||||
|     <img class="h-6 w-6" src="./assets/svg/wikipedia.svg" /> | ||||
|     <Wikipedia class="h-6 w-6"/> | ||||
|     <Tr t={Translations.t.general.wikipedia.fromWikipedia} /> | ||||
|   </a> | ||||
| {/if} | ||||
|  |  | |||
|  | @ -1,4 +0,0 @@ | |||
| <script> | ||||
| export let color = "#000000" | ||||
| </script> | ||||
|  <svg {...$$restProps} on:click on:mouseover on:mouseenter on:mouseleave on:keydown xmlns="http://www.w3.org/2000/svg" width="374px" height="259px" viewBox="0 0 374 259" version="1.1">   <g id="surface1"/> </svg> | ||||
|  | @ -1,4 +0,0 @@ | |||
| <script> | ||||
| export let color = "#000000" | ||||
| </script> | ||||
|  <svg {...$$restProps} on:click on:mouseover on:mouseenter on:mouseleave on:keydown xmlns="http://www.w3.org/2000/svg" width="375px" height="375px" viewBox="0 0 375 375" version="1.1">   <g id="surface1"/> </svg> | ||||
|  | @ -1 +0,0 @@ | |||
|  <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"374px\" height=\"374px\" viewBox=\"0 0 374 374\" version=\"1.1\">   <g id=\"surface1\">     <path style=\"fill: none !important;stroke-width:107.38591;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-miterlimit:4;\" d=\"M 674.316761 62.954545 C 661.244886 65.427807 645.489205 78.502674 631.082102 98.743316 C 611.320739 126.71123 594.430114 160.307487 554.52017 251.283422 C 521.406534 326.684492 508.134375 353.42246 490.856534 378.903743 C 470.721307 408.582888 456.781534 414.505348 407.044318 414.59893 C 378.123295 414.59893 352.353409 412.5 281.532955 404.47861 C 251.930966 401.042781 217.949432 397.513369 192.753693 395.227273 C 174.527841 393.596257 117.153125 393.596257 106.364489 395.227273 C 72.102557 400.574866 56.253409 411.831551 55.011648 431.684492 C 54.344034 443.903743 59.124148 456.97861 70.380114 473.877005 C 89.193466 501.938503 119.449716 532.299465 196.091761 600.160428 C 287.26108 680.828877 315.127273 712.23262 319.22642 738.770053 C 322.764773 761.096257 310.160227 801.377005 275.991761 877.941176 C 241.249148 955.828877 234.946875 969.852941 229.21875 983.890374 C 205.157955 1042.219251 195.624432 1079.157754 198.869034 1101.871658 C 201.819886 1123.061497 212.701989 1133.663102 232.944034 1134.893048 C 262.626136 1136.898396 306.248011 1118.770053 395.120739 1067.700535 C 466.608807 1026.564171 479.600568 1019.21123 495.436364 1010.802139 C 538.484091 987.90107 563.97358 978.542781 583.641477 978.262032 C 595.191193 978.168449 599.009943 979.21123 613.230114 986.283422 C 638.519318 998.970588 668.027841 1022.553476 737.326136 1085.548128 C 833.34233 1172.981283 871.436364 1200.668449 904.55 1207.352941 C 914.297159 1209.358289 921.066761 1208.596257 928.891193 1204.772727 C 938.731818 1200 944.166193 1191.885027 948.372159 1175.748663 C 950.375 1168.02139 950.561932 1165.160428 950.561932 1145.681818 C 950.561932 1133.756684 950.094602 1120.200535 949.413636 1115.13369 C 944.553409 1078.957219 939.2125 1050.802139 925.272727 987.713904 C 903.415057 889.010695 898.634943 861.042781 898.634943 830.494652 C 898.634943 815.026738 900.170455 805.681818 903.882386 797.473262 C 913.522727 776.377005 947.984943 750.695187 1025.588352 706.590909 C 1088.691193 670.802139 1104.914205 661.44385 1121.524432 651.417112 C 1192.064489 608.850267 1223.188636 578.783422 1223.188636 553.395722 C 1223.188636 538.596257 1214.015625 527.339572 1193.973864 517.312834 C 1165.333239 502.994652 1122.579261 494.786096 1025.588352 484.665775 C 925.179261 474.358289 898.26108 470.534759 869.340057 463.181818 C 832.394318 453.636364 820.270455 443.997326 807.478977 414.024064 C 796.316477 387.687166 787.34375 353.983957 770.159375 272.566845 C 757.274432 211.096257 749.91733 179.692513 742.57358 154.491979 C 731.598011 116.497326 719.66108 90.721925 706.295455 75.828877 C 697.322727 65.815508 685.198864 60.949198 674.316761 62.954545 Z M 674.316761 62.954545 \" transform=\"matrix(0.292553,0,0,0.292188,0.0585106,0)\"/>     <path style=\" stroke:none;fill-rule:nonzero;fill:rgb(0%,0%,0%);fill-opacity:1;\" d=\"M 197.332031 18.394531 C 193.507812 19.117188 188.898438 22.9375 184.683594 28.851562 C 178.902344 37.023438 173.960938 46.839844 162.285156 73.421875 C 152.597656 95.453125 148.714844 103.265625 143.660156 110.710938 C 137.769531 119.382812 133.691406 121.113281 119.140625 121.140625 C 110.679688 121.140625 103.140625 120.527344 82.421875 118.183594 C 73.761719 117.179688 63.820312 116.148438 56.449219 115.480469 C 51.117188 115.003906 34.332031 115.003906 31.175781 115.480469 C 21.152344 117.042969 16.515625 120.332031 16.152344 126.132812 C 15.957031 129.703125 17.355469 133.523438 20.648438 138.460938 C 26.152344 146.660156 35.003906 155.53125 57.425781 175.359375 C 84.097656 198.929688 92.25 208.105469 93.449219 215.859375 C 94.484375 222.382812 90.796875 234.152344 80.800781 256.523438 C 70.636719 279.28125 68.792969 283.378906 67.117188 287.480469 C 60.078125 304.523438 57.289062 315.316406 58.238281 321.953125 C 59.101562 328.144531 62.285156 331.242188 68.207031 331.601562 C 76.890625 332.1875 89.652344 326.890625 115.652344 311.96875 C 136.566406 299.949219 140.367188 297.800781 145 295.34375 C 157.996094 288.640625 172.394531 285.695312 170.804688 285.835938 Z M 197.332031 18.394531 \"/>   </g> </svg> | ||||
| Before Width: | Height: | Size: 4.3 KiB | 
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							|  | @ -4,14 +4,16 @@ import { describe, it } from "vitest" | |||
| import { parse as parse_html } from "node-html-parser" | ||||
| import { readFileSync } from "fs" | ||||
| import ScriptUtils from "../scripts/ScriptUtils" | ||||
| 
 | ||||
| function detectInCode(forbidden: string, reason: string) { | ||||
|     return wrap(detectInCodeUnwrapped(forbidden, reason)) | ||||
| } | ||||
| /** | ||||
|  * | ||||
|  * @param forbidden: a GREP-regex. This means that '.' is a wildcard and should be escaped to match a literal dot | ||||
|  * @param reason | ||||
|  * @private | ||||
|  */ | ||||
| function detectInCode(forbidden: string, reason: string): Promise<void> { | ||||
| function detectInCodeUnwrapped(forbidden: string, reason: string): Promise<void> { | ||||
|     return new Promise<void>((done) => { | ||||
|         const excludedDirs = [ | ||||
|             ".git", | ||||
|  | @ -24,15 +26,16 @@ function detectInCode(forbidden: string, reason: string): Promise<void> { | |||
|             ".idea/", | ||||
|         ] | ||||
| 
 | ||||
|         exec( | ||||
|         const command = | ||||
|             'grep -n "' + | ||||
|             forbidden + | ||||
|             '" -r . ' + | ||||
|                 excludedDirs.map((d) => "--exclude-dir=" + d).join(" "), | ||||
|             (error, stdout, stderr) => { | ||||
|             excludedDirs.map((d) => "--exclude-dir=" + d).join(" ") | ||||
|         console.log(command) | ||||
|         exec(command, (error, stdout, stderr) => { | ||||
|             if (error?.message?.startsWith("Command failed: grep")) { | ||||
|                 console.warn("Command failed!", error) | ||||
|                     return | ||||
|                 throw error | ||||
|             } | ||||
|             if (error !== null) { | ||||
|                 throw error | ||||
|  | @ -46,15 +49,12 @@ function detectInCode(forbidden: string, reason: string): Promise<void> { | |||
|                 .filter((s) => s !== "") | ||||
|                 .filter((s) => !s.startsWith("./test/")) | ||||
|             if (found.length > 0) { | ||||
|                     const msg = `Found a '${forbidden}' at \n    ${found.join( | ||||
|                         "\n     " | ||||
|                     )}.\n ${reason}` | ||||
|                 const msg = `Found a '${forbidden}' at \n    ${found.join("\n     ")}.\n ${reason}` | ||||
|                 console.error(msg) | ||||
|                 console.error(found.length, "issues found") | ||||
|                 throw msg | ||||
|             } | ||||
|             } | ||||
|         ) | ||||
|         }) | ||||
|     }) | ||||
| } | ||||
| 
 | ||||
|  | @ -64,10 +64,6 @@ function wrap(promise: Promise<void>): (done: () => void) => void { | |||
|     } | ||||
| } | ||||
| 
 | ||||
| function itAsync(name: string, promise: Promise<void>) { | ||||
|     it(name, wrap(promise)) | ||||
| } | ||||
| 
 | ||||
| function validateScriptIntegrityOf(path: string) { | ||||
|     const htmlContents = readFileSync(path, "utf8") | ||||
|     const doc = parse_html(htmlContents) | ||||
|  | @ -95,7 +91,7 @@ function validateScriptIntegrityOf(path: string) { | |||
| } | ||||
| 
 | ||||
| describe("Code quality", () => { | ||||
|     itAsync( | ||||
|     it( | ||||
|         "should not contain reverse", | ||||
|         detectInCode( | ||||
|             "reverse()", | ||||
|  | @ -103,12 +99,12 @@ describe("Code quality", () => { | |||
|         ) | ||||
|     ) | ||||
| 
 | ||||
|     itAsync( | ||||
|     it( | ||||
|         "should not contain 'constructor.name'", | ||||
|         detectInCode("constructor\\.name", "This is not allowed, as minification does erase names.") | ||||
|     ) | ||||
| 
 | ||||
|     itAsync( | ||||
|     it( | ||||
|         "should not contain 'innerText'", | ||||
|         detectInCode( | ||||
|             "innerText", | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue