From 721bca9c6826e3ef780adc923a33838c8928ddae Mon Sep 17 00:00:00 2001 From: Jonah Shader Date: Mon, 17 Apr 2023 17:36:07 -0400 Subject: [PATCH 01/10] Add presets. Can only load them for now. --- .../components/PresetsModal/PresetsModal.jsx | 95 +++++++++++++++++++ .../components/PresetsModal/PresetsModal.scss | 62 ++++++++++++ .../components/StreamsModal/StreamsModal.jsx | 1 - web2/src/general.scss | 1 + web2/src/pages/Home/Home.jsx | 18 +++- web2/vite.config.js | 5 +- 6 files changed, 175 insertions(+), 7 deletions(-) create mode 100644 web2/src/components/PresetsModal/PresetsModal.jsx create mode 100644 web2/src/components/PresetsModal/PresetsModal.scss diff --git a/web2/src/components/PresetsModal/PresetsModal.jsx b/web2/src/components/PresetsModal/PresetsModal.jsx new file mode 100644 index 000000000..406550542 --- /dev/null +++ b/web2/src/components/PresetsModal/PresetsModal.jsx @@ -0,0 +1,95 @@ +import './PresetsModal.scss' +import Modal from '@/components/Modal/Modal' +import Card from '@/components/Card/Card' +import { useStatusStore } from '@/App' +import { useState } from 'react' + +const timeSince = (timeStamp) => { + var now = new Date(), + secondsPast = (now.getTime() - timeStamp) / 1000; + if (secondsPast < 60) { + return parseInt(secondsPast) + 's'; + } + if (secondsPast < 3600) { + return parseInt(secondsPast / 60) + 'm'; + } + if (secondsPast <= 86400) { + return parseInt(secondsPast / 3600) + 'h'; + } + if (secondsPast > 86400) { + day = timeStamp.getDate(); + month = timeStamp.toDateString().match(/ [a-zA-Z]*/)[0].replace(" ", ""); + year = timeStamp.getFullYear() == now.getFullYear() ? "" : " " + timeStamp.getFullYear(); + return day + " " + month + year; + } +} + +const PresetItem = ({ index, onClick, presetState }) => { + const preset = useStatusStore((state) => state.status.presets[index]) + const name = preset.name + const last_used = timeSince(new Date(preset.last_used * 1000)) // js expects milliseconds from epoch + // const last_used = 0 + + // show checkmark if presetState is 'done' + // show spinner if presetState is 'loading' + return ( +
onClick(index)}> +
+
{name}
+
{last_used}
+
+ + {presetState === 'done' &&
} + {presetState === 'loading' &&
} +
+ ) +} + + +const PresetsModal = ({ onClose }) => { + const presets = useStatusStore((state) => state.status.presets) + const [presetStates, setPresetStates] = useState(presets.map((preset) => false)) + + // resize presetStates (without overriding) if length changes + if (presetStates.length > presets.length) { + setPresetStates(presetStates.slice(0, presets.length)) + } else if (presetStates.length < presets.length) { + setPresetStates([...presetStates, ...Array(presets.length - presetStates.length).fill(false)]) + } + + const apply = (index) => { + setPresetStates(presetStates.map((state, i) => (i === index ? 'loading' : state))) + const id = presets[index].id + console.log(`/api/presets/${id}/load`) + fetch(`/api/presets/${id}/load`, { method: 'POST', accept: 'application/json' }) + .then(() => setPresetStates(presetStates.map((state, i) => (i === index ? 'done' : state)))) + .catch(() => setPresetStates(presetStates.map((state, i) => (i === index ? false : state)))) + } + + const presetItems = presets.map((preset, index) => ( + + )) + + return ( + + +
+ Select Preset +
+
+ {presetItems} +
+
+
+ ) +} + +export default PresetsModal + + + diff --git a/web2/src/components/PresetsModal/PresetsModal.scss b/web2/src/components/PresetsModal/PresetsModal.scss new file mode 100644 index 000000000..68f70a698 --- /dev/null +++ b/web2/src/components/PresetsModal/PresetsModal.scss @@ -0,0 +1,62 @@ +@use "src/general.scss"; + +.presets-modal{ + @include general.header-font; + font-size: 1.5rem; + color: general.$text-color; +} + +.presets-modal-card{ + max-height: 75vh; + overflow: hidden; + display: flex; + flex-direction: column; +} + +.presets-modal-header { + margin: 0.5rem; + font-size: 3rem; + text-align: center; +} + +.presets-modal-body { + padding: 1rem; + overflow-y: scroll; + //the below is to push the scrollbar out of the card to hide it + padding-right: 16px; + width: 100%; + height: 100%; +} + +.presets-modal-list-item{ + margin-bottom: 1rem; +} + +.preset-item { + display: flex; + flex-direction: row; + justify-content: space-between; + // align-items: center; + // padding: 0.5rem; + // border: 1px solid #000000; + // border-radius: 5px; + // margin-bottom: 0.5rem; +} + +.preset-name-and-last-used { + display: flex; + flex-direction: row; + justify-content: flex-start; +} + +.preset-item-last-used { + // just want to make the font smaller + color: general.$select-color-gray; + font-size: 1.25rem; + padding-left: 8px; +} + +.preset-item-icon { + font-size: 1.5rem; + padding-right: 2rem; +} \ No newline at end of file diff --git a/web2/src/components/StreamsModal/StreamsModal.jsx b/web2/src/components/StreamsModal/StreamsModal.jsx index 1622145d0..c47c73d75 100644 --- a/web2/src/components/StreamsModal/StreamsModal.jsx +++ b/web2/src/components/StreamsModal/StreamsModal.jsx @@ -1,7 +1,6 @@ import "./StreamsModal.scss"; import Modal from "../Modal/Modal"; import Card from "../Card/Card"; -import StreamBadge from "../StreamBadge/StreamBadge"; import { useStatusStore } from "@/App"; //TODO: fix RCA behavior diff --git a/web2/src/general.scss b/web2/src/general.scss index e63588173..68972e6b4 100644 --- a/web2/src/general.scss +++ b/web2/src/general.scss @@ -12,6 +12,7 @@ $controls-color: #F2F2F2; $icon-deselected-color: #8D8D8D; $volume-slider-color: #0096FF; $text-color: #FFFFFF; +$select-color-gray: #BDBDBD; // for preset used_last $mute-color: #dc3545; // fonts diff --git a/web2/src/pages/Home/Home.jsx b/web2/src/pages/Home/Home.jsx index c747e4bed..8729773eb 100644 --- a/web2/src/pages/Home/Home.jsx +++ b/web2/src/pages/Home/Home.jsx @@ -3,6 +3,7 @@ import "./Home.scss"; import { useStatusStore } from '@/App.jsx' import ZonesModal from "@/components/ZonesModal/ZonesModal"; import StreamsModal from "@/components/StreamsModal/StreamsModal"; +import PresetsModal from "@/components/PresetsModal/PresetsModal"; import { useState } from "react"; export const getSourceZones = (source_id, zones) => { @@ -20,12 +21,20 @@ const Home = ({ selectedSource, setSelectedSource }) => { const clearSourceZones = useStatusStore((s)=>s.clearSourceZones) const [zonesModalOpen, setZonesModalOpen] = useState(false) const [streamsModalOpen, setStreamsModalOpen] = useState(false) - let playerCards = []; + const [presetsModalOpen, setPresetsModalOpen] = useState(false) + let cards = []; let nextAvailableSource = null; + // temp presets button + cards.push( +
setPresetsModalOpen(true)} key={-1}> + Presets +
+ ) + sources.forEach((source, i) => { if(source.input.toUpperCase() != "NONE" && source.input != "" && source.input != "local"){ - playerCards.push( + cards.push( { /> */} { - playerCards + cards } - {playerCards.length < sources.length && + {cards.length < sources.length &&
{initSource(nextAvailableSource)}}>+
} {zonesModalOpen && {setStreamsModalOpen(!o); setZonesModalOpen(o)}} onClose={()=>setZonesModalOpen(false)}/>} {streamsModalOpen && setStreamsModalOpen(o)} onClose={()=>setStreamsModalOpen(false)}/>} + {presetsModalOpen && setPresetsModalOpen(false)}/>} diff --git a/web2/vite.config.js b/web2/vite.config.js index eb0324d25..48f64fc16 100644 --- a/web2/vite.config.js +++ b/web2/vite.config.js @@ -6,8 +6,9 @@ import path from 'path' // set this to dev server url //TODO: find a way to do this from cli // const amplipiurl = "http://192.168.0.117" -const amplipiurl = "http://192.168.0.178" -// const amplipiurl = "http://192.168.0.195" +// const amplipiurl = "http://192.168.0.178" +// const amplipiurl = "http://fe80::4caf:b851:9a24:ffbc" +const amplipiurl = "http://192.168.0.195" // const amplipiurl = "http://localhost" // https://vitejs.dev/config/ From a57d9cd15b7eaa2f8e43b3d2010006ec8ff997a8 Mon Sep 17 00:00:00 2001 From: Jonah Shader Date: Wed, 19 Apr 2023 16:34:03 -0400 Subject: [PATCH 02/10] Preset creation WIP --- .../components/PresetsModal/PresetsModal.jsx | 6 +- web2/src/main.jsx | 4 + .../Settings/Groups/GroupModal/GroupModal.jsx | 4 +- web2/src/pages/Settings/Groups/Groups.jsx | 2 - .../CreatePresetModal/CreatePresetModal.jsx | 138 ++++++++++++++++++ .../CreatePresetModal/CreatePresetModal.scss | 8 + web2/src/pages/Settings/Presets/Presets.jsx | 50 +++++++ web2/src/pages/Settings/Presets/Presets.scss | 17 +++ web2/src/pages/Settings/Settings.jsx | 4 + web2/vite.config.js | 2 +- 10 files changed, 227 insertions(+), 8 deletions(-) create mode 100644 web2/src/pages/Settings/Presets/CreatePresetModal/CreatePresetModal.jsx create mode 100644 web2/src/pages/Settings/Presets/CreatePresetModal/CreatePresetModal.scss create mode 100644 web2/src/pages/Settings/Presets/Presets.jsx create mode 100644 web2/src/pages/Settings/Presets/Presets.scss diff --git a/web2/src/components/PresetsModal/PresetsModal.jsx b/web2/src/components/PresetsModal/PresetsModal.jsx index 406550542..f9cb75b4d 100644 --- a/web2/src/components/PresetsModal/PresetsModal.jsx +++ b/web2/src/components/PresetsModal/PresetsModal.jsx @@ -17,9 +17,9 @@ const timeSince = (timeStamp) => { return parseInt(secondsPast / 3600) + 'h'; } if (secondsPast > 86400) { - day = timeStamp.getDate(); - month = timeStamp.toDateString().match(/ [a-zA-Z]*/)[0].replace(" ", ""); - year = timeStamp.getFullYear() == now.getFullYear() ? "" : " " + timeStamp.getFullYear(); + let day = timeStamp.getDate(); + let month = timeStamp.toDateString().match(/ [a-zA-Z]*/)[0].replace(" ", ""); + let year = timeStamp.getFullYear() == now.getFullYear() ? "" : " " + timeStamp.getFullYear(); return day + " " + month + year; } } diff --git a/web2/src/main.jsx b/web2/src/main.jsx index 1989f8851..804a521ec 100644 --- a/web2/src/main.jsx +++ b/web2/src/main.jsx @@ -49,6 +49,10 @@ export const router = createHashRouter([ path: 'sessions', element: , }, + { + path: 'presets', + element: , + }, { path: 'config', element: , diff --git a/web2/src/pages/Settings/Groups/GroupModal/GroupModal.jsx b/web2/src/pages/Settings/Groups/GroupModal/GroupModal.jsx index 422a39738..7fb7d239e 100644 --- a/web2/src/pages/Settings/Groups/GroupModal/GroupModal.jsx +++ b/web2/src/pages/Settings/Groups/GroupModal/GroupModal.jsx @@ -34,8 +34,8 @@ const GroupModal = ({ group, zones, onClose, del, apply }) => { })}
- {if(del) del(); onClose()}}> - {apply(groupName, groupZones); onClose()}}> + {if(del) del(); onClose()}}> + {apply(groupName, groupZones); onClose()}}>
diff --git a/web2/src/pages/Settings/Groups/Groups.jsx b/web2/src/pages/Settings/Groups/Groups.jsx index ffdffa253..953d9c83e 100644 --- a/web2/src/pages/Settings/Groups/Groups.jsx +++ b/web2/src/pages/Settings/Groups/Groups.jsx @@ -8,8 +8,6 @@ import GroupModal from './GroupModal/GroupModal' const GroupListItem = ({ group, zones }) => { const [modalOpen, setModalOpen] = useState(false) - const [groupName, setGroupName] = useState(group.name) - const [groupZones, setGroupZones] = useState(group.zones) const editGroup = (name, zones) => { fetch('/api/groups/' + group.id, {method: "PATCH", headers: {"Content-type": "application/json"}, body: JSON.stringify({name: name, zones: zones})}) diff --git a/web2/src/pages/Settings/Presets/CreatePresetModal/CreatePresetModal.jsx b/web2/src/pages/Settings/Presets/CreatePresetModal/CreatePresetModal.jsx new file mode 100644 index 000000000..42b42aad6 --- /dev/null +++ b/web2/src/pages/Settings/Presets/CreatePresetModal/CreatePresetModal.jsx @@ -0,0 +1,138 @@ +import { useState } from "react" +import Modal from '@/components/Modal/Modal' +import Card from '@/components/Card/Card' +import DoneIcon from '@mui/icons-material/Done' +import './CreatePresetModal.scss' + +import List from '@mui/material/List'; +import ListSubheader from '@mui/material/ListSubheader'; +import ListItem from '@mui/material/ListItem'; +import ListItemButton from '@mui/material/ListItemButton'; +import ListItemIcon from '@mui/material/ListItemIcon'; +import ListItemText from '@mui/material/ListItemText'; +import Checkbox from '@mui/material/Checkbox'; +import IconButton from '@mui/material/IconButton'; +import Collapse from '@mui/material/Collapse'; +import StarBorder from '@mui/icons-material/StarBorder'; +import FormGroup from '@mui/material/FormGroup'; +import FormControlLabel from '@mui/material/FormControlLabel'; +import Box from '@mui/material/Box'; + +const isEmpty = (dict) => Object.keys(dict).length === 0 + +const DictToTree = ({dict, depth=0}) => { + if (isEmpty(dict)) return
+ console.log("DictToTree depth " + depth) + + // return
HELLOOOOOOOOOO
+ + let entries = [] + let i = 0 + for (const [key, value] of Object.entries(dict)) { + + entries.push( + <> + + } + /> + + + + ) + + i += 1 + } + + return entries + + return ( +
+ {Object.entries(dict).map((key, value) => { + + hi + } + /> + {/* */} + + })} +
+ ) + + return ( + + {/* } + /> + } + /> */} + {Object.entries(dict).map((key, value) => { + } + /> + })} + + + ) + +} + +const CreatePresetModal = ({ onClose }) => { + const [name, setName] = useState('name') + + const savePreset = () => { + console.log("savePreset") + } + + const testDict = { + 'All' : { + 'Stream1' : { + 'Stream' : {}, + 'Volume' : {}, + 'Zones' : {}, + }, + 'Stream2' : { + 'Stream' : {}, + 'Volume' : {}, + 'Zones' : {}, + }, + 'Stream3' : { + 'Stream' : {}, + 'Volume' : {}, + 'Zones' : {}, + }, + } + } + + return ( + + +
+
+ Create Preset +
+ setName(e.target.value)}/> +
+
+ {savePreset(); onClose()}}> +
+ + + + {/* + } label="Label" /> + */} +
+
+
+ ) +} + +export default CreatePresetModal \ No newline at end of file diff --git a/web2/src/pages/Settings/Presets/CreatePresetModal/CreatePresetModal.scss b/web2/src/pages/Settings/Presets/CreatePresetModal/CreatePresetModal.scss new file mode 100644 index 000000000..7b9705a98 --- /dev/null +++ b/web2/src/pages/Settings/Presets/CreatePresetModal/CreatePresetModal.scss @@ -0,0 +1,8 @@ +@use "src/general.scss"; + +.preset-name { + @include general.header-font; + color: general.$text-color; + font-size: 2rem; + margin: 0.5rem; +} \ No newline at end of file diff --git a/web2/src/pages/Settings/Presets/Presets.jsx b/web2/src/pages/Settings/Presets/Presets.jsx new file mode 100644 index 000000000..12c5c4a4f --- /dev/null +++ b/web2/src/pages/Settings/Presets/Presets.jsx @@ -0,0 +1,50 @@ +import PageHeader from "@/components/PageHeader/PageHeader"; +import "./Presets.scss"; +import { useStatusStore } from "@/App.jsx"; +import { useState } from "react"; +import { Fab } from "@mui/material"; +import AddIcon from "@mui/icons-material/Add"; +import CreatePresetModal from './CreatePresetModal/CreatePresetModal' + +const PresetListItem = ({ preset }) => { + const [editOpen, setEditOpen] = useState(false); + + return ( + <> +
setEditOpen(true)}> + {preset.name} +
+ {/* { editOpen && + setEditOpen(false)}/> + } */} + + ); +}; + +const Presets = ({ onClose }) => { + const [createModalOpen, setCreateModalOpen] = useState(false); + const presets = useStatusStore((s) => s.status.presets); + + const presetListItems = presets.map((preset) => { + return ; + }); + + return ( + <> + +
{presetListItems}
+
+ { + setCreateModalOpen(true); + }} + > + + +
+ {createModalOpen && setCreateModalOpen(false)}/>} + + ); +}; + +export default Presets; diff --git a/web2/src/pages/Settings/Presets/Presets.scss b/web2/src/pages/Settings/Presets/Presets.scss new file mode 100644 index 000000000..e08fea4d9 --- /dev/null +++ b/web2/src/pages/Settings/Presets/Presets.scss @@ -0,0 +1,17 @@ +@use "src/general.scss"; + +.presets-body { + @include general.header-font; + color: general.$text-color; + display: flex; + flex-direction: column; + height: 95%; + overflow-y: auto; +} + +.add-preset-button{ + position: fixed; + margin: 1rem; + right: 0; + bottom: 0; +} diff --git a/web2/src/pages/Settings/Settings.jsx b/web2/src/pages/Settings/Settings.jsx index f797a33fc..e098d1175 100644 --- a/web2/src/pages/Settings/Settings.jsx +++ b/web2/src/pages/Settings/Settings.jsx @@ -4,6 +4,7 @@ import Streams from './Streams/Streams' import Zones from './Zones/Zones' import Groups from './Groups/Groups' import Sessions from './Sessions/Sessions' +import Presets from './Presets/Presets' import Config from './Config/Config' import { useState } from "react"; import { router } from "@/main"; @@ -29,6 +30,8 @@ const Settings = ({ openPage='' }) => { return case "sessions": return + case "presets": + return case "config": return default: @@ -51,6 +54,7 @@ const Settings = ({ openPage='' }) => { router.navigate("/settings/zones")}/> router.navigate("/settings/groups")}/> router.navigate("/settings/sessions")}/> + router.navigate("/settings/presets")}/> router.navigate("/settings/config")}/> {window.location.href="http://"+window.location.hostname+':5001/update'}}/>
diff --git a/web2/vite.config.js b/web2/vite.config.js index 48f64fc16..37bef30d2 100644 --- a/web2/vite.config.js +++ b/web2/vite.config.js @@ -8,7 +8,7 @@ import path from 'path' // const amplipiurl = "http://192.168.0.117" // const amplipiurl = "http://192.168.0.178" // const amplipiurl = "http://fe80::4caf:b851:9a24:ffbc" -const amplipiurl = "http://192.168.0.195" +const amplipiurl = "http://192.168.0.198" // const amplipiurl = "http://localhost" // https://vitejs.dev/config/ From f28e5f3e0312ca282b01f6cb10a1e6edd6db8c9a Mon Sep 17 00:00:00 2001 From: Jonah Shader Date: Fri, 21 Apr 2023 17:59:21 -0400 Subject: [PATCH 03/10] Tree wip --- .../CreatePresetModal/CreatePresetModal.jsx | 174 +++++++++++++----- web2/vite.config.js | 2 +- 2 files changed, 125 insertions(+), 51 deletions(-) diff --git a/web2/src/pages/Settings/Presets/CreatePresetModal/CreatePresetModal.jsx b/web2/src/pages/Settings/Presets/CreatePresetModal/CreatePresetModal.jsx index 42b42aad6..1d62aca1d 100644 --- a/web2/src/pages/Settings/Presets/CreatePresetModal/CreatePresetModal.jsx +++ b/web2/src/pages/Settings/Presets/CreatePresetModal/CreatePresetModal.jsx @@ -1,31 +1,45 @@ import { useState } from "react" +import { useStatusStore } from "@/App" import Modal from '@/components/Modal/Modal' import Card from '@/components/Card/Card' import DoneIcon from '@mui/icons-material/Done' import './CreatePresetModal.scss' -import List from '@mui/material/List'; -import ListSubheader from '@mui/material/ListSubheader'; -import ListItem from '@mui/material/ListItem'; -import ListItemButton from '@mui/material/ListItemButton'; -import ListItemIcon from '@mui/material/ListItemIcon'; -import ListItemText from '@mui/material/ListItemText'; import Checkbox from '@mui/material/Checkbox'; import IconButton from '@mui/material/IconButton'; -import Collapse from '@mui/material/Collapse'; -import StarBorder from '@mui/icons-material/StarBorder'; -import FormGroup from '@mui/material/FormGroup'; import FormControlLabel from '@mui/material/FormControlLabel'; import Box from '@mui/material/Box'; +const baseDict = (open, name, content) => { + return { + 'open' : open, + 'checked' : false, + 'name' : name, + 'content' : content + } +} + +const dictWithOptions = (name) => { + return baseDict(false, name, [ + baseDict(false, 'Stream', []), + baseDict(false, 'Volume', []), + baseDict(false, 'Zones', []), + ]) +} + +const buildTreeDict = (status) => { + const statusClone = JSON.parse(JSON.stringify(status)) + + const sourceDicts = statusClone.sources.filter(source => source.info.state !== 'stopped').map(source => dictWithOptions(source.info.name)); + + const top = baseDict(false, "All", sourceDicts) + return top; +} + const isEmpty = (dict) => Object.keys(dict).length === 0 const DictToTree = ({dict, depth=0}) => { if (isEmpty(dict)) return
- console.log("DictToTree depth " + depth) - - // return
HELLOOOOOOOOOO
- let entries = [] let i = 0 for (const [key, value] of Object.entries(dict)) { @@ -35,7 +49,7 @@ const DictToTree = ({dict, depth=0}) => { } + control={} /> @@ -46,47 +60,106 @@ const DictToTree = ({dict, depth=0}) => { } return entries +} - return ( -
- {Object.entries(dict).map((key, value) => { - - hi - } - /> - {/* */} - - })} -
- ) +const Structured2Child = ({checked, setChecked, level, setLevel, index}) => { + const myChecked = checked[index] + const myLevel = level[index] + +} + +const getTotalCheckboxes = (d) => d.content.reduce((partialSum, d2) => partialSum + getTotalCheckboxes(d2), 1) + +const Structured2Top = ({dict}) => { + // this function uses a flat representation of the tree structure. + // each child will get the state getters/setters along with their index within the state + const totalCheckboxes = getTotalCheckboxes(dict) + const [checked, setChecked] = useState(Array(totalCheckboxes).fill('unchecked')) + const [level, setLevel] = useState(Array(totalCheckboxes).fill(0)) +} + +const StructuredDictAsTree = ({dict, depth=0, checkedInit=false, passInSetCheckedRecur=null, passInGetCheckedRecur=null, refreshTop=null}) => { + const [checked, setChecked] = useState(checkedInit) + + let childSetChecked = [] + let childGetChecked = [] + + const setParentChildSetChecked = (setter) => { + childSetChecked.push(setter) + } + const setParentChildGetChecked = (getter) => { + childGetChecked.push(getter) + } + + const calculateInd = () => { + if (childGetChecked.length > 0) { + let s = childGetChecked.reduce((acc, curr) => curr() === 'ind' ? 'ind' : (acc === curr() ? acc : 'ind'), childGetChecked[0]()) + // TODO: can i do setChecked here? + setChecked(s) + return s + } + + return checked + } + + if (refreshTop === null) { + refreshTop = () => { + if (childGetChecked !== null) { + calculateInd() + } + } + } + const handlePress = (e) => { + setChecked(e.target.checked ? 'checked' : 'unchecked') + if (childSetChecked !== null) { + childSetChecked.forEach(it => it(e.target.checked ? 'checked' : 'unchecked')) + // childSetChecked() + } + refreshTop() + } + + let entries = [] + let i = 0 + + for (const d of dict.content) { + entries.push( + + ) + i += 1 + } + + if (passInSetCheckedRecur !== null) { + passInSetCheckedRecur((s) => {setChecked(s); childSetChecked.forEach(it => it(s))}) + } + if (passInGetCheckedRecur !== null) { + if (childGetChecked.length > 0) { + passInGetCheckedRecur(calculateInd) + } else { + passInGetCheckedRecur(() => checked) // TODO: maybe this is being captured by value, might need to wrap in array + } + } + + return ( - - {/* } - /> - } - /> */} - {Object.entries(dict).map((key, value) => { - } - /> - })} - - + <> + + } + /> + {/* */} + {entries} + + ) - } const CreatePresetModal = ({ onClose }) => { const [name, setName] = useState('name') + const status = useStatusStore(s => s.status) + const savePreset = () => { console.log("savePreset") } @@ -110,6 +183,9 @@ const CreatePresetModal = ({ onClose }) => { }, } } + + const structuredDict = buildTreeDict(status) + return ( @@ -125,10 +201,8 @@ const CreatePresetModal = ({ onClose }) => {
- - {/* - } label="Label" /> - */} + {/* */} + diff --git a/web2/vite.config.js b/web2/vite.config.js index 37bef30d2..01d6d7f1f 100644 --- a/web2/vite.config.js +++ b/web2/vite.config.js @@ -8,7 +8,7 @@ import path from 'path' // const amplipiurl = "http://192.168.0.117" // const amplipiurl = "http://192.168.0.178" // const amplipiurl = "http://fe80::4caf:b851:9a24:ffbc" -const amplipiurl = "http://192.168.0.198" +const amplipiurl = "http://192.168.0.197" // const amplipiurl = "http://localhost" // https://vitejs.dev/config/ From c5248aafa038e8fb0d18ffba3fb5a6a1bd398cb7 Mon Sep 17 00:00:00 2001 From: Jonah Shader Date: Mon, 24 Apr 2023 12:20:57 -0400 Subject: [PATCH 04/10] Committing before flat rewrite --- .../CreatePresetModal/CreatePresetModal.jsx | 20 +++++++++++++------ web2/vite.config.js | 4 ++-- 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/web2/src/pages/Settings/Presets/CreatePresetModal/CreatePresetModal.jsx b/web2/src/pages/Settings/Presets/CreatePresetModal/CreatePresetModal.jsx index 1d62aca1d..8a09feb6a 100644 --- a/web2/src/pages/Settings/Presets/CreatePresetModal/CreatePresetModal.jsx +++ b/web2/src/pages/Settings/Presets/CreatePresetModal/CreatePresetModal.jsx @@ -1,4 +1,5 @@ import { useState } from "react" +import { useEffect } from "react" import { useStatusStore } from "@/App" import Modal from '@/components/Modal/Modal' import Card from '@/components/Card/Card' @@ -81,6 +82,10 @@ const Structured2Top = ({dict}) => { const StructuredDictAsTree = ({dict, depth=0, checkedInit=false, passInSetCheckedRecur=null, passInGetCheckedRecur=null, refreshTop=null}) => { const [checked, setChecked] = useState(checkedInit) + const setCheckedEffect = (s) => { + + } + let childSetChecked = [] let childGetChecked = [] @@ -96,6 +101,7 @@ const StructuredDictAsTree = ({dict, depth=0, checkedInit=false, passInSetChecke let s = childGetChecked.reduce((acc, curr) => curr() === 'ind' ? 'ind' : (acc === curr() ? acc : 'ind'), childGetChecked[0]()) // TODO: can i do setChecked here? setChecked(s) + return s } @@ -104,18 +110,20 @@ const StructuredDictAsTree = ({dict, depth=0, checkedInit=false, passInSetChecke if (refreshTop === null) { refreshTop = () => { - if (childGetChecked !== null) { - calculateInd() - } + calculateInd() } } const handlePress = (e) => { - setChecked(e.target.checked ? 'checked' : 'unchecked') + const newChecked = e.target.checked ? 'checked' : 'unchecked' + if (childSetChecked !== null) { - childSetChecked.forEach(it => it(e.target.checked ? 'checked' : 'unchecked')) + + // childSetChecked.forEach(it => it(e.target.checked ? 'checked' : 'unchecked')) + childSetChecked.forEach(it => it(newChecked)) // childSetChecked() } + setChecked(e.target.checked ? 'checked' : 'unchecked') refreshTop() } @@ -146,7 +154,7 @@ const StructuredDictAsTree = ({dict, depth=0, checkedInit=false, passInSetChecke } + control={} /> {/* */} {entries} diff --git a/web2/vite.config.js b/web2/vite.config.js index 01d6d7f1f..ffa0777f0 100644 --- a/web2/vite.config.js +++ b/web2/vite.config.js @@ -6,9 +6,9 @@ import path from 'path' // set this to dev server url //TODO: find a way to do this from cli // const amplipiurl = "http://192.168.0.117" -// const amplipiurl = "http://192.168.0.178" +const amplipiurl = "http://192.168.0.178" // const amplipiurl = "http://fe80::4caf:b851:9a24:ffbc" -const amplipiurl = "http://192.168.0.197" +// const amplipiurl = "http://192.168.0.198" // const amplipiurl = "http://localhost" // https://vitejs.dev/config/ From d8bb49ac99e59f7463ffe00b20a5c7fc03074545 Mon Sep 17 00:00:00 2001 From: Jonah Shader Date: Mon, 24 Apr 2023 14:20:10 -0400 Subject: [PATCH 05/10] Tree checking works --- .../CreatePresetModal/CreatePresetModal.jsx | 4 - .../CreatePresetModal/CreatePresetModal2.jsx | 159 ++++++++++++++++++ web2/src/pages/Settings/Presets/Presets.jsx | 2 +- 3 files changed, 160 insertions(+), 5 deletions(-) create mode 100644 web2/src/pages/Settings/Presets/CreatePresetModal/CreatePresetModal2.jsx diff --git a/web2/src/pages/Settings/Presets/CreatePresetModal/CreatePresetModal.jsx b/web2/src/pages/Settings/Presets/CreatePresetModal/CreatePresetModal.jsx index 8a09feb6a..2fee29137 100644 --- a/web2/src/pages/Settings/Presets/CreatePresetModal/CreatePresetModal.jsx +++ b/web2/src/pages/Settings/Presets/CreatePresetModal/CreatePresetModal.jsx @@ -82,10 +82,6 @@ const Structured2Top = ({dict}) => { const StructuredDictAsTree = ({dict, depth=0, checkedInit=false, passInSetCheckedRecur=null, passInGetCheckedRecur=null, refreshTop=null}) => { const [checked, setChecked] = useState(checkedInit) - const setCheckedEffect = (s) => { - - } - let childSetChecked = [] let childGetChecked = [] diff --git a/web2/src/pages/Settings/Presets/CreatePresetModal/CreatePresetModal2.jsx b/web2/src/pages/Settings/Presets/CreatePresetModal/CreatePresetModal2.jsx new file mode 100644 index 000000000..7aaed2fdf --- /dev/null +++ b/web2/src/pages/Settings/Presets/CreatePresetModal/CreatePresetModal2.jsx @@ -0,0 +1,159 @@ +import { create } from 'zustand' +import { useState, useEffect } from 'react' +import { useStatusStore } from "@/App" +import produce from 'immer' + +import Modal from '@/components/Modal/Modal' +import Card from '@/components/Card/Card' +import DoneIcon from '@mui/icons-material/Done' +import './CreatePresetModal.scss' + +import Checkbox from '@mui/material/Checkbox'; +import IconButton from '@mui/material/IconButton'; +import FormControlLabel from '@mui/material/FormControlLabel'; +import Box from '@mui/material/Box'; + +const setCheckedRecur = (dict, checked) => {dict.checked = checked; dict.content.forEach(it => setCheckedRecur(it, checked))} + +const computeCheckedStateRecur = (dict) => { + if (dict.content.length === 0) { + return dict.checked + } + + let c = computeCheckedStateRecur(dict.content[0]) + for (let i = 1; i < dict.content.length; i++) { + let curr = computeCheckedStateRecur(dict.content[i]) + if (c === 'ind' || curr === 'ind' || c !== curr) { + c = 'ind' + } + } + + // let newCheckedState = dict.content.reduce((acc, curr) => ( + // computeCheckedStateRecur(curr) === 'ind' ? 'ind' : // if ind, keep ind + // (acc === computeCheckedStateRecur(curr) ? acc : 'ind'), // if same, keep same. else ind + // computeCheckedStateRecur(dict.content[0])) // init value + // ) + dict.checked = c + return c +} + +const useTreeStore = create((set) => ({ + tree: null, + setTree: (newTree) => { + set({tree: newTree}) + }, + setChecked: (path, checked) => { + set(produce((s) => { + // navigate to desired tree + console.log("logging path.....") + console.log(path) + console.log("path logged bruh") + let curr = s.tree + for (const p of path) { + console.log(curr.content.length) + curr = curr.content[p] + } + // set checked on tree and all children recursively + setCheckedRecur(curr, checked) + + // recompute checked state + computeCheckedStateRecur(s.tree) + })) + } +})) + +const baseDict = (open, name, content) => { + return { + 'open' : open, + 'checked' : false, + 'name' : name, + 'content' : content + } +} + +const dictWithOptions = (name) => { + return baseDict(false, name, [ + baseDict(false, 'Stream', []), + baseDict(false, 'Volume', []), + baseDict(false, 'Zones', []), + ]) +} + +const buildTreeDict = (status) => { + const statusClone = JSON.parse(JSON.stringify(status)) + const sourceDicts = statusClone.sources.filter(source => source.info.state !== 'stopped').map(source => dictWithOptions(source.info.name)); + const top = baseDict(false, "All", sourceDicts) + return top; +} + +const StructuredDictAsTree = ({dict, depth=0, path=[]}) => { + const setChecked = useTreeStore((s) => s.setChecked) + const checked = dict.checked + + const handlePress = (e) => { + setChecked(path, e.target.checked ? 'checked' : 'unchecked') + } + + let entries = [] + let i = 0 + for (const d of dict.content) { + entries.push( + + ) + i += 1 + } + + return ( + <> + + } + /> + {entries} + + + ) +} + +const CreatePresetModal = ({ onClose }) => { + const [name, setName] = useState('name') + const status = useStatusStore(s => s.status) + const setTree = useTreeStore(s => s.setTree) + const tree = useTreeStore(s => s.tree) + + console.log("setting tree...") + useEffect(() => { + const newTree = buildTreeDict(status) + console.log("new tree") + console.log(newTree) + setTree(newTree) + }, []) + + + const savePreset = () => { + console.log("savePreset") + } + + if (tree === null) return
+ + return ( + + +
+
+ Create Preset +
+ setName(e.target.value)}/> +
+
+ {savePreset(); onClose()}}> +
+ +
+
+
+ ) +} + +export default CreatePresetModal \ No newline at end of file diff --git a/web2/src/pages/Settings/Presets/Presets.jsx b/web2/src/pages/Settings/Presets/Presets.jsx index 12c5c4a4f..54b128a7f 100644 --- a/web2/src/pages/Settings/Presets/Presets.jsx +++ b/web2/src/pages/Settings/Presets/Presets.jsx @@ -4,7 +4,7 @@ import { useStatusStore } from "@/App.jsx"; import { useState } from "react"; import { Fab } from "@mui/material"; import AddIcon from "@mui/icons-material/Add"; -import CreatePresetModal from './CreatePresetModal/CreatePresetModal' +import CreatePresetModal from './CreatePresetModal/CreatePresetModal2' const PresetListItem = ({ preset }) => { const [editOpen, setEditOpen] = useState(false); From 27be0137ffd87c3cc8ad2c4909f4d3426aeb2fa8 Mon Sep 17 00:00:00 2001 From: Jonah Shader Date: Mon, 24 Apr 2023 17:11:24 -0400 Subject: [PATCH 06/10] Tree now contains payload; state subset --- .../CreatePresetModal/CreatePresetModal.jsx | 260 ++++++++---------- .../CreatePresetModal/CreatePresetModal2.jsx | 159 ----------- web2/src/pages/Settings/Presets/Presets.jsx | 2 +- 3 files changed, 122 insertions(+), 299 deletions(-) delete mode 100644 web2/src/pages/Settings/Presets/CreatePresetModal/CreatePresetModal2.jsx diff --git a/web2/src/pages/Settings/Presets/CreatePresetModal/CreatePresetModal.jsx b/web2/src/pages/Settings/Presets/CreatePresetModal/CreatePresetModal.jsx index 2fee29137..beb8b58e2 100644 --- a/web2/src/pages/Settings/Presets/CreatePresetModal/CreatePresetModal.jsx +++ b/web2/src/pages/Settings/Presets/CreatePresetModal/CreatePresetModal.jsx @@ -1,196 +1,177 @@ -import { useState } from "react" -import { useEffect } from "react" +import { create } from 'zustand' +import { useState, useEffect } from 'react' import { useStatusStore } from "@/App" +import produce from 'immer' + import Modal from '@/components/Modal/Modal' import Card from '@/components/Card/Card' import DoneIcon from '@mui/icons-material/Done' import './CreatePresetModal.scss' import Checkbox from '@mui/material/Checkbox'; +import Switch from '@mui/material/Switch' import IconButton from '@mui/material/IconButton'; import FormControlLabel from '@mui/material/FormControlLabel'; import Box from '@mui/material/Box'; -const baseDict = (open, name, content) => { - return { - 'open' : open, - 'checked' : false, - 'name' : name, - 'content' : content - } -} - -const dictWithOptions = (name) => { - return baseDict(false, name, [ - baseDict(false, 'Stream', []), - baseDict(false, 'Volume', []), - baseDict(false, 'Zones', []), - ]) -} - -const buildTreeDict = (status) => { - const statusClone = JSON.parse(JSON.stringify(status)) - - const sourceDicts = statusClone.sources.filter(source => source.info.state !== 'stopped').map(source => dictWithOptions(source.info.name)); - - const top = baseDict(false, "All", sourceDicts) - return top; +const setCheckedRecur = (dict, checked) => { + dict.checked = checked + dict.content.forEach(it => setCheckedRecur(it, checked)) } -const isEmpty = (dict) => Object.keys(dict).length === 0 - -const DictToTree = ({dict, depth=0}) => { - if (isEmpty(dict)) return
- let entries = [] - let i = 0 - for (const [key, value] of Object.entries(dict)) { - - entries.push( - <> - - } - /> - - - - ) - - i += 1 +const computeCheckedStateRecur = (dict) => { + if (dict.content.length === 0) { + return dict.checked } - return entries -} - -const Structured2Child = ({checked, setChecked, level, setLevel, index}) => { - const myChecked = checked[index] - const myLevel = level[index] - + let c = computeCheckedStateRecur(dict.content[0]) + for (let i = 1; i < dict.content.length; i++) { + let curr = computeCheckedStateRecur(dict.content[i]) + if (c === 'ind' || curr === 'ind' || c !== curr) { + c = 'ind' + } + } + if (dict.checked !== c) { + console.log(`check state changed from ${dict.checked} to ${c} for ${dict}.`) + } + dict.checked = c + return c } -const getTotalCheckboxes = (d) => d.content.reduce((partialSum, d2) => partialSum + getTotalCheckboxes(d2), 1) - -const Structured2Top = ({dict}) => { - // this function uses a flat representation of the tree structure. - // each child will get the state getters/setters along with their index within the state - const totalCheckboxes = getTotalCheckboxes(dict) - const [checked, setChecked] = useState(Array(totalCheckboxes).fill('unchecked')) - const [level, setLevel] = useState(Array(totalCheckboxes).fill(0)) +const useTreeStore = create((set) => ({ + tree: null, + setTree: (newTree) => { + set({tree: newTree}) + }, + setChecked: (path, checked) => { + set(produce((s) => { + // navigate to desired tree + let curr = s.tree + for (const p of path) { + curr = curr.content[p] + } + // set checked on tree and all children recursively + setCheckedRecur(curr, checked) + + // recompute checked state + computeCheckedStateRecur(s.tree) + })) + }, +})) + +const baseDict = (open, name, content, payload=null) => { + return { + 'open' : open, + 'checked' : 'unchecked', + 'name' : name, + 'content' : content, + 'payload' : payload, + } } -const StructuredDictAsTree = ({dict, depth=0, checkedInit=false, passInSetCheckedRecur=null, passInGetCheckedRecur=null, refreshTop=null}) => { - const [checked, setChecked] = useState(checkedInit) - let childSetChecked = [] - let childGetChecked = [] - const setParentChildSetChecked = (setter) => { - childSetChecked.push(setter) - } - const setParentChildGetChecked = (getter) => { - childGetChecked.push(getter) +const buildTreeDict = (status, showInactive=false) => { + const dictWithOptions = (source) => { + // grab zones that are playing this source + // only take id, source_id, mute, vol as the rest is derived (i think) TODO verify + // https://stackoverflow.com/questions/17781472/how-to-get-a-subset-of-a-javascript-objects-properties + const zones_payload = status.zones.filter(zone => zone.source_id === source.id).map(zone => (({ id, source_id, mute, vol }) => ({ id, source_id, mute, vol }))(zone)) + // only take id, source_id, mute, vol_delta + const groups_payload = status.groups.filter(group => group.source_id === source.id).map(group => (({ id, source_id, mute, vol_delta}) => ({ id, source_id, mute, vol_delta}))(group)) + const sources_payload = [(({ id, input }) => ({ id, input }))(source)] + const name = `S${source.id+1}: ${source.info.name}` + return baseDict(false, name, [ + baseDict(false, 'Stream', [], {'sources': sources_payload}), + baseDict(false, 'Volume', [], {'zones': zones_payload, 'groups': groups_payload}), + // baseDict(false, 'Zones', []), + ], {}) } - - const calculateInd = () => { - if (childGetChecked.length > 0) { - let s = childGetChecked.reduce((acc, curr) => curr() === 'ind' ? 'ind' : (acc === curr() ? acc : 'ind'), childGetChecked[0]()) - // TODO: can i do setChecked here? - setChecked(s) - - return s - } - return checked - } + const statusClone = JSON.parse(JSON.stringify(status)) + const sourceDicts = statusClone.sources + .filter(source => showInactive || source.info.state !== 'stopped') + .map(dictWithOptions) + const top = baseDict(false, "All", sourceDicts) + return top; +} - if (refreshTop === null) { - refreshTop = () => { - calculateInd() - } - } +const StructuredDictAsTree = ({dict, depth=0, path=[]}) => { + const setChecked = useTreeStore((s) => s.setChecked) + const checked = dict.checked const handlePress = (e) => { - const newChecked = e.target.checked ? 'checked' : 'unchecked' - - if (childSetChecked !== null) { - - // childSetChecked.forEach(it => it(e.target.checked ? 'checked' : 'unchecked')) - childSetChecked.forEach(it => it(newChecked)) - // childSetChecked() - } - setChecked(e.target.checked ? 'checked' : 'unchecked') - refreshTop() + setChecked(path, e.target.checked ? 'checked' : 'unchecked') } let entries = [] let i = 0 - for (const d of dict.content) { entries.push( - + ) i += 1 } - if (passInSetCheckedRecur !== null) { - passInSetCheckedRecur((s) => {setChecked(s); childSetChecked.forEach(it => it(s))}) - } - if (passInGetCheckedRecur !== null) { - if (childGetChecked.length > 0) { - passInGetCheckedRecur(calculateInd) - } else { - passInGetCheckedRecur(() => checked) // TODO: maybe this is being captured by value, might need to wrap in array - } - } - - return ( <> } + control={} /> - {/* */} {entries} ) } +const mergePayloads = (tree) => { + let zones_merged = [] + let groups_merged = [] + let sources_merged = [] + + const p = tree.payload + if (p !== null && tree.checked === 'checked') { + if (p.zones !== undefined) zones_merged.push(...p.zones) + if (p.groups !== undefined) groups_merged.push(...p.groups) + if (p.sources !== undefined) sources_merged.push(...p.sources) + } + + for (const subtree of tree.content) { + const to_merge = mergePayloads(subtree) + zones_merged.push(...to_merge.zones) + groups_merged.push(...to_merge.groups) + sources_merged.push(...to_merge.sources) + } + + return { + 'zones' : zones_merged, + 'groups' : groups_merged, + 'sources': sources_merged, + } +} + const CreatePresetModal = ({ onClose }) => { const [name, setName] = useState('name') - const status = useStatusStore(s => s.status) + const setTree = useTreeStore(s => s.setTree) + const tree = useTreeStore(s => s.tree) + + useEffect(() => { + const newTree = buildTreeDict(status) + setTree(newTree) + }, []) const savePreset = () => { console.log("savePreset") - } + console.log(mergePayloads(tree)) - const testDict = { - 'All' : { - 'Stream1' : { - 'Stream' : {}, - 'Volume' : {}, - 'Zones' : {}, - }, - 'Stream2' : { - 'Stream' : {}, - 'Volume' : {}, - 'Zones' : {}, - }, - 'Stream3' : { - 'Stream' : {}, - 'Volume' : {}, - 'Zones' : {}, - }, - } + // build } - const structuredDict = buildTreeDict(status) + if (tree === null) return
- return ( @@ -202,11 +183,12 @@ const CreatePresetModal = ({ onClose }) => {
{savePreset(); onClose()}}> + setTree(buildTreeDict(status, e.target.checked))}/>} + />
- - - {/* */} - +
diff --git a/web2/src/pages/Settings/Presets/CreatePresetModal/CreatePresetModal2.jsx b/web2/src/pages/Settings/Presets/CreatePresetModal/CreatePresetModal2.jsx deleted file mode 100644 index 7aaed2fdf..000000000 --- a/web2/src/pages/Settings/Presets/CreatePresetModal/CreatePresetModal2.jsx +++ /dev/null @@ -1,159 +0,0 @@ -import { create } from 'zustand' -import { useState, useEffect } from 'react' -import { useStatusStore } from "@/App" -import produce from 'immer' - -import Modal from '@/components/Modal/Modal' -import Card from '@/components/Card/Card' -import DoneIcon from '@mui/icons-material/Done' -import './CreatePresetModal.scss' - -import Checkbox from '@mui/material/Checkbox'; -import IconButton from '@mui/material/IconButton'; -import FormControlLabel from '@mui/material/FormControlLabel'; -import Box from '@mui/material/Box'; - -const setCheckedRecur = (dict, checked) => {dict.checked = checked; dict.content.forEach(it => setCheckedRecur(it, checked))} - -const computeCheckedStateRecur = (dict) => { - if (dict.content.length === 0) { - return dict.checked - } - - let c = computeCheckedStateRecur(dict.content[0]) - for (let i = 1; i < dict.content.length; i++) { - let curr = computeCheckedStateRecur(dict.content[i]) - if (c === 'ind' || curr === 'ind' || c !== curr) { - c = 'ind' - } - } - - // let newCheckedState = dict.content.reduce((acc, curr) => ( - // computeCheckedStateRecur(curr) === 'ind' ? 'ind' : // if ind, keep ind - // (acc === computeCheckedStateRecur(curr) ? acc : 'ind'), // if same, keep same. else ind - // computeCheckedStateRecur(dict.content[0])) // init value - // ) - dict.checked = c - return c -} - -const useTreeStore = create((set) => ({ - tree: null, - setTree: (newTree) => { - set({tree: newTree}) - }, - setChecked: (path, checked) => { - set(produce((s) => { - // navigate to desired tree - console.log("logging path.....") - console.log(path) - console.log("path logged bruh") - let curr = s.tree - for (const p of path) { - console.log(curr.content.length) - curr = curr.content[p] - } - // set checked on tree and all children recursively - setCheckedRecur(curr, checked) - - // recompute checked state - computeCheckedStateRecur(s.tree) - })) - } -})) - -const baseDict = (open, name, content) => { - return { - 'open' : open, - 'checked' : false, - 'name' : name, - 'content' : content - } -} - -const dictWithOptions = (name) => { - return baseDict(false, name, [ - baseDict(false, 'Stream', []), - baseDict(false, 'Volume', []), - baseDict(false, 'Zones', []), - ]) -} - -const buildTreeDict = (status) => { - const statusClone = JSON.parse(JSON.stringify(status)) - const sourceDicts = statusClone.sources.filter(source => source.info.state !== 'stopped').map(source => dictWithOptions(source.info.name)); - const top = baseDict(false, "All", sourceDicts) - return top; -} - -const StructuredDictAsTree = ({dict, depth=0, path=[]}) => { - const setChecked = useTreeStore((s) => s.setChecked) - const checked = dict.checked - - const handlePress = (e) => { - setChecked(path, e.target.checked ? 'checked' : 'unchecked') - } - - let entries = [] - let i = 0 - for (const d of dict.content) { - entries.push( - - ) - i += 1 - } - - return ( - <> - - } - /> - {entries} - - - ) -} - -const CreatePresetModal = ({ onClose }) => { - const [name, setName] = useState('name') - const status = useStatusStore(s => s.status) - const setTree = useTreeStore(s => s.setTree) - const tree = useTreeStore(s => s.tree) - - console.log("setting tree...") - useEffect(() => { - const newTree = buildTreeDict(status) - console.log("new tree") - console.log(newTree) - setTree(newTree) - }, []) - - - const savePreset = () => { - console.log("savePreset") - } - - if (tree === null) return
- - return ( - - -
-
- Create Preset -
- setName(e.target.value)}/> -
-
- {savePreset(); onClose()}}> -
- -
-
-
- ) -} - -export default CreatePresetModal \ No newline at end of file diff --git a/web2/src/pages/Settings/Presets/Presets.jsx b/web2/src/pages/Settings/Presets/Presets.jsx index 54b128a7f..12c5c4a4f 100644 --- a/web2/src/pages/Settings/Presets/Presets.jsx +++ b/web2/src/pages/Settings/Presets/Presets.jsx @@ -4,7 +4,7 @@ import { useStatusStore } from "@/App.jsx"; import { useState } from "react"; import { Fab } from "@mui/material"; import AddIcon from "@mui/icons-material/Add"; -import CreatePresetModal from './CreatePresetModal/CreatePresetModal2' +import CreatePresetModal from './CreatePresetModal/CreatePresetModal' const PresetListItem = ({ preset }) => { const [editOpen, setEditOpen] = useState(false); From 1a5f74b73dff94079f5ebe19c378e82d61c0aa49 Mon Sep 17 00:00:00 2001 From: Jonah Shader Date: Mon, 24 Apr 2023 17:30:08 -0400 Subject: [PATCH 07/10] Preset creating working --- .../Presets/CreatePresetModal/CreatePresetModal.jsx | 12 +++++++++++- web2/vite.config.js | 3 ++- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/web2/src/pages/Settings/Presets/CreatePresetModal/CreatePresetModal.jsx b/web2/src/pages/Settings/Presets/CreatePresetModal/CreatePresetModal.jsx index beb8b58e2..32ec9283c 100644 --- a/web2/src/pages/Settings/Presets/CreatePresetModal/CreatePresetModal.jsx +++ b/web2/src/pages/Settings/Presets/CreatePresetModal/CreatePresetModal.jsx @@ -167,7 +167,17 @@ const CreatePresetModal = ({ onClose }) => { console.log("savePreset") console.log(mergePayloads(tree)) - // build + // create presett + fetch(`/api/preset`, { + method: 'POST', + headers: { + 'Content-type': 'application/json', + }, + body: JSON.stringify({ + 'name': name, + 'state': mergePayloads(tree), + }) + }) } if (tree === null) return
diff --git a/web2/vite.config.js b/web2/vite.config.js index ffa0777f0..d7d6765f9 100644 --- a/web2/vite.config.js +++ b/web2/vite.config.js @@ -6,7 +6,8 @@ import path from 'path' // set this to dev server url //TODO: find a way to do this from cli // const amplipiurl = "http://192.168.0.117" -const amplipiurl = "http://192.168.0.178" +// const amplipiurl = "http://192.168.0.178" +const amplipiurl = "http://192.168.0.118" // const amplipiurl = "http://fe80::4caf:b851:9a24:ffbc" // const amplipiurl = "http://192.168.0.198" // const amplipiurl = "http://localhost" From eaf3f8c5b690b6d9265e91add5bd88dbbd6a59da Mon Sep 17 00:00:00 2001 From: Jonah Shader Date: Tue, 25 Apr 2023 12:38:45 -0400 Subject: [PATCH 08/10] Add edit, delete preset functionality --- web2/src/pages/Home/Home.jsx | 31 ++---------- .../CreatePresetModal/CreatePresetModal.jsx | 20 +++----- .../EditPresetModal/EditPresetModal.jsx | 50 +++++++++++++++++++ .../EditPresetModal/EditPresetModal.scss | 8 +++ web2/src/pages/Settings/Presets/Presets.jsx | 42 ++++++++-------- 5 files changed, 90 insertions(+), 61 deletions(-) create mode 100644 web2/src/pages/Settings/Presets/EditPresetModal/EditPresetModal.jsx create mode 100644 web2/src/pages/Settings/Presets/EditPresetModal/EditPresetModal.scss diff --git a/web2/src/pages/Home/Home.jsx b/web2/src/pages/Home/Home.jsx index 8729773eb..2c365770a 100644 --- a/web2/src/pages/Home/Home.jsx +++ b/web2/src/pages/Home/Home.jsx @@ -25,13 +25,6 @@ const Home = ({ selectedSource, setSelectedSource }) => { let cards = []; let nextAvailableSource = null; - // temp presets button - cards.push( -
setPresetsModalOpen(true)} key={-1}> - Presets -
- ) - sources.forEach((source, i) => { if(source.input.toUpperCase() != "NONE" && source.input != "" && source.input != "local"){ cards.push( @@ -58,26 +51,10 @@ const Home = ({ selectedSource, setSelectedSource }) => { return (
- {/* - - - */} + {/* temp presets button thing */} +
setPresetsModalOpen(true)}> + Presets +
{ cards diff --git a/web2/src/pages/Settings/Presets/CreatePresetModal/CreatePresetModal.jsx b/web2/src/pages/Settings/Presets/CreatePresetModal/CreatePresetModal.jsx index 32ec9283c..6812eb742 100644 --- a/web2/src/pages/Settings/Presets/CreatePresetModal/CreatePresetModal.jsx +++ b/web2/src/pages/Settings/Presets/CreatePresetModal/CreatePresetModal.jsx @@ -8,11 +8,11 @@ import Card from '@/components/Card/Card' import DoneIcon from '@mui/icons-material/Done' import './CreatePresetModal.scss' -import Checkbox from '@mui/material/Checkbox'; +import Checkbox from '@mui/material/Checkbox' import Switch from '@mui/material/Switch' -import IconButton from '@mui/material/IconButton'; -import FormControlLabel from '@mui/material/FormControlLabel'; -import Box from '@mui/material/Box'; +import IconButton from '@mui/material/IconButton' +import FormControlLabel from '@mui/material/FormControlLabel' +import Box from '@mui/material/Box' const setCheckedRecur = (dict, checked) => { dict.checked = checked @@ -31,9 +31,6 @@ const computeCheckedStateRecur = (dict) => { c = 'ind' } } - if (dict.checked !== c) { - console.log(`check state changed from ${dict.checked} to ${c} for ${dict}.`) - } dict.checked = c return c } @@ -69,8 +66,6 @@ const baseDict = (open, name, content, payload=null) => { } } - - const buildTreeDict = (status, showInactive=false) => { const dictWithOptions = (source) => { // grab zones that are playing this source @@ -164,10 +159,7 @@ const CreatePresetModal = ({ onClose }) => { }, []) const savePreset = () => { - console.log("savePreset") - console.log(mergePayloads(tree)) - - // create presett + // create preset fetch(`/api/preset`, { method: 'POST', headers: { @@ -179,7 +171,7 @@ const CreatePresetModal = ({ onClose }) => { }) }) } - + // creation of tree is delayed due to useEffect, so early return is required if (tree === null) return
return ( diff --git a/web2/src/pages/Settings/Presets/EditPresetModal/EditPresetModal.jsx b/web2/src/pages/Settings/Presets/EditPresetModal/EditPresetModal.jsx new file mode 100644 index 000000000..0f5ffab37 --- /dev/null +++ b/web2/src/pages/Settings/Presets/EditPresetModal/EditPresetModal.jsx @@ -0,0 +1,50 @@ +import { useState } from 'react' + +import Modal from '@/components/Modal/Modal' +import Card from '@/components/Card/Card' +import DoneIcon from '@mui/icons-material/Done' +import DeleteIcon from '@mui/icons-material/Delete' +import IconButton from '@mui/material/IconButton' +import './EditPresetModal.scss' + +const EditPresetModal = ({ onClose, preset }) => { + const [name, setName] = useState(preset.name) + + const editPreset = () => { + const preset_copy = JSON.parse(JSON.stringify(preset)) + preset_copy.name = name + fetch(`/api/presets/${preset.id}`, { + method: 'PATCH', + headers: {"Content-type": "application/json"}, + body: JSON.stringify(preset_copy) + }) + } + + const deletePreset = () => { + fetch(`/api/presets/${preset.id}`, {method: 'DELETE'}) + } + + return ( + + +
+
+ Edit Preset +
+ setName(e.target.value)}/> +
+
+ {editPreset(); onClose()}}> + + + {deletePreset(); onClose()}}> + + +
+
+
+
+ ) +} + +export default EditPresetModal \ No newline at end of file diff --git a/web2/src/pages/Settings/Presets/EditPresetModal/EditPresetModal.scss b/web2/src/pages/Settings/Presets/EditPresetModal/EditPresetModal.scss new file mode 100644 index 000000000..7b9705a98 --- /dev/null +++ b/web2/src/pages/Settings/Presets/EditPresetModal/EditPresetModal.scss @@ -0,0 +1,8 @@ +@use "src/general.scss"; + +.preset-name { + @include general.header-font; + color: general.$text-color; + font-size: 2rem; + margin: 0.5rem; +} \ No newline at end of file diff --git a/web2/src/pages/Settings/Presets/Presets.jsx b/web2/src/pages/Settings/Presets/Presets.jsx index 12c5c4a4f..968b070bd 100644 --- a/web2/src/pages/Settings/Presets/Presets.jsx +++ b/web2/src/pages/Settings/Presets/Presets.jsx @@ -1,33 +1,35 @@ -import PageHeader from "@/components/PageHeader/PageHeader"; -import "./Presets.scss"; -import { useStatusStore } from "@/App.jsx"; -import { useState } from "react"; -import { Fab } from "@mui/material"; -import AddIcon from "@mui/icons-material/Add"; +import PageHeader from "@/components/PageHeader/PageHeader" +import "./Presets.scss" +import { useStatusStore } from "@/App.jsx" +import { useState } from "react" +import { Fab } from "@mui/material" +import AddIcon from "@mui/icons-material/Add" import CreatePresetModal from './CreatePresetModal/CreatePresetModal' +import EditPresetModal from "./EditPresetModal/EditPresetModal" + const PresetListItem = ({ preset }) => { - const [editOpen, setEditOpen] = useState(false); + const [presetOpen, setPresetOpen] = useState(false); return ( <> -
setEditOpen(true)}> +
setPresetOpen(true)}> {preset.name}
- {/* { editOpen && - setEditOpen(false)}/> - } */} + { presetOpen && + setPresetOpen(false)} preset={preset}/> + } - ); -}; + ) +} const Presets = ({ onClose }) => { - const [createModalOpen, setCreateModalOpen] = useState(false); - const presets = useStatusStore((s) => s.status.presets); + const [createModalOpen, setCreateModalOpen] = useState(false) + const presets = useStatusStore((s) => s.status.presets) const presetListItems = presets.map((preset) => { - return ; - }); + return + }) return ( <> @@ -44,7 +46,7 @@ const Presets = ({ onClose }) => {
{createModalOpen && setCreateModalOpen(false)}/>} - ); -}; + ) +} -export default Presets; +export default Presets From 7393f26f98a5aeeb0b4a7fb4421dfbebbfce1108 Mon Sep 17 00:00:00 2001 From: Jonah Shader Date: Tue, 25 Apr 2023 13:39:29 -0400 Subject: [PATCH 09/10] Better presets button --- web2/src/pages/Home/Home.jsx | 28 ++++++++++++++++------------ web2/src/pages/Home/Home.scss | 34 ++++++++++++++++++++++++++++++++-- 2 files changed, 48 insertions(+), 14 deletions(-) diff --git a/web2/src/pages/Home/Home.jsx b/web2/src/pages/Home/Home.jsx index 2c365770a..110b77580 100644 --- a/web2/src/pages/Home/Home.jsx +++ b/web2/src/pages/Home/Home.jsx @@ -48,21 +48,25 @@ const Home = ({ selectedSource, setSelectedSource }) => { setZonesModalOpen(true) } + const PresetAndAdd = () => { + if (cards.length < sources.length) { + return ( +
+
{initSource(nextAvailableSource)}}>+
+
+
setPresetsModalOpen(true)}>Presets
+
+ ) + } else { + return
setPresetsModalOpen(true)}>Presets
+ } + } + return (
- {/* temp presets button thing */} -
setPresetsModalOpen(true)}> - Presets -
- - { - cards - } - - {cards.length < sources.length && -
{initSource(nextAvailableSource)}}>+
- } + {cards} +
{zonesModalOpen && {setStreamsModalOpen(!o); setZonesModalOpen(o)}} onClose={()=>setZonesModalOpen(false)}/>} diff --git a/web2/src/pages/Home/Home.scss b/web2/src/pages/Home/Home.scss index 82138c605..cb62b44e4 100644 --- a/web2/src/pages/Home/Home.scss +++ b/web2/src/pages/Home/Home.scss @@ -16,9 +16,9 @@ width: fit-content; } -.home-add-player-button{ +.home-add-player-button { width: 100%; - height: 5rem; + height: 4rem; display: flex; flex-direction: column; justify-content: center; @@ -30,3 +30,33 @@ border-radius: 1rem; background-color: general.$bg; } + +.home-presets-button { + @include general.header-font; + font-size: 2rem; + width: 100%; + height: 4rem; + // padding-left: 1rem; + // padding-right: 1rem; + // padding-top: 0.1rem; + // padding-bottom: 0.1rem; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + + color: general.$controls-color; + border-color: general.$secondary; + border-style: solid; + border-radius: 1rem; + background-color: general.$bg; +} + +.home-presets-container { + display: flex; + + flex-direction: row; + justify-content: space-between; + align-items: center; +} + From fecc6a5140e23f07cd3fe85b80deaa5f72adac7d Mon Sep 17 00:00:00 2001 From: Jonah Shader Date: Tue, 25 Apr 2023 14:12:11 -0400 Subject: [PATCH 10/10] Change PlayerCard routing --- web2/src/App.jsx | 2 +- web2/src/components/PlayerCard/PlayerCard.jsx | 5 +++-- web2/vite.config.js | 4 ++-- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/web2/src/App.jsx b/web2/src/App.jsx index a0aecae7f..26280175e 100644 --- a/web2/src/App.jsx +++ b/web2/src/App.jsx @@ -213,7 +213,7 @@ function App({ selectedPage }) { const Page = () => { switch(selectedPage) { default: - return + return case 1: return case 2: diff --git a/web2/src/components/PlayerCard/PlayerCard.jsx b/web2/src/components/PlayerCard/PlayerCard.jsx index 38ba28c3b..b246bd80d 100644 --- a/web2/src/components/PlayerCard/PlayerCard.jsx +++ b/web2/src/components/PlayerCard/PlayerCard.jsx @@ -8,15 +8,16 @@ import PlayerImage from "../PlayerImage/PlayerImage"; import ZonesBadge from "../ZonesBadge/ZonesBadge"; import StreamsModal from "../StreamsModal/StreamsModal"; import ZonesModal from "../ZonesModal/ZonesModal"; +import { router } from "@/main"; -const PlayerCard = ({ sourceId, selectedSource, setSelectedPage, setSelectedSource }) => { +const PlayerCard = ({ sourceId, selectedSource, setSelectedSource }) => { const [streamModalOpen, setStreamModalOpen] = useState(false); const [zoneModalOpen, setZoneModalOpen] = useState(false); const selected = selectedSource === sourceId const select = () => { if (selected) { - setSelectedPage(1) + router.navigate('/player') } setSelectedSource(sourceId) diff --git a/web2/vite.config.js b/web2/vite.config.js index d7d6765f9..ff2ddcc69 100644 --- a/web2/vite.config.js +++ b/web2/vite.config.js @@ -6,8 +6,8 @@ import path from 'path' // set this to dev server url //TODO: find a way to do this from cli // const amplipiurl = "http://192.168.0.117" -// const amplipiurl = "http://192.168.0.178" -const amplipiurl = "http://192.168.0.118" +const amplipiurl = "http://192.168.0.178" +// const amplipiurl = "http://192.168.0.118" // const amplipiurl = "http://fe80::4caf:b851:9a24:ffbc" // const amplipiurl = "http://192.168.0.198" // const amplipiurl = "http://localhost"