Files
enviPy-bayer/static/js/ketcher2/script/ui/dialog/miew.jsx
2025-06-23 20:13:54 +02:00

224 lines
5.5 KiB
JavaScript

/****************************************************************************
* Copyright 2017 EPAM Systems
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
***************************************************************************/
import { camelCase } from 'lodash/fp';
import { h, Component } from 'preact';
/** @jsx h */
import Dialog from '../component/dialog';
import { storage } from '../utils';
const MIEW_PATH = '__MIEW_PATH__';
const MIEW_OPTIONS = {
preset: 'small',
settings: {
theme: 'light',
atomLabel: 'bright',
autoPreset: false,
inversePanning: true
},
reps: [{
mode: 'LN',
colorer: 'AT',
selector: 'all'
}]
};
const MIEW_WINDOW = {
location: 'no',
menubar: 'no',
toolbar: 'no',
directories: 'no',
modal: 'yes',
alwaysRaised: 'yes'
};
const MIEW_MODES = {
'lines': 'LN',
'ballsAndSticks': 'BS',
'licorice': 'LC'
};
function getLocalMiewOpts() {
let userOpts = storage.getItem("ketcher-opts");
if (!userOpts)
return MIEW_OPTIONS;
const opts = MIEW_OPTIONS;
if (userOpts.miewTheme)
opts.settings.theme = camelCase(userOpts.miewTheme);
if (userOpts.miewAtomLabel)
opts.settings.atomLabel = camelCase(userOpts.miewAtomLabel);
if (userOpts.miewMode)
opts.reps[0].mode = MIEW_MODES[camelCase(userOpts.miewMode)];
return opts;
}
function origin (url) {
let loc = url;
if (!loc.href) {
loc = document.createElement('a');
loc.href = url;
}
if (loc.origin)
return loc.origin;
if (!loc.hostname) // relative url, IE
loc = document.location;
return loc.protocol + '//' + loc.hostname +
(!loc.port ? '' : ':' + loc.port);
}
function queryOptions(options, sep='&') {
if (Array.isArray(options)) {
return options.reduce((res, item) => {
let value = queryOptions(item);
if (value !== null)
res.push(value);
return res;
}, []).join(sep);
} else if (typeof options === 'object') {
return Object.keys(options).reduce((res, item) => {
let value = options[item];
res.push(typeof value === 'object' ?
queryOptions(value) :
encodeURIComponent(item) + '=' +
encodeURIComponent(value));
return res;
}, []).join(sep);
} else {
return null;
}
}
function miewLoad(wnd, url, options={}) { // TODO: timeout
return new Promise(function (resolve, reject) {
addEventListener('message', function onload(event) {
if (event.origin === origin(url) && event.data === 'miewLoadComplete') {
window.removeEventListener('message', onload);
let miew = wnd.MIEWS[0];
miew._opts.load = false; // setOptions({ load: '' })
miew._menuDisabled = true; // no way to disable menu after constructor return
if (miew.init()) {
miew.setOptions(options);
miew.benchmarkGfx().then(() => {
miew.run();
setTimeout(() => resolve(miew), 10);
// see setOptions message handler
});
}
}
});
});
}
function miewSave(miew, url) {
miew.saveData();
return new Promise(function (resolve, reject) {
addEventListener('message', function onsave(event) {
if (event.origin === origin(url) && event.data.startsWith('CML:')) {
window.removeEventListener('message', onsave);
resolve(atob(event.data.slice(4)));
}
});
});
}
class Miew extends Component {
constructor(props) {
console.info('init');
super(props);
this.opts = getLocalMiewOpts();
}
load(ev) {
let miew = miewLoad(ev.target.contentWindow,
MIEW_PATH, this.opts);
this.setState({ miew });
this.state.miew.then(miew => {
miew.parse(this.props.structStr, {
fileType: 'cml',
loaded: true
});
this.setState({ miew });
});
}
save(ev) {
if (this.props.onOk) {
let structStr = miewSave(this.state.miew, MIEW_PATH);
this.setState({ structStr });
this.state.structStr.then(structStr => {
this.props.onOk({ structStr });
});
}
}
window() {
let opts = {
...this.opts,
load: `CML:${btoa(this.props.structStr)}`,
sourceType: 'message'
};
let br = this.base.getBoundingClientRect(); // Preact specifiec
// see: epa.ms/1NAYWp
let wndProps = {
...MIEW_WINDOW,
top: Math.round(br.top),
left: Math.round(br.left),
width: Math.round(br.width),
height: Math.round(br.height)
};
let wnd = window.open(`${MIEW_PATH}?${queryOptions(opts)}`,
'miew', queryOptions(wndProps, ','));
if (wnd) {
this.props.onCancel && this.props.onCancel();
wnd.onload = function () {
console.info('windowed');
};
}
}
render(props) {
let {miew, structStr} = this.state;
return (
<Dialog title="3D View"
className="miew" params={props}
buttons={[
"Close",
<button disabled={miew instanceof Promise || structStr instanceof Promise}
onClick={ ev => this.save(ev) }>
Apply
</button>,
<button className="window"
disabled={/MSIE|rv:11/i.test(navigator.userAgent)}
onClick={ ev => this.window() }>
Detach to new window
</button>
]}>
<iframe id="miew-iframe"
src={MIEW_PATH}
onLoad={ev => this.load(ev) }></iframe>
</Dialog>
);
}
}
export default Miew;