├── .github └── FUNDING.yml ├── .gitignore ├── .notes └── build.md ├── .startproject ├── LICENSE ├── README.md ├── app.go ├── build └── appicon.png ├── frontend ├── Pictures │ ├── ModalFileManager2-small.afphoto │ ├── ModalFileManager2-small.jpg │ ├── ModalFileManager2.afphoto │ ├── ModalFileManager2.png │ ├── Pref-Extensions.png │ ├── Pref-General1.png │ ├── Pref-General2.png │ ├── Pref-Theme1.png │ ├── Pref-Theme2.png │ ├── Pref-theme3.png │ ├── SecurityPrivacy1.png │ ├── UsingMFMSome.gif │ ├── VerifyDeveloper.png │ └── VerifyDeveloper2.png ├── bun.lock ├── icons │ ├── mfm-icon.afdesign │ ├── mfm-icon.icns │ ├── mfm-icon.png │ └── mfm-icon.svg ├── index.html ├── maskfile.md ├── package.json ├── 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 │ │ ├── ModeLine.svelte │ │ ├── Pane.svelte │ │ ├── Preferences.svelte │ │ ├── QuickSearch.svelte │ │ ├── ResizeBorder.svelte │ │ ├── StatusLine.svelte │ │ ├── ThemeItem.svelte │ │ ├── ThemePrefs.svelte │ │ └── TitleBar.svelte │ ├── main.js │ ├── modules │ │ ├── OS.js │ │ ├── commands.js │ │ ├── extensions.js │ │ ├── filesystems.js │ │ └── util.js │ ├── stores │ │ ├── altKey.js │ │ ├── config.js │ │ ├── ctrlKey.js │ │ ├── currentCursor.js │ │ ├── currentLeftFile.js │ │ ├── currentRightFile.js │ │ ├── dirHistory.js │ │ ├── directoryListeners.js │ │ ├── extraPanel.js │ │ ├── inputState.js │ │ ├── key.js │ │ ├── keyProcess.js │ │ ├── leftDir.js │ │ ├── leftEntries.js │ │ ├── metaKey.js │ │ ├── processKey.js │ │ ├── rightDir.js │ │ ├── rightEntries.js │ │ ├── saved.js │ │ ├── shiftKey.js │ │ ├── stateMapColors.js │ │ └── theme.js │ └── vite-env.d.ts ├── svelte.config.js └── vite.config.js ├── go.mod ├── go.sum ├── jsconfig.json ├── main.go ├── maskfile.md ├── picts ├── appicon.png └── sponsor.svg ├── server.go └── wails.json /.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 | lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry 13 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Wails bin directory 2 | build/bin 3 | build/darwin 4 | 5 | # Don't save the public folder. It shoud be compiled each time. 6 | frontend/dist 7 | 8 | # IDEs 9 | .idea 10 | .vscode 11 | 12 | # The black hole that is... 13 | node_modules 14 | *.lock 15 | 16 | # Binaries zipped 17 | *.zip 18 | -------------------------------------------------------------------------------- /.notes/build.md: -------------------------------------------------------------------------------- 1 | To build the project, run: 2 | 3 | wails build --platform="darwin/universal" 4 | 5 | to build a universal binary for macOS. Or 6 | 7 | mask build 8 | 9 | to build a binary for the current system only. 10 | -------------------------------------------------------------------------------- /.startproject: -------------------------------------------------------------------------------- 1 | #!/bin/zsh 2 | 3 | oni2 "$1" 4 | open workspaces://start/255F71B7-81C6-43E9-B9C7-D89583F14201 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 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 | -------------------------------------------------------------------------------- /app.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "io" 7 | "io/fs" 8 | "os" 9 | "os/exec" 10 | "path/filepath" 11 | goruntime "runtime" 12 | "strings" 13 | "time" 14 | 15 | clip "github.com/atotto/clipboard" 16 | "github.com/go-git/go-git/v5" 17 | github "github.com/google/go-github/v49/github" 18 | cp "github.com/otiai10/copy" 19 | rt "github.com/wailsapp/wails/v2/pkg/runtime" 20 | ) 21 | 22 | // App application struct and other structs 23 | type App struct { 24 | ctx context.Context 25 | err string 26 | lastRightDir string 27 | lastLeftDir string 28 | Commands []string 29 | Timer *time.Timer 30 | Stopped bool 31 | LenLeftFiles int 32 | LenRightFiles int 33 | LeftHash string 34 | RightHash string 35 | } 36 | 37 | type FileParts struct { 38 | Dir string 39 | Name string 40 | Extension string 41 | } 42 | 43 | type FileInfo struct { 44 | Dir string 45 | Name string 46 | Extension string 47 | IsDir bool 48 | Size int64 49 | Modtime string 50 | Index int 51 | Mode fs.FileMode 52 | Link bool 53 | } 54 | 55 | type GitHubRepos struct { 56 | Name string `json:"name"` 57 | URL string `json:"url"` 58 | Stars int `json:"stars"` 59 | Owner string `json:"owner"` 60 | ID int64 `json:"id"` 61 | Description string `json:"description"` 62 | UpdatedAt github.Timestamp `json:"updatedat"` 63 | } 64 | 65 | // NewApp creates a new App application struct 66 | func NewApp() *App { 67 | return &App{} 68 | } 69 | 70 | // startup is called at application startup 71 | func (b *App) startup(ctx context.Context) { 72 | b.ctx = ctx 73 | 74 | // 75 | // We need to start the backend and setup the signaling. 76 | // 77 | go backend(b, ctx) 78 | } 79 | 80 | // domReady is called after the front-end dom has been loaded 81 | func (b *App) domReady(ctx context.Context) { 82 | // 83 | // Start watching the directories. 84 | // 85 | b.lastLeftDir = "" 86 | b.lastRightDir = "" 87 | b.Stopped = false 88 | go b.StartWatcher() 89 | } 90 | 91 | // shutdown is called at application termination 92 | func (b *App) shutdown(ctx context.Context) { 93 | // 94 | // Stop watching directories. 95 | // 96 | b.lastLeftDir = "" 97 | b.lastRightDir = "" 98 | b.Stopped = true 99 | b.StopWatcher() 100 | } 101 | 102 | // These functions are for watching the current directories in the file manager. 103 | func (b *App) SetRightDirWatch(path string) { 104 | b.lastRightDir = path 105 | } 106 | 107 | func (b *App) SetLeftDirWatch(path string) { 108 | b.lastLeftDir = path 109 | } 110 | 111 | func (b *App) CloseRightWatch() { 112 | b.lastRightDir = "" 113 | } 114 | 115 | func (b *App) CloseLeftWatch() { 116 | b.lastLeftDir = "" 117 | } 118 | 119 | func (b *App) StartWatcher() { 120 | // 121 | // NOTE: Directory watching is currently a polling system. This needs to be 122 | // changed so that it doesn't take up processor time. 123 | // 124 | for !b.Stopped { 125 | // 126 | // Create the timer. 127 | // 128 | b.Timer = time.NewTimer(time.Millisecond * 10000) 129 | 130 | // 131 | // Do the Job. check for changes in the current directories. 132 | // 133 | if b.lastLeftDir != "" { 134 | leftFiles := b.ReadDir(b.lastLeftDir) 135 | LenLeftFiles := len(leftFiles) 136 | if LenLeftFiles != b.LenLeftFiles { 137 | // 138 | // The number of files have changed. Reload the directory. 139 | // 140 | b.LenLeftFiles = LenLeftFiles 141 | rt.EventsEmit(b.ctx, "leftSideChange", "") 142 | } 143 | } 144 | if b.lastRightDir != "" { 145 | rightFiles := b.ReadDir(b.lastRightDir) 146 | LenRightFiles := len(rightFiles) 147 | if LenRightFiles != b.LenRightFiles { 148 | // 149 | // The number of files have changed. Reload the directory. 150 | // 151 | b.LenRightFiles = LenRightFiles 152 | rt.EventsEmit(b.ctx, "rightSideChange", "") 153 | } 154 | } 155 | 156 | // 157 | // Wait for the timer to finish. 158 | // 159 | <-b.Timer.C 160 | } 161 | } 162 | 163 | func (b *App) StopWatcher() { 164 | b.lastLeftDir = "" 165 | b.lastRightDir = "" 166 | b.Timer.Stop() 167 | } 168 | 169 | func (b *App) GetCommandLineCommands() []string { 170 | return b.Commands 171 | } 172 | 173 | func (b *App) ReadFile(path string) string { 174 | b.err = "" 175 | contents, err := os.ReadFile(path) 176 | if err != nil { 177 | b.err = err.Error() 178 | } 179 | return string(contents[:]) 180 | } 181 | 182 | func (b *App) GetHomeDir() string { 183 | b.err = "" 184 | hdir, err := os.UserHomeDir() 185 | if err != nil { 186 | b.err = err.Error() 187 | } 188 | return hdir 189 | } 190 | 191 | func (b *App) WriteFile(path string, data string) { 192 | err := os.WriteFile(path, []byte(data), 0666) 193 | if err != nil { 194 | b.err = err.Error() 195 | } 196 | } 197 | 198 | func (b *App) FileExists(path string) bool { 199 | b.err = "" 200 | _, err := os.Stat(path) 201 | return !errors.Is(err, os.ErrNotExist) 202 | } 203 | 204 | func (b *App) DirExists(path string) bool { 205 | b.err = "" 206 | dstat, err := os.Stat(path) 207 | if err != nil { 208 | b.err = err.Error() 209 | return false 210 | } 211 | return dstat.IsDir() 212 | } 213 | 214 | func (b *App) SplitFile(path string) FileParts { 215 | b.err = "" 216 | var parts FileParts 217 | parts.Dir, parts.Name = filepath.Split(path) 218 | parts.Extension = filepath.Ext(path) 219 | return parts 220 | } 221 | 222 | func (b *App) ReadDir(path string) []FileInfo { 223 | b.err = "" 224 | var result []FileInfo 225 | result = make([]FileInfo, 0) 226 | files, err := os.ReadDir(path) 227 | if err != nil { 228 | b.err = err.Error() 229 | } else { 230 | for index, file := range files { 231 | var fileInfo FileInfo 232 | info, err := file.Info() 233 | if err != nil { 234 | b.err = err.Error() 235 | } 236 | fileInfo.Name = file.Name() 237 | fileInfo.Size = info.Size() 238 | fileInfo.IsDir = file.IsDir() 239 | fileInfo.Modtime = info.ModTime().Format(time.ANSIC) 240 | fileInfo.Dir = path 241 | fileInfo.Extension = filepath.Ext(file.Name()) 242 | fileInfo.Index = index 243 | fileInfo.Mode = info.Mode().Perm() 244 | fileInfo.Link = false 245 | 246 | // 247 | // Determine if it is a symlink and if so if it's a directory. Currently, 248 | // if before it wasn't a directory but is now, then it is treated as a 249 | // symlink directory. Nothing is in place to detect a file symlink. 250 | // 251 | ninfo, err := os.Stat(filepath.Join(path, fileInfo.Name)) 252 | if err != nil { 253 | b.err = err.Error() 254 | } else { 255 | dir := ninfo.Mode().IsDir() 256 | if dir && !fileInfo.IsDir { 257 | fileInfo.IsDir = true 258 | fileInfo.Link = true 259 | } 260 | if ninfo.Mode()&os.ModeSymlink == 'l' { 261 | fileInfo.Link = true 262 | } 263 | } 264 | // 265 | // Add it to the rest. 266 | // 267 | result = append(result, fileInfo) 268 | } 269 | } 270 | return result 271 | } 272 | 273 | func (b *App) MakeDir(path string) { 274 | b.err = "" 275 | err := os.MkdirAll(path, 0755) 276 | if err != nil { 277 | b.err = err.Error() 278 | } 279 | } 280 | 281 | func (b *App) MakeFile(path string) { 282 | b.err = "" 283 | b.WriteFile(path, "") 284 | } 285 | 286 | func (b *App) MoveEntries(from string, to string) { 287 | b.err = "" 288 | err := os.Rename(from, to) 289 | if err != nil { 290 | b.err = err.Error() 291 | } 292 | } 293 | 294 | func (b *App) RenameEntry(from string, to string) { 295 | b.err = "" 296 | err := os.Rename(from, to) 297 | if err != nil { 298 | b.err = err.Error() 299 | } 300 | } 301 | 302 | func (b *App) GetError() string { 303 | return b.err 304 | } 305 | 306 | func (b *App) CopyEntries(from string, to string) { 307 | b.err = "" 308 | info, err := os.Stat(from) 309 | if os.IsNotExist(err) { 310 | b.err = err.Error() 311 | return 312 | } 313 | if info.IsDir() { 314 | // 315 | // It's a directory! Do a deap copy. 316 | // 317 | err := cp.Copy(from, to) 318 | if err != nil { 319 | b.err = err.Error() 320 | return 321 | } 322 | } else { 323 | // 324 | // It's a file. Just copy it. 325 | // 326 | source, err := os.Open(from) 327 | if err != nil { 328 | b.err = err.Error() 329 | return 330 | } 331 | defer source.Close() 332 | 333 | destination, err := os.Create(to) 334 | if err != nil { 335 | b.err = err.Error() 336 | return 337 | } 338 | defer destination.Close() 339 | _, err = io.Copy(destination, source) 340 | if err != nil { 341 | b.err = err.Error() 342 | } 343 | } 344 | } 345 | 346 | func (b *App) DeleteEntries(path string) { 347 | b.err = "" 348 | err := os.RemoveAll(path) 349 | if err != nil { 350 | b.err = err.Error() 351 | } 352 | } 353 | 354 | func (b *App) RunCommandLine(cmd string, args []string, env []string, dir string) string { 355 | b.err = "" 356 | cmdline := exec.Command(cmd) 357 | cmdline.Args = args 358 | cmdline.Env = env 359 | cmdline.Dir = dir 360 | result, err := cmdline.CombinedOutput() 361 | if err != nil { 362 | b.err = err.Error() 363 | } 364 | 365 | return string(result[:]) 366 | } 367 | 368 | func (b *App) GetClip() string { 369 | result, err := clip.ReadAll() 370 | if err != nil { 371 | b.err = err.Error() 372 | } 373 | return result 374 | } 375 | 376 | func (b *App) SetClip(msg string) { 377 | err := clip.WriteAll(msg) 378 | if err != nil { 379 | b.err = err.Error() 380 | } 381 | } 382 | 383 | func (b *App) GetEnvironment() []string { 384 | return os.Environ() 385 | } 386 | 387 | func (b *App) AppendPath(dir string, name string) string { 388 | return filepath.Join(dir, name) 389 | } 390 | 391 | func (b *App) Quit() { 392 | rt.Quit(b.ctx) 393 | } 394 | 395 | func (b *App) GetOSName() string { 396 | os := goruntime.GOOS 397 | result := "" 398 | switch os { 399 | case "windows": 400 | result = "windows" 401 | case "darwin": 402 | result = "macos" 403 | case "linux": 404 | result = "linux" 405 | default: 406 | result = os 407 | } 408 | return result 409 | } 410 | 411 | func (b *App) GetGitHubThemes() []GitHubRepos { 412 | var result []GitHubRepos 413 | client := github.NewClient(nil) 414 | topics, _, err := client.Search.Repositories(context.Background(), "in:topic modalfilemanager in:topic theme", nil) 415 | if err == nil { 416 | total := *topics.Total 417 | result = make([]GitHubRepos, total) 418 | for i := 0; i < total; i++ { 419 | result[i].ID = *topics.Repositories[i].ID 420 | result[i].Name = *topics.Repositories[i].Name 421 | result[i].Owner = *topics.Repositories[i].Owner.Login 422 | result[i].URL = *topics.Repositories[i].CloneURL 423 | result[i].Stars = *topics.Repositories[i].StargazersCount 424 | result[i].Description = *topics.Repositories[i].Description 425 | result[i].UpdatedAt = *topics.Repositories[i].UpdatedAt 426 | } 427 | } 428 | return result 429 | } 430 | 431 | func (b *App) GetGitHubScripts() []GitHubRepos { 432 | var result []GitHubRepos 433 | client := github.NewClient(nil) 434 | topics, _, err := client.Search.Repositories(context.Background(), "in:topic modalfilemanager in:topic V2 in:topic extension", nil) 435 | if err == nil { 436 | total := *topics.Total 437 | result = make([]GitHubRepos, total) 438 | for i := 0; i < total; i++ { 439 | result[i].ID = *topics.Repositories[i].ID 440 | result[i].Name = *topics.Repositories[i].Name 441 | result[i].Owner = *topics.Repositories[i].Owner.Login 442 | result[i].URL = *topics.Repositories[i].CloneURL 443 | result[i].Stars = *topics.Repositories[i].StargazersCount 444 | result[i].Description = *topics.Repositories[i].Description 445 | result[i].UpdatedAt = *topics.Repositories[i].UpdatedAt 446 | } 447 | } 448 | return result 449 | } 450 | 451 | func (b *App) SearchMatchingDirectories(path string, pat string, max int) []string { 452 | result := make([]string, max) 453 | count := 0 454 | err := filepath.Walk(path, func(path string, info fs.FileInfo, err error) error { 455 | if err == nil && info.IsDir() { 456 | // 457 | // check for a directory that matches the pattern. 458 | // 459 | if strings.Contains(path, pat) { 460 | result[count] = path 461 | count++ 462 | if count >= max { 463 | return filepath.SkipAll 464 | } 465 | } 466 | } 467 | return nil 468 | }) 469 | if err != nil { 470 | b.err = err.Error() 471 | } 472 | 473 | // 474 | // Return the results. 475 | // 476 | return result 477 | } 478 | 479 | func (b *App) CloneGitHub(url string, dir string) { 480 | _, err := git.PlainClone(dir, false, &git.CloneOptions{ 481 | URL: url, 482 | }) 483 | if err != nil { 484 | b.err = err.Error() 485 | } 486 | } 487 | -------------------------------------------------------------------------------- /build/appicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raguay/ModalFileManager/9e62848bd579b0d9b1a41227fc25eea0219ce520/build/appicon.png -------------------------------------------------------------------------------- /frontend/Pictures/ModalFileManager2-small.afphoto: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raguay/ModalFileManager/9e62848bd579b0d9b1a41227fc25eea0219ce520/frontend/Pictures/ModalFileManager2-small.afphoto -------------------------------------------------------------------------------- /frontend/Pictures/ModalFileManager2-small.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raguay/ModalFileManager/9e62848bd579b0d9b1a41227fc25eea0219ce520/frontend/Pictures/ModalFileManager2-small.jpg -------------------------------------------------------------------------------- /frontend/Pictures/ModalFileManager2.afphoto: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raguay/ModalFileManager/9e62848bd579b0d9b1a41227fc25eea0219ce520/frontend/Pictures/ModalFileManager2.afphoto -------------------------------------------------------------------------------- /frontend/Pictures/ModalFileManager2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raguay/ModalFileManager/9e62848bd579b0d9b1a41227fc25eea0219ce520/frontend/Pictures/ModalFileManager2.png -------------------------------------------------------------------------------- /frontend/Pictures/Pref-Extensions.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raguay/ModalFileManager/9e62848bd579b0d9b1a41227fc25eea0219ce520/frontend/Pictures/Pref-Extensions.png -------------------------------------------------------------------------------- /frontend/Pictures/Pref-General1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raguay/ModalFileManager/9e62848bd579b0d9b1a41227fc25eea0219ce520/frontend/Pictures/Pref-General1.png -------------------------------------------------------------------------------- /frontend/Pictures/Pref-General2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raguay/ModalFileManager/9e62848bd579b0d9b1a41227fc25eea0219ce520/frontend/Pictures/Pref-General2.png -------------------------------------------------------------------------------- /frontend/Pictures/Pref-Theme1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raguay/ModalFileManager/9e62848bd579b0d9b1a41227fc25eea0219ce520/frontend/Pictures/Pref-Theme1.png -------------------------------------------------------------------------------- /frontend/Pictures/Pref-Theme2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raguay/ModalFileManager/9e62848bd579b0d9b1a41227fc25eea0219ce520/frontend/Pictures/Pref-Theme2.png -------------------------------------------------------------------------------- /frontend/Pictures/Pref-theme3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raguay/ModalFileManager/9e62848bd579b0d9b1a41227fc25eea0219ce520/frontend/Pictures/Pref-theme3.png -------------------------------------------------------------------------------- /frontend/Pictures/SecurityPrivacy1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raguay/ModalFileManager/9e62848bd579b0d9b1a41227fc25eea0219ce520/frontend/Pictures/SecurityPrivacy1.png -------------------------------------------------------------------------------- /frontend/Pictures/UsingMFMSome.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raguay/ModalFileManager/9e62848bd579b0d9b1a41227fc25eea0219ce520/frontend/Pictures/UsingMFMSome.gif -------------------------------------------------------------------------------- /frontend/Pictures/VerifyDeveloper.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raguay/ModalFileManager/9e62848bd579b0d9b1a41227fc25eea0219ce520/frontend/Pictures/VerifyDeveloper.png -------------------------------------------------------------------------------- /frontend/Pictures/VerifyDeveloper2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raguay/ModalFileManager/9e62848bd579b0d9b1a41227fc25eea0219ce520/frontend/Pictures/VerifyDeveloper2.png -------------------------------------------------------------------------------- /frontend/bun.lock: -------------------------------------------------------------------------------- 1 | { 2 | "lockfileVersion": 1, 3 | "workspaces": { 4 | "": { 5 | "name": "modalfilemanager", 6 | "dependencies": { 7 | "svelte-awesome-color-picker": "^4.0.1", 8 | "svelte-icons": "^2.1.0", 9 | }, 10 | "devDependencies": { 11 | "@sveltejs/vite-plugin-svelte": "^5.0.3", 12 | "svelte": "^5.26.2", 13 | "vite": "^6.2.6", 14 | }, 15 | }, 16 | }, 17 | "packages": { 18 | "@ampproject/remapping": ["@ampproject/remapping@2.3.0", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw=="], 19 | 20 | "@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.25.2", "", { "os": "aix", "cpu": "ppc64" }, "sha512-wCIboOL2yXZym2cgm6mlA742s9QeJ8DjGVaL39dLN4rRwrOgOyYSnOaFPhKZGLb2ngj4EyfAFjsNJwPXZvseag=="], 21 | 22 | "@esbuild/android-arm": ["@esbuild/android-arm@0.25.2", "", { "os": "android", "cpu": "arm" }, "sha512-NQhH7jFstVY5x8CKbcfa166GoV0EFkaPkCKBQkdPJFvo5u+nGXLEH/ooniLb3QI8Fk58YAx7nsPLozUWfCBOJA=="], 23 | 24 | "@esbuild/android-arm64": ["@esbuild/android-arm64@0.25.2", "", { "os": "android", "cpu": "arm64" }, "sha512-5ZAX5xOmTligeBaeNEPnPaeEuah53Id2tX4c2CVP3JaROTH+j4fnfHCkr1PjXMd78hMst+TlkfKcW/DlTq0i4w=="], 25 | 26 | "@esbuild/android-x64": ["@esbuild/android-x64@0.25.2", "", { "os": "android", "cpu": "x64" }, "sha512-Ffcx+nnma8Sge4jzddPHCZVRvIfQ0kMsUsCMcJRHkGJ1cDmhe4SsrYIjLUKn1xpHZybmOqCWwB0zQvsjdEHtkg=="], 27 | 28 | "@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.25.2", "", { "os": "darwin", "cpu": "arm64" }, "sha512-MpM6LUVTXAzOvN4KbjzU/q5smzryuoNjlriAIx+06RpecwCkL9JpenNzpKd2YMzLJFOdPqBpuub6eVRP5IgiSA=="], 29 | 30 | "@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.25.2", "", { "os": "darwin", "cpu": "x64" }, "sha512-5eRPrTX7wFyuWe8FqEFPG2cU0+butQQVNcT4sVipqjLYQjjh8a8+vUTfgBKM88ObB85ahsnTwF7PSIt6PG+QkA=="], 31 | 32 | "@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.25.2", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-mLwm4vXKiQ2UTSX4+ImyiPdiHjiZhIaE9QvC7sw0tZ6HoNMjYAqQpGyui5VRIi5sGd+uWq940gdCbY3VLvsO1w=="], 33 | 34 | "@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.25.2", "", { "os": "freebsd", "cpu": "x64" }, "sha512-6qyyn6TjayJSwGpm8J9QYYGQcRgc90nmfdUb0O7pp1s4lTY+9D0H9O02v5JqGApUyiHOtkz6+1hZNvNtEhbwRQ=="], 35 | 36 | "@esbuild/linux-arm": ["@esbuild/linux-arm@0.25.2", "", { "os": "linux", "cpu": "arm" }, "sha512-UHBRgJcmjJv5oeQF8EpTRZs/1knq6loLxTsjc3nxO9eXAPDLcWW55flrMVc97qFPbmZP31ta1AZVUKQzKTzb0g=="], 37 | 38 | "@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.25.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-gq/sjLsOyMT19I8obBISvhoYiZIAaGF8JpeXu1u8yPv8BE5HlWYobmlsfijFIZ9hIVGYkbdFhEqC0NvM4kNO0g=="], 39 | 40 | "@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.25.2", "", { "os": "linux", "cpu": "ia32" }, "sha512-bBYCv9obgW2cBP+2ZWfjYTU+f5cxRoGGQ5SeDbYdFCAZpYWrfjjfYwvUpP8MlKbP0nwZ5gyOU/0aUzZ5HWPuvQ=="], 41 | 42 | "@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.25.2", "", { "os": "linux", "cpu": "none" }, "sha512-SHNGiKtvnU2dBlM5D8CXRFdd+6etgZ9dXfaPCeJtz+37PIUlixvlIhI23L5khKXs3DIzAn9V8v+qb1TRKrgT5w=="], 43 | 44 | "@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.25.2", "", { "os": "linux", "cpu": "none" }, "sha512-hDDRlzE6rPeoj+5fsADqdUZl1OzqDYow4TB4Y/3PlKBD0ph1e6uPHzIQcv2Z65u2K0kpeByIyAjCmjn1hJgG0Q=="], 45 | 46 | "@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.25.2", "", { "os": "linux", "cpu": "ppc64" }, "sha512-tsHu2RRSWzipmUi9UBDEzc0nLc4HtpZEI5Ba+Omms5456x5WaNuiG3u7xh5AO6sipnJ9r4cRWQB2tUjPyIkc6g=="], 47 | 48 | "@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.25.2", "", { "os": "linux", "cpu": "none" }, "sha512-k4LtpgV7NJQOml/10uPU0s4SAXGnowi5qBSjaLWMojNCUICNu7TshqHLAEbkBdAszL5TabfvQ48kK84hyFzjnw=="], 49 | 50 | "@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.25.2", "", { "os": "linux", "cpu": "s390x" }, "sha512-GRa4IshOdvKY7M/rDpRR3gkiTNp34M0eLTaC1a08gNrh4u488aPhuZOCpkF6+2wl3zAN7L7XIpOFBhnaE3/Q8Q=="], 51 | 52 | "@esbuild/linux-x64": ["@esbuild/linux-x64@0.25.2", "", { "os": "linux", "cpu": "x64" }, "sha512-QInHERlqpTTZ4FRB0fROQWXcYRD64lAoiegezDunLpalZMjcUcld3YzZmVJ2H/Cp0wJRZ8Xtjtj0cEHhYc/uUg=="], 53 | 54 | "@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.25.2", "", { "os": "none", "cpu": "arm64" }, "sha512-talAIBoY5M8vHc6EeI2WW9d/CkiO9MQJ0IOWX8hrLhxGbro/vBXJvaQXefW2cP0z0nQVTdQ/eNyGFV1GSKrxfw=="], 55 | 56 | "@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.25.2", "", { "os": "none", "cpu": "x64" }, "sha512-voZT9Z+tpOxrvfKFyfDYPc4DO4rk06qamv1a/fkuzHpiVBMOhpjK+vBmWM8J1eiB3OLSMFYNaOaBNLXGChf5tg=="], 57 | 58 | "@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.25.2", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-dcXYOC6NXOqcykeDlwId9kB6OkPUxOEqU+rkrYVqJbK2hagWOMrsTGsMr8+rW02M+d5Op5NNlgMmjzecaRf7Tg=="], 59 | 60 | "@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.25.2", "", { "os": "openbsd", "cpu": "x64" }, "sha512-t/TkWwahkH0Tsgoq1Ju7QfgGhArkGLkF1uYz8nQS/PPFlXbP5YgRpqQR3ARRiC2iXoLTWFxc6DJMSK10dVXluw=="], 61 | 62 | "@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.25.2", "", { "os": "sunos", "cpu": "x64" }, "sha512-cfZH1co2+imVdWCjd+D1gf9NjkchVhhdpgb1q5y6Hcv9TP6Zi9ZG/beI3ig8TvwT9lH9dlxLq5MQBBgwuj4xvA=="], 63 | 64 | "@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.25.2", "", { "os": "win32", "cpu": "arm64" }, "sha512-7Loyjh+D/Nx/sOTzV8vfbB3GJuHdOQyrOryFdZvPHLf42Tk9ivBU5Aedi7iyX+x6rbn2Mh68T4qq1SDqJBQO5Q=="], 65 | 66 | "@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.25.2", "", { "os": "win32", "cpu": "ia32" }, "sha512-WRJgsz9un0nqZJ4MfhabxaD9Ft8KioqU3JMinOTvobbX6MOSUigSBlogP8QB3uxpJDsFS6yN+3FDBdqE5lg9kg=="], 67 | 68 | "@esbuild/win32-x64": ["@esbuild/win32-x64@0.25.2", "", { "os": "win32", "cpu": "x64" }, "sha512-kM3HKb16VIXZyIeVrM1ygYmZBKybX8N4p754bw390wGO3Tf2j4L2/WYL+4suWujpgf6GBYs3jv7TyUivdd05JA=="], 69 | 70 | "@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.8", "", { "dependencies": { "@jridgewell/set-array": "^1.2.1", "@jridgewell/sourcemap-codec": "^1.4.10", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA=="], 71 | 72 | "@jridgewell/resolve-uri": ["@jridgewell/resolve-uri@3.1.2", "", {}, "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw=="], 73 | 74 | "@jridgewell/set-array": ["@jridgewell/set-array@1.2.1", "", {}, "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A=="], 75 | 76 | "@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.0", "", {}, "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ=="], 77 | 78 | "@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.25", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ=="], 79 | 80 | "@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.40.0", "", { "os": "android", "cpu": "arm" }, "sha512-+Fbls/diZ0RDerhE8kyC6hjADCXA1K4yVNlH0EYfd2XjyH0UGgzaQ8MlT0pCXAThfxv3QUAczHaL+qSv1E4/Cg=="], 81 | 82 | "@rollup/rollup-android-arm64": ["@rollup/rollup-android-arm64@4.40.0", "", { "os": "android", "cpu": "arm64" }, "sha512-PPA6aEEsTPRz+/4xxAmaoWDqh67N7wFbgFUJGMnanCFs0TV99M0M8QhhaSCks+n6EbQoFvLQgYOGXxlMGQe/6w=="], 83 | 84 | "@rollup/rollup-darwin-arm64": ["@rollup/rollup-darwin-arm64@4.40.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-GwYOcOakYHdfnjjKwqpTGgn5a6cUX7+Ra2HeNj/GdXvO2VJOOXCiYYlRFU4CubFM67EhbmzLOmACKEfvp3J1kQ=="], 85 | 86 | "@rollup/rollup-darwin-x64": ["@rollup/rollup-darwin-x64@4.40.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-CoLEGJ+2eheqD9KBSxmma6ld01czS52Iw0e2qMZNpPDlf7Z9mj8xmMemxEucinev4LgHalDPczMyxzbq+Q+EtA=="], 87 | 88 | "@rollup/rollup-freebsd-arm64": ["@rollup/rollup-freebsd-arm64@4.40.0", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-r7yGiS4HN/kibvESzmrOB/PxKMhPTlz+FcGvoUIKYoTyGd5toHp48g1uZy1o1xQvybwwpqpe010JrcGG2s5nkg=="], 89 | 90 | "@rollup/rollup-freebsd-x64": ["@rollup/rollup-freebsd-x64@4.40.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-mVDxzlf0oLzV3oZOr0SMJ0lSDd3xC4CmnWJ8Val8isp9jRGl5Dq//LLDSPFrasS7pSm6m5xAcKaw3sHXhBjoRw=="], 91 | 92 | "@rollup/rollup-linux-arm-gnueabihf": ["@rollup/rollup-linux-arm-gnueabihf@4.40.0", "", { "os": "linux", "cpu": "arm" }, "sha512-y/qUMOpJxBMy8xCXD++jeu8t7kzjlOCkoxxajL58G62PJGBZVl/Gwpm7JK9+YvlB701rcQTzjUZ1JgUoPTnoQA=="], 93 | 94 | "@rollup/rollup-linux-arm-musleabihf": ["@rollup/rollup-linux-arm-musleabihf@4.40.0", "", { "os": "linux", "cpu": "arm" }, "sha512-GoCsPibtVdJFPv/BOIvBKO/XmwZLwaNWdyD8TKlXuqp0veo2sHE+A/vpMQ5iSArRUz/uaoj4h5S6Pn0+PdhRjg=="], 95 | 96 | "@rollup/rollup-linux-arm64-gnu": ["@rollup/rollup-linux-arm64-gnu@4.40.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-L5ZLphTjjAD9leJzSLI7rr8fNqJMlGDKlazW2tX4IUF9P7R5TMQPElpH82Q7eNIDQnQlAyiNVfRPfP2vM5Avvg=="], 97 | 98 | "@rollup/rollup-linux-arm64-musl": ["@rollup/rollup-linux-arm64-musl@4.40.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-ATZvCRGCDtv1Y4gpDIXsS+wfFeFuLwVxyUBSLawjgXK2tRE6fnsQEkE4csQQYWlBlsFztRzCnBvWVfcae/1qxQ=="], 99 | 100 | "@rollup/rollup-linux-loongarch64-gnu": ["@rollup/rollup-linux-loongarch64-gnu@4.40.0", "", { "os": "linux", "cpu": "none" }, "sha512-wG9e2XtIhd++QugU5MD9i7OnpaVb08ji3P1y/hNbxrQ3sYEelKJOq1UJ5dXczeo6Hj2rfDEL5GdtkMSVLa/AOg=="], 101 | 102 | "@rollup/rollup-linux-powerpc64le-gnu": ["@rollup/rollup-linux-powerpc64le-gnu@4.40.0", "", { "os": "linux", "cpu": "ppc64" }, "sha512-vgXfWmj0f3jAUvC7TZSU/m/cOE558ILWDzS7jBhiCAFpY2WEBn5jqgbqvmzlMjtp8KlLcBlXVD2mkTSEQE6Ixw=="], 103 | 104 | "@rollup/rollup-linux-riscv64-gnu": ["@rollup/rollup-linux-riscv64-gnu@4.40.0", "", { "os": "linux", "cpu": "none" }, "sha512-uJkYTugqtPZBS3Z136arevt/FsKTF/J9dEMTX/cwR7lsAW4bShzI2R0pJVw+hcBTWF4dxVckYh72Hk3/hWNKvA=="], 105 | 106 | "@rollup/rollup-linux-riscv64-musl": ["@rollup/rollup-linux-riscv64-musl@4.40.0", "", { "os": "linux", "cpu": "none" }, "sha512-rKmSj6EXQRnhSkE22+WvrqOqRtk733x3p5sWpZilhmjnkHkpeCgWsFFo0dGnUGeA+OZjRl3+VYq+HyCOEuwcxQ=="], 107 | 108 | "@rollup/rollup-linux-s390x-gnu": ["@rollup/rollup-linux-s390x-gnu@4.40.0", "", { "os": "linux", "cpu": "s390x" }, "sha512-SpnYlAfKPOoVsQqmTFJ0usx0z84bzGOS9anAC0AZ3rdSo3snecihbhFTlJZ8XMwzqAcodjFU4+/SM311dqE5Sw=="], 109 | 110 | "@rollup/rollup-linux-x64-gnu": ["@rollup/rollup-linux-x64-gnu@4.40.0", "", { "os": "linux", "cpu": "x64" }, "sha512-RcDGMtqF9EFN8i2RYN2W+64CdHruJ5rPqrlYw+cgM3uOVPSsnAQps7cpjXe9be/yDp8UC7VLoCoKC8J3Kn2FkQ=="], 111 | 112 | "@rollup/rollup-linux-x64-musl": ["@rollup/rollup-linux-x64-musl@4.40.0", "", { "os": "linux", "cpu": "x64" }, "sha512-HZvjpiUmSNx5zFgwtQAV1GaGazT2RWvqeDi0hV+AtC8unqqDSsaFjPxfsO6qPtKRRg25SisACWnJ37Yio8ttaw=="], 113 | 114 | "@rollup/rollup-win32-arm64-msvc": ["@rollup/rollup-win32-arm64-msvc@4.40.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-UtZQQI5k/b8d7d3i9AZmA/t+Q4tk3hOC0tMOMSq2GlMYOfxbesxG4mJSeDp0EHs30N9bsfwUvs3zF4v/RzOeTQ=="], 115 | 116 | "@rollup/rollup-win32-ia32-msvc": ["@rollup/rollup-win32-ia32-msvc@4.40.0", "", { "os": "win32", "cpu": "ia32" }, "sha512-+m03kvI2f5syIqHXCZLPVYplP8pQch9JHyXKZ3AGMKlg8dCyr2PKHjwRLiW53LTrN/Nc3EqHOKxUxzoSPdKddA=="], 117 | 118 | "@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.40.0", "", { "os": "win32", "cpu": "x64" }, "sha512-lpPE1cLfP5oPzVjKMx10pgBmKELQnFJXHgvtHCtuJWOv8MxqdEIMNtgHgBFf7Ea2/7EuVwa9fodWUfXAlXZLZQ=="], 119 | 120 | "@sveltejs/acorn-typescript": ["@sveltejs/acorn-typescript@1.0.5", "", { "peerDependencies": { "acorn": "^8.9.0" } }, "sha512-IwQk4yfwLdibDlrXVE04jTZYlLnwsTT2PIOQQGNLWfjavGifnk1JD1LcZjZaBTRcxZu2FfPfNLOE04DSu9lqtQ=="], 121 | 122 | "@sveltejs/vite-plugin-svelte": ["@sveltejs/vite-plugin-svelte@5.0.3", "", { "dependencies": { "@sveltejs/vite-plugin-svelte-inspector": "^4.0.1", "debug": "^4.4.0", "deepmerge": "^4.3.1", "kleur": "^4.1.5", "magic-string": "^0.30.15", "vitefu": "^1.0.4" }, "peerDependencies": { "svelte": "^5.0.0", "vite": "^6.0.0" } }, "sha512-MCFS6CrQDu1yGwspm4qtli0e63vaPCehf6V7pIMP15AsWgMKrqDGCPFF/0kn4SP0ii4aySu4Pa62+fIRGFMjgw=="], 123 | 124 | "@sveltejs/vite-plugin-svelte-inspector": ["@sveltejs/vite-plugin-svelte-inspector@4.0.1", "", { "dependencies": { "debug": "^4.3.7" }, "peerDependencies": { "@sveltejs/vite-plugin-svelte": "^5.0.0", "svelte": "^5.0.0", "vite": "^6.0.0" } }, "sha512-J/Nmb2Q2y7mck2hyCX4ckVHcR5tu2J+MtBEQqpDrrgELZ2uvraQcK/ioCV61AqkdXFgriksOKIceDcQmqnGhVw=="], 125 | 126 | "@types/estree": ["@types/estree@1.0.7", "", {}, "sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ=="], 127 | 128 | "acorn": ["acorn@8.14.1", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg=="], 129 | 130 | "aria-query": ["aria-query@5.3.2", "", {}, "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw=="], 131 | 132 | "axobject-query": ["axobject-query@4.1.0", "", {}, "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ=="], 133 | 134 | "clsx": ["clsx@2.1.1", "", {}, "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA=="], 135 | 136 | "colord": ["colord@2.9.3", "", {}, "sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw=="], 137 | 138 | "debug": ["debug@4.4.0", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA=="], 139 | 140 | "deepmerge": ["deepmerge@4.3.1", "", {}, "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A=="], 141 | 142 | "esbuild": ["esbuild@0.25.2", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.2", "@esbuild/android-arm": "0.25.2", "@esbuild/android-arm64": "0.25.2", "@esbuild/android-x64": "0.25.2", "@esbuild/darwin-arm64": "0.25.2", "@esbuild/darwin-x64": "0.25.2", "@esbuild/freebsd-arm64": "0.25.2", "@esbuild/freebsd-x64": "0.25.2", "@esbuild/linux-arm": "0.25.2", "@esbuild/linux-arm64": "0.25.2", "@esbuild/linux-ia32": "0.25.2", "@esbuild/linux-loong64": "0.25.2", "@esbuild/linux-mips64el": "0.25.2", "@esbuild/linux-ppc64": "0.25.2", "@esbuild/linux-riscv64": "0.25.2", "@esbuild/linux-s390x": "0.25.2", "@esbuild/linux-x64": "0.25.2", "@esbuild/netbsd-arm64": "0.25.2", "@esbuild/netbsd-x64": "0.25.2", "@esbuild/openbsd-arm64": "0.25.2", "@esbuild/openbsd-x64": "0.25.2", "@esbuild/sunos-x64": "0.25.2", "@esbuild/win32-arm64": "0.25.2", "@esbuild/win32-ia32": "0.25.2", "@esbuild/win32-x64": "0.25.2" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-16854zccKPnC+toMywC+uKNeYSv+/eXkevRAfwRD/G9Cleq66m8XFIrigkbvauLLlCfDL45Q2cWegSg53gGBnQ=="], 143 | 144 | "esm-env": ["esm-env@1.2.2", "", {}, "sha512-Epxrv+Nr/CaL4ZcFGPJIYLWFom+YeV1DqMLHJoEd9SYRxNbaFruBwfEX/kkHUJf55j2+TUbmDcmuilbP1TmXHA=="], 145 | 146 | "esrap": ["esrap@1.4.6", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.4.15" } }, "sha512-F/D2mADJ9SHY3IwksD4DAXjTt7qt7GWUf3/8RhCNWmC/67tyb55dpimHmy7EplakFaflV0R/PC+fdSPqrRHAQw=="], 147 | 148 | "fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="], 149 | 150 | "is-reference": ["is-reference@3.0.3", "", { "dependencies": { "@types/estree": "^1.0.6" } }, "sha512-ixkJoqQvAP88E6wLydLGGqCJsrFUnqoH6HnaczB8XmDH1oaWU+xxdptvikTgaEhtZ53Ky6YXiBuUI2WXLMCwjw=="], 151 | 152 | "kleur": ["kleur@4.1.5", "", {}, "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ=="], 153 | 154 | "locate-character": ["locate-character@3.0.0", "", {}, "sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA=="], 155 | 156 | "magic-string": ["magic-string@0.30.17", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0" } }, "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA=="], 157 | 158 | "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], 159 | 160 | "nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="], 161 | 162 | "picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="], 163 | 164 | "postcss": ["postcss@8.5.3", "", { "dependencies": { "nanoid": "^3.3.8", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A=="], 165 | 166 | "rollup": ["rollup@4.40.0", "", { "dependencies": { "@types/estree": "1.0.7" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.40.0", "@rollup/rollup-android-arm64": "4.40.0", "@rollup/rollup-darwin-arm64": "4.40.0", "@rollup/rollup-darwin-x64": "4.40.0", "@rollup/rollup-freebsd-arm64": "4.40.0", "@rollup/rollup-freebsd-x64": "4.40.0", "@rollup/rollup-linux-arm-gnueabihf": "4.40.0", "@rollup/rollup-linux-arm-musleabihf": "4.40.0", "@rollup/rollup-linux-arm64-gnu": "4.40.0", "@rollup/rollup-linux-arm64-musl": "4.40.0", "@rollup/rollup-linux-loongarch64-gnu": "4.40.0", "@rollup/rollup-linux-powerpc64le-gnu": "4.40.0", "@rollup/rollup-linux-riscv64-gnu": "4.40.0", "@rollup/rollup-linux-riscv64-musl": "4.40.0", "@rollup/rollup-linux-s390x-gnu": "4.40.0", "@rollup/rollup-linux-x64-gnu": "4.40.0", "@rollup/rollup-linux-x64-musl": "4.40.0", "@rollup/rollup-win32-arm64-msvc": "4.40.0", "@rollup/rollup-win32-ia32-msvc": "4.40.0", "@rollup/rollup-win32-x64-msvc": "4.40.0", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-Noe455xmA96nnqH5piFtLobsGbCij7Tu+tb3c1vYjNbTkfzGqXqQXG3wJaYXkRZuQ0vEYN4bhwg7QnIrqB5B+w=="], 167 | 168 | "source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="], 169 | 170 | "svelte": ["svelte@5.26.2", "", { "dependencies": { "@ampproject/remapping": "^2.3.0", "@jridgewell/sourcemap-codec": "^1.5.0", "@sveltejs/acorn-typescript": "^1.0.5", "@types/estree": "^1.0.5", "acorn": "^8.12.1", "aria-query": "^5.3.1", "axobject-query": "^4.1.0", "clsx": "^2.1.1", "esm-env": "^1.2.1", "esrap": "^1.4.6", "is-reference": "^3.0.3", "locate-character": "^3.0.0", "magic-string": "^0.30.11", "zimmerframe": "^1.1.2" } }, "sha512-e2TEcGK2YKVwDWYy5OsptVclYgDvfY1E/8IzPiOq63uG/GDo/j5VUYTC9EinQNraoZalbMWN+5f5TYC1QlAqOw=="], 171 | 172 | "svelte-awesome-color-picker": ["svelte-awesome-color-picker@4.0.1", "", { "dependencies": { "colord": "^2.9.3", "svelte-awesome-slider": "2.0.0" }, "peerDependencies": { "svelte": "^5.0.0" } }, "sha512-n9Lcvi8cYCl5RbT1YzjZAL1AeGXZcEynul25xy1tektkII7xW56en5iaqJFKZHmwRLmZ9Wh4CCEqYIXDtq9sDQ=="], 173 | 174 | "svelte-awesome-slider": ["svelte-awesome-slider@2.0.0", "", { "peerDependencies": { "svelte": "^5.0.0" } }, "sha512-YBkOdYm1Feaqsn2JkJBRs+Kc/X3Qy/3GuVmI7GmoYDjBaHkjx9uH4khTuED22z57Hg3gGWeDhp/clIjWDdLNaw=="], 175 | 176 | "svelte-icons": ["svelte-icons@2.1.0", "", {}, "sha512-rHPQjweEc9fGSnvM0/4gA3pDHwyZyYsC5KhttCZRhSMJfLttJST5Uq0B16Czhw+HQ+HbSOk8kLigMlPs7gZtfg=="], 177 | 178 | "vite": ["vite@6.2.6", "", { "dependencies": { "esbuild": "^0.25.0", "postcss": "^8.5.3", "rollup": "^4.30.1" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", "jiti": ">=1.21.0", "less": "*", "lightningcss": "^1.21.0", "sass": "*", "sass-embedded": "*", "stylus": "*", "sugarss": "*", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-9xpjNl3kR4rVDZgPNdTL0/c6ao4km69a/2ihNQbcANz8RuCOK3hQBmLSJf3bRKVQjVMda+YvizNE8AwvogcPbw=="], 179 | 180 | "vitefu": ["vitefu@1.0.6", "", { "peerDependencies": { "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0" }, "optionalPeers": ["vite"] }, "sha512-+Rex1GlappUyNN6UfwbVZne/9cYC4+R2XDk9xkNXBKMw6HQagdX9PgZ8V2v1WUSK1wfBLp7qbI1+XSNIlB1xmA=="], 181 | 182 | "zimmerframe": ["zimmerframe@1.1.2", "", {}, "sha512-rAbqEGa8ovJy4pyBxZM70hg4pE6gDgaQ0Sl9M3enG3I0d6H4XSAM3GeNGLKnsBpuijUow064sf7ww1nutC5/3w=="], 183 | } 184 | } 185 | -------------------------------------------------------------------------------- /frontend/icons/mfm-icon.afdesign: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raguay/ModalFileManager/9e62848bd579b0d9b1a41227fc25eea0219ce520/frontend/icons/mfm-icon.afdesign -------------------------------------------------------------------------------- /frontend/icons/mfm-icon.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raguay/ModalFileManager/9e62848bd579b0d9b1a41227fc25eea0219ce520/frontend/icons/mfm-icon.icns -------------------------------------------------------------------------------- /frontend/icons/mfm-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raguay/ModalFileManager/9e62848bd579b0d9b1a41227fc25eea0219ce520/frontend/icons/mfm-icon.png -------------------------------------------------------------------------------- /frontend/icons/mfm-icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | mfm 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /frontend/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /frontend/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 | ``` 14 | -------------------------------------------------------------------------------- /frontend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "modalfilemanager", 3 | "private": false, 4 | "version": "1.0.0", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vite", 8 | "build": "vite build", 9 | "preview": "vite preview" 10 | }, 11 | "devDependencies": { 12 | "@sveltejs/vite-plugin-svelte": "^5.0.3", 13 | "svelte": "^5.26.2", 14 | "vite": "^6.2.6" 15 | }, 16 | "dependencies": { 17 | "svelte-awesome-color-picker": "^4.0.1", 18 | "svelte-icons": "^2.1.0" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /frontend/src/Start.svelte: -------------------------------------------------------------------------------- 1 | 21 | 22 | { 24 | $ctrlKey = e.ctrlKey; 25 | $shiftKey = e.shiftKey; 26 | $metaKey = e.metaKey; 27 | $altKey = e.altKey; 28 | $key = e.key; 29 | if ($keyProcess) { 30 | e.preventDefault(); 31 | if ($processKey !== null) $processKey(); 32 | } 33 | }} 34 | onkeyup={(e) => { 35 | $ctrlKey = e.ctrlKey; 36 | $shiftKey = e.shiftKey; 37 | $metaKey = e.metaKey; 38 | $altKey = e.altKey; 39 | }} 40 | onresize={() => { 41 | midSize = window.innerHeight - 75; 42 | }} 43 | onbeforeunload={() => { 44 | window.go.main.App.Quit(); 45 | }} 46 | /> 47 | 48 |
49 | 50 | 51 | 52 | 53 | 54 |
55 | 56 | {#if currentView === "preferences"} 57 | 58 | {/if} 59 | 60 | 81 | -------------------------------------------------------------------------------- /frontend/src/components/CommandPrompt.svelte: -------------------------------------------------------------------------------- 1 | 114 | 115 |
{ 124 | exitCP(); 125 | }} 126 | > 127 | 140 | {#if filtered.length > 0} 141 |
146 | 183 |
184 | {/if} 185 |
186 | 187 | 226 | -------------------------------------------------------------------------------- /frontend/src/components/DirectoryListing.svelte: -------------------------------------------------------------------------------- 1 | 317 | 318 | 319 |
323 | {#if !edit} 324 | 325 | {newPath} 326 | 327 | {:else} 328 | 340 | {#if dirlist.length > 0} 341 |
348 |
    349 | {#each dirlist as item, key} 350 | {#if item !== ""} 351 | {#if key === dirIndex} 352 |
  • {}} 356 | onclick={() => { 357 | processListItem(key); 358 | }} 359 | > 360 | {item} 361 |
  • 362 | {:else} 363 |
  • {}} 366 | onclick={() => { 367 | processListItem(key); 368 | }} 369 | > 370 | {item} 371 |
  • 372 | {/if} 373 | {/if} 374 | {/each} 375 |
376 |
377 | {/if} 378 | {/if} 379 |
380 | 381 | 439 | -------------------------------------------------------------------------------- /frontend/src/components/Entry.svelte: -------------------------------------------------------------------------------- 1 | 153 | 154 | 155 | {#if ($currentCursor.pane === pane && $currentCursor.entry.name == entry.name) || entry.selected} 156 |
{}} 161 | onclick={() => { 162 | cursorToEntry(); 163 | }} 164 | ondblclick={() => { 165 | openEntry(); 166 | }} 167 | draggable="true" 168 | ondragstart={dragStart} 169 | ondragend={(e) => { 170 | dropFiles(e, "dragend"); 171 | }} 172 | ondrop={(e) => { 173 | dropFiles(e, "drop"); 174 | }} 175 | ondragover={(e) => { 176 | dropFiles(e, "dragover"); 177 | }} 178 | ondragenter={(e) => { 179 | dropFiles(e, "dragenter"); 180 | }} 181 | > 182 |
183 | 184 | {#if entry.type === 0} 185 | 186 | {:else if entry.type === 1} 187 | 188 | {:else} 189 | 190 | {/if} 191 | 192 | {entry.name} 198 |
199 |
200 | {:else} 201 |
{}} 206 | onclick={() => { 207 | cursorToEntry(); 208 | }} 209 | ondblclick={() => { 210 | openEntry(); 211 | }} 212 | draggable="false" 213 | ondrop={(e) => { 214 | dropFiles(e, "drop"); 215 | }} 216 | ondragover={(e) => { 217 | dropFiles(e, "over"); 218 | }} 219 | > 220 |
221 | 222 | {#if entry.type === 0} 223 | 224 | {:else if entry.type === 1} 225 | 226 | {:else} 227 | 228 | {/if} 229 | 230 | 236 | {entry.name} 237 | 238 |
239 |
240 | {/if} 241 | 242 | 285 | -------------------------------------------------------------------------------- /frontend/src/components/Env.svelte: -------------------------------------------------------------------------------- 1 | 68 | 69 | 70 | {#if $config !== null && typeof $config.env !== "undefined"} 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | {#each Object.entries($config.env) as kv} 82 | 88 | {/each} 89 | {#if addNew} 90 | 91 | 92 | 93 | 108 | 132 | 133 | {:else} 134 | 135 | 147 | 148 | {/if} 149 | 150 |
Name Value
94 | {}} 103 | onmouseover={() => { 104 | setFocus(false); 105 | }} 106 | /> 107 | 109 | {}} 117 | onblur={addKV} 118 | onmouseover={() => { 119 | setFocus(false); 120 | }} 121 | onmouseleave={addKV} 122 | onkeydown={(e) => { 123 | switch (e.key) { 124 | case "Enter": { 125 | addKV(); 126 | setFocus(true); 127 | } 128 | } 129 | }} 130 | /> 131 |
136 | {}} 139 | onclick={() => { 140 | addNew = true; 141 | setFocus(false); 142 | }} 143 | > 144 | + 145 | 146 |
151 | {/if} 152 | 153 | 171 | -------------------------------------------------------------------------------- /frontend/src/components/EnvTableRow.svelte: -------------------------------------------------------------------------------- 1 | 43 | 44 | 45 | 46 | 47 | 🖋️ 48 | 49 | 50 | 51 | 52 | {name} 53 | {#if editValue} 54 | { 61 | saveInput(); 62 | }} 63 | onmouseover={() => { 64 | setFocus(false); 65 | }} 66 | onmouseleave={() => { 67 | saveInput(); 68 | }} 69 | onkeydown={(e) => { 70 | switch (e.key) { 71 | case "Enter": { 72 | e.preventDefault(); 73 | e.stopPropagation(); 74 | saveInput(); 75 | break; 76 | } 77 | } 78 | }} 79 | /> 80 | {:else} 81 | {value} 82 | {/if} 83 | 84 | 85 | 100 | -------------------------------------------------------------------------------- /frontend/src/components/ExtensionPrefs.svelte: -------------------------------------------------------------------------------- 1 | 212 | 213 |
214 | {#if showMsgBox} 215 |
223 |

{msgTitle}

224 |

{msgText}

225 | 236 |
237 | {/if} 238 | {#if extensionList !== null} 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | {#each extensionList as item} 249 | 250 | 253 | 267 | 277 | 278 | {/each} 279 | 280 |
Extension Name
251 | {item.name} 252 | 254 | 266 | 268 | 276 |
281 | {/if} 282 |
283 | 284 | { 295 | blur = false; 296 | if (newExtNameDOM !== null) newExtNameDOM.focus(); 297 | }} 298 | onfocus={() => { 299 | blur = false; 300 | if (newExtNameDOM !== null) newExtNameDOM.focus(); 301 | }} 302 | onblur={() => { 303 | blur = true; 304 | }} 305 | onmouseout={() => { 306 | blur = true; 307 | }} 308 | /> 309 | 319 |
320 |
321 | 322 | 391 | -------------------------------------------------------------------------------- /frontend/src/components/ExtraPanel.svelte: -------------------------------------------------------------------------------- 1 | 118 | 119 |
120 | {#if isExtra} 121 | {@html extraHTML} 122 | {/if} 123 |
{fullPath}
124 | {#if extension === ".png" || extension === ".heic" || extension === ".jpg" || extension === ".svg" || extension === ".jpeg" || extension === ".gif" || extension === ".apng" || extension === ".avif" || extension === ".webp" || extension === ".avi"} 125 | {fullPath} 126 | {:else if isMovieFlag} 127 | 130 |

131 | Dimensions: {videoDem} 132 |

133 | {/if} 134 |
135 | {#if typeof entry.datetime !== "undefined"} 136 |

Date: {entry.datetime}

137 | {/if} 138 |

Size: {size}

139 |
140 |

Permissions:

141 | 142 |
143 |
144 |
145 | 146 | 176 | -------------------------------------------------------------------------------- /frontend/src/components/GeneralPrefs.svelte: -------------------------------------------------------------------------------- 1 | 7 | 8 |
9 | {#if $config !== null} 10 |

Environment for Launching Programs

11 | 12 | 13 |

Other Configuration Items

14 |
15 | 16 | {#if $config.useTrash} 17 | { 22 | $config.useTrash = e.target.checked; 23 | }} 24 | /> 25 | {:else} 26 | { 30 | $config.useTrash = e.target.checked; 31 | }} 32 | /> 33 | {/if} 34 |
35 | {/if} 36 |
37 | 38 | 61 | -------------------------------------------------------------------------------- /frontend/src/components/GitHub.svelte: -------------------------------------------------------------------------------- 1 | 228 | 229 | 230 |
{ 238 | exitGitHub(); 239 | }} 240 | > 241 |
242 |

GitHub Themes and Extensions Importer

243 | {}} 245 | onclick={() => { 246 | exitGitHub(); 247 | }} 248 | style="color: {$theme.Red};" 249 | > 250 | X 251 | 252 | 260 |
261 | {#if loading} 262 |

Loading....

263 | {:else} 264 |
265 | {#each repos as repo} 266 |
267 |
268 |

269 | {repo.name} 270 |

271 |

272 | {repo.stars} ⭐️s 273 |

274 |
275 |
276 |

277 | {repo.description} 278 |

279 |

280 | Created by: {repo.owner} 281 |

282 |
283 | {#if hasMsg(repo)} 284 |
285 | {@html getMsg(repo)} 286 |
287 | {/if} 288 |
289 | {#if repo.loaded} 290 | 298 | {:else} 299 | 307 | {/if} 308 |
309 |
310 | {/each} 311 | {#each themes as thm} 312 |
313 |
314 |

315 | {thm.name} 316 |

317 |

318 | {thm.stars} ⭐️s 319 |

320 |
321 |
322 |

323 | {thm.description} 324 |

325 |

326 | Created by: {thm.owner} 327 |

328 |
329 | {#if hasMsg(thm)} 330 |
331 | {@html getMsg(thm)} 332 |
333 | {/if} 334 |
335 | {#if thm.loaded} 336 | 344 | 352 | {:else} 353 | 361 | {/if} 362 |
363 |
364 | {/each} 365 |
366 | {/if} 367 |
368 | 369 | 444 | -------------------------------------------------------------------------------- /frontend/src/components/MessageBox.svelte: -------------------------------------------------------------------------------- 1 | 233 | 234 | 235 |
236 |
{ 242 | if ( 243 | e.key === "Escape" || 244 | (e.key === "Enter" && items[0].type === "label") 245 | ) { 246 | e.preventDefault(); 247 | closeMsgBox = true; 248 | } 249 | }} 250 | bind:this={msgboxDOM} 251 | > 252 | {#if config !== null} 253 |

{config.title}

254 | {#if typeof items !== null} 255 | {#each items as item} 256 | {#if typeof item !== "undefined"} 257 | {#if item.type === "input"} 258 |

{item.msg}

259 | { 267 | if (e.key === "Enter") { 268 | e.preventDefault(); 269 | returnInputValue(); 270 | } 271 | }} 272 | /> 273 | {:else if item.type === "selector"} 274 | 281 | {:else if item.type === "picker"} 282 |
283 | 291 | 321 |
322 | {:else if item.type === "spinner"} 323 | 324 | {:else if item.type === "label"} 325 | 328 | {:else if item.type === "html"} 329 | {@html item.text} 330 | {/if} 331 | {:else} 332 |

System Error

333 | {/if} 334 | {/each} 335 | {:else} 336 |

System Error

337 | {/if} 338 |
339 | {#if typeof config.noShowButton !== "undefined" && !config.noShowButton} 340 | 351 | {/if} 352 | 353 |
354 | {/if} 355 |
356 |
357 | 358 | 419 | -------------------------------------------------------------------------------- /frontend/src/components/ModeLine.svelte: -------------------------------------------------------------------------------- 1 | 20 | 21 |
22 | {modeString} 23 |
24 | 25 | 36 | -------------------------------------------------------------------------------- /frontend/src/components/Pane.svelte: -------------------------------------------------------------------------------- 1 | 149 | 150 | 151 |
152 | {#each entries as entry} 153 | 154 | {/each} 155 |
{}} 159 | onclick={() => { 160 | cursorToPane(); 161 | }} 162 | ondrop={preventDefault((e) => { 163 | dropFiles(e, "drop"); 164 | })} 165 | ondragover={preventDefault((e) => { 166 | dropFiles(e, "dragover"); 167 | })} 168 | >
169 |
170 | 171 | 191 | -------------------------------------------------------------------------------- /frontend/src/components/Preferences.svelte: -------------------------------------------------------------------------------- 1 | 75 | 76 | 77 |
84 | { 88 | if (keepBlur && vimInput !== null) { 89 | vimInput.focus(); 90 | } 91 | }} 92 | onkeydown={(e) => { 93 | if (keepBlur) { 94 | e.preventDefault(); 95 | switch (e.key) { 96 | case "g": 97 | showPanel = "general"; 98 | break; 99 | case "t": 100 | showPanel = "theme"; 101 | break; 102 | case "e": 103 | showPanel = "extension"; 104 | break; 105 | case "ArrowUp": 106 | case "k": 107 | scrollDiv(-1); 108 | break; 109 | case "ArrowDown": 110 | case "j": 111 | scrollDiv(1); 112 | break; 113 | case "Escape": 114 | keepBlur = false; 115 | exitPrefs(); 116 | break; 117 | default: 118 | break; 119 | } 120 | } 121 | }} 122 | /> 123 |

Modal File Manager: Preferences

124 |
    125 | {#if showPanel === "general"} 126 |
  • { 128 | showPanel = "general"; 129 | }} 130 | style="border-color: {$theme.textColor}; 131 | background-color: {$theme.textColor}; 132 | color: {$theme.backgroundColor};" 133 | > 134 | General 135 |
  • 136 | {:else} 137 |
  • { 139 | showPanel = "general"; 140 | }} 141 | style="border-color: {$theme.textColor}; 142 | color: {$theme.textColor}; 143 | background-color: {$theme.backgroundColor};" 144 | > 145 | General 146 |
  • 147 | {/if} 148 | {#if showPanel === "theme"} 149 |
  • { 151 | showPanel = "theme"; 152 | }} 153 | style="border-color: {$theme.textColor}; 154 | background-color: {$theme.textColor}; 155 | color: {$theme.backgroundColor};" 156 | > 157 | Theme 158 |
  • 159 | {:else} 160 |
  • { 162 | showPanel = "theme"; 163 | }} 164 | style="border-color: {$theme.textColor}; 165 | color: {$theme.textColor}; 166 | background-color: {$theme.backgroundColor};" 167 | > 168 | Theme 169 |
  • 170 | {/if} 171 | {#if showPanel === "extension"} 172 |
  • { 174 | showPanel = "extension"; 175 | }} 176 | style="border-color: {$theme.textColor}; 177 | background-color: {$theme.textColor}; 178 | color: {$theme.backgroundColor};" 179 | > 180 | Extension 181 |
  • 182 | {:else} 183 |
  • { 185 | showPanel = "extension"; 186 | }} 187 | style="border-color: {$theme.textColor}; 188 | color: {$theme.textColor}; 189 | background-color: {$theme.backgroundColor};" 190 | > 191 | Extension 192 |
  • 193 | {/if} 194 |
195 | {#if showPanel === "general"} 196 | 197 | {:else if showPanel === "theme"} 198 | 199 | {:else if showPanel === "extension"} 200 | 201 | {/if} 202 |
203 | 214 |
215 |
216 | 217 | 270 | -------------------------------------------------------------------------------- /frontend/src/components/QuickSearch.svelte: -------------------------------------------------------------------------------- 1 | 62 | 63 |
64 | { 74 | exitQS(); 75 | }} 76 | style="background-color: {$theme.textColor}; 77 | text-color: {$theme.backgroundColor};" 78 | /> 79 |
80 | 81 | 110 | -------------------------------------------------------------------------------- /frontend/src/components/ResizeBorder.svelte: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 |
{ 18 | mdown = true; 19 | }} 20 | onmouseup={() => { 21 | mdown = false; 22 | }} 23 | >
24 | 25 | 33 | -------------------------------------------------------------------------------- /frontend/src/components/StatusLine.svelte: -------------------------------------------------------------------------------- 1 | 44 | 45 |
53 | 57 | {$inputState} 58 | 59 | 63 | {$currentCursor.pane} 64 | 65 | 70 | {$currentCursor.entry.name} 71 | 72 | 77 | {DT} 78 | 79 | 85 | {size} 86 | 87 |
88 | 89 | 152 | -------------------------------------------------------------------------------- /frontend/src/components/ThemeItem.svelte: -------------------------------------------------------------------------------- 1 | 51 | 52 | 53 | 54 | 55 | {label} 56 | 57 | 58 |
59 | {#if showInput} 60 | { 71 | e.preventDefault(); 72 | switch (e.key) { 73 | case "Enter": 74 | e.stopPropagation(); 75 | changeStringValue(nvalue); 76 | value = nvalue; 77 | break; 78 | case "Esc": 79 | showInput = false; 80 | nvalue = value; 81 | break; 82 | case "Tab": 83 | changeStringValue(nvalue); 84 | value = nvalue; 85 | break; 86 | } 87 | }} 88 | onblur={() => { 89 | changeStringValue(nvalue); 90 | value = nvalue; 91 | }} 92 | /> 93 | {:else} 94 |
95 | { 97 | showInput = true; 98 | setFocus(false); 99 | }} 100 | > 101 | {value} 102 | 103 |
104 | {/if} 105 | {#if changeColor} 106 |
{ 110 | hex = value; 111 | showPicker = true; 112 | }} 113 | >
114 | {/if} 115 |
116 | 117 | 118 | {#if showPicker} 119 | { 125 | hex = e.details.hex; 126 | setValue(); 127 | }} 128 | /> 129 | {/if} 130 | 131 | 161 | -------------------------------------------------------------------------------- /frontend/src/components/ThemePrefs.svelte: -------------------------------------------------------------------------------- 1 | 134 | 135 | 136 |
137 |

Current Theme Values

138 | {#if $theme !== null} 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | {#each Object.entries($theme) as kv} 148 | 154 | {/each} 155 | 156 |
Name Value
157 | {/if} 158 |

Save as New Theme

159 |
160 | 161 | { 173 | blur = false; 174 | if (themeNameDOM !== null) themeNameDOM.focus(); 175 | }} 176 | onfocus={() => { 177 | blur = false; 178 | if (themeNameDOM !== null) themeNameDOM.focus(); 179 | }} 180 | onblur={() => { 181 | blur = true; 182 | }} 183 | onmouseout={() => { 184 | blur = true; 185 | }} 186 | /> 187 | 197 |
198 | {#if showMsgBox} 199 |
207 |

{msgTitle}

208 |

{msgText}

209 | 220 |
221 | {/if} 222 |

Existing Themes

223 | {#if themeList !== null} 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | {#each themeList as item} 235 | 236 | 239 | 253 | 267 | 277 | 278 | {/each} 279 | 280 |
Theme Name
237 | {item.name} 238 | 240 | 252 | 254 | 266 | 268 | 276 |
281 | {/if} 282 |
283 | 284 | 357 | -------------------------------------------------------------------------------- /frontend/src/components/TitleBar.svelte: -------------------------------------------------------------------------------- 1 | 4 | 5 |
13 |
14 | 15 |
16 |

Modal File Manager

17 |
18 | 19 |
20 |
21 | 22 | 66 | -------------------------------------------------------------------------------- /frontend/src/main.js: -------------------------------------------------------------------------------- 1 | import Start from "./Start.svelte"; 2 | import { mount } from "svelte"; 3 | 4 | const main = mount(Start, { target: document.body }); 5 | 6 | export default main; 7 | -------------------------------------------------------------------------------- /frontend/src/modules/OS.js: -------------------------------------------------------------------------------- 1 | // 2 | // File: OS.js 3 | // 4 | // Description: This file contains all low level 5 | // functions that would be OS dependent. Now, most OS 6 | // dependent items have been moved to go main program. 7 | // 8 | import * as App from "../../dist/wailsjs/go/main/App.js"; 9 | 10 | let OS = { 11 | dirFirst: true, 12 | sortFunction: null, 13 | filterFunction: null, 14 | lastError: "", 15 | lastOutput: "", 16 | config: null, 17 | type: null, 18 | rootDir: "/", 19 | path: "/", 20 | sep: "/", 21 | shell: null, 22 | localHomeDir: null, 23 | localConfigDir: null, 24 | openCommand: "open", 25 | setConfig: function (cfg) { 26 | this.config = cfg; 27 | }, 28 | getExtension: async function (file) { 29 | let ext = ""; 30 | let match = file.match(/\.(.*)$/); 31 | if (match !== null) { 32 | ext = match[1]; 33 | } 34 | return ext; 35 | }, 36 | getConfigDir: async function () { 37 | if (this.localConfigDir === null) { 38 | if (this.localHomeDir === null) { 39 | this.localHom; 40 | } 41 | this.localConfigDir = await this.appendPath( 42 | this.localHomeDir, 43 | ".config/modalfilemanager", 44 | ); 45 | } 46 | return this.localConfigDir; 47 | }, 48 | terminalScript: "bin/openTerminal.scpt", 49 | init: async function () { 50 | // 51 | // Set the defaults 52 | // 53 | this.sortFunction = this.alphaSort; 54 | this.filterFunction = this.defaultFilter; 55 | 56 | // 57 | // Initialize OS specific items. 58 | // 59 | this.localHomeDir = await this.getHomeDir(); 60 | this.type = await this.getOSname(); 61 | switch (this.type) { 62 | case "macos": { 63 | this.sep = "/"; 64 | this.rootDir = ""; 65 | this.openCommand = "open"; 66 | this.shell = "zsh"; 67 | break; 68 | } 69 | case "linux": { 70 | this.sep = "/"; 71 | this.rootDir = ""; 72 | this.openCommand = "xdg-open"; 73 | this.shell = "bash"; 74 | break; 75 | } 76 | case "windows": { 77 | this.sep = "\\"; 78 | this.rootDir = "C:"; 79 | this.openCommand = "start"; 80 | this.shell = "cmd.exe"; 81 | break; 82 | } 83 | default: { 84 | this.sep = "/"; 85 | this.rootDir = ""; 86 | break; 87 | } 88 | } 89 | }, 90 | getOSname: async function () { 91 | if (this.type === null) { 92 | this.type = App.GetOSName(); 93 | } 94 | return this.type; 95 | }, 96 | getDirFirst: function () { 97 | return this.dirFirst; 98 | }, 99 | setDirFirst: function (flag) { 100 | if (typeof flag === "boolean") { 101 | this.dirFirst = flag; 102 | } 103 | }, 104 | setDirSort: function (sortFunction) { 105 | this.sortFunction = sortFunction; 106 | }, 107 | setFilter: function (flt) { 108 | this.filterFunction = flt; 109 | }, 110 | getTerminalScript: async function () { 111 | let tsFile = this.terminalScript; 112 | if (this.terminalScript.startsWith(this.rootDir)) { 113 | tsFile = await this.appendPath(this.localHomeDir, this.terminalScript); 114 | } 115 | return tsFile; 116 | }, 117 | setTerminalScript: function (scrpt) { 118 | this.terminalScript = scrpt; 119 | }, 120 | getHomeDir: async function () { 121 | if (this.localHomeDir === null) { 122 | this.localHomeDir = await App.GetHomeDir(); 123 | this.localHomeDir = new String(this.localHomeDir).toString(); 124 | } 125 | return this.localHomeDir; 126 | }, 127 | readDir: async function (dir) { 128 | if (typeof dir !== "string") { 129 | dir = await this.appendPath(dir.dir, dir.name); 130 | } 131 | // 132 | // Get the directory information 133 | // 134 | let result = await App.ReadDir(dir); 135 | return result; 136 | }, 137 | normalize: async function (dir) { 138 | if (dir[0] === "~") { 139 | let hd = await this.getHomeDir(); 140 | dir = await this.appendPath(hd, dir.slice(1, dir.length)); 141 | } 142 | return dir; 143 | }, 144 | dirExists: async function (dir) { 145 | if ( 146 | typeof dir !== "string" && 147 | typeof dir.dir !== "undefined" && 148 | typeof dir.name !== "undefined" 149 | ) { 150 | dir = await this.appendPath(dir.dir, dir.name); 151 | } 152 | const dirReal = await App.DirExists(dir); 153 | return dirReal; 154 | }, 155 | fileExists: async function (file) { 156 | let result = true; 157 | if (typeof file !== "string") { 158 | file = await this.appendPath(file.dir, file.name); 159 | } 160 | result = App.FileExists(file); 161 | return result; 162 | }, 163 | makeDir: async function (dir) { 164 | if (typeof dir !== "string") { 165 | dir = await this.appendPath(dir.dir, dir.name); 166 | } 167 | // 168 | // Make a directory. 169 | // 170 | await App.MakeDir(dir); 171 | }, 172 | moveEntries: async function (from, to, callback) { 173 | let fromName; 174 | let toName; 175 | 176 | // 177 | // It can receive an object or string. Check to see which it is 178 | // to get the proper path. 179 | // 180 | if (typeof from !== "string") { 181 | fromName = await this.appendPath(from.dir, from.name); 182 | } else { 183 | fromName = from; 184 | } 185 | if (typeof to !== "string") { 186 | toName = to.dir; 187 | } else { 188 | toName = to; 189 | } 190 | 191 | const parts = fromName.split(this.sep); 192 | toName = await this.appendPath(toName, parts[parts.length - 1]); 193 | 194 | // 195 | // Move the entries. 196 | // 197 | await App.MoveEntries(fromName, toName); 198 | let err = await App.GetError(); 199 | 200 | // 201 | // Run the callback if given. 202 | // 203 | if (typeof callback !== "undefined") { 204 | callback(err); 205 | } 206 | }, 207 | copyEntries: async function (from, to, callback) { 208 | let fromName; 209 | let toName; 210 | 211 | // 212 | // It can receive an object or string. Check to see which it is 213 | // to get the proper path. 214 | // 215 | if (typeof from !== "string") { 216 | fromName = await this.appendPath(from.dir, from.name); 217 | } else { 218 | fromName = from; 219 | } 220 | if (typeof to !== "string") { 221 | toName = await this.appendPath(to.dir, to.name); 222 | } else { 223 | toName = to; 224 | } 225 | const isDir = await this.dirExists(toName); 226 | if (isDir) { 227 | const parts = fromName.split(this.sep); 228 | toName = await this.appendPath(toName, parts[parts.length - 1]); 229 | } 230 | 231 | // 232 | // Copy the entries. 233 | // 234 | await App.CopyEntries(fromName, toName); 235 | let err = await App.GetError(); 236 | 237 | // 238 | // Run the callback if given. 239 | // 240 | if (typeof callback !== "undefined") { 241 | callback(err); 242 | } 243 | }, 244 | deleteEntries: async function (entry, callback) { 245 | let item = entry; 246 | if (typeof entry !== "string") { 247 | item = await this.appendPath(entry.dir, entry.name); 248 | } 249 | let that = this; 250 | if (this.config !== null) { 251 | if (this.config.useTrash) { 252 | // 253 | // Use the trashcan on the system. 254 | // 255 | if (typeof callback === "undefined") { 256 | this.runCommandLine( 257 | `trash '${item}'`, 258 | [], 259 | (err, stdout) => { 260 | if (err) { 261 | that.lastError = err; 262 | } 263 | that.lastOutput = stdout; 264 | }, 265 | ".", 266 | ); 267 | } else { 268 | this.runCommandLine(`trash '${item}'`, [], callback, "."); 269 | } 270 | } else { 271 | // 272 | // Delete the item completely. 273 | // 274 | await App.DeleteEntries(item); 275 | let err = await App.GetError(); 276 | 277 | if (typeof callback !== "undefined") { 278 | callback(err); 279 | } 280 | } 281 | } 282 | }, 283 | getDirList: async function (dir) { 284 | // 285 | // A directory list is provided giving an array of entry object. Each 286 | // entry object has: 287 | // name The name of the file 288 | // type The type of the entry: a file - 0, a directory - 1, a link - 2 289 | // fileSystem The current file system object 290 | // dir The directory of the file 291 | // datetime The creation datetime of the file 292 | // size The integer size of the file in 1kb (Directories and links have zero size) 293 | // selected Boolean true is selected, false is not selected 294 | // 295 | let entries = []; 296 | if (typeof dir !== "string") { 297 | if (typeof dir.dir !== "undefined" && typeof dir.name !== "undefined") { 298 | dir = await this.appendPath(dir.dir, dir.name); 299 | } 300 | } 301 | dir = await this.normalize(dir); 302 | if (await this.dirExists(dir)) { 303 | // 304 | // read directory 305 | // 306 | let items = await this.readDir(dir); 307 | for (let i = 0; i < items.length; i++) { 308 | if (typeof items[i] !== "undefined") { 309 | let newEntry; 310 | newEntry = { 311 | name: items[i].Name, 312 | dir: items[i].Dir, 313 | ext: items[i].Extension, 314 | fileSystemType: "macOS", 315 | fileSystem: this, 316 | selected: false, 317 | datetime: items[i].Modtime, 318 | type: items[i].IsDir ? 1 : 0, 319 | size: items[i].Size, 320 | mode: items[i].Mode, 321 | link: items[i].Link ? 1 : 0, 322 | }; 323 | entries.push(newEntry); 324 | } 325 | } 326 | 327 | // 328 | // filter out the entries. 329 | // 330 | entries = entries.filter(this.filterFunction); 331 | 332 | // 333 | // Sort the entries. 334 | // 335 | if (this.dirFirst) { 336 | let dirEntries = entries.filter((item) => item.type === 1); 337 | let fileEntries = entries.filter((item) => item.type === 0); 338 | dirEntries.sort(this.sortFunction); 339 | fileEntries.sort(this.sortFunction); 340 | entries = [...dirEntries, ...fileEntries]; 341 | } else { 342 | entries.sort(this.sortFunction); 343 | } 344 | } 345 | // 346 | // Return the result. 347 | // 348 | return entries; 349 | }, 350 | defaultFilter: function (item) { 351 | return item.name[0] !== "." && !item.name.includes("Icon"); 352 | }, 353 | allFilter: function (item) { 354 | // 355 | // Still, don't show the Icon and DS_Store files on macOS. 356 | // 357 | return !item.name.includes("Icon") && !item.name.includes(".DS_Store"); 358 | }, 359 | alphaSort: function (item1, item2) { 360 | const a = item1.name.toLowerCase(); 361 | const b = item2.name.toLowerCase(); 362 | return a === b ? 0 : a > b ? 1 : -1; 363 | }, 364 | openFile: async function (pdir, file) { 365 | // 366 | // For macOS, open with the open command line command. 367 | // 368 | this.runCommandLine(`${this.openCommand} '${pdir}/${file}'`); 369 | }, 370 | openFileWithProgram: async function (prog, file) { 371 | // 372 | // For macOS, open with the open command line command. 373 | // 374 | this.runCommandLine(`${this.openCommand} -a ${prog} '${file}'`); 375 | }, 376 | openInTerminal: async function (prog, file) { 377 | // 378 | // Run the terminal Script. 379 | // 380 | if (this.type === "macos") { 381 | this.runCommandLine( 382 | `osascript '${this.terminalScript}' '${prog}' '${file}'`, 383 | [], 384 | (err, stdout) => {}, 385 | ".", 386 | ); 387 | } else { 388 | // 389 | // TODO: Not sure? 390 | // 391 | } 392 | }, 393 | getConfig: async function () { 394 | if (this.config === null) { 395 | // 396 | // Create the minimum config and then add to the path as needed. The path from process 397 | // will not contain everything the user would have in his shell. 398 | // 399 | this.config = { 400 | env: null, 401 | shell: "", 402 | useTrash: true, 403 | maxSearchDepth: 100, 404 | }; 405 | 406 | // 407 | // Copy the environment from the process. Get the environment. 408 | // 409 | let env = await App.GetEnvironment(); 410 | let newEnv = {}; 411 | env.map((item) => { 412 | let parts = item.split("="); 413 | newEnv[parts[0]] = parts[1]; 414 | }); 415 | this.config.env = newEnv; 416 | 417 | // 418 | // Add directories that the user's system should have. 419 | // 420 | if (this.local) { 421 | if (await this.dirExists(`${this.localHomeDir}/bin`)) { 422 | this.config.env.PATH = `${this.localHomeDir}/bin:${this.config.env.PATH}`; 423 | } 424 | } 425 | if (await this.dirExists("/opt/homebrew/bin")) { 426 | this.config.env.PATH = `/opt/homebrew/bin:${this.config.env.PATH}`; 427 | } 428 | if (await this.dirExists("/usr/local/bin")) { 429 | this.config.env.PATH = `/usr/local/bin:${this.config.env.PATH}`; 430 | } 431 | if (await this.dirExists(`${this.localHomeDir}/.cargo/bin`)) { 432 | this.config.env.PATH += `:${this.localHomeDir}/.cargo/bin`; 433 | } 434 | if (await this.dirExists(`${this.localHomeDir}/go/bin`)) { 435 | this.config.env.PATH += `:${this.localHomeDir}/go/bin`; 436 | } 437 | 438 | // 439 | // Set the defaults for everything else. 440 | // 441 | this.config.shell = this.config.env.SHELL; 442 | this.config.useTrash = true; 443 | } 444 | return this.config; 445 | }, 446 | runCommandLine: async function (line, rEnv, callback, dir) { 447 | // 448 | // Get the environment to use. 449 | // 450 | let cnfg = await this.getConfig(); 451 | let nEnv = { ...cnfg.env }; 452 | if (typeof rEnv !== "undefined") { 453 | nEnv = { ...nEnv, ...rEnv }; 454 | } 455 | 456 | // 457 | // Fix the environment from a map to an array of strings. 458 | // 459 | let penv = []; 460 | for (const key in nEnv) { 461 | penv.push(`${key}=${nEnv[key]}`); 462 | } 463 | 464 | // 465 | // Make sure dir has a value. 466 | // 467 | if (typeof dir === "undefined") dir = "."; 468 | 469 | // 470 | // Run the command line in a shell. #TODO make the shell configurable. 471 | // 472 | let args = [this.shell, "-c", line]; 473 | let cmd = this.shell; 474 | 475 | // 476 | // Run the command line. 477 | // 478 | let result = await App.RunCommandLine(cmd, args, penv, dir); 479 | let err = await App.GetError(); 480 | 481 | // 482 | // If callback is given, call it with the results. 483 | // 484 | if (typeof callback !== "undefined") { 485 | callback(err, result); 486 | } 487 | }, 488 | appendPath: async function (dir, name) { 489 | // dir can be an entry or a path string. name is always a string. 490 | // 491 | let path = dir; 492 | if (typeof dir !== "string") { 493 | let tmpdir = await App.appendPath(dir.dir, dir.name); 494 | path = await App.AppendPath(tmpdir, name); 495 | } else { 496 | path = await App.AppendPath(dir, name); 497 | } 498 | return path; 499 | }, 500 | readFile: async function (file) { 501 | let contents = ""; 502 | if (typeof file.name !== "undefined") { 503 | file = await this.appendPath(file.dir, file.name); 504 | } 505 | 506 | // 507 | // Read the file from the backend. 508 | // 509 | contents = await App.ReadFile(file); 510 | 511 | // 512 | // Return what is read. 513 | // 514 | return contents; 515 | }, 516 | writeFile: async function (file, data) { 517 | if (typeof file === "object") { 518 | file = await this.appendPath(file.dir, file.name); 519 | } 520 | // 521 | // Write the file to the os. 522 | // 523 | await App.WriteFile(file, data); 524 | }, 525 | renameEntry: async function (oldE, newE) { 526 | let fromName = oldE; 527 | if (typeof oldE.dir !== "undefined") { 528 | fromName = await this.appendPath(oldE.dir, oldE.name); 529 | } 530 | let toName = newE; 531 | if (typeof newE.dir !== "undefined") { 532 | toName = await this.appendPath(newE.dir, newE.name); 533 | } 534 | // 535 | // Rename the file or directory. 536 | // 537 | await App.RenameEntry(fromName, toName); 538 | }, 539 | createFile: async function (file) { 540 | let fnm = file; 541 | if (typeof file.dir !== "undefined") { 542 | let fnm = await this.appendPath(file.dir, file.name); 543 | } 544 | // 545 | // Create the file with zero contents. 546 | // 547 | await App.MakeFile(fnm); 548 | }, 549 | createDir: async function (dir) { 550 | let dnm = dir; 551 | if (typeof dir.dir !== "undefined") { 552 | dnm = await this.appendPath(dir.dir, dir.name); 553 | } 554 | // 555 | // make the directory. 556 | // 557 | await App.MakeDir(dnm); 558 | }, 559 | loadJavaScript: async function (file) { 560 | let result = ""; 561 | 562 | // 563 | // Read in the JavaScript file and run it. It should return an extension object. 564 | // 565 | let jfile = await this.readFile(file); 566 | jfile = jfile.toString(); 567 | try { 568 | let scriptFunction = new Function("", jfile); 569 | result = scriptFunction(); 570 | } catch (e) { 571 | console.log(e); 572 | this.lastError = e.toString(); 573 | result = null; 574 | } 575 | return result; 576 | }, 577 | searchdir: async function (pat, dir, numEntries, returnFunction) { 578 | try { 579 | if (dir === "") dir = this.path; 580 | if (pat !== "") { 581 | let data = await App.SearchMatchingDirectories(dir, pat, numEntries); 582 | returnFunction(data); 583 | } 584 | } catch (e) { 585 | console.log(e); 586 | this.lastError = e.toString(); 587 | returnFunction(""); 588 | } 589 | }, 590 | splitFilePath: async function (filePath) { 591 | const parts = await App.SplitFile(filePath); 592 | return parts; 593 | }, 594 | getClipBoard: async function () { 595 | result = await App.GetClip(); 596 | return result; 597 | }, 598 | setClipBoard: async function (msg) { 599 | await App.SetClip(msg); 600 | }, 601 | setDirWatch: async function (path, pane) { 602 | if (pane === "left") { 603 | App.SetLeftDirWatch(path); 604 | } else { 605 | App.SetRightDirWatch(path); 606 | } 607 | }, 608 | }; 609 | 610 | export default OS; 611 | -------------------------------------------------------------------------------- /frontend/src/modules/commands.js: -------------------------------------------------------------------------------- 1 | // 2 | // File: commands.js 3 | // 4 | // Description: This file contains the commands object for dealing with commands that 5 | // ModalFileManager uses. 6 | // 7 | 8 | const 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 | altname: item.altname, 49 | description: item.description, 50 | }; 51 | }); 52 | }, 53 | removeCommand: function (com) { 54 | commands.commandList = commands.commandList.filter( 55 | (item) => item.name !== com, 56 | ); 57 | }, 58 | }; 59 | 60 | export default commands; 61 | -------------------------------------------------------------------------------- /frontend/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 | const extensions = { 8 | fileSystems: null, 9 | commands: null, 10 | extCommandList: [], 11 | extensionList: [], 12 | extensionDir: '', 13 | localFS: null, 14 | config: null, 15 | load: async 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 | let items = await extensions.localFS.readDir(extensions.extensionDir); 38 | for (let i = 0; i < items.length; i++) { 39 | const extsDir = await extensions.localFS.appendPath(extensions.extensionDir, items[i].Name); 40 | try { 41 | // 42 | // an extension directory. load it! 43 | // 44 | const paramfile = await extensions.localFS.appendPath(extsDir, 'package.json'); 45 | if (await extensions.localFS.fileExists(paramfile)) { 46 | let parms = await extensions.localFS.readFile(paramfile); 47 | parms = JSON.parse(parms.toString()); 48 | if (typeof parms.mfmextension !== 'undefined') { 49 | const extfile = await extensions.localFS.appendPath(extsDir, parms.mfmextension.main); 50 | const extension = await extensions.localFS.loadJavaScript(extfile); 51 | if (extension !== null) { 52 | extensions.addExtension(parms.mfmextension.name, parms.mfmextension.description, extension, parms.mfmextension.type, parms.mfmextension.github); 53 | } else { 54 | console.log(`extension ${items[i].Name} didn't load.`); 55 | } 56 | } else { 57 | console.log("extension: " + extsDir + " isn't configured correctly."); 58 | } 59 | } 60 | } catch (e) { 61 | // 62 | // There was a problem getting the stats. Therefore, it's not a file or 63 | // directory we need. 64 | // 65 | console.log(e); 66 | } 67 | } 68 | }, 69 | setExtensionDir: function(dir) { 70 | extensions.extensionDir = dir; 71 | }, 72 | getExtensionDir: function() { 73 | return extensions.extensionDir; 74 | }, 75 | getConfigDir: async function() { 76 | return await extensions.localFS.getConfigDir(); 77 | }, 78 | setFileSystems: function(fs) { 79 | extensions.fileSystems = fs; 80 | }, 81 | getFileSystems: function() { 82 | return extensions.fileSystems; 83 | }, 84 | setCommands: function(com) { 85 | extensions.commands = com; 86 | }, 87 | getCommands: function() { 88 | return extensions.commands; 89 | }, 90 | getConfig: function() { 91 | return extensions.config; 92 | }, 93 | setConfig: function(value) { 94 | extensions.config = value; 95 | }, 96 | addExtCommand: function(name, description, extCommand) { 97 | // 98 | // Add it to the stack. 99 | // 100 | extensions.extCommandList.push({ 101 | name: name, 102 | description: description, 103 | command: extCommand 104 | }); 105 | }, 106 | listExtCommands: function() { 107 | return extensions.extCommandList.map(item => { 108 | return { name: item.name, description: item.description }; 109 | }); 110 | }, 111 | getExtCommand: function(name) { 112 | return (extensions.extCommandList.find(item => item.name === name)); 113 | }, 114 | addExtension: function(name, description, extension, type, github) { 115 | // 116 | // Add it to the stack. 117 | // 118 | extensions.extensionList.push({ 119 | name: name, 120 | description: description, 121 | extension: extension, 122 | type: type, 123 | github: github 124 | }); 125 | }, 126 | unloadExtensions: function() { 127 | if (extensions.extensionList !== null) { 128 | extensions.extensionList.forEach(item => { 129 | if ((typeof item.extension !== 'undefined') && (typeof item.extension.unload !== 'undefined')) item.extension.unload(); 130 | }); 131 | } 132 | }, 133 | init: function() { 134 | if (extensions.extensionList !== null) { 135 | extensions.extensionList.forEach(item => { 136 | if (typeof item.extension !== 'undefined') item.extension.init(extensions); 137 | }); 138 | } 139 | }, 140 | installKeyMaps: function() { 141 | if (extensions.extensionList !== null) { 142 | extensions.extensionList.forEach(item => { 143 | if (typeof item.extension !== 'undefined') { 144 | item.extension.installKeyMaps(); 145 | } 146 | }); 147 | } 148 | }, 149 | getExtension: function(ext) { 150 | return extensions.extensionList.find((item) => { item.name == ext }); 151 | }, 152 | listExtensions: function() { 153 | return extensions.extensionList.map(item => { 154 | return { 155 | name: item.name, 156 | description: item.description 157 | }; 158 | }) 159 | }, 160 | removeExtension: function(ext) { 161 | let exten = extensions.extensionList.filter(item => item.name === ext)[0]; 162 | exten.unload(); 163 | extensions.extensionList = extensions.extensionList.filter(item => item.name != ext); 164 | }, 165 | getLocalFS: function() { 166 | return extensions.localFS; 167 | } 168 | } 169 | 170 | export default extensions; 171 | 172 | -------------------------------------------------------------------------------- /frontend/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 | -------------------------------------------------------------------------------- /frontend/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 | -------------------------------------------------------------------------------- /frontend/src/stores/altKey.js: -------------------------------------------------------------------------------- 1 | import { writable } from 'svelte/store'; 2 | 3 | export const altKey = writable(false); 4 | -------------------------------------------------------------------------------- /frontend/src/stores/config.js: -------------------------------------------------------------------------------- 1 | import { writable } from 'svelte/store'; 2 | 3 | export const config = writable(null); 4 | 5 | -------------------------------------------------------------------------------- /frontend/src/stores/ctrlKey.js: -------------------------------------------------------------------------------- 1 | import { writable } from 'svelte/store'; 2 | 3 | export const ctrlKey = writable(false); 4 | -------------------------------------------------------------------------------- /frontend/src/stores/currentCursor.js: -------------------------------------------------------------------------------- 1 | import { writable } from 'svelte/store'; 2 | 3 | export const currentCursor = writable({ 4 | pane: 'right', 5 | entry: {} 6 | }); 7 | 8 | -------------------------------------------------------------------------------- /frontend/src/stores/currentLeftFile.js: -------------------------------------------------------------------------------- 1 | import { writable } from 'svelte/store'; 2 | 3 | export const currentLeftFile = writable({ 4 | entry: {} 5 | }); 6 | 7 | -------------------------------------------------------------------------------- /frontend/src/stores/currentRightFile.js: -------------------------------------------------------------------------------- 1 | import { writable } from 'svelte/store'; 2 | 3 | export const currentRightFile = writable({ 4 | entry: {} 5 | }); 6 | 7 | -------------------------------------------------------------------------------- /frontend/src/stores/dirHistory.js: -------------------------------------------------------------------------------- 1 | import { writable } from "svelte/store"; 2 | 3 | export const dirHistory = writable(null); 4 | -------------------------------------------------------------------------------- /frontend/src/stores/directoryListeners.js: -------------------------------------------------------------------------------- 1 | import { writable } from 'svelte/store'; 2 | 3 | export const directoryListeners = writable([]); 4 | -------------------------------------------------------------------------------- /frontend/src/stores/extraPanel.js: -------------------------------------------------------------------------------- 1 | import { writable } from 'svelte/store'; 2 | 3 | export const extraPanel = writable([]); 4 | -------------------------------------------------------------------------------- /frontend/src/stores/inputState.js: -------------------------------------------------------------------------------- 1 | import { writable } from 'svelte/store'; 2 | 3 | export const inputState = writable('normal'); 4 | 5 | -------------------------------------------------------------------------------- /frontend/src/stores/key.js: -------------------------------------------------------------------------------- 1 | import { writable } from 'svelte/store'; 2 | 3 | export const key = writable(''); 4 | -------------------------------------------------------------------------------- /frontend/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 | -------------------------------------------------------------------------------- /frontend/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 | -------------------------------------------------------------------------------- /frontend/src/stores/leftEntries.js: -------------------------------------------------------------------------------- 1 | import { writable } from "svelte/store"; 2 | 3 | export const leftEntries = writable({}); 4 | -------------------------------------------------------------------------------- /frontend/src/stores/metaKey.js: -------------------------------------------------------------------------------- 1 | import { writable } from 'svelte/store'; 2 | 3 | export const metaKey = writable(false); 4 | -------------------------------------------------------------------------------- /frontend/src/stores/processKey.js: -------------------------------------------------------------------------------- 1 | import { writable } from 'svelte/store'; 2 | 3 | export const processKey = writable(null); 4 | -------------------------------------------------------------------------------- /frontend/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 | -------------------------------------------------------------------------------- /frontend/src/stores/rightEntries.js: -------------------------------------------------------------------------------- 1 | import { writable } from "svelte/store"; 2 | 3 | export const rightEntries = writable({}); 4 | -------------------------------------------------------------------------------- /frontend/src/stores/saved.js: -------------------------------------------------------------------------------- 1 | import { writable } from 'svelte/store'; 2 | 3 | export const saved = writable({ 4 | qs: null, 5 | lockqs: false 6 | }); 7 | 8 | -------------------------------------------------------------------------------- /frontend/src/stores/shiftKey.js: -------------------------------------------------------------------------------- 1 | import { writable } from 'svelte/store'; 2 | 3 | export const shiftKey = writable(false); 4 | -------------------------------------------------------------------------------- /frontend/src/stores/stateMapColors.js: -------------------------------------------------------------------------------- 1 | import { writable } from 'svelte/store'; 2 | 3 | export const stateMapColors = writable([]); 4 | 5 | -------------------------------------------------------------------------------- /frontend/src/stores/theme.js: -------------------------------------------------------------------------------- 1 | import { writable } from 'svelte/store'; 2 | 3 | export const theme = writable({ 4 | }); 5 | 6 | -------------------------------------------------------------------------------- /frontend/src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | -------------------------------------------------------------------------------- /frontend/svelte.config.js: -------------------------------------------------------------------------------- 1 | import { vitePreprocess } from '@sveltejs/vite-plugin-svelte' 2 | 3 | export default { 4 | // Consult https://svelte.dev/docs#compile-time-svelte-preprocess 5 | // for more information about preprocessors 6 | preprocess: vitePreprocess(), 7 | } 8 | -------------------------------------------------------------------------------- /frontend/vite.config.js: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite' 2 | import { svelte } from '@sveltejs/vite-plugin-svelte' 3 | 4 | // https://vite.dev/config/ 5 | export default defineConfig({ 6 | plugins: [svelte()], 7 | }) 8 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module changeme 2 | 3 | go 1.23.0 4 | 5 | toolchain go1.24.1 6 | 7 | require ( 8 | github.com/atotto/clipboard v0.1.4 9 | github.com/gin-contrib/static v1.1.5 10 | github.com/gin-gonic/gin v1.10.0 11 | github.com/go-git/go-git/v5 v5.15.0 12 | github.com/google/go-github/v49 v49.1.0 13 | github.com/otiai10/copy v1.14.1 14 | github.com/wailsapp/wails/v2 v2.10.1 15 | ) 16 | 17 | require ( 18 | dario.cat/mergo v1.0.1 // indirect 19 | github.com/Microsoft/go-winio v0.6.2 // indirect 20 | github.com/ProtonMail/go-crypto v1.2.0 // indirect 21 | github.com/bep/debounce v1.2.1 // indirect 22 | github.com/bytedance/sonic v1.13.2 // indirect 23 | github.com/bytedance/sonic/loader v0.2.4 // indirect 24 | github.com/cloudflare/circl v1.6.1 // indirect 25 | github.com/cloudwego/base64x v0.1.5 // indirect 26 | github.com/cyphar/filepath-securejoin v0.4.1 // indirect 27 | github.com/emirpasic/gods v1.18.1 // indirect 28 | github.com/gabriel-vasile/mimetype v1.4.8 // indirect 29 | github.com/gin-contrib/sse v1.1.0 // indirect 30 | github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect 31 | github.com/go-git/go-billy/v5 v5.6.2 // indirect 32 | github.com/go-ole/go-ole v1.3.0 // indirect 33 | github.com/go-playground/locales v0.14.1 // indirect 34 | github.com/go-playground/universal-translator v0.18.1 // indirect 35 | github.com/go-playground/validator/v10 v10.26.0 // indirect 36 | github.com/goccy/go-json v0.10.5 // indirect 37 | github.com/godbus/dbus/v5 v5.1.0 // indirect 38 | github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect 39 | github.com/google/go-querystring v1.1.0 // indirect 40 | github.com/google/uuid v1.6.0 // indirect 41 | github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect 42 | github.com/jchv/go-winloader v0.0.0-20250406163304-c1995be93bd1 // indirect 43 | github.com/json-iterator/go v1.1.12 // indirect 44 | github.com/kevinburke/ssh_config v1.2.0 // indirect 45 | github.com/klauspost/cpuid/v2 v2.2.10 // indirect 46 | github.com/labstack/echo/v4 v4.13.3 // indirect 47 | github.com/labstack/gommon v0.4.2 // indirect 48 | github.com/leaanthony/go-ansi-parser v1.6.1 // indirect 49 | github.com/leaanthony/gosod v1.0.4 // indirect 50 | github.com/leaanthony/slicer v1.6.0 // indirect 51 | github.com/leaanthony/u v1.1.1 // indirect 52 | github.com/leodido/go-urn v1.4.0 // indirect 53 | github.com/mattn/go-colorable v0.1.14 // indirect 54 | github.com/mattn/go-isatty v0.0.20 // indirect 55 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 56 | github.com/modern-go/reflect2 v1.0.2 // indirect 57 | github.com/otiai10/mint v1.6.3 // indirect 58 | github.com/pelletier/go-toml/v2 v2.2.4 // indirect 59 | github.com/pjbgf/sha1cd v0.3.2 // indirect 60 | github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect 61 | github.com/pkg/errors v0.9.1 // indirect 62 | github.com/rivo/uniseg v0.4.7 // indirect 63 | github.com/samber/lo v1.49.1 // indirect 64 | github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // indirect 65 | github.com/skeema/knownhosts v1.3.1 // indirect 66 | github.com/tkrajina/go-reflector v0.5.8 // indirect 67 | github.com/twitchyliquid64/golang-asm v0.15.1 // indirect 68 | github.com/ugorji/go/codec v1.2.12 // indirect 69 | github.com/valyala/bytebufferpool v1.0.0 // indirect 70 | github.com/valyala/fasttemplate v1.2.2 // indirect 71 | github.com/wailsapp/go-webview2 v1.0.21 // indirect 72 | github.com/wailsapp/mimetype v1.4.1 // indirect 73 | github.com/xanzy/ssh-agent v0.3.3 // indirect 74 | golang.org/x/arch v0.16.0 // indirect 75 | golang.org/x/crypto v0.37.0 // indirect 76 | golang.org/x/net v0.39.0 // indirect 77 | golang.org/x/sync v0.13.0 // indirect 78 | golang.org/x/sys v0.32.0 // indirect 79 | golang.org/x/text v0.24.0 // indirect 80 | google.golang.org/protobuf v1.36.6 // indirect 81 | gopkg.in/warnings.v0 v0.1.2 // indirect 82 | gopkg.in/yaml.v3 v3.0.1 // indirect 83 | ) 84 | -------------------------------------------------------------------------------- /jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "plugins": [ 4 | { 5 | "name": "typescript-svelte-plugin" 6 | } 7 | ] 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "embed" 5 | "fmt" 6 | "log" 7 | "os" 8 | 9 | "github.com/wailsapp/wails/v2" 10 | "github.com/wailsapp/wails/v2/pkg/logger" 11 | "github.com/wailsapp/wails/v2/pkg/options" 12 | "github.com/wailsapp/wails/v2/pkg/options/mac" 13 | "github.com/wailsapp/wails/v2/pkg/options/windows" 14 | ) 15 | 16 | //go:embed frontend/dist 17 | var assets embed.FS 18 | 19 | //go:embed build/appicon.png 20 | var icon []byte 21 | 22 | func main() { 23 | var commands []string 24 | commands = make([]string, 0) 25 | 26 | if len(os.Args) > 1 { 27 | // 28 | // Process the command line arguments. 29 | // 30 | var firstDir = true 31 | for i := 1; i < len(os.Args); i++ { 32 | if os.Args[i][0] != '-' { 33 | if firstDir { 34 | commands = append(commands, fmt.Sprintf("setLeftDir('%s')", os.Args[i])) 35 | firstDir = false 36 | } else { 37 | commands = append(commands, fmt.Sprintf("setRightDir('%s')", os.Args[i])) 38 | } 39 | } 40 | } 41 | } 42 | 43 | // Create an instance of the app structure 44 | a := NewApp() 45 | a.Commands = commands 46 | 47 | // Create application with options 48 | err := wails.Run(&options.App{ 49 | Title: "Modal File Manager", 50 | Width: 800, 51 | Height: 400, 52 | MinWidth: 100, 53 | MinHeight: 100, 54 | MaxWidth: 1280, 55 | MaxHeight: 740, 56 | DisableResize: false, 57 | Fullscreen: false, 58 | Frameless: false, 59 | StartHidden: false, 60 | HideWindowOnClose: false, 61 | BackgroundColour: &options.RGBA{R: 33, G: 37, B: 43, A: 255}, 62 | Assets: assets, 63 | LogLevel: logger.DEBUG, 64 | OnStartup: a.startup, 65 | OnDomReady: a.domReady, 66 | OnShutdown: a.shutdown, 67 | CSSDragProperty: "--wails-draggable", 68 | CSSDragValue: "drag", 69 | Bind: []interface{}{ 70 | a, 71 | }, 72 | // Windows platform specific options 73 | Windows: &windows.Options{ 74 | WebviewIsTransparent: false, 75 | WindowIsTranslucent: false, 76 | DisableWindowIcon: false, 77 | }, 78 | Mac: &mac.Options{ 79 | TitleBar: mac.TitleBarHiddenInset(), 80 | Appearance: mac.NSAppearanceNameDarkAqua, 81 | WebviewIsTransparent: true, 82 | WindowIsTranslucent: true, 83 | About: &mac.AboutInfo{ 84 | Title: "Modal File Manager", 85 | Message: "© 2022 Richard Guay ", 86 | Icon: icon, 87 | }, 88 | }, 89 | }) 90 | 91 | if err != nil { 92 | log.Fatal(err) 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /maskfile.md: -------------------------------------------------------------------------------- 1 | ## build 2 | 3 | ```sh 4 | rm -R build 5 | mkdir build 6 | cp picts/appicon.png build 7 | cd frontend 8 | yarn run build 9 | cd .. 10 | 11 | ## wails build --platform "darwin/universal" 12 | wails build -s 13 | ``` 14 | -------------------------------------------------------------------------------- /picts/appicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raguay/ModalFileManager/9e62848bd579b0d9b1a41227fc25eea0219ce520/picts/appicon.png -------------------------------------------------------------------------------- /picts/sponsor.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | 5 |
6 |
7 |
8 | -------------------------------------------------------------------------------- /server.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "os" 7 | "strings" 8 | 9 | "github.com/gin-contrib/static" 10 | "github.com/gin-gonic/gin" 11 | ) 12 | 13 | // Function: Security 14 | // 15 | // Description: This function supplies a middleware for the gin 16 | // 17 | // server to check if the request is allowed or not. 18 | // This is to help keep the server safe from hacking. 19 | func Security() gin.HandlerFunc { 20 | return func(c *gin.Context) { 21 | clientIP := c.ClientIP() 22 | if strings.HasPrefix(clientIP, "127.0.0.1") { 23 | // 24 | // It's okay to serve. 25 | // 26 | c.Next() 27 | } else { 28 | // 29 | // Cancel the request. 30 | // 31 | c.Abort() 32 | } 33 | } 34 | } 35 | 36 | func backend(app *App, ctx context.Context) { 37 | // 38 | // This will have the web server backend for EmailIt 39 | // 40 | r := gin.Default() 41 | r.Use(gin.Recovery()) 42 | r.Use(Security()) 43 | 44 | // 45 | // Get the user's home directory for sending files from. 46 | // 47 | hmdir, _ := os.UserHomeDir() 48 | 49 | // 50 | // this sets up a static server for the user's home directory. The 51 | // frontend will use it to get pictures and such. 52 | // 53 | r.Use(static.Serve("/filesys", static.LocalFile(hmdir, false))) 54 | 55 | // 56 | // Run the server. 57 | // 58 | err := r.Run(":9998") 59 | if err != nil { 60 | fmt.Print("\n Server error:\n", err.Error()) 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /wails.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ModalFileManager", 3 | "frontend:build": "bun run build", 4 | "frontend:install": "bun install", 5 | "frontend:dev": "", 6 | "wailsjsdir": "./frontend/dist/", 7 | "version": "2", 8 | "Path": "", 9 | "BuildDir": "", 10 | "outputfilename": "ModalFileManager", 11 | "OutputType": "", 12 | "Platform": "darwin/universal", 13 | "bundle_id": "com.customct.wails.ModalFileManager", 14 | "Author": { 15 | "name": "Richard Guay", 16 | "email": "raguay@customct.com" 17 | }, 18 | "debounceMS": 100, 19 | "devserverurl": "http://localhost:34115", 20 | "appargs": "" 21 | } 22 | --------------------------------------------------------------------------------