diff --git a/.eslintrc.json b/.eslintrc.json index a92c4fafd0e..c5385ecc017 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -39,6 +39,7 @@ "react/jsx-filename-extension": "off", "react/no-multi-comp": "warn", "react/prop-types": "off", + "react/require-default-props": "off", "radix": "off", "no-bitwise": "off", "no-confusing-arrow": "off", diff --git a/.flowconfig b/.flowconfig index d7acf918179..8392621461e 100644 --- a/.flowconfig +++ b/.flowconfig @@ -16,6 +16,7 @@ /node_modules/findup/ /node_modules/documentation/ /node_modules/flow-coverage-report/ +/.nyc_output [untyped] /node_modules/ava/ diff --git a/CHANGELOG.md b/CHANGELOG.md index 78675138c66..b1b0f52566e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,71 +5,103 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Calendar Versioning](http://calver.org/) `0Y.0M.MICRO`. For upgrade instructions, please check the [migration guide](MIGRATIONS.md). -## [Unreleased] -[Commits](https://github.com/scalableminds/webknossos/compare/18.07.0...HEAD) + +## Unreleased +[Commits](https://github.com/scalableminds/webknossos/compare/18.08.0...HEAD) ### Added - - Added release version to navbar [#2888](https://github.com/scalableminds/webknossos/pull/2888) - - Users can view datasets in a table from the dashboard. That view also allows to create explorational tracings (which had to be done via the gallery view for non-admins before). [#2866](https://github.com/scalableminds/webknossos/pull/2866) - - Added the task bounding box of a skeleton tracing to NML files. [#2827](https://github.com/scalableminds/webknossos/pull/2827) \ - Example: `` - - Added the possibility to kick a user out of the organization team. [#2801](https://github.com/scalableminds/webknossos/pull/2801) - - Added a mandatory waiting interval of 10 seconds when getting a task with a new task type. The modal containing the task description cannot be closed earlier. These ten seconds should be used to fully understand the new task type. [#2793](https://github.com/scalableminds/webknossos/pull/2793) - - Added possibility to share a special link to invite users to join your organization. Following that link, the sign-up form will automatically register the user for the correct organization. [#2898](https://github.com/scalableminds/webknossos/pull/2898) - - Added more debugging related information in case of unexpected errors. The additional information can be used when reporting the error. [#2766](https://github.com/scalableminds/webknossos/pull/2766) - - Added permission for team managers to create explorational tracings on datasets without allowed teams. [#2758](https://github.com/scalableminds/webknossos/pull/2758) - - Added higher-resolution images for dataset gallery thumbnails. [#2745](https://github.com/scalableminds/webknossos/pull/2745) - - Added permission for admins to get tasks from all projects in their organization. [#2728](https://github.com/scalableminds/webknossos/pull/2728) - - Added the shortcut to copy the currently hovered cell id (CTRL + I) to non-volume-tracings, too. [#2726](https://github.com/scalableminds/webknossos/pull/2726) - - Added permission for team managers to refresh datasets. [#2688](https://github.com/scalableminds/webknossos/pull/2688) - - Added backend-unit-test setup and a first test for NML validation. [#2829](https://github.com/scalableminds/webknossos/pull/2829) - - Added progress indicators to the save button for cases where the saving takes some time (e.g., when importing a large NML). [#2947](https://github.com/scalableminds/webknossos/pull/2947) - - Added the possibility to not sort comments by name. When clicking the sort button multiple times, sorting is switched to sort by IDs. [#2915](https://github.com/scalableminds/webknossos/pull/2915) - - Added displayName for organizations. [#2869](https://github.com/scalableminds/webknossos/pull/2869) - - Added onboarding flow for initial setup of WebKnossos. [#2859](https://github.com/scalableminds/webknossos/pull/2859) - - Added the possibility to show the task in a random order. [#2860](https://github.com/scalableminds/webknossos/pull/2860) +- All dates in webknossos will be shown in the browser's timezone. On hover, a tooltip will show the date in UTC. [#2916](https://github.com/scalableminds/webknossos/pull/2916) ![image](https://user-images.githubusercontent.com/2486553/42888385-74c82bc0-8aa8-11e8-9c3e-7cfc90ce93bc.png) +- When merging datasets within a tracing via the merge-modal, the user can choose whether the merge should be executed directly in the currently opened tracing. Alternatively, a new annotation can be created which is accessible via the dashboard (as it has been before). +- When a lot of changes need to be persisted to the server (e.g., after importing a large NML), the save button will show a percentage-based progress indicator. +- Added the possibility to import multiple NML files into the active tracing. This can be done by dragging and dropping the files directly into the tracing view. [#2908](https://github.com/scalableminds/webknossos/pull/2908) +- During the import of multiple NML files, the user can select an option to automatically create a group per file so that the imported trees are organized in a hierarchy. [#2908](https://github.com/scalableminds/webknossos/pull/2908) ### Changed - - Improved the search functionality in the datasets view. The datasets will be sorted so that the best match is shown first. If a different sorting is desired, the sorting-arrows in the columns can still be used to change the sorting criteria. [#2834](https://github.com/scalableminds/webknossos/pull/2834) - - Improved performance in orthogonal mode. [#2821](https://github.com/scalableminds/webknossos/pull/2821) - - When deleting the last node of a tree, that tree will not be removed automatically anymore. Instead, the tree will just be empty. To remove that active tree, the "delete" shortcut can be used again. [#2806](https://github.com/scalableminds/webknossos/pull/2806) - - Changed the type of the initial node of new tasks to be a branchpoint (if not created via NML). [#2799](https://github.com/scalableminds/webknossos/pull/2799) - - The dataset gallery got a redesign with mobile support. [#2761](https://github.com/scalableminds/webknossos/pull/2761) - - Improved the performance of saving large changes to a tracing (e.g., when importing a large NML). [#2947](https://github.com/scalableminds/webknossos/pull/2947) - - Improved loading speed of buckets. [#2724](https://github.com/scalableminds/webknossos/pull/2724) - - Changed the task search, when filtered by user, to show all instead of just active tasks (except for canceled tasks). [#2774](https://github.com/scalableminds/webknossos/pull/2774) - - Improved the import dialog for datasets. Important fields can now be edited via form inputs instead of having to change the JSON. The JSON is still changeable when enabling an "Advanced" mode. [#2881](https://github.com/scalableminds/webknossos/pull/2881) - - Hid old paused projects in the project progress report even if they have open instances. [#2768](https://github.com/scalableminds/webknossos/pull/2768) - - Excluded canceled tasks and base tracings from the list at `api/projects/:name/usersWithOpenTasks`. [#2765](https://github.com/scalableminds/webknossos/pull/2765) - - Streamlined the order in which initial buckets are loaded when viewing a dataset. [#2749](https://github.com/scalableminds/webknossos/pull/2749) - - Reduced the number of scenarios in which segmentation-related warnings are shown (e.g, not for skeleton tracings when there are multiple resolutions for segmentations anyway). [#2715](https://github.com/scalableminds/webknossos/pull/2715) - - Improved tracing view page load performance by decreasing WebGL shader compilation time. [#2709](https://github.com/scalableminds/webknossos/pull/2709) - - Redesigned the user task list to make it easier to read the whole task description. [#2861](https://github.com/scalableminds/webknossos/pull/2861) +- ### Fixed - - Fixed a bug which caused segmentation data to be requested as four-bit when four-bit-mode was enabled. [#2828](https://github.com/scalableminds/webknossos/pull/2828) - - Fixed a bug where possible comments or branchpoints sometimes were not properly deleted when deleting a node. [2897](https://github.com/scalableminds/webknossos/pull/2897) - - Fixed a bug which caused projects to be unpaused when the project priority was changed. [#2795](https://github.com/scalableminds/webknossos/pull/2795) - - Fixed an unnecessary warning when deleting a tree in a task, that warned about deleting the initial node although the initial node was not contained in the deleted tree. [#2812](https://github.com/scalableminds/webknossos/pull/2812) - - Fixed a bug where the comment tab was scrolled into view horizontally if a node with a comment was activated. [#2805](https://github.com/scalableminds/webknossos/pull/2805) - - Fixed a bug in for Firefox users where a long tree list created an unnecessary scroll region. [#2787](https://github.com/scalableminds/webknossos/pull/2787) - - Fixed clicking on a task type within the task list page, so that the task type page will actually only show the linked task type. [#2769](https://github.com/scalableminds/webknossos/pull/2769) - - Fixed clicking on a project within the task list page, so that the project page will actually only show the linked project. [#2759](https://github.com/scalableminds/webknossos/pull/2759) - - Fixed a bug in the front-end API's `setMapping` call which caused ignored calls if the provided object was mutated. [#2921](https://github.com/scalableminds/webknossos/pull/2921) - - Fixed a bug where cell IDs in the segmentation tab were not shown for all zoomsteps. [#2726](https://github.com/scalableminds/webknossos/pull/2726) - - Fixed the naming of the initial tree in tasks. [#2689](https://github.com/scalableminds/webknossos/pull/2689) - - Fixed a regression affecting node selection, shortcuts and 3d viewport navigation. [#2673](https://github.com/scalableminds/webknossos/pull/2673) - - Fixed the dataset zip upload for datasets, which only have one data layer and no config file. [#2840](https://github.com/scalableminds/webknossos/pull/2840) - - Fixed a bug where task deletion broke the task listing for users who had active annotations for the task [#2884](https://github.com/scalableminds/webknossos/pull/2884) - - Fixed that decimal scales (e.g., 11.24, 11.24, 30) couldn't be defined for datasets in "simple" mode. [#2912](https://github.com/scalableminds/webknossos/pull/2912) +- ### Removed +- + + +## [18.08.0](https://github.com/scalableminds/webknossos/releases/tag/18.08.0) - 2018-07-23 +[Commits](https://github.com/scalableminds/webknossos/compare/18.07.0...18.08.0) + +### Highlights +- Performance improvements for the tracing views. #2709 #2724 #2821 +- Added onboarding flow for initial setup of WebKnossos. #2859 +- The dataset gallery got a redesign with mobile support. #2761 +- Improved the import dialog for datasets. Important fields can now be edited via form inputs instead of having to change the JSON. The JSON is still changeable when enabling an "Advanced" mode. #2881 +- Added possibility to share a special link to invite users to join your organization. Following that link, the sign-up form will automatically register the user for the correct organization. #2898 + +### Added + +- Added release version to navbar [#2888](https://github.com/scalableminds/webknossos/pull/2888) +- Users can view datasets in a table from the dashboard. That view also allows to create explorational tracings (which had to be done via the gallery view for non-admins before). [#2866](https://github.com/scalableminds/webknossos/pull/2866) +- Added the task bounding box of a skeleton tracing to NML files. [#2827](https://github.com/scalableminds/webknossos/pull/2827) \ + Example: `` +- Added the possibility to kick a user out of the organization team. [#2801](https://github.com/scalableminds/webknossos/pull/2801) +- Added a mandatory waiting interval of 10 seconds when getting a task with a new task type. The modal containing the task description cannot be closed earlier. These ten seconds should be used to fully understand the new task type. [#2793](https://github.com/scalableminds/webknossos/pull/2793) +- Added possibility to share a special link to invite users to join your organization. Following that link, the sign-up form will automatically register the user for the correct organization. [#2898](https://github.com/scalableminds/webknossos/pull/2898) +- Added more debugging related information in case of unexpected errors. The additional information can be used when reporting the error. [#2766](https://github.com/scalableminds/webknossos/pull/2766) +- Added permission for team managers to create explorational tracings on datasets without allowed teams. [#2758](https://github.com/scalableminds/webknossos/pull/2758) +- Added higher-resolution images for dataset gallery thumbnails. [#2745](https://github.com/scalableminds/webknossos/pull/2745) +- Added permission for admins to get tasks from all projects in their organization. [#2728](https://github.com/scalableminds/webknossos/pull/2728) +- Added the shortcut to copy the currently hovered cell id (CTRL + I) to non-volume-tracings, too. [#2726](https://github.com/scalableminds/webknossos/pull/2726) +- Added permission for team managers to refresh datasets. [#2688](https://github.com/scalableminds/webknossos/pull/2688) +- Added backend-unit-test setup and a first test for NML validation. [#2829](https://github.com/scalableminds/webknossos/pull/2829) +- Added progress indicators to the save button for cases where the saving takes some time (e.g., when importing a large NML). [#2947](https://github.com/scalableminds/webknossos/pull/2947) +- Added the possibility to not sort comments by name. When clicking the sort button multiple times, sorting is switched to sort by IDs. [#2915](https://github.com/scalableminds/webknossos/pull/2915) +- Added displayName for organizations. [#2869](https://github.com/scalableminds/webknossos/pull/2869) +- Added onboarding flow for initial setup of WebKnossos. [#2859](https://github.com/scalableminds/webknossos/pull/2859) +- Added the possibility to show the task in a random order. [#2860](https://github.com/scalableminds/webknossos/pull/2860) + +### Changed + +- Improved the search functionality in the datasets view. The datasets will be sorted so that the best match is shown first. If a different sorting is desired, the sorting-arrows in the columns can still be used to change the sorting criteria. [#2834](https://github.com/scalableminds/webknossos/pull/2834) +- Improved performance in orthogonal mode. [#2821](https://github.com/scalableminds/webknossos/pull/2821) +- When deleting the last node of a tree, that tree will not be removed automatically anymore. Instead, the tree will just be empty. To remove that active tree, the "delete" shortcut can be used again. [#2806](https://github.com/scalableminds/webknossos/pull/2806) +- Changed the type of the initial node of new tasks to be a branchpoint (if not created via NML). [#2799](https://github.com/scalableminds/webknossos/pull/2799) +- The dataset gallery got a redesign with mobile support. [#2761](https://github.com/scalableminds/webknossos/pull/2761) +- Improved the performance of saving large changes to a tracing (e.g., when importing a large NML). [#2947](https://github.com/scalableminds/webknossos/pull/2947) +- Improved loading speed of buckets. [#2724](https://github.com/scalableminds/webknossos/pull/2724) +- Changed the task search, when filtered by user, to show all instead of just active tasks (except for canceled tasks). [#2774](https://github.com/scalableminds/webknossos/pull/2774) +- Improved the import dialog for datasets. Important fields can now be edited via form inputs instead of having to change the JSON. The JSON is still changeable when enabling an "Advanced" mode. [#2881](https://github.com/scalableminds/webknossos/pull/2881) +- Hid old paused projects in the project progress report even if they have open instances. [#2768](https://github.com/scalableminds/webknossos/pull/2768) +- Excluded canceled tasks and base tracings from the list at `api/projects/:name/usersWithOpenTasks`. [#2765](https://github.com/scalableminds/webknossos/pull/2765) +- Streamlined the order in which initial buckets are loaded when viewing a dataset. [#2749](https://github.com/scalableminds/webknossos/pull/2749) +- Reduced the number of scenarios in which segmentation-related warnings are shown (e.g, not for skeleton tracings when there are multiple resolutions for segmentations anyway). [#2715](https://github.com/scalableminds/webknossos/pull/2715) +- Email addresses for notifications about new users and about task overtime are no longer specified instance-wide but once per organization. [#2939](https://github.com/scalableminds/webknossos/pull/2939) +- Improved tracing view page load performance by decreasing WebGL shader compilation time. [#2709](https://github.com/scalableminds/webknossos/pull/2709) +- Redesigned the user task list to make it easier to read the whole task description. [#2861](https://github.com/scalableminds/webknossos/pull/2861) + + +### Fixed + +- Fixed a bug which caused segmentation data to be requested as four-bit when four-bit-mode was enabled. [#2828](https://github.com/scalableminds/webknossos/pull/2828) +- Fixed a bug where possible comments or branchpoints sometimes were not properly deleted when deleting a node. [2897](https://github.com/scalableminds/webknossos/pull/2897) +- Fixed a bug which caused projects to be unpaused when the project priority was changed. [#2795](https://github.com/scalableminds/webknossos/pull/2795) +- Fixed an unnecessary warning when deleting a tree in a task, that warned about deleting the initial node although the initial node was not contained in the deleted tree. [#2812](https://github.com/scalableminds/webknossos/pull/2812) +- Fixed a bug where the comment tab was scrolled into view horizontally if a node with a comment was activated. [#2805](https://github.com/scalableminds/webknossos/pull/2805) +- Fixed a bug in for Firefox users where a long tree list created an unnecessary scroll region. [#2787](https://github.com/scalableminds/webknossos/pull/2787) +- Fixed clicking on a task type within the task list page, so that the task type page will actually only show the linked task type. [#2769](https://github.com/scalableminds/webknossos/pull/2769) +- Fixed clicking on a project within the task list page, so that the project page will actually only show the linked project. [#2759](https://github.com/scalableminds/webknossos/pull/2759) +- Fixed a bug in the front-end API's `setMapping` call which caused ignored calls if the provided object was mutated. [#2921](https://github.com/scalableminds/webknossos/pull/2921) +- Fixed a bug where cell IDs in the segmentation tab were not shown for all zoomsteps. [#2726](https://github.com/scalableminds/webknossos/pull/2726) +- Fixed the naming of the initial tree in tasks. [#2689](https://github.com/scalableminds/webknossos/pull/2689) +- Fixed a regression affecting node selection, shortcuts and 3d viewport navigation. [#2673](https://github.com/scalableminds/webknossos/pull/2673) +- Fixed the dataset zip upload for datasets, which only have one data layer and no config file. [#2840](https://github.com/scalableminds/webknossos/pull/2840) +- Fixed a bug where task deletion broke the task listing for users who had active annotations for the task [#2884](https://github.com/scalableminds/webknossos/pull/2884) +- Fixed that decimal scales (e.g., 11.24, 11.24, 30) couldn't be defined for datasets in "simple" mode. [#2912](https://github.com/scalableminds/webknossos/pull/2912) + ## [18.07.0](https://github.com/scalableminds/webknossos/releases/tag/18.07.0) - 2018-07-05 diff --git a/Dockerfile b/Dockerfile index 9aa1e7f85bf..5b0e04ef4e3 100644 --- a/Dockerfile +++ b/Dockerfile @@ -15,4 +15,4 @@ RUN addgroup --system --gid 999 webknossos \ USER webknossos -ENTRYPOINT [ "bin/oxalis" ] +ENTRYPOINT [ "bin/webknossos" ] diff --git a/MIGRATIONS.md b/MIGRATIONS.md index 6cb00e2311e..053f9c6c6a0 100644 --- a/MIGRATIONS.md +++ b/MIGRATIONS.md @@ -4,13 +4,19 @@ All migrations of webknossos are documented in this file. This project adheres to [Calendar Versioning](http://calver.org/) `0Y.0M.MICRO`. User-facing changes are documented in the [changelog](CHANGELOG.md). -## [Unreleased] +## Unreleased +### Postgres Evolutions: +- + +## [18.08.0](https://github.com/scalableminds/webknossos/releases/tag/18.08.0) - 2018-07-23 ### Postgres Evolutions: - [013-add-logoUrl.sql](conf/evolutions/013-add-logoUrl.sql) - [014-equalize-schema-and-evolutions.sql](conf/evolutions/014-equalize-schema-and-evolutions.sql) - [015-add-organization-displayname.sql](conf/evolutions/015-add-organization-displayname.sql) - To clean up half-deleted tasks as caused by [this bug](https://github.com/scalableminds/webknossos/issues/2873), run `update webknossos.annotations set isDeleted = true where _id in (select a._id from webknossos.annotations_ a join webknossos.tasks t on a._task = t._id where t.isDeleted and a.typ == 'Task')` - [016-add-schema-version.sql](conf/evolutions/016-add-schema-version.sql) +- [017-add-organization-email.sql](conf/evolutions/017-add-organization-email.sql) +- Add email addresses for notifications about new users and about task overtime to the `webknossos.organizations` entries in the Postgres database (previously in `application.conf` > `braintracing.newuserlist` and `braintracing.overTimeList`) ## [18.07.0](https://github.com/scalableminds/webknossos/releases/tag/18.07.0) - 2018-07-05 First release diff --git a/app/assets/javascripts/admin/statistic/project_progress_report_view.js b/app/assets/javascripts/admin/statistic/project_progress_report_view.js index a47b0ce4278..2ad608c0657 100644 --- a/app/assets/javascripts/admin/statistic/project_progress_report_view.js +++ b/app/assets/javascripts/admin/statistic/project_progress_report_view.js @@ -2,10 +2,10 @@ import * as React from "react"; import { Icon, Spin, Table, Card } from "antd"; import Utils from "libs/utils"; -import FormatUtils from "libs/format_utils"; import Loop from "components/loop"; import { getProjectProgressReport } from "admin/admin_rest_api"; import type { APIProjectProgressReportType, APITeamType } from "admin/api_flow_types"; +import FormattedDate from "components/formatted_date"; import TeamSelectionForm from "./team_selection_form"; const { Column, ColumnGroup } = Table; @@ -69,7 +69,7 @@ class ProjectProgressReportView extends React.PureComponent<{}, State> {
- {this.state.updatedAt != null ? FormatUtils.formatDate(this.state.updatedAt) : null}{" "} + {this.state.updatedAt != null ? : null}{" "}
diff --git a/app/assets/javascripts/admin/task/task_annotation_view.js b/app/assets/javascripts/admin/task/task_annotation_view.js index 11a30762f4e..9cf3e99dfd1 100644 --- a/app/assets/javascripts/admin/task/task_annotation_view.js +++ b/app/assets/javascripts/admin/task/task_annotation_view.js @@ -3,7 +3,6 @@ import { Dropdown, Menu, Icon, Modal } from "antd"; import React from "react"; import { connect } from "react-redux"; -import moment from "moment"; import FormatUtils from "libs/format_utils"; import { getAnnotationsForTask, @@ -16,6 +15,7 @@ import messages from "messages"; import TransferTaskModal from "dashboard/transfer_task_modal"; import type { APIUserType, APITaskType, APIAnnotationType } from "admin/api_flow_types"; import type { OxalisState } from "oxalis/store"; +import FormattedDate from "components/formatted_date"; const { Item } = Menu; const { confirm } = Modal; @@ -163,7 +163,9 @@ class TaskAnnotationView extends React.PureComponent return ( {userString} - {moment(annotation.modified).format("YYYY-MM-DD HH:mm")} + + + diff --git a/app/assets/javascripts/admin/task/task_list_view.js b/app/assets/javascripts/admin/task/task_list_view.js index 5da9b67516f..100bd27cce2 100644 --- a/app/assets/javascripts/admin/task/task_list_view.js +++ b/app/assets/javascripts/admin/task/task_list_view.js @@ -20,6 +20,7 @@ import type { APITaskType, APITaskTypeType } from "admin/api_flow_types"; import type { QueryObjectType, TaskFormFieldValuesType } from "admin/task/task_search_form"; import type { RouterHistory } from "react-router-dom"; import { handleGenericError } from "libs/error_handling"; +import FormattedDate from "components/formatted_date"; const { Column } = Table; const { Search, TextArea } = Input; @@ -244,6 +245,7 @@ class TaskListView extends React.PureComponent { key="created" width={150} sorter={Utils.localeCompareBy(typeHint, "created")} + render={created => } /> { + return new Promise((resolve, reject) => { + const reader = new FileReader(); + reader.onerror = reject; + reader.onload = () => resolve(reader.result.toString()); + reader.readAsText(file); + }); +} + class FileUpload extends React.PureComponent { fileInput: ?HTMLInputElement; @@ -32,10 +41,7 @@ class FileUpload extends React.PureComponent { data: { [this.props.name]: files }, }).then(successCallback, errorCallback); } else { - const reader = new FileReader(); - reader.onerror = errorCallback; - reader.onload = () => successCallback(reader.result); - reader.readAsText(files[0]); + readFileAsText(files[0]).then(successCallback, errorCallback); } }; diff --git a/app/assets/javascripts/components/formatted_date.js b/app/assets/javascripts/components/formatted_date.js new file mode 100644 index 00000000000..8ee9d568a03 --- /dev/null +++ b/app/assets/javascripts/components/formatted_date.js @@ -0,0 +1,38 @@ +// @flow +import * as React from "react"; +import moment from "moment"; +import { Tooltip } from "antd"; + +const defaultTimeFormat = "YYYY-MM-DD HH:mm"; + +/** + * Return current date and time. Please only use this function if you need + * a pure string representation. In all other cases, please prefer the + * component below. + */ +export function formatDateInLocalTimeZone(date?: number = Date.now()): string { + return moment(date).format(defaultTimeFormat); +} + +export default function FormattedDate({ + timestamp, + format, +}: { + timestamp: string | number, + format?: string, +}) { + const _moment = moment.utc(timestamp); + const _format = format || defaultTimeFormat; + return ( + + The displayed time refers to your local timezone. In UTC, the time is:{" "} + {_moment.format(_format)} + + } + > + {_moment.local().format(_format)} + + ); +} diff --git a/app/assets/javascripts/dashboard/advanced_dataset/advanced_dataset_view.js b/app/assets/javascripts/dashboard/advanced_dataset/advanced_dataset_view.js index b5cbcc0d5c5..02841f0d2c9 100644 --- a/app/assets/javascripts/dashboard/advanced_dataset/advanced_dataset_view.js +++ b/app/assets/javascripts/dashboard/advanced_dataset/advanced_dataset_view.js @@ -11,6 +11,7 @@ import type { DatasetType } from "dashboard/dataset_view"; import type { APITeamType } from "admin/api_flow_types"; import dice from "dice-coefficient"; import _ from "lodash"; +import FormattedDate from "components/formatted_date"; const { Column } = Table; @@ -120,9 +121,9 @@ class AdvancedDatasetView extends React.PureComponent { title="Creation Date" dataIndex="created" key="created" - sorter={Utils.localeCompareBy(typeHint, "formattedCreated")} + sorter={Utils.compareBy(typeHint, "created")} sortOrder={sortedInfo.columnKey === "created" && sortedInfo.order} - render={(__, dataset: DatasetType) => dataset.formattedCreated} + render={created => } /> { const TaskCardTitle = ({ task }) => ( - {`${task.type.summary} (${moment(task.created).format( - "YYYY-MM-DD HH:mm", - )}) `} + + {task.type.summary} () + {task.type.settings.allowedModes.map(mode => {mode})} ); diff --git a/app/assets/javascripts/dashboard/dataset_view.js b/app/assets/javascripts/dashboard/dataset_view.js index 6cba9fcf15f..42222dabf6d 100644 --- a/app/assets/javascripts/dashboard/dataset_view.js +++ b/app/assets/javascripts/dashboard/dataset_view.js @@ -4,7 +4,6 @@ import _ from "lodash"; import * as React from "react"; import { Link, withRouter } from "react-router-dom"; import Utils from "libs/utils"; -import moment from "moment"; import { Spin, Input, Button } from "antd"; import AdvancedDatasetView from "dashboard/advanced_dataset/advanced_dataset_view"; import GalleryDatasetView from "dashboard/gallery_dataset_view"; @@ -26,7 +25,6 @@ type Props = { export type DatasetType = APIDatasetType & { hasSegmentation: boolean, thumbnailURL: string, - formattedCreated: string, }; type State = { @@ -57,7 +55,6 @@ export function transformDatasets(datasets: Array): Array layer.category === "segmentation", ), thumbnailURL: createThumbnailURL(dataset.name, dataset.dataSource.dataLayers), - formattedCreated: moment(dataset.created).format("YYYY-MM-DD HH:mm"), }), ), "created", diff --git a/app/assets/javascripts/dashboard/explorative_annotations_view.js b/app/assets/javascripts/dashboard/explorative_annotations_view.js index abb04160c40..66ee6307959 100644 --- a/app/assets/javascripts/dashboard/explorative_annotations_view.js +++ b/app/assets/javascripts/dashboard/explorative_annotations_view.js @@ -27,6 +27,7 @@ import { } from "admin/admin_rest_api"; import type { RouterHistory } from "react-router-dom"; import { handleGenericError } from "libs/error_handling"; +import FormattedDate from "components/formatted_date"; const { Column } = Table; const { Search } = Input; @@ -417,6 +418,7 @@ class ExplorativeAnnotationsView extends React.PureComponent { title="Modification Date" dataIndex="modified" sorter={Utils.localeCompareBy(typeHint, "modified")} + render={modified => } /> { .entries() .map(([organization, datasets]) => // Sort each group of datasets - [ - organization, - datasets.sort(Utils.localeCompareBy(([]: DatasetType[]), "formattedCreated", false)), - ], + [organization, datasets.sort(Utils.localeCompareBy(([]: DatasetType[]), "created", false))], ) .value() .sort( // Sort groups by creation date of first dataset Utils.localeCompareBy( ([]: DatasetType[]), - ([_organization, datasets]) => datasets[0].formattedCreated, + ([_organization, datasets]) => datasets[0].created, false, ), ); diff --git a/app/assets/javascripts/libs/format_utils.js b/app/assets/javascripts/libs/format_utils.js index eadd30bccf7..e52f91b37b9 100644 --- a/app/assets/javascripts/libs/format_utils.js +++ b/app/assets/javascripts/libs/format_utils.js @@ -36,13 +36,6 @@ class FormatUtils { } return text; } - - /** - * Return current date and time - */ - static formatDate(date?: number = Date.now()): string { - return moment(date).format("YYYY-MM-DD HH:mm"); - } } export default FormatUtils; diff --git a/app/assets/javascripts/libs/utils.js b/app/assets/javascripts/libs/utils.js index c1edd6fedbf..2192ac9bbbb 100644 --- a/app/assets/javascripts/libs/utils.js +++ b/app/assets/javascripts/libs/utils.js @@ -503,6 +503,28 @@ const Utils = { // Big endian return [a, b, g, r]; }, + + async promiseAllWithErrors( + promises: Array>, + ): Promise<{ successes: Array, errors: Array }> { + const successOrErrorObjects = await Promise.all(promises.map(p => p.catch(error => error))); + return successOrErrorObjects.reduce( + ({ successes, errors }, successOrError) => { + if (successOrError instanceof Error) { + return { + successes, + errors: errors.concat([successOrError]), + }; + } else { + return { + successes: successes.concat([successOrError]), + errors, + }; + } + }, + { successes: [], errors: [] }, + ); + }, }; export default Utils; diff --git a/app/assets/javascripts/messages.js b/app/assets/javascripts/messages.js index 2bab4a91896..5096b54b95e 100644 --- a/app/assets/javascripts/messages.js +++ b/app/assets/javascripts/messages.js @@ -48,6 +48,8 @@ In order to restore the current window, a reload is necessary.`, "tracing.delete_tree_with_initial_node": "This tree contains the initial node. Do you really want to delete the whole tree?", "tracing.merged": "Merging successfully done", + "tracing.merged_with_redirect": + "Merging successfully done. You will be redirected to the new annotation.", "tracing.tree_viewer_no_cyclic_trees": "Cyclic trees are not supported by the abstract tree viewer.", "tracing.changed_move_value": "The move value was changed to: ", @@ -148,6 +150,9 @@ In order to restore the current window, a reload is necessary.`, "NML contains with same source and target id: Edge", "nml.tree_not_connected": "NML contains tree that is not fully connected: Tree with id", "nml.different_dataset": "Imported NML was originally for a different dataset.", + "merge.different_dataset": + "The merge cannot be executed, because the underlying datasets are not the same.", + "merge.volume_unsupported": "Merging is not supported for volume tracings.", "users.is_admin": "At least one of the selected users is an admin of this organization and already has access to all teams. No team assignments are necessary for this user.", "users.grant_admin_rights_title": "Do you really want to grant admin rights?", diff --git a/app/assets/javascripts/oxalis/api/api_latest.js b/app/assets/javascripts/oxalis/api/api_latest.js index 2635f7a854e..37c99d0d595 100644 --- a/app/assets/javascripts/oxalis/api/api_latest.js +++ b/app/assets/javascripts/oxalis/api/api_latest.js @@ -17,6 +17,7 @@ import { setActiveNodeAction, createCommentAction, deleteNodeAction, + deleteTreeAction, setNodeRadiusAction, setTreeNameAction, } from "oxalis/model/actions/skeletontracing_actions"; @@ -161,6 +162,11 @@ class TracingApi { Store.dispatch(deleteNodeAction(nodeId, treeId)); } + deleteTree(treeId: number) { + assertSkeleton(Store.getState().tracing); + Store.dispatch(deleteTreeAction(treeId)); + } + /** * Sets the comment for a node. * diff --git a/app/assets/javascripts/oxalis/model/actions/actions.js b/app/assets/javascripts/oxalis/model/actions/actions.js index efe0093a602..5c20ba9951d 100644 --- a/app/assets/javascripts/oxalis/model/actions/actions.js +++ b/app/assets/javascripts/oxalis/model/actions/actions.js @@ -10,6 +10,7 @@ import type { ViewModeActionType } from "oxalis/model/actions/view_mode_actions" import type { AnnotationActionTypes } from "oxalis/model/actions/annotation_actions"; import type { FlycamActionType } from "oxalis/model/actions/flycam_actions"; import type { UserActionType } from "oxalis/model/actions/user_actions"; +import type { UiActionType } from "oxalis/model/actions/ui_actions"; export type ActionType = | SkeletonTracingActionType @@ -20,7 +21,8 @@ export type ActionType = | ViewModeActionType | AnnotationActionTypes | FlycamActionType - | UserActionType; + | UserActionType + | UiActionType; export const wkReadyAction = () => ({ type: "WK_READY", diff --git a/app/assets/javascripts/oxalis/model/actions/ui_actions.js b/app/assets/javascripts/oxalis/model/actions/ui_actions.js new file mode 100644 index 00000000000..b1820bce0f4 --- /dev/null +++ b/app/assets/javascripts/oxalis/model/actions/ui_actions.js @@ -0,0 +1,16 @@ +// @flow +/* eslint-disable import/prefer-default-export */ + +type SetDropzoneModalVisibilityActionType = { + type: "SET_DROPZONE_MODAL_VISIBILITY_ACTION_TYPE", + visible: boolean, +}; + +export type UiActionType = SetDropzoneModalVisibilityActionType; + +export const setDropzoneModalVisibilityAction = ( + visible: boolean, +): SetDropzoneModalVisibilityActionType => ({ + type: "SET_DROPZONE_MODAL_VISIBILITY_ACTION_TYPE", + visible, +}); diff --git a/app/assets/javascripts/oxalis/model/helpers/nml_helpers.js b/app/assets/javascripts/oxalis/model/helpers/nml_helpers.js index d300e082ae9..7744e7c33f2 100644 --- a/app/assets/javascripts/oxalis/model/helpers/nml_helpers.js +++ b/app/assets/javascripts/oxalis/model/helpers/nml_helpers.js @@ -20,6 +20,7 @@ import type { } from "oxalis/store"; import type { BoundingBoxType } from "oxalis/constants"; import type { APIBuildInfoType } from "admin/api_flow_types"; +import { getMaximumGroupId } from "oxalis/model/reducers/skeletontracing_reducer_helpers"; // NML Defaults const DEFAULT_COLOR = [1, 0, 0]; @@ -363,8 +364,35 @@ function getEdgeHash(source: number, target: number) { return source < target ? `${source}-${target}` : `${target}-${source}`; } +function wrapInNewGroup( + originalTrees: TreeMapType, + originalTreeGroups: Array, + wrappingGroupName: string, +): [TreeMapType, Array] { + // It does not matter whether the group id is used in the active tracing, since + // this case will be handled during import, anyway. The group id just shouldn't clash + // with the nml itself. + const unusedGroupId = getMaximumGroupId(originalTreeGroups) + 1; + const trees = _.mapValues(originalTrees, tree => ({ + ...tree, + // Give parentless trees the new treeGroup as parent + groupId: tree.groupId || unusedGroupId, + })); + const treeGroups = [ + // Create a new tree group which holds the old ones + { + name: wrappingGroupName, + groupId: unusedGroupId, + children: originalTreeGroups, + }, + ]; + + return [trees, treeGroups]; +} + export function parseNml( nmlString: string, + wrappingGroupName?: ?string, ): Promise<{ trees: TreeMapType, treeGroups: Array }> { return new Promise((resolve, reject) => { const parser = new Saxophone(); @@ -547,7 +575,19 @@ export function parseNml( } }) .on("end", () => { - resolve({ trees, treeGroups }); + if (wrappingGroupName != null) { + const [wrappedTrees, wrappedTreeGroups] = wrapInNewGroup( + trees, + treeGroups, + wrappingGroupName, + ); + resolve({ + trees: wrappedTrees, + treeGroups: wrappedTreeGroups, + }); + } else { + resolve({ trees, treeGroups }); + } }) .on("error", reject); diff --git a/app/assets/javascripts/oxalis/model/reducers/ui_reducer.js b/app/assets/javascripts/oxalis/model/reducers/ui_reducer.js new file mode 100644 index 00000000000..1a4043adaad --- /dev/null +++ b/app/assets/javascripts/oxalis/model/reducers/ui_reducer.js @@ -0,0 +1,22 @@ +// @flow + +import update from "immutability-helper"; +import type { OxalisState } from "oxalis/store"; +import type { ActionType } from "oxalis/model/actions/actions"; + +function UiReducer(state: OxalisState, action: ActionType): OxalisState { + switch (action.type) { + case "SET_DROPZONE_MODAL_VISIBILITY_ACTION_TYPE": { + return update(state, { + uiInformation: { + showDropzoneModal: { $set: action.visible }, + }, + }); + } + + default: + return state; + } +} + +export default UiReducer; diff --git a/app/assets/javascripts/oxalis/store.js b/app/assets/javascripts/oxalis/store.js index 010371d32da..be52b7fc9b4 100644 --- a/app/assets/javascripts/oxalis/store.js +++ b/app/assets/javascripts/oxalis/store.js @@ -16,6 +16,7 @@ import FlycamReducer from "oxalis/model/reducers/flycam_reducer"; import ViewModeReducer from "oxalis/model/reducers/view_mode_reducer"; import AnnotationReducer from "oxalis/model/reducers/annotation_reducer"; import UserReducer from "oxalis/model/reducers/user_reducer"; +import UiReducer from "oxalis/model/reducers/ui_reducer"; import rootSaga from "oxalis/model/sagas/root_saga"; import overwriteActionMiddleware from "oxalis/model/helpers/overwrite_action_middleware"; import googleAnalyticsMiddleware from "oxalis/model/helpers/google_analytics_middleware"; @@ -299,6 +300,10 @@ export type ViewModeData = { +flight: ?FlightModeData, }; +type UiInformationType = { + +showDropzoneModal: boolean, +}; + export type OxalisState = { +datasetConfiguration: DatasetConfigurationType, +userConfiguration: UserConfigurationType, @@ -310,6 +315,7 @@ export type OxalisState = { +flycam: FlycamType, +viewModeData: ViewModeData, +activeUser: ?APIUserType, + +uiInformation: UiInformationType, }; export const defaultState: OxalisState = { @@ -439,6 +445,9 @@ export const defaultState: OxalisState = { flight: null, }, activeUser: null, + uiInformation: { + showDropzoneModal: false, + }, }; const sagaMiddleware = createSagaMiddleware(); @@ -455,6 +464,7 @@ const combinedReducers = reduceReducers( ViewModeReducer, AnnotationReducer, UserReducer, + UiReducer, ); const store = createStore( diff --git a/app/assets/javascripts/oxalis/view/action-bar/merge_modal_view.js b/app/assets/javascripts/oxalis/view/action-bar/merge_modal_view.js index abf97095c37..a654d951d08 100644 --- a/app/assets/javascripts/oxalis/view/action-bar/merge_modal_view.js +++ b/app/assets/javascripts/oxalis/view/action-bar/merge_modal_view.js @@ -1,7 +1,6 @@ // @flow import React, { PureComponent } from "react"; import { connect } from "react-redux"; -import { withRouter } from "react-router-dom"; import Toast from "libs/toast"; import Request from "libs/request"; import { Icon, Alert, Modal, Button, Select, Form, Spin, Checkbox, Tooltip } from "antd"; @@ -9,12 +8,13 @@ import messages from "messages"; import InputComponent from "oxalis/view/components/input_component"; import api from "oxalis/api/internal_api"; import type { OxalisState, TreeMapType, TreeGroupType } from "oxalis/store"; -import type { RouterHistory } from "react-router-dom"; import { getAnnotationInformation, getTracingForAnnotation } from "admin/admin_rest_api"; import { addTreesAndGroupsAction } from "oxalis/model/actions/skeletontracing_actions"; import { createTreeMapFromTreeArray } from "oxalis/model/reducers/skeletontracing_reducer_helpers"; import Utils from "libs/utils"; import type { APIAnnotationType } from "admin/api_flow_types"; +import Store from "oxalis/store"; +import { location } from "libs/window"; type ProjectInfoType = { id: string, @@ -29,7 +29,6 @@ type StateProps = { type Props = { isVisible: boolean, onOk: () => void, - history: RouterHistory, addTreesAndGroupsAction: (TreeMapType, Array) => void, } & StateProps; @@ -95,9 +94,10 @@ class MergeModalView extends PureComponent { async merge(url: string) { await api.tracing.save(); const annotation = await Request.receiveJSON(url); - Toast.success(messages["tracing.merged"]); + Toast.success(messages["tracing.merged_with_redirect"]); const redirectUrl = `/annotations/${annotation.typ}/${annotation.id}`; - this.props.history.push(redirectUrl); + await Utils.sleep(1500); + location.href = redirectUrl; } handleChangeMergeProject = (project: string) => { @@ -152,18 +152,23 @@ class MergeModalView extends PureComponent { }; async mergeAnnotationIntoActiveTracing(annotation: APIAnnotationType): Promise { + if (annotation.dataSetName !== Store.getState().dataset.name) { + Toast.error(messages["merge.different_dataset"]); + return; + } const tracing = await getTracingForAnnotation(annotation); - if (tracing.trees) { - const { trees, treeGroups } = tracing; - this.setState({ isUploading: true }); - // Wait for an animation frame so that the loading animation is kicked off - await Utils.animationFrame(); - this.props.addTreesAndGroupsAction(createTreeMapFromTreeArray(trees), treeGroups || []); - this.setState({ isUploading: false }); - Toast.success(messages["tracing.merged"]); - } else { - Toast.error("Merging is not supported for volume tracings."); + if (!tracing.trees) { + Toast.error(messages["merge.volume_unsupported"]); + return; } + const { trees, treeGroups } = tracing; + this.setState({ isUploading: true }); + // Wait for an animation frame so that the loading animation is kicked off + await Utils.animationFrame(); + this.props.addTreesAndGroupsAction(createTreeMapFromTreeArray(trees), treeGroups || []); + this.setState({ isUploading: false }); + Toast.success(messages["tracing.merged"]); + this.props.onOk(); } render() { @@ -180,7 +185,6 @@ class MergeModalView extends PureComponent { { +