lhasa-ligand-builder 0.1.0 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (80) hide show
  1. package/README.md +111 -12
  2. package/bundler_plugins/utils.mjs +15 -0
  3. package/bundler_plugins/vite.mjs +80 -0
  4. package/bundler_plugins/webpack.cjs +83 -0
  5. package/dist/Lhasa.d.ts +19 -0
  6. package/dist/LhasaEmbedder.d.ts +10 -0
  7. package/{public → dist/assets}/Components-inchikey.ich +706 -41
  8. package/dist/assets/lhasa.js +2 -0
  9. package/dist/assets/lhasa.wasm +0 -0
  10. package/dist/bansu_integration.d.ts +8 -0
  11. package/dist/inchikey_database_parse.d.ts +1 -0
  12. package/{src → dist}/index.d.ts +1 -2
  13. package/dist/lhasa-ligand-builder.css +1 -0
  14. package/dist/lhasa-ligand-builder.js +33378 -0
  15. package/dist/lib.d.ts +5 -0
  16. package/dist/main.d.ts +1 -0
  17. package/dist/qed_property_infobox.d.ts +7 -0
  18. package/{src → dist}/types.d.ts +11 -19
  19. package/package.json +33 -4
  20. package/.eslintrc.cjs +0 -18
  21. package/index.html +0 -49
  22. package/public/.gitkeep +0 -0
  23. package/public/lhasa.js +0 -2
  24. package/public/lhasa.wasm +0 -0
  25. package/public/react.svg +0 -1
  26. package/src/Lhasa.tsx +0 -1452
  27. package/src/assets/.gitkeep +0 -0
  28. package/src/bansu_integration.tsx +0 -315
  29. package/src/customize_mui.scss +0 -97
  30. package/src/inchikey_database_parse.tsx +0 -20
  31. package/src/index.scss +0 -352
  32. package/src/main.tsx +0 -79
  33. package/src/qed_property_infobox.tsx +0 -14
  34. package/tsconfig.json +0 -25
  35. package/tsconfig.node.json +0 -10
  36. package/vite.config.ts +0 -55
  37. /package/{public → dist/assets}/icons/README +0 -0
  38. /package/{public → dist/assets}/icons/dark/layla_3c.svg +0 -0
  39. /package/{public → dist/assets}/icons/dark/layla_4c.svg +0 -0
  40. /package/{public → dist/assets}/icons/dark/layla_5c.svg +0 -0
  41. /package/{public → dist/assets}/icons/dark/layla_6arom.svg +0 -0
  42. /package/{public → dist/assets}/icons/dark/layla_6c.svg +0 -0
  43. /package/{public → dist/assets}/icons/dark/layla_7c.svg +0 -0
  44. /package/{public → dist/assets}/icons/dark/layla_8c.svg +0 -0
  45. /package/{public → dist/assets}/icons/dark/layla_charge_tool.svg +0 -0
  46. /package/{public → dist/assets}/icons/dark/layla_delete_hydrogens_tool.svg +0 -0
  47. /package/{public → dist/assets}/icons/dark/layla_double_bond.svg +0 -0
  48. /package/{public → dist/assets}/icons/dark/layla_format_tool.svg +0 -0
  49. /package/{public → dist/assets}/icons/dark/layla_geometry_tool.svg +0 -0
  50. /package/{public → dist/assets}/icons/dark/layla_key.svg +0 -0
  51. /package/{public → dist/assets}/icons/dark/layla_move_tool.svg +0 -0
  52. /package/{public → dist/assets}/icons/dark/layla_single_bond.svg +0 -0
  53. /package/{public → dist/assets}/icons/dark/layla_triple_bond.svg +0 -0
  54. /package/{public → dist/assets}/icons/dark/lhasa_delete_tool.svg +0 -0
  55. /package/{public → dist/assets}/icons/dark/lhasa_flip_x_tool.svg +0 -0
  56. /package/{public → dist/assets}/icons/dark/lhasa_flip_y_tool.svg +0 -0
  57. /package/{public → dist/assets}/icons/dark/lhasa_rotate_tool.svg +0 -0
  58. /package/{public → dist/assets}/icons/icons/hicolor_apps_scalable_coot-layla.svg +0 -0
  59. /package/{public → dist/assets}/icons/layla_3c.svg +0 -0
  60. /package/{public → dist/assets}/icons/layla_4c.svg +0 -0
  61. /package/{public → dist/assets}/icons/layla_5c.svg +0 -0
  62. /package/{public → dist/assets}/icons/layla_6arom.svg +0 -0
  63. /package/{public → dist/assets}/icons/layla_6c.svg +0 -0
  64. /package/{public → dist/assets}/icons/layla_7c.svg +0 -0
  65. /package/{public → dist/assets}/icons/layla_8c.svg +0 -0
  66. /package/{public → dist/assets}/icons/layla_charge_tool.svg +0 -0
  67. /package/{public → dist/assets}/icons/layla_delete_hydrogens_tool.svg +0 -0
  68. /package/{public → dist/assets}/icons/layla_double_bond.svg +0 -0
  69. /package/{public → dist/assets}/icons/layla_format_tool.svg +0 -0
  70. /package/{public → dist/assets}/icons/layla_geometry_tool-dark.svg +0 -0
  71. /package/{public → dist/assets}/icons/layla_geometry_tool.svg +0 -0
  72. /package/{public → dist/assets}/icons/layla_key.svg +0 -0
  73. /package/{public → dist/assets}/icons/layla_move_tool.svg +0 -0
  74. /package/{public → dist/assets}/icons/layla_single_bond.svg +0 -0
  75. /package/{public → dist/assets}/icons/layla_triple_bond.svg +0 -0
  76. /package/{public → dist/assets}/icons/lhasa_delete_tool.svg +0 -0
  77. /package/{public → dist/assets}/icons/lhasa_flip_x_tool.svg +0 -0
  78. /package/{public → dist/assets}/icons/lhasa_flip_y_tool.svg +0 -0
  79. /package/{public → dist/assets}/icons/lhasa_rotate_tool.svg +0 -0
  80. /package/{src → dist}/vite-env.d.ts +0 -0
package/README.md CHANGED
@@ -1,21 +1,111 @@
1
1
  # LhasaReact
2
2
 
3
- Moorhen's frontend for Lhasa - web version of Coot's ligand builder
3
+ Frontend for Lhasa - Moorhen's ligand builder: a React + WebAssembly version of Layla (Coot's ligand builder).
4
4
 
5
5
  ## Installation / embedding
6
6
 
7
- NPM package is coming soon.
8
- Before that happens, you have to do the build yourself.
7
+ There is an experimental [`lhasa-ligand-builder`](https://www.npmjs.com/package/lhasa-ligand-builder) package.
9
8
 
10
- ## How to build and run
9
+ ### Quick start
10
+
11
+ You can install the package with:
12
+
13
+ ```bash
14
+ npm install lhasa-ligand-builder
15
+ ```
16
+
17
+ Then, embed Lhasa via the `LhasaEmbedder` component:
18
+
19
+ ```tsx
20
+ import { LhasaEmbedder } from 'lhasa-ligand-builder'
21
+ import 'lhasa-ligand-builder/style.css'
22
+
23
+ function App() {
24
+ return <LhasaEmbedder assetsBaseUrl="/lhasa-assets/" />
25
+ }
26
+ ```
27
+
28
+ ### Asset setup
29
+
30
+ Lhasa requires several runtime assets (`lhasa.js`, `lhasa.wasm`, `Components-inchikey.ich`, and the `icons/` directory) to be served by your web server. Use one of the bundler plugins below to handle this automatically, or copy the assets manually.
31
+
32
+ #### Vite
33
+
34
+ ```js
35
+ // vite.config.js
36
+ import lhasaCopyAssets from 'lhasa-ligand-builder/lhasa-vite-plugin'
37
+
38
+ export default {
39
+ plugins: [lhasaCopyAssets()],
40
+ }
41
+ ```
42
+
43
+ During development, the plugin serves assets via middleware. In production builds, it copies them into your output directory under `lhasa-assets/`.
44
+
45
+ #### Webpack
46
+
47
+ ```js
48
+ // webpack.config.js
49
+ const LhasaCopyAssetsPlugin = require('lhasa-ligand-builder/lhasa-webpack-plugin')
50
+
51
+ module.exports = {
52
+ plugins: [new LhasaCopyAssetsPlugin()],
53
+ devServer: {
54
+ static: [LhasaCopyAssetsPlugin.devServerStatic()],
55
+ },
56
+ }
57
+ ```
58
+
59
+ #### Plugin options
60
+
61
+ Both plugins accept an options object:
62
+
63
+ | Option | Default | Description |
64
+ |--------|---------|-------------|
65
+ | `outputPath` | `'lhasa-assets'` | Sub-path in the build output where assets are placed. |
66
+ | `assets` | All entries | Array of asset names to copy: `'lhasa.js'`, `'lhasa.wasm'`, `'Components-inchikey.ich'`, `'icons'`. |
67
+
68
+ #### Manual copy (fallback)
69
+
70
+ If you're not using Vite or Webpack, copy the assets from `node_modules/lhasa-ligand-builder/dist/assets/` to your public directory (e.g. `public/lhasa-assets/`).
71
+
72
+ ### Cross-origin isolation
73
+
74
+ Lhasa's WASM module uses `SharedArrayBuffer`, which requires your page to be served with these HTTP headers:
75
+
76
+ ```
77
+ Cross-Origin-Opener-Policy: same-origin
78
+ Cross-Origin-Embedder-Policy: require-corp
79
+ ```
80
+
81
+ Without these headers, the WASM module will fail to load.
82
+ During development with Vite, the bundled `vite-plugin-cross-origin-isolation` plugin handles this automatically.
83
+
84
+ ### Advanced: using `LhasaComponent` directly
85
+
86
+ If you need full control over WASM loading (e.g. you already have a `MainModule` instance), use `LhasaComponent` directly:
87
+
88
+ ```tsx
89
+ import { LhasaComponent } from 'lhasa-ligand-builder'
90
+ import 'lhasa-ligand-builder/style.css'
91
+
92
+ // `lhasaModule` is a MainModule instance you've initialized yourself
93
+ <LhasaComponent
94
+ Lhasa={lhasaModule}
95
+ icons_path_prefix="/path/to/icons"
96
+ data_path_prefix="/path/to/data/"
97
+ />
98
+ ```
99
+
100
+ ## Building from source
11
101
 
12
102
  LhasaReact can be used standalone, outside of Moorhen.
13
103
 
14
104
  Lhasa is part of [Coot](https://github.com/pemsley/coot) and you need to compile the C++ WebAssembly module first.
15
105
 
16
- NOTE: All build scripts are Unix scripts. On Windows, you might need to use WSL or MinGW's shell (if it works with Emscripten?)
106
+ NOTE: All build scripts are Unix scripts. On Windows, you may need WSL.
17
107
 
18
- ### Building WebAssembly module
108
+ ### Building the WebAssembly module
19
109
 
20
110
  #### Tools you will need
21
111
 
@@ -29,13 +119,22 @@ The build procedure is very much like [Moorhen](https://github.com/moorhen-coot/
29
119
 
30
120
  * Run `get_sources` (download C++ dependencies)
31
121
  * Run `initial_build.sh` to build all the necessary dependencies using Emscripten
32
- * Run `build_lhasa.sh` to build Lhasa WebAssembly module
122
+ * Run `build_lhasa.sh` to build the Lhasa WebAssembly module
33
123
  * Copy `lhasa.js`, `lhasa.worker.js` (if it exists) and `lhasa.wasm` from `Coot/lhasa/lhbuild/` to `LhasaReact/public`
34
124
 
35
- ### Running LhasaReact
125
+ ### Running LhasaReact locally
126
+
127
+ After building and copying the WebAssembly module:
128
+
129
+ ```bash
130
+ npm install
131
+ npm run dev
132
+ ```
133
+
134
+ ### Building the library
36
135
 
37
- After you had built and copied the WebAssembly module, you can launch LhasaReact (you'll need Node.JS / npm):
136
+ ```bash
137
+ npm run build:lib
138
+ ```
38
139
 
39
- * Get JS dependencies: `npm install`
40
- * Run `npx vite serve`
41
- * Done!
140
+ This produces `dist/` with the ESM bundle, CSS, type declarations, and all WASM/icon/data assets.
@@ -0,0 +1,15 @@
1
+ import { dirname, resolve } from 'path';
2
+ import { fileURLToPath } from 'url';
3
+
4
+ const __dirname = dirname(fileURLToPath(import.meta.url));
5
+
6
+ /** Absolute path to the package's dist/assets/ directory. */
7
+ export const ASSETS_DIR = resolve(__dirname, '..', 'dist', 'assets');
8
+
9
+ /** Top-level entries to copy from dist/assets/. */
10
+ export const ASSET_ENTRIES = [
11
+ 'lhasa.js',
12
+ 'lhasa.wasm',
13
+ 'Components-inchikey.ich',
14
+ 'icons',
15
+ ];
@@ -0,0 +1,80 @@
1
+ import { cpSync, existsSync, createReadStream, statSync, mkdirSync } from 'fs';
2
+ import { resolve, extname } from 'path';
3
+ import { ASSETS_DIR, ASSET_ENTRIES } from './utils.mjs';
4
+
5
+ const MIME_TYPES = {
6
+ '.js': 'application/javascript',
7
+ '.wasm': 'application/wasm',
8
+ '.ich': 'application/octet-stream',
9
+ '.svg': 'image/svg+xml',
10
+ };
11
+
12
+ /**
13
+ * Vite plugin that copies Lhasa assets into the build output
14
+ * and serves them during development.
15
+ *
16
+ * @param {Object} [options]
17
+ * @param {string} [options.outputPath='lhasa-assets'] Sub-path within the build output where assets are placed.
18
+ * @param {string[]} [options.assets] Which asset entries to include. Defaults to all.
19
+ * @returns {import('vite').Plugin}
20
+ */
21
+ export default function lhasaCopyAssets(options = {}) {
22
+ const outputPath = options.outputPath ?? 'lhasa-assets';
23
+ const assetFilter = options.assets ?? ASSET_ENTRIES;
24
+ let resolvedOutDir;
25
+
26
+ return {
27
+ name: 'lhasa-copy-assets',
28
+
29
+ configResolved(config) {
30
+ resolvedOutDir = config.build.outDir;
31
+ },
32
+
33
+ // Dev server: serve assets directly from dist/assets/ via middleware
34
+ configureServer(server) {
35
+ const urlPrefix = '/' + outputPath;
36
+
37
+ server.middlewares.use((req, res, next) => {
38
+ if (!req.url || !req.url.startsWith(urlPrefix + '/')) {
39
+ return next();
40
+ }
41
+
42
+ const relativePath = req.url.slice(urlPrefix.length + 1);
43
+ const filePath = resolve(ASSETS_DIR, relativePath);
44
+
45
+ // Prevent path traversal outside assets dir
46
+ if (!filePath.startsWith(ASSETS_DIR)) {
47
+ return next();
48
+ }
49
+
50
+ if (!existsSync(filePath) || statSync(filePath).isDirectory()) {
51
+ return next();
52
+ }
53
+
54
+ const ext = extname(filePath);
55
+ res.setHeader('Content-Type', MIME_TYPES[ext] || 'application/octet-stream');
56
+ createReadStream(filePath).pipe(res);
57
+ });
58
+ },
59
+
60
+ // Production build: copy assets into the output directory
61
+ closeBundle() {
62
+ if (!resolvedOutDir) return;
63
+
64
+ const destDir = resolve(resolvedOutDir, outputPath);
65
+ mkdirSync(destDir, { recursive: true });
66
+
67
+ for (const entry of assetFilter) {
68
+ const src = resolve(ASSETS_DIR, entry);
69
+ const dest = resolve(destDir, entry);
70
+ if (!existsSync(src)) {
71
+ console.warn(`[lhasa-copy-assets] Asset not found: ${src}`);
72
+ continue;
73
+ }
74
+ cpSync(src, dest, { recursive: true });
75
+ }
76
+
77
+ console.log(`[lhasa-copy-assets] Copied assets to ${destDir}`);
78
+ },
79
+ };
80
+ }
@@ -0,0 +1,83 @@
1
+ const path = require('path');
2
+ const fs = require('fs');
3
+
4
+ const ASSETS_DIR = path.resolve(__dirname, '..', 'dist', 'assets');
5
+
6
+ const ASSET_ENTRIES = [
7
+ 'lhasa.js',
8
+ 'lhasa.wasm',
9
+ 'Components-inchikey.ich',
10
+ 'icons',
11
+ ];
12
+
13
+ /**
14
+ * Webpack plugin that copies Lhasa assets into the build output.
15
+ *
16
+ * @example
17
+ * // webpack.config.js
18
+ * const LhasaCopyAssetsPlugin = require('lhasa-ligand-builder/lhasa-webpack-plugin');
19
+ * module.exports = {
20
+ * plugins: [new LhasaCopyAssetsPlugin()],
21
+ * devServer: {
22
+ * static: [LhasaCopyAssetsPlugin.devServerStatic()],
23
+ * },
24
+ * };
25
+ */
26
+ class LhasaCopyAssetsPlugin {
27
+ /**
28
+ * @param {Object} [options]
29
+ * @param {string} [options.outputPath='lhasa-assets'] Sub-path within output.path where assets are placed.
30
+ * @param {string[]} [options.assets] Which asset entries to include. Defaults to all.
31
+ */
32
+ constructor(options = {}) {
33
+ this.outputPath = options.outputPath || 'lhasa-assets';
34
+ this.assets = options.assets || ASSET_ENTRIES;
35
+ }
36
+
37
+ apply(compiler) {
38
+ const pluginName = 'LhasaCopyAssetsPlugin';
39
+
40
+ compiler.hooks.afterEmit.tapAsync(pluginName, (compilation, callback) => {
41
+ const outputDir = compilation.outputOptions.path;
42
+ const destDir = path.resolve(outputDir, this.outputPath);
43
+
44
+ try {
45
+ fs.mkdirSync(destDir, { recursive: true });
46
+
47
+ for (const entry of this.assets) {
48
+ const src = path.resolve(ASSETS_DIR, entry);
49
+ const dest = path.resolve(destDir, entry);
50
+ if (!fs.existsSync(src)) {
51
+ compilation.warnings.push(
52
+ new Error(`[${pluginName}] Asset not found: ${src}`)
53
+ );
54
+ continue;
55
+ }
56
+ fs.cpSync(src, dest, { recursive: true });
57
+ }
58
+
59
+ console.log(`[${pluginName}] Copied assets to ${destDir}`);
60
+ } catch (err) {
61
+ compilation.errors.push(err);
62
+ }
63
+
64
+ callback();
65
+ });
66
+ }
67
+
68
+ /**
69
+ * Returns a devServer.static entry for webpack-dev-server
70
+ * so assets are served during development.
71
+ *
72
+ * @param {string} [outputPath='lhasa-assets']
73
+ * @returns {{ directory: string, publicPath: string }}
74
+ */
75
+ static devServerStatic(outputPath = 'lhasa-assets') {
76
+ return {
77
+ directory: ASSETS_DIR,
78
+ publicPath: '/' + outputPath,
79
+ };
80
+ }
81
+ }
82
+
83
+ module.exports = LhasaCopyAssetsPlugin;
@@ -0,0 +1,19 @@
1
+ import { MainModule } from './types';
2
+ export interface LhasaComponentProps {
3
+ Lhasa: MainModule | any;
4
+ show_top_panel?: boolean;
5
+ show_footer?: boolean;
6
+ icons_path_prefix?: string;
7
+ rdkit_molecule_pickle_list?: {
8
+ pickle: string;
9
+ id: string;
10
+ }[];
11
+ name_of_host_program?: string;
12
+ smiles_callback?: (internal_id: number, id_from_prop: string | null, smiles: string) => void;
13
+ bansu_endpoint?: string | undefined;
14
+ data_path_prefix?: string;
15
+ dark_mode?: boolean;
16
+ max_width?: number | null;
17
+ max_height?: number | null;
18
+ }
19
+ export declare function LhasaComponent({ Lhasa, show_top_panel, show_footer, icons_path_prefix, rdkit_molecule_pickle_list, name_of_host_program, smiles_callback, bansu_endpoint, data_path_prefix, dark_mode, max_width, max_height }: LhasaComponentProps): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,10 @@
1
+ import { LhasaComponentProps } from './Lhasa';
2
+ export interface LhasaEmbedderProps extends Omit<LhasaComponentProps, 'Lhasa'> {
3
+ /** Base URL where lhasa.js, lhasa.wasm, icons/, and Components-inchikey.ich are served from. */
4
+ assetsBaseUrl?: string;
5
+ /** Custom loading indicator. Defaults to a simple "Loading Lhasa..." div. */
6
+ loadingComponent?: React.ReactNode;
7
+ /** Custom error display. Receives the Error object. */
8
+ errorComponent?: (error: Error) => React.ReactNode;
9
+ }
10
+ export declare function LhasaEmbedder({ assetsBaseUrl, loadingComponent, errorComponent, icons_path_prefix, data_path_prefix, ...lhasaProps }: LhasaEmbedderProps): import("react/jsx-runtime").JSX.Element | null;