gristlabs_grist-core/app/client/lib/popupUtils.ts
Jarosław Sadziński da323fb741 (core) Floating formula editor
Summary:
Adding a way to detach an editor. Initially only implemented for the formula editor, includes redesign for the AI part.
- Initially, the detached editor is tight with the formula assistant and both are behind GRIST_FORMULA_ASSISTANT flag, but this can be relaxed
later on, as the detached editor can be used on its own.

- Detached editor is only supported in regular fields and on the creator panel. It is not supported yet for conditional styles, due to preview limitations.
- Old code for the assistant was removed completely, as it was only a temporary solution, but the AI conversation part was copied to the new one.
- Prompting was not modified in this diff, it will be included in the follow-up with more test cases.

Test Plan: Added only new tests; existing tests should pass.

Reviewers: JakubSerafin

Reviewed By: JakubSerafin

Differential Revision: https://phab.getgrist.com/D3863
2023-06-02 17:59:22 +02:00

64 lines
2.0 KiB
TypeScript

import {dom, Holder, IDisposable, MultiHolder} from 'grainjs';
/**
* Overrides the cursor style for the entire document.
* @returns {Disposable} - a Disposable that restores the cursor style to its original value.
*/
export function documentCursor(type: 'ns-resize' | 'grabbing'): IDisposable {
const cursorStyle: HTMLStyleElement = document.createElement('style');
cursorStyle.innerHTML = `*{cursor: ${type}!important;}`;
cursorStyle.id = 'cursor-style';
document.head.appendChild(cursorStyle);
const cursorOwner = {
dispose() {
if (this.isDisposed()) { return; }
document.head.removeChild(cursorStyle);
},
isDisposed() {
return !cursorStyle.isConnected;
}
};
return cursorOwner;
}
/**
* Helper function to create a movable element.
* @param options Handlers for the movable element.
*/
export function movable<T>(options: {
onMove: (dx: number, dy: number, state: T) => void,
onStart: () => T,
}) {
return (el: HTMLElement) => {
// Remember the initial position of the mouse.
let startX = 0;
let startY = 0;
dom.onElem(el, 'mousedown', (md) => {
// Only handle left mouse button.
if (md.button !== 0) { return; }
startX = md.clientX;
startY = md.clientY;
const state = options.onStart();
// We create a holder first so that we can dispose elements earlier on mouseup, and have a fallback
// in case of a situation when the dom is removed before mouseup.
const holder = new Holder();
const owner = MultiHolder.create(holder);
dom.autoDisposeElem(el, holder);
owner.autoDispose(dom.onElem(document, 'mousemove', (mv) => {
const dx = mv.clientX - startX;
const dy = mv.clientY - startY;
options.onMove(dx, dy, state);
}));
owner.autoDispose(dom.onElem(document, 'mouseup', () => {
holder.clear();
}));
owner.autoDispose(documentCursor('ns-resize'));
md.stopPropagation();
md.preventDefault();
}, { useCapture: true });
};
}