diff --git a/frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx b/frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx index c3859552a2fa8dddb1ce5f5f6747c1e49f74c896..c306b66c19fb0e0f3bea3f54a890a94e2199f618 100644 --- a/frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx +++ b/frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx @@ -9,7 +9,6 @@ import AdminPaneLayout from "metabase/components/AdminPaneLayout.jsx"; import MetabaseSettings from "metabase/lib/settings"; import MetabaseUtils from "metabase/lib/utils"; import Modal from "metabase/components/Modal.jsx"; -import ModalContent, { ModalFooter } from "metabase/components/ModalContent.jsx"; import PasswordReveal from "metabase/components/PasswordReveal.jsx"; import UserAvatar from "metabase/components/UserAvatar.jsx"; import Icon from "metabase/components/Icon.jsx"; diff --git a/frontend/src/metabase/admin/permissions/containers/CollectionsPermissionsApp.jsx b/frontend/src/metabase/admin/permissions/containers/CollectionsPermissionsApp.jsx index 8d780bac8305eccc0ba078300dbb927aa4b6e9fa..7e182b4de13545392ae959fe239ba70bbae08d33 100644 --- a/frontend/src/metabase/admin/permissions/containers/CollectionsPermissionsApp.jsx +++ b/frontend/src/metabase/admin/permissions/containers/CollectionsPermissionsApp.jsx @@ -7,7 +7,7 @@ import PermissionsApp from "./PermissionsApp.jsx"; import { CollectionsApi } from "metabase/services"; import { getCollectionsPermissionsGrid, getIsDirty, getSaveError, getDiff } from "../selectors"; -import { updatePermission, savePermissions, loadPermissions, loadCollections } from "../permissions"; +import { updatePermission, savePermissions, loadCollections } from "../permissions"; import { goBack, push } from "react-router-redux"; const mapStateToProps = (state, props) => { diff --git a/frontend/src/metabase/components/CreateDashboardModal.jsx b/frontend/src/metabase/components/CreateDashboardModal.jsx index 745b0e35fe8fb6e675ae746a4485e0a569a17d1b..e39425df144470eefa1301fc06f55246d34c542f 100644 --- a/frontend/src/metabase/components/CreateDashboardModal.jsx +++ b/frontend/src/metabase/components/CreateDashboardModal.jsx @@ -4,8 +4,6 @@ import FormField from "metabase/components/FormField.jsx"; import ModalContent from "metabase/components/ModalContent.jsx"; import Button from "metabase/components/Button.jsx"; -import cx from "classnames"; - export default class CreateDashboardModal extends Component { constructor(props, context) { super(props, context); @@ -78,6 +76,7 @@ export default class CreateDashboardModal extends Component { id="CreateDashboardModal" title="Create dashboard" footer={[ + formError, <Button onClick={this.props.onClose}>Cancel</Button>, <Button primary={formReady} disabled={!formReady} onClick={this.createNewDash}>Create</Button> ]} @@ -88,15 +87,16 @@ export default class CreateDashboardModal extends Component { <FormField displayName="Name" fieldName="name" - errors={this.state.errors}> - <input className="Form-input - full" name="name" placeholder="What is the name of your dashboard?" value={this.state.name} onChange={this.setName} autoFocus /> + errors={this.state.errors} + > + <input className="Form-input full" name="name" placeholder="What is the name of your dashboard?" value={this.state.name} onChange={this.setName} autoFocus /> </FormField> <FormField displayName="Description" fieldName="description" - errors={this.state.errors}> + errors={this.state.errors} + > <input className="Form-input full" name="description" placeholder="It's optional but oh, so helpful" value={this.state.description} onChange={this.setDescription} /> </FormField> </div> diff --git a/frontend/src/metabase/components/Modal.jsx b/frontend/src/metabase/components/Modal.jsx index 0a713c031d08f7190b64134b0e0601d328cff0e9..b9f06220e312a1a044fa44e1c0680c79afee8e72 100644 --- a/frontend/src/metabase/components/Modal.jsx +++ b/frontend/src/metabase/components/Modal.jsx @@ -3,7 +3,7 @@ import ReactDOM from "react-dom"; import cx from "classnames"; import ReactCSSTransitionGroup from "react-addons-css-transition-group"; -import { Motion, spring, presets } from "react-motion"; +import { Motion, spring } from "react-motion"; import OnClickOutsideWrapper from "./OnClickOutsideWrapper.jsx"; import ModalContent from "./ModalContent"; diff --git a/frontend/src/metabase/containers/UndoListing.jsx b/frontend/src/metabase/containers/UndoListing.jsx index 65e1723b40e89f42cd34e86892b04f062633425b..8d1e7785a39cb51b33b74d790bdb01d20421a02a 100644 --- a/frontend/src/metabase/containers/UndoListing.jsx +++ b/frontend/src/metabase/containers/UndoListing.jsx @@ -1,6 +1,5 @@ /* eslint "react/prop-types": "warn" */ import React, { Component, PropTypes } from "react"; -import { Link } from "react-router"; import { connect } from "react-redux"; import S from "./UndoListing.css"; diff --git a/frontend/src/metabase/pulse/components/CardPicker.jsx b/frontend/src/metabase/pulse/components/CardPicker.jsx index c793c7dc328ee7a72fb30978f6bae973404aa254..9a0069980853c17f5fe10fbcd283f4eea6af0f9d 100644 --- a/frontend/src/metabase/pulse/components/CardPicker.jsx +++ b/frontend/src/metabase/pulse/components/CardPicker.jsx @@ -182,7 +182,16 @@ const CollectionListItem = ({ collection, onClick }) => <Icon name="chevronright" className="flex-align-right text-grey-2" /> </li> +CollectionListItem.propTypes = { + collection: PropTypes.object.isRequired, + onClick: PropTypes.func.isRequired +}; + const CollectionList = ({ children }) => <ul className="List text-brand"> {children} </ul> + +CollectionList.propTypes = { + children: PropTypes.array.isRequired +}; diff --git a/frontend/src/metabase/questions/components/ArchivedItem.jsx b/frontend/src/metabase/questions/components/ArchivedItem.jsx index 7de7901ef3f4982f064ea2e10093cfafb62ca7c0..cc8954514ebb172b5837c76dde87b66b2bc67af7 100644 --- a/frontend/src/metabase/questions/components/ArchivedItem.jsx +++ b/frontend/src/metabase/questions/components/ArchivedItem.jsx @@ -5,7 +5,7 @@ import React, { PropTypes } from "react"; import Icon from "metabase/components/Icon"; import Tooltip from "metabase/components/Tooltip"; -const ArchivedItem = ({ name, type, icon, color = '#DEEAF1', isAdmin, onUnarchive }) => +const ArchivedItem = ({ name, type, icon, color = '#DEEAF1', isAdmin = false, onUnarchive }) => <div className="flex align-center p2 hover-parent hover--visibility border-bottom bg-grey-0-hover"> <Icon name={icon} @@ -30,6 +30,7 @@ ArchivedItem.propTypes = { type: PropTypes.string.isRequired, icon: PropTypes.string.isRequired, color: PropTypes.string, + isAdmin: PropTypes.bool, onUnarchive: PropTypes.func.isRequired } diff --git a/frontend/src/metabase/questions/components/CollectionButtons.jsx b/frontend/src/metabase/questions/components/CollectionButtons.jsx index e53a1efda919b0cfb6b88d3c7b975196d4afc82c..8322680511553cc21122895658e3bb1941a64633 100644 --- a/frontend/src/metabase/questions/components/CollectionButtons.jsx +++ b/frontend/src/metabase/questions/components/CollectionButtons.jsx @@ -7,11 +7,11 @@ import ArchiveCollectionWidget from "../containers/ArchiveCollectionWidget"; const COLLECTION_ICON_SIZE = 64; -const CollectionButtons = ({ collections, isAdmin }) => +const CollectionButtons = ({ collections, isAdmin, push }) => <ol className=""> { collections - .map(collection => <CollectionButton {...collection} isAdmin={isAdmin} />) - .concat(isAdmin ? [<NewCollectionButton />] : []) + .map(collection => <CollectionButton {...collection} push={push} isAdmin={isAdmin} />) + .concat(isAdmin ? [<NewCollectionButton push={push} />] : []) .map((element, index) => <li key={index} className="inline-block pr2 pb2" style={{ width: "25%" }}> {element} @@ -20,13 +20,11 @@ const CollectionButtons = ({ collections, isAdmin }) => } </ol> -const NewCollectionButton = () => - <Link +const NewCollectionButton = ({ push }) => + <div className="relative block p4 hover-parent text-centered text-brand-hover bg-grey-0 bg-light-blue-hover no-decoration" - style={{ - borderRadius: 10 - }} - to="/collections/create" + style={{ borderRadius: 10 }} + onClick={() => push(`/collections/create`)} > <div> <div @@ -46,15 +44,13 @@ const NewCollectionButton = () => </div> </div> <h3 className="text-brand">New collection</h3> - </Link> + </div> -const CollectionButton = ({ id, name, color, slug, isAdmin }) => - <Link +const CollectionButton = ({ id, name, color, slug, isAdmin, push }) => + <div className="relative block p4 hover-parent hover--visibility text-centered text-brand-hover bg-grey-0 bg-light-blue-hover no-decoration" - style={{ - borderRadius: 10 - }} - to={`/questions/collections/${slug}`} + style={{ borderRadius: 10 }} + onClick={() => push(`/questions/collections/${slug}`)} > { isAdmin && <div className="absolute top right mt2 mr2 hover-child"> @@ -73,6 +69,6 @@ const CollectionButton = ({ id, name, color, slug, isAdmin }) => style={{ color }} /> <h3>{ name }</h3> - </Link> + </div> export default CollectionButtons; diff --git a/frontend/src/metabase/questions/components/Item.jsx b/frontend/src/metabase/questions/components/Item.jsx index cf48494f6b499e634b039080615fafe9aa9928d2..b8f5003d4fd7c20809de6f5504c96cc032442fd3 100644 --- a/frontend/src/metabase/questions/components/Item.jsx +++ b/frontend/src/metabase/questions/components/Item.jsx @@ -100,11 +100,14 @@ const Item = ({ </div> Item.propTypes = { + entity: PropTypes.object.isRequired, id: PropTypes.number.isRequired, name: PropTypes.string.isRequired, created: PropTypes.string.isRequired, description: PropTypes.string, by: PropTypes.string.isRequired, + labels: PropTypes.array.isRequired, + collection: PropTypes.object, selected: PropTypes.bool.isRequired, favorite: PropTypes.bool.isRequired, archived: PropTypes.bool.isRequired, @@ -112,6 +115,8 @@ Item.propTypes = { setItemSelected: PropTypes.func.isRequired, setFavorited: PropTypes.func.isRequired, setArchived: PropTypes.func.isRequired, + onEntityClick: PropTypes.func, + showCollectionName: PropTypes.bool, }; const ItemBody = pure(({ entity, id, name, description, labels, favorite, collection, setFavorited, onEntityClick }) => diff --git a/frontend/src/metabase/questions/components/MoveToCollection.jsx b/frontend/src/metabase/questions/components/MoveToCollection.jsx deleted file mode 100644 index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..0000000000000000000000000000000000000000 diff --git a/frontend/src/metabase/questions/components/NewCollectionButton.jsx b/frontend/src/metabase/questions/components/NewCollectionButton.jsx deleted file mode 100644 index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..0000000000000000000000000000000000000000 diff --git a/frontend/src/metabase/questions/components/Sidebar.css b/frontend/src/metabase/questions/components/Sidebar.css deleted file mode 100644 index 91fa7d24da2b986d87c41fbdd85355833fcf51ba..0000000000000000000000000000000000000000 --- a/frontend/src/metabase/questions/components/Sidebar.css +++ /dev/null @@ -1,92 +0,0 @@ -:root { - --item-padding: 45px; -} - -:local(.sidebar-padding) { - padding-left: var(--item-padding); - padding-right: var(--item-padding); -} - -:local(.sidebar-margin) { - margin-left: var(--item-padding); - margin-right: var(--item-padding); -} - -:local(.sidebar) { - composes: py2 from "style"; - width: 345px; - background-color: rgb(248, 252, 253); - border-right: 1px solid rgb(223, 238, 245); - color: #606E7B; -} - -:local(.sidebar) a { - text-decoration: none; -} - -:local(.item), -:local(.sectionTitle) { - composes: flex align-center from "style"; - composes: py2 from "style"; - composes: sidebar-padding; -} - -:local(.item) { - composes: transition-color from "style"; - composes: transition-background from "style"; - font-size: 1em; - color: #CFE4F5; -} - -:local(.item) :local(.icon) { - line-height: 1em; -} - -:local(.sectionTitle) { - composes: my1 from "style"; - composes: text-bold from "style"; - font-size: 16px; -} - - -:local(.item.selected), -:local(.item.selected) :local(.icon), -:local(.sectionTitle.selected), -:local(.item):hover, -:local(.item):hover :local(.icon), -:local(.sectionTitle):hover { - background-color: #E3F0F9; - color: #2D86D4; -} - -:local(.divider) { - composes: my2 from "style"; - composes: border-bottom from "style"; - composes: sidebar-margin; -} - -:local(.name) { - composes: ml2 text-bold from "style"; - color: #9CAEBE; - text-overflow: ellipsis; - white-space: nowrap; - overflow-x: hidden; -} - -:local(.item):hover :local(.name), -:local(.item.selected) :local(.name) { - color: #2D86D4; -} - -:local(.icon) { - composes: flex-no-shrink from "style"; -} - - -:local(.noLabelsMessage) { - composes: relative from "style"; - composes: text-centered from "style"; - composes: p2 my3 from "style"; - composes: text-brand-light from "style"; - composes: sidebar-margin; -} diff --git a/frontend/src/metabase/questions/components/Sidebar.jsx b/frontend/src/metabase/questions/components/Sidebar.jsx deleted file mode 100644 index d70b60b752d4003c892824817584c4414e240de6..0000000000000000000000000000000000000000 --- a/frontend/src/metabase/questions/components/Sidebar.jsx +++ /dev/null @@ -1,76 +0,0 @@ -/* eslint "react/prop-types": "warn" */ -import React, { PropTypes } from "react"; -import { Link } from "react-router"; -import S from "./Sidebar.css"; - -import Icon from "metabase/components/Icon.jsx"; -import LabelIcon from "metabase/components/LabelIcon.jsx"; -import LoadingAndErrorWrapper from "metabase/components/LoadingAndErrorWrapper.jsx"; - -import cx from 'classnames'; -import pure from "recompose/pure"; - -const Sidebar = ({ sections, labels, labelsLoading, labelsError, style, className }) => - <div className={cx(S.sidebar, className)} style={style}> - <ul> - {sections.map(section => - <QuestionSidebarItem key={section.id} href={"/questions/" + section.id} {...section} /> - )} - <QuestionSidebarSectionTitle name="Labels" href="/questions/edit/labels" /> - </ul> - <LoadingAndErrorWrapper loading={labelsLoading} error={labelsError} noBackground noWrapper> - { () => labels.length > 0 ? // eslint-disable-line - <ul> - { labels.map(label => - <QuestionSidebarItem key={label.id} href={"/questions/label/"+label.slug} {...label} /> - )} - </ul> - : - <div className={S.noLabelsMessage}> - <div> - <Icon name="label" /> - </div> - Create labels to group and manage questions. - </div> - } - </LoadingAndErrorWrapper> - <ul> - <li className={S.divider} /> - <QuestionSidebarItem name="Archive" href="/questions/archived" icon="archive" /> - </ul> - </div> - -Sidebar.propTypes = { - className: PropTypes.string, - style: PropTypes.object, - sections: PropTypes.array.isRequired, - labels: PropTypes.array.isRequired, - labelsLoading: PropTypes.bool.isRequired, - labelsError: PropTypes.any, -}; - -const QuestionSidebarSectionTitle = ({ name, href }) => - <li> - <Link to={href} className={S.sectionTitle} activeClassName={S.selected}>{name}</Link> - </li> - -QuestionSidebarSectionTitle.propTypes = { - name: PropTypes.string.isRequired, - href: PropTypes.string.isRequired, -}; - -const QuestionSidebarItem = ({ name, icon, href }) => - <li> - <Link to={href} className={S.item} activeClassName={S.selected}> - <LabelIcon className={S.icon} icon={icon}/> - <span className={S.name}>{name}</span> - </Link> - </li> - -QuestionSidebarItem.propTypes = { - name: PropTypes.string.isRequired, - icon: PropTypes.string.isRequired, - href: PropTypes.string.isRequired, -}; - -export default pure(Sidebar); diff --git a/frontend/src/metabase/questions/containers/Archive.jsx b/frontend/src/metabase/questions/containers/Archive.jsx index d820b9b6aedb39006196d0ed85c263db33971065..595b0d1a2b777cae51ed8495e5e4c8d03f81ba19 100644 --- a/frontend/src/metabase/questions/containers/Archive.jsx +++ b/frontend/src/metabase/questions/containers/Archive.jsx @@ -52,12 +52,12 @@ export default class Archive extends Component { <div> { items.map(item => item.type === "collection" ? - <ArchivedItem name={item.name} type="collection" icon="collection" color={item.color} isAdmin={isAdmin} onUnarchive={async () => { + <ArchivedItem key={item.type + item.id} name={item.name} type="collection" icon="collection" color={item.color} isAdmin={isAdmin} onUnarchive={async () => { await this.props.setCollectionArchived(item.id, false); this.loadEntities() }} /> : item.type === "card" ? - <ArchivedItem name={item.name} type="card" icon={visualizations.get(item.display).iconName} isAdmin={isAdmin} onUnarchive={async () => { + <ArchivedItem key={item.type + item.id} name={item.name} type="card" icon={visualizations.get(item.display).iconName} isAdmin={isAdmin} onUnarchive={async () => { await this.props.setArchived(item.id, false, true); this.loadEntities(); }} /> diff --git a/frontend/src/metabase/questions/containers/EntityItem.jsx b/frontend/src/metabase/questions/containers/EntityItem.jsx index bf216bbac6ce1af7a641a53554b5dffba114e67c..42882525c8b39a4ed1e948b8e3ef6e9e22cf5c07 100644 --- a/frontend/src/metabase/questions/containers/EntityItem.jsx +++ b/frontend/src/metabase/questions/containers/EntityItem.jsx @@ -30,6 +30,10 @@ export default class EntityItem extends Component { setItemSelected: PropTypes.func.isRequired, setFavorited: PropTypes.func.isRequired, setArchived: PropTypes.func.isRequired, + editable: PropTypes.bool, + showCollectionName: PropTypes.bool, + onEntityClick: PropTypes.func, + onMove: PropTypes.func, }; render() { diff --git a/frontend/src/metabase/questions/containers/EntityList.jsx b/frontend/src/metabase/questions/containers/EntityList.jsx index ea4986b7726c3187f05fff36aebe1680914ef3fb..2866d636930e00bb1efc226af8b81af384c32dba 100644 --- a/frontend/src/metabase/questions/containers/EntityList.jsx +++ b/frontend/src/metabase/questions/containers/EntityList.jsx @@ -120,14 +120,17 @@ export default class EntityList extends Component { setItemSelected: PropTypes.func.isRequired, setAllSelected: PropTypes.func.isRequired, setArchived: PropTypes.func.isRequired, - loadEntities: PropTypes.func.isRequired, - onChangeSection: PropTypes.func, - showSearchWidget: PropTypes.bool, - showCollectionName: PropTypes.bool, - editable: PropTypes.bool, + loadEntities: PropTypes.func.isRequired, + loadLabels: PropTypes.func.isRequired, onEntityClick: PropTypes.func, + onChangeSection: PropTypes.func, + showSearchWidget: PropTypes.bool.isRequired, + showCollectionName: PropTypes.bool.isRequired, + editable: PropTypes.bool.isRequired, + + defaultEmptyState: PropTypes.string }; static defaultProps = { diff --git a/frontend/src/metabase/questions/containers/QuestionIndex.jsx b/frontend/src/metabase/questions/containers/QuestionIndex.jsx index e639689730637c9ab2903d99703a7c2e7ddb474a..eb63c604f4604e9528f22fd7457c83e2532f8f16 100644 --- a/frontend/src/metabase/questions/containers/QuestionIndex.jsx +++ b/frontend/src/metabase/questions/containers/QuestionIndex.jsx @@ -18,7 +18,7 @@ import { search } from "../questions"; import { loadCollections } from "../collections"; import { getUserIsAdmin } from "metabase/selectors/user"; -import { replace } from "react-router-redux"; +import { replace, push } from "react-router-redux"; const mapStateToProps = (state, props) => ({ items: state.questions.entities.cards, @@ -32,6 +32,7 @@ const mapDispatchToProps = ({ search, loadCollections, replace, + push, }) @connect(mapStateToProps, mapDispatchToProps) @@ -47,7 +48,7 @@ export default class QuestionIndex extends Component { } render () { - const { collections, replace, location, isAdmin } = this.props; + const { collections, replace, push, location, isAdmin } = this.props; const { questionsExpanded } = this.state; const hasCollections = collections.length > 0; const showCollections = isAdmin || hasCollections; @@ -73,7 +74,7 @@ export default class QuestionIndex extends Component { { showCollections && <div className="mb2"> { collections.length > 0 ? - <CollectionButtons collections={collections} isAdmin={isAdmin} /> + <CollectionButtons collections={collections} isAdmin={isAdmin} push={push} /> : <CollectionEmptyState /> } diff --git a/frontend/src/metabase/questions/questions.js b/frontend/src/metabase/questions/questions.js index 23f86b1b7dbfa386a2243c8fc20385cf1668ffab..8e786c570693ab3292dcc9838c0c5b20d4cabdee 100644 --- a/frontend/src/metabase/questions/questions.js +++ b/frontend/src/metabase/questions/questions.js @@ -78,7 +78,7 @@ function createUndo(type, actions, collection) { return { type: type, count: actions.length, - message: (undo) => + message: (undo) => // eslint-disable-line react/display-name <div className="flex flex-column"> <div> { undo.count + " " + inflect(null, undo.count, "question was", "questions were") + " " + type }