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));
}
}