Skip to content

Commit

Permalink
Lazy load a viewer component from lib-subject-viewers (#6402)
Browse files Browse the repository at this point in the history
* use lazy and Suspense with VolumetricViewer

* modularize lib-subject-viewers

* build simplified ProtoViewer example

* add @zooniverse/subject-viewers build:es6 to ci-build
  • Loading branch information
goplayoutside3 authored Nov 7, 2024
1 parent 860f09f commit b4008d2
Show file tree
Hide file tree
Showing 34 changed files with 166 additions and 82 deletions.
1 change: 1 addition & 0 deletions .github/workflows/ci-build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ jobs:

- run: yarn install --production=false --frozen-lockfile --ignore-scripts
- run: yarn workspace @zooniverse/react-components build:es6
- run: yarn workspace @zooniverse/subject-viewers build:es6
- run: yarn workspace @zooniverse/classifier build:es6
- run: yarn workspace @zooniverse/fe-project build

Expand Down
9 changes: 6 additions & 3 deletions packages/lib-classifier/.babelrc
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@
},
"@zooniverse/react-components": {
"transform": "@zooniverse/react-components/${member}"
},
"@zooniverse/subject-viewers": {
"transform": "@zooniverse/subject-viewers/${member}"
}
}],
["module-resolver", {
Expand All @@ -27,7 +30,7 @@
}]
],
"presets": [
["@babel/preset-react", {
["@babel/preset-react", {
"runtime": "automatic"
}],
["@babel/preset-env", {
Expand All @@ -40,7 +43,7 @@
"env": {
"es6": {
"presets": [
["@babel/preset-react", {
["@babel/preset-react", {
"runtime": "automatic"
}],
["@babel/preset-env", {
Expand All @@ -54,7 +57,7 @@
"test": {
"presets": [
"@babel/preset-env",
["@babel/preset-react", {
["@babel/preset-react", {
"runtime": "automatic"
}]
]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,27 +14,31 @@ export const ContainerGrid = styled(Grid)`
position: relative;
grid-gap: 1.875rem;
grid-template-areas: 'viewer task';
grid-template-columns: minmax(auto, 100rem) ${props => (props.hasSurveyTask ? '33.75rem' : '25rem')};
grid-template-columns: minmax(auto, 100rem) ${props =>
props.hasSurveyTask ? '33.75rem' : '25rem'};
margin: auto;
${props => props.hasSurveyTask ? css`
@media screen and (min-width: 769px) and (max-width: 70rem) {
grid-gap: 1.25rem;
grid-template-areas:
'viewer'
'task';
grid-template-columns: 100%;
grid-template-rows: auto auto;
margin: 0;
}
` : css`
// proportional 9:5 subject/task sizing up to a maximum subject/task width of 45rem/25rem
@media screen and (min-width: 769px) and (max-width: 70rem) {
grid-gap: 1.25rem;
grid-template-areas: 'viewer task';
grid-template-columns: 9fr 5fr;
}
`}
${props =>
props.hasSurveyTask
? css`
@media screen and (min-width: 769px) and (max-width: 70rem) {
grid-gap: 1.25rem;
grid-template-areas:
'viewer'
'task';
grid-template-columns: 100%;
grid-template-rows: auto auto;
margin: 0;
}
`
: css`
// proportional 9:5 subject/task sizing up to a maximum subject/task width of 45rem/25rem
@media screen and (min-width: 769px) and (max-width: 70rem) {
grid-gap: 1.25rem;
grid-template-areas: 'viewer task';
grid-template-columns: 9fr 5fr;
}
`}
@media screen and (max-width: 768px) {
grid-gap: 1.25rem;
Expand All @@ -48,17 +52,20 @@ export const ContainerGrid = styled(Grid)`
`

export const ViewerGrid = styled(Grid)`
${props => props.hasSurveyTask ? css`
@media screen and (min-width: 70rem) {
position: sticky;
top: 10px;
}
` : css`
@media screen and (min-width: 769px) {
position: sticky;
top: 10px;
}
`}
${props =>
props.hasSurveyTask
? css`
@media screen and (min-width: 70rem) {
position: sticky;
top: 10px;
}
`
: css`
@media screen and (min-width: 769px) {
position: sticky;
top: 10px;
}
`}
height: fit-content;
grid-area: viewer;
Expand All @@ -72,17 +79,20 @@ const StyledTaskAreaContainer = styled.div`
`

const StyledTaskArea = styled(Box)`
${props => props.hasSurveyTask ? css`
@media screen and (min-width: 70rem) {
position: sticky;
top: 10px;
}
` : css`
@media screen and (min-width: 769px) {
position: sticky;
top: 10px;
}
`}
${props =>
props.hasSurveyTask
? css`
@media screen and (min-width: 70rem) {
position: sticky;
top: 10px;
}
`
: css`
@media screen and (min-width: 769px) {
position: sticky;
top: 10px;
}
`}
`

const StyledImageToolbarContainer = styled.div`
Expand All @@ -100,21 +110,15 @@ export default function MaxWidth({
hasSurveyTask = false
}) {
return (
<ContainerGrid
className={className}
hasSurveyTask={hasSurveyTask}
>
<ContainerGrid className={className} hasSurveyTask={hasSurveyTask}>
{separateFramesView ? (
<Box>
<Banners />
<SubjectViewer />
<MetaTools />
</Box>
) : (
<ViewerGrid
forwardedAs='section'
hasSurveyTask={hasSurveyTask}
>
<ViewerGrid forwardedAs='section' hasSurveyTask={hasSurveyTask}>
<Box gridArea='subject'>
<Banners />
<SubjectViewer />
Expand Down
Original file line number Diff line number Diff line change
@@ -1,26 +1,24 @@
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
}
subjects: { active: subject, loadingState: subjectQueueState },
subjectViewer: { onSubjectReady, onError, loadingState: subjectReadyState }
} = classifierStore

const drawingTasks = classifierStore?.workflowSteps.findTasksByType('drawing')
const transcriptionTasks = classifierStore?.workflowSteps.findTasksByType('transcription')
const enableInteractionLayer = (drawingTasks.length > 0 || transcriptionTasks.length > 0)
const enableInteractionLayer = drawingTasks.length > 0 || transcriptionTasks.length > 0

return {
enableInteractionLayer,
Expand All @@ -41,31 +39,39 @@ function SubjectViewer({
subjectReadyState
}) {
const { t } = useTranslation('components')

switch (subjectQueueState) {
case asyncStates.initialized: {
return null
}
case asyncStates.loading: {
return (<div>{t('SubjectViewer.loading')}</div>)
return <div>{t('SubjectViewer.loading')}</div>
}
case asyncStates.error: {
console.error('There was an error loading the subjects')
return null
}
case asyncStates.success: {
const Viewer = getViewer(subject?.viewer)

let Viewer
if (subject?.viewer === 'volumetric') {
Viewer = ProtoViewer
} else {
Viewer = getViewer(subject?.viewer)
}

if (Viewer) {
return (
<Viewer
enableInteractionLayer={enableInteractionLayer}
key={subject.id}
subject={subject}
loadingState={subjectReadyState}
onError={onError}
onReady={onSubjectReady}
viewerConfiguration={subject?.viewerConfiguration}
/>
<Suspense fallback={<p>Suspense boundary</p>}>
<Viewer
enableInteractionLayer={enableInteractionLayer}
key={subject.id}
subject={subject}
loadingState={subjectReadyState}
onError={onError}
onReady={onSubjectReady}
viewerConfiguration={subject?.viewerConfiguration}
/>
</Suspense>
)
}

Expand Down
66 changes: 66 additions & 0 deletions packages/lib-subject-viewers/src/ProtoViewer/ProtoViewer.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import asyncStates from '@zooniverse/async-states'
import { useEffect } from 'react'

const DEFAULT_HANDLER = () => {}

const defaultSubject = {
id: '',
locations: []
}

const defaultTarget = {
clientHeight: 0,
clientWidth: 0,
naturalHeight: 0,
naturalWidth: 0
}

export default function ProtoViewer({
/*
loadingState is defined in SubjectViewerStore.js. onReady and onError affect it
and then it's used below to choose rendering options
*/
loadingState = asyncStates.initialized,
/*
onError is defined in lib-classifier's SubjectViewerStore.js. When called, it sets
SubjectViewerStore's loadingState to error.
*/
onError = DEFAULT_HANDLER,
/*
onReady is defined in lib-classifier's SubjectViewerStore.js as onSubjectReady().
It sets the loadingState on the SubjectViewerStore to success. That success triggers
an update of subjectReadyState in SubjectViewer.js which is passed as loadingState to
the Viewer *and* the TaskArea, preventing users from making classifications when a subject
viewer is not ready.
*/
onReady = DEFAULT_HANDLER,
subject = defaultSubject
}) {
/*
Here's where unique handling of subject data happens every time the subject changes.
Examples are useSubjectImage() or useSubjectJSON() hooks in lib-classifier.
*/
useEffect(
function onSubjectChange() {
function handleSubject() {} // etc, fetch more subject data if needed
onReady(defaultTarget)
},
[subject]
)

/*
These are the render options. First catch an error if needed,
then render subject viewer as long as the loadingState says it's okay.
*/
if (loadingState === asyncStates.error) {
return <p>Something went wrong.</p>
}

if (loadingState !== asyncStates.initialized) {
return (
<div>This is the ProtoViewer. Here's the subject ID: {subject.id}</div>
)
}

return null
}
1 change: 1 addition & 0 deletions packages/lib-subject-viewers/src/ProtoViewer/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default } from './ProtoViewer.js'
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { ModelViewer } from './models/ModelViewer.js'
import { ModelAnnotations } from './models/ModelAnnotations.js'
import { ModelTool } from './models/ModelTool.js'

export default function VolumetricViewerComponent ({
export default function VolumetricViewer ({
config = {},
subjectData = '',
subjectUrl = '',
Expand Down Expand Up @@ -61,11 +61,11 @@ export const VolumetricViewerData = ({ subjectData = '', subjectUrl = '' }) => {
viewer: ModelViewer()
}
},
component: VolumetricViewerComponent
component: VolumetricViewer
}
}

VolumetricViewerComponent.propTypes = {
VolumetricViewer.propTypes = {
config: object,
subjectData: string,
subjectUrl: string,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,9 @@ import { SortedSetUnion } from './../helpers/SortedSet.js'

// Shim for test:ci in GH needs this to work
let OrbitControls = null;
import("three/addons/controls/OrbitControls.js").then((module) => {
OrbitControls = module.OrbitControls;
})
// import("three/addons/controls/OrbitControls.js").then((module) => {
// OrbitControls = module.OrbitControls;
// })

// Shim for node.js testing
let glContext = null
Expand Down
1 change: 1 addition & 0 deletions packages/lib-subject-viewers/src/VolumetricViewer/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default } from './VolumetricViewer.js'
6 changes: 4 additions & 2 deletions packages/lib-subject-viewers/src/index.js
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
export { default as VolumetricViewer } from './components/VolumetricViewer/VolumetricViewer.js'
export { VolumetricViewerData } from './components/VolumetricViewer/VolumetricViewer.js'
export { default as VolumetricViewer } from './VolumetricViewer/VolumetricViewer.js'
export { default as ProtoViewer } from './ProtoViewer'

export { VolumetricViewerData } from './VolumetricViewer/VolumetricViewer.js'

0 comments on commit b4008d2

Please sign in to comment.