Skip to content

Commit 7a204e5

Browse files
committed
Robust-well-organized js handlers
Signed-off-by: Skye <[email protected]>
1 parent 3875de4 commit 7a204e5

File tree

11 files changed

+609
-200
lines changed

11 files changed

+609
-200
lines changed

fweb/core/static/css/styles.css

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

fweb/core/static/js/FileHandler.js

Lines changed: 108 additions & 119 deletions
Original file line numberDiff line numberDiff line change
@@ -1,140 +1,129 @@
1-
// File Drop Zone Functionality
2-
function setupFileDropZone(dropZoneId, inputId, multiple = true) {
3-
const dropZone = document.getElementById(dropZoneId);
4-
const fileInput = document.getElementById(inputId);
5-
6-
if (multiple) {
7-
fileInput.multiple = true;
1+
class FileHandler {
2+
constructor() {
3+
this.setupGlobalFileHandlers();
84
}
95

10-
dropZone.addEventListener("click", () => fileInput.click());
6+
setupFileDropZone(dropZoneId, inputId, multiple = true) {
7+
const dropZone = document.getElementById(dropZoneId);
8+
const fileInput = document.getElementById(inputId);
119

12-
dropZone.addEventListener("dragover", (e) => {
13-
e.preventDefault();
14-
dropZone.classList.add("dragover");
15-
});
10+
if (!dropZone || !fileInput) return;
1611

17-
dropZone.addEventListener("dragleave", () => {
18-
dropZone.classList.remove("dragover");
19-
});
12+
fileInput.multiple = multiple;
2013

21-
dropZone.addEventListener("drop", (e) => {
22-
e.preventDefault();
23-
dropZone.classList.remove("dragover");
24-
if (multiple) {
25-
fileInput.files = e.dataTransfer.files;
26-
} else {
27-
fileInput.files =
28-
e.dataTransfer.files.length > 0
29-
? [e.dataTransfer.files[0]]
30-
: new DataTransfer().files;
31-
}
32-
updateFileList(dropZoneId, fileInput.files);
33-
});
14+
// Click to select files
15+
dropZone.addEventListener("click", () => fileInput.click());
3416

35-
fileInput.addEventListener("change", () => {
36-
updateFileList(dropZoneId, fileInput.files);
37-
});
38-
}
17+
// Drag and drop handlers
18+
this.setupDragAndDrop(dropZone, fileInput, multiple);
3919

40-
function updateFileList(dropZoneId, files) {
41-
const dropZone = document.getElementById(dropZoneId);
42-
const fileList = dropZone.querySelector(".file-list");
43-
const placeholder = dropZone.querySelector(".drop-placeholder");
44-
45-
if (files.length > 0) {
46-
if (placeholder) placeholder.classList.add("hidden");
47-
fileList.innerHTML = "";
48-
49-
Array.from(files).forEach((file, index) => {
50-
const fileItem = document.createElement("div");
51-
fileItem.className =
52-
"flex items-center justify-between p-3 bg-gray-50 dark:bg-gray-700 rounded-lg mb-2";
53-
fileItem.innerHTML = `
54-
<div class="flex items-center flex-1 min-w-0">
55-
<i class="fas fa-file text-gray-400 mr-3 flex-shrink-0"></i>
56-
<span class="text-sm font-medium truncate">${file.name}</span>
57-
</div>
58-
<div class="flex items-center space-x-3 flex-shrink-0">
59-
<span class="text-xs text-gray-500 dark:text-gray-400">${(file.size / 1024 / 1024).toFixed(2)} MB</span>
60-
<button type="button" onclick="removeFile('${dropZoneId}', ${index})" class="text-red-500 hover:text-red-700">
61-
<i class="fas fa-times"></i>
62-
</button>
63-
</div>
64-
`;
65-
fileList.appendChild(fileItem);
20+
// File input change handler
21+
fileInput.addEventListener("change", () => {
22+
this.updateFileList(dropZoneId, fileInput.files);
6623
});
67-
} else {
68-
if (placeholder) placeholder.classList.remove("hidden");
69-
fileList.innerHTML = "";
7024
}
71-
}
7225

73-
function removeFile(dropZoneId, index) {
74-
const fileInput = document.querySelector(
75-
`#${dropZoneId.replace("-drop-zone", "-file-input")}`,
76-
);
77-
const dt = new DataTransfer();
26+
setupDragAndDrop(dropZone, fileInput, multiple) {
27+
dropZone.addEventListener("dragover", (e) => {
28+
e.preventDefault();
29+
dropZone.classList.add("dragover");
30+
});
31+
32+
dropZone.addEventListener("dragleave", () => {
33+
dropZone.classList.remove("dragover");
34+
});
35+
36+
dropZone.addEventListener("drop", (e) => {
37+
e.preventDefault();
38+
dropZone.classList.remove("dragover");
39+
40+
if (multiple) {
41+
fileInput.files = e.dataTransfer.files;
42+
} else {
43+
fileInput.files =
44+
e.dataTransfer.files.length > 0
45+
? [e.dataTransfer.files[0]]
46+
: new DataTransfer().files;
47+
}
48+
this.updateFileList(dropZone.id, fileInput.files);
49+
});
50+
}
7851

79-
Array.from(fileInput.files).forEach((file, i) => {
80-
if (i !== index) dt.items.add(file);
81-
});
52+
updateFileList(dropZoneId, files) {
53+
const dropZone = document.getElementById(dropZoneId);
54+
const fileList = dropZone.querySelector(".file-list");
55+
const placeholder = dropZone.querySelector(".drop-placeholder");
8256

83-
fileInput.files = dt.files;
84-
updateFileList(dropZoneId, fileInput.files);
85-
}
57+
if (files.length > 0) {
58+
if (placeholder) placeholder.classList.add("hidden");
59+
fileList.innerHTML = "";
8660

87-
// Tool Navigation
88-
function showTool(toolId, category) {
89-
// Hide all tool interfaces
90-
document.querySelectorAll(".tool-interface").forEach((interface) => {
91-
interface.classList.add("hidden");
92-
});
93-
94-
// Show selected tool
95-
const selectedTool = document.getElementById(`tool-${toolId}`);
96-
if (selectedTool) {
97-
selectedTool.classList.remove("hidden");
61+
Array.from(files).forEach((file, index) => {
62+
const fileItem = this.createFileItem(file, dropZoneId, index);
63+
fileList.appendChild(fileItem);
64+
});
65+
} else {
66+
if (placeholder) placeholder.classList.remove("hidden");
67+
fileList.innerHTML = "";
68+
}
9869
}
9970

100-
// Update active nav item
101-
document.querySelectorAll(".tool-nav").forEach((nav) => {
102-
nav.classList.remove("tool-active");
103-
});
104-
document.querySelector(`.tool-${toolId}`).classList.add("tool-active");
71+
createFileItem(file, dropZoneId, index) {
72+
const fileItem = document.createElement("div");
73+
fileItem.className =
74+
"flex items-center justify-between p-3 bg-gray-50 dark:bg-gray-700 rounded-lg mb-2";
75+
fileItem.innerHTML = `
76+
<div class="flex items-center flex-1 min-w-0">
77+
<i class="fas fa-file text-gray-400 mr-3 flex-shrink-0"></i>
78+
<span class="text-sm font-medium truncate">${file.name}</span>
79+
</div>
80+
<div class="flex items-center space-x-3 flex-shrink-0">
81+
<span class="text-xs text-gray-500 dark:text-gray-400">${(file.size / 1024 / 1024).toFixed(2)} MB</span>
82+
<button type="button" onclick="fileHandler.removeFile('${dropZoneId}', ${index})"
83+
class="text-red-500 hover:text-red-700 transition-colors">
84+
<i class="fas fa-times"></i>
85+
</button>
86+
</div>
87+
`;
88+
return fileItem;
89+
}
10590

106-
// Update URL without reload
107-
history.pushState(null, "", `?tool=${toolId}`);
91+
removeFile(dropZoneId, index) {
92+
const fileInput = document.querySelector(
93+
`#${dropZoneId.replace("-drop-zone", "-file-input")}`,
94+
);
95+
const dt = new DataTransfer();
10896

109-
// Initialize tool-specific functionality
110-
initializeTool(toolId);
111-
}
97+
Array.from(fileInput.files).forEach((file, i) => {
98+
if (i !== index) dt.items.add(file);
99+
});
112100

113-
function initializeTool(toolId) {
114-
// Tool-specific initialization
115-
const setupFunctions = {
116-
convert_doc: () =>
117-
setupFileDropZone("doc-drop-zone", "doc-file-input", true),
118-
convert_audio: () =>
119-
setupFileDropZone("audio-drop-zone", "audio-file-input", true),
120-
convert_video: () =>
121-
setupFileDropZone("video-drop-zone", "video-file-input", false),
122-
convert_image: () =>
123-
setupFileDropZone("image-drop-zone", "image-file-input", true),
124-
ocr: () => setupFileDropZone("ocr-drop-zone", "ocr-file-input", true),
125-
// Add more tool initializations
126-
};
127-
128-
if (setupFunctions[toolId]) {
129-
setupFunctions[toolId]();
101+
fileInput.files = dt.files;
102+
this.updateFileList(dropZoneId, fileInput.files);
130103
}
131-
}
132104

133-
// Load tool from URL parameter
134-
document.addEventListener("DOMContentLoaded", function () {
135-
const urlParams = new URLSearchParams(window.location.search);
136-
const toolParam = urlParams.get("tool");
137-
if (toolParam) {
138-
showTool(toolParam, "{{ category }}");
105+
setupGlobalFileHandlers() {
106+
// Add any global file-related event listeners here
139107
}
140-
});
108+
109+
validateFiles(files, allowedTypes = []) {
110+
if (files.length === 0) return { valid: false, error: "No files selected" };
111+
112+
if (allowedTypes.length > 0) {
113+
for (let file of files) {
114+
const extension = file.name.split(".").pop().toLowerCase();
115+
if (!allowedTypes.includes(extension)) {
116+
return {
117+
valid: false,
118+
error: `File type .${extension} is not allowed`,
119+
};
120+
}
121+
}
122+
}
123+
124+
return { valid: true };
125+
}
126+
}
127+
128+
// Initialize global file handler
129+
window.fileHandler = new FileHandler();

0 commit comments

Comments
 (0)