diff --git a/CHANGELOG.unreleased.md b/CHANGELOG.unreleased.md index aa7a57e450a..8ec19dbf3f5 100644 --- a/CHANGELOG.unreleased.md +++ b/CHANGELOG.unreleased.md @@ -11,7 +11,7 @@ For upgrade instructions, please check the [migration guide](MIGRATIONS.released [Commits](https://github.com/scalableminds/webknossos/compare/21.05.1...HEAD) ### Added -- +- Added the option to hide the plane borders and crosshairs in the 3D viewport. Also, this setting was moved from the "Other" section of the user settings to the 3D viewport. Additionally, added a setting to hide the dataset bounding box in the 3D view. [#5440](https://github.com/scalableminds/webknossos/pull/5440) ### Changed - @@ -20,7 +20,7 @@ For upgrade instructions, please check the [migration guide](MIGRATIONS.released - ### Removed -- +- Removed the button to load or refresh the isosurface of the centered cell from the 3D view. Instead, this action can be triggered from the "Meshes" tab. [#5440](https://github.com/scalableminds/webknossos/pull/5440) ### Breaking Change - diff --git a/MIGRATIONS.unreleased.md b/MIGRATIONS.unreleased.md index b6c5f2c2e45..4c90f61ab72 100644 --- a/MIGRATIONS.unreleased.md +++ b/MIGRATIONS.unreleased.md @@ -9,4 +9,4 @@ User-facing changes are documented in the [changelog](CHANGELOG.released.md). - ### Postgres Evolutions: -- +- [071-adapt-td-view-display-planes.sql](conf/evolutions/071-adapt-td-view-display-planes.sql) diff --git a/app/controllers/ConfigurationController.scala b/app/controllers/ConfigurationController.scala index 2a3c91d79c2..e953ec1cc83 100755 --- a/app/controllers/ConfigurationController.scala +++ b/app/controllers/ConfigurationController.scala @@ -2,15 +2,14 @@ package controllers import com.mohiva.play.silhouette.api.Silhouette import com.scalableminds.util.accesscontext.GlobalAccessContext +import javax.inject.Inject import models.binary.{DataSetDAO, DataSetService} -import models.configuration.{DataSetConfigurationService, UserConfiguration} +import models.configuration.DataSetConfigurationService import models.user.UserService import oxalis.security.{URLSharing, WkEnv} import play.api.i18n.Messages -import play.api.libs.json.{JsObject, JsValue} -import play.api.libs.json.Json._ +import play.api.libs.json.{JsObject, JsValue, Json} import play.api.mvc.{Action, AnyContent, PlayBodyParsers} -import javax.inject.Inject import scala.concurrent.ExecutionContext @@ -22,19 +21,15 @@ class ConfigurationController @Inject()( sil: Silhouette[WkEnv])(implicit ec: ExecutionContext, bodyParsers: PlayBodyParsers) extends Controller { - def read: Action[AnyContent] = sil.UserAwareAction.async { implicit request => - request.identity.toFox.flatMap { user => - for { - userConfig <- user.userConfigurationStructured - } yield userConfig.configurationOrDefaults - }.getOrElse(UserConfiguration.default.configuration).map(configuration => Ok(toJson(configuration))) + def read: Action[AnyContent] = sil.UserAwareAction { implicit request => + val config = request.identity.map(_.userConfiguration).getOrElse(Json.obj()) + Ok(Json.toJson(config)) } def update: Action[JsValue] = sil.SecuredAction.async(parse.json(maxLength = 20480)) { implicit request => for { - jsConfiguration <- request.body.asOpt[JsObject] ?~> "user.configuration.invalid" - conf = jsConfiguration.fields.toMap - _ <- userService.updateUserConfiguration(request.identity, UserConfiguration(conf)) + configuration <- request.body.asOpt[JsObject] ?~> "user.configuration.invalid" + _ <- userService.updateUserConfiguration(request.identity, configuration) } yield JsonOk(Messages("user.configuration.updated")) } @@ -55,7 +50,7 @@ class ConfigurationController @Inject()( organizationName)(ctx) ) .getOrElse(Map.empty) - .map(configuration => Ok(toJson(configuration))) + .map(configuration => Ok(Json.toJson(configuration))) } def updateDataSetViewConfiguration(organizationName: String, dataSetName: String): Action[JsValue] = @@ -77,7 +72,7 @@ class ConfigurationController @Inject()( sil.SecuredAction.async { implicit request => dataSetConfigurationService .getCompleteAdminViewConfiguration(dataSetName, organizationName) - .map(configuration => Ok(toJson(configuration))) + .map(configuration => Ok(Json.toJson(configuration))) } def updateDataSetAdminViewConfiguration(organizationName: String, dataSetName: String): Action[JsValue] = diff --git a/app/controllers/InitialDataController.scala b/app/controllers/InitialDataController.scala index 84b108a1662..43e77781209 100644 --- a/app/controllers/InitialDataController.scala +++ b/app/controllers/InitialDataController.scala @@ -6,7 +6,6 @@ import com.scalableminds.util.tools.{Fox, FoxImplicits} import com.typesafe.scalalogging.LazyLogging import models.annotation.{TracingStore, TracingStoreDAO} import models.binary._ -import models.configuration.UserConfiguration import models.project.{Project, ProjectDAO} import models.task.{TaskType, TaskTypeDAO} import models.team._ @@ -87,7 +86,7 @@ Samplecountry "SCM", "Boy", System.currentTimeMillis(), - Json.toJson(UserConfiguration.default), + Json.obj(), userService.createLoginInfo(userId), isAdmin = true, isDatasetManager = true, diff --git a/app/models/configuration/UserConfiguration.scala b/app/models/configuration/UserConfiguration.scala deleted file mode 100644 index 6d41a7d7b7a..00000000000 --- a/app/models/configuration/UserConfiguration.scala +++ /dev/null @@ -1,44 +0,0 @@ -package models.configuration - -import play.api.libs.json.{JsBoolean, JsValue, _} - -case class UserConfiguration(configuration: Map[String, JsValue]) { - - def configurationOrDefaults: Map[String, JsValue] = - UserConfiguration.default.configuration ++ configuration - -} - -object UserConfiguration { - - implicit val userConfigurationFormat: OFormat[UserConfiguration] = Json.format[UserConfiguration] - - val default: UserConfiguration = UserConfiguration( - Map( - "moveValue" -> JsNumber(300), - "moveValue3d" -> JsNumber(300), - "rotateValue" -> JsNumber(0.01), - "crosshairSize" -> JsNumber(0.1), - "scaleValue" -> JsNumber(0.05), - "mouseRotateValue" -> JsNumber(0.004), - "clippingDistance" -> JsNumber(50), - "clippingDistanceArbitrary" -> JsNumber(64), - "dynamicSpaceDirection" -> JsBoolean(true), - "displayCrosshair" -> JsBoolean(true), - "scale" -> JsNumber(1), - "tdViewDisplayPlanes" -> JsBoolean(true), - "isosurfaceDisplay" -> JsBoolean(false), - "isosurfaceBBsize" -> JsNumber(1), - "isosurfaceResolution" -> JsNumber(80), - "newNodeNewTree" -> JsBoolean(false), - "highlightCommentedNodes" -> JsBoolean(false), - "keyboardDelay" -> JsNumber(200), - "particleSize" -> JsNumber(5), - "overrideNodeRadius" -> JsBoolean(true), - "sortTreesByName" -> JsBoolean(false), - "sortCommentsAsc" -> JsBoolean(true), - "sphericalCapRadius" -> JsNumber(140), - "layoutScaleValue" -> JsNumber(1), - "renderComments" -> JsBoolean(false) - )) -} diff --git a/app/models/user/User.scala b/app/models/user/User.scala index 8b6f4c9ba57..92964d1245b 100755 --- a/app/models/user/User.scala +++ b/app/models/user/User.scala @@ -2,12 +2,12 @@ package models.user import com.mohiva.play.silhouette.api.{Identity, LoginInfo} import com.scalableminds.util.accesscontext._ -import com.scalableminds.util.tools.{Fox, FoxImplicits, JsonHelper} +import com.scalableminds.util.tools.JsonHelper.parseJsonToFox +import com.scalableminds.util.tools.{Fox, FoxImplicits} import com.scalableminds.webknossos.datastore.models.datasource.DataSetViewConfiguration.DataSetViewConfiguration import com.scalableminds.webknossos.datastore.models.datasource.LayerViewConfiguration.LayerViewConfiguration import com.scalableminds.webknossos.schema.Tables._ import javax.inject.Inject -import models.configuration.UserConfiguration import models.team._ import play.api.libs.json._ import slick.jdbc.PostgresProfile.api._ @@ -28,7 +28,7 @@ case class User( firstName: String, lastName: String, lastActivity: Long = System.currentTimeMillis(), - userConfiguration: JsValue, + userConfiguration: JsObject, loginInfo: LoginInfo, isAdmin: Boolean, isDatasetManager: Boolean, @@ -48,9 +48,6 @@ case class User( val abreviatedName: String = (firstName.take(1) + lastName).toLowerCase.replace(" ", "_") - def userConfigurationStructured: Fox[UserConfiguration] = - JsonHelper.jsResultToFox(userConfiguration.validate[Map[String, JsValue]]).map(UserConfiguration(_)) - def isAdminOf(_organization: ObjectId): Boolean = isAdmin && _organization == this._organization @@ -67,7 +64,9 @@ class UserDAO @Inject()(sqlClient: SQLClient)(implicit ec: ExecutionContext) def isDeletedColumn(x: Users): Rep[Boolean] = x.isdeleted def parse(r: UsersRow): Fox[User] = - Fox.successful( + for { + userConfiguration <- parseJsonToFox[JsObject](r.userconfiguration) + } yield { User( ObjectId(r._Id), ObjectId(r._Multiuser), @@ -75,7 +74,7 @@ class UserDAO @Inject()(sqlClient: SQLClient)(implicit ec: ExecutionContext) r.firstname, r.lastname, r.lastactivity.getTime, - Json.parse(r.userconfiguration), + userConfiguration, LoginInfo(User.default_login_provider_id, r._Id), r.isadmin, r.isdatasetmanager, @@ -84,7 +83,8 @@ class UserDAO @Inject()(sqlClient: SQLClient)(implicit ec: ExecutionContext) r.created.getTime, r.lasttasktypeid.map(ObjectId(_)), r.isdeleted - )) + ) + } override def readAccessQ(requestingUserId: ObjectId) = s"""(_id in (select _user from webknossos.user_team_roles where _team in (select _team from webknossos.user_team_roles where _user = '$requestingUserId' and isTeamManager))) @@ -203,12 +203,11 @@ class UserDAO @Inject()(sqlClient: SQLClient)(implicit ec: ExecutionContext) def updateLastActivity(userId: ObjectId, lastActivity: Long)(implicit ctx: DBAccessContext): Fox[Unit] = updateTimestampCol(userId, _.lastactivity, new java.sql.Timestamp(lastActivity)) - def updateUserConfiguration(userId: ObjectId, userConfiguration: UserConfiguration)( - implicit ctx: DBAccessContext): Fox[Unit] = + def updateUserConfiguration(userId: ObjectId, userConfiguration: JsObject)(implicit ctx: DBAccessContext): Fox[Unit] = for { _ <- assertUpdateAccess(userId) _ <- run(sqlu"""update webknossos.users - set userConfiguration = '#${sanitize(Json.toJson(userConfiguration.configuration).toString)}' + set userConfiguration = '#${sanitize(Json.toJson(userConfiguration).toString)}' where _id = $userId""") } yield () diff --git a/app/models/user/UserService.scala b/app/models/user/UserService.scala index 99522025a4c..420de3bffaf 100755 --- a/app/models/user/UserService.scala +++ b/app/models/user/UserService.scala @@ -12,7 +12,6 @@ import com.scalableminds.webknossos.datastore.models.datasource.DataSetViewConfi import com.scalableminds.webknossos.datastore.models.datasource.LayerViewConfiguration.LayerViewConfiguration import com.typesafe.scalalogging.LazyLogging import models.binary.DataSetDAO -import models.configuration.UserConfiguration import models.team._ import oxalis.mail.{DefaultMails, Send} import oxalis.security.TokenDAO @@ -105,7 +104,7 @@ class UserService @Inject()(conf: WkConf, firstName, lastName, System.currentTimeMillis(), - Json.toJson(UserConfiguration.default), + Json.obj(), LoginInfo(CredentialsProvider.ID, newUserId.id), isAdmin, isDatasetManager = false, @@ -201,7 +200,7 @@ class UserService @Inject()(conf: WkConf, _ <- multiUserDAO.updatePasswordInfo(user._multiUser, passwordInfo)(GlobalAccessContext) } yield passwordInfo - def updateUserConfiguration(user: User, configuration: UserConfiguration)(implicit ctx: DBAccessContext): Fox[Unit] = + def updateUserConfiguration(user: User, configuration: JsObject)(implicit ctx: DBAccessContext): Fox[Unit] = userDAO.updateUserConfiguration(user._id, configuration).map { result => userCache.invalidateUser(user._id) result diff --git a/conf/evolutions/071-adapt-td-view-display-planes.sql b/conf/evolutions/071-adapt-td-view-display-planes.sql new file mode 100644 index 00000000000..11c17b24dea --- /dev/null +++ b/conf/evolutions/071-adapt-td-view-display-planes.sql @@ -0,0 +1,44 @@ +-- https://github.com/scalableminds/webknossos/pull/5440 + +START TRANSACTION; + +-- Update tdViewDisplayPlanes user configuration setting based on its previous value +-- A previous tdViewDisplayPlanes value of true corresponds to the "DATA" enum value +-- and a value of false corresponds to the "WIREFRAME" enum value + +-- For the user configuration +UPDATE webknossos.users +SET userconfiguration = CASE +WHEN jsonb_typeof(userconfiguration->'tdViewDisplayPlanes') = 'boolean' AND (userconfiguration->>'tdViewDisplayPlanes')::boolean IS TRUE THEN jsonb_set( + userconfiguration, + array['tdViewDisplayPlanes'], + to_jsonb('DATA'::text)) +ELSE jsonb_set( + userconfiguration, + array['tdViewDisplayPlanes'], + to_jsonb('WIREFRAME'::text)) +END +WHERE userconfiguration ? 'tdViewDisplayPlanes'; + +-- For the recommended configuration in task types +UPDATE webknossos.tasktypes +SET recommendedconfiguration = CASE +WHEN jsonb_typeof(recommendedconfiguration->'tdViewDisplayPlanes') = 'boolean' AND (recommendedconfiguration->>'tdViewDisplayPlanes')::boolean IS TRUE THEN jsonb_set( + recommendedconfiguration, + array['tdViewDisplayPlanes'], + to_jsonb('DATA'::text)) +ELSE jsonb_set( + recommendedconfiguration, + array['tdViewDisplayPlanes'], + to_jsonb('WIREFRAME'::text)) +END +WHERE recommendedconfiguration ? 'tdViewDisplayPlanes'; + +-- Remove unused configuration key of user configuration +UPDATE webknossos.users +SET userconfiguration = userconfiguration - 'configuration' +WHERE userconfiguration ? 'configuration'; + +UPDATE webknossos.releaseInformation SET schemaVersion = 71; + +COMMIT TRANSACTION; diff --git a/conf/evolutions/reversions/071-adapt-td-view-display-planes.sql b/conf/evolutions/reversions/071-adapt-td-view-display-planes.sql new file mode 100644 index 00000000000..ee6da3e1eae --- /dev/null +++ b/conf/evolutions/reversions/071-adapt-td-view-display-planes.sql @@ -0,0 +1,37 @@ +START TRANSACTION; + +-- Update tdViewDisplayPlanes user configuration setting based on its previous value +-- A previous tdViewDisplayPlanes value of "DATA" corresponds to true +-- and a value of "WIREFRAME" or "NONE" corresponds to false + +-- For the user configuration +UPDATE webknossos.users +SET userconfiguration = CASE +WHEN jsonb_typeof(userconfiguration->'tdViewDisplayPlanes') = 'string' AND userconfiguration->>'tdViewDisplayPlanes' = 'DATA' THEN jsonb_set( + userconfiguration, + array['tdViewDisplayPlanes'], + to_jsonb('true'::boolean)) +ELSE jsonb_set( + userconfiguration, + array['tdViewDisplayPlanes'], + to_jsonb('false'::boolean)) +END +WHERE userconfiguration ? 'tdViewDisplayPlanes'; + +-- For the recommended configuration in task types +UPDATE webknossos.tasktypes +SET recommendedconfiguration = CASE +WHEN jsonb_typeof(recommendedconfiguration->'tdViewDisplayPlanes') = 'string' AND recommendedconfiguration->>'tdViewDisplayPlanes' = 'DATA' THEN jsonb_set( + recommendedconfiguration, + array['tdViewDisplayPlanes'], + to_jsonb('true'::boolean)) +ELSE jsonb_set( + recommendedconfiguration, + array['tdViewDisplayPlanes'], + to_jsonb('false'::boolean)) +END +WHERE recommendedconfiguration ? 'tdViewDisplayPlanes'; + +UPDATE webknossos.releaseInformation SET schemaVersion = 70; + +COMMIT TRANSACTION; diff --git a/frontend/javascripts/admin/tasktype/recommended_configuration_view.js b/frontend/javascripts/admin/tasktype/recommended_configuration_view.js index bba8459c200..cce24aa4911 100644 --- a/frontend/javascripts/admin/tasktype/recommended_configuration_view.js +++ b/frontend/javascripts/admin/tasktype/recommended_configuration_view.js @@ -9,6 +9,7 @@ import { jsonEditStyle } from "dashboard/dataset/helper_components"; import { jsonStringify } from "libs/utils"; import { settings } from "messages"; import { validateUserSettingsJSON } from "types/validation"; +import { TDViewDisplayModeEnum } from "oxalis/constants"; const FormItem = Form.Item; const { Panel } = Collapse; @@ -20,7 +21,8 @@ const recommendedConfigByCategory = { displayScalebars: false, newNodeNewTree: false, centerNewNode: true, - tdViewDisplayPlanes: false, + tdViewDisplayPlanes: TDViewDisplayModeEnum.WIREFRAME, + tdViewDisplayDatasetBorders: true, }, all: { dynamicSpaceDirection: true, @@ -70,6 +72,7 @@ export const settingComments = { clippingDistanceArbitrary: "flight/oblique mode", moveValue3d: "flight/oblique mode", loadingStrategy: "BEST_QUALITY_FIRST or PROGRESSIVE_QUALITY", + tdViewDisplayPlanes: Object.values(TDViewDisplayModeEnum).join(" or "), }; const columns = [ diff --git a/frontend/javascripts/messages.js b/frontend/javascripts/messages.js index 317b80864b0..dc71f7d5c41 100644 --- a/frontend/javascripts/messages.js +++ b/frontend/javascripts/messages.js @@ -16,7 +16,8 @@ export const settings = { nodeRadius: "Node Radius", overrideNodeRadius: "Override Node Radius", particleSize: "Particle Size", - tdViewDisplayPlanes: "Display Planes in 3D View", + tdViewDisplayPlanes: "Plane Display Mode in 3D View", + tdViewDisplayDatasetBorders: "Display Dataset Borders in 3D View", fourBit: "4 Bit", interpolation: "Interpolation", quality: "Quality", diff --git a/frontend/javascripts/oxalis/api/api_latest.js b/frontend/javascripts/oxalis/api/api_latest.js index 46e0699de92..5a2c22ca16a 100644 --- a/frontend/javascripts/oxalis/api/api_latest.js +++ b/frontend/javascripts/oxalis/api/api_latest.js @@ -16,6 +16,7 @@ import Constants, { type Vector4, type VolumeTool, VolumeToolEnum, + TDViewDisplayModeEnum, } from "oxalis/constants"; import { InputKeyboardNoLoop } from "libs/input"; import { PullQueueConstants } from "oxalis/model/bucket_data_handling/pullqueue"; @@ -1419,6 +1420,7 @@ class UserApi { - displayScalebars - scale - tdViewDisplayPlanes + - tdViewDisplayDatasetBorders - newNodeNewTree - centerNewNode - highlightCommentedNodes @@ -1434,7 +1436,14 @@ class UserApi { * const keyboardDelay = api.user.getConfiguration("keyboardDelay"); */ getConfiguration(key: $Keys) { - return Store.getState().userConfiguration[key]; + const value = Store.getState().userConfiguration[key]; + + // Backwards compatibility + if (key === "tdViewDisplayPlanes") { + return value === TDViewDisplayModeEnum.DATA; + } + + return value; } /** @@ -1445,6 +1454,11 @@ class UserApi { * api.user.setConfiguration("keyboardDelay", 20); */ setConfiguration(key: $Keys, value: any) { + // Backwards compatibility + if (key === "tdViewDisplayPlanes") { + value = value ? TDViewDisplayModeEnum.DATA : TDViewDisplayModeEnum.WIREFRAME; + } + Store.dispatch(updateUserSettingAction(key, value)); } } diff --git a/frontend/javascripts/oxalis/api/api_v2.js b/frontend/javascripts/oxalis/api/api_v2.js index 5387b0e1072..c3624a7ddcc 100644 --- a/frontend/javascripts/oxalis/api/api_v2.js +++ b/frontend/javascripts/oxalis/api/api_v2.js @@ -46,7 +46,12 @@ import { overwriteAction } from "oxalis/model/helpers/overwrite_action_middlewar import Toast from "libs/toast"; import window, { location } from "libs/window"; import * as Utils from "libs/utils"; -import { ControlModeEnum, OrthoViews, VolumeToolEnum } from "oxalis/constants"; +import { + ControlModeEnum, + OrthoViews, + VolumeToolEnum, + TDViewDisplayModeEnum, +} from "oxalis/constants"; import { setPositionAction, setRotationAction } from "oxalis/model/actions/flycam_actions"; import { getPosition, getRotation } from "oxalis/model/accessors/flycam_accessor"; import TWEEN from "tween.js"; @@ -744,7 +749,14 @@ class UserApi { * const keyboardDelay = api.user.getConfiguration("keyboardDelay"); */ getConfiguration(key: $Keys) { - return Store.getState().userConfiguration[key]; + const value = Store.getState().userConfiguration[key]; + + // Backwards compatibility + if (key === "tdViewDisplayPlanes") { + return value === TDViewDisplayModeEnum.DATA; + } + + return value; } /** @@ -755,6 +767,11 @@ class UserApi { * api.user.setConfiguration("keyboardDelay", 20); */ setConfiguration(key: $Keys, value: any) { + // Backwards compatibility + if (key === "tdViewDisplayPlanes") { + value = value ? TDViewDisplayModeEnum.DATA : TDViewDisplayModeEnum.WIREFRAME; + } + Store.dispatch(updateUserSettingAction(key, value)); } } diff --git a/frontend/javascripts/oxalis/constants.js b/frontend/javascripts/oxalis/constants.js index ad7ba5cb70f..21ea70ea28a 100644 --- a/frontend/javascripts/oxalis/constants.js +++ b/frontend/javascripts/oxalis/constants.js @@ -151,6 +151,14 @@ export const OverwriteModeEnum = { export type OverwriteMode = $Keys; +export const TDViewDisplayModeEnum = { + NONE: "NONE", + WIREFRAME: "WIREFRAME", + DATA: "DATA", +}; + +export type TDViewDisplayMode = $Keys; + export const NODE_ID_REF_REGEX = /#([0-9]+)/g; export const POSITION_REF_REGEX = /#\(([0-9]+,[0-9]+,[0-9]+)\)/g; diff --git a/frontend/javascripts/oxalis/controller/scene_controller.js b/frontend/javascripts/oxalis/controller/scene_controller.js index b543836b9b2..3cf4e2b9b0b 100644 --- a/frontend/javascripts/oxalis/controller/scene_controller.js +++ b/frontend/javascripts/oxalis/controller/scene_controller.js @@ -37,6 +37,7 @@ import constants, { OrthoViewValuesWithoutTDView, OrthoViews, type Vector3, + TDViewDisplayModeEnum, } from "oxalis/constants"; import window from "libs/window"; @@ -339,6 +340,12 @@ class SceneController { // though they are all looking at the same scene, some // things have to be changed for each cam. + const { tdViewDisplayPlanes, tdViewDisplayDatasetBorders } = Store.getState().userConfiguration; + + // Only set the visibility of the dataset bounding box for the TDView. + // This has to happen before updateForCam is called as otherwise cross section visibility + // might be changed unintentionally. + this.datasetBoundingBox.setVisibility(id !== OrthoViews.TDView || tdViewDisplayDatasetBorders); this.datasetBoundingBox.updateForCam(id); this.userBoundingBoxes.forEach(bbCube => bbCube.updateForCam(id)); Utils.__guard__(this.taskBoundingBox, x => x.updateForCam(id)); @@ -362,13 +369,14 @@ class SceneController { } } } else { - const { tdViewDisplayPlanes } = Store.getState().userConfiguration; for (const planeId of OrthoViewValuesWithoutTDView) { const pos = getPosition(Store.getState().flycam); this.planes[planeId].setPosition(pos); this.planes[planeId].setGrayCrosshairColor(); - this.planes[planeId].setVisible(true); - this.planes[planeId].plane.visible = this.isPlaneVisible[planeId] && tdViewDisplayPlanes; + this.planes[planeId].setVisible( + tdViewDisplayPlanes !== TDViewDisplayModeEnum.NONE, + this.isPlaneVisible[planeId] && tdViewDisplayPlanes === TDViewDisplayModeEnum.DATA, + ); } } }; diff --git a/frontend/javascripts/oxalis/default_state.js b/frontend/javascripts/oxalis/default_state.js index 5e957fc8594..433cf0f876d 100644 --- a/frontend/javascripts/oxalis/default_state.js +++ b/frontend/javascripts/oxalis/default_state.js @@ -1,7 +1,12 @@ // @flow import type { OxalisState } from "oxalis/store"; -import Constants, { ControlModeEnum, OrthoViews, OverwriteModeEnum } from "oxalis/constants"; +import Constants, { + ControlModeEnum, + OrthoViews, + OverwriteModeEnum, + TDViewDisplayModeEnum, +} from "oxalis/constants"; import { document } from "libs/window"; const defaultViewportRect = { @@ -72,7 +77,8 @@ const defaultState: OxalisState = { sortCommentsAsc: true, sortTreesByName: false, sphericalCapRadius: Constants.DEFAULT_SPHERICAL_CAP_RADIUS, - tdViewDisplayPlanes: true, + tdViewDisplayPlanes: TDViewDisplayModeEnum.DATA, + tdViewDisplayDatasetBorders: true, gpuMemoryFactor: Constants.DEFAULT_GPU_MEMORY_FACTOR, overwriteMode: OverwriteModeEnum.OVERWRITE_ALL, }, diff --git a/frontend/javascripts/oxalis/geometries/cube.js b/frontend/javascripts/oxalis/geometries/cube.js index c3f9b3fd764..eb4bb35df11 100644 --- a/frontend/javascripts/oxalis/geometries/cube.js +++ b/frontend/javascripts/oxalis/geometries/cube.js @@ -3,7 +3,6 @@ * @flow */ -import BackboneEvents from "backbone-events-standalone"; import * as THREE from "three"; import _ from "lodash"; @@ -37,9 +36,6 @@ class Cube { initialized: boolean; visible: boolean; - // Copied from backbone events (TODO: handle this better) - listenTo: Function; - constructor(properties: Properties) { // min/max should denote a half-open interval. this.min = properties.min || [0, 0, 0]; @@ -48,8 +44,6 @@ class Cube { const color = properties.color || 0x000000; this.showCrossSections = properties.showCrossSections || false; - _.extend(this, BackboneEvents); - this.initialized = false; this.visible = true; diff --git a/frontend/javascripts/oxalis/geometries/plane.js b/frontend/javascripts/oxalis/geometries/plane.js index eb68fd88658..e82ae650ccb 100644 --- a/frontend/javascripts/oxalis/geometries/plane.js +++ b/frontend/javascripts/oxalis/geometries/plane.js @@ -178,11 +178,11 @@ class Plane { } }; - setVisible = (visible: boolean): void => { - this.plane.visible = visible; - this.TDViewBorders.visible = visible; - this.crosshair[0].visible = visible && this.displayCrosshair; - this.crosshair[1].visible = visible && this.displayCrosshair; + setVisible = (isVisible: boolean, isDataVisible?: boolean): void => { + this.plane.visible = isDataVisible != null ? isDataVisible : isVisible; + this.TDViewBorders.visible = isVisible; + this.crosshair[0].visible = isVisible && this.displayCrosshair; + this.crosshair[1].visible = isVisible && this.displayCrosshair; }; getMeshes = () => [this.plane, this.TDViewBorders, this.crosshair[0], this.crosshair[1]]; diff --git a/frontend/javascripts/oxalis/store.js b/frontend/javascripts/oxalis/store.js index 046fd4cb9b0..a09e230bb18 100644 --- a/frontend/javascripts/oxalis/store.js +++ b/frontend/javascripts/oxalis/store.js @@ -36,6 +36,7 @@ import { type OverwriteMode, type ControlMode, ControlModeEnum, + type TDViewDisplayMode, type ViewMode, type OrthoView, type Rect, @@ -306,7 +307,8 @@ export type UserConfiguration = {| +sortCommentsAsc: boolean, +sortTreesByName: boolean, +sphericalCapRadius: number, - +tdViewDisplayPlanes: boolean, + +tdViewDisplayPlanes: TDViewDisplayMode, + +tdViewDisplayDatasetBorders: boolean, +gpuMemoryFactor: number, // For volume (and hybrid) annotations, this mode specifies // how volume annotations overwrite existing voxels. diff --git a/frontend/javascripts/oxalis/view/settings/user_settings_view.js b/frontend/javascripts/oxalis/view/settings/user_settings_view.js index 565363eb2fe..398d367fb50 100644 --- a/frontend/javascripts/oxalis/view/settings/user_settings_view.js +++ b/frontend/javascripts/oxalis/view/settings/user_settings_view.js @@ -475,13 +475,6 @@ class UserSettingsView extends PureComponent { - - - ); } diff --git a/frontend/javascripts/oxalis/view/td_view_controls.js b/frontend/javascripts/oxalis/view/td_view_controls.js index 0c91519404a..af9faa94bd4 100644 --- a/frontend/javascripts/oxalis/view/td_view_controls.js +++ b/frontend/javascripts/oxalis/view/td_view_controls.js @@ -1,26 +1,82 @@ // @flow -import { Button, Tooltip } from "antd"; -import { ReloadOutlined } from "@ant-design/icons"; +import { Button, Radio, Tooltip, Menu, Dropdown, Col, Row, Switch } from "antd"; +import { + StopOutlined, + BorderInnerOutlined, + BorderOuterOutlined, + SettingOutlined, +} from "@ant-design/icons"; import * as React from "react"; import { connect } from "react-redux"; -import type { OxalisState, VolumeTracing } from "oxalis/store"; +import type { Dispatch } from "redux"; +import type { OxalisState } from "oxalis/store"; +import { TDViewDisplayModeEnum, type TDViewDisplayMode } from "oxalis/constants"; +import { updateUserSettingAction } from "oxalis/model/actions/settings_actions"; import api from "oxalis/api/internal_api"; type Props = {| - isRefreshingIsosurfaces: boolean, - volumeTracing: ?VolumeTracing, + tdViewDisplayPlanes: TDViewDisplayMode, + tdViewDisplayDatasetBorders: boolean, + onChangeTdViewDisplayPlanes: (SyntheticInputEvent<>) => void, + onChangeTdViewDisplayDatasetBorders: boolean => void, |}; -function TDViewControls({ isRefreshingIsosurfaces, volumeTracing }: Props) { - let refreshIsosurfaceTooltip = "Load Isosurface of centered cell from segmentation layer."; - if (volumeTracing != null) { - if (volumeTracing.fallbackLayer != null) { - refreshIsosurfaceTooltip = "Load Isosurface of centered cell from fallback annotation layer"; - } else { - refreshIsosurfaceTooltip = "Reload annotated Isosurfaces to newest version."; - } - } +function TDViewControls({ + tdViewDisplayPlanes, + tdViewDisplayDatasetBorders, + onChangeTdViewDisplayPlanes, + onChangeTdViewDisplayDatasetBorders, +}: Props) { + const settingsMenu = ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ); + return (
- - +
); } -export function mapStateToProps(state: OxalisState): Props { +function mapStateToProps(state: OxalisState) { + return { + tdViewDisplayPlanes: state.userConfiguration.tdViewDisplayPlanes, + tdViewDisplayDatasetBorders: state.userConfiguration.tdViewDisplayDatasetBorders, + }; +} + +function mapDispatchToProps(dispatch: Dispatch<*>) { return { - isRefreshingIsosurfaces: state.uiInformation.isRefreshingIsosurfaces, - volumeTracing: state.tracing.volume, + onChangeTdViewDisplayPlanes(evt: SyntheticInputEvent<>) { + const tdViewDisplayPlanes: $Values = evt.target.value; + dispatch(updateUserSettingAction("tdViewDisplayPlanes", tdViewDisplayPlanes)); + }, + onChangeTdViewDisplayDatasetBorders(tdViewDisplayDatasetBorders: boolean) { + dispatch(updateUserSettingAction("tdViewDisplayDatasetBorders", tdViewDisplayDatasetBorders)); + }, }; } -export default connect(mapStateToProps)(TDViewControls); +export default connect( + mapStateToProps, + mapDispatchToProps, +)(TDViewControls); diff --git a/frontend/javascripts/types/schemas/user_settings.schema.js b/frontend/javascripts/types/schemas/user_settings.schema.js index 619f468a7c1..31c2196bdec 100644 --- a/frontend/javascripts/types/schemas/user_settings.schema.js +++ b/frontend/javascripts/types/schemas/user_settings.schema.js @@ -1,5 +1,5 @@ // @flow -import { OverwriteModeEnum } from "oxalis/constants"; +import { OverwriteModeEnum, TDViewDisplayModeEnum } from "oxalis/constants"; import { baseDatasetViewConfiguration } from "types/schemas/dataset_view_configuration.schema"; export const userSettings = { @@ -26,7 +26,11 @@ export const userSettings = { sortCommentsAsc: { type: "boolean" }, sortTreesByName: { type: "boolean" }, sphericalCapRadius: { type: "number", minimum: 50, maximum: 500 }, - tdViewDisplayPlanes: { type: "boolean" }, + tdViewDisplayPlanes: { + type: "string", + enum: Object.values(TDViewDisplayModeEnum), + }, + tdViewDisplayDatasetBorders: { type: "boolean" }, hideTreeRemovalWarning: { type: "boolean" }, brushSize: { type: "number", minimum: 1, maximum: 300 }, autoSaveLayouts: { type: "boolean" }, @@ -34,7 +38,7 @@ export const userSettings = { segmentationOpacity: { type: "number", minimum: 0, maximum: 100 }, overwriteMode: { type: "string", - enum: [OverwriteModeEnum.OVERWRITE_ALL, OverwriteModeEnum.OVERWRITE_EMPTY], + enum: Object.values(OverwriteModeEnum), }, ...baseDatasetViewConfiguration, }; diff --git a/frontend/stylesheets/trace_view/_tracing_view.less b/frontend/stylesheets/trace_view/_tracing_view.less index 91d51c84f17..33b9857a35e 100644 --- a/frontend/stylesheets/trace_view/_tracing_view.less +++ b/frontend/stylesheets/trace_view/_tracing_view.less @@ -60,9 +60,10 @@ width: 100%; .ant-btn { - width: 60px; line-height: 23px; vertical-align: baseline; + padding-left: 10px; + padding-right: 10px; & > .colored-dot { display: inline-block; width: 10px; @@ -121,13 +122,12 @@ .margin-bottom { margin-bottom: 10px; } - - .setting-label { - word-wrap: break-word; - white-space: normal; - margin-right: 5px; - display: inline-block; - } +} +.setting-label { + word-wrap: break-word; + white-space: normal; + margin-right: 5px; + display: inline-block; } .tracing-right-menu { min-width: 450px; diff --git a/tools/postgres/schema.sql b/tools/postgres/schema.sql index d49228998b3..945859464f5 100644 --- a/tools/postgres/schema.sql +++ b/tools/postgres/schema.sql @@ -21,7 +21,7 @@ START TRANSACTION; CREATE TABLE webknossos.releaseInformation ( schemaVersion BIGINT NOT NULL ); -INSERT INTO webknossos.releaseInformation(schemaVersion) values(70); +INSERT INTO webknossos.releaseInformation(schemaVersion) values(71); COMMIT TRANSACTION;