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

273 lines
7.2 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 { range } from 'lodash/fp';
import { h, Component } from 'preact';
import { connect } from 'preact-redux';
/** @jsx h */
import element from '../../chem/element';
import Dialog from '../component/dialog';
import Atom from '../component/atom';
import Tabs from '../component/tabs';
import GenericGroups from './generic-groups';
import { fromElement, toElement } from '../structconv';
import { onAction } from '../state';
import { addAtoms } from '../state/toolbar';
const typeSchema = [
{ title: 'Single', value: 'atom' },
{ title: 'List', value: 'list'},
{ title: 'Not List', value: 'not-list'}
];
const beforeSpan = {
'He': 16,
'B': 10,
'Al': 10,
'Hf': 1,
'Rf': 1
};
const main = rowPartition(element.filter(el => el && el.type !== 'actinide' &&
el.type !== 'lanthanide'));
const lanthanides = element.filter(el => el && el.type === 'lanthanide');
const actinides = element.filter(el => el && el.type === 'actinide');
function Header() {
return (
<tr>
{
range(0, 19).map(i => (
<th>{i || ''}</th>
))
}
</tr>
);
}
function TypeChoise({value, onChange, ...props}) {
return (
<fieldset>
{
typeSchema.map(sc => (
<label>
<input type="radio" value={sc.value}
checked={sc.value === value}
onClick={ev => onChange(sc.value) } {...props}/>
{sc.title}
</label>
))
}
</fieldset>
);
}
function MainRow({row, caption, refer, selected, onSelect, curEvents}) {
return (
<tr>
<th>{caption}</th>
{
row.map(el => (typeof el !== 'number') ? (
<td>
<Atom el={el}
className={selected(el.label) ? 'selected' : ''}
onClick={ev => onSelect(el.label)} {...curEvents(el)}/>
</td>
) : (
refer(el) ? ( <td className="ref">{refer(el)}</td> ) :
( <td colspan={el}/> )
))
}
</tr>
);
}
function OutinerRow({row, caption, selected, onSelect, curEvents}) {
return (
<tr>
<th colspan="3" className="ref">{caption}</th>
{
row.map(el => (
<td>
<Atom el={el}
className={selected(el.label) ? 'selected' : ''}
onClick={ev => onSelect(el.label)} {...curEvents(el)}/>
</td>
))
}
<td></td>
</tr>
);
}
function AtomInfo({el, isInfo}) {
const numberStyle = { color: el.color || 'black', 'font-size': '1.2em' };
const elemStyle = { color: el.color || 'black', 'font-weight': 'bold', 'font-size': '2em' };
return (
<div className={`atom-info ${isInfo ? '' : 'none'}`}>
<div style={numberStyle}>{element.map[el.label]}</div>
<span style={elemStyle}>{el.label}</span><br/>
{el.title}<br/>
{el.atomic_mass}
</div>
);
}
class PeriodTable extends Component {
constructor(props) {
super(props);
let genType = !!this.props.pseudo ? 'gen' : null;
this.state = {
type: props.type || genType || 'atom',
value: props.values || props.label || null,
cur: element[2],
isInfo: false
};
this.firstType = true;
}
changeType(type) {
if (this.firstType)
return this.firstType = false;
let pl = this.state.type === 'list' || this.state.type === 'not-list';
let l = type === 'list' || type === 'not-list';
if (l && pl)
this.setState({type});
else
this.setState({
type,
value: type === 'atom' || type === 'gen' ? null : []
});
}
selected(label) {
let {type, value} = this.state;
return (type === 'atom' || type === 'gen') ? value === label :
value.includes(label);
}
onSelect(label) {
let {type, value} = this.state;
if (type === 'atom' || type === 'gen')
this.setState({ value: label });
else {
let i = value.indexOf(label);
if (i < 0)
value.push(label);
else
value.splice(i, 1);
this.setState({ value });
}
}
result() {
let {type, value} = this.state;
if (type === 'atom')
return value ? { label: value, pseudo: null } : null;
else if (type === 'gen')
return value ? { type, label: value, pseudo: value} : null;
else
return value.length ? { type, values: value } : null;
}
curEvents = (el) => {
return {
onMouseEnter: () => this.setState({ cur: el, isInfo: true }),
onMouseLeave: () => this.setState({ isInfo: false })
};
};
render () {
const tabs = ['Table', 'Extended'];
let { type } = this.state;
return (
<Dialog title="Periodic table" className="elements-table"
params={this.props} result={() => this.result()}>
<Tabs className="tabs" captions={tabs} tabIndex={type !== 'gen' ? 0 : 1}
changeTab={(i) => this.changeType(i === 0 ? 'atom' : 'gen')}>
<div className="period-table">
<table summary="Periodic table of the chemical elements">
<Header/>
<AtomInfo el={this.state.cur} isInfo={this.state.isInfo}/>
{
main.map((row, i) => (
<MainRow row={row} caption={i + 1}
refer={o => o === 1 && (i === 5 ? '*' : '**')}
curEvents={this.curEvents}
selected={l => this.selected(l)}
onSelect={l => this.onSelect(l)}/>
))
}
<OutinerRow row={lanthanides} caption="*"
curEvents={this.curEvents}
selected={l => this.selected(l)}
onSelect={l => this.onSelect(l)}/>
<OutinerRow row={actinides} caption="**"
curEvents={this.curEvents}
selected={l => this.selected(l)}
onSelect={l => this.onSelect(l)}/>
</table>
<TypeChoise value={type}
onChange={t => this.changeType(t) }/>
</div>
<GenericGroups className="generic-groups"
selected={this.selected.bind(this)}
onSelect={this.onSelect.bind(this)}/>
</Tabs>
</Dialog>
);
}
}
function rowPartition(elements) {
return elements.reduce(function (res, el) {
let row = res[el.period - 1];
if (!row)
res.push([el]);
else {
if (beforeSpan[el.label])
row.push(beforeSpan[el.label]);
row.push(el);
}
return res;
}, []);
}
function mapSelectionToProps(editor) {
const selection = editor.selection();
if (selection && Object.keys(selection).length === 1 &&
selection.atoms && Object.keys(selection.atoms).length === 1) {
let struct = editor.struct();
let atom = struct.atoms.get(selection.atoms[0]);
return { ...fromElement(atom) }
}
return {};
}
export default connect(
(store, props) => {
if (props.values || props.label) return {};
return mapSelectionToProps(store.editor);
},
(dispatch, props) => ({
onOk: (res) => {
if (!res.type || res.type === 'atom') dispatch(addAtoms(res.label));
dispatch(onAction({ tool: 'atom', opts: toElement(res) }));
props.onOk(res);
}
})
)(PeriodTable);