commit
be473e6850
|
@ -68,7 +68,9 @@ configure = ($routeProvider, $locationProvider, $httpProvider, $provide, $tgEven
|
||||||
},
|
},
|
||||||
loader: true,
|
loader: true,
|
||||||
title: "HOME.PAGE_TITLE",
|
title: "HOME.PAGE_TITLE",
|
||||||
|
loader: true,
|
||||||
description: "HOME.PAGE_DESCRIPTION",
|
description: "HOME.PAGE_DESCRIPTION",
|
||||||
|
joyride: "dashboard"
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -109,7 +111,8 @@ configure = ($routeProvider, $locationProvider, $httpProvider, $provide, $tgEven
|
||||||
{
|
{
|
||||||
templateUrl: "backlog/backlog.html",
|
templateUrl: "backlog/backlog.html",
|
||||||
loader: true,
|
loader: true,
|
||||||
section: "backlog"
|
section: "backlog",
|
||||||
|
joyride: "backlog"
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -117,7 +120,8 @@ configure = ($routeProvider, $locationProvider, $httpProvider, $provide, $tgEven
|
||||||
{
|
{
|
||||||
templateUrl: "kanban/kanban.html",
|
templateUrl: "kanban/kanban.html",
|
||||||
loader: true,
|
loader: true,
|
||||||
section: "kanban"
|
section: "kanban",
|
||||||
|
joyride: "kanban"
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -703,6 +707,7 @@ module.run([
|
||||||
"tgAppMetaService",
|
"tgAppMetaService",
|
||||||
"tgProjectService",
|
"tgProjectService",
|
||||||
"tgLoader",
|
"tgLoader",
|
||||||
"tgNavigationBarService"
|
"tgNavigationBarService",
|
||||||
|
"$route",
|
||||||
init
|
init
|
||||||
])
|
])
|
||||||
|
|
|
@ -37,6 +37,8 @@ html(lang="en")
|
||||||
|
|
||||||
include partials/includes/components/notification-message
|
include partials/includes/components/notification-message
|
||||||
|
|
||||||
|
div(tg-joy-ride)
|
||||||
|
|
||||||
script(src="/js/libs.js?v=#{v}")
|
script(src="/js/libs.js?v=#{v}")
|
||||||
script(src="/js/templates.js?v=#{v}")
|
script(src="/js/templates.js?v=#{v}")
|
||||||
script(src="/js/app-loader.js?v=#{v}")
|
script(src="/js/app-loader.js?v=#{v}")
|
||||||
|
|
|
@ -1284,5 +1284,61 @@
|
||||||
"LOGIN_WITH_ANOTHER_USER": "Login with another user",
|
"LOGIN_WITH_ANOTHER_USER": "Login with another user",
|
||||||
"AUTHORIZE_APP": "Authorize app",
|
"AUTHORIZE_APP": "Authorize app",
|
||||||
"CANCEL": "Cancel"
|
"CANCEL": "Cancel"
|
||||||
|
},
|
||||||
|
"JOYRIDE": {
|
||||||
|
"DASHBOARD": {
|
||||||
|
"STEP1": {
|
||||||
|
"TITLE": "Your project",
|
||||||
|
"TEXT": "Welcome! Here you will find the projects you are involved on. We have left you sample project templates to help you discover the power of Taiga."
|
||||||
|
},
|
||||||
|
"STEP2": {
|
||||||
|
"TITLE": "Working on",
|
||||||
|
"TEXT": "Here you will find the User Stories, Tasks and Issues in which you are working on."
|
||||||
|
},
|
||||||
|
"STEP3": {
|
||||||
|
"TITLE": "Watching",
|
||||||
|
"TEXT1": "And right here you will find the ones that you want to know about.",
|
||||||
|
"TEXT2": "You are already working with Taiga ;)"
|
||||||
|
},
|
||||||
|
"STEP4": {
|
||||||
|
"TITLE": "Let’s start",
|
||||||
|
"TEXT1": "You can start by creating your first Taiga project or taking a look at the sample templates that we have left for you.",
|
||||||
|
"TEXT2": "Good luck!"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"BACKLOG": {
|
||||||
|
"STEP1": {
|
||||||
|
"TITLE": "Project summary",
|
||||||
|
"TEXT1": "Here you will see the state of your project.",
|
||||||
|
"TEXT2": "You can change every kind of project settings through the admin."
|
||||||
|
},
|
||||||
|
"STEP2": {
|
||||||
|
"TITLE": "Product backlog",
|
||||||
|
"TEXT": "The backlog is the list of requirements (User Stories) for the project. Here is where you will plan your sprints."
|
||||||
|
},
|
||||||
|
"STEP3": {
|
||||||
|
"TITLE": "Sprints",
|
||||||
|
"TEXT": "Sprints are short periods of time (usually 2 weeks) during which specific work has to be completed and delivered."
|
||||||
|
},
|
||||||
|
"STEP4": {
|
||||||
|
"TITLE": "User Stories",
|
||||||
|
"TEXT": "Those are the requirements at high level. You can add them to the backlog and drag them to the sprint in which it should be delivered."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"KANBAN": {
|
||||||
|
"STEP1": {
|
||||||
|
"TITLE": "Customize your workflow",
|
||||||
|
"TEXT": "Set up the columns you need to map your workflow statuses through the admin."
|
||||||
|
},
|
||||||
|
"STEP2": {
|
||||||
|
"TITLE": "User Stories & Tasks",
|
||||||
|
"TEXT": "User Stories are the requirements at high level. You can drag them to different columns."
|
||||||
|
},
|
||||||
|
"STEP3": {
|
||||||
|
"TITLE": "Adding User Stories",
|
||||||
|
"TEXT1": "You may want to add a single User Story (add US icon) or a group of them (bulk icon)",
|
||||||
|
"TEXT2": "Good luck!"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,59 @@
|
||||||
|
taiga = @.taiga
|
||||||
|
|
||||||
|
JoyRideDirective = ($rootScope, currentUserService, joyRideService) ->
|
||||||
|
link = (scope, el, attrs, ctrl) ->
|
||||||
|
intro = introJs()
|
||||||
|
|
||||||
|
#Todo: translate
|
||||||
|
intro.setOptions({
|
||||||
|
exitOnEsc: false,
|
||||||
|
exitOnOverlayClick: false,
|
||||||
|
showStepNumbers: false,
|
||||||
|
nextLabel: 'Next →',
|
||||||
|
prevLabel: '← Back',
|
||||||
|
skipLabel: 'Skip',
|
||||||
|
doneLabel: 'Done',
|
||||||
|
disableInteraction: true
|
||||||
|
})
|
||||||
|
|
||||||
|
intro.oncomplete () ->
|
||||||
|
$('html,body').scrollTop(0)
|
||||||
|
|
||||||
|
intro.onexit () ->
|
||||||
|
currentUserService.disableJoyRide()
|
||||||
|
|
||||||
|
initJoyrRide = (next, config) ->
|
||||||
|
if !config[next.joyride]
|
||||||
|
return
|
||||||
|
|
||||||
|
intro.setOption('steps', joyRideService.get(next.joyride))
|
||||||
|
intro.start()
|
||||||
|
|
||||||
|
$rootScope.$on '$routeChangeSuccess', (event, next) ->
|
||||||
|
return if !next.joyride || !currentUserService.isAuthenticated()
|
||||||
|
|
||||||
|
intro.oncomplete () ->
|
||||||
|
currentUserService.disableJoyRide(next.joyride)
|
||||||
|
|
||||||
|
if next.loader
|
||||||
|
un = $rootScope.$on 'loader:end', () ->
|
||||||
|
currentUserService.loadJoyRideConfig()
|
||||||
|
.then (config) -> initJoyrRide(next, config)
|
||||||
|
|
||||||
|
un()
|
||||||
|
else
|
||||||
|
currentUserService.loadJoyRideConfig()
|
||||||
|
.then (config) -> initJoyrRide(next, config)
|
||||||
|
|
||||||
|
return {
|
||||||
|
scope: {},
|
||||||
|
link: link
|
||||||
|
}
|
||||||
|
|
||||||
|
JoyRideDirective.$inject = [
|
||||||
|
"$rootScope",
|
||||||
|
"tgCurrentUserService",
|
||||||
|
"tgJoyRideService"
|
||||||
|
]
|
||||||
|
|
||||||
|
angular.module("taigaComponents").directive("tgJoyRide", JoyRideDirective)
|
|
@ -0,0 +1,152 @@
|
||||||
|
class JoyRideService extends taiga.Service
|
||||||
|
@.$inject = [
|
||||||
|
'$translate',
|
||||||
|
'tgCheckPermissionsService'
|
||||||
|
]
|
||||||
|
|
||||||
|
constructor: (@translate, @checkPermissionsService) ->
|
||||||
|
|
||||||
|
getConfig: () ->
|
||||||
|
return {
|
||||||
|
dashboard: () =>
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
element: '.project-list > section:not(.ng-hide)',
|
||||||
|
position: 'left',
|
||||||
|
joyride: {
|
||||||
|
title: @translate.instant('JOYRIDE.DASHBOARD.STEP1.TITLE'),
|
||||||
|
text: @translate.instant('JOYRIDE.DASHBOARD.STEP1.TEXT')
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
element: '.working-on-container',
|
||||||
|
position: 'right',
|
||||||
|
joyride: {
|
||||||
|
title: @translate.instant('JOYRIDE.DASHBOARD.STEP2.TITLE'),
|
||||||
|
text: @translate.instant('JOYRIDE.DASHBOARD.STEP2.TEXT')
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
element: '.watching-container',
|
||||||
|
position: 'right',
|
||||||
|
joyride: {
|
||||||
|
title: @translate.instant('JOYRIDE.DASHBOARD.STEP3.TITLE')
|
||||||
|
text: [
|
||||||
|
@translate.instant('JOYRIDE.DASHBOARD.STEP3.TEXT1'),
|
||||||
|
@translate.instant('JOYRIDE.DASHBOARD.STEP3.TEXT2')
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
element: '.project-list .see-more-projects-btn',
|
||||||
|
position: 'bottom',
|
||||||
|
joyride: {
|
||||||
|
title: @translate.instant('JOYRIDE.DASHBOARD.STEP4.TITLE')
|
||||||
|
text: [
|
||||||
|
@translate.instant('JOYRIDE.DASHBOARD.STEP4.TEXT1'),
|
||||||
|
@translate.instant('JOYRIDE.DASHBOARD.STEP4.TEXT2')
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
backlog: () =>
|
||||||
|
steps = [
|
||||||
|
{
|
||||||
|
element: '.summary',
|
||||||
|
position: 'bottom',
|
||||||
|
joyride: {
|
||||||
|
title: @translate.instant('JOYRIDE.BACKLOG.STEP1.TITLE')
|
||||||
|
text: [
|
||||||
|
@translate.instant('JOYRIDE.BACKLOG.STEP1.TEXT1'),
|
||||||
|
@translate.instant('JOYRIDE.BACKLOG.STEP1.TEXT2')
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
element: '.backlog-table-empty',
|
||||||
|
position: 'bottom',
|
||||||
|
joyride: {
|
||||||
|
title: @translate.instant('JOYRIDE.BACKLOG.STEP2.TITLE')
|
||||||
|
text: @translate.instant('JOYRIDE.BACKLOG.STEP2.TEXT')
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
element: '.sprints',
|
||||||
|
position: 'left',
|
||||||
|
joyride: {
|
||||||
|
title: @translate.instant('JOYRIDE.BACKLOG.STEP3.TITLE')
|
||||||
|
text: @translate.instant('JOYRIDE.BACKLOG.STEP3.TEXT')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
if @checkPermissionsService.check('add_us')
|
||||||
|
steps.push({
|
||||||
|
element: '.new-us',
|
||||||
|
position: 'rigth',
|
||||||
|
joyride: {
|
||||||
|
title: @translate.instant('JOYRIDE.BACKLOG.STEP4.TITLE')
|
||||||
|
text: @translate.instant('JOYRIDE.BACKLOG.STEP4.TEXT')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return steps
|
||||||
|
|
||||||
|
kanban: () =>
|
||||||
|
steps = [
|
||||||
|
{
|
||||||
|
element: '.kanban-table-inner',
|
||||||
|
position: 'bottom',
|
||||||
|
joyride: {
|
||||||
|
title: @translate.instant('JOYRIDE.KANBAN.STEP1.TITLE')
|
||||||
|
text: @translate.instant('JOYRIDE.KANBAN.STEP1.TEXT')
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
element: '.card-placeholder',
|
||||||
|
position: 'right',
|
||||||
|
joyride: {
|
||||||
|
title: @translate.instant('JOYRIDE.KANBAN.STEP2.TITLE')
|
||||||
|
text: @translate.instant('JOYRIDE.KANBAN.STEP2.TEXT')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
if @checkPermissionsService.check('add_us')
|
||||||
|
steps.push({
|
||||||
|
element: '.icon-plus',
|
||||||
|
position: 'bottom',
|
||||||
|
joyride: {
|
||||||
|
title: @translate.instant('JOYRIDE.KANBAN.STEP3.TITLE')
|
||||||
|
text: [
|
||||||
|
@translate.instant('JOYRIDE.KANBAN.STEP3.TEXT1'),
|
||||||
|
@translate.instant('JOYRIDE.KANBAN.STEP3.TEXT2'),
|
||||||
|
]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return steps
|
||||||
|
}
|
||||||
|
|
||||||
|
get: (name) ->
|
||||||
|
joyRides = @.getConfig()
|
||||||
|
joyRide = joyRides[name].call(this)
|
||||||
|
|
||||||
|
return _.map joyRide, (item) ->
|
||||||
|
html = ""
|
||||||
|
|
||||||
|
if item.joyride.title
|
||||||
|
html += "<h3>#{item.joyride.title}</h3>"
|
||||||
|
|
||||||
|
if _.isArray(item.joyride.text)
|
||||||
|
_.forEach item.joyride.text, (text) ->
|
||||||
|
html += "<p>#{text}</p>"
|
||||||
|
else
|
||||||
|
html += "<p>#{item.joyride.text}</p>"
|
||||||
|
|
||||||
|
item.intro = html
|
||||||
|
|
||||||
|
return item
|
||||||
|
|
||||||
|
angular.module("taigaComponents").service("tgJoyRideService", JoyRideService)
|
|
@ -0,0 +1,57 @@
|
||||||
|
describe "tgJoyRideService", ->
|
||||||
|
joyRideService = provide = null
|
||||||
|
mocks = {}
|
||||||
|
|
||||||
|
_mockTranslate = () ->
|
||||||
|
mocks.translate = {
|
||||||
|
instant: sinon.stub()
|
||||||
|
}
|
||||||
|
|
||||||
|
provide.value "$translate", mocks.translate
|
||||||
|
|
||||||
|
_mockCheckPermissionsService = () ->
|
||||||
|
mocks.checkPermissionsService = {
|
||||||
|
check: sinon.stub()
|
||||||
|
}
|
||||||
|
|
||||||
|
mocks.checkPermissionsService.check.returns(true)
|
||||||
|
|
||||||
|
provide.value "tgCheckPermissionsService", mocks.checkPermissionsService
|
||||||
|
|
||||||
|
_inject = (callback) ->
|
||||||
|
inject (_tgJoyRideService_) ->
|
||||||
|
joyRideService = _tgJoyRideService_
|
||||||
|
callback() if callback
|
||||||
|
|
||||||
|
_mocks = () ->
|
||||||
|
module ($provide) ->
|
||||||
|
provide = $provide
|
||||||
|
_mockTranslate()
|
||||||
|
_mockCheckPermissionsService()
|
||||||
|
return null
|
||||||
|
|
||||||
|
_setup = ->
|
||||||
|
_mocks()
|
||||||
|
|
||||||
|
beforeEach ->
|
||||||
|
module "taigaComponents"
|
||||||
|
_setup()
|
||||||
|
_inject()
|
||||||
|
|
||||||
|
it "get joyride by category", () ->
|
||||||
|
example = {
|
||||||
|
element: '.project-list > section:not(.ng-hide)',
|
||||||
|
position: 'left',
|
||||||
|
joyride: {
|
||||||
|
title: 'test',
|
||||||
|
text: 'test'
|
||||||
|
},
|
||||||
|
intro: '<h3>test</h3><p>test</p>'
|
||||||
|
}
|
||||||
|
|
||||||
|
mocks.translate.instant.returns('test')
|
||||||
|
|
||||||
|
joyRide = joyRideService.get('dashboard')
|
||||||
|
|
||||||
|
expect(joyRide).to.have.length(4)
|
||||||
|
expect(joyRide[0]).to.be.eql(example)
|
|
@ -12,8 +12,8 @@ class ExternalAppController extends taiga.Controller
|
||||||
"tgLoader"
|
"tgLoader"
|
||||||
]
|
]
|
||||||
|
|
||||||
constructor: (@routeParams, @externalAppsService, @window, @currentUserService, @location, @navUrls,
|
constructor: (@routeParams, @externalAppsService, @window, @currentUserService, @location,
|
||||||
@xhrError, @loader) ->
|
@navUrls, @xhrError, @loader) ->
|
||||||
@loader.start(false)
|
@loader.start(false)
|
||||||
@._applicationId = @routeParams.application
|
@._applicationId = @routeParams.application
|
||||||
@._state = @routeParams.state
|
@._state = @routeParams.state
|
||||||
|
|
|
@ -1,17 +1,19 @@
|
||||||
div.title-bar.working-on-title(translate="HOME.WORKING_ON_SECTION")
|
section.working-on-container
|
||||||
|
.title-bar.working-on-title(translate="HOME.WORKING_ON_SECTION")
|
||||||
|
|
||||||
section.working-on(ng-show="vm.assignedTo.size")
|
.working-on(ng-show="vm.assignedTo.size")
|
||||||
div.duty-single(tg-duty="duty", tg-repeat="duty in vm.assignedTo", ng-class="{blocked: duty.is_blocked}")
|
.duty-single(tg-duty="duty", tg-repeat="duty in vm.assignedTo", ng-class="{blocked: duty.is_blocked}")
|
||||||
|
|
||||||
section.working-on-empty(ng-show="!vm.assignedTo.size")
|
.working-on-empty(ng-show="!vm.assignedTo.size")
|
||||||
p(translate="HOME.EMPTY_WORKING_ON")
|
p(translate="HOME.EMPTY_WORKING_ON")
|
||||||
include empty.jade
|
include empty.jade
|
||||||
|
|
||||||
div.title-bar.watching-title(translate="HOME.WATCHING_SECTION")
|
section.watching-container
|
||||||
|
.title-bar.watching-title(translate="HOME.WATCHING_SECTION")
|
||||||
|
|
||||||
section.watching(ng-show="vm.watching.size")
|
.watching(ng-show="vm.watching.size")
|
||||||
div.duty-single(tg-duty="duty", tg-repeat="duty in vm.watching", ng-class="{blocked: duty.is_blocked}")
|
.duty-single(tg-duty="duty", tg-repeat="duty in vm.watching", ng-class="{blocked: duty.is_blocked}")
|
||||||
|
|
||||||
section.watching-empty(ng-show="!vm.watching.size")
|
.watching-empty(ng-show="!vm.watching.size")
|
||||||
p(translate="HOME.EMPTY_WATCHING")
|
p(translate="HOME.EMPTY_WATCHING")
|
||||||
include empty.jade
|
include empty.jade
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
services = [
|
services = [
|
||||||
"tgProjectsResources",
|
"tgProjectsResources",
|
||||||
|
"tgUserResources",
|
||||||
"tgUsersResources",
|
"tgUsersResources",
|
||||||
"tgUserstoriesResource",
|
"tgUserstoriesResource",
|
||||||
"tgTasksResource",
|
"tgTasksResource",
|
||||||
|
|
|
@ -0,0 +1,41 @@
|
||||||
|
Resource = (urlsService, http, paginateResponseService) ->
|
||||||
|
service = {}
|
||||||
|
|
||||||
|
service.getUserStorage = (key) ->
|
||||||
|
url = urlsService.resolve("user-storage")
|
||||||
|
|
||||||
|
if key
|
||||||
|
url += '/' + key
|
||||||
|
|
||||||
|
httpOptions = {}
|
||||||
|
|
||||||
|
return http.get(url, {}).then (response) ->
|
||||||
|
return response.data.value
|
||||||
|
|
||||||
|
service.setUserStorage = (key, value) ->
|
||||||
|
url = urlsService.resolve("user-storage") + '/' + key
|
||||||
|
|
||||||
|
params = {
|
||||||
|
key: key,
|
||||||
|
value: value
|
||||||
|
}
|
||||||
|
|
||||||
|
return http.put(url, params)
|
||||||
|
|
||||||
|
service.createUserStorage = (key, value) ->
|
||||||
|
url = urlsService.resolve("user-storage")
|
||||||
|
|
||||||
|
params = {
|
||||||
|
key: key,
|
||||||
|
value: value
|
||||||
|
}
|
||||||
|
|
||||||
|
return http.post(url, params)
|
||||||
|
|
||||||
|
return () ->
|
||||||
|
return {"user": service}
|
||||||
|
|
||||||
|
Resource.$inject = ["$tgUrls", "$tgHttp"]
|
||||||
|
|
||||||
|
module = angular.module("taigaResources2")
|
||||||
|
module.factory("tgUserResources", Resource)
|
|
@ -0,0 +1,13 @@
|
||||||
|
taiga = @.taiga
|
||||||
|
|
||||||
|
class ChekcPermissionsService
|
||||||
|
@.$inject = [
|
||||||
|
"tgProjectService"
|
||||||
|
]
|
||||||
|
|
||||||
|
constructor: (@projectService) ->
|
||||||
|
|
||||||
|
check: (permission) ->
|
||||||
|
return @projectService.project.get('my_permissions').indexOf(permission) != -1
|
||||||
|
|
||||||
|
angular.module("taigaCommon").service("tgCheckPermissionsService", ChekcPermissionsService)
|
|
@ -0,0 +1,44 @@
|
||||||
|
describe "tgCheckPermissionsService", ->
|
||||||
|
checkPermissionsService = provide = null
|
||||||
|
mocks = {}
|
||||||
|
|
||||||
|
_mockProjectService = () ->
|
||||||
|
mocks.projectService = {
|
||||||
|
project: sinon.stub()
|
||||||
|
}
|
||||||
|
|
||||||
|
provide.value "tgProjectService", mocks.projectService
|
||||||
|
|
||||||
|
_inject = () ->
|
||||||
|
inject (_tgCheckPermissionsService_) ->
|
||||||
|
checkPermissionsService = _tgCheckPermissionsService_
|
||||||
|
|
||||||
|
_mocks = () ->
|
||||||
|
module ($provide) ->
|
||||||
|
provide = $provide
|
||||||
|
_mockProjectService()
|
||||||
|
|
||||||
|
return null
|
||||||
|
|
||||||
|
beforeEach ->
|
||||||
|
module "taigaCommon"
|
||||||
|
_mocks()
|
||||||
|
_inject()
|
||||||
|
|
||||||
|
it "the user has perms", () ->
|
||||||
|
mocks.projectService.project = Immutable.fromJS({
|
||||||
|
my_permissions: ['add_us']
|
||||||
|
})
|
||||||
|
|
||||||
|
perm = checkPermissionsService.check('add_us')
|
||||||
|
|
||||||
|
expect(perm).to.be.true
|
||||||
|
|
||||||
|
it "the user hasn't perms", () ->
|
||||||
|
mocks.projectService.project = Immutable.fromJS({
|
||||||
|
my_permissions: []
|
||||||
|
})
|
||||||
|
|
||||||
|
perm = checkPermissionsService.check('add_us')
|
||||||
|
|
||||||
|
expect(perm).to.be.false
|
|
@ -5,13 +5,15 @@ groupBy = @.taiga.groupBy
|
||||||
class CurrentUserService
|
class CurrentUserService
|
||||||
@.$inject = [
|
@.$inject = [
|
||||||
"tgProjectsService",
|
"tgProjectsService",
|
||||||
"$tgStorage"
|
"$tgStorage",
|
||||||
|
"tgResources"
|
||||||
]
|
]
|
||||||
|
|
||||||
constructor: (@projectsService, @storageService) ->
|
constructor: (@projectsService, @storageService, @rs) ->
|
||||||
@._user = null
|
@._user = null
|
||||||
@._projects = Immutable.Map()
|
@._projects = Immutable.Map()
|
||||||
@._projectsById = Immutable.Map()
|
@._projectsById = Immutable.Map()
|
||||||
|
@._joyride = null
|
||||||
|
|
||||||
taiga.defineImmutableProperty @, "projects", () => return @._projects
|
taiga.defineImmutableProperty @, "projects", () => return @._projects
|
||||||
taiga.defineImmutableProperty @, "projectsById", () => return @._projectsById
|
taiga.defineImmutableProperty @, "projectsById", () => return @._projectsById
|
||||||
|
@ -55,7 +57,43 @@ class CurrentUserService
|
||||||
|
|
||||||
return @.projects
|
return @.projects
|
||||||
|
|
||||||
|
disableJoyRide: (section) ->
|
||||||
|
if section
|
||||||
|
@._joyride[section] = false
|
||||||
|
else
|
||||||
|
@._joyride = {
|
||||||
|
backlog: false,
|
||||||
|
kanban: false,
|
||||||
|
dashboard: false
|
||||||
|
}
|
||||||
|
|
||||||
|
@rs.user.setUserStorage('joyride', @._joyride)
|
||||||
|
|
||||||
|
loadJoyRideConfig: () ->
|
||||||
|
return new Promise (resolve) =>
|
||||||
|
if @._joyride != null
|
||||||
|
resolve(@._joyride)
|
||||||
|
return
|
||||||
|
|
||||||
|
@rs.user.getUserStorage('joyride')
|
||||||
|
.then (config) =>
|
||||||
|
@._joyride = config
|
||||||
|
resolve(@._joyride)
|
||||||
|
.catch () =>
|
||||||
|
#joyride not defined
|
||||||
|
@._joyride = {
|
||||||
|
backlog: true,
|
||||||
|
kanban: true,
|
||||||
|
dashboard: true
|
||||||
|
}
|
||||||
|
|
||||||
|
@rs.user.createUserStorage('joyride', @._joyride)
|
||||||
|
|
||||||
|
resolve(@._joyride)
|
||||||
|
|
||||||
_loadUserInfo: () ->
|
_loadUserInfo: () ->
|
||||||
return @.loadProjects()
|
return Promise.all([
|
||||||
|
@.loadProjects()
|
||||||
|
])
|
||||||
|
|
||||||
angular.module("taigaCommon").service("tgCurrentUserService", CurrentUserService)
|
angular.module("taigaCommon").service("tgCurrentUserService", CurrentUserService)
|
||||||
|
|
|
@ -17,6 +17,17 @@ describe "tgCurrentUserService", ->
|
||||||
|
|
||||||
provide.value "tgProjectsService", mocks.projectsService
|
provide.value "tgProjectsService", mocks.projectsService
|
||||||
|
|
||||||
|
_mockResources = () ->
|
||||||
|
mocks.resources = {
|
||||||
|
user: {
|
||||||
|
setUserStorage: sinon.stub(),
|
||||||
|
getUserStorage: sinon.stub(),
|
||||||
|
createUserStorage: sinon.stub()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
provide.value "tgResources", mocks.resources
|
||||||
|
|
||||||
_inject = (callback) ->
|
_inject = (callback) ->
|
||||||
inject (_tgCurrentUserService_) ->
|
inject (_tgCurrentUserService_) ->
|
||||||
currentUserService = _tgCurrentUserService_
|
currentUserService = _tgCurrentUserService_
|
||||||
|
@ -27,6 +38,7 @@ describe "tgCurrentUserService", ->
|
||||||
provide = $provide
|
provide = $provide
|
||||||
_mockTgStorage()
|
_mockTgStorage()
|
||||||
_mockProjectsService()
|
_mockProjectsService()
|
||||||
|
_mockResources()
|
||||||
|
|
||||||
return null
|
return null
|
||||||
|
|
||||||
|
@ -105,3 +117,35 @@ describe "tgCurrentUserService", ->
|
||||||
currentUserService.removeUser()
|
currentUserService.removeUser()
|
||||||
|
|
||||||
expect(currentUserService._user).to.be.null
|
expect(currentUserService._user).to.be.null
|
||||||
|
|
||||||
|
it "disable joyride", () ->
|
||||||
|
currentUserService.disableJoyRide()
|
||||||
|
|
||||||
|
expect(mocks.resources.user.setUserStorage).to.have.been.calledWith('joyride', {
|
||||||
|
backlog: false,
|
||||||
|
kanban: false,
|
||||||
|
dashboard: false
|
||||||
|
});
|
||||||
|
|
||||||
|
it "load joyride config", (done) ->
|
||||||
|
mocks.resources.user.getUserStorage.withArgs('joyride').promise().resolve(true)
|
||||||
|
|
||||||
|
currentUserService.loadJoyRideConfig().then (config) ->
|
||||||
|
expect(config).to.be.true
|
||||||
|
|
||||||
|
done()
|
||||||
|
|
||||||
|
it "create default joyride config", (done) ->
|
||||||
|
mocks.resources.user.getUserStorage.withArgs('joyride').promise().reject()
|
||||||
|
|
||||||
|
currentUserService.loadJoyRideConfig().then (config) ->
|
||||||
|
joyride = {
|
||||||
|
backlog: true,
|
||||||
|
kanban: true,
|
||||||
|
dashboard: true
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(mocks.resources.user.createUserStorage).to.have.been.calledWith('joyride', joyride)
|
||||||
|
expect(config).to.be.eql(joyride)
|
||||||
|
|
||||||
|
done()
|
||||||
|
|
|
@ -0,0 +1,58 @@
|
||||||
|
// scss-lint:disable SelectorFormat, QualifyingElement
|
||||||
|
|
||||||
|
.introjs-overlay {
|
||||||
|
background: radial-gradient(center, ellipse cover, rgba($white, .2) 0, rgba($whitish, .2) 100%);
|
||||||
|
background-color: $whitish;
|
||||||
|
}
|
||||||
|
.introjs-helperLayer {
|
||||||
|
border: 1px solid rgba($primary-light, .8);
|
||||||
|
}
|
||||||
|
|
||||||
|
.introjs-helperLayer,
|
||||||
|
.introjs-tooltip {
|
||||||
|
box-shadow: 0 1px 8px rgba($grayer, .2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.introjs-tooltip {
|
||||||
|
h3 {
|
||||||
|
@extend %large;
|
||||||
|
margin-bottom: .5rem;
|
||||||
|
}
|
||||||
|
p {
|
||||||
|
@extend %light;
|
||||||
|
line-height: 1.4;
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.introjs-bullets {
|
||||||
|
ul {
|
||||||
|
li {
|
||||||
|
a.active {
|
||||||
|
background: $primary-light;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.introjs-button {
|
||||||
|
background-color: $primary;
|
||||||
|
background-image: none;
|
||||||
|
border: 0;
|
||||||
|
border-radius: 0;
|
||||||
|
color: $white;
|
||||||
|
margin-top: 10px;
|
||||||
|
padding: .3rem .8rem;
|
||||||
|
text-shadow: none;
|
||||||
|
&:focus,
|
||||||
|
&:hover {
|
||||||
|
background: none;
|
||||||
|
background-color: $primary-light;
|
||||||
|
color: $white;
|
||||||
|
}
|
||||||
|
&.introjs-disabled {
|
||||||
|
background: $whitish;
|
||||||
|
background-color: none;
|
||||||
|
color: $white;
|
||||||
|
}
|
||||||
|
}
|
|
@ -81,7 +81,8 @@
|
||||||
"ngInfiniteScroll": "1.2.1",
|
"ngInfiniteScroll": "1.2.1",
|
||||||
"eventemitter2": "~0.4.14",
|
"eventemitter2": "~0.4.14",
|
||||||
"immutable": "~3.7.2",
|
"immutable": "~3.7.2",
|
||||||
"bluebird": "~2.10.2"
|
"bluebird": "~2.10.2",
|
||||||
|
"intro.js": "~1.1.1"
|
||||||
},
|
},
|
||||||
"resolutions": {
|
"resolutions": {
|
||||||
"lodash": "~2.4.2",
|
"lodash": "~2.4.2",
|
||||||
|
|
12
gulpfile.js
12
gulpfile.js
|
@ -58,7 +58,10 @@ paths.htmlPartials = [
|
||||||
|
|
||||||
paths.images = paths.app + "images/**/*";
|
paths.images = paths.app + "images/**/*";
|
||||||
paths.svg = paths.app + "svg/**/*";
|
paths.svg = paths.app + "svg/**/*";
|
||||||
paths.css_vendor = paths.app + "styles/vendor/*.css";
|
paths.css_vendor = [
|
||||||
|
paths.app + "styles/vendor/*.css",
|
||||||
|
paths.vendor + "intro.js/introjs.css"
|
||||||
|
];
|
||||||
paths.locales = paths.app + "locales/**/*.json";
|
paths.locales = paths.app + "locales/**/*.json";
|
||||||
|
|
||||||
paths.sass = [
|
paths.sass = [
|
||||||
|
@ -164,6 +167,7 @@ paths.libs = [
|
||||||
paths.vendor + "ngInfiniteScroll/build/ng-infinite-scroll.js",
|
paths.vendor + "ngInfiniteScroll/build/ng-infinite-scroll.js",
|
||||||
paths.vendor + "eventemitter2/lib/eventemitter2.js",
|
paths.vendor + "eventemitter2/lib/eventemitter2.js",
|
||||||
paths.vendor + "immutable/dist/immutable.js",
|
paths.vendor + "immutable/dist/immutable.js",
|
||||||
|
paths.vendor + "intro.js/intro.js",
|
||||||
paths.app + "js/jquery.ui.git-custom.js",
|
paths.app + "js/jquery.ui.git-custom.js",
|
||||||
paths.app + "js/jquery-ui.drag-multiple-custom.js",
|
paths.app + "js/jquery-ui.drag-multiple-custom.js",
|
||||||
paths.app + "js/jquery.ui.touch-punch.min.js",
|
paths.app + "js/jquery.ui.touch-punch.min.js",
|
||||||
|
@ -244,7 +248,7 @@ gulp.task("scss-lint", [], function() {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
})))
|
})))
|
||||||
.pipe(gulpif(fail, scsslint.failReporter()))
|
.pipe(gulpif(fail, scsslint.failReporter()));
|
||||||
});
|
});
|
||||||
|
|
||||||
gulp.task("clear-sass-cache", function() {
|
gulp.task("clear-sass-cache", function() {
|
||||||
|
@ -311,7 +315,7 @@ gulp.task("main-css", function() {
|
||||||
return gulp.src(_paths)
|
return gulp.src(_paths)
|
||||||
.pipe(concat("theme-" + themes.current.name + ".css"))
|
.pipe(concat("theme-" + themes.current.name + ".css"))
|
||||||
.pipe(gulpif(isDeploy, minifyCSS({noAdvanced: true})))
|
.pipe(gulpif(isDeploy, minifyCSS({noAdvanced: true})))
|
||||||
.pipe(gulp.dest(paths.dist + "styles/"))
|
.pipe(gulp.dest(paths.dist + "styles/"));
|
||||||
});
|
});
|
||||||
|
|
||||||
var compileThemes = function (cb) {
|
var compileThemes = function (cb) {
|
||||||
|
@ -322,7 +326,7 @@ var compileThemes = function (cb) {
|
||||||
["app-css", "vendor-css"],
|
["app-css", "vendor-css"],
|
||||||
"main-css",
|
"main-css",
|
||||||
function() {
|
function() {
|
||||||
themes.next()
|
themes.next();
|
||||||
|
|
||||||
if (themes.current) {
|
if (themes.current) {
|
||||||
compileThemes(cb);
|
compileThemes(cb);
|
||||||
|
|
Loading…
Reference in New Issue