Skip to content
Snippets Groups Projects
Commit acddaf3c authored by Allen Gilliland's avatar Allen Gilliland
Browse files

Merge pull request #1065 from metabase/fix_dash_dropdown

Fix dash dropdown
parents c65a7eaa 9b9f484a
No related branches found
No related tags found
No related merge requests found
"use strict";
import React, { Component, PropTypes } from 'react';
import _ from "underscore";
import cx from "classnames";
import OnClickOut from 'react-onclickout';
import CreateDashboardModal from "metabase/components/CreateDashboardModal.react";
import Icon from "metabase/components/Icon.react";
import Modal from "metabase/components/Modal.react";
export default class DashboardsDropdown extends Component {
constructor(props) {
super(props);
this.state = {
dropdownOpen: false,
modalOpen: false
};
_.bindAll(this, "toggleDropdown", "closeDropdown", "toggleModal", "closeModal");
}
onCreateDashboard(newDashboard) {
let { createDashboardFn } = this.props;
createDashboardFn(newDashboard).then(function() {
// close modal and add new dash to our dashboards list
this.setState({
dropdownOpen: false,
modalOpen: false
});
}.bind(this));
}
toggleModal() {
if (!this.state.modalOpen) {
// when we open our modal we always close the dropdown
this.setState({
dropdownOpen: false,
modalOpen: !this.state.modalOpen
});
}
}
closeModal() {
this.setState({ modalOpen: false });
}
toggleDropdown() {
this.setState({ dropdownOpen: !this.state.dropdownOpen });
}
closeDropdown() {
this.setState({ dropdownOpen: false });
}
renderCreateDashboardModal() {
return (
<Modal>
<CreateDashboardModal
createDashboardFn={this.onCreateDashboard.bind(this)}
closeFn={this.closeModal} />
</Modal>
);
}
render() {
let { dashboards } = this.props;
let { dropdownOpen, modalOpen } = this.state;
return (
<div>
{ modalOpen ? this.renderCreateDashboardModal() : null }
<OnClickOut onClickOut={this.closeDropdown}>
<div className={cx('NavDropdown inline-block cursor-pointer', { 'open': dropdownOpen })}>
<a className="NavDropdown-button NavItem text-white cursor-pointer p2 flex align-center" onClick={this.toggleDropdown}>
<span className="NavDropdown-button-layer">
Dashboards
<Icon className="ml1" name={'chevrondown'} width={8} height={8}></Icon>
</span>
</a>
{ dropdownOpen ?
<div className="NavDropdown-content DashboardList">
{ dashboards.length === 0 ?
<div className="NavDropdown-content-layer text-white text-centered">
<div className="p2"><span className="QuestionCircle">?</span></div>
<div className="px2 py1 text-bold">You dont have any dashboards yet.</div>
<div className="px2 pb2 text-light">Dashboards group visualizations for frequent questions in a single handy place.</div>
<div className="border-top border-light">
<a className="Dropdown-item block text-white no-decoration" href="#" onClick={this.toggleModal}>Create a new dashboard</a>
</div>
</div>
:
<ul className="NavDropdown-content-layer">
{ dashboards.map(dash =>
<li className="block">
<a className="Dropdown-item block text-white no-decoration" href={"/dash/"+dash.id} onClick={this.closeDropdown}>
<div className="flex text-bold">
{dash.name}
</div>
{ dash.description ?
<div className="mt1 text-light text-brand-light">
{dash.description}
</div>
: null }
</a>
</li>
)}
<li className="block border-top border-light">
<a className="Dropdown-item block text-white no-decoration" href="#" onClick={this.toggleModal}>Create a new dashboard</a>
</li>
</ul>
}
</div>
: null }
</div>
</OnClickOut>
</div>
);
}
}
DashboardsDropdown.propTypes = {
createDashboardFn: PropTypes.func.isRequired,
dashboards: PropTypes.array.isRequired
};
......@@ -18,7 +18,12 @@ export default class LogoIcon extends React.Component {
}
}
LogoIcon.PropTypes = {
width: PropTypes.number.isRequired,
height: PropTypes.number.isRequired
LogoIcon.defaultProps = {
height: 32,
width: 32
}
LogoIcon.propTypes = {
width: PropTypes.number,
height: PropTypes.number
}
\ No newline at end of file
"use strict";
import React, { Component, PropTypes } from 'react';
import cx from "classnames";
import DashboardsDropdown from "metabase/components/DashboardsDropdown.react";
import Icon from "metabase/components/Icon.react";
import LogoIcon from "metabase/components/LogoIcon.react";
import ProfileLink from "metabase/components/ProfileLink.react";
// TODO - this relies on props.location, which is angular's $location service
export default class Navbar extends Component {
isActive(path) {
return this.props.location.path().indexOf(path) >= 0;
}
renderAdminNav() {
const classes = "NavItem py1 px2 no-decoration";
return (
<nav className="AdminNav">
<div className="wrapper flex align-center">
<div className="NavTitle flex align-center">
<Icon name={'gear'} className="AdminGear" width={22} height={22}></Icon>
<span className="NavItem-text ml1 hide sm-show">Site Administration</span>
</div>
<ul className="sm-ml4 flex flex-full">
<li>
<a className={cx(classes, {"is--selected": this.isActive("/admin/settings")})} href="/admin/settings/">
Settings
</a>
</li>
<li>
<a className={cx(classes, {"is--selected": this.isActive("/admin/people")})} href="/admin/people/">
People
</a>
</li>
<li>
<a className={cx(classes, {"is--selected": this.isActive("/admin/metadata")})} href="/admin/metadata/">
Metadata
</a>
</li>
<li>
<a className={cx(classes, {"is--selected": this.isActive("/admin/databases")})} href="/admin/databases/">
Databases
</a>
</li>
</ul>
<ProfileLink {...this.props}></ProfileLink>
</div>
</nav>
);
}
renderAuthNav() {
return (
<nav className="py2 sm-py1 xl-py3 relative"></nav>
);
}
renderEmptyNav() {
return (
<nav className="py2 sm-py1 xl-py3 relative">
<ul className="wrapper flex align-center">
<li>
<a className="NavItem cursor-pointer flex align-center" href="/">
<LogoIcon className="text-brand my2"></LogoIcon>
</a>
</li>
</ul>
</nav>
);
}
renderMainNav() {
return (
<nav className="CheckBg CheckBg-offset sm-py2 sm-py1 xl-py3 relative bg-brand">
<ul className="wrapper flex align-center">
<li>
<a className="NavItem cursor-pointer text-white flex align-center my1" href="/">
<LogoIcon className="text-white m1"></LogoIcon>
</a>
</li>
<li>
<DashboardsDropdown {...this.props}></DashboardsDropdown>
</li>
<li className="flex-align-right">
<a className="rounded inline-block bg-white text-brand cursor-pointer p2 no-decoration" href="/q">New <span className="hide sm-show">Question</span></a>
<div className="inline-block text-white"><ProfileLink {...this.props}></ProfileLink></div>
</li>
</ul>
</nav>
);
}
render() {
let { context, user } = this.props;
if (!user) return null;
switch (context) {
case "admin": return this.renderAdminNav();
case "auth": return this.renderAuthNav();
case "none": return this.renderEmptyNav();
case "setup": return null;
default: return this.renderMainNav();
}
}
}
Navbar.propTypes = {
context: PropTypes.string.isRequired,
location: PropTypes.string.isRequired,
user: PropTypes.object
};
'use strict';
import Navbar from 'metabase/components/Navbar.react';
// Global Controllers
var MetabaseControllers = angular.module('metabase.controllers', ['metabase.services', 'metabase.navbar.directives']);
......@@ -47,35 +50,71 @@ MetabaseControllers.controller('NotFound', ['AppState', function(AppState) {
AppState.setAppContext('none');
}]);
MetabaseControllers.controller('Nav', ['$scope', '$routeParams', '$location', 'AppState', function($scope, $routeParams, $location, AppState) {
$scope.isActive = function(location) {
return $location.path().indexOf(location) >= 0;
};
MetabaseControllers.controller('Nav', ['$scope', '$routeParams', '$location', '$rootScope', 'AppState', 'Dashboard',
function($scope, $routeParams, $location, $rootScope, AppState, Dashboard) {
function refreshDashboards() {
Dashboard.list({
'filterMode': 'all'
}, function (dashes) {
$scope.dashboards = dashes;
}, function (error) {
console.log('error getting dahsboards list', error);
});
}
var setNavContext = function(context) {
switch (context) {
case "admin":
$scope.nav = 'admin';
break;
case "setup":
$scope.nav = 'setup';
break;
case "auth":
$scope.nav = 'auth';
break;
case "none":
$scope.nav = 'none';
break;
default:
$scope.nav = 'main';
function setNavContext(context) {
switch (context) {
case "admin":
$scope.context = 'admin';
break;
case "setup":
$scope.context = 'setup';
break;
case "auth":
$scope.context = 'auth';
break;
case "none":
$scope.context = 'none';
break;
default:
$scope.context = 'main';
}
}
};
$scope.$on('appstate:context-changed', function(event, newAppContext) {
setNavContext(newAppContext);
});
$scope.Navbar = Navbar;
$scope.location = $location;
// initialize our state from the current AppState model, which we expect to have resolved already
setNavContext(AppState.model.appContext);
}]);
$scope.dashboards = [];
$scope.createDashboardFn = async function(newDashboard) {
var dashboard = await Dashboard.create(newDashboard).$promise;
$rootScope.$broadcast("dashboard:create", dashboard.id);
$location.path("/dash/" + dashboard.id);
// this is important because it allows our caller to perform any of their own actions after the promis resolves
return dashboard;
};
$scope.$on('appstate:context-changed', function(event, newAppContext) {
setNavContext(newAppContext);
});
$scope.$on("dashboard:create", function(event, dashboardId) {
refreshDashboards();
});
$scope.$on("dashboard:delete", function(event, dashboardId) {
refreshDashboards();
});
$scope.$on("dashboard:update", function(event, dashboardId) {
refreshDashboards();
});
// always initialize with a fresh listing
refreshDashboards();
// initialize our state from the current AppState model, which we expect to have resolved already
setNavContext(AppState.model.appContext);
}
]);
......@@ -41,32 +41,3 @@ DashboardControllers.controller('Dashboard', ['$scope', '$rootScope', '$routePar
$scope.store = finalCreateStore(reducer, { selectedDashboard: $routeParams.dashId });
// $scope.monitor = LogMonitor;
}]);
DashboardControllers.controller('DashList', ['$scope', '$location', 'Dashboard', function($scope, $location, Dashboard) {
$scope.dashboards = [];
var refreshListing = function() {
Dashboard.list({
'filterMode': 'all'
}, function (dashes) {
$scope.dashboards = dashes;
}, function (error) {
console.log('error getting dahsboards list', error);
});
};
$scope.$on("dashboard:create", function(event, dashboardId) {
refreshListing();
});
$scope.$on("dashboard:delete", function(event, dashboardId) {
refreshListing();
});
$scope.$on("dashboard:update", function(event, dashboardId) {
refreshListing();
});
// always initialize with a fresh listing
refreshListing();
}]);
'use strict';
import CreateDashboardModal from '../components/CreateDashboardModal.react';
var DashboardDirectives = angular.module('metabase.dashboard.directives', [
'metabase.metabase.services',
]);
DashboardDirectives.directive('mbDashboardCreate', ['Dashboard', '$modal', '$location', '$rootScope',
function(Dashboard, $modal, $location, $rootScope) {
function link(scope, element, attrs) {
var openModal = function() {
$modal.open({
template: '<div class="Modal" mb-react-component="CreateDashboardModal"></div>',
controller: ['$scope', '$modalInstance',
function($scope, $modalInstance) {
$scope.CreateDashboardModal = CreateDashboardModal;
$scope.createDashboardFn = async function(newDashboard) {
var dashboard = await Dashboard.create(newDashboard).$promise;
$modalInstance.close(dashboard);
$rootScope.$broadcast("dashboard:create", dashboard.id);
$location.path("/dash/" + dashboard.id);
}
$scope.closeFn = function() {
$modalInstance.dismiss('cancel');
};
}
]
});
};
element.bind('click', openModal);
}
return {
restrict: 'A',
link: link,
scope: {
callback: '=',
dashboard: '='
}
};
}
]);
......@@ -7,7 +7,6 @@ var Dashboard = angular.module('metabase.dashboard', [
'metabase.services',
'metabase.dashboard.services',
'metabase.dashboard.controllers',
'metabase.dashboard.directives',
'metabase.card.services'
]);
......
......@@ -16,110 +16,7 @@
</head>
<body ng-controller="Metabase">
<div class="Nav" ng-controller="Nav" ng-if="user" ng-cloak>
<!-- MAIN NAV -->
<nav class="CheckBg CheckBg-offset sm-py2 sm-py1 xl-py3 relative bg-brand" ng-show="nav === 'main'">
<ul class="wrapper flex align-center">
<li>
<a class="NavItem cursor-pointer text-white flex align-center my1" href="/">
<mb-logo-icon class="text-white m1"></mb-logo-icon>
</a>
</li>
<li>
<div class="NavDropdown ml1 md-ml3" dropdown on-toggle="toggled(open)">
<a class="NavDropdown-button NavItem text-white cursor-pointer p2 flex align-center" dropdown-toggle>
<span class="NavDropdown-button-layer">
Dashboards
<mb-icon class="ml1" name="chevrondown" width="8px" height="8px"></mb-icon>
</span>
</a>
<div class="NavDropdown-content DashboardList" ng-controller="DashList">
<div class="NavDropdown-content-layer text-white text-centered" ng-if="dashboards.length === 0">
<div class="p2"><span class="QuestionCircle">?</span></div>
<div class="px2 py1 text-bold">You don’t have any dashboards yet.</div>
<div class="px2 pb2 text-light">Dashboards group visualizations for frequent questions in a single handy place.</div>
<div class="border-top border-light">
<a class="Dropdown-item block text-white no-decoration" href="#" mb-dashboard-create>Create a new dashboard</a>
</div>
</div>
<ul class="NavDropdown-content-layer" ng-if="dashboards.length > 0">
<li class="block" ng-repeat="dash in dashboards">
<a class="Dropdown-item block text-white no-decoration" href="/dash/{{dash.id}}">
<div class="flex text-bold">
{{dash.name}}
<mb-icon class="ml1 flex flex-align-right align-center" name="lock" width="12px" height="12px" ng-if="dash.public_perms === 0"></mb-icon>
</div>
<div class="mt1 text-light text-brand-light" ng-if="dash.description">
{{dash.description}}
</div>
</a>
</li>
<li class="block border-top border-light">
<a class="Dropdown-item block text-white no-decoration" href="#" mb-dashboard-create>Create a new dashboard</a>
</li>
</ul>
</div>
</div>
</li>
<li class="flex-align-right">
<a class="rounded inline-block bg-white text-brand cursor-pointer p2 no-decoration" href="/q">New <span class="hide sm-show">Question</span></a>
<div mb-profile-link class="inline-block text-white" user="user" context="nav"></div>
</li>
</ul>
</nav>
<!-- ADMIN NAV -->
<nav class="AdminNav" ng-show="nav === 'admin'">
<div class="wrapper flex align-center">
<div class="NavTitle flex align-center">
<mb-icon name="gear" class="AdminGear" width="22px" height="22px"></mb-icon>
<span class="NavItem-text ml1 hide sm-show">Site Administration</span>
</div>
<!-- admin sections -->
<ul class="sm-ml4 flex flex-full">
<li>
<a class="NavItem py1 px2 no-decoration" ng-class="{ 'is--selected' : isActive('/admin/settings')}" href="/admin/settings/">
Settings
</a>
</li>
<li>
<a class="NavItem py1 px2 no-decoration" ng-class="{ 'is--selected' : isActive('/admin/people')}" href="/admin/people/">
People
</a>
</li>
<li>
<a class="NavItem py1 px2 no-decoration" ng-class="{ 'is--selected' : isActive('/admin/metadata')}" href="/admin/metadata/">
Metadata
</a>
</li>
<li>
<a class="NavItem py1 px2 no-decoration" ng-class="{ 'is--selected' : isActive('/admin/databases')}" href="/admin/databases/">
Databases
</a>
</li>
</ul>
<!-- user profile dropdown -->
<div mb-profile-link user="user" context="nav"></div>
</div>
</nav>
<!-- AUTH NAV -->
<nav class="py2 sm-py1 xl-py3 relative" ng-show="nav === 'auth'">
</nav>
<!-- NO NAV -->
<nav class="py2 sm-py1 xl-py3 relative" ng-show="nav === 'none'">
<ul class="wrapper flex align-center">
<li>
<a class="NavItem cursor-pointer flex align-center" href="/">
<mb-logo-icon class="text-brand my2"></mb-logo-icon>
</a>
</li>
</ul>
</nav>
</div>
<div class="Nav" ng-controller="Nav" mb-react-component="Navbar"></div>
<main class="Main flex flex-column flex-full" ng-view></main>
</body>
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment