Add direct URL aka path navigation
This commit is contained in:
parent
9e5494035a
commit
bf08bf7000
13 changed files with 143 additions and 46 deletions
26
Cargo.lock
generated
26
Cargo.lock
generated
|
@ -733,7 +733,7 @@ dependencies = [
|
||||||
"gloo-render",
|
"gloo-render",
|
||||||
"gloo-storage",
|
"gloo-storage",
|
||||||
"gloo-timers",
|
"gloo-timers",
|
||||||
"gloo-utils",
|
"gloo-utils 0.1.6",
|
||||||
"gloo-worker",
|
"gloo-worker",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -743,7 +743,7 @@ version = "0.2.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "82b7ce3c05debe147233596904981848862b068862e9ec3e34be446077190d3f"
|
checksum = "82b7ce3c05debe147233596904981848862b068862e9ec3e34be446077190d3f"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"gloo-utils",
|
"gloo-utils 0.1.6",
|
||||||
"js-sys",
|
"js-sys",
|
||||||
"serde",
|
"serde",
|
||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
|
@ -789,7 +789,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5ce5ae65c5d76e2bbd9f274d7dcc00a306a79964305efa275a0ac728caaeb792"
|
checksum = "5ce5ae65c5d76e2bbd9f274d7dcc00a306a79964305efa275a0ac728caaeb792"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"gloo-events",
|
"gloo-events",
|
||||||
"gloo-utils",
|
"gloo-utils 0.1.6",
|
||||||
"serde",
|
"serde",
|
||||||
"serde-wasm-bindgen",
|
"serde-wasm-bindgen",
|
||||||
"serde_urlencoded",
|
"serde_urlencoded",
|
||||||
|
@ -807,7 +807,7 @@ dependencies = [
|
||||||
"futures-channel",
|
"futures-channel",
|
||||||
"futures-core",
|
"futures-core",
|
||||||
"futures-sink",
|
"futures-sink",
|
||||||
"gloo-utils",
|
"gloo-utils 0.1.6",
|
||||||
"js-sys",
|
"js-sys",
|
||||||
"pin-project",
|
"pin-project",
|
||||||
"serde",
|
"serde",
|
||||||
|
@ -834,7 +834,7 @@ version = "0.2.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5d6ab60bf5dbfd6f0ed1f7843da31b41010515c745735c970e821945ca91e480"
|
checksum = "5d6ab60bf5dbfd6f0ed1f7843da31b41010515c745735c970e821945ca91e480"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"gloo-utils",
|
"gloo-utils 0.1.6",
|
||||||
"js-sys",
|
"js-sys",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
|
@ -866,6 +866,19 @@ dependencies = [
|
||||||
"web-sys",
|
"web-sys",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "gloo-utils"
|
||||||
|
version = "0.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0b5555354113b18c547c1d3a98fbf7fb32a9ff4f6fa112ce823a21641a0ba3aa"
|
||||||
|
dependencies = [
|
||||||
|
"js-sys",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
|
"wasm-bindgen",
|
||||||
|
"web-sys",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "gloo-worker"
|
name = "gloo-worker"
|
||||||
version = "0.2.1"
|
version = "0.2.1"
|
||||||
|
@ -875,7 +888,7 @@ dependencies = [
|
||||||
"anymap2",
|
"anymap2",
|
||||||
"bincode",
|
"bincode",
|
||||||
"gloo-console",
|
"gloo-console",
|
||||||
"gloo-utils",
|
"gloo-utils 0.1.6",
|
||||||
"js-sys",
|
"js-sys",
|
||||||
"serde",
|
"serde",
|
||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
|
@ -2218,6 +2231,7 @@ dependencies = [
|
||||||
"bytes",
|
"bytes",
|
||||||
"clap",
|
"clap",
|
||||||
"futures",
|
"futures",
|
||||||
|
"gloo-utils 0.2.0",
|
||||||
"log",
|
"log",
|
||||||
"rand",
|
"rand",
|
||||||
"reqwest",
|
"reqwest",
|
||||||
|
|
|
@ -30,6 +30,7 @@ yew_icons = {version = "0.7", features = [
|
||||||
"FeatherFile",
|
"FeatherFile",
|
||||||
"FeatherRefreshCcw"
|
"FeatherRefreshCcw"
|
||||||
] }
|
] }
|
||||||
|
log = "0.4"
|
||||||
|
|
||||||
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
||||||
yew = { version = "0.20", features = [ "csr", "hydration" ] }
|
yew = { version = "0.20", features = [ "csr", "hydration" ] }
|
||||||
|
@ -42,9 +43,11 @@ web-sys = { version = "0.3", features = [
|
||||||
"RequestMode",
|
"RequestMode",
|
||||||
"Response",
|
"Response",
|
||||||
"Window",
|
"Window",
|
||||||
|
"Location",
|
||||||
|
"History",
|
||||||
] }
|
] }
|
||||||
|
gloo-utils = "0.2"
|
||||||
wasm-logger = "0.2"
|
wasm-logger = "0.2"
|
||||||
log = "0.4"
|
|
||||||
|
|
||||||
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
|
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
|
||||||
yew = { version = "0.20", features = [ "ssr" ] }
|
yew = { version = "0.20", features = [ "ssr" ] }
|
||||||
|
|
|
@ -54,7 +54,7 @@ pub async fn file(path: web::Path<String>, auth: BasicAuth, query: web::Query<Fi
|
||||||
let req_disposition = query.mode.unwrap_or_else(
|
let req_disposition = query.mode.unwrap_or_else(
|
||||||
|| DeliveryMode::default_from_ext(filepath.extension())
|
|| DeliveryMode::default_from_ext(filepath.extension())
|
||||||
).disposition();
|
).disposition();
|
||||||
println!("file PATH: {}", filepath.display());
|
log::debug!("file PATH: {}", filepath.display());
|
||||||
Ok(
|
Ok(
|
||||||
actix_files::NamedFile::open_async(&filepath).await?
|
actix_files::NamedFile::open_async(&filepath).await?
|
||||||
.prefer_utf8(false)
|
.prefer_utf8(false)
|
||||||
|
@ -94,13 +94,25 @@ pub async fn dir(path: web::Path<String>, auth: BasicAuth) -> std::io::Result<im
|
||||||
if !args.authenticate(auth.user_id(), auth.password().unwrap_or("")).await {
|
if !args.authenticate(auth.user_id(), auth.password().unwrap_or("")).await {
|
||||||
return Err(std::io::Error::new(std::io::ErrorKind::PermissionDenied, "Basic Authentication failed"))
|
return Err(std::io::Error::new(std::io::ErrorKind::PermissionDenied, "Basic Authentication failed"))
|
||||||
}
|
}
|
||||||
let root = args.dir.unwrap();
|
let entries = get_dir(&path, &args)?;
|
||||||
let domain = args.domain;
|
|
||||||
let filepath = root.join(&*path);
|
Ok(
|
||||||
|
web::Json(entries)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_dir(end_path: &str, args: &super::CliArgs) -> std::io::Result<Vec<FileEntry>> {
|
||||||
|
let root = args.dir.as_ref().unwrap();
|
||||||
|
let domain = &args.domain;
|
||||||
|
let filepath = root.join(end_path);
|
||||||
let scheme = if args.ssl {"https"} else {"http"};
|
let scheme = if args.ssl {"https"} else {"http"};
|
||||||
println!("dir PATH: {}", filepath.display());
|
|
||||||
let mut entries = Vec::new();
|
let mut entries = Vec::new();
|
||||||
if !(path.is_empty() || &*path == "/") && filepath.parent().is_some() {
|
if let Ok(root_canon) = root.canonicalize() {
|
||||||
|
if !filepath.canonicalize()?.starts_with(root_canon) {
|
||||||
|
return Ok(entries);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !(end_path.is_empty() || &*end_path == "/") && filepath.parent().is_some() {
|
||||||
let external_path = filepath.parent().expect("Path has no parent")
|
let external_path = filepath.parent().expect("Path has no parent")
|
||||||
.strip_prefix(&root).expect("path does not start with root path!").to_path_buf();
|
.strip_prefix(&root).expect("path does not start with root path!").to_path_buf();
|
||||||
let link = format!("{}://{}/api/listdir/{}", scheme, domain, external_path.to_string_lossy());
|
let link = format!("{}://{}/api/listdir/{}", scheme, domain, external_path.to_string_lossy());
|
||||||
|
@ -111,7 +123,6 @@ pub async fn dir(path: web::Path<String>, auth: BasicAuth) -> std::io::Result<im
|
||||||
url: url_escape::encode_path(&link).to_owned().to_string(),
|
url: url_escape::encode_path(&link).to_owned().to_string(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
//let pseudo_root = std::path::PathBuf::from("/");
|
|
||||||
// build dir entry json
|
// build dir entry json
|
||||||
for e in filepath.read_dir()? {
|
for e in filepath.read_dir()? {
|
||||||
let entry = e?;
|
let entry = e?;
|
||||||
|
@ -136,7 +147,5 @@ pub async fn dir(path: web::Path<String>, auth: BasicAuth) -> std::io::Result<im
|
||||||
}
|
}
|
||||||
// sort alphabetically
|
// sort alphabetically
|
||||||
entries.sort_by(|a, b| a.name.cmp(&b.name));
|
entries.sort_by(|a, b| a.name.cmp(&b.name));
|
||||||
Ok(
|
Ok(entries)
|
||||||
web::Json(entries)
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,13 @@ use actix_web_httpauth::extractors::basic::BasicAuth;
|
||||||
use bytes::Bytes;
|
use bytes::Bytes;
|
||||||
use futures::stream::{self, Stream, StreamExt};
|
use futures::stream::{self, Stream, StreamExt};
|
||||||
|
|
||||||
|
use serde::Deserialize;
|
||||||
|
|
||||||
|
#[derive(Deserialize, Default, Clone)]
|
||||||
|
pub struct IndexQuery {
|
||||||
|
path: Option<std::path::PathBuf>,
|
||||||
|
}
|
||||||
|
|
||||||
type BoxedError = Box<dyn std::error::Error + 'static>;
|
type BoxedError = Box<dyn std::error::Error + 'static>;
|
||||||
|
|
||||||
pub struct IndexPage {
|
pub struct IndexPage {
|
||||||
|
@ -12,8 +19,11 @@ pub struct IndexPage {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl IndexPage {
|
impl IndexPage {
|
||||||
async fn render(&self) -> impl Stream<Item = Result<Bytes, BoxedError>> + Send {
|
async fn render(&self, query: IndexQuery) -> impl Stream<Item = Result<Bytes, BoxedError>> + Send {
|
||||||
let renderer = yew::ServerRenderer::<crate::ui::App>::new();
|
let renderer = yew::ServerRenderer::<crate::ui::App>::with_props(|| crate::ui::AppProps {
|
||||||
|
starting_path: query.path.clone(),
|
||||||
|
starting_files: query.path.map(|start| super::get_files::get_dir(&start.to_string_lossy(), &super::CliArgs::get()).ok()).flatten()
|
||||||
|
});
|
||||||
let before = self.before.clone();
|
let before = self.before.clone();
|
||||||
let after = self.after.clone();
|
let after = self.after.clone();
|
||||||
|
|
||||||
|
@ -36,17 +46,17 @@ impl IndexPage {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[get("/")]
|
#[get("/")]
|
||||||
pub async fn index_auth(page: web::Data<IndexPage>, auth: BasicAuth) -> std::io::Result<impl Responder> {
|
pub async fn index_auth(page: web::Data<IndexPage>, query: web::Query<IndexQuery>, auth: BasicAuth) -> std::io::Result<impl Responder> {
|
||||||
let args = super::CliArgs::get();
|
let args = super::CliArgs::get();
|
||||||
if !args.authenticate(auth.user_id(), auth.password().unwrap_or("")).await {
|
if !args.authenticate(auth.user_id(), auth.password().unwrap_or("")).await {
|
||||||
return Err(std::io::Error::new(std::io::ErrorKind::PermissionDenied, "Basic Authentication failed"))
|
return Err(std::io::Error::new(std::io::ErrorKind::PermissionDenied, "Basic Authentication failed"))
|
||||||
}
|
}
|
||||||
Ok(HttpResponse::Ok()
|
Ok(HttpResponse::Ok()
|
||||||
.streaming(page.render().await))
|
.streaming(page.render((&*query).clone()).await))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[get("/")]
|
#[get("/")]
|
||||||
pub async fn index_no_auth(page: web::Data<IndexPage>) -> std::io::Result<impl Responder> {
|
pub async fn index_no_auth(page: web::Data<IndexPage>) -> std::io::Result<impl Responder> {
|
||||||
Ok(HttpResponse::Ok()
|
Ok(HttpResponse::Ok()
|
||||||
.streaming(page.render().await))
|
.streaming(page.render(IndexQuery::default()).await))
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,7 +13,7 @@ use yarrr::api::{
|
||||||
async fn main() -> std::io::Result<()> {
|
async fn main() -> std::io::Result<()> {
|
||||||
let args = yarrr::api::CliArgs::get();
|
let args = yarrr::api::CliArgs::get();
|
||||||
|
|
||||||
println!("cli: {:?}", args);
|
log::info!("cli: {:?}", args);
|
||||||
|
|
||||||
if args.dir.is_some() {
|
if args.dir.is_some() {
|
||||||
HttpServer::new(|| {
|
HttpServer::new(|| {
|
||||||
|
|
|
@ -1,8 +1,20 @@
|
||||||
use yarrr::ui::App;
|
|
||||||
|
|
||||||
#[cfg(target_arch = "wasm32")]
|
#[cfg(target_arch = "wasm32")]
|
||||||
fn main() {
|
fn main() {
|
||||||
yew::Renderer::<App>::new().hydrate();
|
wasm_logger::init(wasm_logger::Config::new(log::Level::Info));
|
||||||
|
let url_str = gloo_utils::document()
|
||||||
|
.location()
|
||||||
|
.expect("Location init failed")
|
||||||
|
.href()
|
||||||
|
.expect("Location.href does not exist");
|
||||||
|
let url = reqwest::Url::parse(&url_str)
|
||||||
|
.expect("Failed to parse URL");
|
||||||
|
let props = url.query_pairs()
|
||||||
|
.filter(|(param, _)| param == "path")
|
||||||
|
.map(|(_, start_path)| yarrr::ui::AppProps { starting_path: Some(std::path::PathBuf::from(&*start_path)), starting_files: None, })
|
||||||
|
.next()
|
||||||
|
.unwrap_or_else(|| yarrr::ui::AppProps { starting_path: None, starting_files: None, });
|
||||||
|
log::debug!("Hydrating yew renderer");
|
||||||
|
yew::Renderer::<yarrr::ui::App>::with_props(props).hydrate();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(target_arch = "wasm32"))]
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
|
|
|
@ -1,10 +1,16 @@
|
||||||
use yew::prelude::*;
|
use yew::prelude::*;
|
||||||
|
|
||||||
|
#[derive(Properties, PartialEq)]
|
||||||
|
pub struct Props {
|
||||||
|
pub starting_path: Option<std::path::PathBuf>,
|
||||||
|
pub starting_files: Option<Vec<crate::data::FileEntry>>,
|
||||||
|
}
|
||||||
|
|
||||||
#[function_component(App)]
|
#[function_component(App)]
|
||||||
pub fn app() -> Html {
|
pub fn app(props: &Props) -> Html {
|
||||||
html! {
|
html! {
|
||||||
<Suspense fallback={html!{"..."}}>
|
<Suspense fallback={html!{"..."}}>
|
||||||
<super::Landing />
|
<super::Landing starting_path={props.starting_path.clone()} starting_files={props.starting_files.clone()}/>
|
||||||
</Suspense>
|
</Suspense>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,7 +9,8 @@ use crate::data::FileEntry;
|
||||||
type FileEntryVec = Vec<FileEntry>;
|
type FileEntryVec = Vec<FileEntry>;
|
||||||
|
|
||||||
async fn listdir(scheme: &str, domain: &str, path: &str) -> reqwest::Result<FileEntryVec> {
|
async fn listdir(scheme: &str, domain: &str, path: &str) -> reqwest::Result<FileEntryVec> {
|
||||||
let url = format!("{}://{}/api/listdir/{}", scheme, domain, path.trim_start_matches("/"));
|
let url = format!("{}://{}/api/listdir/{}", scheme, domain, path.trim_start_matches("/").trim_start_matches(".."));
|
||||||
|
log::debug!("Directory API ({}): what does {} contain?", url, path);
|
||||||
reqwest::get(&url)
|
reqwest::get(&url)
|
||||||
.await?
|
.await?
|
||||||
.json()
|
.json()
|
||||||
|
@ -34,6 +35,8 @@ pub struct Props {
|
||||||
pub scheme: String,
|
pub scheme: String,
|
||||||
pub domain: String,
|
pub domain: String,
|
||||||
pub path: String,
|
pub path: String,
|
||||||
|
pub prepared_path: String,
|
||||||
|
pub prepared_files: Option<FileEntryVec>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct FileExplorer {
|
pub struct FileExplorer {
|
||||||
|
@ -45,12 +48,20 @@ impl Component for FileExplorer {
|
||||||
type Message = Msg;
|
type Message = Msg;
|
||||||
type Properties = Props;
|
type Properties = Props;
|
||||||
|
|
||||||
fn create(_ctx: &Context<Self>) -> Self {
|
fn create(ctx: &Context<Self>) -> Self {
|
||||||
|
log::debug!("prepared_path ({}) == path ({})? {}", ctx.props().prepared_path, ctx.props().path, ctx.props().prepared_path == ctx.props().path);
|
||||||
|
if ctx.props().prepared_path == ctx.props().path {
|
||||||
|
Self {
|
||||||
|
files: ctx.props().prepared_files.as_ref().map(|files| FetchState::Success(files.clone())).unwrap_or(FetchState::NotFetching),
|
||||||
|
cwd: ctx.props().prepared_path.clone(),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
Self {
|
Self {
|
||||||
files: FetchState::NotFetching,
|
files: FetchState::NotFetching,
|
||||||
cwd: "???".to_owned(),
|
cwd: "???".to_owned(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn update(&mut self, ctx: &Context<Self>, msg: Self::Message) -> bool {
|
fn update(&mut self, ctx: &Context<Self>, msg: Self::Message) -> bool {
|
||||||
match msg {
|
match msg {
|
||||||
|
@ -107,6 +118,7 @@ impl Component for FileExplorer {
|
||||||
</div>
|
</div>
|
||||||
},
|
},
|
||||||
FetchState::Success(data) => {
|
FetchState::Success(data) => {
|
||||||
|
log::debug!("cwd ({}) != path ({})? {}", self.cwd, ctx.props().path, self.cwd != ctx.props().path);
|
||||||
if self.cwd != ctx.props().path {
|
if self.cwd != ctx.props().path {
|
||||||
ctx.link().send_message(Msg::Load);
|
ctx.link().send_message(Msg::Load);
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,10 +20,20 @@ pub fn entry(props: &Props) -> Html {
|
||||||
} else {
|
} else {
|
||||||
html! { <Icon icon_id={IconId::FeatherFolder} width={icon_dimension.clone()} height={icon_dimension.clone()}/> }
|
html! { <Icon icon_id={IconId::FeatherFolder} width={icon_dimension.clone()} height={icon_dimension.clone()}/> }
|
||||||
};
|
};
|
||||||
|
let navigation_url = format!("{}://{}/?path={}",
|
||||||
|
if s_ctx.ssl { "https" } else { "http" },
|
||||||
|
s_ctx.domain,
|
||||||
|
props.entry.path.to_string_lossy());
|
||||||
|
let navigation_url2 = navigation_url.clone();
|
||||||
|
#[cfg(target_arch = "wasm32")]
|
||||||
|
let onclick_closure = move |event: web_sys::MouseEvent| {
|
||||||
|
event.prevent_default();
|
||||||
|
fs_ctx.dispatch(super::FilesystemCtxAction::NavigateTo(path.clone(), navigation_url.clone()));
|
||||||
|
};
|
||||||
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
|
let onclick_closure = move |_| fs_ctx.dispatch(super::FilesystemCtxAction::NavigateTo(path.clone(), navigation_url.clone()));
|
||||||
html! {
|
html! {
|
||||||
<a href={"#"} class={classes!("yarrr-file-entry-link")} onclick={
|
<a href={navigation_url2} class={classes!("yarrr-file-entry-link")} onclick={onclick_closure}>
|
||||||
move |_| fs_ctx.dispatch(super::FilesystemCtxAction::NavigateTo(path.clone()))
|
|
||||||
}>
|
|
||||||
<div class={classes!("yarrr-file-entry-dir")}>
|
<div class={classes!("yarrr-file-entry-dir")}>
|
||||||
<div class={classes!("yarrr-file-entry-icon")}>
|
<div class={classes!("yarrr-file-entry-icon")}>
|
||||||
{icon}
|
{icon}
|
||||||
|
|
|
@ -3,7 +3,7 @@ use std::path::PathBuf;
|
||||||
use yew::prelude::*;
|
use yew::prelude::*;
|
||||||
|
|
||||||
pub enum FilesystemCtxAction {
|
pub enum FilesystemCtxAction {
|
||||||
NavigateTo(PathBuf),
|
NavigateTo(PathBuf, String),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, PartialEq, Eq)]
|
#[derive(Clone, PartialEq, Eq)]
|
||||||
|
@ -14,9 +14,9 @@ pub struct FilesystemCtx {
|
||||||
pub type FilesystemContext = UseReducerHandle<FilesystemCtx>;
|
pub type FilesystemContext = UseReducerHandle<FilesystemCtx>;
|
||||||
|
|
||||||
impl FilesystemCtx {
|
impl FilesystemCtx {
|
||||||
pub fn init() -> Self {
|
pub fn init(cwd: &Option<PathBuf>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
cwd: PathBuf::from(""),
|
cwd: cwd.as_ref().map(|x| x.to_owned()).unwrap_or_else(|| PathBuf::from("")),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -26,7 +26,14 @@ impl Reducible for FilesystemCtx {
|
||||||
|
|
||||||
fn reduce(self: Rc<Self>, action: Self::Action) -> Rc<Self> {
|
fn reduce(self: Rc<Self>, action: Self::Action) -> Rc<Self> {
|
||||||
let cwd_new = match action {
|
let cwd_new = match action {
|
||||||
FilesystemCtxAction::NavigateTo(path) => path,
|
#[cfg_attr(not(target_arch = "wasm32"), allow(unused_variables))]
|
||||||
|
FilesystemCtxAction::NavigateTo(path, url) => {
|
||||||
|
#[cfg(target_arch = "wasm32")]
|
||||||
|
gloo_utils::history()
|
||||||
|
.push_state_with_url(&wasm_bindgen::JsValue::undefined(), &path.to_string_lossy(), Some(&url))
|
||||||
|
.unwrap_or(());
|
||||||
|
path
|
||||||
|
},
|
||||||
};
|
};
|
||||||
Rc::new(Self { cwd: cwd_new })
|
Rc::new(Self { cwd: cwd_new })
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,24 +1,34 @@
|
||||||
use yew::prelude::*;
|
use yew::prelude::*;
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
|
||||||
|
#[derive(Properties, PartialEq)]
|
||||||
|
pub struct Props {
|
||||||
|
pub starting_path: Option<std::path::PathBuf>,
|
||||||
|
pub starting_files: Option<Vec<crate::data::FileEntry>>,
|
||||||
|
}
|
||||||
|
|
||||||
#[function_component(Landing)]
|
#[function_component(Landing)]
|
||||||
pub fn landing() -> HtmlResult {
|
pub fn landing(props: &Props) -> HtmlResult {
|
||||||
|
#[cfg_attr(target_arch = "wasm32", allow(unused_variables))]
|
||||||
|
let starting_path = props.starting_path.clone();
|
||||||
|
#[cfg_attr(target_arch = "wasm32", allow(unused_variables))]
|
||||||
|
let starting_files = props.starting_files.clone();
|
||||||
let server_ctx = use_prepared_state!(
|
let server_ctx = use_prepared_state!(
|
||||||
async move |_| -> super::ServerCtx { super::build_server_ctx().await }, ()
|
async move |_| -> super::ServerCtx { super::build_server_ctx(starting_path, starting_files).await }, ()
|
||||||
)?.expect("Missing server-provided context");
|
)?.expect("Missing server-provided context");
|
||||||
|
|
||||||
let fs_ctx = use_reducer(super::dir::FilesystemCtx::init);
|
let fs_ctx = use_reducer(|| super::dir::FilesystemCtx::init(&props.starting_path));
|
||||||
|
|
||||||
if server_ctx.root_dir.is_some() {
|
if server_ctx.root_dir.is_some() {
|
||||||
let scheme = if server_ctx.ssl {"https"} else {"http"}.to_owned();
|
let scheme = if server_ctx.ssl {"https"} else {"http"}.to_owned();
|
||||||
let domain = server_ctx.domain.clone();
|
let domain = server_ctx.domain.clone();
|
||||||
|
let prepared_files = server_ctx.files.clone();
|
||||||
let path = fs_ctx.cwd.to_string_lossy().to_string();
|
let path = fs_ctx.cwd.to_string_lossy().to_string();
|
||||||
Ok(html! {
|
Ok(html! {
|
||||||
<ContextProvider<Rc<super::ServerCtx>> context={server_ctx}>
|
<ContextProvider<Rc<super::ServerCtx>> context={server_ctx}>
|
||||||
<img src="/banner.png" class={classes!("yarrr-proof-of-purchase")}/>
|
<img src="/banner.png" class={classes!("yarrr-proof-of-purchase")}/>
|
||||||
<ContextProvider<super::dir::FilesystemContext> context={fs_ctx}>
|
<ContextProvider<super::dir::FilesystemContext> context={fs_ctx}>
|
||||||
<super::dir::FileExplorer {scheme} {domain} {path}/>
|
<super::dir::FileExplorer {scheme} {domain} {path} prepared_path={props.starting_path.clone().unwrap_or("".into()).to_string_lossy().to_string()} {prepared_files}/>
|
||||||
</ContextProvider<super::dir::FilesystemContext>>
|
</ContextProvider<super::dir::FilesystemContext>>
|
||||||
</ContextProvider<Rc<super::ServerCtx>>>
|
</ContextProvider<Rc<super::ServerCtx>>>
|
||||||
})
|
})
|
||||||
|
|
|
@ -4,7 +4,7 @@ mod landing;
|
||||||
mod rant;
|
mod rant;
|
||||||
mod server_ctx;
|
mod server_ctx;
|
||||||
|
|
||||||
pub use app::App;
|
pub use app::{App, Props as AppProps};
|
||||||
pub use landing::Landing;
|
pub use landing::Landing;
|
||||||
pub use rant::Rant;
|
pub use rant::Rant;
|
||||||
pub use server_ctx::ServerCtx;
|
pub use server_ctx::ServerCtx;
|
||||||
|
|
|
@ -5,14 +5,18 @@ pub struct ServerCtx {
|
||||||
pub domain: String,
|
pub domain: String,
|
||||||
pub root_dir: Option<std::path::PathBuf>,
|
pub root_dir: Option<std::path::PathBuf>,
|
||||||
pub ssl: bool,
|
pub ssl: bool,
|
||||||
|
pub path: Option<std::path::PathBuf>,
|
||||||
|
pub files: Option<Vec<crate::data::FileEntry>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(target_arch = "wasm32"))]
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
pub async fn build_server_ctx() -> ServerCtx {
|
pub async fn build_server_ctx(path: Option<std::path::PathBuf>, files: Option<Vec<crate::data::FileEntry>>) -> ServerCtx {
|
||||||
let args = crate::api::CliArgs::get();
|
let args = crate::api::CliArgs::get();
|
||||||
ServerCtx {
|
ServerCtx {
|
||||||
domain: args.domain,
|
domain: args.domain,
|
||||||
root_dir: args.dir,
|
root_dir: args.dir,
|
||||||
ssl: args.ssl,
|
ssl: args.ssl,
|
||||||
|
path,
|
||||||
|
files,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue