From 88a7f93f8e5666f44121a2e3cf9d33d74c472aa7 Mon Sep 17 00:00:00 2001
From: Rigel Kent <sendmemail@rigelk.eu>
Date: Wed, 11 Dec 2019 22:13:20 +0100
Subject: add loop setting for playlists, and use sessionStorage

---
 .../src/app/+admin/system/jobs/jobs.component.ts   |  2 +-
 client/src/app/core/auth/auth-user.model.ts        |  2 +-
 client/src/app/core/auth/auth.service.ts           |  2 +-
 client/src/app/core/server/server.service.ts       |  2 +-
 client/src/app/core/theme/theme.service.ts         |  2 +-
 .../src/app/shared/images/global-icon.component.ts |  1 +
 .../src/app/shared/misc/peertube-local-storage.ts  | 70 --------------------
 client/src/app/shared/misc/peertube-web-storage.ts | 75 ++++++++++++++++++++++
 client/src/app/shared/rest/rest-table.ts           |  2 +-
 .../video-watch-playlist.component.html            |  9 +++
 .../video-watch-playlist.component.scss            |  4 ++
 .../+video-watch/video-watch-playlist.component.ts | 39 +++++++++--
 .../videos/+video-watch/video-watch.component.ts   |  9 ++-
 .../recommended-videos.component.ts                |  8 +--
 client/src/assets/images/global/repeat.svg         |  1 +
 15 files changed, 136 insertions(+), 92 deletions(-)
 delete mode 100644 client/src/app/shared/misc/peertube-local-storage.ts
 create mode 100644 client/src/app/shared/misc/peertube-web-storage.ts
 create mode 100644 client/src/assets/images/global/repeat.svg

(limited to 'client/src')

diff --git a/client/src/app/+admin/system/jobs/jobs.component.ts b/client/src/app/+admin/system/jobs/jobs.component.ts
index 95ee17023..c3211d71f 100644
--- a/client/src/app/+admin/system/jobs/jobs.component.ts
+++ b/client/src/app/+admin/system/jobs/jobs.component.ts
@@ -1,5 +1,5 @@
 import { Component, OnInit } from '@angular/core'
-import { peertubeLocalStorage } from '@app/shared/misc/peertube-local-storage'
+import { peertubeLocalStorage } from '@app/shared/misc/peertube-web-storage'
 import { Notifier } from '@app/core'
 import { SortMeta } from 'primeng/api'
 import { Job, JobType } from '../../../../../../shared/index'
diff --git a/client/src/app/core/auth/auth-user.model.ts b/client/src/app/core/auth/auth-user.model.ts
index 334ede0cd..d371a923f 100644
--- a/client/src/app/core/auth/auth-user.model.ts
+++ b/client/src/app/core/auth/auth-user.model.ts
@@ -1,4 +1,4 @@
-import { peertubeLocalStorage } from '@app/shared/misc/peertube-local-storage'
+import { peertubeLocalStorage } from '@app/shared/misc/peertube-web-storage'
 import { UserRight } from '../../../../../shared/models/users/user-right.enum'
 import { User as ServerUserModel } from '../../../../../shared/models/users/user.model'
 // Do not use the barrel (dependency loop)
diff --git a/client/src/app/core/auth/auth.service.ts b/client/src/app/core/auth/auth.service.ts
index eaa822e0f..d601cadf5 100644
--- a/client/src/app/core/auth/auth.service.ts
+++ b/client/src/app/core/auth/auth.service.ts
@@ -12,7 +12,7 @@ import { RestExtractor } from '../../shared/rest/rest-extractor.service'
 import { AuthStatus } from './auth-status.model'
 import { AuthUser } from './auth-user.model'
 import { objectToUrlEncoded } from '@app/shared/misc/utils'
-import { peertubeLocalStorage } from '@app/shared/misc/peertube-local-storage'
+import { peertubeLocalStorage } from '@app/shared/misc/peertube-web-storage'
 import { I18n } from '@ngx-translate/i18n-polyfill'
 import { Hotkey, HotkeysService } from 'angular2-hotkeys'
 
diff --git a/client/src/app/core/server/server.service.ts b/client/src/app/core/server/server.service.ts
index 8e76bebb1..cf9c411a4 100644
--- a/client/src/app/core/server/server.service.ts
+++ b/client/src/app/core/server/server.service.ts
@@ -1,7 +1,7 @@
 import { map, shareReplay, switchMap, tap } from 'rxjs/operators'
 import { HttpClient } from '@angular/common/http'
 import { Inject, Injectable, LOCALE_ID } from '@angular/core'
-import { peertubeLocalStorage } from '@app/shared/misc/peertube-local-storage'
+import { peertubeLocalStorage } from '@app/shared/misc/peertube-web-storage'
 import { Observable, of, ReplaySubject } from 'rxjs'
 import { getCompleteLocale, ServerConfig } from '../../../../../shared'
 import { environment } from '../../../environments/environment'
diff --git a/client/src/app/core/theme/theme.service.ts b/client/src/app/core/theme/theme.service.ts
index b312e8f7a..9be8e7a2d 100644
--- a/client/src/app/core/theme/theme.service.ts
+++ b/client/src/app/core/theme/theme.service.ts
@@ -4,7 +4,7 @@ import { ServerService } from '@app/core/server'
 import { environment } from '../../../environments/environment'
 import { PluginService } from '@app/core/plugins/plugin.service'
 import { ServerConfigTheme } from '@shared/models'
-import { peertubeLocalStorage } from '@app/shared/misc/peertube-local-storage'
+import { peertubeLocalStorage } from '@app/shared/misc/peertube-web-storage'
 
 @Injectable()
 export class ThemeService {
diff --git a/client/src/app/shared/images/global-icon.component.ts b/client/src/app/shared/images/global-icon.component.ts
index eb723db94..31cfe2666 100644
--- a/client/src/app/shared/images/global-icon.component.ts
+++ b/client/src/app/shared/images/global-icon.component.ts
@@ -26,6 +26,7 @@ const icons = {
   'cross': require('!!raw-loader?!../../../assets/images/global/cross.svg'),
   'validate': require('!!raw-loader?!../../../assets/images/global/validate.svg'),
   'tick': require('!!raw-loader?!../../../assets/images/global/tick.svg'),
+  'repeat': require('!!raw-loader?!../../../assets/images/global/repeat.svg'),
   'dislike': require('!!raw-loader?!../../../assets/images/video/dislike.svg'),
   'support': require('!!raw-loader?!../../../assets/images/video/support.svg'),
   'like': require('!!raw-loader?!../../../assets/images/video/like.svg'),
diff --git a/client/src/app/shared/misc/peertube-local-storage.ts b/client/src/app/shared/misc/peertube-local-storage.ts
deleted file mode 100644
index fb5c45acf..000000000
--- a/client/src/app/shared/misc/peertube-local-storage.ts
+++ /dev/null
@@ -1,70 +0,0 @@
-// Thanks: https://github.com/capaj/localstorage-polyfill
-
-const valuesMap = new Map()
-
-class MemoryStorage {
-  [key: string]: any
-  [index: number]: string
-
-  getItem (key: any) {
-    const stringKey = String(key)
-    if (valuesMap.has(key)) {
-      return String(valuesMap.get(stringKey))
-    }
-
-    return null
-  }
-
-  setItem (key: any, val: any) {
-    valuesMap.set(String(key), String(val))
-  }
-
-  removeItem (key: any) {
-    valuesMap.delete(key)
-  }
-
-  clear () {
-    valuesMap.clear()
-  }
-
-  key (i: any) {
-    if (arguments.length === 0) {
-      throw new TypeError('Failed to execute "key" on "Storage": 1 argument required, but only 0 present.')
-    }
-
-    const arr = Array.from(valuesMap.keys())
-    return arr[i]
-  }
-
-  get length () {
-    return valuesMap.size
-  }
-}
-
-let peertubeLocalStorage: Storage
-try {
-  peertubeLocalStorage = localStorage
-} catch (err) {
-  const instance = new MemoryStorage()
-
-  peertubeLocalStorage = new Proxy(instance, {
-    set: function (obj, prop: string | number, value) {
-      if (MemoryStorage.prototype.hasOwnProperty(prop)) {
-        instance[prop] = value
-      } else {
-        instance.setItem(prop, value)
-      }
-      return true
-    },
-    get: function (target, name: string | number) {
-      if (MemoryStorage.prototype.hasOwnProperty(name)) {
-        return instance[name]
-      }
-      if (valuesMap.has(name)) {
-        return instance.getItem(name)
-      }
-    }
-  })
-}
-
-export { peertubeLocalStorage }
diff --git a/client/src/app/shared/misc/peertube-web-storage.ts b/client/src/app/shared/misc/peertube-web-storage.ts
new file mode 100644
index 000000000..fff209678
--- /dev/null
+++ b/client/src/app/shared/misc/peertube-web-storage.ts
@@ -0,0 +1,75 @@
+// Thanks: https://github.com/capaj/localstorage-polyfill
+
+const valuesMap = new Map()
+
+class MemoryStorage {
+  [key: string]: any
+  [index: number]: string
+
+  getItem (key: any) {
+    const stringKey = String(key)
+    if (valuesMap.has(key)) {
+      return String(valuesMap.get(stringKey))
+    }
+
+    return null
+  }
+
+  setItem (key: any, val: any) {
+    valuesMap.set(String(key), String(val))
+  }
+
+  removeItem (key: any) {
+    valuesMap.delete(key)
+  }
+
+  clear () {
+    valuesMap.clear()
+  }
+
+  key (i: any) {
+    if (arguments.length === 0) {
+      throw new TypeError('Failed to execute "key" on "Storage": 1 argument required, but only 0 present.')
+    }
+
+    const arr = Array.from(valuesMap.keys())
+    return arr[i]
+  }
+
+  get length () {
+    return valuesMap.size
+  }
+}
+
+let peertubeLocalStorage: Storage
+let peertubeSessionStorage: Storage
+try {
+  peertubeLocalStorage = localStorage
+  peertubeSessionStorage = sessionStorage
+} catch (err) {
+  const instance = new MemoryStorage()
+
+  peertubeLocalStorage = sessionStorage = new Proxy(instance, {
+    set: function (obj, prop: string | number, value) {
+      if (MemoryStorage.prototype.hasOwnProperty(prop)) {
+        instance[prop] = value
+      } else {
+        instance.setItem(prop, value)
+      }
+      return true
+    },
+    get: function (target, name: string | number) {
+      if (MemoryStorage.prototype.hasOwnProperty(name)) {
+        return instance[name]
+      }
+      if (valuesMap.has(name)) {
+        return instance.getItem(name)
+      }
+    }
+  })
+}
+
+export {
+  peertubeLocalStorage,
+  peertubeSessionStorage
+}
diff --git a/client/src/app/shared/rest/rest-table.ts b/client/src/app/shared/rest/rest-table.ts
index 884588207..c180346af 100644
--- a/client/src/app/shared/rest/rest-table.ts
+++ b/client/src/app/shared/rest/rest-table.ts
@@ -1,4 +1,4 @@
-import { peertubeLocalStorage } from '@app/shared/misc/peertube-local-storage'
+import { peertubeLocalStorage } from '@app/shared/misc/peertube-web-storage'
 import { LazyLoadEvent } from 'primeng/components/common/lazyloadevent'
 import { SortMeta } from 'primeng/components/common/sortmeta'
 import { RestPagination } from './rest-pagination'
diff --git a/client/src/app/videos/+video-watch/video-watch-playlist.component.html b/client/src/app/videos/+video-watch/video-watch-playlist.component.html
index c07ba1ed6..a04081d35 100644
--- a/client/src/app/videos/+video-watch/video-watch-playlist.component.html
+++ b/client/src/app/videos/+video-watch/video-watch-playlist.component.html
@@ -24,6 +24,15 @@
         placement="bottom auto"
         container="body"
       ></my-global-icon>
+
+      <my-global-icon
+        iconName="repeat"
+        [class.active]="loopPlaylist"
+        (click)="switchLoopPlaylist()"
+        [ngbTooltip]="'Loop playlist videos'"
+        placement="bottom auto"
+        container="body"
+      ></my-global-icon>
     </div>
   </div>
 
diff --git a/client/src/app/videos/+video-watch/video-watch-playlist.component.scss b/client/src/app/videos/+video-watch/video-watch-playlist.component.scss
index ba8d1c3e1..0dd318cb0 100644
--- a/client/src/app/videos/+video-watch/video-watch-playlist.component.scss
+++ b/client/src/app/videos/+video-watch/video-watch-playlist.component.scss
@@ -39,6 +39,10 @@
       display: flex;
       margin: 10px 0;
 
+      my-global-icon:not(:last-child) {
+        margin-right: .5rem;
+      }
+
       my-global-icon {
         &:not(.active) {
           opacity: .5
diff --git a/client/src/app/videos/+video-watch/video-watch-playlist.component.ts b/client/src/app/videos/+video-watch/video-watch-playlist.component.ts
index ed2aeda6e..c6b04fd4b 100644
--- a/client/src/app/videos/+video-watch/video-watch-playlist.component.ts
+++ b/client/src/app/videos/+video-watch/video-watch-playlist.component.ts
@@ -3,11 +3,11 @@ import { VideoPlaylist } from '@app/shared/video-playlist/video-playlist.model'
 import { ComponentPagination } from '@app/shared/rest/component-pagination.model'
 import { VideoDetails, VideoPlaylistPrivacy } from '@shared/models'
 import { Router } from '@angular/router'
-import { User, UserService } from '@app/shared'
+import { UserService } from '@app/shared'
 import { AuthService, Notifier } from '@app/core'
 import { VideoPlaylistService } from '@app/shared/video-playlist/video-playlist.service'
 import { VideoPlaylistElement } from '@app/shared/video-playlist/video-playlist-element.model'
-import { peertubeLocalStorage } from '@app/shared/misc/peertube-local-storage'
+import { peertubeLocalStorage, peertubeSessionStorage } from '@app/shared/misc/peertube-web-storage'
 import { I18n } from '@ngx-translate/i18n-polyfill'
 
 @Component({
@@ -17,6 +17,7 @@ import { I18n } from '@ngx-translate/i18n-polyfill'
 })
 export class VideoWatchPlaylistComponent {
   static LOCAL_STORAGE_AUTO_PLAY_NEXT_VIDEO_PLAYLIST = 'auto_play_video_playlist'
+  static SESSION_STORAGE_AUTO_PLAY_NEXT_VIDEO_PLAYLIST = 'loop_playlist'
 
   @Input() video: VideoDetails
   @Input() playlist: VideoPlaylist
@@ -30,6 +31,8 @@ export class VideoWatchPlaylistComponent {
 
   autoPlayNextVideoPlaylist: boolean
   autoPlayNextVideoPlaylistSwitchText = ''
+  loopPlaylist: boolean
+  loopPlaylistSwitchText = ''
   noPlaylistVideos = false
   currentPlaylistPosition = 1
 
@@ -45,6 +48,9 @@ export class VideoWatchPlaylistComponent {
       ? this.auth.getUser().autoPlayNextVideoPlaylist
       : peertubeLocalStorage.getItem(VideoWatchPlaylistComponent.LOCAL_STORAGE_AUTO_PLAY_NEXT_VIDEO_PLAYLIST) !== 'false'
     this.setAutoPlayNextVideoPlaylistSwitchText()
+
+    this.loopPlaylist = peertubeSessionStorage.getItem(VideoWatchPlaylistComponent.SESSION_STORAGE_AUTO_PLAY_NEXT_VIDEO_PLAYLIST) === 'true'
+    this.setLoopPlaylistSwitchText()
   }
 
   onPlaylistVideosNearOfBottom () {
@@ -121,9 +127,9 @@ export class VideoWatchPlaylistComponent {
     this.onPlaylistVideosNearOfBottom()
   }
 
-  navigateToNextPlaylistVideo () {
+  navigateToNextPlaylistVideo (_next: VideoPlaylistElement = null) {
     if (this.currentPlaylistPosition < this.playlistPagination.totalItems) {
-      const next = this.playlistElements.find(e => e.position === this.currentPlaylistPosition + 1)
+      const next = _next || this.playlistElements.find(e => e.position === this.currentPlaylistPosition + 1)
 
       if (!next || !next.video) {
         this.currentPlaylistPosition++
@@ -134,6 +140,9 @@ export class VideoWatchPlaylistComponent {
       const start = next.startTimestamp
       const stop = next.stopTimestamp
       this.router.navigate([],{ queryParams: { videoId: next.video.uuid, start, stop } })
+    } else if (this.loopPlaylist) {
+      this.currentPlaylistPosition = 0
+      this.navigateToNextPlaylistVideo(this.playlistElements.find(e => e.position === this.currentPlaylistPosition))
     }
   }
 
@@ -160,9 +169,25 @@ export class VideoWatchPlaylistComponent {
     }
   }
 
+  switchLoopPlaylist () {
+    this.loopPlaylist = !this.loopPlaylist
+    this.setLoopPlaylistSwitchText()
+
+    peertubeSessionStorage.setItem(
+      VideoWatchPlaylistComponent.SESSION_STORAGE_AUTO_PLAY_NEXT_VIDEO_PLAYLIST,
+      this.loopPlaylist.toString()
+    )
+  }
+
   private setAutoPlayNextVideoPlaylistSwitchText () {
-    this.autoPlayNextVideoPlaylistSwitchText = this.i18n('{{verb}} autoplay for playlists', {
-      verb: this.autoPlayNextVideoPlaylist ? this.i18n('Disable') : this.i18n('Enable')
-    })
+    this.autoPlayNextVideoPlaylistSwitchText = this.autoPlayNextVideoPlaylist
+      ? this.i18n('Stop autoplaying next video')
+      : this.i18n('Autoplay next video')
+  }
+
+  private setLoopPlaylistSwitchText () {
+    this.loopPlaylistSwitchText = this.loopPlaylist
+      ? this.i18n('Stop looping playlist videos')
+      : this.i18n('Loop playlist videos')
   }
 }
diff --git a/client/src/app/videos/+video-watch/video-watch.component.ts b/client/src/app/videos/+video-watch/video-watch.component.ts
index 92c1c50c0..aaaa63d4d 100644
--- a/client/src/app/videos/+video-watch/video-watch.component.ts
+++ b/client/src/app/videos/+video-watch/video-watch.component.ts
@@ -2,7 +2,7 @@ import { catchError } from 'rxjs/operators'
 import { ChangeDetectorRef, Component, ElementRef, Inject, LOCALE_ID, NgZone, OnDestroy, OnInit, ViewChild } from '@angular/core'
 import { ActivatedRoute, Router } from '@angular/router'
 import { RedirectService } from '@app/core/routing/redirect.service'
-import { peertubeLocalStorage } from '@app/shared/misc/peertube-local-storage'
+import { peertubeLocalStorage, peertubeSessionStorage } from '@app/shared/misc/peertube-web-storage'
 import { VideoSupportComponent } from '@app/videos/+video-watch/modal/video-support.component'
 import { MetaService } from '@ngx-meta/core'
 import { AuthUser, Notifier, ServerService } from '@app/core'
@@ -46,7 +46,6 @@ import { RecommendedVideosComponent } from '../recommendations/recommended-video
 })
 export class VideoWatchComponent implements OnInit, OnDestroy {
   private static LOCAL_STORAGE_PRIVACY_CONCERN_KEY = 'video-watch-privacy-concern'
-  private static LOCAL_STORAGE_AUTO_PLAY_NEXT_VIDEO = 'auto_play_next_video'
 
   @ViewChild('videoWatchPlaylist', { static: true }) videoWatchPlaylist: VideoWatchPlaylistComponent
   @ViewChild('videoShareModal', { static: false }) videoShareModal: VideoShareComponent
@@ -439,11 +438,11 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
         if (this.playlist) {
           if (
             this.user && this.user.autoPlayNextVideoPlaylist ||
-            peertubeLocalStorage.getItem(VideoWatchPlaylistComponent.LOCAL_STORAGE_AUTO_PLAY_NEXT_VIDEO_PLAYLIST) === 'true'
+            peertubeSessionStorage.getItem(VideoWatchPlaylistComponent.SESSION_STORAGE_AUTO_PLAY_NEXT_VIDEO_PLAYLIST) === 'true'
           ) this.zone.run(() => this.videoWatchPlaylist.navigateToNextPlaylistVideo())
         } else if (
           this.user && this.user.autoPlayNextVideo ||
-          peertubeLocalStorage.getItem(RecommendedVideosComponent.LOCAL_STORAGE_AUTO_PLAY_NEXT_VIDEO) === 'true'
+          peertubeSessionStorage.getItem(RecommendedVideosComponent.SESSION_STORAGE_AUTO_PLAY_NEXT_VIDEO) === 'true'
         ) {
           this.zone.run(() => this.autoplayNext())
         }
@@ -453,7 +452,7 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
         if (this.playlist) {
           if (
             this.user && this.user.autoPlayNextVideoPlaylist ||
-            peertubeLocalStorage.getItem(VideoWatchPlaylistComponent.LOCAL_STORAGE_AUTO_PLAY_NEXT_VIDEO_PLAYLIST) === 'true'
+            peertubeSessionStorage.getItem(VideoWatchPlaylistComponent.SESSION_STORAGE_AUTO_PLAY_NEXT_VIDEO_PLAYLIST) === 'true'
           ) this.zone.run(() => this.videoWatchPlaylist.navigateToNextPlaylistVideo())
         }
       })
diff --git a/client/src/app/videos/recommendations/recommended-videos.component.ts b/client/src/app/videos/recommendations/recommended-videos.component.ts
index 771ae54a2..fdcfb28e3 100644
--- a/client/src/app/videos/recommendations/recommended-videos.component.ts
+++ b/client/src/app/videos/recommendations/recommended-videos.component.ts
@@ -7,7 +7,7 @@ import { RecommendedVideosStore } from '@app/videos/recommendations/recommended-
 import { User } from '@app/shared'
 import { AuthService, Notifier } from '@app/core'
 import { UserService } from '@app/shared/users/user.service'
-import { peertubeLocalStorage } from '@app/shared/misc/peertube-local-storage'
+import { peertubeSessionStorage } from '@app/shared/misc/peertube-web-storage'
 
 @Component({
   selector: 'my-recommended-videos',
@@ -15,7 +15,7 @@ import { peertubeLocalStorage } from '@app/shared/misc/peertube-local-storage'
   styleUrls: [ './recommended-videos.component.scss' ]
 })
 export class RecommendedVideosComponent implements OnChanges {
-  static LOCAL_STORAGE_AUTO_PLAY_NEXT_VIDEO = 'auto_play_next_video'
+  static SESSION_STORAGE_AUTO_PLAY_NEXT_VIDEO = 'auto_play_next_video'
 
   @Input() inputRecommendation: RecommendationInfo
   @Input() user: User
@@ -39,7 +39,7 @@ export class RecommendedVideosComponent implements OnChanges {
 
     this.autoPlayNextVideo = this.authService.isLoggedIn()
       ? this.authService.getUser().autoPlayNextVideo
-      : peertubeLocalStorage.getItem(RecommendedVideosComponent.LOCAL_STORAGE_AUTO_PLAY_NEXT_VIDEO) === 'true' || false
+      : peertubeSessionStorage.getItem(RecommendedVideosComponent.SESSION_STORAGE_AUTO_PLAY_NEXT_VIDEO) === 'true' || false
   }
 
   public ngOnChanges (): void {
@@ -53,7 +53,7 @@ export class RecommendedVideosComponent implements OnChanges {
   }
 
   switchAutoPlayNextVideo () {
-    peertubeLocalStorage.setItem(RecommendedVideosComponent.LOCAL_STORAGE_AUTO_PLAY_NEXT_VIDEO, this.autoPlayNextVideo.toString())
+    peertubeSessionStorage.setItem(RecommendedVideosComponent.SESSION_STORAGE_AUTO_PLAY_NEXT_VIDEO, this.autoPlayNextVideo.toString())
 
     if (this.authService.isLoggedIn()) {
       const details = {
diff --git a/client/src/assets/images/global/repeat.svg b/client/src/assets/images/global/repeat.svg
new file mode 100644
index 000000000..c7657b08e
--- /dev/null
+++ b/client/src/assets/images/global/repeat.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-repeat"><polyline points="17 1 21 5 17 9"></polyline><path d="M3 11V9a4 4 0 0 1 4-4h14"></path><polyline points="7 23 3 19 7 15"></polyline><path d="M21 13v2a4 4 0 0 1-4 4H3"></path></svg>
\ No newline at end of file
-- 
cgit v1.2.3