Skip to content

Commit

Permalink
Create folding-api.html, which exposes the API family of applet.fold
Browse files Browse the repository at this point in the history
  • Loading branch information
guyguy2001 committed Jun 14, 2024
1 parent 274a1b9 commit 228cd42
Show file tree
Hide file tree
Showing 4 changed files with 215 additions and 7 deletions.
119 changes: 119 additions & 0 deletions src/eterna/FoldingAPIApp.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
import * as log from 'loglevel';
import ExternalInterface, {ExternalInterfaceCtx} from './util/ExternalInterface';
import Vienna from './folding/Vienna';
import Vienna2 from './folding/Vienna2';
import NuPACK from './folding/NuPACK';
import Contrafold from './folding/Contrafold';
import EternaFold from './folding/Eternafold';
import EternaFoldThreshknot from './folding/EternafoldThreshknot';
import RNAFoldBasic from './folding/RNAFoldBasic';
import FolderManager from './folding/FolderManager';
import LinearFoldC from './folding/LinearFoldC';
import LinearFoldE from './folding/LinearFoldE';
import LinearFoldV from './folding/LinearFoldV';
import Folder from './folding/Folder';
import FoldingAPI from './eternaScript/FoldingAPI';

interface FoldingAppParams {
containerID?: string;
folderName?: string;
}

interface ProcessedFoldingAppParams {
containerID: string;
folderName: string;
}

export class WasmNotSupportedError extends Error {}

export class ContainerElementNotFound extends Error {}

/**
* Entry point for the folding API provider.
*
* This is an alternate version of EternaJS, only exposing the API needed for scripts to work
* (e.g. `Lib.fold` via `document.getElementById("maingame").fold`).
* */
export default class FoldingAPIApp {
constructor(params: FoldingAppParams) {
// Default param values
params.containerID = params.containerID || 'maingame';
params.folderName = 'vienna';

this._params = {containerID: params.containerID, folderName: params.folderName};

const appContainer: HTMLElement | null = document.getElementById(params.containerID);
if (!appContainer) {
throw new ContainerElementNotFound(`Could not find HTML element with ID ${params.containerID}`);
}
this._appContainer = appContainer;

ExternalInterface.init(appContainer);
}

private static isWebAssemblySupported() {
return typeof WebAssembly === 'object';
}

public async run(): Promise<void> {
if (!FoldingAPIApp.isWebAssemblySupported()) {
throw new WasmNotSupportedError(
"Can't initialize the folding API app, since the browser doesn't support WASM"
);
}

await this.initFoldingEngines();
this.initScriptInterface();
}

public disposeNow(): void {
this._appContainer.innerHTML = '';

FolderManager.dispose();
ExternalInterface.dispose();
}

private async initFoldingEngines(): Promise<void> {
log.info('Initializing folding engines...');
const folders: (Folder | null)[] = await Promise.all([
Vienna.create(),
Vienna2.create(),
NuPACK.create(),
LinearFoldC.create(),
LinearFoldE.create(),
LinearFoldV.create(),
Contrafold.create(),
EternaFold.create(),
EternaFoldThreshknot.create(),
RNAFoldBasic.create()]);

log.info('Folding engines intialized');
for (const folder of folders) {
if (folder !== null) {
FolderManager.instance.addFolder(folder);
}
}

const folder = FolderManager.instance.getFolder(this._params.folderName);
if (folder === null) {
log.warn(`No such folder '${this._params.folderName}'`);
} else {
this._folder = folder;
}
}

private initScriptInterface(): void {
new FoldingAPI({
getFolder: () => this._folder,
getIsPseudoknot: () => false
}).registerToScriptInterface(this._scriptInterface);

ExternalInterface.pushContext(this._scriptInterface);
}

private readonly _params: ProcessedFoldingAppParams;
private readonly _scriptInterface: ExternalInterfaceCtx = new ExternalInterfaceCtx();
private readonly _appContainer: HTMLElement;
// private readonly _folderSwitcher: FolderSwitcher;
private _folder: Folder;
}
3 changes: 3 additions & 0 deletions src/eterna/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import * as log from 'loglevel';
import EternaApp from 'eterna/EternaApp';
import FoldingAPIApp from 'eterna/FoldingAPIApp';
import * as PIXI from 'pixi.js';

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

window.EternaApp = EternaApp;
window.FoldingAPIApp = FoldingAPIApp;
73 changes: 73 additions & 0 deletions src/folding-api.html.tmpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
<!DOCTYPE html>
<html style="height: 100%;">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
<title>EternaJS</title>

<!--
These scripts are all grabbed from https://github.com/EteRNAgame/website/tree/master/frontend
-->

<link type="text/css" rel="stylesheet" media="all" href="./frontend/themes/css/eterna.css" />

<script>
// The Coffeescript code exposes a Comment object, which clobbers a browser global, which we do not use,
// but the browser global is used by DOMPurify
__comment_bak = Comment;
</script>

<script src="./frontend/jscripts/jquery/jquery-1.7.2.min.js"></script>
<script src="./frontend/jscripts/jquery/jquery-unselectable.js"></script>
<script src="./frontend/jscripts/jquery-ui/jquery-ui-1.8.7.custom.min.js"></script>
<script src="./frontend/jscripts/json/json2.js"></script>

<script src="./frontend/jscripts/application.js"></script>
<script src="./frontend/jscripts/utils.js"></script>
<script src="./frontend/jscripts/ajaxmanager.js"></script>
<script src="./frontend/jscripts/datamanager.js"></script>
<script src="./frontend/jscripts/usermanager.js"></script>

<script src="./frontend/jscripts/eterna/eterna-application.js"></script>
<script src="./frontend/jscripts/eterna/eterna-utils.js"></script>
<script src="./frontend/jscripts/eterna/script-library.js"></script>
<script src="./frontend/jscripts/eterna/script-interface.js"></script>
<script src="./frontend/jscripts/eterna/presenter.js"></script>

<script>
// The Coffeescript code exposes a Comment object, which clobbers a browser global, which we do not use,
// but the browser global is used by DOMPurify
Comment = __comment_bak;
</script>
</head>
<body style="margin: 0; padding: 0; height: 100%; display: flex; flex-direction: column;">

<!-- Scripts expect that an element with "maingame" will exist, so this name shouldn't be changed -->
<div id="maingame"></div>

<!-- Load our webpack bundles -->
<%= htmlWebpackPlugin.tags.bodyTags %>

<script>
// Wrapping the code in a function to prevent variables from leaking into the global scope.
(() => {
Application.GET_URI = "<%= htmlWebpackPlugin.options.process.env.APP_SERVER_URL %>/get/";
Application.POST_URI = "<%= htmlWebpackPlugin.options.process.env.APP_SERVER_URL %>/post/";

const params = new URLSearchParams(window.location.search);
const containerID = "maingame";

const app = new FoldingAPIApp({
containerID: containerID,
folderName: params.get("folder"),
})

app.run();

window.stopApp = () => {
app.disposeNow();
}
})();
</script>
</body>
</html>
27 changes: 20 additions & 7 deletions webpack.common.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ function getEngineLocation() {

module.exports = {
devtool: "inline-source-map",

entry: {
main: ['core-js/stable', 'regenerator-runtime/runtime', "./src/eterna/index.ts"],
vendor: vendorDependencies
Expand Down Expand Up @@ -73,7 +73,7 @@ module.exports = {
path: false
}
},

module: {
rules: [
{
Expand All @@ -95,7 +95,7 @@ module.exports = {
}
],
},

// When importing a module whose path matches one of the following, just
// assume a corresponding global variable exists and use that instead.
// This is important because it allows us to avoid bundling all of our
Expand Down Expand Up @@ -124,12 +124,12 @@ module.exports = {
optimization: {
splitChunks: {
chunks: 'all',
},
},
},
plugins: [

plugins: [
new webpack.EnvironmentPlugin(Object.keys(process.env)),

// Generate an index.html that includes our webpack bundles
new HtmlWebpackPlugin({
template: 'src/index.html.tmpl',
Expand All @@ -142,6 +142,19 @@ module.exports = {
}
}),

// Generate folding=api.html, for the scripts page
new HtmlWebpackPlugin({
template: 'src/folding-api.html.tmpl',
filename: "folding-api.html",
inject: false,
scriptLoading: 'blocking',
process: {
env: {
...process.env
}
}
}),

// Generate a manifest.json file containing our entry point file names:
// https://github.com/danethurber/webpack-manifest-plugin#hooks-options
new WebpackManifestPlugin({
Expand Down

0 comments on commit 228cd42

Please sign in to comment.