(core) Forms post-release fixes and improvements

Summary:
Fixes misc. bugs with forms, updates Grist URLs on static form pages to link
to the new forms marketing page, and adds a forms announcement popup that's
shown next to the Add New button within a document.

Test Plan: Browser tests.

Reviewers: jarek

Reviewed By: jarek

Differential Revision: https://phab.getgrist.com/D4185
This commit is contained in:
George Gevoian
2024-02-14 16:18:09 -05:00
parent b8f32d1784
commit cd339ce7cb
43 changed files with 957 additions and 302 deletions

View File

@@ -1,4 +1,5 @@
import {buildEditor} from 'app/client/components/Forms/Editor';
import {FieldModel} from 'app/client/components/Forms/Field';
import {buildMenu} from 'app/client/components/Forms/Menu';
import {BoxModel} from 'app/client/components/Forms/Model';
import * as style from 'app/client/components/Forms/styles';
@@ -86,6 +87,25 @@ export class ColumnsModel extends BoxModel {
);
return buildEditor({ box: this, content });
}
public async deleteSelf(): Promise<void> {
// Prepare all the fields that are children of this column for removal.
const fieldsToRemove = (Array.from(this.filter(b => b instanceof FieldModel)) as FieldModel[]);
const fieldIdsToRemove = fieldsToRemove.map(f => f.leaf.get());
// Remove each child of this column from the layout.
this.children.get().forEach(child => { child.removeSelf(); });
// Remove this column from the layout.
this.removeSelf();
// Finally, remove the fields and save the changes to the layout.
await this.parent?.save(async () => {
if (fieldIdsToRemove.length > 0) {
await this.view.viewSection.removeField(fieldIdsToRemove);
}
});
}
}
export class PlaceholderModel extends BoxModel {

View File

@@ -0,0 +1,30 @@
import {fromKoSave} from 'app/client/lib/fromKoSave';
import {makeT} from 'app/client/lib/localization';
import {ViewFieldRec} from 'app/client/models/DocModel';
import {KoSaveableObservable} from 'app/client/models/modelUtil';
import {cssLabel, cssRow, cssSeparator} from 'app/client/ui/RightPanelStyles';
import {labeledSquareCheckbox} from 'app/client/ui2018/checkbox';
import {testId} from 'app/client/ui2018/cssVars';
import {Disposable} from 'grainjs';
const t = makeT('FormConfig');
export class FieldRulesConfig extends Disposable {
constructor(private _field: ViewFieldRec) {
super();
}
public buildDom() {
const requiredField: KoSaveableObservable<boolean> = this._field.widgetOptionsJson.prop('formRequired');
return [
cssSeparator(),
cssLabel(t('Field rules')),
cssRow(labeledSquareCheckbox(
fromKoSave(requiredField),
t('Required field'),
testId('field-required'),
)),
];
}
}

View File

@@ -644,28 +644,14 @@ export class FormView extends Disposable {
dom.on('click', async (_event, element) => {
try {
this._copyingLink.set(true);
const share = this._pageShare.get();
if (!share) {
throw new Error('Unable to copy link: form is not published');
}
const remoteShare = await this.gristDoc.docComm.getShare(share.linkId());
if (!remoteShare) {
throw new Error('Unable to copy link: form is not published');
}
const url = urlState().makeUrl({
doc: undefined,
form: {
shareKey: remoteShare.key,
vsId: this.viewSection.id(),
},
const data = typeof ClipboardItem !== 'function' ? await this._getFormLink() : new ClipboardItem({
"text/plain": this._getFormLink().then(text => new Blob([text], {type: 'text/plain'})),
});
await copyToClipboard(url);
await copyToClipboard(data);
showTransientTooltip(element, 'Link copied to clipboard', {key: 'copy-form-link'});
} catch(ex) {
} catch (ex) {
if (ex.code === 'AUTH_NO_OWNER') {
throw new Error('Publishing form is only available to owners');
throw new Error('Sharing a form is only available to owners');
}
} finally {
this._copyingLink.set(false);
@@ -693,6 +679,26 @@ export class FormView extends Disposable {
);
}
private async _getFormLink() {
const share = this._pageShare.get();
if (!share) {
throw new Error('Unable to get form link: form is not published');
}
const remoteShare = await this.gristDoc.docComm.getShare(share.linkId());
if (!remoteShare) {
throw new Error('Unable to get form link: form is not published');
}
return urlState().makeUrl({
doc: undefined,
form: {
shareKey: remoteShare.key,
vsId: this.viewSection.id(),
},
});
}
private _buildSwitcherMessage() {
return dom.maybe(use => use(this._published) && use(this._showPublishedMessage), () => {
return style.cssSwitcherMessage(

View File

@@ -1,5 +1,6 @@
import * as style from './styles';
import {buildEditor} from 'app/client/components/Forms/Editor';
import {FieldModel} from 'app/client/components/Forms/Field';
import {buildMenu} from 'app/client/components/Forms/Menu';
import {BoxModel} from 'app/client/components/Forms/Model';
import {makeTestId} from 'app/client/lib/domUtils';
@@ -72,6 +73,25 @@ export class SectionModel extends BoxModel {
return place(dropped);
}
public async deleteSelf(): Promise<void> {
// Prepare all the fields that are children of this section for removal.
const fieldsToRemove = Array.from(this.filter(b => b instanceof FieldModel)) as FieldModel[];
const fieldIdsToRemove = fieldsToRemove.map(f => f.leaf.get());
// Remove each child of this section from the layout.
this.children.get().forEach(child => { child.removeSelf(); });
// Remove this section from the layout.
this.removeSelf();
// Finally, remove the fields and save the changes to the layout.
await this.parent?.save(async () => {
if (fieldIdsToRemove.length > 0) {
await this.view.viewSection.removeField(fieldIdsToRemove);
}
});
}
}
const cssSectionItems = styled('div.hover_border', `

View File

@@ -150,7 +150,7 @@ export class UnmappedFieldsConfig extends Disposable {
allCommands.showColumns.run([column.colId.peek()]);
}),
),
squareCheckbox(props.selected),
cssSquareCheckbox(props.selected),
),
);
}
@@ -171,7 +171,7 @@ export class UnmappedFieldsConfig extends Disposable {
allCommands.hideFields.run([column.colId.peek()]);
}),
),
squareCheckbox(props.selected),
cssSquareCheckbox(props.selected),
),
);
}
@@ -272,3 +272,7 @@ const cssHeader = styled(cssRow, `
line-height: 1em;
}
`);
const cssSquareCheckbox = styled(squareCheckbox, `
flex-shrink: 0;
`);

View File

@@ -147,7 +147,7 @@ export const cssRenderedLabel = styled('div', `
cursor: pointer;
min-height: 16px;
color: ${colors.darkText};
color: ${theme.mediumText};
font-size: 11px;
line-height: 16px;
font-weight: 700;
@@ -213,6 +213,7 @@ export const cssDesc = styled('div', `
export const cssInput = styled('input', `
background-color: ${theme.inputDisabledBg};
font-size: inherit;
height: 27px;
padding: 4px 8px;
border: 1px solid ${theme.inputBorder};
border-radius: 3px;
@@ -232,6 +233,7 @@ export const cssSelect = styled('select', `
width: 100%;
background-color: ${theme.inputDisabledBg};
font-size: inherit;
height: 27px;
padding: 4px 8px;
border: 1px solid ${theme.inputBorder};
border-radius: 3px;