(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:
Jarosław Sadziński
2023-06-02 13:25:14 +02:00
parent e10067ff78
commit da323fb741
36 changed files with 2022 additions and 823 deletions

View File

@@ -1,7 +1,9 @@
import {documentCursor} from 'app/client/lib/popupUtils';
import {hoverTooltip} from 'app/client/ui/tooltips';
import {isNarrowScreen, isNarrowScreenObs, theme, vars} from 'app/client/ui2018/cssVars';
import {icon} from 'app/client/ui2018/icons';
import {Disposable, dom, DomArg, DomContents, IDisposable, makeTestId, Observable, styled} from 'grainjs';
import {Disposable, dom, DomContents, DomElementArg,
IDisposable, makeTestId, Observable, styled} from 'grainjs';
const POPUP_INITIAL_PADDING_PX = 16;
const POPUP_MIN_HEIGHT = 300;
@@ -15,9 +17,11 @@ export interface PopupOptions {
content?: () => DomContents;
onClose?: () => void;
closeButton?: boolean;
closeButtonHover?: () => DomContents;
autoHeight?: boolean;
/** Defaults to false. */
stopClickPropagationOnMove?: boolean;
args?: DomElementArg[];
}
export class FloatingPopup extends Disposable {
@@ -34,7 +38,7 @@ export class FloatingPopup extends Disposable {
private _resize = false;
private _cursorGrab: IDisposable|null = null;
constructor(protected _options: PopupOptions = {}, private _args: DomArg[] = []) {
constructor(protected _options: PopupOptions = {}) {
super();
if (_options.stopClickPropagationOnMove){
@@ -98,7 +102,7 @@ export class FloatingPopup extends Disposable {
}
protected _buildArgs(): any {
return this._args;
return this._options.args ?? [];
}
private _rememberPosition() {
@@ -272,12 +276,12 @@ export class FloatingPopup extends Disposable {
// Copy buttons on the left side of the header, to automatically
// center the title.
cssPopupButtons(
!this._options.closeButton ? null : cssPopupHeaderButton(
icon('CrossSmall'),
),
cssPopupHeaderButton(
icon('Maximize')
),
!this._options.closeButton ? null : cssPopupHeaderButton(
icon('CrossBig'),
),
dom.style('visibility', 'hidden'),
),
cssPopupTitle(
@@ -285,19 +289,20 @@ export class FloatingPopup extends Disposable {
testId('title'),
),
cssPopupButtons(
!this._options.closeButton ? null : cssPopupHeaderButton(
icon('CrossSmall'),
dom.on('click', () => {
this._options.onClose?.() ?? this._closePopup();
}),
testId('close'),
),
this._popupMinimizeButtonElement = cssPopupHeaderButton(
isMinimized ? icon('Maximize'): icon('Minimize'),
hoverTooltip(isMinimized ? 'Maximize' : 'Minimize', {key: 'docTutorialTooltip'}),
dom.on('click', () => this._minimizeOrMaximize()),
testId('minimize-maximize'),
),
!this._options.closeButton ? null : cssPopupHeaderButton(
icon('CrossBig'),
dom.on('click', () => {
this._options.onClose?.() ?? this._closePopup();
}),
testId('close'),
this._options.closeButtonHover && hoverTooltip(this._options.closeButtonHover())
),
// Disable dragging when a button in the header is clicked.
dom.on('mousedown', ev => ev.stopPropagation()),
dom.on('touchstart', ev => ev.stopPropagation()),
@@ -345,20 +350,7 @@ export class FloatingPopup extends Disposable {
private _forceCursor() {
this._cursorGrab?.dispose();
const type = this._resize ? 'ns-resize' : 'grabbing';
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;
}
};
this._cursorGrab = cursorOwner;
this._cursorGrab = documentCursor(type);
}
}
@@ -372,7 +364,7 @@ const POPUP_HEIGHT = `min(var(--height), calc(100% - (2 * ${POPUP_INITIAL_PADDIN
const POPUP_HEIGHT_MOBILE = `min(var(--height), calc(100% - (2 * ${POPUP_INITIAL_PADDING_PX}px) - (2 * 50px)))`;
const POPUP_WIDTH = `min(436px, calc(100% - (2 * ${POPUP_INITIAL_PADDING_PX}px)))`;
const cssPopup = styled('div', `
const cssPopup = styled('div.floating-popup', `
position: fixed;
display: flex;
flex-direction: column;