Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Dark theme #5407

Merged
merged 52 commits into from
May 3, 2021
Merged
Show file tree
Hide file tree
Changes from 40 commits
Commits
Show all changes
52 commits
Select commit Hold shift + click to select a range
1655e09
poc dark/light mode switch
normanrz Apr 17, 2021
d97dee7
tweaks
normanrz Apr 17, 2021
a6afa76
tweaks
normanrz Apr 17, 2021
96ae301
lint
normanrz Apr 17, 2021
95d84ab
tweaks
normanrz Apr 18, 2021
e9c0cb1
tweaks
normanrz Apr 18, 2021
0f79958
version restore
normanrz Apr 18, 2021
03b97e5
tweaks
normanrz Apr 18, 2021
5234ca1
tweaks
normanrz Apr 18, 2021
1959a3a
store theme setting in postgres
fm3 Apr 19, 2021
2121fa3
adapt test db + snapshots to selectedTheme property
fm3 Apr 19, 2021
ea8f373
Pass selected theme to main html template
fm3 Apr 19, 2021
3deea6c
auto-detect theme by browser and read from backend if available
normanrz Apr 19, 2021
9d8bf14
merged
normanrz Apr 21, 2021
c84f3b2
tweaks
normanrz Apr 21, 2021
076f4b3
Merge remote-tracking branch 'origin/master' into dark-theme
normanrz Apr 21, 2021
3b3d4e6
Merge remote-tracking branch 'origin/master' into dark-theme
normanrz Apr 21, 2021
42a240e
more robust dark/light toggle
normanrz Apr 22, 2021
32d2b4a
dataset_settins_view.js
normanrz Apr 22, 2021
aaa4d4e
nml_upload_zone_container.js
normanrz Apr 22, 2021
6261ab5
styling
normanrz Apr 22, 2021
e651eb0
share_modal_view.js + meshes_view.js
normanrz Apr 22, 2021
7fc416d
Merge remote-tracking branch 'origin/master' into dark-theme
normanrz Apr 22, 2021
2e60d8d
styling
normanrz Apr 24, 2021
7aa5873
Merge remote-tracking branch 'origin/master' into dark-theme
normanrz Apr 26, 2021
491133e
dataset_upload_view.js
normanrz Apr 26, 2021
38c27f9
dataset_import_view.js
normanrz Apr 26, 2021
6c3e224
util classes to css vars
normanrz Apr 26, 2021
2e53ba7
persisting the selected theme from the navbar
normanrz Apr 26, 2021
7975f09
changelog
normanrz Apr 26, 2021
8c84691
tweaks
normanrz Apr 26, 2021
821c8d3
Merge branch 'master' into dark-theme
normanrz Apr 27, 2021
dfac310
beta flag
normanrz Apr 28, 2021
4cbdf9d
improve colors for welcome-actions
philippotto Apr 29, 2021
5a63a33
improve colors in dataset table; alert box and setting sliders
philippotto Apr 29, 2021
eeddb7d
fix group-icon in root node of tree tab
philippotto Apr 29, 2021
9e50b21
improve loading screen colors
philippotto Apr 29, 2021
0d80b2a
use correct sidebar-icon in tab title if dark mode is enabled
philippotto Apr 29, 2021
aa3a87f
fix space before layout-entry in actions menu
philippotto Apr 29, 2021
3fcb1d6
improve border color in dataset-upload-view
philippotto Apr 29, 2021
16a128f
switch bright/dark sidebar buttons via pure CSS
philippotto Apr 29, 2021
104805e
Merge branch 'master' into dark-theme
normanrz Apr 29, 2021
668f191
styling
normanrz Apr 30, 2021
d76f8eb
tracing_layout_view.js
normanrz Apr 30, 2021
681e502
styling
normanrz Apr 30, 2021
c2d28e3
store current theme in Store
normanrz Apr 30, 2021
cc80357
lint
normanrz Apr 30, 2021
612b6c1
fix tests
normanrz May 2, 2021
eca9772
no ?.
normanrz May 2, 2021
b718da3
fix tests
normanrz May 2, 2021
f41bf15
re-add titillium css, oops
normanrz May 2, 2021
72fcba4
fix style flickering when switching themes
normanrz May 2, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.unreleased.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ For upgrade instructions, please check the [migration guide](MIGRATIONS.released
- Added a screenshot of the 3D view when using the screenshot functionality in the tracing view. [#5324](https://github.com/scalableminds/webknossos/pull/5324)
- Tiff export jobs of volume annotations now show the link back to the annotation in the jobs list. [#5378](https://github.com/scalableminds/webknossos/pull/5378)
- Added support for flight- and oblique-mode when having non-uint8 dataset layers. [#5396](https://github.com/scalableminds/webknossos/pull/5396)
- Added a dark theme for webKnossos. [#5407](https://github.com/scalableminds/webknossos/pull/5407)

### Changed
- webKnossos is now part of the [image.sc support community](https://forum.image.sc/tag/webknossos). [#5332](https://github.com/scalableminds/webknossos/pull/5332)
Expand Down
5 changes: 4 additions & 1 deletion MIGRATIONS.unreleased.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,7 @@ This project adheres to [Calendar Versioning](http://calver.org/) `0Y.0M.MICRO`.
User-facing changes are documented in the [changelog](CHANGELOG.released.md).

## Unreleased
-
-

### Postgres Evolutions:
- [070-dark-theme.sql](conf/evolutions/070-dark-theme.sql)
12 changes: 10 additions & 2 deletions app/controllers/DemoProxyController.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ package controllers
import com.google.inject.Inject
import com.mohiva.play.silhouette.api.Silhouette
import com.mohiva.play.silhouette.api.actions.UserAwareRequest
import com.scalableminds.util.accesscontext.GlobalAccessContext
import com.scalableminds.util.tools.Fox
import models.user.{MultiUserDAO, Theme}
import oxalis.security.WkEnv
import play.api.libs.ws.WSClient
import play.api.mvc.{Action, AnyContent}
Expand All @@ -12,13 +14,19 @@ import utils.WkConf
import scala.concurrent.ExecutionContext
import scala.util.matching.Regex

class DemoProxyController @Inject()(ws: WSClient, conf: WkConf, sil: Silhouette[WkEnv])(implicit ec: ExecutionContext)
class DemoProxyController @Inject()(ws: WSClient, conf: WkConf, sil: Silhouette[WkEnv], multiUserDAO: MultiUserDAO)(
implicit ec: ExecutionContext)
extends Controller {

def proxyPageOrMainView: Action[AnyContent] = sil.UserAwareAction.async { implicit request =>
if (matchesProxyPage(request)) {
ws.url(conf.Proxy.prefix + request.uri).get().map(resp => Ok(resp.bodyAsBytes.utf8String).as("text/html"))
} else Fox.successful(Ok(views.html.main(conf)))
} else {
for {
multiUserOpt <- Fox.runOptional(request.identity)(user =>
multiUserDAO.findOne(user._multiUser)(GlobalAccessContext))
} yield Ok(views.html.main(conf, multiUserOpt.map(_.selectedTheme).getOrElse(Theme.auto).toString))
}
}

private def matchesProxyPage(request: UserAwareRequest[WkEnv, AnyContent]): Boolean =
Expand Down
12 changes: 12 additions & 0 deletions app/controllers/UserController.scala
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import play.api.libs.json._
import play.api.mvc._
import utils.ObjectId
import javax.inject.Inject
import models.user.Theme.Theme

import scala.concurrent.ExecutionContext

Expand Down Expand Up @@ -386,4 +387,15 @@ class UserController @Inject()(userService: UserService,
} yield Ok(updatedJs)
}

def updateSelectedTheme(userId: String): Action[Theme] =
sil.SecuredAction.async(validateJson[Theme]) { implicit request =>
for {
userIdValidated <- ObjectId.parse(userId) ?~> "user.id.invalid"
_ <- bool2Fox(request.identity._id == userIdValidated) ?~> "notAllowed" ~> FORBIDDEN
_ <- multiUserDAO.updateSelectedTheme(request.identity._multiUser, request.body)
updatedUser <- userDAO.findOne(userIdValidated)
updatedJs <- userService.publicWrites(updatedUser, request.identity)
} yield Ok(updatedJs)
}

}
18 changes: 15 additions & 3 deletions app/models/user/MultiUser.scala
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import com.scalableminds.util.tools.{Fox, JsonHelper}
import javax.inject.Inject
import slick.jdbc.PostgresProfile.api._
import com.scalableminds.webknossos.schema.Tables._
import models.user.Theme.Theme
import play.api.libs.json.Format.GenericFormat
import play.api.libs.json.{JsObject, Json}
import slick.lifted.Rep
Expand All @@ -20,6 +21,7 @@ case class MultiUser(
isSuperUser: Boolean,
_lastLoggedInIdentity: Option[ObjectId] = None,
novelUserExperienceInfos: JsObject = Json.obj(),
selectedTheme: Theme = Theme.auto,
created: Long = System.currentTimeMillis(),
isDeleted: Boolean = false
)
Expand All @@ -33,7 +35,8 @@ class MultiUserDAO @Inject()(sqlClient: SQLClient)(implicit ec: ExecutionContext

def parse(r: MultiusersRow): Fox[MultiUser] =
for {
novelUserExperienceInfos <- JsonHelper.parseJsonToFox[JsObject](r.noveluserexperienceinfos)
novelUserExperienceInfos <- JsonHelper.parseJsonToFox[JsObject](r.noveluserexperienceinfos).toFox
theme <- Theme.fromString(r.selectedtheme).toFox
} yield {
MultiUser(
ObjectId(r._Id),
Expand All @@ -42,6 +45,7 @@ class MultiUserDAO @Inject()(sqlClient: SQLClient)(implicit ec: ExecutionContext
r.issuperuser,
r._Lastloggedinidentity.map(ObjectId(_)),
novelUserExperienceInfos,
theme,
r.created.getTime,
r.isdeleted
)
Expand All @@ -51,9 +55,9 @@ class MultiUserDAO @Inject()(sqlClient: SQLClient)(implicit ec: ExecutionContext
val novelUserExperienceInfosString = sanitize(u.novelUserExperienceInfos.toString)
for {
_ <- run(sqlu"""insert into webknossos.multiusers(_id, email, passwordInfo_hasher, passwordInfo_password,
isSuperUser, novelUserExperienceInfos, created, isDeleted)
isSuperUser, novelUserExperienceInfos, selectedTheme, created, isDeleted)
values(${u._id}, ${u.email}, '#${sanitize(u.passwordInfo.hasher)}', ${u.passwordInfo.password},
${u.isSuperUser}, '#$novelUserExperienceInfosString',
${u.isSuperUser}, '#$novelUserExperienceInfosString', '#${u.selectedTheme}',
${new java.sql.Timestamp(u.created)}, ${u.isDeleted})
""")
} yield ()
Expand Down Expand Up @@ -94,6 +98,14 @@ class MultiUserDAO @Inject()(sqlClient: SQLClient)(implicit ec: ExecutionContext
where _id = $multiUserId""")
} yield ()

def updateSelectedTheme(multiUserId: ObjectId, selectedTheme: Theme)(implicit ctx: DBAccessContext): Fox[Unit] =
for {
_ <- assertUpdateAccess(multiUserId)
_ <- run(sqlu"""update webknossos.multiusers set
selectedTheme = '#$selectedTheme'
where _id = $multiUserId""")
} yield ()

def removeLastLoggedInIdentitiesWithOrga(organizationId: ObjectId): Fox[Unit] =
for {
_ <- run(sqlu"""
Expand Down
8 changes: 8 additions & 0 deletions app/models/user/Theme.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package models.user

import com.scalableminds.util.enumeration.ExtendedEnumeration

object Theme extends ExtendedEnumeration {
type Theme = Value
val light, dark, auto = Value
}
1 change: 1 addition & 0 deletions app/models/user/UserService.scala
Original file line number Diff line number Diff line change
Expand Up @@ -331,6 +331,7 @@ class UserService @Inject()(conf: WkConf,
"isEditable" -> isEditable,
"organization" -> organization.name,
"novelUserExperienceInfos" -> novelUserExperienceInfos,
"selectedTheme" -> multiUser.selectedTheme,
"created" -> user.created,
"lastTaskTypeId" -> user.lastTaskTypeId.map(_.toString)
)
Expand Down
37 changes: 23 additions & 14 deletions app/views/main.scala.html
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
@( conf: utils.WkConf )
@( conf: utils.WkConf, selectedTheme: String )
<!DOCTYPE html>
<html lang="en">
<head>
Expand All @@ -22,20 +22,29 @@
rel="stylesheet"
type="text/css"
media="screen"
href="/assets/bundle/vendors~main.css?nocache=@(webknossos.BuildInfo.commitHash)"
/>
<link
rel="stylesheet"
type="text/css"
media="screen"
href="/assets/bundle/main.css?nocache=@(webknossos.BuildInfo.commitHash)"
/>
<link
rel="stylesheet"
type="text/css"
media="screen"
href="/assets/fonts/titillium-web.css?nocache=@(webknossos.BuildInfo.commitHash)"
href="/assets/bundle/vendors~dark~light.css?nocache=@(webknossos.BuildInfo.commitHash)"
/>
<script>
let initialTheme = "@(selectedTheme)";
if (initialTheme === "auto") {
initialTheme =
window.matchMedia('(prefers-color-scheme: dark)').media !== "not all" &&
window.matchMedia('(prefers-color-scheme: dark)').matches ?
"dark" :
"light";
}
document.documentElement.style.display = 'none';
document.head.insertAdjacentHTML(
'beforeend',
'<link ' +
'id="primary-stylesheet" ' +
'rel="stylesheet" ' +
'type="text/css" ' +
'media="screen" ' +
'href="/assets/bundle/' + initialTheme + '.css?nocache=@(webknossos.BuildInfo.commitHash)" ' +
'onload="document.documentElement.style.display = \'\'" ' +
'/>');
</script>
<script
data-airbrake-project-id="@(conf.Airbrake.projectID)"
data-airbrake-project-key="@(conf.Airbrake.projectKey)"
Expand Down
27 changes: 27 additions & 0 deletions conf/evolutions/070-dark-theme.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
-- https://github.com/scalableminds/webknossos/pull/5407

START TRANSACTION;

DROP VIEW webknossos.userInfos;
DROP VIEW webknossos.multiUsers_;

CREATE TYPE webknossos.THEME AS ENUM ('light', 'dark', 'auto');
ALTER TABLE webknossos.multiUsers ADD COLUMN selectedTheme webknossos.THEME NOT NULL DEFAULT 'auto';

CREATE VIEW webknossos.multiUsers_ AS SELECT * FROM webknossos.multiUsers WHERE NOT isDeleted;

-- identical to previous (has to be dropped first because of the dependency)
CREATE VIEW webknossos.userInfos AS
SELECT
u._id AS _user, m.email, u.firstName, u.lastname, o.displayName AS organization_displayName,
u.isDeactivated, u.isDatasetManager, u.isAdmin, m.isSuperUser,
u._organization, o.name AS organization_name, u.created AS user_created,
m.created AS multiuser_created, u._multiUser, m._lastLoggedInIdentity, u.lastActivity
FROM webknossos.users_ u
JOIN webknossos.organizations_ o ON u._organization = o._id
JOIN webknossos.multiUsers_ m on u._multiUser = m._id;


UPDATE webknossos.releaseInformation SET schemaVersion = 70;

COMMIT TRANSACTION;
4 changes: 2 additions & 2 deletions conf/evolutions/reversions/068-pricing-plan.sql
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ START TRANSACTION;
DROP VIEW webknossos.userInfos;
DROP VIEW webknossos.organizations_;

ALTER TABLE webknossos.organizations DROP COLUMN;
DROP TYPE webknossos.PRICING_PLANS AS ENUM ('Basic', 'Premium', 'Pilot', 'Custom');
ALTER TABLE webknossos.organizations DROP COLUMN pricingPlan;
DROP TYPE webknossos.PRICING_PLANS;

CREATE VIEW webknossos.organizations_ AS SELECT * FROM webknossos.organizations WHERE NOT isDeleted;

Expand Down
25 changes: 25 additions & 0 deletions conf/evolutions/reversions/070-dark-theme.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
START TRANSACTION;

DROP VIEW webknossos.userInfos;
DROP VIEW webknossos.multiUsers_;

ALTER TABLE webknossos.multiUsers DROP COLUMN selectedTheme;
DROP TYPE webknossos.THEME;

CREATE VIEW webknossos.multiUsers_ AS SELECT * FROM webknossos.multiUsers WHERE NOT isDeleted;

-- identical to previous (has to be dropped first because of the dependency)
CREATE VIEW webknossos.userInfos AS
SELECT
u._id AS _user, m.email, u.firstName, u.lastname, o.displayName AS organization_displayName,
u.isDeactivated, u.isDatasetManager, u.isAdmin, m.isSuperUser,
u._organization, o.name AS organization_name, u.created AS user_created,
m.created AS multiuser_created, u._multiUser, m._lastLoggedInIdentity, u.lastActivity
FROM webknossos.users_ u
JOIN webknossos.organizations_ o ON u._organization = o._id
JOIN webknossos.multiUsers_ m on u._multiUser = m._id;


UPDATE webknossos.releaseInformation SET schemaVersion = 69;

COMMIT TRANSACTION;
1 change: 1 addition & 0 deletions conf/webknossos.latest.routes
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ GET /users/:id c
PATCH /users/:id controllers.UserController.update(id: String)
PUT /users/:id/taskTypeId controllers.UserController.updateLastTaskTypeId(id: String)
PUT /users/:id/novelUserExperienceInfos controllers.UserController.updateNovelUserExperienceInfos(id: String)
PUT /users/:id/selectedTheme controllers.UserController.updateSelectedTheme(id: String)
GET /users/:id/tasks controllers.UserController.userTasks(id: String, isFinished: Option[Boolean], limit: Option[Int], pageNumber: Option[Int], includeTotalCount: Option[Boolean])
GET /users/:id/loggedTime controllers.UserController.userLoggedTime(id: String)
POST /users/loggedTime controllers.UserController.usersLoggedTime
Expand Down
11 changes: 11 additions & 0 deletions frontend/javascripts/admin/admin_rest_api.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ import {
APIAnnotationTypeEnum,
type APIUpdateActionBatch,
type APIUser,
type APIUserTheme,
type APIUserLoggedTime,
type ExperienceDomainList,
type HybridServerTracing,
Expand Down Expand Up @@ -226,6 +227,16 @@ export function updateLastTaskTypeIdOfUser(
});
}

export function updateSelectedThemeOfUser(
userId: string,
selectedTheme: APIUserTheme,
): Promise<APIUser> {
return Request.sendJSONReceiveJSON(`/api/users/${userId}/selectedTheme`, {
method: "PUT",
data: JSON.stringify(selectedTheme),
});
}

export async function getAuthToken(): Promise<string> {
const { token } = await Request.receiveJSON("/api/auth/token");
return token;
Expand Down
55 changes: 12 additions & 43 deletions frontend/javascripts/admin/dataset/dataset_upload_view.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,9 @@ import {
} from "antd";
import { InfoCircleOutlined, FileOutlined, FolderOutlined, InboxOutlined } from "@ant-design/icons";
import { connect } from "react-redux";
import React, { useMemo } from "react";
import React from "react";
import moment from "moment";
import classnames from "classnames";
import _ from "lodash";
import { useDropzone } from "react-dropzone";

Expand Down Expand Up @@ -630,36 +631,6 @@ class DatasetUploadView extends React.Component<PropsWithFormAndRouter, State> {
}
}

const baseStyle: Object = {
flex: 1,
display: "flex",
flexDirection: "column",
alignItems: "center",
padding: "20px",
borderWidth: 2,
borderRadius: 2,
borderColor: "#eeeeee",
borderStyle: "dashed",
backgroundColor: "#fafafa",
color: "rgba(0, 0, 0, 0.85)",
fontSize: 16,
outline: "none",
transition: "border .24s ease-in-out",
cursor: "pointer",
};

const activeStyle: Object = {
borderColor: "#2196f3",
};

const acceptStyle: Object = {
borderColor: "#00e676",
};

const rejectStyle: Object = {
borderColor: "#ff1744",
};

function FileUploadArea({ fileList, onChange }) {
const onDropAccepted = acceptedFiles => {
// file.path should be set by react-dropzone (which uses file-selector::toFileWithPath).
Expand All @@ -673,16 +644,6 @@ function FileUploadArea({ fileList, onChange }) {
});
const acceptedFiles = fileList;

const style: Object = useMemo(
() => ({
...baseStyle,
...(isDragActive ? activeStyle : {}),
...(isDragAccept ? acceptStyle : {}),
...(isDragReject ? rejectStyle : {}),
}),
[isDragActive, isDragReject, isDragAccept],
);

const files = acceptedFiles.map(file => <li key={file.path}>{file.path}</li>);

const showSmallFileList = files.length > 10;
Expand Down Expand Up @@ -725,9 +686,17 @@ function FileUploadArea({ fileList, onChange }) {

return (
<div>
<div {...getRootProps({ style })}>
<div
{...getRootProps({
className: classnames("dataset-upload-dropzone", {
"dataset-upload-dropzone-active": isDragActive,
"dataset-upload-dropzone-accept": isDragAccept,
"dataset-upload-dropzone-rejct": isDragReject,
}),
})}
>
<input {...getInputProps()} />
<InboxOutlined style={{ fontSize: 48, color: "#41a9ff" }} />
<InboxOutlined style={{ fontSize: 48, color: "var(--ant-primary)" }} />
<p style={{ maxWidth: 800, textAlign: "center", marginTop: 8 }}>
Drag your file(s) to this area to upload them. Either add individual image files, a zip
archive or a folder.{" "}
Expand Down
Loading