diff --git a/CHANGELOG.md b/CHANGELOG.md index ba4bc13f..8c46e982 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ ## 1.9.1 Taiga Tribe (unreleased) - [118n] Now taiga plugins can be translatable. - New Taiga plugins system. +- Now superadmins can send notifications (live announcement) to the user (through taiga-events). ### Misc - Statics folder hash to prevent cache problems when a new version is released. diff --git a/app/coffee/app.coffee b/app/coffee/app.coffee index 4a8796b1..5c4521e3 100644 --- a/app/coffee/app.coffee +++ b/app/coffee/app.coffee @@ -573,9 +573,10 @@ init = ($log, $rootscope, $auth, $events, $analytics, $translate, $location, $na Promise.setScheduler (cb) -> $rootscope.$evalAsync(cb) + $events.setupConnection() + # Load user if $auth.isAuthenticated() - $events.setupConnection() user = $auth.getUser() # Analytics diff --git a/app/coffee/modules/events.coffee b/app/coffee/modules/events.coffee index 82043d22..f7acf0bc 100644 --- a/app/coffee/modules/events.coffee +++ b/app/coffee/modules/events.coffee @@ -27,7 +27,7 @@ module = angular.module("taigaEvents", []) class EventsService - constructor: (@win, @log, @config, @auth) -> + constructor: (@win, @log, @config, @auth, @liveAnnouncementService, @rootScope) -> bindMethods(@) initialize: (sessionId) -> @@ -78,6 +78,11 @@ class EventsService delete @.ws + notifications: -> + @.subscribe null, 'notifications', (data) => + @liveAnnouncementService.show(data.title, data.desc) + @rootScope.$digest() + ########################################### # Heartbeat (Ping - Pong) ########################################### @@ -144,7 +149,12 @@ class EventsService return subscription = @.subscriptions[routingKey] - subscription.scope.$apply -> + + if subscription.scope + subscription.scope.$apply -> + subscription.callback(data.data) + + else subscription.callback(data.data) ########################################### @@ -168,7 +178,8 @@ class EventsService @.subscriptions[routingKey] = subscription @.sendMessage(message) - scope.$on("$destroy", => @.unsubscribe(routingKey)) + + scope.$on("$destroy", => @.unsubscribe(routingKey)) if scope unsubscribe: (routingKey) -> if @.error @@ -189,6 +200,7 @@ class EventsService onOpen: -> @.connected = true @.startHeartBeatMessages() + @.notifications() @log.debug("WebSocket connection opened") token = @auth.getToken() @@ -204,6 +216,7 @@ class EventsService @.log.debug "WebSocket message received: #{event.data}" data = JSON.parse(event.data) + if data.cmd == "pong" @.processHeartBeatPongMessage(data) else @@ -223,11 +236,18 @@ class EventsProvider setSessionId: (sessionId) -> @.sessionId = sessionId - $get: ($win, $log, $conf, $auth) -> - service = new EventsService($win, $log, $conf, $auth) + $get: ($win, $log, $conf, $auth, liveAnnouncementService, $rootScope) -> + service = new EventsService($win, $log, $conf, $auth, liveAnnouncementService, $rootScope) service.initialize(@.sessionId) return service - @.prototype.$get.$inject = ["$window", "$log", "$tgConfig", "$tgAuth"] + @.prototype.$get.$inject = [ + "$window", + "$log", + "$tgConfig", + "$tgAuth", + "tgLiveAnnouncementService", + "$rootScope" + ] module.provider("$tgEvents", EventsProvider) diff --git a/app/images/notification-decoration.png b/app/images/notification-decoration.png new file mode 100644 index 00000000..2147968c Binary files /dev/null and b/app/images/notification-decoration.png differ diff --git a/app/index.jade b/app/index.jade index 96aa95cd..b53656f9 100644 --- a/app/index.jade +++ b/app/index.jade @@ -38,6 +38,7 @@ html(lang="en") include partials/includes/components/notification-message div(tg-joy-ride) + div(tg-live-announcement) script(src="/#{v}/js/libs.js") script(src="/#{v}/js/templates.js") diff --git a/app/modules/components/live-announcement/live-announcement.directive.coffee b/app/modules/components/live-announcement/live-announcement.directive.coffee new file mode 100644 index 00000000..97f037d8 --- /dev/null +++ b/app/modules/components/live-announcement/live-announcement.directive.coffee @@ -0,0 +1,54 @@ +### +# Copyright (C) 2014-2015 Andrey Antukh +# Copyright (C) 2014-2015 Jesús Espino Garcia +# Copyright (C) 2014-2015 David Barragán Merino +# +# 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: live-announcement.directive.coffee +### + + +LiveAnnouncementDirective = (liveAnnouncementService) -> + link = (scope, el, attrs) -> + + return { + restrict: "AE", + scope: {}, + controllerAs: 'vm', + controller: () -> + this.close = () -> + liveAnnouncementService.open = false + + Object.defineProperties(this, { + open: { + get: () -> return liveAnnouncementService.open + }, + title: { + get: () -> return liveAnnouncementService.title + }, + desc: { + get: () -> return liveAnnouncementService.desc + } + }) + link: link, + templateUrl: "components/live-announcement/live-announcement.html" + } + +LiveAnnouncementDirective.$inject = [ + "tgLiveAnnouncementService" +] + +angular.module("taigaComponents") + .directive("tgLiveAnnouncement", LiveAnnouncementDirective) diff --git a/app/modules/components/live-announcement/live-announcement.jade b/app/modules/components/live-announcement/live-announcement.jade new file mode 100644 index 00000000..25df0db7 --- /dev/null +++ b/app/modules/components/live-announcement/live-announcement.jade @@ -0,0 +1,12 @@ +.live-announcement(ng-class="{visible: vm.open}") + .live-announcement-inner + img.anouncement-decoration(src="/#{v}/images/notification-decoration.png", alt="Loading...") + .text + h2.title {{vm.title}} + p.warning(ng-bind-html="vm.desc") + a.close( + ng-click="vm.close()" + href="" + title="{{ COMMON.CLOSE | translate }}" + ) + include ../../../svg/remove.svg diff --git a/app/modules/components/live-announcement/live-announcement.scss b/app/modules/components/live-announcement/live-announcement.scss new file mode 100644 index 00000000..d30cdd00 --- /dev/null +++ b/app/modules/components/live-announcement/live-announcement.scss @@ -0,0 +1,72 @@ +.live-announcement { + $animation-steps-duration: .5s; + align-content: center; + background: $tribe-primary; + display: flex; + height: 0; + justify-content: center; + overflow: hidden; + pointer-events: none; + position: fixed; + top: 0; + transition: width $animation-steps-duration, height $animation-steps-duration; + transition-delay: $animation-steps-duration; + width: 0; + z-index: 99; + .live-announcement-inner { + opacity: 0; + transition: opacity $animation-steps-duration; + width: 100%; + } + &.visible { + height: 146px; + pointer-events: auto; + transition-delay: 0s; + width: 100%; + .live-announcement-inner { + opacity: 1; + transition: opacity $animation-steps-duration $animation-steps-duration; + } + } +} + +.live-announcement-inner { + display: flex; + max-width: 1200px; + .announcement-decoration { + align-self: flex-end; + margin-right: 1rem; + } + .text { + padding: 1.25rem 3rem 1.25rem 2rem; + position: relative; + width: 100%; + } + .title { + @extend %bold; + @extend %larger; + color: $tribe-secondary; + margin-bottom: .5rem; + } + .warning { + color: $tribe-secondary; + a { + @extend %bold; + color: $tribe-secondary; + } + } + .close { + height: 2.5rem; + position: absolute; + right: 0; + top: 1rem; + width: 2.5rem; + svg { + fill: lighten($tribe-secondary, 15%); + transition: fill .2s; + &:hover { + fill: $tribe-secondary; + } + } + } +} diff --git a/app/modules/components/live-announcement/live-announcement.service.coffee b/app/modules/components/live-announcement/live-announcement.service.coffee new file mode 100644 index 00000000..a8c8168b --- /dev/null +++ b/app/modules/components/live-announcement/live-announcement.service.coffee @@ -0,0 +1,31 @@ +### +# 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: notification.service.coffee +### + +class LiveAnnouncementService extends taiga.Service + constructor: () -> + @.open = false + @.title = "" + @.desc = "" + + show: (title, desc) -> + @.open = true + @.title = title + @.desc = desc + +angular.module("taigaComponents").service("tgLiveAnnouncementService", LiveAnnouncementService) diff --git a/app/modules/projects/project/project.jade b/app/modules/projects/project/project.jade index 9f48bf12..8977c920 100644 --- a/app/modules/projects/project/project.jade +++ b/app/modules/projects/project/project.jade @@ -1,6 +1,6 @@ div.wrapper tg-project-menu - div.centered.single-project + div.single-project.centered section.single-project-intro div.intro-options h1 diff --git a/app/themes/taiga/variables.scss b/app/themes/taiga/variables.scss index 2eb4144b..12202bf2 100755 --- a/app/themes/taiga/variables.scss +++ b/app/themes/taiga/variables.scss @@ -31,6 +31,10 @@ $red-amaranth: #e43050; $purple-eggplant: #810061; $yellow-pear: #bbe831; +$tribe-primary: #98e0eb; +$tribe-secondary: #107a8a; + + $top-icon-color: #11241f; $dropdown-color: rgba(darken($grayer, 20%), 1);