Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cc7959bb15 | ||
|
|
56044e6d8a | ||
|
|
e5bd73f33e | ||
|
|
e65ab6ece7 | ||
|
|
14cc55a8c0 | ||
|
|
b4306d0a4b | ||
|
|
283c850ebf | ||
|
|
62de71bd97 |
Generated
+11
@@ -6,6 +6,7 @@
|
||||
"": {
|
||||
"name": "planka-client",
|
||||
"dependencies": {
|
||||
"@juggle/resize-observer": "^3.4.0",
|
||||
"classnames": "^2.3.1",
|
||||
"connected-react-router": "^6.9.3",
|
||||
"date-fns": "^2.29.1",
|
||||
@@ -3030,6 +3031,11 @@
|
||||
"@jridgewell/sourcemap-codec": "^1.4.10"
|
||||
}
|
||||
},
|
||||
"node_modules/@juggle/resize-observer": {
|
||||
"version": "3.4.0",
|
||||
"resolved": "https://registry.npmjs.org/@juggle/resize-observer/-/resize-observer-3.4.0.tgz",
|
||||
"integrity": "sha512-dfLbk+PwWvFzSxwk3n5ySL0hfBog779o8h68wK/7/APo/7cgyWp5jcXockbxdk5kFRkbeXWm4Fbi9FrdN381sA=="
|
||||
},
|
||||
"node_modules/@leichtgewicht/ip-codec": {
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.4.tgz",
|
||||
@@ -27333,6 +27339,11 @@
|
||||
"@jridgewell/sourcemap-codec": "^1.4.10"
|
||||
}
|
||||
},
|
||||
"@juggle/resize-observer": {
|
||||
"version": "3.4.0",
|
||||
"resolved": "https://registry.npmjs.org/@juggle/resize-observer/-/resize-observer-3.4.0.tgz",
|
||||
"integrity": "sha512-dfLbk+PwWvFzSxwk3n5ySL0hfBog779o8h68wK/7/APo/7cgyWp5jcXockbxdk5kFRkbeXWm4Fbi9FrdN381sA=="
|
||||
},
|
||||
"@leichtgewicht/ip-codec": {
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.4.tgz",
|
||||
|
||||
@@ -63,6 +63,7 @@
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"@juggle/resize-observer": "^3.4.0",
|
||||
"classnames": "^2.3.1",
|
||||
"connected-react-router": "^6.9.3",
|
||||
"date-fns": "^2.29.1",
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 22 KiB |
@@ -5,6 +5,7 @@ import { Button, Icon } from 'semantic-ui-react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { Draggable } from 'react-beautiful-dnd';
|
||||
|
||||
import { startTimer, stopTimer } from '../../utils/timer';
|
||||
import Paths from '../../constants/Paths';
|
||||
import Tasks from './Tasks';
|
||||
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(
|
||||
(newName) => {
|
||||
onUpdate({
|
||||
@@ -108,7 +120,13 @@ const Card = React.memo(
|
||||
)}
|
||||
{timer && (
|
||||
<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>
|
||||
|
||||
@@ -78,7 +78,9 @@
|
||||
.cover {
|
||||
border-radius: 3px 3px 0 0;
|
||||
margin-bottom: 2px;
|
||||
padding: 2px 2px 0;
|
||||
vertical-align: middle;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.details {
|
||||
|
||||
@@ -5,6 +5,7 @@ import { useTranslation } from 'react-i18next';
|
||||
import { Button, Grid, Icon, Modal } from 'semantic-ui-react';
|
||||
import { Markdown } from '../../lib/custom-ui';
|
||||
|
||||
import { startTimer, stopTimer } from '../../utils/timer';
|
||||
import NameField from './NameField';
|
||||
import DescriptionEdit from './DescriptionEdit';
|
||||
import Tasks from './Tasks';
|
||||
@@ -80,6 +81,12 @@ const CardModal = React.memo(
|
||||
|
||||
const isGalleryOpened = useRef(false);
|
||||
|
||||
const handleToggleTimerClick = useCallback(() => {
|
||||
onUpdate({
|
||||
timer: timer.startedAt ? stopTimer(timer) : startTimer(timer),
|
||||
});
|
||||
}, [timer, onUpdate]);
|
||||
|
||||
const handleNameUpdate = useCallback(
|
||||
(newName) => {
|
||||
onUpdate({
|
||||
@@ -291,6 +298,17 @@ const CardModal = React.memo(
|
||||
<Timer startedAt={timer.startedAt} total={timer.total} />
|
||||
)}
|
||||
</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>
|
||||
|
||||
@@ -14,7 +14,7 @@ const SIZES = {
|
||||
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 forceUpdate = useForceUpdate();
|
||||
|
||||
@@ -52,6 +52,7 @@ const Timer = React.memo(({ startedAt, total, size, isDisabled, onClick }) => {
|
||||
className={classNames(
|
||||
styles.wrapper,
|
||||
styles[`wrapper${upperFirst(size)}`],
|
||||
startedAt && styles.wrapperActive,
|
||||
onClick && styles.wrapperHoverable,
|
||||
)}
|
||||
>
|
||||
@@ -59,16 +60,19 @@ const Timer = React.memo(({ startedAt, total, size, isDisabled, onClick }) => {
|
||||
</span>
|
||||
);
|
||||
|
||||
const ElementType = as;
|
||||
|
||||
return onClick ? (
|
||||
<button type="button" disabled={isDisabled} className={styles.button} onClick={onClick}>
|
||||
<ElementType type="button" disabled={isDisabled} className={styles.button} onClick={onClick}>
|
||||
{contentNode}
|
||||
</button>
|
||||
</ElementType>
|
||||
) : (
|
||||
contentNode
|
||||
);
|
||||
});
|
||||
|
||||
Timer.propTypes = {
|
||||
as: PropTypes.elementType,
|
||||
startedAt: PropTypes.instanceOf(Date),
|
||||
total: PropTypes.number.isRequired, // eslint-disable-line react/no-unused-prop-types
|
||||
size: PropTypes.oneOf(Object.values(SIZES)),
|
||||
@@ -77,6 +81,7 @@ Timer.propTypes = {
|
||||
};
|
||||
|
||||
Timer.defaultProps = {
|
||||
as: 'button',
|
||||
startedAt: undefined,
|
||||
size: SIZES.MEDIUM,
|
||||
isDisabled: false,
|
||||
|
||||
@@ -9,17 +9,28 @@
|
||||
}
|
||||
|
||||
.wrapper {
|
||||
background: #f3f4f6;
|
||||
background: #dce0e4;
|
||||
border: none;
|
||||
border-radius: 3px;
|
||||
color: #7e8ea3;
|
||||
color: #6a808b;
|
||||
display: inline-block;
|
||||
font-variant-numeric: tabular-nums;
|
||||
outline: none;
|
||||
text-align: left;
|
||||
transition: background 0.3s ease;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
.wrapperActive {
|
||||
background: #21ba45;
|
||||
color: #fff;
|
||||
|
||||
&.wrapperHoverable:hover {
|
||||
background: #16ab39;
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
|
||||
.wrapperHoverable:hover {
|
||||
background: #d2d8dc;
|
||||
color: #17394d;
|
||||
@@ -42,6 +53,5 @@
|
||||
.wrapperMedium {
|
||||
line-height: 20px;
|
||||
padding: 6px 12px;
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
import React from 'react';
|
||||
// import { useTranslation } from 'react-i18next';
|
||||
import { Image, Tab } from 'semantic-ui-react';
|
||||
|
||||
import logo from '../../assets/images/logo.png';
|
||||
|
||||
import styles from './AboutPane.module.scss';
|
||||
|
||||
const AboutPane = React.memo(() => {
|
||||
// const [t] = useTranslation();
|
||||
|
||||
return (
|
||||
<Tab.Pane attached={false} className={styles.wrapper}>
|
||||
<Image centered src={logo} size="large" className={styles.logo} />
|
||||
</Tab.Pane>
|
||||
);
|
||||
});
|
||||
|
||||
export default AboutPane;
|
||||
@@ -0,0 +1,10 @@
|
||||
:global(#app) {
|
||||
/* .logo {
|
||||
margin-bottom: 16px;
|
||||
} */
|
||||
|
||||
.wrapper {
|
||||
border: none;
|
||||
box-shadow: none;
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,7 @@ import { Modal, Tab } from 'semantic-ui-react';
|
||||
|
||||
import AccountPane from './AccountPane';
|
||||
import PreferencesPane from './PreferencesPane';
|
||||
import AboutPane from './AboutPane';
|
||||
|
||||
const UserSettingsModal = React.memo(
|
||||
({
|
||||
@@ -71,6 +72,10 @@ const UserSettingsModal = React.memo(
|
||||
<PreferencesPane subscribeToOwnCards={subscribeToOwnCards} onUpdate={onUpdate} />
|
||||
),
|
||||
},
|
||||
{
|
||||
menuItem: 'About Planka',
|
||||
render: () => <AboutPane />,
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import React, { useCallback, useState } from 'react';
|
||||
import { ResizeObserver } from '@juggle/resize-observer';
|
||||
import React, { useCallback, useRef, useState } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Button, Popup as SemanticUIPopup } from 'semantic-ui-react';
|
||||
|
||||
@@ -8,6 +9,9 @@ export default (WrappedComponent, defaultProps) => {
|
||||
const Popup = React.memo(({ children, ...props }) => {
|
||||
const [isOpened, setIsOpened] = useState(false);
|
||||
|
||||
const wrapper = useRef(null);
|
||||
const resizeObserver = useRef(null);
|
||||
|
||||
const handleOpen = useCallback(() => {
|
||||
setIsOpened(true);
|
||||
}, []);
|
||||
@@ -37,6 +41,29 @@ export default (WrappedComponent, defaultProps) => {
|
||||
[children],
|
||||
);
|
||||
|
||||
const handleContentRef = useCallback((element) => {
|
||||
if (resizeObserver.current) {
|
||||
resizeObserver.current.disconnect();
|
||||
}
|
||||
|
||||
if (!element) {
|
||||
resizeObserver.current = null;
|
||||
return;
|
||||
}
|
||||
|
||||
resizeObserver.current = new ResizeObserver(() => {
|
||||
if (resizeObserver.current.isInitial) {
|
||||
resizeObserver.current.isInitial = false;
|
||||
return;
|
||||
}
|
||||
|
||||
wrapper.current.positionUpdate();
|
||||
});
|
||||
|
||||
resizeObserver.current.isInitial = true;
|
||||
resizeObserver.current.observe(element);
|
||||
}, []);
|
||||
|
||||
const tigger = React.cloneElement(children, {
|
||||
onClick: handleTriggerClick,
|
||||
});
|
||||
@@ -45,6 +72,7 @@ export default (WrappedComponent, defaultProps) => {
|
||||
<SemanticUIPopup
|
||||
basic
|
||||
wide
|
||||
ref={wrapper}
|
||||
trigger={tigger}
|
||||
on="click"
|
||||
open={isOpened}
|
||||
@@ -64,9 +92,11 @@ export default (WrappedComponent, defaultProps) => {
|
||||
onClick={handleClick}
|
||||
{...defaultProps} // eslint-disable-line react/jsx-props-no-spreading
|
||||
>
|
||||
<div ref={handleContentRef}>
|
||||
<Button icon="close" onClick={handleClose} className={styles.closeButton} />
|
||||
{/* eslint-disable-next-line react/jsx-props-no-spreading */}
|
||||
<WrappedComponent {...props} onClose={handleClose} />
|
||||
</div>
|
||||
</SemanticUIPopup>
|
||||
);
|
||||
});
|
||||
|
||||
@@ -5,7 +5,7 @@ import core from './core';
|
||||
|
||||
export default {
|
||||
language: 'en',
|
||||
country: 'us',
|
||||
country: 'gb',
|
||||
name: 'English',
|
||||
embeddedLocale: merge(login, core),
|
||||
};
|
||||
|
||||
Generated
+2
-2
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "planka",
|
||||
"version": "1.8.0",
|
||||
"version": "1.8.1",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "planka",
|
||||
"version": "1.8.0",
|
||||
"version": "1.8.1",
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "planka",
|
||||
"version": "1.8.0",
|
||||
"version": "1.8.1",
|
||||
"private": true,
|
||||
"homepage": "https://plankanban.github.io/planka",
|
||||
"repository": {
|
||||
|
||||
Reference in New Issue
Block a user