From b11239f3bf4436271152d86ded7736ffe9bcbd46 Mon Sep 17 00:00:00 2001 From: Joseph Schorr Date: Fri, 28 Apr 2017 17:03:38 -0400 Subject: [PATCH] Change cor-tabs to be a TypeScript and Angular "neu" component We no longer use bootstrap tabs code in this version This is in prep for changing the tab style --- static/css/core-ui.css | 252 ----------------- static/css/directives/ui/cor-tabs.css | 254 ++++++++++++++++++ static/directives/cor-tab.html | 12 - static/directives/cor-tabs.html | 4 - static/directives/credentials-dialog.html | 60 ++--- static/js/core-ui.js | 83 ------ .../cor-tabs/cor-tab-content.component.html | 1 + .../ui/cor-tabs/cor-tab-content.component.ts | 15 ++ .../ui/cor-tabs/cor-tab-handlers.ts | 83 ++++++ .../ui/cor-tabs/cor-tab-pane.component.html | 3 + .../ui/cor-tabs/cor-tab-pane.component.ts | 31 +++ .../ui/cor-tabs/cor-tab-panel.component.html} | 0 .../ui/cor-tabs/cor-tab-panel.component.ts | 115 ++++++++ .../ui/cor-tabs/cor-tab.component.html | 10 + .../ui/cor-tabs/cor-tab.component.ts | 46 ++++ .../ui/cor-tabs/cor-tabs.component.html | 4 + .../ui/cor-tabs/cor-tabs.component.ts | 22 ++ static/js/quay.module.ts | 12 + static/partials/image-view.html | 52 ++-- static/partials/manage-application.html | 46 ++-- static/partials/org-view.html | 76 +++--- static/partials/repo-view.html | 78 +++--- static/partials/super-user.html | 100 ++++--- static/partials/user-view.html | 61 +++-- 24 files changed, 832 insertions(+), 588 deletions(-) create mode 100644 static/css/directives/ui/cor-tabs.css delete mode 100644 static/directives/cor-tab.html delete mode 100644 static/directives/cor-tabs.html create mode 100644 static/js/directives/ui/cor-tabs/cor-tab-content.component.html create mode 100644 static/js/directives/ui/cor-tabs/cor-tab-content.component.ts create mode 100644 static/js/directives/ui/cor-tabs/cor-tab-handlers.ts create mode 100644 static/js/directives/ui/cor-tabs/cor-tab-pane.component.html create mode 100644 static/js/directives/ui/cor-tabs/cor-tab-pane.component.ts rename static/{directives/cor-tab-panel.html => js/directives/ui/cor-tabs/cor-tab-panel.component.html} (100%) create mode 100644 static/js/directives/ui/cor-tabs/cor-tab-panel.component.ts create mode 100644 static/js/directives/ui/cor-tabs/cor-tab.component.html create mode 100644 static/js/directives/ui/cor-tabs/cor-tab.component.ts create mode 100644 static/js/directives/ui/cor-tabs/cor-tabs.component.html create mode 100644 static/js/directives/ui/cor-tabs/cor-tabs.component.ts diff --git a/static/css/core-ui.css b/static/css/core-ui.css index 1c4672081..ef80ab7d7 100644 --- a/static/css/core-ui.css +++ b/static/css/core-ui.css @@ -162,161 +162,6 @@ a:focus { } } -.co-tab-container { - padding: 0px; -} - -.co-tabs { - margin: 0px; - padding: 0px; - width: 82px; - display: table-cell; - vertical-align: top; -} - -.co-tab-element { - display: table-cell; - float: none; - vertical-align: top; - background-color: #e8f1f6; - border-right: 1px solid #DDE7ED; -} - -.co-tab-content { - width: 100%; - display: table-cell; - float: none; - padding: 30px; -} - -@media (max-width: 767px) { - .co-tab-content { - padding: 20px; - width: 100%; - display: block; - } -} - -.co-tabs li { - list-style: none; - display: block; - border-bottom: 1px solid #DDE7ED; -} - -.co-tabs li.active { - background-color: white; - border-right: 1px solid white; - margin-right: -1px; -} - -.co-tabs li a { - display: block; - width: 82px; - height: 82px; - line-height: 82px; - text-align: center; - font-size: 36px; -} - -.co-tabs li a i { - font-size: 36px; - color: gray; -} - -.co-tabs li.active a { - color: black; -} - -.co-tabs .xs-toggle { - display: none; -} - -@media (max-width: 767px) { - .co-tabs { - display: block; - width: auto; - border-right: none; - border-bottom: 1px solid #DDE7ED; - } - - .co-tab-element { - position: relative; - display: block; - } - - .co-tab-element .xs-toggle { - display: inline-block; - position: absolute; - top: 10px; - right: 10px; - z-index: 2; - width: 40px; - height: 40px; - cursor: pointer; - font-size: 18px; - text-align: center; - line-height: 40px; - border: 2px solid #DDE7ED; - background: white; - } - - .co-tab-element.closed .xs-toggle:before { - content: "\f0d7"; - font-family: FontAwesome; - } - - .co-tab-element.open .xs-toggle:before { - content: "\f0d8"; - font-family: FontAwesome; - } - - .co-tab-element .xs-label { - line-height: 60px; - font-size: 16px; - margin-left: 16px; - display: inline-block !important; - color: gray; - } - - .co-tabs li a { - display: inline-block; - height: 60px; - line-height: 60px; - white-space: nowrap; - width: 100%; - text-align: left; - padding-left: 20px; - text-decoration: none !important; - font-size: 28px; - } - - .co-tabs li a i { - vertical-align: middle; - font-size: 28px; - } - - .co-tabs li.active a .xs-label { - color: black; - } - - .co-tabs li.active a i { - color: black; - } - - .co-tab-element.open li { - height: 60px; - } - - .co-tab-element.closed li { - height: 0px; - overflow: hidden; - } - - .co-tab-element.closed li.active { - height: 60px; - } -} - .co-main-content-panel { margin-bottom: 20px; background-color: #fff; @@ -330,11 +175,6 @@ a:focus { box-shadow: 0px 2px 2px rgba(0, 0, 0, 0.4); } -.co-tab-panel { - padding: 0px; -} - - .cor-log-box { width: 100%; height: 550px; @@ -1647,98 +1487,6 @@ a:focus { color: #aaa; } -.co-dialog .co-tabs { - width: auto; - min-height: 400px; - padding-top: 58px; -} - -.co-dialog .co-tabs li a { - width: auto; - height: 42px; - line-height: 42px; - text-align: left; - font-size: 120%; - white-space: nowrap; - padding: 0px; - padding-left: 16px; - padding-right: 30px; - color: #5A5A5A; -} - -.co-dialog .co-tabs li.active a, .co-dialog .co-tabs li.active a .fa { - color: black; - - filter: none !important; - -webkit-filter: none !important; -} - -.co-dialog .co-tabs li a .fa { - vertical-align: middle; - margin-right: 10px; - font-size: 20px; - line-height: 20px; - width: 20px; - text-align: center; - margin-top: -2px; -} - -.co-dialog .co-tabs li a .fa.icon { - width: 20px; - height: 20px; - background-size: 20px; - filter: grayscale(100%); - -webkit-filter: grayscale(100%); -} - -.co-dialog .co-tab-content { - padding: 16px; - padding-bottom: 30px; -} - -.co-dialog .co-tab-content h3 { - margin-top: 0px; - margin-bottom: 0px; -} - -.co-dialog .co-tab-content label { - margin-top: 24px; - font-weight: bold; - font-size: 16px; -} - -.co-dialog .co-tab-content .help-text { - margin-top: 7px; - margin-left: 6px; - color: #aaa; -} - -.co-dialog .co-tab-content .co-list-table { - width: 100%; -} - -.co-dialog .co-tab-content .co-list-table td:first-child { - white-space: nowrap; - vertical-align: middle; - font-weight: normal; -} - -.co-dialog .co-tabs li:first-child { - border-top: 1px solid #DDE7ED; -} - -@media screen and (max-width: 767px) { - .co-dialog .co-tabs { - min-height: 0px; - padding-top: 0px; - } - - .co-dialog .co-tabs li a { - line-height: 60px; - height: auto; - } -} - .co-modal-body-scrollable { overflow-y: auto; overflow-x: hidden; diff --git a/static/css/directives/ui/cor-tabs.css b/static/css/directives/ui/cor-tabs.css new file mode 100644 index 000000000..e7f405d9c --- /dev/null +++ b/static/css/directives/ui/cor-tabs.css @@ -0,0 +1,254 @@ +cor-tabs { + display: table-cell; + float: none; + vertical-align: top; + background-color: #e8f1f6; + border-right: 1px solid #DDE7ED; +} + +.co-tab-container { + padding: 0px; +} + +.co-tabs { + margin: 0px; + padding: 0px; + width: 82px; + display: table-cell; + vertical-align: top; +} + +.co-tab-content { + width: 100%; + display: table-cell; + float: none; + padding: 30px; +} + +@media (max-width: 767px) { + .co-tab-content { + padding: 20px; + width: 100%; + display: block; + } +} + +.co-tabs li { + list-style: none; + display: block; + border-bottom: 1px solid #DDE7ED; +} + +.co-tabs li.active { + background-color: white; + border-right: 1px solid white; + margin-right: -1px; +} + +.co-tabs li a { + display: block; + width: 82px; + height: 82px; + line-height: 82px; + text-align: center; + font-size: 36px; +} + +.co-tabs li a i { + font-size: 36px; + color: gray; +} + +.co-tabs li.active a { + color: black; +} + +.co-tabs .xs-toggle { + display: none; +} + +@media (max-width: 767px) { + .co-tabs { + display: block; + width: auto; + border-right: none; + border-bottom: 1px solid #DDE7ED; + } + + cor-tabs { + position: relative; + display: block; + } + + .co-tab-element .xs-toggle { + display: inline-block; + position: absolute; + top: 10px; + right: 10px; + z-index: 2; + width: 40px; + height: 40px; + cursor: pointer; + font-size: 18px; + text-align: center; + line-height: 40px; + border: 2px solid #DDE7ED; + background: white; + } + + .co-tab-element.closed .xs-toggle:before { + content: "\f0d7"; + font-family: FontAwesome; + } + + .co-tab-element.open .xs-toggle:before { + content: "\f0d8"; + font-family: FontAwesome; + } + + .co-tab-element .xs-label { + line-height: 60px; + font-size: 16px; + margin-left: 16px; + display: inline-block !important; + color: gray; + } + + .co-tabs li a { + display: inline-block; + height: 60px; + line-height: 60px; + white-space: nowrap; + width: 100%; + text-align: left; + padding-left: 20px; + text-decoration: none !important; + font-size: 28px; + } + + .co-tabs li a i { + vertical-align: middle; + font-size: 28px; + } + + .co-tabs li.active a .xs-label { + color: black; + } + + .co-tabs li.active a i { + color: black; + } + + .co-tab-element.open li { + height: 60px; + } + + .co-tab-element.closed li { + height: 0px; + overflow: hidden; + } + + .co-tab-element.closed li.active { + height: 60px; + } +} + +.co-tab-panel { + padding: 0px; +} + +.co-dialog .co-tabs { + width: auto; + min-height: 400px; + padding-top: 58px; +} + +.co-dialog .co-main-content-panel { + margin-bottom: 0px; +} + +.co-dialog .co-tabs li a { + width: auto; + height: 42px; + line-height: 42px; + text-align: left; + font-size: 120%; + white-space: nowrap; + padding: 0px; + padding-left: 16px; + padding-right: 30px; + color: #5A5A5A; +} + +.co-dialog .co-tabs li.active a, .co-dialog .co-tabs li.active a .fa { + color: black; + + filter: none !important; + -webkit-filter: none !important; +} + +.co-dialog .co-tabs li a .fa { + vertical-align: middle; + margin-right: 10px; + font-size: 20px; + line-height: 20px; + width: 20px; + text-align: center; + margin-top: -2px; +} + +.co-dialog .co-tabs li a .fa.icon { + width: 20px; + height: 20px; + background-size: 20px; + filter: grayscale(100%); + -webkit-filter: grayscale(100%); +} + +.co-dialog .co-tab-content { + padding: 16px; + padding-bottom: 30px; +} + +.co-dialog .co-tab-content h3 { + margin-top: 0px; + margin-bottom: 0px; +} + +.co-dialog .co-tab-content label { + margin-top: 24px; + font-weight: bold; + font-size: 16px; +} + +.co-dialog .co-tab-content .help-text { + margin-top: 7px; + margin-left: 6px; + color: #aaa; +} + +.co-dialog .co-tab-content .co-list-table { + width: 100%; +} + +.co-dialog .co-tab-content .co-list-table td:first-child { + white-space: nowrap; + vertical-align: middle; + font-weight: normal; +} + +.co-dialog .co-tabs li:first-child { + border-top: 1px solid #DDE7ED; +} + +@media screen and (max-width: 767px) { + .co-dialog .co-tabs { + min-height: 0px; + padding-top: 0px; + } + + .co-dialog .co-tabs li a { + line-height: 60px; + height: auto; + } +} \ No newline at end of file diff --git a/static/directives/cor-tab.html b/static/directives/cor-tab.html deleted file mode 100644 index a0ba352da..000000000 --- a/static/directives/cor-tab.html +++ /dev/null @@ -1,12 +0,0 @@ -
  • - - {{ tabTitle }} - - -
  • diff --git a/static/directives/cor-tabs.html b/static/directives/cor-tabs.html deleted file mode 100644 index 7903bb8a4..000000000 --- a/static/directives/cor-tabs.html +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/static/directives/credentials-dialog.html b/static/directives/credentials-dialog.html index ea082318b..b2bef307d 100644 --- a/static/directives/credentials-dialog.html +++ b/static/directives/credentials-dialog.html @@ -6,42 +6,42 @@
    -
    + -
    - + + {{ secretTitle }} - + - + Kubernetes Secret - + - + rkt Configuration - + - + Docker Login - + - + Docker Configuration - + - + Mesos Credentials - -
    + + -
    +

    Credentials for {{ credentials.username }}

    -
    +
    @@ -49,9 +49,9 @@
    -
    +
    -
    +
    First, download the Kubernetes pull secret for the {{ entityTitle }}:
      @@ -83,9 +83,9 @@ spec: imagePullSecrets: - name: {{ getKubernetesSecretName(credentials) }}
    -
    + -
    +
    First, download the Docker credentials file as a bundle:
      @@ -122,9 +122,9 @@ spec: ] }
    -
    + -
    +
    First, download the rkt credentials file for the {{ entityTitle }}:
      @@ -137,15 +137,15 @@ spec:
      Second, place the file in the rkt configuration directory:
      -
    + -
    +
    Enter the following command on the command line:
    -
    + -
    +
    First, download the Docker credentials file for the {{ entityTitle }}:
      @@ -158,9 +158,9 @@ spec:
      Second, place the file in the Docker configuration directory. Note: This will overwrite existing credentials:
      -
    -
    -
    + + + diff --git a/static/js/core-ui.js b/static/js/core-ui.js index 94728c83b..3d94d659c 100644 --- a/static/js/core-ui.js +++ b/static/js/core-ui.js @@ -214,61 +214,6 @@ angular.module("core-ui", []) return directiveDefinitionObject; }) - .directive('corTabPanel', function() { - var directiveDefinitionObject = { - priority: 1, - templateUrl: '/static/directives/cor-tab-panel.html', - replace: true, - transclude: true, - restrict: 'C', - scope: {}, - controller: function($rootScope, $scope, $element) { - } - }; - return directiveDefinitionObject; - }) - - .directive('corTabContent', function() { - var directiveDefinitionObject = { - priority: 2, - replace: true, - transclude: false, - restrict: 'C', - scope: {}, - controller: function($rootScope, $scope, $element) { - $element.addClass('co-tab-content tab-content col-md-11'); - } - }; - return directiveDefinitionObject; - }) - - .directive('corTabs', function() { - var directiveDefinitionObject = { - priority: 3, - templateUrl: '/static/directives/cor-tabs.html', - replace: true, - transclude: true, - restrict: 'C', - scope: { - 'rememberCookie': '@rememberCookie' - }, - controller: function($rootScope, $scope, $element, $timeout, $location, UIService) { - $scope.isClosed = true; - - $scope.toggleClosed = function(e) { - $scope.isClosed = !$scope.isClosed; - e.stopPropagation(); - e.preventDefault(); - }; - - UIService.initializeTabs($scope, $element, function() { - $scope.isClosed = true; - }, $scope.rememberCookie); - } - }; - return directiveDefinitionObject; - }) - .directive('corFloatingBottomBar', function() { var directiveDefinitionObject = { priority: 3, @@ -338,34 +283,6 @@ angular.module("core-ui", []) return directiveDefinitionObject; }) - .directive('corTab', function() { - var directiveDefinitionObject = { - priority: 4, - templateUrl: '/static/directives/cor-tab.html', - replace: true, - transclude: true, - restrict: 'C', - scope: { - 'tabActive': '@tabActive', - 'tabTitle': '@tabTitle', - 'tabTarget': '@tabTarget', - 'tabInit': '&tabInit', - 'tabShown': '&tabShown', - 'tabHidden': '&tabHidden' - }, - controller: function($rootScope, $scope, $element) { - $element.find('a[data-toggle="tab"]').on('hidden.bs.tab', function (e) { - $scope.tabHidden({}); - }); - - $element.find('a[data-toggle="tab"]').on('shown.bs.tab', function (e) { - $scope.tabShown({}); - }); - } - }; - return directiveDefinitionObject; - }) - .directive('corStep', function() { var directiveDefinitionObject = { priority: 4, diff --git a/static/js/directives/ui/cor-tabs/cor-tab-content.component.html b/static/js/directives/ui/cor-tabs/cor-tab-content.component.html new file mode 100644 index 000000000..747ccb2c8 --- /dev/null +++ b/static/js/directives/ui/cor-tabs/cor-tab-content.component.html @@ -0,0 +1 @@ +
    diff --git a/static/js/directives/ui/cor-tabs/cor-tab-content.component.ts b/static/js/directives/ui/cor-tabs/cor-tab-content.component.ts new file mode 100644 index 000000000..a79d7db5d --- /dev/null +++ b/static/js/directives/ui/cor-tabs/cor-tab-content.component.ts @@ -0,0 +1,15 @@ +import { Component } from 'ng-metadata/core'; + + +/** + * A component that is placed under a cor-tabs to wrap tab content with additional styling. + */ +@Component({ + selector: 'cor-tab-content', + templateUrl: '/static/js/directives/ui/cor-tabs/cor-tab-content.component.html', + legacy: { + transclude: true, + replace: true, + } +}) +export class CorTabContentComponent {} diff --git a/static/js/directives/ui/cor-tabs/cor-tab-handlers.ts b/static/js/directives/ui/cor-tabs/cor-tab-handlers.ts new file mode 100644 index 000000000..a9eba4945 --- /dev/null +++ b/static/js/directives/ui/cor-tabs/cor-tab-handlers.ts @@ -0,0 +1,83 @@ +import { CorTabComponent } from './cor-tab.component'; +import { CorTabPanelComponent } from './cor-tab-panel.component'; + +/** + * Defines an interface for reading and writing the current tab state. + */ +export interface CorTabCurrentHandler { + getInitialTabId(): string + + notifyTabChanged(tab: CorTabComponent, isDefaultTab: boolean) + + dispose(): void +} + +export function CorTabCurrentHandlerFactory(options?: any): CorTabCurrentHandler { + switch (options.type) { + case "cookie": + return new CookieCurrentTabHandler(options.cookieService, options.cookieName); + default: + return new LocationCurrentTabHandler(options.panel, options.$location, options.$rootScope); + } +} + +/** + * Reads and writes the tab from the `tab` query parameter in the location. + */ +export class LocationCurrentTabHandler implements CorTabCurrentHandler { + private cancelWatchHandle: Function; + + constructor (private panel: CorTabPanelComponent, + private $location: ng.ILocationService, + private $rootScope: ng.IRootScopeService) { + } + + private checkLocation(): void { + var specifiedTabId = this.$location.search()['tab']; + var specifiedTab = this.panel.findTab(specifiedTabId); + this.panel.setActiveTab(specifiedTab); + } + + public getInitialTabId(): string { + if (!this.cancelWatchHandle) { + this.cancelWatchHandle = this.$rootScope.$on('$routeUpdate', () => this.checkLocation()); + } + return this.$location.search()['tab']; + } + + public notifyTabChanged(tab: CorTabComponent, isDefaultTab: boolean) { + var newSearch = $.extend(this.$location.search(), {}); + if (isDefaultTab) { + delete newSearch['tab']; + } else { + newSearch['tab'] = tab.tabId; + } + + this.$location.search(newSearch); + } + + public dispose(): void { + this.cancelWatchHandle(); + } +} + +/** + * Reads and writes the tab from a cookie,. + */ +export class CookieCurrentTabHandler implements CorTabCurrentHandler { + constructor (private CookieService: any, private cookieName: string) {} + + public getInitialTabId(): string { + return this.CookieService.get(this.cookieName); + } + + public notifyTabChanged(tab: CorTabComponent, isDefaultTab: boolean) { + if (isDefaultTab) { + this.CookieService.clear(this.cookieName); + } else { + this.CookieService.putPermanent(this.cookieName, tab.tabId); + } + } + + public dispose(): void {} +} \ No newline at end of file diff --git a/static/js/directives/ui/cor-tabs/cor-tab-pane.component.html b/static/js/directives/ui/cor-tabs/cor-tab-pane.component.html new file mode 100644 index 000000000..f452537ab --- /dev/null +++ b/static/js/directives/ui/cor-tabs/cor-tab-pane.component.html @@ -0,0 +1,3 @@ +
    +
    +
    \ No newline at end of file diff --git a/static/js/directives/ui/cor-tabs/cor-tab-pane.component.ts b/static/js/directives/ui/cor-tabs/cor-tab-pane.component.ts new file mode 100644 index 000000000..03fcc04c9 --- /dev/null +++ b/static/js/directives/ui/cor-tabs/cor-tab-pane.component.ts @@ -0,0 +1,31 @@ +import { Component, Input, Inject, Host, OnInit } from 'ng-metadata/core'; +import { CorTabPanelComponent } from './cor-tab-panel.component'; + + +/** + * A component that creates a single tab pane under a cor-tabs component. + */ +@Component({ + selector: 'cor-tab-pane', + templateUrl: '/static/js/directives/ui/cor-tabs/cor-tab-pane.component.html', + legacy: { + transclude: true, + } +}) +export class CorTabPaneComponent implements OnInit { + @Input('@') public id: string; + + // Whether this is the active tab. + private isActiveTab: boolean = false; + + constructor(@Host() @Inject(CorTabPanelComponent) private parent: CorTabPanelComponent) { + } + + public ngOnInit(): void { + this.parent.addTabPane(this); + } + + public changeState(isActive: boolean): void { + this.isActiveTab = isActive; + } +} diff --git a/static/directives/cor-tab-panel.html b/static/js/directives/ui/cor-tabs/cor-tab-panel.component.html similarity index 100% rename from static/directives/cor-tab-panel.html rename to static/js/directives/ui/cor-tabs/cor-tab-panel.component.html diff --git a/static/js/directives/ui/cor-tabs/cor-tab-panel.component.ts b/static/js/directives/ui/cor-tabs/cor-tab-panel.component.ts new file mode 100644 index 000000000..834121f28 --- /dev/null +++ b/static/js/directives/ui/cor-tabs/cor-tab-panel.component.ts @@ -0,0 +1,115 @@ +import { Component, Input, Inject, OnDestroy } from 'ng-metadata/core'; +import { CorTabComponent } from './cor-tab.component'; +import { CorTabPaneComponent } from './cor-tab-pane.component'; +import { CorTabCurrentHandler, LocationCurrentTabHandler, CookieCurrentTabHandler, CorTabCurrentHandlerFactory } from './cor-tab-handlers' + + +/** + * A component that contains a cor-tabs and handles all of its logic. + */ +@Component({ + selector: 'cor-tab-panel', + templateUrl: '/static/js/directives/ui/cor-tabs/cor-tab-panel.component.html', + legacy: { + transclude: true, + } +}) +export class CorTabPanelComponent implements OnDestroy { + // If 'true', the currently selected tab will be remembered via a cookie and not the page URL. + @Input('@') public rememberCookie: string; + + // The tabs under this tabs component. + private tabs: CorTabComponent[] = []; + + // The tab panes under the tabs component, indexed by the tab id. + private tabPanes: {[id: string]: CorTabPaneComponent} = {}; + + // The currently active tab, if any. + private activeTab: CorTabComponent = null; + + // Whether the initial tab was set. + private initialTabSet: boolean = false; + + // The handler to use to read/write the current tab. + private currentTabHandler: CorTabCurrentHandler = null; + + constructor(@Inject('$location') private $location: ng.ILocationService, + @Inject('$rootScope') private $rootScope: ng.IRootScopeService, + @Inject('CookieService') private CookieService: any, + @Inject('CorTabCurrentHandlerFactory') private CorTabCurrentHandlerFactory: (Object) => CorTabCurrentHandler) { + } + + public ngOnDestroy(): void { + this.currentTabHandler.dispose(); + } + + public tabClicked(tab: CorTabComponent): void { + this.setActiveTab(tab); + } + + public findTab(tabId: string): CorTabComponent { + if (!this.tabs.length) { + return null; + } + + var tab = this.tabs.find(function(current) { + return current.tabId == tabId; + }) || this.tabs[0]; + + if (!this.tabPanes[tab.tabId]) { + return null; + } + + return tab; + } + + public setActiveTab(tab: CorTabComponent): void { + if (this.activeTab == tab) { + return; + } + + if (this.activeTab != null) { + this.activeTab.changeState(false); + this.tabPanes[this.activeTab.tabId].changeState(false); + } + + this.activeTab = tab; + this.activeTab.changeState(true); + this.tabPanes[this.activeTab.tabId].changeState(true); + this.currentTabHandler.notifyTabChanged(tab, this.tabs[0] == tab); + } + + public addTab(tab: CorTabComponent): void { + this.tabs.push(tab); + this.checkInitialTab(); + } + + public addTabPane(tabPane: CorTabPaneComponent): void { + this.tabPanes[tabPane.id] = tabPane; + this.checkInitialTab(); + } + + private checkInitialTab(): void { + if (this.tabs.length < 1 || this.initialTabSet) { + return; + } + + this.currentTabHandler = this.CorTabCurrentHandlerFactory({ + type: this.rememberCookie ? 'cookie' : 'location', + cookieService: this.CookieService, + cookeName: this.rememberCookie, + panel: this, + $location: this.$location, + $rootScope: this.$rootScope, + }); + + var tabId = this.currentTabHandler.getInitialTabId(); + var tab = this.findTab(tabId); + if (!tab) { + return; + } + + this.initialTabSet = true; + this.setActiveTab(tab); + } +} diff --git a/static/js/directives/ui/cor-tabs/cor-tab.component.html b/static/js/directives/ui/cor-tabs/cor-tab.component.html new file mode 100644 index 000000000..ca8e42f49 --- /dev/null +++ b/static/js/directives/ui/cor-tabs/cor-tab.component.html @@ -0,0 +1,10 @@ +
  • + + {{ ::$ctrl.tabTitle }} + + +
  • diff --git a/static/js/directives/ui/cor-tabs/cor-tab.component.ts b/static/js/directives/ui/cor-tabs/cor-tab.component.ts new file mode 100644 index 000000000..7697d757b --- /dev/null +++ b/static/js/directives/ui/cor-tabs/cor-tab.component.ts @@ -0,0 +1,46 @@ +import { Component, Input, Output, Inject, EventEmitter, Host, OnInit } from 'ng-metadata/core'; +import { CorTabPanelComponent } from './cor-tab-panel.component'; + + +/** + * A component that creates a single tab under a cor-tabs component. + */ +@Component({ + selector: 'cor-tab', + templateUrl: '/static/js/directives/ui/cor-tabs/cor-tab.component.html', + legacy: { + transclude: true, + } +}) +export class CorTabComponent implements OnInit { + @Input('@') public tabId: string; + @Input('@') public tabTitle: string; + + @Output() public tabInit: EventEmitter = new EventEmitter(); + @Output() public tabShown: EventEmitter = new EventEmitter(); + @Output() public tabHidden: EventEmitter = new EventEmitter(); + + // Whether this is the active tab. + private isActive: boolean = false; + + constructor(@Host() @Inject(CorTabPanelComponent) private parent: CorTabPanelComponent) { + } + + public ngOnInit(): void { + this.parent.addTab(this); + } + + public changeState(isActive: boolean): void { + this.isActive = isActive; + if (isActive) { + this.tabInit.emit({}); + this.tabShown.emit({}); + } else { + this.tabHidden.emit({}); + } + } + + private tabClicked(): void { + this.parent.tabClicked(this); + } +} diff --git a/static/js/directives/ui/cor-tabs/cor-tabs.component.html b/static/js/directives/ui/cor-tabs/cor-tabs.component.html new file mode 100644 index 000000000..0e1b4bf22 --- /dev/null +++ b/static/js/directives/ui/cor-tabs/cor-tabs.component.html @@ -0,0 +1,4 @@ + + +
      +
      diff --git a/static/js/directives/ui/cor-tabs/cor-tabs.component.ts b/static/js/directives/ui/cor-tabs/cor-tabs.component.ts new file mode 100644 index 000000000..6b58ae50e --- /dev/null +++ b/static/js/directives/ui/cor-tabs/cor-tabs.component.ts @@ -0,0 +1,22 @@ +import { Component, Host, Inject } from 'ng-metadata/core'; +import { CorTabComponent } from './cor-tab.component'; + + +/** + * A component that holds the actual tabs. + */ +@Component({ + selector: 'cor-tabs', + templateUrl: '/static/js/directives/ui/cor-tabs/cor-tabs.component.html', + legacy: { + transclude: true, + } +}) +export class CorTabsComponent { + // If true, the tabs are in a closed state. Only applies in the mobile view. + private isClosed: boolean = true; + + private toggleClosed(e): void { + this.isClosed = !this.isClosed; + } +} diff --git a/static/js/quay.module.ts b/static/js/quay.module.ts index ecbf57eeb..0e3509faf 100644 --- a/static/js/quay.module.ts +++ b/static/js/quay.module.ts @@ -13,6 +13,12 @@ import { AppPublicViewComponent } from './directives/ui/app-public-view/app-publ import { VisibilityIndicatorComponent } from './directives/ui/visibility-indicator/visibility-indicator.component'; import { CorTableComponent } from './directives/ui/cor-table/cor-table.component'; import { CorTableColumn } from './directives/ui/cor-table/cor-table-col.component'; +import { CorTabPanelComponent } from './directives/ui/cor-tabs/cor-tab-panel.component'; +import { CorTabContentComponent } from './directives/ui/cor-tabs/cor-tab-content.component'; +import { CorTabsComponent } from './directives/ui/cor-tabs/cor-tabs.component'; +import { CorTabComponent } from './directives/ui/cor-tabs/cor-tab.component'; +import { CorTabPaneComponent } from './directives/ui/cor-tabs/cor-tab-pane.component'; +import { CorTabCurrentHandlerFactory } from './directives/ui/cor-tabs/cor-tab-handlers'; import { ChannelIconComponent } from './directives/ui/channel-icon/channel-icon.component'; import { TagSigningDisplayComponent } from './directives/ui/tag-signing-display/tag-signing-display.component'; import { RepositorySigningConfigComponent } from './directives/ui/repository-signing-config/repository-signing-config.component'; @@ -56,6 +62,11 @@ import { QuayRequireDirective } from './directives/structural/quay-require/quay- DurationInputComponent, SearchBoxComponent, TypeaheadDirective, + CorTabPanelComponent, + CorTabContentComponent, + CorTabsComponent, + CorTabComponent, + CorTabPaneComponent, ], providers: [ ViewArrayImpl, @@ -65,6 +76,7 @@ import { QuayRequireDirective } from './directives/structural/quay-require/quay- DataFileServiceImpl, UtilServiceImpl, {provide: 'fileReaderFactory', useValue: () => new FileReader()}, + {provide: 'CorTabCurrentHandlerFactory', useValue: CorTabCurrentHandlerFactory}, ], }) export class QuayModule { diff --git a/static/partials/image-view.html b/static/partials/image-view.html index a773d567f..beec1526d 100644 --- a/static/partials/image-view.html +++ b/static/partials/image-view.html @@ -15,42 +15,46 @@
      -
      -
      - + + + - - + + - - + + - -
      + + -
      + -
      +

      Image Layers

      -
      + -
      -
      -
      + +
      +
      +
      +
      -
      -
      -
      -
      -
      + +
      +
      +
      +
      + + diff --git a/static/partials/manage-application.html b/static/partials/manage-application.html index b5187c678..698dca5c0 100644 --- a/static/partials/manage-application.html +++ b/static/partials/manage-application.html @@ -20,29 +20,28 @@ -
      -
      - + + + - + - + - + - + - + - + - -
      - -
      + + + -
      +
      @@ -81,10 +80,10 @@
      -
      + -
      +
      @@ -93,10 +92,10 @@
      -
      + -
      +
      Click the button below to generate a new OAuth 2 Access Token. @@ -125,10 +124,10 @@ ng-disabled="!getScopes(genScopes).length" ng-safenewtab> Generate Access Token -
      + -
      +
      Client ID:
      @@ -139,10 +138,9 @@
      -
      - -
      -
      + +
      +
      diff --git a/static/partials/org-view.html b/static/partials/org-view.html index d750ac66c..1fd56e0bb 100644 --- a/static/partials/org-view.html +++ b/static/partials/org-view.html @@ -20,75 +20,75 @@
       
      -
      -
      - + + + - - + + - - + + - - + + - - + + - - + + - - + + - -
      + + -
      + -
      +

      Repositories

      -
      + -
      +
      -
      + -
      +
      -
      + -
      +
      -
      + -
      +
      -
      + -
      +
      -
      + -
      +
      @@ -129,9 +129,9 @@
      -
      -
      -
      + + + diff --git a/static/partials/repo-view.html b/static/partials/repo-view.html index 746345390..95da42e2a 100644 --- a/static/partials/repo-view.html +++ b/static/partials/repo-view.html @@ -35,91 +35,91 @@ -
      -
      - + + + - + - + - + - + - + - + - + - + - + - + - -
      + + -
      + -
      +
      -
      + -
      +
      -
      + -
      +

      Tag History

      -
      + -
      +
      -
      + -
      +
      -
      + -
      +
      -
      -
      -
      - + + + + diff --git a/static/partials/super-user.html b/static/partials/super-user.html index 827a49e59..f272834bb 100644 --- a/static/partials/super-user.html +++ b/static/partials/super-user.html @@ -22,72 +22,71 @@ Quay Enterprise Management -
      -
      - + + + - - + + - - + + - - + + - - + + - - + + - - - - -
      + + -
      + -
      +
      Warning: If you are running Quay Enterprise under multiple containers, additional actions are necessary in order to apply any configuration changes made here to the entire cluster. It is recommended to make (and validate) configuration changes here, then copy your configuration to all instances and restart them.
      -
      + -
      +
      -
      + -
      +
      -
      - + -
      +
      -
      + -
      +
      @@ -110,22 +109,22 @@
      -
      + -
      +
      -
      + -
      +

      Change Log

      -
      + -
      +
      @@ -180,15 +179,14 @@
      -
      +
      -
      +
      -
      - -
      -
      + + + -
      -
      - + + + - - + + - - + - - + - - + + - -
      + + -
      + -
      +

      Repositories

      -
      + -
      +
      -
      + -
      +
      -
      + -
      +
      -
      + -
      +

      Docker CLI Password

      @@ -133,21 +133,20 @@ - -
      - -
      +
      + + +

      Billing Information

      - -
      -
      -
      + + +