Move license picker into usersettings, add possibility to highlight a setting

This commit is contained in:
Pieter Vander Vennet 2023-01-13 02:48:48 +01:00
parent 9202cbe8e2
commit 4ed88609e5
16 changed files with 204 additions and 103 deletions

View file

@ -1,7 +1,7 @@
import { Utils } from "../Utils"
export default class Constants {
public static vNumber = "0.25.5"
public static vNumber = "0.25.6"
public static ImgurApiKey = "7070e7167f0a25a"
public static readonly mapillary_client_token_v4 =

View file

@ -6,7 +6,8 @@ import Constants from "../Constants"
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"
export default class LayoutConfig {
public static readonly defaultSocialImage = "assets/SocialImage.png"
public readonly id: string
@ -235,6 +236,54 @@ export default class LayoutConfig {
return this.layers.some((l) => l.isLeftRightSensitive())
}
public missingTranslations(): {
completeness: Map<string, number>
untranslated: Map<string, string[]>
total: number
} {
const layout = this
let total = 0
const completeness = new Map<string, number>()
const untranslated = new Map<string, string[]>()
Utils.WalkObject(
layout,
(o) => {
const translation = <Translation>(<any>o)
if (translation.translations["*"] !== undefined) {
return
}
if (translation.context === undefined || translation.context.indexOf(":") < 0) {
// no source given - lets ignore
return
}
total++
used_languages.languages.forEach((ln) => {
const trans = translation.translations
if (trans["*"] !== undefined) {
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))
}
})
},
(o) => {
if (o === undefined || o === null) {
return false
}
return o instanceof Translation
}
)
return { completeness, untranslated, total }
}
public getMatchingLayer(tags: any): LayerConfig | undefined {
if (tags === undefined) {
return undefined

View file

@ -15,15 +15,13 @@ import { Store } from "../../Logic/UIEventSource"
import { SubtleButton } from "../Base/SubtleButton"
import Svg from "../../Svg"
import * as native_languages from "../../assets/language_native.json"
import * as used_languages from "../../assets/generated/used_languages.json"
import BaseUIElement from "../BaseUIElement"
class TranslatorsPanelContent extends Combine {
constructor(layout: LayoutConfig, isTranslator: Store<boolean>) {
const t = Translations.t.translations
const { completeness, untranslated, total } =
TranslatorsPanel.MissingTranslationsFor(layout)
const { completeness, untranslated, total } = layout.missingTranslations()
const seed = t.completeness
for (const ln of Array.from(completeness.keys())) {
@ -147,52 +145,4 @@ export default class TranslatorsPanel extends Toggle {
)
this.SetClass("hidden-on-mobile")
}
public static MissingTranslationsFor(layout: LayoutConfig): {
completeness: Map<string, number>
untranslated: Map<string, string[]>
total: number
} {
let total = 0
const completeness = new Map<string, number>()
const untranslated = new Map<string, string[]>()
Utils.WalkObject(
layout,
(o) => {
const translation = <Translation>(<any>o)
if (translation.translations["*"] !== undefined) {
return
}
if (translation.context === undefined || translation.context.indexOf(":") < 0) {
// no source given - lets ignore
return
}
total++
used_languages.languages.forEach((ln) => {
const trans = translation.translations
if (trans["*"] !== undefined) {
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))
}
})
},
(o) => {
if (o === undefined || o === null) {
return false
}
return o instanceof Translation
}
)
return { completeness, untranslated, total }
}
}

View file

@ -22,7 +22,7 @@ import { TagUtils } from "../../Logic/Tags/TagUtils"
import * as 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"
export class ImportViewerLinks extends VariableUiElement {
constructor(osmConnection: OsmConnection) {
super(
@ -44,24 +44,27 @@ export class ImportViewerLinks extends VariableUiElement {
}
class SingleUserSettingsPanel extends EditableTagRendering {
constructor(config: TagRenderingConfig, osmConnection: OsmConnection) {
constructor(
config: TagRenderingConfig,
osmConnection: OsmConnection,
amendedPrefs: UIEventSource<any>,
userInfoFocusedQuestion?: UIEventSource<string>
) {
const editMode = new UIEventSource(false)
// Isolate the preferences. THey'll be updated explicitely later on anyway
super(
osmConnection.preferencesHandler.preferences,
amendedPrefs,
config,
[],
{ osmConnection },
{
answerElementClasses: "p-2",
editMode,
createSaveButton: (store) =>
new SaveButton(
osmConnection.preferencesHandler.preferences,
osmConnection
).onClick(() => {
const prefs = osmConnection.preferencesHandler.preferences
new SaveButton(amendedPrefs, osmConnection).onClick(() => {
const selection = TagUtils.FlattenMultiAnswer(
TagUtils.FlattenAnd(store.data, prefs.data)
).asChange(prefs.data)
TagUtils.FlattenAnd(store.data, amendedPrefs.data)
).asChange(amendedPrefs.data)
for (const kv of selection) {
osmConnection.GetPreference(kv.k, "", "").setData(kv.v)
}
@ -70,6 +73,16 @@ class SingleUserSettingsPanel extends EditableTagRendering {
}),
}
)
const self = this
this.SetClass("rounded-xl")
userInfoFocusedQuestion.addCallbackAndRun((selected) => {
if (config.id !== selected) {
console.log("Removing the glowingshadow...")
self.RemoveClass("glowing-shadow")
} else {
self.SetClass("glowing-shadow")
}
})
}
}
@ -78,11 +91,35 @@ class UserInformationMainPanel extends VariableUiElement {
osmConnection: OsmConnection,
locationControl: UIEventSource<Loc>,
layout: LayoutConfig,
isOpened: UIEventSource<boolean>
isOpened: UIEventSource<boolean>,
userInfoFocusedQuestion?: UIEventSource<string>
) {
const t = Translations.t.userinfo
const imgSize = "h-6 w-6"
const ud = osmConnection.userDetails
const amendedPrefs = new UIEventSource<any>({})
osmConnection.preferencesHandler.preferences.addCallback((newPrefs) => {
for (const k in newPrefs) {
amendedPrefs.data[k] = newPrefs[k]
}
amendedPrefs.ping()
})
osmConnection.userDetails.addCallback((userDetails) => {
for (const k in userDetails) {
amendedPrefs.data["_" + k] = "" + userDetails[k]
}
const simplifiedName = userDetails.name.toLowerCase().replace(/\s+/g, "")
const isTranslator = translators.contributors.some(
(c: { contributor: string; commits: number }) => {
const replaced = c.contributor.toLowerCase().replace(/\s+/g, "")
return replaced === simplifiedName
}
)
amendedPrefs.data["_is_translator"] = "" + isTranslator
amendedPrefs.ping()
})
super(
ud.map((ud) => {
let img: BaseUIElement = Svg.person_ui().SetClass("block")
@ -138,7 +175,12 @@ class UserInformationMainPanel extends VariableUiElement {
const usersettingsConfig = new LayerConfig(usersettings, "userinformationpanel")
const questions = usersettingsConfig.tagRenderings.map((c) =>
new SingleUserSettingsPanel(c, osmConnection).SetClass("block my-4")
new SingleUserSettingsPanel(
c,
osmConnection,
amendedPrefs,
userInfoFocusedQuestion
).SetClass("block my-4")
)
return new Combine([
@ -187,6 +229,7 @@ export default class UserInformationPanel extends ScrollableFullScreen {
},
options?: {
isOpened?: UIEventSource<boolean>
userInfoFocusedQuestion?: UIEventSource<string>
}
) {
const isOpened = options?.isOpened ?? new UIEventSource<boolean>(false)
@ -207,7 +250,8 @@ export default class UserInformationPanel extends ScrollableFullScreen {
state.osmConnection,
state.locationControl,
state.layoutToUse,
isOpened
isOpened,
options?.userInfoFocusedQuestion
),
Translations.t.general.getStartedLogin,
state

View file

@ -205,9 +205,9 @@ export default class DefaultGUI {
const self = this
const userInfoMapControl = Toggle.If(state.featureSwitchUserbadge, () => {
console.log("Guistate is", guiState)
new UserInformationPanel(state, {
isOpened: guiState.userInfoIsOpened,
userInfoFocusedQuestion: guiState.userInfoFocusedQuestion,
})
const mapControl = new MapControlButton(

View file

@ -19,6 +19,9 @@ export class DefaultGuiState {
false
)
public readonly userInfoIsOpened: UIEventSource<boolean> = new UIEventSource<boolean>(false)
public readonly userInfoFocusedQuestion: UIEventSource<string> = new UIEventSource<string>(
undefined
)
public readonly welcomeMessageOpenedTab: UIEventSource<number>
constructor() {
@ -38,6 +41,14 @@ export class DefaultGuiState {
userinfo: this.userInfoIsOpened,
}
const self = this
this.userInfoIsOpened.addCallback((isOpen) => {
if (!isOpen) {
console.log("Resetting focused question")
self.userInfoFocusedQuestion.setData(undefined)
}
})
sources[Hash.hash.data?.toLowerCase()]?.setData(true)
if (Hash.hash.data === "" || Hash.hash.data === undefined) {

View file

@ -17,6 +17,8 @@ import { Changes } from "../../Logic/Osm/Changes"
import Loading from "../Base/Loading"
import { LoginToggle } from "../Popup/LoginButton"
import Constants from "../../Models/Constants"
import { DefaultGuiState } from "../DefaultGuiState"
import ScrollableFullScreen from "../Base/ScrollableFullScreen"
export class ImageUploadFlow extends Toggle {
private static readonly uploadCountsPerId = new Map<string, UIEventSource<number>>()
@ -80,6 +82,10 @@ export class ImageUploadFlow extends Toggle {
]).SetClass(
"p-2 border-4 border-detail rounded-full font-bold h-full align-middle w-full flex justify-center"
)
const licenseStore = state?.osmConnection?.GetPreference(
Constants.OsmPreferenceKeyPicturesLicense,
"CC0"
)
const fileSelector = new FileSelectorButton(label)
fileSelector.GetValue().addCallback((filelist) => {
@ -101,12 +107,7 @@ export class ImageUploadFlow extends Toggle {
}
}
console.log("Received images from the user, starting upload")
const license =
state?.osmConnection?.GetPreference(
Constants.OsmPreferenceKeyPicturesLicense,
"CC0"
)?.data ?? "CC0"
const license = licenseStore?.data ?? "CC0"
const tags = tagsSource.data
@ -174,7 +175,21 @@ export class ImageUploadFlow extends Toggle {
),
fileSelector,
Translations.t.image.respectPrivacy.Clone().SetStyle("font-size:small;"),
new Combine([
Translations.t.image.respectPrivacy,
new VariableUiElement(
licenseStore.map((license) =>
Translations.t.image.currentLicense.Subs({ license })
)
)
.onClick(() => {
console.log("Opening the license settings... ")
ScrollableFullScreen.collapse()
DefaultGuiState.state.userInfoIsOpened.setData(true)
DefaultGuiState.state.userInfoFocusedQuestion.setData("picture-license")
})
.SetClass("underline"),
]).SetStyle("font-size:small;"),
]).SetClass("flex flex-col image-upload-flow mt-4 mb-8 text-center")
super(

View file

@ -21,6 +21,8 @@ export default class EditableTagRendering extends Toggle {
options: {
editMode?: UIEventSource<boolean>
innerElementClasses?: string
/* Classes applied _only_ on the rendered element, not on the question*/
answerElementClasses?: string
/* Default will apply the tags to the relevant object, only use in special cases */
createSaveButton?: (src: Store<UploadableTag>) => BaseUIElement
}
@ -43,7 +45,10 @@ export default class EditableTagRendering extends Toggle {
configuration,
units,
editMode,
{ saveButtonConstructor: options?.createSaveButton }
{
saveButtonConstructor: options?.createSaveButton,
answerElementClasses: options?.answerElementClasses,
}
)
rendering.SetClass(options.innerElementClasses)
if (state?.featureSwitchIsDebugging?.data || state?.featureSwitchIsTesting?.data) {
@ -73,6 +78,7 @@ export default class EditableTagRendering extends Toggle {
editMode: UIEventSource<boolean>,
options?: {
saveButtonConstructor?: (src: Store<UploadableTag>) => BaseUIElement
answerElementClasses?: string
}
): BaseUIElement {
const answer: BaseUIElement = new TagRenderingAnswer(tags, configuration, state)
@ -107,7 +113,7 @@ export default class EditableTagRendering extends Toggle {
onlyIfPartiallyHidden: true,
})
}),
]).SetClass("flex justify-between w-full")
]).SetClass("flex justify-between w-full " + (options?.answerElementClasses ?? ""))
rendering = new Toggle(question, answerWithEditButton, editMode)
}
return rendering

View file

@ -155,14 +155,13 @@ export class Translation extends BaseUIElement {
return el
}
const linkToWeblate = new LinkToWeblate(self.context, self.translations)
const wrapper = document.createElement("span")
wrapper.appendChild(el)
Locale.showLinkToWeblate.addCallbackAndRun((doShow) => {
if (!doShow) {
return
}
const linkToWeblate = new LinkToWeblate(self.context, self.translations)
wrapper.appendChild(linkToWeblate.ConstructElement())
return true
})

View file

@ -18,29 +18,45 @@
{
"if": "mapcomplete-pictures-license=",
"then": {
"en": "No license has been chosen yet"
"en": "Pictures you take will be licensed with <b>CC0</b> and added to the public domain. This means that everyone can use your pictures for any purpose. <span class='subtle'>This is the default choice.</span>"
},
"hideInAnswer": true
},
{
"if": "mapcomplete-pictures-license=CC0",
"then": {
"en": "Pictures you take will be licensed with CC0 and added to the public domain. This means that everyone can use your pictures for any purpose."
"en": "Pictures you take will be licensed with <b>CC0</b> and added to the public domain. This means that everyone can use your pictures for any purpose."
}
},
{
"if": "mapcomplete-pictures-license=CC-BY 4.0",
"then": {
"en": "Pictures you take will be licensed with CC-BY 4.0 which requires everyone using your picture that they have to attribute you"
"en": "Pictures you take will be licensed with <b>CC-BY 4.0</b> which requires everyone using your picture that they have to attribute you"
}
},
{
"if": "mapcomplete-pictures-license=CC-BY-SA 4.0",
"then": {
"en": "Pictures you take will be licensed with CC-BY-SA 4.0 which means that everyone using your picture must attribute you and that derivatives of your picture must be reshared with the same license."
"en": "Pictures you take will be licensed with <b>CC-BY-SA 4.0</b> which means that everyone using your picture must attribute you and that derivatives of your picture must be reshared with the same license."
}
}
]
},
{
"id": "translation-thanks",
"condition": "_is_translator=true",
"mappings": [{
"if": "_is_translator=true",
"then": {
"en": "You have contributed to translating MapComplete! That's awesome!"
},
"icon": "party"
}]
},
{
"id": "debug",
"condition": "_name=Pieter Vander Vennet",
"render": "{all_tags()}"
}
],
"mapRendering": null

View file

@ -1493,7 +1493,7 @@
}
]
},
"induction-loop": {
"induction-loop": {
"description": "An accessibility feature: induction loops are for hard-hearing persons which have an FM-receiver.",
"question": {
"en": "Does this place have an audio induction loop for people with reduced hearing?",
@ -1766,4 +1766,4 @@
"ca": "El nom de la xarxa és <b>{internet_access:ssid}</b>"
}
}
}
}

View file

@ -2505,6 +2505,21 @@ input {
/* The checkbox that toggles a single layer */
}
.glowing-shadow {
-webkit-animation: glowing 1s ease-in-out infinite alternate;
animation: glowing 1s ease-in-out infinite alternate;
}
@-webkit-keyframes glowing {
from {
box-shadow: 0 0 20px 10px #eaaf2588, inset 0 0 0px 1px #eaaf25;
}
to {
box-shadow: 0 0 20px 20px #eaaf2588, inset 0 0 5px 1px #eaaf25;
}
}
.mapping-icon-small-height {
/* A mapping icon type */
height: 1.5rem;

View file

@ -10,7 +10,7 @@
background-color: var(--subtle-detail-color);
color: var(--subtle-detail-color-contrast);
padding: 1em;
border-radius: 1em;
border-radius: 0.75rem;
font-size: larger !important;
overflow-wrap: initial;
}

View file

@ -637,7 +637,19 @@ input {
/* The checkbox that toggles a single layer */
}
.glowing-shadow {
-webkit-animation: glowing 1s ease-in-out infinite alternate;
-moz-animation: glowing 1s ease-in-out infinite alternate;
animation: glowing 1s ease-in-out infinite alternate;
}
@-webkit-keyframes glowing {
from {
box-shadow: 0 0 20px 10px #eaaf2588, inset 0 0 0px 1px #eaaf25;
}
to {
box-shadow: 0 0 20px 20px #eaaf2588, inset 0 0 5px 1px #eaaf25;
}
}
.mapping-icon-small-height {
/* A mapping icon type */

View file

@ -364,6 +364,7 @@
},
"image": {
"addPicture": "Add picture",
"currentLicense": "Your images will be published under {license}",
"doDelete": "Remove image",
"dontDelete": "Cancel",
"isDeleted": "Deleted",

View file

@ -200,23 +200,6 @@
"phone": {
"question": "What is the phone number of {title()}?"
},
"picture-license": {
"mappings": {
"0": {
"then": "No license has been chosen yet"
},
"1": {
"then": "Pictures you take will be licensed with CC0 and added to the public domain. This means that everyone can use your pictures for any purpose."
},
"2": {
"then": "Pictures you take will be licensed with CC-BY 4.0 which requires everyone using your picture that they have to attribute you"
},
"3": {
"then": "Pictures you take will be licensed with CC-BY-SA 4.0 which means that everyone using your picture must attribute you and that derivatives of your picture must be reshared with the same license."
}
},
"question": "Under what license do you want to publish your pictures?"
},
"service:electricity": {
"mappings": {
"0": {