Merge Request #58

Merged
noosfero-themes/angular-theme!58
Created by Carlos Purificação

Upload profile photo

Assignee: Michel Felipe
Milestone: 2016.07

Merged by Michel Felipe

Source branch has been removed
Commits (4)
2 participants
bower.json
... ... @@ -40,12 +40,19 @@
40 40 "ng-ckeditor": "^0.2.1",
41 41 "angular-tag-cloud": "^0.3.0",
42 42 "angular-ui-switch": "^0.1.1",
43   - "angular-password": "^1.0.1"
  43 + "angular-password": "^1.0.1",
  44 + "ng-file-upload": "^12.0.4",
  45 + "ng-img-crop": "^0.3.2"
44 46 },
45 47 "devDependencies": {
46 48 "angular-mocks": "~1.5.0"
47 49 },
48 50 "overrides": {
  51 + "ng-file-upload": {
  52 + "main": [
  53 + "ng-file-upload-all.js"
  54 + ]
  55 + },
49 56 "ng-ckeditor": {
50 57 "main": [
51 58 "ng-ckeditor.js",
... ...
gulp/watch.js
... ... @@ -46,6 +46,7 @@ gulp.task('watch', ['inject'], function () {
46 46 watchPaths.push(path.join(src, '/app/**/*.html'));
47 47 watchPaths.push(path.join(src, conf.paths.plugins, '/**/*.html'));
48 48 });
  49 + watchPaths.push(stylePaths);
49 50 gulp.watch(watchPaths, function(event) {
50 51 browserSync.reload(event.path);
51 52 });
... ...
src/app/layout/blocks/profile-image/profile-image-block.component.spec.ts
1   -import {TestComponentBuilder, ComponentFixture} from 'ng-forward/cjs/testing/test-component-builder';
2   -import {Pipe, Input, provide, Component} from 'ng-forward';
  1 +import { TestComponentBuilder, ComponentFixture } from 'ng-forward/cjs/testing/test-component-builder';
  2 +import { Pipe, Input, provide, Component } from 'ng-forward';
3 3  
4   -import {ProfileImageBlockComponent} from './profile-image-block.component';
  4 +import { ProfileImageBlockComponent } from './profile-image-block.component';
5 5  
6 6 import * as helpers from "./../../../../spec/helpers";
7 7  
... ... @@ -14,6 +14,7 @@ describe("Components", () => {
14 14 describe("Profile Image Block Component", () => {
15 15  
16 16 beforeEach(angular.mock.module("templates"));
  17 + let personService = jasmine.createSpyObj("personService", ["upload"]);
17 18  
18 19 let profileService = jasmine.createSpyObj("ProfileService", ["isMember", "addMember", "removeMember"]);
19 20 profileService.isMember = jasmine.createSpy("isMember").and.returnValue(Promise.resolve(false));
... ... @@ -24,6 +25,8 @@ describe("Components", () => {
24 25 directives: [ProfileImageBlockComponent],
25 26 providers: [
26 27 helpers.createProviderToValue('SessionService', helpers.mocks.sessionWithCurrentUser({})),
  28 + helpers.createProviderToValue("PersonService", personService),
  29 + helpers.createProviderToValue("$uibModal", helpers.mocks.$modal),
27 30 helpers.createProviderToValue('ProfileService', profileService),
28 31 helpers.createProviderToValue('NotificationService', helpers.mocks.notificationService)
29 32 ].concat(helpers.provideFilters("translateFilter"))
... ...
src/app/layout/blocks/profile-image/profile-image-block.html
1 1 <div class="center-block text-center profile-image-block">
2 2 <a ui-sref="main.profile.info({profile: ctrl.owner.identifier})">
3   - <noosfero-profile-image [profile]="ctrl.owner"></noosfero-profile-image>
  3 + <noosfero-profile-image [profile]="ctrl.owner" [editable]="true" [edit-class]="'profile-image-block-editable'"></noosfero-profile-image>
4 4 </a>
5 5 <a class="settings-link" target="_self" ui-sref="main.profile.settings({profile: ctrl.owner.identifier})">{{"blocks.profile_image.control_panel" | translate}}</a>
6 6 <div class="actions" ng-show="ctrl.isMember!=null">
... ...
src/app/layout/blocks/profile-image/profile-image-block.scss
... ... @@ -2,4 +2,22 @@
2 2 .settings-link {
3 3 display: block;
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;
5 18 }
  19 +
  20 +
  21 +
  22 +
  23 +
... ...
src/app/main/main.component.ts
... ... @@ -129,7 +129,7 @@ export class EnvironmentContent {
129 129 "angular-bind-html-compile", "angularMoment", "angular.filter", "akoenig.deckgrid",
130 130 "angular-timeline", "duScroll", "oitozero.ngSweetAlert",
131 131 "pascalprecht.translate", "tmh.dynamicLocale", "angularLoad",
132   - "angular-click-outside", "ngTagCloud", "noosfero.init", "uiSwitch"]
  132 + "angular-click-outside", "ngTagCloud", "noosfero.init", "uiSwitch", "ngFileUpload", "ngImgCrop"]
133 133 })
134 134 @StateConfig([
135 135 {
... ...
src/app/profile/image/image.component.spec.ts
... ... @@ -4,48 +4,72 @@
4 4 * @description
5 5 * This file contains the tests for the {@link components.noosfero.profile-image.ProfileImage} component.
6 6 */
7   -
8   -import {TestComponentBuilder, ComponentFixture} from 'ng-forward/cjs/testing/test-component-builder';
9   -import {Pipe, Input, provide, Component} from 'ng-forward';
  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";
10 11  
11 12 import * as helpers from "../../../spec/helpers";
12 13  
13   -import {ProfileImageComponent} from "./image.component";
  14 +import { ProfileImageComponent } from "./image.component";
14 15  
15   -const tcb = new TestComponentBuilder();
  16 +const htmlTemplate: string = '<noosfero-profile-image [editable]="true" [edit-class]="editable-class" [profile]="ctrl.profile"></noosfero-profile-image>';
16 17  
17 18 describe("Components", () => {
18 19  
19 20 describe("Profile Image Component", () => {
20 21  
  22 + let helper: ComponentTestHelper<ProfileImageComponent>;
  23 +
21 24 beforeEach(angular.mock.module("templates"));
22 25  
23   - it("show community users image if profile is not Person", done => {
24   - helpers.tcb.createAsync(ProfileImageComponent).then(fixture => {
25   - let profileImageComponent: ProfileImageComponent = fixture.componentInstance;
26   - let profile = <noosfero.Profile>{ id: 1, identifier: "myprofile", type: "Community" };
27   - profileImageComponent.profile = profile;
28   - profileImageComponent.ngOnInit();
29   -
30   - // Check the attribute
31   - expect(profileImageComponent.defaultIcon).toBe("fa-users", "The default icon should be community users");
32   - // var elProfile = fixture.debugElement.componentViewChildren[0];
33   - // expect(elProfile.query('div.profile-image-block').length).toEqual(1);
34   - done();
  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 + ]
35 39 });
  40 + helper = new ComponentTestHelper<ProfileImageComponent>(cls, done);
36 41 });
37 42  
38   - it("show Person image if profile is Person", done => {
39   - tcb.createAsync(ProfileImageComponent).then(fixture => {
40   - let profileImageComponent: ProfileImageComponent = fixture.componentInstance;
41   - let profile = <noosfero.Profile>{ id: 1, identifier: "myprofile", type: "Person" };
42   - profileImageComponent.profile = profile;
43   - profileImageComponent.ngOnInit();
44   - // Check the attribute
45   - expect(profileImageComponent.defaultIcon).toEqual("fa-user", "The default icon should be person user");
46   - done();
47   - });
  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();
48 72 });
49 73  
50 74 });
51   -});
52 75 \ No newline at end of file
  76 +});
... ...
src/app/profile/image/image.component.ts
1   -import {Inject, Input, Component} from "ng-forward";
2   -
  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";
3 4  
4 5 /**
5 6 * @ngdoc controller
... ... @@ -10,14 +11,16 @@ import {Inject, Input, Component} from &quot;ng-forward&quot;;
10 11 @Component({
11 12 selector: "noosfero-profile-image",
12 13 templateUrl: 'app/profile/image/image.html',
  14 + providers: [provide('personService', { useClass: PersonService })]
13 15 })
  16 +@Inject(PersonService, "$uibModal", "$scope")
14 17 export class ProfileImageComponent {
15 18  
16 19 /**
17 20 * @ngdoc property
18 21 * @name profile
19 22 * @propertyOf components.noosfero.profile-image.ProfileImage
20   - * @description
  23 + * @description
21 24 * The Noosfero {@link models.Profile} holding the image.
22 25 */
23 26 @Input() profile: noosfero.Profile;
... ... @@ -30,12 +33,54 @@ export class ProfileImageComponent {
30 33 */
31 34 defaultIcon: string;
32 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 + console.log("File selected: ", file);
  49 + if (file) {
  50 + this.picFile = file;
  51 + this.modalInstance = this.$uibModal.open({
  52 + templateUrl: 'app/profile/image/profile-image-editor.html',
  53 + controller: ProfileImageEditorComponent,
  54 + controllerAs: 'ctrl',
  55 + scope: this.$scope,
  56 + bindToController: true,
  57 + backdrop: 'static',
  58 + resolve: {
  59 + picFile: this.picFile,
  60 + profile: this.profile,
  61 + personService: this.personService
  62 + }
  63 + });
  64 + }
  65 + }
  66 +
  67 + private _showCamera: boolean = false;
1
  • Me
    Michel Felipe @mfdeveloper

    Estamos utilizando essa nomenclatura de prefixar atributos privados com "_" em outros componentes do projeto? Caso contrário, talvez seja importante padronizar

    Choose File ...   File name...
    Cancel
  68 +
  69 + showChange(show: boolean) {
  70 + this._showCamera = show;
  71 + }
  72 +
  73 + showCamera() {
  74 + return this._showCamera;
  75 + }
  76 +
  77 +
33 78 /**
34 79 * @ngdoc method
35 80 * @name ngOnInit
36 81 * @methodOf components.noosfero.profile-image.ProfileImage
37   - * @description
38   - * Initializes the icon names to their corresponding values depending on the profile type passed to the controller
  82 + * @description
  83 + * Initializes the icon names to their corresponding values depending on the profile type passed to the controller
39 84 */
40 85 ngOnInit() {
41 86 this.defaultIcon = 'fa-users';
... ... @@ -43,5 +88,5 @@ export class ProfileImageComponent {
43 88 this.defaultIcon = 'fa-user';
44 89 }
45 90 }
46   -}
47 91  
  92 +}
... ...
src/app/profile/image/image.html
1   -<span class="profile-image-wrap" title="{{ctrl.profile.name}}">
2   - <img ng-if="ctrl.profile.image" ng-src="{{ctrl.profile.image.url}}" class="img-responsive profile-image">
3   - <i ng-if="!ctrl.profile.image" class="fa {{ctrl.defaultIcon}} fa-5x profile-image"></i>
4   -</span>
  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
... ... @@ -5,3 +5,91 @@ i.profile-image {
5 5 background-clip: padding-box;
6 6 margin-bottom: 15px;
7 7 }
  8 +
  9 +.profile-image-wrap {
  10 + display: inline;
1
  • Me
    Michel Felipe @mfdeveloper (Edited )

    Este seletor possui exatamente a mesma regra css que #profile-image-container? Se sim, eles deveriam ser agrupados em uma mesma declaração, ou utilizado o @extend do sass para herança.

    Choose File ...   File name...
    Cancel
  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/profile-image-editor.component.spec.ts 0 → 100644
... ... @@ -0,0 +1,62 @@
  1 +import { Pipe, Input, provide, Component } from 'ng-forward';
  2 +import { ComponentTestHelper, createClass } from '../../../spec/component-test-helper';
  3 +import * as helpers from "../../../spec/helpers";
  4 +
  5 +import { ProfileImageEditorComponent } from "./profile-image-editor.component";
  6 +
  7 +describe("Components", () => {
  8 +
  9 + describe("Profile Image Editor Component", () => {
  10 +
  11 + beforeEach(angular.mock.module("templates"));
  12 +
  13 + let expectedData = "iVBORw0KGgoAAAANSUhEUgAAAMgAAADICAYAAACtWK6eAAAgAElEQ…Cm2OLHvfdNPte3zrH709Q0esN1LPQ0t7DL696ERpu+9/8BVPLIpElf7VYAAAAASUVORK5CYII=";
  14 + let testDataUrl = "data:image/png;base64," + expectedData;
  15 +
  16 + let profile = <noosfero.Profile>{ name: "profile_name", id: 1, identifier: "test" };
  17 + let modal = helpers.mocks.$modal;
  18 + let modalInstance = jasmine.createSpyObj("$uibModalInstance", ["close"]);
  19 + let picFile = { type: "png" };
  20 + let $q: ng.IQService;
  21 + let personServiceMock: any;
  22 + let $rootScope: ng.IRootScopeService;
  23 +
  24 + beforeEach(inject((_$q_: ng.IQService, _$rootScope_: ng.IRootScopeService) => {
  25 + $q = _$q_;
  26 + $rootScope = _$rootScope_;
  27 + }));
  28 +
  29 + let comp = new ProfileImageEditorComponent(picFile, this.profile, personServiceMock, modalInstance);
  30 +
  31 + it("get data", done => {
  32 +
  33 + let result = comp.getData(testDataUrl);
  34 + expect(result).toBe(expectedData);
  35 + done();
  36 + });
  37 +
  38 + it("get image name", done => {
  39 + let imageName = "image1";
  40 + let expectedName = "profile_name_" + imageName;
  41 + comp['profile'] = profile;
  42 + let result = comp.getImageName(imageName);
  43 + expect(result).toBe(expectedName);
  44 + done();
  45 + });
  46 +
  47 + it("upload image", done => {
  48 + let imageName = "image1";
  49 + personServiceMock = jasmine.createSpyObj("personServiceMock", ["uploadImage"]);
  50 + console.log("PersonServiceMock:", personServiceMock);
  51 + let deferredUploadImage = $q.defer();
  52 + personServiceMock.uploadImage = jasmine.createSpy('uploadImage').and.returnValue(deferredUploadImage.promise);
  53 + comp.personService = personServiceMock;
  54 + comp.uploadImage(testDataUrl, imageName);
  55 + deferredUploadImage.resolve();
  56 + $rootScope.$apply();
  57 + expect(comp.modalInstance.close).toHaveBeenCalled();
  58 + done();
  59 + });
  60 +
  61 + });
  62 +});
... ...
src/app/profile/image/profile-image-editor.component.ts 0 → 100644
... ... @@ -0,0 +1,43 @@
  1 +import { StateConfig, Component, Input, Output, Inject, provide } from 'ng-forward';
  2 +import { TranslateProfile } from "../../shared/pipes/translate-profile.filter";
  3 +import { PersonService } from "../../../lib/ng-noosfero-api/http/person.service";
  4 +
  5 +export class ProfileImageEditorComponent {
  6 +
  7 + activities: any;
  8 + croppedDataUrl: string;
  9 + static $inject = ["picFile", "profile", "personService", "$uibModalInstance"];
  10 +
  11 + constructor(public picFile: any, public profile: noosfero.Profile, public personService: PersonService,
  12 + public modalInstance: ng.ui.bootstrap.IModalServiceInstance) {
  13 + }
  14 +
  15 + uploadImage(dataUrl: any, name: any) {
  16 + let base64ImageJson = this.getBase64ImageJson(dataUrl, name);
  17 + this.personService.uploadImage(this.profile, base64ImageJson).then((result: any) => {
  18 + this.modalInstance.close(name);
  19 + });
  20 + }
  21 +
  22 + getBase64ImageJson(dataUrl: any, name: any): any {
  23 + let data = this.getData(dataUrl);
  24 + let image_name = this.getImageName(name);
  25 + return {
  26 + tempfile: data,
  27 + filename: image_name,
  28 + type: this.picFile.type
  29 + };
  30 + }
  31 +
  32 + getImageName(name: any): string {
  33 + return this.profile.name + "_" + name;
  34 + }
  35 +
  36 + getData(dataUrl: any): string {
  37 + return dataUrl.substring(dataUrl.indexOf('base64,') + 7);
  38 + }
  39 +
  40 + cancel() {
  41 + this.modalInstance.close();
  42 + }
  43 +}
... ...
src/app/profile/image/profile-image-editor.html 0 → 100644
... ... @@ -0,0 +1,24 @@
  1 +<div class="modal-header">
  2 + <h3>{{"profile.image.edit" | translate}}</h3>
  3 +</div>
  4 +<div class="modal-body">
  5 + <form class="">
  6 + <div ngf-drop ng-model="ctrl.picFile" ngf-pattern="image/*" class="cropArea">
  7 + <img-crop image="ctrl.picFile | ngfDataUrl" area-type="square"
  8 + result-image="ctrl.croppedDataUrl" ng-init="ctrl.croppedDataUrl=''">
  9 + </img-crop>
  10 + </div>
  11 + <div>
  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>
  18 + </form>
  19 +
  20 + <div class="actions">
  21 + <button type="submit" class="btn btn-default" (click)="ctrl.uploadImage(ctrl.croppedDataUrl, ctrl.picFile.name)">Upload</button>
  22 + <button type="submit" class="btn btn-danger" (click)="ctrl.cancel()">Cancel</button>
  23 + </div>
  24 +</div>
... ...
src/app/profile/info/profile-info.html
... ... @@ -6,10 +6,12 @@
6 6 <h2>{{vm.profile.name}}</h2>
7 7 </header>
8 8 <div id="profile-left" class="main-box-body clearfix">
9   - <noosfero-profile-image [profile]="vm.profile" class="img-responsive center-block"></noosfero-profile-image>
10   - <span class="label" ng-class="{'label-danger': vm.profile.type == 'Community', 'label-info': vm.profile.type == 'Person'}">{{vm.profile | translateProfile}}</span>
11   - <div class="profile-since">
12   - {{"profile.member_since" | translate}}: {{vm.profile.created_at | amDateFormat:'MMMM YYYY'}}
  9 + <noosfero-profile-image [profile]="vm.profile" [editable]="true" [edit-class]="'profile-info-editable'" class="img-responsive center-block profile-info"></noosfero-profile-image>
  10 + <div id="profile-info-extrainfo" class="profile-info-extrainfo">
  11 + <span class="label" ng-class="{'label-danger': vm.profile.type == 'Community', 'label-info': vm.profile.type == 'Person'}">{{vm.profile | translateProfile}}</span>
  12 + <div class="profile-since">
  13 + {{"profile.member_since" | translate}}: {{vm.profile.created_at | amDateFormat:'MMMM YYYY'}}
  14 + </div>
13 15 </div>
14 16 </div>
15 17 </div>
... ...
src/app/profile/info/profile-info.scss 0 → 100644
... ... @@ -0,0 +1,25 @@
  1 +.profile-info {
  2 + .upload-camera-container {
  3 + top: 55%;
  4 + left: 39px;
  5 + }
  6 +}
  7 +
  8 +.profile-info-editable {
  9 + top: 51%;
  10 + width: 103px;
  11 + height: 28px;
  12 +}
  13 +
  14 +.profile-info-editable {
  15 + .upload-button {
  16 + font-size: 0.8em;
  17 + padding-top: 9px;
  18 + padding-left: 6px;
  19 + font-weight: bold;
  20 + }
  21 +}
  22 +
  23 +.profile-info-extrainfo {
  24 + margin-top: 10px;
  25 +}
... ...
src/languages/en.json
... ... @@ -23,6 +23,8 @@
23 23 "profile.others_info": "Others",
24 24 "profile.community.title": "Community",
25 25 "profile.person.title": "Person",
  26 + "profile.image.edit": "Crop photo",
  27 + "profile.image.upload": "Upload Photo",
26 28 "activities.title": "Activities",
27 29 "activities.create_article.description": "has published on",
28 30 "activities.scrap.description": "wrote in its timeline",
... ...
src/languages/pt.json
... ... @@ -23,6 +23,8 @@
23 23 "profile.others_info": "Outras informações",
24 24 "profile.community.title": "Comunidade",
25 25 "profile.person.title": "Pessoa",
  26 + "profile.image.edit": "Recortar photo",
  27 + "profile.image.upload": "Enviar photo",
26 28 "activities.title": "Atividades",
27 29 "activities.create_article.description": "publicou em",
28 30 "activities.scrap.description": "escreveu em sua linha do tempo",
... ...
src/lib/ng-noosfero-api/http/person.service.ts
1 1 import { Injectable, Inject } from "ng-forward";
2   -import {RestangularService} from "./restangular_service";
3   -import {ProfileService} from "./profile.service";
  2 +import { RestangularService } from "./restangular_service";
  3 +import { ProfileService } from "./profile.service";
4 4  
5 5 @Injectable()
6 6 @Inject("Restangular", "$q", "$log", ProfileService)
... ... @@ -28,4 +28,19 @@ export class PersonService extends RestangularService&lt;noosfero.Person&gt; {
28 28 p.catch(this.getHandleErrorFunction<noosfero.RestResult<any>>(deferred));
29 29 return deferred.promise;
30 30 }
  31 +
  32 + uploadImage(profile: noosfero.Profile, base64ImageJson: any) {
  33 + let headers = { 'Content-Type': 'application/json' };
  34 + let deferred = this.$q.defer<noosfero.RestResult<noosfero.Profile>>();
  35 + // TODO dynamically copy the selected attributes to update
1
  • Me
    Michel Felipe @mfdeveloper (Edited )

    Esse TODO foi realizado? Senão, talvez seja interessante adicionar um comentário neste MR explicando o que falta ser feito, para criarmos uma issue para essa modificação, se de fato for necessário :)

    Choose File ...   File name...
    Cancel
  36 + let attributesToUpdate: any = {
  37 + person: { image_builder: base64ImageJson }
  38 + };
  39 + let restRequest: ng.IPromise<noosfero.RestResult<any>> =
  40 + this.getElement(profile.id).customPOST(attributesToUpdate, null, null, headers);
  41 + restRequest.then(this.getHandleSuccessFunction(deferred))
  42 + .catch(this.getHandleErrorFunction(deferred));
  43 + return deferred.promise;
  44 + }
  45 +
31 46 }
... ...
  • 2c5c4299d62769e3da7d432cd2823dd6?s=40&d=identicon
    Carlos Purificação @carloseugenio

    mentioned in issue #111

    Choose File ...   File name...
    Cancel
  • Me
    Michel Felipe @mfdeveloper

    Reassigned to @mfdeveloper

    Choose File ...   File name...
    Cancel
  • Me
    Michel Felipe started a discussion on the outdated diff
    last updated by Michel Felipe
    src/app/profile/image/image.component.spec.ts
      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 + /*
    1
    Me
    Michel Felipe started a discussion on the diff
    last updated by Michel Felipe
    src/app/profile/image/image.component.ts
      52 + templateUrl: 'app/profile/image/profile-image-editor.html',
      53 + controller: ProfileImageEditorComponent,
      54 + controllerAs: 'ctrl',
      55 + scope: this.$scope,
      56 + bindToController: true,
      57 + backdrop: 'static',
      58 + resolve: {
      59 + picFile: this.picFile,
      60 + profile: this.profile,
      61 + personService: this.personService
      62 + }
      63 + });
      64 + }
      65 + }
      66 +
      67 + private _showCamera: boolean = false;
    1
    • Me
      Michel Felipe @mfdeveloper

      Estamos utilizando essa nomenclatura de prefixar atributos privados com "_" em outros componentes do projeto? Caso contrário, talvez seja importante padronizar

      Choose File ...   File name...
      Cancel
    Me
    Michel Felipe started a discussion on the outdated diff
    last updated by Michel Felipe
    src/app/profile/image/image.html
    2   - <img ng-if="ctrl.profile.image" ng-src="{{ctrl.profile.image.url}}" class="img-responsive profile-image">
    3   - <i ng-if="!ctrl.profile.image" class="fa {{ctrl.defaultIcon}} fa-5x profile-image"></i>
    4   -</span>
      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}"
    1
    • Me
      Michel Felipe @mfdeveloper

      Esses parâmetros: ngf-max-size, ngf-accept e etc, deveriam ser atributos do componente ou até mesmo de um serviço nao? Dessa forma, esses valores poderiam ser configurados pelo componente ConfigBarComponent visualmente pelo admin do ambiente :)

      Choose File ...   File name...
      Cancel
    Me
    Michel Felipe started a discussion on the diff
    last updated by Michel Felipe
    src/app/profile/image/image.scss
    5 5 background-clip: padding-box;
    6 6 margin-bottom: 15px;
    7 7 }
      8 +
      9 +.profile-image-wrap {
      10 + display: inline;
    1
    • Me
      Michel Felipe @mfdeveloper (Edited )

      Este seletor possui exatamente a mesma regra css que #profile-image-container? Se sim, eles deveriam ser agrupados em uma mesma declaração, ou utilizado o @extend do sass para herança.

      Choose File ...   File name...
      Cancel
    Me
    Michel Felipe started a discussion on the outdated diff
    last updated by Michel Felipe
    src/app/profile/image/image.scss
      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 + //overflow: hidden;
    1
    • Me
      Michel Felipe @mfdeveloper (Edited )

      Essa regra comentada deve ser removida?

      Choose File ...   File name...
      Cancel
    Me
    Michel Felipe started a discussion on the outdated diff
    last updated by Michel Felipe
    src/app/profile/image/image.scss
      46 + transition: top .13s ease-out;
      47 +}
      48 +
      49 +#upload-container {
      50 + position: relative;
      51 + text-decoration: none;
      52 +}
      53 +
      54 +.upload-container a:hover {
      55 + text-decoration: none;
      56 +}
      57 +
      58 +.upload-button {
      59 + -webkit-font-smoothing: antialiased;
      60 + color: #fff;
      61 + //word-wrap: break-word;
    1
    Me
    Michel Felipe started a discussion on the diff
    last updated by Michel Felipe
    src/app/profile/image/image.scss
      39 +.select-photo-container {
      40 + //overflow: hidden;
      41 + position: absolute;
      42 + z-index: -1;
      43 + background: #000;
      44 + background: rgba(0, 0, 0, .6);
      45 + background: linear-gradient(transparent, rgba(0, 0, 0, .6) 70%, rgba(0, 0, 0, .6) 100%);
      46 + transition: top .13s ease-out;
      47 +}
      48 +
      49 +#upload-container {
      50 + position: relative;
      51 + text-decoration: none;
      52 +}
      53 +
      54 +.upload-container a:hover {
    1
    • Me
      Michel Felipe @mfdeveloper (Edited )

      Utilize o seletor & para referenciar o pai. È uma boa prática de uso para seletores do tipo :hover, :after, :focus.

      Observe a documentação em: http://sass-lang.com/documentation/file.SASS_REFERENCE.html#parent-selector

      Choose File ...   File name...
      Cancel
    Me
    Michel Felipe started a discussion on the diff
    last updated by Michel Felipe
    src/app/profile/image/image.scss
      44 + background: rgba(0, 0, 0, .6);
      45 + background: linear-gradient(transparent, rgba(0, 0, 0, .6) 70%, rgba(0, 0, 0, .6) 100%);
      46 + transition: top .13s ease-out;
      47 +}
      48 +
      49 +#upload-container {
      50 + position: relative;
      51 + text-decoration: none;
      52 +}
      53 +
      54 +.upload-container a:hover {
      55 + text-decoration: none;
      56 +}
      57 +
      58 +.upload-button {
      59 + -webkit-font-smoothing: antialiased;
    1
    • Me
      Michel Felipe @mfdeveloper (Edited )

      Utilizar o prefixo -webkit somente para font-smoothing não funciona no Firefox por exemplo. Estes seletores CSS3 que possuem prefixos de browsers, estamos utilizando como padrão no projeto a criação de um @mixin dentro do arquivo _mixins.scss, contendo as diversas formas e seus prefixos de regras CSS3. Após isso, basta incluir de forma explicita com @import "caminho/mixin" e utilizar o mixin dentro do seu seletor!!

      Choose File ...   File name...
      Cancel
    Me
    Michel Felipe started a discussion on the diff
    last updated by Michel Felipe
    src/app/profile/image/image.scss
      61 + //word-wrap: break-word;
      62 +}
      63 +
      64 +.upload-container {
      65 + color:#fff;
      66 + display: block;
      67 + overflow: hidden;
      68 + position: relative;
      69 + text-align: left;
      70 + min-width: 89px;
      71 +}
      72 +
      73 +.cropArea {
      74 + background: #E4E4E4;
      75 + overflow: hidden;
      76 + width:300px;
    1
    Me
    Michel Felipe started a discussion on the diff
    last updated by Michel Felipe
    src/app/profile/image/image.scss
      75 + overflow: hidden;
      76 + width:300px;
      77 + height:150px;
      78 +}
      79 +
      80 +.crop-area {
      81 + display: none;
      82 +}
      83 +
      84 +form .progress {
      85 + line-height: 15px;
      86 +}
      87 +
      88 +.progress {
      89 + display: inline-block;
      90 + width: 100px;
    1
    Me
    Michel Felipe started a discussion on the outdated diff
    last updated by Michel Felipe
    src/app/profile/image/profile-image-editor.component.ts 0 → 100644
      1 +import {StateConfig, Component, Input, Output, Inject, provide} from 'ng-forward';
      2 +import {TranslateProfile} from "../../shared/pipes/translate-profile.filter";
      3 +import {PersonService} from "../../../lib/ng-noosfero-api/http/person.service";
      4 +
      5 +export class ProfileImageEditorComponent {
      6 +
      7 + activities: any;
      8 + croppedDataUrl: string;
      9 + static $inject = ["picFile", "profile", "personService", "$uibModalInstance"];
      10 +
      11 + constructor(public picFile: any, public profile: noosfero.Profile, public personService: PersonService,
      12 + public $uibModalInstance: any) {
      13 + }
      14 +
      15 + uploadImage(dataUrl: any, name: any) {
      16 + let base64_image_json = this.getBase64ImageJson(dataUrl, name);
    1
    • Me
      Michel Felipe @mfdeveloper

      O padrão de nomenclatura que estamos utilizando de variáveis geralmente é cammelCase. Algum motivo em especial para utilizar com underscore?

      Choose File ...   File name...
      Cancel
    Me
    Michel Felipe started a discussion on the outdated diff
    last updated by Michel Felipe
    src/app/profile/image/profile-image-editor.component.ts 0 → 100644
      7 + activities: any;
      8 + croppedDataUrl: string;
      9 + static $inject = ["picFile", "profile", "personService", "$uibModalInstance"];
      10 +
      11 + constructor(public picFile: any, public profile: noosfero.Profile, public personService: PersonService,
      12 + public $uibModalInstance: any) {
      13 + }
      14 +
      15 + uploadImage(dataUrl: any, name: any) {
      16 + let base64_image_json = this.getBase64ImageJson(dataUrl, name);
      17 + this.personService.uploadImage(this.profile, base64_image_json).then( (result: any) => {
      18 + this.$uibModalInstance.close(name);
      19 + });
      20 + }
      21 +
      22 + getBase64ImageJson(dataUrl: any, name: any) {
    1
    • Me
      Michel Felipe @mfdeveloper

      Pq este método está sem definição de tipo de retorno? Algum motivo especifico?

      Choose File ...   File name...
      Cancel
    Me
    Michel Felipe started a discussion on the outdated diff
    last updated by Michel Felipe
    src/app/profile/image/profile-image-editor.component.ts 0 → 100644
      17 + this.personService.uploadImage(this.profile, base64_image_json).then( (result: any) => {
      18 + this.$uibModalInstance.close(name);
      19 + });
      20 + }
      21 +
      22 + getBase64ImageJson(dataUrl: any, name: any) {
      23 + let data = this.getData(dataUrl);
      24 + let image_name = this.getImageName(name);
      25 + return {
      26 + tempfile: data,
      27 + filename: image_name,
      28 + type: this.picFile.type
      29 + };
      30 + }
      31 +
      32 + getImageName(name: any) {
    1
    Me
    Michel Felipe started a discussion on the outdated diff
    last updated by Michel Felipe
    src/app/profile/image/profile-image-editor.component.ts 0 → 100644
      1 +import {StateConfig, Component, Input, Output, Inject, provide} from 'ng-forward';
      2 +import {TranslateProfile} from "../../shared/pipes/translate-profile.filter";
      3 +import {PersonService} from "../../../lib/ng-noosfero-api/http/person.service";
      4 +
      5 +export class ProfileImageEditorComponent {
      6 +
      7 + activities: any;
      8 + croppedDataUrl: string;
      9 + static $inject = ["picFile", "profile", "personService", "$uibModalInstance"];
      10 +
      11 + constructor(public picFile: any, public profile: noosfero.Profile, public personService: PersonService,
      12 + public $uibModalInstance: any) {
    1
    • Me
      Michel Felipe @mfdeveloper

      Recentemente foi adicionado ao projeto uma definição de typescript para um namespace do angular-bootstrap. Agora é possível adicionar um tipo para tudo que envolve o $uibModal. Para instâncias do modal, utilize o tipo: ng.ui.bootstrap.IModalServiceInstance;

      Choose File ...   File name...
      Cancel
    Me
    Michel Felipe started a discussion on the diff
    last updated by Michel Felipe
    src/app/profile/image/image.component.spec.ts
      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");
    1
    • Me
      Michel Felipe @mfdeveloper

      Acesse o $uibModal diretamente como uma propriedade. Algo como: helper.component.$uibModal. Para isso, o atributo precisa ser público, ou possuir um get/set através de Acessors do typescript.

      Documentação: https://www.typescriptlang.org/docs/handbook/classes.html

      Choose File ...   File name...
      Cancel
    Me
    Michel Felipe started a discussion on the outdated diff
    last updated by Michel Felipe
    src/app/profile/image/image.component.ts
    30 33 */
    31 34 defaultIcon: string;
    32 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: any, private $scope: ng.IScope) {
    1
    • Me
      Michel Felipe @mfdeveloper

      Agora é possível adicionar o tipo do modal para ng.ui.bootstrap.IModalService. Substitua o any por esse tipo.

      Choose File ...   File name...
      Cancel
    Me
    Michel Felipe started a discussion on the outdated diff
    last updated by Michel Felipe
    src/lib/ng-noosfero-api/http/person.service.ts
    28 28 p.catch(this.getHandleErrorFunction<noosfero.RestResult<any>>(deferred));
    29 29 return deferred.promise;
    30 30 }
      31 +
      32 + uploadImage(profile: noosfero.Profile, base64_image_json: any) {
      33 + let headers = { 'Content-Type': 'application/json' };
      34 + let deferred = this.$q.defer<noosfero.RestResult<noosfero.Profile>>();
      35 + // TODO dynamically copy the selected attributes to update
      36 + let attributesToUpdate: any = {
      37 + person: { image_builder: base64_image_json }
    1
    Me
    Michel Felipe started a discussion on the diff
    last updated by Michel Felipe
    src/lib/ng-noosfero-api/http/person.service.ts
    28 28 p.catch(this.getHandleErrorFunction<noosfero.RestResult<any>>(deferred));
    29 29 return deferred.promise;
    30 30 }
      31 +
      32 + uploadImage(profile: noosfero.Profile, base64_image_json: any) {
      33 + let headers = { 'Content-Type': 'application/json' };
      34 + let deferred = this.$q.defer<noosfero.RestResult<noosfero.Profile>>();
      35 + // TODO dynamically copy the selected attributes to update
    1
    • Me
      Michel Felipe @mfdeveloper (Edited )

      Esse TODO foi realizado? Senão, talvez seja interessante adicionar um comentário neste MR explicando o que falta ser feito, para criarmos uma issue para essa modificação, se de fato for necessário :)

      Choose File ...   File name...
      Cancel
    Me
    Michel Felipe started a discussion on the outdated diff
    last updated by Michel Felipe
    src/app/profile/image/profile-image-editor.html 0 → 100644
      6 + <div ngf-drop ng-model="ctrl.picFile" ngf-pattern="image/*" class="cropArea">
      7 + <img-crop image="ctrl.picFile | ngfDataUrl" area-type="square"
      8 + result-image="ctrl.croppedDataUrl" ng-init="ctrl.croppedDataUrl=''">
      9 + </img-crop>
      10 + </div>
      11 + <div>
      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>
      18 + </form>
      19 +
      20 + <div class="actions">
      21 + <button type="submit" class="btn btn-default" ng-click="ctrl.uploadImage(ctrl.croppedDataUrl, ctrl.picFile.name)">Upload</button>
    1
    Me
    Michel Felipe started a discussion on the outdated diff
    last updated by Michel Felipe
    src/app/profile/image/profile-image-editor.component.spec.ts 0 → 100644
      16 + let modal = helpers.mocks.$modal;
      17 + let modalInstance = jasmine.createSpyObj("$uibModalInstance", ["close"]);
      18 + let picFile = { type: "png" };
      19 + let $q: ng.IQService;
      20 + let personServiceMock: any;
      21 + let $rootScope: ng.IRootScopeService;
      22 +
      23 + beforeEach(inject((_$q_: ng.IQService, _$rootScope_: ng.IRootScopeService) => {
      24 + $q = _$q_;
      25 + $rootScope = _$rootScope_;
      26 + }));
      27 +
      28 + let comp = new ProfileImageEditorComponent(picFile, this.profile, personServiceMock, modalInstance);
      29 +
      30 + it("get data", done => {
      31 + let testDataUrl = "data:image/png;base64," + expectedData;
    1
    • Me
      Michel Felipe @mfdeveloper

      Mova esta variável para o describe, afim de reutilizá-la entre todos os testes, para evitar essa duplicidade de código

      Choose File ...   File name...
      Cancel
    Me
    Michel Felipe started a discussion on the outdated diff
    last updated by Michel Felipe
    src/app/profile/info/profile-info.scss 0 → 100644
      1 +.profile-info .upload-camera-container {
      2 + top: 55%;
      3 + left: 39px;
      4 +}
      5 +
      6 +.profile-info-editable {
      7 + top: 51%;
      8 + width: 103px;
      9 + height: 28px;
      10 +}
      11 +
      12 +.profile-info-editable .upload-button {
    1
    Me
    Michel Felipe started a discussion on the diff
    last updated by Michel Felipe
    src/app/profile/info/profile-info.scss 0 → 100644
      1 +.profile-info .upload-camera-container {
      2 + top: 55%;
      3 + left: 39px;
      4 +}
      5 +
      6 +.profile-info-editable {
      7 + top: 51%;
      8 + width: 103px;
    1
    • Me
      Michel Felipe @mfdeveloper

      È interessante que esses valores de width, height, top e etc estejam em variáveis no topo do arquivo .scss

      Choose File ...   File name...
      Cancel
    Me
    Michel Felipe started a discussion on the diff
    last updated by Michel Felipe
    src/app/profile/image/image.scss
      65 + color:#fff;
      66 + display: block;
      67 + overflow: hidden;
      68 + position: relative;
      69 + text-align: left;
      70 + min-width: 89px;
      71 +}
      72 +
      73 +.cropArea {
      74 + background: #E4E4E4;
      75 + overflow: hidden;
      76 + width:300px;
      77 + height:150px;
      78 +}
      79 +
      80 +.crop-area {
    1
  • Me
    Michel Felipe @mfdeveloper

    Realize um git rebase com o branch [master] para dirimir os conflitos!

    Choose File ...   File name...
    Cancel
  • Me
    Michel Felipe @mfdeveloper

    Added 42 new commits:

    • 8784fe3c - Ticket #116: Better display of recent activities
    • 1e4736a4 - added events-hub service
    • b3953cbd - improving adding a base interface EventsHubKnownEventNames
    • 332c24ec - added changes to allow pass string[] or EventsHubKnownEventNames to EventsHubService constructor
    • 328a7a82 - some small refactoring on class naming and changed to allow EVENTS_HUB_KNOW_EVEN…
    • fb1e923a - Merge branch 'events-hub' into 'master'
    • 3d2172f3 - Adds account register module
    • bc91225e - Refactor of the account register component to uses service and restangular instead.
    • 42443554 - Fixing registration - it is working now
    • 503976b5 - add redirect to home in case of succeded signup
    • e10ddb2a - Added notification when user is created
    • 716c2a26 - Added welcome message for translation on signup
    • 74b67520 - Refactory html template + new environment service property
    • 64b0a116 - Added environment fields and ngMessages form validation
    • 4c25e839 - Added the ng.ui.bootstrap typescript definitions
    • 015b68e2 - Added the modal to show environment terms of use text
    • 224b593d - Added module 'angular-password' to check password confir match
    • a4dac9ec - Added unit tests and minor refactories
    • b4a0a739 - Fix rebase with master adjusts
    • 64000ab5 - Merge branch 'register_page' into 'master'
    • 26ff5335 - Change txt navbar menuitem from 'Novo Artigo' to 'Novo post' on pt.json
    • d3bba43c - Ticket #118: Profile images block
    • 6596b7af - adding scrap activity in profile timeline
    • e80a93b4 - change scrap timeline visualization
    • de08c0f6 - Merge branch 'add_scrap_in_timeline' into 'master'
    • 7309dec7 - Add button to join in community
    • f3cb9503 - Merge branch 'join-community' into 'master'
    • 4d167676 - Add component to display tasks in menu
    • 917544be - Add component to list tasks
    • 07f15849 - Add accept/reject buttons to task list
    • 45183e9d - Add tests to task components
    • 118eeb9d - List roles when accept add member tasks
    • 0354c769 - Accept parameters when accept/reject tasks
    • 7205a81d - Emit and subscribe to events for task accept/reject
    • 917a672d - Translate tasks messages
    • 1adb5479 - Use .checkbox-nice in accept member task
    • 6fca41b5 - Refactor methods to close task
    • d6d5bae0 - Create interface for AddMemberTask
    • a1375e04 - Merge branch 'tasks' into 'master'
    • 07858dff - Initial implementation
    • ccf7dc63 - Added upload image to person service
    • c132a4a9 - Upload image initial and tests
    Choose File ...   File name...
    Cancel
  • Me
    Michel Felipe @mfdeveloper

    Added 1 new commit:

    • 338aae2e - Minor refactory and sintax adjusts
    Choose File ...   File name...
    Cancel
  • Me
    Michel Felipe @mfdeveloper
    Choose File ...   File name...
    Cancel
  • Me
    Michel Felipe @mfdeveloper

    mentioned in issue #111

    Choose File ...   File name...
    Cancel
  • Me
    Michel Felipe @mfdeveloper (Edited )

    Este merge request foi aceito, mas necessita das seguintes melhorias/ajustes:

    1. O botão de Upload photo que está sendo exibido no :hover seu container, está com a exibição intermitente. A depender do tamanho da imagem contida no bloco profile-image, o :hover funciona de forma correta ou não.

    2. Ajustes de responsividade. Talvez seja interessante avaliar se este elemento HTML (Exibido ao passar o mouse) é realmente a melhor forma

    3. Refatorar os arquivos .sass envolvidos

      Pode depender das definições dos itens 1 e 2

    4. Exibir feedback para o usuário quando um erro ocorrer no upload da imagem (tamanho excedido, formato inválido, erro no servidor ao requisitar a API e etc...)

    5. Verificar permissões: Não exibir o botão de upload ao acessar o profile de um outro usuário

    Importante criar uma outra issue contendo estes ajustes

    Choose File ...   File name...
    Cancel