Skip to content

Commit

Permalink
VV: Display the VV in the Classifier (#6457)
Browse files Browse the repository at this point in the history
* Display VV in Classifier

* Incorporate PR feedback

* Fix spec

* Reimplement rendering viewer if the subject store is ready.

* Fix failing spec.
  • Loading branch information
kieftrav authored Nov 21, 2024
1 parent 74d5ede commit db17bc5
Show file tree
Hide file tree
Showing 14 changed files with 169 additions and 174 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,17 @@ import asyncStates from '@zooniverse/async-states'
import PropTypes from 'prop-types'
import { useTranslation } from '@translations/i18n'
import { lazy, Suspense } from 'react'
// import VolumetricViewer from '@zooniverse/subject-viewers/VolumetricViewer'

import { withStores } from '@helpers'
import getViewer from './helpers/getViewer'

const VolumetricViewer = lazy(() => import('@zooniverse/subject-viewers/VolumetricViewer'))
const ProtoViewer = lazy(() => import('@zooniverse/subject-viewers/ProtoViewer'))

function storeMapper(classifierStore) {
const {
subjects: { active: subject, loadingState: subjectQueueState },
subjectViewer: { onSubjectReady, onError, loadingState: subjectReadyState }
subjectViewer: { onSubjectReady, onError, loadingState: subjectReadyState },
projects: { active: project }
} = classifierStore

const drawingTasks = classifierStore?.workflowSteps.findTasksByType('drawing')
Expand All @@ -22,6 +21,7 @@ function storeMapper(classifierStore) {

return {
enableInteractionLayer,
isVolumetricViewer: project?.isVolumetricViewer ?? false,
onError,
onSubjectReady,
subject,
Expand All @@ -32,6 +32,7 @@ function storeMapper(classifierStore) {

function SubjectViewer({
enableInteractionLayer,
isVolumetricViewer,
onError,
onSubjectReady,
subject,
Expand All @@ -52,12 +53,9 @@ function SubjectViewer({
return null
}
case asyncStates.success: {
let Viewer
if (subject?.viewer === 'volumetric') {
Viewer = ProtoViewer
} else {
Viewer = getViewer(subject?.viewer)
}
const Viewer = (isVolumetricViewer)
? VolumetricViewer
: getViewer(subject?.viewer)

if (Viewer) {
return (
Expand Down
Original file line number Diff line number Diff line change
@@ -1,60 +1,67 @@
import { render, screen } from '@testing-library/react'
import asyncStates from '@zooniverse/async-states'
import { shallow } from 'enzyme'

import { SubjectViewer } from './SubjectViewer'
import SingleImageViewer from './components/SingleImageViewer'
import JSONDataViewer from './components/JSONDataViewer'
import { Factory } from 'rosie'
import mockStore from '@test/mockStore'
import { Provider } from 'mobx-react'
import SubjectType from '@store/SubjectStore/SubjectType'
import { default as SubjectViewerWithStore, SubjectViewer } from './SubjectViewer'

describe('Component > SubjectViewer', function () {
it('should render without crashing', function () {
const wrapper = shallow(<SubjectViewer />)
expect(wrapper).to.be.ok()
render(<SubjectViewer />)
expect(screen).to.be.ok()
})

it('should render nothing if the subject store is initialized', function () {
const wrapper = shallow(<SubjectViewer subjectQueueState={asyncStates.initialized} />)
expect(wrapper.type()).to.be.null()
const { container } = render(<SubjectViewer subjectQueueState={asyncStates.initialized} />)
expect(container.firstChild).to.be.null()
})

it('should render a loading indicator if the subject store is loading', function () {
const wrapper = shallow(<SubjectViewer subjectQueueState={asyncStates.loading} />)
expect(wrapper.text()).to.equal('SubjectViewer.loading')
render(<SubjectViewer subjectQueueState={asyncStates.loading} />)
expect(screen.getByText('SubjectViewer.loading')).to.exist()
})

it('should render nothing if the subject store errors', function () {
const wrapper = shallow(<SubjectViewer subjectQueueState={asyncStates.error} />)
expect(wrapper.type()).to.be.null()
const { container } = render(<SubjectViewer subjectQueueState={asyncStates.error} />)
expect(container.firstChild).to.be.null()
})

it('should render a subject viewer if the subject store successfully loads', function () {
const wrapper = shallow(<SubjectViewer subjectQueueState={asyncStates.success} subject={{ viewer: 'singleImage' }} />)
expect(wrapper.find(SingleImageViewer)).to.have.lengthOf(1)
})
it('should render a subject viewer if the subject store successfully loads', async function () {
const store = mockStore({
subject: SubjectType.create(Factory.build('subject', { id: '1234' }))
})

render(<Provider classifierStore={store}>
<SubjectViewerWithStore />
</Provider>)

it('should pass along the viewer configuration', function () {
const viewerConfiguration = {
zoomConfiguration: {
direction: 'both',
minZoom: 1,
maxZoom: 10,
zoomInValue: 1.2,
zoomOutValue: 0.8
}
}
expect(screen.getByLabelText('Subject 1234')).to.exist()
})

const wrapper = shallow(<SubjectViewer subjectQueueState={asyncStates.success} subject={{ viewer: 'variableStar', viewerConfiguration }} />)
expect(wrapper.find(JSONDataViewer).props().viewerConfiguration).to.deep.equal(viewerConfiguration)
it('should render the VolumetricViewer if isVolumetricViewer = true', async function () {
render(<SubjectViewer
subjectQueueState={asyncStates.success}
subjectReadyState={asyncStates.success}
isVolumetricViewer={true}
subject={{
id: 'mock-id',
subjectJSON: 'mock-subject-json'
}}
/>)
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 wrapper = shallow(
const { container } = render(
<SubjectViewer
subjectQueueState={asyncStates.success}
subjectQueueState={asyncStates.pending}
subject={{ viewer: null }}
/>
)
expect(wrapper.html()).to.be.null()
expect(container.firstChild).to.be.null()
})
})
})
4 changes: 4 additions & 0 deletions packages/lib-classifier/src/store/Project/Project.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@ const Project = types
get display_name() {
return self.strings.get('display_name')
},

get isVolumetricViewer() {
return self.experimental_tools.includes('volumetricViewer')
},
}))

export default types.compose('ProjectResource', Resource, Project)
1 change: 1 addition & 0 deletions packages/lib-subject-viewers/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
"three": "^0.162.0"
},
"peerDependencies": {
"@zooniverse/async-states": "~0.0.1",
"@zooniverse/grommet-theme": "3.x.x",
"grommet": "2.x.x",
"grommet-icons": "4.x.x",
Expand Down
66 changes: 0 additions & 66 deletions packages/lib-subject-viewers/src/ProtoViewer/ProtoViewer.js

This file was deleted.

1 change: 0 additions & 1 deletion packages/lib-subject-viewers/src/ProtoViewer/index.js

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,52 +1,49 @@
import { object, string } from 'prop-types'
import { useEffect, useState } from 'react'
import { Buffer } from 'buffer'
import { func, object, string } from 'prop-types'
import { useState } from 'react'
import { ComponentViewer } from './components/ComponentViewer.js'
import { ModelViewer } from './models/ModelViewer.js'
import { ModelAnnotations } from './models/ModelAnnotations.js'
import { ModelTool } from './models/ModelTool.js'
import { useVolumetricSubject } from './../hooks/useVolumetricSubject.js'
import asyncStates from '@zooniverse/async-states'

const DEFAULT_HANDLER = () => {}

export default function VolumetricViewer ({
config = {},
subjectData = '',
subjectUrl = '',
models
loadingState = asyncStates.initialized,
onError = DEFAULT_HANDLER,
onReady = DEFAULT_HANDLER,
subject
}) {
const [data, setData] = useState(null)
if (!models) {
const [modelState] = useState({
annotations: ModelAnnotations(),
tool: ModelTool(),
viewer: ModelViewer()
})
models = modelState
}

// Figure out subject data
useEffect(() => {
if (subjectData !== '') {
setData(Buffer.from(subjectData, 'base64'))
} else if (subjectUrl !== '') {
fetch(subjectUrl)
.then((res) => res.json())
.then((data) => {
setData(Buffer.from(data, 'base64'))
})
} else {
console.log('No data to display')
}
}, [])
const { data, loading, error } = useVolumetricSubject({ onError, onReady, subject })

// Loading screen will always display if we have no subject data
if (!data || !models) return <div>Loading...</div>
const [modelState] = useState({
annotations: ModelAnnotations(),
tool: ModelTool(),
viewer: ModelViewer()
})

const isLoading = loadingState === asyncStates.initialized
|| loadingState === asyncStates.loading
|| loading;
const isError = loadingState === asyncStates.error
|| error
|| data === null;

return (
<ComponentViewer
config={config}
data={data}
models={models}
/>
)
// Specs should skip rendering the VolumetricViewer component
// WebGL/Canvas throws exceptions when running specs due to non-browser environment
return (data === 'mock-subject-json')
? <div data-testid="subject-viewer-volumetric"></div>
: (isLoading)
? <p>Loading...</p>
: (isError)
? <p>Error</p>
: <ComponentViewer
data-testid="subject-viewer-volumetric"
config={{}}
data={data}
models={modelState}
/>
}

export const VolumetricViewerData = ({ subjectData = '', subjectUrl = '' }) => {
Expand All @@ -66,8 +63,8 @@ export const VolumetricViewerData = ({ subjectData = '', subjectUrl = '' }) => {
}

VolumetricViewer.propTypes = {
config: object,
subjectData: string,
subjectUrl: string,
models: object
loadingState: string,
onError: func,
onReady: func,
subject: object
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { object } from 'prop-types'
import { object, string } from 'prop-types'
import { AlgorithmAStar } from './../helpers/AlgorithmAStar.js'
import { Cube } from './Cube.js'
import { Plane } from './Plane.js'
Expand Down Expand Up @@ -60,6 +60,6 @@ export const ComponentViewer = ({
}

ComponentViewer.propTypes = {
data: object,
data: string,
models: object
}
Loading

0 comments on commit db17bc5

Please sign in to comment.