mirror of
https://github.com/plankanban/planka.git
synced 2025-07-18 20:59:44 +02:00
Change user size on card, save card description on click outside, prevent list deletion if any filter is active
This commit is contained in:
parent
febc614ce8
commit
ba2017705b
11 changed files with 74 additions and 38 deletions
|
@ -61,7 +61,7 @@ const Filter = React.memo(
|
||||||
<User
|
<User
|
||||||
name={user.name}
|
name={user.name}
|
||||||
avatarUrl={user.avatarUrl}
|
avatarUrl={user.avatarUrl}
|
||||||
size="small"
|
size="tiny"
|
||||||
onClick={() => handleUserRemoveClick(user.id)}
|
onClick={() => handleUserRemoveClick(user.id)}
|
||||||
/>
|
/>
|
||||||
</span>
|
</span>
|
||||||
|
|
|
@ -73,7 +73,7 @@
|
||||||
|
|
||||||
.tab {
|
.tab {
|
||||||
border-radius: 3px 3px 0 0;
|
border-radius: 3px 3px 0 0;
|
||||||
min-width: 160px;
|
min-width: 100px;
|
||||||
position: relative;
|
position: relative;
|
||||||
transition: all 0.1s ease;
|
transition: all 0.1s ease;
|
||||||
}
|
}
|
||||||
|
|
|
@ -112,7 +112,7 @@ const Card = React.memo(
|
||||||
key={user.id}
|
key={user.id}
|
||||||
className={classNames(styles.attachment, styles.attachmentRight)}
|
className={classNames(styles.attachment, styles.attachmentRight)}
|
||||||
>
|
>
|
||||||
<User name={user.name} avatarUrl={user.avatarUrl} size="tiny" />
|
<User name={user.name} avatarUrl={user.avatarUrl} size="small" />
|
||||||
</span>
|
</span>
|
||||||
))}
|
))}
|
||||||
</span>
|
</span>
|
||||||
|
|
|
@ -21,19 +21,15 @@ const EditDescription = React.forwardRef(({ children, defaultValue, onUpdate },
|
||||||
}, [defaultValue, setValue]);
|
}, [defaultValue, setValue]);
|
||||||
|
|
||||||
const close = useCallback(() => {
|
const close = useCallback(() => {
|
||||||
setIsOpened(false);
|
|
||||||
setValue(null);
|
|
||||||
}, [setValue]);
|
|
||||||
|
|
||||||
const submit = useCallback(() => {
|
|
||||||
const cleanValue = value.trim() || null;
|
const cleanValue = value.trim() || null;
|
||||||
|
|
||||||
if (cleanValue !== defaultValue) {
|
if (cleanValue !== defaultValue) {
|
||||||
onUpdate(cleanValue);
|
onUpdate(cleanValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
close();
|
setIsOpened(false);
|
||||||
}, [defaultValue, onUpdate, value, close]);
|
setValue(null);
|
||||||
|
}, [defaultValue, onUpdate, value, setValue]);
|
||||||
|
|
||||||
useImperativeHandle(
|
useImperativeHandle(
|
||||||
ref,
|
ref,
|
||||||
|
@ -53,10 +49,10 @@ const EditDescription = React.forwardRef(({ children, defaultValue, onUpdate },
|
||||||
const handleFieldKeyDown = useCallback(
|
const handleFieldKeyDown = useCallback(
|
||||||
(event) => {
|
(event) => {
|
||||||
if (event.ctrlKey && event.key === 'Enter') {
|
if (event.ctrlKey && event.key === 'Enter') {
|
||||||
submit();
|
close();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[submit],
|
[close],
|
||||||
);
|
);
|
||||||
|
|
||||||
const [handleFieldBlur, handleControlMouseOver, handleControlMouseOut] = useClosableForm(
|
const [handleFieldBlur, handleControlMouseOver, handleControlMouseOut] = useClosableForm(
|
||||||
|
@ -65,8 +61,8 @@ const EditDescription = React.forwardRef(({ children, defaultValue, onUpdate },
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleSubmit = useCallback(() => {
|
const handleSubmit = useCallback(() => {
|
||||||
submit();
|
close();
|
||||||
}, [submit]);
|
}, [close]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (isOpened) {
|
if (isOpened) {
|
||||||
|
|
|
@ -65,11 +65,13 @@ const ActionsStep = React.memo(({ onNameEdit, onCardAdd, onDelete, onClose }) =>
|
||||||
context: 'title',
|
context: 'title',
|
||||||
})}
|
})}
|
||||||
</Menu.Item>
|
</Menu.Item>
|
||||||
<Menu.Item className={styles.menuItem} onClick={handleDeleteClick}>
|
{onDelete && (
|
||||||
{t('action.deleteList', {
|
<Menu.Item className={styles.menuItem} onClick={handleDeleteClick}>
|
||||||
context: 'title',
|
{t('action.deleteList', {
|
||||||
})}
|
context: 'title',
|
||||||
</Menu.Item>
|
})}
|
||||||
|
</Menu.Item>
|
||||||
|
)}
|
||||||
</Menu>
|
</Menu>
|
||||||
</Popup.Content>
|
</Popup.Content>
|
||||||
</>
|
</>
|
||||||
|
@ -79,8 +81,12 @@ const ActionsStep = React.memo(({ onNameEdit, onCardAdd, onDelete, onClose }) =>
|
||||||
ActionsStep.propTypes = {
|
ActionsStep.propTypes = {
|
||||||
onNameEdit: PropTypes.func.isRequired,
|
onNameEdit: PropTypes.func.isRequired,
|
||||||
onCardAdd: PropTypes.func.isRequired,
|
onCardAdd: PropTypes.func.isRequired,
|
||||||
onDelete: PropTypes.func.isRequired,
|
onDelete: PropTypes.func,
|
||||||
onClose: PropTypes.func.isRequired,
|
onClose: PropTypes.func.isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
ActionsStep.defaultProps = {
|
||||||
|
onDelete: undefined,
|
||||||
|
};
|
||||||
|
|
||||||
export default withPopup(ActionsStep);
|
export default withPopup(ActionsStep);
|
||||||
|
|
|
@ -114,8 +114,12 @@ List.propTypes = {
|
||||||
isPersisted: PropTypes.bool.isRequired,
|
isPersisted: PropTypes.bool.isRequired,
|
||||||
cardIds: PropTypes.array.isRequired, // eslint-disable-line react/forbid-prop-types
|
cardIds: PropTypes.array.isRequired, // eslint-disable-line react/forbid-prop-types
|
||||||
onUpdate: PropTypes.func.isRequired,
|
onUpdate: PropTypes.func.isRequired,
|
||||||
onDelete: PropTypes.func.isRequired,
|
onDelete: PropTypes.func,
|
||||||
onCardCreate: PropTypes.func.isRequired,
|
onCardCreate: PropTypes.func.isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
List.defaultProps = {
|
||||||
|
onDelete: undefined,
|
||||||
|
};
|
||||||
|
|
||||||
export default List;
|
export default List;
|
||||||
|
|
|
@ -16,20 +16,20 @@ const SIZES = {
|
||||||
// TODO: move to styles
|
// TODO: move to styles
|
||||||
const STYLES = {
|
const STYLES = {
|
||||||
tiny: {
|
tiny: {
|
||||||
fontSize: '10px',
|
|
||||||
fontWeight: '400',
|
|
||||||
height: '20px',
|
|
||||||
padding: '5.5px 0px 4.5px',
|
|
||||||
width: '20px',
|
|
||||||
},
|
|
||||||
small: {
|
|
||||||
fontSize: '12px',
|
fontSize: '12px',
|
||||||
fontWeight: '400',
|
fontWeight: '400',
|
||||||
height: '24px',
|
height: '24px',
|
||||||
lineHeight: '20px',
|
lineHeight: '20px',
|
||||||
padding: '2px 0px',
|
padding: '2px 0',
|
||||||
width: '24px',
|
width: '24px',
|
||||||
},
|
},
|
||||||
|
small: {
|
||||||
|
fontSize: '12px',
|
||||||
|
fontWeight: '400',
|
||||||
|
height: '28px',
|
||||||
|
padding: '8px 0',
|
||||||
|
width: '28px',
|
||||||
|
},
|
||||||
medium: {
|
medium: {
|
||||||
fontSize: '14px',
|
fontSize: '14px',
|
||||||
fontWeight: '500',
|
fontWeight: '500',
|
||||||
|
|
|
@ -1,7 +1,12 @@
|
||||||
import { bindActionCreators } from 'redux';
|
import { bindActionCreators } from 'redux';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
|
import omit from 'lodash/omit';
|
||||||
|
|
||||||
import { makeCardIdsByListIdSelector, makeListByIdSelector } from '../selectors';
|
import {
|
||||||
|
isAnyFilterActiveForCurrentBoardSelector,
|
||||||
|
makeCardIdsByListIdSelector,
|
||||||
|
makeListByIdSelector,
|
||||||
|
} from '../selectors';
|
||||||
import { createCard, deleteList, updateList } from '../actions/entry';
|
import { createCard, deleteList, updateList } from '../actions/entry';
|
||||||
import List from '../components/List';
|
import List from '../components/List';
|
||||||
|
|
||||||
|
@ -12,6 +17,7 @@ const makeMapStateToProps = () => {
|
||||||
return (state, { id, index }) => {
|
return (state, { id, index }) => {
|
||||||
const { name, isPersisted } = listByIdSelector(state, id);
|
const { name, isPersisted } = listByIdSelector(state, id);
|
||||||
const cardIds = cardIdsByListIdSelector(state, id);
|
const cardIds = cardIdsByListIdSelector(state, id);
|
||||||
|
const isAnyFilterActive = isAnyFilterActiveForCurrentBoardSelector(state);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
id,
|
id,
|
||||||
|
@ -19,6 +25,7 @@ const makeMapStateToProps = () => {
|
||||||
name,
|
name,
|
||||||
isPersisted,
|
isPersisted,
|
||||||
cardIds,
|
cardIds,
|
||||||
|
isDeletable: !isAnyFilterActive,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
@ -33,4 +40,9 @@ const mapDispatchToProps = (dispatch, { id }) =>
|
||||||
dispatch,
|
dispatch,
|
||||||
);
|
);
|
||||||
|
|
||||||
export default connect(makeMapStateToProps, mapDispatchToProps)(List);
|
const mergeProps = (stateProps, dispatchProps) => ({
|
||||||
|
...omit(stateProps, 'isDeletable'),
|
||||||
|
...(stateProps.isDeletable ? dispatchProps : omit(dispatchProps, 'onDelete')),
|
||||||
|
});
|
||||||
|
|
||||||
|
export default connect(makeMapStateToProps, mapDispatchToProps, mergeProps)(List);
|
||||||
|
|
|
@ -5,10 +5,10 @@ import { logoutService } from './login';
|
||||||
import { closeModalService } from './modal';
|
import { closeModalService } from './modal';
|
||||||
import { deleteNotificationsRequest, fetchUsersRequest } from '../requests';
|
import { deleteNotificationsRequest, fetchUsersRequest } from '../requests';
|
||||||
import {
|
import {
|
||||||
attachmentWithIdExistsSelector,
|
|
||||||
currentModalSelector,
|
currentModalSelector,
|
||||||
currentUserIdSelector,
|
currentUserIdSelector,
|
||||||
currentUserSelector,
|
currentUserSelector,
|
||||||
|
isAttachmentWithIdExistsSelector,
|
||||||
pathSelector,
|
pathSelector,
|
||||||
} from '../../../selectors';
|
} from '../../../selectors';
|
||||||
import {
|
import {
|
||||||
|
@ -210,9 +210,9 @@ export function* deleteTaskReceivedService(task) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function* createAttachmentReceivedService(attachment, requestId) {
|
export function* createAttachmentReceivedService(attachment, requestId) {
|
||||||
const exists = yield select(attachmentWithIdExistsSelector, requestId);
|
const isExists = yield select(isAttachmentWithIdExistsSelector, requestId);
|
||||||
|
|
||||||
if (!exists) {
|
if (!isExists) {
|
||||||
yield put(createAttachmentReceived(attachment));
|
yield put(createAttachmentReceived(attachment));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,27 @@
|
||||||
import { createSelector } from 'redux-orm';
|
import { createSelector } from 'redux-orm';
|
||||||
|
|
||||||
import orm from '../orm';
|
import orm from '../orm';
|
||||||
|
import { pathSelector } from './path';
|
||||||
|
|
||||||
// eslint-disable-next-line import/prefer-default-export
|
export const isAnyFilterActiveForCurrentBoardSelector = createSelector(
|
||||||
export const attachmentWithIdExistsSelector = () =>
|
orm,
|
||||||
|
(state) => pathSelector(state).boardId,
|
||||||
|
({ Board }, id) => {
|
||||||
|
if (!id) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const boardModel = Board.withId(id);
|
||||||
|
|
||||||
|
if (!boardModel) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return boardModel.filterUsers.exists() || boardModel.filterLabels.exists();
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
export const isAttachmentWithIdExistsSelector = () =>
|
||||||
createSelector(
|
createSelector(
|
||||||
orm,
|
orm,
|
||||||
(_, id) => id,
|
(_, id) => id,
|
||||||
|
|
|
@ -4,9 +4,9 @@ const knex = require('knex')(config); // eslint-disable-line import/order
|
||||||
|
|
||||||
(async () => {
|
(async () => {
|
||||||
try {
|
try {
|
||||||
const exists = await knex.schema.hasTable(config.migrations.tableName);
|
const isExists = await knex.schema.hasTable(config.migrations.tableName);
|
||||||
|
|
||||||
if (!exists) {
|
if (!isExists) {
|
||||||
await knex.migrate.latest();
|
await knex.migrate.latest();
|
||||||
await knex.seed.run();
|
await knex.seed.run();
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue