Merge pull request #891 from taigaio/us/3845/redesign-modules

Redesign modules
stable
David Barragán Merino 2016-03-02 10:24:25 +01:00
commit ed50191a41
9 changed files with 259 additions and 186 deletions

View File

@ -6,6 +6,7 @@
### Features
- Ability to create url custom fields. (thanks to [@astagi](https://github.com/astagi)).
- Moved from iconfont to SVG sprite icon system and redesign.
- Redesign 'Admin > Project > Modules' panel.
- Add badge to project owners
### Misc

View File

@ -207,41 +207,51 @@ ProjectModulesDirective = ($repo, $confirm, $loading, projectService) ->
link = ($scope, $el, $attrs) ->
submit = =>
form = $el.find("form").checksley()
form.initializeFields() # Need to reset the form constrains
form.reset() # Need to reset the form constrains
return if not form.validate()
target = angular.element(".admin-functionalities .submit-button")
currentLoading = $loading()
.target(target)
.start()
promise = $repo.save($scope.project)
promise.then ->
currentLoading.finish()
$confirm.notify("success")
$scope.$emit("project:loaded", $scope.project)
$confirm.notify("success")
projectService.fetchProject()
promise.then null, (data) ->
currentLoading.finish()
$confirm.notify("error", data._error_message)
form.setErrors(data)
if data._error_message
$confirm.notify("error", data._error_message)
$el.on "change", ".module-activation.module-direct-active input", (event) ->
event.preventDefault()
submit()
$el.on "submit", "form", (event) ->
event.preventDefault()
submit()
$el.on "click", ".admin-functionalities a.button-green", (event) ->
$el.on "click", ".icon-save", (event) ->
event.preventDefault()
submit()
$scope.$watch "isVideoconferenceActivated", (isVideoconferenceActivated) ->
if isVideoconferenceActivated
$el.find(".videoconference-attributes").removeClass("hidden")
else
$el.find(".videoconference-attributes").addClass("hidden")
$el.on "keydown", ".videoconference-attributes input", (e) ->
return e.which != 32
$scope.$watch "project.videoconferences", (newVal, oldVal) ->
# Reset videoconferences_extra_data if videoconference system change
if newVal? and oldVal? and newVal != oldVal
$scope.project.videoconferences_extra_data = ""
$scope.$watch "isVideoconferenceActivated", (newValue, oldValue) ->
if newValue == false
# Reset videoconference attributes
$scope.project.videoconferences = null
$scope.project.videoconferences_extra_data = ""
# Save when videoconference is desactivated
submit() if oldValue == true
$scope.$watch "project", (project) ->
if project.videoconferences?
$scope.isVideoconferenceActivated = true

View File

@ -430,6 +430,10 @@
"DISABLE": "Disable",
"BACKLOG": "Backlog",
"BACKLOG_DESCRIPTION": "Manage your user stories to maintain an organized view of upcoming and prioritized work.",
"NUMBER_SPRINTS": "Expected number of sprints",
"NUMBER_SPRINTS_HELP": "0 for an undetermined quantity",
"NUMBER_US_POINTS": "Expected total of story points",
"NUMBER_US_POINTS_HELP": "0 for an undetermined quantity",
"KANBAN": "Kanban",
"KANBAN_DESCRIPTION": "Organize your project in a lean way with this board.",
"ISSUES": "Issues",
@ -437,9 +441,9 @@
"WIKI": "Wiki",
"WIKI_DESCRIPTION": "Add, modify, or delete content in collaboration with others. This is the right place for your project documentation.",
"MEETUP": "Meet Up",
"MEETUP_DESCRIPTION": "Choose your videoconference system. Even developers need face to face contact.",
"MEETUP_DESCRIPTION": "Choose your videoconference system.",
"SELECT_VIDEOCONFERENCE": "Select a videoconference system",
"SALT_CHAT_ROOM": "If you want you can append a salt code to the name of the chat room",
"SALT_CHAT_ROOM": "Add a prefix to the chat room name",
"JITSI_CHAT_ROOM": "Jitsi",
"APPEARIN_CHAT_ROOM": "AppearIn",
"TALKY_CHAT_ROOM": "Talky",
@ -451,8 +455,6 @@
"PROJECT_DETAILS": "Project details",
"PROJECT_NAME": "Project name",
"PROJECT_SLUG": "Project slug",
"NUMBER_SPRINTS": "Number of sprints (0 for an undetermined quantity)",
"NUMBER_US_POINTS": "Number of US points (0 for an undetermined quantity)",
"TAGS": "Tags",
"DESCRIPTION": "Description",
"RECRUITING": "Is this project looking for people?",

View File

@ -95,25 +95,28 @@ class ProjectMenuController
return sectionName
_videoConferenceUrl: () ->
# Get base url
if @.project.get("videoconferences") == "appear-in"
baseUrl = "https://appear.in/"
else if @.project.get("videoconferences") == "talky"
baseUrl = "https://talky.io/"
else if @.project.get("videoconferences") == "jitsi"
baseUrl = "https://meet.jit.si/"
url = @.project.get("slug") + "-" + taiga.slugify(@.project.get("videoconferences_extra_data"))
url = url.replace(/-/g, "")
return baseUrl + url
else if @.project.get("videoconferences") == "custom"
return @.project.get("videoconferences_extra_data")
else
return ""
# Add prefix to the chat room name if exist
if @.project.get("videoconferences_extra_data")
url = @.project.get("slug") + "-" + @.project.get("videoconferences_extra_data")
url = @.project.get("slug") + "-" + taiga.slugify(@.project.get("videoconferences_extra_data"))
else
url = @.project.get("slug")
# Some special cases
if @.project.get("videoconferences") == "jitsi"
url = url.replace(/-/g, "")
return baseUrl + url
angular.module("taigaComponents").controller("ProjectMenu", ProjectMenuController)

View File

@ -1,7 +1,10 @@
doctype html
div.wrapper(tg-project-modules, ng-controller="ProjectProfileController as ctrl",
ng-init="section='admin'; sectionName='ADMIN.MODULES.TITLE'")
div.wrapper(
tg-project-modules
ng-controller="ProjectProfileController as ctrl"
ng-init="section='admin'; sectionName='ADMIN.MODULES.TITLE'"
)
tg-project-menu
sidebar.menu-secondary.sidebar.settings-nav(tg-admin-navigation="project-profile")
include ../includes/modules/admin-menu
@ -13,94 +16,159 @@ div.wrapper(tg-project-modules, ng-controller="ProjectProfileController as ctrl"
header
include ../includes/components/mainTitle
form
div.functionality(ng-class="{true:'active', false:''}[project.is_backlog_activated]")
svg.icon.icon-scrum
use(xlink:href="#icon-scrum")
div.desc
p
span.title(translate="ADMIN.MODULES.BACKLOG")
span(translate="ADMIN.MODULES.BACKLOG_DESCRIPTION")
div.activate
input.activate-input(type="checkbox", id="functionality-backlog",
ng-model="project.is_backlog_activated")
label.button.button-gray(ng-switch="project.is_backlog_activated",
for="functionality-backlog")
span(ng-switch-when="true", translate="ADMIN.MODULES.DISABLE")
span(ng-switch-when="false", translate="ADMIN.MODULES.ENABLE")
form.module-container
.module.module-scrum(ng-class="{true:'active', false:''}[project.is_backlog_activated]")
.module-icon
svg.icon.icon-scrum
use(xlink:href="#icon-scrum")
.module-name(translate="ADMIN.MODULES.BACKLOG")
.module-desc
p(translate="ADMIN.MODULES.BACKLOG_DESCRIPTION")
.module-desc-options(ng-if="project.is_backlog_activated")
fieldset
label(for="total-sprints") {{ 'ADMIN.MODULES.NUMBER_SPRINTS' | translate }}
input(
id="total-sprints"
name="total-sprints"
type="number"
min="0"
placeholder="{{'ADMIN.MODULES.NUMBER_SPRINTS_HELP' | translate}}"
ng-model="project.total_milestones"
data-type="digits"
)
div.functionality(ng-class="{true:'active', false:''}[project.is_kanban_activated]")
svg.icon.icon-kanban
use(xlink:href="#icon-kanban")
div.desc
p
span.title(translate="ADMIN.MODULES.KANBAN")
span(translate="ADMIN.MODULES.KANBAN_DESCRIPTION")
div.activate
input.activate-input(type="checkbox", id="functionality-kanban",
ng-model="project.is_kanban_activated")
label.button.button-gray(ng-switch="project.is_kanban_activated",
for="functionality-kanban")
span(ng-switch-when="true", translate="ADMIN.MODULES.DISABLE")
span(ng-switch-when="false", translate="ADMIN.MODULES.ENABLE")
fieldset
label(for="total-story-points") {{ 'ADMIN.MODULES.NUMBER_US_POINTS' | translate }}
input(
id="total-story-points"
name="total-story-points"
type="number"
min="0"
placeholder="{{'ADMIN.MODULES.NUMBER_US_POINTS_HELP' | translate}}"
ng-model="project.total_story_points"
data-type="digits"
)
svg.icon.icon-save(ng-if="project.is_backlog_activated")
use(xlink:href="#icon-save")
.module-activation.module-direct-active
div.check
input.activate-input(
id="functionality-backlog"
name="functionality-backlog"
type="checkbox"
ng-checked="project.is_backlog_activated"
ng-model="project.is_backlog_activated"
)
div
span.check-text.check-yes(translate="COMMON.YES")
span.check-text.check-no(translate="COMMON.NO")
div.functionality(ng-class="{true:'active', false:''}[project.is_issues_activated]")
svg.icon.icon-issues
use(xlink:href="#icon-issues")
div.desc
p
span.title(translate="ADMIN.MODULES.ISSUES")
span(translate="ADMIN.MODULES.ISSUES_DESCRIPTION")
div.activate
input.activate-input(type="checkbox", id="functionality-issues",
ng-model="project.is_issues_activated")
label.button.button-gray(ng-switch="project.is_issues_activated",
for="functionality-issues")
span(ng-switch-when="true", translate="ADMIN.MODULES.DISABLE")
span(ng-switch-when="false", translate="ADMIN.MODULES.ENABLE")
.module.module-kanban(ng-class="{true:'active', false:''}[project.is_kanban_activated]")
.module-icon
svg.icon.icon-kanban
use(xlink:href="#icon-kanban")
.module-name(translate="ADMIN.MODULES.KANBAN")
.module-desc(translate="ADMIN.MODULES.KANBAN_DESCRIPTION")
.module-activation.module-direct-active
div.check
input.activate-input(
id="functionality-kanban"
name="functionality-kanban"
type="checkbox"
ng-checked="project.is_kanban_activated"
ng-model="project.is_kanban_activated"
)
div
span.check-text.check-yes(translate="COMMON.YES")
span.check-text.check-no(translate="COMMON.NO")
div.functionality(ng-class="{true:'active', false:''}[project.is_wiki_activated]")
svg.icon.icon-wiki
use(xlink:href="#icon-wiki")
div.desc
p
span.title(translate="ADMIN.MODULES.WIKI")
span(translate="ADMIN.MODULES.WIKI_DESCRIPTION")
div.activate
input.activate-input(type="checkbox", id="functionality-wiki",
ng-model="project.is_wiki_activated")
label.button.button-gray(ng-switch="project.is_wiki_activated",
for="functionality-wiki")
span(ng-switch-when="true", translate="ADMIN.MODULES.DISABLE")
span(ng-switch-when="false", translate="ADMIN.MODULES.ENABLE")
.module.module-issues(ng-class="{true:'active', false:''}[project.is_issues_activated]")
.module-icon
svg.icon.icon-issues
use(xlink:href="#icon-issues")
.module-name(translate="ADMIN.MODULES.ISSUES")
.module-desc(translate="ADMIN.MODULES.ISSUES_DESCRIPTION")
.module-activation.module-direct-active
div.check
input.activate-input(
id="functionality-issues"
name="functionality-issues"
type="checkbox"
ng-checked="project.is_issues_activated"
ng-model="project.is_issues_activated"
)
div
span.check-text.check-yes(translate="COMMON.YES")
span.check-text.check-no(translate="COMMON.NO")
div.functionality(ng-class="{true:'active', false:''}[isVideoconferenceActivated]")
svg.icon.icon-bubble-empty
use(xlink:href="#icon-bubble-empty")
div.desc
p
span.title(translate="ADMIN.MODULES.MEETUP")
span(translate="ADMIN.MODULES.MEETUP_DESCRIPTION")
div.activate
input.activate-input(type="checkbox", id="functionality-video",
ng-model="isVideoconferenceActivated")
label.button.button-gray(ng-switch="isVideoconferenceActivated",
for="functionality-video")
span(ng-switch-when="true", translate="ADMIN.MODULES.DISABLE")
span(ng-switch-when="false", translate="ADMIN.MODULES.ENABLE")
.module.module-wiki(ng-class="{true:'active', false:''}[project.is_wiki_activated]")
.module-icon
svg.icon.icon-wiki
use(xlink:href="#icon-wiki")
.module-name(translate="ADMIN.MODULES.WIKI")
.module-desc(translate="ADMIN.MODULES.WIKI_DESCRIPTION")
.module-activation.module-direct-active
div.check
input.activate-input(
id="functionality-wiki"
name="functionality-wiki"
type="checkbox"
ng-checked="project.is_wiki_activated"
ng-model="project.is_wiki_activated"
)
div
span.check-text.check-yes(translate="COMMON.YES")
span.check-text.check-no(translate="COMMON.NO")
div.videoconference-attributes.hidden
select(ng-model="project.videoconferences",
ng-options="e.id as e.name|translate for e in [{'id':'appear-in', 'name':'ADMIN.MODULES.APPEARIN_CHAT_ROOM'},{'id':'jitsi', 'name': 'ADMIN.MODULES.JITSI_CHAT_ROOM'},{'id':'talky', 'name': 'ADMIN.MODULES.TALKY_CHAT_ROOM'},{'id':'custom', 'name': 'ADMIN.MODULES.CUSTOM_CHAT_ROOM'}]")
option(value="", translate="ADMIN.MODULES.SELECT_VIDEOCONFERENCE")
input(ng-if="project.videoconferences && project.videoconferences != 'custom'",
type="text",
ng-model="project.videoconferences_extra_data",
data-maxlength="255",
placeholder="{{'ADMIN.MODULES.SALT_CHAT_ROOM' | translate}}")
input(ng-if="project.videoconferences == 'custom'",
type="text",
ng-model="project.videoconferences_extra_data",
data-maxlength="255",
placeholder="{{'ADMIN.MODULES.URL_CHAT_ROOM' | translate}}")
button.button-green.submit-button(type="submit", title="{{'COMMON.SAVE' | translate}}", translate="COMMON.SAVE")
.module.module-videoconference(ng-class="{true:'active', false:''}[isVideoconferenceActivated]")
.module-icon
svg.icon.icon-bubble-empty
use(xlink:href="#icon-bubble-empty")
.module-name(translate="ADMIN.MODULES.MEETUP")
.module-desc
p(translate="ADMIN.MODULES.MEETUP_DESCRIPTION")
div.videoconference-attributes(ng-if="isVideoconferenceActivated")
select(
id="videoconference-type"
name="videoconference-type"
ng-model="project.videoconferences"
ng-options="e.id as e.name|translate for e in [{'id':'appear-in', 'name':'ADMIN.MODULES.APPEARIN_CHAT_ROOM'},{'id':'jitsi', 'name': 'ADMIN.MODULES.JITSI_CHAT_ROOM'},{'id':'talky', 'name': 'ADMIN.MODULES.TALKY_CHAT_ROOM'},{'id':'custom', 'name': 'ADMIN.MODULES.CUSTOM_CHAT_ROOM'}]")
option(
value=""
translate="ADMIN.MODULES.SELECT_VIDEOCONFERENCE"
)
fieldset(ng-if="project.videoconferences && project.videoconferences != 'custom'")
input(
id="videoconference-prefix"
name="videoconference-prefix"
type="text"
ng-model="project.videoconferences_extra_data"
data-maxlength="250"
placeholder="{{'ADMIN.MODULES.SALT_CHAT_ROOM' | translate}}"
)
fieldset(ng-if="project.videoconferences == 'custom'")
input(
id="videoconference-url"
name="videoconference-url"
type="url"
ng-model="project.videoconferences_extra_data"
data-maxlength="250"
placeholder="{{'ADMIN.MODULES.URL_CHAT_ROOM' | translate}}"
data-type="url"
data-required="true"
)
svg.icon.icon-save(ng-if="project.videoconferences")
use(xlink:href="#icon-save")
.module-activation
div.check
input.activate-input(
id="functionality-video"
name="functionality-video"
type="checkbox"
ng-checked="project.isVideoconferenceActivated"
ng-model="isVideoconferenceActivated"
)
div
span.check-text.check-yes(translate="COMMON.YES")
span.check-text.check-no(translate="COMMON.NO")

View File

@ -78,30 +78,6 @@ div.wrapper(
ng-model="project.tags"
)
fieldset
label(for="project-sprints") {{ 'ADMIN.PROJECT_PROFILE.NUMBER_SPRINTS' | translate }}
input(
type="number"
name="total_milestones"
min="0"
placeholder="{{'ADMIN.PROJECT_PROFILE.NUMBER_SPRINTS' | translate}}"
id="project-sprints"
ng-model="project.total_milestones"
data-type="digits"
)
fieldset
label(for="total-story-points") {{ 'ADMIN.PROJECT_PROFILE.NUMBER_US_POINTS' | translate }}
input(
type="number"
name="total_story_points"
min="0"
placeholder="{{'ADMIN.PROJECT_PROFILE.NUMBER_US_POINTS' | translate}}"
id="total-story-points"
ng-model="project.total_story_points"
data-type="digits"
)
fieldset.looking-for-people
.looking-for-people-selector
span {{ 'ADMIN.PROJECT_PROFILE.RECRUITING' | translate }}

View File

@ -16,7 +16,7 @@ div.wrapper(tg-backlog, ng-controller="BacklogController as ctrl",
use(xlink:href="#icon-graph")
div.empty-text
p.title(translate="BACKLOG.CUSTOMIZE_GRAPH")
p {{'BACKLOG.CUSTOMIZE_GRAPH_TEXT' | translate}} #[a(href="", tg-nav="project-admin-project-profile-details:project=project.slug", title="{{'BACKLOG.CUSTOMIZE_GRAPH_TITLE' | translate}}") {{'BACKLOG.CUSTOMIZE_GRAPH_ADMIN' | translate}}]
p {{'BACKLOG.CUSTOMIZE_GRAPH_TEXT' | translate}} #[a(href="", tg-nav="project-admin-project-profile-modules:project=project.slug", title="{{'BACKLOG.CUSTOMIZE_GRAPH_TITLE' | translate}}") {{'BACKLOG.CUSTOMIZE_GRAPH_ADMIN' | translate}}]
div.graphics-container.js-burndown-graph
div.burndown(tg-burndown-backlog-graph)

View File

@ -8,12 +8,12 @@
width: 65px;
input {
cursor: pointer;
height: 500px;
height: 50px;
left: -10px;
opacity: 0;
position: absolute;
top: -10px;
width: 500px;
width: 100px;
z-index: 999;
+ div {
background-color: $gray;

View File

@ -1,62 +1,75 @@
.admin-functionalities {
form {
display: flex;
flex-wrap: wrap;
.module-container {
max-width: 900px;
width: 100%;
}
.functionality {
.module {
align-content: center;
align-items: center;
background-color: $whitish;
align-items: flex-start;
border-bottom: 1px solid $whitish;
display: flex;
flex-direction: column;
justify-content: center;
margin-bottom: .3rem;
margin-right: .3rem;
opacity: .5;
padding: 1rem;
position: relative;
transition: all .2s linear;
vertical-align: top;
width: 32%;
padding: 1rem 0;
&.active {
background-color: rgba($primary, .3);
opacity: 1;
}
.icon {
fill: $gray;
height: 3rem;
margin: 1rem auto;
width: 3rem;
}
.desc {
text-align: center;
width: 100%;
}
.activate-input {
display: none;
+label {
color: $white;
cursor: pointer;
display: block;
text-align: center;
.module-icon .icon,
.module-name {
color: $primary;
fill: $primary;
}
}
.title {
@extend %bold;
display: block;
}
select {
margin-top: 1rem;
}
.module-icon {
flex-basis: 2rem;
flex-shrink: 0;
margin: 0 .5rem 0 0;
.icon {
@include svg-size(3rem);
fill: $gray-light;
}
}
.module-name {
@extend %bold;
@extend %large;
color: $gray-light;
flex-basis: 100px;
flex-shrink: 0;
margin: 0 .5rem;
}
.module-desc {
@extend %small;
color: $gray-light;
flex: 1;
margin: 0 2rem 0 0;
p {
margin: 0;
}
}
.module-desc-options,
.videoconference-attributes {
select {
margin-bottom: .5rem;
align-items: flex-start;
display: flex;
margin-top: .5rem;
fieldset,
.icon {
margin: 0 .5rem;
}
.icon {
@include svg-size(2.5rem);
align-self: center;
cursor: pointer;
fill: $gray-light;
&:hover {
fill: $primary;
}
}
}
.button-green {
color: $white;
display: block;
text-align: center;
.module-scrum {
.icon {
align-self: flex-end;
}
}
.module-videoconference {
.icon {
align-self: flex-start;
}
}
}