Rewrite index page in svelte, rewrite language picker in Svelte, fix #1612
This commit is contained in:
parent
303d3a0337
commit
d638237e38
16 changed files with 265 additions and 333 deletions
|
@ -725,14 +725,6 @@ video {
|
|||
left: 0px;
|
||||
}
|
||||
|
||||
.top-2 {
|
||||
top: 0.5rem;
|
||||
}
|
||||
|
||||
.right-3 {
|
||||
right: 0.75rem;
|
||||
}
|
||||
|
||||
.bottom-0 {
|
||||
bottom: 0px;
|
||||
}
|
||||
|
@ -777,18 +769,22 @@ video {
|
|||
margin: 2rem;
|
||||
}
|
||||
|
||||
.m-5 {
|
||||
margin: 1.25rem;
|
||||
.m-4 {
|
||||
margin: 1rem;
|
||||
}
|
||||
|
||||
.m-3 {
|
||||
margin: 0.75rem;
|
||||
}
|
||||
|
||||
.m-0 {
|
||||
margin: 0px;
|
||||
}
|
||||
|
||||
.m-2 {
|
||||
margin: 0.5rem;
|
||||
}
|
||||
|
||||
.m-4 {
|
||||
margin: 1rem;
|
||||
}
|
||||
|
||||
.m-1 {
|
||||
margin: 0.25rem;
|
||||
}
|
||||
|
@ -797,14 +793,6 @@ video {
|
|||
margin: 0.125rem;
|
||||
}
|
||||
|
||||
.m-0 {
|
||||
margin: 0px;
|
||||
}
|
||||
|
||||
.m-3 {
|
||||
margin: 0.75rem;
|
||||
}
|
||||
|
||||
.m-6 {
|
||||
margin: 1.5rem;
|
||||
}
|
||||
|
@ -823,16 +811,16 @@ video {
|
|||
margin-bottom: 0.25rem;
|
||||
}
|
||||
|
||||
.mx-1 {
|
||||
margin-left: 0.25rem;
|
||||
margin-right: 0.25rem;
|
||||
}
|
||||
|
||||
.my-4 {
|
||||
margin-top: 1rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.mx-1 {
|
||||
margin-left: 0.25rem;
|
||||
margin-right: 0.25rem;
|
||||
}
|
||||
|
||||
.my-2 {
|
||||
margin-top: 0.5rem;
|
||||
margin-bottom: 0.5rem;
|
||||
|
@ -853,14 +841,22 @@ video {
|
|||
margin-right: 3rem;
|
||||
}
|
||||
|
||||
.mr-2 {
|
||||
margin-right: 0.5rem;
|
||||
.mt-4 {
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
.mr-4 {
|
||||
margin-right: 1rem;
|
||||
}
|
||||
|
||||
.mr-2 {
|
||||
margin-right: 0.5rem;
|
||||
}
|
||||
|
||||
.mb-16 {
|
||||
margin-bottom: 4rem;
|
||||
}
|
||||
|
||||
.mr-6 {
|
||||
margin-right: 1.5rem;
|
||||
}
|
||||
|
@ -897,10 +893,6 @@ video {
|
|||
margin-top: 0.5rem;
|
||||
}
|
||||
|
||||
.mt-4 {
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
.mb-2 {
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
@ -917,10 +909,6 @@ video {
|
|||
margin-left: 1rem;
|
||||
}
|
||||
|
||||
.mt-3 {
|
||||
margin-top: 0.75rem;
|
||||
}
|
||||
|
||||
.mb-10 {
|
||||
margin-bottom: 2.5rem;
|
||||
}
|
||||
|
@ -1058,24 +1046,24 @@ video {
|
|||
height: 2rem;
|
||||
}
|
||||
|
||||
.h-16 {
|
||||
height: 4rem;
|
||||
.h-12 {
|
||||
height: 3rem;
|
||||
}
|
||||
|
||||
.h-6 {
|
||||
height: 1.5rem;
|
||||
}
|
||||
|
||||
.h-12 {
|
||||
height: 3rem;
|
||||
}
|
||||
|
||||
.h-fit {
|
||||
height: -webkit-fit-content;
|
||||
height: -moz-fit-content;
|
||||
height: fit-content;
|
||||
}
|
||||
|
||||
.h-16 {
|
||||
height: 4rem;
|
||||
}
|
||||
|
||||
.h-4 {
|
||||
height: 1rem;
|
||||
}
|
||||
|
@ -1152,16 +1140,16 @@ video {
|
|||
width: 2rem;
|
||||
}
|
||||
|
||||
.w-16 {
|
||||
width: 4rem;
|
||||
.w-12 {
|
||||
width: 3rem;
|
||||
}
|
||||
|
||||
.w-6 {
|
||||
width: 1.5rem;
|
||||
}
|
||||
|
||||
.w-12 {
|
||||
width: 3rem;
|
||||
.w-16 {
|
||||
width: 4rem;
|
||||
}
|
||||
|
||||
.w-screen {
|
||||
|
@ -1809,6 +1797,11 @@ video {
|
|||
line-height: 1.75rem;
|
||||
}
|
||||
|
||||
.text-base {
|
||||
font-size: 1rem;
|
||||
line-height: 1.5rem;
|
||||
}
|
||||
|
||||
.text-lg {
|
||||
font-size: 1.125rem;
|
||||
line-height: 1.75rem;
|
||||
|
@ -1829,20 +1822,11 @@ video {
|
|||
line-height: 1.25rem;
|
||||
}
|
||||
|
||||
.text-base {
|
||||
font-size: 1rem;
|
||||
line-height: 1.5rem;
|
||||
}
|
||||
|
||||
.text-4xl {
|
||||
font-size: 2.25rem;
|
||||
line-height: 2.5rem;
|
||||
}
|
||||
|
||||
.font-bold {
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.font-extrabold {
|
||||
font-weight: 800;
|
||||
}
|
||||
|
@ -1851,6 +1835,10 @@ video {
|
|||
font-weight: 600;
|
||||
}
|
||||
|
||||
.font-bold {
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.font-normal {
|
||||
font-weight: 400;
|
||||
}
|
||||
|
@ -1927,21 +1915,6 @@ video {
|
|||
color: rgb(255 255 255 / var(--tw-text-opacity));
|
||||
}
|
||||
|
||||
.text-gray-900 {
|
||||
--tw-text-opacity: 1;
|
||||
color: rgb(17 24 39 / var(--tw-text-opacity));
|
||||
}
|
||||
|
||||
.text-gray-800 {
|
||||
--tw-text-opacity: 1;
|
||||
color: rgb(31 41 55 / var(--tw-text-opacity));
|
||||
}
|
||||
|
||||
.text-gray-500 {
|
||||
--tw-text-opacity: 1;
|
||||
color: rgb(107 114 128 / var(--tw-text-opacity));
|
||||
}
|
||||
|
||||
.underline {
|
||||
text-decoration-line: underline;
|
||||
}
|
||||
|
@ -2753,11 +2726,6 @@ a.link-underline {
|
|||
margin-right: 0.25rem;
|
||||
}
|
||||
|
||||
.sm\:mx-auto {
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
.sm\:mt-2 {
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
|
@ -2766,10 +2734,6 @@ a.link-underline {
|
|||
margin-right: 0.25rem;
|
||||
}
|
||||
|
||||
.sm\:mt-5 {
|
||||
margin-top: 1.25rem;
|
||||
}
|
||||
|
||||
.sm\:mr-4 {
|
||||
margin-right: 1rem;
|
||||
}
|
||||
|
@ -2790,10 +2754,6 @@ a.link-underline {
|
|||
width: 6rem;
|
||||
}
|
||||
|
||||
.sm\:max-w-xl {
|
||||
max-width: 36rem;
|
||||
}
|
||||
|
||||
.sm\:flex-nowrap {
|
||||
flex-wrap: nowrap;
|
||||
}
|
||||
|
@ -2822,15 +2782,6 @@ a.link-underline {
|
|||
padding-top: 0.25rem;
|
||||
}
|
||||
|
||||
.sm\:text-center {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.sm\:text-5xl {
|
||||
font-size: 3rem;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.sm\:text-lg {
|
||||
font-size: 1.125rem;
|
||||
line-height: 1.75rem;
|
||||
|
@ -2842,25 +2793,17 @@ a.link-underline {
|
|||
margin: 0.25rem;
|
||||
}
|
||||
|
||||
.md\:m-2 {
|
||||
margin: 0.5rem;
|
||||
}
|
||||
|
||||
.md\:mx-2 {
|
||||
margin-left: 0.5rem;
|
||||
margin-right: 0.5rem;
|
||||
}
|
||||
|
||||
.md\:mr-2 {
|
||||
margin-right: 0.5rem;
|
||||
}
|
||||
|
||||
.md\:mt-5 {
|
||||
margin-top: 1.25rem;
|
||||
}
|
||||
|
||||
.md\:mt-4 {
|
||||
margin-top: 1rem;
|
||||
.md\:mr-2 {
|
||||
margin-right: 0.5rem;
|
||||
}
|
||||
|
||||
.md\:grid {
|
||||
|
@ -2932,14 +2875,6 @@ a.link-underline {
|
|||
margin-right: 0px;
|
||||
}
|
||||
|
||||
.lg\:ml-40 {
|
||||
margin-left: 10rem;
|
||||
}
|
||||
|
||||
.lg\:w-3\/4 {
|
||||
width: 75%;
|
||||
}
|
||||
|
||||
.lg\:w-5\/12 {
|
||||
width: 41.666667%;
|
||||
}
|
||||
|
@ -2951,17 +2886,9 @@ a.link-underline {
|
|||
.lg\:grid-cols-3 {
|
||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||
}
|
||||
|
||||
.lg\:text-left {
|
||||
text-align: left;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 1280px) {
|
||||
.xl\:inline {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
.xl\:w-4\/12 {
|
||||
width: 33.333333%;
|
||||
}
|
||||
|
|
|
@ -21,6 +21,7 @@ export default class UserDetails {
|
|||
public account_created: string
|
||||
public tracesCount: number = 0
|
||||
public description: string
|
||||
public languages: string[]
|
||||
|
||||
constructor(backend: string) {
|
||||
this.backend = backend
|
||||
|
@ -89,6 +90,7 @@ export class OsmConnection {
|
|||
ud.unreadMessages = 0
|
||||
ud.name = "Fake user"
|
||||
ud.totalMessages = 42
|
||||
ud.languages = ["en"]
|
||||
}
|
||||
const self = this
|
||||
this.UpdateCapabilities()
|
||||
|
@ -193,7 +195,7 @@ export class OsmConnection {
|
|||
method: "GET",
|
||||
path: "/api/0.6/user/details",
|
||||
},
|
||||
function (err, details) {
|
||||
function (err, details: XMLDocument) {
|
||||
if (err != null) {
|
||||
console.log(err)
|
||||
self.loadingStatus.setData("error")
|
||||
|
@ -222,11 +224,14 @@ export class OsmConnection {
|
|||
data.name = userInfo.getAttribute("display_name")
|
||||
data.account_created = userInfo.getAttribute("account_created")
|
||||
data.uid = Number(userInfo.getAttribute("id"))
|
||||
data.languages = Array.from(
|
||||
userInfo.getElementsByTagName("languages")[0].getElementsByTagName("lang")
|
||||
).map((l) => l.textContent)
|
||||
data.csCount = Number.parseInt(
|
||||
userInfo.getElementsByTagName("changesets")[0].getAttribute("count") ?? 0
|
||||
userInfo.getElementsByTagName("changesets")[0].getAttribute("count") ?? "0"
|
||||
)
|
||||
data.tracesCount = Number.parseInt(
|
||||
userInfo.getElementsByTagName("traces")[0].getAttribute("count") ?? 0
|
||||
userInfo.getElementsByTagName("traces")[0].getAttribute("count") ?? "0"
|
||||
)
|
||||
|
||||
data.img = undefined
|
||||
|
|
|
@ -42,6 +42,10 @@ export default class UserRelatedState {
|
|||
public readonly showCrosshair: UIEventSource<"yes" | undefined>
|
||||
public readonly fixateNorth: UIEventSource<undefined | "yes">
|
||||
public readonly homeLocation: FeatureSource
|
||||
/**
|
||||
* The language as saved into the preferences of the user, if logged in.
|
||||
* Note that this is _different_ from the languages a user can set via the osm.org interface here: https://www.openstreetmap.org/preferences
|
||||
*/
|
||||
public readonly language: UIEventSource<string>
|
||||
public readonly preferredBackgroundLayer: UIEventSource<
|
||||
string | "photo" | "map" | "osmbasedmap" | undefined
|
||||
|
@ -134,7 +138,7 @@ export default class UserRelatedState {
|
|||
return
|
||||
}
|
||||
|
||||
this.language.addCallbackAndRunD((language) => Locale.language.setData(language))
|
||||
this.language.syncWith(Locale.language)
|
||||
}
|
||||
|
||||
private static initUserRelatedState(): LayerConfig {
|
||||
|
|
|
@ -9,7 +9,9 @@ import { RasterLayerProperties } from "./RasterLayerProperties"
|
|||
export class AvailableRasterLayers {
|
||||
public static EditorLayerIndex: (Feature<Polygon, EditorLayerIndexProperties> &
|
||||
RasterLayerPolygon)[] = <any>editorlayerindex.features
|
||||
public static globalLayers: RasterLayerPolygon[] = globallayers.layers.map(
|
||||
public static globalLayers: RasterLayerPolygon[] = globallayers.layers
|
||||
.filter((properties) => properties.id !== "osm.carto" /*Added separately*/)
|
||||
.map(
|
||||
(properties) =>
|
||||
<RasterLayerPolygon>{
|
||||
type: "Feature",
|
||||
|
@ -74,6 +76,7 @@ export class AvailableRasterLayers {
|
|||
}
|
||||
return GeoOperations.inside(lonlat, eliPolygon)
|
||||
})
|
||||
matching.unshift(AvailableRasterLayers.osmCarto)
|
||||
matching.push(AvailableRasterLayers.maptilerDefaultLayer)
|
||||
matching.push(...AvailableRasterLayers.globalLayers)
|
||||
return matching
|
||||
|
|
74
src/UI/AllThemesGui.svelte
Normal file
74
src/UI/AllThemesGui.svelte
Normal file
|
@ -0,0 +1,74 @@
|
|||
<script lang="ts">
|
||||
import { OsmConnectionFeatureSwitches } from "../Logic/State/FeatureSwitchState";
|
||||
import { OsmConnection } from "../Logic/Osm/OsmConnection";
|
||||
import { QueryParameters } from "../Logic/Web/QueryParameters";
|
||||
import UserRelatedState from "../Logic/State/UserRelatedState";
|
||||
import LanguagePicker from "./InputElement/LanguagePicker.svelte";
|
||||
import Translations from "./i18n/Translations";
|
||||
import Logo from "../assets/svg/Logo.svelte";
|
||||
import Tr from "./Base/Tr.svelte";
|
||||
import ToSvelte from "./Base/ToSvelte.svelte";
|
||||
import MoreScreen from "./BigComponents/MoreScreen";
|
||||
import LoginToggle from "./Base/LoginToggle.svelte";
|
||||
import Pencil from "../assets/svg/Pencil.svelte";
|
||||
import Login from "../assets/svg/Login.svelte";
|
||||
import Constants from "../Models/Constants";
|
||||
|
||||
const featureSwitches = new OsmConnectionFeatureSwitches();
|
||||
const osmConnection = new OsmConnection({
|
||||
fakeUser: featureSwitches.featureSwitchFakeUser.data,
|
||||
oauth_token: QueryParameters.GetQueryParameter(
|
||||
"oauth_token",
|
||||
undefined,
|
||||
"Used to complete the login"
|
||||
)
|
||||
});
|
||||
const state = new UserRelatedState(osmConnection);
|
||||
const t = Translations.t.index;
|
||||
let userLanguages = osmConnection.userDetails.map(ud => ud.languages);
|
||||
</script>
|
||||
|
||||
<div class="flex flex-col m-4">
|
||||
<div class="self-end">
|
||||
<LanguagePicker assignTo={state.language} availableLanguages={t.title.SupportedLanguages()}
|
||||
preferredLanguages={userLanguages} />
|
||||
</div>
|
||||
|
||||
<div class="flex mt-4">
|
||||
|
||||
<div class="flex-none m-3">
|
||||
<Logo alt="MapComplete Logo" class="w-12 h-12 sm:h-24 sm:w-24" />
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col">
|
||||
<h1 class="tracking-tight font-extrabold md:text-6xl m-0">
|
||||
<Tr t={t.title} />
|
||||
</h1>
|
||||
<Tr cls="my-4 mr-4 text-base font-semibold sm:text-lg md:mt-5 md:text-xl lg:mx-0"
|
||||
t={Translations.t.index.intro} />
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<ToSvelte construct={new MoreScreen(state, true)} />
|
||||
|
||||
<LoginToggle state={state}>
|
||||
<div slot="not-logged-in">
|
||||
<button class="w-full" on:click={() => osmConnection.AttemptLogin()}>
|
||||
<Login class="w-6 h-6 mr-2 "/>
|
||||
<Tr t={Translations.t.index.logIn} />
|
||||
</button>
|
||||
</div>
|
||||
<a class="w-full h-fit button" href={window.location.protocol + "//" + window.location.host + "/studio.html"}>
|
||||
<Pencil class="w-6 h-6 mr-2" />
|
||||
<Tr t={ Translations.t.general.morescreen.createYourOwnTheme} />
|
||||
</a>
|
||||
</LoginToggle>
|
||||
|
||||
<Tr cls="link-underline" t={Translations.t.general.aboutMapComplete.intro}/>
|
||||
<div class="subtle self-end mb-16">
|
||||
v{Constants.vNumber}
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -1,69 +0,0 @@
|
|||
import UserRelatedState from "../Logic/State/UserRelatedState"
|
||||
import { FixedUiElement } from "./Base/FixedUiElement"
|
||||
import Combine from "./Base/Combine"
|
||||
import MoreScreen from "./BigComponents/MoreScreen"
|
||||
import Translations from "./i18n/Translations"
|
||||
import Constants from "../Models/Constants"
|
||||
import LanguagePicker from "./LanguagePicker"
|
||||
import IndexText from "./BigComponents/IndexText"
|
||||
import { LoginToggle } from "./Popup/LoginButton"
|
||||
import { ImmutableStore } from "../Logic/UIEventSource"
|
||||
import { OsmConnection } from "../Logic/Osm/OsmConnection"
|
||||
import { QueryParameters } from "../Logic/Web/QueryParameters"
|
||||
import { OsmConnectionFeatureSwitches } from "../Logic/State/FeatureSwitchState"
|
||||
import { SubtleButton } from "./Base/SubtleButton"
|
||||
import Svg from "../Svg"
|
||||
import Link from "./Base/Link"
|
||||
|
||||
export default class AllThemesGui {
|
||||
setup() {
|
||||
try {
|
||||
const featureSwitches = new OsmConnectionFeatureSwitches()
|
||||
const osmConnection = new OsmConnection({
|
||||
fakeUser: featureSwitches.featureSwitchFakeUser.data,
|
||||
oauth_token: QueryParameters.GetQueryParameter(
|
||||
"oauth_token",
|
||||
undefined,
|
||||
"Used to complete the login"
|
||||
),
|
||||
})
|
||||
const state = new UserRelatedState(osmConnection)
|
||||
const intro = new Combine([
|
||||
new LanguagePicker(
|
||||
Translations.t.index.title.SupportedLanguages(),
|
||||
state.language
|
||||
).SetClass("flex absolute top-2 right-3"),
|
||||
new IndexText(),
|
||||
])
|
||||
new Combine([
|
||||
intro,
|
||||
new MoreScreen(state, true),
|
||||
new LoginToggle(
|
||||
new Link(
|
||||
new Combine([
|
||||
Svg.pencil_svg().SetClass("w-6 h-6 mr-2"),
|
||||
Translations.t.general.morescreen.createYourOwnTheme,
|
||||
]).SetClass("flex p-2"),
|
||||
window.location.protocol + "//" + window.location.host + "/studio.html"
|
||||
).SetClass("w-full h-fit button"),
|
||||
Translations.t.index.logIn,
|
||||
{
|
||||
osmConnection,
|
||||
featureSwitchUserbadge: new ImmutableStore(true),
|
||||
}
|
||||
).SetClass("flex justify-center w-full"),
|
||||
Translations.t.general.aboutMapComplete.intro.SetClass("link-underline"),
|
||||
new FixedUiElement("v" + Constants.vNumber).SetClass("block"),
|
||||
])
|
||||
.SetClass("block m-5 lg:w-3/4 lg:ml-40")
|
||||
.AttachTo("main")
|
||||
} catch (e) {
|
||||
console.error(">>>> CRITICAL", e)
|
||||
new FixedUiElement(
|
||||
"Seems like no layers are compiled - check the output of `npm run generate:layeroverview`. Is this visible online? Contact pietervdvn immediately!"
|
||||
)
|
||||
.SetClass("alert")
|
||||
.AttachTo("main")
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,14 +1,31 @@
|
|||
<script lang="ts">
|
||||
import { UIEventSource } from "../../Logic/UIEventSource.js"
|
||||
import { UIEventSource } from "../../Logic/UIEventSource.js";
|
||||
|
||||
/**
|
||||
* For some stupid reason, it is very hard to bind inputs
|
||||
*/
|
||||
export let value: UIEventSource<number>
|
||||
let i: number = value.data
|
||||
$: value.setData(i)
|
||||
export let value: UIEventSource<any>
|
||||
let i: any = value.data
|
||||
let htmlElement : HTMLSelectElement
|
||||
function selectAppropriateValue(){
|
||||
if(!htmlElement){
|
||||
return;
|
||||
}
|
||||
const v = value.data
|
||||
for (let option of htmlElement.getElementsByTagName("option")) {
|
||||
if(option.value === v){
|
||||
option.selected = true
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
value.addCallbackD(() => selectAppropriateValue())
|
||||
$: {
|
||||
if(htmlElement){
|
||||
selectAppropriateValue()
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<select bind:value={i}>
|
||||
<select bind:this={htmlElement} on:change={(e) => {value.setData(e.srcElement.value)}}>
|
||||
<slot />
|
||||
</select>
|
||||
|
|
|
@ -4,25 +4,7 @@ import { FixedUiElement } from "../Base/FixedUiElement"
|
|||
|
||||
export default class IndexText extends Combine {
|
||||
constructor() {
|
||||
super([
|
||||
new FixedUiElement(
|
||||
`<img class="w-12 h-12 sm:h-24 sm:w-24" src="./assets/svg/logo.svg" alt="MapComplete Logo">`
|
||||
).SetClass("flex-none m-3"),
|
||||
|
||||
new Combine([
|
||||
Translations.t.index.title.SetClass(
|
||||
"text-2xl tracking-tight font-extrabold text-gray-900 sm:text-5xl md:text-6xl block text-gray-800 xl:inline"
|
||||
),
|
||||
|
||||
Translations.t.index.intro.SetClass(
|
||||
"mt-3 text-base font-semibold text-gray-500 sm:mt-5 sm:text-lg sm:max-w-xl sm:mx-auto md:mt-5 md:text-xl lg:mx-0"
|
||||
),
|
||||
|
||||
Translations.t.index.pickTheme.SetClass(
|
||||
"mt-3 text-base sm:mt-5 sm:text-lg sm:max-w-xl sm:mx-auto md:mt-5 md:text-xl lg:mx-0"
|
||||
),
|
||||
]).SetClass("flex flex-col sm:text-center lg:text-left m-1 mt-2 md:m-2 md:mt-4"),
|
||||
])
|
||||
super([])
|
||||
|
||||
this.SetClass("flex flex-row")
|
||||
}
|
||||
|
|
67
src/UI/InputElement/LanguagePicker.svelte
Normal file
67
src/UI/InputElement/LanguagePicker.svelte
Normal file
|
@ -0,0 +1,67 @@
|
|||
<script lang="ts">
|
||||
// Languages in the language itself
|
||||
import native from "../../assets/language_native.json";
|
||||
// Translated languages
|
||||
import language_translations from "../../assets/language_translations.json";
|
||||
|
||||
import { UIEventSource } from "../../Logic/UIEventSource";
|
||||
import Locale from "../i18n/Locale";
|
||||
import { LanguageIcon } from "@babeard/svelte-heroicons/solid";
|
||||
import Dropdown from "../Base/Dropdown.svelte";
|
||||
|
||||
/**
|
||||
* Languages one can choose from
|
||||
* Defaults to _all_ languages known by MapComplete
|
||||
*/
|
||||
export let availableLanguages: string[] = Object.keys(native);
|
||||
/**
|
||||
* EventStore to assign to, defaults to 'Locale.langauge'
|
||||
*/
|
||||
export let assignTo: UIEventSource<string> = Locale.language;
|
||||
export let preferredLanguages: UIEventSource<string[]>;
|
||||
let preferredFiltered: string[] = undefined;
|
||||
preferredLanguages.addCallbackAndRunD(preferredLanguages => {
|
||||
|
||||
let lng = navigator.language;
|
||||
if (lng === "en-US") {
|
||||
lng = "en";
|
||||
}
|
||||
if (preferredLanguages?.indexOf(lng) < 0) {
|
||||
preferredLanguages?.push(lng);
|
||||
}
|
||||
preferredFiltered = preferredLanguages?.filter(l => availableLanguages.indexOf(l) >= 0);
|
||||
});
|
||||
|
||||
let current = Locale.language;
|
||||
|
||||
</script>
|
||||
|
||||
{#if availableLanguages?.length > 1}
|
||||
<form class="flex items-center">
|
||||
|
||||
<LanguageIcon class="h-4 w-4 mr-1" />
|
||||
<Dropdown value={assignTo}>
|
||||
{#if preferredFiltered}
|
||||
{#each preferredFiltered as language}
|
||||
<option value={language} class="font-bold">
|
||||
{native[language] ?? ""}
|
||||
{#if language !== $current}
|
||||
({language_translations[language]?.[$current] ?? language})
|
||||
{/if}
|
||||
</option>
|
||||
{/each}
|
||||
<option disabled></option>
|
||||
{/if}
|
||||
|
||||
{#each availableLanguages as language}
|
||||
<option value={language} class="font-bold">
|
||||
{native[language] ?? ""}
|
||||
{#if language !== $current}
|
||||
({(language_translations[language]?.[$current] + " - " + language) ?? language})
|
||||
{/if}
|
||||
</option>
|
||||
{/each}
|
||||
</Dropdown>
|
||||
</form>
|
||||
|
||||
{/if}
|
|
@ -1,67 +0,0 @@
|
|||
import { DropDown } from "./Input/DropDown"
|
||||
import Locale from "./i18n/Locale"
|
||||
import BaseUIElement from "./BaseUIElement"
|
||||
import native from "../assets/language_native.json"
|
||||
import language_translations from "../assets/language_translations.json"
|
||||
import { Translation } from "./i18n/Translation"
|
||||
import Lazy from "./Base/Lazy"
|
||||
import Toggle from "./Input/Toggle"
|
||||
import LanguageUtils from "../Utils/LanguageUtils"
|
||||
import { UIEventSource } from "../Logic/UIEventSource"
|
||||
import { QueryParameters } from "../Logic/Web/QueryParameters"
|
||||
|
||||
export default class LanguagePicker extends Toggle {
|
||||
constructor(languages: string[], assignTo: UIEventSource<string>) {
|
||||
console.log("Constructing a language picker for languages", languages)
|
||||
if (
|
||||
languages === undefined ||
|
||||
languages.length <= 1 ||
|
||||
QueryParameters.wasInitialized("language")
|
||||
) {
|
||||
super(undefined, undefined, undefined)
|
||||
} else {
|
||||
const normalPicker = LanguagePicker.dropdownFor(languages, assignTo ?? Locale.language)
|
||||
const fullPicker = new Lazy(() =>
|
||||
LanguagePicker.dropdownFor(allLanguages, assignTo ?? Locale.language)
|
||||
)
|
||||
super(fullPicker, normalPicker, Locale.showLinkToWeblate)
|
||||
const allLanguages: string[] = LanguageUtils.usedLanguagesSorted
|
||||
}
|
||||
}
|
||||
|
||||
private static dropdownFor(
|
||||
languages: string[],
|
||||
assignTo: UIEventSource<string>
|
||||
): BaseUIElement {
|
||||
return new DropDown(
|
||||
undefined,
|
||||
languages
|
||||
.filter((lang) => lang !== "_context")
|
||||
.map((lang) => {
|
||||
return { value: lang, shown: LanguagePicker.hybrid(lang) }
|
||||
}),
|
||||
assignTo
|
||||
)
|
||||
}
|
||||
|
||||
private static hybrid(lang: string): Translation {
|
||||
const nativeText = native[lang] ?? lang
|
||||
const translation = {}
|
||||
const trans = language_translations[lang]
|
||||
if (trans === undefined) {
|
||||
return new Translation({ "*": nativeText })
|
||||
}
|
||||
for (const key in trans) {
|
||||
if (key.startsWith("_")) {
|
||||
continue
|
||||
}
|
||||
const translationInKey = language_translations[lang][key]
|
||||
if (nativeText.toLowerCase() === translationInKey.toLowerCase()) {
|
||||
translation[key] = nativeText
|
||||
} else {
|
||||
translation[key] = nativeText + " (" + translationInKey + ")"
|
||||
}
|
||||
}
|
||||
return new Translation(translation)
|
||||
}
|
||||
}
|
|
@ -45,7 +45,6 @@ import { GeoOperations } from "../Logic/GeoOperations"
|
|||
import CreateNewNote from "./Popup/CreateNewNote.svelte"
|
||||
import AddNewPoint from "./Popup/AddNewPoint/AddNewPoint.svelte"
|
||||
import UserProfile from "./BigComponents/UserProfile.svelte"
|
||||
import LanguagePicker from "./LanguagePicker"
|
||||
import Link from "./Base/Link"
|
||||
import LayerConfig from "../Models/ThemeConfig/LayerConfig"
|
||||
import TagRenderingConfig from "../Models/ThemeConfig/TagRenderingConfig"
|
||||
|
@ -78,6 +77,7 @@ import Questionbox from "./Popup/TagRendering/Questionbox.svelte"
|
|||
import { TagUtils } from "../Logic/Tags/TagUtils"
|
||||
import Giggity from "./BigComponents/Giggity.svelte"
|
||||
import ThemeViewState from "../Models/ThemeViewState"
|
||||
import LanguagePicker from "./InputElement/LanguagePicker.svelte"
|
||||
|
||||
class NearbyImageVis implements SpecialVisualization {
|
||||
// Class must be in SpecialVisualisations due to weird cyclical import that breaks the tests
|
||||
|
@ -453,10 +453,10 @@ export default class SpecialVisualizations {
|
|||
needsUrls: [],
|
||||
docs: "A component to set the language of the user interface",
|
||||
constr(state: SpecialVisualizationState): BaseUIElement {
|
||||
return new LanguagePicker(
|
||||
state.layout.language,
|
||||
state.userRelatedState.language
|
||||
)
|
||||
return new SvelteUIElement(LanguagePicker, {
|
||||
assignTo: state.userRelatedState.language,
|
||||
availableLanguages: state.layout.language,
|
||||
})
|
||||
},
|
||||
},
|
||||
{
|
||||
|
|
|
@ -1,16 +1,15 @@
|
|||
<script lang="ts">
|
||||
// Testing grounds
|
||||
import { UIEventSource } from "../Logic/UIEventSource"
|
||||
import TabbedGroup from "./Base/TabbedGroup.svelte"
|
||||
|
||||
let tab = new UIEventSource(1)
|
||||
console.log("Tab control", tab)
|
||||
import LanguagePicker from "./InputElement/LanguagePicker.svelte";
|
||||
import Translations from "./i18n/Translations";
|
||||
import Tr from "./Base/Tr.svelte";
|
||||
import Locale from "./i18n/Locale";
|
||||
let language = Locale.language
|
||||
</script>
|
||||
|
||||
<TabbedGroup {tab}>
|
||||
<div slot="title0">Title 0</div>
|
||||
<div slot="content0">Content 0 loaded</div>
|
||||
<div class="w-full">
|
||||
<LanguagePicker preferredLanguages={["nl", "en"]}/>
|
||||
|
||||
<div slot="title1">Title 1</div>
|
||||
<div slot="content1">Content 1</div>
|
||||
</TabbedGroup>
|
||||
<Tr t={Translations.t.general.download.downloadAsPdf}/>
|
||||
{$language}
|
||||
</div>
|
||||
|
|
|
@ -48,13 +48,13 @@
|
|||
import OpenIdEditor from "./BigComponents/OpenIdEditor.svelte";
|
||||
import OpenBackgroundSelectorButton from "./BigComponents/OpenBackgroundSelectorButton.svelte";
|
||||
import StateIndicator from "./BigComponents/StateIndicator.svelte";
|
||||
import LanguagePicker from "./LanguagePicker";
|
||||
import Locale from "./i18n/Locale";
|
||||
import ShareScreen from "./BigComponents/ShareScreen.svelte";
|
||||
import UploadingImageCounter from "./Image/UploadingImageCounter.svelte";
|
||||
import PendingChangesIndicator from "./BigComponents/PendingChangesIndicator.svelte";
|
||||
import Cross from "../assets/svg/Cross.svelte";
|
||||
import Summary from "./BigComponents/Summary.svelte";
|
||||
import LanguagePicker from "./InputElement/LanguagePicker.svelte";
|
||||
|
||||
export let state: ThemeViewState;
|
||||
let layout = state.layout;
|
||||
|
@ -263,7 +263,7 @@
|
|||
</div>
|
||||
|
||||
<LoginToggle ignoreLoading={true} {state}>
|
||||
{#if $showCrosshair === "yes" && ($currentZoom >= 17 || $arrowKeysWereUsed !== undefined) }
|
||||
{#if ($showCrosshair === "yes" && $currentZoom >= 17) || $showCrosshair === "always" || $arrowKeysWereUsed !== undefined }
|
||||
<div
|
||||
class="pointer-events-none absolute top-0 left-0 flex h-full w-full items-center justify-center"
|
||||
>
|
||||
|
@ -466,7 +466,7 @@
|
|||
<!-- All shown components are set by 'usersettings.json', which happily uses some special visualisations created specifically for it -->
|
||||
<LoginToggle {state}>
|
||||
<div class="flex flex-col" slot="not-logged-in">
|
||||
<ToSvelte construct={() => new LanguagePicker(layout.language, Locale.language)} />
|
||||
<LanguagePicker availableLanguages={layout.language} />
|
||||
<Tr cls="alert" t={Translations.t.userinfo.notLoggedIn} />
|
||||
<LoginButton clss="primary" osmConnection={state.osmConnection} />
|
||||
</div>
|
||||
|
|
|
@ -56,6 +56,9 @@ export default class Locale {
|
|||
if (typeof navigator !== "undefined") {
|
||||
browserLanguage = navigator.languages?.[0] ?? navigator.language ?? "en"
|
||||
console.log("Browser language is", browserLanguage)
|
||||
if (browserLanguage === "en-US") {
|
||||
browserLanguage = "en"
|
||||
}
|
||||
}
|
||||
source = LocalStorageSource.Get("language", browserLanguage)
|
||||
}
|
||||
|
@ -75,6 +78,7 @@ export default class Locale {
|
|||
Locale.showLinkToWeblate.setData(Locale.showLinkToWeblate.data || tr)
|
||||
})
|
||||
|
||||
console.log("Initial language:", source, source.data)
|
||||
return source
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import AllThemesGui from "./UI/AllThemesGui"
|
||||
import { QueryParameters } from "./Logic/Web/QueryParameters"
|
||||
import SvelteUIElement from "./UI/Base/SvelteUIElement"
|
||||
import AllThemesGui from "./UI/AllThemesGui.svelte"
|
||||
|
||||
const layout = QueryParameters.GetQueryParameter("layout", undefined).data ?? ""
|
||||
const customLayout = QueryParameters.GetQueryParameter("userlayout", undefined).data ?? ""
|
||||
|
@ -27,4 +28,4 @@ if (layout !== "") {
|
|||
)
|
||||
}
|
||||
|
||||
new AllThemesGui().setup()
|
||||
new SvelteUIElement(AllThemesGui, {}).AttachTo("main")
|
||||
|
|
21
src/test.ts
21
src/test.ts
|
@ -1,19 +1,4 @@
|
|||
import { Utils } from "./Utils"
|
||||
import SvelteUIElement from "./UI/Base/SvelteUIElement"
|
||||
import PointRenderingConfig from "./Models/ThemeConfig/PointRenderingConfig"
|
||||
import { UIEventSource } from "./Logic/UIEventSource"
|
||||
import Marker from "./UI/Map/Marker.svelte"
|
||||
import Qrcode from "qrcode-generator"
|
||||
import { FixedUiElement } from "./UI/Base/FixedUiElement"
|
||||
function generateQr(message: string, attachTo: string) {
|
||||
const typeNumber = 0
|
||||
const errorCorrectionLevel = "L"
|
||||
const qr = Qrcode(typeNumber, errorCorrectionLevel)
|
||||
qr.addData(message)
|
||||
qr.make()
|
||||
document.getElementById(attachTo).innerHTML = qr.createImgTag()
|
||||
}
|
||||
generateQr(
|
||||
"http://127.0.0.1:1234/theme.html?layout=cyclofix&z=14&lat=51.21571770000094&lon=3.219866599996749&layer-range=true&layer-gps_location=false#theme-menu:download",
|
||||
"qr"
|
||||
)
|
||||
import Test from "./UI/Test.svelte"
|
||||
|
||||
new SvelteUIElement(Test, {}).AttachTo("maindiv")
|
||||
|
|
Loading…
Reference in a new issue