Browse Source

add scatter button to interleave last N videos in playlist

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Brandon Wong 1 week ago
parent
commit
eebdfbac04
4 changed files with 134 additions and 0 deletions
  1. 40 0
      2026-06-18-claude-add-scatter-button.md
  2. 28 0
      popup/popup.css
  3. 7 0
      popup/popup.html
  4. 59 0
      popup/popup.js

File diff suppressed because it is too large
+ 40 - 0
2026-06-18-claude-add-scatter-button.md


+ 28 - 0
popup/popup.css

@@ -445,6 +445,34 @@ button:hover {
   background-color: #ccc;
 }
 
+.scatter-count-input {
+  width: 48px;
+  padding: 8px 4px;
+  border: 1px solid #ccc;
+  border-radius: 4px;
+  font-size: 14px;
+  text-align: center;
+}
+
+.scatter-btn {
+  background-color: #4285f4;
+  color: white;
+  padding: 8px 12px;
+  border-radius: 4px;
+  font-size: 14px;
+  font-weight: 500;
+}
+
+.scatter-btn:hover:not(:disabled) {
+  background-color: #3367d6;
+}
+
+.scatter-btn:disabled {
+  background-color: #ccc;
+  color: #888;
+  cursor: not-allowed;
+}
+
 /* Save Channel view styles */
 .save-channel-container {
   background: #fff;

+ 7 - 0
popup/popup.html

@@ -383,6 +383,13 @@
           x-bind="addCurrentPageButton"
           x-text="addCurrentPageButtonText"
         ></button>
+        <input
+          class="scatter-count-input"
+          type="number"
+          min="2"
+          x-bind="scatterCountInput"
+        />
+        <button class="scatter-btn" x-bind="scatterButton">Scatter</button>
       </div>
 
       <!-- Individual playlist view -->

+ 59 - 0
popup/popup.js

@@ -35,6 +35,7 @@ document.addEventListener("alpine:init", () => {
     isCurrentTabYoutube: false, // Whether current tab is YouTube
     isCurrentTabChannelPage: false, // Whether current tab is YouTube videos page
     addCurrentPageButtonText: "Add Cur. Page", // Button text
+    scatterCount: 4,
 
     // Save channel properties
     selectedCategory: "FOR BOTH", // Currently selected category
@@ -693,6 +694,38 @@ document.addEventListener("alpine:init", () => {
       return playlist.length;
     },
 
+    async scatterLastN(playlistName, count) {
+      const playlists = JSON.parse(JSON.stringify(this.playlists));
+      const playlist = playlists[playlistName];
+      const n = playlist.length;
+      if (!playlist || count < 2 || count >= n) return;
+
+      const prefix = playlist.slice(0, n - count);
+      const tail = playlist.slice(n - count);
+      const insertions = [];
+      let cursor = prefix.length;
+
+      for (let i = count - 2; i >= 0; i--) {
+        const gap = Math.floor(Math.random() * 3) + 1;
+        cursor = Math.max(1, cursor - gap);
+        insertions.push([cursor, tail[i]]);
+      }
+      for (const [idx, video] of insertions) {
+        prefix.splice(idx, 0, video);
+      }
+      prefix.push(tail[count - 1]);
+
+      playlists[playlistName] = prefix;
+      try {
+        await browser.storage.local.set({ playlists });
+        this.playlists = playlists;
+        this.updatePlaylistsForDisplay();
+        this.updateCurrentPlaylistVideos();
+      } catch (error) {
+        console.error("Error scattering videos:", error);
+      }
+    },
+
     async toggleVideoDoneStatus(playlistName, index) {
       const playlists = JSON.parse(JSON.stringify(this.playlists));
       const playlist = [...playlists[playlistName]];
@@ -1463,6 +1496,32 @@ document.addEventListener("alpine:init", () => {
       },
     },
 
+    scatterCountInput: {
+      [":value"]() {
+        return this.scatterCount;
+      },
+      [":max"]() {
+        const pl = this.playlists[this.currentPlaylistName];
+        return pl ? pl.length - 1 : 2;
+      },
+      ["@input"]() {
+        const pl = this.playlists[this.currentPlaylistName];
+        const max = pl ? pl.length - 1 : 2;
+        const v = Math.min(max, Math.max(2, parseInt(this.$el.value) || 2));
+        this.scatterCount = v;
+      },
+    },
+
+    scatterButton: {
+      ["@click"]() {
+        this.scatterLastN(this.currentPlaylistName, this.scatterCount);
+      },
+      [":disabled"]() {
+        const pl = this.playlists[this.currentPlaylistName];
+        return !pl || this.scatterCount >= pl.length;
+      },
+    },
+
     // Add current page button binding
     addCurrentPageButton: {
       ["@click"]() {