--- /dev/null
+export * from './my-account-routing.module'
+export * from './my-account.component'
+export * from './my-account.module'
--- /dev/null
+import { NgModule } from '@angular/core'
+import { RouterModule, Routes } from '@angular/router'
+import { MetaGuard } from '@ngx-meta/core'
+import { LoginGuard } from '../core'
+import { MyAccountComponent } from './my-account.component'
+import { MyAccountSettingsComponent } from './my-account-settings/my-account-settings.component'
+import { MyAccountVideosComponent } from './my-account-videos/my-account-videos.component'
+import { MyAccountVideoChannelsComponent } from '@app/+my-account/my-account-video-channels/my-account-video-channels.component'
+import { MyAccountVideoChannelCreateComponent } from '@app/+my-account/my-account-video-channels/my-account-video-channel-create.component'
+import { MyAccountVideoChannelUpdateComponent } from '@app/+my-account/my-account-video-channels/my-account-video-channel-update.component'
+
+const myAccountRoutes: Routes = [
+ {
+ path: '',
+ component: MyAccountComponent,
+ canActivateChild: [ MetaGuard, LoginGuard ],
+ children: [
+ {
+ path: 'settings',
+ component: MyAccountSettingsComponent,
+ data: {
+ meta: {
+ title: 'Account settings'
+ }
+ }
+ },
+ {
+ path: 'video-channels',
+ component: MyAccountVideoChannelsComponent,
+ data: {
+ meta: {
+ title: 'Account video channels'
+ }
+ }
+ },
+ {
+ path: 'video-channels/create',
+ component: MyAccountVideoChannelCreateComponent,
+ data: {
+ meta: {
+ title: 'Create new video channel'
+ }
+ }
+ },
+ {
+ path: 'video-channels/update/:videoChannelId',
+ component: MyAccountVideoChannelUpdateComponent,
+ data: {
+ meta: {
+ title: 'Update video channel'
+ }
+ }
+ },
+ {
+ path: 'videos',
+ component: MyAccountVideosComponent,
+ data: {
+ meta: {
+ title: 'Account videos'
+ }
+ }
+ }
+ ]
+ }
+]
+
+@NgModule({
+ imports: [ RouterModule.forChild(myAccountRoutes) ],
+ exports: [ RouterModule ]
+})
+export class MyAccountRoutingModule {}
--- /dev/null
+export * from './my-account-change-password.component'
--- /dev/null
+<div *ngIf="error" class="alert alert-danger">{{ error }}</div>
+
+<form role="form" (ngSubmit)="changePassword()" [formGroup]="form">
+
+ <label for="new-password">Change password</label>
+ <input
+ type="password" id="new-password" placeholder="New password"
+ formControlName="new-password" [ngClass]="{ 'input-error': formErrors['new-password'] }"
+ >
+ <div *ngIf="formErrors['new-password']" class="form-error">
+ {{ formErrors['new-password'] }}
+ </div>
+
+ <input
+ type="password" id="new-confirmed-password" placeholder="Confirm new password"
+ formControlName="new-confirmed-password"
+ >
+
+ <input type="submit" value="Change password" [disabled]="!form.valid">
+</form>
--- /dev/null
+@import '_variables';
+@import '_mixins';
+
+input[type=password] {
+ @include peertube-input-text(340px);
+ display: block;
+
+ &#new-confirmed-password {
+ margin-top: 15px;
+ }
+}
+
+input[type=submit] {
+ @include peertube-button;
+ @include orange-button;
+
+ margin-top: 15px;
+}
+
--- /dev/null
+import { Component, OnInit } from '@angular/core'
+import { FormBuilder, FormGroup } from '@angular/forms'
+import { NotificationsService } from 'angular2-notifications'
+import { FormReactive, USER_PASSWORD, UserService } from '../../../shared'
+
+@Component({
+ selector: 'my-account-change-password',
+ templateUrl: './my-account-change-password.component.html',
+ styleUrls: [ './my-account-change-password.component.scss' ]
+})
+export class MyAccountChangePasswordComponent extends FormReactive implements OnInit {
+ error: string = null
+
+ form: FormGroup
+ formErrors = {
+ 'new-password': '',
+ 'new-confirmed-password': ''
+ }
+ validationMessages = {
+ 'new-password': USER_PASSWORD.MESSAGES,
+ 'new-confirmed-password': USER_PASSWORD.MESSAGES
+ }
+
+ constructor (
+ private formBuilder: FormBuilder,
+ private notificationsService: NotificationsService,
+ private userService: UserService
+ ) {
+ super()
+ }
+
+ buildForm () {
+ this.form = this.formBuilder.group({
+ 'new-password': [ '', USER_PASSWORD.VALIDATORS ],
+ 'new-confirmed-password': [ '', USER_PASSWORD.VALIDATORS ]
+ })
+
+ this.form.valueChanges.subscribe(data => this.onValueChanged(data))
+ }
+
+ ngOnInit () {
+ this.buildForm()
+ }
+
+ changePassword () {
+ const newPassword = this.form.value['new-password']
+ const newConfirmedPassword = this.form.value['new-confirmed-password']
+
+ this.error = null
+
+ if (newPassword !== newConfirmedPassword) {
+ this.error = 'The new password and the confirmed password do not correspond.'
+ return
+ }
+
+ this.userService.changePassword(newPassword).subscribe(
+ () => this.notificationsService.success('Success', 'Password updated.'),
+
+ err => this.error = err.message
+ )
+ }
+}
--- /dev/null
+export * from './my-account-profile.component'
--- /dev/null
+<div *ngIf="error" class="alert alert-danger">{{ error }}</div>
+
+<form role="form" (ngSubmit)="updateMyProfile()" [formGroup]="form">
+
+ <label for="display-name">Display name</label>
+ <input
+ type="text" id="display-name"
+ formControlName="display-name" [ngClass]="{ 'input-error': formErrors['display-name'] }"
+ >
+ <div *ngIf="formErrors['display-name']" class="form-error">
+ {{ formErrors['display-name'] }}
+ </div>
+
+ <label for="description">Description</label>
+ <textarea
+ id="description" formControlName="description"
+ [ngClass]="{ 'input-error': formErrors['description'] }"
+ ></textarea>
+ <div *ngIf="formErrors.description" class="form-error">
+ {{ formErrors.description }}
+ </div>
+
+ <input type="submit" value="Update my profile" [disabled]="!form.valid">
+</form>
--- /dev/null
+@import '_variables';
+@import '_mixins';
+
+input[type=text] {
+ @include peertube-input-text(340px);
+
+ display: block;
+ margin-bottom: 15px;
+}
+
+textarea {
+ @include peertube-textarea(500px, 150px);
+
+ display: block;
+}
+
+input[type=submit] {
+ @include peertube-button;
+ @include orange-button;
+
+ margin-top: 15px;
+}
+
--- /dev/null
+import { Component, Input, OnInit } from '@angular/core'
+import { FormBuilder, FormGroup } from '@angular/forms'
+import { NotificationsService } from 'angular2-notifications'
+import { FormReactive, USER_DESCRIPTION, USER_DISPLAY_NAME, UserService } from '../../../shared'
+import { User } from '@app/shared'
+
+@Component({
+ selector: 'my-account-profile',
+ templateUrl: './my-account-profile.component.html',
+ styleUrls: [ './my-account-profile.component.scss' ]
+})
+export class MyAccountProfileComponent extends FormReactive implements OnInit {
+ @Input() user: User = null
+
+ error: string = null
+
+ form: FormGroup
+ formErrors = {
+ 'display-name': '',
+ 'description': ''
+ }
+ validationMessages = {
+ 'display-name': USER_DISPLAY_NAME.MESSAGES,
+ 'description': USER_DESCRIPTION.MESSAGES
+ }
+
+ constructor (
+ private formBuilder: FormBuilder,
+ private notificationsService: NotificationsService,
+ private userService: UserService
+ ) {
+ super()
+ }
+
+ buildForm () {
+ this.form = this.formBuilder.group({
+ 'display-name': [ this.user.account.displayName, USER_DISPLAY_NAME.VALIDATORS ],
+ 'description': [ this.user.account.description, USER_DESCRIPTION.VALIDATORS ]
+ })
+
+ this.form.valueChanges.subscribe(data => this.onValueChanged(data))
+ }
+
+ ngOnInit () {
+ this.buildForm()
+ }
+
+ updateMyProfile () {
+ const displayName = this.form.value['display-name']
+ const description = this.form.value['description']
+
+ this.error = null
+
+ this.userService.updateMyProfile({ displayName, description }).subscribe(
+ () => {
+ this.user.account.displayName = displayName
+ this.user.account.description = description
+
+ this.notificationsService.success('Success', 'Profile updated.')
+ },
+
+ err => this.error = err.message
+ )
+ }
+}
--- /dev/null
+<div class="user">
+ <img [src]="user.accountAvatarUrl" alt="Avatar" />
+
+ <div class="user-info">
+ <div class="user-info-names">
+ <div class="user-info-display-name">{{ user.account.displayName }}</div>
+ <div class="user-info-username">{{ user.username }}</div>
+ </div>
+ <div class="user-info-followers">{{ user.account?.followersCount }} subscribers</div>
+ </div>
+</div>
+
+<div class="button-file">
+ <span>Change your avatar</span>
+ <input #avatarfileInput type="file" name="avatarfile" id="avatarfile" [accept]="avatarExtensions" (change)="changeAvatar()" />
+</div>
+<div class="file-max-size">(extensions: {{ avatarExtensions }}, max size: {{ maxAvatarSize | bytes }})</div>
+
+<div class="user-quota">
+ <span class="user-quota-label">Video quota:</span> {{ userVideoQuotaUsed | bytes: 0 }} / {{ userVideoQuota }}
+</div>
+
+<ng-template [ngIf]="user && user.account">
+ <div class="account-title">Profile</div>
+ <my-account-profile [user]="user"></my-account-profile>
+</ng-template>
+
+<div class="account-title">Password</div>
+<my-account-change-password></my-account-change-password>
+
+<div class="account-title">Video settings</div>
+<my-account-video-settings [user]="user"></my-account-video-settings>
--- /dev/null
+@import '_variables';
+@import '_mixins';
+
+.user {
+ display: flex;
+
+ img {
+ @include avatar(50px);
+
+ margin-right: 15px;
+ }
+
+ .user-info {
+ .user-info-names {
+ display: flex;
+ align-items: center;
+
+ .user-info-display-name {
+ font-size: 20px;
+ font-weight: $font-bold;
+ }
+
+ .user-info-username {
+ margin-left: 7px;
+ position: relative;
+ top: 2px;
+ font-size: 14px;
+ color: #777272;
+ }
+ }
+
+ .user-info-followers {
+ font-size: 15px;
+ }
+ }
+}
+
+.button-file {
+ @include peertube-button-file(160px);
+
+ margin-top: 10px;
+ margin-bottom: 5px;
+}
+
+.file-max-size {
+ display: inline-block;
+ font-size: 13px;
+
+ position: relative;
+ top: -10px;
+}
+
+.user-quota {
+ font-size: 15px;
+ margin-top: 20px;
+
+ .user-quota-label {
+ font-weight: $font-semibold;
+ }
+}
+
+.account-title {
+ @include in-content-small-title;
+
+ margin-top: 55px;
+ margin-bottom: 30px;
+}
--- /dev/null
+import { Component, OnInit, ViewChild } from '@angular/core'
+import { NotificationsService } from 'angular2-notifications'
+import { BytesPipe } from 'ngx-pipes'
+import { AuthService } from '../../core'
+import { ServerService } from '../../core/server'
+import { User } from '../../shared'
+import { UserService } from '../../shared/users'
+
+@Component({
+ selector: 'my-account-settings',
+ templateUrl: './my-account-settings.component.html',
+ styleUrls: [ './my-account-settings.component.scss' ]
+})
+export class MyAccountSettingsComponent implements OnInit {
+ @ViewChild('avatarfileInput') avatarfileInput
+
+ user: User = null
+ userVideoQuota = '0'
+ userVideoQuotaUsed = 0
+
+ constructor (
+ private userService: UserService,
+ private authService: AuthService,
+ private serverService: ServerService,
+ private notificationsService: NotificationsService
+ ) {}
+
+ ngOnInit () {
+ this.user = this.authService.getUser()
+
+ this.authService.userInformationLoaded.subscribe(
+ () => {
+ if (this.user.videoQuota !== -1) {
+ this.userVideoQuota = new BytesPipe().transform(this.user.videoQuota, 0).toString()
+ } else {
+ this.userVideoQuota = 'Unlimited'
+ }
+ }
+ )
+
+ this.userService.getMyVideoQuotaUsed()
+ .subscribe(data => this.userVideoQuotaUsed = data.videoQuotaUsed)
+ }
+
+ changeAvatar () {
+ const avatarfile = this.avatarfileInput.nativeElement.files[ 0 ]
+
+ const formData = new FormData()
+ formData.append('avatarfile', avatarfile)
+
+ this.userService.changeAvatar(formData)
+ .subscribe(
+ data => {
+ this.notificationsService.success('Success', 'Avatar changed.')
+
+ this.user.account.avatar = data.avatar
+ },
+
+ err => this.notificationsService.error('Error', err.message)
+ )
+ }
+
+ get maxAvatarSize () {
+ return this.serverService.getConfig().avatar.file.size.max
+ }
+
+ get avatarExtensions () {
+ return this.serverService.getConfig().avatar.file.extensions.join(',')
+ }
+}
--- /dev/null
+export * from './my-account-video-settings.component'
--- /dev/null
+<form role="form" (ngSubmit)="updateDetails()" [formGroup]="form">
+ <div class="form-group">
+ <label for="nsfwPolicy">Default policy on videos containing sensitive content</label>
+ <my-help helpType="custom" customHtml="With <strong>Do not list</strong> or <strong>Blur thumbnails</strong>, a confirmation will be requested to watch the video."></my-help>
+
+ <div class="peertube-select-container">
+ <select id="nsfwPolicy" formControlName="nsfwPolicy">
+ <option value="do_not_list">Do not list</option>
+ <option value="blur">Blur thumbnails</option>
+ <option value="display">Display</option>
+ </select>
+ </div>
+ </div>
+
+ <div class="form-group">
+ <input
+ type="checkbox" id="autoPlayVideo"
+ formControlName="autoPlayVideo"
+ >
+ <label for="autoPlayVideo"></label>
+ <label for="autoPlayVideo">Automatically plays video</label>
+ </div>
+
+ <input type="submit" value="Save" [disabled]="!form.valid">
+</form>
--- /dev/null
+@import '_variables';
+@import '_mixins';
+
+input[type=checkbox] {
+ @include peertube-checkbox(1px);
+}
+
+input[type=submit] {
+ @include peertube-button;
+ @include orange-button;
+
+ display: block;
+ margin-top: 15px;
+}
+
+.peertube-select-container {
+ @include peertube-select-container(340px);
+
+ margin-bottom: 30px;
+}
\ No newline at end of file
--- /dev/null
+import { Component, Input, OnInit } from '@angular/core'
+import { FormBuilder, FormGroup } from '@angular/forms'
+import { NotificationsService } from 'angular2-notifications'
+import { UserUpdateMe } from '../../../../../../shared'
+import { AuthService } from '../../../core'
+import { FormReactive, User, UserService } from '../../../shared'
+
+@Component({
+ selector: 'my-account-video-settings',
+ templateUrl: './my-account-video-settings.component.html',
+ styleUrls: [ './my-account-video-settings.component.scss' ]
+})
+export class MyAccountVideoSettingsComponent extends FormReactive implements OnInit {
+ @Input() user: User = null
+
+ form: FormGroup
+ formErrors = {}
+ validationMessages = {}
+
+ constructor (
+ private authService: AuthService,
+ private formBuilder: FormBuilder,
+ private notificationsService: NotificationsService,
+ private userService: UserService
+ ) {
+ super()
+ }
+
+ buildForm () {
+ this.form = this.formBuilder.group({
+ nsfwPolicy: [ this.user.nsfwPolicy ],
+ autoPlayVideo: [ this.user.autoPlayVideo ]
+ })
+
+ this.form.valueChanges.subscribe(data => this.onValueChanged(data))
+ }
+
+ ngOnInit () {
+ this.buildForm()
+ }
+
+ updateDetails () {
+ const nsfwPolicy = this.form.value['nsfwPolicy']
+ const autoPlayVideo = this.form.value['autoPlayVideo']
+ const details: UserUpdateMe = {
+ nsfwPolicy,
+ autoPlayVideo
+ }
+
+ this.userService.updateMyProfile(details).subscribe(
+ () => {
+ this.notificationsService.success('Success', 'Information updated.')
+
+ this.authService.refreshUserInformation()
+ },
+
+ err => this.notificationsService.error('Error', err.message)
+ )
+ }
+}
--- /dev/null
+import { Component, OnInit } from '@angular/core'
+import { Router } from '@angular/router'
+import { NotificationsService } from 'angular2-notifications'
+import 'rxjs/add/observable/from'
+import 'rxjs/add/operator/concatAll'
+import { MyAccountVideoChannelEdit } from './my-account-video-channel-edit'
+import { FormBuilder, FormGroup } from '@angular/forms'
+import { VideoChannelCreate } from '../../../../../shared/models/videos'
+import {
+ VIDEO_CHANNEL_DESCRIPTION,
+ VIDEO_CHANNEL_DISPLAY_NAME,
+ VIDEO_CHANNEL_SUPPORT
+} from '@app/shared/forms/form-validators/video-channel'
+import { VideoChannelService } from '@app/shared/video-channel/video-channel.service'
+import { AuthService } from '@app/core'
+
+@Component({
+ selector: 'my-account-video-channel-create',
+ templateUrl: './my-account-video-channel-edit.component.html',
+ styleUrls: [ './my-account-video-channel-edit.component.scss' ]
+})
+export class MyAccountVideoChannelCreateComponent extends MyAccountVideoChannelEdit implements OnInit {
+ error: string
+
+ form: FormGroup
+ formErrors = {
+ 'display-name': '',
+ 'description': '',
+ 'support': ''
+ }
+ validationMessages = {
+ 'display-name': VIDEO_CHANNEL_DISPLAY_NAME.MESSAGES,
+ 'description': VIDEO_CHANNEL_DESCRIPTION.MESSAGES,
+ 'support': VIDEO_CHANNEL_SUPPORT.MESSAGES
+ }
+
+ constructor (
+ private authService: AuthService,
+ private notificationsService: NotificationsService,
+ private router: Router,
+ private formBuilder: FormBuilder,
+ private videoChannelService: VideoChannelService
+ ) {
+ super()
+ }
+
+ buildForm () {
+ this.form = this.formBuilder.group({
+ 'display-name': [ '', VIDEO_CHANNEL_DISPLAY_NAME.VALIDATORS ],
+ description: [ '', VIDEO_CHANNEL_DESCRIPTION.VALIDATORS ],
+ support: [ '', VIDEO_CHANNEL_SUPPORT.VALIDATORS ]
+ })
+
+ this.form.valueChanges.subscribe(data => this.onValueChanged(data))
+ }
+
+ ngOnInit () {
+ this.buildForm()
+ }
+
+ formValidated () {
+ this.error = undefined
+
+ const body = this.form.value
+ const videoChannelCreate: VideoChannelCreate = {
+ displayName: body['display-name'],
+ description: body.description || undefined,
+ support: body.support || undefined
+ }
+
+ this.videoChannelService.createVideoChannel(videoChannelCreate).subscribe(
+ () => {
+ this.authService.refreshUserInformation()
+ this.notificationsService.success('Success', `Video channel ${videoChannelCreate.displayName} created.`)
+ this.router.navigate([ '/my-account', 'video-channels' ])
+ },
+
+ err => this.error = err.message
+ )
+ }
+
+ isCreation () {
+ return true
+ }
+
+ getFormButtonTitle () {
+ return 'Create'
+ }
+}
--- /dev/null
+<div class="form-sub-title" *ngIf="isCreation() === true">Create a video channel</div>
+<div class="form-sub-title" *ngIf="isCreation() === false">Update {{ videoChannel?.displayName }}</div>
+
+<div *ngIf="error" class="alert alert-danger">{{ error }}</div>
+
+<form role="form" (ngSubmit)="formValidated()" [formGroup]="form">
+ <div class="form-group">
+ <label for="display-name">Display name</label>
+ <input
+ type="text" id="display-name"
+ formControlName="display-name" [ngClass]="{ 'input-error': formErrors['display-name'] }"
+ >
+ <div *ngIf="formErrors['display-name']" class="form-error">
+ {{ formErrors['display-name'] }}
+ </div>
+ </div>
+
+ <div class="form-group">
+ <label for="description">Description</label>
+ <textarea
+ id="description" formControlName="description"
+ [ngClass]="{ 'input-error': formErrors['description'] }"
+ ></textarea>
+ <div *ngIf="formErrors.description" class="form-error">
+ {{ formErrors.description }}
+ </div>
+ </div>
+
+ <div class="form-group">
+ <label for="support">Support</label>
+ <my-help helpType="markdownEnhanced" preHtml="Short text to tell people how they can support your channel (membership platform...)."></my-help>
+ <my-markdown-textarea
+ id="support" formControlName="support" textareaWidth="500px" [previewColumn]="true" markdownType="enhanced"
+ [classes]="{ 'input-error': formErrors['support'] }"
+ ></my-markdown-textarea>
+ <div *ngIf="formErrors.support" class="form-error">
+ {{ formErrors.support }}
+ </div>
+ </div>
+
+ <input type="submit" value="{{ getFormButtonTitle() }}" [disabled]="!form.valid">
+</form>
--- /dev/null
+@import '_variables';
+@import '_mixins';
+
+.form-sub-title {
+ margin-bottom: 20px;
+}
+
+input[type=text] {
+ @include peertube-input-text(340px);
+
+ display: block;
+}
+
+textarea {
+ @include peertube-textarea(500px, 150px);
+
+ display: block;
+}
+
+.peertube-select-container {
+ @include peertube-select-container(340px);
+}
+
+input[type=submit] {
+ @include peertube-button;
+ @include orange-button;
+}
\ No newline at end of file
--- /dev/null
+import { FormReactive } from '@app/shared'
+
+export abstract class MyAccountVideoChannelEdit extends FormReactive {
+ abstract isCreation (): boolean
+ abstract getFormButtonTitle (): string
+}
--- /dev/null
+import { Component, OnInit, OnDestroy } from '@angular/core'
+import { ActivatedRoute, Router } from '@angular/router'
+import { NotificationsService } from 'angular2-notifications'
+import 'rxjs/add/observable/from'
+import 'rxjs/add/operator/concatAll'
+import { MyAccountVideoChannelEdit } from './my-account-video-channel-edit'
+import { FormBuilder, FormGroup } from '@angular/forms'
+import { VideoChannelUpdate } from '../../../../../shared/models/videos'
+import {
+ VIDEO_CHANNEL_DESCRIPTION,
+ VIDEO_CHANNEL_DISPLAY_NAME,
+ VIDEO_CHANNEL_SUPPORT
+} from '@app/shared/forms/form-validators/video-channel'
+import { VideoChannelService } from '@app/shared/video-channel/video-channel.service'
+import { Subscription } from 'rxjs/Subscription'
+import { VideoChannel } from '@app/shared/video-channel/video-channel.model'
+import { AuthService } from '@app/core'
+
+@Component({
+ selector: 'my-account-video-channel-update',
+ templateUrl: './my-account-video-channel-edit.component.html',
+ styleUrls: [ './my-account-video-channel-edit.component.scss' ]
+})
+export class MyAccountVideoChannelUpdateComponent extends MyAccountVideoChannelEdit implements OnInit, OnDestroy {
+ error: string
+
+ form: FormGroup
+ formErrors = {
+ 'display-name': '',
+ 'description': '',
+ 'support': ''
+ }
+ validationMessages = {
+ 'display-name': VIDEO_CHANNEL_DISPLAY_NAME.MESSAGES,
+ 'description': VIDEO_CHANNEL_DESCRIPTION.MESSAGES,
+ 'support': VIDEO_CHANNEL_SUPPORT.MESSAGES
+ }
+
+ private videoChannelToUpdate: VideoChannel
+ private paramsSub: Subscription
+
+ constructor (
+ private authService: AuthService,
+ private notificationsService: NotificationsService,
+ private router: Router,
+ private route: ActivatedRoute,
+ private formBuilder: FormBuilder,
+ private videoChannelService: VideoChannelService
+ ) {
+ super()
+ }
+
+ buildForm () {
+ this.form = this.formBuilder.group({
+ 'display-name': [ '', VIDEO_CHANNEL_DISPLAY_NAME.VALIDATORS ],
+ description: [ '', VIDEO_CHANNEL_DESCRIPTION.VALIDATORS ],
+ support: [ '', VIDEO_CHANNEL_SUPPORT.VALIDATORS ]
+ })
+
+ this.form.valueChanges.subscribe(data => this.onValueChanged(data))
+ }
+
+ ngOnInit () {
+ this.buildForm()
+
+ this.paramsSub = this.route.params.subscribe(routeParams => {
+ const videoChannelId = routeParams['videoChannelId']
+
+ this.videoChannelService.getVideoChannel(videoChannelId).subscribe(
+ videoChannelToUpdate => {
+ this.videoChannelToUpdate = videoChannelToUpdate
+
+ this.form.patchValue({
+ 'display-name': videoChannelToUpdate.displayName,
+ description: videoChannelToUpdate.description,
+ support: videoChannelToUpdate.support
+ })
+ },
+
+ err => this.error = err.message
+ )
+ })
+ }
+
+ ngOnDestroy () {
+ if (this.paramsSub) this.paramsSub.unsubscribe()
+ }
+
+ formValidated () {
+ this.error = undefined
+
+ const body = this.form.value
+ const videoChannelUpdate: VideoChannelUpdate = {
+ displayName: body['display-name'],
+ description: body.description || undefined,
+ support: body.support || undefined
+ }
+
+ this.videoChannelService.updateVideoChannel(this.videoChannelToUpdate.uuid, videoChannelUpdate).subscribe(
+ () => {
+ this.authService.refreshUserInformation()
+ this.notificationsService.success('Success', `Video channel ${videoChannelUpdate.displayName} updated.`)
+ this.router.navigate([ '/my-account', 'video-channels' ])
+ },
+
+ err => this.error = err.message
+ )
+ }
+
+ isCreation () {
+ return false
+ }
+
+ getFormButtonTitle () {
+ return 'Update'
+ }
+}
--- /dev/null
+<div class="video-channels-header">
+ <a class="create-button" routerLink="create">
+ <span class="icon icon-add"></span>
+ Create another video channel
+ </a>
+</div>
+
+<div class="video-channels">
+ <div *ngFor="let videoChannel of videoChannels" class="video-channel">
+ <a [routerLink]="[ '/video-channels', videoChannel.uuid ]">
+ <img [src]="videoChannel.avatarUrl" alt="Avatar" />
+ </a>
+
+ <div class="video-channel-info">
+ <a [routerLink]="[ '/video-channels', videoChannel.uuid ]" class="video-channel-names" title="Go to the channel">
+ <div class="video-channel-display-name">{{ videoChannel.displayName }}</div>
+ <!-- Hide the name for now, because it's an UUID not very friendly -->
+ <!--<div class="video-channel-name">{{ videoChannel.name }}</div>-->
+ </a>
+
+ <div class="video-channel-followers">{{ videoChannel.followersCount }} subscribers</div>
+ </div>
+
+ <div class="video-channel-buttons">
+ <my-delete-button (click)="deleteVideoChannel(videoChannel)"></my-delete-button>
+
+ <my-edit-button [routerLink]="[ 'update', videoChannel.uuid ]"></my-edit-button>
+ </div>
+ </div>
+</div>
--- /dev/null
+@import '_variables';
+@import '_mixins';
+
+.create-button {
+ @include create-button;
+}
+
+/deep/ .action-button {
+ &.action-button-delete {
+ margin-right: 10px;
+ }
+}
+
+.video-channel {
+ display: flex;
+ min-height: 130px;
+ padding-bottom: 20px;
+ margin-bottom: 20px;
+ border-bottom: 1px solid #C6C6C6;
+
+ img {
+ @include avatar(80px);
+
+ margin-right: 10px;
+ }
+
+ .video-channel-info {
+ flex-grow: 1;
+
+ a.video-channel-names {
+ @include disable-default-a-behaviour;
+
+ display: flex;
+ color: #000;
+
+ .video-channel-display-name {
+ font-weight: $font-semibold;
+ font-size: 18px;
+ }
+
+ .video-channel-name {
+ font-size: 14px;
+ color: #777272;
+ }
+ }
+ }
+
+ .video-channel-buttons {
+ min-width: 190px;
+ }
+}
+
+.video-channels-header {
+ text-align: right;
+ margin: 20px 0 50px;
+}
+
+@media screen and (max-width: 800px) {
+ .video-channel {
+ flex-direction: column;
+ height: auto;
+ text-align: center;
+
+ img {
+ margin-right: 0;
+ }
+
+ .video-channel-buttons {
+ margin-top: 10px;
+ }
+ }
+}
--- /dev/null
+import { Component, OnInit } from '@angular/core'
+import { NotificationsService } from 'angular2-notifications'
+import 'rxjs/add/observable/from'
+import 'rxjs/add/operator/concatAll'
+import { AuthService } from '../../core/auth'
+import { ConfirmService } from '../../core/confirm'
+import { VideoChannel } from '@app/shared/video-channel/video-channel.model'
+import { VideoChannelService } from '@app/shared/video-channel/video-channel.service'
+import { User } from '@app/shared'
+
+@Component({
+ selector: 'my-account-video-channels',
+ templateUrl: './my-account-video-channels.component.html',
+ styleUrls: [ './my-account-video-channels.component.scss' ]
+})
+export class MyAccountVideoChannelsComponent implements OnInit {
+ videoChannels: VideoChannel[] = []
+
+ private user: User
+
+ constructor (
+ private authService: AuthService,
+ private notificationsService: NotificationsService,
+ private confirmService: ConfirmService,
+ private videoChannelService: VideoChannelService
+ ) {}
+
+ ngOnInit () {
+ this.user = this.authService.getUser()
+
+ this.loadVideoChannels()
+ }
+
+ async deleteVideoChannel (videoChannel: VideoChannel) {
+ const res = await this.confirmService.confirmWithInput(
+ `Do you really want to delete ${videoChannel.displayName}? It will delete all videos uploaded in this channel too.`,
+ 'Please type the name of the video channel to confirm',
+ videoChannel.displayName,
+ 'Delete'
+ )
+ if (res === false) return
+
+ this.videoChannelService.removeVideoChannel(videoChannel)
+ .subscribe(
+ status => {
+ this.loadVideoChannels()
+ this.notificationsService.success('Success', `Video channel ${videoChannel.name} deleted.`)
+ },
+
+ error => this.notificationsService.error('Error', error.message)
+ )
+ }
+
+ private loadVideoChannels () {
+ this.authService.userInformationLoaded
+ .flatMap(() => this.videoChannelService.listAccountVideoChannels(this.user.account.id))
+ .subscribe(res => this.videoChannels = res.data)
+ }
+}
--- /dev/null
+<div *ngIf="pagination.totalItems === 0">No results.</div>
+
+<div
+ myInfiniteScroller
+ [pageHeight]="pageHeight"
+ (nearOfTop)="onNearOfTop()" (nearOfBottom)="onNearOfBottom()" (pageChanged)="onPageChanged($event)"
+ class="videos" #videosElement
+>
+ <div *ngFor="let videos of videoPages; let i = index" class="videos-page">
+ <div class="video" *ngFor="let video of videos; let j = index">
+ <div class="checkbox-container">
+ <input [id]="'video-check-' + video.id" type="checkbox" [(ngModel)]="checkedVideos[video.id]" />
+ <label [for]="'video-check-' + video.id"></label>
+ </div>
+
+ <my-video-thumbnail [video]="video"></my-video-thumbnail>
+
+ <div class="video-info">
+ <a class="video-info-name" [routerLink]="['/videos/watch', video.uuid]" [attr.title]="video.name">{{ video.name }}</a>
+ <span class="video-info-date-views">{{ video.createdAt | myFromNow }} - {{ video.views | myNumberFormatter }} views</span>
+ <div class="video-info-private">{{ video.privacy.label }}</div>
+ </div>
+
+ <!-- Display only once -->
+ <div class="action-selection-mode" *ngIf="isInSelectionMode() === true && i === 0 && j === 0">
+ <div class="action-selection-mode-child">
+ <span class="action-button action-button-cancel-selection" (click)="abortSelectionMode()">
+ Cancel
+ </span>
+
+ <span class="action-button action-button-delete-selection" (click)="deleteSelectedVideos()">
+ <span class="icon icon-delete-white"></span>
+ Delete
+ </span>
+ </div>
+ </div>
+
+ <div class="video-buttons" *ngIf="isInSelectionMode() === false">
+ <my-delete-button (click)="deleteVideo(video)"></my-delete-button>
+
+ <my-edit-button [routerLink]="[ '/videos', 'edit', video.uuid ]"></my-edit-button>
+ </div>
+ </div>
+ </div>
+</div>
--- /dev/null
+@import '_variables';
+@import '_mixins';
+
+.action-selection-mode {
+ width: 174px;
+ display: flex;
+ justify-content: flex-end;
+
+ .action-selection-mode-child {
+ position: fixed;
+
+ .action-button {
+ display: inline-block;
+ }
+
+ .action-button-cancel-selection {
+ @include peertube-button;
+ @include grey-button;
+
+ margin-right: 10px;
+ }
+
+ .action-button-delete-selection {
+ @include peertube-button;
+ @include orange-button;
+ }
+
+ .icon.icon-delete-white {
+ @include icon(21px);
+
+ position: relative;
+ top: -2px;
+ background-image: url('../../../assets/images/global/delete-white.svg');
+ }
+ }
+}
+
+/deep/ .action-button {
+ &.action-button-delete {
+ margin-right: 10px;
+ }
+}
+
+.video {
+ display: flex;
+ min-height: 130px;
+ padding-bottom: 20px;
+ margin-bottom: 20px;
+ border-bottom: 1px solid #C6C6C6;
+
+ &:first-child {
+ margin-top: 47px;
+ }
+
+ .checkbox-container {
+ display: flex;
+ align-items: center;
+ margin-right: 20px;
+ margin-left: 12px;
+
+ input[type=checkbox] {
+ @include peertube-checkbox(2px);
+ }
+ }
+
+ my-video-thumbnail {
+ margin-right: 10px;
+ }
+
+ .video-info {
+ flex-grow: 1;
+
+ .video-info-name {
+ @include disable-default-a-behaviour;
+
+ color: #000;
+ display: block;
+ font-size: 16px;
+ font-weight: $font-semibold;
+ }
+
+ .video-info-date-views, .video-info-private {
+ font-size: 13px;
+
+ &.video-info-private {
+ font-weight: $font-semibold;
+ }
+ }
+ }
+
+ .video-buttons {
+ min-width: 190px;
+ }
+}
+
+@media screen and (max-width: 800px) {
+ .video {
+ flex-direction: column;
+ height: auto;
+ text-align: center;
+
+ input[type=checkbox] {
+ display: none;
+ }
+
+ my-video-thumbnail {
+ margin-right: 0;
+ }
+
+ .video-buttons {
+ margin-top: 10px;
+ }
+ }
+}
--- /dev/null
+import { Component, OnInit, OnDestroy } from '@angular/core'
+import { ActivatedRoute, Router } from '@angular/router'
+import { Location } from '@angular/common'
+import { immutableAssign } from '@app/shared/misc/utils'
+import { ComponentPagination } from '@app/shared/rest/component-pagination.model'
+import { NotificationsService } from 'angular2-notifications'
+import 'rxjs/add/observable/from'
+import 'rxjs/add/operator/concatAll'
+import { Observable } from 'rxjs/Observable'
+import { AuthService } from '../../core/auth'
+import { ConfirmService } from '../../core/confirm'
+import { AbstractVideoList } from '../../shared/video/abstract-video-list'
+import { Video } from '../../shared/video/video.model'
+import { VideoService } from '../../shared/video/video.service'
+
+@Component({
+ selector: 'my-account-videos',
+ templateUrl: './my-account-videos.component.html',
+ styleUrls: [ './my-account-videos.component.scss' ]
+})
+export class MyAccountVideosComponent extends AbstractVideoList implements OnInit, OnDestroy {
+ titlePage = 'My videos'
+ currentRoute = '/my-account/videos'
+ checkedVideos: { [ id: number ]: boolean } = {}
+ pagination: ComponentPagination = {
+ currentPage: 1,
+ itemsPerPage: 5,
+ totalItems: null
+ }
+
+ protected baseVideoWidth = -1
+ protected baseVideoHeight = 155
+
+ constructor (protected router: Router,
+ protected route: ActivatedRoute,
+ protected authService: AuthService,
+ protected notificationsService: NotificationsService,
+ protected confirmService: ConfirmService,
+ protected location: Location,
+ private videoService: VideoService) {
+ super()
+ }
+
+ ngOnInit () {
+ super.ngOnInit()
+ }
+
+ ngOnDestroy () {
+ super.ngOnDestroy()
+ }
+
+ abortSelectionMode () {
+ this.checkedVideos = {}
+ }
+
+ isInSelectionMode () {
+ return Object.keys(this.checkedVideos).some(k => this.checkedVideos[k] === true)
+ }
+
+ getVideosObservable (page: number) {
+ const newPagination = immutableAssign(this.pagination, { currentPage: page })
+
+ return this.videoService.getMyVideos(newPagination, this.sort)
+ }
+
+ generateSyndicationList () {
+ throw new Error('Method not implemented.')
+ }
+
+ async deleteSelectedVideos () {
+ const toDeleteVideosIds = Object.keys(this.checkedVideos)
+ .filter(k => this.checkedVideos[k] === true)
+ .map(k => parseInt(k, 10))
+
+ const res = await this.confirmService.confirm(`Do you really want to delete ${toDeleteVideosIds.length} videos?`, 'Delete')
+ if (res === false) return
+
+ const observables: Observable<any>[] = []
+ for (const videoId of toDeleteVideosIds) {
+ const o = this.videoService
+ .removeVideo(videoId)
+ .do(() => this.spliceVideosById(videoId))
+
+ observables.push(o)
+ }
+
+ Observable.from(observables)
+ .concatAll()
+ .subscribe(
+ res => {
+ this.notificationsService.success('Success', `${toDeleteVideosIds.length} videos deleted.`)
+ this.buildVideoPages()
+ },
+
+ err => this.notificationsService.error('Error', err.message)
+ )
+ }
+
+ async deleteVideo (video: Video) {
+ const res = await this.confirmService.confirm(`Do you really want to delete ${video.name}?`, 'Delete')
+ if (res === false) return
+
+ this.videoService.removeVideo(video.id)
+ .subscribe(
+ status => {
+ this.notificationsService.success('Success', `Video ${video.name} deleted.`)
+ this.spliceVideosById(video.id)
+ this.buildVideoPages()
+ },
+
+ error => this.notificationsService.error('Error', error.message)
+ )
+ }
+
+ protected buildVideoHeight () {
+ // In account videos, the video height is fixed
+ return this.baseVideoHeight
+ }
+
+ private spliceVideosById (id: number) {
+ for (const key of Object.keys(this.loadedPages)) {
+ const videos = this.loadedPages[key]
+ const index = videos.findIndex(v => v.id === id)
+
+ if (index !== -1) {
+ videos.splice(index, 1)
+ return
+ }
+ }
+ }
+}
--- /dev/null
+<div class="row">
+ <div class="sub-menu">
+ <a routerLink="/my-account/settings" routerLinkActive="active" class="title-page">My settings</a>
+
+ <a routerLink="/my-account/video-channels" routerLinkActive="active" class="title-page">My video channels</a>
+
+ <a routerLink="/my-account/videos" routerLinkActive="active" class="title-page">My videos</a>
+ </div>
+
+ <div class="margin-content">
+ <router-outlet></router-outlet>
+ </div>
+</div>
--- /dev/null
+import { Component } from '@angular/core'
+
+@Component({
+ selector: 'my-my-account',
+ templateUrl: './my-account.component.html'
+})
+export class MyAccountComponent {}
--- /dev/null
+import { NgModule } from '@angular/core'
+import { SharedModule } from '../shared'
+import { MyAccountRoutingModule } from './my-account-routing.module'
+import { MyAccountChangePasswordComponent } from './my-account-settings/my-account-change-password/my-account-change-password.component'
+import { MyAccountVideoSettingsComponent } from './my-account-settings/my-account-video-settings/my-account-video-settings.component'
+import { MyAccountSettingsComponent } from './my-account-settings/my-account-settings.component'
+import { MyAccountComponent } from './my-account.component'
+import { MyAccountVideosComponent } from './my-account-videos/my-account-videos.component'
+import { MyAccountProfileComponent } from '@app/+my-account/my-account-settings/my-account-profile/my-account-profile.component'
+import { MyAccountVideoChannelsComponent } from '@app/+my-account/my-account-video-channels/my-account-video-channels.component'
+import { MyAccountVideoChannelCreateComponent } from '@app/+my-account/my-account-video-channels/my-account-video-channel-create.component'
+import { MyAccountVideoChannelUpdateComponent } from '@app/+my-account/my-account-video-channels/my-account-video-channel-update.component'
+
+@NgModule({
+ imports: [
+ MyAccountRoutingModule,
+ SharedModule
+ ],
+
+ declarations: [
+ MyAccountComponent,
+ MyAccountSettingsComponent,
+ MyAccountChangePasswordComponent,
+ MyAccountVideoSettingsComponent,
+ MyAccountProfileComponent,
+ MyAccountVideosComponent,
+ MyAccountVideoChannelsComponent,
+ MyAccountVideoChannelCreateComponent,
+ MyAccountVideoChannelUpdateComponent
+ ],
+
+ exports: [
+ MyAccountComponent
+ ],
+
+ providers: []
+})
+export class MyAccountModule { }
path: 'admin',
loadChildren: './+admin/admin.module#AdminModule'
},
+ {
+ path: 'my-account',
+ loadChildren: './+my-account/my-account.module#MyAccountModule'
+ },
{
path: 'accounts',
loadChildren: './+accounts/accounts.module#AccountsModule'
import { MetaLoader, MetaModule, MetaStaticLoader, PageTitlePositioning } from '@ngx-meta/core'
-import { MyAccountModule } from './my-account'
-
import { AppRoutingModule } from './app-routing.module'
import { AppComponent } from './app.component'
import { CoreModule } from './core'
AppRoutingModule,
- MyAccountModule,
CoreModule,
LoginModule,
ResetPasswordModule,
+++ /dev/null
-export * from './my-account-routing.module'
-export * from './my-account.component'
-export * from './my-account.module'
+++ /dev/null
-import { NgModule } from '@angular/core'
-import { RouterModule, Routes } from '@angular/router'
-import { MetaGuard } from '@ngx-meta/core'
-import { LoginGuard } from '../core'
-import { MyAccountComponent } from './my-account.component'
-import { MyAccountSettingsComponent } from './my-account-settings/my-account-settings.component'
-import { MyAccountVideosComponent } from './my-account-videos/my-account-videos.component'
-import { MyAccountVideoChannelsComponent } from '@app/my-account/my-account-video-channels/my-account-video-channels.component'
-import { MyAccountVideoChannelCreateComponent } from '@app/my-account/my-account-video-channels/my-account-video-channel-create.component'
-import { MyAccountVideoChannelUpdateComponent } from '@app/my-account/my-account-video-channels/my-account-video-channel-update.component'
-
-const myAccountRoutes: Routes = [
- {
- path: 'my-account',
- component: MyAccountComponent,
- canActivateChild: [ MetaGuard, LoginGuard ],
- children: [
- {
- path: 'settings',
- component: MyAccountSettingsComponent,
- data: {
- meta: {
- title: 'Account settings'
- }
- }
- },
- {
- path: 'video-channels',
- component: MyAccountVideoChannelsComponent,
- data: {
- meta: {
- title: 'Account video channels'
- }
- }
- },
- {
- path: 'video-channels/create',
- component: MyAccountVideoChannelCreateComponent,
- data: {
- meta: {
- title: 'Create new video channel'
- }
- }
- },
- {
- path: 'video-channels/update/:videoChannelId',
- component: MyAccountVideoChannelUpdateComponent,
- data: {
- meta: {
- title: 'Update video channel'
- }
- }
- },
- {
- path: 'videos',
- component: MyAccountVideosComponent,
- data: {
- meta: {
- title: 'Account videos'
- }
- }
- }
- ]
- }
-]
-
-@NgModule({
- imports: [ RouterModule.forChild(myAccountRoutes) ],
- exports: [ RouterModule ]
-})
-export class MyAccountRoutingModule {}
+++ /dev/null
-export * from './my-account-change-password.component'
+++ /dev/null
-<div *ngIf="error" class="alert alert-danger">{{ error }}</div>
-
-<form role="form" (ngSubmit)="changePassword()" [formGroup]="form">
-
- <label for="new-password">Change password</label>
- <input
- type="password" id="new-password" placeholder="New password"
- formControlName="new-password" [ngClass]="{ 'input-error': formErrors['new-password'] }"
- >
- <div *ngIf="formErrors['new-password']" class="form-error">
- {{ formErrors['new-password'] }}
- </div>
-
- <input
- type="password" id="new-confirmed-password" placeholder="Confirm new password"
- formControlName="new-confirmed-password"
- >
-
- <input type="submit" value="Change password" [disabled]="!form.valid">
-</form>
+++ /dev/null
-@import '_variables';
-@import '_mixins';
-
-input[type=password] {
- @include peertube-input-text(340px);
- display: block;
-
- &#new-confirmed-password {
- margin-top: 15px;
- }
-}
-
-input[type=submit] {
- @include peertube-button;
- @include orange-button;
-
- margin-top: 15px;
-}
-
+++ /dev/null
-import { Component, OnInit } from '@angular/core'
-import { FormBuilder, FormGroup } from '@angular/forms'
-import { NotificationsService } from 'angular2-notifications'
-import { FormReactive, USER_PASSWORD, UserService } from '../../../shared'
-
-@Component({
- selector: 'my-account-change-password',
- templateUrl: './my-account-change-password.component.html',
- styleUrls: [ './my-account-change-password.component.scss' ]
-})
-export class MyAccountChangePasswordComponent extends FormReactive implements OnInit {
- error: string = null
-
- form: FormGroup
- formErrors = {
- 'new-password': '',
- 'new-confirmed-password': ''
- }
- validationMessages = {
- 'new-password': USER_PASSWORD.MESSAGES,
- 'new-confirmed-password': USER_PASSWORD.MESSAGES
- }
-
- constructor (
- private formBuilder: FormBuilder,
- private notificationsService: NotificationsService,
- private userService: UserService
- ) {
- super()
- }
-
- buildForm () {
- this.form = this.formBuilder.group({
- 'new-password': [ '', USER_PASSWORD.VALIDATORS ],
- 'new-confirmed-password': [ '', USER_PASSWORD.VALIDATORS ]
- })
-
- this.form.valueChanges.subscribe(data => this.onValueChanged(data))
- }
-
- ngOnInit () {
- this.buildForm()
- }
-
- changePassword () {
- const newPassword = this.form.value['new-password']
- const newConfirmedPassword = this.form.value['new-confirmed-password']
-
- this.error = null
-
- if (newPassword !== newConfirmedPassword) {
- this.error = 'The new password and the confirmed password do not correspond.'
- return
- }
-
- this.userService.changePassword(newPassword).subscribe(
- () => this.notificationsService.success('Success', 'Password updated.'),
-
- err => this.error = err.message
- )
- }
-}
+++ /dev/null
-export * from './my-account-profile.component'
+++ /dev/null
-<div *ngIf="error" class="alert alert-danger">{{ error }}</div>
-
-<form role="form" (ngSubmit)="updateMyProfile()" [formGroup]="form">
-
- <label for="display-name">Display name</label>
- <input
- type="text" id="display-name"
- formControlName="display-name" [ngClass]="{ 'input-error': formErrors['display-name'] }"
- >
- <div *ngIf="formErrors['display-name']" class="form-error">
- {{ formErrors['display-name'] }}
- </div>
-
- <label for="description">Description</label>
- <textarea
- id="description" formControlName="description"
- [ngClass]="{ 'input-error': formErrors['description'] }"
- ></textarea>
- <div *ngIf="formErrors.description" class="form-error">
- {{ formErrors.description }}
- </div>
-
- <input type="submit" value="Update my profile" [disabled]="!form.valid">
-</form>
+++ /dev/null
-@import '_variables';
-@import '_mixins';
-
-input[type=text] {
- @include peertube-input-text(340px);
-
- display: block;
- margin-bottom: 15px;
-}
-
-textarea {
- @include peertube-textarea(500px, 150px);
-
- display: block;
-}
-
-input[type=submit] {
- @include peertube-button;
- @include orange-button;
-
- margin-top: 15px;
-}
-
+++ /dev/null
-import { Component, Input, OnInit } from '@angular/core'
-import { FormBuilder, FormGroup } from '@angular/forms'
-import { NotificationsService } from 'angular2-notifications'
-import { FormReactive, USER_DESCRIPTION, USER_DISPLAY_NAME, UserService } from '../../../shared'
-import { User } from '@app/shared'
-
-@Component({
- selector: 'my-account-profile',
- templateUrl: './my-account-profile.component.html',
- styleUrls: [ './my-account-profile.component.scss' ]
-})
-export class MyAccountProfileComponent extends FormReactive implements OnInit {
- @Input() user: User = null
-
- error: string = null
-
- form: FormGroup
- formErrors = {
- 'display-name': '',
- 'description': ''
- }
- validationMessages = {
- 'display-name': USER_DISPLAY_NAME.MESSAGES,
- 'description': USER_DESCRIPTION.MESSAGES
- }
-
- constructor (
- private formBuilder: FormBuilder,
- private notificationsService: NotificationsService,
- private userService: UserService
- ) {
- super()
- }
-
- buildForm () {
- this.form = this.formBuilder.group({
- 'display-name': [ this.user.account.displayName, USER_DISPLAY_NAME.VALIDATORS ],
- 'description': [ this.user.account.description, USER_DESCRIPTION.VALIDATORS ]
- })
-
- this.form.valueChanges.subscribe(data => this.onValueChanged(data))
- }
-
- ngOnInit () {
- this.buildForm()
- }
-
- updateMyProfile () {
- const displayName = this.form.value['display-name']
- const description = this.form.value['description']
-
- this.error = null
-
- this.userService.updateMyProfile({ displayName, description }).subscribe(
- () => {
- this.user.account.displayName = displayName
- this.user.account.description = description
-
- this.notificationsService.success('Success', 'Profile updated.')
- },
-
- err => this.error = err.message
- )
- }
-}
+++ /dev/null
-<div class="user">
- <img [src]="user.accountAvatarUrl" alt="Avatar" />
-
- <div class="user-info">
- <div class="user-info-names">
- <div class="user-info-display-name">{{ user.account.displayName }}</div>
- <div class="user-info-username">{{ user.username }}</div>
- </div>
- <div class="user-info-followers">{{ user.account?.followersCount }} subscribers</div>
- </div>
-</div>
-
-<div class="button-file">
- <span>Change your avatar</span>
- <input #avatarfileInput type="file" name="avatarfile" id="avatarfile" [accept]="avatarExtensions" (change)="changeAvatar()" />
-</div>
-<div class="file-max-size">(extensions: {{ avatarExtensions }}, max size: {{ maxAvatarSize | bytes }})</div>
-
-<div class="user-quota">
- <span class="user-quota-label">Video quota:</span> {{ userVideoQuotaUsed | bytes: 0 }} / {{ userVideoQuota }}
-</div>
-
-<ng-template [ngIf]="user && user.account">
- <div class="account-title">Profile</div>
- <my-account-profile [user]="user"></my-account-profile>
-</ng-template>
-
-<div class="account-title">Password</div>
-<my-account-change-password></my-account-change-password>
-
-<div class="account-title">Video settings</div>
-<my-account-video-settings [user]="user"></my-account-video-settings>
+++ /dev/null
-@import '_variables';
-@import '_mixins';
-
-.user {
- display: flex;
-
- img {
- @include avatar(50px);
-
- margin-right: 15px;
- }
-
- .user-info {
- .user-info-names {
- display: flex;
- align-items: center;
-
- .user-info-display-name {
- font-size: 20px;
- font-weight: $font-bold;
- }
-
- .user-info-username {
- margin-left: 7px;
- position: relative;
- top: 2px;
- font-size: 14px;
- color: #777272;
- }
- }
-
- .user-info-followers {
- font-size: 15px;
- }
- }
-}
-
-.button-file {
- @include peertube-button-file(160px);
-
- margin-top: 10px;
- margin-bottom: 5px;
-}
-
-.file-max-size {
- display: inline-block;
- font-size: 13px;
-
- position: relative;
- top: -10px;
-}
-
-.user-quota {
- font-size: 15px;
- margin-top: 20px;
-
- .user-quota-label {
- font-weight: $font-semibold;
- }
-}
-
-.account-title {
- @include in-content-small-title;
-
- margin-top: 55px;
- margin-bottom: 30px;
-}
+++ /dev/null
-import { Component, OnInit, ViewChild } from '@angular/core'
-import { NotificationsService } from 'angular2-notifications'
-import { BytesPipe } from 'ngx-pipes'
-import { AuthService } from '../../core'
-import { ServerService } from '../../core/server'
-import { User } from '../../shared'
-import { UserService } from '../../shared/users'
-
-@Component({
- selector: 'my-account-settings',
- templateUrl: './my-account-settings.component.html',
- styleUrls: [ './my-account-settings.component.scss' ]
-})
-export class MyAccountSettingsComponent implements OnInit {
- @ViewChild('avatarfileInput') avatarfileInput
-
- user: User = null
- userVideoQuota = '0'
- userVideoQuotaUsed = 0
-
- constructor (
- private userService: UserService,
- private authService: AuthService,
- private serverService: ServerService,
- private notificationsService: NotificationsService
- ) {}
-
- ngOnInit () {
- this.user = this.authService.getUser()
-
- this.authService.userInformationLoaded.subscribe(
- () => {
- if (this.user.videoQuota !== -1) {
- this.userVideoQuota = new BytesPipe().transform(this.user.videoQuota, 0).toString()
- } else {
- this.userVideoQuota = 'Unlimited'
- }
- }
- )
-
- this.userService.getMyVideoQuotaUsed()
- .subscribe(data => this.userVideoQuotaUsed = data.videoQuotaUsed)
- }
-
- changeAvatar () {
- const avatarfile = this.avatarfileInput.nativeElement.files[ 0 ]
-
- const formData = new FormData()
- formData.append('avatarfile', avatarfile)
-
- this.userService.changeAvatar(formData)
- .subscribe(
- data => {
- this.notificationsService.success('Success', 'Avatar changed.')
-
- this.user.account.avatar = data.avatar
- },
-
- err => this.notificationsService.error('Error', err.message)
- )
- }
-
- get maxAvatarSize () {
- return this.serverService.getConfig().avatar.file.size.max
- }
-
- get avatarExtensions () {
- return this.serverService.getConfig().avatar.file.extensions.join(',')
- }
-}
+++ /dev/null
-export * from './my-account-video-settings.component'
+++ /dev/null
-<form role="form" (ngSubmit)="updateDetails()" [formGroup]="form">
- <div class="form-group">
- <label for="nsfwPolicy">Default policy on videos containing sensitive content</label>
- <my-help helpType="custom" customHtml="With <strong>Do not list</strong> or <strong>Blur thumbnails</strong>, a confirmation will be requested to watch the video."></my-help>
-
- <div class="peertube-select-container">
- <select id="nsfwPolicy" formControlName="nsfwPolicy">
- <option value="do_not_list">Do not list</option>
- <option value="blur">Blur thumbnails</option>
- <option value="display">Display</option>
- </select>
- </div>
- </div>
-
- <div class="form-group">
- <input
- type="checkbox" id="autoPlayVideo"
- formControlName="autoPlayVideo"
- >
- <label for="autoPlayVideo"></label>
- <label for="autoPlayVideo">Automatically plays video</label>
- </div>
-
- <input type="submit" value="Save" [disabled]="!form.valid">
-</form>
+++ /dev/null
-@import '_variables';
-@import '_mixins';
-
-input[type=checkbox] {
- @include peertube-checkbox(1px);
-}
-
-input[type=submit] {
- @include peertube-button;
- @include orange-button;
-
- display: block;
- margin-top: 15px;
-}
-
-.peertube-select-container {
- @include peertube-select-container(340px);
-
- margin-bottom: 30px;
-}
\ No newline at end of file
+++ /dev/null
-import { Component, Input, OnInit } from '@angular/core'
-import { FormBuilder, FormGroup } from '@angular/forms'
-import { NotificationsService } from 'angular2-notifications'
-import { UserUpdateMe } from '../../../../../../shared'
-import { AuthService } from '../../../core'
-import { FormReactive, User, UserService } from '../../../shared'
-
-@Component({
- selector: 'my-account-video-settings',
- templateUrl: './my-account-video-settings.component.html',
- styleUrls: [ './my-account-video-settings.component.scss' ]
-})
-export class MyAccountVideoSettingsComponent extends FormReactive implements OnInit {
- @Input() user: User = null
-
- form: FormGroup
- formErrors = {}
- validationMessages = {}
-
- constructor (
- private authService: AuthService,
- private formBuilder: FormBuilder,
- private notificationsService: NotificationsService,
- private userService: UserService
- ) {
- super()
- }
-
- buildForm () {
- this.form = this.formBuilder.group({
- nsfwPolicy: [ this.user.nsfwPolicy ],
- autoPlayVideo: [ this.user.autoPlayVideo ]
- })
-
- this.form.valueChanges.subscribe(data => this.onValueChanged(data))
- }
-
- ngOnInit () {
- this.buildForm()
- }
-
- updateDetails () {
- const nsfwPolicy = this.form.value['nsfwPolicy']
- const autoPlayVideo = this.form.value['autoPlayVideo']
- const details: UserUpdateMe = {
- nsfwPolicy,
- autoPlayVideo
- }
-
- this.userService.updateMyProfile(details).subscribe(
- () => {
- this.notificationsService.success('Success', 'Information updated.')
-
- this.authService.refreshUserInformation()
- },
-
- err => this.notificationsService.error('Error', err.message)
- )
- }
-}
+++ /dev/null
-import { Component, OnInit } from '@angular/core'
-import { Router } from '@angular/router'
-import { NotificationsService } from 'angular2-notifications'
-import 'rxjs/add/observable/from'
-import 'rxjs/add/operator/concatAll'
-import { MyAccountVideoChannelEdit } from './my-account-video-channel-edit'
-import { FormBuilder, FormGroup } from '@angular/forms'
-import { VideoChannelCreate } from '../../../../../shared/models/videos'
-import {
- VIDEO_CHANNEL_DESCRIPTION,
- VIDEO_CHANNEL_DISPLAY_NAME,
- VIDEO_CHANNEL_SUPPORT
-} from '@app/shared/forms/form-validators/video-channel'
-import { VideoChannelService } from '@app/shared/video-channel/video-channel.service'
-import { AuthService } from '@app/core'
-
-@Component({
- selector: 'my-account-video-channel-create',
- templateUrl: './my-account-video-channel-edit.component.html',
- styleUrls: [ './my-account-video-channel-edit.component.scss' ]
-})
-export class MyAccountVideoChannelCreateComponent extends MyAccountVideoChannelEdit implements OnInit {
- error: string
-
- form: FormGroup
- formErrors = {
- 'display-name': '',
- 'description': '',
- 'support': ''
- }
- validationMessages = {
- 'display-name': VIDEO_CHANNEL_DISPLAY_NAME.MESSAGES,
- 'description': VIDEO_CHANNEL_DESCRIPTION.MESSAGES,
- 'support': VIDEO_CHANNEL_SUPPORT.MESSAGES
- }
-
- constructor (
- private authService: AuthService,
- private notificationsService: NotificationsService,
- private router: Router,
- private formBuilder: FormBuilder,
- private videoChannelService: VideoChannelService
- ) {
- super()
- }
-
- buildForm () {
- this.form = this.formBuilder.group({
- 'display-name': [ '', VIDEO_CHANNEL_DISPLAY_NAME.VALIDATORS ],
- description: [ '', VIDEO_CHANNEL_DESCRIPTION.VALIDATORS ],
- support: [ '', VIDEO_CHANNEL_SUPPORT.VALIDATORS ]
- })
-
- this.form.valueChanges.subscribe(data => this.onValueChanged(data))
- }
-
- ngOnInit () {
- this.buildForm()
- }
-
- formValidated () {
- this.error = undefined
-
- const body = this.form.value
- const videoChannelCreate: VideoChannelCreate = {
- displayName: body['display-name'],
- description: body.description || undefined,
- support: body.support || undefined
- }
-
- this.videoChannelService.createVideoChannel(videoChannelCreate).subscribe(
- () => {
- this.authService.refreshUserInformation()
- this.notificationsService.success('Success', `Video channel ${videoChannelCreate.displayName} created.`)
- this.router.navigate([ '/my-account', 'video-channels' ])
- },
-
- err => this.error = err.message
- )
- }
-
- isCreation () {
- return true
- }
-
- getFormButtonTitle () {
- return 'Create'
- }
-}
+++ /dev/null
-<div class="form-sub-title" *ngIf="isCreation() === true">Create a video channel</div>
-<div class="form-sub-title" *ngIf="isCreation() === false">Update {{ videoChannel?.displayName }}</div>
-
-<div *ngIf="error" class="alert alert-danger">{{ error }}</div>
-
-<form role="form" (ngSubmit)="formValidated()" [formGroup]="form">
- <div class="form-group">
- <label for="display-name">Display name</label>
- <input
- type="text" id="display-name"
- formControlName="display-name" [ngClass]="{ 'input-error': formErrors['display-name'] }"
- >
- <div *ngIf="formErrors['display-name']" class="form-error">
- {{ formErrors['display-name'] }}
- </div>
- </div>
-
- <div class="form-group">
- <label for="description">Description</label>
- <textarea
- id="description" formControlName="description"
- [ngClass]="{ 'input-error': formErrors['description'] }"
- ></textarea>
- <div *ngIf="formErrors.description" class="form-error">
- {{ formErrors.description }}
- </div>
- </div>
-
- <div class="form-group">
- <label for="support">Support</label>
- <my-help helpType="markdownEnhanced" preHtml="Short text to tell people how they can support your channel (membership platform...)."></my-help>
- <my-markdown-textarea
- id="support" formControlName="support" textareaWidth="500px" [previewColumn]="true" markdownType="enhanced"
- [classes]="{ 'input-error': formErrors['support'] }"
- ></my-markdown-textarea>
- <div *ngIf="formErrors.support" class="form-error">
- {{ formErrors.support }}
- </div>
- </div>
-
- <input type="submit" value="{{ getFormButtonTitle() }}" [disabled]="!form.valid">
-</form>
+++ /dev/null
-@import '_variables';
-@import '_mixins';
-
-.form-sub-title {
- margin-bottom: 20px;
-}
-
-input[type=text] {
- @include peertube-input-text(340px);
-
- display: block;
-}
-
-textarea {
- @include peertube-textarea(500px, 150px);
-
- display: block;
-}
-
-.peertube-select-container {
- @include peertube-select-container(340px);
-}
-
-input[type=submit] {
- @include peertube-button;
- @include orange-button;
-}
\ No newline at end of file
+++ /dev/null
-import { FormReactive } from '@app/shared'
-
-export abstract class MyAccountVideoChannelEdit extends FormReactive {
- abstract isCreation (): boolean
- abstract getFormButtonTitle (): string
-}
+++ /dev/null
-import { Component, OnInit, OnDestroy } from '@angular/core'
-import { ActivatedRoute, Router } from '@angular/router'
-import { NotificationsService } from 'angular2-notifications'
-import 'rxjs/add/observable/from'
-import 'rxjs/add/operator/concatAll'
-import { MyAccountVideoChannelEdit } from './my-account-video-channel-edit'
-import { FormBuilder, FormGroup } from '@angular/forms'
-import { VideoChannelUpdate } from '../../../../../shared/models/videos'
-import {
- VIDEO_CHANNEL_DESCRIPTION,
- VIDEO_CHANNEL_DISPLAY_NAME,
- VIDEO_CHANNEL_SUPPORT
-} from '@app/shared/forms/form-validators/video-channel'
-import { VideoChannelService } from '@app/shared/video-channel/video-channel.service'
-import { Subscription } from 'rxjs/Subscription'
-import { VideoChannel } from '@app/shared/video-channel/video-channel.model'
-import { AuthService } from '@app/core'
-
-@Component({
- selector: 'my-account-video-channel-update',
- templateUrl: './my-account-video-channel-edit.component.html',
- styleUrls: [ './my-account-video-channel-edit.component.scss' ]
-})
-export class MyAccountVideoChannelUpdateComponent extends MyAccountVideoChannelEdit implements OnInit, OnDestroy {
- error: string
-
- form: FormGroup
- formErrors = {
- 'display-name': '',
- 'description': '',
- 'support': ''
- }
- validationMessages = {
- 'display-name': VIDEO_CHANNEL_DISPLAY_NAME.MESSAGES,
- 'description': VIDEO_CHANNEL_DESCRIPTION.MESSAGES,
- 'support': VIDEO_CHANNEL_SUPPORT.MESSAGES
- }
-
- private videoChannelToUpdate: VideoChannel
- private paramsSub: Subscription
-
- constructor (
- private authService: AuthService,
- private notificationsService: NotificationsService,
- private router: Router,
- private route: ActivatedRoute,
- private formBuilder: FormBuilder,
- private videoChannelService: VideoChannelService
- ) {
- super()
- }
-
- buildForm () {
- this.form = this.formBuilder.group({
- 'display-name': [ '', VIDEO_CHANNEL_DISPLAY_NAME.VALIDATORS ],
- description: [ '', VIDEO_CHANNEL_DESCRIPTION.VALIDATORS ],
- support: [ '', VIDEO_CHANNEL_SUPPORT.VALIDATORS ]
- })
-
- this.form.valueChanges.subscribe(data => this.onValueChanged(data))
- }
-
- ngOnInit () {
- this.buildForm()
-
- this.paramsSub = this.route.params.subscribe(routeParams => {
- const videoChannelId = routeParams['videoChannelId']
-
- this.videoChannelService.getVideoChannel(videoChannelId).subscribe(
- videoChannelToUpdate => {
- this.videoChannelToUpdate = videoChannelToUpdate
-
- this.form.patchValue({
- 'display-name': videoChannelToUpdate.displayName,
- description: videoChannelToUpdate.description,
- support: videoChannelToUpdate.support
- })
- },
-
- err => this.error = err.message
- )
- })
- }
-
- ngOnDestroy () {
- if (this.paramsSub) this.paramsSub.unsubscribe()
- }
-
- formValidated () {
- this.error = undefined
-
- const body = this.form.value
- const videoChannelUpdate: VideoChannelUpdate = {
- displayName: body['display-name'],
- description: body.description || undefined,
- support: body.support || undefined
- }
-
- this.videoChannelService.updateVideoChannel(this.videoChannelToUpdate.uuid, videoChannelUpdate).subscribe(
- () => {
- this.authService.refreshUserInformation()
- this.notificationsService.success('Success', `Video channel ${videoChannelUpdate.displayName} updated.`)
- this.router.navigate([ '/my-account', 'video-channels' ])
- },
-
- err => this.error = err.message
- )
- }
-
- isCreation () {
- return false
- }
-
- getFormButtonTitle () {
- return 'Update'
- }
-}
+++ /dev/null
-<div class="video-channels-header">
- <a class="create-button" routerLink="create">
- <span class="icon icon-add"></span>
- Create another video channel
- </a>
-</div>
-
-<div class="video-channels">
- <div *ngFor="let videoChannel of videoChannels" class="video-channel">
- <a [routerLink]="[ '/video-channels', videoChannel.uuid ]">
- <img [src]="videoChannel.avatarUrl" alt="Avatar" />
- </a>
-
- <div class="video-channel-info">
- <a [routerLink]="[ '/video-channels', videoChannel.uuid ]" class="video-channel-names" title="Go to the channel">
- <div class="video-channel-display-name">{{ videoChannel.displayName }}</div>
- <!-- Hide the name for now, because it's an UUID not very friendly -->
- <!--<div class="video-channel-name">{{ videoChannel.name }}</div>-->
- </a>
-
- <div class="video-channel-followers">{{ videoChannel.followersCount }} subscribers</div>
- </div>
-
- <div class="video-channel-buttons">
- <my-delete-button (click)="deleteVideoChannel(videoChannel)"></my-delete-button>
-
- <my-edit-button [routerLink]="[ 'update', videoChannel.uuid ]"></my-edit-button>
- </div>
- </div>
-</div>
+++ /dev/null
-@import '_variables';
-@import '_mixins';
-
-.create-button {
- @include create-button;
-}
-
-/deep/ .action-button {
- &.action-button-delete {
- margin-right: 10px;
- }
-}
-
-.video-channel {
- display: flex;
- min-height: 130px;
- padding-bottom: 20px;
- margin-bottom: 20px;
- border-bottom: 1px solid #C6C6C6;
-
- img {
- @include avatar(80px);
-
- margin-right: 10px;
- }
-
- .video-channel-info {
- flex-grow: 1;
-
- a.video-channel-names {
- @include disable-default-a-behaviour;
-
- display: flex;
- color: #000;
-
- .video-channel-display-name {
- font-weight: $font-semibold;
- font-size: 18px;
- }
-
- .video-channel-name {
- font-size: 14px;
- color: #777272;
- }
- }
- }
-
- .video-channel-buttons {
- min-width: 190px;
- }
-}
-
-.video-channels-header {
- text-align: right;
- margin: 20px 0 50px;
-}
-
-@media screen and (max-width: 800px) {
- .video-channel {
- flex-direction: column;
- height: auto;
- text-align: center;
-
- img {
- margin-right: 0;
- }
-
- .video-channel-buttons {
- margin-top: 10px;
- }
- }
-}
+++ /dev/null
-import { Component, OnInit } from '@angular/core'
-import { NotificationsService } from 'angular2-notifications'
-import 'rxjs/add/observable/from'
-import 'rxjs/add/operator/concatAll'
-import { AuthService } from '../../core/auth'
-import { ConfirmService } from '../../core/confirm'
-import { VideoChannel } from '@app/shared/video-channel/video-channel.model'
-import { VideoChannelService } from '@app/shared/video-channel/video-channel.service'
-import { User } from '@app/shared'
-
-@Component({
- selector: 'my-account-video-channels',
- templateUrl: './my-account-video-channels.component.html',
- styleUrls: [ './my-account-video-channels.component.scss' ]
-})
-export class MyAccountVideoChannelsComponent implements OnInit {
- videoChannels: VideoChannel[] = []
-
- private user: User
-
- constructor (
- private authService: AuthService,
- private notificationsService: NotificationsService,
- private confirmService: ConfirmService,
- private videoChannelService: VideoChannelService
- ) {}
-
- ngOnInit () {
- this.user = this.authService.getUser()
-
- this.loadVideoChannels()
- }
-
- async deleteVideoChannel (videoChannel: VideoChannel) {
- const res = await this.confirmService.confirmWithInput(
- `Do you really want to delete ${videoChannel.displayName}? It will delete all videos uploaded in this channel too.`,
- 'Please type the name of the video channel to confirm',
- videoChannel.displayName,
- 'Delete'
- )
- if (res === false) return
-
- this.videoChannelService.removeVideoChannel(videoChannel)
- .subscribe(
- status => {
- this.loadVideoChannels()
- this.notificationsService.success('Success', `Video channel ${videoChannel.name} deleted.`)
- },
-
- error => this.notificationsService.error('Error', error.message)
- )
- }
-
- private loadVideoChannels () {
- this.authService.userInformationLoaded
- .flatMap(() => this.videoChannelService.listAccountVideoChannels(this.user.account.id))
- .subscribe(res => this.videoChannels = res.data)
- }
-}
+++ /dev/null
-<div *ngIf="pagination.totalItems === 0">No results.</div>
-
-<div
- myInfiniteScroller
- [pageHeight]="pageHeight"
- (nearOfTop)="onNearOfTop()" (nearOfBottom)="onNearOfBottom()" (pageChanged)="onPageChanged($event)"
- class="videos" #videosElement
->
- <div *ngFor="let videos of videoPages; let i = index" class="videos-page">
- <div class="video" *ngFor="let video of videos; let j = index">
- <div class="checkbox-container">
- <input [id]="'video-check-' + video.id" type="checkbox" [(ngModel)]="checkedVideos[video.id]" />
- <label [for]="'video-check-' + video.id"></label>
- </div>
-
- <my-video-thumbnail [video]="video"></my-video-thumbnail>
-
- <div class="video-info">
- <a class="video-info-name" [routerLink]="['/videos/watch', video.uuid]" [attr.title]="video.name">{{ video.name }}</a>
- <span class="video-info-date-views">{{ video.createdAt | myFromNow }} - {{ video.views | myNumberFormatter }} views</span>
- <div class="video-info-private">{{ video.privacy.label }}</div>
- </div>
-
- <!-- Display only once -->
- <div class="action-selection-mode" *ngIf="isInSelectionMode() === true && i === 0 && j === 0">
- <div class="action-selection-mode-child">
- <span class="action-button action-button-cancel-selection" (click)="abortSelectionMode()">
- Cancel
- </span>
-
- <span class="action-button action-button-delete-selection" (click)="deleteSelectedVideos()">
- <span class="icon icon-delete-white"></span>
- Delete
- </span>
- </div>
- </div>
-
- <div class="video-buttons" *ngIf="isInSelectionMode() === false">
- <my-delete-button (click)="deleteVideo(video)"></my-delete-button>
-
- <my-edit-button [routerLink]="[ '/videos', 'edit', video.uuid ]"></my-edit-button>
- </div>
- </div>
- </div>
-</div>
+++ /dev/null
-@import '_variables';
-@import '_mixins';
-
-.action-selection-mode {
- width: 174px;
- display: flex;
- justify-content: flex-end;
-
- .action-selection-mode-child {
- position: fixed;
-
- .action-button {
- display: inline-block;
- }
-
- .action-button-cancel-selection {
- @include peertube-button;
- @include grey-button;
-
- margin-right: 10px;
- }
-
- .action-button-delete-selection {
- @include peertube-button;
- @include orange-button;
- }
-
- .icon.icon-delete-white {
- @include icon(21px);
-
- position: relative;
- top: -2px;
- background-image: url('../../../assets/images/global/delete-white.svg');
- }
- }
-}
-
-/deep/ .action-button {
- &.action-button-delete {
- margin-right: 10px;
- }
-}
-
-.video {
- display: flex;
- min-height: 130px;
- padding-bottom: 20px;
- margin-bottom: 20px;
- border-bottom: 1px solid #C6C6C6;
-
- &:first-child {
- margin-top: 47px;
- }
-
- .checkbox-container {
- display: flex;
- align-items: center;
- margin-right: 20px;
- margin-left: 12px;
-
- input[type=checkbox] {
- @include peertube-checkbox(2px);
- }
- }
-
- my-video-thumbnail {
- margin-right: 10px;
- }
-
- .video-info {
- flex-grow: 1;
-
- .video-info-name {
- @include disable-default-a-behaviour;
-
- color: #000;
- display: block;
- font-size: 16px;
- font-weight: $font-semibold;
- }
-
- .video-info-date-views, .video-info-private {
- font-size: 13px;
-
- &.video-info-private {
- font-weight: $font-semibold;
- }
- }
- }
-
- .video-buttons {
- min-width: 190px;
- }
-}
-
-@media screen and (max-width: 800px) {
- .video {
- flex-direction: column;
- height: auto;
- text-align: center;
-
- input[type=checkbox] {
- display: none;
- }
-
- my-video-thumbnail {
- margin-right: 0;
- }
-
- .video-buttons {
- margin-top: 10px;
- }
- }
-}
+++ /dev/null
-import { Component, OnInit, OnDestroy } from '@angular/core'
-import { ActivatedRoute, Router } from '@angular/router'
-import { Location } from '@angular/common'
-import { immutableAssign } from '@app/shared/misc/utils'
-import { ComponentPagination } from '@app/shared/rest/component-pagination.model'
-import { NotificationsService } from 'angular2-notifications'
-import 'rxjs/add/observable/from'
-import 'rxjs/add/operator/concatAll'
-import { Observable } from 'rxjs/Observable'
-import { AuthService } from '../../core/auth'
-import { ConfirmService } from '../../core/confirm'
-import { AbstractVideoList } from '../../shared/video/abstract-video-list'
-import { Video } from '../../shared/video/video.model'
-import { VideoService } from '../../shared/video/video.service'
-
-@Component({
- selector: 'my-account-videos',
- templateUrl: './my-account-videos.component.html',
- styleUrls: [ './my-account-videos.component.scss' ]
-})
-export class MyAccountVideosComponent extends AbstractVideoList implements OnInit, OnDestroy {
- titlePage = 'My videos'
- currentRoute = '/my-account/videos'
- checkedVideos: { [ id: number ]: boolean } = {}
- pagination: ComponentPagination = {
- currentPage: 1,
- itemsPerPage: 5,
- totalItems: null
- }
-
- protected baseVideoWidth = -1
- protected baseVideoHeight = 155
-
- constructor (protected router: Router,
- protected route: ActivatedRoute,
- protected authService: AuthService,
- protected notificationsService: NotificationsService,
- protected confirmService: ConfirmService,
- protected location: Location,
- private videoService: VideoService) {
- super()
- }
-
- ngOnInit () {
- super.ngOnInit()
- }
-
- ngOnDestroy () {
- super.ngOnDestroy()
- }
-
- abortSelectionMode () {
- this.checkedVideos = {}
- }
-
- isInSelectionMode () {
- return Object.keys(this.checkedVideos).some(k => this.checkedVideos[k] === true)
- }
-
- getVideosObservable (page: number) {
- const newPagination = immutableAssign(this.pagination, { currentPage: page })
-
- return this.videoService.getMyVideos(newPagination, this.sort)
- }
-
- generateSyndicationList () {
- throw new Error('Method not implemented.')
- }
-
- async deleteSelectedVideos () {
- const toDeleteVideosIds = Object.keys(this.checkedVideos)
- .filter(k => this.checkedVideos[k] === true)
- .map(k => parseInt(k, 10))
-
- const res = await this.confirmService.confirm(`Do you really want to delete ${toDeleteVideosIds.length} videos?`, 'Delete')
- if (res === false) return
-
- const observables: Observable<any>[] = []
- for (const videoId of toDeleteVideosIds) {
- const o = this.videoService
- .removeVideo(videoId)
- .do(() => this.spliceVideosById(videoId))
-
- observables.push(o)
- }
-
- Observable.from(observables)
- .concatAll()
- .subscribe(
- res => {
- this.notificationsService.success('Success', `${toDeleteVideosIds.length} videos deleted.`)
- this.buildVideoPages()
- },
-
- err => this.notificationsService.error('Error', err.message)
- )
- }
-
- async deleteVideo (video: Video) {
- const res = await this.confirmService.confirm(`Do you really want to delete ${video.name}?`, 'Delete')
- if (res === false) return
-
- this.videoService.removeVideo(video.id)
- .subscribe(
- status => {
- this.notificationsService.success('Success', `Video ${video.name} deleted.`)
- this.spliceVideosById(video.id)
- this.buildVideoPages()
- },
-
- error => this.notificationsService.error('Error', error.message)
- )
- }
-
- protected buildVideoHeight () {
- // In account videos, the video height is fixed
- return this.baseVideoHeight
- }
-
- private spliceVideosById (id: number) {
- for (const key of Object.keys(this.loadedPages)) {
- const videos = this.loadedPages[key]
- const index = videos.findIndex(v => v.id === id)
-
- if (index !== -1) {
- videos.splice(index, 1)
- return
- }
- }
- }
-}
+++ /dev/null
-<div class="row">
- <div class="sub-menu">
- <a routerLink="/my-account/settings" routerLinkActive="active" class="title-page">My settings</a>
-
- <a routerLink="/my-account/video-channels" routerLinkActive="active" class="title-page">My video channels</a>
-
- <a routerLink="/my-account/videos" routerLinkActive="active" class="title-page">My videos</a>
- </div>
-
- <div class="margin-content">
- <router-outlet></router-outlet>
- </div>
-</div>
+++ /dev/null
-import { Component } from '@angular/core'
-
-@Component({
- selector: 'my-my-account',
- templateUrl: './my-account.component.html'
-})
-export class MyAccountComponent {}
+++ /dev/null
-import { NgModule } from '@angular/core'
-import { SharedModule } from '../shared'
-import { MyAccountRoutingModule } from './my-account-routing.module'
-import { MyAccountChangePasswordComponent } from './my-account-settings/my-account-change-password/my-account-change-password.component'
-import { MyAccountVideoSettingsComponent } from './my-account-settings/my-account-video-settings/my-account-video-settings.component'
-import { MyAccountSettingsComponent } from './my-account-settings/my-account-settings.component'
-import { MyAccountComponent } from './my-account.component'
-import { MyAccountVideosComponent } from './my-account-videos/my-account-videos.component'
-import { MyAccountProfileComponent } from '@app/my-account/my-account-settings/my-account-profile/my-account-profile.component'
-import { MyAccountVideoChannelsComponent } from '@app/my-account/my-account-video-channels/my-account-video-channels.component'
-import { MyAccountVideoChannelCreateComponent } from '@app/my-account/my-account-video-channels/my-account-video-channel-create.component'
-import { MyAccountVideoChannelUpdateComponent } from '@app/my-account/my-account-video-channels/my-account-video-channel-update.component'
-
-@NgModule({
- imports: [
- MyAccountRoutingModule,
- SharedModule
- ],
-
- declarations: [
- MyAccountComponent,
- MyAccountSettingsComponent,
- MyAccountChangePasswordComponent,
- MyAccountVideoSettingsComponent,
- MyAccountProfileComponent,
- MyAccountVideosComponent,
- MyAccountVideoChannelsComponent,
- MyAccountVideoChannelCreateComponent,
- MyAccountVideoChannelUpdateComponent
- ],
-
- exports: [
- MyAccountComponent
- ],
-
- providers: []
-})
-export class MyAccountModule { }