Cleaning away fullscreenmessage
This commit is contained in:
parent
977991cced
commit
7f1b78198a
12 changed files with 50 additions and 145 deletions
|
@ -10,14 +10,12 @@ import StrayClickHandler from "./Logic/Actors/StrayClickHandler";
|
||||||
import SimpleAddUI from "./UI/BigComponents/SimpleAddUI";
|
import SimpleAddUI from "./UI/BigComponents/SimpleAddUI";
|
||||||
import CenterMessageBox from "./UI/CenterMessageBox";
|
import CenterMessageBox from "./UI/CenterMessageBox";
|
||||||
import {AllKnownLayouts} from "./Customizations/AllKnownLayouts";
|
import {AllKnownLayouts} from "./Customizations/AllKnownLayouts";
|
||||||
import {TagUtils} from "./Logic/Tags";
|
|
||||||
import UserBadge from "./UI/BigComponents/UserBadge";
|
import UserBadge from "./UI/BigComponents/UserBadge";
|
||||||
import SearchAndGo from "./UI/BigComponents/SearchAndGo";
|
import SearchAndGo from "./UI/BigComponents/SearchAndGo";
|
||||||
import FullScreenMessageBox from "./UI/FullScreenMessageBoxHandler";
|
import FullScreenMessageBox from "./UI/FullScreenMessageBoxHandler";
|
||||||
import GeoLocationHandler from "./Logic/Actors/GeoLocationHandler";
|
import GeoLocationHandler from "./Logic/Actors/GeoLocationHandler";
|
||||||
import {LocalStorageSource} from "./Logic/Web/LocalStorageSource";
|
import {LocalStorageSource} from "./Logic/Web/LocalStorageSource";
|
||||||
import {Utils} from "./Utils";
|
import {Utils} from "./Utils";
|
||||||
import FeatureInfoBox from "./UI/Popup/FeatureInfoBox";
|
|
||||||
import Svg from "./Svg";
|
import Svg from "./Svg";
|
||||||
import Link from "./UI/Base/Link";
|
import Link from "./UI/Base/Link";
|
||||||
import * as personal from "./assets/themes/personalLayout/personalLayout.json"
|
import * as personal from "./assets/themes/personalLayout/personalLayout.json"
|
||||||
|
@ -121,49 +119,6 @@ export class InitUiElements {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Show the questions and information for the selected element
|
|
||||||
* This is given to the div which renders fullscreen on mobile devices
|
|
||||||
*/
|
|
||||||
State.state.selectedElement.addCallback((feature) => {
|
|
||||||
|
|
||||||
if (feature === undefined) {
|
|
||||||
State.state.fullScreenMessage.setData(undefined);
|
|
||||||
}
|
|
||||||
if (feature?.properties === undefined) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const data = feature.properties;
|
|
||||||
// Which is the applicable set?
|
|
||||||
for (const layer of layoutToUse.layers) {
|
|
||||||
if (typeof layer === "string") {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
const applicable = layer.overpassTags.matches(TagUtils.proprtiesToKV(data));
|
|
||||||
if (!applicable) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((layer.title ?? null) === null && layer.tagRenderings.length === 0) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// This layer is the layer that gives the questions
|
|
||||||
const featureBox = new FeatureInfoBox(
|
|
||||||
State.state.allElements.getEventSourceById(data.id),
|
|
||||||
layer
|
|
||||||
);
|
|
||||||
|
|
||||||
State.state.fullScreenMessage.setData({
|
|
||||||
content: featureBox,
|
|
||||||
hashText: feature.properties.id.replace("/", "_"),
|
|
||||||
titleText: featureBox.title
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
new HistoryHandling(Hash.hash, State.state.fullScreenMessage);
|
new HistoryHandling(Hash.hash, State.state.fullScreenMessage);
|
||||||
|
|
||||||
InitUiElements.OnlyIf(State.state.featureSwitchUserbadge, () => {
|
InitUiElements.OnlyIf(State.state.featureSwitchUserbadge, () => {
|
||||||
|
@ -256,7 +211,8 @@ export class InitUiElements {
|
||||||
|
|
||||||
private static InitWelcomeMessage() {
|
private static InitWelcomeMessage() {
|
||||||
|
|
||||||
const fullOptions = new FullWelcomePaneWithTabs();
|
const isOpened = new UIEventSource<boolean>(true);
|
||||||
|
const fullOptions = new FullWelcomePaneWithTabs(() => isOpened.setData(false));
|
||||||
|
|
||||||
const help = Svg.help_svg().SetClass("open-welcome-button");
|
const help = Svg.help_svg().SetClass("open-welcome-button");
|
||||||
const close = Svg.close_svg().SetClass("close-welcome-button");
|
const close = Svg.close_svg().SetClass("close-welcome-button");
|
||||||
|
@ -285,27 +241,12 @@ export class InitUiElements {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
const fullOptions2 = new FullWelcomePaneWithTabs();
|
|
||||||
if (Hash.hash.data === undefined) {
|
|
||||||
State.state.fullScreenMessage.setData({content: fullOptions2, hashText: "welcome"})
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
Svg.help_svg()
|
|
||||||
.SetClass("open-welcome-button")
|
|
||||||
.SetClass("shadow")
|
|
||||||
.onClick(() => {
|
|
||||||
State.state.fullScreenMessage.setData({content: fullOptions2, hashText: "welcome"})
|
|
||||||
}).AttachTo("help-button-mobile");
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static InitLayerSelection() {
|
private static InitLayerSelection() {
|
||||||
InitUiElements.OnlyIf(State.state.featureSwitchLayers, () => {
|
InitUiElements.OnlyIf(State.state.featureSwitchLayers, () => {
|
||||||
|
|
||||||
const layerControlPanel = new LayerControlPanel()
|
const layerControlPanel = new LayerControlPanel(() => State.state.layerControlIsOpened.setData(false))
|
||||||
.SetStyle("display:block;padding:0.75em;border-radius:1em;");
|
.SetStyle("display:block;padding:0.75em;border-radius:1em;");
|
||||||
const closeButton = Svg.close_svg().SetClass("layer-selection-toggle").SetStyle(" background: var(--subtle-detail-color);")
|
const closeButton = Svg.close_svg().SetClass("layer-selection-toggle").SetStyle(" background: var(--subtle-detail-color);")
|
||||||
const checkbox = new CheckBox(
|
const checkbox = new CheckBox(
|
||||||
|
@ -324,20 +265,9 @@ export class InitUiElements {
|
||||||
State.state.locationControl
|
State.state.locationControl
|
||||||
.addCallback(() => {
|
.addCallback(() => {
|
||||||
// Close the layer selection when the map is moved
|
// Close the layer selection when the map is moved
|
||||||
// checkbox.isEnabled.setData(false);
|
checkbox.isEnabled.setData(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
const fullScreen = new LayerControlPanel();
|
|
||||||
checkbox.isEnabled.addCallback(isEnabled => {
|
|
||||||
if (isEnabled) {
|
|
||||||
State.state.fullScreenMessage.setData({content: fullScreen, hashText: "layer-select"});
|
|
||||||
}
|
|
||||||
})
|
|
||||||
State.state.fullScreenMessage.addCallback(latest => {
|
|
||||||
if (latest === undefined) {
|
|
||||||
checkbox.isEnabled.setData(false);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -451,7 +381,9 @@ export class InitUiElements {
|
||||||
State.state.leafletMap,
|
State.state.leafletMap,
|
||||||
State.state.fullScreenMessage,
|
State.state.fullScreenMessage,
|
||||||
() => {
|
() => {
|
||||||
return new SimpleAddUI();
|
return new SimpleAddUI(
|
||||||
|
() => State.state.LastClickLocation.setData(undefined)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
|
@ -32,11 +32,16 @@ export default class StrayClickHandler {
|
||||||
})
|
})
|
||||||
|
|
||||||
lastClickLocation.addCallback(function (lastClick) {
|
lastClickLocation.addCallback(function (lastClick) {
|
||||||
selectedElement.setData(undefined);
|
|
||||||
|
|
||||||
if (self._lastMarker !== undefined) {
|
if (self._lastMarker !== undefined) {
|
||||||
leafletMap.data?.removeLayer(self._lastMarker);
|
leafletMap.data?.removeLayer(self._lastMarker);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(lastClick === undefined){
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
selectedElement.setData(undefined);
|
||||||
self._lastMarker = L.marker([lastClick.lat, lastClick.lon], {
|
self._lastMarker = L.marker([lastClick.lat, lastClick.lon], {
|
||||||
icon: L.icon({
|
icon: L.icon({
|
||||||
iconUrl: Img.AsData(Svg.add),
|
iconUrl: Img.AsData(Svg.add),
|
||||||
|
@ -51,7 +56,6 @@ export default class StrayClickHandler {
|
||||||
self._lastMarker.bindPopup(popup);
|
self._lastMarker.bindPopup(popup);
|
||||||
|
|
||||||
self._lastMarker.on("click", () => {
|
self._lastMarker.on("click", () => {
|
||||||
fullscreenMessage.setData({content: self._uiToShow(), hashText: "new"});
|
|
||||||
uiElement.Update();
|
uiElement.Update();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -11,22 +11,23 @@ export default class ScrollableFullScreen extends UIElement {
|
||||||
private _component: Combine;
|
private _component: Combine;
|
||||||
|
|
||||||
|
|
||||||
constructor(title: UIElement, content: UIElement) {
|
constructor(title: UIElement, content: UIElement, onClose: (() => void)) {
|
||||||
super();
|
super();
|
||||||
const returnToTheMap = Svg.back_svg().onClick(() => {
|
const returnToTheMap = Svg.back_svg().onClick(() => {
|
||||||
State.state.fullScreenMessage.setData(undefined);
|
onClose();
|
||||||
State.state.selectedElement.setData(undefined);
|
}).SetClass("sm:hidden")
|
||||||
}).SetClass("only-on-mobile")
|
|
||||||
.SetClass("featureinfobox-back-to-the-map")
|
.SetClass("featureinfobox-back-to-the-map")
|
||||||
title.SetStyle("width: 100%; display: block;")
|
title.SetStyle("width: 100%; display: block;")
|
||||||
const ornament = new Combine([new Ornament().SetStyle("height:5em;")]).SetClass("only-on-mobile")
|
const ornament = new Combine([new Ornament().SetStyle("height:5em;")])
|
||||||
|
.SetClass("block sm:hidden")
|
||||||
|
|
||||||
this._component = new Combine([
|
this._component = new Combine([
|
||||||
new Combine([returnToTheMap, title]).SetClass("featureinfobox-titlebar"),
|
new Combine([returnToTheMap, title])
|
||||||
new Combine(["<span>",content,"</span>", ornament]).SetClass("featureinfobox-content"),
|
.SetClass("text-xl break-words"),
|
||||||
// We add an ornament which takes around 5em. This is in order to make sure the Web UI doesn't hide
|
new Combine([content, ornament])
|
||||||
|
|
||||||
])
|
])
|
||||||
this.SetClass("featureinfobox");
|
this.SetClass("fixed h-screen w-screen fixed sm:relative");
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -15,8 +15,7 @@ import {TabbedComponent} from "../Base/TabbedComponent";
|
||||||
import {UIEventSource} from "../../Logic/UIEventSource";
|
import {UIEventSource} from "../../Logic/UIEventSource";
|
||||||
import LayoutConfig from "../../Customizations/JSON/LayoutConfig";
|
import LayoutConfig from "../../Customizations/JSON/LayoutConfig";
|
||||||
import UserDetails from "../../Logic/Osm/OsmConnection";
|
import UserDetails from "../../Logic/Osm/OsmConnection";
|
||||||
import {FixedUiElement} from "../Base/FixedUiElement";
|
import ScrollableFullScreen from "../Base/ScrollableFullScreen";
|
||||||
import CombinedInputElement from "../Input/CombinedInputElement";
|
|
||||||
|
|
||||||
export default class FullWelcomePaneWithTabs extends UIElement {
|
export default class FullWelcomePaneWithTabs extends UIElement {
|
||||||
private readonly _layoutToUse: UIEventSource<LayoutConfig>;
|
private readonly _layoutToUse: UIEventSource<LayoutConfig>;
|
||||||
|
@ -24,7 +23,7 @@ export default class FullWelcomePaneWithTabs extends UIElement {
|
||||||
|
|
||||||
private readonly _component: UIElement;
|
private readonly _component: UIElement;
|
||||||
|
|
||||||
constructor() {
|
constructor(onClose: () => void) {
|
||||||
super(State.state.layoutToUse);
|
super(State.state.layoutToUse);
|
||||||
this._layoutToUse = State.state.layoutToUse;
|
this._layoutToUse = State.state.layoutToUse;
|
||||||
this._userDetails = State.state.osmConnection.userDetails;
|
this._userDetails = State.state.osmConnection.userDetails;
|
||||||
|
@ -71,14 +70,13 @@ export default class FullWelcomePaneWithTabs extends UIElement {
|
||||||
const tabbedPart = new TabbedComponent(tabs, State.state.welcomeMessageOpenedTab)
|
const tabbedPart = new TabbedComponent(tabs, State.state.welcomeMessageOpenedTab)
|
||||||
.ListenTo(this._userDetails);
|
.ListenTo(this._userDetails);
|
||||||
|
|
||||||
const backButton = new Combine([
|
|
||||||
new Combine([Translations.t.general.returnToTheMap.Clone().SetClass("to-the-map")])
|
|
||||||
.SetClass("to-the-map-inner")
|
|
||||||
|
|
||||||
]).SetClass("only-on-mobile")
|
|
||||||
.onClick(() => State.state.fullScreenMessage.setData(undefined));
|
|
||||||
|
|
||||||
this._component = new Combine([tabbedPart, backButton]).SetStyle("width:100%;");
|
this._component = new ScrollableFullScreen(
|
||||||
|
layoutToUse.title,
|
||||||
|
tabbedPart,
|
||||||
|
onClose
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
InnerRender(): string {
|
InnerRender(): string {
|
||||||
|
|
|
@ -11,7 +11,7 @@ export default class LayerControlPanel extends UIElement {
|
||||||
private readonly _panel: UIElement;
|
private readonly _panel: UIElement;
|
||||||
|
|
||||||
|
|
||||||
constructor() {
|
constructor(onClose: () => void) {
|
||||||
super();
|
super();
|
||||||
let layerControlPanel: UIElement = new FixedUiElement("");
|
let layerControlPanel: UIElement = new FixedUiElement("");
|
||||||
if (State.state.layoutToUse.data.enableBackgroundLayerSelection) {
|
if (State.state.layoutToUse.data.enableBackgroundLayerSelection) {
|
||||||
|
@ -31,7 +31,9 @@ export default class LayerControlPanel extends UIElement {
|
||||||
|
|
||||||
const title =Translations.t.general.layerSelection.title.SetClass("featureinfobox-title")
|
const title =Translations.t.general.layerSelection.title.SetClass("featureinfobox-title")
|
||||||
|
|
||||||
this._panel = new ScrollableFullScreen(title, layerControlPanel);
|
this._panel = new ScrollableFullScreen(title, layerControlPanel, () => {
|
||||||
|
onClose
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
InnerRender(): string {
|
InnerRender(): string {
|
||||||
|
|
|
@ -37,7 +37,7 @@ export default class SimpleAddUI extends UIElement {
|
||||||
private readonly goToInboxButton: UIElement = new SubtleButton(Svg.envelope_ui(),
|
private readonly goToInboxButton: UIElement = new SubtleButton(Svg.envelope_ui(),
|
||||||
Translations.t.general.goToInbox, {url: "https://www.openstreetmap.org/messages/inbox", newTab: false});
|
Translations.t.general.goToInbox, {url: "https://www.openstreetmap.org/messages/inbox", newTab: false});
|
||||||
|
|
||||||
constructor() {
|
constructor(onClose: () => void) {
|
||||||
super(State.state.locationControl.map(loc => loc.zoom));
|
super(State.state.locationControl.map(loc => loc.zoom));
|
||||||
const self = this;
|
const self = this;
|
||||||
this.ListenTo(Locale.language);
|
this.ListenTo(Locale.language);
|
||||||
|
@ -61,17 +61,17 @@ export default class SimpleAddUI extends UIElement {
|
||||||
this.openLayerControl = new SubtleButton(Svg.layers_ui(),
|
this.openLayerControl = new SubtleButton(Svg.layers_ui(),
|
||||||
Translations.t.general.add.openLayerControl
|
Translations.t.general.add.openLayerControl
|
||||||
).onClick(() => {
|
).onClick(() => {
|
||||||
State.state.fullScreenMessage.setData(undefined);
|
|
||||||
State.state.layerControlIsOpened.setData(true);
|
State.state.layerControlIsOpened.setData(true);
|
||||||
})
|
})
|
||||||
}
|
|
||||||
|
|
||||||
InnerRender(): string {
|
|
||||||
|
|
||||||
this._component = new ScrollableFullScreen(
|
this._component = new ScrollableFullScreen(
|
||||||
Translations.t.general.add.title,
|
Translations.t.general.add.title,
|
||||||
this.CreateContent()
|
this.CreateContent(),
|
||||||
)
|
onClose
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
InnerRender(): string {
|
||||||
return this._component.Render();
|
return this._component.Render();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -68,7 +68,7 @@ export default class AllLayersPanel extends UIElement {
|
||||||
const layer = config.layers[i];
|
const layer = config.layers[i];
|
||||||
if (typeof layer !== "string") {
|
if (typeof layer !== "string") {
|
||||||
try {
|
try {
|
||||||
const iconTagRendering = new TagRenderingConfig(layer.icon, undefined, "icon")
|
const iconTagRendering = new TagRenderingConfig(layer["icon"], undefined, "icon")
|
||||||
const icon = iconTagRendering.GetRenderValue({"id": "node/-1"}).txt;
|
const icon = iconTagRendering.GetRenderValue({"id": "node/-1"}).txt;
|
||||||
return `<img src='${icon}'>`
|
return `<img src='${icon}'>`
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
|
|
@ -16,7 +16,8 @@ export default class FeatureInfoBox extends UIElement {
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
tags: UIEventSource<any>,
|
tags: UIEventSource<any>,
|
||||||
layerConfig: LayerConfig
|
layerConfig: LayerConfig,
|
||||||
|
onClose: () => {}
|
||||||
) {
|
) {
|
||||||
super();
|
super();
|
||||||
if (layerConfig === undefined) {
|
if (layerConfig === undefined) {
|
||||||
|
@ -60,7 +61,7 @@ export default class FeatureInfoBox extends UIElement {
|
||||||
new Combine([title, titleIcons]).SetClass("featureinfobox-titlebar-title")
|
new Combine([title, titleIcons]).SetClass("featureinfobox-titlebar-title")
|
||||||
])
|
])
|
||||||
|
|
||||||
this._component = new ScrollableFullScreen(titleBar, content)
|
this._component = new ScrollableFullScreen(titleBar, content, onClose)
|
||||||
}
|
}
|
||||||
|
|
||||||
InnerRender(): string {
|
InnerRender(): string {
|
||||||
|
|
|
@ -126,7 +126,7 @@ export default class ShowDataLayer {
|
||||||
|
|
||||||
|
|
||||||
const tags = State.state.allElements.getEventSourceFor(feature);
|
const tags = State.state.allElements.getEventSourceFor(feature);
|
||||||
const uiElement: LazyElement = new LazyElement(() => new FeatureInfoBox(tags, layer),
|
const uiElement: LazyElement = new LazyElement(() => new FeatureInfoBox(tags, layer, () => popup.closePopup()),
|
||||||
"<div style='height: 90vh'>Rendering</div>");
|
"<div style='height: 90vh'>Rendering</div>");
|
||||||
popup.setContent(uiElement.Render());
|
popup.setContent(uiElement.Render());
|
||||||
popup.on('remove', () => {
|
popup.on('remove', () => {
|
||||||
|
|
|
@ -20,17 +20,6 @@ Contains tweaks for small screens
|
||||||
display: none !important;
|
display: none !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
#messagesbox {
|
|
||||||
display: none;
|
|
||||||
background-color: var(--background-color);
|
|
||||||
color: var(--foreground-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
#help-button-mobile{
|
|
||||||
display: unset;
|
|
||||||
pointer-events: all;
|
|
||||||
}
|
|
||||||
|
|
||||||
#help-button-mobile div {
|
#help-button-mobile div {
|
||||||
box-shadow: 0 0 10px #0006;
|
box-shadow: 0 0 10px #0006;
|
||||||
margin-bottom: 10px;
|
margin-bottom: 10px;
|
||||||
|
@ -40,12 +29,6 @@ Contains tweaks for small screens
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
|
|
||||||
.leaflet-popup {
|
|
||||||
/* On mobile, the popups are shown as a full-screen element */
|
|
||||||
display: none;
|
|
||||||
visibility: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
#centermessage {
|
#centermessage {
|
||||||
top: 30%;
|
top: 30%;
|
||||||
left: 15%;
|
left: 15%;
|
||||||
|
|
|
@ -4,11 +4,6 @@
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
|
|
||||||
.featureinfobox-title {
|
|
||||||
font-size: xx-large;
|
|
||||||
word-break: break-word;
|
|
||||||
}
|
|
||||||
|
|
||||||
.featureinfobox-icons img {
|
.featureinfobox-icons img {
|
||||||
max-height: 1.5em;
|
max-height: 1.5em;
|
||||||
width: 1.5em;
|
width: 1.5em;
|
||||||
|
@ -63,15 +58,6 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.featureinfobox-content {
|
|
||||||
display: block;
|
|
||||||
max-height: 75vh;
|
|
||||||
overflow-y: auto;
|
|
||||||
padding-top: 1em;
|
|
||||||
width:100%;
|
|
||||||
overflow-x: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media only screen and (max-width: 600px), only screen and (max-height: 600px) {
|
@media only screen and (max-width: 600px), only screen and (max-height: 600px) {
|
||||||
.featureinfobox-content {
|
.featureinfobox-content {
|
||||||
display: block;
|
display: block;
|
||||||
|
|
|
@ -82,9 +82,7 @@ a {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#help-button-mobile {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
#layer-selection {
|
#layer-selection {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
|
Loading…
Reference in a new issue