Skip to content
Snippets Groups Projects
Commit ab00a5f2 authored by Tom Robinson's avatar Tom Robinson
Browse files

Merge branch 'nested-collection-item-pickers' of github.com:metabase/metabase...

Merge branch 'nested-collection-item-pickers' of github.com:metabase/metabase into nested-collection-item-pickers
parents 69d0e085 0a689133
Branches
Tags
No related merge requests found
......@@ -4,16 +4,14 @@ import Ellipsified from "metabase/components/Ellipsified";
import Icon from "metabase/components/Icon";
import Link from "metabase/components/Link";
import colors, { normal } from "metabase/lib/colors";
import colors from "metabase/lib/colors";
const CollectionItem = ({ collection, color, iconName = "all" }) => (
<Link
to={`collection/${collection.id}`}
color={normal.grey2}
bg={colors["bg-medium"]}
className="block rounded"
className="block rounded relative text-brand-hover text-medium"
hover={{
color: colors.primary,
backgroundColor: colors["bg-medium"],
}}
p={[1, 2]}
......
......@@ -41,6 +41,54 @@ import ItemsDragLayer from "metabase/containers/dnd/ItemsDragLayer";
const ROW_HEIGHT = 72;
const PAGE_PADDING = [2, 3, 4];
const EmptyStateWrapper = ({ children }) => (
<Flex
align="center"
justify="center"
py={3}
flexDirection="column"
w={1}
h={"200px"}
className="text-medium"
>
{children}
</Flex>
);
const DashboardEmptyState = () => (
<EmptyStateWrapper>
<Box>
<Icon name="dashboard" size={32} />
</Box>
<h3>{t`Dashboards let you collect and share data in one place.`}</h3>
</EmptyStateWrapper>
);
const PulseEmptyState = () => (
<EmptyStateWrapper>
<Box>
<Icon name="pulse" size={32} />
</Box>
<h3
>{t`Pulses let you send out the latest data to your team on a schedule via email or slack.`}</h3>
</EmptyStateWrapper>
);
const QuestionEmptyState = () => (
<EmptyStateWrapper>
<Box>
<Icon name="beaker" size={32} />
</Box>
<h3>{t`Quesitons are a saved look at your data.`}</h3>
</EmptyStateWrapper>
);
const EMPTY_STATES = {
dashboard: <DashboardEmptyState />,
pulse: <PulseEmptyState />,
card: <QuestionEmptyState />,
};
import { entityListLoader } from "metabase/entities/containers/EntityListLoader";
@entityListLoader({
......@@ -104,6 +152,12 @@ class DefaultLanding extends React.Component {
const itemWidth = unpinned.length > 0 ? [1, 2 / 3] : 0;
const collectionGridSize = unpinned.length > 0 ? 1 : [1, 1 / 4];
let unpinnedItems = unpinned;
if (location.query.type) {
unpinnedItems = unpinned.filter(u => u.model === location.query.type);
}
return (
<Box>
<Box>
......@@ -229,63 +283,66 @@ class DefaultLanding extends React.Component {
</Box>
</GridItem>
<GridItem w={itemWidth}>
{unpinned.length > 0 ? (
<PinDropTarget pinIndex={null} margin={8}>
<Box>
<ItemTypeFilterBar />
<Card mt={1}>
<Box>
<ItemTypeFilterBar />
<Card mt={1} className="relative">
{unpinnedItems.length > 0 ? (
<PinDropTarget pinIndex={null} margin={8}>
<Box
mb={selected.length > 0 ? 5 : 2}
style={{
position: "relative",
height: ROW_HEIGHT * unpinned.length,
height: ROW_HEIGHT * unpinnedItems.length,
}}
>
<VirtualizedList
items={
location.query.type
? unpinned.filter(
u => u.model === location.query.type,
)
: unpinned
}
items={unpinnedItems}
rowHeight={ROW_HEIGHT}
renderItem={({ item, index }) => (
<ItemDragSource
item={item}
selection={selection}
>
<NormalItem
key={`${item.type}:${item.id}`}
<Box className="relative">
<ItemDragSource
item={item}
collection={collection}
selection={selection}
onToggleSelected={onToggleSelected}
onMove={moveItems =>
this.setState({ moveItems })
}
/>
</ItemDragSource>
>
<NormalItem
key={`${item.type}:${item.id}`}
item={item}
collection={collection}
selection={selection}
onToggleSelected={onToggleSelected}
onMove={moveItems =>
this.setState({ moveItems })
}
/>
</ItemDragSource>
</Box>
)}
/>
</Box>
</Card>
</Box>
</PinDropTarget>
) : (
<PinDropTarget pinIndex={null} hideUntilDrag margin={10}>
{({ hovered }) => (
<div
className={cx(
"m2 flex layout-centered",
hovered ? "text-brand" : "text-grey-2",
)}
>
{t`Drag here to un-pin`}
</div>
</PinDropTarget>
) : (
<Box>
{location.query.type &&
EMPTY_STATES[location.query.type]}
<PinDropTarget
pinIndex={null}
hideUntilDrag
margin={10}
>
{({ hovered }) => (
<div
className={cx(
"m2 flex layout-centered",
hovered ? "text-brand" : "text-grey-2",
)}
>
{t`Drag here to un-pin`}
</div>
)}
</PinDropTarget>
</Box>
)}
</PinDropTarget>
)}
</Card>
</Box>
</GridItem>
</Grid>
</Box>
......@@ -358,7 +415,7 @@ export const NormalItem = ({
onToggleSelected,
onMove,
}) => (
<Link to={item.getUrl()} px={2}>
<Link to={item.getUrl()}>
<EntityItem
variant="list"
showSelect={selection.size > 0}
......
......@@ -25,7 +25,7 @@ class CollectionList extends React.Component {
w,
} = this.props;
return (
<Box>
<Box className="relative">
<Grid>
{collections
.filter(c => c.id !== currentUser.personal_collection_id)
......@@ -39,7 +39,7 @@ class CollectionList extends React.Component {
</GridItem>
))}
{isRoot && (
<GridItem w={w}>
<GridItem w={w} className="relative">
<CollectionDropTarget
collection={{ id: currentUser.personal_collection_id }}
>
......@@ -76,12 +76,10 @@ class CollectionList extends React.Component {
color={normal.grey2}
hover={{ color: normal.blue }}
>
<Box p={[1, 2]}>
<Flex align="center" py={1}>
<Icon name="add" mr={1} bordered />
<h4>{t`New collection`}</h4>
</Flex>
</Box>
<Flex align="center" py={1}>
<Icon name="add" mr={1} bordered />
<h4>{t`New collection`}</h4>
</Flex>
</Link>
</GridItem>
)}
......
......@@ -52,7 +52,7 @@ const ItemTypeFilterBar = props => {
}}
color={color}
hover={{ color: colors.brand }}
className="flex-full flex align-center justify-center sm-block"
className="flex-full flex align-center justify-center sm-block text-brand-hover text-medium"
mr={[0, 2]}
key={f.filter}
py={1}
......@@ -66,7 +66,7 @@ const ItemTypeFilterBar = props => {
<h5
className="text-uppercase hide sm-show"
style={{
color: isActive ? colors.brand : colors["text-medium"],
color: isActive ? colors.brand : "inherit",
fontWeight: 900,
}}
>
......
......@@ -28,6 +28,7 @@ class Swapper extends React.Component {
onMouseEnter={() => this._onMouseEnter()}
onMouseLeave={() => this._onMouseLeave()}
className="block relative"
style={{ lineHeight: 1 }}
>
<Motion
defaultStyle={{
......@@ -39,10 +40,7 @@ class Swapper extends React.Component {
>
{({ scale }) => {
return (
<span
className=""
style={{ display: "block", transform: `scale(${scale})` }}
>
<span style={{ display: "block", transform: `scale(${scale})` }}>
{defaultElement}
</span>
);
......
......@@ -13,13 +13,17 @@ import EntityListLoader, {
entityListLoader,
} from "metabase/entities/containers/EntityListLoader";
import LoadingAndErrorWrapper from "metabase/components/LoadingAndErrorWrapper";
import Collections from "metabase/entities/collections";
const COLLECTION_ICON_COLOR = colors["text-light"];
const isRoot = collection => collection.id === "root" || collection.id == null;
@entityListLoader({ entityType: "collections" })
@entityListLoader({
entityType: "collections",
loadingAndErrorWrapper: false,
})
@connect((state, props) => ({
collectionsById: Collections.selectors.getExpandedCollectionsById(state),
}))
......@@ -98,103 +102,109 @@ export default class ItemPicker extends React.Component {
(models.size === 1 || item.model === value.model);
return (
<Box style={style} className={className}>
{searchMode ? (
<Box pb={1} mb={2} className="border-bottom flex align-center">
<input
type="search"
className="input rounded flex-full"
placeholder="Search"
autoFocus
onKeyPress={e => {
if (e.key === "Enter") {
this.setState({ searchString: e.target.value });
}
}}
/>
<Icon
name="close"
className="ml-auto pl2 text-grey-2 text-grey-4-hover cursor-pointer"
onClick={() =>
this.setState({ searchMode: null, searchString: null })
}
/>
</Box>
) : (
<Box pb={1} mb={2} className="border-bottom flex align-center">
<Breadcrumbs crumbs={crumbs} />
<Icon
name="search"
className="ml-auto pl2 text-grey-2 text-grey-4-hover cursor-pointer"
onClick={() => this.setState({ searchMode: true })}
/>
</Box>
)}
<Box className="scroll-y">
{!searchString
? allCollections.map(collection => (
<Item
item={collection}
name={collection.name}
color={COLLECTION_ICON_COLOR}
icon="all"
selected={isSelected(collection) && models.has("collection")}
canSelect={
models.has("collection") && collection.can_edit !== false
}
hasChildren={
(collection.children &&
collection.children.length > 0 &&
// exclude root since we show root's subcollections alongside it
!isRoot(collection)) ||
modelsIncludeNonCollections
<LoadingAndErrorWrapper loading={!collectionsById} className="scroll-y">
<Box style={style} className={cx(className, "scroll-y")}>
{searchMode ? (
<Box pb={1} mb={2} className="border-bottom flex align-center">
<input
type="search"
className="input rounded flex-full"
placeholder="Search"
autoFocus
onKeyPress={e => {
if (e.key === "Enter") {
this.setState({ searchString: e.target.value });
}
onChange={collection =>
isRoot(collection)
? // "root" collection should have `null` id
onChange({ id: null, model: "collection" })
: onChange(collection)
}
onChangeParentId={parentId => this.setState({ parentId })}
/>
))
: null}
{(modelsIncludeNonCollections || searchString) && (
<EntityListLoader
entityType="search"
entityQuery={{
...(searchString
? { q: searchString }
: { collection: parentId }),
...(models.size === 1 ? { model: Array.from(models)[0] } : {}),
}}
wrapped
>
{({ list }) =>
list
.filter(
item =>
// remove collections unless we're searching
(item.model !== "collection" || !!searchString) &&
// only include desired models (TODO: ideally the endpoint would handle this)
models.has(item.model),
)
.map(item => (
<Item
item={item}
name={item.getName()}
color={item.getColor()}
icon={item.getIcon()}
selected={isSelected(item)}
canSelect
onChange={onChange}
/>
))
}
</EntityListLoader>
}}
/>
<Icon
name="close"
className="ml-auto pl2 text-grey-2 text-grey-4-hover cursor-pointer"
onClick={() =>
this.setState({ searchMode: null, searchString: null })
}
/>
</Box>
) : (
<Box pb={1} mb={2} className="border-bottom flex align-center">
<Breadcrumbs crumbs={crumbs} />
<Icon
name="search"
className="ml-auto pl2 text-grey-2 text-grey-4-hover cursor-pointer"
onClick={() => this.setState({ searchMode: true })}
/>
</Box>
)}
<Box className="scroll-y">
{!searchString
? allCollections.map(collection => (
<Item
item={collection}
name={collection.name}
color={COLLECTION_ICON_COLOR}
icon="all"
selected={
isSelected(collection) && models.has("collection")
}
canSelect={
models.has("collection") && collection.can_edit !== false
}
hasChildren={
(collection.children &&
collection.children.length > 0 &&
// exclude root since we show root's subcollections alongside it
!isRoot(collection)) ||
modelsIncludeNonCollections
}
onChange={collection =>
isRoot(collection)
? // "root" collection should have `null` id
onChange({ id: null, model: "collection" })
: onChange(collection)
}
onChangeParentId={parentId => this.setState({ parentId })}
/>
))
: null}
{(modelsIncludeNonCollections || searchString) && (
<EntityListLoader
entityType="search"
entityQuery={{
...(searchString
? { q: searchString }
: { collection: parentId }),
...(models.size === 1
? { model: Array.from(models)[0] }
: {}),
}}
wrapped
>
{({ list }) =>
list
.filter(
item =>
// remove collections unless we're searching
(item.model !== "collection" || !!searchString) &&
// only include desired models (TODO: ideally the endpoint would handle this)
models.has(item.model),
)
.map(item => (
<Item
item={item}
name={item.getName()}
color={item.getColor()}
icon={item.getIcon()}
selected={isSelected(item)}
canSelect
onChange={onChange}
/>
))
}
</EntityListLoader>
)}
</Box>
</Box>
</Box>
</LoadingAndErrorWrapper>
);
}
}
......
......@@ -53,6 +53,7 @@ export default (PickerComponent, NameComponent, type) =>
} = this.props;
return (
<PopoverWithTrigger
pinInitialAttachment // keep the popover from jumping if content height changes
triggerClasses={className}
triggerElement={
<SelectButton style={style}>
......
......@@ -336,3 +336,13 @@
.text-question {
color: var(--color-text-medium);
}
.text-light {
color: var(--color-text-light);
}
.text-medium {
color: var(--color-text-medium);
}
.text-dark {
color: var(--color-text-dark);
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment