From 1de8588b867e75fffe9c44d271e12b797ea1256a Mon Sep 17 00:00:00 2001 From: Roeckelein-Nina Date: Wed, 22 Oct 2025 10:16:22 +0200 Subject: [PATCH 1/2] LayerBookmarks Plugin: Save and restore custom layer visibilities without changing map position Adds new LayerBookmarks Plugin + Quick Select Menu in Bottom Bar --------- Co-authored-by: Benedikt Seidl --- actions/bookmark.js | 39 ++++ appConfig.js | 2 + components/BookmarkPanel.jsx | 143 ++++++++++++ components/StandardApp.jsx | 4 + components/widgets/GroupSelect.jsx | 56 +++++ doc/plugins.md | 16 +- plugins/Bookmark.jsx | 334 +++++++++++------------------ plugins/BottomBar.jsx | 32 +++ plugins/LayerBookmark.jsx | 111 ++++++++++ plugins/Settings.jsx | 47 ++-- reducers/bookmark.js | 25 +++ static/translations/bg-BG.json | 19 +- static/translations/ca-ES.json | 19 +- static/translations/cs-CZ.json | 19 +- static/translations/de-CH.json | 19 +- static/translations/de-DE.json | 19 +- static/translations/en-US.json | 19 +- static/translations/es-ES.json | 19 +- static/translations/fi-FI.json | 19 +- static/translations/fr-FR.json | 19 +- static/translations/hu-HU.json | 19 +- static/translations/it-IT.json | 19 +- static/translations/ja-JP.json | 19 +- static/translations/nl-NL.json | 19 +- static/translations/no-NO.json | 19 +- static/translations/pl-PL.json | 19 +- static/translations/pt-BR.json | 19 +- static/translations/pt-PT.json | 19 +- static/translations/ro-RO.json | 19 +- static/translations/ru-RU.json | 19 +- static/translations/sv-SE.json | 19 +- static/translations/tr-TR.json | 19 +- static/translations/tsconfig.json | 17 +- static/translations/uk-UA.json | 19 +- utils/PermaLinkUtils.js | 81 ++++--- 35 files changed, 1034 insertions(+), 291 deletions(-) create mode 100644 actions/bookmark.js create mode 100644 components/BookmarkPanel.jsx create mode 100644 components/widgets/GroupSelect.jsx create mode 100644 plugins/LayerBookmark.jsx create mode 100644 reducers/bookmark.js diff --git a/actions/bookmark.js b/actions/bookmark.js new file mode 100644 index 000000000..9356f5f3f --- /dev/null +++ b/actions/bookmark.js @@ -0,0 +1,39 @@ +/** + * Copyright 2025 Stadtwerke München GmbH + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. + */ + +import axios from 'axios'; + +import bookmarkReducer from '../reducers/bookmark'; +import ReducerIndex from '../reducers/index'; +import ConfigUtils from '../utils/ConfigUtils'; +ReducerIndex.register("bookmark", bookmarkReducer); + +export const SET_BOOKMARKS = 'SET_BOOKMARKS'; + +export function setBookmarks(bookmarks) { + return { + type: SET_BOOKMARKS, + bookmarks + }; +} + +export function refreshUserBookmarks() { + return (dispatch, getState) => { + const username = ConfigUtils.getConfigProp("username"); + const permalinkServiceUrl = ConfigUtils.getConfigProp("permalinkServiceUrl"); + if (username && permalinkServiceUrl) { + axios.get(permalinkServiceUrl.replace(/\/$/, '') + "/bookmarks/") + .then(response => { + dispatch(setBookmarks(response.data || [])); + }) + .catch(() => { + dispatch(setBookmarks([])); + }); + } + }; +} diff --git a/appConfig.js b/appConfig.js index 064513887..24bd0b0da 100644 --- a/appConfig.js +++ b/appConfig.js @@ -28,6 +28,7 @@ import HeightProfilePlugin from './plugins/HeightProfile'; import HelpPlugin from './plugins/Help'; import HomeButtonPlugin from './plugins/HomeButton'; import IdentifyPlugin from './plugins/Identify'; +import LayerBookmarkPlugin from './plugins/LayerBookmark'; import LayerCatalogPlugin from './plugins/LayerCatalog'; import LayerTreePlugin from './plugins/LayerTree'; import LocateButtonPlugin from './plugins/LocateButton'; @@ -89,6 +90,7 @@ export default { AuthenticationPlugin: AuthenticationPlugin, BackgroundSwitcherPlugin: BackgroundSwitcherPlugin, BookmarkPlugin: BookmarkPlugin, + LayerBookmarkPlugin: LayerBookmarkPlugin, BottomBarPlugin: BottomBarPlugin, CookiePopupPlugin: CookiePopupPlugin, CyclomediaPlugin: CyclomediaPlugin, diff --git a/components/BookmarkPanel.jsx b/components/BookmarkPanel.jsx new file mode 100644 index 000000000..a7982dd4a --- /dev/null +++ b/components/BookmarkPanel.jsx @@ -0,0 +1,143 @@ +/** + * Copyright 2021 Oslandia SAS + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. + */ + +import React from 'react'; + +import classnames from 'classnames'; +import isEmpty from 'lodash.isempty'; +import isEqual from 'lodash.isequal'; +import PropTypes from 'prop-types'; + +import ConfigUtils from '../utils/ConfigUtils'; +import Icon from './Icon'; +import Spinner from './widgets/Spinner'; + +import '../plugins/style/Bookmark.css'; + + +/** + * Reusable panel component for managing bookmarks. + * + * Used in both Bookmark and LayerBookmark plugins. + */ +export default class BookmarkPanel extends React.Component { + static propTypes = { + bookmarks: PropTypes.array, + onAdd: PropTypes.func, + onOpen: PropTypes.func, + onRemove: PropTypes.func, + onUpdate: PropTypes.func, + onZoomToExtent: PropTypes.func, + translations: PropTypes.objectOf(PropTypes.string) + }; + state = { + currentBookmark: null, + description: "", + adding: false, + saving: false, + trashing: false + }; + componentDidUpdate(prevProps) { + if (prevProps.bookmarks !== this.props.bookmarks) { + this.setState({adding: false, saving: false, trashing: false}); + // Select a recently added bookmark + const addedBookmark = this.props.bookmarks.find(bookmark => + !prevProps.bookmarks.some(prevBookmark => prevBookmark.key === bookmark.key) + ); + if (addedBookmark) { + this.setState({ + currentBookmark: addedBookmark.key, + description: addedBookmark.description + }); + } + } + } + render() { + const username = ConfigUtils.getConfigProp("username"); + const currentBookmark = this.props.bookmarks.find(bookmark => bookmark.key === this.state.currentBookmark); + + return ( +
+ {!username ? ( + this.props.translations?.notloggedin + ) : ( + <> +

{this.props.translations?.manage}

+
+ this.setState({description: ev.target.value})} + onKeyDown={ev => {if (ev.key === "Enter" && this.state.description !== "") { this.addBookmark(); }}} + placeholder={this.props.translations?.description} type="text" value={this.state.description} /> +
+
+ + + {this.props.onZoomToExtent ? ( + + ) : null} + + + + +
+
+ {this.props.bookmarks.map((bookmark) => { + const itemclasses = classnames({ + "bookmark-list-item": true, + "bookmark-list-item-active": this.state.currentBookmark === bookmark.key + }); + return ( +
this.toggleCurrentBookmark(bookmark)} + onDoubleClick={() => this.props.onOpen(bookmark.key, false)} + title={this.props.translations?.lastUpdate + ": " + bookmark.date} + > + {bookmark.description} +
+ ); + })} + {isEmpty(this.props.bookmarks) ? ( +
{this.props.translations?.nobookmarks}
+ ) : null} +
+ + )} +
+ ); + } + toggleCurrentBookmark = (bookmark) => { + if (this.state.currentBookmark === bookmark.key) { + this.setState({currentBookmark: null, description: ""}); + } else { + this.setState({currentBookmark: bookmark.key, description: bookmark.description}); + } + }; + addBookmark = () => { + this.setState({adding: true, description: "", currentBookmark: null}); + this.props.onAdd(this.state.description); + }; + updateBookmark = (bookmark) => { + this.setState({saving: true}); + this.props.onUpdate(bookmark.key, this.state.description); + }; + removeBookmark = (bookmark) => { + this.setState({trashing: true, description: "", currentBookmark: null}); + this.props.onRemove(bookmark.key); + }; +} diff --git a/components/StandardApp.jsx b/components/StandardApp.jsx index c7fa391d0..21084e7ae 100644 --- a/components/StandardApp.jsx +++ b/components/StandardApp.jsx @@ -34,6 +34,7 @@ import PluginsContainer from './PluginsContainer'; import './style/App.css'; import './style/DefaultColorScheme.css'; +import {refreshUserBookmarks} from '../actions/bookmark'; const CSRF_TOKEN = MiscUtils.getCsrfToken(); @@ -303,6 +304,9 @@ export default class StandardApp extends React.Component { const colorScheme = initialParams.style || storedColorScheme || ConfigUtils.getConfigProp("defaultColorScheme"); StandardApp.store.dispatch(setColorScheme(colorScheme)); + // Load all bookmarks + StandardApp.store.dispatch(refreshUserBookmarks()); + // Resolve permalink and restore settings resolvePermaLink(initialParams, (params, state, success) => { StandardApp.store.dispatch(setStartupParameters(params, state)); diff --git a/components/widgets/GroupSelect.jsx b/components/widgets/GroupSelect.jsx new file mode 100644 index 000000000..914e795b2 --- /dev/null +++ b/components/widgets/GroupSelect.jsx @@ -0,0 +1,56 @@ +/** + * Copyright 2025 Stadtwerke München GmbH + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. + */ + +import React, {Component} from 'react'; + +import PropTypes from 'prop-types'; + +/** + * Dropdown for selecting options grouped under sections. + */ +export default class GroupSelect extends Component { + static propTypes = { + defaultOption: PropTypes.array, + onChange: PropTypes.func, + options: PropTypes.object, + placeholder: PropTypes.string, + value: PropTypes.string + }; + static defaultProps = { + defaultOption: null, + placeholder: null + }; + render() { + return ( + + ); + } + onChange = (e) => { + this.props.onChange(e.target.value); + }; +} diff --git a/doc/plugins.md b/doc/plugins.md index 084ec1d02..cd57bade7 100644 --- a/doc/plugins.md +++ b/doc/plugins.md @@ -17,6 +17,7 @@ Plugin reference * [Help](#help) * [HomeButton](#homebutton) * [Identify](#identify) +* [LayerBookmark](#layerbookmark) * [LayerCatalog](#layercatalog) * [LayerTree](#layertree) * [LocateButton](#locatebutton) @@ -288,7 +289,7 @@ Map button for switching the background layer. Bookmark ---------------------------------------------------------------- -Allows managing user bookmarks. +Allows managing user bookmarks which are storing the current view, including the location and zoom level. Bookmarks are only allowed for authenticated users. @@ -306,6 +307,7 @@ Bottom bar, displaying mouse coordinate, scale, etc. |----------|------|-------------|---------------| | additionalBottomBarLinks | `[{`
`  label: string,`
`  labelMsgId: string,`
`  side: string,`
`  url: string,`
`  urlTarget: string,`
`  icon: string,`
`}]` | Additional bottombar links.`side` can be `left` or `right` (default). | `undefined` | | coordinateFormatter | `func` | Custom coordinate formatter, as `(coordinate, crs) => string`. | `undefined` | +| displayBookmarkDropdown | `string` | Whether to display a dropdown menu for the selection of user bookmarks in the bottom bar. Possible values are `none`, `all`, `withPosition` and `withoutPosition`. | `'none'` | | displayCoordinates | `bool` | Whether to display the coordinates in the bottom bar. | `true` | | displayScalebar | `bool` | Whether to display the scalebar in the bottom bar. | `true` | | displayScales | `bool` | Whether to display the scale in the bottom bar. | `true` | @@ -498,6 +500,18 @@ for customized queries and templates for the result presentation. | resultDisplayMode | `string` | Result display mode, one of `tree`, `flat`, `paginated`. | `'flat'` | | showLayerSelector | `bool` | Whether to show a layer selector to filter the identify results by layer. | `true` | +LayerBookmark +---------------------------------------------------------------- +Allows managing layer bookmarks which are storing the currently visible layers without the location and zoom level. + +Bookmarks are only allowed for authenticated users. + +Requires `permalinkServiceUrl` to point to a `qwc-permalink-service`. + +| Property | Type | Description | Default value | +|----------|------|-------------|---------------| +| side | `string` | The side of the application on which to display the sidebar. | `'right'` | + LayerCatalog ---------------------------------------------------------------- Displays a pre-configured catalog of external layers in a window. diff --git a/plugins/Bookmark.jsx b/plugins/Bookmark.jsx index 7f52c96ab..4be94d4af 100644 --- a/plugins/Bookmark.jsx +++ b/plugins/Bookmark.jsx @@ -1,204 +1,130 @@ -/** - * Copyright 2021 Oslandia SAS - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. - */ - -import React from 'react'; -import {connect} from 'react-redux'; - -import classnames from 'classnames'; -import isEmpty from 'lodash.isempty'; -import PropTypes from 'prop-types'; - -import {zoomToExtent, zoomToPoint} from '../actions/map'; -import Icon from '../components/Icon'; -import SideBar from '../components/SideBar'; -import Spinner from '../components/widgets/Spinner'; -import ConfigUtils from '../utils/ConfigUtils'; -import LocaleUtils from '../utils/LocaleUtils'; -import MapUtils from '../utils/MapUtils'; -import {createBookmark, getUserBookmarks, removeBookmark, resolveBookmark, updateBookmark} from '../utils/PermaLinkUtils'; - -import './style/Bookmark.css'; - -/** - * Allows managing user bookmarks. - * - * Bookmarks are only allowed for authenticated users. - * - * Requires `permalinkServiceUrl` to point to a `qwc-permalink-service`. - */ -class Bookmark extends React.Component { - static availableIn3D = true; - - static propTypes = { - mapCrs: PropTypes.string, - mapScales: PropTypes.array, - /** The side of the application on which to display the sidebar. */ - side: PropTypes.string, - zoomToExtent: PropTypes.func, - zoomToPoint: PropTypes.func - }; - static defaultProps = { - side: 'right' - }; - state = { - bookmarks: [], - currentBookmark: null, - description: "", - saving: false - }; - componentDidMount() { - this.refresh(); - } - render() { - const openTitle = LocaleUtils.tr("bookmark.open"); - const openTabTitle = LocaleUtils.tr("bookmark.openTab"); - const zoomTitle = LocaleUtils.tr("bookmark.zoomToExtent"); - const username = ConfigUtils.getConfigProp("username"); - const placeholder = LocaleUtils.tr("bookmark.description"); - const addBookmarkTitle = LocaleUtils.tr("bookmark.add"); - const updateTitle = LocaleUtils.tr("bookmark.update"); - const removeTitle = LocaleUtils.tr("bookmark.remove"); - const lastUpdateTitle = LocaleUtils.tr("bookmark.lastUpdate"); - - const currentBookmark = this.state.bookmarks.find(bookmark => bookmark.key === this.state.currentBookmark); - return ( - - {!username ? ( -
{LocaleUtils.tr("bookmark.notloggedin")}
- ) : ( -
-

{LocaleUtils.tr("bookmark.manage")}

-
- this.setState({description: ev.target.value})} - onKeyDown={ev => {if (ev.key === "Enter" && this.state.description !== "") { this.addBookmark(); }}} - placeholder={placeholder} type="text" value={this.state.description} /> -
-
- - - {this.props.mapCrs && this.props.mapScales ? ( - - ) : null} - - - - -
-
- {this.state.bookmarks.map((bookmark) => { - const itemclasses = classnames({ - "bookmark-list-item": true, - "bookmark-list-item-active": this.state.currentBookmark === bookmark.key - }); - return ( -
this.toggleCurrentBookmark(bookmark)} - onDoubleClick={() => this.open(bookmark.key, false)} - title={lastUpdateTitle + ": " + bookmark.date} - > - {bookmark.description} -
- ); - })} - {isEmpty(this.state.bookmarks) ? ( -
{LocaleUtils.tr("bookmark.nobookmarks")}
- ) : null} -
-
- )} -
- ); - } - open = (bookmarkkey, newtab) => { - const url = location.href.split("?")[0] + '?bk=' + bookmarkkey; - if (newtab) { - window.open(url, '_blank'); - } else { - location.href = url; - } - }; - zoomToBookmarkExtent = (bookmarkkey) => { - resolveBookmark(bookmarkkey, (params) => { - if ('c' in params && 's' in params) { - const scale = parseFloat(params.s); - const zoom = MapUtils.computeZoom(this.props.mapScales, scale); - const center = params.c.split(/[;,]/g).map(x => parseFloat(x)); - this.props.zoomToPoint(center, zoom, params.crs ?? this.props.mapCrs); - } else if ('e' in params) { - const bounds = (params.e).split(',').map(n => parseFloat(n)); - this.props.zoomToExtent(bounds, params.crs ?? this.props.mapCrs); - } - }); - }; - toggleCurrentBookmark = (bookmark) => { - if (this.state.currentBookmark === bookmark.key) { - this.setState({currentBookmark: null, description: ""}); - } else { - this.setState({currentBookmark: bookmark.key, description: bookmark.description}); - } - }; - addBookmark = () => { - createBookmark(this.state.description, (success) => { - if (!success) { - /* eslint-disable-next-line */ - alert(LocaleUtils.tr("bookmark.addfailed")); - } - this.refresh(); - }); - this.setState({description: "", currentBookmark: null}); - }; - updateBookmark = (bookmark) => { - this.setState({saving: true}); - updateBookmark(bookmark.key, this.state.description, (success) => { - if (!success) { - /* eslint-disable-next-line */ - alert(LocaleUtils.tr("bookmark.savefailed")); - } - this.setState({saving: false, description: "", currentBookmark: null}); - this.refresh(); - }); - }; - removeBookmark = (bookmark) => { - removeBookmark(bookmark.key, (success) => { - if (!success) { - /* eslint-disable-next-line */ - alert(LocaleUtils.tr("bookmark.removefailed")); - } - this.refresh(); - }); - }; - refresh = () => { - getUserBookmarks(ConfigUtils.getConfigProp("username"), (bookmarks) => { - this.setState({bookmarks: bookmarks}); - }); - }; -} - -const selector = state => ({ - mapCrs: state.map?.projection, - mapScales: state.map?.scales -}); - -export default connect(selector, { - zoomToExtent: zoomToExtent, - zoomToPoint: zoomToPoint -})(Bookmark); +import React from 'react'; +import {connect} from 'react-redux'; + +import PropTypes from 'prop-types'; + +import {refreshUserBookmarks} from '../actions/bookmark'; +import {zoomToExtent, zoomToPoint} from "../actions/map"; +import BookmarkPanel from '../components/BookmarkPanel'; +import SideBar from '../components/SideBar'; +import LocaleUtils from "../utils/LocaleUtils"; +import MapUtils from "../utils/MapUtils"; +import {createBookmark, openBookmark, removeBookmark, resolveBookmark, updateBookmark} from '../utils/PermaLinkUtils'; + + +/** + * Allows managing user bookmarks which are storing the current view, including the location and zoom level. + * + * Bookmarks are only allowed for authenticated users. + * + * Requires `permalinkServiceUrl` to point to a `qwc-permalink-service`. + */ +class Bookmark extends React.Component { + static availableIn3D = true; + static propTypes = { + bookmarks: PropTypes.array, + mapCrs: PropTypes.string, + mapScales: PropTypes.array, + refreshUserBookmarks: PropTypes.func, + /** The side of the application on which to display the sidebar. */ + side: PropTypes.string, + zoomToExtent: PropTypes.func, + zoomToPoint: PropTypes.func + }; + static defaultProps = { + side: 'right' + }; + translations = { + add: LocaleUtils.tr("bookmark.add"), + description: LocaleUtils.tr("bookmark.description"), + lastUpdate: LocaleUtils.tr("bookmark.lastUpdate"), + manage: LocaleUtils.tr("bookmark.manage"), + nobookmarks: LocaleUtils.tr("bookmark.nobookmarks"), + notloggedin: LocaleUtils.tr("bookmark.notloggedin"), + open: LocaleUtils.tr("bookmark.open"), + openTab: LocaleUtils.tr("bookmark.openTab"), + remove: LocaleUtils.tr("bookmark.remove"), + update: LocaleUtils.tr("bookmark.update"), + zoomToExtent: LocaleUtils.tr("bookmark.zoomToExtent") + }; + render() { + return ( + + {() => ({ + body: this.renderBody() + })} + + ); + } + renderBody = () => { + const bookmarks = (this.props.bookmarks || []).filter(b => b.data.query.c && b.data.query.s); + return ( + + ); + }; + onOpen = (bookmark, newtab) => { + openBookmark(bookmark, newtab); + }; + onAdd = (description) => { + createBookmark(description, (success) => { + if (!success) { + /* eslint-disable-next-line */ + alert(LocaleUtils.tr("bookmark.addfailed")); + } + this.props.refreshUserBookmarks(); + }); + }; + onRemove = (bookmarkKey) => { + removeBookmark(bookmarkKey, (success) => { + if (!success) { + /* eslint-disable-next-line */ + alert(LocaleUtils.tr("bookmark.removefailed")); + } + this.props.refreshUserBookmarks(); + }); + }; + onUpdate = (bookmarkKey, description) => { + updateBookmark(bookmarkKey, description, (success) => { + if (!success) { + /* eslint-disable-next-line */ + alert(LocaleUtils.tr("bookmark.savefailed")); + } + this.props.refreshUserBookmarks(); + }); + }; + zoomToBookmarkExtent = (bookmarkkey) => { + if (this.props.mapCrs && this.props.mapScales) { + resolveBookmark(bookmarkkey, (params) => { + if ('c' in params && 's' in params) { + const scale = parseFloat(params.s); + const zoom = MapUtils.computeZoom(this.props.mapScales, scale); + const center = params.c.split(/[;,]/g).map(x => parseFloat(x)); + this.props.zoomToPoint(center, zoom, params.crs ?? this.props.mapCrs); + } else if ('e' in params) { + const bounds = (params.e).split(',').map(n => parseFloat(n)); + this.props.zoomToExtent(bounds, params.crs ?? this.props.mapCrs); + } + }); + } + }; +} +const selector = state => ({ + bookmarks: state.bookmark?.bookmarks, + mapCrs: state.map?.projection, + mapScales: state.map?.scales +}); +export default connect(selector, { + refreshUserBookmarks: refreshUserBookmarks, + zoomToExtent: zoomToExtent, + zoomToPoint: zoomToPoint +})(Bookmark); diff --git a/plugins/BottomBar.jsx b/plugins/BottomBar.jsx index f55cd0e70..460eaf986 100644 --- a/plugins/BottomBar.jsx +++ b/plugins/BottomBar.jsx @@ -22,6 +22,8 @@ import LocaleUtils from '../utils/LocaleUtils'; import MapUtils from '../utils/MapUtils'; import './style/BottomBar.css'; +import GroupSelect from '../components/widgets/GroupSelect'; +import {openBookmark} from '../utils/PermaLinkUtils'; /** @@ -39,9 +41,12 @@ class BottomBar extends React.Component { icon: PropTypes.string })), additionalMouseCrs: PropTypes.array, + bookmarks: PropTypes.array, changeZoomLevel: PropTypes.func, /** Custom coordinate formatter, as `(coordinate, crs) => string`. */ coordinateFormatter: PropTypes.func, + /** Whether to display a dropdown menu for the selection of user bookmarks in the bottom bar. */ + displayBookmarkDropdown: PropTypes.bool, /** Whether to display the coordinates in the bottom bar. */ displayCoordinates: PropTypes.bool, /** Whether to display the scalebar in the bottom bar. */ @@ -71,6 +76,7 @@ class BottomBar extends React.Component { }; static defaultProps = { displayCoordinates: true, + displayBookmarkDropdown: false, displayScalebar: true, displayScales: true }; @@ -143,6 +149,21 @@ class BottomBar extends React.Component { ); } + let bookmarkDropdown = null; + if (this.props.displayBookmarkDropdown && this.props.bookmarks && this.props.bookmarks.length > 0) { + const bookmarks = (this.props.bookmarks || []).filter(b => b.data.query.c && b.data.query.s); + const layerBookmarks = (this.props.bookmarks || []).filter(b => !b.data.query.c && !b.data.query.s); + const options = { + [LocaleUtils.tr("appmenu.items.Bookmark")]: bookmarks.map(bm => [bm.key, bm.description]), + [LocaleUtils.tr("appmenu.items.LayerBookmark")]: layerBookmarks.map(bm => [bm.key, bm.description]) + }; + bookmarkDropdown = ( +
+ {LocaleUtils.tr("bottombar.bookmark_label")}:  + +
+ ); + } const style = { marginLeft: this.props.mapMargins.outerLeft + 'px', marginRight: this.props.mapMargins.outerRight + 'px' @@ -157,6 +178,7 @@ class BottomBar extends React.Component { {coordinates} {scales} + {bookmarkDropdown} {rightBottomLinks} @@ -188,6 +210,15 @@ class BottomBar extends React.Component { this.props.openExternalUrl(url, target, {title, icon}); ev.preventDefault(); }; + openBookmark = (key) => { + openBookmark( + key, + false, + this.props.map.center, + this.props.map.scales, + this.props.map.zoom + ); + }; setScale = (value) => { const scale = parseInt(value, 10); if (!isNaN(scale)) { @@ -206,6 +237,7 @@ class BottomBar extends React.Component { export default connect((state) => ({ map: state.map, + bookmarks: state.bookmark?.bookmarks, fullscreen: state.display?.fullscreen, mapMargins: state.windows.mapMargins, additionalMouseCrs: state.theme.current?.additionalMouseCrs ?? [] diff --git a/plugins/LayerBookmark.jsx b/plugins/LayerBookmark.jsx new file mode 100644 index 000000000..f7d15ee82 --- /dev/null +++ b/plugins/LayerBookmark.jsx @@ -0,0 +1,111 @@ +import React from 'react'; +import {connect} from 'react-redux'; + +import PropTypes from 'prop-types'; + +import {refreshUserBookmarks} from '../actions/bookmark'; +import BookmarkPanel from '../components/BookmarkPanel'; +import SideBar from '../components/SideBar'; +import LocaleUtils from "../utils/LocaleUtils"; +import {createBookmark, openBookmark, removeBookmark, updateBookmark} from '../utils/PermaLinkUtils'; + + +/** + * Allows managing layer bookmarks which are storing the currently visible layers without the location and zoom level. + * + * Bookmarks are only allowed for authenticated users. + * + * Requires `permalinkServiceUrl` to point to a `qwc-permalink-service`. + */ +class LayerBookmark extends React.Component { + static availableIn3D = true; + static propTypes = { + bookmarks: PropTypes.array, + mapCenter: PropTypes.string, + mapScales: PropTypes.array, + mapZoom: PropTypes.number, + refreshUserBookmarks: PropTypes.func, + /** The side of the application on which to display the sidebar. */ + side: PropTypes.string + }; + static defaultProps = { + side: 'right' + }; + translations = { + add: LocaleUtils.tr("layerbookmark.add"), + description: LocaleUtils.tr("layerbookmark.description"), + lastUpdate: LocaleUtils.tr("layerbookmark.lastUpdate"), + manage: LocaleUtils.tr("layerbookmark.manage"), + nobookmarks: LocaleUtils.tr("layerbookmark.nobookmarks"), + notloggedin: LocaleUtils.tr("layerbookmark.notloggedin"), + open: LocaleUtils.tr("layerbookmark.open"), + openTab: LocaleUtils.tr("layerbookmark.openTab"), + remove: LocaleUtils.tr("layerbookmark.remove"), + update: LocaleUtils.tr("layerbookmark.update") + }; + render() { + return ( + + {() => ({ + body: this.renderBody() + })} + + ); + } + renderBody = () => { + const bookmarks = (this.props.bookmarks || []).filter(b => !b.data.query.c && !b.data.query.s); + return ( + + ); + }; + onOpen = (bookmark, newtab) => { + openBookmark(bookmark, newtab, this.props.mapCenter, this.props.mapScales, this.props.mapZoom); + }; + onAdd = (description) => { + // creates a bookmark without saving the current map position + createBookmark(description, (success) => { + if (!success) { + /* eslint-disable-next-line */ + alert(LocaleUtils.tr("layerbookmark.addfailed")); + } + this.props.refreshUserBookmarks(); + }, true); + }; + onRemove = (bookmarkKey) => { + removeBookmark(bookmarkKey, (success) => { + if (!success) { + /* eslint-disable-next-line */ + alert(LocaleUtils.tr("layerbookmark.removefailed")); + } + this.props.refreshUserBookmarks(); + }); + }; + onUpdate = (bookmarkKey, description) => { + // updates a bookmark without saving the current map position + updateBookmark(bookmarkKey, description, (success) => { + if (!success) { + /* eslint-disable-next-line */ + alert(LocaleUtils.tr("layerbookmark.savefailed")); + } + this.props.refreshUserBookmarks(); + }, true); + }; +} +const selector = state => ({ + bookmarks: state.bookmark?.bookmarks, + mapCenter: state.map?.center, + mapScales: state.map?.scales, + mapZoom: state.map?.zoom +}); +export default connect(selector, { + refreshUserBookmarks: refreshUserBookmarks +})(LayerBookmark); diff --git a/plugins/Settings.jsx b/plugins/Settings.jsx index b421b90c1..99f71354a 100644 --- a/plugins/Settings.jsx +++ b/plugins/Settings.jsx @@ -16,9 +16,9 @@ import url from 'url'; import {setColorScheme, setUserInfoFields} from '../actions/localConfig'; import SideBar from '../components/SideBar'; +import GroupSelect from '../components/widgets/GroupSelect'; import ConfigUtils from '../utils/ConfigUtils'; import LocaleUtils from '../utils/LocaleUtils'; -import {getUserBookmarks} from '../utils/PermaLinkUtils'; import ThemeUtils from '../utils/ThemeUtils'; import './style/Settings.css'; @@ -32,6 +32,7 @@ import './style/Settings.css'; class Settings extends React.Component { static availableIn3D = true; static propTypes = { + bookmarks: PropTypes.array, colorScheme: PropTypes.string, /** List of available color schemes. Value is the css class name, title/titleMsgId the display name. */ colorSchemes: PropTypes.arrayOf(PropTypes.shape({ @@ -60,18 +61,6 @@ class Settings extends React.Component { side: 'right', showDefaultThemeSelector: true }; - state = { - bookmarks: {} - }; - onShow = () => { - const username = ConfigUtils.getConfigProp("username"); - if (this.props.showDefaultThemeSelector && username) { - getUserBookmarks(username, (bookmarks) => { - const bookmarkKeys = bookmarks.reduce((res, entry) => ({...res, [entry.key]: entry.description}), {}); - this.setState({bookmarks: bookmarkKeys}); - }); - } - }; render() { return ( @@ -135,42 +124,43 @@ class Settings extends React.Component { return null; } const themeNames = ThemeUtils.getThemeNames(this.props.themes); + const bookmarks = (this.props.bookmarks || []).filter(b => b.data.query.c && b.data.query.s); + const layerBookmarks = (this.props.bookmarks || []).filter(b => !b.data.query.c && !b.data.query.s); + const options = { + [LocaleUtils.tr("settings.themes")]: Object.entries(themeNames).map(([id, name]) => ["t=" + id, name]), + [LocaleUtils.tr("appmenu.items.Bookmark")]: bookmarks.map(bm => ["bk=" + bm.key, bm.description]), + [LocaleUtils.tr("appmenu.items.LayerBookmark")]: layerBookmarks.map(bm => ["bk=" + bm.key, bm.description]) + }; + const defaultOption = ["", LocaleUtils.tr("settings.default")]; return ( {LocaleUtils.tr("settings.defaulttheme")} - + ); }; - changeDefaultUrlParams = (ev) => { + changeDefaultUrlParams = (value) => { const params = { - default_url_params: ev.target.value + default_url_params: value }; const baseurl = location.href.split("?")[0].replace(/\/$/, ''); axios.get(baseurl + "/setuserinfo", {params}).then(response => { if (!response.data.success) { /* eslint-disable-next-line */ alert(LocaleUtils.tr("settings.defaultthemefailed", response.data.error)); - ev.target.value = this.props.defaultUrlParams; } else { this.props.setUserInfoFields(response.data.fields); } }).catch((e) => { /* eslint-disable-next-line */ alert(LocaleUtils.tr("settings.defaultthemefailed", String(e))); - ev.target.value = this.props.defaultUrlParams; }); }; changeLocale = (ev) => { @@ -196,6 +186,7 @@ class Settings extends React.Component { } export default connect((state) => ({ + bookmarks: state.bookmark.bookmarks, colorScheme: state.localConfig.colorScheme, defaultUrlParams: state.localConfig.user_infos?.default_url_params || "", themes: state.theme.themes diff --git a/reducers/bookmark.js b/reducers/bookmark.js new file mode 100644 index 000000000..437b2adf7 --- /dev/null +++ b/reducers/bookmark.js @@ -0,0 +1,25 @@ +/** + * Copyright 2025 Stadtwerke München GmbH + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. + */ + +import { + SET_BOOKMARKS +} from '../actions/bookmark'; + +const defaultState = { + bookmarks: [] +}; + +export default function bookmark(state = defaultState, action) { + switch (action.type) { + case SET_BOOKMARKS: { + return {...state, bookmarks: action.bookmarks}; + } + default: + return state; + } +} diff --git a/static/translations/bg-BG.json b/static/translations/bg-BG.json index 3a80bbd65..7ee938435 100644 --- a/static/translations/bg-BG.json +++ b/static/translations/bg-BG.json @@ -18,6 +18,7 @@ "ExportObjects3D": "", "Help": "Помощ", "HideObjects3D": "", + "LayerBookmark": "", "LayerTree": "Слоеве и легенда", "LayerTree3D": "Слоеве", "MapExport": "Експорт на карта", @@ -104,8 +105,10 @@ "zoomToExtent": "Увеличаване на обхвата" }, "bottombar": { + "bookmark_label": "", "mousepos_label": "Координати", "scale_label": "Стълбища", + "select": "", "terms_label": "Условия за ползване", "viewertitle_label": "Начална страница" }, @@ -298,6 +301,21 @@ "clickhelpRadius": "Задайте радиус и щракнете върху местоположението, за да идентифицирате функции около точката...", "radius": "Радиус" }, + "layerbookmark": { + "add": "", + "addfailed": "", + "description": "", + "lastUpdate": "", + "manage": "", + "nobookmarks": "", + "notloggedin": "", + "open": "", + "openTab": "", + "remove": "", + "removefailed": "", + "savefailed": "", + "update": "" + }, "layercatalog": { "windowtitle": "Каталог на слоевете" }, @@ -610,7 +628,6 @@ "title": "Информация за темата" }, "settings": { - "bookmarks": "Отметки", "colorscheme": "Цветова схема", "confirmlang": "Приложението ще бъде презаредено, за да се промени езикът. Продължете?", "default": "По подразбиране", diff --git a/static/translations/ca-ES.json b/static/translations/ca-ES.json index b4f08d808..b8f43f235 100644 --- a/static/translations/ca-ES.json +++ b/static/translations/ca-ES.json @@ -18,6 +18,7 @@ "ExportObjects3D": "Exportar objectes", "Help": "Ajuda", "HideObjects3D": "Ocultar objectes", + "LayerBookmark": "", "LayerTree": "Capes i Llegenda", "LayerTree3D": "Capes", "MapExport": "Exportar mapa", @@ -104,8 +105,10 @@ "zoomToExtent": "Fer zoom a l'extensió" }, "bottombar": { + "bookmark_label": "", "mousepos_label": "Coordenades", "scale_label": "Escala", + "select": "", "terms_label": "Condicions del servei", "viewertitle_label": "Pàgina d'inici" }, @@ -298,6 +301,21 @@ "clickhelpRadius": "Defineix un radi i fes click en una ubicació per identificar elements al voltant del punt...", "radius": "Radi" }, + "layerbookmark": { + "add": "", + "addfailed": "", + "description": "", + "lastUpdate": "", + "manage": "", + "nobookmarks": "", + "notloggedin": "", + "open": "", + "openTab": "", + "remove": "", + "removefailed": "", + "savefailed": "", + "update": "" + }, "layercatalog": { "windowtitle": "Titol" }, @@ -610,7 +628,6 @@ "title": "Informació del tema" }, "settings": { - "bookmarks": "Marcadors", "colorscheme": "Tema", "confirmlang": "L'aplicació es reiniciarà per canviar l'idioma. Vol continuar?", "default": "Per defecte", diff --git a/static/translations/cs-CZ.json b/static/translations/cs-CZ.json index 8fbe97ea3..339f9287d 100644 --- a/static/translations/cs-CZ.json +++ b/static/translations/cs-CZ.json @@ -18,6 +18,7 @@ "ExportObjects3D": "", "Help": "Nápověda", "HideObjects3D": "", + "LayerBookmark": "", "LayerTree": "Vrstvy & legenda", "LayerTree3D": "", "MapExport": "Export mapu", @@ -104,8 +105,10 @@ "zoomToExtent": "" }, "bottombar": { + "bookmark_label": "", "mousepos_label": "Souřadnice", "scale_label": "Měřítko", + "select": "", "terms_label": "Podmínky použití", "viewertitle_label": "" }, @@ -298,6 +301,21 @@ "clickhelpRadius": "Nastavte velikost okruhu a klikněte na místo, okolo kterého chcete načíst informace o prvcích.", "radius": "Velikost okruhu" }, + "layerbookmark": { + "add": "", + "addfailed": "", + "description": "", + "lastUpdate": "", + "manage": "", + "nobookmarks": "", + "notloggedin": "", + "open": "", + "openTab": "", + "remove": "", + "removefailed": "", + "savefailed": "", + "update": "" + }, "layercatalog": { "windowtitle": "Katalog vrstev" }, @@ -610,7 +628,6 @@ "title": "Informace o tématu" }, "settings": { - "bookmarks": "", "colorscheme": "Barevné schéma", "confirmlang": "Pro změnu jazyka je třeba aplikaci znovu načíst. Pokračovat?", "default": "", diff --git a/static/translations/de-CH.json b/static/translations/de-CH.json index a237ccaf5..2898f6ec5 100644 --- a/static/translations/de-CH.json +++ b/static/translations/de-CH.json @@ -18,6 +18,7 @@ "ExportObjects3D": "Objektexport", "Help": "Hilfe", "HideObjects3D": "Objekte ausblenden", + "LayerBookmark": "", "LayerTree": "Ebenen und Legende", "LayerTree3D": "Ebenen", "MapExport": "Karte exportieren", @@ -104,8 +105,10 @@ "zoomToExtent": "Auf Ausschnitt zoomen" }, "bottombar": { + "bookmark_label": "", "mousepos_label": "Koordinaten", "scale_label": "Massstab", + "select": "", "terms_label": "Nutzungsbedingungen", "viewertitle_label": "Homepage" }, @@ -298,6 +301,21 @@ "clickhelpRadius": "Einen Radius setzten und in die Karte klicken, um Objekte um den Punkt zu identifizieren...", "radius": "Radius" }, + "layerbookmark": { + "add": "", + "addfailed": "", + "description": "", + "lastUpdate": "", + "manage": "", + "nobookmarks": "", + "notloggedin": "", + "open": "", + "openTab": "", + "remove": "", + "removefailed": "", + "savefailed": "", + "update": "" + }, "layercatalog": { "windowtitle": "Ebenenkatalog" }, @@ -610,7 +628,6 @@ "title": "Informationen zum Thema" }, "settings": { - "bookmarks": "Lesezeichen", "colorscheme": "Farbschema", "confirmlang": "Die Anwendung wird neugeladen, um die Sprache zu wechseln. Fortsetzen?", "default": "Standard", diff --git a/static/translations/de-DE.json b/static/translations/de-DE.json index 3809e9410..24ea7bd6b 100644 --- a/static/translations/de-DE.json +++ b/static/translations/de-DE.json @@ -18,6 +18,7 @@ "ExportObjects3D": "Objektexport", "Help": "Hilfe", "HideObjects3D": "Objekte ausblenden", + "LayerBookmark": "Sichtbarkeiten", "LayerTree": "Ebenen und Legende", "LayerTree3D": "Ebenen", "MapExport": "Karte exportieren", @@ -104,8 +105,10 @@ "zoomToExtent": "Auf Ausschnitt zoomen" }, "bottombar": { + "bookmark_label": "Lesezeichen", "mousepos_label": "Koordinaten", "scale_label": "Maßstab", + "select": "Auswählen...", "terms_label": "Nutzungsbedingungen", "viewertitle_label": "Homepage" }, @@ -298,6 +301,21 @@ "clickhelpRadius": "Einen Radius setzten und in die Karte klicken, um Objekte um den Punkt zu identifizieren...", "radius": "Radius" }, + "layerbookmark": { + "add": "Neue Sichtbarkeit anlegen", + "addfailed": "Sichtbarkeit konnte nicht angelegt werden", + "description": "Beschreibung eingeben...", + "lastUpdate": "Zuletzt gespeichert", + "manage": "Sichtbarkeiten verwalten", + "nobookmarks": "Keine Sichtbarkeiten", + "notloggedin": "Sie müssen angemeldet sein, um Sichtbarkeiten zu verwenden", + "open": "In aktuellem Fenster öffnen", + "openTab": "In neuem Fenster öffnen", + "remove": "Sichtbarkeit entfernen", + "removefailed": "Sichtbarkeit konnte nicht entfernt werden", + "savefailed": "Sichtbarkeit konnte nicht aktualisiert werden", + "update": "Sichtbarkeit aktualisieren" + }, "layercatalog": { "windowtitle": "Ebenenkatalog" }, @@ -610,7 +628,6 @@ "title": "Informationen zum Thema" }, "settings": { - "bookmarks": "Lesezeichen", "colorscheme": "Farbschema", "confirmlang": "Die Anwendung wird neugeladen, um die Sprache zu wechseln. Fortsetzen?", "default": "Standard", diff --git a/static/translations/en-US.json b/static/translations/en-US.json index 7903ddfb9..44c5fb3a2 100644 --- a/static/translations/en-US.json +++ b/static/translations/en-US.json @@ -18,6 +18,7 @@ "ExportObjects3D": "Export Objects", "Help": "Help", "HideObjects3D": "Hide Objects", + "LayerBookmark": "Visibilities", "LayerTree": "Layers & Legend", "LayerTree3D": "Layers", "MapExport": "Export Map", @@ -104,8 +105,10 @@ "zoomToExtent": "Zoom to extent" }, "bottombar": { + "bookmark_label": "Bookmarks", "mousepos_label": "Coordinates", "scale_label": "Scale", + "select": "Select...", "terms_label": "Terms of use", "viewertitle_label": "Homepage" }, @@ -298,6 +301,21 @@ "clickhelpRadius": "Set a radius and click a location to identify features around the point...", "radius": "Radius" }, + "layerbookmark": { + "add": "Create new visibility", + "addfailed": "Failed to create visibility", + "description": "Enter description...", + "lastUpdate": "Last saved", + "manage": "Manage visibilities", + "nobookmarks": "No visibilities", + "notloggedin": "You need to be logged in to store visibilities", + "open": "Open visibility in current tab", + "openTab": "Open visibility in tab", + "remove": "Remove visibility", + "removefailed": "Failed to remove visibility", + "savefailed": "Failed to update visibility", + "update": "Update visibility" + }, "layercatalog": { "windowtitle": "Layer catalog" }, @@ -610,7 +628,6 @@ "title": "Theme Information" }, "settings": { - "bookmarks": "Bookmarks", "colorscheme": "Color scheme", "confirmlang": "The application will be reloaded to change the language. Proceed?", "default": "Default", diff --git a/static/translations/es-ES.json b/static/translations/es-ES.json index 77e4f2683..087233c0d 100644 --- a/static/translations/es-ES.json +++ b/static/translations/es-ES.json @@ -18,6 +18,7 @@ "ExportObjects3D": "Exportar objetos", "Help": "Ayuda", "HideObjects3D": "Ocultar objetos", + "LayerBookmark": "", "LayerTree": "Capas & Leyenda", "LayerTree3D": "Capas", "MapExport": "Exportar mapa", @@ -104,8 +105,10 @@ "zoomToExtent": "Zoom a la extensión" }, "bottombar": { + "bookmark_label": "", "mousepos_label": "Coordenadas", "scale_label": "Escala", + "select": "", "terms_label": "Términos de uso", "viewertitle_label": "Página de inicio" }, @@ -298,6 +301,21 @@ "clickhelpRadius": "Define un radio y haga click en una ubicación para identificar los elementos alrededor del punto...", "radius": "Radio" }, + "layerbookmark": { + "add": "", + "addfailed": "", + "description": "", + "lastUpdate": "", + "manage": "", + "nobookmarks": "", + "notloggedin": "", + "open": "", + "openTab": "", + "remove": "", + "removefailed": "", + "savefailed": "", + "update": "" + }, "layercatalog": { "windowtitle": "Catálogo de capas" }, @@ -610,7 +628,6 @@ "title": "Información del tema" }, "settings": { - "bookmarks": "Marcadores", "colorscheme": "Tema", "confirmlang": "La aplicación se reiniciará para cambiar de idioma. Quiere continuar?", "default": "Predeterminado", diff --git a/static/translations/fi-FI.json b/static/translations/fi-FI.json index aaade4f4b..fbf1ad7b9 100644 --- a/static/translations/fi-FI.json +++ b/static/translations/fi-FI.json @@ -18,6 +18,7 @@ "ExportObjects3D": "", "Help": "Apua", "HideObjects3D": "", + "LayerBookmark": "", "LayerTree": "Karttatasot ja selite", "LayerTree3D": "", "MapExport": "Vienti kartta", @@ -104,8 +105,10 @@ "zoomToExtent": "" }, "bottombar": { + "bookmark_label": "", "mousepos_label": "Koordinaatit", "scale_label": "Mittakaava", + "select": "", "terms_label": "Käyttöehdot", "viewertitle_label": "" }, @@ -298,6 +301,21 @@ "clickhelpRadius": "", "radius": "" }, + "layerbookmark": { + "add": "", + "addfailed": "", + "description": "", + "lastUpdate": "", + "manage": "", + "nobookmarks": "", + "notloggedin": "", + "open": "", + "openTab": "", + "remove": "", + "removefailed": "", + "savefailed": "", + "update": "" + }, "layercatalog": { "windowtitle": "" }, @@ -610,7 +628,6 @@ "title": "Teema informaatio" }, "settings": { - "bookmarks": "", "colorscheme": "", "confirmlang": "", "default": "", diff --git a/static/translations/fr-FR.json b/static/translations/fr-FR.json index 222049ebf..6cc25fd83 100644 --- a/static/translations/fr-FR.json +++ b/static/translations/fr-FR.json @@ -18,6 +18,7 @@ "ExportObjects3D": "Exporter objets", "Help": "Aide", "HideObjects3D": "Cacher objets", + "LayerBookmark": "", "LayerTree": "Couches", "LayerTree3D": "Couches", "MapExport": "Exporter la carte", @@ -104,8 +105,10 @@ "zoomToExtent": "Zommer sur l'étendue" }, "bottombar": { + "bookmark_label": "", "mousepos_label": "Coordonnées", "scale_label": "Echelle", + "select": "", "terms_label": "Conditions d'utilisation", "viewertitle_label": "Page d'accueil" }, @@ -298,6 +301,21 @@ "clickhelpRadius": "Configurez un rayon et cliquez sur un emplacement pour identifier les objets autour de ce point", "radius": "Rayon" }, + "layerbookmark": { + "add": "", + "addfailed": "", + "description": "", + "lastUpdate": "", + "manage": "", + "nobookmarks": "", + "notloggedin": "", + "open": "", + "openTab": "", + "remove": "", + "removefailed": "", + "savefailed": "", + "update": "" + }, "layercatalog": { "windowtitle": "Catalogue de couches" }, @@ -610,7 +628,6 @@ "title": "Information sur le thème" }, "settings": { - "bookmarks": "Marque-pages", "colorscheme": "Schéma de couleurs", "confirmlang": "L'application sera rechargée pour changer la langue. Procéder?", "default": "Par défaut", diff --git a/static/translations/hu-HU.json b/static/translations/hu-HU.json index c43e7341b..57c8c1d32 100644 --- a/static/translations/hu-HU.json +++ b/static/translations/hu-HU.json @@ -18,6 +18,7 @@ "ExportObjects3D": "", "Help": "Segítség", "HideObjects3D": "", + "LayerBookmark": "", "LayerTree": "Fóliák és jelmagyarázat", "LayerTree3D": "", "MapExport": "Export térkép", @@ -104,8 +105,10 @@ "zoomToExtent": "" }, "bottombar": { + "bookmark_label": "", "mousepos_label": "Koordináták", "scale_label": "Méretarány", + "select": "", "terms_label": "Használati feltételek", "viewertitle_label": "" }, @@ -298,6 +301,21 @@ "clickhelpRadius": "", "radius": "" }, + "layerbookmark": { + "add": "", + "addfailed": "", + "description": "", + "lastUpdate": "", + "manage": "", + "nobookmarks": "", + "notloggedin": "", + "open": "", + "openTab": "", + "remove": "", + "removefailed": "", + "savefailed": "", + "update": "" + }, "layercatalog": { "windowtitle": "" }, @@ -610,7 +628,6 @@ "title": "" }, "settings": { - "bookmarks": "", "colorscheme": "", "confirmlang": "", "default": "", diff --git a/static/translations/it-IT.json b/static/translations/it-IT.json index 6403e4949..87c9c1bab 100644 --- a/static/translations/it-IT.json +++ b/static/translations/it-IT.json @@ -18,6 +18,7 @@ "ExportObjects3D": "Esporta oggetti", "Help": "Aiuto", "HideObjects3D": "Nascondi oggetti", + "LayerBookmark": "", "LayerTree": "Livelli", "LayerTree3D": "Livelli", "MapExport": "Esporta mappa", @@ -104,8 +105,10 @@ "zoomToExtent": "Zoomare sull'estensione" }, "bottombar": { + "bookmark_label": "", "mousepos_label": "Coordinate", "scale_label": "Scala", + "select": "", "terms_label": "Condizioni di uso", "viewertitle_label": "Homepage" }, @@ -298,6 +301,21 @@ "clickhelpRadius": "Imposta un raggio e clicca nella mappa per identificare gli oggetti attorno al punto...", "radius": "Raggio" }, + "layerbookmark": { + "add": "", + "addfailed": "", + "description": "", + "lastUpdate": "", + "manage": "", + "nobookmarks": "", + "notloggedin": "", + "open": "", + "openTab": "", + "remove": "", + "removefailed": "", + "savefailed": "", + "update": "" + }, "layercatalog": { "windowtitle": "Catalogo livelli" }, @@ -610,7 +628,6 @@ "title": "Informazioni sul tema" }, "settings": { - "bookmarks": "Segnalibri", "colorscheme": "Stile", "confirmlang": "L'applicazione sarà ricaricata per cambiare la lingua. Procedere?", "default": "Predefinito", diff --git a/static/translations/ja-JP.json b/static/translations/ja-JP.json index be8459766..0c38fa3b1 100644 --- a/static/translations/ja-JP.json +++ b/static/translations/ja-JP.json @@ -18,6 +18,7 @@ "ExportObjects3D": "オブジェクトをエクスポート", "Help": "ヘルプ", "HideObjects3D": "オブジェクトを非表示", + "LayerBookmark": "", "LayerTree": "レイヤ & 凡例", "LayerTree3D": "レイヤ", "MapExport": "地図をエクスポート", @@ -104,8 +105,10 @@ "zoomToExtent": "領域にズーム" }, "bottombar": { + "bookmark_label": "", "mousepos_label": "座標", "scale_label": "縮尺", + "select": "", "terms_label": "使用許諾条件", "viewertitle_label": "QWC2 デモ" }, @@ -298,6 +301,21 @@ "clickhelpRadius": "中心をクリックして半径を指定し、円内を確認...", "radius": "半径" }, + "layerbookmark": { + "add": "", + "addfailed": "", + "description": "", + "lastUpdate": "", + "manage": "", + "nobookmarks": "", + "notloggedin": "", + "open": "", + "openTab": "", + "remove": "", + "removefailed": "", + "savefailed": "", + "update": "" + }, "layercatalog": { "windowtitle": "レイヤ・カタログ" }, @@ -610,7 +628,6 @@ "title": "テーマの情報" }, "settings": { - "bookmarks": "ブックマーク", "colorscheme": "カラー・スキーム", "confirmlang": "言語を変更するためにアプリケーションを再ロードします。よろしいか?", "default": "デフォルト", diff --git a/static/translations/nl-NL.json b/static/translations/nl-NL.json index 770d7004c..458b90549 100644 --- a/static/translations/nl-NL.json +++ b/static/translations/nl-NL.json @@ -18,6 +18,7 @@ "ExportObjects3D": "", "Help": "Help", "HideObjects3D": "", + "LayerBookmark": "", "LayerTree": "Lagen & legenda", "LayerTree3D": "", "MapExport": "Export kaart", @@ -104,8 +105,10 @@ "zoomToExtent": "Zoom tot extent" }, "bottombar": { + "bookmark_label": "", "mousepos_label": "Coördinaten", "scale_label": "Schaal", + "select": "", "terms_label": "Voorwaarden", "viewertitle_label": "" }, @@ -298,6 +301,21 @@ "clickhelpRadius": "Stel een straal in en klik op een locatie om objecten rond het punt te identificeren...", "radius": "Straal" }, + "layerbookmark": { + "add": "", + "addfailed": "", + "description": "", + "lastUpdate": "", + "manage": "", + "nobookmarks": "", + "notloggedin": "", + "open": "", + "openTab": "", + "remove": "", + "removefailed": "", + "savefailed": "", + "update": "" + }, "layercatalog": { "windowtitle": "Lagencatalogus" }, @@ -610,7 +628,6 @@ "title": "Kaart informatie" }, "settings": { - "bookmarks": "Bookmarks", "colorscheme": "Kleurenschema", "confirmlang": "De applicatie wordt opnieuw geladen om de taal te wijzigen. Doorgaan?", "default": "Default", diff --git a/static/translations/no-NO.json b/static/translations/no-NO.json index c92d6cfb4..7d2abc4ab 100644 --- a/static/translations/no-NO.json +++ b/static/translations/no-NO.json @@ -18,6 +18,7 @@ "ExportObjects3D": "", "Help": "Hjelp", "HideObjects3D": "", + "LayerBookmark": "", "LayerTree": "Lag & tegnforklaring", "LayerTree3D": "", "MapExport": "Eksport kart", @@ -104,8 +105,10 @@ "zoomToExtent": "" }, "bottombar": { + "bookmark_label": "", "mousepos_label": "Koordinater", "scale_label": "Skala", + "select": "", "terms_label": "Vilkår", "viewertitle_label": "" }, @@ -298,6 +301,21 @@ "clickhelpRadius": "", "radius": "" }, + "layerbookmark": { + "add": "", + "addfailed": "", + "description": "", + "lastUpdate": "", + "manage": "", + "nobookmarks": "", + "notloggedin": "", + "open": "", + "openTab": "", + "remove": "", + "removefailed": "", + "savefailed": "", + "update": "" + }, "layercatalog": { "windowtitle": "" }, @@ -610,7 +628,6 @@ "title": "" }, "settings": { - "bookmarks": "", "colorscheme": "", "confirmlang": "", "default": "", diff --git a/static/translations/pl-PL.json b/static/translations/pl-PL.json index 66c69d77e..cb377b110 100644 --- a/static/translations/pl-PL.json +++ b/static/translations/pl-PL.json @@ -18,6 +18,7 @@ "ExportObjects3D": "", "Help": "Pomoc", "HideObjects3D": "", + "LayerBookmark": "", "LayerTree": "Warstwy i ich legendy", "LayerTree3D": "", "MapExport": "Eksportuj mapę", @@ -104,8 +105,10 @@ "zoomToExtent": "" }, "bottombar": { + "bookmark_label": "", "mousepos_label": "Współrzędne", "scale_label": "Skala", + "select": "", "terms_label": "Warunki korzystania", "viewertitle_label": "" }, @@ -298,6 +301,21 @@ "clickhelpRadius": "", "radius": "" }, + "layerbookmark": { + "add": "", + "addfailed": "", + "description": "", + "lastUpdate": "", + "manage": "", + "nobookmarks": "", + "notloggedin": "", + "open": "", + "openTab": "", + "remove": "", + "removefailed": "", + "savefailed": "", + "update": "" + }, "layercatalog": { "windowtitle": "" }, @@ -610,7 +628,6 @@ "title": "" }, "settings": { - "bookmarks": "", "colorscheme": "", "confirmlang": "", "default": "", diff --git a/static/translations/pt-BR.json b/static/translations/pt-BR.json index 53f60efa5..78392e99b 100644 --- a/static/translations/pt-BR.json +++ b/static/translations/pt-BR.json @@ -18,6 +18,7 @@ "ExportObjects3D": "", "Help": "Ajuda", "HideObjects3D": "", + "LayerBookmark": "", "LayerTree": "Camadas & Legendas", "LayerTree3D": "", "MapExport": "Exportar mapa", @@ -104,8 +105,10 @@ "zoomToExtent": "" }, "bottombar": { + "bookmark_label": "", "mousepos_label": "Coordenadas", "scale_label": "Escala", + "select": "", "terms_label": "Termos de uso", "viewertitle_label": "Home page" }, @@ -298,6 +301,21 @@ "clickhelpRadius": "Desenhe um raio em torno das feiçoes para identificar", "radius": "Raio" }, + "layerbookmark": { + "add": "", + "addfailed": "", + "description": "", + "lastUpdate": "", + "manage": "", + "nobookmarks": "", + "notloggedin": "", + "open": "", + "openTab": "", + "remove": "", + "removefailed": "", + "savefailed": "", + "update": "" + }, "layercatalog": { "windowtitle": "Titulo da janela" }, @@ -610,7 +628,6 @@ "title": "Título" }, "settings": { - "bookmarks": "Favoritos", "colorscheme": "Esquema de cores", "confirmlang": "Confirmar o idioma", "default": "Padrão", diff --git a/static/translations/pt-PT.json b/static/translations/pt-PT.json index 4496fe7a2..b3c6c0917 100644 --- a/static/translations/pt-PT.json +++ b/static/translations/pt-PT.json @@ -18,6 +18,7 @@ "ExportObjects3D": "", "Help": "Ajuda", "HideObjects3D": "", + "LayerBookmark": "", "LayerTree": "Árvore de Camadas", "LayerTree3D": "", "MapExport": "Exportar Mapa", @@ -104,8 +105,10 @@ "zoomToExtent": "" }, "bottombar": { + "bookmark_label": "", "mousepos_label": "Coordenadas", "scale_label": "Escala", + "select": "", "terms_label": "Termos de Uso", "viewertitle_label": "Visualizador" }, @@ -298,6 +301,21 @@ "clickhelpRadius": "Clique para desenhar um raio...", "radius": "" }, + "layerbookmark": { + "add": "", + "addfailed": "", + "description": "", + "lastUpdate": "", + "manage": "", + "nobookmarks": "", + "notloggedin": "", + "open": "", + "openTab": "", + "remove": "", + "removefailed": "", + "savefailed": "", + "update": "" + }, "layercatalog": { "windowtitle": "Catálogo de Camadas" }, @@ -610,7 +628,6 @@ "title": "Informação Temática" }, "settings": { - "bookmarks": "Marcadores", "colorscheme": "Esquema de Cores", "confirmlang": "Confirmar Idioma", "default": "Padrão", diff --git a/static/translations/ro-RO.json b/static/translations/ro-RO.json index 153227e0a..be96b5b22 100644 --- a/static/translations/ro-RO.json +++ b/static/translations/ro-RO.json @@ -18,6 +18,7 @@ "ExportObjects3D": "", "Help": "Ajutor", "HideObjects3D": "", + "LayerBookmark": "", "LayerTree": "Straturi & Legendă", "LayerTree3D": "", "MapExport": "", @@ -104,8 +105,10 @@ "zoomToExtent": "" }, "bottombar": { + "bookmark_label": "", "mousepos_label": "Coordonate", "scale_label": "Scara", + "select": "", "terms_label": "Termeni de utilizare", "viewertitle_label": "Despre" }, @@ -298,6 +301,21 @@ "clickhelpRadius": "Setați raza și apoi faceți click pe hartă pentru a identifica elementele din jurul punctului...", "radius": "Raza" }, + "layerbookmark": { + "add": "", + "addfailed": "", + "description": "", + "lastUpdate": "", + "manage": "", + "nobookmarks": "", + "notloggedin": "", + "open": "", + "openTab": "", + "remove": "", + "removefailed": "", + "savefailed": "", + "update": "" + }, "layercatalog": { "windowtitle": "Catalog straturi" }, @@ -610,7 +628,6 @@ "title": "Infomrații Temă" }, "settings": { - "bookmarks": "", "colorscheme": "Schema de culori", "confirmlang": "Aplicația va fi reîncărcată pentru a aplica schimbarea limbii. Continuați?", "default": "", diff --git a/static/translations/ru-RU.json b/static/translations/ru-RU.json index 73cdbe645..711566fce 100644 --- a/static/translations/ru-RU.json +++ b/static/translations/ru-RU.json @@ -18,6 +18,7 @@ "ExportObjects3D": "", "Help": "Помощь", "HideObjects3D": "", + "LayerBookmark": "", "LayerTree": "Слои и легенда", "LayerTree3D": "", "MapExport": "экспортировать карту", @@ -104,8 +105,10 @@ "zoomToExtent": "" }, "bottombar": { + "bookmark_label": "", "mousepos_label": "Координаты", "scale_label": "Масштаб", + "select": "", "terms_label": "Условия использования", "viewertitle_label": "Домашняя страница" }, @@ -298,6 +301,21 @@ "clickhelpRadius": "", "radius": "" }, + "layerbookmark": { + "add": "", + "addfailed": "", + "description": "", + "lastUpdate": "", + "manage": "", + "nobookmarks": "", + "notloggedin": "", + "open": "", + "openTab": "", + "remove": "", + "removefailed": "", + "savefailed": "", + "update": "" + }, "layercatalog": { "windowtitle": "" }, @@ -610,7 +628,6 @@ "title": "" }, "settings": { - "bookmarks": "", "colorscheme": "", "confirmlang": "", "default": "", diff --git a/static/translations/sv-SE.json b/static/translations/sv-SE.json index 164a49976..6c37896f7 100644 --- a/static/translations/sv-SE.json +++ b/static/translations/sv-SE.json @@ -18,6 +18,7 @@ "ExportObjects3D": "", "Help": "Hjälp", "HideObjects3D": "", + "LayerBookmark": "", "LayerTree": "Lager & teckenförklaring", "LayerTree3D": "", "MapExport": "Exportera karta", @@ -104,8 +105,10 @@ "zoomToExtent": "" }, "bottombar": { + "bookmark_label": "", "mousepos_label": "Koordinater", "scale_label": "Skala", + "select": "", "terms_label": "Villkor", "viewertitle_label": "" }, @@ -298,6 +301,21 @@ "clickhelpRadius": "", "radius": "" }, + "layerbookmark": { + "add": "", + "addfailed": "", + "description": "", + "lastUpdate": "", + "manage": "", + "nobookmarks": "", + "notloggedin": "", + "open": "", + "openTab": "", + "remove": "", + "removefailed": "", + "savefailed": "", + "update": "" + }, "layercatalog": { "windowtitle": "" }, @@ -610,7 +628,6 @@ "title": "" }, "settings": { - "bookmarks": "", "colorscheme": "", "confirmlang": "", "default": "", diff --git a/static/translations/tr-TR.json b/static/translations/tr-TR.json index 564baa99f..bde863104 100644 --- a/static/translations/tr-TR.json +++ b/static/translations/tr-TR.json @@ -18,6 +18,7 @@ "ExportObjects3D": "", "Help": "Yardım", "HideObjects3D": "", + "LayerBookmark": "", "LayerTree": "Katmanlar & Gösterim", "LayerTree3D": "Katmanlar", "MapExport": "Dışarıya ver", @@ -104,8 +105,10 @@ "zoomToExtent": "Kapsama yaklaş" }, "bottombar": { + "bookmark_label": "", "mousepos_label": "Koordinatlar", "scale_label": "Ölçek", + "select": "", "terms_label": "Kullanım Şartları", "viewertitle_label": "QWC" }, @@ -298,6 +301,21 @@ "clickhelpRadius": "Yarıçap belirleyip çevresinden bilgi almak için bir noktayı tıklayınız", "radius": "Yarıçap" }, + "layerbookmark": { + "add": "", + "addfailed": "", + "description": "", + "lastUpdate": "", + "manage": "", + "nobookmarks": "", + "notloggedin": "", + "open": "", + "openTab": "", + "remove": "", + "removefailed": "", + "savefailed": "", + "update": "" + }, "layercatalog": { "windowtitle": "Katman kataloğu" }, @@ -610,7 +628,6 @@ "title": "Tema bilgisi" }, "settings": { - "bookmarks": "Yer işaretleri", "colorscheme": "Renk şeması", "confirmlang": "Dil değişikliği için uygulama yeniden yüklenecek. Devam edilsin mi?", "default": "Varsayılan", diff --git a/static/translations/tsconfig.json b/static/translations/tsconfig.json index d6235c034..c9b4a800e 100644 --- a/static/translations/tsconfig.json +++ b/static/translations/tsconfig.json @@ -74,6 +74,7 @@ "appmenu.items.ExportObjects3D", "appmenu.items.Help", "appmenu.items.HideObjects3D", + "appmenu.items.LayerBookmark", "appmenu.items.LayerTree", "appmenu.items.LayerTree3D", "appmenu.items.MapExport", @@ -123,8 +124,10 @@ "bookmark.savefailed", "bookmark.update", "bookmark.zoomToExtent", + "bottombar.bookmark_label", "bottombar.mousepos_label", "bottombar.scale_label", + "bottombar.select", "bottombar.terms_label", "bottombar.viewertitle_label", "compare3d.clipplane", @@ -278,6 +281,19 @@ "infotool.clickhelpPolygon", "infotool.clickhelpRadius", "infotool.radius", + "layerbookmark.add", + "layerbookmark.addfailed", + "layerbookmark.description", + "layerbookmark.lastUpdate", + "layerbookmark.manage", + "layerbookmark.nobookmarks", + "layerbookmark.notloggedin", + "layerbookmark.open", + "layerbookmark.openTab", + "layerbookmark.remove", + "layerbookmark.removefailed", + "layerbookmark.savefailed", + "layerbookmark.update", "layercatalog.windowtitle", "layerinfo.abstract", "layerinfo.attribution", @@ -527,7 +543,6 @@ "serviceinfo.keywords", "serviceinfo.onlineResource", "serviceinfo.title", - "settings.bookmarks", "settings.colorscheme", "settings.confirmlang", "settings.default", diff --git a/static/translations/uk-UA.json b/static/translations/uk-UA.json index cd1a846fd..1b8f423a0 100644 --- a/static/translations/uk-UA.json +++ b/static/translations/uk-UA.json @@ -18,6 +18,7 @@ "ExportObjects3D": "", "Help": "Допомога", "HideObjects3D": "", + "LayerBookmark": "", "LayerTree": "Шари і легенда", "LayerTree3D": "", "MapExport": "Експортувати карту", @@ -104,8 +105,10 @@ "zoomToExtent": "" }, "bottombar": { + "bookmark_label": "", "mousepos_label": "Координати", "scale_label": "Масштаб", + "select": "", "terms_label": "Правила користування", "viewertitle_label": "Домашня сторінка" }, @@ -298,6 +301,21 @@ "clickhelpRadius": "", "radius": "" }, + "layerbookmark": { + "add": "", + "addfailed": "", + "description": "", + "lastUpdate": "", + "manage": "", + "nobookmarks": "", + "notloggedin": "", + "open": "", + "openTab": "", + "remove": "", + "removefailed": "", + "savefailed": "", + "update": "" + }, "layercatalog": { "windowtitle": "" }, @@ -610,7 +628,6 @@ "title": "" }, "settings": { - "bookmarks": "", "colorscheme": "", "confirmlang": "", "default": "", diff --git a/utils/PermaLinkUtils.js b/utils/PermaLinkUtils.js index b01d1c6e0..f88259b0d 100644 --- a/utils/PermaLinkUtils.js +++ b/utils/PermaLinkUtils.js @@ -13,6 +13,7 @@ import {LayerRole} from '../actions/layers'; import StandardApp from '../components/StandardApp'; import ConfigUtils from '../utils/ConfigUtils'; import LayerUtils from '../utils/LayerUtils'; +import MapUtils from "./MapUtils"; let UrlQuery = {}; let historyUpdateTimeout = null; @@ -169,37 +170,43 @@ export function resolveBookmark(bookmarkKey, callback) { }); } -export function getUserBookmarks(user, callback) { - if (user) { - axios.get(ConfigUtils.getConfigProp("permalinkServiceUrl").replace(/\/$/, '') + "/bookmarks/") - .then(response => { - callback(response.data || []); - }) - .catch(() => { - callback([]); - }); - } -} - -export async function createBookmark(description, callback) { - if (!ConfigUtils.getConfigProp("permalinkServiceUrl")) { - callback(false); - return; - } - const state = StandardApp.store.getState(); - // Only store redlining layers - const exploded = LayerUtils.explodeLayers(state.layers.flat.filter(layer => layer.role !== LayerRole.BACKGROUND)); +function buildBookmarkState(state, omitPosition = false) { const bookmarkState = {}; if (ConfigUtils.getConfigProp("storeAllLayersInPermalink")) { bookmarkState.layers = state.layers.flat.filter(layer => layer.role !== LayerRole.BACKGROUND); } else { + const exploded = LayerUtils.explodeLayers(state.layers.flat.filter(layer => layer.role !== LayerRole.BACKGROUND)); const redliningLayers = exploded.map((entry, idx) => ({...entry, pos: idx})) .filter(entry => entry.layer.role === LayerRole.USERLAYER && entry.layer.type === 'vector') .map(entry => ({...entry.layer, pos: entry.pos})); bookmarkState.layers = redliningLayers; } bookmarkState.permalinkParams = state.localConfig.permalinkParams; - bookmarkState.url = UrlParams.getFullUrl(); + + const urlObj = new URL(UrlParams.getFullUrl()); + + // ignore search term and highlight parameters + urlObj.searchParams.delete("st"); + urlObj.searchParams.delete("hp"); + urlObj.searchParams.delete("hf"); + + // omit center/scale/extent parameters if requested + if (omitPosition) { + urlObj.searchParams.delete("c"); + urlObj.searchParams.delete("s"); + urlObj.searchParams.delete("e"); + } + bookmarkState.url = urlObj.toString(); + return bookmarkState; +} + +export async function createBookmark(description, callback, omitPosition = false) { + if (!ConfigUtils.getConfigProp("permalinkServiceUrl")) { + callback(false); + return; + } + const state = StandardApp.store.getState(); + const bookmarkState = buildBookmarkState(state, omitPosition); await executePermalinkDataStoreHooks(bookmarkState); axios.post(ConfigUtils.getConfigProp("permalinkServiceUrl").replace(/\/$/, '') + "/bookmarks/" + "?description=" + description, bookmarkState) @@ -207,25 +214,13 @@ export async function createBookmark(description, callback) { .catch(() => callback(false)); } -export async function updateBookmark(bkey, description, callback) { +export async function updateBookmark(bkey, description, callback, omitPosition = false) { if (!ConfigUtils.getConfigProp("permalinkServiceUrl")) { callback(false); return; } const state = StandardApp.store.getState(); - // Only store redlining layers - const exploded = LayerUtils.explodeLayers(state.layers.flat.filter(layer => layer.role !== LayerRole.BACKGROUND)); - const bookmarkState = {}; - if (ConfigUtils.getConfigProp("storeAllLayersInPermalink")) { - bookmarkState.layers = state.layers.flat.filter(layer => layer.role !== LayerRole.BACKGROUND); - } else { - const redliningLayers = exploded.map((entry, idx) => ({...entry, pos: idx})) - .filter(entry => entry.layer.role === LayerRole.USERLAYER && entry.layer.type === 'vector') - .map(entry => ({...entry.layer, pos: entry.pos})); - bookmarkState.layers = redliningLayers; - } - bookmarkState.permalinkParams = state.localConfig.permalinkParams; - bookmarkState.url = UrlParams.getFullUrl(); + const bookmarkState = buildBookmarkState(state, omitPosition); await executePermalinkDataStoreHooks(bookmarkState); axios.put(ConfigUtils.getConfigProp("permalinkServiceUrl").replace(/\/$/, '') + "/bookmarks/" + bkey + "?description=" + description, bookmarkState) @@ -241,3 +236,19 @@ export function removeBookmark(bkey, callback) { }).catch(() => callback(false)); } } + +export function openBookmark(bookmarkkey, newtab, center = undefined, scales = [], zoom = undefined) { + const bookmarkUrl = new URL(location.href); + bookmarkUrl.search = 'bk=' + bookmarkkey; + bookmarkUrl.hash = ''; + if (center && scales && zoom) { + const centerParam = center ? '&c=' + center.map(x => Math.trunc(x)).join(",") : ''; + const scaleParam = scales ? '&s=' + MapUtils.computeForZoom(scales, zoom) : ''; + bookmarkUrl.search += centerParam + scaleParam; + } + if (newtab) { + window.open(bookmarkUrl.href, '_blank'); + } else { + location.href = bookmarkUrl.href; + } +} From addba04f363ed79be265d2ce031dbfdd320833aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nina=20R=C3=B6ckelein?= Date: Tue, 11 Nov 2025 15:49:46 +0100 Subject: [PATCH 2/2] sort imports --- components/BookmarkPanel.jsx | 1 - components/StandardApp.jsx | 2 +- plugins/BottomBar.jsx | 4 ++-- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/components/BookmarkPanel.jsx b/components/BookmarkPanel.jsx index a7982dd4a..7801413b6 100644 --- a/components/BookmarkPanel.jsx +++ b/components/BookmarkPanel.jsx @@ -10,7 +10,6 @@ import React from 'react'; import classnames from 'classnames'; import isEmpty from 'lodash.isempty'; -import isEqual from 'lodash.isequal'; import PropTypes from 'prop-types'; import ConfigUtils from '../utils/ConfigUtils'; diff --git a/components/StandardApp.jsx b/components/StandardApp.jsx index 21084e7ae..da8277d95 100644 --- a/components/StandardApp.jsx +++ b/components/StandardApp.jsx @@ -15,6 +15,7 @@ import {register as olProj4Register} from 'ol/proj/proj4'; import Proj4js from 'proj4'; import PropTypes from 'prop-types'; +import {refreshUserBookmarks} from '../actions/bookmark'; import {localConfigLoaded, setStartupParameters, setColorScheme} from '../actions/localConfig'; import {changeLocale} from '../actions/locale'; import {setCurrentTask} from '../actions/task'; @@ -34,7 +35,6 @@ import PluginsContainer from './PluginsContainer'; import './style/App.css'; import './style/DefaultColorScheme.css'; -import {refreshUserBookmarks} from '../actions/bookmark'; const CSRF_TOKEN = MiscUtils.getCsrfToken(); diff --git a/plugins/BottomBar.jsx b/plugins/BottomBar.jsx index 460eaf986..42edf74af 100644 --- a/plugins/BottomBar.jsx +++ b/plugins/BottomBar.jsx @@ -15,15 +15,15 @@ import PropTypes from 'prop-types'; import {changeZoomLevel, setDisplayCrs} from '../actions/map'; import {openExternalUrl, setBottombarHeight} from '../actions/windows'; import CoordinateDisplayer from '../components/CoordinateDisplayer'; +import GroupSelect from '../components/widgets/GroupSelect'; import InputContainer from '../components/widgets/InputContainer'; import NumberInput from '../components/widgets/NumberInput'; import CoordinatesUtils from '../utils/CoordinatesUtils'; import LocaleUtils from '../utils/LocaleUtils'; import MapUtils from '../utils/MapUtils'; +import {openBookmark} from '../utils/PermaLinkUtils'; import './style/BottomBar.css'; -import GroupSelect from '../components/widgets/GroupSelect'; -import {openBookmark} from '../utils/PermaLinkUtils'; /**