Compare commits

...

4 Commits

Author SHA1 Message Date
jaina heartles 951bb90ad8 Fix link href for upload button 2022-12-17 12:10:17 -08:00
jaina heartles f35d52f287 Basic file details page 2022-12-17 12:05:17 -08:00
jaina heartles 71a03b30f0 Allow printing optionals 2022-12-17 12:05:08 -08:00
jaina heartles af396a0cb6 Basic upload form 2022-12-17 07:46:54 -08:00
4 changed files with 139 additions and 2 deletions

View File

@ -233,6 +233,7 @@ const user_details = struct {
const drive = struct {
const dir_tmpl = @embedFile("./web/drive/directory.tmpl.html");
const file_tmpl = @embedFile("./web/drive/file.tmpl.html");
fn servePage(req: anytype, res: anytype, srv: anytype) !void {
const info = try srv.driveGet(req.args.path);
defer util.deepFree(srv.allocator, info);
@ -246,6 +247,14 @@ const drive = struct {
try breadcrumbs.append(if (p.len != 0) p else continue);
}
// TODO: put this into the db layer
const FileClass = enum {
image,
video,
audio,
other,
};
switch (info) {
.dir => |dir| try res.template(.ok, srv, dir_tmpl, .{
.dir = dir,
@ -253,7 +262,16 @@ const drive = struct {
.mount_path = req.mount_path,
.base_drive_path = "drive",
}),
else => unreachable,
.file => |file| try res.template(.ok, srv, file_tmpl, .{
.file = file,
.breadcrumbs = breadcrumbs.items,
.mount_path = req.mount_path,
.base_drive_path = "drive",
.class = if (std.mem.eql(u8, file.meta.content_type orelse "", "image/jpeg"))
FileClass.image
else
FileClass.other,
}),
}
}
@ -281,6 +299,7 @@ const drive = struct {
const Action = enum {
mkdir,
delete,
upload,
};
pub const body_tag_from_query_param = "action";
@ -289,9 +308,15 @@ const drive = struct {
name: []const u8,
},
delete: struct {},
upload: struct {
file: http.FormFile,
sensitive: bool = false,
description: []const u8 = "",
},
};
pub fn handler(req: anytype, res: anytype, srv: anytype) !void {
const trimmed_path = std.mem.trim(u8, req.args.path, "/");
switch (req.body) {
.mkdir => |body| {
_ = try srv.driveMkdir(req.args.path, body.name);
@ -300,7 +325,6 @@ const drive = struct {
try servePage(req, res, srv);
},
.delete => {
const trimmed_path = std.mem.trim(u8, req.args.path, "/");
_ = try srv.driveDelete(trimmed_path);
const dir = trimmed_path[0 .. std.mem.lastIndexOfScalar(u8, trimmed_path, '/') orelse trimmed_path.len];
@ -312,6 +336,27 @@ const drive = struct {
try res.headers.put("Location", url);
return res.status(.see_other);
},
.upload => |body| {
const entry = try srv.driveUpload(
.{
.filename = body.file.filename,
.dir = trimmed_path,
.description = body.description,
.content_type = body.file.content_type,
.sensitive = body.sensitive,
},
body.file.data,
);
defer util.deepFree(srv.allocator, entry);
const url = try std.fmt.allocPrint(srv.allocator, "{s}/drive/{s}", .{
req.mount_path,
std.mem.trim(u8, entry.file.path, "/"),
});
defer srv.allocator.free(url);
try res.headers.put("Location", url);
return res.status(.see_other);
},
}
}
};

View File

@ -37,6 +37,30 @@
<button type="submit">Create</button>
</form>
</div>
<div class="popup" id="upload">
<a class="button popup-open" href="#upload">
<i class="fa-solid fa-cloud-arrow-up"></i>
</a>
<a class="button popup-close" href="#">
<i class="fa-solid fa-xmark"></i>
</a>
<form class="popup-dialog" action="?action=upload" method="post" enctype="multipart/form-data">
<div>Upload</div>
<label>
<div>Select file</div>
<input type="file" name="file" />
</label>
<label>
<div>Description</div>
<input type="text" name="description" />
</label>
<label>
<div>Sensitive?</div>
<input type="checkbox" name="sensitive" />
</label>
<button type="submit">Upload</button>
</form>
</div>
</div>
<table class="directory-listing">
{#for .dir.children.? |$child| =}

View File

@ -0,0 +1,67 @@
<div class="drive">
<ol class="breadcrumbs">
<li>
<a href="{.mount_path}/{.base_drive_path}/">
<i class="fa-solid fa-cloud"></i>
<span class="directory">/</span>
</a>
</li>
{#for .breadcrumbs |$crumb, $i| =}
<i class="fa-solid fa-chevron-right"></i>
<li>
<a href="{.mount_path}/{.base_drive_path}
{= #for @slice(.breadcrumbs, 0, $i) |$c|}/{$c}{/for =}
/{$crumb}">
{$crumb}
</a>
</li>
{/for =}
</ol>
<div class="popup-buttons">
<div class="popup" id="delete-{.file.name.?}">
<a href="#delete-{.file.name.?}">
<i class="fa-solid fa-trash"></i>
</a>
<form class="popup-dialog" action="?action=delete" method="post">
<div>Are you sure you want to delete this file?</div>
<input type="hidden" name="action" value="delete" />
<button type="submit">Yes, Delete</button>
<a href="#">No, Cancel</a>
</form>
</div>
</div>
<div class="details">
<h2>{.file.name.?}</h2>
<div class="content">
{#if @isTag(.class, image) =}
<img src="/media/{.file.meta.id}" />
{#elif @isTag(.class, video) =}
<video src="/media/{.file.meta.id}" />
{#elif @isTag(.class, audio) =}
<audio src="/media/{.file.meta.id}" />
{#else =}
<a download href="/media/{.file.meta.id}"><i class="fa-solid fa-download"></i>{.file.name.?}</a>
{/if}
</div>
<div class="meta">
<h3>Metadata</h3>
<h4>Drive Path</h4>
<div>{.file.path}</div>
<h4>Filename</h4>
<div>{.file.meta.filename}</div>
<h4>Content Type</h4>
<div>{.file.meta.content_type}</div>
<h4>Sensitive?</h4>
<div>{.file.meta.sensitive}</div>
<h4>Description</h4>
<div>{.file.meta.description}</div>
</div>
</div>
</div>

View File

@ -210,6 +210,7 @@ fn print(writer: anytype, arg: anytype) !void {
if (T == void) return;
if (comptime std.meta.trait.isZigString(T)) return htmlEscape(writer, arg);
if (comptime std.meta.trait.isNumber(T)) return std.fmt.format(writer, "{}", .{arg});
if (comptime std.meta.trait.is(.Optional)(T)) return if (arg) |a| try print(writer, a);
try std.fmt.format(writer, "{}", .{arg});
}