diff --git a/.gitignore b/.gitignore index 6770a760a..3eea34f06 100644 --- a/.gitignore +++ b/.gitignore @@ -36,3 +36,5 @@ service-worker.js # Built Visual Studio Code Extensions *.vsix +public/*.webmanifest +public/assets/generated/ diff --git a/Customizations/AllKnownLayouts.ts b/Customizations/AllKnownLayouts.ts index 7a7ec06f0..e5255018a 100644 --- a/Customizations/AllKnownLayouts.ts +++ b/Customizations/AllKnownLayouts.ts @@ -1,4 +1,4 @@ -import * as known_themes from "../assets/generated/known_layers_and_themes.json" +import known_themes from "../assets/generated/known_layers_and_themes.json" import LayoutConfig from "../Models/ThemeConfig/LayoutConfig" import LayerConfig from "../Models/ThemeConfig/LayerConfig" import BaseUIElement from "../UI/BaseUIElement" diff --git a/Customizations/SharedTagRenderings.ts b/Customizations/SharedTagRenderings.ts index 9938c7813..11b8954f4 100644 --- a/Customizations/SharedTagRenderings.ts +++ b/Customizations/SharedTagRenderings.ts @@ -1,5 +1,5 @@ -import * as questions from "../assets/tagRenderings/questions.json" -import * as icons from "../assets/tagRenderings/icons.json" +import questions from "../assets/tagRenderings/questions.json" +import icons from "../assets/tagRenderings/icons.json" import { Utils } from "../Utils" import TagRenderingConfig from "../Models/ThemeConfig/TagRenderingConfig" import { TagRenderingConfigJson } from "../Models/ThemeConfig/Json/TagRenderingConfigJson" diff --git a/Logic/Actors/GeoLocationHandler.ts b/Logic/Actors/GeoLocationHandler.ts index 6f702b768..9c6fbce3b 100644 --- a/Logic/Actors/GeoLocationHandler.ts +++ b/Logic/Actors/GeoLocationHandler.ts @@ -45,13 +45,10 @@ export default class GeoLocationHandler { (new Date().getTime() - geolocationState.requestMoment.data?.getTime() ?? 0) / 1000 if (!this.mapHasMoved.data) { // The map hasn't moved yet; we received our first coordinates, so let's move there! - console.log( - "Moving the map to an initial location; time since last request is", - timeSinceLastRequest - ) - if (timeSinceLastRequest < Constants.zoomToLocationTimeout) { - self.MoveMapToCurrentLocation() - } + self.MoveMapToCurrentLocation() + } + if (timeSinceLastRequest < Constants.zoomToLocationTimeout) { + self.MoveMapToCurrentLocation() } if (this.geolocationState.isLocked.data) { @@ -109,11 +106,12 @@ export default class GeoLocationHandler { } mapLocation.setData({ - zoom: mapLocation.data.zoom, + zoom: Math.max(mapLocation.data.zoom, 16), lon: newLocation.longitude, lat: newLocation.latitude, }) this.mapHasMoved.setData(true) + this.geolocationState.requestMoment.setData(undefined) } private CopyGeolocationIntoMapstate() { diff --git a/Logic/Actors/SelectedFeatureHandler.ts b/Logic/Actors/SelectedFeatureHandler.ts index db6776fec..3591bc4eb 100644 --- a/Logic/Actors/SelectedFeatureHandler.ts +++ b/Logic/Actors/SelectedFeatureHandler.ts @@ -90,12 +90,12 @@ export default class SelectedFeatureHandler { if (feature === undefined) { return } - const currentlySeleced = state.selectedElement.data - if (currentlySeleced === undefined) { + const currentlySelected = state.selectedElement.data + if (currentlySelected === undefined) { state.selectedElement.setData(feature) return } - if (currentlySeleced.properties?.id === feature.properties.id) { + if (currentlySelected.properties?.id === feature.properties.id) { // We already have the right feature return } diff --git a/Logic/DetermineLayout.ts b/Logic/DetermineLayout.ts index ffc69b4be..072581d46 100644 --- a/Logic/DetermineLayout.ts +++ b/Logic/DetermineLayout.ts @@ -12,9 +12,9 @@ import LZString from "lz-string" import { FixLegacyTheme } from "../Models/ThemeConfig/Conversion/LegacyJsonConvert" import { LayerConfigJson } from "../Models/ThemeConfig/Json/LayerConfigJson" import SharedTagRenderings from "../Customizations/SharedTagRenderings" -import * as known_layers from "../assets/generated/known_layers.json" +import known_layers from "../assets/generated/known_layers.json" import { PrepareTheme } from "../Models/ThemeConfig/Conversion/PrepareTheme" -import * as licenses from "../assets/generated/license_info.json" +import licenses from "../assets/generated/license_info.json" import TagRenderingConfig from "../Models/ThemeConfig/TagRenderingConfig" import { FixImages } from "../Models/ThemeConfig/Conversion/FixImages" import Svg from "../Svg" diff --git a/Logic/MetaTagging.ts b/Logic/MetaTagging.ts index 10d5c285c..47d698ac0 100644 --- a/Logic/MetaTagging.ts +++ b/Logic/MetaTagging.ts @@ -234,7 +234,6 @@ export default class MetaTagging { for (const f of functions) { f(feature) } - state?.allElements?.getEventSourceById(feature.properties.id)?.ping() } catch (e) { console.error("Invalid syntax in calculated tags or some other error: ", e) } diff --git a/Logic/Osm/OsmObject.ts b/Logic/Osm/OsmObject.ts index 7b1a729c1..298277894 100644 --- a/Logic/Osm/OsmObject.ts +++ b/Logic/Osm/OsmObject.ts @@ -1,5 +1,5 @@ import { Utils } from "../../Utils" -import * as polygon_features from "../../assets/polygon-features.json" +import polygon_features from "../../assets/polygon-features.json" import { Store, UIEventSource } from "../UIEventSource" import { BBox } from "../BBox" import OsmToGeoJson from "osmtogeojson" @@ -290,7 +290,7 @@ export abstract class OsmObject { { values: Set; blacklist: boolean } > { const result = new Map; blacklist: boolean }>() - for (const polygonFeature of polygon_features["default"] ?? polygon_features) { + for (const polygonFeature of polygon_features) { const key = polygonFeature.key if (polygonFeature.polygon === "all") { diff --git a/Logic/SimpleMetaTagger.ts b/Logic/SimpleMetaTagger.ts index b427291ee..f39963ead 100644 --- a/Logic/SimpleMetaTagger.ts +++ b/Logic/SimpleMetaTagger.ts @@ -33,6 +33,10 @@ export class SimpleMetaTagger { docs: { keys: string[] doc: string + /** + * Set this flag if the data is volatile or date-based. + * It'll _won't_ be cached in this case + */ includesDates?: boolean isLazy?: boolean cleanupRetagger?: boolean @@ -492,6 +496,7 @@ export default class SimpleMetaTaggers { { keys: ["_referencing_ways"], isLazy: true, + includesDates: true, doc: "_referencing_ways contains - for a node - which ways use this this node as point in their geometry. ", }, (feature, _, __, state) => { diff --git a/Logic/State/GeoLocationState.ts b/Logic/State/GeoLocationState.ts index c010d1b78..b6bd13e24 100644 --- a/Logic/State/GeoLocationState.ts +++ b/Logic/State/GeoLocationState.ts @@ -1,5 +1,6 @@ import { UIEventSource } from "../UIEventSource" import { LocalStorageSource } from "../Web/LocalStorageSource" +import { QueryParameters } from "../Web/QueryParameters" type GeolocationState = "prompt" | "requested" | "granted" | "denied" @@ -11,8 +12,6 @@ export interface GeoLocationPointProperties extends GeolocationCoordinates { /** * An abstract representation of the current state of the geolocation. - * - * */ export class GeoLocationState { /** @@ -21,10 +20,12 @@ export class GeoLocationState { * 'requested' means the user tapped the 'locate me' button at least once * 'granted' means that it is granted * 'denied' means that we don't have access - * */ public readonly permission: UIEventSource = new UIEventSource("prompt") + /** + * Important to determine e.g. if we move automatically on fix or not + */ public readonly requestMoment: UIEventSource = new UIEventSource(undefined) /** * If true: the map will center (and re-center) to this location @@ -79,6 +80,11 @@ export class GeoLocationState { // We set the flag to false again. If the user only wanted to share their location once, we are not gonna keep bothering them this._previousLocationGrant.setData("false") console.log("Requesting access to GPS as this was previously granted") + const latLonGivenViaUrl = + QueryParameters.wasInitialized("lat") || QueryParameters.wasInitialized("lon") + if (!latLonGivenViaUrl) { + this.requestMoment.setData(new Date()) + } this.requestPermission() } window["geolocation_state"] = this @@ -120,7 +126,7 @@ export class GeoLocationState { // Hence that we continue the flow if it is "requested" return } - this.requestMoment.setData(new Date()) + this.permission.setData("requested") try { navigator?.permissions diff --git a/Logic/State/UserRelatedState.ts b/Logic/State/UserRelatedState.ts index 2db745d20..ba5f4cab6 100644 --- a/Logic/State/UserRelatedState.ts +++ b/Logic/State/UserRelatedState.ts @@ -1,7 +1,7 @@ import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig" import { OsmConnection } from "../Osm/OsmConnection" import { MangroveIdentity } from "../Web/MangroveReviews" -import { Store } from "../UIEventSource" +import { Store, UIEventSource } from "../UIEventSource" import { QueryParameters } from "../Web/QueryParameters" import Locale from "../../UI/i18n/Locale" import ElementsState from "./ElementsState" @@ -9,7 +9,6 @@ import SelectedElementTagsUpdater from "../Actors/SelectedElementTagsUpdater" import { Changes } from "../Osm/Changes" import ChangeToElementsActor from "../Actors/ChangeToElementsActor" import PendingChangesUploader from "../Actors/PendingChangesUploader" -import * as translators from "../../assets/translators.json" import Maproulette from "../Maproulette" /** @@ -53,29 +52,25 @@ export default class UserRelatedState extends ElementsState { osmConfiguration: <"osm" | "osm-test">this.featureSwitchApiURL.data, attemptLogin: options?.attemptLogin, }) - const translationMode = this.osmConnection.GetPreference("translation-mode").sync( - (str) => (str === undefined ? undefined : str === "true"), - [], - (b) => (b === undefined ? undefined : b + "") - ) - - translationMode.syncWith(Locale.showLinkToWeblate) - - this.isTranslator = this.osmConnection.userDetails.map((ud) => { - if (!ud.loggedIn) { - return false - } - const name = ud.name.toLowerCase().replace(/\s+/g, "") - return translators.contributors.some( - (c) => c.contributor.toLowerCase().replace(/\s+/g, "") === name - ) - }) - - this.isTranslator.addCallbackAndRunD((ud) => { - if (ud) { - Locale.showLinkToWeblate.setData(true) - } - }) + { + const translationMode: UIEventSource = + this.osmConnection.GetPreference("translation-mode") + translationMode.addCallbackAndRunD((mode) => { + mode = mode.toLowerCase() + if (mode === "true" || mode === "yes") { + Locale.showLinkOnMobile.setData(false) + Locale.showLinkToWeblate.setData(true) + } else if (mode === "false" || mode === "no") { + Locale.showLinkToWeblate.setData(false) + } else if (mode === "mobile") { + Locale.showLinkOnMobile.setData(true) + Locale.showLinkToWeblate.setData(true) + } else { + Locale.showLinkOnMobile.setData(false) + Locale.showLinkToWeblate.setData(false) + } + }) + } this.changes = new Changes(this, layoutToUse?.isLeftRightSensitive() ?? false) @@ -117,41 +112,6 @@ export default class UserRelatedState extends ElementsState { this.installedUserThemes = this.InitInstalledUserThemes() } - private InitializeLanguage() { - const layoutToUse = this.layoutToUse - Locale.language.syncWith(this.osmConnection.GetPreference("language")) - Locale.language.addCallback((currentLanguage) => { - if (layoutToUse === undefined) { - return - } - if (Locale.showLinkToWeblate.data) { - return true // Disable auto switching as we are in translators mode - } - if (this.layoutToUse.language.indexOf(currentLanguage) < 0) { - console.log( - "Resetting language to", - layoutToUse.language[0], - "as", - currentLanguage, - " is unsupported" - ) - // The current language is not supported -> switch to a supported one - Locale.language.setData(layoutToUse.language[0]) - } - }) - Locale.language.ping() - } - - private InitInstalledUserThemes(): Store { - const prefix = "mapcomplete-unofficial-theme-" - const postfix = "-combined-length" - return this.osmConnection.preferencesHandler.preferences.map((prefs) => - Object.keys(prefs) - .filter((k) => k.startsWith(prefix) && k.endsWith(postfix)) - .map((k) => k.substring(prefix.length, k.length - postfix.length)) - ) - } - public GetUnofficialTheme(id: string): | { id: string @@ -193,4 +153,39 @@ export default class UserRelatedState extends ElementsState { return undefined } } + + private InitializeLanguage() { + const layoutToUse = this.layoutToUse + Locale.language.syncWith(this.osmConnection.GetPreference("language")) + Locale.language.addCallback((currentLanguage) => { + if (layoutToUse === undefined) { + return + } + if (Locale.showLinkToWeblate.data) { + return true // Disable auto switching as we are in translators mode + } + if (this.layoutToUse.language.indexOf(currentLanguage) < 0) { + console.log( + "Resetting language to", + layoutToUse.language[0], + "as", + currentLanguage, + " is unsupported" + ) + // The current language is not supported -> switch to a supported one + Locale.language.setData(layoutToUse.language[0]) + } + }) + Locale.language.ping() + } + + private InitInstalledUserThemes(): Store { + const prefix = "mapcomplete-unofficial-theme-" + const postfix = "-combined-length" + return this.osmConnection.preferencesHandler.preferences.map((prefs) => + Object.keys(prefs) + .filter((k) => k.startsWith(prefix) && k.endsWith(postfix)) + .map((k) => k.substring(prefix.length, k.length - postfix.length)) + ) + } } diff --git a/Logic/Tags/TagUtils.ts b/Logic/Tags/TagUtils.ts index b66828692..b0cc2989b 100644 --- a/Logic/Tags/TagUtils.ts +++ b/Logic/Tags/TagUtils.ts @@ -7,13 +7,13 @@ import { RegexTag } from "./RegexTag" import SubstitutingTag from "./SubstitutingTag" import { Or } from "./Or" import { TagConfigJson } from "../../Models/ThemeConfig/Json/TagConfigJson" -import * as key_counts from "../../assets/key_totals.json" +import key_counts from "../../assets/key_totals.json" type Tags = Record export type UploadableTag = Tag | SubstitutingTag | And export class TagUtils { - private static keyCounts: { keys: any; tags: any } = key_counts["default"] ?? key_counts + private static keyCounts: { keys: any; tags: any } = key_counts private static comparators: [string, (a: number, b: number) => boolean][] = [ ["<=", (a, b) => a <= b], [">=", (a, b) => a >= b], diff --git a/Models/Constants.ts b/Models/Constants.ts index 73d99d66a..54004ac9b 100644 --- a/Models/Constants.ts +++ b/Models/Constants.ts @@ -106,7 +106,7 @@ export default class Constants { * * In seconds */ - static zoomToLocationTimeout = 60 + static zoomToLocationTimeout = 15 static countryCoderEndpoint: string = "https://raw.githubusercontent.com/pietervdvn/MapComplete-data/main/latlon2country" public static readonly OsmPreferenceKeyPicturesLicense = "pictures-license" diff --git a/Models/Denomination.ts b/Models/Denomination.ts index cddd0ac7d..f3466515b 100644 --- a/Models/Denomination.ts +++ b/Models/Denomination.ts @@ -31,7 +31,7 @@ export class Denomination { this.alternativeDenominations = json.alternativeDenomination?.map((v) => v.trim()) ?? [] - if (json["default"] !== undefined) { + if (json["default" /* @code-quality: ignore*/] !== undefined) { throw `${context} uses the old 'default'-key. Use "useIfNoUnitGiven" or "useAsDefaultInput" instead` } this.useIfNoUnitGiven = json.useIfNoUnitGiven diff --git a/Models/ThemeConfig/Conversion/FixImages.ts b/Models/ThemeConfig/Conversion/FixImages.ts index fbd7d07c2..a873abb6d 100644 --- a/Models/ThemeConfig/Conversion/FixImages.ts +++ b/Models/ThemeConfig/Conversion/FixImages.ts @@ -1,21 +1,20 @@ import { Conversion, DesugaringStep } from "./Conversion" import { LayoutConfigJson } from "../Json/LayoutConfigJson" import { Utils } from "../../../Utils" -import * as metapaths from "../../../assets/layoutconfigmeta.json" -import * as tagrenderingmetapaths from "../../../assets/questionabletagrenderingconfigmeta.json" +import metapaths from "../../../assets/layoutconfigmeta.json" +import tagrenderingmetapaths from "../../../assets/questionabletagrenderingconfigmeta.json" import Translations from "../../../UI/i18n/Translations" export class ExtractImages extends Conversion { private _isOfficial: boolean private _sharedTagRenderings: Map - private static readonly layoutMetaPaths = (metapaths["default"] ?? metapaths).filter( + private static readonly layoutMetaPaths = metapaths.filter( (mp) => - ExtractImages.mightBeTagRendering(mp) || + ExtractImages.mightBeTagRendering(mp) || (mp.typeHint !== undefined && (mp.typeHint === "image" || mp.typeHint === "icon")) ) - private static readonly tagRenderingMetaPaths = - tagrenderingmetapaths["default"] ?? tagrenderingmetapaths + private static readonly tagRenderingMetaPaths = tagrenderingmetapaths constructor(isOfficial: boolean, sharedTagRenderings: Map) { super("Extract all images from a layoutConfig using the meta paths.", [], "ExctractImages") @@ -23,14 +22,16 @@ export class ExtractImages extends Conversion { this._sharedTagRenderings = sharedTagRenderings } - public static mightBeTagRendering(metapath: { type: string | string[] }): boolean { + public static mightBeTagRendering(metapath: { type?: string | string[] }): boolean { if (!Array.isArray(metapath.type)) { return false } - return metapath.type.some( - (t) => - t["$ref"] == "#/definitions/TagRenderingConfigJson" || - t["$ref"] == "#/definitions/QuestionableTagRenderingConfigJson" + return ( + metapath.type?.some( + (t) => + t["$ref"] == "#/definitions/TagRenderingConfigJson" || + t["$ref"] == "#/definitions/QuestionableTagRenderingConfigJson" + ) ?? false ) } @@ -83,7 +84,7 @@ export class ExtractImages extends Conversion { const errors = [] const warnings = [] for (const metapath of ExtractImages.layoutMetaPaths) { - const mightBeTr = ExtractImages.mightBeTagRendering(metapath) + const mightBeTr = ExtractImages.mightBeTagRendering(metapath) const allRenderedValuesAreImages = metapath.typeHint === "icon" || metapath.typeHint === "image" const found = Utils.CollectPath(metapath.path, json) @@ -271,14 +272,11 @@ export class FixImages extends DesugaringStep { json = Utils.Clone(json) - let paths = metapaths["default"] ?? metapaths - let trpaths = tagrenderingmetapaths["default"] ?? tagrenderingmetapaths - - for (const metapath of paths) { + for (const metapath of metapaths) { if (metapath.typeHint !== "image" && metapath.typeHint !== "icon") { continue } - const mightBeTr = ExtractImages.mightBeTagRendering(metapath) + const mightBeTr = ExtractImages.mightBeTagRendering(metapath) Utils.WalkPath(metapath.path, json, (leaf, path) => { if (typeof leaf === "string") { return replaceString(leaf) @@ -287,7 +285,7 @@ export class FixImages extends DesugaringStep { if (mightBeTr) { // We might have reached a tagRenderingConfig containing icons // lets walk every rendered value and fix the images in there - for (const trpath of trpaths) { + for (const trpath of tagrenderingmetapaths) { if (trpath.typeHint !== "rendered") { continue } diff --git a/Models/ThemeConfig/Conversion/PrepareLayer.ts b/Models/ThemeConfig/Conversion/PrepareLayer.ts index 134a74067..fb00edb80 100644 --- a/Models/ThemeConfig/Conversion/PrepareLayer.ts +++ b/Models/ThemeConfig/Conversion/PrepareLayer.ts @@ -16,10 +16,10 @@ import RewritableConfigJson from "../Json/RewritableConfigJson" import SpecialVisualizations from "../../../UI/SpecialVisualizations" import Translations from "../../../UI/i18n/Translations" import { Translation } from "../../../UI/i18n/Translation" -import * as tagrenderingconfigmeta from "../../../assets/tagrenderingconfigmeta.json" +import tagrenderingconfigmeta from "../../../assets/tagrenderingconfigmeta.json" import { AddContextToTranslations } from "./AddContextToTranslations" import FilterConfigJson from "../Json/FilterConfigJson" -import * as predifined_filters from "../../../assets/layers/filters/filters.json" +import predifined_filters from "../../../assets/layers/filters/filters.json" class ExpandFilter extends DesugaringStep { private static load_filters(): Map { @@ -730,8 +730,7 @@ export class RewriteSpecial extends DesugaringStep { } { const errors = [] json = Utils.Clone(json) - const paths: { path: string[]; type?: any; typeHint?: string }[] = - tagrenderingconfigmeta["default"] ?? tagrenderingconfigmeta + const paths: { path: string[]; type?: any; typeHint?: string }[] = tagrenderingconfigmeta for (const path of paths) { if (path.typeHint !== "rendered") { continue diff --git a/Models/ThemeConfig/Conversion/Validation.ts b/Models/ThemeConfig/Conversion/Validation.ts index 9fec8d940..6e366b64b 100644 --- a/Models/ThemeConfig/Conversion/Validation.ts +++ b/Models/ThemeConfig/Conversion/Validation.ts @@ -9,11 +9,9 @@ import LayoutConfig from "../LayoutConfig" import { TagRenderingConfigJson } from "../Json/TagRenderingConfigJson" import { TagUtils } from "../../../Logic/Tags/TagUtils" import { ExtractImages } from "./FixImages" -import ScriptUtils from "../../../scripts/ScriptUtils" import { And } from "../../../Logic/Tags/And" import Translations from "../../../UI/i18n/Translations" import Svg from "../../../Svg" -import { QuestionableTagRenderingConfigJson } from "../Json/QuestionableTagRenderingConfigJson" import FilterConfigJson from "../Json/FilterConfigJson" import DeleteConfig from "../DeleteConfig" @@ -365,7 +363,7 @@ export class PrevalidateTheme extends Fuse { } } -export class DetectShadowedMappings extends DesugaringStep { +export class DetectShadowedMappings extends DesugaringStep { private readonly _calculatedTagNames: string[] constructor(layerConfig?: LayerConfigJson) { @@ -425,9 +423,9 @@ export class DetectShadowedMappings extends DesugaringStep= 0 // => true */ convert( - json: QuestionableTagRenderingConfigJson, + json: TagRenderingConfigJson, context: string - ): { result: QuestionableTagRenderingConfigJson; errors?: string[]; warnings?: string[] } { + ): { result: TagRenderingConfigJson; errors?: string[]; warnings?: string[] } { const errors = [] const warnings = [] if (json.mappings === undefined || json.mappings.length === 0) { @@ -441,12 +439,9 @@ export class DetectShadowedMappings extends DesugaringStep { const ctx = `${context}.mappings[${i}]` const ifTags = TagUtils.Tag(m.if, ctx) - if ( - m.hideInAnswer !== undefined && - m.hideInAnswer !== false && - m.hideInAnswer !== true - ) { - let conditionTags = TagUtils.Tag(m.hideInAnswer) + const hideInAnswer = m["hideInAnswer"] + if (hideInAnswer !== undefined && hideInAnswer !== false && hideInAnswer !== true) { + let conditionTags = TagUtils.Tag(hideInAnswer) // Merge the condition too! return new And([conditionTags, ifTags]) } @@ -467,8 +462,8 @@ export class DetectShadowedMappings extends DesugaringStep { super( "Various validation on tagRenderingConfigs", new DetectShadowedMappings(layerConfig), - new DetectMappingsWithImages(doesImageExist) + new DetectMappingsWithImages(doesImageExist), + new MiscTagRenderingChecks() ) } } diff --git a/Models/ThemeConfig/Json/QuestionableTagRenderingConfigJson.ts b/Models/ThemeConfig/Json/QuestionableTagRenderingConfigJson.ts index a435d0502..89f97acf2 100644 --- a/Models/ThemeConfig/Json/QuestionableTagRenderingConfigJson.ts +++ b/Models/ThemeConfig/Json/QuestionableTagRenderingConfigJson.ts @@ -26,7 +26,7 @@ export interface MappingConfigJson { /** * Size of the image */ - class: "small" | "medium" | "large" | string + class?: "small" | "medium" | "large" | string } /** diff --git a/Models/ThemeConfig/Json/TagRenderingConfigJson.ts b/Models/ThemeConfig/Json/TagRenderingConfigJson.ts index 460fa1be2..9f9a6a9af 100644 --- a/Models/ThemeConfig/Json/TagRenderingConfigJson.ts +++ b/Models/ThemeConfig/Json/TagRenderingConfigJson.ts @@ -125,7 +125,7 @@ export interface TagRenderingConfigJson { * A hint to mapcomplete on how to render this icon within the mapping. * This is translated to 'mapping-icon-', so defining your own in combination with a custom CSS is possible (but discouraged) */ - class: "small" | "medium" | "large" | string + class?: "small" | "medium" | "large" | string } }[] } diff --git a/Models/ThemeConfig/LayoutConfig.ts b/Models/ThemeConfig/LayoutConfig.ts index 6c63525d8..fdca8d638 100644 --- a/Models/ThemeConfig/LayoutConfig.ts +++ b/Models/ThemeConfig/LayoutConfig.ts @@ -7,7 +7,7 @@ import TilesourceConfig from "./TilesourceConfig" import { ExtractImages } from "./Conversion/FixImages" import ExtraLinkConfig from "./ExtraLinkConfig" import { Utils } from "../../Utils" -import * as used_languages from "../../assets/generated/used_languages.json" +import used_languages from "../../assets/generated/used_languages.json" export default class LayoutConfig { public static readonly defaultSocialImage = "assets/SocialImage.png" public readonly id: string @@ -237,13 +237,11 @@ export default class LayoutConfig { } public missingTranslations(): { - completeness: Map untranslated: Map total: number } { const layout = this let total = 0 - const completeness = new Map() const untranslated = new Map() Utils.WalkObject( @@ -264,13 +262,21 @@ export default class LayoutConfig { if (trans["*"] !== undefined) { return } + if (translation.context.indexOf(":") < 0) { + return + } if (trans[ln] === undefined) { if (!untranslated.has(ln)) { untranslated.set(ln, []) } - untranslated.get(ln).push(translation.context) - } else { - completeness.set(ln, 1 + (completeness.get(ln) ?? 0)) + untranslated + .get(ln) + .push( + translation.context.replace( + /^note_import_[a-zA-Z0-9_]*/, + "note_import" + ) + ) } }) }, @@ -282,7 +288,7 @@ export default class LayoutConfig { } ) - return { completeness, untranslated, total } + return { untranslated, total } } public getMatchingLayer(tags: any): LayerConfig | undefined { if (tags === undefined) { diff --git a/UI/AutomatonGui.ts b/UI/AutomatonGui.ts index 8a0233d86..1a8b446a8 100644 --- a/UI/AutomatonGui.ts +++ b/UI/AutomatonGui.ts @@ -27,7 +27,7 @@ import { QueryParameters } from "../Logic/Web/QueryParameters" import { SubstitutedTranslation } from "./SubstitutedTranslation" import { AutoAction } from "./Popup/AutoApplyButton" import DynamicGeoJsonTileSource from "../Logic/FeatureSource/TiledFeatureSource/DynamicGeoJsonTileSource" -import * as themeOverview from "../assets/generated/theme_overview.json" +import themeOverview from "../assets/generated/theme_overview.json" class AutomationPanel extends Combine { private static readonly openChangeset = new UIEventSource(undefined) diff --git a/UI/Base/LinkToWeblate.ts b/UI/Base/LinkToWeblate.ts index e6bc59a90..73afabb56 100644 --- a/UI/Base/LinkToWeblate.ts +++ b/UI/Base/LinkToWeblate.ts @@ -2,6 +2,7 @@ import { VariableUiElement } from "./VariableUIElement" import Locale from "../i18n/Locale" import Link from "./Link" import Svg from "../../Svg" +import show = Mocha.reporters.Base.cursor.show /** * The little 'translate'-icon next to every icon + some static helper functions @@ -21,7 +22,7 @@ export default class LinkToWeblate extends VariableUiElement { return undefined } const icon = Svg.translate_svg().SetClass( - "rounded-full border border-gray-400 inline-block w-4 h-4 m-1 weblate-link self-center" + "rounded-full inline-block w-3 h-3 ml-1 weblate-link self-center" ) if (availableTranslations[ln] === undefined) { icon.SetClass("bg-red-400") @@ -31,7 +32,15 @@ export default class LinkToWeblate extends VariableUiElement { [Locale.showLinkToWeblate] ) ) - this.SetClass("enable-links hidden-on-mobile") + this.SetClass("enable-links") + const self = this + Locale.showLinkOnMobile.addCallbackAndRunD((showOnMobile) => { + if (showOnMobile) { + self.RemoveClass("hidden-on-mobile") + } else { + self.SetClass("hidden-on-mobile") + } + }) } /** diff --git a/UI/Base/ScrollableFullScreen.ts b/UI/Base/ScrollableFullScreen.ts index 9dd6a83c8..6a9261d94 100644 --- a/UI/Base/ScrollableFullScreen.ts +++ b/UI/Base/ScrollableFullScreen.ts @@ -143,7 +143,7 @@ export default class ScrollableFullScreen { ) const contentWrapper = new Combine([content]).SetClass( - "block p-2 md:pt-4 w-full h-full overflow-y-auto desktop:max-h-65vh" + "block p-2 md:pt-4 w-full h-full overflow-y-auto" ) this._resetScrollSignal.addCallback((_) => { @@ -159,7 +159,7 @@ export default class ScrollableFullScreen { // We add an ornament which takes around 5em. This is in order to make sure the Web UI doesn't hide ]).SetClass("flex flex-col h-full relative bg-white"), ]).SetClass( - "fixed top-0 left-0 right-0 h-screen w-screen desktop:max-h-65vh md:w-auto md:relative z-above-controls md:rounded-xl overflow-hidden" + "fixed top-0 left-0 right-0 h-screen w-screen md:w-auto md:relative z-above-controls md:rounded-xl overflow-hidden" ) } diff --git a/UI/BigComponents/ActionButtons.ts b/UI/BigComponents/ActionButtons.ts index cb492a80b..238279179 100644 --- a/UI/BigComponents/ActionButtons.ts +++ b/UI/BigComponents/ActionButtons.ts @@ -9,9 +9,10 @@ import { SubtleButton } from "../Base/SubtleButton" import Svg from "../../Svg" import { Utils } from "../../Utils" import { MapillaryLink } from "./MapillaryLink" -import TranslatorsPanel from "./TranslatorsPanel" import { OpenIdEditor, OpenJosm } from "./CopyrightPanel" import Toggle from "../Input/Toggle" +import ScrollableFullScreen from "../Base/ScrollableFullScreen" +import { DefaultGuiState } from "../DefaultGuiState" export class BackToThemeOverview extends Toggle { constructor( @@ -77,7 +78,14 @@ export class ActionButtons extends Combine { new OpenIdEditor(state, iconStyle), new MapillaryLink(state, iconStyle), new OpenJosm(state, iconStyle).SetClass("hidden-on-mobile"), - new TranslatorsPanel(state, iconStyle), + new SubtleButton( + Svg.translate_ui().SetStyle(iconStyle), + Translations.t.translations.activateButton + ).onClick(() => { + ScrollableFullScreen.collapse() + DefaultGuiState.state.userInfoIsOpened.setData(true) + DefaultGuiState.state.userInfoFocusedQuestion.setData("translation-mode") + }), ]) this.SetClass("block w-full link-no-underline") } diff --git a/UI/BigComponents/FeaturedMessage.ts b/UI/BigComponents/FeaturedMessage.ts index 086eca256..e1be38f7c 100644 --- a/UI/BigComponents/FeaturedMessage.ts +++ b/UI/BigComponents/FeaturedMessage.ts @@ -1,9 +1,9 @@ import Combine from "../Base/Combine" -import * as welcome_messages from "../../assets/welcome_message.json" +import welcome_messages from "../../assets/welcome_message.json" import BaseUIElement from "../BaseUIElement" import { FixedUiElement } from "../Base/FixedUiElement" import MoreScreen from "./MoreScreen" -import * as themeOverview from "../../assets/generated/theme_overview.json" +import themeOverview from "../../assets/generated/theme_overview.json" import Translations from "../i18n/Translations" import Title from "../Base/Title" @@ -43,7 +43,7 @@ export default class FeaturedMessage extends Combine { }[] = [] const themesById = new Map() - for (const theme of themeOverview["default"]) { + for (const theme of themeOverview) { themesById.set(theme.id, theme) } @@ -88,9 +88,7 @@ export default class FeaturedMessage extends Combine { const msg = new FixedUiElement(welcome_message.message).SetClass("link-underline font-lg") els.push(new Combine([title, msg]).SetClass("m-4")) if (welcome_message.featured_theme !== undefined) { - const theme = themeOverview["default"].filter( - (th) => th.id === welcome_message.featured_theme - )[0] + const theme = themeOverview.filter((th) => th.id === welcome_message.featured_theme)[0] els.push( MoreScreen.createLinkButton({}, theme) diff --git a/UI/BigComponents/GeolocationControl.ts b/UI/BigComponents/GeolocationControl.ts index dfb50fe21..9350d252a 100644 --- a/UI/BigComponents/GeolocationControl.ts +++ b/UI/BigComponents/GeolocationControl.ts @@ -6,6 +6,7 @@ import { BBox } from "../../Logic/BBox" import Loc from "../../Models/Loc" import Hotkeys from "../Base/Hotkeys" import Translations from "../i18n/Translations" +import Constants from "../../Models/Constants" /** * Displays an icon depending on the state of the geolocation. @@ -20,6 +21,9 @@ export class GeolocationControl extends VariableUiElement { } ) { const lastClick = new UIEventSource(undefined) + lastClick.addCallbackD((date) => { + geolocationHandler.geolocationState.requestMoment.setData(date) + }) const lastClickWithinThreeSecs = lastClick.map((lastClick) => { if (lastClick === undefined) { return false @@ -27,6 +31,16 @@ export class GeolocationControl extends VariableUiElement { const timeDiff = (new Date().getTime() - lastClick.getTime()) / 1000 return timeDiff <= 3 }) + const lastRequestWithinTimeout = geolocationHandler.geolocationState.requestMoment.map( + (date) => { + if (date === undefined) { + return false + } + const timeDiff = (new Date().getTime() - date.getTime()) / 1000 + console.log("Timediff", timeDiff) + return timeDiff <= Constants.zoomToLocationTimeout + } + ) const geolocationState = geolocationHandler?.geolocationState super( geolocationState?.permission?.map( @@ -43,9 +57,10 @@ export class GeolocationControl extends VariableUiElement { return Svg.location_empty_svg() } // Position not yet found, but permission is either requested or granted: we spin to indicate activity - const icon = !geolocationHandler.mapHasMoved.data - ? Svg.location_svg() - : Svg.location_empty_svg() + const icon = + !geolocationHandler.mapHasMoved.data || lastRequestWithinTimeout.data + ? Svg.location_svg() + : Svg.location_empty_svg() return icon .SetClass("cursor-wait") .SetStyle("animation: spin 4s linear infinite;") @@ -65,6 +80,7 @@ export class GeolocationControl extends VariableUiElement { geolocationState.isLocked, geolocationHandler.mapHasMoved, lastClickWithinThreeSecs, + lastRequestWithinTimeout, ] ) ) @@ -74,6 +90,8 @@ export class GeolocationControl extends VariableUiElement { geolocationState.permission.data !== "granted" && geolocationState.currentGPSLocation.data === undefined ) { + lastClick.setData(new Date()) + geolocationState.requestMoment.setData(new Date()) await geolocationState.requestPermission() } @@ -85,6 +103,7 @@ export class GeolocationControl extends VariableUiElement { if (geolocationState.currentGPSLocation.data === undefined) { // No location is known yet, not much we can do + lastClick.setData(new Date()) return } @@ -126,5 +145,12 @@ export class GeolocationControl extends VariableUiElement { } }, 500) }) + geolocationHandler.geolocationState.requestMoment.addCallbackAndRunD((_) => { + window.setTimeout(() => { + if (lastRequestWithinTimeout.data) { + geolocationHandler.geolocationState.requestMoment.ping() + } + }, 500) + }) } } diff --git a/UI/BigComponents/MoreScreen.ts b/UI/BigComponents/MoreScreen.ts index 86b74e16a..05775dd47 100644 --- a/UI/BigComponents/MoreScreen.ts +++ b/UI/BigComponents/MoreScreen.ts @@ -3,7 +3,7 @@ import Svg from "../../Svg" import Combine from "../Base/Combine" import { SubtleButton } from "../Base/SubtleButton" import Translations from "../i18n/Translations" -import * as personal from "../../assets/themes/personal/personal.json" +import personal from "../../assets/themes/personal/personal.json" import Constants from "../../Models/Constants" import BaseUIElement from "../BaseUIElement" import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig" @@ -14,7 +14,7 @@ import UserRelatedState from "../../Logic/State/UserRelatedState" import Toggle from "../Input/Toggle" import { Utils } from "../../Utils" import Title from "../Base/Title" -import * as themeOverview from "../../assets/generated/theme_overview.json" +import themeOverview from "../../assets/generated/theme_overview.json" import { Translation } from "../i18n/Translation" import { TextField } from "../Input/TextField" import FilteredCombine from "../Base/FilteredCombine" @@ -30,7 +30,7 @@ export default class MoreScreen extends Combine { mustHaveLanguage?: boolean hideFromOverview: boolean keywors?: any[] - }[] = themeOverview["default"] + }[] = themeOverview constructor( state: UserRelatedState & { @@ -287,7 +287,7 @@ export default class MoreScreen extends Combine { ): BaseUIElement { const t = Translations.t.general.morescreen const prefix = "mapcomplete-hidden-theme-" - const hiddenThemes = themeOverview["default"].filter((layout) => layout.hideFromOverview) + const hiddenThemes = themeOverview.filter((layout) => layout.hideFromOverview) const hiddenTotal = hiddenThemes.length return new Toggle( diff --git a/UI/BigComponents/TranslatorsPanel.ts b/UI/BigComponents/TranslatorsPanel.ts deleted file mode 100644 index 2be3f939a..000000000 --- a/UI/BigComponents/TranslatorsPanel.ts +++ /dev/null @@ -1,148 +0,0 @@ -import Toggle from "../Input/Toggle" -import Lazy from "../Base/Lazy" -import { Utils } from "../../Utils" -import Translations from "../i18n/Translations" -import Combine from "../Base/Combine" -import Locale from "../i18n/Locale" -import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig" -import { Translation } from "../i18n/Translation" -import { VariableUiElement } from "../Base/VariableUIElement" -import Link from "../Base/Link" -import LinkToWeblate from "../Base/LinkToWeblate" -import Toggleable from "../Base/Toggleable" -import Title from "../Base/Title" -import { Store } from "../../Logic/UIEventSource" -import { SubtleButton } from "../Base/SubtleButton" -import Svg from "../../Svg" -import * as native_languages from "../../assets/language_native.json" -import BaseUIElement from "../BaseUIElement" - -class TranslatorsPanelContent extends Combine { - constructor(layout: LayoutConfig, isTranslator: Store) { - const t = Translations.t.translations - - const { completeness, untranslated, total } = layout.missingTranslations() - - const seed = t.completeness - for (const ln of Array.from(completeness.keys())) { - if (ln === "*") { - continue - } - if (seed.translations[ln] === undefined) { - seed.translations[ln] = seed.translations["en"] - } - } - - const completenessTr = {} - const completenessPercentage = {} - seed.SupportedLanguages().forEach((ln) => { - completenessTr[ln] = "" + (completeness.get(ln) ?? 0) - completenessPercentage[ln] = - "" + Math.round((100 * (completeness.get(ln) ?? 0)) / total) - }) - - function missingTranslationsFor(language: string): BaseUIElement[] { - // e.g. "themes:.layers.0.tagRenderings..., or "layers:.description - const missingKeys = Utils.NoNull(untranslated.get(language) ?? []) - .filter((ctx) => ctx.indexOf(":") >= 0) - .map((ctx) => ctx.replace(/note_import_[a-zA-Z0-9_]*/, "note_import")) - - const hasMissingTheme = missingKeys.some((k) => k.startsWith("themes:")) - const missingLayers = Utils.Dedup( - missingKeys - .filter((k) => k.startsWith("layers:")) - .map((k) => k.slice("layers:".length).split(".")[0]) - ) - - console.log( - "Getting untranslated string for", - language, - "raw:", - missingKeys, - "hasMissingTheme:", - hasMissingTheme, - "missingLayers:", - missingLayers - ) - return Utils.NoNull([ - hasMissingTheme - ? new Link( - "themes:" + layout.id + ".* (zen mode)", - LinkToWeblate.hrefToWeblateZen(language, "themes", layout.id), - true - ) - : undefined, - ...missingLayers.map( - (id) => - new Link( - "layer:" + id + ".* (zen mode)", - LinkToWeblate.hrefToWeblateZen(language, "layers", id), - true - ) - ), - ...missingKeys.map( - (context) => - new Link(context, LinkToWeblate.hrefToWeblate(language, context), true) - ), - ]) - } - - // "translationCompleteness": "Translations for {theme} in {language} are at {percentage}: {translated} out of {total}", - const translated = seed.Subs({ - total, - theme: layout.title, - percentage: new Translation(completenessPercentage), - translated: new Translation(completenessTr), - language: seed.OnEveryLanguage((_, lng) => native_languages[lng] ?? lng), - }) - - super([ - new Title(Translations.t.translations.activateButton), - new Toggle(t.isTranslator.SetClass("thanks block"), undefined, isTranslator), - t.help, - translated, - /*Disable button:*/ - new SubtleButton(undefined, t.deactivate).onClick(() => { - Locale.showLinkToWeblate.setData(false) - }), - - new VariableUiElement( - Locale.language.map((ln) => { - const missing = missingTranslationsFor(ln) - if (missing.length === 0) { - return undefined - } - let title = Translations.t.translations.allMissing - if (untranslated.get(ln) !== undefined) { - title = Translations.t.translations.missing.Subs({ - count: untranslated.get(ln).length, - }) - } - return new Toggleable( - new Title(title), - new Combine(missing).SetClass("flex flex-col") - ) - }) - ), - ]) - } -} - -export default class TranslatorsPanel extends Toggle { - constructor( - state: { layoutToUse: LayoutConfig; isTranslator: Store }, - iconStyle?: string - ) { - const t = Translations.t.translations - super( - new Lazy( - () => new TranslatorsPanelContent(state.layoutToUse, state.isTranslator) - ).SetClass("flex flex-col"), - new SubtleButton(Svg.translate_ui().SetStyle(iconStyle), t.activateButton).onClick(() => - Locale.showLinkToWeblate.setData(true) - ), - Locale.showLinkToWeblate - ) - this.SetClass("hidden-on-mobile") - } -} diff --git a/UI/BigComponents/UserInformation.ts b/UI/BigComponents/UserInformation.ts index 640bd278e..7acc9110a 100644 --- a/UI/BigComponents/UserInformation.ts +++ b/UI/BigComponents/UserInformation.ts @@ -19,11 +19,14 @@ import EditableTagRendering from "../Popup/EditableTagRendering" import TagRenderingConfig from "../../Models/ThemeConfig/TagRenderingConfig" import { SaveButton } from "../Popup/SaveButton" import { TagUtils } from "../../Logic/Tags/TagUtils" -import * as usersettings from "../../assets/generated/layers/usersettings.json" +import usersettings from "../../assets/generated/layers/usersettings.json" import { LoginToggle } from "../Popup/LoginButton" import LayerConfig from "../../Models/ThemeConfig/LayerConfig" -import * as translators from "../../assets/translators.json" -import * as codeContributors from "../../assets/contributors.json" +import translators from "../../assets/translators.json" +import codeContributors from "../../assets/contributors.json" +import Locale from "../i18n/Locale" +import { Utils } from "../../Utils" +import LinkToWeblate from "../Base/LinkToWeblate" export class ImportViewerLinks extends VariableUiElement { constructor(osmConnection: OsmConnection) { @@ -53,7 +56,7 @@ class SingleUserSettingsPanel extends EditableTagRendering { userInfoFocusedQuestion?: UIEventSource ) { const editMode = new UIEventSource(false) - // Isolate the preferences. THey'll be updated explicitely later on anyway + // Isolate the preferences. They'll be updated explicitely later on anyway super( amendedPrefs, config, @@ -68,6 +71,9 @@ class SingleUserSettingsPanel extends EditableTagRendering { TagUtils.FlattenAnd(store.data, amendedPrefs.data) ).asChange(amendedPrefs.data) for (const kv of selection) { + if (kv.k.startsWith("_")) { + continue + } osmConnection.GetPreference(kv.k, "", "").setData(kv.v) } @@ -104,13 +110,59 @@ class UserInformationMainPanel extends VariableUiElement { const settings = new UIEventSource>({}) const usersettingsConfig = new LayerConfig(usersettings, "userinformationpanel") - const amendedPrefs = new UIEventSource({}) + const amendedPrefs = new UIEventSource({ _theme: layout?.id }) osmConnection.preferencesHandler.preferences.addCallback((newPrefs) => { for (const k in newPrefs) { amendedPrefs.data[k] = newPrefs[k] } amendedPrefs.ping() }) + const translationMode = osmConnection.GetPreference("translation-mode") + Locale.language.mapD( + (language) => { + amendedPrefs.data["_language"] = language + const trmode = translationMode.data + if (trmode === "true" || trmode === "mobile") { + const missing = layout.missingTranslations() + const total = missing.total + + const untranslated = missing.untranslated.get(language) ?? [] + const hasMissingTheme = untranslated.some((k) => k.startsWith("themes:")) + const missingLayers = Utils.Dedup( + untranslated + .filter((k) => k.startsWith("layers:")) + .map((k) => k.slice("layers:".length).split(".")[0]) + ) + + const zenLinks: { link: string; id: string }[] = Utils.NoNull([ + hasMissingTheme + ? { + id: "theme:" + layout.id, + link: LinkToWeblate.hrefToWeblateZen( + language, + "themes", + layout.id + ), + } + : undefined, + ...missingLayers.map((id) => ({ + id: "layer:" + id, + link: LinkToWeblate.hrefToWeblateZen(language, "layers", id), + })), + ]) + const untranslated_count = untranslated.length + amendedPrefs.data["_translation_total"] = "" + total + amendedPrefs.data["_translation_translated_count"] = + "" + (total - untranslated_count) + amendedPrefs.data["_translation_percentage"] = + "" + Math.floor((100 * (total - untranslated_count)) / total) + console.log("Setting zenLinks", zenLinks) + amendedPrefs.data["_translation_links"] = JSON.stringify(zenLinks) + } + amendedPrefs.ping() + }, + [translationMode] + ) osmConnection.userDetails.addCallback((userDetails) => { for (const k in userDetails) { amendedPrefs.data["_" + k] = "" + userDetails[k] diff --git a/UI/DashboardGui.ts b/UI/DashboardGui.ts index 8587cd66a..213e692bf 100644 --- a/UI/DashboardGui.ts +++ b/UI/DashboardGui.ts @@ -5,7 +5,7 @@ import { Utils } from "../Utils" import Combine from "./Base/Combine" import ShowDataLayer from "./ShowDataLayer/ShowDataLayer" import LayerConfig from "../Models/ThemeConfig/LayerConfig" -import * as home_location_json from "../assets/layers/home_location/home_location.json" +import home_location_json from "../assets/layers/home_location/home_location.json" import State from "../State" import Title from "./Base/Title" import { MinimapObj } from "./Base/Minimap" diff --git a/UI/DefaultGUI.ts b/UI/DefaultGUI.ts index 5aa832043..97caccdb8 100644 --- a/UI/DefaultGUI.ts +++ b/UI/DefaultGUI.ts @@ -18,7 +18,7 @@ import SimpleAddUI from "./BigComponents/SimpleAddUI" import StrayClickHandler from "../Logic/Actors/StrayClickHandler" import { DefaultGuiState } from "./DefaultGuiState" import LayerConfig from "../Models/ThemeConfig/LayerConfig" -import * as home_location_json from "../assets/layers/home_location/home_location.json" +import home_location_json from "../assets/layers/home_location/home_location.json" import NewNoteUi from "./Popup/NewNoteUi" import Combine from "./Base/Combine" import AddNewMarker from "./BigComponents/AddNewMarker" @@ -33,7 +33,6 @@ import GeoLocationHandler from "../Logic/Actors/GeoLocationHandler" import { GeoLocationState } from "../Logic/State/GeoLocationState" import Hotkeys from "./Base/Hotkeys" import AvailableBaseLayers from "../Logic/Actors/AvailableBaseLayers" -import Lazy from "./Base/Lazy" import CopyrightPanel from "./BigComponents/CopyrightPanel" /** diff --git a/UI/ImportFlow/CompareToAlreadyExistingNotes.ts b/UI/ImportFlow/CompareToAlreadyExistingNotes.ts index 37aa64994..c936c7921 100644 --- a/UI/ImportFlow/CompareToAlreadyExistingNotes.ts +++ b/UI/ImportFlow/CompareToAlreadyExistingNotes.ts @@ -13,12 +13,12 @@ import Minimap from "../Base/Minimap" import ShowDataLayer from "../ShowDataLayer/ShowDataLayer" import FeatureInfoBox from "../Popup/FeatureInfoBox" import { ImportUtils } from "./ImportUtils" -import * as import_candidate from "../../assets/layers/import_candidate/import_candidate.json" +import import_candidate from "../../assets/layers/import_candidate/import_candidate.json" import StaticFeatureSource from "../../Logic/FeatureSource/Sources/StaticFeatureSource" import Title from "../Base/Title" import Loading from "../Base/Loading" import { VariableUiElement } from "../Base/VariableUIElement" -import * as known_layers from "../../assets/generated/known_layers.json" +import known_layers from "../../assets/generated/known_layers.json" import { LayerConfigJson } from "../../Models/ThemeConfig/Json/LayerConfigJson" import Translations from "../i18n/Translations" import { Feature } from "geojson" diff --git a/UI/ImportFlow/ConflationChecker.ts b/UI/ImportFlow/ConflationChecker.ts index 517ded7f6..1b18e6722 100644 --- a/UI/ImportFlow/ConflationChecker.ts +++ b/UI/ImportFlow/ConflationChecker.ts @@ -22,12 +22,12 @@ import ShowDataLayer from "../ShowDataLayer/ShowDataLayer" import StaticFeatureSource from "../../Logic/FeatureSource/Sources/StaticFeatureSource" import ValidatedTextField from "../Input/ValidatedTextField" import { LocalStorageSource } from "../../Logic/Web/LocalStorageSource" -import * as import_candidate from "../../assets/layers/import_candidate/import_candidate.json" +import import_candidate from "../../assets/layers/import_candidate/import_candidate.json" import { GeoOperations } from "../../Logic/GeoOperations" import FeatureInfoBox from "../Popup/FeatureInfoBox" import { ImportUtils } from "./ImportUtils" import Translations from "../i18n/Translations" -import * as currentview from "../../assets/layers/current_view/current_view.json" +import currentview from "../../assets/layers/current_view/current_view.json" import { CheckBox } from "../Input/Checkboxes" import BackgroundMapSwitch from "../BigComponents/BackgroundMapSwitch" import { Feature, FeatureCollection, Point } from "geojson" diff --git a/UI/Input/LocationInput.ts b/UI/Input/LocationInput.ts index 4a65e7d8f..56f20f445 100644 --- a/UI/Input/LocationInput.ts +++ b/UI/Input/LocationInput.ts @@ -14,7 +14,7 @@ import { FixedUiElement } from "../Base/FixedUiElement" import ShowDataLayer from "../ShowDataLayer/ShowDataLayer" import BaseUIElement from "../BaseUIElement" import Toggle from "./Toggle" -import * as matchpoint from "../../assets/layers/matchpoint/matchpoint.json" +import matchpoint from "../../assets/layers/matchpoint/matchpoint.json" import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig" import FilteredLayer from "../../Models/FilteredLayer" import { ElementStorage } from "../../Logic/ElementStorage" diff --git a/UI/Input/TextField.ts b/UI/Input/TextField.ts index a87d9fa17..291906c2f 100644 --- a/UI/Input/TextField.ts +++ b/UI/Input/TextField.ts @@ -61,17 +61,6 @@ export class TextField extends InputElement { return this._isValid(t) } - private static test() { - const placeholder = new UIEventSource("placeholder") - const tf = new TextField({ - placeholder, - }) - const html = tf.InnerConstructElement().children[0] - html.placeholder // => 'placeholder' - placeholder.setData("another piece of text") - html.placeholder // => "another piece of text" - } - /** * * // should update placeholders dynamically diff --git a/UI/Input/ValidatedTextField.ts b/UI/Input/ValidatedTextField.ts index 865391ef2..93b3bccf3 100644 --- a/UI/Input/ValidatedTextField.ts +++ b/UI/Input/ValidatedTextField.ts @@ -52,10 +52,6 @@ export class TextFieldDef { } } - protectedisValid(s: string, _: (() => string) | undefined): boolean { - return true - } - public getFeedback(s: string): Translation { const tr = Translations.t.validation[this.name] if (tr !== undefined) { @@ -82,6 +78,9 @@ export class TextFieldDef { } options["textArea"] = this.name === "text" + if (this.name === "text") { + options["htmlType"] = "area" + } const self = this @@ -589,7 +588,7 @@ class StringTextField extends TextFieldDef { class TextTextField extends TextFieldDef { declare inputmode: "text" constructor() { - super("text", "A longer piece of text") + super("text", "A longer piece of text. Uses an textArea instead of a textField") } } diff --git a/UI/LanguagePicker.ts b/UI/LanguagePicker.ts index eaabf833f..9babbab0a 100644 --- a/UI/LanguagePicker.ts +++ b/UI/LanguagePicker.ts @@ -1,10 +1,10 @@ import { DropDown } from "./Input/DropDown" import Locale from "./i18n/Locale" import BaseUIElement from "./BaseUIElement" -import * as native from "../assets/language_native.json" -import * as language_translations from "../assets/language_translations.json" +import native from "../assets/language_native.json" +import language_translations from "../assets/language_translations.json" import { Translation } from "./i18n/Translation" -import * as used_languages from "../assets/generated/used_languages.json" +import used_languages from "../assets/generated/used_languages.json" import Lazy from "./Base/Lazy" import Toggle from "./Input/Toggle" @@ -35,9 +35,8 @@ export default class LanguagePicker extends Toggle { private static hybrid(lang: string): Translation { const nativeText = native[lang] ?? lang - const allTranslations = language_translations["default"] ?? language_translations const translation = {} - const trans = allTranslations[lang] + const trans = language_translations[lang] if (trans === undefined) { return new Translation({ "*": nativeText }) } @@ -45,7 +44,7 @@ export default class LanguagePicker extends Toggle { if (key.startsWith("_")) { continue } - const translationInKey = allTranslations[lang][key] + const translationInKey = language_translations[lang][key] if (nativeText.toLowerCase() === translationInKey.toLowerCase()) { translation[key] = nativeText } else { diff --git a/UI/Popup/AllLanguagesSelector.ts b/UI/Popup/AllLanguagesSelector.ts index 46f070c80..3d3216e67 100644 --- a/UI/Popup/AllLanguagesSelector.ts +++ b/UI/Popup/AllLanguagesSelector.ts @@ -1,7 +1,7 @@ import { SearchablePillsSelector } from "../Input/SearchableMappingsSelector" import { Store } from "../../Logic/UIEventSource" import BaseUIElement from "../BaseUIElement" -import * as all_languages from "../../assets/language_translations.json" +import all_languages from "../../assets/language_translations.json" import { Translation } from "../i18n/Translation" export class AllLanguagesSelector extends SearchablePillsSelector { @@ -18,7 +18,7 @@ export class AllLanguagesSelector extends SearchablePillsSelector { hasPriority?: Store }[] = [] - const langs = options?.supportedLanguages ?? all_languages["default"] ?? all_languages + const langs = options?.supportedLanguages ?? all_languages for (const ln in langs) { let languageInfo: Record & { _meta?: { countries: string[] } } = all_languages[ln] diff --git a/UI/Popup/ImportButton.ts b/UI/Popup/ImportButton.ts index 940d94d71..037ec6be6 100644 --- a/UI/Popup/ImportButton.ts +++ b/UI/Popup/ImportButton.ts @@ -35,7 +35,7 @@ import CreateMultiPolygonWithPointReuseAction from "../../Logic/Osm/Actions/Crea import { Tag } from "../../Logic/Tags/Tag" import TagApplyButton from "./TagApplyButton" import LayerConfig from "../../Models/ThemeConfig/LayerConfig" -import * as conflation_json from "../../assets/layers/conflation/conflation.json" +import conflation_json from "../../assets/layers/conflation/conflation.json" import { GeoOperations } from "../../Logic/GeoOperations" import { LoginToggle } from "./LoginButton" import { AutoAction } from "./AutoApplyButton" diff --git a/UI/Popup/LanguageElement.ts b/UI/Popup/LanguageElement.ts index ffad59189..12bd75bd6 100644 --- a/UI/Popup/LanguageElement.ts +++ b/UI/Popup/LanguageElement.ts @@ -4,7 +4,7 @@ import { UIEventSource } from "../../Logic/UIEventSource" import FeaturePipelineState from "../../Logic/State/FeaturePipelineState" import { VariableUiElement } from "../Base/VariableUIElement" import { OsmTags } from "../../Models/OsmFeature" -import * as all_languages from "../../assets/language_translations.json" +import all_languages from "../../assets/language_translations.json" import { Translation } from "../i18n/Translation" import Combine from "../Base/Combine" import Title from "../Base/Title" diff --git a/UI/Popup/NewNoteUi.ts b/UI/Popup/NewNoteUi.ts index 350e78e8a..19e267711 100644 --- a/UI/Popup/NewNoteUi.ts +++ b/UI/Popup/NewNoteUi.ts @@ -11,6 +11,7 @@ import Toggle from "../Input/Toggle" import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig" import FeaturePipeline from "../../Logic/FeatureSource/FeaturePipeline" import FilteredLayer from "../../Models/FilteredLayer" +import Hash from "../../Logic/Web/Hash" export default class NewNoteUi extends Toggle { constructor( @@ -33,7 +34,7 @@ export default class NewNoteUi extends Toggle { text.SetClass("border rounded-sm border-grey-500") const postNote = new SubtleButton(Svg.addSmall_svg().SetClass("max-h-7"), t.createNote) - postNote.onClick(async () => { + postNote.OnClickWithLoading(t.creating, async () => { let txt = text.GetValue().data if (txt === undefined || txt === "") { return @@ -63,6 +64,7 @@ export default class NewNoteUi extends Toggle { } state?.featurePipeline?.InjectNewPoint(feature) state.selectedElement?.setData(feature) + Hash.hash.setData(feature.properties.id) text.GetValue().setData("") isCreated.setData(true) }) @@ -73,12 +75,12 @@ export default class NewNoteUi extends Toggle { new Combine([ new Toggle( undefined, - t.warnAnonymous.SetClass("alert"), + t.warnAnonymous.SetClass("block alert"), state?.osmConnection?.isLoggedIn ), new Toggle( postNote, - t.textNeeded.SetClass("alert"), + t.textNeeded.SetClass("block alert"), text.GetValue().map((txt) => txt?.length > 3) ), ]).SetClass("flex justify-end items-center"), diff --git a/UI/Popup/SidedMinimap.ts b/UI/Popup/SidedMinimap.ts index 66e4f1505..4e34ecdda 100644 --- a/UI/Popup/SidedMinimap.ts +++ b/UI/Popup/SidedMinimap.ts @@ -3,7 +3,7 @@ import Loc from "../../Models/Loc" import Minimap from "../Base/Minimap" import ShowDataLayer from "../ShowDataLayer/ShowDataLayer" import LayerConfig from "../../Models/ThemeConfig/LayerConfig" -import * as left_right_style_json from "../../assets/layers/left_right_style/left_right_style.json" +import left_right_style_json from "../../assets/layers/left_right_style/left_right_style.json" import StaticFeatureSource from "../../Logic/FeatureSource/Sources/StaticFeatureSource" import { SpecialVisualization } from "../SpecialVisualization" diff --git a/UI/Popup/SplitRoadWizard.ts b/UI/Popup/SplitRoadWizard.ts index 036adec1c..afe508bcd 100644 --- a/UI/Popup/SplitRoadWizard.ts +++ b/UI/Popup/SplitRoadWizard.ts @@ -15,7 +15,7 @@ import StaticFeatureSource from "../../Logic/FeatureSource/Sources/StaticFeature import ShowDataMultiLayer from "../ShowDataLayer/ShowDataMultiLayer" import LayerConfig from "../../Models/ThemeConfig/LayerConfig" import { BBox } from "../../Logic/BBox" -import * as split_point from "../../assets/layers/split_point/split_point.json" +import split_point from "../../assets/layers/split_point/split_point.json" import { OsmConnection } from "../../Logic/Osm/OsmConnection" import { Changes } from "../../Logic/Osm/Changes" import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig" diff --git a/UI/ShowDataLayer/ShowDataLayerImplementation.ts b/UI/ShowDataLayer/ShowDataLayerImplementation.ts index 0f3d9e19f..72305405c 100644 --- a/UI/ShowDataLayer/ShowDataLayerImplementation.ts +++ b/UI/ShowDataLayer/ShowDataLayerImplementation.ts @@ -136,7 +136,7 @@ export default class ShowDataLayerImplementation { if (this._leafletMap.data === undefined) { return } - const v = this.leafletLayersPerId.get(selected.properties.id + selected.geometry.type) + const v = this.leafletLayersPerId.get(selected.properties.id) if (v === undefined) { return } @@ -335,7 +335,20 @@ export default class ShowDataLayerImplementation { icon: L.divIcon(style), }) } + + /** + * Creates a function which, for the given feature, will open the featureInfoBox (and lazyly create it) + * This function is cached + * @param feature + * @param key + * @param layer + * @private + */ private createActivateFunction(feature, key: string, layer: LayerConfig): (event) => void { + if (this.leafletLayersPerId.has(key)) { + return this.leafletLayersPerId.get(key).activateFunc + } + let infobox: ScrollableFullScreen = undefined const self = this @@ -373,12 +386,7 @@ export default class ShowDataLayerImplementation { return } const key = feature.properties.id - let activate: (event) => void - if (this.leafletLayersPerId.has(key)) { - activate = this.leafletLayersPerId.get(key).activateFunc - } else { - activate = this.createActivateFunction(feature, key, layer) - } + const activate = this.createActivateFunction(feature, key, layer) // We also have to open on rightclick, doubleclick, ... as users sometimes do this. See #1219 leafletLayer.on({ diff --git a/UI/ShowDataLayer/ShowTileInfo.ts b/UI/ShowDataLayer/ShowTileInfo.ts index 9f4d4b4d5..b20266170 100644 --- a/UI/ShowDataLayer/ShowTileInfo.ts +++ b/UI/ShowDataLayer/ShowTileInfo.ts @@ -5,7 +5,7 @@ import ShowDataLayer from "./ShowDataLayer" import StaticFeatureSource from "../../Logic/FeatureSource/Sources/StaticFeatureSource" import { GeoOperations } from "../../Logic/GeoOperations" import { Tiles } from "../../Models/TileRange" -import * as clusterstyle from "../../assets/layers/cluster_style/cluster_style.json" +import clusterstyle from "../../assets/layers/cluster_style/cluster_style.json" export default class ShowTileInfo { public static readonly styling = new LayerConfig(clusterstyle, "ShowTileInfo", true) diff --git a/UI/SpecialVisualizations.ts b/UI/SpecialVisualizations.ts index 67349cdf1..b9b16a878 100644 --- a/UI/SpecialVisualizations.ts +++ b/UI/SpecialVisualizations.ts @@ -612,8 +612,8 @@ export default class SpecialVisualizations { special: { type: "multi", key: "_doors_from_building_properties", - tagRendering: { - render: "The building containing this feature has a door of width {entrance:width}", + tagrendering: { + en: "The building containing this feature has a door of width {entrance:width}", }, }, }, diff --git a/UI/i18n/Locale.ts b/UI/i18n/Locale.ts index c88a655c7..d989db3b3 100644 --- a/UI/i18n/Locale.ts +++ b/UI/i18n/Locale.ts @@ -5,6 +5,10 @@ import { QueryParameters } from "../../Logic/Web/QueryParameters" export default class Locale { public static showLinkToWeblate: UIEventSource = new UIEventSource(false) + /** + * Indicates that -if showLinkToWeblate is true- a link on mobile mode is shown as well + */ + public static showLinkOnMobile: UIEventSource = new UIEventSource(false) public static language: UIEventSource = Locale.setup() private static setup() { diff --git a/UI/i18n/Translations.ts b/UI/i18n/Translations.ts index 3002ef56d..9d6836a20 100644 --- a/UI/i18n/Translations.ts +++ b/UI/i18n/Translations.ts @@ -1,7 +1,7 @@ import { FixedUiElement } from "../Base/FixedUiElement" import { Translation, TypedTranslation } from "./Translation" import BaseUIElement from "../BaseUIElement" -import * as known_languages from "../../assets/generated/used_languages.json" +import known_languages from "../../assets/generated/used_languages.json" import CompiledTranslations from "../../assets/generated/CompiledTranslations" export default class Translations { diff --git a/Utils.ts b/Utils.ts index ef100d77e..6ed49a2fa 100644 --- a/Utils.ts +++ b/Utils.ts @@ -1,4 +1,4 @@ -import * as colors from "./assets/colors.json" +import colors from "./assets/colors.json" export class Utils { /** diff --git a/Utils/LanguageUtils.ts b/Utils/LanguageUtils.ts index 734a4725f..43db28a08 100644 --- a/Utils/LanguageUtils.ts +++ b/Utils/LanguageUtils.ts @@ -1,4 +1,4 @@ -import * as used_languages from "../assets/generated/used_languages.json" +import used_languages from "../assets/generated/used_languages.json" export default class LanguageUtils { /** diff --git a/assets/layers/hydrant/hydrant.json b/assets/layers/hydrant/hydrant.json index 98b70b8c4..6c97a33f7 100644 --- a/assets/layers/hydrant/hydrant.json +++ b/assets/layers/hydrant/hydrant.json @@ -192,7 +192,7 @@ "fr": "Pilier.", "de": "Überflurhydrant.", "it": "Soprasuolo.", - "nl": "Pillaar type.", + "nl": "Bovengrondse brandkraan.", "es": "De pilar.", "ca": "De pilar." }, @@ -257,7 +257,7 @@ "fr": "Enterré.", "de": "Unterflurhydrant.", "it": "Sottosuolo.", - "nl": "Ondergronds type.", + "nl": "Ondergrondse brandkraan.", "ca": "Subterrani.", "es": "Bajo tierra." }, @@ -361,6 +361,29 @@ "nl": "Pijpdiameter:{canonical(fire_hydrant:diameter)}" } }, + { + "id": "hydrant-number-of-couplings", + "question": { + "en": "How many couplings does this fire hydrant have?", + "de": "Wie viele Kupplungen hat dieser Hydrant?", + "nl": "Hoe veel koppelingen bezit deze brandkraan?", + "ca": "Quants acoblaments té aquest hidrant?" + }, + "freeform": { + "key": "couplings", + "placeholder": { + "en": "Number of couplings", + "de": "Anzahl der Kupplungen", + "nl": "Aantal koppelingen" + }, + "type": "int" + }, + "render": { + "en": "Number of couplings: {couplings}", + "de": "Anzahl der Kupplungen: {couplings}", + "nl": "Aantal koppelingen: {couplings}" + } + }, { "id": "hydrant-couplings", "question": { @@ -526,4 +549,4 @@ ] } ] -} \ No newline at end of file +} diff --git a/assets/layers/usersettings/usersettings.json b/assets/layers/usersettings/usersettings.json index a02ee467a..830f6d0d9 100644 --- a/assets/layers/usersettings/usersettings.json +++ b/assets/layers/usersettings/usersettings.json @@ -62,6 +62,118 @@ } ] }, + { + "id": "translations-title", + "group": "translations", + "render": "

Translating MapComplete

" + }, + { + "group": "translations", + "id": "translation-mode", + "question": { + "en": "Do you want to help translating MapComplete?" + }, + "mappings": [ + { + "if": "mapcomplete-translation-mode=false", + "then": { + "en": "Don't show a button to quickly change translations" + } + }, + { + "if": "mapcomplete-translation-mode=true", + "then": { + "en": "Show a button to quickly open translations when using MapComplete on a big screen" + } + }, + { + "if": "mapcomplete-translation-mode=mobile", + "then": { + "en": "Always show the translation buttons, including on mobile" + } + } + ] + }, + { + "group": "translations", + "id": "translation-help", + "mappings": [ + { + "if": { + "or": [ + "mapcomplete-translation-mode=yes", + "mapcomplete-translation-mode=true", + "mapcomplete-translation-mode=mobile" + ] + }, + "then": { + "ca": "Fes clic a la icona 'tradueix' al costat d'una cadena per introduir o actualitzar un fragment de text. Necessites un compte de Weblate per a això. Crea'n un amb el teu nom d'usuari OSM per desbloquejar automàticament el mode de traducció.", + "da": "Klik på 'oversæt'-ikonet ved siden af en streng for at indtaste eller opdatere et stykke tekst. Du skal have en Weblate-konto for at kunne gøre dette. Opret en med dit OSM-brugernavn for automatisk at låse oversættelsestilstanden op.", + "de": "Klicken Sie auf das Übersetzungssymbol neben einer Zeichenfolge, um den Übersetzungstext einzugeben oder zu aktualisieren. Dazu benötigen Sie ein Weblate-Konto. Erstellen Sie eines mit Ihrem OSM-Benutzernamen, um den Übersetzungsmodus automatisch freizuschalten.", + "en": "Click the 'translate'-icon next to a string to enter or update a piece of text. You need a Weblate-account for this. Create one with your OSM-username to automatically unlock translation mode.", + "es": "Haz clic en el icono 'traducir' al lado de una cadena para introducir o actualizar un texto. Necesitas una cuenta de Weblate para esto. Crea una con tu usuario de OSM para desbloquear el modo de traducción automáticamente.", + "fr": "Cliquez sur l'icône \"traduire\" à côté d'une chaîne de caractères pour saisir ou mettre à jour la chaine de texte. Vous aurez besoin d'un compte Weblate pour cela. Créez-en un avec votre nom d'utilisateur OSM pour déverrouiller automatiquement le mode traduction.", + "nl": "Klik op het 'vertaal'-icoontje die naast een stukje tekst staat om deze tekst te vertalen of aan te passen. Hiervoor heb je een (gratis) Weblate-account nodig. Indien je jouw account maakt met dezelfde naam als je OSM-gebruikersnaam, dan zullen de vertaalknoppen automatisch verschijnen.", + "zh_Hant": "點字串旁邊的 'translate'-icon 來輸入或是更新一段文字。你需要 Weblate 帳號。用你 OSM 帳號名稱來創建帳號,並且自動解鎖翻譯模式。" + }, + "icon": "translate" + } + ] + }, + { + "group": "translations", + "id": "translation-completeness", + "render": { + "ca": "Les traduccions de {_theme} en {_language} tenen un {_translation_percentage}%: {_translation_translated_count} cadenes de {_translation_total} estan traduïdes", + "da": "Oversættelser for {_theme} i {_language} er på {_translation_percentage}%: {_translation_translated_count} strenge ud af {_translation_total} er oversat", + "de": "Die Übersetzung für {_theme} in {_language} ist zu {_translation_percentage}% vollständig: {_translation_translated_count} Zeichenfolgen von {_translation_total} sind übersetzt", + "en": "Translations for {_theme} in {_language} are at {_translation_percentage}%: {_translation_translated_count} strings out of {_translation_total} are translated", + "es": "Las traducciones para {_theme} en {_language} están al {_translation_percentage}%: {_translation_translated_count} cadenas de {_translation_total} están traducidas", + "id": "Terjemahan untuk {_theme} dalam {_language} masih {_translation_percentage}%: {_translation_translated_count} string dari {_translation_total} diterjemahkan", + "nb_NO": "Oversettelsen for {_theme} i {_language} har {_translation_percentage}% dekning: {_translation_translated_count} strenger av {_translation_total} har blitt oversatt", + "nl": "Vertalingen voor {_theme} in {_language} zijn momenteel op {_translation_percentage}%: van {_translation_total} teksten zijn er reeds {_translation_translated_count} vertaald", + "zh_Hant": "{_theme} 的 {_language} 翻譯目前是 {_translation_percentage}%:{_translation_total} 中的 {_translation_translated_count} 已經翻譯了" + }, + "condition": { + "or": [ + "mapcomplete-translation-mode=yes", + "mapcomplete-translation-mode=true", + "mapcomplete-translation-mode=mobile" + ] + }, + "mappings": [ + { + "if": "_translation_percentage=100", + "icon": "confirm", + "then": { + "en": "Completely translated", + "nl": "Volledig vertaald" + } + } + ] + }, + { + "id": "translation-links", + "group": "translations", + "condition": { + "and": [ + "_translation_links~*", + { + "or": [ + "mapcomplete-translation-mode=true", + "mapcomplete-translation-mode=mobile" + ] + } + ] + }, + "render": { + "special": { + "type": "multi", + "key": "_translation_links", + "tagrendering": "Translate entries of {id}" + } + } + }, { "id": "verified-mastodon", "mappings": [ @@ -85,6 +197,18 @@ } ] }, + { + "id": "cscount-thanks", + "mappings": [ + { + "if": "_csCount>0", + "then": { + "en": "You have made changes on {_csCount} different occasions! That is awesome!" + }, + "icon": "party" + } + ] + }, { "id": "translation-thanks", "mappings": [ @@ -110,15 +234,43 @@ "de": "Sie haben Code zu MapComplete mit {_code_contributions} Commits beigetragen! Das ist großartig!", "nl": "Je hebt mee geprogrammeerd aan MapComplete met {_code_contributions} commits! Das supercool van je! Bedankt hiervoor!" }, - "icon": "party" + "icon": "party", + "hideInAnswer": true + } + ] + }, + { + "id": "show_debug", + "question": { + "en": "Show user settings debug info?" + }, + "mappings": [ + { + "if": "mapcomplete-show_debug=yes", + "then": { + "en": "Show debug info" + } + }, + { + "if": "mapcomplete-show_debug=no", + "then": { + "en": "Don't show debug info" + } + }, + { + "if": "mapcomplete-show_debug=", + "then": { + "en": "Don't show debug info" + }, + "hideInAnswer": true } ] }, { "id": "debug", - "condition": "_name=Pieter Vander Vennet", + "condition": "mapcomplete-show_debug=yes", "render": "{all_tags()}" } ], "mapRendering": null -} \ No newline at end of file +} diff --git a/assets/themes/healthcare/healthcare.json b/assets/themes/healthcare/healthcare.json index b51803b8c..0e999220b 100644 --- a/assets/themes/healthcare/healthcare.json +++ b/assets/themes/healthcare/healthcare.json @@ -31,6 +31,65 @@ "physiotherapist", "dentist", "hospital", - "pharmacy" + "pharmacy", + { + "builtin": "shops", + "override": { + "id": "medical-shops", + "minzoom": 13, + "=filter": [ + "open_now", + "accepts_cash", + "accepts_cards" + ], + "=presets": [ + { + "title": { + "en": "a medical supply shop" + }, + "tags": [ + "shop=medical_supply" + ] + }, + { + "title": { + "en": "a hearing aids shop" + }, + "tags": [ + "shop=hearing_aids" + ] + }, + { + "title": { + "en": "an optician" + }, + "tags": [ + "shop=optician" + ] + } + ], + "source": { + "osmTags": { + "and+": [ + { + "or": [ + "shop=medical_supply", + "shop=hearing_aids", + "shop=optician" + ] + } + ] + } + } + } + }, + { + "builtin": "shops", + "=presets": [], + "=name": null, + "override": { + "minzoom": 19 + } + } ] -} \ No newline at end of file +} diff --git a/assets/themes/uk_addresses/uk_addresses.json b/assets/themes/uk_addresses/uk_addresses.json index cc0878021..b1d0ef8c9 100644 --- a/assets/themes/uk_addresses/uk_addresses.json +++ b/assets/themes/uk_addresses/uk_addresses.json @@ -102,7 +102,7 @@ }, { "id": "uk_addresses_embedding_outline", - "render": "Warning: This point lies within a building or area for which we already have an address. You should only add this address if it is different.
The number and street name we have for the existing address is {_embedding_object:addr:housenumber} {_embedding_object:addr:street}", + "render": "Warning: This point lies within a building or area for which we already have an address. You should only add this address if it is different.
The number and street name we have for the existing address is {_embedding_object:addr:housenumber} {_embedding_object:addr:street}", "mappings": [ { "if": "_embedding_object:id=true", @@ -681,4 +681,4 @@ "enableShareScreen": false, "enableMoreQuests": false, "credits": "Pieter Vander Vennet, Rob Nickerson, Russ Garrett" -} \ No newline at end of file +} diff --git a/css/index-tailwind-output.css b/css/index-tailwind-output.css index 1b42e5aca..628a27c37 100644 --- a/css/index-tailwind-output.css +++ b/css/index-tailwind-output.css @@ -862,6 +862,10 @@ video { margin-bottom: 6rem; } +.ml-1 { + margin-left: 0.25rem; +} + .mb-2 { margin-bottom: 0.5rem; } @@ -910,10 +914,6 @@ video { margin-bottom: 2rem; } -.ml-1 { - margin-left: 0.25rem; -} - .mb-1 { margin-bottom: 0.25rem; } @@ -1027,6 +1027,10 @@ video { height: 50%; } +.h-3 { + height: 0.75rem; +} + .h-screen { height: 100vh; } @@ -1047,10 +1051,6 @@ video { height: 0px; } -.h-3 { - height: 0.75rem; -} - .h-48 { height: 12rem; } @@ -1115,6 +1115,10 @@ video { width: 0px; } +.w-3 { + width: 0.75rem; +} + .w-screen { width: 100vw; } @@ -1422,11 +1426,6 @@ video { border-color: rgb(0 0 0 / var(--tw-border-opacity)); } -.border-gray-400 { - --tw-border-opacity: 1; - border-color: rgb(156 163 175 / var(--tw-border-opacity)); -} - .border-gray-300 { --tw-border-opacity: 1; border-color: rgb(209 213 219 / var(--tw-border-opacity)); @@ -1437,6 +1436,11 @@ video { border-color: rgb(252 165 165 / var(--tw-border-opacity)); } +.border-gray-400 { + --tw-border-opacity: 1; + border-color: rgb(156 163 175 / var(--tw-border-opacity)); +} + .border-blue-500 { --tw-border-opacity: 1; border-color: rgb(59 130 246 / var(--tw-border-opacity)); diff --git a/index.ts b/index.ts index 321b320ba..43642982c 100644 --- a/index.ts +++ b/index.ts @@ -60,7 +60,7 @@ new Combine([ .SetClass("link-underline small") .onClick(() => { localStorage.clear() - window.location.reload(true) + window.location.reload() }), ]).AttachTo("centermessage") // Add an initialization and reset button if something goes wrong diff --git a/index_theme.ts.template b/index_theme.ts.template index e4e5bb594..711ce5727 100644 --- a/index_theme.ts.template +++ b/index_theme.ts.template @@ -33,9 +33,9 @@ ShowOverlayLayerImplementation.Implement(); // Miscelleanous Utils.DisableLongPresses() if(new URLSearchParams(window.location.search).get("test") === "true"){ - console.log(themeConfig["default"]) + console.log(themeConfig) } -const layoutToUse = new LayoutConfig(themeConfig["default"]) +const layoutToUse = new LayoutConfig(themeConfig) // Workaround/legacy to keep the old paramters working as I renamed some of them diff --git a/langs/ca.json b/langs/ca.json index 2ca085f7e..3931b8067 100644 --- a/langs/ca.json +++ b/langs/ca.json @@ -586,10 +586,6 @@ }, "translations": { "activateButton": "Ajudar a traduir MapComplete", - "completeness": "Les traduccions de {theme} en {language} tenen un {percentage}%: {translated} cadenes de {total} estan traduïdes", - "deactivate": "Deshabilitar els botons de traducció", - "help": "Fes clic a la icona 'tradueix' al costat d'una cadena per introduir o actualitzar un fragment de text. Necessites un compte de Weblate per a això. Crea'n un amb el teu nom d'usuari OSM per desbloquejar automàticament el mode de traducció.", - "isTranslator": "El mode de traducció està actiu, ja que el vostre nom d'usuari coincideix amb el nom d'un traductor anterior", "missing": "{count} cadenes sense traduir", "notImmediate": "Les traduccions no s'actualitzen directament. Això sol trigar uns quants dies" }, diff --git a/langs/da.json b/langs/da.json index 37bb0ed6a..cf9c5716f 100644 --- a/langs/da.json +++ b/langs/da.json @@ -803,10 +803,6 @@ "translations": { "activateButton": "Hjælp med at oversætte MapComplete", "allMissing": "Ingen oversættelser endnu", - "completeness": "Oversættelser for {theme} i {language} er på {percentage}%: {translated} strenge ud af {total} er oversat", - "deactivate": "Slå oversættelsesknapper fra", - "help": "Klik på 'oversæt'-ikonet ved siden af en streng for at indtaste eller opdatere et stykke tekst. Du skal have en Weblate-konto for at kunne gøre dette. Opret en med dit OSM-brugernavn for automatisk at låse oversættelsestilstanden op.", - "isTranslator": "Oversættelsestilstanden er slået til, da dit brugernavn svarer til navnet på en tidligere oversætter", "missing": "{count} uoversatte strenge", "notImmediate": "Oversættelser opdateres ikke direkte. Det tager typisk et par dage" }, diff --git a/langs/de.json b/langs/de.json index 307d9f21f..bc0bfd25b 100644 --- a/langs/de.json +++ b/langs/de.json @@ -928,10 +928,6 @@ "translations": { "activateButton": "MapComplete übersetzen", "allMissing": "Noch keine Übersetzungen", - "completeness": "Die Übersetzung für {theme} in {language} ist zu {percentage}% vollständig: {translated} Zeichenfolgen von {total} sind übersetzt", - "deactivate": "Übersetzungssymbol ausblenden", - "help": "Klicken Sie auf das Übersetzungssymbol neben einer Zeichenfolge, um den Übersetzungstext einzugeben oder zu aktualisieren. Dazu benötigen Sie ein Weblate-Konto. Erstellen Sie eines mit Ihrem OSM-Benutzernamen, um den Übersetzungsmodus automatisch freizuschalten.", - "isTranslator": "Der Übersetzungsmodus ist aktiv, da Ihr Benutzername mit dem Namen eines früheren Übersetzers übereinstimmt", "missing": "{count} nicht übersetzte Zeichenfolgen", "notImmediate": "Die Übersetzung wird nicht direkt aktualisiert. Dies dauert in der Regel ein paar Tage" }, diff --git a/langs/en.json b/langs/en.json index 50605772e..aa08ff1c1 100644 --- a/langs/en.json +++ b/langs/en.json @@ -746,6 +746,7 @@ "createNote": "Create a new note", "createNoteIntro": "Is something wrong or missing on the map? Create a note here. These will be checked by volunteers.", "createNoteTitle": "Create a new note here", + "creating": "Creating note...", "disableAllNoteFilters": "Disable all filters", "isClosed": "This note is resolved", "isCreated": "Your note has been created!", @@ -930,10 +931,6 @@ "translations": { "activateButton": "Help translate MapComplete", "allMissing": "No translations yet", - "completeness": "Translations for {theme} in {language} are at {percentage}%: {translated} strings out of {total} are translated", - "deactivate": "Disable translation buttons", - "help": "Click the 'translate'-icon next to a string to enter or update a piece of text. You need a Weblate-account for this. Create one with your OSM-username to automatically unlock translation mode.", - "isTranslator": "Translation mode is active as your username matches the name of a previous translator", "missing": "{count} untranslated strings", "notImmediate": "Translations are not updated directly. This typically takes a few days" }, diff --git a/langs/es.json b/langs/es.json index ca373fdac..b5c09434c 100644 --- a/langs/es.json +++ b/langs/es.json @@ -708,10 +708,6 @@ "translations": { "activateButton": "Ayuda a traducir MapComplete", "allMissing": "Aún sin traducciónes", - "completeness": "Las traducciones para {theme} en {language} están al {percentage}%: {translated} cadenas de {total} están traducidas", - "deactivate": "Deshabilitar los botones de traducción", - "help": "Haz clic en el icono 'traducir' al lado de una cadena para introducir o actualizar un texto. Necesitas una cuenta de Weblate para esto. Crea una con tu usuario de OSM para desbloquear el modo de traducción automáticamente.", - "isTranslator": "El modo de traducción está activo si tu nombre de usuario coincide con el nombre de un traductor anterior", "missing": "{count} cadenas sin traducir", "notImmediate": "Las traducciones no se actualizan directamente. Habitualmente esto lleva unos días" }, diff --git a/langs/fr.json b/langs/fr.json index aa9248a75..778c22efd 100644 --- a/langs/fr.json +++ b/langs/fr.json @@ -473,9 +473,6 @@ }, "translations": { "activateButton": "Aidez à traduire MapComplete", - "deactivate": "Désactiver les boutons de traduction", - "help": "Cliquez sur l'icône \"traduire\" à côté d'une chaîne de caractères pour saisir ou mettre à jour la chaine de texte. Vous aurez besoin d'un compte Weblate pour cela. Créez-en un avec votre nom d'utilisateur OSM pour déverrouiller automatiquement le mode traduction.", - "isTranslator": "Mode traduction activé, votre pseudo correspond à celui d’une personne de l’équipe de traduction", "missing": "{count} segments non traduits" }, "userinfo": { diff --git a/langs/id.json b/langs/id.json index 0e7952577..9bc317537 100644 --- a/langs/id.json +++ b/langs/id.json @@ -265,8 +265,6 @@ }, "translations": { "allMissing": "Belum ada terjemahan", - "completeness": "Terjemahan untuk {theme} dalam {language} masih {percentage}%: {translated} string dari {total} diterjemahkan", - "isTranslator": "Mode terjemahan aktif karena nama pengguna Anda cocok dengan nama penerjemah sebelumnya", "notImmediate": "Terjemahan tidak diperbarui secara langsung. Biasanya memakan waktu beberapa hari" }, "validation": { diff --git a/langs/nb_NO.json b/langs/nb_NO.json index 27578a9b7..7c4bea604 100644 --- a/langs/nb_NO.json +++ b/langs/nb_NO.json @@ -689,9 +689,6 @@ "translations": { "activateButton": "Bistå oversettelsen av MapComplete", "allMissing": "Ingen oversettelser enda", - "completeness": "Oversettelsen for {theme} i {language} har {percentage}% dekning: {translated} strenger av {total} har blitt oversatt", - "deactivate": "Skru av oversettelsesknapper", - "isTranslator": "Oversettelsesmodus er aktivt siden brukernavnet ditt samsvarer med navnet på forrige oversetter", "missing": "{count} uoversatte strenger", "notImmediate": "Oversettelser oppdateres ikke direkte. Dette tar typisk et par dager." }, diff --git a/langs/nl.json b/langs/nl.json index 4ca58aed9..f6177c851 100644 --- a/langs/nl.json +++ b/langs/nl.json @@ -928,10 +928,6 @@ "translations": { "activateButton": "Help met het vertalen van MapComplete", "allMissing": "Nog geen vertalingen", - "completeness": "Vertalingen voor {theme} in {language} zijn momenteel op {percentage}%: van {total} teksten zijn er reeds {translated} vertaald", - "deactivate": "Verberg de vertaalknoppen", - "help": "Klik op het 'vertaal'-icoontje die naast een stukje tekst staat om deze tekst te vertalen of aan te passen. Hiervoor heb je een (gratis) Weblate-account nodig. Indien je jouw account maakt met dezelfde naam als je OSM-gebruikersnaam, dan zullen de vertaalknoppen automatisch verschijnen.", - "isTranslator": "Vertaalmode is actief: je gebruikersnaam is dezelfde als van een vertaler. We gaan er dus vanuit dat jij die vertaler bent", "missing": "{count} niet-vertaalde teksten", "notImmediate": "Vertalingen worden niet onmiddelijk geupdate. Dit duurt gemiddeld enkele dagen" }, diff --git a/langs/pl.json b/langs/pl.json index 4a3054fd1..4e95bf8f8 100644 --- a/langs/pl.json +++ b/langs/pl.json @@ -189,8 +189,6 @@ "translations": { "activateButton": "Pomóż przetłumaczyć MapComplete", "allMissing": "Brak tłumaczeń", - "deactivate": "Wyłącz przyciski tłumaczenia", - "isTranslator": "Tryb tłumaczenia jest aktywny, ponieważ Twoja nazwa użytkownika odpowiada nazwisku poprzedniego tłumacza", "notImmediate": "Tłumaczenia nie są aktualizowane bezpośrednio. Zwykle trwa to kilka dni" }, "userinfo": { diff --git a/langs/zh_Hant.json b/langs/zh_Hant.json index 7e5ad6cc7..1bc0a8a25 100644 --- a/langs/zh_Hant.json +++ b/langs/zh_Hant.json @@ -720,10 +720,6 @@ "translations": { "activateButton": "協助翻譯 MapComplete", "allMissing": "還沒有翻譯", - "completeness": "{theme} 的 {language} 翻譯目前是 {percentage}%:{total} 中的 {translated} 已經翻譯了", - "deactivate": "關閉翻譯按鈕", - "help": "點字串旁邊的 'translate'-icon 來輸入或是更新一段文字。你需要 Weblate 帳號。用你 OSM 帳號名稱來創建帳號,並且自動解鎖翻譯模式。", - "isTranslator": "翻譯模式已經啟用,你的名字符合前一位翻譯者的名字", "missing": "{count} 未翻譯字串", "notImmediate": "翻譯不會直接更新,通常會需要幾天時間" }, diff --git a/scripts/automoveTranslations.ts b/scripts/automoveTranslations.ts index 7f11efbfc..9b2534d2c 100644 --- a/scripts/automoveTranslations.ts +++ b/scripts/automoveTranslations.ts @@ -1,4 +1,3 @@ -import * as languages from "../assets/generated/used_languages.json" import { readFileSync, writeFileSync } from "fs" /** diff --git a/scripts/generateCache.ts b/scripts/generateCache.ts index cd3fdf111..f145378e2 100644 --- a/scripts/generateCache.ts +++ b/scripts/generateCache.ts @@ -78,13 +78,13 @@ function geoJsonName(targetDir: string, x: number, y: number, z: number): string return targetDir + "_" + z + "_" + x + "_" + y + ".geojson" } -/// Downloads the given feature and saves them to disk +/// Downloads the given tilerange from overpass and saves them to disk async function downloadRaw( targetdir: string, r: TileRange, theme: LayoutConfig, relationTracker: RelationsTracker -) /* : {failed: number, skipped :number} */ { +): Promise<{ failed: number; skipped: number }> { let downloaded = 0 let failed = 0 let skipped = 0 diff --git a/scripts/generateDocs.ts b/scripts/generateDocs.ts index cf33aa0a1..b92d519fd 100644 --- a/scripts/generateDocs.ts +++ b/scripts/generateDocs.ts @@ -15,14 +15,15 @@ import List from "../UI/Base/List" import SharedTagRenderings from "../Customizations/SharedTagRenderings" import { writeFile } from "fs" import Translations from "../UI/i18n/Translations" -import * as themeOverview from "../assets/generated/theme_overview.json" +import themeOverview from "../assets/generated/theme_overview.json" import DefaultGUI from "../UI/DefaultGUI" import FeaturePipelineState from "../Logic/State/FeaturePipelineState" import LayoutConfig from "../Models/ThemeConfig/LayoutConfig" -import * as bookcases from "../assets/generated/themes/bookcases.json" +import bookcases from "../assets/generated/themes/bookcases.json" import { DefaultGuiState } from "../UI/DefaultGuiState" -import * as fakedom from "fake-dom" +import fakedom from "fake-dom" import Hotkeys from "../UI/Base/Hotkeys" +import { QueryParameters } from "../Logic/Web/QueryParameters" function WriteFile( filename, html: BaseUIElement, @@ -103,7 +104,7 @@ function generateWikipage() { "! Name, link !! Genre !! Covered region !! Language !! Description !! Free materials !! Image\n" + "|-" - for (const layout of themeOverview["default"] ?? themeOverview) { + for (const layout of themeOverview) { if (layout.hideFromOverview) { continue } @@ -225,6 +226,12 @@ WriteFile("./Docs/URL_Parameters.md", QueryParameterDocumentation.GenerateQueryP if (fakedom === undefined || window === undefined) { throw "FakeDom not initialized" } +QueryParameters.GetQueryParameter( + "mode", + "map", + "The mode the application starts in, e.g. 'map', 'dashboard' or 'statistics'" +) + new DefaultGUI( new FeaturePipelineState(new LayoutConfig(bookcases)), new DefaultGuiState() diff --git a/scripts/generateLayerOverview.ts b/scripts/generateLayerOverview.ts index 85890487e..38c4e34b7 100644 --- a/scripts/generateLayerOverview.ts +++ b/scripts/generateLayerOverview.ts @@ -1,6 +1,6 @@ import ScriptUtils from "./ScriptUtils" import { existsSync, mkdirSync, readFileSync, statSync, writeFileSync } from "fs" -import * as licenses from "../assets/generated/license_info.json" +import licenses from "../assets/generated/license_info.json" import { LayoutConfigJson } from "../Models/ThemeConfig/Json/LayoutConfigJson" import { LayerConfigJson } from "../Models/ThemeConfig/Json/LayerConfigJson" import Constants from "../Models/Constants" @@ -14,8 +14,8 @@ import { } from "../Models/ThemeConfig/Conversion/Validation" import { Translation } from "../UI/i18n/Translation" import { TagRenderingConfigJson } from "../Models/ThemeConfig/Json/TagRenderingConfigJson" -import * as questions from "../assets/tagRenderings/questions.json" -import * as icons from "../assets/tagRenderings/icons.json" +import questions from "../assets/tagRenderings/questions.json" +import icons from "../assets/tagRenderings/icons.json" import PointRenderingConfigJson from "../Models/ThemeConfig/Json/PointRenderingConfigJson" import { PrepareLayer } from "../Models/ThemeConfig/Conversion/PrepareLayer" import { PrepareTheme } from "../Models/ThemeConfig/Conversion/PrepareTheme" @@ -155,7 +155,7 @@ class LayerOverviewUtils { const dict = new Map() const validator = new ValidateTagRenderings(undefined, doesImageExist) - for (const key in questions["default"]) { + for (const key in questions) { if (key === "id") { continue } @@ -168,7 +168,7 @@ class LayerOverviewUtils { ) dict.set(key, config) } - for (const key in icons["default"]) { + for (const key in icons) { if (key === "id") { continue } diff --git a/scripts/generateLayouts.ts b/scripts/generateLayouts.ts index 0921f68e1..3d4bf282e 100644 --- a/scripts/generateLayouts.ts +++ b/scripts/generateLayouts.ts @@ -2,7 +2,7 @@ import { appendFileSync, existsSync, mkdirSync, readFileSync, writeFile, writeFi import Locale from "../UI/i18n/Locale" import Translations from "../UI/i18n/Translations" import { Translation } from "../UI/i18n/Translation" -import * as all_known_layouts from "../assets/generated/known_layers_and_themes.json" +import all_known_layouts from "../assets/generated/known_layers_and_themes.json" import { LayoutConfigJson } from "../Models/ThemeConfig/Json/LayoutConfigJson" import LayoutConfig from "../Models/ThemeConfig/LayoutConfig" import xml2js from "xml2js" @@ -23,7 +23,7 @@ async function createIcon(iconPath: string, size: number, alreadyWritten: string name = name.substr(2) } - const newname = `assets/generated/images/${name.replace(/\//g, "_")}${size}.png` + const newname = `public/assets/generated/images/${name.replace(/\//g, "_")}${size}.png` if (alreadyWritten.indexOf(newname) >= 0) { return newname @@ -60,7 +60,7 @@ async function createSocialImage(layout: LayoutConfig, template: "" | "Wide"): P ) return undefined } - const path = `./assets/generated/images/social_image_${layout.id}_${template}.svg` + const path = `./public/assets/generated/images/social_image_${layout.id}_${template}.svg` if (existsSync(path)) { return path } @@ -121,7 +121,7 @@ async function createManifest( // This is an svg. Lets create the needed pngs and do some checkes! const whiteBackgroundPath = - "./assets/generated/images/theme_" + layout.id + "_white_background.svg" + "./public/assets/generated/images/theme_" + layout.id + "_white_background.svg" { const svg = await ScriptUtils.ReadSvg(icon) const width: string = svg.$.width @@ -136,7 +136,7 @@ async function createManifest( let path = layout.icon if (layout.icon.startsWith("<")) { // THis is already the svg - path = "./assets/generated/images/" + layout.id + "_logo.svg" + path = "./public/assets/generated/images/" + layout.id + "_logo.svg" writeFileSync(path, layout.icon) } @@ -235,7 +235,7 @@ async function createLandingPage(layout: LayoutConfig, manifest, whiteIcons, alr let icon = layout.icon if (icon.startsWith(" { createDir("./assets/generated") createDir("./assets/generated/layers") createDir("./assets/generated/themes") - createDir("./assets/generated/images") + createDir("./public/assets/") + createDir("./public/assets/generated") + createDir("./public/assets/generated/images") const blacklist = [ "", diff --git a/scripts/generateStats.ts b/scripts/generateStats.ts index 126d31f12..a86006412 100644 --- a/scripts/generateStats.ts +++ b/scripts/generateStats.ts @@ -1,4 +1,4 @@ -import * as known_layers from "../assets/generated/known_layers.json" +import known_layers from "../assets/generated/known_layers.json" import { LayerConfigJson } from "../Models/ThemeConfig/Json/LayerConfigJson" import { TagUtils } from "../Logic/Tags/TagUtils" import { Utils } from "../Utils" @@ -10,7 +10,7 @@ import Constants from "../Models/Constants" async function main(includeTags = true) { ScriptUtils.fixUtils() - const layers: LayerConfigJson[] = (known_layers["default"] ?? known_layers).layers + const layers = known_layers.layers const keysAndTags = new Map>() diff --git a/scripts/removeTranslationString.ts b/scripts/removeTranslationString.ts index 08f26f482..f54d875fb 100644 --- a/scripts/removeTranslationString.ts +++ b/scripts/removeTranslationString.ts @@ -13,7 +13,7 @@ async function main(args: string[]) { console.log("Removing translation string ", path, "from the general translations") const files = ScriptUtils.readDirRecSync("./langs", 1).filter((f) => f.endsWith(".json")) for (const file of files) { - const json = JSON.parse(fs.readFileSync(file, "UTF-8")) + const json = JSON.parse(fs.readFileSync(file, { encoding: "utf-8" })) Utils.WalkPath(path, json, (_) => undefined) fs.writeFileSync(file, JSON.stringify(json, null, " ") + "\n") } diff --git a/scripts/thieves/readIdPresets.ts b/scripts/thieves/readIdPresets.ts index 3d8537c6a..e6f93d7c4 100644 --- a/scripts/thieves/readIdPresets.ts +++ b/scripts/thieves/readIdPresets.ts @@ -3,7 +3,7 @@ */ import ScriptUtils from "../ScriptUtils" import { existsSync, readFileSync, writeFileSync } from "fs" -import * as known_languages from "../../assets/language_native.json" +import known_languages from "../../assets/language_native.json" import { LayerConfigJson } from "../../Models/ThemeConfig/Json/LayerConfigJson" import { MappingConfigJson } from "../../Models/ThemeConfig/Json/QuestionableTagRenderingConfigJson" import SmallLicense from "../../Models/smallLicense" diff --git a/test/CodeQuality.spec.ts b/test/CodeQuality.spec.ts index b0e58c6e5..6b0926be6 100644 --- a/test/CodeQuality.spec.ts +++ b/test/CodeQuality.spec.ts @@ -7,62 +7,86 @@ import { exec } from "child_process" * @param reason * @private */ -function detectInCode(forbidden: string, reason: string) { - const excludedDirs = [ - ".git", - "node_modules", - "dist", - ".cache", - ".parcel-cache", - "assets", - "vendor", - ".idea/", - ] +function detectInCode(forbidden: string, reason: string): (done: () => void) => void { + return (done: () => void) => { + const excludedDirs = [ + ".git", + "node_modules", + "dist", + ".cache", + ".parcel-cache", + "assets", + "vendor", + ".idea/", + ] - exec( - '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!") - return - } - if (error !== null) { - throw error - } - if (stderr !== "") { - throw stderr - } + exec( + '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) { - throw `Found a '${forbidden}' at \n ${found.join("\n ")}.\n ${reason}` + 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 + } + done() } - } - ) + ) + } } describe("Code quality", () => { - it("should not contain reverse", () => { + it( + "should not contain reverse", detectInCode( "reverse()", "Reverse is stateful and changes the source list. This often causes subtle bugs" ) - }) + ) - it("should not contain 'constructor.name'", () => { + it( + "should not contain 'constructor.name'", detectInCode("constructor\\.name", "This is not allowed, as minification does erase names.") - }) + ) - it("should not contain 'innerText'", () => { + it( + "should not contain 'innerText'", detectInCode( "innerText", "innerText is not allowed as it is not testable with fakeDom. Use 'textContent' instead." ) - }) + ) + + it( + "should not contain 'import * as name from \"xyz.json\"'", + detectInCode( + 'import \\* as [a-zA-Z0-9_]\\+ from \\"[.-_/a-zA-Z0-9]\\+\\.json\\"', + "With vite, json files have a default export. Use import name from file.json instead" + ) + ) + + it( + "should not contain '[\"default\"]'", + detectInCode('\\[\\"default\\"\\]', "Possible leftover of faulty default import") + ) })