Load up thumbnails of all images in the image directory

This commit is contained in:
Savanni D'Gerinel 2024-11-12 00:16:54 -05:00
parent 6416931c67
commit 69ef3c3892
9 changed files with 112 additions and 34 deletions

2
Cargo.lock generated
View File

@ -4858,6 +4858,8 @@ version = "0.1.0"
dependencies = [ dependencies = [
"authdb", "authdb",
"http 1.1.0", "http 1.1.0",
"mime 0.3.17",
"mime_guess 2.0.5",
"serde 1.0.210", "serde 1.0.210",
"serde_json", "serde_json",
"tokio", "tokio",

View File

@ -12,3 +12,5 @@ serde_json = { version = "*" }
serde = { version = "1" } serde = { version = "1" }
tokio = { version = "1", features = [ "full" ] } tokio = { version = "1", features = [ "full" ] }
warp = { version = "0.3" } warp = { version = "0.3" }
mime_guess = "2.0.5"
mime = "0.3.17"

View File

@ -1,21 +1,58 @@
use std::sync::{Arc, RwLock}; use std::{
io::Read,
path::PathBuf,
sync::{Arc, RwLock},
};
#[derive(Debug)] #[derive(Debug)]
pub enum AppError { pub enum AppError {
JsonError(serde_json::Error) JsonError(serde_json::Error),
} }
#[derive(Clone, Debug)]
pub struct AppState {
pub image_base: PathBuf,
}
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct AppState {} pub struct Core(pub Arc<RwLock<AppState>>);
#[derive(Clone, Debug)]
pub struct Core(Arc<RwLock<AppState>>);
impl Core { impl Core {
pub fn new() -> Self { pub fn new() -> Self {
Self(Arc::new(RwLock::new(AppState {}))) Self(Arc::new(RwLock::new(AppState {
} image_base: PathBuf::from("/home/savanni/Pictures"),
})))
} }
pub fn get_file(&self, file_name: String) -> Vec<u8> {
let mut full_path = self.0.read().unwrap().image_base.clone();
full_path.push(&file_name);
println!("path: {:?}", full_path);
let mut content: Vec<u8> = Vec::new();
let mut file = std::fs::File::open(&full_path).unwrap();
file.read_to_end(&mut content).unwrap();
content
}
pub fn available_images(&self) -> Vec<String> {
std::fs::read_dir(&self.0.read().unwrap().image_base)
.unwrap()
.filter_map(|entry| match entry {
Ok(entry_) => match mime_guess::from_path(entry_.path()).first() {
Some(mime) if mime.type_() == mime::IMAGE => Some(
entry_
.path()
.file_name()
.and_then(|filename| filename.to_str())
.and_then(|filename| Some(filename.to_owned()))
.unwrap(),
),
_ => None,
},
Err(_) => None,
})
.collect()
}
}

View File

@ -4,6 +4,8 @@ use authdb::{AuthDB, AuthToken};
use http::{response::Response, status::StatusCode, Error}; use http::{response::Response, status::StatusCode, Error};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use crate::core::Core;
pub async fn handle_auth( pub async fn handle_auth(
auth_ctx: &AuthDB, auth_ctx: &AuthDB,
auth_token: AuthToken, auth_token: AuthToken,
@ -37,17 +39,6 @@ pub fn handle_playing_field() -> PlayArea {
} }
} }
pub fn handle_file(file_name: String) -> Vec<u8> { pub fn handle_file(core: Core, file_name: String) -> Vec<u8> {
let mut full_path = PathBuf::new(); core.get_file(file_name)
full_path.push("/home");
full_path.push("savanni");
full_path.push("Pictures");
full_path.push(&file_name);
println!("path: {:?}", full_path);
let mut content: Vec<u8> = Vec::new();
let mut file = std::fs::File::open(&full_path).unwrap();
file.read_to_end(&mut content).unwrap();
content
} }

View File

@ -93,22 +93,39 @@ async fn handle_rejection(err: warp::Rejection) -> Result<impl Reply, Infallible
#[tokio::main] #[tokio::main]
pub async fn main() { pub async fn main() {
let core = core::Core::new();
let route_playing_field = let route_playing_field =
warp::path!("api" / "v1" / "field").map(move || warp::reply::json(&handle_playing_field())); warp::path!("api" / "v1" / "field").map(move || warp::reply::json(&handle_playing_field()));
let route_static_file = warp::path!("api" / "v1" / "file" / String) let route_image = warp::path!("api" / "v1" / "image" / String)
.and(warp::get()) .and(warp::get())
.map(move |file_name| { .map({
let bytes = handle_file(file_name); let core = core.clone();
move |file_name| {
let core = core.clone();
let mimetype = mime_guess::from_path(&file_name).first().unwrap();
let bytes = handle_file(core, file_name);
Response::builder() Response::builder()
.header("application-type", "image/jpeg") .header("application-type", mimetype.to_string())
.body(bytes) .body(bytes)
.unwrap() .unwrap()
}
});
let route_available_images = warp::path!("api" / "v1" / "image").and(warp::get()).map({
let core = core.clone();
move || {
let core = core.clone();
Response::builder()
.header("Access-Control-Allow-Origin", "*")
.body(serde_json::to_string(&core.available_images()).unwrap())
}
}); });
let core = core::Core::new();
let filter = route_playing_field let filter = route_playing_field
.or(route_static_file) .or(route_image)
.or(route_available_images)
.recover(handle_rejection); .recover(handle_rejection);
let server = warp::serve(filter); let server = warp::serve(filter);

View File

@ -11,11 +11,17 @@ export class Client {
imageUrl(imageId: string) { imageUrl(imageId: string) {
const url = new URL(this.base); const url = new URL(this.base);
url.pathname = `/api/v1/file/${imageId}`; url.pathname = `/api/v1/image/${imageId}`;
return url; return url;
} }
async playingField(): Promise<PlayingField> { async playingField(): Promise<PlayingField> {
return { backgroundImage: "su-pearl.png" }; return { backgroundImage: "trans-ferris.jpg" };
}
async availableImages(): Promise<string[]> {
const url = new URL(this.base);
url.pathname = `/api/v1/image`;
return fetch(url).then((response) => response.json());
} }
} }

View File

@ -1,5 +1,6 @@
import React, { useEffect, useState } from 'react'; import React, { useEffect, useState } from 'react';
import { Client, PlayingField } from '../../client'; import { Client, PlayingField } from '../../client';
import { ThumbnailComponent } from '../Thumbnail';
import './PlayingField.css'; import './PlayingField.css';
interface PlayingFieldProps { interface PlayingFieldProps {
@ -12,9 +13,16 @@ export const PlayingFieldComponent = ({ client }: PlayingFieldProps) => {
client.playingField().then((field) => setField(field)); client.playingField().then((field) => setField(field));
}, [client]); }, [client]);
const [images, setImages] = useState<string[]>([]);
useEffect(() => {
client.availableImages().then((images) => setImages(images));
}, [client]);
const backgroundUrl = field && client.imageUrl(field.backgroundImage); const backgroundUrl = field && client.imageUrl(field.backgroundImage);
return (<div className="playing-field"> return (<div className="playing-field">
<div> Left Panel </div> <div>
{images.map((imageName) => <ThumbnailComponent client={client} imageId={imageName} />)}
</div>
<div className="playing-field__background"> {backgroundUrl && <img src={backgroundUrl.toString()} alt="playing field" />} </div> <div className="playing-field__background"> {backgroundUrl && <img src={backgroundUrl.toString()} alt="playing field" />} </div>
<div> Right Panel </div> <div> Right Panel </div>
</div>) </div>)

View File

@ -0,0 +1,4 @@
.thumbnail > img {
max-width: 300px;
max-height: 300px;
}

View File

@ -0,0 +1,11 @@
import React, { useEffect, useState } from 'react';
import { Client } from '../client';
import './Thumbnail.css';
interface ThumbnailProps {
client: Client
imageId: string
}
export const ThumbnailComponent = ({ client, imageId }: ThumbnailProps) =>
(<div className="thumbnail"> <img src={client.imageUrl(imageId).toString()} /> </div>)