Skip to content

Commit 48fedc4

Browse files
committed
Create folding-api.html, which exposes the API family of applet.fold
1 parent 274a1b9 commit 48fedc4

File tree

4 files changed

+216
-7
lines changed

4 files changed

+216
-7
lines changed

src/eterna/FoldingAPIApp.ts

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
import * as log from 'loglevel';
2+
import ExternalInterface, {ExternalInterfaceCtx} from './util/ExternalInterface';
3+
import Vienna from './folding/Vienna';
4+
import Vienna2 from './folding/Vienna2';
5+
import NuPACK from './folding/NuPACK';
6+
import Contrafold from './folding/Contrafold';
7+
import EternaFold from './folding/Eternafold';
8+
import EternaFoldThreshknot from './folding/EternafoldThreshknot';
9+
import RNAFoldBasic from './folding/RNAFoldBasic';
10+
import FolderManager from './folding/FolderManager';
11+
import LinearFoldC from './folding/LinearFoldC';
12+
import LinearFoldE from './folding/LinearFoldE';
13+
import LinearFoldV from './folding/LinearFoldV';
14+
import Folder from './folding/Folder';
15+
import FoldingAPI from './eternaScript/FoldingAPI';
16+
17+
interface FoldingAppParams {
18+
containerID?: string;
19+
folderName?: string;
20+
}
21+
22+
interface ProcessedFoldingAppParams {
23+
containerID: string;
24+
folderName: string;
25+
}
26+
27+
export class WasmNotSupportedError extends Error {}
28+
29+
export class ContainerElementNotFound extends Error {}
30+
31+
/**
32+
* Entry point for the folding API provider.
33+
*
34+
* This is an alternate version of EternaJS, only exposing the API needed for scripts to work
35+
* (e.g. `Lib.fold` via `document.getElementById("maingame").fold`).
36+
* */
37+
export default class FoldingAPIApp {
38+
constructor(params: FoldingAppParams) {
39+
// Default param values
40+
params.containerID = params.containerID || 'maingame';
41+
params.folderName = 'vienna';
42+
43+
this._params = {containerID: params.containerID, folderName: params.folderName};
44+
45+
const appContainer: HTMLElement | null = document.getElementById(params.containerID);
46+
if (!appContainer) {
47+
throw new ContainerElementNotFound(`Could not find HTML element with ID ${params.containerID}`);
48+
}
49+
this._appContainer = appContainer;
50+
51+
ExternalInterface.init(appContainer);
52+
}
53+
54+
private static isWebAssemblySupported() {
55+
return typeof WebAssembly === 'object';
56+
}
57+
58+
public async run(): Promise<void> {
59+
if (!FoldingAPIApp.isWebAssemblySupported()) {
60+
throw new WasmNotSupportedError(
61+
"Can't initialize the folding API app, since the browser doesn't support WASM"
62+
);
63+
}
64+
65+
await this.initFoldingEngines();
66+
this.initScriptInterface();
67+
}
68+
69+
public disposeNow(): void {
70+
this._appContainer.innerHTML = '';
71+
72+
FolderManager.dispose();
73+
ExternalInterface.dispose();
74+
}
75+
76+
private async initFoldingEngines(): Promise<void> {
77+
log.info('Initializing folding engines...');
78+
console.time('Test');
79+
const folders: (Folder | null)[] = await Promise.all([
80+
Vienna.create(),
81+
Vienna2.create(),
82+
NuPACK.create(),
83+
LinearFoldC.create(),
84+
LinearFoldE.create(),
85+
LinearFoldV.create(),
86+
Contrafold.create(),
87+
EternaFold.create(),
88+
EternaFoldThreshknot.create(),
89+
RNAFoldBasic.create()]);
90+
91+
log.info('Folding engines intialized');
92+
for (const folder of folders) {
93+
if (folder !== null) {
94+
FolderManager.instance.addFolder(folder);
95+
}
96+
}
97+
98+
const folder = FolderManager.instance.getFolder(this._params.folderName);
99+
if (folder === null) {
100+
log.warn(`No such folder '${this._params.folderName}'`);
101+
} else {
102+
this._folder = folder;
103+
}
104+
}
105+
106+
private initScriptInterface(): void {
107+
new FoldingAPI({
108+
getFolder: () => this._folder,
109+
getIsPseudoknot: () => false
110+
}).registerToScriptInterface(this._scriptInterface);
111+
112+
ExternalInterface.pushContext(this._scriptInterface);
113+
}
114+
115+
private readonly _params: ProcessedFoldingAppParams;
116+
private readonly _scriptInterface: ExternalInterfaceCtx = new ExternalInterfaceCtx();
117+
private readonly _appContainer: HTMLElement;
118+
// private readonly _folderSwitcher: FolderSwitcher;
119+
private _folder: Folder;
120+
}

src/eterna/index.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import * as log from 'loglevel';
22
import EternaApp from 'eterna/EternaApp';
3+
import FoldingAPIApp from 'eterna/FoldingAPIApp';
34
import * as PIXI from 'pixi.js';
45

56
const isProduction = process.env.NODE_ENV === 'production';
@@ -8,9 +9,11 @@ log.setLevel(isProduction ? 'info' : 'trace');
89
declare global {
910
interface Window {
1011
EternaApp: typeof EternaApp;
12+
FoldingAPIApp: typeof FoldingAPIApp;
1113
app: EternaApp; // this syntax is used in index.html.tmpl, at least...
1214
__PIXI_APP__?: PIXI.Application;
1315
}
1416
}
1517

1618
window.EternaApp = EternaApp;
19+
window.FoldingAPIApp = FoldingAPIApp;

src/folding-api.html.tmpl

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
<!DOCTYPE html>
2+
<html style="height: 100%;">
3+
<head>
4+
<meta charset="utf-8">
5+
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
6+
<title>EternaJS</title>
7+
8+
<!--
9+
These scripts are all grabbed from https://github.com/EteRNAgame/website/tree/master/frontend
10+
-->
11+
12+
<link type="text/css" rel="stylesheet" media="all" href="./frontend/themes/css/eterna.css" />
13+
14+
<script>
15+
// The Coffeescript code exposes a Comment object, which clobbers a browser global, which we do not use,
16+
// but the browser global is used by DOMPurify
17+
__comment_bak = Comment;
18+
</script>
19+
20+
<script src="./frontend/jscripts/jquery/jquery-1.7.2.min.js"></script>
21+
<script src="./frontend/jscripts/jquery/jquery-unselectable.js"></script>
22+
<script src="./frontend/jscripts/jquery-ui/jquery-ui-1.8.7.custom.min.js"></script>
23+
<script src="./frontend/jscripts/json/json2.js"></script>
24+
25+
<script src="./frontend/jscripts/application.js"></script>
26+
<script src="./frontend/jscripts/utils.js"></script>
27+
<script src="./frontend/jscripts/ajaxmanager.js"></script>
28+
<script src="./frontend/jscripts/datamanager.js"></script>
29+
<script src="./frontend/jscripts/usermanager.js"></script>
30+
31+
<script src="./frontend/jscripts/eterna/eterna-application.js"></script>
32+
<script src="./frontend/jscripts/eterna/eterna-utils.js"></script>
33+
<script src="./frontend/jscripts/eterna/script-library.js"></script>
34+
<script src="./frontend/jscripts/eterna/script-interface.js"></script>
35+
<script src="./frontend/jscripts/eterna/presenter.js"></script>
36+
37+
<script>
38+
// The Coffeescript code exposes a Comment object, which clobbers a browser global, which we do not use,
39+
// but the browser global is used by DOMPurify
40+
Comment = __comment_bak;
41+
</script>
42+
</head>
43+
<body style="margin: 0; padding: 0; height: 100%; display: flex; flex-direction: column;">
44+
45+
<!-- Scripts expect that an element with "maingame" will exist, so this name shouldn't be changed -->
46+
<div id="maingame"></div>
47+
48+
<!-- Load our webpack bundles -->
49+
<%= htmlWebpackPlugin.tags.bodyTags %>
50+
51+
<script>
52+
// Wrapping the code in a function to prevent variables from leaking into the global scope.
53+
(() => {
54+
Application.GET_URI = "<%= htmlWebpackPlugin.options.process.env.APP_SERVER_URL %>/get/";
55+
Application.POST_URI = "<%= htmlWebpackPlugin.options.process.env.APP_SERVER_URL %>/post/";
56+
57+
const params = new URLSearchParams(window.location.search);
58+
const containerID = "maingame";
59+
60+
const app = new FoldingAPIApp({
61+
containerID: containerID,
62+
folderName: params.get("folder"),
63+
})
64+
65+
app.run();
66+
67+
window.stopApp = () => {
68+
app.disposeNow();
69+
}
70+
})();
71+
</script>
72+
</body>
73+
</html>

webpack.common.js

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ function getEngineLocation() {
3838

3939
module.exports = {
4040
devtool: "inline-source-map",
41-
41+
4242
entry: {
4343
main: ['core-js/stable', 'regenerator-runtime/runtime', "./src/eterna/index.ts"],
4444
vendor: vendorDependencies
@@ -73,7 +73,7 @@ module.exports = {
7373
path: false
7474
}
7575
},
76-
76+
7777
module: {
7878
rules: [
7979
{
@@ -95,7 +95,7 @@ module.exports = {
9595
}
9696
],
9797
},
98-
98+
9999
// When importing a module whose path matches one of the following, just
100100
// assume a corresponding global variable exists and use that instead.
101101
// This is important because it allows us to avoid bundling all of our
@@ -124,12 +124,12 @@ module.exports = {
124124
optimization: {
125125
splitChunks: {
126126
chunks: 'all',
127-
},
127+
},
128128
},
129-
130-
plugins: [
129+
130+
plugins: [
131131
new webpack.EnvironmentPlugin(Object.keys(process.env)),
132-
132+
133133
// Generate an index.html that includes our webpack bundles
134134
new HtmlWebpackPlugin({
135135
template: 'src/index.html.tmpl',
@@ -142,6 +142,19 @@ module.exports = {
142142
}
143143
}),
144144

145+
// Generate folding=api.html, for the scripts page
146+
new HtmlWebpackPlugin({
147+
template: 'src/folding-api.html.tmpl',
148+
filename: "folding-api.html",
149+
inject: false,
150+
scriptLoading: 'blocking',
151+
process: {
152+
env: {
153+
...process.env
154+
}
155+
}
156+
}),
157+
145158
// Generate a manifest.json file containing our entry point file names:
146159
// https://github.com/danethurber/webpack-manifest-plugin#hooks-options
147160
new WebpackManifestPlugin({

0 commit comments

Comments
 (0)