Merge Request #39
Design mode - Added 'inDesign' Toggler
Added 'inDesign' Toggler to allow users with permission to edit to profile to change the profile layout dispositions and customizations
issue #91
-
The use of bootstrap to start the application broke the startup process.
-
…designModeToggler Component
... | ... | @@ -35,9 +35,10 @@ |
35 | 35 | "angular-i18n": "^1.5.0", |
36 | 36 | "angular-load": "^0.4.1", |
37 | 37 | "angular-translate-interpolation-messageformat": "^2.10.0", |
38 | - "angular-bind-html-compile": "^1.2.1", | |
39 | - "angular-click-outside": "^2.7.1", | |
40 | - "ng-ckeditor": "^0.2.1" | |
38 | + "angular-bind-html-compile": "^1.2.1", | |
39 | + "angular-click-outside": "^2.7.1", | |
40 | + "ng-ckeditor": "^0.2.1", | |
41 | + "angular-bootstrap-toggle-switch": "^0.5.6" | |
41 | 42 | }, |
42 | 43 | "devDependencies": { |
43 | 44 | "angular-mocks": "~1.5.0" | ... | ... |
... | ... | @@ -0,0 +1,31 @@ |
1 | +import {DesignModeService} from './designMode.service'; | |
2 | + | |
3 | +describe('DesignMode Service', () => { | |
4 | + let service: DesignModeService; | |
5 | + | |
6 | + beforeEach(() => { | |
7 | + service = new DesignModeService(); | |
8 | + }); | |
9 | + | |
10 | + it('has the designModeOn equals false as default', () => { | |
11 | + expect(service.isInDesignMode()).toBeFalsy(); | |
12 | + }); | |
13 | + | |
14 | + it('allows set the designMode value', () => { | |
15 | + spyOn(service.onToggle, 'next').and.stub(); | |
16 | + service.setInDesignMode(true); | |
17 | + expect(service.isInDesignMode).toBeTruthy(); | |
18 | + }); | |
19 | + | |
20 | + it('emits the onToggle event when changing the designModeOn property', () => { | |
21 | + spyOn(service.onToggle, 'next').and.stub(); | |
22 | + service.setInDesignMode(true); | |
23 | + expect(service.onToggle.next).toHaveBeenCalled(); | |
24 | + }); | |
25 | + | |
26 | + it('does not emit onToggle event when there is no change on designModeOn property', () => { | |
27 | + spyOn(service.onToggle, 'next').and.stub(); | |
28 | + service.setInDesignMode(false); | |
29 | + expect(service.onToggle.next).not.toHaveBeenCalled(); | |
30 | + }); | |
31 | +}); | |
0 | 32 | \ No newline at end of file | ... | ... |
... | ... | @@ -0,0 +1,23 @@ |
1 | +import {Component, Injectable, Output, EventEmitter} from 'ng-forward'; | |
2 | +import {BodyStateClassesService} from '../../layout/services/body-state-classes.service'; | |
3 | + | |
4 | +@Injectable() | |
5 | +export class DesignModeService { | |
6 | + @Output() onToggle: EventEmitter<boolean> = new EventEmitter<boolean>(); | |
7 | + | |
8 | + private designModeOn: boolean = false; | |
9 | + | |
10 | + isInDesignMode(): boolean { | |
11 | + return this.designModeOn; | |
12 | + } | |
13 | + | |
14 | + setInDesignMode(value: boolean) { | |
15 | + if (this.designModeOn !== value) { | |
16 | + this.designModeOn = value; | |
17 | + this.onToggle.next(this.designModeOn); | |
18 | + } | |
19 | + } | |
20 | + | |
21 | + constructor() { | |
22 | + } | |
23 | +} | |
0 | 24 | \ No newline at end of file | ... | ... |
... | ... | @@ -0,0 +1,59 @@ |
1 | +import {ComponentTestHelper, createClass} from '../../../spec/component-test-helper'; | |
2 | +import * as helpers from '../../../spec/helpers'; | |
3 | +import {DesignModeTogglerComponent} from './designModeToggler.component'; | |
4 | +import {DesignModeService} from './designMode.service'; | |
5 | + | |
6 | +describe('DesignModeToggler Component', () => { | |
7 | + const htmlTemplate: string = '<noosfero-design-toggler></noosfero-design-toggler>'; | |
8 | + | |
9 | + let helper: ComponentTestHelper<DesignModeTogglerComponent>; | |
10 | + beforeEach(() => { | |
11 | + angular.mock.module('templates'); | |
12 | + angular.mock.module('ngSanitize'); | |
13 | + angular.mock.module('toggle-switch'); | |
14 | + }); | |
15 | + | |
16 | + let designModeService: DesignModeService; | |
17 | + beforeEach((done) => { | |
18 | + designModeService = new DesignModeService(); | |
2 |
|
|
19 | + let cls = createClass({ | |
20 | + template: htmlTemplate, | |
21 | + directives: [DesignModeTogglerComponent], | |
22 | + providers: [ | |
23 | + helpers.createProviderToValue('DesignModeService', designModeService) | |
24 | + ] | |
25 | + }); | |
26 | + helper = new ComponentTestHelper<DesignModeTogglerComponent>(cls, done); | |
27 | + }); | |
28 | + | |
29 | + it('changes css classes representing the switch is on or off', () => { | |
30 | + expect(helper.debugElement.query('div.switch-animate').hasClass('switch-off')).toBeTruthy(); | |
31 | + expect(helper.debugElement.query('div.switch-animate').hasClass('switch-on')).toBeFalsy(); | |
32 | + helper.component.inDesignMode = true; | |
33 | + helper.detectChanges(); | |
34 | + expect(helper.debugElement.query('div.switch-animate').hasClass('switch-on')).toBeTruthy(); | |
35 | + expect(helper.debugElement.query('div.switch-animate').hasClass('switch-off')).toBeFalsy(); | |
36 | + }); | |
37 | + | |
38 | + it('emits event with value "true" when changing inDesignMode to On', (done) => { | |
39 | + designModeService.onToggle.subscribe((designModeOn: boolean) => { | |
40 | + expect(designModeOn).toBeTruthy(); | |
41 | + done(); | |
42 | + }); | |
43 | + helper.component.inDesignMode = true; | |
44 | + helper.detectChanges(); | |
45 | + }); | |
46 | + | |
47 | + it('emits events with value "false" when changing inDesignMode to Off', (done) => { | |
48 | + helper.component.inDesignMode = true; | |
49 | + helper.detectChanges(); | |
50 | + | |
51 | + designModeService.onToggle.subscribe((designModeOn: boolean) => { | |
52 | + expect(designModeOn).toBeFalsy(); | |
53 | + done(); | |
54 | + }); | |
55 | + | |
56 | + helper.component.inDesignMode = false; | |
57 | + helper.detectChanges(); | |
58 | + }); | |
59 | +}); | |
0 | 60 | \ No newline at end of file | ... | ... |
... | ... | @@ -0,0 +1,24 @@ |
1 | +import {Component, Inject} from 'ng-forward'; | |
2 | +import {DesignModeService} from './designMode.service'; | |
3 | +@Component({ | |
4 | + selector: 'noosfero-design-toggler', | |
5 | + templateUrl: 'app/admin/layout-edit/designModeToggler.html' | |
6 | +}) | |
7 | +@Inject(DesignModeService) | |
8 | +export class DesignModeTogglerComponent { | |
9 | + | |
10 | + icon: string = " <i class='glyphicon glyphicon-wrench'></i> "; | |
2 |
|
|
11 | + | |
12 | + constructor(private designModeService: DesignModeService) { | |
13 | + } | |
14 | + | |
15 | + private _inDesignMode: boolean = false; | |
16 | + | |
17 | + get inDesignMode(): boolean { | |
2 |
|
|
18 | + return this.designModeService.isInDesignMode(); | |
19 | + }; | |
20 | + | |
21 | + set inDesignMode(value: boolean) { | |
22 | + this.designModeService.setInDesignMode(value); | |
23 | + }; | |
24 | +} | |
0 | 25 | \ No newline at end of file | ... | ... |
... | ... | @@ -0,0 +1,8 @@ |
1 | +<toggle-switch | |
2 | + html="true" | |
3 | + ng-model="ctrl.inDesignMode" | |
4 | + on-label="{{'designMode.toggle.ON' | translate}}" | |
5 | + off-label="{{'designMode.toggle.OFF' | translate}}" | |
6 | + class="switch-small" | |
7 | + knob-label="{{ ctrl.icon + ('designMode.label' | translate) }}"> | |
8 | +</toggle-switch> | |
0 | 9 | \ No newline at end of file | ... | ... |
... | ... | @@ -14,7 +14,7 @@ import {ProfileService} from "../../../lib/ng-noosfero-api/http/profile.service" |
14 | 14 | provide('profileService', { useClass: ProfileService }) |
15 | 15 | ] |
16 | 16 | }) |
17 | -@Inject(ArticleService, ProfileService, "$log", "$stateParams") | |
17 | +@Inject(ArticleService, ProfileService, "$stateParams") | |
18 | 18 | export class ContentViewerComponent { |
19 | 19 | |
20 | 20 | @Input() |
... | ... | @@ -23,7 +23,10 @@ export class ContentViewerComponent { |
23 | 23 | @Input() |
24 | 24 | profile: noosfero.Profile = null; |
25 | 25 | |
26 | - constructor(private articleService: ArticleService, private profileService: ProfileService, private $log: ng.ILogService, private $stateParams: angular.ui.IStateParamsService) { | |
26 | + constructor( | |
27 | + private articleService: ArticleService, | |
28 | + private profileService: ProfileService, | |
29 | + private $stateParams: angular.ui.IStateParamsService) { | |
27 | 30 | this.activate(); |
28 | 31 | } |
29 | 32 | ... | ... |
... | ... | @@ -26,19 +26,20 @@ |
26 | 26 | <span ng-bind="ctrl.currentUser.person.name"></span> <b class="caret"></b> |
27 | 27 | </a> |
28 | 28 | <ul class="dropdown-menu" uib-dropdown-menu> |
29 | - <li> | |
3 |
|
|
30 | - <a ui-sref="main.profile.info({profile: ctrl.currentUser.person.identifier})"><i class="fa fa-fw fa-user"></i> {{"navbar.profile" | translate}}</a> | |
31 | - </li> | |
32 | - <li> | |
33 | - <a target="_self" ui-sref="main.profile.settings({profile: ctrl.currentUser.person.identifier})"><i class="fa fa-fw fa-gear"></i> {{"navbar.settings" | translate}}</a> | |
34 | - </li> | |
35 | - <li class="divider"></li> | |
36 | - <li> | |
37 | - <a href="#" ng-click="ctrl.logout()"><i class="fa fa-fw fa-power-off"></i> {{"navbar.logout" | translate}}</a> | |
38 | - </li> | |
29 | + <li> | |
30 | + <a ui-sref="main.profile.info({profile: ctrl.currentUser.person.identifier})"><i class="fa fa-fw fa-user"></i> {{"navbar.profile" | translate}}</a> | |
31 | + </li> | |
32 | + <li> | |
33 | + <a target="_self" ui-sref="main.profile.settings({profile: ctrl.currentUser.person.identifier})"><i class="fa fa-fw fa-gear"></i> {{"navbar.settings" | translate}}</a> | |
34 | + </li> | |
35 | + <li class="divider"></li> | |
36 | + <li> | |
37 | + <a href="#" ng-click="ctrl.logout()"><i class="fa fa-fw fa-power-off"></i> {{"navbar.logout" | translate}}</a> | |
38 | + </li> | |
39 | 39 | </ul> |
40 | 40 | </li> |
41 | 41 | </ul> |
42 | + | |
42 | 43 | <ul class="nav navbar-nav navbar-right"> |
43 | 44 | <language-selector class="nav navbar-nav navbar-right"></language-selector> |
44 | 45 | </ul> |
... | ... | @@ -49,3 +50,5 @@ |
49 | 50 | </div> |
50 | 51 | </div> |
51 | 52 | </nav> |
53 | +<div ui-view="toolbar"> | |
54 | +</div> | |
52 | 55 | \ No newline at end of file | ... | ... |
... | ... | @@ -4,11 +4,13 @@ import {SessionService, AuthService, AuthController, AuthEvents} from "./../../l |
4 | 4 | import {EnvironmentService} from "./../../../lib/ng-noosfero-api/http/environment.service"; |
5 | 5 | import {SidebarNotificationService} from "../sidebar/sidebar.notification.service"; |
6 | 6 | import {BodyStateClassesService} from '../services/body-state-classes.service'; |
7 | +import {DesignModeTogglerComponent} from './../../admin/layout-edit/designModeToggler.component'; | |
8 | +import {BootstrapSwitcherComponent, BootstrapSwitcherItem} from './../../shared/components/bootstrap-switcher/bootstrap-switcher.component'; | |
7 | 9 | |
8 | 10 | @Component({ |
9 | 11 | selector: "acme-navbar", |
10 | 12 | templateUrl: "app/layout/navbar/navbar.html", |
11 | - directives: [LanguageSelectorComponent], | |
13 | + directives: [LanguageSelectorComponent, DesignModeTogglerComponent, BootstrapSwitcherComponent], | |
12 | 14 | providers: [AuthService, SessionService, SidebarNotificationService, EnvironmentService] |
13 | 15 | }) |
14 | 16 | @Inject("$uibModal", AuthService, "SessionService", "$state", SidebarNotificationService, BodyStateClassesService, EnvironmentService) |
... | ... | @@ -18,7 +20,6 @@ export class Navbar { |
18 | 20 | private modalInstance: any = null; |
19 | 21 | public showHamburger: boolean = false; |
20 | 22 | public currentEnvironment: noosfero.Environment = <any>{ name: '' }; |
21 | - | |
22 | 23 | /** |
23 | 24 | * |
24 | 25 | */ | ... | ... |
... | ... | @@ -35,3 +35,24 @@ |
35 | 35 | padding: 0 20px 20px 20px; |
36 | 36 | } |
37 | 37 | } |
38 | + | |
39 | + | |
40 | +body.noosfero-design-on { | |
41 | + | |
42 | + div.content-wrapper { | |
43 | + opacity: 0.5; | |
44 | + } | |
45 | +} | |
46 | + | |
47 | +ul.nav > li.toggler-container { | |
2 |
|
|
48 | + position: relative; | |
49 | + padding-top: 12px; | |
50 | +} | |
51 | + | |
52 | +.noosfero-main-toolbar { | |
53 | + padding: 5px; | |
54 | + @include make-row(); | |
55 | + margin-left: 0px; | |
56 | + margin-right: 0px; | |
57 | + background-color: #edecec; | |
58 | +} | |
38 | 59 | \ No newline at end of file | ... | ... |
... | ... | @@ -281,6 +281,31 @@ $whbl-font-color: #16191c; |
281 | 281 | .pace .pace-progress { |
282 | 282 | background-color: #fff; |
283 | 283 | } |
284 | + | |
285 | + noosfero-design-toggler .ats-switch .knob i { | |
286 | + color: #999999; | |
287 | + } | |
288 | + | |
289 | + .ats-switch .knob { | |
290 | + padding-right: 15px; | |
291 | + } | |
292 | + | |
293 | + .ats-switch .glyphicon { | |
294 | + top: 2px; | |
295 | + } | |
296 | + | |
297 | + .ats-switch .switch-left { | |
298 | + background-color: #41b941; | |
299 | + font-weight: bold; | |
300 | + color: white; | |
301 | + } | |
302 | + | |
303 | + .ats-switch .switch-right { | |
304 | + background-color: #ce3b3b; | |
305 | + font-weight: bold; | |
306 | + color: white; | |
307 | + } | |
308 | + | |
284 | 309 | } |
285 | 310 | .rtl.skin-whbl #content-wrapper { |
286 | 311 | border-left: 0; | ... | ... |
... | ... | @@ -4,7 +4,7 @@ import {AuthService} from "./../../login/auth.service"; |
4 | 4 | import {AuthEvents} from "./../../login/auth-events"; |
5 | 5 | |
6 | 6 | import {EventEmitter} from 'ng-forward'; |
7 | - | |
7 | +import {DesignModeService} from './../../admin/layout-edit/designMode.service'; | |
8 | 8 | |
9 | 9 | describe("BodyStateClasses Service", () => { |
10 | 10 | |
... | ... | @@ -19,10 +19,13 @@ describe("BodyStateClasses Service", () => { |
19 | 19 | }, |
20 | 20 | authService: any = helpers.mocks.authService, |
21 | 21 | bodyEl: { className: string }, |
22 | - bodyElJq: any; | |
22 | + bodyElJq: any, | |
23 | + designModeService = new DesignModeService(); | |
24 | + | |
25 | + | |
23 | 26 | |
24 | 27 | let getService = (): BodyStateClassesService => { |
25 | - return new BodyStateClassesService($rootScope, $document, $state, authService); | |
28 | + return new BodyStateClassesService($rootScope, $document, $state, authService, designModeService); | |
26 | 29 | }; |
27 | 30 | |
28 | 31 | beforeEach(() => { |
... | ... | @@ -168,4 +171,30 @@ describe("BodyStateClasses Service", () => { |
168 | 171 | |
169 | 172 | expect(contentWrapperMock.removeClass).toHaveBeenCalledWith(BodyStateClassesService.CONTENT_WRAPPER_FULL); |
170 | 173 | }); |
174 | + | |
175 | + it("should add the class noosfero-design-on when designMode is changed to true", () => { | |
176 | + let fnOnToggle: Function = null; | |
177 | + designModeService.onToggle = <any> { | |
178 | + subscribe: (fn: Function) => { | |
179 | + fnOnToggle = fn; | |
180 | + }, | |
181 | + next: (value: boolean) => { | |
182 | + fnOnToggle.apply(designModeService, [value]); | |
183 | + } | |
184 | + }; | |
185 | + | |
186 | + let service = getService(); | |
187 | + | |
188 | + bodyElJq.addClass = jasmine.createSpy("addClass"); | |
189 | + bodyElJq.removeClass = jasmine.createSpy("removeClass"); | |
190 | + service["bodyElement"] = bodyElJq; | |
191 | + | |
192 | + service.start(); | |
193 | + | |
194 | + debugger; | |
195 | + designModeService.setInDesignMode(true); | |
196 | + | |
197 | + | |
198 | + expect(bodyElJq.addClass).toHaveBeenCalledWith(BodyStateClassesService.DESIGN_MODE_ON_CLASSNAME); | |
199 | + }); | |
171 | 200 | }); | ... | ... |
... | ... | @@ -3,6 +3,7 @@ import {AuthEvents} from "../../login/auth-events"; |
3 | 3 | import {AuthService} from "./../../login/auth.service"; |
4 | 4 | import {HtmlUtils} from "../html-utils"; |
5 | 5 | import {INgForwardJQuery} from 'ng-forward/cjs/util/jqlite-extensions'; |
6 | +import {DesignModeService} from './../../admin/layout-edit/designMode.service'; | |
6 | 7 | |
7 | 8 | export interface StartParams { |
8 | 9 | skin?: string; |
... | ... | @@ -22,12 +23,13 @@ export interface StartParams { |
22 | 23 | * - full-content |
23 | 24 | */ |
24 | 25 | @Injectable() |
25 | -@Inject("$rootScope", "$document", "$state", AuthService) | |
26 | +@Inject("$rootScope", "$document", "$state", AuthService, DesignModeService) | |
26 | 27 | export class BodyStateClassesService { |
27 | 28 | |
28 | 29 | private started: boolean = false; |
29 | 30 | private skin: string; |
30 | 31 | |
32 | + public static get DESIGN_MODE_ON_CLASSNAME(): string { return "noosfero-design-on"; } | |
31 | 33 | public static get USER_LOGGED_CLASSNAME(): string { return "noosfero-user-logged"; } |
32 | 34 | public static get ROUTE_STATE_CLASSNAME_PREFIX(): string { return "noosfero-route-"; } |
33 | 35 | public static get CONTENT_WRAPPER_FULL(): string { return "full-content"; } |
... | ... | @@ -38,16 +40,16 @@ export class BodyStateClassesService { |
38 | 40 | private $rootScope: ng.IRootScopeService, |
39 | 41 | private $document: ng.IDocumentService, |
40 | 42 | private $state: ng.ui.IStateService, |
41 | - private authService: AuthService | |
43 | + private authService: AuthService, | |
44 | + private designModeService: DesignModeService | |
42 | 45 | ) { |
43 | - | |
44 | 46 | } |
45 | 47 | |
46 | 48 | start(config?: StartParams) { |
47 | 49 | if (!this.started) { |
48 | 50 | this.setupUserLoggedClassToggle(); |
49 | 51 | this.setupStateClassToggle(); |
50 | - | |
52 | + this.setupDesignModeClassToggle(); | |
51 | 53 | if (config) { |
52 | 54 | this.setThemeSkin(config.skin); |
53 | 55 | } |
... | ... | @@ -87,9 +89,19 @@ export class BodyStateClassesService { |
87 | 89 | } |
88 | 90 | |
89 | 91 | /** |
90 | - * Setup the initial class name on body element indicating the current route | |
91 | - * and adds event handler to swith this class when the current page/state changes | |
92 | + * setup the listeners to the desigModeService to add class on the Body Element | |
93 | + * indicating the user activated the designMode | |
92 | 94 | */ |
95 | + private setupDesignModeClassToggle() { | |
96 | + this.designModeService.onToggle.subscribe((designOn: boolean) => { | |
97 | + if (designOn) { | |
98 | + this.getBodyElement().addClass(BodyStateClassesService.DESIGN_MODE_ON_CLASSNAME); | |
99 | + } else { | |
100 | + this.getBodyElement().removeClass(BodyStateClassesService.DESIGN_MODE_ON_CLASSNAME); | |
101 | + } | |
102 | + }); | |
103 | + } | |
104 | + | |
93 | 105 | private setupStateClassToggle() { |
94 | 106 | let bodyElement = this.getBodyElement(); |
95 | 107 | bodyElement.addClass(BodyStateClassesService.ROUTE_STATE_CLASSNAME_PREFIX + this.$state.current.name); | ... | ... |
... | ... | @@ -111,7 +111,7 @@ export class EnvironmentContent { |
111 | 111 | "angular-bind-html-compile", "angularMoment", "angular.filter", "akoenig.deckgrid", |
112 | 112 | "angular-timeline", "duScroll", "oitozero.ngSweetAlert", |
113 | 113 | "pascalprecht.translate", "tmh.dynamicLocale", "angularLoad", |
114 | - "angular-click-outside", "noosfero.init"] | |
114 | + "angular-click-outside", "toggle-switch", "noosfero.init"] | |
115 | 115 | }) |
116 | 116 | @StateConfig([ |
117 | 117 | { | ... | ... |
... | ... | @@ -0,0 +1,21 @@ |
1 | +import {Component, Inject, provide} from "ng-forward"; | |
2 | +import {ProfileService} from "../../lib/ng-noosfero-api/http/profile.service"; | |
3 | + | |
4 | +@Component({ | |
5 | + selector: "profile-toolbar", | |
6 | + templateUrl: "app/profile/toolbar.html", | |
7 | + providers: [ | |
8 | + provide('profileService', { useClass: ProfileService }) | |
9 | + ] | |
10 | +}) | |
11 | +@Inject(ProfileService) | |
12 | +export class ProfileToolbarComponent { | |
2 |
|
|
13 | + profile: noosfero.Profile; | |
14 | + parentId: number; | |
15 | + | |
16 | + constructor(profileService: ProfileService) { | |
17 | + profileService.getCurrentProfile().then((profile: noosfero.Profile) => { | |
18 | + this.profile = profile; | |
19 | + }); | |
20 | + } | |
21 | +} | ... | ... |
... | ... | @@ -10,7 +10,7 @@ import {ProfileService} from "../../lib/ng-noosfero-api/http/profile.service"; |
10 | 10 | import {NotificationService} from "../shared/services/notification.service"; |
11 | 11 | import {MyProfileComponent} from "./myprofile.component"; |
12 | 12 | import {ProfileActionsComponent} from "./profile-actions.component"; |
13 | - | |
13 | +import {ProfileToolbarComponent} from "./profile-toolbar.component"; | |
14 | 14 | /** |
15 | 15 | * @ngdoc controller |
16 | 16 | * @name profile.Profile |
... | ... | @@ -42,6 +42,11 @@ import {ProfileActionsComponent} from "./profile-actions.component"; |
42 | 42 | templateUrl: "app/profile/navbar-actions.html", |
43 | 43 | controller: ProfileActionsComponent, |
44 | 44 | controllerAs: "vm" |
45 | + }, | |
46 | + "toolbar@main": { | |
47 | + templateUrl: "app/profile/toolbar.html", | |
48 | + controller: ProfileToolbarComponent, | |
49 | + controllerAs: "vm" | |
45 | 50 | } |
46 | 51 | } |
47 | 52 | }, |
... | ... | @@ -54,6 +59,11 @@ import {ProfileActionsComponent} from "./profile-actions.component"; |
54 | 59 | templateUrl: "app/profile/navbar-actions.html", |
55 | 60 | controller: ProfileActionsComponent, |
56 | 61 | controllerAs: "vm" |
62 | + }, | |
63 | + "toolbar@main": { | |
64 | + templateUrl: "app/profile/toolbar.html", | |
65 | + controller: ProfileToolbarComponent, | |
66 | + controllerAs: "vm" | |
57 | 67 | } |
58 | 68 | } |
59 | 69 | }, |
... | ... | @@ -106,6 +116,11 @@ import {ProfileActionsComponent} from "./profile-actions.component"; |
106 | 116 | templateUrl: "app/article/content-viewer/navbar-actions.html", |
107 | 117 | controller: ContentViewerActionsComponent, |
108 | 118 | controllerAs: "vm" |
119 | + }, | |
120 | + "toolbar@main": { | |
121 | + templateUrl: "app/profile/toolbar.html", | |
122 | + controller: ProfileToolbarComponent, | |
123 | + controllerAs: "vm" | |
109 | 124 | } |
110 | 125 | } |
111 | 126 | } | ... | ... |
... | ... | @@ -0,0 +1,51 @@ |
1 | +import {Component, Input, Output, EventEmitter} from 'ng-forward'; | |
2 | + | |
3 | + | |
4 | +export interface BootstrapSwitcherItem { | |
5 | + value: any; | |
6 | + label: string; | |
7 | +} | |
8 | +@Component({ | |
9 | + selector: 'noosfero-bootstrap-switcher', | |
10 | + template: ` | |
11 | + <span class="switcher-label" ng-bind="ctrl.label | translate"></span> | |
12 | + <div class="btn-group switcher"> | |
13 | + <button ng-repeat="option in ctrl.options track by $index" | |
14 | + (click)="ctrl.switcherClick(option)" | |
15 | + ng-class="ctrl.getCssClassForItem(option)" | |
16 | + class="btn btn-xs" ng-bind="option.label | translate"> | |
17 | + </button> | |
18 | + </div> | |
19 | + `, | |
20 | + inputs: ['activeClass', 'defaultClass', 'label', 'options', 'defaultOption'], | |
21 | + outputs: ['onSwitch'] | |
22 | +}) | |
23 | +export class BootstrapSwitcherComponent { | |
24 | + @Input() activeClass: string = 'active btn-danger'; | |
25 | + @Input() defaultClass: string = 'btn-default'; | |
26 | + @Input() label: string; | |
27 | + @Input() options: BootstrapSwitcherItem[]; | |
28 | + @Input() defaultOption: BootstrapSwitcherItem; | |
29 | + @Output() onSwitch: EventEmitter<BootstrapSwitcherItem> = new EventEmitter<BootstrapSwitcherItem>(); | |
30 | + | |
31 | + selectedOption: BootstrapSwitcherItem = null; | |
32 | + | |
33 | + constructor() { } | |
34 | + | |
35 | + ngOnInit() { | |
36 | + this.selectedOption = this.defaultOption; | |
37 | + } | |
38 | + | |
39 | + isSelectedOption(value: BootstrapSwitcherItem): boolean { | |
40 | + return this.selectedOption === value; | |
41 | + } | |
42 | + | |
43 | + getCssClassForItem(value: BootstrapSwitcherItem): string { | |
44 | + return this.isSelectedOption(value) ? this.activeClass : this.defaultClass; | |
45 | + } | |
46 | + | |
47 | + switcherClick(value: BootstrapSwitcherItem) { | |
48 | + this.selectedOption = value; | |
49 | + this.onSwitch.next(value); | |
50 | + } | |
51 | +} | |
0 | 52 | \ No newline at end of file | ... | ... |
... | ... | @@ -0,0 +1,5 @@ |
1 | + | |
2 | + | |
3 | +export interface IComponentWithPermissions { | |
2 |
|
|
4 | + permissions: () => string[]; | |
5 | +} | |
0 | 6 | \ No newline at end of file | ... | ... |
... | ... | @@ -6,7 +6,7 @@ import {Directive, Inject, Input} from "ng-forward"; |
6 | 6 | @Inject('$attrs', '$scope', '$element') |
7 | 7 | export class PermissionDirective { |
8 | 8 | |
9 | - constructor($attrs: ng.IAttributes, $scope: ng.IScope, $element: any) { | |
9 | + constructor($attrs: ng.IAttributes, $scope: ng.IScope, $element: ng.IAugmentedJQuery) { | |
10 | 10 | $scope.$watch($attrs['permission'], () => { |
11 | 11 | let permissions = $scope.$eval($attrs['permission']); |
12 | 12 | let permissionAction = $attrs['permissionAction']; | ... | ... |
... | ... | @@ -75,6 +75,9 @@ |
75 | 75 | "custom_content.title": "Edit content", |
76 | 76 | "profile.custom_header.label": "Header", |
77 | 77 | "profile.custom_footer.label": "Footer", |
78 | + "designMode.label": "In Design", | |
79 | + "designMode.toggle.ON": "ON", | |
80 | + "designMode.toggle.OFF": "OFF", | |
78 | 81 | "search.results.summary": "{results, plural, one{result} other{# results}}", |
79 | 82 | "search.results.query.label": "Search for:", |
80 | 83 | "search.results.query.placeholder": "Search" | ... | ... |
... | ... | @@ -75,6 +75,9 @@ |
75 | 75 | "custom_content.title": "Editar conteúdo", |
76 | 76 | "profile.custom_header.label": "Cabeçalho", |
77 | 77 | "profile.custom_footer.label": "Rodapé", |
78 | + "designMode.label": "Modo de Edição", | |
79 | + "designMode.toggle.ON": "Ligado", | |
80 | + "designMode.toggle.OFF": "Desligado", | |
78 | 81 | "search.results.summary": "{results, plural, one{# resultado} other{# resultados}}", |
79 | 82 | "search.results.query.label": "Buscar:", |
80 | 83 | "search.results.query.placeholder": "Informe aqui sua busca" | ... | ... |
... | ... | @@ -22,7 +22,7 @@ export class ProfileService { |
22 | 22 | this._currentProfilePromise.resolve(profile); |
23 | 23 | } |
24 | 24 | |
25 | - setCurrentProfileByIdentifier(identifier: string) { | |
25 | + setCurrentProfileByIdentifier(identifier: string): ng.IPromise<noosfero.Profile> { | |
26 | 26 | this.resetCurrentProfile(); |
27 | 27 | return this.getByIdentifier(identifier).then((profile: noosfero.Profile) => { |
28 | 28 | this.setCurrentProfile(profile); | ... | ... |
-
Reassigned to @mfdeveloper
-
Pq não utilizar um mock deste serviço nesse teste? Algum motivo em especial? Em linhas gerais, temos mocado todas as dependências nos outros testes, correto?
Talvez seja interessante padronizar uma abordagem geral, para que todos nós saibamos quando (ou não) utilizar mocks para as dependências. Principalmente nesse caso q este serviço utiliza um
EventEmitter
internamente :) -
a idéia é fazer mock das dependências e não do próprio componente "under test". Nesse caso como ele não tem dependências não precisei criar mocks. Apenas utilizei "stubs" onde precisei
-
Não seria mais interessante passar somente as classes css aq neste atributo e mover o HTML para o template? Me pareceu meio "poluído" este HTML dentro do componente.
-
OK, Feito
-
Outros componentes que quiserem utilizar o serviço
DesignModeService
precisariam criar um get/set dessa forma? -
Não. utilizado apenas para bind com o componente visual
-
Este css n deveria ficar no arquivo .sass do component
DesignModeTogglerComponent
? -
OK
-
Essa nova toolbar é especifica do profile ou do contexto geral da aplicação? Ela só será exibida ao acessar profiles (pessoas, comunidades...) ?
-
A idéia é que possa apresentar botões relativos aos profiles e de contexto geral. No momento existe apenas um botão (toggler) que é comum mas a idéia é ter opções diferenciadas se for profile ou se for environment
Segui o que é feito com o navbar-actions.
-
Subtemas deveriam sobrescrever os arquivos .scss do tema principal n? Não entendi muito bem como está essa estrutura de modificações de sass em subtemas, mas colocar tdo isso em um único sass me parece "quebrar" a organização do tema principal :(
-
Algumas sugestões de alteração visual:
- O botão de toggle está com uma borda forte quando está em foco (
:focus
). Talvez igualar o mesmo estilo do:hover
já melhore bastante!
- Sugiro remover o gradiente da nova toolbar e ajustar a cor de fundo para um cinza claro e sucinto:
#edecec
- O botão de toggle está com uma borda forte quando está em foco (
-
Added 14 new commits:
- a8b16135 - Fix application startup process
- dc8c43fd - adding KNOWN ISSUES section to README.md
- 6a66f44b - Merge branch 'master' of softwarepublico.gov.br:noosfero-themes/angular-theme
- 63a0a102 - Fix application startup process
- d00c6e8c - Added export comments button on comment paragraph plugin
- ba519042 - Display button to export comments only to users with permission to edit article
- 36e4d9d9 - Merge branch 'export-comments' into 'master'
- f276722d - New npm config 'skin' to allow sass by theme skins
- 04a99db3 - Merge branch 'theme-skin-yellow'
- 69ff070c - Component to search for articles in the environment
- 5bc45a30 - fixed pt translation
- 575daccf - changed search.component and the search-form.component to fill the query field w…
- d0fa7823 - Merge branch 'search' into 'master'
- f4964918 - merge with master. fixed toolbar background-color. small fixes on BootstrapSwitcherItem
-
Status changed to closed
-
Status changed to reopened
-
mentioned in commit 9d2ce38269021599b00f6f56c51207a021c2566e
-
mentioned in commit 9f5c1aa1ff115e8e76be0e77e120d1b726cf5602
-
mentioned in commit 8690849bc505ac8e8581406b4993aebb453aecfb
-
mentioned in commit f74c41d25288ab4a55b81192240ecfce965ef4ce
3 | +import {DesignModeTogglerComponent} from './designModeToggler.component'; | |
4 | +import {DesignModeService} from './designMode.service'; | |
5 | + | |
6 | +describe('DesignModeToggler Component', () => { | |
7 | + const htmlTemplate: string = '<noosfero-design-toggler></noosfero-design-toggler>'; | |
8 | + | |
9 | + let helper: ComponentTestHelper<DesignModeTogglerComponent>; | |
10 | + beforeEach(() => { | |
11 | + angular.mock.module('templates'); | |
12 | + angular.mock.module('ngSanitize'); | |
13 | + angular.mock.module('toggle-switch'); | |
14 | + }); | |
15 | + | |
16 | + let designModeService: DesignModeService; | |
17 | + beforeEach((done) => { | |
18 | + designModeService = new DesignModeService(); | |
2 |
|
1 | +import {Component, Inject} from 'ng-forward'; | |
2 | +import {DesignModeService} from './designMode.service'; | |
3 | +@Component({ | |
4 | + selector: 'noosfero-design-toggler', | |
5 | + templateUrl: 'app/admin/layout-edit/designModeToggler.html' | |
6 | +}) | |
7 | +@Inject(DesignModeService) | |
8 | +export class DesignModeTogglerComponent { | |
9 | + | |
10 | + icon: string = " <i class='glyphicon glyphicon-wrench'></i> "; | |
2 |
|
2 | +import {DesignModeService} from './designMode.service'; | |
3 | +@Component({ | |
4 | + selector: 'noosfero-design-toggler', | |
5 | + templateUrl: 'app/admin/layout-edit/designModeToggler.html' | |
6 | +}) | |
7 | +@Inject(DesignModeService) | |
8 | +export class DesignModeTogglerComponent { | |
9 | + | |
10 | + icon: string = " <i class='glyphicon glyphicon-wrench'></i> "; | |
11 | + | |
12 | + constructor(private designModeService: DesignModeService) { | |
13 | + } | |
14 | + | |
15 | + private _inDesignMode: boolean = false; | |
16 | + | |
17 | + get inDesignMode(): boolean { | |
2 |
|
35 | 35 | padding: 0 20px 20px 20px; |
36 | 36 | } |
37 | 37 | } |
38 | + | |
39 | + | |
40 | +body.noosfero-design-on { | |
41 | + | |
42 | + div.content-wrapper { | |
43 | + opacity: 0.5; | |
44 | + } | |
45 | +} | |
46 | + | |
47 | +ul.nav > li.toggler-container { | |
2 |
|
1 | +import {Component, Inject, provide} from "ng-forward"; | |
2 | +import {ProfileService} from "../../lib/ng-noosfero-api/http/profile.service"; | |
3 | + | |
4 | +@Component({ | |
5 | + selector: "profile-toolbar", | |
6 | + templateUrl: "app/profile/toolbar.html", | |
7 | + providers: [ | |
8 | + provide('profileService', { useClass: ProfileService }) | |
9 | + ] | |
10 | +}) | |
11 | +@Inject(ProfileService) | |
12 | +export class ProfileToolbarComponent { | |
2 |
|
33 | 33 | background-color: #7E7E7E; |
34 | 34 | } |
35 | 35 | } |
36 | + | |
37 | +.ats-switch { | |
1 |
|