1
0
Fork 0
mirror of https://github.com/plankanban/planka.git synced 2025-07-19 05:09:43 +02:00

feat: Highlight active timer, add quick actions

This commit is contained in:
Maksim Eltyshev 2022-09-15 00:20:05 +05:00
parent e9ed59d90f
commit bad3e089ce
4 changed files with 57 additions and 7 deletions

View file

@ -5,6 +5,7 @@ import { Button, Icon } from 'semantic-ui-react';
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom';
import { Draggable } from 'react-beautiful-dnd'; import { Draggable } from 'react-beautiful-dnd';
import { startTimer, stopTimer } from '../../utils/timer';
import Paths from '../../constants/Paths'; import Paths from '../../constants/Paths';
import Tasks from './Tasks'; import Tasks from './Tasks';
import NameEdit from './NameEdit'; import NameEdit from './NameEdit';
@ -57,6 +58,17 @@ const Card = React.memo(
} }
}, []); }, []);
const handleToggleTimerClick = useCallback(
(event) => {
event.preventDefault();
onUpdate({
timer: timer.startedAt ? stopTimer(timer) : startTimer(timer),
});
},
[timer, onUpdate],
);
const handleNameUpdate = useCallback( const handleNameUpdate = useCallback(
(newName) => { (newName) => {
onUpdate({ onUpdate({
@ -108,7 +120,13 @@ const Card = React.memo(
)} )}
{timer && ( {timer && (
<span className={classNames(styles.attachment, styles.attachmentLeft)}> <span className={classNames(styles.attachment, styles.attachmentLeft)}>
<Timer startedAt={timer.startedAt} total={timer.total} size="tiny" /> <Timer
as="span"
startedAt={timer.startedAt}
total={timer.total}
size="tiny"
onClick={handleToggleTimerClick}
/>
</span> </span>
)} )}
</span> </span>

View file

@ -5,6 +5,7 @@ import { useTranslation } from 'react-i18next';
import { Button, Grid, Icon, Modal } from 'semantic-ui-react'; import { Button, Grid, Icon, Modal } from 'semantic-ui-react';
import { Markdown } from '../../lib/custom-ui'; import { Markdown } from '../../lib/custom-ui';
import { startTimer, stopTimer } from '../../utils/timer';
import NameField from './NameField'; import NameField from './NameField';
import DescriptionEdit from './DescriptionEdit'; import DescriptionEdit from './DescriptionEdit';
import Tasks from './Tasks'; import Tasks from './Tasks';
@ -80,6 +81,12 @@ const CardModal = React.memo(
const isGalleryOpened = useRef(false); const isGalleryOpened = useRef(false);
const handleToggleTimerClick = useCallback(() => {
onUpdate({
timer: timer.startedAt ? stopTimer(timer) : startTimer(timer),
});
}, [timer, onUpdate]);
const handleNameUpdate = useCallback( const handleNameUpdate = useCallback(
(newName) => { (newName) => {
onUpdate({ onUpdate({
@ -291,6 +298,17 @@ const CardModal = React.memo(
<Timer startedAt={timer.startedAt} total={timer.total} /> <Timer startedAt={timer.startedAt} total={timer.total} />
)} )}
</span> </span>
<button
onClick={handleToggleTimerClick}
type="button"
className={classNames(styles.attachment, styles.dueDate)}
>
<Icon
name={timer.startedAt ? 'pause' : 'play'}
size="small"
className={styles.addAttachment}
/>
</button>
</div> </div>
)} )}
</div> </div>

View file

@ -14,7 +14,7 @@ const SIZES = {
MEDIUM: 'medium', MEDIUM: 'medium',
}; };
const Timer = React.memo(({ startedAt, total, size, isDisabled, onClick }) => { const Timer = React.memo(({ as, startedAt, total, size, isDisabled, onClick }) => {
const prevStartedAt = usePrevious(startedAt); const prevStartedAt = usePrevious(startedAt);
const forceUpdate = useForceUpdate(); const forceUpdate = useForceUpdate();
@ -52,6 +52,7 @@ const Timer = React.memo(({ startedAt, total, size, isDisabled, onClick }) => {
className={classNames( className={classNames(
styles.wrapper, styles.wrapper,
styles[`wrapper${upperFirst(size)}`], styles[`wrapper${upperFirst(size)}`],
startedAt && styles.wrapperActive,
onClick && styles.wrapperHoverable, onClick && styles.wrapperHoverable,
)} )}
> >
@ -59,16 +60,19 @@ const Timer = React.memo(({ startedAt, total, size, isDisabled, onClick }) => {
</span> </span>
); );
const ElementType = as;
return onClick ? ( return onClick ? (
<button type="button" disabled={isDisabled} className={styles.button} onClick={onClick}> <ElementType type="button" disabled={isDisabled} className={styles.button} onClick={onClick}>
{contentNode} {contentNode}
</button> </ElementType>
) : ( ) : (
contentNode contentNode
); );
}); });
Timer.propTypes = { Timer.propTypes = {
as: PropTypes.elementType,
startedAt: PropTypes.instanceOf(Date), startedAt: PropTypes.instanceOf(Date),
total: PropTypes.number.isRequired, // eslint-disable-line react/no-unused-prop-types total: PropTypes.number.isRequired, // eslint-disable-line react/no-unused-prop-types
size: PropTypes.oneOf(Object.values(SIZES)), size: PropTypes.oneOf(Object.values(SIZES)),
@ -77,6 +81,7 @@ Timer.propTypes = {
}; };
Timer.defaultProps = { Timer.defaultProps = {
as: 'button',
startedAt: undefined, startedAt: undefined,
size: SIZES.MEDIUM, size: SIZES.MEDIUM,
isDisabled: false, isDisabled: false,

View file

@ -9,10 +9,10 @@
} }
.wrapper { .wrapper {
background: #f3f4f6; background: #dce0e4;
border: none; border: none;
border-radius: 3px; border-radius: 3px;
color: #7e8ea3; color: #6a808b;
display: inline-block; display: inline-block;
font-variant-numeric: tabular-nums; font-variant-numeric: tabular-nums;
outline: none; outline: none;
@ -21,6 +21,16 @@
vertical-align: top; vertical-align: top;
} }
.wrapperActive {
background: #21ba45;
color: #fff;
&.wrapperHoverable:hover {
background: #16ab39;
color: #fff;
}
}
.wrapperHoverable:hover { .wrapperHoverable:hover {
background: #d2d8dc; background: #d2d8dc;
color: #17394d; color: #17394d;
@ -43,6 +53,5 @@
.wrapperMedium { .wrapperMedium {
line-height: 20px; line-height: 20px;
padding: 6px 12px; padding: 6px 12px;
text-decoration: underline;
} }
} }