const imageExtensions = ['gif','jpg','jpeg','png', 'webp', 'svg', 'ico', 'bmp']; const videoExtensions =['mpg', 'mp2', 'mpeg', 'mpe', 'mpv', 'mp4']; const audioExtensions =['mp3', 'ogg', 'wav', 'flac']; const invalidFileNameCharsRe =/[\\/\|:\*\?"<>]/; // dont allow \/|:*?<> const invalidFileNameChars = "\\/|:*?<>"; const mappings = new Map(JSON.parse(localStorage.getItem('mappings')) || []); let statusLine = document.getElementById('status'); // store for when done with sorting TODO display let splash = document.getElementById('current-file').innerHTML; let noFiles = document.getElementById('file-list').innerHTML; let history = []; // // SERVER COMMUNICATION // /** * Only show the server response text if its a 500 internal */ function showServerError(fname, status, text) { if (status == 500) { message = `in ${fname}: server returned ${status}: '${text}'`; } else { message = `in ${fname}: server returned ${status}`; } statusLine.textContent = message; console.error(message); } /** * get the path in which the images are located and can be loaded from, by appending to them to the stagingPath */ let stagingPath = ""; async function setStagingPath() { const response = await fetch("imgsort.php?action=getStagingPath"); text = await response.text(); if (response.ok) { stagingPath = text; } else { showServerError("setStagingPath", response.status, text); throw new Error("Can not get staging path"); } } /** * list of files to be processed */ let fileList = []; async function setFileList() { const response = await fetch("imgsort.php?action=getFileList"); const text = await response.text(); // console.log(text); if (response.ok) { fileList = JSON.parse(text); // fileList.sort(); } else { showServerError("setFileList", response.status, text); throw new Error("Can not get file list"); } } // // RENDERING // /* * Create button for each mapping */ function renderMoveButtons() { const buttons = document.getElementById('move-buttons'); buttons.innerHTML = ''; mappings.forEach((directory, key) => { const moveButton = document.createElement('button'); moveButton.innerHTML = `${key} ${directory}`; moveButton.onclick = function() { moveFile(directory); }; moveButton.className = "move-button"; buttons.appendChild(moveButton); }); } function renderFileList() { const fileListDiv = document.getElementById('file-list'); if (fileList.length == 0) { fileListDiv.innerHTML = noFiles; return; } fileListDiv.innerHTML = ''; fileList.forEach(file => { const p = document.createElement('p'); classes = "file-list-item path"; if (file === currentFile) { classes += " selected-file-list-item"; } p.setAttribute("class", classes) p.textContent = file; fileListDiv.appendChild(p); }); } let currentFile = ""; let currentFileIdx = null; function createMediaFileElement(file, autoplay=false) { const fileExt = file.split('.').pop().toLowerCase(); if (imageExtensions.includes(fileExt)) { // if image let img = document.createElement("img"); img.setAttribute('src', stagingPath + file) return img; } else if (videoExtensions.includes(fileExt)) { // if video let vid = document.createElement("video"); if (autoplay) vid.setAttribute('autoplay', true); vid.setAttribute('controls', true); src = document.createElement("source"); src.setAttribute("src", stagingPath + file); src.setAttribute("type", `video/${fileExt}`); vid.appendChild(src); return vid; } else if (audioExtensions.includes(fileExt)) { // if audio let aud = document.createElement("audio"); if (autoplay) aud.setAttribute('autoplay', true); aud.setAttribute('controls', true); src = document.createElement("source"); src.setAttribute("src", stagingPath + file); src.setAttribute("type", `audio/${fileExt}`); aud.appendChild(src); return aud; } else { // if none of the above let p = document.createElement("p"); p.innerHTML = 'No preview available'; return p; } } function renderCurrentFile() { // const currentFileNameDiv = document.getElementById('current-file-name'); // if (!currentFile) { // currentFileNameDiv.innerHTML = "No files in staging"; // } // else { // currentFileNameDiv.innerHTML = currentFile; // } const currentFileDiv = document.getElementById('current-file'); currentFileDiv.innerHTML = ""; if (currentFile) { let element = createMediaFileElement(currentFile, true); currentFileDiv.appendChild(element); } else { currentFileDiv.innerHTML = splash; } } // load media tag of the next file in hidden div, without autoplay function preloadNextFile() { const nextFile = fileList[currentFileIdx+1]; if (!nextFile) { return } const preloadFileDiv = document.getElementById('preload-file'); preloadFileDiv.innerHTML = ""; let element = createMediaFileElement(nextFile, false); preloadFileDiv.appendChild(element); console.log("Preloaded " + nextFile); } // // LOGIC // // set the currentFile variable according to currentFileIdx function setCurrentFile() { // check configuration if (mappings.size == 0) { statusLine.textContent = "No mappings configured - Click 'Configure' first"; return; } // if none set and there are files if (currentFileIdx == null && fileList.length > 0) { currentFileIdx = 0; } // else if (currentFileIdx < fileList.length) { // } else if (currentFileIdx >= fileList.length) { currentFileIdx = fileList.length - 1; } // if index is valid if (typeof(currentFileIdx) == 'number' && currentFileIdx >= 0 && currentFileIdx < fileList.length) { currentFile = fileList[currentFileIdx]; } else { currentFileIdx = null; currentFile = ""; statusLine.textContent = "No more files to sort"; } // set filename in input field const fileNameNoExt = currentFile.replace(/\.[^/.]+$/, ""); document.getElementById('filename').value = fileNameNoExt; renderCurrentFile(); renderFileList(); } function setCurrentFileFromName(filename) { let idx = fileList.indexOf(filename); if (idx < 0) { message = `Can not set file from name '${filename}': Not found in list`; statusLine.textContent = message; console.error(message); return; } currentFileIdx = idx; setCurrentFile(); } // Event delegation document.getElementById('file-list').addEventListener('click', function(event) { if (event.target && event.target.tagName === 'P') { // Call the function with the content of the clicked paragraph if (event.target.textContent != "No files") { setCurrentFileFromName(event.target.textContent); } } }); async function moveFile(directory) { if (!currentFile) { statusLine.textContent = "No file to process!"; return; } const userSetFilename = document.getElementById('filename').value; if (!userSetFilename) { statusLine.textContent = "Filename must not be empty"; return; } // file names invalid on windows not handled if (invalidFileNameCharsRe.test(userSetFilename)) { statusLine.textContent = `Filename must not contain ${invalidFileNameChars}`; return; } // re-add the extension const fileExt = currentFile.split('.').pop(); let filename = userSetFilename + '.' + fileExt; let newFilePath = directory + '/' + filename; const response = await fetch(`imgsort.php?action=moveFile&file=${escape(currentFile)}&dest=${escape(newFilePath)}`); const text = await response.text(); // console.log(text); if (response.ok) { if (filename != currentFile) { statusLine.textContent = `Moved '${currentFile}' to '${directory}'`; } else { statusLine.textContent = `Moved '${currentFile}' to '${directory}' as '${filename}'`; } history.push([currentFile, newFilePath]); // remove file from list fileList.splice(currentFileIdx, 1); // set next file setCurrentFile(); preloadNextFile(); } else { showServerError("moveFile", response.status, text); } } async function undo() { if (!history.length > 0) { statusLine.textContent = "Nothing to undo" return; } const [file, newFilePath] = history.pop(); const response = await fetch(`imgsort.php?action=undoFile&file=${escape(file)}&dest=${escape(newFilePath)}`); const text = await response.text(); // console.log(text); if (response.ok) { statusLine.textContent = `Undo: move '${file}' to '${newFilePath}'`; await setFileList() setCurrentFile(); } else { showServerError("undo", response.status, text); } } function skip() { currentFileIdx++; setCurrentFile(); } async function initialize() { // remove the value on reload document.getElementById('filename').value = ""; await setStagingPath(); await setFileList(); renderMoveButtons(); renderFileList(); // setCurrentFile(); preloadNextFile(); statusLine.textContent = "Ready"; } // load existing mappings on page load window.onload = function() { initialize(); }; // handle keyboard document.onkeypress = function(e) { // dont handle keys while user is typing in filename box if (document.querySelector('input') === document.activeElement) { return; } e = e || window.event; let keydebug = document.getElementById("keydebug"); if (keydebug) { keydebug.innerHTML = e.key; } if (e.key == "u") { undo(); } else if (e.key == "s") { skip(); } else if (mappings.has(e.key)) { moveFile(mappings.get(e.key)); } }