Signal drop!
Relay (operand.online) is unreachable.
Usually, a dropped signal means an upgrade is happening. Hold on!
Sorry, no connección.
Hang in there while we get back on track
gram: essay
> ./src/tee/codemirrorPlugins/dropCursor.ts
Lenses
(coming soon!)
// This version of dropCursor is a copied from https://github.com/codemirror/view/blob/main/src/dropcursor.ts
// We need our own copy to register drag and drop event handlers in a way that doesn't get intercepted by react-dnd
// todo: delete this once we've removed react-dnd as a dependency
import { StateField, StateEffect, Extension } from "@codemirror/state";
import { EditorView, ViewPlugin, ViewUpdate } from "@codemirror/view";
interface MeasureRequest<T> {
/// Called in a DOM read phase to gather information that requires
/// DOM layout. Should _not_ mutate the document.
read(view: EditorView): T;
/// Called in a DOM write phase to update the document. Should _not_
/// do anything that triggers DOM layout.
write?(measure: T, view: EditorView): void;
/// When multiple requests with the same key are scheduled, only the
/// last one will actually be run.
key?: any;
}
const setDropCursorPos = StateEffect.define<number | null>({
map(pos, mapping) {
return pos == null ? null : mapping.mapPos(pos);
},
});
const dropCursorPos = StateField.define<number | null>({
create() {
return null;
},
update(pos, tr) {
if (pos != null) pos = tr.changes.mapPos(pos);
return tr.effects.reduce(
(pos, e) => (e.is(setDropCursorPos) ? e.value : pos),
pos
);
},
});
const drawDropCursor = ViewPlugin.fromClass(
class {
cursor: HTMLElement | null = null;
measureReq: MeasureRequest<{
left: number;
top: number;
height: number;
} | null>;
constructor(readonly view: EditorView) {
this.measureReq = {
read: this.readPos.bind(this),
write: this.drawCursor.bind(this),
};
this.onDragOver = this.onDragOver.bind(this);
this.onDragLeave = this.onDragLeave.bind(this);
this.onDragEnd = this.onDragEnd.bind(this);
this.onDrop = this.onDrop.bind(this);
// instead of relying on codemirrors event handling system we register the event handlers directly on the dom element
view.dom.addEventListener("dragover", this.onDragOver);
view.dom.addEventListener("dragenter", this.onDragLeave);
view.dom.addEventListener("dragenter", this.onDragEnd);
view.dom.addEventListener("drop", this.onDrop);
}
private onDragOver(event: DragEvent) {
this.setDropPos(
this.view.posAtCoords({ x: event.clientX, y: event.clientY })
);
}
private onDragLeave(event: DragEvent) {
if (
event.target == this.view.contentDOM ||
!this.view.contentDOM.contains(event.relatedTarget as HTMLElement)
)
this.setDropPos(null);
}
private onDragEnd(event: DragEvent) {
this.setDropPos(null);
}
private onDrop(event: DragEvent) {
this.setDropPos(null);
}
update(update: ViewUpdate) {
let cursorPos = update.state.field(dropCursorPos);
if (cursorPos == null) {
if (this.cursor != null) {
this.cursor?.remove();
this.cursor = null;
}
} else {
if (!this.cursor) {
this.cursor = this.view.scrollDOM.appendChild(
document.createElement("div")
);
this.cursor!.className = "cm-dropCursor";
}
if (
update.startState.field(dropCursorPos) != cursorPos ||
update.docChanged ||
update.geometryChanged
)
this.view.requestMeasure(this.measureReq);
}
}
readPos(): { left: number; top: number; height: number } | null {
let { view } = this;
let pos = view.state.field(dropCursorPos);
let rect = pos != null && view.coordsAtPos(pos);
if (!rect) return null;
let outer = view.scrollDOM.getBoundingClientRect();
return {
left: rect.left - outer.left + view.scrollDOM.scrollLeft * view.scaleX,
top: rect.top - outer.top + view.scrollDOM.scrollTop * view.scaleY,
height: rect.bottom - rect.top,
};
}
drawCursor(pos: { left: number; top: number; height: number } | null) {
if (this.cursor) {
let { scaleX, scaleY } = this.view;
if (pos) {
this.cursor.style.left = pos.left / scaleX + "px";
this.cursor.style.top = pos.top / scaleY + "px";
this.cursor.style.height = pos.height / scaleY + "px";
} else {
this.cursor.style.left = "-100000px";
}
}
}
destroy() {
if (this.cursor) this.cursor.remove();
// unregister manually adedd event handlers
this.view.dom.removeEventListener("dragover", this.onDragOver);
this.view.dom.removeEventListener("dragenter", this.onDragLeave);
this.view.dom.removeEventListener("dragenter", this.onDragEnd);
this.view.dom.removeEventListener("drop", this.onDrop);
}
setDropPos(pos: number | null) {
if (this.view.state.field(dropCursorPos) != pos)
this.view.dispatch({ effects: setDropCursorPos.of(pos) });
}
}
);
/// Draws a cursor at the current drop position when something is
/// dragged over the editor.
export function dropCursor(): Extension {
return [dropCursorPos, drawDropCursor];
}