@cmdcolin/jbrowse-plugin-hubs 1.0.9 → 1.0.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +69 -28
- package/dist/index.js.map +1 -1
- package/dist/jbrowse-plugin-hubs.umd.production.min.js +1 -1
- package/dist/jbrowse-plugin-hubs.umd.production.min.js.map +3 -3
- package/dist/util.d.ts +1 -1
- package/dist/util.js +14 -6
- package/dist/util.js.map +1 -1
- package/package.json +23 -23
- package/src/index.ts +81 -31
- package/src/util.ts +14 -6
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import Plugin from '@jbrowse/core/Plugin';
|
|
2
2
|
import { isAbstractMenuManager, isElectron } from '@jbrowse/core/util';
|
|
3
3
|
import { getRoot, getSnapshot } from '@jbrowse/mobx-state-tree';
|
|
4
|
-
import { toDesktopSnapshot } from './util';
|
|
4
|
+
import { saveAs, toDesktopSnapshot } from './util';
|
|
5
5
|
import { version } from '../package.json';
|
|
6
6
|
// Serializes the current web session as a desktop `.jbrowse` file. genomes
|
|
7
7
|
// sessions are config-shaped already, so this just folds the live session into
|
|
@@ -9,11 +9,7 @@ import { version } from '../package.json';
|
|
|
9
9
|
function downloadDesktopSession(session) {
|
|
10
10
|
const { jbrowse } = getRoot(session);
|
|
11
11
|
const snap = toDesktopSnapshot(getSnapshot(jbrowse), getSnapshot(session));
|
|
12
|
-
|
|
13
|
-
anchor.href = URL.createObjectURL(new Blob([JSON.stringify(snap, null, 2)], { type: 'application/json' }));
|
|
14
|
-
anchor.download = 'session.jbrowse';
|
|
15
|
-
anchor.click();
|
|
16
|
-
URL.revokeObjectURL(anchor.href);
|
|
12
|
+
saveAs(new Blob([JSON.stringify(snap, null, 2)], { type: 'application/json' }), 'session.jbrowse');
|
|
17
13
|
}
|
|
18
14
|
function getGenArkConfigUrl(accession) {
|
|
19
15
|
const [base, rest] = accession.split('_');
|
|
@@ -33,22 +29,26 @@ function getConfigUrl(assemblyName) {
|
|
|
33
29
|
}
|
|
34
30
|
return `https://jbrowse.org/ucsc/${assemblyName}/config.json`;
|
|
35
31
|
}
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
32
|
+
// Names already being resolved, so the extension point firing repeatedly for
|
|
33
|
+
// the same assembly (it fires on every unresolved read) starts one probe, not
|
|
34
|
+
// one per read. The connection itself is deduped by connectionId, but that
|
|
35
|
+
// check only happens once the probe has come back.
|
|
36
|
+
const pending = new Set();
|
|
37
|
+
// The url is a guess from the name, and plenty of assemblies aren't at it:
|
|
38
|
+
// GenArk strain hubs (mouseStrains and friends) live under
|
|
39
|
+
// /hubs/genark/<hub>/<strain>/, and a session can name an assembly this host
|
|
40
|
+
// has never heard of. Connecting to a config that 404s puts a red error
|
|
41
|
+
// snackbar over a session that is otherwise working, so probe first and stay
|
|
42
|
+
// quiet when there's nothing there — some other handler, or the session
|
|
43
|
+
// itself, may be supplying the assembly.
|
|
44
|
+
async function connectIfConfigExists(session, assemblyName, uri) {
|
|
45
|
+
const connectionId = `jb2hub-${assemblyName}`;
|
|
46
|
+
if (!pending.has(assemblyName) &&
|
|
47
|
+
!session.connections.find(f => f.connectionId === connectionId)) {
|
|
48
|
+
pending.add(assemblyName);
|
|
49
|
+
try {
|
|
50
|
+
const response = await fetch(uri, { method: 'HEAD' });
|
|
51
|
+
if (response.ok) {
|
|
52
52
|
const conf = {
|
|
53
53
|
type: 'JB2TrackHubConnection',
|
|
54
54
|
uri,
|
|
@@ -59,6 +59,31 @@ export default class HubsViewerPlugin extends Plugin {
|
|
|
59
59
|
session.addConnectionConf(conf);
|
|
60
60
|
session.makeConnection(conf);
|
|
61
61
|
}
|
|
62
|
+
}
|
|
63
|
+
finally {
|
|
64
|
+
pending.delete(assemblyName);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
export default class HubsViewerPlugin extends Plugin {
|
|
69
|
+
name = 'HubsViewerPlugin';
|
|
70
|
+
version = version;
|
|
71
|
+
install(pluginManager) {
|
|
72
|
+
pluginManager.addToExtensionPoint('Core-handleUnrecognizedAssembly', (defaultResult, args) => {
|
|
73
|
+
const session = args.session;
|
|
74
|
+
const assemblyName = args.assemblyName;
|
|
75
|
+
const uri = session && assemblyName && getConfigUrl(assemblyName);
|
|
76
|
+
if (session && assemblyName && uri) {
|
|
77
|
+
// the extension point is sync; the probe and the connection it may
|
|
78
|
+
// create land later, which is fine because assemblyManager re-reads
|
|
79
|
+
// reactively once the assembly shows up
|
|
80
|
+
connectIfConfigExists(session, assemblyName, uri).catch((e) => {
|
|
81
|
+
console.error(e);
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
// Pass the accumulator through: this extension point chains handlers, so
|
|
85
|
+
// returning undefined would clobber another plugin's result.
|
|
86
|
+
return defaultResult;
|
|
62
87
|
});
|
|
63
88
|
}
|
|
64
89
|
configure(pluginManager) {
|
|
@@ -66,12 +91,28 @@ export default class HubsViewerPlugin extends Plugin {
|
|
|
66
91
|
// .jbrowse file carries this plugin in its plugins list, so an older desktop
|
|
67
92
|
// would otherwise load and run this menu code when opening that file.
|
|
68
93
|
if (!isElectron && isAbstractMenuManager(pluginManager.rootModel)) {
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
94
|
+
// 'Tools', not 'File'. Every released core from v4.0.0 to latest defines
|
|
95
|
+
// the File menu as a thunk and appends with `menu.menuItems.push()`, which
|
|
96
|
+
// throws on a function; the throw escapes configure() and renders the app
|
|
97
|
+
// as an error page rather than costing just this menu item. Because the
|
|
98
|
+
// plugin store serves `latest/` no-cache, that took out every
|
|
99
|
+
// jbrowse.org/ucsc launch on hg38/hg19/mm39/hs1 the moment 1.0.9 shipped.
|
|
100
|
+
// core@main resolves item contributions lazily and accepts either form,
|
|
101
|
+
// but this plugin has to keep working on the releases already in the wild.
|
|
102
|
+
//
|
|
103
|
+
// Belt and braces on the same reasoning: a cosmetic menu item should never
|
|
104
|
+
// be able to cost a user their whole session.
|
|
105
|
+
try {
|
|
106
|
+
pluginManager.rootModel.appendToMenu('Tools', {
|
|
107
|
+
label: 'Download desktop session (.jbrowse)',
|
|
108
|
+
onClick: (session) => {
|
|
109
|
+
downloadDesktopSession(session);
|
|
110
|
+
},
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
catch (e) {
|
|
114
|
+
console.error('could not add the desktop-session download item', e);
|
|
115
|
+
}
|
|
75
116
|
}
|
|
76
117
|
}
|
|
77
118
|
}
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,MAAM,MAAM,sBAAsB,CAAA;AACzC,OAAO,EAAE,qBAAqB,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAA;AACtE,OAAO,EAAE,OAAO,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAA;AAE/D,OAAO,EAAE,iBAAiB,EAAE,MAAM,QAAQ,CAAA;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
"use strict";var JBrowsePluginHubs=(()=>{var
|
|
1
|
+
"use strict";var JBrowsePluginHubs=(()=>{var v=Object.create;var a=Object.defineProperty;var E=Object.getOwnPropertyDescriptor;var I=Object.getOwnPropertyNames;var T=Object.getPrototypeOf,D=Object.prototype.hasOwnProperty;var u=(n,e)=>()=>(e||n((e={exports:{}}).exports,e),e.exports),$=(n,e)=>{for(var o in e)a(n,o,{get:e[o],enumerable:!0})},m=(n,e,o,t)=>{if(e&&typeof e=="object"||typeof e=="function")for(let s of I(e))!D.call(n,s)&&s!==o&&a(n,s,{get:()=>e[s],enumerable:!(t=E(e,s))||t.enumerable});return n};var g=(n,e,o)=>(o=n!=null?v(T(n)):{},m(e||!n||!n.__esModule?a(o,"default",{value:n,enumerable:!0}):o,n)),B=n=>m(a({},"__esModule",{value:!0}),n);var w=u((H,b)=>{b.exports=JBrowseExports["@jbrowse/core/Plugin"]});var h=u((W,k)=>{k.exports=JBrowseExports["@jbrowse/core/util"]});var x=u((z,j)=>{j.exports=JBrowseExports["mobx-state-tree"]});var G={};$(G,{default:()=>l});var S=g(w()),p=g(h()),i=g(x());function A(n,e){let o=URL.createObjectURL(n),t=document.createElement("a");t.href=o,t.download=e,t.rel="noopener",t.click(),setTimeout(()=>{URL.revokeObjectURL(o)},4e4)}function d(n){return Array.isArray(n)?n:[]}function y(n,e,o){let t=new Map;for(let s of[...n,...e])t.set(s[o],s);return[...t.values()]}function R(n,e){let{sessionTracks:o,sessionAssemblies:t,...s}=e;return{...n,tracks:y(d(n.tracks),d(o),"trackId"),assemblies:y(d(n.assemblies),d(t),"name"),defaultSession:s}}var C="1.0.12";function U(n){let{jbrowse:e}=(0,i.getRoot)(n),o=R((0,i.getSnapshot)(e),(0,i.getSnapshot)(n));A(new Blob([JSON.stringify(o,null,2)],{type:"application/json"}),"session.jbrowse")}function O(n){let[e,o]=n.split("_");if(!o)return;let t=o.match(/.{1,3}/g);if(!t||t.length<3)return;let[s,r,c]=t;return`https://jbrowse.org/hubs/genark/${e}/${s}/${r}/${c}/${n}/config.json`}function J(n){return n.startsWith("GCA_")||n.startsWith("GCF_")?O(n):`https://jbrowse.org/ucsc/${n}/config.json`}var f=new Set;async function _(n,e,o){let t=`jb2hub-${e}`;if(!f.has(e)&&!n.connections.find(s=>s.connectionId===t)){f.add(e);try{if((await fetch(o,{method:"HEAD"})).ok){let r={type:"JB2TrackHubConnection",uri:o,name:`conn_${e}`,assemblyNames:[e],connectionId:t};n.addConnectionConf(r),n.makeConnection(r)}}finally{f.delete(e)}}}var l=class extends S.default{name="HubsViewerPlugin";version=C;install(e){e.addToExtensionPoint("Core-handleUnrecognizedAssembly",(o,t)=>{let s=t.session,r=t.assemblyName,c=s&&r&&J(r);return s&&r&&c&&_(s,r,c).catch(M=>{console.error(M)}),o})}configure(e){if(!p.isElectron&&(0,p.isAbstractMenuManager)(e.rootModel))try{e.rootModel.appendToMenu("Tools",{label:"Download desktop session (.jbrowse)",onClick:o=>{U(o)}})}catch(o){console.error("could not add the desktop-session download item",o)}}};return B(G);})();
|
|
2
2
|
//# sourceMappingURL=jbrowse-plugin-hubs.umd.production.min.js.map
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["global-externals:@jbrowse/core/Plugin", "global-externals:@jbrowse/core/util", "global-externals:@jbrowse/mobx-state-tree", "../src/index.ts", "../src/util.ts", "../package.json"],
|
|
4
|
-
"sourcesContent": ["module.exports = JBrowseExports[\"@jbrowse/core/Plugin\"];", "module.exports = JBrowseExports[\"@jbrowse/core/util\"];", "module.exports = JBrowseExports[\"mobx-state-tree\"];", "import Plugin from '@jbrowse/core/Plugin'\nimport { isAbstractMenuManager, isElectron } from '@jbrowse/core/util'\nimport { getRoot, getSnapshot } from '@jbrowse/mobx-state-tree'\n\nimport { toDesktopSnapshot } from './util'\nimport { version } from '../package.json'\n\nimport type PluginManager from '@jbrowse/core/PluginManager'\nimport type { AbstractSessionModel } from '@jbrowse/core/util'\nimport type { IAnyStateTreeNode } from '@jbrowse/mobx-state-tree'\n\ninterface Session {\n makeConnection: (conf: unknown) => void\n addConnectionConf: (conf: unknown) => void\n connections: { connectionId: string }[]\n}\n\n// Serializes the current web session as a desktop `.jbrowse` file. genomes\n// sessions are config-shaped already, so this just folds the live session into\n// a defaultSession and downloads it; desktop opens it via File > Open session.\nfunction downloadDesktopSession(session: AbstractSessionModel) {\n const { jbrowse } = getRoot<{ jbrowse: IAnyStateTreeNode }>(session)\n const snap = toDesktopSnapshot(\n getSnapshot<Record<string, unknown>>(jbrowse),\n getSnapshot<Record<string, unknown>>(session),\n )\n const anchor = document.createElement('a')\n anchor.href = URL.createObjectURL(\n new Blob([JSON.stringify(snap, null, 2)], { type: 'application/json' }),\n )\n anchor.download = 'session.jbrowse'\n anchor.click()\n URL.revokeObjectURL(anchor.href)\n}\n\nfunction getGenArkConfigUrl(accession: string) {\n const [base, rest] = accession.split('_')\n if (!rest) {\n return undefined\n }\n const match = rest.match(/.{1,3}/g)\n if (!match || match.length < 3) {\n return undefined\n }\n const [b1, b2, b3] = match\n return `https://jbrowse.org/hubs/genark/${base}/${b1}/${b2}/${b3}/${accession}/config.json`\n}\n\nfunction getConfigUrl(assemblyName: string) {\n if (assemblyName.startsWith('GCA_') || assemblyName.startsWith('GCF_')) {\n return getGenArkConfigUrl(assemblyName)\n }\n return `https://jbrowse.org/ucsc/${assemblyName}/config.json`\n}\n\nexport default class HubsViewerPlugin extends Plugin {\n name = 'HubsViewerPlugin'\n version = version\n\n install(pluginManager: PluginManager) {\n pluginManager.addToExtensionPoint(\n 'Core-handleUnrecognizedAssembly',\n (_defaultResult, args) => {\n const session = args.session as Session | undefined\n const assemblyName = args.assemblyName as string | undefined\n if (!session || !assemblyName) {\n return\n }\n const uri = getConfigUrl(assemblyName)\n if (!uri) {\n return\n }\n const connectionId = `jb2hub-${assemblyName}`\n if (!session.connections.find(f => f.connectionId === connectionId)) {\n const conf = {\n type: 'JB2TrackHubConnection',\n uri,\n name: `conn_${assemblyName}`,\n assemblyNames: [assemblyName],\n connectionId,\n }\n session.addConnectionConf(conf)\n session.makeConnection(conf)\n }\n },\n )\n }\n\n configure(pluginManager: PluginManager) {\n // jbrowse-web only. In desktop you'd just save the session, and an exported\n // .jbrowse file carries this plugin in its plugins list, so an older desktop\n // would otherwise load and run this menu code when opening that file.\n if (!isElectron && isAbstractMenuManager(pluginManager.rootModel)) {\n pluginManager.rootModel.appendToMenu('File', {\n label: 'Download desktop session (.jbrowse)',\n onClick: (session: AbstractSessionModel) => {\n downloadDesktopSession(session)\n },\n })\n }\n }\n}\n", "export async function textfetch(url: string, arg?: RequestInit) {\n const res = await fetch(url, arg)\n if (!res.ok) {\n throw new Error(`HTTP ${res.status} from ${url}`)\n }\n return res.text()\n}\n\nfunction asObjArray(value: unknown): Record<string, unknown>[] {\n return Array.isArray(value) ? (value as Record<string, unknown>[]) : []\n}\n\n// Merges two lists of config objects by an id key, with `overrides` winning so\n// a session-scoped track/assembly that shadows a config one (same id) replaces\n// it rather than duplicating.\nfunction mergeById(\n base: Record<string, unknown>[],\n overrides: Record<string, unknown>[],\n key: string,\n): Record<string, unknown>[] {\n const merged = new Map<unknown, Record<string, unknown>>()\n for (const item of [...base, ...overrides]) {\n merged.set(item[key], item)\n }\n return [...merged.values()]\n}\n\n// Builds a desktop `.jbrowse` snapshot (a config plus a defaultSession) from a\n// web root config and its live session. Session-scoped tracks/assemblies are\n// hoisted into the config arrays so they open as first-class config entries in\n// desktop; the rest of the session becomes the defaultSession. Web sessions are\n// always UriLocation-based, so the result is fully portable to desktop.\nexport function toDesktopSnapshot(\n jbrowse: Record<string, unknown>,\n session: Record<string, unknown>,\n): Record<string, unknown> {\n const { sessionTracks, sessionAssemblies, ...defaultSession } = session\n return {\n ...jbrowse,\n tracks: mergeById(\n asObjArray(jbrowse.tracks),\n asObjArray(sessionTracks),\n 'trackId',\n ),\n assemblies: mergeById(\n asObjArray(jbrowse.assemblies),\n asObjArray(sessionAssemblies),\n 'name',\n ),\n defaultSession,\n }\n}\n", "{\n \"version\": \"1.0.9\",\n \"license\": \"MIT\",\n \"name\": \"@cmdcolin/jbrowse-plugin-hubs\",\n \"repository\": {\n \"type\": \"git\",\n \"url\": \"git+https://github.com/GMOD/jbrowse-plugin-hubs.git\"\n },\n \"keywords\": [\n \"jbrowse\",\n \"jbrowse2\"\n ],\n \"main\": \"dist/index.js\",\n \"module\": \"dist/index.esm.js\",\n \"typings\": \"dist/index.d.ts\",\n \"files\": [\n \"dist\",\n \"src\"\n ],\n \"scripts\": {\n \"clean\": \"rimraf dist\",\n \"start\": \"node esbuild.mjs --watch\",\n \"test\": \"echo 'no tests'\",\n \"format\": \"pnpm prettier --write .\",\n \"prebuild\": \"pnpm clean\",\n \"build\": \"tsc && NODE_ENV=production node esbuild.mjs\",\n \"lint\": \"eslint src --report-unused-disable-directives --max-warnings 0\",\n \"prepack\": \"pnpm build\",\n \"postversion\": \"git push --follow-tags\",\n \"preversion\": \"pnpm lint\"\n },\n \"devDependencies\": {\n \"@eslint/js\": \"^10.0.1\",\n \"@fal-works/esbuild-plugin-global-externals\": \"^2.1.2\",\n \"@jbrowse/core\": \"^4.1.3\",\n \"@jbrowse/mobx-state-tree\": \"^5.0.2\",\n \"@mui/material\": \"^7.3.7\",\n \"@mui/system\": \"^7.3.7\",\n \"@mui/x-data-grid\": \"^8.26.0\",\n \"@types/node\": \"^25.1.0\",\n \"@types/react\": \"^19.2.10\",\n \"@typescript-eslint/eslint-plugin\": \"^8.59.1\",\n \"@typescript-eslint/parser\": \"^8.59.1\",\n \"esbuild\": \"^0.27.2\",\n \"eslint\": \"^10.2.1\",\n \"eslint-plugin-import-x\": \"^4.16.2\",\n \"eslint-plugin-react\": \"^7.37.5\",\n \"eslint-plugin-react-hooks\": \"^7.1.1\",\n \"eslint-plugin-unicorn\": \"^64.0.0\",\n \"mobx\": \"^6.0.0\",\n \"mobx-react\": \"^9.0.1\",\n \"prettier\": \"^3.8.1\",\n \"pretty-bytes\": \"^7.0.0\",\n \"react\": \"^19.2.4\",\n \"react-dom\": \"^19.2.4\",\n \"rimraf\": \"^6.1.2\",\n \"rxjs\": \"^7.8.1\",\n \"serve\": \"^14.2.0\",\n \"typescript\": \"^5.1.6\",\n \"typescript-eslint\": \"^8.59.1\"\n },\n \"publishConfig\": {\n \"access\": \"public\"\n }\n}\n"],
|
|
5
|
-
"mappings": "gpBAAA,IAAAA,EAAAC,EAAA,CAAAC,EAAAC,IAAA,CAAAA,EAAO,QAAU,eAAe,sBAAsB,ICAtD,IAAAC,EAAAC,EAAA,CAAAC,EAAAC,IAAA,CAAAA,EAAO,QAAU,eAAe,oBAAoB,ICApD,IAAAC,EAAAC,EAAA,CAAAC,EAAAC,IAAA,CAAAA,EAAO,QAAU,eAAe,iBAAiB,ICAjD,IAAAC,EAAA,GAAAC,EAAAD,EAAA,aAAAE,IAAA,IAAAC,EAAmB,OACnBC,EAAkD,OAClDC,EAAqC,
|
|
6
|
-
"names": ["require_Plugin", "__commonJSMin", "exports", "module", "require_util", "__commonJSMin", "exports", "module", "require_mobx_state_tree", "__commonJSMin", "exports", "module", "index_exports", "__export", "HubsViewerPlugin", "import_Plugin", "import_util", "import_mobx_state_tree", "asObjArray", "value", "mergeById", "base", "overrides", "key", "merged", "item", "toDesktopSnapshot", "jbrowse", "session", "sessionTracks", "sessionAssemblies", "defaultSession", "version", "downloadDesktopSession", "session", "jbrowse", "snap", "toDesktopSnapshot", "
|
|
4
|
+
"sourcesContent": ["module.exports = JBrowseExports[\"@jbrowse/core/Plugin\"];", "module.exports = JBrowseExports[\"@jbrowse/core/util\"];", "module.exports = JBrowseExports[\"mobx-state-tree\"];", "import Plugin from '@jbrowse/core/Plugin'\nimport { isAbstractMenuManager, isElectron } from '@jbrowse/core/util'\nimport { getRoot, getSnapshot } from '@jbrowse/mobx-state-tree'\n\nimport { saveAs, toDesktopSnapshot } from './util'\nimport { version } from '../package.json'\n\nimport type PluginManager from '@jbrowse/core/PluginManager'\nimport type { AbstractSessionModel } from '@jbrowse/core/util'\nimport type { IAnyStateTreeNode } from '@jbrowse/mobx-state-tree'\n\ninterface Session {\n makeConnection: (conf: unknown) => void\n addConnectionConf: (conf: unknown) => void\n connections: { connectionId: string }[]\n}\n\n// Serializes the current web session as a desktop `.jbrowse` file. genomes\n// sessions are config-shaped already, so this just folds the live session into\n// a defaultSession and downloads it; desktop opens it via File > Open session.\nfunction downloadDesktopSession(session: AbstractSessionModel) {\n const { jbrowse } = getRoot<{ jbrowse: IAnyStateTreeNode }>(session)\n const snap = toDesktopSnapshot(\n getSnapshot<Record<string, unknown>>(jbrowse),\n getSnapshot<Record<string, unknown>>(session),\n )\n saveAs(\n new Blob([JSON.stringify(snap, null, 2)], { type: 'application/json' }),\n 'session.jbrowse',\n )\n}\n\nfunction getGenArkConfigUrl(accession: string) {\n const [base, rest] = accession.split('_')\n if (!rest) {\n return undefined\n }\n const match = rest.match(/.{1,3}/g)\n if (!match || match.length < 3) {\n return undefined\n }\n const [b1, b2, b3] = match\n return `https://jbrowse.org/hubs/genark/${base}/${b1}/${b2}/${b3}/${accession}/config.json`\n}\n\nfunction getConfigUrl(assemblyName: string) {\n if (assemblyName.startsWith('GCA_') || assemblyName.startsWith('GCF_')) {\n return getGenArkConfigUrl(assemblyName)\n }\n return `https://jbrowse.org/ucsc/${assemblyName}/config.json`\n}\n\n// Names already being resolved, so the extension point firing repeatedly for\n// the same assembly (it fires on every unresolved read) starts one probe, not\n// one per read. The connection itself is deduped by connectionId, but that\n// check only happens once the probe has come back.\nconst pending = new Set<string>()\n\n// The url is a guess from the name, and plenty of assemblies aren't at it:\n// GenArk strain hubs (mouseStrains and friends) live under\n// /hubs/genark/<hub>/<strain>/, and a session can name an assembly this host\n// has never heard of. Connecting to a config that 404s puts a red error\n// snackbar over a session that is otherwise working, so probe first and stay\n// quiet when there's nothing there \u2014 some other handler, or the session\n// itself, may be supplying the assembly.\nasync function connectIfConfigExists(\n session: Session,\n assemblyName: string,\n uri: string,\n) {\n const connectionId = `jb2hub-${assemblyName}`\n if (\n !pending.has(assemblyName) &&\n !session.connections.find(f => f.connectionId === connectionId)\n ) {\n pending.add(assemblyName)\n try {\n const response = await fetch(uri, { method: 'HEAD' })\n if (response.ok) {\n const conf = {\n type: 'JB2TrackHubConnection',\n uri,\n name: `conn_${assemblyName}`,\n assemblyNames: [assemblyName],\n connectionId,\n }\n session.addConnectionConf(conf)\n session.makeConnection(conf)\n }\n } finally {\n pending.delete(assemblyName)\n }\n }\n}\n\nexport default class HubsViewerPlugin extends Plugin {\n name = 'HubsViewerPlugin'\n version = version\n\n install(pluginManager: PluginManager) {\n pluginManager.addToExtensionPoint(\n 'Core-handleUnrecognizedAssembly',\n (defaultResult, args) => {\n const session = args.session as Session | undefined\n const assemblyName = args.assemblyName as string | undefined\n const uri = session && assemblyName && getConfigUrl(assemblyName)\n if (session && assemblyName && uri) {\n // the extension point is sync; the probe and the connection it may\n // create land later, which is fine because assemblyManager re-reads\n // reactively once the assembly shows up\n connectIfConfigExists(session, assemblyName, uri).catch(\n (e: unknown) => {\n console.error(e)\n },\n )\n }\n // Pass the accumulator through: this extension point chains handlers, so\n // returning undefined would clobber another plugin's result.\n return defaultResult\n },\n )\n }\n\n configure(pluginManager: PluginManager) {\n // jbrowse-web only. In desktop you'd just save the session, and an exported\n // .jbrowse file carries this plugin in its plugins list, so an older desktop\n // would otherwise load and run this menu code when opening that file.\n if (!isElectron && isAbstractMenuManager(pluginManager.rootModel)) {\n // 'Tools', not 'File'. Every released core from v4.0.0 to latest defines\n // the File menu as a thunk and appends with `menu.menuItems.push()`, which\n // throws on a function; the throw escapes configure() and renders the app\n // as an error page rather than costing just this menu item. Because the\n // plugin store serves `latest/` no-cache, that took out every\n // jbrowse.org/ucsc launch on hg38/hg19/mm39/hs1 the moment 1.0.9 shipped.\n // core@main resolves item contributions lazily and accepts either form,\n // but this plugin has to keep working on the releases already in the wild.\n //\n // Belt and braces on the same reasoning: a cosmetic menu item should never\n // be able to cost a user their whole session.\n try {\n pluginManager.rootModel.appendToMenu('Tools', {\n label: 'Download desktop session (.jbrowse)',\n onClick: (session: AbstractSessionModel) => {\n downloadDesktopSession(session)\n },\n })\n } catch (e) {\n console.error('could not add the desktop-session download item', e)\n }\n }\n }\n}\n", "// Trigger a browser download of `blob` as `filename`. Mirrors the anchor-download\n// path of @jbrowse/core's saveAs: the deferred revoke avoids canceling the\n// download before the browser has read the blob (the read is async in some\n// browsers). Inlined until a published core release exports saveAs from its util.\nexport function saveAs(blob: Blob, filename: string) {\n const url = URL.createObjectURL(blob)\n const anchor = document.createElement('a')\n anchor.href = url\n anchor.download = filename\n anchor.rel = 'noopener'\n anchor.click()\n setTimeout(() => {\n URL.revokeObjectURL(url)\n }, 40000)\n}\n\nfunction asObjArray(value: unknown): Record<string, unknown>[] {\n return Array.isArray(value) ? (value as Record<string, unknown>[]) : []\n}\n\n// Merges two lists of config objects by an id key, with `overrides` winning so\n// a session-scoped track/assembly that shadows a config one (same id) replaces\n// it rather than duplicating.\nfunction mergeById(\n base: Record<string, unknown>[],\n overrides: Record<string, unknown>[],\n key: string,\n): Record<string, unknown>[] {\n const merged = new Map<unknown, Record<string, unknown>>()\n for (const item of [...base, ...overrides]) {\n merged.set(item[key], item)\n }\n return [...merged.values()]\n}\n\n// Builds a desktop `.jbrowse` snapshot (a config plus a defaultSession) from a\n// web root config and its live session. Session-scoped tracks/assemblies are\n// hoisted into the config arrays so they open as first-class config entries in\n// desktop; the rest of the session becomes the defaultSession. Web sessions are\n// always UriLocation-based, so the result is fully portable to desktop.\nexport function toDesktopSnapshot(\n jbrowse: Record<string, unknown>,\n session: Record<string, unknown>,\n): Record<string, unknown> {\n const { sessionTracks, sessionAssemblies, ...defaultSession } = session\n return {\n ...jbrowse,\n tracks: mergeById(\n asObjArray(jbrowse.tracks),\n asObjArray(sessionTracks),\n 'trackId',\n ),\n assemblies: mergeById(\n asObjArray(jbrowse.assemblies),\n asObjArray(sessionAssemblies),\n 'name',\n ),\n defaultSession,\n }\n}\n", "{\n \"version\": \"1.0.12\",\n \"license\": \"MIT\",\n \"name\": \"@cmdcolin/jbrowse-plugin-hubs\",\n \"repository\": {\n \"type\": \"git\",\n \"url\": \"git+https://github.com/GMOD/jbrowse-plugin-hubs.git\"\n },\n \"keywords\": [\n \"jbrowse\",\n \"jbrowse2\"\n ],\n \"main\": \"dist/index.js\",\n \"module\": \"dist/index.esm.js\",\n \"typings\": \"dist/index.d.ts\",\n \"files\": [\n \"dist\",\n \"src\"\n ],\n \"scripts\": {\n \"clean\": \"rimraf dist\",\n \"start\": \"node esbuild.mjs --watch\",\n \"test\": \"echo 'no tests'\",\n \"format\": \"pnpm prettier --write .\",\n \"prebuild\": \"pnpm clean\",\n \"build\": \"tsc && NODE_ENV=production node esbuild.mjs\",\n \"lint\": \"eslint src --report-unused-disable-directives --max-warnings 0\",\n \"prepack\": \"pnpm build\",\n \"postversion\": \"git push --follow-tags\",\n \"preversion\": \"pnpm lint\"\n },\n \"devDependencies\": {\n \"@eslint/js\": \"^10.0.1\",\n \"@fal-works/esbuild-plugin-global-externals\": \"^2.1.2\",\n \"@jbrowse/core\": \"^4.3.0\",\n \"@jbrowse/mobx-state-tree\": \"^5.11.1\",\n \"@mui/material\": \"^7.3.11\",\n \"@mui/system\": \"^7.3.11\",\n \"@mui/x-data-grid\": \"^8.29.1\",\n \"@types/node\": \"^25.9.3\",\n \"@types/react\": \"^19.2.17\",\n \"@typescript-eslint/eslint-plugin\": \"^8.61.1\",\n \"@typescript-eslint/parser\": \"^8.61.1\",\n \"esbuild\": \"^0.27.7\",\n \"eslint\": \"^10.5.0\",\n \"eslint-plugin-import-x\": \"^4.16.2\",\n \"eslint-plugin-react\": \"^7.37.5\",\n \"eslint-plugin-react-hooks\": \"^7.1.1\",\n \"eslint-plugin-unicorn\": \"^64.0.0\",\n \"mobx\": \"^6.16.1\",\n \"mobx-react\": \"^9.2.2\",\n \"prettier\": \"^3.8.4\",\n \"pretty-bytes\": \"^7.1.0\",\n \"react\": \"^19.2.7\",\n \"react-dom\": \"^19.2.7\",\n \"rimraf\": \"^6.1.3\",\n \"rxjs\": \"^7.8.2\",\n \"serve\": \"^14.2.6\",\n \"typescript\": \"^5.9.3\",\n \"typescript-eslint\": \"^8.61.1\"\n },\n \"publishConfig\": {\n \"access\": \"public\"\n }\n}\n"],
|
|
5
|
+
"mappings": "gpBAAA,IAAAA,EAAAC,EAAA,CAAAC,EAAAC,IAAA,CAAAA,EAAO,QAAU,eAAe,sBAAsB,ICAtD,IAAAC,EAAAC,EAAA,CAAAC,EAAAC,IAAA,CAAAA,EAAO,QAAU,eAAe,oBAAoB,ICApD,IAAAC,EAAAC,EAAA,CAAAC,EAAAC,IAAA,CAAAA,EAAO,QAAU,eAAe,iBAAiB,ICAjD,IAAAC,EAAA,GAAAC,EAAAD,EAAA,aAAAE,IAAA,IAAAC,EAAmB,OACnBC,EAAkD,OAClDC,EAAqC,OCE9B,SAASC,EAAOC,EAAYC,EAAkB,CACnD,IAAMC,EAAM,IAAI,gBAAgBF,CAAI,EAC9BG,EAAS,SAAS,cAAc,GAAG,EACzCA,EAAO,KAAOD,EACdC,EAAO,SAAWF,EAClBE,EAAO,IAAM,WACbA,EAAO,MAAM,EACb,WAAW,IAAM,CACf,IAAI,gBAAgBD,CAAG,CACzB,EAAG,GAAK,CACV,CAEA,SAASE,EAAWC,EAA2C,CAC7D,OAAO,MAAM,QAAQA,CAAK,EAAKA,EAAsC,CAAC,CACxE,CAKA,SAASC,EACPC,EACAC,EACAC,EAC2B,CAC3B,IAAMC,EAAS,IAAI,IACnB,QAAWC,IAAQ,CAAC,GAAGJ,EAAM,GAAGC,CAAS,EACvCE,EAAO,IAAIC,EAAKF,CAAG,EAAGE,CAAI,EAE5B,MAAO,CAAC,GAAGD,EAAO,OAAO,CAAC,CAC5B,CAOO,SAASE,EACdC,EACAC,EACyB,CACzB,GAAM,CAAE,cAAAC,EAAe,kBAAAC,EAAmB,GAAGC,CAAe,EAAIH,EAChE,MAAO,CACL,GAAGD,EACH,OAAQP,EACNF,EAAWS,EAAQ,MAAM,EACzBT,EAAWW,CAAa,EACxB,SACF,EACA,WAAYT,EACVF,EAAWS,EAAQ,UAAU,EAC7BT,EAAWY,CAAiB,EAC5B,MACF,EACA,eAAAC,CACF,CACF,CC1DE,IAAAC,EAAW,SFmBb,SAASC,EAAuBC,EAA+B,CAC7D,GAAM,CAAE,QAAAC,CAAQ,KAAI,WAAwCD,CAAO,EAC7DE,EAAOC,KACX,eAAqCF,CAAO,KAC5C,eAAqCD,CAAO,CAC9C,EACAI,EACE,IAAI,KAAK,CAAC,KAAK,UAAUF,EAAM,KAAM,CAAC,CAAC,EAAG,CAAE,KAAM,kBAAmB,CAAC,EACtE,iBACF,CACF,CAEA,SAASG,EAAmBC,EAAmB,CAC7C,GAAM,CAACC,EAAMC,CAAI,EAAIF,EAAU,MAAM,GAAG,EACxC,GAAI,CAACE,EACH,OAEF,IAAMC,EAAQD,EAAK,MAAM,SAAS,EAClC,GAAI,CAACC,GAASA,EAAM,OAAS,EAC3B,OAEF,GAAM,CAACC,EAAIC,EAAIC,CAAE,EAAIH,EACrB,MAAO,mCAAmCF,CAAI,IAAIG,CAAE,IAAIC,CAAE,IAAIC,CAAE,IAAIN,CAAS,cAC/E,CAEA,SAASO,EAAaC,EAAsB,CAC1C,OAAIA,EAAa,WAAW,MAAM,GAAKA,EAAa,WAAW,MAAM,EAC5DT,EAAmBS,CAAY,EAEjC,4BAA4BA,CAAY,cACjD,CAMA,IAAMC,EAAU,IAAI,IASpB,eAAeC,EACbhB,EACAc,EACAG,EACA,CACA,IAAMC,EAAe,UAAUJ,CAAY,GAC3C,GACE,CAACC,EAAQ,IAAID,CAAY,GACzB,CAACd,EAAQ,YAAY,KAAKmB,GAAKA,EAAE,eAAiBD,CAAY,EAC9D,CACAH,EAAQ,IAAID,CAAY,EACxB,GAAI,CAEF,IADiB,MAAM,MAAMG,EAAK,CAAE,OAAQ,MAAO,CAAC,GACvC,GAAI,CACf,IAAMG,EAAO,CACX,KAAM,wBACN,IAAAH,EACA,KAAM,QAAQH,CAAY,GAC1B,cAAe,CAACA,CAAY,EAC5B,aAAAI,CACF,EACAlB,EAAQ,kBAAkBoB,CAAI,EAC9BpB,EAAQ,eAAeoB,CAAI,CAC7B,CACF,QAAE,CACAL,EAAQ,OAAOD,CAAY,CAC7B,CACF,CACF,CAEA,IAAqBO,EAArB,cAA8C,EAAAC,OAAO,CACnD,KAAO,mBACP,QAAUC,EAEV,QAAQC,EAA8B,CACpCA,EAAc,oBACZ,kCACA,CAACC,EAAeC,IAAS,CACvB,IAAM1B,EAAU0B,EAAK,QACfZ,EAAeY,EAAK,aACpBT,EAAMjB,GAAWc,GAAgBD,EAAaC,CAAY,EAChE,OAAId,GAAWc,GAAgBG,GAI7BD,EAAsBhB,EAASc,EAAcG,CAAG,EAAE,MAC/CU,GAAe,CACd,QAAQ,MAAMA,CAAC,CACjB,CACF,EAIKF,CACT,CACF,CACF,CAEA,UAAUD,EAA8B,CAItC,GAAI,CAAC,iBAAc,yBAAsBA,EAAc,SAAS,EAY9D,GAAI,CACFA,EAAc,UAAU,aAAa,QAAS,CAC5C,MAAO,sCACP,QAAUxB,GAAkC,CAC1CD,EAAuBC,CAAO,CAChC,CACF,CAAC,CACH,OAAS2B,EAAG,CACV,QAAQ,MAAM,kDAAmDA,CAAC,CACpE,CAEJ,CACF",
|
|
6
|
+
"names": ["require_Plugin", "__commonJSMin", "exports", "module", "require_util", "__commonJSMin", "exports", "module", "require_mobx_state_tree", "__commonJSMin", "exports", "module", "index_exports", "__export", "HubsViewerPlugin", "import_Plugin", "import_util", "import_mobx_state_tree", "saveAs", "blob", "filename", "url", "anchor", "asObjArray", "value", "mergeById", "base", "overrides", "key", "merged", "item", "toDesktopSnapshot", "jbrowse", "session", "sessionTracks", "sessionAssemblies", "defaultSession", "version", "downloadDesktopSession", "session", "jbrowse", "snap", "toDesktopSnapshot", "saveAs", "getGenArkConfigUrl", "accession", "base", "rest", "match", "b1", "b2", "b3", "getConfigUrl", "assemblyName", "pending", "connectIfConfigExists", "uri", "connectionId", "f", "conf", "HubsViewerPlugin", "Plugin", "version", "pluginManager", "defaultResult", "args", "e"]
|
|
7
7
|
}
|
package/dist/util.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export declare function
|
|
1
|
+
export declare function saveAs(blob: Blob, filename: string): void;
|
|
2
2
|
export declare function toDesktopSnapshot(jbrowse: Record<string, unknown>, session: Record<string, unknown>): Record<string, unknown>;
|
package/dist/util.js
CHANGED
|
@@ -1,9 +1,17 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
1
|
+
// Trigger a browser download of `blob` as `filename`. Mirrors the anchor-download
|
|
2
|
+
// path of @jbrowse/core's saveAs: the deferred revoke avoids canceling the
|
|
3
|
+
// download before the browser has read the blob (the read is async in some
|
|
4
|
+
// browsers). Inlined until a published core release exports saveAs from its util.
|
|
5
|
+
export function saveAs(blob, filename) {
|
|
6
|
+
const url = URL.createObjectURL(blob);
|
|
7
|
+
const anchor = document.createElement('a');
|
|
8
|
+
anchor.href = url;
|
|
9
|
+
anchor.download = filename;
|
|
10
|
+
anchor.rel = 'noopener';
|
|
11
|
+
anchor.click();
|
|
12
|
+
setTimeout(() => {
|
|
13
|
+
URL.revokeObjectURL(url);
|
|
14
|
+
}, 40000);
|
|
7
15
|
}
|
|
8
16
|
function asObjArray(value) {
|
|
9
17
|
return Array.isArray(value) ? value : [];
|
package/dist/util.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"util.js","sourceRoot":"","sources":["../src/util.ts"],"names":[],"mappings":"AAAA,
|
|
1
|
+
{"version":3,"file":"util.js","sourceRoot":"","sources":["../src/util.ts"],"names":[],"mappings":"AAAA,kFAAkF;AAClF,2EAA2E;AAC3E,2EAA2E;AAC3E,kFAAkF;AAClF,MAAM,UAAU,MAAM,CAAC,IAAU,EAAE,QAAgB;IACjD,MAAM,GAAG,GAAG,GAAG,CAAC,eAAe,CAAC,IAAI,CAAC,CAAA;IACrC,MAAM,MAAM,GAAG,QAAQ,CAAC,aAAa,CAAC,GAAG,CAAC,CAAA;IAC1C,MAAM,CAAC,IAAI,GAAG,GAAG,CAAA;IACjB,MAAM,CAAC,QAAQ,GAAG,QAAQ,CAAA;IAC1B,MAAM,CAAC,GAAG,GAAG,UAAU,CAAA;IACvB,MAAM,CAAC,KAAK,EAAE,CAAA;IACd,UAAU,CAAC,GAAG,EAAE;QACd,GAAG,CAAC,eAAe,CAAC,GAAG,CAAC,CAAA;IAC1B,CAAC,EAAE,KAAK,CAAC,CAAA;AACX,CAAC;AAED,SAAS,UAAU,CAAC,KAAc;IAChC,OAAO,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAE,KAAmC,CAAC,CAAC,CAAC,EAAE,CAAA;AACzE,CAAC;AAED,+EAA+E;AAC/E,+EAA+E;AAC/E,8BAA8B;AAC9B,SAAS,SAAS,CAChB,IAA+B,EAC/B,SAAoC,EACpC,GAAW;IAEX,MAAM,MAAM,GAAG,IAAI,GAAG,EAAoC,CAAA;IAC1D,KAAK,MAAM,IAAI,IAAI,CAAC,GAAG,IAAI,EAAE,GAAG,SAAS,CAAC,EAAE,CAAC;QAC3C,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,IAAI,CAAC,CAAA;IAC7B,CAAC;IACD,OAAO,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,CAAC,CAAA;AAC7B,CAAC;AAED,+EAA+E;AAC/E,6EAA6E;AAC7E,+EAA+E;AAC/E,gFAAgF;AAChF,wEAAwE;AACxE,MAAM,UAAU,iBAAiB,CAC/B,OAAgC,EAChC,OAAgC;IAEhC,MAAM,EAAE,aAAa,EAAE,iBAAiB,EAAE,GAAG,cAAc,EAAE,GAAG,OAAO,CAAA;IACvE,OAAO;QACL,GAAG,OAAO;QACV,MAAM,EAAE,SAAS,CACf,UAAU,CAAC,OAAO,CAAC,MAAM,CAAC,EAC1B,UAAU,CAAC,aAAa,CAAC,EACzB,SAAS,CACV;QACD,UAAU,EAAE,SAAS,CACnB,UAAU,CAAC,OAAO,CAAC,UAAU,CAAC,EAC9B,UAAU,CAAC,iBAAiB,CAAC,EAC7B,MAAM,CACP;QACD,cAAc;KACf,CAAA;AACH,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "1.0.
|
|
2
|
+
"version": "1.0.12",
|
|
3
3
|
"license": "MIT",
|
|
4
4
|
"name": "@cmdcolin/jbrowse-plugin-hubs",
|
|
5
5
|
"repository": {
|
|
@@ -32,32 +32,32 @@
|
|
|
32
32
|
"devDependencies": {
|
|
33
33
|
"@eslint/js": "^10.0.1",
|
|
34
34
|
"@fal-works/esbuild-plugin-global-externals": "^2.1.2",
|
|
35
|
-
"@jbrowse/core": "^4.
|
|
36
|
-
"@jbrowse/mobx-state-tree": "^5.
|
|
37
|
-
"@mui/material": "^7.3.
|
|
38
|
-
"@mui/system": "^7.3.
|
|
39
|
-
"@mui/x-data-grid": "^8.
|
|
40
|
-
"@types/node": "^25.
|
|
41
|
-
"@types/react": "^19.2.
|
|
42
|
-
"@typescript-eslint/eslint-plugin": "^8.
|
|
43
|
-
"@typescript-eslint/parser": "^8.
|
|
44
|
-
"esbuild": "^0.27.
|
|
45
|
-
"eslint": "^10.
|
|
35
|
+
"@jbrowse/core": "^4.3.0",
|
|
36
|
+
"@jbrowse/mobx-state-tree": "^5.11.1",
|
|
37
|
+
"@mui/material": "^7.3.11",
|
|
38
|
+
"@mui/system": "^7.3.11",
|
|
39
|
+
"@mui/x-data-grid": "^8.29.1",
|
|
40
|
+
"@types/node": "^25.9.3",
|
|
41
|
+
"@types/react": "^19.2.17",
|
|
42
|
+
"@typescript-eslint/eslint-plugin": "^8.61.1",
|
|
43
|
+
"@typescript-eslint/parser": "^8.61.1",
|
|
44
|
+
"esbuild": "^0.27.7",
|
|
45
|
+
"eslint": "^10.5.0",
|
|
46
46
|
"eslint-plugin-import-x": "^4.16.2",
|
|
47
47
|
"eslint-plugin-react": "^7.37.5",
|
|
48
48
|
"eslint-plugin-react-hooks": "^7.1.1",
|
|
49
49
|
"eslint-plugin-unicorn": "^64.0.0",
|
|
50
|
-
"mobx": "^6.
|
|
51
|
-
"mobx-react": "^9.
|
|
52
|
-
"prettier": "^3.8.
|
|
53
|
-
"pretty-bytes": "^7.
|
|
54
|
-
"react": "^19.2.
|
|
55
|
-
"react-dom": "^19.2.
|
|
56
|
-
"rimraf": "^6.1.
|
|
57
|
-
"rxjs": "^7.8.
|
|
58
|
-
"serve": "^14.2.
|
|
59
|
-
"typescript": "^5.
|
|
60
|
-
"typescript-eslint": "^8.
|
|
50
|
+
"mobx": "^6.16.1",
|
|
51
|
+
"mobx-react": "^9.2.2",
|
|
52
|
+
"prettier": "^3.8.4",
|
|
53
|
+
"pretty-bytes": "^7.1.0",
|
|
54
|
+
"react": "^19.2.7",
|
|
55
|
+
"react-dom": "^19.2.7",
|
|
56
|
+
"rimraf": "^6.1.3",
|
|
57
|
+
"rxjs": "^7.8.2",
|
|
58
|
+
"serve": "^14.2.6",
|
|
59
|
+
"typescript": "^5.9.3",
|
|
60
|
+
"typescript-eslint": "^8.61.1"
|
|
61
61
|
},
|
|
62
62
|
"publishConfig": {
|
|
63
63
|
"access": "public"
|
package/src/index.ts
CHANGED
|
@@ -2,7 +2,7 @@ import Plugin from '@jbrowse/core/Plugin'
|
|
|
2
2
|
import { isAbstractMenuManager, isElectron } from '@jbrowse/core/util'
|
|
3
3
|
import { getRoot, getSnapshot } from '@jbrowse/mobx-state-tree'
|
|
4
4
|
|
|
5
|
-
import { toDesktopSnapshot } from './util'
|
|
5
|
+
import { saveAs, toDesktopSnapshot } from './util'
|
|
6
6
|
import { version } from '../package.json'
|
|
7
7
|
|
|
8
8
|
import type PluginManager from '@jbrowse/core/PluginManager'
|
|
@@ -24,13 +24,10 @@ function downloadDesktopSession(session: AbstractSessionModel) {
|
|
|
24
24
|
getSnapshot<Record<string, unknown>>(jbrowse),
|
|
25
25
|
getSnapshot<Record<string, unknown>>(session),
|
|
26
26
|
)
|
|
27
|
-
|
|
28
|
-
anchor.href = URL.createObjectURL(
|
|
27
|
+
saveAs(
|
|
29
28
|
new Blob([JSON.stringify(snap, null, 2)], { type: 'application/json' }),
|
|
29
|
+
'session.jbrowse',
|
|
30
30
|
)
|
|
31
|
-
anchor.download = 'session.jbrowse'
|
|
32
|
-
anchor.click()
|
|
33
|
-
URL.revokeObjectURL(anchor.href)
|
|
34
31
|
}
|
|
35
32
|
|
|
36
33
|
function getGenArkConfigUrl(accession: string) {
|
|
@@ -53,6 +50,49 @@ function getConfigUrl(assemblyName: string) {
|
|
|
53
50
|
return `https://jbrowse.org/ucsc/${assemblyName}/config.json`
|
|
54
51
|
}
|
|
55
52
|
|
|
53
|
+
// Names already being resolved, so the extension point firing repeatedly for
|
|
54
|
+
// the same assembly (it fires on every unresolved read) starts one probe, not
|
|
55
|
+
// one per read. The connection itself is deduped by connectionId, but that
|
|
56
|
+
// check only happens once the probe has come back.
|
|
57
|
+
const pending = new Set<string>()
|
|
58
|
+
|
|
59
|
+
// The url is a guess from the name, and plenty of assemblies aren't at it:
|
|
60
|
+
// GenArk strain hubs (mouseStrains and friends) live under
|
|
61
|
+
// /hubs/genark/<hub>/<strain>/, and a session can name an assembly this host
|
|
62
|
+
// has never heard of. Connecting to a config that 404s puts a red error
|
|
63
|
+
// snackbar over a session that is otherwise working, so probe first and stay
|
|
64
|
+
// quiet when there's nothing there — some other handler, or the session
|
|
65
|
+
// itself, may be supplying the assembly.
|
|
66
|
+
async function connectIfConfigExists(
|
|
67
|
+
session: Session,
|
|
68
|
+
assemblyName: string,
|
|
69
|
+
uri: string,
|
|
70
|
+
) {
|
|
71
|
+
const connectionId = `jb2hub-${assemblyName}`
|
|
72
|
+
if (
|
|
73
|
+
!pending.has(assemblyName) &&
|
|
74
|
+
!session.connections.find(f => f.connectionId === connectionId)
|
|
75
|
+
) {
|
|
76
|
+
pending.add(assemblyName)
|
|
77
|
+
try {
|
|
78
|
+
const response = await fetch(uri, { method: 'HEAD' })
|
|
79
|
+
if (response.ok) {
|
|
80
|
+
const conf = {
|
|
81
|
+
type: 'JB2TrackHubConnection',
|
|
82
|
+
uri,
|
|
83
|
+
name: `conn_${assemblyName}`,
|
|
84
|
+
assemblyNames: [assemblyName],
|
|
85
|
+
connectionId,
|
|
86
|
+
}
|
|
87
|
+
session.addConnectionConf(conf)
|
|
88
|
+
session.makeConnection(conf)
|
|
89
|
+
}
|
|
90
|
+
} finally {
|
|
91
|
+
pending.delete(assemblyName)
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
56
96
|
export default class HubsViewerPlugin extends Plugin {
|
|
57
97
|
name = 'HubsViewerPlugin'
|
|
58
98
|
version = version
|
|
@@ -60,28 +100,23 @@ export default class HubsViewerPlugin extends Plugin {
|
|
|
60
100
|
install(pluginManager: PluginManager) {
|
|
61
101
|
pluginManager.addToExtensionPoint(
|
|
62
102
|
'Core-handleUnrecognizedAssembly',
|
|
63
|
-
(
|
|
103
|
+
(defaultResult, args) => {
|
|
64
104
|
const session = args.session as Session | undefined
|
|
65
105
|
const assemblyName = args.assemblyName as string | undefined
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
type: 'JB2TrackHubConnection',
|
|
77
|
-
uri,
|
|
78
|
-
name: `conn_${assemblyName}`,
|
|
79
|
-
assemblyNames: [assemblyName],
|
|
80
|
-
connectionId,
|
|
81
|
-
}
|
|
82
|
-
session.addConnectionConf(conf)
|
|
83
|
-
session.makeConnection(conf)
|
|
106
|
+
const uri = session && assemblyName && getConfigUrl(assemblyName)
|
|
107
|
+
if (session && assemblyName && uri) {
|
|
108
|
+
// the extension point is sync; the probe and the connection it may
|
|
109
|
+
// create land later, which is fine because assemblyManager re-reads
|
|
110
|
+
// reactively once the assembly shows up
|
|
111
|
+
connectIfConfigExists(session, assemblyName, uri).catch(
|
|
112
|
+
(e: unknown) => {
|
|
113
|
+
console.error(e)
|
|
114
|
+
},
|
|
115
|
+
)
|
|
84
116
|
}
|
|
117
|
+
// Pass the accumulator through: this extension point chains handlers, so
|
|
118
|
+
// returning undefined would clobber another plugin's result.
|
|
119
|
+
return defaultResult
|
|
85
120
|
},
|
|
86
121
|
)
|
|
87
122
|
}
|
|
@@ -91,12 +126,27 @@ export default class HubsViewerPlugin extends Plugin {
|
|
|
91
126
|
// .jbrowse file carries this plugin in its plugins list, so an older desktop
|
|
92
127
|
// would otherwise load and run this menu code when opening that file.
|
|
93
128
|
if (!isElectron && isAbstractMenuManager(pluginManager.rootModel)) {
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
129
|
+
// 'Tools', not 'File'. Every released core from v4.0.0 to latest defines
|
|
130
|
+
// the File menu as a thunk and appends with `menu.menuItems.push()`, which
|
|
131
|
+
// throws on a function; the throw escapes configure() and renders the app
|
|
132
|
+
// as an error page rather than costing just this menu item. Because the
|
|
133
|
+
// plugin store serves `latest/` no-cache, that took out every
|
|
134
|
+
// jbrowse.org/ucsc launch on hg38/hg19/mm39/hs1 the moment 1.0.9 shipped.
|
|
135
|
+
// core@main resolves item contributions lazily and accepts either form,
|
|
136
|
+
// but this plugin has to keep working on the releases already in the wild.
|
|
137
|
+
//
|
|
138
|
+
// Belt and braces on the same reasoning: a cosmetic menu item should never
|
|
139
|
+
// be able to cost a user their whole session.
|
|
140
|
+
try {
|
|
141
|
+
pluginManager.rootModel.appendToMenu('Tools', {
|
|
142
|
+
label: 'Download desktop session (.jbrowse)',
|
|
143
|
+
onClick: (session: AbstractSessionModel) => {
|
|
144
|
+
downloadDesktopSession(session)
|
|
145
|
+
},
|
|
146
|
+
})
|
|
147
|
+
} catch (e) {
|
|
148
|
+
console.error('could not add the desktop-session download item', e)
|
|
149
|
+
}
|
|
100
150
|
}
|
|
101
151
|
}
|
|
102
152
|
}
|
package/src/util.ts
CHANGED
|
@@ -1,9 +1,17 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
1
|
+
// Trigger a browser download of `blob` as `filename`. Mirrors the anchor-download
|
|
2
|
+
// path of @jbrowse/core's saveAs: the deferred revoke avoids canceling the
|
|
3
|
+
// download before the browser has read the blob (the read is async in some
|
|
4
|
+
// browsers). Inlined until a published core release exports saveAs from its util.
|
|
5
|
+
export function saveAs(blob: Blob, filename: string) {
|
|
6
|
+
const url = URL.createObjectURL(blob)
|
|
7
|
+
const anchor = document.createElement('a')
|
|
8
|
+
anchor.href = url
|
|
9
|
+
anchor.download = filename
|
|
10
|
+
anchor.rel = 'noopener'
|
|
11
|
+
anchor.click()
|
|
12
|
+
setTimeout(() => {
|
|
13
|
+
URL.revokeObjectURL(url)
|
|
14
|
+
}, 40000)
|
|
7
15
|
}
|
|
8
16
|
|
|
9
17
|
function asObjArray(value: unknown): Record<string, unknown>[] {
|