From e07b3097d2d7b5395eda07604c84af265af46e5f Mon Sep 17 00:00:00 2001 From: Colin Diesh Date: Wed, 22 Sep 2021 11:09:16 -0400 Subject: [PATCH] Fix install plugin workflow and error handling on desktop, update to electron 15 (#2322) * Add text searching to desktop and allow it to reload after installing plugins * Update to electron 15 * Rearrange some error handling * Avoid the volatile for pluginsUpdated, and instead update directly in setPluginsUpdated to avoid 1s delay --- package.json | 2 +- products/jbrowse-desktop/package.json | 2 +- products/jbrowse-desktop/public/electron.ts | 20 +-- products/jbrowse-desktop/src/Loader.tsx | 114 ++++++++++++++++-- .../src/StartScreen/data/preloadedConfigs.js | 77 ++++++++++++ .../jbrowse-desktop/src/StartScreen/index.tsx | 10 +- products/jbrowse-desktop/src/corePlugins.ts | 2 + products/jbrowse-desktop/src/index.js | 9 +- products/jbrowse-desktop/src/jbrowseModel.js | 3 + products/jbrowse-desktop/src/rootModel.ts | 44 +++---- yarn.lock | 22 ++-- 11 files changed, 238 insertions(+), 67 deletions(-) diff --git a/package.json b/package.json index 1291265420..83b6b6831d 100644 --- a/package.json +++ b/package.json @@ -98,7 +98,7 @@ "cross-spawn": "^7.0.1", "del": "^5.1.0", "dependency-graph": "^0.9.0", - "electron": "13.1.2", + "electron": "15.0.0", "electron-builder": "^22.1.0", "electron-mock-ipc": "^0.3.8", "electron-notarize": "^1.0.0", diff --git a/products/jbrowse-desktop/package.json b/products/jbrowse-desktop/package.json index cbb803af73..146600cb48 100644 --- a/products/jbrowse-desktop/package.json +++ b/products/jbrowse-desktop/package.json @@ -92,7 +92,7 @@ "last 1 chrome version" ], "build": { - "electronVersion": "13.1.2", + "electronVersion": "15.0.0", "extraMetadata": { "main": "build/electron.js" }, diff --git a/products/jbrowse-desktop/public/electron.ts b/products/jbrowse-desktop/public/electron.ts index 3ebda2c7a4..0966153fc2 100644 --- a/products/jbrowse-desktop/public/electron.ts +++ b/products/jbrowse-desktop/public/electron.ts @@ -221,19 +221,21 @@ ipcMain.handle('listSessions', async () => { ) }) -ipcMain.handle('loadExternalConfig', (_event: unknown, sessionPath) => - readFile(sessionPath, 'utf8'), -) -ipcMain.handle('loadSession', (_event: unknown, sessionName: string) => - readFile(getPath(sessionName), 'utf8'), -) +ipcMain.handle('loadExternalConfig', (_event: unknown, sessionPath) => { + return readFile(sessionPath, 'utf8') +}) + +ipcMain.handle('loadSession', (_event: unknown, sessionName: string) => { + return readFile(getPath(sessionName), 'utf8') +}) -ipcMain.on('saveSession', async (_event: unknown, snap: SessionSnap) => { +ipcMain.handle('saveSession', async (_event: unknown, snap: SessionSnap) => { const page = await mainWindow?.capturePage() + const name = snap.defaultSession.name if (page) { - writeFile(getPath(snap.defaultSession.name, 'thumbnail'), page.toDataURL()) + await writeFile(getPath(name, 'thumbnail'), page.toDataURL()) } - writeFile(getPath(snap.defaultSession.name), JSON.stringify(snap, null, 2)) + await writeFile(getPath(name), JSON.stringify(snap, null, 2)) }) ipcMain.handle( diff --git a/products/jbrowse-desktop/src/Loader.tsx b/products/jbrowse-desktop/src/Loader.tsx index 4d7af54a5d..4809c041d6 100644 --- a/products/jbrowse-desktop/src/Loader.tsx +++ b/products/jbrowse-desktop/src/Loader.tsx @@ -1,23 +1,117 @@ -import React, { useState } from 'react' -import PluginManager from '@jbrowse/core/PluginManager' -import { CssBaseline, ThemeProvider } from '@material-ui/core' +import React, { useState, useCallback, useEffect } from 'react' import { observer } from 'mobx-react' +import PluginManager from '@jbrowse/core/PluginManager' +import { CssBaseline, ThemeProvider, makeStyles } from '@material-ui/core' +import { createJBrowseTheme } from '@jbrowse/core/ui' +import { StringParam, useQueryParam } from 'use-query-params' +import { ipcRenderer } from 'electron' +import { createPluginManager } from './StartScreen/util' + import JBrowse from './JBrowse' import StartScreen from './StartScreen' -import { createJBrowseTheme } from '@jbrowse/core/ui' -function Loader() { +const useStyles = makeStyles(theme => ({ + message: { + border: '1px solid black', + overflow: 'auto', + maxHeight: 200, + margin: theme.spacing(1), + padding: theme.spacing(1), + }, + + errorBox: { + background: 'lightgrey', + border: '1px solid black', + margin: 20, + }, +})) + +const ErrorMessage = ({ + error, + snapshotError, +}: { + error: Error + snapshotError?: string +}) => { + const classes = useStyles() + return ( +
+ {`${error}`} + {snapshotError ? ( + <> + ... Failed element had snapshot: +
+            {JSON.stringify(JSON.parse(snapshotError), null, 2)}
+          
+ + ) : null} +
+ ) +} + +const Loader = observer(() => { const [pluginManager, setPluginManager] = useState() + const [config, setConfig] = useQueryParam('config', StringParam) + const [error, setError] = useState() + const [snapshotError, setSnapshotError] = useState('') + + function handleError(e: Error) { + const match = e.message.match( + /.*at path "(.*)" snapshot `(.*)` is not assignable/, + ) + + // best effort to make a better error message than the default + // mobx-state-tree + if (match) { + setError(new Error(`Failed to load element at ${match[1]}`)) + setSnapshotError(match[2]) + } else { + setError(new Error(e.message.slice(0, 10000))) + } + console.error(e) + } + + const handleSetPluginManager = useCallback( + (pm: PluginManager) => { + setPluginManager(pm) + setError(undefined) + setSnapshotError('') + setConfig('') + }, + [setConfig], + ) + + useEffect(() => { + ;(async () => { + if (config) { + try { + const data = await ipcRenderer.invoke('loadSession', config) + const pm = await createPluginManager(JSON.parse(data)) + handleSetPluginManager(pm) + } catch (e) { + handleError(e) + } + } + })() + }, [config, handleSetPluginManager]) + return ( + + {error ? ( + + ) : null} {pluginManager?.rootModel?.session ? ( - ) : ( - - )} + ) : !config || error ? ( + + ) : null} ) -} +}) -export default observer(Loader) +export default Loader diff --git a/products/jbrowse-desktop/src/StartScreen/data/preloadedConfigs.js b/products/jbrowse-desktop/src/StartScreen/data/preloadedConfigs.js index e7759fcdd2..313828bfe9 100644 --- a/products/jbrowse-desktop/src/StartScreen/data/preloadedConfigs.js +++ b/products/jbrowse-desktop/src/StartScreen/data/preloadedConfigs.js @@ -203,11 +203,47 @@ const preloadedConfigs = { }, }, }, + { + type: 'FeatureTrack', + trackId: 'ncbi_refseq_109_hg38_latest', + name: 'NCBI RefSeq', + assemblyNames: ['hg38'], + category: ['Annotation'], + adapter: { + type: 'Gff3TabixAdapter', + gffGzLocation: { + uri: + 'https://s3.amazonaws.com/jbrowse.org/genomes/GRCh38/ncbi_refseq/GRCh38_latest_genomic.sort.gff.gz', + }, + index: { + location: { + uri: + 'https://s3.amazonaws.com/jbrowse.org/genomes/GRCh38/ncbi_refseq/GRCh38_latest_genomic.sort.gff.gz.tbi', + }, + }, + }, + }, ], defaultSession: { name: 'New Session', }, + aggregateTextSearchAdapters: [ + { + type: 'TrixTextSearchAdapter', + textSearchAdapterId: 'hg38-index', + ixFilePath: { + uri: 'https://jbrowse.org/genomes/GRCh38/trix/hg38.ix', + }, + ixxFilePath: { + uri: 'https://jbrowse.org/genomes/GRCh38/trix/hg38.ixx', + }, + metaFilePath: { + uri: 'https://jbrowse.org/genomes/GRCh38/trix/meta.json', + }, + assemblyNames: ['hg38'], + }, + ], }, hg19: { assemblies: [ @@ -243,6 +279,30 @@ const preloadedConfigs = { }, ], tracks: [ + { + type: 'FeatureTrack', + trackId: 'ncbi_gff_hg19', + name: 'NCBI RefSeq', + assemblyNames: ['hg19'], + category: ['Annotation'], + metadata: { + source: 'https://www.ncbi.nlm.nih.gov/genome/guide/human/', + dateaccessed: '12/03/2020', + }, + adapter: { + type: 'Gff3TabixAdapter', + gffGzLocation: { + uri: + 'https://s3.amazonaws.com/jbrowse.org/genomes/hg19/ncbi_refseq/GRCh37_latest_genomic.sort.gff.gz', + }, + index: { + location: { + uri: + 'https://s3.amazonaws.com/jbrowse.org/genomes/hg19/ncbi_refseq/GRCh37_latest_genomic.sort.gff.gz.tbi', + }, + }, + }, + }, { type: 'FeatureTrack', trackId: 'repeats_hg19', @@ -397,6 +457,23 @@ const preloadedConfigs = { defaultSession: { name: 'New Session', }, + + aggregateTextSearchAdapters: [ + { + type: 'TrixTextSearchAdapter', + textSearchAdapterId: 'hg19-index', + ixFilePath: { + uri: 'https://jbrowse.org/genomes/hg19/trix/hg19.ix', + }, + ixxFilePath: { + uri: 'https://jbrowse.org/genomes/hg19/trix/hg19.ixx', + }, + metaFilePath: { + uri: 'https://jbrowse.org/genomes/hg19/trix/meta.json', + }, + assemblyNames: ['hg19'], + }, + ], }, mm10: { assemblies: [ diff --git a/products/jbrowse-desktop/src/StartScreen/index.tsx b/products/jbrowse-desktop/src/StartScreen/index.tsx index 9ada89308f..89794c8ea5 100644 --- a/products/jbrowse-desktop/src/StartScreen/index.tsx +++ b/products/jbrowse-desktop/src/StartScreen/index.tsx @@ -80,8 +80,10 @@ function LogoWithVersion() { } export default function StartScreen({ setPluginManager, + setError, }: { setPluginManager: (arg: PluginManager) => void + setError: (arg: Error) => void }) { const classes = useStyles() const [sessions, setSessions] = useState>() @@ -90,7 +92,6 @@ export default function StartScreen({ const [factoryResetDialogOpen, setFactoryResetDialogOpen] = useState(false) const [updateSessionsList, setUpdateSessionsList] = useState(true) const [menuAnchorEl, setMenuAnchorEl] = useState(null) - const [error, setError] = useState() const sessionNames = useMemo(() => Object.keys(sessions || {}), [sessions]) @@ -110,10 +111,9 @@ export default function StartScreen({ } } catch (e) { setError(e) - console.error(e) } })() - }, [updateSessionsList]) + }, [setError, updateSessionsList]) if (!sessions) { return ( @@ -156,7 +156,6 @@ export default function StartScreen({ setUpdateSessionsList(true) } catch (e) { setError(e) - console.error(e) } finally { setFactoryResetDialogOpen(false) } @@ -175,9 +174,6 @@ export default function StartScreen({ - {error ? ( - {`${error}`} - ) : null}
diff --git a/products/jbrowse-desktop/src/corePlugins.ts b/products/jbrowse-desktop/src/corePlugins.ts index bd4925966e..471f18524b 100644 --- a/products/jbrowse-desktop/src/corePlugins.ts +++ b/products/jbrowse-desktop/src/corePlugins.ts @@ -20,6 +20,7 @@ import Wiggle from '@jbrowse/plugin-wiggle' import SpreadsheetViewPlugin from '@jbrowse/plugin-spreadsheet-view' import SvInspectorPlugin from '@jbrowse/plugin-sv-inspector' import HicPlugin from '@jbrowse/plugin-hic' +import TrixPlugin from '@jbrowse/plugin-trix' import GridBookmarkPlugin from '@jbrowse/plugin-grid-bookmark' const corePlugins = [ @@ -45,6 +46,7 @@ const corePlugins = [ SvInspectorPlugin, BreakpointSplitView, HicPlugin, + TrixPlugin, GridBookmarkPlugin, ] diff --git a/products/jbrowse-desktop/src/index.js b/products/jbrowse-desktop/src/index.js index a8984aae7a..1371a9ef08 100644 --- a/products/jbrowse-desktop/src/index.js +++ b/products/jbrowse-desktop/src/index.js @@ -1,8 +1,11 @@ -import { FatalErrorDialog } from '@jbrowse/core/ui' import React from 'react' import ReactDOM from 'react-dom' +import { FatalErrorDialog } from '@jbrowse/core/ui' import { ErrorBoundary } from 'react-error-boundary' +import { QueryParamProvider } from 'use-query-params' + import 'fontsource-roboto' + import factoryReset from './factoryReset' import Loader from './Loader' @@ -14,7 +17,9 @@ const PlatformSpecificFatalErrorDialog = props => { ReactDOM.render( - + + + , document.getElementById('root'), ) diff --git a/products/jbrowse-desktop/src/jbrowseModel.js b/products/jbrowse-desktop/src/jbrowseModel.js index c9a78607c2..b23d42bf1b 100644 --- a/products/jbrowse-desktop/src/jbrowseModel.js +++ b/products/jbrowse-desktop/src/jbrowseModel.js @@ -58,6 +58,9 @@ export default function JBrowseDesktop( // track configuration is an array of track config schemas. multiple // instances of a track can exist that use the same configuration tracks: types.array(pluginManager.pluggableConfigSchemaType('track')), + aggregateTextSearchAdapters: types.array( + pluginManager.pluggableConfigSchemaType('text search adapter'), + ), connections: types.array( pluginManager.pluggableConfigSchemaType('connection'), ), diff --git a/products/jbrowse-desktop/src/rootModel.ts b/products/jbrowse-desktop/src/rootModel.ts index 9d45dec9f2..a877a70b18 100644 --- a/products/jbrowse-desktop/src/rootModel.ts +++ b/products/jbrowse-desktop/src/rootModel.ts @@ -5,8 +5,8 @@ import { autorun } from 'mobx' import PluginManager from '@jbrowse/core/PluginManager' import RpcManager from '@jbrowse/core/rpc/RpcManager' import { MenuItem } from '@jbrowse/core/ui' -import AddIcon from '@material-ui/icons/Add' import SettingsIcon from '@material-ui/icons/Settings' +import TextSearchManager from '@jbrowse/core/TextSearch/TextSearchManager' import AppsIcon from '@material-ui/icons/Apps' import electron from 'electron' import { @@ -56,10 +56,18 @@ export default function rootModelFactory(pluginManager: PluginManager) { isAssemblyEditing: false, }) .volatile(() => ({ - pluginsUpdated: false, error: undefined as Error | undefined, + textSearchManager: new TextSearchManager(pluginManager), })) .actions(self => ({ + async saveSession() { + if (self.session) { + await ipcRenderer.invoke('saveSession', { + ...getSnapshot(self.jbrowse), + defaultSession: getSnapshot(self.session), + }) + } + }, setSavedSessionNames(sessionNames: string[]) { self.savedSessionNames = cast(sessionNames) }, @@ -75,9 +83,7 @@ export default function rootModelFactory(pluginManager: PluginManager) { setAssemblyEditing(flag: boolean) { self.isAssemblyEditing = flag }, - setPluginsUpdated(flag: boolean) { - self.pluginsUpdated = flag - }, + renameCurrentSession(sessionName: string) { if (self.session) { const snapshot = JSON.parse(JSON.stringify(getSnapshot(self.session))) @@ -109,13 +115,6 @@ export default function rootModelFactory(pluginManager: PluginManager) { { label: 'File', menuItems: [ - { - label: 'New Session', - icon: AddIcon, - onClick: (session: { setDefaultSession: () => void }) => { - session.setDefaultSession() - }, - }, { label: 'Return to start screen', icon: AppsIcon, @@ -161,6 +160,12 @@ export default function rootModelFactory(pluginManager: PluginManager) { setMenus(newMenus: Menu[]) { self.menus = newMenus }, + async setPluginsUpdated() { + await self.saveSession() + const url = window.location.href.split('?')[0] + const name = self.session?.name || '' + window.location.href = `${url}?config=${encodeURIComponent(name)}` + }, /** * Add a top-level menu * @param menuName - Name of the menu to insert. @@ -292,20 +297,7 @@ export default function rootModelFactory(pluginManager: PluginManager) { afterCreate() { addDisposer( self, - autorun( - () => { - // if (self.session) { - // ipcRenderer.send('saveSession', getSnapshot(self.session)) - // } - if (self.session) { - ipcRenderer.send('saveSession', { - ...getSnapshot(self.jbrowse), - defaultSession: getSnapshot(self.session), - }) - } - }, - { delay: 1000 }, - ), + autorun(() => self.saveSession(), { delay: 1000 }), ) }, })) diff --git a/yarn.lock b/yarn.lock index 75af556d5b..a84c7f5482 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1580,17 +1580,17 @@ ajv "^6.12.0" ajv-keywords "^3.4.1" -"@electron/get@^1.0.1": - version "1.12.2" - resolved "https://registry.yarnpkg.com/@electron/get/-/get-1.12.2.tgz#6442066afb99be08cefb9a281e4b4692b33764f3" - integrity sha512-vAuHUbfvBQpYTJ5wB7uVIDq5c/Ry0fiTBMs7lnEYAo/qXXppIVcWdfBr57u6eRnKdVso7KSiH6p/LbQAG6Izrg== +"@electron/get@^1.13.0": + version "1.13.0" + resolved "https://registry.yarnpkg.com/@electron/get/-/get-1.13.0.tgz#95c6bcaff4f9a505ea46792424f451efea89228c" + integrity sha512-+SjZhRuRo+STTO1Fdhzqnv9D2ZhjxXP6egsJ9kiO8dtP68cDx7dFCwWi64dlMQV7sWcfW1OYCW4wviEBzmRsfQ== dependencies: debug "^4.1.1" env-paths "^2.2.0" fs-extra "^8.1.0" got "^9.6.0" progress "^2.0.3" - sanitize-filename "^1.6.2" + semver "^6.2.0" sumchecker "^3.0.1" optionalDependencies: global-agent "^2.0.2" @@ -9379,12 +9379,12 @@ electron-window-state@^5.0.3: jsonfile "^4.0.0" mkdirp "^0.5.1" -electron@13.1.2: - version "13.1.2" - resolved "https://registry.yarnpkg.com/electron/-/electron-13.1.2.tgz#8c9abf9015766c9cbc16f10c99282d00d6ae1b90" - integrity sha512-aNT9t+LgdQaZ7FgN36pN7MjSEoj+EWc2T9yuOqBApbmR4HavGRadSz7u9N2Erw2ojdIXtei2RVIAvVm8mbDZ0g== +electron@15.0.0: + version "15.0.0" + resolved "https://registry.yarnpkg.com/electron/-/electron-15.0.0.tgz#b1b6244b1cffddf348c27c54b1310b3a3680246e" + integrity sha512-LlBjN5nCJoC7EDrgfDQwEGSGSAo/o08nSP5uJxN2m+ZtNA69SxpnWv4yPgo1K08X/iQPoGhoZu6C8LYYlk1Dtg== dependencies: - "@electron/get" "^1.0.1" + "@electron/get" "^1.13.0" "@types/node" "^14.6.2" extract-zip "^1.0.3" @@ -19311,7 +19311,7 @@ sane@^4.0.3: minimist "^1.1.1" walker "~1.0.5" -sanitize-filename@^1.6.2, sanitize-filename@^1.6.3: +sanitize-filename@^1.6.3: version "1.6.3" resolved "https://registry.yarnpkg.com/sanitize-filename/-/sanitize-filename-1.6.3.tgz#755ebd752045931977e30b2025d340d7c9090378" integrity sha512-y/52Mcy7aw3gRm7IrcGDFx/bCk4AhRh2eI9luHOQM86nZsqwiRkkq2GekHXBBD+SmPidc8i2PqtYZl+pWJ8Oeg==