implement componenitized navigation and remove old nav and layout code

This commit is contained in:
John Mitchell
2017-09-06 17:32:32 -04:00
parent 2daaef2a99
commit ea91fabba0
49 changed files with 677 additions and 1540 deletions

View File

@@ -1,5 +1,6 @@
@import 'action/_index';
@import 'input/_index';
@import 'layout/_index';
@import 'modal/_index';
@import 'panel/_index';
@import 'popover/_index';

View File

@@ -47,6 +47,31 @@ function ComponentsStrings (BaseString) {
DEFAULT: t.s('Copy full revision to clipboard.'),
COPIED: t.s('Copied to clipboard.')
}
ns.layout = {
CURRENT_USER_LABEL: t.s('Logged in as'),
VIEW_DOCS: t.s('View Documentation'),
LOGOUT: t.s('Logout'),
DASHBOARD: t.s('Dashboard'),
JOBS: t.s('Jobs'),
SCHEDULES: t.s('Schedules'),
PORTAL_MODE: t.s('Portal Mode'),
PROJECTS: t.s('Projects'),
CREDENTIALS: t.s('Credentials'),
CREDENTIAL_TYPES: t.s('Credential Types'),
INVENTORIES: t.s('Inventories'),
TEMPLATES: t.s('Templates'),
ORGANIZATIONS: t.s('Organizations'),
USERS: t.s('Users'),
TEAMS: t.s('Teams'),
INVENTORY_SCRIPTS: t.s('Inventory Scripts'),
NOTIFICATIONS: t.s('Notifications'),
MANAGEMENT_JOBS: t.s('Management Jobs'),
INSTANCE_GROUPS: t.s('Instance Groups'),
SETTINGS: t.s('Settings'),
FOOTER_ABOUT: t.s('About'),
FOOTER_COPYRIGHT: t.s('Copyright © 2017 Red Hat, Inc.')
}
}
ComponentsStrings.$inject = ['BaseStringService'];

View File

@@ -1,3 +1,7 @@
import layout from './layout/layout.directive';
import topNavItem from './layout/top-nav-item.directive';
import sideNav from './layout/side-nav.directive';
import sideNavItem from './layout/side-nav-item.directive';
import actionGroup from './action/action-group.directive';
import divider from './utility/divider.directive';
import form from './form/form.directive';
@@ -26,6 +30,10 @@ import ComponentsStrings from './components.strings';
angular
.module('at.lib.components', [])
.directive('atLayout', layout)
.directive('atTopNavItem', topNavItem)
.directive('atSideNav', sideNav)
.directive('atSideNavItem', sideNavItem)
.directive('atActionGroup', actionGroup)
.directive('atDivider', divider)
.directive('atForm', form)
@@ -50,5 +58,3 @@ angular
.directive('atTruncate', truncate)
.service('ComponentsStrings', ComponentsStrings)
.service('BaseInputController', BaseInputController);

View File

@@ -0,0 +1,172 @@
.at-Layout {
height: 100vh;
width: 100vw;
display: flex;
&-topNav {
display: flex;
background-color: @at-color-top-nav-background;
border-bottom: @at-border-default-width solid @at-color-top-nav-border-bottom;
z-index: @at-z-index-nav;
position: fixed;
right: 0;
left: 0;
top: 0;
height: @at-height-top-nav;
.at-Layout-topNavRightAligner {
margin-left: auto;
}
.at-Layout-topNavItem {
color: @at-color-top-nav-item-text;
padding: 0 @at-padding-top-nav-item-sides;
a {
cursor: pointer;
}
a, div {
color: @at-color-top-nav-item-text;
display: flex;
align-items: center;
justify-content: center;
height: 100%;
}
i {
color: @at-color-top-nav-item-icon;
font-size: @at-height-top-nav-item-icon;
}
&--logo {
padding-left: 0px;
img {
max-width: @main-menu-max-width;
max-height: @main-menu-max-height;
height: @main-menu-height;
width: @main-menu-width;
margin: @main-menu-margin;
flex: initial;
}
}
&--user {
i {
margin-right: @at-margin-top-nav-item-between-icon-and-name;
}
}
&--socket {
i {
margin-top: @at-margin-top-nav-item-icon-socket-top-makeup;
font-size: @at-height-top-nav-item-icon-socket;
text-shadow:
-@at-border-default-width -@at-border-default-width 0 @at-color-top-nav-item-icon-socket-outline,
@at-border-default-width -@at-border-default-width 0 @at-color-top-nav-item-icon-socket-outline,
-@at-border-default-width @at-border-default-width 0 @at-color-top-nav-item-icon-socket-outline,
@at-border-default-width @at-border-default-width 0 @at-color-top-nav-item-icon-socket-outline
}
}
&:focus,
&:hover,
&.is-currentRoute {
background-color: @at-color-top-nav-item-background-hover;
}
&.is-loggedOut {
opacity: 0;
}
}
}
&-side {
background: @at-color-side-nav-background;
color: @at-color-side-nav-content;
position: fixed;
bottom: 0;
top: @at-height-top-side-nav-makeup;
overflow-y: auto;
min-height: 100vh;
min-width: @at-width-collapsed-side-nav;
.at-Layout-sideNavItem {
display: flex;
cursor: pointer;
i {
font-size: @at-height-side-nav-item-icon;
padding: @at-padding-side-nav-item-icon;
}
&:hover,
&.is-active {
background: @at-color-side-nav-item-background-hover;
border-left: @at-highlight-left-border-size solid @at-color-side-nav-item-border-hover;
i {
margin-left: @at-highlight-left-border-margin-makeup;
}
}
}
.at-Layout-sideNavSpacer {
height: @at-height-side-nav-spacer;
}
&--expanded {
width: @at-width-expanded-side-nav;
.at-Layout-sideNavItem {
display: flex;
justify-content: flex-start;
align-items: center;
padding-right: @at-padding-between-side-nav-icon-text;
text-transform: uppercase;
}
+ .at-Layout-main {
padding-left: @at-width-expanded-side-nav;
}
}
}
&-main {
display: flex;
height: 100%;
width: 100%;
flex-direction: column;
padding-left: @at-width-collapsed-side-nav;
overflow-x: hidden;
}
&-footer {
height: 40px;
background-color: @at-color-footer-background;
color: @at-color-footer;
z-index: 1040;
position: absolute;
right: @at-padding-footer-right;
left: 0;
bottom: 0;
display: flex;
align-items: center;
justify-content: flex-end;
margin-left: (@at-width-collapsed-side-nav + 5);
a {
cursor: pointer;
margin-right: @at-margin-after-footer-link;
}
}
&-side--expanded {
+ .at-Layout-main {
.at-Layout-footer {
margin-left: @at-width-expanded-side-nav;
}
}
}
}

View File

@@ -0,0 +1,52 @@
function AtLayoutController ($scope, strings) {
let vm = this || {};
$scope.$on('$stateChangeSuccess', function(event, next) {
vm.currentState = next.name;
});
$scope.$watch('$root.current_user', function(val) {
vm.isLoggedIn = val && val.username;
if (val) {
vm.isSuperUser = $scope.$root.user_is_superuser || $scope.$root.user_is_system_auditor;
vm.currentUsername = val.username;
vm.currentUserId = val.id;
}
});
$scope.$watch('$root.socketStatus', function(newStatus) {
vm.socketState = newStatus;
vm.socketIconClass = "icon-socket-" + $scope.socketStatus;
});
$scope.$watch('$root.licenseMissing', function(licenseMissing) {
vm.licenseIsMissing = licenseMissing;
});
vm.getString = function(string) {
try {
return strings.get(`layout.${string}`);
} catch(err) {
return strings.get(string);
}
};
}
AtLayoutController.$inject = ['$scope', 'ComponentsStrings'];
function atLayout (pathService) {
return {
restrict: 'E',
replace: true,
transclude: true,
templateUrl: pathService.getPartialPath('components/layout/layout'),
controller: AtLayoutController,
controllerAs: 'vm',
scope: {
}
};
}
atLayout.$inject = ['PathService'];
export default atLayout;

View File

@@ -0,0 +1,85 @@
<div class="at-Layout">
<div class="at-Layout-topNav">
<at-top-nav-item is-shown="missingLicense" class="at-Layout-topNavItem--logo">
<a href="/#/">
<img ng-src="/static/assets/logo-header.svg">
</a>
</at-top-nav-item>
<div class="at-Layout-topNavRightAligner"></div>
<at-top-nav-item class="at-Layout-topNavItem--user">
<a ng-href="/#/users/{{ $parent.layoutVm.currentUserId }}">
<i class="fa fa-user"
alt="{{ $parent.layoutVm.getString('CURRENT_USER_LABEL') }} {{ $parent.layoutVm.currentUsername }}">
</i>
<span>{{ $parent.layoutVm.currentUsername }}</span>
</a>
</at-top-nav-item>
<at-top-nav-item>
<a href="http://docs.ansible.com/ansible-tower/" target="_blank">
<i class="fa fa-book" alt="{{ $parent.layoutVm.getString('VIEW_DOCS') }}"></i>
</a>
</at-top-nav-item>
<at-top-nav-item class="at-Layout-topNavItem--socket"
ng-if="$parent.layoutVm.socketState &&
$parent.layoutVm.socketState !== 'ok'">
<div><i class="fa" ng-class="$parent.layoutVm.socketIconClass"></i></div>
</at-top-nav-item>
<at-top-nav-item is-shown="missingLicense">
<a href="/#/logout" is-always-shown="license">
<i class="fa fa-power-off" alt="{{ $parent.layoutVm.getString('LOGOUT') }}"></i>
</a>
</at-top-nav-item>
</div>
<at-side-nav>
<at-side-nav-item icon-class="fa-tachometer" route="dashboard" name="DASHBOARD">
</at-side-nav-item>
<at-side-nav-item icon-class="fa-spinner" route="jobs" name="JOBS">
</at-side-nav-item>
<at-side-nav-item icon-class="fa-calendar" route="jobs.schedules" name="SCHEDULES">
</at-side-nav-item>
<at-side-nav-item icon-class="fa-columns" route="portalMode.myJobs" name="PORTAL_MODE">
</at-side-nav-item>
<div class="at-Layout-sideNavSpacer"></div>
<at-side-nav-item icon-class="fa-folder-open" route="projects" name="PROJECTS">
</at-side-nav-item>
<at-side-nav-item icon-class="fa-key" route="credentials" name="CREDENTIALS">
</at-side-nav-item>
<at-side-nav-item icon-class="fa-list-alt" route="credentialTypes" name="CREDENTIAL_TYPES"
system-admin-only="true">
</at-side-nav-item>
<at-side-nav-item icon-class="fa-sitemap" route="inventories" name="INVENTORIES">
</at-side-nav-item>
<at-side-nav-item icon-class="fa-pencil-square-o" route="templates" name="TEMPLATES">
</at-side-nav-item>
<div class="at-Layout-sideNavSpacer"></div>
<at-side-nav-item icon-class="fa-building" route="organizations" name="ORGANIZATIONS">
</at-side-nav-item>
<at-side-nav-item icon-class="fa-user" route="users" name="USERS">
</at-side-nav-item>
<at-side-nav-item icon-class="fa-users" route="teams" name="TEAMS">
</at-side-nav-item>
<div class="at-Layout-sideNavSpacer"></div>
<at-side-nav-item icon-class="fa-code" route="inventoryScripts" name="INVENTORY_SCRIPTS">
</at-side-nav-item>
<at-side-nav-item icon-class="fa-bell" route="notifications" name="NOTIFICATIONS"
system-admin-only="true">
</at-side-nav-item>
<at-side-nav-item icon-class="fa-wrench" route="managementJobsList" name="MANAGEMENT_JOBS"
system-admin-only="true">
</at-side-nav-item>
<at-side-nav-item icon-class="fa-server" route="instanceGroups" name="INSTANCE_GROUPS"
system-admin-only="true">
</at-side-nav-item>
<div class="at-Layout-sideNavSpacer"></div>
<at-side-nav-item icon-class="fa-cog" route="configuration" name="SETTINGS"
system-admin-only="true">
</at-side-nav-item>
</at-side-nav>
<div class="at-Layout-main">
<ng-transclude></ng-transclude>
<div class="at-Layout-footer">
<a ui-sref="about">{{ vm.getString('FOOTER_ABOUT') }} {{ vm.getString('BRAND_NAME') }}</a>|
{{ vm.getString('FOOTER_COPYRIGHT') }}
</div>
</div>
</div>

View File

@@ -0,0 +1,51 @@
function atSideNavItemLink (scope, element, attrs, ctrl) {
scope.navVm = ctrl[0];
scope.layoutVm = ctrl[1];
}
function AtSideNavItemController ($state, $scope) {
let vm = this || {};
$scope.$watch('layoutVm.currentState', function(current) {
if ($scope.name === 'portal mode') {
vm.isRoute = (current && current.indexOf('portalMode') === 0);
} else {
if (current && current.indexOf($scope.route) === 0) {
if (current.indexOf('jobs.schedules') === 0 && $scope.route === 'jobs') {
vm.isRoute = false;
} else {
vm.isRoute = true;
}
} else {
vm.isRoute = false;
}
}
});
vm.go = function() {
$state.go($scope.route, {}, {reload: true});
}
}
AtSideNavItemController.$inject = ['$state', '$scope'];
function atSideNavItem (pathService) {
return {
restrict: 'E',
templateUrl: pathService.getPartialPath('components/layout/side-nav-item'),
require: ['^^atSideNav', '^^atLayout'],
controller: AtSideNavItemController,
controllerAs: 'vm',
link: atSideNavItemLink,
scope: {
iconClass: '@',
name: '@',
route: '@',
systemAdminOnly: '@'
}
};
}
atSideNavItem.$inject = ['PathService'];
export default atSideNavItem;

View File

@@ -0,0 +1,8 @@
<div class="at-Layout-sideNavItem" ng-click="vm.go()" ng-class="{'is-active': vm.isRoute}"
ng-show="(!systemAdminOnly || layoutVm.isSuperUser) && layoutVm.isLoggedIn &&
!layoutVm.licenseIsMissing">
<i class="fa {{ iconClass }}"></i>
<span class="at-Layout-sideNavItemName" ng-show="navVm.isExpanded">
{{ layoutVm.getString(name) }}
</span>
</div>

View File

@@ -0,0 +1,32 @@
function atSideNavLink (scope, element, attrs, ctrl) {
scope.layoutVm = ctrl;
}
function AtSideNavController () {
let vm = this || {};
vm.isExpanded = true;
vm.toggleExpansion = () => {
vm.isExpanded = !vm.isExpanded;
}
}
function atSideNav (pathService) {
return {
restrict: 'E',
replace: true,
require: '^^atLayout',
controller: AtSideNavController,
controllerAs: 'vm',
link: atSideNavLink,
transclude: true,
templateUrl: pathService.getPartialPath('components/layout/side-nav'),
scope: {
}
};
}
atSideNav.$inject = ['PathService'];
export default atSideNav;

View File

@@ -0,0 +1,8 @@
<div class="at-Layout-side"
ng-class="{'at-Layout-side--expanded': vm.isExpanded && layoutVm.isLoggedIn}">
<div class="at-Layout-sideNavItem" ng-click="vm.toggleExpansion()"
ng-show="layoutVm.isLoggedIn && !layoutVm.licenseIsMissing">
<i class="fa fa-bars"></i>
</div>
<ng-transclude></ng-transclude>
</div>

View File

@@ -0,0 +1,30 @@
function atTopNavItemLink (scope, element, attrs, ctrl) {
scope.layoutVm = ctrl;
scope.isHidden = false;
var shownWhen = attrs.isShown;
if (shownWhen !== 'missingLicense') {
scope.$watch('layoutVm.licenseIsMissing', function(val) {
scope.isHidden = val;
});
}
}
function atTopNavItem (pathService) {
return {
restrict: 'E',
replace: true,
transclude: true,
templateUrl: pathService.getPartialPath('components/layout/top-nav-item'),
require: '^^atLayout',
link: atTopNavItemLink,
scope: {
}
};
}
atTopNavItem.$inject = ['PathService'];
export default atTopNavItem;

View File

@@ -0,0 +1,3 @@
<div class="at-Layout-topNavItem" ng-class="{'is-loggedOut': !layoutVm.isLoggedIn}"
ng-show="!isHidden" ng-transclude>
</div>