diff --git a/pkg/interface/src/views/apps/launch/components/tiles/clock.js b/pkg/interface/src/views/apps/launch/components/tiles/clock.js
index da0cf2ad1..dd0a4c79f 100644
--- a/pkg/interface/src/views/apps/launch/components/tiles/clock.js
+++ b/pkg/interface/src/views/apps/launch/components/tiles/clock.js
@@ -1,28 +1,38 @@
import React from 'react';
import moment from 'moment';
import SunCalc from 'suncalc';
-import styled from 'styled-components';
import Tile from './tile';
-const VIEWBOX_SIZE = 100;
-const CX = VIEWBOX_SIZE / 2;
-const CY = VIEWBOX_SIZE / 2;
-const RADIUS = VIEWBOX_SIZE / 2;
-const CELESTIAL_BODY_SIZE = 16;
+const innerSize = 124; // clock size
-const ApplyClockBg = styled.div`
- .background {
- fill: ${p => p.theme.colors.white};
+// polar to cartesian
+// var ptc = function(r, theta) {
+// return {
+// x: r * Math.cos(theta),
+// y: r * Math.sin(theta)
+// }
+// }
+
+let timeTextColor = '#000000', dateTextColor = '#333333', background = '#ffffff';
+
+const dark = window.matchMedia('(prefers-color-scheme: dark)');
+
+if (dark.matches) {
+ timeTextColor = '#ffffff';
+ dateTextColor = '#7f7f7f';
+ background = '#333';
+}
+
+function darkColors(dark) {
+ if (dark.matches) {
+ timeTextColor = '#ffffff';
+ dateTextColor = '#7f7f7f';
+ background = '#ffffff';
}
- .time {
- fill: ${p => p.theme.colors.black};
- color: ${p => p.theme.colors.black};
- }
- .date {
- fill: ${p => p.theme.colors.gray};
- }
-`;
+ }
+
+dark.addListener(darkColors);
const toRelativeTime = (date, referenceTime, unit) => moment(date)
.diff(referenceTime, unit);
@@ -32,6 +42,10 @@ const minsToDegs = (mins) => {
return (mins / 1440) * 360;
};
+const splitArc = (start, end) => end + ((start - end) * 0.5);
+
+const isOdd = n => Math.abs(n % 2) == 1;
+
const radToDeg = rad => rad * (180 / Math.PI);
const degToRad = deg => deg * (Math.PI / 180);
@@ -40,137 +54,58 @@ const convert = (date, referenceTime) => {
return minsToDegs(toRelativeTime(date, referenceTime, 'minutes'));
};
-// https://github.com/tingletech/moon-phase
-export const dFromPhase = (moonPhase) => {
- let mag, sweep, d = "m50,0";
- if (moonPhase <= 0.25) {
- sweep = [ 1, 0 ];
- mag = 20 - 20 * moonPhase * 4;
- } else if (moonPhase <= 0.50) {
- sweep = [ 0, 0 ];
- mag = 20 * (moonPhase - 0.25) * 4;
- } else if (moonPhase <= 0.75) {
- sweep = [ 1, 1 ];
- mag = 20 - 20 * (moonPhase - 0.50) * 4;
- } else if (moonPhase <= 1) {
- sweep = [ 0, 1 ];
- mag = 20 * (moonPhase - 0.75) * 4;
+const circle = (ctx, x, y, r, from, to, fill) => {
+ ctx.beginPath();
+ ctx.arc( x, y, r, from, to );
+ ctx.strokeStyle = 'rgba(0,0,0,0)';
+ ctx.fillStyle = fill || 'rgba(0,0,0,0)';
+ ctx.fill();
+};
+
+const circleClip = (ctx, x, y, r, from, to, fill) => {
+ ctx.globalCompositeOperation = 'xor';
+ circle(ctx, x, y, r, from, to, fill);
+ ctx.globalCompositeOperation = 'source-over';
+};
+
+const circleOutline = (ctx, x, y, r, from, to, stroke, lineWidth) => {
+ ctx.beginPath();
+ ctx.arc( x, y, r, from, to );
+ ctx.fillStyle = 'rgba(0,0,0,0)';
+ ctx.lineWidth = lineWidth;
+ ctx.strokeStyle = stroke || 'rgba(0,0,0,0)';
+ if (lineWidth) {
+ ctx.stroke();
}
+};
- d = d + "a" + mag + ",20 0 1," + sweep[0] + " 0,100 ";
- d = d + "a20,20 0 1," + sweep[1] + " 0,-100";
- return d;
-}
+const arc = (ctx, x, y, r, from, to, fill) => {
+ ctx.beginPath();
+ ctx.arc( x, y, r, from, to );
+ ctx.fillStyle = 'rgba(0,0,0,0)';
+ ctx.lineWidth = r * 2;
+ ctx.strokeStyle = fill || 'rgba(0,0,0,0)';
+ ctx.stroke();
+};
-const Moon = ({ angle, ...props }) => {
- const phase = SunCalc.getMoonIllumination(moment().toDate()).phase.toFixed(2);
- const cx = CX + (RADIUS - 12) * Math.cos(degToRad(angle)) - (CELESTIAL_BODY_SIZE / 2);
- const cy = CY + (RADIUS - 12) * Math.sin(degToRad(angle)) - (CELESTIAL_BODY_SIZE / 2);
- return (
-
-
-
-
-
-
-
- );
-}
+const degArc = (ctx, x, y, r, from, to, fill) => {
+ ctx.beginPath();
+ ctx.arc( x, y, r, degToRad(from), degToRad(to));
+ ctx.fillStyle = 'rgba(0,0,0,0)';
+ ctx.lineWidth = r * 2;
+ ctx.strokeStyle = fill || 'rgba(0,0,0,0)';
+ ctx.stroke();
+};
-const Sun = ({ angle, ...props}) => (
-
-);
-
-const SvgArc = ({ start, end, ...rest }) => {
- const x1 = CX + RADIUS * Math.cos(degToRad(start));
- const y1 = CY + RADIUS * Math.sin(degToRad(start));
- const x2 = CX + RADIUS * Math.cos(degToRad(end));
- const y2 = CY + RADIUS * Math.sin(degToRad(end));
-
- const isLarge = Math.abs((start > 360 ? start - 360 : start) - end) > 180;
-
- const d = [
- 'M', CX, CY,
- 'L', x1, y1,
- 'A', RADIUS, RADIUS, '0', (isLarge ? '1' : '0'), '1', x2, y2, 'z'
- ].join(' ');
-
- return ;
-}
-
-class ClockText extends React.Component {
- constructor(props) {
- super(props);
- this.state = {
- time: Date.now()
- }
- }
- componentDidMount() {
- this.interval = setInterval(() => this.setState({ time: Date.now() }), 1000);
- }
-
- componentWillUnmount() {
- clearInterval(this.interval);
- }
-
- render() {
- const now = moment(this.state.time);
- return (
- <>
-
-
- {now.format('h')}
- :
-
- {now.format('mm A')}
-
- {now.format('MMM D')}{now.format('Do').replace(now.format('D'), '')}
- >
- );
- }
-}
-
-class Clock extends React.PureComponent {
+class Clock extends React.Component {
constructor(props) {
super(props);
+ this.animate = this.animate.bind(this);
+ this.canvasRef = React.createRef();
+ this.canvas = null;
this.angle = 0;
this.referenceTime = moment().startOf('day').subtract(6, 'hours');
this.state = {
- time: Date.now(),
lat: 0,
lon: 0,
geolocationSuccess: false,
@@ -229,84 +164,246 @@ class Clock extends React.PureComponent {
}
componentDidMount() {
+ this.canvas = initCanvas(
+ this.canvasRef,
+ { x: innerSize, y: innerSize },
+ 4
+ );
+
this.initGeolocation();
- this.interval = setInterval(() => this.setState({ time: Date.now() }), 60000);
+ this.animate();
}
componentWillUnmount() {
- clearInterval(this.interval);
+ if (this.animationTimer) {
+ window.clearTimeout(this.animationTimer);
+ }
+ }
+
+ animate() {
+ this.animationTimer =
+ window.setTimeout(() => window.requestAnimationFrame(this.animate), 1000);
+
+ const { state } = this;
+ const time = new Date();
+ const ctx = this.canvas.getContext('2d');
+ ctx.clearRect(0, 0, ctx.width, ctx.height);
+ ctx.save();
+
+ const ctr = innerSize / 2;
+
+ // Sun+moon calculations
+ const cx = ctr;
+ const cy = ctr;
+ this.angle = degToRad(convert(time, this.referenceTime));
+ const newX = cx + (ctr - 15) * Math.cos(this.angle);
+ const newY = cy + (ctr - 15) * Math.sin(this.angle);
+
+ // Center white circle with time and date
+ circle(
+ ctx,
+ ctr,
+ ctr,
+ ctr,
+ -1,
+ 2 * Math.PI,
+ background
+ );
+
+ // Day
+ degArc(
+ ctx,
+ ctr,
+ ctr,
+ ctr / 2,
+ state.sunriseEnd,
+ state.sunset,
+ 'rgba(33, 157, 255, .2)'
+ );
+
+ // Sunrise
+ degArc(
+ ctx,
+ ctr,
+ ctr,
+ ctr / 2,
+ state.sunsetStart,
+ state.sunriseEnd,
+ '#FFC700'
+ );
+
+ // Sunset
+ degArc(
+ ctx,
+ ctr,
+ ctr,
+ ctr / 2,
+ state.dusk,
+ state.dawn,
+ 'rgba(255, 65, 54, .8)'
+ );
+
+ // Night
+ degArc(
+ ctx,
+ ctr,
+ ctr,
+ ctr / 2,
+ state.night,
+ state.nightEnd,
+ 'rgba(0, 0, 0, .8)'
+ );
+
+ if (
+ radToDeg(this.angle) > splitArc(state.sunriseEnd, state.nightEnd)
+ && radToDeg(this.angle) < splitArc(state.sunset, state.night)
+ ) {
+ // Sun circle
+ circle(
+ ctx,
+ newX-1/2,
+ newY-1/2,
+ 8,
+ 0,
+ 2 * Math.PI,
+ '#FCC440'
+ );
+
+ // Sun circle border
+ circleOutline(
+ ctx,
+ newX-1/2,
+ newY-1/2,
+ 8,
+ 0,
+ 2 * Math.PI,
+ 'rgba(0,0,0,0.1)',
+ 1
+ );
+ } else {
+ // Moon circle
+ circle(
+ ctx,
+ newX-1/2,
+ newY-1/2,
+ 8,
+ 0,
+ 2 * Math.PI,
+ '#FFFFFF'
+ );
+ // Moon circle outline
+ circleOutline(
+ ctx,
+ newX-1/2,
+ newY-1/2,
+ 8,
+ 0,
+ 2 * Math.PI,
+ '#000000',
+ 1
+ );
+ }
+
+
+ // Outer borders
+ circleOutline(
+ ctx,
+ ctr,
+ ctr,
+ ctr-1,
+ -1,
+ 2 * Math.PI,
+ 'none',
+ 0
+ );
+
+ // Center white circle border
+ circleOutline(
+ ctx,
+ ctr,
+ ctr,
+ ctr/1.85,
+ -1,
+ 2 * Math.PI,
+ 'none',
+ 0
+ );
+
+ // Inner hole
+ circle(
+ ctx,
+ ctr,
+ ctr,
+ ctr/1.85,
+ -1,
+ 2 * Math.PI,
+ background
+ );
+
+ // Text for time and date
+ const timeText = isOdd(time.getSeconds())
+ ? moment().format('h mm A')
+ : moment().format('h:mm A');
+ const dateText = moment().format('MMM Do');
+ ctx.textAlign = 'center';
+ ctx.fillStyle = timeTextColor;
+ ctx.font = '12px Inter';
+ ctx.fillText(timeText, ctr, ctr + 6 - 7);
+ ctx.fillStyle = dateTextColor;
+ ctx.font = '12px Inter';
+ ctx.fillText(dateText, ctr, ctr + 6 + 7);
+
+ ctx.restore();
}
render() {
- const now = moment(this.state.time);
- const angle = convert(now, this.referenceTime);
-
return (
-
-
-
+