Skip to content

Commit

Permalink
app: Add config interface for overlays
Browse files Browse the repository at this point in the history
  • Loading branch information
evanpurkhiser committed Jul 15, 2020
1 parent e1276dc commit dc41fe4
Show file tree
Hide file tree
Showing 13 changed files with 442 additions and 90 deletions.
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@
"@types/react": "^16.8.18",
"@types/react-dom": "^16.8.4",
"@types/react-router-dom": "^5.1.5",
"@types/react-select": "^3.0.14",
"@types/react-test-renderer": "^16.8.1",
"@types/semver": "^7.2.0",
"@types/socket.io": "^2.1.8",
Expand All @@ -102,7 +103,7 @@
"file-loader": "^6.0.0",
"filesize": "^6.1.0",
"fork-ts-checker-webpack-plugin": "^4.1.3",
"framer-motion": "^2.0.0-beta.76",
"framer-motion": "^1.11.1",
"html-webpack-plugin": "^4.3.0",
"http-proxy": "^1.18.1",
"image-webpack-loader": "^6.0.0",
Expand All @@ -119,6 +120,7 @@
"react-feather": "^2.0.8",
"react-refresh": "^0.8.3",
"react-router-dom": "^5.2.0",
"react-select": "^3.1.0",
"react-test-renderer": "^16.8.6",
"regenerator-runtime": "^0.13.5",
"semver": "^7.3.2",
Expand Down
4 changes: 4 additions & 0 deletions src/overlay/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,10 @@ export type OverlayDescriptor<T extends OverlayMeta = any> = {
* Provides a default configuration for the overlay
*/
defaultConfig: Partial<T['config']>;
/**
* Renders the settings UI for the overlay component
*/
configInterface: React.ComponentType<{config: T['config']}>;
};

/**
Expand Down
64 changes: 47 additions & 17 deletions src/overlay/overlays/taggedNowPlaying/Track.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import {formatDistance} from 'date-fns';

import {PlayedTrack} from 'src/shared/store';
import TimeTicker from 'src/shared/components/TimeTicker';
import {Hash, Disc, X, Layers} from 'react-feather';
import {Hash, Disc, X, Layers, Activity, Code} from 'react-feather';

const artToSrc = (d: Buffer | undefined) =>
d && d.length > 0
Expand All @@ -18,6 +18,25 @@ type OrientedMotionDivProps = MotionDivProps & {
alignRight?: boolean;
};

type TagConfig = {
icon: React.ComponentType<React.ComponentProps<typeof Disc>>;
getter: (track: PlayedTrack['track']) => string | undefined;
};

const makeTagConfig = <T extends {[name: string]: TagConfig}>(config: T) => config;

const tagsConfig = makeTagConfig({
album: {icon: Disc, getter: track => track.album?.name},
label: {icon: Layers, getter: track => track.label?.name},
comment: {icon: Hash, getter: track => track.comment},
tempo: {icon: Activity, getter: track => (track.tempo > 0 ? `${track.tempo} BPM` : '')},
key: {icon: Code, getter: track => track.key?.name},
});

export type Tags = Array<keyof typeof tagsConfig>;

export const availableTags = Object.keys(tagsConfig);

const MissingArtwork = styled(
React.forwardRef<HTMLDivElement, MotionDivProps>((p, ref) => (
<motion.div ref={ref} {...p}>
Expand All @@ -40,16 +59,16 @@ type ArtworkProps = {alignRight?: boolean; animateIn: boolean} & (

const BaseArtwork = ({animateIn, alignRight, ...p}: ArtworkProps) => {
const animation = {
init: {
initial: {
clipPath: !animateIn
? 'inset(0% 0% 0% 0%)'
: alignRight
? 'inset(0% 0% 0% 100%)'
: 'inset(0% 100% 0% 0%)',
},
enter: {
animate: {
clipPath: 'inset(0% 0% 0% 0%)',
transitionEnd: {zIndex: 10},
transitionEnd: {zIndex: 1},
},
exit: {
clipPath: 'inset(0% 0% 100% 0%)',
Expand Down Expand Up @@ -90,8 +109,8 @@ let Text = styled(motion.div)`

Text.defaultProps = {
variants: {
init: {opacity: 0, x: -20},
enter: {opacity: 1, x: 0},
initial: {opacity: 0, x: -20},
animate: {opacity: 1, x: 0},
exit: {x: 0},
},
};
Expand All @@ -111,7 +130,7 @@ const Artist = styled(Text)`

const Attributes = styled(({alignRight, ...p}: OrientedMotionDivProps) => {
const animation = {
enter: {
animate: {
x: 0,
transition: {
when: 'beforeChildren',
Expand Down Expand Up @@ -169,10 +188,10 @@ let MetadataWrapper = styled(motion.div)<{alignRight?: boolean}>`

MetadataWrapper.defaultProps = {
variants: {
init: {
initial: {
clipPath: 'inset(0% 100% 0% 0%)',
},
enter: {
animate: {
clipPath: 'inset(0% 0% 0% 0%)',
transition: {
staggerChildren: 0.2,
Expand All @@ -191,17 +210,20 @@ MetadataWrapper.defaultProps = {

type FullMetadataProps = OrientedMotionDivProps & {
track: PlayedTrack['track'];
tags: Tags;
};

const FullMetadata = ({track, ...p}: FullMetadataProps) => (
const FullMetadata = ({track, tags, ...p}: FullMetadataProps) => (
<MetadataWrapper {...p}>
<Title>{track.title}</Title>
<Artist>{track.artist?.name}</Artist>
<Attributes alignRight={p.alignRight}>
<Attribute icon={Disc} text={track.album?.name} />
<Attribute icon={Layers} text={track.label?.name} />
<Attribute icon={Hash} text={track.comment} />
{!(track.comment || track.label?.name || track.album?.name) && (
{tags.map(tag => {
const {icon, getter} = tagsConfig[tag];
const text = getter(track);
return <Attribute {...{icon, text}} />;
})}
{tags.map(t => tagsConfig[t].getter(track)).join('') === '' && (
<NoAttributes key="no-field" />
)}
</Attributes>
Expand All @@ -215,6 +237,10 @@ type BaseTrackProps = MotionDivProps & {
* Disables animation of the artwork
*/
firstPlayed?: boolean;
/**
* The list of tags to show on the 3rd row
*/
tags?: Tags;
};

const FullTrack = React.forwardRef<HTMLDivElement, BaseTrackProps>(
Expand All @@ -226,7 +252,11 @@ const FullTrack = React.forwardRef<HTMLDivElement, BaseTrackProps>(
src={artToSrc(played.artwork)}
size="80px"
/>
<FullMetadata alignRight={props.alignRight} track={played.track} />
<FullMetadata
alignRight={props.alignRight}
track={played.track}
tags={props.tags ?? []}
/>
</TrackContainer>
)
);
Expand All @@ -250,8 +280,8 @@ const TrackContainer = styled(motion.div)<{alignRight?: boolean}>`
`;

TrackContainer.defaultProps = {
animate: 'enter',
initial: 'init',
animate: 'animate',
initial: 'initial',
exit: 'exit',
};

Expand Down
83 changes: 69 additions & 14 deletions src/overlay/overlays/taggedNowPlaying/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,17 @@ import * as React from 'react';
import styled from '@emotion/styled';
import {AnimatePresence} from 'framer-motion';
import {observer} from 'mobx-react';
import {set} from 'mobx';

import store, {PlayedTrack} from 'src/shared/store';
import {OverlayDescriptor} from 'src/overlay';

import Track from './Track';
import Track, {Tags, availableTags} from './Track';
import useRandomHistory from 'src/utils/useRandomHistory';
import Checkbox from 'app/components/form/Checkbox';
import Text from 'app/components/form/Text';
import Field from 'app/components/form/Field';
import Select from 'src/renderer/components/form/Select';

type TaggedNowPlaying = {
type: 'taggedNowPlaying';
Expand All @@ -26,7 +31,7 @@ type Config = {
/**
* The specific set of tags to display
*/
tags?: string[];
tags?: Tags;
};

const CurrentTrack = ({played, ...p}: React.ComponentProps<typeof Track>) => (
Expand Down Expand Up @@ -54,31 +59,35 @@ type Props = {
history: PlayedTrack[];
};

const Overlay: React.FC<Props> = ({config, history}) =>
history.length === 0 ? null : (
const Overlay: React.FC<Props> = observer(({config, history}) => {
console.log(history);
return history.length === 0 ? null : (
<React.Fragment>
<CurrentTrack
alignRight={config.alignRight}
tags={config.tags}
firstPlayed={store.mixstatus.trackHistory.length === 1}
played={history[0]}
/>
{history.length > 1 && (
{config.historyCount > 0 && history.length > 1 && (
<RecentWrapper>
<AnimatePresence>
{history.slice(1, config.historyCount).map(track => (
{history.slice(1, config.historyCount + 1).map(track => (
<Track
mini
animate
positionTransition
alignRight={config.alignRight}
played={track}
variants={{exit: {display: 'none'}}}
key={track.playedAt.toString()}
key={`${track.playedAt}-${track.track.id}`}
/>
))}
</AnimatePresence>
</RecentWrapper>
)}
</React.Fragment>
);
});

const RecentWrapper = styled('div')`
display: grid;
Expand Down Expand Up @@ -111,26 +120,72 @@ const EmptyExample = styled('div')`
}
`;

const Example: React.FC<{config?: Config}> = () => {
const history = useRandomHistory({cutoff: 1, updateInterval: 5000});
const Example: React.FC<{config?: Config}> = ({config}) => {
const history = useRandomHistory({cutoff: 5, updateInterval: 5000});

console.log(history);

return history.length === 0 ? (
<EmptyExample />
) : (
<Overlay config={{historyCount: 0}} history={history} />
<Overlay config={config ?? {historyCount: 0}} history={history.slice().reverse()} />
);
};

const ConnectedOverlay: React.FC<{config: Config}> = observer(({config}) => (
<Overlay history={store.mixstatus.trackHistory.reverse()} config={config} />
const HistoryOverlay: React.FC<{config: Config}> = observer(({config}) => (
<Overlay history={store.mixstatus.trackHistory.slice().reverse()} config={config} />
));

const valueTransform = <T extends string[]>(t: T) => t.map(v => ({label: v, value: v}));

const ConfigInterface: React.FC<{config: Config}> = observer(({config}) => (
<div>
<Field
size="sm"
name="Align to right side"
description="Display the history and now playing details aligned towards the right of the screen."
>
<Checkbox
checked={config.alignRight}
onChange={() => set(config, {alignRight: !config.alignRight})}
/>
</Field>
<Field
size="sm"
name="History items shown"
description="Number of history items to show below the now playing metadata. You can set this to 0 to completely disable displaying history."
>
<Text
type="number"
style={{textAlign: 'center', appearance: 'textfield'}}
value={config.historyCount}
onChange={e => set(config, {historyCount: Math.max(0, Number(e.target.value))})}
/>
</Field>
<Field
size="full"
description="Select the tags you want to show on the bottom row of the now playing detail"
>
<Select
isMulti
placeholder="Add metadata items to display..."
options={valueTransform(availableTags)}
value={valueTransform(config.tags ?? [])}
onChange={values => set(config, {tags: values?.map((v: any) => v.value) ?? []})}
/>
</Field>
</div>
));

const descriptor: OverlayDescriptor<TaggedNowPlaying> = {
type: 'taggedNowPlaying',
name: 'Now playing with tags',
component: ConnectedOverlay,
component: HistoryOverlay,
example: Example,
configInterface: ConfigInterface,
defaultConfig: {
historyCount: 4,
tags: ['album', 'label', 'comment'],
},
};

Expand Down
2 changes: 1 addition & 1 deletion src/renderer/components/ConnectingSplash.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ const ConnectingSplash = () => {

return (
<Container>
<Text animate transition={{duration: 0.2}}>
<Text positionTransition transition={{duration: 0.2}}>
<Indicator />
Waiting for PROLINK devices
</Text>
Expand Down
51 changes: 51 additions & 0 deletions src/renderer/components/form/Checkbox.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import styled from '@emotion/styled';

const Checkbox = styled('input')`
margin: 0;
appearance: none;
width: 2.25rem;
height: 1.25rem;
position: relative;
&:before {
content: '';
position: absolute;
display: block;
top: 0;
left: 0;
bottom: 0;
right: 0;
border-radius: 1rem;
border: 1px solid #ccc;
transition: border-color 200ms ease-in-out;
background: #fefefe;
}
&:after {
content: '';
position: absolute;
display: block;
top: 0.25rem;
left: 0.25rem;
height: 0.75rem;
width: 0.75rem;
border-radius: 100%;
background: #9e9e9e;
transition: transform 200ms ease-in-out, background 200ms ease-in-out;
}
&:checked:before {
border-color: #72d145;
}
&:checked:after {
transform: translateX(1rem);
background: #72d145;
}
`;

Checkbox.defaultProps = {
type: 'checkbox',
};

export default Checkbox;
Loading

0 comments on commit dc41fe4

Please sign in to comment.