]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/commitdiff
Refactor my actor avatar edit
authorChocobozzz <me@florianbigard.com>
Mon, 6 Mar 2023 10:39:23 +0000 (11:39 +0100)
committerChocobozzz <me@florianbigard.com>
Tue, 7 Mar 2023 09:18:32 +0000 (10:18 +0100)
client/src/app/+manage/video-channel-edit/video-channel-update.component.ts
client/src/app/+my-account/my-account-settings/my-account-settings.component.ts
client/src/app/shared/shared-actor-image-edit/actor-avatar-edit.component.html
client/src/app/shared/shared-actor-image-edit/actor-avatar-edit.component.scss
client/src/app/shared/shared-actor-image-edit/actor-avatar-edit.component.ts
client/src/app/shared/shared-actor-image-edit/actor-banner-edit.component.html
client/src/app/shared/shared-actor-image-edit/actor-banner-edit.component.scss
client/src/app/shared/shared-actor-image-edit/actor-image-edit.scss
client/src/app/shared/shared-actor-image/actor-avatar.component.ts
client/src/sass/bootstrap.scss
shared/core-utils/common/object.ts

index 32f6d650d9d24d882b6b792c239cb625cc309669..3326a15052fb12457bd0287f139c26c6e2dee6c3 100644 (file)
@@ -13,6 +13,7 @@ import { FormReactiveService } from '@app/shared/shared-forms'
 import { VideoChannel, VideoChannelService } from '@app/shared/shared-main'
 import { HTMLServerConfig, VideoChannelUpdate } from '@shared/models'
 import { VideoChannelEdit } from './video-channel-edit'
+import { shallowCopy } from '@shared/core-utils'
 
 @Component({
   selector: 'my-video-channel-update',
@@ -118,6 +119,9 @@ export class VideoChannelUpdateComponent extends VideoChannelEdit implements OnI
             this.notifier.success($localize`Avatar changed.`)
 
             this.videoChannel.updateAvatar(data.avatars)
+
+            // So my-actor-avatar component detects changes
+            this.videoChannel = shallowCopy(this.videoChannel)
           },
 
           error: (err: HttpErrorResponse) => genericUploadErrorHandler({
@@ -135,6 +139,9 @@ export class VideoChannelUpdateComponent extends VideoChannelEdit implements OnI
                                 this.notifier.success($localize`Avatar deleted.`)
 
                                 this.videoChannel.resetAvatar()
+
+                                // So my-actor-avatar component detects changes
+                                this.videoChannel = shallowCopy(this.videoChannel)
                               },
 
                               error: err => this.notifier.error(err.message)
index 577f4a252a7d31e5ee8e03595962b5ba1544be6c..a276bb12624c3783b1f98ed25498e35a6d5e3c6b 100644 (file)
@@ -3,6 +3,7 @@ import { HttpErrorResponse } from '@angular/common/http'
 import { AfterViewChecked, Component, OnInit } from '@angular/core'
 import { AuthService, Notifier, User, UserService } from '@app/core'
 import { genericUploadErrorHandler } from '@app/helpers'
+import { shallowCopy } from '@shared/core-utils'
 
 @Component({
   selector: 'my-account-settings',
@@ -44,6 +45,9 @@ export class MyAccountSettingsComponent implements OnInit, AfterViewChecked {
           this.notifier.success($localize`Avatar changed.`)
 
           this.user.updateAccountAvatar(data.avatars)
+
+          // So my-actor-avatar component detects changes
+          this.user.account = shallowCopy(this.user.account)
         },
 
         error: (err: HttpErrorResponse) => genericUploadErrorHandler({
@@ -57,10 +61,13 @@ export class MyAccountSettingsComponent implements OnInit, AfterViewChecked {
   onAvatarDelete () {
     this.userService.deleteAvatar()
       .subscribe({
-        next: data => {
+        next: () => {
           this.notifier.success($localize`Avatar deleted.`)
 
           this.user.updateAccountAvatar()
+
+          // So my-actor-avatar component detects changes
+          this.user.account = shallowCopy(this.user.account)
         },
 
         error: (err: HttpErrorResponse) => this.notifier.error(err.message)
index 6459c5ffedbc222d31e8ea43300aec13fff0c02a..a0f65a3d9b445be12181e41122bcaad53c39a9e8 100644 (file)
@@ -1,23 +1,32 @@
 <div class="actor" *ngIf="actor">
-  <div class="d-flex">
+  <div class="position-relative me-3">
     <my-actor-avatar [actor]="actor" [actorType]="getActorType()" [previewImage]="preview" size="100"></my-actor-avatar>
 
-    <div class="actor-img-edit-container">
-
-      <div *ngIf="editable && !hasAvatar()" class="actor-img-edit-button" [ngbTooltip]="avatarFormat" placement="right" container="body">
-        <my-global-icon iconName="upload"></my-global-icon>
-        <label class="visually-hidden" for="avatarfile" i18n>Upload a new avatar</label>
-        <input #avatarfileInput type="file" name="avatarfile" id="avatarfile" [accept]="avatarExtensions" (change)="onAvatarChange(avatarfileInput)"/>
-      </div>
+    <div *ngIf="editable && !hasAvatar()" class="actor-img-edit-button" [ngbTooltip]="avatarFormat" placement="right" container="body">
+      <my-global-icon iconName="upload"></my-global-icon>
+      <label class="visually-hidden" for="avatarfile" i18n>Upload a new avatar</label>
+      <input #avatarfileInput type="file" name="avatarfile" id="avatarfile" [accept]="avatarExtensions" (change)="onAvatarChange(avatarfileInput)"/>
+    </div>
 
-      <div
-        *ngIf="editable && hasAvatar()" class="actor-img-edit-button"
-        #avatarPopover="ngbPopover" [ngbPopover]="avatarEditContent" popoverClass="popover-image-info" autoClose="outside" placement="right"
-      >
+    <div *ngIf="editable && hasAvatar()" ngbDropdown placement="right">
+      <div class="actor-img-edit-button" ngbDropdownToggle>
         <my-global-icon iconName="edit"></my-global-icon>
         <label class="visually-hidden" for="avatarMenu" i18n>Change your avatar</label>
       </div>
 
+      <div ngbDropdownMenu>
+        <div class="dropdown-item c-hand dropdown-file" [ngbTooltip]="avatarFormat">
+          <my-global-icon iconName="upload"></my-global-icon>
+          <span for="avatarfile" i18n>Upload a new avatar</span>
+          <input #avatarfileInput type="file" name="avatarfile" id="avatarfile" [accept]="avatarExtensions" (change)="onAvatarChange(avatarfileInput)"/>
+        </div>
+
+        <div class="dropdown-item c-hand" (click)="deleteAvatar()" (key.enter)="deleteAvatar()">
+          <my-global-icon iconName="delete"></my-global-icon>
+          <span i18n>Remove avatar</span>
+        </div>
+      </div>
+
     </div>
   </div>
 
     <div *ngIf="displaySubscribers" i18n class="actor-info-followers">{{ actor.followersCount }} subscribers</div>
   </div>
 </div>
-
-<ng-template #avatarEditContent>
-  <div class="dropdown-item c-hand" [ngbTooltip]="avatarFormat" placement="right" container="body">
-    <my-global-icon iconName="upload"></my-global-icon>
-    <span for="avatarfile" i18n>Upload a new avatar</span>
-    <input #avatarfileInput type="file" name="avatarfile" id="avatarfile" [accept]="avatarExtensions" (change)="onAvatarChange(avatarfileInput)"/>
-  </div>
-
-  <div class="dropdown-item c-hand" (click)="deleteAvatar()" (key.enter)="deleteAvatar()">
-    <my-global-icon iconName="delete"></my-global-icon>
-    <span i18n>Remove avatar</span>
-  </div>
-</ng-template>
index fd8cd7ffccc19f54dc9d0ae0acb9d5d8e5ac7e35..01e2131ba7365ac8f03464bb5777c2bd399de57a 100644 (file)
@@ -5,10 +5,6 @@
   display: flex;
 }
 
-my-actor-avatar {
-  @include margin-right(15px);
-}
-
 .actor-info {
   display: inline-flex;
   flex-direction: column;
@@ -16,12 +12,12 @@ my-actor-avatar {
 
 .actor-info-display-name {
   @include peertube-word-wrap;
+  @include font-size(1.25rem);
 
-  font-size: 20px;
   font-weight: $font-bold;
 
   @media screen and (max-width: $small-view) {
-    font-size: 16px;
+    @include font-size(18px);
   }
 }
 
@@ -35,17 +31,18 @@ my-actor-avatar {
   padding-bottom: .5rem;
 }
 
-.actor-img-edit-container {
-  position: relative;
-  width: 0;
-}
-
 .actor-img-edit-button {
-  top: 55px;
-  right: 45px;
   border-radius: 50%;
+
+  position: absolute;
+  bottom: 5px;
+  right: 5px;
 }
 
 .dropdown-item {
   @include dropdown-with-icon-item;
 }
+
+.dropdown-toggle::after {
+  display: none;
+}
index b71a3c485b3daa53d1e890e435cae490443aff73..fc925083ea4e67555edf899ed394fb1b972bb286 100644 (file)
@@ -1,7 +1,6 @@
 import { Component, ElementRef, EventEmitter, Input, OnInit, Output, ViewChild } from '@angular/core'
 import { Notifier, ServerService } from '@app/core'
 import { Account, VideoChannel } from '@app/shared/shared-main'
-import { NgbPopover } from '@ng-bootstrap/ng-bootstrap'
 import { getBytes } from '@root-helpers/bytes'
 import { imageToDataURL } from '@root-helpers/images'
 
@@ -15,7 +14,6 @@ import { imageToDataURL } from '@root-helpers/images'
 })
 export class ActorAvatarEditComponent implements OnInit {
   @ViewChild('avatarfileInput') avatarfileInput: ElementRef<HTMLInputElement>
-  @ViewChild('avatarPopover') avatarPopover: NgbPopover
 
   @Input() actor: VideoChannel | Account
   @Input() editable = true
@@ -58,7 +56,6 @@ export class ActorAvatarEditComponent implements OnInit {
 
     const formData = new FormData()
     formData.append('avatarfile', avatarfile)
-    this.avatarPopover?.close()
     this.avatarChange.emit(formData)
 
     if (this.previewImage) {
index f675371d9c5679c004d812607f80e3733ec44a49..d6fe3709493185f4d37cc8bf6e089fa03e37fa5a 100644 (file)
@@ -8,26 +8,25 @@
       <ng-container *ngTemplateOutlet="uploadNewBanner"></ng-container>
     </div>
 
-    <div
-      *ngIf="hasBanner()" class="actor-img-edit-button"
-      #bannerPopover="ngbPopover" [ngbPopover]="bannerEditContent" popoverClass="popover-image-info" autoClose="outside" placement="right"
-    >
-      <my-global-icon iconName="edit"></my-global-icon>
-      <label for="bannerMenu" i18n>Change your banner</label>
-    </div>
-  </div>
-</div>
+    <div *ngIf="hasBanner()" ngbDropdown placement="right">
+      <div class="actor-img-edit-button" ngbDropdownToggle>
+        <my-global-icon iconName="edit"></my-global-icon>
+        <label for="bannerMenu" i18n>Change your banner</label>
+      </div>
 
-<ng-template #bannerEditContent>
-  <div class="dropdown-item c-hand" [ngbTooltip]="bannerFormat" placement="right" container="body">
-    <ng-container *ngTemplateOutlet="uploadNewBanner"></ng-container>
-  </div>
+      <div ngbDropdownMenu>
+        <div class="dropdown-item c-hand dropdown-file" [ngbTooltip]="bannerFormat">
+          <ng-container *ngTemplateOutlet="uploadNewBanner"></ng-container>
+        </div>
 
-  <div class="dropdown-item c-hand" (click)="deleteBanner()" (key.enter)="deleteBanner()">
-    <my-global-icon iconName="delete"></my-global-icon>
-    <span i18n>Remove banner</span>
+        <div class="dropdown-item c-hand" (click)="deleteBanner()" (key.enter)="deleteBanner()">
+          <my-global-icon iconName="delete"></my-global-icon>
+          <span i18n>Remove banner</span>
+        </div>
+      </div>
+    </div>
   </div>
-</ng-template>
+</div>
 
 <ng-template #uploadNewBanner>
   <my-global-icon iconName="upload"></my-global-icon>
index ec2de252881485f13adb3831f3d08022de7cd949..b2c64fff79b86125fb4c674cd2d4800f35443bba 100644 (file)
   align-items: center;
 }
 
-.actor-img-edit-button {
+.dropdown {
   position: absolute;
+
+  > .actor-img-edit-button {
+    position: relative;
+  }
+}
+
+.actor-img-edit-button {
   width: auto;
+  position: absolute;
 
   label {
     font-weight: $font-semibold;
     margin-bottom: 0;
   }
 }
+
+.dropdown-item {
+  @include dropdown-with-icon-item;
+}
+
+.dropdown-toggle::after {
+  display: none;
+}
index b054086e4d53aa23150d5cc1c24f2191dd4a42a9..9e4ff26542e933344b13642354eea85e51b331e0 100644 (file)
@@ -1,18 +1,8 @@
 @use '_variables' as *;
 @use '_mixins' as *;
 
-.actor ::ng-deep .popover-image-info .popover-body {
-  padding: 0;
-
-  .dropdown-item {
-    padding: 6px 10px;
-    border-radius: 4px;
-
-    &:first-child {
-      @include peertube-file;
-      display: block;
-    }
-  }
+.dropdown-file {
+  @include peertube-file;
 }
 
 .actor-img-edit-button {
@@ -22,8 +12,6 @@
 
   display: flex;
   justify-content: center;
-  margin-top: 10px;
-  margin-bottom: 5px;
   cursor: pointer;
 
   input {
index 6036123f93fd8b131fdceccd742a2892da727948..a52e68a176614f856e4823f89bf0b83194512df5 100644 (file)
@@ -50,15 +50,15 @@ export class ActorAvatarComponent implements OnInit, OnChanges {
   ngOnInit () {
     this.buildDefaultAvatarUrl()
 
-    this.buildClasses()
     this.buildAlt()
     this.buildAvatarUrl()
+    this.buildClasses()
   }
 
   ngOnChanges () {
-    this.buildClasses()
     this.buildAlt()
     this.buildAvatarUrl()
+    this.buildClasses()
   }
 
   private buildClasses () {
@@ -114,12 +114,13 @@ export class ActorAvatarComponent implements OnInit, OnChanges {
 
   displayImage () {
     if (this.actorType === 'unlogged') return true
+    if (this.previewImage) return true
 
     return !!(this.actor && this.avatarUrl)
   }
 
   displayActorInitial () {
-    return this.actor && !this.avatarUrl
+    return !this.displayImage() && this.actor && !this.avatarUrl
   }
 
   displayPlaceholder () {
index 3b847c75b8f17a17eaa60dae32ed547ceeec4b17..4d956d65216e70ce03754797680b52f7fc3ff28a 100644 (file)
@@ -30,7 +30,7 @@
 @import 'bootstrap/scss/helpers';
 @import 'bootstrap/scss/utilities/api';
 
-:root {
+body {
   --bs-border-color-translucent: #{pvar(--inputBorderColor)};
 }
 
index 2330c94038da1f436878f012bdc2f5e377921809..7f1f147f4b3870f49680db9f7cfc0a991e54b758 100644 (file)
@@ -41,9 +41,14 @@ function sortObjectComparator (key: string, order: 'asc' | 'desc') {
   }
 }
 
+function shallowCopy <T> (o: T): T {
+  return Object.assign(Object.create(Object.getPrototypeOf(o)), o)
+}
+
 export {
   pick,
   omit,
   getKeys,
+  shallowCopy,
   sortObjectComparator
 }