@hamak/ui-store-impl 0.2.6 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +41 -0
- package/dist/es2015/fs/commands/fs-commands.js +311 -0
- package/dist/es2015/fs/commands/structure-commands.js +21 -0
- package/dist/es2015/fs/core/fs-adapter.js +123 -0
- package/dist/es2015/fs/core/fs-facade.js +78 -0
- package/dist/es2015/fs/index.js +9 -0
- package/dist/es2015/fs/utils/data-updater.js +244 -0
- package/dist/es2015/fs/utils/deep-equal.js +28 -0
- package/dist/es2015/index.js +1 -0
- package/dist/fs/commands/fs-commands.d.ts +96 -0
- package/dist/fs/commands/fs-commands.d.ts.map +1 -0
- package/dist/fs/commands/fs-commands.js +311 -0
- package/dist/fs/commands/structure-commands.d.ts +76 -0
- package/dist/fs/commands/structure-commands.d.ts.map +1 -0
- package/dist/fs/commands/structure-commands.js +21 -0
- package/dist/fs/core/fs-adapter.d.ts +57 -0
- package/dist/fs/core/fs-adapter.d.ts.map +1 -0
- package/dist/fs/core/fs-adapter.js +123 -0
- package/dist/fs/core/fs-facade.d.ts +37 -0
- package/dist/fs/core/fs-facade.d.ts.map +1 -0
- package/dist/fs/core/fs-facade.js +78 -0
- package/dist/fs/index.d.ts +7 -0
- package/dist/fs/index.d.ts.map +1 -0
- package/dist/fs/index.js +9 -0
- package/dist/fs/utils/data-updater.d.ts +36 -0
- package/dist/fs/utils/data-updater.d.ts.map +1 -0
- package/dist/fs/utils/data-updater.js +248 -0
- package/dist/fs/utils/deep-equal.d.ts +5 -0
- package/dist/fs/utils/deep-equal.d.ts.map +1 -0
- package/dist/fs/utils/deep-equal.js +28 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/package.json +8 -5
- package/project.json +24 -0
- package/src/fs/commands/fs-commands.ts +406 -0
- package/src/fs/commands/structure-commands.ts +105 -0
- package/src/fs/core/fs-adapter.ts +180 -0
- package/src/fs/core/fs-facade.ts +100 -0
- package/src/fs/index.ts +11 -0
- package/src/fs/utils/data-updater.ts +273 -0
- package/src/fs/utils/deep-equal.ts +35 -0
- package/src/index.ts +1 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
## 0.4.0 (2025-11-10)
|
|
2
|
+
|
|
3
|
+
### 🚀 Features
|
|
4
|
+
|
|
5
|
+
- implement notification plugin with UI and backend components ([c19ffcf](https://github.com/amah/app-framework/commit/c19ffcf))
|
|
6
|
+
- add ES2015 build support and fix TypeScript config for logging packages ([be5e45e](https://github.com/amah/app-framework/commit/be5e45e))
|
|
7
|
+
- complete logging system build and add optional console interception ([f390bc6](https://github.com/amah/app-framework/commit/f390bc6))
|
|
8
|
+
- implement core pluggable logging system (Phase 1) ([2abdc1a](https://github.com/amah/app-framework/commit/2abdc1a))
|
|
9
|
+
|
|
10
|
+
### 🩹 Fixes
|
|
11
|
+
|
|
12
|
+
- add notification packages to workspaces ([97a234d](https://github.com/amah/app-framework/commit/97a234d))
|
|
13
|
+
|
|
14
|
+
### ❤️ Thank You
|
|
15
|
+
|
|
16
|
+
- Amah
|
|
17
|
+
- Claude
|
|
18
|
+
|
|
19
|
+
## 0.3.0 (2025-11-06)
|
|
20
|
+
|
|
21
|
+
### 🚀 Features
|
|
22
|
+
|
|
23
|
+
- migrate from Turbo to Nx 22 with comprehensive monorepo setup ([e63801e](https://github.com/amah/app-framework/commit/e63801e))
|
|
24
|
+
- add Nx Release for automated dependency management ([01d474f](https://github.com/amah/app-framework/commit/01d474f))
|
|
25
|
+
- migrate from Turbo to Nx 22 monorepo orchestration ([d374271](https://github.com/amah/app-framework/commit/d374271))
|
|
26
|
+
- add configurable main padding and resizable sidebar to DashboardLayout ([c1d25bf](https://github.com/amah/app-framework/commit/c1d25bf))
|
|
27
|
+
- add debug logging and version management system ([ea514fc](https://github.com/amah/app-framework/commit/ea514fc))
|
|
28
|
+
- **ui-store:** add STORE_EXTENSIONS_TOKEN for DI-based middleware/reducer registration ([e855bdd](https://github.com/amah/app-framework/commit/e855bdd))
|
|
29
|
+
- Rename package scope from @amk to @hamak and configure npm publishing ([b6040b5](https://github.com/amah/app-framework/commit/b6040b5))
|
|
30
|
+
- Add hybrid local/CI-CD development workflow with bun link ([d09f528](https://github.com/amah/app-framework/commit/d09f528))
|
|
31
|
+
- Add Turborepo for intelligent build orchestration and fix test type errors ([ba41db8](https://github.com/amah/app-framework/commit/ba41db8))
|
|
32
|
+
- Add Redux store integration with ui-store package and demo ([e5aafa8](https://github.com/amah/app-framework/commit/e5aafa8))
|
|
33
|
+
|
|
34
|
+
### 🩹 Fixes
|
|
35
|
+
|
|
36
|
+
- move git config to top-level release.git in nx.json ([1bb2187](https://github.com/amah/app-framework/commit/1bb2187))
|
|
37
|
+
|
|
38
|
+
### ❤️ Thank You
|
|
39
|
+
|
|
40
|
+
- Amah
|
|
41
|
+
- Claude
|
|
@@ -0,0 +1,311 @@
|
|
|
1
|
+
import { fileSystemNodeInitialState } from '@hamak/ui-store-api';
|
|
2
|
+
import { DataUpdater } from '../utils/data-updater';
|
|
3
|
+
import { produce, current, isDraft, original } from 'immer';
|
|
4
|
+
import { deepEqual } from '../utils/deep-equal';
|
|
5
|
+
/**
|
|
6
|
+
* Convert path to steps array
|
|
7
|
+
*/
|
|
8
|
+
export function pathSteps(path) {
|
|
9
|
+
if (Array.isArray(path)) {
|
|
10
|
+
return path;
|
|
11
|
+
}
|
|
12
|
+
return path.split('/').filter(s => s.trim().length > 0);
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Get parent path steps
|
|
16
|
+
*/
|
|
17
|
+
export function parentPathSteps(path) {
|
|
18
|
+
const steps = pathSteps(path);
|
|
19
|
+
return steps.length > 0 ? steps.slice(0, steps.length - 1) : steps;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Get filesystem node at path
|
|
23
|
+
*/
|
|
24
|
+
export function getFileSystemNode(fsNode, path) {
|
|
25
|
+
const steps = pathSteps(path);
|
|
26
|
+
let result = fsNode;
|
|
27
|
+
for (let i = 0; i < steps.length; i++) {
|
|
28
|
+
const step = steps[i];
|
|
29
|
+
if (result === undefined) {
|
|
30
|
+
return undefined;
|
|
31
|
+
}
|
|
32
|
+
else if (result.type === 'directory') {
|
|
33
|
+
result = result.children[step];
|
|
34
|
+
}
|
|
35
|
+
else {
|
|
36
|
+
return undefined;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return result;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Create a directory node
|
|
43
|
+
*/
|
|
44
|
+
function createDirectoryNode(step) {
|
|
45
|
+
return {
|
|
46
|
+
type: 'directory',
|
|
47
|
+
state: fileSystemNodeInitialState(),
|
|
48
|
+
name: step,
|
|
49
|
+
children: {}
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Create a file node
|
|
54
|
+
*/
|
|
55
|
+
function createFileNode(name, content, schema, state) {
|
|
56
|
+
return { type: 'file', name, content, schema, state };
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* FileSystem command handler - executes filesystem operations
|
|
60
|
+
*/
|
|
61
|
+
export class FileSystemCommandHandler {
|
|
62
|
+
constructor() {
|
|
63
|
+
this.contentCommandHandler = new FileContentCommandHandler();
|
|
64
|
+
}
|
|
65
|
+
execute(state, command) {
|
|
66
|
+
switch (command.name) {
|
|
67
|
+
case 'mkdir': return this.executeMkdir(state, command);
|
|
68
|
+
case 'set-file': return this.executeSetFile(state, command);
|
|
69
|
+
case 'update-file-content': return this.executeContentCommand(state, command);
|
|
70
|
+
case 'set-file-content': return this.executeSetContentCommand(state, command);
|
|
71
|
+
case 'remove': return this.executeRemove(state, command);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
executeMkdir(state, command) {
|
|
75
|
+
const { path, parents } = command;
|
|
76
|
+
const steps = pathSteps(path);
|
|
77
|
+
return produce(state, draft => {
|
|
78
|
+
const { root } = draft;
|
|
79
|
+
let dir = root;
|
|
80
|
+
for (let i = 0; i < steps.length; i++) {
|
|
81
|
+
const step = steps[i];
|
|
82
|
+
if (i + 1 === steps.length) {
|
|
83
|
+
// Last step is the element to create
|
|
84
|
+
if (dir.children[step] === undefined) {
|
|
85
|
+
dir.children[step] = createDirectoryNode(step);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
else {
|
|
89
|
+
let child = dir.children[step];
|
|
90
|
+
if (child === undefined) {
|
|
91
|
+
if (parents === true) {
|
|
92
|
+
child = createDirectoryNode(step);
|
|
93
|
+
dir.children[step] = child;
|
|
94
|
+
}
|
|
95
|
+
else {
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
else if (child.type === 'directory') {
|
|
100
|
+
dir = child;
|
|
101
|
+
}
|
|
102
|
+
else {
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
dir = child;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
executeRemove(state, command) {
|
|
111
|
+
const { path, recursive } = command;
|
|
112
|
+
const steps = pathSteps(path);
|
|
113
|
+
return produce(state, draft => {
|
|
114
|
+
const { root } = draft;
|
|
115
|
+
const parentDir = getFileSystemNode(root, steps.slice(0, steps.length - 1));
|
|
116
|
+
if (parentDir === undefined) {
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
if (parentDir.type === 'directory') {
|
|
120
|
+
const last = steps[steps.length - 1];
|
|
121
|
+
const child = parentDir.children[last];
|
|
122
|
+
if ((child === null || child === void 0 ? void 0 : child.type) === 'directory'
|
|
123
|
+
&& Object.keys(child.children).length > 0
|
|
124
|
+
&& !(recursive === true)) {
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
delete parentDir.children[last];
|
|
128
|
+
}
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
executeSetFile(state, command) {
|
|
132
|
+
const { path, content, schema, override, contentIsPresent } = command;
|
|
133
|
+
const steps = pathSteps(path);
|
|
134
|
+
return produce(state, draft => {
|
|
135
|
+
const { root } = draft;
|
|
136
|
+
const parentPath = steps.slice(0, steps.length - 1);
|
|
137
|
+
const parentDir = getFileSystemNode(root, parentPath);
|
|
138
|
+
if (parentDir === undefined) {
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
if (parentDir.type === 'directory') {
|
|
142
|
+
const last = steps[steps.length - 1];
|
|
143
|
+
const child = parentDir.children[last];
|
|
144
|
+
if (child === undefined || override === true) {
|
|
145
|
+
parentDir.children[last] = createFileNode(last, content, schema, fileSystemNodeInitialState(contentIsPresent));
|
|
146
|
+
}
|
|
147
|
+
else {
|
|
148
|
+
console.warn(`File already exists at location`, path);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
else {
|
|
152
|
+
console.warn(`Parent file is not directory`, parentPath);
|
|
153
|
+
}
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
executeContentCommand(state, command) {
|
|
157
|
+
const { path, contentCommand } = command;
|
|
158
|
+
return produce(state, draft => {
|
|
159
|
+
const { root } = draft;
|
|
160
|
+
const fileNode = getFileSystemNode(root, path);
|
|
161
|
+
if (fileNode !== undefined) {
|
|
162
|
+
if (fileNode.type === 'file') {
|
|
163
|
+
this.contentCommandHandler.execute(fileNode, contentCommand);
|
|
164
|
+
const previousFile = getFileSystemNode(state.root, path);
|
|
165
|
+
const previousContent = (previousFile === null || previousFile === void 0 ? void 0 : previousFile.type) === "file" ? previousFile.content : undefined;
|
|
166
|
+
this.contentMayChange(fileNode);
|
|
167
|
+
}
|
|
168
|
+
else {
|
|
169
|
+
console.warn(`Not a file at location`, path);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
else {
|
|
173
|
+
console.warn(`No file found at location`, path);
|
|
174
|
+
}
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
contentMayChange(fileNode) {
|
|
178
|
+
const changed = !deepEqual(this.original(fileNode.content), this.current(fileNode.content));
|
|
179
|
+
if (changed) {
|
|
180
|
+
if (fileNode.state === undefined) {
|
|
181
|
+
fileNode.state = fileSystemNodeInitialState();
|
|
182
|
+
}
|
|
183
|
+
fileNode.state.contentHistory.push(this.original(fileNode.content));
|
|
184
|
+
// check compared to memo modification status
|
|
185
|
+
if (fileNode.state.memo !== undefined) {
|
|
186
|
+
const memo = fileNode.state.memo;
|
|
187
|
+
memo.modified = !deepEqual(this.current(memo.originalContent), this.current(fileNode.content));
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
executeSetContentCommand(state, command) {
|
|
192
|
+
const { path, content, fromRemote } = command;
|
|
193
|
+
return produce(state, draft => {
|
|
194
|
+
const { root } = draft;
|
|
195
|
+
const fileNode = getFileSystemNode(root, path);
|
|
196
|
+
if (fileNode !== undefined) {
|
|
197
|
+
if (fileNode.type === 'file') {
|
|
198
|
+
fileNode.content = content;
|
|
199
|
+
if (fileNode.state === undefined) {
|
|
200
|
+
fileNode.state = fileSystemNodeInitialState(true);
|
|
201
|
+
}
|
|
202
|
+
if (fromRemote) {
|
|
203
|
+
fileNode.state.contentLoaded = true;
|
|
204
|
+
if (fileNode.state.memo === undefined) {
|
|
205
|
+
fileNode.state.memo = { originalContent: { value: content }, modified: false };
|
|
206
|
+
}
|
|
207
|
+
else {
|
|
208
|
+
const memo = fileNode.state.memo;
|
|
209
|
+
memo.originalContent = { value: content };
|
|
210
|
+
memo.modified = false;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
else {
|
|
214
|
+
this.contentMayChange(fileNode);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
else {
|
|
218
|
+
console.warn(`Not a file at location`, path);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
else {
|
|
222
|
+
console.warn(`No file found at location`, path);
|
|
223
|
+
}
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
current(o) {
|
|
227
|
+
if (o === null || o === undefined) {
|
|
228
|
+
return o;
|
|
229
|
+
}
|
|
230
|
+
return isDraft(o) ? current(o) : o;
|
|
231
|
+
}
|
|
232
|
+
original(o) {
|
|
233
|
+
if (o === null || o === undefined) {
|
|
234
|
+
return o;
|
|
235
|
+
}
|
|
236
|
+
return isDraft(o) ? original(o) : o;
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
/**
|
|
240
|
+
* File content command handler - handles structure commands on file content
|
|
241
|
+
*/
|
|
242
|
+
export class FileContentCommandHandler {
|
|
243
|
+
constructor() {
|
|
244
|
+
this.updater = new DataUpdater();
|
|
245
|
+
}
|
|
246
|
+
execute(file, command) {
|
|
247
|
+
switch (command.name) {
|
|
248
|
+
case 'add-at':
|
|
249
|
+
{
|
|
250
|
+
this.executeAdd(file, command);
|
|
251
|
+
}
|
|
252
|
+
break;
|
|
253
|
+
case 'insert-at':
|
|
254
|
+
{
|
|
255
|
+
this.executeInsert(file, command);
|
|
256
|
+
}
|
|
257
|
+
break;
|
|
258
|
+
case 'set-at':
|
|
259
|
+
{
|
|
260
|
+
this.executeSet(file, command);
|
|
261
|
+
}
|
|
262
|
+
break;
|
|
263
|
+
case 'delete-at':
|
|
264
|
+
{
|
|
265
|
+
this.executeDelete(file, command);
|
|
266
|
+
}
|
|
267
|
+
break;
|
|
268
|
+
case 'batch-update':
|
|
269
|
+
{
|
|
270
|
+
this.executeBatch(file, command);
|
|
271
|
+
}
|
|
272
|
+
break;
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
executeDelete(file, command) {
|
|
276
|
+
const { itinerary } = command;
|
|
277
|
+
if (itinerary === undefined) {
|
|
278
|
+
file.content = undefined;
|
|
279
|
+
}
|
|
280
|
+
const { content } = file;
|
|
281
|
+
this.updater.executeDelete(content, itinerary);
|
|
282
|
+
}
|
|
283
|
+
executeSet(file, command) {
|
|
284
|
+
const { itinerary, value } = command;
|
|
285
|
+
if (itinerary === undefined) {
|
|
286
|
+
file.content = value;
|
|
287
|
+
}
|
|
288
|
+
const { content } = file;
|
|
289
|
+
this.updater.executeSet(content, itinerary, value);
|
|
290
|
+
}
|
|
291
|
+
executeAdd(file, command) {
|
|
292
|
+
const { itinerary, value } = command;
|
|
293
|
+
const { content } = file;
|
|
294
|
+
this.updater.executeAdd(content, itinerary, value);
|
|
295
|
+
}
|
|
296
|
+
executeInsert(file, command) {
|
|
297
|
+
const { itinerary, value } = command;
|
|
298
|
+
if (itinerary === undefined) {
|
|
299
|
+
return;
|
|
300
|
+
}
|
|
301
|
+
const { content } = file;
|
|
302
|
+
this.updater.executeInsert(content, itinerary, value);
|
|
303
|
+
}
|
|
304
|
+
/**
|
|
305
|
+
* Executes a batch of commands, rebasing each on previous mutations.
|
|
306
|
+
*/
|
|
307
|
+
executeBatch(file, command) {
|
|
308
|
+
const { content } = file;
|
|
309
|
+
this.updater.executeBatch(content, command);
|
|
310
|
+
}
|
|
311
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Helper class for creating structure node commands
|
|
3
|
+
*/
|
|
4
|
+
export class StructureNodeCommandHelper {
|
|
5
|
+
addNode(itinerary, value, prototypes) {
|
|
6
|
+
return { name: 'add-at', itinerary, value, prototypes };
|
|
7
|
+
}
|
|
8
|
+
insertNode(itinerary, value, prototypes) {
|
|
9
|
+
return { name: 'insert-at', itinerary, value, prototypes };
|
|
10
|
+
}
|
|
11
|
+
setNode(itinerary, value, prototypes) {
|
|
12
|
+
return { name: 'set-at', itinerary, value, prototypes };
|
|
13
|
+
}
|
|
14
|
+
deleteNode(itinerary) {
|
|
15
|
+
return { name: 'delete-at', itinerary };
|
|
16
|
+
}
|
|
17
|
+
batchUpdate(commands, autoRebase = false) {
|
|
18
|
+
return { name: 'batch-update', commands, autoRebase };
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
export const structs = new StructureNodeCommandHelper();
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { createSelector } from '@reduxjs/toolkit';
|
|
2
|
+
import { FileSystemCommandHandler, pathSteps } from '../commands/fs-commands';
|
|
3
|
+
import { fileSystemNodeInitialState } from '@hamak/ui-store-api';
|
|
4
|
+
/**
|
|
5
|
+
* Factory function to create a FileSystemAdapter
|
|
6
|
+
*/
|
|
7
|
+
export function createFileSystemAdapter(sliceName) {
|
|
8
|
+
return new FileSystemAdapter(sliceName);
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* FileSystemAdapter - Manages Redux state for virtual filesystem
|
|
12
|
+
*/
|
|
13
|
+
export class FileSystemAdapter {
|
|
14
|
+
constructor(sliceName) {
|
|
15
|
+
this.sliceName = sliceName;
|
|
16
|
+
this.actions = new FileSystemNodeActions(sliceName);
|
|
17
|
+
this.commandHandler = new FileSystemCommandHandler();
|
|
18
|
+
this.reducer = (state = this.getInitialState(), action) => {
|
|
19
|
+
if (this.actions.isFileSystemNodeAction(action)) {
|
|
20
|
+
return this.commandHandler.execute(state, action.command);
|
|
21
|
+
}
|
|
22
|
+
return state;
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
getInitialState() {
|
|
26
|
+
return { root: { type: 'directory', name: '', children: {}, state: fileSystemNodeInitialState() } };
|
|
27
|
+
}
|
|
28
|
+
getActions() {
|
|
29
|
+
return this.actions;
|
|
30
|
+
}
|
|
31
|
+
getReducer(extraReducers) {
|
|
32
|
+
if (extraReducers === undefined) {
|
|
33
|
+
return this.reducer;
|
|
34
|
+
}
|
|
35
|
+
return (state, action) => this.reducer(extraReducers(state, action), action);
|
|
36
|
+
}
|
|
37
|
+
createSelector(fileSystemSelector, path) {
|
|
38
|
+
const rootSelector = createSelector(fileSystemSelector, fs => fs === null || fs === void 0 ? void 0 : fs.root);
|
|
39
|
+
const steps = pathSteps(path);
|
|
40
|
+
if (steps === undefined || steps.length === 0) {
|
|
41
|
+
return rootSelector;
|
|
42
|
+
}
|
|
43
|
+
return steps.reduce((acc, step) => {
|
|
44
|
+
return createSelector(acc, o => {
|
|
45
|
+
if (o === undefined || o.type === 'file') {
|
|
46
|
+
return o;
|
|
47
|
+
}
|
|
48
|
+
else {
|
|
49
|
+
return o.children[step];
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
}, rootSelector);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* FileSystemNodeActions - Action creators for filesystem operations
|
|
57
|
+
*/
|
|
58
|
+
export class FileSystemNodeActions {
|
|
59
|
+
constructor(sliceName) {
|
|
60
|
+
this.sliceName = sliceName;
|
|
61
|
+
this._mkdirType = `${this.sliceName}/createDirectoryNode`;
|
|
62
|
+
this._setFileType = `${this.sliceName}/createFileNode`;
|
|
63
|
+
this._removeType = `${this.sliceName}/removeNode`;
|
|
64
|
+
this._updateFileContentType = `${this.sliceName}/updateFileContent`;
|
|
65
|
+
this._setFileContentType = `${this.sliceName}/setFileContent`;
|
|
66
|
+
this.actionTypeSet = new Set([
|
|
67
|
+
this._mkdirType,
|
|
68
|
+
this._setFileType,
|
|
69
|
+
this._removeType,
|
|
70
|
+
this._updateFileContentType,
|
|
71
|
+
this._setFileContentType
|
|
72
|
+
]);
|
|
73
|
+
}
|
|
74
|
+
get mkdirType() {
|
|
75
|
+
return this._mkdirType;
|
|
76
|
+
}
|
|
77
|
+
get setFileType() {
|
|
78
|
+
return this._setFileType;
|
|
79
|
+
}
|
|
80
|
+
get removeType() {
|
|
81
|
+
return this._removeType;
|
|
82
|
+
}
|
|
83
|
+
get updateFileContentType() {
|
|
84
|
+
return this._updateFileContentType;
|
|
85
|
+
}
|
|
86
|
+
get setFileContentType() {
|
|
87
|
+
return this._setFileContentType;
|
|
88
|
+
}
|
|
89
|
+
isFileSystemNodeAction(action) {
|
|
90
|
+
return this.actionTypeSet.has(action.type);
|
|
91
|
+
}
|
|
92
|
+
mkdir(path, parents) {
|
|
93
|
+
return {
|
|
94
|
+
type: this.mkdirType,
|
|
95
|
+
command: { name: 'mkdir', path, parents }
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
setFile(path, content, schema, params = { override: true, contentIsPresent: true }) {
|
|
99
|
+
const { override, contentIsPresent } = params;
|
|
100
|
+
return {
|
|
101
|
+
type: this.setFileType,
|
|
102
|
+
command: { name: 'set-file', path, content, schema, override, contentIsPresent }
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
updateFileContent(path, contentCommand) {
|
|
106
|
+
return {
|
|
107
|
+
type: this.updateFileContentType,
|
|
108
|
+
command: { name: 'update-file-content', path, contentCommand }
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
setFileContent(path, content, fromRemote = false) {
|
|
112
|
+
return {
|
|
113
|
+
type: this.setFileContentType,
|
|
114
|
+
command: { name: 'set-file-content', path, content, fromRemote }
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
removeNode(path, recursive) {
|
|
118
|
+
return {
|
|
119
|
+
type: this.removeType,
|
|
120
|
+
command: { name: 'remove', path, recursive }
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { createSelector } from '@reduxjs/toolkit';
|
|
2
|
+
import { Pathway, RelativePathwayResolver } from '@hamak/navigation-utils';
|
|
3
|
+
/**
|
|
4
|
+
* StoreFileSystemFacade - High-level API for filesystem operations
|
|
5
|
+
*/
|
|
6
|
+
export class StoreFileSystemFacade {
|
|
7
|
+
constructor(fileSystemSelector, adapter) {
|
|
8
|
+
this.fileSystemSelector = fileSystemSelector;
|
|
9
|
+
this.adapter = adapter;
|
|
10
|
+
}
|
|
11
|
+
getActions() {
|
|
12
|
+
return this.adapter.getActions();
|
|
13
|
+
}
|
|
14
|
+
createSelector(path) {
|
|
15
|
+
return this.adapter.createSelector(this.fileSystemSelector, path);
|
|
16
|
+
}
|
|
17
|
+
createFileSelector(path) {
|
|
18
|
+
const nodeSelector = this.adapter.createSelector(this.fileSystemSelector, path);
|
|
19
|
+
return createSelector([nodeSelector], (f) => {
|
|
20
|
+
if ((f === null || f === void 0 ? void 0 : f.type) === 'file') {
|
|
21
|
+
return f;
|
|
22
|
+
}
|
|
23
|
+
else {
|
|
24
|
+
return undefined;
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
selectFromRoot(state, path = []) {
|
|
29
|
+
const fileSystemState = this.fileSystemSelector(state);
|
|
30
|
+
if (fileSystemState === undefined || fileSystemState === null) {
|
|
31
|
+
return undefined;
|
|
32
|
+
}
|
|
33
|
+
else {
|
|
34
|
+
const root = fileSystemState.root;
|
|
35
|
+
return (path !== null && path !== void 0 ? path : []).reduce((acc, step) => {
|
|
36
|
+
if (acc === undefined || acc === null) {
|
|
37
|
+
return undefined;
|
|
38
|
+
}
|
|
39
|
+
else {
|
|
40
|
+
switch (acc.type) {
|
|
41
|
+
case "directory": return acc.children[step];
|
|
42
|
+
case "file": return undefined;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}, root);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
selectFileFromRoot(state, path = []) {
|
|
49
|
+
const fsNode = this.selectFromRoot(state, path);
|
|
50
|
+
if (fsNode === undefined) {
|
|
51
|
+
return undefined;
|
|
52
|
+
}
|
|
53
|
+
else {
|
|
54
|
+
if (fsNode.type === "file") {
|
|
55
|
+
return fsNode;
|
|
56
|
+
}
|
|
57
|
+
else {
|
|
58
|
+
throw new Error(`Expecting file at path '${Pathway.of(path).toString()}' but got '${fsNode.type}'`);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* StoreFileSystemPathwayResolver - Pathway resolver for filesystem state
|
|
65
|
+
*/
|
|
66
|
+
export class StoreFileSystemPathwayResolver {
|
|
67
|
+
constructor(state, fs) {
|
|
68
|
+
this.state = state;
|
|
69
|
+
this.fs = fs;
|
|
70
|
+
}
|
|
71
|
+
resolve(path) {
|
|
72
|
+
const segments = Pathway.of(path).getSegments();
|
|
73
|
+
return this.fs.selectFromRoot(this.state, segments);
|
|
74
|
+
}
|
|
75
|
+
relativeTo(path) {
|
|
76
|
+
return new RelativePathwayResolver(this, path);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
// Core filesystem functionality
|
|
2
|
+
export * from './core/fs-adapter';
|
|
3
|
+
export * from './core/fs-facade';
|
|
4
|
+
// Commands
|
|
5
|
+
export * from './commands/fs-commands';
|
|
6
|
+
export * from './commands/structure-commands';
|
|
7
|
+
// Utilities
|
|
8
|
+
export * from './utils/data-updater';
|
|
9
|
+
export * from './utils/deep-equal';
|