mirror of
https://github.com/molstar/molstar.git
synced 2026-06-04 13:30:24 +08:00
Add mvs-stories app (#1523)
* mvs-stories app * update mvs-stories example * fix build * fix UI bug * support search params in stories app * merge fixes * PR feedback * customize build filenames * mvs-stories loading state & dev build script fixes * multiple context example
This commit is contained in:
@@ -5,6 +5,8 @@ Note that since we don't clearly distinguish between a public and private interf
|
||||
|
||||
## [Unreleased]
|
||||
- Remove `xhr2` dependency for NodeJS, use `fetch`
|
||||
- Add `mvs-stories` app included in the `molstar` NPM package
|
||||
- Use the app in the corresponding example
|
||||
|
||||
## [v4.16.0] - 2025-05-20
|
||||
- Load potentially big text files as `StringLike` to bypass string size limit
|
||||
|
||||
@@ -11,23 +11,28 @@ import * as argparse from 'argparse';
|
||||
import { sassPlugin } from 'esbuild-sass-plugin';
|
||||
import * as os from 'os';
|
||||
|
||||
const AllApps = [
|
||||
'viewer',
|
||||
'docking-viewer',
|
||||
'mesoscale-explorer'
|
||||
const Apps = [
|
||||
// Apps
|
||||
{ kind: 'app', name: 'viewer' },
|
||||
{ kind: 'app', name: 'docking-viewer' },
|
||||
{ kind: 'app', name: 'mesoscale-explorer' },
|
||||
{ kind: 'app', name: 'mvs-stories', globalName: 'mvsStories', filename: 'mvs-stories.js' },
|
||||
|
||||
// Examples
|
||||
{ kind: 'example', name: 'proteopedia-wrapper' },
|
||||
{ kind: 'example', name: 'basic-wrapper' },
|
||||
{ kind: 'example', name: 'lighting' },
|
||||
{ kind: 'example', name: 'alpha-orbitals' },
|
||||
{ kind: 'example', name: 'alphafolddb-pae' },
|
||||
{ kind: 'example', name: 'mvs-stories' },
|
||||
{ kind: 'example', name: 'ihm-restraints' },
|
||||
{ kind: 'example', name: 'interactions' },
|
||||
{ kind: 'example', name: 'ligand-editor' },
|
||||
];
|
||||
|
||||
const AllExamples = [
|
||||
'proteopedia-wrapper',
|
||||
'basic-wrapper',
|
||||
'lighting',
|
||||
'alpha-orbitals',
|
||||
'alphafolddb-pae',
|
||||
'mvs-stories',
|
||||
'ihm-restraints',
|
||||
'interactions',
|
||||
'ligand-editor',
|
||||
];
|
||||
function findApp(name, kind) {
|
||||
return Apps.find(a => a.name === name && a.kind === kind);
|
||||
}
|
||||
|
||||
function mkDir(dir) {
|
||||
try {
|
||||
@@ -92,7 +97,9 @@ function examplesCssRenamePlugin({ root }) {
|
||||
};
|
||||
}
|
||||
|
||||
async function watch(name, kind) {
|
||||
async function watch(app) {
|
||||
const { name, kind } = app;
|
||||
|
||||
const prefix = kind === 'app'
|
||||
? `./build/${name}`
|
||||
: `./build/examples/${name}`;
|
||||
@@ -102,14 +109,19 @@ async function watch(name, kind) {
|
||||
entry = `./src/${kind}s/${name}/index.tsx`;
|
||||
}
|
||||
|
||||
let filename = app.filename;
|
||||
if (!filename) {
|
||||
filename = kind === 'app' ? 'molstar.js' : 'index.js';
|
||||
}
|
||||
|
||||
const ctx = await esbuild.context({
|
||||
entryPoints: [entry],
|
||||
tsconfig: './tsconfig.json',
|
||||
bundle: true,
|
||||
globalName: 'molstar',
|
||||
globalName: app.globalName || 'molstar',
|
||||
outfile: kind === 'app'
|
||||
? `./build/${name}/molstar.js`
|
||||
: `./build/examples/${name}/index.js`,
|
||||
? `./build/${name}/${filename}`
|
||||
: `./build/examples/${name}/${filename}`,
|
||||
plugins: [
|
||||
fileLoaderPlugin({ out: prefix }),
|
||||
sassPlugin({
|
||||
@@ -162,11 +174,11 @@ argParser.add_argument('--host', {
|
||||
|
||||
const args = argParser.parse_args();
|
||||
|
||||
const apps = (!args.apps ? [] : (args.apps.length ? args.apps : AllApps)).filter(a => AllApps.includes(a));
|
||||
const examples = (!args.examples ? [] : (args.examples.length ? args.examples : AllExamples)).filter(e => AllExamples.includes(e));
|
||||
const apps = (!args.apps ? [] : (args.apps.length ? args.apps.map(a => findApp(a, 'app')).filter(a => a) : Apps.filter(a => a.kind === 'app')));
|
||||
const examples = (!args.examples ? [] : (args.examples.length ? args.examples.map(e => findApp(e, 'example')).filter(a => a) : Apps.filter(a => a.kind === 'example')));
|
||||
|
||||
console.log('Apps:', apps);
|
||||
console.log('Examples:', examples);
|
||||
console.log('Apps:', apps.map(a => a.name));
|
||||
console.log('Examples:', examples.map(e => e.name));
|
||||
console.log('');
|
||||
|
||||
function getLocalIPs() {
|
||||
@@ -186,8 +198,8 @@ function getLocalIPs() {
|
||||
|
||||
async function main() {
|
||||
const promises = [];
|
||||
for (const app of apps) promises.push(watch(app, 'app'));
|
||||
for (const example of examples) promises.push(watch(example, 'example'));
|
||||
for (const app of apps) promises.push(watch(app));
|
||||
for (const example of examples) promises.push(watch(example));
|
||||
|
||||
console.log('Initial build...');
|
||||
|
||||
|
||||
4052
examples/mvs/kinase-story.mvsj
Normal file
4052
examples/mvs/kinase-story.mvsj
Normal file
File diff suppressed because it is too large
Load Diff
568
package-lock.json
generated
568
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -51,7 +51,8 @@
|
||||
},
|
||||
"files": [
|
||||
"lib/",
|
||||
"build/viewer/"
|
||||
"build/viewer/",
|
||||
"build/mvs-stories/"
|
||||
],
|
||||
"bin": {
|
||||
"cif2bcif": "lib/commonjs/cli/cif2bcif/index.js",
|
||||
|
||||
@@ -6,19 +6,20 @@
|
||||
|
||||
import { BehaviorSubject } from 'rxjs';
|
||||
import { MVSData } from '../../extensions/mvs/mvs-data';
|
||||
import type { MolComponentViewerModel } from './elements/viewer';
|
||||
import type { MVSStoriesViewerModel } from './elements/viewer';
|
||||
|
||||
export type MolComponentCommand =
|
||||
| { kind: 'load-mvs', format?: 'mvsj' | 'mvsx', url?: string, data?: MVSData }
|
||||
export type MVSStoriesCommand =
|
||||
| { kind: 'load-mvs', format?: 'mvsj' | 'mvsx', url?: string, data?: MVSData | string | Uint8Array }
|
||||
|
||||
|
||||
export class MolComponentContext {
|
||||
commands = new BehaviorSubject<MolComponentCommand | undefined>(undefined);
|
||||
behavior = {
|
||||
viewers: new BehaviorSubject<{ name?: string, model: MolComponentViewerModel }[]>([]),
|
||||
export class MVSStoriesContext {
|
||||
commands = new BehaviorSubject<MVSStoriesCommand | undefined>(undefined);
|
||||
state = {
|
||||
viewers: new BehaviorSubject<{ name?: string, model: MVSStoriesViewerModel }[]>([]),
|
||||
isLoading: new BehaviorSubject(false),
|
||||
};
|
||||
|
||||
dispatch(command: MolComponentCommand) {
|
||||
dispatch(command: MVSStoriesCommand) {
|
||||
this.commands.next(command);
|
||||
}
|
||||
|
||||
@@ -26,12 +27,12 @@ export class MolComponentContext {
|
||||
}
|
||||
}
|
||||
|
||||
export function getMolComponentContext(options?: { name?: string, container?: object }) {
|
||||
export function getMVSStoriesContext(options?: { name?: string, container?: object }) {
|
||||
const container: any = options?.container ?? window;
|
||||
container.componentContexts ??= {};
|
||||
const name = options?.name ?? '<default>';
|
||||
if (!container.componentContexts[name]) {
|
||||
container.componentContexts[name] = new MolComponentContext(options?.name);
|
||||
container.componentContexts[name] = new MVSStoriesContext(options?.name);
|
||||
}
|
||||
return container.componentContexts[name];
|
||||
}
|
||||
2
src/apps/mvs-stories/elements/index.ts
Normal file
2
src/apps/mvs-stories/elements/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
import './snapshot-markdown';
|
||||
import './viewer';
|
||||
@@ -6,17 +6,18 @@
|
||||
|
||||
import { BehaviorSubject, distinctUntilChanged, map } from 'rxjs';
|
||||
import { PluginComponent } from '../../../mol-plugin-state/component';
|
||||
import { getMolComponentContext, MolComponentContext } from '../context';
|
||||
import { MolComponentViewerModel } from './viewer';
|
||||
import { getMVSStoriesContext, MVSStoriesContext } from '../context';
|
||||
import { MVSStoriesViewerModel } from './viewer';
|
||||
import Markdown from 'react-markdown';
|
||||
import { useBehavior } from '../../../mol-plugin-ui/hooks/use-behavior';
|
||||
import { createRoot } from 'react-dom/client';
|
||||
import { PluginStateSnapshotManager } from '../../../mol-plugin-state/manager/snapshots';
|
||||
import { MarkdownAnchor } from '../../../mol-plugin-ui/controls';
|
||||
import { PluginReactContext } from '../../../mol-plugin-ui/base';
|
||||
import { CSSProperties } from 'react';
|
||||
|
||||
export class MolComponentSnapshotMarkdownModel extends PluginComponent {
|
||||
readonly context: MolComponentContext;
|
||||
export class MVSStoriesSnapshotMarkdownModel extends PluginComponent {
|
||||
readonly context: MVSStoriesContext;
|
||||
root: HTMLElement | undefined = undefined;
|
||||
|
||||
state = new BehaviorSubject<{
|
||||
@@ -26,7 +27,7 @@ export class MolComponentSnapshotMarkdownModel extends PluginComponent {
|
||||
}>({ all: [] });
|
||||
|
||||
get viewer() {
|
||||
return this.context.behavior.viewers.value?.find(v => this.options?.viewerName === v.name);
|
||||
return this.context.state.viewers.value?.find(v => this.options?.viewerName === v.name);
|
||||
}
|
||||
|
||||
sync() {
|
||||
@@ -41,11 +42,11 @@ export class MolComponentSnapshotMarkdownModel extends PluginComponent {
|
||||
async mount(root: HTMLElement) {
|
||||
this.root = root;
|
||||
|
||||
createRoot(root).render(<MolComponentSnapshotMarkdownUI model={this} />);
|
||||
createRoot(root).render(<MVSStoriesSnapshotMarkdownUI model={this} />);
|
||||
|
||||
let currentViewer: MolComponentViewerModel | undefined = undefined;
|
||||
let currentViewer: MVSStoriesViewerModel | undefined = undefined;
|
||||
let sub: { unsubscribe: () => void } | undefined = undefined;
|
||||
this.subscribe(this.context.behavior.viewers.pipe(
|
||||
this.subscribe(this.context.state.viewers.pipe(
|
||||
map(xs => xs.find(v => this.options?.viewerName === v.name)),
|
||||
distinctUntilChanged((a, b) => a?.model === b?.model)
|
||||
), viewer => {
|
||||
@@ -66,21 +67,31 @@ export class MolComponentSnapshotMarkdownModel extends PluginComponent {
|
||||
constructor(private options?: { context?: { name?: string, container?: object }, viewerName?: string }) {
|
||||
super();
|
||||
|
||||
this.context = getMolComponentContext(options?.context);
|
||||
this.context = getMVSStoriesContext(options?.context);
|
||||
}
|
||||
}
|
||||
|
||||
export function MolComponentSnapshotMarkdownUI({ model }: { model: MolComponentSnapshotMarkdownModel }) {
|
||||
export function MVSStoriesSnapshotMarkdownUI({ model }: { model: MVSStoriesSnapshotMarkdownModel }) {
|
||||
const state = useBehavior(model.state);
|
||||
const isLoading = useBehavior(model.context.state.isLoading);
|
||||
|
||||
if (state.all.length === 0) {
|
||||
return <div>
|
||||
<i>No snapshot loaded</i>
|
||||
const style: CSSProperties = { display: 'flex', flexDirection: 'column', height: '100%' };
|
||||
const className = 'mvs-stories-markdown-explanation';
|
||||
|
||||
if (isLoading) {
|
||||
return <div style={style} className={className}>
|
||||
<i>Loading...</i>
|
||||
</div>;
|
||||
}
|
||||
|
||||
return <div style={{ display: 'flex', flexDirection: 'column', height: '100%' }}>
|
||||
<div style={{ display: 'flex', flexDirection: 'row', width: '100%', gap: '8px' }} className='mc-snapshot-markdown-header'>
|
||||
if (state.all.length === 0) {
|
||||
return <div style={style} className={className}>
|
||||
<i>No snapshot loaded or no description available</i>
|
||||
</div>;
|
||||
}
|
||||
|
||||
return <div style={style} className={className}>
|
||||
<div style={{ display: 'flex', flexDirection: 'row', width: '100%', gap: '8px' }}>
|
||||
<span style={{ lineHeight: '38px', minWidth: 60, maxWidth: 60, flexShrink: 0 }}>{typeof state.index === 'number' ? state.index + 1 : '-'}/{state.all.length}</span>
|
||||
<button onClick={() => model.viewer?.model.plugin?.managers.snapshot.applyNext(-1)} style={{ flexGrow: 1, flexShrink: 0 }}>Prev</button>
|
||||
<button onClick={() => model.viewer?.model.plugin?.managers.snapshot.applyNext(1)} style={{ flexGrow: 1, flexShrink: 0 }}>Next</button>
|
||||
@@ -95,11 +106,11 @@ export function MolComponentSnapshotMarkdownUI({ model }: { model: MolComponentS
|
||||
</div>;
|
||||
}
|
||||
|
||||
export class MolComponentSnapshotMarkdownViewer extends HTMLElement {
|
||||
private model: MolComponentSnapshotMarkdownModel | undefined = undefined;
|
||||
export class MVSStoriesSnapshotMarkdownViewer extends HTMLElement {
|
||||
private model: MVSStoriesSnapshotMarkdownModel | undefined = undefined;
|
||||
|
||||
async connectedCallback() {
|
||||
this.model = new MolComponentSnapshotMarkdownModel({
|
||||
this.model = new MVSStoriesSnapshotMarkdownModel({
|
||||
context: { name: this.getAttribute('context-name') ?? undefined },
|
||||
viewerName: this.getAttribute('viewer-name') ?? undefined,
|
||||
});
|
||||
@@ -116,4 +127,4 @@ export class MolComponentSnapshotMarkdownViewer extends HTMLElement {
|
||||
}
|
||||
}
|
||||
|
||||
window.customElements.define('mc-snapshot-markdown', MolComponentSnapshotMarkdownViewer);
|
||||
window.customElements.define('mvs-stories-snapshot-markdown', MVSStoriesSnapshotMarkdownViewer);
|
||||
@@ -5,20 +5,19 @@
|
||||
*/
|
||||
|
||||
import { MolViewSpec } from '../../../extensions/mvs/behavior';
|
||||
import { loadMVS } from '../../../extensions/mvs/load';
|
||||
import { MVSData } from '../../../extensions/mvs/mvs-data';
|
||||
import { StringLike } from '../../../mol-io/common/string-like';
|
||||
import { loadMVSData } from '../../../extensions/mvs/components/formats';
|
||||
import { PluginComponent } from '../../../mol-plugin-state/component';
|
||||
import { createPluginUI } from '../../../mol-plugin-ui';
|
||||
import { renderReact18 } from '../../../mol-plugin-ui/react18';
|
||||
import { DefaultPluginUISpec } from '../../../mol-plugin-ui/spec';
|
||||
import { PluginCommands } from '../../../mol-plugin/commands';
|
||||
import { PluginConfig } from '../../../mol-plugin/config';
|
||||
import { PluginContext } from '../../../mol-plugin/context';
|
||||
import { PluginSpec } from '../../../mol-plugin/spec';
|
||||
import { getMolComponentContext, MolComponentContext } from '../context';
|
||||
import { getMVSStoriesContext, MVSStoriesContext } from '../context';
|
||||
|
||||
export class MolComponentViewerModel extends PluginComponent {
|
||||
readonly context: MolComponentContext;
|
||||
export class MVSStoriesViewerModel extends PluginComponent {
|
||||
readonly context: MVSStoriesContext;
|
||||
plugin?: PluginContext = undefined;
|
||||
|
||||
async mount(root: HTMLElement) {
|
||||
@@ -52,36 +51,46 @@ export class MolComponentViewerModel extends PluginComponent {
|
||||
});
|
||||
|
||||
this.subscribe(this.context.commands, async (cmd) => {
|
||||
if (!cmd) return;
|
||||
if (!cmd || !this.plugin) return;
|
||||
|
||||
if (cmd.kind === 'load-mvs') {
|
||||
if (cmd.url) {
|
||||
const data = await this.plugin!.runTask(this.plugin!.fetch({ url: cmd.url, type: 'string' }));
|
||||
const mvsData = MVSData.fromMVSJ(StringLike.toString(data));
|
||||
await loadMVS(this.plugin!, mvsData, { sanityChecks: true, sourceUrl: cmd.url });
|
||||
} else if (cmd.data) {
|
||||
await loadMVS(this.plugin!, cmd.data, { sanityChecks: true });
|
||||
try {
|
||||
this.context.state.isLoading.next(true);
|
||||
if (cmd.kind === 'load-mvs') {
|
||||
if (cmd.url) {
|
||||
const data = await this.plugin.runTask(this.plugin.fetch({ url: cmd.url, type: cmd.format === 'mvsx' ? 'binary' : 'string' }));
|
||||
await loadMVSData(this.plugin, data, cmd.format ?? 'mvsj', { sourceUrl: cmd.url });
|
||||
} else if (cmd.data) {
|
||||
await loadMVSData(this.plugin, cmd.data, cmd.format ?? 'mvsj');
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
PluginCommands.Toast.Show(
|
||||
this.plugin,
|
||||
{ key: '<mvsload>', title: 'Error', message: e?.message ? `${e?.message}` : `${e}`, timeoutMs: 10000 }
|
||||
);
|
||||
} finally {
|
||||
this.context.state.isLoading.next(false);
|
||||
}
|
||||
});
|
||||
|
||||
const viewers = this.context.behavior.viewers.value;
|
||||
const viewers = this.context.state.viewers.value;
|
||||
const next = [...viewers, { name: this.options?.name, model: this }];
|
||||
this.context.behavior.viewers.next(next);
|
||||
this.context.state.viewers.next(next);
|
||||
}
|
||||
|
||||
constructor(private options?: { context?: { name?: string, container?: object }, name?: string }) {
|
||||
super();
|
||||
|
||||
this.context = getMolComponentContext(options?.context);
|
||||
this.context = getMVSStoriesContext(options?.context);
|
||||
|
||||
const viewers = this.context.behavior.viewers.value;
|
||||
const viewers = this.context.state.viewers.value;
|
||||
const index = viewers.findIndex(v => v.name === options?.name);
|
||||
if (index >= 0) {
|
||||
const next = [...viewers];
|
||||
next[index].model.dispose();
|
||||
next.splice(index, 0);
|
||||
this.context.behavior.viewers.next(next);
|
||||
this.context.state.viewers.next(next);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -90,11 +99,11 @@ function EmptyDescription() {
|
||||
return <></>;
|
||||
}
|
||||
|
||||
export class MolComponentViewer extends HTMLElement {
|
||||
private model: MolComponentViewerModel | undefined = undefined;
|
||||
export class MVSStoriesViewer extends HTMLElement {
|
||||
private model: MVSStoriesViewerModel | undefined = undefined;
|
||||
|
||||
async connectedCallback() {
|
||||
this.model = new MolComponentViewerModel({
|
||||
this.model = new MVSStoriesViewerModel({
|
||||
name: this.getAttribute('name') ?? undefined,
|
||||
context: { name: this.getAttribute('context-name') ?? undefined },
|
||||
});
|
||||
@@ -111,4 +120,4 @@ export class MolComponentViewer extends HTMLElement {
|
||||
}
|
||||
}
|
||||
|
||||
window.customElements.define('mc-viewer', MolComponentViewer);
|
||||
window.customElements.define('mvs-stories-viewer', MVSStoriesViewer);
|
||||
BIN
src/apps/mvs-stories/favicon.ico
Normal file
BIN
src/apps/mvs-stories/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 15 KiB |
94
src/apps/mvs-stories/index.html
Normal file
94
src/apps/mvs-stories/index.html
Normal file
@@ -0,0 +1,94 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
|
||||
<link rel="icon" href="./favicon.ico" type="image/x-icon">
|
||||
<title>Molecular Stories</title>
|
||||
<style>
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
#viewer {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
right: 34%;
|
||||
bottom: 0;
|
||||
}
|
||||
|
||||
#controls {
|
||||
position: absolute;
|
||||
left: 66%;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
padding: 16px;
|
||||
padding-bottom: 20px;
|
||||
border: 1px solid #ccc;
|
||||
border-left: none;
|
||||
background: #F6F5F3;
|
||||
z-index: -2;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
@media (orientation:portrait) {
|
||||
#viewer {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 40%;
|
||||
}
|
||||
|
||||
#controls {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 60%;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
border-top: none;
|
||||
}
|
||||
|
||||
.msp-viewport-controls-buttons {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<link rel="stylesheet" type="text/css" href="mvs-stories.css" />
|
||||
<script type="text/javascript" src="mvs-stories.js"></script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<!-- the context-name parameter is optional and useful when embedding multiple stories in a single page -->
|
||||
<div id="viewer">
|
||||
<mvs-stories-viewer context-name="story1" />
|
||||
</div>
|
||||
<div id="controls">
|
||||
<mvs-stories-snapshot-markdown context-name="story1" style="flex-grow: 1;" />
|
||||
</div>
|
||||
|
||||
<script>
|
||||
var urlParams = new URLSearchParams(window.location.search);
|
||||
var storyUrl = urlParams.get('story-url');
|
||||
var format = urlParams.get('data-format');
|
||||
|
||||
// For testing purposes:
|
||||
// if (!storyUrl) {
|
||||
// storyUrl = 'https://raw.githubusercontent.com/molstar/molstar/master/examples/mvs/kinase-story.mvsj';
|
||||
// }
|
||||
|
||||
mvsStories.loadFromURL(
|
||||
storyUrl,
|
||||
{ format: format || 'mvsj', contextName: 'story1' },
|
||||
);
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
40
src/apps/mvs-stories/index.tsx
Normal file
40
src/apps/mvs-stories/index.tsx
Normal file
@@ -0,0 +1,40 @@
|
||||
/**
|
||||
* Copyright (c) 2025 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
*/
|
||||
|
||||
import { getMVSStoriesContext } from './context';
|
||||
import './elements';
|
||||
import { MVSData } from '../../extensions/mvs/mvs-data';
|
||||
|
||||
import './favicon.ico';
|
||||
import '../../mol-plugin-ui/skin/light.scss';
|
||||
import './styles.scss';
|
||||
import './index.html';
|
||||
|
||||
export function getContext(name?: string) {
|
||||
return getMVSStoriesContext({ name });
|
||||
}
|
||||
|
||||
export function loadFromURL(url: string, options?: { format: 'mvsx' | 'mvsj', contextName?: string }) {
|
||||
setTimeout(() => {
|
||||
getContext(options?.contextName).dispatch({
|
||||
kind: 'load-mvs',
|
||||
format: options?.format ?? 'mvsj',
|
||||
url,
|
||||
});
|
||||
}, 0);
|
||||
}
|
||||
|
||||
export function loadFromData(data: MVSData | string | Uint8Array, options?: { format: 'mvsx' | 'mvsj', contextName?: string }) {
|
||||
setTimeout(() => {
|
||||
getContext(options?.contextName).dispatch({
|
||||
kind: 'load-mvs',
|
||||
format: options?.format ?? 'mvsj',
|
||||
data,
|
||||
});
|
||||
}, 0);
|
||||
}
|
||||
|
||||
export { MVSData };
|
||||
66
src/apps/mvs-stories/readme.md
Normal file
66
src/apps/mvs-stories/readme.md
Normal file
@@ -0,0 +1,66 @@
|
||||
# MolViewSpec Stories App
|
||||
|
||||
An app that defines `mvs-stories-snapshot-markdown` and `mvs-stories-viewer` web components that can be used to view MolViewSpec molecular stories.
|
||||
|
||||
See the [mvs-stories](../../examples/mvs-stories) example that includes specific stories.
|
||||
|
||||
### Usage
|
||||
|
||||
- Get `mvs-stories.css` and `mvs-stories.js` from `build/mvs-stories` and include these to your HTML page
|
||||
|
||||
```html
|
||||
<link rel="stylesheet" type="text/css" href="mvs-stories.css" />
|
||||
<script type="text/javascript" src="mvs-stories.js"></script>
|
||||
```
|
||||
|
||||
Can also use `https://cdn.jsdelivr.net/npm/molstar@latest/build/mvs-stories/mvs-stories.js` (and `.css`). `latest` can be substituted by specific version.
|
||||
|
||||
- Place the components in your page wrapper in `<div>` elements to set up positioning:
|
||||
|
||||
```html
|
||||
<div class="viewer">
|
||||
<mvs-stories-viewer />
|
||||
</div>
|
||||
<div class="snapshot">
|
||||
<mvs-stories-snapshot-markdown />
|
||||
</div>
|
||||
```
|
||||
|
||||
- Load MolViewSpec state:
|
||||
|
||||
```html
|
||||
<script>
|
||||
mvsStories.loadFromURL('https://raw.githubusercontent.com/molstar/molstar/master/examples/mvs/1cbs.mvsj');
|
||||
</script>
|
||||
```
|
||||
|
||||
- See [index.html](./index.html) for full example of how to embed the app.
|
||||
|
||||
- For interactive development build (for production use `npm run build`) of the example that immediately reflects changes use:
|
||||
|
||||
```bash
|
||||
npm run dev -- -a mvs-stories
|
||||
```
|
||||
|
||||
### Multiple Stories on a Single Page
|
||||
|
||||
To support multiple instances of stories, use the `context-name='unique-name'` attribute on the `mvs-` components together with `loadFromURL/Data(..., { contextName: 'unique-name' })`.
|
||||
|
||||
For example (simplified to not include layout):
|
||||
|
||||
```html
|
||||
<div>
|
||||
<mvs-stories-viewer context-name="1" />
|
||||
<mvs-stories-snapshot-markdown context-name="1" />
|
||||
</div>
|
||||
<div>
|
||||
<mvs-stories-viewer context-name="2" />
|
||||
<mvs-stories-snapshot-markdown context-name="2" />
|
||||
</div>
|
||||
|
||||
<script>
|
||||
mvsStories.loadFromURL('1.mvsj', { format: 'mvsj', contextName: '1' });
|
||||
mvsStories.loadFromURL('2.mvsj', { format: 'mvsj', contextName: '2' });
|
||||
</script>
|
||||
|
||||
```
|
||||
@@ -1,22 +1,4 @@
|
||||
.select-story {
|
||||
select {
|
||||
width: 100%;
|
||||
display: inline-block;
|
||||
height: 38px;
|
||||
padding: 0 8px;
|
||||
color: #555;
|
||||
line-height: 38px;
|
||||
text-decoration: none;
|
||||
white-space: nowrap;
|
||||
background-color: transparent;
|
||||
border-radius: 4px;
|
||||
border: 1px solid #bbb;
|
||||
cursor: pointer;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
}
|
||||
|
||||
.markdown-explanation {
|
||||
.mvs-stories-markdown-explanation {
|
||||
// Adapted from skeleton.css, The MIT License (MIT), Copyright (c) 2011-2014 Dave Gamache
|
||||
line-height: 1.4;
|
||||
font-weight: 400;
|
||||
@@ -179,4 +161,14 @@
|
||||
border-width: 0;
|
||||
border-top: 1px solid #E1E1E1;
|
||||
}
|
||||
}
|
||||
|
||||
@media (orientation:portrait) {
|
||||
.mvs-stories-markdown-explanation {
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.mvs-stories-markdown-explanation h3 {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
}
|
||||
@@ -17,7 +17,7 @@ import { QualityAssessment } from '../../extensions/model-archive/quality-assess
|
||||
import { ModelExport } from '../../extensions/model-export';
|
||||
import { Mp4Export } from '../../extensions/mp4-export';
|
||||
import { MolViewSpec } from '../../extensions/mvs/behavior';
|
||||
import { loadMVSX } from '../../extensions/mvs/components/formats';
|
||||
import { loadMVSData, loadMVSX } from '../../extensions/mvs/components/formats';
|
||||
import { loadMVS, MolstarLoadingExtension } from '../../extensions/mvs/load';
|
||||
import { MVSData } from '../../extensions/mvs/mvs-data';
|
||||
import { PDBeStructureQualityReport } from '../../extensions/pdbe';
|
||||
@@ -536,27 +536,8 @@ export class Viewer {
|
||||
/** Load MolViewSpec from `data`.
|
||||
* If `format` is 'mvsj', `data` must be a string or a Uint8Array containing a UTF8-encoded string.
|
||||
* If `format` is 'mvsx', `data` must be a Uint8Array or a string containing base64-encoded binary data prefixed with 'base64,'. */
|
||||
async loadMvsData(data: string | Uint8Array, format: 'mvsj' | 'mvsx', options?: { appendSnapshots?: boolean, keepCamera?: boolean, extensions?: MolstarLoadingExtension<any>[] }) {
|
||||
if (typeof data === 'string' && data.startsWith('base64')) {
|
||||
data = Uint8Array.from(atob(data.substring(7)), c => c.charCodeAt(0)); // Decode base64 string to Uint8Array
|
||||
}
|
||||
if (format === 'mvsj') {
|
||||
if (typeof data !== 'string') {
|
||||
data = new TextDecoder().decode(data); // Decode Uint8Array to string using UTF8
|
||||
}
|
||||
const mvsData = MVSData.fromMVSJ(data);
|
||||
await loadMVS(this.plugin, mvsData, { sanityChecks: true, sourceUrl: undefined, ...options });
|
||||
} else if (format === 'mvsx') {
|
||||
if (typeof data === 'string') {
|
||||
throw new Error("loadMvsData: if `format` is 'mvsx', then `data` must be a Uint8Array or a base64-encoded string prefixed with 'base64,'.");
|
||||
}
|
||||
await this.plugin.runTask(Task.create('Load MVSX file', async ctx => {
|
||||
const parsed = await loadMVSX(this.plugin, ctx, data as Uint8Array);
|
||||
await loadMVS(this.plugin, parsed.mvsData, { sanityChecks: true, sourceUrl: parsed.sourceUrl, ...options });
|
||||
}));
|
||||
} else {
|
||||
throw new Error(`Unknown MolViewSpec format: ${format}`);
|
||||
}
|
||||
loadMvsData(data: string | Uint8Array, format: 'mvsj' | 'mvsx', options?: { appendSnapshots?: boolean, keepCamera?: boolean, extensions?: MolstarLoadingExtension<any>[] }) {
|
||||
return loadMVSData(this.plugin, data, format, options);
|
||||
}
|
||||
|
||||
loadFiles(files: File[]) {
|
||||
@@ -641,7 +622,7 @@ export const ViewerAutoPreset = StructureRepresentationPresetProvider({
|
||||
|
||||
export const PluginExtensions = {
|
||||
wwPDBStructConn: wwPDBStructConnExtensionFunctions,
|
||||
mvs: { MVSData, loadMVS },
|
||||
mvs: { MVSData, loadMVS, loadMVSData },
|
||||
modelArchive: {
|
||||
qualityAssessment: {
|
||||
config: MAQualityAssessmentConfig
|
||||
|
||||
BIN
src/examples/mvs-stories/favicon.ico
Normal file
BIN
src/examples/mvs-stories/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 15 KiB |
@@ -4,6 +4,7 @@
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
|
||||
<link rel="icon" href="./favicon.ico" type="image/x-icon">
|
||||
<title>Molecular Stories</title>
|
||||
<style>
|
||||
* {
|
||||
@@ -70,32 +71,38 @@
|
||||
border-top: none;
|
||||
}
|
||||
|
||||
.markdown-explanation {
|
||||
font-size: 0.9rem !important;
|
||||
}
|
||||
|
||||
.markdown-explanation h3 {
|
||||
font-size: 1.5rem !important;
|
||||
}
|
||||
|
||||
.msp-viewport-controls-buttons {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.select-story select {
|
||||
width: 100%;
|
||||
display: inline-block;
|
||||
height: 38px;
|
||||
padding: 0 8px;
|
||||
color: #555;
|
||||
line-height: 38px;
|
||||
text-decoration: none;
|
||||
white-space: nowrap;
|
||||
background-color: transparent;
|
||||
border-radius: 4px;
|
||||
border: 1px solid #bbb;
|
||||
cursor: pointer;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
</style>
|
||||
<link rel="stylesheet" type="text/css" href="molstar.css" />
|
||||
<script type="text/javascript" src="./index.js"></script>
|
||||
<script type="text/javascript" src="index.js"></script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="viewer">
|
||||
<mc-viewer name="v1" />
|
||||
<mvs-stories-viewer />
|
||||
</div>
|
||||
<div id="controls">
|
||||
<div id="select-story" class="select-story"></div>
|
||||
<div class="markdown-explanation" style="flex-grow: 1;">
|
||||
<mc-snapshot-markdown viewer-name="v1" />
|
||||
</div>
|
||||
<mvs-stories-snapshot-markdown style="flex-grow: 1;" />
|
||||
</div>
|
||||
<div id="links">
|
||||
<a href="#" id="mvs-data" filename="kinase-story.mvsj">Download MVS State</a> | <a href="https://github.com/molstar/molstar/tree/master/src/examples/mvs-stories" id="mvs-data" target="_blank" rel="noopener noreferrer">Source Code</a>
|
||||
|
||||
@@ -4,26 +4,23 @@
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
*/
|
||||
|
||||
import { getMolComponentContext } from './context';
|
||||
import './index.html';
|
||||
import './elements/snapshot-markdown';
|
||||
import './elements/viewer';
|
||||
import '../../mol-plugin-ui/skin/light.scss';
|
||||
import './styles.scss';
|
||||
import { download } from '../../mol-util/download';
|
||||
import { BehaviorSubject } from 'rxjs';
|
||||
import { Stories } from './stories';
|
||||
import { useBehavior } from '../../mol-plugin-ui/hooks/use-behavior';
|
||||
import { createRoot } from 'react-dom/client';
|
||||
import { getMVSStoriesContext } from '../../apps/mvs-stories/context';
|
||||
import '../../apps/mvs-stories/elements';
|
||||
|
||||
export class MolComponents {
|
||||
getContext(name?: string) {
|
||||
return getMolComponentContext({ name });
|
||||
}
|
||||
import './favicon.ico';
|
||||
import '../../mol-plugin-ui/skin/light.scss';
|
||||
import '../../apps/mvs-stories/styles.scss';
|
||||
import './index.html';
|
||||
|
||||
function getContext(name?: string) {
|
||||
return getMVSStoriesContext({ name });
|
||||
}
|
||||
|
||||
const MC = new MolComponents();
|
||||
|
||||
type Story = { kind: 'built-in', id: string } | { kind: 'url', url: string, format: 'mvsx' | 'mvsj' } | undefined;
|
||||
const CurrentStory = new BehaviorSubject<Story>(undefined);
|
||||
|
||||
@@ -50,7 +47,7 @@ function init() {
|
||||
history.replaceState({}, '', '');
|
||||
} else if (story.kind === 'url') {
|
||||
history.replaceState({}, '', story ? `?story-url=${encodeURIComponent(story.url)}&data-format=${story.format}` : '');
|
||||
MC.getContext().dispatch({
|
||||
getContext().dispatch({
|
||||
kind: 'load-mvs',
|
||||
format: story.format,
|
||||
url: story.url,
|
||||
@@ -59,7 +56,7 @@ function init() {
|
||||
history.replaceState({}, '', story ? `?story=${story.id}` : '');
|
||||
const s = Stories.find(s => s.id === story.id);
|
||||
if (s) {
|
||||
MC.getContext().dispatch({
|
||||
getContext().dispatch({
|
||||
kind: 'load-mvs',
|
||||
data: s.buildStory(),
|
||||
});
|
||||
@@ -86,14 +83,13 @@ function init() {
|
||||
createRoot(document.getElementById('select-story')!).render(<SelectStoryUI subject={CurrentStory} />);
|
||||
}
|
||||
|
||||
(window as any).mc = MC;
|
||||
(window as any).downloadStory = () => {
|
||||
if (CurrentStory.value?.kind !== 'built-in') return;
|
||||
const id = CurrentStory.value.id;
|
||||
const story = Stories.find(s => s.id === id);
|
||||
if (!story) return;
|
||||
const data = JSON.stringify(story.buildStory(), null, 2);
|
||||
download(new Blob([data], { type: 'application/json' }), 'story.mvsj');
|
||||
download(new Blob([data], { type: 'application/json' }), `${id}-story.mvsj`);
|
||||
};
|
||||
(window as any).initStories = init;
|
||||
(window as any).CurrentStory = CurrentStory;
|
||||
@@ -1,10 +1,8 @@
|
||||
# MolViewSpec Stories Example
|
||||
|
||||
This example illustrates:
|
||||
This example illustrates using the `mvs-stories` app to tell molecular stories built with MolViewSpec.
|
||||
|
||||
- Using MolViewSpec to tell a story
|
||||
- A proof of concept for separating Mol* into a ready-to-use web component library.
|
||||
- Ability to load MVS states
|
||||
See the [mvs-stories](../../apps/mvs-stories) app for more info about how to use this app separately.
|
||||
|
||||
### Usage
|
||||
|
||||
@@ -16,39 +14,7 @@ This example illustrates:
|
||||
npm build
|
||||
```
|
||||
|
||||
- Get `molstar.css` and `index.js` from `build/examples/mvs-stories` and include these to your HTML page
|
||||
|
||||
```html
|
||||
<link rel="stylesheet" type="text/css" href="molstar.css" />
|
||||
<script type="text/javascript" src="index.js"></script>
|
||||
```
|
||||
|
||||
- Plate the components in your page wrapper in `<div>` elements to set up positioning:
|
||||
|
||||
```html
|
||||
<div class="viewer">
|
||||
<mc-viewer name="v1" />
|
||||
</div>
|
||||
<div class="snapshot">
|
||||
<mc-snapshot-markdown viewer-name="v1" />
|
||||
</div>
|
||||
```
|
||||
|
||||
- Load MolViewSpec state:
|
||||
|
||||
```html
|
||||
<script>
|
||||
window.mc.getContext().dispatch({
|
||||
kind: 'load-mvs',
|
||||
format: 'mvsj',
|
||||
url: 'https://path/to/file.mvsj',
|
||||
// or provide data directly
|
||||
// data: mvsJSON
|
||||
});
|
||||
</script>
|
||||
```
|
||||
|
||||
See [index.html](./index.html) for a full example.
|
||||
- See [index.html](./index.html) for example usage.
|
||||
|
||||
- For interactive development build (for production use `npm run build`) of the example that immediately reflects changes use:
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ import { RuntimeContext, Task } from '../../../mol-task';
|
||||
import { Asset, AssetManager } from '../../../mol-util/assets';
|
||||
import { ParamDefinition as PD } from '../../../mol-util/param-definition';
|
||||
import { unzip } from '../../../mol-util/zip/zip';
|
||||
import { loadMVS } from '../load';
|
||||
import { loadMVS, MVSLoadOptions } from '../load';
|
||||
import { MVSData } from '../mvs-data';
|
||||
import { MVSTransform } from './annotation-structure-component';
|
||||
|
||||
@@ -131,6 +131,36 @@ export async function loadMVSX(plugin: PluginContext, runtimeCtx: RuntimeContext
|
||||
return { mvsData, sourceUrl };
|
||||
}
|
||||
|
||||
export async function loadMVSData(plugin: PluginContext, data: MVSData | StringLike | Uint8Array, format: 'mvsj' | 'mvsx', options?: MVSLoadOptions) {
|
||||
if (typeof data === 'string' && data.startsWith('base64')) {
|
||||
data = Uint8Array.from(atob(data.substring(7)), c => c.charCodeAt(0)); // Decode base64 string to Uint8Array
|
||||
}
|
||||
|
||||
if (format === 'mvsj') {
|
||||
if ((data as Uint8Array).BYTES_PER_ELEMENT && (data as Uint8Array).buffer) {
|
||||
data = new TextDecoder().decode(data as Uint8Array); // Decode Uint8Array to string using UTF8
|
||||
}
|
||||
|
||||
let mvsData: MVSData;
|
||||
if (typeof data === 'string') {
|
||||
mvsData = MVSData.fromMVSJ(data);
|
||||
} else {
|
||||
mvsData = data as MVSData;
|
||||
}
|
||||
await loadMVS(plugin, mvsData, { sanityChecks: true, sourceUrl: undefined, ...options });
|
||||
} else if (format === 'mvsx') {
|
||||
if (typeof data === 'string') {
|
||||
throw new Error("loadMvsData: if `format` is 'mvsx', then `data` must be a Uint8Array or a base64-encoded string prefixed with 'base64,'.");
|
||||
}
|
||||
await plugin.runTask(Task.create('Load MVSX file', async ctx => {
|
||||
const parsed = await loadMVSX(plugin, ctx, data as Uint8Array);
|
||||
await loadMVS(plugin, parsed.mvsData, { sanityChecks: true, sourceUrl: parsed.sourceUrl, ...options });
|
||||
}));
|
||||
} else {
|
||||
throw new Error(`Unknown MolViewSpec format: ${format}`);
|
||||
}
|
||||
}
|
||||
|
||||
/** If the PluginStateObject `pso` comes from a Download transform, try to get its `url` parameter. */
|
||||
function tryGetDownloadUrl(pso: SO.Data.String, plugin: PluginContext): string | undefined {
|
||||
const theCell = plugin.state.data.selectQ(q => q.ofTransformer(Download)).find(cell => cell.obj === pso);
|
||||
|
||||
@@ -207,7 +207,8 @@ class StateTreeNodeLabel extends PluginUIComponent<{ cell: StateObjectCell, dept
|
||||
setCurrentRoot = (e?: React.MouseEvent<HTMLElement>) => {
|
||||
e?.preventDefault();
|
||||
e?.currentTarget.blur();
|
||||
PluginCommands.State.SetCurrentObject(this.plugin, { state: this.props.cell.parent!, ref: StateTransform.RootRef });
|
||||
if (!this.props.cell.parent) return;
|
||||
PluginCommands.State.SetCurrentObject(this.plugin, { state: this.props.cell.parent, ref: StateTransform.RootRef });
|
||||
};
|
||||
|
||||
remove = (e?: React.MouseEvent<HTMLElement>) => {
|
||||
|
||||
@@ -13,61 +13,63 @@ class VersionFilePlugin {
|
||||
}
|
||||
}
|
||||
|
||||
const sharedConfig = {
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.(html|ico)$/,
|
||||
use: [{
|
||||
loader: 'file-loader',
|
||||
options: { name: '[name].[ext]' }
|
||||
}]
|
||||
},
|
||||
{
|
||||
test: /\.(s*)css$/,
|
||||
use: [
|
||||
MiniCssExtractPlugin.loader,
|
||||
{ loader: 'css-loader', options: { sourceMap: false } },
|
||||
{ loader: 'sass-loader', options: { sourceMap: false } },
|
||||
]
|
||||
},
|
||||
{
|
||||
test: /\.(jpg)$/i,
|
||||
type: 'asset/resource',
|
||||
},
|
||||
]
|
||||
},
|
||||
plugins: [
|
||||
new ExtraWatchWebpackPlugin({
|
||||
files: [
|
||||
'./lib/**/*.scss',
|
||||
'./lib/**/*.html'
|
||||
],
|
||||
}),
|
||||
new webpack.DefinePlugin({
|
||||
'process.env.DEBUG': JSON.stringify(process.env.DEBUG),
|
||||
'__MOLSTAR_DEBUG_TIMESTAMP__': webpack.DefinePlugin.runtimeValue(() => `${new Date().valueOf()}`, true)
|
||||
}),
|
||||
new MiniCssExtractPlugin({ filename: 'molstar.css' }),
|
||||
new VersionFilePlugin(),
|
||||
],
|
||||
resolve: {
|
||||
modules: [
|
||||
'node_modules',
|
||||
path.resolve(__dirname, 'lib/')
|
||||
function sharedConfig(options) {
|
||||
return {
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.(html|ico)$/,
|
||||
use: [{
|
||||
loader: 'file-loader',
|
||||
options: { name: '[name].[ext]' }
|
||||
}]
|
||||
},
|
||||
{
|
||||
test: /\.(s*)css$/,
|
||||
use: [
|
||||
MiniCssExtractPlugin.loader,
|
||||
{ loader: 'css-loader', options: { sourceMap: false } },
|
||||
{ loader: 'sass-loader', options: { sourceMap: false } },
|
||||
]
|
||||
},
|
||||
{
|
||||
test: /\.(jpg)$/i,
|
||||
type: 'asset/resource',
|
||||
},
|
||||
]
|
||||
},
|
||||
plugins: [
|
||||
new ExtraWatchWebpackPlugin({
|
||||
files: [
|
||||
'./lib/**/*.scss',
|
||||
'./lib/**/*.html'
|
||||
],
|
||||
}),
|
||||
new webpack.DefinePlugin({
|
||||
'process.env.DEBUG': JSON.stringify(process.env.DEBUG),
|
||||
'__MOLSTAR_DEBUG_TIMESTAMP__': webpack.DefinePlugin.runtimeValue(() => `${new Date().valueOf()}`, true)
|
||||
}),
|
||||
new MiniCssExtractPlugin({ filename: (options && options.cssFilename) || 'molstar.css' }),
|
||||
new VersionFilePlugin(),
|
||||
],
|
||||
fallback: {
|
||||
fs: false,
|
||||
vm: false,
|
||||
buffer: false,
|
||||
crypto: require.resolve('crypto-browserify'),
|
||||
path: require.resolve('path-browserify'),
|
||||
stream: require.resolve('stream-browserify'),
|
||||
resolve: {
|
||||
modules: [
|
||||
'node_modules',
|
||||
path.resolve(__dirname, 'lib/')
|
||||
],
|
||||
fallback: {
|
||||
fs: false,
|
||||
vm: false,
|
||||
buffer: false,
|
||||
crypto: require.resolve('crypto-browserify'),
|
||||
path: require.resolve('path-browserify'),
|
||||
stream: require.resolve('stream-browserify'),
|
||||
}
|
||||
},
|
||||
watchOptions: {
|
||||
aggregateTimeout: 750
|
||||
}
|
||||
},
|
||||
watchOptions: {
|
||||
aggregateTimeout: 750
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
function createEntry(src, outFolder, outFilename, isNode) {
|
||||
@@ -75,15 +77,16 @@ function createEntry(src, outFolder, outFilename, isNode) {
|
||||
target: isNode ? 'node' : void 0,
|
||||
entry: path.resolve(__dirname, `lib/${src}.js`),
|
||||
output: { filename: `${outFilename}.js`, path: path.resolve(__dirname, `build/${outFolder}`) },
|
||||
...sharedConfig
|
||||
...sharedConfig()
|
||||
};
|
||||
}
|
||||
|
||||
function createEntryPoint(name, dir, out, library) {
|
||||
function createEntryPoint(name, dir, out, library, options) {
|
||||
const filename = options && options.filename ? options.filename : `${library || name}.js`;
|
||||
return {
|
||||
entry: path.resolve(__dirname, `lib/${dir}/${name}.js`),
|
||||
output: { filename: `${library || name}.js`, path: path.resolve(__dirname, `build/${out}`), library: library || out, libraryTarget: 'umd', assetModuleFilename: 'images/[hash][ext][query]', 'publicPath': '' },
|
||||
...sharedConfig
|
||||
output: { filename: filename, path: path.resolve(__dirname, `build/${out}`), library: library || out, libraryTarget: 'umd', assetModuleFilename: 'images/[hash][ext][query]', 'publicPath': '' },
|
||||
...sharedConfig(options)
|
||||
};
|
||||
}
|
||||
|
||||
@@ -97,11 +100,11 @@ function createNodeEntryPoint(name, dir, out) {
|
||||
'node-fetch': 'require("node-fetch")',
|
||||
'util.promisify': 'require("util.promisify")',
|
||||
},
|
||||
...sharedConfig
|
||||
...sharedConfig()
|
||||
};
|
||||
}
|
||||
|
||||
function createApp(name, library) { return createEntryPoint('index', `apps/${name}`, name, library); }
|
||||
function createApp(name, library, options) { return createEntryPoint('index', `apps/${name}`, name, library, options); }
|
||||
function createExample(name) { return createEntry(`examples/${name}/index`, `examples/${name}`, 'index'); }
|
||||
function createBrowserTest(name) { return createEntryPoint(name, 'tests/browser', 'tests'); }
|
||||
function createNodeApp(name) { return createNodeEntryPoint('index', `apps/${name}`, name); }
|
||||
|
||||
@@ -22,6 +22,7 @@ module.exports = [
|
||||
createApp('viewer', 'molstar'),
|
||||
createApp('docking-viewer', 'molstar'),
|
||||
createApp('mesoscale-explorer', 'molstar'),
|
||||
createApp('mvs-stories', 'mvsStories', { filename: 'mvs-stories.js', cssFilename: 'mvs-stories.css' }),
|
||||
...examples.map(createExample),
|
||||
...tests.map(createBrowserTest)
|
||||
];
|
||||
|
||||
@@ -15,5 +15,6 @@ module.exports = [
|
||||
createApp('viewer', 'molstar'),
|
||||
createApp('docking-viewer', 'molstar'),
|
||||
createApp('mesoscale-explorer', 'molstar'),
|
||||
createApp('mvs-stories', 'mvsStories', { filename: 'mvs-stories.js', cssFilename: 'mvs-stories.css' }),
|
||||
...examples.map(createExample)
|
||||
];
|
||||
Reference in New Issue
Block a user