Skip to content

Commit

Permalink
NGL renderer (#2711)
Browse files Browse the repository at this point in the history
  • Loading branch information
fiskus authored Mar 2, 2022
1 parent 85f6690 commit 21d2a87
Show file tree
Hide file tree
Showing 9 changed files with 30,261 additions and 15,417 deletions.
23 changes: 23 additions & 0 deletions catalog/app/@types/ngl.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
interface LoadFileArgs {
defaultRepresentation?: boolean
ext: string
}

interface IDecompressorRegistry {
get(ext: string): (input: string) => string
}

interface StageParams {
backgroundColor: string
}

declare module 'ngl' {
export class Stage {
constructor(wrapper: HTMLDivElement, options: StageParams)
handleResize(): void
loadFile(blob: Blob, options: LoadFileArgs): Promise<void>
dispose(): void
}

export const DecompressorRegistry: IDecompressorRegistry
}
2 changes: 2 additions & 0 deletions catalog/app/components/Preview/load.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import * as Html from './loaders/Html'
import * as Image from './loaders/Image'
import * as Json from './loaders/Json'
import * as Markdown from './loaders/Markdown'
import * as Ngl from './loaders/Ngl'
import * as Notebook from './loaders/Notebook'
import * as Pdf from './loaders/Pdf'
import * as Tabular from './loaders/Tabular'
Expand All @@ -21,6 +22,7 @@ const loaderChain = [
Echarts, // should be before Json, or TODO: add "type is not 'echarts'" to Json.detect
Json,
Markdown,
Ngl,
Voila, // should be before Notebook, or TODO: add "type is not 'voila'" to Notebook.detect
Notebook,
Pdf,
Expand Down
33 changes: 33 additions & 0 deletions catalog/app/components/Preview/loaders/Ngl.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import type { PromiseResult } from 'aws-sdk/lib/request'
import * as R from 'ramda'
import * as React from 'react'
import { DecompressorRegistry } from 'ngl'

import type { S3HandleBase } from 'utils/s3paths'

import { PreviewData } from '../types'

import * as utils from './utils'

export const detect = R.pipe(utils.stripCompression, utils.extIs('.pdb'))

const gzipDecompress = DecompressorRegistry.get('gz')

interface NglLoaderProps {
children: (result: $TSFixMe) => React.ReactNode
handle: S3HandleBase
}

export const Loader = function NglLoader({ handle, children }: NglLoaderProps) {
const data = utils.useObjectGetter(handle)
const processed = utils.useProcessing(
data.result,
(r: PromiseResult<{ Body: Uint8Array | string }, null>) => {
const compression = utils.getCompression(handle.key)
const body = compression === 'gz' ? gzipDecompress(r.Body as string) : r.Body
return PreviewData.Ngl({ blob: new Blob([body]) })
},
)
const handled = utils.useErrorHandling(processed, { handle, retry: data.fetch })
return children(handled)
}
61 changes: 61 additions & 0 deletions catalog/app/components/Preview/renderers/Ngl.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import * as NGL from 'ngl'
import * as React from 'react'
import * as M from '@material-ui/core'

async function renderNgl(blob: Blob, wrapperEl: HTMLDivElement, t: M.Theme) {
const stage = new NGL.Stage(wrapperEl, { backgroundColor: t.palette.common.white })

const resizeObserver = new window.ResizeObserver(() => stage.handleResize())
resizeObserver.observe(wrapperEl)

await stage.loadFile(blob, {
defaultRepresentation: true,
ext: 'pdb',
})
return stage
}

const useStyles = M.makeStyles((t) => ({
root: {
height: t.spacing(50),
overflow: 'auto',
resize: 'vertical',
width: '100%',
},
}))

interface NglProps extends React.HTMLAttributes<HTMLDivElement> {
blob: Blob
}

function Ngl({ blob, ...props }: NglProps) {
const classes = useStyles()

const t = M.useTheme()
const viewport = React.useRef<HTMLDivElement | null>(null)

const handleWheel = React.useCallback(
(event) => {
if (viewport.current?.contains(event.target)) {
event.preventDefault()
}
},
[viewport],
)

React.useEffect(() => {
let stage: NGL.Stage
if (viewport.current) {
renderNgl(blob, viewport.current, t).then((s) => (stage = s))
window.addEventListener('wheel', handleWheel, { passive: false })
}
return () => {
stage?.dispose()
window.removeEventListener('wheel', handleWheel)
}
}, [viewport, blob, handleWheel, t])

return <div ref={viewport} className={classes.root} {...props} />
}

export default (img: NglProps, props: NglProps) => <Ngl {...img} {...props} />
1 change: 1 addition & 0 deletions catalog/app/components/Preview/renderers/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ export { default as IFrame } from './IFrame'
export { default as Image } from './Image'
export { default as Json } from './Json'
export { default as Markdown } from './Markdown'
export { default as Ngl } from './Ngl'
export { default as Notebook } from './Notebook'
export { default as Pdf } from './Pdf'
export { default as Perspective } from './Perspective'
Expand Down
1 change: 1 addition & 0 deletions catalog/app/components/Preview/types.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ export const PreviewData = tagged([
'Json', // { rendered: object }
'Markdown', // { rendered: string }
'Notebook', // { preview: string, ...PreviewStatus }
'Ngl', // { blob: Blob([Uint8Array|string]) }
'Pdf', // { handle: object, pages: number, firstPageBlob: Blob, type: 'pdf' | 'pptx' }
'Perspective', // { context: CONTEXT, data: string | ArrayBuffer, handle: S3Handle, meta: ParquetMeta, onLoadMore: () => void, truncated: boolean }
'Text', // { head: string, tail: string, lang: string, highlighted: { head: string, tail: string }, ...PreviewStatus }
Expand Down
Loading

0 comments on commit 21d2a87

Please sign in to comment.