Skip to content

Commit

Permalink
Create cropper and screenshot editor mode
Browse files Browse the repository at this point in the history
  • Loading branch information
chukitow committed Jun 1, 2020
1 parent a7c52df commit ae4d8bc
Show file tree
Hide file tree
Showing 17 changed files with 1,114 additions and 30 deletions.
5 changes: 4 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@
"url": "https://github.com/chukitow/odin/issues"
},
"scripts": {
"build": "./node_modules/webpack/bin/webpack.js --config ./webpack.config.js",
"copy:bin": "node ./scripts/binaries.js",
"build": "./node_modules/webpack/bin/webpack.js --config ./webpack.config.js && yarn copy:bin",
"start": "npm run build && electron --enable-transparent-visuals --disable-gpu ./build/electron.js",
"postinstall": "electron-builder install-app-deps",
"package:mac": "yarn build && electron-builder -c.extraMetadata.main=build/electron.js --mac --publish=never",
Expand Down Expand Up @@ -58,10 +59,12 @@
"electron-notarize": "^0.3.0",
"electron-store": "^5.1.1",
"fluent-ffmpeg": "^2.1.2",
"jimp": "^0.12.1",
"lodash": "^4.17.15",
"react": "^16.13.1",
"react-dom": "^16.13.1",
"react-draggable": "^4.4.2",
"screenshot-desktop": "^1.12.1",
"tempy": "^0.5.0"
}
}
20 changes: 20 additions & 0 deletions scripts/binaries.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
const fs = require('fs');
const path = require('path')

fs.copyFile(
path.join(__dirname, '../node_modules/screenshot-desktop/lib/win32/screenCapture_1.3.2.bat'),
path.join(__dirname, '../build/screenCapture_1.3.2.bat'),
(err) => {
if(err) {
console.log(err);
}
});

fs.copyFile(
path.join(__dirname, '../node_modules/screenshot-desktop/lib/win32/app.manifest'),
path.join(__dirname, '../build/app.manifest'),
(err) => {
if(err) {
console.log(err);
}
});
145 changes: 145 additions & 0 deletions src/applications/cropper/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
import React, { useState, useEffect, useRef } from 'react';
import { remote, ipcRenderer } from 'electron';
import { isEqual } from 'lodash';
import cx from 'classnames';
import querystring from 'querystring';
import './styles.scss';

interface Rect {
startX?: number,
startY?: number,
width?: number,
height?: number,
};

const Cropper: React.FC = () => {
const params = querystring.parse(window.location.search.slice(1));
let dragging = false;
const DEFAULT_RECT = {
startX: 0,
startY: 0,
width: 0,
height: 0
};
const canvas = useRef(null);
const [frame, setFrame] = useState<Rect>({...DEFAULT_RECT});

const bounds = remote.getCurrentWindow().getBounds();

useEffect(() => {
let rect : Rect = {};

const startPainting = (e: MouseEvent) => {
e.preventDefault();
rect = { width: 0, height: 0};
rect.startX = e.pageX - canvas.current.offsetLeft;
rect.startY = e.pageY - canvas.current.offsetTop;
dragging = true;
ipcRenderer.send('CLOSE_CAMERA');
ipcRenderer.send('CLOSE_TOOLS');
setFrame(DEFAULT_RECT);
};

const getRectPoints = (rect: Rect) => {
return {
x: rect.width >= 0 ? rect.startX : rect.startX - Math.abs(rect.width),
y: rect.height >= 0 ? rect.startY : rect.startY - Math.abs(rect.height),
}
}

const finishPainting = () => {
dragging = false;
removeListeners();
const { x, y } = getRectPoints(rect);
if(params.screenshot) {
ipcRenderer.send('TAKE_SCREENSHOT', {
x,
y,
height: Math.abs(rect.height),
width: Math.abs(rect.width)
})
}
};

const draw = (event : MouseEvent) => {
if(!dragging ) return;

rect.width = (event.pageX - canvas.current.offsetLeft) - rect.startX;
rect.height = (event.pageY - canvas.current.offsetTop) - rect.startY ;
const { x, y } = getRectPoints(rect);
setFrame({
startX: x,
startY: y,
width: Math.abs(rect.width),
height: Math.abs(rect.height)
});
}

function removeListeners() {
canvas.current.removeEventListener('mousedown', startPainting);
canvas.current.removeEventListener('mouseup', finishPainting);
canvas.current.removeEventListener('mousemove', draw);
}

canvas.current.addEventListener('mousedown', startPainting);
canvas.current.addEventListener('mouseup', finishPainting);
canvas.current.addEventListener('mousemove', draw);

return () => {
removeListeners();
}
}, []);

const firstDrag = isEqual(DEFAULT_RECT, frame);

return (
<div className={cx('cropper', { 'first-drag': firstDrag })} ref={canvas}>
{
firstDrag &&
<div className="drag-instructions">
<strong>Drag</strong>&nbsp;to make a selection
</div>
}
{
!firstDrag &&
<>
<div
style={{
width: '100%',
height: `${frame.startY}px`,
}}
className="top" />
<div className="middle">
<div
style={{
width: frame.startX,
}}
className="left" />
<div
style={{
left: frame.startX,
top: frame.startY,
width: frame.width,
height: frame.height
}}
className="frame">
</div>
<div
style={{
width: `${bounds.width - frame.width - frame.startX}px`,
}}
className="right" />
</div>
<div
style={{
width: '100%',
height: `${bounds.height - frame.height - frame.startY}px`,
}}
className="bottom" />
</>
}
</div>
);
}

export default Cropper;
43 changes: 43 additions & 0 deletions src/applications/cropper/styles.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
.cropper {
height: 100vh;
width: 100vw;

&.first-drag {
display: flex;
justify-content: center;
align-items: center;
background-color: #233245;
opacity: 0.5;

.drag-instructions {
width: 300px;
height: 100px;
display: flex;
justify-content: center;
align-items: center;
background-color: #FFF;
font-size: 20px;
border-radius: 10px;
}
}

.top, .bottom, .left, .right {
background-color: #233245;
opacity: 0.5;
}

.middle {
display: flex;
flex: 1;
}

.frame.ready {
display: flex;
justify-content: center;
align-items: center;

.button:hover {
cursor: pointer;
}
}
}
97 changes: 97 additions & 0 deletions src/applications/image_editor/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
import React, { useEffect, useRef, useState } from 'react';
import { ipcRenderer, remote, shell } from 'electron';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import jimp from 'jimp';
import { writeFile } from 'fs';
import { uniqueId } from 'lodash';
import path from 'path';
import log from 'electron-log';
import './styles.scss';

const ImageEditor : React.FC = () => {
const imgPreview = useRef(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
ipcRenderer.on('DID_MOUNT', async (_, data) => {
try {
const image = await jimp.read(data.path);
const { x, y } = remote.getCurrentWindow().getBounds();
const activeDisplay = remote.screen.getDisplayNearestPoint({ x, y });
const { width, height } = activeDisplay.bounds;
image
.resize(width, height)
.crop(data.x, data.y, data.width, data.height);
const base64 = await new Promise((resolve, reject) => {
image.getBase64('image/png', (err, base64) => {
if(err) {
reject(err);
}
else {
resolve(base64);
}
});
});
imgPreview.current.src = base64;
setLoading(false);
} catch(err) {
log.warn(err.message);
}
});
}, [
setLoading
]);

const save = async () => {
const { filePath } = await remote.dialog.showSaveDialog({
buttonLabel: 'Save',
defaultPath: `Screenshot-${Date.now()}.png`,
});

if(!filePath) {
return;
}

const srcBase64 = imgPreview.current.src.replace(/^data:image\/png;base64,/, "");

writeFile(filePath, srcBase64, 'base64', (err) => {
if(err) {
return;
}

const id = uniqueId()
const notification = {
id,
title: 'Screenshot saved',
body: 'Click here to open',
}

ipcRenderer.send('NOTIFICATION', notification);
ipcRenderer.on(`NOTIFICATION:click:${id}`, () => {
shell.openItem(path.dirname(filePath));
});
});
}

return (
<div className="image-editor-window">
<div className="editor">
<div className="panes">
</div>
<div className="preview">
{
loading &&
<FontAwesomeIcon icon="spinner" size="3x" id="spinner" spin/>
}
<img ref={imgPreview} style={{ display: loading ? 'none' : 'block' }}/>
</div>
</div>
<div className="toolbar">
<button
onClick={save}
className="button">Save</button>
</div>
</div>
);
}

export default ImageEditor;
34 changes: 34 additions & 0 deletions src/applications/image_editor/styles.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
.image-editor-window {
display: flex;
flex-direction: column;

.toolbar {
height: 50px;
width: 100%;
background-color: #C8CCCE;
justify-content: flex-end;
align-items: center;
display: flex;
padding-right: 20px;
}

.editor {
display: flex;
flex-direction: row;

.preview {
display: flex;
justify-content: center;
align-items: center;
width: 100%;
height: calc(100vh - 50px);
padding: 20px;

img {
height: auto;
max-width: 100%;
max-height: 100%;
}
}
}
}
Loading

0 comments on commit ae4d8bc

Please sign in to comment.