├── .github └── FUNDING.yml ├── .gitignore ├── .notes ├── README.md.md └── maskfile.md.md ├── .startproject ├── CHANGELOG.md ├── LICENSE ├── Pictures ├── Buffy-DP-MFM-500.jpg ├── Buffy-DP-MFM.jpg ├── DraculaPro-MFM.jpg ├── ModalFileManager.jpeg ├── Pref-Extensions.png ├── Pref-General1.png ├── Pref-General2.png ├── Pref-Theme1.png ├── Pref-Theme2.png ├── Pref-theme3.png ├── SecurityPrivacy1.png ├── Van-HelsingDP-MFM.jpg ├── VerifyDeveloper.png └── VerifyDeveloper2.png ├── README.md ├── icons ├── mfm-icon.afdesign ├── mfm-icon.icns ├── mfm-icon.png └── mfm-icon.svg ├── maskfile.md ├── package-lock.json ├── package.json ├── public ├── bundle.bin ├── bundle.css ├── bundle.js ├── global.css ├── index-dev.html ├── index-normal.html ├── index.html └── package.json ├── rollup.config.js └── src ├── Start.svelte ├── components ├── CommandPrompt.svelte ├── DirectoryListing.svelte ├── Entry.svelte ├── Env.svelte ├── EnvTableRow.svelte ├── ExtensionPrefs.svelte ├── ExtraPanel.svelte ├── FileManager.svelte ├── GeneralPrefs.svelte ├── GitHub.svelte ├── MessageBox.svelte ├── Pane.svelte ├── Preferences.svelte ├── QuickSearch.svelte ├── ResizeBorder.svelte ├── StatusLine.svelte ├── ThemeItem.svelte └── ThemePrefs.svelte ├── main.js ├── modules ├── commands.js ├── extensions.js ├── filesystems.js ├── linux.js ├── macOS.js ├── util.js └── windows.js └── stores ├── altKey.js ├── config.js ├── ctrlKey.js ├── currentCursor.js ├── currentLeftFile.js ├── currentRightFile.js ├── dirHistory.js ├── directoryListeners.js ├── extraPanel.js ├── inputState.js ├── keyProcess.js ├── leftDir.js ├── metaKey.js ├── processKey.js ├── rightDir.js ├── shiftKey.js ├── skipKey.js ├── stateMapColors.js └── theme.js /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: [ 'raguay' ] 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 13 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # large files ignore 2 | mfm.app 3 | mfm-mac.zip 4 | mac 5 | 6 | # Logs 7 | logs 8 | *.log 9 | npm-debug.log* 10 | yarn-debug.log* 11 | yarn-error.log* 12 | lerna-debug.log* 13 | *.map 14 | 15 | # Diagnostic reports (https://nodejs.org/api/report.html) 16 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 17 | 18 | # Runtime data 19 | pids 20 | *.pid 21 | *.seed 22 | *.pid.lock 23 | 24 | # Directory for instrumented libs generated by jscoverage/JSCover 25 | lib-cov 26 | 27 | # Coverage directory used by tools like istanbul 28 | coverage 29 | *.lcov 30 | 31 | # nyc test coverage 32 | .nyc_output 33 | 34 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 35 | .grunt 36 | 37 | # Bower dependency directory (https://bower.io/) 38 | bower_components 39 | 40 | # node-waf configuration 41 | .lock-wscript 42 | 43 | # Compiled binary addons (https://nodejs.org/api/addons.html) 44 | build/Release 45 | 46 | # Dependency directories 47 | node_modules/ 48 | jspm_packages/ 49 | 50 | # TypeScript v1 declaration files 51 | typings/ 52 | 53 | # TypeScript cache 54 | *.tsbuildinfo 55 | 56 | # Optional npm cache directory 57 | .npm 58 | 59 | # Optional eslint cache 60 | .eslintcache 61 | 62 | # Microbundle cache 63 | .rpt2_cache/ 64 | .rts2_cache_cjs/ 65 | .rts2_cache_es/ 66 | .rts2_cache_umd/ 67 | 68 | # Optional REPL history 69 | .node_repl_history 70 | 71 | # Output of 'npm pack' 72 | *.tgz 73 | 74 | # Yarn Integrity file 75 | .yarn-integrity 76 | 77 | # dotenv environment variables file 78 | .env 79 | .env.test 80 | 81 | # parcel-bundler cache (https://parceljs.org/) 82 | .cache 83 | 84 | # Next.js build output 85 | .next 86 | 87 | # Nuxt.js build / generate output 88 | .nuxt 89 | dist 90 | 91 | # Gatsby files 92 | .cache/ 93 | # Comment in the public line in if your project uses Gatsby and *not* Next.js 94 | # https://nextjs.org/blog/next-9-1#public-directory-support 95 | # public 96 | 97 | # vuepress build output 98 | .vuepress/dist 99 | 100 | # Serverless directories 101 | .serverless/ 102 | 103 | # FuseBox cache 104 | .fusebox/ 105 | 106 | # DynamoDB Local files 107 | .dynamodb/ 108 | 109 | # TernJS port file 110 | .tern-port 111 | -------------------------------------------------------------------------------- /.notes/README.md.md: -------------------------------------------------------------------------------- 1 | # README.md 2 | 3 | This is the readme file that GitHub displays on their site. 4 | 5 | -------------------------------------------------------------------------------- /.notes/maskfile.md.md: -------------------------------------------------------------------------------- 1 | # Maskfile for Modal File Manager 2 | 3 | This file contains all the scripts for building and running the Modal File Manager. 4 | -------------------------------------------------------------------------------- /.startproject: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | cd "$1" 4 | oni2 . 5 | 6 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | 4 | ## 1.0.0 (2021-04-29) 5 | 6 | ### Added 7 | 8 | - ✨ MessageBox has Picker [[fe849db](https://github.com/raguay/ModalFileManager/commit/fe849dbedebc6ad7041fb7a1ed85503df41fd939)] 9 | - ✨ Added the '.' repeat last command and bug fixes [[8e3f10b](https://github.com/raguay/ModalFileManager/commit/8e3f10be554d12484ab61abd0581200138fb57d7)] 10 | 11 | ### Changed 12 | 13 | - 🎨 Removed redundant code. [[c025eb8](https://github.com/raguay/ModalFileManager/commit/c025eb8ace917053dfbe1b4a06757e1fb54ef0bc)] 14 | - 🎨 Major restructuring of LowLevel to High Level [[e06b4bd](https://github.com/raguay/ModalFileManager/commit/e06b4bd3d2f6e7bdcbdd0ebb4fa61d1731d2ad3a)] 15 | - 🎨 Moving FileSystem dependent stuff lower [[a0e7fc3](https://github.com/raguay/ModalFileManager/commit/a0e7fc324eecf9fd3878978e3e6ec15175666a15)] 16 | - 💄 Added Font Styling option [[a8ce863](https://github.com/raguay/ModalFileManager/commit/a8ce8630f97b82d296ada9c68ab28227124cd411)] 17 | 18 | ### Fixed 19 | 20 | - 🐛 Fixed Various Bugs [[b77bcc4](https://github.com/raguay/ModalFileManager/commit/b77bcc4252b1100f417f6245715630f84e414836)] 21 | - 🐛 StatusLine overflows fixed [[e67c365](https://github.com/raguay/ModalFileManager/commit/e67c365bebe55a01cb912db0ba76ef64d6e3bcf2)] 22 | - 🐛 StatusLine name and dateline fixed. [[44dd2e2](https://github.com/raguay/ModalFileManager/commit/44dd2e2c8861a0dfc819d8ada4489a974e14caae)] 23 | - 🐛 Critical bug with MessageBox [[2d0dd5f](https://github.com/raguay/ModalFileManager/commit/2d0dd5f8384cafa3ca106232fcf5a85d5c7293e1)] 24 | - 🐛 Single config file missing doesn't kill the whole thing. [[7569f6b](https://github.com/raguay/ModalFileManager/commit/7569f6bd361832db2e42a1994872c968d072ed95)] 25 | - 🐛 Extra files generated [[a1fb489](https://github.com/raguay/ModalFileManager/commit/a1fb4896803f48af3a2602b1ca15cd8539c5779a)] 26 | - 🐛 Major Bug fix and New Features [[8afcc0a](https://github.com/raguay/ModalFileManager/commit/8afcc0a966fa626bc26f9e3554a557c5f041f1c2)] 27 | - 🐛 Fixing Quick Search Position and Look [[f5d671f](https://github.com/raguay/ModalFileManager/commit/f5d671f87764be8ec7602b1930737857170b73da)] 28 | 29 | ### Miscellaneous 30 | 31 | - daily commit [[16c558a](https://github.com/raguay/ModalFileManager/commit/16c558a8c657e71ee801ed74c1e440c0bfa678b1)] 32 | - cleaning up [[7b5b805](https://github.com/raguay/ModalFileManager/commit/7b5b8057b74ce5980949bae6564bb1290d19bd95)] 33 | - cleaning up [[acb884b](https://github.com/raguay/ModalFileManager/commit/acb884bf8d1a3b32a555603f5af644b5fb98a06a)] 34 | - Adding to the README.md [[df788b9](https://github.com/raguay/ModalFileManager/commit/df788b993b19fc8e6c3cae60a7db46d58229507d)] 35 | - fixing README.md for font changes. [[73c2b58](https://github.com/raguay/ModalFileManager/commit/73c2b5872b8cec1e8f21fef39df4cf5a4ab614da)] 36 | - Fixing title [[4406c3b](https://github.com/raguay/ModalFileManager/commit/4406c3b64d054a88b7b4d088817f7282f5be568f)] 37 | - initial commit [[d8fb672](https://github.com/raguay/ModalFileManager/commit/d8fb6726c746dfa10b4a8f29ec1e37c45f06aa37)] 38 | - Create FUNDING.yml [[80365e5](https://github.com/raguay/ModalFileManager/commit/80365e51dcba76d70a231fa6d3def17c5898087f)] 39 | - Updating the README.md [[1f2ce25](https://github.com/raguay/ModalFileManager/commit/1f2ce25489f71c5406c54c3c8b0bd094657d15a5)] 40 | - Initial commit [[2b2663f](https://github.com/raguay/ModalFileManager/commit/2b2663fee1c6d1fe2ba25529ddcdf54b1e75ac42)] 41 | 42 | 43 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Richard Guay 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Pictures/Buffy-DP-MFM-500.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raguay/ModalFileManager-NWjs/642ed447cade19842d776a237f79dcf6468a69aa/Pictures/Buffy-DP-MFM-500.jpg -------------------------------------------------------------------------------- /Pictures/Buffy-DP-MFM.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raguay/ModalFileManager-NWjs/642ed447cade19842d776a237f79dcf6468a69aa/Pictures/Buffy-DP-MFM.jpg -------------------------------------------------------------------------------- /Pictures/DraculaPro-MFM.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raguay/ModalFileManager-NWjs/642ed447cade19842d776a237f79dcf6468a69aa/Pictures/DraculaPro-MFM.jpg -------------------------------------------------------------------------------- /Pictures/ModalFileManager.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raguay/ModalFileManager-NWjs/642ed447cade19842d776a237f79dcf6468a69aa/Pictures/ModalFileManager.jpeg -------------------------------------------------------------------------------- /Pictures/Pref-Extensions.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raguay/ModalFileManager-NWjs/642ed447cade19842d776a237f79dcf6468a69aa/Pictures/Pref-Extensions.png -------------------------------------------------------------------------------- /Pictures/Pref-General1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raguay/ModalFileManager-NWjs/642ed447cade19842d776a237f79dcf6468a69aa/Pictures/Pref-General1.png -------------------------------------------------------------------------------- /Pictures/Pref-General2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raguay/ModalFileManager-NWjs/642ed447cade19842d776a237f79dcf6468a69aa/Pictures/Pref-General2.png -------------------------------------------------------------------------------- /Pictures/Pref-Theme1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raguay/ModalFileManager-NWjs/642ed447cade19842d776a237f79dcf6468a69aa/Pictures/Pref-Theme1.png -------------------------------------------------------------------------------- /Pictures/Pref-Theme2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raguay/ModalFileManager-NWjs/642ed447cade19842d776a237f79dcf6468a69aa/Pictures/Pref-Theme2.png -------------------------------------------------------------------------------- /Pictures/Pref-theme3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raguay/ModalFileManager-NWjs/642ed447cade19842d776a237f79dcf6468a69aa/Pictures/Pref-theme3.png -------------------------------------------------------------------------------- /Pictures/SecurityPrivacy1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raguay/ModalFileManager-NWjs/642ed447cade19842d776a237f79dcf6468a69aa/Pictures/SecurityPrivacy1.png -------------------------------------------------------------------------------- /Pictures/Van-HelsingDP-MFM.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raguay/ModalFileManager-NWjs/642ed447cade19842d776a237f79dcf6468a69aa/Pictures/Van-HelsingDP-MFM.jpg -------------------------------------------------------------------------------- /Pictures/VerifyDeveloper.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raguay/ModalFileManager-NWjs/642ed447cade19842d776a237f79dcf6468a69aa/Pictures/VerifyDeveloper.png -------------------------------------------------------------------------------- /Pictures/VerifyDeveloper2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raguay/ModalFileManager-NWjs/642ed447cade19842d776a237f79dcf6468a69aa/Pictures/VerifyDeveloper2.png -------------------------------------------------------------------------------- /icons/mfm-icon.afdesign: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raguay/ModalFileManager-NWjs/642ed447cade19842d776a237f79dcf6468a69aa/icons/mfm-icon.afdesign -------------------------------------------------------------------------------- /icons/mfm-icon.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raguay/ModalFileManager-NWjs/642ed447cade19842d776a237f79dcf6468a69aa/icons/mfm-icon.icns -------------------------------------------------------------------------------- /icons/mfm-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raguay/ModalFileManager-NWjs/642ed447cade19842d776a237f79dcf6468a69aa/icons/mfm-icon.png -------------------------------------------------------------------------------- /icons/mfm-icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | mfm 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /maskfile.md: -------------------------------------------------------------------------------- 1 | ## build 2 | 3 | > Build the Modal File Manager. 4 | 5 | **OPTIONS** 6 | * dev 7 | * flags: -d --dev 8 | * type: boolean 9 | * desc: Runs the development build which doesn't compile the bitcode. 10 | 11 | ```fish 12 | npm run build 13 | if set -q dev 14 | rm public/index.html 15 | rm -Rf mfm.app/Contents/Resources/app.nw 16 | mkdir mfm.app/Contents/Resources/app.nw 17 | cp public/index-dev.html public/index.html 18 | cp public/*.js mfm.app/Contents/Resources/app.nw 19 | cp public/index.html mfm.app/Contents/Resources/app.nw 20 | cp public/package.json mfm.app/Contents/Resources/app.nw 21 | cp public/*.css mfm.app/Contents/Resources/app.nw 22 | cp icons/mfm-icon.icns mfm.app/Contents/Resources/app.icns 23 | else 24 | /Applications/nwjs-sdk/nwjc public/bundle.js public/bundle.bin 25 | rm public/*.map 26 | rm -Rf mfm.app/Contents/Resources/app.nw 27 | mkdir mfm.app/Contents/Resources/app.nw 28 | rm public/index.html 29 | cp public/index-normal.html public/index.html 30 | cp public/*.bin mfm.app/Contents/Resources/app.nw 31 | cp public/index.html mfm.app/Contents/Resources/app.nw 32 | cp public/package.json mfm.app/Contents/Resources/app.nw 33 | cp public/*.css mfm.app/Contents/Resources/app.nw 34 | cp icons/mfm-icon.icns mfm.app/Contents/Resources/app.icns 35 | end 36 | cd mfm.app/Contents/Resources/app.nw 37 | npm install 38 | ``` 39 | 40 | ## launch 41 | 42 | > Launch the application using the sdk version of NW.js. 43 | 44 | **OPTIONS** 45 | * dev 46 | * flags: -d --dev 47 | * type: boolean 48 | * desc: Runs the development build which doesn't compile the bitcode. 49 | 50 | ```fish 51 | if set -q dev 52 | /Applications/nwjs-sdk/nwjs.app/Contents/MacOS/nwjs ./public > /dev/null 2>&1 & 53 | else 54 | /Applications/nwjs.app/Contents/MacOS/nwjs ./public > /dev/null 2>&1 & 55 | end 56 | ``` 57 | 58 | ## launchapp 59 | > Launch the application. 60 | 61 | ```fish 62 | open -a mfm.app . 63 | ``` 64 | 65 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "modalfilemanager", 3 | "description": "A file manager with Vim style hotkeys", 4 | "version": "1.0.0", 5 | "scripts": { 6 | "build": "rollup -c" 7 | }, 8 | "devDependencies": { 9 | "@rollup/plugin-commonjs": "^14.0.0", 10 | "@rollup/plugin-node-resolve": "^8.0.0", 11 | "rollup": "^2.3.4", 12 | "rollup-plugin-livereload": "^2.0.0", 13 | "rollup-plugin-node-polyfills": "^0.2.1", 14 | "rollup-plugin-svelte": "^6.0.0", 15 | "rollup-plugin-terser": "^7.0.0", 16 | "svelte": "^3.0.0" 17 | }, 18 | "dependencies": { 19 | "@octokit/rest": "^18.3.5", 20 | "chokidar": "^3.5.1", 21 | "sirv-cli": "^1.0.10", 22 | "svelte-color-picker": "^1.0.7", 23 | "svelte-icons": "^2.1.0" 24 | }, 25 | "fig": { 26 | "build": { 27 | "name": "build", 28 | "description": "Build the Modal File Manager.", 29 | "icon": "🛠️" 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /public/bundle.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raguay/ModalFileManager-NWjs/642ed447cade19842d776a237f79dcf6468a69aa/public/bundle.bin -------------------------------------------------------------------------------- /public/bundle.css: -------------------------------------------------------------------------------- 1 | .main-container.svelte-8esefu{width:240px;height:265px;background:#f2f2f2;border-radius:1px;-webkit-box-shadow:0px 0px 4px 0px rgba(0, 0, 0, 0.51);-moz-box-shadow:0px 0px 4px 0px rgba(0, 0, 0, 0.51);box-shadow:0px 0px 4px 0px rgba(0, 0, 0, 0.51);-webkit-touch-callout:none;-webkit-user-select:none;-khtml-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.saturation-gradient.svelte-8esefu{background:linear-gradient(to right, rgb(255, 255, 255), rgba(255, 255, 255, 0));width:240px;height:160px}.value-gradient.svelte-8esefu{background:linear-gradient(to top, rgb(0, 0, 0), rgba(0, 0, 0, 0));overflow:hidden;width:240px;height:160px}.hue-selector.svelte-8esefu{background:linear-gradient(to right, #ff0000 0%, #ffff00 17%, #00ff00 33%, #00ffff 50%, #0000ff 67%, #ff00ff 83%, #ff0000 100%);margin:15px 10px 10px 10px;border-radius:10px;height:10px}#hue-picker.svelte-8esefu{background:#FFF;width:12px;height:12px;border-radius:50%;left:0%;position:relative;cursor:default;transform:translate(-5px, -1px);-webkit-box-shadow:0px 0px 5px 0px rgba(0, 0, 0, 0.67);-moz-box-shadow:0px 0px 5px 0px rgba(0, 0, 0, 0.67);box-shadow:0px 0px 5px 0px rgba(0, 0, 0, 0.67)}#hue-event.svelte-8esefu{width:236px;height:14px;transform:translate(-8px, -14px);cursor:default;touch-action:none}.alpha-selector.svelte-8esefu{background-image:linear-gradient(45deg, #808080 25%, transparent 25%), linear-gradient(-45deg, #808080 25%, transparent 25%), linear-gradient(45deg, transparent 75%, #808080 75%), linear-gradient(-45deg, transparent 75%, #808080 75%);background-size:10px 10px;background-position:0 0, 0 5px, 5px -5px, -5px 0px;margin:10px 10px;border-radius:10px;height:10px;position:relative}#alpha-picker.svelte-8esefu{background:#FFF;width:12px;height:12px;border-radius:50%;left:100%;position:relative;cursor:default;transform:translate(-5px, -11px);-webkit-box-shadow:0px 0px 5px 0px rgba(0, 0, 0, 0.67);-moz-box-shadow:0px 0px 5px 0px rgba(0, 0, 0, 0.67);box-shadow:0px 0px 5px 0px rgba(0, 0, 0, 0.67)}#alpha-event.svelte-8esefu{width:236px;height:14px;transform:translate(-8px, -24px);cursor:default;touch-action:none}.alpha-value.svelte-8esefu{background:linear-gradient(to right, rgba(0, 0, 0, 0), rgba(0, 0, 0, 1));width:100%;height:100%;border-radius:10px}.colorsquare.svelte-8esefu{background:rgb(255, 0, 0)}#colorsquare-picker.svelte-8esefu{margin:0;padding:0;width:12px;height:12px;border-radius:50%;border:2px solid #FFFB;position:relative;transform:translate(-9px, -9px);left:100%}#colorsquare-event.svelte-8esefu{width:100%;height:100%;position:relative;transform:translate(0, -16px);touch-action:none}.color-info-box.svelte-8esefu{margin:10px;width:100%;height:22px;vertical-align:middle;position:relative}.color-picked.svelte-8esefu{width:18px;height:18px;border-radius:2px;background:rgba(255, 0, 0, 1);display:inline-block}.color-picked-bg.svelte-8esefu{background-image:linear-gradient(45deg, #808080 25%, transparent 25%), linear-gradient(-45deg, #808080 25%, transparent 25%), linear-gradient(45deg, transparent 75%, #808080 75%), linear-gradient(-45deg, transparent 75%, #808080 75%);background-size:10px 10px;background-position:0 0, 0 5px, 5px -5px, -5px 0px;border:2px solid #FFF;border-radius:4px;width:18px;height:18px;color:#fff;display:inline-block}.hex-text-block.svelte-8esefu{display:inline-block;background:white;border-radius:2px;padding:2px;border:1px solid #e3e3e3;height:16px;width:54px;vertical-align:top;text-align:center}.rgb-text-block.svelte-8esefu{display:inline-block;background:white;border-radius:2px;padding:2px;margin:0 1px;border:1px solid #dcdcdc;height:16px;width:23px;vertical-align:top;text-align:center}.rgb-text-div.svelte-8esefu{right:10%;display:inline-block;vertical-align:top;position:absolute}.text-label.svelte-8esefu{position:relative;top:-12px;font-family:sans-serif;font-size:small;color:#888}.text.svelte-8esefu{display:inline;font-family:sans-serif;margin:0;display:inline-block;font-size:12px;font-size-adjust:0.50;position:relative;top:-1px;vertical-align:middle;-webkit-touch-callout:all;-webkit-user-select:all;-khtml-user-select:all;-moz-user-select:all;-ms-user-select:all;user-select:all} 2 | svg.svelte-c8tyih{stroke:currentColor;fill:currentColor;stroke-width:0;width:100%;height:auto;max-height:100%} 3 | body{padding:0px;margin:0px;width:100%;height:100%;overflow:hidden;user-select:none} 4 | #commandPrompt.svelte-1cf6v2y.svelte-1cf6v2y{display:flex;flex-direction:column;position:absolute;z-index:500;top:20px;left:10px;border:3px solid;border-radius:5px}#commandPrompt.svelte-1cf6v2y input.svelte-1cf6v2y{outline-color:transparent;margin:auto;padding:0px;width:100px;border:0px solid transparent;margin:10px auto}#commandPrompt.svelte-1cf6v2y ul.svelte-1cf6v2y{list-style:none}#commandPrompt.svelte-1cf6v2y ul li a.svelte-1cf6v2y{text-decoration:none;color:inherit}#commandlist.svelte-1cf6v2y.svelte-1cf6v2y{overflow-y:scroll;overflow-x:hidden}.description.svelte-1cf6v2y.svelte-1cf6v2y{margin:5px 0px 10px 20px} 5 | #searchList.svelte-mc3y08.svelte-mc3y08{position:absolute;display:flex;flex-direction:column;z-index:200}#searchList.svelte-mc3y08 ul.svelte-mc3y08{list-style-type:none;margin:0px;padding:0px}#searchList.svelte-mc3y08 ul li.svelte-mc3y08{text-decoration:none;margin:0px;padding:0px}.dirList.svelte-mc3y08.svelte-mc3y08{display:flex;flex-direction:row;margin:0px;padding:0px;height:20px}.dir.svelte-mc3y08.svelte-mc3y08{height:20px;margin:0px auto 0px 10px;white-space:nowrap;overflow-x:scroll;direction:rtl}.dirinputclass.svelte-mc3y08.svelte-mc3y08{height:20px;margin:0px auto 0px 10px;white-space:nowrap} 6 | .entry.svelte-kn761a{display:flex;flex-direction:row;height:20px;max-height:20px;min-height:20px;width:100%;margin:3px 3px}.name.svelte-kn761a{white-space:nowrap}.type.svelte-kn761a{height:20px;width:20px;min-height:20px;min-width:20px;max-height:20px;max-width:20px;margin:0px 10px 0px 5px} 7 | table.svelte-1bbviaz{margin-left:10px;user-select:text}td.svelte-1bbviaz,th.svelte-1bbviaz{text-align:left}.addNewItem.svelte-1bbviaz{color:red;cursor:pointer;font-size:20px;margin-left:10px} 8 | input.svelte-26er1v{width:100%}.editTD.svelte-26er1v{margin-left:10px;cursor:pointer}.deleteTD.svelte-26er1v{margin-left:10px;cursor:pointer} 9 | #extension.svelte-1has95i.svelte-1has95i{display:flex;flex-direction:column;height:100%;margin:0px 0px 0px 10px;overflow-y:auto;overflow-x:hidden}#createExtensionButton.svelte-1has95i.svelte-1has95i{padding:5px;margin:0px 10px;border:solid 1px transparent;border-radius:5px}#msgBoxExt.svelte-1has95i.svelte-1has95i{display:flex;flex-direction:column;position:absolute;border:solid 2px;border-radius:10px;top:25%;left:25%;width:50%;padding:10px}#msgBoxExt.svelte-1has95i button.svelte-1has95i{border:solid 1px;border-radius:10px;width:50%;margin:auto}#extensionTable.svelte-1has95i.svelte-1has95i{width:100%;margin:20px 0px 20px 0px}#extensionNameInput.svelte-1has95i.svelte-1has95i{margin:0px 10px}label.svelte-1has95i.svelte-1has95i{margin:auto 10px}.deleteButton.svelte-1has95i.svelte-1has95i{border:solid 1px transparent;border-radius:10px;background:red;color:black}.editButton.svelte-1has95i.svelte-1has95i{padding:5px;margin:0px 10px;border:solid 1px transparent;border-radius:5px}.row.svelte-1has95i.svelte-1has95i{display:flex;flex-direction:row;margin:10px} 10 | #extrapanel.svelte-14e7pta.svelte-14e7pta{display:flex;flex-direction:column;height:100%;margin:0px;padding:5px;overflow-y:auto;overflow-wrap:anywhere;word-break:break-all}#extrapanel.svelte-14e7pta img.svelte-14e7pta{width:100%}.stats.svelte-14e7pta.svelte-14e7pta{display:flex;flex-direction:column}.stats.svelte-14e7pta p.svelte-14e7pta{margin:5px 0px 0px 0px} 11 | #leftSide.svelte-1az5yr9{display:flex;flex-direction:column;padding:0px;margin:0px;width:50%}#rightSide.svelte-1az5yr9{display:flex;flex-direction:column;padding:0px;margin:0px;width:50%}#container.svelte-1az5yr9{padding:0px;margin:0px;display:flex;flex-direction:row;position:fixed;top:0px;bottom:34px;width:100%} 12 | #general.svelte-1uugwin{display:flex;flex-direction:column;height:100%;overflow-y:auto;overflow-x:auto}#trashcanans.svelte-1uugwin{margin:auto 0px auto 10px}h3.svelte-1uugwin{margin-left:10px}.row.svelte-1uugwin{display:flex;flex-direction:row;margin:10px} 13 | #GitHub.svelte-34rydv.svelte-34rydv{position:absolute;display:flex;flex-direction:column;top:20px;left:20px;border:3px solid;border-radius:3px;z-index:100}#GitHubHeader.svelte-34rydv.svelte-34rydv{display:flex;flex-direction:row;margin:10px}#GitHubHeader.svelte-34rydv h3.svelte-34rydv{margin:0px auto 0px 0px}#GitHubHeader.svelte-34rydv span.svelte-34rydv{margin:0px 0px 0px auto}#GitHubList.svelte-34rydv.svelte-34rydv{margin:5px 10px;overflow-y:auto;overflow-x:hidden}#inputHidden.svelte-34rydv.svelte-34rydv{width:0px;height:0px;margin:0px;padding:0px;border:0px solid transparent}.reporow.svelte-34rydv.svelte-34rydv{display:flex;flex-direction:row;margin:0px}.reponame.svelte-34rydv.svelte-34rydv{margin:0px auto 0px 0px}.repostars.svelte-34rydv.svelte-34rydv{margin:0px 0px 0px auto}.repodisc.svelte-34rydv.svelte-34rydv{margin:0px 0px 0px 15px}.repoblock.svelte-34rydv.svelte-34rydv{display:flex;flex-direction:column;margin:5px 0px}.repobuttons.svelte-34rydv.svelte-34rydv{display:flex;flex-direction:row;margin:5px auto}.repobuttons.svelte-34rydv button.svelte-34rydv{margin:0px 10px;border-radius:5px} 14 | #messageboxbg.svelte-8ji6gn.svelte-8ji6gn{position:absolute;top:0px;left:0px;height:100%;width:100%;background-color:transparent;z-index:100}#messagebox.svelte-8ji6gn.svelte-8ji6gn{display:flex;flex-direction:column;margin:auto;padding:10px;width:70%;border:3px solid;border-radius:5px}#butRow.svelte-8ji6gn.svelte-8ji6gn{display:flex;flex-direction:row;margin:20px 0px 0px 0px}#butRow.svelte-8ji6gn button.svelte-8ji6gn:first-child{margin:auto 10px auto auto;border-radius:5px}#butRow.svelte-8ji6gn button.svelte-8ji6gn:last-child{margin:auto auto auto 10px;border-radius:5px}#pickerDiv.svelte-8ji6gn input.svelte-8ji6gn{width:100% 15 | }.picker.svelte-8ji6gn.svelte-8ji6gn{display:flex;flex-direction:column;overflow-y:scroll;overflow-x:hidden}.picker.svelte-8ji6gn a.svelte-8ji6gn{text-decoration:none}.pickerSelected.svelte-8ji6gn.svelte-8ji6gn{text-decoration:none} 16 | .panel.svelte-18ls1gz{display:flex;flex-direction:column;padding:0px;margin:0px;height:100%;overflow-y:scroll;overflow-x:hidden}.empty.svelte-18ls1gz{height:100%} 17 | #Preferences.svelte-1u7uvz.svelte-1u7uvz{display:flex;flex-direction:column;position:absolute;top:0px;left:0px;z-index:100;width:100%;height:100%}#vimInputDiv.svelte-1u7uvz.svelte-1u7uvz{width:0px;height:0px;margin:0px;padding:0px;border:0px solid transparent}#buttonRow.svelte-1u7uvz.svelte-1u7uvz{display:flex;flex-direction:row;width:100%;margin:5px}#buttonRow.svelte-1u7uvz button.svelte-1u7uvz{margin:auto;border-radius:5px;padding:5px}ul.svelte-1u7uvz.svelte-1u7uvz{display:flex;flex-direction:row;padding:0px;margin:0px 5px 0px 10px}ul.svelte-1u7uvz li.svelte-1u7uvz{border-radius:25px 5px 0px 0px;border:3px solid;padding:5px 5px 2px 15px;margin:0px 10px 0px 0px;list-style:none}h2.svelte-1u7uvz.svelte-1u7uvz{text-align:center;margin-top:3px} 18 | #quickSearch.svelte-xwdsdw.svelte-xwdsdw{position:absolute;display:flex;flex-direction:row;margin:0px;padding:0px;border:0px solid transparent;height:30px;max-height:30px;min-height:30px;width:100px;max-width:100px;min-width:100px;bottom:0px;z-index:100;user-select:none;text-decoration:none}#quickSearch.svelte-xwdsdw input.svelte-xwdsdw{outline-color:transparent;margin:auto;padding:0px;width:100px;border:3px solid transparent;border-radius:5px} 19 | #resizeBorder.svelte-1ll3mt7{display:flex;flex-direction:row;height:auto;cursor:ew-resize} 20 | #statusLine.svelte-1w9ii3p{display:flex;flex-direction:row;flex-grow:0;margin:0px;padding:0px;width:100%;min-height:31px;height:31px;max-height:31px;position:fixed;bottom:0px;left:0px}.pane.svelte-1w9ii3p{margin:0px;padding:5px 5px 5px 10px;width:35px;min-width:50px}.state.svelte-1w9ii3p{margin:0px;padding:5px 10px 5px 10px;color:black;min-width:50px}.file.svelte-1w9ii3p{margin:0px;padding:5px 10px 5px 5px;min-width:50px;overflow:hidden;white-space:nowrap}span.customdata.svelte-1w9ii3p{position:relative }span.customdata.svelte-1w9ii3p:hover::before{content:attr(data-tooltip);background-color:inherit;color:inherit;position:fixed;bottom:2em;min-width:20px;border:1px #808080 solid;padding:8px;z-index:1} 21 | .cpicker.svelte-1cxeta0.svelte-1cxeta0{display:flex;flex-direction:row;color:black}.rowCell.svelte-1cxeta0.svelte-1cxeta0{display:flex;flex-direction:row}.colorDiv.svelte-1cxeta0.svelte-1cxeta0{height:18px;width:18px;margin:auto 0px auto 10px;padding:0px;border:solid 1px}.rowCell.svelte-1cxeta0 input.svelte-1cxeta0{width:100%;border:solid 1px transparent;border-radius:10px} 22 | #theme.svelte-wb0n4a.svelte-wb0n4a{display:flex;flex-direction:column;height:100%;margin:0px 0px 0px 10px;overflow-y:auto;overflow-x:hidden}#createThemeButton.svelte-wb0n4a.svelte-wb0n4a{padding:5px;margin:0px 10px;border:solid 1px transparent;border-radius:5px}#msgBoxTheme.svelte-wb0n4a.svelte-wb0n4a{display:flex;flex-direction:column;position:absolute;border:solid 2px;border-radius:10px;top:25%;left:25%;width:50%;padding:10px}#msgBoxTheme.svelte-wb0n4a button.svelte-wb0n4a{border:solid 1px;border-radius:10px;width:50%;margin:auto}#themeTable.svelte-wb0n4a.svelte-wb0n4a{width:100%;margin:20px 0px 20px 0px}#themeNameInput.svelte-wb0n4a.svelte-wb0n4a{padding:5px;margin:0px 10px 0px 10px;border:solid 1px transparent;border-radius:5px}label.svelte-wb0n4a.svelte-wb0n4a{margin:auto 10px}table.svelte-wb0n4a.svelte-wb0n4a{width:60%}.row.svelte-wb0n4a.svelte-wb0n4a{display:flex;flex-direction:row}.updateButton.svelte-wb0n4a.svelte-wb0n4a{border:solid 1px transparent;border-radius:10px}.deleteButton.svelte-wb0n4a.svelte-wb0n4a{border:solid 1px transparent;border-radius:10px;background:red;color:black} 23 | 24 | /*# sourceMappingURL=bundle.css.map */ -------------------------------------------------------------------------------- /public/global.css: -------------------------------------------------------------------------------- 1 | html, body { 2 | position: relative; 3 | width: 100%; 4 | height: 100%; 5 | } 6 | 7 | body { 8 | color: #333; 9 | margin: 0; 10 | padding: 8px; 11 | box-sizing: border-box; 12 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif; 13 | } 14 | 15 | a { 16 | color: rgb(0,100,200); 17 | text-decoration: none; 18 | } 19 | 20 | a:hover { 21 | text-decoration: underline; 22 | } 23 | 24 | a:visited { 25 | color: rgb(0,80,160); 26 | } 27 | 28 | label { 29 | display: block; 30 | } 31 | 32 | input, button, select, textarea { 33 | font-family: inherit; 34 | font-size: inherit; 35 | -webkit-padding: 0.4em 0; 36 | padding: 0.4em; 37 | margin: 0 0 0.5em 0; 38 | box-sizing: border-box; 39 | border: 1px solid #ccc; 40 | border-radius: 2px; 41 | } 42 | 43 | input:disabled { 44 | color: #ccc; 45 | } 46 | 47 | button { 48 | color: #333; 49 | background-color: #f4f4f4; 50 | outline: none; 51 | } 52 | 53 | button:disabled { 54 | color: #999; 55 | } 56 | 57 | button:not(:disabled):active { 58 | background-color: #ddd; 59 | } 60 | 61 | button:focus { 62 | border-color: #666; 63 | } 64 | -------------------------------------------------------------------------------- /public/index-dev.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Modal File Manager 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /public/index-normal.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Modal File Manager 8 | 9 | 10 | 11 | 12 | 13 | 14 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Modal File Manager 8 | 9 | 10 | 11 | 12 | 13 | 14 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /public/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mfm", 3 | "description": "A file manager written in Svelte, HTML, and CSS running in NW.js.", 4 | "license": "MIT", 5 | "version": "1.0.0", 6 | "window": { 7 | "id": "mfm", 8 | "height": 300, 9 | "width": 500, 10 | "show_in_taskbar": true, 11 | "frame": true, 12 | "show": true, 13 | "transparent": false 14 | }, 15 | "main": "index.html", 16 | "node-remote": "", 17 | "nodejs": true, 18 | "dependencies": { 19 | "chokidar": "^3.5.2" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | import svelte from 'rollup-plugin-svelte'; 2 | import resolve from '@rollup/plugin-node-resolve'; 3 | import commonjs from '@rollup/plugin-commonjs'; 4 | import livereload from 'rollup-plugin-livereload'; 5 | import { terser } from 'rollup-plugin-terser'; 6 | import nodePolyfills from 'rollup-plugin-node-polyfills'; 7 | 8 | const production = !process.env.ROLLUP_WATCH; 9 | 10 | function serve() { 11 | let server; 12 | 13 | function toExit() { 14 | if (server) server.kill(0); 15 | } 16 | 17 | return { 18 | writeBundle() { 19 | if (server) return; 20 | server = require('child_process').spawn('npm', ['run', 'start', '--', '--dev'], { 21 | stdio: ['ignore', 'inherit', 'inherit'], 22 | shell: true 23 | }); 24 | 25 | process.on('SIGTERM', toExit); 26 | process.on('exit', toExit); 27 | } 28 | }; 29 | } 30 | 31 | export default { 32 | input: 'src/main.js', 33 | output: { 34 | sourcemap: true, 35 | format: 'iife', 36 | name: 'app', 37 | file: 'public/bundle.js' 38 | }, 39 | plugins: [ 40 | nodePolyfills(), 41 | svelte({ 42 | // enable run-time checks when not in production 43 | dev: !production, 44 | // we'll extract any component CSS out into 45 | // a separate file - better for performance 46 | css: css => { 47 | css.write('bundle.css'); 48 | } 49 | }), 50 | 51 | // If you have external dependencies installed from 52 | // npm, you'll most likely need these plugins. In 53 | // some cases you'll need additional configuration - 54 | // consult the documentation for details: 55 | // https://github.com/rollup/plugins/tree/master/packages/commonjs 56 | resolve({ 57 | browser: true, 58 | dedupe: ['svelte'] 59 | }), 60 | commonjs(), 61 | 62 | // In dev mode, call `npm run start` once 63 | // the bundle has been generated 64 | !production && serve(), 65 | 66 | // Watch the `public` directory and refresh the 67 | // browser on changes when not in production 68 | !production && livereload('public'), 69 | 70 | // If we're building for production (npm run build 71 | // instead of npm run dev), minify 72 | production && terser() 73 | ], 74 | watch: { 75 | clearScreen: false 76 | } 77 | }; 78 | -------------------------------------------------------------------------------- /src/Start.svelte: -------------------------------------------------------------------------------- 1 | { 3 | $ctrlKey = e.ctrlKey; 4 | $shiftKey = e.shiftKey; 5 | $metaKey = e.metaKey; 6 | if(($skipKey && (e.key === 'Enter'))||(showComponent !== 'filemanager')) { 7 | $keyProcess = true; 8 | } else { 9 | if($keyProcess) { 10 | if($processKey !== null) $processKey(e); 11 | } 12 | } 13 | $skipKey = false; 14 | }} 15 | on:keyup={(e) => { 16 | $ctrlKey = e.ctrlKey; 17 | $shiftKey = e.shiftKey; 18 | $metaKey = e.metaKey; 19 | }} 20 | /> 21 | 22 | 25 | {#if showComponent === 'preferences'} 26 | 29 | {/if} 30 | 31 | 41 | 42 | 59 | -------------------------------------------------------------------------------- /src/components/CommandPrompt.svelte: -------------------------------------------------------------------------------- 1 |
{ exitCP(); }} 8 | > 9 | 19 | {#if filtered.length > 0} 20 |
24 | 55 |
56 | {/if} 57 |
58 | 59 | 98 | 99 | 214 | 215 | -------------------------------------------------------------------------------- /src/components/DirectoryListing.svelte: -------------------------------------------------------------------------------- 1 |
4 | {#if show} 5 | 9 | {newPath} 10 | 11 | {:else} 12 | {}} 17 | on:keypress|stopPropagation={(e) => {}} 18 | bind:value={inputPath} 19 | on:blur={editOff} 20 | on:input={processInput} 21 | /> 22 | {#if (dirlist.length > 0) && (dirListDOM !== null) && (dirInputDOM !== null)} 23 |
26 |
    27 | {#each dirlist as item, key} 28 | {#if item !== ''} 29 |
  • 30 | {item} 31 |
  • 32 | {/if} 33 | {/each} 34 |
35 |
36 | {/if} 37 | {/if} 38 |
39 | 40 | 82 | 83 | 358 | -------------------------------------------------------------------------------- /src/components/Entry.svelte: -------------------------------------------------------------------------------- 1 | {#if (((localCursor.pane === pane) && (localCursor.entry.name == entry.name))||(entry.selected))} 2 |
{ dropFiles(e,'dragend'); }} 10 | on:drop|preventDefault={(e) => { dropFiles(e, 'drop'); }} 11 | on:dragover|preventDefault={(e) => { dropFiles(e,'dragover'); }} 12 | on:dragenter|preventDefault={(e) => {dropFiles(e, 'dragenter'); }} 13 | > 14 | 15 | {#if entry.type === 0} 16 | 17 | {:else if entry.type === 1} 18 | 19 | {:else} 20 | 21 | {/if} 22 | 23 | {entry.name} 24 |
25 | {:else } 26 |
{ dropFiles(e, 'drop'); }} 33 | on:dragover|preventDefault={(e) => { dropFiles(e,'dragover'); }} 34 | > 35 | 36 | {#if entry.type === 0} 37 | 38 | {:else if entry.type === 1} 39 | 40 | {:else} 41 | 42 | {/if} 43 | 44 | 47 | {entry.name} 48 | 49 |
50 | {/if} 51 | 52 | 77 | 78 | 285 | -------------------------------------------------------------------------------- /src/components/Env.svelte: -------------------------------------------------------------------------------- 1 | {#if ((localConfig !== null) && (typeof localConfig.configuration.env !== 'undefined'))} 2 | 3 | 4 | 5 | 7 | 9 | 12 | 15 | 16 | 17 | 18 | {#each Object.entries(localConfig.configuration.env) as kv} 19 | { deleteCell(kv); }} 23 | on:save={(e) => { saveCell(kv, e); }} 24 | /> 25 | {/each} 26 | {#if addNew} 27 | 28 | 29 | 30 | 33 | 36 | 37 | {:else} 38 | 39 | 44 | 45 | {/if} 46 | 47 |
6 | 8 | 10 | Name 11 | 13 | Value 14 |
31 | 32 | 34 | 35 |
40 | { addNew = true; }}> 41 | + 42 | 43 |
48 | {/if} 49 | 50 | 67 | 68 | 108 | -------------------------------------------------------------------------------- /src/components/EnvTableRow.svelte: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 🖋️ 8 | 9 | 10 | 14 | 15 | ❌ 16 | 17 | 18 | {name} 19 | {#if editValue} 20 | 24 | {:else} 25 | {value} 26 | {/if} 27 | 28 | 29 | 44 | 45 | 73 | -------------------------------------------------------------------------------- /src/components/ExtensionPrefs.svelte: -------------------------------------------------------------------------------- 1 |
5 | {#if showMsgBox} 6 |
14 |

{msgTitle}

15 |

{msgText}

16 | 27 |
28 | {/if} 29 | {#if extensionList !== null} 30 | 33 | 34 | 35 | 38 | 40 | 42 | 43 | 44 | 45 | {#each extensionList as item} 46 | 47 | 50 | 64 | 74 | 75 | {/each} 76 | 77 |
36 | Extension Name 37 | 39 | 41 |
48 | {item.name} 49 | 51 | 63 | 65 | 73 |
78 | {/if} 79 |
82 | 87 | { 95 | setFocus(false); 96 | }} 97 | on:mouseleave={() => { 98 | setFocus(true); 99 | }} 100 | on:blur={()=>{ 101 | setFocus(true); 102 | }} 103 | /> 104 | 114 |
115 |
116 | 117 | 186 | 187 | 422 | -------------------------------------------------------------------------------- /src/components/ExtraPanel.svelte: -------------------------------------------------------------------------------- 1 |
5 | {#if isExtra} 6 | {@html extraHTML} 7 | {/if} 8 |
{ fullPath }
9 | {#if (extension === '.png') || 10 | (extension === '.jpg') || 11 | (extension === '.svg') || 12 | (extension === '.jpeg') || 13 | (extension === '.gif') || 14 | (extension === '.apng') || 15 | (extension === '.avif') || 16 | (extension === '.webp') || 17 | (extension === '.avi')} 18 | {fullPath} 21 | {:else if isMovieFlag} 22 | 27 |

28 | Dimensions: {videoDem} 29 |

30 | {/if} 31 |
32 |

Date: {localCurrentCursor.entry.datetime}

33 |

Size: {size}

34 |
35 |
36 | 37 | 62 | 63 | 177 | -------------------------------------------------------------------------------- /src/components/GeneralPrefs.svelte: -------------------------------------------------------------------------------- 1 |
5 | {#if localConfig !== null} 6 |

Environment for Launching Programs

7 | 8 | 9 |

Other Configuration Items

10 |
13 | 18 | {#if localConfig.configuration.useTrash} 19 | 25 | {:else} 26 | 31 | {/if} 32 |
33 | {/if} 34 |
35 | 36 | 59 | 60 | 105 | -------------------------------------------------------------------------------- /src/components/GitHub.svelte: -------------------------------------------------------------------------------- 1 |
{ exitGitHub(); }} 8 | > 9 |
10 |

GitHub Themes and Extensions Importer

11 | { 13 | exitGitHub(); 14 | }} 15 | style="color: {$theme.Red};" 16 | > 17 | X 18 | 19 | 24 |
25 |
28 | {#await repos} 29 |

Loading Extensions Repositories....

30 | {:then value} 31 | {#if check(value)} 32 | {#each value.data.items as repo} 33 |
34 |
35 |

36 | {repo.name} 37 |

38 |

41 | {repo.stargazers_count} ⭐️ s 42 |

43 |
44 |
45 |

46 | {repo.description} 47 |

48 |
49 | {#if hasMsg(repo)} 50 |
53 | {@html getMsg(repo)} 54 |
55 | {/if} 56 |
57 | {#if extExists(repo)} 58 | 66 | {:else} 67 | 75 | {/if} 76 |
77 |
78 | {/each} 79 | {/if} 80 | {:catch error} 81 |

There was an error: {error}

82 | {/await} 83 | {#await themes} 84 |

Loading Theme Repositories....

85 | {:then valueTheme} 86 | {#if check(valueTheme)} 87 | {#each valueTheme.data.items as thm} 88 |
89 |
90 |

91 | {thm.name} 92 |

93 |

96 | {thm.stargazers_count} ⭐️ s 97 |

98 |
99 |
100 |

101 | {thm.description} 102 |

103 |
104 | {#if hasMsg(thm)} 105 |
108 | {@html getMsg(thm)} 109 |
110 | {/if} 111 |
112 | {#if themeExists(thm)} 113 | 121 | 129 | {:else} 130 | 138 | {/if} 139 |
140 |
141 | {/each} 142 | {/if} 143 | {:catch error} 144 |

There was an error: {error}

145 | {/await} 146 |
147 |
148 | 149 | 224 | 225 | 439 | 440 | -------------------------------------------------------------------------------- /src/components/MessageBox.svelte: -------------------------------------------------------------------------------- 1 |
2 |
7 | {#if config !== null} 8 |

{config.title}

9 | {#if typeof items !== null} 10 | {#each items as item} 11 | {#if typeof item !== 'undefined'} 12 | {#if item.type === 'input'} 13 |

{item.msg}

14 | { 18 | if(e.key === 'Enter') { 19 | e.preventDefault(); 20 | returnValue(true); 21 | } 22 | }} 23 | /> 24 | {:else if item.type === 'selector'} 25 | 37 | {:else if item.type === 'picker'} 38 |
41 | 46 |
51 | {#each pickerItems as selection, key } 52 | {#if key === pickerNum } 53 | { 59 | pickerSelected(selection); 60 | }} 61 | > 62 | {selection.name} 63 | 64 | {:else} 65 | { 70 | pickerSelected(selection); 71 | }} 72 | > 73 | {selection.name} 74 | 75 | {/if} 76 | {/each} 77 |
78 |
79 | {:else if item.type === 'spinner'} 80 | 85 | {:else if item.type === 'label'} 86 | 92 | {:else if item.type === 'html'} 93 | {@html item.text} 94 | {/if} 95 | {:else} 96 |

System Error

97 | {/if} 98 | {/each} 99 | {:else} 100 |

System Error

101 | {/if} 102 | {#if ((typeof config.noShowButton !== 'undefined')&&(!config.noShowButton))} 103 |
104 | 107 | 110 |
111 | {/if} 112 | {/if} 113 |
114 |
115 | 116 | 172 | 173 | 361 | 362 | -------------------------------------------------------------------------------- /src/components/Pane.svelte: -------------------------------------------------------------------------------- 1 |
5 | {#each entries as entry,index} 6 | 15 | {/each} 16 |
{cursorToPane()}} 20 | on:drop|preventDefault={(e) => { dropFiles(e, 'drop'); }} 21 | on:dragover|preventDefault={(e) => { dropFiles(e,'dragover'); }} 22 | > 23 |
24 |
25 | 26 | 41 | 42 | 197 | -------------------------------------------------------------------------------- /src/components/Preferences.svelte: -------------------------------------------------------------------------------- 1 |
8 | { 12 | if(keepBlur&&(vimInput !== null)) { 13 | vimInput.focus(); 14 | } 15 | }} 16 | on:keydown={(e) => { 17 | if(keepBlur) { 18 | e.preventDefault(); 19 | switch(e.key) { 20 | case 'g': 21 | showPanel = 'general'; 22 | break; 23 | case 't': 24 | showPanel = 'theme'; 25 | break; 26 | case 'e': 27 | showPanel = 'extension'; 28 | break; 29 | case 'ArrowUp': 30 | case 'k': 31 | scrollDiv(-1); 32 | break; 33 | case 'ArrowDown': 34 | case 'j': 35 | scrollDiv(1); 36 | break; 37 | case 'Escape': 38 | keepBlur = false; 39 | exitPrefs(); 40 | break; 41 | default: 42 | break; 43 | } 44 | } 45 | }} 46 | /> 47 |

Preferences

48 |
    49 | {#if showPanel === 'general'} 50 |
  • { showPanel = 'general'; }} 52 | style="border-color: {$theme.textColor}; 53 | background-color: {$theme.textColor}; 54 | color: {$theme.backgroundColor};" 55 | > 56 | General 57 |
  • 58 | {:else} 59 |
  • { showPanel = 'general'; }} 61 | style="border-color: {$theme.textColor}; 62 | color: {$theme.textColor}; 63 | background-color: {$theme.backgroundColor};" 64 | > 65 | General 66 |
  • 67 | {/if} 68 | {#if showPanel === 'theme'} 69 |
  • { showPanel = 'theme'; }} 71 | style="border-color: {$theme.textColor}; 72 | background-color: {$theme.textColor}; 73 | color: {$theme.backgroundColor};" 74 | > 75 | Theme 76 |
  • 77 | {:else} 78 |
  • { showPanel = 'theme'; }} 80 | style="border-color: {$theme.textColor}; 81 | color: {$theme.textColor}; 82 | background-color: {$theme.backgroundColor};" 83 | > 84 | Theme 85 |
  • 86 | {/if} 87 | {#if showPanel === 'extension'} 88 |
  • { showPanel = 'extension'; }} 90 | style="border-color: {$theme.textColor}; 91 | background-color: {$theme.textColor}; 92 | color: {$theme.backgroundColor};" 93 | > 94 | Extension 95 |
  • 96 | {:else} 97 |
  • { showPanel = 'extension'; }} 99 | style="border-color: {$theme.textColor}; 100 | color: {$theme.textColor}; 101 | background-color: {$theme.backgroundColor};" 102 | > 103 | Extension 104 |
  • 105 | {/if} 106 |
107 | {#if showPanel === 'general'} 108 | { 110 | scrollDOM = e.detail.DOM; 111 | }} 112 | on:setKeyProcess={(e) => { 113 | keepBlur = e.detail.blur; 114 | if(keepBlur) timeOut = setTimeout(focusInput, 1000); 115 | }} 116 | /> 117 | {:else if showPanel === 'theme'} 118 | { 120 | scrollDOM = e.detail.DOM; 121 | }} 122 | on:setKeyProcess={(e) => { 123 | keepBlur = e.detail.blur; 124 | if(keepBlur) timeOut = setTimeout(focusInput, 1000); 125 | }} 126 | /> 127 | {:else if showPanel === 'extension'} 128 | { 130 | switchView(e.detail.view); 131 | }} 132 | on:setScrollDOM={(e) => { 133 | scrollDOM = e.detail.DOM; 134 | }} 135 | on:setKeyProcess={(e) => { 136 | keepBlur = e.detail.blur; 137 | if(keepBlur) timeOut = setTimeout(focusInput, 1000); 138 | }} 139 | /> 140 | {/if} 141 |
144 | 155 |
156 |
157 | 158 | 211 | 212 | 347 | 348 | -------------------------------------------------------------------------------- /src/components/QuickSearch.svelte: -------------------------------------------------------------------------------- 1 |
4 | { exitQS(); }} 10 | style="background-color: {localTheme.textColor}; 11 | text-color: {localTheme.backgroundColor};" 12 | /> 13 |
14 | 15 | 44 | 45 | 146 | -------------------------------------------------------------------------------- /src/components/ResizeBorder.svelte: -------------------------------------------------------------------------------- 1 |
{ 10 | dispatch('mouseDown', true); 11 | }} 12 | on:mouseup={e => { 13 | dispatch('mouseDown', false); 14 | }} 15 | > 16 |
17 | 18 | 26 | 27 | 44 | -------------------------------------------------------------------------------- /src/components/StatusLine.svelte: -------------------------------------------------------------------------------- 1 |
4 | 7 | {localInputState} 8 | 9 | 12 | {localCurrentCursor.pane} 13 | 14 | 18 | {localCurrentCursor.entry.name} 19 | 20 | 24 | {localCurrentCursor.entry.datetime} 25 | 26 | 31 | {size} 32 | 33 |
34 | 35 | 89 | 90 | 148 | 149 | -------------------------------------------------------------------------------- /src/components/ThemeItem.svelte: -------------------------------------------------------------------------------- 1 | 2 | 3 | {label} 4 | 5 | { changeValue(); }} 7 | > 8 |
9 | {#if changeString} 10 | { 17 | changeStringValue(value); 18 | }} 19 | /> 20 | {:else} 21 | {value} 22 | {/if} 23 | {#if value.startsWith('#')} 24 |
25 |
26 | {/if} 27 |
28 | 29 | 30 | {#if changeColor} 31 |
34 | { colorCallback(e.detail); }} startColor={value}/> 35 | 40 |
41 | {/if} 42 | 43 | 69 | 70 | 118 | -------------------------------------------------------------------------------- /src/components/ThemePrefs.svelte: -------------------------------------------------------------------------------- 1 |
5 |

Current Theme Values

6 | {#if localTheme !== null} 7 | 8 | 9 | 10 | 13 | 16 | 17 | 18 | 19 | {#each Object.entries(localTheme) as kv} 20 | { changeValue(kv, e); }} 24 | /> 25 | {/each} 26 | 27 |
11 | Name 12 | 14 | Value 15 |
28 | {/if} 29 |

Save as New Theme

30 |
33 | 38 | { 46 | setFocus(false); 47 | }} 48 | on:mouseleave={() => { 49 | setFocus(true); 50 | }} 51 | on:blur={() => { 52 | setFocus(true); 53 | }} 54 | /> 55 | 65 |
66 | {#if showMsgBox} 67 |
75 |

{msgTitle}

76 |

{msgText}

77 | 88 |
89 | {/if} 90 |

Existing Themes

91 | {#if themeList !== null} 92 | 95 | 96 | 97 | 100 | 102 | 104 | 106 | 107 | 108 | 109 | {#each themeList as item} 110 | 111 | 114 | 128 | 142 | 152 | 153 | {/each} 154 | 155 |
98 | Theme Name 99 | 101 | 103 | 105 |
112 | {item.name} 113 | 115 | 127 | 129 | 141 | 143 | 151 |
156 | {/if} 157 |
158 | 159 | 233 | 234 | 373 | -------------------------------------------------------------------------------- /src/main.js: -------------------------------------------------------------------------------- 1 | import Start from './Start.svelte'; 2 | 3 | var mb = new nw.Menu({type:"menubar"}); 4 | var submenu = new nw.Menu(); 5 | submenu.append(new nw.MenuItem({ 6 | label: "About MFM" 7 | })); 8 | submenu.append(new nw.MenuItem({ type: 'separator' })); 9 | submenu.append(new nw.MenuItem({ 10 | label: "Quit MFM", 11 | click: function() { 12 | nw.App.closeAllWindows(); 13 | }, 14 | key: 'q' 15 | })) 16 | mb.append(new nw.MenuItem({ 17 | label: "MFM", 18 | tooltip: "MFM", 19 | submenu: submenu 20 | })); 21 | nw.Window.get().menu = mb; 22 | 23 | const ST = new Start({ 24 | target: document.body, 25 | props: {} 26 | }); 27 | 28 | export default ST; 29 | -------------------------------------------------------------------------------- /src/modules/commands.js: -------------------------------------------------------------------------------- 1 | // 2 | // File: commands.js 3 | // 4 | // Description: This file contains the commands object for dealing with commands that 5 | // SvelteFileManager uses. 6 | // 7 | 8 | var commands = { 9 | commandList: [], 10 | lastError: '', 11 | addCommand: function(name, altname, description, command) { 12 | commands.commandList.push({ 13 | name: name, 14 | altname: altname, 15 | description: description, 16 | command: command 17 | }); 18 | }, 19 | runCommand: function(com) { 20 | // 21 | // Get the command. 22 | // 23 | const command = commands.getCommand(com); 24 | 25 | // 26 | // Run the command. 27 | // 28 | try { 29 | if(command !== null) command.command(); 30 | } catch(e) { 31 | // 32 | // Something happened in the command. Tell about it. 33 | // 34 | commands.lastError = e; 35 | console.log(e); 36 | } 37 | }, 38 | getCommand: function(com) { 39 | return commands.commandList.find(item => item.name === com ) 40 | }, 41 | getAltCommand: function(com) { 42 | return commands.commandList.find(item => item.altname === com ) 43 | }, 44 | listCommands: function() { 45 | return commands.commandList.map(item => { 46 | return { 47 | name: item.name, 48 | description: item.description 49 | }; 50 | }) 51 | }, 52 | removeCommand: function(com) { 53 | commands.commandList = commands.commandList.filter(item => item.name !== com); 54 | } 55 | } 56 | 57 | export default commands; 58 | -------------------------------------------------------------------------------- /src/modules/extensions.js: -------------------------------------------------------------------------------- 1 | // 2 | // File: extensions.js 3 | // 4 | // Description: This object contains the extensions used and interacts with them. 5 | // 6 | 7 | var extensions = { 8 | fileSystems: null, 9 | commands: null, 10 | extCommandList: [], 11 | extensionList: [], 12 | extensionDir: '', 13 | localFS: null, 14 | config: null, 15 | load: function(confg, LFS) { 16 | // 17 | // Load the extensions from the file system. The extension directory 18 | // contains each extension in it's own directory. Each extension should 19 | // have a standard `package.json` that all npm packages have. This json 20 | // will also have fields for the extension as follows: 21 | // 22 | //{ 23 | // ... 24 | // mfmextension: { 25 | // name: 'name of the extension', 26 | // description: 'description of the extension', 27 | // type: 0, // This a designation as the extensions origin: 0 - local, 1 - github 28 | // github: 'address to the extension on GitHub', 29 | // main: 'name of the JavaScript file' 30 | // } 31 | //} 32 | // 33 | // Single files in the extensions directory will be ignored. 34 | // 35 | extensions.config = confg; 36 | extensions.localFS = LFS; 37 | try{ 38 | var items = extensions.localFS.readDir(extensions.extensionDir); 39 | var stats = []; 40 | for (var i=0; i { 116 | return { name: item.name, description: item.description}; 117 | }); 118 | }, 119 | getExtCommand: function(name) { 120 | return(extensions.extCommandList.find(item => item.name === name)); 121 | }, 122 | addExtension: function(name, description, extension, type, github) { 123 | // 124 | // Add it to the stack. 125 | // 126 | extensions.extensionList.push({ 127 | name: name, 128 | description: description, 129 | extension: extension, 130 | type: type, 131 | github: github 132 | }); 133 | }, 134 | unloadExtensions: function() { 135 | if(extensions.extensionList !== null) { 136 | extensions.extensionList.forEach(item => { 137 | if((typeof item.extension !== 'undefined')&&(typeof item.extension.unload !== 'undefined')) item.extension.unload(); 138 | }); 139 | } 140 | }, 141 | init: function() { 142 | if(extensions.extensionList !== null) { 143 | extensions.extensionList.forEach(item => { 144 | if(typeof item.extension !== 'undefined') item.extension.init(extensions); 145 | }); 146 | } 147 | }, 148 | installKeyMaps: function() { 149 | if(extensions.extensionList !== null) { 150 | extensions.extensionList.forEach(item => { 151 | if(typeof item.extension !== 'undefined') item.extension.installKeyMaps(); 152 | }); 153 | } 154 | }, 155 | getExtension: function(ext) { 156 | return extensions.extensionList.find((item) => { item.name == ext }); 157 | }, 158 | listExtensions: function() { 159 | return extensions.extensionList.map(item => { 160 | return { 161 | name: item.name, 162 | description: item.description 163 | }; 164 | }) 165 | }, 166 | removeExtension: function(ext) { 167 | var exten = extensions.extensionList.filter(item => item.name === ext)[0]; 168 | exten.unload(); 169 | extensions.extensionList = extensions.extensionList.filter(item => item.name != ext); 170 | }, 171 | getLocalFS: function() { 172 | return extensions.localFS; 173 | } 174 | } 175 | 176 | export default extensions; 177 | 178 | -------------------------------------------------------------------------------- /src/modules/filesystems.js: -------------------------------------------------------------------------------- 1 | // 2 | // File: filesystems.js 3 | // 4 | // Description: This module contains all the supported file system objects. 5 | // 6 | 7 | var filesystems = { 8 | fileSystemList: [], 9 | addFileSystem: function(name, description, fs) { 10 | filesystems.fileSystemList.push({ 11 | name: name, 12 | description: description, 13 | fileSystem: fs 14 | }); 15 | }, 16 | getFileSystem: function(fs) { 17 | return filesystems.fileSystemList.find((item) => { item.name == com }) 18 | }, 19 | listFileSystems: function() { 20 | return filesystems.fileSystemList.map(item => { 21 | return { 22 | name: item.name, 23 | description: item.description 24 | }; 25 | }) 26 | }, 27 | removeFileSystem: function(fs) { 28 | filesystems.fileSystemList = filesystems.fileSystemList.filter(item => item.name != fs); 29 | } 30 | } 31 | 32 | export default filesystems; 33 | -------------------------------------------------------------------------------- /src/modules/linux.js: -------------------------------------------------------------------------------- 1 | // 2 | // File: linux.js 3 | // 4 | // Description: This file contains all low level 5 | // functions that would be OS dependent. 6 | // 7 | 8 | const path = require('path'); 9 | const os = require('os'); 10 | const fs = require('fs'); 11 | const childProcess = require('child_process'); 12 | 13 | var linux = { 14 | dirFirst: true, 15 | sortFunction: null, 16 | filterFunction: null, 17 | lastError: '', 18 | lastOutput: '', 19 | getExtension: function(file) { 20 | return path.extname(file); 21 | }, 22 | getConfigDir: function() { 23 | return this.appendPath(this.getHomeDir(), '.config/modalfilemanager'); 24 | }, 25 | terminalScript: "bin/openTerminal.scpt", 26 | init: function() { 27 | this.sortFunction = this.alphaSort; 28 | this.filterFunction = this.defaultFilter; 29 | }, 30 | getDirFirst: function() { 31 | return dirFirst; 32 | }, 33 | setDirFirst: function(flag) { 34 | if(typeof flag === 'boolean') { 35 | this.dirFirst = flag; 36 | } 37 | }, 38 | setDirSort: function(sortFunction) { 39 | this.sortFunction = sortFunction; 40 | }, 41 | setFilter: function(flt) { 42 | this.filterFunction = flt; 43 | }, 44 | getTerminalScript: function() { 45 | return this.appendPath(this.getHomeDir(), this.terminalScript); 46 | }, 47 | setTerminalScript: function(scrpt) { 48 | this.terminalScript = scrpt; 49 | }, 50 | getHomeDir: function() { 51 | return(os.homedir()); 52 | }, 53 | pathSep() { 54 | return(path.sep); 55 | }, 56 | readDir: function(dir) { 57 | if(typeof dir === 'object') dir = this.appendPath(dir.dir, dir.name); 58 | return fs.readdirSync(this.preserveQuotes(dir)); 59 | }, 60 | dirExists: function(dir) { 61 | if(typeof dir.name !== 'undefined') dir = this.appendPath(dir.dir, dir.name); 62 | return this.fileExists(this.preserveQuotes(dir)); 63 | }, 64 | fileExists: function(file) { 65 | var result = true; 66 | if(typeof file === 'object') file = this.preserveQuotes(this.appendPath(file.dir, file.name)); 67 | try { 68 | fs.accessSync(file); 69 | result = true; 70 | } catch(e) { 71 | result = false; 72 | } 73 | return result; 74 | }, 75 | makeDir: function(dir) { 76 | if(typeof dir.name !== 'undefined') dir = this.appendpath(dir.dir, dir.name); 77 | fs.mkdirSync(this.preserveQuotes(dir)); 78 | }, 79 | preserveQuotes: function(str) { 80 | return(str.replaceAll(/(\"|\'|\`)/gi,'\\$1')); 81 | }, 82 | moveEntries: function(from, to, callback) { 83 | var fromName = this.preserveQuotes(from.fileSystem.appendPath(from.dir, from.name)); 84 | var toName = this.preserveQuotes(to.dir); 85 | var that = this; 86 | if(typeof callback === 'undefined') { 87 | childProcess.exec("mv '" + fromName + "' '" + toName + "'",(err,stdout) => { 88 | if(err) that.lastError = err; 89 | that.lastOutput = stdout; 90 | }); 91 | } else { 92 | childProcess.exec("mv '" + fromName + "' '" + toName + "'", callback); 93 | } 94 | }, 95 | copyEntries: function(from, to, flag, callback) { 96 | if(typeof flag === 'undefined') flag = false; 97 | var fromName = this.preserveQuotes(from.fileSystem.appendPath(from.dir, from.name)); 98 | var toName = this.preserveQuotes(to.dir); 99 | if(flag) { 100 | toName = this.preserveQuotes(to.fileSystem.appendPath(to.dir,to.name)); 101 | } 102 | var that = this; 103 | if(typeof callback === 'undefined') { 104 | childProcess.exec("cp -R '" + fromName + "' '" + toName + "'",(err,stdout) => { 105 | if(err) that.lastError = err; 106 | that.lastOutput = stdout; 107 | }); 108 | } else { 109 | childProcess.exec("cp -R '" + fromName + "' '" + toName + "'", callback); 110 | } 111 | }, 112 | deleteEntries: function(entry, callback) { 113 | var item = this.preserveQuotes(entry.fileSystem.appendPath(entry.dir, entry.name)); 114 | var that = this; 115 | if(typeof callback === 'undefined') { 116 | childProcess.exec("rm -R '" + item + "'",(err,stdout) => { 117 | if(err) that.lastError = err; 118 | that.lastOutput = stdout; 119 | }); 120 | } else { 121 | childProcess.exec("rm -R '" + item + "'", callback); 122 | } 123 | }, 124 | getDirList: function(dir) { 125 | // 126 | // A directory list is provided giving an array of entry object. Each 127 | // entry object has: 128 | // name The name of the file 129 | // type The type of the entry: a file - 0, a directory - 1, a link - 2 130 | // fileSystem The current file system object 131 | // dir The directory of the file 132 | // datetime The creation datetime of the file 133 | // size The integer size of the file in 1kb (Directories and links have zero size) 134 | // selected Boolean true is selected, false is not selected 135 | // 136 | var entries = []; 137 | if((typeof dir === 'object') && (typeof dir.name !== 'undefined')) dir = this.appendPath(dir.dir, dir.name); 138 | if(this.dirExists(dir)) { 139 | var items = fs.readdirSync(dir); 140 | for (var i=0; i 1)) { 145 | file = dir + path.sep + items[i]; 146 | } else { 147 | file = path.sep + items[i]; 148 | } 149 | var newEntry; 150 | try { 151 | var stats = fs.statSync(file); 152 | } catch(e) { 153 | newEntry = { 154 | name: items[i], 155 | dir: dir, 156 | fileSystemType: "macOS", 157 | fileSystem: this, 158 | selected: false, 159 | datetime: '', 160 | type: 0, 161 | size: 0, 162 | stats: null 163 | }; 164 | } 165 | newEntry = { 166 | name: items[i], 167 | dir: dir, 168 | fileSystemType: "macOS", 169 | fileSystem: this, 170 | selected: false, 171 | datetime: stats.mtime.toLocaleString(), 172 | type: 0, 173 | size: 0, 174 | stats: stats 175 | }; 176 | if(stats.isDirectory()) { 177 | // 178 | // It's a directory. 179 | // 180 | newEntry.type = 1; 181 | newEntry.size = 0; 182 | } else if(stats.isSymbolicLink()) { 183 | // 184 | // It's a link. 185 | // 186 | newEntry.type = 2; 187 | newEntry.size = stats['size']; 188 | } else { 189 | // 190 | // It's a file. 191 | // 192 | newEntry.type = 0; 193 | newEntry.size = stats['size']; 194 | } 195 | entries.push(newEntry); 196 | } 197 | } 198 | 199 | // 200 | // filter out the entries. 201 | // 202 | entries = entries.filter(this.filterFunction); 203 | 204 | // 205 | // Sort the entries. 206 | // 207 | if(this.dirFirst) { 208 | var dirEntries = entries.filter(item => item.type === 1); 209 | var fileEntries = entries.filter(item => item.type === 0); 210 | dirEntries.sort(this.sortFunction); 211 | fileEntries.sort(this.sortFunction); 212 | entries = [...dirEntries, ...fileEntries]; 213 | } else { 214 | entries.sort(this.sortFunction); 215 | } 216 | } 217 | 218 | // 219 | // Return the result. 220 | // 221 | return(entries); 222 | }, 223 | defaultFilter: function(item) { 224 | return ((item.name[0] !== '.') && 225 | (!item.name.includes('Icon'))); 226 | }, 227 | allFilter: function(item) { 228 | // 229 | // Still, don't show the Icon and DS_Store files. 230 | // 231 | return((!item.name.includes('Icon')) && 232 | (!item.name.includes('.DS_Store'))); 233 | }, 234 | alphaSort: function(item1, item2) { 235 | const a = item1.name.toLowerCase(); 236 | const b = item2.name.toLowerCase(); 237 | return a === b ? 0 : a > b ? 1 : -1; 238 | }, 239 | openFile: function(dir, file) { 240 | // 241 | // For macOS, open with the open command line command. 242 | // 243 | childProcess.execSync("/usr/bin/open '" + this.appendPath(dir, file) + "'"); 244 | }, 245 | openFileWithProgram: function(prog,file) { 246 | // 247 | // For macOS, open with the open command line command. 248 | // 249 | childProcess.exec("/usr/bin/open -a '" + prog + "' '" + file + "'", (err, stdin, stdout) => {}); 250 | }, 251 | openInTerminal: function(prog, file) { 252 | childProcess.exec("/usr/bin/osascript " + this.terminalScript + " '" + prog + " \"" + file + "\"'", (err, stdin, stdout) => {}); 253 | }, 254 | runCommandLine: function(line, callback) { 255 | if(typeof callback === 'undefined') { 256 | childProcess.exec(line, (err, stdin, stdout) => {}); 257 | } else { 258 | childProcess.exec(line, callback); 259 | } 260 | }, 261 | appendPath: function(dir, name) { 262 | // 263 | // dir can be an entry or a path string. name is always a string. 264 | // 265 | if(typeof dir === 'object') dir = this.appendPath(dir.dir, dir.name); 266 | if(dir == path.sep) { 267 | return path.sep + name; 268 | } else { 269 | if(dir[dir.length-1] === path.sep) { 270 | return dir + name; 271 | } else { 272 | return dir + path.sep + name; 273 | } 274 | } 275 | }, 276 | getStats: function(file) { 277 | return fs.statSync(file); 278 | }, 279 | readFile: function(file) { 280 | if(typeof file === 'objct') file = this.appendPath(file.dir, file.name); 281 | return fs.readFileSync(file); 282 | }, 283 | writeFile: function(file,data) { 284 | if(typeof file === 'object') file = this.appendPath(file.dir, file.name); 285 | fs.writeFileSync(file,data); 286 | }, 287 | renameEntry: function(oldE, newE) { 288 | console.log(oldE); 289 | console.log(newE); 290 | var fromName = this.preserveQuotes(oldE.fileSystem.appendPath(oldE.dir, oldE.name)); 291 | var toName = this.preserveQuotes(newE.fileSystem.appendPath(newE.dir, newE.name)); 292 | childProcess.execSync('mv "' + fromName + '" "' + toName + '"'); 293 | }, 294 | createFile: function(file) { 295 | var fnm = this.preserveQuotes(file.fileSystem.appendPath(file.dir, file.name)); 296 | childProcess.execSync('touch "' + fnm + '"'); 297 | }, 298 | createDir: function(dir) { 299 | var dnm = this.preserveQuotes(dir.fileSystem.appendPath(dir.dir, dir.name)); 300 | childProcess.execSync('mkdir "' + dnm + '"'); 301 | }, 302 | loadJavaScript: function(file) { 303 | var result = ''; 304 | 305 | // 306 | // Read in the JavaScript file and run it. It should return an extension object. 307 | // 308 | var jfile = this.readFile(file).toString(); 309 | try { 310 | var scriptFunction = new Function('',jfile); 311 | result = scriptFunction(); 312 | }catch(e) { 313 | console.log(e); 314 | this.lastError = e.toString(); 315 | result = null; 316 | } 317 | return(result); 318 | }, 319 | searchDir: function(pat, dir, numEntries, returnFunction) { 320 | try { 321 | if(dir === '') dir = this.pathSep(); 322 | if(pat !== '') { 323 | childProcess.exec('/usr/local/bin/fd -i --max-results ' + numEntries + ' -t d "' + pat + '" "' + dir + '"', (err, data) => { 324 | if(err) { 325 | console.log(err); 326 | this.lastError = err.toString(); 327 | } else { 328 | returnFunction(data.toString().split('\n')); 329 | } 330 | }); 331 | } 332 | } catch(e) { 333 | console.log(e); 334 | this.lastError = e.toString(); 335 | } 336 | } 337 | } 338 | 339 | export default linux; 340 | -------------------------------------------------------------------------------- /src/modules/macOS.js: -------------------------------------------------------------------------------- 1 | // 2 | // File: macOS.js 3 | // 4 | // Description: This file contains all low level 5 | // functions that would be OS dependent. 6 | // 7 | 8 | const path = require('path'); 9 | const os = require('os'); 10 | const fs = require('fs'); 11 | const childProcess = require('child_process'); 12 | 13 | var macOS = { 14 | dirFirst: true, 15 | sortFunction: null, 16 | filterFunction: null, 17 | lastError: '', 18 | lastOutput: '', 19 | config: null, 20 | type: 'macOS', 21 | setConfig: function(cfg) { 22 | this.config = cfg; 23 | }, 24 | getExtension: function(file) { 25 | return path.extname(file); 26 | }, 27 | getConfigDir: function() { 28 | return this.appendPath(this.getHomeDir(), '.config/modalfilemanager'); 29 | }, 30 | terminalScript: "bin/openTerminal.scpt", 31 | init: function() { 32 | this.sortFunction = this.alphaSort; 33 | this.filterFunction = this.defaultFilter; 34 | }, 35 | getDirFirst: function() { 36 | return dirFirst; 37 | }, 38 | setDirFirst: function(flag) { 39 | if(typeof flag === 'boolean') { 40 | this.dirFirst = flag; 41 | } 42 | }, 43 | setDirSort: function(sortFunction) { 44 | this.sortFunction = sortFunction; 45 | }, 46 | setFilter: function(flt) { 47 | this.filterFunction = flt; 48 | }, 49 | getTerminalScript: function() { 50 | return this.appendPath(this.getHomeDir(), this.terminalScript); 51 | }, 52 | setTerminalScript: function(scrpt) { 53 | this.terminalScript = scrpt; 54 | }, 55 | getHomeDir: function() { 56 | return(os.homedir()); 57 | }, 58 | pathSep() { 59 | return(path.sep); 60 | }, 61 | readDir: function(dir) { 62 | if(typeof dir === 'object') dir = this.appendPath(dir.dir, dir.name); 63 | return fs.readdirSync(this.preserveQuotes(dir)); 64 | }, 65 | normalize: function(dir) { 66 | if(dir[0] === '~') dir = this.appendPath(this.getHomeDir(),dir.slice(1,dir.length)); 67 | dir = path.normalize(dir); 68 | return(dir); 69 | }, 70 | dirExists: function(dir) { 71 | if((typeof dir === 'object') && (typeof dir.name !== 'undefined')) dir = this.appendPath(dir.dir, dir.name); 72 | return this.fileExists(this.preserveQuotes(dir)); 73 | }, 74 | fileExists: function(file) { 75 | var result = true; 76 | if(typeof file === 'object') file = this.preserveQuotes(this.appendPath(file.dir, file.name)); 77 | try { 78 | fs.accessSync(file); 79 | result = true; 80 | } catch(e) { 81 | result = false; 82 | } 83 | return result; 84 | }, 85 | makeDir: function(dir) { 86 | if(typeof dir.name !== 'undefined') dir = this.appendpath(dir.dir, dir.name); 87 | fs.mkdirSync(this.preserveQuotes(dir)); 88 | }, 89 | preserveQuotes: function(str) { 90 | return(str); 91 | }, 92 | moveEntries: function(from, to, callback) { 93 | var fromName = this.preserveQuotes(this.appendPath(from.dir, from.name)); 94 | var toName = this.preserveQuotes(to.dir); 95 | var that = this; 96 | if(typeof callback === 'undefined') { 97 | childProcess.exec("mv '" + fromName + "' '" + toName + "'",(err,stdout) => { 98 | if(err) that.lastError = err; 99 | that.lastOutput = stdout; 100 | }); 101 | } else { 102 | childProcess.exec("mv '" + fromName + "' '" + toName + "'", callback); 103 | } 104 | }, 105 | copyEntries: function(from, to, flag, callback) { 106 | if(typeof flag === 'undefined') flag = false; 107 | var fromName = this.preserveQuotes(this.appendPath(from.dir, from.name)); 108 | if(typeof from === 'string') fromName = this.preserveQuotes(from); 109 | var toName = this.preserveQuotes(to.dir); 110 | if(typeof to === 'string') toName = this.preserveQuotes(to); 111 | if(flag) { 112 | toName = this.preserveQuotes(to.fileSystem.appendPath(to.dir,to.name)); 113 | } 114 | var that = this; 115 | if(typeof callback === 'undefined') { 116 | childProcess.exec("cp -R '" + fromName + "' '" + toName + "'",(err,stdout) => { 117 | if(err) that.lastError = err; 118 | that.lastOutput = stdout; 119 | }); 120 | } else { 121 | childProcess.exec("cp -R '" + fromName + "' '" + toName + "'", callback); 122 | } 123 | }, 124 | deleteEntries: function(entry, callback) { 125 | var item = this.preserveQuotes(this.appendPath(entry.dir, entry.name)); 126 | var that = this; 127 | if(this.config !== null) { 128 | if(this.config.useTrash) { 129 | // 130 | // Use the trashcan on the system. 131 | // 132 | if(typeof callback === 'undefined') { 133 | this.runCommandLine("trash '" + item + "'",(err,stdout) => { 134 | if(err) { 135 | that.lastError = err; 136 | console.log(err); 137 | } 138 | that.lastOutput = stdout; 139 | }); 140 | } else { 141 | this.runCommandLine("trash '" + item + "'", callback); 142 | } 143 | } else { 144 | if(typeof callback === 'undefined') { 145 | childProcess.exec("rm -R '" + item + "'",(err,stdout) => { 146 | if(err) that.lastError = err; 147 | that.lastOutput = stdout; 148 | }); 149 | } else { 150 | childProcess.exec("rm -R '" + item + "'", callback); 151 | } 152 | } 153 | } 154 | }, 155 | getDirList: function(dir) { 156 | // 157 | // A directory list is provided giving an array of entry object. Each 158 | // entry object has: 159 | // name The name of the file 160 | // type The type of the entry: a file - 0, a directory - 1, a link - 2 161 | // fileSystem The current file system object 162 | // dir The directory of the file 163 | // datetime The creation datetime of the file 164 | // size The integer size of the file in 1kb (Directories and links have zero size) 165 | // selected Boolean true is selected, false is not selected 166 | // 167 | var entries = []; 168 | if((typeof dir === 'object') && (typeof dir.name !== 'undefined')) dir = this.appendPath(dir.dir, dir.name); 169 | dir = this.normalize(dir); 170 | if(this.dirExists(dir)) { 171 | var items = fs.readdirSync(dir); 172 | for (var i=0; i 1)) { 177 | file = dir + path.sep + items[i]; 178 | } else { 179 | file = path.sep + items[i]; 180 | } 181 | var newEntry; 182 | try { 183 | var stats = fs.statSync(file); 184 | } catch(e) { 185 | newEntry = { 186 | name: items[i], 187 | dir: dir, 188 | fileSystemType: "macOS", 189 | fileSystem: this, 190 | selected: false, 191 | datetime: '', 192 | type: 0, 193 | size: 0, 194 | stats: null 195 | }; 196 | } 197 | newEntry = { 198 | name: items[i], 199 | dir: dir, 200 | fileSystemType: "macOS", 201 | fileSystem: this, 202 | selected: false, 203 | datetime: stats.mtime.toLocaleString(), 204 | type: 0, 205 | size: 0, 206 | stats: stats 207 | }; 208 | if(stats.isDirectory()) { 209 | // 210 | // It's a directory. 211 | // 212 | newEntry.type = 1; 213 | newEntry.size = 0; 214 | } else if(stats.isSymbolicLink()) { 215 | // 216 | // It's a link. 217 | // 218 | newEntry.type = 2; 219 | newEntry.size = stats['size']; 220 | } else { 221 | // 222 | // It's a file. 223 | // 224 | newEntry.type = 0; 225 | newEntry.size = stats['size']; 226 | } 227 | entries.push(newEntry); 228 | } 229 | } 230 | 231 | // 232 | // filter out the entries. 233 | // 234 | entries = entries.filter(this.filterFunction); 235 | 236 | // 237 | // Sort the entries. 238 | // 239 | if(this.dirFirst) { 240 | var dirEntries = entries.filter(item => item.type === 1); 241 | var fileEntries = entries.filter(item => item.type === 0); 242 | dirEntries.sort(this.sortFunction); 243 | fileEntries.sort(this.sortFunction); 244 | entries = [...dirEntries, ...fileEntries]; 245 | } else { 246 | entries.sort(this.sortFunction); 247 | } 248 | } 249 | 250 | // 251 | // Return the result. 252 | // 253 | return(entries); 254 | }, 255 | defaultFilter: function(item) { 256 | return ((item.name[0] !== '.') && 257 | (!item.name.includes('Icon'))); 258 | }, 259 | allFilter: function(item) { 260 | // 261 | // Still, don't show the Icon and DS_Store files. 262 | // 263 | return((!item.name.includes('Icon')) && 264 | (!item.name.includes('.DS_Store'))); 265 | }, 266 | alphaSort: function(item1, item2) { 267 | const a = item1.name.toLowerCase(); 268 | const b = item2.name.toLowerCase(); 269 | return a === b ? 0 : a > b ? 1 : -1; 270 | }, 271 | openFile: function(dir, file) { 272 | // 273 | // For macOS, open with the open command line command. 274 | // 275 | childProcess.execSync("/usr/bin/open '" + this.appendPath(dir, file) + "'"); 276 | }, 277 | openFileWithProgram: function(prog,file) { 278 | // 279 | // For macOS, open with the open command line command. 280 | // 281 | childProcess.exec("/usr/bin/open -a '" + prog + "' '" + file + "'", (err, stdin, stdout) => {}); 282 | }, 283 | openInTerminal: function(prog, file) { 284 | childProcess.exec("/usr/bin/osascript " + this.terminalScript + " '" + prog + " \"" + file + "\"'", (err, stdin, stdout) => {}); 285 | }, 286 | getConfig: function() { 287 | if(this.config === null) { 288 | // 289 | // Create the minimum config and then add to the path as needed. The path from process 290 | // will not contain everything the user would have in his shell. 291 | // 292 | this.config = { 293 | env: null, 294 | shell: '', 295 | useTrash: false 296 | }; 297 | 298 | // 299 | // Copy the environment from the process. 300 | // 301 | this.config.env = { 302 | ...process.env 303 | }; 304 | 305 | // 306 | // Add directories that the user's system should have. 307 | // 308 | if(this.dirExists(this.getHomeDir() + '/bin')) { 309 | this.config.env.PATH = this.getHomeDir() + '/bin:' + this.config.env.PATH; 310 | } 311 | if(this.dirExists('/opt/homebrew/bin')) { 312 | this.config.env.PATH = '/opt/homebrew/bin:' + this.config.env.PATH; 313 | } 314 | if(this.dirExists('/usr/local/bin')) { 315 | this.config.env.PATH = '/usr/local/bin:' + this.config.env.PATH; 316 | } 317 | if(this.dirExists(this.getHomeDir() + '/.cargo/bin')) { 318 | this.config.env.PATH += ':' + this.getHomeDir() + '/.cargo/bin'; 319 | } 320 | if(this.dirExists(this.getHomeDir() + '/go/bin')) { 321 | this.config.env.PATH += ':' + this.getHomeDir() + '/go/bin'; 322 | } 323 | 324 | // 325 | // Set the defaults for everything else. 326 | // 327 | this.config.shell = this.config.env.SHELL; 328 | this.config.useTrash = true; 329 | } 330 | return(this.config); 331 | }, 332 | runCommandLine: function(line, callback, rEnv, rOpt) { 333 | // 334 | // Get the environment to use. 335 | // 336 | var cnfg = this.getConfig(); 337 | var nEnv = {...cnfg.env}; 338 | if(typeof rEnv !== 'undefined') { 339 | nEnv = {...nEnv, ...rEnv}; 340 | } 341 | 342 | var nOpt = { 343 | env: nEnv, 344 | shell: cnfg.shell 345 | }; 346 | if(typeof rOpt !== 'undefined') { 347 | nOpt = {...nOpt, ...rOpt}; 348 | } 349 | 350 | // 351 | // Run with default callback if a callback wasn't given. 352 | // 353 | if(typeof callback === 'undefined') { 354 | childProcess.exec(line, nOpt, (err, stdin, stdout) => {}); 355 | } else { 356 | childProcess.exec(line, nOpt, callback); 357 | } 358 | }, 359 | appendPath: function(dir, name) { 360 | // 361 | // dir can be an entry or a path string. name is always a string. 362 | // 363 | if(typeof dir === 'object') dir = this.appendPath(dir.dir, dir.name); 364 | if(dir == path.sep) { 365 | return path.normalize(path.sep + name); 366 | } else { 367 | if(dir[dir.length-1] === path.sep) { 368 | return path.normalize(dir + name); 369 | } else { 370 | return path.normalize(dir + path.sep + name); 371 | } 372 | } 373 | }, 374 | getStats: function(file) { 375 | return fs.statSync(file); 376 | }, 377 | readFile: function(file) { 378 | if(typeof file === 'objct') file = this.appendPath(file.dir, file.name); 379 | return fs.readFileSync(file); 380 | }, 381 | writeFile: function(file,data) { 382 | if(typeof file === 'object') file = this.appendPath(file.dir, file.name); 383 | fs.writeFileSync(file,data); 384 | }, 385 | renameEntry: function(oldE, newE) { 386 | var fromName = this.preserveQuotes(this.appendPath(oldE.dir, oldE.name)); 387 | var toName = this.preserveQuotes(this.appendPath(newE.dir, newE.name)); 388 | childProcess.execSync('mv "' + fromName + '" "' + toName + '"'); 389 | }, 390 | createFile: function(file) { 391 | var fnm = this.preserveQuotes(this.appendPath(file.dir, file.name)); 392 | childProcess.execSync('touch "' + fnm + '"'); 393 | }, 394 | createDir: function(dir) { 395 | var dnm = dir; 396 | if(typeof dir === 'object') dnm = this.preserveQuotes(this.appendPath(dir.dir, dir.name)); 397 | childProcess.execSync('mkdir "' + dnm + '"'); 398 | }, 399 | loadJavaScript: function(file) { 400 | var result = ''; 401 | 402 | // 403 | // Read in the JavaScript file and run it. It should return an extension object. 404 | // 405 | var jfile = this.readFile(file).toString(); 406 | try { 407 | var scriptFunction = new Function('',jfile); 408 | result = scriptFunction(); 409 | }catch(e) { 410 | console.log(e); 411 | this.lastError = e.toString(); 412 | result = null; 413 | } 414 | return(result); 415 | }, 416 | searchDir: function(pat, dir, numEntries, returnFunction) { 417 | try { 418 | if(dir === '') dir = this.pathSep(); 419 | if(pat !== '') { 420 | this.runCommandLine('fd -i --max-results ' + numEntries + ' -t d "' + pat + '" "' + dir + '"', (err, data) => { 421 | if(err) { 422 | console.log(err); 423 | this.lastError = err.toString(); 424 | } else { 425 | returnFunction(data.toString().split('\n')); 426 | } 427 | }); 428 | } 429 | } catch(e) { 430 | console.log(e); 431 | this.lastError = e.toString(); 432 | } 433 | }, 434 | splitFilePath: function(filePath) { 435 | return({ 436 | dir: path.dirname(filePath), 437 | name: path.basename(filePath) 438 | }); 439 | } 440 | } 441 | 442 | export default macOS; 443 | -------------------------------------------------------------------------------- /src/modules/util.js: -------------------------------------------------------------------------------- 1 | // 2 | // File: util.js 3 | // 4 | // Description: This file contains utility functions used in the project. 5 | // 6 | 7 | var util = { 8 | readableSize: function(sz) { 9 | var size = sz; 10 | if(size < 1024) { 11 | size = size + ' Bytes'; 12 | } else { 13 | size = size / 1024; 14 | if(size < 1024) { 15 | size = size.toFixed(2) + " KBytes"; 16 | } else { 17 | size = size / 1024; 18 | if(size < 1024) { 19 | size = size.toFixed(2) + " MBytes"; 20 | } else { 21 | size = size / 1024; 22 | size = size.toFixed(2) + " GBytes"; 23 | } 24 | } 25 | } 26 | return(size); 27 | }, 28 | // 29 | // Taken from: 30 | // https://stackoverflow.com/questions/5560248/programmatically-lighten-or-darken-a-hex-color-or-rgb-and-blend-colors 31 | // 32 | pSBC: function(p,c0,c1,l) { 33 | let r,g,b,P,f,t,h,i=parseInt,m=Math.round,a=typeof(c1)=="string"; 34 | if(typeof(p)!="number"||p<-1||p>1||typeof(c0)!="string"||(c0[0]!='r'&&c0[0]!='#')||(c1&&!a))return null; 35 | if(!this.pSBCr)this.pSBCr=(d)=>{ 36 | let n=d.length,x={}; 37 | if(n>9){ 38 | [r,g,b,a]=d=d.split(","),n=d.length; 39 | if(n<3||n>4)return null; 40 | x.r=i(r[3]=="a"?r.slice(5):r.slice(4)),x.g=i(g),x.b=i(b),x.a=a?parseFloat(a):-1 41 | }else{ 42 | if(n==8||n==6||n<4)return null; 43 | if(n<6)d="#"+d[1]+d[1]+d[2]+d[2]+d[3]+d[3]+(n>4?d[4]+d[4]:""); 44 | d=i(d.slice(1),16); 45 | if(n==9||n==5)x.r=d>>24&255,x.g=d>>16&255,x.b=d>>8&255,x.a=m((d&255)/0.255)/1000; 46 | else x.r=d>>16,x.g=d>>8&255,x.b=d&255,x.a=-1 47 | }return x}; 48 | h=c0.length>9,h=a?c1.length>9?true:c1=="c"?!h:false:h,f=this.pSBCr(c0),P=p<0,t=c1&&c1!="c"?this.pSBCr(c1):P?{r:0,g:0,b:0,a:-1}:{r:255,g:255,b:255,a:-1},p=P?p*-1:p,P=1-p; 49 | if(!f||!t)return null; 50 | if(l)r=m(P*f.r+p*t.r),g=m(P*f.g+p*t.g),b=m(P*f.b+p*t.b); 51 | else r=m((P*f.r**2+p*t.r**2)**0.5),g=m((P*f.g**2+p*t.g**2)**0.5),b=m((P*f.b**2+p*t.b**2)**0.5); 52 | a=f.a,t=t.a,f=a>=0||t>=0,a=f?a<0?t:t<0?a:a*P+t*p:0; 53 | if(h)return"rgb"+(f?"a(":"(")+r+","+g+","+b+(f?","+m(a*1000)/1000:"")+")"; 54 | else return"#"+(4294967296+r*16777216+g*65536+b*256+(f?m(a*255):0)).toString(16).slice(1,f?undefined:-2) 55 | } 56 | } 57 | 58 | export default util; 59 | -------------------------------------------------------------------------------- /src/modules/windows.js: -------------------------------------------------------------------------------- 1 | // 2 | // File: windows.js 3 | // 4 | // Description: This file contains all low level 5 | // functions that would be OS dependent. 6 | // 7 | 8 | const path = require('path'); 9 | const os = require('os'); 10 | const fs = require('fs'); 11 | const childProcess = require('child_process'); 12 | 13 | var windows = { 14 | dirFirst: true, 15 | sortFunction: null, 16 | filterFunction: null, 17 | lastError: '', 18 | lastOutput: '', 19 | getExtension: function(file) { 20 | return path.extname(file); 21 | }, 22 | getConfigDir: function() { 23 | return this.appendPath(this.getHomeDir(), '.config/modalfilemanager'); 24 | }, 25 | terminalScript: "bin/openTerminal.scpt", 26 | init: function() { 27 | this.sortFunction = this.alphaSort; 28 | this.filterFunction = this.defaultFilter; 29 | }, 30 | getDirFirst: function() { 31 | return dirFirst; 32 | }, 33 | setDirFirst: function(flag) { 34 | if(typeof flag === 'boolean') { 35 | this.dirFirst = flag; 36 | } 37 | }, 38 | setDirSort: function(sortFunction) { 39 | this.sortFunction = sortFunction; 40 | }, 41 | setFilter: function(flt) { 42 | this.filterFunction = flt; 43 | }, 44 | getTerminalScript: function() { 45 | return this.appendPath(this.getHomeDir(), this.terminalScript); 46 | }, 47 | setTerminalScript: function(scrpt) { 48 | this.terminalScript = scrpt; 49 | }, 50 | getHomeDir: function() { 51 | return(os.homedir()); 52 | }, 53 | pathSep() { 54 | return(path.sep); 55 | }, 56 | readDir: function(dir) { 57 | if(typeof dir === 'object') dir = this.appendPath(dir.dir, dir.name); 58 | return fs.readdirSync(this.preserveQuotes(dir)); 59 | }, 60 | dirExists: function(dir) { 61 | if(typeof dir.name !== 'undefined') dir = this.appendPath(dir.dir, dir.name); 62 | return this.fileExists(this.preserveQuotes(dir)); 63 | }, 64 | fileExists: function(file) { 65 | var result = true; 66 | if(typeof file === 'object') file = this.preserveQuotes(this.appendPath(file.dir, file.name)); 67 | try { 68 | fs.accessSync(file); 69 | result = true; 70 | } catch(e) { 71 | result = false; 72 | } 73 | return result; 74 | }, 75 | makeDir: function(dir) { 76 | if(typeof dir.name !== 'undefined') dir = this.appendpath(dir.dir, dir.name); 77 | fs.mkdirSync(this.preserveQuotes(dir)); 78 | }, 79 | preserveQuotes: function(str) { 80 | return(str.replaceAll(/(\"|\'|\`)/gi,'\\$1')); 81 | }, 82 | moveEntries: function(from, to, callback) { 83 | var fromName = this.preserveQuotes(from.fileSystem.appendPath(from.dir, from.name)); 84 | var toName = this.preserveQuotes(to.dir); 85 | var that = this; 86 | if(typeof callback === 'undefined') { 87 | childProcess.exec("mv '" + fromName + "' '" + toName + "'",(err,stdout) => { 88 | if(err) that.lastError = err; 89 | that.lastOutput = stdout; 90 | }); 91 | } else { 92 | childProcess.exec("mv '" + fromName + "' '" + toName + "'", callback); 93 | } 94 | }, 95 | copyEntries: function(from, to, flag, callback) { 96 | if(typeof flag === 'undefined') flag = false; 97 | var fromName = this.preserveQuotes(from.fileSystem.appendPath(from.dir, from.name)); 98 | var toName = this.preserveQuotes(to.dir); 99 | if(flag) { 100 | toName = this.preserveQuotes(to.fileSystem.appendPath(to.dir,to.name)); 101 | } 102 | var that = this; 103 | if(typeof callback === 'undefined') { 104 | childProcess.exec("cp -R '" + fromName + "' '" + toName + "'",(err,stdout) => { 105 | if(err) that.lastError = err; 106 | that.lastOutput = stdout; 107 | }); 108 | } else { 109 | childProcess.exec("cp -R '" + fromName + "' '" + toName + "'", callback); 110 | } 111 | }, 112 | deleteEntries: function(entry, callback) { 113 | var item = this.preserveQuotes(entry.fileSystem.appendPath(entry.dir, entry.name)); 114 | var that = this; 115 | if(typeof callback === 'undefined') { 116 | childProcess.exec("rm -R '" + item + "'",(err,stdout) => { 117 | if(err) that.lastError = err; 118 | that.lastOutput = stdout; 119 | }); 120 | } else { 121 | childProcess.exec("rm -R '" + item + "'", callback); 122 | } 123 | }, 124 | getDirList: function(dir) { 125 | // 126 | // A directory list is provided giving an array of entry object. Each 127 | // entry object has: 128 | // name The name of the file 129 | // type The type of the entry: a file - 0, a directory - 1, a link - 2 130 | // fileSystem The current file system object 131 | // dir The directory of the file 132 | // datetime The creation datetime of the file 133 | // size The integer size of the file in 1kb (Directories and links have zero size) 134 | // selected Boolean true is selected, false is not selected 135 | // 136 | var entries = []; 137 | if((typeof dir === 'object') && (typeof dir.name !== 'undefined')) dir = this.appendPath(dir.dir, dir.name); 138 | if(this.dirExists(dir)) { 139 | var items = fs.readdirSync(dir); 140 | for (var i=0; i 1)) { 145 | file = dir + path.sep + items[i]; 146 | } else { 147 | file = path.sep + items[i]; 148 | } 149 | var newEntry; 150 | try { 151 | var stats = fs.statSync(file); 152 | } catch(e) { 153 | newEntry = { 154 | name: items[i], 155 | dir: dir, 156 | fileSystemType: "macOS", 157 | fileSystem: this, 158 | selected: false, 159 | datetime: '', 160 | type: 0, 161 | size: 0, 162 | stats: null 163 | }; 164 | } 165 | newEntry = { 166 | name: items[i], 167 | dir: dir, 168 | fileSystemType: "macOS", 169 | fileSystem: this, 170 | selected: false, 171 | datetime: stats.mtime.toLocaleString(), 172 | type: 0, 173 | size: 0, 174 | stats: stats 175 | }; 176 | if(stats.isDirectory()) { 177 | // 178 | // It's a directory. 179 | // 180 | newEntry.type = 1; 181 | newEntry.size = 0; 182 | } else if(stats.isSymbolicLink()) { 183 | // 184 | // It's a link. 185 | // 186 | newEntry.type = 2; 187 | newEntry.size = stats['size']; 188 | } else { 189 | // 190 | // It's a file. 191 | // 192 | newEntry.type = 0; 193 | newEntry.size = stats['size']; 194 | } 195 | entries.push(newEntry); 196 | } 197 | } 198 | 199 | // 200 | // filter out the entries. 201 | // 202 | entries = entries.filter(this.filterFunction); 203 | 204 | // 205 | // Sort the entries. 206 | // 207 | if(this.dirFirst) { 208 | var dirEntries = entries.filter(item => item.type === 1); 209 | var fileEntries = entries.filter(item => item.type === 0); 210 | dirEntries.sort(this.sortFunction); 211 | fileEntries.sort(this.sortFunction); 212 | entries = [...dirEntries, ...fileEntries]; 213 | } else { 214 | entries.sort(this.sortFunction); 215 | } 216 | } 217 | 218 | // 219 | // Return the result. 220 | // 221 | return(entries); 222 | }, 223 | defaultFilter: function(item) { 224 | return ((item.name[0] !== '.') && 225 | (!item.name.includes('Icon'))); 226 | }, 227 | allFilter: function(item) { 228 | // 229 | // Still, don't show the Icon and DS_Store files. 230 | // 231 | return((!item.name.includes('Icon')) && 232 | (!item.name.includes('.DS_Store'))); 233 | }, 234 | alphaSort: function(item1, item2) { 235 | const a = item1.name.toLowerCase(); 236 | const b = item2.name.toLowerCase(); 237 | return a === b ? 0 : a > b ? 1 : -1; 238 | }, 239 | openFile: function(dir, file) { 240 | // 241 | // For macOS, open with the open command line command. 242 | // 243 | childProcess.execSync("/usr/bin/open '" + this.appendPath(dir, file) + "'"); 244 | }, 245 | openFileWithProgram: function(prog,file) { 246 | // 247 | // For macOS, open with the open command line command. 248 | // 249 | childProcess.exec("/usr/bin/open -a '" + prog + "' '" + file + "'", (err, stdin, stdout) => {}); 250 | }, 251 | openInTerminal: function(prog, file) { 252 | childProcess.exec("/usr/bin/osascript " + this.terminalScript + " '" + prog + " \"" + file + "\"'", (err, stdin, stdout) => {}); 253 | }, 254 | runCommandLine: function(line, callback) { 255 | if(typeof callback === 'undefined') { 256 | childProcess.exec(line, (err, stdin, stdout) => {}); 257 | } else { 258 | childProcess.exec(line, callback); 259 | } 260 | }, 261 | appendPath: function(dir, name) { 262 | // 263 | // dir can be an entry or a path string. name is always a string. 264 | // 265 | if(typeof dir === 'object') dir = this.appendPath(dir.dir, dir.name); 266 | if(dir == path.sep) { 267 | return path.sep + name; 268 | } else { 269 | if(dir[dir.length-1] === path.sep) { 270 | return dir + name; 271 | } else { 272 | return dir + path.sep + name; 273 | } 274 | } 275 | }, 276 | getStats: function(file) { 277 | return fs.statSync(file); 278 | }, 279 | readFile: function(file) { 280 | if(typeof file === 'objct') file = this.appendPath(file.dir, file.name); 281 | return fs.readFileSync(file); 282 | }, 283 | writeFile: function(file,data) { 284 | if(typeof file === 'object') file = this.appendPath(file.dir, file.name); 285 | fs.writeFileSync(file,data); 286 | }, 287 | renameEntry: function(oldE, newE) { 288 | console.log(oldE); 289 | console.log(newE); 290 | var fromName = this.preserveQuotes(oldE.fileSystem.appendPath(oldE.dir, oldE.name)); 291 | var toName = this.preserveQuotes(newE.fileSystem.appendPath(newE.dir, newE.name)); 292 | childProcess.execSync('mv "' + fromName + '" "' + toName + '"'); 293 | }, 294 | createFile: function(file) { 295 | var fnm = this.preserveQuotes(file.fileSystem.appendPath(file.dir, file.name)); 296 | childProcess.execSync('touch "' + fnm + '"'); 297 | }, 298 | createDir: function(dir) { 299 | var dnm = this.preserveQuotes(dir.fileSystem.appendPath(dir.dir, dir.name)); 300 | childProcess.execSync('mkdir "' + dnm + '"'); 301 | }, 302 | loadJavaScript: function(file) { 303 | var result = ''; 304 | 305 | // 306 | // Read in the JavaScript file and run it. It should return an extension object. 307 | // 308 | var jfile = this.readFile(file).toString(); 309 | try { 310 | var scriptFunction = new Function('',jfile); 311 | result = scriptFunction(); 312 | }catch(e) { 313 | console.log(e); 314 | this.lastError = e.toString(); 315 | result = null; 316 | } 317 | return(result); 318 | }, 319 | searchDir: function(pat, dir, numEntries, returnFunction) { 320 | try { 321 | if(dir === '') dir = this.pathSep(); 322 | if(pat !== '') { 323 | childProcess.exec('/usr/local/bin/fd -i --max-results ' + numEntries + ' -t d "' + pat + '" "' + dir + '"', (err, data) => { 324 | if(err) { 325 | console.log(err); 326 | this.lastError = err.toString(); 327 | } else { 328 | returnFunction(data.toString().split('\n')); 329 | } 330 | }); 331 | } 332 | } catch(e) { 333 | console.log(e); 334 | this.lastError = e.toString(); 335 | } 336 | } 337 | } 338 | 339 | export default windows; 340 | -------------------------------------------------------------------------------- /src/stores/altKey.js: -------------------------------------------------------------------------------- 1 | import { writable } from 'svelte/store'; 2 | 3 | export const altKey = writable(false); 4 | -------------------------------------------------------------------------------- /src/stores/config.js: -------------------------------------------------------------------------------- 1 | import { writable, get } from 'svelte/store'; 2 | 3 | var confg = { 4 | configDir: '', 5 | localFS: {}, 6 | configuration: {}, 7 | commands: {}, 8 | extensions: {}, 9 | userEditor: '' 10 | } 11 | 12 | export const config = writable(confg); 13 | 14 | -------------------------------------------------------------------------------- /src/stores/ctrlKey.js: -------------------------------------------------------------------------------- 1 | import { writable } from 'svelte/store'; 2 | 3 | export const ctrlKey = writable(false); 4 | -------------------------------------------------------------------------------- /src/stores/currentCursor.js: -------------------------------------------------------------------------------- 1 | import { writable } from 'svelte/store'; 2 | 3 | export const currentCursor = writable({ 4 | pane: 'right', 5 | entry: {} 6 | }); 7 | 8 | -------------------------------------------------------------------------------- /src/stores/currentLeftFile.js: -------------------------------------------------------------------------------- 1 | import { writable } from 'svelte/store'; 2 | 3 | export const currentLeftFile = writable({ 4 | entry: {} 5 | }); 6 | 7 | -------------------------------------------------------------------------------- /src/stores/currentRightFile.js: -------------------------------------------------------------------------------- 1 | import { writable } from 'svelte/store'; 2 | 3 | export const currentRightFile = writable({ 4 | entry: {} 5 | }); 6 | 7 | -------------------------------------------------------------------------------- /src/stores/dirHistory.js: -------------------------------------------------------------------------------- 1 | import { writable, get } from 'svelte/store'; 2 | import { config } from '../stores/config.js'; 3 | 4 | var history = { 5 | histStore: [], 6 | addHistory: function(dir) { 7 | var el = this.histStore.find(item => item.toLowerCase().includes(dir.toLowerCase())); 8 | if((typeof el === 'undefined')||(el === null)) { 9 | this.histStore.push(dir); 10 | this.saveHistory(); 11 | } 12 | }, 13 | searchHistory: function(pat) { 14 | return this.histStore.filter(item => item.toLowerCase().includes(pat.toLowerCase())); 15 | }, 16 | saveHistory: function() { 17 | const cfg = get(config); 18 | if((cfg.configDir !== '')&&(cfg.localFS !== null)) { 19 | // 20 | // Save the history. 21 | // 22 | cfg.localFS.writeFile(cfg.localFS.appendPath(cfg.configDir, 'history.json'), JSON.stringify(this.histStore)); 23 | } 24 | }, 25 | loadHistory: function() { 26 | const cfg = get(config); 27 | const hf = cfg.localFS.appendPath(cfg.configDir, 'history.json'); 28 | if((cfg.configDir !== '')&&(cfg.localFS !== null)) { 29 | // 30 | // load the history. 31 | // 32 | if(cfg.localFS.fileExists(hf)) { 33 | try { 34 | this.histStore = JSON.parse(cfg.localFS.readFile(cfg.localFS.appendPath(cfg.configDir, 'history.json'))); 35 | } catch(e) { 36 | // 37 | // Something was wrong with the history. Just forget it. 38 | // 39 | this.histStore = []; 40 | } 41 | } 42 | } 43 | } 44 | } 45 | 46 | export const dirHistory = writable(history); 47 | 48 | -------------------------------------------------------------------------------- /src/stores/directoryListeners.js: -------------------------------------------------------------------------------- 1 | import { writable } from 'svelte/store'; 2 | 3 | export const directoryListeners = writable([]); 4 | -------------------------------------------------------------------------------- /src/stores/extraPanel.js: -------------------------------------------------------------------------------- 1 | import { writable } from 'svelte/store'; 2 | 3 | export const extraPanel = writable([]); 4 | -------------------------------------------------------------------------------- /src/stores/inputState.js: -------------------------------------------------------------------------------- 1 | import { writable } from 'svelte/store'; 2 | 3 | export const inputState = writable('normal'); 4 | 5 | -------------------------------------------------------------------------------- /src/stores/keyProcess.js: -------------------------------------------------------------------------------- 1 | // 2 | // KeyProcess 3 | // 4 | // Description: 5 | // When true, keypresses will be processed for functionality. Otherwise, 6 | // they will be treated as standard typing. 7 | // 8 | 9 | import { writable } from 'svelte/store'; 10 | 11 | export const keyProcess = writable(true); 12 | 13 | -------------------------------------------------------------------------------- /src/stores/leftDir.js: -------------------------------------------------------------------------------- 1 | import { writable } from 'svelte/store'; 2 | 3 | export const leftDir = writable({ 4 | fileSystemType: 'local', 5 | fileSystem: null, 6 | path: '' 7 | }); 8 | 9 | -------------------------------------------------------------------------------- /src/stores/metaKey.js: -------------------------------------------------------------------------------- 1 | import { writable } from 'svelte/store'; 2 | 3 | export const metaKey = writable(false); 4 | -------------------------------------------------------------------------------- /src/stores/processKey.js: -------------------------------------------------------------------------------- 1 | import { writable } from 'svelte/store'; 2 | 3 | export const processKey = writable(null); 4 | -------------------------------------------------------------------------------- /src/stores/rightDir.js: -------------------------------------------------------------------------------- 1 | import { writable } from 'svelte/store'; 2 | 3 | export const rightDir = writable({ 4 | fileSystemType: 'local', 5 | fileSystem: null, 6 | path: '' 7 | }); 8 | 9 | -------------------------------------------------------------------------------- /src/stores/shiftKey.js: -------------------------------------------------------------------------------- 1 | import { writable } from 'svelte/store'; 2 | 3 | export const shiftKey = writable(false); 4 | -------------------------------------------------------------------------------- /src/stores/skipKey.js: -------------------------------------------------------------------------------- 1 | import { writable } from 'svelte/store'; 2 | 3 | export const skipKey = writable(false); 4 | -------------------------------------------------------------------------------- /src/stores/stateMapColors.js: -------------------------------------------------------------------------------- 1 | import { writable } from 'svelte/store'; 2 | 3 | export const stateMapColors = writable([]); 4 | 5 | -------------------------------------------------------------------------------- /src/stores/theme.js: -------------------------------------------------------------------------------- 1 | import { writable } from 'svelte/store'; 2 | 3 | export const theme = writable({ 4 | }); 5 | 6 | --------------------------------------------------------------------------------