Add buttons to quickly swap background layers (also in the locationInput), move copyright into home panel, split privacy policy to seperate welcome message tab

This commit is contained in:
Pieter Vander Vennet 2021-11-21 02:44:35 +01:00
parent 1d0fbe701c
commit 37c0129a6d
22 changed files with 477 additions and 183 deletions

View file

@ -1,24 +1,140 @@
import Combine from "../Base/Combine";
import {UIEventSource} from "../../Logic/UIEventSource";
import AvailableBaseLayers from "../../Logic/Actors/AvailableBaseLayers";
import State from "../../State";
import Loc from "../../Models/Loc";
import Svg from "../../Svg";
import {VariableUiElement} from "../Base/VariableUIElement";
import Toggle from "../Input/Toggle";
import BaseLayer from "../../Models/BaseLayer";
import AvailableBaseLayers from "../../Logic/Actors/AvailableBaseLayers";
import BaseUIElement from "../BaseUIElement";
import {GeoOperations} from "../../Logic/GeoOperations";
class SingleLayerSelectionButton extends Toggle {
constructor(state: {
locationControl: UIEventSource<Loc>
}, prefered: string) {
const layer = AvailableBaseLayers.SelectBestLayerAccordingTo(state.locationControl, new UIEventSource(prefered))
const layerIsCorrectType = layer.map(bl => bl?.category === prefered)
public readonly activate: () => void
/**
*
* The SingeLayerSelectionButton also acts as an actor to keep the layers in check
*
* It works the following way:
*
* - It has a boolean state to indicate wether or not the button is active
* - It keeps track of the available layers
*/
constructor(
locationControl: UIEventSource<Loc>,
options: {
currentBackground: UIEventSource<BaseLayer>,
preferredType: string,
preferredLayer?: BaseLayer,
notAvailable?: () => void
}) {
const prefered = options.preferredType
const previousLayer = new UIEventSource(options.preferredLayer)
const unselected = SingleLayerSelectionButton.getIconFor(prefered)
.SetClass("rounded-lg p-1 h-12 w-12 overflow-hidden subtle-background border-invisible")
const selected = SingleLayerSelectionButton.getIconFor(prefered)
.SetClass("rounded-lg p-1 h-12 w-12 overflow-hidden subtle-background border-attention-catch")
const available = AvailableBaseLayers
.SelectBestLayerAccordingTo(locationControl, new UIEventSource<string | string[]>(options.preferredType))
let toggle: BaseUIElement = new Toggle(
selected,
unselected,
options.currentBackground.map(bg => bg.category === options.preferredType)
)
super(
SingleLayerSelectionButton.getIconFor(prefered).SetClass("rounded-full p-3 h-10 w-10"),
toggle,
undefined,
layerIsCorrectType
available.map(av => av.category === options.preferredType)
);
/**
* Checks that the previous layer is still usable on the current location.
* If not, clears the 'previousLayer'
*/
function checkPreviousLayer() {
if (previousLayer.data === undefined) {
return
}
if (previousLayer.data.feature === null || previousLayer.data.feature === undefined) {
// Global layer
return
}
const loc = locationControl.data
if (!GeoOperations.inside([loc.lon, loc.lat], previousLayer.data.feature)) {
// The previous layer is out of bounds
previousLayer.setData(undefined)
}
}
unselected.onClick(() => {
// Note: a check if 'available' has the correct type is not needed:
// Unselected will _not_ be visible if availableBaseLayer has a wrong type!
checkPreviousLayer()
previousLayer.setData(previousLayer.data ?? available.data)
options.currentBackground.setData(previousLayer.data)
})
available.addCallbackAndRunD(availableLayer => {
if (previousLayer.data === undefined) {
// PreviousLayer is unset -> we definitively weren't using this category -> no need to switch
return;
}
if (options.currentBackground.data?.id !== previousLayer.data?.id) {
// The previously used layer doesn't match the current layer -> no need to switch
return;
}
if (availableLayer.category === options.preferredType) {
// Allright, we can set this different layer
options.currentBackground.setData(availableLayer)
previousLayer.setData(availableLayer)
} else {
// Uh oh - no correct layer is available... We pass the torch!
if (options.notAvailable !== undefined) {
options.notAvailable()
} else {
// Fallback to OSM carto
options.currentBackground.setData(AvailableBaseLayers.osmCarto)
}
}
})
options.currentBackground.addCallbackAndRunD(background => {
if (background.category === options.preferredType) {
previousLayer.setData(background)
}
})
this.activate = () => {
checkPreviousLayer()
if (available.data.category !== options.preferredType) {
// This object can't help either - pass the torch!
if (options.notAvailable !== undefined) {
options.notAvailable()
} else {
// Fallback to OSM carto
options.currentBackground.setData(AvailableBaseLayers.osmCarto)
}
return;
}
previousLayer.setData(previousLayer.data ?? available.data)
options.currentBackground.setData(previousLayer.data)
}
}
private static getIconFor(type: string) {
@ -27,27 +143,54 @@ class SingleLayerSelectionButton extends Toggle {
return Svg.generic_map_svg()
case "photo":
return Svg.satellite_svg()
case "osmbasedmap":
return Svg.osm_logo_svg()
default:
return Svg.generic_map_svg()
}
}
}
export default class BackgroundMapSwitch extends VariableUiElement {
export default class BackgroundMapSwitch extends Combine {
constructor(
state: {
locationControl: UIEventSource<Loc>
locationControl: UIEventSource<Loc>,
backgroundLayer: UIEventSource<BaseLayer>
},
options?: {
allowedLayers?: UIEventSource<string[]>
}
currentBackground: UIEventSource<BaseLayer>,
preferredCategory?: string
) {
options = options ?? {}
options.allowedLayers = options.allowedLayers ?? new UIEventSource<string[]>(["photo", "map"])
const allowedCategories = ["osmbasedmap", "photo", "map"]
const previousLayer = state.backgroundLayer.data
const buttons = []
let activatePrevious: () => void = undefined
for (const category of allowedCategories) {
let preferredLayer = undefined
if (previousLayer.category === category) {
preferredLayer = previousLayer
}
super(options.allowedLayers.map(layers => new Combine(layers.map(prefered => new SingleLayerSelectionButton(state, prefered)))));
const button = new SingleLayerSelectionButton(
state.locationControl,
{
preferredType: category,
preferredLayer: preferredLayer,
currentBackground: currentBackground,
notAvailable: activatePrevious
})
activatePrevious = button.activate
if (category === preferredCategory) {
button.activate()
}
buttons.push(button)
}
// Selects the initial map
super(buttons)
this.SetClass("flex")
}
}

View file

@ -1,7 +1,5 @@
import Combine from "../Base/Combine";
import Translations from "../i18n/Translations";
import Attribution from "./Attribution";
import State from "../../State";
import {UIEventSource} from "../../Logic/UIEventSource";
import {FixedUiElement} from "../Base/FixedUiElement";
import * as licenses from "../../assets/generated/license_info.json"
@ -22,6 +20,7 @@ import Toggle from "../Input/Toggle";
import {OsmConnection} from "../../Logic/Osm/OsmConnection";
import Constants from "../../Models/Constants";
import PrivacyPolicy from "./PrivacyPolicy";
import ContributorCount from "../../Logic/ContributorCount";
/**
* The attribution panel shown on mobile
@ -36,7 +35,7 @@ export default class CopyrightPanel extends Combine {
currentBounds: UIEventSource<BBox>,
locationControl: UIEventSource<Loc>,
osmConnection: OsmConnection
}, contributions: UIEventSource<Map<string, number>>) {
}) {
const t = Translations.t.general.attribution
const layoutToUse = state.layoutToUse
@ -103,6 +102,8 @@ export default class CopyrightPanel extends Combine {
maintainer = Translations.t.general.attribution.themeBy.Subs({author: layoutToUse.maintainer})
}
const contributions = new ContributorCount(state).Contributors
super([
Translations.t.general.attribution.attributionContent,
new FixedUiElement("MapComplete "+Constants.vNumber).SetClass("font-bold"),
@ -144,8 +145,7 @@ export default class CopyrightPanel extends Combine {
})),
CopyrightPanel.CodeContributors(),
new Title(t.iconAttribution.title, 3),
...iconAttributions,
new PrivacyPolicy()
...iconAttributions
].map(e => e?.SetClass("mt-4")));
this.SetClass("flex flex-col link-underline overflow-hidden")
this.SetStyle("max-width: calc(100vw - 3em); width: 40rem; margin-left: 0.75rem; margin-right: 0.5rem")

View file

@ -17,6 +17,9 @@ import UserRelatedState from "../../Logic/State/UserRelatedState";
import Loc from "../../Models/Loc";
import BaseLayer from "../../Models/BaseLayer";
import FilteredLayer from "../../Models/FilteredLayer";
import CopyrightPanel from "./CopyrightPanel";
import FeaturePipeline from "../../Logic/FeatureSource/FeaturePipeline";
import PrivacyPolicy from "./PrivacyPolicy";
export default class FullWelcomePaneWithTabs extends ScrollableFullScreen {
@ -29,6 +32,7 @@ export default class FullWelcomePaneWithTabs extends ScrollableFullScreen {
featureSwitchShareScreen: UIEventSource<boolean>,
featureSwitchMoreQuests: UIEventSource<boolean>,
locationControl: UIEventSource<Loc>,
featurePipeline: FeaturePipeline,
backgroundLayer: UIEventSource<BaseLayer>,
filteredLayers: UIEventSource<FilteredLayer[]>
} & UserRelatedState) {
@ -46,6 +50,7 @@ export default class FullWelcomePaneWithTabs extends ScrollableFullScreen {
osmConnection: OsmConnection,
featureSwitchShareScreen: UIEventSource<boolean>,
featureSwitchMoreQuests: UIEventSource<boolean>,
featurePipeline: FeaturePipeline,
locationControl: UIEventSource<Loc>, backgroundLayer: UIEventSource<BaseLayer>, filteredLayers: UIEventSource<FilteredLayer[]>
} & UserRelatedState,
isShown: UIEventSource<boolean>):
@ -55,16 +60,8 @@ export default class FullWelcomePaneWithTabs extends ScrollableFullScreen {
const tabs: { header: string | BaseUIElement, content: BaseUIElement }[] = [
{header: `<img src='${state.layoutToUse.icon}'>`, content: welcome},
{
header: Svg.osm_logo_img,
content: Translations.t.general.openStreetMapIntro.SetClass("link-underline")
},
]
if (state.featureSwitchShareScreen.data) {
tabs.push({header: Svg.share_img, content: new ShareScreen(state)});
}
if (state.featureSwitchMoreQuests.data) {
tabs.push({
@ -77,6 +74,31 @@ export default class FullWelcomePaneWithTabs extends ScrollableFullScreen {
});
}
if (state.featureSwitchShareScreen.data) {
tabs.push({header: Svg.share_img, content: new ShareScreen(state)});
}
const copyright = {
header: Svg.copyright_svg(),
content:
new Combine(
[
Translations.t.general.openStreetMapIntro.SetClass("link-underline"),
Translations.t.general.attribution.attributionTitle,
new CopyrightPanel(state)
]
)
}
tabs.push(copyright)
const privacy = {
header: Svg.eye_svg(),
content: new PrivacyPolicy()
}
tabs.push(privacy)
return tabs;
}
@ -85,6 +107,7 @@ export default class FullWelcomePaneWithTabs extends ScrollableFullScreen {
osmConnection: OsmConnection,
featureSwitchShareScreen: UIEventSource<boolean>,
featureSwitchMoreQuests: UIEventSource<boolean>,
featurePipeline: FeaturePipeline,
locationControl: UIEventSource<Loc>, backgroundLayer: UIEventSource<BaseLayer>, filteredLayers: UIEventSource<FilteredLayer[]>
} & UserRelatedState, currentTab: UIEventSource<number>, isShown: UIEventSource<boolean>) {

View file

@ -1,8 +1,6 @@
import Combine from "../Base/Combine";
import ScrollableFullScreen from "../Base/ScrollableFullScreen";
import Translations from "../i18n/Translations";
import CopyrightPanel from "./CopyrightPanel";
import ContributorCount from "../../Logic/ContributorCount";
import Toggle from "../Input/Toggle";
import MapControlButton from "../MapControlButton";
import Svg from "../../Svg";
@ -16,6 +14,7 @@ import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig";
import FilteredLayer from "../../Models/FilteredLayer";
import BaseLayer from "../../Models/BaseLayer";
import {OsmConnection} from "../../Logic/Osm/OsmConnection";
import BackgroundMapSwitch from "./BackgroundMapSwitch";
export default class LeftControls extends Combine {
@ -38,23 +37,6 @@ export default class LeftControls extends Combine {
copyrightViewIsOpened: UIEventSource<boolean>
}) {
const toggledCopyright = new ScrollableFullScreen(
() => Translations.t.general.attribution.attributionTitle.Clone(),
() =>
new CopyrightPanel(
state,
new ContributorCount(state).Contributors
),
"copyright",
guiState.copyrightViewIsOpened
);
const copyrightButton = new Toggle(
toggledCopyright,
new MapControlButton(Svg.copyright_svg())
.onClick(() => toggledCopyright.isShown.setData(true)),
toggledCopyright.isShown
).SetClass("p-0.5");
const toggledDownload = new Toggle(
new AllDownloads(
@ -93,10 +75,10 @@ export default class LeftControls extends Combine {
state.featureSwitchFilter
);
super([filterButton,
downloadButtonn,
copyrightButton])
new BackgroundMapSwitch(state, state.backgroundLayer)
])
this.SetClass("flex flex-col")