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

157 lines
4.3 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 { isEqual, debounce } from 'lodash/fp';
import molfile from '../../chem/molfile';
import keyNorm from '../keynorm';
import actions from '../action';
import * as clipArea from '../component/cliparea';
import * as structFormat from '../structformat';
import { onAction, openDialog, load } from './';
export function initKeydownListener(element) {
return function (dispatch, getState) {
const hotKeys = initHotKeys();
element.addEventListener('keydown', (event) => keyHandle(dispatch, getState, hotKeys, event));
}
}
/* HotKeys */
function keyHandle(dispatch, getState, hotKeys, event) {
const state = getState();
if (state.modal) return;
const editor = state.editor;
const actionState = state.actionState;
const actionTool = actionState.activeTool;
const key = keyNorm(event);
const atomsSelected = editor.selection() && editor.selection().atoms;
let group = null;
if (key && key.length === 1 && atomsSelected && key.match(/\w/)) {
console.assert(atomsSelected.length > 0);
openDialog(dispatch, 'labelEdit', { letter: key }).then(res => {
dispatch(onAction({ tool: 'atom', opts: res }));
});
event.preventDefault();
} else if (group = keyNorm.lookup(hotKeys, event)) {
let index = checkGroupOnTool(group, actionTool); // index currentTool in group || -1
index = (index + 1) % group.length;
let actName = group[index];
if (actionState[actName] && actionState[actName].disabled === true)
return event.preventDefault();
if (clipArea.actions.indexOf(actName) === -1) {
let newAction = actions[actName].action;
dispatch(onAction(newAction));
event.preventDefault();
} else if (window.clipboardData) // IE support
clipArea.exec(event);
}
}
function setHotKey(key, actName, hotKeys) {
if (Array.isArray(hotKeys[key]))
hotKeys[key].push(actName);
else
hotKeys[key] = [actName];
}
function initHotKeys() {
const hotKeys = {};
let act;
for (let actName in actions) {
act = actions[actName];
if (!act.shortcut) continue;
if (Array.isArray(act.shortcut))
act.shortcut.forEach(key => setHotKey(key, actName, hotKeys));
else
setHotKey(act.shortcut, actName, hotKeys);
}
return keyNorm(hotKeys);
}
function checkGroupOnTool(group, actionTool) {
let index = group.indexOf(actionTool.tool);
group.forEach((actName, i) => {
if (isEqual(actions[actName].action, actionTool))
index = i;
});
return index;
}
/* ClipArea */
export function initClipboard(dispatch, getState) {
const formats = Object.keys(structFormat.map).map(function (fmt) {
return structFormat.map[fmt].mime;
});
const debAction = debounce(0, (action) => dispatch( onAction(action) ));
const loadStruct = debounce(0, (structStr, opts) => dispatch( load(structStr, opts) ));
return {
formats: formats,
focused: function () {
return !getState().modal;
},
onCut: function () {
let data = clipData(getState().editor);
debAction({ tool: 'eraser', opts: 1 });
return data;
},
onCopy: function () {
let editor = getState().editor;
let data = clipData(editor);
editor.selection(null);
return data;
},
onPaste: function (data) {
const structStr = data['chemical/x-mdl-molfile'] ||
data['chemical/x-mdl-rxnfile'] ||
data['text/plain'];
if (structStr)
loadStruct(structStr, { fragment: true });
}
};
}
function clipData(editor) {
const res = {};
const struct = editor.structSelected();
if (struct.isBlank())
return null;
const type = struct.isReaction ?
'chemical/x-mdl-molfile' : 'chemical/x-mdl-rxnfile';
res['text/plain'] = res[type] = molfile.stringify(struct);
// res['chemical/x-daylight-smiles'] =
// smiles.stringify(struct);
return res;
}