@danixl30/file-explorer-cli 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (110) hide show
  1. package/LICENSE +21 -0
  2. package/dist/alerts/AlertContextProvider.js +13 -0
  3. package/dist/alerts/components/AlertDisplay.js +19 -0
  4. package/dist/alerts/logic/alert.logic.js +16 -0
  5. package/dist/app.js +27 -0
  6. package/dist/bookmark/Bookmark.js +123 -0
  7. package/dist/bookmark/components/BookmarkList.js +47 -0
  8. package/dist/bookmark/context/BookmarkPaneContext.js +9 -0
  9. package/dist/bookmark/events/add.item.js +8 -0
  10. package/dist/bookmark/implementations/clean.bookmarks.js +6 -0
  11. package/dist/bookmark/implementations/get.all.bookmarks.js +2 -0
  12. package/dist/bookmark/implementations/save.bookmarks.js +5 -0
  13. package/dist/bookmark/logic/bookmark.manager.js +25 -0
  14. package/dist/bookmark/services/clean.bookmarks.js +1 -0
  15. package/dist/bookmark/services/get.all.bookmarks.js +1 -0
  16. package/dist/bookmark/services/write.bookmarks.js +1 -0
  17. package/dist/bookmark/types/bookmark.js +1 -0
  18. package/dist/cli.js +31 -0
  19. package/dist/clipboard/ClipboardProvider.js +8 -0
  20. package/dist/clipboard/logic/clipboard-logic.js +10 -0
  21. package/dist/core/application/event-handler/event-handler.js +1 -0
  22. package/dist/core/application/event-handler/listener/event-listener.js +1 -0
  23. package/dist/core/application/event-handler/types/event.js +1 -0
  24. package/dist/core/application/event-handler/types/subscription.js +1 -0
  25. package/dist/core/application/id-generator/id.generator.js +1 -0
  26. package/dist/core/application/init-layout/init-layout.js +1 -0
  27. package/dist/core/application/input-manager/input-manager.js +1 -0
  28. package/dist/core/application/input-manager/types/input-manager-result.js +1 -0
  29. package/dist/core/application/on-init/on-init.js +1 -0
  30. package/dist/core/application/on-init-job/lazy/on-init-job-lazy.js +1 -0
  31. package/dist/core/application/on-init-job/on-init-job.js +1 -0
  32. package/dist/core/application/pagination-manager/pagination-manager.js +1 -0
  33. package/dist/core/application/pagination-manager/types/pagination-result.js +1 -0
  34. package/dist/core/application/service/application-service.js +1 -0
  35. package/dist/core/application/state/state-factory.js +1 -0
  36. package/dist/core/application/state/state-provider.js +1 -0
  37. package/dist/core/application/state-observers/state-observer.js +1 -0
  38. package/dist/core/application/timer/sleep/sleep.js +1 -0
  39. package/dist/core/application/timer/timer.js +19 -0
  40. package/dist/core/application/value-provider/value-provider.js +1 -0
  41. package/dist/core/infraestructure/db/bookmark.db.js +4 -0
  42. package/dist/core/infraestructure/event-handler/context/EventProvider.js +9 -0
  43. package/dist/core/infraestructure/event-handler/listener/event-listener-factory.js +8 -0
  44. package/dist/core/infraestructure/event-handler/useEventHadler.js +27 -0
  45. package/dist/core/infraestructure/initLayout/useLayoutEffectOnInit.js +4 -0
  46. package/dist/core/infraestructure/input-manager/useInputManager.js +14 -0
  47. package/dist/core/infraestructure/on-init/useEffectOnInit.js +9 -0
  48. package/dist/core/infraestructure/on-init-job/nativeOnInitJob.js +37 -0
  49. package/dist/core/infraestructure/on-init-job/nativeOnInitJobLazy.js +32 -0
  50. package/dist/core/infraestructure/pagination-manager/data-transforms/infinite.js +1 -0
  51. package/dist/core/infraestructure/pagination-manager/data-transforms/normal.js +1 -0
  52. package/dist/core/infraestructure/pagination-manager/usePaginationManager.js +59 -0
  53. package/dist/core/infraestructure/state/useRefStateProvider.js +39 -0
  54. package/dist/core/infraestructure/state/useStateProvider.js +35 -0
  55. package/dist/core/infraestructure/state-observer/useEffectStateObserver.js +16 -0
  56. package/dist/core/infraestructure/timer/sleep.js +2 -0
  57. package/dist/core/infraestructure/uuid/uuid.generator.js +2 -0
  58. package/dist/core/infraestructure/value-provider/useRefValueProvider.js +12 -0
  59. package/dist/core/utils/argument.type.js +1 -0
  60. package/dist/core/utils/optional.js +1 -0
  61. package/dist/file-display/FileDisplay.js +456 -0
  62. package/dist/file-display/components/Loading.js +4 -0
  63. package/dist/file-display/components/NodeDetails.js +50 -0
  64. package/dist/file-display/components/NodeList.js +61 -0
  65. package/dist/file-display/events/node.written.js +7 -0
  66. package/dist/file-display/hooks/useResize.js +24 -0
  67. package/dist/file-display/implementation/create.dir.js +7 -0
  68. package/dist/file-display/implementation/delete.js +9 -0
  69. package/dist/file-display/implementation/execute.command.async.js +6 -0
  70. package/dist/file-display/implementation/execute.command.sync.js +9 -0
  71. package/dist/file-display/implementation/execute.file.js +4 -0
  72. package/dist/file-display/implementation/filter.glob.js +4 -0
  73. package/dist/file-display/implementation/get.all.files.js +44 -0
  74. package/dist/file-display/implementation/get.directory.js +8 -0
  75. package/dist/file-display/implementation/get.upper.directory.js +17 -0
  76. package/dist/file-display/implementation/move.trash.js +9 -0
  77. package/dist/file-display/implementation/node.details.js +79 -0
  78. package/dist/file-display/implementation/paste.operation.js +40 -0
  79. package/dist/file-display/implementation/rename.js +6 -0
  80. package/dist/file-display/logic/file.manager.js +107 -0
  81. package/dist/file-display/logic/history.manager.js +47 -0
  82. package/dist/file-display/logic/node.detail.js +8 -0
  83. package/dist/file-display/logic/operations.manager.js +103 -0
  84. package/dist/file-display/services/create.dir.js +1 -0
  85. package/dist/file-display/services/delete.js +1 -0
  86. package/dist/file-display/services/execute.command.async.js +1 -0
  87. package/dist/file-display/services/execute.command.sync.js +1 -0
  88. package/dist/file-display/services/execute.file.js +1 -0
  89. package/dist/file-display/services/filter.glob.js +1 -0
  90. package/dist/file-display/services/get.all.files.js +1 -0
  91. package/dist/file-display/services/get.directory.js +1 -0
  92. package/dist/file-display/services/get.upper.directory.js +1 -0
  93. package/dist/file-display/services/move.trash.js +1 -0
  94. package/dist/file-display/services/node.details.js +1 -0
  95. package/dist/file-display/services/paste.operation.js +1 -0
  96. package/dist/file-display/services/remane.js +1 -0
  97. package/dist/file-display/types/node.details.js +1 -0
  98. package/dist/file-display/types/node.js +1 -0
  99. package/dist/file-display/utils/icon.parser.js +144 -0
  100. package/dist/input-capture/InputCapureWrapper.js +10 -0
  101. package/dist/input-capture/InputContextCapture.js +26 -0
  102. package/dist/tabs/TabsContext.js +17 -0
  103. package/dist/tabs/events/update.tab.data.js +8 -0
  104. package/dist/tabs/events/update.tab.selection.js +8 -0
  105. package/dist/tabs/logic/tabsManagerLogic.js +58 -0
  106. package/dist/tabs/wrapper/tabs.wrapper.js +38 -0
  107. package/dist/text-field/TextFieldCaptureContext.js +24 -0
  108. package/dist/text-field/TextFieldWrapper.js +21 -0
  109. package/package.json +62 -0
  110. package/readme.md +57 -0
@@ -0,0 +1,50 @@
1
+ import { Alert, Spinner } from '@inkjs/ui';
2
+ import { Box, Text } from 'ink';
3
+ import React from 'react';
4
+ import { nativeOnInitJob } from '../../core/infraestructure/on-init-job/nativeOnInitJob.js';
5
+ import { useEffectOnInit } from '../../core/infraestructure/on-init/useEffectOnInit.js';
6
+ import { useEffectStateObserver } from '../../core/infraestructure/state-observer/useEffectStateObserver.js';
7
+ import { useRefStateFactory } from '../../core/infraestructure/state/useRefStateProvider.js';
8
+ import { getNodeDetails } from '../implementation/node.details.js';
9
+ import { nodeDetailLogic } from '../logic/node.detail.js';
10
+ import { getNodeIcon } from '../utils/icon.parser.js';
11
+ function formatBytes(bytes, decimals = 2) {
12
+ if (!+bytes)
13
+ return '0 Bytes';
14
+ const k = 1024;
15
+ const dm = decimals < 0 ? 0 : decimals;
16
+ const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
17
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
18
+ return `${parseFloat((bytes / Math.pow(k, i)).toFixed(dm))} ${sizes[i]}`;
19
+ }
20
+ export const NodeDetails = (props) => {
21
+ const details = nodeDetailLogic(props.node, nativeOnInitJob(useRefStateFactory(), useEffectStateObserver, useEffectOnInit), getNodeDetails());
22
+ if (details.error.value)
23
+ return React.createElement(Alert, { variant: "error" }, details.error.value.message);
24
+ if (details.isLoading.value || !details.data.value)
25
+ return (React.createElement(Box, { borderStyle: "single", width: "100%", alignItems: "stretch", flexDirection: "row" },
26
+ React.createElement(Spinner, { label: "Loading..." })));
27
+ return (React.createElement(Box, { borderStyle: "single", flexDirection: "column", width: "50%" },
28
+ React.createElement(Text, null, 'Path: ' + details.data.value.path),
29
+ React.createElement(Text, null, 'Name: ' +
30
+ getNodeIcon(details.data.value) +
31
+ details.data.value.name),
32
+ React.createElement(Text, null, 'Size: ' + formatBytes(details.data.value.size)),
33
+ details.data.value.createdDate && (React.createElement(Text, null, 'Created at: ' +
34
+ details.data.value.createdDate.toLocaleString())),
35
+ details.data.value.lastModifiedDate && (React.createElement(Text, null, 'Last modified: ' +
36
+ details.data.value.lastModifiedDate.toLocaleString())),
37
+ details.data.value.additionalData && (React.createElement(React.Fragment, null,
38
+ React.createElement(Text, null, 'Available: ' +
39
+ formatBytes(details.data.value.additionalData.available)),
40
+ React.createElement(Text, null, 'Used: ' +
41
+ formatBytes(details.data.value.additionalData.used)),
42
+ React.createElement(Text, null, 'Percent occupied: ' +
43
+ details.data.value.additionalData.percent),
44
+ React.createElement(Text, null, 'Partition schema: ' +
45
+ details.data.value.additionalData.partitionSchema),
46
+ React.createElement(Text, null, 'File system: ' +
47
+ details.data.value.additionalData.fileSystem),
48
+ React.createElement(Text, null, 'Device type: ' +
49
+ details.data.value.additionalData.deviceType)))));
50
+ };
@@ -0,0 +1,61 @@
1
+ import { Badge } from '@inkjs/ui';
2
+ import { Box, Text } from 'ink';
3
+ import React, { useEffect, useMemo, useState } from 'react';
4
+ import { useResize } from '../hooks/useResize.js';
5
+ import { getNodeIcon } from '../utils/icon.parser.js';
6
+ const createRange = (start, end) => {
7
+ const arr = [];
8
+ for (let index = start; index <= end; index++)
9
+ arr.push(index);
10
+ return arr;
11
+ };
12
+ const calculatePercent = (nodes, index) => Math.round((index * 100) / nodes.length);
13
+ export const NodeList = (props) => {
14
+ const { selections, nodes, index } = props;
15
+ const [lines, setLines] = useState(0);
16
+ const [[top, down], setSubIndexes] = useState([
17
+ -1,
18
+ lines - 2,
19
+ ]);
20
+ const [_columns, rows] = useResize();
21
+ useEffect(() => {
22
+ if (index === -1) {
23
+ setSubIndexes([-1, lines - 2]);
24
+ return;
25
+ }
26
+ if (index < top) {
27
+ setSubIndexes([index, index + lines - 1]);
28
+ return;
29
+ }
30
+ if (index > down) {
31
+ setSubIndexes([index - lines + 1, index]);
32
+ return;
33
+ }
34
+ }, [lines, props.index, nodes, top, down]);
35
+ useEffect(() => {
36
+ const lines = rows - 6 - (props.isTabs ? 3 : 0);
37
+ setLines(lines);
38
+ setSubIndexes([-1, lines - 2]);
39
+ }, [rows, props.isTabs]);
40
+ const range = useMemo(() => createRange(top, down), [top, down]);
41
+ return (React.createElement(React.Fragment, null,
42
+ React.createElement(Box, { flexDirection: "column", height: "100%" },
43
+ top >= -1 && down <= lines - 2 && (React.createElement(Text, { backgroundColor: props.index === -1 ? 'blue' : undefined }, "..")),
44
+ props.nodes?.length > 0 &&
45
+ range.map((e) => {
46
+ if (e === -1)
47
+ return React.createElement(React.Fragment, null);
48
+ const file = props.nodes[e];
49
+ if (!file)
50
+ return React.createElement(React.Fragment, null);
51
+ return (React.createElement(React.Fragment, null,
52
+ React.createElement(Text, { backgroundColor: index === e ? 'blue' : undefined }, `${selections.length > 0 &&
53
+ selections.some((e) => e.path === file.path)
54
+ ? ' '
55
+ : selections.length > 0 &&
56
+ selections.some((e) => e.path !== file.path)
57
+ ? '󱗝 '
58
+ : ''}${getNodeIcon(file)} ${file.name}`)));
59
+ })),
60
+ React.createElement(Badge, { color: "blueBright" }, calculatePercent(nodes ?? [], index + 1) + '%')));
61
+ };
@@ -0,0 +1,7 @@
1
+ import { randomUUID } from 'node:crypto';
2
+ export const createNodeWrittenEvent = () => ({
3
+ id: randomUUID(),
4
+ name: NODE_WRITTEN,
5
+ timestamp: new Date(),
6
+ });
7
+ export const NODE_WRITTEN = 'NODE_WRITTEN';
@@ -0,0 +1,24 @@
1
+ import { useStdout } from 'ink';
2
+ import { useEffect, useState } from 'react';
3
+ export const useResize = () => {
4
+ const { stdout } = useStdout();
5
+ const [size, setSize] = useState({
6
+ columns: stdout.columns,
7
+ rows: stdout.rows,
8
+ });
9
+ useEffect(() => {
10
+ function onResize() {
11
+ setSize({
12
+ columns: process.stdout.columns,
13
+ rows: process.stdout.rows,
14
+ });
15
+ }
16
+ process.stdout.on('resize', onResize);
17
+ process.stdout.write('\x1b[?1049h');
18
+ return () => {
19
+ process.stdout.off('resize', onResize);
20
+ process.stdout.write('\x1b[?1049l');
21
+ };
22
+ }, []);
23
+ return [size.columns, size.rows];
24
+ };
@@ -0,0 +1,7 @@
1
+ import { mkdirSync } from 'node:fs';
2
+ import { join } from 'node:path';
3
+ export const createDirectory = async (path, dir) => {
4
+ mkdirSync(join(path, '/' + dir), {
5
+ recursive: true,
6
+ });
7
+ };
@@ -0,0 +1,9 @@
1
+ import { rmSync, unlinkSync } from 'node:fs';
2
+ export const deleteNode = async (node) => {
3
+ if (node.type === 'FILE')
4
+ unlinkSync(node.path);
5
+ if (node.type === 'DIR')
6
+ rmSync(node.path, {
7
+ recursive: true,
8
+ });
9
+ };
@@ -0,0 +1,6 @@
1
+ import { exec } from 'node:child_process';
2
+ export const executeCommandAsync = async (path, command) => {
3
+ exec(command, {
4
+ cwd: path,
5
+ });
6
+ };
@@ -0,0 +1,9 @@
1
+ import { spawnSync } from 'node:child_process';
2
+ export const executeCommandSync = async (path, command) => {
3
+ spawnSync(command, {
4
+ cwd: path,
5
+ stdio: 'inherit',
6
+ shell: true,
7
+ });
8
+ return 'Command executed';
9
+ };
@@ -0,0 +1,4 @@
1
+ import { exec } from 'node:child_process';
2
+ export const executeFile = async (node) => {
3
+ exec(node.path);
4
+ };
@@ -0,0 +1,4 @@
1
+ import { minimatch } from 'minimatch';
2
+ export const filterByGlobPattern = (nodes, pattern) => nodes.filter((e) => minimatch(e.path, pattern, {
3
+ matchBase: true,
4
+ }));
@@ -0,0 +1,44 @@
1
+ import { lstatSync, readdirSync } from 'node:fs';
2
+ import { join, parse } from 'node:path';
3
+ import { list } from 'drivelist';
4
+ export const getAllFiles = async (path) => {
5
+ if (path === '/' && process.platform === 'win32') {
6
+ const drives = await list();
7
+ return drives
8
+ .map((e) => e.mountpoints.map((mout) => ({
9
+ path: mout.path.replace('\\', '/'),
10
+ name: `(${mout.path.replace('\\', '')}) ${e.description}`,
11
+ type: 'DRIVE',
12
+ })))
13
+ .flat();
14
+ }
15
+ const nodes = readdirSync(path);
16
+ return nodes
17
+ .map((e) => e.replaceAll('\\', '/'))
18
+ .map((node) => {
19
+ try {
20
+ const nodePath = join(path, node).replaceAll('\\', '/');
21
+ if (lstatSync(nodePath).isDirectory())
22
+ return {
23
+ path: nodePath,
24
+ type: 'DIR',
25
+ name: parse(node).base,
26
+ };
27
+ return {
28
+ path: nodePath,
29
+ type: 'FILE',
30
+ name: parse(node).base,
31
+ };
32
+ }
33
+ catch (_e) {
34
+ return undefined;
35
+ }
36
+ })
37
+ .filter((e) => e != undefined)
38
+ .reduce((acc, e) => {
39
+ if (e.type === 'FILE')
40
+ return [acc[0], [...acc[1], e]];
41
+ return [[...acc[0], e], acc[1]];
42
+ }, [[], []])
43
+ .flat();
44
+ };
@@ -0,0 +1,8 @@
1
+ import { existsSync } from 'node:fs';
2
+ export const getNextDir = async (node) => {
3
+ if (node.type === 'DRIVE')
4
+ return node.path;
5
+ if (!existsSync(node.path))
6
+ throw new Error('Directory not exist');
7
+ return node.path;
8
+ };
@@ -0,0 +1,17 @@
1
+ import { existsSync } from 'node:fs';
2
+ export const getUpperDirectory = async (path) => {
3
+ if (path === '/')
4
+ return path;
5
+ if (path.split('/').length === 2 &&
6
+ path.endsWith('/') &&
7
+ process.platform === 'win32')
8
+ return '/';
9
+ let pathToSearch = path.split('/').toSpliced(-1, 1).join('/');
10
+ if (process.platform === 'win32' && !path)
11
+ return '/';
12
+ if (pathToSearch.split('/').length === 1 && process.platform === 'win32')
13
+ pathToSearch = pathToSearch + '/';
14
+ if (!existsSync(pathToSearch))
15
+ return path;
16
+ return pathToSearch;
17
+ };
@@ -0,0 +1,9 @@
1
+ import { execSync } from 'node:child_process';
2
+ import { resolve } from 'node:path';
3
+ import trash from 'trash';
4
+ export const moveNodeToTrash = async (node) => {
5
+ if (process.platform === 'win32')
6
+ execSync(`recycle -f ${resolve(node.path)}`);
7
+ else
8
+ await trash(node.path);
9
+ };
@@ -0,0 +1,79 @@
1
+ import { statSync } from 'node:fs';
2
+ import { readdir, stat } from 'node:fs/promises';
3
+ import { join } from 'node:path';
4
+ import { list } from 'drivelist';
5
+ import { getDiskInfoSync } from 'node-disk-info';
6
+ import { useEffect, useRef } from 'react';
7
+ const dirSize = (isMounted) => {
8
+ const calculate = async (dir) => {
9
+ if (!isMounted.current)
10
+ throw new Error();
11
+ const files = await readdir(dir, { withFileTypes: true });
12
+ const paths = files.map(async (file) => {
13
+ const path = join(dir, file.name);
14
+ if (file.isDirectory())
15
+ return calculate(path);
16
+ if (file.isFile()) {
17
+ if (!isMounted.current)
18
+ throw new Error();
19
+ const { size } = await stat(path);
20
+ return size;
21
+ }
22
+ return 0;
23
+ });
24
+ return (await Promise.all(paths))
25
+ .flat()
26
+ .reduce((acc, size) => acc + size, 0);
27
+ };
28
+ return calculate;
29
+ };
30
+ export const getNodeDetails = () => {
31
+ const isMounted = useRef(true);
32
+ useEffect(() => {
33
+ isMounted.current = true;
34
+ return () => {
35
+ isMounted.current = false;
36
+ };
37
+ }, []);
38
+ return async (node) => {
39
+ if (node.type === 'DRIVE') {
40
+ const drives = await list();
41
+ const infos = getDiskInfoSync();
42
+ const disk = drives.find((e) => e.mountpoints.some((e) => e.path.replaceAll('\\', '/') === node.path));
43
+ if (!disk)
44
+ throw new Error('Disk not exist');
45
+ const info = infos.find((e) => node.path.startsWith(e.mounted));
46
+ if (!info)
47
+ throw new Error('Not disk info');
48
+ return {
49
+ ...node,
50
+ size: disk.size ?? info.blocks,
51
+ additionalData: {
52
+ used: info.used,
53
+ percent: info.capacity,
54
+ fileSystem: info.filesystem,
55
+ available: info.available,
56
+ partitionSchema: disk.partitionTableType?.toUpperCase() ?? 'No schema',
57
+ deviceType: disk.isCard
58
+ ? 'CARD'
59
+ : disk.isUSB
60
+ ? 'USB'
61
+ : disk.isSCSI
62
+ ? 'SCSI'
63
+ : disk.isVirtual
64
+ ? 'VIRTUAL'
65
+ : 'REMOVABLE',
66
+ },
67
+ };
68
+ }
69
+ const stats = statSync(node.path);
70
+ return {
71
+ ...node,
72
+ size: node.type === 'FILE'
73
+ ? stats.size
74
+ : await dirSize(isMounted)(node.path),
75
+ createdDate: stats.birthtime,
76
+ lastModifiedDate: stats.mtime,
77
+ };
78
+ };
79
+ };
@@ -0,0 +1,40 @@
1
+ import { copyFileSync, cpSync, existsSync, lstatSync, renameSync, } from 'node:fs';
2
+ import { join, parse } from 'node:path';
3
+ const generateFilename = (data, path) => {
4
+ const name = parse(data.path);
5
+ const numberGenerator = (index = 1) => {
6
+ if (data.type === 'FILE') {
7
+ if (existsSync(join(path, name.name + ` (${index})` + name.ext)))
8
+ return numberGenerator(++index);
9
+ return name.name + ` (${index})` + name.ext;
10
+ }
11
+ if (existsSync(join(path, name.base + ` (${index})`)))
12
+ return numberGenerator(++index);
13
+ return name.base + ` (${index})`;
14
+ };
15
+ if (existsSync(join(path, name.base)))
16
+ return numberGenerator();
17
+ return name.base;
18
+ };
19
+ const pasteWorker = (data, destinate, operation, replace) => {
20
+ const filename = replace
21
+ ? parse(data.path).base
22
+ : generateFilename(data, destinate);
23
+ if (operation === 'CUT') {
24
+ renameSync(data.path, join(destinate, `/${filename}`));
25
+ }
26
+ if (operation === 'COPY' && data.type === 'FILE') {
27
+ copyFileSync(data.path, join(destinate, `/${filename}`));
28
+ }
29
+ if (operation === 'COPY' && data.type === 'DIR') {
30
+ cpSync(data.path, join(destinate, `/${filename}`), {
31
+ recursive: true,
32
+ force: true,
33
+ });
34
+ }
35
+ };
36
+ export const pasteOperation = async (data, destinate, replace) => {
37
+ if (!lstatSync(destinate).isDirectory())
38
+ throw new Error('The destinate must be a directory');
39
+ data.items.map((node) => pasteWorker(node, destinate, data.opeation, replace));
40
+ };
@@ -0,0 +1,6 @@
1
+ import { renameSync } from 'node:fs';
2
+ import { join } from 'node:path';
3
+ export const renameNode = async (node, newName) => {
4
+ const basePath = node.path.split('/').toSpliced(-1, 1).join('/');
5
+ renameSync(node.path, join(basePath, `/${newName}`));
6
+ };
@@ -0,0 +1,107 @@
1
+ import { createUpdateTabSelectionEvent } from '../../tabs/events/update.tab.selection.js';
2
+ export const fileManagerLogic = (stateProvider, stateObserver, eventHandler, onInitJob, path, getAllFiles, uppderDir, nextDir, executeFile, history, selection, filterByGlobPattern) => {
3
+ const pathState = stateProvider(path);
4
+ const selectionsState = stateProvider(selection);
5
+ const selectedNode = stateProvider(undefined);
6
+ const filePaginated = onInitJob(() => getAllFiles(pathState.state.value), pathState.state);
7
+ const toggleSelected = (node) => {
8
+ if (selectionsState.state.value.some((e) => e.path === node.path))
9
+ selectionsState.setState(selectionsState.state.value.filter((e) => e.path !== node.path));
10
+ else
11
+ selectionsState.setState([...selectionsState.state.value, node]);
12
+ };
13
+ const inevertSelection = () => {
14
+ selectionsState.setState(filePaginated.data.value.filter((e) => !selectionsState.state.value.some((node) => e.path === node.path) && e.type !== 'DRIVE'));
15
+ };
16
+ const selectAll = () => {
17
+ selectionsState.setState(filePaginated.data.value.filter((e) => e.type !== 'DRIVE'));
18
+ };
19
+ const unSelectAll = () => {
20
+ selectionsState.setState([]);
21
+ };
22
+ const selectNode = async (node) => {
23
+ if (filePaginated.isLoading.value || filePaginated.isReloading.value)
24
+ return;
25
+ if (node.type === 'DIR' || node.type === 'DRIVE') {
26
+ const nextPath = await nextDir(node);
27
+ const prevPath = pathState.state.value;
28
+ pathState.setState(nextPath);
29
+ history.push(nextPath, prevPath);
30
+ return;
31
+ }
32
+ await executeFile(node);
33
+ };
34
+ const gotToUpper = async () => {
35
+ if (filePaginated.isLoading.value)
36
+ return;
37
+ const nextPath = await uppderDir(pathState.state.value);
38
+ if (!nextPath)
39
+ return;
40
+ const prevPath = pathState.state.value;
41
+ pathState.setState(nextPath);
42
+ history.push(pathState.state.value, prevPath);
43
+ };
44
+ const popHistory = () => {
45
+ if (filePaginated.isLoading.value)
46
+ return;
47
+ const nextPath = history.pop(pathState.state.value);
48
+ if (!nextPath)
49
+ return;
50
+ pathState.setState(nextPath);
51
+ };
52
+ const forwardHistory = () => {
53
+ if (filePaginated.isLoading.value)
54
+ return;
55
+ const nextPath = history.forward(pathState.state.value);
56
+ if (!nextPath)
57
+ return;
58
+ pathState.setState(nextPath);
59
+ };
60
+ const refresh = () => filePaginated.reload();
61
+ const selectNodeToDetails = (node) => {
62
+ selectedNode.setState(node);
63
+ };
64
+ const cleanNodeSelected = () => {
65
+ selectedNode.setState(undefined);
66
+ };
67
+ const navigateTo = (path) => {
68
+ const prevPath = pathState.state.value;
69
+ pathState.setState(path);
70
+ history.push(pathState.state.value, prevPath);
71
+ };
72
+ const selectByGlobPattern = (pattern) => {
73
+ selectionsState.setState(filterByGlobPattern(filePaginated.data.value.filter((e) => e.type !== 'DRIVE'), pattern));
74
+ };
75
+ stateObserver(() => {
76
+ if (!filePaginated.data.value)
77
+ return;
78
+ selectionsState.setState(selectionsState.state.value.filter((e) => filePaginated.data.value.some((node) => e.path === node.path)));
79
+ }, filePaginated.data);
80
+ stateObserver(() => {
81
+ selectionsState.setState([]);
82
+ }, pathState.state);
83
+ stateObserver(() => {
84
+ eventHandler.publish(createUpdateTabSelectionEvent(selectionsState.state.value));
85
+ }, selectionsState.state);
86
+ return {
87
+ files: filePaginated.data,
88
+ isLoading: filePaginated.isLoading,
89
+ isError: filePaginated.error,
90
+ forwardHistory,
91
+ popHistory,
92
+ selectNode,
93
+ refresh,
94
+ path: pathState.state,
95
+ gotToUpper,
96
+ selections: selectionsState.state,
97
+ toggleSelected,
98
+ selectAll,
99
+ inevertSelection,
100
+ unSelectAll,
101
+ selectedNode: selectedNode.state,
102
+ selectNodeToDetails,
103
+ navigateTo,
104
+ cleanNodeSelected,
105
+ selectByGlobPattern,
106
+ };
107
+ };
@@ -0,0 +1,47 @@
1
+ import { createUpdateTabDataEvent } from '../../tabs/events/update.tab.data.js';
2
+ export const historyManager = (valueProvider, eventHandler, historyInitial) => {
3
+ const backHitory = valueProvider(historyInitial.previous);
4
+ const forwardHistory = valueProvider(historyInitial.forward);
5
+ const push = (path, currentPath) => {
6
+ backHitory.value.push(currentPath);
7
+ forwardHistory.value = [];
8
+ eventHandler.publish(createUpdateTabDataEvent({
9
+ path: path,
10
+ history: {
11
+ previous: backHitory.value,
12
+ forward: forwardHistory.value,
13
+ },
14
+ }));
15
+ };
16
+ const pop = (currentPath) => {
17
+ const path = backHitory.value.pop();
18
+ if (path)
19
+ forwardHistory.value.push(currentPath);
20
+ eventHandler.publish(createUpdateTabDataEvent({
21
+ path: path ?? currentPath,
22
+ history: {
23
+ previous: backHitory.value,
24
+ forward: forwardHistory.value,
25
+ },
26
+ }));
27
+ return path;
28
+ };
29
+ const forward = (currentPath) => {
30
+ const path = forwardHistory.value.pop();
31
+ if (path)
32
+ backHitory.value.push(currentPath);
33
+ eventHandler.publish(createUpdateTabDataEvent({
34
+ path: path ?? currentPath,
35
+ history: {
36
+ previous: backHitory.value,
37
+ forward: forwardHistory.value,
38
+ },
39
+ }));
40
+ return path;
41
+ };
42
+ return {
43
+ push,
44
+ pop,
45
+ forward,
46
+ };
47
+ };
@@ -0,0 +1,8 @@
1
+ export const nodeDetailLogic = (node, onInitJob, getNodeDetails) => {
2
+ const detailsJob = onInitJob(() => getNodeDetails(node));
3
+ return {
4
+ data: detailsJob.data,
5
+ isLoading: detailsJob.isLoading,
6
+ error: detailsJob.error,
7
+ };
8
+ };