diff --git a/exampleData/x.gbz.db b/exampleData/x.gbz.db new file mode 100644 index 00000000..6542087e Binary files /dev/null and b/exampleData/x.gbz.db differ diff --git a/package-lock.json b/package-lock.json index 2ffa6061..8d2aab2e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7,6 +7,7 @@ "": { "name": "sequence-tube-maps", "version": "0.1.0", + "hasInstallScript": true, "license": "MIT", "dependencies": { "@bjorn3/browser_wasi_shim": "^0.2.17", @@ -34,7 +35,7 @@ "es-dirname": "^0.1.0", "express": "^4.18.2", "fs-extra": "^10.1.0", - "gbz-base": "^0.1.0-alpha.0", + "gbz-base": "^0.1.0-alpha.1", "gh-pages": "^4.0.0", "markdown-to-jsx": "^7.2.0", "multer": "^1.4.5-lts.1", @@ -61,7 +62,8 @@ "uuid": "^9.0.0", "webpack": "^5.82.0", "webpack-dev-server": "4.11.1", - "websocket": "^1.0.34" + "websocket": "^1.0.34", + "worker-rpc": "^0.2.0" }, "devDependencies": { "jest-compact-reporter": "^1.2.9", @@ -9906,9 +9908,9 @@ } }, "node_modules/gbz-base": { - "version": "0.1.0-alpha.0", - "resolved": "https://registry.npmjs.org/gbz-base/-/gbz-base-0.1.0-alpha.0.tgz", - "integrity": "sha512-OkIQeQpRH5K5uiNp/4+0eMP5eug/bdsABwVGSHHQLY+YW+I+uPxkMvF2gNx/FBcHKtDZxATZDz6zFDXM2LGT0A==" + "version": "0.1.0-alpha.1", + "resolved": "https://registry.npmjs.org/gbz-base/-/gbz-base-0.1.0-alpha.1.tgz", + "integrity": "sha512-ULedBstK4T9geErTMrkLfeMf/rtMYZ1SabIx+5GAcdq2gZDxFrtzrodtcsP21pqDiNj0VOy/ODo2mZtWlVSwGg==" }, "node_modules/gensync": { "version": "1.0.0-beta.2", @@ -13720,6 +13722,11 @@ "node": ">= 0.6" } }, + "node_modules/microevent.ts": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/microevent.ts/-/microevent.ts-0.2.1.tgz", + "integrity": "sha512-YaOQr4V70QzTy3sTRkBUa7+clmN4rMdKs9L5wCCxYjo8gknO/FXhcEX5Pot4IWtAdiZqhxN7vskoywQbAOAkDQ==" + }, "node_modules/micromatch": { "version": "4.0.5", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", @@ -19716,6 +19723,14 @@ "workbox-core": "6.5.4" } }, + "node_modules/worker-rpc": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/worker-rpc/-/worker-rpc-0.2.0.tgz", + "integrity": "sha512-S74HnfAdmMlUYmr6+Lx6TmxvffM2vRZSk4RfI/Bxco4xZGw+FREzLRZhFxf8QIzI2/5NKNMn5+Pj69Bp+rweIg==", + "dependencies": { + "microevent.ts": "~0.2.1" + } + }, "node_modules/wrap-ansi": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", @@ -26963,9 +26978,9 @@ "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==" }, "gbz-base": { - "version": "0.1.0-alpha.0", - "resolved": "https://registry.npmjs.org/gbz-base/-/gbz-base-0.1.0-alpha.0.tgz", - "integrity": "sha512-OkIQeQpRH5K5uiNp/4+0eMP5eug/bdsABwVGSHHQLY+YW+I+uPxkMvF2gNx/FBcHKtDZxATZDz6zFDXM2LGT0A==" + "version": "0.1.0-alpha.1", + "resolved": "https://registry.npmjs.org/gbz-base/-/gbz-base-0.1.0-alpha.1.tgz", + "integrity": "sha512-ULedBstK4T9geErTMrkLfeMf/rtMYZ1SabIx+5GAcdq2gZDxFrtzrodtcsP21pqDiNj0VOy/ODo2mZtWlVSwGg==" }, "gensync": { "version": "1.0.0-beta.2", @@ -29915,6 +29930,11 @@ "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==" }, + "microevent.ts": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/microevent.ts/-/microevent.ts-0.2.1.tgz", + "integrity": "sha512-YaOQr4V70QzTy3sTRkBUa7+clmN4rMdKs9L5wCCxYjo8gknO/FXhcEX5Pot4IWtAdiZqhxN7vskoywQbAOAkDQ==" + }, "micromatch": { "version": "4.0.5", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", @@ -34162,6 +34182,14 @@ "workbox-core": "6.5.4" } }, + "worker-rpc": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/worker-rpc/-/worker-rpc-0.2.0.tgz", + "integrity": "sha512-S74HnfAdmMlUYmr6+Lx6TmxvffM2vRZSk4RfI/Bxco4xZGw+FREzLRZhFxf8QIzI2/5NKNMn5+Pj69Bp+rweIg==", + "requires": { + "microevent.ts": "~0.2.1" + } + }, "wrap-ansi": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", diff --git a/package.json b/package.json index 9754631b..7cdc60f6 100644 --- a/package.json +++ b/package.json @@ -29,7 +29,7 @@ "es-dirname": "^0.1.0", "express": "^4.18.2", "fs-extra": "^10.1.0", - "gbz-base": "^0.1.0-alpha.0", + "gbz-base": "^0.1.0-alpha.1", "gh-pages": "^4.0.0", "markdown-to-jsx": "^7.2.0", "multer": "^1.4.5-lts.1", @@ -56,7 +56,8 @@ "uuid": "^9.0.0", "webpack": "^5.82.0", "webpack-dev-server": "4.11.1", - "websocket": "^1.0.34" + "websocket": "^1.0.34", + "worker-rpc": "^0.2.0" }, "scripts": { "start": "concurrently -n frontend,backend -c red,green 'HOST=${HOST:=127.0.0.1} PORT=${PORT:=3001} react-scripts start' 'npm:serve'", diff --git a/src/App.js b/src/App.js index b92cc3ce..43f7c821 100644 --- a/src/App.js +++ b/src/App.js @@ -14,8 +14,8 @@ import Footer from "./components/Footer"; import { dataOriginTypes } from "./enums"; import "./config-client.js"; import { config } from "./config-global.mjs"; -import ServerAPI from "./ServerAPI.mjs"; -import { GBZBaseAPI } from "./GBZBaseAPI.mjs"; +import ServerAPI from "./api/ServerAPI.mjs"; +import { LocalAPI } from "./api/LocalAPI.mjs"; const EXAMPLE_TRACKS = [ // Fake tracks for the generated examples. @@ -47,19 +47,6 @@ class App extends Component { constructor(props) { super(props); - // See if the WASM API is available. - // Right now this just tests and logs, but eventually we will be able to use it. - let gbzApi = new GBZBaseAPI(); - gbzApi.available().then((working) => { - if (working) { - console.log("WASM API implementation available!"); - } else { - console.error("WASM API implementation not available!"); - } - }); - - this.APIInterface = new ServerAPI(props.apiUrl); - console.log("App component starting up with API URL: " + props.apiUrl); // Set defaultViewTarget to either URL params (if present) or the first example @@ -83,8 +70,68 @@ class App extends Component { colorSchemes: getColorSchemesFromTracks(this.defaultViewTarget.tracks), mappingQualityCutoff: 0, }, + APIInterface: new ServerAPI(props.apiUrl) }; } + + /** + * Set which API implementation to query for graph data. + * + * Mode can be "local" or "server". + */ + setAPIMode(mode) { + this.setState((state) => { + if (mode !== this.getAPIMode(state)) { + if (mode === "local") { + // Make a local API + return { + APIInterface: new LocalAPI(), + // Set up an empty view target that can't really render. + // TODO: Let us control HeaderForm's dataType state so we can pop it right over to custom, or feed it a different defaultViewTarget + dataOrigin: dataOriginTypes.API, + viewTarget: { + tracks: [] + }, + visOptions: { + ...state.visOptions, + colorSchemes: [], + }, + }; + } else if (mode === "server") { + // Make a server API + return { + APIInterface: new ServerAPI(this.props.apiUrl), + // Also reset to a current view target this can show + dataOrigin: dataOriginTypes.API, + viewTarget: this.defaultViewTarget, + visOptions: { + ...state.visOptions, + colorSchemes: getColorSchemesFromTracks(this.defaultViewTarget.tracks), + }, + }; + } else { + throw new Error("Unimplemented API mode: " + mode) + } + } + }); + } + + /** + * Get the string describing the current API mode ("local" or "server"), + * given the state (by default the current state). + */ + getAPIMode(state) { + if (state === undefined) { + state = this.state; + } + if (state.APIInterface instanceof LocalAPI) { + return "local"; + } else if (state.APIInterface instanceof ServerAPI) { + return "server"; + } else { + throw new Error("Unnamed API implementation: " + state.APIInterface); + } + } /* * Drop undefined values @@ -197,13 +244,13 @@ class App extends Component { dataOrigin={this.state.dataOrigin} defaultViewTarget={this.defaultViewTarget} getCurrentViewTarget={this.getCurrentViewTarget} - APIInterface={this.APIInterface} + APIInterface={this.state.APIInterface} />