Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
# AmpliPi Software Releases

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


# 0.4.11
Expand Down
4 changes: 2 additions & 2 deletions web/src/components/RectangularButton/RectangularButton.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ export default function RectangularButton({onClick, large, label}){
>
{label}
</div>
)
);
}
RectangularButton.propTypes = {
onClick: PropTypes.func.isRequired,
Expand All @@ -20,4 +20,4 @@ RectangularButton.propTypes = {
};
RectangularButton.defaultProps = {
large: false,
}
};
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
@use "src/general";

.rectangular-button {
// @include general.low-shadow;
@include general.low-shadow;
@include general.regular-font;
width: 100%;
height: 4rem;
Expand Down
38 changes: 22 additions & 16 deletions web/src/components/VolumeZones/VolumeZones.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,16 @@ import "./VolumesZones.scss";
import ZoneVolumeSlider from "../ZoneVolumeSlider/ZoneVolumeSlider";
import GroupVolumeSlider from "../GroupVolumeSlider/GroupVolumeSlider";
import Card from "../Card/Card";
import RectangularButton from "@/components/RectangularButton/RectangularButton";

import PropTypes from "prop-types";

const VolumeZones = ({ sourceId, open, zones, groups, groupsLeft, alone }) => {
const VolumeZones = ({ sourceId, open, zones, groups, groupsLeft, setZonesModalOpen }) => {
const groupVolumeSliders = [];
for (const group of groups) {
groupVolumeSliders.push(
<Card secondary={!alone} className={`group-vol-card ${!alone ? "vol-margin" : ""}`} key={group.id}>
<Card secondary className="group-vol-card vol-margin" key={group.id}>
<GroupVolumeSlider
alone={alone}
groupId={group.id}
sourceId={sourceId}
groupsLeft={groupsLeft}
Expand All @@ -24,19 +24,28 @@ const VolumeZones = ({ sourceId, open, zones, groups, groupsLeft, alone }) => {
const zoneVolumeSliders = [];
zones.forEach((zone) => {
zoneVolumeSliders.push(
<Card secondary={!alone} className={`zone-vol-card ${!alone ? "vol-margin" : ""}`} key={zone.id}>
<ZoneVolumeSlider alone={alone} zoneId={zone.id} />
<Card secondary className="zone-vol-card vol-margin" key={zone.id}>
<ZoneVolumeSlider zoneId={zone.id} />
</Card>
);
});

if(open){
return (
<div className="volume-sliders-container">
{groupVolumeSliders}
{zoneVolumeSliders}
</div>
);
const noZones = zoneVolumeSliders.length == 0 && groupVolumeSliders.length == 0;

if (open) {
return (
<div className={`volume-sliders-container ${(!noZones) && "add-padding"}`}>
{groupVolumeSliders}
{zoneVolumeSliders}
<RectangularButton large label="+" onClick={() => {setZonesModalOpen(true);}} />
</div>
);
} else if (noZones){
return(
<div className="volume-sliders-container">
<RectangularButton large label="+" onClick={() => {setZonesModalOpen(true);}} />
</div>
);
}
};
VolumeZones.propTypes = {
Expand All @@ -45,10 +54,7 @@ VolumeZones.propTypes = {
zones: PropTypes.array.isRequired,
groups: PropTypes.array.isRequired,
groupsLeft: PropTypes.array.isRequired,
alone: PropTypes.bool,
setZonesModalOpen: PropTypes.func.isRequired,
};
VolumeZones.defaultProps = {
alone: false,
}

export default VolumeZones;
4 changes: 4 additions & 0 deletions web/src/components/VolumeZones/VolumesZones.scss
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@
color: general.$controls-color;
}

.add-padding {
padding-bottom: 1rem;
}

.zone-vol-card {
@media (max-width: general.$small-mobile){
padding: 0.5rem;
Expand Down
9 changes: 3 additions & 6 deletions web/src/components/ZoneVolumeSlider/ZoneVolumeSlider.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ import PropTypes from "prop-types";
let sendingRequestCount = 0;

// Volume slider for individual zone in volume drawer
const ZoneVolumeSlider = ({ zoneId, alone }) => {
const ZoneVolumeSlider = ({ zoneId }) => {
const setSystemState = useStatusStore((s) => s.setSystemState);
const zoneName = useStatusStore((s) => s.status.zones[zoneId].name);
const volume = useStatusStore((s) => s.status.zones[zoneId].vol_f);
const mute = useStatusStore((s) => s.status.zones[zoneId].mute);
Expand Down Expand Up @@ -46,7 +47,7 @@ const ZoneVolumeSlider = ({ zoneId, alone }) => {
};

return (
<div className={`zone-volume-container ${alone ? "alone" : "grouped"}`}>
<div className="zone-volume-container grouped">
{zoneName}
<VolumeSlider
mute={mute}
Expand All @@ -59,10 +60,6 @@ const ZoneVolumeSlider = ({ zoneId, alone }) => {
};
ZoneVolumeSlider.propTypes = {
zoneId: PropTypes.number.isRequired,
alone: PropTypes.bool,
};
ZoneVolumeSlider.defaultProps = {
alone: false,
}

export default ZoneVolumeSlider;
4 changes: 0 additions & 4 deletions web/src/components/ZoneVolumeSlider/ZoneVolumeSlider.scss
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,6 @@
font-size: 1rem;
}

.alone {
padding-right: 8px;
}

.grouped {
// 47px is the width of the dropdown icon + padding, this causes the child volume sliders to end in the same spot as the parent
padding-right: 47px;
Expand Down
57 changes: 34 additions & 23 deletions web/src/pages/Player/Player.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,10 @@ import { useState } from "react";
import VolumeZones from "@/components/VolumeZones/VolumeZones";
import Card from "@/components/Card/Card";
import StreamsModal from "@/components/StreamsModal/StreamsModal";
import ZonesModal from "@/components/ZonesModal/ZonesModal";
import { getSourceInputType } from "@/utils/getSourceInputType";
import Chip from "@/components/Chip/Chip";
import Grid from "@mui/material/Grid/Grid"
import Grid from "@mui/material/Grid/Grid";
import selectActiveSource from "@/utils/selectActiveSource";
import Box from "@mui/material/Box/Box";

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

const Player = () => {
const [streamsModalOpen, setStreamsModalOpen] = React.useState(false);
const [zonesModalOpen, setZonesModalOpen] = React.useState(false);
const selectedSourceId = usePersistentStore((s) => s.selectedSource);
// TODO: dont index into sources. id isn't guarenteed to line up with order
// TODO: Don't index into sources. id isn't guaranteed to line up with order
const img_url = useStatusStore(
(s) => s.status.sources[selectedSourceId].info.img_url
);
Expand Down Expand Up @@ -62,6 +64,12 @@ const Player = () => {
// 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
const alone = ((usedGroups.length == 1) || (zonesLeft.length == 1)) && !((usedGroups.length > 0) && (zonesLeft.length > 0));

React.useEffect(() => {
if(zonesLeft.length == 0 && usedGroups.length == 0){
setExpanded(false);
}
}, [zonesLeft.length, usedGroups.length]); // Automatically unexpand when no zones or groups are connected

selectActiveSource();

function DropdownArrow() {
Expand All @@ -85,11 +93,19 @@ const Player = () => {
onClose={() => setStreamsModalOpen(false)}
/>
)}

{zonesModalOpen && (
<ZonesModal
sourceId={selectedSourceId}
setZoneModalOpen={setZonesModalOpen}
onClose={() => setZonesModalOpen(false)}
/>
)}
<Grid
container
direction="column"
justifyContent="center"
alignItems="center"
container
direction="column"
justifyContent="center"
alignItems="center"
>
<Grid item xs={2} sm={4} md={4}>
<div className="stream-title" >
Expand All @@ -102,9 +118,9 @@ const Player = () => {
<Box
className="album-art-container"
sx={{
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
display: "flex",
justifyContent: "center",
alignItems: "center",
}}
>
<img src={img_url} className="player-album-art" />
Expand All @@ -116,26 +132,21 @@ const Player = () => {
<div className={alone ? "solo-media-controls" : "grouped-media-controls" } >
<MediaControl selectedSource={selectedSourceId} />
</div>
{ (!is_streamer && zones.length > 0) ? (
(alone) ? (
<div className="player-volume-container" >
<VolumeZones alone open sourceId={selectedSourceId} zones={zonesLeft} groups={usedGroups} groupsLeft={groupsLeft} />
</div>
) : (
<Card className="player-volume-container">
{ !is_streamer && (
<Card className="player-volume-container">
{ (zones.length > 0) && (
<div className="player-volume-header">
<CardVolumeSlider sourceId={selectedSourceId} />
<IconButton onClick={() => setExpanded(!expanded)}>
<DropdownArrow />
</IconButton>
</div>

<div className={`player-volume-body pill-scrollbar ${expanded ? "expanded-volume-body" : ""}`}>
<VolumeZones open={(expanded)} sourceId={selectedSourceId} zones={zonesLeft} groups={usedGroups} groupsLeft={groupsLeft} />
</div>
</Card>
)
) : null }
)}
<div className={`player-volume-body ${(expanded) && "expanded-volume-body pill-scrollbar scrollable"}`}>
<VolumeZones setZonesModalOpen={setZonesModalOpen} open={(expanded)} sourceId={selectedSourceId} zones={zonesLeft} groups={usedGroups} groupsLeft={groupsLeft} />
</div>
</Card>
) }
</div>
);
};
Expand Down
5 changes: 5 additions & 0 deletions web/src/pages/Player/Player.scss
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,11 @@
flex-direction: column;
@media (general.$is-portrait) {
max-height: calc(85vh - 120px);
}
}

.scrollable{ // Was part of .player-volume-body until it started adding scrollbars when only the [ + ] was in that div
@media (general.$is-portrait) {
overflow-y: auto;
}
}
Expand Down
Loading