Commit 07858dff0017d2387d04beda61fa4204946657a5
Committed by
Michel Felipe
1 parent
a1375e04
Exists in
master
and in
6 other branches
Initial implementation
Showing
14 changed files
with
312 additions
and
13 deletions
Show diff stats
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.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: 305px; | |
| 7 | + left: 23px; | |
| 8 | + } | |
| 9 | +} | |
| 10 | + | |
| 11 | +.profile-image-block-editable { | |
| 12 | + top: 287px; | |
| 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.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,7 +11,9 @@ import {Inject, Input, Component} from "ng-forward"; |
| 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", "Upload", "$timeout", "$scope") | |
| 14 | 17 | export class ProfileImageComponent { |
| 15 | 18 | |
| 16 | 19 | /** |
| ... | ... | @@ -29,7 +32,53 @@ export class ProfileImageComponent { |
| 29 | 32 | * The default icon used by this profile |
| 30 | 33 | */ |
| 31 | 34 | defaultIcon: string; |
| 35 | + | |
| 36 | + @Input() editable: boolean; | |
| 37 | + | |
| 38 | + @Input() editClass: string; | |
| 39 | + | |
| 40 | + picFile: any; | |
| 41 | + croppedDataUrl: any; | |
| 42 | + modalInstance: any; | |
| 32 | 43 | |
| 44 | + constructor(private personService: PersonService, private $uibModal: any, private Upload: any, | |
| 45 | + private $timeout: any, private $scope: ng.IScope) { | |
| 46 | + //console.log("ImageComponent.Created with upload: ", this.Upload); | |
| 47 | + //console.log("ImageComponent.Cropped: ", this.croppedDataUrl); | |
| 48 | + //console.log("ImageComponent.PicFile: ", this.picFile); | |
| 49 | + } | |
| 50 | + | |
| 51 | + fileSelected(file: any, errFiles: any) { | |
| 52 | + console.log("File selected: ", file); | |
| 53 | + if (file) { | |
| 54 | + this.picFile = file; | |
| 55 | + this.modalInstance = this.$uibModal.open({ | |
| 56 | + templateUrl: 'app/profile/image/profile-image-editor.html', | |
| 57 | + controller: ProfileImageEditorComponent, | |
| 58 | + controllerAs: 'ctrl', | |
| 59 | + scope: this.$scope, | |
| 60 | + bindToController: true, | |
| 61 | + backdrop: 'static', | |
| 62 | + resolve: { | |
| 63 | + picFile: this.picFile, | |
| 64 | + profile: this.profile, | |
| 65 | + personService: this.personService | |
| 66 | + } | |
| 67 | + }); | |
| 68 | + } | |
| 69 | + } | |
| 70 | + | |
| 71 | + private _showCamera: boolean = false; | |
| 72 | + | |
| 73 | + showChange(show: boolean) { | |
| 74 | + this._showCamera = show; | |
| 75 | + } | |
| 76 | + | |
| 77 | + showCamera() { | |
| 78 | + return this._showCamera; | |
| 79 | + } | |
| 80 | + | |
| 81 | + | |
| 33 | 82 | /** |
| 34 | 83 | * @ngdoc method |
| 35 | 84 | * @name ngOnInit |
| ... | ... | @@ -43,5 +92,11 @@ export class ProfileImageComponent { |
| 43 | 92 | this.defaultIcon = 'fa-user'; |
| 44 | 93 | } |
| 45 | 94 | } |
| 95 | + | |
| 96 | + ngAfterViewInit() { | |
| 97 | + console.log("Parent scope: ", this.$scope.$parent['ctrl']['__proto__']); | |
| 98 | + console.log("Editable: " + this.editable); | |
| 99 | + console.log("Edit_class: " + this.editClass); | |
| 100 | + } | |
| 46 | 101 | } |
| 47 | 102 | ... | ... |
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,94 @@ i.profile-image { |
| 5 | 5 | background-clip: padding-box; |
| 6 | 6 | margin-bottom: 15px; |
| 7 | 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 | + //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 { | |
| 55 | + text-decoration: none; | |
| 56 | +} | |
| 57 | + | |
| 58 | +.upload-button { | |
| 59 | + -webkit-font-smoothing: antialiased; | |
| 60 | + color: #fff; | |
| 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; | |
| 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; | |
| 91 | + border: 3px groove #CCC; | |
| 92 | +} | |
| 93 | +.progress div { | |
| 94 | + font-size: smaller; | |
| 95 | + background: orange; | |
| 96 | + width: 0; | |
| 97 | +} | |
| 98 | + | ... | ... |
| ... | ... | @@ -0,0 +1,61 @@ |
| 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 = ["Upload", "$timeout", "$scope", "picFile", "profile", "personService", "$uibModalInstance"]; | |
| 10 | + | |
| 11 | + constructor( | |
| 12 | + private upload: any, private $timeout: any, private $scope: ng.IScope, | |
| 13 | + public picFile: any, private profile: noosfero.Profile, private personService: PersonService, | |
| 14 | + private $uibModalInstance: any) { | |
| 15 | + //this.picFile = this.picFile; | |
| 16 | + console.log("Value set: ", this.picFile); | |
| 17 | + } | |
| 18 | + | |
| 19 | + uploadImage(dataUrl: any, name: any) { | |
| 20 | + console.log("Uploading [" + name + "] with data: ", dataUrl); | |
| 21 | + let data = dataUrl.substring(dataUrl.indexOf('base64,') + 7); | |
| 22 | + let image_name = this.profile.name + "_" + name; | |
| 23 | + let base64_image_json = { | |
| 24 | + tempfile: data, | |
| 25 | + filename: image_name, | |
| 26 | + type: this.picFile.type | |
| 27 | + }; | |
| 28 | + console.log("Base64Image JSON: ", base64_image_json); | |
| 29 | + this.personService.uploadImage(this.profile, base64_image_json).then( (result: any) => { | |
| 30 | + console.log("Upload finished: ", result); | |
| 31 | + this.$uibModalInstance.close(name); | |
| 32 | + }); | |
| 33 | + } | |
| 34 | + | |
| 35 | + uploadFiles(file: any, errFiles: any) { | |
| 36 | + console.log("Going to upload: ", file); | |
| 37 | + | |
| 38 | + //$scope.f = file; | |
| 39 | + let errFile = errFiles && errFiles[0]; | |
| 40 | + if (file) { | |
| 41 | + let base64 = this.upload.base64DataUrl(file); | |
| 42 | + console.log("Base64", base64); | |
| 43 | + base64.then( (base64Urls: any) => { | |
| 44 | + console.log("Uploading base64Urls: ", base64Urls); | |
| 45 | + let data = base64Urls.substring(base64Urls.indexOf('base64,') + 7); | |
| 46 | + let image_name = this.profile.name + "_" + file.name; | |
| 47 | + let base64_image_json = { | |
| 48 | + tempfile: data, | |
| 49 | + filename: image_name, | |
| 50 | + type: file.type | |
| 51 | + }; | |
| 52 | + console.log("Base64Image JSON: ", base64_image_json); | |
| 53 | + this.personService.uploadImage(this.profile, base64_image_json); | |
| 54 | + }); | |
| 55 | + } | |
| 56 | + } | |
| 57 | + | |
| 58 | + cancel() { | |
| 59 | + this.$uibModalInstance.close(); | |
| 60 | + } | |
| 61 | +} | ... | ... |
| ... | ... | @@ -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" ng-click="ctrl.uploadImage(ctrl.croppedDataUrl, ctrl.picFile.name)">Upload</button> | |
| 22 | + <button type="submit" class="btn btn-danger" ng-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> | ... | ... |
| ... | ... | @@ -0,0 +1,21 @@ |
| 1 | +.profile-info .upload-camera-container { | |
| 2 | + top: 162px; | |
| 3 | + left: 39px; | |
| 4 | +} | |
| 5 | + | |
| 6 | +.profile-info-editable { | |
| 7 | + top: 151px; | |
| 8 | + width: 103px; | |
| 9 | + height: 28px; | |
| 10 | +} | |
| 11 | + | |
| 12 | +.profile-info-editable .upload-button { | |
| 13 | + font-size: 0.8em; | |
| 14 | + padding-top: 9px; | |
| 15 | + padding-left: 6px; | |
| 16 | + font-weight: bold; | |
| 17 | +} | |
| 18 | + | |
| 19 | +.profile-info-extrainfo { | |
| 20 | + margin-top: 10px; | |
| 21 | +} | |
| 0 | 22 | \ No newline at end of file | ... | ... |
src/languages/en.json
| ... | ... | @@ -23,6 +23,7 @@ |
| 23 | 23 | "profile.others_info": "Others", |
| 24 | 24 | "profile.community.title": "Community", |
| 25 | 25 | "profile.person.title": "Person", |
| 26 | + "profile.image.upload": "Upload Photo", | |
| 26 | 27 | "activities.title": "Activities", |
| 27 | 28 | "activities.create_article.description": "has published on", |
| 28 | 29 | "activities.scrap.description": "wrote in its timeline", | ... | ... |
src/languages/pt.json
| ... | ... | @@ -23,6 +23,7 @@ |
| 23 | 23 | "profile.others_info": "Outras informações", |
| 24 | 24 | "profile.community.title": "Comunidade", |
| 25 | 25 | "profile.person.title": "Pessoa", |
| 26 | + "profile.image.upload": "Enviar photo", | |
| 26 | 27 | "activities.title": "Atividades", |
| 27 | 28 | "activities.create_article.description": "publicou em", |
| 28 | 29 | "activities.scrap.description": "escreveu em sua linha do tempo", | ... | ... |