;
+
+ return validFalies.flat();
}
/**
@@ -154,6 +158,10 @@ export class MRIFileLoader {
};
xhr.onload = () => {
+ if (xhr.status === 403) {
+ return this.handleVolumeLoadFailed(`Error 403 Forbiden, failed to fetch file from URL: ${url}`);
+ }
+
if (this.filesProgressByLength) {
this.filesLoaded = this.filesLoaded + 1;
const percentComplete = this.filesLoaded / this.filesLength;
@@ -166,7 +174,7 @@ export class MRIFileLoader {
xhr.onerror = () => {
this.handleVolumeLoadFailed(`Failed to fetch file from URL: ${url}`);
- reject(new Error());
+ reject();
};
xhr.responseType = 'blob';
@@ -189,6 +197,6 @@ export class MRIFileLoader {
*/
handleVolumeLoadFailed(error: string) {
this.events.emit(MriEvents.FILE_READ_ERROR, { error });
- this.store.setVolumeLoadFailed(this.fileName, [error]);
+ this.store.setVolumeLoadFailed(this.fileName);
}
}
diff --git a/src/engine/lib/core/readers/MRIReader.ts b/src/engine/lib/core/readers/MRIReader.ts
index f4e12778..7a3edae0 100644
--- a/src/engine/lib/core/readers/MRIReader.ts
+++ b/src/engine/lib/core/readers/MRIReader.ts
@@ -10,8 +10,11 @@ export class MRIReader {
if (data && data.length && data[0] instanceof File) {
this.fileReader.read(data as File[]);
} else if (isValidUrl(data as string)) {
- const files: File[] = await this.fileLoader.load(data as string);
- this.fileReader.read(files);
+ const files: File[] | null = await this.fileLoader.load(data as string);
+
+ if (files) {
+ this.fileReader.read(files);
+ }
} else {
throw new Error('Invalid input. Expected a File or URL.');
}
diff --git a/src/engine/lib/core/readers/abstract-file-reader/AbstractFileReader.ts b/src/engine/lib/core/readers/abstract-file-reader/AbstractFileReader.ts
index bbeb9a6a..ce12de81 100644
--- a/src/engine/lib/core/readers/abstract-file-reader/AbstractFileReader.ts
+++ b/src/engine/lib/core/readers/abstract-file-reader/AbstractFileReader.ts
@@ -75,7 +75,7 @@ export abstract class AbstractFileReader {
*/
public handleVolumeReadFailed(error: string) {
this.events.emit(MriEvents.FILE_READ_ERROR, { error });
- this.store.setVolumeLoadFailed(this.fileName, [error]);
+ this.store.setVolumeLoadFailed(this.fileName);
}
/**
diff --git a/src/engine/lib/services/StoreService.ts b/src/engine/lib/services/StoreService.ts
index e58797ab..cc82fa95 100644
--- a/src/engine/lib/services/StoreService.ts
+++ b/src/engine/lib/services/StoreService.ts
@@ -82,11 +82,10 @@ export class MRIStoreService {
this.dispatchActions(actions);
}
- public setVolumeLoadFailed(fileName: string, errors: string[]): void {
+ public setVolumeLoadFailed(fileName: string): void {
const actions = [
- { type: StoreActionType.SET_ERR_ARRAY, errors },
{ type: StoreActionType.SET_VOLUME_SET, volume: null },
- { type: StoreActionType.SET_FILENAME, fileName: fileName },
+ { type: StoreActionType.SET_FILENAME, fileName },
{ type: StoreActionType.SET_PROGRESS, progress: 0 },
{ type: StoreActionType.SET_SPINNER, spinner: false },
{ type: StoreActionType.SET_IS_LOADED, isLoaded: false },
diff --git a/src/engine/tools2d/ToolDelete.js b/src/engine/tools2d/ToolDelete.js
index bf861718..fab4cea2 100644
--- a/src/engine/tools2d/ToolDelete.js
+++ b/src/engine/tools2d/ToolDelete.js
@@ -75,12 +75,13 @@ class ToolDelete {
y: yScr,
};
+ const toolPaint = this.m_objGraphics2d.m_toolPaint;
const toolDist = this.m_objGraphics2d.m_toolDistance;
const toolAngle = this.m_objGraphics2d.m_toolAngle;
const toolArea = this.m_objGraphics2d.m_toolArea;
const toolRect = this.m_objGraphics2d.m_toolRect;
const toolText = this.m_objGraphics2d.m_toolText;
- const tools = [toolDist, toolAngle, toolArea, toolRect, toolText];
+ const tools = [toolPaint, toolDist, toolAngle, toolArea, toolRect, toolText];
const trackedBefore = this.m_pointTracked !== null;
this.m_pointTracked = null;
const numTools = tools.length;
diff --git a/src/engine/tools2d/ToolDistance.js b/src/engine/tools2d/ToolDistance.js
index a77bc873..8a172aa0 100644
--- a/src/engine/tools2d/ToolDistance.js
+++ b/src/engine/tools2d/ToolDistance.js
@@ -113,9 +113,13 @@ class ToolDistance {
const zDim = vol.m_zDim;
const objCanvas = store.graphics2d.m_mount.current;
const canvasRect = objCanvas.getBoundingClientRect();
+ const canvasWidth = canvasRect.width;
+ const canvasHeight = canvasRect.height;
+ const centerX = (canvasWidth - wScr) / 2;
+ const centerY = (canvasHeight - hScr) / 2;
+ const xPos = (store.render2dxPos - centerX) / wScr;
+ const yPos = (store.render2dyPos - centerY) / hScr;
const zoom = store.render2dZoom;
- const xPos = store.render2dxPos / canvasRect.width;
- const yPos = store.render2dyPos / canvasRect.height;
const vTex = {
x: 0.0,
@@ -152,9 +156,14 @@ class ToolDistance {
const zDim = vol.m_zDim;
const objCanvas = store.graphics2d.m_mount.current;
const canvasRect = objCanvas.getBoundingClientRect();
+ const canvasWidth = canvasRect.width;
+ const canvasHeight = canvasRect.height;
+ const centerX = (canvasWidth - wScr) / 2;
+ const centerY = (canvasHeight - hScr) / 2;
+ const xPos = (store.render2dxPos - centerX) / wScr;
+ const yPos = (store.render2dyPos - centerY) / hScr;
const zoom = store.render2dZoom;
- const xPos = store.render2dxPos / canvasRect.width;
- const yPos = store.render2dyPos / canvasRect.height;
+
if (mode2d === Modes2d.TRANSVERSE) {
// z const
vScr.x = (xTex / xDim - xPos) / zoom;
diff --git a/src/engine/tools2d/ToolPaint.js b/src/engine/tools2d/ToolPaint.js
new file mode 100644
index 00000000..d984e5c8
--- /dev/null
+++ b/src/engine/tools2d/ToolPaint.js
@@ -0,0 +1,112 @@
+import ToolDistance from './ToolDistance';
+import PointerChecker from '../utils/PointerChecker';
+
+class ToolPaint {
+ constructor(objGra) {
+ this.m_objGraphics2d = objGra;
+ this.m_wScreen = 0;
+ this.m_hScreen = 0;
+ this.m_lines = [];
+ this.m_mouseDown = false;
+ this.m_objEdit = null;
+ }
+
+ setScreenDim(wScr, hScr) {
+ this.m_wScreen = wScr;
+ this.m_hScreen = hScr;
+ }
+
+ getEditPoint(vScr, store) {
+ const numLines = this.m_lines.length;
+ for (let i = 0; i < numLines; i++) {
+ const objLine = this.m_lines[i];
+
+ if (objLine.points.length >= 2) {
+ for (let j = 1; j < objLine.points.length; j++) {
+ const vScrS = ToolDistance.textureToScreen(
+ objLine.points[j - 1].x,
+ objLine.points[j - 1].y,
+ this.m_wScreen,
+ this.m_hScreen,
+ store
+ );
+ const vScrE = ToolDistance.textureToScreen(objLine.points[j].x, objLine.points[j].y, this.m_wScreen, this.m_hScreen, store);
+
+ if (PointerChecker.isPointerOnLine(vScrS, vScrE, vScr)) {
+ this.m_objEdit = objLine;
+ return objLine.points[j - 1];
+ }
+ }
+ }
+ }
+ return null;
+ }
+
+ deleteObject() {
+ if (this.m_objEdit != null) {
+ const ind = this.m_lines.indexOf(this.m_objEdit);
+ if (ind >= 0) {
+ this.m_lines.splice(ind, 1);
+ }
+ }
+ }
+
+ onMouseDown(xScr, yScr, store) {
+ const vTex = ToolDistance.screenToTexture(xScr, yScr, this.m_wScreen, this.m_hScreen, store);
+ const newLine = {
+ points: [{ x: vTex.x, y: vTex.y }],
+ distMm: 0.0,
+ color: store.selectedColor,
+ };
+ this.m_lines.push(newLine);
+ this.m_mouseDown = true;
+ }
+
+ onMouseMove(xScr, yScr, store) {
+ if (!this.m_mouseDown) {
+ return;
+ }
+ const vTex = ToolDistance.screenToTexture(xScr, yScr, this.m_wScreen, this.m_hScreen, store);
+ const numLines = this.m_lines.length;
+ if (numLines > 0) {
+ const currentLine = this.m_lines[numLines - 1];
+ currentLine.points.push({ x: vTex.x, y: vTex.y });
+ this.m_objGraphics2d.forceUpdate();
+ }
+ }
+
+ onMouseUp() {
+ this.m_mouseDown = false;
+ }
+
+ clear() {
+ this.m_lines = [];
+ }
+
+ render(ctx, store) {
+ const numLines = this.m_lines.length;
+ ctx.lineWidth = 2;
+
+ for (let i = 0; i < numLines; i++) {
+ const objLine = this.m_lines[i];
+ const points = objLine.points;
+
+ if (points.length > 1) {
+ ctx.strokeStyle = objLine.color;
+ for (let j = 1; j < points.length; j++) {
+ const vsTex = points[j - 1];
+ const veTex = points[j];
+
+ const vs = ToolDistance.textureToScreen(vsTex.x, vsTex.y, this.m_wScreen, this.m_hScreen, store);
+ const ve = ToolDistance.textureToScreen(veTex.x, veTex.y, this.m_wScreen, this.m_hScreen, store);
+
+ ctx.beginPath();
+ ctx.moveTo(vs.x, vs.y);
+ ctx.lineTo(ve.x, ve.y);
+ ctx.stroke();
+ }
+ }
+ }
+ }
+}
+export default ToolPaint;
diff --git a/src/engine/tools2d/ToolPick.js b/src/engine/tools2d/ToolPick.js
index 1de1152d..d93b51a3 100644
--- a/src/engine/tools2d/ToolPick.js
+++ b/src/engine/tools2d/ToolPick.js
@@ -60,9 +60,14 @@ class ToolPick {
const zDim = vol.m_zDim;
const objCanvas = store.graphics2d.m_mount.current;
const canvasRect = objCanvas.getBoundingClientRect();
+ const canvasWidth = canvasRect.width;
+ const canvasHeight = canvasRect.height;
+ const centerX = (canvasWidth - store.graphics2d.imgData.width) / 2;
+ const centerY = (canvasHeight - store.graphics2d.imgData.height) / 2;
+ const xPos = (store.render2dxPos - centerX) / store.graphics2d.imgData.width;
+ const yPos = (store.render2dyPos - centerY) / store.graphics2d.imgData.height;
const zoom = store.render2dZoom;
- const xPos = store.render2dxPos / canvasRect.width;
- const yPos = store.render2dyPos / canvasRect.height;
+
if (mode2d === Modes2d.TRANSVERSE) {
// z: const
vTex.x = Math.floor((xPos + xScr * zoom) * xDim);
@@ -92,16 +97,16 @@ class ToolPick {
const xRatioImage = xScr / this.m_wScreen;
const yRatioImage = yScr / this.m_hScreen;
- if (xRatioImage > 1.0 || yRatioImage > 1.0) {
- // out if rendered image
- return;
- }
const vTex = this.screenToTexture(xRatioImage, yRatioImage, store);
const volSet = store.volumeSet;
const vol = volSet.getVolume(store.volumeIndex);
const xDim = vol.m_xDim;
const yDim = vol.m_yDim;
+
+ if (vTex.x < 0 || vTex.y < 0 || vTex.z < 0 || vTex.x >= vol.m_xDim || vTex.y >= vol.m_yDim) {
+ return;
+ }
/*
if (mode2d === Modes2d.SAGGITAL) {
// x
diff --git a/src/engine/tools2d/ToolTypes.js b/src/engine/tools2d/ToolTypes.js
index 4087ad85..406c8c99 100644
--- a/src/engine/tools2d/ToolTypes.js
+++ b/src/engine/tools2d/ToolTypes.js
@@ -24,5 +24,6 @@ const Tools2dType = {
ZOOM_100: 11,
FILTER: 12,
HAND: 13,
+ PAINT: 14,
};
export default Tools2dType;
diff --git a/src/store/ActionTypes.js b/src/store/ActionTypes.js
index 350ecd8a..712b791c 100644
--- a/src/store/ActionTypes.js
+++ b/src/store/ActionTypes.js
@@ -53,5 +53,6 @@ const StoreActionType = {
SET_SHOW_MODAL_CONFIRMATION: 41,
SET_SHOW_MODAL_WINDOW_WC: 42,
SET_SHOW_MODAL_SELECT_FILES: 43,
+ SET_SELECTED_COLOR: 44,
};
export default StoreActionType;
diff --git a/src/store/Store.js b/src/store/Store.js
index bdd5fcf0..dd2d8dbb 100644
--- a/src/store/Store.js
+++ b/src/store/Store.js
@@ -52,6 +52,7 @@ export const initialState = {
showModalConfirmation: false,
showModalWindowCW: false,
showModalSelectFiles: false,
+ selectedColor: '#ffff00',
};
//
// App reducer
@@ -144,6 +145,8 @@ const medReducer = (state = initialState, action) => {
return Object.assign({}, state, { spinnerTitle: action.spinnerTitle });
case StoreActionType.SET_SPINNER_PROGRESS:
return Object.assign({}, state, { spinnerProgress: action.spinnerProgress });
+ case StoreActionType.SET_SELECTED_COLOR:
+ return Object.assign({}, state, { selectedColor: action.selectedColor });
default:
return state;
}
diff --git a/src/ui/Button/Button.module.css b/src/ui/Button/Button.module.css
index 1ff3473e..29ccb7ab 100644
--- a/src/ui/Button/Button.module.css
+++ b/src/ui/Button/Button.module.css
@@ -48,11 +48,21 @@ button:hover,
background-color: var(--red);
}
+.button > svg {
+ width: 42px;
+ height: 42px;
+}
+
@media screen and (min-width: 768px) {
.reset {
display: flex;
justify-content: flex-end;
}
+
+ .button > svg {
+ width: 24px;
+ height: 24px;
+ }
}
.reset {
display: inline-block;
@@ -82,11 +92,6 @@ button:hover,
border-radius: 36px;
}
-.button > svg {
- width: 24px;
- height: 24px;
-}
-
.rounded {
overflow: hidden;
border-radius: 50%;
diff --git a/src/ui/DragAndDrop/DragAndDropContainer.jsx b/src/ui/DragAndDrop/DragAndDropContainer.jsx
index ec1697be..fc63ce12 100644
--- a/src/ui/DragAndDrop/DragAndDropContainer.jsx
+++ b/src/ui/DragAndDrop/DragAndDropContainer.jsx
@@ -1,65 +1,41 @@
-import React, { useCallback, useState } from 'react';
+import React, { useState } from 'react';
import css from '../Main.module.css';
-import { DnDItemTypes } from '../Constants/DnDItemTypes';
-import { useDrag } from 'react-dnd';
export const DragAndDropContainer = ({ children }) => {
- const [position, setPosition] = useState({ top: 100, left: null });
- const [isCanDrag, setIsCanDrag] = useState(false);
+ const [position, setPosition] = useState({ top: 100, left: 900 });
+ const [isDragging, setIsDragging] = useState(false);
+ const [offset, setOffset] = useState({ x: 0, y: 0 });
- const checkEventTagName = useCallback(
- (e) => {
- e.target.tagName === 'DIV' ? setIsCanDrag(true) : setIsCanDrag(false);
- },
- [isCanDrag, setIsCanDrag]
- );
+ const startDrag = (e) => {
+ if (e.target.tagName.toLowerCase() !== 'span') {
+ setIsDragging(true);
+ setOffset({
+ x: e.clientX - position.left,
+ y: e.clientY - position.top,
+ });
+ }
+ };
- const getLeftPositionSettings = useCallback(
- (e) => {
- if (position.left === null) {
- setPosition({
- top: position.top,
- left: window.innerWidth - e.currentTarget.offsetWidth - 25,
- });
- }
- },
- [position]
- );
+ const stopDrag = () => {
+ setIsDragging(false);
+ };
- const [{ isDragging }, drag, dragPreview] = useDrag(
- () => ({
- type: DnDItemTypes.SETTINGS,
- item: { left: position.left, top: position.top, isCanDrag },
- options: {
- dropEffect: 'move',
- },
- end: (item, monitor) => {
- const { x, y } = monitor.getDropResult();
- const top = Math.round(item.top + y);
- const left = Math.round(item.left + x);
- setPosition({ top, left });
- },
- canDrag: () => {
- return isCanDrag;
- },
- collect: (monitor) => ({
- isDragging: monitor.isDragging(),
- }),
- }),
- [position.left, position.top, isCanDrag]
- );
+ const handleDrag = (e) => {
+ if (isDragging) {
+ const x = e.clientX - offset.x;
+ const y = e.clientY - offset.y;
+ setPosition({ left: x, top: y });
+ }
+ };
- return isDragging ? (
-
- {children}
-
- ) : (
+ return (
{
const dispatch = useDispatch();
- const { arrErrors, isLoaded, progress, spinner, viewMode, showModalText, showModalAlert, showModalWindowCW, showModalConfirmation } =
- useSelector((state) => state);
+ const { isLoaded, progress, spinner, viewMode, showModalText, showModalAlert, showModalWindowCW, showModalConfirmation } = useSelector(
+ (state) => state
+ );
const [m_fileNameOnLoad, setM_fileNameOnLoad] = useState(false);
const [isWebGl20supported, setIsWebGl20supported] = useState(true);
@@ -48,6 +51,7 @@ export const Main = () => {
const [isFullMode, setIsFullMode] = useState(false);
const [isMobile, setIsMobile] = useState(window.innerWidth <= 768);
const appRef = useRef();
+ const mriViwer = useRef(MriViwer).current;
useEffect(() => {
function handleResize() {
@@ -60,6 +64,23 @@ export const Main = () => {
window.removeEventListener('resize', handleResize);
};
}, []);
+
+ useEffect(() => {
+ const handleFileReadError = (eventData) => {
+ setStrAlertTitle('File Read Error');
+ setStrAlertText(eventData.error);
+ onShowModalAlert();
+ };
+
+ // Subscribe to the FILE_READ_ERROR event
+ mriViwer.events.on(MriEvents.FILE_READ_ERROR, handleFileReadError);
+
+ // Clean up
+ return () => {
+ mriViwer.events.off(MriEvents.FILE_READ_ERROR, handleFileReadError);
+ };
+ }, []);
+
const [, drop] = useDrop(
() => ({
accept: DnDItemTypes.SETTINGS,
@@ -215,7 +236,6 @@ export const Main = () => {
)}
- {arrErrors.length > 0 && }
{showModalText && (
)}
diff --git a/src/ui/Main.module.css b/src/ui/Main.module.css
index b5f8a3fe..cfecaccf 100644
--- a/src/ui/Main.module.css
+++ b/src/ui/Main.module.css
@@ -27,9 +27,8 @@
display: flex;
justify-content: center;
align-items: center;
- flex-direction: row-reverse;
- width: 100vw;
- right: 0;
+ flex-direction: row;
+ width: 96vw;
height: 50px;
padding: 1rem;
background-color: var(--beige);
@@ -68,7 +67,7 @@
position: absolute;
right: 0;
top: 50%;
- z-index: 10;
+ z-index: 2;
}
.left {
display: none;
@@ -81,6 +80,7 @@
right: 0;
bottom: 0;
z-index: 1;
+ width: 100%;
user-select: none;
}
@@ -115,20 +115,21 @@
.header__right {
display: flex;
+ flex-direction: row-reverse;
justify-content: flex-start;
flex-wrap: nowrap;
align-items: center;
margin-left: auto;
position: static;
background: inherit;
+ right: 0;
}
.left {
display: block;
position: absolute;
left: 25px;
- top: 12%;
- z-index: 1010;
+ top: 15%;
}
.bottleft {
@@ -189,16 +190,13 @@
display: flex;
flex-direction: column;
position: absolute;
- right: 25px;
- top: 100px;
background-color: var(--dark-gray);
padding: 25px;
width: 400px;
opacity: var(--opacity);
- cursor: all-scroll;
border-radius: 24px;
transition: opacity 300ms ease-in-out;
- z-index: 1100;
+ z-index: 11;
}
.left {
top: 8%;
@@ -216,7 +214,7 @@
}
.left {
- top: 10%;
+ top: 15%;
}
}
diff --git a/src/ui/OpenFile/UiReportMenu.js b/src/ui/OpenFile/UiReportMenu.js
index 5ccbbd87..4cd716fc 100644
--- a/src/ui/OpenFile/UiReportMenu.js
+++ b/src/ui/OpenFile/UiReportMenu.js
@@ -46,9 +46,8 @@ export const UiReportMenu = () => {
const strDisabled = !isLoadedLocal;
return (
<>
-
+
{
{
};
return (
<>
-
+
{
+ return (
+
+
+
+ );
+};
+
+export default ColorPicker;
diff --git a/src/ui/Panels/Mode2dSettingsPanel.jsx b/src/ui/Panels/Mode2dSettingsPanel.jsx
index 492ba893..d1b08b6e 100644
--- a/src/ui/Panels/Mode2dSettingsPanel.jsx
+++ b/src/ui/Panels/Mode2dSettingsPanel.jsx
@@ -8,11 +8,22 @@ import { SegmentationProperty } from './Properties2d/SegmentationProperty';
import SelectVolumeProperty from './Properties2d/SelectVolumeProperty';
import { SliderCaption } from '../Form';
import { TransverseProperty } from './Properties2d/TransverseProperty';
-import { useSelector } from 'react-redux';
+import { useSelector, useDispatch } from 'react-redux';
+import ColorPicker from '../Panels/ColorPicker/ColorPicker';
+import './ColorPicker/ColorPicker.css';
+import StoreActionType from '../../store/ActionTypes';
+import Tools2dType from '../../engine/tools2d/ToolTypes';
export const Mode2dSettingsPanel = () => {
const { volumeSet } = useSelector((state) => state);
const { m_volumes } = volumeSet;
+ const { indexTools2d } = useSelector((state) => state);
+ const { selectedColor } = useSelector((state) => state);
+ const dispatch = useDispatch();
+
+ const handleColorChange = (newColor) => {
+ dispatch({ type: StoreActionType.SET_SELECTED_COLOR, selectedColor: newColor.hex });
+ };
return (
<>
@@ -20,6 +31,12 @@ export const Mode2dSettingsPanel = () => {
{m_volumes.length > 1 && }
+ {indexTools2d === Tools2dType.PAINT && (
+
+ )}
>
);
};
diff --git a/src/ui/Panels/Properties2d/SegmentationProperty.jsx b/src/ui/Panels/Properties2d/SegmentationProperty.jsx
index f12274e5..35c4726e 100644
--- a/src/ui/Panels/Properties2d/SegmentationProperty.jsx
+++ b/src/ui/Panels/Properties2d/SegmentationProperty.jsx
@@ -31,12 +31,10 @@ export const SegmentationProperty = () => {
return (
<>
- You can use automatic 2d image segmentation only for brain-like data
- Segmentation 2d (brain only)
+ AI Brain Segmentation (Beta):
- Switch checker above on and see segmentation result on right
>
);
};
diff --git a/src/ui/StartScreen/RecentlyFiles/RecentlyFiles.module.css b/src/ui/StartScreen/RecentlyFiles/RecentlyFiles.module.css
index 227e3ddd..3b9ce34a 100644
--- a/src/ui/StartScreen/RecentlyFiles/RecentlyFiles.module.css
+++ b/src/ui/StartScreen/RecentlyFiles/RecentlyFiles.module.css
@@ -23,8 +23,24 @@
.left {
color: var(--dark-gray4);
- margin-right: 20px;
- flex-basis: 50%;
+ margin-right: 2%;
+ flex-basis: 40%;
+ overflow: hidden;
+}
+.right {
+ flex-basis: 58%;
+ overflow: hidden;
+}
+
+.element {
+ width: 100%;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+}
+
+.element:not(:first-child) {
+ margin-top: 10px;
}
@media screen and (min-width: 768px) {
diff --git a/src/ui/StartScreen/StartScreen.module.css b/src/ui/StartScreen/StartScreen.module.css
index 88ccdffb..a60e9f7a 100644
--- a/src/ui/StartScreen/StartScreen.module.css
+++ b/src/ui/StartScreen/StartScreen.module.css
@@ -21,6 +21,10 @@
display: none;
}
+.container {
+ width: 400px;
+}
+
@media screen and (min-width: 768px) {
.subheader {
margin: 0;
diff --git a/src/ui/TopToolbar/ExploreTools.jsx b/src/ui/TopToolbar/ExploreTools.jsx
index 48cca240..1ddf7110 100644
--- a/src/ui/TopToolbar/ExploreTools.jsx
+++ b/src/ui/TopToolbar/ExploreTools.jsx
@@ -47,6 +47,12 @@ const ExploreTools = (props) => {
handler: mediator.bind(null, Tools2dType.INTENSITY),
id: Tools2dType.INTENSITY,
},
+ {
+ icon: 'paint',
+ caption: 'Paint',
+ handler: mediator.bind(null, Tools2dType.PAINT),
+ id: Tools2dType.PAINT,
+ },
{
icon: 'line',
caption: 'Measure distance between voxels',
diff --git a/src/ui/UiZoomTools.jsx b/src/ui/UiZoomTools.jsx
index ca187212..e84c5863 100644
--- a/src/ui/UiZoomTools.jsx
+++ b/src/ui/UiZoomTools.jsx
@@ -27,10 +27,10 @@ const UiZoomTools = (props) => {
xPosNew = props.render2dxPos + (canvasRect.width / 2) * Math.abs(step);
yPosNew = props.render2dyPos + (canvasRect.height / 2) * Math.abs(step);
} else if (buttonId === Tools2dType.ZOOM_OUT && newZoom < 1) {
- const initialX = canvasRect.width * currentZoom + props.render2dxPos;
- const initialY = canvasRect.height * currentZoom + props.render2dyPos;
- xPosNew = initialX - (initialX - props.render2dxPos) * (newZoom / currentZoom);
- yPosNew = initialY - (initialY - props.render2dyPos) * (newZoom / currentZoom);
+ const centerX = (canvasRect.width * newZoom) / 2 + props.render2dxPos;
+ const centerY = (canvasRect.height * newZoom) / 2 + props.render2dyPos;
+ xPosNew = centerX - (centerX - props.render2dxPos) * (newZoom / currentZoom);
+ yPosNew = centerY - (centerY - props.render2dyPos) * (newZoom / currentZoom);
}
if (xPosNew < 0) {