mirror of
https://github.com/gristlabs/grist-core.git
synced 2026-03-02 04:09:24 +00:00
(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:
@@ -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 {
|
||||
|
||||
30
app/client/components/Forms/FormConfig.ts
Normal file
30
app/client/components/Forms/FormConfig.ts
Normal 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'),
|
||||
)),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -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(
|
||||
|
||||
@@ -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', `
|
||||
|
||||
@@ -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;
|
||||
`);
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user