Skip to content

Commit

Permalink
Sonos integration 90% done (disabled), addition of Halloween theme
Browse files Browse the repository at this point in the history
  • Loading branch information
mbohgard committed Oct 29, 2023
1 parent 1a8e475 commit b187a26
Show file tree
Hide file tree
Showing 43 changed files with 1,240 additions and 119 deletions.
5 changes: 5 additions & 0 deletions @types/common.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,3 +32,8 @@ declare interface LightConfig {
label: string;
};
}

declare module "*.png" {
const content: string;
export default content;
}
52 changes: 42 additions & 10 deletions @types/services.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -247,12 +247,16 @@ declare interface FoodResponse {
}

interface SonosTrack {
artist: string;
title: string;
album: string;
artist?: string;
title?: string;
album?: string;
duration: 116;
type: string;
absoluteAlbumArtUri: string;
type: "track" | "line_in";
stationName?: string;
uri?: string;
trackUri?: string;
absoluteAlbumArtUri?: string;
albumArtSrc?: string;
}

interface SonosCompleteDevice {
Expand All @@ -265,7 +269,7 @@ interface SonosCompleteDevice {
trackNo: number;
elapsedTime: number;
elapsedTimeFormatted: string;
playbackState: "PLAYING" | "PAUSED_PLAYBACK";
playbackState: "PLAYING" | "PAUSED_PLAYBACK" | "STOPPED" | "TRANSITIONING";
playMode: {
repeat: string;
shuffle: boolean;
Expand Down Expand Up @@ -293,6 +297,34 @@ declare type SonosResponse = Array<{
members: SonosCompleteDevice[];
}>;

declare type SonosFeedResponse =
| {
type: "transport-state";
data: SonosCompleteDevice;
}
| {
type: "volume-change";
data: {
uuid: string;
previousVolume: number;
newVolume: number;
roomName: string;
};
}
| {
type: "mute-change";
data: {
uuid: string;
previousMute: boolean;
newMute: boolean;
roomName: string;
};
}
| {
type: "topology-change";
data: SonosResponse;
};

declare interface SonosEmit {
roomName: string;
action: "play" | "pause" | "volume" | "next" | "previous";
Expand All @@ -304,6 +336,9 @@ declare type InitServiceData = ServiceData<{
launched: number;
config: LightConfig;
}>;
declare type ControlServiceData = ServiceData<{
action: "RELOAD";
}>;
declare type TimeServiceData = ServiceData<TimeZone>;
declare type EnergyServiceData = ServiceData<Energy>;
declare type WeatherServiceData = ServiceData<Forecast>;
Expand All @@ -322,7 +357,4 @@ declare type HueServiceData = ServiceData<HueGroups>;
declare type VOCServiceData = ServiceData<VOCData>;
declare type CalendarServiceData = ServiceData<CalendarEvent[]>;
declare type FoodServiceData = ServiceData<FoodWeek[]>;
declare type SonosServiceData = ServiceData<
SonosDevice[],
{ playing: boolean }
>;
declare type SonosServiceData = ServiceData<SonosDevice[], { feed: boolean }>;
3 changes: 2 additions & 1 deletion config.example.js
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ module.exports = {
label: "",
},
sonos: {
api: "", // url to local node-sonos-http-api
api: "", // url to local node-sonos-http-api,
feed: "",
},
};
6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
"ts:dev:no-poll": "ts-node --files ./src/server/index.ts no-poll",
"dev": "nodemon --exec 'yarn ts:dev' & yarn dev:client",
"dev:no-poll": "nodemon --exec 'yarn ts:dev:no-poll' & yarn dev:client",
"dev:client": "parcel src/index.html -p 8080 ",
"dev:client": "parcel src/index.html -p 8080",
"tsc": "tsc --noEmit --noUnusedLocals",
"build": "rm -rf dist && yarn install && parcel build src/index.html",
"serve": "NODE_ENV=production TS_NODE_FILES=true ts-node ./src/server/index.ts",
Expand All @@ -22,7 +22,7 @@
"@types/react": "^18.0.25"
},
"dependencies": {
"@ctrl/tinycolor": "^4.0.1",
"@ctrl/tinycolor": "^4.0.2",
"@emotion/is-prop-valid": "^1.2.1",
"axios": "^1.5.0",
"axios-retry": "^3.7.0",
Expand Down Expand Up @@ -53,7 +53,7 @@
"buffer": "^6.0.3",
"nodemon": "^3.0.1",
"parcel": "^2.9.3",
"prettier": "^3.0.2",
"prettier": "^3.0.3",
"process": "^0.11.10",
"ts-node": "^10.9.1",
"typescript": "5.2.2"
Expand Down
Binary file added src/assets/halloween/chucky.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added src/assets/halloween/freddy.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added src/assets/halloween/jason.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added src/assets/halloween/jason2.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added src/assets/halloween/pennywise.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added src/assets/halloween/pennywise2.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added src/assets/halloween/scream.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added src/assets/halloween/scream2.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added src/assets/halloween/spider.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added src/assets/halloween/spiderweb1.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added src/assets/halloween/spiderweb2.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added src/assets/halloween/spiderweb3.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added src/assets/halloween/spiderweb4.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions src/assets/icons/caret.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
6 changes: 6 additions & 0 deletions src/assets/icons/pause.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 4 additions & 0 deletions src/assets/icons/play.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 4 additions & 0 deletions src/assets/icons/skip.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
6 changes: 6 additions & 0 deletions src/assets/icons/speaker.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
69 changes: 56 additions & 13 deletions src/components/About.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,11 @@ import React, { useState, useRef, useLayoutEffect } from "react";
import styled from "styled-components";

import { colors } from "../styles";
import { ms2Sec, sec2Str } from "../utils/time";
import { ms2Sec, sec2Time } from "../utils/time";
import { useConnected } from "../hooks";

import { Overlay } from "./Molecules";
import { settingsStore } from "../stores";

const Trigger = styled.a`
position: fixed;
Expand All @@ -18,7 +19,7 @@ const Trigger = styled.a`
`;

const Content = styled.div`
padding: 30px;
padding: 20px;
background: linear-gradient(
0deg,
rgba(255, 201, 161, 1) 0%,
Expand All @@ -28,6 +29,13 @@ const Content = styled.div`
margin: 0 auto;
color: rgba(0, 0, 0, 0.7);
min-width: 380px;
line-height: 1.65;
font-size: 18px;
text-align: center;
strong {
font-weight: bold;
}
h2 {
font-size: 30px;
Expand All @@ -38,13 +46,6 @@ const Content = styled.div`

const ContentData = styled.div`
white-space: nowrap;
line-height: 1.65;
font-size: 18px;
text-align: center;
strong {
font-weight: bold;
}
`;

const Status = styled.span<{ connected: boolean }>`
Expand All @@ -56,6 +57,45 @@ const Status = styled.span<{ connected: boolean }>`
color: ${colors.white};
`;

const SettingsContainer = styled.div`
display: flex;
gap: 10px;
padding-top: 16px;
flex-wrap: wrap;
justify-content: center;
strong {
width: 100%;
}
`;

const Setting = styled.button<{ active: boolean }>`
background: ${(p) => (p.active ? colors.green : colors.gray)};
color: ${(p) => (p.active ? colors.white : colors.lightGray)};
text-transform: capitalize;
border: none;
padding: 4px 8px;
`;

const Settings: React.FC = () => {
const [settings, setSettings] = settingsStore.useStore();

return (
<SettingsContainer>
<strong>Settings:</strong>
{Object.entries(settings).map(([key, val]) => (
<Setting
key={key}
active={val}
onClick={() => setSettings((s) => ({ ...s, [key]: !val }))}
>
{key}
</Setting>
))}
</SettingsContainer>
);
};

type Props = {
version?: string;
launched?: number;
Expand All @@ -80,8 +120,8 @@ export const About = ({ version, launched: serverStarted }: Props) => {
}, [show]);

return show ? (
<Overlay closeOnPress autoClose={5000} close={() => setShow(false)}>
<Content>
<Overlay closeOnPress autoClose={undefined} close={() => setShow(false)}>
<Content onClick={(e) => e.stopPropagation()}>
<h2>dashboard v{version}</h2>
<ContentData>
<strong>Status:</strong>{" "}
Expand All @@ -90,13 +130,16 @@ export const About = ({ version, launched: serverStarted }: Props) => {
</Status>
</ContentData>
<ContentData>
<strong>Client uptime:</strong> {sec2Str(now - started.current)}
<strong>Client uptime:</strong>{" "}
{sec2Time(now - started.current).formatted}
</ContentData>
{serverStarted && connected && (
<ContentData>
<strong>Server uptime:</strong> {sec2Str(now - serverStarted)}
<strong>Server uptime:</strong>{" "}
{sec2Time(now - serverStarted).formatted}
</ContentData>
)}
<Settings />
</Content>
</Overlay>
) : (
Expand Down
87 changes: 87 additions & 0 deletions src/components/Dropdown.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import React, { useEffect, useState } from "react";
import styled from "styled-components";
import { colors } from "../styles";
import { Icon } from "./Icon";

const Container = styled.div<{
active: boolean;
position: Position;
}>`
position: relative;
display: flex;
button {
color: ${(p) => (p.active ? colors.white : colors.dimmed)};
}
ul {
position: absolute;
left: 0;
top: ${(p) => (p.position === "below" ? "100%" : "auto")};
bottom: ${(p) => (p.position === "above" ? "100%" : "auto")};
display: ${(p) => (p.active ? "block" : "none")};
}
`;

const Select = styled.button`
background: ${colors.black};
border: solid 3px ${colors.superDimmed};
border-radius: 8px;
padding: 8px;
display: flex;
gap: 20px;
font-size: 16px;
align-items: center;
svg {
fill: currentColor;
}
`;

type Position = "above" | "below";

type DropdownProps = {
position?: Position;
items?: Array<{
label: string;
value: string;
}>;
defaultValue?: string;
};

export const Dropdown: React.FC<DropdownProps> = ({
position = "below",
items = [],
defaultValue,
}) => {
const [selected, setSelected] = useState(
defaultValue ?? items[0]?.value ?? null
);
const [active, setActive] = useState(false);

useEffect(() => {
if (!active) return;

const listener = () => setActive(false);

window.setTimeout(() => document.addEventListener("click", listener));

return () => document.removeEventListener("click", listener);
}, [active]);

return (
<Container position={position} active={active}>
<Select type="button" onClick={() => setActive((s) => !s)}>
{selected}
<Icon Caret size={20} />
</Select>
<ul>
{items.map(({ label, value }) => (
<li key={value} onClick={() => setSelected(value)}>
{label}
</li>
))}
</ul>
</Container>
);
};
4 changes: 3 additions & 1 deletion src/components/Errors.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@ import { errorStore } from "../stores";
import { Icon } from "./Icon";
import { Overlay } from "./Molecules";

const ErrorsContainer = styled(Overlay)`
const ErrorsContainer = styled(Overlay).withConfig({
shouldForwardProp: () => true,
})`
border: solid 10px ${colors.red};
`;

Expand Down
Loading

0 comments on commit b187a26

Please sign in to comment.