Split up allKnownLayouts, make parsing it lazy for faster loading
This commit is contained in:
parent
6dc0fa0851
commit
6ee85b12f8
14 changed files with 311 additions and 323 deletions
|
@ -1,298 +1,52 @@
|
||||||
import known_themes from "../assets/generated/known_layers_and_themes.json"
|
import known_themes from "../assets/generated/known_themes.json"
|
||||||
import LayoutConfig from "../Models/ThemeConfig/LayoutConfig"
|
import LayoutConfig from "../Models/ThemeConfig/LayoutConfig"
|
||||||
import LayerConfig from "../Models/ThemeConfig/LayerConfig"
|
|
||||||
import BaseUIElement from "../UI/BaseUIElement"
|
|
||||||
import Combine from "../UI/Base/Combine"
|
|
||||||
import Title from "../UI/Base/Title"
|
|
||||||
import List from "../UI/Base/List"
|
|
||||||
import DependencyCalculator from "../Models/ThemeConfig/DependencyCalculator"
|
|
||||||
import Constants from "../Models/Constants"
|
|
||||||
import { Utils } from "../Utils"
|
|
||||||
import Link from "../UI/Base/Link"
|
|
||||||
import { LayoutConfigJson } from "../Models/ThemeConfig/Json/LayoutConfigJson"
|
import { LayoutConfigJson } from "../Models/ThemeConfig/Json/LayoutConfigJson"
|
||||||
import { LayerConfigJson } from "../Models/ThemeConfig/Json/LayerConfigJson"
|
|
||||||
|
|
||||||
export class AllKnownLayouts {
|
|
||||||
public static allKnownLayouts: Map<string, LayoutConfig> = AllKnownLayouts.AllLayouts()
|
|
||||||
public static layoutsList: LayoutConfig[] = AllKnownLayouts.GenerateOrderedList(
|
|
||||||
AllKnownLayouts.allKnownLayouts
|
|
||||||
)
|
|
||||||
// Must be below the list...
|
|
||||||
private static sharedLayers: Map<string, LayerConfig> = AllKnownLayouts.getSharedLayers()
|
|
||||||
|
|
||||||
public static AllPublicLayers(options?: {
|
|
||||||
includeInlineLayers: true | boolean
|
|
||||||
}): LayerConfig[] {
|
|
||||||
const allLayers: LayerConfig[] = []
|
|
||||||
const seendIds = new Set<string>()
|
|
||||||
AllKnownLayouts.sharedLayers.forEach((layer, key) => {
|
|
||||||
seendIds.add(key)
|
|
||||||
allLayers.push(layer)
|
|
||||||
})
|
|
||||||
if (options?.includeInlineLayers ?? true) {
|
|
||||||
const publicLayouts = AllKnownLayouts.layoutsList.filter((l) => !l.hideFromOverview)
|
|
||||||
for (const layout of publicLayouts) {
|
|
||||||
if (layout.hideFromOverview) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
for (const layer of layout.layers) {
|
|
||||||
if (seendIds.has(layer.id)) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
seendIds.add(layer.id)
|
|
||||||
allLayers.push(layer)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return allLayers
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns all themes which use the given layer, reverse sorted by minzoom. This sort maximizes the chances that the layer is prominently featured on the first theme
|
* Somewhat of a dictionary, which lazily parses needed themes
|
||||||
*/
|
*/
|
||||||
public static themesUsingLayer(id: string, publicOnly = true): LayoutConfig[] {
|
export class AllKnownLayoutsLazy {
|
||||||
const themes = AllKnownLayouts.layoutsList
|
private readonly dict: Map<string, { data: LayoutConfig } | { func: () => LayoutConfig }> =
|
||||||
.filter((l) => !(publicOnly && l.hideFromOverview) && l.id !== "personal")
|
new Map()
|
||||||
.map((theme) => ({
|
constructor() {
|
||||||
theme,
|
|
||||||
minzoom: theme.layers.find((layer) => layer.id === id)?.minzoom,
|
|
||||||
}))
|
|
||||||
.filter((obj) => obj.minzoom !== undefined)
|
|
||||||
themes.sort((th0, th1) => th1.minzoom - th0.minzoom)
|
|
||||||
return themes.map((th) => th.theme)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Generates documentation for the layers.
|
|
||||||
* Inline layers are included (if the theme is public)
|
|
||||||
* @param callback
|
|
||||||
* @constructor
|
|
||||||
*/
|
|
||||||
public static GenOverviewsForSingleLayer(
|
|
||||||
callback: (layer: LayerConfig, element: BaseUIElement, inlineSource: string) => void
|
|
||||||
): void {
|
|
||||||
const allLayers: LayerConfig[] = Array.from(AllKnownLayouts.sharedLayers.values()).filter(
|
|
||||||
(layer) => Constants.priviliged_layers.indexOf(layer.id) < 0
|
|
||||||
)
|
|
||||||
const builtinLayerIds: Set<string> = new Set<string>()
|
|
||||||
allLayers.forEach((l) => builtinLayerIds.add(l.id))
|
|
||||||
const inlineLayers = new Map<string, string>()
|
|
||||||
|
|
||||||
for (const layout of Array.from(AllKnownLayouts.allKnownLayouts.values())) {
|
|
||||||
if (layout.hideFromOverview) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const layer of layout.layers) {
|
|
||||||
if (Constants.priviliged_layers.indexOf(layer.id) >= 0) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if (builtinLayerIds.has(layer.id)) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if (layer.source.geojsonSource !== undefined) {
|
|
||||||
// Not an OSM-source
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
allLayers.push(layer)
|
|
||||||
builtinLayerIds.add(layer.id)
|
|
||||||
inlineLayers.set(layer.id, layout.id)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const themesPerLayer = new Map<string, string[]>()
|
|
||||||
|
|
||||||
for (const layout of Array.from(AllKnownLayouts.allKnownLayouts.values())) {
|
|
||||||
if (layout.hideFromOverview) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
for (const layer of layout.layers) {
|
|
||||||
if (!builtinLayerIds.has(layer.id)) {
|
|
||||||
// This is an inline layer
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if (!themesPerLayer.has(layer.id)) {
|
|
||||||
themesPerLayer.set(layer.id, [])
|
|
||||||
}
|
|
||||||
themesPerLayer.get(layer.id).push(layout.id)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Determine the cross-dependencies
|
|
||||||
const layerIsNeededBy: Map<string, string[]> = new Map<string, string[]>()
|
|
||||||
|
|
||||||
for (const layer of allLayers) {
|
|
||||||
for (const dep of DependencyCalculator.getLayerDependencies(layer)) {
|
|
||||||
const dependency = dep.neededLayer
|
|
||||||
if (!layerIsNeededBy.has(dependency)) {
|
|
||||||
layerIsNeededBy.set(dependency, [])
|
|
||||||
}
|
|
||||||
layerIsNeededBy.get(dependency).push(layer.id)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
allLayers.forEach((layer) => {
|
|
||||||
const element = layer.GenerateDocumentation(
|
|
||||||
themesPerLayer.get(layer.id),
|
|
||||||
layerIsNeededBy,
|
|
||||||
DependencyCalculator.getLayerDependencies(layer)
|
|
||||||
)
|
|
||||||
callback(layer, element, inlineLayers.get(layer.id))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Generates the documentation for the layers overview page
|
|
||||||
* @constructor
|
|
||||||
*/
|
|
||||||
public static GenLayerOverviewText(): BaseUIElement {
|
|
||||||
for (const id of Constants.priviliged_layers) {
|
|
||||||
if (!AllKnownLayouts.sharedLayers.has(id)) {
|
|
||||||
throw "Priviliged layer definition not found: " + id
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const allLayers: LayerConfig[] = Array.from(AllKnownLayouts.sharedLayers.values()).filter(
|
|
||||||
(layer) => Constants.priviliged_layers.indexOf(layer.id) < 0
|
|
||||||
)
|
|
||||||
|
|
||||||
const builtinLayerIds: Set<string> = new Set<string>()
|
|
||||||
allLayers.forEach((l) => builtinLayerIds.add(l.id))
|
|
||||||
|
|
||||||
const themesPerLayer = new Map<string, string[]>()
|
|
||||||
|
|
||||||
for (const layout of Array.from(AllKnownLayouts.allKnownLayouts.values())) {
|
|
||||||
for (const layer of layout.layers) {
|
|
||||||
if (!builtinLayerIds.has(layer.id)) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if (!themesPerLayer.has(layer.id)) {
|
|
||||||
themesPerLayer.set(layer.id, [])
|
|
||||||
}
|
|
||||||
themesPerLayer.get(layer.id).push(layout.id)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Determine the cross-dependencies
|
|
||||||
const layerIsNeededBy: Map<string, string[]> = new Map<string, string[]>()
|
|
||||||
|
|
||||||
for (const layer of allLayers) {
|
|
||||||
for (const dep of DependencyCalculator.getLayerDependencies(layer)) {
|
|
||||||
const dependency = dep.neededLayer
|
|
||||||
if (!layerIsNeededBy.has(dependency)) {
|
|
||||||
layerIsNeededBy.set(dependency, [])
|
|
||||||
}
|
|
||||||
layerIsNeededBy.get(dependency).push(layer.id)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return new Combine([
|
|
||||||
new Title("Special and other useful layers", 1),
|
|
||||||
"MapComplete has a few data layers available in the theme which have special properties through builtin-hooks. Furthermore, there are some normal layers (which are built from normal Theme-config files) but are so general that they get a mention here.",
|
|
||||||
new Title("Priviliged layers", 1),
|
|
||||||
new List(Constants.priviliged_layers.map((id) => "[" + id + "](#" + id + ")")),
|
|
||||||
...Constants.priviliged_layers
|
|
||||||
.map((id) => AllKnownLayouts.sharedLayers.get(id))
|
|
||||||
.map((l) =>
|
|
||||||
l.GenerateDocumentation(
|
|
||||||
themesPerLayer.get(l.id),
|
|
||||||
layerIsNeededBy,
|
|
||||||
DependencyCalculator.getLayerDependencies(l),
|
|
||||||
Constants.added_by_default.indexOf(l.id) >= 0,
|
|
||||||
Constants.no_include.indexOf(l.id) < 0
|
|
||||||
)
|
|
||||||
),
|
|
||||||
new Title("Normal layers", 1),
|
|
||||||
"The following layers are included in MapComplete:",
|
|
||||||
new List(
|
|
||||||
Array.from(AllKnownLayouts.sharedLayers.keys()).map(
|
|
||||||
(id) => new Link(id, "./Layers/" + id + ".md")
|
|
||||||
)
|
|
||||||
),
|
|
||||||
])
|
|
||||||
}
|
|
||||||
|
|
||||||
public static GenerateDocumentationForTheme(theme: LayoutConfig): BaseUIElement {
|
|
||||||
return new Combine([
|
|
||||||
new Title(
|
|
||||||
new Combine([
|
|
||||||
theme.title,
|
|
||||||
"(",
|
|
||||||
new Link(theme.id, "https://mapcomplete.osm.be/" + theme.id),
|
|
||||||
")",
|
|
||||||
]),
|
|
||||||
2
|
|
||||||
),
|
|
||||||
theme.description,
|
|
||||||
"This theme contains the following layers:",
|
|
||||||
new List(
|
|
||||||
theme.layers
|
|
||||||
.filter((l) => !l.id.startsWith("note_import_"))
|
|
||||||
.map((l) => new Link(l.id, "../Layers/" + l.id + ".md"))
|
|
||||||
),
|
|
||||||
"Available languages:",
|
|
||||||
new List(theme.language.filter((ln) => ln !== "_context")),
|
|
||||||
]).SetClass("flex flex-col")
|
|
||||||
}
|
|
||||||
|
|
||||||
public static getSharedLayers(): Map<string, LayerConfig> {
|
|
||||||
const sharedLayers = new Map<string, LayerConfig>()
|
|
||||||
for (const layer of known_themes["layers"]) {
|
|
||||||
try {
|
|
||||||
// @ts-ignore
|
|
||||||
const parsed = new LayerConfig(layer, "shared_layers")
|
|
||||||
sharedLayers.set(layer.id, parsed)
|
|
||||||
} catch (e) {
|
|
||||||
if (!Utils.runningFromConsole) {
|
|
||||||
console.error(
|
|
||||||
"CRITICAL: Could not parse a layer configuration!",
|
|
||||||
layer.id,
|
|
||||||
" due to",
|
|
||||||
e
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return sharedLayers
|
|
||||||
}
|
|
||||||
|
|
||||||
public static getSharedLayersConfigs(): Map<string, LayerConfigJson> {
|
|
||||||
const sharedLayers = new Map<string, LayerConfigJson>()
|
|
||||||
for (const layer of known_themes["layers"]) {
|
|
||||||
// @ts-ignore
|
|
||||||
sharedLayers.set(layer.id, layer)
|
|
||||||
}
|
|
||||||
|
|
||||||
return sharedLayers
|
|
||||||
}
|
|
||||||
|
|
||||||
private static GenerateOrderedList(allKnownLayouts: Map<string, LayoutConfig>): LayoutConfig[] {
|
|
||||||
const list = []
|
|
||||||
allKnownLayouts.forEach((layout) => {
|
|
||||||
list.push(layout)
|
|
||||||
})
|
|
||||||
return list
|
|
||||||
}
|
|
||||||
|
|
||||||
private static AllLayouts(): Map<string, LayoutConfig> {
|
|
||||||
const dict: Map<string, LayoutConfig> = new Map()
|
|
||||||
for (const layoutConfigJson of known_themes["themes"]) {
|
for (const layoutConfigJson of known_themes["themes"]) {
|
||||||
|
this.dict.set(layoutConfigJson.id, {
|
||||||
|
func: () => {
|
||||||
const layout = new LayoutConfig(<LayoutConfigJson>layoutConfigJson, true)
|
const layout = new LayoutConfig(<LayoutConfigJson>layoutConfigJson, true)
|
||||||
dict.set(layout.id, layout)
|
|
||||||
for (let i = 0; i < layout.layers.length; i++) {
|
for (let i = 0; i < layout.layers.length; i++) {
|
||||||
let layer = layout.layers[i]
|
let layer = layout.layers[i]
|
||||||
if (typeof layer === "string") {
|
if (typeof layer === "string") {
|
||||||
layer = AllKnownLayouts.sharedLayers.get(layer)
|
throw "Layer " + layer + " was not expanded in " + layout.id
|
||||||
layout.layers[i] = layer
|
|
||||||
if (layer === undefined) {
|
|
||||||
console.log("Defined layers are ", AllKnownLayouts.sharedLayers.keys())
|
|
||||||
throw `Layer ${layer} was not found or defined - probably a type was made`
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return layout
|
||||||
|
},
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return dict
|
|
||||||
|
public get(key: string): LayoutConfig {
|
||||||
|
const thunk = this.dict.get(key)
|
||||||
|
if (thunk === undefined) {
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
if (thunk["data"]) {
|
||||||
|
return thunk["data"]
|
||||||
|
}
|
||||||
|
const layout = thunk["func"]()
|
||||||
|
this.dict.set(key, { data: layout })
|
||||||
|
return layout
|
||||||
|
}
|
||||||
|
|
||||||
|
public keys() {
|
||||||
|
return this.dict.keys()
|
||||||
|
}
|
||||||
|
|
||||||
|
public values() {
|
||||||
|
return Array.from(this.keys()).map((k) => this.get(k))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class AllKnownLayouts {
|
||||||
|
public static allKnownLayouts: AllKnownLayoutsLazy = new AllKnownLayoutsLazy()
|
||||||
|
}
|
||||||
|
|
69
Customizations/AllSharedLayers.ts
Normal file
69
Customizations/AllSharedLayers.ts
Normal file
|
@ -0,0 +1,69 @@
|
||||||
|
import LayerConfig from "../Models/ThemeConfig/LayerConfig"
|
||||||
|
import { Utils } from "../Utils"
|
||||||
|
import known_themes from "../assets/generated/known_layers.json"
|
||||||
|
import { LayerConfigJson } from "../Models/ThemeConfig/Json/LayerConfigJson"
|
||||||
|
import { ALL } from "dns"
|
||||||
|
import { AllKnownLayouts } from "./AllKnownLayouts"
|
||||||
|
export class AllSharedLayers {
|
||||||
|
public static sharedLayers: Map<string, LayerConfig> = AllSharedLayers.getSharedLayers()
|
||||||
|
public static getSharedLayersConfigs(): Map<string, LayerConfigJson> {
|
||||||
|
const sharedLayers = new Map<string, LayerConfigJson>()
|
||||||
|
for (const layer of known_themes.layers) {
|
||||||
|
// @ts-ignore
|
||||||
|
sharedLayers.set(layer.id, layer)
|
||||||
|
}
|
||||||
|
|
||||||
|
return sharedLayers
|
||||||
|
}
|
||||||
|
private static getSharedLayers(): Map<string, LayerConfig> {
|
||||||
|
const sharedLayers = new Map<string, LayerConfig>()
|
||||||
|
for (const layer of known_themes.layers) {
|
||||||
|
try {
|
||||||
|
// @ts-ignore
|
||||||
|
const parsed = new LayerConfig(layer, "shared_layers")
|
||||||
|
sharedLayers.set(layer.id, parsed)
|
||||||
|
} catch (e) {
|
||||||
|
if (!Utils.runningFromConsole) {
|
||||||
|
console.error(
|
||||||
|
"CRITICAL: Could not parse a layer configuration!",
|
||||||
|
layer.id,
|
||||||
|
" due to",
|
||||||
|
e
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return sharedLayers
|
||||||
|
}
|
||||||
|
|
||||||
|
public static AllPublicLayers(options?: {
|
||||||
|
includeInlineLayers: true | boolean
|
||||||
|
}): LayerConfig[] {
|
||||||
|
const allLayers: LayerConfig[] = []
|
||||||
|
const seendIds = new Set<string>()
|
||||||
|
AllSharedLayers.sharedLayers.forEach((layer, key) => {
|
||||||
|
seendIds.add(key)
|
||||||
|
allLayers.push(layer)
|
||||||
|
})
|
||||||
|
if (options?.includeInlineLayers ?? true) {
|
||||||
|
const publicLayouts = Array.from(AllKnownLayouts.allKnownLayouts.values()).filter(
|
||||||
|
(l) => !l.hideFromOverview
|
||||||
|
)
|
||||||
|
for (const layout of publicLayouts) {
|
||||||
|
if (layout.hideFromOverview) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
for (const layer of layout.layers) {
|
||||||
|
if (seendIds.has(layer.id)) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
seendIds.add(layer.id)
|
||||||
|
allLayers.push(layer)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return allLayers
|
||||||
|
}
|
||||||
|
}
|
|
@ -30,7 +30,7 @@ export default class DetermineLayout {
|
||||||
/**
|
/**
|
||||||
* Gets the correct layout for this website
|
* Gets the correct layout for this website
|
||||||
*/
|
*/
|
||||||
public static async GetLayout(): Promise<LayoutConfig> {
|
public static async GetLayout(): Promise<LayoutConfig | undefined> {
|
||||||
const loadCustomThemeParam = QueryParameters.GetQueryParameter(
|
const loadCustomThemeParam = QueryParameters.GetQueryParameter(
|
||||||
"userlayout",
|
"userlayout",
|
||||||
"false",
|
"false",
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { Utils } from "../Utils"
|
import { Utils } from "../Utils"
|
||||||
|
|
||||||
export default class Constants {
|
export default class Constants {
|
||||||
public static vNumber = "0.26.1"
|
public static vNumber = "0.26.2"
|
||||||
|
|
||||||
public static ImgurApiKey = "7070e7167f0a25a"
|
public static ImgurApiKey = "7070e7167f0a25a"
|
||||||
public static readonly mapillary_client_token_v4 =
|
public static readonly mapillary_client_token_v4 =
|
||||||
|
|
|
@ -35,7 +35,7 @@ export default class AllThemesGui {
|
||||||
])
|
])
|
||||||
.SetClass("block m-5 lg:w-3/4 lg:ml-40")
|
.SetClass("block m-5 lg:w-3/4 lg:ml-40")
|
||||||
.SetStyle("pointer-events: all;")
|
.SetStyle("pointer-events: all;")
|
||||||
.AttachTo("topleft-tools")
|
.AttachTo("top-left")
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(">>>> CRITICAL", e)
|
console.error(">>>> CRITICAL", e)
|
||||||
new FixedUiElement(
|
new FixedUiElement(
|
||||||
|
|
|
@ -3,7 +3,6 @@ import Svg from "../../Svg"
|
||||||
import Combine from "../Base/Combine"
|
import Combine from "../Base/Combine"
|
||||||
import { SubtleButton } from "../Base/SubtleButton"
|
import { SubtleButton } from "../Base/SubtleButton"
|
||||||
import Translations from "../i18n/Translations"
|
import Translations from "../i18n/Translations"
|
||||||
import personal from "../../assets/themes/personal/personal.json"
|
|
||||||
import Constants from "../../Models/Constants"
|
import Constants from "../../Models/Constants"
|
||||||
import BaseUIElement from "../BaseUIElement"
|
import BaseUIElement from "../BaseUIElement"
|
||||||
import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig"
|
import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig"
|
||||||
|
@ -382,7 +381,7 @@ export default class MoreScreen extends Combine {
|
||||||
return undefined
|
return undefined
|
||||||
}
|
}
|
||||||
const button = MoreScreen.createLinkButton(state, layout)?.SetClass(buttonClass)
|
const button = MoreScreen.createLinkButton(state, layout)?.SetClass(buttonClass)
|
||||||
if (layout.id === personal.id) {
|
if (layout.id === "personal") {
|
||||||
const element = new VariableUiElement(
|
const element = new VariableUiElement(
|
||||||
state.osmConnection.userDetails
|
state.osmConnection.userDetails
|
||||||
.map((userdetails) => userdetails.csCount)
|
.map((userdetails) => userdetails.csCount)
|
||||||
|
|
|
@ -37,7 +37,7 @@ export default class SelectTheme
|
||||||
|
|
||||||
constructor(params: { features: any[]; layer: LayerConfig; bbox: BBox }) {
|
constructor(params: { features: any[]; layer: LayerConfig; bbox: BBox }) {
|
||||||
const t = Translations.t.importHelper.selectTheme
|
const t = Translations.t.importHelper.selectTheme
|
||||||
let options: InputElement<string>[] = AllKnownLayouts.layoutsList
|
let options: InputElement<string>[] = Array.from(AllKnownLayouts.allKnownLayouts.values())
|
||||||
.filter((th) => th.layers.some((l) => l.id === params.layer.id))
|
.filter((th) => th.layers.some((l) => l.id === params.layer.id))
|
||||||
.filter((th) => th.id !== "personal")
|
.filter((th) => th.id !== "personal")
|
||||||
.map(
|
.map(
|
||||||
|
@ -60,7 +60,7 @@ export default class SelectTheme
|
||||||
return []
|
return []
|
||||||
}
|
}
|
||||||
// we get the layer with the correct ID via the actual theme config, as the actual theme might have different presets due to overrides
|
// we get the layer with the correct ID via the actual theme config, as the actual theme might have different presets due to overrides
|
||||||
const themeConfig = AllKnownLayouts.layoutsList.find((th) => th.id === theme)
|
const themeConfig = AllKnownLayouts.allKnownLayouts.get(theme)
|
||||||
const layer = themeConfig.layers.find((l) => l.id === params.layer.id)
|
const layer = themeConfig.layers.find((l) => l.id === params.layer.id)
|
||||||
return layer.presets
|
return layer.presets
|
||||||
})
|
})
|
||||||
|
|
|
@ -8,17 +8,17 @@ import { Utils } from "../Utils"
|
||||||
import Combine from "./Base/Combine"
|
import Combine from "./Base/Combine"
|
||||||
import { StackedRenderingChart } from "./BigComponents/TagRenderingChart"
|
import { StackedRenderingChart } from "./BigComponents/TagRenderingChart"
|
||||||
import { LayerFilterPanel } from "./BigComponents/FilterView"
|
import { LayerFilterPanel } from "./BigComponents/FilterView"
|
||||||
import { AllKnownLayouts } from "../Customizations/AllKnownLayouts"
|
|
||||||
import MapState from "../Logic/State/MapState"
|
import MapState from "../Logic/State/MapState"
|
||||||
import BaseUIElement from "./BaseUIElement"
|
import BaseUIElement from "./BaseUIElement"
|
||||||
import Title from "./Base/Title"
|
import Title from "./Base/Title"
|
||||||
import { FixedUiElement } from "./Base/FixedUiElement"
|
import { FixedUiElement } from "./Base/FixedUiElement"
|
||||||
import List from "./Base/List"
|
import List from "./Base/List"
|
||||||
|
import LayoutConfig from "../Models/ThemeConfig/LayoutConfig"
|
||||||
|
import mcChanges from "../assets/generated/themes/mapcomplete-changes.json"
|
||||||
class StatisticsForOverviewFile extends Combine {
|
class StatisticsForOverviewFile extends Combine {
|
||||||
constructor(homeUrl: string, paths: string[]) {
|
constructor(homeUrl: string, paths: string[]) {
|
||||||
paths = paths.filter((p) => !p.endsWith("file-overview.json"))
|
paths = paths.filter((p) => !p.endsWith("file-overview.json"))
|
||||||
const layer = AllKnownLayouts.allKnownLayouts.get("mapcomplete-changes").layers[0]
|
const layer = new LayoutConfig(<any>mcChanges, true).layers[0]
|
||||||
const filteredLayer = MapState.InitializeFilteredLayers(
|
const filteredLayer = MapState.InitializeFilteredLayers(
|
||||||
{ id: "statistics-view", layers: [layer] },
|
{ id: "statistics-view", layers: [layer] },
|
||||||
undefined
|
undefined
|
||||||
|
|
|
@ -58,12 +58,7 @@
|
||||||
<!-- DECORATION 0 END -->
|
<!-- DECORATION 0 END -->
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="z-index-above-map pointer-events-none" id="topleft-tools">
|
<div id="top-left">
|
||||||
<div class="p-3 flex flex-col items-end sm:items-start sm:flex-row sm:flex-wrap w-full sm:justify-between">
|
|
||||||
<div class="shadow rounded-full h-min w-full overflow-hidden sm:max-w-sm pointer-events-auto"
|
|
||||||
id="searchbox"></div>
|
|
||||||
<div class="m-1 pointer-events-auto" id="userbadge"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="clutter absolute h-24 left-24 right-24 top-56 text-xl text-center"
|
<div class="clutter absolute h-24 left-24 right-24 top-56 text-xl text-center"
|
||||||
|
|
|
@ -36,7 +36,7 @@
|
||||||
"generate:service-worker": "tsc service-worker.ts --outFile public/service-worker.js && git_hash=$(git rev-parse HEAD) && sed -i \"s/GITHUB-COMMIT/$git_hash/\" public/service-worker.js",
|
"generate:service-worker": "tsc service-worker.ts --outFile public/service-worker.js && git_hash=$(git rev-parse HEAD) && sed -i \"s/GITHUB-COMMIT/$git_hash/\" public/service-worker.js",
|
||||||
"optimize-images": "cd assets/generated/ && find -name '*.png' -exec optipng '{}' \\; && echo 'PNGs are optimized'",
|
"optimize-images": "cd assets/generated/ && find -name '*.png' -exec optipng '{}' \\; && echo 'PNGs are optimized'",
|
||||||
"generate:stats": "ts-node scripts/GenerateSeries.ts",
|
"generate:stats": "ts-node scripts/GenerateSeries.ts",
|
||||||
"reset:layeroverview": "echo {\\\"layers\\\":[], \\\"themes\\\":[]} > ./assets/generated/known_layers_and_themes.json && echo {\\\"layers\\\": []} > ./assets/generated/known_layers.json && rm -f ./assets/generated/layers/*.json && rm -f ./assets/generated/themes/*.json && npm run generate:layeroverview && ts-node scripts/generateLayerOverview.ts --force",
|
"reset:layeroverview": "echo {\\\"themes\\\":[]} > ./assets/generated/known_themes.json && echo {\\\"layers\\\": []} > ./assets/generated/known_layers.json && rm -f ./assets/generated/layers/*.json && rm -f ./assets/generated/themes/*.json && npm run generate:layeroverview && ts-node scripts/generateLayerOverview.ts --force",
|
||||||
"generate": "mkdir -p ./assets/generated; npm run generate:licenses; npm run generate:images; npm run generate:charging-stations; npm run generate:translations; npm run reset:layeroverview; npm run generate:service-worker",
|
"generate": "mkdir -p ./assets/generated; npm run generate:licenses; npm run generate:images; npm run generate:charging-stations; npm run generate:translations; npm run reset:layeroverview; npm run generate:service-worker",
|
||||||
"generate:charging-stations": "cd ./assets/layers/charging_station && ts-node csvToJson.ts && cd -",
|
"generate:charging-stations": "cd ./assets/layers/charging_station && ts-node csvToJson.ts && cd -",
|
||||||
"prepare-deploy": "npm run generate:service-worker && ./scripts/build.sh",
|
"prepare-deploy": "npm run generate:service-worker && ./scripts/build.sh",
|
||||||
|
|
|
@ -539,10 +539,7 @@ export async function main(args: string[]) {
|
||||||
|
|
||||||
const theme = AllKnownLayouts.allKnownLayouts.get(themeName)
|
const theme = AllKnownLayouts.allKnownLayouts.get(themeName)
|
||||||
if (theme === undefined) {
|
if (theme === undefined) {
|
||||||
const keys = []
|
const keys = Array.from(AllKnownLayouts.allKnownLayouts.keys())
|
||||||
AllKnownLayouts.allKnownLayouts.forEach((_, key) => {
|
|
||||||
keys.push(key)
|
|
||||||
})
|
|
||||||
console.error("The theme " + theme + " was not found; try one of ", keys)
|
console.error("The theme " + theme + " was not found; try one of ", keys)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,6 +24,11 @@ import { DefaultGuiState } from "../UI/DefaultGuiState"
|
||||||
import fakedom from "fake-dom"
|
import fakedom from "fake-dom"
|
||||||
import Hotkeys from "../UI/Base/Hotkeys"
|
import Hotkeys from "../UI/Base/Hotkeys"
|
||||||
import { QueryParameters } from "../Logic/Web/QueryParameters"
|
import { QueryParameters } from "../Logic/Web/QueryParameters"
|
||||||
|
import Link from "../UI/Base/Link"
|
||||||
|
import Constants from "../Models/Constants"
|
||||||
|
import LayerConfig from "../Models/ThemeConfig/LayerConfig"
|
||||||
|
import DependencyCalculator from "../Models/ThemeConfig/DependencyCalculator"
|
||||||
|
import { AllSharedLayers } from "../Customizations/AllSharedLayers"
|
||||||
function WriteFile(
|
function WriteFile(
|
||||||
filename,
|
filename,
|
||||||
html: BaseUIElement,
|
html: BaseUIElement,
|
||||||
|
@ -74,6 +79,179 @@ function WriteFile(
|
||||||
writeFileSync(filename, warnAutomated + md)
|
writeFileSync(filename, warnAutomated + md)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function GenerateDocumentationForTheme(theme: LayoutConfig): BaseUIElement {
|
||||||
|
return new Combine([
|
||||||
|
new Title(
|
||||||
|
new Combine([
|
||||||
|
theme.title,
|
||||||
|
"(",
|
||||||
|
new Link(theme.id, "https://mapcomplete.osm.be/" + theme.id),
|
||||||
|
")",
|
||||||
|
]),
|
||||||
|
2
|
||||||
|
),
|
||||||
|
theme.description,
|
||||||
|
"This theme contains the following layers:",
|
||||||
|
new List(
|
||||||
|
theme.layers
|
||||||
|
.filter((l) => !l.id.startsWith("note_import_"))
|
||||||
|
.map((l) => new Link(l.id, "../Layers/" + l.id + ".md"))
|
||||||
|
),
|
||||||
|
"Available languages:",
|
||||||
|
new List(theme.language.filter((ln) => ln !== "_context")),
|
||||||
|
]).SetClass("flex flex-col")
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates the documentation for the layers overview page
|
||||||
|
* @constructor
|
||||||
|
*/
|
||||||
|
function GenLayerOverviewText(): BaseUIElement {
|
||||||
|
for (const id of Constants.priviliged_layers) {
|
||||||
|
if (!AllSharedLayers.sharedLayers.has(id)) {
|
||||||
|
throw "Priviliged layer definition not found: " + id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const allLayers: LayerConfig[] = Array.from(AllSharedLayers.sharedLayers.values()).filter(
|
||||||
|
(layer) => Constants.priviliged_layers.indexOf(layer.id) < 0
|
||||||
|
)
|
||||||
|
|
||||||
|
const builtinLayerIds: Set<string> = new Set<string>()
|
||||||
|
allLayers.forEach((l) => builtinLayerIds.add(l.id))
|
||||||
|
|
||||||
|
const themesPerLayer = new Map<string, string[]>()
|
||||||
|
|
||||||
|
for (const layout of Array.from(AllKnownLayouts.allKnownLayouts.values())) {
|
||||||
|
for (const layer of layout.layers) {
|
||||||
|
if (!builtinLayerIds.has(layer.id)) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if (!themesPerLayer.has(layer.id)) {
|
||||||
|
themesPerLayer.set(layer.id, [])
|
||||||
|
}
|
||||||
|
themesPerLayer.get(layer.id).push(layout.id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Determine the cross-dependencies
|
||||||
|
const layerIsNeededBy: Map<string, string[]> = new Map<string, string[]>()
|
||||||
|
|
||||||
|
for (const layer of allLayers) {
|
||||||
|
for (const dep of DependencyCalculator.getLayerDependencies(layer)) {
|
||||||
|
const dependency = dep.neededLayer
|
||||||
|
if (!layerIsNeededBy.has(dependency)) {
|
||||||
|
layerIsNeededBy.set(dependency, [])
|
||||||
|
}
|
||||||
|
layerIsNeededBy.get(dependency).push(layer.id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Combine([
|
||||||
|
new Title("Special and other useful layers", 1),
|
||||||
|
"MapComplete has a few data layers available in the theme which have special properties through builtin-hooks. Furthermore, there are some normal layers (which are built from normal Theme-config files) but are so general that they get a mention here.",
|
||||||
|
new Title("Priviliged layers", 1),
|
||||||
|
new List(Constants.priviliged_layers.map((id) => "[" + id + "](#" + id + ")")),
|
||||||
|
...Constants.priviliged_layers
|
||||||
|
.map((id) => AllSharedLayers.sharedLayers.get(id))
|
||||||
|
.map((l) =>
|
||||||
|
l.GenerateDocumentation(
|
||||||
|
themesPerLayer.get(l.id),
|
||||||
|
layerIsNeededBy,
|
||||||
|
DependencyCalculator.getLayerDependencies(l),
|
||||||
|
Constants.added_by_default.indexOf(l.id) >= 0,
|
||||||
|
Constants.no_include.indexOf(l.id) < 0
|
||||||
|
)
|
||||||
|
),
|
||||||
|
new Title("Normal layers", 1),
|
||||||
|
"The following layers are included in MapComplete:",
|
||||||
|
new List(
|
||||||
|
Array.from(AllSharedLayers.sharedLayers.keys()).map(
|
||||||
|
(id) => new Link(id, "./Layers/" + id + ".md")
|
||||||
|
)
|
||||||
|
),
|
||||||
|
])
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates documentation for the layers.
|
||||||
|
* Inline layers are included (if the theme is public)
|
||||||
|
* @param callback
|
||||||
|
* @constructor
|
||||||
|
*/
|
||||||
|
function GenOverviewsForSingleLayer(
|
||||||
|
callback: (layer: LayerConfig, element: BaseUIElement, inlineSource: string) => void
|
||||||
|
): void {
|
||||||
|
const allLayers: LayerConfig[] = Array.from(AllSharedLayers.sharedLayers.values()).filter(
|
||||||
|
(layer) => Constants.priviliged_layers.indexOf(layer.id) < 0
|
||||||
|
)
|
||||||
|
const builtinLayerIds: Set<string> = new Set<string>()
|
||||||
|
allLayers.forEach((l) => builtinLayerIds.add(l.id))
|
||||||
|
const inlineLayers = new Map<string, string>()
|
||||||
|
|
||||||
|
for (const layout of Array.from(AllKnownLayouts.allKnownLayouts.values())) {
|
||||||
|
if (layout.hideFromOverview) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const layer of layout.layers) {
|
||||||
|
if (Constants.priviliged_layers.indexOf(layer.id) >= 0) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if (builtinLayerIds.has(layer.id)) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if (layer.source.geojsonSource !== undefined) {
|
||||||
|
// Not an OSM-source
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
allLayers.push(layer)
|
||||||
|
builtinLayerIds.add(layer.id)
|
||||||
|
inlineLayers.set(layer.id, layout.id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const themesPerLayer = new Map<string, string[]>()
|
||||||
|
|
||||||
|
for (const layout of Array.from(AllKnownLayouts.allKnownLayouts.values())) {
|
||||||
|
if (layout.hideFromOverview) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
for (const layer of layout.layers) {
|
||||||
|
if (!builtinLayerIds.has(layer.id)) {
|
||||||
|
// This is an inline layer
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if (!themesPerLayer.has(layer.id)) {
|
||||||
|
themesPerLayer.set(layer.id, [])
|
||||||
|
}
|
||||||
|
themesPerLayer.get(layer.id).push(layout.id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Determine the cross-dependencies
|
||||||
|
const layerIsNeededBy: Map<string, string[]> = new Map<string, string[]>()
|
||||||
|
|
||||||
|
for (const layer of allLayers) {
|
||||||
|
for (const dep of DependencyCalculator.getLayerDependencies(layer)) {
|
||||||
|
const dependency = dep.neededLayer
|
||||||
|
if (!layerIsNeededBy.has(dependency)) {
|
||||||
|
layerIsNeededBy.set(dependency, [])
|
||||||
|
}
|
||||||
|
layerIsNeededBy.get(dependency).push(layer.id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
allLayers.forEach((layer) => {
|
||||||
|
const element = layer.GenerateDocumentation(
|
||||||
|
themesPerLayer.get(layer.id),
|
||||||
|
layerIsNeededBy,
|
||||||
|
DependencyCalculator.getLayerDependencies(layer)
|
||||||
|
)
|
||||||
|
callback(layer, element, inlineLayers.get(layer.id))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The wikitable is updated as some tools show an overview of apps based on the wiki.
|
* The wikitable is updated as some tools show an overview of apps based on the wiki.
|
||||||
*/
|
*/
|
||||||
|
@ -131,7 +309,7 @@ console.log("Starting documentation generation...")
|
||||||
ScriptUtils.fixUtils()
|
ScriptUtils.fixUtils()
|
||||||
generateWikipage()
|
generateWikipage()
|
||||||
|
|
||||||
AllKnownLayouts.GenOverviewsForSingleLayer((layer, element, inlineSource) => {
|
GenOverviewsForSingleLayer((layer, element, inlineSource) => {
|
||||||
console.log("Exporting ", layer.id)
|
console.log("Exporting ", layer.id)
|
||||||
if (!existsSync("./Docs/Layers")) {
|
if (!existsSync("./Docs/Layers")) {
|
||||||
mkdirSync("./Docs/Layers")
|
mkdirSync("./Docs/Layers")
|
||||||
|
@ -144,7 +322,7 @@ AllKnownLayouts.GenOverviewsForSingleLayer((layer, element, inlineSource) => {
|
||||||
})
|
})
|
||||||
|
|
||||||
Array.from(AllKnownLayouts.allKnownLayouts.values()).map((theme) => {
|
Array.from(AllKnownLayouts.allKnownLayouts.values()).map((theme) => {
|
||||||
const docs = AllKnownLayouts.GenerateDocumentationForTheme(theme)
|
const docs = GenerateDocumentationForTheme(theme)
|
||||||
WriteFile(
|
WriteFile(
|
||||||
"./Docs/Themes/" + theme.id + ".md",
|
"./Docs/Themes/" + theme.id + ".md",
|
||||||
docs,
|
docs,
|
||||||
|
@ -167,9 +345,7 @@ WriteFile(
|
||||||
WriteFile("./Docs/SpecialInputElements.md", ValidatedTextField.HelpText(), [
|
WriteFile("./Docs/SpecialInputElements.md", ValidatedTextField.HelpText(), [
|
||||||
"UI/Input/ValidatedTextField.ts",
|
"UI/Input/ValidatedTextField.ts",
|
||||||
])
|
])
|
||||||
WriteFile("./Docs/BuiltinLayers.md", AllKnownLayouts.GenLayerOverviewText(), [
|
WriteFile("./Docs/BuiltinLayers.md", GenLayerOverviewText(), ["Customizations/AllKnownLayouts.ts"])
|
||||||
"Customizations/AllKnownLayouts.ts",
|
|
||||||
])
|
|
||||||
WriteFile("./Docs/BuiltinQuestions.md", SharedTagRenderings.HelpText(), [
|
WriteFile("./Docs/BuiltinQuestions.md", SharedTagRenderings.HelpText(), [
|
||||||
"Customizations/SharedTagRenderings.ts",
|
"Customizations/SharedTagRenderings.ts",
|
||||||
"assets/tagRenderings/questions.json",
|
"assets/tagRenderings/questions.json",
|
||||||
|
|
|
@ -21,8 +21,7 @@ import { PrepareLayer } from "../Models/ThemeConfig/Conversion/PrepareLayer"
|
||||||
import { PrepareTheme } from "../Models/ThemeConfig/Conversion/PrepareTheme"
|
import { PrepareTheme } from "../Models/ThemeConfig/Conversion/PrepareTheme"
|
||||||
import { DesugaringContext } from "../Models/ThemeConfig/Conversion/Conversion"
|
import { DesugaringContext } from "../Models/ThemeConfig/Conversion/Conversion"
|
||||||
import { Utils } from "../Utils"
|
import { Utils } from "../Utils"
|
||||||
import { AllKnownLayouts } from "../Customizations/AllKnownLayouts"
|
import { AllSharedLayers } from "../Customizations/AllSharedLayers"
|
||||||
import { Script } from "vm"
|
|
||||||
|
|
||||||
// This scripts scans 'assets/layers/*.json' for layer definition files and 'assets/themes/*.json' for theme definition files.
|
// This scripts scans 'assets/layers/*.json' for layer definition files and 'assets/themes/*.json' for theme definition files.
|
||||||
// It spits out an overview of those to be used to load them
|
// It spits out an overview of those to be used to load them
|
||||||
|
@ -259,9 +258,8 @@ class LayerOverviewUtils {
|
||||||
)
|
)
|
||||||
|
|
||||||
writeFileSync(
|
writeFileSync(
|
||||||
"./assets/generated/known_layers_and_themes.json",
|
"./assets/generated/known_themes.json",
|
||||||
JSON.stringify({
|
JSON.stringify({
|
||||||
layers: Array.from(sharedLayers.values()),
|
|
||||||
themes: Array.from(sharedThemes.values()),
|
themes: Array.from(sharedThemes.values()),
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
@ -306,7 +304,7 @@ class LayerOverviewUtils {
|
||||||
"GenerateLayerOverview:"
|
"GenerateLayerOverview:"
|
||||||
)
|
)
|
||||||
|
|
||||||
if (AllKnownLayouts.getSharedLayersConfigs().size == 0) {
|
if (AllSharedLayers.getSharedLayersConfigs().size == 0) {
|
||||||
console.error("This was a bootstrapping-run. Run generate layeroverview again!")
|
console.error("This was a bootstrapping-run. Run generate layeroverview again!")
|
||||||
} else {
|
} else {
|
||||||
const green = (s) => "\x1b[92m" + s + "\x1b[0m"
|
const green = (s) => "\x1b[92m" + s + "\x1b[0m"
|
||||||
|
@ -325,7 +323,7 @@ class LayerOverviewUtils {
|
||||||
const sharedTagRenderings = this.getSharedTagRenderings(doesImageExist)
|
const sharedTagRenderings = this.getSharedTagRenderings(doesImageExist)
|
||||||
const state: DesugaringContext = {
|
const state: DesugaringContext = {
|
||||||
tagRenderings: sharedTagRenderings,
|
tagRenderings: sharedTagRenderings,
|
||||||
sharedLayers: AllKnownLayouts.getSharedLayersConfigs(),
|
sharedLayers: AllSharedLayers.getSharedLayersConfigs(),
|
||||||
}
|
}
|
||||||
const sharedLayers = new Map<string, LayerConfigJson>()
|
const sharedLayers = new Map<string, LayerConfigJson>()
|
||||||
const prepLayer = new PrepareLayer(state)
|
const prepLayer = new PrepareLayer(state)
|
||||||
|
|
|
@ -205,7 +205,7 @@ function main() {
|
||||||
|
|
||||||
const files = []
|
const files = []
|
||||||
|
|
||||||
for (const layout of AllKnownLayouts.layoutsList) {
|
for (const layout of AllKnownLayouts.allKnownLayouts.values()) {
|
||||||
if (layout.hideFromOverview) {
|
if (layout.hideFromOverview) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue