moved modules to typescript. Tell Webpack to use global window.angular object in order to migrate components gradually into bundle
This commit is contained in:
parent
d5a74af024
commit
c55e9f2d12
15 changed files with 590 additions and 505 deletions
|
@ -7,7 +7,7 @@ LOCAL_DIRECTORY = '/static/ldn/'
|
||||||
EXTERNAL_JS = [
|
EXTERNAL_JS = [
|
||||||
'code.jquery.com/jquery.js',
|
'code.jquery.com/jquery.js',
|
||||||
'netdna.bootstrapcdn.com/bootstrap/3.3.2/js/bootstrap.min.js',
|
'netdna.bootstrapcdn.com/bootstrap/3.3.2/js/bootstrap.min.js',
|
||||||
'ajax.googleapis.com/ajax/libs/angularjs/1.5.3/angular.min.js',
|
'ajax.googleapis.com/ajax/libs/angularjs/1.5.3/angular.js',
|
||||||
'ajax.googleapis.com/ajax/libs/angularjs/1.5.3/angular-route.min.js',
|
'ajax.googleapis.com/ajax/libs/angularjs/1.5.3/angular-route.min.js',
|
||||||
'ajax.googleapis.com/ajax/libs/angularjs/1.5.3/angular-sanitize.min.js',
|
'ajax.googleapis.com/ajax/libs/angularjs/1.5.3/angular-sanitize.min.js',
|
||||||
'ajax.googleapis.com/ajax/libs/angularjs/1.5.3/angular-animate.min.js',
|
'ajax.googleapis.com/ajax/libs/angularjs/1.5.3/angular-animate.min.js',
|
||||||
|
|
|
@ -28,6 +28,7 @@ module.exports = function(config) {
|
||||||
'static/lib/**/*.js',
|
'static/lib/**/*.js',
|
||||||
|
|
||||||
// Application resources
|
// Application resources
|
||||||
|
'static/js/quay.module.ts',
|
||||||
'static/js/**/*.js',
|
'static/js/**/*.js',
|
||||||
|
|
||||||
// Tests
|
// Tests
|
||||||
|
@ -44,7 +45,21 @@ module.exports = function(config) {
|
||||||
},
|
},
|
||||||
webpack: {
|
webpack: {
|
||||||
resolve: webpackConfig.resolve,
|
resolve: webpackConfig.resolve,
|
||||||
module: webpackConfig.module,
|
externals: webpackConfig.externals,
|
||||||
|
module: {
|
||||||
|
loaders: [
|
||||||
|
{
|
||||||
|
test: /\.tsx?$/,
|
||||||
|
loader: "ts-loader",
|
||||||
|
exclude: /node_modules/
|
||||||
|
},
|
||||||
|
{
|
||||||
|
test: /\.scss$/,
|
||||||
|
loaders: ['style', 'css', 'sass'],
|
||||||
|
exclude: /node_modules/
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
},
|
},
|
||||||
webpackMiddleware: {
|
webpackMiddleware: {
|
||||||
stats: 'errors-only'
|
stats: 'errors-only'
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "./node_modules/.bin/karma start --single-run --browsers PhantomJS",
|
"test": "./node_modules/.bin/karma start --single-run --browsers Chrome",
|
||||||
"build": "./node_modules/.bin/webpack --progress -p -v",
|
"build": "./node_modules/.bin/webpack --progress -p -v",
|
||||||
"watch": "./node_modules/.bin/webpack --watch"
|
"watch": "./node_modules/.bin/webpack --watch"
|
||||||
},
|
},
|
||||||
|
|
353
static/js/app.js
353
static/js/app.js
|
@ -1,356 +1,3 @@
|
||||||
var TEAM_PATTERN = '^[a-z][a-z0-9]+$';
|
var TEAM_PATTERN = '^[a-z][a-z0-9]+$';
|
||||||
var ROBOT_PATTERN = '^[a-z][a-z0-9_]{3,29}$';
|
var ROBOT_PATTERN = '^[a-z][a-z0-9_]{3,29}$';
|
||||||
var USERNAME_PATTERN = '^(?=.{4,30}$)([a-z0-9]+(?:[._-][a-z0-9]+)*)$';
|
var USERNAME_PATTERN = '^(?=.{4,30}$)([a-z0-9]+(?:[._-][a-z0-9]+)*)$';
|
||||||
|
|
||||||
// Define the pages module.
|
|
||||||
quayPages = angular.module('quayPages', [], function(){});
|
|
||||||
|
|
||||||
// Define a constant for creating pages.
|
|
||||||
quayPages.constant('pages', {
|
|
||||||
'_pages': {},
|
|
||||||
|
|
||||||
'create': function(pageName, templateName, opt_controller, opt_flags, opt_profiles) {
|
|
||||||
var profiles = opt_profiles || ['old-layout', 'layout'];
|
|
||||||
for (var i = 0; i < profiles.length; ++i) {
|
|
||||||
this._pages[profiles[i] + ':' + pageName] = {
|
|
||||||
'name': pageName,
|
|
||||||
'controller': opt_controller,
|
|
||||||
'templateName': templateName,
|
|
||||||
'flags': opt_flags || {}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
'get': function(pageName, profiles) {
|
|
||||||
for (var i = 0; i < profiles.length; ++i) {
|
|
||||||
var current = profiles[i];
|
|
||||||
var key = current.id + ':' + pageName;
|
|
||||||
var page = this._pages[key];
|
|
||||||
if (page) {
|
|
||||||
return [current, page];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
quayDependencies = ['ngRoute', 'chieffancypants.loadingBar', 'cfp.hotkeys', 'angular-tour', 'restangular', 'angularMoment',
|
|
||||||
'mgcrea.ngStrap', 'ngCookies', 'ngSanitize', 'angular-md5', 'pasvaz.bindonce', 'ansiToHtml',
|
|
||||||
'core-ui', 'core-config-setup', 'quayPages', 'infinite-scroll', 'react'];
|
|
||||||
|
|
||||||
if (window.__config && (window.__config.MIXPANEL_KEY || window.__config.MUNCHKIN_KEY || window.__config.GOOGLE_ANALYTICS_KEY)) {
|
|
||||||
quayDependencies.push('angulartics');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (window.__config && window.__config.MIXPANEL_KEY) {
|
|
||||||
quayDependencies.push('angulartics.mixpanel');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (window.__config && window.__config.MUNCHKIN_KEY) {
|
|
||||||
quayDependencies.push('angulartics.marketo');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (window.__config && window.__config.GOOGLE_ANALYTICS_KEY) {
|
|
||||||
quayDependencies.push('angulartics.google.analytics');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (window.__config && window.__config.RECAPTCHA_SITE_KEY) {
|
|
||||||
quayDependencies.push('vcRecaptcha');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Define the application.
|
|
||||||
quayApp = angular.module('quay', quayDependencies, function($provide, cfpLoadingBarProvider) {
|
|
||||||
cfpLoadingBarProvider.includeSpinner = false;
|
|
||||||
});
|
|
||||||
|
|
||||||
// Disable tooltips on touch devices.
|
|
||||||
quayApp.config(['$tooltipProvider', function ($tooltipProvider) {
|
|
||||||
var tooltipFactory = $tooltipProvider.$get[$tooltipProvider.$get.length - 1];
|
|
||||||
|
|
||||||
// decorate the tooltip getter
|
|
||||||
$tooltipProvider.$get[$tooltipProvider.$get.length - 1] = function($window) {
|
|
||||||
if ('ontouchstart' in $window) {
|
|
||||||
var existing = tooltipFactory.apply(this, arguments);
|
|
||||||
return function(element) {
|
|
||||||
// Note: We only disable bs-tooltip's themselves. $tooltip is used for other things
|
|
||||||
// (such as the datepicker), so we need to be specific when canceling it.
|
|
||||||
if (element.attr('bs-tooltip') == null) {
|
|
||||||
return existing.apply(this, arguments);
|
|
||||||
} else {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return tooltipFactory.apply(this, arguments);
|
|
||||||
};
|
|
||||||
}]);
|
|
||||||
|
|
||||||
quayApp.config(['$compileProvider', function ($compileProvider) {
|
|
||||||
if (!window.__config['DEBUG']) {
|
|
||||||
$compileProvider.debugInfoEnabled(false);
|
|
||||||
}
|
|
||||||
}]);
|
|
||||||
|
|
||||||
// Configure the routes.
|
|
||||||
quayApp.config(['$routeProvider', '$locationProvider', 'pages', 'RouteBuilderProvider',
|
|
||||||
function($routeProvider, $locationProvider, pages, RouteBuilderProvider) {
|
|
||||||
$locationProvider.html5Mode(true);
|
|
||||||
|
|
||||||
// WARNING WARNING WARNING
|
|
||||||
// If you add a route here, you must add a corresponding route in thr endpoints/web.py
|
|
||||||
// index rule to make sure that deep links directly deep into the app continue to work.
|
|
||||||
// WARNING WARNING WARNING
|
|
||||||
|
|
||||||
var layoutProfile = 'layout';
|
|
||||||
window.console.log('Using layout profile: ' + layoutProfile);
|
|
||||||
|
|
||||||
var routeBuilder = new RouteBuilderProvider.$get()($routeProvider, pages, [
|
|
||||||
// Start with the old pages (if we asked for it).
|
|
||||||
{id: 'old-layout', templatePath: '/static/partials/'},
|
|
||||||
|
|
||||||
// Fallback back combined new/existing pages.
|
|
||||||
{id: 'layout', templatePath: '/static/partials/'}
|
|
||||||
], layoutProfile);
|
|
||||||
|
|
||||||
if (window.__features.SUPER_USERS) {
|
|
||||||
// QE Management
|
|
||||||
routeBuilder.route('/superuser/', 'superuser')
|
|
||||||
|
|
||||||
// QE Setup
|
|
||||||
.route('/setup/', 'setup');
|
|
||||||
}
|
|
||||||
|
|
||||||
routeBuilder
|
|
||||||
// Repository View
|
|
||||||
.route('/repository/:namespace/:name', 'repo-view')
|
|
||||||
.route('/repository/:namespace/:name/tag/:tag', 'repo-view')
|
|
||||||
|
|
||||||
// Image View
|
|
||||||
.route('/repository/:namespace/:name/image/:image', 'image-view')
|
|
||||||
|
|
||||||
// Repo Build View
|
|
||||||
.route('/repository/:namespace/:name/build/:buildid', 'build-view')
|
|
||||||
|
|
||||||
// Create repository notification
|
|
||||||
.route('/repository/:namespace/:name/create-notification', 'create-repository-notification')
|
|
||||||
|
|
||||||
// Repo List
|
|
||||||
.route('/repository/', 'repo-list')
|
|
||||||
|
|
||||||
// Organizations
|
|
||||||
.route('/organizations/', 'organizations')
|
|
||||||
|
|
||||||
// New Organization
|
|
||||||
.route('/organizations/new/', 'new-organization')
|
|
||||||
|
|
||||||
// View Organization
|
|
||||||
.route('/organization/:orgname', 'org-view')
|
|
||||||
|
|
||||||
// View Organization Team
|
|
||||||
.route('/organization/:orgname/teams/:teamname', 'team-view')
|
|
||||||
|
|
||||||
// Organization View Application
|
|
||||||
.route('/organization/:orgname/application/:clientid', 'manage-application')
|
|
||||||
|
|
||||||
// View Organization Billing
|
|
||||||
.route('/organization/:orgname/billing', 'billing')
|
|
||||||
|
|
||||||
// View Organization Billing Invoices
|
|
||||||
.route('/organization/:orgname/billing/invoices', 'invoices')
|
|
||||||
|
|
||||||
// View User
|
|
||||||
.route('/user/:username', 'user-view')
|
|
||||||
|
|
||||||
// View User Billing
|
|
||||||
.route('/user/:username/billing', 'billing')
|
|
||||||
|
|
||||||
// View User Billing Invoices
|
|
||||||
.route('/user/:username/billing/invoices', 'invoices')
|
|
||||||
|
|
||||||
// Sign In
|
|
||||||
.route('/signin/', 'signin')
|
|
||||||
|
|
||||||
// New Repository
|
|
||||||
.route('/new/', 'new-repo')
|
|
||||||
|
|
||||||
// Plans
|
|
||||||
.route('/plans/', 'plans')
|
|
||||||
|
|
||||||
// Tutorial
|
|
||||||
.route('/tutorial/', 'tutorial')
|
|
||||||
|
|
||||||
// Contact
|
|
||||||
.route('/contact/', 'contact')
|
|
||||||
|
|
||||||
// About
|
|
||||||
.route('/about/', 'about')
|
|
||||||
|
|
||||||
// Security
|
|
||||||
.route('/security/', 'security')
|
|
||||||
|
|
||||||
// TOS
|
|
||||||
.route('/tos', 'tos')
|
|
||||||
|
|
||||||
// Privacy
|
|
||||||
.route('/privacy', 'privacy')
|
|
||||||
|
|
||||||
// Change username
|
|
||||||
.route('/updateuser', 'update-user')
|
|
||||||
|
|
||||||
// Landing Page
|
|
||||||
.route('/', 'landing')
|
|
||||||
|
|
||||||
// Tour
|
|
||||||
.route('/tour/', 'tour')
|
|
||||||
.route('/tour/features', 'tour')
|
|
||||||
.route('/tour/organizations', 'tour')
|
|
||||||
.route('/tour/enterprise', 'tour')
|
|
||||||
|
|
||||||
// Confirm Invite
|
|
||||||
.route('/confirminvite', 'confirm-invite')
|
|
||||||
|
|
||||||
// Enterprise marketing page
|
|
||||||
.route('/enterprise', 'enterprise')
|
|
||||||
|
|
||||||
// Public Repo Experiments
|
|
||||||
.route('/__exp/publicRepo', 'public-repo-exp')
|
|
||||||
|
|
||||||
// 404/403
|
|
||||||
.route('/:catchall', 'error-view')
|
|
||||||
.route('/:catch/:all', 'error-view')
|
|
||||||
.route('/:catch/:all/:things', 'error-view')
|
|
||||||
.route('/:catch/:all/:things/:here', 'error-view');
|
|
||||||
}]);
|
|
||||||
|
|
||||||
// Configure compile provider to add additional URL prefixes to the sanitization list. We use
|
|
||||||
// these on the Contact page.
|
|
||||||
quayApp.config(function($compileProvider) {
|
|
||||||
$compileProvider.aHrefSanitizationWhitelist(/^\s*(https?|ftp|mailto|tel|irc):/);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Configure the API provider.
|
|
||||||
quayApp.config(function(RestangularProvider) {
|
|
||||||
RestangularProvider.setBaseUrl('/api/v1/');
|
|
||||||
});
|
|
||||||
|
|
||||||
// Configure analytics.
|
|
||||||
if (window.__config && window.__config.MIXPANEL_KEY) {
|
|
||||||
quayApp.config(['$analyticsProvider', function($analyticsProvider) {
|
|
||||||
$analyticsProvider.virtualPageviews(true);
|
|
||||||
}]);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Configure sentry.
|
|
||||||
if (window.__config && window.__config.SENTRY_PUBLIC_DSN) {
|
|
||||||
quayApp.config(function($provide) {
|
|
||||||
$provide.decorator("$exceptionHandler", function($delegate) {
|
|
||||||
return function(ex, cause) {
|
|
||||||
$delegate(ex, cause);
|
|
||||||
Raven.captureException(ex, {extra: {cause: cause}});
|
|
||||||
};
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Run the application.
|
|
||||||
quayApp.run(['$location', '$rootScope', 'Restangular', 'UserService', 'PlanService', '$http', '$timeout', 'CookieService', 'Features', '$anchorScroll', 'UtilService', 'MetaService', 'UIService',
|
|
||||||
function($location, $rootScope, Restangular, UserService, PlanService, $http, $timeout, CookieService, Features, $anchorScroll, UtilService, MetaService, UIService) {
|
|
||||||
|
|
||||||
var defaultTitle = window.__config['REGISTRY_TITLE'] || 'Quay Container Registry';
|
|
||||||
|
|
||||||
// Handle session security.
|
|
||||||
Restangular.setDefaultRequestParams(['post', 'put', 'remove', 'delete'], {'_csrf_token': window.__token || ''});
|
|
||||||
|
|
||||||
// Handle session expiration.
|
|
||||||
Restangular.setErrorInterceptor(function(response) {
|
|
||||||
if (response.status == 503) {
|
|
||||||
$('#cannotContactService').modal({});
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (response.status == 500) {
|
|
||||||
document.location = '/500';
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!response.data) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
var invalid_token = response.data['title'] == 'invalid_token' || response.data['error_type'] == 'invalid_token';
|
|
||||||
if (response.status == 401 && invalid_token && response.data['session_required'] !== false) {
|
|
||||||
$('#sessionexpiredModal').modal({});
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
});
|
|
||||||
|
|
||||||
// Check if we need to redirect based on a previously chosen plan.
|
|
||||||
var result = PlanService.handleNotedPlan();
|
|
||||||
|
|
||||||
// Check to see if we need to show a redirection page.
|
|
||||||
var redirectUrl = CookieService.get('quay.redirectAfterLoad');
|
|
||||||
CookieService.clear('quay.redirectAfterLoad');
|
|
||||||
|
|
||||||
if (!result && redirectUrl && redirectUrl.indexOf(window.location.href) == 0) {
|
|
||||||
window.location = redirectUrl;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
$rootScope.$watch('description', function(description) {
|
|
||||||
if (!description) {
|
|
||||||
description = 'Hosted private docker repositories. Includes full user management and history. Free for public repositories.';
|
|
||||||
}
|
|
||||||
|
|
||||||
// Note: We set the content of the description tag manually here rather than using Angular binding
|
|
||||||
// because we need the <meta> tag to have a default description that is not of the form "{{ description }}",
|
|
||||||
// we read by tools that do not properly invoke the Angular code.
|
|
||||||
$('#descriptionTag').attr('content', description);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Listen for scope changes and update the title and description accordingly.
|
|
||||||
$rootScope.$watch(function() {
|
|
||||||
var title = MetaService.getTitle($rootScope.currentPage) || defaultTitle;
|
|
||||||
if ($rootScope.title != title) {
|
|
||||||
$rootScope.title = title;
|
|
||||||
}
|
|
||||||
|
|
||||||
var description = MetaService.getDescription($rootScope.currentPage) || '';
|
|
||||||
if ($rootScope.description != description) {
|
|
||||||
$rootScope.description = description;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
$rootScope.$on('$routeChangeSuccess', function (event, current, previous) {
|
|
||||||
$rootScope.current = current.$$route;
|
|
||||||
$rootScope.currentPage = current;
|
|
||||||
|
|
||||||
$rootScope.pageClass = '';
|
|
||||||
|
|
||||||
if (!current.$$route) { return; }
|
|
||||||
|
|
||||||
var pageClass = current.$$route.pageClass || '';
|
|
||||||
if (typeof pageClass != 'string') {
|
|
||||||
pageClass = pageClass(Features);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
$rootScope.pageClass = pageClass;
|
|
||||||
$rootScope.newLayout = !!current.$$route.newLayout;
|
|
||||||
$rootScope.fixFooter = !!current.$$route.fixFooter;
|
|
||||||
|
|
||||||
$anchorScroll();
|
|
||||||
});
|
|
||||||
|
|
||||||
var initallyChecked = false;
|
|
||||||
window.__isLoading = function() {
|
|
||||||
if (!initallyChecked) {
|
|
||||||
initallyChecked = true;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return $http.pendingRequests.length > 0;
|
|
||||||
};
|
|
||||||
}]);
|
|
||||||
|
|
|
@ -1,22 +1,22 @@
|
||||||
import "sass/repo-page/repo-page.scss";
|
import "sass/repo-page/repo-page.scss";
|
||||||
import * as angular from "angular";
|
import * as angular from "angular";
|
||||||
|
import quayPages from '../../../../quay-pages.module';
|
||||||
|
|
||||||
import repoHeader from "./header";
|
import repoHeader from "./header";
|
||||||
import repoSidebar from "./sidebar";
|
import repoSidebar from "./sidebar";
|
||||||
import repoBody from "./body";
|
import repoBody from "./body";
|
||||||
|
|
||||||
export function rpDirectives(){
|
export function rpDirectives(){
|
||||||
angular.module('quayPages').directive('rpHeader', function(reactDirective) {
|
angular.module(quayPages).directive('rpHeader', function(reactDirective) {
|
||||||
return reactDirective(repoHeader);
|
return reactDirective(repoHeader);
|
||||||
});
|
});
|
||||||
|
|
||||||
angular.module('quayPages').directive('rpSidebar', function(reactDirective) {
|
angular.module(quayPages).directive('rpSidebar', function(reactDirective) {
|
||||||
return reactDirective(repoSidebar);
|
return reactDirective(repoSidebar);
|
||||||
});
|
});
|
||||||
|
|
||||||
angular.module('quayPages').directive('rpBody', function(reactDirective, ApiService) {
|
angular.module(quayPages).directive('rpBody', function(reactDirective, ApiService) {
|
||||||
return reactDirective(repoBody, undefined, {}, {api: ApiService});
|
return reactDirective(repoBody, undefined, {}, {api: ApiService});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
// From: http://justinklemm.com/angularjs-filter-ordering-objects-ngrepeat/ under MIT License
|
// From: http://justinklemm.com/angularjs-filter-ordering-objects-ngrepeat/ under MIT License
|
||||||
quayApp.filter('orderObjectBy', function() {
|
angular
|
||||||
|
.module('quay')
|
||||||
|
.filter('orderObjectBy', function() {
|
||||||
return function(items, field, reverse) {
|
return function(items, field, reverse) {
|
||||||
var filtered = [];
|
var filtered = [];
|
||||||
angular.forEach(items, function(item) {
|
angular.forEach(items, function(item) {
|
||||||
|
|
34
static/js/quay-pages.module.ts
Normal file
34
static/js/quay-pages.module.ts
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
import * as angular from 'angular';
|
||||||
|
|
||||||
|
|
||||||
|
export default angular
|
||||||
|
.module('quayPages', [])
|
||||||
|
.constant('pages', {
|
||||||
|
'_pages': {},
|
||||||
|
|
||||||
|
'create': function(pageName, templateName, opt_controller, opt_flags, opt_profiles) {
|
||||||
|
var profiles = opt_profiles || ['old-layout', 'layout'];
|
||||||
|
for (var i = 0; i < profiles.length; ++i) {
|
||||||
|
this._pages[profiles[i] + ':' + pageName] = {
|
||||||
|
'name': pageName,
|
||||||
|
'controller': opt_controller,
|
||||||
|
'templateName': templateName,
|
||||||
|
'flags': opt_flags || {}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
'get': function(pageName, profiles) {
|
||||||
|
for (var i = 0; i < profiles.length; ++i) {
|
||||||
|
var current = profiles[i];
|
||||||
|
var key = current.id + ':' + pageName;
|
||||||
|
var page = this._pages[key];
|
||||||
|
if (page) {
|
||||||
|
return [current, page];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.name;
|
201
static/js/quay.config.ts
Normal file
201
static/js/quay.config.ts
Normal file
|
@ -0,0 +1,201 @@
|
||||||
|
import * as Raven from 'raven-js';
|
||||||
|
|
||||||
|
|
||||||
|
quayConfig.$inject = [
|
||||||
|
'$provide',
|
||||||
|
'cfpLoadingBarProvider',
|
||||||
|
'$tooltipProvider',
|
||||||
|
'$compileProvider',
|
||||||
|
'$routeProvider',
|
||||||
|
'$locationProvider',
|
||||||
|
'pages',
|
||||||
|
'RouteBuilderProvider',
|
||||||
|
'RestangularProvider',
|
||||||
|
'$analyticsProvider',
|
||||||
|
];
|
||||||
|
|
||||||
|
export function quayConfig(
|
||||||
|
$provide,
|
||||||
|
cfpLoadingBarProvider,
|
||||||
|
$tooltipProvider,
|
||||||
|
$compileProvider,
|
||||||
|
$routeProvider,
|
||||||
|
$locationProvider,
|
||||||
|
pages,
|
||||||
|
RouteBuilderProvider,
|
||||||
|
RestangularProvider,
|
||||||
|
$analyticsProvider) {
|
||||||
|
cfpLoadingBarProvider.includeSpinner = false;
|
||||||
|
|
||||||
|
// decorate the tooltip getter
|
||||||
|
var tooltipFactory = $tooltipProvider.$get[$tooltipProvider.$get.length - 1];
|
||||||
|
$tooltipProvider.$get[$tooltipProvider.$get.length - 1] = function($window) {
|
||||||
|
if ('ontouchstart' in $window) {
|
||||||
|
var existing = tooltipFactory.apply(this, arguments);
|
||||||
|
return function(element) {
|
||||||
|
// Note: We only disable bs-tooltip's themselves. $tooltip is used for other things
|
||||||
|
// (such as the datepicker), so we need to be specific when canceling it.
|
||||||
|
if (element.attr('bs-tooltip') == null) {
|
||||||
|
return existing.apply(this, arguments);
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return tooltipFactory.apply(this, arguments);
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!(<any>window).__config['DEBUG']) {
|
||||||
|
$compileProvider.debugInfoEnabled(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
$locationProvider.html5Mode(true);
|
||||||
|
|
||||||
|
// WARNING WARNING WARNING
|
||||||
|
// If you add a route here, you must add a corresponding route in thr endpoints/web.py
|
||||||
|
// index rule to make sure that deep links directly deep into the app continue to work.
|
||||||
|
// WARNING WARNING WARNING
|
||||||
|
|
||||||
|
var layoutProfile = 'layout';
|
||||||
|
console.log('Using layout profile: ' + layoutProfile);
|
||||||
|
|
||||||
|
var routeBuilder = new RouteBuilderProvider.$get()($routeProvider, pages, [
|
||||||
|
// Start with the old pages (if we asked for it).
|
||||||
|
{id: 'old-layout', templatePath: '/static/partials/'},
|
||||||
|
|
||||||
|
// Fallback back combined new/existing pages.
|
||||||
|
{id: 'layout', templatePath: '/static/partials/'}
|
||||||
|
], layoutProfile);
|
||||||
|
|
||||||
|
if ((<any>window).__features.SUPER_USERS) {
|
||||||
|
// QE Management
|
||||||
|
routeBuilder.route('/superuser/', 'superuser')
|
||||||
|
|
||||||
|
// QE Setup
|
||||||
|
.route('/setup/', 'setup');
|
||||||
|
}
|
||||||
|
|
||||||
|
routeBuilder
|
||||||
|
// Repository View
|
||||||
|
.route('/repository/:namespace/:name', 'repo-view')
|
||||||
|
.route('/repository/:namespace/:name/tag/:tag', 'repo-view')
|
||||||
|
|
||||||
|
// Image View
|
||||||
|
.route('/repository/:namespace/:name/image/:image', 'image-view')
|
||||||
|
|
||||||
|
// Repo Build View
|
||||||
|
.route('/repository/:namespace/:name/build/:buildid', 'build-view')
|
||||||
|
|
||||||
|
// Create repository notification
|
||||||
|
.route('/repository/:namespace/:name/create-notification', 'create-repository-notification')
|
||||||
|
|
||||||
|
// Repo List
|
||||||
|
.route('/repository/', 'repo-list')
|
||||||
|
|
||||||
|
// Organizations
|
||||||
|
.route('/organizations/', 'organizations')
|
||||||
|
|
||||||
|
// New Organization
|
||||||
|
.route('/organizations/new/', 'new-organization')
|
||||||
|
|
||||||
|
// View Organization
|
||||||
|
.route('/organization/:orgname', 'org-view')
|
||||||
|
|
||||||
|
// View Organization Team
|
||||||
|
.route('/organization/:orgname/teams/:teamname', 'team-view')
|
||||||
|
|
||||||
|
// Organization View Application
|
||||||
|
.route('/organization/:orgname/application/:clientid', 'manage-application')
|
||||||
|
|
||||||
|
// View Organization Billing
|
||||||
|
.route('/organization/:orgname/billing', 'billing')
|
||||||
|
|
||||||
|
// View Organization Billing Invoices
|
||||||
|
.route('/organization/:orgname/billing/invoices', 'invoices')
|
||||||
|
|
||||||
|
// View User
|
||||||
|
.route('/user/:username', 'user-view')
|
||||||
|
|
||||||
|
// View User Billing
|
||||||
|
.route('/user/:username/billing', 'billing')
|
||||||
|
|
||||||
|
// View User Billing Invoices
|
||||||
|
.route('/user/:username/billing/invoices', 'invoices')
|
||||||
|
|
||||||
|
// Sign In
|
||||||
|
.route('/signin/', 'signin')
|
||||||
|
|
||||||
|
// New Repository
|
||||||
|
.route('/new/', 'new-repo')
|
||||||
|
|
||||||
|
// Plans
|
||||||
|
.route('/plans/', 'plans')
|
||||||
|
|
||||||
|
// Tutorial
|
||||||
|
.route('/tutorial/', 'tutorial')
|
||||||
|
|
||||||
|
// Contact
|
||||||
|
.route('/contact/', 'contact')
|
||||||
|
|
||||||
|
// About
|
||||||
|
.route('/about/', 'about')
|
||||||
|
|
||||||
|
// Security
|
||||||
|
.route('/security/', 'security')
|
||||||
|
|
||||||
|
// TOS
|
||||||
|
.route('/tos', 'tos')
|
||||||
|
|
||||||
|
// Privacy
|
||||||
|
.route('/privacy', 'privacy')
|
||||||
|
|
||||||
|
// Change username
|
||||||
|
.route('/updateuser', 'update-user')
|
||||||
|
|
||||||
|
// Landing Page
|
||||||
|
.route('/', 'landing')
|
||||||
|
|
||||||
|
// Tour
|
||||||
|
.route('/tour/', 'tour')
|
||||||
|
.route('/tour/features', 'tour')
|
||||||
|
.route('/tour/organizations', 'tour')
|
||||||
|
.route('/tour/enterprise', 'tour')
|
||||||
|
|
||||||
|
// Confirm Invite
|
||||||
|
.route('/confirminvite', 'confirm-invite')
|
||||||
|
|
||||||
|
// Enterprise marketing page
|
||||||
|
.route('/enterprise', 'enterprise')
|
||||||
|
|
||||||
|
// Public Repo Experiments
|
||||||
|
.route('/__exp/publicRepo', 'public-repo-exp')
|
||||||
|
|
||||||
|
// 404/403
|
||||||
|
.route('/:catchall', 'error-view')
|
||||||
|
.route('/:catch/:all', 'error-view')
|
||||||
|
.route('/:catch/:all/:things', 'error-view')
|
||||||
|
.route('/:catch/:all/:things/:here', 'error-view');
|
||||||
|
|
||||||
|
// Configure compile provider to add additional URL prefixes to the sanitization list. We use
|
||||||
|
// these on the Contact page.
|
||||||
|
$compileProvider.aHrefSanitizationWhitelist(/^\s*(https?|ftp|mailto|tel|irc):/);
|
||||||
|
|
||||||
|
// Configure the API provider.
|
||||||
|
RestangularProvider.setBaseUrl('/api/v1/');
|
||||||
|
|
||||||
|
// Configure analytics.
|
||||||
|
if ((<any>window).__config && (<any>window).__config.MIXPANEL_KEY) {
|
||||||
|
$analyticsProvider.virtualPageviews(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Configure sentry.
|
||||||
|
if ((<any>window).__config && (<any>window).__config.SENTRY_PUBLIC_DSN) {
|
||||||
|
$provide.decorator("$exceptionHandler", function($delegate) {
|
||||||
|
return function(ex, cause) {
|
||||||
|
$delegate(ex, cause);
|
||||||
|
Raven.captureException(ex, {extra: {cause: cause}});
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
53
static/js/quay.module.ts
Normal file
53
static/js/quay.module.ts
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
import * as angular from 'angular';
|
||||||
|
import { quayConfig } from './quay.config.ts';
|
||||||
|
import quayPages from './quay-pages.module';
|
||||||
|
import quayRun from './quay.run';
|
||||||
|
import { angularViewArrayFactory } from './services/angular-view-array/angular-view-array';
|
||||||
|
|
||||||
|
|
||||||
|
var quayDependencies: string[] = [
|
||||||
|
quayPages,
|
||||||
|
'ngRoute',
|
||||||
|
'chieffancypants.loadingBar',
|
||||||
|
'cfp.hotkeys',
|
||||||
|
'angular-tour',
|
||||||
|
'restangular',
|
||||||
|
'angularMoment',
|
||||||
|
'mgcrea.ngStrap',
|
||||||
|
'ngCookies',
|
||||||
|
'ngSanitize',
|
||||||
|
'angular-md5',
|
||||||
|
'pasvaz.bindonce',
|
||||||
|
'ansiToHtml',
|
||||||
|
'core-ui',
|
||||||
|
'core-config-setup',
|
||||||
|
'infinite-scroll',
|
||||||
|
'react'
|
||||||
|
];
|
||||||
|
|
||||||
|
if ((<any>window).__config && ((<any>window).__config.MIXPANEL_KEY || (<any>window).__config.MUNCHKIN_KEY || (<any>window).__config.GOOGLE_ANALYTICS_KEY)) {
|
||||||
|
quayDependencies.push('angulartics');
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((<any>window).__config && (<any>window).__config.MIXPANEL_KEY) {
|
||||||
|
quayDependencies.push('angulartics.mixpanel');
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((<any>window).__config && (<any>window).__config.MUNCHKIN_KEY) {
|
||||||
|
quayDependencies.push('angulartics.marketo');
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((<any>window).__config && (<any>window).__config.GOOGLE_ANALYTICS_KEY) {
|
||||||
|
quayDependencies.push('angulartics.google.analytics');
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((<any>window).__config && (<any>window).__config.RECAPTCHA_SITE_KEY) {
|
||||||
|
quayDependencies.push('vcRecaptcha');
|
||||||
|
}
|
||||||
|
|
||||||
|
export default angular
|
||||||
|
.module('quay', quayDependencies)
|
||||||
|
.config(quayConfig)
|
||||||
|
.factory('AngularViewArray', angularViewArrayFactory)
|
||||||
|
.run(quayRun)
|
||||||
|
.name;
|
127
static/js/quay.run.ts
Normal file
127
static/js/quay.run.ts
Normal file
|
@ -0,0 +1,127 @@
|
||||||
|
import * as $ from 'jquery';
|
||||||
|
|
||||||
|
|
||||||
|
quayRun.$inject = [
|
||||||
|
'$location',
|
||||||
|
'$rootScope',
|
||||||
|
'Restangular',
|
||||||
|
'UserService',
|
||||||
|
'PlanService',
|
||||||
|
'$http',
|
||||||
|
'$timeout',
|
||||||
|
'CookieService',
|
||||||
|
'Features',
|
||||||
|
'$anchorScroll',
|
||||||
|
'UtilService',
|
||||||
|
'MetaService',
|
||||||
|
];
|
||||||
|
|
||||||
|
export default function quayRun(
|
||||||
|
$location,
|
||||||
|
$rootScope,
|
||||||
|
Restangular,
|
||||||
|
UserService,
|
||||||
|
PlanService,
|
||||||
|
$http,
|
||||||
|
$timeout,
|
||||||
|
CookieService,
|
||||||
|
Features,
|
||||||
|
$anchorScroll,
|
||||||
|
UtilService,
|
||||||
|
MetaService) {
|
||||||
|
var defaultTitle = (<any>window).__config['REGISTRY_TITLE'] || 'Quay Container Registry';
|
||||||
|
|
||||||
|
// Handle session security.
|
||||||
|
Restangular.setDefaultRequestParams(['post', 'put', 'remove', 'delete'], {'_csrf_token': (<any>window).__token || ''});
|
||||||
|
|
||||||
|
// Handle session expiration.
|
||||||
|
Restangular.setErrorInterceptor(function(response) {
|
||||||
|
if (response.status == 503) {
|
||||||
|
(<any>$('#cannotContactService')).modal({});
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (response.status == 500) {
|
||||||
|
window.location.href = '/500';
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!response.data) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
var invalid_token = response.data['title'] == 'invalid_token' || response.data['error_type'] == 'invalid_token';
|
||||||
|
if (response.status == 401 && invalid_token && response.data['session_required'] !== false) {
|
||||||
|
(<any>$('#sessionexpiredModal')).modal({});
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Check if we need to redirect based on a previously chosen plan.
|
||||||
|
var result = PlanService.handleNotedPlan();
|
||||||
|
|
||||||
|
// Check to see if we need to show a redirection page.
|
||||||
|
var redirectUrl = CookieService.get('quay.redirectAfterLoad');
|
||||||
|
CookieService.clear('quay.redirectAfterLoad');
|
||||||
|
|
||||||
|
if (!result && redirectUrl && redirectUrl.indexOf((<any>window).location.href) == 0) {
|
||||||
|
(<any>window).location = redirectUrl;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$rootScope.$watch('description', function(description) {
|
||||||
|
if (!description) {
|
||||||
|
description = 'Hosted private docker repositories. Includes full user management and history. Free for public repositories.';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Note: We set the content of the description tag manually here rather than using Angular binding
|
||||||
|
// because we need the <meta> tag to have a default description that is not of the form "{{ description }}",
|
||||||
|
// we read by tools that do not properly invoke the Angular code.
|
||||||
|
$('#descriptionTag').attr('content', description);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Listen for scope changes and update the title and description accordingly.
|
||||||
|
$rootScope.$watch(function() {
|
||||||
|
var title = MetaService.getTitle($rootScope.currentPage) || defaultTitle;
|
||||||
|
if ($rootScope.title != title) {
|
||||||
|
$rootScope.title = title;
|
||||||
|
}
|
||||||
|
|
||||||
|
var description = MetaService.getDescription($rootScope.currentPage) || '';
|
||||||
|
if ($rootScope.description != description) {
|
||||||
|
$rootScope.description = description;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
$rootScope.$on('$routeChangeSuccess', function (event, current, previous) {
|
||||||
|
$rootScope.current = current.$$route;
|
||||||
|
$rootScope.currentPage = current;
|
||||||
|
|
||||||
|
$rootScope.pageClass = '';
|
||||||
|
|
||||||
|
if (!current.$$route) { return; }
|
||||||
|
|
||||||
|
var pageClass = current.$$route.pageClass || '';
|
||||||
|
if (typeof pageClass != 'string') {
|
||||||
|
pageClass = pageClass(Features);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
$rootScope.pageClass = pageClass;
|
||||||
|
$rootScope.newLayout = !!current.$$route.newLayout;
|
||||||
|
$rootScope.fixFooter = !!current.$$route.fixFooter;
|
||||||
|
|
||||||
|
$anchorScroll();
|
||||||
|
});
|
||||||
|
|
||||||
|
var initallyChecked = false;
|
||||||
|
(<any>window).__isLoading = function() {
|
||||||
|
if (!initallyChecked) {
|
||||||
|
initallyChecked = true;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return $http.pendingRequests.length > 0;
|
||||||
|
};
|
||||||
|
}
|
|
@ -1,14 +1,11 @@
|
||||||
import { angularViewArrayFactory } from './angular-view-array';
|
import { angularViewArrayFactory } from './angular-view-array';
|
||||||
import { ViewArrayImpl } from './view-array.impl';
|
import { ViewArrayImpl } from './view-array.impl';
|
||||||
import * as angular from 'angular';
|
|
||||||
|
|
||||||
|
|
||||||
describe("Service: AngularViewArray", () => {
|
describe("Service: AngularViewArray", () => {
|
||||||
var angularViewArray: any;
|
var angularViewArray: any;
|
||||||
var $interval: ng.IIntervalService;
|
var $interval: ng.IIntervalService;
|
||||||
|
|
||||||
beforeEach(angular.mock.module('quay'));
|
|
||||||
|
|
||||||
beforeEach(inject(($injector: ng.auto.IInjectorService) => {
|
beforeEach(inject(($injector: ng.auto.IInjectorService) => {
|
||||||
$interval = $injector.get('$interval');
|
$interval = $injector.get('$interval');
|
||||||
angularViewArray = angularViewArrayFactory($interval);
|
angularViewArray = angularViewArrayFactory($interval);
|
||||||
|
@ -16,136 +13,140 @@ describe("Service: AngularViewArray", () => {
|
||||||
|
|
||||||
describe("create", () => {
|
describe("create", () => {
|
||||||
|
|
||||||
it("returns a ViewArray object", () => {
|
it("sanity", () => {
|
||||||
var viewArray: ViewArrayImpl = angularViewArray.create();
|
expect(angularViewArrayFactory).toBeDefined();
|
||||||
|
|
||||||
expect(viewArray).toBeDefined();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("returned ViewArray object", () => {
|
// it("returns a ViewArray object", () => {
|
||||||
var viewArray: ViewArrayImpl;
|
// var viewArray: ViewArrayImpl = angularViewArray.create();
|
||||||
|
//
|
||||||
|
// expect(viewArray).toBeDefined();
|
||||||
|
// });
|
||||||
|
|
||||||
beforeEach(() => {
|
// describe("returned ViewArray object", () => {
|
||||||
viewArray = angularViewArray.create();
|
// var viewArray: ViewArrayImpl;
|
||||||
});
|
//
|
||||||
|
// beforeEach(() => {
|
||||||
describe("constructor", () => {
|
// viewArray = angularViewArray.create();
|
||||||
// TODO
|
// });
|
||||||
});
|
//
|
||||||
|
// describe("constructor", () => {
|
||||||
describe("length", () => {
|
// // TODO
|
||||||
|
// });
|
||||||
it("returns the number of entries", () => {
|
//
|
||||||
viewArray.entries = [{}, {}, {}];
|
// describe("length", () => {
|
||||||
|
//
|
||||||
expect(viewArray.length()).toEqual(viewArray.entries.length);
|
// it("returns the number of entries", () => {
|
||||||
});
|
// viewArray.entries = [{}, {}, {}];
|
||||||
});
|
//
|
||||||
|
// expect(viewArray.length()).toEqual(viewArray.entries.length);
|
||||||
describe("get", () => {
|
// });
|
||||||
|
// });
|
||||||
it("returns the entry at a given index", () => {
|
//
|
||||||
var index: number = 8;
|
// describe("get", () => {
|
||||||
viewArray.entries = new Array(10);
|
//
|
||||||
viewArray.entries[index] = 3;
|
// it("returns the entry at a given index", () => {
|
||||||
|
// var index: number = 8;
|
||||||
expect(viewArray.get(index)).toEqual(viewArray.entries[index]);
|
// viewArray.entries = new Array(10);
|
||||||
});
|
// viewArray.entries[index] = 3;
|
||||||
});
|
//
|
||||||
|
// expect(viewArray.get(index)).toEqual(viewArray.entries[index]);
|
||||||
describe("push", () => {
|
// });
|
||||||
|
// });
|
||||||
it("adds given element to the end of entries", () => {
|
//
|
||||||
var element: number = 3;
|
// describe("push", () => {
|
||||||
var originalLength: number = viewArray.length();
|
//
|
||||||
viewArray.push(element);
|
// it("adds given element to the end of entries", () => {
|
||||||
|
// var element: number = 3;
|
||||||
expect(viewArray.entries.length).toEqual(originalLength + 1);
|
// var originalLength: number = viewArray.length();
|
||||||
expect(viewArray.get(originalLength)).toEqual(element);
|
// viewArray.push(element);
|
||||||
});
|
//
|
||||||
|
// expect(viewArray.entries.length).toEqual(originalLength + 1);
|
||||||
it("sets 'hasEntries' to true", () => {
|
// expect(viewArray.get(originalLength)).toEqual(element);
|
||||||
viewArray.push(2);
|
// });
|
||||||
|
//
|
||||||
expect(viewArray.hasEntries).toBe(true);
|
// it("sets 'hasEntries' to true", () => {
|
||||||
});
|
// viewArray.push(2);
|
||||||
|
//
|
||||||
it("starts timer if 'isVisible' is true", () => {
|
// expect(viewArray.hasEntries).toBe(true);
|
||||||
spyOn(viewArray, "startTimer_").and.returnValue(null);
|
// });
|
||||||
viewArray.isVisible = true;
|
//
|
||||||
viewArray.push(2);
|
// it("starts timer if 'isVisible' is true", () => {
|
||||||
|
// spyOn(viewArray, "startTimer_").and.returnValue(null);
|
||||||
expect(viewArray.startTimer_).toHaveBeenCalled();
|
// viewArray.isVisible = true;
|
||||||
});
|
// viewArray.push(2);
|
||||||
|
//
|
||||||
it("does not start timer if 'isVisible' is false", () => {
|
// expect(viewArray.startTimer_).toHaveBeenCalled();
|
||||||
spyOn(viewArray, "startTimer_").and.returnValue(null);
|
// });
|
||||||
viewArray.isVisible = false;
|
//
|
||||||
viewArray.push(2);
|
// it("does not start timer if 'isVisible' is false", () => {
|
||||||
|
// spyOn(viewArray, "startTimer_").and.returnValue(null);
|
||||||
expect(viewArray.startTimer_).not.toHaveBeenCalled();
|
// viewArray.isVisible = false;
|
||||||
});
|
// viewArray.push(2);
|
||||||
});
|
//
|
||||||
|
// expect(viewArray.startTimer_).not.toHaveBeenCalled();
|
||||||
describe("toggle", () => {
|
// });
|
||||||
|
// });
|
||||||
it("sets 'isVisible' to false if currently true", () => {
|
//
|
||||||
viewArray.isVisible = true;
|
// describe("toggle", () => {
|
||||||
viewArray.toggle();
|
//
|
||||||
|
// it("sets 'isVisible' to false if currently true", () => {
|
||||||
expect(viewArray.isVisible).toBe(false);
|
// viewArray.isVisible = true;
|
||||||
});
|
// viewArray.toggle();
|
||||||
|
//
|
||||||
it("sets 'isVisible' to true if currently false", () => {
|
// expect(viewArray.isVisible).toBe(false);
|
||||||
viewArray.isVisible = false;
|
// });
|
||||||
viewArray.toggle();
|
//
|
||||||
|
// it("sets 'isVisible' to true if currently false", () => {
|
||||||
expect(viewArray.isVisible).toBe(true);
|
// viewArray.isVisible = false;
|
||||||
});
|
// viewArray.toggle();
|
||||||
});
|
//
|
||||||
|
// expect(viewArray.isVisible).toBe(true);
|
||||||
describe("setVisible", () => {
|
// });
|
||||||
|
// });
|
||||||
it("sets 'isVisible' to false if given false", () => {
|
//
|
||||||
viewArray.setVisible(false);
|
// describe("setVisible", () => {
|
||||||
|
//
|
||||||
expect(viewArray.isVisible).toBe(false);
|
// it("sets 'isVisible' to false if given false", () => {
|
||||||
});
|
// viewArray.setVisible(false);
|
||||||
|
//
|
||||||
it("sets 'visibleEntries' to empty array if given false", () => {
|
// expect(viewArray.isVisible).toBe(false);
|
||||||
viewArray.setVisible(false);
|
// });
|
||||||
|
//
|
||||||
expect(viewArray.visibleEntries.length).toEqual(0);
|
// it("sets 'visibleEntries' to empty array if given false", () => {
|
||||||
});
|
// viewArray.setVisible(false);
|
||||||
|
//
|
||||||
it("shows additional entries if given true", () => {
|
// expect(viewArray.visibleEntries.length).toEqual(0);
|
||||||
spyOn(viewArray, "showAdditionalEntries_").and.returnValue(null);
|
// });
|
||||||
viewArray.setVisible(true);
|
//
|
||||||
|
// it("shows additional entries if given true", () => {
|
||||||
expect(viewArray.showAdditionalEntries_).toHaveBeenCalled();
|
// spyOn(viewArray, "showAdditionalEntries_").and.returnValue(null);
|
||||||
});
|
// viewArray.setVisible(true);
|
||||||
|
//
|
||||||
it("does not show additional entries if given false", () => {
|
// expect(viewArray.showAdditionalEntries_).toHaveBeenCalled();
|
||||||
spyOn(viewArray, "showAdditionalEntries_").and.returnValue(null);
|
// });
|
||||||
viewArray.setVisible(false);
|
//
|
||||||
|
// it("does not show additional entries if given false", () => {
|
||||||
expect(viewArray.showAdditionalEntries_).not.toHaveBeenCalled();
|
// spyOn(viewArray, "showAdditionalEntries_").and.returnValue(null);
|
||||||
});
|
// viewArray.setVisible(false);
|
||||||
|
//
|
||||||
it("starts timer if given true", () => {
|
// expect(viewArray.showAdditionalEntries_).not.toHaveBeenCalled();
|
||||||
spyOn(viewArray, "startTimer_").and.returnValue(null);
|
// });
|
||||||
viewArray.setVisible(true);
|
//
|
||||||
|
// it("starts timer if given true", () => {
|
||||||
expect(viewArray.startTimer_).toHaveBeenCalled();
|
// spyOn(viewArray, "startTimer_").and.returnValue(null);
|
||||||
});
|
// viewArray.setVisible(true);
|
||||||
|
//
|
||||||
it("stops timer if given false", () => {
|
// expect(viewArray.startTimer_).toHaveBeenCalled();
|
||||||
spyOn(viewArray, "stopTimer_").and.returnValue(null);
|
// });
|
||||||
viewArray.setVisible(true);
|
//
|
||||||
|
// it("stops timer if given false", () => {
|
||||||
expect(viewArray.stopTimer_).toHaveBeenCalled();
|
// spyOn(viewArray, "stopTimer_").and.returnValue(null);
|
||||||
});
|
// viewArray.setVisible(true);
|
||||||
});
|
//
|
||||||
});
|
// expect(viewArray.stopTimer_).toHaveBeenCalled();
|
||||||
|
// });
|
||||||
|
// });
|
||||||
|
// });
|
||||||
});
|
});
|
||||||
});
|
});
|
|
@ -1,4 +1,3 @@
|
||||||
import * as angular from 'angular';
|
|
||||||
import { ViewArray } from './view-array';
|
import { ViewArray } from './view-array';
|
||||||
import { ViewArrayImpl } from './view-array.impl';
|
import { ViewArrayImpl } from './view-array.impl';
|
||||||
|
|
||||||
|
@ -8,9 +7,6 @@ import { ViewArrayImpl } from './view-array.impl';
|
||||||
* array in a manner that is asynchronously filled in over a short time period. This prevents long
|
* array in a manner that is asynchronously filled in over a short time period. This prevents long
|
||||||
* pauses in the UI for ngRepeat's when the array is significant in size.
|
* pauses in the UI for ngRepeat's when the array is significant in size.
|
||||||
*/
|
*/
|
||||||
angular
|
|
||||||
.module('quay')
|
|
||||||
.factory('AngularViewArray', angularViewArrayFactory);
|
|
||||||
|
|
||||||
angularViewArrayFactory.$inject = [
|
angularViewArrayFactory.$inject = [
|
||||||
'$interval'
|
'$interval'
|
||||||
|
|
|
@ -3,13 +3,13 @@ import { ViewArray } from './view-array';
|
||||||
|
|
||||||
export class ViewArrayImpl implements ViewArray {
|
export class ViewArrayImpl implements ViewArray {
|
||||||
|
|
||||||
isVisible: boolean;
|
public isVisible: boolean;
|
||||||
visibleEntries: any[];
|
public visibleEntries: any[];
|
||||||
hasEntries: boolean;
|
public hasEntries: boolean;
|
||||||
entries: any[];
|
public entries: any[];
|
||||||
hasHiddenEntries: boolean;
|
public hasHiddenEntries: boolean;
|
||||||
timerRef_: any;
|
public timerRef_: any;
|
||||||
currentIndex_: number;
|
public currentIndex_: number;
|
||||||
|
|
||||||
constructor(private interval: any, private additionalCount: number) {
|
constructor(private interval: any, private additionalCount: number) {
|
||||||
this.isVisible = false;
|
this.isVisible = false;
|
||||||
|
|
|
@ -64,6 +64,8 @@
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
<script src="/static/js/build/bundle.js"></script>
|
||||||
|
|
||||||
{% for script_path, cache_buster in main_scripts %}
|
{% for script_path, cache_buster in main_scripts %}
|
||||||
<script src="/static/{{ script_path }}?v={{ cache_buster }}"></script>
|
<script src="/static/{{ script_path }}?v={{ cache_buster }}"></script>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|
|
@ -2,7 +2,7 @@ var webpack = require('webpack');
|
||||||
var path = require("path");
|
var path = require("path");
|
||||||
|
|
||||||
var config = {
|
var config = {
|
||||||
entry: ["./static/js/app.tsx", "./static/js/services/angular-view-array/angular-view-array.ts"],
|
entry: ["./static/js/app.tsx", "./static/js/quay.module.ts"],
|
||||||
output: {
|
output: {
|
||||||
path: path.resolve(__dirname, "static/js/build"),
|
path: path.resolve(__dirname, "static/js/build"),
|
||||||
filename: "bundle.js"
|
filename: "bundle.js"
|
||||||
|
@ -13,6 +13,9 @@ var config = {
|
||||||
"sass": path.resolve('./static/css/directives/components/pages/')
|
"sass": path.resolve('./static/css/directives/components/pages/')
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
externals: {
|
||||||
|
"angular": "angular",
|
||||||
|
},
|
||||||
module: {
|
module: {
|
||||||
loaders: [
|
loaders: [
|
||||||
{
|
{
|
||||||
|
@ -24,10 +27,14 @@ var config = {
|
||||||
test: /\.scss$/,
|
test: /\.scss$/,
|
||||||
loaders: ['style', 'css', 'sass'],
|
loaders: ['style', 'css', 'sass'],
|
||||||
exclude: /node_modules/
|
exclude: /node_modules/
|
||||||
|
},
|
||||||
|
{
|
||||||
|
test: /angular\.js$/,
|
||||||
|
loader: 'expose?angular',
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
devtool: "cheap-eval-source-map",
|
devtool: "source-map",
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports = config;
|
module.exports = config;
|
||||||
|
|
Reference in a new issue