aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--CHANGELOG.md1
-rw-r--r--client/src/app/+admin/overview/users/user-edit/user-edit.component.html2
-rw-r--r--client/src/app/+admin/overview/users/user-edit/user-password.component.ts6
-rw-r--r--client/src/app/+manage/video-channel-edit/video-channel-update.component.ts7
-rw-r--r--client/src/app/+my-account/my-account-settings/my-account-settings.component.ts9
-rw-r--r--client/src/app/+videos/video-list/overview/video-overview.component.scss12
-rw-r--r--client/src/app/shared/shared-actor-image-edit/actor-avatar-edit.component.html46
-rw-r--r--client/src/app/shared/shared-actor-image-edit/actor-avatar-edit.component.scss23
-rw-r--r--client/src/app/shared/shared-actor-image-edit/actor-avatar-edit.component.ts3
-rw-r--r--client/src/app/shared/shared-actor-image-edit/actor-banner-edit.component.html33
-rw-r--r--client/src/app/shared/shared-actor-image-edit/actor-banner-edit.component.scss18
-rw-r--r--client/src/app/shared/shared-actor-image-edit/actor-image-edit.scss16
-rw-r--r--client/src/app/shared/shared-actor-image/actor-avatar.component.html2
-rw-r--r--client/src/app/shared/shared-actor-image/actor-avatar.component.ts16
-rw-r--r--client/src/sass/bootstrap.scss2
-rw-r--r--client/src/sass/include/_mixins.scss6
-rw-r--r--server/models/user/user.ts4
-rw-r--r--server/tests/cli/create-move-video-storage-job.ts11
-rw-r--r--shared/core-utils/common/object.ts5
-rw-r--r--support/docker/production/config/custom-environment-variables.yaml9
20 files changed, 129 insertions, 102 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index d6b0f9c97..84f9c0aae 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,6 +4,7 @@
4 4
5### IMPORTANT NOTES 5### IMPORTANT NOTES
6 6
7 * If your instance has signup enabled, user registration approval is automatically enabled by the default configuration of this release. You can change this setting in your `production.yaml` or in the configuration page in the web admin
7 * Update [web browsers support list](https://joinpeertube.org/faq#what-web-browsers-are-supported-by-peertube): 8 * Update [web browsers support list](https://joinpeertube.org/faq#what-web-browsers-are-supported-by-peertube):
8 * Drop support of Safari 11 on iOS 9 * Drop support of Safari 11 on iOS
9 * Drop support of Safari 11 on desktop 10 * Drop support of Safari 11 on desktop
diff --git a/client/src/app/+admin/overview/users/user-edit/user-edit.component.html b/client/src/app/+admin/overview/users/user-edit/user-edit.component.html
index ecbcfe72f..278bd8c48 100644
--- a/client/src/app/+admin/overview/users/user-edit/user-edit.component.html
+++ b/client/src/app/+admin/overview/users/user-edit/user-edit.component.html
@@ -224,7 +224,7 @@
224 224
225 <div class="form-group"> 225 <div class="form-group">
226 <label i18n>Manually set the user password</label> 226 <label i18n>Manually set the user password</label>
227 <my-user-password [userId]="user.id"></my-user-password> 227 <my-user-password [userId]="user.id" [username]="user.username"></my-user-password>
228 </div> 228 </div>
229 229
230 <div *ngIf="user.twoFactorEnabled" class="form-group"> 230 <div *ngIf="user.twoFactorEnabled" class="form-group">
diff --git a/client/src/app/+admin/overview/users/user-edit/user-password.component.ts b/client/src/app/+admin/overview/users/user-edit/user-password.component.ts
index d6616e077..ec93619f5 100644
--- a/client/src/app/+admin/overview/users/user-edit/user-password.component.ts
+++ b/client/src/app/+admin/overview/users/user-edit/user-password.component.ts
@@ -11,12 +11,12 @@ import { UserUpdate } from '@shared/models'
11 styleUrls: [ './user-password.component.scss' ] 11 styleUrls: [ './user-password.component.scss' ]
12}) 12})
13export class UserPasswordComponent extends FormReactive implements OnInit { 13export class UserPasswordComponent extends FormReactive implements OnInit {
14 @Input() userId: number
15 @Input() username: string
16
14 error: string 17 error: string
15 username: string
16 showPassword = false 18 showPassword = false
17 19
18 @Input() userId: number
19
20 constructor ( 20 constructor (
21 protected formReactiveService: FormReactiveService, 21 protected formReactiveService: FormReactiveService,
22 private notifier: Notifier, 22 private notifier: Notifier,
diff --git a/client/src/app/+manage/video-channel-edit/video-channel-update.component.ts b/client/src/app/+manage/video-channel-edit/video-channel-update.component.ts
index 32f6d650d..3326a1505 100644
--- a/client/src/app/+manage/video-channel-edit/video-channel-update.component.ts
+++ b/client/src/app/+manage/video-channel-edit/video-channel-update.component.ts
@@ -13,6 +13,7 @@ import { FormReactiveService } from '@app/shared/shared-forms'
13import { VideoChannel, VideoChannelService } from '@app/shared/shared-main' 13import { VideoChannel, VideoChannelService } from '@app/shared/shared-main'
14import { HTMLServerConfig, VideoChannelUpdate } from '@shared/models' 14import { HTMLServerConfig, VideoChannelUpdate } from '@shared/models'
15import { VideoChannelEdit } from './video-channel-edit' 15import { VideoChannelEdit } from './video-channel-edit'
16import { shallowCopy } from '@shared/core-utils'
16 17
17@Component({ 18@Component({
18 selector: 'my-video-channel-update', 19 selector: 'my-video-channel-update',
@@ -118,6 +119,9 @@ export class VideoChannelUpdateComponent extends VideoChannelEdit implements OnI
118 this.notifier.success($localize`Avatar changed.`) 119 this.notifier.success($localize`Avatar changed.`)
119 120
120 this.videoChannel.updateAvatar(data.avatars) 121 this.videoChannel.updateAvatar(data.avatars)
122
123 // So my-actor-avatar component detects changes
124 this.videoChannel = shallowCopy(this.videoChannel)
121 }, 125 },
122 126
123 error: (err: HttpErrorResponse) => genericUploadErrorHandler({ 127 error: (err: HttpErrorResponse) => genericUploadErrorHandler({
@@ -135,6 +139,9 @@ export class VideoChannelUpdateComponent extends VideoChannelEdit implements OnI
135 this.notifier.success($localize`Avatar deleted.`) 139 this.notifier.success($localize`Avatar deleted.`)
136 140
137 this.videoChannel.resetAvatar() 141 this.videoChannel.resetAvatar()
142
143 // So my-actor-avatar component detects changes
144 this.videoChannel = shallowCopy(this.videoChannel)
138 }, 145 },
139 146
140 error: err => this.notifier.error(err.message) 147 error: err => this.notifier.error(err.message)
diff --git a/client/src/app/+my-account/my-account-settings/my-account-settings.component.ts b/client/src/app/+my-account/my-account-settings/my-account-settings.component.ts
index 577f4a252..a276bb126 100644
--- a/client/src/app/+my-account/my-account-settings/my-account-settings.component.ts
+++ b/client/src/app/+my-account/my-account-settings/my-account-settings.component.ts
@@ -3,6 +3,7 @@ import { HttpErrorResponse } from '@angular/common/http'
3import { AfterViewChecked, Component, OnInit } from '@angular/core' 3import { AfterViewChecked, Component, OnInit } from '@angular/core'
4import { AuthService, Notifier, User, UserService } from '@app/core' 4import { AuthService, Notifier, User, UserService } from '@app/core'
5import { genericUploadErrorHandler } from '@app/helpers' 5import { genericUploadErrorHandler } from '@app/helpers'
6import { shallowCopy } from '@shared/core-utils'
6 7
7@Component({ 8@Component({
8 selector: 'my-account-settings', 9 selector: 'my-account-settings',
@@ -44,6 +45,9 @@ export class MyAccountSettingsComponent implements OnInit, AfterViewChecked {
44 this.notifier.success($localize`Avatar changed.`) 45 this.notifier.success($localize`Avatar changed.`)
45 46
46 this.user.updateAccountAvatar(data.avatars) 47 this.user.updateAccountAvatar(data.avatars)
48
49 // So my-actor-avatar component detects changes
50 this.user.account = shallowCopy(this.user.account)
47 }, 51 },
48 52
49 error: (err: HttpErrorResponse) => genericUploadErrorHandler({ 53 error: (err: HttpErrorResponse) => genericUploadErrorHandler({
@@ -57,10 +61,13 @@ export class MyAccountSettingsComponent implements OnInit, AfterViewChecked {
57 onAvatarDelete () { 61 onAvatarDelete () {
58 this.userService.deleteAvatar() 62 this.userService.deleteAvatar()
59 .subscribe({ 63 .subscribe({
60 next: data => { 64 next: () => {
61 this.notifier.success($localize`Avatar deleted.`) 65 this.notifier.success($localize`Avatar deleted.`)
62 66
63 this.user.updateAccountAvatar() 67 this.user.updateAccountAvatar()
68
69 // So my-actor-avatar component detects changes
70 this.user.account = shallowCopy(this.user.account)
64 }, 71 },
65 72
66 error: (err: HttpErrorResponse) => this.notifier.error(err.message) 73 error: (err: HttpErrorResponse) => this.notifier.error(err.message)
diff --git a/client/src/app/+videos/video-list/overview/video-overview.component.scss b/client/src/app/+videos/video-list/overview/video-overview.component.scss
index 5a789b66d..b1b42b517 100644
--- a/client/src/app/+videos/video-list/overview/video-overview.component.scss
+++ b/client/src/app/+videos/video-list/overview/video-overview.component.scss
@@ -21,9 +21,9 @@
21 } 21 }
22 22
23 .section-title { 23 .section-title {
24 font-size: 24px; 24 @include font-size(1.5rem);
25 padding-top: 20px; 25 @include padding-top(1.25rem);
26 margin-bottom: 30px; 26 @include margin-bottom(2rem);
27 27
28 &:not(h2) { 28 &:not(h2) {
29 border-top: 1px solid $separator-border-color; 29 border-top: 1px solid $separator-border-color;
@@ -38,8 +38,8 @@
38 my-actor-avatar { 38 my-actor-avatar {
39 @include margin-right(8px); 39 @include margin-right(8px);
40 40
41 position: relative; 41 display: inline-block;
42 top: -2px; 42 vertical-align: text-top;
43 } 43 }
44 } 44 }
45 45
@@ -49,8 +49,6 @@
49 49
50 .section-title { 50 .section-title {
51 @include margin-left(10px); 51 @include margin-left(10px);
52
53 font-size: 17px;
54 } 52 }
55 } 53 }
56} 54}
diff --git a/client/src/app/shared/shared-actor-image-edit/actor-avatar-edit.component.html b/client/src/app/shared/shared-actor-image-edit/actor-avatar-edit.component.html
index 6459c5ffe..a0f65a3d9 100644
--- a/client/src/app/shared/shared-actor-image-edit/actor-avatar-edit.component.html
+++ b/client/src/app/shared/shared-actor-image-edit/actor-avatar-edit.component.html
@@ -1,23 +1,32 @@
1<div class="actor" *ngIf="actor"> 1<div class="actor" *ngIf="actor">
2 <div class="d-flex"> 2 <div class="position-relative me-3">
3 <my-actor-avatar [actor]="actor" [actorType]="getActorType()" [previewImage]="preview" size="100"></my-actor-avatar> 3 <my-actor-avatar [actor]="actor" [actorType]="getActorType()" [previewImage]="preview" size="100"></my-actor-avatar>
4 4
5 <div class="actor-img-edit-container"> 5 <div *ngIf="editable && !hasAvatar()" class="actor-img-edit-button" [ngbTooltip]="avatarFormat" placement="right" container="body">
6 6 <my-global-icon iconName="upload"></my-global-icon>
7 <div *ngIf="editable && !hasAvatar()" class="actor-img-edit-button" [ngbTooltip]="avatarFormat" placement="right" container="body"> 7 <label class="visually-hidden" for="avatarfile" i18n>Upload a new avatar</label>
8 <my-global-icon iconName="upload"></my-global-icon> 8 <input #avatarfileInput type="file" name="avatarfile" id="avatarfile" [accept]="avatarExtensions" (change)="onAvatarChange(avatarfileInput)"/>
9 <label class="visually-hidden" for="avatarfile" i18n>Upload a new avatar</label> 9 </div>
10 <input #avatarfileInput type="file" name="avatarfile" id="avatarfile" [accept]="avatarExtensions" (change)="onAvatarChange(avatarfileInput)"/>
11 </div>
12 10
13 <div 11 <div *ngIf="editable && hasAvatar()" ngbDropdown placement="right">
14 *ngIf="editable && hasAvatar()" class="actor-img-edit-button" 12 <div class="actor-img-edit-button" ngbDropdownToggle>
15 #avatarPopover="ngbPopover" [ngbPopover]="avatarEditContent" popoverClass="popover-image-info" autoClose="outside" placement="right"
16 >
17 <my-global-icon iconName="edit"></my-global-icon> 13 <my-global-icon iconName="edit"></my-global-icon>
18 <label class="visually-hidden" for="avatarMenu" i18n>Change your avatar</label> 14 <label class="visually-hidden" for="avatarMenu" i18n>Change your avatar</label>
19 </div> 15 </div>
20 16
17 <div ngbDropdownMenu>
18 <div class="dropdown-item c-hand dropdown-file" [ngbTooltip]="avatarFormat">
19 <my-global-icon iconName="upload"></my-global-icon>
20 <span for="avatarfile" i18n>Upload a new avatar</span>
21 <input #avatarfileInput type="file" name="avatarfile" id="avatarfile" [accept]="avatarExtensions" (change)="onAvatarChange(avatarfileInput)"/>
22 </div>
23
24 <div class="dropdown-item c-hand" (click)="deleteAvatar()" (key.enter)="deleteAvatar()">
25 <my-global-icon iconName="delete"></my-global-icon>
26 <span i18n>Remove avatar</span>
27 </div>
28 </div>
29
21 </div> 30 </div>
22 </div> 31 </div>
23 32
@@ -27,16 +36,3 @@
27 <div *ngIf="displaySubscribers" i18n class="actor-info-followers">{{ actor.followersCount }} subscribers</div> 36 <div *ngIf="displaySubscribers" i18n class="actor-info-followers">{{ actor.followersCount }} subscribers</div>
28 </div> 37 </div>
29</div> 38</div>
30
31<ng-template #avatarEditContent>
32 <div class="dropdown-item c-hand" [ngbTooltip]="avatarFormat" placement="right" container="body">
33 <my-global-icon iconName="upload"></my-global-icon>
34 <span for="avatarfile" i18n>Upload a new avatar</span>
35 <input #avatarfileInput type="file" name="avatarfile" id="avatarfile" [accept]="avatarExtensions" (change)="onAvatarChange(avatarfileInput)"/>
36 </div>
37
38 <div class="dropdown-item c-hand" (click)="deleteAvatar()" (key.enter)="deleteAvatar()">
39 <my-global-icon iconName="delete"></my-global-icon>
40 <span i18n>Remove avatar</span>
41 </div>
42</ng-template>
diff --git a/client/src/app/shared/shared-actor-image-edit/actor-avatar-edit.component.scss b/client/src/app/shared/shared-actor-image-edit/actor-avatar-edit.component.scss
index fd8cd7ffc..01e2131ba 100644
--- a/client/src/app/shared/shared-actor-image-edit/actor-avatar-edit.component.scss
+++ b/client/src/app/shared/shared-actor-image-edit/actor-avatar-edit.component.scss
@@ -5,10 +5,6 @@
5 display: flex; 5 display: flex;
6} 6}
7 7
8my-actor-avatar {
9 @include margin-right(15px);
10}
11
12.actor-info { 8.actor-info {
13 display: inline-flex; 9 display: inline-flex;
14 flex-direction: column; 10 flex-direction: column;
@@ -16,12 +12,12 @@ my-actor-avatar {
16 12
17.actor-info-display-name { 13.actor-info-display-name {
18 @include peertube-word-wrap; 14 @include peertube-word-wrap;
15 @include font-size(1.25rem);
19 16
20 font-size: 20px;
21 font-weight: $font-bold; 17 font-weight: $font-bold;
22 18
23 @media screen and (max-width: $small-view) { 19 @media screen and (max-width: $small-view) {
24 font-size: 16px; 20 @include font-size(18px);
25 } 21 }
26} 22}
27 23
@@ -35,17 +31,18 @@ my-actor-avatar {
35 padding-bottom: .5rem; 31 padding-bottom: .5rem;
36} 32}
37 33
38.actor-img-edit-container {
39 position: relative;
40 width: 0;
41}
42
43.actor-img-edit-button { 34.actor-img-edit-button {
44 top: 55px;
45 right: 45px;
46 border-radius: 50%; 35 border-radius: 50%;
36
37 position: absolute;
38 bottom: 5px;
39 right: 5px;
47} 40}
48 41
49.dropdown-item { 42.dropdown-item {
50 @include dropdown-with-icon-item; 43 @include dropdown-with-icon-item;
51} 44}
45
46.dropdown-toggle::after {
47 display: none;
48}
diff --git a/client/src/app/shared/shared-actor-image-edit/actor-avatar-edit.component.ts b/client/src/app/shared/shared-actor-image-edit/actor-avatar-edit.component.ts
index b71a3c485..fc925083e 100644
--- a/client/src/app/shared/shared-actor-image-edit/actor-avatar-edit.component.ts
+++ b/client/src/app/shared/shared-actor-image-edit/actor-avatar-edit.component.ts
@@ -1,7 +1,6 @@
1import { Component, ElementRef, EventEmitter, Input, OnInit, Output, ViewChild } from '@angular/core' 1import { Component, ElementRef, EventEmitter, Input, OnInit, Output, ViewChild } from '@angular/core'
2import { Notifier, ServerService } from '@app/core' 2import { Notifier, ServerService } from '@app/core'
3import { Account, VideoChannel } from '@app/shared/shared-main' 3import { Account, VideoChannel } from '@app/shared/shared-main'
4import { NgbPopover } from '@ng-bootstrap/ng-bootstrap'
5import { getBytes } from '@root-helpers/bytes' 4import { getBytes } from '@root-helpers/bytes'
6import { imageToDataURL } from '@root-helpers/images' 5import { imageToDataURL } from '@root-helpers/images'
7 6
@@ -15,7 +14,6 @@ import { imageToDataURL } from '@root-helpers/images'
15}) 14})
16export class ActorAvatarEditComponent implements OnInit { 15export class ActorAvatarEditComponent implements OnInit {
17 @ViewChild('avatarfileInput') avatarfileInput: ElementRef<HTMLInputElement> 16 @ViewChild('avatarfileInput') avatarfileInput: ElementRef<HTMLInputElement>
18 @ViewChild('avatarPopover') avatarPopover: NgbPopover
19 17
20 @Input() actor: VideoChannel | Account 18 @Input() actor: VideoChannel | Account
21 @Input() editable = true 19 @Input() editable = true
@@ -58,7 +56,6 @@ export class ActorAvatarEditComponent implements OnInit {
58 56
59 const formData = new FormData() 57 const formData = new FormData()
60 formData.append('avatarfile', avatarfile) 58 formData.append('avatarfile', avatarfile)
61 this.avatarPopover?.close()
62 this.avatarChange.emit(formData) 59 this.avatarChange.emit(formData)
63 60
64 if (this.previewImage) { 61 if (this.previewImage) {
diff --git a/client/src/app/shared/shared-actor-image-edit/actor-banner-edit.component.html b/client/src/app/shared/shared-actor-image-edit/actor-banner-edit.component.html
index f675371d9..d6fe37094 100644
--- a/client/src/app/shared/shared-actor-image-edit/actor-banner-edit.component.html
+++ b/client/src/app/shared/shared-actor-image-edit/actor-banner-edit.component.html
@@ -8,26 +8,25 @@
8 <ng-container *ngTemplateOutlet="uploadNewBanner"></ng-container> 8 <ng-container *ngTemplateOutlet="uploadNewBanner"></ng-container>
9 </div> 9 </div>
10 10
11 <div 11 <div *ngIf="hasBanner()" ngbDropdown placement="right">
12 *ngIf="hasBanner()" class="actor-img-edit-button" 12 <div class="actor-img-edit-button" ngbDropdownToggle>
13 #bannerPopover="ngbPopover" [ngbPopover]="bannerEditContent" popoverClass="popover-image-info" autoClose="outside" placement="right" 13 <my-global-icon iconName="edit"></my-global-icon>
14 > 14 <label for="bannerMenu" i18n>Change your banner</label>
15 <my-global-icon iconName="edit"></my-global-icon> 15 </div>
16 <label for="bannerMenu" i18n>Change your banner</label>
17 </div>
18 </div>
19</div>
20 16
21<ng-template #bannerEditContent> 17 <div ngbDropdownMenu>
22 <div class="dropdown-item c-hand" [ngbTooltip]="bannerFormat" placement="right" container="body"> 18 <div class="dropdown-item c-hand dropdown-file" [ngbTooltip]="bannerFormat">
23 <ng-container *ngTemplateOutlet="uploadNewBanner"></ng-container> 19 <ng-container *ngTemplateOutlet="uploadNewBanner"></ng-container>
24 </div> 20 </div>
25 21
26 <div class="dropdown-item c-hand" (click)="deleteBanner()" (key.enter)="deleteBanner()"> 22 <div class="dropdown-item c-hand" (click)="deleteBanner()" (key.enter)="deleteBanner()">
27 <my-global-icon iconName="delete"></my-global-icon> 23 <my-global-icon iconName="delete"></my-global-icon>
28 <span i18n>Remove banner</span> 24 <span i18n>Remove banner</span>
25 </div>
26 </div>
27 </div>
29 </div> 28 </div>
30</ng-template> 29</div>
31 30
32<ng-template #uploadNewBanner> 31<ng-template #uploadNewBanner>
33 <my-global-icon iconName="upload"></my-global-icon> 32 <my-global-icon iconName="upload"></my-global-icon>
diff --git a/client/src/app/shared/shared-actor-image-edit/actor-banner-edit.component.scss b/client/src/app/shared/shared-actor-image-edit/actor-banner-edit.component.scss
index ec2de2528..b2c64fff7 100644
--- a/client/src/app/shared/shared-actor-image-edit/actor-banner-edit.component.scss
+++ b/client/src/app/shared/shared-actor-image-edit/actor-banner-edit.component.scss
@@ -16,12 +16,28 @@
16 align-items: center; 16 align-items: center;
17} 17}
18 18
19.actor-img-edit-button { 19.dropdown {
20 position: absolute; 20 position: absolute;
21
22 > .actor-img-edit-button {
23 position: relative;
24 }
25}
26
27.actor-img-edit-button {
21 width: auto; 28 width: auto;
29 position: absolute;
22 30
23 label { 31 label {
24 font-weight: $font-semibold; 32 font-weight: $font-semibold;
25 margin-bottom: 0; 33 margin-bottom: 0;
26 } 34 }
27} 35}
36
37.dropdown-item {
38 @include dropdown-with-icon-item;
39}
40
41.dropdown-toggle::after {
42 display: none;
43}
diff --git a/client/src/app/shared/shared-actor-image-edit/actor-image-edit.scss b/client/src/app/shared/shared-actor-image-edit/actor-image-edit.scss
index b054086e4..9e4ff2654 100644
--- a/client/src/app/shared/shared-actor-image-edit/actor-image-edit.scss
+++ b/client/src/app/shared/shared-actor-image-edit/actor-image-edit.scss
@@ -1,18 +1,8 @@
1@use '_variables' as *; 1@use '_variables' as *;
2@use '_mixins' as *; 2@use '_mixins' as *;
3 3
4.actor ::ng-deep .popover-image-info .popover-body { 4.dropdown-file {
5 padding: 0; 5 @include peertube-file;
6
7 .dropdown-item {
8 padding: 6px 10px;
9 border-radius: 4px;
10
11 &:first-child {
12 @include peertube-file;
13 display: block;
14 }
15 }
16} 6}
17 7
18.actor-img-edit-button { 8.actor-img-edit-button {
@@ -22,8 +12,6 @@
22 12
23 display: flex; 13 display: flex;
24 justify-content: center; 14 justify-content: center;
25 margin-top: 10px;
26 margin-bottom: 5px;
27 cursor: pointer; 15 cursor: pointer;
28 16
29 input { 17 input {
diff --git a/client/src/app/shared/shared-actor-image/actor-avatar.component.html b/client/src/app/shared/shared-actor-image/actor-avatar.component.html
index fb9efc20a..011afef27 100644
--- a/client/src/app/shared/shared-actor-image/actor-avatar.component.html
+++ b/client/src/app/shared/shared-actor-image/actor-avatar.component.html
@@ -1,5 +1,5 @@
1<ng-template #img> 1<ng-template #img>
2 <img *ngIf="displayImage()" [class]="classes" [src]="previewImage || avatarUrl || defaultAvatarUrl" [alt]="alt" /> 2 <img *ngIf="displayImage()" [class]="classes" [src]="previewImage || avatarUrl || defaultAvatarUrl" alt="" />
3 3
4 <div *ngIf="displayActorInitial()" [ngClass]="classes"> 4 <div *ngIf="displayActorInitial()" [ngClass]="classes">
5 <span>{{ getActorInitial() }}</span> 5 <span>{{ getActorInitial() }}</span>
diff --git a/client/src/app/shared/shared-actor-image/actor-avatar.component.ts b/client/src/app/shared/shared-actor-image/actor-avatar.component.ts
index 6036123f9..8e6ad4015 100644
--- a/client/src/app/shared/shared-actor-image/actor-avatar.component.ts
+++ b/client/src/app/shared/shared-actor-image/actor-avatar.component.ts
@@ -43,22 +43,19 @@ export class ActorAvatarComponent implements OnInit, OnChanges {
43 } 43 }
44 44
45 classes: string[] = [] 45 classes: string[] = []
46 alt: string
47 defaultAvatarUrl: string 46 defaultAvatarUrl: string
48 avatarUrl: string 47 avatarUrl: string
49 48
50 ngOnInit () { 49 ngOnInit () {
51 this.buildDefaultAvatarUrl() 50 this.buildDefaultAvatarUrl()
52 51
53 this.buildClasses()
54 this.buildAlt()
55 this.buildAvatarUrl() 52 this.buildAvatarUrl()
53 this.buildClasses()
56 } 54 }
57 55
58 ngOnChanges () { 56 ngOnChanges () {
59 this.buildClasses()
60 this.buildAlt()
61 this.buildAvatarUrl() 57 this.buildAvatarUrl()
58 this.buildClasses()
62 } 59 }
63 60
64 private buildClasses () { 61 private buildClasses () {
@@ -81,12 +78,6 @@ export class ActorAvatarComponent implements OnInit, OnChanges {
81 } 78 }
82 } 79 }
83 80
84 private buildAlt () {
85 if (this.isAccount()) this.alt = $localize`Account avatar`
86 else if (this.isChannel()) this.alt = $localize`Channel avatar`
87 else this.alt = ''
88 }
89
90 private buildDefaultAvatarUrl () { 81 private buildDefaultAvatarUrl () {
91 this.defaultAvatarUrl = this.isChannel() 82 this.defaultAvatarUrl = this.isChannel()
92 ? VideoChannel.GET_DEFAULT_AVATAR_URL(this.getSizeNumber()) 83 ? VideoChannel.GET_DEFAULT_AVATAR_URL(this.getSizeNumber())
@@ -114,12 +105,13 @@ export class ActorAvatarComponent implements OnInit, OnChanges {
114 105
115 displayImage () { 106 displayImage () {
116 if (this.actorType === 'unlogged') return true 107 if (this.actorType === 'unlogged') return true
108 if (this.previewImage) return true
117 109
118 return !!(this.actor && this.avatarUrl) 110 return !!(this.actor && this.avatarUrl)
119 } 111 }
120 112
121 displayActorInitial () { 113 displayActorInitial () {
122 return this.actor && !this.avatarUrl 114 return !this.displayImage() && this.actor && !this.avatarUrl
123 } 115 }
124 116
125 displayPlaceholder () { 117 displayPlaceholder () {
diff --git a/client/src/sass/bootstrap.scss b/client/src/sass/bootstrap.scss
index 3b847c75b..4d956d652 100644
--- a/client/src/sass/bootstrap.scss
+++ b/client/src/sass/bootstrap.scss
@@ -30,7 +30,7 @@
30@import 'bootstrap/scss/helpers'; 30@import 'bootstrap/scss/helpers';
31@import 'bootstrap/scss/utilities/api'; 31@import 'bootstrap/scss/utilities/api';
32 32
33:root { 33body {
34 --bs-border-color-translucent: #{pvar(--inputBorderColor)}; 34 --bs-border-color-translucent: #{pvar(--inputBorderColor)};
35} 35}
36 36
diff --git a/client/src/sass/include/_mixins.scss b/client/src/sass/include/_mixins.scss
index e41fe9389..0f301dab2 100644
--- a/client/src/sass/include/_mixins.scss
+++ b/client/src/sass/include/_mixins.scss
@@ -155,6 +155,7 @@
155@mixin orange-button-inverted { 155@mixin orange-button-inverted {
156 @include button-focus(pvar(--mainColorLightest)); 156 @include button-focus(pvar(--mainColorLightest));
157 157
158 padding: 2px 13px;
158 border: 2px solid pvar(--mainColor); 159 border: 2px solid pvar(--mainColor);
159 font-weight: $font-semibold; 160 font-weight: $font-semibold;
160 161
@@ -263,6 +264,7 @@
263 cursor: pointer; 264 cursor: pointer;
264 265
265 font-size: $button-font-size; 266 font-size: $button-font-size;
267 line-height: $button-font-size + math.round(math.div($button-font-size, 2));
266 268
267 my-global-icon + * { 269 my-global-icon + * {
268 @include margin-right(4px); 270 @include margin-right(4px);
@@ -312,6 +314,10 @@
312 width: $width; 314 width: $width;
313 top: $top; 315 top: $top;
314 } 316 }
317
318 span {
319 vertical-align: middle;
320 }
315} 321}
316 322
317@mixin peertube-file { 323@mixin peertube-file {
diff --git a/server/models/user/user.ts b/server/models/user/user.ts
index bfc9b3049..b6b120c92 100644
--- a/server/models/user/user.ts
+++ b/server/models/user/user.ts
@@ -781,12 +781,12 @@ export class UserModel extends Model<Partial<AttributesOnly<UserModel>>> {
781 `WHERE "account"."userId" = ${options.whereUserId} ${andWhere}` 781 `WHERE "account"."userId" = ${options.whereUserId} ${andWhere}`
782 782
783 const webtorrentFiles = 'SELECT "videoFile"."size" AS "size", "video"."id" AS "videoId" FROM "videoFile" ' + 783 const webtorrentFiles = 'SELECT "videoFile"."size" AS "size", "video"."id" AS "videoId" FROM "videoFile" ' +
784 'INNER JOIN "video" ON "videoFile"."videoId" = "video"."id" ' + 784 'INNER JOIN "video" ON "videoFile"."videoId" = "video"."id" AND "video"."isLive" IS FALSE ' +
785 videoChannelJoin 785 videoChannelJoin
786 786
787 const hlsFiles = 'SELECT "videoFile"."size" AS "size", "video"."id" AS "videoId" FROM "videoFile" ' + 787 const hlsFiles = 'SELECT "videoFile"."size" AS "size", "video"."id" AS "videoId" FROM "videoFile" ' +
788 'INNER JOIN "videoStreamingPlaylist" ON "videoFile"."videoStreamingPlaylistId" = "videoStreamingPlaylist".id ' + 788 'INNER JOIN "videoStreamingPlaylist" ON "videoFile"."videoStreamingPlaylistId" = "videoStreamingPlaylist".id ' +
789 'INNER JOIN "video" ON "videoStreamingPlaylist"."videoId" = "video"."id" ' + 789 'INNER JOIN "video" ON "videoStreamingPlaylist"."videoId" = "video"."id" AND "video"."isLive" IS FALSE ' +
790 videoChannelJoin 790 videoChannelJoin
791 791
792 return 'SELECT COALESCE(SUM("size"), 0) AS "total" ' + 792 return 'SELECT COALESCE(SUM("size"), 0) AS "total" ' +
diff --git a/server/tests/cli/create-move-video-storage-job.ts b/server/tests/cli/create-move-video-storage-job.ts
index c357f501b..4927e0309 100644
--- a/server/tests/cli/create-move-video-storage-job.ts
+++ b/server/tests/cli/create-move-video-storage-job.ts
@@ -1,5 +1,6 @@
1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */ 1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2 2
3import { join } from 'path'
3import { areMockObjectStorageTestsDisabled } from '@shared/core-utils' 4import { areMockObjectStorageTestsDisabled } from '@shared/core-utils'
4import { HttpStatusCode, VideoDetails } from '@shared/models' 5import { HttpStatusCode, VideoDetails } from '@shared/models'
5import { 6import {
@@ -12,7 +13,7 @@ import {
12 setAccessTokensToServers, 13 setAccessTokensToServers,
13 waitJobs 14 waitJobs
14} from '@shared/server-commands' 15} from '@shared/server-commands'
15import { expectStartWith } from '../shared' 16import { checkDirectoryIsEmpty, expectStartWith } from '../shared'
16 17
17async function checkFiles (origin: PeerTubeServer, video: VideoDetails, inObjectStorage: boolean) { 18async function checkFiles (origin: PeerTubeServer, video: VideoDetails, inObjectStorage: boolean) {
18 for (const file of video.files) { 19 for (const file of video.files) {
@@ -106,6 +107,14 @@ describe('Test create move video storage job', function () {
106 } 107 }
107 }) 108 })
108 109
110 it('Should not have files on disk anymore', async function () {
111 await checkDirectoryIsEmpty(servers[0], 'videos', [ 'private' ])
112 await checkDirectoryIsEmpty(servers[0], join('videos', 'private'))
113
114 await checkDirectoryIsEmpty(servers[0], join('streaming-playlists', 'hls'), [ 'private' ])
115 await checkDirectoryIsEmpty(servers[0], join('streaming-playlists', 'hls', 'private'))
116 })
117
109 after(async function () { 118 after(async function () {
110 await cleanupTests(servers) 119 await cleanupTests(servers)
111 }) 120 })
diff --git a/shared/core-utils/common/object.ts b/shared/core-utils/common/object.ts
index 2330c9403..7f1f147f4 100644
--- a/shared/core-utils/common/object.ts
+++ b/shared/core-utils/common/object.ts
@@ -41,9 +41,14 @@ function sortObjectComparator (key: string, order: 'asc' | 'desc') {
41 } 41 }
42} 42}
43 43
44function shallowCopy <T> (o: T): T {
45 return Object.assign(Object.create(Object.getPrototypeOf(o)), o)
46}
47
44export { 48export {
45 pick, 49 pick,
46 omit, 50 omit,
47 getKeys, 51 getKeys,
52 shallowCopy,
48 sortObjectComparator 53 sortObjectComparator
49} 54}
diff --git a/support/docker/production/config/custom-environment-variables.yaml b/support/docker/production/config/custom-environment-variables.yaml
index 1d889fe7d..10432e6a0 100644
--- a/support/docker/production/config/custom-environment-variables.yaml
+++ b/support/docker/production/config/custom-environment-variables.yaml
@@ -57,6 +57,15 @@ object_storage:
57 57
58 region: "PEERTUBE_OBJECT_STORAGE_REGION" 58 region: "PEERTUBE_OBJECT_STORAGE_REGION"
59 59
60 upload_acl:
61 public: "PEERTUBE_OBJECT_STORAGE_UPLOAD_ACL_PUBLIC"
62 private: "PEERTUBE_OBJECT_STORAGE_UPLOAD_ACL_PRIVATE"
63
64 proxy:
65 proxify_private_files:
66 __name: "PEERTUBE_OBJECT_STORAGE_PROXY_PROXIFY_PRIVATE_FILES"
67 __format: "json"
68
60 credentials: 69 credentials:
61 access_key_id: "PEERTUBE_OBJECT_STORAGE_CREDENTIALS_ACCESS_KEY_ID" 70 access_key_id: "PEERTUBE_OBJECT_STORAGE_CREDENTIALS_ACCESS_KEY_ID"
62 secret_access_key: 'PEERTUBE_OBJECT_STORAGE_CREDENTIALS_SECRET_ACCESS_KEY' 71 secret_access_key: 'PEERTUBE_OBJECT_STORAGE_CREDENTIALS_SECRET_ACCESS_KEY'