diff --git a/package.json b/package.json index 511a1549b..24906dac4 100644 --- a/package.json +++ b/package.json @@ -17,6 +17,7 @@ "@types/react": "0.14.39", "@types/react-dom": "0.14.17", "angular": "1.5.8", + "moment": "2.16.0", "ngreact": "0.3.0", "react": "15.3.2", "react-dom": "15.3.2", diff --git a/static/css/directives/components/pages/_mixins.scss b/static/css/directives/components/pages/_mixins.scss new file mode 100644 index 000000000..c3aef8b2b --- /dev/null +++ b/static/css/directives/components/pages/_mixins.scss @@ -0,0 +1,10 @@ +/* + A list of useful mixins +*/ + +@mixin box-shadow($args...) { + -webkit-box-shadow: $args; + -moz-box-shadow: $args; + box-shadow: $args; + -o-box-shadow: $args; +} diff --git a/static/css/directives/components/pages/repo-page/repo-page.scss b/static/css/directives/components/pages/repo-page/repo-page.scss index b5ce5fc36..899287260 100644 --- a/static/css/directives/components/pages/repo-page/repo-page.scss +++ b/static/css/directives/components/pages/repo-page/repo-page.scss @@ -1 +1,148 @@ // Repo Page specific styles here +@import "../mixins"; + +.rp-badge { + float: left; + width: 100%; + margin-bottom: 20px; +} + +.rp-badge__icon { + float: left; + height: 25px; + font-size: 16px; + padding: 0 12px; + color: #ffffff; +} + +.rp-badge__icon--private { + @extend .rp-badge__icon; + background-color: #d64456; +} + +.rp-badge__icon--public { + @extend .rp-badge__icon; + background-color: #2fc98e; +} + +.rp-description { + font-size: 16px; +} + +.rp-header { + padding: 30px; +} + +.rp-header__row { + margin: 0; +} + +.rp-imagesHeader { + font-size: 18px; + margin-bottom: 30px; +} + +.rp-imagesTable { + margin-bottom: 30px; +} + +.rp-imagesTable__headerCell { + font-size: 13px; + font-weight: 300; + font-style: normal; + color: #999; + padding: 10px; + border-bottom: 1px solid #ddd; +} + +.rp-imagesTable__tagIcon { + padding-right: 4px; +} + +.rp-mainPanel { + margin-bottom: 20px; + background-color: #fff; + @include box-shadow(0px 2px 2px rgba(0, 0, 0, 0.4)); + overflow: hidden; +} + +.rp-main { + padding: 0; + border-right: 1px solid #ddd; +} + +.rp-panelBody { + padding: 15px 30px; +} + +.rp-sharing { + font-size: 16px; + color: #333; + margin-bottom: 30px; +} + +.rp-sidebar { + padding: 30px 30px 0 30px; + border-left: 1px solid #DDD; +} + +.rp-tabs { + border-bottom: 1px solid #DDD; +} +.rp-tabs > li.active > a, +.rp-tabs > li.active > a:focus, +.rp-tabs > li.active > a:hover { + border-width: 0; +} + +.rp-tabs { + padding: 0 15px; + font-size: 20px; + + li.active a { + color: #51a3d9; + border: none; + border-bottom: 1px solid #51a3d9; + + &:hover { + color: #51a3d9; + border: none; + border-bottom: 1px solid #51a3d9; + } + } + + li a { + color: #333; + + &:focus, + &:hover { + border: none; + background-color: #fff; + } + } +} + +.rp-title { + font-size: 24px; + color: #333; + float: left; +} + +.rp-button { + float: right; + margin-right: 30px; +} + +.rp-button__dropdown { + background-color: #fff; + border-radius: 4px; + box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.25), 0 0 1px 0 rgba(0, 0, 0, 0.5), inset 0 1px 0 0 rgba(255, 255, 255, 0.2); +} + +.rp-button__text { + margin-right: 10px; +} + +.rp-button__text--bold { + font-weight: 600; +} diff --git a/static/js/directives/components/pages/repo-page/body.tsx b/static/js/directives/components/pages/repo-page/body.tsx index 6b145c0b1..e3d3fedef 100644 --- a/static/js/directives/components/pages/repo-page/body.tsx +++ b/static/js/directives/components/pages/repo-page/body.tsx @@ -1,8 +1,32 @@ import * as React from "react"; -class body extends React.Component<{}, {}> { +interface IMain { + description: string +} + +class body extends React.Component { + static propTypes = { + description: React.PropTypes.string.isRequired, + } render () { - return
The component for the main content
; + return( +
+ +
+
+
+
{this.props.description}
+
+
+ TODO (IAN): Convert the build directives to angular components +
+
+
+
+ ); } } diff --git a/static/js/directives/components/pages/repo-page/header.tsx b/static/js/directives/components/pages/repo-page/header.tsx index 606345da8..224327cd4 100644 --- a/static/js/directives/components/pages/repo-page/header.tsx +++ b/static/js/directives/components/pages/repo-page/header.tsx @@ -1,8 +1,36 @@ import * as React from "react"; -class repoHeader extends React.Component<{}, {}> { +interface IHeader { + name: string, + namespace: string +} + +class repoHeader extends React.Component { + static propTypes = { + name: React.PropTypes.string.isRequired, + namespace: React.PropTypes.string.isRequired + } render () { - return
The component for the header
; + return( +
+
{this.props.namespace}/{this.props.name}
+
+
+ + +
+
+
+ ); } } diff --git a/static/js/directives/components/pages/repo-page/sidebar.tsx b/static/js/directives/components/pages/repo-page/sidebar.tsx index fe51ae950..ff9ea5e5e 100644 --- a/static/js/directives/components/pages/repo-page/sidebar.tsx +++ b/static/js/directives/components/pages/repo-page/sidebar.tsx @@ -1,8 +1,84 @@ import * as React from "react"; +import * as moment from "moment"; -class repoSidebar extends React.Component<{}, {}> { +interface tag { + image_id: string; + last_modified: string; + name: string; + size: number; +} + +interface ISidebar { + isPublic: string, + tags: Array, + repository: Object +} + +class repoSidebar extends React.Component { + static propTypes = { + isPublic: React.PropTypes.string.isRequired, + tags: React.PropTypes.array.isRequired, + repository: React.PropTypes.object.isRequired + } render () { - return
The component for the sidebar
; + let isPublic: string = (this.props.isPublic) ? "Public" : "Private"; + let sortedTags: Array = []; + let tagRows: Array = []; + let badgeIcon: string = (this.props.isPublic) ? "rp-badge__icon--public" : "rp-badge__icon--private"; + let repository: any = this.props.repository; + let sharing: string = repository.company || repository.namespace; + for (let t in this.props.tags){ + sortedTags.push( + { + name: this.props.tags[t].name, + lastModified: Date.parse(this.props.tags[t].last_modified) + } + ); + } + + sortedTags = sortedTags.sort(function(a,b){return a.lastModified - b.lastModified}); + sortedTags.forEach(function(el, i){ + tagRows.push( + + + + {el.name} + + + {moment(el.lastModified).fromNow()} + + + ); + }); + + return( +
+
+
+ {isPublic} +
+
+
+ {sharing} is sharing this container {this.props.isPublic ? "publically" : "privately"} +
+
+ Latest Images +
+
+ + + + + + + + + {tagRows} + +
NAMELAST MODIFIED
+
+
+ ); } } diff --git a/static/js/pages/repo-view.js b/static/js/pages/repo-view.js index 3810f294c..8053e673f 100644 --- a/static/js/pages/repo-view.js +++ b/static/js/pages/repo-view.js @@ -61,10 +61,13 @@ $scope.repository = repo; $scope.viewScope.repository = repo; $scope.publicRepoExperiment = CookieService.get('quay.public-repo-exp') == 'true'; + if (!$scope.repository.description){ + $scope.repository.description = "No Description"; + } // Flag for new repo page experiment $scope.newRepoExperiment = $scope.repository.is_public && $scope.user.username != $scope.repository.namespace && $scope.publicRepoExperiment; - + // Load the remainder of the data async, so we don't block the initial view from // showing. $timeout(function() { diff --git a/static/partials/repo-view.html b/static/partials/repo-view.html index 4a4a368c2..c7427a82f 100644 --- a/static/partials/repo-view.html +++ b/static/partials/repo-view.html @@ -3,11 +3,22 @@ error-message="'Repository not found'">
-
+
- - - +
+ +
+ +
+ +
+ +
+
+ +
+ +