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"
]
},
"then": "./assets/layers/id_presets/fas-key.svg"
"then": "circle:white;./assets/layers/id_presets/fas-key.svg"
}
],
"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"
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")

View file

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

View file

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

View file

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

View file

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

View file

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

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

View file

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

View file

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

View file

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

View file

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

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

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 { 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,37 +26,35 @@ 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) => {
if (error?.message?.startsWith("Command failed: grep")) {
console.warn("Command failed!", error)
return
}
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
}
forbidden +
'" -r . ' +
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)
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
}
})
})
}
@ -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",