diff --git a/CHANGELOG.md b/CHANGELOG.md index 1fcfc1e6627..83b832ef80d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -47,6 +47,7 @@ For upgrade instructions, please check the [migration guide](MIGRATIONS.md). - Fixed a bug where for uint24 color layers, scrambled data was shown for missing magnifications. [#4188](https://github.com/scalableminds/webknossos/pull/4188) - Fixed a bug where collapsing/expanding all tree groups would trigger when toggling a single tree [#4178](https://github.com/scalableminds/webknossos/pull/4178) - Fixed performance for time logging. [#4196](https://github.com/scalableminds/webknossos/pull/4196) +- Personal tracing layouts are saved per user now. [#4217](https://github.com/scalableminds/webknossos/pull/4217) - Fixed an error message when quickly resizing the browser window. [#4205](https://github.com/scalableminds/webknossos/pull/4205) ### Removed diff --git a/frontend/javascripts/dashboard/dashboard_view.js b/frontend/javascripts/dashboard/dashboard_view.js index 38e0af2ade8..03fe9dc30f4 100644 --- a/frontend/javascripts/dashboard/dashboard_view.js +++ b/frontend/javascripts/dashboard/dashboard_view.js @@ -14,6 +14,7 @@ import DatasetView from "dashboard/dataset_view"; import ExplorativeAnnotationsView from "dashboard/explorative_annotations_view"; import NmlUploadZoneContainer from "oxalis/view/nml_upload_zone_container"; import Request from "libs/request"; +import UserLocalStorage from "libs/user_local_storage"; const { TabPane } = Tabs; @@ -45,7 +46,7 @@ class DashboardView extends React.PureComponent { const validTabKeys = this.getValidTabKeys(); const { initialTabKey } = this.props; - const lastUsedTabKey = localStorage.getItem("lastUsedDashboardTab"); + const lastUsedTabKey = UserLocalStorage.getItem("lastUsedDashboardTab"); const defaultTabKey = this.props.isAdminView ? "tasks" : "datasets"; // Flow doesn't allow validTabKeys[key] where key may be null, so check that first @@ -132,7 +133,7 @@ class DashboardView extends React.PureComponent { const tabKeyToURLMap = _.invert(urlTokenToTabKeyMap); const url = tabKeyToURLMap[activeTabKey]; if (url) { - localStorage.setItem("lastUsedDashboardTab", activeTabKey); + UserLocalStorage.setItem("lastUsedDashboardTab", activeTabKey); if (!this.props.isAdminView) { this.props.history.push(`/dashboard/${url}`); } diff --git a/frontend/javascripts/dashboard/dataset_view.js b/frontend/javascripts/dashboard/dataset_view.js index 8c2aa97a04c..031f8e7b608 100644 --- a/frontend/javascripts/dashboard/dataset_view.js +++ b/frontend/javascripts/dashboard/dataset_view.js @@ -14,6 +14,7 @@ import Persistence from "libs/persistence"; import SampleDatasetsModal from "dashboard/dataset/sample_datasets_modal"; import * as Utils from "libs/utils"; import renderIndependently from "libs/render_independently"; +import UserLocalStorage from "libs/user_local_storage"; const { Search, Group: InputGroup } = Input; @@ -46,13 +47,13 @@ const persistence: Persistence = new Persistence( export const wkDatasetsCacheKey = "wk.datasets"; export const datasetCache = { set(datasets: APIMaybeUnimportedDataset[]): void { - localStorage.setItem(wkDatasetsCacheKey, JSON.stringify(datasets)); + UserLocalStorage.setItem(wkDatasetsCacheKey, JSON.stringify(datasets)); }, get(): APIMaybeUnimportedDataset[] { - return Utils.parseAsMaybe(localStorage.getItem(wkDatasetsCacheKey)).getOrElse([]); + return Utils.parseAsMaybe(UserLocalStorage.getItem(wkDatasetsCacheKey)).getOrElse([]); }, clear(): void { - localStorage.removeItem(wkDatasetsCacheKey); + UserLocalStorage.removeItem(wkDatasetsCacheKey); }, }; diff --git a/frontend/javascripts/dashboard/explorative_annotations_view.js b/frontend/javascripts/dashboard/explorative_annotations_view.js index 4a4dc9a827a..7c49539aa3a 100644 --- a/frontend/javascripts/dashboard/explorative_annotations_view.js +++ b/frontend/javascripts/dashboard/explorative_annotations_view.js @@ -30,6 +30,7 @@ import Toast from "libs/toast"; import * as Utils from "libs/utils"; import messages from "messages"; import { trackAction } from "oxalis/model/helpers/analytics"; +import UserLocalStorage from "libs/user_local_storage"; const { Column } = Table; const { Search } = Input; @@ -99,7 +100,7 @@ class ExplorativeAnnotationsView extends React.PureComponent { restoreSearchTags() { // restore the search query tags from the last session - const searchTagString = localStorage.getItem("lastDashboardSearchTags"); + const searchTagString = UserLocalStorage.getItem("lastDashboardSearchTags"); if (searchTagString) { try { const searchTags = JSON.parse(searchTagString); @@ -297,7 +298,7 @@ class ExplorativeAnnotationsView extends React.PureComponent { if (!this.state.tags.includes(tag)) { this.setState(prevState => { const newTags = update(prevState.tags, { $push: [tag] }); - localStorage.setItem("lastDashboardSearchTags", JSON.stringify(newTags)); + UserLocalStorage.setItem("lastDashboardSearchTags", JSON.stringify(newTags)); return { tags: newTags }; }); } @@ -306,7 +307,7 @@ class ExplorativeAnnotationsView extends React.PureComponent { removeTagFromSearch = (tag: string): void => { this.setState(prevState => { const newTags = prevState.tags.filter(t => t !== tag); - localStorage.setItem("lastDashboardSearchTags", JSON.stringify(newTags)); + UserLocalStorage.setItem("lastDashboardSearchTags", JSON.stringify(newTags)); return { tags: newTags }; }); }; diff --git a/frontend/javascripts/libs/user_local_storage.js b/frontend/javascripts/libs/user_local_storage.js new file mode 100644 index 00000000000..50d19035a1c --- /dev/null +++ b/frontend/javascripts/libs/user_local_storage.js @@ -0,0 +1,29 @@ +// @flow + +import Store from "oxalis/store"; + +function prefixKey(key) { + const { activeUser } = Store.getState(); + const prefix = !activeUser ? "Anonymous" : activeUser.email; + return `${prefix}-${key}`; +} + +const UserLocalStorage = { + getItem(key: string): ?string { + return localStorage.getItem(prefixKey(key)); + }, + + setItem(key: string, value: string): void { + return localStorage.setItem(prefixKey(key), value); + }, + + removeItem(key: string): void { + return localStorage.removeItem(prefixKey(key)); + }, + + clear(): void { + localStorage.clear(); + }, +}; + +export default UserLocalStorage; diff --git a/frontend/javascripts/oxalis/view/action-bar/add_new_layout_modal.js b/frontend/javascripts/oxalis/view/action-bar/add_new_layout_modal.js index 9d88a639a66..ca41e5011e3 100644 --- a/frontend/javascripts/oxalis/view/action-bar/add_new_layout_modal.js +++ b/frontend/javascripts/oxalis/view/action-bar/add_new_layout_modal.js @@ -18,16 +18,18 @@ class AddNewLayoutModal extends React.PureComponent { value: "", }; + onConfirm = () => { + const value = this.state.value; + this.setState({ value: "" }); + this.props.addLayout(value); + }; + render() { return ( { - const value = this.state.value; - this.setState({ value: "" }); - this.props.addLayout(value); - }} + onOk={this.onConfirm} onCancel={this.props.onCancel} > { onChange={evt => { this.setState({ value: evt.target.value }); }} + autoFocus + onPressEnter={this.onConfirm} /> ); diff --git a/frontend/javascripts/oxalis/view/layouting/layout_persistence.js b/frontend/javascripts/oxalis/view/layouting/layout_persistence.js index 44f83be728c..cc74e024c73 100644 --- a/frontend/javascripts/oxalis/view/layouting/layout_persistence.js +++ b/frontend/javascripts/oxalis/view/layouting/layout_persistence.js @@ -3,9 +3,11 @@ import NanoEvents from "nanoevents"; import _ from "lodash"; import { getIsInIframe } from "libs/utils"; +import { listenToStoreProperty } from "oxalis/model/helpers/listener_helpers"; import { setStoredLayoutsAction } from "oxalis/model/actions/ui_actions"; import Store from "oxalis/store"; import Toast from "libs/toast"; +import UserLocalStorage from "libs/user_local_storage"; import getDefaultLayouts, { currentLayoutVersion, @@ -25,12 +27,12 @@ const localStorageKeys = { }; function readStoredLayoutConfigs() { - const storedLayoutVersion = localStorage.getItem(localStorageKeys.currentLayoutVersion); + const storedLayoutVersion = UserLocalStorage.getItem(localStorageKeys.currentLayoutVersion); const defaultLayoutConfig = getCurrentDefaultLayoutConfig(); if (getIsInIframe() || !storedLayoutVersion || disableLayoutPersistance) { return defaultLayoutConfig; } - const layoutString = localStorage.getItem(localStorageKeys.goldenWkLayouts); + const layoutString = UserLocalStorage.getItem(localStorageKeys.goldenWkLayouts); if (!layoutString) { return defaultLayoutConfig; } @@ -80,7 +82,13 @@ function readStoredLayoutConfigs() { return defaultLayoutConfig; } -Store.dispatch(setStoredLayoutsAction(readStoredLayoutConfigs())); +listenToStoreProperty( + storeState => storeState.activeUser, + () => { + Store.dispatch(setStoredLayoutsAction(readStoredLayoutConfigs())); + }, + true, +); function persistLayoutConfigs() { if (getIsInIframe()) { @@ -88,8 +96,11 @@ function persistLayoutConfigs() { return; } const { storedLayouts } = Store.getState().uiInformation; - localStorage.setItem(localStorageKeys.goldenWkLayouts, JSON.stringify(storedLayouts)); - localStorage.setItem(localStorageKeys.currentLayoutVersion, JSON.stringify(currentLayoutVersion)); + UserLocalStorage.setItem(localStorageKeys.goldenWkLayouts, JSON.stringify(storedLayouts)); + UserLocalStorage.setItem( + localStorageKeys.currentLayoutVersion, + JSON.stringify(currentLayoutVersion), + ); } layoutEmitter.on("resetLayout", (layoutKey: LayoutKeys, activeLayout: string) => { diff --git a/frontend/javascripts/oxalis/view/layouting/tracing_layout_view.js b/frontend/javascripts/oxalis/view/layouting/tracing_layout_view.js index 5455407f9d8..f79f8607a56 100644 --- a/frontend/javascripts/oxalis/view/layouting/tracing_layout_view.js +++ b/frontend/javascripts/oxalis/view/layouting/tracing_layout_view.js @@ -95,7 +95,7 @@ class TracingLayoutView extends React.PureComponent { ) { lastActiveLayout = props.storedLayouts.LastActiveLayouts[layoutType]; } else { - // added as a valide fallback when there are no stored last active layouts + // added as a fallback when there are no stored last active layouts const firstStoredLayout = Object.keys(props.storedLayouts[layoutType])[0]; lastActiveLayout = firstStoredLayout; }