diff --git a/package-lock.json b/package-lock.json
index a0a46311..2ffa6061 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -9,6 +9,7 @@
"version": "0.1.0",
"license": "MIT",
"dependencies": {
+ "@bjorn3/browser_wasi_shim": "^0.2.17",
"@emotion/react": "^11.11.0",
"@emotion/styled": "^11.11.0",
"@fortawesome/fontawesome-svg-core": "^6.4.0",
@@ -33,10 +34,12 @@
"es-dirname": "^0.1.0",
"express": "^4.18.2",
"fs-extra": "^10.1.0",
+ "gbz-base": "^0.1.0-alpha.0",
"gh-pages": "^4.0.0",
"markdown-to-jsx": "^7.2.0",
"multer": "^1.4.5-lts.1",
"node-cron": "^3.0.2",
+ "patch-package": "^8.0.0",
"path-is-inside": "^1.0.2",
"polyfill-object.fromentries": "^1.0.1",
"prop-types": "^15.8.1",
@@ -1983,6 +1986,11 @@
"resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz",
"integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw=="
},
+ "node_modules/@bjorn3/browser_wasi_shim": {
+ "version": "0.2.17",
+ "resolved": "https://registry.npmjs.org/@bjorn3/browser_wasi_shim/-/browser_wasi_shim-0.2.17.tgz",
+ "integrity": "sha512-B2qcaGROo4e2s4nXb3VPATrczVrntM4BUXtAU1gEzUOfqKTcVuePq4NfhH5hmLBSvZ45YcT4gflDRUFYqLhkxA=="
+ },
"node_modules/@csstools/normalize.css": {
"version": "12.0.0",
"resolved": "https://registry.npmjs.org/@csstools/normalize.css/-/normalize.css-12.0.0.tgz",
@@ -5445,6 +5453,11 @@
"resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz",
"integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ=="
},
+ "node_modules/@yarnpkg/lockfile": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@yarnpkg/lockfile/-/lockfile-1.1.0.tgz",
+ "integrity": "sha512-GpSwvyXOcOOlV70vbnzjj4fW5xW/FdUF6nQEt1ENy7m4ZCczi1+/buVUPAqmGfqznsORNFzUMjctTIp8a9tuCQ=="
+ },
"node_modules/abab": {
"version": "2.0.6",
"resolved": "https://registry.npmjs.org/abab/-/abab-2.0.6.tgz",
@@ -6392,12 +6405,13 @@
}
},
"node_modules/call-bind": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz",
- "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==",
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.5.tgz",
+ "integrity": "sha512-C3nQxfFZxFRVoJoGKKI8y3MOEo129NQ+FgQ08iye+Mk4zNZZGdjfs06bVTr+DBSlA66Q2VEcMki/cUCP4SercQ==",
"dependencies": {
- "function-bind": "^1.1.1",
- "get-intrinsic": "^1.0.2"
+ "function-bind": "^1.1.2",
+ "get-intrinsic": "^1.2.1",
+ "set-function-length": "^1.1.1"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
@@ -7892,6 +7906,19 @@
"node": ">= 10"
}
},
+ "node_modules/define-data-property": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.1.tgz",
+ "integrity": "sha512-E7uGkTzkk1d0ByLeSc6ZsFS79Axg+m1P/VsgYsxHgiuc3tFSj+MjMIwe90FC4lOAZzNBdY7kkO2P2wKdsQ1vgQ==",
+ "dependencies": {
+ "get-intrinsic": "^1.2.1",
+ "gopd": "^1.0.1",
+ "has-property-descriptors": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
"node_modules/define-lazy-prop": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz",
@@ -9594,6 +9621,14 @@
"node": ">=8"
}
},
+ "node_modules/find-yarn-workspace-root": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/find-yarn-workspace-root/-/find-yarn-workspace-root-2.0.0.tgz",
+ "integrity": "sha512-1IMnbjt4KzsQfnhnzNd8wUEgXZ44IzZaZmnLYx7D5FZlaHt2gW20Cri8Q+E/t5tIj4+epTBub+2Zxu/vNILzqQ==",
+ "dependencies": {
+ "micromatch": "^4.0.2"
+ }
+ },
"node_modules/flat-cache": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz",
@@ -9838,9 +9873,12 @@
}
},
"node_modules/function-bind": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
- "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A=="
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
+ "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
},
"node_modules/function.prototype.name": {
"version": "1.1.5",
@@ -9867,6 +9905,11 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "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=="
+ },
"node_modules/gensync": {
"version": "1.0.0-beta.2",
"resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz",
@@ -9884,13 +9927,14 @@
}
},
"node_modules/get-intrinsic": {
- "version": "1.2.0",
- "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.0.tgz",
- "integrity": "sha512-L049y6nFOuom5wGyRc3/gdTLO94dySVKRACj1RmJZBQXlbTMhtNIgkWkUHq+jYmZvKf14EW1EoJnnjbmoHij0Q==",
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.2.tgz",
+ "integrity": "sha512-0gSo4ml/0j98Y3lngkFEot/zhiCeWsbYIlZ+uZOVgzLyLaUw7wxUL+nCTP0XJvJg1AXulJRI3UJi8GsbDuxdGA==",
"dependencies": {
- "function-bind": "^1.1.1",
- "has": "^1.0.3",
- "has-symbols": "^1.0.3"
+ "function-bind": "^1.1.2",
+ "has-proto": "^1.0.1",
+ "has-symbols": "^1.0.3",
+ "hasown": "^2.0.0"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
@@ -10211,6 +10255,17 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/hasown": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.0.tgz",
+ "integrity": "sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA==",
+ "dependencies": {
+ "function-bind": "^1.1.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
"node_modules/he": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz",
@@ -13230,6 +13285,23 @@
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
"integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="
},
+ "node_modules/json-stable-stringify": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-1.1.0.tgz",
+ "integrity": "sha512-zfA+5SuwYN2VWqN1/5HZaDzQKLJHaBVMZIIM+wuYjdptkaQsqzDdqjqf+lZZJUuJq1aanHiY8LhH8LmH+qBYJA==",
+ "dependencies": {
+ "call-bind": "^1.0.5",
+ "isarray": "^2.0.5",
+ "jsonify": "^0.0.1",
+ "object-keys": "^1.1.1"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
"node_modules/json-stable-stringify-without-jsonify": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz",
@@ -13257,6 +13329,14 @@
"graceful-fs": "^4.1.6"
}
},
+ "node_modules/jsonify": {
+ "version": "0.0.1",
+ "resolved": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.1.tgz",
+ "integrity": "sha512-2/Ki0GcmuqSrgFyelQq9M05y7PS0mEwuIzrf3f1fPqkVDVRvZrPZtVSMHxdgo8Aq0sxAOb/cr2aqqA3LeWHVPg==",
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
"node_modules/jsonpointer": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/jsonpointer/-/jsonpointer-5.0.1.tgz",
@@ -13367,6 +13447,14 @@
"node": ">=0.10.0"
}
},
+ "node_modules/klaw-sync": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/klaw-sync/-/klaw-sync-6.0.0.tgz",
+ "integrity": "sha512-nIeuVSzdCCs6TDPTqI8w1Yre34sSq7AkZ4B3sfOBbI2CgVSB4Du4aLQijFU2+lhAFCwt9+42Hel6lQNIv6AntQ==",
+ "dependencies": {
+ "graceful-fs": "^4.1.11"
+ }
+ },
"node_modules/kleur": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz",
@@ -14228,6 +14316,14 @@
"node": ">= 0.8.0"
}
},
+ "node_modules/os-tmpdir": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz",
+ "integrity": "sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
"node_modules/p-limit": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
@@ -14332,6 +14428,121 @@
"tslib": "^2.0.3"
}
},
+ "node_modules/patch-package": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/patch-package/-/patch-package-8.0.0.tgz",
+ "integrity": "sha512-da8BVIhzjtgScwDJ2TtKsfT5JFWz1hYoBl9rUQ1f38MC2HwnEIkK8VN3dKMKcP7P7bvvgzNDbfNHtx3MsQb5vA==",
+ "dependencies": {
+ "@yarnpkg/lockfile": "^1.1.0",
+ "chalk": "^4.1.2",
+ "ci-info": "^3.7.0",
+ "cross-spawn": "^7.0.3",
+ "find-yarn-workspace-root": "^2.0.0",
+ "fs-extra": "^9.0.0",
+ "json-stable-stringify": "^1.0.2",
+ "klaw-sync": "^6.0.0",
+ "minimist": "^1.2.6",
+ "open": "^7.4.2",
+ "rimraf": "^2.6.3",
+ "semver": "^7.5.3",
+ "slash": "^2.0.0",
+ "tmp": "^0.0.33",
+ "yaml": "^2.2.2"
+ },
+ "bin": {
+ "patch-package": "index.js"
+ },
+ "engines": {
+ "node": ">=14",
+ "npm": ">5"
+ }
+ },
+ "node_modules/patch-package/node_modules/fs-extra": {
+ "version": "9.1.0",
+ "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz",
+ "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==",
+ "dependencies": {
+ "at-least-node": "^1.0.0",
+ "graceful-fs": "^4.2.0",
+ "jsonfile": "^6.0.1",
+ "universalify": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/patch-package/node_modules/lru-cache": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
+ "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
+ "dependencies": {
+ "yallist": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/patch-package/node_modules/open": {
+ "version": "7.4.2",
+ "resolved": "https://registry.npmjs.org/open/-/open-7.4.2.tgz",
+ "integrity": "sha512-MVHddDVweXZF3awtlAS+6pgKLlm/JgxZ90+/NBurBoQctVOOB/zDdVjcyPzQ+0laDGbsWgrRkflI65sQeOgT9Q==",
+ "dependencies": {
+ "is-docker": "^2.0.0",
+ "is-wsl": "^2.1.1"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/patch-package/node_modules/rimraf": {
+ "version": "2.7.1",
+ "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz",
+ "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==",
+ "dependencies": {
+ "glob": "^7.1.3"
+ },
+ "bin": {
+ "rimraf": "bin.js"
+ }
+ },
+ "node_modules/patch-package/node_modules/semver": {
+ "version": "7.5.4",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz",
+ "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==",
+ "dependencies": {
+ "lru-cache": "^6.0.0"
+ },
+ "bin": {
+ "semver": "bin/semver.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/patch-package/node_modules/slash": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz",
+ "integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/patch-package/node_modules/yallist": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
+ "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="
+ },
+ "node_modules/patch-package/node_modules/yaml": {
+ "version": "2.3.4",
+ "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.3.4.tgz",
+ "integrity": "sha512-8aAvwVUSHpfEqTQ4w/KMlf3HcRdt50E5ODIQJBw1fQ5RL34xabzxtUlzTXVqc4rkZsPbvrXKWnABCD7kWSmocA==",
+ "engines": {
+ "node": ">= 14"
+ }
+ },
"node_modules/path-exists": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
@@ -17125,6 +17336,20 @@
"node": ">= 0.8.0"
}
},
+ "node_modules/set-function-length": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.1.1.tgz",
+ "integrity": "sha512-VoaqjbBJKiWtg4yRcKBQ7g7wnGnLV3M8oLvVWwOk2PdYY6PEFegR1vezXR0tw6fZGF9csVakIRjrJiy2veSBFQ==",
+ "dependencies": {
+ "define-data-property": "^1.1.1",
+ "get-intrinsic": "^1.2.1",
+ "gopd": "^1.0.1",
+ "has-property-descriptors": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
"node_modules/setimmediate": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz",
@@ -18157,6 +18382,17 @@
"resolved": "https://registry.npmjs.org/tinycolor2/-/tinycolor2-1.6.0.tgz",
"integrity": "sha512-XPaBkWQJdsf3pLKJV9p4qN/S+fm2Oj8AIPo1BTUhg5oxkvm9+SVEGFdhyOz7tTdUTfvxMiAs4sp6/eZO2Ew+pw=="
},
+ "node_modules/tmp": {
+ "version": "0.0.33",
+ "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz",
+ "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==",
+ "dependencies": {
+ "os-tmpdir": "~1.0.2"
+ },
+ "engines": {
+ "node": ">=0.6.0"
+ }
+ },
"node_modules/tmpl": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz",
@@ -20905,6 +21141,11 @@
"resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz",
"integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw=="
},
+ "@bjorn3/browser_wasi_shim": {
+ "version": "0.2.17",
+ "resolved": "https://registry.npmjs.org/@bjorn3/browser_wasi_shim/-/browser_wasi_shim-0.2.17.tgz",
+ "integrity": "sha512-B2qcaGROo4e2s4nXb3VPATrczVrntM4BUXtAU1gEzUOfqKTcVuePq4NfhH5hmLBSvZ45YcT4gflDRUFYqLhkxA=="
+ },
"@csstools/normalize.css": {
"version": "12.0.0",
"resolved": "https://registry.npmjs.org/@csstools/normalize.css/-/normalize.css-12.0.0.tgz",
@@ -23393,6 +23634,11 @@
"resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz",
"integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ=="
},
+ "@yarnpkg/lockfile": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@yarnpkg/lockfile/-/lockfile-1.1.0.tgz",
+ "integrity": "sha512-GpSwvyXOcOOlV70vbnzjj4fW5xW/FdUF6nQEt1ENy7m4ZCczi1+/buVUPAqmGfqznsORNFzUMjctTIp8a9tuCQ=="
+ },
"abab": {
"version": "2.0.6",
"resolved": "https://registry.npmjs.org/abab/-/abab-2.0.6.tgz",
@@ -24090,12 +24336,13 @@
"integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg=="
},
"call-bind": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz",
- "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==",
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.5.tgz",
+ "integrity": "sha512-C3nQxfFZxFRVoJoGKKI8y3MOEo129NQ+FgQ08iye+Mk4zNZZGdjfs06bVTr+DBSlA66Q2VEcMki/cUCP4SercQ==",
"requires": {
- "function-bind": "^1.1.1",
- "get-intrinsic": "^1.0.2"
+ "function-bind": "^1.1.2",
+ "get-intrinsic": "^1.2.1",
+ "set-function-length": "^1.1.1"
}
},
"callsites": {
@@ -25248,6 +25495,16 @@
"execa": "^5.0.0"
}
},
+ "define-data-property": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.1.tgz",
+ "integrity": "sha512-E7uGkTzkk1d0ByLeSc6ZsFS79Axg+m1P/VsgYsxHgiuc3tFSj+MjMIwe90FC4lOAZzNBdY7kkO2P2wKdsQ1vgQ==",
+ "requires": {
+ "get-intrinsic": "^1.2.1",
+ "gopd": "^1.0.1",
+ "has-property-descriptors": "^1.0.0"
+ }
+ },
"define-lazy-prop": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz",
@@ -26517,6 +26774,14 @@
"path-exists": "^4.0.0"
}
},
+ "find-yarn-workspace-root": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/find-yarn-workspace-root/-/find-yarn-workspace-root-2.0.0.tgz",
+ "integrity": "sha512-1IMnbjt4KzsQfnhnzNd8wUEgXZ44IzZaZmnLYx7D5FZlaHt2gW20Cri8Q+E/t5tIj4+epTBub+2Zxu/vNILzqQ==",
+ "requires": {
+ "micromatch": "^4.0.2"
+ }
+ },
"flat-cache": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz",
@@ -26677,9 +26942,9 @@
"optional": true
},
"function-bind": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
- "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A=="
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
+ "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="
},
"function.prototype.name": {
"version": "1.1.5",
@@ -26697,6 +26962,11 @@
"resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz",
"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=="
+ },
"gensync": {
"version": "1.0.0-beta.2",
"resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz",
@@ -26708,13 +26978,14 @@
"integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg=="
},
"get-intrinsic": {
- "version": "1.2.0",
- "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.0.tgz",
- "integrity": "sha512-L049y6nFOuom5wGyRc3/gdTLO94dySVKRACj1RmJZBQXlbTMhtNIgkWkUHq+jYmZvKf14EW1EoJnnjbmoHij0Q==",
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.2.tgz",
+ "integrity": "sha512-0gSo4ml/0j98Y3lngkFEot/zhiCeWsbYIlZ+uZOVgzLyLaUw7wxUL+nCTP0XJvJg1AXulJRI3UJi8GsbDuxdGA==",
"requires": {
- "function-bind": "^1.1.1",
- "has": "^1.0.3",
- "has-symbols": "^1.0.3"
+ "function-bind": "^1.1.2",
+ "has-proto": "^1.0.1",
+ "has-symbols": "^1.0.3",
+ "hasown": "^2.0.0"
}
},
"get-own-enumerable-property-symbols": {
@@ -26939,6 +27210,14 @@
"has-symbols": "^1.0.2"
}
},
+ "hasown": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.0.tgz",
+ "integrity": "sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA==",
+ "requires": {
+ "function-bind": "^1.1.2"
+ }
+ },
"he": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz",
@@ -29284,6 +29563,17 @@
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
"integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="
},
+ "json-stable-stringify": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-1.1.0.tgz",
+ "integrity": "sha512-zfA+5SuwYN2VWqN1/5HZaDzQKLJHaBVMZIIM+wuYjdptkaQsqzDdqjqf+lZZJUuJq1aanHiY8LhH8LmH+qBYJA==",
+ "requires": {
+ "call-bind": "^1.0.5",
+ "isarray": "^2.0.5",
+ "jsonify": "^0.0.1",
+ "object-keys": "^1.1.1"
+ }
+ },
"json-stable-stringify-without-jsonify": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz",
@@ -29303,6 +29593,11 @@
"universalify": "^2.0.0"
}
},
+ "jsonify": {
+ "version": "0.0.1",
+ "resolved": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.1.tgz",
+ "integrity": "sha512-2/Ki0GcmuqSrgFyelQq9M05y7PS0mEwuIzrf3f1fPqkVDVRvZrPZtVSMHxdgo8Aq0sxAOb/cr2aqqA3LeWHVPg=="
+ },
"jsonpointer": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/jsonpointer/-/jsonpointer-5.0.1.tgz",
@@ -29400,6 +29695,14 @@
"resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz",
"integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw=="
},
+ "klaw-sync": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/klaw-sync/-/klaw-sync-6.0.0.tgz",
+ "integrity": "sha512-nIeuVSzdCCs6TDPTqI8w1Yre34sSq7AkZ4B3sfOBbI2CgVSB4Du4aLQijFU2+lhAFCwt9+42Hel6lQNIv6AntQ==",
+ "requires": {
+ "graceful-fs": "^4.1.11"
+ }
+ },
"kleur": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz",
@@ -30025,6 +30328,11 @@
"word-wrap": "^1.2.3"
}
},
+ "os-tmpdir": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz",
+ "integrity": "sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g=="
+ },
"p-limit": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
@@ -30102,6 +30410,89 @@
"tslib": "^2.0.3"
}
},
+ "patch-package": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/patch-package/-/patch-package-8.0.0.tgz",
+ "integrity": "sha512-da8BVIhzjtgScwDJ2TtKsfT5JFWz1hYoBl9rUQ1f38MC2HwnEIkK8VN3dKMKcP7P7bvvgzNDbfNHtx3MsQb5vA==",
+ "requires": {
+ "@yarnpkg/lockfile": "^1.1.0",
+ "chalk": "^4.1.2",
+ "ci-info": "^3.7.0",
+ "cross-spawn": "^7.0.3",
+ "find-yarn-workspace-root": "^2.0.0",
+ "fs-extra": "^9.0.0",
+ "json-stable-stringify": "^1.0.2",
+ "klaw-sync": "^6.0.0",
+ "minimist": "^1.2.6",
+ "open": "^7.4.2",
+ "rimraf": "^2.6.3",
+ "semver": "^7.5.3",
+ "slash": "^2.0.0",
+ "tmp": "^0.0.33",
+ "yaml": "^2.2.2"
+ },
+ "dependencies": {
+ "fs-extra": {
+ "version": "9.1.0",
+ "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz",
+ "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==",
+ "requires": {
+ "at-least-node": "^1.0.0",
+ "graceful-fs": "^4.2.0",
+ "jsonfile": "^6.0.1",
+ "universalify": "^2.0.0"
+ }
+ },
+ "lru-cache": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
+ "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
+ "requires": {
+ "yallist": "^4.0.0"
+ }
+ },
+ "open": {
+ "version": "7.4.2",
+ "resolved": "https://registry.npmjs.org/open/-/open-7.4.2.tgz",
+ "integrity": "sha512-MVHddDVweXZF3awtlAS+6pgKLlm/JgxZ90+/NBurBoQctVOOB/zDdVjcyPzQ+0laDGbsWgrRkflI65sQeOgT9Q==",
+ "requires": {
+ "is-docker": "^2.0.0",
+ "is-wsl": "^2.1.1"
+ }
+ },
+ "rimraf": {
+ "version": "2.7.1",
+ "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz",
+ "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==",
+ "requires": {
+ "glob": "^7.1.3"
+ }
+ },
+ "semver": {
+ "version": "7.5.4",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz",
+ "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==",
+ "requires": {
+ "lru-cache": "^6.0.0"
+ }
+ },
+ "slash": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz",
+ "integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A=="
+ },
+ "yallist": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
+ "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="
+ },
+ "yaml": {
+ "version": "2.3.4",
+ "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.3.4.tgz",
+ "integrity": "sha512-8aAvwVUSHpfEqTQ4w/KMlf3HcRdt50E5ODIQJBw1fQ5RL34xabzxtUlzTXVqc4rkZsPbvrXKWnABCD7kWSmocA=="
+ }
+ }
+ },
"path-exists": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
@@ -31979,6 +32370,17 @@
"send": "0.18.0"
}
},
+ "set-function-length": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.1.1.tgz",
+ "integrity": "sha512-VoaqjbBJKiWtg4yRcKBQ7g7wnGnLV3M8oLvVWwOk2PdYY6PEFegR1vezXR0tw6fZGF9csVakIRjrJiy2veSBFQ==",
+ "requires": {
+ "define-data-property": "^1.1.1",
+ "get-intrinsic": "^1.2.1",
+ "gopd": "^1.0.1",
+ "has-property-descriptors": "^1.0.0"
+ }
+ },
"setimmediate": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz",
@@ -32736,6 +33138,14 @@
"resolved": "https://registry.npmjs.org/tinycolor2/-/tinycolor2-1.6.0.tgz",
"integrity": "sha512-XPaBkWQJdsf3pLKJV9p4qN/S+fm2Oj8AIPo1BTUhg5oxkvm9+SVEGFdhyOz7tTdUTfvxMiAs4sp6/eZO2Ew+pw=="
},
+ "tmp": {
+ "version": "0.0.33",
+ "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz",
+ "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==",
+ "requires": {
+ "os-tmpdir": "~1.0.2"
+ }
+ },
"tmpl": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz",
diff --git a/package.json b/package.json
index 2a3c5349..9754631b 100644
--- a/package.json
+++ b/package.json
@@ -4,6 +4,7 @@
"author": "Wolfgang Beyer",
"license": "MIT",
"dependencies": {
+ "@bjorn3/browser_wasi_shim": "^0.2.17",
"@emotion/react": "^11.11.0",
"@emotion/styled": "^11.11.0",
"@fortawesome/fontawesome-svg-core": "^6.4.0",
@@ -28,10 +29,12 @@
"es-dirname": "^0.1.0",
"express": "^4.18.2",
"fs-extra": "^10.1.0",
+ "gbz-base": "^0.1.0-alpha.0",
"gh-pages": "^4.0.0",
"markdown-to-jsx": "^7.2.0",
"multer": "^1.4.5-lts.1",
"node-cron": "^3.0.2",
+ "patch-package": "^8.0.0",
"path-is-inside": "^1.0.2",
"polyfill-object.fromentries": "^1.0.1",
"prop-types": "^15.8.1",
@@ -63,7 +66,8 @@
"predeploy": "npm run build",
"deploy": "gh-pages -d build",
"serve": "node ./src/server.mjs",
- "format": "prettier --write \"**/*.+(mjs|js|css)\""
+ "format": "prettier --write \"**/*.+(mjs|js|css)\"",
+ "postinstall": "patch-package"
},
"eslintConfig": {
"extends": "react-app"
@@ -86,7 +90,7 @@
"jest": {
"resetMocks": false,
"transformIgnorePatterns": [
- "node_modules/(?!(@streamparser/json)/)"
+ "node_modules/(?!(@streamparser/json|@bjorn3/browser_wasi_shim)/)"
]
}
}
diff --git a/patches/@bjorn3+browser_wasi_shim+0.2.17.patch b/patches/@bjorn3+browser_wasi_shim+0.2.17.patch
new file mode 100644
index 00000000..5d5edbea
--- /dev/null
+++ b/patches/@bjorn3+browser_wasi_shim+0.2.17.patch
@@ -0,0 +1,14 @@
+diff --git a/node_modules/@bjorn3/browser_wasi_shim/package.json b/node_modules/@bjorn3/browser_wasi_shim/package.json
+index af9de55..2e7a121 100644
+--- a/node_modules/@bjorn3/browser_wasi_shim/package.json
++++ b/node_modules/@bjorn3/browser_wasi_shim/package.json
+@@ -21,7 +21,8 @@
+ "exports": {
+ ".": {
+ "types": "./typings/index.d.ts",
+- "import": "./dist/index.js"
++ "import": "./dist/index.js",
++ "default": "./dist/index.js"
+ }
+ },
+ "typings": "./typings/index.d.ts",
diff --git a/src/APIInterface.mjs b/src/APIInterface.mjs
index 29c2e7f6..e0a44d43 100644
--- a/src/APIInterface.mjs
+++ b/src/APIInterface.mjs
@@ -12,11 +12,29 @@ export class APIInterface {
// Returns files used to determine what options are available in the track picker.
// Returns object with keys: files, bedFiles.
+ // files holds an array of objects like { name: string; type: filetype;}, where filetype is a file type like "graph".
+ // bedFiles just holds an array of strings.
// cancelSignal is an AbortSignal that can be used to cancel the request.
async getFilenames(cancelSignal) {
throw new Error("getFilenames function not implemented");
}
+ // Get notifications (via calls to handler()) when the set of filenames available from getFilenames() has changed.
+ // Returns a subscription object that should be kept around as long as you still want updates.
+ // cancelSignal is an AbortSignal that can be used to cancel the stream of notifications.
+ subscribeToFilenameChanges(handler, cancelSignal) {
+ throw new Error("subscribeToFilenameChanges function not implemented");
+ }
+
+ // Upload a file.
+ // fileType is a track type like "graph" or "read".
+ // file is the file data (Blob or File).
+ // cancelSignal is an AbortSignal that can be used to cancel the upload.
+ // Resolves with the file name that can be used to refer to the uploaded file.
+ async putFile(fileType, file, cancelSignal) {
+ throw new Error("putFile function not implemented");
+ }
+
// Takes in a bedfile path or a url pointing to a raw bed file.
// Returns object with key: bedRegions.
// bedRegions contains information extrapolated from each line of the bedfile.
diff --git a/src/App.js b/src/App.js
index 2ebe9404..b92cc3ce 100644
--- a/src/App.js
+++ b/src/App.js
@@ -15,6 +15,7 @@ import { dataOriginTypes } from "./enums";
import "./config-client.js";
import { config } from "./config-global.mjs";
import ServerAPI from "./ServerAPI.mjs";
+import { GBZBaseAPI } from "./GBZBaseAPI.mjs";
const EXAMPLE_TRACKS = [
// Fake tracks for the generated examples.
@@ -46,6 +47,17 @@ 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);
@@ -183,7 +195,6 @@ class App extends Component {
setDataOrigin={this.setDataOrigin}
setColorSetting={this.setColorSetting}
dataOrigin={this.state.dataOrigin}
- apiUrl={this.props.apiUrl}
defaultViewTarget={this.defaultViewTarget}
getCurrentViewTarget={this.getCurrentViewTarget}
APIInterface={this.APIInterface}
@@ -191,7 +202,6 @@ class App extends Component {
diff --git a/src/GBZBaseAPI.mjs b/src/GBZBaseAPI.mjs
new file mode 100644
index 00000000..36d4c2e6
--- /dev/null
+++ b/src/GBZBaseAPI.mjs
@@ -0,0 +1,210 @@
+import { APIInterface } from "./APIInterface.mjs";
+import { WASI, File, OpenFile } from "@bjorn3/browser_wasi_shim";
+
+// TODO: The Webpack way to get the WASM would be something like:
+//import QueryWasm from "gbz-base/target/wasm32-wasi/release/query.wasm";
+// if the export mapping is broken, or
+//import QueryWasm from "gbz-base/query.wasm";
+// if it is working. In Jest, not only is the export mapping not working, but
+// also it can't get us a fetch-able string from the import like Webpack does.
+// So we will need some fancy Jest config to mock the WASM file into a js
+// module that does *something*, and also to mock fetch() into something that
+// can fetch it. Or else we need to hide that all behind something that can
+// fetch the WASM on either Webpack or Jest with its own strategies/by being
+// swapped out.
+
+// Resolve with the bytes or Response of the WASM query blob, on Jest or Webpack.
+async function getWasmBytes() {
+ let blobBytes = null;
+
+ if (!window["jest"]) {
+ // Not running on Jest, we should be able to dynamic import a binary asset
+ // by export name and get the bytes, and Webpack will handle it.
+ try {
+ let blobImport = await import("gbz-base/query.wasm");
+ return fetch(blobImport.default);
+ } catch (e) {
+ console.error("Could not dynamically import WASM blob.", e);
+ // Leave blobBytes unset to try a fallback method.
+ }
+ }
+
+ if (!blobBytes) {
+ // Either we're on Jest, or the dynamic import didn't work (maybe we're on
+ // plain Node?).
+ //
+ // Try to open the file from the filesystem.
+ //
+ // Don't actually try and ship the filesystem module in the browser though:
+ // see
+ let fs = await import(/* webpackIgnore: true */ "fs-extra");
+ blobBytes = await fs.readFile("node_modules/gbz-base/target/wasm32-wasi/release/query.wasm");
+ }
+
+ console.log("Got blob bytes: ", blobBytes);
+ return blobBytes;
+}
+
+/**
+ * API implementation that uses tools compiled to WebAssembly, client-side.
+ */
+export class GBZBaseAPI extends APIInterface {
+ constructor() {
+ super();
+
+ // We can take user uploads, in which case we need to hold on to them somewhere.
+ // This holds all the file objects.
+ this.files = [];
+
+ // We need to index all their names by type.
+ this.filesByType = {};
+
+ // This is a promise for the compiled WebAssembly blob.
+ this.compiledWasm = undefined;
+ }
+
+ // Make sure our WASM backend is ready.
+ async setUp() {
+ if (this.compiledWasm === undefined) {
+ // Kick off and save exactly one request to get and load the WASM bytes.
+ this.compiledWasm = getWasmBytes().then((result) => {
+ if (result instanceof Response) {
+ // If a fetch request was made, compile as it streams in
+ return WebAssembly.compileStreaming(result);
+ } else {
+ // We have all the bytes, so compile right away.
+ // TODO: Put this logic in the function?
+ return WebAssembly.compile(result);
+ }
+ });
+ }
+
+ // Wait for the bytes to be available.
+ this.compiledWasm = await this.compiledWasm;
+ }
+
+ // Make a call into the WebAssembly code and return the result.
+ async callWasm(argv) {
+ if (argv.length < 1) {
+ // We need at least one command line argument to be the program name.
+ throw new Error("Not safe to invoke main() without program name");
+ }
+
+ // Make sure this.compiledWasm is set.
+ // TODO: Change to an accessor method?
+ await this.setUp();
+
+ // Define the places to store program input and output
+ let stdin = new File([]);
+ let stdout = new File([]);
+ let stderr = new File([]);
+
+ // Environment variables as NAME=value strings
+ const environment = ["RUST_BACKTRACE=full"];
+
+ // File descriptors for the process in number order
+ let file_descriptors = [new OpenFile(stdin), new OpenFile(stdout), new OpenFile(stderr)];
+
+ // Set up the WASI interface
+ let wasi = new WASI(argv, environment, file_descriptors);
+
+ // Set up the WebAssembly run
+ let instantiation = await WebAssembly.instantiate(this.compiledWasm, {
+ "wasi_snapshot_preview1": wasi.wasiImport,
+ });
+
+ try {
+ // Make the WASI system call main
+ let returnCode = wasi.start(instantiation);
+ console.log("Return code:", returnCode);
+ } finally {
+ // The WASM code can throw right out of the WASI shim if Rust panics.
+ console.log("Standard Output:", new TextDecoder().decode(stdout.data));
+ console.log("Standard Error:", new TextDecoder().decode(stderr.data));
+ }
+ }
+
+ // Return true if the WASM setup is working, and false otherwise.
+ async available() {
+ try {
+ await this.callWasm(["query", "--help"]);
+ return true;
+ } catch {
+ return false;
+ }
+ }
+
+ /////////
+ // Tube Map API implementation
+ /////////
+
+ async getChunkedData(viewTarget, cancelSignal) {
+ return {
+ graph: {},
+ gam: {},
+ region: null,
+ coloredNodes: [],
+ };
+ }
+
+ async getFilenames(cancelSignal) {
+ // Set up an empty response.
+ let response = {
+ files: [],
+ bedFiles: [],
+ };
+
+ for (let type of this.filesByType) {
+ if (type === "bed") {
+ // Just send all these files in bedFiles.
+ response.bedFiles = this.filesByType[type];
+ } else {
+ for (let fileName of this.filesByType[type]) {
+ // We sens a name/type record for each non-BED file
+ response.files.push({ name: fileName, type: type });
+ }
+ }
+ }
+
+ return response;
+ }
+
+ subscribeToFilenameChanges(handler, cancelSignal) {
+ return {};
+ }
+
+ async putFile(fileType, file, cancelSignal) {
+ // We track files just by array index.
+ let fileName = this.files.length.toString();
+ // Just hang on to the File object.
+ this.files.push(file);
+
+ if (this.filesByType[fileType] === undefined) {
+ this.filesByType[fileType] = [];
+ }
+ // Index the name we produced by type.
+ this.filesByType[fileType].push(fileName);
+
+ return fileName;
+ }
+
+ async getBedRegions(bedFile, cancelSignal) {
+ return {
+ bedRegions: [],
+ };
+ }
+
+ async getPathNames(graphFile, cancelSignal) {
+ return {
+ pathNames: [],
+ };
+ }
+
+ async getChunkTracks(bedFile, chunk, cancelSignal) {
+ return {
+ tracks: [],
+ };
+ }
+}
+
+export default GBZBaseAPI;
diff --git a/src/GBZBaseAPI.test.js b/src/GBZBaseAPI.test.js
new file mode 100644
index 00000000..e8e3562c
--- /dev/null
+++ b/src/GBZBaseAPI.test.js
@@ -0,0 +1,38 @@
+import { GBZBaseAPI } from "./GBZBaseAPI.mjs";
+
+import fs from "fs-extra";
+
+it("can be constructed", () => {
+ let api = new GBZBaseAPI();
+});
+
+it("can self-test its WASM setup", async () => {
+ let api = new GBZBaseAPI();
+ let working = await api.available();
+ expect(working).toBeTruthy();
+});
+
+it("can have a file uploaded", async () => {
+ let api = new GBZBaseAPI();
+
+ // We need to make sure we make a jsdom File (which is a jsdom Blob), and not
+ // a Node Blob, for our test file. Otherwise it doesn't work with jsdom's
+ // upload machinery.
+ // See for example for
+ // background on the many flavors of Blob.
+ const fileData = await fs.readFileSync("exampleData/cactus.vg");
+ // Since a Node Buffer is an ArrayBuffer, we can use it to make a jsdom File.
+ // We need to put the data block in an enclosing array, or else the block
+ // will be iterated and each byte will be stringified and *those* bytes will
+ // be uploaded.
+ const file = new window.File([fileData], "cactus.vg", {
+ type: "application/octet-stream",
+ });
+
+ // Set up for canceling the upload
+ let controller = new AbortController();
+
+ let uploadName = await api.putFile("graph", file, controller.signal);
+
+ expect(uploadName).toBeTruthy();
+});
diff --git a/src/ServerAPI.mjs b/src/ServerAPI.mjs
index 199aeede..39ee0bf5 100644
--- a/src/ServerAPI.mjs
+++ b/src/ServerAPI.mjs
@@ -35,6 +35,107 @@ export class ServerAPI extends APIInterface {
return json;
}
+ subscribeToFilenameChanges(handler, cancelSignal) {
+ // We need something to hold the one currently active websocket.
+ let subscription = {};
+
+ // We make a function to connect the websocket, which we can call to reconnect.
+ let connect = () => {
+ subscription.ws = new WebSocket(this.apiUrl.replace(/^http/, "ws"));
+ subscription.ws.onmessage = (message) => {
+ if (!cancelSignal.aborted) {
+ // Tell the user that something changed
+ handler();
+ } else {
+ subscription.ws.close();
+ }
+ };
+ subscription.ws.onclose = (event) => {
+ if (!cancelSignal.aborted) {
+ // Reconnect if the socket closed
+ setTimeout(connect, 1000);
+ }
+ };
+ subscription.ws.onerror = (event) => {
+ // Close the socket if something went wrong
+ subscription.ws.close();
+ };
+ };
+
+ connect();
+
+ // Give the subscription back to the caller to hold.
+ // TODO: Do we really need to hold the web socket in scope?
+ // TODO: How does the user close the socket without a message arriving after cancelation?
+ return subscription;
+ }
+
+ async putFile(fileType, file, cancelSignal) {
+ // Prepare the form data for upload
+ const formData = new FormData();
+ // If the file is anything other than a Blob, it will be turned into a
+ // string and added as a normal form value. If it is a Blob it will
+ // become a file upload. Note that a File is a kind of Blob. See
+ //
+ //
+ // But in jsdom in the test environment there are two Blob types: Node's
+ // and jdsom's, and only jsdom's will work. Node's will turn into a
+ // string. And it seems hard to get at both types in a way that makes
+ // sense in a browser. So we will add the file and make sure it added OK
+ // and didn't stringify.
+
+ // According to , we *must* set a filename for uploads.
+ // In jsdom it turns on jsdom's own type checking support.
+ let fileName = file.name || "upload.dat";
+ formData.append("trackFile", file, fileName);
+ if (typeof formData.get("trackFile") == "string") {
+ // Catch stringification in case jsdom didn't.
+ console.error(
+ "Cannot upload file because it is not the appropriate type:",
+ file
+ );
+ throw new Error("File is not an appropriate type to upload");
+ }
+ // Make sure server can identify a Read file
+ formData.append("fileType", fileType);
+
+ return new Promise((resolve, reject) => {
+ const xhr = new XMLHttpRequest();
+ xhr.responseType = "json";
+ xhr.onreadystatechange = () => {
+ if (cancelSignal.aborted && xhr.readyState !== 0) {
+ // First time we have noticed we are aborted. Stop the request.
+ xhr.abort();
+ reject(new Error("Upload aborted"));
+ return;
+ }
+
+ if (xhr.readyState === 4) {
+ if (xhr.status === 200 && xhr.response.path) {
+ // Every thing ok, file uploaded, and we got a path.
+ resolve(xhr.response.path);
+ } else {
+ // Something weird happened.
+ reject(
+ new Error(
+ "Failed to upload file: status " +
+ xhr.status +
+ " and response: " +
+ xhr.response
+ )
+ );
+ }
+ }
+ };
+
+ console.log("Uploading file", file);
+ console.log("Sending form data", formData);
+ console.log("Form file is a " + typeof formData.get("trackFile"));
+ xhr.open("POST", `${this.apiUrl}/trackFileSubmission`, true);
+ xhr.send(formData);
+ });
+ }
+
async getBedRegions(bedFile, cancelSignal) {
const json = await fetchAndParse(`${this.apiUrl}/getBedRegions`, {
signal: cancelSignal,
diff --git a/src/components/HeaderForm.js b/src/components/HeaderForm.js
index 4540504c..5a1b837f 100644
--- a/src/components/HeaderForm.js
+++ b/src/components/HeaderForm.js
@@ -645,81 +645,34 @@ class HeaderForm extends Component {
// Sends uploaded file to server and returns a path to the file
handleFileUpload = async (fileType, file) => {
- return new Promise(
- function (resolve, reject) {
- if (file.size > config.MAXUPLOADSIZE) {
- this.showFileSizeAlert();
- return;
- }
-
- this.setUploadInProgress(true);
-
- const formData = new FormData();
- // If the file is anything other than a Blob, it will be turned into a
- // string and added as a normal form value. If it is a Blob it will
- // become a file upload. Note that a File is a kind of Blob. See
- //
- //
- // But in jsdom in the test environment there are two Blob types: Node's
- // and jdsom's, and only jsdom's will work. Node's will turn into a
- // string. And it seems hard to get at both types in a way that makes
- // sense in a browser. So we will add the file and make sure it added OK
- // and didn't stringify.
-
- // According to , we *must* set a filename for uploads.
- // In jsdom it turns on jsdom's own type checking support.
- let fileName = file.name || "upload.dat";
- formData.append("trackFile", file, fileName);
- if (typeof formData.get("trackFile") == "string") {
- // Catch stringification in case jsdom didn't.
- console.error(
- "Cannot upload file because it is not the appropriate type:",
- file
- );
- throw new Error("File is not an appropriate type to upload");
- }
- // Make sure server can identify a Read file
- formData.append("fileType", fileType);
- const xhr = new XMLHttpRequest();
- xhr.responseType = "json";
- xhr.onreadystatechange = () => {
- if (xhr.readyState === 4 && xhr.status === 200) {
- // Every thing ok, file uploaded
- this.setUploadInProgress(false);
- if (fileType === "graph") {
- this.getPathNames(xhr.response.path);
- }
+ if (file.size > config.MAXUPLOADSIZE) {
+ this.showFileSizeAlert();
+ return;
+ }
- resolve(xhr.response.path);
- }
- };
+ this.setUploadInProgress(true);
- console.log("Uploading file", file);
- console.log("Sending form data", formData);
- console.log("Form file is a " + typeof formData.get("trackFile"));
- xhr.open("POST", `${this.props.apiUrl}/trackFileSubmission`, true);
- xhr.send(formData);
- }.bind(this)
- );
- };
-
- setUpWebsocket = () => {
- this.ws = new WebSocket(this.props.apiUrl.replace(/^http/, "ws"));
- this.ws.onmessage = (message) => {
- if (!this.cancelSignal.aborted) {
+ try {
+ let fileName = await this.api.putFile(fileType, file, this.cancelSignal);
+ if (fileType === "graph") {
+ // Refresh the graphs right away
this.getMountedFilenames();
- } else {
- this.ws.close();
}
- };
- this.ws.onclose = (event) => {
+ this.setUploadInProgress(false);
+ return fileName;
+ } catch (e) {
if (!this.cancelSignal.aborted) {
- setTimeout(this.setUpWebsocket, 1000);
+ // Only pass along errors if we haven't canceled our fetches.
+ throw e;
}
- };
- this.ws.onerror = (event) => {
- this.ws.close();
- };
+ }
+ };
+
+ setUpWebsocket = () => {
+ this.subscription = this.api.subscribeToFilenameChanges(
+ this.getMountedFilenames,
+ this.cancelSignal
+ );
};
/* Function for toggling simplify button, enabling vg simplify to be turned on or off */
@@ -907,7 +860,6 @@ class HeaderForm extends Component {
}
HeaderForm.propTypes = {
- apiUrl: PropTypes.string.isRequired,
dataOrigin: PropTypes.string.isRequired,
setColorSetting: PropTypes.func.isRequired,
setDataOrigin: PropTypes.func.isRequired,
diff --git a/src/components/TubeMapContainer.js b/src/components/TubeMapContainer.js
index dd8ac702..47fa48ac 100644
--- a/src/components/TubeMapContainer.js
+++ b/src/components/TubeMapContainer.js
@@ -218,7 +218,7 @@ class TubeMapContainer extends Component {
} catch (error) {
this.handleFetchError(
error,
- `Fetching and parsing POST to ${this.props.apiUrl}/getChunkedData failed:`
+ "Fetching and parsing getChunkedData failed:"
);
}
};
@@ -296,7 +296,6 @@ class TubeMapContainer extends Component {
}
TubeMapContainer.propTypes = {
- apiUrl: PropTypes.string.isRequired,
dataOrigin: PropTypes.oneOf(Object.values(dataOriginTypes)).isRequired,
viewTarget: PropTypes.object.isRequired,
visOptions: PropTypes.object.isRequired,
diff --git a/src/setupTests.js b/src/setupTests.js
new file mode 100644
index 00000000..24149bdd
--- /dev/null
+++ b/src/setupTests.js
@@ -0,0 +1,11 @@
+// Configure Jest environment for all tests.
+//
+// create-react-app auto-magically executes this file by name for all the test
+// suites. See .
+
+// Make TextEncoder and TextDecoder available. Browsers and Node both have them
+// but jsdom refuses to let us use them. See
+//
+import { TextEncoder, TextDecoder } from "util";
+globalThis.TextEncoder = TextEncoder;
+globalThis.TextDecoder = TextDecoder;