mirror of
https://github.com/gristlabs/grist-core.git
synced 2024-10-27 20:44:07 +00:00
(core) Implement 'Print widget' option to print individual view sections.
Summary: - Supports multi-page printing with some aggressive css overrides. - Relies on a new function implemented by grist-plugin-api to print a multi-page CustomView. - Renders all rows for printing for scrolly-based views. Test Plan: Doesn't seem possible to do a selenium test for printing. Tested manually on Chrome, Firefox, and Safari. Reviewers: paulfitz Reviewed By: paulfitz Differential Revision: https://phab.getgrist.com/D2635
This commit is contained in:
parent
d2ad5edc46
commit
99ab09651e
@ -230,6 +230,9 @@ function BaseView(gristDoc, viewSectionModel, options) {
|
|||||||
this.autoDispose(this.viewSection.activeSortSpec.subscribe(() => this.setCursorPos({rowIndex: 0})));
|
this.autoDispose(this.viewSection.activeSortSpec.subscribe(() => this.setCursorPos({rowIndex: 0})));
|
||||||
|
|
||||||
this.copySelection = ko.observable(null);
|
this.copySelection = ko.observable(null);
|
||||||
|
|
||||||
|
// Whether parts needed for printing should be rendered now.
|
||||||
|
this._isPrinting = ko.observable(false);
|
||||||
}
|
}
|
||||||
Base.setBaseFor(BaseView);
|
Base.setBaseFor(BaseView);
|
||||||
_.extend(Base.prototype, BackboneEvents);
|
_.extend(Base.prototype, BackboneEvents);
|
||||||
@ -612,6 +615,13 @@ BaseView.prototype.onResize = function() {
|
|||||||
BaseView.prototype.onRowResize = function(rowModels) {
|
BaseView.prototype.onRowResize = function(rowModels) {
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called before and after printing this section.
|
||||||
|
*/
|
||||||
|
BaseView.prototype.prepareToPrint = function(onOff) {
|
||||||
|
this._isPrinting(onOff);
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called to obtain the rowModel for the given rowId. Returns a rowModel if it belongs to the
|
* Called to obtain the rowModel for the given rowId. Returns a rowModel if it belongs to the
|
||||||
* section and is rendered, otherwise returns null.
|
* section and is rendered, otherwise returns null.
|
||||||
|
@ -89,6 +89,12 @@ export class CustomView extends Disposable {
|
|||||||
this.autoDispose(this.cursor.rowIndex.subscribe(this._updateCursor));
|
this.autoDispose(this.cursor.rowIndex.subscribe(this._updateCursor));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async triggerPrint() {
|
||||||
|
if (!this.isDisposed() && this._rpc) {
|
||||||
|
return await this._rpc.callRemoteFunc("print");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private _updateView(dataChange: boolean) {
|
private _updateView(dataChange: boolean) {
|
||||||
if (this.isDisposed()) { return; }
|
if (this.isDisposed()) { return; }
|
||||||
if (this._rpc) {
|
if (this._rpc) {
|
||||||
|
@ -79,11 +79,13 @@
|
|||||||
margin-left: -3px;
|
margin-left: -3px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@media not print {
|
||||||
.detailview_record_detail.active {
|
.detailview_record_detail.active {
|
||||||
/* highlight active record in Card List by overlaying the active-section highlight */
|
/* highlight active record in Card List by overlaying the active-section highlight */
|
||||||
margin-left: -3px;
|
margin-left: -3px;
|
||||||
border-left: 3px solid var(--grist-color-light-green);
|
border-left: 3px solid var(--grist-color-light-green);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/*** single record ***/
|
/*** single record ***/
|
||||||
.detailview_single {
|
.detailview_single {
|
||||||
|
@ -4,6 +4,7 @@ var ko = require('knockout');
|
|||||||
var dom = require('app/client/lib/dom');
|
var dom = require('app/client/lib/dom');
|
||||||
var kd = require('app/client/lib/koDom');
|
var kd = require('app/client/lib/koDom');
|
||||||
var koDomScrolly = require('app/client/lib/koDomScrolly');
|
var koDomScrolly = require('app/client/lib/koDomScrolly');
|
||||||
|
const {renderAllRows} = require('app/client/components/Printing');
|
||||||
|
|
||||||
require('app/client/lib/koUtil'); // Needed for subscribeInit.
|
require('app/client/lib/koUtil'); // Needed for subscribeInit.
|
||||||
|
|
||||||
@ -282,6 +283,11 @@ DetailView.prototype.buildDom = function() {
|
|||||||
}),
|
}),
|
||||||
koDomScrolly.scrolly(this.viewData, {fitToWidth: true},
|
koDomScrolly.scrolly(this.viewData, {fitToWidth: true},
|
||||||
row => this.makeRecord(row)),
|
row => this.makeRecord(row)),
|
||||||
|
|
||||||
|
kd.maybe(this._isPrinting, () =>
|
||||||
|
renderAllRows(this.tableModel, this.sortedRows.getKoArray().peek(), row =>
|
||||||
|
this.makeRecord(row))
|
||||||
|
),
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
return dom(
|
return dom(
|
||||||
|
@ -71,6 +71,7 @@
|
|||||||
left: 0px;
|
left: 0px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
width: 4rem; /* Also should match width for .gridview_header_corner, and the overlay elements */
|
width: 4rem; /* Also should match width for .gridview_header_corner, and the overlay elements */
|
||||||
|
flex: none;
|
||||||
|
|
||||||
border-bottom: 1px solid var(--grist-color-dark-grey);
|
border-bottom: 1px solid var(--grist-color-dark-grey);
|
||||||
background-color: var(--grist-color-light-grey);
|
background-color: var(--grist-color-light-grey);
|
||||||
@ -89,6 +90,15 @@
|
|||||||
.gridview_data_row_num {
|
.gridview_data_row_num {
|
||||||
background-color: var(--grist-color-light-grey) !important;
|
background-color: var(--grist-color-light-grey) !important;
|
||||||
}
|
}
|
||||||
|
.gridview_header_backdrop_top {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.column_name.mod-add-column {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.gridview_data_header {
|
||||||
|
background-color: var(--grist-color-light-grey) !important;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ========= Overlay styles ========== */
|
/* ========= Overlay styles ========== */
|
||||||
|
@ -21,6 +21,7 @@ var BaseView = require('./BaseView');
|
|||||||
var selector = require('./Selector');
|
var selector = require('./Selector');
|
||||||
var CopySelection = require('./CopySelection');
|
var CopySelection = require('./CopySelection');
|
||||||
|
|
||||||
|
const {renderAllRows} = require('app/client/components/Printing');
|
||||||
const {reportError} = require('app/client/models/AppModel');
|
const {reportError} = require('app/client/models/AppModel');
|
||||||
|
|
||||||
// Grist UI Components
|
// Grist UI Components
|
||||||
@ -831,7 +832,15 @@ GridView.prototype.buildDom = function() {
|
|||||||
) //end hbox
|
) //end hbox
|
||||||
), // END COL HEADER BOX
|
), // END COL HEADER BOX
|
||||||
|
|
||||||
koDomScrolly.scrolly(data, { paddingBottom: 80, paddingRight: 28 }, function(row) {
|
koDomScrolly.scrolly(data, { paddingBottom: 80, paddingRight: 28 }, renderRow),
|
||||||
|
|
||||||
|
kd.maybe(this._isPrinting, () =>
|
||||||
|
renderAllRows(this.tableModel, this.sortedRows.getKoArray().peek(), renderRow)
|
||||||
|
),
|
||||||
|
) // end scrollpane
|
||||||
|
);// END MAIN VIEW BOX
|
||||||
|
|
||||||
|
function renderRow(row) {
|
||||||
// TODO. There are several ways to implement a cursor; similar concerns may arise
|
// TODO. There are several ways to implement a cursor; similar concerns may arise
|
||||||
// when implementing selection and cell editor.
|
// when implementing selection and cell editor.
|
||||||
// (1) Class on 'div.field.field_clip'. Fewest elements, seems possibly best for
|
// (1) Class on 'div.field.field_clip'. Fewest elements, seems possibly best for
|
||||||
@ -948,10 +957,7 @@ GridView.prototype.buildDom = function() {
|
|||||||
})
|
})
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}) //end scrolly
|
}
|
||||||
|
|
||||||
) // end scrollpane
|
|
||||||
);// END MAIN VIEW BOX
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/** @inheritdoc */
|
/** @inheritdoc */
|
||||||
|
@ -690,4 +690,9 @@ const cssViewContentPane = styled('div', `
|
|||||||
position: relative;
|
position: relative;
|
||||||
min-width: 240px;
|
min-width: 240px;
|
||||||
margin: 12px;
|
margin: 12px;
|
||||||
|
@media print {
|
||||||
|
& {
|
||||||
|
margin: 0px;
|
||||||
|
}
|
||||||
|
}
|
||||||
`);
|
`);
|
||||||
|
60
app/client/components/Printing.css
Normal file
60
app/client/components/Printing.css
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
@media print {
|
||||||
|
/* Various style overrides needed to print a single section (page widget). */
|
||||||
|
|
||||||
|
.print-hide {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.print-parent {
|
||||||
|
display: block !important;
|
||||||
|
position: relative !important;
|
||||||
|
height: max-content !important;
|
||||||
|
overflow: visible !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.print-widget {
|
||||||
|
margin: 0px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.print-widget .viewsection_title {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
.print-widget .view_data_pane_container {
|
||||||
|
border: none !important;
|
||||||
|
}
|
||||||
|
.print-widget .viewsection_content {
|
||||||
|
margin: 0px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.print-widget .detailview_single {
|
||||||
|
overflow: visible;
|
||||||
|
}
|
||||||
|
|
||||||
|
.print-widget .gridview_data_pane {
|
||||||
|
display: block !important;
|
||||||
|
position: relative !important;
|
||||||
|
height: max-content !important;
|
||||||
|
overflow: visible !important;
|
||||||
|
border-top: 1px solid var(--grist-color-dark-grey);
|
||||||
|
border-left: 1px solid var(--grist-color-dark-grey);
|
||||||
|
}
|
||||||
|
|
||||||
|
.print-widget .gridview_data_scroll {
|
||||||
|
position: relative !important;
|
||||||
|
height: max-content !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.print-widget .scrolly_outer {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.print-widget .custom_view {
|
||||||
|
height: calc(100vh - 24px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media not print {
|
||||||
|
.print-all-rows {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
108
app/client/components/Printing.ts
Normal file
108
app/client/components/Printing.ts
Normal file
@ -0,0 +1,108 @@
|
|||||||
|
import {CustomView} from 'app/client/components/CustomView';
|
||||||
|
import {DataRowModel} from 'app/client/models/DataRowModel';
|
||||||
|
import * as DataTableModel from 'app/client/models/DataTableModel';
|
||||||
|
import {ViewSectionRec} from 'app/client/models/DocModel';
|
||||||
|
import {dom} from 'grainjs';
|
||||||
|
|
||||||
|
type RowId = number|'new';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Print the specified viewSection (aka page widget). We use the existing view instance rather
|
||||||
|
* than render a new one, since it may have state local to this instance view, such as current
|
||||||
|
* filters.
|
||||||
|
*
|
||||||
|
* Views get a chance to render things specially for printing (which is needed when they use
|
||||||
|
* scrolly for normal rendering).
|
||||||
|
*
|
||||||
|
* To let an existing view print across multiple pages, we can't have it nested in a flexbox or a
|
||||||
|
* div with 'height: 100%'. We achieve it by forcing all parents of our view to have a simple
|
||||||
|
* layout. This is potentially fragile.
|
||||||
|
*/
|
||||||
|
export async function printViewSection(layout: any, viewSection: ViewSectionRec) {
|
||||||
|
const viewInstance = viewSection.viewInstance.peek();
|
||||||
|
const sectionElem = viewInstance?.viewPane?.closest('.viewsection_content');
|
||||||
|
if (!sectionElem) {
|
||||||
|
throw new Error("No page widget to print");
|
||||||
|
}
|
||||||
|
if (viewInstance instanceof CustomView) {
|
||||||
|
try {
|
||||||
|
await viewInstance.triggerPrint();
|
||||||
|
return;
|
||||||
|
} catch (e) {
|
||||||
|
// tslint:disable-next-line:no-console
|
||||||
|
console.warn(`Failed to trigger print in CustomView: ${e}`);
|
||||||
|
// continue on to trying to print from outside, which should work OK for a single page.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function prepareToPrint(onOff: boolean) {
|
||||||
|
// Hide all layout boxes that do NOT contain the section to be printed.
|
||||||
|
layout.forEachBox((box: any) => {
|
||||||
|
if (!box.dom.contains(sectionElem)) {
|
||||||
|
box.dom.classList.toggle('print-hide', onOff);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Mark the section to be printed.
|
||||||
|
sectionElem.classList.toggle('print-widget', onOff);
|
||||||
|
|
||||||
|
// Let the view instance update its rendering, e.g. to render all rows when scrolly is in use.
|
||||||
|
viewInstance?.prepareToPrint(onOff);
|
||||||
|
|
||||||
|
// If .print-all-rows element is present (created for scrolly-based views), use it as the
|
||||||
|
// start element for the loop below, to ensure it's rendered flexbox-free.
|
||||||
|
const keyElem = sectionElem.querySelector('.print-all-rows') || sectionElem;
|
||||||
|
|
||||||
|
// Go through all parents of the element to be printed. For @media print, we override their
|
||||||
|
// layout in a heavy-handed way, forcing them all to be non-flexbox and sized to content,
|
||||||
|
// since our normal flexbox-based layout is sized to screen and would not print multiple pages.
|
||||||
|
let elem = keyElem.parentElement;
|
||||||
|
while (elem) {
|
||||||
|
elem.classList.toggle('print-parent', onOff);
|
||||||
|
elem = elem.parentElement;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const sub1 = dom.onElem(window, 'beforeprint', () => prepareToPrint(true));
|
||||||
|
const sub2 = dom.onElem(window, 'afterprint', () => {
|
||||||
|
sub1.dispose();
|
||||||
|
sub2.dispose();
|
||||||
|
// To debug printing, set window.debugPringint=1 in the console, then print a section, dismiss
|
||||||
|
// the print dialog, switch to "@media print" emulation, and you can explore the styles. You'd
|
||||||
|
// need to reload the page to do it again.
|
||||||
|
if (!(window as any).debugPrinting) {
|
||||||
|
prepareToPrint(false);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
window.print();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Produces a div with all requested rows using the same renderRow() function as used with scrolly
|
||||||
|
* for dynamically rendered views. This is used for printing, so these rows do not subscribe to
|
||||||
|
* data.
|
||||||
|
*
|
||||||
|
* To avoid creating a lot of subscriptions when rendering rows this way, we render one DOM row at
|
||||||
|
* a time, copy the produced HTML, and dispose the produced DOM.
|
||||||
|
*/
|
||||||
|
export function renderAllRows(
|
||||||
|
tableModel: DataTableModel, rowIds: RowId[], renderRow: (r: DataRowModel) => Element,
|
||||||
|
) {
|
||||||
|
const rowModel = tableModel.createFloatingRowModel(null) as DataRowModel;
|
||||||
|
const html: string[] = [];
|
||||||
|
rowIds.forEach((rowId, index) => {
|
||||||
|
if (rowId !== 'new') {
|
||||||
|
rowModel._index(index);
|
||||||
|
rowModel.assign(rowId);
|
||||||
|
const elem = renderRow(rowModel);
|
||||||
|
html.push(elem.outerHTML);
|
||||||
|
dom.domDispose(elem);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
rowModel.dispose();
|
||||||
|
const result = dom('div.print-all-rows');
|
||||||
|
result.innerHTML = html.join("\n");
|
||||||
|
return result;
|
||||||
|
}
|
@ -7,6 +7,7 @@ import * as GridView from 'app/client/components/GridView';
|
|||||||
import {GristDoc} from 'app/client/components/GristDoc';
|
import {GristDoc} from 'app/client/components/GristDoc';
|
||||||
import {Layout} from 'app/client/components/Layout';
|
import {Layout} from 'app/client/components/Layout';
|
||||||
import {LayoutEditor} from 'app/client/components/LayoutEditor';
|
import {LayoutEditor} from 'app/client/components/LayoutEditor';
|
||||||
|
import {printViewSection} from 'app/client/components/Printing';
|
||||||
import {Delay} from 'app/client/lib/Delay';
|
import {Delay} from 'app/client/lib/Delay';
|
||||||
import {createObsArray} from 'app/client/lib/koArrayWrap';
|
import {createObsArray} from 'app/client/lib/koArrayWrap';
|
||||||
import {ViewRec, ViewSectionRec} from 'app/client/models/DocModel';
|
import {ViewRec, ViewSectionRec} from 'app/client/models/DocModel';
|
||||||
@ -124,6 +125,7 @@ export class ViewLayout extends DisposableWithEvents implements IDomComponent {
|
|||||||
deleteSection: () => { this._removeViewSection(this.viewModel.activeSectionId()); },
|
deleteSection: () => { this._removeViewSection(this.viewModel.activeSectionId()); },
|
||||||
nextSection: () => { this._otherSection(+1); },
|
nextSection: () => { this._otherSection(+1); },
|
||||||
prevSection: () => { this._otherSection(-1); },
|
prevSection: () => { this._otherSection(-1); },
|
||||||
|
printSection: () => { printViewSection(this._layout, this.viewModel.activeSection()).catch(reportError); },
|
||||||
};
|
};
|
||||||
this.autoDispose(commands.createGroup(commandGroup, this, true));
|
this.autoDispose(commands.createGroup(commandGroup, this, true));
|
||||||
}
|
}
|
||||||
|
@ -92,6 +92,11 @@ exports.groups = [{
|
|||||||
name: 'dataSelectionTabOpen',
|
name: 'dataSelectionTabOpen',
|
||||||
keys: [],
|
keys: [],
|
||||||
desc: 'Shortcut to data selection tab'
|
desc: 'Shortcut to data selection tab'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'printSection',
|
||||||
|
keys: [],
|
||||||
|
desc: 'Print currently selected page widget',
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}, {
|
}, {
|
||||||
|
1
app/client/declarations.d.ts
vendored
1
app/client/declarations.d.ts
vendored
@ -68,6 +68,7 @@ declare module "app/client/components/BaseView" {
|
|||||||
public buildTitleControls(): DomArg;
|
public buildTitleControls(): DomArg;
|
||||||
public getLoadingDonePromise(): Promise<void>;
|
public getLoadingDonePromise(): Promise<void>;
|
||||||
public onResize(): void;
|
public onResize(): void;
|
||||||
|
public prepareToPrint(onOff: boolean): void;
|
||||||
}
|
}
|
||||||
export = BaseView;
|
export = BaseView;
|
||||||
}
|
}
|
||||||
|
@ -199,6 +199,12 @@ const cssTopHeader = styled('div', `
|
|||||||
const cssResizeFlexVHandle = styled(resizeFlexVHandle, `
|
const cssResizeFlexVHandle = styled(resizeFlexVHandle, `
|
||||||
--resize-handle-color: ${colors.mediumGrey};
|
--resize-handle-color: ${colors.mediumGrey};
|
||||||
--resize-handle-highlight: ${colors.lightGreen};
|
--resize-handle-highlight: ${colors.lightGreen};
|
||||||
|
|
||||||
|
@media print {
|
||||||
|
& {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
`);
|
`);
|
||||||
const cssResizeDisabledBorder = styled('div', `
|
const cssResizeDisabledBorder = styled('div', `
|
||||||
flex: none;
|
flex: none;
|
||||||
|
@ -9,17 +9,19 @@ import {dom} from 'grainjs';
|
|||||||
*/
|
*/
|
||||||
export function makeViewLayoutMenu(viewModel: ViewRec, viewSection: ViewSectionRec, isReadonly: boolean) {
|
export function makeViewLayoutMenu(viewModel: ViewRec, viewSection: ViewSectionRec, isReadonly: boolean) {
|
||||||
return [
|
return [
|
||||||
|
menuItemCmd(allCommands.printSection, 'Print widget', testId('print-section')),
|
||||||
dom.maybe((use) => ['detail', 'single'].includes(use(viewSection.parentKey)), () =>
|
dom.maybe((use) => ['detail', 'single'].includes(use(viewSection.parentKey)), () =>
|
||||||
menuItemCmd(allCommands.editLayout, 'Edit Card Layout',
|
menuItemCmd(allCommands.editLayout, 'Edit Card Layout',
|
||||||
dom.cls('disabled', isReadonly))),
|
dom.cls('disabled', isReadonly))),
|
||||||
|
|
||||||
|
menuDivider(),
|
||||||
|
menuItemCmd(allCommands.viewTabOpen, 'Widget options', testId('widget-options')),
|
||||||
|
menuItemCmd(allCommands.sortFilterTabOpen, 'Advanced Sort & Filter'),
|
||||||
|
menuItemCmd(allCommands.dataSelectionTabOpen, 'Data selection'),
|
||||||
|
|
||||||
|
menuDivider(),
|
||||||
menuItemCmd(allCommands.deleteSection, 'Delete widget',
|
menuItemCmd(allCommands.deleteSection, 'Delete widget',
|
||||||
dom.cls('disabled', viewModel.viewSections().peekLength <= 1 || isReadonly),
|
dom.cls('disabled', viewModel.viewSections().peekLength <= 1 || isReadonly),
|
||||||
testId('section-delete')),
|
testId('section-delete')),
|
||||||
menuDivider(),
|
|
||||||
|
|
||||||
menuItemCmd(allCommands.viewTabOpen, 'Widget options', testId('widget-options')),
|
|
||||||
menuItemCmd(allCommands.sortFilterTabOpen, 'Advanced Sort & Filter'),
|
|
||||||
menuItemCmd(allCommands.dataSelectionTabOpen, 'Data selection')
|
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
@ -45,6 +45,12 @@ const cssMenuElem = styled('div', `
|
|||||||
z-index: 999;
|
z-index: 999;
|
||||||
--weaseljs-selected-background-color: ${vars.primaryBg};
|
--weaseljs-selected-background-color: ${vars.primaryBg};
|
||||||
--weaseljs-menu-item-padding: 8px 24px;
|
--weaseljs-menu-item-padding: 8px 24px;
|
||||||
|
|
||||||
|
@media print {
|
||||||
|
& {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
`);
|
`);
|
||||||
|
|
||||||
const menuItemStyle = `
|
const menuItemStyle = `
|
||||||
|
@ -150,6 +150,11 @@ if (typeof window !== 'undefined') {
|
|||||||
rpc.setSendMessage(msg => window.parent.postMessage(msg, "*"));
|
rpc.setSendMessage(msg => window.parent.postMessage(msg, "*"));
|
||||||
window.onmessage = (e: MessageEvent) => rpc.receiveMessage(e.data);
|
window.onmessage = (e: MessageEvent) => rpc.receiveMessage(e.data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Allow outer Grist application to trigger printing. This is similar to using
|
||||||
|
// iframe.contentWindow.print(), but that call does not work cross-domain.
|
||||||
|
rpc.registerFunc("print", () => window.print());
|
||||||
|
|
||||||
} else if (typeof process === 'undefined') {
|
} else if (typeof process === 'undefined') {
|
||||||
// Web worker. We can't really bring in the types for WebWorker (available with --lib flag)
|
// Web worker. We can't really bring in the types for WebWorker (available with --lib flag)
|
||||||
// without conflicting with a regular window, so use just use `self as any` here.
|
// without conflicting with a regular window, so use just use `self as any` here.
|
||||||
|
Loading…
Reference in New Issue
Block a user