From 9c5be2ea4fc2c3951d6ea60108907d7b588782ba Mon Sep 17 00:00:00 2001
From: Christian Heller <c.heller@plomlompom.de>
Date: Thu, 28 Nov 2024 15:52:54 +0100
Subject: [PATCH] Add playlist jumping.

---
 src/templates/playlist.tmpl | 26 ++++++++++++++++++--------
 src/ytplom/misc.py          | 33 +++++++++++++++++++++++----------
 2 files changed, 41 insertions(+), 18 deletions(-)

diff --git a/src/templates/playlist.tmpl b/src/templates/playlist.tmpl
index b25d131..977c65d 100644
--- a/src/templates/playlist.tmpl
+++ b/src/templates/playlist.tmpl
@@ -29,8 +29,8 @@ window.onload = keep_updated;
 
 
 {% block css %}
-#status { text-align: center; font-weight: bold; }
 table { width: 100%; }
+#status { text-align: center; font-weight: bold; }
 th { text-align: center; }
 td { vertical-align: top; }
 td.history { width: 50%; }
@@ -49,21 +49,31 @@ td.history { width: 50%; }
 <input type="submit" name="next" value="next">
 <input type="submit" name="stop" value="{% if running %}stop{% else %}start{% endif %}">
 <input type="submit" name="reload" value="reload">
-</form>
 </td></tr>
 <tr><th>past</th><th>future</th></tr>
 <tr>
 <td class="history">
-<ul>{% for file in prev_files %}
-<li><a href="/{{page_names.file}}/{{file.rel_path_b64}}">{{ file.basename }}</a>
-{% endfor %}</ul>
+<table>
+{% for idx, file in prev_files_w_idx %}
+<tr>
+<td><input type="submit" name="jump_{{idx}}" value=">" /></td>
+<td><a href="/{{page_names.file}}/{{file.rel_path_b64}}">{{ file.basename }}</a></td>
+</tr>
+{% endfor %}
+</table>
 </td>
 <td class="history">
-<ul>{% for file in next_files %}
-<li><a href="/{{page_names.file}}/{{file.rel_path_b64}}">{{ file.basename }}</a>
-{% endfor %}</ul>
+<table>
+{% for idx, file in next_files_w_idx %}
+<tr>
+<td><input type="submit" name="jump_{{idx}}" value=">" /></td>
+<td><a href="/{{page_names.file}}/{{file.rel_path_b64}}">{{ file.basename }}</a></td>
+</tr>
+{% endfor %}
+</table>
 </td>
 </tr>
+</form>
 </table>
 {% endblock %}
 
diff --git a/src/ytplom/misc.py b/src/ytplom/misc.py
index e5f3c6f..339c97c 100644
--- a/src/ytplom/misc.py
+++ b/src/ytplom/misc.py
@@ -47,11 +47,12 @@ PlayerUpdateId = NewType('PlayerUpdateId', str)
 B64Str = NewType('B64Str', str)
 PageNames: TypeAlias = dict[str, PathStr]
 DownloadsIndex: TypeAlias = dict[YoutubeId, PathStr]
+FilesWithIndex: TypeAlias = list[tuple[int, 'VideoFile']]
 TemplateContext: TypeAlias = dict[
         str,
         None | bool
-        | PageNames | PathStr | PlayerUpdateId | QueryText | QuotaCost
-        | 'VideoFile' | YoutubeId | 'YoutubeVideo'
+        | FilesWithIndex | PageNames | PathStr | PlayerUpdateId | QueryText
+        | QuotaCost | 'VideoFile' | YoutubeId | 'YoutubeVideo'
         | list[FlagName] | list['VideoFile'] | list['YoutubeVideo']
         | list['YoutubeQuery']
 ]
@@ -491,8 +492,8 @@ class Player:
 
     @staticmethod
     def _if_mpv_available(f) -> Callable:
-        def wrapper(self):
-            return f(self) if self._mpv else None
+        def wrapper(self, *args, **kwargs):
+            return f(self, *args, **kwargs) if self._mpv else None
         return wrapper
 
     def _signal_update(self) -> None:
@@ -534,14 +535,18 @@ class Player:
         return self._files[self._idx]
 
     @property
-    def prev_files(self) -> list[VideoFile]:
+    def _files_w_idx(self) -> FilesWithIndex:
+        return list(enumerate(self._files))
+
+    @property
+    def prev_files_w_idx(self) -> FilesWithIndex:
         """List 'past' files of playlist."""
-        return list(reversed(self._files[:self._idx]))
+        return list(reversed(self._files_w_idx[:self._idx]))
 
     @property
-    def next_files(self) -> list[VideoFile]:
+    def next_files_w_idx(self) -> FilesWithIndex:
         """List 'coming' files of playlist."""
-        return self._files[self._idx + 1:]
+        return self._files_w_idx[self._idx + 1:]
 
     @property
     def is_running(self) -> bool:
@@ -590,6 +595,12 @@ class Player:
         else:
             self._mpv.command('playlist-play-index', max_idx)
 
+    @_if_mpv_available
+    def jump_to(self, target_idx: int) -> None:
+        """Move player to target_idx position in playlist."""
+        assert self._mpv is not None
+        self._mpv.command('playlist-play_index', target_idx)
+
     def reload(self) -> None:
         """Close MPV, re-read (and re-shuffle) filenames, then re-start MPV."""
         self._kill_mpv()
@@ -732,6 +743,8 @@ class TaskHandler(BaseHTTPRequestHandler):
             self.server.player.toggle_run()
         elif 'reload' == command:
             self.server.player.reload()
+        elif command.startswith('jump_'):
+            self.server.player.jump_to(int(command.split('_')[1]))
         sleep(0.5)  # avoid redir happening before current_file update
         self._redirect(PathStr('/'))
 
@@ -944,5 +957,5 @@ class TaskHandler(BaseHTTPRequestHandler):
                  'running': self.server.player.is_running,
                  'paused': self.server.player.is_paused,
                  'current_video': self.server.player.current_file,
-                 'prev_files': self.server.player.prev_files,
-                 'next_files': self.server.player.next_files})
+                 'prev_files_w_idx': self.server.player.prev_files_w_idx,
+                 'next_files_w_idx': self.server.player.next_files_w_idx})
-- 
2.30.2