Skip to content

Commit

Permalink
Merge branch 'master' into feat/extensions
Browse files Browse the repository at this point in the history
  • Loading branch information
kimlimjustin committed Nov 28, 2021
2 parents 3748932 + 73a856a commit e98c1dd
Show file tree
Hide file tree
Showing 20 changed files with 296 additions and 34 deletions.
7 changes: 7 additions & 0 deletions src-tauri/Cargo.lock

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

1 change: 1 addition & 0 deletions src-tauri/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ normpath = "0.3.1"
lazy_static = "1.4.0"
font-loader = "0.11.0"
clap = "3.0.0-beta.5"
glob = "0.3.0"

[features]
default = [ "custom-protocol" ]
Expand Down
59 changes: 57 additions & 2 deletions src-tauri/src/files_api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,21 @@ extern crate open;
extern crate trash;
use crate::file_lib;
use crate::storage;
use glob::{glob_with, MatchOptions};
#[cfg(not(target_os = "macos"))]
use normpath::PathExt;
use notify::{raw_watcher, RawEvent, RecursiveMode, Watcher};
use serde_json::Value;
use std::sync::mpsc::channel;
use tauri::api::path::local_data_dir;
use tauri::Manager;

#[cfg(windows)]
use std::os::windows::prelude::*;
#[cfg(target_os = "windows")]
use std::os::windows::process::CommandExt;

#[derive(serde::Serialize)]
#[derive(serde::Serialize, Clone)]
pub struct FileMetaData {
file_path: String,
basename: String,
Expand All @@ -34,6 +37,7 @@ pub struct FileMetaData {
created: SystemTime,
is_trash: bool,
}

#[derive(serde::Serialize)]
pub struct TrashMetaData {
file_path: String,
Expand Down Expand Up @@ -531,7 +535,6 @@ pub async fn listen_dir(dir: String, window: tauri::Window) -> Result<String, St
}
}
}
use tauri::api::path::local_data_dir;

#[cfg(target_os = "windows")]
#[tauri::command]
Expand Down Expand Up @@ -575,3 +578,55 @@ pub async fn calculate_files_total_size(files: Vec<String>) -> u64 {
}
total_size
}

#[tauri::command]
pub async fn search_in_dir(
dir_path: String,
pattern: String,
window: tauri::Window,
) -> Vec<FileMetaData> {
let glob_pattern = match dir_path.as_ref() {
"xplorer://Home" => match cfg!(target_os = "windows") {
true => "C://**/".to_string() + &pattern,
false => "~/**/".to_string() + &pattern,
},
_ => format!("{}/**/{}", dir_path, pattern),
};
let glob_option = MatchOptions {
case_sensitive: false,
require_literal_separator: false,
require_literal_leading_dot: false,
..Default::default()
};
let continue_search = std::sync::Arc::new(std::sync::Mutex::new(true));
let id = window.listen("unsearch", {
let continue_search = continue_search.clone();
move |_| {
*continue_search.lock().unwrap() = false;
}
});
let mut files = Vec::new();
let glob_result = glob_with(&glob_pattern, glob_option).unwrap();
for entry in glob_result {
if continue_search.lock().unwrap().clone() {
match entry {
Ok(path) => {
files.push(
get_file_properties(path.to_str().unwrap().to_string())
.await
.unwrap(),
);
if files.len() % 100 == 0 {
window.emit("search_partial_result", files.clone()).unwrap();
files.clear();
}
}
Err(e) => println!("{:?}", e),
}
} else {
break;
}
}
window.unlisten(id);
files
}
1 change: 1 addition & 0 deletions src-tauri/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,7 @@ fn main() {
files_api::get_file_properties,
files_api::extract_icon,
files_api::calculate_files_total_size,
files_api::search_in_dir,
drives::get_drives,
storage::write_data,
storage::read_data,
Expand Down
28 changes: 28 additions & 0 deletions src/Api/directory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import type FileMetaData from '../Typings/fileMetaData';
import { getCurrent } from '@tauri-apps/api/window';
import { UnlistenFn } from '@tauri-apps/api/event';
let listener: UnlistenFn;
let searchListener: UnlistenFn;
interface DirectoryData {
files: FileMetaData[];
number_of_files: number;
Expand Down Expand Up @@ -89,6 +90,33 @@ class DirectoryAPI {
async getSize(): Promise<number> {
return await invoke('get_dir_size', { dir: this.dirName });
}

/**
* Stop all searching progress
* @returns {Promise<boolean>}
*/
async stopSearching(): Promise<boolean> {
const listenerExist = searchListener !== null && searchListener !== undefined;
searchListener?.();
await getCurrent().emit('unsearch');
return listenerExist;
}

/**
* Search for a file/folder in a directory
* @param {string} pattern - glob pattern
* @param {FileMetaData[] => void} callback - progress callback
* @returns {any}
*/
async search(pattern: string, callback: (partialFound: FileMetaData[]) => void): Promise<FileMetaData[]> {
searchListener = await getCurrent().listen('search_partial_result', (res) => {
if (searchListener !== null && searchListener !== undefined) callback(res.payload as FileMetaData[]);
});
const res = await invoke('search_in_dir', { dirPath: this.dirName, pattern });
await this.stopSearching();
searchListener = null;
return res as FileMetaData[];
}
}

export default DirectoryAPI;
2 changes: 2 additions & 0 deletions src/Components/ContextMenu/configs/bodyMenu.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,7 @@ const BodyMenu = async (target: HTMLElement, filePath: string): Promise<contextM
{
menu: await Translate('Restore all files'),
visible: _focusingPath === 'xplorer://Trash',
icon: 'delete',
role: () => {
const filePaths = [...document.querySelectorAll<HTMLElement>('.file')].map((file) => unescape(file.dataset.path));
Restore(filePaths);
Expand All @@ -176,6 +177,7 @@ const BodyMenu = async (target: HTMLElement, filePath: string): Promise<contextM
{
menu: await Translate('Permanently delete all files'),
visible: _focusingPath === 'xplorer://Trash',
icon: 'delete',
role: () => {
const filePaths = [...document.querySelectorAll<HTMLElement>('.file')].map((file) => unescape(file.dataset.path));
Purge(filePaths);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ const MultipleSelectedMenu = async (_: HTMLElement, _filePath: string): Promise<
{
menu: await Translate('Restore these files'),
visible: _focusingPath === 'xplorer://Trash',
icon: 'delete',
role: () => {
const filePaths = [];
for (const element of getSelected()) {
Expand All @@ -89,8 +90,9 @@ const MultipleSelectedMenu = async (_: HTMLElement, _filePath: string): Promise<
},
},
{
menu: await Translate('Permanently these files'),
menu: await Translate('Permanently delete these files'),
visible: _focusingPath === 'xplorer://Trash',
icon: 'delete',
role: () => {
const filePaths = [];
for (const element of getSelected()) {
Expand Down
36 changes: 12 additions & 24 deletions src/Components/Files/File Operation/rename.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,31 +14,19 @@ import ConfirmDialog from '../../Prompt/confirm';
* @returns {void}
*/
const Rename = (filePath: string): void => {
Ask('Rename', 'New File Name:', { value: basename(filePath) }).then(
async (newName: string) => {
const target =
getDirname(newName) === '.'
? joinPath(await focusingPath(), newName)
: joinPath(getDirname(filePath), newName);
if (await new FileAPI(target).exists()) {
const confirm = await ConfirmDialog(
'File Exists',
'The new name already exists, do you want to overwrite it?',
'No'
);
if (!confirm) return;
}
try {
new OperationAPI(filePath, target).rename();
} catch (err) {
PromptError(
'Error renaming file',
`Failed to rename ${filePath} [${err}]`
);
}
OperationLog('rename', unescape(filePath), target);
Ask('Rename', 'New File Name:', { value: basename(filePath) }).then(async (newName: string) => {
const target = getDirname(newName) === '.' ? joinPath(await focusingPath(), newName) : joinPath(getDirname(filePath), newName);
if (await new FileAPI(target).exists()) {
const confirm = await ConfirmDialog('File Exists', 'The new name already exists, do you want to overwrite it?', 'No');
if (!confirm) return;
}
);
try {
new OperationAPI(filePath, target).rename();
} catch (err) {
PromptError('Error renaming file', `Failed to rename ${filePath} [${err}]`);
}
OperationLog('rename', unescape(filePath), target);
});
};

export default Rename;
104 changes: 104 additions & 0 deletions src/Components/Files/File Operation/search.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
import focusingPath from '../../Functions/focusingPath';
import DirectoryAPI from '../../../Api/directory';
import { startLoading, stopLoading } from '../../Functions/Loading/loading';
import displayFiles from '../../Open/displayFiles';
import changePosition from '../../Functions/changePosition';
import { LOAD_IMAGE } from '../../Functions/lazyLoadingImage';
import { updateTheme } from '../../Theme/theme';
import { OpenLog } from '../../Functions/log';
import { OpenDir } from '../../Open/open';
let being_watch: string;

const stopSearchingProcess = async (): Promise<void> => {
being_watch = null;
if (await new DirectoryAPI('').stopSearching()) stopLoading();
};

/**
* Process searching
* @param {string} to_search - glob pattern to search
* @param {string} search_in - dir to search into
* @returns {Promise<void>}
*/
const processSearch = async (to_search: string, search_in: string): Promise<void> => {
const MAIN_ELEMENT = document.getElementById('workspace');
MAIN_ELEMENT.innerHTML = '';
if (!to_search.length) OpenDir(search_in);
startLoading();
const search_path = `Search: [[${to_search}]] inside [[${search_in}]]`;
changePosition(search_path);
let foundSomething = false;

const finalResult = await new DirectoryAPI(search_in).search(to_search, async (partialFound) => {
let _el = document.createElement('div') as HTMLElement;
foundSomething = true;
if (document.querySelector<HTMLInputElement>('.path-navigator').value.startsWith('Search: '))
_el = await displayFiles(partialFound, search_path, _el, null, true);
for (const childEl of _el.children) {
MAIN_ELEMENT.appendChild(childEl);
}
});
const _el = document.createElement('div');
MAIN_ELEMENT.appendChild(await displayFiles(finalResult, search_path, _el, null, true));
if (!finalResult.length && !foundSomething) {
MAIN_ELEMENT.classList.add('empty-dir-notification');
MAIN_ELEMENT.innerText = "Can't find specified query";
}
stopLoading();
updateTheme('grid');
LOAD_IMAGE();
OpenLog(search_path);
};

/**
* Get the real focusing path
* @returns {Promise<string>}
*/
const getFocusingPath = async (): Promise<string> => {
let _focusingPath = await focusingPath();
if (_focusingPath.startsWith('Search: ')) {
const splitByInsideKeyword = _focusingPath.split(' inside ');
if (splitByInsideKeyword.length === 2) {
_focusingPath = splitByInsideKeyword[1].slice(2, -2);
} else {
for (let i = 0; i < splitByInsideKeyword.length; i++) {
if (splitByInsideKeyword[i]?.endsWith(']]') && splitByInsideKeyword[i + 1]?.startsWith('[[')) {
_focusingPath = splitByInsideKeyword
.slice(i + 1)
.join(' inside ')
.slice(2, -2);
}
}
}
}
return _focusingPath;
};
/**
* Initialize search feature in Xplorer
* @returns {Promise<void>}
*/
const Search = async (): Promise<void> => {
let listener: ReturnType<typeof setTimeout>;
document.querySelector('.search-bar').addEventListener('keydown', async (e: KeyboardEvent) => {
clearTimeout(listener);
if (e.ctrlKey && e.key === 'f') {
return;
} else if (e.key === 'Enter') {
const value = (e.target as HTMLInputElement).value;
if (value !== being_watch) {
processSearch(value, await getFocusingPath());
being_watch = value;
}
} else {
listener = setTimeout(async () => {
const value = (e.target as HTMLInputElement).value;
if (value !== being_watch) {
processSearch(value, await getFocusingPath());
being_watch = value;
}
}, 1000);
}
});
};
export default Search;
export { processSearch, stopSearchingProcess };
6 changes: 5 additions & 1 deletion src/Components/Functions/changePosition.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,11 @@ const changePosition = async (newPath: string, forceChange = false): Promise<voi
tabs.tabs[String(tabs.focus)] = _focusingTab;

document.getElementById(`tab${tabs.focus}`).querySelector<HTMLInputElement>('#tab-position').innerText = await Translate(
basename(newPath) === '' ? newPath : basename(newPath)
document.querySelector<HTMLInputElement>('.path-navigator').value.startsWith('Search: ')
? newPath
: basename(newPath) === ''
? newPath
: basename(newPath)
);
Storage.set(`tabs-${windowName}`, tabs);
changeSelectedAllStatus();
Expand Down
4 changes: 3 additions & 1 deletion src/Components/Layout/hover.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ const Hover = (): void => {
if (hoveringElement?.dataset?.path && displayName) hoveringElement.querySelector('.file-grid-filename').innerHTML = displayName;
return;
}
const isOnSearch = document.querySelector<HTMLInputElement>('.path-navigator').value.startsWith('Search: ');

hoveringElement?.classList?.remove('hovering');

const target = (e.target as HTMLElement)?.dataset?.path ? (e.target as HTMLElement) : ((e.target as HTMLElement)?.parentNode as HTMLElement);
Expand All @@ -48,7 +50,7 @@ const Hover = (): void => {
timeOut = window.setTimeout(async () => {
displayName = filenameGrid.innerHTML;
const path = unescape(target.dataset.path);
filenameGrid.innerHTML = getBasename(path);
filenameGrid.innerHTML = isOnSearch ? path : getBasename(path);
target?.classList?.add('hovering');

const previewImageOnHover = (await Storage.get('appearance')).previewImageOnHover ?? true;
Expand Down
Loading

0 comments on commit e98c1dd

Please sign in to comment.