@rool-dev/svelte 0.10.2-dev.47747e3 → 0.10.2-dev.491e451
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/README.md +24 -14
- package/dist/channel.svelte.d.ts +10 -18
- package/dist/channel.svelte.d.ts.map +1 -1
- package/dist/channel.svelte.js +86 -160
- package/dist/file-tree.svelte.d.ts +91 -0
- package/dist/file-tree.svelte.d.ts.map +1 -0
- package/dist/file-tree.svelte.js +418 -0
- package/dist/index.d.ts +4 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/rool.svelte.d.ts +21 -0
- package/dist/rool.svelte.d.ts.map +1 -1
- package/dist/rool.svelte.js +33 -0
- package/dist/space.svelte.d.ts +2 -0
- package/dist/space.svelte.d.ts.map +1 -1
- package/dist/space.svelte.js +6 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -2,8 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
Svelte 5 bindings for Rool Spaces. Adds reactive state to the SDK using `$state` runes.
|
|
4
4
|
|
|
5
|
-
> **Building a new Rool extension?** Start with [`@rool-dev/extension`](/extension/) — it includes a reactive channel and handles hosting for you. This package is for integrating Rool into an existing Svelte application that manages its own auth, routing, and build setup.
|
|
6
|
-
|
|
7
5
|
**Requires Svelte 5.** For core concepts (objects, references, AI placeholders, undo/redo), see the [SDK documentation](../sdk/README.md).
|
|
8
6
|
|
|
9
7
|
## Installation
|
|
@@ -55,9 +53,10 @@ The Svelte wrapper adds reactive state on top of the SDK:
|
|
|
55
53
|
| `rool.spacesError` | Error from loading spaces |
|
|
56
54
|
| `rool.connectionState` | SSE connection state |
|
|
57
55
|
| `rool.userStorage` | User storage (cross-device preferences) |
|
|
56
|
+
| `space.fileTree` | Canonical reactive WebDAV tree for `/`, including `/space` objects and `/rool-drive` user files |
|
|
58
57
|
| `channel.interactions` | Channel interactions (auto-updates) |
|
|
59
|
-
| `channel.objectLocations` | All object locations
|
|
60
|
-
| `channel.collections` | Collection
|
|
58
|
+
| `channel.objectLocations` | All object locations derived from `space.fileTree` |
|
|
59
|
+
| `channel.collections` | Collection directories derived from `space.fileTree` |
|
|
61
60
|
| `channel.conversations` | Conversations in this channel (auto-updates on create/delete/rename) |
|
|
62
61
|
| `thread.interactions` | Interactions for a specific conversation (auto-updates) |
|
|
63
62
|
| `watch.objects` | Objects matching a filter (auto-updates) |
|
|
@@ -65,6 +64,22 @@ The Svelte wrapper adds reactive state on top of the SDK:
|
|
|
65
64
|
|
|
66
65
|
Everything else passes through to the SDK directly. See the [SDK documentation](../sdk/README.md) for full API details.
|
|
67
66
|
|
|
67
|
+
### Reactive File Tree
|
|
68
|
+
|
|
69
|
+
Every `ReactiveSpace` owns a canonical reactive WebDAV tree. It is kept current with server `filesChanged`/`filesReset` events and WebDAV `sync-collection`, so it covers both object files and user files without polling.
|
|
70
|
+
|
|
71
|
+
```ts
|
|
72
|
+
const space = await rool.openSpace(spaceId);
|
|
73
|
+
|
|
74
|
+
space.fileTree.nodes; // ReactiveFileNode[]
|
|
75
|
+
space.fileTree.byPath['/space']; // lookup by machine/WebDAV path
|
|
76
|
+
space.fileTree.childrenOf('/'); // /space and /rool-drive
|
|
77
|
+
space.fileTree.childrenOf('/rool-drive');
|
|
78
|
+
space.fileTree.objectLocations(); // object locations from /space/**/*.json
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
Use this tree when UI needs to react to both files and objects. Object helpers like `channel.object()`, `channel.watch()`, `channel.objectLocations`, and `channel.collections` are backed by this tree.
|
|
82
|
+
|
|
68
83
|
## API
|
|
69
84
|
|
|
70
85
|
### Lifecycle
|
|
@@ -161,7 +176,7 @@ space.close();
|
|
|
161
176
|
|
|
162
177
|
### ReactiveChannel
|
|
163
178
|
|
|
164
|
-
`space.openChannel()` returns a `ReactiveChannel` — the SDK's `RoolChannel` with reactive `interactions` and
|
|
179
|
+
`space.openChannel()` returns a `ReactiveChannel` — the SDK's `RoolChannel` with reactive `interactions` and file-tree-backed object helpers:
|
|
165
180
|
|
|
166
181
|
```svelte
|
|
167
182
|
<script>
|
|
@@ -258,12 +273,12 @@ Create auto-updating watches of objects filtered by field values:
|
|
|
258
273
|
{/if}
|
|
259
274
|
```
|
|
260
275
|
|
|
261
|
-
Watches automatically re-fetch when
|
|
276
|
+
Watches automatically re-fetch when matching object files change in `space.fileTree`.
|
|
262
277
|
|
|
263
278
|
**Lifecycle:** Watches are tied to their channel. Closing the channel stops all updates — existing watches will retain their last data but no longer refresh. Calling `channel.watch()` after `close()` throws.
|
|
264
279
|
|
|
265
280
|
```typescript
|
|
266
|
-
// Watch options
|
|
281
|
+
// Watch options
|
|
267
282
|
const articles = channel.watch({
|
|
268
283
|
collection: 'article',
|
|
269
284
|
where: { status: 'published' },
|
|
@@ -376,7 +391,6 @@ await channel.createObject('note', { text: 'Hello' }, { basename: 'welcome' })
|
|
|
376
391
|
await channel.updateObject(location, { data: { text: 'Updated' } })
|
|
377
392
|
await channel.moveObject(from, to)
|
|
378
393
|
await channel.deleteObjects([location])
|
|
379
|
-
await channel.findObjects({ collection: 'note' })
|
|
380
394
|
|
|
381
395
|
// AI
|
|
382
396
|
await channel.prompt('Summarize everything')
|
|
@@ -417,13 +431,10 @@ See the [SDK documentation](../sdk/README.md) for complete API details.
|
|
|
417
431
|
### Utilities
|
|
418
432
|
|
|
419
433
|
```typescript
|
|
420
|
-
import {
|
|
421
|
-
|
|
422
|
-
// 6-character alphanumeric basename
|
|
423
|
-
const basename = generateBasename();
|
|
434
|
+
import { loc, parseLocation, normalizeLocation } from '@rool-dev/svelte';
|
|
424
435
|
|
|
425
436
|
// Build / parse location strings
|
|
426
|
-
const location = loc('article',
|
|
437
|
+
const location = loc('article', 'welcome'); // '/space/article/welcome.json'
|
|
427
438
|
const parts = parseLocation(location); // { collection, basename }
|
|
428
439
|
const canonical = normalizeLocation('article/welcome'); // '/space/article/welcome.json'
|
|
429
440
|
```
|
|
@@ -450,7 +461,6 @@ import type {
|
|
|
450
461
|
ConversationInfo,
|
|
451
462
|
CurrentUser,
|
|
452
463
|
Interaction,
|
|
453
|
-
FindObjectsOptions,
|
|
454
464
|
PromptOptions,
|
|
455
465
|
CreateObjectOptions,
|
|
456
466
|
UpdateObjectOptions,
|
package/dist/channel.svelte.d.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import type { RoolChannel, RoolSpace, Interaction, RoolObject, ChannelInfo, ConversationInfo, ConversationHandle } from '@rool-dev/sdk';
|
|
2
|
+
import { ReactiveFileTree } from './file-tree.svelte.js';
|
|
2
3
|
/**
|
|
3
4
|
* Options for creating a reactive watch.
|
|
4
|
-
*
|
|
5
|
+
* Structured object filter for reactive updates.
|
|
5
6
|
*/
|
|
6
7
|
export interface WatchOptions {
|
|
7
8
|
/** Field requirements for exact matching */
|
|
@@ -14,16 +15,14 @@ export interface WatchOptions {
|
|
|
14
15
|
order?: 'asc' | 'desc';
|
|
15
16
|
}
|
|
16
17
|
/**
|
|
17
|
-
* A reactive watch of objects that auto-updates when matching
|
|
18
|
+
* A reactive watch of objects that auto-updates when matching object files change.
|
|
18
19
|
*/
|
|
19
20
|
declare class ReactiveWatchImpl {
|
|
20
21
|
#private;
|
|
21
22
|
objects: RoolObject[];
|
|
22
23
|
loading: boolean;
|
|
23
|
-
constructor(channel: RoolChannel, options: WatchOptions);
|
|
24
|
-
/**
|
|
25
|
-
* Re-fetch the watched objects from the channel.
|
|
26
|
-
*/
|
|
24
|
+
constructor(channel: RoolChannel, fileTree: ReactiveFileTree, options: WatchOptions);
|
|
25
|
+
/** Re-fetch matching objects using the canonical file tree for locations. */
|
|
27
26
|
refresh(): Promise<void>;
|
|
28
27
|
close(): void;
|
|
29
28
|
}
|
|
@@ -35,7 +34,7 @@ declare class ReactiveObjectImpl {
|
|
|
35
34
|
#private;
|
|
36
35
|
data: RoolObject | undefined;
|
|
37
36
|
loading: boolean;
|
|
38
|
-
constructor(channel: RoolChannel, location: string);
|
|
37
|
+
constructor(channel: RoolChannel, fileTree: ReactiveFileTree, location: string);
|
|
39
38
|
refresh(): Promise<void>;
|
|
40
39
|
close(): void;
|
|
41
40
|
}
|
|
@@ -52,10 +51,6 @@ declare class ReactiveConversationHandleImpl {
|
|
|
52
51
|
getSystemInstruction(): string | undefined;
|
|
53
52
|
setSystemInstruction(...args: Parameters<ConversationHandle['setSystemInstruction']>): Promise<void>;
|
|
54
53
|
rename(...args: Parameters<ConversationHandle['rename']>): Promise<void>;
|
|
55
|
-
findObjects(...args: Parameters<ConversationHandle['findObjects']>): Promise<{
|
|
56
|
-
objects: RoolObject[];
|
|
57
|
-
message: string;
|
|
58
|
-
}>;
|
|
59
54
|
createObject(...args: Parameters<ConversationHandle['createObject']>): Promise<{
|
|
60
55
|
object: RoolObject;
|
|
61
56
|
message: string;
|
|
@@ -90,7 +85,7 @@ declare class ReactiveChannelImpl {
|
|
|
90
85
|
objectLocations: string[];
|
|
91
86
|
collections: string[];
|
|
92
87
|
conversations: ConversationInfo[];
|
|
93
|
-
constructor(channel: RoolChannel);
|
|
88
|
+
constructor(channel: RoolChannel, fileTree: ReactiveFileTree);
|
|
94
89
|
get id(): string;
|
|
95
90
|
get name(): string;
|
|
96
91
|
get role(): import("@rool-dev/sdk").RoolUserRole;
|
|
@@ -100,16 +95,13 @@ declare class ReactiveChannelImpl {
|
|
|
100
95
|
get isReadOnly(): boolean;
|
|
101
96
|
get linkAccess(): import("@rool-dev/sdk").LinkAccess;
|
|
102
97
|
get extensionUrl(): string | null;
|
|
98
|
+
get extensionId(): string | null;
|
|
103
99
|
get manifest(): import("@rool-dev/sdk").ExtensionManifest | null;
|
|
104
100
|
get isClosed(): boolean;
|
|
105
101
|
close(): void;
|
|
106
102
|
getObject(...args: Parameters<RoolChannel['getObject']>): Promise<RoolObject | undefined>;
|
|
103
|
+
getObjects(...args: Parameters<RoolChannel['getObjects']>): Promise<import("@rool-dev/sdk").GetObjectsResult>;
|
|
107
104
|
stat(...args: Parameters<RoolChannel['stat']>): import("@rool-dev/sdk").RoolObjectStat | undefined;
|
|
108
|
-
findObjects(...args: Parameters<RoolChannel['findObjects']>): Promise<{
|
|
109
|
-
objects: RoolObject[];
|
|
110
|
-
message: string;
|
|
111
|
-
}>;
|
|
112
|
-
getObjectLocations(...args: Parameters<RoolChannel['getObjectLocations']>): string[];
|
|
113
105
|
createObject(...args: Parameters<RoolChannel['createObject']>): Promise<{
|
|
114
106
|
object: RoolObject;
|
|
115
107
|
message: string;
|
|
@@ -163,7 +155,7 @@ declare class ReactiveChannelImpl {
|
|
|
163
155
|
*/
|
|
164
156
|
watch(options: WatchOptions): ReactiveWatch;
|
|
165
157
|
}
|
|
166
|
-
export declare function wrapChannel(channel: RoolChannel): ReactiveChannel;
|
|
158
|
+
export declare function wrapChannel(channel: RoolChannel, fileTree: ReactiveFileTree): ReactiveChannel;
|
|
167
159
|
export type ReactiveChannel = ReactiveChannelImpl;
|
|
168
160
|
/**
|
|
169
161
|
* A reactive list of channels for a space that auto-updates via SSE events.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"channel.svelte.d.ts","sourceRoot":"","sources":["../src/channel.svelte.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"channel.svelte.d.ts","sourceRoot":"","sources":["../src/channel.svelte.ts"],"names":[],"mappings":"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"}
|
package/dist/channel.svelte.js
CHANGED
|
@@ -1,122 +1,76 @@
|
|
|
1
|
+
import { normalizeLocation } from '@rool-dev/sdk';
|
|
2
|
+
function eventTouchesObject(event, location, collection) {
|
|
3
|
+
if (event.reset)
|
|
4
|
+
return true;
|
|
5
|
+
for (const path of [...event.changedPaths, ...event.deletedPaths]) {
|
|
6
|
+
if (location && path === location)
|
|
7
|
+
return true;
|
|
8
|
+
if (!location && path.startsWith('/space/') && path.endsWith('.json')) {
|
|
9
|
+
if (!collection || path.startsWith(`/space/${collection}/`))
|
|
10
|
+
return true;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
return false;
|
|
14
|
+
}
|
|
15
|
+
function sameJsonValue(a, b) {
|
|
16
|
+
return JSON.stringify(a) === JSON.stringify(b);
|
|
17
|
+
}
|
|
18
|
+
async function watchObjectsFromTree(channel, fileTree, options) {
|
|
19
|
+
if (fileTree.loading)
|
|
20
|
+
await fileTree.ready();
|
|
21
|
+
const locations = fileTree.objectLocations({ collection: options.collection, order: options.order });
|
|
22
|
+
const objects = [];
|
|
23
|
+
for (const location of locations) {
|
|
24
|
+
const object = await channel.getObject(location);
|
|
25
|
+
if (!object)
|
|
26
|
+
continue;
|
|
27
|
+
if (options.where) {
|
|
28
|
+
let matches = true;
|
|
29
|
+
for (const [key, value] of Object.entries(options.where)) {
|
|
30
|
+
if (!sameJsonValue(object.body[key], value)) {
|
|
31
|
+
matches = false;
|
|
32
|
+
break;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
if (!matches)
|
|
36
|
+
continue;
|
|
37
|
+
}
|
|
38
|
+
objects.push(object);
|
|
39
|
+
if (options.limit !== undefined && objects.length >= options.limit)
|
|
40
|
+
break;
|
|
41
|
+
}
|
|
42
|
+
return objects;
|
|
43
|
+
}
|
|
1
44
|
/**
|
|
2
|
-
* A reactive watch of objects that auto-updates when matching
|
|
45
|
+
* A reactive watch of objects that auto-updates when matching object files change.
|
|
3
46
|
*/
|
|
4
47
|
class ReactiveWatchImpl {
|
|
5
48
|
#channel;
|
|
49
|
+
#fileTree;
|
|
6
50
|
#options;
|
|
7
51
|
#unsubscribers = [];
|
|
8
|
-
#currentLocations = new Set();
|
|
9
52
|
// Reactive state
|
|
10
53
|
objects = $state([]);
|
|
11
54
|
loading = $state(true);
|
|
12
|
-
constructor(channel, options) {
|
|
55
|
+
constructor(channel, fileTree, options) {
|
|
13
56
|
this.#channel = channel;
|
|
57
|
+
this.#fileTree = fileTree;
|
|
14
58
|
this.#options = options;
|
|
15
59
|
this.#setup();
|
|
16
60
|
}
|
|
17
61
|
#setup() {
|
|
18
|
-
// Initial fetch
|
|
19
62
|
this.refresh();
|
|
20
|
-
const
|
|
21
|
-
if (this.#
|
|
22
|
-
this.refresh();
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
this.#channel.on('objectCreated', onObjectCreated);
|
|
26
|
-
this.#unsubscribers.push(() => this.#channel.off('objectCreated', onObjectCreated));
|
|
27
|
-
const onObjectUpdated = ({ location, object }) => {
|
|
28
|
-
const wasInCollection = this.#currentLocations.has(location);
|
|
29
|
-
const nowMatches = this.#matches(object);
|
|
30
|
-
if (wasInCollection && nowMatches) {
|
|
31
|
-
// Update in place (merge to preserve fields from partial optimistic updates)
|
|
32
|
-
const index = this.objects.findIndex((o) => o.location === location);
|
|
33
|
-
if (index !== -1) {
|
|
34
|
-
this.objects[index] = {
|
|
35
|
-
...this.objects[index],
|
|
36
|
-
...object,
|
|
37
|
-
body: { ...this.objects[index].body, ...object.body },
|
|
38
|
-
};
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
else if (wasInCollection && !nowMatches) {
|
|
42
|
-
// Check if the mismatch is due to missing keys in body (partial optimistic update)
|
|
43
|
-
const where = this.#options.where;
|
|
44
|
-
const isPartialUpdate = where && Object.keys(where).some((key) => !(key in object.body));
|
|
45
|
-
if (isPartialUpdate) {
|
|
46
|
-
const index = this.objects.findIndex((o) => o.location === location);
|
|
47
|
-
if (index !== -1) {
|
|
48
|
-
this.objects[index] = {
|
|
49
|
-
...this.objects[index],
|
|
50
|
-
...object,
|
|
51
|
-
body: { ...this.objects[index].body, ...object.body },
|
|
52
|
-
};
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
else {
|
|
56
|
-
// Genuine mismatch — remove from collection
|
|
57
|
-
this.objects = this.objects.filter((o) => o.location !== location);
|
|
58
|
-
this.#currentLocations.delete(location);
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
else if (!wasInCollection && nowMatches) {
|
|
62
|
-
// Add to collection (re-fetch to respect limit/order)
|
|
63
|
-
this.refresh();
|
|
64
|
-
}
|
|
65
|
-
};
|
|
66
|
-
this.#channel.on('objectUpdated', onObjectUpdated);
|
|
67
|
-
this.#unsubscribers.push(() => this.#channel.off('objectUpdated', onObjectUpdated));
|
|
68
|
-
const onObjectDeleted = ({ location }) => {
|
|
69
|
-
if (this.#currentLocations.has(location)) {
|
|
70
|
-
this.objects = this.objects.filter((o) => o.location !== location);
|
|
71
|
-
this.#currentLocations.delete(location);
|
|
72
|
-
}
|
|
73
|
-
};
|
|
74
|
-
this.#channel.on('objectDeleted', onObjectDeleted);
|
|
75
|
-
this.#unsubscribers.push(() => this.#channel.off('objectDeleted', onObjectDeleted));
|
|
76
|
-
const onObjectMoved = ({ from, object }) => {
|
|
77
|
-
const wasInCollection = this.#currentLocations.has(from);
|
|
78
|
-
const nowMatches = this.#matches(object);
|
|
79
|
-
if (wasInCollection || nowMatches) {
|
|
80
|
-
this.refresh();
|
|
81
|
-
}
|
|
82
|
-
};
|
|
83
|
-
this.#channel.on('objectMoved', onObjectMoved);
|
|
84
|
-
this.#unsubscribers.push(() => this.#channel.off('objectMoved', onObjectMoved));
|
|
85
|
-
const onReset = () => this.refresh();
|
|
86
|
-
this.#channel.on('reset', onReset);
|
|
87
|
-
this.#unsubscribers.push(() => this.#channel.off('reset', onReset));
|
|
63
|
+
const unsubscribe = this.#fileTree.subscribe((event) => {
|
|
64
|
+
if (eventTouchesObject(event, undefined, this.#options.collection))
|
|
65
|
+
void this.refresh();
|
|
66
|
+
});
|
|
67
|
+
this.#unsubscribers.push(unsubscribe);
|
|
88
68
|
}
|
|
89
|
-
/**
|
|
90
|
-
* Check if an object matches the filter (collection + where on body).
|
|
91
|
-
*/
|
|
92
|
-
#matches(object) {
|
|
93
|
-
if (this.#options.collection && object.collection !== this.#options.collection)
|
|
94
|
-
return false;
|
|
95
|
-
const where = this.#options.where;
|
|
96
|
-
if (!where)
|
|
97
|
-
return true;
|
|
98
|
-
for (const [key, value] of Object.entries(where)) {
|
|
99
|
-
if (object.body[key] !== value)
|
|
100
|
-
return false;
|
|
101
|
-
}
|
|
102
|
-
return true;
|
|
103
|
-
}
|
|
104
|
-
/**
|
|
105
|
-
* Re-fetch the watched objects from the channel.
|
|
106
|
-
*/
|
|
69
|
+
/** Re-fetch matching objects using the canonical file tree for locations. */
|
|
107
70
|
async refresh() {
|
|
108
71
|
this.loading = true;
|
|
109
72
|
try {
|
|
110
|
-
|
|
111
|
-
where: this.#options.where,
|
|
112
|
-
collection: this.#options.collection,
|
|
113
|
-
limit: this.#options.limit,
|
|
114
|
-
order: this.#options.order,
|
|
115
|
-
ephemeral: true,
|
|
116
|
-
};
|
|
117
|
-
const { objects } = await this.#channel.findObjects(findOptions);
|
|
118
|
-
this.objects = objects;
|
|
119
|
-
this.#currentLocations = new Set(objects.map((o) => o.location));
|
|
73
|
+
this.objects = await watchObjectsFromTree(this.#channel, this.#fileTree, this.#options);
|
|
120
74
|
}
|
|
121
75
|
finally {
|
|
122
76
|
this.loading = false;
|
|
@@ -133,53 +87,29 @@ class ReactiveWatchImpl {
|
|
|
133
87
|
*/
|
|
134
88
|
class ReactiveObjectImpl {
|
|
135
89
|
#channel;
|
|
90
|
+
#fileTree;
|
|
136
91
|
#location;
|
|
137
92
|
#unsubscribers = [];
|
|
138
93
|
// Reactive state
|
|
139
94
|
data = $state(undefined);
|
|
140
95
|
loading = $state(true);
|
|
141
|
-
constructor(channel, location) {
|
|
96
|
+
constructor(channel, fileTree, location) {
|
|
142
97
|
this.#channel = channel;
|
|
143
|
-
this.#
|
|
98
|
+
this.#fileTree = fileTree;
|
|
99
|
+
this.#location = normalizeLocation(location);
|
|
144
100
|
this.#setup();
|
|
145
101
|
}
|
|
146
102
|
#setup() {
|
|
147
103
|
this.refresh();
|
|
148
|
-
const
|
|
149
|
-
if (
|
|
150
|
-
this.data = object;
|
|
151
|
-
}
|
|
152
|
-
};
|
|
153
|
-
this.#channel.on('objectUpdated', onObjectUpdated);
|
|
154
|
-
this.#unsubscribers.push(() => this.#channel.off('objectUpdated', onObjectUpdated));
|
|
155
|
-
const onObjectCreated = ({ location, object }) => {
|
|
156
|
-
if (location === this.#location) {
|
|
157
|
-
this.data = object;
|
|
158
|
-
}
|
|
159
|
-
};
|
|
160
|
-
this.#channel.on('objectCreated', onObjectCreated);
|
|
161
|
-
this.#unsubscribers.push(() => this.#channel.off('objectCreated', onObjectCreated));
|
|
162
|
-
const onObjectDeleted = ({ location }) => {
|
|
163
|
-
if (location === this.#location) {
|
|
164
|
-
this.data = undefined;
|
|
165
|
-
}
|
|
166
|
-
};
|
|
167
|
-
this.#channel.on('objectDeleted', onObjectDeleted);
|
|
168
|
-
this.#unsubscribers.push(() => this.#channel.off('objectDeleted', onObjectDeleted));
|
|
169
|
-
const onObjectMoved = ({ from, to, object }) => {
|
|
170
|
-
if (from === this.#location) {
|
|
171
|
-
// Object moved away from this location; data is gone.
|
|
104
|
+
const unsubscribe = this.#fileTree.subscribe((event) => {
|
|
105
|
+
if (event.deletedPaths.has(this.#location)) {
|
|
172
106
|
this.data = undefined;
|
|
107
|
+
return;
|
|
173
108
|
}
|
|
174
|
-
|
|
175
|
-
this.
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
this.#channel.on('objectMoved', onObjectMoved);
|
|
179
|
-
this.#unsubscribers.push(() => this.#channel.off('objectMoved', onObjectMoved));
|
|
180
|
-
const onReset = () => this.refresh();
|
|
181
|
-
this.#channel.on('reset', onReset);
|
|
182
|
-
this.#unsubscribers.push(() => this.#channel.off('reset', onReset));
|
|
109
|
+
if (eventTouchesObject(event, this.#location))
|
|
110
|
+
void this.refresh();
|
|
111
|
+
});
|
|
112
|
+
this.#unsubscribers.push(unsubscribe);
|
|
183
113
|
}
|
|
184
114
|
async refresh() {
|
|
185
115
|
this.loading = true;
|
|
@@ -232,7 +162,6 @@ class ReactiveConversationHandleImpl {
|
|
|
232
162
|
setSystemInstruction(...args) { return this.#handle.setSystemInstruction(...args); }
|
|
233
163
|
rename(...args) { return this.#handle.rename(...args); }
|
|
234
164
|
// Object operations
|
|
235
|
-
findObjects(...args) { return this.#handle.findObjects(...args); }
|
|
236
165
|
createObject(...args) { return this.#handle.createObject(...args); }
|
|
237
166
|
updateObject(...args) { return this.#handle.updateObject(...args); }
|
|
238
167
|
moveObject(...args) { return this.#handle.moveObject(...args); }
|
|
@@ -257,6 +186,7 @@ class ReactiveConversationHandleImpl {
|
|
|
257
186
|
*/
|
|
258
187
|
class ReactiveChannelImpl {
|
|
259
188
|
#channel;
|
|
189
|
+
#fileTree;
|
|
260
190
|
#unsubscribers = [];
|
|
261
191
|
#closed = false;
|
|
262
192
|
// Reactive state
|
|
@@ -264,14 +194,16 @@ class ReactiveChannelImpl {
|
|
|
264
194
|
objectLocations = $state([]);
|
|
265
195
|
collections = $state([]);
|
|
266
196
|
conversations = $state([]);
|
|
267
|
-
constructor(channel) {
|
|
197
|
+
constructor(channel, fileTree) {
|
|
268
198
|
this.#channel = channel;
|
|
199
|
+
this.#fileTree = fileTree;
|
|
269
200
|
this.interactions = channel.getInteractions();
|
|
270
|
-
this.objectLocations =
|
|
271
|
-
this.collections =
|
|
201
|
+
this.objectLocations = fileTree.objectLocations();
|
|
202
|
+
this.collections = fileTree.collections();
|
|
272
203
|
this.conversations = channel.getConversations();
|
|
273
204
|
const onChannelUpdated = () => {
|
|
274
205
|
this.interactions = channel.getInteractions();
|
|
206
|
+
this.conversations = channel.getConversations();
|
|
275
207
|
};
|
|
276
208
|
channel.on('channelUpdated', onChannelUpdated);
|
|
277
209
|
this.#unsubscribers.push(() => channel.off('channelUpdated', onChannelUpdated));
|
|
@@ -280,24 +212,18 @@ class ReactiveChannelImpl {
|
|
|
280
212
|
};
|
|
281
213
|
channel.on('conversationUpdated', onConversationUpdated);
|
|
282
214
|
this.#unsubscribers.push(() => channel.off('conversationUpdated', onConversationUpdated));
|
|
283
|
-
const
|
|
284
|
-
this.objectLocations =
|
|
215
|
+
const refreshFromFileTree = () => {
|
|
216
|
+
this.objectLocations = fileTree.objectLocations();
|
|
217
|
+
this.collections = fileTree.collections();
|
|
285
218
|
};
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
this.#unsubscribers.push(() => channel.off('objectMoved', refreshObjectLocations));
|
|
292
|
-
const onSchemaUpdated = () => {
|
|
293
|
-
this.collections = Object.keys(channel.getSchema());
|
|
294
|
-
};
|
|
295
|
-
channel.on('schemaUpdated', onSchemaUpdated);
|
|
296
|
-
this.#unsubscribers.push(() => channel.off('schemaUpdated', onSchemaUpdated));
|
|
219
|
+
this.#unsubscribers.push(fileTree.subscribe((event) => {
|
|
220
|
+
if (event.reset || eventTouchesObject(event) || [...event.changedPaths, ...event.deletedPaths].some((path) => path === '/space' || path.startsWith('/space/'))) {
|
|
221
|
+
refreshFromFileTree();
|
|
222
|
+
}
|
|
223
|
+
}));
|
|
297
224
|
const onReset = () => {
|
|
298
225
|
this.interactions = channel.getInteractions();
|
|
299
|
-
|
|
300
|
-
this.collections = Object.keys(channel.getSchema());
|
|
226
|
+
refreshFromFileTree();
|
|
301
227
|
this.conversations = channel.getConversations();
|
|
302
228
|
};
|
|
303
229
|
channel.on('reset', onReset);
|
|
@@ -313,6 +239,7 @@ class ReactiveChannelImpl {
|
|
|
313
239
|
get isReadOnly() { return this.#channel.isReadOnly; }
|
|
314
240
|
get linkAccess() { return this.#channel.linkAccess; }
|
|
315
241
|
get extensionUrl() { return this.#channel.extensionUrl; }
|
|
242
|
+
get extensionId() { return this.#channel.extensionId; }
|
|
316
243
|
get manifest() { return this.#channel.manifest; }
|
|
317
244
|
get isClosed() { return this.#closed; }
|
|
318
245
|
close() {
|
|
@@ -326,9 +253,8 @@ class ReactiveChannelImpl {
|
|
|
326
253
|
}
|
|
327
254
|
// Object operations
|
|
328
255
|
getObject(...args) { return this.#channel.getObject(...args); }
|
|
256
|
+
getObjects(...args) { return this.#channel.getObjects(...args); }
|
|
329
257
|
stat(...args) { return this.#channel.stat(...args); }
|
|
330
|
-
findObjects(...args) { return this.#channel.findObjects(...args); }
|
|
331
|
-
getObjectLocations(...args) { return this.#channel.getObjectLocations(...args); }
|
|
332
258
|
createObject(...args) { return this.#channel.createObject(...args); }
|
|
333
259
|
updateObject(...args) { return this.#channel.updateObject(...args); }
|
|
334
260
|
moveObject(...args) { return this.#channel.moveObject(...args); }
|
|
@@ -381,7 +307,7 @@ class ReactiveChannelImpl {
|
|
|
381
307
|
object(location) {
|
|
382
308
|
if (this.#closed)
|
|
383
309
|
throw new Error('Cannot create reactive object: channel is closed');
|
|
384
|
-
return new ReactiveObjectImpl(this.#channel, location);
|
|
310
|
+
return new ReactiveObjectImpl(this.#channel, this.#fileTree, location);
|
|
385
311
|
}
|
|
386
312
|
/**
|
|
387
313
|
* Create a reactive watch that auto-updates when matching objects change.
|
|
@@ -389,11 +315,11 @@ class ReactiveChannelImpl {
|
|
|
389
315
|
watch(options) {
|
|
390
316
|
if (this.#closed)
|
|
391
317
|
throw new Error('Cannot create reactive watch: channel is closed');
|
|
392
|
-
return new ReactiveWatchImpl(this.#channel, options);
|
|
318
|
+
return new ReactiveWatchImpl(this.#channel, this.#fileTree, options);
|
|
393
319
|
}
|
|
394
320
|
}
|
|
395
|
-
export function wrapChannel(channel) {
|
|
396
|
-
return new ReactiveChannelImpl(channel);
|
|
321
|
+
export function wrapChannel(channel, fileTree) {
|
|
322
|
+
return new ReactiveChannelImpl(channel, fileTree);
|
|
397
323
|
}
|
|
398
324
|
/**
|
|
399
325
|
* A reactive list of channels for a space that auto-updates via SSE events.
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import type { RoolSpace, WebDAVDepth, WebDAVPropName, WebDAVResponse, WebDAVSyncLevel } from '@rool-dev/sdk';
|
|
2
|
+
export type ReactiveFilePath = '/' | `/space${string}` | `/rool-drive${string}`;
|
|
3
|
+
export type ReactiveFileRoot = '' | 'space' | 'rool-drive';
|
|
4
|
+
export interface ReactiveFileNode {
|
|
5
|
+
/** Stable node id. Same as `path`. */
|
|
6
|
+
id: ReactiveFilePath;
|
|
7
|
+
/** Machine/WebDAV path (`/`, `/space/...`, `/rool-drive/...`). */
|
|
8
|
+
path: ReactiveFilePath;
|
|
9
|
+
/** Parent path, or `null` for `/`. */
|
|
10
|
+
parent: ReactiveFilePath | null;
|
|
11
|
+
/** Last path segment, decoded by the server when available. */
|
|
12
|
+
name: string;
|
|
13
|
+
/** Which top-level filesystem this node belongs to. `/` has `root: ''`. */
|
|
14
|
+
root: ReactiveFileRoot;
|
|
15
|
+
isCollection: boolean;
|
|
16
|
+
size: number | null;
|
|
17
|
+
contentType: string | null;
|
|
18
|
+
etag: string | null;
|
|
19
|
+
modifiedAt: number | null;
|
|
20
|
+
href: string | null;
|
|
21
|
+
}
|
|
22
|
+
export interface ReactiveFileTreeEvent {
|
|
23
|
+
/** `true` when the tree was replaced from a full snapshot. */
|
|
24
|
+
reset: boolean;
|
|
25
|
+
changedPaths: Set<ReactiveFilePath>;
|
|
26
|
+
deletedPaths: Set<ReactiveFilePath>;
|
|
27
|
+
token: string | null;
|
|
28
|
+
}
|
|
29
|
+
export interface ReactiveFileTreeSyncResult extends ReactiveFileTreeEvent {
|
|
30
|
+
changed: boolean;
|
|
31
|
+
}
|
|
32
|
+
export interface ReactiveFileTreeTransport {
|
|
33
|
+
propfind(path: string, options: {
|
|
34
|
+
depth: WebDAVDepth;
|
|
35
|
+
props?: WebDAVPropName[];
|
|
36
|
+
signal?: AbortSignal;
|
|
37
|
+
}): Promise<{
|
|
38
|
+
responses: WebDAVResponse[];
|
|
39
|
+
}>;
|
|
40
|
+
syncCollection(path: string, options: {
|
|
41
|
+
token?: string | null;
|
|
42
|
+
level: WebDAVSyncLevel;
|
|
43
|
+
props?: WebDAVPropName[];
|
|
44
|
+
limit?: number;
|
|
45
|
+
signal?: AbortSignal;
|
|
46
|
+
}): Promise<{
|
|
47
|
+
token: string;
|
|
48
|
+
responses: WebDAVResponse[];
|
|
49
|
+
}>;
|
|
50
|
+
}
|
|
51
|
+
type Listener = (event: ReactiveFileTreeEvent) => void;
|
|
52
|
+
/**
|
|
53
|
+
* Canonical Svelte-owned tree for the whole per-space WebDAV filesystem.
|
|
54
|
+
*
|
|
55
|
+
* It watches the SDK's coarse `filesChanged` / `filesReset` events and
|
|
56
|
+
* reconciles with WebDAV `sync-collection`. Consumers that care about both
|
|
57
|
+
* object files (`/space/...`) and user files (`/rool-drive/...`) should depend
|
|
58
|
+
* on this tree.
|
|
59
|
+
*/
|
|
60
|
+
export declare class ReactiveFileTree {
|
|
61
|
+
#private;
|
|
62
|
+
nodes: ReactiveFileNode[];
|
|
63
|
+
byPath: Record<string, ReactiveFileNode>;
|
|
64
|
+
token: string | null;
|
|
65
|
+
version: number;
|
|
66
|
+
loading: boolean;
|
|
67
|
+
syncing: boolean;
|
|
68
|
+
error: Error | null;
|
|
69
|
+
constructor(space: RoolSpace);
|
|
70
|
+
get isClosed(): boolean;
|
|
71
|
+
get root(): ReactiveFilePath;
|
|
72
|
+
subscribe(listener: Listener): () => void;
|
|
73
|
+
ready(): Promise<void>;
|
|
74
|
+
get(path: string): ReactiveFileNode | undefined;
|
|
75
|
+
has(path: string): boolean;
|
|
76
|
+
childrenOf(path: string): ReactiveFileNode[];
|
|
77
|
+
descendantsOf(path: string): ReactiveFileNode[];
|
|
78
|
+
/** Object file locations sorted by modified time descending. */
|
|
79
|
+
objectLocations(options?: {
|
|
80
|
+
collection?: string;
|
|
81
|
+
order?: 'asc' | 'desc';
|
|
82
|
+
limit?: number;
|
|
83
|
+
}): string[];
|
|
84
|
+
collections(): string[];
|
|
85
|
+
loadSnapshot(): Promise<ReactiveFileTreeSyncResult>;
|
|
86
|
+
sync(): Promise<ReactiveFileTreeSyncResult>;
|
|
87
|
+
refresh(): Promise<ReactiveFileTreeSyncResult>;
|
|
88
|
+
close(): void;
|
|
89
|
+
}
|
|
90
|
+
export {};
|
|
91
|
+
//# sourceMappingURL=file-tree.svelte.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"file-tree.svelte.d.ts","sourceRoot":"","sources":["../src/file-tree.svelte.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,SAAS,EACT,WAAW,EACX,cAAc,EACd,cAAc,EACd,eAAe,EAChB,MAAM,eAAe,CAAC;AAGvB,MAAM,MAAM,gBAAgB,GAAG,GAAG,GAAG,SAAS,MAAM,EAAE,GAAG,cAAc,MAAM,EAAE,CAAC;AAChF,MAAM,MAAM,gBAAgB,GAAG,EAAE,GAAG,OAAO,GAAG,YAAY,CAAC;AAE3D,MAAM,WAAW,gBAAgB;IAC/B,sCAAsC;IACtC,EAAE,EAAE,gBAAgB,CAAC;IACrB,kEAAkE;IAClE,IAAI,EAAE,gBAAgB,CAAC;IACvB,sCAAsC;IACtC,MAAM,EAAE,gBAAgB,GAAG,IAAI,CAAC;IAChC,+DAA+D;IAC/D,IAAI,EAAE,MAAM,CAAC;IACb,2EAA2E;IAC3E,IAAI,EAAE,gBAAgB,CAAC;IACvB,YAAY,EAAE,OAAO,CAAC;IACtB,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;CACrB;AAED,MAAM,WAAW,qBAAqB;IACpC,8DAA8D;IAC9D,KAAK,EAAE,OAAO,CAAC;IACf,YAAY,EAAE,GAAG,CAAC,gBAAgB,CAAC,CAAC;IACpC,YAAY,EAAE,GAAG,CAAC,gBAAgB,CAAC,CAAC;IACpC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;CACtB;AAED,MAAM,WAAW,0BAA2B,SAAQ,qBAAqB;IACvE,OAAO,EAAE,OAAO,CAAC;CAClB;AAED,MAAM,WAAW,yBAAyB;IACxC,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE;QAAE,KAAK,EAAE,WAAW,CAAC;QAAC,KAAK,CAAC,EAAE,cAAc,EAAE,CAAC;QAAC,MAAM,CAAC,EAAE,WAAW,CAAA;KAAE,GAAG,OAAO,CAAC;QAAE,SAAS,EAAE,cAAc,EAAE,CAAA;KAAE,CAAC,CAAC;IAClJ,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE;QAAE,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;QAAC,KAAK,EAAE,eAAe,CAAC;QAAC,KAAK,CAAC,EAAE,cAAc,EAAE,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,WAAW,CAAA;KAAE,GAAG,OAAO,CAAC;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,cAAc,EAAE,CAAA;KAAE,CAAC,CAAC;CACnN;AAYD,KAAK,QAAQ,GAAG,CAAC,KAAK,EAAE,qBAAqB,KAAK,IAAI,CAAC;AAmFvD;;;;;;;GAOG;AACH,qBAAa,gBAAgB;;IAW3B,KAAK,qBAAkC;IACvC,MAAM,mCAAgD;IACtD,KAAK,gBAA+B;IACpC,OAAO,SAAa;IACpB,OAAO,UAAgB;IACvB,OAAO,UAAiB;IACxB,KAAK,eAA8B;gBAEvB,KAAK,EAAE,SAAS;IAQ5B,IAAI,QAAQ,IAAI,OAAO,CAAyB;IAChD,IAAI,IAAI,IAAI,gBAAgB,CAAiB;IAE7C,SAAS,CAAC,QAAQ,EAAE,QAAQ,GAAG,MAAM,IAAI;IAKzC,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAItB,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,gBAAgB,GAAG,SAAS;IAI/C,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO;IAI1B,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,gBAAgB,EAAE;IAM5C,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,gBAAgB,EAAE;IAK/C,gEAAgE;IAChE,eAAe,CAAC,OAAO,GAAE;QAAE,UAAU,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,KAAK,GAAG,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAO,GAAG,MAAM,EAAE;IAUxG,WAAW,IAAI,MAAM,EAAE;IAOjB,YAAY,IAAI,OAAO,CAAC,0BAA0B,CAAC;IAgCnD,IAAI,IAAI,OAAO,CAAC,0BAA0B,CAAC;IAuBjD,OAAO,IAAI,OAAO,CAAC,0BAA0B,CAAC;IAI9C,KAAK,IAAI,IAAI;CA0Id"}
|
|
@@ -0,0 +1,418 @@
|
|
|
1
|
+
import { parseLocation } from '@rool-dev/sdk';
|
|
2
|
+
const ROOT = '/';
|
|
3
|
+
const DEFAULT_PROPS = [
|
|
4
|
+
'displayname',
|
|
5
|
+
'getcontentlength',
|
|
6
|
+
'getcontenttype',
|
|
7
|
+
'getetag',
|
|
8
|
+
'getlastmodified',
|
|
9
|
+
'resourcetype',
|
|
10
|
+
];
|
|
11
|
+
function normalizePath(path) {
|
|
12
|
+
if (!path || path === '/')
|
|
13
|
+
return ROOT;
|
|
14
|
+
const normalized = `/${path.replace(/^\/+|\/+$/g, '')}`;
|
|
15
|
+
if (normalized === '/space' || normalized.startsWith('/space/'))
|
|
16
|
+
return normalized;
|
|
17
|
+
if (normalized === '/rool-drive' || normalized.startsWith('/rool-drive/'))
|
|
18
|
+
return normalized;
|
|
19
|
+
return ROOT;
|
|
20
|
+
}
|
|
21
|
+
function parentPath(path) {
|
|
22
|
+
if (path === ROOT)
|
|
23
|
+
return null;
|
|
24
|
+
const parts = path.split('/').filter(Boolean);
|
|
25
|
+
parts.pop();
|
|
26
|
+
return parts.length === 0 ? ROOT : `/${parts.join('/')}`;
|
|
27
|
+
}
|
|
28
|
+
function basename(path) {
|
|
29
|
+
if (path === ROOT)
|
|
30
|
+
return 'Space';
|
|
31
|
+
const leaf = path.split('/').filter(Boolean).pop() ?? '';
|
|
32
|
+
try {
|
|
33
|
+
return decodeURIComponent(leaf);
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
return leaf;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
function rootOf(path) {
|
|
40
|
+
if (path === ROOT)
|
|
41
|
+
return '';
|
|
42
|
+
if (path === '/space' || path.startsWith('/space/'))
|
|
43
|
+
return 'space';
|
|
44
|
+
if (path === '/rool-drive' || path.startsWith('/rool-drive/'))
|
|
45
|
+
return 'rool-drive';
|
|
46
|
+
return '';
|
|
47
|
+
}
|
|
48
|
+
function modifiedAt(props) {
|
|
49
|
+
const raw = props.getlastmodified;
|
|
50
|
+
if (typeof raw !== 'string')
|
|
51
|
+
return null;
|
|
52
|
+
const parsed = Date.parse(raw);
|
|
53
|
+
return Number.isNaN(parsed) ? null : parsed;
|
|
54
|
+
}
|
|
55
|
+
function nodeFromResponse(response) {
|
|
56
|
+
const path = normalizePath(response.path);
|
|
57
|
+
return {
|
|
58
|
+
id: path,
|
|
59
|
+
path,
|
|
60
|
+
parent: parentPath(path),
|
|
61
|
+
name: typeof response.props.displayname === 'string' && response.props.displayname
|
|
62
|
+
? response.props.displayname
|
|
63
|
+
: basename(path),
|
|
64
|
+
root: rootOf(path),
|
|
65
|
+
isCollection: response.isCollection,
|
|
66
|
+
size: typeof response.props.getcontentlength === 'number' ? response.props.getcontentlength : null,
|
|
67
|
+
contentType: typeof response.props.getcontenttype === 'string' ? response.props.getcontenttype : null,
|
|
68
|
+
etag: typeof response.props.getetag === 'string' ? response.props.getetag : null,
|
|
69
|
+
modifiedAt: modifiedAt(response.props),
|
|
70
|
+
href: response.href || null,
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
function isDeletedResponse(response) {
|
|
74
|
+
if (response.status === 404 || response.status === 410)
|
|
75
|
+
return true;
|
|
76
|
+
return response.propstats.length > 0 && response.propstats.every((p) => p.status === 404 || p.status === 410);
|
|
77
|
+
}
|
|
78
|
+
function sameNode(a, b) {
|
|
79
|
+
return a.id === b.id
|
|
80
|
+
&& a.path === b.path
|
|
81
|
+
&& a.parent === b.parent
|
|
82
|
+
&& a.name === b.name
|
|
83
|
+
&& a.root === b.root
|
|
84
|
+
&& a.isCollection === b.isCollection
|
|
85
|
+
&& a.size === b.size
|
|
86
|
+
&& a.contentType === b.contentType
|
|
87
|
+
&& a.etag === b.etag
|
|
88
|
+
&& a.modifiedAt === b.modifiedAt
|
|
89
|
+
&& a.href === b.href;
|
|
90
|
+
}
|
|
91
|
+
function sortNodes(a, b) {
|
|
92
|
+
return Number(b.isCollection) - Number(a.isCollection) || a.name.localeCompare(b.name);
|
|
93
|
+
}
|
|
94
|
+
function emptyEvent(token, reset = false) {
|
|
95
|
+
return { reset, changedPaths: new Set(), deletedPaths: new Set(), token };
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Canonical Svelte-owned tree for the whole per-space WebDAV filesystem.
|
|
99
|
+
*
|
|
100
|
+
* It watches the SDK's coarse `filesChanged` / `filesReset` events and
|
|
101
|
+
* reconciles with WebDAV `sync-collection`. Consumers that care about both
|
|
102
|
+
* object files (`/space/...`) and user files (`/rool-drive/...`) should depend
|
|
103
|
+
* on this tree.
|
|
104
|
+
*/
|
|
105
|
+
export class ReactiveFileTree {
|
|
106
|
+
#space;
|
|
107
|
+
#nodes = new Map();
|
|
108
|
+
#children = new Map();
|
|
109
|
+
#listeners = new Set();
|
|
110
|
+
#unsubscribers = [];
|
|
111
|
+
#syncing = null;
|
|
112
|
+
#initialLoad = null;
|
|
113
|
+
#syncAgain = false;
|
|
114
|
+
#closed = false;
|
|
115
|
+
nodes = $state([]);
|
|
116
|
+
byPath = $state({});
|
|
117
|
+
token = $state(null);
|
|
118
|
+
version = $state(0);
|
|
119
|
+
loading = $state(true);
|
|
120
|
+
syncing = $state(false);
|
|
121
|
+
error = $state(null);
|
|
122
|
+
constructor(space) {
|
|
123
|
+
this.#space = space;
|
|
124
|
+
this.#setNode(rootNode());
|
|
125
|
+
this.#publishState();
|
|
126
|
+
this.#setupSpaceListeners();
|
|
127
|
+
this.#initialLoad = this.loadSnapshot().then(() => undefined, () => undefined);
|
|
128
|
+
}
|
|
129
|
+
get isClosed() { return this.#closed; }
|
|
130
|
+
get root() { return ROOT; }
|
|
131
|
+
subscribe(listener) {
|
|
132
|
+
this.#listeners.add(listener);
|
|
133
|
+
return () => this.#listeners.delete(listener);
|
|
134
|
+
}
|
|
135
|
+
ready() {
|
|
136
|
+
return this.#initialLoad ?? Promise.resolve();
|
|
137
|
+
}
|
|
138
|
+
get(path) {
|
|
139
|
+
return this.#nodes.get(normalizePath(path));
|
|
140
|
+
}
|
|
141
|
+
has(path) {
|
|
142
|
+
return this.#nodes.has(normalizePath(path));
|
|
143
|
+
}
|
|
144
|
+
childrenOf(path) {
|
|
145
|
+
return (this.#children.get(normalizePath(path)) ?? [])
|
|
146
|
+
.map((child) => this.#nodes.get(child))
|
|
147
|
+
.filter((node) => !!node);
|
|
148
|
+
}
|
|
149
|
+
descendantsOf(path) {
|
|
150
|
+
const root = normalizePath(path);
|
|
151
|
+
return this.nodes.filter((node) => node.path !== root && isDescendant(node.path, root));
|
|
152
|
+
}
|
|
153
|
+
/** Object file locations sorted by modified time descending. */
|
|
154
|
+
objectLocations(options = {}) {
|
|
155
|
+
const locations = this.nodes
|
|
156
|
+
.filter((node) => !node.isCollection && isObjectLocation(node.path))
|
|
157
|
+
.filter((node) => !options.collection || safeCollection(node.path) === options.collection)
|
|
158
|
+
.sort((a, b) => (b.modifiedAt ?? 0) - (a.modifiedAt ?? 0))
|
|
159
|
+
.map((node) => node.path);
|
|
160
|
+
if (options.order === 'asc')
|
|
161
|
+
locations.reverse();
|
|
162
|
+
return options.limit === undefined ? locations : locations.slice(0, options.limit);
|
|
163
|
+
}
|
|
164
|
+
collections() {
|
|
165
|
+
return this.childrenOf('/space')
|
|
166
|
+
.filter((node) => node.isCollection)
|
|
167
|
+
.map((node) => node.name)
|
|
168
|
+
.sort((a, b) => a.localeCompare(b));
|
|
169
|
+
}
|
|
170
|
+
async loadSnapshot() {
|
|
171
|
+
if (this.#closed)
|
|
172
|
+
return { changed: false, ...emptyEvent(this.token, true) };
|
|
173
|
+
this.loading = true;
|
|
174
|
+
this.error = null;
|
|
175
|
+
try {
|
|
176
|
+
let snapshot;
|
|
177
|
+
try {
|
|
178
|
+
snapshot = await this.#space.webdav.syncCollection('/', { token: null, level: 'infinite', props: [...DEFAULT_PROPS] });
|
|
179
|
+
}
|
|
180
|
+
catch {
|
|
181
|
+
const [listing, tokenListing] = await Promise.all([
|
|
182
|
+
this.#space.webdav.propfind('/', { depth: 'infinity', props: [...DEFAULT_PROPS] }),
|
|
183
|
+
this.#space.webdav.propfind('/', { depth: '0', props: ['sync-token'] }),
|
|
184
|
+
]);
|
|
185
|
+
snapshot = {
|
|
186
|
+
token: tokenListing.responses[0]?.props.syncToken ?? null,
|
|
187
|
+
responses: listing.responses,
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
this.token = snapshot.token;
|
|
191
|
+
const result = this.#replaceSnapshot(snapshot.responses);
|
|
192
|
+
const event = { reset: true, changedPaths: result.changedPaths, deletedPaths: result.deletedPaths, token: this.token };
|
|
193
|
+
this.#emit(event);
|
|
194
|
+
return { changed: result.changed, ...event };
|
|
195
|
+
}
|
|
196
|
+
catch (error) {
|
|
197
|
+
this.error = error instanceof Error ? error : new Error(String(error));
|
|
198
|
+
throw error;
|
|
199
|
+
}
|
|
200
|
+
finally {
|
|
201
|
+
this.loading = false;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
async sync() {
|
|
205
|
+
if (this.#closed)
|
|
206
|
+
return { changed: false, ...emptyEvent(this.token) };
|
|
207
|
+
if (this.#syncing) {
|
|
208
|
+
this.#syncAgain = true;
|
|
209
|
+
return this.#syncing;
|
|
210
|
+
}
|
|
211
|
+
this.syncing = true;
|
|
212
|
+
this.error = null;
|
|
213
|
+
this.#syncing = this.#syncOnce();
|
|
214
|
+
try {
|
|
215
|
+
const result = await this.#syncing;
|
|
216
|
+
return result;
|
|
217
|
+
}
|
|
218
|
+
finally {
|
|
219
|
+
this.#syncing = null;
|
|
220
|
+
this.syncing = false;
|
|
221
|
+
if (this.#syncAgain && !this.#closed) {
|
|
222
|
+
this.#syncAgain = false;
|
|
223
|
+
void this.sync();
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
refresh() {
|
|
228
|
+
return this.loadSnapshot();
|
|
229
|
+
}
|
|
230
|
+
close() {
|
|
231
|
+
if (this.#closed)
|
|
232
|
+
return;
|
|
233
|
+
this.#closed = true;
|
|
234
|
+
for (const unsub of this.#unsubscribers)
|
|
235
|
+
unsub();
|
|
236
|
+
this.#unsubscribers.length = 0;
|
|
237
|
+
this.#listeners.clear();
|
|
238
|
+
}
|
|
239
|
+
#setupSpaceListeners() {
|
|
240
|
+
const onFilesChanged = () => { void this.sync(); };
|
|
241
|
+
const onFilesReset = () => { void this.loadSnapshot(); };
|
|
242
|
+
const onConnectionStateChanged = (state) => {
|
|
243
|
+
if (state === 'connected' && !this.loading)
|
|
244
|
+
void this.sync();
|
|
245
|
+
};
|
|
246
|
+
this.#space.on('filesChanged', onFilesChanged);
|
|
247
|
+
this.#space.on('filesReset', onFilesReset);
|
|
248
|
+
this.#space.on('connectionStateChanged', onConnectionStateChanged);
|
|
249
|
+
this.#unsubscribers.push(() => this.#space.off('filesChanged', onFilesChanged));
|
|
250
|
+
this.#unsubscribers.push(() => this.#space.off('filesReset', onFilesReset));
|
|
251
|
+
this.#unsubscribers.push(() => this.#space.off('connectionStateChanged', onConnectionStateChanged));
|
|
252
|
+
}
|
|
253
|
+
async #syncOnce() {
|
|
254
|
+
try {
|
|
255
|
+
const delta = await this.#space.webdav.syncCollection('/', { token: this.token, level: 'infinite', props: [...DEFAULT_PROPS] });
|
|
256
|
+
this.token = delta.token;
|
|
257
|
+
const result = this.#applyResponses(delta.responses);
|
|
258
|
+
const event = { reset: false, changedPaths: result.changedPaths, deletedPaths: result.deletedPaths, token: this.token };
|
|
259
|
+
if (result.changed || event.deletedPaths.size > 0)
|
|
260
|
+
this.#emit(event);
|
|
261
|
+
return { changed: result.changed, ...event };
|
|
262
|
+
}
|
|
263
|
+
catch {
|
|
264
|
+
return this.loadSnapshot();
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
#replaceSnapshot(responses) {
|
|
268
|
+
const next = new Map();
|
|
269
|
+
next.set(ROOT, rootNode());
|
|
270
|
+
for (const response of responses) {
|
|
271
|
+
if (isDeletedResponse(response))
|
|
272
|
+
continue;
|
|
273
|
+
const node = nodeFromResponse(response);
|
|
274
|
+
next.set(node.path, node);
|
|
275
|
+
}
|
|
276
|
+
// Ensure aggregate roots exist even if a server/proxy omits them.
|
|
277
|
+
if (!next.has('/space'))
|
|
278
|
+
next.set('/space', syntheticRootChild('/space', 'space'));
|
|
279
|
+
if (!next.has('/rool-drive'))
|
|
280
|
+
next.set('/rool-drive', syntheticRootChild('/rool-drive', 'rool-drive'));
|
|
281
|
+
const changedPaths = new Set();
|
|
282
|
+
const deletedPaths = new Set();
|
|
283
|
+
for (const [path, node] of next) {
|
|
284
|
+
const old = this.#nodes.get(path);
|
|
285
|
+
if (!old || !sameNode(old, node))
|
|
286
|
+
changedPaths.add(path);
|
|
287
|
+
}
|
|
288
|
+
for (const path of this.#nodes.keys()) {
|
|
289
|
+
if (!next.has(path))
|
|
290
|
+
deletedPaths.add(path);
|
|
291
|
+
}
|
|
292
|
+
this.#nodes = next;
|
|
293
|
+
this.#rebuildChildren();
|
|
294
|
+
const changed = changedPaths.size > 0 || deletedPaths.size > 0;
|
|
295
|
+
if (changed)
|
|
296
|
+
this.#publishState();
|
|
297
|
+
return { changed, changedPaths, deletedPaths };
|
|
298
|
+
}
|
|
299
|
+
#applyResponses(responses) {
|
|
300
|
+
let changed = false;
|
|
301
|
+
const changedPaths = new Set();
|
|
302
|
+
const deletedPaths = new Set();
|
|
303
|
+
for (const response of responses) {
|
|
304
|
+
const path = normalizePath(response.path);
|
|
305
|
+
if (path === ROOT)
|
|
306
|
+
continue;
|
|
307
|
+
if (isDeletedResponse(response)) {
|
|
308
|
+
if (this.#deleteSubtree(path, deletedPaths))
|
|
309
|
+
changed = true;
|
|
310
|
+
else
|
|
311
|
+
deletedPaths.add(path);
|
|
312
|
+
continue;
|
|
313
|
+
}
|
|
314
|
+
const node = nodeFromResponse(response);
|
|
315
|
+
const old = this.#nodes.get(node.path);
|
|
316
|
+
if (!old || !sameNode(old, node)) {
|
|
317
|
+
this.#setNode(node);
|
|
318
|
+
changed = true;
|
|
319
|
+
changedPaths.add(node.path);
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
if (changed) {
|
|
323
|
+
this.#rebuildChildren();
|
|
324
|
+
this.#publishState();
|
|
325
|
+
}
|
|
326
|
+
return { changed, changedPaths, deletedPaths };
|
|
327
|
+
}
|
|
328
|
+
#setNode(node) {
|
|
329
|
+
this.#nodes.set(node.path, node);
|
|
330
|
+
}
|
|
331
|
+
#deleteSubtree(path, deletedPaths) {
|
|
332
|
+
let deleted = false;
|
|
333
|
+
for (const nodePath of [...this.#nodes.keys()].sort((a, b) => b.length - a.length)) {
|
|
334
|
+
if (nodePath === path || isDescendant(nodePath, path)) {
|
|
335
|
+
this.#nodes.delete(nodePath);
|
|
336
|
+
deletedPaths.add(nodePath);
|
|
337
|
+
deleted = true;
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
return deleted;
|
|
341
|
+
}
|
|
342
|
+
#rebuildChildren() {
|
|
343
|
+
this.#children = new Map();
|
|
344
|
+
for (const node of this.#nodes.values()) {
|
|
345
|
+
if (!node.parent)
|
|
346
|
+
continue;
|
|
347
|
+
const bucket = this.#children.get(node.parent) ?? [];
|
|
348
|
+
bucket.push(node.path);
|
|
349
|
+
this.#children.set(node.parent, bucket);
|
|
350
|
+
}
|
|
351
|
+
for (const [parent, children] of this.#children) {
|
|
352
|
+
children.sort((a, b) => sortNodes(this.#nodes.get(a), this.#nodes.get(b)));
|
|
353
|
+
this.#children.set(parent, children);
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
#publishState() {
|
|
357
|
+
const nodes = [...this.#nodes.values()].sort((a, b) => a.path === ROOT ? -1 : b.path === ROOT ? 1 : a.path.localeCompare(b.path));
|
|
358
|
+
this.nodes = nodes;
|
|
359
|
+
this.byPath = Object.fromEntries(nodes.map((node) => [node.path, node]));
|
|
360
|
+
this.version += 1;
|
|
361
|
+
}
|
|
362
|
+
#emit(event) {
|
|
363
|
+
for (const listener of this.#listeners)
|
|
364
|
+
listener(event);
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
function rootNode() {
|
|
368
|
+
return {
|
|
369
|
+
id: ROOT,
|
|
370
|
+
path: ROOT,
|
|
371
|
+
parent: null,
|
|
372
|
+
name: 'Space',
|
|
373
|
+
root: '',
|
|
374
|
+
isCollection: true,
|
|
375
|
+
size: null,
|
|
376
|
+
contentType: null,
|
|
377
|
+
etag: null,
|
|
378
|
+
modifiedAt: null,
|
|
379
|
+
href: null,
|
|
380
|
+
};
|
|
381
|
+
}
|
|
382
|
+
function syntheticRootChild(path, name) {
|
|
383
|
+
return {
|
|
384
|
+
id: path,
|
|
385
|
+
path,
|
|
386
|
+
parent: ROOT,
|
|
387
|
+
name,
|
|
388
|
+
root: name,
|
|
389
|
+
isCollection: true,
|
|
390
|
+
size: null,
|
|
391
|
+
contentType: null,
|
|
392
|
+
etag: null,
|
|
393
|
+
modifiedAt: null,
|
|
394
|
+
href: null,
|
|
395
|
+
};
|
|
396
|
+
}
|
|
397
|
+
function isDescendant(path, ancestor) {
|
|
398
|
+
if (ancestor === ROOT)
|
|
399
|
+
return path !== ROOT;
|
|
400
|
+
return path.startsWith(`${ancestor}/`);
|
|
401
|
+
}
|
|
402
|
+
function isObjectLocation(path) {
|
|
403
|
+
try {
|
|
404
|
+
parseLocation(path);
|
|
405
|
+
return true;
|
|
406
|
+
}
|
|
407
|
+
catch {
|
|
408
|
+
return false;
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
function safeCollection(path) {
|
|
412
|
+
try {
|
|
413
|
+
return parseLocation(path).collection;
|
|
414
|
+
}
|
|
415
|
+
catch {
|
|
416
|
+
return undefined;
|
|
417
|
+
}
|
|
418
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
export { createRool, generateId } from './rool.svelte.js';
|
|
2
|
-
export { loc, parseLocation, normalizeLocation, isLocation,
|
|
2
|
+
export { loc, parseLocation, normalizeLocation, isLocation, resolveMachineResource, } from '@rool-dev/sdk';
|
|
3
3
|
export type { ParsedLocation, MachineResource } from '@rool-dev/sdk';
|
|
4
4
|
export { wrapChannel } from './channel.svelte.js';
|
|
5
5
|
export { wrapSpace } from './space.svelte.js';
|
|
6
|
+
export { ReactiveFileTree } from './file-tree.svelte.js';
|
|
6
7
|
export type { Rool } from './rool.svelte.js';
|
|
7
8
|
export type { ReactiveChannel, ReactiveConversationHandle, ReactiveObject, ReactiveWatch, ReactiveChannelList, WatchOptions } from './channel.svelte.js';
|
|
8
9
|
export type { ReactiveSpace } from './space.svelte.js';
|
|
9
|
-
export type {
|
|
10
|
+
export type { ReactiveFileNode, ReactiveFilePath, ReactiveFileRoot, ReactiveFileTreeEvent, ReactiveFileTreeSyncResult } from './file-tree.svelte.js';
|
|
11
|
+
export type { RoolClientConfig, RoolChannel, RoolSpace, RoolSpaceInfo, RoolObject, GetObjectsResult, RoolObjectStat, RoolUserRole, ConnectionState, ChannelInfo, Conversation, ConversationInfo, CurrentUser, Interaction, PromptOptions, PromptAttachment, CreateObjectOptions, UpdateObjectOptions, MoveObjectOptions, CollectionOptions, FieldType, FieldDef, CollectionDef, SpaceSchema, SpaceMember, UserResult, RoolClient, ExtensionInfo, PublishedExtensionInfo, UploadExtensionOptions, ExtensionManifest, FindExtensionsOptions, RoolSpaceEvents, ProbeRequestEvent, OpenExtensionEvent, SpaceFileStorageUsage, WebDAVDepth, WebDAVSyncLevel, WebDAVPropName, WebDAVResponse, WebDAVProps, } from '@rool-dev/sdk';
|
|
10
12
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAG1D,OAAO,EACL,GAAG,EACH,aAAa,EACb,iBAAiB,EACjB,UAAU,EACV,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAG1D,OAAO,EACL,GAAG,EACH,aAAa,EACb,iBAAiB,EACjB,UAAU,EACV,sBAAsB,GACvB,MAAM,eAAe,CAAC;AACvB,YAAY,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAGrE,OAAO,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAClD,OAAO,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AAC9C,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAGzD,YAAY,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC;AAC7C,YAAY,EAAE,eAAe,EAAE,0BAA0B,EAAE,cAAc,EAAE,aAAa,EAAE,mBAAmB,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACzJ,YAAY,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AACvD,YAAY,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,qBAAqB,EAAE,0BAA0B,EAAE,MAAM,uBAAuB,CAAC;AAGrJ,YAAY,EACV,gBAAgB,EAChB,WAAW,EACX,SAAS,EACT,aAAa,EACb,UAAU,EACV,gBAAgB,EAChB,cAAc,EACd,YAAY,EACZ,eAAe,EACf,WAAW,EACX,YAAY,EACZ,gBAAgB,EAChB,WAAW,EACX,WAAW,EACX,aAAa,EACb,gBAAgB,EAChB,mBAAmB,EACnB,mBAAmB,EACnB,iBAAiB,EACjB,iBAAiB,EACjB,SAAS,EACT,QAAQ,EACR,aAAa,EACb,WAAW,EACX,WAAW,EACX,UAAU,EACV,UAAU,EACV,aAAa,EACb,sBAAsB,EACtB,sBAAsB,EACtB,iBAAiB,EACjB,qBAAqB,EACrB,eAAe,EACf,iBAAiB,EACjB,kBAAkB,EAClB,qBAAqB,EACrB,WAAW,EACX,eAAe,EACf,cAAc,EACd,cAAc,EACd,WAAW,GACZ,MAAM,eAAe,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
// Main export
|
|
2
2
|
export { createRool, generateId } from './rool.svelte.js';
|
|
3
3
|
// Location and machine-resource helpers — re-exported from the SDK for convenience
|
|
4
|
-
export { loc, parseLocation, normalizeLocation, isLocation,
|
|
4
|
+
export { loc, parseLocation, normalizeLocation, isLocation, resolveMachineResource, } from '@rool-dev/sdk';
|
|
5
5
|
// Reactive wrappers
|
|
6
6
|
export { wrapChannel } from './channel.svelte.js';
|
|
7
7
|
export { wrapSpace } from './space.svelte.js';
|
|
8
|
+
export { ReactiveFileTree } from './file-tree.svelte.js';
|
package/dist/rool.svelte.d.ts
CHANGED
|
@@ -58,6 +58,10 @@ declare class RoolImpl {
|
|
|
58
58
|
* Create a new space. Returns a ReactiveSpace.
|
|
59
59
|
*/
|
|
60
60
|
createSpace(name: string): Promise<ReactiveSpace>;
|
|
61
|
+
/**
|
|
62
|
+
* Duplicate an existing space. Returns a ReactiveSpace.
|
|
63
|
+
*/
|
|
64
|
+
duplicateSpace(sourceSpaceId: string, name: string): Promise<ReactiveSpace>;
|
|
61
65
|
/**
|
|
62
66
|
* Manually refresh the spaces list.
|
|
63
67
|
*/
|
|
@@ -66,6 +70,22 @@ declare class RoolImpl {
|
|
|
66
70
|
* Delete a space.
|
|
67
71
|
*/
|
|
68
72
|
deleteSpace(spaceId: string): Promise<void>;
|
|
73
|
+
/**
|
|
74
|
+
* Mark the current user for deletion, then log out locally.
|
|
75
|
+
*/
|
|
76
|
+
deleteCurrentUser(): Promise<void>;
|
|
77
|
+
/**
|
|
78
|
+
* Set or change the current user's password.
|
|
79
|
+
*/
|
|
80
|
+
setPassword(password: string): Promise<void>;
|
|
81
|
+
/**
|
|
82
|
+
* Get a value from user storage.
|
|
83
|
+
*/
|
|
84
|
+
getUserStorage<T = unknown>(key: string): T | undefined;
|
|
85
|
+
/**
|
|
86
|
+
* Get all user storage data.
|
|
87
|
+
*/
|
|
88
|
+
getAllUserStorage(): Record<string, unknown>;
|
|
69
89
|
/**
|
|
70
90
|
* Set a value in user storage.
|
|
71
91
|
* Updates reactive state immediately, then syncs to server.
|
|
@@ -98,6 +118,7 @@ declare class RoolImpl {
|
|
|
98
118
|
updateCurrentUser(input: {
|
|
99
119
|
name?: string;
|
|
100
120
|
slug?: string;
|
|
121
|
+
marketingOptIn?: boolean;
|
|
101
122
|
}): Promise<CurrentUser>;
|
|
102
123
|
/**
|
|
103
124
|
* Install an extension into a space.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rool.svelte.d.ts","sourceRoot":"","sources":["../src/rool.svelte.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,KAAK,aAAa,EAAE,KAAK,eAAe,EAAE,KAAK,gBAAgB,EAAE,KAAK,WAAW,EAAE,KAAK,qBAAqB,EAAE,KAAK,aAAa,EAAE,KAAK,sBAAsB,EAAE,KAAK,sBAAsB,EAAE,MAAM,eAAe,CAAC;AACxO,OAAO,EAAqB,KAAK,mBAAmB,EAAE,MAAM,qBAAqB,CAAC;AAClF,OAAO,EAAa,KAAK,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAElE;;;;;;;;GAQG;AACH,cAAM,QAAQ;;IAMZ,aAAa,iBAAgC;IAC7C,MAAM,8BAAkD;IACxD,aAAa,UAAiB;IAC9B,WAAW,eAA8B;IACzC,eAAe,kBAA2C;IAC1D,WAAW,0BAAuC;IAClD,WAAW,qBAAoC;gBAEnC,MAAM,CAAC,EAAE,gBAAgB;IAKrC;;;OAGG;IACH,IAAI,MAAM,IAAI,UAAU,CAEvB;IAwDD;;;OAGG;IACG,IAAI,IAAI,OAAO,CAAC,OAAO,CAAC;IAgB9B;;OAEG;IACH,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,IAAI;IAI7D;;OAEG;IACH,MAAM,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,IAAI;IAI9D;;;;OAIG;IACG,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAa7C;;OAEG;IACH,MAAM,IAAI,IAAI;IAQd;;;;OAIG;IACG,SAAS,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC;IAOxD;;OAEG;IACG,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC;IAOvD;;OAEG;IACH,aAAa,IAAI,OAAO,CAAC,IAAI,CAAC;IAI9B;;OAEG;IACH,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAI3C;;;OAGG;IACH,cAAc,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,IAAI;IAKjD;;;OAGG;IACH,WAAW,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,MAAM,GAAG,IAAI;IAI9C;;OAEG;IACH,UAAU,CAAC,KAAK,EAAE,MAAM;IAIxB;;OAEG;IACG,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,IAAI,GAAG,OAAO,CAAC,aAAa,CAAC;IAOxE;;OAEG;IACH,IAAI,QAAQ,qCAEX;IAED;;OAEG;IACG,cAAc;IAMpB;;OAEG;IACG,iBAAiB,CAAC,KAAK,EAAE;QAAE,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE;
|
|
1
|
+
{"version":3,"file":"rool.svelte.d.ts","sourceRoot":"","sources":["../src/rool.svelte.ts"],"names":[],"mappings":"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"}
|
package/dist/rool.svelte.js
CHANGED
|
@@ -163,6 +163,15 @@ class RoolImpl {
|
|
|
163
163
|
this.#openSpaces.add(reactive);
|
|
164
164
|
return reactive;
|
|
165
165
|
}
|
|
166
|
+
/**
|
|
167
|
+
* Duplicate an existing space. Returns a ReactiveSpace.
|
|
168
|
+
*/
|
|
169
|
+
async duplicateSpace(sourceSpaceId, name) {
|
|
170
|
+
const raw = await this.#client.duplicateSpace(sourceSpaceId, name);
|
|
171
|
+
const reactive = wrapSpace(raw);
|
|
172
|
+
this.#openSpaces.add(reactive);
|
|
173
|
+
return reactive;
|
|
174
|
+
}
|
|
166
175
|
/**
|
|
167
176
|
* Manually refresh the spaces list.
|
|
168
177
|
*/
|
|
@@ -175,6 +184,30 @@ class RoolImpl {
|
|
|
175
184
|
deleteSpace(spaceId) {
|
|
176
185
|
return this.#client.deleteSpace(spaceId);
|
|
177
186
|
}
|
|
187
|
+
/**
|
|
188
|
+
* Mark the current user for deletion, then log out locally.
|
|
189
|
+
*/
|
|
190
|
+
deleteCurrentUser() {
|
|
191
|
+
return this.#client.deleteCurrentUser();
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* Set or change the current user's password.
|
|
195
|
+
*/
|
|
196
|
+
setPassword(password) {
|
|
197
|
+
return this.#client.setPassword(password);
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* Get a value from user storage.
|
|
201
|
+
*/
|
|
202
|
+
getUserStorage(key) {
|
|
203
|
+
return this.#client.getUserStorage(key);
|
|
204
|
+
}
|
|
205
|
+
/**
|
|
206
|
+
* Get all user storage data.
|
|
207
|
+
*/
|
|
208
|
+
getAllUserStorage() {
|
|
209
|
+
return this.#client.getAllUserStorage();
|
|
210
|
+
}
|
|
178
211
|
/**
|
|
179
212
|
* Set a value in user storage.
|
|
180
213
|
* Updates reactive state immediately, then syncs to server.
|
package/dist/space.svelte.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { RoolSpace, ChannelInfo, ConnectionState, RoolUserRole, LinkAccess, SpaceMember } from '@rool-dev/sdk';
|
|
2
2
|
import { type ReactiveChannel } from './channel.svelte.js';
|
|
3
|
+
import { ReactiveFileTree } from './file-tree.svelte.js';
|
|
3
4
|
/**
|
|
4
5
|
* A reactive wrapper around a RoolSpace. Exposes reactive `channels` and
|
|
5
6
|
* `connectionState`, and provides `openChannel()` that returns a ReactiveChannel.
|
|
@@ -29,6 +30,7 @@ declare class ReactiveSpaceImpl {
|
|
|
29
30
|
get linkAccess(): LinkAccess;
|
|
30
31
|
get memberCount(): number;
|
|
31
32
|
get webdav(): import("@rool-dev/sdk").RoolWebDAV;
|
|
33
|
+
get fileTree(): ReactiveFileTree;
|
|
32
34
|
getStorageUsage(...args: Parameters<RoolSpace['getStorageUsage']>): Promise<import("@rool-dev/sdk").SpaceFileStorageUsage>;
|
|
33
35
|
fetchMachineResource(...args: Parameters<RoolSpace['fetchMachineResource']>): Promise<Response>;
|
|
34
36
|
rename(newName: string): Promise<void>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"space.svelte.d.ts","sourceRoot":"","sources":["../src/space.svelte.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,WAAW,EAAE,eAAe,EAAE,YAAY,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AACpH,OAAO,EAAe,KAAK,eAAe,EAAE,MAAM,qBAAqB,CAAC;
|
|
1
|
+
{"version":3,"file":"space.svelte.d.ts","sourceRoot":"","sources":["../src/space.svelte.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,WAAW,EAAE,eAAe,EAAE,YAAY,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AACpH,OAAO,EAAe,KAAK,eAAe,EAAE,MAAM,qBAAqB,CAAC;AACxE,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAEzD;;;;;;GAMG;AACH,cAAM,iBAAiB;;IASrB,eAAe,kBAA2C;gBAE9C,KAAK,EAAE,SAAS;IAoB5B,IAAI,QAAQ,YAA2B;IAEvC;;;OAGG;IACG,WAAW,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,CAAC;IAY9D;;;OAGG;IACH,KAAK,IAAI,IAAI;IAiBb,IAAI,QAAQ,IAAI,WAAW,EAAE,CAA8B;IAG3D,IAAI,EAAE,IAAI,MAAM,CAA2B;IAC3C,IAAI,IAAI,IAAI,MAAM,CAA6B;IAC/C,IAAI,IAAI,IAAI,YAAY,CAA6B;IACrD,IAAI,UAAU,IAAI,UAAU,CAAmC;IAC/D,IAAI,WAAW,IAAI,MAAM,CAAoC;IAC7D,IAAI,MAAM,uCAAiC;IAC3C,IAAI,QAAQ,IAAI,gBAAgB,CAA2B;IAG3D,eAAe,CAAC,GAAG,IAAI,EAAE,UAAU,CAAC,SAAS,CAAC,iBAAiB,CAAC,CAAC;IACjE,oBAAoB,CAAC,GAAG,IAAI,EAAE,UAAU,CAAC,SAAS,CAAC,sBAAsB,CAAC,CAAC;IAG3E,MAAM,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IACtC,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC;IACvB,SAAS,IAAI,OAAO,CAAC,WAAW,EAAE,CAAC;IACnC,OAAO,CAAC,GAAG,IAAI,EAAE,UAAU,CAAC,SAAS,CAAC,SAAS,CAAC,CAAC;IACjD,UAAU,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IACzC,aAAa,CAAC,GAAG,IAAI,EAAE,UAAU,CAAC,SAAS,CAAC,eAAe,CAAC,CAAC;IAC7D,aAAa,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAC7D,aAAa,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAC/C,gBAAgB,CAAC,WAAW,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IACzE,aAAa,IAAI,OAAO,CAAC,IAAI,CAAC;IAC9B,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IAGxB,EAAE,CAAC,GAAG,IAAI,EAAE,UAAU,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC;IACvC,GAAG,CAAC,GAAG,IAAI,EAAE,UAAU,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;CAC1C;AAED,wBAAgB,SAAS,CAAC,KAAK,EAAE,SAAS,GAAG,aAAa,CAEzD;AAED,MAAM,MAAM,aAAa,GAAG,iBAAiB,CAAC"}
|
package/dist/space.svelte.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { wrapChannel } from './channel.svelte.js';
|
|
2
|
+
import { ReactiveFileTree } from './file-tree.svelte.js';
|
|
2
3
|
/**
|
|
3
4
|
* A reactive wrapper around a RoolSpace. Exposes reactive `channels` and
|
|
4
5
|
* `connectionState`, and provides `openChannel()` that returns a ReactiveChannel.
|
|
@@ -10,12 +11,14 @@ class ReactiveSpaceImpl {
|
|
|
10
11
|
#space;
|
|
11
12
|
#channels = new Map();
|
|
12
13
|
#unsubscribers = [];
|
|
14
|
+
#fileTree;
|
|
13
15
|
#closed = false;
|
|
14
16
|
// Reactive state mirroring the underlying space
|
|
15
17
|
#channelList = $state([]);
|
|
16
18
|
connectionState = $state('reconnecting');
|
|
17
19
|
constructor(space) {
|
|
18
20
|
this.#space = space;
|
|
21
|
+
this.#fileTree = new ReactiveFileTree(space);
|
|
19
22
|
this.#channelList = space.channels;
|
|
20
23
|
const refreshChannels = () => { this.#channelList = space.channels; };
|
|
21
24
|
space.on('channelCreated', refreshChannels);
|
|
@@ -42,7 +45,7 @@ class ReactiveSpaceImpl {
|
|
|
42
45
|
if (existing && !existing.isClosed)
|
|
43
46
|
return existing;
|
|
44
47
|
const raw = await this.#space.openChannel(channelId);
|
|
45
|
-
const reactive = wrapChannel(raw);
|
|
48
|
+
const reactive = wrapChannel(raw, this.#fileTree);
|
|
46
49
|
this.#channels.set(channelId, reactive);
|
|
47
50
|
return reactive;
|
|
48
51
|
}
|
|
@@ -61,6 +64,7 @@ class ReactiveSpaceImpl {
|
|
|
61
64
|
for (const unsub of this.#unsubscribers)
|
|
62
65
|
unsub();
|
|
63
66
|
this.#unsubscribers.length = 0;
|
|
67
|
+
this.#fileTree.close();
|
|
64
68
|
this.#space.close();
|
|
65
69
|
}
|
|
66
70
|
// Reactive getters
|
|
@@ -72,6 +76,7 @@ class ReactiveSpaceImpl {
|
|
|
72
76
|
get linkAccess() { return this.#space.linkAccess; }
|
|
73
77
|
get memberCount() { return this.#space.memberCount; }
|
|
74
78
|
get webdav() { return this.#space.webdav; }
|
|
79
|
+
get fileTree() { return this.#fileTree; }
|
|
75
80
|
// Proxy resource methods
|
|
76
81
|
getStorageUsage(...args) { return this.#space.getStorageUsage(...args); }
|
|
77
82
|
fetchMachineResource(...args) { return this.#space.fetchMachineResource(...args); }
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rool-dev/svelte",
|
|
3
|
-
"version": "0.10.2-dev.
|
|
3
|
+
"version": "0.10.2-dev.491e451",
|
|
4
4
|
"description": "Svelte 5 runes for Rool Spaces",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"svelte": "./dist/index.js",
|
|
@@ -41,7 +41,7 @@
|
|
|
41
41
|
},
|
|
42
42
|
"license": "MIT",
|
|
43
43
|
"dependencies": {
|
|
44
|
-
"@rool-dev/sdk": "0.10.2-dev.
|
|
44
|
+
"@rool-dev/sdk": "0.10.2-dev.491e451"
|
|
45
45
|
},
|
|
46
46
|
"peerDependencies": {
|
|
47
47
|
"svelte": "^5.0.0"
|