Performance: trim svg.ts, use Svelte-SVG-componenets where possible (WIP)

This commit is contained in:
Pieter Vander Vennet 2023-11-19 04:38:34 +01:00
parent ac5c60c3f0
commit c13d80f062
37 changed files with 367 additions and 378 deletions

View file

@ -160,7 +160,7 @@
"craft=key_cutter" "craft=key_cutter"
] ]
}, },
"then": "./assets/layers/id_presets/fas-key.svg" "then": "circle:white;./assets/layers/id_presets/fas-key.svg"
} }
], ],
"label": { "label": {

View file

@ -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

View file

@ -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

View file

@ -1,11 +1,47 @@
import * as fs from "fs" import * as fs from "fs"
function genImages(dryrun = false) { function genImages(dryrun = false) {
console.log("Generating images") 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") const dir = fs.readdirSync("./assets/svg")
let module = let module =
'import Img from "./UI/Base/Img";\nimport {FixedUiElement} from "./UI/Base/FixedUiElement";\n\n/* @deprecated */\nexport default class Svg {\n\n\n' '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) { for (const path of dir) {
if (path.endsWith("license_info.json")) { if (path.endsWith("license_info.json")) {
continue continue
@ -21,6 +57,7 @@ function genImages(dryrun = false) {
let svg: string = fs let svg: string = fs
.readFileSync("./assets/svg/" + path, "utf-8") .readFileSync("./assets/svg/" + path, "utf-8")
.replace(/<\?xml.*?>/, "") .replace(/<\?xml.*?>/, "")
.replace(/<!DOCTYPE [^>]*>/, "")
.replace(/fill: ?none;/g, "fill: none !important;") // This is such a brittle hack... .replace(/fill: ?none;/g, "fill: none !important;") // This is such a brittle hack...
.replace(/\n/g, " ") .replace(/\n/g, " ")
.replace(/\r/g, "") .replace(/\r/g, "")
@ -37,18 +74,6 @@ function genImages(dryrun = false) {
} }
const name = path.substring(0, path.length - 4).replace(/[ -]/g, "_") 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 nameUC = name.toUpperCase().at(0) + name.substring(1)
const svelteCode = const svelteCode =
'<script>\nexport let color = "#000000"\n</script>\n' + '<script>\nexport let color = "#000000"\n</script>\n' +
@ -60,8 +85,23 @@ function genImages(dryrun = false) {
.replace(/\\"/g, '"') .replace(/\\"/g, '"')
.replace(/(rgb\(0%,0%,0%\)|#000000|#000)/g, "{color}") .replace(/(rgb\(0%,0%,0%\)|#000000|#000)/g, "{color}")
fs.writeFileSync("./src/assets/svg/" + nameUC + ".svelte", svelteCode, "utf8") 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" module += "}\n"
fs.writeFileSync("src/Svg.ts", module) fs.writeFileSync("src/Svg.ts", module)
console.log("Done") console.log("Done")

View file

@ -92,7 +92,7 @@ export class DoesImageExist extends DesugaringStep<string> {
return image return image
} }
if (image.match(/[a-z]*/)) { 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' // This is a builtin img, e.g. 'checkmark' or 'crosshair'
return image return image
} }

View file

@ -14,6 +14,7 @@ import { VariableUiElement } from "../../UI/Base/VariableUIElement"
import { TagRenderingConfigJson } from "./Json/TagRenderingConfigJson" import { TagRenderingConfigJson } from "./Json/TagRenderingConfigJson"
import SvelteUIElement from "../../UI/Base/SvelteUIElement" import SvelteUIElement from "../../UI/Base/SvelteUIElement"
import DynamicMarker from "../../UI/Map/DynamicMarker.svelte" import DynamicMarker from "../../UI/Map/DynamicMarker.svelte"
import { html } from "svelte/types/compiler/utils/namespaces"
export class IconConfig extends WithContextLoader { export class IconConfig extends WithContextLoader {
public static readonly defaultIcon = new IconConfig({ icon: "pin", color: "#ff9939" }) public static readonly defaultIcon = new IconConfig({ icon: "pin", color: "#ff9939" })
@ -133,71 +134,23 @@ export default class PointRenderingConfig extends WithContextLoader {
context + ".rotationAlignment" context + ".rotationAlignment"
) )
} }
private static FromHtmlMulti(multiSpec: string, tags: Store<Record<string, string>>) {
/** const icons: IconConfig[] = []
* Given a single HTML spec (either a single image path OR "image_path_to_known_svg:fill-colour", returns a fixedUIElement containing that for (const subspec of multiSpec.split(";")) {
* The element will fill 100% and be positioned absolutely with top:0 and left: 0 const [icon, color] = subspec.split(":")
*/ icons.push(new IconConfig({ icon, color }))
private static FromHtmlSpec(htmlSpec: string, style: string, isBadge = false): BaseUIElement {
if (htmlSpec === undefined) {
return undefined
} }
const match = htmlSpec.match(/([a-zA-Z0-9_]*):([^;]*)/) return new SvelteUIElement(DynamicMarker, { marker: icons, tags }).SetClass(
if (match !== null && Svg.All[match[1] + ".svg"] !== undefined) { "w-full h-full block absolute top-0 left-0"
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)
) )
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 { 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( public RenderIcon(
@ -249,9 +202,11 @@ export default class PointRenderingConfig extends WithContextLoader {
anchorH = -iconH / 2 anchorH = -iconH / 2
} }
const icon = new SvelteUIElement(DynamicMarker, { config: this, tags }).SetClass( const icon = new SvelteUIElement(DynamicMarker, {
"w-full h-full" marker: this.marker,
) rotation: this.rotation,
tags,
}).SetClass("w-full h-full")
let badges = undefined let badges = undefined
if (options?.includeBadges ?? true) { if (options?.includeBadges ?? true) {
badges = this.GetBadges(tags, options?.metatags) 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( tags.map((tags) => this.iconSize.GetRenderValue(tags).Subs(tags).txt ?? "[40,40]").map(
(size) => { (size) => {
const [iconW, iconH] = size.split(",").map((x) => num(x)) const [iconW, iconH] = size.split(",").map((x) => num(x))
console.log("Setting size to", iconW, iconH)
iconAndBadges.SetStyle(`width: ${iconW}px; height: ${iconH}px`) iconAndBadges.SetStyle(`width: ${iconW}px; height: ${iconH}px`)
} }
) )
@ -307,9 +261,9 @@ export default class PointRenderingConfig extends WithContextLoader {
} }
return new VariableUiElement( return new VariableUiElement(
tags.map( tags.map(
(tags) => { (tagsData) => {
const badgeElements = this.iconBadges.map((badge) => { const badgeElements = this.iconBadges.map((badge) => {
if (!badge.if.matchesProperties(tags)) { if (!badge.if.matchesProperties(tagsData)) {
// Doesn't match... // Doesn't match...
return undefined return undefined
} }
@ -324,19 +278,23 @@ export default class PointRenderingConfig extends WithContextLoader {
} }
const htmlDefs = Utils.SubstituteKeys( const htmlDefs = Utils.SubstituteKeys(
badge.then.GetRenderValue(tags)?.txt, badge.then.GetRenderValue(tagsData)?.txt,
tags tagsData
) )
if (htmlDefs.startsWith("<") && htmlDefs.endsWith(">")) { if (htmlDefs.startsWith("<") && htmlDefs.endsWith(">")) {
// This is probably an HTML-element // This is probably an HTML-element
return new FixedUiElement(Utils.SubstituteKeys(htmlDefs, tags)) return new FixedUiElement(Utils.SubstituteKeys(htmlDefs, tagsData))
.SetStyle("width: 1.5rem") .SetStyle("width: 1.5rem")
.SetClass("block") .SetClass("block")
} }
if (!htmlDefs) {
return undefined
}
const badgeElement = PointRenderingConfig.FromHtmlMulti( const badgeElement = PointRenderingConfig.FromHtmlMulti(
htmlDefs, htmlDefs,
"0", tags
true
)?.SetClass("block relative") )?.SetClass("block relative")
if (badgeElement === undefined) { if (badgeElement === undefined) {
return undefined return undefined

View file

@ -19,6 +19,7 @@ import { Paragraph } from "../../UI/Base/Paragraph"
import Svg from "../../Svg" import Svg from "../../Svg"
import Validators, { ValidatorType } from "../../UI/InputElement/Validators" import Validators, { ValidatorType } from "../../UI/InputElement/Validators"
import { TagRenderingConfigJson } from "./Json/TagRenderingConfigJson" import { TagRenderingConfigJson } from "./Json/TagRenderingConfigJson"
import Constants from "../Constants"
export interface Icon {} export interface Icon {}
@ -362,7 +363,7 @@ export default class TagRenderingConfig {
if (stripped.endsWith(".svg")) { if (stripped.endsWith(".svg")) {
stripped = stripped.substring(0, stripped.length - 4) stripped = stripped.substring(0, stripped.length - 4)
} }
if (Svg.All[stripped + ".svg"] !== undefined) { if (Constants.defaultPinIcons.indexOf(stripped) >= 0) {
icon = "./assets/svg/" + mapping.icon icon = "./assets/svg/" + mapping.icon
if (!icon.endsWith(".svg")) { if (!icon.endsWith(".svg")) {
icon += ".svg" icon += ".svg"

View file

@ -5,6 +5,7 @@
*/ */
import { Store } from "../../Logic/UIEventSource" import { Store } from "../../Logic/UIEventSource"
import { onDestroy } from "svelte" import { onDestroy } from "svelte"
import Hand from "../../assets/svg/Hand.svelte";
let mainElem: HTMLElement let mainElem: HTMLElement
export let hideSignal: Store<any> 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 bind:this={mainElem} class="pointer-events-none absolute bottom-0 right-0 h-full w-full">
<div id="hand-container"> <div id="hand-container">
<img src="./assets/svg/hand.svg" /> <Hand />
</div> </div>
</div> </div>

View file

@ -6,6 +6,7 @@
import Tr from "./Tr.svelte" import Tr from "./Tr.svelte"
import { OsmConnection } from "../../Logic/Osm/OsmConnection" import { OsmConnection } from "../../Logic/Osm/OsmConnection"
import { ImmutableStore, UIEventSource } from "../../Logic/UIEventSource" import { ImmutableStore, UIEventSource } from "../../Logic/UIEventSource"
import Invalid from "../../assets/svg/Invalid.svelte";
export let state: { export let state: {
osmConnection: OsmConnection osmConnection: OsmConnection
@ -35,7 +36,7 @@
</slot> </slot>
{:else if $loadingStatus === "error"} {:else if $loadingStatus === "error"}
<div class="alert max-w-64 flex items-center"> <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]} /> <Tr t={offlineModes[$apiState]} />
</div> </div>
{:else if $loadingStatus === "logged-in"} {:else if $loadingStatus === "logged-in"}

View 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>

View 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}

View file

@ -1,6 +1,7 @@
<script lang="ts"> <script lang="ts">
import ToSvelte from "./ToSvelte.svelte" import ToSvelte from "./ToSvelte.svelte"
import Svg from "../../Svg" import Svg from "../../Svg"
import Share from "../../assets/svg/Share.svelte";
export let generateShareData: () => { export let generateShareData: () => {
text: string text: string
@ -25,6 +26,6 @@
<button on:click={share} class="secondary m-0 h-8 w-8 p-0"> <button on:click={share} class="secondary m-0 h-8 w-8 p-0">
<slot name="content"> <slot name="content">
<ToSvelte construct={Svg.share_svg().SetClass("w-7 h-7 p-1")} /> <Share class="w-7 h-7 p-1"/>
</slot> </slot>
</button> </button>

View file

@ -23,10 +23,10 @@ export default class SvelteUIElement<
private readonly _events: Events private readonly _events: Events
private readonly _slots: Slots private readonly _slots: Slots
constructor(svelteElement, props: Props, events?: Events, slots?: Slots) { constructor(svelteElement, props?: Props, events?: Events, slots?: Slots) {
super() super()
this._svelteComponent = svelteElement this._svelteComponent = svelteElement
this._props = props this._props = props ?? <Props>{}
this._events = events this._events = events
this._slots = slots this._slots = slots
} }

View file

@ -1,6 +1,7 @@
<script lang="ts"> <script lang="ts">
import Locale from "../i18n/Locale" import Locale from "../i18n/Locale"
import LinkToWeblate from "./LinkToWeblate" 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 * Shows a small icon which will open up weblate; a contributor can translate the item for 'context' there
@ -19,7 +20,7 @@
target="_blank" target="_blank"
class="weblate-link mx-1" class="weblate-link mx-1"
> >
<img src="./assets/svg/translate.svg" class="font-gray" /> <Translate class="font-gray" />
</a> </a>
{:else if $linkToWeblate} {:else if $linkToWeblate}
<a <a
@ -27,7 +28,7 @@
class="weblate-link hidden-on-mobile mx-1" class="weblate-link hidden-on-mobile mx-1"
target="_blank" target="_blank"
> >
<img src="./assets/svg/translate.svg" class="font-gray inline-block" /> <Translate class="font-gray inline-block" />
</a> </a>
{/if} {/if}
{/if} {/if}

View file

@ -4,6 +4,7 @@
import { Store } from "../../Logic/UIEventSource" import { Store } from "../../Logic/UIEventSource"
import Tr from "../Base/Tr.svelte" import Tr from "../Base/Tr.svelte"
import ToSvelte from "../Base/ToSvelte.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 A subtleButton which opens mapillary in a new tab at the current location
@ -21,7 +22,7 @@
</script> </script>
<a class="button flex items-center" href={mapillaryLink} target="_blank"> <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"> <div class="flex flex-col">
<Tr t={Translations.t.general.attribution.openMapillary} /> <Tr t={Translations.t.general.attribution.openMapillary} />
<Tr cls="subtle" t={Translations.t.general.attribution.mapillaryHelp} /> <Tr cls="subtle" t={Translations.t.general.attribution.mapillaryHelp} />

View file

@ -16,6 +16,7 @@
import LayerConfig from "../../Models/ThemeConfig/LayerConfig" import LayerConfig from "../../Models/ThemeConfig/LayerConfig"
import { Utils } from "../../Utils" import { Utils } from "../../Utils"
import { createEventDispatcher } from "svelte" import { createEventDispatcher } from "svelte"
import Move_arrows from "../../assets/svg/Move_arrows.svelte";
/** /**
* An advanced location input, which has support to: * An advanced location input, which has support to:
@ -125,6 +126,6 @@
maxDistanceInMeters="50" maxDistanceInMeters="50"
> >
<slot name="image" slot="image"> <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> </slot>
</LocationInput> </LocationInput>

View file

@ -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])
}
}

View file

@ -15,6 +15,7 @@
import If from "../Base/If.svelte" import If from "../Base/If.svelte"
import { ExclamationTriangleIcon } from "@babeard/svelte-heroicons/mini" import { ExclamationTriangleIcon } from "@babeard/svelte-heroicons/mini"
import type { Readable } from "svelte/store" import type { Readable } from "svelte/store"
import Add from "../../assets/svg/Add.svelte";
/** /**
* The theme introduction panel * The theme introduction panel
@ -156,7 +157,7 @@
<div class="links-as-button links-w-full m-2 flex flex-col gap-y-1"> <div class="links-as-button links-w-full m-2 flex flex-col gap-y-1">
<!-- bottom buttons, a bit hidden away: switch layout --> <!-- bottom buttons, a bit hidden away: switch layout -->
<a class="flex" href={Utils.HomepageLink()}> <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} /> <Tr t={Translations.t.general.backToIndex} />
</a> </a>
</div> </div>

View file

@ -12,6 +12,8 @@ import { OsmConnection } from "../../Logic/Osm/OsmConnection"
import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig" import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig"
import { Translation } from "../i18n/Translation" import { Translation } from "../i18n/Translation"
import { LoginToggle } from "../Popup/LoginButton" import { LoginToggle } from "../Popup/LoginButton"
import SvelteUIElement from "../Base/SvelteUIElement"
import Upload from "../../assets/svg/Upload.svelte"
export default class UploadTraceToOsmUI extends LoginToggle { export default class UploadTraceToOsmUI extends LoginToggle {
constructor( constructor(
@ -83,7 +85,7 @@ export default class UploadTraceToOsmUI extends LoginToggle {
clicked.setData(false) clicked.setData(false)
}) })
.SetClass(""), .SetClass(""),
new SubtleButton(Svg.upload_svg(), t.confirm).OnClickWithLoading( new SubtleButton(new SvelteUIElement(Upload, {}), t.confirm).OnClickWithLoading(
t.uploading, t.uploading,
async () => { async () => {
const titleStr = UploadTraceToOsmUI.createDefault( 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"), ]).SetClass("flex p-2 rounded-xl border-2 subtle-border items-center"),
new Toggle( new Toggle(
confirmPanel, confirmPanel,
new SubtleButton(Svg.upload_svg(), t.title).onClick(() => new SubtleButton(new SvelteUIElement(Upload), t.title).onClick(() =>
clicked.setData(true) clicked.setData(true)
), ),
clicked clicked

View file

@ -3,16 +3,15 @@
* Shows an 'upload'-button which will start the upload for this feature * Shows an 'upload'-button which will start the upload for this feature
*/ */
import type { SpecialVisualizationState } from "../SpecialVisualization" import type { SpecialVisualizationState } from "../SpecialVisualization";
import { ImmutableStore, Store } from "../../Logic/UIEventSource" import { ImmutableStore, Store } from "../../Logic/UIEventSource";
import type { OsmTags } from "../../Models/OsmFeature" import type { OsmTags } from "../../Models/OsmFeature";
import LoginToggle from "../Base/LoginToggle.svelte" import LoginToggle from "../Base/LoginToggle.svelte";
import Translations from "../i18n/Translations" import Translations from "../i18n/Translations";
import Tr from "../Base/Tr.svelte" import Tr from "../Base/Tr.svelte";
import UploadingImageCounter from "./UploadingImageCounter.svelte" import UploadingImageCounter from "./UploadingImageCounter.svelte";
import FileSelector from "../Base/FileSelector.svelte" import FileSelector from "../Base/FileSelector.svelte";
import ToSvelte from "../Base/ToSvelte.svelte" import Camera_plus from "../../assets/svg/Camera_plus.svelte";
import Svg from "../../Svg"
export let state: SpecialVisualizationState export let state: SpecialVisualizationState
@ -58,7 +57,7 @@
{#if image !== undefined} {#if image !== undefined}
<img src={image} /> <img src={image} />
{:else} {: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}
{#if labelText} {#if labelText}
{labelText} {labelText}

View file

@ -12,6 +12,7 @@
import * as turf from "@turf/turf" import * as turf from "@turf/turf"
import LayerConfig from "../../../Models/ThemeConfig/LayerConfig" import LayerConfig from "../../../Models/ThemeConfig/LayerConfig"
import { createEventDispatcher, onDestroy } from "svelte" import { createEventDispatcher, onDestroy } from "svelte"
import Move_arrows from "../../../assets/svg/Move_arrows.svelte";
/** /**
* A visualisation to pick a location on a map background * 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" class="pointer-events-none absolute top-0 left-0 flex h-full w-full items-center p-8 opacity-50"
> >
<slot name="image"> <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> </slot>
</div> </div>

View file

@ -1,21 +1,21 @@
<script lang="ts"> <script lang="ts">
import PointRenderingConfig, { IconConfig } from "../../Models/ThemeConfig/PointRenderingConfig" import { IconConfig } from "../../Models/ThemeConfig/PointRenderingConfig";
import { Store } from "../../Logic/UIEventSource" import { ImmutableStore, Store } from "../../Logic/UIEventSource";
import DynamicIcon from "./DynamicIcon.svelte" import DynamicIcon from "./DynamicIcon.svelte";
import TagRenderingConfig from "../../Models/ThemeConfig/TagRenderingConfig";
/** /**
* Renders a 'marker', which consists of multiple 'icons' * Renders a 'marker', which consists of multiple 'icons'
*/ */
export let config: PointRenderingConfig export let marker: IconConfig[] = config?.marker;
let icons: IconConfig[] = config.marker export let rotation: TagRenderingConfig
export let tags: Store<Record<string, string>> export let tags: Store<Record<string, string>>;
let rotation = tags.map(tags => config.rotation.GetRenderValue(tags).Subs(tags).txt) let _rotation = rotation ? tags.map(tags => rotation.GetRenderValue(tags).Subs(tags).txt) : new ImmutableStore(0);
</script> </script>
{#if config !== undefined} {#if marker && marker}
<div class="relative h-full w-full" style={`transform: rotate(${$rotation})`}> <div class="relative h-full w-full" style={`transform: rotate(${$_rotation})`}>
{#each icons as icon} {#each marker as icon}
<DynamicIcon {icon} {tags} /> <DynamicIcon {icon} {tags} />
{/each} {/each}
</div> </div>

View file

@ -12,6 +12,7 @@
import { createEventDispatcher } from "svelte" import { createEventDispatcher } from "svelte"
import ToSvelte from "../Base/ToSvelte.svelte" import ToSvelte from "../Base/ToSvelte.svelte"
import Svg from "../../Svg" import Svg from "../../Svg"
import Plantnet_logo from "../../assets/svg/Plantnet_logo.svelte";
/** /**
* The main entry point for the plantnet wizard * The main entry point for the plantnet wizard
@ -142,9 +143,7 @@
</BackButton> </BackButton>
{/if} {/if}
<div class="low-interaction flex self-end rounded-xl p-2"> <div class="low-interaction flex self-end rounded-xl p-2">
<ToSvelte <Plantnet_logo class="w-8 h-8 p-1 mr-1 bg-white rounded-full"/>
construct={Svg.plantnet_logo_svg().SetClass("w-8 h-8 p-1 mr-1 bg-white rounded-full")}
/>
<Tr t={t.poweredByPlantnet} /> <Tr t={t.poweredByPlantnet} />
</div> </div>
</div> </div>

View file

@ -3,109 +3,110 @@
* This component ties together all the steps that are needed to create a new point. * This component ties together all the steps that are needed to create a new point.
* There are many subcomponents which help with that * There are many subcomponents which help with that
*/ */
import type { SpecialVisualizationState } from "../../SpecialVisualization" import type { SpecialVisualizationState } from "../../SpecialVisualization";
import PresetList from "./PresetList.svelte" import PresetList from "./PresetList.svelte";
import type PresetConfig from "../../../Models/ThemeConfig/PresetConfig" import type PresetConfig from "../../../Models/ThemeConfig/PresetConfig";
import LayerConfig from "../../../Models/ThemeConfig/LayerConfig" import LayerConfig from "../../../Models/ThemeConfig/LayerConfig";
import Tr from "../../Base/Tr.svelte" import Tr from "../../Base/Tr.svelte";
import SubtleButton from "../../Base/SubtleButton.svelte" import SubtleButton from "../../Base/SubtleButton.svelte";
import FromHtml from "../../Base/FromHtml.svelte" import Translations from "../../i18n/Translations.js";
import Translations from "../../i18n/Translations.js" import TagHint from "../TagHint.svelte";
import TagHint from "../TagHint.svelte" import { And } from "../../../Logic/Tags/And.js";
import { And } from "../../../Logic/Tags/And.js" import LoginToggle from "../../Base/LoginToggle.svelte";
import LoginToggle from "../../Base/LoginToggle.svelte" import Constants from "../../../Models/Constants.js";
import Constants from "../../../Models/Constants.js" import FilteredLayer from "../../../Models/FilteredLayer";
import FilteredLayer from "../../../Models/FilteredLayer" import { Store, UIEventSource } from "../../../Logic/UIEventSource";
import { Store, UIEventSource } from "../../../Logic/UIEventSource" import { EyeIcon, EyeOffIcon } from "@rgossiaux/svelte-heroicons/solid";
import { EyeIcon, EyeOffIcon } from "@rgossiaux/svelte-heroicons/solid" import LoginButton from "../../Base/LoginButton.svelte";
import LoginButton from "../../Base/LoginButton.svelte" import NewPointLocationInput from "../../BigComponents/NewPointLocationInput.svelte";
import NewPointLocationInput from "../../BigComponents/NewPointLocationInput.svelte" import CreateNewNodeAction from "../../../Logic/Osm/Actions/CreateNewNodeAction";
import CreateNewNodeAction from "../../../Logic/Osm/Actions/CreateNewNodeAction" import { OsmWay } from "../../../Logic/Osm/OsmObject";
import { OsmWay } from "../../../Logic/Osm/OsmObject" import { Tag } from "../../../Logic/Tags/Tag";
import { Tag } from "../../../Logic/Tags/Tag" import type { WayId } from "../../../Models/OsmFeature";
import type { WayId } from "../../../Models/OsmFeature" import Loading from "../../Base/Loading.svelte";
import Loading from "../../Base/Loading.svelte" import type { GlobalFilter } from "../../../Models/GlobalFilter";
import type { GlobalFilter } from "../../../Models/GlobalFilter" import { onDestroy } from "svelte";
import { onDestroy } from "svelte" import NextButton from "../../Base/NextButton.svelte";
import NextButton from "../../Base/NextButton.svelte" import BackButton from "../../Base/BackButton.svelte";
import BackButton from "../../Base/BackButton.svelte" import ToSvelte from "../../Base/ToSvelte.svelte";
import ToSvelte from "../../Base/ToSvelte.svelte" import Svg from "../../../Svg";
import Svg from "../../../Svg" import OpenBackgroundSelectorButton from "../../BigComponents/OpenBackgroundSelectorButton.svelte";
import OpenBackgroundSelectorButton from "../../BigComponents/OpenBackgroundSelectorButton.svelte" import { twJoin } from "tailwind-merge";
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 coordinate: { lon: number; lat: number };
export let state: SpecialVisualizationState export let state: SpecialVisualizationState;
let selectedPreset: { let selectedPreset: {
preset: PresetConfig preset: PresetConfig
layer: LayerConfig layer: LayerConfig
icon: string icon: string
tags: Record<string, string> tags: Record<string, string>
} = undefined } = undefined;
let checkedOfGlobalFilters: number = 0 let checkedOfGlobalFilters: number = 0;
let confirmedCategory = false let confirmedCategory = false;
$: if (selectedPreset === undefined) { $: if (selectedPreset === undefined) {
confirmedCategory = false confirmedCategory = false;
creating = false creating = false;
checkedOfGlobalFilters = 0 checkedOfGlobalFilters = 0;
} }
let flayer: FilteredLayer = undefined let flayer: FilteredLayer = undefined;
let layerIsDisplayed: UIEventSource<boolean> | undefined = undefined let layerIsDisplayed: UIEventSource<boolean> | undefined = undefined;
let layerHasFilters: Store<boolean> | undefined = undefined let layerHasFilters: Store<boolean> | undefined = undefined;
let globalFilter: UIEventSource<GlobalFilter[]> = state.layerState.globalFilters let globalFilter: UIEventSource<GlobalFilter[]> = state.layerState.globalFilters;
let _globalFilter: GlobalFilter[] = [] let _globalFilter: GlobalFilter[] = [];
onDestroy( onDestroy(
globalFilter.addCallbackAndRun((globalFilter) => { globalFilter.addCallbackAndRun((globalFilter) => {
console.log("Global filters are", globalFilter) console.log("Global filters are", globalFilter);
_globalFilter = globalFilter ?? [] _globalFilter = globalFilter ?? [];
}) })
) );
$: { $: {
flayer = state.layerState.filteredLayers.get(selectedPreset?.layer?.id) flayer = state.layerState.filteredLayers.get(selectedPreset?.layer?.id);
layerIsDisplayed = flayer?.isDisplayed layerIsDisplayed = flayer?.isDisplayed;
layerHasFilters = flayer?.hasFilter 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 const isLoading = state.dataIsLoading;
let preciseCoordinate: UIEventSource<{ lon: number; lat: number }> = new UIEventSource(undefined) let preciseCoordinate: UIEventSource<{ lon: number; lat: number }> = new UIEventSource(undefined);
let snappedToObject: UIEventSource<string> = new UIEventSource<string>(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 // 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. * Call when the user should restart the flow by clicking on the map, e.g. because they disabled filters.
* Will delete the lastclick-location * Will delete the lastclick-location
*/ */
function abort() { function abort() {
state.selectedElement.setData(undefined) state.selectedElement.setData(undefined);
// When aborted, we force the contributors to place the pin _again_ // 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 // This is because there might be a nearby object that was disabled; this forces them to re-evaluate the map
state.lastClickObject.features.setData([]) state.lastClickObject.features.setData([]);
preciseInputIsTapped = false preciseInputIsTapped = false;
} }
async function confirm() { async function confirm() {
creating = true creating = true;
const location: { lon: number; lat: number } = preciseCoordinate.data const location: { lon: number; lat: number } = preciseCoordinate.data;
const snapTo: WayId | undefined = <WayId>snappedToObject.data const snapTo: WayId | undefined = <WayId>snappedToObject.data;
const tags: Tag[] = selectedPreset.preset.tags.concat( const tags: Tag[] = selectedPreset.preset.tags.concat(
..._globalFilter.map((f) => f?.onNewPoint?.tags ?? []) ..._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) { if (snapTo !== undefined && snapTo !== null) {
const downloaded = await state.osmObjectDownloader.DownloadObjectAsync(snapTo, 0) const downloaded = await state.osmObjectDownloader.DownloadObjectAsync(snapTo, 0);
if (downloaded !== "deleted") { if (downloaded !== "deleted") {
snapToWay = downloaded snapToWay = downloaded;
} }
} }
@ -113,44 +114,44 @@
theme: state.layout?.id ?? "unkown", theme: state.layout?.id ?? "unkown",
changeType: "create", changeType: "create",
snapOnto: snapToWay, snapOnto: snapToWay,
reusePointWithinMeters: 1, reusePointWithinMeters: 1
}) });
await state.changes.applyAction(newElementAction) await state.changes.applyAction(newElementAction);
state.newFeatures.features.ping() state.newFeatures.features.ping();
// The 'changes' should have created a new point, which added this into the 'featureProperties' // The 'changes' should have created a new point, which added this into the 'featureProperties'
const newId = newElementAction.newElementId const newId = newElementAction.newElementId;
console.log("Applied pending changes, fetching store for", newId) console.log("Applied pending changes, fetching store for", newId);
const tagsStore = state.featureProperties.getStore(newId) const tagsStore = state.featureProperties.getStore(newId);
if (!tagsStore) { if (!tagsStore) {
console.error("Bug: no tagsStore found for", newId) console.error("Bug: no tagsStore found for", newId);
} }
{ {
// Set some metainfo // Set some metainfo
const properties = tagsStore.data const properties = tagsStore.data;
if (snapTo) { if (snapTo) {
// metatags (starting with underscore) are not uploaded, so we can safely mark this // metatags (starting with underscore) are not uploaded, so we can safely mark this
delete properties["_referencing_ways"] delete properties["_referencing_ways"];
properties["_referencing_ways"] = `["${snapTo}"]` properties["_referencing_ways"] = `["${snapTo}"]`;
} }
properties["_backend"] = state.osmConnection.Backend() properties["_backend"] = state.osmConnection.Backend();
properties["_last_edit:timestamp"] = new Date().toISOString() properties["_last_edit:timestamp"] = new Date().toISOString();
const userdetails = state.osmConnection.userDetails.data const userdetails = state.osmConnection.userDetails.data;
properties["_last_edit:contributor"] = userdetails.name properties["_last_edit:contributor"] = userdetails.name;
properties["_last_edit:uid"] = "" + userdetails.uid properties["_last_edit:uid"] = "" + userdetails.uid;
tagsStore.ping() tagsStore.ping();
} }
const feature = state.indexedFeatures.featuresById.data.get(newId) const feature = state.indexedFeatures.featuresById.data.get(newId);
console.log("Selecting feature", feature, "and opening their popup") console.log("Selecting feature", feature, "and opening their popup");
abort() abort();
state.selectedLayer.setData(selectedPreset.layer) state.selectedLayer.setData(selectedPreset.layer);
state.selectedElement.setData(feature) state.selectedElement.setData(feature);
tagsStore.ping() tagsStore.ping();
} }
function confirmSync() { function confirmSync() {
confirm() confirm()
.then((_) => console.debug("New point successfully handled")) .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> </script>
@ -285,7 +286,7 @@
<NextButton on:click={() => (confirmedCategory = true)} clss="primary w-full"> <NextButton on:click={() => (confirmedCategory = true)} clss="primary w-full">
<div slot="image" class="relative"> <div slot="image" class="relative">
<ToSvelte construct={selectedPreset.icon} /> <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>
<div class="w-full"> <div class="w-full">
<Tr t={selectedPreset.text} /> <Tr t={selectedPreset.text} />
@ -299,11 +300,7 @@
checkedOfGlobalFilters = checkedOfGlobalFilters + 1 checkedOfGlobalFilters = checkedOfGlobalFilters + 1
}} }}
> >
<img <Confirm slot="image" class="h-12 w-12" />
slot="image"
src={_globalFilter[checkedOfGlobalFilters].onNewPoint?.icon ?? "./assets/svg/confirm.svg"}
class="h-12 w-12"
/>
<Tr <Tr
slot="message" slot="message"
t={_globalFilter[checkedOfGlobalFilters].onNewPoint?.confirmAddNew.Subs({ t={_globalFilter[checkedOfGlobalFilters].onNewPoint?.confirmAddNew.Subs({
@ -317,7 +314,7 @@
abort() 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} /> <Tr slot="message" t={Translations.t.general.cancel} />
</SubtleButton> </SubtleButton>
{:else if !creating} {:else if !creating}

View file

@ -15,6 +15,8 @@
import NewPointLocationInput from "../BigComponents/NewPointLocationInput.svelte" import NewPointLocationInput from "../BigComponents/NewPointLocationInput.svelte"
import ToSvelte from "../Base/ToSvelte.svelte" import ToSvelte from "../Base/ToSvelte.svelte"
import Svg from "../../Svg" 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 coordinate: UIEventSource<{ lon: number; lat: number }>
export let state: SpecialVisualizationState export let state: SpecialVisualizationState
@ -97,7 +99,7 @@
<Tr t={Translations.t.notes.noteLayerHasFilters} /> <Tr t={Translations.t.notes.noteLayerHasFilters} />
</div> </div>
<SubtleButton on:click={() => notelayer.disableAllFilters()}> <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} /> <Tr slot="message" t={Translations.t.notes.disableAllNoteFilters} />
</SubtleButton> </SubtleButton>
</div> </div>
@ -126,7 +128,7 @@
{#if $comment?.length >= 3} {#if $comment?.length >= 3}
<SubtleButton on:click={uploadNote}> <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} /> <Tr slot="message" t={Translations.t.notes.createNote} />
</SubtleButton> </SubtleButton>
{:else} {:else}
@ -143,7 +145,7 @@
<Tr t={Translations.t.notes.noteLayerNotEnabled} /> <Tr t={Translations.t.notes.noteLayerNotEnabled} />
</div> </div>
<SubtleButton on:click={enableNoteLayer}> <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} /> <Tr slot="message" t={Translations.t.notes.noteLayerDoEnable} />
</SubtleButton> </SubtleButton>
</div> </div>

View file

@ -11,6 +11,7 @@
import ToSvelte from "../Base/ToSvelte.svelte" import ToSvelte from "../Base/ToSvelte.svelte"
import { XCircleIcon } from "@babeard/svelte-heroicons/solid" import { XCircleIcon } from "@babeard/svelte-heroicons/solid"
import exp from "constants" import exp from "constants"
import Camera_plus from "../../assets/svg/Camera_plus.svelte";
export let tags: Store<OsmTags> export let tags: Store<OsmTags>
export let state: SpecialVisualizationState export let state: SpecialVisualizationState
@ -42,7 +43,7 @@
expanded = true 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} /> <Tr t={t.seeNearby} />
</button> </button>
{/if} {/if}

View file

@ -23,6 +23,8 @@
import UserRelatedState from "../../../Logic/State/UserRelatedState" import UserRelatedState from "../../../Logic/State/UserRelatedState"
import { twJoin } from "tailwind-merge" import { twJoin } from "tailwind-merge"
import { TagUtils } from "../../../Logic/Tags/TagUtils" 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 config: TagRenderingConfig
export let tags: UIEventSource<Record<string, string>> export let tags: UIEventSource<Record<string, string>>
@ -217,7 +219,7 @@
{#if config.mappings?.length >= 8} {#if config.mappings?.length >= 8}
<div class="sticky flex w-full"> <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" /> <input type="text" bind:value={$searchTerm} class="w-full" />
</div> </div>
{/if} {/if}
@ -324,7 +326,7 @@
<LoginToggle {state}> <LoginToggle {state}>
<Loading slot="loading" /> <Loading slot="loading" />
<SubtleButton slot="not-logged-in" on:click={() => state?.osmConnection?.AttemptLogin()}> <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" /> <Tr t={Translations.t.general.loginToStart} slot="message" />
</SubtleButton> </SubtleButton>
{#if $feedback !== undefined} {#if $feedback !== undefined}

View file

@ -12,6 +12,7 @@
import LayerConfig from "../../Models/ThemeConfig/LayerConfig" import LayerConfig from "../../Models/ThemeConfig/LayerConfig"
import ToSvelte from "../Base/ToSvelte.svelte" import ToSvelte from "../Base/ToSvelte.svelte"
import Svg from "../../Svg" import Svg from "../../Svg"
import Mangrove_logo from "../../assets/svg/Mangrove_logo.svelte";
/** /**
* An element showing all reviews * An element showing all reviews
@ -40,7 +41,7 @@
<Tr t={Translations.t.reviews.no_reviews_yet} /> <Tr t={Translations.t.reviews.no_reviews_yet} />
{/if} {/if}
<div class="flex justify-end"> <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} /> <Tr cls="text-sm subtle" t={Translations.t.reviews.attribution} />
</div> </div>
</div> </div>

View file

@ -58,7 +58,6 @@ import { PointImportButtonViz } from "./Popup/ImportButtons/PointImportButtonViz
import WayImportButtonViz from "./Popup/ImportButtons/WayImportButtonViz" import WayImportButtonViz from "./Popup/ImportButtons/WayImportButtonViz"
import ConflateImportButtonViz from "./Popup/ImportButtons/ConflateImportButtonViz" import ConflateImportButtonViz from "./Popup/ImportButtons/ConflateImportButtonViz"
import DeleteWizard from "./Popup/DeleteFlow/DeleteWizard.svelte" import DeleteWizard from "./Popup/DeleteFlow/DeleteWizard.svelte"
import { OpenJosm } from "./BigComponents/OpenJosm"
import OpenIdEditor from "./BigComponents/OpenIdEditor.svelte" import OpenIdEditor from "./BigComponents/OpenIdEditor.svelte"
import FediverseValidator from "./InputElement/Validators/FediverseValidator" import FediverseValidator from "./InputElement/Validators/FediverseValidator"
import SendEmail from "./Popup/SendEmail.svelte" import SendEmail from "./Popup/SendEmail.svelte"
@ -78,6 +77,8 @@ import { TagUtils } from "../Logic/Tags/TagUtils"
import Giggity from "./BigComponents/Giggity.svelte" import Giggity from "./BigComponents/Giggity.svelte"
import ThemeViewState from "../Models/ThemeViewState" import ThemeViewState from "../Models/ThemeViewState"
import LanguagePicker from "./InputElement/LanguagePicker.svelte" import LanguagePicker from "./InputElement/LanguagePicker.svelte"
import LogoutButton from "./Base/LogoutButton.svelte"
import OpenJosm from "./Base/OpenJosm.svelte"
class NearbyImageVis implements SpecialVisualization { class NearbyImageVis implements SpecialVisualization {
// Class must be in SpecialVisualisations due to weird cyclical import that breaks the tests // 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], needsUrls: [Constants.osmAuthConfig.url],
docs: "Shows a button where the user can log out", docs: "Shows a button where the user can log out",
constr(state: SpecialVisualizationState): BaseUIElement { constr(state: SpecialVisualizationState): BaseUIElement {
return new SubtleButton(Svg.logout_svg(), Translations.t.general.logout, { return new SvelteUIElement(LogoutButton, { osmConnection: state.osmConnection })
imgSize: "w-6 h-6",
}).onClick(() => {
state.osmConnection.LogOut()
})
}, },
}, },
new HistogramViz(), new HistogramViz(),
@ -903,10 +900,10 @@ export default class SpecialVisualizations {
funcName: "open_in_josm", funcName: "open_in_josm",
docs: "Opens the current view in the JOSM-editor", docs: "Opens the current view in the JOSM-editor",
args: [], args: [],
needsUrls: OpenJosm.needsUrls, needsUrls: ["http://127.0.0.1:8111/load_and_zoom"],
constr: (state) => { 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) { if (maproulette_id_key === "" || maproulette_id_key === undefined) {
maproulette_id_key = "mr_taskId" 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 failed = new UIEventSource(false)
const closeButton = new SubtleButton(image, message).OnClickWithLoading( const closeButton = new SubtleButton(image, message).OnClickWithLoading(

View file

@ -29,6 +29,7 @@
import { Utils } from "../Utils"; import { Utils } from "../Utils";
import Translations from "./i18n/Translations"; import Translations from "./i18n/Translations";
import Tr from "./Base/Tr.svelte"; import Tr from "./Base/Tr.svelte";
import Add from "../assets/svg/Add.svelte";
export let studioUrl = export let studioUrl =
window.location.hostname === "127.0.0.2" window.location.hostname === "127.0.0.2"
@ -197,7 +198,7 @@
Show the introduction again Show the introduction again
</button> </button>
<a class="flex button" href={Utils.HomepageLink()}> <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} /> <Tr t={Translations.t.general.backToIndex} />
</a> </a>
</div> </div>

View file

@ -43,18 +43,25 @@
import RasterLayerOverview from "./Map/RasterLayerOverview.svelte"; import RasterLayerOverview from "./Map/RasterLayerOverview.svelte";
import IfHidden from "./Base/IfHidden.svelte"; import IfHidden from "./Base/IfHidden.svelte";
import { onDestroy } from "svelte"; import { onDestroy } from "svelte";
import { OpenJosm } from "./BigComponents/OpenJosm";
import MapillaryLink from "./BigComponents/MapillaryLink.svelte"; import MapillaryLink from "./BigComponents/MapillaryLink.svelte";
import OpenIdEditor from "./BigComponents/OpenIdEditor.svelte"; import OpenIdEditor from "./BigComponents/OpenIdEditor.svelte";
import OpenBackgroundSelectorButton from "./BigComponents/OpenBackgroundSelectorButton.svelte"; import OpenBackgroundSelectorButton from "./BigComponents/OpenBackgroundSelectorButton.svelte";
import StateIndicator from "./BigComponents/StateIndicator.svelte"; import StateIndicator from "./BigComponents/StateIndicator.svelte";
import Locale from "./i18n/Locale";
import ShareScreen from "./BigComponents/ShareScreen.svelte"; import ShareScreen from "./BigComponents/ShareScreen.svelte";
import UploadingImageCounter from "./Image/UploadingImageCounter.svelte"; import UploadingImageCounter from "./Image/UploadingImageCounter.svelte";
import PendingChangesIndicator from "./BigComponents/PendingChangesIndicator.svelte"; import PendingChangesIndicator from "./BigComponents/PendingChangesIndicator.svelte";
import Cross from "../assets/svg/Cross.svelte"; import Cross from "../assets/svg/Cross.svelte";
import Summary from "./BigComponents/Summary.svelte"; import Summary from "./BigComponents/Summary.svelte";
import LanguagePicker from "./InputElement/LanguagePicker.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; export let state: ThemeViewState;
let layout = state.layout; let layout = state.layout;
@ -205,7 +212,7 @@
<!-- bottom left elements --> <!-- bottom left elements -->
<If condition={state.featureSwitches.featureSwitchFilter}> <If condition={state.featureSwitches.featureSwitchFilter}>
<MapControlButton on:click={() => state.guistate.openFilterView()}> <MapControlButton on:click={() => state.guistate.openFilterView()}>
<ToSvelte construct={Svg.filter_svg().SetClass("h-6 w-6")} /> <Filter class="h-6 w-6"/>
</MapControlButton> </MapControlButton>
</If> </If>
<If condition={state.featureSwitches.featureSwitchBackgroundSelection}> <If condition={state.featureSwitches.featureSwitchBackgroundSelection}>
@ -244,10 +251,10 @@
</div> </div>
</If> </If>
<MapControlButton on:click={() => mapproperties.zoom.update((z) => z + 1)}> <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>
<MapControlButton on:click={() => mapproperties.zoom.update((z) => z - 1)}> <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> </MapControlButton>
<If condition={featureSwitches.featureSwitchGeolocation}> <If condition={featureSwitches.featureSwitchGeolocation}>
<MapControlButton> <MapControlButton>
@ -340,7 +347,7 @@
</div> </div>
<div class="flex" slot="title1"> <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} /> <Tr t={Translations.t.general.menu.filter} />
</div> </div>
@ -431,27 +438,27 @@
<Tr t={Translations.t.general.aboutMapComplete.intro} /> <Tr t={Translations.t.general.aboutMapComplete.intro} />
<a class="flex" href={Utils.HomepageLink()}> <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} /> <Tr t={Translations.t.general.backToIndex} />
</a> </a>
<a class="flex" href="https://github.com/pietervdvn/MapComplete/issues" target="_blank"> <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} /> <Tr t={Translations.t.general.attribution.openIssueTracker} />
</a> </a>
<a class="flex" href="https://en.osm.town/@MapComplete" target="_blank"> <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} /> <Tr t={Translations.t.general.attribution.followOnMastodon} />
</a> </a>
<a class="flex" href="https://liberapay.com/pietervdvn/" target="_blank"> <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} /> <Tr t={Translations.t.general.attribution.donate} />
</a> </a>
<a class="flex" href={Utils.OsmChaLinkFor(7)} target="_blank"> <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" })} /> <Tr t={Translations.t.general.attribution.openOsmcha.Subs({ theme: "MapComplete" })} />
</a> </a>
{Constants.vNumber} {Constants.vNumber}
@ -503,10 +510,7 @@
<div class="m-2 flex flex-col" slot="content4"> <div class="m-2 flex flex-col" slot="content4">
<If condition={featureSwitches.featureSwitchEnableLogin}> <If condition={featureSwitches.featureSwitchEnableLogin}>
<OpenIdEditor mapProperties={state.mapProperties} /> <OpenIdEditor mapProperties={state.mapProperties} />
<ToSvelte <OpenJosm {state}/>
construct={() =>
new OpenJosm(state.osmConnection, state.mapProperties.bounds).SetClass("w-full")}
/>
<MapillaryLink mapProperties={state.mapProperties} /> <MapillaryLink mapProperties={state.mapProperties} />
</If> </If>

View file

@ -9,6 +9,7 @@
import WikidataPreviewBox from "./WikidataPreviewBox" import WikidataPreviewBox from "./WikidataPreviewBox"
import Tr from "../Base/Tr.svelte" import Tr from "../Base/Tr.svelte"
import Translations from "../i18n/Translations" import Translations from "../i18n/Translations"
import Wikipedia from "../../assets/svg/Wikipedia.svelte";
/** /**
* Shows a wikipedia-article + wikidata preview for the given item * Shows a wikipedia-article + wikidata preview for the given item
@ -18,7 +19,7 @@
{#if $wikipediaDetails.articleUrl} {#if $wikipediaDetails.articleUrl}
<a class="flex" href={$wikipediaDetails.articleUrl} rel="noreferrer" target="_blank"> <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} /> <Tr t={Translations.t.general.wikipedia.fromWikipedia} />
</a> </a>
{/if} {/if}

View file

@ -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>

View file

@ -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>

View file

@ -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

View file

@ -4,14 +4,16 @@ import { describe, it } from "vitest"
import { parse as parse_html } from "node-html-parser" import { parse as parse_html } from "node-html-parser"
import { readFileSync } from "fs" import { readFileSync } from "fs"
import ScriptUtils from "../scripts/ScriptUtils" 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 forbidden: a GREP-regex. This means that '.' is a wildcard and should be escaped to match a literal dot
* @param reason * @param reason
* @private * @private
*/ */
function detectInCode(forbidden: string, reason: string): Promise<void> { function detectInCodeUnwrapped(forbidden: string, reason: string): Promise<void> {
return new Promise<void>((done) => { return new Promise<void>((done) => {
const excludedDirs = [ const excludedDirs = [
".git", ".git",
@ -24,37 +26,35 @@ function detectInCode(forbidden: string, reason: string): Promise<void> {
".idea/", ".idea/",
] ]
exec( const command =
'grep -n "' + 'grep -n "' +
forbidden + forbidden +
'" -r . ' + '" -r . ' +
excludedDirs.map((d) => "--exclude-dir=" + d).join(" "), excludedDirs.map((d) => "--exclude-dir=" + d).join(" ")
(error, stdout, stderr) => { console.log(command)
if (error?.message?.startsWith("Command failed: grep")) { exec(command, (error, stdout, stderr) => {
console.warn("Command failed!", error) if (error?.message?.startsWith("Command failed: grep")) {
return console.warn("Command failed!", error)
} throw error
if (error !== null) {
throw error
}
if (stderr !== "") {
throw stderr
}
const found = stdout
.split("\n")
.filter((s) => s !== "")
.filter((s) => !s.startsWith("./test/"))
if (found.length > 0) {
const msg = `Found a '${forbidden}' at \n ${found.join(
"\n "
)}.\n ${reason}`
console.error(msg)
console.error(found.length, "issues found")
throw msg
}
} }
) if (error !== null) {
throw error
}
if (stderr !== "") {
throw stderr
}
const found = stdout
.split("\n")
.filter((s) => s !== "")
.filter((s) => !s.startsWith("./test/"))
if (found.length > 0) {
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) { function validateScriptIntegrityOf(path: string) {
const htmlContents = readFileSync(path, "utf8") const htmlContents = readFileSync(path, "utf8")
const doc = parse_html(htmlContents) const doc = parse_html(htmlContents)
@ -95,7 +91,7 @@ function validateScriptIntegrityOf(path: string) {
} }
describe("Code quality", () => { describe("Code quality", () => {
itAsync( it(
"should not contain reverse", "should not contain reverse",
detectInCode( detectInCode(
"reverse()", "reverse()",
@ -103,12 +99,12 @@ describe("Code quality", () => {
) )
) )
itAsync( it(
"should not contain 'constructor.name'", "should not contain 'constructor.name'",
detectInCode("constructor\\.name", "This is not allowed, as minification does erase names.") detectInCode("constructor\\.name", "This is not allowed, as minification does erase names.")
) )
itAsync( it(
"should not contain 'innerText'", "should not contain 'innerText'",
detectInCode( detectInCode(
"innerText", "innerText",