Guest User

Untitled

a guest
Aug 23rd, 2025
52
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
HTML 5 13.18 KB | None | 0 0
  1. <!DOCTYPE html>
  2. <html lang="en">
  3.     <head>
  4.         <meta charset="UTF-8" />
  5.         <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  6.         <title>Image and Text Side-by-Side Editor</title>
  7.         <!-- We will use the JSZip library to create the zip file. It is included here from a CDN. -->
  8.         <script src="https://cdnjs.cloudflare.com/ajax/libs/jszip/3.10.1/jszip.min.js"></script>
  9.         <style>
  10.             :root {
  11.                 --primary-bg: #f4f4f9;
  12.                 --secondary-bg: #ffffff;
  13.                 --text-colour: #333333;
  14.                 --border-colour: #dddddd;
  15.                 --button-bg: #005792;
  16.                 --button-hover-bg: #007bff;
  17.                 --button-text: #ffffff;
  18.                 --disabled-bg: #cccccc;
  19.             }
  20.             body {
  21.                 font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  22.                 background-color: var(--primary-bg);
  23.                 color: var(--text-colour);
  24.                 margin: 0;
  25.                 padding: 20px;
  26.                 display: flex;
  27.                 justify-content: center;
  28.                 align-items: flex-start;
  29.                 min-height: 100vh;
  30.             }
  31.             main {
  32.                 width: 95%;
  33.                 max-width: 1400px;
  34.                 background-color: var(--secondary-bg);
  35.                 border-radius: 8px;
  36.                 box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
  37.                 padding: 2em;
  38.             }
  39.             h1,
  40.             h2 {
  41.                 text-align: center;
  42.                 color: var(--button-bg);
  43.             }
  44.  
  45.             /*--- Setup View ---*/
  46.             #setup-view {
  47.                 text-align: center;
  48.             }
  49.             .instructions {
  50.                 margin: 2em auto;
  51.                 max-width: 700px;
  52.                 text-align: left;
  53.                 padding: 1em;
  54.                 background-color: #eef7ff;
  55.                 border-left: 5px solid var(--button-bg);
  56.             }
  57.             .instructions ul {
  58.                 padding-left: 20px;
  59.             }
  60.  
  61.             /*--- Editor View ---*/
  62.             #editor-view {
  63.                 display: none; /*Hidden by default*/
  64.             }
  65.             #navigation-bar {
  66.                 display: flex;
  67.                 justify-content: space-between;
  68.                 align-items: center;
  69.                 margin-bottom: 1.5em;
  70.                 padding: 1em;
  71.                 border-bottom: 1px solid var(--border-colour);
  72.             }
  73.             #status {
  74.                 font-size: 1.2em;
  75.                 font-weight: 500;
  76.             }
  77.             #content-viewer {
  78.                 display: flex;
  79.                 gap: 20px;
  80.                 min-height: 60vh;
  81.             }
  82.             .panel {
  83.                 flex: 1;
  84.                 display: flex;
  85.                 flex-direction: column;
  86.                 border: 1px solid var(--border-colour);
  87.                 border-radius: 5px;
  88.                 overflow: hidden;
  89.             }
  90.             .panel-header {
  91.                 padding: 0.5em 1em;
  92.                 background-color: var(--primary-bg);
  93.                 font-weight: bold;
  94.                 border-bottom: 1px solid var(--border-colour);
  95.             }
  96.             #image-container {
  97.                 padding: 10px;
  98.                 display: flex;
  99.                 justify-content: center;
  100.                 align-items: center;
  101.                 flex-grow: 1;
  102.             }
  103.             #image-display {
  104.                 max-width: 100%;
  105.                 max-height: 100%;
  106.                 object-fit: contain;
  107.                 border-radius: 4px;
  108.             }
  109.             #text-editor {
  110.                 flex-grow: 1;
  111.                 width: 100%;
  112.                 padding: 10px;
  113.                 border: none;
  114.                 font-family: "Courier New", Courier, monospace;
  115.                 font-size: 1rem;
  116.                 line-height: 1.5;
  117.                 resize: none;
  118.                 box-sizing: border-box;
  119.             }
  120.             #text-editor:focus {
  121.                 outline: none;
  122.             }
  123.             #download-section {
  124.                 text-align: center;
  125.                 margin-top: 2em;
  126.             }
  127.  
  128.             /*--- Common Elements ---*/
  129.             button {
  130.                 background-color: var(--button-bg);
  131.                 color: var(--button-text);
  132.                 border: none;
  133.                 padding: 12px 24px;
  134.                 font-size: 1rem;
  135.                 font-weight: bold;
  136.                 border-radius: 5px;
  137.                 cursor: pointer;
  138.                 transition: background-color 0.2s ease;
  139.             }
  140.             button:hover {
  141.                 background-color: var(--button-hover-bg);
  142.             }
  143.             button:disabled {
  144.                 background-color: var(--disabled-bg);
  145.                 cursor: not-allowed;
  146.             }
  147.         </style>
  148.     </head>
  149.     <body>
  150.         <main>
  151.             <!-- Initial setup view, shown on page load -->
  152.             <div id="setup-view">
  153.                 <h1>Side-by-Side Image and Text Editor</h1>
  154.                 <div class="instructions">
  155.                     <h2>Instructions</h2>
  156.                     <p>This tool allows you to view and edit text files alongside their corresponding images.</p>
  157.                     <ul>
  158.                         <li>Click the button below to start.</li>
  159.                         <li>You will be asked to select your <strong>image folder</strong> first.</li>
  160.                         <li>Then, you will be asked to select your <strong>text file folder</strong>.</li>
  161.                         <li>
  162.                             The tool will automatically match files with the same name (e.g.,
  163.                             <code>image1.png</code> and <code>image1.txt</code>).
  164.                         </li>
  165.                     </ul>
  166.                     <p>
  167.                         <strong>Browser Compatibility:</strong> This tool requires a modern browser like Chrome, Edge,
  168.                         or Opera. It will not work in Firefox or Safari.
  169.                     </p>
  170.                 </div>
  171.                 <button id="start-button">Select Folders and Begin</button>
  172.             </div>
  173.  
  174.             <!-- Main editor view, hidden until files are loaded -->
  175.             <div id="editor-view">
  176.                 <div id="navigation-bar">
  177.                     <button id="prev-button">Previous</button>
  178.                     <div id="status">File 0 of 0</div>
  179.                     <button id="next-button">Next</button>
  180.                 </div>
  181.  
  182.                 <div id="content-viewer">
  183.                     <div class="panel">
  184.                         <div class="panel-header" id="image-filename">Image File</div>
  185.                         <div id="image-container">
  186.                             <img id="image-display" alt="Image will be displayed here." />
  187.                         </div>
  188.                     </div>
  189.                     <div class="panel">
  190.                         <div class="panel-header" id="text-filename">Text File</div>
  191.                         <textarea id="text-editor" placeholder="Text content will appear here ..."></textarea>
  192.                     </div>
  193.                 </div>
  194.  
  195.                 <div id="download-section">
  196.                     <button id="download-zip-button">Save All Text Files to ZIP</button>
  197.                 </div>
  198.             </div>
  199.         </main>
  200.  
  201.         <script>
  202.             // --- DOM Element References ---
  203.             const setupView = document.getElementById("setup-view");
  204.             const editorView = document.getElementById("editor-view");
  205.             const startButton = document.getElementById("start-button");
  206.             const prevButton = document.getElementById("prev-button");
  207.             const nextButton = document.getElementById("next-button");
  208.             const statusDisplay = document.getElementById("status");
  209.             const imageDisplay = document.getElementById("image-display");
  210.             const textEditor = document.getElementById("text-editor");
  211.             const imageFilenameDisplay = document.getElementById("image-filename");
  212.             const textFilenameDisplay = document.getElementById("text-filename");
  213.             const downloadZipButton = document.getElementById("download-zip-button");
  214.  
  215.             // --- Application State ---
  216.             let filePairs = [];
  217.             let currentIndex = -1;
  218.             let currentImageObjectUrl = null;
  219.  
  220.             // --- Event Listeners ---
  221.             startButton.addEventListener("click", initialiseFileSelection);
  222.             prevButton.addEventListener("click", showPreviousPair);
  223.             nextButton.addEventListener("click", showNextPair);
  224.             downloadZipButton.addEventListener("click", createAndDownloadZip);
  225.  
  226.             /**
  227.              * Checks browser compatibility and starts the folder selection process.
  228.              */
  229.             async function initialiseFileSelection() {
  230.                 if (!window.showDirectoryPicker) {
  231.                     alert(
  232.                         "Your browser does not support the required File System Access API. Please use a recent version of Chrome, Edge, or Opera."
  233.                     );
  234.                     return;
  235.                 }
  236.  
  237.                 try {
  238.                     const imageDirHandle = await window.showDirectoryPicker({title: "Select Image Folder"});
  239.                     const textDirHandle = await window.showDirectoryPicker({title: "Select Text File Folder"});
  240.  
  241.                     startButton.textContent = "Processing ...";
  242.                     startButton.disabled = true;
  243.  
  244.                     await processFolders(imageDirHandle, textDirHandle);
  245.                 } catch (error) {
  246.                     // This will catch if the user cancels the picker dialogue.
  247.                     console.log("Folder selection cancelled or failed:", error.message);
  248.                     startButton.textContent = "Select Folders and Begin";
  249.                     startButton.disabled = false;
  250.                 }
  251.             }
  252.  
  253.             /**
  254.              * Reads files from selected directories, finds matching pairs, and populates the application state.
  255.              * @param {FileSystemDirectoryHandle} imageDirHandle - Handle for the image directory.
  256.              * @param {FileSystemDirectoryHandle} textDirHandle - Handle for the text directory.
  257.              */
  258.             async function processFolders(imageDirHandle, textDirHandle) {
  259.                 const imageFiles = new Map();
  260.                 const textFiles = new Map();
  261.                 const imageExtensions = [".jpg", ".jpeg", ".png", ".gif", ".bmp", ".webp"];
  262.                 const textExtension = ".txt";
  263.  
  264.                 // Helper to get file base name (without extension)
  265.                 const getBaseName = (fileName) => fileName.substring(0, fileName.lastIndexOf("."));
  266.  
  267.                 // Read image files
  268.                 for await (const entry of imageDirHandle.values()) {
  269.                     if (
  270.                         entry.kind === "file" &&
  271.                         imageExtensions.some((ext) => entry.name.toLowerCase().endsWith(ext))
  272.                     ) {
  273.                         const baseName = getBaseName(entry.name);
  274.                         imageFiles.set(baseName, await entry.getFile());
  275.                     }
  276.                 }
  277.  
  278.                 // Read text files
  279.                 for await (const entry of textDirHandle.values()) {
  280.                     if (entry.kind === "file" && entry.name.toLowerCase().endsWith(textExtension)) {
  281.                         const baseName = getBaseName(entry.name);
  282.                         textFiles.set(baseName, await entry.getFile());
  283.                     }
  284.                 }
  285.  
  286.                 // Find pairs
  287.                 filePairs = [];
  288.                 for (const [baseName, imageFile] of imageFiles.entries()) {
  289.                     if (textFiles.has(baseName)) {
  290.                         const textFile = textFiles.get(baseName);
  291.                         filePairs.push({
  292.                             baseName: baseName,
  293.                             image: {file: imageFile},
  294.                             text: {file: textFile, content: null}, // content will be loaded on demand
  295.                         });
  296.                     }
  297.                 }
  298.  
  299.                 // Sort pairs alphabetically by base name for consistent order
  300.                 filePairs.sort((a, b) => a.baseName.localeCompare(b.baseName));
  301.  
  302.                 if (filePairs.length > 0) {
  303.                     setupView.style.display = "none";
  304.                     editorView.style.display = "block";
  305.                     currentIndex = 0;
  306.                     displayPair(currentIndex);
  307.                 } else {
  308.                     alert(
  309.                         'No matching image and text files were found. Please check your folders and ensure filenames match (e.g., "my_image.png" and "my_image.txt").'
  310.                     );
  311.                     startButton.textContent = "Select Folders and Begin";
  312.                     startButton.disabled = false;
  313.                 }
  314.             }
  315.  
  316.             /**
  317.              * Displays a specific image/text pair in the editor.
  318.              * @param {number} index - The index of the pair to display.
  319.              */
  320.             async function displayPair(index) {
  321.                 if (index < 0 || index >= filePairs.length) return;
  322.  
  323.                 const pair = filePairs[index];
  324.  
  325.                 // --- Update Image Panel ---
  326.                 // Revoke the previous object URL to free up memory
  327.                 if (currentImageObjectUrl) {
  328.                     URL.revokeObjectURL(currentImageObjectUrl);
  329.                 }
  330.                 currentImageObjectUrl = URL.createObjectURL(pair.image.file);
  331.                 imageDisplay.src = currentImageObjectUrl;
  332.                 imageFilenameDisplay.textContent = pair.image.file.name;
  333.  
  334.                 // --- Update Text Panel ---
  335.                 textFilenameDisplay.textContent = pair.text.file.name;
  336.                 // Load content from memory if already edited, otherwise read from file
  337.                 if (pair.text.content !== null) {
  338.                     textEditor.value = pair.text.content;
  339.                 } else {
  340.                     textEditor.value = "Loading text ...";
  341.                     try {
  342.                         const text = await pair.text.file.text();
  343.                         pair.text.content = text; // Cache the content
  344.                         textEditor.value = text;
  345.                     } catch (e) {
  346.                         textEditor.value = "Error loading text file.";
  347.                         console.error("Failed to read text file:", e);
  348.                     }
  349.                 }
  350.  
  351.                 // --- Update UI State ---
  352.                 statusDisplay.textContent = `File ${index + 1} of ${filePairs.length}`;
  353.                 prevButton.disabled = index === 0;
  354.                 nextButton.disabled = index === filePairs.length - 1;
  355.             }
  356.  
  357.             /**
  358.              * Saves the current text content and navigates to the previous pair.
  359.              */
  360.             function showPreviousPair() {
  361.                 if (currentIndex > 0) {
  362.                     saveCurrentText();
  363.                     currentIndex--;
  364.                     displayPair(currentIndex);
  365.                 }
  366.             }
  367.  
  368.             /**
  369.              * Saves the current text content and navigates to the next pair.
  370.              */
  371.             function showNextPair() {
  372.                 if (currentIndex < filePairs.length - 1) {
  373.                     saveCurrentText();
  374.                     currentIndex++;
  375.                     displayPair(currentIndex);
  376.                 }
  377.             }
  378.  
  379.             /**
  380.              * Saves the content of the textarea into the current pair's state.
  381.              */
  382.             function saveCurrentText() {
  383.                 if (currentIndex >= 0 && currentIndex < filePairs.length) {
  384.                     filePairs[currentIndex].text.content = textEditor.value;
  385.                 }
  386.             }
  387.  
  388.             /**
  389.              * Generates a ZIP archive of all text files and prompts the user to download it.
  390.              */
  391.             async function createAndDownloadZip() {
  392.                 // Ensure the very last edit is saved before zipping
  393.                 saveCurrentText();
  394.  
  395.                 downloadZipButton.textContent = "Zipping ...";
  396.                 downloadZipButton.disabled = true;
  397.  
  398.                 const zip = new JSZip();
  399.  
  400.                 for (const pair of filePairs) {
  401.                     // Ensure all text contents are loaded before adding to the zip
  402.                     if (pair.text.content === null) {
  403.                         pair.text.content = await pair.text.file.text();
  404.                     }
  405.                     zip.file(pair.text.file.name, pair.text.content);
  406.                 }
  407.  
  408.                 try {
  409.                     const zipBlob = await zip.generateAsync({type: "blob"});
  410.  
  411.                     // Create a temporary link to trigger the download
  412.                     const link = document.createElement("a");
  413.                     link.href = URL.createObjectURL(zipBlob);
  414.                     link.download = "edited_texts.zip";
  415.                     document.body.appendChild(link);
  416.                     link.click();
  417.                     document.body.removeChild(link);
  418.                     URL.revokeObjectURL(link.href); // Clean up
  419.                 } catch (error) {
  420.                     alert("An error occurred while creating the ZIP file.");
  421.                     console.error("ZIP generation failed:", error);
  422.                 } finally {
  423.                     downloadZipButton.textContent = "Save All Text Files to ZIP";
  424.                     downloadZipButton.disabled = false;
  425.                 }
  426.             }
  427.         </script>
  428.     </body>
  429. </html>
Advertisement
Add Comment
Please, Sign In to add comment