diff --git a/docs/docs/guides/cli.md b/docs/docs/guides/cli.md index 3e4bee2b..f89c141e 100644 --- a/docs/docs/guides/cli.md +++ b/docs/docs/guides/cli.md @@ -9,11 +9,19 @@ This feature hasn't optimized yet. It works but it might be laggy. Will be optim Xplorer CLI: ```bash -xplorer [dir1] [dir2] [dir3] +xplorer [dir1] [dir2] [dir3] ``` Xplorer will open `dir`, `dir2`, `dir3` as tabs on Xplorer. If there's no any dir passed into the command, Xplorer will starts at Home page. +Options: + +| Command | Alias | Description | +| ----------- | ----- | ---------------------------------------------- | +| `--help` | `-h` | Show help | +| `--version` | `-v` | Show version number | +| `--reveal` | `-r` | Open the containing folder and select the file | +
xplorer: command not found error on Windows diff --git a/docs/package.json b/docs/package.json index 68910c0a..a48f2fc8 100644 --- a/docs/package.json +++ b/docs/package.json @@ -31,7 +31,8 @@ "prism-react-renderer": "^1.2.1", "react": "^17.0.1", "react-dom": "^17.0.1", - "url-loader": "^4.1.1" + "url-loader": "^4.1.1", + "yargs": "^17.1.1" }, "browserslist": { "production": [ diff --git a/docs/yarn.lock b/docs/yarn.lock index 5794c7b5..a59a4370 100644 --- a/docs/yarn.lock +++ b/docs/yarn.lock @@ -2834,6 +2834,15 @@ cliui@^5.0.0: strip-ansi "^5.2.0" wrap-ansi "^5.1.0" +cliui@^7.0.2: + version "7.0.4" + resolved "https://registry.yarnpkg.com/cliui/-/cliui-7.0.4.tgz#a0265ee655476fc807aea9df3df8df7783808b4f" + integrity sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ== + dependencies: + string-width "^4.2.0" + strip-ansi "^6.0.0" + wrap-ansi "^7.0.0" + clone-deep@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/clone-deep/-/clone-deep-4.0.1.tgz#c19fd9bdbbf85942b4fd979c84dcf7d5f07c2387" @@ -4215,7 +4224,7 @@ gensync@^1.0.0-beta.1, gensync@^1.0.0-beta.2: resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0" integrity sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg== -get-caller-file@^2.0.1: +get-caller-file@^2.0.1, get-caller-file@^2.0.5: version "2.0.5" resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== @@ -9182,6 +9191,11 @@ y18n@^4.0.0: resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.3.tgz#b5f259c82cd6e336921efd7bfd8bf560de9eeedf" integrity sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ== +y18n@^5.0.5: + version "5.0.8" + resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55" + integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA== + yallist@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" @@ -9200,6 +9214,11 @@ yargs-parser@^13.1.2: camelcase "^5.0.0" decamelize "^1.2.0" +yargs-parser@^20.2.2: + version "20.2.9" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.9.tgz#2eb7dc3b0289718fc295f362753845c41a0c94ee" + integrity sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w== + yargs@^13.3.2: version "13.3.2" resolved "https://registry.yarnpkg.com/yargs/-/yargs-13.3.2.tgz#ad7ffefec1aa59565ac915f82dccb38a9c31a2dd" @@ -9216,6 +9235,19 @@ yargs@^13.3.2: y18n "^4.0.0" yargs-parser "^13.1.2" +yargs@^17.1.1: + version "17.1.1" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.1.1.tgz#c2a8091564bdb196f7c0a67c1d12e5b85b8067ba" + integrity sha512-c2k48R0PwKIqKhPMWjeiF6y2xY/gPMUlro0sgxqXpbOIohWiLNXWslsootttv7E1e73QPAMQSg5FeySbVcpsPQ== + dependencies: + cliui "^7.0.2" + escalade "^3.1.1" + get-caller-file "^2.0.5" + require-directory "^2.1.1" + string-width "^4.2.0" + y18n "^5.0.5" + yargs-parser "^20.2.2" + yocto-queue@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" diff --git a/src/Components/Context Menu/contextMenu.ts b/src/Components/Context Menu/contextMenu.ts index 5c10cc63..ea607231 100644 --- a/src/Components/Context Menu/contextMenu.ts +++ b/src/Components/Context Menu/contextMenu.ts @@ -51,9 +51,9 @@ interface MenuItem{ type Menu = MenuItem[][]; type openFileWithDefaultApp = (file: string) => void; -type openDir = (dir:string) => void; +type open = (dir:string) => void; -const ContextMenuInner = (target: HTMLElement, coorX:number, coorY:number, openDir:openDir) => { +const ContextMenuInner = (target: HTMLElement, coorX:number, coorY:number, open:open) => { if (target.classList.contains("home-section")) target = document.getElementById("workspace") // If context menu target is on home-section, use main element as target instead. while (!target.dataset.path) { target = target.parentNode as HTMLElement @@ -280,32 +280,32 @@ const ContextMenuInner = (target: HTMLElement, coorX:number, coorY:number, openD case "A-Z": sort[currentPath] = "A" // A = A - Z storage.set("sort", sort) - openDir(currentPath) + open(currentPath) break; case "Z-A": sort[currentPath] = "Z" // Z = Z - A storage.set("sort", sort) - openDir(currentPath) + open(currentPath) break; case "Last Modified": sort[currentPath] = "L" // L = Last Modified storage.set("sort", sort) - openDir(currentPath) + open(currentPath) break; case "First Modified": sort[currentPath] = "F" // F = First Modified storage.set("sort", sort) - openDir(currentPath) + open(currentPath) break; case "Size": sort[currentPath] = "S" // S = Size storage.set("sort", sort) - openDir(currentPath) + open(currentPath) break; case "Type": sort[currentPath] = "T" // T = Type storage.set("sort", sort) - openDir(currentPath) + open(currentPath) break; } if (target.innerHTML.startsWith("File")) { @@ -345,13 +345,13 @@ const ContextMenuInner = (target: HTMLElement, coorX:number, coorY:number, openD * Create context menu of an elememt * @param {HTMLElement} element - Element you want to create context menu of * @param {openFileWithDefaultApp} openFileWithDefaultApp - openFileWithDefaultApp function (optional), pass in the function to avoid circular dependencies - * @param {openDir} openDir - openDir function (optional), pass in the function to avoid circular dependencies + * @param {open} open - open function (optional), pass in the function to avoid circular dependencies * @returns {void} */ -const ContextMenu = (element:HTMLElement, openFileWithDefaultApp?: openFileWithDefaultApp, openDir?: openDir):void => { +const ContextMenu = (element:HTMLElement, openFileWithDefaultApp?: openFileWithDefaultApp, open?: open):void => { // Escape circular dependency if (!openFileWithDefaultApp) openFileWithDefaultApp = require('../Files/File Operation/open').openFileWithDefaultApp //eslint-disable-line @typescript-eslint/no-var-requires - if (!openDir) openDir = require('../Files/File Operation/open').openDir //eslint-disable-line @typescript-eslint/no-var-requires + if (!open) open = require('../Files/File Operation/open').open //eslint-disable-line @typescript-eslint/no-var-requires const { reload } = require("../Layout/windowManager"); //eslint-disable-line @typescript-eslint/no-var-requires @@ -368,7 +368,7 @@ const ContextMenu = (element:HTMLElement, openFileWithDefaultApp?: openFileWithD contextMenu.style.left = coorX + "px"; contextMenu.style.top = coorY + "px"; - ContextMenuInner(e.target as HTMLElement, coorX, coorY, openDir) + ContextMenuInner(e.target as HTMLElement, coorX, coorY, open) contextMenu.querySelectorAll("span").forEach(menu => { menu.addEventListener("click", () => { @@ -400,7 +400,7 @@ const ContextMenu = (element:HTMLElement, openFileWithDefaultApp?: openFileWithD } else storage.set('recent', [filePath]) } else { - openDir(filePath) + open(filePath) } break; case "openMultipleTabs": diff --git a/src/Components/Files/File Operation/open.ts b/src/Components/Files/File Operation/open.ts index 7c4a5497..0d619929 100644 --- a/src/Components/Files/File Operation/open.ts +++ b/src/Components/Files/File Operation/open.ts @@ -14,7 +14,7 @@ import {isHiddenFile} from "is-hidden-file"; import { ContextMenu } from "../../Context Menu/contextMenu"; import formatBytes from "../../Functions/filesize"; import getType from "../File Type/type"; -import { SelectListener } from "./select"; +import { SelectListener, Select } from "./select"; import { InfoLog, ErrorLog } from "../../Functions/log"; import { closePreviewFile } from "../File Preview/preview"; import {dialog} from "@electron/remote"; @@ -93,7 +93,7 @@ const openFileHandler = (e:Event):void => { } else storage.set('recent', [filePath]) } else { - openDir(filePath) + open(filePath) } } @@ -120,7 +120,7 @@ const listenOpen = (elements: NodeListOf):void => { * @param {string} dir - directory base path * @returns {void} */ -const displayFiles = async (files: fileData[], dir:string) => { +const displayFiles = async (files: fileData[], dir:string, options?: {reveal: boolean, initialDirToOpen: string}) => { const hideSystemFile = storage.get("preference")?.data?.hideSystemFiles ?? true const dirAlongsideFiles = storage.get("preference")?.data?.dirAlongsideFiles ?? false const layout = storage.get("layout")?.data?.[dir] ?? storage.get("preference")?.data?.layout ?? "s" @@ -193,8 +193,13 @@ const displayFiles = async (files: fileData[], dir:string) => { ` MAIN_ELEMENT.appendChild(fileGrid) - ContextMenu(fileGrid, openFileWithDefaultApp, openDir) + ContextMenu(fileGrid, openFileWithDefaultApp, open) }) + if(options.reveal || !fs.statSync(dir)?.isDirectory()){ + Select(document.querySelector( + `[data-path="${escape(options.initialDirToOpen)}"]` + ), false, false, document.querySelectorAll(".file")) + } updateTheme() nativeDrag(document.querySelectorAll(".file"), dir) @@ -211,9 +216,14 @@ const displayFiles = async (files: fileData[], dir:string) => { /** * Open a directory on Xplorer * @param {string} dir + * @param {boolean} boolean - Open the parent directory and select the file/dir * @returns {Promise} */ -const openDir = async (dir:string):Promise => { +const open = async (dir:string, reveal?:boolean):Promise => { + const initialDirToOpen = dir; + if(reveal || !fs.statSync(dir)?.isDirectory()){ + dir = path.dirname(dir) + } closePreviewFile() timeStarted = Date.now() startLoading() @@ -306,7 +316,7 @@ const openDir = async (dir:string):Promise => { }) } const files = getFiles() - displayFiles(files, dir) + displayFiles(files, dir, {reveal, initialDirToOpen}) // Watch the directory watcher?.close() watcher = fs.watch(dir, async (_, filePath) => { @@ -319,5 +329,6 @@ const openDir = async (dir:string):Promise => { }) } } + -export { listenOpen, openDir, openFileWithDefaultApp, closeWatcher } \ No newline at end of file +export { listenOpen, open, openFileWithDefaultApp, closeWatcher } \ No newline at end of file diff --git a/src/Components/Layout/tab.ts b/src/Components/Layout/tab.ts index 9d4340d0..f25fec8c 100644 --- a/src/Components/Layout/tab.ts +++ b/src/Components/Layout/tab.ts @@ -46,8 +46,8 @@ const createNewTab = (path?: string): void => { storage.set(`tabs-${windowGUID}`, tabs); newTab.parentElement.removeChild(newTab); - const { openDir } = require('../Files/File Operation/open'); //eslint-disable-line - openDir(tabs.tabs[tabs.focus].position); + const { open } = require('../Files/File Operation/open'); //eslint-disable-line + open(tabs.tabs[tabs.focus].position); } }); createNewTabElement.parentElement.insertBefore(newTab, createNewTabElement); // Insert the new tab @@ -71,8 +71,8 @@ const createNewTab = (path?: string): void => { tabsInfo.focusHistory.push(tabsInfo.latestIndex); storage.set(`tabs-${windowGUID}`, tabsInfo); - const { openDir } = require('../Files/File Operation/open'); //eslint-disable-line - openDir(path || 'xplorer://Home'); + const { open } = require('../Files/File Operation/open'); //eslint-disable-line + open(path || 'xplorer://Home'); newTab.addEventListener('click', () => { SwitchTab(newTab.dataset.tabIndex); @@ -91,8 +91,8 @@ const SwitchTab = (tabIndex: number | string): void => { tabs.focusHistory.push(parseInt(String(tabIndex))); tabs.tabs[tabs.focus].currentIndex -= 1; storage.set(`tabs-${windowGUID}`, tabs); - const { openDir } = require('../Files/File Operation/open'); //eslint-disable-line - openDir(tabs.tabs[tabIndex].position); + const { open } = require('../Files/File Operation/open'); //eslint-disable-line + open(tabs.tabs[tabIndex].position); }; /** @@ -101,10 +101,10 @@ const SwitchTab = (tabIndex: number | string): void => { */ const goBack = (): void => { const tabs = storage.get(`tabs-${windowGUID}`)?.data; - const { openDir } = require('../Files/File Operation/open'); //eslint-disable-line + const { open } = require('../Files/File Operation/open'); //eslint-disable-line const _focusingTab = tabs.tabs[tabs.focus]; if (_focusingTab.currentIndex > 0) { - openDir(_focusingTab.history[_focusingTab.currentIndex - 1]); + open(_focusingTab.history[_focusingTab.currentIndex - 1]); } }; @@ -114,13 +114,13 @@ const goBack = (): void => { */ const goForward = (): void => { const tabs = storage.get(`tabs-${windowGUID}`)?.data; - const { openDir } = require('../Files/File Operation/open'); //eslint-disable-line + const { open } = require('../Files/File Operation/open'); //eslint-disable-line const _focusingTab = tabs.tabs[tabs.focus]; if ( _focusingTab.currentIndex >= 0 && _focusingTab.history?.[_focusingTab.currentIndex + 1] ) { - openDir(_focusingTab.history[_focusingTab.currentIndex + 1]); + open(_focusingTab.history[_focusingTab.currentIndex + 1]); } }; /** @@ -168,8 +168,8 @@ const Tab = (): void => { delete tabs.tabs[index + 1]; storage.set(`tabs-${windowGUID}`, tabs); - const { openDir } = require('../Files/File Operation/open'); //eslint-disable-line - openDir(tabs.tabs[tabs.focus].position); + const { open } = require('../Files/File Operation/open'); //eslint-disable-line + open(tabs.tabs[tabs.focus].position); } }); tab.appendChild(closeTab); diff --git a/src/Components/Layout/windowManager.ts b/src/Components/Layout/windowManager.ts index d132aea2..1ec67d1c 100644 --- a/src/Components/Layout/windowManager.ts +++ b/src/Components/Layout/windowManager.ts @@ -1,4 +1,4 @@ -import { openDir } from '../Files/File Operation/open'; +import { open } from '../Files/File Operation/open'; import storage from 'electron-json-storage-sync'; import { BrowserWindow } from '@electron/remote'; import createSidebar from './sidebar'; @@ -12,7 +12,7 @@ import windowGUID from '../Constants/windowGUID'; const reload = async (): Promise => { const tabs = storage.get(`tabs-${windowGUID}`)?.data; await createSidebar(); - openDir(tabs.tabs[tabs.focus].position); + open(tabs.tabs[tabs.focus].position); closePreviewFile(); document.querySelector('.properties').style.animation = 'close-properties 1s forwards'; @@ -61,7 +61,7 @@ const windowManager = (): void => { .addEventListener( 'change', (event: Event & { target: HTMLInputElement }) => { - openDir(event.target.value); + open(event.target.value); } ); }; diff --git a/src/Components/Shortcut/shortcut.ts b/src/Components/Shortcut/shortcut.ts index 563ea6d9..23a761ef 100644 --- a/src/Components/Shortcut/shortcut.ts +++ b/src/Components/Shortcut/shortcut.ts @@ -93,11 +93,11 @@ const Shortcut = (): void => { exec(`code "${targetPath.replaceAll('"', '\\"')}"`); } else { const { - openDir, + open, openFileWithDefaultApp, } = require('../Files/File Operation/open'); //eslint-disable-line if (isDir) { - openDir(selectedFilePath); + open(selectedFilePath); } else { openFileWithDefaultApp(selectedFilePath); } diff --git a/src/main.ts b/src/main.ts index f5e38097..1ffedeeb 100644 --- a/src/main.ts +++ b/src/main.ts @@ -15,7 +15,15 @@ import os from 'os'; import windowStateKeeper from 'electron-window-state'; import log from 'electron-log'; import { autoUpdater } from 'electron-updater'; +import yargs from 'yargs/yargs'; +const args = yargs(process.argv.slice(isDev ? 2 : 1)) + .usage('Usage: $0 ') + .alias('h', 'help') + .command('reveal', 'Open the containing folder and select the file') + .alias('r', 'reveal') + .alias('v', 'version').argv; +console.log(args); autoUpdater.logger = log; /** @@ -55,7 +63,7 @@ const FILES_ON_OPERATION: string[] = []; let id: string; ipcMain.on('args', (e) => { - e.returnValue = isDev ? process.argv.slice(2) : process.argv.slice(1); + e.returnValue = args; }); ipcMain.on('GUID', (_, arg: string) => { id = arg; diff --git a/src/preload.ts b/src/preload.ts index 0558ddc9..2176a943 100644 --- a/src/preload.ts +++ b/src/preload.ts @@ -3,7 +3,7 @@ import { ipcRenderer, webFrame } from 'electron'; import createSidebar from './Components/Layout/sidebar'; import { windowManager } from './Components/Layout/windowManager'; import Home from './Components/Layout/home'; -import { listenOpen, openDir } from './Components/Files/File Operation/open'; +import { listenOpen, open } from './Components/Files/File Operation/open'; import { ContextMenu } from './Components/Context Menu/contextMenu'; import { createNewTab, Tab } from './Components/Layout/tab'; import { toggleHiddenFiles } from './Components/Functions/toggleHiddenFiles'; @@ -12,7 +12,8 @@ import { SelectListener } from './Components/Files/File Operation/select'; import { Shortcut } from './Components/Shortcut/shortcut'; import path from 'path'; -const requestedOpen = ipcRenderer.sendSync('args'); +const args = ipcRenderer.sendSync('args'); +const requestedOpen = args._; // Wait DOM Content to be loaded document.addEventListener('DOMContentLoaded', async () => { @@ -35,7 +36,7 @@ document.addEventListener('DOMContentLoaded', async () => { } })(); } else { - openDir(path.resolve(requestedOpen[0])); + open(path.resolve(requestedOpen[0]), args.r); for (let i = 1; i < requestedOpen.length; i++) { createNewTab(path.resolve(requestedOpen[i])); }