mirror of
https://github.com/gristlabs/grist-core.git
synced 2026-03-02 04:09:24 +00:00
(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
This commit is contained in:
@@ -249,7 +249,7 @@ function toggleDisabled(boolValueOrFunc) {
|
||||
exports.toggleDisabled = toggleDisabled;
|
||||
|
||||
/**
|
||||
* Adds a css class named by an observable value. If the value changes, the previous class will be
|
||||
* Adds a css class (one or many) named by an observable value. If the value changes, the previous class will be
|
||||
* removed and the new one added. The value may be empty to avoid adding any class.
|
||||
* Similar to knockout's `css` binding with a dynamic class.
|
||||
* @param {Object} valueOrFunc An observable, a constant, or a function for a computed observable.
|
||||
@@ -258,11 +258,15 @@ function cssClass(valueOrFunc) {
|
||||
var prevClass;
|
||||
return makeBinding(valueOrFunc, function(elem, value) {
|
||||
if (prevClass) {
|
||||
elem.classList.remove(prevClass);
|
||||
for(const name of prevClass.split(' ')) {
|
||||
elem.classList.remove(name);
|
||||
}
|
||||
}
|
||||
prevClass = value;
|
||||
if (value) {
|
||||
elem.classList.add(value);
|
||||
for (const name of value.split(' ')) {
|
||||
elem.classList.add(name);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
63
app/client/lib/popupUtils.ts
Normal file
63
app/client/lib/popupUtils.ts
Normal file
@@ -0,0 +1,63 @@
|
||||
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 });
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user