diff --git a/InitUiElements.ts b/InitUiElements.ts index 23b8cd4f88..9a35eb2e06 100644 --- a/InitUiElements.ts +++ b/InitUiElements.ts @@ -37,6 +37,8 @@ import SelectedFeatureHandler from "./Logic/Actors/SelectedFeatureHandler"; import LZString from "lz-string"; import {LayoutConfigJson} from "./Customizations/JSON/LayoutConfigJson"; import AttributionPanel from "./UI/BigComponents/AttributionPanel"; +import ContributorCount from "./Logic/ContributorCount"; +import FeatureSource from "./Logic/FeatureSource/FeatureSource"; export class InitUiElements { @@ -270,12 +272,12 @@ export class InitUiElements { isOpened.setData(Hash.hash.data === undefined || Hash.hash.data === "" || Hash.hash.data == "welcome") } - private static InitLayerSelection() { + private static InitLayerSelection(featureSource: FeatureSource) { const copyrightNotice = new ScrollableFullScreen( () => Translations.t.general.attribution.attributionTitle.Clone(), - () => new AttributionPanel(State.state.layoutToUse), + () => new AttributionPanel(State.state.layoutToUse, new ContributorCount(featureSource).Contributors), "copyright" ) @@ -371,7 +373,7 @@ export class InitUiElements { } - private static InitLayers() { + private static InitLayers() : FeatureSource{ const state = State.state; @@ -409,15 +411,15 @@ export class InitUiElements { const selectedFeatureHandler = new SelectedFeatureHandler(Hash.hash, State.state.selectedElement, source, State.state.osmApiFeatureSource); selectedFeatureHandler.zoomToSelectedFeature(State.state.locationControl); - + return source; } private static setupAllLayerElements() { // ------------- Setup the layers ------------------------------- - InitUiElements.InitLayers(); - InitUiElements.InitLayerSelection(); + const source = InitUiElements.InitLayers(); + InitUiElements.InitLayerSelection(source); // ------------------ Setup various other UI elements ------------ diff --git a/UI/BigComponents/AttributionPanel.ts b/UI/BigComponents/AttributionPanel.ts index f5cf1a8da2..711d099e97 100644 --- a/UI/BigComponents/AttributionPanel.ts +++ b/UI/BigComponents/AttributionPanel.ts @@ -9,6 +9,7 @@ import * as licenses from "../../assets/generated/license_info.json" import SmallLicense from "../../Models/smallLicense"; import {Utils} from "../../Utils"; import Link from "../Base/Link"; +import {VariableUiElement} from "../Base/VariableUIElement"; /** * The attribution panel shown on mobile @@ -17,20 +18,51 @@ export default class AttributionPanel extends Combine { private static LicenseObject = AttributionPanel.GenerateLicenses(); - constructor(layoutToUse: UIEventSource) { + constructor(layoutToUse: UIEventSource, contributions: UIEventSource>) { super([ Translations.t.general.attribution.attributionContent, - ((layoutToUse.data.maintainer ?? "") == "") ? "" : Translations.t.general.attribution.themeBy.Subs({author: layoutToUse.data.maintainer}), - layoutToUse.data.credits , + layoutToUse.data.credits, "
", new Attribution(undefined, undefined, State.state.layoutToUse, undefined), "
", - "

",Translations.t.general.attribution.iconAttribution.title.Clone().SetClass("pt-6 pb-3"),"

", + + new VariableUiElement(contributions.map(contributions => { + const sorted = Array.from(contributions, ([name, value]) => ({name, value})); + if (sorted.length === 0) { + return ""; + } + sorted.sort((a, b) => b.value - a.value); + let hiddenCount = 0; + if (sorted.length > 10) { + hiddenCount = sorted.length - 10 + sorted.splice(10, sorted.length - 10) + } + const links = sorted.map(kv => `${kv.name}`) + const contribs = links.join(", ") + + if (hiddenCount == 0) { + + + return Translations.t.general.attribution.mapContributionsBy.Subs({ + contributors: contribs + }).InnerRender() + } else { + return Translations.t.general.attribution.mapContributionsByAndHidden.Subs({ + contributors: contribs, + hiddenCount: hiddenCount + }).InnerRender(); + } + + + })), + "
", + "

", Translations.t.general.attribution.iconAttribution.title.Clone().SetClass("pt-6 pb-3"), "

", ...Utils.NoNull(Array.from(layoutToUse.data.ExtractImages())) .map(AttributionPanel.IconAttribution) ]); this.SetClass("flex flex-col link-underline") + this.SetStyle("max-width: calc(100vw - 5em); width: 40em;") } private static IconAttribution(iconPath: string) { @@ -42,28 +74,28 @@ export default class AttributionPanel extends Combine { if (license == undefined) { return undefined; } - if(license.license.indexOf("trivial")>=0){ + if (license.license.indexOf("trivial") >= 0) { return undefined; } - const sources =Utils.NoNull(Utils.NoEmpty(license.sources)) - + const sources = Utils.NoNull(Utils.NoEmpty(license.sources)) + return new Combine([ ``, new Combine([ new FixedUiElement(license.authors.join("; ")).SetClass("font-bold"), - new Combine([license.license, - sources.length > 0 ? " - " : "", - ... sources.map(lnk => { - let sourceLinkContent = lnk; - try{ - sourceLinkContent = new URL(lnk).hostname - }catch{ - console.error("Not a valid URL:", lnk) - } - return new Link(sourceLinkContent, lnk, true); - }) - ] + new Combine([license.license, + sources.length > 0 ? " - " : "", + ...sources.map(lnk => { + let sourceLinkContent = lnk; + try { + sourceLinkContent = new URL(lnk).hostname + } catch { + console.error("Not a valid URL:", lnk) + } + return new Link(sourceLinkContent, lnk, true); + }) + ] ).SetClass("block") ]).SetClass("flex flex-col") ]).SetClass("flex")