Implement switching sort / display

This commit is contained in:
Jackzie 2025-04-17 19:11:58 -05:00
parent a1d740aa05
commit 8ef99d1ff7
5 changed files with 86 additions and 42 deletions

View file

@ -1,5 +1,7 @@
use std::cell::OnceCell;
use std::time::Duration;
use rocket::data::ByteUnit;
use rocket::serde::Serialize;
/// The maximum amount of bytes that can be uploaded at once
pub const MAX_UPLOAD_SIZE: ByteUnit = ByteUnit::Mebibyte(100_000);
@ -9,4 +11,17 @@ pub const ENCRYPTION_ROUNDS: u32 = 12;
pub const SESSION_LIFETIME_SECONDS: u64 = 3600 * 24 * 14; // 14 days
pub const SESSION_COOKIE_NAME: &'static str = "storage-session";
pub const SESSION_COOKIE_NAME: &'static str = "storage-session";
#[derive(Serialize)]
pub struct FileConstants<'a> {
pub display_options: &'a[&'a str],
pub sort_keys: &'a[&'a str],
}
pub const FILE_CONSTANTS: FileConstants = FileConstants {
display_options: &["list", "grid"],
sort_keys: &["name", "last_modified", "size"],
};

View file

@ -76,6 +76,7 @@ async fn rocket() -> _ {
warn!("warn");
error!("error");
// TODO: move to own fn
let pool = PgPoolOptions::new()
.max_connections(5)
.connect(std::env::var("DATABASE_URL").unwrap().as_str())
@ -96,6 +97,7 @@ async fn rocket() -> _ {
Arc::new(Mutex::new(manager))
};
// TODO: move to own func
let memory_store: MemoryStore::<SessionData> = MemoryStore::default();
let store: SessionStore<SessionData> = SessionStore {
store: Box::new(memory_store),
@ -109,7 +111,8 @@ async fn rocket() -> _ {
// slash which prevents the cookie from being sent for `example.com/myapp2/`).
.path("/")
};
// TODO: move to constants
let metadata = GlobalMetadata {
app_name: env!("CARGO_PKG_NAME").to_string(),
app_version: env!("CARGO_PKG_VERSION").to_string(),

View file

@ -1,3 +1,4 @@
use std::cell::OnceCell;
use std::io::Cursor;
use std::path::{Path, PathBuf};
use std::sync::Arc;
@ -13,6 +14,7 @@ use rocket_dyn_templates::{context, Template};
use serde::Serialize;
use serde_json::Value;
use tokio::sync::Mutex;
use crate::consts::FILE_CONSTANTS;
use crate::guards::{AuthUser};
use crate::managers::libraries::LibraryManager;
use crate::routes::ui::auth;
@ -30,14 +32,20 @@ pub async fn redirect_list_library_files(user: AuthUser, libraries: &State<Arc<M
{
let libs = libraries.lock().await;
let library = libs.get(library_id).await?;
Ok(Redirect::to(uri!(list_library_files(library_id, library.model().name, ""))))
Ok(Redirect::to(uri!(list_library_files(library_id, library.model().name, "", Some("name"), Some("asc"), Some("list")))))
}
#[get("/library/<library_id>/<_>/<path..>")]
pub async fn list_library_files(user: AuthUser, route: &Route, libraries: &State<Arc<Mutex<LibraryManager>>>, library_id: &str, path: PathBuf)
-> Result<Template, ResponseError>
{
#[get("/library/<library_id>/<_>/<path..>?<sort_key>&<sort_dir>&<display>")]
pub async fn list_library_files(
user: AuthUser,
route: &Route,
libraries: &State<Arc<Mutex<LibraryManager>>>,
library_id: &str,
path: PathBuf,
sort_key: Option<String>,
sort_dir: Option<String>,
display: Option<String>,
) -> Result<Template, ResponseError> {
let libs = libraries.lock().await;
let library = libs.get(library_id).await?;
let files = library.list_files(&PathBuf::from(&path)).await
@ -72,10 +80,36 @@ pub async fn list_library_files(user: AuthUser, route: &Route, libraries: &State
library: library.model(),
files: files,
parent,
path_segments: segments
path_segments: segments,
// TODO: have struct?
options: FileDisplayOptions {
// TODO: prevent bad values
// TODO: fix login errror msg -------_____------
sort_key: validate_option(sort_key, FILE_CONSTANTS.sort_keys, "name"),
sort_dir: validate_option(sort_dir, &["asc", "desc"], "asc"),
display: validate_option(display, FILE_CONSTANTS.display_options, "list"),
},
DATA: FILE_CONSTANTS
}))
}
/// Checks if option is in list of valid values, if not returns default_value
fn validate_option(option: Option<String>, valid_values: &[&str], default_value: &str) -> String {
if let Some(option) = option {
if valid_values.contains(&&*option) {
return option.to_string()
}
}
default_value.to_string()
}
#[derive(Serialize)]
struct FileDisplayOptions {
sort_key: String,
sort_dir: String,
display: String
}
#[derive(Debug, Serialize)]
struct PathSegmentPiece {
pub path: PathBuf,

View file

@ -6,6 +6,7 @@ use std::io::BufReader;
use std::path::PathBuf;
use anyhow::{anyhow, Error};
use int_enum::IntEnum;
use rocket::FromFormField;
use rocket::serde::json::Json;
use rocket::serde::{Deserialize, Serialize};
use serde_json::Value;
@ -18,7 +19,7 @@ pub enum StorageBackendMap {
S3(S3Storage)
}
#[derive(Debug, Serialize, Deserialize)]
#[derive(Debug, Serialize, Deserialize, FromFormField)]
#[serde(rename_all = "lowercase")]
pub enum FileType {
File,

View file

@ -36,9 +36,9 @@
<div class="dropdown-trigger">
<button class="button is-small has-background-white-ter" aria-haspopup="true" aria-controls="dropdown-menu">
<span class="icon">
<i class="fa fa-list is-small"></i>
<i class="fa {{#if (eq options.display 'grid')}}fa-grip{{else}}fa-list{{/if}} is-small"></i>
</span>
<span>List</span>
<span>{{#if (eq options.display 'grid')}}Grid{{else}}List{{/if}}</span>
<span class="icon is-small">
<i class="fas fa-angle-down" aria-hidden="true"></i>
</span>
@ -46,8 +46,11 @@
</div>
<div class="dropdown-menu" role="menu">
<div class="dropdown-content">
<a href="#" class="dropdown-item is-active"> <i class="fa fa-list"></i> List View</a>
<a class="dropdown-item"> <i class="fa fa-grip"></i> Grid View</a>
<a href="?display=list&sort_key={{options.sort_key}}&sort_dir={{options.sort_dir}}"
class="dropdown-item {{#if (eq options.display 'list')}}is-active{{/if}}"
><i class="fa fa-list"></i> List View</a>
<a href="?display=grid&sort_key={{options.sort_key}}&sort_dir={{options.sort_dir}}"
class="dropdown-item {{#if (eq options.display 'grid')}}is-active{{/if}}"> <i class="fa fa-grip"></i> Grid View</a>
</div>
</div>
</div>
@ -57,7 +60,7 @@
<span class="icon">
<i class="fa fa-arrow-up is-small"></i>
</span>
<span>Sort by Name (asc)</span>
<span>Sort by {{ options.sort_key }} ({{ options.sort_dir }})</span>
<span class="icon is-small">
<i class="fas fa-angle-down" aria-hidden="true"></i>
</span>
@ -65,12 +68,20 @@
</div>
<div class="dropdown-menu" role="menu">
<div class="dropdown-content">
<a href="#" class="dropdown-item is-active"> By name ascending</a>
<a class="dropdown-item"> By name descending</a>
<a class="dropdown-item"> By last modified ascending</a>
<a class="dropdown-item"> By last modified descending</a>
<a class="dropdown-item"> By size ascending</a>
<a class="dropdown-item"> By size ascending</a>
{{#each DATA.sort_keys}}
<a href="?display={{../options.display}}&sort_key={{this}}&sort_dir=asc"
class="dropdown-item {{#if (eq ../options.sort_dir "asc")}}
{{#if (eq ../options.sort_key this)}}
is-active
{{/if}}
{{/if}}">By {{this}} ascending</a>
<a href="?display={{../options.display}}&sort_key={{this}}&sort_dir=desc"
class="dropdown-item {{#if (eq ../options.sort_dir "desc")}}
{{#if (eq ../options.sort_key this)}}
is-active
{{/if}}
{{/if}}">By {{this}} descending</a>
{{/each}}
</div>
</div>
</div>
@ -166,6 +177,7 @@
<script>
const LIBRARY_ID = "{{ library.id }}";
const LIBRARY_PATH = "/{{ parent }}";
{{!-- let OPTIONS = JSON.parse(`{{{ options }}}`) --}}
</script>
<script src="/static/js/add_button.js"></script>
<script>
@ -175,27 +187,6 @@ document.addEventListener('alpine:init', () => {
});
</script>
<script src="//unpkg.com/alpinejs" defer></script>
{{!-- <script type="module">
import { createApp, ref } from 'https://unpkg.com/vue@3/dist/vue.esm-browser.js'
createApp({
delimiters: ['{%', '%}'],
setup() {
const message = ref('Hello Vue!')
const loaded = ref(false)
const touchPrompt = ref(false)
return {
loaded,
message,
touchPrompt
}
},
mounted() {
this.loaded = true
console.info("Vue ready")
}
}).mount('#app')
</script> --}}
{{/inline}}
{{/layouts/main}}