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