Skip to content

Commit

Permalink
feat: Introducing new Timer UI, including Idle Shutdown
Browse files Browse the repository at this point in the history
  • Loading branch information
pabera committed Apr 24, 2024
1 parent e418f9a commit 698de7d
Show file tree
Hide file tree
Showing 7 changed files with 183 additions and 45 deletions.
19 changes: 12 additions & 7 deletions src/jukebox/components/timers/idle_shutdown_timer.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,23 +38,26 @@ def __init__(self, package: str, idle_timeout: int) -> None:
self.idle_timeout = 0
self.package = package
self.idle_check_interval = IDLE_CHECK_INTERVAL
self.set_idle_timeout(idle_timeout)

self.init_idle_shutdown()
plugin.register(self.private_timer_idle_shutdown, name='private_timer_idle_shutdown', package=self.package)

self.init_idle_check()
plugin.register(self.private_timer_idle_check, name='private_timer_idle_check', package=self.package)

def set_idle_timeout(self, idle_timeout):
try:
self.idle_timeout = int(idle_timeout)
except ValueError:
logger.warning(f'invalid timers.idle_shutdown.timeout_sec value {repr(idle_timeout)}')

if self.idle_timeout < IDLE_SHUTDOWN_TIMER_MIN_TIMEOUT_SECONDS:
logger.info('disabling idle shutdown timer; set '
'timers.idle_shutdown.timeout_sec to at least '
f'{IDLE_SHUTDOWN_TIMER_MIN_TIMEOUT_SECONDS} seconds to enable')
self.idle_timeout = 0

self.init_idle_shutdown()
plugin.register(self.private_timer_idle_shutdown, name='private_timer_idle_shutdown', package=self.package)

self.init_idle_check()
plugin.register(self.private_timer_idle_check, name='private_timer_idle_check', package=self.package)

# Using GenericMultiTimerClass instead of GenericTimerClass as it supports classes rather than functions
# Calling GenericMultiTimerClass with iterations=1 is the same as GenericTimerClass
def init_idle_shutdown(self):
Expand Down Expand Up @@ -86,18 +89,20 @@ def start(self, wait_seconds: int):

@plugin.tag
def cancel(self):
"""Cancels all idle timers and disables idle shutdown in jukebox.yaml"""
plugin.call_ignore_errors('timers', 'private_timer_idle_check', 'cancel')
plugin.call_ignore_errors('timers', 'private_timer_idle_shutdown', 'cancel')
cfg.setn('timers', 'idle_shutdown', 'timeout_sec', value=0)

@plugin.tag
def get_state(self):
"""Return idle_shutdown timeout_sec stored in jukebox.yaml"""
"""Returns the current state of Idle Shutdown"""
idle_check_state = plugin.call_ignore_errors('timers', 'private_timer_idle_check', 'get_state')
idle_shutdown_state = plugin.call_ignore_errors('timers', 'private_timer_idle_shutdown', 'get_state')

return {
'enabled': idle_check_state['enabled'],
'running': idle_shutdown_state['enabled'],
'remaining_seconds': idle_shutdown_state['remaining_seconds'],
'wait_seconds': idle_shutdown_state['wait_seconds_per_iteration'],
}
Expand Down
11 changes: 10 additions & 1 deletion src/webapp/public/locales/de/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -227,7 +227,16 @@
"shutdown": "Herunterfahren",
"fade-volume": "Lautstärke ausblenden",
"idle-shutdown": "Leerlaufabschaltung",
"ended": "Beendet"
"set": "Timer erstellen",
"cancel": "Abbrechen",
"paused": "Pausiert",
"ended": "Beendet",
"dialog": {
"title": "{{value}} Timer erstellen",
"description": "Wähle die Anzahl der Minuten nachdem die Aktion ausgeführt werden soll.",
"start": "Timer starten",
"cancel": "Abbrechen"
}
},
"secondswipe": {
"title": "Erneute Aktivierung (Second Swipe)",
Expand Down
15 changes: 12 additions & 3 deletions src/webapp/public/locales/en/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -224,10 +224,19 @@
"option-label-off": "Off",
"title": "Timers",
"stop-player": "Stop player",
"shutdown": "Shut Down",
"shutdown": "Shutdown",
"fade-volume": "Fade volume",
"idle-shutdown": "Idle Shut Down",
"ended": "Done"
"idle-shutdown": "Idle shutdown",
"ended": "Done",
"set": "Set timer",
"cancel": "Cancel",
"paused": "Paused",
"dialog": {
"title": "Set {{value}} timer",
"description": "Choose the amount of minutes you want the action to be performed.",
"start": "Start timer",
"cancel": "Cancel"
}
},
"secondswipe": {
"title": "Second Swipe",
Expand Down
19 changes: 19 additions & 0 deletions src/webapp/src/commands/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,25 @@ const commands = {
},


'timer_idle_shutdown.cancel': {
_package: 'timers',
plugin: 'timer_idle_shutdown',
method: 'cancel',
},
'timer_idle_shutdown.get_state': {
_package: 'timers',
plugin: 'timer_idle_shutdown',
method: 'get_state',
},
'timer_idle_shutdown': {
_package: 'timers',
plugin: 'timer_idle_shutdown',
method: 'start',
argKeys: ['wait_seconds'],
},



// Host
getAutohotspotStatus: {
_package: 'host',
Expand Down
4 changes: 2 additions & 2 deletions src/webapp/src/components/Settings/timers/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,8 @@ const SettingsTimers = () => {
>
<Timer type={'shutdown'} />
<Timer type={'stop-player'} />
<Timer type={'fade-volume'} />
{/* <Timer type={'idle-shutdown'} /> */}
{/* <Timer type={'fade-volume'} /> */}
<Timer type={'idle-shutdown'} />
</Grid>
</CardContent>
</Card>
Expand Down
101 changes: 101 additions & 0 deletions src/webapp/src/components/Settings/timers/set-timer-dialog.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
import React, { useState } from 'react';
import { useTranslation } from 'react-i18next';

import {
Box,
Button,
Dialog,
DialogActions,
DialogContent,
DialogContentText,
DialogTitle,
Grid,
} from '@mui/material';
import { useTheme } from '@mui/material/styles';

import {
SliderTimer
} from '../../general';

export default function SetTimerDialog({
type,
enabled,
setTimer,
cancelTimer,
waitSeconds,
setWaitSeconds,
}) {
const { t } = useTranslation();
const theme = useTheme();

const [dialogOpen, setDialogOpen] = useState(false);

const handleClickOpen = () => {
setWaitSeconds(0);
setDialogOpen(true);
};

const handleCancel = () => {
setDialogOpen(false);
};

const handleSetTimer = () => {
setTimer(waitSeconds)
setDialogOpen(false);
}

return (
<Box sx={{ marginLeft: '10px' }}>
{!enabled &&
<Button
variant="outlined"
onClick={handleClickOpen}
>
{t('settings.timers.set')}
</Button>
}
{enabled &&
<Button
variant="outlined"
onClick={cancelTimer}
>
{t('settings.timers.cancel')}
</Button>
}
<Dialog
open={dialogOpen}
onClose={handleCancel}
aria-labelledby="alert-dialog-title"
aria-describedby="alert-dialog-description"
>
<DialogTitle id="alert-dialog-title">
{t('settings.timers.dialog.title', { value: t(`settings.timers.${type}`)} )}
</DialogTitle>
<DialogContent>
<DialogContentText id="alert-dialog-description">
{t('settings.timers.dialog.description')}
</DialogContentText>
<Grid item sx={{ padding: theme.spacing(1) }}>
<SliderTimer
value={waitSeconds || 0}
onChangeCommitted={(evt, value) => { setWaitSeconds(value) }}
/>
</Grid>
</DialogContent>
<DialogActions>
<Button onClick={handleCancel} color="secondary">
{t('settings.timers.dialog.cancel')}
</Button>
<Button
onClick={handleSetTimer}
color="primary"
autoFocus
disabled={waitSeconds === 0}
>
{t('settings.timers.dialog.start')}
</Button>
</DialogActions>
</Dialog>
</Box>
);
}
59 changes: 27 additions & 32 deletions src/webapp/src/components/Settings/timers/timer.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,15 @@ import { useTranslation } from 'react-i18next';
import {
Box,
Grid,
Switch,
Typography,
} from '@mui/material';
import { useTheme } from '@mui/material/styles';

import request from '../../../utils/request';
import {
Countdown,
SliderTimer
} from '../../general';
import { Countdown } from '../../general';
import SetTimerDialog from './set-timer-dialog';

const Timer = ({ type }) => {
const { t } = useTranslation();
const theme = useTheme();

// Constants
const pluginName = `timer_${type.replace('-', '_')}`;
Expand All @@ -28,14 +23,15 @@ const Timer = ({ type }) => {
const [isLoading, setIsLoading] = useState(true);
const [status, setStatus] = useState({ enabled: false });
const [waitSeconds, setWaitSeconds] = useState(0);
const [running, setRunning] = useState(true);

// Requests
const cancelTimer = async () => {
await request(`${pluginName}.cancel`);
setStatus({ enabled: false });
setEnabled(false);
};

const setTimer = async (event, wait_seconds) => {
const setTimer = async (wait_seconds) => {
await cancelTimer();

if (wait_seconds > 0) {
Expand All @@ -57,17 +53,14 @@ const Timer = ({ type }) => {

setStatus(timerStatus);
setEnabled(timerStatus?.enabled);
setWaitSeconds(timerStatus?.wait_seconds || 0);
if (timerStatus.running === undefined) {
setRunning(true);
}
else {
setRunning(timerStatus.running);
}
}, [pluginName]);


// Event Handlers
const handleSwitch = (event) => {
setEnabled(event.target.checked);
setWaitSeconds(0); // Always start the slider at 0
cancelTimer();
}

// Effects
useEffect(() => {
fetchTimerStatus();
Expand All @@ -85,31 +78,33 @@ const Timer = ({ type }) => {
alignItems: 'center',
marginLeft: '0',
}}>
{status?.enabled &&
{enabled && running &&
<Countdown
seconds={status.remaining_seconds}
onEnd={() => setEnabled(false)}
stringEnded={t('settings.timers.ended')}
/>
}
{enabled && !running &&
<Typography>
Paused
</Typography>
}
{error &&
<Typography>⚠️</Typography>
}
<Switch
checked={enabled}
disabled={isLoading}
onChange={handleSwitch}
/>
{!isLoading &&
<SetTimerDialog
type={type}
enabled={enabled}
setTimer={setTimer}
cancelTimer={cancelTimer}
waitSeconds={waitSeconds}
setWaitSeconds={setWaitSeconds}
/>
}
</Box>
</Grid>
{enabled &&
<Grid item sx={{ padding: theme.spacing(1) }}>
<SliderTimer
value={waitSeconds}
onChangeCommitted={setTimer}
/>
</Grid>
}
</Grid>
);
};
Expand Down

0 comments on commit 698de7d

Please sign in to comment.