@cmdcolin/jbrowse-plugin-hubs 1.0.6 → 1.0.9

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.d.ts CHANGED
@@ -1,8 +1,8 @@
1
1
  import Plugin from '@jbrowse/core/Plugin';
2
- import PluginManager from '@jbrowse/core/PluginManager';
2
+ import type PluginManager from '@jbrowse/core/PluginManager';
3
3
  export default class HubsViewerPlugin extends Plugin {
4
4
  name: string;
5
5
  version: any;
6
6
  install(pluginManager: PluginManager): void;
7
- configure(_pluginManager: PluginManager): void;
7
+ configure(pluginManager: PluginManager): void;
8
8
  }
package/dist/index.js CHANGED
@@ -1,5 +1,20 @@
1
1
  import Plugin from '@jbrowse/core/Plugin';
2
+ import { isAbstractMenuManager, isElectron } from '@jbrowse/core/util';
3
+ import { getRoot, getSnapshot } from '@jbrowse/mobx-state-tree';
4
+ import { toDesktopSnapshot } from './util';
2
5
  import { version } from '../package.json';
6
+ // Serializes the current web session as a desktop `.jbrowse` file. genomes
7
+ // sessions are config-shaped already, so this just folds the live session into
8
+ // a defaultSession and downloads it; desktop opens it via File > Open session.
9
+ function downloadDesktopSession(session) {
10
+ const { jbrowse } = getRoot(session);
11
+ const snap = toDesktopSnapshot(getSnapshot(jbrowse), getSnapshot(session));
12
+ const anchor = document.createElement('a');
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);
17
+ }
3
18
  function getGenArkConfigUrl(accession) {
4
19
  const [base, rest] = accession.split('_');
5
20
  if (!rest) {
@@ -46,6 +61,18 @@ export default class HubsViewerPlugin extends Plugin {
46
61
  }
47
62
  });
48
63
  }
49
- configure(_pluginManager) { }
64
+ configure(pluginManager) {
65
+ // jbrowse-web only. In desktop you'd just save the session, and an exported
66
+ // .jbrowse file carries this plugin in its plugins list, so an older desktop
67
+ // would otherwise load and run this menu code when opening that file.
68
+ if (!isElectron && isAbstractMenuManager(pluginManager.rootModel)) {
69
+ pluginManager.rootModel.appendToMenu('File', {
70
+ label: 'Download desktop session (.jbrowse)',
71
+ onClick: (session) => {
72
+ downloadDesktopSession(session);
73
+ },
74
+ });
75
+ }
76
+ }
50
77
  }
51
78
  //# sourceMappingURL=index.js.map
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;AAGzC,OAAO,EAAE,OAAO,EAAE,MAAM,iBAAiB,CAAA;AAQzC,SAAS,kBAAkB,CAAC,SAAiB;IAC3C,MAAM,CAAC,IAAI,EAAE,IAAI,CAAC,GAAG,SAAS,CAAC,KAAK,CAAC,GAAG,CAAC,CAAA;IACzC,IAAI,CAAC,IAAI,EAAE,CAAC;QACV,OAAO,SAAS,CAAA;IAClB,CAAC;IACD,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC,CAAA;IACnC,IAAI,CAAC,KAAK,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC/B,OAAO,SAAS,CAAA;IAClB,CAAC;IACD,MAAM,CAAC,EAAE,EAAE,EAAE,EAAE,EAAE,CAAC,GAAG,KAAK,CAAA;IAC1B,OAAO,mCAAmC,IAAI,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,SAAS,cAAc,CAAA;AAC7F,CAAC;AAED,SAAS,YAAY,CAAC,YAAoB;IACxC,IAAI,YAAY,CAAC,UAAU,CAAC,MAAM,CAAC,IAAI,YAAY,CAAC,UAAU,CAAC,MAAM,CAAC,EAAE,CAAC;QACvE,OAAO,kBAAkB,CAAC,YAAY,CAAC,CAAA;IACzC,CAAC;IACD,OAAO,4BAA4B,YAAY,cAAc,CAAA;AAC/D,CAAC;AAED,MAAM,CAAC,OAAO,OAAO,gBAAiB,SAAQ,MAAM;IAClD,IAAI,GAAG,kBAAkB,CAAA;IACzB,OAAO,GAAG,OAAO,CAAA;IAEjB,OAAO,CAAC,aAA4B;QAClC,aAAa,CAAC,mBAAmB,CAC/B,iCAAiC,EACjC,CAAC,cAAc,EAAE,IAAI,EAAE,EAAE;YACvB,MAAM,OAAO,GAAG,IAAI,CAAC,OAA8B,CAAA;YACnD,MAAM,YAAY,GAAG,IAAI,CAAC,YAAkC,CAAA;YAC5D,IAAI,CAAC,OAAO,IAAI,CAAC,YAAY,EAAE,CAAC;gBAC9B,OAAM;YACR,CAAC;YACD,MAAM,GAAG,GAAG,YAAY,CAAC,YAAY,CAAC,CAAA;YACtC,IAAI,CAAC,GAAG,EAAE,CAAC;gBACT,OAAM;YACR,CAAC;YACD,MAAM,YAAY,GAAG,UAAU,YAAY,EAAE,CAAA;YAC7C,IAAI,CAAC,OAAO,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,YAAY,KAAK,YAAY,CAAC,EAAE,CAAC;gBACpE,MAAM,IAAI,GAAG;oBACX,IAAI,EAAE,uBAAuB;oBAC7B,GAAG;oBACH,IAAI,EAAE,QAAQ,YAAY,EAAE;oBAC5B,aAAa,EAAE,CAAC,YAAY,CAAC;oBAC7B,YAAY;iBACb,CAAA;gBACD,OAAO,CAAC,iBAAiB,CAAC,IAAI,CAAC,CAAA;gBAC/B,OAAO,CAAC,cAAc,CAAC,IAAI,CAAC,CAAA;YAC9B,CAAC;QACH,CAAC,CACF,CAAA;IACH,CAAC;IAED,SAAS,CAAC,cAA6B,IAAG,CAAC;CAC5C"}
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 b=Object.create;var a=Object.defineProperty;var j=Object.getOwnPropertyDescriptor;var w=Object.getOwnPropertyNames;var h=Object.getPrototypeOf,x=Object.prototype.hasOwnProperty;var y=(n,e)=>()=>(e||n((e={exports:{}}).exports,e),e.exports),C=(n,e)=>{for(var t in e)a(n,t,{get:e[t],enumerable:!0})},l=(n,e,t,i)=>{if(e&&typeof e=="object"||typeof e=="function")for(let s of w(e))!x.call(n,s)&&s!==t&&a(n,s,{get:()=>e[s],enumerable:!(i=j(e,s))||i.enumerable});return n};var k=(n,e,t)=>(t=n!=null?b(h(n)):{},l(e||!n||!n.__esModule?a(t,"default",{value:n,enumerable:!0}):t,n)),v=n=>l(a({},"__esModule",{value:!0}),n);var p=y((I,g)=>{g.exports=JBrowseExports["@jbrowse/core/Plugin"]});var M={};C(M,{default:()=>d});var m=k(p());var f="1.0.6";function _(n){let[e,t]=n.split("_");if(!t)return;let i=t.match(/.{1,3}/g);if(!i||i.length<3)return;let[s,o,r]=i;return`https://jbrowse.org/hubs/genark/${e}/${s}/${o}/${r}/${n}/config.json`}function P(n){return n.startsWith("GCA_")||n.startsWith("GCF_")?_(n):`https://jbrowse.org/ucsc/${n}/config.json`}var d=class extends m.default{name="HubsViewerPlugin";version=f;install(e){e.addToExtensionPoint("Core-handleUnrecognizedAssembly",(t,i)=>{let s=i.session,o=i.assemblyName;if(!s||!o)return;let r=P(o);if(!r)return;let u=`jb2hub-${o}`;if(!s.connections.find(c=>c.connectionId===u)){let c={type:"JB2TrackHubConnection",uri:r,name:`conn_${o}`,assemblyNames:[o],connectionId:u};s.addConnectionConf(c),s.makeConnection(c)}})}configure(e){}};return v(M);})();
1
+ "use strict";var JBrowsePluginHubs=(()=>{var S=Object.create;var u=Object.defineProperty;var v=Object.getOwnPropertyDescriptor;var $=Object.getOwnPropertyNames;var E=Object.getPrototypeOf,I=Object.prototype.hasOwnProperty;var g=(n,e)=>()=>(e||n((e={exports:{}}).exports,e),e.exports),T=(n,e)=>{for(var t in e)u(n,t,{get:e[t],enumerable:!0})},b=(n,e,t,o)=>{if(e&&typeof e=="object"||typeof e=="function")for(let s of $(e))!I.call(n,s)&&s!==t&&u(n,s,{get:()=>e[s],enumerable:!(o=v(e,s))||o.enumerable});return n};var m=(n,e,t)=>(t=n!=null?S(E(n)):{},b(e||!n||!n.__esModule?u(t,"default",{value:n,enumerable:!0}):t,n)),P=n=>b(u({},"__esModule",{value:!0}),n);var k=g((N,w)=>{w.exports=JBrowseExports["@jbrowse/core/Plugin"]});var j=g((G,h)=>{h.exports=JBrowseExports["@jbrowse/core/util"]});var x=g((L,y)=>{y.exports=JBrowseExports["mobx-state-tree"]});var _={};T(_,{default:()=>l});var A=m(k()),p=m(j()),i=m(x());function d(n){return Array.isArray(n)?n:[]}function R(n,e,t){let o=new Map;for(let s of[...n,...e])o.set(s[t],s);return[...o.values()]}function C(n,e){let{sessionTracks:t,sessionAssemblies:o,...s}=e;return{...n,tracks:R(d(n.tracks),d(t),"trackId"),assemblies:R(d(n.assemblies),d(o),"name"),defaultSession:s}}var M="1.0.9";function U(n){let{jbrowse:e}=(0,i.getRoot)(n),t=C((0,i.getSnapshot)(e),(0,i.getSnapshot)(n)),o=document.createElement("a");o.href=URL.createObjectURL(new Blob([JSON.stringify(t,null,2)],{type:"application/json"})),o.download="session.jbrowse",o.click(),URL.revokeObjectURL(o.href)}function B(n){let[e,t]=n.split("_");if(!t)return;let o=t.match(/.{1,3}/g);if(!o||o.length<3)return;let[s,r,c]=o;return`https://jbrowse.org/hubs/genark/${e}/${s}/${r}/${c}/${n}/config.json`}function O(n){return n.startsWith("GCA_")||n.startsWith("GCF_")?B(n):`https://jbrowse.org/ucsc/${n}/config.json`}var l=class extends A.default{name="HubsViewerPlugin";version=M;install(e){e.addToExtensionPoint("Core-handleUnrecognizedAssembly",(t,o)=>{let s=o.session,r=o.assemblyName;if(!s||!r)return;let c=O(r);if(!c)return;let f=`jb2hub-${r}`;if(!s.connections.find(a=>a.connectionId===f)){let a={type:"JB2TrackHubConnection",uri:c,name:`conn_${r}`,assemblyNames:[r],connectionId:f};s.addConnectionConf(a),s.makeConnection(a)}})}configure(e){!p.isElectron&&(0,p.isAbstractMenuManager)(e.rootModel)&&e.rootModel.appendToMenu("File",{label:"Download desktop session (.jbrowse)",onClick:t=>{U(t)}})}};return P(_);})();
2
2
  //# sourceMappingURL=jbrowse-plugin-hubs.umd.production.min.js.map
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
- "sources": ["global-externals:@jbrowse/core/Plugin", "../src/index.ts", "../package.json"],
4
- "sourcesContent": ["module.exports = JBrowseExports[\"@jbrowse/core/Plugin\"];", "import Plugin from '@jbrowse/core/Plugin'\nimport PluginManager from '@jbrowse/core/PluginManager'\n\nimport { version } from '../package.json'\n\ninterface Session {\n makeConnection: (conf: unknown) => void\n addConnectionConf: (conf: unknown) => void\n connections: { connectionId: string }[]\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}\n", "{\n \"version\": \"1.0.6\",\n \"license\": \"MIT\",\n \"name\": \"@cmdcolin/jbrowse-plugin-hubs\",\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\": \"prettier --write .\",\n \"prebuild\": \"yarn clean\",\n \"build\": \"tsc && NODE_ENV=production node esbuild.mjs\",\n \"lint\": \"eslint --report-unused-disable-directives --max-warnings 0\",\n \"prepack\": \"yarn build\",\n \"postversion\": \"git push --follow-tags\"\n },\n \"devDependencies\": {\n \"@eslint/js\": \"^9.39.2\",\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 \"esbuild\": \"^0.27.2\",\n \"eslint\": \"^9.39.2\",\n \"eslint-plugin-import\": \"^2.31.0\",\n \"eslint-plugin-unicorn\": \"^62.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.54.0\"\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,EAAA,GAAAC,EAAAD,EAAA,aAAAE,IAAA,IAAAC,EAAmB,OCCjB,IAAAC,EAAW,QDUb,SAASC,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,CAEA,IAAqBC,EAArB,cAA8C,EAAAC,OAAO,CACnD,KAAO,mBACP,QAAUC,EAEV,QAAQC,EAA8B,CACpCA,EAAc,oBACZ,kCACA,CAACC,EAAgBC,IAAS,CACxB,IAAMC,EAAUD,EAAK,QACfN,EAAeM,EAAK,aAC1B,GAAI,CAACC,GAAW,CAACP,EACf,OAEF,IAAMQ,EAAMT,EAAaC,CAAY,EACrC,GAAI,CAACQ,EACH,OAEF,IAAMC,EAAe,UAAUT,CAAY,GAC3C,GAAI,CAACO,EAAQ,YAAY,KAAKG,GAAKA,EAAE,eAAiBD,CAAY,EAAG,CACnE,IAAME,EAAO,CACX,KAAM,wBACN,IAAAH,EACA,KAAM,QAAQR,CAAY,GAC1B,cAAe,CAACA,CAAY,EAC5B,aAAAS,CACF,EACAF,EAAQ,kBAAkBI,CAAI,EAC9BJ,EAAQ,eAAeI,CAAI,CAC7B,CACF,CACF,CACF,CAEA,UAAUC,EAA+B,CAAC,CAC5C",
6
- "names": ["require_Plugin", "__commonJSMin", "exports", "module", "index_exports", "__export", "HubsViewerPlugin", "import_Plugin", "version", "getGenArkConfigUrl", "accession", "base", "rest", "match", "b1", "b2", "b3", "getConfigUrl", "assemblyName", "HubsViewerPlugin", "Plugin", "version", "pluginManager", "_defaultResult", "args", "session", "uri", "connectionId", "f", "conf", "_pluginManager"]
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": "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",
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", "anchor", "getGenArkConfigUrl", "accession", "base", "rest", "match", "b1", "b2", "b3", "getConfigUrl", "assemblyName", "HubsViewerPlugin", "Plugin", "version", "pluginManager", "_defaultResult", "args", "uri", "connectionId", "f", "conf"]
7
7
  }
package/dist/util.d.ts CHANGED
@@ -1 +1,2 @@
1
1
  export declare function textfetch(url: string, arg?: RequestInit): Promise<string>;
2
+ export declare function toDesktopSnapshot(jbrowse: Record<string, unknown>, session: Record<string, unknown>): Record<string, unknown>;
package/dist/util.js CHANGED
@@ -5,4 +5,31 @@ export async function textfetch(url, arg) {
5
5
  }
6
6
  return res.text();
7
7
  }
8
+ function asObjArray(value) {
9
+ return Array.isArray(value) ? value : [];
10
+ }
11
+ // Merges two lists of config objects by an id key, with `overrides` winning so
12
+ // a session-scoped track/assembly that shadows a config one (same id) replaces
13
+ // it rather than duplicating.
14
+ function mergeById(base, overrides, key) {
15
+ const merged = new Map();
16
+ for (const item of [...base, ...overrides]) {
17
+ merged.set(item[key], item);
18
+ }
19
+ return [...merged.values()];
20
+ }
21
+ // Builds a desktop `.jbrowse` snapshot (a config plus a defaultSession) from a
22
+ // web root config and its live session. Session-scoped tracks/assemblies are
23
+ // hoisted into the config arrays so they open as first-class config entries in
24
+ // desktop; the rest of the session becomes the defaultSession. Web sessions are
25
+ // always UriLocation-based, so the result is fully portable to desktop.
26
+ export function toDesktopSnapshot(jbrowse, session) {
27
+ const { sessionTracks, sessionAssemblies, ...defaultSession } = session;
28
+ return {
29
+ ...jbrowse,
30
+ tracks: mergeById(asObjArray(jbrowse.tracks), asObjArray(sessionTracks), 'trackId'),
31
+ assemblies: mergeById(asObjArray(jbrowse.assemblies), asObjArray(sessionAssemblies), 'name'),
32
+ defaultSession,
33
+ };
34
+ }
8
35
  //# sourceMappingURL=util.js.map
package/dist/util.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"util.js","sourceRoot":"","sources":["../src/util.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,KAAK,UAAU,SAAS,CAAC,GAAW,EAAE,GAAiB;IAC5D,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,GAAG,EAAE,GAAG,CAAC,CAAA;IACjC,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC;QACZ,MAAM,IAAI,KAAK,CAAC,QAAQ,GAAG,CAAC,MAAM,SAAS,GAAG,EAAE,CAAC,CAAA;IACnD,CAAC;IACD,OAAO,GAAG,CAAC,IAAI,EAAE,CAAA;AACnB,CAAC"}
1
+ {"version":3,"file":"util.js","sourceRoot":"","sources":["../src/util.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,KAAK,UAAU,SAAS,CAAC,GAAW,EAAE,GAAiB;IAC5D,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,GAAG,EAAE,GAAG,CAAC,CAAA;IACjC,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC;QACZ,MAAM,IAAI,KAAK,CAAC,QAAQ,GAAG,CAAC,MAAM,SAAS,GAAG,EAAE,CAAC,CAAA;IACnD,CAAC;IACD,OAAO,GAAG,CAAC,IAAI,EAAE,CAAA;AACnB,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,7 +1,11 @@
1
1
  {
2
- "version": "1.0.6",
2
+ "version": "1.0.9",
3
3
  "license": "MIT",
4
4
  "name": "@cmdcolin/jbrowse-plugin-hubs",
5
+ "repository": {
6
+ "type": "git",
7
+ "url": "git+https://github.com/GMOD/jbrowse-plugin-hubs.git"
8
+ },
5
9
  "keywords": [
6
10
  "jbrowse",
7
11
  "jbrowse2"
@@ -17,15 +21,16 @@
17
21
  "clean": "rimraf dist",
18
22
  "start": "node esbuild.mjs --watch",
19
23
  "test": "echo 'no tests'",
20
- "format": "prettier --write .",
21
- "prebuild": "yarn clean",
24
+ "format": "pnpm prettier --write .",
25
+ "prebuild": "pnpm clean",
22
26
  "build": "tsc && NODE_ENV=production node esbuild.mjs",
23
- "lint": "eslint --report-unused-disable-directives --max-warnings 0",
24
- "prepack": "yarn build",
25
- "postversion": "git push --follow-tags"
27
+ "lint": "eslint src --report-unused-disable-directives --max-warnings 0",
28
+ "prepack": "pnpm build",
29
+ "postversion": "git push --follow-tags",
30
+ "preversion": "pnpm lint"
26
31
  },
27
32
  "devDependencies": {
28
- "@eslint/js": "^9.39.2",
33
+ "@eslint/js": "^10.0.1",
29
34
  "@fal-works/esbuild-plugin-global-externals": "^2.1.2",
30
35
  "@jbrowse/core": "^4.1.3",
31
36
  "@jbrowse/mobx-state-tree": "^5.0.2",
@@ -34,10 +39,14 @@
34
39
  "@mui/x-data-grid": "^8.26.0",
35
40
  "@types/node": "^25.1.0",
36
41
  "@types/react": "^19.2.10",
42
+ "@typescript-eslint/eslint-plugin": "^8.59.1",
43
+ "@typescript-eslint/parser": "^8.59.1",
37
44
  "esbuild": "^0.27.2",
38
- "eslint": "^9.39.2",
39
- "eslint-plugin-import": "^2.31.0",
40
- "eslint-plugin-unicorn": "^62.0.0",
45
+ "eslint": "^10.2.1",
46
+ "eslint-plugin-import-x": "^4.16.2",
47
+ "eslint-plugin-react": "^7.37.5",
48
+ "eslint-plugin-react-hooks": "^7.1.1",
49
+ "eslint-plugin-unicorn": "^64.0.0",
41
50
  "mobx": "^6.0.0",
42
51
  "mobx-react": "^9.0.1",
43
52
  "prettier": "^3.8.1",
@@ -48,7 +57,7 @@
48
57
  "rxjs": "^7.8.1",
49
58
  "serve": "^14.2.0",
50
59
  "typescript": "^5.1.6",
51
- "typescript-eslint": "^8.54.0"
60
+ "typescript-eslint": "^8.59.1"
52
61
  },
53
62
  "publishConfig": {
54
63
  "access": "public"
package/src/index.ts CHANGED
@@ -1,14 +1,38 @@
1
1
  import Plugin from '@jbrowse/core/Plugin'
2
- import PluginManager from '@jbrowse/core/PluginManager'
2
+ import { isAbstractMenuManager, isElectron } from '@jbrowse/core/util'
3
+ import { getRoot, getSnapshot } from '@jbrowse/mobx-state-tree'
3
4
 
5
+ import { toDesktopSnapshot } from './util'
4
6
  import { version } from '../package.json'
5
7
 
8
+ import type PluginManager from '@jbrowse/core/PluginManager'
9
+ import type { AbstractSessionModel } from '@jbrowse/core/util'
10
+ import type { IAnyStateTreeNode } from '@jbrowse/mobx-state-tree'
11
+
6
12
  interface Session {
7
13
  makeConnection: (conf: unknown) => void
8
14
  addConnectionConf: (conf: unknown) => void
9
15
  connections: { connectionId: string }[]
10
16
  }
11
17
 
18
+ // Serializes the current web session as a desktop `.jbrowse` file. genomes
19
+ // sessions are config-shaped already, so this just folds the live session into
20
+ // a defaultSession and downloads it; desktop opens it via File > Open session.
21
+ function downloadDesktopSession(session: AbstractSessionModel) {
22
+ const { jbrowse } = getRoot<{ jbrowse: IAnyStateTreeNode }>(session)
23
+ const snap = toDesktopSnapshot(
24
+ getSnapshot<Record<string, unknown>>(jbrowse),
25
+ getSnapshot<Record<string, unknown>>(session),
26
+ )
27
+ const anchor = document.createElement('a')
28
+ anchor.href = URL.createObjectURL(
29
+ new Blob([JSON.stringify(snap, null, 2)], { type: 'application/json' }),
30
+ )
31
+ anchor.download = 'session.jbrowse'
32
+ anchor.click()
33
+ URL.revokeObjectURL(anchor.href)
34
+ }
35
+
12
36
  function getGenArkConfigUrl(accession: string) {
13
37
  const [base, rest] = accession.split('_')
14
38
  if (!rest) {
@@ -62,5 +86,17 @@ export default class HubsViewerPlugin extends Plugin {
62
86
  )
63
87
  }
64
88
 
65
- configure(_pluginManager: PluginManager) {}
89
+ configure(pluginManager: PluginManager) {
90
+ // jbrowse-web only. In desktop you'd just save the session, and an exported
91
+ // .jbrowse file carries this plugin in its plugins list, so an older desktop
92
+ // would otherwise load and run this menu code when opening that file.
93
+ if (!isElectron && isAbstractMenuManager(pluginManager.rootModel)) {
94
+ pluginManager.rootModel.appendToMenu('File', {
95
+ label: 'Download desktop session (.jbrowse)',
96
+ onClick: (session: AbstractSessionModel) => {
97
+ downloadDesktopSession(session)
98
+ },
99
+ })
100
+ }
101
+ }
66
102
  }
package/src/util.ts CHANGED
@@ -5,3 +5,48 @@ export async function textfetch(url: string, arg?: RequestInit) {
5
5
  }
6
6
  return res.text()
7
7
  }
8
+
9
+ function asObjArray(value: unknown): Record<string, unknown>[] {
10
+ return Array.isArray(value) ? (value as Record<string, unknown>[]) : []
11
+ }
12
+
13
+ // Merges two lists of config objects by an id key, with `overrides` winning so
14
+ // a session-scoped track/assembly that shadows a config one (same id) replaces
15
+ // it rather than duplicating.
16
+ function mergeById(
17
+ base: Record<string, unknown>[],
18
+ overrides: Record<string, unknown>[],
19
+ key: string,
20
+ ): Record<string, unknown>[] {
21
+ const merged = new Map<unknown, Record<string, unknown>>()
22
+ for (const item of [...base, ...overrides]) {
23
+ merged.set(item[key], item)
24
+ }
25
+ return [...merged.values()]
26
+ }
27
+
28
+ // Builds a desktop `.jbrowse` snapshot (a config plus a defaultSession) from a
29
+ // web root config and its live session. Session-scoped tracks/assemblies are
30
+ // hoisted into the config arrays so they open as first-class config entries in
31
+ // desktop; the rest of the session becomes the defaultSession. Web sessions are
32
+ // always UriLocation-based, so the result is fully portable to desktop.
33
+ export function toDesktopSnapshot(
34
+ jbrowse: Record<string, unknown>,
35
+ session: Record<string, unknown>,
36
+ ): Record<string, unknown> {
37
+ const { sessionTracks, sessionAssemblies, ...defaultSession } = session
38
+ return {
39
+ ...jbrowse,
40
+ tracks: mergeById(
41
+ asObjArray(jbrowse.tracks),
42
+ asObjArray(sessionTracks),
43
+ 'trackId',
44
+ ),
45
+ assemblies: mergeById(
46
+ asObjArray(jbrowse.assemblies),
47
+ asObjArray(sessionAssemblies),
48
+ 'name',
49
+ ),
50
+ defaultSession,
51
+ }
52
+ }