├── .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 |
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 |
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 |
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 | Extension Name |
243 | |
244 | |
245 |
246 |
247 |
248 | {#each extensionList as item}
249 |
250 |
251 | {item.name}
252 | |
253 |
254 |
266 | |
267 |
268 |
276 | |
277 |
278 | {/each}
279 |
280 |
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 |
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 |
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 |
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 | Name |
143 | Value |
144 |
145 |
146 |
147 | {#each Object.entries($theme) as kv}
148 |
154 | {/each}
155 |
156 |
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 |
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 |
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 |
--------------------------------------------------------------------------------