(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
This commit is contained in:
George Gevoian
2024-03-20 10:51:59 -04:00
parent aff9c7075c
commit 418681915e
40 changed files with 1643 additions and 617 deletions

View File

@@ -10,6 +10,7 @@ import {ApplyUAResult, DataSourceTransformed, ImportOptions, ImportResult, Impor
TransformRuleMap} from 'app/common/ActiveDocAPI';
import {ApiError} from 'app/common/ApiError';
import {BulkColValues, CellValue, fromTableDataAction, UserAction} from 'app/common/DocActions';
import {isBlankValue} from 'app/common/gristTypes';
import * as gutil from 'app/common/gutil';
import {localTimestampToUTC} from 'app/common/RelativeDates';
import {DocStateComparison} from 'app/common/UserAPI';
@@ -667,12 +668,6 @@ export class ActiveDocImport {
}
}
// Helper function that returns true if a given cell is blank (i.e. null or empty).
function isBlank(value: CellValue): boolean {
return value === null || (typeof value === 'string' && value.trim().length === 0);
}
// Helper function that returns new `colIds` with import prefixes stripped.
function stripPrefixes(colIds: string[]): string[] {
return colIds.map(id => id.startsWith(IMPORT_TRANSFORM_COLUMN_PREFIX) ?
@@ -691,13 +686,13 @@ type MergeFunction = (srcVal: CellValue, destVal: CellValue) => CellValue;
function getMergeFunction({type}: MergeStrategy): MergeFunction {
switch (type) {
case 'replace-with-nonblank-source': {
return (srcVal, destVal) => isBlank(srcVal) ? destVal : srcVal;
return (srcVal, destVal) => isBlankValue(srcVal) ? destVal : srcVal;
}
case 'replace-all-fields': {
return (srcVal, _destVal) => srcVal;
}
case 'replace-blank-fields-only': {
return (srcVal, destVal) => isBlank(destVal) ? srcVal : destVal;
return (srcVal, destVal) => isBlankValue(destVal) ? srcVal : destVal;
}
default: {
// Normally, we should never arrive here. If we somehow do, throw an error.

View File

@@ -12,7 +12,7 @@ import {
UserAction
} from 'app/common/DocActions';
import {DocData} from 'app/common/DocData';
import {extractTypeFromColType, isFullReferencingType, isRaisedException} from "app/common/gristTypes";
import {extractTypeFromColType, isBlankValue, isFullReferencingType, isRaisedException} from "app/common/gristTypes";
import {INITIAL_FIELDS_COUNT} from "app/common/Forms";
import {buildUrlId, parseUrlId, SHARE_KEY_PREFIX} from "app/common/gristUrls";
import {isAffirmative, safeJsonParse, timeoutReached} from "app/common/gutil";
@@ -573,6 +573,9 @@ export class DocWorkerApi {
validateCore(RecordsPost, req, body);
const ops = await getTableOperations(req, activeDoc);
const records = await ops.create(body.records);
if (req.query.utm_source === 'grist-forms') {
activeDoc.logTelemetryEvent(docSessionFromRequest(req), 'submittedForm');
}
res.json({records});
})
);
@@ -1422,7 +1425,7 @@ export class DocWorkerApi {
.filter(f => {
const col = Tables_column.getRecord(f.colRef);
// Formulas and attachments are currently unsupported.
return col && !(col.isFormula && col.formula) && col.type !== 'Attachment';
return col && !(col.isFormula && col.formula) && col.type !== 'Attachments';
});
let {layoutSpec: formLayoutSpec} = section;
@@ -1474,7 +1477,8 @@ export class DocWorkerApi {
if (!refTableId || !refColId) { return () => []; }
if (typeof refTableId !== 'string' || typeof refColId !== 'string') { return []; }
return await getTableValues(refTableId, refColId);
const values = await getTableValues(refTableId, refColId);
return values.filter(([_id, value]) => !isBlankValue(value));
};
const formFields = await Promise.all(fields.map(async (field) => {