diff --git a/app/coffee/app.coffee b/app/coffee/app.coffee index 24f955e1..54ff9b5d 100644 --- a/app/coffee/app.coffee +++ b/app/coffee/app.coffee @@ -66,9 +66,8 @@ configure = ($routeProvider, $locationProvider, $httpProvider, $provide, $tgEven $routeProvider.when("/", { templateUrl: "home/home.html", - access: { - requiresLogin: true - }, + controller: "Home", + controllerAs: "vm" loader: true, title: "HOME.PAGE_TITLE", loader: true, @@ -77,6 +76,27 @@ configure = ($routeProvider, $locationProvider, $httpProvider, $provide, $tgEven } ) + $routeProvider.when("/discover", + { + templateUrl: "discover/discover-home/discover-home.html", + controller: "DiscoverHome", + controllerAs: "vm", + title: "PROJECT.NAVIGATION.DISCOVER", + loader: true + } + ) + + $routeProvider.when("/discover/search", + { + templateUrl: "discover/discover-search/discover-search.html", + title: "PROJECT.NAVIGATION.DISCOVER", + loader: true, + controller: "DiscoverSearch", + controllerAs: "vm", + reloadOnSearch: false + } + ) + $routeProvider.when("/projects/", { templateUrl: "projects/listing/projects-listing.html", @@ -577,7 +597,7 @@ init = ($log, $rootscope, $auth, $events, $analytics, $translate, $location, $na $rootscope.$evalAsync(cb) $events.setupConnection() - + # Load user if $auth.isAuthenticated() user = $auth.getUser() @@ -664,6 +684,7 @@ modules = [ "taigaHome", "taigaUserTimeline", "taigaExternalApps", + "taigaDiscover", # template cache "templates", diff --git a/app/coffee/modules/admin/project-profile.coffee b/app/coffee/modules/admin/project-profile.coffee index 74055a96..4a6bee65 100644 --- a/app/coffee/modules/admin/project-profile.coffee +++ b/app/coffee/modules/admin/project-profile.coffee @@ -449,3 +449,64 @@ CsvIssueDirective = ($translate) -> } module.directive("tgCsvIssue", ["$translate", CsvIssueDirective]) + + +############################################################################# +## Project Logo Directive +############################################################################# + +ProjectLogoDirective = ($auth, $model, $rs, $confirm) -> + link = ($scope, $el, $attrs) -> + showSizeInfo = -> + $el.find(".size-info").addClass("active") + + onSuccess = (response) -> + project = $model.make_model("projects", response.data) + $scope.project = project + + $el.find('.loading-overlay').removeClass('active') + $confirm.notify('success') + + onError = (response) -> + showSizeInfo() if response.status == 413 + $el.find('.loading-overlay').removeClass('active') + $confirm.notify('error', response.data._error_message) + + # Change photo + $el.on "click", ".js-change-logo", -> + $el.find("#logo-field").click() + + $el.on "change", "#logo-field", (event) -> + if $scope.logoAttachment + $el.find('.loading-overlay').addClass("active") + $rs.projects.changeLogo($scope.project.id, $scope.logoAttachment).then(onSuccess, onError) + + # Use default photo + $el.on "click", "a.js-use-default-logo", (event) -> + $el.find('.loading-overlay').addClass("active") + $rs.projects.removeLogo($scope.project.id).then(onSuccess, onError) + + $scope.$on "$destroy", -> + $el.off() + + return {link:link} + +module.directive("tgProjectLogo", ["$tgAuth", "$tgModel", "$tgResources", "$tgConfirm", ProjectLogoDirective]) + + +############################################################################# +## Project Logo Model Directive +############################################################################# + +ProjectLogoModelDirective = ($parse) -> + link = ($scope, $el, $attrs) -> + model = $parse($attrs.tgProjectLogoModel) + modelSetter = model.assign + + $el.bind 'change', -> + $scope.$apply -> + modelSetter($scope, $el[0].files[0]) + + return {link:link} + +module.directive('tgProjectLogoModel', ['$parse', ProjectLogoModelDirective]) diff --git a/app/coffee/modules/base.coffee b/app/coffee/modules/base.coffee index d45b879f..feeb02f8 100644 --- a/app/coffee/modules/base.coffee +++ b/app/coffee/modules/base.coffee @@ -52,6 +52,9 @@ urls = { "not-found": "/not-found" "permission-denied": "/permission-denied" + "discover": "/discover" + "discover-search": "/discover/search" + "login": "/login" "forgot-password": "/forgot-password" "change-password": "/change-password/:token" diff --git a/app/coffee/modules/base/navurls.coffee b/app/coffee/modules/base/navurls.coffee index 3885b013..85fee430 100644 --- a/app/coffee/modules/base/navurls.coffee +++ b/app/coffee/modules/base/navurls.coffee @@ -117,7 +117,7 @@ NavigationUrlsDirective = ($navurls, $auth, $q, $location) -> $el.on "mouseenter", (event) -> target = $(event.currentTarget) - if !target.data("fullUrl") + if !target.data("fullUrl") || $attrs.tgNavGetParams != target.data("params") parseNav($attrs.tgNav, $scope).then (result) -> [name, options] = result user = $auth.getUser() @@ -131,6 +131,8 @@ NavigationUrlsDirective = ($navurls, $auth, $q, $location) -> getURLParamsStr = $.param(getURLParams) fullUrl = "#{fullUrl}?#{getURLParamsStr}" + target.data("params", $attrs.tgNavGetParams) + target.data("fullUrl", fullUrl) if target.is("a") diff --git a/app/coffee/modules/common/loading.coffee b/app/coffee/modules/common/loading.coffee index 4d58b052..2c128ba9 100644 --- a/app/coffee/modules/common/loading.coffee +++ b/app/coffee/modules/common/loading.coffee @@ -112,7 +112,7 @@ LoadingDirective = ($loading) -> if showLoading currentLoading = $loading() .target($el) - .timeout(50) + .timeout(100) .template(template) .scope($scope) .start() diff --git a/app/coffee/modules/resources.coffee b/app/coffee/modules/resources.coffee index 77677ca7..fda8262b 100644 --- a/app/coffee/modules/resources.coffee +++ b/app/coffee/modules/resources.coffee @@ -175,6 +175,9 @@ urls = { # Application tokens "applications": "/applications" "application-tokens": "/application-tokens" + + # Stats + "stats-discover": "/stats/discover" } # Initialize api urls service diff --git a/app/coffee/modules/resources/projects.coffee b/app/coffee/modules/resources/projects.coffee index cfb758d1..a1745420 100644 --- a/app/coffee/modules/resources/projects.coffee +++ b/app/coffee/modules/resources/projects.coffee @@ -153,6 +153,31 @@ resourceProvider = ($config, $repo, $http, $urls, $auth, $q, $translate) -> return defered.promise + service.changeLogo = (projectId, file) -> + maxFileSize = $config.get("maxUploadFileSize", null) + if maxFileSize and file.size > maxFileSize + response = { + status: 413, + data: _error_message: "'#{file.name}' (#{sizeFormat(file.size)}) is too heavy for our oompa + loompas, try it with a smaller than (#{sizeFormat(maxFileSize)})" + } + defered = $q.defer() + defered.reject(response) + return defered.promise + + data = new FormData() + data.append('logo', file) + options = { + transformRequest: angular.identity, + headers: {'Content-Type': undefined} + } + url = "#{$urls.resolve("projects")}/#{projectId}/change_logo" + return $http.post(url, data, {}, options) + + service.removeLogo = (projectId) -> + url = "#{$urls.resolve("projects")}/#{projectId}/remove_logo" + return $http.post(url) + return (instance) -> instance.projects = service diff --git a/app/coffee/modules/user-settings/main.coffee b/app/coffee/modules/user-settings/main.coffee index 8b203aa5..98348150 100644 --- a/app/coffee/modules/user-settings/main.coffee +++ b/app/coffee/modules/user-settings/main.coffee @@ -148,12 +148,12 @@ UserAvatarDirective = ($auth, $model, $rs, $confirm) -> $auth.setUser(user) $scope.user = user - $el.find('.overlay').addClass('hidden') + $el.find('.loading-overlay').removeClass('active') $confirm.notify('success') onError = (response) -> showSizeInfo() if response.status == 413 - $el.find('.overlay').addClass('hidden') + $el.find('.loading-overlay').removeClass('active') $confirm.notify('error', response.data._error_message) # Change photo @@ -162,12 +162,12 @@ UserAvatarDirective = ($auth, $model, $rs, $confirm) -> $el.on "change", "#avatar-field", (event) -> if $scope.avatarAttachment - $el.find('.overlay').removeClass('hidden') + $el.find('.loading-overlay').addClass("active") $rs.userSettings.changeAvatar($scope.avatarAttachment).then(onSuccess, onError) # Use gravatar photo - $el.on "click", "a.use-gravatar", (event) -> - $el.find('.overlay').removeClass('hidden') + $el.on "click", "a.js-use-gravatar", (event) -> + $el.find('.loading-overlay').addClass("active") $rs.userSettings.removeAvatar().then(onSuccess, onError) $scope.$on "$destroy", -> diff --git a/app/coffee/utils.coffee b/app/coffee/utils.coffee index 81276e6d..6ced2cc5 100644 --- a/app/coffee/utils.coffee +++ b/app/coffee/utils.coffee @@ -195,6 +195,14 @@ _.mixin delete obj[key]; obj , obj).value() + cartesianProduct: -> + _.reduceRight( + arguments, (a,b) -> + _.flatten(_.map(a, (x) -> _.map b, (y) -> [y].concat(x)), true) + , [ [] ]) + + + isImage = (name) -> return name.match(/\.(jpe?g|png|gif|gifv|webm)/i) != null diff --git a/app/modules/navigation-bar/dropdown-user/dropdown-user.directive.coffee b/app/modules/navigation-bar/dropdown-user/dropdown-user.directive.coffee index aeac042e..ac09365c 100644 --- a/app/modules/navigation-bar/dropdown-user/dropdown-user.directive.coffee +++ b/app/modules/navigation-bar/dropdown-user/dropdown-user.directive.coffee @@ -27,7 +27,8 @@ DropdownUserDirective = (authService, configService, locationService, scope.vm.logout = -> authService.logout() - locationService.path(navUrlsService.resolve("login")) + locationService.url(navUrlsService.resolve("discover")) + locationService.search({}) scope.vm.sendFeedback = -> feedbackService.sendFeedback() diff --git a/app/modules/navigation-bar/dropdown-user/dropdown-user.directive.spec.coffee b/app/modules/navigation-bar/dropdown-user/dropdown-user.directive.spec.coffee index d85e69fd..87287d6a 100644 --- a/app/modules/navigation-bar/dropdown-user/dropdown-user.directive.spec.coffee +++ b/app/modules/navigation-bar/dropdown-user/dropdown-user.directive.spec.coffee @@ -50,8 +50,10 @@ describe "dropdownUserDirective", () -> _mockTgLocation = () -> mockTgLocation = { - path: sinon.stub() + url: sinon.stub() + search: sinon.stub() } + provide.value "$tgLocation", mockTgLocation _mockTgNavUrls = () -> @@ -97,16 +99,19 @@ describe "dropdownUserDirective", () -> expect(vm.isFeedbackEnabled).to.be.equal(true) it "dropdown user log out", () -> - mockTgNavUrls.resolve.withArgs("login").returns("/login") + mockTgNavUrls.resolve.withArgs("discover").returns("/discover") elm = createDirective() scope.$apply() vm = elm.isolateScope().vm expect(mockTgAuth.logout.callCount).to.be.equal(0) - expect(mockTgLocation.path.callCount).to.be.equal(0) + expect(mockTgLocation.url.callCount).to.be.equal(0) + expect(mockTgLocation.search.callCount).to.be.equal(0) vm.logout() expect(mockTgAuth.logout.callCount).to.be.equal(1) - expect(mockTgLocation.path.callCount).to.be.equal(1) - expect(mockTgLocation.path.calledWith("/login")).to.be.true + expect(mockTgLocation.url.callCount).to.be.equal(1) + expect(mockTgLocation.search.callCount).to.be.equal(1) + expect(mockTgLocation.url.calledWith("/discover")).to.be.true + expect(mockTgLocation.search.calledWith({})).to.be.true it "dropdown user send feedback", () -> elm = createDirective() diff --git a/app/modules/navigation-bar/navigation-bar.directive.coffee b/app/modules/navigation-bar/navigation-bar.directive.coffee index 67d4589d..c32a792f 100644 --- a/app/modules/navigation-bar/navigation-bar.directive.coffee +++ b/app/modules/navigation-bar/navigation-bar.directive.coffee @@ -17,12 +17,14 @@ # File: navigation-bar.directive.coffee ### -NavigationBarDirective = (currentUserService, navigationBarService, $location) -> +NavigationBarDirective = (currentUserService, navigationBarService, + locationService, navUrlsService) -> + link = (scope, el, attrs, ctrl) -> scope.vm = {} scope.$on "$routeChangeSuccess", () -> - if $location.path() == "/" + if locationService.path() == "/" scope.vm.active = true else scope.vm.active = false @@ -31,6 +33,15 @@ NavigationBarDirective = (currentUserService, navigationBarService, $location) - taiga.defineImmutableProperty(scope.vm, "isAuthenticated", () -> currentUserService.isAuthenticated()) taiga.defineImmutableProperty(scope.vm, "isEnabledHeader", () -> navigationBarService.isEnabledHeader()) + scope.vm.login = -> + nextUrl = encodeURIComponent(locationService.url()) + locationService.url(navUrlsService.resolve("login")) + locationService.search({next: nextUrl}) + + scope.vm.register = -> + nextUrl = encodeURIComponent(locationService.url()) + locationService.url(navUrlsService.resolve("register")) + locationService.search({next: nextUrl}) directive = { templateUrl: "navigation-bar/navigation-bar.html" @@ -42,8 +53,9 @@ NavigationBarDirective = (currentUserService, navigationBarService, $location) - NavigationBarDirective.$inject = [ "tgCurrentUserService", - "tgNavigationBarService" - "$location" + "tgNavigationBarService", + "$tgLocation", + "$tgNavUrls" ] angular.module("taigaNavigationBar").directive("tgNavigationBar", NavigationBarDirective) diff --git a/app/modules/navigation-bar/navigation-bar.directive.spec.coffee b/app/modules/navigation-bar/navigation-bar.directive.spec.coffee index 7c3251bc..36041e2f 100644 --- a/app/modules/navigation-bar/navigation-bar.directive.spec.coffee +++ b/app/modules/navigation-bar/navigation-bar.directive.spec.coffee @@ -41,6 +41,19 @@ describe "navigationBarDirective", () -> provide.value "tgCurrentUserService", mocks.currentUserService + _mocksLocationService = () -> + mocks.locationService = { + url: sinon.stub() + search: sinon.stub() + } + + provide.value "$tgLocation", mocks.locationService + + _mockTgNavUrls = () -> + mocks.navUrls = { + resolve: sinon.stub() + } + provide.value "$tgNavUrls", mocks.navUrls _mockTranslateFilter = () -> mockTranslateFilter = (value) -> @@ -58,6 +71,8 @@ describe "navigationBarDirective", () -> provide = $provide _mocksCurrentUserService() + _mocksLocationService() + _mockTgNavUrls( ) _mockTranslateFilter() _mockTgDropdownProjectListDirective() _mockTgDropdownUserDirective() @@ -90,3 +105,33 @@ describe "navigationBarDirective", () -> mocks.currentUserService.isAuthenticated.returns(true) expect(elm.isolateScope().vm.isAuthenticated).to.be.true + + it "navigation bar login", () -> + mocks.navUrls.resolve.withArgs("login").returns("/login") + nextUrl = "/discover/search?order_by=-total_activity_last_month" + mocks.locationService.url.returns(nextUrl) + elm = createDirective() + scope.$apply() + vm = elm.isolateScope().vm + expect(mocks.locationService.url.callCount).to.be.equal(0) + expect(mocks.locationService.search.callCount).to.be.equal(0) + vm.login() + expect(mocks.locationService.url.callCount).to.be.equal(2) + expect(mocks.locationService.search.callCount).to.be.equal(1) + expect(mocks.locationService.url.calledWith("/login")).to.be.true + expect(mocks.locationService.search.calledWith({next: encodeURIComponent(nextUrl)})).to.be.true + + it "navigation bar register", () -> + mocks.navUrls.resolve.withArgs("register").returns("/register") + nextUrl = "/discover/search?order_by=-total_activity_last_month" + mocks.locationService.url.returns(nextUrl) + elm = createDirective() + scope.$apply() + vm = elm.isolateScope().vm + expect(mocks.locationService.url.callCount).to.be.equal(0) + expect(mocks.locationService.search.callCount).to.be.equal(0) + vm.register() + expect(mocks.locationService.url.callCount).to.be.equal(2) + expect(mocks.locationService.search.callCount).to.be.equal(1) + expect(mocks.locationService.url.calledWith("/register")).to.be.true + expect(mocks.locationService.search.calledWith({next: encodeURIComponent(nextUrl)})).to.be.true diff --git a/app/modules/navigation-bar/navigation-bar.jade b/app/modules/navigation-bar/navigation-bar.jade index 54aa167e..83f49a36 100644 --- a/app/modules/navigation-bar/navigation-bar.jade +++ b/app/modules/navigation-bar/navigation-bar.jade @@ -3,7 +3,7 @@ nav.navbar(ng-if="vm.isEnabledHeader") a.logo( href="#", tg-nav="home", - title="{{'PROJECT.NAVIGATION.DASHBOARD_TITLE' | translate}}") + title="{{'PROJECT.NAVIGATION.HOMEPAGE' | translate}}") include ../../svg/logo.svg @@ -15,16 +15,16 @@ nav.navbar(ng-if="vm.isEnabledHeader") div.nav-right(ng-if="!vm.isAuthenticated") a.login( - tg-nav="login", + ng-click="vm.login()" href="#", - title="{{ 'LOGIN_COMMON.ACTION_SIGN_IN' | translate }}" + title="{{ 'LOGIN_COMMON.ACTION_SIGN_IN' | translate }}" ) {{ 'LOGIN_COMMON.ACTION_SIGN_IN' | translate }} a.register( - tg-nav="register", + ng-click="vm.register()" href="#", - title="{{ 'REGISTER_FORM.ACTION_SIGN_UP' | translate }}" + title="{{ 'REGISTER_FORM.ACTION_SIGN_UP' | translate }}" ) {{ 'REGISTER_FORM.ACTION_SIGN_UP' | translate }} - + div.nav-right(ng-if="vm.isAuthenticated") a(tg-nav="home", ng-class="{active: vm.active}", @@ -32,6 +32,13 @@ nav.navbar(ng-if="vm.isEnabledHeader") include ../../svg/dashboard.svg + a( + href="#", + tg-nav="discover", + title="{{'PROJECT.NAVIGATION.DISCOVER_TITLE' | translate}}", + ) + include ../../svg/discover.svg + div.topnav-dropdown-wrapper(ng-show="vm.projects.size", tg-dropdown-project-list) - //div.topnav-dropdown-wrapper(tg-dropdown-organization-list) + //- div.topnav-dropdown-wrapper(tg-dropdown-organization-list) div.topnav-dropdown-wrapper(tg-dropdown-user)