Gaëtan Renaudeau
7 years ago
committed by
GitHub
16 changed files with 304 additions and 523 deletions
@ -1,298 +0,0 @@ |
|||||
// @flow
|
|
||||
|
|
||||
/* eslint-disable import/no-named-as-default-member */ |
|
||||
|
|
||||
import React, { PureComponent } from 'react' |
|
||||
import { remote, ipcRenderer } from 'electron' |
|
||||
import { translate } from 'react-i18next' |
|
||||
import styled from 'styled-components' |
|
||||
import color from 'color' |
|
||||
import fs from 'fs' |
|
||||
import path from 'path' |
|
||||
|
|
||||
import takeRight from 'lodash/takeRight' |
|
||||
import last from 'lodash/last' |
|
||||
import reduce from 'lodash/fp/reduce' |
|
||||
import flow from 'lodash/fp/flow' |
|
||||
import filter from 'lodash/fp/filter' |
|
||||
import sortBy from 'lodash/fp/sortBy' |
|
||||
import chunk from 'lodash/chunk' |
|
||||
|
|
||||
import Chart from 'components/base/Chart' |
|
||||
import Box from 'components/base/Box' |
|
||||
import Bar from 'components/base/Bar' |
|
||||
import CopyToClipboard from 'components/base/CopyToClipboard' |
|
||||
|
|
||||
import staticPath from 'helpers/staticPath' |
|
||||
|
|
||||
import theme from 'styles/theme' |
|
||||
|
|
||||
const getLanguages = p => fs.readdirSync(p).filter(f => fs.statSync(path.join(p, f)).isDirectory()) |
|
||||
const languages = getLanguages(path.join(staticPath, '/i18n')) |
|
||||
|
|
||||
const mainWindow = remote.BrowserWindow.getAllWindows().find(w => w.name === 'MainWindow') |
|
||||
|
|
||||
type HslColor = { |
|
||||
color: Array<number>, |
|
||||
} |
|
||||
|
|
||||
type ColorType = { |
|
||||
name: string, |
|
||||
val: string, |
|
||||
isDark: boolean, |
|
||||
color: { |
|
||||
isDark: () => boolean, |
|
||||
hsl: () => HslColor, |
|
||||
}, |
|
||||
} |
|
||||
|
|
||||
type SimpleType = { |
|
||||
name: string, |
|
||||
val: string, |
|
||||
} |
|
||||
|
|
||||
const transform = flow( |
|
||||
reduce.convert({ cap: false })((acc, cur, key) => { |
|
||||
const c = color(cur) |
|
||||
return [ |
|
||||
...acc, |
|
||||
{ |
|
||||
name: key, |
|
||||
val: cur, |
|
||||
color: c, |
|
||||
isDark: c.isDark(), |
|
||||
}, |
|
||||
] |
|
||||
}, []), |
|
||||
filter(el => el.name !== 'transparent'), |
|
||||
sortBy(el => el.color.hsl().color[2]), |
|
||||
) |
|
||||
|
|
||||
const colors: Array<ColorType> = transform(theme.colors) |
|
||||
const spaces: Array<SimpleType> = theme.space.map((s, i) => ({ |
|
||||
name: s.toString(), |
|
||||
val: i.toString(), |
|
||||
})) |
|
||||
const fontSizes: Array<SimpleType> = theme.fontSizes.map((s, i) => ({ |
|
||||
name: s.toString(), |
|
||||
val: i.toString(), |
|
||||
})) |
|
||||
|
|
||||
const Container = styled(Box).attrs({ |
|
||||
bg: 'dark', |
|
||||
p: 5, |
|
||||
grow: true, |
|
||||
color: 'white', |
|
||||
fontSize: 3, |
|
||||
})`` |
|
||||
|
|
||||
const Title = styled(Box).attrs({ |
|
||||
color: 'white', |
|
||||
})` |
|
||||
text-transform: uppercase; |
|
||||
` |
|
||||
|
|
||||
const Items = styled(Box).attrs({ |
|
||||
horizontal: true, |
|
||||
flow: 4, |
|
||||
})`` |
|
||||
|
|
||||
const Item = styled(Box).attrs({ |
|
||||
alignItems: 'center', |
|
||||
bg: 'dark', |
|
||||
borderRadius: 1, |
|
||||
color: 'white', |
|
||||
justifyContent: 'center', |
|
||||
py: 2, |
|
||||
px: 4, |
|
||||
})` |
|
||||
border: 2px solid ${p => p.theme.colors.white}; |
|
||||
flex: 1; |
|
||||
overflow: hidden; |
|
||||
cursor: pointer; |
|
||||
` |
|
||||
|
|
||||
type State = { |
|
||||
cpuUsage: Object, |
|
||||
} |
|
||||
|
|
||||
class DevTools extends PureComponent<any, State> { |
|
||||
state = { |
|
||||
cpuUsage: {}, |
|
||||
} |
|
||||
|
|
||||
componentDidMount() { |
|
||||
ipcRenderer.on('msg', this.handleMessage) |
|
||||
} |
|
||||
|
|
||||
componentWillUnmount() { |
|
||||
ipcRenderer.removeListener('msg', this.handleMessage) |
|
||||
} |
|
||||
|
|
||||
handleMessage = (e: any, { type, data }: Object) => { |
|
||||
if (type === 'usage.cpu') { |
|
||||
this.setState(prev => ({ |
|
||||
cpuUsage: { |
|
||||
...prev.cpuUsage, |
|
||||
[data.name]: takeRight( |
|
||||
[ |
|
||||
...(prev.cpuUsage[data.name] || []), |
|
||||
{ |
|
||||
date: new Date().getTime().toString(), |
|
||||
value: parseFloat(data.value), |
|
||||
}, |
|
||||
], |
|
||||
10, |
|
||||
), |
|
||||
}, |
|
||||
})) |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
handleChangeLanguage = lang => () => { |
|
||||
mainWindow.webContents.send('msg', { |
|
||||
type: 'application.changeLanguage', |
|
||||
data: lang, |
|
||||
}) |
|
||||
} |
|
||||
|
|
||||
handleStartSync = () => |
|
||||
mainWindow.webContents.send('msg', { |
|
||||
type: 'accounts.sync.start', |
|
||||
}) |
|
||||
|
|
||||
handleStopSync = () => |
|
||||
mainWindow.webContents.send('msg', { |
|
||||
type: 'accounts.sync.stop', |
|
||||
}) |
|
||||
|
|
||||
render() { |
|
||||
const { cpuUsage } = this.state |
|
||||
|
|
||||
return ( |
|
||||
<Container> |
|
||||
<Box grow flow={4}> |
|
||||
<Section title="Colors"> |
|
||||
{chunk(colors, 5).map((c, i) => ( |
|
||||
<Items |
|
||||
key={i} // eslint-disable-line react/no-array-index-key
|
|
||||
> |
|
||||
{c.map(color => ( |
|
||||
<CopyToClipboard |
|
||||
key={color.name} |
|
||||
data={color.name} |
|
||||
render={copy => <Color color={color} onClick={copy} />} |
|
||||
/> |
|
||||
))} |
|
||||
</Items> |
|
||||
))} |
|
||||
</Section> |
|
||||
<Section title="Space"> |
|
||||
{chunk(spaces, 5).map((s, i) => ( |
|
||||
<Items |
|
||||
key={i} // eslint-disable-line react/no-array-index-key
|
|
||||
> |
|
||||
{s.map(space => ( |
|
||||
<CopyToClipboard |
|
||||
key={space.val} |
|
||||
data={space.val} |
|
||||
render={copy => <Item onClick={copy}>{space.name}</Item>} |
|
||||
/> |
|
||||
))} |
|
||||
</Items> |
|
||||
))} |
|
||||
</Section> |
|
||||
<Section title="Font Sizes"> |
|
||||
{chunk(fontSizes, 5).map((f, i) => ( |
|
||||
<Items |
|
||||
key={i} // eslint-disable-line react/no-array-index-key
|
|
||||
> |
|
||||
{f.map(fontSize => ( |
|
||||
<CopyToClipboard |
|
||||
key={fontSize.val} |
|
||||
data={fontSize.val} |
|
||||
render={copy => <Item onClick={copy}>{fontSize.name}</Item>} |
|
||||
/> |
|
||||
))} |
|
||||
</Items> |
|
||||
))} |
|
||||
</Section> |
|
||||
<Bar size={2} color="white" /> |
|
||||
<Section title="Languages" horizontal> |
|
||||
{languages.map(lang => ( |
|
||||
<Item key={lang} onClick={this.handleChangeLanguage(lang)} style={{ flex: 0 }}> |
|
||||
{lang} |
|
||||
</Item> |
|
||||
))} |
|
||||
</Section> |
|
||||
<Bar size={2} color="white" /> |
|
||||
<Section title="Sync Accounts" horizontal> |
|
||||
<Item onClick={this.handleStartSync} style={{ flex: 0 }}> |
|
||||
Start |
|
||||
</Item> |
|
||||
<Item onClick={this.handleStopSync} style={{ flex: 0 }}> |
|
||||
Stop |
|
||||
</Item> |
|
||||
</Section> |
|
||||
<Bar size={2} color="white" /> |
|
||||
<Section title="CPU Usage"> |
|
||||
{chunk(Object.keys(cpuUsage).sort(), 2).map((l, i) => ( |
|
||||
<Items |
|
||||
key={i} // eslint-disable-line react/no-array-index-key
|
|
||||
> |
|
||||
{l.map(k => ( |
|
||||
<Box key={k} style={{ flex: 1 }}> |
|
||||
<Box horizontal alignItems="center" flow={2}> |
|
||||
<Box fontSize={1}>{last(cpuUsage[k]).value}%</Box> |
|
||||
<Box>{k}</Box> |
|
||||
</Box> |
|
||||
<Box> |
|
||||
<Chart |
|
||||
id={k} |
|
||||
data={cpuUsage[k]} |
|
||||
dateFormat="%Q" |
|
||||
color="#8884d8" |
|
||||
height={50} |
|
||||
hideAxis |
|
||||
isInteractive={false} |
|
||||
/> |
|
||||
</Box> |
|
||||
</Box> |
|
||||
))} |
|
||||
</Items> |
|
||||
))} |
|
||||
</Section> |
|
||||
</Box> |
|
||||
</Container> |
|
||||
) |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
const Color = ({ onClick, color }: { onClick: Function, color: ColorType }) => ( |
|
||||
<Item bg={color.val} color={color.isDark ? 'white' : 'dark'} onClick={onClick}> |
|
||||
{color.name} |
|
||||
</Item> |
|
||||
) |
|
||||
|
|
||||
const Section = ({ |
|
||||
title, |
|
||||
children, |
|
||||
horizontal, |
|
||||
}: { |
|
||||
title: string, |
|
||||
children: any, |
|
||||
horizontal?: boolean, |
|
||||
}) => ( |
|
||||
<Box flow={2}> |
|
||||
<Title>{title}</Title> |
|
||||
<Box flow={4} horizontal={horizontal}> |
|
||||
{children} |
|
||||
</Box> |
|
||||
</Box> |
|
||||
) |
|
||||
|
|
||||
Section.defaultProps = { |
|
||||
horizontal: false, |
|
||||
} |
|
||||
|
|
||||
export default translate()(DevTools) |
|
@ -0,0 +1,239 @@ |
|||||
|
// @flow
|
||||
|
|
||||
|
import React, { Component } from 'react' |
||||
|
import QrCode from 'qrcode-reader' |
||||
|
|
||||
|
export default class QRCodeCameraPickerCanvas extends Component< |
||||
|
{ |
||||
|
width: number, |
||||
|
height: number, |
||||
|
centerSize: number, |
||||
|
cameraBorderSize: number, |
||||
|
cameraBorderLength: number, |
||||
|
intervalCheck: number, |
||||
|
dpr: number, |
||||
|
onPick: string => void, |
||||
|
}, |
||||
|
{ |
||||
|
message: ?string, |
||||
|
}, |
||||
|
> { |
||||
|
static defaultProps = { |
||||
|
width: 260, |
||||
|
height: 185, |
||||
|
centerSize: 110, |
||||
|
cameraBorderSize: 4, |
||||
|
cameraBorderLength: 35, |
||||
|
intervalCheck: 250, |
||||
|
dpr: window.devicePixelRatio || 1, |
||||
|
} |
||||
|
|
||||
|
state = { |
||||
|
message: 'Please accept Camera permission', |
||||
|
} |
||||
|
|
||||
|
componentDidMount() { |
||||
|
let getUserMedia |
||||
|
let sum = 0 |
||||
|
const onkeyup = (e: *) => { |
||||
|
sum += e.which |
||||
|
if (sum === 439 && this.canvasSecond) { |
||||
|
this.canvasSecond.style.filter = 'hue-rotate(90deg)' |
||||
|
} |
||||
|
} |
||||
|
if (document) document.addEventListener('keyup', onkeyup) |
||||
|
this.unsubscribes.push(() => { |
||||
|
if (document) document.removeEventListener('keyup', onkeyup) |
||||
|
}) |
||||
|
const { navigator } = window |
||||
|
if (navigator.mediaDevices) { |
||||
|
const mediaDevices = navigator.mediaDevices |
||||
|
getUserMedia = opts => mediaDevices.getUserMedia(opts) |
||||
|
} else { |
||||
|
const f = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia |
||||
|
if (f) { |
||||
|
getUserMedia = opts => new Promise((res, rej) => f.call(navigator, opts, res, rej)) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
if (!getUserMedia) { |
||||
|
this.setState({ message: 'Incompatible browser' }) // eslint-disable-line
|
||||
|
} else { |
||||
|
const qr = new QrCode() |
||||
|
qr.callback = (err, value) => { |
||||
|
if (!err) { |
||||
|
this.props.onPick(value.result) |
||||
|
} |
||||
|
} |
||||
|
getUserMedia({ |
||||
|
video: { facingMode: 'environment' }, |
||||
|
}) |
||||
|
.then(stream => { |
||||
|
if (this.unmounted) return |
||||
|
this.setState({ message: null }) |
||||
|
let video = document.createElement('video') |
||||
|
video.setAttribute('playsinline', 'true') |
||||
|
video.setAttribute('autoplay', 'true') |
||||
|
video.srcObject = stream |
||||
|
video.load() |
||||
|
this.unsubscribes.push(() => { |
||||
|
if (video) { |
||||
|
video.pause() |
||||
|
video.srcObject = null |
||||
|
video = null |
||||
|
} |
||||
|
}) |
||||
|
video.onloadedmetadata = () => { |
||||
|
if (this.unmounted || !video) return |
||||
|
try { |
||||
|
video.play() |
||||
|
} catch (e) { |
||||
|
console.error(e) |
||||
|
} |
||||
|
let lastCheck = 0 |
||||
|
let raf |
||||
|
const loop = (t: number) => { |
||||
|
raf = requestAnimationFrame(loop) |
||||
|
const { ctxMain, ctxSecond } = this |
||||
|
if (!ctxMain || !ctxSecond || !video) return |
||||
|
const { |
||||
|
centerSize, |
||||
|
cameraBorderSize, |
||||
|
cameraBorderLength, |
||||
|
dpr, |
||||
|
intervalCheck, |
||||
|
} = this.props |
||||
|
const cs = centerSize * dpr |
||||
|
const cbs = cameraBorderSize * dpr |
||||
|
const cbl = cameraBorderLength * dpr |
||||
|
const { width, height } = ctxMain.canvas |
||||
|
ctxMain.drawImage(video, 0, 0, width, height) |
||||
|
|
||||
|
// draw second in the inner
|
||||
|
const x = Math.floor((width - cs) / 2 - cbs) |
||||
|
const y = Math.floor((height - cs) / 2 - cbs) |
||||
|
const w = cs + cbs * 2 |
||||
|
const h = cs + cbs * 2 |
||||
|
ctxSecond.beginPath() |
||||
|
ctxSecond.rect(x, y, w, h) |
||||
|
ctxSecond.clip() |
||||
|
ctxSecond.drawImage(ctxMain.canvas, 0, 0) |
||||
|
|
||||
|
// draw the camera borders
|
||||
|
ctxSecond.strokeStyle = '#fff' |
||||
|
ctxSecond.lineWidth = cbs |
||||
|
ctxSecond.beginPath() |
||||
|
ctxSecond.moveTo(x + cbl, y) |
||||
|
ctxSecond.lineTo(x, y) |
||||
|
ctxSecond.lineTo(x, y + cbl) |
||||
|
ctxSecond.stroke() |
||||
|
ctxSecond.beginPath() |
||||
|
ctxSecond.moveTo(x + cbl, y + h) |
||||
|
ctxSecond.lineTo(x, y + h) |
||||
|
ctxSecond.lineTo(x, y + h - cbl) |
||||
|
ctxSecond.stroke() |
||||
|
ctxSecond.beginPath() |
||||
|
ctxSecond.moveTo(x + w - cbl, y + h) |
||||
|
ctxSecond.lineTo(x + w, y + h) |
||||
|
ctxSecond.lineTo(x + w, y + h - cbl) |
||||
|
ctxSecond.stroke() |
||||
|
ctxSecond.beginPath() |
||||
|
ctxSecond.moveTo(x + w - cbl, y) |
||||
|
ctxSecond.lineTo(x + w, y) |
||||
|
ctxSecond.lineTo(x + w, y + cbl) |
||||
|
ctxSecond.stroke() |
||||
|
|
||||
|
if (t - lastCheck >= intervalCheck) { |
||||
|
lastCheck = t |
||||
|
qr.decode(ctxMain.getImageData(0, 0, width, height)) |
||||
|
} |
||||
|
} |
||||
|
raf = requestAnimationFrame(loop) |
||||
|
this.unsubscribes.push(() => cancelAnimationFrame(raf)) |
||||
|
} |
||||
|
}) |
||||
|
.catch(e => { |
||||
|
if (this.unmounted) return |
||||
|
this.setState({ |
||||
|
message: String(e.message || e), |
||||
|
}) |
||||
|
}) |
||||
|
} |
||||
|
} |
||||
|
componentWillUnmount() { |
||||
|
this.unmounted = true |
||||
|
this.unsubscribes.forEach(f => f()) |
||||
|
} |
||||
|
|
||||
|
canvasMain: ?HTMLCanvasElement |
||||
|
ctxMain: ?CanvasRenderingContext2D |
||||
|
canvasSecond: ?HTMLCanvasElement |
||||
|
ctxSecond: ?CanvasRenderingContext2D |
||||
|
unsubscribes: Array<() => void> = [] |
||||
|
unmounted = false |
||||
|
|
||||
|
_onMainRef = (canvasMain: ?HTMLCanvasElement) => { |
||||
|
if (canvasMain === this.canvasMain) return |
||||
|
this.canvasMain = canvasMain |
||||
|
if (canvasMain) { |
||||
|
this.ctxMain = canvasMain.getContext('2d') |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
_onSecondRef = (canvasSecond: ?HTMLCanvasElement) => { |
||||
|
if (canvasSecond === this.canvasSecond) return |
||||
|
this.canvasSecond = canvasSecond |
||||
|
if (canvasSecond) { |
||||
|
this.ctxSecond = canvasSecond.getContext('2d') |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
render() { |
||||
|
const { width, height, dpr } = this.props |
||||
|
const { message } = this.state |
||||
|
const style = { |
||||
|
width, |
||||
|
height, |
||||
|
position: 'relative', |
||||
|
display: 'flex', |
||||
|
alignItems: 'center', |
||||
|
justifyContent: 'center', |
||||
|
background: '#eee', |
||||
|
color: '#666', |
||||
|
fontSize: `${(width / 30).toFixed(0)}px`, |
||||
|
overflow: 'hidden', |
||||
|
} |
||||
|
const mainStyle = { |
||||
|
width, |
||||
|
height, |
||||
|
position: 'absolute', |
||||
|
top: 0, |
||||
|
left: 0, |
||||
|
filter: 'brightness(80%) blur(6px)', |
||||
|
transform: 'scaleX(-1)', |
||||
|
} |
||||
|
const secondStyle = { |
||||
|
width, |
||||
|
height, |
||||
|
position: 'absolute', |
||||
|
top: 0, |
||||
|
left: 0, |
||||
|
transform: 'scaleX(-1)', |
||||
|
} |
||||
|
return message ? ( |
||||
|
<div style={style}> |
||||
|
<p>{message}</p> |
||||
|
</div> |
||||
|
) : ( |
||||
|
<div style={style}> |
||||
|
<canvas ref={this._onMainRef} style={mainStyle} width={dpr * width} height={dpr * height} /> |
||||
|
<canvas |
||||
|
ref={this._onSecondRef} |
||||
|
style={secondStyle} |
||||
|
width={dpr * width} |
||||
|
height={dpr * height} |
||||
|
/> |
||||
|
</div> |
||||
|
) |
||||
|
} |
||||
|
} |
@ -1,29 +0,0 @@ |
|||||
// @flow
|
|
||||
|
|
||||
import React, { PureComponent } from 'react' |
|
||||
import styled from 'styled-components' |
|
||||
|
|
||||
import Box from 'components/base/Box' |
|
||||
import GrowScroll from 'components/base/GrowScroll' |
|
||||
|
|
||||
import DevTools from 'components/DevTools' |
|
||||
|
|
||||
const Container = styled(Box).attrs({ |
|
||||
grow: true, |
|
||||
})` |
|
||||
height: 100%; |
|
||||
` |
|
||||
|
|
||||
class Dev extends PureComponent<{}> { |
|
||||
render() { |
|
||||
return ( |
|
||||
<Container> |
|
||||
<GrowScroll> |
|
||||
<DevTools /> |
|
||||
</GrowScroll> |
|
||||
</Container> |
|
||||
) |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
export default Dev |
|
@ -1,43 +0,0 @@ |
|||||
const TIMEOUT_CPU_USAGE = 5e3 |
|
||||
|
|
||||
const wait = ms => new Promise(resolve => setTimeout(resolve, ms)) |
|
||||
|
|
||||
const cpuUsage = async (startTime, startUsage) => { |
|
||||
await wait(500) |
|
||||
|
|
||||
const newStartTime = process.hrtime() |
|
||||
const newStartUsage = process.cpuUsage() |
|
||||
|
|
||||
const elapTime = process.hrtime(startTime) |
|
||||
const elapUsage = process.cpuUsage(startUsage) |
|
||||
|
|
||||
startTime = newStartTime |
|
||||
startUsage = newStartUsage |
|
||||
|
|
||||
const elapTimeMS = elapTime[0] * 1e3 + elapTime[1] / 1e6 |
|
||||
|
|
||||
const elapUserMS = elapUsage.user / 1e3 |
|
||||
const elapSystMS = elapUsage.system / 1e3 |
|
||||
const cpuPercent = (100 * (elapUserMS + elapSystMS) / elapTimeMS).toFixed(1) |
|
||||
|
|
||||
return { |
|
||||
cpuPercent, |
|
||||
newStartTime: startTime, |
|
||||
newStartUsage: startUsage, |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
export default callback => { |
|
||||
const initCpuUsage = async (startTime, startUsage) => { |
|
||||
const { cpuPercent, newStartTime, newStartUsage } = await cpuUsage(startTime, startUsage) |
|
||||
|
|
||||
callback(cpuPercent) |
|
||||
|
|
||||
setTimeout(() => initCpuUsage(newStartTime, newStartUsage), TIMEOUT_CPU_USAGE) |
|
||||
} |
|
||||
|
|
||||
const startTime = process.hrtime() |
|
||||
const startUsage = process.cpuUsage() |
|
||||
|
|
||||
initCpuUsage(startTime, startUsage) |
|
||||
} |
|
Loading…
Reference in new issue