Summary: Adds a "Duplicate Table" menu option to the tables listed on the Raw Data page. Clicking it opens a dialog that allows you to make a copy of the table (with or without its data). Test Plan: Python, server, and browser tests. Reviewers: jarek, paulfitz Reviewed By: jarek, paulfitz Subscribers: jarek Differential Revision: https://phab.getgrist.com/D3619pull/296/head
parent
0eb1fec3d7
commit
cd64237dad
@ -0,0 +1,122 @@
|
|||||||
|
import {GristDoc} from 'app/client/components/GristDoc';
|
||||||
|
import {cssInput} from 'app/client/ui/cssInput';
|
||||||
|
import {cssField} from 'app/client/ui/MakeCopyMenu';
|
||||||
|
import {labeledSquareCheckbox} from 'app/client/ui2018/checkbox';
|
||||||
|
import {colors} from 'app/client/ui2018/cssVars';
|
||||||
|
import {icon} from 'app/client/ui2018/icons';
|
||||||
|
import {cssLink} from 'app/client/ui2018/links';
|
||||||
|
import {saveModal} from 'app/client/ui2018/modals';
|
||||||
|
import {commonUrls} from 'app/common/gristUrls';
|
||||||
|
import {Computed, Disposable, dom, input, makeTestId, Observable, styled} from 'grainjs';
|
||||||
|
|
||||||
|
const testId = makeTestId('test-duplicate-table-');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Response returned by a DuplicateTable user action.
|
||||||
|
*/
|
||||||
|
export interface DuplicateTableResponse {
|
||||||
|
/** Row id of the new table. */
|
||||||
|
id: number;
|
||||||
|
/** Table id of the new table. */
|
||||||
|
table_id: string;
|
||||||
|
/** Row id of the new raw view section. */
|
||||||
|
raw_section_id: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface DuplicateTableOptions {
|
||||||
|
onSuccess?(response: DuplicateTableResponse): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shows a modal with options for duplicating the table `tableId`.
|
||||||
|
*/
|
||||||
|
export function duplicateTable(
|
||||||
|
gristDoc: GristDoc,
|
||||||
|
tableId: string,
|
||||||
|
{onSuccess}: DuplicateTableOptions = {}
|
||||||
|
) {
|
||||||
|
saveModal((_ctl, owner) => {
|
||||||
|
const duplicateTableModal = DuplicateTableModal.create(owner, gristDoc, tableId);
|
||||||
|
return {
|
||||||
|
title: 'Duplicate Table',
|
||||||
|
body: duplicateTableModal.buildDom(),
|
||||||
|
saveFunc: async () => {
|
||||||
|
const response = await duplicateTableModal.save();
|
||||||
|
onSuccess?.(response);
|
||||||
|
},
|
||||||
|
saveDisabled: duplicateTableModal.saveDisabled,
|
||||||
|
width: 'normal',
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
class DuplicateTableModal extends Disposable {
|
||||||
|
private _newTableName = Observable.create<string>(this, '');
|
||||||
|
private _includeData = Observable.create<boolean>(this, false);
|
||||||
|
private _saveDisabled = Computed.create(this, this._newTableName, (_use, name) => !name.trim());
|
||||||
|
|
||||||
|
constructor(private _gristDoc: GristDoc, private _tableId: string) {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
public get saveDisabled() { return this._saveDisabled; }
|
||||||
|
|
||||||
|
public save() {
|
||||||
|
return this._duplicateTable();
|
||||||
|
}
|
||||||
|
|
||||||
|
public buildDom() {
|
||||||
|
return [
|
||||||
|
cssField(
|
||||||
|
input(
|
||||||
|
this._newTableName,
|
||||||
|
{onInput: true},
|
||||||
|
{placeholder: 'Name for new table'},
|
||||||
|
(elem) => { setTimeout(() => { elem.focus(); }, 20); },
|
||||||
|
dom.on('focus', (_ev, elem) => { elem.select(); }),
|
||||||
|
dom.cls(cssInput.className),
|
||||||
|
testId('name'),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
cssWarning(
|
||||||
|
cssWarningIcon('Warning'),
|
||||||
|
dom('div',
|
||||||
|
"Instead of duplicating tables, it's usually better to segment data using linked views. ",
|
||||||
|
cssLink({href: commonUrls.helpLinkingWidgets, target: '_blank'}, 'Read More.')
|
||||||
|
),
|
||||||
|
),
|
||||||
|
cssField(
|
||||||
|
cssCheckbox(
|
||||||
|
this._includeData,
|
||||||
|
'Copy all data in addition to the table structure.',
|
||||||
|
testId('copy-all-data'),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
dom.maybe(this._includeData, () => cssWarning(
|
||||||
|
cssWarningIcon('Warning'),
|
||||||
|
dom('div', 'Only the document default access rules will apply to the copy.'),
|
||||||
|
testId('acl-warning'),
|
||||||
|
)),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
private _duplicateTable() {
|
||||||
|
const {docData} = this._gristDoc;
|
||||||
|
const [newTableName, includeData] = [this._newTableName.get(), this._includeData.get()];
|
||||||
|
return docData.sendAction(['DuplicateTable', this._tableId, newTableName, includeData]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const cssCheckbox = styled(labeledSquareCheckbox, `
|
||||||
|
margin-top: 8px;
|
||||||
|
`);
|
||||||
|
|
||||||
|
const cssWarning = styled('div', `
|
||||||
|
display: flex;
|
||||||
|
column-gap: 8px;
|
||||||
|
`);
|
||||||
|
|
||||||
|
const cssWarningIcon = styled(icon, `
|
||||||
|
--icon-color: ${colors.orange};
|
||||||
|
flex-shrink: 0;
|
||||||
|
`);
|
Loading…
Reference in new issue