Browse Source

refactor: create separate import playlist screen

make the "import playlist" view a separate screen, just like the "history" view or the "save channel" view. this will require updating `popup.js` to hook up the html components with the code functionality (and maybe the corresponding styles in `popup.css`). remove the existing file selector input, and have the current "Import Playlists" button navigate to the screen.

```git-revs
548843a  (Base revision)
5ce127a  Remove the hidden file input and change Import Playlists button to navigate to import view
7fa2824  Snapshot before codemcp change
4ab4bca  Add state property for import text and method to show import view
1e12ae2  Add showImport method and update importButton to navigate to import view
e1c9b8e  Update importButton to navigate to import view instead of triggering file input
2432f20  Remove the importFile method since we're using textarea now
3279cee  Add importContainer binding to show/hide import view
3d569b1  Add bindings for import textarea and submit button
2431d6a  Add CSS styles for import view
f3214b6  Add export-btn style to match the import button
8f9de7a  Save context window contents for this feature
f6210c2  Add importHeader binding to show/hide import view header
fd4e83f  Update context documentation with the header binding fix
HEAD     Make import textarea much larger to fill most of the popup
```

codemcp-id: 16-refactor-create-separate-import-playlist-screen
Brandon Wong 7 months ago
parent
commit
9febb23e56
4 changed files with 209 additions and 34 deletions
  1. 63 0
      2025-11-03-codemcp-import-playlist-view.md
  2. 94 0
      popup/popup.css
  3. 0 7
      popup/popup.html
  4. 52 27
      popup/popup.js

+ 63 - 0
2025-11-03-codemcp-import-playlist-view.md

@@ -0,0 +1,63 @@
+# Import Playlist View Refactor - 2025-11-03
+
+## User Request
+make the "import playlist" view a separate screen, just like the "history" view or the "save channel" view. this will require updating `popup.js` to hook up the html components with the code functionality (and maybe the corresponding styles in `popup.css`). remove the existing file selector input, and have the current "Import Playlists" button navigate to the screen.
+
+## Changes Made
+
+### 1. HTML Changes (popup.html)
+- **Removed**: Hidden file input element (`<input type="file" id="import-file-input" ...>`)
+- **Kept**: Import view container structure with textarea and submit button (already existed in HTML)
+- **Kept**: Import header with back button (already existed in HTML)
+
+### 2. JavaScript Changes (popup.js)
+
+#### Added State Properties
+```javascript
+// Import properties
+importText: "", // Text area content for import
+```
+
+#### Added Methods
+- `showImport()`: Navigates to the import view by setting `currentView` to "import"
+- `handleImport()`: Processes the JSON text from textarea, validates and imports playlists
+
+#### Modified Methods
+- `importButton` binding: Changed from triggering file input click to calling `showImport()`
+- Removed `importFileInput` binding (no longer needed)
+- Removed `importFile(event)` method (replaced by `handleImport()`)
+
+#### Added Bindings
+- `importHeader`: Controls visibility of import view header (shows only when `currentView === "import"`)
+- `importContainer`: Controls visibility of import view container
+- `importTextarea`: Two-way binding for textarea value
+- `importSubmitButton`: Click handler for import button
+
+### 3. CSS Changes (popup.css)
+
+#### Added Styles
+- `.import-container`: Container for import view (matches other view containers)
+- `.import-content`: Content wrapper with flex layout
+- `.import-section`: Section wrapper for textarea and button
+- `.import-textarea`: Large monospace textarea with focus styles
+- `.import-submit-btn`: Green submit button with hover/active states
+- `.import-btn`: Green button style for main navigation button
+- `.export-btn`: Blue button style (added for consistency)
+
+## Architecture Pattern
+The import view now follows the same pattern as other views (history, saveChannel, wikiInsp):
+1. Header with back button (controlled by `importHeader` binding)
+2. Container div with x-bind for visibility control
+3. Navigation button on main screen
+4. View-specific Alpine.js bindings for interactivity
+5. Dedicated CSS styles for the view
+
+## CSP Compliance
+All Alpine.js code remains CSP-compliant:
+- No JavaScript expressions in HTML attributes
+- All bindings use Alpine.data properties
+- Pre-computed display data in Alpine state
+- Event handlers defined in binding objects
+
+## Bug Fix
+- Added missing `importHeader` binding to properly hide the import view header when not in that view

+ 94 - 0
popup/popup.css

@@ -798,3 +798,97 @@ button:hover {
 .wiki-insp-btn:hover {
   background-color: #7b1fa2;
 }
+
+/* Import view styles */
+.import-container {
+  background: #fff;
+  border-radius: 8px;
+  padding: 16px;
+  margin-bottom: 16px;
+  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
+  min-height: 300px;
+}
+
+.import-content {
+  display: flex;
+  flex-direction: column;
+  gap: 20px;
+}
+
+.import-section {
+  display: flex;
+  flex-direction: column;
+  gap: 12px;
+}
+
+.import-textarea {
+  width: 100%;
+  min-height: 450px;
+  max-height: 500px;
+  padding: 12px;
+  border: 1px solid #ddd;
+  border-radius: 6px;
+  font-family: monospace;
+  font-size: 12px;
+  background-color: #f8f9fa;
+  resize: vertical;
+  line-height: 1.4;
+}
+
+.import-textarea:focus {
+  outline: none;
+  border-color: #4285f4;
+  box-shadow: 0 0 0 2px rgba(66, 133, 244, 0.2);
+}
+
+.import-submit-btn {
+  width: 100%;
+  background-color: #34a853;
+  color: white;
+  padding: 10px 16px;
+  border-radius: 6px;
+  font-size: 14px;
+  font-weight: 500;
+  transition: all 0.2s ease;
+}
+
+.import-submit-btn:hover {
+  background-color: #2d7d3a;
+  transform: translateY(-1px);
+  box-shadow: 0 2px 8px rgba(52, 168, 83, 0.3);
+}
+
+.import-submit-btn:active {
+  background-color: #1e5f2a;
+  transform: translateY(0);
+  box-shadow: 0 1px 3px rgba(52, 168, 83, 0.4);
+  transition: all 0.1s ease;
+}
+
+/* Import button styles */
+.import-btn {
+  background-color: #34a853;
+  color: white;
+  padding: 8px 12px;
+  border-radius: 4px;
+  font-size: 14px;
+  font-weight: 500;
+}
+
+.import-btn:hover {
+  background-color: #2d7d3a;
+}
+
+/* Export button styles */
+.export-btn {
+  background-color: #4285f4;
+  color: white;
+  padding: 8px 12px;
+  border-radius: 4px;
+  font-size: 14px;
+  font-weight: 500;
+}
+
+.export-btn:hover {
+  background-color: #3367d6;
+}

+ 0 - 7
popup/popup.html

@@ -420,13 +420,6 @@
         <button class="wiki-insp-btn" x-bind="wikiInspButton">
           Wiki/Insp
         </button>
-        <input
-          type="file"
-          id="import-file-input"
-          accept=".json"
-          style="display: none"
-          x-bind="importFileInput"
-        />
       </div>
     </div>
   </body>

+ 52 - 27
popup/popup.js

@@ -9,6 +9,7 @@ document.addEventListener("alpine:init", () => {
   // X view playback history (?)
   // X include currently open tabs in export
   // - support playlist management (add, remove, rename playlists)
+  //   - support dynamic (on-demand) playlists
   // - support input text box for adding to playlist (how to handle title?)
   //   - or raw json editing
   // X option (probably a button) to add current page (url, title) to playlist
@@ -60,6 +61,9 @@ document.addEventListener("alpine:init", () => {
     currentVideoTimestamp: "", // Current video timestamp
     includeTimestamp: true, // Whether to include timestamp line
 
+    // Import properties
+    importText: "", // Text area content for import
+
     init() {
       this.loadPlaylists();
       this.loadHistory();
@@ -333,6 +337,10 @@ document.addEventListener("alpine:init", () => {
       }
     },
 
+    showImport() {
+      this.currentView = "import";
+    },
+
     async refreshVideoTimestamp() {
       if (this.isCurrentVideoYoutube) {
         await this.getVideoTimestamp();
@@ -847,37 +855,26 @@ document.addEventListener("alpine:init", () => {
 
     importButton: {
       ["@click"]() {
-        document.getElementById("import-file-input").click();
-      },
-    },
-
-    importFileInput: {
-      ["@change"]() {
-        this.importFile(this.$event);
+        this.showImport();
       },
     },
 
-    importFile(event) {
-      const file = event.target.files[0];
-      if (!file) return;
-
-      const reader = new FileReader();
-      reader.onload = (e) => {
-        try {
-          const importedData = JSON.parse(e.target.result);
-          this.validateAndImportPlaylists(importedData);
-        } catch (error) {
-          console.error("Error parsing JSON file:", error);
-          alert(
-            "Invalid JSON file. Please select a valid playlist export file.",
-          );
-        } finally {
-          // Reset the file input so the same file can be selected again
-          event.target.value = "";
-        }
-      };
+    async handleImport() {
+      if (!this.importText.trim()) {
+        alert("Please paste your JSON export data");
+        return;
+      }
 
-      reader.readAsText(file);
+      try {
+        const importedData = JSON.parse(this.importText);
+        this.validateAndImportPlaylists(importedData);
+        this.importText = "";
+      } catch (error) {
+        console.error("Error parsing JSON:", error);
+        alert(
+          "Invalid JSON format. Please paste valid playlist export data.",
+        );
+      }
     },
 
     validateAndImportPlaylists(data) {
@@ -1138,6 +1135,12 @@ document.addEventListener("alpine:init", () => {
       },
     },
 
+    importHeader: {
+      ["x-show"]() {
+        return this.currentView === "import";
+      },
+    },
+
     playlistsContainer: {
       ["x-show"]() {
         return this.currentView === "playlists";
@@ -1168,6 +1171,12 @@ document.addEventListener("alpine:init", () => {
       },
     },
 
+    importContainer: {
+      ["x-show"]() {
+        return this.currentView === "import";
+      },
+    },
+
     exportContainer: {
       ["x-show"]() {
         return this.currentView === "playlists";
@@ -1297,5 +1306,21 @@ document.addEventListener("alpine:init", () => {
         };
       },
     },
+
+    // Import view bindings
+    importTextarea: {
+      [":value"]() {
+        return this.importText;
+      },
+      ["@input"]() {
+        this.importText = this.$el.value;
+      },
+    },
+
+    importSubmitButton: {
+      ["@click"]() {
+        this.handleImport();
+      },
+    },
   }));
 });