gristlabs_grist-core/app/client/components/Forms/Paragraph.ts
George Gevoian 418681915e (core) Forms Improvements
Summary:
 - Forms now have a reset button.
 - Choice and Reference fields in forms now have an improved select menu.
 - Formula and attachments column types are no longer mappable or visible in forms.
 - Fields in a form widget are now removed if their column is deleted.
 - The preview button in a published form widget has been replaced with a view button. It now opens the published form in a new tab.
 - A new share menu for published form widgets, with options to copy a link or embed code.
 - Forms can now have multiple sections.
 - Form widgets now indicate when publishing is unavailable (e.g. in forks or unsaved documents).
 - General improvements to form styling.

Test Plan: Browser tests.

Reviewers: jarek

Reviewed By: jarek

Subscribers: paulfitz

Differential Revision: https://phab.getgrist.com/D4203
2024-03-21 13:01:25 -04:00

100 lines
3.3 KiB
TypeScript

import {FormLayoutNode} from 'app/client/components/FormRenderer';
import {buildEditor} from 'app/client/components/Forms/Editor';
import {BoxModel} from 'app/client/components/Forms/Model';
import * as css from 'app/client/components/Forms/styles';
import {textarea} from 'app/client/ui/inputs';
import {theme} from 'app/client/ui2018/cssVars';
import {not} from 'app/common/gutil';
import {Computed, dom, Observable, styled} from 'grainjs';
import {v4 as uuidv4} from 'uuid';
export class ParagraphModel extends BoxModel {
public edit = Observable.create(this, false);
protected defaultValue = '**Lorem** _ipsum_ dolor';
protected cssClass = '';
private _overlay = Computed.create(this, not(this.selected));
public override render(): HTMLElement {
const box = this;
const editMode = box.edit;
let element: HTMLElement;
const text = this.prop('text', this.defaultValue) as Observable<string|undefined>;
// There is a spacial hack here. We might be created as a separator component, but the rendering
// for separator looks bad when it is the only content, so add a special case for that.
const isSeparator = Computed.create(this, (use) => use(text) === '---');
return buildEditor({
box: this,
overlay: this._overlay,
editMode,
content: css.cssMarkdownRendered(
css.markdown(use => use(text) || '', dom.hide(editMode)),
dom.maybe(use => !use(text) && !use(editMode), () => cssEmpty('(empty)')),
css.cssMarkdownRendered.cls('-separator', isSeparator),
dom.on('click', () => {
if (!editMode.get() && this.selected.get()) {
editMode.set(true);
}
}),
css.cssMarkdownRendered.cls('-edit', editMode),
css.cssMarkdownRendered.cls(u => `-alignment-${u(box.prop('alignment', 'left'))}`),
this.cssClass ? dom.cls(this.cssClass, not(editMode)) : null,
dom.maybe(editMode, () => {
const draft = Observable.create(null, text.get() || '');
setTimeout(() => element?.focus(), 10);
return [
element = cssTextArea(draft, {autoGrow: true, onInput: true},
cssTextArea.cls('-edit', editMode),
css.saveControls(editMode, (ok) => {
if (ok && editMode.get()) {
text.set(draft.get());
this.save().catch(reportError);
}
})
),
];
}),
)
});
}
}
export function Paragraph(text: string, alignment?: 'left'|'right'|'center'): FormLayoutNode {
return {id: uuidv4(), type: 'Paragraph', text, alignment};
}
const cssTextArea = styled(textarea, `
color: ${theme.inputFg};
background-color: ${theme.mainPanelBg};
border: 0px;
width: 100%;
padding: 3px 6px;
outline: none;
max-height: 300px;
min-height: calc(3em * 1.5);
resize: none;
border-radius: 3px;
&-edit {
cursor: auto;
background: ${theme.inputBg};
outline: 2px solid black;
outline-offset: 1px;
border-radius: 2px;
}
&::placeholder {
color: ${theme.inputPlaceholderFg};
}
&[readonly] {
background-color: ${theme.inputDisabledBg};
color: ${theme.inputDisabledFg};
}
`);
const cssEmpty = styled('div', `
color: ${theme.inputPlaceholderFg};
font-style: italic;
`);