aboutsummaryrefslogtreecommitdiffhomepage
path: root/client/src/app/shared/shared-main
diff options
context:
space:
mode:
Diffstat (limited to 'client/src/app/shared/shared-main')
-rw-r--r--client/src/app/shared/shared-main/angular/autofocus.directive.ts4
-rw-r--r--client/src/app/shared/shared-main/angular/from-now.pipe.ts34
-rw-r--r--client/src/app/shared/shared-main/buttons/action-dropdown.component.html2
-rw-r--r--client/src/app/shared/shared-main/buttons/action-dropdown.component.ts2
-rw-r--r--client/src/app/shared/shared-main/buttons/button.component.html14
-rw-r--r--client/src/app/shared/shared-main/buttons/button.component.scss40
-rw-r--r--client/src/app/shared/shared-main/buttons/button.component.ts12
-rw-r--r--client/src/app/shared/shared-main/buttons/delete-button.component.html8
-rw-r--r--client/src/app/shared/shared-main/buttons/delete-button.component.ts7
-rw-r--r--client/src/app/shared/shared-main/buttons/edit-button.component.html8
-rw-r--r--client/src/app/shared/shared-main/buttons/edit-button.component.ts13
-rw-r--r--client/src/app/shared/shared-main/loaders/index.ts1
-rw-r--r--client/src/app/shared/shared-main/loaders/loader.component.html8
-rw-r--r--client/src/app/shared/shared-main/loaders/loader.component.scss45
-rw-r--r--client/src/app/shared/shared-main/loaders/loader.component.ts22
-rw-r--r--client/src/app/shared/shared-main/loaders/small-loader.component.html3
-rw-r--r--client/src/app/shared/shared-main/loaders/small-loader.component.ts11
-rw-r--r--client/src/app/shared/shared-main/misc/channels-setup-message.component.html2
-rw-r--r--client/src/app/shared/shared-main/misc/channels-setup-message.component.scss34
-rw-r--r--client/src/app/shared/shared-main/misc/list-overflow.component.html12
-rw-r--r--client/src/app/shared/shared-main/misc/list-overflow.component.scss3
-rw-r--r--client/src/app/shared/shared-main/misc/list-overflow.component.ts9
-rw-r--r--client/src/app/shared/shared-main/misc/simple-search-input.component.html15
-rw-r--r--client/src/app/shared/shared-main/misc/simple-search-input.component.scss6
-rw-r--r--client/src/app/shared/shared-main/misc/top-menu-dropdown.component.html12
-rw-r--r--client/src/app/shared/shared-main/shared-main.module.ts4
-rw-r--r--client/src/app/shared/shared-main/users/user-quota.component.html2
-rw-r--r--client/src/app/shared/shared-main/users/user-quota.component.scss5
-rw-r--r--client/src/app/shared/shared-main/video-channel/video-channel.model.ts5
-rw-r--r--client/src/app/shared/shared-main/video/video.model.ts11
30 files changed, 161 insertions, 193 deletions
diff --git a/client/src/app/shared/shared-main/angular/autofocus.directive.ts b/client/src/app/shared/shared-main/angular/autofocus.directive.ts
index 2da492ea1..051635f45 100644
--- a/client/src/app/shared/shared-main/angular/autofocus.directive.ts
+++ b/client/src/app/shared/shared-main/angular/autofocus.directive.ts
@@ -7,6 +7,8 @@ export class AutofocusDirective implements AfterViewInit {
7 constructor (private host: ElementRef) { } 7 constructor (private host: ElementRef) { }
8 8
9 ngAfterViewInit () { 9 ngAfterViewInit () {
10 this.host.nativeElement.focus() 10 const el = this.host.nativeElement as HTMLElement
11
12 el.focus({ preventScroll: true })
11 } 13 }
12} 14}
diff --git a/client/src/app/shared/shared-main/angular/from-now.pipe.ts b/client/src/app/shared/shared-main/angular/from-now.pipe.ts
index d62c1f88e..dc6a25e83 100644
--- a/client/src/app/shared/shared-main/angular/from-now.pipe.ts
+++ b/client/src/app/shared/shared-main/angular/from-now.pipe.ts
@@ -1,37 +1,51 @@
1import { Pipe, PipeTransform } from '@angular/core' 1import { Pipe, PipeTransform } from '@angular/core'
2import { prepareIcu } from '@app/helpers'
2 3
3// Thanks: https://stackoverflow.com/questions/3177836/how-to-format-time-since-xxx-e-g-4-minutes-ago-similar-to-stack-exchange-site 4// Thanks: https://stackoverflow.com/questions/3177836/how-to-format-time-since-xxx-e-g-4-minutes-ago-similar-to-stack-exchange-site
4@Pipe({ name: 'myFromNow' }) 5@Pipe({ name: 'myFromNow' })
5export class FromNowPipe implements PipeTransform { 6export class FromNowPipe implements PipeTransform {
7 private yearICU = prepareIcu($localize`{interval, plural, =1 {1 year ago} other {{interval} years ago}}`)
8 private monthICU = prepareIcu($localize`{interval, plural, =1 {1 month ago} other {{interval} months ago}}`)
9 private weekICU = prepareIcu($localize`{interval, plural, =1 {1 week ago} other {{interval} weeks ago}}`)
10 private dayICU = prepareIcu($localize`{interval, plural, =1 {1 day ago} other {{interval} days ago}}`)
11 private hourICU = prepareIcu($localize`{interval, plural, =1 {1 hour ago} other {{interval} hours ago}}`)
12
6 transform (arg: number | Date | string) { 13 transform (arg: number | Date | string) {
7 const argDate = new Date(arg) 14 const argDate = new Date(arg)
8 const seconds = Math.floor((Date.now() - argDate.getTime()) / 1000) 15 const seconds = Math.floor((Date.now() - argDate.getTime()) / 1000)
9 16
10 let interval = Math.floor(seconds / 31536000) 17 let interval = Math.floor(seconds / 31536000)
11 if (interval > 1) return $localize`${interval} years ago` 18 if (interval >= 1) {
12 if (interval === 1) return $localize`1 year ago` 19 return this.yearICU({ interval }, $localize`${interval} year(s) ago`)
20 }
13 21
14 interval = Math.floor(seconds / 2419200) 22 interval = Math.floor(seconds / 2419200)
15 // 12 months = 360 days, but a year ~ 365 days 23 // 12 months = 360 days, but a year ~ 365 days
16 // Display "1 year ago" rather than "12 months ago" 24 // Display "1 year ago" rather than "12 months ago"
17 if (interval >= 12) return $localize`1 year ago` 25 if (interval >= 12) return $localize`1 year ago`
18 if (interval > 1) return $localize`${interval} months ago` 26
19 if (interval === 1) return $localize`1 month ago` 27 if (interval >= 1) {
28 return this.monthICU({ interval }, $localize`${interval} month(s) ago`)
29 }
20 30
21 interval = Math.floor(seconds / 604800) 31 interval = Math.floor(seconds / 604800)
22 // 4 weeks ~ 28 days, but our month is 30 days 32 // 4 weeks ~ 28 days, but our month is 30 days
23 // Display "1 month ago" rather than "4 weeks ago" 33 // Display "1 month ago" rather than "4 weeks ago"
24 if (interval >= 4) return $localize`1 month ago` 34 if (interval >= 4) return $localize`1 month ago`
25 if (interval > 1) return $localize`${interval} weeks ago` 35
26 if (interval === 1) return $localize`1 week ago` 36 if (interval >= 1) {
37 return this.weekICU({ interval }, $localize`${interval} week(s) ago`)
38 }
27 39
28 interval = Math.floor(seconds / 86400) 40 interval = Math.floor(seconds / 86400)
29 if (interval > 1) return $localize`${interval} days ago` 41 if (interval >= 1) {
30 if (interval === 1) return $localize`1 day ago` 42 return this.dayICU({ interval }, $localize`${interval} day(s) ago`)
43 }
31 44
32 interval = Math.floor(seconds / 3600) 45 interval = Math.floor(seconds / 3600)
33 if (interval > 1) return $localize`${interval} hours ago` 46 if (interval >= 1) {
34 if (interval === 1) return $localize`1 hour ago` 47 return this.hourICU({ interval }, $localize`${interval} hour(s) ago`)
48 }
35 49
36 interval = Math.floor(seconds / 60) 50 interval = Math.floor(seconds / 60)
37 if (interval >= 1) return $localize`${interval} min ago` 51 if (interval >= 1) return $localize`${interval} min ago`
diff --git a/client/src/app/shared/shared-main/buttons/action-dropdown.component.html b/client/src/app/shared/shared-main/buttons/action-dropdown.component.html
index 10dae68f0..017355bd0 100644
--- a/client/src/app/shared/shared-main/buttons/action-dropdown.component.html
+++ b/client/src/app/shared/shared-main/buttons/action-dropdown.component.html
@@ -39,7 +39,7 @@
39 </span> 39 </span>
40 40
41 <h6 41 <h6
42 *ngIf="!action.linkBuilder && action.isHeader" [ngClass]="{ 'with-icon': !!action.iconName }" 42 *ngIf="!action.linkBuilder && action.isHeader && areActionsDisplayed(actions, entry)" [ngClass]="{ 'with-icon': !!action.iconName }"
43 class="dropdown-header" tabindex="0" role="button" [title]="action.title || ''" (click)="action.handler(entry)" (keyup.enter)="action.handler(entry)" 43 class="dropdown-header" tabindex="0" role="button" [title]="action.title || ''" (click)="action.handler(entry)" (keyup.enter)="action.handler(entry)"
44 > 44 >
45 <ng-container *ngTemplateOutlet="templateActionLabel; context:{ $implicit: action }"></ng-container> 45 <ng-container *ngTemplateOutlet="templateActionLabel; context:{ $implicit: action }"></ng-container>
diff --git a/client/src/app/shared/shared-main/buttons/action-dropdown.component.ts b/client/src/app/shared/shared-main/buttons/action-dropdown.component.ts
index 67ac6e1aa..749773f8a 100644
--- a/client/src/app/shared/shared-main/buttons/action-dropdown.component.ts
+++ b/client/src/app/shared/shared-main/buttons/action-dropdown.component.ts
@@ -48,7 +48,7 @@ export class ActionDropdownComponent<T> {
48 return actions.some(a => { 48 return actions.some(a => {
49 if (Array.isArray(a)) return this.areActionsDisplayed(a, entry) 49 if (Array.isArray(a)) return this.areActionsDisplayed(a, entry)
50 50
51 return a.isDisplayed === undefined || a.isDisplayed(entry) 51 return a.isHeader !== true && (a.isDisplayed === undefined || a.isDisplayed(entry))
52 }) 52 })
53 } 53 }
54} 54}
diff --git a/client/src/app/shared/shared-main/buttons/button.component.html b/client/src/app/shared/shared-main/buttons/button.component.html
index d1a4215e6..3e3728623 100644
--- a/client/src/app/shared/shared-main/buttons/button.component.html
+++ b/client/src/app/shared/shared-main/buttons/button.component.html
@@ -1,8 +1,16 @@
1<span class="action-button" [ngClass]="getClasses()" [ngbTooltip]="title" tabindex="0"> 1<div *ngIf="!routerLink" class="action-button" [ngClass]="classes" [ngbTooltip]="title" tabindex="0">
2 <my-small-loader [loading]="loading"></my-small-loader> 2 <ng-container *ngTemplateOutlet="content"></ng-container>
3</div>
4
5<a *ngIf="routerLink" class="action-button" [ngClass]="classes" [ngbTooltip]="title" [routerLink]="routerLink">
6 <ng-container *ngTemplateOutlet="content"></ng-container>
7</a>
8
9<ng-template #content>
10 <my-loader size="sm" [loading]="loading"></my-loader>
3 <my-global-icon *ngIf="icon && !loading" [iconName]="icon"></my-global-icon> 11 <my-global-icon *ngIf="icon && !loading" [iconName]="icon"></my-global-icon>
4 12
5 <span *ngIf="label" class="button-label">{{ label }}</span> 13 <span *ngIf="label" class="button-label">{{ label }}</span>
6 14
7 <ng-content></ng-content> 15 <ng-content></ng-content>
8</span> 16</ng-template>
diff --git a/client/src/app/shared/shared-main/buttons/button.component.scss b/client/src/app/shared/shared-main/buttons/button.component.scss
index c53b8f2e5..7f0cdf1ed 100644
--- a/client/src/app/shared/shared-main/buttons/button.component.scss
+++ b/client/src/app/shared/shared-main/buttons/button.component.scss
@@ -9,17 +9,14 @@
9 .button-label { 9 .button-label {
10 display: none; 10 display: none;
11 } 11 }
12}
13 12
14:host { 13 my-global-icon {
15 outline: none; 14 margin: 0 !important;
16 display: inline-block; 15 }
17} 16}
18 17
19my-small-loader ::ng-deep .root { 18:host {
20 display: inline-block; 19 display: inline-block;
21 margin: 0 3px 0 0;
22 width: 20px;
23} 20}
24 21
25a[class$=-button], 22a[class$=-button],
@@ -30,35 +27,34 @@ span[class$=-button] {
30} 27}
31 28
32.action-button { 29.action-button {
33 @include peertube-button-link;
34 @include button-with-icon(21px);
35
36 width: 100%; // useful for ellipsis, allow to define a max-width on host component 30 width: 100%; // useful for ellipsis, allow to define a max-width on host component
37 31
38 &.icon-only { 32 &.has-icon {
39 my-global-icon { 33 @include button-with-icon(21px);
40 margin: 0; 34 }
41 } 35
36 &.icon-only my-global-icon {
37 margin: 0 !important;
42 } 38 }
43} 39}
44 40
45.orange-button { 41.orange-button,
42.grey-button {
46 @include peertube-button; 43 @include peertube-button;
47 @include orange-button;
48} 44}
49 45
50.orange-button-link { 46.orange-button-link,
47.grey-button-link {
51 @include peertube-button-link; 48 @include peertube-button-link;
52 @include orange-button;
53} 49}
54 50
55.grey-button { 51.orange-button,
56 @include peertube-button; 52.orange-button-link {
57 @include grey-button; 53 @include orange-button;
58} 54}
59 55
56.grey-button,
60.grey-button-link { 57.grey-button-link {
61 @include peertube-button-link;
62 @include grey-button; 58 @include grey-button;
63} 59}
64 60
diff --git a/client/src/app/shared/shared-main/buttons/button.component.ts b/client/src/app/shared/shared-main/buttons/button.component.ts
index 52936a4d4..10d67831f 100644
--- a/client/src/app/shared/shared-main/buttons/button.component.ts
+++ b/client/src/app/shared/shared-main/buttons/button.component.ts
@@ -1,4 +1,4 @@
1import { Component, Input } from '@angular/core' 1import { Component, Input, OnChanges } from '@angular/core'
2import { GlobalIconName } from '@app/shared/shared-icons' 2import { GlobalIconName } from '@app/shared/shared-icons'
3 3
4@Component({ 4@Component({
@@ -7,20 +7,24 @@ import { GlobalIconName } from '@app/shared/shared-icons'
7 templateUrl: './button.component.html' 7 templateUrl: './button.component.html'
8}) 8})
9 9
10export class ButtonComponent { 10export class ButtonComponent implements OnChanges {
11 @Input() label = '' 11 @Input() label = ''
12 @Input() className = 'grey-button' 12 @Input() className = 'grey-button'
13 @Input() icon: GlobalIconName = undefined 13 @Input() icon: GlobalIconName = undefined
14 @Input() routerLink: string[] | string
14 @Input() title: string = undefined 15 @Input() title: string = undefined
15 @Input() loading = false 16 @Input() loading = false
16 @Input() disabled = false 17 @Input() disabled = false
17 @Input() responsiveLabel = false 18 @Input() responsiveLabel = false
18 19
19 getClasses () { 20 classes: { [id: string]: boolean } = {}
20 return { 21
22 ngOnChanges () {
23 this.classes = {
21 [this.className]: true, 24 [this.className]: true,
22 disabled: this.disabled, 25 disabled: this.disabled,
23 'icon-only': !this.label, 26 'icon-only': !this.label,
27 'has-icon': !!this.icon,
24 'responsive-label': this.responsiveLabel 28 'responsive-label': this.responsiveLabel
25 } 29 }
26 } 30 }
diff --git a/client/src/app/shared/shared-main/buttons/delete-button.component.html b/client/src/app/shared/shared-main/buttons/delete-button.component.html
deleted file mode 100644
index d7a6702a7..000000000
--- a/client/src/app/shared/shared-main/buttons/delete-button.component.html
+++ /dev/null
@@ -1,8 +0,0 @@
1<span
2 class="action-button action-button-delete grey-button"
3 [ngClass]="{ 'responsive-label': responsiveLabel }" [ngbTooltip]="title" role="button" tabindex="0"
4>
5 <my-global-icon iconName="delete" aria-hidden="true"></my-global-icon>
6
7 <span class="button-label" *ngIf="label">{{ label }}</span>
8</span>
diff --git a/client/src/app/shared/shared-main/buttons/delete-button.component.ts b/client/src/app/shared/shared-main/buttons/delete-button.component.ts
index 90735852c..1cab10803 100644
--- a/client/src/app/shared/shared-main/buttons/delete-button.component.ts
+++ b/client/src/app/shared/shared-main/buttons/delete-button.component.ts
@@ -2,17 +2,16 @@ import { Component, Input, OnInit } from '@angular/core'
2 2
3@Component({ 3@Component({
4 selector: 'my-delete-button', 4 selector: 'my-delete-button',
5 styleUrls: [ './button.component.scss' ], 5 template: `
6 templateUrl: './delete-button.component.html' 6 <my-button icon="delete" className="grey-button" [label]="label" [title]="title" [responsiveLabel]="responsiveLabel"></my-button>
7 `
7}) 8})
8
9export class DeleteButtonComponent implements OnInit { 9export class DeleteButtonComponent implements OnInit {
10 @Input() label: string 10 @Input() label: string
11 @Input() title: string 11 @Input() title: string
12 @Input() responsiveLabel = false 12 @Input() responsiveLabel = false
13 13
14 ngOnInit () { 14 ngOnInit () {
15 // <my-delete-button /> No label
16 if (this.label === undefined && !this.title) { 15 if (this.label === undefined && !this.title) {
17 this.title = $localize`Delete` 16 this.title = $localize`Delete`
18 } 17 }
diff --git a/client/src/app/shared/shared-main/buttons/edit-button.component.html b/client/src/app/shared/shared-main/buttons/edit-button.component.html
deleted file mode 100644
index 8beeee6c4..000000000
--- a/client/src/app/shared/shared-main/buttons/edit-button.component.html
+++ /dev/null
@@ -1,8 +0,0 @@
1<a
2 class="action-button action-button-edit grey-button"
3 [ngClass]="{ 'responsive-label': responsiveLabel }" [routerLink]="routerLink" [ngbTooltip]="title"
4>
5 <my-global-icon iconName="edit" aria-hidden="true"></my-global-icon>
6
7 <span class="button-label" *ngIf="label">{{ label }}</span>
8</a>
diff --git a/client/src/app/shared/shared-main/buttons/edit-button.component.ts b/client/src/app/shared/shared-main/buttons/edit-button.component.ts
index 24c8625ff..28aacbbff 100644
--- a/client/src/app/shared/shared-main/buttons/edit-button.component.ts
+++ b/client/src/app/shared/shared-main/buttons/edit-button.component.ts
@@ -2,8 +2,13 @@ import { Component, Input, OnInit } from '@angular/core'
2 2
3@Component({ 3@Component({
4 selector: 'my-edit-button', 4 selector: 'my-edit-button',
5 styleUrls: [ './button.component.scss' ], 5 template: `
6 templateUrl: './edit-button.component.html' 6 <my-button
7 icon="edit" className="grey-button-link"
8 [label]="label" [title]="title" [responsiveLabel]="responsiveLabel"
9 [routerLink]="routerLink"
10 ></my-button>
11 `
7}) 12})
8export class EditButtonComponent implements OnInit { 13export class EditButtonComponent implements OnInit {
9 @Input() label: string 14 @Input() label: string
@@ -20,10 +25,6 @@ export class EditButtonComponent implements OnInit {
20 // <my-edit-button label /> Use default label 25 // <my-edit-button label /> Use default label
21 if (this.label === '') { 26 if (this.label === '') {
22 this.label = $localize`Update` 27 this.label = $localize`Update`
23
24 if (!this.title) {
25 this.title = this.label
26 }
27 } 28 }
28 } 29 }
29} 30}
diff --git a/client/src/app/shared/shared-main/loaders/index.ts b/client/src/app/shared/shared-main/loaders/index.ts
index a061914d5..60483727c 100644
--- a/client/src/app/shared/shared-main/loaders/index.ts
+++ b/client/src/app/shared/shared-main/loaders/index.ts
@@ -1,2 +1 @@
1export * from './loader.component' export * from './loader.component'
2export * from './small-loader.component'
diff --git a/client/src/app/shared/shared-main/loaders/loader.component.html b/client/src/app/shared/shared-main/loaders/loader.component.html
deleted file mode 100644
index ca8ed063e..000000000
--- a/client/src/app/shared/shared-main/loaders/loader.component.html
+++ /dev/null
@@ -1,8 +0,0 @@
1<div *ngIf="loading">
2 <div class="loader">
3 <div></div>
4 <div></div>
5 <div></div>
6 <div></div>
7 </div>
8</div>
diff --git a/client/src/app/shared/shared-main/loaders/loader.component.scss b/client/src/app/shared/shared-main/loaders/loader.component.scss
deleted file mode 100644
index b88b0db6a..000000000
--- a/client/src/app/shared/shared-main/loaders/loader.component.scss
+++ /dev/null
@@ -1,45 +0,0 @@
1@use '_variables' as *;
2@use '_mixins' as *;
3
4// Thanks to https://loading.io/css/ (CC0 License)
5
6.loader {
7 display: inline-block;
8 position: relative;
9 width: 50px;
10 height: 50px;
11}
12
13.loader div {
14 box-sizing: border-box;
15 display: block;
16 position: absolute;
17 width: 44px;
18 height: 44px;
19 margin: 6px;
20 border: 4px solid;
21 border-radius: 50%;
22 animation: loader 1.2s cubic-bezier(0.5, 0, 0.5, 1) infinite;
23 border-color: #999999 transparent transparent;
24}
25
26.loader div:nth-child(1) {
27 animation-delay: -0.45s;
28}
29
30.loader div:nth-child(2) {
31 animation-delay: -0.3s;
32}
33
34.loader div:nth-child(3) {
35 animation-delay: -0.15s;
36}
37
38@keyframes loader {
39 0% {
40 transform: rotate(0deg);
41 }
42 100% {
43 transform: rotate(360deg);
44 }
45}
diff --git a/client/src/app/shared/shared-main/loaders/loader.component.ts b/client/src/app/shared/shared-main/loaders/loader.component.ts
index e3b1eea3a..bd038f8b5 100644
--- a/client/src/app/shared/shared-main/loaders/loader.component.ts
+++ b/client/src/app/shared/shared-main/loaders/loader.component.ts
@@ -2,9 +2,27 @@ import { Component, Input } from '@angular/core'
2 2
3@Component({ 3@Component({
4 selector: 'my-loader', 4 selector: 'my-loader',
5 styleUrls: [ './loader.component.scss' ], 5 template: `<div *ngIf="loading" class="spinner-border" [ngStyle]="getStyle()" role="status"></div>`
6 templateUrl: './loader.component.html'
7}) 6})
8export class LoaderComponent { 7export class LoaderComponent {
9 @Input() loading: boolean 8 @Input() loading: boolean
9 @Input() size: 'sm' | 'xl'
10
11 private readonly sizes = {
12 sm: {
13 width: '1rem',
14 height: '1rem',
15 'border-width': '0.15rem'
16 },
17 xl: {
18 width: '3rem',
19 height: '3rem'
20 }
21 }
22
23 getStyle () {
24 if (!this.size) return undefined
25
26 return this.sizes[this.size]
27 }
10} 28}
diff --git a/client/src/app/shared/shared-main/loaders/small-loader.component.html b/client/src/app/shared/shared-main/loaders/small-loader.component.html
deleted file mode 100644
index 7886f8918..000000000
--- a/client/src/app/shared/shared-main/loaders/small-loader.component.html
+++ /dev/null
@@ -1,3 +0,0 @@
1<div class="root" *ngIf="loading">
2 <div class="glyphicon glyphicon-refresh glyphicon-refresh-animate"></div>
3</div>
diff --git a/client/src/app/shared/shared-main/loaders/small-loader.component.ts b/client/src/app/shared/shared-main/loaders/small-loader.component.ts
deleted file mode 100644
index 191877f14..000000000
--- a/client/src/app/shared/shared-main/loaders/small-loader.component.ts
+++ /dev/null
@@ -1,11 +0,0 @@
1import { Component, Input } from '@angular/core'
2
3@Component({
4 selector: 'my-small-loader',
5 styleUrls: [ ],
6 templateUrl: './small-loader.component.html'
7})
8
9export class SmallLoaderComponent {
10 @Input() loading: boolean
11}
diff --git a/client/src/app/shared/shared-main/misc/channels-setup-message.component.html b/client/src/app/shared/shared-main/misc/channels-setup-message.component.html
index 3fe888a35..539df06bd 100644
--- a/client/src/app/shared/shared-main/misc/channels-setup-message.component.html
+++ b/client/src/app/shared/shared-main/misc/channels-setup-message.component.html
@@ -1,4 +1,4 @@
1<div *ngIf="hasChannelNotConfigured()" class="channels-setup-message alert alert-info"> 1<div *ngIf="hasChannelNotConfigured()" class="channels-setup-message alert pt-alert-primary">
2 <my-global-icon iconName="tip"></my-global-icon> 2 <my-global-icon iconName="tip"></my-global-icon>
3 3
4 <div> 4 <div>
diff --git a/client/src/app/shared/shared-main/misc/channels-setup-message.component.scss b/client/src/app/shared/shared-main/misc/channels-setup-message.component.scss
index 7dcba2ca5..2aa176e1b 100644
--- a/client/src/app/shared/shared-main/misc/channels-setup-message.component.scss
+++ b/client/src/app/shared/shared-main/misc/channels-setup-message.component.scss
@@ -5,28 +5,24 @@
5 display: flex; 5 display: flex;
6 align-items: center; 6 align-items: center;
7 justify-content: center; 7 justify-content: center;
8}
8 9
9 my-global-icon { 10my-global-icon {
10 width: 32px; 11 @include apply-svg-color(pvar(--mainColor));
11 align-self: flex-start;
12 12
13 ::ng-deep { 13 width: 32px;
14 svg { 14 align-self: flex-start;
15 fill: #0c5460;
16 }
17 }
18 15
19 + div { 16 + div {
20 margin-left: 10px; 17 margin-left: 10px;
21 text-align: center; 18 text-align: center;
19 }
20}
22 21
23 a.channels-settings-link { 22.channels-settings-link {
24 @include peertube-button-link; 23 @include peertube-button-link;
25 @include grey-button; 24 @include grey-button;
26 25
27 height: fit-content; 26 height: fit-content;
28 margin-top: 10px; 27 margin-top: 10px;
29 }
30 }
31 }
32} 28}
diff --git a/client/src/app/shared/shared-main/misc/list-overflow.component.html b/client/src/app/shared/shared-main/misc/list-overflow.component.html
index b2e0982f1..6f29eaefa 100644
--- a/client/src/app/shared/shared-main/misc/list-overflow.component.html
+++ b/client/src/app/shared/shared-main/misc/list-overflow.component.html
@@ -1,18 +1,22 @@
1<div #itemsParent class="d-flex align-items-center text-nowrap w-100 list-overflow-parent"> 1<div #itemsParent class="list-overflow-parent">
2 <span [id]="getId(id)" #itemsRendered *ngFor="let item of items; index as id"> 2 <span [id]="getId(id)" #itemsRendered *ngFor="let item of items; index as id">
3 <ng-container *ngTemplateOutlet="itemTemplate; context: {item: item}"></ng-container> 3 <ng-container *ngTemplateOutlet="itemTemplate; context: {item: item}"></ng-container>
4 </span> 4 </span>
5 5
6 <ng-container *ngIf="isMenuDisplayed()"> 6 <ng-container *ngIf="isMenuDisplayed()">
7 <button *ngIf="isInMobileView" class="btn btn-outline-secondary btn-sm list-overflow-menu" (click)="toggleModal()"> 7 <button *ngIf="isInMobileView" class="btn btn-outline-secondary btn-sm list-overflow-menu" (click)="toggleModal()">
8 <span class="glyphicon glyphicon-chevron-down"></span> 8 <span class="chevron-down"></span>
9 </button> 9 </button>
10 10
11 <div *ngIf="!isInMobileView" class="list-overflow-menu" ngbDropdown container="body" #dropdown="ngbDropdown" (mouseleave)="closeDropdownIfHovered(dropdown)" (mouseenter)="openDropdownOnHover(dropdown)"> 11 <div
12 *ngIf="!isInMobileView" class="list-overflow-menu"
13 ngbDropdown container="body" #dropdown="ngbDropdown"
14 (mouseleave)="closeDropdownIfHovered(dropdown)" (mouseenter)="openDropdownOnHover(dropdown)"
15 >
12 <button class="btn btn-outline-secondary btn-sm" [ngClass]="{ 'route-active': active }" 16 <button class="btn btn-outline-secondary btn-sm" [ngClass]="{ 'route-active': active }"
13 ngbDropdownAnchor (click)="dropdownAnchorClicked(dropdown)" role="button" 17 ngbDropdownAnchor (click)="dropdownAnchorClicked(dropdown)" role="button"
14 > 18 >
15 <span class="glyphicon glyphicon-chevron-down"></span> 19 <span class="chevron-down"></span>
16 </button> 20 </button>
17 21
18 <div ngbDropdownMenu> 22 <div ngbDropdownMenu>
diff --git a/client/src/app/shared/shared-main/misc/list-overflow.component.scss b/client/src/app/shared/shared-main/misc/list-overflow.component.scss
index 19c055fd3..b06418568 100644
--- a/client/src/app/shared/shared-main/misc/list-overflow.component.scss
+++ b/client/src/app/shared/shared-main/misc/list-overflow.component.scss
@@ -7,6 +7,9 @@
7 7
8.list-overflow-parent { 8.list-overflow-parent {
9 overflow: hidden; 9 overflow: hidden;
10 display: flex;
11 // For the menu icon
12 max-width: calc(100vw - 30px);
10} 13}
11 14
12.list-overflow-menu { 15.list-overflow-menu {
diff --git a/client/src/app/shared/shared-main/misc/list-overflow.component.ts b/client/src/app/shared/shared-main/misc/list-overflow.component.ts
index fbc481093..541991f74 100644
--- a/client/src/app/shared/shared-main/misc/list-overflow.component.ts
+++ b/client/src/app/shared/shared-main/misc/list-overflow.component.ts
@@ -15,6 +15,9 @@ import {
15} from '@angular/core' 15} from '@angular/core'
16import { ScreenService } from '@app/core' 16import { ScreenService } from '@app/core'
17import { NgbDropdown, NgbModal } from '@ng-bootstrap/ng-bootstrap' 17import { NgbDropdown, NgbModal } from '@ng-bootstrap/ng-bootstrap'
18import * as debug from 'debug'
19
20const logger = debug('peertube:main:ListOverflowItem')
18 21
19export interface ListOverflowItem { 22export interface ListOverflowItem {
20 label: string 23 label: string
@@ -37,7 +40,6 @@ export class ListOverflowComponent<T extends ListOverflowItem> implements AfterV
37 40
38 showItemsUntilIndexExcluded: number 41 showItemsUntilIndexExcluded: number
39 active = false 42 active = false
40 isInTouchScreen = false
41 isInMobileView = false 43 isInMobileView = false
42 44
43 private openedOnHover = false 45 private openedOnHover = false
@@ -58,13 +60,14 @@ export class ListOverflowComponent<T extends ListOverflowItem> implements AfterV
58 60
59 @HostListener('window:resize') 61 @HostListener('window:resize')
60 onWindowResize () { 62 onWindowResize () {
61 this.isInTouchScreen = !!this.screenService.isInTouchScreen()
62 this.isInMobileView = !!this.screenService.isInMobileView() 63 this.isInMobileView = !!this.screenService.isInMobileView()
63 64
64 const parentWidth = this.parent.nativeElement.getBoundingClientRect().width 65 const parentWidth = this.parent.nativeElement.getBoundingClientRect().width
65 let showItemsUntilIndexExcluded: number 66 let showItemsUntilIndexExcluded: number
66 let accWidth = 0 67 let accWidth = 0
67 68
69 logger('Parent width is %d', parentWidth)
70
68 for (const [ index, el ] of this.itemsRendered.toArray().entries()) { 71 for (const [ index, el ] of this.itemsRendered.toArray().entries()) {
69 accWidth += el.nativeElement.getBoundingClientRect().width 72 accWidth += el.nativeElement.getBoundingClientRect().width
70 if (showItemsUntilIndexExcluded === undefined) { 73 if (showItemsUntilIndexExcluded === undefined) {
@@ -76,6 +79,8 @@ export class ListOverflowComponent<T extends ListOverflowItem> implements AfterV
76 e.style.visibility = shouldBeVisible ? 'inherit' : 'hidden' 79 e.style.visibility = shouldBeVisible ? 'inherit' : 'hidden'
77 } 80 }
78 81
82 logger('Accumulated children width is %d so exclude index is %d', accWidth, showItemsUntilIndexExcluded)
83
79 this.showItemsUntilIndexExcluded = showItemsUntilIndexExcluded 84 this.showItemsUntilIndexExcluded = showItemsUntilIndexExcluded
80 this.cdr.markForCheck() 85 this.cdr.markForCheck()
81 } 86 }
diff --git a/client/src/app/shared/shared-main/misc/simple-search-input.component.html b/client/src/app/shared/shared-main/misc/simple-search-input.component.html
index 1e2f6c6a9..386d26116 100644
--- a/client/src/app/shared/shared-main/misc/simple-search-input.component.html
+++ b/client/src/app/shared/shared-main/misc/simple-search-input.component.html
@@ -1,17 +1,10 @@
1<div class="root"> 1<div class="root">
2 <div class="input-group has-feedback has-clear"> 2 <div class="input-group has-clear">
3 <input 3 <input #ref type="text" class="last-in-group"
4 #ref 4 [(ngModel)]="value" (keyup.enter)="sendSearch()" [hidden]="!inputShown" [name]="name" [placeholder]="placeholder"
5 type="text"
6 [(ngModel)]="value"
7 (keyup.enter)="sendSearch()"
8 [hidden]="!inputShown"
9 [name]="name"
10 [placeholder]="placeholder"
11 > 5 >
12 6
13 <a class="glyphicon glyphicon-remove-sign form-control-feedback form-control-clear" (click)="onResetFilter()"></a> 7 <my-global-icon iconName="cross" role="button" class="form-control-clear" title="Clear filter" i18n-title (click)="onResetFilter()"></my-global-icon>
14 <span class="sr-only" i18n>Clear filters</span>
15 </div> 8 </div>
16 9
17 <my-global-icon iconName="search" aria-label="Search" role="button" (click)="onIconClick()" [title]="iconTitle"></my-global-icon> 10 <my-global-icon iconName="search" aria-label="Search" role="button" (click)="onIconClick()" [title]="iconTitle"></my-global-icon>
diff --git a/client/src/app/shared/shared-main/misc/simple-search-input.component.scss b/client/src/app/shared/shared-main/misc/simple-search-input.component.scss
index d5fcff760..ee0f7a8d2 100644
--- a/client/src/app/shared/shared-main/misc/simple-search-input.component.scss
+++ b/client/src/app/shared/shared-main/misc/simple-search-input.component.scss
@@ -5,7 +5,7 @@
5 display: flex; 5 display: flex;
6} 6}
7 7
8my-global-icon { 8.root > my-global-icon {
9 @include margin-left(10px); 9 @include margin-left(10px);
10 10
11 height: 28px; 11 height: 28px;
@@ -25,3 +25,7 @@ input {
25 box-shadow: 0 0 5px 0 #a5a5a5; 25 box-shadow: 0 0 5px 0 #a5a5a5;
26 } 26 }
27} 27}
28
29.input-group > my-global-icon {
30 width: 20px;
31}
diff --git a/client/src/app/shared/shared-main/misc/top-menu-dropdown.component.html b/client/src/app/shared/shared-main/misc/top-menu-dropdown.component.html
index d884e75b2..d96fdbdc6 100644
--- a/client/src/app/shared/shared-main/misc/top-menu-dropdown.component.html
+++ b/client/src/app/shared/shared-main/misc/top-menu-dropdown.component.html
@@ -1,7 +1,13 @@
1<div class="sub-menu" [ngClass]="{ 'sub-menu-fixed': !isBroadcastMessageDisplayed, 'no-scroll': isModalOpened }"> 1<div class="sub-menu" [ngClass]="{ 'sub-menu-fixed': !isBroadcastMessageDisplayed, 'no-scroll': isModalOpened }">
2 <ng-container *ngFor="let menuEntry of menuEntries; index as id"> 2 <ng-container *ngFor="let menuEntry of menuEntries; index as id">
3 3
4 <a *ngIf="menuEntry.routerLink && isDisplayed(menuEntry)" [routerLink]="menuEntry.routerLink" routerLinkActive="active" class="title-page title-page-settings" #routerLink (click)="onActiveLinkScrollToTop(routerLink)">{{ menuEntry.label }}</a> 4 <a
5 *ngIf="menuEntry.routerLink && isDisplayed(menuEntry)" class="sub-menu-entry"
6 [routerLink]="menuEntry.routerLink" routerLinkActive="active" #routerLink
7 (click)="onActiveLinkScrollToTop(routerLink)"
8 >
9 {{ menuEntry.label }}
10 </a>
5 11
6 <div *ngIf="!menuEntry.routerLink && isDisplayed(menuEntry)" ngbDropdown class="parent-entry" 12 <div *ngIf="!menuEntry.routerLink && isDisplayed(menuEntry)" ngbDropdown class="parent-entry"
7 #dropdown="ngbDropdown" autoClose="true" container="body"> 13 #dropdown="ngbDropdown" autoClose="true" container="body">
@@ -10,7 +16,7 @@
10 tabindex=0 16 tabindex=0
11 [ngClass]="{ active: !!suffixLabels[menuEntry.label] }" 17 [ngClass]="{ active: !!suffixLabels[menuEntry.label] }"
12 (click)="openModal(id)" (keydown.enter)="openModal(id)" 18 (click)="openModal(id)" (keydown.enter)="openModal(id)"
13 role="button" class="title-page title-page-settings"> 19 role="button" class="sub-menu-entry">
14 <ng-container i18n>{{ menuEntry.label }}</ng-container> 20 <ng-container i18n>{{ menuEntry.label }}</ng-container>
15 </span> 21 </span>
16 22
@@ -19,7 +25,7 @@
19 tabindex=0 25 tabindex=0
20 [ngClass]="{ active: !!suffixLabels[menuEntry.label] }" ngbDropdownAnchor 26 [ngClass]="{ active: !!suffixLabels[menuEntry.label] }" ngbDropdownAnchor
21 (click)="dropdownAnchorClicked(dropdown)" (keydown.enter)="dropdownAnchorClicked(dropdown)" 27 (click)="dropdownAnchorClicked(dropdown)" (keydown.enter)="dropdownAnchorClicked(dropdown)"
22 role="button" class="title-page title-page-settings" 28 role="button" class="sub-menu-entry"
23 > 29 >
24 <ng-container i18n>{{ menuEntry.label }}</ng-container> 30 <ng-container i18n>{{ menuEntry.label }}</ng-container>
25 </span> 31 </span>
diff --git a/client/src/app/shared/shared-main/shared-main.module.ts b/client/src/app/shared/shared-main/shared-main.module.ts
index 5629640bc..89f43239f 100644
--- a/client/src/app/shared/shared-main/shared-main.module.ts
+++ b/client/src/app/shared/shared-main/shared-main.module.ts
@@ -34,7 +34,7 @@ import { ActionDropdownComponent, ButtonComponent, DeleteButtonComponent, EditBu
34import { CustomPageService } from './custom-page' 34import { CustomPageService } from './custom-page'
35import { DateToggleComponent } from './date' 35import { DateToggleComponent } from './date'
36import { FeedComponent } from './feeds' 36import { FeedComponent } from './feeds'
37import { LoaderComponent, SmallLoaderComponent } from './loaders' 37import { LoaderComponent } from './loaders'
38import { 38import {
39 ChannelsSetupMessageComponent, 39 ChannelsSetupMessageComponent,
40 HelpComponent, 40 HelpComponent,
@@ -97,7 +97,6 @@ import { VideoChannelService } from './video-channel'
97 FeedComponent, 97 FeedComponent,
98 98
99 LoaderComponent, 99 LoaderComponent,
100 SmallLoaderComponent,
101 100
102 ChannelsSetupMessageComponent, 101 ChannelsSetupMessageComponent,
103 HelpComponent, 102 HelpComponent,
@@ -157,7 +156,6 @@ import { VideoChannelService } from './video-channel'
157 FeedComponent, 156 FeedComponent,
158 157
159 LoaderComponent, 158 LoaderComponent,
160 SmallLoaderComponent,
161 159
162 ChannelsSetupMessageComponent, 160 ChannelsSetupMessageComponent,
163 HelpComponent, 161 HelpComponent,
diff --git a/client/src/app/shared/shared-main/users/user-quota.component.html b/client/src/app/shared/shared-main/users/user-quota.component.html
index dd1fc20d0..0e0d38c2a 100644
--- a/client/src/app/shared/shared-main/users/user-quota.component.html
+++ b/client/src/app/shared/shared-main/users/user-quota.component.html
@@ -12,7 +12,7 @@
12 <div *ngIf="hasDailyQuota()" class="mt-3"> 12 <div *ngIf="hasDailyQuota()" class="mt-3">
13 <label class="user-quota-title" tabindex="0" i18n>Daily video quota</label> 13 <label class="user-quota-title" tabindex="0" i18n>Daily video quota</label>
14 <div class="progress" tabindex="0" [ngbTooltip]="titleVideoQuotaDaily()"> 14 <div class="progress" tabindex="0" [ngbTooltip]="titleVideoQuotaDaily()">
15 <div class="progress-bar secondary" role="progressbar" [style]="{ width: userVideoQuotaDailyPercentage + '%' }" 15 <div class="progress-bar" role="progressbar" [style]="{ width: userVideoQuotaDailyPercentage + '%' }"
16 [attr.aria-valuenow]="userVideoQuotaUsedDaily" aria-valuemin="0" [attr.aria-valuemax]="user.videoQuotaDaily"></div> 16 [attr.aria-valuenow]="userVideoQuotaUsedDaily" aria-valuemin="0" [attr.aria-valuemax]="user.videoQuotaDaily"></div>
17 <span>{{ userVideoQuotaUsedDaily | bytes: 1 }}</span> 17 <span>{{ userVideoQuotaUsedDaily | bytes: 1 }}</span>
18 <span>{{ userVideoQuotaDaily }}</span> 18 <span>{{ userVideoQuotaDaily }}</span>
diff --git a/client/src/app/shared/shared-main/users/user-quota.component.scss b/client/src/app/shared/shared-main/users/user-quota.component.scss
index 70571bde6..f3e86ce78 100644
--- a/client/src/app/shared/shared-main/users/user-quota.component.scss
+++ b/client/src/app/shared/shared-main/users/user-quota.component.scss
@@ -1,11 +1,6 @@
1@use '_variables' as *; 1@use '_variables' as *;
2@use '_mixins' as *; 2@use '_mixins' as *;
3 3
4label {
5 font-weight: $font-regular;
6 font-size: 100%;
7}
8
9.user-quota { 4.user-quota {
10 label { 5 label {
11 @include margin-right(5px); 6 @include margin-right(5px);
diff --git a/client/src/app/shared/shared-main/video-channel/video-channel.model.ts b/client/src/app/shared/shared-main/video-channel/video-channel.model.ts
index 32376bf62..62bd94349 100644
--- a/client/src/app/shared/shared-main/video-channel/video-channel.model.ts
+++ b/client/src/app/shared/shared-main/video-channel/video-channel.model.ts
@@ -27,6 +27,7 @@ export class VideoChannel extends Actor implements ServerVideoChannel {
27 videosCount?: number 27 videosCount?: number
28 28
29 viewsPerDay?: ViewsPerDate[] 29 viewsPerDay?: ViewsPerDate[]
30 totalViews?: number
30 31
31 static GET_ACTOR_AVATAR_URL (actor: { avatars: { width: number, url?: string, path: string }[] }, size: number) { 32 static GET_ACTOR_AVATAR_URL (actor: { avatars: { width: number, url?: string, path: string }[] }, size: number) {
32 return Actor.GET_ACTOR_AVATAR_URL(actor, size) 33 return Actor.GET_ACTOR_AVATAR_URL(actor, size)
@@ -74,6 +75,10 @@ export class VideoChannel extends Actor implements ServerVideoChannel {
74 this.viewsPerDay = hash.viewsPerDay.map(v => ({ ...v, date: new Date(v.date) })) 75 this.viewsPerDay = hash.viewsPerDay.map(v => ({ ...v, date: new Date(v.date) }))
75 } 76 }
76 77
78 if (hash.totalViews !== null && hash.totalViews !== undefined) {
79 this.totalViews = hash.totalViews
80 }
81
77 if (hash.ownerAccount) { 82 if (hash.ownerAccount) {
78 this.ownerAccount = hash.ownerAccount 83 this.ownerAccount = hash.ownerAccount
79 this.ownerBy = Actor.CREATE_BY_STRING(hash.ownerAccount.name, hash.ownerAccount.host) 84 this.ownerBy = Actor.CREATE_BY_STRING(hash.ownerAccount.name, hash.ownerAccount.host)
diff --git a/client/src/app/shared/shared-main/video/video.model.ts b/client/src/app/shared/shared-main/video/video.model.ts
index 022bb95ad..2e4ab87d7 100644
--- a/client/src/app/shared/shared-main/video/video.model.ts
+++ b/client/src/app/shared/shared-main/video/video.model.ts
@@ -1,6 +1,6 @@
1import { AuthUser } from '@app/core' 1import { AuthUser } from '@app/core'
2import { User } from '@app/core/users/user.model' 2import { User } from '@app/core/users/user.model'
3import { durationToString, getAbsoluteAPIUrl, getAbsoluteEmbedUrl } from '@app/helpers' 3import { durationToString, prepareIcu, getAbsoluteAPIUrl, getAbsoluteEmbedUrl } from '@app/helpers'
4import { Actor } from '@app/shared/shared-main/account/actor.model' 4import { Actor } from '@app/shared/shared-main/account/actor.model'
5import { buildVideoWatchPath } from '@shared/core-utils' 5import { buildVideoWatchPath } from '@shared/core-utils'
6import { peertubeTranslate } from '@shared/core-utils/i18n' 6import { peertubeTranslate } from '@shared/core-utils/i18n'
@@ -19,6 +19,9 @@ import {
19} from '@shared/models' 19} from '@shared/models'
20 20
21export class Video implements VideoServerModel { 21export class Video implements VideoServerModel {
22 private static readonly viewsICU = prepareIcu($localize`{views, plural, =0 {No view} =1 {1 view} other {{views} views}}`)
23 private static readonly viewersICU = prepareIcu($localize`{viewers, plural, =0 {No viewers} =1 {1 viewer} other {{viewers} viewers}}`)
24
22 byVideoChannel: string 25 byVideoChannel: string
23 byAccount: string 26 byAccount: string
24 27
@@ -269,12 +272,10 @@ export class Video implements VideoServerModel {
269 } 272 }
270 273
271 getExactNumberOfViews () { 274 getExactNumberOfViews () {
272 if (this.views < 1000) return ''
273
274 if (this.isLive) { 275 if (this.isLive) {
275 return $localize`${this.views} viewers` 276 return Video.viewersICU({ viewers: this.viewers }, $localize`${this.viewers} viewer(s)`)
276 } 277 }
277 278
278 return $localize`${this.views} views` 279 return Video.viewsICU({ views: this.views }, $localize`{${this.views} view(s)}`)
279 } 280 }
280} 281}