aboutsummaryrefslogtreecommitdiffhomepage
path: root/client
diff options
context:
space:
mode:
Diffstat (limited to 'client')
-rw-r--r--client/e2e/src/po/video-watch.po.ts29
-rw-r--r--client/e2e/src/suites-local/custom-server-defaults.e2e-spec.ts37
-rw-r--r--client/e2e/src/suites-local/videos-list.e2e-spec.ts24
-rw-r--r--client/e2e/src/utils/hooks.ts64
-rw-r--r--client/e2e/src/utils/index.ts2
-rw-r--r--client/e2e/src/utils/server.ts63
-rw-r--r--client/e2e/wdio.browserstack.conf.ts7
-rw-r--r--client/e2e/wdio.local-test.conf.ts9
-rw-r--r--client/e2e/wdio.local.conf.ts13
-rw-r--r--client/src/app/+videos/+video-edit/shared/video-edit.component.ts9
-rw-r--r--client/src/app/+videos/+video-edit/video-add-components/video-go-live.component.ts2
-rw-r--r--client/src/app/+videos/+video-edit/video-add-components/video-import-torrent.component.ts2
-rw-r--r--client/src/app/+videos/+video-edit/video-add-components/video-import-url.component.ts2
-rw-r--r--client/src/app/+videos/+video-edit/video-add-components/video-send.ts4
-rw-r--r--client/src/app/+videos/+video-edit/video-add-components/video-upload.component.ts2
-rw-r--r--client/src/app/+videos/+video-watch/shared/action-buttons/action-buttons.component.html2
-rw-r--r--client/src/app/+videos/+video-watch/shared/action-buttons/action-buttons.component.scss3
-rw-r--r--client/src/app/+videos/+video-watch/shared/metadata/video-attributes.component.html14
18 files changed, 252 insertions, 36 deletions
diff --git a/client/e2e/src/po/video-watch.po.ts b/client/e2e/src/po/video-watch.po.ts
index 41425f4d7..1406c971a 100644
--- a/client/e2e/src/po/video-watch.po.ts
+++ b/client/e2e/src/po/video-watch.po.ts
@@ -21,6 +21,24 @@ export class VideoWatchPage {
21 return this.getVideoNameElement().then(e => e.getText()) 21 return this.getVideoNameElement().then(e => e.getText())
22 } 22 }
23 23
24 getPrivacy () {
25 return $('.attribute-privacy .attribute-value').getText()
26 }
27
28 getLicence () {
29 return $('.attribute-licence .attribute-value').getText()
30 }
31
32 async isDownloadEnabled () {
33 await this.clickOnMoreDropdownIcon()
34
35 return $('.dropdown-item .icon-download').isExisting()
36 }
37
38 areCommentsEnabled () {
39 return $('my-video-comment-add').isExisting()
40 }
41
24 async goOnAssociatedEmbed () { 42 async goOnAssociatedEmbed () {
25 let url = await browser.getUrl() 43 let url = await browser.getUrl()
26 url = url.replace('/w/', '/videos/embed/') 44 url = url.replace('/w/', '/videos/embed/')
@@ -38,10 +56,8 @@ export class VideoWatchPage {
38 } 56 }
39 57
40 async clickOnUpdate () { 58 async clickOnUpdate () {
41 const dropdown = $('my-video-actions-dropdown .action-button') 59 await this.clickOnMoreDropdownIcon()
42 await dropdown.click()
43 60
44 await $('.dropdown-menu.show .dropdown-item').waitForDisplayed()
45 const items = await $$('.dropdown-menu.show .dropdown-item') 61 const items = await $$('.dropdown-menu.show .dropdown-item')
46 62
47 for (const item of items) { 63 for (const item of items) {
@@ -86,6 +102,13 @@ export class VideoWatchPage {
86 }, { timeout: maxTime }) 102 }, { timeout: maxTime })
87 } 103 }
88 104
105 async clickOnMoreDropdownIcon () {
106 const dropdown = $('my-video-actions-dropdown .action-button')
107 await dropdown.click()
108
109 await $('.dropdown-menu.show .dropdown-item').waitForDisplayed()
110 }
111
89 private async getVideoNameElement () { 112 private async getVideoNameElement () {
90 // We have 2 video info name block, pick the first that is not empty 113 // We have 2 video info name block, pick the first that is not empty
91 const elem = async () => { 114 const elem = async () => {
diff --git a/client/e2e/src/suites-local/custom-server-defaults.e2e-spec.ts b/client/e2e/src/suites-local/custom-server-defaults.e2e-spec.ts
new file mode 100644
index 000000000..c2c8edcc9
--- /dev/null
+++ b/client/e2e/src/suites-local/custom-server-defaults.e2e-spec.ts
@@ -0,0 +1,37 @@
1import { LoginPage } from '../po/login.po'
2import { VideoUploadPage } from '../po/video-upload.po'
3import { VideoWatchPage } from '../po/video-watch.po'
4import { isMobileDevice, isSafari, waitServerUp } from '../utils'
5
6describe('Custom server defaults', () => {
7 let videoUploadPage: VideoUploadPage
8 let loginPage: LoginPage
9 let videoWatchPage: VideoWatchPage
10
11 before(async () => {
12 await waitServerUp()
13 })
14
15 beforeEach(async () => {
16 loginPage = new LoginPage()
17 videoUploadPage = new VideoUploadPage()
18 videoWatchPage = new VideoWatchPage(isMobileDevice(), isSafari())
19
20 await browser.maximizeWindow()
21 })
22
23 it('Should upload a video with custom default values', async function () {
24 await loginPage.loginAsRootUser()
25 await videoUploadPage.navigateTo()
26 await videoUploadPage.uploadVideo()
27 await videoUploadPage.validSecondUploadStep('video')
28
29 await videoWatchPage.waitWatchVideoName('video')
30
31 expect(await videoWatchPage.getPrivacy()).toBe('Internal')
32 expect(await videoWatchPage.getLicence()).toBe('Attribution - Non Commercial')
33 expect(await videoWatchPage.isDownloadEnabled()).toBeFalsy()
34 expect(await videoWatchPage.areCommentsEnabled()).toBeFalsy()
35 })
36
37})
diff --git a/client/e2e/src/suites-local/videos-list.e2e-spec.ts b/client/e2e/src/suites-local/videos-list.e2e-spec.ts
index 1e0a88859..bca6018b9 100644
--- a/client/e2e/src/suites-local/videos-list.e2e-spec.ts
+++ b/client/e2e/src/suites-local/videos-list.e2e-spec.ts
@@ -4,6 +4,7 @@ import { MyAccountPage } from '../po/my-account'
4import { VideoListPage } from '../po/video-list.po' 4import { VideoListPage } from '../po/video-list.po'
5import { VideoSearchPage } from '../po/video-search.po' 5import { VideoSearchPage } from '../po/video-search.po'
6import { VideoUploadPage } from '../po/video-upload.po' 6import { VideoUploadPage } from '../po/video-upload.po'
7import { VideoWatchPage } from '../po/video-watch.po'
7import { NSFWPolicy } from '../types/common' 8import { NSFWPolicy } from '../types/common'
8import { isMobileDevice, isSafari, waitServerUp } from '../utils' 9import { isMobileDevice, isSafari, waitServerUp } from '../utils'
9 10
@@ -14,6 +15,7 @@ describe('Videos list', () => {
14 let loginPage: LoginPage 15 let loginPage: LoginPage
15 let myAccountPage: MyAccountPage 16 let myAccountPage: MyAccountPage
16 let videoSearchPage: VideoSearchPage 17 let videoSearchPage: VideoSearchPage
18 let videoWatchPage: VideoWatchPage
17 19
18 const seed = Math.random() 20 const seed = Math.random()
19 const nsfwVideo = seed + ' - nsfw' 21 const nsfwVideo = seed + ' - nsfw'
@@ -108,6 +110,7 @@ describe('Videos list', () => {
108 videoUploadPage = new VideoUploadPage() 110 videoUploadPage = new VideoUploadPage()
109 myAccountPage = new MyAccountPage() 111 myAccountPage = new MyAccountPage()
110 videoSearchPage = new VideoSearchPage() 112 videoSearchPage = new VideoSearchPage()
113 videoWatchPage = new VideoWatchPage(isMobileDevice(), isSafari())
111 114
112 await browser.maximizeWindow() 115 await browser.maximizeWindow()
113 }) 116 })
@@ -191,5 +194,26 @@ describe('Videos list', () => {
191 await checkCommonVideoListPages('display') 194 await checkCommonVideoListPages('display')
192 await checkSearchPage('display') 195 await checkSearchPage('display')
193 }) 196 })
197
198 after(async () => {
199 await loginPage.logout()
200 })
201 })
202
203 describe('Default upload values', function () {
204
205 it('Should have default video values', async function () {
206 await loginPage.loginAsRootUser()
207 await videoUploadPage.navigateTo()
208 await videoUploadPage.uploadVideo()
209 await videoUploadPage.validSecondUploadStep('video')
210
211 await videoWatchPage.waitWatchVideoName('video')
212
213 expect(await videoWatchPage.getPrivacy()).toBe('Public')
214 expect(await videoWatchPage.getLicence()).toBe('Unknown')
215 expect(await videoWatchPage.isDownloadEnabled()).toBeTruthy()
216 expect(await videoWatchPage.areCommentsEnabled()).toBeTruthy()
217 })
194 }) 218 })
195}) 219})
diff --git a/client/e2e/src/utils/hooks.ts b/client/e2e/src/utils/hooks.ts
new file mode 100644
index 000000000..e42c6a5d8
--- /dev/null
+++ b/client/e2e/src/utils/hooks.ts
@@ -0,0 +1,64 @@
1import { ChildProcessWithoutNullStreams } from 'child_process'
2import { basename } from 'path'
3import { runCommand, runServer } from './server'
4
5let appInstance: string
6let app: ChildProcessWithoutNullStreams
7
8async function beforeLocalSuite (suite: any) {
9 const config = buildConfig(suite.file)
10
11 await runCommand('npm run clean:server:test -- ' + appInstance)
12 app = runServer(appInstance, config)
13}
14
15function afterLocalSuite () {
16 app.kill()
17 app = undefined
18}
19
20function beforeLocalSession (config: { baseUrl: string }, capabilities: { browserName: string }) {
21 appInstance = capabilities['browserName'] === 'chrome' ? '1' : '2'
22 config.baseUrl = 'http://localhost:900' + appInstance
23}
24
25async function onBrowserStackPrepare () {
26 const appInstance = '1'
27
28 await runCommand('npm run clean:server:test -- ' + appInstance)
29 app = runServer(appInstance)
30}
31
32function onBrowserStackComplete () {
33 app.kill()
34 app = undefined
35}
36
37export {
38 beforeLocalSession,
39 afterLocalSuite,
40 beforeLocalSuite,
41 onBrowserStackPrepare,
42 onBrowserStackComplete
43}
44
45// ---------------------------------------------------------------------------
46
47function buildConfig (suiteFile: string = undefined) {
48 const filename = basename(suiteFile)
49
50 if (filename === 'custom-server-defaults.e2e-spec.ts') {
51 return {
52 defaults: {
53 publish: {
54 download_enabled: false,
55 comments_enabled: false,
56 privacy: 4,
57 licence: 4
58 }
59 }
60 }
61 }
62
63 return {}
64}
diff --git a/client/e2e/src/utils/index.ts b/client/e2e/src/utils/index.ts
index 5da1ad517..354352ee2 100644
--- a/client/e2e/src/utils/index.ts
+++ b/client/e2e/src/utils/index.ts
@@ -1,3 +1,5 @@
1export * from './common' 1export * from './common'
2export * from './elements' 2export * from './elements'
3export * from './hooks'
4export * from './server'
3export * from './urls' 5export * from './urls'
diff --git a/client/e2e/src/utils/server.ts b/client/e2e/src/utils/server.ts
new file mode 100644
index 000000000..7089a5c9c
--- /dev/null
+++ b/client/e2e/src/utils/server.ts
@@ -0,0 +1,63 @@
1import { exec, spawn } from 'child_process'
2import { join, resolve } from 'path'
3
4function runServer (appInstance: string, config: any = {}) {
5 const env = Object.create(process.env)
6 env['NODE_ENV'] = 'test'
7 env['NODE_APP_INSTANCE'] = appInstance
8
9 env['NODE_CONFIG'] = JSON.stringify({
10 rates_limit: {
11 api: {
12 max: 5000
13 },
14 login: {
15 max: 5000
16 }
17 },
18 log: {
19 level: 'warn'
20 },
21 signup: {
22 enabled: false
23 },
24 transcoding: {
25 enabled: false
26 },
27
28 ...config
29 })
30
31 const forkOptions = {
32 env,
33 cwd: getRootCWD(),
34 detached: false
35 }
36
37 const p = spawn('node', [ join('dist', 'server.js') ], forkOptions)
38 p.stderr.on('data', data => console.error(data.toString()))
39 p.stdout.on('data', data => console.error(data.toString()))
40
41 return p
42}
43
44function runCommand (command: string) {
45 return new Promise<void>((res, rej) => {
46 const p = exec(command, { cwd: getRootCWD() })
47
48 p.stderr.on('data', data => console.error(data.toString()))
49 p.on('error', err => rej(err))
50 p.on('exit', () => res())
51 })
52}
53
54export {
55 runServer,
56 runCommand
57}
58
59// ---------------------------------------------------------------------------
60
61function getRootCWD () {
62 return resolve('../..')
63}
diff --git a/client/e2e/wdio.browserstack.conf.ts b/client/e2e/wdio.browserstack.conf.ts
index 43614a862..b89cdbc2e 100644
--- a/client/e2e/wdio.browserstack.conf.ts
+++ b/client/e2e/wdio.browserstack.conf.ts
@@ -1,3 +1,4 @@
1import { onBrowserStackComplete, onBrowserStackPrepare } from './src/utils'
1import { config as mainConfig } from './wdio.main.conf' 2import { config as mainConfig } from './wdio.main.conf'
2 3
3const user = process.env.BROWSERSTACK_USER 4const user = process.env.BROWSERSTACK_USER
@@ -114,6 +115,10 @@ module.exports = {
114 if (capabilities['bstack:options'].realMobile === true) { 115 if (capabilities['bstack:options'].realMobile === true) {
115 capabilities['bstack:options'].local = false 116 capabilities['bstack:options'].local = false
116 } 117 }
117 } 118 },
119
120 onPrepare: onBrowserStackPrepare,
121 onComplete: onBrowserStackComplete
122
118 } as WebdriverIO.Config 123 } as WebdriverIO.Config
119} 124}
diff --git a/client/e2e/wdio.local-test.conf.ts b/client/e2e/wdio.local-test.conf.ts
index 32e6d340c..5389ebcf0 100644
--- a/client/e2e/wdio.local-test.conf.ts
+++ b/client/e2e/wdio.local-test.conf.ts
@@ -1,3 +1,4 @@
1import { afterLocalSuite, beforeLocalSuite, beforeLocalSession } from './src/utils'
1import { config as mainConfig } from './wdio.main.conf' 2import { config as mainConfig } from './wdio.main.conf'
2 3
3const prefs = { 4const prefs = {
@@ -21,12 +22,16 @@ module.exports = {
21 browserName: 'chrome', 22 browserName: 'chrome',
22 acceptInsecureCerts: true, 23 acceptInsecureCerts: true,
23 'goog:chromeOptions': { 24 'goog:chromeOptions': {
24 args: [ '--headless', '--disable-gpu', '--window-size=1280,1024' ], 25 args: [ '--disable-gpu', '--window-size=1280,1024' ],
25 prefs 26 prefs
26 } 27 }
27 } 28 }
28 ], 29 ],
29 30
30 services: [ 'chromedriver' ] 31 services: [ 'chromedriver' ],
32
33 beforeSession: beforeLocalSession,
34 beforeSuite: beforeLocalSuite,
35 afterSuite: afterLocalSuite
31 } as WebdriverIO.Config 36 } as WebdriverIO.Config
32} 37}
diff --git a/client/e2e/wdio.local.conf.ts b/client/e2e/wdio.local.conf.ts
index 43b820ca6..d02679e06 100644
--- a/client/e2e/wdio.local.conf.ts
+++ b/client/e2e/wdio.local.conf.ts
@@ -1,3 +1,4 @@
1import { afterLocalSuite, beforeLocalSession, beforeLocalSuite } from './src/utils'
1import { config as mainConfig } from './wdio.main.conf' 2import { config as mainConfig } from './wdio.main.conf'
2 3
3const prefs = { 4const prefs = {
@@ -11,7 +12,7 @@ module.exports = {
11 12
12 runner: 'local', 13 runner: 'local',
13 14
14 maxInstances: 2, 15 maxInstancesPerCapability: 1,
15 16
16 capabilities: [ 17 capabilities: [
17 { 18 {
@@ -34,12 +35,8 @@ module.exports = {
34 35
35 services: [ 'chromedriver', 'geckodriver' ], 36 services: [ 'chromedriver', 'geckodriver' ],
36 37
37 beforeSession: function (config, capabilities) { 38 beforeSession: beforeLocalSession,
38 if (capabilities['browserName'] === 'chrome') { 39 beforeSuite: beforeLocalSuite,
39 config.baseUrl = 'http://localhost:9001' 40 afterSuite: afterLocalSuite
40 } else {
41 config.baseUrl = 'http://localhost:9002'
42 }
43 }
44 } as WebdriverIO.Config 41 } as WebdriverIO.Config
45} 42}
diff --git a/client/src/app/+videos/+video-edit/shared/video-edit.component.ts b/client/src/app/+videos/+video-edit/shared/video-edit.component.ts
index 39767f258..2bec933e9 100644
--- a/client/src/app/+videos/+video-edit/shared/video-edit.component.ts
+++ b/client/src/app/+videos/+video-edit/shared/video-edit.component.ts
@@ -110,9 +110,10 @@ export class VideoEditComponent implements OnInit, OnDestroy {
110 updateForm () { 110 updateForm () {
111 const defaultValues: any = { 111 const defaultValues: any = {
112 nsfw: 'false', 112 nsfw: 'false',
113 commentsEnabled: 'true', 113 commentsEnabled: this.serverConfig.defaults.publish.commentsEnabled,
114 downloadEnabled: 'true', 114 downloadEnabled: this.serverConfig.defaults.publish.downloadEnabled,
115 waitTranscoding: 'true', 115 waitTranscoding: 'true',
116 licence: this.serverConfig.defaults.publish.licence,
116 tags: [] 117 tags: []
117 } 118 }
118 const obj: any = { 119 const obj: any = {
@@ -160,6 +161,8 @@ export class VideoEditComponent implements OnInit, OnDestroy {
160 } 161 }
161 162
162 ngOnInit () { 163 ngOnInit () {
164 this.serverConfig = this.serverService.getHTMLConfig()
165
163 this.updateForm() 166 this.updateForm()
164 167
165 this.pluginService.ensurePluginsAreLoaded('video-edit') 168 this.pluginService.ensurePluginsAreLoaded('video-edit')
@@ -200,8 +203,6 @@ export class VideoEditComponent implements OnInit, OnDestroy {
200 } 203 }
201 }) 204 })
202 205
203 this.serverConfig = this.serverService.getHTMLConfig()
204
205 this.initialVideoCaptions = this.videoCaptions.map(c => c.language.id) 206 this.initialVideoCaptions = this.videoCaptions.map(c => c.language.id)
206 207
207 this.ngZone.runOutsideAngular(() => { 208 this.ngZone.runOutsideAngular(() => {
diff --git a/client/src/app/+videos/+video-edit/video-add-components/video-go-live.component.ts b/client/src/app/+videos/+video-edit/video-add-components/video-go-live.component.ts
index 01eabb0d7..46a7ebb0b 100644
--- a/client/src/app/+videos/+video-edit/video-add-components/video-go-live.component.ts
+++ b/client/src/app/+videos/+video-edit/video-add-components/video-go-live.component.ts
@@ -70,8 +70,6 @@ export class VideoGoLiveComponent extends VideoSend implements OnInit, AfterView
70 privacy: this.highestPrivacy, 70 privacy: this.highestPrivacy,
71 nsfw: this.serverConfig.instance.isNSFW, 71 nsfw: this.serverConfig.instance.isNSFW,
72 waitTranscoding: true, 72 waitTranscoding: true,
73 commentsEnabled: true,
74 downloadEnabled: true,
75 permanentLive: this.firstStepPermanentLive, 73 permanentLive: this.firstStepPermanentLive,
76 saveReplay: this.firstStepPermanentLive === false && this.isReplayAllowed(), 74 saveReplay: this.firstStepPermanentLive === false && this.isReplayAllowed(),
77 channelId: this.firstStepChannelId 75 channelId: this.firstStepChannelId
diff --git a/client/src/app/+videos/+video-edit/video-add-components/video-import-torrent.component.ts b/client/src/app/+videos/+video-edit/video-add-components/video-import-torrent.component.ts
index 87e47683f..5e758910e 100644
--- a/client/src/app/+videos/+video-edit/video-add-components/video-import-torrent.component.ts
+++ b/client/src/app/+videos/+video-edit/video-add-components/video-import-torrent.component.ts
@@ -81,8 +81,6 @@ export class VideoImportTorrentComponent extends VideoSend implements OnInit, Af
81 const videoUpdate: VideoUpdate = { 81 const videoUpdate: VideoUpdate = {
82 privacy: this.highestPrivacy, 82 privacy: this.highestPrivacy,
83 waitTranscoding: false, 83 waitTranscoding: false,
84 commentsEnabled: true,
85 downloadEnabled: true,
86 channelId: this.firstStepChannelId 84 channelId: this.firstStepChannelId
87 } 85 }
88 86
diff --git a/client/src/app/+videos/+video-edit/video-add-components/video-import-url.component.ts b/client/src/app/+videos/+video-edit/video-add-components/video-import-url.component.ts
index 3487c1adf..2ea70ed55 100644
--- a/client/src/app/+videos/+video-edit/video-add-components/video-import-url.component.ts
+++ b/client/src/app/+videos/+video-edit/video-add-components/video-import-url.component.ts
@@ -68,8 +68,6 @@ export class VideoImportUrlComponent extends VideoSend implements OnInit, AfterV
68 const videoUpdate: VideoUpdate = { 68 const videoUpdate: VideoUpdate = {
69 privacy: this.highestPrivacy, 69 privacy: this.highestPrivacy,
70 waitTranscoding: false, 70 waitTranscoding: false,
71 commentsEnabled: true,
72 downloadEnabled: true,
73 channelId: this.firstStepChannelId 71 channelId: this.firstStepChannelId
74 } 72 }
75 73
diff --git a/client/src/app/+videos/+video-edit/video-add-components/video-send.ts b/client/src/app/+videos/+video-edit/video-add-components/video-send.ts
index efa8c85a3..5e086ef42 100644
--- a/client/src/app/+videos/+video-edit/video-add-components/video-send.ts
+++ b/client/src/app/+videos/+video-edit/video-add-components/video-send.ts
@@ -49,7 +49,9 @@ export abstract class VideoSend extends FormReactive implements OnInit {
49 this.serverService.getVideoPrivacies() 49 this.serverService.getVideoPrivacies()
50 .subscribe( 50 .subscribe(
51 privacies => { 51 privacies => {
52 const { videoPrivacies, defaultPrivacyId } = this.videoService.explainedPrivacyLabels(privacies) 52 const defaultPrivacy = this.serverConfig.defaults.publish.privacy
53
54 const { videoPrivacies, defaultPrivacyId } = this.videoService.explainedPrivacyLabels(privacies, defaultPrivacy)
53 55
54 this.videoPrivacies = videoPrivacies 56 this.videoPrivacies = videoPrivacies
55 this.firstStepPrivacyId = defaultPrivacyId 57 this.firstStepPrivacyId = defaultPrivacyId
diff --git a/client/src/app/+videos/+video-edit/video-add-components/video-upload.component.ts b/client/src/app/+videos/+video-edit/video-add-components/video-upload.component.ts
index 28d7ec458..76f154249 100644
--- a/client/src/app/+videos/+video-edit/video-add-components/video-upload.component.ts
+++ b/client/src/app/+videos/+video-edit/video-add-components/video-upload.component.ts
@@ -277,8 +277,6 @@ export class VideoUploadComponent extends VideoSend implements OnInit, OnDestroy
277 private uploadFile (file: File, previewfile?: File) { 277 private uploadFile (file: File, previewfile?: File) {
278 const metadata = { 278 const metadata = {
279 waitTranscoding: true, 279 waitTranscoding: true,
280 commentsEnabled: true,
281 downloadEnabled: true,
282 channelId: this.firstStepChannelId, 280 channelId: this.firstStepChannelId,
283 nsfw: this.serverConfig.instance.isNSFW, 281 nsfw: this.serverConfig.instance.isNSFW,
284 privacy: this.highestPrivacy.toString(), 282 privacy: this.highestPrivacy.toString(),
diff --git a/client/src/app/+videos/+video-watch/shared/action-buttons/action-buttons.component.html b/client/src/app/+videos/+video-watch/shared/action-buttons/action-buttons.component.html
index 8fb244cc4..f23efca98 100644
--- a/client/src/app/+videos/+video-watch/shared/action-buttons/action-buttons.component.html
+++ b/client/src/app/+videos/+video-watch/shared/action-buttons/action-buttons.component.html
@@ -36,7 +36,7 @@
36 36
37 <ng-container *ngIf="!isUserLoggedIn && !video.isLive"> 37 <ng-container *ngIf="!isUserLoggedIn && !video.isLive">
38 <button 38 <button
39 *ngIf="isVideoDownloadable()" class="action-button action-button-save" 39 *ngIf="isVideoDownloadable()" class="action-button action-button-download"
40 (click)="showDownloadModal()" (keydown.enter)="showDownloadModal()" 40 (click)="showDownloadModal()" (keydown.enter)="showDownloadModal()"
41 > 41 >
42 <my-global-icon iconName="download" aria-hidden="true"></my-global-icon> 42 <my-global-icon iconName="download" aria-hidden="true"></my-global-icon>
diff --git a/client/src/app/+videos/+video-watch/shared/action-buttons/action-buttons.component.scss b/client/src/app/+videos/+video-watch/shared/action-buttons/action-buttons.component.scss
index 967d515e6..fdf4e3edb 100644
--- a/client/src/app/+videos/+video-watch/shared/action-buttons/action-buttons.component.scss
+++ b/client/src/app/+videos/+video-watch/shared/action-buttons/action-buttons.component.scss
@@ -49,7 +49,8 @@
49 } 49 }
50 } 50 }
51 51
52 &.action-button-save { 52 &.action-button-save,
53 &.action-button-download {
53 my-global-icon { 54 my-global-icon {
54 top: 0 !important; 55 top: 0 !important;
55 right: -1px; 56 right: -1px;
diff --git a/client/src/app/+videos/+video-watch/shared/metadata/video-attributes.component.html b/client/src/app/+videos/+video-watch/shared/metadata/video-attributes.component.html
index d65c9356a..10ff46595 100644
--- a/client/src/app/+videos/+video-watch/shared/metadata/video-attributes.component.html
+++ b/client/src/app/+videos/+video-watch/shared/metadata/video-attributes.component.html
@@ -1,9 +1,9 @@
1<div class="attribute"> 1<div class="attribute attribute-privacy">
2 <span i18n class="attribute-label">Privacy</span> 2 <span i18n class="attribute-label">Privacy</span>
3 <span class="attribute-value">{{ video.privacy.label }}</span> 3 <span class="attribute-value">{{ video.privacy.label }}</span>
4</div> 4</div>
5 5
6<div *ngIf="video.isLocal === false" class="attribute"> 6<div *ngIf="video.isLocal === false" class="attribute attribute-origin">
7 <span i18n class="attribute-label">Origin</span> 7 <span i18n class="attribute-label">Origin</span>
8 <a 8 <a
9 class="attribute-value" target="_blank" rel="noopener noreferrer" 9 class="attribute-value" target="_blank" rel="noopener noreferrer"
@@ -16,12 +16,12 @@
16 ></a> 16 ></a>
17</div> 17</div>
18 18
19<div *ngIf="!!video.originallyPublishedAt" class="attribute"> 19<div *ngIf="!!video.originallyPublishedAt" class="attribute attribute-originally-published-at">
20 <span i18n class="attribute-label">Originally published</span> 20 <span i18n class="attribute-label">Originally published</span>
21 <span class="attribute-value">{{ video.originallyPublishedAt | date: 'dd MMMM yyyy' }}</span> 21 <span class="attribute-value">{{ video.originallyPublishedAt | date: 'dd MMMM yyyy' }}</span>
22</div> 22</div>
23 23
24<div class="attribute"> 24<div class="attribute attribute-category">
25 <span i18n class="attribute-label">Category</span> 25 <span i18n class="attribute-label">Category</span>
26 <span *ngIf="!video.category.id" class="attribute-value">{{ video.category.label }}</span> 26 <span *ngIf="!video.category.id" class="attribute-value">{{ video.category.label }}</span>
27 <a 27 <a
@@ -30,7 +30,7 @@
30 >{{ video.category.label }}</a> 30 >{{ video.category.label }}</a>
31</div> 31</div>
32 32
33<div class="attribute"> 33<div class="attribute attribute-licence">
34 <span i18n class="attribute-label">Licence</span> 34 <span i18n class="attribute-label">Licence</span>
35 <span *ngIf="!video.licence.id" class="attribute-value">{{ video.licence.label }}</span> 35 <span *ngIf="!video.licence.id" class="attribute-value">{{ video.licence.label }}</span>
36 <a 36 <a
@@ -39,7 +39,7 @@
39 >{{ video.licence.label }}</a> 39 >{{ video.licence.label }}</a>
40</div> 40</div>
41 41
42<div class="attribute"> 42<div class="attribute attribute-language">
43 <span i18n class="attribute-label">Language</span> 43 <span i18n class="attribute-label">Language</span>
44 <span *ngIf="!video.language.id" class="attribute-value">{{ video.language.label }}</span> 44 <span *ngIf="!video.language.id" class="attribute-value">{{ video.language.label }}</span>
45 <a 45 <a
@@ -56,7 +56,7 @@
56 >{{ tag }}</a> 56 >{{ tag }}</a>
57</div> 57</div>
58 58
59<div class="attribute" *ngIf="!video.isLive"> 59<div class="attribute attribute-duration" *ngIf="!video.isLive">
60 <span i18n class="attribute-label">Duration</span> 60 <span i18n class="attribute-label">Duration</span>
61 <span class="attribute-value">{{ video.duration | myDurationFormatter }}</span> 61 <span class="attribute-value">{{ video.duration | myDurationFormatter }}</span>
62</div> 62</div>