Skip to content

Commit

Permalink
Merge pull request #295 from opeolluwa/master
Browse files Browse the repository at this point in the history
new patch
opeolluwa authored May 2, 2024

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
2 parents dc76a4e + f846469 commit b56a38d
Showing 62 changed files with 1,737 additions and 812 deletions.
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -33,4 +33,6 @@ core/views
mobile/node_modules

core/test.db
core/src/scripts
core/src/scripts

public/test
22 changes: 12 additions & 10 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
install-toolchain:


install:



run:


build:
npm install -g yarn
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
#install the system depencies
install-deps:
yarn install
cd core && cargo build
# run the dev server
dev:
yarn tauri dev
# build the binary for the current OS
build:
yarn tauri build
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -16,7 +16,7 @@ _⚠️ the application is still a work in progress, hence, Some features are mi

## Description

FIleSync is an offline file-sharing application designed for Windows, Mac, and Linux operating systems. It allows users to transfer files seamlessly between PCs over WiFi without an internet connection.
FIleSync is an wifi file-sharing application designed for Windows, Mac, and Linux operating systems. It allows users to transfer files seamlessly between PCs over WiFi without an internet connection.

## Getting Started

7 changes: 7 additions & 0 deletions core/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 3 additions & 2 deletions core/Cargo.toml
Original file line number Diff line number Diff line change
@@ -14,7 +14,7 @@ tauri-build = {version = "1.3", features = [] }

[dependencies]
assert_fs = "1.0.13"
axum = {version = "0.6.12", features = ["multipart", "headers"] }
axum = { version = "0.6.12", features = ["multipart", "headers"] }
battery = "0.7.8"
chrono = "0.4.31"
dirs = "5.0.0"
@@ -37,7 +37,7 @@ serde_json = "1.0"
sqlx = {version = "0.6.2", features = ["sqlite", "runtime-tokio-native-tls"] }
sys-info = "0.9.1"
sysinfo = "0.29.2"
tauri = {version = "1.2", features = ["app-all", "dialog-all", "fs-all", "path-all", "shell-open"] }
tauri = {version = "1.2", features = [ "window-all", "app-all", "dialog-all", "fs-all", "path-all", "shell-open"] }
tokio = {version = "1.26.0", features = ["full"] }
tokio-util = {version = "0.7", features = ["io"] }
tower = {version = "0.4", features = ["util"] }
@@ -52,6 +52,7 @@ ts-rs = "7.0.0"
pnet = "0.34.0"
open = "5.1.2"
path-absolutize = "3.1.1"
fs_extra = "1.3.0"
[features]
# this feature is used for production builds or when `devPath` points to the filesystem
# DO NOT REMOVE!!
2 changes: 1 addition & 1 deletion core/src/api/fs.rs
Original file line number Diff line number Diff line change
@@ -11,7 +11,7 @@ use serde_json::{json, Value};
use ts_rs::TS;

use crate::database::{self, TransferHistory, TransferHistoryBuilder};
use crate::fs::search::search_files;
// use crate::fs::search::search_files;
use crate::wifi::ip_manager;
use tokio::io::AsyncReadExt;

14 changes: 14 additions & 0 deletions core/src/api/utils.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use std::net::Ipv4Addr;
use local_ip_address::local_ip;

use crate::{
utils::{system_info::SystemInformation, CommandData},
@@ -33,3 +34,16 @@ pub fn get_ip_address() -> String {
pub fn get_system_information() -> CommandData<SystemInformation> {
CommandData::ok("connected system information ", SystemInformation::new())
}



#[tauri::command]
pub fn is_connected_to_wifi() -> CommandData<bool> {
// the app would have a local ip address if it is connected to a network
// else it would crash, this is leveraged to check the network status
let has_ip_addr = local_ip().ok();
if has_ip_addr.is_none() {
return CommandData::ok("wifi status", false);
}
CommandData::ok("server address", true)
}
2 changes: 1 addition & 1 deletion core/src/api/wifi.rs
Original file line number Diff line number Diff line change
@@ -2,7 +2,7 @@
/// once this is done pass the network conf
use crate::{
utils::CommandData,
wifi::{hotspot, network_scanner, WifiHotspotConfig},
wifi::{network_scanner, WifiHotspotConfig},
};

#[tauri::command]
10 changes: 2 additions & 8 deletions core/src/app_state.rs
Original file line number Diff line number Diff line change
@@ -2,6 +2,7 @@
#[allow(dead_code)]
use crate::database::{Settings, TransferHistory};

#[derive(Default)]
pub struct State {
pub settings: Settings,
pub transfer_history: Vec<TransferHistory>,
@@ -19,11 +20,4 @@ impl State {
}
}

impl Default for State {
fn default() -> Self {
Self {
settings: Settings::default(),
transfer_history: Vec::new(),
}
}
}

7 changes: 5 additions & 2 deletions core/src/fs/file.rs
Original file line number Diff line number Diff line change
@@ -48,7 +48,7 @@ impl File {
pub fn from_path(path: &PathBuf) -> Self {
let file_name = path.file_name().unwrap().to_str().unwrap();
let file_path = path.display().to_string();
let file_size: u128 = path.size_on_disk().unwrap_or(0).into();
let mut file_size: u128 = path.size_on_disk().unwrap_or(0).into();
let file_format = path
.extension()
.unwrap_or_default()
@@ -57,11 +57,14 @@ impl File {

let is_folder = path.is_dir();

if is_folder {
file_size = fs_extra::dir::get_size(path).unwrap_or(0) as u128;
}
let is_hidden = path
.file_name()
.unwrap()
.to_str()
.map(|s| s.starts_with("."))
.map(|s| s.starts_with('.'))
.unwrap();

Self {
18 changes: 10 additions & 8 deletions core/src/main.rs
Original file line number Diff line number Diff line change
@@ -3,6 +3,7 @@

extern crate uptime_lib;

use crate::api::fs_api::get_transfer_history;
/**
* the application is structured thus
* src
@@ -11,15 +12,14 @@ extern crate uptime_lib;
* __fs
* ...
*/


use crate::api::fs_api::read_dir;
use crate::api::fs_api::get_transfer_history;
use crate::api::fs_api::read_dir;
// Import individual items from crate::api::settings
use crate::api::settings::{get_settings, update_settings};

// Import individual items from crate::api::utils
use crate::api::utils::{generate_qr_code, get_ip_address, get_system_information};
use crate::api::utils::{
generate_qr_code, get_ip_address, get_system_information, is_connected_to_wifi,
};

// Import individual items from crate::api::wifi
use crate::api::wifi::{create_wifi_hotspot, kill_wifi_hotspot, scan_wifi};
@@ -45,8 +45,8 @@ lazy_static! {
*
* Herein the server port made globally available, this allow for ease of sharing same with file upload directory
*/
pub static ref SERVER_PORT: u16 =
portpicker::pick_unused_port().expect("failed to get an unused port");
pub static ref SERVER_PORT: u16 = 18005;
// portpicker::pick_unused_port().expect("failed to get an unused port");
pub static ref UPLOAD_DIRECTORY: std::string::String = String::from("filesync");

/* create a database in the home dir and / save files to $HOME/filesync/.dat */
@@ -86,7 +86,6 @@ fn main() -> Result<(), tauri::Error> {
..Default::default()
};

scan_wifi();
// run core the server in a separate thread from tauri
tauri::async_runtime::spawn(http_server::core_server());
// run the UI code and the IPC (internal Procedure Call functions)
@@ -100,9 +99,12 @@ fn main() -> Result<(), tauri::Error> {
get_system_information,
get_transfer_history,
get_settings,
is_connected_to_wifi,
update_settings,
read_dir,
scan_wifi // download_file, TODO: implement file transfering between peers
])
.run(tauri::generate_context!())

// Ok(())
}
10 changes: 4 additions & 6 deletions core/src/server/http_server.rs
Original file line number Diff line number Diff line change
@@ -9,6 +9,7 @@ use axum::extract::DefaultBodyLimit;

use crate::database::Database;
use crate::server::router;

use crate::server::routes::handle_404;
use crate::SERVER_PORT;

@@ -52,17 +53,14 @@ pub async fn core_server() {
.parse::<std::net::SocketAddr>()
.expect("invalid socket address");

tracing::debug!("server running on http://{}", &ip_address.to_string());
println!(" the server port is http://{}", ip_address);

// build our application with the required routes
let app = router::app()
.layer(file_limit)
.layer(cors_layer)
.layer(tower_http::trace::TraceLayer::new_for_http());
// .fallback(handle_404);

// add a fallback service for handling routes to unknown paths
// let app = app.fallback(handle_404);
.layer(tower_http::trace::TraceLayer::new_for_http())
.fallback(handle_404);

// run the server
axum::Server::bind(&ip_address)
4 changes: 3 additions & 1 deletion core/src/server/router.rs
Original file line number Diff line number Diff line change
@@ -6,13 +6,15 @@ use axum::{
};

use super::routes::{
accept_file_upload, download_file, file_upload_form, get_file, system_information,
accept_file_upload, download_file, file_upload_form, get_file, health_check, ping_server, system_information
};

// the app is moved here to allow sharing across test modules
pub fn app() -> Router {
Router::new()
.route("/", get(ping_server))
.route("/upload", post(accept_file_upload).get(file_upload_form))
.route("/health", post(accept_file_upload).get(health_check))
.route("/api/sys-info", get(system_information))
.route("/api/download", get(download_file))
.route("/api/file", get(get_file))
49 changes: 43 additions & 6 deletions core/src/server/routes.rs
Original file line number Diff line number Diff line change
@@ -20,12 +20,12 @@ use crate::UPLOAD_DIRECTORY;
#[derive(Debug, Serialize, Deserialize)]

/// destructure query parameter
pub struct Params {
pub struct QueryParams {
pub file_path: String,
}
/// accept file path amd return the file
pub async fn download_file(Query(params): Query<Params>) -> impl IntoResponse {
let Params { file_path } = params;
pub async fn download_file(Query(params): Query<QueryParams>) -> impl IntoResponse {
let QueryParams { file_path } = params;

let Some(file) = tokio::fs::File::open(file_path).await.ok() else {
return Err((
@@ -64,7 +64,7 @@ pub async fn system_information() -> (StatusCode, Json<CommandData<SystemInforma
)
}

// return an html page to receive file upload
/// return an html page to receive file upload
pub async fn file_upload_form() -> Html<&'static str> {
Html(
r#"
@@ -235,9 +235,46 @@ pub async fn handle_404() -> impl IntoResponse {
)
}


/// health check handler
pub async fn health_check() -> impl IntoResponse {
(
StatusCode::OK,
axum::response::Json(serde_json::json!({
"success":true,
"message":String::from("Server is ready to accept connection"),
})),
)
}

/// ping the server
pub async fn ping_server() -> impl IntoResponse {
"FileSync Server 1.0.0"
}



/// for a given file path, return the file the the used as a downloadable one
pub async fn get_file() {
unimplemented!()
pub async fn get_file(Query(QueryParams { file_path }): Query<QueryParams>) -> impl IntoResponse {
// `File` implements `AsyncRead`
let file = match tokio::fs::File::open(file_path).await {
Ok(file) => file,
Err(err) => return Err((StatusCode::NOT_FOUND, format!("File not found: {}", err))),
};
// convert the `AsyncRead` into a `Stream`
let stream = ReaderStream::new(file);
// convert the `Stream` into an `axum::body::HttpBody`
let body = StreamBody::new(stream);

let headers =[
(header::CONTENT_TYPE, "text/toml; charset=utf-8"),
(
header::CONTENT_DISPOSITION,
"attachment; filename=\"Cargo.toml\"",
),
];

Ok((headers, body))
}

#[cfg(test)]
4 changes: 2 additions & 2 deletions core/src/utils/fs.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use std::process::Command;
use sysinfo::{DiskExt, System, SystemExt};

use sysinfo::{DiskExt, SystemExt};
// pub mod storage_information;
/// a function to compute file size
/// accept files size in byte and parse it to human readable KB, MB, TB, GB e.t.c
35 changes: 17 additions & 18 deletions core/src/wifi/hotspot/windows.rs
Original file line number Diff line number Diff line change
@@ -2,7 +2,7 @@ use mockall::predicate::*;
use mockall::*;
use std::process::Command;
fn command_output_to_string(output: &Vec<u8>) -> String {
let _output = std::str::from_utf8(&output);
let _output = std::str::from_utf8(output);
match _output {
Ok(s) => s.to_string(),
Err(_) => "None".to_string(),
@@ -41,9 +41,8 @@ fn create_ap_with_hotspotcommand<T: HotSpotCommand>(hotspotcommand: T, ssid: &st
Ok(_) => println!("Started a new hotspot"),
Err(e) => {
println!("{}", e);
return;
}
};
}
}

/// Turnoff the hotspot
@@ -84,7 +83,7 @@ impl HotSpotCommand for DefaultHotSpotCommand {
Ok(false)
}
}
Err(_) => Err(format!("Failed to get the wlan drivers information")),
Err(_) => Err("Failed to get the wlan drivers information".to_string()),
}
}

@@ -100,16 +99,16 @@ impl HotSpotCommand for DefaultHotSpotCommand {
.output()
{
Ok(output) => {
if let true = output.status.success() {
return Ok(());
if output.status.success() {
Ok(())
} else {
return Err(format!(
Err(format!(
"Failed to create hotspot: {}",
command_output_to_string(&output.stderr)
));
))
}
}
Err(_) => return Err(format!("Failed to execute create hotspot through netsh.")),
Err(_) => Err("Failed to execute create hotspot through netsh.".to_string()),
}
}

@@ -121,12 +120,12 @@ impl HotSpotCommand for DefaultHotSpotCommand {
Ok(output) => {
if command_output_to_string(&output.stdout).contains("The hosted network started.")
{
return Ok(());
Ok(())
} else {
return Err(format!("Failed to start the hosted network"));
Err("Failed to start the hosted network".to_string())
}
}
Err(_) => return Err(format!("Failed to start the hosted network through netsh")),
Err(_) => Err("Failed to start the hosted network through netsh".to_string()),
}
}

@@ -137,12 +136,12 @@ impl HotSpotCommand for DefaultHotSpotCommand {
{
Ok(output) => {
if command_output_to_string(&output.stdout).contains("The hosted network stoped.") {
return Ok(());
Ok(())
} else {
return Err(format!("Failed to stop the hosted network"));
Err("Failed to stop the hosted network".to_string())
}
}
Err(_) => return Err(format!("Failed to stop the hosted network through netsh")),
Err(_) => Err("Failed to stop the hosted network through netsh".to_string()),
}
}
}
@@ -212,7 +211,7 @@ mod tests {
mock_hotspotcommand
.expect_create_hotspot()
.times(1)
.returning(|_, _| Err(format!("Failed")))
.returning(|_, _| Err("Failed".to_string()))
.in_sequence(&mut seq);
mock_hotspotcommand.expect_start_hotspot().times(0);
create_ap_with_hotspotcommand(mock_hotspotcommand, ssid, key);
@@ -237,7 +236,7 @@ mod tests {
mock_hotspotcommand
.expect_start_hotspot()
.times(1)
.returning(|| Err(format!("Failed")))
.returning(|| Err("Failed".to_string()))
.in_sequence(&mut seq);
create_ap_with_hotspotcommand(mock_hotspotcommand, ssid, key);
}
@@ -259,7 +258,7 @@ mod tests {
mock_hotspotcommand
.expect_stop_hotspot()
.times(1)
.returning(|| Err(format!("Failed")));
.returning(|| Err("Failed".to_string()));
turn_off_hotspot_with_hotspotcommand(mock_hotspotcommand);
}
}
3 changes: 3 additions & 0 deletions core/tauri.conf.json
Original file line number Diff line number Diff line change
@@ -13,6 +13,9 @@
"tauri": {
"allowlist": {
"all": false,
"window": {
"all": true
},
"app": {
"all": true,
"hide": true,
21 changes: 13 additions & 8 deletions next.config.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
/** @type {import('next').NextConfig} */

const nextConfig = {
reactStrictMode: true,
// Note: This feature is required to use NextJS Image in SSG mode.
// See https://nextjs.org/docs/messages/export-image-api for different workarounds.
images: {
unoptimized: true,
},
}
reactStrictMode: true,
// Note: This feature is required to use NextJS Image in SSG mode.
// See https://nextjs.org/docs/messages/export-image-api for different workarounds.
images: {
unoptimized: true,
},

module.exports = nextConfig
// webpack: (config) => {
// config.resolve.alias.canvas = false;
// return config;
// },
};

module.exports = nextConfig;
9 changes: 7 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -11,23 +11,28 @@
"export": "next export",
"start": "next start",
"tauri": "tauri",
"lint": "next lint"
"lint": "next lint",
"postbuild": "sh ./scripts/copy_renderer_files"
},
"dependencies": {
"@cyntler/react-doc-viewer": "^1.14.1",
"@headlessui/react": "^1.7.13",
"@heroicons/react": "^2.0.16",
"@tauri-apps/api": "^1.3.0",
"antd": "^5.6.2",
"axios": "^1.6.8",
"iqons": "^1.0.4",
"next": "^13.2.4",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-file-viewer": "^1.2.1",
"react-identicon": "^1.0.2",
"react-identicons": "^1.2.5",
"react-qr-code": "^2.0.11",
"react-router-dom": "^6.9.0",
"tauri-plugin-sqlite-api": "github:lzdyes/tauri-plugin-sqlite#v0.1.1",
"tauri-plugin-store-api": "https://github.com/tauri-apps/tauri-plugin-store#v1"
"tauri-plugin-store-api": "https://github.com/tauri-apps/tauri-plugin-store#v1",
"viewerjs": "^1.11.6"
},
"devDependencies": {
"@tailwindcss/forms": "^0.5.3",
6 changes: 4 additions & 2 deletions render/index.html → public/renderer.html
Original file line number Diff line number Diff line change
@@ -3,9 +3,11 @@
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Assets Renderer</title>
<title>Document</title>
</head>
<body>

<h1>
render here
</h1>
</body>
</html>
File renamed without changes.
21 changes: 21 additions & 0 deletions public/renderer/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<head>
<link href="https://vjs.zencdn.net/8.10.0/video-js.css" rel="stylesheet" />

<!-- If you'd like to support IE8 (for Video.js versions prior to v7) -->
<!-- <script src="https://vjs.zencdn.net/ie8/1.1.2/videojs-ie8.min.js"></script> -->
</head>

<body>
<video id="my-video" class="video-js" controls preload="auto" width="640" height="264" poster="MY_VIDEO_POSTER.jpg"
data-setup="{}">
<source src="MY_VIDEO.mp4" type="video/mp4" />
<source src="MY_VIDEO.webm" type="video/webm" />
<p class="vjs-no-js">
To view this video please enable JavaScript, and consider upgrading to a
web browser that
<a href="https://videojs.com/html5-video-support/" target="_blank">supports HTML5 video</a>
</p>
</video>

<script src="https://vjs.zencdn.net/8.10.0/video.min.js"></script>
</body>
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
1 change: 1 addition & 0 deletions react-file-viewer.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
declare module "react-file-viewer";
4 changes: 4 additions & 0 deletions scripts/copy_renderer_files
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
#!bin/bash


cp -r renderer ./out/media_renderer
7 changes: 0 additions & 7 deletions src/components/Codecs/AudioCodec.tsx

This file was deleted.

7 changes: 0 additions & 7 deletions src/components/Codecs/DocumentCodec.tsx

This file was deleted.

7 changes: 0 additions & 7 deletions src/components/Codecs/MusicCodec.tsx

This file was deleted.

7 changes: 0 additions & 7 deletions src/components/Codecs/VideoCodec.tsx

This file was deleted.

20 changes: 10 additions & 10 deletions src/components/MemoryInformation.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
import { WifiStatusContext } from "@/store/wifi-status";
import { useContext } from "react";

// use this to display the available memory
export const MemoryInformation = ({
systemName,
@@ -8,11 +11,10 @@ export const MemoryInformation = ({
usedMemory: string;
totalMemory: string;
}) => {
const { data: isConnectedToWifi } = useContext(WifiStatusContext);

const freeMemory =
Number(totalMemory?.split(" ")[0]) - Number(usedMemory?.split(" ")[0]);
// const memBarWidth =
// Math.round(freeMemory / Number(totalMemory?.split(" ")[0])) + "%";

const memBarWidth = "56%";
return (
<div
@@ -24,19 +26,17 @@ export const MemoryInformation = ({
}}
>
<div className="flex justify-between mb-2 px-4">
{/* {
<span className=" font-medium text-blue-700 text-sm capitalize ">
{systemName}
</span>
} */}

<span className=" font-medium text-blue-700 text-sm">
{usedMemory} of {totalMemory}
</span>
</div>
<div className="w-fill bg-gray-200 rounded-md mx-4 h-2">
<div
className="bg-app-400 h-2 rounded-full"
className={
isConnectedToWifi
? "bg-app-400 h-2 rounded-full"
: "bg-gray-400 h-2 rounded-full"
}
style={{ width: memBarWidth }}
></div>
</div>
4 changes: 2 additions & 2 deletions src/components/history/FileTransferCard.tsx
Original file line number Diff line number Diff line change
@@ -10,7 +10,7 @@ import {
PlayCircleIcon,
} from "@heroicons/react/24/outline";
import Image from "next/image";
import { getFileIcon } from "../thumbnail";
import { getFileIcon } from "../thumbnail/media-icon-maker";

// the required data to render the file card component
// the data will be passed dynamically
@@ -71,7 +71,7 @@ function FileIcon({ fileType }: { fileType: string }) {
return (
<>
<Image
src={thumbnail}
src={thumbnail.icon}
height={120}
width={120}
alt="file card icon"
4 changes: 2 additions & 2 deletions src/components/loaders/LoaderCircle.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
function HelloWorld() {
function Loader() {
return (
<>
<span className="loader block my-10"></span>
@@ -43,4 +43,4 @@ function HelloWorld() {
);
}

export default HelloWorld;
export default Loader;
133 changes: 0 additions & 133 deletions src/components/loaders/LoaderDevices.tsx

This file was deleted.

102 changes: 0 additions & 102 deletions src/components/loaders/LoaderSpaceShuttle.tsx

This file was deleted.

40 changes: 40 additions & 0 deletions src/components/loaders/LoaderWifi.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
function Loader() {
return (
<>
<span className="loader block my-10"></span>
<style jsx>{`
.loader,
.loader:before {
display: inline-block;
border: 20px double transparent;
border-top-color: #7EA8F9;
border-radius: 50%;
box-sizing: border-box;
}
.loader {
padding: 8px;
animation: wifiLoading 1s ease-in infinite;
}
.loader:before {
content: "";
width: 0;
height: 0;
}
@keyframes wifiLoading {
0% {
border-style: none;
}
100% {
border-style: double;
}
}
`}</style>
</>
);
}

export default Loader;
Original file line number Diff line number Diff line change
@@ -8,6 +8,7 @@ export interface Route {
action?: () => any; // action that will be executed when the route is clicked
path: string; // the path string
isActive?: any;
disabled?: boolean;
}

export default function NavigationTab({
@@ -16,16 +17,40 @@ export default function NavigationTab({
action,
path,
name,
disabled,
}: Route) {
const [currentIcon, setIcon] = useState(icon);
const router = useRouter();

if (disabled) {
return (
<div onClick={action}>
<div
className={
"flex items-left justify-start lg:items-start lg:my-6 my-4 rounded ease-in-out py-3 px-1 lg:pl-2 first:mt-4 text-gray-500 cursor-pointer"
}
>
<span className="cursor-pointer">
<span className="sr-only">{path}</span>
<div className="gap-2 justify-left mx-4 flex capitalize">
{router.pathname == path.trim() ? alternateIcon : currentIcon}
<span className="">{name}</span>
</div>
</span>
</div>
</div>
);
}

return (
<div onClick={action}>
<Link
href={path}
className={router.pathname == path.trim()
? "flex items-left justify-start lg:items-start lg:my-6 my-4 rounded ease-in-out text-app bg-app-50 py-3 px-1 lg:pl-2 first:mt-4 cursor-pointer"
: "flex items-left justify-start lg:items-start lg:my-6 my-4 rounded ease-in-out hover:text-app py-3 px-1 lg:pl-2 first:mt-4 text-gray-500 cursor-pointer"}
className={
router.pathname == path.trim()
? "flex items-left justify-start lg:items-start lg:my-6 my-4 rounded ease-in-out text-app bg-app-50 py-3 px-1 lg:pl-2 first:mt-4 cursor-pointer"
: "flex items-left justify-start lg:items-start lg:my-6 my-4 rounded ease-in-out hover:text-app py-3 px-1 lg:pl-2 first:mt-4 text-gray-500 cursor-pointer"
}
onBlur={() => setIcon(icon)}
onMouseEnter={() => setIcon(alternateIcon)}
onClick={() => setIcon(alternateIcon)}
25 changes: 0 additions & 25 deletions src/components/nav/QrConnect.tsx

This file was deleted.

65 changes: 16 additions & 49 deletions src/components/nav/index.tsx
Original file line number Diff line number Diff line change
@@ -9,95 +9,61 @@ import {
} from "@heroicons/react/24/outline";
import {
ClockIcon as SolidClockIcon,
Cog8ToothIcon as SolidCog8ToothIcon,
FolderOpenIcon as SolidFolderIconOpen,
HomeIcon as SolidHomeIcon,
Cog8ToothIcon as SolidCog8ToothIcon, HomeIcon as SolidHomeIcon,
InformationCircleIcon as SolidInformationIcon,
ShareIcon as SolidShareIcon,
SignalIcon as SolidSignalIcon,
SignalIcon as SolidSignalIcon
} from "@heroicons/react/24/solid";
import { goToPage as gotoPage } from "@/utils";
import NavigationTab, { Route } from "./NavigationTab";
import { useEffect, useState } from "react";
import { SystemInformation } from "@/store/sys-info";
import { invoke } from "@tauri-apps/api/tauri";
import NavigationTab, { Route } from "./NavItem";
import { useContext } from "react";
import {
SystemInformationContext
} from "@/store/sys-info";
import { MemoryInformation } from "../MemoryInformation";
import { message } from "@tauri-apps/api/dialog";
import { open } from "@tauri-apps/api/dialog";
import { WifiStatusContext } from "@/store/wifi-status";

export default function Navigation() {
/**
* @function openFileManager - opens a file manager
* @returns {Array<Files>} an array of selected files
*/
const openFileManager = async () => /* : Array<File> */ {
try {
const selectedFilePath = await open({
directory: false,
multiple: true,
// filters: allowedExtension,
// defaultPath: await pictureDir(),
});
// upload select file with tauri upload plugin
} catch (err) {
message((err as Error).message, {
title: "Access error",
type: "error",
});
}
};
let [systemInformation, setSystemInformation] = useState(
{} as SystemInformation
const { data: isConnectedToWifi } = useContext(WifiStatusContext);
const { availableDisk, usedDisk, systemName } = useContext(
SystemInformationContext
);

useEffect(() => {
// fetch sys information from app core
invoke("get_system_information").then((sysInfo) => {
setSystemInformation((sysInfo as any).data);
});
}, []);

const routes: Route[] = [
{
path: "/",
icon: <HomeIcon className="w-6 h-6" />,
name: "home",
alternateIcon: <SolidHomeIcon className="w-6 h-6" />,
action: () => gotoPage({ routePath: "/" }),
},
{
icon: <SignalIcon className="w-6 h-6" />,
name: "Connect Device",
alternateIcon: <SolidSignalIcon className="w-6 h-6" />,
action: () => gotoPage({ routePath: "/connection" }),
path: "/connection",
},
{
path: "/share",
icon: <ShareIcon className="w-6 h-6" />,
name: "Share files",
alternateIcon: <SolidShareIcon className="w-6 h-6" />,
action: () => gotoPage({ routePath: "share" }),
},
{
path: "/history",
icon: <ClockIcon className="w-6 h-6" />,
name: "Transfer History",
alternateIcon: <SolidClockIcon className="w-6 h-6" />,
action: () => gotoPage({ routePath: "history" }),
},

{
path: "/settings",
icon: <Cog8ToothIcon className="w-6 h-6" />,
alternateIcon: <SolidCog8ToothIcon className="w-6 h-6" />,
action: () => gotoPage({ routePath: "settings" }),
name: "settings",
},
{
path: "/about",
icon: <InformationCircleIcon className="w-6 h-6" />,
alternateIcon: <SolidInformationIcon className="w-6 h-6" />,
action: () => gotoPage({ routePath: "about" }),
name: "About",
},
];
@@ -121,14 +87,15 @@ export default function Navigation() {
action={route.action}
alternateIcon={route.alternateIcon}
path={route.path}
disabled={Boolean(isConnectedToWifi)=== false}
/>
))}
</div>

<MemoryInformation
systemName={systemInformation.systemName}
usedMemory={systemInformation.usedDisk}
totalMemory={systemInformation.availableDisk}
systemName={systemName}
usedMemory={usedDisk}
totalMemory={availableDisk}
/>
</nav>
</>
32 changes: 32 additions & 0 deletions src/components/thumbnail/icon-renderer.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
// provide file and folder icon
import { StaticImageData } from "next/image";
import { getFileIcon } from "./media-icon-maker";
import folderIcon from "@/assets/common/folder-icon.png";
import Image from "next/image";

export interface ThumbnailIconInterface {
isFolder: boolean;
fileFormat: string;
}

export default function ThumbnailIcon({
isFolder,
fileFormat,
}: ThumbnailIconInterface) {
let thumbnail: StaticImageData;
if (isFolder) {
thumbnail = folderIcon;
} else {
thumbnail = getFileIcon(fileFormat).icon;
}

return (
<Image
src={thumbnail}
height={144}
width={144}
alt="file card icon"
className="w-[32px] mr-4"
/>
);
}
375 changes: 65 additions & 310 deletions src/components/thumbnail/index.tsx
Original file line number Diff line number Diff line change
@@ -1,324 +1,79 @@
import { FileTransferStatus } from "@/store/context";
import { computeFileSize, isClient } from "@/utils";
import imageIcon from "@/assets/common/image.png";
import audioIcon from "@/assets/common/audio.png";
import presentationIcon from "@/assets/common/presentation.png";
import pdfIcon from "@/assets/common/pdf.png";
import videoIcon from "@/assets/common/video.png";
import csvIcon from "@/assets/common/csv.png";
import defaultIcon from "@/assets/common/default.png";
import archiveIcon from "@/assets/common/archived.png";
import documentIcon from "@/assets/common/document.png";
import textIcon from "@/assets/common/text.png";
import svgIcon from "@/assets/common/svg.png";
import Image, { StaticImageData } from "next/image";
import folderIcon from "@/assets/common/folder-icon.png";
import { File } from "../../../core/bindings/File";

// to interface with audio files coming from the application core
// the type extends the AppData type
export interface FileInterface extends File {
}

// the required data to render the file card component
export interface FileCardInterface extends FileInterface {
action?: () => void; // the action to perform when the file is clicked, for example it can be used to play an audio file
}

// the required data to render the file card component
// the data will be passed dynamically
"use client";

type TFileType = {
fileType: string;
fileName: string;
fileSize: number;
status: FileTransferStatus;
// status: 'error' | 'done' | 'pending' | 'completed' | 'downloading' | 'paused';
};
export interface FileTransferInterface {
fileType: string;
fileName: string;
fileSize: number;
status: FileTransferStatus;
}
import { computeFileSize } from "@/utils";
import { useRouter } from "next/router";
import { useState } from "react";
import { File } from "../../../core/bindings/File";
import PreviewMedia from "./preview-media";
import { getFileIcon } from "./media-icon-maker";
import ThumbnailIcon from "./icon-renderer";
import { Modal } from "antd";

interface Props extends FileCardInterface {
}
export default function FileCard({
fileName,
fileFormat,
filePath,
fileSize,
isFolder,
isHidden,
}: File) {
const [openModal, setOpenModal] = useState(false);
const fileMeta = getFileIcon(fileFormat);
const router = useRouter();

export default function FileCard(
{ fileName, fileFormat, filePath, fileSize, action, isFolder, isHidden }: Props,
) {
let thumbnail: StaticImageData ;
// if it is a folder open in folder renderer
// otherwise open in file renderer
let path:string;
if (isFolder) {
thumbnail = folderIcon;
path = `/render?filePath=${filePath}&fileType=${fileFormat}&isFolder=${isFolder}`;
} else {
thumbnail = getFileIcon(fileFormat);
path = `/render?filePath=${filePath}&fileType=${fileFormat}&isFolder=${isFolder}`;
}

return (
<div
onClick={action}
className="flex w-full hover:shadow hover:rounded-lg rouned bg-[#f9fbfe] flex-wrap items-center gap-2 cursor-pointer px-4 py-2 last:mb-10 "
>
<div>
{
<Image
src={thumbnail} // Route of the image file
height={144} // Desired size with correct aspect ratio
width={144} // Desired size with correct aspect ratio
alt="file card icon"
className="w-[32px] mr-4" // automatic height calculation
/>
}
</div>
<div className="flex flex-col justify-between mt-3">
<h6 className=" dark:text-gray-500 small overflow-clip w-[240px] lg:w-[400px] truncate">
{fileName}
</h6>

<div
className="flex gap-3 mt[1.5px] text-gray-600 text-xs height={30} // Desired size with correct aspect ratio
<>
<Modal
title={"Preview Media"}
open={openModal}
onOk={() => setOpenModal(false)}
onCancel={() => setOpenModal(false)}
centered
okButtonProps={{ hidden: true }}
cancelButtonProps={{ hidden: true }}
width={600}
>
<>
<div className="h-[600px] ">
<PreviewMedia fileType={fileFormat} filePath={filePath}/>
</div>
</>
</Modal>
<div
onClick={() => {
isFolder ? router.push(path) : setOpenModal(true);
}}
className="flex w-full hover:shadow hover:rounded-lg rouned bg-[#f9fbfe] flex-wrap items-center gap-2 cursor-pointer px-4 py-2 last:mb-10 "
>
<ThumbnailIcon isFolder={isFolder} fileFormat={fileFormat} />
<div className="flex flex-col justify-between mt-3">
<h6 className=" dark:text-gray-500 small overflow-clip w-[240px] lg:w-[400px] truncate select-none">
{fileName}
</h6>

<div
className="flex gap-3 mt[1.5px] text-gray-600 text-xs height={30} // Desired size with correct aspect ratio
width={30} "
>
<span>{computeFileSize(fileSize as unknown as number)}</span>{" "}
<span>{/**file duration goes here */}</span>
>
<span className="select-none">
{computeFileSize(fileSize as unknown as number)}
</span>
</div>
</div>
</div>
</div>
</>
);
}

export function getFileIcon(fileExtension: string) {
const imageExtensions = [
"jpg",
"jpeg",
"png",
"gif",
"bmp",
"tiff",
"raw",
"svg",
"ai",
"eps",
"psd",
"xcf",
"ico",
"webp",
"jxr",
"hdr",
"tif",
"exif",
"pgm",
"ppm",
"pbm",
"pnm",
"heic",
"heif",
"dng",
"cr2",
"nef",
"arw",
"orf",
"rw2",
"sr2",
"raf",
"mrw",
"pef",
"x3f",
"3fr",
"kdc",
"srw",
"nrw",
"rwz",
"rwl",
"iiq",
"rw1",
"r3d",
"fff",
"yuv",
"cin",
"dpx",
"jp2",
"j2k",
"jpf",
"jpx",
"jpm",
"mj2",
"wdp",
"hdp",
"dds",
"pvr",
"tga",
"cur",
"icl",
"thm",
"sai",
"ora",
"pdn",
"kra",
"cpt",
"pdd",
"mng",
"apng",
"svgz",
"emf",
"wmf",
];
const documentExtensions = [
"doc",
"docx",
"rtf",
"odt",
"ods",
"odp",
"odg",
"odp",
"fodp",
"otp",
"doc",
"dot",
"docx",
"docm",
"dotx",
"dotm",
"docb",
"odt",
"fodt",
];
const svgExtensions = ["svg"];
const textExtensions = ["txt"];
const audioExtensions = [
"3gp",
"aa",
"aac",
"aax",
"act",
"aiff",
"alac",
"amr",
"ape",
"au",
"awb",
"dss",
"dvf",
"flac",
"gsm",
"iklax",
"ivs",
"m4a",
"m4b",
"m4p",
"mmf",
"movpkg",
"mp3",
"mpc",
"msv",
"nmf",
"ogg",
"oga",
"mogg",
"opus",
"ra",
"rm",
"raw",
"rf64",
"sln",
"tta",
"voc",
"vox",
"wav",
"wma",
"wv",
"webm",
"8svx",
"cda",
];
const pdfExtensions = ["pdf"];
const csvExtensions = ["csv"];
const presentationExtensions = [
"ppt",
"pot",
"pps",
"pptx",
"pptm",
"potx",
"potm",
"ppam",
"ppsx",
"ppsm",
"sldx",
"sldm",
"thmx",
];
const videoExtensions = [
"mp4",
"mkv",
"webm",
"flv",
"vob",
"ogv",
"ogg",
"drc",
"gif",
"gifv",
"mng",
"avi",
"MTS",
"MT2S",
"TS",
"mov",
"qt",
"wmv",
"yuv",
"rm",
"rmvb",
"viv",
"asf",
"amv",
"m4p",
"m4v",
"mpg",
"mp2",
"mpeg",
"mpe",
"mpv",
"m2v",
"svi",
"3gp",
"3g2",
"mxf",
"roq",
"nsv",
"f4v",
"f4p",
"f4a",
"f4b",
];
const archiveExtensions = ["zip", "rar", "tar", "gz"];
const extension = fileExtension.toLocaleLowerCase().trim();

if (imageExtensions.includes(extension)) {
return imageIcon;
} else if (audioExtensions.includes(extension)) {
return audioIcon;
} else if (pdfExtensions.includes(extension)) {
return pdfIcon;
} else if (csvExtensions.includes(extension)) {
return csvIcon;
} else if (
presentationExtensions.includes(extension)
) {
return presentationIcon;
} else if (videoExtensions.includes(extension)) {
return videoIcon;
} else if (archiveExtensions.includes(extension)) {
return archiveIcon;
} else if (documentExtensions.includes(extension)) {
return documentIcon;
} else if (textExtensions.includes(extension)) {
return textIcon;
} else if (svgExtensions.includes(extension)) {
return svgIcon;
} else {
return defaultIcon;
}
}

export interface FileInterface extends File {}
247 changes: 247 additions & 0 deletions src/components/thumbnail/media-icon-maker.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,247 @@
// render the folder icon
import { FileType } from "./preview-media";
import archiveIcon from "@/assets/common/archived.png";
import audioIcon from "@/assets/common/audio.png";
import csvIcon from "@/assets/common/csv.png";
import defaultIcon from "@/assets/common/default.png";
import documentIcon from "@/assets/common/document.png";
import imageIcon from "@/assets/common/image.png";
import pdfIcon from "@/assets/common/pdf.png";
import presentationIcon from "@/assets/common/presentation.png";
import svgIcon from "@/assets/common/svg.png";
import textIcon from "@/assets/common/text.png";
import videoIcon from "@/assets/common/video.png";

export function getFileIcon(fileExtension: string) {
const imageExtensions = [
"jpg",
"jpeg",
"png",
"gif",
"bmp",
"tiff",
"raw",
"svg",
"ai",
"eps",
"psd",
"xcf",
"ico",
"webp",
"jxr",
"hdr",
"tif",
"exif",
"pgm",
"ppm",
"pbm",
"pnm",
"heic",
"heif",
"dng",
"cr2",
"nef",
"arw",
"orf",
"rw2",
"sr2",
"raf",
"mrw",
"pef",
"x3f",
"3fr",
"kdc",
"srw",
"nrw",
"rwz",
"rwl",
"iiq",
"rw1",
"r3d",
"fff",
"yuv",
"cin",
"dpx",
"jp2",
"j2k",
"jpf",
"jpx",
"jpm",
"mj2",
"wdp",
"hdp",
"dds",
"pvr",
"tga",
"cur",
"icl",
"thm",
"sai",
"ora",
"pdn",
"kra",
"cpt",
"pdd",
"mng",
"apng",
"svgz",
"emf",
"wmf",
];
const documentExtensions = [
"doc",
"docx",
"rtf",
"odt",
"ods",
"odp",
"odg",
"odp",
"fodp",
"otp",
"doc",
"dot",
"docx",
"docm",
"dotx",
"dotm",
"docb",
"odt",
"fodt",
];
const svgExtensions = ["svg"];
const textExtensions = ["txt"];
const audioExtensions = [
"3gp",
"aa",
"aac",
"aax",
"act",
"aiff",
"alac",
"amr",
"ape",
"au",
"awb",
"dss",
"dvf",
"flac",
"gsm",
"iklax",
"ivs",
"m4a",
"m4b",
"m4p",
"mmf",
"movpkg",
"mp3",
"mpc",
"msv",
"nmf",
"ogg",
"oga",
"mogg",
"opus",
"ra",
"rm",
"raw",
"rf64",
"sln",
"tta",
"voc",
"vox",
"wav",
"wma",
"wv",
"webm",
"8svx",
"cda",
];
const pdfExtensions = ["pdf"];
const csvExtensions = ["csv"];
const presentationExtensions = [
"ppt",
"pot",
"pps",
"pptx",
"pptm",
"potx",
"potm",
"ppam",
"ppsx",
"ppsm",
"sldx",
"sldm",
"thmx",
];
const videoExtensions = [
"mp4",
"mkv",
"webm",
"flv",
"vob",
"ogv",
"ogg",
"drc",
"gif",
"gifv",
"mng",
"avi",
"MTS",
"MT2S",
"TS",
"mov",
"qt",
"wmv",
"yuv",
"rm",
"rmvb",
"viv",
"asf",
"amv",
"m4p",
"m4v",
"mpg",
"mp2",
"mpeg",
"mpe",
"mpv",
"m2v",
"svi",
"3gp",
"3g2",
"mxf",
"roq",
"nsv",
"f4v",
"f4p",
"f4a",
"f4b",
];
const archiveExtensions = ["zip", "rar", "tar", "gz"];
const extension = fileExtension.toLocaleLowerCase().trim();

if (imageExtensions.includes(extension)) {
return { type: FileType.Image, icon: imageIcon };
} else if (audioExtensions.includes(extension)) {
return { type: FileType.Audio, icon: audioIcon };
} else if (pdfExtensions.includes(extension)) {
return { type: FileType.PDF, icon: pdfIcon };
} else if (csvExtensions.includes(extension)) {
return { type: FileType.CSV, icon: csvIcon };
} else if (presentationExtensions.includes(extension)) {
return { type: FileType.Presentation, icon: presentationIcon };
} else if (videoExtensions.includes(extension)) {
return { type: FileType.Video, icon: videoIcon };
} else if (archiveExtensions.includes(extension)) {
return { type: FileType.Archive, icon: archiveIcon };
} else if (documentExtensions.includes(extension)) {
return { type: FileType.Document, icon: documentIcon };
} else if (textExtensions.includes(extension)) {
return { type: FileType.Text, icon: textIcon };
} else if (svgExtensions.includes(extension)) {
return { type: FileType.SVG, icon: svgIcon };
} else {
return { type: FileType.Default, icon: defaultIcon };
}
}
41 changes: 41 additions & 0 deletions src/components/thumbnail/preview-media.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
"use client";

import { SystemInformationContext } from "@/store/sys-info";
import { useContext } from "react";
/// give preview for differet file type
import FileViewer from "react-file-viewer";

const isClient = typeof window !== "undefined";

export enum FileType {
Image = "image",
Audio = "audio",
PDF = "pdf",
CSV = "csv",
Presentation = "presentation",
Video = "video",
Archive = "archive",
Document = "document",
Text = "text",
SVG = "svg",
Default = "default",
}

interface Props {
fileType: string;
filePath: string;
}

export default function PreviewMedia({ fileType, filePath }: Props) {
const { serverBaseUrl } = useContext(SystemInformationContext);

const fileUrl = isClient
? `${serverBaseUrl}/api/file?file_path=${filePath}`
: "";

return (
<div className="w-full h-full">
<FileViewer fileType={fileType} filePath={fileUrl} />
</div>
);
}
32 changes: 32 additions & 0 deletions src/components/thumbnail/thumbnail-types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@

// to interface with audio files coming from the application core

import { FileTransferStatus } from "@/store/context";

// the type extends the AppData type
export interface FileInterface extends File {}

// the required data to render the file card component
export interface FileCardInterface extends FileInterface {
action?: () => void; // the action to perform when the file is clicked, for example it can be used to play an audio file
}

// the required data to render the file card component
// the data will be passed dynamically

export type TFileType = {
fileType: string;
fileName: string;
fileSize: number;
status: FileTransferStatus;
};


export interface FileTransferInterface {
fileType: string;
fileName: string;
fileSize: number;
status: FileTransferStatus;
}

export interface Props extends FileCardInterface {}
86 changes: 86 additions & 0 deletions src/pages/[...slug].tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
"use client";

import { useRouter } from "next/router";
import { useEffect, useState } from "react";
import { useSearchParams } from "next/navigation";
import FileCard from "@/components/thumbnail";
import { AppData } from "@/types";
import { invoke } from "@tauri-apps/api";
import LoaderCircle from "@/components/loaders/LoaderCircle";
import QuickAccessLayout from "@/components/layout/PageLayout";
import {File} from "../../core/bindings/File"


export default function PreviewMediaPage() {
const [data, setData] = useState(null);
const [isLoading, setLoading] = useState(false);

const router = useRouter();
const searchParams = useSearchParams();
const filePath = searchParams.get("filePath");
const isFolder = searchParams.get("isFolder");
const fileType = searchParams.get("fileType");



const openInWebView = (filePath: string|null) => {
console.log({ filePath });
};

useEffect(() => {
if (isFolder) {
setLoading(true);
invoke("read_dir", { path: filePath?.trim() }).then((res) => {
setData(res as any);
setLoading(false);
});
} else {
setLoading(false);
setData(null);
}
}, [filePath, isFolder]);

// typecast the response into AppData type
const fetchedFiles = data as unknown as AppData<Array<File>>;

// if it is a folder, get the files nd list them
// get the data from the application core
if (isLoading) {
return (
<>
<LoaderCircle />
<h2 className="font-xl font-bold mt-8">Loading...</h2>
<p className="leading-5 text-gray-400">
Please wait while we load your documents. This might take a while.
</p>
</>
);
}

// render them
if (data) {
return (
<QuickAccessLayout
pageTitle={"Document"}
includeSearchBar={true}
searchBarText="search document"
>
<div>
<div className="flex flex-wrap flex-grow gap-4 justify-start mt-12">
{fetchedFiles?.data.map((file, index) => (
<FileCard
key={index}
fileName={file.fileName}
fileSize={file.fileSize}
fileFormat={file.fileFormat}
filePath={file.filePath}
isHidden={file.isHidden}
isFolder={file.isFolder}
/>
))}
</div>
</div>
</QuickAccessLayout>
);
}
}
10 changes: 7 additions & 3 deletions src/pages/_app.tsx
Original file line number Diff line number Diff line change
@@ -3,14 +3,18 @@ import FileStore from "@/store/context";
import SystemInfoStore from "@/store/sys-info";
import "@/styles/globals.css";
import type { AppProps } from "next/app";
import WifiStatus from "@/store/wifi-status";


export default function App({ Component, pageProps }: AppProps) {
return (
<SystemInfoStore>
<FileStore>
<Layout>
<Component {...pageProps} />
</Layout>
<WifiStatus>
<Layout>
<Component {...pageProps} />
</Layout>
</WifiStatus>
</FileStore>
</SystemInfoStore>
);
5 changes: 2 additions & 3 deletions src/pages/connection.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import PageTitle from "@/components/PageTitle";
import Heading from "@/components/Heading";
import Text from "@/components/Text";
import PageLayout from "@/components/layout/PageLayout";
import { Switch, Modal, Button } from "antd";
import { Button, Modal, Switch } from "antd";
import { useState } from "react";
export default function ConnectionPage() {
const [isModalOpen, setIsModalOpen] = useState(false);
@@ -28,7 +27,7 @@ export default function ConnectionPage() {
<>
<PageLayout pageTitle={"Connect Device"} includeSearchBar={false}>
<>
<div className="hidden flex justify-between items-center py-4 rounded-lg px-4">
<div className="hidden justify-between items-center py-4 rounded-lg px-4">
<div className="flex flex-col gap-4">
<Heading context={"Turn on hotspot"} />
<Text
83 changes: 70 additions & 13 deletions src/pages/index.tsx
Original file line number Diff line number Diff line change
@@ -1,26 +1,30 @@
"use client";

import Heading from "@/components/Heading";
import PageTitle from "@/components/PageTitle";
import SearchBar from "@/components/Search";
import Text from "@/components/Text";
import LoaderCircle from "@/components/loaders/LoaderCircle";
import { WifiStatusContext } from "@/store/wifi-status";
import { computeFileSize } from "@/utils";
import { LoadingOutlined } from "@ant-design/icons";
import {
ArchiveBoxIcon,
ComputerDesktopIcon,
CloudArrowDownIcon,
DocumentDuplicateIcon,
FilmIcon,
MusicalNoteIcon,
PhotoIcon,
} from "@heroicons/react/24/outline";
import { ask } from "@tauri-apps/api/dialog";
import { exit, relaunch } from "@tauri-apps/api/process";
import { invoke } from "@tauri-apps/api/tauri";
import { Spin } from "antd";
import Link from "next/link";
import { useEffect, useState } from "react";
import { useContext, useEffect, useState } from "react";
import { CommandData } from "../../core/bindings/CommandData";
import { TransferHistory } from "../../core/bindings/TransferHistory";
import { computeFileSize } from "@/utils";
import Text from "@/components/Text";
import Heading from "@/components/Heading";
import { LoadingOutlined } from "@ant-design/icons";
import { Spin } from "antd";
import { ChartPieIcon, FilmIcon } from "@heroicons/react/24/outline";

interface QuickAccessTab {
name: string;
icon: any;
@@ -59,30 +63,57 @@ const quickAccessTabs: QuickAccessTab[] = [
),
},
{
name: "Desktop",
name: "Downloads",
icon: (
<ComputerDesktopIcon className="rounded-sm my-4 mx-2 flex w-[40px] text-gray-100 " />
<CloudArrowDownIcon className="rounded-sm my-4 mx-2 flex w-[40px] text-gray-100 " />
),
},
];

export default function Main() {
const [data, setData] = useState(null);
const [isLoading, setLoading] = useState(false);
const { data: isConnectedToWifi } = useContext(WifiStatusContext);

//close the application
async function close() {
const yes = await ask(
"Current file transfer may be lost. Do you still want to proceed?",
{ title: "Close", type: "warning" }
);
if (yes) {
await exit(1).then(()=>{
console.log("exited")
}).catch(error => {
console.log(error.message);
});
}
}

// refresh the application
async function refresh() {
await relaunch().then(()=>{
console.log("refreshed")
}).catch(error => {
console.log(error.message);

});
}

// get the data from the application core
useEffect(() => {
setLoading(true);
invoke("get_transfer_history").then((res) => {
setData(res as any);
setLoading(false);
});
// when the data has ben fetched, stop the loading process
setLoading(false);
}, []);

// typecast the response into AppData type
const transferHistory = data as unknown as CommandData<
Array<TransferHistory>
>;

if (isLoading) {
return (
<>
@@ -92,11 +123,37 @@ export default function Main() {
</>
);
}

// if done loading and not connected to wifi
if (!isLoading && !isConnectedToWifi) {
return (
<>
<div className="">
<div className="">
<LoaderCircle />
</div>
<PageTitle title={" Waiting for WiFi Connection"} />
<p className="mt-2 py-4 w-3/4 dark:text-gray-400 leading-2 font-medium">
You should see your system files as soon as you are connected to a
WiFi network
</p>
<div className="flex gap-5">
<button className=" bg-app text-white px-4 py-1 rounded w-24 " onClick={refresh}>
Refresh
</button>
<button className=" px-4 py-1 border-2 text-gray-400 border-gray-400 rounded w-24" onClick={close}>
Exit
</button>
</div>
</div>
</>
);
}
return (
<>
<section>
<SearchBar
onSearch={function (city: string): void {
onSearch={function (): void {
throw new Error("Function not implemented.");
}}
placeholder={"search files"}
18 changes: 2 additions & 16 deletions src/pages/quick-access/audio.tsx
Original file line number Diff line number Diff line change
@@ -6,17 +6,7 @@ import { useEffect, useState } from "react";
import LoaderCircle from "@/components/loaders/LoaderCircle";

export default function Music() {
// display a module to play music
function playMusic(filePath: string) {
const assetUrl = convertFileSrc(filePath);
const audio = document.getElementById("testNode") as HTMLAudioElement;
const source = document.createElement("source");
source.type = "audio/mp3";
source.src = assetUrl;
audio?.appendChild(source);
audio?.load();
console.log("playing ", filePath);
}


const [data, setData] = useState(null);
const [isLoading, setLoading] = useState(false);
@@ -30,8 +20,6 @@ export default function Music() {
setLoading(false);
});
}, []);
// TODO(@opeolluwa): use Tauri Js API to render musicData
// TODO(@opeolluwa) add modal to play audio file, audio and document using web APIs
// typecast the response into AppData type
const musicData = data as unknown as AppData<Array<FileInterface>>;
if (isLoading) {
@@ -63,9 +51,7 @@ export default function Music() {
filePath={file.filePath}
isHidden={file.isHidden}
isFolder={file.isFolder}
action={() => {
console.log("play audio file");
}}

/>
))}
</div>
6 changes: 1 addition & 5 deletions src/pages/quick-access/documents.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,10 @@
import FileCard, { FileInterface } from "@/components/thumbnail";
import QuickAccessLayout from "@/components/layout/PageLayout";
import { AppData, AudioFile } from "@/types";
import { AppData} from "@/types";
import { invoke } from "@tauri-apps/api/tauri";
import { useEffect, useState } from "react";
import { shareFile } from "@/utils";
import LoaderCircle from "@/components/loaders/LoaderCircle";
import { Dir } from "../../../core/bindings/Dir";

const isClient = typeof window !== "undefined";

export default function Document() {
const [data, setData] = useState(null);
@@ -51,7 +48,6 @@ export default function Document() {
fileSize={file.fileSize}
fileFormat={file.fileFormat}
filePath={file.filePath}
action={() => shareFile(file.filePath)}
isHidden={file.isHidden}
isFolder={file.isFolder}
/>
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import FileCard, { FileInterface } from "@/components/thumbnail";
import QuickAccessLayout from "@/components/layout/PageLayout";
import { AppData, AudioFile } from "@/types";
import { AppData} from "@/types";
import { invoke } from "@tauri-apps/api/tauri";
import { useEffect, useState } from "react";
import { shareFile } from "@/utils";
import LoaderCircle from "@/components/loaders/LoaderCircle";


@@ -52,7 +51,6 @@ export default function Document() {
filePath={file.filePath}
isHidden={file.isHidden}
isFolder={file.isFolder}
action={() => shareFile(file.filePath)}
/>
))}
</div>
5 changes: 1 addition & 4 deletions src/pages/quick-access/pictures.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
import FileCard, { FileInterface } from "@/components/thumbnail";
import QuickAccessLayout from "@/components/layout/PageLayout";
import { AppData, AudioFile } from "@/types";
import { AppData} from "@/types";
import { invoke } from "@tauri-apps/api/tauri";
import { useEffect, useState } from "react";
import { shareFile } from "@/utils";
import LoaderCircle from "@/components/loaders/LoaderCircle";

const isClient = typeof window !== "undefined";

export default function Images() {
const [data, setData] = useState(null);
@@ -53,7 +51,6 @@ export default function Images() {
filePath={file.filePath}
isHidden={file.isHidden}
isFolder={file.isFolder}
action={() => shareFile(file.filePath)}
/>
))}
</div>
10 changes: 4 additions & 6 deletions src/pages/quick-access/videos.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
"is client";

import FileCard, { FileInterface } from "@/components/thumbnail";
import QuickAccessLayout from "@/components/layout/PageLayout";
import { AppData, AudioFile } from "@/types";
import { AppData } from "@/types";
import { invoke } from "@tauri-apps/api/tauri";
import { useEffect, useState } from "react";
import { shareFile } from "@/utils";
import LoaderCircle from "@/components/loaders/LoaderCircle";
import path from "path";

const isClient = typeof window !== "undefined";

export default function Video() {
const [data, setData] = useState(null);
@@ -16,7 +15,7 @@ export default function Video() {
// get the data from the application core
useEffect(() => {
setLoading(true);
invoke("read_dir", {path:"videos"}).then((res) => {
invoke("read_dir", { path: "videos" }).then((res) => {
setData(res as any);
setLoading(false);
});
@@ -52,7 +51,6 @@ export default function Video() {
filePath={file.filePath}
isHidden={file.isHidden}
isFolder={file.isFolder}
action={() => shareFile(file.filePath)}
/>
))}
</div>
4 changes: 2 additions & 2 deletions src/store/context.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { ReactNode, createContext, useState } from "react";

interface TFileData {
interface TFileData {
size: number;
type: string;
name: string;
status: FileTransferStatus;
};
}

export enum FileTransferStatus {
DOWNLOADING = "downloading",
32 changes: 32 additions & 0 deletions src/store/wifi-status.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { invoke } from "@tauri-apps/api/tauri";
import { ReactNode, createContext, useEffect, useState } from "react";
import { CommandData } from "../../core/bindings/CommandData";

type WifiStatusInterface = CommandData<boolean>;

export const WifiStatusContext = createContext({} as WifiStatusInterface);

export default function WifiStatus({ children }: { children: ReactNode }) {
const [isConnectedToWifi, setConnectedToWifi] = useState(null);

useEffect(() => {
invoke("is_connected_to_wifi").then((res) => {
setConnectedToWifi(res as any);
});
}, []);

// typecast the wifi response too
const wifiStatus = isConnectedToWifi as unknown as CommandData<boolean>;

return (
<WifiStatusContext.Provider
value={{
data: wifiStatus?.data,
message: wifiStatus?.message,
status: wifiStatus?.status,
}}
>
{children}
</WifiStatusContext.Provider>
);
}
3 changes: 3 additions & 0 deletions src/styles/globals.css
Original file line number Diff line number Diff line change
@@ -2,6 +2,9 @@
@tailwind components;
@tailwind utilities;

*{
user-select: none!important;
}

#layout>aside,
#layout>main {
1 change: 1 addition & 0 deletions tsconfig.json
Original file line number Diff line number Diff line change
@@ -16,6 +16,7 @@
"incremental": true,
"paths": {
"@/*": ["./src/*"],
"@public/*": ["./public/*"],
"@binding/*": ["../core/bindings/*"]
}
},
1 change: 1 addition & 0 deletions viewerjs.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
declare module 'viewerjs'
842 changes: 828 additions & 14 deletions yarn.lock

Large diffs are not rendered by default.

0 comments on commit b56a38d

Please sign in to comment.