diff --git a/src-tauri/src/files_api.rs b/src-tauri/src/files_api.rs index ba4be7b8..d6022c95 100644 --- a/src-tauri/src/files_api.rs +++ b/src-tauri/src/files_api.rs @@ -6,9 +6,11 @@ extern crate notify; extern crate open; extern crate trash; use crate::file_lib; +use crate::storage; #[cfg(not(target_os = "macos"))] use normpath::PathExt; use notify::{raw_watcher, RawEvent, RecursiveMode, Watcher}; +use serde_json::Value; use std::sync::mpsc::channel; #[cfg(windows)] @@ -199,6 +201,18 @@ pub fn is_dir(path: &Path) -> Result { } #[tauri::command] pub async fn read_directory(dir: &Path) -> Result { + let preference = storage::read_data("preference".to_string()); + let preference = match preference { + Ok(result) => result, + Err(_) => return Err("Error reading preference".into()), + }; + let preference: Result = serde_json::from_str(&preference.data); + let preference = match preference { + Ok(result) => result, + Err(_) => return Err("Error parsing preference".into()), + }; + let hide_system_files = &preference["hideSystemFiles"]; + let hide_system_files = hide_system_files.as_bool().unwrap(); let paths = fs::read_dir(dir).map_err(|err| err.to_string())?; let mut number_of_files: u16 = 0; let mut files = Vec::new(); @@ -211,7 +225,12 @@ pub async fn read_directory(dir: &Path) -> Result { skipped_files.push(file_name); continue; } else { - files.push(file_info.unwrap()) + let file_info = file_info.unwrap(); + if hide_system_files && file_info.is_system { + skipped_files.push(file_name); + continue; + } + files.push(file_info) }; } Ok(FolderInformation { @@ -545,3 +564,16 @@ pub async fn extract_icon(file_path: String) -> Result { pub async fn extract_icon(_file_path: String) -> Result { Err("Not supported".to_string()) } + +#[tauri::command] +pub async fn calculate_files_total_size(files: Vec) -> u64 { + let mut total_size: u64 = 0; + for file in files { + let metadata = fs::metadata(file.clone()).unwrap(); + if metadata.is_dir() { + total_size += get_dir_size(file).await; + } + total_size += metadata.len(); + } + total_size +} diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index 103fe703..779f6c12 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -106,6 +106,7 @@ fn main() { files_api::get_dir_size, files_api::get_file_properties, files_api::extract_icon, + files_api::calculate_files_total_size, drives::get_drives, storage::write_data, storage::read_data, diff --git a/src-tauri/src/storage.rs b/src-tauri/src/storage.rs index 66deb98c..2ed6bf32 100644 --- a/src-tauri/src/storage.rs +++ b/src-tauri/src/storage.rs @@ -4,8 +4,8 @@ use tauri::api::path::local_data_dir; #[derive(serde::Serialize)] pub struct StorageData { - data: String, - status: bool, + pub data: String, + pub status: bool, } #[tauri::command] diff --git a/src/Api/files.ts b/src/Api/files.ts index 9480092a..8e7c2e87 100644 --- a/src/Api/files.ts +++ b/src/Api/files.ts @@ -5,15 +5,15 @@ import FileMetaData from '../Typings/fileMetaData'; /** Invoke Rust command to handle files */ class FileAPI { - readonly fileName: string; + readonly fileName: string | string[]; readonly parentDir: string; /** * Construct FileAPI Class * @param {string} fileName - Your file path * @param {string} parentDir - Parent directory of the file */ - constructor(fileName: string, parentDir?: string) { - if (parentDir) { + constructor(fileName: string | string[], parentDir?: string) { + if (parentDir && typeof fileName === 'string') { this.parentDir = parentDir; this.fileName = joinPath(parentDir, fileName); } else this.fileName = fileName; @@ -23,14 +23,20 @@ class FileAPI { * @returns {Promise} */ readFile(): Promise { - return new Promise((resolve) => { - fs.readTextFile(this.fileName).then((fileContent) => resolve(fileContent)); + return new Promise((resolve, reject) => { + if (typeof this.fileName === 'string') { + fs.readTextFile(this.fileName).then((fileContent) => resolve(fileContent)); + } else { + reject('File name is not a string'); + } }); } async readBuffer(): Promise { const Buffer = require('buffer/').Buffer; - return Buffer.from(await fs.readBinaryFile(this.fileName)); + if (typeof this.fileName === 'string') { + return Buffer.from(await fs.readBinaryFile(this.fileName)); + } } /** * Open file on default app @@ -44,7 +50,7 @@ class FileAPI { * @returns {string} */ readAsset(): string { - return tauri.convertFileSrc(this.fileName); + return typeof this.fileName === 'string' ? tauri.convertFileSrc(this.fileName) : ''; } /** * Read file and return as JSON @@ -67,10 +73,12 @@ class FileAPI { * @returns {Promise} */ async createFile(): Promise { - await invoke('create_dir_recursive', { - dirPath: dirname(this.fileName), - }); - return await invoke('create_file', { filePath: this.fileName }); + if (typeof this.fileName === 'string') { + await invoke('create_dir_recursive', { + dirPath: dirname(this.fileName), + }); + return await invoke('create_file', { filePath: this.fileName }); + } } /** * Read properties of a file @@ -90,9 +98,21 @@ class FileAPI { }); } + /** + * Extract icon of executable file + * @returns {Promise} + */ async extractIcon(): Promise { return await invoke('extract_icon', { filePath: this.fileName }); } + + /** + * Calculate total size of given file paths + * @returns {number} - Size in bytes + */ + async calculateFilesSize(): Promise { + return await invoke('calculate_files_total_size', { files: this.fileName }); + } } export default FileAPI; diff --git a/src/Components/Files/File Operation/new.ts b/src/Components/Files/File Operation/new.ts index 63362e56..32fc0430 100644 --- a/src/Components/Files/File Operation/new.ts +++ b/src/Components/Files/File Operation/new.ts @@ -10,32 +10,23 @@ import PromptError from '../../Prompt/error'; * @param {boolean} writeLog - does the operation need to be written * @returns {Promise} */ -const NewFile = async ( - fileName: string, - parentDir?: string, - writeLog = true -): Promise => { +const NewFile = async (fileName: string, parentDir?: string, writeLog = true): Promise => { if (!parentDir) parentDir = await focusingPath(); const newFile = new FileAPI(fileName, parentDir); if (await newFile.exists()) { - PromptError( - 'Error creating file', - `Failed to create file ${newFile.fileName}: File already existed` - ); + PromptError('Error creating file', `Failed to create file ${newFile.fileName}: File already existed`); } else { try { await newFile.createFile(); } catch (err) { - PromptError( - 'Error creating file', - `Failed to create file ${newFile.fileName}: Something went wrong (${err})` - ); + PromptError('Error creating file', `Failed to create file ${newFile.fileName}: Something went wrong (${err})`); } if (writeLog) { - console.log(writeLog); - OperationLog('newfile', null, newFile.fileName); + if (typeof newFile.fileName === 'string') { + OperationLog('newfile', null, newFile.fileName); + } } } }; diff --git a/src/Components/Files/File Operation/select.ts b/src/Components/Files/File Operation/select.ts index 3f3500ef..96ac4273 100644 --- a/src/Components/Files/File Operation/select.ts +++ b/src/Components/Files/File Operation/select.ts @@ -1,9 +1,26 @@ import { isElementInViewport } from '../../Functions/lazyLoadingImage'; import { elementClassNameContains } from '../../Functions/elementClassNameContains'; import Storage from '../../../Api/storage'; +import { UpdateInfo } from '../../Layout/infobar'; +import FileAPI from '../../../Api/files'; +import formatBytes from '../../Functions/filesize'; let latestSelected: HTMLElement; let latestShiftSelected: HTMLElement; + +/** + * Call this function whenever user selected (a) file grid(s). + * @returns {Promise} + */ +const ChangeSelectedEvent = async (): Promise => { + const selectedFileGrid = document.querySelectorAll('.file-grid.selected'); + if (!selectedFileGrid.length) UpdateInfo('selected-files', ''); + else { + const selectedFilePaths = Array.from(selectedFileGrid).map((element) => unescape((element as HTMLElement).dataset.path)); + const total_sizes = await new FileAPI(selectedFilePaths).calculateFilesSize(); + UpdateInfo('selected-files', `${selectedFileGrid.length} file${selectedFileGrid.length > 1 ? 's' : ''} selected ${formatBytes(total_sizes)}`); + } +}; /** * Select a file grid... * @@ -20,8 +37,7 @@ let latestShiftSelected: HTMLElement; const Select = (element: HTMLElement, ctrl: boolean, shift: boolean): void => { if (!ctrl && !shift) unselectAllSelected(); // add 'selected' class if element classlist does not contain it... - if (!element.classList.contains('selected')) - element.classList.add('selected'); + if (!element.classList.contains('selected')) element.classList.add('selected'); // ...Otherwise, remove it else element.classList.remove('selected'); if (shift && latestSelected) { @@ -43,6 +59,7 @@ const Select = (element: HTMLElement, ctrl: boolean, shift: boolean): void => { latestSelected = element; latestShiftSelected = element; } + ChangeSelectedEvent(); ensureElementInViewPort(element); }; @@ -60,14 +77,10 @@ const ensureElementInViewPort = (element: HTMLElement): void => { * @returns {Promise} */ const selectFirstFile = async (): Promise => { - const hideHiddenFiles = - (await Storage.get('preference'))?.hideHiddenFiles ?? true; - const firstFileElement = document - .getElementById('workspace') - .querySelector( - `.file${hideHiddenFiles ? ':not([data-hidden-file])' : ''}` - ); + const hideHiddenFiles = (await Storage.get('preference'))?.hideHiddenFiles ?? true; + const firstFileElement = document.getElementById('workspace').querySelector(`.file${hideHiddenFiles ? ':not([data-hidden-file])' : ''}`); firstFileElement.classList.add('selected'); + ChangeSelectedEvent(); latestSelected = firstFileElement as HTMLElement; }; @@ -95,8 +108,7 @@ const SelectInit = (): void => { if (!(e.target as HTMLElement).className.startsWith('file')) return; else { let fileTarget = e.target as HTMLElement; - while (!fileTarget.classList.contains('file')) - fileTarget = fileTarget.parentNode as HTMLElement; + while (!fileTarget.classList.contains('file')) fileTarget = fileTarget.parentNode as HTMLElement; if (fileTarget.id === 'workspace') return; Select(fileTarget, e.ctrlKey, e.shiftKey); } @@ -106,8 +118,7 @@ const SelectInit = (): void => { // Ignore keyboard shortcuts for select files if input element has focus. if (document.activeElement.tagName === 'INPUT') return; - const hideHiddenFiles = - (await Storage.get('preference'))?.hideHiddenFiles ?? true; + const hideHiddenFiles = (await Storage.get('preference'))?.hideHiddenFiles ?? true; const keyHandlers: { [key: string]: (e: KeyboardEvent, hideHiddenFiles: boolean) => void; @@ -134,9 +145,8 @@ const SelectInit = (): void => { * @returns {void} */ const unselectAllSelected = (): void => { - document - .querySelectorAll('.selected') - .forEach((element) => element.classList.remove('selected')); + document.querySelectorAll('.selected').forEach((element) => element.classList.remove('selected')); + ChangeSelectedEvent(); return; }; @@ -148,22 +158,12 @@ const getSelected = (): NodeListOf => { return document.querySelectorAll('.selected'); }; -const arrowRightHandler = ( - e: KeyboardEvent, - hideHiddenFiles: boolean -): void => { - if ( - latestShiftSelected && - elementIndex(latestShiftSelected) < elementIndex(latestSelected) - ) { +const arrowRightHandler = (e: KeyboardEvent, hideHiddenFiles: boolean): void => { + if (latestShiftSelected && elementIndex(latestShiftSelected) < elementIndex(latestSelected)) { latestShiftSelected = latestSelected; } e.preventDefault(); - let nextSibling = ( - e.shiftKey - ? latestShiftSelected.nextSibling - : latestSelected.nextSibling - ) as HTMLElement; + let nextSibling = (e.shiftKey ? latestShiftSelected.nextSibling : latestSelected.nextSibling) as HTMLElement; if (hideHiddenFiles) { while (nextSibling && nextSibling.dataset.hiddenFile !== undefined) { nextSibling = nextSibling.nextSibling as HTMLElement; @@ -175,19 +175,8 @@ const arrowRightHandler = ( if (e.shiftKey) { let start = false; for (const sibling of latestSelected.parentNode.children) { - if ( - start || - sibling === nextSibling || - sibling === latestSelected - ) { - if ( - !( - hideHiddenFiles && - (sibling as HTMLElement).dataset.hiddenFile === - 'true' - ) - ) - sibling.classList.add('selected'); + if (start || sibling === nextSibling || sibling === latestSelected) { + if (!(hideHiddenFiles && (sibling as HTMLElement).dataset.hiddenFile === 'true')) sibling.classList.add('selected'); } if (sibling === latestSelected) start = true; if (sibling === nextSibling) break; @@ -198,27 +187,17 @@ const arrowRightHandler = ( latestSelected = nextSibling; nextSibling.classList.add('selected'); } + ChangeSelectedEvent(); } }; const arrowLeftHandler = (e: KeyboardEvent, hideHiddenFiles: boolean): void => { - if ( - latestShiftSelected && - elementIndex(latestShiftSelected) > elementIndex(latestSelected) - ) - latestShiftSelected = latestSelected; + if (latestShiftSelected && elementIndex(latestShiftSelected) > elementIndex(latestSelected)) latestShiftSelected = latestSelected; e.preventDefault(); - let previousSibling = ( - e.shiftKey - ? latestShiftSelected.previousSibling - : latestSelected.previousSibling - ) as HTMLElement; + let previousSibling = (e.shiftKey ? latestShiftSelected.previousSibling : latestSelected.previousSibling) as HTMLElement; if (hideHiddenFiles) { - while ( - previousSibling && - previousSibling.dataset.hiddenFile !== undefined - ) { + while (previousSibling && previousSibling.dataset.hiddenFile !== undefined) { previousSibling = previousSibling.previousSibling as HTMLElement; } } @@ -228,19 +207,8 @@ const arrowLeftHandler = (e: KeyboardEvent, hideHiddenFiles: boolean): void => { unselectAllSelected(); if (e.shiftKey) { for (const sibling of latestSelected.parentNode.children) { - if ( - start || - sibling === previousSibling || - sibling === latestSelected - ) { - if ( - !( - hideHiddenFiles && - (sibling as HTMLElement).dataset.hiddenFile === - 'true' - ) - ) - sibling.classList.add('selected'); + if (start || sibling === previousSibling || sibling === latestSelected) { + if (!(hideHiddenFiles && (sibling as HTMLElement).dataset.hiddenFile === 'true')) sibling.classList.add('selected'); } if (sibling === previousSibling) start = true; if (sibling === latestSelected) break; @@ -251,33 +219,23 @@ const arrowLeftHandler = (e: KeyboardEvent, hideHiddenFiles: boolean): void => { latestSelected = previousSibling; previousSibling.classList.add('selected'); } + ChangeSelectedEvent(); } }; const arrowDownHandler = (e: KeyboardEvent, hideHiddenFiles: boolean): void => { - if ( - latestShiftSelected && - elementIndex(latestShiftSelected) < elementIndex(latestSelected) - ) - latestShiftSelected = latestSelected; + if (latestShiftSelected && elementIndex(latestShiftSelected) < elementIndex(latestSelected)) latestShiftSelected = latestSelected; e.preventDefault(); const totalGridInArrow = Math.floor( (latestSelected.parentNode as HTMLElement).offsetWidth / - (latestSelected.offsetWidth + - parseInt(getComputedStyle(latestSelected).marginLeft) * 2) + (latestSelected.offsetWidth + parseInt(getComputedStyle(latestSelected).marginLeft) * 2) ); // Calculate the total of grids in arrow const siblings = latestSelected.parentNode.children; - let elementBelow = siblings[ - Array.from(siblings).indexOf( - e.shiftKey ? latestShiftSelected : latestSelected - ) + totalGridInArrow - ] as HTMLElement; + let elementBelow = siblings[Array.from(siblings).indexOf(e.shiftKey ? latestShiftSelected : latestSelected) + totalGridInArrow] as HTMLElement; if (hideHiddenFiles) { while (elementBelow && elementBelow.dataset.hiddenFile !== undefined) { - elementBelow = siblings[ - Array.from(siblings).indexOf(elementBelow) + totalGridInArrow - ] as HTMLElement; + elementBelow = siblings[Array.from(siblings).indexOf(elementBelow) + totalGridInArrow] as HTMLElement; } } if (elementClassNameContains(elementBelow, /file/)) { @@ -286,19 +244,8 @@ const arrowDownHandler = (e: KeyboardEvent, hideHiddenFiles: boolean): void => { unselectAllSelected(); if (e.shiftKey) { for (const sibling of latestSelected.parentNode.children) { - if ( - start || - sibling === elementBelow || - sibling === latestSelected - ) { - if ( - !( - hideHiddenFiles && - (sibling as HTMLElement).dataset.hiddenFile === - 'true' - ) - ) - sibling.classList.add('selected'); + if (start || sibling === elementBelow || sibling === latestSelected) { + if (!(hideHiddenFiles && (sibling as HTMLElement).dataset.hiddenFile === 'true')) sibling.classList.add('selected'); } if (sibling === latestSelected) start = true; if (sibling === elementBelow) break; @@ -309,33 +256,23 @@ const arrowDownHandler = (e: KeyboardEvent, hideHiddenFiles: boolean): void => { latestSelected = elementBelow; elementBelow.classList.add('selected'); } + ChangeSelectedEvent(); } }; const arrowUpHandler = (e: KeyboardEvent, hideHiddenFiles: boolean): void => { - if ( - latestShiftSelected && - elementIndex(latestShiftSelected) > elementIndex(latestSelected) - ) - latestShiftSelected = latestSelected; + if (latestShiftSelected && elementIndex(latestShiftSelected) > elementIndex(latestSelected)) latestShiftSelected = latestSelected; e.preventDefault(); const totalGridInArrow = Math.floor( (latestSelected.parentNode as HTMLElement).offsetWidth / - (latestSelected.offsetWidth + - parseInt(getComputedStyle(latestSelected).marginLeft) * 2) + (latestSelected.offsetWidth + parseInt(getComputedStyle(latestSelected).marginLeft) * 2) ); // Calculate the total of grids in arrow const siblings = latestSelected.parentNode.children; - let elementAbove = siblings[ - Array.from(siblings).indexOf( - e.shiftKey ? latestShiftSelected : latestSelected - ) - totalGridInArrow - ] as HTMLElement; + let elementAbove = siblings[Array.from(siblings).indexOf(e.shiftKey ? latestShiftSelected : latestSelected) - totalGridInArrow] as HTMLElement; if (hideHiddenFiles) { while (elementAbove && elementAbove.dataset.hiddenFile !== undefined) { - elementAbove = siblings[ - Array.from(siblings).indexOf(elementAbove) - totalGridInArrow - ] as HTMLElement; + elementAbove = siblings[Array.from(siblings).indexOf(elementAbove) - totalGridInArrow] as HTMLElement; } } if (elementClassNameContains(elementAbove, /file/)) { @@ -344,19 +281,8 @@ const arrowUpHandler = (e: KeyboardEvent, hideHiddenFiles: boolean): void => { unselectAllSelected(); if (e.shiftKey) { for (const sibling of latestSelected.parentNode.children) { - if ( - start || - sibling === elementAbove || - sibling === latestSelected - ) { - if ( - !( - hideHiddenFiles && - (sibling as HTMLElement).dataset.hiddenFile === - 'true' - ) - ) - sibling.classList.add('selected'); + if (start || sibling === elementAbove || sibling === latestSelected) { + if (!(hideHiddenFiles && (sibling as HTMLElement).dataset.hiddenFile === 'true')) sibling.classList.add('selected'); } if (sibling === elementAbove) start = true; if (sibling === latestSelected) break; @@ -367,7 +293,8 @@ const arrowUpHandler = (e: KeyboardEvent, hideHiddenFiles: boolean): void => { latestSelected = elementAbove; elementAbove.classList.add('selected'); } + ChangeSelectedEvent(); } }; -export { Select, SelectInit, getSelected }; +export { Select, SelectInit, getSelected, ChangeSelectedEvent }; diff --git a/src/Components/Layout/infobar.ts b/src/Components/Layout/infobar.ts new file mode 100644 index 00000000..33f0e1cb --- /dev/null +++ b/src/Components/Layout/infobar.ts @@ -0,0 +1,30 @@ +import { updateTheme } from '../Theme/theme'; +import Storage from '../../Api/storage'; +let infoBarElement: HTMLElement; + +const UpdateInfo = (key: string, value: string): void => { + const el = document.querySelector(`.infobar-item#${key}`); + if (!el) return; + el.innerHTML = value; +}; + +/** + * Initialize and create infobar element + * @returns {any} + */ +const Infobar = async (): Promise => { + const appearance = await Storage.get('appearance'); + document.body.dataset.infobarEnabled = appearance.showInfoBar ?? true; + if (!(appearance.showInfoBar ?? true)) return; + infoBarElement = document.createElement('div'); + infoBarElement.classList.add('infobar'); + infoBarElement.id = 'infobar'; + infoBarElement.innerHTML = ` +
+
+ `; + document.querySelector('.main').appendChild(infoBarElement); + updateTheme('infobar'); +}; +export default Infobar; +export { UpdateInfo }; diff --git a/src/Components/Layout/layout.scss b/src/Components/Layout/layout.scss index a704c8d0..3aedd149 100644 --- a/src/Components/Layout/layout.scss +++ b/src/Components/Layout/layout.scss @@ -219,7 +219,6 @@ } } .main-box { - border-radius: 0 0 var(--edge-radius) 0; overflow-x: hidden; overflow-y: auto; height: 100%; @@ -233,6 +232,17 @@ width: -webkit-fill-available; } } + .infobar { + border-radius: 0 0 var(--edge-radius) 0; + display: flex; + .infobar-item { + padding: 2.5px 10px; + } + } +} + +[data-infobar-enabled='false'] .main-box { + border-radius: 0 0 var(--edge-radius) 0; } .workspace-split { diff --git a/src/Components/Open/displayFiles.ts b/src/Components/Open/displayFiles.ts index 9c4088b6..3eddd73c 100644 --- a/src/Components/Open/displayFiles.ts +++ b/src/Components/Open/displayFiles.ts @@ -23,7 +23,6 @@ const displayFiles = async ( const FilesElement = onElement ?? document.createElement('div'); const preference = await Storage.get('preference'); const appearance = await Storage.get('appearance'); - const hideSystemFile = preference?.hideSystemFiles ?? true; const dirAlongsideFiles = preference?.dirAlongsideFiles ?? false; const layout = (await Storage.get('layout'))?.[dir] ?? appearance?.layout ?? 'd'; const sort = (await Storage.get('sort'))?.[dir] ?? (await getDefaultSort(dir)) ?? 'a'; @@ -51,9 +50,6 @@ const displayFiles = async ( if (!dirAlongsideFiles) { files = files.sort((a, b) => -(Number(a.is_dir) - Number(b.is_dir))); } - if (hideSystemFile) { - files = files.filter((file) => !file.is_system); - } const imageAsThumbnail = (appearance.imageAsThumbnail ?? 'smalldir') === 'smalldir' ? files.length < 100 : appearance.imageAsThumbnail === 'yes'; for (const file of files) { diff --git a/src/Components/Open/open.ts b/src/Components/Open/open.ts index 298bc71d..7527558b 100644 --- a/src/Components/Open/open.ts +++ b/src/Components/Open/open.ts @@ -17,6 +17,7 @@ import { reload } from '../Layout/windowManager'; import focusingPath from '../Functions/focusingPath'; import { LOAD_IMAGE } from '../Functions/lazyLoadingImage'; import PromptError from '../Prompt/error'; +import { UpdateInfo } from '../Layout/infobar'; let platform: string; let directoryInfo: DirectoryAPI; /** @@ -42,6 +43,7 @@ const OpenDir = async (dir: string, reveal?: boolean, forceOpen = false): Promis if (MAIN_ELEMENT.classList.contains('empty-dir-notification')) MAIN_ELEMENT.classList.remove('empty-dir-notification'); // Remove class if exist if (dir === 'xplorer://Home') { Home(); + UpdateInfo('number-of-files', ''); } else if (dir === 'xplorer://Trash') { if (!platform) platform = await OS(); if (platform === 'darwin') { @@ -50,6 +52,7 @@ const OpenDir = async (dir: string, reveal?: boolean, forceOpen = false): Promis stopLoading(); } else { getTrashedFiles().then(async (trashedFiles) => { + UpdateInfo('number-of-files', `${trashedFiles.files.length} files`); if (!trashedFiles.files.length) { MAIN_ELEMENT.classList.add('empty-dir-notification'); MAIN_ELEMENT.innerText = 'This folder is empty.'; @@ -65,6 +68,7 @@ const OpenDir = async (dir: string, reveal?: boolean, forceOpen = false): Promis } } else if (dir === 'xplorer://Recent') { Recent(); + UpdateInfo('number-of-files', ''); } else { if (reveal) { directoryInfo = new DirectoryAPI(getDirname(dir)); @@ -74,6 +78,7 @@ const OpenDir = async (dir: string, reveal?: boolean, forceOpen = false): Promis return; } directoryInfo.getFiles().then(async (files) => { + UpdateInfo('number-of-files', `${files.number_of_files - files.skipped_files.length} files`); if (!files.files.length) { MAIN_ELEMENT.classList.add('empty-dir-notification'); MAIN_ELEMENT.innerText = 'This folder is empty.'; @@ -99,6 +104,7 @@ const OpenDir = async (dir: string, reveal?: boolean, forceOpen = false): Promis return; } const files = await directoryInfo.getFiles(); + UpdateInfo('number-of-files', `${files.number_of_files - files.skipped_files.length} files`); if (!files.files.length) { MAIN_ELEMENT.classList.add('empty-dir-notification'); MAIN_ELEMENT.innerText = 'This folder is empty.'; diff --git a/src/Components/Setting/Appearance/Appearance.ts b/src/Components/Setting/Appearance/Appearance.ts index e6db8d80..98768e88 100644 --- a/src/Components/Setting/Appearance/Appearance.ts +++ b/src/Components/Setting/Appearance/Appearance.ts @@ -5,6 +5,7 @@ import OS from '../../../Api/platform'; import { getAvailableFonts } from '../../../Api/app'; import { getElementStyle, updateTheme } from '../../Theme/theme'; import { setDecorations } from '../../../Api/window'; +import Infobar from '../../Layout/infobar'; let platform: string; /** * Create appearence section @@ -30,6 +31,7 @@ const Appearance = async (): Promise => { const transparentTopbar = _appearance?.transparentTopbar ?? false; const transparentWorkspace = _appearance?.transparentWorkspace ?? false; const frameStyle = _appearance?.frameStyle ?? 'default'; + const showInfoBar = _appearance?.showInfoBar ?? true; const availableThemes = [ { name: 'Light', identifier: 'light', category: 'light' }, @@ -54,6 +56,8 @@ const Appearance = async (): Promise => { const transparentTopbar_i18n = await Translate('Transparent Topbar'); const transparentWorkspace_i18n = await Translate('Transparent Workspace'); const frameStyle_i18n = await Translate('Frame Style'); + const workspace_i18n = await Translate('Workspace'); + const showInfoBar_i18n = await Translate('Show Info Bar'); const appearancePage = `

${appTheme_i18n}

`; + +

${workspace_i18n}

+
+ +
+ `; settingsMain.innerHTML = appearancePage; updateTheme('settings'); settingsMain.querySelectorAll('.number-ctrl').forEach((ctrl) => { @@ -289,6 +302,18 @@ const Appearance = async (): Promise => { Storage.set('appearance', appearance); reload(); }); + settingsMain.querySelector('[name="show-info-bar"]').addEventListener('change', (event: Event & { target: HTMLInputElement }) => { + const value = event.target.checked; + const appearance = _appearance ?? {}; + appearance.showInfoBar = value; + Storage.set('appearance', appearance); + if (value) { + Infobar(); + reload(); + } else { + document.getElementById('infobar')?.parentNode?.removeChild?.(document.getElementById('infobar')); + } + }); }; export default Appearance; diff --git a/src/Components/Shortcut/shortcut.ts b/src/Components/Shortcut/shortcut.ts index e9d8eb49..2396094d 100644 --- a/src/Components/Shortcut/shortcut.ts +++ b/src/Components/Shortcut/shortcut.ts @@ -3,7 +3,7 @@ import focusingPath from '../Functions/focusingPath'; import { OpenDir } from '../Open/open'; import getDirname from '../Functions/path/dirname'; import copyLocation from '../Files/File Operation/location'; -import { getSelected } from '../Files/File Operation/select'; +import { ChangeSelectedEvent, getSelected } from '../Files/File Operation/select'; import Pin from '../Files/File Operation/pin'; import New from '../Functions/new'; import { createNewWindow } from '../../Api/window'; @@ -220,6 +220,7 @@ const Shortcut = (): void => { if (selectedAll) { document.querySelectorAll('.file').forEach((element) => element.classList.add('selected')); } else document.querySelectorAll('.file').forEach((element) => element.classList.remove('selected')); + ChangeSelectedEvent(); } // File properties (Ctrl+P) diff --git a/src/Components/Theme/theme.json b/src/Components/Theme/theme.json index 9dde1948..1cab8566 100644 --- a/src/Components/Theme/theme.json +++ b/src/Components/Theme/theme.json @@ -85,6 +85,9 @@ "promptCancelBackground": "#87c1ce", "promptCancelColor": "#000", + "infobarBackground": "#14141b", + "infobarColor": "inherit", + "settingsNumberCtrlInputBackground": "rgb(135, 193, 206)", "settingsNumberCtrlControllerBackground": "#4e8895", "settingsNumberCtrlInputColor": "#000", @@ -176,6 +179,9 @@ "promptCancelBackground": "#87c1ce", "promptCancelColor": "#000", + "infobarBackground": "#1f2336", + "infobarColor": "inherit", + "settingsNumberCtrlInputBackground": "rgb(135, 193, 206)", "settingsNumberCtrlControllerBackground": "#4e8895", "settingsNumberCtrlInputColor": "#000", @@ -267,6 +273,9 @@ "promptCancelBackground": "#87c1ce", "promptCancelColor": "#000", + "infobarBackground": "#d0d3dd", + "infobarColor": "inherit", + "settingsNumberCtrlInputBackground": "#ebeaea", "settingsNumberCtrlControllerBackground": "#e1e1e1", "settingsNumberCtrlInputColor": "#000", @@ -358,6 +367,9 @@ "promptCancelBackground": "#87c1ce", "promptCancelColor": "#000", + "infobarBackground": "#FAFAFA", + "infobarColor": "inherit", + "settingsNumberCtrlInputBackground": "#e1e1e1", "settingsNumberCtrlControllerBackground": "#f1f1f1", "settingsNumberCtrlInputColor": "#000", diff --git a/src/Components/Theme/theme.ts b/src/Components/Theme/theme.ts index 8e2bcc2a..4f965fda 100644 --- a/src/Components/Theme/theme.ts +++ b/src/Components/Theme/theme.ts @@ -67,7 +67,19 @@ const getXYCoordinates = (e: MouseEvent): { x: number; y: number } => { */ const changeTheme = async ( theme?: string, - category?: '*' | 'root' | 'windowmanager' | 'tabbing' | 'settings' | 'favorites' | 'grid' | 'contextmenu' | 'prompt' | 'preview' | 'properties' + category?: + | '*' + | 'root' + | 'windowmanager' + | 'tabbing' + | 'settings' + | 'favorites' + | 'grid' + | 'contextmenu' + | 'prompt' + | 'preview' + | 'properties' + | 'infobar' ): Promise => { if (!category) category = '*'; const appearance = await Storage.get('appearance'); @@ -316,12 +328,16 @@ const changeTheme = async ( }); } }); + document.querySelectorAll('.file-grid').forEach((grid) => { + changeElementTheme(grid, 'gridBackground', 'background', theme); + changeElementTheme(grid, 'gridColor', 'color', theme); + }); + } + if (category === '*' || category === 'infobar') { + changeElementTheme(document.querySelector('.infobar'), 'infobarBackground', 'background', theme); + changeElementTheme(document.querySelector('.infobar'), 'infobarColor', 'color', theme); } - document.querySelectorAll('.file-grid').forEach((grid) => { - changeElementTheme(grid, 'gridBackground', 'background', theme); - changeElementTheme(grid, 'gridColor', 'color', theme); - }); return; }; @@ -330,7 +346,19 @@ const changeTheme = async ( * @returns {Promise} */ const updateTheme = async ( - category?: '*' | 'root' | 'windowmanager' | 'tabbing' | 'settings' | 'favorites' | 'grid' | 'contextmenu' | 'prompt' | 'preview' | 'properties' + category?: + | '*' + | 'root' + | 'windowmanager' + | 'tabbing' + | 'settings' + | 'favorites' + | 'grid' + | 'contextmenu' + | 'prompt' + | 'preview' + | 'properties' + | 'infobar' ): Promise => { const data: themeData = await Storage.get('theme'); // If user has no preference theme diff --git a/src/Locales/base.json b/src/Locales/base.json index 4440411b..1f2aa04f 100644 --- a/src/Locales/base.json +++ b/src/Locales/base.json @@ -39,6 +39,8 @@ "Hide hidden files": "Hide hidden files", "List and sort directories alongside files": "List and sort directories alongside files", "On startup": "On startup", + "Workspace": "Workspace", + "Show Info Bar": "Show Info Bar", "About": "About", "Layout Mode": "Layout Mode", "Grid View (Large)": "Grid View (Large)", diff --git a/src/index.ts b/src/index.ts index f657b0de..3cc99fc9 100644 --- a/src/index.ts +++ b/src/index.ts @@ -13,6 +13,7 @@ import Setting from './Components/Setting/setting'; import ContextMenu from './Components/ContextMenu/contextMenu'; import Hover from './Components/Layout/hover'; import LAZY_LOAD_INIT from './Components/Functions/lazyLoadingImage'; +import Infobar from './Components/Layout/infobar'; // Wait DOM Loaded to be loaded document.addEventListener('DOMContentLoaded', async () => { // Read user preferences @@ -57,6 +58,8 @@ document.addEventListener('DOMContentLoaded', async () => { document.getElementById('workspace').dataset.hideHiddenFiles = String(_preference.hideHiddenFiles ?? true); // Initialize settings Setting(); + // Initialize info bar + Infobar(); // Initialize context menu ContextMenu(); // Initialize hover handler