forked from MapComplete/MapComplete
new color and icon for navigation
This commit is contained in:
parent
d585ec9048
commit
bd1b29e344
10 changed files with 870 additions and 815 deletions
|
@ -1,3 +1,4 @@
|
|||
import { CenterFlexedElement } from "./UI/Base/CenterFlexedElement";
|
||||
import { FixedUiElement } from "./UI/Base/FixedUiElement";
|
||||
import Toggle from "./UI/Input/Toggle";
|
||||
import { Basemap } from "./UI/BigComponents/Basemap";
|
||||
|
@ -15,7 +16,7 @@ import {LocalStorageSource} from "./Logic/Web/LocalStorageSource";
|
|||
import { Utils } from "./Utils";
|
||||
import Svg from "./Svg";
|
||||
import Link from "./UI/Base/Link";
|
||||
import * as personal from "./assets/themes/personalLayout/personalLayout.json"
|
||||
import * as personal from "./assets/themes/personalLayout/personalLayout.json";
|
||||
import LayoutConfig from "./Customizations/JSON/LayoutConfig";
|
||||
import * as L from "leaflet";
|
||||
import Img from "./UI/Base/Img";
|
||||
|
@ -42,19 +43,29 @@ import LayerConfig from "./Customizations/JSON/LayerConfig";
|
|||
import AvailableBaseLayers from "./Logic/Actors/AvailableBaseLayers";
|
||||
|
||||
export class InitUiElements {
|
||||
|
||||
|
||||
static InitAll(layoutToUse: LayoutConfig, layoutFromBase64: string, testing: UIEventSource<string>, layoutName: string,
|
||||
layoutDefinition: string = "") {
|
||||
|
||||
static InitAll(
|
||||
layoutToUse: LayoutConfig,
|
||||
layoutFromBase64: string,
|
||||
testing: UIEventSource<string>,
|
||||
layoutName: string,
|
||||
layoutDefinition: string = ""
|
||||
) {
|
||||
if (layoutToUse === undefined) {
|
||||
console.log("Incorrect layout")
|
||||
new FixedUiElement(`Error: incorrect layout <i>${layoutName}</i><br/><a href='https://${window.location.host}/'>Go back</a>`).AttachTo("centermessage").onClick(() => {
|
||||
});
|
||||
throw "Incorrect layout"
|
||||
console.log("Incorrect layout");
|
||||
new FixedUiElement(
|
||||
`Error: incorrect layout <i>${layoutName}</i><br/><a href='https://${window.location.host}/'>Go back</a>`
|
||||
)
|
||||
.AttachTo("centermessage")
|
||||
.onClick(() => {});
|
||||
throw "Incorrect layout";
|
||||
}
|
||||
|
||||
console.log("Using layout: ", layoutToUse.id, "LayoutFromBase64 is ", layoutFromBase64);
|
||||
console.log(
|
||||
"Using layout: ",
|
||||
layoutToUse.id,
|
||||
"LayoutFromBase64 is ",
|
||||
layoutFromBase64
|
||||
);
|
||||
|
||||
State.state = new State(layoutToUse);
|
||||
|
||||
|
@ -63,42 +74,48 @@ export class InitUiElements {
|
|||
window.mapcomplete_state = State.state;
|
||||
|
||||
if (layoutToUse.hideFromOverview) {
|
||||
State.state.osmConnection.GetPreference("hidden-theme-" + layoutToUse.id + "-enabled").setData("true");
|
||||
State.state.osmConnection
|
||||
.GetPreference("hidden-theme-" + layoutToUse.id + "-enabled")
|
||||
.setData("true");
|
||||
}
|
||||
|
||||
if (layoutFromBase64 !== "false") {
|
||||
State.state.layoutDefinition = layoutDefinition;
|
||||
console.log("Layout definition:", Utils.EllipsesAfter(State.state.layoutDefinition, 100))
|
||||
console.log(
|
||||
"Layout definition:",
|
||||
Utils.EllipsesAfter(State.state.layoutDefinition, 100)
|
||||
);
|
||||
if (testing.data !== "true") {
|
||||
State.state.osmConnection.OnLoggedIn(() => {
|
||||
State.state.osmConnection.GetLongPreference("installed-theme-" + layoutToUse.id).setData(State.state.layoutDefinition);
|
||||
})
|
||||
State.state.osmConnection
|
||||
.GetLongPreference("installed-theme-" + layoutToUse.id)
|
||||
.setData(State.state.layoutDefinition);
|
||||
});
|
||||
} else {
|
||||
console.warn("NOT saving custom layout to OSM as we are tesing -> probably in an iFrame")
|
||||
console.warn(
|
||||
"NOT saving custom layout to OSM as we are tesing -> probably in an iFrame"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function updateFavs() {
|
||||
// This is purely for the personal theme to load the layers there
|
||||
const favs = State.state.favouriteLayers.data ?? [];
|
||||
|
||||
const neededLayers = new Set<LayerConfig>();
|
||||
|
||||
console.log("Favourites are: ", favs)
|
||||
console.log("Favourites are: ", favs);
|
||||
layoutToUse.layers.splice(0, layoutToUse.layers.length);
|
||||
let somethingChanged = false;
|
||||
for (const fav of favs) {
|
||||
|
||||
if (AllKnownLayers.sharedLayers.has(fav)) {
|
||||
const layer = AllKnownLayers.sharedLayers.get(fav)
|
||||
const layer = AllKnownLayers.sharedLayers.get(fav);
|
||||
if (!neededLayers.has(layer)) {
|
||||
neededLayers.add(layer)
|
||||
neededLayers.add(layer);
|
||||
somethingChanged = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
for (const layouts of State.state.installedThemes.data) {
|
||||
for (const layer of layouts.layout.layers) {
|
||||
if (typeof layer === "string") {
|
||||
|
@ -106,7 +123,7 @@ export class InitUiElements {
|
|||
}
|
||||
if (layer.id === fav) {
|
||||
if (!neededLayers.has(layer)) {
|
||||
neededLayers.add(layer)
|
||||
neededLayers.add(layer);
|
||||
somethingChanged = true;
|
||||
}
|
||||
}
|
||||
|
@ -114,15 +131,13 @@ export class InitUiElements {
|
|||
}
|
||||
}
|
||||
if (somethingChanged) {
|
||||
console.log("layoutToUse.layers:", layoutToUse.layers)
|
||||
console.log("layoutToUse.layers:", layoutToUse.layers);
|
||||
State.state.layoutToUse.data.layers = Array.from(neededLayers);
|
||||
State.state.layoutToUse.ping();
|
||||
State.state.layerUpdater?.ForceRefresh();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
if (layoutToUse.customCss !== undefined) {
|
||||
Utils.LoadCustomCss(layoutToUse.customCss);
|
||||
}
|
||||
|
@ -130,38 +145,47 @@ export class InitUiElements {
|
|||
InitUiElements.InitBaseMap();
|
||||
|
||||
InitUiElements.OnlyIf(State.state.featureSwitchUserbadge, () => {
|
||||
new UserBadge().AttachTo('userbadge');
|
||||
new UserBadge().AttachTo("userbadge");
|
||||
});
|
||||
|
||||
InitUiElements.OnlyIf((State.state.featureSwitchSearch), () => {
|
||||
InitUiElements.OnlyIf(State.state.featureSwitchSearch, () => {
|
||||
new SearchAndGo().AttachTo("searchbox");
|
||||
});
|
||||
|
||||
|
||||
InitUiElements.OnlyIf(State.state.featureSwitchWelcomeMessage, () => {
|
||||
InitUiElements.InitWelcomeMessage()
|
||||
InitUiElements.InitWelcomeMessage();
|
||||
});
|
||||
|
||||
if ((window != window.top && !State.state.featureSwitchWelcomeMessage.data) || State.state.featureSwitchIframe.data) {
|
||||
if (
|
||||
(window != window.top && !State.state.featureSwitchWelcomeMessage.data) ||
|
||||
State.state.featureSwitchIframe.data
|
||||
) {
|
||||
const currentLocation = State.state.locationControl;
|
||||
const url = `${window.location.origin}${window.location.pathname}?z=${currentLocation.data.zoom ?? 0}&lat=${currentLocation.data.lat ?? 0}&lon=${currentLocation.data.lon ?? 0}`;
|
||||
const url = `${window.location.origin}${window.location.pathname}?z=${
|
||||
currentLocation.data.zoom ?? 0
|
||||
}&lat=${currentLocation.data.lat ?? 0}&lon=${
|
||||
currentLocation.data.lon ?? 0
|
||||
}`;
|
||||
new MapControlButton(
|
||||
new Link(Svg.pop_out_img, url, true)
|
||||
.SetClass("block w-full h-full p-1.5")
|
||||
new Link(Svg.pop_out_img, url, true).SetClass(
|
||||
"block w-full h-full p-1.5"
|
||||
)
|
||||
.AttachTo("messagesbox");
|
||||
).AttachTo("messagesbox");
|
||||
}
|
||||
|
||||
State.state.osmConnection.userDetails.map((userDetails: UserDetails) => userDetails?.home)
|
||||
.addCallbackAndRunD(home => {
|
||||
const color = getComputedStyle(document.body).getPropertyValue("--subtle-detail-color")
|
||||
State.state.osmConnection.userDetails
|
||||
.map((userDetails: UserDetails) => userDetails?.home)
|
||||
.addCallbackAndRunD((home) => {
|
||||
const color = getComputedStyle(document.body).getPropertyValue(
|
||||
"--subtle-detail-color"
|
||||
);
|
||||
const icon = L.icon({
|
||||
iconUrl: Img.AsData(Svg.home_white_bg.replace(/#ffffff/g, color)),
|
||||
iconSize: [30, 30],
|
||||
iconAnchor: [15, 15]
|
||||
iconAnchor: [15, 15],
|
||||
});
|
||||
const marker = L.marker([home.lat, home.lon], {icon: icon})
|
||||
marker.addTo(State.state.leafletMap.data)
|
||||
const marker = L.marker([home.lat, home.lon], { icon: icon });
|
||||
marker.addTo(State.state.leafletMap.data);
|
||||
});
|
||||
|
||||
const geolocationButton = new Toggle(
|
||||
|
@ -170,25 +194,33 @@ export class InitUiElements {
|
|||
State.state.currentGPSLocation,
|
||||
State.state.leafletMap,
|
||||
State.state.layoutToUse
|
||||
)),
|
||||
)
|
||||
),
|
||||
undefined,
|
||||
State.state.featureSwitchGeolocation);
|
||||
State.state.featureSwitchGeolocation
|
||||
);
|
||||
|
||||
const plus = new MapControlButton(
|
||||
Svg.plus_ui()
|
||||
new CenterFlexedElement(
|
||||
Img.AsImageElement(Svg.plus_zoom, "", "width:1.5rem;height:1.5rem")
|
||||
)
|
||||
).onClick(() => {
|
||||
State.state.locationControl.data.zoom++;
|
||||
State.state.locationControl.ping();
|
||||
})
|
||||
});
|
||||
|
||||
const min = new MapControlButton(
|
||||
Svg.min_ui()
|
||||
new CenterFlexedElement(
|
||||
Img.AsImageElement(Svg.min_zoom, "", "width:1.5rem;height:1.5rem")
|
||||
)
|
||||
).onClick(() => {
|
||||
State.state.locationControl.data.zoom--;
|
||||
State.state.locationControl.ping();
|
||||
})
|
||||
});
|
||||
|
||||
new Combine([plus, min, geolocationButton].map(el => el.SetClass("m-0.5 md:m-1")))
|
||||
new Combine(
|
||||
[plus, min, geolocationButton].map((el) => el.SetClass("m-0.5 md:m-1"))
|
||||
)
|
||||
.SetClass("flex flex-col")
|
||||
.AttachTo("bottom-right");
|
||||
|
||||
|
@ -206,38 +238,45 @@ export class InitUiElements {
|
|||
|
||||
// Reset the loading message once things are loaded
|
||||
new CenterMessageBox().AttachTo("centermessage");
|
||||
document.getElementById("centermessage").classList.add("pointer-events-none")
|
||||
|
||||
|
||||
document
|
||||
.getElementById("centermessage")
|
||||
.classList.add("pointer-events-none");
|
||||
}
|
||||
|
||||
static LoadLayoutFromHash(userLayoutParam: UIEventSource<string>): [LayoutConfig, string] {
|
||||
static LoadLayoutFromHash(
|
||||
userLayoutParam: UIEventSource<string>
|
||||
): [LayoutConfig, string] {
|
||||
try {
|
||||
let hash = location.hash.substr(1);
|
||||
const layoutFromBase64 = userLayoutParam.data;
|
||||
// layoutFromBase64 contains the name of the theme. This is partly to do tracking with goat counter
|
||||
|
||||
const dedicatedHashFromLocalStorage = LocalStorageSource.Get("user-layout-" + layoutFromBase64.replace(" ", "_"));
|
||||
const dedicatedHashFromLocalStorage = LocalStorageSource.Get(
|
||||
"user-layout-" + layoutFromBase64.replace(" ", "_")
|
||||
);
|
||||
if (dedicatedHashFromLocalStorage.data?.length < 10) {
|
||||
dedicatedHashFromLocalStorage.setData(undefined);
|
||||
}
|
||||
|
||||
const hashFromLocalStorage = LocalStorageSource.Get("last-loaded-user-layout");
|
||||
const hashFromLocalStorage = LocalStorageSource.Get(
|
||||
"last-loaded-user-layout"
|
||||
);
|
||||
if (hash.length < 10) {
|
||||
hash = dedicatedHashFromLocalStorage.data ?? hashFromLocalStorage.data;
|
||||
} else {
|
||||
console.log("Saving hash to local storage")
|
||||
console.log("Saving hash to local storage");
|
||||
hashFromLocalStorage.setData(hash);
|
||||
dedicatedHashFromLocalStorage.setData(hash);
|
||||
}
|
||||
|
||||
let json: {}
|
||||
let json: {};
|
||||
try {
|
||||
json = JSON.parse(atob(hash));
|
||||
} catch (e) {
|
||||
// We try to decode with lz-string
|
||||
json = JSON.parse(Utils.UnMinify(LZString.decompressFromBase64(hash))) as LayoutConfigJson;
|
||||
|
||||
json = JSON.parse(
|
||||
Utils.UnMinify(LZString.decompressFromBase64(hash))
|
||||
) as LayoutConfigJson;
|
||||
}
|
||||
|
||||
// @ts-ignore
|
||||
|
@ -245,13 +284,17 @@ export class InitUiElements {
|
|||
userLayoutParam.setData(layoutToUse.id);
|
||||
return [layoutToUse, btoa(Utils.MinifyJSON(JSON.stringify(json)))];
|
||||
} catch (e) {
|
||||
|
||||
new FixedUiElement("Error: could not parse the custom layout:<br/> " + e).AttachTo("centermessage");
|
||||
new FixedUiElement(
|
||||
"Error: could not parse the custom layout:<br/> " + e
|
||||
).AttachTo("centermessage");
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
private static OnlyIf(featureSwitch: UIEventSource<boolean>, callback: () => void) {
|
||||
private static OnlyIf(
|
||||
featureSwitch: UIEventSource<boolean>,
|
||||
callback: () => void
|
||||
) {
|
||||
featureSwitch.addCallbackAndRun(() => {
|
||||
if (featureSwitch.data) {
|
||||
callback();
|
||||
|
@ -260,19 +303,15 @@ export class InitUiElements {
|
|||
}
|
||||
|
||||
private static InitWelcomeMessage() {
|
||||
|
||||
const isOpened = new UIEventSource<boolean>(false);
|
||||
const fullOptions = new FullWelcomePaneWithTabs(isOpened);
|
||||
|
||||
// ?-Button on Desktop, opens panel with close-X.
|
||||
const help = new MapControlButton(Svg.help_svg());
|
||||
help.onClick(() => isOpened.setData(true))
|
||||
new Toggle(
|
||||
fullOptions
|
||||
.SetClass("welcomeMessage"),
|
||||
help
|
||||
, isOpened
|
||||
).AttachTo("messagesbox");
|
||||
help.onClick(() => isOpened.setData(true));
|
||||
new Toggle(fullOptions.SetClass("welcomeMessage"), help, isOpened).AttachTo(
|
||||
"messagesbox"
|
||||
);
|
||||
const openedTime = new Date().getTime();
|
||||
State.state.locationControl.addCallback(() => {
|
||||
if (new Date().getTime() - openedTime < 15 * 1000) {
|
||||
|
@ -280,75 +319,77 @@ export class InitUiElements {
|
|||
return;
|
||||
}
|
||||
isOpened.setData(false);
|
||||
})
|
||||
});
|
||||
|
||||
State.state.selectedElement.addCallbackAndRunD(_ => {
|
||||
State.state.selectedElement.addCallbackAndRunD((_) => {
|
||||
isOpened.setData(false);
|
||||
})
|
||||
isOpened.setData(Hash.hash.data === undefined || Hash.hash.data === "" || Hash.hash.data == "welcome")
|
||||
});
|
||||
isOpened.setData(
|
||||
Hash.hash.data === undefined ||
|
||||
Hash.hash.data === "" ||
|
||||
Hash.hash.data == "welcome"
|
||||
);
|
||||
}
|
||||
|
||||
private static InitLayerSelection(featureSource: FeatureSource) {
|
||||
|
||||
const copyrightNotice =
|
||||
new ScrollableFullScreen(
|
||||
const copyrightNotice = new ScrollableFullScreen(
|
||||
() => Translations.t.general.attribution.attributionTitle.Clone(),
|
||||
() => new AttributionPanel(State.state.layoutToUse, new ContributorCount(featureSource).Contributors),
|
||||
() =>
|
||||
new AttributionPanel(
|
||||
State.state.layoutToUse,
|
||||
new ContributorCount(featureSource).Contributors
|
||||
),
|
||||
"copyright"
|
||||
)
|
||||
);
|
||||
|
||||
;
|
||||
const copyrightButton = new Toggle(
|
||||
copyrightNotice,
|
||||
new MapControlButton(Svg.osm_copyright_svg()),
|
||||
copyrightNotice.isShown
|
||||
).ToggleOnClick()
|
||||
.SetClass("p-0.5")
|
||||
)
|
||||
.ToggleOnClick()
|
||||
.SetClass("p-0.5");
|
||||
|
||||
const layerControlPanel = new LayerControlPanel(
|
||||
State.state.layerControlIsOpened)
|
||||
.SetClass("block p-1 rounded-full");
|
||||
State.state.layerControlIsOpened
|
||||
).SetClass("block p-1 rounded-full");
|
||||
const layerControlButton = new Toggle(
|
||||
layerControlPanel,
|
||||
new MapControlButton(Svg.layers_svg()),
|
||||
State.state.layerControlIsOpened
|
||||
).ToggleOnClick()
|
||||
).ToggleOnClick();
|
||||
|
||||
const layerControl = new Toggle(
|
||||
layerControlButton,
|
||||
"",
|
||||
State.state.featureSwitchLayers
|
||||
)
|
||||
);
|
||||
|
||||
new Combine([copyrightButton, layerControl])
|
||||
.AttachTo("bottom-left");
|
||||
new Combine([copyrightButton, layerControl]).AttachTo("bottom-left");
|
||||
|
||||
|
||||
State.state.locationControl
|
||||
.addCallback(() => {
|
||||
State.state.locationControl.addCallback(() => {
|
||||
// Close the layer selection when the map is moved
|
||||
layerControlButton.isEnabled.setData(false);
|
||||
copyrightButton.isEnabled.setData(false);
|
||||
});
|
||||
|
||||
State.state.selectedElement.addCallbackAndRunD(_ => {
|
||||
State.state.selectedElement.addCallbackAndRunD((_) => {
|
||||
layerControlButton.isEnabled.setData(false);
|
||||
copyrightButton.isEnabled.setData(false);
|
||||
})
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
private static InitBaseMap() {
|
||||
State.state.availableBackgroundLayers = new AvailableBaseLayers(
|
||||
State.state.locationControl
|
||||
).availableEditorLayers;
|
||||
|
||||
State.state.availableBackgroundLayers = new AvailableBaseLayers(State.state.locationControl).availableEditorLayers;
|
||||
|
||||
State.state.backgroundLayer = State.state.backgroundLayerId
|
||||
.map((selectedId: string) => {
|
||||
State.state.backgroundLayer = State.state.backgroundLayerId.map(
|
||||
(selectedId: string) => {
|
||||
if (selectedId === undefined) {
|
||||
return AvailableBaseLayers.osmCarto
|
||||
return AvailableBaseLayers.osmCarto;
|
||||
}
|
||||
|
||||
|
||||
const available = State.state.availableBackgroundLayers.data;
|
||||
for (const layer of available) {
|
||||
if (layer.id === selectedId) {
|
||||
|
@ -356,96 +397,124 @@ export class InitUiElements {
|
|||
}
|
||||
}
|
||||
return AvailableBaseLayers.osmCarto;
|
||||
}, [State.state.availableBackgroundLayers], layer => layer.id);
|
||||
},
|
||||
[State.state.availableBackgroundLayers],
|
||||
(layer) => layer.id
|
||||
);
|
||||
|
||||
new LayerResetter(
|
||||
State.state.backgroundLayer, State.state.locationControl,
|
||||
State.state.availableBackgroundLayers, State.state.layoutToUse.map((layout: LayoutConfig) => layout.defaultBackgroundId));
|
||||
State.state.backgroundLayer,
|
||||
State.state.locationControl,
|
||||
State.state.availableBackgroundLayers,
|
||||
State.state.layoutToUse.map(
|
||||
(layout: LayoutConfig) => layout.defaultBackgroundId
|
||||
)
|
||||
);
|
||||
|
||||
const attr = new Attribution(
|
||||
State.state.locationControl,
|
||||
State.state.osmConnection.userDetails,
|
||||
State.state.layoutToUse,
|
||||
State.state.leafletMap
|
||||
);
|
||||
|
||||
const attr = new Attribution(State.state.locationControl, State.state.osmConnection.userDetails, State.state.layoutToUse,
|
||||
State.state.leafletMap);
|
||||
|
||||
const bm = new Basemap("leafletDiv",
|
||||
const bm = new Basemap(
|
||||
"leafletDiv",
|
||||
State.state.locationControl,
|
||||
State.state.backgroundLayer,
|
||||
State.state.LastClickLocation,
|
||||
attr
|
||||
);
|
||||
State.state.leafletMap.setData(bm.map);
|
||||
const layout = State.state.layoutToUse.data
|
||||
const layout = State.state.layoutToUse.data;
|
||||
if (layout.lockLocation) {
|
||||
|
||||
if (layout.lockLocation === true) {
|
||||
const tile = Utils.embedded_tile(layout.startLat, layout.startLon, layout.startZoom - 1)
|
||||
const bounds = Utils.tile_bounds(tile.z, tile.x, tile.y)
|
||||
const tile = Utils.embedded_tile(
|
||||
layout.startLat,
|
||||
layout.startLon,
|
||||
layout.startZoom - 1
|
||||
);
|
||||
const bounds = Utils.tile_bounds(tile.z, tile.x, tile.y);
|
||||
// We use the bounds to get a sense of distance for this zoom level
|
||||
const latDiff = bounds[0][0] - bounds[1][0]
|
||||
const lonDiff = bounds[0][1] - bounds[1][1]
|
||||
layout.lockLocation = [[layout.startLat - latDiff, layout.startLon - lonDiff],
|
||||
const latDiff = bounds[0][0] - bounds[1][0];
|
||||
const lonDiff = bounds[0][1] - bounds[1][1];
|
||||
layout.lockLocation = [
|
||||
[layout.startLat - latDiff, layout.startLon - lonDiff],
|
||||
[layout.startLat + latDiff, layout.startLon + lonDiff],
|
||||
];
|
||||
}
|
||||
console.warn("Locking the bounds to ", layout.lockLocation)
|
||||
console.warn("Locking the bounds to ", layout.lockLocation);
|
||||
bm.map.setMaxBounds(layout.lockLocation);
|
||||
bm.map.setMinZoom(layout.startZoom)
|
||||
bm.map.setMinZoom(layout.startZoom);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static InitLayers(): FeatureSource {
|
||||
|
||||
|
||||
const state = State.state;
|
||||
state.filteredLayers =
|
||||
state.layoutToUse.map(layoutToUse => {
|
||||
state.filteredLayers = state.layoutToUse.map((layoutToUse) => {
|
||||
const flayers = [];
|
||||
|
||||
|
||||
for (const layer of layoutToUse.layers) {
|
||||
const isDisplayed = QueryParameters.GetQueryParameter("layer-" + layer.id, "true", "Wether or not layer " + layer.id + " is shown")
|
||||
.map<boolean>((str) => str !== "false", [], (b) => b.toString());
|
||||
const isDisplayed = QueryParameters.GetQueryParameter(
|
||||
"layer-" + layer.id,
|
||||
"true",
|
||||
"Wether or not layer " + layer.id + " is shown"
|
||||
).map<boolean>(
|
||||
(str) => str !== "false",
|
||||
[],
|
||||
(b) => b.toString()
|
||||
);
|
||||
const flayer = {
|
||||
isDisplayed: isDisplayed,
|
||||
layerDef: layer
|
||||
}
|
||||
layerDef: layer,
|
||||
};
|
||||
flayers.push(flayer);
|
||||
}
|
||||
return flayers;
|
||||
});
|
||||
|
||||
const updater = new LoadFromOverpass(state.locationControl, state.layoutToUse, state.leafletMap);
|
||||
const updater = new LoadFromOverpass(
|
||||
state.locationControl,
|
||||
state.layoutToUse,
|
||||
state.leafletMap
|
||||
);
|
||||
State.state.layerUpdater = updater;
|
||||
|
||||
|
||||
const source = new FeaturePipeline(state.filteredLayers,
|
||||
const source = new FeaturePipeline(
|
||||
state.filteredLayers,
|
||||
updater,
|
||||
state.osmApiFeatureSource,
|
||||
state.layoutToUse,
|
||||
state.changes,
|
||||
state.locationControl,
|
||||
state.selectedElement);
|
||||
state.selectedElement
|
||||
);
|
||||
|
||||
new ShowDataLayer(source.features, State.state.leafletMap, State.state.layoutToUse);
|
||||
new ShowDataLayer(
|
||||
source.features,
|
||||
State.state.leafletMap,
|
||||
State.state.layoutToUse
|
||||
);
|
||||
|
||||
const selectedFeatureHandler = new SelectedFeatureHandler(Hash.hash, State.state.selectedElement, source, State.state.osmApiFeatureSource);
|
||||
const selectedFeatureHandler = new SelectedFeatureHandler(
|
||||
Hash.hash,
|
||||
State.state.selectedElement,
|
||||
source,
|
||||
State.state.osmApiFeatureSource
|
||||
);
|
||||
selectedFeatureHandler.zoomToSelectedFeature(State.state.locationControl);
|
||||
return source;
|
||||
}
|
||||
|
||||
private static setupAllLayerElements() {
|
||||
|
||||
// ------------- Setup the layers -------------------------------
|
||||
|
||||
const source = InitUiElements.InitLayers();
|
||||
InitUiElements.InitLayerSelection(source);
|
||||
|
||||
|
||||
// ------------------ Setup various other UI elements ------------
|
||||
|
||||
|
||||
InitUiElements.OnlyIf(State.state.featureSwitchAddNew, () => {
|
||||
|
||||
let presetCount = 0;
|
||||
for (const layer of State.state.filteredLayers.data) {
|
||||
for (const preset of layer.layerDef.presets) {
|
||||
|
@ -456,18 +525,18 @@ export class InitUiElements {
|
|||
return;
|
||||
}
|
||||
|
||||
|
||||
const newPointDialogIsShown = new UIEventSource<boolean>(false);
|
||||
const addNewPoint = new ScrollableFullScreen(
|
||||
() => Translations.t.general.add.title.Clone(),
|
||||
() => new SimpleAddUI(newPointDialogIsShown),
|
||||
"new",
|
||||
newPointDialogIsShown)
|
||||
addNewPoint.isShown.addCallback(isShown => {
|
||||
newPointDialogIsShown
|
||||
);
|
||||
addNewPoint.isShown.addCallback((isShown) => {
|
||||
if (!isShown) {
|
||||
State.state.LastClickLocation.setData(undefined)
|
||||
State.state.LastClickLocation.setData(undefined);
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
new StrayClickHandler(
|
||||
State.state.LastClickLocation,
|
||||
|
@ -477,7 +546,5 @@ export class InitUiElements {
|
|||
addNewPoint
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
}
|
||||
}
|
|
@ -6,9 +6,9 @@ import Img from "../../UI/Base/Img";
|
|||
import { LocalStorageSource } from "../Web/LocalStorageSource";
|
||||
import LayoutConfig from "../../Customizations/JSON/LayoutConfig";
|
||||
import { VariableUiElement } from "../../UI/Base/VariableUIElement";
|
||||
import { CenterFlexedElement } from "../../UI/Base/CenterFlexedElement";
|
||||
|
||||
export default class GeoLocationHandler extends VariableUiElement {
|
||||
|
||||
/**
|
||||
* Wether or not the geolocation is active, aka the user requested the current location
|
||||
* @private
|
||||
|
@ -30,7 +30,10 @@ export default class GeoLocationHandler extends VariableUiElement {
|
|||
* @private
|
||||
*/
|
||||
private readonly _hasLocation: UIEventSource<boolean>;
|
||||
private readonly _currentGPSLocation: UIEventSource<{ latlng: any; accuracy: number }>;
|
||||
private readonly _currentGPSLocation: UIEventSource<{
|
||||
latlng: any;
|
||||
accuracy: number;
|
||||
}>;
|
||||
/**
|
||||
* Kept in order to update the marker
|
||||
* @private
|
||||
|
@ -52,29 +55,41 @@ export default class GeoLocationHandler extends VariableUiElement {
|
|||
private readonly _previousLocationGrant: UIEventSource<string>;
|
||||
private readonly _layoutToUse: UIEventSource<LayoutConfig>;
|
||||
|
||||
|
||||
constructor(currentGPSLocation: UIEventSource<{ latlng: any; accuracy: number }>,
|
||||
constructor(
|
||||
currentGPSLocation: UIEventSource<{ latlng: any; accuracy: number }>,
|
||||
leafletMap: UIEventSource<L.Map>,
|
||||
layoutToUse: UIEventSource<LayoutConfig>) {
|
||||
|
||||
const hasLocation = currentGPSLocation.map((location) => location !== undefined);
|
||||
const previousLocationGrant = LocalStorageSource.Get("geolocation-permissions")
|
||||
layoutToUse: UIEventSource<LayoutConfig>
|
||||
) {
|
||||
const hasLocation = currentGPSLocation.map(
|
||||
(location) => location !== undefined
|
||||
);
|
||||
const previousLocationGrant = LocalStorageSource.Get(
|
||||
"geolocation-permissions"
|
||||
);
|
||||
const isActive = new UIEventSource<boolean>(false);
|
||||
|
||||
super(
|
||||
hasLocation.map(hasLocation => {
|
||||
|
||||
hasLocation.map(
|
||||
(hasLocation) => {
|
||||
if (hasLocation) {
|
||||
return Svg.crosshair_blue_ui()
|
||||
return new CenterFlexedElement(
|
||||
Img.AsImageElement(Svg.location, "", "width:1.5rem;height:1.5rem")
|
||||
); // crosshair_blue_ui()
|
||||
}
|
||||
if (isActive.data) {
|
||||
return Svg.crosshair_blue_center_ui();
|
||||
return new CenterFlexedElement(
|
||||
Img.AsImageElement(Svg.location, "", "width:1.5rem;height:1.5rem")
|
||||
); // crosshair_blue_center_ui
|
||||
}
|
||||
return Svg.crosshair_ui();
|
||||
}, [isActive])
|
||||
return new CenterFlexedElement(
|
||||
Img.AsImageElement(Svg.location, "", "width:1.5rem;height:1.5rem")
|
||||
); //crosshair_ui
|
||||
},
|
||||
[isActive]
|
||||
)
|
||||
);
|
||||
this._isActive = isActive;
|
||||
this._permission = new UIEventSource<string>("")
|
||||
this._permission = new UIEventSource<string>("");
|
||||
this._previousLocationGrant = previousLocationGrant;
|
||||
this._currentGPSLocation = currentGPSLocation;
|
||||
this._leafletMap = leafletMap;
|
||||
|
@ -82,47 +97,49 @@ export default class GeoLocationHandler extends VariableUiElement {
|
|||
this._hasLocation = hasLocation;
|
||||
const self = this;
|
||||
|
||||
const currentPointer = this._isActive.map(isActive => {
|
||||
const currentPointer = this._isActive.map(
|
||||
(isActive) => {
|
||||
if (isActive && !self._hasLocation.data) {
|
||||
return "cursor-wait"
|
||||
return "cursor-wait";
|
||||
}
|
||||
return "cursor-pointer"
|
||||
}, [this._hasLocation])
|
||||
currentPointer.addCallbackAndRun(pointerClass => {
|
||||
return "cursor-pointer";
|
||||
},
|
||||
[this._hasLocation]
|
||||
);
|
||||
currentPointer.addCallbackAndRun((pointerClass) => {
|
||||
self.SetClass(pointerClass);
|
||||
})
|
||||
|
||||
|
||||
this.onClick(() => self.init(true))
|
||||
this.init(false)
|
||||
});
|
||||
|
||||
this.onClick(() => self.init(true));
|
||||
this.init(false);
|
||||
}
|
||||
|
||||
private init(askPermission: boolean) {
|
||||
|
||||
const self = this;
|
||||
const map = this._leafletMap.data;
|
||||
|
||||
this._currentGPSLocation.addCallback((location) => {
|
||||
self._previousLocationGrant.setData("granted");
|
||||
|
||||
const timeSinceRequest = (new Date().getTime() - (self._lastUserRequest?.getTime() ?? 0)) / 1000;
|
||||
const timeSinceRequest =
|
||||
(new Date().getTime() - (self._lastUserRequest?.getTime() ?? 0)) / 1000;
|
||||
if (timeSinceRequest < 30) {
|
||||
self.MoveToCurrentLoction(16)
|
||||
self.MoveToCurrentLoction(16);
|
||||
}
|
||||
|
||||
let color = "#1111cc";
|
||||
try {
|
||||
color = getComputedStyle(document.body).getPropertyValue("--catch-detail-color")
|
||||
color = getComputedStyle(document.body).getPropertyValue(
|
||||
"--catch-detail-color"
|
||||
);
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
console.error(e);
|
||||
}
|
||||
const icon = L.icon(
|
||||
{
|
||||
const icon = L.icon({
|
||||
iconUrl: Img.AsData(Svg.crosshair.replace(/#000000/g, color)),
|
||||
iconSize: [40, 40], // size of the icon
|
||||
iconAnchor: [20, 20], // point of the icon which will correspond to marker's location
|
||||
})
|
||||
});
|
||||
|
||||
const newMarker = L.marker(location.latlng, { icon: icon });
|
||||
newMarker.addTo(map);
|
||||
|
@ -134,21 +151,20 @@ export default class GeoLocationHandler extends VariableUiElement {
|
|||
});
|
||||
|
||||
try {
|
||||
|
||||
navigator?.permissions?.query({name: 'geolocation'})
|
||||
navigator?.permissions
|
||||
?.query({ name: "geolocation" })
|
||||
?.then(function (status) {
|
||||
console.log("Geolocation is already", status)
|
||||
console.log("Geolocation is already", status);
|
||||
if (status.state === "granted") {
|
||||
self.StartGeolocating(false);
|
||||
}
|
||||
self._permission.setData(status.state);
|
||||
status.onchange = function () {
|
||||
self._permission.setData(status.state);
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
console.error(e);
|
||||
}
|
||||
if (askPermission) {
|
||||
self.StartGeolocating(true);
|
||||
|
@ -156,7 +172,6 @@ export default class GeoLocationHandler extends VariableUiElement {
|
|||
this._previousLocationGrant.setData("");
|
||||
self.StartGeolocating(false);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private locate() {
|
||||
|
@ -164,19 +179,22 @@ export default class GeoLocationHandler extends VariableUiElement {
|
|||
const map: any = this._leafletMap.data;
|
||||
|
||||
if (navigator.geolocation) {
|
||||
navigator.geolocation.getCurrentPosition(function (position) {
|
||||
navigator.geolocation.getCurrentPosition(
|
||||
function (position) {
|
||||
self._currentGPSLocation.setData({
|
||||
latlng: [position.coords.latitude, position.coords.longitude],
|
||||
accuracy: position.coords.accuracy
|
||||
});
|
||||
}, function () {
|
||||
console.warn("Could not get location with navigator.geolocation")
|
||||
accuracy: position.coords.accuracy,
|
||||
});
|
||||
},
|
||||
function () {
|
||||
console.warn("Could not get location with navigator.geolocation");
|
||||
}
|
||||
);
|
||||
return;
|
||||
} else {
|
||||
map.findAccuratePosition({
|
||||
maxWait: 10000, // defaults to 10000
|
||||
desiredAccuracy: 50 // defaults to 20
|
||||
desiredAccuracy: 50, // defaults to 20
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -185,34 +203,41 @@ export default class GeoLocationHandler extends VariableUiElement {
|
|||
const location = this._currentGPSLocation.data;
|
||||
this._lastUserRequest = undefined;
|
||||
|
||||
|
||||
if (this._currentGPSLocation.data.latlng[0] === 0 && this._currentGPSLocation.data.latlng[1] === 0) {
|
||||
console.debug("Not moving to GPS-location: it is null island")
|
||||
if (
|
||||
this._currentGPSLocation.data.latlng[0] === 0 &&
|
||||
this._currentGPSLocation.data.latlng[1] === 0
|
||||
) {
|
||||
console.debug("Not moving to GPS-location: it is null island");
|
||||
return;
|
||||
}
|
||||
|
||||
// We check that the GPS location is not out of bounds
|
||||
const b = this._layoutToUse.data.lockLocation
|
||||
const b = this._layoutToUse.data.lockLocation;
|
||||
let inRange = true;
|
||||
if (b) {
|
||||
if (b !== true) {
|
||||
// B is an array with our locklocation
|
||||
inRange = b[0][0] <= location.latlng[0] && location.latlng[0] <= b[1][0] &&
|
||||
b[0][1] <= location.latlng[1] && location.latlng[1] <= b[1][1];
|
||||
inRange =
|
||||
b[0][0] <= location.latlng[0] &&
|
||||
location.latlng[0] <= b[1][0] &&
|
||||
b[0][1] <= location.latlng[1] &&
|
||||
location.latlng[1] <= b[1][1];
|
||||
}
|
||||
}
|
||||
if (!inRange) {
|
||||
console.log("Not zooming to GPS location: out of bounds", b, location.latlng)
|
||||
} else {
|
||||
this._leafletMap.data.setView(
|
||||
location.latlng, targetZoom
|
||||
console.log(
|
||||
"Not zooming to GPS location: out of bounds",
|
||||
b,
|
||||
location.latlng
|
||||
);
|
||||
} else {
|
||||
this._leafletMap.data.setView(location.latlng, targetZoom);
|
||||
}
|
||||
}
|
||||
|
||||
private StartGeolocating(zoomToGPS = true) {
|
||||
const self = this;
|
||||
console.log("Starting geolocation")
|
||||
console.log("Starting geolocation");
|
||||
|
||||
this._lastUserRequest = zoomToGPS ? new Date() : new Date(0);
|
||||
if (self._permission.data === "denied") {
|
||||
|
@ -220,25 +245,21 @@ export default class GeoLocationHandler extends VariableUiElement {
|
|||
return "";
|
||||
}
|
||||
if (this._currentGPSLocation.data !== undefined) {
|
||||
this.MoveToCurrentLoction(16)
|
||||
this.MoveToCurrentLoction(16);
|
||||
}
|
||||
|
||||
|
||||
console.log("Searching location using GPS")
|
||||
console.log("Searching location using GPS");
|
||||
this.locate();
|
||||
|
||||
|
||||
if (!self._isActive.data) {
|
||||
self._isActive.setData(true);
|
||||
Utils.DoEvery(60000, () => {
|
||||
|
||||
if (document.visibilityState !== "visible") {
|
||||
console.log("Not starting gps: document not visible")
|
||||
console.log("Not starting gps: document not visible");
|
||||
return;
|
||||
}
|
||||
this.locate();
|
||||
})
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
}
|
17
Svg.ts
17
Svg.ts
File diff suppressed because one or more lines are too long
32
UI/Base/CenterFlexedElement.ts
Normal file
32
UI/Base/CenterFlexedElement.ts
Normal file
|
@ -0,0 +1,32 @@
|
|||
import BaseUIElement from "../BaseUIElement";
|
||||
|
||||
export class CenterFlexedElement extends BaseUIElement {
|
||||
private _html: string;
|
||||
|
||||
constructor(html: string) {
|
||||
super();
|
||||
this._html = html ?? "";
|
||||
}
|
||||
|
||||
InnerRender(): string {
|
||||
return this._html;
|
||||
}
|
||||
|
||||
protected InnerConstructElement(): HTMLElement {
|
||||
const e = document.createElement("div");
|
||||
e.innerHTML = this._html;
|
||||
e.style.display = "flex";
|
||||
e.style.height = "100%";
|
||||
e.style.width = "100%";
|
||||
e.style.flexDirection = "column";
|
||||
e.style.flexWrap = "nowrap";
|
||||
e.style.alignContent = "center";
|
||||
e.style.justifyContent = "center";
|
||||
e.style.alignItems = "center";
|
||||
return e;
|
||||
}
|
||||
|
||||
AsMarkdown(): string {
|
||||
return this._html;
|
||||
}
|
||||
}
|
|
@ -5,11 +5,11 @@ import Combine from "./Base/Combine";
|
|||
* A button floating above the map, in a uniform style
|
||||
*/
|
||||
export default class MapControlButton extends Combine {
|
||||
|
||||
constructor(contents: BaseUIElement) {
|
||||
super([contents]);
|
||||
this.SetClass("relative block rounded-full w-10 h-10 p-1 pointer-events-auto z-above-map subtle-background")
|
||||
this.SetClass(
|
||||
"relative block rounded-full w-10 h-10 p-1 pointer-events-auto z-above-map subtle-background"
|
||||
);
|
||||
this.SetStyle("box-shadow: 0 0 10px var(--shadow-color);");
|
||||
}
|
||||
|
||||
}
|
|
@ -1,50 +1,36 @@
|
|||
[
|
||||
{
|
||||
"authors": [
|
||||
"Pieter Vander Vennet"
|
||||
],
|
||||
"authors": ["Pieter Vander Vennet"],
|
||||
"path": "direction_masked.svg",
|
||||
"license": "CC0",
|
||||
"sources": []
|
||||
},
|
||||
{
|
||||
"authors": [
|
||||
"Pieter Vander Vennet"
|
||||
],
|
||||
"authors": ["Pieter Vander Vennet"],
|
||||
"path": "direction_outline.svg",
|
||||
"license": "CC0",
|
||||
"sources": []
|
||||
},
|
||||
{
|
||||
"authors": [
|
||||
"Pieter Vander Vennet"
|
||||
],
|
||||
"authors": ["Pieter Vander Vennet"],
|
||||
"path": "direction_stroke.svg",
|
||||
"license": "CC0",
|
||||
"sources": []
|
||||
},
|
||||
{
|
||||
"authors": [
|
||||
"Pieter Vander Vennet"
|
||||
],
|
||||
"authors": ["Pieter Vander Vennet"],
|
||||
"path": "SocialImageForeground.svg",
|
||||
"license": "CC-BY-SA",
|
||||
"sources": [
|
||||
"https://mapcomplete.osm.be"
|
||||
]
|
||||
"sources": ["https://mapcomplete.osm.be"]
|
||||
},
|
||||
{
|
||||
"authors": [
|
||||
"Pieter Vander Vennet"
|
||||
],
|
||||
"authors": ["Pieter Vander Vennet"],
|
||||
"path": "add.svg",
|
||||
"license": "CC0",
|
||||
"sources": []
|
||||
},
|
||||
{
|
||||
"authors": [
|
||||
"Pieter Vander Vennet"
|
||||
],
|
||||
"authors": ["Pieter Vander Vennet"],
|
||||
"path": "addSmall.svg",
|
||||
"license": "CC0",
|
||||
"sources": []
|
||||
|
@ -56,33 +42,25 @@
|
|||
"sources": []
|
||||
},
|
||||
{
|
||||
"authors": [
|
||||
"Pieter Vander Vennet"
|
||||
],
|
||||
"authors": ["Pieter Vander Vennet"],
|
||||
"path": "arrow-left-smooth.svg",
|
||||
"license": "CC0",
|
||||
"sources": []
|
||||
},
|
||||
{
|
||||
"authors": [
|
||||
"Pieter Vander Vennet"
|
||||
],
|
||||
"authors": ["Pieter Vander Vennet"],
|
||||
"path": "arrow-right-smooth.svg",
|
||||
"license": "CC0",
|
||||
"sources": []
|
||||
},
|
||||
{
|
||||
"authors": [
|
||||
"Pieter Vander Vennet"
|
||||
],
|
||||
"authors": ["Pieter Vander Vennet"],
|
||||
"path": "back.svg",
|
||||
"license": "CC0",
|
||||
"sources": []
|
||||
},
|
||||
{
|
||||
"authors": [
|
||||
"Github"
|
||||
],
|
||||
"authors": ["Github"],
|
||||
"path": "bug.svg",
|
||||
"license": "MIT",
|
||||
"sources": [
|
||||
|
@ -93,35 +71,26 @@
|
|||
{
|
||||
"path": "camera-plus.svg",
|
||||
"license": "CC-BY-SA 3.0",
|
||||
"authors": [
|
||||
"Dave Gandy",
|
||||
"Pieter Vander Vennet"
|
||||
],
|
||||
"authors": ["Dave Gandy", "Pieter Vander Vennet"],
|
||||
"sources": [
|
||||
"https://fontawesome.com/",
|
||||
"https://commons.wikimedia.org/wiki/File:Camera_font_awesome.svg"
|
||||
]
|
||||
},
|
||||
{
|
||||
"authors": [
|
||||
"Pieter Vander Vennet"
|
||||
],
|
||||
"authors": ["Pieter Vander Vennet"],
|
||||
"path": "checkmark.svg",
|
||||
"license": "CC0",
|
||||
"sources": []
|
||||
},
|
||||
{
|
||||
"authors": [
|
||||
"Pieter Vander Vennet"
|
||||
],
|
||||
"authors": ["Pieter Vander Vennet"],
|
||||
"path": "circle.svg",
|
||||
"license": "CC0",
|
||||
"sources": []
|
||||
},
|
||||
{
|
||||
"authors": [
|
||||
"Pieter Vander Vennet"
|
||||
],
|
||||
"authors": ["Pieter Vander Vennet"],
|
||||
"path": "clock.svg",
|
||||
"license": "CC0",
|
||||
"sources": []
|
||||
|
@ -163,9 +132,7 @@
|
|||
"sources": []
|
||||
},
|
||||
{
|
||||
"authors": [
|
||||
"Dave Gandy"
|
||||
],
|
||||
"authors": ["Dave Gandy"],
|
||||
"path": "delete_icon.svg",
|
||||
"license": "CC-BY-SA",
|
||||
"sources": [
|
||||
|
@ -197,9 +164,7 @@
|
|||
"sources": []
|
||||
},
|
||||
{
|
||||
"authors": [
|
||||
"The Tango Desktop Project"
|
||||
],
|
||||
"authors": ["The Tango Desktop Project"],
|
||||
"path": "floppy.svg",
|
||||
"license": "CC0",
|
||||
"sources": [
|
||||
|
@ -220,29 +185,19 @@
|
|||
"sources": []
|
||||
},
|
||||
{
|
||||
"authors": [
|
||||
"Timothy Miller"
|
||||
],
|
||||
"authors": ["Timothy Miller"],
|
||||
"path": "home.svg",
|
||||
"license": "CC-BY-SA 3.0",
|
||||
"sources": [
|
||||
"https://commons.wikimedia.org/wiki/File:Home-icon.svg"
|
||||
]
|
||||
"sources": ["https://commons.wikimedia.org/wiki/File:Home-icon.svg"]
|
||||
},
|
||||
{
|
||||
"authors": [
|
||||
"Timothy Miller"
|
||||
],
|
||||
"authors": ["Timothy Miller"],
|
||||
"path": "home_white_bg.svg",
|
||||
"license": "CC-BY-SA 3.0",
|
||||
"sources": [
|
||||
"https://commons.wikimedia.org/wiki/File:Home-icon.svg"
|
||||
]
|
||||
"sources": ["https://commons.wikimedia.org/wiki/File:Home-icon.svg"]
|
||||
},
|
||||
{
|
||||
"authors": [
|
||||
"JOSM Team"
|
||||
],
|
||||
"authors": ["JOSM Team"],
|
||||
"path": "josm_logo.svg",
|
||||
"license": "CC0",
|
||||
"sources": [
|
||||
|
@ -265,9 +220,7 @@
|
|||
{
|
||||
"path": "Ornament-Horiz-0.svg",
|
||||
"license": "CC-BY",
|
||||
"authors": [
|
||||
"Nightwolfdezines"
|
||||
],
|
||||
"authors": ["Nightwolfdezines"],
|
||||
"sources": [
|
||||
"https://www.vecteezy.com/vector-art/226361-ornaments-and-flourishes"
|
||||
]
|
||||
|
@ -275,9 +228,7 @@
|
|||
{
|
||||
"path": "Ornament-Horiz-1.svg",
|
||||
"license": "CC-BY",
|
||||
"authors": [
|
||||
"Nightwolfdezines"
|
||||
],
|
||||
"authors": ["Nightwolfdezines"],
|
||||
"sources": [
|
||||
"https://www.vecteezy.com/vector-art/226361-ornaments-and-flourishes"
|
||||
]
|
||||
|
@ -285,9 +236,7 @@
|
|||
{
|
||||
"path": "Ornament-Horiz-2.svg",
|
||||
"license": "CC-BY",
|
||||
"authors": [
|
||||
"Nightwolfdezines"
|
||||
],
|
||||
"authors": ["Nightwolfdezines"],
|
||||
"sources": [
|
||||
"https://www.vecteezy.com/vector-art/226361-ornaments-and-flourishes"
|
||||
]
|
||||
|
@ -295,9 +244,7 @@
|
|||
{
|
||||
"path": "Ornament-Horiz-3.svg",
|
||||
"license": "CC-BY",
|
||||
"authors": [
|
||||
"Nightwolfdezines"
|
||||
],
|
||||
"authors": ["Nightwolfdezines"],
|
||||
"sources": [
|
||||
"https://www.vecteezy.com/vector-art/226361-ornaments-and-flourishes"
|
||||
]
|
||||
|
@ -305,9 +252,7 @@
|
|||
{
|
||||
"path": "Ornament-Horiz-4.svg",
|
||||
"license": "CC-BY",
|
||||
"authors": [
|
||||
"Nightwolfdezines"
|
||||
],
|
||||
"authors": ["Nightwolfdezines"],
|
||||
"sources": [
|
||||
"https://www.vecteezy.com/vector-art/226361-ornaments-and-flourishes"
|
||||
]
|
||||
|
@ -315,9 +260,7 @@
|
|||
{
|
||||
"path": "Ornament-Horiz-5.svg",
|
||||
"license": "CC-BY",
|
||||
"authors": [
|
||||
"Nightwolfdezines"
|
||||
],
|
||||
"authors": ["Nightwolfdezines"],
|
||||
"sources": [
|
||||
"https://www.vecteezy.com/vector-art/226361-ornaments-and-flourishes"
|
||||
]
|
||||
|
@ -325,9 +268,7 @@
|
|||
{
|
||||
"path": "Ornament-Horiz-6.svg",
|
||||
"license": "CC-BY",
|
||||
"authors": [
|
||||
"Nightwolfdezines"
|
||||
],
|
||||
"authors": ["Nightwolfdezines"],
|
||||
"sources": [
|
||||
"https://www.vecteezy.com/vector-art/226361-ornaments-and-flourishes"
|
||||
]
|
||||
|
@ -369,25 +310,16 @@
|
|||
"sources": []
|
||||
},
|
||||
{
|
||||
"authors": [
|
||||
"Pieter Vander Vennet",
|
||||
" OSM"
|
||||
],
|
||||
"authors": ["Pieter Vander Vennet", " OSM"],
|
||||
"path": "mapcomplete_logo.svg",
|
||||
"license": "Logo; CC-BY-SA",
|
||||
"sources": [
|
||||
"https://mapcomplete.osm.be"
|
||||
]
|
||||
"sources": ["https://mapcomplete.osm.be"]
|
||||
},
|
||||
{
|
||||
"authors": [
|
||||
"Mapillary"
|
||||
],
|
||||
"authors": ["Mapillary"],
|
||||
"path": "mapillary.svg",
|
||||
"license": "Logo; All rights reserved",
|
||||
"sources": [
|
||||
"https://mapillary.com/"
|
||||
]
|
||||
"sources": ["https://mapillary.com/"]
|
||||
},
|
||||
{
|
||||
"authors": [],
|
||||
|
@ -411,32 +343,22 @@
|
|||
"authors": [],
|
||||
"path": "osm-copyright.svg",
|
||||
"license": "logo; all rights reserved",
|
||||
"sources": [
|
||||
"https://www.OpenStreetMap.org"
|
||||
]
|
||||
"sources": ["https://www.OpenStreetMap.org"]
|
||||
},
|
||||
{
|
||||
"authors": [
|
||||
"OpenStreetMap U.S. Chapter"
|
||||
],
|
||||
"authors": ["OpenStreetMap U.S. Chapter"],
|
||||
"path": "osm-logo-us.svg",
|
||||
"license": "Logo",
|
||||
"sources": [
|
||||
"https://www.openstreetmap.us/"
|
||||
]
|
||||
"sources": ["https://www.openstreetmap.us/"]
|
||||
},
|
||||
{
|
||||
"authors": [],
|
||||
"path": "osm-logo.svg",
|
||||
"license": "logo; all rights reserved",
|
||||
"sources": [
|
||||
"https://www.OpenStreetMap.org"
|
||||
]
|
||||
"sources": ["https://www.OpenStreetMap.org"]
|
||||
},
|
||||
{
|
||||
"authors": [
|
||||
"GitHub Octicons"
|
||||
],
|
||||
"authors": ["GitHub Octicons"],
|
||||
"path": "pencil.svg",
|
||||
"license": "MIT",
|
||||
"sources": [
|
||||
|
@ -445,14 +367,10 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"authors": [
|
||||
"@ tyskrat"
|
||||
],
|
||||
"authors": ["@ tyskrat"],
|
||||
"path": "phone.svg",
|
||||
"license": "CC-BY 3.0",
|
||||
"sources": [
|
||||
"https://www.onlinewebfonts.com/icon/1059"
|
||||
]
|
||||
"sources": ["https://www.onlinewebfonts.com/icon/1059"]
|
||||
},
|
||||
{
|
||||
"authors": [],
|
||||
|
@ -467,14 +385,10 @@
|
|||
"sources": []
|
||||
},
|
||||
{
|
||||
"authors": [
|
||||
"@fatih"
|
||||
],
|
||||
"authors": ["@fatih"],
|
||||
"path": "pop-out.svg",
|
||||
"license": "CC-BY 3.0",
|
||||
"sources": [
|
||||
"https://www.onlinewebfonts.com/icon/2151"
|
||||
]
|
||||
"sources": ["https://www.onlinewebfonts.com/icon/2151"]
|
||||
},
|
||||
{
|
||||
"authors": [],
|
||||
|
@ -489,9 +403,7 @@
|
|||
"sources": []
|
||||
},
|
||||
{
|
||||
"authors": [
|
||||
"OOjs UI Team and other contributors"
|
||||
],
|
||||
"authors": ["OOjs UI Team and other contributors"],
|
||||
"path": "search.svg",
|
||||
"license": "MIT",
|
||||
"sources": [
|
||||
|
@ -518,19 +430,13 @@
|
|||
"sources": []
|
||||
},
|
||||
{
|
||||
"authors": [
|
||||
"@felpgrc"
|
||||
],
|
||||
"authors": ["@felpgrc"],
|
||||
"path": "statistics.svg",
|
||||
"license": "CC-BY 3.0",
|
||||
"sources": [
|
||||
"https://www.onlinewebfonts.com/icon/197818"
|
||||
]
|
||||
"sources": ["https://www.onlinewebfonts.com/icon/197818"]
|
||||
},
|
||||
{
|
||||
"authors": [
|
||||
"MGalloway (WMF)"
|
||||
],
|
||||
"authors": ["MGalloway (WMF)"],
|
||||
"path": "translate.svg",
|
||||
"license": "CC-BY-SA 3.0",
|
||||
"sources": [
|
||||
|
@ -544,43 +450,45 @@
|
|||
"sources": []
|
||||
},
|
||||
{
|
||||
"authors": [
|
||||
"Wikidata"
|
||||
],
|
||||
"authors": ["Wikidata"],
|
||||
"path": "wikidata.svg",
|
||||
"license": "Logo; All rights reserved",
|
||||
"sources": [
|
||||
"https://www.wikidata.org"
|
||||
]
|
||||
"sources": ["https://www.wikidata.org"]
|
||||
},
|
||||
{
|
||||
"authors": [
|
||||
"Wikimedia"
|
||||
],
|
||||
"authors": ["Wikimedia"],
|
||||
"path": "wikimedia-commons-white.svg",
|
||||
"license": "Logo; All rights reserved",
|
||||
"sources": [
|
||||
"https://commons.wikimedia.org"
|
||||
]
|
||||
"sources": ["https://commons.wikimedia.org"]
|
||||
},
|
||||
{
|
||||
"authors": [
|
||||
"Wikipedia"
|
||||
],
|
||||
"authors": ["Wikipedia"],
|
||||
"path": "wikipedia.svg",
|
||||
"license": "Logo; All rights reserved",
|
||||
"sources": [
|
||||
"https://www.wikipedia.org/"
|
||||
]
|
||||
"sources": ["https://www.wikipedia.org/"]
|
||||
},
|
||||
{
|
||||
"authors": [
|
||||
"Mapillary"
|
||||
],
|
||||
"authors": ["Mapillary"],
|
||||
"path": "mapillary_black.svg",
|
||||
"license": "Logo; All rights reserved",
|
||||
"sources": [
|
||||
"https://www.mapillary.com/"
|
||||
]
|
||||
"sources": ["https://www.mapillary.com/"]
|
||||
},
|
||||
{
|
||||
"authors": ["Hannah Declerck"],
|
||||
"path": "location.svg",
|
||||
"license": "CC0",
|
||||
"sources": []
|
||||
},
|
||||
{
|
||||
"authors": ["Hannah Declerck"],
|
||||
"path": "min-zoom.svg",
|
||||
"license": "CC0",
|
||||
"sources": []
|
||||
},
|
||||
{
|
||||
"authors": ["Hannah Declerck"],
|
||||
"path": "plus-zoom.svg",
|
||||
"license": "CC0",
|
||||
"sources": []
|
||||
}
|
||||
]
|
4
assets/svg/location.svg
Normal file
4
assets/svg/location.svg
Normal file
|
@ -0,0 +1,4 @@
|
|||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M23.7304 11.1411H19.8992C19.7137 9.27763 18.8888 7.53597 17.5647 6.21181C16.2405 4.88765 14.4988 4.06268 12.6353 3.87719V0H11.1029V3.91554C9.2389 4.09962 7.49635 4.92404 6.17192 6.24847C4.84748 7.5729 4.02306 9.31545 3.83898 11.1794H0.0078125V12.7579H3.83898C4.02191 14.6215 4.84615 16.3639 6.17098 17.6874C7.49581 19.0108 9.23903 19.8331 11.1029 20.0141V23.8376H12.6353V20.0064C14.4986 19.8241 16.2411 19.0012 17.5656 17.678C18.8902 16.3548 19.7149 14.6134 19.8992 12.7502H23.7304V11.1717V11.1411ZM11.8614 18.4433C10.5736 18.4418 9.31515 18.0586 8.24511 17.342C7.17506 16.6254 6.34147 15.6076 5.84969 14.4174C5.35791 13.2272 5.23002 11.918 5.48219 10.6551C5.73436 9.39217 6.35529 8.23234 7.26646 7.32225C8.17762 6.41216 9.33805 5.7926 10.6012 5.54191C11.8644 5.29123 13.1736 5.42063 14.3633 5.91381C15.5529 6.40698 16.5696 7.24189 17.2849 8.31278C18.0002 9.38366 18.3821 10.6425 18.3821 11.9303C18.3851 12.7885 18.2187 13.639 17.8923 14.4327C17.566 15.2265 17.0861 15.948 16.4803 16.556C15.8745 17.1639 15.1547 17.6463 14.3621 17.9754C13.5694 18.3046 12.7197 18.474 11.8614 18.474V18.4433Z" fill="white"/>
|
||||
<path d="M11.8613 7.61646C12.7145 7.61646 13.5485 7.86939 14.2579 8.3434C14.9673 8.81742 15.5203 9.49112 15.8468 10.2794C16.1733 11.0676 16.2587 11.9351 16.0923 12.7719C15.9258 13.6087 15.5149 14.3773 14.9116 14.9806C14.3083 15.5839 13.5397 15.9949 12.7029 16.1614C11.8661 16.3278 10.9986 16.2423 10.2104 15.9158C9.42212 15.5893 8.74842 15.0364 8.27441 14.327C7.80039 13.6176 7.54736 12.7835 7.54736 11.9303C7.54939 10.7868 8.0045 9.69074 8.81307 8.88216C9.62164 8.07359 10.7178 7.61848 11.8613 7.61646Z" fill="white"/>
|
||||
</svg>
|
After Width: | Height: | Size: 1.7 KiB |
3
assets/svg/min-zoom.svg
Normal file
3
assets/svg/min-zoom.svg
Normal file
|
@ -0,0 +1,3 @@
|
|||
<svg width="22" height="22" viewBox="0 0 22 22" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M15.346 13.5045H14.3761L14.0324 13.173C15.2355 11.7734 15.9598 9.95647 15.9598 7.97991C15.9598 3.57254 12.3873 0 7.97991 0C3.57254 0 0 3.57254 0 7.97991C0 12.3873 3.57254 15.9598 7.97991 15.9598C9.95647 15.9598 11.7734 15.2355 13.173 14.0324L13.5045 14.3761V15.346L19.6429 21.4721L21.4721 19.6429L15.346 13.5045ZM7.97991 13.5045C4.92299 13.5045 2.45536 11.0368 2.45536 7.97991C2.45536 4.92299 4.92299 2.45536 7.97991 2.45536C11.0368 2.45536 13.5045 4.92299 13.5045 7.97991C13.5045 11.0368 11.0368 13.5045 7.97991 13.5045ZM4.91071 7.36607H11.0491V8.59375H4.91071V7.36607Z" fill="white"/>
|
||||
</svg>
|
After Width: | Height: | Size: 699 B |
5
assets/svg/plus-zoom.svg
Normal file
5
assets/svg/plus-zoom.svg
Normal file
|
@ -0,0 +1,5 @@
|
|||
<svg width="23" height="23" viewBox="0 0 23 23" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M16.0291 14.1876H15.0592L14.7155 13.8561C15.9186 12.4565 16.6429 10.6396 16.6429 8.66302C16.6429 4.25565 13.0704 0.683105 8.66302 0.683105C4.25565 0.683105 0.683105 4.25565 0.683105 8.66302C0.683105 13.0704 4.25565 16.6429 8.66302 16.6429C10.6396 16.6429 12.4565 15.9186 13.8561 14.7155L14.1876 15.0592V16.0291L20.326 22.1552L22.1552 20.326L16.0291 14.1876ZM8.66302 14.1876C5.6061 14.1876 3.13846 11.7199 3.13846 8.66302C3.13846 5.6061 5.6061 3.13846 8.66302 3.13846C11.7199 3.13846 14.1876 5.6061 14.1876 8.66302C14.1876 11.7199 11.7199 14.1876 8.66302 14.1876Z" fill="black"/>
|
||||
<path d="M16.0291 14.1876H15.0592L14.7155 13.8561C15.9186 12.4565 16.6429 10.6396 16.6429 8.66302C16.6429 4.25565 13.0704 0.683105 8.66302 0.683105C4.25565 0.683105 0.683105 4.25565 0.683105 8.66302C0.683105 13.0704 4.25565 16.6429 8.66302 16.6429C10.6396 16.6429 12.4565 15.9186 13.8561 14.7155L14.1876 15.0592V16.0291L20.326 22.1552L22.1552 20.326L16.0291 14.1876ZM8.66302 14.1876C5.6061 14.1876 3.13846 11.7199 3.13846 8.66302C3.13846 5.6061 5.6061 3.13846 8.66302 3.13846C11.7199 3.13846 14.1876 5.6061 14.1876 8.66302C14.1876 11.7199 11.7199 14.1876 8.66302 14.1876Z" fill="white"/>
|
||||
<path d="M11.7321 9.27679H9.27679V11.7321H8.04911V9.27679H5.59375V8.04911H8.04911V5.59375H9.27679V8.04911H11.7321V9.27679Z" fill="white"/>
|
||||
</svg>
|
After Width: | Height: | Size: 1.4 KiB |
|
@ -48,7 +48,7 @@
|
|||
|
||||
|
||||
:root {
|
||||
--subtle-detail-color: #e5f5ff;
|
||||
--subtle-detail-color: #007759;
|
||||
--subtle-detail-color-contrast: black;
|
||||
--subtle-detail-color-light-contrast: lightgrey;
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue