@danixl30/file-explorer-cli 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- package/LICENSE +21 -0
- package/dist/alerts/AlertContextProvider.js +13 -0
- package/dist/alerts/components/AlertDisplay.js +19 -0
- package/dist/alerts/logic/alert.logic.js +16 -0
- package/dist/app.js +27 -0
- package/dist/bookmark/Bookmark.js +123 -0
- package/dist/bookmark/components/BookmarkList.js +47 -0
- package/dist/bookmark/context/BookmarkPaneContext.js +9 -0
- package/dist/bookmark/events/add.item.js +8 -0
- package/dist/bookmark/implementations/clean.bookmarks.js +6 -0
- package/dist/bookmark/implementations/get.all.bookmarks.js +2 -0
- package/dist/bookmark/implementations/save.bookmarks.js +5 -0
- package/dist/bookmark/logic/bookmark.manager.js +25 -0
- package/dist/bookmark/services/clean.bookmarks.js +1 -0
- package/dist/bookmark/services/get.all.bookmarks.js +1 -0
- package/dist/bookmark/services/write.bookmarks.js +1 -0
- package/dist/bookmark/types/bookmark.js +1 -0
- package/dist/cli.js +31 -0
- package/dist/clipboard/ClipboardProvider.js +8 -0
- package/dist/clipboard/logic/clipboard-logic.js +10 -0
- package/dist/core/application/event-handler/event-handler.js +1 -0
- package/dist/core/application/event-handler/listener/event-listener.js +1 -0
- package/dist/core/application/event-handler/types/event.js +1 -0
- package/dist/core/application/event-handler/types/subscription.js +1 -0
- package/dist/core/application/id-generator/id.generator.js +1 -0
- package/dist/core/application/init-layout/init-layout.js +1 -0
- package/dist/core/application/input-manager/input-manager.js +1 -0
- package/dist/core/application/input-manager/types/input-manager-result.js +1 -0
- package/dist/core/application/on-init/on-init.js +1 -0
- package/dist/core/application/on-init-job/lazy/on-init-job-lazy.js +1 -0
- package/dist/core/application/on-init-job/on-init-job.js +1 -0
- package/dist/core/application/pagination-manager/pagination-manager.js +1 -0
- package/dist/core/application/pagination-manager/types/pagination-result.js +1 -0
- package/dist/core/application/service/application-service.js +1 -0
- package/dist/core/application/state/state-factory.js +1 -0
- package/dist/core/application/state/state-provider.js +1 -0
- package/dist/core/application/state-observers/state-observer.js +1 -0
- package/dist/core/application/timer/sleep/sleep.js +1 -0
- package/dist/core/application/timer/timer.js +19 -0
- package/dist/core/application/value-provider/value-provider.js +1 -0
- package/dist/core/infraestructure/db/bookmark.db.js +4 -0
- package/dist/core/infraestructure/event-handler/context/EventProvider.js +9 -0
- package/dist/core/infraestructure/event-handler/listener/event-listener-factory.js +8 -0
- package/dist/core/infraestructure/event-handler/useEventHadler.js +27 -0
- package/dist/core/infraestructure/initLayout/useLayoutEffectOnInit.js +4 -0
- package/dist/core/infraestructure/input-manager/useInputManager.js +14 -0
- package/dist/core/infraestructure/on-init/useEffectOnInit.js +9 -0
- package/dist/core/infraestructure/on-init-job/nativeOnInitJob.js +37 -0
- package/dist/core/infraestructure/on-init-job/nativeOnInitJobLazy.js +32 -0
- package/dist/core/infraestructure/pagination-manager/data-transforms/infinite.js +1 -0
- package/dist/core/infraestructure/pagination-manager/data-transforms/normal.js +1 -0
- package/dist/core/infraestructure/pagination-manager/usePaginationManager.js +59 -0
- package/dist/core/infraestructure/state/useRefStateProvider.js +39 -0
- package/dist/core/infraestructure/state/useStateProvider.js +35 -0
- package/dist/core/infraestructure/state-observer/useEffectStateObserver.js +16 -0
- package/dist/core/infraestructure/timer/sleep.js +2 -0
- package/dist/core/infraestructure/uuid/uuid.generator.js +2 -0
- package/dist/core/infraestructure/value-provider/useRefValueProvider.js +12 -0
- package/dist/core/utils/argument.type.js +1 -0
- package/dist/core/utils/optional.js +1 -0
- package/dist/file-display/FileDisplay.js +456 -0
- package/dist/file-display/components/Loading.js +4 -0
- package/dist/file-display/components/NodeDetails.js +50 -0
- package/dist/file-display/components/NodeList.js +61 -0
- package/dist/file-display/events/node.written.js +7 -0
- package/dist/file-display/hooks/useResize.js +24 -0
- package/dist/file-display/implementation/create.dir.js +7 -0
- package/dist/file-display/implementation/delete.js +9 -0
- package/dist/file-display/implementation/execute.command.async.js +6 -0
- package/dist/file-display/implementation/execute.command.sync.js +9 -0
- package/dist/file-display/implementation/execute.file.js +4 -0
- package/dist/file-display/implementation/filter.glob.js +4 -0
- package/dist/file-display/implementation/get.all.files.js +44 -0
- package/dist/file-display/implementation/get.directory.js +8 -0
- package/dist/file-display/implementation/get.upper.directory.js +17 -0
- package/dist/file-display/implementation/move.trash.js +9 -0
- package/dist/file-display/implementation/node.details.js +79 -0
- package/dist/file-display/implementation/paste.operation.js +40 -0
- package/dist/file-display/implementation/rename.js +6 -0
- package/dist/file-display/logic/file.manager.js +107 -0
- package/dist/file-display/logic/history.manager.js +47 -0
- package/dist/file-display/logic/node.detail.js +8 -0
- package/dist/file-display/logic/operations.manager.js +103 -0
- package/dist/file-display/services/create.dir.js +1 -0
- package/dist/file-display/services/delete.js +1 -0
- package/dist/file-display/services/execute.command.async.js +1 -0
- package/dist/file-display/services/execute.command.sync.js +1 -0
- package/dist/file-display/services/execute.file.js +1 -0
- package/dist/file-display/services/filter.glob.js +1 -0
- package/dist/file-display/services/get.all.files.js +1 -0
- package/dist/file-display/services/get.directory.js +1 -0
- package/dist/file-display/services/get.upper.directory.js +1 -0
- package/dist/file-display/services/move.trash.js +1 -0
- package/dist/file-display/services/node.details.js +1 -0
- package/dist/file-display/services/paste.operation.js +1 -0
- package/dist/file-display/services/remane.js +1 -0
- package/dist/file-display/types/node.details.js +1 -0
- package/dist/file-display/types/node.js +1 -0
- package/dist/file-display/utils/icon.parser.js +144 -0
- package/dist/input-capture/InputCapureWrapper.js +10 -0
- package/dist/input-capture/InputContextCapture.js +26 -0
- package/dist/tabs/TabsContext.js +17 -0
- package/dist/tabs/events/update.tab.data.js +8 -0
- package/dist/tabs/events/update.tab.selection.js +8 -0
- package/dist/tabs/logic/tabsManagerLogic.js +58 -0
- package/dist/tabs/wrapper/tabs.wrapper.js +38 -0
- package/dist/text-field/TextFieldCaptureContext.js +24 -0
- package/dist/text-field/TextFieldWrapper.js +21 -0
- package/package.json +62 -0
- package/readme.md +57 -0
@@ -0,0 +1,103 @@
|
|
1
|
+
export const operationsManager = (fileManager, clipboard, pasteOperation, deleteNode, renameNode, createDir, executeCommandSync, executeCommandAsync, moveToTrash) => {
|
2
|
+
const paste = async (replace) => {
|
3
|
+
if (!clipboard.item.value)
|
4
|
+
return;
|
5
|
+
if (fileManager.files.value?.some((e) => e.type === 'DRIVE'))
|
6
|
+
throw new Error(`Can't past outside a drive`);
|
7
|
+
await pasteOperation(clipboard.item.value, fileManager.path.value, replace);
|
8
|
+
clipboard.removeItem();
|
9
|
+
fileManager.cleanNodeSelected();
|
10
|
+
fileManager.refresh();
|
11
|
+
};
|
12
|
+
const copy = (node) => {
|
13
|
+
if (node.type === 'DRIVE')
|
14
|
+
return;
|
15
|
+
clipboard.setItem({
|
16
|
+
items: fileManager.selections.value.length === 0
|
17
|
+
? [node]
|
18
|
+
: fileManager.selections.value,
|
19
|
+
opeation: 'COPY',
|
20
|
+
});
|
21
|
+
};
|
22
|
+
const cut = (node) => {
|
23
|
+
if (node.type === 'DRIVE')
|
24
|
+
return;
|
25
|
+
clipboard.setItem({
|
26
|
+
items: fileManager.selections.value.length === 0
|
27
|
+
? [node]
|
28
|
+
: fileManager.selections.value,
|
29
|
+
opeation: 'COPY',
|
30
|
+
});
|
31
|
+
};
|
32
|
+
const removeNode = async (node) => {
|
33
|
+
const clipboardItem = clipboard.item.value;
|
34
|
+
if (fileManager.selections.value.length > 0) {
|
35
|
+
if (clipboardItem?.items.some((e) => fileManager.selections.value.some((node) => e.path === node.path)))
|
36
|
+
throw new Error(`Can't delete the item that you set on clipboard`);
|
37
|
+
if (fileManager.selections.value.some((e) => e.type === 'DRIVE'))
|
38
|
+
throw new Error('you can not remove a drive');
|
39
|
+
await Promise.all(fileManager.selections.value.map((node) => deleteNode(node)));
|
40
|
+
fileManager.refresh();
|
41
|
+
return;
|
42
|
+
}
|
43
|
+
if (clipboardItem?.items.some((e) => e.path === node.path))
|
44
|
+
throw new Error(`Can't delete the item that you set on clipboard`);
|
45
|
+
if (node.type === 'DRIVE')
|
46
|
+
throw new Error('you can not remove a drive');
|
47
|
+
await deleteNode(node);
|
48
|
+
fileManager.cleanNodeSelected();
|
49
|
+
fileManager.refresh();
|
50
|
+
};
|
51
|
+
const moveNodeToTrash = async (node) => {
|
52
|
+
const clipboardItem = clipboard.item.value;
|
53
|
+
if (fileManager.selections.value.length > 0) {
|
54
|
+
if (clipboardItem?.items.some((e) => fileManager.selections.value.some((node) => e.path === node.path)))
|
55
|
+
throw new Error(`Can't delete the item that you set on clipboard`);
|
56
|
+
if (fileManager.selections.value.some((e) => e.type === 'DRIVE'))
|
57
|
+
throw new Error('you can not remove a drive');
|
58
|
+
await Promise.all(fileManager.selections.value.map((node) => moveNodeToTrash(node)));
|
59
|
+
fileManager.refresh();
|
60
|
+
return;
|
61
|
+
}
|
62
|
+
if (clipboardItem?.items.some((e) => e.path === node.path))
|
63
|
+
throw new Error(`Can't delete the item that you set on clipboard`);
|
64
|
+
if (node.type === 'DRIVE')
|
65
|
+
throw new Error('you can not remove a drive');
|
66
|
+
await moveToTrash(node);
|
67
|
+
fileManager.cleanNodeSelected();
|
68
|
+
fileManager.refresh();
|
69
|
+
};
|
70
|
+
const rename = async (node, newName) => {
|
71
|
+
const clipboardItem = clipboard.item.value;
|
72
|
+
if (clipboardItem?.items.some((e) => e.path === node.path))
|
73
|
+
throw new Error(`Can't rename item that you set on clipboard`);
|
74
|
+
if (node.type === 'DRIVE')
|
75
|
+
throw new Error('you can not rename a drive');
|
76
|
+
await renameNode(node, newName);
|
77
|
+
fileManager.cleanNodeSelected();
|
78
|
+
fileManager.refresh();
|
79
|
+
};
|
80
|
+
const createDirectory = async (name) => {
|
81
|
+
if (fileManager.files.value?.some((e) => e.type === 'DRIVE'))
|
82
|
+
throw new Error(`Can't create a folder outside a drive`);
|
83
|
+
await createDir(fileManager.path.value, name);
|
84
|
+
fileManager.refresh();
|
85
|
+
};
|
86
|
+
const runCommandAsync = async (command) => executeCommandAsync(fileManager.path.value, command);
|
87
|
+
const runCommandSync = async (command) => {
|
88
|
+
const data = await executeCommandSync(fileManager.path.value, command);
|
89
|
+
fileManager.refresh();
|
90
|
+
return data;
|
91
|
+
};
|
92
|
+
return {
|
93
|
+
paste,
|
94
|
+
copy,
|
95
|
+
rename,
|
96
|
+
removeNode,
|
97
|
+
createDirectory,
|
98
|
+
cut,
|
99
|
+
runCommandAsync,
|
100
|
+
runCommandSync,
|
101
|
+
moveNodeToTrash,
|
102
|
+
};
|
103
|
+
};
|
@@ -0,0 +1 @@
|
|
1
|
+
export {};
|
@@ -0,0 +1 @@
|
|
1
|
+
export {};
|
@@ -0,0 +1 @@
|
|
1
|
+
export {};
|
@@ -0,0 +1 @@
|
|
1
|
+
export {};
|
@@ -0,0 +1 @@
|
|
1
|
+
export {};
|
@@ -0,0 +1 @@
|
|
1
|
+
export {};
|
@@ -0,0 +1 @@
|
|
1
|
+
export {};
|
@@ -0,0 +1 @@
|
|
1
|
+
export {};
|
@@ -0,0 +1 @@
|
|
1
|
+
export {};
|
@@ -0,0 +1 @@
|
|
1
|
+
export {};
|
@@ -0,0 +1 @@
|
|
1
|
+
export {};
|
@@ -0,0 +1 @@
|
|
1
|
+
export {};
|
@@ -0,0 +1 @@
|
|
1
|
+
export {};
|
@@ -0,0 +1 @@
|
|
1
|
+
export {};
|
@@ -0,0 +1 @@
|
|
1
|
+
export {};
|
@@ -0,0 +1,144 @@
|
|
1
|
+
const dirIcons = (node) => {
|
2
|
+
if (node.name === 'test' || node.name === 'tests')
|
3
|
+
return ' ';
|
4
|
+
if (node.name === 'node_modules')
|
5
|
+
return ' ';
|
6
|
+
if (node.name === '.git')
|
7
|
+
return ' ';
|
8
|
+
if (node.name === 'src')
|
9
|
+
return ' ';
|
10
|
+
if (node.name === '.vscode')
|
11
|
+
return ' ';
|
12
|
+
if (node.name === '.aws')
|
13
|
+
return ' ';
|
14
|
+
if (node.name === '.docker')
|
15
|
+
return ' ';
|
16
|
+
if (node.name === '.config')
|
17
|
+
return ' ';
|
18
|
+
if (node.name === 'Documents' || node.name === 'docs')
|
19
|
+
return ' ';
|
20
|
+
if (node.name === 'Downloads')
|
21
|
+
return ' ';
|
22
|
+
if (node.name === 'Desktop')
|
23
|
+
return ' ';
|
24
|
+
if (node.name.toLowerCase() === 'music')
|
25
|
+
return ' ';
|
26
|
+
if (node.name.toLowerCase() === 'pictures' ||
|
27
|
+
node.name.toLowerCase() === 'picture')
|
28
|
+
return ' ';
|
29
|
+
if (node.name.toLowerCase() === 'videos' ||
|
30
|
+
node.name.toLowerCase() === 'video')
|
31
|
+
return ' ';
|
32
|
+
if (node.name === 'bin')
|
33
|
+
return ' ';
|
34
|
+
return ' ';
|
35
|
+
};
|
36
|
+
const fileIcons = (node) => {
|
37
|
+
if (node.name === 'package.json' ||
|
38
|
+
node.name === '.nvmrc' ||
|
39
|
+
node.name === '.node-version')
|
40
|
+
return ' ';
|
41
|
+
if (node.name === 'Dockerfile' ||
|
42
|
+
node.name === '.dockerignore' ||
|
43
|
+
node.name === 'docker-compose.yml')
|
44
|
+
return ' ';
|
45
|
+
if (node.name === '.gitignore' || node.name === '.gitattributes')
|
46
|
+
return ' ';
|
47
|
+
if (node.name.toLowerCase() === 'readme.md')
|
48
|
+
return ' ';
|
49
|
+
if (node.name.endsWith('.key') || node.name.endsWith('.gpg'))
|
50
|
+
return ' ';
|
51
|
+
if (node.name.endsWith('.ts'))
|
52
|
+
return ' ';
|
53
|
+
if (node.name.endsWith('.c'))
|
54
|
+
return ' ';
|
55
|
+
if (node.name.endsWith('.exe'))
|
56
|
+
return 'ﬓ ';
|
57
|
+
if (node.name.endsWith('.sln'))
|
58
|
+
return ' ';
|
59
|
+
if (node.name.endsWith('.cs'))
|
60
|
+
return ' ';
|
61
|
+
if (node.name.endsWith('.js'))
|
62
|
+
return ' ';
|
63
|
+
if (node.name.endsWith('.txt'))
|
64
|
+
return ' ';
|
65
|
+
if (node.name.endsWith('.zip') ||
|
66
|
+
node.name.endsWith('.rar') ||
|
67
|
+
node.name.endsWith('.7z'))
|
68
|
+
return ' ';
|
69
|
+
if (node.name.endsWith('.cpp'))
|
70
|
+
return ' ';
|
71
|
+
if (node.name.endsWith('.html'))
|
72
|
+
return ' ';
|
73
|
+
if (node.name.endsWith('.png') ||
|
74
|
+
node.name.endsWith('.jpg') ||
|
75
|
+
node.name.endsWith('.jpeg') ||
|
76
|
+
node.name.endsWith('.gif') ||
|
77
|
+
node.name.endsWith('.bmp') ||
|
78
|
+
node.name.endsWith('.cur') ||
|
79
|
+
node.name.endsWith('.tiff') ||
|
80
|
+
node.name.endsWith('.svg'))
|
81
|
+
return ' ';
|
82
|
+
if (node.name.endsWith('.java'))
|
83
|
+
return ' ';
|
84
|
+
if (node.name.endsWith('.bat'))
|
85
|
+
return ' ';
|
86
|
+
if (node.name.endsWith('.md'))
|
87
|
+
return ' ';
|
88
|
+
if (node.name.endsWith('.kt'))
|
89
|
+
return ' ';
|
90
|
+
if (node.name.endsWith('.php'))
|
91
|
+
return ' ';
|
92
|
+
if (node.name.endsWith('.json'))
|
93
|
+
return ' ';
|
94
|
+
if (node.name.endsWith('.py'))
|
95
|
+
return ' ';
|
96
|
+
if (node.name.endsWith('.lock'))
|
97
|
+
return ' ';
|
98
|
+
if (node.name.endsWith('.toml') || node.name.endsWith('.ini'))
|
99
|
+
return ' ';
|
100
|
+
if (node.name.endsWith('.rs'))
|
101
|
+
return ' ';
|
102
|
+
if (node.name.endsWith('.sql'))
|
103
|
+
return '.sql';
|
104
|
+
if (node.name.endsWith('.zig'))
|
105
|
+
return ' ';
|
106
|
+
if (node.name.endsWith('.tsx') || node.name.endsWith('.jsx'))
|
107
|
+
return ' ';
|
108
|
+
if (node.name.endsWith('.pdf'))
|
109
|
+
return ' ';
|
110
|
+
if (node.name.endsWith('.docx'))
|
111
|
+
return ' ';
|
112
|
+
if (node.name.endsWith('.doc'))
|
113
|
+
return ' ';
|
114
|
+
if (node.name.endsWith('.xls') || node.name.endsWith('.xlsx'))
|
115
|
+
return ' ';
|
116
|
+
if (node.name.endsWith('.ppt') || node.name.endsWith('.pptx'))
|
117
|
+
return ' ';
|
118
|
+
if (node.name.endsWith('.psd'))
|
119
|
+
return ' ';
|
120
|
+
if (node.name.endsWith('.css'))
|
121
|
+
return ' ';
|
122
|
+
if (node.name.endsWith('.dll'))
|
123
|
+
return ' ';
|
124
|
+
if (node.name.endsWith('.mp3') ||
|
125
|
+
node.name.endsWith('.ma4') ||
|
126
|
+
node.name.endsWith('.wav') ||
|
127
|
+
node.name.endsWith('.mp3'))
|
128
|
+
return ' ';
|
129
|
+
if (node.name.endsWith('.mkv') ||
|
130
|
+
node.name.endsWith('.mp4') ||
|
131
|
+
node.name.endsWith('.avi'))
|
132
|
+
return ' ';
|
133
|
+
if (node.name.endsWith('.log'))
|
134
|
+
return ' ';
|
135
|
+
return ' ';
|
136
|
+
};
|
137
|
+
const driveIcon = (_node) => {
|
138
|
+
return ' ';
|
139
|
+
};
|
140
|
+
export const getNodeIcon = (node) => node.type === 'DIR'
|
141
|
+
? dirIcons(node)
|
142
|
+
: node.type === 'FILE'
|
143
|
+
? fileIcons(node)
|
144
|
+
: driveIcon(node);
|
@@ -0,0 +1,10 @@
|
|
1
|
+
import { Box, Text } from 'ink';
|
2
|
+
import React, { useContext } from 'react';
|
3
|
+
import { InputContextCapture } from './InputContextCapture.js';
|
4
|
+
export const InputCaptureWarpper = () => {
|
5
|
+
const inputCapture = useContext(InputContextCapture);
|
6
|
+
if (!inputCapture.isInputEnabled)
|
7
|
+
return React.createElement(React.Fragment, null);
|
8
|
+
return (React.createElement(Box, { borderStyle: "classic" },
|
9
|
+
React.createElement(Text, null, inputCapture.text)));
|
10
|
+
};
|
@@ -0,0 +1,26 @@
|
|
1
|
+
import { useInput } from 'ink';
|
2
|
+
import React, { createContext, useRef, useState } from 'react';
|
3
|
+
export const InputContextCapture = createContext(undefined);
|
4
|
+
export const InputContextCaptureProvider = (props) => {
|
5
|
+
const callback = useRef(undefined);
|
6
|
+
const [isInputEnabled, setInputEnable] = useState(false);
|
7
|
+
const [text, setText] = useState('');
|
8
|
+
const addCallback = (c, text) => {
|
9
|
+
callback.current = c;
|
10
|
+
setInputEnable(true);
|
11
|
+
setText(text);
|
12
|
+
};
|
13
|
+
useInput((input) => {
|
14
|
+
if (!isInputEnabled)
|
15
|
+
return;
|
16
|
+
callback.current?.(input);
|
17
|
+
callback.current = undefined;
|
18
|
+
setInputEnable(false);
|
19
|
+
setText('');
|
20
|
+
});
|
21
|
+
return (React.createElement(InputContextCapture.Provider, { value: {
|
22
|
+
addCallback,
|
23
|
+
text,
|
24
|
+
isInputEnabled,
|
25
|
+
} }, props.children));
|
26
|
+
};
|
@@ -0,0 +1,17 @@
|
|
1
|
+
import React, { createContext, useContext } from 'react';
|
2
|
+
import { EventContext } from '../core/infraestructure/event-handler/context/EventProvider.js';
|
3
|
+
import { useEffectOnInit } from '../core/infraestructure/on-init/useEffectOnInit.js';
|
4
|
+
import { useRefStateFactory } from '../core/infraestructure/state/useRefStateProvider.js';
|
5
|
+
import { useRefValueProvider } from '../core/infraestructure/value-provider/useRefValueProvider.js';
|
6
|
+
import { tabsManagerLogic } from './logic/tabsManagerLogic.js';
|
7
|
+
export const TabsContext = createContext(undefined);
|
8
|
+
export const TabsContextProvider = (props) => {
|
9
|
+
const data = tabsManagerLogic(useRefStateFactory(), useRefValueProvider(), () => ({
|
10
|
+
path: props.defaultPath,
|
11
|
+
history: {
|
12
|
+
previous: [],
|
13
|
+
forward: [],
|
14
|
+
},
|
15
|
+
}), useEffectOnInit, useContext(EventContext));
|
16
|
+
return (React.createElement(TabsContext.Provider, { value: data }, props.children));
|
17
|
+
};
|
@@ -0,0 +1,58 @@
|
|
1
|
+
import { UPDATE_TAB_DATA } from '../events/update.tab.data.js';
|
2
|
+
import { UPDATE_TAB_SELECTION, } from '../events/update.tab.selection.js';
|
3
|
+
export const tabsManagerLogic = (stateFactory, valueProvider, createTab, onInit, eventHandler) => {
|
4
|
+
const tabsState = stateFactory([createTab()]);
|
5
|
+
const selectionInTabs = valueProvider([]);
|
6
|
+
const indexState = stateFactory(0);
|
7
|
+
const updateTabData = (tabData) => {
|
8
|
+
tabsState.setState(tabsState.state.value.map((e, index) => indexState.state.value === index ? tabData : e));
|
9
|
+
};
|
10
|
+
const addTab = (tab) => {
|
11
|
+
selectionInTabs.value.push([]);
|
12
|
+
tabsState.setState([...tabsState.state.value, tab ?? createTab()]);
|
13
|
+
indexState.setState(tabsState.state.value.length - 1);
|
14
|
+
};
|
15
|
+
const removeTab = (index) => {
|
16
|
+
selectionInTabs.value = selectionInTabs.value.filter((_e, i) => index !== i);
|
17
|
+
tabsState.setState(tabsState.state.value.filter((_e, i) => index !== i));
|
18
|
+
if (index >= tabsState.state.value.length)
|
19
|
+
indexState.setState(tabsState.state.value.length - 1);
|
20
|
+
};
|
21
|
+
const moveToNext = () => {
|
22
|
+
if (tabsState.state.value.length === 1)
|
23
|
+
return;
|
24
|
+
if (indexState.state.value === tabsState.state.value.length - 1)
|
25
|
+
indexState.setState(0);
|
26
|
+
else
|
27
|
+
indexState.setState(indexState.state.value + 1);
|
28
|
+
};
|
29
|
+
const moveToPrev = () => {
|
30
|
+
if (tabsState.state.value.length === 1)
|
31
|
+
return;
|
32
|
+
if (indexState.state.value === 0)
|
33
|
+
indexState.setState(tabsState.state.value.length - 1);
|
34
|
+
else
|
35
|
+
indexState.setState(indexState.state.value - 1);
|
36
|
+
};
|
37
|
+
onInit(() => {
|
38
|
+
eventHandler.subscribe(UPDATE_TAB_DATA, (data) => {
|
39
|
+
updateTabData({
|
40
|
+
path: data.path,
|
41
|
+
history: data.history,
|
42
|
+
});
|
43
|
+
});
|
44
|
+
eventHandler.subscribe(UPDATE_TAB_SELECTION, (data) => {
|
45
|
+
selectionInTabs.value[indexState.state.value] = data.selection;
|
46
|
+
});
|
47
|
+
});
|
48
|
+
return {
|
49
|
+
moveToPrev,
|
50
|
+
moveToNext,
|
51
|
+
removeTab,
|
52
|
+
addTab,
|
53
|
+
updateTabData,
|
54
|
+
index: indexState.state,
|
55
|
+
tabs: tabsState.state,
|
56
|
+
selectionInTabs,
|
57
|
+
};
|
58
|
+
};
|
@@ -0,0 +1,38 @@
|
|
1
|
+
import { Box, Text, useInput } from 'ink';
|
2
|
+
import React, { useContext, useEffect, useState } from 'react';
|
3
|
+
import { Bookmark } from '../../bookmark/Bookmark.js';
|
4
|
+
import FileDisplay from '../../file-display/FileDisplay.js';
|
5
|
+
import { InputContextCapture } from '../../input-capture/InputContextCapture.js';
|
6
|
+
import { TextFieldContextCapture } from '../../text-field/TextFieldCaptureContext.js';
|
7
|
+
import { TabsContext } from '../TabsContext.js';
|
8
|
+
export default function TabsWrapper() {
|
9
|
+
const tabs = useContext(TabsContext);
|
10
|
+
const inputCapture = useContext(InputContextCapture);
|
11
|
+
const textField = useContext(TextFieldContextCapture);
|
12
|
+
const [childKey, setChildKey] = useState(0);
|
13
|
+
useInput((input, key) => {
|
14
|
+
if (inputCapture.isInputEnabled || textField.isInputEnabled)
|
15
|
+
return;
|
16
|
+
if (key.tab)
|
17
|
+
tabs.moveToNext();
|
18
|
+
if (key.tab && key.shift)
|
19
|
+
tabs.moveToPrev();
|
20
|
+
if (input === 't' && key.ctrl)
|
21
|
+
tabs.addTab();
|
22
|
+
if (input === 'w' && key.ctrl)
|
23
|
+
tabs.removeTab(tabs.index.value);
|
24
|
+
});
|
25
|
+
useEffect(() => {
|
26
|
+
setChildKey((prev) => ++prev);
|
27
|
+
}, [tabs.tabs.value, tabs.index.value]);
|
28
|
+
return (React.createElement(React.Fragment, null,
|
29
|
+
React.createElement(Box, { flexDirection: "row" },
|
30
|
+
React.createElement(Bookmark, null),
|
31
|
+
React.createElement(Box, { height: "100%", flexDirection: "column" },
|
32
|
+
tabs.tabs.value.length > 1 && (React.createElement(Box, { width: "100%", flexDirection: "row" }, tabs.tabs.value.map((_e, index) => (React.createElement(Box, { borderStyle: "round", borderColor: index === tabs.index.value
|
33
|
+
? 'blue'
|
34
|
+
: 'white' },
|
35
|
+
React.createElement(Text, null, `Tab: ${index + 1}`)))))),
|
36
|
+
tabs.index.value < tabs.tabs.value.length &&
|
37
|
+
tabs.index.value !== -1 && (React.createElement(FileDisplay, { selection: tabs.selectionInTabs.value[tabs.index.value] ?? [], key: childKey, initialData: tabs.tabs.value[tabs.index.value] }))))));
|
38
|
+
}
|
@@ -0,0 +1,24 @@
|
|
1
|
+
import React, { createContext, useRef, useState } from 'react';
|
2
|
+
export const TextFieldContextCapture = createContext(undefined);
|
3
|
+
export const TextFieldContextCaptureProvider = (props) => {
|
4
|
+
const textFieldData = useRef(undefined);
|
5
|
+
const [isInputEnabled, setInputEnable] = useState(false);
|
6
|
+
const [error, setError] = useState('');
|
7
|
+
const addCallback = (data) => {
|
8
|
+
textFieldData.current = data;
|
9
|
+
setInputEnable(true);
|
10
|
+
};
|
11
|
+
const cleanUp = () => {
|
12
|
+
textFieldData.current = undefined;
|
13
|
+
setInputEnable(false);
|
14
|
+
setError('');
|
15
|
+
};
|
16
|
+
return (React.createElement(TextFieldContextCapture.Provider, { value: {
|
17
|
+
addCallback,
|
18
|
+
isInputEnabled,
|
19
|
+
error,
|
20
|
+
setError,
|
21
|
+
data: textFieldData.current,
|
22
|
+
cleanUp,
|
23
|
+
} }, props.children));
|
24
|
+
};
|
@@ -0,0 +1,21 @@
|
|
1
|
+
import { StatusMessage, TextInput } from '@inkjs/ui';
|
2
|
+
import { Box, useInput } from 'ink';
|
3
|
+
import React, { useContext } from 'react';
|
4
|
+
import { TextFieldContextCapture } from './TextFieldCaptureContext.js';
|
5
|
+
export const TextFieldWrapper = () => {
|
6
|
+
const textField = useContext(TextFieldContextCapture);
|
7
|
+
useInput((_input, key) => {
|
8
|
+
if (key.escape)
|
9
|
+
textField.cleanUp();
|
10
|
+
});
|
11
|
+
if (!textField.isInputEnabled)
|
12
|
+
return React.createElement(React.Fragment, null);
|
13
|
+
return (React.createElement(Box, { borderStyle: "classic", flexDirection: "column", gap: 1 },
|
14
|
+
React.createElement(TextInput, { defaultValue: textField.data?.defaultValue, onChange: textField.data?.callback.onChange, placeholder: textField.data?.placeHolder, onSubmit: (data) => {
|
15
|
+
textField.cleanUp();
|
16
|
+
if (!textField.error)
|
17
|
+
textField.data?.callback.onSuccess(data);
|
18
|
+
} }),
|
19
|
+
textField.data?.text && !textField.error && (React.createElement(StatusMessage, { variant: "info" }, textField.data.text)),
|
20
|
+
textField.error && (React.createElement(StatusMessage, { variant: "error" }, textField.error))));
|
21
|
+
};
|
package/package.json
ADDED
@@ -0,0 +1,62 @@
|
|
1
|
+
{
|
2
|
+
"name": "@danixl30/file-explorer-cli",
|
3
|
+
"version": "1.0.0",
|
4
|
+
"license": "MIT",
|
5
|
+
"bin": {
|
6
|
+
"files": "dist/cli.js"
|
7
|
+
},
|
8
|
+
"type": "module",
|
9
|
+
"engines": {
|
10
|
+
"node": ">=16"
|
11
|
+
},
|
12
|
+
"files": [
|
13
|
+
"dist"
|
14
|
+
],
|
15
|
+
"dependencies": {
|
16
|
+
"@inkjs/ui": "^2.0.0",
|
17
|
+
"clipboardy": "^4.0.0",
|
18
|
+
"drivelist": "^12.0.2",
|
19
|
+
"fullscreen-ink": "^0.0.2",
|
20
|
+
"ink": "^4.1.0",
|
21
|
+
"lowdb": "^7.0.1",
|
22
|
+
"meow": "^11.0.0",
|
23
|
+
"minimatch": "^10.0.1",
|
24
|
+
"node-disk-info": "^1.3.0",
|
25
|
+
"react": "^18.2.0",
|
26
|
+
"trash": "^9.0.0"
|
27
|
+
},
|
28
|
+
"devDependencies": {
|
29
|
+
"@biomejs/biome": "^1.9.4",
|
30
|
+
"@sindresorhus/tsconfig": "^3.0.1",
|
31
|
+
"@types/node": "^22.9.0",
|
32
|
+
"@types/react": "^18.0.32",
|
33
|
+
"ava": "^5.2.0",
|
34
|
+
"chalk": "^5.2.0",
|
35
|
+
"ink-testing-library": "^3.0.0",
|
36
|
+
"ts-node": "^10.9.1",
|
37
|
+
"typescript": "^5.0.3",
|
38
|
+
"xo": "^0.53.1"
|
39
|
+
},
|
40
|
+
"ava": {
|
41
|
+
"extensions": {
|
42
|
+
"ts": "module",
|
43
|
+
"tsx": "module"
|
44
|
+
},
|
45
|
+
"nodeArguments": [
|
46
|
+
"--loader=ts-node/esm"
|
47
|
+
]
|
48
|
+
},
|
49
|
+
"xo": {
|
50
|
+
"extends": "xo-react",
|
51
|
+
"prettier": true,
|
52
|
+
"rules": {
|
53
|
+
"react/prop-types": "off"
|
54
|
+
}
|
55
|
+
},
|
56
|
+
"scripts": {
|
57
|
+
"lint": "pnpm biome check --write src/",
|
58
|
+
"build": "tsc",
|
59
|
+
"start": "tsc && node dist/cli",
|
60
|
+
"dev": "tsc --watch"
|
61
|
+
}
|
62
|
+
}
|
package/readme.md
ADDED
@@ -0,0 +1,57 @@
|
|
1
|
+
# CLI file explorer
|
2
|
+
|
3
|
+
Simple CLI file explorer with NodeJs
|
4
|
+
|
5
|
+
## Install
|
6
|
+
|
7
|
+
```bash
|
8
|
+
$ npm install -g @danixl30/cli-file-explorer
|
9
|
+
```
|
10
|
+
|
11
|
+
## CLI
|
12
|
+
|
13
|
+
```
|
14
|
+
$ files --help
|
15
|
+
```
|
16
|
+
|
17
|
+
## Usage
|
18
|
+
|
19
|
+
Use ```UP``` and ```DOWN``` key to move the cursor at the directory.
|
20
|
+
|
21
|
+
Use ```CTRL + t``` to create a new tab. If the cursor in on a directory press ```t``` to create a new tab with that directory.
|
22
|
+
|
23
|
+
User ```CRTL + w``` to close current tab.
|
24
|
+
|
25
|
+
Use ```TAB``` and ```SHIFT + TAB``` to change tabs.
|
26
|
+
|
27
|
+
Use ```RIGHT``` to select files or directories. ```SHIFT + RIGHT``` to unselect. ```SHIFT + i``` to invert selection. ```SHIFT + a``` to selcet all.
|
28
|
+
|
29
|
+
Use ```c``` to copy the item in the cursor or copy current selection.
|
30
|
+
|
31
|
+
Use ```SHIFT + c``` to cut the item in the cursor or cut current selection.
|
32
|
+
|
33
|
+
Use ```p``` to paste items copied or cut. ```SHIFT + p``` to past and override if exist.
|
34
|
+
|
35
|
+
Use ```d``` and confirm with ```y``` to move file or dir to trash. ```SHIFT + d``` to romeve item, confirm with ```SHIFT + y```.
|
36
|
+
|
37
|
+
Use ```r``` to refresh current tab.
|
38
|
+
|
39
|
+
Use ```SHIFT + r``` to rename file or dir that is on cursor.
|
40
|
+
|
41
|
+
Use ```g``` to select items in dir by glob pattern.
|
42
|
+
|
43
|
+
Use ```CTRL + l``` to set path o current tab.
|
44
|
+
|
45
|
+
Use ```i``` to see details of item that is on cursor.
|
46
|
+
|
47
|
+
Use ```CTRL + d``` to close details pane.
|
48
|
+
|
49
|
+
Use ```;``` to set not blocking command.
|
50
|
+
|
51
|
+
Use ```:``` to set blocking command.
|
52
|
+
|
53
|
+
Use ```SHITF + s``` to add dir that is on cursor to bookmarks.
|
54
|
+
|
55
|
+
Use ```s``` to switch to bookmarks pane.
|
56
|
+
|
57
|
+
Use ```a``` to create a directory in current tab.
|