Skip to content

Commit 5a757e2

Browse files
authored
Screenshot (#2)
1 parent 622fbc1 commit 5a757e2

File tree

8 files changed

+1100
-3302
lines changed

8 files changed

+1100
-3302
lines changed

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -255,12 +255,14 @@
255255
"electron-store": "^8.0.0",
256256
"electron-updater": "^4.3.4",
257257
"history": "^5.0.0",
258+
"pdfkit": "^0.12.3",
258259
"qs": "^6.10.1",
259260
"react": "^17.0.1",
260261
"react-dom": "^17.0.1",
261262
"react-helmet": "^6.1.0",
262263
"react-router-dom": "^5.2.0",
263264
"regenerator-runtime": "^0.13.5",
265+
"screenshot-desktop": "^1.12.7",
264266
"source-map-support": "^0.5.19"
265267
},
266268
"devEngines": {

src/components/printer/Main.tsx

Lines changed: 104 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import React, { useState } from 'react';
22
import { ipcRenderer } from 'electron';
3+
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
34

45
interface Coord {
56
select: string;
@@ -12,7 +13,11 @@ interface Coord {
1213
const Main = () => {
1314
const [frameCoord, setFrameCoord] = useState<Coord>();
1415
const [nextCoord, setNextCoord] = useState<Coord>();
15-
const [pages, setPages] = useState(0);
16+
17+
const [pages, setPages] = useState(1);
18+
const [delay, setDelay] = useState(1);
19+
const [pageNum, setPageNum] = useState(0);
20+
const [printing, setPrinting] = useState(false);
1621

1722
const handleCloseScreen = (c: Coord) => {
1823
if (c.select === 'frame') {
@@ -33,74 +38,136 @@ const Main = () => {
3338
};
3439

3540
const handlePrint = () => {
36-
ipcRenderer.invoke('start-printing', { frameCoord, nextCoord, pages });
41+
if (printing) {
42+
setPrinting(false);
43+
ipcRenderer.invoke('stop-printing');
44+
} else {
45+
setPrinting(true);
46+
setPageNum(0);
47+
ipcRenderer.invoke('start-printing', {
48+
frameCoord,
49+
nextCoord,
50+
pages: nextCoord ? pages : 1,
51+
delay,
52+
});
53+
}
3754
};
3855

3956
ipcRenderer.on('close-screen', (_, c: Coord) => {
4057
handleCloseScreen(c);
4158
});
4259

60+
ipcRenderer.on(
61+
'print-progress',
62+
(_, { page, done }: { page: number; done: boolean }) => {
63+
setPageNum(page);
64+
if (done) {
65+
setPrinting(false);
66+
}
67+
}
68+
);
69+
4370
return (
4471
<section className="absolute inset-0 flex flex-col items-stretch justify-center p-8 space-y-8 bg-gray-100">
45-
<section className="flex flex-col items-stretch flex-1 p-4 border rounded space-y-7">
72+
<section className="flex flex-col items-stretch justify-center flex-1 px-4 py-4 space-y-6 border rounded">
4673
<section className="flex items-center justify-start space-x-4">
4774
<button
4875
type="button"
4976
onClick={() => handleOpenScreen('frame')}
5077
className="btn"
5178
>
52-
Select screenshot frame...
79+
Select printing area...
5380
</button>
5481
{frameCoord ? (
55-
<p>
56-
Rect: ({frameCoord.x0}, {frameCoord.y0}) and ({frameCoord.x1},{' '}
57-
{frameCoord.y1})
58-
</p>
82+
<span className="flex items-center justify-center space-x-2 opacity-70">
83+
<p>
84+
Rectangle: ({frameCoord.x0}, {frameCoord.y0}) ({frameCoord.x1},{' '}
85+
{frameCoord.y1})
86+
</p>
87+
<FontAwesomeIcon
88+
onClick={() => setFrameCoord(undefined)}
89+
icon="times-circle"
90+
className="w-3 h-3 cursor-pointer hover:opacity-50"
91+
/>
92+
</span>
5993
) : (
6094
<p />
6195
)}
6296
</section>
6397

64-
<section className="flex items-center justify-start space-x-4">
65-
<button
66-
type="button"
67-
onClick={() => handleOpenScreen('next')}
68-
className="btn"
98+
<section className="flex flex-col items-start justify-center space-y-3">
99+
<section className="flex items-center justify-start space-x-4">
100+
<button
101+
type="button"
102+
onClick={() => handleOpenScreen('next')}
103+
className="btn"
104+
>
105+
Select next button...
106+
</button>
107+
{nextCoord ? (
108+
<span className="flex items-center justify-center space-x-2 opacity-70">
109+
<p>
110+
Point: ({(nextCoord.x0 + nextCoord.x1) / 2},{' '}
111+
{(nextCoord.y0 + nextCoord.y1) / 2})
112+
</p>
113+
<FontAwesomeIcon
114+
onClick={() => setNextCoord(undefined)}
115+
icon="times-circle"
116+
className="w-3 h-3 cursor-pointer hover:opacity-50"
117+
/>
118+
</span>
119+
) : (
120+
<p />
121+
)}
122+
</section>
123+
124+
<section
125+
className={`flex items-center justify-start ml-5 space-x-2 ${
126+
nextCoord ? 'opacity-90' : 'opacity-50'
127+
}`}
69128
>
70-
Select next button...
71-
</button>
72-
{nextCoord ? (
73-
<p>
74-
Point: ({(nextCoord.x0 + nextCoord.x1) / 2},{' '}
75-
{(nextCoord.y0 + nextCoord.y1) / 2})
76-
</p>
77-
) : (
78-
<p />
79-
)}
80-
</section>
129+
<p>Click next button every:</p>
130+
<input
131+
value={delay}
132+
onChange={(e) => setDelay(parseInt(e.target.value, 10))}
133+
type="number"
134+
className="w-10"
135+
disabled={!nextCoord}
136+
/>
137+
<p>second{delay === 1 ? '' : 's'}</p>
138+
</section>
81139

82-
<section className="flex items-center justify-start ml-1 space-x-4">
83-
<p>Total pages:</p>
84-
<input
85-
value={pages}
86-
onChange={(e) => setPages(parseInt(e.target.value, 10))}
87-
type="number"
88-
/>
140+
<section
141+
className={`flex items-center justify-start ml-5 space-x-2 ${
142+
nextCoord ? 'opacity-90' : 'opacity-50'
143+
}`}
144+
>
145+
<p>Total clicks:</p>
146+
<input
147+
value={pages}
148+
onChange={(e) => setPages(parseInt(e.target.value, 10))}
149+
type="number"
150+
className="w-20"
151+
disabled={!nextCoord}
152+
/>
153+
<p className="text-red-600">
154+
{pageNum > 0 && printing
155+
? `(Printing page ${pageNum} of ${pages}...)`
156+
: null}
157+
</p>
158+
</section>
89159
</section>
90160
</section>
91161

92162
<section className="flex flex-col items-center space-y-4">
93163
<button
94164
type="button"
95165
onClick={handlePrint}
96-
className="w-full py-2 text-base btn"
97-
disabled={!frameCoord || !nextCoord || !pages}
166+
className="w-full py-1 text-base btn"
167+
disabled={!frameCoord || (nextCoord && !pages)}
98168
>
99-
Start printing
169+
{printing ? 'Stop printing' : 'Start printing'}
100170
</button>
101-
<p className="opacity-50">
102-
You can stop printing anytime by pressing the &quot;ESC&quot; button
103-
</p>
104171
</section>
105172
</section>
106173
);

src/helpers/fontAwesome.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { library } from '@fortawesome/fontawesome-svg-core';
22
import { fab } from '@fortawesome/free-brands-svg-icons';
3+
import { faTimesCircle } from '@fortawesome/free-solid-svg-icons';
34

4-
library.add(fab);
5+
library.add(fab, faTimesCircle);

src/main.dev.ts

Lines changed: 106 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
/* eslint-disable no-await-in-loop */
12
/* eslint global-require: off, no-console: off */
23

34
/**
@@ -17,6 +18,7 @@ import {
1718
dialog,
1819
globalShortcut,
1920
ipcMain,
21+
nativeImage,
2022
screen,
2123
shell,
2224
} from 'electron';
@@ -25,8 +27,14 @@ import log from 'electron-log';
2527
import { FileFilter, IpcMainInvokeEvent } from 'electron/main';
2628
import fs from 'fs';
2729
import { promisify } from 'util';
30+
import nodeurl from 'url';
31+
2832
import MenuBuilder from './menu';
2933

34+
const screenshot = require('screenshot-desktop');
35+
const PDFDocument = require('pdfkit');
36+
const robot = require('robotjs');
37+
3038
const writeFile = promisify(fs.writeFile);
3139
const readFile = promisify(fs.readFile);
3240

@@ -95,6 +103,7 @@ const createWindow = async () => {
95103
width: 512,
96104
height: 364,
97105
icon: getAssetPath('icon.png'),
106+
maximizable: false,
98107
webPreferences: {
99108
nodeIntegration: true,
100109
},
@@ -191,6 +200,8 @@ const createScreenWindow = (select: string) => {
191200
screenWindow = new BrowserWindow({
192201
frame: false,
193202
transparent: true,
203+
minimizable: false,
204+
maximizable: false,
194205
parent: mainWindow || undefined,
195206
width: screen.getPrimaryDisplay().size.width,
196207
height: screen.getPrimaryDisplay().size.height,
@@ -209,17 +220,22 @@ const createScreenWindow = (select: string) => {
209220
screenWindow?.webContents.send('screen-show');
210221
});
211222

212-
screenWindow.webContents.on('before-input-event', (_event, ipnut) => {
213-
if (ipnut.key === 'Escape') {
214-
screenWindow?.close();
215-
}
216-
});
217-
218223
screenWindow.on('closed', () => {
219224
screenWindow = null;
220225
});
226+
};
221227

222-
screenWindow.webContents.openDevTools({ mode: 'undocked' });
228+
const openPdf = (pdfPath: string) => {
229+
const win = new BrowserWindow({
230+
title: 'Preview',
231+
width: 512,
232+
height: 768,
233+
webPreferences: {
234+
plugins: true,
235+
contextIsolation: false,
236+
},
237+
});
238+
win.loadURL(nodeurl.pathToFileURL(pdfPath).toString());
223239
};
224240

225241
ipcMain.handle('open-screen', async (_, { select }) => {
@@ -235,14 +251,92 @@ ipcMain.handle('close-screen', (_, coord) => {
235251
screenWindow?.close();
236252
});
237253

238-
ipcMain.handle('start-printing', (_, { frameCoord, nextCoord, pages }) => {
239-
console.log('Print with params', frameCoord, nextCoord, pages);
254+
interface Coord {
255+
select: string;
256+
x0: number;
257+
y0: number;
258+
x1: number;
259+
y1: number;
260+
}
240261

241-
if (stopPrinting) {
242-
console.log('Stop now');
243-
}
262+
interface Screenshot {
263+
frameCoord: Coord;
264+
nextCoord: Coord;
265+
pages: number;
266+
delay: number;
267+
}
268+
269+
ipcMain.handle('stop-printing', () => {
270+
stopPrinting = true;
244271
});
245272

273+
ipcMain.handle(
274+
'start-printing',
275+
async (_, { frameCoord, nextCoord, pages, delay }: Screenshot) => {
276+
// Calculate x, y
277+
let x = frameCoord.x0 > frameCoord.x1 ? frameCoord.x1 : frameCoord.x0;
278+
let y = frameCoord.x0 > frameCoord.x1 ? frameCoord.y1 : frameCoord.y0;
279+
let width = Math.abs(frameCoord.x0 - frameCoord.x1);
280+
let height = Math.abs(frameCoord.y0 - frameCoord.y1);
281+
282+
// For retina screen and the like
283+
const factor = screen.getPrimaryDisplay().scaleFactor;
284+
x *= factor;
285+
y *= factor;
286+
width *= factor;
287+
height *= factor;
288+
289+
const doc = new PDFDocument({ autoFirstPage: false });
290+
const pdfPath = path.join(app.getPath('temp'), 'preview.pdf');
291+
doc.pipe(fs.createWriteStream(pdfPath));
292+
293+
try {
294+
for (let p = 0; p < pages; p += 1) {
295+
// Screenshot
296+
const buff: Buffer = await screenshot({ format: 'png' });
297+
const image = nativeImage.createFromBuffer(buff);
298+
const png = image.crop({ x, y, height, width }).toPNG();
299+
300+
// Create pdf
301+
doc.addPage({ size: [width, height] });
302+
doc.image(png, 0, 0);
303+
doc.save();
304+
305+
// Click
306+
if (nextCoord) {
307+
const nextX = (nextCoord.x0 + nextCoord.x1) / 2;
308+
const nextY = (nextCoord.y0 + nextCoord.y1) / 2;
309+
robot.moveMouse(nextX, nextY);
310+
robot.mouseClick();
311+
}
312+
313+
// Send progress
314+
mainWindow?.webContents.send('print-progress', {
315+
page: p + 1,
316+
done: p + 1 === pages,
317+
});
318+
319+
// Sleep
320+
await new Promise((resolve) => setTimeout(resolve, 1000 * delay));
321+
322+
if (stopPrinting) {
323+
stopPrinting = false;
324+
mainWindow?.webContents.send('print-progress', {
325+
page: p + 1,
326+
done: true,
327+
});
328+
break;
329+
}
330+
}
331+
332+
doc.end();
333+
openPdf(pdfPath);
334+
} catch (e) {
335+
console.error(e);
336+
}
337+
}
338+
);
339+
246340
/**
247341
* Add event listeners...
248342
*/

0 commit comments

Comments
 (0)