diff --git a/Logic/Actors/SelectedFeatureHandler.ts b/Logic/Actors/SelectedFeatureHandler.ts index 69816866b..db6776fec 100644 --- a/Logic/Actors/SelectedFeatureHandler.ts +++ b/Logic/Actors/SelectedFeatureHandler.ts @@ -45,20 +45,6 @@ export default class SelectedFeatureHandler { const self = this hash.addCallback(() => self.setSelectedElementFromHash()) - - state.featurePipeline?.newDataLoadedSignal?.addCallbackAndRunD((_) => { - // New data was loaded. In initial startup, the hash might be set (via the URL) but might not be selected yet - if (hash.data === undefined || SelectedFeatureHandler._no_trigger_on.has(hash.data)) { - // This is an invalid hash anyway - return - } - if (state.selectedElement.data !== undefined) { - // We already have something selected - return - } - self.setSelectedElementFromHash() - }) - this.initialLoad() } diff --git a/Logic/Osm/OsmConnection.ts b/Logic/Osm/OsmConnection.ts index 692421b3f..6e294d2bc 100644 --- a/Logic/Osm/OsmConnection.ts +++ b/Logic/Osm/OsmConnection.ts @@ -62,7 +62,7 @@ export class OsmConnection { private readonly _singlePage: boolean private isChecking = false - constructor(options: { + constructor(options?: { dryRun?: UIEventSource fakeUser?: false | boolean oauth_token?: UIEventSource @@ -71,6 +71,7 @@ export class OsmConnection { osmConfiguration?: "osm" | "osm-test" attemptLogin?: true | boolean }) { + options = options ?? {} this.fakeUser = options.fakeUser ?? false this._singlePage = options.singlePage ?? true this._oauth_config = diff --git a/Logic/UIEventSource.ts b/Logic/UIEventSource.ts index 0ee8b5967..7db59652f 100644 --- a/Logic/UIEventSource.ts +++ b/Logic/UIEventSource.ts @@ -230,10 +230,12 @@ export abstract class Store { const newSource = new UIEventSource(this.data) + const self = this this.addCallback((latestData) => { window.setTimeout(() => { - if (this.data == latestData) { - // compare by reference + if (self.data == latestData) { + // compare by reference. + // Note that 'latestData' and 'self.data' are both from the same UIEVentSource, but both are dereferenced at a different time newSource.setData(latestData) } }, millisToStabilize) diff --git a/UI/Base/MinimapImplementation.ts b/UI/Base/MinimapImplementation.ts index 3b6364602..ebb46f033 100644 --- a/UI/Base/MinimapImplementation.ts +++ b/UI/Base/MinimapImplementation.ts @@ -414,6 +414,7 @@ export default class MinimapImplementation extends BaseUIElement implements Mini map.on("contextmenu", function (e) { // @ts-ignore lastClickLocation?.setData({ lat: e.latlng.lat, lon: e.latlng.lng }) + map.setZoom(map.getZoom() + 1) }) } diff --git a/UI/BigComponents/FullWelcomePaneWithTabs.ts b/UI/BigComponents/FullWelcomePaneWithTabs.ts index f21e01c29..e54b2e29a 100644 --- a/UI/BigComponents/FullWelcomePaneWithTabs.ts +++ b/UI/BigComponents/FullWelcomePaneWithTabs.ts @@ -37,12 +37,13 @@ export default class FullWelcomePaneWithTabs extends ScrollableFullScreen { featurePipeline: FeaturePipeline backgroundLayer: UIEventSource filteredLayers: UIEventSource - } & UserRelatedState + } & UserRelatedState, + guistate?: { userInfoIsOpened: UIEventSource } ) { const layoutToUse = state.layoutToUse super( () => layoutToUse.title.Clone(), - () => FullWelcomePaneWithTabs.GenerateContents(state, currentTab, isShown), + () => FullWelcomePaneWithTabs.GenerateContents(state, currentTab, isShown, guistate), "welcome", isShown ) @@ -60,12 +61,13 @@ export default class FullWelcomePaneWithTabs extends ScrollableFullScreen { filteredLayers: UIEventSource } & UserRelatedState, isShown: UIEventSource, - currentTab: UIEventSource + currentTab: UIEventSource, + guistate?: { userInfoIsOpened: UIEventSource } ): { header: string | BaseUIElement; content: BaseUIElement }[] { const tabs: { header: string | BaseUIElement; content: BaseUIElement }[] = [ { header: ``, - content: new ThemeIntroductionPanel(isShown, currentTab, state), + content: new ThemeIntroductionPanel(isShown, currentTab, state, guistate), }, ] @@ -113,11 +115,12 @@ export default class FullWelcomePaneWithTabs extends ScrollableFullScreen { filteredLayers: UIEventSource } & UserRelatedState, currentTab: UIEventSource, - isShown: UIEventSource + isShown: UIEventSource, + guistate?: { userInfoIsOpened: UIEventSource } ) { - const tabs = FullWelcomePaneWithTabs.ConstructBaseTabs(state, isShown, currentTab) + const tabs = FullWelcomePaneWithTabs.ConstructBaseTabs(state, isShown, currentTab, guistate) const tabsWithAboutMc = [ - ...FullWelcomePaneWithTabs.ConstructBaseTabs(state, isShown, currentTab), + ...FullWelcomePaneWithTabs.ConstructBaseTabs(state, isShown, currentTab, guistate), ] tabsWithAboutMc.push({ diff --git a/UI/BigComponents/ThemeIntroductionPanel.ts b/UI/BigComponents/ThemeIntroductionPanel.ts index 75b4fd1e2..09669d693 100644 --- a/UI/BigComponents/ThemeIntroductionPanel.ts +++ b/UI/BigComponents/ThemeIntroductionPanel.ts @@ -9,6 +9,7 @@ import Svg from "../../Svg" import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig" import { OsmConnection } from "../../Logic/Osm/OsmConnection" import FullWelcomePaneWithTabs from "./FullWelcomePaneWithTabs" +import LoggedInUserIndicator from "../LoggedInUserIndicator" export default class ThemeIntroductionPanel extends Combine { constructor( @@ -20,7 +21,8 @@ export default class ThemeIntroductionPanel extends Combine { featureSwitchUserbadge: UIEventSource layoutToUse: LayoutConfig osmConnection: OsmConnection - } + }, + guistate?: { userInfoIsOpened: UIEventSource } ) { const t = Translations.t.general const layout = state.layoutToUse @@ -36,9 +38,18 @@ export default class ThemeIntroductionPanel extends Combine { }) .SetClass("only-on-mobile") + const loggedInUserInfo = new LoggedInUserIndicator(state.osmConnection, { + firstLine: Translations.t.general.welcomeBack.Clone(), + }) + if (guistate?.userInfoIsOpened) { + loggedInUserInfo.onClick(() => { + guistate.userInfoIsOpened.setData(true) + }) + } + const loginStatus = new Toggle( new LoginToggle( - undefined, + loggedInUserInfo, new Combine([ Translations.t.general.loginWithOpenStreetMap.SetClass("text-xl font-bold"), Translations.t.general.loginOnlyNeededToEdit.Clone().SetClass("font-bold"), @@ -60,7 +71,7 @@ export default class ThemeIntroductionPanel extends Combine { ]).SetClass("flex flex-col mt-2"), toTheMap, - loginStatus.SetClass("block"), + loginStatus.SetClass("block mt-6 pt-2 md:border-t-2 border-dotted border-gray-400"), layout.descriptionTail?.Clone().SetClass("block mt-4"), languagePicker?.SetClass("block mt-4"), diff --git a/UI/BigComponents/UserInformation.ts b/UI/BigComponents/UserInformation.ts index 5f70fc228..25523b32d 100644 --- a/UI/BigComponents/UserInformation.ts +++ b/UI/BigComponents/UserInformation.ts @@ -140,12 +140,17 @@ class UserInformationMainPanel extends Combine { } export default class UserInformationPanel extends ScrollableFullScreen { - constructor(state: { - layoutToUse: LayoutConfig - osmConnection: OsmConnection - locationControl: UIEventSource - }) { - const isOpened = new UIEventSource(false) + constructor( + state: { + layoutToUse: LayoutConfig + osmConnection: OsmConnection + locationControl: UIEventSource + }, + options?: { + isOpened?: UIEventSource + } + ) { + const isOpened = options?.isOpened ?? new UIEventSource(false) super( () => { return new VariableUiElement( diff --git a/UI/DefaultGUI.ts b/UI/DefaultGUI.ts index 7497da269..b0f319ff8 100644 --- a/UI/DefaultGUI.ts +++ b/UI/DefaultGUI.ts @@ -33,7 +33,6 @@ import GeoLocationHandler from "../Logic/Actors/GeoLocationHandler" import { GeoLocationState } from "../Logic/State/GeoLocationState" import Hotkeys from "./Base/Hotkeys" import AvailableBaseLayers from "../Logic/Actors/AvailableBaseLayers" -import { Translation } from "./i18n/Translation" /** * The default MapComplete GUI initializer @@ -205,7 +204,9 @@ export default class DefaultGUI { const self = this new Combine([ Toggle.If(state.featureSwitchUserbadge, () => { - const userInfo = new UserInformationPanel(state) + const userInfo = new UserInformationPanel(state, { + isOpened: guiState.userInfoIsOpened, + }) const mapControl = new MapControlButton( new VariableUiElement( @@ -219,7 +220,7 @@ export default class DefaultGUI { { dontStyle: true, } - ).onClick(() => userInfo.Activate()) + ).onClick(() => guiState.userInfoIsOpened.setData(true)) return new LoginToggle( mapControl, @@ -292,7 +293,12 @@ export default class DefaultGUI { private InitWelcomeMessage(): BaseUIElement { const isOpened = this.guiState.welcomeMessageIsOpened - new FullWelcomePaneWithTabs(isOpened, this.guiState.welcomeMessageOpenedTab, this.state) + new FullWelcomePaneWithTabs( + isOpened, + this.guiState.welcomeMessageOpenedTab, + this.state, + this.guiState + ) // ?-Button on Desktop, opens panel with close-X. const help = new MapControlButton(Svg.help_svg()) diff --git a/UI/DefaultGuiState.ts b/UI/DefaultGuiState.ts index ccdfe323e..43c2c94de 100644 --- a/UI/DefaultGuiState.ts +++ b/UI/DefaultGuiState.ts @@ -9,6 +9,7 @@ export class DefaultGuiState { public readonly filterViewIsOpened: UIEventSource public readonly copyrightViewIsOpened: UIEventSource public readonly currentViewControlIsOpened: UIEventSource + public readonly userInfoIsOpened: UIEventSource public readonly welcomeMessageOpenedTab: UIEventSource public readonly allFullScreenStates: UIEventSource[] = [] @@ -43,8 +44,14 @@ export class DefaultGuiState { this.currentViewControlIsOpened = QueryParameters.GetBooleanQueryParameter( "currentview-toggle", false, - "Whether or not the current view box is shown" + "Whether or not the current view box is shown (metalayer showing current view, allows to do calculate stats for all in view)" ) + this.userInfoIsOpened = QueryParameters.GetBooleanQueryParameter( + "userinfo-toggle", + false, + "Whether or not the user info is shown" + ) + const states = { download: this.downloadControlIsOpened, filters: this.filterViewIsOpened, @@ -66,7 +73,8 @@ export class DefaultGuiState { this.filterViewIsOpened, this.copyrightViewIsOpened, this.welcomeMessageIsOpened, - this.currentViewControlIsOpened + this.currentViewControlIsOpened, + this.userInfoIsOpened ) for (let i = 0; i < this.allFullScreenStates.length; i++) { diff --git a/UI/LoggedInUserIndicator.ts b/UI/LoggedInUserIndicator.ts new file mode 100644 index 000000000..4d0ca0ac4 --- /dev/null +++ b/UI/LoggedInUserIndicator.ts @@ -0,0 +1,42 @@ +import { VariableUiElement } from "./Base/VariableUIElement" +import { OsmConnection } from "../Logic/Osm/OsmConnection" +import Svg from "../Svg" +import Img from "./Base/Img" +import Combine from "./Base/Combine" +import { FixedUiElement } from "./Base/FixedUiElement" +import BaseUIElement from "./BaseUIElement" + +export default class LoggedInUserIndicator extends VariableUiElement { + constructor( + osmConnection: OsmConnection, + options?: { + size?: "small" | "medium" | "large" + firstLine?: BaseUIElement + } + ) { + options = options ?? {} + let size = "w-8 h-8 mr-2" + if (options.size == "medium") { + size = "w-16 h-16 mr-4" + } else if (options.size == "large") { + size = "w-32 h-32 mr-6" + } + super( + osmConnection.userDetails.mapD((ud) => { + let img = Svg.person_svg().SetClass( + "rounded-full border border-black overflow-hidden" + ) + if (ud.img) { + img = new Img(ud.img) + } + let contents: BaseUIElement = new FixedUiElement(ud.name).SetClass("font-bold") + if (options?.firstLine) { + contents = new Combine([options.firstLine, contents]).SetClass("flex flex-col") + } + return new Combine([img.SetClass("rounded-full " + size), contents]).SetClass( + "flex items-center" + ) + }) + ) + } +} diff --git a/UI/Popup/DeleteWizard.ts b/UI/Popup/DeleteWizard.ts index ed999b066..ff5950aff 100644 --- a/UI/Popup/DeleteWizard.ts +++ b/UI/Popup/DeleteWizard.ts @@ -176,6 +176,13 @@ export default class DeleteWizard extends Toggle { undefined, isShown ) + + const self = this + confirm.addCallbackAndRunD((dialogIsOpened) => { + if (dialogIsOpened) { + self.ScrollIntoView() + } + }) } private static constructConfirmButton( diff --git a/UI/Popup/MoveWizard.ts b/UI/Popup/MoveWizard.ts index f1a617590..8a11d97b6 100644 --- a/UI/Popup/MoveWizard.ts +++ b/UI/Popup/MoveWizard.ts @@ -284,5 +284,13 @@ export default class MoveWizard extends Toggle { ]).SetClass("flex m-2 p-2 rounded-lg bg-gray-200"), moveDisallowedReason.map((r) => r === undefined) ) + + const self = this + currentStep.addCallback((state) => { + if (state === "start") { + return + } + self.ScrollIntoView() + }) } } diff --git a/UI/Popup/SplitRoadWizard.ts b/UI/Popup/SplitRoadWizard.ts index ab963a8f5..dbdac314c 100644 --- a/UI/Popup/SplitRoadWizard.ts +++ b/UI/Popup/SplitRoadWizard.ts @@ -24,6 +24,7 @@ import BaseLayer from "../../Models/BaseLayer" import FilteredLayer from "../../Models/FilteredLayer" import BaseUIElement from "../BaseUIElement" import { VariableUiElement } from "../Base/VariableUIElement" +import ScrollableFullScreen from "../Base/ScrollableFullScreen" export default class SplitRoadWizard extends Combine { // @ts-ignore @@ -54,6 +55,7 @@ export default class SplitRoadWizard extends Combine { changes: Changes layoutToUse: LayoutConfig allElements: ElementStorage + selectedElement: UIEventSource } ) { const t = Translations.t.split @@ -79,9 +81,6 @@ export default class SplitRoadWizard extends Combine { hasBeenSplit ) ) - splitButton.onClick(() => { - splitClicked.setData(true) - }) // Only show the splitButton if logged in, else show login prompt const loginBtn = t.loginToSplit @@ -110,6 +109,9 @@ export default class SplitRoadWizard extends Combine { // We throw away the old map and splitpoints, and create a new map from scratch splitPoints.setData([]) leafletMap.setData(SplitRoadWizard.setupMapComponent(id, splitPoints, state)) + + // Close the popup. The contributor has to select a segment again to make sure they continue editing the correct segment; see #1219 + ScrollableFullScreen.collapse() }) saveButton.SetClass("btn btn-primary mr-3") @@ -147,6 +149,11 @@ export default class SplitRoadWizard extends Combine { new Toggle(mapView, splitToggle, splitClicked), ]) this.dialogIsOpened = splitClicked + const self = this + splitButton.onClick(() => { + splitClicked.setData(true) + self.ScrollIntoView() + }) } private static setupMapComponent( diff --git a/UI/ShowDataLayer/ShowDataLayerImplementation.ts b/UI/ShowDataLayer/ShowDataLayerImplementation.ts index 8febac169..2db66af1c 100644 --- a/UI/ShowDataLayer/ShowDataLayerImplementation.ts +++ b/UI/ShowDataLayer/ShowDataLayerImplementation.ts @@ -4,8 +4,10 @@ import { ShowDataLayerOptions } from "./ShowDataLayerOptions" import { ElementStorage } from "../../Logic/ElementStorage" import RenderingMultiPlexerFeatureSource from "../../Logic/FeatureSource/Sources/RenderingMultiPlexerFeatureSource" import ScrollableFullScreen from "../Base/ScrollableFullScreen" -import { LeafletMouseEvent } from "leaflet" +import { LeafletMouseEvent, PathOptions } from "leaflet" import Hash from "../../Logic/Web/Hash" +import { BBox } from "../../Logic/BBox" +import { Utils } from "../../Utils" /* // import 'leaflet-polylineoffset'; We don't actually import it here. It is imported in the 'MinimapImplementation'-class, which'll result in a patched 'L' object. @@ -47,6 +49,7 @@ export default class ShowDataLayerImplementation { string, { feature: any; activateFunc: (event: LeafletMouseEvent) => void } >() + private readonly showDataLayerid: number private readonly createPopup: ( tags: UIEventSource, @@ -81,7 +84,7 @@ export default class ShowDataLayerImplementation { } const self = this - options.leafletMap.addCallback((_) => { + options.leafletMap.addCallback(() => { return self.update(options) }) @@ -112,6 +115,10 @@ export default class ShowDataLayerImplementation { }) this._selectedElement?.addCallbackAndRunD((selected) => { + if (selected === undefined) { + ScrollableFullScreen.collapse() + return + } self.openPopupOfSelectedElement(selected) }) @@ -171,17 +178,8 @@ export default class ShowDataLayerImplementation { } const self = this - const data = { - type: "FeatureCollection", - features: [], - } - // @ts-ignore - this.geoLayer = L.geoJSON(data, { - style: (feature) => self.createStyleFor(feature), - pointToLayer: (feature, latLng) => self.pointToLayer(feature, latLng), - onEachFeature: (feature, leafletLayer) => - self.postProcessFeature(feature, leafletLayer), - }) + + this.geoLayer = new L.LayerGroup() const selfLayer = this.geoLayer const allFeats = this._features.features.data @@ -189,6 +187,31 @@ export default class ShowDataLayerImplementation { if (feat === undefined) { continue } + + // Why not one geojson layer with _all_ features, and attaching a right-click onto every feature individually? + // Because that somehow doesn't work :( + const feature = feat + const geojsonLayer = L.geoJSON(feature, { + style: (feature) => self.createStyleFor(feature), + pointToLayer: (feature, latLng) => self.pointToLayer(feature, latLng), + onEachFeature: (feature, leafletLayer) => + self.postProcessFeature(feature, leafletLayer), + }) + if (feature.geometry.type === "Point") { + geojsonLayer.on({ + contextmenu: (e) => { + const o = self.leafletLayersPerId.get(feature?.properties?.id) + o?.activateFunc(e) + Utils.preventDefaultOnMouseEvent(e.originalEvent) + }, + dblclick: (e) => { + const o = self.leafletLayersPerId.get(feature?.properties?.id) + o?.activateFunc(e) + Utils.preventDefaultOnMouseEvent(e.originalEvent) + }, + }) + } + this.geoLayer.addLayer(geojsonLayer) try { if (feat.geometry.type === "LineString") { const coords = L.GeoJSON.coordsToLatLngs(feat.geometry.coordinates) @@ -229,7 +252,7 @@ export default class ShowDataLayerImplementation { return self.geoLayer !== selfLayer }) } else { - this.geoLayer.addData(feat) + geojsonLayer.addData(feat) } } catch (e) { console.error( @@ -242,14 +265,14 @@ export default class ShowDataLayerImplementation { } } - if (options.zoomToFeatures ?? false) { - if (this.geoLayer.getLayers().length > 0) { - try { - const bounds = this.geoLayer.getBounds() - mp.fitBounds(bounds, { animate: false }) - } catch (e) { - console.debug("Invalid bounds", e) - } + if ((options.zoomToFeatures ?? false) && allFeats.length > 0) { + let bound = undefined + for (const feat of allFeats) { + const fbound = BBox.get(feat) + bound = bound?.unionWith(fbound) ?? fbound + } + if (bound !== undefined) { + mp.fitBounds(bound?.toLeaflet(), { animate: false }) } } @@ -312,29 +335,7 @@ export default class ShowDataLayerImplementation { icon: L.divIcon(style), }) } - - /** - * Post processing - basically adding the popup - * @param feature - * @param leafletLayer - * @private - */ - private postProcessFeature(feature, leafletLayer: L.Evented) { - const layer: LayerConfig = this._layerToShow - if (layer.title === undefined || !this._enablePopups) { - // No popup action defined -> Don't do anything - // or probably a map in the popup - no popups needed! - return - } - const key = feature.properties.id - if (this.leafletLayersPerId.has(key)) { - const activate = this.leafletLayersPerId.get(key) - leafletLayer.addEventListener("click", activate.activateFunc) - if (Hash.hash.data === key) { - activate.activateFunc(null) - } - return - } + private createActivateFunction(feature, key: string, layer: LayerConfig): (event) => void { let infobox: ScrollableFullScreen = undefined const self = this @@ -354,17 +355,36 @@ export default class ShowDataLayerImplementation { self._selectedElement.setData( self.allElements.ContainingFeatures.get(feature.id) ?? feature ) - event?.originalEvent?.preventDefault() - event?.originalEvent?.stopPropagation() - event?.originalEvent?.stopImmediatePropagation() - if (event?.originalEvent) { - // This is a total workaround, as 'preventDefault' and everything above seems to be not working - event.originalEvent["dismissed"] = true - } + } + return activate + } + /** + * Post processing - basically adding the popup + * @param feature + * @param leafletLayer + * @private + */ + private postProcessFeature(feature, leafletLayer: L.Evented) { + const layer: LayerConfig = this._layerToShow + if (layer.title === undefined || !this._enablePopups) { + // No popup action defined -> Don't do anything + // or probably a map in the popup - no popups needed! + return + } + const key = feature.properties.id + let activate: (event) => void + if (this.leafletLayersPerId.has(key)) { + activate = this.leafletLayersPerId.get(key).activateFunc + } else { + activate = this.createActivateFunction(feature, key, layer) } - leafletLayer.addEventListener("click", activate) - + // We also have to open on rightclick, doubleclick, ... as users sometimes do this. See #1219 + leafletLayer.on({ + dblclick: activate, + contextmenu: activate, + click: activate, + }) // Add the feature to the index to open the popup when needed this.leafletLayersPerId.set(key, { feature: feature, diff --git a/Utils.ts b/Utils.ts index 8817853e6..6fae645c5 100644 --- a/Utils.ts +++ b/Utils.ts @@ -900,7 +900,7 @@ In the case that MapComplete is pointed to the testing grounds, the edit will be url: string, maxCacheTimeMs: number, headers?: any - ): Promise { + ): Promise<{ content: any } | { error: string; url: string; statuscode?: number }> { const cached = Utils._download_cache.get(url) if (cached !== undefined) { if (new Date().getTime() - cached.timestamp <= maxCacheTimeMs) { @@ -1074,6 +1074,16 @@ In the case that MapComplete is pointed to the testing grounds, the edit will be ) } + public static preventDefaultOnMouseEvent(event: any) { + event?.originalEvent?.preventDefault() + event?.originalEvent?.stopPropagation() + event?.originalEvent?.stopImmediatePropagation() + if (event?.originalEvent) { + // This is a total workaround, as 'preventDefault' and everything above seems to be not working + event.originalEvent["dismissed"] = true + } + } + public static OsmChaLinkFor(daysInThePast, theme = undefined): string { const now = new Date() const lastWeek = new Date(now.getTime() - daysInThePast * 24 * 60 * 60 * 1000) diff --git a/css/index-tailwind-output.css b/css/index-tailwind-output.css index e76ec2439..f3ae9bd25 100644 --- a/css/index-tailwind-output.css +++ b/css/index-tailwind-output.css @@ -624,6 +624,10 @@ video { position: relative; } +.\!relative { + position: relative !important; +} + .sticky { position: -webkit-sticky; position: sticky; @@ -807,18 +811,22 @@ video { margin-top: 0.25rem; } -.mt-4 { - margin-top: 1rem; -} - -.ml-3 { - margin-left: 0.75rem; -} - .mr-2 { margin-right: 0.5rem; } +.mr-4 { + margin-right: 1rem; +} + +.mr-6 { + margin-right: 1.5rem; +} + +.mt-4 { + margin-top: 1rem; +} + .ml-4 { margin-left: 1rem; } @@ -827,10 +835,6 @@ video { margin-bottom: 6rem; } -.mr-4 { - margin-right: 1rem; -} - .mb-2 { margin-bottom: 0.5rem; } @@ -839,6 +843,10 @@ video { margin-left: 0.5rem; } +.ml-3 { + margin-left: 0.75rem; +} + .ml-12 { margin-left: 3rem; } @@ -960,6 +968,18 @@ video { height: 16rem; } +.h-8 { + height: 2rem; +} + +.h-16 { + height: 4rem; +} + +.h-32 { + height: 8rem; +} + .h-10 { height: 2.5rem; } @@ -988,22 +1008,10 @@ video { height: 1.5rem; } -.h-8 { - height: 2rem; -} - -.h-32 { - height: 8rem; -} - .h-96 { height: 24rem; } -.h-16 { - height: 4rem; -} - .h-0 { height: 0px; } @@ -1048,6 +1056,18 @@ video { width: 1.5rem; } +.w-8 { + width: 2rem; +} + +.w-16 { + width: 4rem; +} + +.w-32 { + width: 8rem; +} + .w-10 { width: 2.5rem; } @@ -1072,10 +1092,6 @@ video { width: 2.75rem; } -.w-8 { - width: 2rem; -} - .w-min { width: -webkit-min-content; width: min-content; @@ -1090,14 +1106,6 @@ video { width: 24rem; } -.w-32 { - width: 8rem; -} - -.w-16 { - width: 4rem; -} - .w-auto { width: auto; } @@ -1374,6 +1382,10 @@ video { border-bottom-width: 1px; } +.border-dotted { + border-style: dotted; +} + .border-black { --tw-border-opacity: 1; border-color: rgb(0 0 0 / var(--tw-border-opacity)); @@ -1864,6 +1876,11 @@ body { box-sizing: initial !important; } +.leaflet-marker-icon img { + -webkit-touch-callout: none; + /* prevent callout to copy image, etc when tap to hold */ +} + .leaflet-control-attribution { display: block ruby; } @@ -2738,6 +2755,10 @@ input { border-radius: 0.75rem; } + .md\:border-t-2 { + border-top-width: 2px; + } + .md\:p-1 { padding: 0.25rem; } diff --git a/index.css b/index.css index 6a2fb0a3f..2b5e5df62 100644 --- a/index.css +++ b/index.css @@ -113,6 +113,10 @@ body { box-sizing: initial !important; } +.leaflet-marker-icon img { + -webkit-touch-callout: none; /* prevent callout to copy image, etc when tap to hold */ +} + .leaflet-control-attribution { display: block ruby; } diff --git a/langs/en.json b/langs/en.json index 0d63cc6f8..a67879fcf 100644 --- a/langs/en.json +++ b/langs/en.json @@ -327,7 +327,7 @@ "tuesday": "Tuesday", "wednesday": "Wednesday" }, - "welcomeBack": "You are logged in, welcome back!", + "welcomeBack": "Welcome back!", "welcomeExplanation": { "addNew": "Tap the map to add a new POI.", "browseMoreMaps": "Discover more maps",