2
0
mirror of https://github.com/frappe/books.git synced 2024-11-09 23:30:56 +00:00

Merge pull request #645 from frappe/print-view-issues

fix: print view issues
This commit is contained in:
Alan 2023-06-02 07:23:05 -07:00 committed by GitHub
commit 0eb0f81a88
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 369 additions and 35 deletions

View File

@ -42,8 +42,8 @@ export default function registerIpcMainActionListeners(main: Main) {
ipcMain.handle(
IPC_ACTIONS.SAVE_HTML_AS_PDF,
async (event, html, savePath) => {
return await saveHtmlAsPdf(html, savePath, app);
async (event, html, savePath, width: number, height: number) => {
return await saveHtmlAsPdf(html, savePath, app, width, height);
}
);

View File

@ -2,18 +2,12 @@ import { App, BrowserWindow } from 'electron';
import fs from 'fs/promises';
import path from 'path';
const PRINT_OPTIONS = {
marginsType: 1, // no margin
pageSize: 'A4',
printBackground: true,
printBackgrounds: true,
printSelectionOnly: false,
};
export async function saveHtmlAsPdf(
html: string,
savePath: string,
app: App
app: App,
width: number, // centimeters
height: number // centimeters
): Promise<boolean> {
/**
* Store received html as a file in a tempdir,
@ -24,7 +18,17 @@ export async function saveHtmlAsPdf(
const htmlPath = path.join(tempRoot, `${filename}.html`);
await fs.writeFile(htmlPath, html, { encoding: 'utf-8' });
const printWindow = getInitializedPrintWindow(htmlPath);
const printWindow = getInitializedPrintWindow(htmlPath, width, height);
const printOptions = {
marginsType: 1, // no margin
pageSize: {
height: height * 10_000, // micrometers
width: width * 10_000, // micrometers
},
printBackground: true,
printBackgrounds: true,
printSelectionOnly: false,
};
/**
* After the printWindow content is ready, save as pdf and
@ -32,7 +36,7 @@ export async function saveHtmlAsPdf(
*/
return await new Promise((resolve) => {
printWindow.webContents.once('did-finish-load', () => {
printWindow.webContents.printToPDF(PRINT_OPTIONS).then((data) => {
printWindow.webContents.printToPDF(printOptions).then((data) => {
fs.writeFile(savePath, data).then(() => {
printWindow.close();
fs.unlink(htmlPath).then(() => {
@ -44,10 +48,14 @@ export async function saveHtmlAsPdf(
});
}
function getInitializedPrintWindow(printFilePath: string) {
function getInitializedPrintWindow(
printFilePath: string,
width: number,
height: number
) {
const printWindow = new BrowserWindow({
width: 595,
height: 842,
width: Math.floor(width * 28.333333), // pixels
height: Math.floor(height * 28.333333), // pixels
show: false,
});

View File

@ -7,6 +7,8 @@ import { Fyo } from 'fyo';
export class PrintTemplate extends Doc {
name?: string;
type?: string;
width?: number;
height?: number;
template?: string;
isCustom?: boolean;

View File

@ -23,6 +23,18 @@
"fieldtype": "Text",
"required": true
},
{
"fieldname": "height",
"label": "Height (in cm)",
"fieldtype": "Float",
"default": 29.7
},
{
"fieldname": "width",
"label": "Width (in cm)",
"fieldtype": "Float",
"default": 21
},
{
"fieldname": "isCustom",
"label": "Is Custom",

View File

@ -38,6 +38,8 @@
:template="printProps.template"
:values="printProps.values"
:scale="scale"
:width="templateDoc?.width"
:height="templateDoc?.height"
/>
</div>
</div>

View File

@ -1,6 +1,8 @@
<template>
<ScaledContainer
:scale="Math.max(scale, 0.1)"
:width="width"
:height="height"
ref="scaledContainer"
class="mx-auto shadow-lg border"
>
@ -69,6 +71,8 @@ export default defineComponent({
props: {
template: { type: String, required: true },
scale: { type: Number, default: 0.65 },
width: { type: Number, default: 21 },
height: { type: Number, default: 29.7 },
values: {
type: Object as PropType<PrintValues>,
required: true,
@ -144,7 +148,12 @@ export default defineComponent({
return;
}
await getPathAndMakePDF(name ?? this.t`Entry`, innerHTML);
await getPathAndMakePDF(
name ?? this.t`Entry`,
innerHTML,
this.width,
this.height
);
},
},
computed: {
@ -165,7 +174,7 @@ export default defineComponent({
return '';
},
},
};
} as {} /** to silence :is type check */;
},
},
components: { ScaledContainer, ErrorBoundary },

View File

@ -18,20 +18,20 @@ import { defineComponent } from 'vue';
export default defineComponent({
props: {
height: { type: String, default: '29.7cm' },
width: { type: String, default: '21cm' },
height: { type: Number, default: 29.7 },
width: { type: Number, default: 21 },
scale: { type: Number, default: 0.65 },
},
computed: {
innerContainerStyle(): Record<string, string> {
const style: Record<string, string> = {};
style['width'] = this.width;
style['height'] = this.height;
style['width'] = this.width + 'cm';
style['height'] = this.height + 'cm';
style['transform'] = `scale(${this.scale})`;
style['margin-top'] = `calc(-1 * (${this.height} * ${
style['margin-top'] = `calc(-1 * (${this.height}cm * ${
1 - this.scale
}) / 2)`;
style['margin-left'] = `calc(-1 * (${this.width} * ${
style['margin-left'] = `calc(-1 * (${this.width}cm * ${
1 - this.scale
}) / 2)`;
@ -39,8 +39,8 @@ export default defineComponent({
},
outerContainerStyle(): Record<string, string> {
const style: Record<string, string> = {};
style['height'] = `calc(${this.scale} * ${this.height})`;
style['width'] = `calc(${this.scale} * ${this.width})`;
style['height'] = `calc(${this.scale} * ${this.height}cm)`;
style['width'] = `calc(${this.scale} * ${this.width}cm)`;
return style;
},

View File

@ -0,0 +1,265 @@
<template>
<div class="w-form">
<FormHeader :form-title="t`Set Print Size`" />
<hr />
<div class="p-4 w-full flex flex-col gap-4">
<p class="text-base text-gray-900">
{{
t`Select a pre-defined page size, or set a custom page size for your Print Template.`
}}
</p>
<Select
:df="df"
:value="size"
@change="sizeChange"
:border="true"
:show-label="true"
/>
<div class="flex gap-4 w-full">
<Float
class="w-full"
:df="fyo.getField('PrintTemplate', 'height')"
:border="true"
:show-label="true"
:value="height"
@change="(v) => valueChange(v, 'height')"
/>
<Float
class="w-full"
:df="fyo.getField('PrintTemplate', 'width')"
:border="true"
:show-label="true"
:value="width"
@change="(v) => valueChange(v, 'width')"
/>
</div>
</div>
<div class="flex border-t p-4">
<Button class="ml-auto" type="primary" @click="done">{{
t`Done`
}}</Button>
</div>
</div>
</template>
<script lang="ts">
import { PrintTemplate } from 'models/baseModels/PrintTemplate';
import { OptionField } from 'schemas/types';
import Button from 'src/components/Button.vue';
import Float from 'src/components/Controls/Float.vue';
import Select from 'src/components/Controls/Select.vue';
import FormHeader from 'src/components/FormHeader.vue';
import { defineComponent } from 'vue';
const printSizes = [
'A0',
'A1',
'A2',
'A3',
'A4',
'A5',
'A6',
'A7',
'A8',
'A9',
'B0',
'B1',
'B2',
'B3',
'B4',
'B5',
'B6',
'B7',
'B8',
'B9',
'Letter',
'Legal',
'Executive',
'C5E',
'Comm10',
'DLE',
'Folio',
'Ledger',
'Tabloid',
'Custom',
] as const;
type SizeName = typeof printSizes[number];
const paperSizeMap: Record<SizeName, { width: number; height: number }> = {
A0: {
width: 84.1,
height: 118.9,
},
A1: {
width: 59.4,
height: 84.1,
},
A2: {
width: 42,
height: 59.4,
},
A3: {
width: 29.7,
height: 42,
},
A4: {
width: 21,
height: 29.7,
},
A5: {
width: 14.8,
height: 21,
},
A6: {
width: 10.5,
height: 14.8,
},
A7: {
width: 7.4,
height: 10.5,
},
A8: {
width: 5.2,
height: 7.4,
},
A9: {
width: 3.7,
height: 5.2,
},
B0: {
width: 100,
height: 141.4,
},
B1: {
width: 70.7,
height: 100,
},
B2: {
width: 50,
height: 70.7,
},
B3: {
width: 35.3,
height: 50,
},
B4: {
width: 25,
height: 35.3,
},
B5: {
width: 17.6,
height: 25,
},
B6: {
width: 12.5,
height: 17.6,
},
B7: {
width: 8.8,
height: 12.5,
},
B8: {
width: 6.2,
height: 8.8,
},
B9: {
width: 4.4,
height: 6.2,
},
Letter: {
width: 21.59,
height: 27.94,
},
Legal: {
width: 21.59,
height: 35.56,
},
Executive: {
width: 19.05,
height: 25.4,
},
C5E: {
width: 16.3,
height: 22.9,
},
Comm10: {
width: 10.5,
height: 24.1,
},
DLE: {
width: 11,
height: 22,
},
Folio: {
width: 21,
height: 33,
},
Ledger: {
width: 43.2,
height: 27.9,
},
Tabloid: {
width: 27.9,
height: 43.2,
},
Custom: {
width: -1,
height: -1,
},
};
export default defineComponent({
props: { doc: { type: PrintTemplate, required: true } },
data() {
return { size: 'A4', width: 21, height: 29.7 };
},
components: { Float, FormHeader, Select, Button },
mounted() {
this.width = this.doc.width ?? 21;
this.height = this.doc.height ?? 29.7;
this.size = '';
Object.entries(paperSizeMap).forEach(([name, { width, height }]) => {
if (this.width === width && this.height === height) {
this.size = name;
}
});
this.size ||= 'Custom';
},
methods: {
sizeChange(v: string) {
const size = paperSizeMap[v as SizeName];
if (!size) {
return;
}
this.height = size.height;
this.width = size.width;
},
valueChange(v: number, name: 'width' | 'height') {
if (this[name] === v) {
return;
}
this.size = 'Custom';
this[name] = v;
},
done() {
this.doc.set('width', this.width);
this.doc.set('height', this.height);
this.$emit('done');
},
},
computed: {
df(): OptionField {
return {
label: 'Page Size',
fieldname: 'size',
fieldtype: 'Select',
options: printSizes.map((value) => ({ value, label: value })),
default: 'A4',
};
},
},
});
</script>

View File

@ -49,6 +49,8 @@
:template="doc.template!"
:values="values!"
:scale="scale"
:height="doc.height"
:width="doc.width"
/>
</div>
@ -203,6 +205,13 @@
</div>
</div>
</div>
<Modal
v-if="doc"
:open-modal="showSizeModal"
@closemodal="showSizeModal = !showSizeModal"
>
<SetPrintSize :doc="doc" @done="showSizeModal = !showSizeModal" />
</Modal>
</div>
</template>
<script lang="ts">
@ -216,6 +225,7 @@ import Button from 'src/components/Button.vue';
import FormControl from 'src/components/Controls/FormControl.vue';
import DropdownWithActions from 'src/components/DropdownWithActions.vue';
import HorizontalResizer from 'src/components/HorizontalResizer.vue';
import Modal from 'src/components/Modal.vue';
import PageHeader from 'src/components/PageHeader.vue';
import ShortcutKeys from 'src/components/ShortcutKeys.vue';
import { handleErrorWithDialog } from 'src/errorHandling';
@ -231,12 +241,12 @@ import {
import { docsPathRef, showSidebar } from 'src/utils/refs';
import { DocRef, PrintValues } from 'src/utils/types';
import {
ShortcutKey,
focusOrSelectFormControl,
getActionsForDoc,
getDocFromNameIfExistsElseNew,
openSettings,
selectTextFile,
ShortcutKey,
} from 'src/utils/ui';
import { useDocShortcuts } from 'src/utils/vueUtils';
import { getMapFromList } from 'utils/index';
@ -244,6 +254,7 @@ import { computed, defineComponent, inject, ref } from 'vue';
import PrintContainer from './PrintContainer.vue';
import TemplateBuilderHint from './TemplateBuilderHint.vue';
import TemplateEditor from './TemplateEditor.vue';
import SetPrintSize from './SetPrintSize.vue';
export default defineComponent({
props: { name: String },
@ -257,6 +268,8 @@ export default defineComponent({
FormControl,
TemplateBuilderHint,
ShortcutKeys,
Modal,
SetPrintSize,
},
setup() {
const doc = ref(null) as DocRef<PrintTemplate>;
@ -286,6 +299,7 @@ export default defineComponent({
scale: 0.6,
panelWidth: 22 /** rem */ * 16 /** px */,
templateChanged: false,
showSizeModal: false,
preEditMode: {
scale: 0.6,
showSidebar: true,
@ -297,6 +311,7 @@ export default defineComponent({
hints?: Record<string, unknown>;
values: null | PrintValues;
displayDoc: PrintTemplate | null;
showSizeModal: boolean;
scale: number;
panelWidth: number;
templateChanged: boolean;
@ -632,6 +647,14 @@ export default defineComponent({
},
});
if (this.doc.isCustom && !this.showSizeModal) {
actions.push({
label: this.t`Set Print Size`,
group: this.t`Action`,
action: () => (this.showSizeModal = true),
});
}
if (this.doc.isCustom) {
actions.push({
label: this.t`Select Template File`,

View File

@ -69,15 +69,22 @@ export async function saveData(data: string, savePath: string) {
await ipcRenderer.invoke(IPC_ACTIONS.SAVE_DATA, data, savePath);
}
export async function showItemInFolder(filePath: string) {
await ipcRenderer.send(IPC_MESSAGES.SHOW_ITEM_IN_FOLDER, filePath);
export function showItemInFolder(filePath: string) {
ipcRenderer.send(IPC_MESSAGES.SHOW_ITEM_IN_FOLDER, filePath);
}
export async function makePDF(html: string, savePath: string) {
export async function makePDF(
html: string,
savePath: string,
width: number,
height: number
) {
const success = await ipcRenderer.invoke(
IPC_ACTIONS.SAVE_HTML_AS_PDF,
html,
savePath
savePath,
width,
height
);
if (success) {

View File

@ -20,8 +20,9 @@ const printSettingsFields = [
'email',
'phone',
'address',
'companyName',
];
const accountingSettingsFields = ['companyName', 'gstin'];
const accountingSettingsFields = ['gstin'];
export async function getPrintTemplatePropValues(
doc: Doc
@ -212,14 +213,19 @@ async function getPrintTemplateDocValues(doc: Doc, fieldnames?: string[]) {
return values;
}
export async function getPathAndMakePDF(name: string, innerHTML: string) {
export async function getPathAndMakePDF(
name: string,
innerHTML: string,
width: number,
height: number
) {
const { filePath } = await getSavePath(name, 'pdf');
if (!filePath) {
return;
}
const html = constructPrintDocument(innerHTML);
await makePDF(html, filePath);
await makePDF(html, filePath, width, height);
}
function constructPrintDocument(innerHTML: string) {

View File

@ -20,7 +20,7 @@
<!-- Right Section -->
<section class="text-right">
<p class="font-semibold text-xl" :style="{ color: print.color }">
{{ doc.entryType }}
{{ doc.entryLabel }}
</p>
<p>{{ doc.name }}</p>
</section>