diff --git a/client/package-lock.json b/client/package-lock.json index f206a64..33ccafd 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -7,6 +7,7 @@ "name": "planka-client", "dependencies": { "@juggle/resize-observer": "^3.4.0", + "@szhsin/react-menu": "^3.2.1", "classnames": "^2.3.2", "date-fns": "^2.29.3", "dequal": "^2.0.3", @@ -4277,6 +4278,19 @@ "url": "https://github.com/sponsors/gregberge" } }, + "node_modules/@szhsin/react-menu": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/@szhsin/react-menu/-/react-menu-3.2.1.tgz", + "integrity": "sha512-HEStuXNYHxLcGByiWaxFcE+ZaZ5BPQd/NtwBcxmUvHdvAaCZngmZ2ZddWuDAYZval94Kpahi1ZH0/WSv3oFnFg==", + "dependencies": { + "prop-types": "^15.7.2", + "react-transition-state": "^1.1.5" + }, + "peerDependencies": { + "react": ">=16.14.0", + "react-dom": ">=16.14.0" + } + }, "node_modules/@testing-library/dom": { "version": "8.19.0", "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-8.19.0.tgz", @@ -19768,6 +19782,15 @@ "react": "^16.8.0 || ^17.0.0 || ^18.0.0" } }, + "node_modules/react-transition-state": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/react-transition-state/-/react-transition-state-1.1.5.tgz", + "integrity": "sha512-ITY2mZqc2dWG2eitJkYNdcSFW8aKeOlkL2A/vowRrLL8GH3J6Re/SpD/BLvQzrVOTqjsP0b5S9N10vgNNzwMUQ==", + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, "node_modules/read-cache": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", @@ -26499,6 +26522,15 @@ "loader-utils": "^2.0.0" } }, + "@szhsin/react-menu": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/@szhsin/react-menu/-/react-menu-3.2.1.tgz", + "integrity": "sha512-HEStuXNYHxLcGByiWaxFcE+ZaZ5BPQd/NtwBcxmUvHdvAaCZngmZ2ZddWuDAYZval94Kpahi1ZH0/WSv3oFnFg==", + "requires": { + "prop-types": "^15.7.2", + "react-transition-state": "^1.1.5" + } + }, "@testing-library/dom": { "version": "8.19.0", "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-8.19.0.tgz", @@ -37710,6 +37742,12 @@ "use-latest": "^1.2.1" } }, + "react-transition-state": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/react-transition-state/-/react-transition-state-1.1.5.tgz", + "integrity": "sha512-ITY2mZqc2dWG2eitJkYNdcSFW8aKeOlkL2A/vowRrLL8GH3J6Re/SpD/BLvQzrVOTqjsP0b5S9N10vgNNzwMUQ==", + "requires": {} + }, "read-cache": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", diff --git a/client/package.json b/client/package.json index 82d94e1..6e790f2 100755 --- a/client/package.json +++ b/client/package.json @@ -54,6 +54,7 @@ }, "dependencies": { "@juggle/resize-observer": "^3.4.0", + "@szhsin/react-menu": "^3.2.1", "classnames": "^2.3.2", "date-fns": "^2.29.3", "dequal": "^2.0.3", diff --git a/client/src/components/Card/ActionsPopup.jsx b/client/src/components/Card/ActionsPopup.jsx index 3b2a7e0..7f5f2b9 100644 --- a/client/src/components/Card/ActionsPopup.jsx +++ b/client/src/components/Card/ActionsPopup.jsx @@ -245,4 +245,6 @@ ActionsStep.propTypes = { onClose: PropTypes.func.isRequired, }; +export { ActionsStep }; + export default withPopup(ActionsStep); diff --git a/client/src/components/Card/Card.jsx b/client/src/components/Card/Card.jsx index 4efb17a..ef066c5 100755 --- a/client/src/components/Card/Card.jsx +++ b/client/src/components/Card/Card.jsx @@ -5,17 +5,20 @@ import { Button, Icon } from 'semantic-ui-react'; import { Link } from 'react-router-dom'; import { Draggable } from 'react-beautiful-dnd'; +import BoardMembershipsStep from '../BoardMembershipsStep'; + +import usePopupMenu from '../../lib/hooks/use-popup-menu'; import { startTimer, stopTimer } from '../../utils/timer'; import Paths from '../../constants/Paths'; import Tasks from './Tasks'; import NameEdit from './NameEdit'; -import ActionsPopup from './ActionsPopup'; import User from '../User'; import Label from '../Label'; import DueDate from '../DueDate'; import Timer from '../Timer'; import styles from './Card.module.scss'; +import '@szhsin/react-menu/dist/index.css'; const Card = React.memo( ({ @@ -82,6 +85,34 @@ const Card = React.memo( nameEdit.current.open(); }, []); + const { + handleContextMenu, + element: popupElement, + setElement, + toggleMenu, + } = usePopupMenu('test', [ + { + title: 'Edit Title', + onClick: () => { + handleNameEdit(); + toggleMenu(false); + }, + }, + { + title: 'Edit Members', + onClick: () => { + setElement( + user.id)} + onUserSelect={onUserAdd} + onUserDeselect={onUserRemove} + />, + ); + }, + }, + ]); + const contentNode = ( <> {coverUrl && } @@ -150,8 +181,15 @@ const Card = React.memo( return ( {({ innerRef, draggableProps, dragHandleProps }) => ( - // eslint-disable-next-line react/jsx-props-no-spreading -
+
{isPersisted ? ( @@ -163,39 +201,15 @@ const Card = React.memo( > {contentNode} - {canEdit && ( - user.id)} - labels={allLabels} - currentLabelIds={labels.map((label) => label.id)} - onNameEdit={handleNameEdit} - onUpdate={onUpdate} - onMove={onMove} - onTransfer={onTransfer} - onDelete={onDelete} - onUserAdd={onUserAdd} - onUserRemove={onUserRemove} - onBoardFetch={onBoardFetch} - onLabelAdd={onLabelAdd} - onLabelRemove={onLabelRemove} - onLabelCreate={onLabelCreate} - onLabelUpdate={onLabelUpdate} - onLabelDelete={onLabelDelete} - > - - - )} + + + + {canEdit && popupElement} ) : ( {contentNode} diff --git a/client/src/lib/hooks/use-popup-menu.jsx b/client/src/lib/hooks/use-popup-menu.jsx new file mode 100644 index 0000000..9617d3c --- /dev/null +++ b/client/src/lib/hooks/use-popup-menu.jsx @@ -0,0 +1,68 @@ +import React, { useState, useCallback } from 'react'; +import { ControlledMenu, useMenuState } from '@szhsin/react-menu'; +import { Menu } from 'semantic-ui-react'; +import { Popup } from '../custom-ui'; + +import '@szhsin/react-menu/dist/index.css'; + +/** + * hooks for popup menu + * + * @param {string} title Title of the popup + * @param {{ + * title: string, + * onClick: () => void, + * }[]} list Menu list to shown + */ +export default function usePopupMenu(title, list) { + const [menuProps, toggleMenu] = useMenuState(); + const [anchorPoint, setAnchorPoint] = useState({ x: 0, y: 0 }); + const [customElement, setElement] = useState(null); + + const handleContextMenu = useCallback( + (e) => { + e.preventDefault(); + setAnchorPoint({ x: e.clientX, y: e.clientY }); + toggleMenu(true); + }, + [toggleMenu], + ); + + const handleClose = useCallback(() => { + toggleMenu(false); + setElement(null); + }, [toggleMenu]); + + const element = ( + + {customElement || ( + <> + {title} + + + + {list.map(({ title: itemTitle, onClick }) => ( + + {itemTitle} + + ))} + + + + )} + + ); + + return { + handleContextMenu, + toggleMenu, + element, + setElement, + }; +} diff --git a/client/src/redux-history-context.js b/client/src/redux-history-context.js old mode 100755 new mode 100644 diff --git a/client/src/styles.module.scss b/client/src/styles.module.scss index ba74822..9faef14 100644 --- a/client/src/styles.module.scss +++ b/client/src/styles.module.scss @@ -2,11 +2,15 @@ #app { background: #22252a; + .szh-menu-container { + position: fixed !important; + } + .react-datepicker { border: 0; color: #444444; font-size: 14px; - font-family: "Museo Sans", "Helvetica Neue", Arial, Helvetica, sans-serif; + font-family: 'Museo Sans', 'Helvetica Neue', Arial, Helvetica, sans-serif; font-weight: 400; width: 100%; padding-bottom: 8px; @@ -261,11 +265,28 @@ } .backgroundBlueDanube { - background: radial-gradient(circle, rgba(9, 9, 121, 1) 0%, rgba(2, 0, 36, 1) 0%, rgba(2, 29, 66, 1) 0%, rgba(2, 41, 78, 1) 0%, rgba(2, 57, 95, 1) 0%, rgba(1, 105, 144, 1) 100%, rgba(1, 151, 192, 1) 100%, rgba(0, 212, 255, 1) 100%) !important; + background: radial-gradient( + circle, + rgba(9, 9, 121, 1) 0%, + rgba(2, 0, 36, 1) 0%, + rgba(2, 29, 66, 1) 0%, + rgba(2, 41, 78, 1) 0%, + rgba(2, 57, 95, 1) 0%, + rgba(1, 105, 144, 1) 100%, + rgba(1, 151, 192, 1) 100%, + rgba(0, 212, 255, 1) 100% + ) !important; } .backgroundSundownStripe { - background: linear-gradient(22deg, rgba(31, 30, 30, 1) 0%, rgba(255, 128, 0, 1) 10%, rgba(255, 128, 0, 1) 41%, rgba(0, 0, 0, 1) 41%, rgba(0, 102, 204, 1) 89%) !important; + background: linear-gradient( + 22deg, + rgba(31, 30, 30, 1) 0%, + rgba(255, 128, 0, 1) 10%, + rgba(255, 128, 0, 1) 41%, + rgba(0, 0, 0, 1) 41%, + rgba(0, 102, 204, 1) 89% + ) !important; } .backgroundMagicalDawn { @@ -273,15 +294,27 @@ } .backgroundStrawberryDust { - background: linear-gradient(180deg, rgba(172, 79, 115, 1) 0%, rgba(254, 158, 150, 1) 66%) !important; + background: linear-gradient( + 180deg, + rgba(172, 79, 115, 1) 0%, + rgba(254, 158, 150, 1) 66% + ) !important; } .backgroundPurpleRose { - background: linear-gradient(128deg, rgba(116, 43, 62, 1) 19%, rgba(192, 71, 103, 1) 90%) !important; + background: linear-gradient( + 128deg, + rgba(116, 43, 62, 1) 19%, + rgba(192, 71, 103, 1) 90% + ) !important; } .backgroundSunScream { - background: linear-gradient(112deg, rgba(251, 221, 19, 1) 19%, rgba(255, 153, 1, 1) 62%) !important; + background: linear-gradient( + 112deg, + rgba(251, 221, 19, 1) 19%, + rgba(255, 153, 1, 1) 62% + ) !important; } .backgroundWarmRust { @@ -293,7 +326,11 @@ } .backgroundGreenEyes { - background: linear-gradient(138deg, rgba(19, 170, 82, 1) 0%, rgba(0, 102, 43, 1) 90%) !important; + background: linear-gradient( + 138deg, + rgba(19, 170, 82, 1) 0%, + rgba(0, 102, 43, 1) 90% + ) !important; } .backgroundBlueXchange { @@ -321,15 +358,28 @@ } .backgroundAlgaeGreen { - background: radial-gradient(circle farthest-corner at 10% 20%, rgba(0, 95, 104, 1) 0%, rgba(15, 156, 168, 1) 90%) !important; + background: radial-gradient( + circle farthest-corner at 10% 20%, + rgba(0, 95, 104, 1) 0%, + rgba(15, 156, 168, 1) 90% + ) !important; } .backgroundCoralReef { - background: linear-gradient(110.3deg, rgba(238, 179, 123, 1) 8.7%, rgba(216, 103, 77, 1) 47.5%, rgba(114, 43, 54, 1) 89.1%) !important; + background: linear-gradient( + 110.3deg, + rgba(238, 179, 123, 1) 8.7%, + rgba(216, 103, 77, 1) 47.5%, + rgba(114, 43, 54, 1) 89.1% + ) !important; } .backgroundSteelGrey { - background: radial-gradient(circle farthest-corner at -4% -12.9%, rgba(74, 98, 110, 1) 0.3%, rgba(30, 33, 48, 1) 90.2%) !important; + background: radial-gradient( + circle farthest-corner at -4% -12.9%, + rgba(74, 98, 110, 1) 0.3%, + rgba(30, 33, 48, 1) 90.2% + ) !important; } .backgroundHeatWaves { @@ -337,19 +387,35 @@ } .backgroundWowBlue { - background: linear-gradient(111.8deg, rgba(0, 104, 155, 1) 19.8%, rgba(0, 173, 239, 1) 92.1%) !important; + background: linear-gradient( + 111.8deg, + rgba(0, 104, 155, 1) 19.8%, + rgba(0, 173, 239, 1) 92.1% + ) !important; } .backgroundVelvetLounge { - background: radial-gradient(circle farthest-corner at 10% 20%, rgba(151, 10, 130, 1) 0%, rgba(33, 33, 33, 1) 100.2%) !important; + background: radial-gradient( + circle farthest-corner at 10% 20%, + rgba(151, 10, 130, 1) 0%, + rgba(33, 33, 33, 1) 100.2% + ) !important; } .backgroundLagoon { - background: radial-gradient(circle farthest-corner at 10% 20%, rgba(0, 107, 141, 1) 0%, rgba(0, 69, 91, 1) 90%) !important; + background: radial-gradient( + circle farthest-corner at 10% 20%, + rgba(0, 107, 141, 1) 0%, + rgba(0, 69, 91, 1) 90% + ) !important; } .backgroundPurpleRain { - background: linear-gradient(91.7deg, rgba(50, 25, 79, 1) -4.3%, rgba(122, 101, 149, 1) 101.8%) !important; + background: linear-gradient( + 91.7deg, + rgba(50, 25, 79, 1) -4.3%, + rgba(122, 101, 149, 1) 101.8% + ) !important; } .backgroundBlueSteel { @@ -357,22 +423,49 @@ } .backgroundBlueishCurve { - background: linear-gradient(171.8deg, rgba(5, 111, 146, 1) 13.5%, rgba(6, 57, 84, 1) 78.6%) !important; + background: linear-gradient( + 171.8deg, + rgba(5, 111, 146, 1) 13.5%, + rgba(6, 57, 84, 1) 78.6% + ) !important; } .backgroundPrismLight { - background: linear-gradient(111.7deg, rgba(251, 198, 6, 1) 2.4%, rgba(224, 82, 95, 1) 28.3%, rgba(194, 78, 154, 1) 46.2%, rgba(32, 173, 190, 1) 79.4%, rgba(22, 158, 95, 1) 100.2%) !important; + background: linear-gradient( + 111.7deg, + rgba(251, 198, 6, 1) 2.4%, + rgba(224, 82, 95, 1) 28.3%, + rgba(194, 78, 154, 1) 46.2%, + rgba(32, 173, 190, 1) 79.4%, + rgba(22, 158, 95, 1) 100.2% + ) !important; } .backgroundTheBow { - background: radial-gradient(circle farthest-corner at -8.9% 51.2%, rgba(255, 124, 0, 1) 0%, rgba(255, 124, 0, 1) 15.9%, rgba(255, 163, 77, 1) 15.9%, rgba(255, 163, 77, 1) 24.4%, rgba(19, 30, 37, 1) 24.5%, rgba(19, 30, 37, 1) 66%) !important; + background: radial-gradient( + circle farthest-corner at -8.9% 51.2%, + rgba(255, 124, 0, 1) 0%, + rgba(255, 124, 0, 1) 15.9%, + rgba(255, 163, 77, 1) 15.9%, + rgba(255, 163, 77, 1) 24.4%, + rgba(19, 30, 37, 1) 24.5%, + rgba(19, 30, 37, 1) 66% + ) !important; } .backgroundGreenMist { - background: linear-gradient(180.5deg, rgba(0, 128, 128, 1) 8.5%, rgba(174, 206, 100, 1) 118.2%) !important; + background: linear-gradient( + 180.5deg, + rgba(0, 128, 128, 1) 8.5%, + rgba(174, 206, 100, 1) 118.2% + ) !important; } .backgroundRedCurtain { - background: radial-gradient(circle 371px at 2.9% 14.3%, rgba(255, 0, 102, 1) 0%, rgba(80, 5, 35, 1) 100.7%) !important; + background: radial-gradient( + circle 371px at 2.9% 14.3%, + rgba(255, 0, 102, 1) 0%, + rgba(80, 5, 35, 1) 100.7% + ) !important; } } diff --git a/server/package-lock.json b/server/package-lock.json index 3e7060a..8c658bc 100644 --- a/server/package-lock.json +++ b/server/package-lock.json @@ -40,6 +40,23 @@ "node": "^12.10" } }, + "..": { + "version": "1.8.5", + "extraneous": true, + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "concurrently": "^7.3.0", + "husky": "^8.0.1", + "lint-staged": "^13.0.3" + }, + "devDependencies": { + "eslint": "^8.28.0", + "eslint-config-prettier": "^8.5.0", + "eslint-plugin-prettier": "^4.2.1", + "prettier": "^2.7.1" + } + }, "node_modules/@colors/colors": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz",