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:
commit
0eb0f81a88
@ -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);
|
||||
}
|
||||
);
|
||||
|
||||
|
@ -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,
|
||||
});
|
||||
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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",
|
||||
|
@ -38,6 +38,8 @@
|
||||
:template="printProps.template"
|
||||
:values="printProps.values"
|
||||
:scale="scale"
|
||||
:width="templateDoc?.width"
|
||||
:height="templateDoc?.height"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -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 },
|
||||
|
@ -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;
|
||||
},
|
||||
|
265
src/pages/TemplateBuilder/SetPrintSize.vue
Normal file
265
src/pages/TemplateBuilder/SetPrintSize.vue
Normal 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>
|
@ -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`,
|
||||
|
@ -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) {
|
||||
|
@ -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) {
|
||||
|
@ -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>
|
||||
|
Loading…
Reference in New Issue
Block a user