Skip to content

Commit e5aa043

Browse files
authored
Revert "Revert "Add Recommended Configuration to Task Types (#3466)" (#3502)"
This reverts commit 246fbae.
1 parent 246fbae commit e5aa043

38 files changed

+511
-98
lines changed

CHANGELOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ For upgrade instructions, please check the [migration guide](MIGRATIONS.md).
1111

1212
### Added
1313

14-
-
14+
- Added the possibility to specify a recommended user configuration in a task type. The recommended configuration will be shown to users when they trace a task with a different task type and the configuration can be accepted or declined. [#3466](https://github.com/scalableminds/webknossos/pull/3466)
1515

1616
### Changed
1717

MIGRATIONS.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ User-facing changes are documented in the [changelog](CHANGELOG.md).
88
-
99

1010
### Postgres Evolutions:
11-
-
11+
- [036-add-lastTaskTypeId-to-user.sql](conf/evolutions/036-add-lastTaskTypeId-to-user.sql)
1212

1313

1414
## [18.12.0](https://github.com/scalableminds/webknossos/releases/tag/18.12.0) - 2018-11-26

app/assets/javascripts/admin/admin_rest_api.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -142,9 +142,9 @@ export function getUser(userId: string): Promise<APIUser> {
142142
return Request.receiveJSON(`/api/users/${userId}`);
143143
}
144144

145-
export function updateUser(newUser: APIUser): Promise<APIUser> {
145+
export function updateUser(newUser: $Shape<APIUser>): Promise<APIUser> {
146146
return Request.sendJSONReceiveJSON(`/api/users/${newUser.id}`, {
147-
method: "PUT",
147+
method: "PATCH",
148148
data: newUser,
149149
});
150150
}

app/assets/javascripts/admin/api_flow_types.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,7 @@ export type APIUser = APIUserBase & {
137137
+isActive: boolean,
138138
+isEditable: boolean,
139139
+lastActivity: number,
140+
+lastTaskTypeId: ?string,
140141
+organization: string,
141142
};
142143

@@ -190,6 +191,7 @@ export type APITaskType = {
190191
+teamId: string,
191192
+teamName: string,
192193
+settings: APISettings,
194+
+recommendedConfiguration: ?string,
193195
};
194196

195197
export type TaskStatus = { +open: number, +active: number, +finished: number };
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
// @flow
2+
import { Checkbox, Col, Collapse, Form, Icon, Input, Row, Tooltip } from "antd";
3+
import * as React from "react";
4+
5+
import type { DatasetConfiguration, UserConfiguration } from "oxalis/store";
6+
import { jsonEditStyle } from "dashboard/dataset/helper_components";
7+
import { jsonStringify } from "libs/utils";
8+
import { validateUserSettingsJSON } from "dashboard/dataset/validation";
9+
10+
const FormItem = Form.Item;
11+
const { Panel } = Collapse;
12+
13+
export const DEFAULT_RECOMMENDED_CONFIGURATION: $Shape<{|
14+
...UserConfiguration,
15+
...DatasetConfiguration,
16+
|}> = {
17+
clippingDistance: 500,
18+
clippingDistanceArbitrary: 60,
19+
displayCrosshair: true,
20+
displayScalebars: false,
21+
dynamicSpaceDirection: true,
22+
keyboardDelay: 0,
23+
moveValue: 500,
24+
moveValue3d: 600,
25+
mouseRotateValue: 0.001,
26+
newNodeNewTree: false,
27+
highlightCommentedNodes: false,
28+
overrideNodeRadius: true,
29+
particleSize: 5,
30+
rotateValue: 0.01,
31+
sphericalCapRadius: 500,
32+
tdViewDisplayPlanes: false,
33+
fourBit: false,
34+
interpolation: true,
35+
quality: 0,
36+
segmentationOpacity: 0,
37+
highlightHoveredCellId: false,
38+
zoom: 0.8,
39+
renderMissingDataBlack: false,
40+
};
41+
42+
const errorIcon = (
43+
<Tooltip title="The recommended user settings JSON has errors.">
44+
<Icon type="exclamation-circle" style={{ color: "#f5222d", marginLeft: 4 }} />
45+
</Tooltip>
46+
);
47+
48+
export default function RecommendedConfigurationView({
49+
form,
50+
enabled,
51+
onChangeEnabled,
52+
}: {
53+
form: Object,
54+
enabled: boolean,
55+
onChangeEnabled: boolean => void,
56+
}) {
57+
return (
58+
<Collapse
59+
onChange={openedPanels => onChangeEnabled(openedPanels.length === 1)}
60+
activeKey={enabled ? "config" : null}
61+
>
62+
<Panel
63+
key="config"
64+
header={
65+
<React.Fragment>
66+
<Checkbox checked={enabled} style={{ marginRight: 10 }} /> Add Recommended User Settings
67+
{enabled && form.getFieldError("recommendedConfiguration") && errorIcon}
68+
</React.Fragment>
69+
}
70+
showArrow={false}
71+
>
72+
<Row gutter={32}>
73+
<Col span={18}>
74+
<FormItem>
75+
The recommended configuration will be displayed to users when starting to trace a task
76+
with this task type. The user is able to accept or decline this recommendation.
77+
<br />
78+
{form.getFieldDecorator("recommendedConfiguration", {
79+
rules: [
80+
{
81+
validator: validateUserSettingsJSON,
82+
},
83+
],
84+
})(
85+
<Input.TextArea
86+
spellCheck={false}
87+
autosize={{ minRows: 20 }}
88+
style={jsonEditStyle}
89+
/>,
90+
)}
91+
</FormItem>
92+
</Col>
93+
<Col span={6}>
94+
Valid settings and their default values: <br />
95+
<pre>{jsonStringify(DEFAULT_RECOMMENDED_CONFIGURATION)}</pre>
96+
</Col>
97+
</Row>
98+
</Panel>
99+
</Collapse>
100+
);
101+
}

app/assets/javascripts/admin/tasktype/task_type_create_view.js

Lines changed: 35 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,14 @@ import {
1111
updateTaskType,
1212
getTaskType,
1313
} from "admin/admin_rest_api";
14+
import { jsonStringify } from "libs/utils";
15+
import RecommendedConfigurationView, {
16+
DEFAULT_RECOMMENDED_CONFIGURATION,
17+
} from "admin/tasktype/recommended_configuration_view";
1418

1519
const FormItem = Form.Item;
16-
const Option = Select.Option;
17-
const TextArea = Input.TextArea;
20+
const { Option } = Select;
21+
const { TextArea } = Input;
1822

1923
type Props = {
2024
taskTypeId: ?string,
@@ -24,11 +28,13 @@ type Props = {
2428

2529
type State = {
2630
teams: Array<APITeam>,
31+
useRecommendedConfiguration: boolean,
2732
};
2833

2934
class TaskTypeCreateView extends React.PureComponent<Props, State> {
3035
state = {
3136
teams: [],
37+
useRecommendedConfiguration: false,
3238
};
3339

3440
componentDidMount() {
@@ -43,12 +49,22 @@ class TaskTypeCreateView extends React.PureComponent<Props, State> {
4349
branchPointsAllowed: true,
4450
preferredMode: null,
4551
},
52+
recommendedConfiguration: jsonStringify(DEFAULT_RECOMMENDED_CONFIGURATION),
4653
};
4754
const taskType = this.props.taskTypeId ? await getTaskType(this.props.taskTypeId) : null;
4855
// Use merge which is deep _.extend
49-
// eslint-disable-next-line no-unused-vars
50-
const { recommendedConfiguration, ...formValues } = _.merge({}, defaultValues, taskType);
56+
const formValues = _.merge({}, defaultValues, taskType);
57+
if (formValues.recommendedConfiguration == null) {
58+
// A recommended configuration of null overrides the default configuration when using _.merge
59+
// If the task type has no recommended configuration, suggest the default one
60+
formValues.recommendedConfiguration = defaultValues.recommendedConfiguration;
61+
}
5162
this.props.form.setFieldsValue(formValues);
63+
64+
if (taskType != null && taskType.recommendedConfiguration != null) {
65+
// Only "activate" the recommended configuration checkbox if the existing task type contained one
66+
this.setState({ useRecommendedConfiguration: true });
67+
}
5268
}
5369

5470
async fetchData() {
@@ -57,6 +73,9 @@ class TaskTypeCreateView extends React.PureComponent<Props, State> {
5773

5874
handleSubmit = e => {
5975
e.preventDefault();
76+
if (!this.state.useRecommendedConfiguration) {
77+
this.props.form.setFieldsValue({ recommendedConfiguration: null });
78+
}
6079
this.props.form.validateFields(async (err, formValues) => {
6180
if (!err) {
6281
if (this.props.taskTypeId) {
@@ -69,6 +88,10 @@ class TaskTypeCreateView extends React.PureComponent<Props, State> {
6988
});
7089
};
7190

91+
onChangeUseRecommendedConfiguration = (useRecommendedConfiguration: boolean) => {
92+
this.setState({ useRecommendedConfiguration });
93+
};
94+
7295
render() {
7396
const { getFieldDecorator } = this.props.form;
7497
const titlePrefix = this.props.taskTypeId ? "Update " : "Create";
@@ -170,6 +193,14 @@ class TaskTypeCreateView extends React.PureComponent<Props, State> {
170193
)}
171194
</FormItem>
172195

196+
<FormItem>
197+
<RecommendedConfigurationView
198+
form={this.props.form}
199+
enabled={this.state.useRecommendedConfiguration}
200+
onChangeEnabled={this.onChangeUseRecommendedConfiguration}
201+
/>
202+
</FormItem>
203+
173204
<FormItem>
174205
<Button type="primary" htmlType="submit">
175206
{titlePrefix} Task Type

app/assets/javascripts/dashboard/dataset/dataset_import_view.js

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import {
2424
updateDatasetTeams,
2525
} from "admin/admin_rest_api";
2626
import { handleGenericError } from "libs/error_handling";
27+
import { jsonStringify } from "libs/utils";
2728
import Toast from "libs/toast";
2829
import messages from "messages";
2930

@@ -35,8 +36,6 @@ import SimpleAdvancedDataForm from "./simple_advanced_data_form";
3536
const { TabPane } = Tabs;
3637
const FormItem = Form.Item;
3738

38-
const toJSON = json => JSON.stringify(json, null, " ");
39-
4039
type Props = {
4140
form: Object,
4241
datasetId: APIDatasetId,
@@ -97,7 +96,7 @@ class DatasetImportView extends React.PureComponent<Props, State> {
9796
throw new Error("No datasource received from server.");
9897
}
9998
this.props.form.setFieldsValue({
100-
dataSourceJson: toJSON(dataSource),
99+
dataSourceJson: jsonStringify(dataSource),
101100
dataset: {
102101
displayName: dataset.displayName || undefined,
103102
isPublic: dataset.isPublic || false,
@@ -325,7 +324,7 @@ class DatasetImportView extends React.PureComponent<Props, State> {
325324
// _.merge does a deep merge which mutates newDataSource
326325
_.merge(newDataSource, form.getFieldValue("dataSource"));
327326
form.setFieldsValue({
328-
dataSourceJson: toJSON(newDataSource),
327+
dataSourceJson: jsonStringify(newDataSource),
329328
});
330329
} else {
331330
// Copy from advanced to simple: update form values

app/assets/javascripts/dashboard/dataset/validation.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,11 @@
33
import jsonschema from "jsonschema";
44

55
import DatasourceSchema from "libs/datasource.schema.json";
6+
import UserSettingsSchema from "libs/user_settings.schema.json";
67

78
const validator = new jsonschema.Validator();
89
validator.addSchema(DatasourceSchema, "/");
10+
validator.addSchema(UserSettingsSchema, "/");
911

1012
const validateWithSchema = (type: string) => (rule: Object, value: string, callback: Function) => {
1113
try {
@@ -29,6 +31,7 @@ const validateWithSchema = (type: string) => (rule: Object, value: string, callb
2931

3032
export const validateDatasourceJSON = validateWithSchema("types::DatasourceConfiguration");
3133
export const validateLayerConfigurationJSON = validateWithSchema("types::LayerUserConfiguration");
34+
export const validateUserSettingsJSON = validateWithSchema("types::UserSettings");
3235

3336
export const isValidJSON = (json: string) => {
3437
try {

app/assets/javascripts/libs/render_independently.js

Lines changed: 19 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -6,18 +6,25 @@ import { document } from "libs/window";
66

77
type DestroyFunction = () => void;
88

9-
export default function renderIndependently(getComponent: DestroyFunction => React$Element<*>) {
10-
const div = document.createElement("div");
11-
if (!document.body) {
12-
return;
13-
}
14-
document.body.appendChild(div);
15-
function destroy() {
16-
const unmountResult = ReactDOM.unmountComponentAtNode(div);
17-
if (unmountResult && div.parentNode) {
18-
div.parentNode.removeChild(div);
9+
// The returned promise gets resolved once the element is destroyed.
10+
export default function renderIndependently(
11+
getComponent: DestroyFunction => React$Element<*>,
12+
): Promise<void> {
13+
return new Promise(resolve => {
14+
const div = document.createElement("div");
15+
if (!document.body) {
16+
resolve();
17+
return;
18+
}
19+
document.body.appendChild(div);
20+
function destroy() {
21+
const unmountResult = ReactDOM.unmountComponentAtNode(div);
22+
if (unmountResult && div.parentNode) {
23+
div.parentNode.removeChild(div);
24+
}
25+
resolve();
1926
}
20-
}
2127

22-
ReactDOM.render(getComponent(destroy), div);
28+
ReactDOM.render(getComponent(destroy), div);
29+
});
2330
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
{
2+
"$schema": "http://json-schema.org/draft-06/schema#",
3+
"definitions": {
4+
"types::UserSettings": {
5+
"type": ["object", "null"],
6+
"properties": {
7+
"clippingDistance": { "type": "number", "minimum": 1, "maximum": 12000 },
8+
"clippingDistanceArbitrary": { "type": "number", "minimum": 1, "maximum": 127 },
9+
"crosshairSize": { "type": "number", "minimum": 0.05, "maximum": 0.5 },
10+
"displayCrosshair": { "type": "boolean" },
11+
"displayScalebars": { "type": "boolean" },
12+
"dynamicSpaceDirection": { "type": "boolean" },
13+
"keyboardDelay": { "type": "number", "minimum": 0, "maximum": 500 },
14+
"mouseRotateValue": { "type": "number", "minimum": 0.0001, "maximum": 0.02 },
15+
"moveValue": { "type": "number", "minimum": 30, "maximum": 14000 },
16+
"moveValue3d": { "type": "number", "minimum": 30, "maximum": 1500 },
17+
"newNodeNewTree": { "type": "boolean" },
18+
"highlightCommentedNodes": { "type": "boolean" },
19+
"overrideNodeRadius": { "type": "boolean" },
20+
"particleSize": { "type": "number", "minimum": 1, "maximum": 20 },
21+
"radius": { "type": "number", "minimum": 1, "maximum": 5000 },
22+
"rotateValue": { "type": "number", "minimum": 0.001, "maximum": 0.08 },
23+
"sortCommentsAsc": { "type": "boolean" },
24+
"sortTreesByName": { "type": "boolean" },
25+
"sphericalCapRadius": { "type": "number", "minimum": 50, "maximum": 500 },
26+
"tdViewDisplayPlanes": { "type": "boolean" },
27+
"hideTreeRemovalWarning": { "type": "boolean" },
28+
"fourBit": { "type": "boolean" },
29+
"interpolation": { "type": "boolean" },
30+
"quality": { "type": "number", "enum": [0, 1, 2] },
31+
"segmentationOpacity": { "type": "number", "minimum": 0, "maximum": 100 },
32+
"highlightHoveredCellId": { "type": "boolean" },
33+
"zoom": { "type": "number", "minimum": 0.001 },
34+
"renderMissingDataBlack": { "type": "boolean" }
35+
},
36+
"additionalProperties": false
37+
}
38+
}
39+
}

0 commit comments

Comments
 (0)