From 4c34d7eda58530eddb2c8b3479627180d6eeb248 Mon Sep 17 00:00:00 2001 From: retoor Date: Fri, 9 May 2025 14:30:53 +0200 Subject: [PATCH] New stuff. --- src/snek/static/file-manager.css | 41 ++++++++++++ src/snek/static/file-manager.js | 100 +++++++++++++++++++++++++++++ src/snek/templates/repository.html | 82 +++++++++++++++++++++++ src/snek/view/repository.py | 15 +++++ 4 files changed, 238 insertions(+) create mode 100644 src/snek/static/file-manager.css create mode 100644 src/snek/static/file-manager.js create mode 100644 src/snek/templates/repository.html create mode 100644 src/snek/view/repository.py diff --git a/src/snek/static/file-manager.css b/src/snek/static/file-manager.css new file mode 100644 index 0000000..89b3eec --- /dev/null +++ b/src/snek/static/file-manager.css @@ -0,0 +1,41 @@ + .file-manager { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); + gap: 16px; + padding: 20px; + background: #111; + color: #ddd; + font-family: Arial, sans-serif; + max-width: 800px; + margin: 0 auto; + border-radius: 8px; + } + .file-tile { + background-color: #1a1a1a; + border: 1px solid #333; + border-radius: 8px; + overflow: hidden; + text-align: center; + padding: 10px; + transition: transform 0.2s; + } + .file-tile:hover { + transform: translateY(-5px); + } + .file-icon { + font-size: 40px; + margin-bottom: 10px; + color: #888; + } + .file-name { + font-size: 14px; + overflow-wrap: break-word; + } + .file-tile img { + max-width: 80%; + height: auto; + margin-bottom: 10px; + border-radius: 4px; + } + + diff --git a/src/snek/static/file-manager.js b/src/snek/static/file-manager.js new file mode 100644 index 0000000..55dbac6 --- /dev/null +++ b/src/snek/static/file-manager.js @@ -0,0 +1,100 @@ +/* A  custom element that talks to /api/files */ +class FileBrowser extends HTMLElement { + constructor() { + super(); + this.attachShadow({ mode: "open" }); + this.path = ""; // current virtual path ("" = ROOT) + this.offset = 0; // pagination offset + this.limit = 40; // items per request + } + + connectedCallback() { + this.renderShell(); + this.load(); + } + + // ---------- UI scaffolding ------------------------------------------- + renderShell() { + this.shadowRoot.innerHTML = ` + + + +
+ + `; + this.shadowRoot.getElementById("up").addEventListener("click", () => this.goUp()); + this.shadowRoot.getElementById("prev").addEventListener("click", () => { + if (this.offset > 0) { this.offset -= this.limit; this.load(); } + }); + this.shadowRoot.getElementById("next").addEventListener("click", () => { + this.offset += this.limit; this.load(); + }); + } + + // ---------- Networking ---------------------------------------------- + async load() { + const r = await fetch(`/drive.json?path=${encodeURIComponent(this.path)}&offset=${this.offset}&limit=${this.limit}`); + if (!r.ok) { console.error(await r.text()); return; } + const data = await r.json(); + this.renderTiles(data.items); + this.updateNav(data.pagination); + } + + // ---------- Rendering ------------------------------------------------- + renderTiles(items) { + const grid = this.shadowRoot.getElementById("grid"); + grid.innerHTML = ""; + items.forEach(item => { + const tile = document.createElement("div"); + tile.className = "tile"; + + if (item.type === "directory") { + tile.innerHTML = `
📂
${item.name}
`; + tile.addEventListener("click", () => { this.path = item.path; this.offset = 0; this.load(); }); + } else { + if (item.mimetype?.startsWith("image/")) { + tile.innerHTML = `${item.name}
${item.name}
`; + } else { + tile.innerHTML = `
📄
${item.name}
`; + } + tile.addEventListener("click", () => window.open(item.url, "_blank")); + } + + grid.appendChild(tile); + }); + } + + // ---------- Navigation + pagination ---------------------------------- + updateNav({ offset, limit, total }) { + this.shadowRoot.getElementById("crumb").textContent = `/${this.path}`; + this.shadowRoot.getElementById("prev").disabled = offset === 0; + this.shadowRoot.getElementById("next").disabled = offset + limit >= total; + this.shadowRoot.getElementById("up").disabled = this.path === ""; + } + + goUp() { + if (!this.path) return; + this.path = this.path.split("/").slice(0, -1).join("/"); + this.offset = 0; + this.load(); + } +} + +customElements.define("file-manager", FileBrowser); diff --git a/src/snek/templates/repository.html b/src/snek/templates/repository.html new file mode 100644 index 0000000..0052dc5 --- /dev/null +++ b/src/snek/templates/repository.html @@ -0,0 +1,82 @@ +{% extends "app.html" %} +{% block header_text %}{{rel_path}}{% endblock %} + +{% block main %} + + + + + {% endblock %} + diff --git a/src/snek/view/repository.py b/src/snek/view/repository.py new file mode 100644 index 0000000..f7a2e9d --- /dev/null +++ b/src/snek/view/repository.py @@ -0,0 +1,15 @@ +from snek.system.view import BaseView +from aiohttp import web +class RepositoryView(BaseView): + async def get(A): + G='type';H='name';I='.git';J='username';B=A.request.match_info[J];K=A.request.match_info['repo_name'];C=A.request.match_info.get('rel_path','') + if not B.count('-')==4:E=await A.services.user.get_by_username(B) + else:E=await A.services.user.get(B) + if not E:return web.HTTPNotFound() + B=E[J];M=await A.services.user.get_repository_path(E['uid']) + if C.endswith(I):C=C[:-4] + L=M.joinpath(K+I) + if not L.exists():return web.HTTPNotFound() + import os;from git import Repo;N=Repo(L.joinpath(C));F=[];O=[];P=N.head.commit + for D in P.tree.traverse():F.append({H:D.name,'mode':D.mode,G:D.type,'path':D.path,'size':D.size}) + sorted(F,key=lambda x:x[H]);sorted(F,key=lambda x:x[G],reverse=True);Q=f"{B}/{C}"[:-4];return await A.render_template('repository.html',dict(username=B,repo_name=K,rel_path=C,full_path=Q,files=F,directories=O)) \ No newline at end of file