Skip to content

Commit

Permalink
Feature/generate pdf (#124)
Browse files Browse the repository at this point in the history
* Update theme, fix colors

* Fix section rename bug

* Add generate buttons and loader

* Remove active id listener in paged previewer

* Add API framework for generating PDF

* Create window for paged renderer

* Creating separate page entry for paged renderer

* print to pdf once paged renderer is complete

* Minor tweaks

* update icons

* Refactor modals

* Switch to using modal for generating book, open pdf file in file explorer/finder when done

* Add platforms checkbox to generate book modal

* Fix packaged build and bump version

Co-authored-by: Zach Hannum <[email protected]>
  • Loading branch information
zachhannum and Zach Hannum authored Jun 20, 2022
1 parent 637c827 commit 548335c
Show file tree
Hide file tree
Showing 72 changed files with 1,754 additions and 923 deletions.
34 changes: 28 additions & 6 deletions .erb/configs/webpack.config.renderer.dev.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,16 +46,23 @@ const configuration: webpack.Configuration = {

target: ['web', 'electron-renderer'],

entry: [
`webpack-dev-server/client?http://localhost:${port}/dist`,
'webpack/hot/only-dev-server',
path.join(webpackPaths.srcRendererPath, 'index.tsx'),
],
entry: {
main: [
`webpack-dev-server/client?http://localhost:${port}/dist`,
'webpack/hot/only-dev-server',
path.join(webpackPaths.srcRendererPath, 'index.tsx'),
],
paged: [
`webpack-dev-server/client?http://localhost:${port}/dist`,
'webpack/hot/only-dev-server',
path.join(webpackPaths.srcRendererPath, 'paged.tsx'),
],
},

output: {
path: webpackPaths.distRendererPath,
publicPath: '/',
filename: 'renderer.dev.js',
filename: '[name].dev.js',
library: {
type: 'umd',
},
Expand Down Expand Up @@ -134,6 +141,21 @@ const configuration: webpack.Configuration = {
new HtmlWebpackPlugin({
filename: path.join('index.html'),
template: path.join(webpackPaths.srcRendererPath, 'index.ejs'),
chunks: ['main'],
minify: {
collapseWhitespace: true,
removeAttributeQuotes: true,
removeComments: true,
},
isBrowser: false,
env: process.env.NODE_ENV,
isDevelopment: process.env.NODE_ENV !== 'production',
nodeModules: webpackPaths.appNodeModulesPath,
}),
new HtmlWebpackPlugin({
filename: path.join('paged.html'),
template: path.join(webpackPaths.srcRendererPath, 'paged.ejs'),
chunks: ['paged'],
minify: {
collapseWhitespace: true,
removeAttributeQuotes: true,
Expand Down
22 changes: 19 additions & 3 deletions .erb/configs/webpack.config.renderer.prod.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,15 @@ const configuration: webpack.Configuration = {

target: ['web', 'electron-renderer'],

entry: [path.join(webpackPaths.srcRendererPath, 'index.tsx')],
entry: {
main: [path.join(webpackPaths.srcRendererPath, 'index.tsx')],
paged: [path.join(webpackPaths.srcRendererPath, 'paged.tsx')],
},

output: {
path: webpackPaths.distRendererPath,
publicPath: './',
filename: 'renderer.js',
filename: '[name].js',
library: {
type: 'umd',
},
Expand Down Expand Up @@ -105,7 +108,7 @@ const configuration: webpack.Configuration = {
}),

new MiniCssExtractPlugin({
filename: 'style.css',
filename: '[name].css',
}),

new BundleAnalyzerPlugin({
Expand All @@ -114,6 +117,7 @@ const configuration: webpack.Configuration = {

new HtmlWebpackPlugin({
filename: 'index.html',
chunks: ['main'],
template: path.join(webpackPaths.srcRendererPath, 'index.ejs'),
minify: {
collapseWhitespace: true,
Expand All @@ -123,6 +127,18 @@ const configuration: webpack.Configuration = {
isBrowser: false,
isDevelopment: process.env.NODE_ENV !== 'production',
}),
new HtmlWebpackPlugin({
filename: 'paged.html',
chunks: ['paged'],
template: path.join(webpackPaths.srcRendererPath, 'paged.ejs'),
minify: {
collapseWhitespace: true,
removeAttributeQuotes: true,
removeComments: true,
},
isBrowser: false,
isDevelopment: process.env.NODE_ENV !== 'production',
}),
],
};

Expand Down
8 changes: 5 additions & 3 deletions app/main/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import log from 'electron-log';
import MenuBuilder from './menu';
import { getPlatformWindowSettings, resolveHtmlPath } from './util';
import { setupProjectListeners } from './project';
import { setupPdfListeners } from './pdf';

export default class AppUpdater {
constructor() {
Expand Down Expand Up @@ -85,8 +86,8 @@ const createWindow = async () => {

mainWindow = new BrowserWindow({
show: false,
width: 1024,
height: 728,
width: 1200,
height: 700,
minWidth: 800,
minHeight: 600,
icon: getAssetPath('icon.png'),
Expand Down Expand Up @@ -131,14 +132,15 @@ const createWindow = async () => {
// Open urls in the user's browser
mainWindow.webContents.setWindowOpenHandler((edata) => {
shell.openExternal(edata.url);
return { action: 'deny' };
return { action: 'allow' };
});

// Remove this if your app does not use auto updates
// eslint-disable-next-line
new AppUpdater();

setupProjectListeners(mainWindow);
setupPdfListeners(mainWindow);
};

/**
Expand Down
48 changes: 48 additions & 0 deletions app/main/pdf/generatePdf.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { BrowserWindow, ipcMain, app, shell } from 'electron';
import fs from 'fs';
import path from 'path';
import { resolveHtmlPath } from '../util';
import { PagedBookContents } from 'types/types';

const generatePdf = (
mainWindow: BrowserWindow,
pdfBookContent: PagedBookContents
) => {
console.log('Creating pdf render window');
const pdfWindow = new BrowserWindow({
show: false,
parent: mainWindow,
width: 500,
height: 500,
minWidth: 800,
minHeight: 600,
webPreferences: {
preload: app.isPackaged
? path.join(__dirname, 'preload.js')
: path.join(__dirname, '../../../.erb/dll/preload.js'),
},
});
pdfWindow.loadURL(resolveHtmlPath('paged.html'));
pdfWindow.webContents.once('dom-ready', () => {
console.log('sending paged contents');
pdfWindow.webContents.send('pagedContents', pdfBookContent);
});
ipcMain.once('pagedRenderComplete', (_event, _arg) => {
pdfWindow.webContents.printToPDF({}).then((buffer: Buffer) => {
const filePath = path.join(
app.getPath('downloads'),
pdfBookContent.title.toLowerCase().trim().replace(/\s+/g, '_') + '.pdf'
);
fs.writeFile(filePath, buffer, (err) => {
if (err) {
console.log(err);
}
});
pdfWindow.close();
mainWindow.webContents.send('pdfGenerated', buffer);
shell.showItemInFolder(filePath);
});
});
};

export default generatePdf;
1 change: 1 addition & 0 deletions app/main/pdf/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default as setupPdfListeners } from './pdfListeners';
12 changes: 12 additions & 0 deletions app/main/pdf/pdfListeners.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { ipcMain } from 'electron';
import { BrowserWindow } from 'electron';
import { PagedBookContents } from '../../types/types';
import generatePdf from './generatePdf';

const setupProjectListeners = (mainWindow: BrowserWindow) => {
ipcMain.on('generatePdf', async (_event, arg: PagedBookContents) => {
generatePdf(mainWindow, arg);
});
};

export default setupProjectListeners;
19 changes: 16 additions & 3 deletions app/main/preload.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
import { contextBridge, ipcRenderer, IpcRendererEvent } from 'electron';
import type { BookDetails, ProjectData } from '../types/types';
import type {
BookDetails,
ProjectData,
PagedBookContents,
} from '../types/types';

contextBridge.exposeInMainWorld('electron', {
ipcRenderer: {
myPing() {
ipcRenderer.send('ipc-example', 'ping');
},
on(channel: string, func: (...args: unknown[]) => void) {
const validChannels = ['ipc-example', 'window'];
const validChannels = ['ipc-example', 'window', 'pagedContents'];
if (validChannels.includes(channel)) {
const subscription = (_event: IpcRendererEvent, ...args: unknown[]) =>
func(...args);
Expand All @@ -20,7 +24,7 @@ contextBridge.exposeInMainWorld('electron', {
return undefined;
},
once(channel: string, func: (...args: unknown[]) => void) {
const validChannels = ['ipc-example', 'window'];
const validChannels = ['ipc-example', 'window', 'pagedContents'];
if (validChannels.includes(channel)) {
// Deliberately strip event as it includes `sender`
ipcRenderer.once(channel, (_event, ...args) => func(...args));
Expand Down Expand Up @@ -49,3 +53,12 @@ contextBridge.exposeInMainWorld('projectApi', {
onOpenProject: (func: (projectData: ProjectData) => void) =>
ipcRenderer.on('openProject', (_event, arg) => func(arg)),
});

contextBridge.exposeInMainWorld('pagedApi', {
generateBookPdf: (pagedBookContents: PagedBookContents) => {
ipcRenderer.send('generatePdf', pagedBookContents);
},
onBookPdfGenerated: (func: (pdfStream: Buffer) => void) =>
ipcRenderer.on('pdfGenerated', (_event, arg) => func(arg)),
pagedRenderComplete: () => ipcRenderer.send('pagedRenderComplete'),
});
2 changes: 1 addition & 1 deletion app/renderer/components/ContextMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ const StyledContextMenu = styled.div<StyledContextMenuProps>`
opacity: ${(p) => (p.show ? '1' : '0')};
transform: ${(p) => (p.show ? 'scale(1)' : 'scale(.7)')};
animation: ${onOpenKeyframes} 100ms ease-in-out alternate;
background-color: ${(p) => Color(p.theme.contextMenuBg).lighten(0.2).hex()};
background-color: ${(p) => Color(p.theme.contextMenuBg).lighten(0.2).hsl().string()};
border: ${(p) => p.theme.contextMenuDivider} 1px solid;
backdrop-filter: blur(40px);
border-radius: 10px;
Expand Down
6 changes: 3 additions & 3 deletions app/renderer/components/MoreOptionsSidebarItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -63,8 +63,8 @@ const MoreOptionsSidebarItem = ({
label,
}: MoreOptionsSidebarItemProps) => {
const theme = useTheme();
const menuItemHoverColor = Color(theme.contextMenuBg).lighten(0.6).hex();
const menuItemActiveColor = Color(theme.contextMenuBg).darken(0.2).hex();
const menuItemHoverColor = Color(theme.contextMenuBg).lighten(0.6).hsl().string();
const menuItemActiveColor = Color(theme.contextMenuBg).darken(0.2).hsl().string();

return (
<StyledMenuItem
Expand All @@ -79,7 +79,7 @@ const MoreOptionsSidebarItem = ({
iconColorOverride !== undefined
? iconColorOverride
: theme.mainFgTextSecondary,
size: '10px',
size: '15px',
})}
<span>{label}</span>
</StyledMenuItemIconDesc>
Expand Down
66 changes: 43 additions & 23 deletions app/renderer/components/MoreOptionsSidebarMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,9 @@ import {
PreviewIcon,
UpdateIcon,
SaveIcon,
InfoIcon,
GenerateBookIcon,
} from '../icons';
import icon from '../../../assets/icon.png';
import useStore from '../store/useStore';
import { saveProject } from '../utils/projectUtils';

Expand All @@ -29,9 +30,11 @@ const MoreOptionsSidebarMenu = () => {
const [showMenu, setShowMenu] = useState(false);
const buttonRef = useRef<HTMLAnchorElement>(null);
const isProjectOpen = useStore((state) => state.isProjectOpen);
const activeSectionId = useStore((state) => state.activeSectionId);
const previewEnabled = useStore((state) => state.previewEnabled);
const setPreviewEnabled = useStore((state) => state.setPreviewEnabled);
const setNewBookModalOpen = useStore((state) => state.setNewBookModalOpen);
const setGenerateBookModalOpen = useStore((state) => state.setGenerateBookModalOpen);
const [menuPosition, setMenuPosition] = useState({ x: 0, y: 0 });

const getMenuPosition = (): Position => {
Expand Down Expand Up @@ -87,28 +90,45 @@ const MoreOptionsSidebarMenu = () => {
window.projectApi.openProject();
}}
/>
<MoreOptionsSidebarItem
hover
iconElement={<SaveIcon />}
rightElement={<span>⌘S</span>}
label="Save Book"
onClick={() => {
setShowMenu(false);
saveProject();
}}
/>
<MoreOptionsSidebarItem
iconElement={<PreviewIcon />}
rightElement={
<ToggleSwitch
altColor
onChange={setPreviewEnabled}
defaultValue={previewEnabled}
disabled={!isProjectOpen}
{isProjectOpen && (
<>
<MoreOptionsSidebarItem
hover
iconElement={<SaveIcon />}
rightElement={<span>⌘S</span>}
label="Save Book"
onClick={() => {
if (isProjectOpen) {
setShowMenu(false);
saveProject();
}
}}
/>
}
label="Preview"
/>
<MoreOptionsSidebarItem
hover
iconElement={<GenerateBookIcon />}
rightElement={<span>⌘G</span>}
label="Generate Book"
onClick={() => {
setShowMenu(false);
setGenerateBookModalOpen(true);
}}
/>
<MoreOptionsSidebarItem
iconElement={<PreviewIcon />}
rightElement={
<ToggleSwitch
altColor
onChange={setPreviewEnabled}
defaultValue={previewEnabled}
disabled={activeSectionId === ""}
/>
}
label="Preview"
/>
</>
)}

<StyledMenuDivider />
<MoreOptionsSidebarItem
hover
Expand All @@ -122,7 +142,7 @@ const MoreOptionsSidebarMenu = () => {
/>
<MoreOptionsSidebarItem
hover
iconElement={<img width="10px" alt="icon" src={icon} />}
iconElement={<InfoIcon />}
label="About"
/>
<MoreOptionsSidebarItem
Expand Down
Loading

0 comments on commit 548335c

Please sign in to comment.