Design second video upload step
authorChocobozzz <florian.bigard@gmail.com>
Fri, 8 Dec 2017 07:39:15 +0000 (08:39 +0100)
committerChocobozzz <florian.bigard@gmail.com>
Fri, 8 Dec 2017 08:57:29 +0000 (09:57 +0100)
26 files changed:
client/src/app/core/auth/auth.service.ts
client/src/app/core/server/server.service.ts
client/src/app/menu/menu.component.scss
client/src/app/shared/forms/form-validators/video.ts
client/src/app/shared/video/video-edit.model.ts
client/src/app/shared/video/video.service.ts
client/src/app/videos/+video-edit/shared/video-description.component.html [new file with mode: 0644]
client/src/app/videos/+video-edit/shared/video-description.component.scss [new file with mode: 0644]
client/src/app/videos/+video-edit/shared/video-description.component.ts [new file with mode: 0644]
client/src/app/videos/+video-edit/shared/video-edit.component.scss
client/src/app/videos/+video-edit/shared/video-edit.module.ts
client/src/app/videos/+video-edit/video-add.component.html
client/src/app/videos/+video-edit/video-add.component.scss
client/src/app/videos/+video-edit/video-add.component.ts
client/src/app/videos/+video-edit/video-update.component.html
client/src/app/videos/+video-watch/video-watch.component.html
client/src/app/videos/+video-watch/video-watch.component.ts
client/src/app/videos/shared/index.ts
client/src/app/videos/shared/video-description.component.html [deleted file]
client/src/app/videos/shared/video-description.component.scss [deleted file]
client/src/app/videos/shared/video-description.component.ts [deleted file]
client/src/sass/_mixins.scss
client/src/sass/application.scss
server/controllers/api/videos/index.ts
server/tests/api/single-server.ts
server/tests/utils/videos.ts

index fd2708c11e4d3260c69079f0bef1856a216e2237..0db197f0201d8e8ec665523a4dfaeba8dc13cbb7 100644 (file)
@@ -194,7 +194,6 @@ export class AuthService {
     }
 
     this.mergeUserInformation(obj)
-      .do(() => this.userInformationLoaded.next(true))
       .subscribe(
         res => {
           this.user.displayNSFW = res.displayNSFW
@@ -203,6 +202,8 @@ export class AuthService {
           this.user.account = res.account
 
           this.user.save()
+
+          this.userInformationLoaded.next(true)
         }
       )
   }
index 43a836c5a5423f121927dfa6e9582f4e0e7622b3..16e0595b6fefa8f4d8fcff47c6a69f79639afa8a 100644 (file)
@@ -77,7 +77,6 @@ export class ServerService {
     notifier: ReplaySubject<boolean>
   ) {
     return this.http.get(ServerService.BASE_VIDEO_URL + attributeName)
-      .do(() => notifier.next(true))
        .subscribe(data => {
          Object.keys(data)
                .forEach(dataKey => {
@@ -86,6 +85,8 @@ export class ServerService {
                    label: data[dataKey]
                  })
                })
+
+         notifier.next(true)
        })
   }
 }
index eda3e1a85e553fb43f1097e2ee6a33a2cf1a3ec6..63d63d2874f18fadb25dad91b46e6d02c7950e54 100644 (file)
@@ -43,6 +43,10 @@ menu {
       .logged-in-email {
         font-size: 13px;
         color: #C6C6C6;
+        white-space: nowrap;
+        overflow: hidden;
+        text-overflow: ellipsis;
+        max-width: 140px;
       }
     }
 
index 8e512e8c82695dcc48a086f556bca51fd4421221..45da7df4a53cf27678dc13da4e3b01ac2c74ea82 100644 (file)
@@ -23,17 +23,13 @@ export const VIDEO_PRIVACY = {
 }
 
 export const VIDEO_CATEGORY = {
-  VALIDATORS: [ Validators.required ],
-  MESSAGES: {
-    'required': 'Video category is required.'
-  }
+  VALIDATORS: [ ],
+  MESSAGES: {}
 }
 
 export const VIDEO_LICENCE = {
-  VALIDATORS: [ Validators.required ],
-  MESSAGES: {
-    'required': 'Video licence is required.'
-  }
+  VALIDATORS: [ ],
+  MESSAGES: {}
 }
 
 export const VIDEO_LANGUAGE = {
@@ -49,9 +45,8 @@ export const VIDEO_CHANNEL = {
 }
 
 export const VIDEO_DESCRIPTION = {
-  VALIDATORS: [ Validators.required, Validators.minLength(3), Validators.maxLength(3000) ],
+  VALIDATORS: [ Validators.minLength(3), Validators.maxLength(3000) ],
   MESSAGES: {
-    'required': 'Video description is required.',
     'minlength': 'Video description must be at least 3 characters long.',
     'maxlength': 'Video description cannot be more than 3000 characters long.'
   }
@@ -64,10 +59,3 @@ export const VIDEO_TAGS = {
     'maxlength': 'A tag should be less than 30 characters long.'
   }
 }
-
-export const VIDEO_FILE = {
-  VALIDATORS: [ Validators.required ],
-  MESSAGES: {
-    'required': 'Video file is required.'
-  }
-}
index 88d23a59f036ffa5449ccca7b0e5958029cb74c7..955255bfa0dca2a3f40f3fda815c1ce41640a6eb 100644 (file)
@@ -14,18 +14,20 @@ export class VideoEdit {
   uuid?: string
   id?: number
 
-  constructor (videoDetails: VideoDetails) {
-    this.id = videoDetails.id
-    this.uuid = videoDetails.uuid
-    this.category = videoDetails.category
-    this.licence = videoDetails.licence
-    this.language = videoDetails.language
-    this.description = videoDetails.description
-    this.name = videoDetails.name
-    this.tags = videoDetails.tags
-    this.nsfw = videoDetails.nsfw
-    this.channel = videoDetails.channel.id
-    this.privacy = videoDetails.privacy
+  constructor (videoDetails?: VideoDetails) {
+    if (videoDetails) {
+      this.id = videoDetails.id
+      this.uuid = videoDetails.uuid
+      this.category = videoDetails.category
+      this.licence = videoDetails.licence
+      this.language = videoDetails.language
+      this.description = videoDetails.description
+      this.name = videoDetails.name
+      this.tags = videoDetails.tags
+      this.nsfw = videoDetails.nsfw
+      this.channel = videoDetails.channel.id
+      this.privacy = videoDetails.privacy
+    }
   }
 
   patch (values: Object) {
index 3f35b67c42ab1a30190f1fa37f8dbb0c43287e27..1a0644c3d896294ad2fe9538ea5b072324a99b7d 100644 (file)
@@ -42,14 +42,17 @@ export class VideoService {
   }
 
   updateVideo (video: VideoEdit) {
-    const language = video.language ? video.language : null
+    const language = video.language || undefined
+    const licence = video.licence || undefined
+    const category = video.category || undefined
+    const description = video.description || undefined
 
     const body: VideoUpdate = {
       name: video.name,
-      category: video.category,
-      licence: video.licence,
+      category,
+      licence,
       language,
-      description: video.description,
+      description,
       privacy: video.privacy,
       tags: video.tags,
       nsfw: video.nsfw
diff --git a/client/src/app/videos/+video-edit/shared/video-description.component.html b/client/src/app/videos/+video-edit/shared/video-description.component.html
new file mode 100644 (file)
index 0000000..da66a97
--- /dev/null
@@ -0,0 +1,9 @@
+<textarea
+  [(ngModel)]="description" (ngModelChange)="onModelChange()"
+  id="description" placeholder="My super video">
+</textarea>
+
+<tabset #staticTabs class="previews">
+  <tab heading="Truncated description preview" [innerHTML]="truncatedDescriptionHTML"></tab>
+  <tab heading="Complete description preview" [innerHTML]="descriptionHTML"></tab>
+</tabset>
diff --git a/client/src/app/videos/+video-edit/shared/video-description.component.scss b/client/src/app/videos/+video-edit/shared/video-description.component.scss
new file mode 100644 (file)
index 0000000..38506bb
--- /dev/null
@@ -0,0 +1,41 @@
+textarea {
+  @include peertube-input-text(100%);
+
+  padding: 5px 15px;
+  font-size: 15px;
+  height: 150px;
+}
+
+.previews /deep/ {
+  font-size: 15px !important;
+
+  .nav {
+    margin-top: 10px;
+    font-size: 16px !important;
+    border: none !important;
+
+    .nav-item .nav-link {
+      color: #000 !important;
+      height: 30px !important;
+      margin-right: 30px;
+      padding: 0 15px;
+      display: flex;
+      align-items: center;
+      border-radius: 3px;
+      border: none !important;
+
+      &.active, &:hover {
+        background-color: #F0F0F0;
+      }
+
+      &.active {
+        font-weight: $font-semibold !important;
+      }
+    }
+  }
+
+  .tab-content {
+    min-height: 75px;
+    padding: 15px;
+  }
+}
diff --git a/client/src/app/videos/+video-edit/shared/video-description.component.ts b/client/src/app/videos/+video-edit/shared/video-description.component.ts
new file mode 100644 (file)
index 0000000..8dfb74b
--- /dev/null
@@ -0,0 +1,66 @@
+import { Component, forwardRef, Input, OnInit } from '@angular/core'
+import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'
+import { truncate } from 'lodash'
+import 'rxjs/add/operator/debounceTime'
+import 'rxjs/add/operator/distinctUntilChanged'
+import { Subject } from 'rxjs/Subject'
+import { MarkdownService } from '../../shared'
+
+@Component({
+  selector: 'my-video-description',
+  templateUrl: './video-description.component.html',
+  styleUrls: [ './video-description.component.scss' ],
+  providers: [
+    {
+      provide: NG_VALUE_ACCESSOR,
+      useExisting: forwardRef(() => VideoDescriptionComponent),
+      multi: true
+    }
+  ]
+})
+
+export class VideoDescriptionComponent implements ControlValueAccessor, OnInit {
+  @Input() description = ''
+  truncatedDescriptionHTML = ''
+  descriptionHTML = ''
+
+  private descriptionChanged = new Subject<string>()
+
+  constructor (private markdownService: MarkdownService) {}
+
+  ngOnInit () {
+    this.descriptionChanged
+      .debounceTime(150)
+      .distinctUntilChanged()
+      .subscribe(() => this.updateDescriptionPreviews())
+
+    this.descriptionChanged.next(this.description)
+  }
+
+  propagateChange = (_: any) => { /* empty */ }
+
+  writeValue (description: string) {
+    this.description = description
+
+    this.descriptionChanged.next(this.description)
+  }
+
+  registerOnChange (fn: (_: any) => void) {
+    this.propagateChange = fn
+  }
+
+  registerOnTouched () {
+    // Unused
+  }
+
+  onModelChange () {
+    this.propagateChange(this.description)
+
+    this.descriptionChanged.next(this.description)
+  }
+
+  private updateDescriptionPreviews () {
+    this.truncatedDescriptionHTML = this.markdownService.markdownToHTML(truncate(this.description, { length: 250 }))
+    this.descriptionHTML = this.markdownService.markdownToHTML(this.description)
+  }
+}
index 2d0bfc36e60b058baa370e629ef66888385aecf9..d363499ce1c7147ec6f829e5eb4366e76e81e6b9 100644 (file)
   position: relative;
   bottom: $button-height;
 
+  .message-submit {
+    display: inline-block;
+    margin-right: 25px;
+
+    color: #585858;
+    font-size: 15px;
+  }
+
   .submit-button {
     @include peertube-button;
     @include orange-button;
@@ -54,6 +62,7 @@
       background-color: inherit;
       border: none;
       padding: 0;
+      outline: 0;
     }
 
     .icon.icon-validate {
index c7ed8c351ffc29260bc92569beaa48e3133cc111..ce106d82f5bcb19e13bef04a74093753ec2bf3a4 100644 (file)
@@ -3,8 +3,9 @@ import { NgModule } from '@angular/core'
 import { TagInputModule } from 'ngx-chips'
 import { TabsModule } from 'ngx-bootstrap/tabs'
 
-import { MarkdownService, VideoDescriptionComponent } from '../../shared'
+import { MarkdownService } from '../../shared'
 import { SharedModule } from '../../../shared'
+import { VideoDescriptionComponent } from './video-description.component'
 import { VideoEditComponent } from './video-edit.component'
 
 @NgModule({
index 6883f828023c113dd74b1e49cd8dad06bd2f6228..a6f2bf6f28e50146e2b8858bd7a7d6f528a63dc8 100644 (file)
       </div>
 
       <div class="form-group">
-        <select [(ngModel)]="firstStepPrivacy">
+        <select [(ngModel)]="firstStepPrivacyId">
           <option *ngFor="let privacy of videoPrivacies" [value]="privacy.id">{{ privacy.label }}</option>
         </select>
       </div>
 
       <div class="form-group">
-        <select [(ngModel)]="firstStepChannel">
+        <select [(ngModel)]="firstStepChannelId">
           <option *ngFor="let channel of userVideoChannels" [value]="channel.id">{{ channel.label }}</option>
         </select>
       </div>
     </div>
   </div>
 
-  <p-progressBar *ngIf="isUploadingVideo" [value]="videoUploadPercents"></p-progressBar>
+  <p-progressBar
+      *ngIf="isUploadingVideo" [value]="videoUploadPercents"
+      [ngClass]="{ processing: videoUploadPercents === 100 && videoUploaded === false }"
+  ></p-progressBar>
 
   <!-- Hidden because we need to load the component -->
   <form [hidden]="!isUploadingVideo" novalidate [formGroup]="form">
         [validationMessages]="validationMessages" [videoPrivacies]="videoPrivacies"
     ></my-video-edit>
 
+
     <div class="submit-container">
-      <div class="submit-button" [ngClass]="{ disabled: !form.valid }">
+      <div *ngIf="videoUploaded === false" class="message-submit">Publish will be available when upload is finished</div>
+
+      <div class="submit-button" (click)="updateSecondStep()" [ngClass]="{ disabled: !form.valid || videoUploaded !== true }">
         <span class="icon icon-validate"></span>
-        <input type="button" value="Publish" (click)="upload()" />
+        <input type="button" value="Publish" />
       </div>
     </div>
   </form>
index dfdf7ff73d25029125d128d96155939da9bb84af..39673b4b7c7b22129d11e02b4b07b081b5060a95 100644 (file)
@@ -18,6 +18,7 @@
     .icon.icon-upload {
       @include icon(90px);
       margin-bottom: 25px;
+      cursor: default;
 
       background-image: url('../../../assets/images/video/upload.svg');
     }
 }
 
 p-progressBar {
-  margin-top: 50px;
-  margin-bottom: 40px;
-
   /deep/ .ui-progressbar {
+    margin-top: 25px !important;
+    margin-bottom: 40px !important;
     font-size: 15px !important;
     color: #fff !important;
     height: 30px !important;
@@ -76,6 +76,19 @@ p-progressBar {
     .ui-progressbar-label {
       text-align: left;
       padding-left: 18px;
+      margin-top: 0 !important;
+    }
+  }
+
+  &.processing {
+    /deep/ .ui-progressbar-label {
+      // Same color as background to hide "100%"
+      color: rgba(11, 204, 41, 0.16) !important;
+
+      &::before {
+        content: 'Processing...';
+        color: #fff;
+      }
     }
   }
 }
index 2409acfda38c3d52fe8078a1dbbc0b78198d1bd8..2bbc3de173f3857152795651e5e55eca0c842c75 100644 (file)
@@ -5,6 +5,7 @@ import { Router } from '@angular/router'
 import { NotificationsService } from 'angular2-notifications'
 import { VideoService } from 'app/shared/video/video.service'
 import { VideoCreate } from '../../../../../shared'
+import { VideoPrivacy } from '../../../../../shared/models/videos'
 import { AuthService, ServerService } from '../../core'
 import { FormReactive } from '../../shared'
 import { ValidatorMessage } from '../../shared/forms/form-validators'
@@ -25,6 +26,7 @@ export class VideoAddComponent extends FormReactive implements OnInit {
   isUploadingVideo = false
   videoUploaded = false
   videoUploadPercents = 0
+  videoUploadedId = 0
 
   error: string = null
   form: FormGroup
@@ -33,8 +35,8 @@ export class VideoAddComponent extends FormReactive implements OnInit {
 
   userVideoChannels = []
   videoPrivacies = []
-  firstStepPrivacy = 0
-  firstStepChannel = 0
+  firstStepPrivacyId = 0
+  firstStepChannelId = 0
 
   constructor (
     private formBuilder: FormBuilder,
@@ -59,7 +61,9 @@ export class VideoAddComponent extends FormReactive implements OnInit {
       .subscribe(
         () => {
           this.videoPrivacies = this.serverService.getVideoPrivacies()
-          this.firstStepPrivacy = this.videoPrivacies[0].id
+
+          // Public by default
+          this.firstStepPrivacyId = VideoPrivacy.PUBLIC
         })
 
     this.authService.userInformationLoaded
@@ -72,7 +76,7 @@ export class VideoAddComponent extends FormReactive implements OnInit {
           if (Array.isArray(videoChannels) === false) return
 
           this.userVideoChannels = videoChannels.map(v => ({ id: v.id, label: v.name }))
-          this.firstStepChannel = this.userVideoChannels[0].id
+          this.firstStepChannelId = this.userVideoChannels[0].id
         }
       )
   }
@@ -89,14 +93,15 @@ export class VideoAddComponent extends FormReactive implements OnInit {
 
   uploadFirstStep () {
     const videofile = this.videofileInput.nativeElement.files[0]
-    const name = videofile.name
-    const privacy = this.firstStepPrivacy.toString()
+    const name = videofile.name.replace(/\.[^/.]+$/, '')
+    const privacy = this.firstStepPrivacyId.toString()
     const nsfw = false
-    const channelId = this.firstStepChannel.toString()
+    const channelId = this.firstStepChannelId.toString()
 
     const formData = new FormData()
     formData.append('name', name)
-    formData.append('privacy', privacy.toString())
+    // Put the video "private" -> we wait he validates the second step
+    formData.append('privacy', VideoPrivacy.PRIVATE.toString())
     formData.append('nsfw', '' + nsfw)
     formData.append('channelId', '' + channelId)
     formData.append('videofile', videofile)
@@ -117,6 +122,8 @@ export class VideoAddComponent extends FormReactive implements OnInit {
           console.log('Video uploaded.')
 
           this.videoUploaded = true
+
+          this.videoUploadedId = event.body.video.id
         }
       },
 
@@ -133,13 +140,16 @@ export class VideoAddComponent extends FormReactive implements OnInit {
       return
     }
 
-    const video = new VideoEdit(this.form.value)
+    const video = new VideoEdit()
+    video.patch(this.form.value)
+    video.channel = this.firstStepChannelId
+    video.id = this.videoUploadedId
 
     this.videoService.updateVideo(video)
       .subscribe(
         () => {
           this.notificationsService.success('Success', 'Video published.')
-          this.router.navigate([ '/videos/watch', video.uuid ])
+          this.router.navigate([ '/videos/watch', video.id ])
         },
 
         err => {
index 3163495bfc872529c5f22e1d8db5237fa7903b51..261b8a130330970553dad91166949c2ffc6f73ba 100644 (file)
@@ -11,9 +11,9 @@
     ></my-video-edit>
 
     <div class="submit-container">
-      <div class="submit-button" [ngClass]="{ disabled: !form.valid }">
+      <div class="submit-button" (click)="update()" [ngClass]="{ disabled: !form.valid }">
         <span class="icon icon-validate"></span>
-        <input type="button" value="Update" (click)="update()" />
+        <input type="button" value="Update" />
       </div>
     </div>
   </form>
index 583da4685017ed5fb0db615e703f43179066728e..dfed4768c48ceb9bc897965ef85a6019d7e092f5 100644 (file)
@@ -78,7 +78,7 @@
     <div class="video-info-description">
       <div class="video-info-description-html" [innerHTML]="videoHTMLDescription"></div>
 
-      <div class="video-info-description-more" *ngIf="completeDescriptionShown === false && video.description.length === 250" (click)="showMoreDescription()">
+      <div class="video-info-description-more" *ngIf="completeDescriptionShown === false && video.description?.length === 250" (click)="showMoreDescription()">
         Show more
         <span *ngIf="descriptionLoading === false" class="glyphicon glyphicon-menu-down"></span>
         <my-loader class="description-loading" [loading]="descriptionLoading"></my-loader>
index 87db023bfe6d7ec0d00eadae218b3c112b9a1568..d4e3ec014903c8b051ba6a0af72d0726f192ccdb 100644 (file)
@@ -219,6 +219,11 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
   }
 
   private setVideoDescriptionHTML () {
+    if (!this.video.description) {
+      this.videoHTMLDescription = ''
+      return
+    }
+
     this.videoHTMLDescription = this.markdownService.markdownToHTML(this.video.description)
   }
 
index 3c72ed895e6e4dfd0f721f250205ac8ddb361e4a..7a66944b945868f3c2bca422d2dd033d6142e5da 100644 (file)
@@ -1,2 +1 @@
 export * from './markdown.service'
-export * from './video-description.component'
diff --git a/client/src/app/videos/shared/video-description.component.html b/client/src/app/videos/shared/video-description.component.html
deleted file mode 100644 (file)
index da66a97..0000000
+++ /dev/null
@@ -1,9 +0,0 @@
-<textarea
-  [(ngModel)]="description" (ngModelChange)="onModelChange()"
-  id="description" placeholder="My super video">
-</textarea>
-
-<tabset #staticTabs class="previews">
-  <tab heading="Truncated description preview" [innerHTML]="truncatedDescriptionHTML"></tab>
-  <tab heading="Complete description preview" [innerHTML]="descriptionHTML"></tab>
-</tabset>
diff --git a/client/src/app/videos/shared/video-description.component.scss b/client/src/app/videos/shared/video-description.component.scss
deleted file mode 100644 (file)
index 6ef81ae..0000000
+++ /dev/null
@@ -1,19 +0,0 @@
-textarea {
-  @include peertube-input-text(100%);
-
-  font-size: 15px;
-  height: 150px;
-}
-
-.previews /deep/ {
-  font-size: 15px !important;
-
-  .nav {
-    margin-top: 10px;
-  }
-
-  .tab-content {
-    min-height: 75px;
-    padding: 5px;
-  }
-}
diff --git a/client/src/app/videos/shared/video-description.component.ts b/client/src/app/videos/shared/video-description.component.ts
deleted file mode 100644 (file)
index d9ffb78..0000000
+++ /dev/null
@@ -1,68 +0,0 @@
-import { Component, forwardRef, Input, OnInit } from '@angular/core'
-import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'
-import { Subject } from 'rxjs/Subject'
-import 'rxjs/add/operator/debounceTime'
-import 'rxjs/add/operator/distinctUntilChanged'
-
-import { truncate } from 'lodash'
-
-import { MarkdownService } from './markdown.service'
-
-@Component({
-  selector: 'my-video-description',
-  templateUrl: './video-description.component.html',
-  styleUrls: [ './video-description.component.scss' ],
-  providers: [
-    {
-      provide: NG_VALUE_ACCESSOR,
-      useExisting: forwardRef(() => VideoDescriptionComponent),
-      multi: true
-    }
-  ]
-})
-
-export class VideoDescriptionComponent implements ControlValueAccessor, OnInit {
-  @Input() description = ''
-  truncatedDescriptionHTML = ''
-  descriptionHTML = ''
-
-  private descriptionChanged = new Subject<string>()
-
-  constructor (private markdownService: MarkdownService) {}
-
-  ngOnInit () {
-    this.descriptionChanged
-      .debounceTime(150)
-      .distinctUntilChanged()
-      .subscribe(() => this.updateDescriptionPreviews())
-
-    this.descriptionChanged.next(this.description)
-  }
-
-  propagateChange = (_: any) => { /* empty */ }
-
-  writeValue (description: string) {
-    this.description = description
-
-    this.descriptionChanged.next(this.description)
-  }
-
-  registerOnChange (fn: (_: any) => void) {
-    this.propagateChange = fn
-  }
-
-  registerOnTouched () {
-    // Unused
-  }
-
-  onModelChange () {
-    this.propagateChange(this.description)
-
-    this.descriptionChanged.next(this.description)
-  }
-
-  private updateDescriptionPreviews () {
-    this.truncatedDescriptionHTML = this.markdownService.markdownToHTML(truncate(this.description, { length: 250 }))
-    this.descriptionHTML = this.markdownService.markdownToHTML(this.description)
-  }
-}
index 121e16e10653fb274b66dfc3f0986f0e61723a58..d9c9e45ec09537fbecefa6a6439f4f1378537cbe 100644 (file)
@@ -1,5 +1,5 @@
 @mixin disable-default-a-behaviour {
-  &:hover, &:focus {
+  &:hover, &:focus, &:active {
     text-decoration: none !important;
     outline: none !important;
   }
   color: #fff;
   background-color: $orange-color;
 
-  &:hover, &:active, &:focus, &[disabled], &.disabled {
+  &:hover, &:active, &:focus {
     color: #fff;
     background-color: $orange-hoover-color;
   }
 
   &[disabled], &.disabled {
     cursor: default;
+    color: #fff;
+    background-color: #C6C6C6;
   }
 }
 
index 0c999d6599a3148c1c3f9bf7c2263792e1a2b7f7..3c5a0030970a9115e9b352af4ec5cab75fc090c3 100644 (file)
@@ -86,6 +86,10 @@ label {
     margin-top: 30px;
     margin-bottom: 25px;
   }
+
+  &:hover, &:active, &:focus {
+    color: #000;
+}
 }
 
 // On small screen, menu is absolute and displayed over the page
index 2b70d535e049571bb7819b090106ba481c985f74..f427a25c02639009e8aedc706830dda170316c4e 100644 (file)
@@ -15,6 +15,7 @@ import { getServerAccount } from '../../../helpers/utils'
 import { CONFIG, VIDEO_CATEGORIES, VIDEO_LANGUAGES, VIDEO_LICENCES, VIDEO_MIMETYPE_EXT, VIDEO_PRIVACIES } from '../../../initializers'
 import { database as db } from '../../../initializers/database'
 import { sendAddVideo } from '../../../lib/activitypub/send/send-add'
+import { sendCreateViewToOrigin } from '../../../lib/activitypub/send/send-create'
 import { sendUpdateVideo } from '../../../lib/activitypub/send/send-update'
 import { shareVideoByServer } from '../../../lib/activitypub/share'
 import { getVideoActivityPubUrl } from '../../../lib/activitypub/url'
@@ -39,7 +40,6 @@ import { abuseVideoRouter } from './abuse'
 import { blacklistRouter } from './blacklist'
 import { videoChannelRouter } from './channel'
 import { rateVideoRouter } from './rate'
-import { sendCreateViewToOrigin } from '../../../lib/activitypub/send/send-create'
 
 const videosRouter = express.Router()
 
@@ -154,17 +154,20 @@ async function addVideoRetryWrapper (req: express.Request, res: express.Response
     errorMessage: 'Cannot insert the video with many retries.'
   }
 
-  await retryTransactionWrapper(addVideo, options)
+  const video = await retryTransactionWrapper(addVideo, options)
 
-  // TODO : include Location of the new video -> 201
-  res.type('json').status(204).end()
+  res.json({
+    video: {
+      id: video.id,
+      uuid: video.uuid
+    }
+  }).end()
 }
 
-async function addVideo (req: express.Request, res: express.Response, videoPhysicalFile: Express.Multer.File) {
+function addVideo (req: express.Request, res: express.Response, videoPhysicalFile: Express.Multer.File) {
   const videoInfo: VideoCreate = req.body
-  let videoUUID = ''
 
-  await db.sequelize.transaction(async t => {
+  return db.sequelize.transaction(async t => {
     const sequelizeOptions = { transaction: t }
 
     const videoData = {
@@ -223,7 +226,6 @@ async function addVideo (req: express.Request, res: express.Response, videoPhysi
     const videoCreated = await video.save(sequelizeOptions)
     // Do not forget to add video channel information to the created video
     videoCreated.VideoChannel = res.locals.videoChannel
-    videoUUID = videoCreated.uuid
 
     videoFile.videoId = video.id
 
@@ -238,15 +240,17 @@ async function addVideo (req: express.Request, res: express.Response, videoPhysi
     }
 
     // Let transcoding job send the video to friends because the video file extension might change
-    if (CONFIG.TRANSCODING.ENABLED === true) return undefined
+    if (CONFIG.TRANSCODING.ENABLED === true) return videoCreated
     // Don't send video to remote servers, it is private
-    if (video.privacy === VideoPrivacy.PRIVATE) return undefined
+    if (video.privacy === VideoPrivacy.PRIVATE) return videoCreated
 
     await sendAddVideo(video, t)
     await shareVideoByServer(video, t)
-  })
 
-  logger.info('Video with name %s and uuid %s created.', videoInfo.name, videoUUID)
+    logger.info('Video with name %s and uuid %s created.', videoInfo.name, videoCreated.uuid)
+
+    return videoCreated
+  })
 }
 
 async function updateVideoRetryWrapper (req: express.Request, res: express.Response, next: express.NextFunction) {
index fbb2dd1fb1c6ec5900afee9ad4505433f8a6cc90..ed79f9e1c61d5a5d87f2b356780d2f58ae0a4dd3 100644 (file)
@@ -104,7 +104,10 @@ describe('Test a single server', function () {
       licence: 6,
       tags: [ 'tag1', 'tag2', 'tag3' ]
     }
-    await uploadVideo(server.url, server.accessToken, videoAttributes)
+    const res = await uploadVideo(server.url, server.accessToken, videoAttributes)
+    expect(res.body.video).to.not.be.undefined
+    expect(res.body.video.id).to.equal(1)
+    expect(res.body.video.uuid).to.have.length.above(5)
   })
 
   it('Should seed the uploaded video', async function () {
index ff7da9bb280ce5f9a43c9bb493f28b0884398d15..bdf3368ac87d460401348eaead2596c7287f6d2a 100644 (file)
@@ -201,7 +201,7 @@ async function testVideoImage (url: string, imageName: string, imagePath: string
   }
 }
 
-async function uploadVideo (url: string, accessToken: string, videoAttributesArg: VideoAttributes, specialStatus = 204) {
+async function uploadVideo (url: string, accessToken: string, videoAttributesArg: VideoAttributes, specialStatus = 201) {
   const path = '/api/v1/videos/upload'
   let defaultChannelId = '1'