359 lines
10 KiB
JavaScript
359 lines
10 KiB
JavaScript
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 splitFilePathRe = /(.*\/)?([^/]*?)(\.[^/.]+)?$/;
|
|
|
|
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 = `<span class="key">${key}</span> <span class="directory">${directory}</span>`;
|
|
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) {
|
|
// wrap the file in link to the full resolution version served at images-full
|
|
let a = document.createElement("a");
|
|
a.href = stagingPath.replace("images/", "images-full/") + currentFile;
|
|
a.target = "_blank";
|
|
let element = createMediaFileElement(currentFile, true);
|
|
a.appendChild(element);
|
|
currentFileDiv.appendChild(a);
|
|
} 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) {
|
|
// }
|
|
// st:
|
|
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(splitFilePathRe, "$2");
|
|
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));
|
|
}
|
|
}
|