From 56df7d2709319084a70747d52ef58d57b0adb488 Mon Sep 17 00:00:00 2001 From: Kyle Doherty <kyle.l.doherty@gmail.com> Date: Tue, 5 Jun 2018 12:06:40 -0700 Subject: [PATCH] per item actions --- .../src/metabase/components/ArchivedItem.jsx | 6 +- .../src/metabase/components/BulkActionBar.jsx | 20 ++++-- frontend/src/metabase/components/CheckBox.jsx | 14 +++- .../metabase/components/CollectionLanding.jsx | 14 +++- .../src/metabase/components/EntityItem.jsx | 72 ++++++++++++++----- .../containers/CollectionItemsLoader.jsx | 2 - frontend/src/metabase/entities/questions.js | 5 ++ .../metabase/home/containers/ArchiveApp.jsx | 18 ++--- 8 files changed, 107 insertions(+), 44 deletions(-) diff --git a/frontend/src/metabase/components/ArchivedItem.jsx b/frontend/src/metabase/components/ArchivedItem.jsx index 9ea7158562b..bd7f09e8e34 100644 --- a/frontend/src/metabase/components/ArchivedItem.jsx +++ b/frontend/src/metabase/components/ArchivedItem.jsx @@ -6,8 +6,8 @@ import { t } from "c-3po"; import Icon from "metabase/components/Icon"; import Tooltip from "metabase/components/Tooltip"; import CheckBox from "metabase/components/CheckBox.jsx"; -import { Box } from "rebass" -import cx from "classnames" +import { Box } from "rebass"; +import cx from "classnames"; const ArchivedItem = ({ name, @@ -22,7 +22,7 @@ const ArchivedItem = ({ }) => ( <div className="flex align-center p2 hover-parent hover--visibility border-bottom bg-grey-0-hover"> <Box className="hover-parent hover--visibility"> - <Box className="hover-child--hiden"> + <Box className="hover-child hover-child--hiden"> <Icon name={icon} className="mr2" style={{ color: color }} size={20} /> </Box> {onToggleSelected && ( diff --git a/frontend/src/metabase/components/BulkActionBar.jsx b/frontend/src/metabase/components/BulkActionBar.jsx index 9fdd574086a..5e88036e8e7 100644 --- a/frontend/src/metabase/components/BulkActionBar.jsx +++ b/frontend/src/metabase/components/BulkActionBar.jsx @@ -1,9 +1,9 @@ -import React from "react" +import React from "react"; import { Fixed, Flex } from "rebass"; import Card from "metabase/components/Card"; import { Motion, spring } from "react-motion"; -const BulkActionBar = ({ children, showing }) => +const BulkActionBar = ({ children, showing }) => ( <Motion defaultStyle={{ opacity: 0, @@ -16,15 +16,21 @@ const BulkActionBar = ({ children, showing }) => > {({ opacity, translateY }) => ( <Fixed bottom left right> - <Card dark style={{ borderRadius: 0, opacity, - transform: `translateY(${translateY}px)`, - }}> - <Flex align='center' py={2} px={2}> + <Card + dark + style={{ + borderRadius: 0, + opacity, + transform: `translateY(${translateY}px)`, + }} + > + <Flex align="center" py={2} px={4}> {children} </Flex> </Card> </Fixed> )} </Motion> +); -export default BulkActionBar +export default BulkActionBar; diff --git a/frontend/src/metabase/components/CheckBox.jsx b/frontend/src/metabase/components/CheckBox.jsx index 9b902a1613b..4a76ed419b4 100644 --- a/frontend/src/metabase/components/CheckBox.jsx +++ b/frontend/src/metabase/components/CheckBox.jsx @@ -22,8 +22,11 @@ export default class CheckBox extends Component { onClick(e) { if (this.props.onChange) { // TODO: use a proper event object? - this.props.onChange({ target: { checked: !this.props.checked } }); - e.stopPropagation() + this.props.onChange({ + // add preventDefault so checkboxes can optionally prevent + preventDefault: () => e.preventDefault(), + target: { checked: !this.props.checked }, + }); } } @@ -39,7 +42,12 @@ export default class CheckBox extends Component { border: `2px solid ${checked ? themeColor : "#ddd"}`, }; return ( - <div className="cursor-pointer" onClick={e => { this.onClick(e)}}> + <div + className="cursor-pointer" + onClick={e => { + this.onClick(e); + }} + > <div style={checkboxStyle} className="flex align-center justify-center rounded" diff --git a/frontend/src/metabase/components/CollectionLanding.jsx b/frontend/src/metabase/components/CollectionLanding.jsx index fd3f3ec4d8c..426f2b4c053 100644 --- a/frontend/src/metabase/components/CollectionLanding.jsx +++ b/frontend/src/metabase/components/CollectionLanding.jsx @@ -145,8 +145,15 @@ class DefaultLanding extends React.Component { } render() { - const { collectionId, location, selected, onToggleSelected, selection } = this.props; + const { + collectionId, + location, + selected, + onToggleSelected, + selection, + } = this.props; + console.log(this.props); // Show the const showCollectionList = collectionId === "root"; @@ -272,7 +279,10 @@ class DefaultLanding extends React.Component { : null } selected={selection.has(item)} - onToggleSelected={() => onToggleSelected(item) } + onToggleSelected={ev => { + ev.preventDefault(); + onToggleSelected(item); + }} /> </Link> </Box> diff --git a/frontend/src/metabase/components/EntityItem.jsx b/frontend/src/metabase/components/EntityItem.jsx index f09d385c474..b78d1ed2ba0 100644 --- a/frontend/src/metabase/components/EntityItem.jsx +++ b/frontend/src/metabase/components/EntityItem.jsx @@ -1,8 +1,10 @@ import React from "react"; +import { t } from "c-3po"; +import EntityMenu from "metabase/components/EntityMenu"; import { Flex, Box, Truncate } from "rebass"; -import CheckBox from "metabase/components/CheckBox" +import CheckBox from "metabase/components/CheckBox"; import Icon from "metabase/components/Icon"; import { normal } from "metabase/lib/colors"; @@ -24,29 +26,67 @@ const IconWrapper = Flex.extend` border-radius: 6px; `; -const EntityItem = ({ name, iconName, iconColor, item, onPin, selected, onToggleSelected }) => { +const EntityItem = ({ + name, + iconName, + iconColor, + item, + onPin, + selected, + onToggleSelected, +}) => { return ( <EntityItemWrapper py={2} px={2} className="hover-parent hover--visibility"> - <IconWrapper p={1} mr={1} align="center" justify="center" className="hover-parent hover--visibility"> + <IconWrapper + p={1} + mr={1} + align="center" + justify="center" + className="hover-parent hover--visibility" + > + <CheckBox + checked={selected} + onChange={onToggleSelected} + className="hover-child" + /> <Icon name={iconName} color={iconColor} /> - <CheckBox checked={selected} onChange={onToggleSelected} className="hover-child" /> </IconWrapper> <h3> <Truncate>{name}</Truncate> </h3> - {onPin && ( - <Box - className="hover-child" - ml="auto" - onClick={e => { - e.preventDefault(); - onPin(item); - }} - > - <Icon name="pin" /> - </Box> - )} + <Flex + ml="auto" + align="center" + className="hover-child" + onClick={e => e.preventDefault()} + > + <Icon + name="staroutline" + mr={1} + onClick={() => item.setFavorited(item)} + /> + <EntityMenu + triggerIcon="ellipsis" + items={[ + { + title: t`Pin this item`, + icon: "pin", + action: () => onPin(item), + }, + { + title: t`Move this item`, + icon: "move", + action: () => onPin(item), + }, + { + title: t`Archive`, + icon: "archive", + action: () => item.setArchived(item), + }, + ]} + /> + </Flex> </EntityItemWrapper> ); }; diff --git a/frontend/src/metabase/containers/CollectionItemsLoader.jsx b/frontend/src/metabase/containers/CollectionItemsLoader.jsx index be376759e36..02ff760143f 100644 --- a/frontend/src/metabase/containers/CollectionItemsLoader.jsx +++ b/frontend/src/metabase/containers/CollectionItemsLoader.jsx @@ -3,8 +3,6 @@ import React from "react"; import EntityObjectLoader from "metabase/entities/containers/EntityObjectLoader"; import EntityListLoader from "metabase/entities/containers/EntityListLoader"; -import _ from "underscore"; - type Props = { collectionId: number, children: () => void, diff --git a/frontend/src/metabase/entities/questions.js b/frontend/src/metabase/entities/questions.js index 557f27a83d9..06098c265cf 100644 --- a/frontend/src/metabase/entities/questions.js +++ b/frontend/src/metabase/entities/questions.js @@ -20,6 +20,11 @@ const Questions = createEntity({ pin: ({ id }) => Questions.actions.update({ id, collection_position: 1 }), unpin: ({ id }) => Questions.actions.update({ id, collection_position: null }), + setFavorited: ({ id }, favorited) => + Questions.actions.updated({ + id, + favorited, + }), }, objectSelectors: { diff --git a/frontend/src/metabase/home/containers/ArchiveApp.jsx b/frontend/src/metabase/home/containers/ArchiveApp.jsx index 34e4a4ee129..510ce94e344 100644 --- a/frontend/src/metabase/home/containers/ArchiveApp.jsx +++ b/frontend/src/metabase/home/containers/ArchiveApp.jsx @@ -8,7 +8,7 @@ import HeaderWithBack from "metabase/components/HeaderWithBack"; import Card from "metabase/components/Card"; import ArchivedItem from "../../components/ArchivedItem"; import Button from "metabase/components/Button"; -import BulkActionBar from "metabase/components/BulkActionBar" +import BulkActionBar from "metabase/components/BulkActionBar"; import StackedCheckBox from "metabase/components/StackedCheckBox"; @@ -45,7 +45,7 @@ export default class ArchiveApp extends Component { <Flex align="center" mb={2} py={3}> <HeaderWithBack name={t`Archive`} /> </Flex> - <Box w={2/3}> + <Box w={2 / 3}> <Card> {list.map(item => ( <ArchivedItem @@ -69,9 +69,10 @@ export default class ArchiveApp extends Component { ))} </Card> </Box> - <BulkActionBar showing={selected.length > 0 }> + <BulkActionBar showing={selected.length > 0}> <SelectionControls {...this.props} /> <BulkActionControls {...this.props} /> + <Box ml="auto">{t`${selected.length} items selected`}</Box> </BulkActionBar> </Box> ); @@ -81,6 +82,7 @@ export default class ArchiveApp extends Component { const BulkActionControls = ({ selected, reload }) => ( <span> <Button + ml={1} medium onClick={async () => { try { @@ -100,13 +102,7 @@ const SelectionControls = ({ onSelectNone, }) => deselected.length === 0 ? ( - <span className="flex align-center"> - <StackedCheckBox checked={true} onChange={onSelectNone} /> - <div className="ml1">Select None</div> - </span> + <StackedCheckBox checked={true} onChange={onSelectNone} /> ) : ( - <span className="flex align-center"> - <StackedCheckBox checked={false} onChange={onSelectAll} /> - <div className="ml1">Select All</div> - </span> + <StackedCheckBox checked={false} onChange={onSelectAll} /> ); -- GitLab