added docker deployment and added a preview to the files.
This commit is contained in:
29
.dockerignore
Normal file
29
.dockerignore
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
# ---> Java
|
||||||
|
# Compiled class file
|
||||||
|
*.class
|
||||||
|
|
||||||
|
# Log file
|
||||||
|
*.log
|
||||||
|
|
||||||
|
# BlueJ files
|
||||||
|
*.ctxt
|
||||||
|
|
||||||
|
# Mobile Tools for Java (J2ME)
|
||||||
|
.mtj.tmp/
|
||||||
|
|
||||||
|
# Package Files #
|
||||||
|
*.jar
|
||||||
|
*.war
|
||||||
|
*.nar
|
||||||
|
*.ear
|
||||||
|
*.zip
|
||||||
|
*.tar.gz
|
||||||
|
*.rar
|
||||||
|
|
||||||
|
# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
|
||||||
|
hs_err_pid*
|
||||||
|
replay_pid*
|
||||||
|
|
||||||
|
target
|
||||||
|
|
||||||
|
|
||||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -27,4 +27,5 @@ replay_pid*
|
|||||||
target
|
target
|
||||||
|
|
||||||
*.env
|
*.env
|
||||||
|
.idea
|
||||||
|
|
||||||
|
|||||||
9
dockerfile
Normal file
9
dockerfile
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
#fjet dockerfile
|
||||||
|
FROM eclipse-temurin:17-jdk
|
||||||
|
WORKDIR /app
|
||||||
|
COPY pom.xml mvnw* .mvn/ ./
|
||||||
|
COPY src ./src
|
||||||
|
RUN ./mvnw clean package -DskipTests
|
||||||
|
RUN cp target/*.jar fjet.jar
|
||||||
|
EXPOSE 8080
|
||||||
|
CMD ["java", "-jar", "fjet.jar"]
|
||||||
@@ -15,6 +15,10 @@ public class Server {
|
|||||||
app.get("/api/download", DownloadRoute::handle); // for ?path=... downloads
|
app.get("/api/download", DownloadRoute::handle); // for ?path=... downloads
|
||||||
app.get("/api/download/{file}", DownloadRoute::handle); // legacy/compat
|
app.get("/api/download/{file}", DownloadRoute::handle); // legacy/compat
|
||||||
app.get("/api/list-isos", DownloadRoute::list);
|
app.get("/api/list-isos", DownloadRoute::list);
|
||||||
|
app.get("/api/view", DownloadRoute::view); // for file preview
|
||||||
|
|
||||||
|
// New endpoint for directory version/hash
|
||||||
|
app.get("/api/dir-version", DownloadRoute::dirVersion);
|
||||||
|
|
||||||
app.before("/api/*", ctx -> {
|
app.before("/api/*", ctx -> {
|
||||||
String ua = ctx.header("User-Agent");
|
String ua = ctx.header("User-Agent");
|
||||||
|
|||||||
@@ -30,11 +30,6 @@ public class DownloadRoute {
|
|||||||
if (resource == null) {
|
if (resource == null) {
|
||||||
ctx.status(404).json(new String[]{"Directory not found"});
|
ctx.status(404).json(new String[]{"Directory not found"});
|
||||||
return;
|
return;
|
||||||
// Helper: construct the full resource path from isoDir and relPath
|
|
||||||
private static String getFullPath(String relPath) {
|
|
||||||
String basePath = isoDir.endsWith("/") ? isoDir : isoDir + "/";
|
|
||||||
return basePath + (relPath.startsWith("/") ? relPath.substring(1) : relPath);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
File dir = new File(resource.toURI());
|
File dir = new File(resource.toURI());
|
||||||
if (!dir.exists() || !dir.isDirectory()) {
|
if (!dir.exists() || !dir.isDirectory()) {
|
||||||
@@ -71,6 +66,12 @@ public class DownloadRoute {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Helper: construct the full resource path from isoDir and relPath
|
||||||
|
private static String getFullPath(String relPath) {
|
||||||
|
String basePath = isoDir.endsWith("/") ? isoDir : isoDir + "/";
|
||||||
|
return basePath + (relPath.startsWith("/") ? relPath.substring(1) : relPath);
|
||||||
|
}
|
||||||
|
|
||||||
// Helper: recursively search for files/folders containing searchTerm, add to result with relative path
|
// Helper: recursively search for files/folders containing searchTerm, add to result with relative path
|
||||||
private static void recursiveSearch(File dir, String relPath, String searchTerm, List<Map<String, Object>> result) {
|
private static void recursiveSearch(File dir, String relPath, String searchTerm, List<Map<String, Object>> result) {
|
||||||
File[] files = dir.listFiles();
|
File[] files = dir.listFiles();
|
||||||
@@ -147,4 +148,85 @@ public class DownloadRoute {
|
|||||||
ctx.status(500).result("Error: " + e.getMessage());
|
ctx.status(500).result("Error: " + e.getMessage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// TODO: Fix the refreshing of the files. it should be done
|
||||||
|
// but fucking isnt. idk what the fuck is going on
|
||||||
|
|
||||||
|
// Serve text/code file content for preview
|
||||||
|
public static void view(Context ctx) {
|
||||||
|
String relPath = ctx.queryParam("path");
|
||||||
|
if (relPath == null || relPath.isEmpty()) {
|
||||||
|
ctx.status(400).result("Missing file path");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
var classLoader = Thread.currentThread().getContextClassLoader();
|
||||||
|
String fullPath = getFullPath(relPath);
|
||||||
|
var resource = classLoader.getResource(fullPath);
|
||||||
|
if (resource == null) {
|
||||||
|
ctx.status(404).result("Not Found!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
File file = new File(resource.toURI());
|
||||||
|
if (!file.exists() || !file.isFile()) {
|
||||||
|
ctx.status(404).result("Not Found!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Only allow preview for small files (e.g. <1MB)
|
||||||
|
if (file.length() > 1024 * 1024) {
|
||||||
|
ctx.status(413).result("File too large to preview");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Try to detect text
|
||||||
|
String mime = ctx.header("Accept");
|
||||||
|
if (mime == null || mime.isEmpty()) mime = "text/plain";
|
||||||
|
ctx.res().setContentType(mime);
|
||||||
|
try (InputStream is = new FileInputStream(file)) {
|
||||||
|
String text = new String(is.readAllBytes(), java.nio.charset.StandardCharsets.UTF_8);
|
||||||
|
ctx.result(text);
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
ctx.status(500).result("Error: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Returns a hash/version of the directory contents for change detection
|
||||||
|
public static void dirVersion(io.javalin.http.Context ctx) {
|
||||||
|
String relPath = ctx.queryParam("path");
|
||||||
|
if (relPath == null || relPath.isEmpty()) relPath = "/";
|
||||||
|
try {
|
||||||
|
var classLoader = Thread.currentThread().getContextClassLoader();
|
||||||
|
String fullPath = getFullPath(relPath);
|
||||||
|
var resource = classLoader.getResource(fullPath);
|
||||||
|
if (resource == null) {
|
||||||
|
ctx.status(404).result("Directory not found");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
File dir = new File(resource.toURI());
|
||||||
|
if (!dir.exists() || !dir.isDirectory()) {
|
||||||
|
ctx.status(404).result("Not a directory");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Compute a hash based on file names and last modified times
|
||||||
|
java.security.MessageDigest md = java.security.MessageDigest.getInstance("MD5");
|
||||||
|
File[] files = dir.listFiles();
|
||||||
|
if (files != null) {
|
||||||
|
for (File f : files) {
|
||||||
|
md.update(f.getName().getBytes());
|
||||||
|
md.update(Long.toString(f.lastModified()).getBytes());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
String hash = bytesToHex(md.digest());
|
||||||
|
ctx.json(java.util.Collections.singletonMap("version", hash));
|
||||||
|
} catch (Exception e) {
|
||||||
|
ctx.status(500).result("Error: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper: convert byte array to hex string (replacement for DatatypeConverter.printHexBinary)
|
||||||
|
private static String bytesToHex(byte[] bytes) {
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
for (byte b : bytes) {
|
||||||
|
sb.append(String.format("%02X", b));
|
||||||
|
}
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
10
src/main/resources/HTML/index.html
vendored
10
src/main/resources/HTML/index.html
vendored
@@ -21,6 +21,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
<div class="main-content">
|
<div class="main-content">
|
||||||
<div class="file-header">
|
<div class="file-header">
|
||||||
<div></div>
|
<div></div>
|
||||||
@@ -30,10 +31,17 @@
|
|||||||
<div>MODIFIED</div>
|
<div>MODIFIED</div>
|
||||||
<div>ACTION</div>
|
<div>ACTION</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="file-list" class="file-list"></div>
|
<div id="file-list" class="file-list"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- File preview modal -->
|
||||||
|
<div id="preview-modal" style="display:none; position:fixed; top:0; left:0; width:100vw; height:100vh; background:rgba(0,0,0,0.85); z-index:1000; align-items:center; justify-content:center;">
|
||||||
|
<div style="background:#181818; color:#eee; max-width:80vw; max-height:80vh; min-width:320px; min-height:120px; border-radius:8px; box-shadow:0 2px 16px #000; padding:24px; position:relative; display:flex; flex-direction:column;">
|
||||||
|
<button id="close-preview" style="position:absolute; top:8px; right:12px; background:#222; color:#fff; border:none; font-size:18px; cursor:pointer;">×</button>
|
||||||
|
<pre id="preview-content" style="overflow:auto; font-family:monospace; font-size:15px; background:none; color:inherit; margin:0; white-space:pre-wrap;"></pre>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="status-bar">
|
<div class="status-bar">
|
||||||
<div id="status-text">Ready</div>
|
<div id="status-text">Ready</div>
|
||||||
<div class="help-text">
|
<div class="help-text">
|
||||||
|
|||||||
123
src/main/resources/HTML/script.js
vendored
123
src/main/resources/HTML/script.js
vendored
@@ -57,6 +57,11 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
forwardStack = [];
|
forwardStack = [];
|
||||||
selectedIndex = -1;
|
selectedIndex = -1;
|
||||||
fetchFiles();
|
fetchFiles();
|
||||||
|
} else if (isPreviewable(item.name)) {
|
||||||
|
// Preview file
|
||||||
|
let cur = getCurrentPath();
|
||||||
|
let filePath = cur.endsWith('/') ? cur + item.name : cur + '/' + item.name;
|
||||||
|
showPreview(filePath, item.name);
|
||||||
} else {
|
} else {
|
||||||
// Download file
|
// Download file
|
||||||
let cur = getCurrentPath();
|
let cur = getCurrentPath();
|
||||||
@@ -64,9 +69,73 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
window.location.href = `/api/download?path=${encodeURIComponent(filePath)}`;
|
window.location.href = `/api/download?path=${encodeURIComponent(filePath)}`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Returns true if file is previewable (text, md, code, etc)
|
||||||
|
function isPreviewable(name) {
|
||||||
|
return /\.(txt|md|js|ts|json|css|html?|java|py|c|cpp|h|xml|sh|bat|ini|conf|log|csv|yml|yaml|go|rs|php|rb|pl|swift|kt|scala|cs|sql)$/i.test(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
function fetchFiles(searchTerm = '') {
|
// Show preview modal and fetch file content
|
||||||
|
function showPreview(filePath, fileName) {
|
||||||
|
const modal = document.getElementById('preview-modal');
|
||||||
|
const content = document.getElementById('preview-content');
|
||||||
|
modal.style.display = 'flex';
|
||||||
|
content.textContent = 'Loading...';
|
||||||
|
fetch(`/api/view?path=${encodeURIComponent(filePath)}`)
|
||||||
|
.then(res => {
|
||||||
|
if (!res.ok) throw new Error('Failed to load file');
|
||||||
|
return res.text();
|
||||||
|
})
|
||||||
|
.then(text => {
|
||||||
|
content.textContent = text;
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
content.textContent = 'Error: ' + err.message;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close preview modal
|
||||||
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
|
const modal = document.getElementById('preview-modal');
|
||||||
|
const closeBtn = document.getElementById('close-preview');
|
||||||
|
if (closeBtn) {
|
||||||
|
closeBtn.onclick = () => {
|
||||||
|
modal.style.display = 'none';
|
||||||
|
};
|
||||||
|
}
|
||||||
|
modal.onclick = (e) => {
|
||||||
|
if (e.target === modal) modal.style.display = 'none';
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns true if file is previewable (text, md, code, etc)
|
||||||
|
function isPreviewable(name) {
|
||||||
|
return /\.(txt|md|js|ts|json|css|html?|java|py|c|cpp|h|xml|sh|bat|ini|conf|log|csv|yml|yaml|go|rs|php|rb|pl|swift|kt|scala|cs|sql|java|cpp|c|py|rs|toml)$/i.test(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show preview modal and fetch file content
|
||||||
|
function showPreview(filePath, fileName) {
|
||||||
|
const modal = document.getElementById('preview-modal');
|
||||||
|
const content = document.getElementById('preview-content');
|
||||||
|
modal.style.display = 'flex';
|
||||||
|
content.textContent = 'Loading...';
|
||||||
|
fetch(`/api/view?path=${encodeURIComponent(filePath)}`)
|
||||||
|
.then(res => {
|
||||||
|
if (!res.ok) throw new Error('Failed to load file');
|
||||||
|
return res.text();
|
||||||
|
})
|
||||||
|
.then(text => {
|
||||||
|
content.textContent = text;
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
content.textContent = 'Error: ' + err.message;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
let lastDirVersion = null;
|
||||||
|
let dirVersionPoller = null;
|
||||||
|
|
||||||
|
function fetchFiles(searchTerm = '', skipVersionCheck = false) {
|
||||||
setAddressBar();
|
setAddressBar();
|
||||||
statusText.textContent = 'Loading...';
|
statusText.textContent = 'Loading...';
|
||||||
let path = getCurrentPath();
|
let path = getCurrentPath();
|
||||||
@@ -93,14 +162,12 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
const div = document.createElement('div');
|
const div = document.createElement('div');
|
||||||
div.className = 'file-item';
|
div.className = 'file-item';
|
||||||
div.dataset.index = index;
|
div.dataset.index = index;
|
||||||
|
|
||||||
// Icon
|
// Icon
|
||||||
const icon = document.createElement('img');
|
const icon = document.createElement('img');
|
||||||
icon.className = 'icon';
|
icon.className = 'icon';
|
||||||
icon.src = item.isDir ? 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTYiIGhlaWdodD0iMTYiIHZpZXdCb3g9IjAgMCAxNiAxNiIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZD0iTTIgM0MxLjQ0NzcyIDMgMSAzLjQ0NzcyIDEgNFYxMkMxIDEyLjU1MjMgMS40NDc3MiAxMyAyIDEzSDE0QzE0LjU1MjMgMTMgMTUgMTIuNTUyMyAxNSAxMlY2QzE1IDUuNDQ3NzIgMTQuNTUyMyA1IDE0IDVIOEw2IDNIMloiIGZpbGw9IiNGRkQ3MDAiLz4KPC9zdmc+' : 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTYiIGhlaWdodD0iMTYiIHZpZXdCb3g9IjAgMCAxNiAxNiIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZD0iTTMgM0MyLjQ0NzcyIDMgMiAzLjQ0NzcyIDIgNFYxMkMyIDEyLjU1MjMgMi40NDc3MiAxMyAzIDEzSDEzQzEzLjU1MjMgMTMgMTQgMTIuNTUyMyAxNCAxMlY0QzE0IDMuNDQ3NzIgMTMuNTUyMyAzIDEzIDNIM1oiIGZpbGw9IiM4ODg4ODgiLz4KPC9zdmc+';
|
icon.src = item.isDir ? 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTYiIGhlaWdodD0iMTYiIHZpZXdCb3g9IjAgMCAxNiAxNiIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZD0iTTIgM0MxLjQ0NzcyIDMgMSAzLjQ0NzcyIDEgNFYxMkMxIDEyLjU1MjMgMS40NDc3MiAxMyAyIDEzSDE0QzE0LjU1MjMgMTMgMTUgMTIuNTUyMyAxNSAxMlY2QzE1IDUuNDQ3NzIgMTQuNTUyMyA1IDE0IDVIOEw2IDNIMloiIGZpbGw9IiNGRkQ3MDAiLz4KPC9zdmc+' : 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTYiIGhlaWdodD0iMTYiIHZpZXdCb3g9IjAgMCAxNiAxNiIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZD0iTTMgM0MyLjQ0NzcyIDMgMiAzLjQ0NzcyIDIgNFYxMkMyIDEyLjU1MjMgMi40NDc3MiAxMyAzIDEzSDEzQzEzLjU1MjMgMTMgMTQgMTIuNTUyMyAxNCAxMlY0QzE0IDMuNDQ3NzIgMTMuNTUyMyAzIDEzIDNIM1oiIGZpbGw9IiM4ODg4ODgiLz4KPC9zdmc+';
|
||||||
icon.onerror = () => icon.style.display = 'none';
|
icon.onerror = () => icon.style.display = 'none';
|
||||||
div.appendChild(icon);
|
div.appendChild(icon);
|
||||||
|
|
||||||
// Name (show relative path if searching)
|
// Name (show relative path if searching)
|
||||||
const nameSpan = document.createElement('span');
|
const nameSpan = document.createElement('span');
|
||||||
nameSpan.className = 'file-name';
|
nameSpan.className = 'file-name';
|
||||||
@@ -110,25 +177,21 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
nameSpan.textContent = item.name;
|
nameSpan.textContent = item.name;
|
||||||
}
|
}
|
||||||
div.appendChild(nameSpan);
|
div.appendChild(nameSpan);
|
||||||
|
|
||||||
// Type
|
// Type
|
||||||
const typeSpan = document.createElement('span');
|
const typeSpan = document.createElement('span');
|
||||||
typeSpan.className = 'file-type';
|
typeSpan.className = 'file-type';
|
||||||
typeSpan.textContent = item.isDir ? 'Folder' : 'File';
|
typeSpan.textContent = item.isDir ? 'Folder' : 'File';
|
||||||
div.appendChild(typeSpan);
|
div.appendChild(typeSpan);
|
||||||
|
|
||||||
// Size
|
// Size
|
||||||
const sizeSpan = document.createElement('span');
|
const sizeSpan = document.createElement('span');
|
||||||
sizeSpan.className = 'file-size';
|
sizeSpan.className = 'file-size';
|
||||||
sizeSpan.textContent = item.isDir ? '' : '-';
|
sizeSpan.textContent = item.isDir ? '' : '-';
|
||||||
div.appendChild(sizeSpan);
|
div.appendChild(sizeSpan);
|
||||||
|
|
||||||
// Date Modified
|
// Date Modified
|
||||||
const dateSpan = document.createElement('span');
|
const dateSpan = document.createElement('span');
|
||||||
dateSpan.className = 'file-date';
|
dateSpan.className = 'file-date';
|
||||||
dateSpan.textContent = '';
|
dateSpan.textContent = '';
|
||||||
div.appendChild(dateSpan);
|
div.appendChild(dateSpan);
|
||||||
|
|
||||||
// Action
|
// Action
|
||||||
const actionDiv = document.createElement('div');
|
const actionDiv = document.createElement('div');
|
||||||
if (!item.isDir) {
|
if (!item.isDir) {
|
||||||
@@ -144,20 +207,21 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
actionDiv.appendChild(dlBtn);
|
actionDiv.appendChild(dlBtn);
|
||||||
}
|
}
|
||||||
div.appendChild(actionDiv);
|
div.appendChild(actionDiv);
|
||||||
|
|
||||||
// Click handlers
|
// Click handlers
|
||||||
div.onclick = () => {
|
div.onclick = (e) => {
|
||||||
selectItem(index);
|
selectItem(index);
|
||||||
if (item.isDir) {
|
if (item.isDir) {
|
||||||
setTimeout(() => openSelectedItem(), 200);
|
openSelectedItem();
|
||||||
|
} else if (isPreviewable(item.name)) {
|
||||||
|
// Open preview immediately on single click
|
||||||
|
let cur = getCurrentPath();
|
||||||
|
let filePath = cur.endsWith('/') ? cur + item.name : cur + '/' + item.name;
|
||||||
|
showPreview(filePath, item.name);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
fileList.appendChild(div);
|
fileList.appendChild(div);
|
||||||
});
|
});
|
||||||
|
|
||||||
statusText.textContent = `${items.length} item${items.length !== 1 ? 's' : ''}`;
|
statusText.textContent = `${items.length} item${items.length !== 1 ? 's' : ''}`;
|
||||||
|
|
||||||
// Update button states
|
// Update button states
|
||||||
backBtn.disabled = historyIndex <= 0;
|
backBtn.disabled = historyIndex <= 0;
|
||||||
forwardBtn.disabled = historyIndex >= history.length - 1;
|
forwardBtn.disabled = historyIndex >= history.length - 1;
|
||||||
@@ -167,6 +231,39 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
fileList.innerHTML = `<div class="file-item" style="grid-column: 1 / -1; text-align: center; color: #d9534f;">${err.message}</div>`;
|
fileList.innerHTML = `<div class="file-item" style="grid-column: 1 / -1; text-align: center; color: #d9534f;">${err.message}</div>`;
|
||||||
statusText.textContent = 'Error loading files';
|
statusText.textContent = 'Error loading files';
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Start polling for directory version if not already started
|
||||||
|
if (!skipVersionCheck && !dirVersionPoller) {
|
||||||
|
startDirVersionPolling();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function startDirVersionPolling() {
|
||||||
|
if (dirVersionPoller) clearInterval(dirVersionPoller);
|
||||||
|
let path = getCurrentPath();
|
||||||
|
function poll() {
|
||||||
|
fetch(`/api/dir-version?path=${encodeURIComponent(path)}`)
|
||||||
|
.then(res => res.ok ? res.json() : null)
|
||||||
|
.then(data => {
|
||||||
|
if (!data || !data.version) return;
|
||||||
|
if (lastDirVersion && data.version !== lastDirVersion) {
|
||||||
|
// Directory changed, refresh file list
|
||||||
|
fetchFiles('', true); // skipVersionCheck=true to avoid infinite loop
|
||||||
|
}
|
||||||
|
lastDirVersion = data.version;
|
||||||
|
})
|
||||||
|
.catch(() => {});
|
||||||
|
}
|
||||||
|
poll();
|
||||||
|
dirVersionPoller = setInterval(poll, 3000); // poll every 3 seconds
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stop polling when navigating away
|
||||||
|
function stopDirVersionPolling() {
|
||||||
|
if (dirVersionPoller) {
|
||||||
|
clearInterval(dirVersionPoller);
|
||||||
|
dirVersionPoller = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Navigation event handlers
|
// Navigation event handlers
|
||||||
|
|||||||
Reference in New Issue
Block a user