(function(){
const FileManager = {
id: "fileManager",
label: "📁 File Manager",
html: `
<div class="fm-container">
<div class="fm-toolbar">
<div class="fm-left" data-toolbar></div>
<span id="fmStatus" class="fm-status">Loading...</span>
</div>
<div id="fmList" class="fm-list"></div>
</div>
`,
toolbar: [
{ id: "up", label: "⬅️ Up", action: "goUpDirectory" },
{ id: "refresh", label: "🔄 Refresh", action: "reload" }
],
currentPath: "/",
async onRender(el) {
this.el = el;
this.renderToolbar();
await this.loadFiles("/");
},
renderToolbar() {
const bar = this.el.querySelector("[data-toolbar]");
bar.innerHTML = "";
this.toolbar.forEach(btn => {
const el = document.createElement("button");
el.className = "fm-btn";
el.textContent = btn.label;
el.addEventListener("click", () => this.runAction(btn.action));
bar.appendChild(el);
});
},
runAction(action) {
if (FileManagerActions[action]) FileManagerActions[action](this);
},
async loadFiles(path="/") {
const fmList = this.el.querySelector("#fmList");
const fmStatus = this.el.querySelector("#fmStatus");
fmList.innerHTML = `<p style="color:#94a3b8;">Loading ${path}...</p>`;
fmStatus.textContent = `Loading ${path}...`;
this.currentPath = path;
try {
const res = await fetch("SFTPconnector.php", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ action: "list", path })
});
const text = await res.text();
let data;
try {
data = JSON.parse(text);
} catch {
throw new Error("Server returned non-JSON:\n" + text);
}
if (!data.success) throw new Error(data.message);
this.renderFiles(data.data);
fmStatus.textContent = `Path: ${path} (${data.data.length} items)`;
} catch (err) {
fmList.innerHTML = `<p style="color:#ef4444;">${err.message}</p>`;
fmStatus.textContent = "Error loading files";
}
},
renderFiles(files) {
const fmList = this.el.querySelector("#fmList");
fmList.innerHTML = "";
if (!files || files.length === 0) {
fmList.innerHTML = `<p>No files found.</p>`;
return;
}
files.forEach(f => {
const div = document.createElement("div");
div.className = "fm-item";
div.innerHTML = `
<div class="fm-name">${f.is_dir ? "📁" : "📄"} ${f.name}</div>
<div class="fm-meta">${f.is_dir ? "Folder" : this.formatBytes(f.size)} • ${f.modified}</div>
`;
// Folder click = open next directory
if (f.is_dir) {
div.querySelector(".fm-name").addEventListener("click", () => {
const newPath = this.currentPath.endsWith("/")
? this.currentPath + f.name
: this.currentPath + "/" + f.name;
this.loadFiles(newPath);
});
}
fmList.appendChild(div);
});
},
formatBytes(bytes) {
if (!bytes) return "";
if (bytes < 1024) return bytes + " B";
const units = ["KB", "MB", "GB", "TB"];
let u = -1;
do { bytes /= 1024; ++u; } while (bytes >= 1024 && u < units.length - 1);
return bytes.toFixed(1) + " " + units[u];
}
};
const FileManagerActions = {
reload: ctx => ctx.loadFiles(ctx.currentPath),
goUpDirectory: ctx => {
const parts = ctx.currentPath.split("/").filter(Boolean);
parts.pop();
const newPath = "/" + parts.join("/");
ctx.loadFiles(newPath || "/");
}
};
window.AppItems = window.AppItems || [];
window.AppItems.push(FileManager);
})();