react-cheminfo 0.17.0 → 0.19.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/about/core/about.d.ts +9 -0
- package/lib/about/core/about.d.ts.map +1 -1
- package/lib/about/core/about.js +1 -0
- package/lib/about/core/about.js.map +1 -1
- package/lib/about/core/index.d.ts +1 -0
- package/lib/about/core/index.d.ts.map +1 -1
- package/lib/about/core/index.js +1 -0
- package/lib/about/core/index.js.map +1 -1
- package/lib/about/core/repository.d.ts +14 -0
- package/lib/about/core/repository.d.ts.map +1 -0
- package/lib/about/core/repository.js +21 -0
- package/lib/about/core/repository.js.map +1 -0
- package/lib/about/ui/AboutBuild.d.ts +5 -4
- package/lib/about/ui/AboutBuild.d.ts.map +1 -1
- package/lib/about/ui/AboutBuild.js +10 -10
- package/lib/about/ui/AboutBuild.js.map +1 -1
- package/lib/about/ui/AboutPage.d.ts.map +1 -1
- package/lib/about/ui/AboutPage.js +7 -8
- package/lib/about/ui/AboutPage.js.map +1 -1
- package/lib/about/ui/AboutVersion.d.ts +25 -0
- package/lib/about/ui/AboutVersion.d.ts.map +1 -0
- package/lib/about/ui/AboutVersion.js +31 -0
- package/lib/about/ui/AboutVersion.js.map +1 -0
- package/lib/about/ui/index.d.ts +2 -0
- package/lib/about/ui/index.d.ts.map +1 -1
- package/lib/about/ui/index.js +1 -0
- package/lib/about/ui/index.js.map +1 -1
- package/lib/build/core/buildInfo.d.ts +16 -0
- package/lib/build/core/buildInfo.d.ts.map +1 -1
- package/lib/build/core/buildInfo.js +21 -0
- package/lib/build/core/buildInfo.js.map +1 -1
- package/lib/build/core/index.d.ts +1 -1
- package/lib/build/core/index.d.ts.map +1 -1
- package/lib/build/core/index.js +1 -1
- package/lib/build/core/index.js.map +1 -1
- package/lib/build/vite/buildInfo.d.ts.map +1 -1
- package/lib/build/vite/buildInfo.js +2 -2
- package/lib/build/vite/buildInfo.js.map +1 -1
- package/lib/ecosystem/core/sites.d.ts.map +1 -1
- package/lib/ecosystem/core/sites.js +12 -0
- package/lib/ecosystem/core/sites.js.map +1 -1
- package/lib/ecosystem/core/types.d.ts +8 -0
- package/lib/ecosystem/core/types.d.ts.map +1 -1
- package/lib/molecule3d/core/camera.d.ts +89 -0
- package/lib/molecule3d/core/camera.d.ts.map +1 -0
- package/lib/molecule3d/core/camera.js +172 -0
- package/lib/molecule3d/core/camera.js.map +1 -0
- package/lib/molecule3d/core/index.d.ts +2 -0
- package/lib/molecule3d/core/index.d.ts.map +1 -1
- package/lib/molecule3d/core/index.js +1 -0
- package/lib/molecule3d/core/index.js.map +1 -1
- package/lib/molecule3d/ui/MoleculeCanvas3D.d.ts.map +1 -1
- package/lib/molecule3d/ui/MoleculeCanvas3D.js +14 -37
- package/lib/molecule3d/ui/MoleculeCanvas3D.js.map +1 -1
- package/lib/molecule3d/ui/camera.d.ts +35 -2
- package/lib/molecule3d/ui/camera.d.ts.map +1 -1
- package/lib/molecule3d/ui/camera.js +113 -4
- package/lib/molecule3d/ui/camera.js.map +1 -1
- package/lib/molecule3d/ui/captureScene.d.ts +14 -0
- package/lib/molecule3d/ui/captureScene.d.ts.map +1 -0
- package/lib/molecule3d/ui/captureScene.js +24 -0
- package/lib/molecule3d/ui/captureScene.js.map +1 -0
- package/lib/molecule3d/ui/createViewer.d.ts +18 -0
- package/lib/molecule3d/ui/createViewer.d.ts.map +1 -0
- package/lib/molecule3d/ui/createViewer.js +19 -0
- package/lib/molecule3d/ui/createViewer.js.map +1 -0
- package/lib/molecule3d/ui/moleculeViewer3DProps.d.ts +21 -0
- package/lib/molecule3d/ui/moleculeViewer3DProps.d.ts.map +1 -1
- package/lib/molecule3d/ui/useMoleculeScene.d.ts +47 -0
- package/lib/molecule3d/ui/useMoleculeScene.d.ts.map +1 -0
- package/lib/molecule3d/ui/useMoleculeScene.js +82 -0
- package/lib/molecule3d/ui/useMoleculeScene.js.map +1 -0
- package/lib/molecule3d/ui/useSharedCamera.d.ts +52 -0
- package/lib/molecule3d/ui/useSharedCamera.d.ts.map +1 -0
- package/lib/molecule3d/ui/useSharedCamera.js +82 -0
- package/lib/molecule3d/ui/useSharedCamera.js.map +1 -0
- package/lib/molecule3d/ui/viewer.d.ts +21 -15
- package/lib/molecule3d/ui/viewer.d.ts.map +1 -1
- package/lib/molecule3d/ui/viewer.js +41 -47
- package/lib/molecule3d/ui/viewer.js.map +1 -1
- package/lib/molecule3d/ui/viewerSpec.d.ts +17 -0
- package/lib/molecule3d/ui/viewerSpec.d.ts.map +1 -0
- package/lib/molecule3d/ui/viewerSpec.js +34 -0
- package/lib/molecule3d/ui/viewerSpec.js.map +1 -0
- package/lib/structure/core/editorGuide.d.ts +69 -0
- package/lib/structure/core/editorGuide.d.ts.map +1 -0
- package/lib/structure/core/editorGuide.js +162 -0
- package/lib/structure/core/editorGuide.js.map +1 -0
- package/lib/structure/core/editorToolbar.d.ts +45 -0
- package/lib/structure/core/editorToolbar.d.ts.map +1 -0
- package/lib/structure/core/editorToolbar.js +132 -0
- package/lib/structure/core/editorToolbar.js.map +1 -0
- package/lib/structure/core/editorToolbarGeometry.d.ts +36 -0
- package/lib/structure/core/editorToolbarGeometry.d.ts.map +1 -0
- package/lib/structure/core/editorToolbarGeometry.js +61 -0
- package/lib/structure/core/editorToolbarGeometry.js.map +1 -0
- package/lib/structure/core/index.d.ts +6 -0
- package/lib/structure/core/index.d.ts.map +1 -1
- package/lib/structure/core/index.js +3 -0
- package/lib/structure/core/index.js.map +1 -1
- package/lib/structure/core/pendingCall.d.ts +21 -0
- package/lib/structure/core/pendingCall.d.ts.map +1 -0
- package/lib/structure/core/pendingCall.js +37 -0
- package/lib/structure/core/pendingCall.js.map +1 -0
- package/lib/structure/ui/EditorCanvas.d.ts +1 -16
- package/lib/structure/ui/EditorCanvas.d.ts.map +1 -1
- package/lib/structure/ui/EditorCanvas.js +5 -18
- package/lib/structure/ui/EditorCanvas.js.map +1 -1
- package/lib/structure/ui/EditorHelpButton.d.ts +22 -0
- package/lib/structure/ui/EditorHelpButton.d.ts.map +1 -0
- package/lib/structure/ui/EditorHelpButton.js +38 -0
- package/lib/structure/ui/EditorHelpButton.js.map +1 -0
- package/lib/structure/ui/KeyCaps.d.ts +12 -0
- package/lib/structure/ui/KeyCaps.d.ts.map +1 -0
- package/lib/structure/ui/KeyCaps.js +29 -0
- package/lib/structure/ui/KeyCaps.js.map +1 -0
- package/lib/structure/ui/StructureEditor.d.ts +9 -2
- package/lib/structure/ui/StructureEditor.d.ts.map +1 -1
- package/lib/structure/ui/StructureEditor.js +17 -24
- package/lib/structure/ui/StructureEditor.js.map +1 -1
- package/lib/structure/ui/StructureEditorHelp.d.ts +18 -0
- package/lib/structure/ui/StructureEditorHelp.d.ts.map +1 -0
- package/lib/structure/ui/StructureEditorHelp.js +68 -0
- package/lib/structure/ui/StructureEditorHelp.js.map +1 -0
- package/lib/structure/ui/ToolbarTooltip.d.ts +20 -0
- package/lib/structure/ui/ToolbarTooltip.d.ts.map +1 -0
- package/lib/structure/ui/ToolbarTooltip.js +49 -0
- package/lib/structure/ui/ToolbarTooltip.js.map +1 -0
- package/lib/structure/ui/editorChange.d.ts +52 -0
- package/lib/structure/ui/editorChange.d.ts.map +1 -0
- package/lib/structure/ui/editorChange.js +34 -0
- package/lib/structure/ui/editorChange.js.map +1 -0
- package/lib/structure/ui/index.d.ts +3 -1
- package/lib/structure/ui/index.d.ts.map +1 -1
- package/lib/structure/ui/index.js +1 -0
- package/lib/structure/ui/index.js.map +1 -1
- package/lib/structure/ui/useToolbarFloor.d.ts.map +1 -1
- package/lib/structure/ui/useToolbarFloor.js +6 -45
- package/lib/structure/ui/useToolbarFloor.js.map +1 -1
- package/lib/structure/ui/useToolbarHover.d.ts +28 -0
- package/lib/structure/ui/useToolbarHover.d.ts.map +1 -0
- package/lib/structure/ui/useToolbarHover.js +96 -0
- package/lib/structure/ui/useToolbarHover.js.map +1 -0
- package/lib/structure/ui/watchToolbar.d.ts +17 -0
- package/lib/structure/ui/watchToolbar.d.ts.map +1 -0
- package/lib/structure/ui/watchToolbar.js +56 -0
- package/lib/structure/ui/watchToolbar.js.map +1 -0
- package/package.json +1 -1
- package/src/about/core/about.ts +11 -0
- package/src/about/core/index.ts +1 -0
- package/src/about/core/repository.ts +22 -0
- package/src/about/ui/AboutBuild.tsx +13 -20
- package/src/about/ui/AboutPage.tsx +30 -22
- package/src/about/ui/AboutVersion.tsx +67 -0
- package/src/about/ui/index.ts +2 -0
- package/src/build/core/buildInfo.ts +24 -0
- package/src/build/core/index.ts +6 -1
- package/src/build/vite/buildInfo.ts +2 -2
- package/src/ecosystem/core/sites.ts +12 -0
- package/src/ecosystem/core/types.ts +8 -0
- package/src/molecule3d/core/camera.ts +212 -0
- package/src/molecule3d/core/index.ts +14 -0
- package/src/molecule3d/ui/MoleculeCanvas3D.tsx +14 -43
- package/src/molecule3d/ui/camera.ts +128 -7
- package/src/molecule3d/ui/captureScene.ts +31 -0
- package/src/molecule3d/ui/createViewer.ts +24 -0
- package/src/molecule3d/ui/moleculeViewer3DProps.ts +21 -0
- package/src/molecule3d/ui/useMoleculeScene.ts +141 -0
- package/src/molecule3d/ui/useSharedCamera.ts +121 -0
- package/src/molecule3d/ui/viewer.ts +45 -50
- package/src/molecule3d/ui/viewerSpec.ts +38 -0
- package/src/structure/core/editorGuide.ts +221 -0
- package/src/structure/core/editorToolbar.ts +175 -0
- package/src/structure/core/editorToolbarGeometry.ts +80 -0
- package/src/structure/core/index.ts +29 -0
- package/src/structure/core/pendingCall.ts +59 -0
- package/src/structure/ui/EditorCanvas.tsx +9 -35
- package/src/structure/ui/EditorHelpButton.tsx +70 -0
- package/src/structure/ui/KeyCaps.tsx +40 -0
- package/src/structure/ui/StructureEditor.tsx +39 -21
- package/src/structure/ui/StructureEditorHelp.tsx +136 -0
- package/src/structure/ui/ToolbarTooltip.tsx +95 -0
- package/src/structure/ui/editorChange.ts +81 -0
- package/src/structure/ui/index.ts +5 -1
- package/src/structure/ui/useToolbarFloor.ts +7 -46
- package/src/structure/ui/useToolbarHover.ts +134 -0
- package/src/structure/ui/watchToolbar.ts +59 -0
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What each button of the openchemlib canvas editor's toolbar does.
|
|
3
|
+
*
|
|
4
|
+
* The toolbar is one canvas painted from a sprite, so there is no element per
|
|
5
|
+
* button to carry a title: the button under the pointer is found from where
|
|
6
|
+
* the pointer is (see `editorToolbarGeometry.ts`) and its entry here is what
|
|
7
|
+
* the tooltip says.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Which editor a button works in. The reaction-only button is drawn greyed in
|
|
12
|
+
* a molecule editor, and the text tool is greyed in every editor react-ocl
|
|
13
|
+
* builds, since neither enables drawing objects.
|
|
14
|
+
*/
|
|
15
|
+
export type EditorToolbarAvailability = 'always' | 'reaction' | 'never';
|
|
16
|
+
|
|
17
|
+
/** One button of the toolbar. */
|
|
18
|
+
export interface EditorToolbarButton {
|
|
19
|
+
/** What the button is called. */
|
|
20
|
+
name: string;
|
|
21
|
+
/** What it does, in a sentence or two. */
|
|
22
|
+
description: string;
|
|
23
|
+
/**
|
|
24
|
+
* Keys that pick the tool, pressed while the pointer is over empty canvas.
|
|
25
|
+
* `Mod` stands for ⌘ on a Mac and Ctrl elsewhere.
|
|
26
|
+
* @default undefined — no key
|
|
27
|
+
*/
|
|
28
|
+
keys?: readonly string[];
|
|
29
|
+
/**
|
|
30
|
+
* Which editor the button works in.
|
|
31
|
+
* @default 'always'
|
|
32
|
+
*/
|
|
33
|
+
availability?: EditorToolbarAvailability;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* The buttons, in the order the toolbar numbers them: down the first column,
|
|
38
|
+
* then down the second.
|
|
39
|
+
*/
|
|
40
|
+
export const EDITOR_TOOLBAR_BUTTONS: readonly EditorToolbarButton[] = [
|
|
41
|
+
{ name: 'Clear', description: 'Erase the whole drawing.' },
|
|
42
|
+
{
|
|
43
|
+
name: 'Clean up',
|
|
44
|
+
description:
|
|
45
|
+
'Redraw the structure with fresh 2D coordinates. With part of it selected, the rest keeps its layout.',
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
name: 'Select',
|
|
49
|
+
description:
|
|
50
|
+
'Drag around atoms to select them: hold Alt for a rectangle, Shift to add to the selection. Drag a selection to move it, Shift-drag to copy it.',
|
|
51
|
+
keys: ['Space'],
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
name: 'Unknown configuration',
|
|
55
|
+
description:
|
|
56
|
+
'Click a stereocentre to mark its configuration as unknown; click again to undo it.',
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
name: 'Delete',
|
|
60
|
+
description: 'Click an atom or a bond to delete it.',
|
|
61
|
+
keys: ['0'],
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
name: 'Single bond',
|
|
65
|
+
description:
|
|
66
|
+
'Click empty space for a new bond, or drag from an atom. Clicking a bond again makes it double, then triple.',
|
|
67
|
+
keys: ['1'],
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
name: 'Up bond',
|
|
71
|
+
description:
|
|
72
|
+
'Wedged stereo bond, pointing toward the viewer. Its narrow end goes on the stereocentre.',
|
|
73
|
+
keys: ['u'],
|
|
74
|
+
},
|
|
75
|
+
ring('3-membered ring', '3'),
|
|
76
|
+
ring('5-membered ring', '5'),
|
|
77
|
+
ring('7-membered ring', '7'),
|
|
78
|
+
{
|
|
79
|
+
name: 'Positive charge',
|
|
80
|
+
description: 'Click an atom to raise its charge by one.',
|
|
81
|
+
keys: ['+'],
|
|
82
|
+
},
|
|
83
|
+
atom('Carbon', 'C', 'c'),
|
|
84
|
+
atom('Nitrogen', 'N', 'n'),
|
|
85
|
+
atom('Oxygen', 'O', 'o'),
|
|
86
|
+
atom('Fluorine', 'F', 'f'),
|
|
87
|
+
atom('Bromine', 'Br', 'b'),
|
|
88
|
+
atom('Hydrogen', 'H'),
|
|
89
|
+
{ name: 'Undo', description: 'Revert the last change.', keys: ['Mod', 'Z'] },
|
|
90
|
+
{
|
|
91
|
+
name: 'Zoom and rotate',
|
|
92
|
+
description:
|
|
93
|
+
'Press where the centre should be, then drag up or down to zoom and sideways to rotate. With a selection, only that part moves.',
|
|
94
|
+
keys: ['z'],
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
name: 'Atom mapping',
|
|
98
|
+
description:
|
|
99
|
+
'Drag from a reactant atom to the same atom in a product: both get the same number. Click a mapped atom to unmap it.',
|
|
100
|
+
keys: ['m'],
|
|
101
|
+
availability: 'reaction',
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
name: 'Enhanced stereo',
|
|
105
|
+
description:
|
|
106
|
+
'Click a stereo bond to say what its centre means: abs, this enantiomer; &, both (racemic); or, one of the two. Press the button again to switch between the three.',
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
name: 'Text',
|
|
110
|
+
description: 'Place a text label in the drawing.',
|
|
111
|
+
availability: 'never',
|
|
112
|
+
},
|
|
113
|
+
{
|
|
114
|
+
name: 'Chain',
|
|
115
|
+
description:
|
|
116
|
+
'Drag from empty space or from an atom to draw a zigzag carbon chain.',
|
|
117
|
+
keys: ['2'],
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
name: 'Down bond',
|
|
121
|
+
description:
|
|
122
|
+
'Hashed stereo bond, pointing away from the viewer. Its narrow end goes on the stereocentre.',
|
|
123
|
+
keys: ['d'],
|
|
124
|
+
},
|
|
125
|
+
ring('4-membered ring', '4'),
|
|
126
|
+
ring('6-membered ring', '6'),
|
|
127
|
+
ring('Benzene ring', 'a'),
|
|
128
|
+
{
|
|
129
|
+
name: 'Negative charge',
|
|
130
|
+
description: 'Click an atom to lower its charge by one.',
|
|
131
|
+
keys: ['-'],
|
|
132
|
+
},
|
|
133
|
+
atom('Silicon', 'Si'),
|
|
134
|
+
atom('Phosphorus', 'P', 'p'),
|
|
135
|
+
atom('Sulfur', 'S', 's'),
|
|
136
|
+
atom('Chlorine', 'Cl', 'l'),
|
|
137
|
+
atom('Iodine', 'I', 'i'),
|
|
138
|
+
{
|
|
139
|
+
name: 'Any atom',
|
|
140
|
+
description:
|
|
141
|
+
'Pick any element, isotope, valence or radical in a dialog, then click to place it or to change an atom. Alt-click an atom to edit its own properties.',
|
|
142
|
+
keys: ['.'],
|
|
143
|
+
},
|
|
144
|
+
];
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Whether a button can be used in an editor of the given kind.
|
|
148
|
+
* @param button - The button.
|
|
149
|
+
* @param mode - The kind of editor.
|
|
150
|
+
* @returns True when pressing the button selects its tool.
|
|
151
|
+
*/
|
|
152
|
+
export function isEditorToolbarButtonAvailable(
|
|
153
|
+
button: EditorToolbarButton,
|
|
154
|
+
mode: 'molecule' | 'reaction',
|
|
155
|
+
): boolean {
|
|
156
|
+
const { availability = 'always' } = button;
|
|
157
|
+
if (availability === 'always') return true;
|
|
158
|
+
return availability === 'reaction' && mode === 'reaction';
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function ring(name: string, key: string): EditorToolbarButton {
|
|
162
|
+
return {
|
|
163
|
+
name,
|
|
164
|
+
description:
|
|
165
|
+
'Click empty space for a new ring, a bond to fuse one onto it, or an atom for a spiro ring.',
|
|
166
|
+
keys: [key],
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function atom(name: string, symbol: string, key?: string): EditorToolbarButton {
|
|
171
|
+
const description = `Click empty space for a new ${symbol} atom, or an atom to turn it into ${symbol}.`;
|
|
172
|
+
return key === undefined
|
|
173
|
+
? { name, description }
|
|
174
|
+
: { name, description, keys: [key] };
|
|
175
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Where the openchemlib editor draws each toolbar button.
|
|
3
|
+
*
|
|
4
|
+
* The toolbar is two columns of seventeen square buttons inside a thin border,
|
|
5
|
+
* numbered down the first column and then down the second, as
|
|
6
|
+
* `EDITOR_TOOLBAR_BUTTONS` lists them. Every length here is in CSS pixels at
|
|
7
|
+
* the size openchemlib draws the toolbar, and is scaled by the height the
|
|
8
|
+
* toolbar is measured at, so a toolbar drawn larger still maps correctly.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
/** Buttons in one column of the toolbar. */
|
|
12
|
+
export const EDITOR_TOOLBAR_ROWS = 17;
|
|
13
|
+
|
|
14
|
+
/** Columns of the toolbar. */
|
|
15
|
+
export const EDITOR_TOOLBAR_COLUMNS = 2;
|
|
16
|
+
|
|
17
|
+
/** Side of one button, in CSS pixels. */
|
|
18
|
+
const BUTTON_SIZE = 21;
|
|
19
|
+
|
|
20
|
+
/** Border around the buttons, in CSS pixels. */
|
|
21
|
+
const BORDER = 2;
|
|
22
|
+
|
|
23
|
+
/** Height of the toolbar as openchemlib draws it, in CSS pixels. */
|
|
24
|
+
const DRAWN_HEIGHT = EDITOR_TOOLBAR_ROWS * BUTTON_SIZE + 2 * BORDER;
|
|
25
|
+
|
|
26
|
+
/** Where a button is drawn, in CSS pixels from the toolbar's top left corner. */
|
|
27
|
+
export interface EditorToolbarButtonBox {
|
|
28
|
+
left: number;
|
|
29
|
+
top: number;
|
|
30
|
+
width: number;
|
|
31
|
+
height: number;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* The button under a point of the toolbar.
|
|
36
|
+
* @param x - Distance from the toolbar's left edge, in CSS pixels.
|
|
37
|
+
* @param y - Distance from the toolbar's top edge, in CSS pixels.
|
|
38
|
+
* @param toolbarHeight - Height the toolbar is measured at, in CSS pixels.
|
|
39
|
+
* @returns The button's index, or -1 when the point is on no button.
|
|
40
|
+
*/
|
|
41
|
+
export function editorToolbarButtonAt(
|
|
42
|
+
x: number,
|
|
43
|
+
y: number,
|
|
44
|
+
toolbarHeight: number,
|
|
45
|
+
): number {
|
|
46
|
+
const scale = scaleOf(toolbarHeight);
|
|
47
|
+
if (scale === 0) return -1;
|
|
48
|
+
const column = Math.floor((x / scale - BORDER) / BUTTON_SIZE);
|
|
49
|
+
const row = Math.floor((y / scale - BORDER) / BUTTON_SIZE);
|
|
50
|
+
if (column < 0 || column >= EDITOR_TOOLBAR_COLUMNS) return -1;
|
|
51
|
+
if (row < 0 || row >= EDITOR_TOOLBAR_ROWS) return -1;
|
|
52
|
+
return column * EDITOR_TOOLBAR_ROWS + row;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Where a button is drawn.
|
|
57
|
+
* @param button - The button's index.
|
|
58
|
+
* @param toolbarHeight - Height the toolbar is measured at, in CSS pixels.
|
|
59
|
+
* @returns The button's box, relative to the toolbar.
|
|
60
|
+
*/
|
|
61
|
+
export function editorToolbarButtonBox(
|
|
62
|
+
button: number,
|
|
63
|
+
toolbarHeight: number,
|
|
64
|
+
): EditorToolbarButtonBox {
|
|
65
|
+
const scale = scaleOf(toolbarHeight);
|
|
66
|
+
const column = Math.floor(button / EDITOR_TOOLBAR_ROWS);
|
|
67
|
+
const row = button % EDITOR_TOOLBAR_ROWS;
|
|
68
|
+
return {
|
|
69
|
+
left: (BORDER + column * BUTTON_SIZE) * scale,
|
|
70
|
+
top: (BORDER + row * BUTTON_SIZE) * scale,
|
|
71
|
+
width: BUTTON_SIZE * scale,
|
|
72
|
+
height: BUTTON_SIZE * scale,
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function scaleOf(toolbarHeight: number): number {
|
|
77
|
+
return Number.isFinite(toolbarHeight) && toolbarHeight > 0
|
|
78
|
+
? toolbarHeight / DRAWN_HEIGHT
|
|
79
|
+
: 0;
|
|
80
|
+
}
|
|
@@ -1,5 +1,34 @@
|
|
|
1
1
|
export type { AtomLabelPlacement, LabelledMolecule } from './atomLabels.ts';
|
|
2
2
|
export { applyAtomLabels, customAtomLabel } from './atomLabels.ts';
|
|
3
|
+
export type {
|
|
4
|
+
EditorGesture,
|
|
5
|
+
EditorGuideKey,
|
|
6
|
+
EditorGuideLink,
|
|
7
|
+
EditorGuideOptions,
|
|
8
|
+
EditorGuideScope,
|
|
9
|
+
EditorGuideSection,
|
|
10
|
+
} from './editorGuide.ts';
|
|
11
|
+
export {
|
|
12
|
+
STRUCTURE_EDITOR_DOCS,
|
|
13
|
+
STRUCTURE_EDITOR_GUIDE,
|
|
14
|
+
editorGuideSections,
|
|
15
|
+
editorKeyLabel,
|
|
16
|
+
} from './editorGuide.ts';
|
|
17
|
+
export type {
|
|
18
|
+
EditorToolbarAvailability,
|
|
19
|
+
EditorToolbarButton,
|
|
20
|
+
} from './editorToolbar.ts';
|
|
21
|
+
export {
|
|
22
|
+
EDITOR_TOOLBAR_BUTTONS,
|
|
23
|
+
isEditorToolbarButtonAvailable,
|
|
24
|
+
} from './editorToolbar.ts';
|
|
25
|
+
export type { EditorToolbarButtonBox } from './editorToolbarGeometry.ts';
|
|
26
|
+
export {
|
|
27
|
+
EDITOR_TOOLBAR_COLUMNS,
|
|
28
|
+
EDITOR_TOOLBAR_ROWS,
|
|
29
|
+
editorToolbarButtonAt,
|
|
30
|
+
editorToolbarButtonBox,
|
|
31
|
+
} from './editorToolbarGeometry.ts';
|
|
3
32
|
export type { EditorLine, EditorValue } from './editorValue.ts';
|
|
4
33
|
export type { IdCodeValue } from './editorValue.ts';
|
|
5
34
|
export { isEmptyIdCode, splitEditorValue, splitIdCode } from './editorValue.ts';
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/** A call held back until its value has been still, which can be cut short. */
|
|
2
|
+
export interface PendingCall<TValue> {
|
|
3
|
+
/**
|
|
4
|
+
* Hold a value back, replacing whichever one was waiting.
|
|
5
|
+
* @param value - The value to deliver.
|
|
6
|
+
* @param delay - How long to wait, in milliseconds; `0` or less delivers now.
|
|
7
|
+
* @param deliver - What the value is handed to.
|
|
8
|
+
*/
|
|
9
|
+
push: (
|
|
10
|
+
value: TValue,
|
|
11
|
+
delay: number,
|
|
12
|
+
deliver: (value: TValue) => void,
|
|
13
|
+
) => void;
|
|
14
|
+
/** Forget the value waiting, which is then never delivered. */
|
|
15
|
+
drop: () => void;
|
|
16
|
+
/** Deliver the value waiting now rather than when its delay runs out. */
|
|
17
|
+
flush: () => void;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* A trailing debounce for one value at a time, whose waiting value can be
|
|
22
|
+
* delivered early or thrown away.
|
|
23
|
+
* @returns The pending call, holding nothing.
|
|
24
|
+
*/
|
|
25
|
+
export function createPendingCall<TValue>(): PendingCall<TValue> {
|
|
26
|
+
let pending: {
|
|
27
|
+
value: TValue;
|
|
28
|
+
deliver: (value: TValue) => void;
|
|
29
|
+
timer: ReturnType<typeof setTimeout>;
|
|
30
|
+
} | null = null;
|
|
31
|
+
|
|
32
|
+
function drop(): void {
|
|
33
|
+
if (pending === null) return;
|
|
34
|
+
clearTimeout(pending.timer);
|
|
35
|
+
pending = null;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return {
|
|
39
|
+
push(value, delay, deliver) {
|
|
40
|
+
drop();
|
|
41
|
+
if (delay <= 0) {
|
|
42
|
+
deliver(value);
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
const timer = setTimeout(() => {
|
|
46
|
+
pending = null;
|
|
47
|
+
deliver(value);
|
|
48
|
+
}, delay);
|
|
49
|
+
pending = { value, deliver, timer };
|
|
50
|
+
},
|
|
51
|
+
drop,
|
|
52
|
+
flush() {
|
|
53
|
+
if (pending === null) return;
|
|
54
|
+
const { value, deliver } = pending;
|
|
55
|
+
drop();
|
|
56
|
+
deliver(value);
|
|
57
|
+
},
|
|
58
|
+
};
|
|
59
|
+
}
|
|
@@ -7,23 +7,11 @@ import type {
|
|
|
7
7
|
} from 'react-ocl';
|
|
8
8
|
import { CanvasMoleculeEditor, CanvasReactionEditor } from 'react-ocl';
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
/** Which kind of editor produced it. */
|
|
16
|
-
mode: StructureEditorMode;
|
|
17
|
-
/**
|
|
18
|
-
* The idCode, with the coordinates the editor laid out written after a
|
|
19
|
-
* space. `splitIdCode` takes the two apart.
|
|
20
|
-
*/
|
|
21
|
-
idCode: string;
|
|
22
|
-
/** The V2000 molfile of a molecule, or the RXN file of a reaction. */
|
|
23
|
-
molfile: string;
|
|
24
|
-
/** The SMILES of a molecule, or the reaction SMILES. */
|
|
25
|
-
smiles: string;
|
|
26
|
-
}
|
|
10
|
+
import type {
|
|
11
|
+
StructureEditorChange,
|
|
12
|
+
StructureEditorMode,
|
|
13
|
+
} from './editorChange.ts';
|
|
14
|
+
import { moleculeChange, reactionChange } from './editorChange.ts';
|
|
27
15
|
|
|
28
16
|
interface EditorCanvasProps {
|
|
29
17
|
/** Called on every stroke, with the editor read out synchronously. */
|
|
@@ -71,29 +59,15 @@ export function EditorCanvas(props: EditorCanvasProps): ReactElement {
|
|
|
71
59
|
} = props;
|
|
72
60
|
const [initialValue] = useState(value);
|
|
73
61
|
|
|
62
|
+
// The event describes the editor only while the callback runs, so it is read
|
|
63
|
+
// out now and whatever waiting there is happens afterwards.
|
|
74
64
|
const handleMolecule = useCallback(
|
|
75
|
-
(event: CanvasEditorOnChangeMolecule) =>
|
|
76
|
-
// The event describes the editor only while the callback runs, so every
|
|
77
|
-
// notation is read now and whatever waiting there is happens afterwards.
|
|
78
|
-
onChange({
|
|
79
|
-
mode: 'molecule',
|
|
80
|
-
idCode: event.getIdcode(),
|
|
81
|
-
molfile: event.getMolfile(),
|
|
82
|
-
smiles: event.getSmiles(),
|
|
83
|
-
});
|
|
84
|
-
},
|
|
65
|
+
(event: CanvasEditorOnChangeMolecule) => onChange(moleculeChange(event)),
|
|
85
66
|
[onChange],
|
|
86
67
|
);
|
|
87
68
|
|
|
88
69
|
const handleReaction = useCallback(
|
|
89
|
-
(event: CanvasEditorOnChangeReaction) =>
|
|
90
|
-
onChange({
|
|
91
|
-
mode: 'reaction',
|
|
92
|
-
idCode: event.getIdcode(),
|
|
93
|
-
molfile: event.getRxn(),
|
|
94
|
-
smiles: event.getSmiles(),
|
|
95
|
-
});
|
|
96
|
-
},
|
|
70
|
+
(event: CanvasEditorOnChangeReaction) => onChange(reactionChange(event)),
|
|
97
71
|
[onChange],
|
|
98
72
|
);
|
|
99
73
|
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { Button, PopoverNext, Tooltip } from '@blueprintjs/core';
|
|
2
|
+
import type { CSSProperties, ReactElement, RefObject } from 'react';
|
|
3
|
+
import { useEffect, useState } from 'react';
|
|
4
|
+
|
|
5
|
+
import { StructureEditorHelp } from './StructureEditorHelp.tsx';
|
|
6
|
+
import type { StructureEditorMode } from './editorChange.ts';
|
|
7
|
+
|
|
8
|
+
/** What {@link EditorHelpButton} needs. */
|
|
9
|
+
export interface EditorHelpButtonProps {
|
|
10
|
+
/** The element wrapping the editor, which F1 is listened for on. */
|
|
11
|
+
containerRef: RefObject<HTMLElement | null>;
|
|
12
|
+
/** Which editor the guide is written for. */
|
|
13
|
+
mode: StructureEditorMode;
|
|
14
|
+
/** Whether the editor draws a query fragment. */
|
|
15
|
+
fragment: boolean;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* The editor's help button, in the corner of the drawing, opening the guide to
|
|
20
|
+
* the mouse and the keyboard.
|
|
21
|
+
*
|
|
22
|
+
* F1 opens it too: openchemlib binds that key to a help dialog it does not
|
|
23
|
+
* implement on the web.
|
|
24
|
+
* @param props - See {@link EditorHelpButtonProps}.
|
|
25
|
+
* @returns The button and its popover.
|
|
26
|
+
*/
|
|
27
|
+
export function EditorHelpButton(props: EditorHelpButtonProps): ReactElement {
|
|
28
|
+
const { containerRef, mode, fragment } = props;
|
|
29
|
+
const [isOpen, setIsOpen] = useState(false);
|
|
30
|
+
|
|
31
|
+
useEffect(() => {
|
|
32
|
+
const container = containerRef.current;
|
|
33
|
+
if (container === null) return;
|
|
34
|
+
const onKeyDown = (event: KeyboardEvent): void => {
|
|
35
|
+
if (event.key !== 'F1') return;
|
|
36
|
+
event.preventDefault();
|
|
37
|
+
setIsOpen(true);
|
|
38
|
+
};
|
|
39
|
+
container.addEventListener('keydown', onKeyDown);
|
|
40
|
+
return () => container.removeEventListener('keydown', onKeyDown);
|
|
41
|
+
}, [containerRef]);
|
|
42
|
+
|
|
43
|
+
return (
|
|
44
|
+
<div style={CORNER_STYLE}>
|
|
45
|
+
<PopoverNext
|
|
46
|
+
isOpen={isOpen}
|
|
47
|
+
onInteraction={setIsOpen}
|
|
48
|
+
placement="left-start"
|
|
49
|
+
content={<StructureEditorHelp mode={mode} fragment={fragment} />}
|
|
50
|
+
>
|
|
51
|
+
<Tooltip content={LABEL} placement="left" disabled={isOpen}>
|
|
52
|
+
<Button
|
|
53
|
+
variant="minimal"
|
|
54
|
+
size="small"
|
|
55
|
+
icon="help"
|
|
56
|
+
aria-label={LABEL}
|
|
57
|
+
/>
|
|
58
|
+
</Tooltip>
|
|
59
|
+
</PopoverNext>
|
|
60
|
+
</div>
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const LABEL = 'Mouse and keyboard';
|
|
65
|
+
|
|
66
|
+
const CORNER_STYLE: CSSProperties = {
|
|
67
|
+
position: 'absolute',
|
|
68
|
+
top: 4,
|
|
69
|
+
right: 4,
|
|
70
|
+
};
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { Classes } from '@blueprintjs/core';
|
|
2
|
+
import type { CSSProperties, ReactElement } from 'react';
|
|
3
|
+
|
|
4
|
+
import { editorKeyLabel } from '../core/editorGuide.ts';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Keys pressed together, each drawn as a key cap, `Mod` as the modifier the
|
|
8
|
+
* editor listens to on this platform.
|
|
9
|
+
* @param props - The keys.
|
|
10
|
+
* @param props.keys - The keys, unique within the list.
|
|
11
|
+
* @returns The key caps.
|
|
12
|
+
*/
|
|
13
|
+
export function KeyCaps(props: { keys: readonly string[] }): ReactElement {
|
|
14
|
+
const isMac = isMacPlatform();
|
|
15
|
+
return (
|
|
16
|
+
<span style={KEYS_STYLE}>
|
|
17
|
+
{props.keys.map((key) => (
|
|
18
|
+
<kbd key={key} className={Classes.KEY}>
|
|
19
|
+
{editorKeyLabel(key, isMac)}
|
|
20
|
+
</kbd>
|
|
21
|
+
))}
|
|
22
|
+
</span>
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Whether the page runs on a Mac, decided as openchemlib decides it, so the
|
|
28
|
+
* cap shown is the modifier the editor actually reads: ⌘ there, Ctrl elsewhere.
|
|
29
|
+
* @returns True on a Mac.
|
|
30
|
+
*/
|
|
31
|
+
function isMacPlatform(): boolean {
|
|
32
|
+
return typeof navigator !== 'undefined' && navigator.platform === 'MacIntel';
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const KEYS_STYLE: CSSProperties = {
|
|
36
|
+
display: 'inline-flex',
|
|
37
|
+
alignItems: 'center',
|
|
38
|
+
gap: 3,
|
|
39
|
+
whiteSpace: 'nowrap',
|
|
40
|
+
};
|
|
@@ -8,15 +8,25 @@
|
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
import type { CSSProperties, ReactElement } from 'react';
|
|
11
|
-
import {
|
|
11
|
+
import {
|
|
12
|
+
Suspense,
|
|
13
|
+
lazy,
|
|
14
|
+
useCallback,
|
|
15
|
+
useEffect,
|
|
16
|
+
useRef,
|
|
17
|
+
useState,
|
|
18
|
+
} from 'react';
|
|
12
19
|
import type { CanvasEditorInputFormat } from 'react-ocl';
|
|
13
20
|
|
|
14
21
|
import { TOKEN } from '../../tokens/core/familyTokens.ts';
|
|
22
|
+
import { createPendingCall } from '../core/pendingCall.ts';
|
|
15
23
|
|
|
24
|
+
import { EditorHelpButton } from './EditorHelpButton.tsx';
|
|
25
|
+
import { ToolbarTooltip } from './ToolbarTooltip.tsx';
|
|
16
26
|
import type {
|
|
17
27
|
StructureEditorChange,
|
|
18
28
|
StructureEditorMode,
|
|
19
|
-
} from './
|
|
29
|
+
} from './editorChange.ts';
|
|
20
30
|
import { useToolbarFloor } from './useToolbarFloor.ts';
|
|
21
31
|
|
|
22
32
|
const EditorCanvas = lazy(async () => {
|
|
@@ -76,6 +86,12 @@ export interface StructureEditorProps {
|
|
|
76
86
|
* @default 'molecule'
|
|
77
87
|
*/
|
|
78
88
|
mode?: StructureEditorMode;
|
|
89
|
+
/**
|
|
90
|
+
* Explain each toolbar button in a tooltip, and put a button in the corner
|
|
91
|
+
* that opens a guide to the mouse and the keyboard, which F1 opens too.
|
|
92
|
+
* @default true
|
|
93
|
+
*/
|
|
94
|
+
help?: boolean;
|
|
79
95
|
/**
|
|
80
96
|
* Class the container carries, so a site can reach it from its stylesheet.
|
|
81
97
|
* @default undefined
|
|
@@ -90,7 +106,8 @@ export interface StructureEditorProps {
|
|
|
90
106
|
|
|
91
107
|
/**
|
|
92
108
|
* The canvas structure editor, sized to fill its container and never to hide
|
|
93
|
-
* part of its toolbar
|
|
109
|
+
* part of its toolbar, with a tooltip on every toolbar button and a guide to
|
|
110
|
+
* the keys.
|
|
94
111
|
* @param props - What to draw, when to reload it, and where to send it.
|
|
95
112
|
* @returns The editor.
|
|
96
113
|
*/
|
|
@@ -104,6 +121,7 @@ export function StructureEditor(props: StructureEditorProps): ReactElement {
|
|
|
104
121
|
debounce = 300,
|
|
105
122
|
minHeight = 320,
|
|
106
123
|
mode = 'molecule',
|
|
124
|
+
help = true,
|
|
107
125
|
className,
|
|
108
126
|
style,
|
|
109
127
|
} = props;
|
|
@@ -126,6 +144,16 @@ export function StructureEditor(props: StructureEditorProps): ReactElement {
|
|
|
126
144
|
value={value}
|
|
127
145
|
mode={mode}
|
|
128
146
|
/>
|
|
147
|
+
{help ? (
|
|
148
|
+
<>
|
|
149
|
+
<ToolbarTooltip containerRef={containerRef} mode={mode} />
|
|
150
|
+
<EditorHelpButton
|
|
151
|
+
containerRef={containerRef}
|
|
152
|
+
mode={mode}
|
|
153
|
+
fragment={fragment}
|
|
154
|
+
/>
|
|
155
|
+
</>
|
|
156
|
+
) : null}
|
|
129
157
|
</Suspense>
|
|
130
158
|
</div>
|
|
131
159
|
);
|
|
@@ -136,6 +164,8 @@ export function StructureEditor(props: StructureEditorProps): ReactElement {
|
|
|
136
164
|
*
|
|
137
165
|
* The editor's own event is read out before the wait starts, so what arrives
|
|
138
166
|
* late is a plain object rather than a handle on an editor that has moved on.
|
|
167
|
+
* An editor removed mid-burst — a tab switched, a card folded — still reports
|
|
168
|
+
* its last edit, which would otherwise be lost with the timer.
|
|
139
169
|
* @param onChange - What the caller wants told.
|
|
140
170
|
* @param delay - How long the drawing has to be still, in milliseconds.
|
|
141
171
|
* @param resetKey - Dropped edits: a burst still waiting when the caller
|
|
@@ -147,33 +177,21 @@ function useDebounced(
|
|
|
147
177
|
delay: number,
|
|
148
178
|
resetKey: number,
|
|
149
179
|
): (change: StructureEditorChange) => void {
|
|
150
|
-
const
|
|
180
|
+
const [pending] = useState(createPendingCall<StructureEditorChange>);
|
|
151
181
|
const onChangeRef = useRef(onChange);
|
|
152
182
|
useEffect(() => {
|
|
153
183
|
onChangeRef.current = onChange;
|
|
154
184
|
});
|
|
155
185
|
useEffect(() => {
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
timerRef.current = null;
|
|
160
|
-
}
|
|
161
|
-
};
|
|
162
|
-
}, [resetKey]);
|
|
186
|
+
pending.drop();
|
|
187
|
+
}, [pending, resetKey]);
|
|
188
|
+
useEffect(() => () => pending.flush(), [pending]);
|
|
163
189
|
|
|
164
190
|
return useCallback(
|
|
165
191
|
(change: StructureEditorChange) => {
|
|
166
|
-
|
|
167
|
-
if (delay <= 0) {
|
|
168
|
-
onChangeRef.current(change);
|
|
169
|
-
return;
|
|
170
|
-
}
|
|
171
|
-
timerRef.current = setTimeout(() => {
|
|
172
|
-
timerRef.current = null;
|
|
173
|
-
onChangeRef.current(change);
|
|
174
|
-
}, delay);
|
|
192
|
+
pending.push(change, delay, (value) => onChangeRef.current(value));
|
|
175
193
|
},
|
|
176
|
-
[delay],
|
|
194
|
+
[pending, delay],
|
|
177
195
|
);
|
|
178
196
|
}
|
|
179
197
|
|