Bladeren bron

fixed some ui stuff and added more ui stuff

sleepytaco 3 jaren geleden
bovenliggende
commit
e441fcc75d

+ 91 - 3
apps/main/models.py

@@ -1070,6 +1070,12 @@ class PlaylistManager(models.Manager):
         playlist_items = user.playlists.get(playlist_id=from_playlist_id).playlist_items.select_related('video').filter(
             playlist_item_id__in=playlist_item_ids)
 
+        result = {
+            "status": 0,
+            "num_moved_copied": 0,
+            "playlistContainsMaximumNumberOfVideos": False,
+        }
+
         with build('youtube', 'v3', credentials=credentials) as youtube:
             for playlist_id in to_playlist_ids:
                 for playlist_item in playlist_items:
@@ -1097,14 +1103,96 @@ class PlaylistManager(models.Manager):
                         # errors i ran into:
                         # runs into HttpError 400 "Invalid playlist snippet." when the description contains <, >
                         print("ERROR UPDATING PLAYLIST DETAILS", e.status_code, e.error_details)
-                        return [-1, e.status_code]
+                        if e.status_code == 400:
+                            pl_request = youtube.playlistItems().insert(
+                                part="snippet",
+                                body={
+                                    "snippet": {
+                                        "playlistId": playlist_id,
+                                        "resourceId": {
+                                            "kind": "youtube#video",
+                                            "videoId": playlist_item.video.video_id,
+                                        }
+                                    },
+                                }
+                            )
 
-                    print(pl_response)
+                            try:
+                                pl_response = pl_request.execute()
+                            except googleapiclient.errors.HttpError as e:
+                                result['status'] = -1
+                        elif e.status_code == 403:
+                            result["playlistContainsMaximumNumberOfVideos"] = True
+                        else:
+                            result['status'] = -1
+                    result["num_moved_copied"] += 1
 
         if action == "move":  # delete from the current playlist
             self.deletePlaylistItems(user, from_playlist_id, playlist_item_ids)
 
-        return [0]
+        return result
+
+    def addVideosToPlaylist(self, user, playlist_id, video_ids):
+        """
+        Takes in playlist itemids for the videos in a particular playlist
+        """
+        credentials = self.getCredentials(user)
+
+        playlist = user.playlists.get(playlist_id=playlist_id)
+
+        result = {
+            "num_added": 0,
+            "playlistContainsMaximumNumberOfVideos": False,
+        }
+
+        added = 0
+        with build('youtube', 'v3', credentials=credentials) as youtube:
+
+            for video_id in video_ids:
+                pl_request = youtube.playlistItems().insert(
+                    part="snippet",
+                    body={
+                        "snippet": {
+                            "playlistId": playlist_id,
+                            "position": 0,
+                            "resourceId": {
+                                "kind": "youtube#video",
+                                "videoId": video_id,
+                            }
+                        },
+                    }
+                )
+
+                try:
+                    pl_response = pl_request.execute()
+                except googleapiclient.errors.HttpError as e:  # failed to update add video to playlis
+                    print("ERROR ADDDING VIDEOS TO PLAYLIST", e.status_code, e.error_details)
+                    if e.status_code == 400: # manualSortRequired - see errors https://developers.google.com/youtube/v3/docs/playlistItems/insert
+                        pl_request = youtube.playlistItems().insert(
+                            part="snippet",
+                            body={
+                                "snippet": {
+                                    "playlistId": playlist_id,
+                                    "resourceId": {
+                                        "kind": "youtube#video",
+                                        "videoId": video_id,
+                                    }
+                                },
+                            }
+                        )
+                        try:
+                            pl_response = pl_request.execute()
+                        except googleapiclient.errors.HttpError as e:  # failed to update playlist details
+                            pass
+                    elif e.status_code == 403:
+                        result["playlistContainsMaximumNumberOfVideos"] = True
+                    continue
+                added += 1
+        result["num_added"] = added
+        if added > 0:
+            playlist.has_playlist_changed = True
+            playlist.save(update_fields=['has_playlist_changed'])
+        return result
 
 
 class Tag(models.Model):

+ 5 - 3
apps/main/templates/all_playlists.html

@@ -6,6 +6,7 @@
         <div class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 mb-3">
             <h1 class="h2"><span style="border-bottom: 3px #e24949 dashed;">{{ library_type_display|title }}</span> <span class="badge bg-primary rounded-pill">{{ playlists.count }}</span></h1>
 
+
             {% if playlists %}
             <div class="btn-toolbar mb-2 mb-md-0">
                 <!--
@@ -13,7 +14,6 @@
                     <button type="button" class="btn btn-outline-info">Grid</button>
                     <button type="button" class="btn btn-outline-info">List</button>
                 </div>
-                -->
                 <div class="btn-group">
                   <button type="button" class="btn btn-success dropdown-toggle" data-bs-toggle="dropdown" aria-expanded="false">
                     Sort By
@@ -24,7 +24,7 @@
                     <li class="dropdown-item"><a hx-get="{% url 'order_playlists_by' library_type 'recently-accessed' %}" hx-trigger="click" hx-target="#search-results">Recently Accessed</a></li>
                   </ul>
                 </div>
-
+                -->
             </div>
             {% endif %}
 
@@ -38,7 +38,7 @@
                    hx-target="#search-results"
                    hx-indicator=".htmx-indicator"
                     aria-describedby="searchHelp">
-            <div id="searchHelp" class="form-text">For a more extensive playlist search, <a href="{% url 'search' %}?mode=playlists&type={{ library_type }}">click here</a>.</div>
+            <div id="searchHelp" class="form-text">For a more extensive playlist search and filtering, <a href="{% url 'search' %}?mode=playlists&type={{ library_type }}">click here</a>.</div>
 
             <br>
 
@@ -62,6 +62,8 @@
                         You can mark a playlist as plan to watch by heading over to the playlist and marking it from the dropdown.
                     {% elif library_type == "favorites" %}
                         You can mark a playlist as favorite by heading over to the playlist page and pressing the star icon.
+                    {% elif library_type == "yt-mix" %}
+                        No YouTube mixes imported. Head over to Manage to import the ones you like.
                     {% elif library_type == "user-owned" %}
                         {% if user.profile.imported_yt_playlists %}
                             Looks like you have no playlists on YouTube.

+ 19 - 5
apps/main/templates/favorites.html

@@ -5,10 +5,17 @@
     <div class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3 border-bottom">
         <h1 class="h2">Favorite Playlists
             <span class="badge bg-primary rounded-pill">{{ playlists.count|default:"0" }}</span>
-            {% if playlists %}
-                <a href="{% url 'search' %}?mode=playlists&type=favorites" style="text-decoration: none; color: black"><i class="fas fa-search"></i></a>
-            {% endif %}
+
         </h1>
+        {% if playlists %}
+                    <form method="get" action="{% url 'search' %}" >
+                    <input size="50" class="form-control border border-secondary" type="text"
+                   name="query" placeholder="Search your favorite playlists...">
+                    <input type="text" class="visually-hidden" name="mode" value="playlists">
+                    <input type="text" class="visually-hidden" name="type" value="favorites">
+                    <button type="submit" class="visually-hidden"></button>
+                    </form>
+            {% endif %}
     </div>
 
     <div>
@@ -27,10 +34,17 @@
     <div class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3 border-bottom">
         <h1 class="h2">Favorite Videos
             <span class="badge bg-primary rounded-pill">{{ videos.count }}</span>
+
+        </h1>
             {% if videos %}
-                <a href="{% url 'search' %}?mode=videos&type=favorite" style="text-decoration: none; color: black"><i class="fas fa-search"></i></a>
+                    <form method="get" action="{% url 'search' %}" >
+                    <input size="50" class="form-control border border-secondary" type="text"
+                   name="query" placeholder="Search your favorite videos...">
+                    <input type="text" class="visually-hidden" name="mode" value="videos">
+                    <input type="text" class="visually-hidden" name="type" value="favorite">
+                    <button type="submit" class="visually-hidden"></button>
+                    </form>
             {% endif %}
-        </h1>
     </div>
 
     <div>

+ 4 - 4
apps/main/templates/home.html

@@ -48,7 +48,7 @@
             <div class="col-6 mb-4">
                 <div class="card bg-transparent text-dark">
                     <div class="card-body">
-                        <h6 class="d-flex align-items-center mb-3"><span class="text-warning me-2">{{ user.playlists.count }}</span>Playlists Statistics{% if user.playlists.count == 0 %}: You have no playlists in your UnTube!{% endif %}</h6>
+                        <h6 class="d-flex justify-content-center align-items-center mb-3">You have a total of <span class="text-warning ms-1 me-1">{{ user.playlists.count }}</span> Playlists in your UnTube collection</h6>
                         <div class="d-flex align-items-center mb-3">
 
                             <canvas id="overall-playlists-distribution" data-url="{% url 'overall_playlists_distribution' %}">
@@ -62,7 +62,7 @@
             <div class="col-6 mb-4">
                 <div class="card bg-transparent text-dark">
                     <div class="card-body">
-                        <h6 class="d-flex align-items-center mb-3">A total of <span class="text-warning me-1 ms-1" id="num-channels">{{ channels.count|intword|intcomma }} channels</span> and <span class="text-warning ms-1 me-1" id="num-channels"> {{ videos.count|intword|intcomma }} videos</span> found in your UnTube collection </h6>
+                        <h6 class="d-flex justify-content-center align-items-center mb-3">A total of <span class="text-warning me-1 ms-1" id="num-channels">{{ channels.count|intword|intcomma }} channels</span> and <span class="text-warning ms-1 me-1" id="num-channels"> {{ videos.count|intword|intcomma }} videos</span> found in your UnTube collection </h6>
                         {% if channels.count > 100 %}<h6 class="d-flex justify-content-center">(Only top 100 channels shown below)</h6>{% endif %}
                         <div class="d-flex align-items-center mb-3">
 
@@ -169,7 +169,7 @@
                 <div class="card bg-transparent text-dark">
                     <div class="card-body">
 
-                        <h6 class="d-flex align-items-center mb-3"><span class="text-warning me-2">{{ watching.count }}</span>
+                        <h6 class="d-flex justify-content-center align-items-center mb-3"><span class="text-warning me-2">{{ watching.count }}</span>
                             {% if watching.count > 0 %}
                                 Playlist{% if watching.count > 1 %}s{% endif %} Watching: Percent Complete Chart
 
@@ -260,7 +260,7 @@
                     <div class="row flex-row g-3 flex-nowrap">
                         {% for playlist in watching %}
                             <div class="col">
-                                <div class="card overflow-auto" style="background-color: #c2c68f; width: 275px; height: auto">
+                                <div class="card overflow-auto" style="background-color: #9363af; width: 275px; height: auto">
                                     <img  class="bd-placeholder-img card-img-top" src="{{ playlist.thumbnail_url }}" style="max-width:100%; height: 200px;   object-fit: cover;" alt="{{ playlist.name }} thumbnail">
 
                                     <div class="card-body">

+ 2 - 2
apps/main/templates/intercooler/playlist_items.html

@@ -50,7 +50,7 @@
                             {{ playlist_item.video_position|add:"1" }}.
                             <a class="link-dark" href="{% url 'video' playlist_item.video.video_id %}">
                                 {{ playlist_item.video.name|truncatewords:"16" }}
-                            </a> by {{ playlist_item.video.channel_name }} <br>
+                            </a> by <a href="{% url 'search' %}?mode=videos&channel={{ playlist_item.video.channel_name }}" style="text-decoration: none; color: black"><span style="border-bottom: 3px #e35959 dashed;"> {{ playlist_item.video.channel_name }}</span></a> <br>
                             <a style="text-decoration: none" hx-get="{% url 'video_completion_times' playlist_item.video.video_id %}" hx-trigger="click once" hx-target="#{{ playlist_item.playlist_item_id }}-completion-times" data-bs-toggle="collapse" href="#{{ playlist_item.playlist_item_id }}DurationCollapse" role="button" aria-expanded="false" aria-controls="{{ playlist_item.playlist_item_id }}DurationCollapse">
                                     <span class="badge bg-secondary">{{ playlist_item.video.duration }}</span>
                                 </a>
@@ -63,7 +63,7 @@
                             </div>
 
                             {% if playlist_item.video.has_cc %}<span class="badge bg-secondary">CC</span>{% endif %}
-                            {% if playlist_item.video.published_at %}<span class="badge bg-secondary">{{ playlist_item.video.published_at }}</span>{% endif %}
+                            {% if playlist_item.published_at %}<span class="badge bg-secondary">added to playlist on {{ playlist_item.published_at }}</span>{% endif %}
                             <span class="badge bg-info text-black-50"><i class="fas fa-eye"></i> {% if playlist_item.video.view_count == -1 %}HIDDEN{% else %}{{ playlist_item.video.view_count|intword|intcomma }}{% endif %}</span>
                             <span class="badge bg-warning text-black-50"><i class="fas fa-thumbs-up"></i> {% if playlist_item.video.like_count == -1 %}HIDDEN{% else %}{{ playlist_item.video.like_count|intword|intcomma }}{% endif %}</span>
 

+ 12 - 4
apps/main/templates/intercooler/playlists.html

@@ -3,16 +3,16 @@
     {% for playlist in playlists %}
         <div class="col">
 
-            <div class="card overflow-auto" style="background-color: {{ bg_color|default:"#c2c68f" }};">
+            <div class="card overflow-auto" style="background-color: {{ bg_color|default:"#9363af" }};">
                 <img  class="bd-placeholder-img card-img-top" src="{{ playlist.thumbnail_url }}" style="max-width:100%; height: 200px;   object-fit: cover;" alt="{{ playlist.name }} thumbnail">
 
                 <div class="card-body">
-                    <h5 class="card-title"><a href="{% url 'playlist' playlist.playlist_id %}" class="stretched-link" style="text-decoration: none; color: black">{{ playlist.name }}</a></h5>
+                    <h5 class="card-title"><a href="{% url 'playlist' playlist.playlist_id %}" class="stretched-link" style="text-decoration: none; color: white">{{ playlist.name }}</a></h5>
                     <p class="card-text">
                         <span class="badge bg-{% if playlist.get_watch_time_left == "0secs." %}success{% else %}primary{% endif %} text-white">{{ playlist.get_watched_videos_count }}/{{ playlist.get_watchable_videos_count }} viewed</span>
                         {% if playlist.get_watch_time_left != "0secs." %}<span class="badge bg-dark text-white">{{ playlist.get_watch_time_left }} left</span>{% endif %}
                     </p>
-                    <p class="card-text"><small class="text-muted">Last watched {{ playlist.last_watched|naturaltime }}</small></p>
+                    <p class="card-text"><small class="text-black">Last watched {{ playlist.last_watched|naturaltime }}</small></p>
                 </div>
             </div>
         </div>
@@ -26,7 +26,15 @@
                     <img  class="bd-placeholder-img card-img-top" src="{% if playlist.thumbnail_url %}{{ playlist.thumbnail_url }}{% else %}https://i.ytimg.com/vi/9219YrnwDXE/maxresdefault.jpg{% endif %}" style="max-width:100%; height: 200px;   object-fit: cover;" alt="{{ playlist.name }} thumbnail">
                 </a>
                 <div class="card-body">
-                    <h5 class="card-title"><a href="{% url 'playlist' playlist.playlist_id %}" style="text-decoration: none; color: black">{{ playlist.name }}</a></h5>
+                    <h5 class="card-title">
+                        <a href="{% url 'playlist' playlist.playlist_id %}" style="text-decoration: none; color: white">
+                            {% if playlist.user_label %}
+                                <span style="border-bottom: 3px #eeeaea dashed;">{{ playlist.user_label }}</span>
+                            {% else %}
+                            {{ playlist.name }}
+                            {% endif %}
+                        </a>
+                    </h5>
 
                     <p class="card-text text-uppercase">
                         {% if playlist.is_user_owned %}<span class="badge bg-light text-black-50">OWNED</span>{% else %}<span class="badge bg-light text-black-50">IMPORTED</span>{% endif %}

+ 11 - 2
apps/main/templates/intercooler/video_cards.html

@@ -2,14 +2,22 @@
 {% for video in videos %}
     <div class="col">
 
-        <div class="card" style="max-width: 540px; background-color: #1A4464;">
+        <div class="card overflow-auto" style="max-width: 540px; background-color: #1A4464;">
             <div class="row g-0">
                 <div class="col-md-4">
                     <img src="{{ video.thumbnail_url }}" class="img-fluid" style="width: 100%; height: 15vw; object-fit: cover;">
                 </div>
                 <div class="col-md-8">
                     <div class="card-body">
-                        <h5 class="card-title"><a href="{% url 'video' video.video_id %}" style="text-decoration: none; color: white"> {{ video.name|truncatewords:"15" }}</a></h5>
+                        <h5 class="card-title"><a href="{% url 'video' video.video_id %}" style="text-decoration: none; color: white">
+                            {% if video.user_label %}
+                                <span style="border-bottom: 3px #eeeaea dashed;">{{ video.user_label }}</span>
+                            {% else %}
+                            {{ video.name|truncatewords:"15" }}
+                            {% endif %}
+                        </a>
+                        </h5>
+                        {% if not video.is_unavailable_on_yt and not video.was_deleted_on_yt %}
                         <h5 class="card-text">
                             <small>
                                 <span class="badge bg-dark text-white-50">{{ video.duration }}</span>
@@ -20,6 +28,7 @@
 
                             </small>
                         </h5>
+                        {% endif %}
 
                         <span class="card-text d-flex justify-content-start">
                             <a href="https://www.youtube.com/watch?v={{ video.video_id }}" class="btn btn-info me-1" target="_blank" id="share_link" style=""><i class="fas fa-external-link-alt" aria-hidden="true"></i></a>

+ 2 - 2
apps/main/templates/library.html

@@ -64,11 +64,11 @@
         </a>
     </div>
     <div class="col">
-        <a href="#" class="text-decoration-none text-white">
+        <a href="{% url 'library' 'yt-mix' %}" class="text-decoration-none text-white">
         <div class="card h-100" style="background-color: #bd39a7;">
             <div class="card-body">
                 <h5 class="card-title">Your YouTube Mixes</h5>
-                <p class="card-text">YouTube creates nice mixes that relate to songs you're currently jamming to. You can import those YT Mixes by going over to manage playlists. Any mixes you import will all be here.</p>
+                <p class="card-text">YouTube creates nice mixes that relate to songs you're currently jamming to. You can import those YT Mixes by going over to Manage. Any YouTube mixes you import will all be here.</p>
             </div>
         </div>
         </a>

+ 0 - 6
apps/main/templates/manage_playlists_import.html

@@ -13,12 +13,6 @@
                 hx-post="{% url 'manage_save' 'manage_playlists_import_textarea' %}"
                 hx-trigger="keyup changed delay:500ms"
                 hx-indicator="#spinner">{{ manage_playlists_import_textarea }}</textarea>
-        <!--
-        <input class="form-check-input mx-3 big-checkbox" type="checkbox" name="e" id="checkbox"> <br>
-        <input class="form-check-input mx-3 big-checkbox" type="checkbox" name="f" id="checkbox"> <br>
-        <input class="form-check-input mx-3 big-checkbox" type="checkbox" name="g" id="checkbox"> <br>
-        <input class="form-check-input mx-3 big-checkbox" type="checkbox" name="h" id="checkbox"> <br>
-        -->
     </div>
       <br>
       <div class="d-flex justify-content-start">

File diff suppressed because it is too large
+ 565 - 465
apps/main/templates/view_playlist.html


+ 0 - 9
apps/main/templates/view_playlist_settings.html

@@ -128,16 +128,7 @@
                         <input type="text" class="form-control" name="username" id="username" value="{{ playlist.name }}" disabled>
                     </div>
                   </div>
-                    <hr>
 
-                    <div class="row">
-                    <div class="col-sm-3">
-                      <h6 class="mb-0">Playlist User Label</h6>
-                    </div>
-                    <div class="col-sm-9 text-white-50">
-                        <input type="text" class="form-control" name="user_label" id="user_label" placeholder="Enter a personal label you want to identify this playlist with" value="{{ playlist.user_label }}">
-                    </div>
-                  </div>
                       <hr>
                       <div class="row">
                         <div class="col-sm-3">

+ 88 - 124
apps/main/templates/view_video.html

@@ -19,7 +19,7 @@
                     <button type="submit" class="btn btn-primary">Save</button>
 
                 </form>
-                              <button type="button" class="btn-close mt-2 me-2" onclick='document.getElementById("user-label-alert").style.display = "none";' aria-label="Close"></button>
+                <button type="button" class="btn-close mt-2 me-2" onclick='document.getElementById("user-label-alert").style.display = "none";' aria-label="Close"></button>
 
             </div>
             <div class="card text-white bg-dark" style="max-width: 100%;">
@@ -27,8 +27,22 @@
                     <div class="col-md-4 p-3">
                         <img  class="img-fluid rounded-3" src="{{ video.thumbnail_url }}" style="max-width:100%; height: auto;   object-fit: cover;">
                         <span class="d-flex justify-content-center">
-                        <a type="submit" onclick="window.open('{{ video.thumbnail_url }}')" class="btn btn-primary mt-3"><i class="fas fa-image fa-lg me-2"></i> Download Thumbnail</a>
-                            </span>
+                            <a type="submit" onclick="window.open('{{ video.thumbnail_url }}')" class="btn btn-primary mt-3"><i class="fas fa-image fa-lg"></i></a>
+                            <a href="https://www.youtube.com/watch?v={{ video.video_id }}" class="btn btn-info ms-2 mt-3" target="_blank" id="share_link" style=""><i class="fas fa-external-link-alt" aria-hidden="true"></i></a>
+                            <input class="form-control me-1 visually-hidden" id="video-{{ video.video_id }}" value="https://www.youtube.com/watch?v={{ video.video_id }}">
+                            <button class="copy-btn btn btn-success  ms-2 mt-3" data-clipboard-target="#video-{{ video.video_id }}">
+                                <i class="far fa-copy" aria-hidden="true"></i>
+                            </button>
+                            <button class="btn btn-dark mt-3 ms-2" type="button" hx-get="{% url 'mark_video_favorite' video.video_id %}" hx-target="#video-fav">
+                                <div id="video-fav">
+                                    {% if video.is_favorite %}
+                                        <i class="fas fa-heart" style="color: #fafa06"></i>
+                                    {% else %}
+                                        <i class="far fa-heart"></i>
+                                    {% endif %}
+                                </div>
+                            </button>
+                        </span>
                     </div>
                     <div class="col-md-8">
                         <div class="card-body">
@@ -36,34 +50,13 @@
                                 <h2 class="card-title text-white col-10">
                                     <a href="https://www.youtube.com/watch?v={{ video.video_id }}" target="_blank" style="color: white; text-decoration: none">{{ video.name }}</a>
                                     <button class="btn btn-light btn-sm ms-2" onclick='document.getElementById("user-label-alert").style.display = "block";'>
-                                            <i class="fas fa-pencil-alt" aria-hidden="true"></i>
-                                        </button>
+                                        <i class="fas fa-pencil-alt" aria-hidden="true"></i>
+                                    </button>
 
                                     <br><small class="h4">{% if video.user_label %}a.k.a <span style="border-bottom: 3px #ffffff dashed;"> {{ video.user_label }}</span>{% endif %}</small>
                                 </h2>
-                                <h4 class="col d-flex justify-content-end">
-                                    <span id="notice-div">
-
-                                    </span>
-                                    <span id="">
-
-                                        <input class="form-control me-1 visually-hidden" id="video-{{ video.video_id }}" value="https://www.youtube.com/watch?v={{ video.video_id }}">
-                                        <button class="copy-btn btn btn-success  me-1" data-clipboard-target="#video-{{ video.video_id }}">
-                                            <i class="far fa-copy" aria-hidden="true"></i>
-                                        </button>
-                                        <button class="btn btn-dark" type="button" hx-get="{% url 'mark_video_favorite' video.video_id %}" hx-target="#video-fav">
-                                            <div id="video-fav">
-                                                {% if video.is_favorite %}
-                                                    <i class="fas fa-heart" style="color: #fafa06"></i>
-                                                {% else %}
-                                                    <i class="far fa-heart"></i>
-                                                {% endif %}
-                                            </div>
-                                        </button>
-                                    </span>
-                                </h4>
                             </div>
-                            <h6>by {{ video.channel_name }}</h6>
+                            <h6>by <a href="{% url 'search' %}?mode=videos&channel={{ video.channel_name }}" style="text-decoration: none; color: white"><span style="border-bottom: 3px #e35959 dashed;">{{ video.channel_name }}</span></a></h6>
                             <p class="card-text">
                                 {% if video.description %}
                                     <h5 class="overflow-auto" style="max-height: 350px;">
@@ -83,27 +76,27 @@
                                 <div class="collapse" id="{{ video.video_id }}DurationCollapse">
                                     <div class="card card-body bg-dark text-white text-capitalize border border-3 mt-2 mb-2 border-light">
                                         <div hx-get="{% url 'video_completion_times' video.video_id %}"
-                                hx-trigger="revealed"
-                                hx-swap="outerHTML">
+                                            hx-trigger="revealed"
+                                            hx-swap="outerHTML">
 
                                         </div>
                                     </div>
                                 </div>
                                 <small>
-                                {% if video.has_cc %}<span class="badge bg-danger mb-1">CC</span>{% endif %}
-                                {% if video.published_at %}<span class="badge bg-secondary mb-1">Video uploaded on {{ video.published_at }}</span>{% endif %}
-                                <span class="badge bg-primary text-white mb-1"><i class="fas fa-eye"></i> {% if video.view_count == -1 %}HIDDEN{% else %}{{ video.view_count|intcomma }}{% endif %}</span>
-                                <span class="badge bg-warning text-black-50 mb-1"><i class="fas fa-thumbs-up"></i> {% if video.like_count == -1 %}HIDDEN{% else %}{{ video.like_count|intcomma }}{% endif %}</span>
-                                <span class="badge bg-warning text-black-50 mb-1"><i class="fas fa-thumbs-down"></i> {% if video.dislike_count == -1 %}HIDDEN{% else %}{{ video.dislike_count|intcomma }}{% endif %}</span>
-                                <span class="badge bg-info text-black-50 mb-1"><i class="fas fa-comments"></i> {% if video.comment_count == -1 %}HIDDEN{% else %}{{ video.comment_count|intcomma }}{% endif %} </span>
-                                {% if video.is_unavailable_on_yt or video.was_deleted_on_yt %}<span class="badge bg-light text-black-50 mb-1">UNAVAILABLE</span>{% endif %}
-                                <span class="badge bg-light text-black-50 mb-1"><a href="#found-in" style="text-decoration: none; color: grey"> Found in {{ video.playlists.all.count }} playlist{% if video.playlists.all.count > 1 %}s{% endif %}</a></span>
-                                {% if video.is_marked_as_watched %}
-                                    <span class="badge bg-dark text-white" >
-
-                                        <i class="fas fa-flag-checkered me-1"></i> marked watched
-                                    </span>
-                                {% endif %}
+                                    {% if video.has_cc %}<span class="badge bg-danger mb-1">CC</span>{% endif %}
+                                    {% if video.published_at %}<span class="badge bg-secondary mb-1">Video uploaded on {{ video.published_at }}</span>{% endif %}
+                                    <span class="badge bg-primary text-white mb-1"><i class="fas fa-eye"></i> {% if video.view_count == -1 %}HIDDEN{% else %}{{ video.view_count|intcomma }}{% endif %}</span>
+                                    <span class="badge bg-warning text-black-50 mb-1"><i class="fas fa-thumbs-up"></i> {% if video.like_count == -1 %}HIDDEN{% else %}{{ video.like_count|intcomma }}{% endif %}</span>
+                                    <span class="badge bg-warning text-black-50 mb-1"><i class="fas fa-thumbs-down"></i> {% if video.dislike_count == -1 %}HIDDEN{% else %}{{ video.dislike_count|intcomma }}{% endif %}</span>
+                                    <span class="badge bg-info text-black-50 mb-1"><i class="fas fa-comments"></i> {% if video.comment_count == -1 %}HIDDEN{% else %}{{ video.comment_count|intcomma }}{% endif %} </span>
+                                    {% if video.is_unavailable_on_yt or video.was_deleted_on_yt %}<span class="badge bg-light text-black-50 mb-1">UNAVAILABLE</span>{% endif %}
+                                    <span class="badge bg-light text-black-50 mb-1"><a href="#found-in" style="text-decoration: none; color: grey"> Found in {{ video.playlists.all.count }} playlist{% if video.playlists.all.count > 1 %}s{% endif %}</a></span>
+                                    {% if video.is_marked_as_watched %}
+                                        <span class="badge bg-dark text-white" >
+
+                                            <i class="fas fa-flag-checkered me-1"></i> marked watched
+                                        </span>
+                                    {% endif %}
 
                                 </small>
 
@@ -130,7 +123,7 @@
         <div class="col-6">
             <div class="row">
                 <div class="col-6">
-                        <h4>Your notes for this video
+                    <h4>Your notes for this video
 
                     </h4>
                 </div>
@@ -143,13 +136,13 @@
 
             <div >
                 <textarea name="video-notes-text-area"
-                          hx-post="{% url 'video_notes' video.video_id %}"
-                          hx-trigger="keyup changed delay:1.5s"
-                          hx-target="#notes-save-status"
-                          class="form-control"
-                          id="video-notes-text-area"
-                          placeholder="Enter here"
-                          rows="13">
+                    hx-post="{% url 'video_notes' video.video_id %}"
+                    hx-trigger="keyup changed delay:1.5s"
+                    hx-target="#notes-save-status"
+                    class="form-control"
+                    id="video-notes-text-area"
+                    placeholder="Enter here"
+                    rows="13">
                     {{ video.user_notes }}
                 </textarea>
 
@@ -161,41 +154,12 @@
         </div>
     </div>
 
-      <br>
+    <br>
 
     <div class="">
         <h3><span style="border-bottom: 3px #497ce2 dashed;">Video found in the following playlist{% if video.playlists.all.count > 1 %}s{% endif %}</span><i class="fas fa-binoculars ms-2" style="color: #4669d2"></i></h3>
         <div id="found-in" class="row row-cols-1 row-cols-md-4 g-4 text-dark mt-0" data-masonry='{"percentPosition": true }'>
-            {% for playlist in video.playlists.all %}
-                <div class="col">
-
-                    <div class="card" style="background-color: #EFEFEF;">
-                        <img  class="bd-placeholder-img card-img-top" src="{{ playlist.thumbnail_url }}" style="max-width:100%; height: 200px;   object-fit: cover;" alt="{{ playlist.name }} thumbnail">
-
-                        <div class="card-body">
-                            <h5 class="card-title"><a href="{% url 'playlist' playlist.playlist_id %}" class="stretched-link" style="text-decoration: none; color: black">{{ playlist.name }}</a></h5>
-                            <p class="card-text">
-                                <span class="badge bg-{% if playlist.get_watch_time_left == "0secs." %}success{% else %}primary{% endif %} text-white">{{ playlist.get_watched_videos_count }}/{{ playlist.get_watchable_videos_count }} viewed</span>
-                                {% if playlist.get_watch_time_left != "0secs." %}<span class="badge bg-dark text-white">{{ playlist.get_watch_time_left }} left</span>{% endif %}
-                            </p>
-                            <p class="card-text">
-                                {% if playlist.tags.all %}
-                                    <small>
-                                        <i class="fas fa-tags fa-sm" style="color: black"></i>
-                                        {% for tag in playlist.tags.all %}
-                                            <span class="badge rounded-pill bg-primary mb-lg-1">
-                                                {{ tag.name }}
-                                            </span>
-                                        {% endfor %}
-                                    </small>
-                                {% endif %}
-                            </p>
-                            <p class="card-text"><small class="text-muted">Last watched {{ playlist.last_watched|naturalday }}</small></p>
-                        </div>
-                    </div>
-                </div>
-
-            {% endfor %}
+            {% include 'intercooler/playlists.html' with playlists=video.playlists.all watching=False bg_color="#357779" show_controls=True %}
         </div>
     </div>
 
@@ -203,51 +167,51 @@
 
     <script type="text/javascript">
         // from https://developers.google.com/youtube/iframe_api_reference#Examples
-      var tag = document.createElement('script');
-      tag.id = 'iframe-demo';
-      tag.src = 'https://www.youtube.com/iframe_api';
-      var firstScriptTag = document.getElementsByTagName('script')[0];
-      firstScriptTag.parentNode.insertBefore(tag, firstScriptTag);
-
-      var player;
-      function onYouTubeIframeAPIReady() {
-        player = new YT.Player('ytplayer', {
-            events: {
-              'onReady': onPlayerReady,
-              'onStateChange': onPlayerStateChange
+        var tag = document.createElement('script');
+        tag.id = 'iframe-demo';
+        tag.src = 'https://www.youtube.com/iframe_api';
+        var firstScriptTag = document.getElementsByTagName('script')[0];
+        firstScriptTag.parentNode.insertBefore(tag, firstScriptTag);
+
+        var player;
+        function onYouTubeIframeAPIReady() {
+            player = new YT.Player('ytplayer', {
+                events: {
+                    'onReady': onPlayerReady,
+                    'onStateChange': onPlayerStateChange
+                }
+            });
+        }
+        function onPlayerReady(event) {
+            document.getElementById('ytplayer').style.borderColor = '#FF6D00';
+        }
+        function changeBorderColor(playerStatus) {
+            var color;
+            if (playerStatus === -1) {
+                color = "#37474F"; // unstarted = gray
+            } else if (playerStatus === 0) {
+                color = "#FFFF00"; // ended = yellow
+            } else if (playerStatus === 1) {
+                color = "#33691E"; // playing = green
+            } else if (playerStatus === 2) {
+                color = "#DD2C00"; // paused = red
+                // console.log(player.playerInfo.currentTime + " secs elapsed!");
+            } else if (playerStatus === 3) {
+                color = "#AA00FF"; // buffering = purple
+            } else if (playerStatus === 5) {
+                color = "#FF6DOO"; // video cued = orange
+            }
+            if (color) {
+                document.getElementById('ytplayer').style.borderColor = color;
             }
-        });
-      }
-      function onPlayerReady(event) {
-        document.getElementById('ytplayer').style.borderColor = '#FF6D00';
-      }
-      function changeBorderColor(playerStatus) {
-        var color;
-        if (playerStatus === -1) {
-          color = "#37474F"; // unstarted = gray
-        } else if (playerStatus === 0) {
-          color = "#FFFF00"; // ended = yellow
-        } else if (playerStatus === 1) {
-          color = "#33691E"; // playing = green
-        } else if (playerStatus === 2) {
-          color = "#DD2C00"; // paused = red
-            // console.log(player.playerInfo.currentTime + " secs elapsed!");
-        } else if (playerStatus === 3) {
-          color = "#AA00FF"; // buffering = purple
-        } else if (playerStatus === 5) {
-          color = "#FF6DOO"; // video cued = orange
         }
-        if (color) {
-          document.getElementById('ytplayer').style.borderColor = color;
+        function onPlayerStateChange(event) {
+            changeBorderColor(event.data);
+
+            // can use the below info to create a stream room
+            // player.playerInfo.currentTime returns player elapsed time
+            // console.log(player)
         }
-      }
-      function onPlayerStateChange(event) {
-        changeBorderColor(event.data);
-
-        // can use the below info to create a stream room
-        // player.playerInfo.currentTime returns player elapsed time
-        // console.log(player)
-      }
     </script>
 
 {% endblock %}

+ 2 - 0
apps/main/urls.py

@@ -20,6 +20,8 @@ urlpatterns = [
 
     ### STUFF RELATED TO ONE PLAYLIST
     path("playlist/<slug:playlist_id>", views.view_playlist, name='playlist'),
+    path("playlist/<slug:playlist_id>/add-user-label", views.add_playlist_user_label, name='add_playlist_user_label'),
+
     path('playlist/<slug:playlist_id>/<slug:video_id>/video-details/watched', views.mark_video_watched,
          name='mark_video_watched'),
     path("playlist/<slug:playlist_id>/settings", views.view_playlist_settings, name="view_playlist_settings"),

+ 16 - 15
apps/main/views.py

@@ -218,6 +218,9 @@ def library(request, library_type):
         library_type_display = library_type.lower().replace("-", " ")
         if library_type.lower() == "watching":
             watching = True
+    elif library_type.lower() == "yt-mix":
+        playlists = request.user.playlists.all().filter(Q(is_yt_mix=True) & Q(is_in_db=True))
+        library_type_display = "Your YouTube Mixes"
     elif library_type.lower() == "home":  # displays cards of all playlist types
         return render(request, 'library.html')
     elif library_type.lower() == "random":  # randomize playlist
@@ -410,11 +413,7 @@ def delete_videos(request, playlist_id, command):
     extra_text = " "
     if num_vids == 0:
         return HttpResponse("""
-        <div hx-ext="class-tools">
-            <div classes="add visually-hidden:3s">
-                <h5>Select some videos first!</h5><hr>
-            </div>
-        </div>
+            <h5>Select some videos first!</h5><hr>
         """)
 
     if 'confirm before deleting' in request.POST:
@@ -687,14 +686,6 @@ def update_playlist_settings(request, playlist_id):
 
     print(request.POST)
     playlist = request.user.playlists.get(playlist_id=playlist_id)
-    if "user_label" in request.POST:
-        playlist.user_label = request.POST["user_label"]
-        playlist.save(update_fields=['user_label'])
-
-        return HttpResponse(loader.get_template("intercooler/messages.html")
-            .render(
-            {"message_type": message_type,
-             "message_content": message_content}))
 
     if 'confirm before deleting' in request.POST:
         playlist.confirm_before_deleting = True
@@ -1065,12 +1056,12 @@ def playlist_move_copy_videos(request, playlist_id, action):
                 </div>
                 """
     if action == "move":
-        status = Playlist.objects.moveCopyVideosFromPlaylist(request.user,
+        result = Playlist.objects.moveCopyVideosFromPlaylist(request.user,
                                                              from_playlist_id=playlist_id,
                                                              to_playlist_ids=playlist_ids,
                                                              playlist_item_ids=playlist_item_ids,
                                                              action="move")
-        if status[0] == -1:
+        if result['status'] == -1:
             if status[1] == 404:
                 return HttpResponse(
                     "<span class='text-danger'>You cannot copy/move unavailable videos! De-select them and try again.</span>")
@@ -1133,3 +1124,13 @@ def add_video_user_label(request, video_id):
         video.user_label = bleach.clean(request.POST["user_label"])
         video.save(update_fields=['user_label'])
     return redirect('video', video_id=video_id)
+
+
+@login_required
+@require_POST
+def add_playlist_user_label(request, playlist_id):
+    playlist = request.user.playlists.get(playlist_id=playlist_id)
+    if "user_label" in request.POST:
+        playlist.user_label = bleach.clean(request.POST["user_label"].strip())
+        playlist.save(update_fields=['user_label'])
+    return redirect('playlist', playlist_id=playlist_id)

+ 4 - 4
apps/search/templates/intercooler/search_untube_results.html

@@ -1,8 +1,8 @@
 {% load humanize %}
 
 {% if view_mode == "playlists" %}
-    <div class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3 border-bottom">
-        <h1 class="h2">{% if search_query == "" %}{{ playlist_type }}{% endif %} Playlists {% if search_query != "" %}results for '{{ search_query|escape }}' {% endif %} <span class="badge bg-primary rounded-pill">{{ playlists.count|default:"0" }}</span></h1>
+    <div class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center overflow-auto pt-3 pb-2 mb-3 border-bottom">
+        <h1 class="h2">{{ playlist_type }} Playlists {% if search_query != "" %}results for '{{ search_query|escape }}' {% endif %} <span class="badge bg-primary rounded-pill">{{ playlists.count|default:"0" }}</span></h1>
     </div>
 
     <div class="row row-cols-1 row-cols-md-4 g-4">
@@ -14,8 +14,8 @@
     </div>
 {% else %}
 
-    <div class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3 border-bottom">
-        <h1 class="h2">{% if search_query == "" %}{{ videos_type }}{% endif %} Videos {% if search_query != "" %}results for '{{ search_query|escape }}' {% endif %} <span class="badge bg-primary rounded-pill">{{ videos.count }}</span> {% if videos.count > 100 %}<small>(only top 100 results shown)</small>{% endif %}</h1>
+    <div class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center overflow-auto pt-3 pb-2 mb-3 border-bottom">
+        <h1 class="h2">{{ videos_type }} Videos {% if search_query != "" %}results for '{{ search_query|escape }}' {% endif %} <span class="badge bg-primary rounded-pill">{{ videos.count }}</span> {% if videos.count > 100 %}<small>(only top 100 results shown)</small>{% endif %}</h1>
     </div>
 
     <div>

+ 4 - 3
apps/search/templates/search_untube_page.html

@@ -36,9 +36,9 @@
                     <div class="col">
                         Filter by playlist tags:
                         <select class="visually-hidden" onchange="triggerSubmit()"
-                            id="choices-playlist-tags" name="playlist-tags" placeholder="Add playlist tags to search within" multiple>
+                            id="choices-playlist-tags" name="playlist-tags" placeholder="Select playlist tags" multiple>
                             {% for tag in user.playlist_tags.all %}
-                                <option value="{{ tag.name }}" hx-post="{% url 'search_UnTube' %}" {% if pl_tag == tag.name %}selected{% endif %}>{{ tag.name }}</option>
+                                <option value="{{ tag.name }}" {% if pl_tag == tag.name %}selected{% endif %}>{{ tag.name }}</option>
                             {% endfor %}
                         </select>
 
@@ -78,7 +78,7 @@
                         <select class="visually-hidden" onchange="triggerSubmit()"
                             id="choices-channels" name="channel-names" placeholder="Select channels to search within" multiple>
                             {% for channel in user.profile.get_channels_list %}
-                                <option value="{{ channel }}" hx-post="{% url 'search_UnTube' %}">{{ channel }}</option>
+                                <option value="{{ channel }}" {% if channel == vid_channel_name %}selected{% endif %}>{{ channel }}</option>
                             {% endfor %}
                         </select>
                     </div>
@@ -91,6 +91,7 @@
                                     <option value="All" {% if item_type == "all" %}selected{% endif %}>All</option>
                                     <option value="Favorite" {% if item_type == "favorite" %}selected{% endif %}>Favorite</option>
                                     <option value="Watched" {% if item_type == "watched" %}selected{% endif %}>Watched</option>
+                                     <option value="Unavailable" {% if item_type == "unavailable" %}selected{% endif %}>Unavailable</option>
                                 </select>
                             </div>
                             <div class="ms-3">

+ 31 - 9
apps/search/views.py

@@ -33,12 +33,18 @@ def search(request):
         else:
             pl_tag = ""
 
+        if 'channel' in request.GET:
+            vid_channel_name = request.GET["channel"]
+        else:
+            vid_channel_name = ""
+
         return render(request, 'search_untube_page.html',
                       {"playlists": request.user.playlists.all(),
                        "mode": mode,
                        "item_type": item_type,
                        "query": query,
-                       "pl_tag": pl_tag})
+                       "pl_tag": pl_tag,
+                       "vid_channel_name": vid_channel_name})
     else:
         return redirect('home')
 
@@ -73,8 +79,12 @@ def search_UnTube(request):
             for tag in tags:
                 all_playlists = all_playlists.filter(tags__name=tag)
 
-        playlists = all_playlists.filter(Q(name__icontains=search_query) | Q(
-            user_label__icontains=search_query))
+        playlists = all_playlists.filter(Q(name__istartswith=search_query) | Q(
+            user_label__istartswith=search_query))
+
+        if not playlists.exists():
+            playlists = all_playlists.filter(Q(name__icontains=search_query) | Q(
+                user_label__icontains=search_query))
 
         if search_query.strip() == "":
             playlists = all_playlists
@@ -100,13 +110,19 @@ def search_UnTube(request):
             all_videos = all_videos.filter(is_favorite=True)
         elif videos_type == "Watched":
             all_videos = all_videos.filter(is_marked_as_watched=True)
+        elif videos_type == "Unavailable":
+            all_videos = all_videos.filter(Q(is_unavailable_on_yt=False) & Q(was_deleted_on_yt=True))
 
         if 'channel-names' in request.POST:
             channels = request.POST.getlist('channel-names')
             all_videos = all_videos.filter(channel_name__in=channels)
 
         videos = all_videos.filter(
-            Q(name__icontains=search_query) | Q(user_label__icontains=search_query))
+            Q(name__istartswith=search_query) | Q(user_label__istartswith=search_query))
+
+        if not videos.exists():
+            videos = all_videos.filter(Q(name__icontains=search_query) | Q(
+                user_label__icontains=search_query))
 
         if search_query.strip() == "":
             videos = all_videos
@@ -144,35 +160,41 @@ def search_playlists(request, playlist_type):
     playlists = None
     if playlist_type == "all":
         try:
-            playlists = request.user.playlists.all().filter(Q(name__startswith=search_query) & Q(is_in_db=True))
+            playlists = request.user.playlists.all().filter(Q(name__startswith=search_query) | Q(user_label__startswith=search_query) & Q(is_in_db=True))
         except:
             playlists = request.user.playlists.all()
     elif playlist_type == "user-owned":  # YT playlists owned by user
         try:
             playlists = request.user.playlists.filter(
-                Q(name__startswith=search_query) & Q(is_user_owned=True) & Q(is_in_db=True))
+                Q(name__startswith=search_query) | Q(user_label__startswith=search_query) & Q(is_user_owned=True) & Q(is_in_db=True))
         except:
             playlists = request.user.playlists.filter(Q(is_user_owned=True) & Q(is_in_db=True))
     elif playlist_type == "imported":  # YT playlists (public) owned by others
         try:
             playlists = request.user.playlists.filter(
-                Q(name__startswith=search_query) & Q(is_user_owned=False) & Q(is_in_db=True))
+                Q(name__startswith=search_query) | Q(user_label__startswith=search_query) & Q(is_user_owned=False) & Q(is_in_db=True))
         except:
             playlists = request.user.playlists.filter(Q(is_user_owned=True) & Q(is_in_db=True))
     elif playlist_type == "favorites":  # YT playlists (public) owned by others
         try:
             playlists = request.user.playlists.filter(
-                Q(name__startswith=search_query) & Q(is_favorite=True) & Q(is_in_db=True))
+                Q(name__startswith=search_query) | Q(user_label__startswith=search_query) & Q(is_favorite=True) & Q(is_in_db=True))
         except:
             playlists = request.user.playlists.filter(Q(is_favorite=True) & Q(is_in_db=True))
     elif playlist_type in ["watching", "plan-to-watch"]:
         try:
             playlists = request.user.playlists.filter(
-                Q(name__startswith=search_query) & Q(marked_as=playlist_type) & Q(is_in_db=True))
+                Q(name__startswith=search_query) | Q(user_label__startswith=search_query) & Q(marked_as=playlist_type) & Q(is_in_db=True))
         except:
             playlists = request.user.playlists.all().filter(Q(marked_as=playlist_type) & Q(is_in_db=True))
         if playlist_type == "watching":
             watching = True
+    elif playlist_type == "yt-mix":  # YT playlists owned by user
+        try:
+            playlists = request.user.playlists.filter(
+                Q(name__startswith=search_query) | Q(user_label__startswith=search_query) & Q(is_yt_mix=True) & Q(is_in_db=True))
+        except:
+            playlists = request.user.playlists.filter(Q(is_yt_mix=True) & Q(is_in_db=True))
 
     return HttpResponse(loader.get_template("intercooler/playlists.html")
                         .render({"playlists": playlists,

Some files were not shown because too many files changed in this diff