forked from enviPath/enviPy
Current Dev State
This commit is contained in:
50
static/js/ketcher2/script/ui/component/accordion.jsx
Normal file
50
static/js/ketcher2/script/ui/component/accordion.jsx
Normal file
@ -0,0 +1,50 @@
|
||||
/****************************************************************************
|
||||
* 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 {h, Component} from 'preact';
|
||||
/** @jsx h */
|
||||
|
||||
class Accordion extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state.active = props.active ? props.active : {};
|
||||
}
|
||||
onActive(index) {
|
||||
let newActive = {};
|
||||
newActive[index] = !this.state.active[index];
|
||||
this.setState({ active: Object.assign(this.state.active, newActive)});
|
||||
if (this.props.onActive) this.props.onActive();
|
||||
}
|
||||
|
||||
render() {
|
||||
let {children, captions, ...props} = this.props;
|
||||
return (
|
||||
<ul {...props}>
|
||||
{ captions.map((caption, index) => (
|
||||
<li className="tab">
|
||||
<a className={this.state.active[index] ? 'active' : ''}
|
||||
onClick={() => this.onActive(index)}>
|
||||
{caption}
|
||||
</a>
|
||||
{this.state.active[index] ? children[index] : null }
|
||||
</li>
|
||||
)) }
|
||||
</ul>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default Accordion;
|
||||
99
static/js/ketcher2/script/ui/component/actionmenu.jsx
Normal file
99
static/js/ketcher2/script/ui/component/actionmenu.jsx
Normal file
@ -0,0 +1,99 @@
|
||||
/****************************************************************************
|
||||
* 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 { h } from 'preact';
|
||||
/** @jsx h */
|
||||
import classNames from 'classnames';
|
||||
|
||||
import action from '../action';
|
||||
import { hiddenAncestor } from '../state/toolbar';
|
||||
|
||||
const isMac = /Mac/.test(navigator.platform);
|
||||
const shortcutAliasMap = {
|
||||
'Escape': 'Esc',
|
||||
'Delete': 'Del',
|
||||
'Mod': isMac ? '⌘' : 'Ctrl'
|
||||
};
|
||||
|
||||
export function shortcutStr(shortcut) {
|
||||
const key = Array.isArray(shortcut) ? shortcut[0] : shortcut;
|
||||
return key.replace(/(\b[a-z]\b$|Mod|Escape|Delete)/g, function (key) {
|
||||
return shortcutAliasMap[key] || key.toUpperCase();
|
||||
});
|
||||
}
|
||||
|
||||
function ActionButton({action, status={}, onAction, ...props}) {
|
||||
let shortcut = action.shortcut && shortcutStr(action.shortcut);
|
||||
return (
|
||||
<button disabled={status.disabled}
|
||||
onClick={(ev) => {
|
||||
if (!status.selected || action.action.tool === 'chiralFlag') {
|
||||
onAction(action.action);
|
||||
ev.stopPropagation();
|
||||
}
|
||||
} }
|
||||
title={shortcut ? `${action.title} (${shortcut})` : action.title}>
|
||||
{action.title}
|
||||
</button>
|
||||
)
|
||||
}
|
||||
|
||||
function ActionMenu({name, menu, className, role, ...props}) {
|
||||
return (
|
||||
<menu className={className} role={role}
|
||||
style={toolMargin(name, menu, props.visibleTools)}>
|
||||
{
|
||||
menu.map(item => (
|
||||
<li id={item.id || item}
|
||||
className={classNames(props.status[item]) + ` ${item.id === props.opened ? 'opened' : ''}`}
|
||||
onClick={(ev) => openHandle(ev, props.onOpen) }>
|
||||
{ typeof item !== 'object' ?
|
||||
( <ActionButton {...props} action={action[item]}
|
||||
status={props.status[item]} /> ) :
|
||||
item.menu ?
|
||||
( <ActionMenu {...props} name={item.id} menu={item.menu} /> ) :
|
||||
item.component(props)
|
||||
}
|
||||
</li>
|
||||
))
|
||||
}
|
||||
</menu>
|
||||
);
|
||||
}
|
||||
|
||||
function toolMargin(menuName, menu, visibleTools) {
|
||||
if (!visibleTools[menuName]) return {};
|
||||
let iconHeight = (window.innerHeight < 600 || window.innerWidth < 1040) ? 32 : 40;
|
||||
// now not found better way
|
||||
let index = menu.indexOf(visibleTools[menuName]); // first level
|
||||
|
||||
if (index === -1) {
|
||||
let tools = [];
|
||||
menu.forEach(item => tools = tools.concat(item.menu));
|
||||
index = tools.indexOf(visibleTools[menuName]); // second level. example: `bond: bond-any`
|
||||
}
|
||||
|
||||
return (index !== -1) ? { marginTop: -(iconHeight * index) + 'px' } : {};
|
||||
}
|
||||
|
||||
function openHandle(event, onOpen) {
|
||||
let hiddenEl = hiddenAncestor(event.currentTarget);
|
||||
|
||||
if (hiddenEl) onOpen(hiddenEl.id);
|
||||
event.stopPropagation();
|
||||
}
|
||||
|
||||
export default ActionMenu;
|
||||
41
static/js/ketcher2/script/ui/component/atom.jsx
Normal file
41
static/js/ketcher2/script/ui/component/atom.jsx
Normal file
@ -0,0 +1,41 @@
|
||||
/****************************************************************************
|
||||
* 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 { h } from 'preact';
|
||||
/** @jsx h */
|
||||
import element from '../../chem/element';
|
||||
|
||||
const metPrefix = ['alkali', 'alkaline-earth', 'transition',
|
||||
'post-transition']; // 'lanthanide', 'actinide'
|
||||
|
||||
function atomClass(el) {
|
||||
let own = `atom-${el.label.toLowerCase()}`;
|
||||
let type = metPrefix.indexOf(el.type) >= 0 ? `${el.type} metal` :
|
||||
(el.type || 'unknown-props');
|
||||
return [own, type, el.state || 'unknown-state', el.origin];
|
||||
}
|
||||
|
||||
function Atom({el, shortcut, className, ...props}) {
|
||||
return (
|
||||
<button title={shortcut ? `${el.title} (${shortcut})` : el.title}
|
||||
className={[...atomClass(el), className].join(' ')}
|
||||
value={element.map[el.label]} {...props}>
|
||||
{el.label}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
export default Atom;
|
||||
136
static/js/ketcher2/script/ui/component/cliparea.jsx
Normal file
136
static/js/ketcher2/script/ui/component/cliparea.jsx
Normal file
@ -0,0 +1,136 @@
|
||||
/****************************************************************************
|
||||
* 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 { h, Component } from 'preact';
|
||||
/** @jsx h */
|
||||
|
||||
const ieCb = window.clipboardData;
|
||||
|
||||
class ClipArea extends Component {
|
||||
shouldComponentUpdate() {
|
||||
return false;
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
const el = this.refs ? this.refs.base : this.base;
|
||||
this.target = this.props.target || el.parentNode;
|
||||
|
||||
this.listeners = {
|
||||
'mouseup': event => {
|
||||
if (this.props.focused() && !isFormElement(event.target))
|
||||
autofocus(el);
|
||||
},
|
||||
'copy': event => {
|
||||
if (this.props.focused() && this.props.onCopy) {
|
||||
const data = this.props.onCopy();
|
||||
if (data)
|
||||
copy(event.clipboardData, data);
|
||||
event.preventDefault();
|
||||
}
|
||||
},
|
||||
'cut': event => {
|
||||
if (this.props.focused() && this.props.onCut) {
|
||||
const data = this.props.onCut();
|
||||
if (data)
|
||||
copy(event.clipboardData, data);
|
||||
event.preventDefault();
|
||||
}
|
||||
},
|
||||
'paste': event => {
|
||||
if (this.props.focused() && this.props.onPaste) {
|
||||
const data = paste(event.clipboardData, this.props.formats);
|
||||
if (data)
|
||||
this.props.onPaste(data);
|
||||
event.preventDefault();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Object.keys(this.listeners).forEach(en => {
|
||||
this.target.addEventListener(en, this.listeners[en]);
|
||||
});
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
Object.keys(this.listeners).forEach(en => {
|
||||
this.target.removeEventListener(en, this.listeners[en]);
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<textarea className="cliparea" contentEditable={true}
|
||||
autoFocus={true}/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function isFormElement(el) {
|
||||
if (el.tagName === 'INPUT' && el.type === 'button') return false;
|
||||
return ['INPUT', 'SELECT', 'TEXTAREA'].indexOf(el.tagName) > -1;
|
||||
}
|
||||
|
||||
function autofocus(cliparea) {
|
||||
cliparea.value = ' ';
|
||||
cliparea.focus();
|
||||
cliparea.select();
|
||||
}
|
||||
|
||||
function copy(cb, data) {
|
||||
if (!cb && ieCb) {
|
||||
ieCb.setData('text', data['text/plain']);
|
||||
} else {
|
||||
cb.setData('text/plain', data['text/plain']);
|
||||
try {
|
||||
Object.keys(data).forEach(function (fmt) {
|
||||
cb.setData(fmt, data[fmt]);
|
||||
});
|
||||
} catch (ex) {
|
||||
console.info('Could not write exact type', ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function paste(cb, formats) {
|
||||
let data = {};
|
||||
if (!cb && ieCb) {
|
||||
data['text/plain'] = ieCb.getData('text');
|
||||
} else {
|
||||
data['text/plain'] = cb.getData('text/plain');
|
||||
data = formats.reduce(function (data, fmt) {
|
||||
const d = cb.getData(fmt);
|
||||
if (d)
|
||||
data[fmt] = d;
|
||||
return data;
|
||||
}, data);
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
export const actions = ['cut', 'copy', 'paste'];
|
||||
|
||||
export function exec(action) {
|
||||
let enabled = document.queryCommandSupported(action);
|
||||
if (enabled) try {
|
||||
enabled = document.execCommand(action) || ieCb;
|
||||
} catch (ex) {
|
||||
// FF < 41
|
||||
enabled = false;
|
||||
}
|
||||
return enabled;
|
||||
}
|
||||
|
||||
export default ClipArea;
|
||||
75
static/js/ketcher2/script/ui/component/combobox.jsx
Normal file
75
static/js/ketcher2/script/ui/component/combobox.jsx
Normal file
@ -0,0 +1,75 @@
|
||||
/****************************************************************************
|
||||
* 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 { h, Component } from 'preact';
|
||||
/** @jsx h */
|
||||
|
||||
class ComboBox extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
suggestsHidden: true
|
||||
};
|
||||
|
||||
this.click = this.click.bind(this);
|
||||
this.blur = this.blur.bind(this);
|
||||
this.updateInput = this.updateInput.bind(this);
|
||||
}
|
||||
|
||||
updateInput(event) {
|
||||
const value = (event.target.value || event.target.textContent);
|
||||
this.setState({ suggestsHidden: true });
|
||||
this.props.onChange(value);
|
||||
}
|
||||
|
||||
click() {
|
||||
this.setState({ suggestsHidden: false });
|
||||
}
|
||||
|
||||
blur() {
|
||||
this.setState({ suggestsHidden: true });
|
||||
}
|
||||
|
||||
render(props) {
|
||||
const { value, type = 'text', schema } = props;
|
||||
|
||||
const suggestList = schema.enumNames
|
||||
.filter(item => item !== value)
|
||||
.map(item => <li onMouseDown={this.updateInput}>{item}</li>);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<input type={type} value={value} onClick={this.click}
|
||||
onBlur={this.blur} onInput={this.updateInput} autocomplete="off"
|
||||
/>
|
||||
{
|
||||
suggestList.length !== 0 ?
|
||||
(
|
||||
<ui className='suggestList'
|
||||
style={`display: ${this.state.suggestsHidden ? 'none' : 'block'}`}
|
||||
>
|
||||
{
|
||||
suggestList
|
||||
}
|
||||
</ui>
|
||||
) : ''
|
||||
}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default ComboBox;
|
||||
82
static/js/ketcher2/script/ui/component/dialog.jsx
Normal file
82
static/js/ketcher2/script/ui/component/dialog.jsx
Normal file
@ -0,0 +1,82 @@
|
||||
/****************************************************************************
|
||||
* 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 { h, Component } from 'preact';
|
||||
/** @jsx h */
|
||||
|
||||
import keyName from 'w3c-keyname';
|
||||
|
||||
class Dialog extends Component {
|
||||
exit(mode) {
|
||||
let { params, result=() => null,
|
||||
valid=() => !!result() } = this.props;
|
||||
let key = (mode === 'OK') ? 'onOk' : 'onCancel';
|
||||
if (params && key in params && (key !== 'onOk' || valid()) )
|
||||
params[key](result());
|
||||
}
|
||||
keyDown(ev) {
|
||||
let key = keyName(ev);
|
||||
let active = document.activeElement;
|
||||
let activeTextarea = active && active.tagName === 'TEXTAREA';
|
||||
if (key === 'Escape' || key === 'Enter' && !activeTextarea) {
|
||||
this.exit(key === 'Enter' ? 'OK': 'Cancel');
|
||||
ev.preventDefault();
|
||||
}
|
||||
ev.stopPropagation();
|
||||
}
|
||||
componentDidMount() {
|
||||
const fe = this.base.querySelector(['input:not([type=checkbox]):not([type=button])', 'textarea',
|
||||
'[contenteditable]','select'].join(',')) ||
|
||||
this.base.querySelector(['button.close'].join(','));
|
||||
console.assert(fe, 'No active buttons');
|
||||
if (fe.focus) fe.focus();
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
(document.querySelector('.cliparea') || document.body).focus();
|
||||
}
|
||||
|
||||
render() {
|
||||
let {
|
||||
children, title, params = {},
|
||||
result = () => null, valid = () => !!result(), // Hmm, dublicate.. No simple default props
|
||||
buttons = ["Cancel", "OK"], ...props
|
||||
} = this.props; // see: https://git.io/v1KR6
|
||||
return (
|
||||
<form role="dialog" onSubmit={ev => ev.preventDefault()}
|
||||
onKeyDown={ev => this.keyDown(ev)} tabIndex="-1" {...props}>
|
||||
<header>{title}
|
||||
{params.onCancel && title && (
|
||||
<button className="close"
|
||||
onClick={() => this.exit('Cancel')}>×
|
||||
</button> )
|
||||
}
|
||||
</header>
|
||||
{children}
|
||||
<footer>{
|
||||
buttons.map(b => (
|
||||
typeof b !== 'string' ? b :
|
||||
<input type="button" value={b}
|
||||
disabled={b === 'OK' && !valid()}
|
||||
onClick={() => this.exit(b)}/>
|
||||
))
|
||||
}</footer>
|
||||
</form>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default Dialog;
|
||||
196
static/js/ketcher2/script/ui/component/form.jsx
Normal file
196
static/js/ketcher2/script/ui/component/form.jsx
Normal file
@ -0,0 +1,196 @@
|
||||
/****************************************************************************
|
||||
* 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 jsonschema from 'jsonschema';
|
||||
import { h, Component } from 'preact';
|
||||
import { connect } from 'preact-redux';
|
||||
/** @jsx h */
|
||||
import Input from './input';
|
||||
import { updateFormState } from '../state/form';
|
||||
|
||||
class Form extends Component {
|
||||
constructor({ onUpdate, schema, init, ...props }) {
|
||||
super();
|
||||
this.schema = propSchema(schema, props);
|
||||
|
||||
if (init) {
|
||||
let { valid, errors } = this.schema.serialize(init);
|
||||
const errs = getErrorsObj(errors);
|
||||
|
||||
init = Object.assign({}, init, { init: true });
|
||||
onUpdate(init, valid, errs);
|
||||
}
|
||||
}
|
||||
|
||||
updateState(newstate) {
|
||||
const { instance, valid, errors } = this.schema.serialize(newstate);
|
||||
const errs = getErrorsObj(errors);
|
||||
this.props.onUpdate(instance, valid, errs);
|
||||
}
|
||||
|
||||
getChildContext() {
|
||||
const { schema } = this.props;
|
||||
return { schema, stateStore: this };
|
||||
}
|
||||
|
||||
field(name, onChange) {
|
||||
const { result, errors } = this.props;
|
||||
const value = result[name];
|
||||
const self = this;
|
||||
|
||||
return {
|
||||
dataError: errors && errors[name] || false,
|
||||
value: value,
|
||||
onChange(value) {
|
||||
const newstate = Object.assign({}, self.props.result, { [name]: value });
|
||||
self.updateState(newstate);
|
||||
if (onChange) onChange(value);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
render(props) {
|
||||
const { result, children, schema, ...prop } = props;
|
||||
|
||||
if (schema.key && schema.key !== this.schema.key) {
|
||||
this.schema = propSchema(schema, prop);
|
||||
this.schema.serialize(result); // hack: valid first state
|
||||
this.updateState(result);
|
||||
}
|
||||
|
||||
return (
|
||||
<form {...prop}>
|
||||
{children}
|
||||
</form>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Form = connect(
|
||||
null,
|
||||
dispatch => ({
|
||||
onUpdate: function (result, valid, errors) {
|
||||
dispatch(updateFormState({ result, valid, errors }));
|
||||
}
|
||||
})
|
||||
)(Form);
|
||||
|
||||
function Label({ labelPos, title, children, ...props }) {
|
||||
return (
|
||||
<label {...props}>{ title && labelPos !== 'after' ? `${title}:` : '' }
|
||||
{children}
|
||||
{ title && labelPos === 'after' ? title : '' }
|
||||
</label>
|
||||
);
|
||||
}
|
||||
|
||||
class Field extends Component {
|
||||
render(props) {
|
||||
const { name, onChange, className, component, ...prop } = props;
|
||||
const { schema, stateStore } = this.context;
|
||||
|
||||
const desc = prop.schema || schema.properties[name];
|
||||
const { dataError, ...fieldOpts } = stateStore.field(name, onChange);
|
||||
|
||||
return (
|
||||
<Label className={className} data-error={dataError} title={prop.title || desc.title} >
|
||||
{
|
||||
component ?
|
||||
h(component, { ...fieldOpts, ...prop }) :
|
||||
<Input name={name} schema={desc}
|
||||
{...fieldOpts} {...prop}/>
|
||||
}
|
||||
</Label>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const SelectOneOf = (props) => {
|
||||
const { title, name, schema, ...prop } = props;
|
||||
|
||||
const selectDesc = {
|
||||
title: title,
|
||||
enum: [],
|
||||
enumNames: []
|
||||
};
|
||||
|
||||
Object.keys(schema).forEach(item => {
|
||||
selectDesc.enum.push(item);
|
||||
selectDesc.enumNames.push(schema[item].title || item);
|
||||
});
|
||||
|
||||
return <Field name={name} schema={selectDesc} {...prop}/>;
|
||||
};
|
||||
|
||||
////
|
||||
|
||||
function propSchema(schema, { customValid, serialize = {}, deserialize = {} }) {
|
||||
const v = new jsonschema.Validator();
|
||||
|
||||
if (customValid) {
|
||||
schema = Object.assign({}, schema); // copy
|
||||
schema.properties = Object.keys(customValid).reduce((res, prop) => {
|
||||
v.customFormats[prop] = customValid[prop];
|
||||
res[prop] = { format: prop, ...res[prop] };
|
||||
return res;
|
||||
}, schema.properties);
|
||||
}
|
||||
|
||||
return {
|
||||
key: schema.key || '',
|
||||
serialize: inst => v.validate(inst, schema, {
|
||||
rewrite: serializeRewrite.bind(null, serialize)
|
||||
}),
|
||||
deserialize: inst => v.validate(inst, schema, {
|
||||
rewrite: deserializeRewrite.bind(null, deserialize)
|
||||
})
|
||||
};
|
||||
}
|
||||
|
||||
function serializeRewrite(serializeMap, instance, schema) {
|
||||
const res = {};
|
||||
if (typeof instance !== 'object' || !schema.properties) {
|
||||
return instance !== undefined ? instance :
|
||||
schema.default;
|
||||
}
|
||||
|
||||
for (let p in schema.properties) {
|
||||
if (schema.properties.hasOwnProperty(p) && (p in instance)) {
|
||||
res[p] = instance[serializeMap[p]] || instance[p];
|
||||
}
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
function deserializeRewrite(deserializeMap, instance, schema) {
|
||||
return instance;
|
||||
}
|
||||
|
||||
function getErrorsObj(errors) {
|
||||
let errs = {};
|
||||
let field;
|
||||
|
||||
errors.forEach(item => {
|
||||
field = item.property.split('.')[1];
|
||||
if (!errs[field])
|
||||
errs[field] = item.schema.invalidMessage || item.message;
|
||||
});
|
||||
|
||||
return errs;
|
||||
}
|
||||
|
||||
export { Form, Field, SelectOneOf };
|
||||
232
static/js/ketcher2/script/ui/component/input.jsx
Normal file
232
static/js/ketcher2/script/ui/component/input.jsx
Normal file
@ -0,0 +1,232 @@
|
||||
/****************************************************************************
|
||||
* 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 { h, Component } from 'preact';
|
||||
/** @jsx h */
|
||||
|
||||
function GenericInput({ value, onChange, type = "text", ...props }) {
|
||||
return (
|
||||
<input type={type} value={value} onInput={onChange} {...props} />
|
||||
);
|
||||
}
|
||||
|
||||
GenericInput.val = function (ev, schema) {
|
||||
const input = ev.target;
|
||||
const isNumber = (input.type === 'number' || input.type === 'range') ||
|
||||
(schema && (schema.type === 'number' || schema.type === 'integer'));
|
||||
const value = isNumber ? input.value.replace(/,/g, '.') : input.value;
|
||||
|
||||
return (isNumber && !isNaN(value - 0)) ? value - 0 : value;
|
||||
};
|
||||
|
||||
function TextArea({ value, onChange, ...props }) {
|
||||
return (
|
||||
<textarea value={value} onInput={onChange} {...props}/>
|
||||
);
|
||||
}
|
||||
|
||||
TextArea.val = (ev) => ev.target.value;
|
||||
|
||||
function CheckBox({ value, onChange, ...props }) {
|
||||
return (
|
||||
<input type="checkbox" checked={value} onClick={onChange} {...props} />
|
||||
);
|
||||
}
|
||||
|
||||
CheckBox.val = function (ev) {
|
||||
ev.stopPropagation();
|
||||
return !!ev.target.checked;
|
||||
};
|
||||
|
||||
function Select({ schema, value, selected, onSelect, ...props }) {
|
||||
return (
|
||||
<select onChange={onSelect} {...props}>
|
||||
{
|
||||
enumSchema(schema, (title, val) => (
|
||||
<option selected={selected(val, value)}
|
||||
value={typeof val !== 'object' && val}>
|
||||
{title}
|
||||
</option>
|
||||
))
|
||||
}
|
||||
</select>
|
||||
);
|
||||
}
|
||||
|
||||
Select.val = function (ev, schema) {
|
||||
const select = ev.target;
|
||||
if (!select.multiple)
|
||||
return enumSchema(schema, select.selectedIndex);
|
||||
|
||||
return [].reduce.call(select.options, function (res, o, i) {
|
||||
return !o.selected ? res :
|
||||
[enumSchema(schema, i), ...res];
|
||||
}, []);
|
||||
};
|
||||
|
||||
function FieldSet({ schema, value, selected, onSelect, type = "radio", ...props }) {
|
||||
return (
|
||||
<fieldset onClick={onSelect} className="radio">
|
||||
{
|
||||
enumSchema(schema, (title, val) => (
|
||||
<label>
|
||||
<input type={type} checked={selected(val, value)}
|
||||
value={typeof val !== 'object' && val}
|
||||
{...props}/>
|
||||
{title}
|
||||
</label>
|
||||
))
|
||||
}
|
||||
</fieldset>
|
||||
);
|
||||
}
|
||||
|
||||
FieldSet.val = function (ev, schema) {
|
||||
const input = ev.target;
|
||||
if (ev.target.tagName !== 'INPUT') {
|
||||
ev.stopPropagation();
|
||||
return undefined;
|
||||
}
|
||||
// Hm.. looks like premature optimization
|
||||
// should we inline this?
|
||||
const fieldset = input.parentNode.parentNode;
|
||||
const res = [].reduce.call(fieldset.querySelectorAll('input'),
|
||||
function (res, inp, i) {
|
||||
return !inp.checked ? res :
|
||||
[enumSchema(schema, i), ...res];
|
||||
}, []);
|
||||
return input.type === 'radio' ? res[0] : res;
|
||||
};
|
||||
|
||||
function enumSchema(schema, cbOrIndex) {
|
||||
const isTypeValue = Array.isArray(schema);
|
||||
if (!isTypeValue && schema.items)
|
||||
schema = schema.items;
|
||||
|
||||
if (typeof cbOrIndex === 'function') {
|
||||
return (isTypeValue ? schema : schema.enum).map((item, i) => {
|
||||
const title = isTypeValue ? item.title :
|
||||
schema.enumNames && schema.enumNames[i];
|
||||
return cbOrIndex(title !== undefined ? title : item,
|
||||
item.value !== undefined ? item.value : item);
|
||||
});
|
||||
}
|
||||
|
||||
if (!isTypeValue)
|
||||
return schema.enum[cbOrIndex];
|
||||
|
||||
const res = schema[cbOrIndex];
|
||||
return res.value !== undefined ? res.value : res;
|
||||
}
|
||||
|
||||
function inputCtrl(component, schema, onChange) {
|
||||
let props = {};
|
||||
if (schema) {
|
||||
// TODO: infer maxLength, min, max, step, etc
|
||||
if (schema.type === 'number' || schema.type === 'integer')
|
||||
props = { type: 'text' };
|
||||
}
|
||||
|
||||
return {
|
||||
onChange: function (ev) {
|
||||
const val = !component.val ? ev :
|
||||
component.val(ev, schema);
|
||||
onChange(val);
|
||||
},
|
||||
...props
|
||||
};
|
||||
}
|
||||
|
||||
function singleSelectCtrl(component, schema, onChange) {
|
||||
return {
|
||||
selected: (testVal, value) => (value === testVal),
|
||||
onSelect: function (ev, value) {
|
||||
const val = !component.val ? ev :
|
||||
component.val(ev, schema);
|
||||
if (val !== undefined)
|
||||
onChange(val);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function multipleSelectCtrl(component, schema, onChange) {
|
||||
return {
|
||||
multiple: true,
|
||||
selected: (testVal, values) =>
|
||||
(values && values.indexOf(testVal) >= 0),
|
||||
onSelect: function (ev, values) {
|
||||
if (component.val) {
|
||||
let val = component.val(ev, schema);
|
||||
if (val !== undefined)
|
||||
onChange(val);
|
||||
} else {
|
||||
const i = values ? values.indexOf(ev) : -1;
|
||||
if (i < 0)
|
||||
onChange(values ? [ev, ...values] : [ev]);
|
||||
else
|
||||
onChange([...values.slice(0, i),
|
||||
...values.slice(i + 1)]);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function ctrlMap(component, { schema, multiple, onChange }) {
|
||||
if (!schema || !schema.enum && !schema.items && !Array.isArray(schema) || schema.type === 'string')
|
||||
return inputCtrl(component, schema, onChange);
|
||||
|
||||
if (multiple || schema.type === 'array')
|
||||
return multipleSelectCtrl(component, schema, onChange);
|
||||
|
||||
return singleSelectCtrl(component, schema, onChange);
|
||||
}
|
||||
|
||||
function componentMap({ schema, type, multiple }) {
|
||||
if (!schema || !schema.enum && !schema.items && !Array.isArray(schema)) {
|
||||
if (type === 'checkbox' || schema && schema.type === 'boolean')
|
||||
return CheckBox;
|
||||
|
||||
return (type === 'textarea') ? TextArea : GenericInput;
|
||||
}
|
||||
if (multiple || schema.type === 'array')
|
||||
return (type === 'checkbox') ? FieldSet : Select;
|
||||
|
||||
return (type === 'radio') ? FieldSet : Select;
|
||||
}
|
||||
|
||||
function shallowCompare(a, b) {
|
||||
for (let i in a) if (!(i in b)) return true;
|
||||
for (let i in b) if (a[i] !== b[i]) { return true; }
|
||||
return false;
|
||||
}
|
||||
|
||||
export default class Input extends Component {
|
||||
constructor({ component, ...props }) {
|
||||
super(props);
|
||||
this.component = component || componentMap(props);
|
||||
this.ctrl = ctrlMap(this.component, props);
|
||||
}
|
||||
|
||||
shouldComponentUpdate({ children, onChange, ...nextProps }) {
|
||||
var { children, onChange, ...oldProps } = this.props;
|
||||
return shallowCompare(oldProps, nextProps);
|
||||
}
|
||||
|
||||
render() {
|
||||
var { children, onChange, ...props } = this.props;
|
||||
return h(this.component, { ...this.ctrl, ...props });
|
||||
}
|
||||
}
|
||||
71
static/js/ketcher2/script/ui/component/measure-input.jsx
Normal file
71
static/js/ketcher2/script/ui/component/measure-input.jsx
Normal file
@ -0,0 +1,71 @@
|
||||
/****************************************************************************
|
||||
* 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 { h, Component } from 'preact';
|
||||
/** @jsx h */
|
||||
import Input from './input';
|
||||
|
||||
class MeasureInput extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = { meas: 'px' };
|
||||
}
|
||||
|
||||
handleChange(value, onChange) {
|
||||
const convValue = convertValue(value, this.state.meas, 'px');
|
||||
this.state.cust = value;
|
||||
onChange(convValue);
|
||||
}
|
||||
|
||||
render() {
|
||||
const { meas, cust } = this.state;
|
||||
const { schema, value, onChange, ...props } = this.props;
|
||||
|
||||
if (convertValue(cust, meas, 'px') !== value)
|
||||
this.setState({ meas: 'px', cust: value }); // Hack: New store (RESET)
|
||||
|
||||
return (
|
||||
<div style="display: inline-flex;" {...props}>
|
||||
<Input schema={schema} step={meas === 'px' || meas === 'pt' ? '1' : '0.001'} style="width: 75%;"
|
||||
value={cust} onChange={(v) => this.handleChange(v, onChange)} />
|
||||
<Input schema={{ enum: ['cm', 'px', 'pt', 'inch'] }} style="width: 25%;"
|
||||
value={meas}
|
||||
onChange={(m) => this.setState({
|
||||
meas: m,
|
||||
cust: convertValue(this.state.cust, this.state.meas, m)
|
||||
})} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const measureMap = {
|
||||
'px': 1,
|
||||
'cm': 37.795278,
|
||||
'pt': 1.333333,
|
||||
'inch': 96,
|
||||
};
|
||||
|
||||
function convertValue(value, measureFrom, measureTo) {
|
||||
if (!value && value !== 0 || isNaN(value)) return null;
|
||||
|
||||
return (measureTo === 'px' || measureTo === 'pt')
|
||||
? (value * measureMap[measureFrom] / measureMap[measureTo]).toFixed( ) - 0
|
||||
: (value * measureMap[measureFrom] / measureMap[measureTo]).toFixed(3) - 0;
|
||||
}
|
||||
|
||||
export default MeasureInput;
|
||||
|
||||
109
static/js/ketcher2/script/ui/component/openbutton.jsx
Normal file
109
static/js/ketcher2/script/ui/component/openbutton.jsx
Normal file
@ -0,0 +1,109 @@
|
||||
/****************************************************************************
|
||||
* 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 { h, Component } from 'preact';
|
||||
/** @jsx h */
|
||||
|
||||
class OpenButton extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
if (props.server) {
|
||||
fileOpener(props.server).then(opener => {
|
||||
this.setState({opener});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
open(ev) {
|
||||
const files = ev.target.files;
|
||||
const noop = () => null;
|
||||
const { onLoad = noop, onError = noop } = this.props;
|
||||
|
||||
if (this.state.opener && files.length) {
|
||||
this.state.opener(files[0]).then(onLoad, onError);
|
||||
} else if (files.length)
|
||||
onLoad(files[0]);
|
||||
ev.target.value = null;
|
||||
ev.preventDefault();
|
||||
}
|
||||
|
||||
render() {
|
||||
const { children, type, ...props } = this.props;
|
||||
|
||||
return (
|
||||
<div { ...props }>
|
||||
<input id="input-file" onChange={ ev => this.open(ev) }
|
||||
accept={ type } type="file"/>
|
||||
<label for="input-file">
|
||||
{ children }
|
||||
</label>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function fileOpener (server) {
|
||||
return new Promise((resolve, reject) => {
|
||||
// TODO: refactor return
|
||||
if (global.FileReader)
|
||||
resolve(throughFileReader);
|
||||
|
||||
else if (global.ActiveXObject) {
|
||||
try {
|
||||
const fso = new ActiveXObject('Scripting.FileSystemObject');
|
||||
resolve(file => Promise.resolve(throughFileSystemObject(fso, file)));
|
||||
} catch (e) {
|
||||
reject(e);
|
||||
}
|
||||
} else if (server) {
|
||||
resolve(server.then(() => {
|
||||
throw "Server doesn't still support echo method";
|
||||
//return resolve(throughForm2IframePosting);
|
||||
}));
|
||||
} else
|
||||
reject(new Error("Your browser does not support " +
|
||||
"opening files locally"));
|
||||
});
|
||||
}
|
||||
|
||||
function throughFileReader(file) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const rd = new FileReader();
|
||||
|
||||
rd.onload = () => {
|
||||
const content = rd.result;
|
||||
if (file.msClose)
|
||||
file.msClose();
|
||||
resolve(content);
|
||||
};
|
||||
|
||||
rd.onerror = event => {
|
||||
reject(event);
|
||||
};
|
||||
|
||||
rd.readAsText(file, 'UTF-8');
|
||||
});
|
||||
}
|
||||
|
||||
function throughFileSystemObject(fso, file) {
|
||||
// IE9 and below
|
||||
const fd = fso.OpenTextFile(file.name, 1),
|
||||
content = fd.ReadAll();
|
||||
fd.Close();
|
||||
return content;
|
||||
}
|
||||
|
||||
export default OpenButton;
|
||||
77
static/js/ketcher2/script/ui/component/savebutton.jsx
Normal file
77
static/js/ketcher2/script/ui/component/savebutton.jsx
Normal file
@ -0,0 +1,77 @@
|
||||
/****************************************************************************
|
||||
* 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 { h, Component } from 'preact';
|
||||
/** @jsx h */
|
||||
import fs from 'filesaver.js';
|
||||
|
||||
class SaveButton extends Component {
|
||||
constructor({filename="unnamed", type="text/plain", className='', ...props}) {
|
||||
super({filename, type, className, ...props});
|
||||
fileSaver(props.server).then(saver => {
|
||||
this.setState({saver});
|
||||
});
|
||||
}
|
||||
|
||||
save(ev) {
|
||||
const noop = () => null;
|
||||
const { filename, data, type, onSave = noop, onError = noop } = this.props;
|
||||
|
||||
if (this.state.saver && data)
|
||||
try {
|
||||
this.state.saver(data, filename, type);
|
||||
onSave();
|
||||
}
|
||||
catch(e) {
|
||||
onError(e);
|
||||
}
|
||||
|
||||
ev.preventDefault();
|
||||
}
|
||||
|
||||
render() {
|
||||
let { children, filename, data, className, ...props } = this.props;
|
||||
|
||||
if (!this.state.saver || !data)
|
||||
className = `disabled ${className}`;
|
||||
|
||||
return (
|
||||
<a download={filename} onClick={ev => this.save(ev)}
|
||||
className={className} {...props}>
|
||||
{ children }
|
||||
</a>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function fileSaver(server) {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (global.Blob && fs.saveAs) {
|
||||
resolve((data, fn, type) => {
|
||||
const blob = new Blob([data], { type });
|
||||
fs.saveAs(blob, fn);
|
||||
});
|
||||
} else if (server) {
|
||||
resolve(server.then(() => {
|
||||
throw "Server doesn't still support echo method";
|
||||
}));
|
||||
} else
|
||||
reject(new Error("Your browser does not support " +
|
||||
"opening files locally"));
|
||||
});
|
||||
}
|
||||
|
||||
export default SaveButton;
|
||||
40
static/js/ketcher2/script/ui/component/select.jsx
Normal file
40
static/js/ketcher2/script/ui/component/select.jsx
Normal file
@ -0,0 +1,40 @@
|
||||
/****************************************************************************
|
||||
* 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 { h } from 'preact';
|
||||
/** @jsx h */
|
||||
|
||||
function SelectList({ schema, value, onSelect, splitIndexes, ...props }) {
|
||||
return (
|
||||
<ul {...props}>{
|
||||
schema.enum.map((opt, index) => (
|
||||
<li onClick={() => onSelect(opt, index) }
|
||||
className={
|
||||
(opt === value ? 'selected ' : '') +
|
||||
(isSplitIndex(index, splitIndexes) ? 'split' : '')
|
||||
}>
|
||||
{schema.enumNames ? schema.enumNames[index] : opt}
|
||||
</li>
|
||||
))
|
||||
}</ul>
|
||||
);
|
||||
}
|
||||
|
||||
function isSplitIndex(index, splitIndexes) {
|
||||
return index > 0 && splitIndexes && splitIndexes.includes(index);
|
||||
}
|
||||
|
||||
export default SelectList;
|
||||
26
static/js/ketcher2/script/ui/component/spin.jsx
Normal file
26
static/js/ketcher2/script/ui/component/spin.jsx
Normal file
@ -0,0 +1,26 @@
|
||||
/****************************************************************************
|
||||
* 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 { h } from 'preact';
|
||||
/** @jsx h */
|
||||
|
||||
function Spin({...props}) {
|
||||
return (
|
||||
<div className="spinner" {...props}></div>
|
||||
);
|
||||
}
|
||||
|
||||
export default Spin;
|
||||
78
static/js/ketcher2/script/ui/component/structeditor.jsx
Normal file
78
static/js/ketcher2/script/ui/component/structeditor.jsx
Normal file
@ -0,0 +1,78 @@
|
||||
/****************************************************************************
|
||||
* 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 { upperFirst } from 'lodash/fp';
|
||||
import { h, Component } from 'preact';
|
||||
/** @jsx h */
|
||||
|
||||
import Editor from '../../editor'
|
||||
|
||||
function setupEditor(editor, props, oldProps = {}) {
|
||||
const { struct, tool, toolOpts, options } = props;
|
||||
|
||||
if (struct !== oldProps.struct)
|
||||
editor.struct(struct);
|
||||
|
||||
if (tool !== oldProps.tool || toolOpts !== oldProps.toolOpts)
|
||||
editor.tool(tool, toolOpts);
|
||||
|
||||
if (oldProps.options && options !== oldProps.options)
|
||||
editor.options(options);
|
||||
|
||||
// update handlers
|
||||
for (let name in editor.event) {
|
||||
if (!editor.event.hasOwnProperty(name))
|
||||
continue;
|
||||
|
||||
let eventName = `on${upperFirst(name)}`;
|
||||
|
||||
if (props[eventName] !== oldProps[eventName]) {
|
||||
console.info('update editor handler', eventName);
|
||||
if (oldProps[eventName])
|
||||
editor.event[name].remove(oldProps[eventName]);
|
||||
|
||||
if (props[eventName])
|
||||
editor.event[name].add(props[eventName]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class StructEditor extends Component {
|
||||
shouldComponentUpdate() {
|
||||
return false;
|
||||
}
|
||||
|
||||
componentWillReceiveProps(props) {
|
||||
setupEditor(this.instance, props, this.props);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
console.assert(this.base, "No backing element");
|
||||
this.instance = new Editor(this.base, { ...this.props.options });
|
||||
setupEditor(this.instance, this.props);
|
||||
if (this.props.onInit)
|
||||
this.props.onInit(this.instance);
|
||||
}
|
||||
|
||||
render () {
|
||||
let { Tag="div", struct, tool, toolOpts, options, ...props } = this.props;
|
||||
return (
|
||||
<Tag onMouseDown={ev => ev.preventDefault()} {...props} />
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default StructEditor;
|
||||
71
static/js/ketcher2/script/ui/component/structrender.jsx
Normal file
71
static/js/ketcher2/script/ui/component/structrender.jsx
Normal file
@ -0,0 +1,71 @@
|
||||
/****************************************************************************
|
||||
* 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 { h, Component } from 'preact';
|
||||
/** @jsx h */
|
||||
|
||||
import Struct from '../../chem/struct';
|
||||
import molfile from '../../chem/molfile';
|
||||
import Render from '../../render';
|
||||
|
||||
function renderStruct(el, struct, options={}) {
|
||||
if (el) {
|
||||
if (struct.prerender) // Should it sit here?
|
||||
el.innerHTML = struct.prerender;
|
||||
else {
|
||||
console.info('render!', el.clientWidth, el.clientWidth);
|
||||
const rnd = new Render(el, {
|
||||
autoScale: true,
|
||||
...options
|
||||
});
|
||||
rnd.setMolecule(struct);
|
||||
rnd.update();
|
||||
// console.info('render!');//, el.innerHTML);
|
||||
// struct.prerender = el.innerHTML;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class StructRender extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
if (!(props.struct instanceof Struct)) try {
|
||||
this.props.struct = molfile.parse(props.struct);
|
||||
} catch (e) {
|
||||
alert("Could not parse structure\n" + e);
|
||||
this.props.struct = null;
|
||||
}
|
||||
}
|
||||
|
||||
shouldComponentUpdate() {
|
||||
return false;
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
const el = this.refs ? this.refs.base : this.base;
|
||||
const { struct, options } = this.props;
|
||||
renderStruct(el, struct, options);
|
||||
}
|
||||
|
||||
render () {
|
||||
let { struct, Tag="div", ...props } = this.props;
|
||||
return (
|
||||
<Tag /*ref="el"*/ {...props}>{ struct ? null : 'No molecule' }</Tag>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default StructRender;
|
||||
88
static/js/ketcher2/script/ui/component/systemfonts.jsx
Normal file
88
static/js/ketcher2/script/ui/component/systemfonts.jsx
Normal file
@ -0,0 +1,88 @@
|
||||
/****************************************************************************
|
||||
* 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 { h, Component } from 'preact';
|
||||
import FontFaceObserver from "font-face-observer";
|
||||
import Input from './input';
|
||||
/** @jsx h */
|
||||
|
||||
const commonFonts = [
|
||||
"Arial",
|
||||
"Arial Black",
|
||||
"Comic Sans MS",
|
||||
"Courier New",
|
||||
"Georgia",
|
||||
"Impact", "Charcoal",
|
||||
"Lucida Console", "Monaco",
|
||||
"Palatino Linotype", "Book Antiqua", "Palatino",
|
||||
"Tahoma", "Geneva",
|
||||
"Times New Roman", "Times",
|
||||
"Verdana",
|
||||
"Symbol",
|
||||
"MS Serif", "MS Sans Serif", "New York",
|
||||
"Droid Sans", "Droid Serif", "Droid Sans Mono", "Roboto"
|
||||
];
|
||||
|
||||
function checkInSystem() {
|
||||
const availableFontsPromises = commonFonts.map((fontName) => {
|
||||
const observer = new FontFaceObserver(fontName);
|
||||
return observer.check().then(() => fontName, () => null);
|
||||
});
|
||||
|
||||
return Promise.all(availableFontsPromises);
|
||||
}
|
||||
|
||||
let cache = null;
|
||||
|
||||
class SystemFonts extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = { availableFonts: [subfontname(props.value)] };
|
||||
this.setAvailableFonts();
|
||||
}
|
||||
|
||||
setAvailableFonts() {
|
||||
cache ? this.setState({ availableFonts: cache }) :
|
||||
checkInSystem().then((results) => {
|
||||
cache = results.filter((i) => i !== null);
|
||||
this.setState({ availableFonts: cache });
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
const {...props} = this.props;
|
||||
|
||||
const desc = {
|
||||
enum: [],
|
||||
enumNames: []
|
||||
};
|
||||
|
||||
this.state.availableFonts.forEach((font) => {
|
||||
desc.enum.push(`30px ${font}`);
|
||||
desc.enumNames.push(font);
|
||||
});
|
||||
|
||||
return desc.enum.length !== 1
|
||||
? <Input schema={desc} {...props} />
|
||||
: <select><option>{desc.enumNames[0]}</option></select>;
|
||||
}
|
||||
}
|
||||
|
||||
function subfontname(name) {
|
||||
return name.substring(name.indexOf('px ') + 3);
|
||||
}
|
||||
|
||||
export default SystemFonts;
|
||||
53
static/js/ketcher2/script/ui/component/tabs.jsx
Normal file
53
static/js/ketcher2/script/ui/component/tabs.jsx
Normal file
@ -0,0 +1,53 @@
|
||||
/****************************************************************************
|
||||
* 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 {h, Component} from 'preact';
|
||||
/** @jsx h */
|
||||
|
||||
class Tabs extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state.tabIndex = props.tabIndex || 0;
|
||||
this.props.changeTab(this.state.tabIndex);
|
||||
}
|
||||
|
||||
changeTab(ev, index) {
|
||||
this.setState({ tabIndex: index });
|
||||
if (this.props.changeTab)
|
||||
this.props.changeTab(index);
|
||||
}
|
||||
|
||||
render() {
|
||||
const {children, captions, ...props} = this.props;
|
||||
return (
|
||||
<ul {...props}>
|
||||
<li className="tabs">
|
||||
{ captions.map((caption, index) => (
|
||||
<a className={this.state.tabIndex === index ? 'active' : ''}
|
||||
onClick={ ev => this.changeTab(ev, index)}>
|
||||
{caption}
|
||||
</a>
|
||||
)) }
|
||||
</li>
|
||||
<li className="tabs-content">
|
||||
{ children[this.state.tabIndex] }
|
||||
</li>
|
||||
</ul>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default Tabs;
|
||||
101
static/js/ketcher2/script/ui/component/visibleview.jsx
Normal file
101
static/js/ketcher2/script/ui/component/visibleview.jsx
Normal file
@ -0,0 +1,101 @@
|
||||
/****************************************************************************
|
||||
* 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 { h, Component } from 'preact';
|
||||
/** @jsx h */
|
||||
|
||||
const STYLE_INNER = 'position:relative; overflow:hidden; width:100%; min-height:100%;';
|
||||
|
||||
const STYLE_CONTENT = 'position:absolute; top:0; left:0; height:100%; width:100%; overflow:visible;';
|
||||
|
||||
export default class VirtualList extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
offset: 0,
|
||||
height: 0
|
||||
};
|
||||
}
|
||||
|
||||
resize = (ev, reset) => {
|
||||
const height = this.base.offsetHeight;
|
||||
|
||||
if (this.state.height !== height) {
|
||||
this.setState({ height });
|
||||
}
|
||||
|
||||
if (reset) {
|
||||
this.setState({offset: 0});
|
||||
this.base.scrollTop = 0;
|
||||
}
|
||||
};
|
||||
|
||||
handleScroll = () => {
|
||||
this.setState({ offset: this.base.scrollTop });
|
||||
if (this.props.sync) this.forceUpdate();
|
||||
};
|
||||
|
||||
componentDidUpdate({data}) {
|
||||
const equal = (data.length === this.props.data.length &&
|
||||
this.props.data.every((v, i)=> v === data[i]));
|
||||
|
||||
this.resize(null, !equal);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.resize();
|
||||
addEventListener('resize', this.resize);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
removeEventListener('resize', this.resize);
|
||||
}
|
||||
|
||||
render() {
|
||||
const { data, rowHeight, children, Tag="div", overscanCount=1, sync, ...props } = this.props;
|
||||
const { offset, height } = this.state;
|
||||
|
||||
// first visible row index
|
||||
let start = (offset / rowHeight) || 0;
|
||||
const renderRow = children[0];
|
||||
|
||||
// actual number of visible rows (without overscan)
|
||||
let visibleRowCount = (height / rowHeight) || 0;
|
||||
|
||||
// Overscan: render blocks of rows modulo an overscan row count
|
||||
// This dramatically reduces DOM writes during scrolling
|
||||
if (overscanCount) {
|
||||
start = Math.max(0, start - (start % overscanCount));
|
||||
visibleRowCount += overscanCount;
|
||||
}
|
||||
|
||||
// last visible + overscan row index
|
||||
const end = start + 1 + visibleRowCount;
|
||||
|
||||
// data slice currently in viewport plus overscan items
|
||||
let selection = data.slice(start, end);
|
||||
|
||||
return (
|
||||
<div onScroll={this.handleScroll} {...props}>
|
||||
<div style={`${STYLE_INNER} height:${data.length*rowHeight}px;`}>
|
||||
<Tag style={`${STYLE_CONTENT} top:${start*rowHeight}px;`}>
|
||||
{ selection.map((d, i) => renderRow(d, start + i)) }
|
||||
</Tag>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user