diff --git a/frontend/src/metabase/components/ArchivedItem.jsx b/frontend/src/metabase/components/ArchivedItem.jsx index 9ea7158562bb01c01c0d6557203e7f2f3dae35a9..bd7f09e8e347cbc100a186585f8eb67a3eaa05cb 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 9fdd574086a3764dec57d4c1ed980610f41d518a..5e88036e8e79d992c880708503740b89ec6ce5ba 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 9b902a1613bb50f5c452961b3646ded0f05827e0..4a76ed419b485dc6fdc709f7f4bb93c6c48ff950 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 fd3f3ec4d8c708eee1c3078eaa74489614aab85d..426f2b4c05368d3d6bc7165ea78c99ea39adce41 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 f09d385c47403236faa477d0c6bbef2b08302b16..b78d1ed2ba0a6451b74864e2abee5b7f814b15f3 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 be376759e36acafb55e8aa3598c5d7c68018c591..02ff760143f786803e2ee0895aa4e07f7c027fa7 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 557f27a83d97f7e07e86dc075a2c13c8487c3424..06098c265cf18afd8b9edd76af0189a5048ad08b 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 34e4a4ee1297ecb368a34bef79bb8af7f236dd3b..510ce94e3449cc467f2db2e05450a7bdc6df93b0 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} /> );