Skip to content

Commit

Permalink
Finish integrating mark.undo() and mark.delete() for VolumetricViewer…
Browse files Browse the repository at this point in the history
…. Clean up image toolbar to only allow annotating. Fix accessibility issues with VolumetricViewer as seen is Storybook. Update tests to include mark.undo() and mark.delete().
  • Loading branch information
kieftrav committed Feb 3, 2025
1 parent d862f4a commit 79bc425
Show file tree
Hide file tree
Showing 13 changed files with 188 additions and 31 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,14 @@ import { useKeyZoom } from '@hooks'
function storeMapper(classifierStore) {
return {
hasAnnotateTask: classifierStore.subjectViewer.hasAnnotateTask,
isVolumetricViewer: classifierStore.projects?.active?.isVolumetricViewer,
rotation: classifierStore.subjectViewer.rotation
}
}

// Generalized ...props here are css rules from the page layout
function ImageToolbar (props) {
const { hasAnnotateTask, rotation } = useStores(storeMapper)
const { hasAnnotateTask, isVolumetricViewer, rotation } = useStores(storeMapper)
const { onKeyZoom } = useKeyZoom(rotation)

return (
Expand All @@ -49,13 +50,13 @@ function ImageToolbar (props) {
gap='2px'
>
{hasAnnotateTask && <AnnotateButton />}
<MoveButton />
<ZoomInButton />
<ZoomOutButton />
<RotateButton />
<FullscreenButton show={false} />
<ResetButton />
<InvertButton />
{!isVolumetricViewer && <MoveButton />}
{!isVolumetricViewer && <ZoomInButton />}
{!isVolumetricViewer && <ZoomOutButton />}
{!isVolumetricViewer && <RotateButton />}
{!isVolumetricViewer && <FullscreenButton show={false} />}
{!isVolumetricViewer && <ResetButton />}
{!isVolumetricViewer && <InvertButton />}
</Box>
<FieldGuide />
</Box>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,14 @@ import asyncStates from '@zooniverse/async-states'
import { lazy, Suspense } from 'react'
import { MobXProviderContext } from 'mobx-react'
import { useContext } from 'react'
import { useEffect } from 'react'

const DEFAULT_HANDLER = () => {}
const VolumetricViewer = lazy(() => import('@zooniverse/subject-viewers/VolumetricViewer'))
const EXTERNAL_EVENTS = {
undo: null,
delete: null,
}

function VolumetricViewerWrapper({
loadingState = asyncStates.initialized,
Expand All @@ -16,13 +21,19 @@ function VolumetricViewerWrapper({
const addAnnotation = stores?.classifierStore?.classifications?.addAnnotation ?? DEFAULT_HANDLER
const activeStepTasks = stores?.classifierStore?.workflowSteps?.activeStepTasks ?? []

// this object enables classifier events to be initiated by the TaskArea
useEffect(() => {
stores?.classifierStore?.projects?.active?.setVolumetricEvents(EXTERNAL_EVENTS)
}, [])

function onAnnotationUpdate(annotations) {
if (activeStepTasks[0])
addAnnotation(activeStepTasks[0], annotations)
}

const config = {
loadingState,
externalEvents: EXTERNAL_EVENTS,
onAnnotation: onAnnotationUpdate,
onError,
onReady,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@ import { Box, Text } from "grommet"
import { Blank } from "grommet-icons"
import InputStatus from "../../../components/InputStatus"
import { Markdownz } from "@zooniverse/react-components"
import { MobXProviderContext } from 'mobx-react'
import { observer } from "mobx-react"
import styled from "styled-components"
import TaskInput from "../../../components/TaskInput"
import { useContext } from 'react'

const SVG_ARROW = "48 50, 48 15, 40 15, 50 0, 60 15, 52 15, 52 50"

Expand All @@ -31,11 +33,57 @@ const StyledToolIcon = styled.div`
}
`

const StyledAnnotation = styled.div`
.mark {
display: flex;
align-items: center;
padding: 15px;
background-color: rgba(0,0,0,0.8);
color: white;
margin-top: -8px;
.mark-color {
width: 25px;
height: 25px;
margin-right: 10px;
}
.mark-label {
flex: 1;
}
.mark-undo {
cursor: pointer;
},
.mark-delete {
cursor: pointer;
margin-left: 10px;
}
}
`


function VolumetricTask({
annotation,
disabled = false,
task
}) {
const stores = useContext(MobXProviderContext)
const volumetricEvents = stores?.classifierStore?.projects?.active?.volumetricEvents

function clickUndo(index) {
volumetricEvents.undo({ index })
}

function clickDelete(index) {
volumetricEvents.delete({ index })
}

const annotationCount = annotation.value.reduce((acc, a) => {
return (a.points.active.length > 0) ? acc + 1 : acc
}, 0)

return (
<Box>
<StyledInstructionText as="legend" size="small">
Expand Down Expand Up @@ -73,10 +121,29 @@ function VolumetricTask({
</Blank>
</StyledToolIcon>
}
labelStatus={<InputStatus count={annotation.value.length} />}
labelStatus={<InputStatus count={annotationCount} />}
name="volumetric-tool"
type="radio"
/>
<StyledAnnotation>
{annotation.value.map((mark, index) => {
return (mark.points.active.length === 0)
? null
: (<div className="mark" key={index}>
<div className="mark-color" style={{ backgroundColor: mark.color }}></div>
<div className="mark-label">Mark #{index+1}</div>
<div
className="mark-undo"
onClick={() => clickUndo(index)}
>Undo</div>
<div
className="mark-delete"
onClick={() => clickDelete(index)}
>Delete</div>
</div>
)
})}
</StyledAnnotation>
</Box>
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ const PointsModel = types.model('PointsModel', {
});

const AnnotationModel = types.model('AnnotationModel', {
color: types.string,
label: types.string,
threshold: types.number,
points: PointsModel,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ describe("Model > VolumetricTask", function () {
}

const mockAnnotationsSnapshot = [{
color: '#000000',
label: `Test Annotation`,
threshold: 15,
points: {
Expand Down
9 changes: 9 additions & 0 deletions packages/lib-classifier/src/store/Project/Project.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,15 @@ const Project = types
strings: TranslationStrings,
slug: types.string
})
.actions(self => {
function setVolumetricEvents(ve) {
self.volumetricEvents = ve
}

return {
setVolumetricEvents
}
})
.views(self => ({
get defaultWorkflow() {
const activeWorkflows = self.links['active_workflows']
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,12 @@ const WorkflowStepStore = types
get hasAnnotateTask () {
for (const key in self.workflow?.tasks) {
const task = self.workflow.tasks[key]
if (task.type === 'drawing' || task.type === 'transcription' || task.type === 'dataVisAnnotation' || task.type === 'subjectGroupComparison') {
if (task.type === 'dataVisAnnotation'
|| task.type === 'drawing'
|| task.type === 'subjectGroupComparison'
|| task.type === 'transcription'
|| task.type === 'volumetric'
) {
return true
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ export default function VolumetricViewer ({
}

export const VolumetricViewerData = ({
externalEvents = {},
onAnnotation = DEFAULT_HANDLER,
subjectData = '',
subjectUrl = ''
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ export const InputRangeDual = ({
<StyledContainer>
<div className='range-slider-container'>
<input
aria-label="Range slider minimum value"
className='range-slider-dual range-slider-lower-value'
max={state.valueMax}
min={state.valueMin}
Expand All @@ -115,6 +116,7 @@ export const InputRangeDual = ({
value={state.valueMinCurrent}
/>
<input
aria-label="Range slider max value"
className='range-slider-dual range-slider-upper-value'
max={state.valueMax}
min={state.valueMin}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@ export const Slider = ({ dimension, viewer }) => {
onMouseUp={inMouseUp}
/>
<input
aria-label={`Plane ${dimension} Slider`}
max={viewer.base - 1}
min='0'
onChange={inChange}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ export const AnnotationBase = ({ annotationIndex, point, viewer }) => {
}
}

export const ModelAnnotations = ({ externalEvents, onAnnotation }) => {
export const ModelAnnotations = ({ externalEvents = {}, onAnnotation }) => {
const annotationModel = {
annotations: [],
config: {
Expand Down Expand Up @@ -193,14 +193,7 @@ export const ModelAnnotations = ({ externalEvents, onAnnotation }) => {
const annotationExport = JSON.parse(JSON.stringify(annotationModel.annotations))
annotationExport.forEach(a => {
a.points.all = a.points.all.data
a.undo = () => {
console.log('annotation.undo() called!');
}
a.delete = () => {
console.log('annotation.delete() called!');
}
})
console.log('annotationExport #1', annotationExport)
onAnnotation(annotationExport)

},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,11 @@ describe('Component > VolumetricViewer > AlgorithmAStar', () => {
const data = Buffer.from(subjectData, 'base64')
const viewer = ModelViewer().initialize({ data })
const point = 1
const annotation = AnnotationBase({ point })
const annotation = AnnotationBase({
annotationIndex: 0,
point,
viewer
})

it('should generate the same connected points', () => {
const resultsP0 = AlgorithmAStar({ annotation, point: 0, viewer })
Expand Down
Loading

0 comments on commit 79bc425

Please sign in to comment.