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

VV: Display the VV in the Classifier #6457

Merged
merged 5 commits into from
Nov 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
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