@floegence/floe-webapp-core 0.3.2 → 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.
|
@@ -7,4 +7,4 @@ export { FileContextMenu, type FileContextMenuProps, type BuiltinContextMenuActi
|
|
|
7
7
|
export { Breadcrumb, type BreadcrumbProps } from './Breadcrumb';
|
|
8
8
|
export { FileBrowserToolbar, type FileBrowserToolbarProps } from './FileBrowserToolbar';
|
|
9
9
|
export { FolderIcon, FolderOpenIcon, FileIcon, CodeFileIcon, ImageFileIcon, DocumentFileIcon, ConfigFileIcon, StyleFileIcon, getFileIcon, } from './FileIcons';
|
|
10
|
-
export type { FileItem, ViewMode, SortField, SortDirection, SortConfig, FileBrowserContextValue, ContextMenuActionType, ContextMenuItem, ContextMenuEvent, ContextMenuCallbacks, } from './types';
|
|
10
|
+
export type { FileItem, ViewMode, SortField, SortDirection, SortConfig, FileBrowserContextValue, ContextMenuActionType, ContextMenuItem, ContextMenuEvent, ContextMenuCallbacks, OptimisticUpdateType, OptimisticRemove, OptimisticUpdate, OptimisticInsert, OptimisticOperation, ScrollPosition, } from './types';
|
|
@@ -82,6 +82,49 @@ export interface FilterMatchInfo {
|
|
|
82
82
|
/** Matched character indices in the name */
|
|
83
83
|
matchedIndices: number[];
|
|
84
84
|
}
|
|
85
|
+
/**
|
|
86
|
+
* Optimistic update operation types
|
|
87
|
+
*/
|
|
88
|
+
export type OptimisticUpdateType = 'remove' | 'update' | 'insert';
|
|
89
|
+
/**
|
|
90
|
+
* Optimistic remove operation
|
|
91
|
+
*/
|
|
92
|
+
export interface OptimisticRemove {
|
|
93
|
+
type: 'remove';
|
|
94
|
+
/** Paths to remove */
|
|
95
|
+
paths: string[];
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Optimistic update operation (rename/move)
|
|
99
|
+
*/
|
|
100
|
+
export interface OptimisticUpdate {
|
|
101
|
+
type: 'update';
|
|
102
|
+
/** Original path */
|
|
103
|
+
oldPath: string;
|
|
104
|
+
/** Updated item data */
|
|
105
|
+
updates: Partial<FileItem>;
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Optimistic insert operation (duplicate/copy/new)
|
|
109
|
+
*/
|
|
110
|
+
export interface OptimisticInsert {
|
|
111
|
+
type: 'insert';
|
|
112
|
+
/** Parent folder path where item will be inserted */
|
|
113
|
+
parentPath: string;
|
|
114
|
+
/** Item to insert */
|
|
115
|
+
item: FileItem;
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Union type for all optimistic operations
|
|
119
|
+
*/
|
|
120
|
+
export type OptimisticOperation = OptimisticRemove | OptimisticUpdate | OptimisticInsert;
|
|
121
|
+
/**
|
|
122
|
+
* Scroll position state
|
|
123
|
+
*/
|
|
124
|
+
export interface ScrollPosition {
|
|
125
|
+
top: number;
|
|
126
|
+
left: number;
|
|
127
|
+
}
|
|
85
128
|
/**
|
|
86
129
|
* File browser context value for internal state management
|
|
87
130
|
*/
|
|
@@ -114,4 +157,62 @@ export interface FileBrowserContextValue {
|
|
|
114
157
|
showContextMenu: (event: ContextMenuEvent) => void;
|
|
115
158
|
hideContextMenu: () => void;
|
|
116
159
|
openItem: (item: FileItem) => void;
|
|
160
|
+
/**
|
|
161
|
+
* Optimistically remove items from the file list.
|
|
162
|
+
* Call this before the actual delete operation for instant UI feedback.
|
|
163
|
+
* @param paths - Array of file/folder paths to remove
|
|
164
|
+
*/
|
|
165
|
+
optimisticRemove: (paths: string[]) => void;
|
|
166
|
+
/**
|
|
167
|
+
* Optimistically update an item (rename/move).
|
|
168
|
+
* Call this before the actual operation for instant UI feedback.
|
|
169
|
+
* @param oldPath - Original path of the item
|
|
170
|
+
* @param updates - Partial updates to apply (name, path, etc.)
|
|
171
|
+
*/
|
|
172
|
+
optimisticUpdate: (oldPath: string, updates: Partial<FileItem>) => void;
|
|
173
|
+
/**
|
|
174
|
+
* Optimistically insert a new item (duplicate/copy/create).
|
|
175
|
+
* Call this before the actual operation for instant UI feedback.
|
|
176
|
+
* @param parentPath - Parent folder path
|
|
177
|
+
* @param item - The new item to insert
|
|
178
|
+
*/
|
|
179
|
+
optimisticInsert: (parentPath: string, item: FileItem) => void;
|
|
180
|
+
/**
|
|
181
|
+
* Clear all pending optimistic updates.
|
|
182
|
+
* Call this after successful server confirmation to sync with real data.
|
|
183
|
+
*/
|
|
184
|
+
clearOptimisticUpdates: () => void;
|
|
185
|
+
/**
|
|
186
|
+
* Rollback all optimistic updates and restore original state.
|
|
187
|
+
* Call this when an operation fails to revert the UI.
|
|
188
|
+
*/
|
|
189
|
+
rollbackOptimisticUpdates: () => void;
|
|
190
|
+
/**
|
|
191
|
+
* Check if there are pending optimistic updates.
|
|
192
|
+
*/
|
|
193
|
+
hasOptimisticUpdates: Accessor<boolean>;
|
|
194
|
+
/**
|
|
195
|
+
* Register a scroll container element for position tracking.
|
|
196
|
+
* Pass this as a ref callback to your scrollable container.
|
|
197
|
+
*/
|
|
198
|
+
setScrollContainer: (el: HTMLElement | null) => void;
|
|
199
|
+
/**
|
|
200
|
+
* Get the current scroll position of the registered container.
|
|
201
|
+
*/
|
|
202
|
+
getScrollPosition: () => ScrollPosition;
|
|
203
|
+
/**
|
|
204
|
+
* Set the scroll position of the registered container.
|
|
205
|
+
* Useful for restoring position after data refresh.
|
|
206
|
+
*/
|
|
207
|
+
setScrollPosition: (position: ScrollPosition) => void;
|
|
208
|
+
/**
|
|
209
|
+
* Save current scroll position and return it.
|
|
210
|
+
* Convenience method that combines get + internal save.
|
|
211
|
+
*/
|
|
212
|
+
saveScrollPosition: () => ScrollPosition;
|
|
213
|
+
/**
|
|
214
|
+
* Restore the last saved scroll position.
|
|
215
|
+
* Call this after an operation completes to maintain user's view.
|
|
216
|
+
*/
|
|
217
|
+
restoreScrollPosition: () => void;
|
|
117
218
|
}
|
package/dist/index39.js
CHANGED
|
@@ -1,145 +1,236 @@
|
|
|
1
|
-
import { createComponent as
|
|
2
|
-
import { createContext as
|
|
3
|
-
import { deferNonBlocking as
|
|
4
|
-
const
|
|
5
|
-
function
|
|
6
|
-
if (!
|
|
7
|
-
const
|
|
8
|
-
let
|
|
9
|
-
for (const
|
|
10
|
-
const
|
|
11
|
-
if (
|
|
12
|
-
|
|
1
|
+
import { createComponent as he } from "solid-js/web";
|
|
2
|
+
import { createContext as me, createSignal as a, createMemo as N, useContext as we } from "solid-js";
|
|
3
|
+
import { deferNonBlocking as I } from "./index68.js";
|
|
4
|
+
const V = me();
|
|
5
|
+
function Q(r, p) {
|
|
6
|
+
if (!p) return [];
|
|
7
|
+
const x = r.toLowerCase(), w = p.toLowerCase(), h = [];
|
|
8
|
+
let g = 0;
|
|
9
|
+
for (const S of w) {
|
|
10
|
+
const m = x.indexOf(S, g);
|
|
11
|
+
if (m === -1) return null;
|
|
12
|
+
h.push(m), g = m + 1;
|
|
13
13
|
}
|
|
14
|
-
return
|
|
14
|
+
return h;
|
|
15
15
|
}
|
|
16
|
-
function
|
|
17
|
-
const [
|
|
16
|
+
function Ce(r) {
|
|
17
|
+
const [p, x] = a(r.initialPath ?? "/"), [w, h] = a(/* @__PURE__ */ new Set()), [g, S] = a(r.initialViewMode ?? "list"), [m, j] = a({
|
|
18
18
|
field: "name",
|
|
19
19
|
direction: "asc"
|
|
20
|
-
}), [
|
|
20
|
+
}), [M, O] = a(/* @__PURE__ */ new Set(["/"])), [R, D] = a(!1), [G, k] = a(null), [v, b] = a(""), [H, B] = a(!1), [z, P] = a([]);
|
|
21
|
+
let u = null, C = {
|
|
22
|
+
top: 0,
|
|
23
|
+
left: 0
|
|
24
|
+
};
|
|
25
|
+
const A = () => r.files, c = (e) => {
|
|
21
26
|
const t = (e ?? "").trim();
|
|
22
27
|
return t === "" ? "/" : t;
|
|
23
|
-
},
|
|
24
|
-
const
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
28
|
+
}, T = (e) => {
|
|
29
|
+
const t = c(e);
|
|
30
|
+
if (t === "/") return "/";
|
|
31
|
+
const n = t.split("/").filter(Boolean);
|
|
32
|
+
return n.pop(), n.length ? "/" + n.join("/") : "/";
|
|
33
|
+
}, J = (e, t) => {
|
|
34
|
+
const n = z();
|
|
35
|
+
if (n.length === 0) return e;
|
|
36
|
+
let s = [...e];
|
|
37
|
+
const d = c(t);
|
|
38
|
+
for (const o of n)
|
|
39
|
+
switch (o.type) {
|
|
40
|
+
case "remove": {
|
|
41
|
+
const l = new Set(o.paths.map(c));
|
|
42
|
+
s = s.filter((i) => !l.has(c(i.path)));
|
|
43
|
+
break;
|
|
44
|
+
}
|
|
45
|
+
case "update": {
|
|
46
|
+
const l = c(o.oldPath), i = s.findIndex((f) => c(f.path) === l);
|
|
47
|
+
if (i !== -1) {
|
|
48
|
+
const f = o.updates.path ?? s[i].path;
|
|
49
|
+
T(f) === d ? s[i] = {
|
|
50
|
+
...s[i],
|
|
51
|
+
...o.updates
|
|
52
|
+
} : s.splice(i, 1);
|
|
53
|
+
} else {
|
|
54
|
+
const f = o.updates.path;
|
|
55
|
+
f && T(f);
|
|
56
|
+
}
|
|
57
|
+
break;
|
|
58
|
+
}
|
|
59
|
+
case "insert": {
|
|
60
|
+
c(o.parentPath) === d && (s.some((i) => c(i.path) === c(o.item.path)) || s.push(o.item));
|
|
61
|
+
break;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return s;
|
|
65
|
+
}, K = N(() => {
|
|
66
|
+
const e = /* @__PURE__ */ new Map(), t = (s) => {
|
|
67
|
+
var d;
|
|
68
|
+
for (const o of s)
|
|
69
|
+
o.type === "folder" && (e.set(c(o.path), o.children ?? []), (d = o.children) != null && d.length && t(o.children));
|
|
70
|
+
}, n = A();
|
|
29
71
|
return e.set("/", n), t(n), e;
|
|
30
|
-
}),
|
|
31
|
-
const e =
|
|
32
|
-
let t =
|
|
33
|
-
|
|
34
|
-
n
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
72
|
+
}), U = N(() => {
|
|
73
|
+
const e = c(p());
|
|
74
|
+
let t = K().get(e) ?? [];
|
|
75
|
+
t = J(t, e);
|
|
76
|
+
const n = v().trim();
|
|
77
|
+
n && (t = t.filter((o) => Q(o.name, n) !== null));
|
|
78
|
+
const s = m();
|
|
79
|
+
return [...t].sort((o, l) => {
|
|
80
|
+
var f, F;
|
|
81
|
+
if (o.type !== l.type)
|
|
82
|
+
return o.type === "folder" ? -1 : 1;
|
|
83
|
+
let i = 0;
|
|
84
|
+
switch (s.field) {
|
|
42
85
|
case "name":
|
|
43
|
-
|
|
86
|
+
i = o.name.localeCompare(l.name);
|
|
44
87
|
break;
|
|
45
88
|
case "size":
|
|
46
|
-
|
|
89
|
+
i = (o.size ?? 0) - (l.size ?? 0);
|
|
47
90
|
break;
|
|
48
91
|
case "modifiedAt":
|
|
49
|
-
|
|
92
|
+
i = (((f = o.modifiedAt) == null ? void 0 : f.getTime()) ?? 0) - (((F = l.modifiedAt) == null ? void 0 : F.getTime()) ?? 0);
|
|
50
93
|
break;
|
|
51
94
|
case "type":
|
|
52
|
-
|
|
95
|
+
i = (o.extension ?? "").localeCompare(l.extension ?? "");
|
|
53
96
|
break;
|
|
54
97
|
}
|
|
55
|
-
return
|
|
98
|
+
return s.direction === "asc" ? i : -i;
|
|
56
99
|
});
|
|
57
|
-
}),
|
|
58
|
-
var
|
|
59
|
-
const t =
|
|
60
|
-
|
|
61
|
-
const n =
|
|
62
|
-
|
|
63
|
-
},
|
|
64
|
-
const e =
|
|
100
|
+
}), y = (e) => {
|
|
101
|
+
var s;
|
|
102
|
+
const t = c(e);
|
|
103
|
+
x(t), h(/* @__PURE__ */ new Set()), b(""), B(!1);
|
|
104
|
+
const n = r.onSelect;
|
|
105
|
+
I(() => n == null ? void 0 : n([])), (s = r.onNavigate) == null || s.call(r, t);
|
|
106
|
+
}, W = () => {
|
|
107
|
+
const e = p();
|
|
65
108
|
if (e === "/" || e === "") return;
|
|
66
109
|
const t = e.split("/").filter(Boolean);
|
|
67
|
-
t.pop(),
|
|
68
|
-
},
|
|
69
|
-
e.type === "folder" && (
|
|
110
|
+
t.pop(), y(t.length ? "/" + t.join("/") : "/");
|
|
111
|
+
}, E = (e) => {
|
|
112
|
+
e.type === "folder" && (y(e.path), O((t) => {
|
|
70
113
|
const n = new Set(t);
|
|
71
114
|
return n.add(e.path), n;
|
|
72
115
|
}));
|
|
73
|
-
},
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
116
|
+
}, X = (e, t = !1) => {
|
|
117
|
+
const n = w(), s = t ? new Set(n) : /* @__PURE__ */ new Set();
|
|
118
|
+
t ? s.has(e) ? s.delete(e) : s.add(e) : (s.clear(), s.add(e)), h(s);
|
|
119
|
+
const d = U().filter((l) => s.has(l.id)), o = r.onSelect;
|
|
120
|
+
I(() => o == null ? void 0 : o(d));
|
|
121
|
+
}, Y = () => {
|
|
122
|
+
h(/* @__PURE__ */ new Set());
|
|
123
|
+
const e = r.onSelect;
|
|
124
|
+
I(() => e == null ? void 0 : e([]));
|
|
125
|
+
}, Z = (e) => w().has(e), _ = (e) => {
|
|
126
|
+
O((t) => {
|
|
127
|
+
const n = new Set(t);
|
|
128
|
+
return n.has(e) ? n.delete(e) : n.add(e), n;
|
|
129
|
+
});
|
|
130
|
+
}, $ = (e) => M().has(e), ee = () => D((e) => !e), te = (e) => k(e), ne = () => k(null), oe = (e) => {
|
|
131
|
+
b(e);
|
|
132
|
+
}, se = (e) => {
|
|
133
|
+
const t = v().trim();
|
|
134
|
+
if (!t) return null;
|
|
135
|
+
const n = Q(e, t);
|
|
136
|
+
return n ? {
|
|
137
|
+
matchedIndices: n
|
|
138
|
+
} : null;
|
|
139
|
+
}, re = (e) => {
|
|
140
|
+
var t;
|
|
141
|
+
e.type === "folder" ? E(e) : (t = r.onOpen) == null || t.call(r, e);
|
|
142
|
+
}, ie = (e) => {
|
|
143
|
+
e.length !== 0 && P((t) => [...t, {
|
|
144
|
+
type: "remove",
|
|
145
|
+
paths: e
|
|
146
|
+
}]);
|
|
147
|
+
}, ce = (e, t) => {
|
|
148
|
+
P((n) => [...n, {
|
|
149
|
+
type: "update",
|
|
150
|
+
oldPath: e,
|
|
151
|
+
updates: t
|
|
152
|
+
}]);
|
|
153
|
+
}, le = (e, t) => {
|
|
154
|
+
P((n) => [...n, {
|
|
155
|
+
type: "insert",
|
|
156
|
+
parentPath: e,
|
|
157
|
+
item: t
|
|
158
|
+
}]);
|
|
159
|
+
}, ae = () => {
|
|
160
|
+
P([]);
|
|
161
|
+
}, de = () => {
|
|
162
|
+
P([]);
|
|
163
|
+
}, fe = () => z().length > 0, ue = (e) => {
|
|
164
|
+
u = e;
|
|
165
|
+
}, L = () => u ? {
|
|
166
|
+
top: u.scrollTop,
|
|
167
|
+
left: u.scrollLeft
|
|
168
|
+
} : {
|
|
169
|
+
top: 0,
|
|
170
|
+
left: 0
|
|
171
|
+
}, q = (e) => {
|
|
172
|
+
u && (u.scrollTop = e.top, u.scrollLeft = e.left);
|
|
173
|
+
}, pe = {
|
|
174
|
+
currentPath: p,
|
|
175
|
+
setCurrentPath: y,
|
|
176
|
+
navigateUp: W,
|
|
177
|
+
navigateTo: E,
|
|
178
|
+
selectedItems: () => w(),
|
|
179
|
+
selectItem: X,
|
|
180
|
+
clearSelection: Y,
|
|
181
|
+
isSelected: Z,
|
|
182
|
+
viewMode: g,
|
|
183
|
+
setViewMode: S,
|
|
184
|
+
sortConfig: m,
|
|
185
|
+
setSortConfig: j,
|
|
186
|
+
expandedFolders: M,
|
|
187
|
+
toggleFolder: _,
|
|
188
|
+
isExpanded: $,
|
|
189
|
+
files: A,
|
|
190
|
+
currentFiles: U,
|
|
191
|
+
filterQuery: v,
|
|
192
|
+
setFilterQuery: oe,
|
|
193
|
+
isFilterActive: H,
|
|
194
|
+
setFilterActive: B,
|
|
195
|
+
getFilterMatch: se,
|
|
196
|
+
sidebarCollapsed: R,
|
|
197
|
+
toggleSidebar: ee,
|
|
198
|
+
contextMenu: G,
|
|
199
|
+
showContextMenu: te,
|
|
200
|
+
hideContextMenu: ne,
|
|
201
|
+
openItem: re,
|
|
202
|
+
// Optimistic updates
|
|
203
|
+
optimisticRemove: ie,
|
|
204
|
+
optimisticUpdate: ce,
|
|
205
|
+
optimisticInsert: le,
|
|
206
|
+
clearOptimisticUpdates: ae,
|
|
207
|
+
rollbackOptimisticUpdates: de,
|
|
208
|
+
hasOptimisticUpdates: fe,
|
|
209
|
+
// Scroll position management
|
|
210
|
+
setScrollContainer: ue,
|
|
211
|
+
getScrollPosition: L,
|
|
212
|
+
setScrollPosition: q,
|
|
213
|
+
saveScrollPosition: () => (C = L(), C),
|
|
214
|
+
restoreScrollPosition: () => {
|
|
215
|
+
requestAnimationFrame(() => {
|
|
216
|
+
q(C);
|
|
100
217
|
});
|
|
101
|
-
},
|
|
102
|
-
isExpanded: (e) => y().has(e),
|
|
103
|
-
files: B,
|
|
104
|
-
currentFiles: z,
|
|
105
|
-
filterQuery: x,
|
|
106
|
-
setFilterQuery: (e) => {
|
|
107
|
-
I(e);
|
|
108
|
-
},
|
|
109
|
-
isFilterActive: N,
|
|
110
|
-
setFilterActive: P,
|
|
111
|
-
getFilterMatch: (e) => {
|
|
112
|
-
const t = x().trim();
|
|
113
|
-
if (!t) return null;
|
|
114
|
-
const n = E(e, t);
|
|
115
|
-
return n ? {
|
|
116
|
-
matchedIndices: n
|
|
117
|
-
} : null;
|
|
118
|
-
},
|
|
119
|
-
sidebarCollapsed: V,
|
|
120
|
-
toggleSidebar: () => q((e) => !e),
|
|
121
|
-
contextMenu: L,
|
|
122
|
-
showContextMenu: (e) => v(e),
|
|
123
|
-
hideContextMenu: () => v(null),
|
|
124
|
-
openItem: (e) => {
|
|
125
|
-
var t;
|
|
126
|
-
e.type === "folder" ? b(e) : (t = o.onOpen) == null || t.call(o, e);
|
|
127
218
|
}
|
|
128
219
|
};
|
|
129
|
-
return
|
|
130
|
-
value:
|
|
220
|
+
return he(V.Provider, {
|
|
221
|
+
value: pe,
|
|
131
222
|
get children() {
|
|
132
|
-
return
|
|
223
|
+
return r.children;
|
|
133
224
|
}
|
|
134
225
|
});
|
|
135
226
|
}
|
|
136
|
-
function
|
|
137
|
-
const
|
|
138
|
-
if (!
|
|
227
|
+
function ye() {
|
|
228
|
+
const r = we(V);
|
|
229
|
+
if (!r)
|
|
139
230
|
throw new Error("useFileBrowser must be used within a FileBrowserProvider");
|
|
140
|
-
return
|
|
231
|
+
return r;
|
|
141
232
|
}
|
|
142
233
|
export {
|
|
143
|
-
|
|
144
|
-
|
|
234
|
+
Ce as FileBrowserProvider,
|
|
235
|
+
ye as useFileBrowser
|
|
145
236
|
};
|