forked from enviPath/enviPy
273 lines
7.2 KiB
JavaScript
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);
|