initial commit

This commit is contained in:
2021-04-06 15:03:44 +02:00
commit 606832a141
21 changed files with 9238 additions and 0 deletions

BIN
src/front/assets/chime.mp3 Normal file

Binary file not shown.

View File

@@ -0,0 +1,60 @@
@import 'tachyons/css/tachyons.css';
body {
background-color: #111;
color: #eee;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans',
'Helvetica Neue', sans-serif;
padding: 0;
margin: 0;
}
main {
font-size: min(5vw, 5rem);
text-align: center;
width: 100vw;
height: 100vh;
display: flex;
justify-content: center;
align-items: center;
}
h1 {
border-bottom: 0.5em solid currentColor;
padding-bottom: 0.15em;
}
.countdown {
font-size: min(8rem, 25vh);
}
.tile {
position: fixed;
left: 50%;
transform: translateX(-50%);
font-size: 1.5rem;
background: #ffffff12;
border: none;
padding: 0.25em 1em;
color: currentColor;
opacity: 0.5;
transition: all 150ms ease;
}
.tile:hover {
opacity: 1;
}
.message {
top: 3rem;
}
button {
bottom: 3rem;
cursor: pointer;
appearance: none;
}
button:hover {
background-color: #2c2cce;
}

View File

@@ -0,0 +1,13 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>Look Away</title>
<meta http-equiv="Content-Security-Policy" content="script-src 'self' 'unsafe-inline';" />
<link rel="stylesheet" href="./index.css" charset="utf-8" />
</head>
<body>
<main></main>
<script src="./index.tsx"></script>
</body>
</html>

View File

@@ -0,0 +1,88 @@
console.log('test')
import { ipcRenderer } from 'electron'
import React, { useCallback, useEffect, useRef, useState } from 'react'
import { render } from 'react-dom'
// @ts-ignore
import chime from 'url:../assets/chime.mp3'
const useKeyPress = (handler) => {
const handlerRef = useRef<(e: KeyboardEvent) => void>()
useEffect(() => {
handlerRef.current = handler
}, [handler])
useEffect(() => {
const fn = (event) => handlerRef.current(event)
window.addEventListener('keydown', fn)
return () => {
window.removeEventListener('keydown', fn)
}
}, [])
}
const Banner: React.FC = () => {
const close = useCallback(() => {
ipcRenderer.send('close')
}, [])
const [done, setDone] = useState(false)
const [auto, setAuto] = useState(false)
const [countdown, setCountdown] = useState<null | number>(null)
const handler = useCallback(
(e: KeyboardEvent) => {
if (done) close()
},
[countdown, done]
)
useKeyPress(handler)
useEffect(() => {
if (done && auto) setTimeout(() => close(), 1500)
}, [done, auto])
useEffect(() => {
if (countdown === null) return
else if (countdown > 0) {
setTimeout(() => setCountdown(countdown - 1), 1000)
} else {
const audio = new Audio(chime)
audio.play()
setDone(true)
}
}, [countdown])
useEffect(() => {
const autoClose = ipcRenderer.sendSync('load', { key: 'autoClose' })
setAuto(autoClose)
const time = ipcRenderer.sendSync('load', { key: 'duration' })
setCountdown(time)
}, [])
return (
<div>
<h1 className="ma0 mb4">Look Away</h1>
<div className="code countdown">{countdown}</div>
<div className="tile message">
Look at least <b>6 meters</b> away. <br />
<small>You will hear a sound when you are done.</small>
</div>
<button className="tile" onClick={close}>
{done ? (
<span>
Close me
<br />
<small className="code f6">or press any key</small>
</span>
) : (
`I'm weak, close me now`
)}
</button>
</div>
)
}
render(<Banner />, document.querySelector('main'))

17
src/front/base.css Normal file
View File

@@ -0,0 +1,17 @@
/* @import 'hiq/dist/hiq.css'; */
@import 'spectre.css/dist/spectre.min.css';
@import 'tachyons/css/tachyons.min.css';
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans',
'Helvetica Neue', sans-serif;
}
input[type='range'] {
display: block;
width: 100%;
}
.form-switch {
cursor: pointer;
}

View File

@@ -0,0 +1,36 @@
import React, { useCallback } from 'react'
import { shell } from 'electron'
const Link: React.FC<{ text: string; link: string }> = ({ text, link }) => {
const fn = useCallback(
(e: any) => {
e.preventDefault()
shell.openExternal(link)
},
[link]
)
return (
<a onClick={fn} href={link}>
{' '}
{text}{' '}
</a>
)
}
const About: React.FC = () => {
return (
<div>
<h3 className="ma0 mb2">About</h3>
<p>
UnPixel aims at helping you following the 20/20/20 rule to alleviate stress on the eyes caused by Computer
Vision Syndrome (CVS).
<br />
Read more
<Link text="here" link="https://en.wikipedia.org/wiki/Computer_vision_syndrome" /> and
<Link text="here." link="https://www.aoa.org/healthy-eyes/eye-and-vision-conditions/computer-vision-syndrome" />
</p>
</div>
)
}
export default About

View File

@@ -0,0 +1,54 @@
import { ipcRenderer } from 'electron'
import React, { useEffect, useState } from 'react'
const labels = {
every: ['Alert every me', 'minutes'],
duration: ['For', 'seconds'],
boot: ['Start on boot'],
autoClose: ['Close window after countdown'],
}
const Field: React.FC<{ setting: keyof typeof labels }> = ({ setting: key }) => {
const label = labels[key]
const [value, setValue] = useState<null | number | boolean>(null)
useEffect(() => {
const initial = ipcRenderer.sendSync('load', { key })
setValue(initial)
}, [])
useEffect(() => {
if (value === null) return
ipcRenderer.send('save', { key, value })
}, [value])
return value === null ? null : (
<div className="ma0 mt0">
{typeof value === 'boolean' ? (
<label className="form-switch">
<input type="checkbox" id={key} onChange={(e) => setValue(e.target.checked)} checked={value} />
<i className="form-icon"></i> {label[0]}
</label>
) : (
<div>
<label htmlFor={key}>
{label[0]} <b>{value}</b> {label[1]}
</label>
<input
className="mt0 mb3"
type="range"
id={key}
min="1"
max="60"
step="1"
value={value}
onChange={(e) => setValue(parseInt(e.target.value))}
/>
</div>
)}
</div>
)
}
export default Field

View File

@@ -0,0 +1,12 @@
import React from 'react'
import { version } from '../../../package.json'
const Footer: React.FC = () => {
return (
<div className="tc mt4 f6 o-40">
<span className="code">version: {version}</span>
</div>
)
}
export default Footer

View File

@@ -0,0 +1,24 @@
import React from 'react'
import { render } from 'react-dom'
import '../base.css'
import Field from './Field'
const Settings = () => {
return (
<div>
<h3 className="ma0 mb2">Settings</h3>
<form>
<fieldset className="ma0 pa0">
<Field setting="every" />
<Field setting="duration" />
<Field setting="autoClose" />
<Field setting="boot" />
</fieldset>
</form>
</div>
)
}
export default Settings

View File

@@ -0,0 +1,12 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>Settings</title>
<meta http-equiv="Content-Security-Policy" content="script-src 'self' 'unsafe-inline';" />
</head>
<body>
<main></main>
<script src="./index.tsx"></script>
</body>
</html>

View File

@@ -0,0 +1,20 @@
import React from 'react'
import { render } from 'react-dom'
import '../base.css'
import About from './About'
import Settings from './Settings'
import Footer from './Footer'
const Main = () => {
return (
<div className="pa4">
<About />
<Settings />
<Footer />
</div>
)
}
render(<Main />, window.document.querySelector('main'))