From 58a19f1425a854e38e2a7ec2207021641893646a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20Juli=C3=A1n?= Date: Thu, 11 Aug 2016 16:51:50 +0200 Subject: [PATCH] Header detail WIP --- app/coffee/modules/common/components.coffee | 168 ++++++------ .../belong-to-epics/belong-to-epics.scss | 1 + .../header/story-header.controller.coffee | 67 +++++ .../header/story-header.directive.coffee | 42 +++ app/modules/stories/header/story-header.jade | 76 ++++++ app/modules/stories/header/story-header.scss | 251 ++++++++++++++++++ app/partials/us/us-detail.jade | 98 ++++--- app/styles/layout/ticket-detail.scss | 180 ------------- 8 files changed, 567 insertions(+), 316 deletions(-) create mode 100644 app/modules/stories/header/story-header.controller.coffee create mode 100644 app/modules/stories/header/story-header.directive.coffee create mode 100644 app/modules/stories/header/story-header.jade create mode 100644 app/modules/stories/header/story-header.scss diff --git a/app/coffee/modules/common/components.coffee b/app/coffee/modules/common/components.coffee index a6f4fffb..d1c0cea5 100644 --- a/app/coffee/modules/common/components.coffee +++ b/app/coffee/modules/common/components.coffee @@ -493,90 +493,90 @@ DeleteButtonDirective = ($log, $repo, $confirm, $location, $template) -> module.directive("tgDeleteButton", ["$log", "$tgRepo", "$tgConfirm", "$tgLocation", "$tgTemplate", DeleteButtonDirective]) -############################################################################# -## Editable subject directive -############################################################################# - -EditableSubjectDirective = ($rootscope, $repo, $confirm, $loading, $modelTransform, $template) -> - template = $template.get("common/components/editable-subject.html") - - link = ($scope, $el, $attrs, $model) -> - - $scope.$on "object:updated", () -> - $el.find('.edit-subject').hide() - $el.find('.view-subject').show() - - isEditable = -> - return $scope.project.my_permissions.indexOf($attrs.requiredPerm) != -1 - - save = (subject) -> - currentLoading = $loading() - .target($el.find('.save-container')) - .start() - - transform = $modelTransform.save (item) -> - item.subject = subject - - return item - - transform.then => - $confirm.notify("success") - $rootscope.$broadcast("object:updated") - $el.find('.edit-subject').hide() - $el.find('.view-subject').show() - - transform.then null, -> - $confirm.notify("error") - - transform.finally -> - currentLoading.finish() - - return transform - - $el.click -> - return if not isEditable() - $el.find('.edit-subject').show() - $el.find('.view-subject').hide() - $el.find('input').focus() - - $el.on "click", ".save", (e) -> - e.preventDefault() - - subject = $scope.item.subject - save(subject) - - $el.on "keyup", "input", (event) -> - if event.keyCode == 13 - subject = $scope.item.subject - save(subject) - else if event.keyCode == 27 - $scope.$apply () => $model.$modelValue.revert() - - $el.find('.edit-subject').hide() - $el.find('.view-subject').show() - - $el.find('.edit-subject').hide() - - $scope.$watch $attrs.ngModel, (value) -> - return if not value - $scope.item = value - - if not isEditable() - $el.find('.view-subject .edit').remove() - - $scope.$on "$destroy", -> - $el.off() - - - return { - link: link - restrict: "EA" - require: "ngModel" - template: template - } - -module.directive("tgEditableSubject", ["$rootScope", "$tgRepo", "$tgConfirm", "$tgLoading", "$tgQueueModelTransformation", - "$tgTemplate", EditableSubjectDirective]) +# ############################################################################# +# ## Editable subject directive +# ############################################################################# +# +# EditableSubjectDirective = ($rootscope, $repo, $confirm, $loading, $modelTransform, $template) -> +# template = $template.get("common/components/editable-subject.html") +# +# link = ($scope, $el, $attrs, $model) -> +# +# $scope.$on "object:updated", () -> +# $el.find('.edit-subject').hide() +# $el.find('.view-subject').show() +# +# isEditable = -> +# return $scope.project.my_permissions.indexOf($attrs.requiredPerm) != -1 +# +# save = (subject) -> +# currentLoading = $loading() +# .target($el.find('.save-container')) +# .start() +# +# transform = $modelTransform.save (item) -> +# item.subject = subject +# +# return item +# +# transform.then => +# $confirm.notify("success") +# $rootscope.$broadcast("object:updated") +# $el.find('.edit-subject').hide() +# $el.find('.view-subject').show() +# +# transform.then null, -> +# $confirm.notify("error") +# +# transform.finally -> +# currentLoading.finish() +# +# return transform +# +# $el.click -> +# return if not isEditable() +# $el.find('.edit-subject').show() +# $el.find('.view-subject').hide() +# $el.find('input').focus() +# +# $el.on "click", ".save", (e) -> +# e.preventDefault() +# +# subject = $scope.item.subject +# save(subject) +# +# $el.on "keyup", "input", (event) -> +# if event.keyCode == 13 +# subject = $scope.item.subject +# save(subject) +# else if event.keyCode == 27 +# $scope.$apply () => $model.$modelValue.revert() +# +# $el.find('.edit-subject').hide() +# $el.find('.view-subject').show() +# +# $el.find('.edit-subject').hide() +# +# $scope.$watch $attrs.ngModel, (value) -> +# return if not value +# $scope.item = value +# +# if not isEditable() +# $el.find('.view-subject .edit').remove() +# +# $scope.$on "$destroy", -> +# $el.off() +# +# +# return { +# link: link +# restrict: "EA" +# require: "ngModel" +# template: template +# } +# +# module.directive("tgEditableSubject", ["$rootScope", "$tgRepo", "$tgConfirm", "$tgLoading", "$tgQueueModelTransformation", +# "$tgTemplate", EditableSubjectDirective]) ############################################################################# diff --git a/app/modules/components/belong-to-epics/belong-to-epics.scss b/app/modules/components/belong-to-epics/belong-to-epics.scss index fd487f3c..e59c4fb0 100644 --- a/app/modules/components/belong-to-epics/belong-to-epics.scss +++ b/app/modules/components/belong-to-epics/belong-to-epics.scss @@ -13,6 +13,7 @@ border-radius: 50%; display: inline-block; height: .7rem; + margin: 0 .1rem; position: relative; width: .7rem; } diff --git a/app/modules/stories/header/story-header.controller.coffee b/app/modules/stories/header/story-header.controller.coffee new file mode 100644 index 00000000..7fb78177 --- /dev/null +++ b/app/modules/stories/header/story-header.controller.coffee @@ -0,0 +1,67 @@ +### +# Copyright (C) 2014-2015 Taiga Agile LLC +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +# File: epics.dashboard.controller.coffee +### + +module = angular.module("taigaUserStories") + +class StoryHeaderController + @.$inject = [ + "$rootScope", + "$tgConfirm", + "$tgQueueModelTransformation" + ] + + constructor: (@rootScope, @confirm, @modelTransform) -> + @.editMode = false + @.loadingSubject = false + @.originalSubject = @.item.subject + + _checkPermissions: () -> + @.permissions = { + canEdit: _.includes(@.project.my_permissions, @.requiredPerm) + } + + editSubject: (value) -> + if value + @.editMode = true + if !value + @.editMode = false + + onCancelEdition: (event) -> + if event.which == 27 + @.item.subject = @.originalSubject + @.editSubject(false) + + saveSubject: () -> + onEditSubjectSuccess = () => + @.loadingSubject = false + @rootScope.$broadcast("object:updated") + @confirm.notify('success') + + onEditSubjectError = () => + @.loadingSubject = false + @confirm.notify('error') + + @.editMode = false + @.loadingSubject = true + item = @.item + transform = @modelTransform.save (item) -> + return item + return transform.then(onEditSubjectSuccess, onEditSubjectError) + +module.controller("StoryHeaderCtrl", StoryHeaderController) diff --git a/app/modules/stories/header/story-header.directive.coffee b/app/modules/stories/header/story-header.directive.coffee new file mode 100644 index 00000000..58d9088a --- /dev/null +++ b/app/modules/stories/header/story-header.directive.coffee @@ -0,0 +1,42 @@ +### +# Copyright (C) 2014-2016 Taiga Agile LLC +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +# File: story-header.directive.coffee +### + +module = angular.module('taigaUserStories') + +DetailHeaderDirective = () -> + @.$inject = [] + + link = (scope, el, attrs, ctrl) -> + ctrl._checkPermissions() + + return { + link: link, + controller: "StoryHeaderCtrl", + bindToController: true, + scope: { + item: "=", + project: "=", + requiredPerm: "@" + }, + controllerAs: "vm", + templateUrl:"stories/header/story-header.html" + } + + +module.directive("tgDetailHeader", DetailHeaderDirective) diff --git a/app/modules/stories/header/story-header.jade b/app/modules/stories/header/story-header.jade new file mode 100644 index 00000000..8c42b97f --- /dev/null +++ b/app/modules/stories/header/story-header.jade @@ -0,0 +1,76 @@ +.detail-title-wrapper + h2.detail-title-text.ng-animate-disabled( + ng-show="!vm.editMode" + ng-hide="vm.editMode" + ) + span.detail-number {{'#' + vm.item.ref}} + span.detail-subject( + ng-click="vm.editSubject(true)" + ng-if="vm.permissions.canEdit" + ) {{vm.item.subject}} + span.detail-subject( + ng-if="!vm.permissions.canEdit" + ) {{vm.item.subject}} + tg-svg.detail-edit( + ng-if="vm.permissions.canEdit" + svg-icon="icon-edit" + ng-click="vm.editSubject(true)" + ) + + .edit-title-wrapper(ng-if="vm.editMode") + input.edit-title-input( + type="text" + ng-model="vm.item.subject" + maxlength="500" + autofocus + required + ng-keydown="vm.onCancelEdition($event)" + ) + button.edit-title-button( + ng-click="vm.saveSubject()" + tg-loading="vm.loadingSubject" + ) + tg-svg( + svg-icon="icon-save" + ) +.belong-to-epics-wrapper(ng-if="vm.item.epics") + span This User Story belongs to + tg-belong-to-epics( + ng-if="::vm.item.epics" + epics="::vm.item.epics" + format="text" + project="project" + ) + +.item-origin-issue( + ng-if="vm.item.origin_issue" +) + span(translate="US.PROMOTED") + a( + href="" + tg-check-permission="view_us" + tg-nav="project-issues-detail:project=vm.project.slug,ref=vm.item.origin_issue.ref" + title="{{'US.TITLE_LINK_GO_TO_ISSUE' | translate}}" + ) + span {{'#' + vm.item.origin_issue.ref}} + span {{vm.item.origin_issue.subject}} + +.block-desc-container(ng-show="vm.item.is_blocked") + span.block-description-title(translate="COMMON.BLOCKED") + span.block-description( + ng-if="vm.item.blocked_note" + ) {{vm.item.blocked_note}} + +.issue-nav + a( + ng-show="previousUrl" + tg-bo-href="previousUrl" + title="{{'US.PREVIOUS' | translate}}" + ) + tg-svg(svg-icon="icon-arrow-left") + a( + ng-show="nextUrl" + tg-bo-href="nextUrl" + title="{{'US.NEXT' | translate}}" + ) + tg-svg(svg-icon="icon-arrow-right") diff --git a/app/modules/stories/header/story-header.scss b/app/modules/stories/header/story-header.scss new file mode 100644 index 00000000..a8d0693e --- /dev/null +++ b/app/modules/stories/header/story-header.scss @@ -0,0 +1,251 @@ +.detail-header-container { + background: $mass-white; + flex: 1; + padding: 1rem; + &:hover { + .detail-edit { + opacity: 1; + } + } + &.blocked { + background: $red; + color: $white; + transition: all .2s linear; + a, + .detail-number, + .detail-subject { + color: $white; + } + svg { + fill: $white; + } + } + .item-origin-issue, + .belong-to-epics-wrapper, + .block-desc-container { + @include font-size(small); + margin-top: .5rem; + } + .item-origin-issue { + a { + padding: 0 .2rem; + } + } +} + +.detail-title-wrapper { + @include font-size(larger); + @include font-type(text); + align-content: center; + display: flex; + position: relative; + transition: all .2s linear; + &.blocked { + background: $red; + transition: all .2s linear; + } + .detail-title-text { + margin: 0; + } + .detail-number { + color: $gray-light; + flex-shrink: 0; + margin-right: .5rem; + } + .detail-subject { + color: $gray; + flex-grow: 1; + } + + .detail-edit { + cursor: pointer; + margin-left: .75rem; + opacity: 0; + transition: opacity .2s; + svg { + @include svg-size(1.25rem); + } + } +} + +.edit-title-wrapper { + @include font-size(larger); + @include font-type(text); + display: flex; + flex: 1; + .edit-title-input { + background: $white; + flex: 1; + } + .edit-title-button { + background: none; + display: inline; + margin-left: 1rem; + transition: fill .2s; + &:hover { + fill: $primary; + } + } +} + +.block-desc-container { + .block-description-title { + @include font-type(bold); + margin-right: .5rem; + + } +} + +.issue-nav { + position: absolute; + right: 1rem; + top: 1rem; + a { + display: inline-block; + } + svg { + @include svg-size(1.2rem); + fill: currentColor; + } +} + +// +// .us-title { +// +// &.blocked { +// background: $red; +// transition: all .2s linear; +// vertical-align: middle; +// .us-title-text, +// input { +// margin-bottom: .5rem; +// } +// .us-number, +// .us-name, +// .us-related-task { +// color: $white; +// } +// a { +// color: $white; +// transition: color .3s linear; +// } +// a:hover { +// color: $red-light; +// } +// .unblock { +// @include font-type(bold); +// color: $white; +// float: right; +// } +// .unblock:hover { +// color: $red-light; +// transition: color .3s linear; +// } +// } +// p { +// margin-bottom: 0; +// } +// .us-edit-name-inner { +// display: flex; +// } +// .edit-subject { +// align-content: center; +// align-items: center; +// display: flex; +// width: 100%; +// } +// input { +// background: $white; +// flex-grow: 1; +// } +// .save-container { +// flex-grow: 1; +// .save { +// display: block; +// } +// } +// .us-title-text { +// @include font-size(larger); +// @include font-type(text); +// align-content: center; +// align-items: center; +// display: flex; +// flex: 1; +// margin-bottom: 0; +// max-width: 92%; +// width: 100%; +// } +// .us-title-text:hover { +// .edit { +// opacity: 1; +// transition: opacity .3s linear; +// } +// } +// .us-number { +// @include font-type(text); +// color: $gray-light; +// flex-shrink: 0; +// line-height: 2.2rem; +// margin-right: .5rem; +// } +// .us-name { +// color: $gray; +// display: inline-block; +// flex-grow: 1; +// line-height: 2.2rem; +// padding-right: 1rem; +// width: 100%; +// } +// .save, +// .edit { +// cursor: pointer; +// margin-left: .5rem; +// svg { +// fill: $gray-light; +// } +// } +// .edit { +// opacity: 0; +// } +// .us-related-task { +// @include font-size(small); +// color: $gray-light; +// margin-top: .5rem; +// a { +// border-left: 1px solid $gray-light; +// padding: 0 .2rem; +// } +// a:hover { +// color: $primary; +// } +// a:first-child { +// border: 0; +// } +// } +// .block-desc-container { +// @include font-size(small); +// } +// .block-description-title { +// @include font-type(bold); +// color: $white; +// margin-right: .5rem; +// } +// .block-description { +// color: $white; +// display: inline-block; +// margin-right: 5rem; +// } +// } +// .belong-to-epics-wrapper { +// @include font-size(small); +// color: $gray-light; +// margin-top: .5rem; +// a:hover { +// color: $primary; +// } +// } +// .loading-spinner { +// @include loading-spinner; +// max-height: 1.5rem; +// max-width: 1.5rem; +// } diff --git a/app/partials/us/us-detail.jade b/app/partials/us/us-detail.jade index eb3a0b5f..fbf4fd23 100644 --- a/app/partials/us/us-detail.jade +++ b/app/partials/us/us-detail.jade @@ -26,59 +26,53 @@ div.wrapper( on-upvote="ctrl.onUpvote" on-downvote="ctrl.onDownvote" ) - div.us-title(ng-class="{blocked: us.is_blocked}") - h2.us-title-text - span.us-number(tg-bo-ref="us.ref") - span.us-name( - tg-editable-subject - ng-model="us" - required-perm="modify_us" - ) - - p.belong-to-epics-wrapper(ng-if="us.epics") - span This User Story belongs to - tg-belong-to-epics( - ng-if="us.epics" - epics="us.epics" - format="text" - project="project" - ) + tg-detail-header.detail-header-container( + item="us" + project="project" + required-perm="modify_us" + ng-class="{blocked: us.is_blocked}" + ng-if="project && us" + ) - p.us-related-task(ng-if="us.origin_issue") {{ 'US.PROMOTED'|translate }} - a( - href="" - tg-check-permission="view_us" - tg-nav="project-issues-detail:project=project.slug,ref=us.origin_issue.ref" - tg-bo-title="'#' + us.origin_issue.ref + ' ' + us.origin_issue.subject" - title="{{'US.TITLE_LINK_GO_TO_ISSUE' | translate}}" - ) - span(tg-bo-ref="us.origin_issue.ref") - - p.external-reference(ng-if="us.external_reference") - | {{ 'US.EXTERNAL_REFERENCE'|translate }} - a( - tg-bo-href="us.external_reference[1]", - title="{{'US.GO_TO_EXTERNAL_REFERENCE' | translate}}" - target="_blank" - ) - span {{ us.external_reference[1] }} - - p.block-desc-container(ng-show="us.is_blocked") - span.block-description-title(translate="COMMON.BLOCKED") - span.block-description(ng-bind="us.blocked_note || ('US.BLOCKED' | translate)") - div.issue-nav - a( - ng-show="previousUrl" - tg-bo-href="previousUrl" - title="{{'US.PREVIOUS' | translate}}" - ) - tg-svg(svg-icon="icon-arrow-left") - a( - ng-show="nextUrl" - tg-bo-href="nextUrl" - title="{{'US.NEXT' | translate}}" - ) - tg-svg(svg-icon="icon-arrow-right") + //- div.us-title(ng-class="{blocked: us.is_blocked}") + //- h2.us-title-text + //- + //- + //- p.us-related-task(ng-if="us.origin_issue") {{ 'US.PROMOTED'|translate }} + //- a( + //- href="" + //- tg-check-permission="view_us" + //- tg-nav="project-issues-detail:project=project.slug,ref=us.origin_issue.ref" + //- tg-bo-title="'#' + us.origin_issue.ref + ' ' + us.origin_issue.subject" + //- title="{{'US.TITLE_LINK_GO_TO_ISSUE' | translate}}" + //- ) + //- span(tg-bo-ref="us.origin_issue.ref") + //- + //- p.external-reference(ng-if="us.external_reference") + //- | {{ 'US.EXTERNAL_REFERENCE'|translate }} + //- a( + //- tg-bo-href="us.external_reference[1]", + //- title="{{'US.GO_TO_EXTERNAL_REFERENCE' | translate}}" + //- target="_blank" + //- ) + //- span {{ us.external_reference[1] }} + //- + //- p.block-desc-container(ng-show="us.is_blocked") + //- span.block-description-title(translate="COMMON.BLOCKED") + //- span.block-description(ng-bind="us.blocked_note || ('US.BLOCKED' | translate)") + //- div.issue-nav + //- a( + //- ng-show="previousUrl" + //- tg-bo-href="previousUrl" + //- title="{{'US.PREVIOUS' | translate}}" + //- ) + //- tg-svg(svg-icon="icon-arrow-left") + //- a( + //- ng-show="nextUrl" + //- tg-bo-href="nextUrl" + //- title="{{'US.NEXT' | translate}}" + //- ) + //- tg-svg(svg-icon="icon-arrow-right") .subheader tg-tag-line.tags-block( ng-if="us && project" diff --git a/app/styles/layout/ticket-detail.scss b/app/styles/layout/ticket-detail.scss index 5daa3f6a..30af3d0a 100644 --- a/app/styles/layout/ticket-detail.scss +++ b/app/styles/layout/ticket-detail.scss @@ -7,186 +7,6 @@ justify-content: center; margin-bottom: .5rem; } - .us-title { - @include font-size(large); - @include font-type(text); - align-items: flex-start; - background: $mass-white; - display: flex; - flex: 1; - flex-direction: column; - padding: .5rem; - position: relative; - transition: all .2s linear; - &.blocked { - background: $red; - transition: all .2s linear; - vertical-align: middle; - .us-title-text, - input { - margin-bottom: .5rem; - } - .us-number, - .us-name, - .us-related-task { - color: $white; - } - a { - color: $white; - transition: color .3s linear; - } - a:hover { - color: $red-light; - } - .unblock { - @include font-type(bold); - color: $white; - float: right; - } - .unblock:hover { - color: $red-light; - transition: color .3s linear; - } - } - p { - margin-bottom: 0; - } - .us-edit-name-inner { - display: flex; - } - .edit-subject { - align-content: center; - align-items: center; - display: flex; - width: 100%; - } - input { - background: $white; - flex-grow: 1; - } - .save-container { - flex-grow: 1; - .save { - display: block; - } - } - .us-title-text { - @include font-size(larger); - @include font-type(text); - align-content: center; - align-items: center; - display: flex; - flex: 1; - margin-bottom: 0; - max-width: 92%; - width: 100%; - } - .us-title-text:hover { - .edit { - opacity: 1; - transition: opacity .3s linear; - } - } - .us-number { - @include font-type(text); - color: $gray-light; - flex-shrink: 0; - line-height: 2.2rem; - margin-right: .5rem; - } - .us-name { - color: $gray; - display: inline-block; - flex-grow: 1; - line-height: 2.2rem; - padding-right: 1rem; - width: 100%; - } - .save, - .edit { - cursor: pointer; - margin-left: .5rem; - svg { - fill: $gray-light; - } - } - .edit { - opacity: 0; - } - .us-related-task { - @include font-size(small); - color: $gray-light; - margin-top: .5rem; - a { - border-left: 1px solid $gray-light; - padding: 0 .2rem; - } - a:hover { - color: $primary; - } - a:first-child { - border: 0; - } - } - .block-desc-container { - @include font-size(small); - } - .block-description-title { - @include font-type(bold); - color: $white; - margin-right: .5rem; - } - .block-description { - color: $white; - display: inline-block; - margin-right: 5rem; - } - } - .belong-to-epics-wrapper { - @include font-size(small); - color: $gray-light; - margin-top: .5rem; - a:hover { - color: $primary; - } - } - .loading-spinner { - @include loading-spinner; - max-height: 1.5rem; - max-width: 1.5rem; - } -} - -.blocked-warning { - margin-bottom: 1rem; - .blocked { - @include font-type(text); - @include font-size(xlarge); - color: $red; - line-height: 2.5rem; - margin-bottom: .5rem; - } - .icon { - @include font-size(xlarge); - vertical-align: middle; - } - .block-description { - color: $grayer; - margin: 0; - } -} - -.issue-nav { - position: absolute; - right: 1rem; - top: 1rem; - a { - display: inline-block; - } - svg { - @include svg-size(1.2rem); - fill: currentColor; - } } .subheader {