Commit 16cf61d455479106a0e91771e6af44f6d262a4e4

Authored by Leandro Santos
2 parents fe36bbdd 4e13e9c2
Exists in staging

Merge branch 'master' into staging

src/app/layout/blocks/profile-image/profile-image-block.component.ts
1 import { Inject, Input, Component } from "ng-forward"; 1 import { Inject, Input, Component } from "ng-forward";
2 -import { ProfileImageComponent } from "./../../../profile/image/image.component"; 2 +import { ProfileImageComponent } from "./../../../profile/image/profile-image.component";
3 import { ProfileService } from "../../../../lib/ng-noosfero-api/http/profile.service"; 3 import { ProfileService } from "../../../../lib/ng-noosfero-api/http/profile.service";
4 import { SessionService } from "./../../../login"; 4 import { SessionService } from "./../../../login";
5 import { NotificationService } from "../../../shared/services/notification.service"; 5 import { NotificationService } from "../../../shared/services/notification.service";
src/app/layout/blocks/profile-image/profile-image-block.html
1 <div class="center-block text-center profile-image-block"> 1 <div class="center-block text-center profile-image-block">
2 <a ui-sref="main.profile.info({profile: ctrl.owner.identifier})"> 2 <a ui-sref="main.profile.info({profile: ctrl.owner.identifier})">
3 <noosfero-profile-image [profile]="ctrl.owner" [editable]="true" [edit-class]="'profile-image-block-editable'"></noosfero-profile-image> 3 <noosfero-profile-image [profile]="ctrl.owner" [editable]="true" [edit-class]="'profile-image-block-editable'"></noosfero-profile-image>
4 - </a>  
5 - <a class="settings-link" target="_self" ui-sref="main.profile.settings({profile: ctrl.owner.identifier})">{{"blocks.profile_image.control_panel" | translate}}</a> 4 + </a>
6 <div class="actions" ng-show="ctrl.isMember!=null"> 5 <div class="actions" ng-show="ctrl.isMember!=null">
7 <div class="organization-actions" ng-if="ctrl.displayOrganizationActions()"> 6 <div class="organization-actions" ng-if="ctrl.displayOrganizationActions()">
8 <a ng-if="!ctrl.isMember" ng-click="ctrl.join()" class="btn btn-primary btn-sm join" href="#">{{"blocks.profile_image.join" | translate}}</a> 7 <a ng-if="!ctrl.isMember" ng-click="ctrl.join()" class="btn btn-primary btn-sm join" href="#">{{"blocks.profile_image.join" | translate}}</a>
src/app/layout/blocks/profile-image/profile-image-block.scss
1 .profile-image-block { 1 .profile-image-block {
2 - .settings-link {  
3 - display: block; 2 + .img-responsive {
  3 + display: initial;
4 } 4 }
5 - .upload-camera-container {  
6 - top: 77%;  
7 - left: 6%;  
8 - }  
9 -}  
10 -  
11 -.profile-image-block-editable {  
12 - top: 68%;  
13 - width: 284px;  
14 - font-weight: 700;  
15 - height: 43px;  
16 - padding-left: 30px;  
17 - padding-top: 13px;  
18 } 5 }
19 -  
20 -  
21 -  
22 -  
23 -  
src/app/main/main.component.spec.ts
@@ -61,6 +61,7 @@ describe(&quot;MainComponent&quot;, function () { @@ -61,6 +61,7 @@ describe(&quot;MainComponent&quot;, function () {
61 quickCreateComponent({ directives: [MainComponentParent], template: "<parent></parent>" }) 61 quickCreateComponent({ directives: [MainComponentParent], template: "<parent></parent>" })
62 .then((fixture) => { 62 .then((fixture) => {
63 fixture.debugElement.getLocal("$httpBackend").expectGET("/api/v1/environments/1/boxes").respond(200, {}); 63 fixture.debugElement.getLocal("$httpBackend").expectGET("/api/v1/environments/1/boxes").respond(200, {});
  64 + fixture.debugElement.getLocal("$httpBackend").expectGET("/api/v1/tasks?all_pending=true&per_page=5&status=1").respond(200, {});
64 localFixture = fixture; 65 localFixture = fixture;
65 // get the $state service to navigate between routes 66 // get the $state service to navigate between routes
66 $state = fixture.debugElement.getLocal("$state"); 67 $state = fixture.debugElement.getLocal("$state");
src/app/profile/image/image.component.spec.ts
@@ -1,75 +0,0 @@ @@ -1,75 +0,0 @@
1 -/**  
2 - * @ngdoc overview  
3 - * @name components.noosfero.profile-image.ProfileImageSpec  
4 - * @description  
5 - * This file contains the tests for the {@link components.noosfero.profile-image.ProfileImage} component.  
6 - */  
7 -import { ComponentTestHelper, createClass } from '../../../spec/component-test-helper';  
8 -import { TestComponentBuilder, ComponentFixture } from 'ng-forward/cjs/testing/test-component-builder';  
9 -import { Pipe, Input, provide, Component } from 'ng-forward';  
10 -import { PersonService } from "../../../lib/ng-noosfero-api/http/person.service";  
11 -  
12 -import * as helpers from "../../../spec/helpers";  
13 -  
14 -import { ProfileImageComponent } from "./image.component";  
15 -  
16 -const htmlTemplate: string = '<noosfero-profile-image [editable]="true" [edit-class]="editable-class" [profile]="ctrl.profile"></noosfero-profile-image>';  
17 -  
18 -describe("Components", () => {  
19 -  
20 - describe("Profile Image Component", () => {  
21 -  
22 - let helper: ComponentTestHelper<ProfileImageComponent>;  
23 -  
24 - beforeEach(angular.mock.module("templates"));  
25 -  
26 - beforeEach((done) => {  
27 - let scope = helpers.mocks.scopeWithEvents;  
28 - let personService = jasmine.createSpyObj("personService", ["upload"]);  
29 - let properties = { profile: { custom_footer: "footer" } };  
30 - let cls = createClass({  
31 - template: htmlTemplate,  
32 - directives: [ProfileImageComponent],  
33 - properties: properties,  
34 - providers: [  
35 - helpers.createProviderToValue("PersonService", personService),  
36 - helpers.createProviderToValue("$uibModal", helpers.mocks.$modal),  
37 - helpers.createProviderToValue("$scope", scope)  
38 - ]  
39 - });  
40 - helper = new ComponentTestHelper<ProfileImageComponent>(cls, done);  
41 - });  
42 -  
43 - it("set modal instance when select files modal", () => {  
44 - helper.component['$uibModal'].open = jasmine.createSpy("open");  
45 - helper.component.fileSelected("file", []);  
46 - expect(helper.component['$uibModal'].open).toHaveBeenCalled();  
47 - });  
48 -  
49 -  
50 - it("show community users image if profile is not Person", (done) => {  
51 -  
52 - let profile = <noosfero.Profile>{ id: 1, identifier: "myprofile", type: "Community" };  
53 - helper.component.profile = profile;  
54 - helper.component.ngOnInit();  
55 -  
56 - // Check the attribute  
57 - expect(helper.component.defaultIcon).toBe("fa-users", "The default icon should be community users");  
58 - // var elProfile = fixture.debugElement.componentViewChildren[0];  
59 - // expect(elProfile.query('div.profile-image-block').length).toEqual(1);  
60 - done();  
61 -  
62 - });  
63 -  
64 - it("show Person image if profile is Person", (done) => {  
65 -  
66 - let profile = <noosfero.Profile>{ id: 1, identifier: "myprofile", type: "Person" };  
67 - helper.component.profile = profile;  
68 - helper.component.ngOnInit();  
69 - // Check the attribute  
70 - expect(helper.component.defaultIcon).toEqual("fa-user", "The default icon should be person user");  
71 - done();  
72 - });  
73 -  
74 - });  
75 -});  
src/app/profile/image/image.component.ts
@@ -1,91 +0,0 @@ @@ -1,91 +0,0 @@
1 -import { Inject, Input, Component, provide } from "ng-forward";  
2 -import { PersonService } from "../../../lib/ng-noosfero-api/http/person.service";  
3 -import { ProfileImageEditorComponent } from "./profile-image-editor.component";  
4 -  
5 -/**  
6 - * @ngdoc controller  
7 - * @name components.noosfero.profile-image.ProfileImage  
8 - * @description The component responsible for rendering the profile image  
9 - * @exports ProfileImage  
10 - */  
11 -@Component({  
12 - selector: "noosfero-profile-image",  
13 - templateUrl: 'app/profile/image/image.html',  
14 - providers: [provide('personService', { useClass: PersonService })]  
15 -})  
16 -@Inject(PersonService, "$uibModal", "$scope")  
17 -export class ProfileImageComponent {  
18 -  
19 - /**  
20 - * @ngdoc property  
21 - * @name profile  
22 - * @propertyOf components.noosfero.profile-image.ProfileImage  
23 - * @description  
24 - * The Noosfero {@link models.Profile} holding the image.  
25 - */  
26 - @Input() profile: noosfero.Profile;  
27 - /**  
28 - * @ngdoc property  
29 - * @name defaultIcon  
30 - * @propertyOf components.noosfero.profile-image.ProfileImage  
31 - * @descritpion  
32 - * The default icon used by this profile  
33 - */  
34 - defaultIcon: string;  
35 -  
36 - @Input() editable: boolean;  
37 -  
38 - @Input() editClass: string;  
39 -  
40 - picFile: any;  
41 - croppedDataUrl: any;  
42 - modalInstance: any;  
43 -  
44 - constructor(private personService: PersonService, private $uibModal: ng.ui.bootstrap.IModalService, private $scope: ng.IScope) {  
45 - }  
46 -  
47 - fileSelected(file: any, errFiles: any) {  
48 - if (file) {  
49 - this.picFile = file;  
50 - this.modalInstance = this.$uibModal.open({  
51 - templateUrl: 'app/profile/image/profile-image-editor.html',  
52 - controller: ProfileImageEditorComponent,  
53 - controllerAs: 'ctrl',  
54 - scope: this.$scope,  
55 - bindToController: true,  
56 - backdrop: 'static',  
57 - resolve: {  
58 - picFile: this.picFile,  
59 - profile: this.profile,  
60 - personService: this.personService  
61 - }  
62 - });  
63 - }  
64 - }  
65 -  
66 - private _showCamera: boolean = false;  
67 -  
68 - showChange(show: boolean) {  
69 - this._showCamera = show;  
70 - }  
71 -  
72 - showCamera() {  
73 - return this._showCamera;  
74 - }  
75 -  
76 -  
77 - /**  
78 - * @ngdoc method  
79 - * @name ngOnInit  
80 - * @methodOf components.noosfero.profile-image.ProfileImage  
81 - * @description  
82 - * Initializes the icon names to their corresponding values depending on the profile type passed to the controller  
83 - */  
84 - ngOnInit() {  
85 - this.defaultIcon = 'fa-users';  
86 - if (this.profile && this.profile.type === 'Person') {  
87 - this.defaultIcon = 'fa-user';  
88 - }  
89 - }  
90 -  
91 -}  
src/app/profile/image/image.html
@@ -1,21 +0,0 @@ @@ -1,21 +0,0 @@
1 -<div id="profile-image-container" style="">  
2 - <div class="profile-image-wrap" title="{{ctrl.profile.name}}" ng-mouseenter="ctrl.showChange(true)" ng-mouseleave="ctrl.showChange(false)">  
3 - <img ng-if="ctrl.profile.image" ng-src="{{ctrl.profile.image.url}}" class="img-responsive profile-image">  
4 - <i ng-if="!ctrl.profile.image" class="fa {{ctrl.defaultIcon}} fa-5x profile-image"></i>  
5 - <div ng-if="ctrl.editable" class="upload-camera-container">  
6 - <i id="camera" class="fa fa-camera upload-camera" aria-hidden="true"></i>  
7 - </div>  
8 - <div ng-if="ctrl.editable" id="select-photo-container" name="select-photo-container" class="select-photo-container container" ng-class="ctrl.editClass">  
9 - <a id="upload-container" class="upload-container" href="#" rel="dialog" role="button">  
10 - <!-- The upload button hidden behind the camera -->  
11 - <div class="upload-button" ngf-select="ctrl.fileSelected($file)"  
12 - ngf-pattern="'image/*'" ngf-accept="'image/*'"  
13 - ngf-max-size="20MB" ngf-resize="{width: 100, height: 100}"  
14 - data-toggle="modal" data-target=".crop-dialog">  
15 - {{"profile.image.upload" | translate}}  
16 - </div>  
17 - </a>  
18 - </div>  
19 -  
20 - </div>  
21 -</div>  
src/app/profile/image/image.scss
@@ -1,95 +0,0 @@ @@ -1,95 +0,0 @@
1 -i.profile-image {  
2 - color: rgb(44, 62, 80);  
3 -  
4 - border-radius: 50%;  
5 - background-clip: padding-box;  
6 - margin-bottom: 15px;  
7 -}  
8 -  
9 -.profile-image-wrap {  
10 - display: inline;  
11 -}  
12 -  
13 -#profile-image-container {  
14 - display: inline;  
15 -}  
16 -  
17 -#profile-image-container:hover {  
18 - .select-photo-container {  
19 - z-index: 1;  
20 - }  
21 - .upload-camera-container {  
22 - transform: scale(.75);  
23 - }  
24 -}  
25 -  
26 -.upload-camera-container {  
27 - text-align: left;  
28 - position: absolute;  
29 - z-index: 5;  
30 -}  
31 -  
32 -.upload-camera {  
33 - color: white;  
34 - position: absolute;  
35 - transition: all .3s cubic-bezier(.175, .885, .32, 1.275);  
36 - opacity: 1;  
37 -}  
38 -  
39 -.select-photo-container {  
40 - position: absolute;  
41 - z-index: -1;  
42 - background: #000;  
43 - background: rgba(0, 0, 0, .6);  
44 - background: linear-gradient(transparent, rgba(0, 0, 0, .6) 70%, rgba(0, 0, 0, .6) 100%);  
45 - transition: top .13s ease-out;  
46 -}  
47 -  
48 -#upload-container {  
49 - position: relative;  
50 - text-decoration: none;  
51 -}  
52 -  
53 -.upload-container a:hover {  
54 - text-decoration: none;  
55 -}  
56 -  
57 -.upload-button {  
58 - -webkit-font-smoothing: antialiased;  
59 - color: #fff;  
60 -}  
61 -  
62 -.upload-container {  
63 - color:#fff;  
64 - display: block;  
65 - overflow: hidden;  
66 - position: relative;  
67 - text-align: left;  
68 - min-width: 89px;  
69 -}  
70 -  
71 -.cropArea {  
72 - background: #E4E4E4;  
73 - overflow: hidden;  
74 - width:300px;  
75 - height:150px;  
76 -}  
77 -  
78 -.crop-area {  
79 - display: none;  
80 -}  
81 -  
82 -form .progress {  
83 - line-height: 15px;  
84 -}  
85 -  
86 -.progress {  
87 - display: inline-block;  
88 - width: 100px;  
89 - border: 3px groove #CCC;  
90 -}  
91 -.progress div {  
92 - font-size: smaller;  
93 - background: orange;  
94 - width: 0;  
95 -}  
src/app/profile/image/index.ts
1 /* Module Index Entry - generated using the script npm run generate-index */ 1 /* Module Index Entry - generated using the script npm run generate-index */
2 -export * from "./image.component"; 2 +export * from "./profile-image.component";
src/app/profile/image/profile-image-editor.component.spec.ts
@@ -18,7 +18,7 @@ describe(&quot;Components&quot;, () =&gt; { @@ -18,7 +18,7 @@ describe(&quot;Components&quot;, () =&gt; {
18 let modalInstance = jasmine.createSpyObj("$uibModalInstance", ["close"]); 18 let modalInstance = jasmine.createSpyObj("$uibModalInstance", ["close"]);
19 let picFile = { type: "png" }; 19 let picFile = { type: "png" };
20 let $q: ng.IQService; 20 let $q: ng.IQService;
21 - let personServiceMock: any; 21 + let profileServiceMock: any;
22 let $rootScope: ng.IRootScopeService; 22 let $rootScope: ng.IRootScopeService;
23 23
24 beforeEach(inject((_$q_: ng.IQService, _$rootScope_: ng.IRootScopeService) => { 24 beforeEach(inject((_$q_: ng.IQService, _$rootScope_: ng.IRootScopeService) => {
@@ -26,7 +26,7 @@ describe(&quot;Components&quot;, () =&gt; { @@ -26,7 +26,7 @@ describe(&quot;Components&quot;, () =&gt; {
26 $rootScope = _$rootScope_; 26 $rootScope = _$rootScope_;
27 })); 27 }));
28 28
29 - let comp = new ProfileImageEditorComponent(picFile, this.profile, personServiceMock, modalInstance); 29 + let comp = new ProfileImageEditorComponent(picFile, this.profile, profileServiceMock, modalInstance);
30 30
31 it("get data", done => { 31 it("get data", done => {
32 32
@@ -46,10 +46,10 @@ describe(&quot;Components&quot;, () =&gt; { @@ -46,10 +46,10 @@ describe(&quot;Components&quot;, () =&gt; {
46 46
47 it("upload image", done => { 47 it("upload image", done => {
48 let imageName = "image1"; 48 let imageName = "image1";
49 - personServiceMock = jasmine.createSpyObj("personServiceMock", ["uploadImage"]); 49 + profileServiceMock = jasmine.createSpyObj("profileServiceMock", ["uploadImage"]);
50 let deferredUploadImage = $q.defer(); 50 let deferredUploadImage = $q.defer();
51 - personServiceMock.uploadImage = jasmine.createSpy('uploadImage').and.returnValue(deferredUploadImage.promise);  
52 - comp.personService = personServiceMock; 51 + profileServiceMock.uploadImage = jasmine.createSpy('uploadImage').and.returnValue(deferredUploadImage.promise);
  52 + comp.profileService = profileServiceMock;
53 comp.uploadImage(testDataUrl, imageName); 53 comp.uploadImage(testDataUrl, imageName);
54 deferredUploadImage.resolve(); 54 deferredUploadImage.resolve();
55 $rootScope.$apply(); 55 $rootScope.$apply();
src/app/profile/image/profile-image-editor.component.ts
1 import { StateConfig, Component, Input, Output, Inject, provide } from 'ng-forward'; 1 import { StateConfig, Component, Input, Output, Inject, provide } from 'ng-forward';
2 import { TranslateProfile } from "../../shared/pipes/translate-profile.filter"; 2 import { TranslateProfile } from "../../shared/pipes/translate-profile.filter";
3 -import { PersonService } from "../../../lib/ng-noosfero-api/http/person.service"; 3 +import { ProfileService } from "../../../lib/ng-noosfero-api/http/profile.service";
4 4
5 export class ProfileImageEditorComponent { 5 export class ProfileImageEditorComponent {
6 6
7 - activities: any;  
8 croppedDataUrl: string; 7 croppedDataUrl: string;
9 - static $inject = ["picFile", "profile", "personService", "$uibModalInstance"]; 8 + static $inject = ["picFile", "profile", "profileService", "$uibModalInstance"];
10 9
11 - constructor(public picFile: any, public profile: noosfero.Profile, public personService: PersonService, 10 + constructor(public picFile: any, public profile: noosfero.Profile, public profileService: ProfileService,
12 public modalInstance: ng.ui.bootstrap.IModalServiceInstance) { 11 public modalInstance: ng.ui.bootstrap.IModalServiceInstance) {
13 } 12 }
14 13
15 uploadImage(dataUrl: any, name: any) { 14 uploadImage(dataUrl: any, name: any) {
16 let base64ImageJson = this.getBase64ImageJson(dataUrl, name); 15 let base64ImageJson = this.getBase64ImageJson(dataUrl, name);
17 - this.personService.uploadImage(this.profile, base64ImageJson).then((result: any) => { 16 + this.profileService.uploadImage(this.profile, base64ImageJson).then((result: any) => {
18 this.modalInstance.close(name); 17 this.modalInstance.close(name);
19 }); 18 });
20 } 19 }
src/app/profile/image/profile-image-editor.html
@@ -3,18 +3,14 @@ @@ -3,18 +3,14 @@
3 </div> 3 </div>
4 <div class="modal-body"> 4 <div class="modal-body">
5 <form class=""> 5 <form class="">
6 - <div ngf-drop ng-model="ctrl.picFile" ngf-pattern="image/*" class="cropArea"> 6 + <div ngf-drop ng-model="ctrl.picFile" ngf-pattern="image/*" class="crop-area">
7 <img-crop image="ctrl.picFile | ngfDataUrl" area-type="square" 7 <img-crop image="ctrl.picFile | ngfDataUrl" area-type="square"
8 result-image="ctrl.croppedDataUrl" ng-init="ctrl.croppedDataUrl=''"> 8 result-image="ctrl.croppedDataUrl" ng-init="ctrl.croppedDataUrl=''">
9 </img-crop> 9 </img-crop>
10 </div> 10 </div>
11 - <div> 11 + <div class='cropped-image'>
12 <img ng-src="{{ctrl.croppedDataUrl}}" /> 12 <img ng-src="{{ctrl.croppedDataUrl}}" />
13 - </div>  
14 - <span class="progress" ng-show="progress >= 0">  
15 - <div style="width: {{progress" ng-bind="progress + '%'"></div>  
16 - </span> <span ng-show="ctrl.result">Upload Successful</span> <span class="err"  
17 - ng-show="ctrl.errorMsg">{{errorMsg}}</span> 13 + </div>
18 </form> 14 </form>
19 15
20 <div class="actions"> 16 <div class="actions">
src/app/profile/image/profile-image-editor.scss 0 → 100644
@@ -0,0 +1,13 @@ @@ -0,0 +1,13 @@
  1 +.crop-area{
  2 + background: #E4E4E4;
  3 + height: 200px;
  4 +}
  5 +.cropped-image {
  6 + margin-top: 10px;
  7 + text-align: center;
  8 +}
  9 +
  10 +.modal-body .actions{
  11 + text-align: center;
  12 +
  13 +}
src/app/profile/image/profile-image.component.spec.ts 0 → 100644
@@ -0,0 +1,87 @@ @@ -0,0 +1,87 @@
  1 +/**
  2 + * @ngdoc overview
  3 + * @name components.noosfero.profile-image.ProfileImageSpec
  4 + * @description
  5 + * This file contains the tests for the {@link components.noosfero.profile-image.ProfileImage} component.
  6 + */
  7 +import { ComponentTestHelper, createClass } from '../../../spec/component-test-helper';
  8 +import { TestComponentBuilder, ComponentFixture } from 'ng-forward/cjs/testing/test-component-builder';
  9 +import { Pipe, Input, provide, Component } from 'ng-forward';
  10 +import { ProfileService } from "../../../lib/ng-noosfero-api/http/profile.service";
  11 +
  12 +import * as helpers from "../../../spec/helpers";
  13 +
  14 +import { ProfileImageComponent } from "./profile-image.component";
  15 +
  16 +const htmlTemplate: string = '<noosfero-profile-image [editable]="true" [edit-class]="editable-class" [profile]="ctrl.profile"></noosfero-profile-image>';
  17 +
  18 +describe("Components", () => {
  19 +
  20 + describe("Profile Image Component", () => {
  21 +
  22 + let helper: ComponentTestHelper<ProfileImageComponent>;
  23 +
  24 + beforeEach(angular.mock.module("templates"));
  25 +
  26 + beforeEach((done) => {
  27 + let scope = helpers.mocks.scopeWithEvents;
  28 + let profileService = jasmine.createSpyObj("profileService", ["upload"]);
  29 + let permissionService = jasmine.createSpyObj("permissionService", ["isAllowed"]);
  30 + let properties = { profile: { custom_footer: "footer" } };
  31 + let cls = createClass({
  32 + template: htmlTemplate,
  33 + directives: [ProfileImageComponent],
  34 + properties: properties,
  35 + providers: [
  36 + helpers.createProviderToValue("ProfileService", profileService),
  37 + helpers.createProviderToValue("PermissionService", permissionService),
  38 + helpers.createProviderToValue("$uibModal", helpers.mocks.$modal),
  39 + helpers.createProviderToValue("$scope", scope)
  40 + ]
  41 + });
  42 + helper = new ComponentTestHelper<ProfileImageComponent>(cls, done);
  43 + });
  44 +
  45 + it("set modal instance when select files modal", () => {
  46 + helper.component['$uibModal'].open = jasmine.createSpy("open");
  47 + helper.component.fileSelected("file", []);
  48 + expect(helper.component['$uibModal'].open).toHaveBeenCalled();
  49 + });
  50 +
  51 +
  52 + it("show community users image if profile is not Person", (done) => {
  53 + let profile = <noosfero.Profile>{ id: 1, identifier: "myprofile", type: "Community" };
  54 + helper.component.profile = profile;
  55 + helper.component.ngOnInit();
  56 + expect(helper.component.defaultIcon).toBe("fa-users", "The default icon should be community users");
  57 + done();
  58 +
  59 + });
  60 +
  61 + it("show Person image if profile is Person", (done) => {
  62 + let profile = <noosfero.Profile>{ id: 1, identifier: "myprofile", type: "Person" };
  63 + helper.component.profile = profile;
  64 + helper.component.ngOnInit();
  65 + expect(helper.component.defaultIcon).toEqual("fa-user", "The default icon should be person user");
  66 + done();
  67 + });
  68 +
  69 + it("is editable be true in blocks that are editable", (done) => {
  70 + expect(helper.component.editable).toBe(true);
  71 + done();
  72 + });
  73 +
  74 + it("is not editable in editable blocks but without permission", (done) => {
  75 + helper.component['permissionService'].isAllowed = jasmine.createSpy("isAllowed").and.returnValue(false);
  76 + expect(helper.component.isEditable()).toBe(false);
  77 + done();
  78 + });
  79 +
  80 + it("is editable in editable blocks with edit permission", (done) => {
  81 + helper.component['permissionService'].isAllowed = jasmine.createSpy("isAllowed").and.returnValue(true);
  82 + expect(helper.component.isEditable()).toBe(true);
  83 + done();
  84 + });
  85 +
  86 + });
  87 +});
src/app/profile/image/profile-image.component.ts 0 → 100644
@@ -0,0 +1,82 @@ @@ -0,0 +1,82 @@
  1 +import { Inject, Input, Component, provide } from "ng-forward";
  2 +import { ProfileService } from "../../../lib/ng-noosfero-api/http/profile.service";
  3 +import { PermissionService } from "../../shared/services/permission.service";
  4 +import { ProfileImageEditorComponent } from "./profile-image-editor.component";
  5 +
  6 +/**
  7 + * @ngdoc controller
  8 + * @name components.noosfero.profile-image.ProfileImage
  9 + * @description The component responsible for rendering the profile image
  10 + * @exports ProfileImage
  11 + */
  12 +@Component({
  13 + selector: "noosfero-profile-image",
  14 + templateUrl: 'app/profile/image/profile-image.html',
  15 + providers: [provide('profileService', { useClass: ProfileService })]
  16 +})
  17 +@Inject(ProfileService, PermissionService, "$uibModal", "$scope")
  18 +export class ProfileImageComponent {
  19 +
  20 + /**
  21 + * @ngdoc property
  22 + * @name profile
  23 + * @propertyOf components.noosfero.profile-image.ProfileImage
  24 + * @description
  25 + * The Noosfero {@link models.Profile} holding the image.
  26 + */
  27 + @Input() profile: noosfero.Profile;
  28 + /**
  29 + * @ngdoc property
  30 + * @name defaultIcon
  31 + * @propertyOf components.noosfero.profile-image.ProfileImage
  32 + * @descritpion
  33 + * The default icon used by this profile
  34 + */
  35 + defaultIcon: string;
  36 +
  37 + @Input() editable: boolean;
  38 +
  39 + picFile: any;
  40 + modalInstance: any;
  41 +
  42 + constructor(private profileService: ProfileService, private permissionService: PermissionService, private $uibModal: ng.ui.bootstrap.IModalService, private $scope: ng.IScope) {
  43 + }
  44 +
  45 + fileSelected(file: any, errFiles: any) {
  46 + if (file) {
  47 + this.picFile = file;
  48 + this.modalInstance = this.$uibModal.open({
  49 + templateUrl: 'app/profile/image/profile-image-editor.html',
  50 + controller: ProfileImageEditorComponent,
  51 + controllerAs: 'ctrl',
  52 + scope: this.$scope,
  53 + bindToController: true,
  54 + backdrop: 'static',
  55 + resolve: {
  56 + picFile: this.picFile,
  57 + profile: this.profile,
  58 + profileService: this.profileService
  59 + }
  60 + });
  61 + }
  62 + }
  63 +
  64 + isEditable() {
  65 + return this.editable && this.permissionService.isAllowed(this.profile, 'allow_edit');
  66 + }
  67 +
  68 + /**
  69 + * @ngdoc method
  70 + * @name ngOnInit
  71 + * @methodOf components.noosfero.profile-image.ProfileImage
  72 + * @description
  73 + * Initializes the icon names to their corresponding values depending on the profile type passed to the controller
  74 + */
  75 + ngOnInit() {
  76 + this.defaultIcon = 'fa-users';
  77 + if (this.profile && this.profile.type === 'Person') {
  78 + this.defaultIcon = 'fa-user';
  79 + }
  80 + }
  81 +
  82 +}
src/app/profile/image/profile-image.html 0 → 100644
@@ -0,0 +1,20 @@ @@ -0,0 +1,20 @@
  1 +<div ng-if="ctrl.isEditable()" id="profile-image-container">
  2 + <div class="profile-image-wrap" title="{{ctrl.profile.name}}" ng-mouseenter="ctrl.showChange(true)" ng-mouseleave="ctrl.showChange(false)">
  3 + <div class="hovereffect ">
  4 + <i ng-if="!ctrl.profile.image" class="fa {{ctrl.defaultIcon}} fa-5x profile-image"></i>
  5 + <img ng-if="ctrl.profile.image" ng-src="{{ctrl.profile.image.url}}" class="img-responsive profile-image">
  6 + <div class='container-camera'>
  7 + <a ngf-select="ctrl.fileSelected($file)"
  8 + ngf-pattern="'image/*'" ngf-accept="'image/*'"
  9 + ngf-max-size="20MB"
  10 + data-toggle="modal" data-target=".crop-dialog" href="#"><i class="fa fa-camera upload-camera" ></i>
  11 + </a>
  12 + </div>
  13 + <div class="overlay"></div>
  14 + </div>
  15 + </div>
  16 +</div>
  17 +<span ng-if="!ctrl.isEditable()" class="profile-image-wrap" title="{{ctrl.profile.name}}">
  18 + <img ng-if="ctrl.profile.image" ng-src="{{ctrl.profile.image.url}}" class="img-responsive profile-image">
  19 + <i ng-if="!ctrl.profile.image" class="fa {{ctrl.defaultIcon}} fa-5x profile-image"></i>
  20 +</span>
src/app/profile/image/profile-image.scss 0 → 100644
@@ -0,0 +1,59 @@ @@ -0,0 +1,59 @@
  1 +.hovereffect {
  2 + width:100%;
  3 + height:100%;
  4 + float:left;
  5 + overflow:hidden;
  6 + position:relative;
  7 + text-align:center;
  8 + cursor:default;
  9 + .container-camera {
  10 + position: relative;
  11 + z-index: 2;
  12 + .upload-camera{
  13 + right: 0px;
  14 + left: 0px;
  15 + position: absolute;
  16 + font-size: 1.5em;
  17 + bottom: 2px;
  18 + text-shadow: 0px 0px 3px black;
  19 + color: white;
  20 + padding-top: 5px;
  21 + &:hover{
  22 + font-size: 1.5em;
  23 + background-color: rgba(0, 0, 0, 0.50);
  24 + }
  25 +
  26 + }
  27 + }
  28 + .overlay {
  29 + width:100%;
  30 + height:100%;
  31 + position:absolute;
  32 + overflow:hidden;
  33 + top:0;
  34 + left:0;
  35 + opacity:0;
  36 + background-color:rgba(0,0,0,0.5);
  37 + -webkit-transition:all .4s ease-in-out;
  38 + transition:all .4s ease-in-out;
  39 + z-index: 1;
  40 + }
  41 + img {
  42 + display:block;
  43 + position:relative;
  44 + -webkit-transition:all .4s linear;
  45 + transition:all .4s linear;
  46 + width: 100%;
  47 + }
  48 + &:hover {
  49 + img {
  50 + -ms-transform:scale(1.2);
  51 + -webkit-transform:scale(1.2);
  52 + transform:scale(1.2);
  53 + }
  54 + .overlay {
  55 + opacity:1;
  56 + filter:alpha(opacity=100);
  57 + }
  58 + }
  59 +}
src/app/shared/services/permission.service.spec.ts 0 → 100644
@@ -0,0 +1,137 @@ @@ -0,0 +1,137 @@
  1 +import { PermissionService } from './permission.service';
  2 +
  3 +
  4 +describe("PermissionService", () => {
  5 +
  6 + // Profile TESTS
  7 + it("check if a person has no permission if there is no permissions in object", (done) => {
  8 + let permissionService: PermissionService = new PermissionService();
  9 + let target = <noosfero.Profile>{ id: 1, identifier: "some" };
  10 + expect(permissionService.isAllowed(target, null)).toBe(false);
  11 + done();
  12 + });
  13 +
  14 + it("check if a person has no permission if the permissions is null", (done) => {
  15 + let permissionService: PermissionService = new PermissionService();
  16 + let target = <noosfero.Profile>{ id: 1, identifier: "some", permissions: null };
  17 + expect(permissionService.isAllowed(target, null)).toBe(false);
  18 + done();
  19 + });
  20 +
  21 + it("check if a person has no permission if the permissions is empty", (done) => {
  22 + let permissionService: PermissionService = new PermissionService();
  23 + let target = <noosfero.Profile>{ id: 1, identifier: "some", permissions: [] };
  24 + expect(permissionService.isAllowed(target, null)).toBe(false);
  25 + done();
  26 + });
  27 +
  28 + it("check person has no permission if permissions has no element named like permission desired", (done) => {
  29 + let permissionService: PermissionService = new PermissionService();
  30 + let target = <noosfero.Profile>{ id: 1, identifier: "some", permissions: ['another_permission'] };
  31 + expect(permissionService.isAllowed(target, 'some_permission')).toBe(false);
  32 + done();
  33 + });
  34 +
  35 + it("check person has permission if permissions has the element named like permission desired", (done) => {
  36 + let permissionService: PermissionService = new PermissionService();
  37 + let target = <noosfero.Profile>{ id: 1, identifier: "some", permissions: ['some_permission'] };
  38 + expect(permissionService.isAllowed(target, 'some_permission')).toBe(true);
  39 + done();
  40 + });
  41 +
  42 + it("check person has permission if permissions has some element named like permission desired", (done) => {
  43 + let permissionService: PermissionService = new PermissionService();
  44 + let target = <noosfero.Profile>{ id: 1, identifier: "some", permissions: ['another', 'some_permission'] };
  45 + expect(permissionService.isAllowed(target, 'some_permission')).toBe(true);
  46 + done();
  47 + });
  48 +
  49 +
  50 + // Comment TESTS
  51 + it("check if a person has no permission if there is no permissions in object", (done) => {
  52 + let permissionService: PermissionService = new PermissionService();
  53 + let target = <noosfero.Comment>{ id: 1 };
  54 + expect(permissionService.isAllowed(target, null)).toBe(false);
  55 + done();
  56 + });
  57 +
  58 + it("check if a person has no permission if the permissions is null", (done) => {
  59 + let permissionService: PermissionService = new PermissionService();
  60 + let target = <noosfero.Comment>{ id: 1, permissions: null };
  61 + expect(permissionService.isAllowed(target, null)).toBe(false);
  62 + done();
  63 + });
  64 +
  65 + it("check if a person has no permission if the permissions is empty", (done) => {
  66 + let permissionService: PermissionService = new PermissionService();
  67 + let target = <noosfero.Comment>{ id: 1, permissions: [] };
  68 + expect(permissionService.isAllowed(target, null)).toBe(false);
  69 + done();
  70 + });
  71 +
  72 + it("check person has no permission if permissions has no element named like permission desired", (done) => {
  73 + let permissionService: PermissionService = new PermissionService();
  74 + let target = <noosfero.Comment>{ id: 1, permissions: ['another_permission'] };
  75 + expect(permissionService.isAllowed(target, 'some_permission')).toBe(false);
  76 + done();
  77 + });
  78 +
  79 + it("check person has permission if permissions has the element named like permission desired", (done) => {
  80 + let permissionService: PermissionService = new PermissionService();
  81 + let target = <noosfero.Comment>{ id: 1, permissions: ['some_permission'] };
  82 + expect(permissionService.isAllowed(target, 'some_permission')).toBe(true);
  83 + done();
  84 + });
  85 +
  86 + it("check person has permission if permissions has some element named like permission desired", (done) => {
  87 + let permissionService: PermissionService = new PermissionService();
  88 + let target = <noosfero.Comment>{ id: 1, permissions: ['another', 'some_permission'] };
  89 + expect(permissionService.isAllowed(target, 'some_permission')).toBe(true);
  90 + done();
  91 + });
  92 +
  93 + // Article TESTS
  94 + it("check if a person has no permission if there is no permissions in object", (done) => {
  95 + let permissionService: PermissionService = new PermissionService();
  96 + let target = <noosfero.Article>{ id: 1 };
  97 + expect(permissionService.isAllowed(target, null)).toBe(false);
  98 + done();
  99 + });
  100 +
  101 + it("check if a person has no permission if the permissions is null", (done) => {
  102 + let permissionService: PermissionService = new PermissionService();
  103 + let target = <noosfero.Article>{ id: 1, permissions: null };
  104 + expect(permissionService.isAllowed(target, null)).toBe(false);
  105 + done();
  106 + });
  107 +
  108 + it("check if a person has no permission if the permissions is empty", (done) => {
  109 + let permissionService: PermissionService = new PermissionService();
  110 + let target = <noosfero.Article>{ id: 1, permissions: [] };
  111 + expect(permissionService.isAllowed(target, null)).toBe(false);
  112 + done();
  113 + });
  114 +
  115 + it("check person has no permission if permissions has no element named like permission desired", (done) => {
  116 + let permissionService: PermissionService = new PermissionService();
  117 + let target = <noosfero.Article>{ id: 1, permissions: ['another_permission'] };
  118 + expect(permissionService.isAllowed(target, 'some_permission')).toBe(false);
  119 + done();
  120 + });
  121 +
  122 + it("check person has permission if permissions has the element named like permission desired", (done) => {
  123 + let permissionService: PermissionService = new PermissionService();
  124 + let target = <noosfero.Article>{ id: 1, permissions: ['some_permission'] };
  125 + expect(permissionService.isAllowed(target, 'some_permission')).toBe(true);
  126 + done();
  127 + });
  128 +
  129 + it("check person has permission if permissions has some element named like permission desired", (done) => {
  130 + let permissionService: PermissionService = new PermissionService();
  131 + let target = <noosfero.Article>{ id: 1, permissions: ['another', 'some_permission'] };
  132 + expect(permissionService.isAllowed(target, 'some_permission')).toBe(true);
  133 + done();
  134 + });
  135 +
  136 +
  137 +});
src/app/shared/services/permission.service.ts 0 → 100644
@@ -0,0 +1,11 @@ @@ -0,0 +1,11 @@
  1 +import {Injectable, Inject} from "ng-forward";
  2 +
  3 +type WithPermissions = noosfero.Profile | noosfero.Comment | noosfero.Article;
  4 +
  5 +@Injectable()
  6 +export class PermissionService {
  7 + isAllowed(target: WithPermissions, permission: string) {
  8 + return (target.permissions || []).indexOf(permission) >= 0;
  9 + }
  10 +
  11 +}
src/lib/ng-noosfero-api/http/article.service.ts
@@ -58,7 +58,7 @@ export class ArticleService extends RestangularService&lt;noosfero.Article&gt; { @@ -58,7 +58,7 @@ export class ArticleService extends RestangularService&lt;noosfero.Article&gt; {
58 } 58 }
59 59
60 createInProfile(profile: noosfero.Profile, article: noosfero.Article): ng.IPromise<noosfero.RestResult<noosfero.Article>> { 60 createInProfile(profile: noosfero.Profile, article: noosfero.Article): ng.IPromise<noosfero.RestResult<noosfero.Article>> {
61 - let profileElement = this.profileService.get(<number>profile.id); 61 + let profileElement = this.profileService.getProfileElement(<number>profile.id);
62 (<any>profileElement).id = profile.id; 62 (<any>profileElement).id = profile.id;
63 let headers = { 63 let headers = {
64 'Content-Type': 'application/json' 64 'Content-Type': 'application/json'
@@ -84,13 +84,13 @@ export class ArticleService extends RestangularService&lt;noosfero.Article&gt; { @@ -84,13 +84,13 @@ export class ArticleService extends RestangularService&lt;noosfero.Article&gt; {
84 } 84 }
85 85
86 getByProfile<T>(profile: noosfero.Profile, params?: any): ng.IPromise<noosfero.RestResult<noosfero.Article[]>> { 86 getByProfile<T>(profile: noosfero.Profile, params?: any): ng.IPromise<noosfero.RestResult<noosfero.Article[]>> {
87 - let profileElement = this.profileService.get(<number>profile.id); 87 + let profileElement = this.profileService.getProfileElement(<number>profile.id);
88 return this.list(profileElement, params); 88 return this.list(profileElement, params);
89 } 89 }
90 90
91 getArticleByProfileAndPath(profile: noosfero.Profile, path: string): ng.IPromise<noosfero.RestResult<noosfero.Article>> { 91 getArticleByProfileAndPath(profile: noosfero.Profile, path: string): ng.IPromise<noosfero.RestResult<noosfero.Article>> {
92 let deferred = this.$q.defer<noosfero.RestResult<noosfero.Article>>(); 92 let deferred = this.$q.defer<noosfero.RestResult<noosfero.Article>>();
93 - let profileElement = this.profileService.get(<number>profile.id); 93 + let profileElement = this.profileService.getProfileElement(<number>profile.id);
94 94
95 let restRequest: ng.IPromise<any>; 95 let restRequest: ng.IPromise<any>;
96 96
@@ -108,7 +108,7 @@ export class ArticleService extends RestangularService&lt;noosfero.Article&gt; { @@ -108,7 +108,7 @@ export class ArticleService extends RestangularService&lt;noosfero.Article&gt; {
108 } 108 }
109 109
110 getOneByProfile<T>(profile: noosfero.Profile, params?: any): ng.IPromise<noosfero.RestResult<noosfero.Article>> { 110 getOneByProfile<T>(profile: noosfero.Profile, params?: any): ng.IPromise<noosfero.RestResult<noosfero.Article>> {
111 - let profileElement = this.profileService.get(<number>profile.id); 111 + let profileElement = this.profileService.getProfileElement(<number>profile.id);
112 return this.getSub(profileElement, params); 112 return this.getSub(profileElement, params);
113 } 113 }
114 114
src/lib/ng-noosfero-api/http/profile.service.ts
1 import { Injectable, Inject } from "ng-forward"; 1 import { Injectable, Inject } from "ng-forward";
  2 +import { RestangularService } from "./restangular_service";
  3 +
2 4
3 @Injectable() 5 @Injectable()
4 @Inject("Restangular", "$q") 6 @Inject("Restangular", "$q")
5 -export class ProfileService { 7 +export class ProfileService extends RestangularService<noosfero.Profile> {
6 8
7 private _currentProfilePromise: ng.IDeferred<noosfero.Profile>; 9 private _currentProfilePromise: ng.IDeferred<noosfero.Profile>;
8 10
9 - constructor(private restangular: restangular.IService, private $q: ng.IQService) { 11 + constructor(private restangular: restangular.IService, $q: ng.IQService, $log: ng.ILogService) {
  12 + super(restangular, $q, $log);
10 this.resetCurrentProfile(); 13 this.resetCurrentProfile();
11 } 14 }
12 15
  16 + getResourcePath() {
  17 + return "profiles";
  18 + }
  19 +
  20 + getDataKeys() {
  21 + return {
  22 + singular: 'profile',
  23 + plural: 'profiles'
  24 + };
  25 + }
  26 +
13 resetCurrentProfile() { 27 resetCurrentProfile() {
14 this._currentProfilePromise = this.$q.defer(); 28 this._currentProfilePromise = this.$q.defer();
15 } 29 }
@@ -31,7 +45,7 @@ export class ProfileService { @@ -31,7 +45,7 @@ export class ProfileService {
31 } 45 }
32 46
33 getHomePage(profileId: number, params?: any) { 47 getHomePage(profileId: number, params?: any) {
34 - return this.get(profileId).customGET("home_page", params); 48 + return this.getProfileElement(profileId).customGET("home_page", params);
35 } 49 }
36 50
37 getByIdentifier(identifier: string): ng.IPromise<noosfero.Profile> { 51 getByIdentifier(identifier: string): ng.IPromise<noosfero.Profile> {
@@ -45,28 +59,28 @@ export class ProfileService { @@ -45,28 +59,28 @@ export class ProfileService {
45 } 59 }
46 60
47 getProfileMembers(profileId: number, params?: any): restangular.IPromise<any> { 61 getProfileMembers(profileId: number, params?: any): restangular.IPromise<any> {
48 - return this.get(profileId).customGET("members", params); 62 + return this.getProfileElement(profileId).customGET("members", params);
49 } 63 }
50 64
51 getBoxes(profileId: number): restangular.IPromise<any> { 65 getBoxes(profileId: number): restangular.IPromise<any> {
52 - return this.get(profileId).customGET('boxes'); 66 + return this.getProfileElement(profileId).customGET('boxes');
53 } 67 }
54 68
55 getActivities(profileId: number, params?: any): restangular.IPromise<any> { 69 getActivities(profileId: number, params?: any): restangular.IPromise<any> {
56 - return this.get(profileId).customGET("activities", params); 70 + return this.getProfileElement(profileId).customGET("activities", params);
57 } 71 }
58 72
59 - get(profileId: number): restangular.IElement { 73 + getProfileElement(profileId: number): restangular.IElement {
60 return this.restangular.one('profiles', profileId); 74 return this.restangular.one('profiles', profileId);
61 } 75 }
62 76
63 update(profile: noosfero.Profile) { 77 update(profile: noosfero.Profile) {
64 let headers = { 'Content-Type': 'application/json' }; 78 let headers = { 'Content-Type': 'application/json' };
65 - return this.get(profile.id).customPOST({ profile: profile }, null, null, headers); 79 + return this.getProfileElement(profile.id).customPOST({ profile: profile }, null, null, headers);
66 } 80 }
67 81
68 getMembers(profile: noosfero.Profile, params?: any) { 82 getMembers(profile: noosfero.Profile, params?: any) {
69 - let p = this.get(profile.id); 83 + let p = this.getProfileElement(profile.id);
70 return p.customGET('members', params); 84 return p.customGET('members', params);
71 } 85 }
72 86
@@ -83,10 +97,24 @@ export class ProfileService { @@ -83,10 +97,24 @@ export class ProfileService {
83 } 97 }
84 98
85 addMember(person: noosfero.Person, profile: noosfero.Profile) { 99 addMember(person: noosfero.Person, profile: noosfero.Profile) {
86 - return this.get(profile.id).customPOST({}, "members", null, null); 100 + return this.getProfileElement(profile.id).customPOST({}, "members", null, null);
87 } 101 }
88 102
89 removeMember(person: noosfero.Person, profile: noosfero.Profile) { 103 removeMember(person: noosfero.Person, profile: noosfero.Profile) {
90 - return this.get(profile.id).customDELETE("members", null, null); 104 + return this.getProfileElement(profile.id).customDELETE("members", null, null);
  105 + }
  106 +
  107 + uploadImage(profile: noosfero.Profile, base64ImageJson: any) {
  108 + let headers = { 'Content-Type': 'application/json' };
  109 + let deferred = this.$q.defer<noosfero.RestResult<noosfero.Profile>>();
  110 + // TODO dynamically copy the selected attributes to update
  111 + let attributesToUpdate: any = {
  112 + profile: { image_builder: base64ImageJson }
  113 + };
  114 + let restRequest: ng.IPromise<noosfero.RestResult<any>> =
  115 + this.getProfileElement(profile.id).customPOST(attributesToUpdate, null, null, headers);
  116 + restRequest.then(this.getHandleSuccessFunction(deferred))
  117 + .catch(this.getHandleErrorFunction(deferred));
  118 + return deferred.promise;
91 } 119 }
92 } 120 }
src/lib/ng-noosfero-api/interfaces/article.ts
@@ -8,6 +8,8 @@ namespace noosfero { @@ -8,6 +8,8 @@ namespace noosfero {
8 parent: Article; 8 parent: Article;
9 body: string; 9 body: string;
10 title: string; 10 title: string;
  11 +
  12 +
11 name: string; 13 name: string;
12 published: boolean; 14 published: boolean;
13 setting: any; 15 setting: any;
src/lib/ng-noosfero-api/interfaces/comment.ts
@@ -5,6 +5,7 @@ namespace noosfero { @@ -5,6 +5,7 @@ namespace noosfero {
5 reply_of: Comment; 5 reply_of: Comment;
6 replies: Comment[]; 6 replies: Comment[];
7 body: string; 7 body: string;
  8 + permissions: string[];
8 } 9 }
9 10
10 export interface CommentViewModel extends Comment { 11 export interface CommentViewModel extends Comment {