Browse Source

Merge branch 'release/5.1.0' into develop

tags/v5.1.0
Chocobozzz 3 weeks ago
parent
commit
d9dbf27a7d
No known key found for this signature in database GPG Key ID: 583A612D890159BE
20 changed files with 129 additions and 102 deletions
  1. +1
    -0
      CHANGELOG.md
  2. +1
    -1
      client/src/app/+admin/overview/users/user-edit/user-edit.component.html
  3. +3
    -3
      client/src/app/+admin/overview/users/user-edit/user-password.component.ts
  4. +7
    -0
      client/src/app/+manage/video-channel-edit/video-channel-update.component.ts
  5. +8
    -1
      client/src/app/+my-account/my-account-settings/my-account-settings.component.ts
  6. +5
    -7
      client/src/app/+videos/video-list/overview/video-overview.component.scss
  7. +21
    -25
      client/src/app/shared/shared-actor-image-edit/actor-avatar-edit.component.html
  8. +10
    -13
      client/src/app/shared/shared-actor-image-edit/actor-avatar-edit.component.scss
  9. +0
    -3
      client/src/app/shared/shared-actor-image-edit/actor-avatar-edit.component.ts
  10. +16
    -17
      client/src/app/shared/shared-actor-image-edit/actor-banner-edit.component.html
  11. +17
    -1
      client/src/app/shared/shared-actor-image-edit/actor-banner-edit.component.scss
  12. +2
    -14
      client/src/app/shared/shared-actor-image-edit/actor-image-edit.scss
  13. +1
    -1
      client/src/app/shared/shared-actor-image/actor-avatar.component.html
  14. +4
    -12
      client/src/app/shared/shared-actor-image/actor-avatar.component.ts
  15. +1
    -1
      client/src/sass/bootstrap.scss
  16. +6
    -0
      client/src/sass/include/_mixins.scss
  17. +2
    -2
      server/models/user/user.ts
  18. +10
    -1
      server/tests/cli/create-move-video-storage-job.ts
  19. +5
    -0
      shared/core-utils/common/object.ts
  20. +9
    -0
      support/docker/production/config/custom-environment-variables.yaml

+ 1
- 0
CHANGELOG.md View File

@@ -4,6 +4,7 @@

### IMPORTANT NOTES

* 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
* Update [web browsers support list](https://joinpeertube.org/faq#what-web-browsers-are-supported-by-peertube):
* Drop support of Safari 11 on iOS
* Drop support of Safari 11 on desktop


+ 1
- 1
client/src/app/+admin/overview/users/user-edit/user-edit.component.html View File

@@ -224,7 +224,7 @@

<div class="form-group">
<label i18n>Manually set the user password</label>
<my-user-password [userId]="user.id"></my-user-password>
<my-user-password [userId]="user.id" [username]="user.username"></my-user-password>
</div>

<div *ngIf="user.twoFactorEnabled" class="form-group">


+ 3
- 3
client/src/app/+admin/overview/users/user-edit/user-password.component.ts View File

@@ -11,12 +11,12 @@ import { UserUpdate } from '@shared/models'
styleUrls: [ './user-password.component.scss' ]
})
export class UserPasswordComponent extends FormReactive implements OnInit {
@Input() userId: number
@Input() username: string

error: string
username: string
showPassword = false

@Input() userId: number

constructor (
protected formReactiveService: FormReactiveService,
private notifier: Notifier,


+ 7
- 0
client/src/app/+manage/video-channel-edit/video-channel-update.component.ts View 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)


+ 8
- 1
client/src/app/+my-account/my-account-settings/my-account-settings.component.ts View 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)


+ 5
- 7
client/src/app/+videos/video-list/overview/video-overview.component.scss View File

@@ -21,9 +21,9 @@
}

.section-title {
font-size: 24px;
padding-top: 20px;
margin-bottom: 30px;
@include font-size(1.5rem);
@include padding-top(1.25rem);
@include margin-bottom(2rem);

&:not(h2) {
border-top: 1px solid $separator-border-color;
@@ -38,8 +38,8 @@
my-actor-avatar {
@include margin-right(8px);

position: relative;
top: -2px;
display: inline-block;
vertical-align: text-top;
}
}

@@ -49,8 +49,6 @@

.section-title {
@include margin-left(10px);

font-size: 17px;
}
}
}

+ 21
- 25
client/src/app/shared/shared-actor-image-edit/actor-avatar-edit.component.html View 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>

@@ -27,16 +36,3 @@
<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>

+ 10
- 13
client/src/app/shared/shared-actor-image-edit/actor-avatar-edit.component.scss View 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;
}

+ 0
- 3
client/src/app/shared/shared-actor-image-edit/actor-avatar-edit.component.ts View 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) {


+ 16
- 17
client/src/app/shared/shared-actor-image-edit/actor-banner-edit.component.html View 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>


+ 17
- 1
client/src/app/shared/shared-actor-image-edit/actor-banner-edit.component.scss View File

@@ -16,12 +16,28 @@
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;
}

+ 2
- 14
client/src/app/shared/shared-actor-image-edit/actor-image-edit.scss View 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 {


+ 1
- 1
client/src/app/shared/shared-actor-image/actor-avatar.component.html View File

@@ -1,5 +1,5 @@
<ng-template #img>
<img *ngIf="displayImage()" [class]="classes" [src]="previewImage || avatarUrl || defaultAvatarUrl" [alt]="alt" />
<img *ngIf="displayImage()" [class]="classes" [src]="previewImage || avatarUrl || defaultAvatarUrl" alt="" />

<div *ngIf="displayActorInitial()" [ngClass]="classes">
<span>{{ getActorInitial() }}</span>


+ 4
- 12
client/src/app/shared/shared-actor-image/actor-avatar.component.ts View File

@@ -43,22 +43,19 @@ export class ActorAvatarComponent implements OnInit, OnChanges {
}

classes: string[] = []
alt: string
defaultAvatarUrl: string
avatarUrl: string

ngOnInit () {
this.buildDefaultAvatarUrl()

this.buildClasses()
this.buildAlt()
this.buildAvatarUrl()
this.buildClasses()
}

ngOnChanges () {
this.buildClasses()
this.buildAlt()
this.buildAvatarUrl()
this.buildClasses()
}

private buildClasses () {
@@ -81,12 +78,6 @@ export class ActorAvatarComponent implements OnInit, OnChanges {
}
}

private buildAlt () {
if (this.isAccount()) this.alt = $localize`Account avatar`
else if (this.isChannel()) this.alt = $localize`Channel avatar`
else this.alt = ''
}

private buildDefaultAvatarUrl () {
this.defaultAvatarUrl = this.isChannel()
? VideoChannel.GET_DEFAULT_AVATAR_URL(this.getSizeNumber())
@@ -114,12 +105,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 () {


+ 1
- 1
client/src/sass/bootstrap.scss View File

@@ -30,7 +30,7 @@
@import 'bootstrap/scss/helpers';
@import 'bootstrap/scss/utilities/api';

:root {
body {
--bs-border-color-translucent: #{pvar(--inputBorderColor)};
}



+ 6
- 0
client/src/sass/include/_mixins.scss View File

@@ -155,6 +155,7 @@
@mixin orange-button-inverted {
@include button-focus(pvar(--mainColorLightest));

padding: 2px 13px;
border: 2px solid pvar(--mainColor);
font-weight: $font-semibold;

@@ -263,6 +264,7 @@
cursor: pointer;

font-size: $button-font-size;
line-height: $button-font-size + math.round(math.div($button-font-size, 2));

my-global-icon + * {
@include margin-right(4px);
@@ -312,6 +314,10 @@
width: $width;
top: $top;
}

span {
vertical-align: middle;
}
}

@mixin peertube-file {


+ 2
- 2
server/models/user/user.ts View File

@@ -781,12 +781,12 @@ export class UserModel extends Model<Partial<AttributesOnly<UserModel>>> {
`WHERE "account"."userId" = ${options.whereUserId} ${andWhere}`

const webtorrentFiles = 'SELECT "videoFile"."size" AS "size", "video"."id" AS "videoId" FROM "videoFile" ' +
'INNER JOIN "video" ON "videoFile"."videoId" = "video"."id" ' +
'INNER JOIN "video" ON "videoFile"."videoId" = "video"."id" AND "video"."isLive" IS FALSE ' +
videoChannelJoin

const hlsFiles = 'SELECT "videoFile"."size" AS "size", "video"."id" AS "videoId" FROM "videoFile" ' +
'INNER JOIN "videoStreamingPlaylist" ON "videoFile"."videoStreamingPlaylistId" = "videoStreamingPlaylist".id ' +
'INNER JOIN "video" ON "videoStreamingPlaylist"."videoId" = "video"."id" ' +
'INNER JOIN "video" ON "videoStreamingPlaylist"."videoId" = "video"."id" AND "video"."isLive" IS FALSE ' +
videoChannelJoin

return 'SELECT COALESCE(SUM("size"), 0) AS "total" ' +


+ 10
- 1
server/tests/cli/create-move-video-storage-job.ts View File

@@ -1,5 +1,6 @@
/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */

import { join } from 'path'
import { areMockObjectStorageTestsDisabled } from '@shared/core-utils'
import { HttpStatusCode, VideoDetails } from '@shared/models'
import {
@@ -12,7 +13,7 @@ import {
setAccessTokensToServers,
waitJobs
} from '@shared/server-commands'
import { expectStartWith } from '../shared'
import { checkDirectoryIsEmpty, expectStartWith } from '../shared'

async function checkFiles (origin: PeerTubeServer, video: VideoDetails, inObjectStorage: boolean) {
for (const file of video.files) {
@@ -106,6 +107,14 @@ describe('Test create move video storage job', function () {
}
})

it('Should not have files on disk anymore', async function () {
await checkDirectoryIsEmpty(servers[0], 'videos', [ 'private' ])
await checkDirectoryIsEmpty(servers[0], join('videos', 'private'))

await checkDirectoryIsEmpty(servers[0], join('streaming-playlists', 'hls'), [ 'private' ])
await checkDirectoryIsEmpty(servers[0], join('streaming-playlists', 'hls', 'private'))
})

after(async function () {
await cleanupTests(servers)
})


+ 5
- 0
shared/core-utils/common/object.ts View 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
}

+ 9
- 0
support/docker/production/config/custom-environment-variables.yaml View File

@@ -57,6 +57,15 @@ object_storage:

region: "PEERTUBE_OBJECT_STORAGE_REGION"

upload_acl:
public: "PEERTUBE_OBJECT_STORAGE_UPLOAD_ACL_PUBLIC"
private: "PEERTUBE_OBJECT_STORAGE_UPLOAD_ACL_PRIVATE"

proxy:
proxify_private_files:
__name: "PEERTUBE_OBJECT_STORAGE_PROXY_PROXIFY_PRIVATE_FILES"
__format: "json"

credentials:
access_key_id: "PEERTUBE_OBJECT_STORAGE_CREDENTIALS_ACCESS_KEY_ID"
secret_access_key: 'PEERTUBE_OBJECT_STORAGE_CREDENTIALS_SECRET_ACCESS_KEY'


Loading…
Cancel
Save