gristlabs_grist-core/app/client/ui/Pages.ts
Alex Hall 5d671bf0b3 (core) New type conversion in the backend
Summary: This is https://phab.getgrist.com/D3205 plus some changes (https://github.com/dsagal/grist/compare/type-convert...type-convert-server?expand=1) that move the conversion process to the backend. A new user action ConvertFromColumn uses `call_external` so that the data engine can delegate back to ActiveDoc. Code for creating formatters and parsers is significantly refactored so that most of the logic is in `common` and can be used in different ways.

Test Plan: The original diff adds plenty of tests.

Reviewers: georgegevoian

Reviewed By: georgegevoian

Subscribers: dsagal

Differential Revision: https://phab.getgrist.com/D3240
2022-02-04 20:28:13 +02:00

102 lines
4.6 KiB
TypeScript

import { createGroup } from "app/client/components/commands";
import { duplicatePage } from "app/client/components/duplicatePage";
import { GristDoc } from "app/client/components/GristDoc";
import { PageRec } from "app/client/models/DocModel";
import { urlState } from "app/client/models/gristUrlState";
import { isHiddenTable } from 'app/common/isHiddenTable';
import * as MetaTableModel from "app/client/models/MetaTableModel";
import { find as findInTree, fromTableData, TreeItemRecord, TreeRecord,
TreeTableData} from "app/client/models/TreeModel";
import { TreeViewComponent } from "app/client/ui/TreeViewComponent";
import { confirmModal } from 'app/client/ui2018/modals';
import { buildPageDom, PageActions } from "app/client/ui2018/pages";
import { mod } from 'app/common/gutil';
import { Computed, Disposable, dom, fromKo, observable, Observable } from "grainjs";
// build dom for the tree view of pages
export function buildPagesDom(owner: Disposable, activeDoc: GristDoc, isOpen: Observable<boolean>) {
const pagesTable = activeDoc.docModel.pages;
const buildDom = buildDomFromTable.bind(null, pagesTable, activeDoc);
const records = Computed.create<TreeRecord[]>(owner, (use) =>
use(activeDoc.docModel.visibleDocPages).map(page => ({
id: page.getRowId(),
indentation: use(page.indentation),
pagePos: use(page.pagePos),
viewRef: use(page.viewRef),
}))
);
const getTreeTableData = (): TreeTableData => ({
getRecords: () => records.get(),
sendTableActions: (...args) => pagesTable.tableData.sendTableActions(...args),
});
// create the model and keep in sync with the table
const model = observable(fromTableData(getTreeTableData(), buildDom));
owner.autoDispose(records.addListener(() => {
model.set(fromTableData(getTreeTableData(), buildDom, model.get()));
}));
// create a computed that reads the selected page from the url and return the corresponding item
const selected = Computed.create(owner, activeDoc.activeViewId, (use, viewId) =>
findInTree(model.get(), (i: TreeItemRecord) => i.record.viewRef === viewId) || null
);
owner.autoDispose(createGroup({
nextPage: () => selected.get() && otherPage(selected.get()!, +1),
prevPage: () => selected.get() && otherPage(selected.get()!, -1)
}, null, true));
// dom
return dom('div', dom.create(TreeViewComponent, model, {isOpen, selected, isReadonly: activeDoc.isReadonly}));
}
function buildDomFromTable(pagesTable: MetaTableModel<PageRec>, activeDoc: GristDoc, id: number) {
const {docModel, isReadonly} = activeDoc;
const pageName = pagesTable.rowModels[id].view.peek().name;
const viewId = pagesTable.rowModels[id].view.peek().id.peek();
const docData = pagesTable.tableData.docData;
const actions: PageActions = {
onRename: (newName: string) => newName.length && pageName.saveOnly(newName),
onRemove: () => docData.sendAction(['RemoveRecord', '_grist_Views', viewId]),
// TODO: duplicate should prompt user for confirmation
onDuplicate: () => duplicatePage(activeDoc, id),
isRemoveDisabled: () => false,
isReadonly
};
// find a table with a matching primary view
const tableRef = docModel.tables.tableData.findRow('primaryViewId', viewId);
if (tableRef) {
function doRemove() {
const tableId = docModel.tables.tableData.getValue(tableRef, 'tableId');
return docData.sendAction(['RemoveTable', tableId]);
}
// if user removes a primary view, let's confirm first, because this will remove the
// corresponsing table and also all pages that are using this table.
// TODO: once we have raw table view, removing page should remove just the view (not the
// table), but for now this is the only way to remove a table in the newui.
actions.onRemove = () => confirmModal(
`Delete ${pageName()} data, and remove it from all pages?`, 'Delete', doRemove);
// Disable removing the last page. Sometimes hidden pages end up showing in the side panel
// (e.g. GristHidden_import* for aborted imports); those aren't listed in allTables, and we
// should allow removing them.
actions.isRemoveDisabled = () => (docModel.allTables.all().length <= 1) &&
!isHiddenTable(docModel.tables.tableData, tableRef);
}
return buildPageDom(fromKo(pageName), actions, urlState().setLinkUrl({docPage: viewId}));
}
// Select another page in cyclic ordering of pages. Order is downard if given a positive `delta`,
// upward otherwise.
function otherPage(currentPage: TreeItemRecord, delta: number) {
const records = currentPage.storage.records;
const index = mod(currentPage.index + delta, records.length);
const docPage = records[index].viewRef;
return urlState().pushUrl({docPage});
}