Customizing the app
Theme, session state, sizing and the web worker.
On this page: Dark theme · Observe the session · Put the session in the URL · Fit the app to a container · Web worker RPC
Dark theme
palette.mode: dark.
Every palette option is in the theming guide.
View source — 33 lines
import { JBrowse } from '@jbrowse/react-app2'
const base = 'https://jbrowse.org/code/jb2/main/test_data/volvox'
const assemblies = [{ name: 'volvox', uri: `${base}/volvox.2bit` }]
const tracks = [
{
type: 'AlignmentsTrack',
trackId: 'volvox_cram',
name: 'volvox-sorted.cram',
assemblyNames: ['volvox'],
adapter: { type: 'CramAdapter', uri: `${base}/volvox-sorted.cram` },
},
]
export default function DarkTheme() {
return (
<JBrowse
assemblies={assemblies}
tracks={tracks}
configuration={{ theme: { palette: { mode: 'dark' } } }}
views={[
{
type: 'LinearGenomeView',
assembly: 'volvox',
loc: 'ctgA:1..50000',
tracks: ['volvox_cram'],
},
]}
/>
)
}Observe the session
An observer reading the open views.
Anything marked #getter or #property on the
session model or a
view model is reactive.
There is no change callback: to save the session, take
getSnapshot(viewState.session) and pass it back as the session option.
View source — 60 lines
import { JBrowseApp, useCreateViewState } from '@jbrowse/react-app2'
import { observer } from 'mobx-react'
const base = 'https://jbrowse.org/code/jb2/main/test_data/volvox'
const config = {
assemblies: [{ name: 'volvox', uri: `${base}/volvox.2bit` }],
tracks: [
{
type: 'AlignmentsTrack',
trackId: 'volvox_cram',
name: 'volvox-sorted.cram',
assemblyNames: ['volvox'],
adapter: { type: 'CramAdapter', uri: `${base}/volvox-sorted.cram` },
},
],
defaultSession: {
name: 'observe',
views: [
{
id: 'view-0',
type: 'LinearGenomeView',
assembly: 'volvox',
loc: 'ctgA:1..50000',
tracks: ['volvox_cram'],
},
],
},
}
const SessionSummary = observer(function SessionSummary({
viewState,
}: {
viewState: NonNullable<ReturnType<typeof useCreateViewState>>
}) {
const { views } = viewState.session
return (
<div style={{ padding: 8, fontFamily: 'monospace', fontSize: 12 }}>
<div>{views.length} view(s) open</div>
<ul style={{ margin: 0 }}>
{views.map(view => (
<li key={view.id}>
{view.type} — {view.coarseVisibleLocStrings || 'no region'}
</li>
))}
</ul>
</div>
)
})
export default function ObserveSession() {
const viewState = useCreateViewState({ config })
return viewState ? (
<div>
<SessionSummary viewState={viewState} />
<JBrowseApp viewState={viewState} />
</div>
) : null
}Put the session in the URL
encodeSession / decodeSession, for a sharable link.
The hash fragment never reaches the server, so a long session cannot fail with
HTTP 414. Only the session travels: the receiving page supplies its own
assemblies and tracks, and File → New session still returns to
defaultSession.
View source — 114 lines
import { useEffect, useState } from 'react'
import {
JBrowseApp,
decodeSession,
encodeSession,
useCreateViewState,
} from '@jbrowse/react-app2'
import type { SessionSnapshot } from '@jbrowse/react-app2'
const base = 'https://jbrowse.org/code/jb2/main/test_data/volvox'
const config = {
assemblies: [{ name: 'volvox', uri: `${base}/volvox.2bit` }],
tracks: [
{
type: 'FeatureTrack',
trackId: 'volvox_gff3',
name: 'Volvox genes',
assemblyNames: ['volvox'],
adapter: { type: 'Gff3TabixAdapter', uri: `${base}/volvox.sort.gff3.gz` },
},
],
defaultSession: {
name: 'Session in URL',
views: [
{
id: 'view-0',
type: 'LinearGenomeView',
assembly: 'volvox',
loc: 'ctgA:1..50000',
tracks: ['volvox_gff3'],
},
],
},
}
function readSessionParam() {
return (
new URLSearchParams(window.location.hash.slice(1)).get('session') ??
undefined
)
}
function writeSessionParam(value: string) {
const params = new URLSearchParams(window.location.hash.slice(1))
params.set('session', value)
window.history.replaceState(null, '', `#${params.toString()}`)
}
function App({ session, note }: { session?: SessionSnapshot; note: string }) {
const viewState = useCreateViewState({ config, session })
const [status, setStatus] = useState(note)
return viewState ? (
<div>
<div style={{ padding: 8, fontSize: 13, background: '#8881' }}>
{status || 'navigate or open a track, then save from the app toolbar'}
</div>
<JBrowseApp
viewState={viewState}
headerButtons={
<button
type="button"
onClick={() => {
void encodeSession(viewState)
.then(encoded => {
writeSessionParam(encoded)
setStatus(
`saved to the URL (${encoded.length} chars) — copy the address bar, or reload to restore it`,
)
})
.catch((e: unknown) => {
console.error(e)
setStatus(`could not save the session to the URL: ${e}`)
})
}}
>
Save to URL
</button>
}
/>
</div>
) : null
}
export default function SessionInUrl() {
const [session, setSession] = useState<SessionSnapshot | null | undefined>(
() => (readSessionParam() ? undefined : null),
)
const [note, setNote] = useState('')
useEffect(() => {
const param = readSessionParam()
if (!param) {
return
}
decodeSession(param)
.then(snap => {
setSession(snap)
setNote(`restored "${snap.name}" from the URL`)
})
.catch((e: unknown) => {
console.error(e)
setSession(null)
setNote(`could not restore the session in the URL: ${e}`)
})
}, [])
return session === undefined ? null : (
<App session={session ?? undefined} note={note} />
)
}Fit the app to a container
The --jbrowse-app-height CSS variable.
--jbrowse-app-height defaults to 100vh. A percentage resolves only inside a
container with a definite height, which here is the flex child with
minHeight: 0.
View source — 74 lines
import { JBrowse } from '@jbrowse/react-app2'
const assemblies = [
{
name: 'volvox',
sequence: {
adapter: {
type: 'TwoBitAdapter',
uri: 'https://jbrowse.org/genomes/volvox/volvox.2bit',
},
},
refNameAliases: {
adapter: {
type: 'FromConfigAdapter',
adapterId: 'W6DyPGJ0UU',
features: [
{ refName: 'ctgA', uniqueId: 'alias1', aliases: ['A'] },
{ refName: 'ctgB', uniqueId: 'alias2', aliases: ['B'] },
],
},
},
},
]
const tracks = [
{
type: 'AlignmentsTrack',
trackId: 'volvox_cram',
name: 'volvox-sorted.cram',
assemblyNames: ['volvox'],
category: ['Alignments'],
adapter: {
type: 'CramAdapter',
uri: 'https://jbrowse.org/code/jb2/main/test_data/volvox/volvox-sorted.cram',
},
},
]
export default function FitToContainer() {
return (
<>
<style>{`.jbrowseFitDemo { --jbrowse-app-height: 100%; }`}</style>
<div style={{ display: 'flex', flexDirection: 'column', height: '100%' }}>
<div
style={{
padding: '8px 12px',
fontSize: 14,
background: 'color-mix(in srgb, CanvasText 8%, Canvas)',
borderBottom:
'1px solid color-mix(in srgb, CanvasText 20%, Canvas)',
}}
>
Your own app chrome lives here. The embedded JBrowse below fills the
remaining space instead of forcing the full viewport height.
</div>
<div className="jbrowseFitDemo" style={{ flex: 1, minHeight: 0 }}>
<JBrowse
assemblies={assemblies}
tracks={tracks}
views={[
{
type: 'LinearGenomeView',
assembly: 'volvox',
loc: 'ctgA:1..50000',
tracks: ['volvox_cram'],
tracklist: true,
},
]}
/>
</div>
</div>
</>
)
}Web worker RPC
Move parsing and rendering off the main thread.
Under webpack, import @jbrowse/react-app2/esm/makeWorkerInstance instead of
the ?worker entry, and set output.publicPath: 'auto' so the worker finds its
own URL. A module worker, which Vite builds here, cannot load a UMD plugin; a
classic worker can.
View source — 55 lines
import { JBrowse } from '@jbrowse/react-app2'
import RpcWorker from '@jbrowse/react-app2/esm/rpcWorker?worker'
const assemblies = [
{
name: 'volvox',
sequence: {
adapter: {
type: 'TwoBitAdapter',
uri: 'https://jbrowse.org/genomes/volvox/volvox.2bit',
},
},
refNameAliases: {
adapter: {
type: 'FromConfigAdapter',
adapterId: 'W6DyPGJ0UU',
features: [
{ refName: 'ctgA', uniqueId: 'alias1', aliases: ['A'] },
{ refName: 'ctgB', uniqueId: 'alias2', aliases: ['B'] },
],
},
},
},
]
const tracks = [
{
type: 'FeatureTrack',
trackId: 'volvox_gff3',
name: 'Volvox genes',
assemblyNames: ['volvox'],
adapter: {
type: 'Gff3TabixAdapter',
uri: 'https://jbrowse.org/code/jb2/main/test_data/volvox/volvox.sort.gff3.gz',
},
},
]
export default function WithWebWorker() {
return (
<JBrowse
assemblies={assemblies}
tracks={tracks}
makeWorkerInstance={() => new RpcWorker()}
views={[
{
type: 'LinearGenomeView',
assembly: 'volvox',
loc: 'ctgA:1..50000',
tracks: ['volvox_gff3'],
},
]}
/>
)
}