Move license picker into usersettings, add possibility to highlight a setting
This commit is contained in:
parent
9202cbe8e2
commit
4ed88609e5
16 changed files with 204 additions and 103 deletions
|
@ -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 =
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
})
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
14
index.css
14
index.css
|
@ -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 */
|
||||
|
|
|
@ -364,6 +364,7 @@
|
|||
},
|
||||
"image": {
|
||||
"addPicture": "Add picture",
|
||||
"currentLicense": "Your images will be published under {license}",
|
||||
"doDelete": "Remove image",
|
||||
"dontDelete": "Cancel",
|
||||
"isDeleted": "Deleted",
|
||||
|
|
|
@ -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": {
|
||||
|
|
Loading…
Reference in a new issue