Skip to content

Commit b59ba7b

Browse files
Add [ + ] button to player volume dropdown to enable adding and removing of zones from player view
1 parent 18b969b commit b59ba7b

9 files changed

Lines changed: 73 additions & 52 deletions

File tree

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
# AmpliPi Software Releases
22

33
# Future Release
4+
* Web App
5+
* Added [ + ] button to player page to allow selection of zones and groups from player page
46

57

68
# 0.4.11

web/src/components/RectangularButton/RectangularButton.jsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ export default function RectangularButton({onClick, large, label}){
1111
>
1212
{label}
1313
</div>
14-
)
14+
);
1515
}
1616
RectangularButton.propTypes = {
1717
onClick: PropTypes.func.isRequired,
@@ -20,4 +20,4 @@ RectangularButton.propTypes = {
2020
};
2121
RectangularButton.defaultProps = {
2222
large: false,
23-
}
23+
};

web/src/components/RectangularButton/RectangularButton.scss

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
@use "src/general";
22

33
.rectangular-button {
4-
// @include general.low-shadow;
4+
@include general.low-shadow;
55
@include general.regular-font;
66
width: 100%;
77
height: 4rem;

web/src/components/VolumeZones/VolumeZones.jsx

Lines changed: 22 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,16 @@ import "./VolumesZones.scss";
33
import ZoneVolumeSlider from "../ZoneVolumeSlider/ZoneVolumeSlider";
44
import GroupVolumeSlider from "../GroupVolumeSlider/GroupVolumeSlider";
55
import Card from "../Card/Card";
6+
import RectangularButton from "@/components/RectangularButton/RectangularButton";
67

78
import PropTypes from "prop-types";
89

9-
const VolumeZones = ({ sourceId, open, zones, groups, groupsLeft, alone }) => {
10+
const VolumeZones = ({ sourceId, open, zones, groups, groupsLeft, setZonesModalOpen }) => {
1011
const groupVolumeSliders = [];
1112
for (const group of groups) {
1213
groupVolumeSliders.push(
13-
<Card secondary={!alone} className={`group-vol-card ${!alone ? "vol-margin" : ""}`} key={group.id}>
14+
<Card secondary className="group-vol-card vol-margin" key={group.id}>
1415
<GroupVolumeSlider
15-
alone={alone}
1616
groupId={group.id}
1717
sourceId={sourceId}
1818
groupsLeft={groupsLeft}
@@ -24,19 +24,28 @@ const VolumeZones = ({ sourceId, open, zones, groups, groupsLeft, alone }) => {
2424
const zoneVolumeSliders = [];
2525
zones.forEach((zone) => {
2626
zoneVolumeSliders.push(
27-
<Card secondary={!alone} className={`zone-vol-card ${!alone ? "vol-margin" : ""}`} key={zone.id}>
28-
<ZoneVolumeSlider alone={alone} zoneId={zone.id} />
27+
<Card secondary className="zone-vol-card vol-margin" key={zone.id}>
28+
<ZoneVolumeSlider zoneId={zone.id} />
2929
</Card>
3030
);
3131
});
3232

33-
if(open){
34-
return (
35-
<div className="volume-sliders-container">
36-
{groupVolumeSliders}
37-
{zoneVolumeSliders}
38-
</div>
39-
);
33+
const noZones = zoneVolumeSliders.length == 0 && groupVolumeSliders.length == 0;
34+
35+
if (open) {
36+
return (
37+
<div className={`volume-sliders-container ${(!noZones) && "add-padding"}`}>
38+
{groupVolumeSliders}
39+
{zoneVolumeSliders}
40+
<RectangularButton large label="+" onClick={() => {setZonesModalOpen(true);}} />
41+
</div>
42+
);
43+
} else if (noZones){
44+
return(
45+
<div className="volume-sliders-container">
46+
<RectangularButton large label="+" onClick={() => {setZonesModalOpen(true);}} />
47+
</div>
48+
);
4049
}
4150
};
4251
VolumeZones.propTypes = {
@@ -45,10 +54,7 @@ VolumeZones.propTypes = {
4554
zones: PropTypes.array.isRequired,
4655
groups: PropTypes.array.isRequired,
4756
groupsLeft: PropTypes.array.isRequired,
48-
alone: PropTypes.bool,
57+
setZonesModalOpen: PropTypes.func.isRequired,
4958
};
50-
VolumeZones.defaultProps = {
51-
alone: false,
52-
}
5359

5460
export default VolumeZones;

web/src/components/VolumeZones/VolumesZones.scss

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,10 @@
55
color: general.$controls-color;
66
}
77

8+
.add-padding {
9+
padding-bottom: 1rem;
10+
}
11+
812
.zone-vol-card {
913
@media (max-width: general.$small-mobile){
1014
padding: 0.5rem;

web/src/components/ZoneVolumeSlider/ZoneVolumeSlider.jsx

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@ import PropTypes from "prop-types";
88
let sendingRequestCount = 0;
99

1010
// Volume slider for individual zone in volume drawer
11-
const ZoneVolumeSlider = ({ zoneId, alone }) => {
11+
const ZoneVolumeSlider = ({ zoneId }) => {
12+
const setSystemState = useStatusStore((s) => s.setSystemState);
1213
const zoneName = useStatusStore((s) => s.status.zones[zoneId].name);
1314
const volume = useStatusStore((s) => s.status.zones[zoneId].vol_f);
1415
const mute = useStatusStore((s) => s.status.zones[zoneId].mute);
@@ -46,7 +47,7 @@ const ZoneVolumeSlider = ({ zoneId, alone }) => {
4647
};
4748

4849
return (
49-
<div className={`zone-volume-container ${alone ? "alone" : "grouped"}`}>
50+
<div className="zone-volume-container grouped">
5051
{zoneName}
5152
<VolumeSlider
5253
mute={mute}
@@ -59,10 +60,6 @@ const ZoneVolumeSlider = ({ zoneId, alone }) => {
5960
};
6061
ZoneVolumeSlider.propTypes = {
6162
zoneId: PropTypes.number.isRequired,
62-
alone: PropTypes.bool,
6363
};
64-
ZoneVolumeSlider.defaultProps = {
65-
alone: false,
66-
}
6764

6865
export default ZoneVolumeSlider;

web/src/components/ZoneVolumeSlider/ZoneVolumeSlider.scss

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,6 @@
55
font-size: 1rem;
66
}
77

8-
.alone {
9-
padding-right: 8px;
10-
}
11-
128
.grouped {
139
// 47px is the width of the dropdown icon + padding, this causes the child volume sliders to end in the same spot as the parent
1410
padding-right: 47px;

web/src/pages/Player/Player.jsx

Lines changed: 34 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,10 @@ import { useState } from "react";
1212
import VolumeZones from "@/components/VolumeZones/VolumeZones";
1313
import Card from "@/components/Card/Card";
1414
import StreamsModal from "@/components/StreamsModal/StreamsModal";
15+
import ZonesModal from "@/components/ZonesModal/ZonesModal";
1516
import { getSourceInputType } from "@/utils/getSourceInputType";
1617
import Chip from "@/components/Chip/Chip";
17-
import Grid from "@mui/material/Grid/Grid"
18+
import Grid from "@mui/material/Grid/Grid";
1819
import selectActiveSource from "@/utils/selectActiveSource";
1920
import Box from "@mui/material/Box/Box";
2021

@@ -24,8 +25,9 @@ import { getFittestRep } from "@/utils/GroupZoneFiltering";
2425

2526
const Player = () => {
2627
const [streamsModalOpen, setStreamsModalOpen] = React.useState(false);
28+
const [zonesModalOpen, setZonesModalOpen] = React.useState(false);
2729
const selectedSourceId = usePersistentStore((s) => s.selectedSource);
28-
// TODO: dont index into sources. id isn't guarenteed to line up with order
30+
// TODO: Don't index into sources. id isn't guaranteed to line up with order
2931
const img_url = useStatusStore(
3032
(s) => s.status.sources[selectedSourceId].info.img_url
3133
);
@@ -62,6 +64,12 @@ const Player = () => {
6264
// This is a bootleg XOR statement, only works if there is exactly one zone or exactly one group, no more than that and not both
6365
const alone = ((usedGroups.length == 1) || (zonesLeft.length == 1)) && !((usedGroups.length > 0) && (zonesLeft.length > 0));
6466

67+
React.useEffect(() => {
68+
if(zonesLeft.length == 0 && usedGroups.length == 0){
69+
setExpanded(false);
70+
}
71+
}, [zonesLeft.length, usedGroups.length]); // Automatically unexpand when no zones or groups are connected
72+
6573
selectActiveSource();
6674

6775
function DropdownArrow() {
@@ -85,11 +93,19 @@ const Player = () => {
8593
onClose={() => setStreamsModalOpen(false)}
8694
/>
8795
)}
96+
97+
{zonesModalOpen && (
98+
<ZonesModal
99+
sourceId={selectedSourceId}
100+
setZoneModalOpen={setZonesModalOpen}
101+
onClose={() => setZonesModalOpen(false)}
102+
/>
103+
)}
88104
<Grid
89-
container
90-
direction="column"
91-
justifyContent="center"
92-
alignItems="center"
105+
container
106+
direction="column"
107+
justifyContent="center"
108+
alignItems="center"
93109
>
94110
<Grid item xs={2} sm={4} md={4}>
95111
<div className="stream-title" >
@@ -102,9 +118,9 @@ const Player = () => {
102118
<Box
103119
className="album-art-container"
104120
sx={{
105-
display: 'flex',
106-
justifyContent: 'center',
107-
alignItems: 'center',
121+
display: "flex",
122+
justifyContent: "center",
123+
alignItems: "center",
108124
}}
109125
>
110126
<img src={img_url} className="player-album-art" />
@@ -116,26 +132,21 @@ const Player = () => {
116132
<div className={alone ? "solo-media-controls" : "grouped-media-controls" } >
117133
<MediaControl selectedSource={selectedSourceId} />
118134
</div>
119-
{ (!is_streamer && zones.length > 0) ? (
120-
(alone) ? (
121-
<div className="player-volume-container" >
122-
<VolumeZones alone open sourceId={selectedSourceId} zones={zonesLeft} groups={usedGroups} groupsLeft={groupsLeft} />
123-
</div>
124-
) : (
125-
<Card className="player-volume-container">
135+
{ !is_streamer && (
136+
<Card className="player-volume-container">
137+
{ (zones.length > 0) && (
126138
<div className="player-volume-header">
127139
<CardVolumeSlider sourceId={selectedSourceId} />
128140
<IconButton onClick={() => setExpanded(!expanded)}>
129141
<DropdownArrow />
130142
</IconButton>
131143
</div>
132-
133-
<div className={`player-volume-body pill-scrollbar ${expanded ? "expanded-volume-body" : ""}`}>
134-
<VolumeZones open={(expanded)} sourceId={selectedSourceId} zones={zonesLeft} groups={usedGroups} groupsLeft={groupsLeft} />
135-
</div>
136-
</Card>
137-
)
138-
) : null }
144+
)}
145+
<div className={`player-volume-body ${(expanded) && "expanded-volume-body pill-scrollbar scrollable"}`}>
146+
<VolumeZones setZonesModalOpen={setZonesModalOpen} open={(expanded)} sourceId={selectedSourceId} zones={zonesLeft} groups={usedGroups} groupsLeft={groupsLeft} />
147+
</div>
148+
</Card>
149+
) }
139150
</div>
140151
);
141152
};

web/src/pages/Player/Player.scss

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,11 @@
8484
flex-direction: column;
8585
@media (general.$is-portrait) {
8686
max-height: calc(85vh - 120px);
87+
}
88+
}
89+
90+
.scrollable{ // Was part of .player-volume-body until it started adding scrollbars when only the [ + ] was in that div
91+
@media (general.$is-portrait) {
8792
overflow-y: auto;
8893
}
8994
}

0 commit comments

Comments
 (0)