diff --git a/packages/lib-classifier/src/components/Classifier/components/SubjectViewer/SubjectViewer.js b/packages/lib-classifier/src/components/Classifier/components/SubjectViewer/SubjectViewer.js
index 31b8f7431e..65250cfb7b 100644
--- a/packages/lib-classifier/src/components/Classifier/components/SubjectViewer/SubjectViewer.js
+++ b/packages/lib-classifier/src/components/Classifier/components/SubjectViewer/SubjectViewer.js
@@ -1,27 +1,21 @@
import asyncStates from '@zooniverse/async-states'
import PropTypes from 'prop-types'
import { useTranslation } from '@translations/i18n'
-import { lazy, Suspense } from 'react'
-
import { withStores } from '@helpers'
import getViewer from './helpers/getViewer'
-const VolumetricViewer = lazy(() => import('@zooniverse/subject-viewers/VolumetricViewer'))
-
function storeMapper(classifierStore) {
const {
subjects: { active: subject, loadingState: subjectQueueState },
subjectViewer: { onSubjectReady, onError, loadingState: subjectReadyState },
- projects: { active: project }
} = classifierStore
const drawingTasks = classifierStore?.workflowSteps.findTasksByType('drawing')
const transcriptionTasks = classifierStore?.workflowSteps.findTasksByType('transcription')
const enableInteractionLayer = drawingTasks.length > 0 || transcriptionTasks.length > 0
-
+
return {
enableInteractionLayer,
- isVolumetricViewer: project?.isVolumetricViewer ?? false,
onError,
onSubjectReady,
subject,
@@ -32,7 +26,6 @@ function storeMapper(classifierStore) {
function SubjectViewer({
enableInteractionLayer,
- isVolumetricViewer,
onError,
onSubjectReady,
subject,
@@ -53,23 +46,19 @@ function SubjectViewer({
return null
}
case asyncStates.success: {
- const Viewer = (isVolumetricViewer)
- ? VolumetricViewer
- : getViewer(subject?.viewer)
+ const Viewer = getViewer(subject?.viewer)
if (Viewer) {
return (
- Suspense boundary
}>
-
-
+
)
}
diff --git a/packages/lib-classifier/src/components/Classifier/components/SubjectViewer/SubjectViewer.spec.js b/packages/lib-classifier/src/components/Classifier/components/SubjectViewer/SubjectViewer.spec.js
index 35ac7a0dac..41ba788a66 100644
--- a/packages/lib-classifier/src/components/Classifier/components/SubjectViewer/SubjectViewer.spec.js
+++ b/packages/lib-classifier/src/components/Classifier/components/SubjectViewer/SubjectViewer.spec.js
@@ -39,20 +39,6 @@ describe('Component > SubjectViewer', function () {
expect(screen.getByLabelText('Subject 1234')).to.exist()
})
- it('should render the VolumetricViewer if isVolumetricViewer = true', async function () {
- render()
- expect(screen.getByText('Suspense boundary')).to.exist()
- expect(await screen.findByTestId('subject-viewer-volumetric')).to.exist()
- })
-
describe('when there is an null viewer because of invalid subject media', function () {
it('should render null', function () {
const { container } = render(
diff --git a/packages/lib-classifier/src/components/Classifier/components/SubjectViewer/components/VolumetricViewer/README.md b/packages/lib-classifier/src/components/Classifier/components/SubjectViewer/components/VolumetricViewer/README.md
new file mode 100644
index 0000000000..5e8cdb0251
--- /dev/null
+++ b/packages/lib-classifier/src/components/Classifier/components/SubjectViewer/components/VolumetricViewer/README.md
@@ -0,0 +1,3 @@
+# Volumetric Viewer
+
+Implementation of the `lib-subject-viewers/VolumetricViewer` component.
diff --git a/packages/lib-classifier/src/components/Classifier/components/SubjectViewer/components/VolumetricViewer/VolumetricMockSubject.js b/packages/lib-classifier/src/components/Classifier/components/SubjectViewer/components/VolumetricViewer/VolumetricMockSubject.js
new file mode 100644
index 0000000000..e653b6d17b
--- /dev/null
+++ b/packages/lib-classifier/src/components/Classifier/components/SubjectViewer/components/VolumetricViewer/VolumetricMockSubject.js
@@ -0,0 +1,9 @@
+export const VolumetricSubjectMock = {
+ id: 'mockSubject',
+ locations: [
+ {
+ 'application/json': 'https://zooniverse.org/subject.json'
+ },
+ ],
+ subjectJSON: 'GRnI+hnIr5bIr5Z9r5Z9+uHIr5bIr5Z9r5Z9ZJZ9ZEv6r5Z9r5Z9ZJZ9ZEt9ZEsyGZZ9GRl9ZEt9ZEsyGUsyGQ=='
+}
diff --git a/packages/lib-classifier/src/components/Classifier/components/SubjectViewer/components/VolumetricViewer/VolumetricViewer.spec.js b/packages/lib-classifier/src/components/Classifier/components/SubjectViewer/components/VolumetricViewer/VolumetricViewer.spec.js
new file mode 100644
index 0000000000..60420e7e84
--- /dev/null
+++ b/packages/lib-classifier/src/components/Classifier/components/SubjectViewer/components/VolumetricViewer/VolumetricViewer.spec.js
@@ -0,0 +1,19 @@
+import { render, screen } from '@testing-library/react'
+import { expect } from 'chai'
+import asyncStates from '@zooniverse/async-states'
+import VolumetricViewerWrapper from './VolumetricViewerWrapper'
+
+describe('Component > VolumetricViewer', function () {
+ it('should render the Volumetric Viewer asynchronously', async function () {
+ render()
+ expect(screen.getByText('Suspense boundary')).to.exist()
+ expect(await screen.findByTestId('subject-viewer-volumetric')).to.exist()
+ })
+})
diff --git a/packages/lib-classifier/src/components/Classifier/components/SubjectViewer/components/VolumetricViewer/VolumetricViewer.stories.js b/packages/lib-classifier/src/components/Classifier/components/SubjectViewer/components/VolumetricViewer/VolumetricViewer.stories.js
new file mode 100644
index 0000000000..1aefad7d8e
--- /dev/null
+++ b/packages/lib-classifier/src/components/Classifier/components/SubjectViewer/components/VolumetricViewer/VolumetricViewer.stories.js
@@ -0,0 +1,17 @@
+import asyncStates from '@zooniverse/async-states'
+import { VolumetricViewerWrapper } from './VolumetricViewerWrapper'
+import { VolumetricSubjectMock } from './VolumetricMockSubject'
+
+export default {
+ title: 'Subject Viewers / VolumetricViewer',
+ component: VolumetricViewerWrapper
+}
+
+export function Default() {
+ return (
+
+ )
+}
diff --git a/packages/lib-classifier/src/components/Classifier/components/SubjectViewer/components/VolumetricViewer/VolumetricViewerWrapper.js b/packages/lib-classifier/src/components/Classifier/components/SubjectViewer/components/VolumetricViewer/VolumetricViewerWrapper.js
new file mode 100644
index 0000000000..a90d9e244f
--- /dev/null
+++ b/packages/lib-classifier/src/components/Classifier/components/SubjectViewer/components/VolumetricViewer/VolumetricViewerWrapper.js
@@ -0,0 +1,35 @@
+import asyncStates from '@zooniverse/async-states'
+import { lazy, Suspense } from 'react'
+import { MobXProviderContext } from 'mobx-react'
+import { useContext } from 'react'
+
+const VolumetricViewer = lazy(() => import('@zooniverse/subject-viewers/VolumetricViewer'))
+const DEFAULT_HANDLER = () => {}
+
+function VolumetricViewerWrapper({
+ loadingState = asyncStates.initialized,
+ onError = DEFAULT_HANDLER,
+ onReady = DEFAULT_HANDLER,
+ subject,
+}) {
+ const stores = useContext(MobXProviderContext)
+ const addAnnotation = stores?.classifierStore?.classifications?.addAnnotation ?? DEFAULT_HANDLER
+ const activeStepTasks = stores?.classifierStore?.workflowSteps?.activeStepTasks ?? []
+
+ function onAnnotationUpdate(annotations) {
+ if (activeStepTasks[0])
+ addAnnotation(activeStepTasks[0], annotations)
+ }
+
+ return Suspense boundary}>
+
+
+}
+
+export default VolumetricViewerWrapper
diff --git a/packages/lib-classifier/src/components/Classifier/components/SubjectViewer/helpers/getViewer/getViewer.js b/packages/lib-classifier/src/components/Classifier/components/SubjectViewer/helpers/getViewer/getViewer.js
index 5be32db460..1012b4b724 100644
--- a/packages/lib-classifier/src/components/Classifier/components/SubjectViewer/helpers/getViewer/getViewer.js
+++ b/packages/lib-classifier/src/components/Classifier/components/SubjectViewer/helpers/getViewer/getViewer.js
@@ -7,6 +7,7 @@ import SingleImageViewer from '../../components/SingleImageViewer'
import SingleTextViewer from '../../components/SingleTextViewer'
import SingleVideoViewer from '../../components/SingleVideoViewer'
import SubjectGroupViewer from '../../components/SubjectGroupViewer'
+import VolumetricViewer from '../../components/VolumetricViewer/VolumetricViewerWrapper'
const viewers = {
dataImage: DataImageViewer,
@@ -20,7 +21,8 @@ const viewers = {
singleText: SingleTextViewer,
singleVideo: SingleVideoViewer,
subjectGroup: SubjectGroupViewer,
- variableStar: JSONDataViewer
+ variableStar: JSONDataViewer,
+ volumetric: VolumetricViewer
}
function getViewer (viewer) {
diff --git a/packages/lib-classifier/src/helpers/subjectViewers/subjectViewers.js b/packages/lib-classifier/src/helpers/subjectViewers/subjectViewers.js
index 481dfb07ca..fedfa88e49 100644
--- a/packages/lib-classifier/src/helpers/subjectViewers/subjectViewers.js
+++ b/packages/lib-classifier/src/helpers/subjectViewers/subjectViewers.js
@@ -55,12 +55,16 @@ Object.defineProperty(subjectViewers, 'subjectGroup', {
enumerable: true
})
-
Object.defineProperty(subjectViewers, 'variableStar', {
value: 'variableStar',
enumerable: true
})
+Object.defineProperty(subjectViewers, 'volumetric', {
+ value: 'volumetric',
+ enumerable: true
+})
+
// helper for returning subject viewers (e.g. for use in MST enumerable type)
Object.defineProperty(subjectViewers, 'values', {
value: Object.values(subjectViewers)
diff --git a/packages/lib-classifier/src/helpers/subjectViewers/subjectViewers.spec.js b/packages/lib-classifier/src/helpers/subjectViewers/subjectViewers.spec.js
index 4114a87ee7..e6245ee20a 100644
--- a/packages/lib-classifier/src/helpers/subjectViewers/subjectViewers.spec.js
+++ b/packages/lib-classifier/src/helpers/subjectViewers/subjectViewers.spec.js
@@ -13,7 +13,8 @@ describe('Helpers > subjectViewers', function () {
'singleText',
'singleVideo',
'subjectGroup',
- 'variableStar'
+ 'variableStar',
+ 'volumetric'
]
viewers.forEach(function (viewer) {
diff --git a/packages/lib-classifier/src/plugins/tasks/experimental/volumetric/components/VolumetricTask.js b/packages/lib-classifier/src/plugins/tasks/experimental/volumetric/components/VolumetricTask.js
index 8be639c1c0..818338a771 100644
--- a/packages/lib-classifier/src/plugins/tasks/experimental/volumetric/components/VolumetricTask.js
+++ b/packages/lib-classifier/src/plugins/tasks/experimental/volumetric/components/VolumetricTask.js
@@ -1,14 +1,12 @@
+import { bool, shape, string } from "prop-types"
import { Box, Text } from "grommet"
import { Blank } from "grommet-icons"
import InputStatus from "../../../components/InputStatus"
import { Markdownz } from "@zooniverse/react-components"
import { observer } from "mobx-react"
-import { bool, shape, string } from "prop-types"
import styled from "styled-components"
import TaskInput from "../../../components/TaskInput"
-// Note: ANNOTATION_COUNT will be refactored in next PR to use MobX Annotations
-const ANNOTATION_COUNT = 3
const SVG_ARROW = "48 50, 48 15, 40 15, 50 0, 60 15, 52 15, 52 50"
const StyledInstructionText = styled(Text)`
@@ -33,7 +31,11 @@ const StyledToolIcon = styled.div`
}
`
-function VolumetricTask({ disabled = false, task }) {
+function VolumetricTask({
+ annotation,
+ disabled = false,
+ task
+}) {
return (
@@ -71,7 +73,7 @@ function VolumetricTask({ disabled = false, task }) {
}
- labelStatus={}
+ labelStatus={}
name="volumetric-tool"
type="radio"
/>
diff --git a/packages/lib-classifier/src/plugins/tasks/experimental/volumetric/models/VolumetricAnnotation.js b/packages/lib-classifier/src/plugins/tasks/experimental/volumetric/models/VolumetricAnnotation.js
index 7587fa958d..426802df4e 100644
--- a/packages/lib-classifier/src/plugins/tasks/experimental/volumetric/models/VolumetricAnnotation.js
+++ b/packages/lib-classifier/src/plugins/tasks/experimental/volumetric/models/VolumetricAnnotation.js
@@ -1,10 +1,22 @@
import { types } from 'mobx-state-tree'
import Annotation from '../../../models/Annotation'
+const PointsModel = types.model('PointsModel', {
+ active: types.array(types.number),
+ connected: types.array(types.array(types.number), []),
+ all: types.array(types.number),
+});
+
+const AnnotationModel = types.model('AnnotationModel', {
+ label: types.string,
+ threshold: types.number,
+ points: PointsModel,
+});
+
const Volumetric = types
.model('Volumetric', {
taskType: types.literal('volumetric'),
- value: types.optional(types.string, ''),
+ value: types.array(AnnotationModel)
})
const VolumetricAnnotation = types.compose('VolumetricAnnotation', Annotation, Volumetric)
diff --git a/packages/lib-classifier/src/plugins/tasks/experimental/volumetric/models/VolumetricTask.spec.js b/packages/lib-classifier/src/plugins/tasks/experimental/volumetric/models/VolumetricTask.spec.js
index dbc2d3b7e4..512787c226 100644
--- a/packages/lib-classifier/src/plugins/tasks/experimental/volumetric/models/VolumetricTask.spec.js
+++ b/packages/lib-classifier/src/plugins/tasks/experimental/volumetric/models/VolumetricTask.spec.js
@@ -19,6 +19,16 @@ describe("Model > VolumetricTask", function () {
type: "single",
}
+ const mockAnnotationsSnapshot = [{
+ label: `Test Annotation`,
+ threshold: 15,
+ points: {
+ active: [],
+ connected: [],
+ all: []
+ }
+ }]
+
it("should exist", function () {
const task = VolumetricTask.TaskModel.create(volumetricTask)
expect(task).to.be.ok()
@@ -65,13 +75,13 @@ describe("Model > VolumetricTask", function () {
annotation = task.defaultAnnotation()
})
- it("should start up with an empty string", function () {
- expect(annotation.value).to.equal("")
+ it("should start up with an empty array", function () {
+ expect(annotation.value).to.deep.equal([])
})
it("should update annotations", function () {
- annotation.update("Hello there!")
- expect(annotation.value).to.equal("Hello there!")
+ annotation.update(mockAnnotationsSnapshot);
+ expect(annotation.value).to.deep.equal(mockAnnotationsSnapshot)
})
})
})
diff --git a/packages/lib-classifier/src/store/subjects/Subject/Subject.js b/packages/lib-classifier/src/store/subjects/Subject/Subject.js
index e1125da301..a7adceab1f 100644
--- a/packages/lib-classifier/src/store/subjects/Subject/Subject.js
+++ b/packages/lib-classifier/src/store/subjects/Subject/Subject.js
@@ -57,6 +57,10 @@ const Subject = types
viewer = configuration.viewerType
+ // Volumetric Viewer is set at the Project level
+ if (!viewer && self.project?.isVolumetricViewer)
+ viewer = subjectViewers.volumetric
+
if (!viewer && counts.total === 1) {
if (counts.images) {
viewer = subjectViewers.singleImage
diff --git a/packages/lib-subject-viewers/src/VolumetricViewer/VolumetricViewer.js b/packages/lib-subject-viewers/src/VolumetricViewer/VolumetricViewer.js
index fd7f3d27e0..bcb2fa37b9 100644
--- a/packages/lib-subject-viewers/src/VolumetricViewer/VolumetricViewer.js
+++ b/packages/lib-subject-viewers/src/VolumetricViewer/VolumetricViewer.js
@@ -11,14 +11,15 @@ const DEFAULT_HANDLER = () => {}
export default function VolumetricViewer ({
loadingState = asyncStates.initialized,
+ onAnnotation = DEFAULT_HANDLER,
onError = DEFAULT_HANDLER,
onReady = DEFAULT_HANDLER,
subject
}) {
const { data, loading, error } = useVolumetricSubject({ onError, onReady, subject })
-
+
const [modelState] = useState({
- annotations: ModelAnnotations(),
+ annotations: ModelAnnotations({ onAnnotation }),
tool: ModelTool(),
viewer: ModelViewer()
})
@@ -46,14 +47,18 @@ export default function VolumetricViewer ({
/>
}
-export const VolumetricViewerData = ({ subjectData = '', subjectUrl = '' }) => {
+export const VolumetricViewerData = ({
+ onAnnotation = DEFAULT_HANDLER,
+ subjectData = '',
+ subjectUrl = ''
+}) => {
return {
data: {
config: {},
subjectData,
subjectUrl,
models: {
- annotations: ModelAnnotations(),
+ annotations: ModelAnnotations({ onAnnotation }),
tool: ModelTool(),
viewer: ModelViewer()
}
diff --git a/packages/lib-subject-viewers/src/VolumetricViewer/models/ModelAnnotations.js b/packages/lib-subject-viewers/src/VolumetricViewer/models/ModelAnnotations.js
index 6737b0ea01..ea71aa1d6d 100644
--- a/packages/lib-subject-viewers/src/VolumetricViewer/models/ModelAnnotations.js
+++ b/packages/lib-subject-viewers/src/VolumetricViewer/models/ModelAnnotations.js
@@ -1,6 +1,6 @@
import { SortedSet, SortedSetUnion } from './../helpers/SortedSet.js'
-const THRESHOLD_DEFAULT = 30
+const THRESHOLD_DEFAULT = 15
let ANNOTATION_COUNT = 0
// Creates the base object for an Annotation
@@ -49,7 +49,7 @@ const History = {
}
}
-export const ModelAnnotations = () => {
+export const ModelAnnotations = ({ onAnnotation }) => {
const annotationModel = {
annotations: [],
config: {
@@ -106,6 +106,11 @@ export const ModelAnnotations = () => {
annotationIndex,
annotations: annotationModel.annotations
})
+
+ // Send to update handler
+ const annotationExport = JSON.parse(JSON.stringify(annotationModel.annotations))
+ annotationExport.forEach(a => a.points.all = a.points.all.data)
+ onAnnotation(annotationExport)
},
active: ({ index }) => {
// Update the Annotation Data
@@ -206,6 +211,11 @@ export const ModelAnnotations = () => {
annotationIndex: _index,
annotations: annotationModel.annotations
})
+
+ // Send to update handler
+ const annotationExport = JSON.parse(JSON.stringify(annotationModel.annotations))
+ annotationExport.forEach(a => a.points.all = a.points.all.data)
+ onAnnotation(annotationExport)
}
}
}
diff --git a/packages/lib-subject-viewers/src/VolumetricViewer/tests/ModelAnnotations.spec.js b/packages/lib-subject-viewers/src/VolumetricViewer/tests/ModelAnnotations.spec.js
index 6fa8f8100a..f621b2df96 100644
--- a/packages/lib-subject-viewers/src/VolumetricViewer/tests/ModelAnnotations.spec.js
+++ b/packages/lib-subject-viewers/src/VolumetricViewer/tests/ModelAnnotations.spec.js
@@ -1,7 +1,8 @@
import { ModelAnnotations } from './../models/ModelAnnotations'
describe('Component > VolumetricViewer > ModelAnnotations', () => {
- const model = ModelAnnotations()
+ const ANNOTATION_THRESHOLD = 15
+ const model = ModelAnnotations({ onAnnotation: () => {} })
const viewerMock = {
setPointsAnnotationIndex: () => {}
}
@@ -44,7 +45,7 @@ describe('Component > VolumetricViewer > ModelAnnotations', () => {
expect(obj.annotations[0]).to.equal(obj.annotation)
expect(obj.annotation.label).to.equal('Annotation 2')
- expect(obj.annotation.threshold).to.equal(30)
+ expect(obj.annotation.threshold).to.equal(ANNOTATION_THRESHOLD)
expect(obj.annotation.points.active).deep.to.equal([activePoint])
expect(obj.annotation.points.connected).deep.to.equal([[]])
expect(obj.annotation.points.all.data).deep.to.equal([])
@@ -73,7 +74,7 @@ describe('Component > VolumetricViewer > ModelAnnotations', () => {
expect(obj.annotations[1]).to.equal(obj.annotation)
expect(obj.annotation.label).to.equal('Annotation 3')
- expect(obj.annotation.threshold).to.equal(30)
+ expect(obj.annotation.threshold).to.equal(ANNOTATION_THRESHOLD)
expect(obj.annotation.points.active).deep.to.equal([pointToAdd])
expect(obj.annotation.points.connected).deep.to.equal([[]])
expect(obj.annotation.points.all.data).deep.to.equal([])
@@ -139,7 +140,7 @@ describe('Component > VolumetricViewer > ModelAnnotations', () => {
expect(obj.annotations[1]).to.equal(obj.annotation)
expect(obj.annotation.label).to.equal('Annotation 4')
- expect(obj.annotation.threshold).to.equal(30)
+ expect(obj.annotation.threshold).to.equal(ANNOTATION_THRESHOLD)
expect(obj.annotation.points.active).deep.to.equal([activePoint])
expect(obj.annotation.points.connected).deep.to.equal([[]])
expect(obj.annotation.points.all.data).deep.to.equal([])
@@ -189,7 +190,7 @@ describe('Component > VolumetricViewer > ModelAnnotations', () => {
active: [2],
connected: [[]]
},
- threshold: 30
+ threshold: 15
},
{
label: 'Annotation 4',
@@ -197,7 +198,7 @@ describe('Component > VolumetricViewer > ModelAnnotations', () => {
active: [3, 4],
connected: [[], []]
},
- threshold: 30
+ threshold: 15
}
])
})