mirror of
https://github.com/Jackzmc/storage.git
synced 2025-05-05 20:53:21 +00:00
Implement switching sort / display
This commit is contained in:
parent
a1d740aa05
commit
8ef99d1ff7
5 changed files with 86 additions and 42 deletions
|
@ -1,5 +1,7 @@
|
||||||
|
use std::cell::OnceCell;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
use rocket::data::ByteUnit;
|
use rocket::data::ByteUnit;
|
||||||
|
use rocket::serde::Serialize;
|
||||||
|
|
||||||
/// The maximum amount of bytes that can be uploaded at once
|
/// The maximum amount of bytes that can be uploaded at once
|
||||||
pub const MAX_UPLOAD_SIZE: ByteUnit = ByteUnit::Mebibyte(100_000);
|
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_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"],
|
||||||
|
};
|
||||||
|
|
||||||
|
|
|
@ -76,6 +76,7 @@ async fn rocket() -> _ {
|
||||||
warn!("warn");
|
warn!("warn");
|
||||||
error!("error");
|
error!("error");
|
||||||
|
|
||||||
|
// TODO: move to own fn
|
||||||
let pool = PgPoolOptions::new()
|
let pool = PgPoolOptions::new()
|
||||||
.max_connections(5)
|
.max_connections(5)
|
||||||
.connect(std::env::var("DATABASE_URL").unwrap().as_str())
|
.connect(std::env::var("DATABASE_URL").unwrap().as_str())
|
||||||
|
@ -96,6 +97,7 @@ async fn rocket() -> _ {
|
||||||
Arc::new(Mutex::new(manager))
|
Arc::new(Mutex::new(manager))
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// TODO: move to own func
|
||||||
let memory_store: MemoryStore::<SessionData> = MemoryStore::default();
|
let memory_store: MemoryStore::<SessionData> = MemoryStore::default();
|
||||||
let store: SessionStore<SessionData> = SessionStore {
|
let store: SessionStore<SessionData> = SessionStore {
|
||||||
store: Box::new(memory_store),
|
store: Box::new(memory_store),
|
||||||
|
@ -109,7 +111,8 @@ async fn rocket() -> _ {
|
||||||
// slash which prevents the cookie from being sent for `example.com/myapp2/`).
|
// slash which prevents the cookie from being sent for `example.com/myapp2/`).
|
||||||
.path("/")
|
.path("/")
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// TODO: move to constants
|
||||||
let metadata = GlobalMetadata {
|
let metadata = GlobalMetadata {
|
||||||
app_name: env!("CARGO_PKG_NAME").to_string(),
|
app_name: env!("CARGO_PKG_NAME").to_string(),
|
||||||
app_version: env!("CARGO_PKG_VERSION").to_string(),
|
app_version: env!("CARGO_PKG_VERSION").to_string(),
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
use std::cell::OnceCell;
|
||||||
use std::io::Cursor;
|
use std::io::Cursor;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
@ -13,6 +14,7 @@ use rocket_dyn_templates::{context, Template};
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
use serde_json::Value;
|
use serde_json::Value;
|
||||||
use tokio::sync::Mutex;
|
use tokio::sync::Mutex;
|
||||||
|
use crate::consts::FILE_CONSTANTS;
|
||||||
use crate::guards::{AuthUser};
|
use crate::guards::{AuthUser};
|
||||||
use crate::managers::libraries::LibraryManager;
|
use crate::managers::libraries::LibraryManager;
|
||||||
use crate::routes::ui::auth;
|
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 libs = libraries.lock().await;
|
||||||
let library = libs.get(library_id).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..>?<sort_key>&<sort_dir>&<display>")]
|
||||||
#[get("/library/<library_id>/<_>/<path..>")]
|
pub async fn list_library_files(
|
||||||
pub async fn list_library_files(user: AuthUser, route: &Route, libraries: &State<Arc<Mutex<LibraryManager>>>, library_id: &str, path: PathBuf)
|
user: AuthUser,
|
||||||
-> Result<Template, ResponseError>
|
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 libs = libraries.lock().await;
|
||||||
let library = libs.get(library_id).await?;
|
let library = libs.get(library_id).await?;
|
||||||
let files = library.list_files(&PathBuf::from(&path)).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(),
|
library: library.model(),
|
||||||
files: files,
|
files: files,
|
||||||
parent,
|
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)]
|
#[derive(Debug, Serialize)]
|
||||||
struct PathSegmentPiece {
|
struct PathSegmentPiece {
|
||||||
pub path: PathBuf,
|
pub path: PathBuf,
|
||||||
|
|
|
@ -6,6 +6,7 @@ use std::io::BufReader;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use anyhow::{anyhow, Error};
|
use anyhow::{anyhow, Error};
|
||||||
use int_enum::IntEnum;
|
use int_enum::IntEnum;
|
||||||
|
use rocket::FromFormField;
|
||||||
use rocket::serde::json::Json;
|
use rocket::serde::json::Json;
|
||||||
use rocket::serde::{Deserialize, Serialize};
|
use rocket::serde::{Deserialize, Serialize};
|
||||||
use serde_json::Value;
|
use serde_json::Value;
|
||||||
|
@ -18,7 +19,7 @@ pub enum StorageBackendMap {
|
||||||
S3(S3Storage)
|
S3(S3Storage)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
#[derive(Debug, Serialize, Deserialize, FromFormField)]
|
||||||
#[serde(rename_all = "lowercase")]
|
#[serde(rename_all = "lowercase")]
|
||||||
pub enum FileType {
|
pub enum FileType {
|
||||||
File,
|
File,
|
||||||
|
|
|
@ -36,9 +36,9 @@
|
||||||
<div class="dropdown-trigger">
|
<div class="dropdown-trigger">
|
||||||
<button class="button is-small has-background-white-ter" aria-haspopup="true" aria-controls="dropdown-menu">
|
<button class="button is-small has-background-white-ter" aria-haspopup="true" aria-controls="dropdown-menu">
|
||||||
<span class="icon">
|
<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>
|
||||||
<span>List</span>
|
<span>{{#if (eq options.display 'grid')}}Grid{{else}}List{{/if}}</span>
|
||||||
<span class="icon is-small">
|
<span class="icon is-small">
|
||||||
<i class="fas fa-angle-down" aria-hidden="true"></i>
|
<i class="fas fa-angle-down" aria-hidden="true"></i>
|
||||||
</span>
|
</span>
|
||||||
|
@ -46,8 +46,11 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="dropdown-menu" role="menu">
|
<div class="dropdown-menu" role="menu">
|
||||||
<div class="dropdown-content">
|
<div class="dropdown-content">
|
||||||
<a href="#" class="dropdown-item is-active"> <i class="fa fa-list"></i> List View</a>
|
<a href="?display=list&sort_key={{options.sort_key}}&sort_dir={{options.sort_dir}}"
|
||||||
<a class="dropdown-item"> <i class="fa fa-grip"></i> Grid View</a>
|
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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -57,7 +60,7 @@
|
||||||
<span class="icon">
|
<span class="icon">
|
||||||
<i class="fa fa-arrow-up is-small"></i>
|
<i class="fa fa-arrow-up is-small"></i>
|
||||||
</span>
|
</span>
|
||||||
<span>Sort by Name (asc)</span>
|
<span>Sort by {{ options.sort_key }} ({{ options.sort_dir }})</span>
|
||||||
<span class="icon is-small">
|
<span class="icon is-small">
|
||||||
<i class="fas fa-angle-down" aria-hidden="true"></i>
|
<i class="fas fa-angle-down" aria-hidden="true"></i>
|
||||||
</span>
|
</span>
|
||||||
|
@ -65,12 +68,20 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="dropdown-menu" role="menu">
|
<div class="dropdown-menu" role="menu">
|
||||||
<div class="dropdown-content">
|
<div class="dropdown-content">
|
||||||
<a href="#" class="dropdown-item is-active"> By name ascending</a>
|
{{#each DATA.sort_keys}}
|
||||||
<a class="dropdown-item"> By name descending</a>
|
<a href="?display={{../options.display}}&sort_key={{this}}&sort_dir=asc"
|
||||||
<a class="dropdown-item"> By last modified ascending</a>
|
class="dropdown-item {{#if (eq ../options.sort_dir "asc")}}
|
||||||
<a class="dropdown-item"> By last modified descending</a>
|
{{#if (eq ../options.sort_key this)}}
|
||||||
<a class="dropdown-item"> By size ascending</a>
|
is-active
|
||||||
<a class="dropdown-item"> By size ascending</a>
|
{{/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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -166,6 +177,7 @@
|
||||||
<script>
|
<script>
|
||||||
const LIBRARY_ID = "{{ library.id }}";
|
const LIBRARY_ID = "{{ library.id }}";
|
||||||
const LIBRARY_PATH = "/{{ parent }}";
|
const LIBRARY_PATH = "/{{ parent }}";
|
||||||
|
{{!-- let OPTIONS = JSON.parse(`{{{ options }}}`) --}}
|
||||||
</script>
|
</script>
|
||||||
<script src="/static/js/add_button.js"></script>
|
<script src="/static/js/add_button.js"></script>
|
||||||
<script>
|
<script>
|
||||||
|
@ -175,27 +187,6 @@ document.addEventListener('alpine:init', () => {
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
<script src="//unpkg.com/alpinejs" defer></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}}
|
{{/inline}}
|
||||||
{{/layouts/main}}
|
{{/layouts/main}}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue