UNPKG

@macrostrat/column-components

Version:

React rendering primitives for stratigraphic columns

295 lines (294 loc) 7.85 kB
import { useContext, createContext, useState } from "react"; import { EditableText } from "@blueprintjs/core"; import classNames from "classnames"; import h from "../hyper.js"; import { ForeignObject } from "../util/index.js"; import { NoteLayoutContext, NoteRect } from "./layout.js"; import { NoteConnector, NotePositioner } from "./connector.js"; import Draggable from "react-draggable"; import { hasSpan } from "./utils.js"; import { ErrorBoundary } from "@macrostrat/ui-components"; import { useModelEditor, ModelEditorProvider } from "../context/model-editor.js"; import { ColumnContext } from "../context/column.js"; const NoteEditorContext = createContext({ inEditMode: false }); const NoteTextEditor = function(props) { const { updateModel: updateModel2 } = useModelEditor(); const { note } = props; return h(EditableText, { multiline: true, className: "col-note-label note-editing", defaultValue: note.note, isEditing: true, onConfirm(newText) { return updateModel2({ note: { $set: newText } }); } }); }; function NoteEditorProvider(props) { let { children, inEditMode = false, noteEditor } = props; const { notes } = useContext(NoteLayoutContext); const [editingNote, setEditingNote] = useState(null); const deleteNote = function() { const val = editingNote; setEditingNote(null); return props.onDeleteNote?.(val); }; const onCreateNote = function(pos) { const { height, top_height } = pos; const val = { height, top_height, note: null, symbol: null }; return setEditingNote(val); }; const value = { editingNote, setEditingNote, deleteNote, inEditMode, noteEditor, onCreateNote }; const onConfirmChanges = function(n) { if (n?.note == null && n == editingNote) { console.log("No changes to note"); return; } if (notes.includes(n)) { return; } return props.onUpdateNote?.(n); }; return h(NoteEditorContext.Provider, { value }, [ h( ModelEditorProvider, { model: editingNote, onDelete: deleteNote, onConfirmChanges, logUpdates: true, alwaysConfirm: true }, children ) ]); } const NoteConnectorPath = function(props) { const { d, offsetX, className } = props; return h("path", { d, className, transform: `translate(${offsetX})`, fill: "transparent" }); }; const EditableNoteConnector = function(props) { const { notes, nodes, columnIndex, generatePath, createNodeForNote } = useContext(NoteLayoutContext); let { note, node, index } = props; if (note.id != null) { node = nodes[note.id]; } if (node == null) { node = createNodeForNote(note); } const x = columnIndex[note.id] * 5 || 0; const d = generatePath(node, x); return h([ h(NoteConnectorPath, { className: "note-connector", d, offsetX: x }), h( ForeignObject, { width: 30, x, y: 0, height: 1, style: { overflowY: "visible" } }, h(PositionEditorInner, { note }) ) ]); }; const PointHandle = function(props) { let { height, size, className, ...rest } = props; className = classNames("handle point-handle", className); if (size == null) { size = 10; } return h( Draggable, { position: { x: 0, y: height }, axis: "y", ...rest }, h("div.handle", { style: { height: size, width: size, marginLeft: -size / 2, marginTop: -size / 2, position: "absolute" }, className }) ); }; function PositionEditorInner(props) { let { margin } = props; if (margin == null) { margin = 3; } const { scaleClamped: scale } = useContext(ColumnContext); const ctx = useModelEditor(); if (ctx == null) { return null; } const { updatedModel, editedModel } = ctx; const note = editedModel ?? props.note; if (note == null) { return null; } const noteHasSpan = hasSpan(note); const bottomHeight = scale(note.height); let topHeight = bottomHeight; let height = 0; if (noteHasSpan) { topHeight = scale(note.top_height); height = Math.abs(topHeight - bottomHeight); } const moveEntireNote = function(e, data) { const { y } = data; const spec = { height: { $set: scale.invert(y + height) } }; if (noteHasSpan) { spec.top_height = { $set: scale.invert(y) }; } return updateModel(spec); }; const moveTop = function(e, data) { const spec = { top_height: { $set: scale.invert(data.y) } }; if (Math.abs(data.y - bottomHeight) < 2) { spec.top_height = { $set: null }; } return updateModel(spec); }; const moveBottom = function(e, data) { const spec = { height: { $set: scale.invert(data.y) } }; if (Math.abs(data.y - topHeight) < 2) { spec.top_height = { $set: null }; } return updateModel(spec); }; return h( ErrorBoundary, null, h("div.position-editor", [ h( Draggable, { handle: ".handle", position: { x: 0, y: topHeight }, onDrag: moveEntireNote, axis: "y" }, h("div", [ h("div.handle", { className: "handle", style: { height, width: 2 * margin, marginLeft: -margin, marginTop: -margin, position: "absolute" } }) ]) ), h(PointHandle, { height: noteHasSpan ? topHeight : topHeight - 15, onDrag: moveTop, className: classNames("top-handle", { "add-span-handle": !noteHasSpan }), bounds: { bottom: bottomHeight } }), h(PointHandle, { height: bottomHeight, onDrag: moveBottom, className: "bottom-handle", bounds: noteHasSpan ? { top: topHeight } : null }) ]) ); } function NoteEditorUnderlay() { return h(NoteRect, { style: { pointerEvents: "none" }, className: "underlay" }); } function NoteEditor(props) { const { allowPositionEditing } = props; const { noteEditor, setEditingNote } = useContext(NoteEditorContext); const { notes, nodes, elementHeights, createNodeForNote } = useContext(NoteLayoutContext); const { editedModel, model } = useModelEditor(); if (editedModel == null) { return null; } const index = notes.indexOf(editedModel); const { id: noteID } = editedModel; let node = nodes[noteID] || createNodeForNote(editedModel); const noteHeight = elementHeights[noteID] || 20; if (editedModel.height != null) { const newNode = createNodeForNote(editedModel); newNode.currentPos = node.currentPos; const pos = newNode.centerPos || newNode.idealPos; const dy = pos - node.currentPos; if (dy > 50) { newNode.currentPos = pos - 50; } if (dy < -50) { newNode.currentPos = pos + 50; } node = newNode; } const edited = editedModel === model; return h(ErrorBoundary, [ h("g.note-editor.note", [ h(NoteEditorUnderlay), h.if(!allowPositionEditing)(NoteConnector, { note: editedModel }), h.if(allowPositionEditing)(EditableNoteConnector, { note: editedModel, node }), h( NotePositioner, { offsetY: node.currentPos, noteHeight, onClick(evt) { if (edited) { setEditingNote(null); evt.stopPropagation(); } } }, [ h(noteEditor, { note: editedModel, key: index, focused: true, edited }) ] ) ]) ]); } export { NoteEditor, NoteEditorContext, NoteEditorProvider, NoteTextEditor }; //# sourceMappingURL=editor.js.map