Skip to content

Commit

Permalink
Multiple custom layouts for tracing views (#3299)
Browse files Browse the repository at this point in the history
* added UI support, moving layout into store

* made reset layout items in action bar into an own component; added active layout as state to action bar; reworked layout persistence to match new layout schema

* moved layout into less file, moved persisting methods

* changed layout, goldenlayout adapter no gets layout via a method, and active layout is resetable

* added resetting, deleting, adding a new layout; added modal to enter new layout name

* added modal

* fixed import errors and added saving in local storage aber deleting and adding

* corrected adding, deleting and resetting the layout

* fixed linter error

* fixed flow errors

* using lodash to create deepcopy of stored layouts

* v1.10.1

* modified yarn.lock -> added checksum?

* removed unused comment; removed useless emit

* Update CHANGELOG.md

* Update CHANGELOG.md

* applied requested feedback

* removed lastActive from filtering layout names

* fixed linting error

* changed ui to indicate that layout are stored separately

* fix default layout bug

* some visual tweaks

* made the code pretty

* fixed typos and change toast to thrown error message

* renamed labels for layout sub menu

* fixed visual bug with the icons

* removed bullet points from layout list
  • Loading branch information
MichaelBuessemeyer authored Oct 10, 2018
1 parent b673829 commit 3c762e9
Show file tree
Hide file tree
Showing 14 changed files with 2,238 additions and 81 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ For upgrade instructions, please check the [migration guide](MIGRATIONS.md).
### Added

- Added support for duplicate dataset names for different organizations [#3137](https://github.com/scalableminds/webknossos/pull/3137)
- A User can now have multiple layouts for tracing views. [#3299](https://github.com/scalableminds/webknossos/pull/3299)

### Changed

Expand Down
15 changes: 14 additions & 1 deletion app/assets/javascripts/oxalis/model/actions/ui_actions.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,15 @@ type SetVersionRestoreVisibilityAction = {
active: boolean,
};

export type UiAction = SetDropzoneModalVisibilityAction | SetVersionRestoreVisibilityAction;
type SetStoredLayoutsAction = {
type: "SET_STORED_LAYOUTS",
storedLayouts: Object,
};

export type UiAction =
| SetDropzoneModalVisibilityAction
| SetVersionRestoreVisibilityAction
| SetStoredLayoutsAction;

export const setDropzoneModalVisibilityAction = (
visible: boolean,
Expand All @@ -26,3 +34,8 @@ export const setVersionRestoreVisibilityAction = (
type: "SET_VERSION_RESTORE_VISIBILITY",
active,
});

export const setStoredLayoutsAction = (storedLayouts: Object): SetStoredLayoutsAction => ({
type: "SET_STORED_LAYOUTS",
storedLayouts,
});
8 changes: 8 additions & 0 deletions app/assets/javascripts/oxalis/model/reducers/ui_reducer.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,14 @@ function UiReducer(state: OxalisState, action: Action): OxalisState {
});
}

case "SET_STORED_LAYOUTS": {
return update(state, {
uiInformation: {
storedLayouts: { $set: action.storedLayouts },
},
});
}

default:
return state;
}
Expand Down
3 changes: 3 additions & 0 deletions app/assets/javascripts/oxalis/store.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ import type {
APIDataset,
APIDataLayer,
} from "admin/api_flow_types";
import { defaultLayoutSchema } from "oxalis/view/layouting/default_layout_configs";

export type CommentType = {|
+content: string,
Expand Down Expand Up @@ -345,6 +346,7 @@ export type ViewModeData = {
type UiInformation = {
+showDropzoneModal: boolean,
+showVersionRestore: boolean,
+storedLayouts: Object,
};

export type OxalisState = {|
Expand Down Expand Up @@ -527,6 +529,7 @@ export const defaultState: OxalisState = {
uiInformation: {
showDropzoneModal: false,
showVersionRestore: false,
storedLayouts: defaultLayoutSchema,
},
};

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
// @flow

import * as React from "react";
import { Input, Modal } from "antd";

type Props = {
addLayout: string => void,
visible: boolean,
onCancel: () => void,
};

type State = {
value: string,
};

class AddNewLayoutModal extends React.PureComponent<Props, State> {
state = {
value: "",
};

render() {
return (
<Modal
title="Add a new layout"
visible={this.props.visible}
onOk={() => {
const value = this.state.value;
this.setState({ value: "" });
this.props.addLayout(value);
}}
onCancel={this.props.onCancel}
>
<Input
placeholder="Layout Name"
value={this.state.value}
onChange={evt => {
this.setState({ value: evt.target.value });
}}
/>
</Modal>
);
}
}

export default AddNewLayoutModal;
138 changes: 121 additions & 17 deletions app/assets/javascripts/oxalis/view/action-bar/tracing_actions_view.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import React, { PureComponent } from "react";
import Model from "oxalis/model";
import Store from "oxalis/store";
import { connect } from "react-redux";
import { Upload, Button, Dropdown, Menu, Icon, Modal } from "antd";
import { Upload, Button, Dropdown, Menu, Icon, Modal, Tooltip } from "antd";
import Constants from "oxalis/constants";
import MergeModalView from "oxalis/view/action-bar/merge_modal_view";
import ShareModalView from "oxalis/view/action-bar/share_modal_view";
Expand All @@ -18,11 +18,10 @@ import { copyAnnotationToUserAccount, finishAnnotation } from "admin/admin_rest_
import { location } from "libs/window";
import type { OxalisState, RestrictionsAndSettings, Task } from "oxalis/store";
import type { APIUser, APITracingType } from "admin/api_flow_types";
import { layoutEmitter } from "oxalis/view/layouting/layout_persistence";
import { updateUserSettingAction } from "oxalis/model/actions/settings_actions";
import SceneController from "oxalis/controller/scene_controller";
import { readFileAsArrayBuffer } from "libs/read_file";
import { AsyncButton } from "components/async_clickables";
import { mapLayoutKeysToLanguage } from "oxalis/view/layouting/default_layout_configs";

type StateProps = {
tracingType: APITracingType,
Expand All @@ -32,27 +31,121 @@ type StateProps = {
activeUser: ?APIUser,
};

type Props = StateProps & {
storedLayoutNamesForView: Array<string>,
layoutKey: string,
activeLayout: string,
onResetLayout: () => void,
onSelectLayout: string => void,
onDeleteLayout: string => void,
addNewLayout: () => void,
};

type State = {
isShareModalOpen: boolean,
isMergeModalOpen: boolean,
isUserScriptsModalOpen: boolean,
};

export const resetLayoutItem = (
<Menu.Item key="reset-layout">
<div
onClick={() => {
Store.dispatch(updateUserSettingAction("layoutScaleValue", 1));
layoutEmitter.emit("resetLayout");
}}
type ResetLayoutItemProps = {
storedLayoutNamesForView: Array<string>,
layoutKey: string,
activeLayout: string,
onResetLayout: () => void,
onSelectLayout: string => void,
onDeleteLayout: string => void,
addNewLayout: () => void,
};

export const ResetLayoutItem = (props: ResetLayoutItemProps) => {
const {
storedLayoutNamesForView,
layoutKey,
activeLayout,
onResetLayout,
onSelectLayout,
onDeleteLayout,
addNewLayout,
...others
} = props;
const layoutMissingHelpTitle = (
<React.Fragment>
<h5 style={{ color: "#fff" }}>Where is my layout?</h5>
<p>
{`The tracing views are separated into four classes. Each of them has their own layouts. If you
can't find your layout please open the tracing in the correct view mode or just add it here manually.`}
</p>
</React.Fragment>
);
const customLayoutsItems = storedLayoutNamesForView.map(layout => {
const isSelectedLayout = layout === activeLayout;
return (
<Menu.Item
key={layout}
className={
isSelectedLayout ? "selected-layout-item bullet-point-less-li" : "bullet-point-less-li"
}
>
<span
onClick={() => onSelectLayout(layout)}
style={{ minWidth: "100%", minHeight: "auto", display: "inline-block" }}
>
<div className="inline-with-margin">
{layout}
{isSelectedLayout ? (
<Icon type="check" className="sub-menu-item-icon" theme="outlined" />
) : (
<Tooltip placement="top" title="Remove this layout">
<Icon
type="delete"
className="clickable-icon sub-menu-item-icon"
onClick={() => onDeleteLayout(layout)}
/>
</Tooltip>
)}
</div>
</span>
</Menu.Item>
);
});
return (
<Menu.SubMenu
{...others}
title={
<span style={{ display: "inline-block", minWidth: 120 }}>
<Icon type="laptop" /> Layout
<Tooltip placement="top" title={layoutMissingHelpTitle}>
<Icon
type="info-circle-o"
style={{ color: "gray", marginRight: 36 }}
className="sub-menu-item-icon"
/>
</Tooltip>
</span>
}
>
<Icon type="laptop" />
Reset Layout
</div>
</Menu.Item>
);
<Menu.Item>
<div onClick={onResetLayout}>Reset Layout</div>
</Menu.Item>
<Menu.Item>
<div onClick={addNewLayout}>Add a new Layout</div>
</Menu.Item>
<Menu.Divider />
<Menu.ItemGroup
className="available-layout-list"
title={
<span style={{ fontSize: 14 }}>{`Layouts for ${
mapLayoutKeysToLanguage[layoutKey]
}`}</span>
}
>
{customLayoutsItems}
</Menu.ItemGroup>
</Menu.SubMenu>
);
};

class TracingActionsView extends PureComponent<StateProps, State> {
class TracingActionsView extends PureComponent<Props, State> {
state = {
isShareModalOpen: false,
isMergeModalOpen: false,
Expand Down Expand Up @@ -266,7 +359,18 @@ class TracingActionsView extends PureComponent<StateProps, State> {
);
}

elements.push(resetLayoutItem);
elements.push(
<ResetLayoutItem
storedLayoutNamesForView={this.props.storedLayoutNamesForView}
layoutKey={this.props.layoutKey}
activeLayout={this.props.activeLayout}
onResetLayout={this.props.onResetLayout}
onSelectLayout={this.props.onSelectLayout}
onDeleteLayout={this.props.onDeleteLayout}
addNewLayout={this.props.addNewLayout}
key="layout"
/>,
);

const onStlUpload = async info => {
const buffer = await readFileAsArrayBuffer(info.file);
Expand Down
Loading

0 comments on commit 3c762e9

Please sign in to comment.