1
0
Fork 0
mirror of https://github.com/plankanban/planka.git synced 2025-08-07 14:35:29 +02:00

feat: Add ability to create new card at top (#1261)

Closes #1260
This commit is contained in:
Mashood ur Rehman 2025-07-21 15:10:58 +05:00 committed by GitHub
parent fdac299fc7
commit 2e0483d9e9
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 62 additions and 42 deletions

View file

@ -21,7 +21,7 @@ const FiniteContent = React.memo(() => {
const handleCardCreate = useCallback( const handleCardCreate = useCallback(
(data, autoOpen) => { (data, autoOpen) => {
dispatch(entryActions.createCardInFirstFiniteList(data, autoOpen)); dispatch(entryActions.createCardInFirstFiniteList(data, undefined, autoOpen));
}, },
[dispatch], [dispatch],
); );

View file

@ -30,6 +30,15 @@ import PlusMathIcon from '../../../assets/images/plus-math-icon.svg?react';
import styles from './List.module.scss'; import styles from './List.module.scss';
import globalStyles from '../../../styles.module.scss'; import globalStyles from '../../../styles.module.scss';
const AddCardPositions = {
TOP: 'top',
BOTTOM: 'bottom',
};
const INDEX_BY_ADD_CARD_POSITION = {
[AddCardPositions.TOP]: 0,
};
const List = React.memo(({ id, index }) => { const List = React.memo(({ id, index }) => {
const selectListById = useMemo(() => selectors.makeSelectListById(), []); const selectListById = useMemo(() => selectors.makeSelectListById(), []);
@ -59,16 +68,18 @@ const List = React.memo(({ id, index }) => {
const dispatch = useDispatch(); const dispatch = useDispatch();
const [t] = useTranslation(); const [t] = useTranslation();
const [isEditNameOpened, setIsEditNameOpened] = useState(false); const [isEditNameOpened, setIsEditNameOpened] = useState(false);
const [isAddCardOpened, setIsAddCardOpened] = useState(false); const [addCardPosition, setAddCardPosition] = useState(null);
const wrapperRef = useRef(null); const wrapperRef = useRef(null);
const cardsWrapperRef = useRef(null); const cardsWrapperRef = useRef(null);
const handleCardCreate = useCallback( const handleCardCreate = useCallback(
(data, autoOpen) => { (data, autoOpen) => {
dispatch(entryActions.createCard(id, data, autoOpen)); dispatch(
entryActions.createCard(id, data, INDEX_BY_ADD_CARD_POSITION[addCardPosition], autoOpen),
);
}, },
[id, dispatch], [id, dispatch, addCardPosition],
); );
const handleHeaderClick = useCallback(() => { const handleHeaderClick = useCallback(() => {
@ -78,15 +89,15 @@ const List = React.memo(({ id, index }) => {
}, [list.isPersisted, canEdit]); }, [list.isPersisted, canEdit]);
const handleAddCardClick = useCallback(() => { const handleAddCardClick = useCallback(() => {
setIsAddCardOpened(true); setAddCardPosition(AddCardPositions.BOTTOM);
}, []); }, []);
const handleAddCardClose = useCallback(() => { const handleAddCardClose = useCallback(() => {
setIsAddCardOpened(false); setAddCardPosition(null);
}, []); }, []);
const handleCardAdd = useCallback(() => { const handleCardAdd = useCallback(() => {
setIsAddCardOpened(true); setAddCardPosition(AddCardPositions.TOP);
}, []); }, []);
const handleNameEdit = useCallback(() => { const handleNameEdit = useCallback(() => {
@ -104,14 +115,26 @@ const List = React.memo(({ id, index }) => {
); );
useDidUpdate(() => { useDidUpdate(() => {
if (isAddCardOpened) { if (!addCardPosition) {
cardsWrapperRef.current.scrollTop = cardsWrapperRef.current.scrollHeight; return;
} }
}, [cardIds, isAddCardOpened]);
cardsWrapperRef.current.scrollTop =
addCardPosition === AddCardPositions.TOP ? 0 : cardsWrapperRef.current.scrollHeight;
}, [cardIds, addCardPosition]);
const ActionsPopup = usePopup(ActionsStep); const ActionsPopup = usePopup(ActionsStep);
const ArchiveCardsPopup = usePopup(ArchiveCardsStep); const ArchiveCardsPopup = usePopup(ArchiveCardsStep);
const addCardNode = canAddCard && (
<AddCard
isOpened={!!addCardPosition}
className={styles.addCard}
onCreate={handleCardCreate}
onClose={handleAddCardClose}
/>
);
const cardsNode = ( const cardsNode = (
<Droppable <Droppable
droppableId={`list:${id}`} droppableId={`list:${id}`}
@ -122,18 +145,12 @@ const List = React.memo(({ id, index }) => {
// eslint-disable-next-line react/jsx-props-no-spreading // eslint-disable-next-line react/jsx-props-no-spreading
<div {...droppableProps} ref={innerRef}> <div {...droppableProps} ref={innerRef}>
<div className={styles.cards}> <div className={styles.cards}>
{addCardPosition === AddCardPositions.TOP && addCardNode}
{cardIds.map((cardId, cardIndex) => ( {cardIds.map((cardId, cardIndex) => (
<DraggableCard key={cardId} id={cardId} index={cardIndex} className={styles.card} /> <DraggableCard key={cardId} id={cardId} index={cardIndex} className={styles.card} />
))} ))}
{placeholder} {placeholder}
{canAddCard && ( {addCardPosition === AddCardPositions.BOTTOM && addCardNode}
<AddCard
isOpened={isAddCardOpened}
className={styles.addCard}
onCreate={handleCardCreate}
onClose={handleAddCardClose}
/>
)}
</div> </div>
</div> </div>
)} )}
@ -213,7 +230,7 @@ const List = React.memo(({ id, index }) => {
<div ref={cardsWrapperRef} className={styles.cardsInnerWrapper}> <div ref={cardsWrapperRef} className={styles.cardsInnerWrapper}>
<div className={styles.cardsOuterWrapper}>{cardsNode}</div> <div className={styles.cardsOuterWrapper}>{cardsNode}</div>
</div> </div>
{!isAddCardOpened && canAddCard && ( {!addCardPosition && canAddCard && (
<button <button
type="button" type="button"
disabled={!list.isPersisted} disabled={!list.isPersisted}

View file

@ -173,8 +173,8 @@ export default {
CARDS_IN_CURRENT_LIST_FETCH: `${PREFIX}/CARDS_IN_CURRENT_LIST_FETCH`, CARDS_IN_CURRENT_LIST_FETCH: `${PREFIX}/CARDS_IN_CURRENT_LIST_FETCH`,
CARDS_UPDATE_HANDLE: `${PREFIX}/CARDS_UPDATE_HANDLE`, CARDS_UPDATE_HANDLE: `${PREFIX}/CARDS_UPDATE_HANDLE`,
CARD_CREATE: `${PREFIX}/CARD_CREATE`, CARD_CREATE: `${PREFIX}/CARD_CREATE`,
CARD_IN_CURRENT_LIST_CREATE: `${PREFIX}/CARD_IN_CURRENT_LIST_CREATE`,
CARD_IN_FIRST_FINITE_LIST_CREATE: `${PREFIX}/CARD_IN_FIRST_FINITE_LIST_CREATE`, CARD_IN_FIRST_FINITE_LIST_CREATE: `${PREFIX}/CARD_IN_FIRST_FINITE_LIST_CREATE`,
CARD_IN_CURRENT_LIST_CREATE: `${PREFIX}/CARD_IN_CURRENT_LIST_CREATE`,
CARD_CREATE_HANDLE: `${PREFIX}/CARD_CREATE_HANDLE`, CARD_CREATE_HANDLE: `${PREFIX}/CARD_CREATE_HANDLE`,
CARD_UPDATE: `${PREFIX}/CARD_UPDATE`, CARD_UPDATE: `${PREFIX}/CARD_UPDATE`,
CURRENT_CARD_UPDATE: `${PREFIX}/CURRENT_CARD_UPDATE`, CURRENT_CARD_UPDATE: `${PREFIX}/CURRENT_CARD_UPDATE`,

View file

@ -18,25 +18,27 @@ const handleCardsUpdate = (cards, activities) => ({
}, },
}); });
const createCard = (listId, data, autoOpen) => ({ const createCard = (listId, data, index, autoOpen = false) => ({
type: EntryActionTypes.CARD_CREATE, type: EntryActionTypes.CARD_CREATE,
payload: { payload: {
listId, listId,
data, data,
index,
autoOpen, autoOpen,
}, },
}); });
const createCardInCurrentList = (data, autoOpen) => ({ const createCardInFirstFiniteList = (data, index = 0, autoOpen = false) => ({
type: EntryActionTypes.CARD_IN_CURRENT_LIST_CREATE, type: EntryActionTypes.CARD_IN_FIRST_FINITE_LIST_CREATE,
payload: { payload: {
data, data,
index,
autoOpen, autoOpen,
}, },
}); });
const createCardInFirstFiniteList = (data, autoOpen) => ({ const createCardInCurrentList = (data, autoOpen = false) => ({
type: EntryActionTypes.CARD_IN_FIRST_FINITE_LIST_CREATE, type: EntryActionTypes.CARD_IN_CURRENT_LIST_CREATE,
payload: { payload: {
data, data,
autoOpen, autoOpen,
@ -178,8 +180,8 @@ export default {
fetchCardsInCurrentList, fetchCardsInCurrentList,
handleCardsUpdate, handleCardsUpdate,
createCard, createCard,
createCardInCurrentList,
createCardInFirstFiniteList, createCardInFirstFiniteList,
createCardInCurrentList,
handleCardCreate, handleCardCreate,
updateCard, updateCard,
updateCurrentCard, updateCurrentCard,

View file

@ -113,7 +113,7 @@ export function* handleCardsUpdate(cards, activities) {
yield put(actions.handleCardsUpdate(cards, activities)); yield put(actions.handleCardsUpdate(cards, activities));
} }
export function* createCard(listId, data, autoOpen) { export function* createCard(listId, data, index, autoOpen) {
const localId = yield call(createLocalId); const localId = yield call(createLocalId);
const list = yield select(selectors.selectListById, listId); const list = yield select(selectors.selectListById, listId);
@ -127,7 +127,7 @@ export function* createCard(listId, data, autoOpen) {
}; };
if (isListFinite(list)) { if (isListFinite(list)) {
nextData.position = yield select(selectors.selectNextCardPosition, listId); nextData.position = yield select(selectors.selectNextCardPosition, listId, index);
} }
yield put( yield put(
@ -167,16 +167,16 @@ export function* createCard(listId, data, autoOpen) {
} }
} }
export function* createCardInFirstFiniteList(data, index, autoOpen) {
const firstFiniteListId = yield select(selectors.selectFirstFiniteListId);
yield call(createCard, firstFiniteListId, data, index, autoOpen);
}
export function* createCardInCurrentList(data, autoOpen) { export function* createCardInCurrentList(data, autoOpen) {
const currentListId = yield select(selectors.selectCurrentListId); const currentListId = yield select(selectors.selectCurrentListId);
yield call(createCard, currentListId, data, autoOpen); yield call(createCard, currentListId, data, undefined, autoOpen);
}
export function* createCardInFirstFiniteList(data, autoOpen) {
const firstFiniteListId = yield select(selectors.selectFirstFiniteListId);
yield call(createCard, firstFiniteListId, data, autoOpen);
} }
export function* handleCardCreate(card) { export function* handleCardCreate(card) {
@ -596,9 +596,9 @@ export default {
fetchCardsInCurrentList, fetchCardsInCurrentList,
handleCardsUpdate, handleCardsUpdate,
createCard, createCard,
createCardInFirstFiniteList,
createCardInCurrentList, createCardInCurrentList,
handleCardCreate, handleCardCreate,
createCardInFirstFiniteList,
updateCard, updateCard,
updateCurrentCard, updateCurrentCard,
handleCardUpdate, handleCardUpdate,

View file

@ -16,15 +16,16 @@ export default function* cardsWatchers() {
takeEvery(EntryActionTypes.CARDS_UPDATE_HANDLE, ({ payload: { cards, activities } }) => takeEvery(EntryActionTypes.CARDS_UPDATE_HANDLE, ({ payload: { cards, activities } }) =>
services.handleCardsUpdate(cards, activities), services.handleCardsUpdate(cards, activities),
), ),
takeEvery(EntryActionTypes.CARD_CREATE, ({ payload: { listId, data, autoOpen } }) => takeEvery(EntryActionTypes.CARD_CREATE, ({ payload: { listId, data, index, autoOpen } }) =>
services.createCard(listId, data, autoOpen), services.createCard(listId, data, index, autoOpen),
),
takeEvery(EntryActionTypes.CARD_IN_CURRENT_LIST_CREATE, ({ payload: { data, autoOpen } }) =>
services.createCardInCurrentList(data, autoOpen),
), ),
takeEvery( takeEvery(
EntryActionTypes.CARD_IN_FIRST_FINITE_LIST_CREATE, EntryActionTypes.CARD_IN_FIRST_FINITE_LIST_CREATE,
({ payload: { data, autoOpen } }) => services.createCardInFirstFiniteList(data, autoOpen), ({ payload: { data, index, autoOpen } }) =>
services.createCardInFirstFiniteList(data, index, autoOpen),
),
takeEvery(EntryActionTypes.CARD_IN_CURRENT_LIST_CREATE, ({ payload: { data, autoOpen } }) =>
services.createCardInCurrentList(data, autoOpen),
), ),
takeEvery(EntryActionTypes.CARD_CREATE_HANDLE, ({ payload: { card } }) => takeEvery(EntryActionTypes.CARD_CREATE_HANDLE, ({ payload: { card } }) =>
services.handleCardCreate(card), services.handleCardCreate(card),