@kispace-io/core 0.7.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/dist/api/base-classes.d.ts +7 -0
- package/dist/api/base-classes.d.ts.map +1 -0
- package/dist/api/constants.d.ts +2 -0
- package/dist/api/constants.d.ts.map +1 -0
- package/dist/api/index.d.ts +6 -0
- package/dist/api/index.d.ts.map +1 -0
- package/dist/api/index.js +80 -0
- package/dist/api/index.js.map +1 -0
- package/dist/api/services.d.ts +27 -0
- package/dist/api/services.d.ts.map +1 -0
- package/dist/api/types.d.ts +11 -0
- package/dist/api/types.d.ts.map +1 -0
- package/dist/commands/files.d.ts +2 -0
- package/dist/commands/files.d.ts.map +1 -0
- package/dist/commands/global.d.ts +1 -0
- package/dist/commands/global.d.ts.map +1 -0
- package/dist/commands/index.d.ts +1 -0
- package/dist/commands/index.d.ts.map +1 -0
- package/dist/commands/version-info.d.ts +2 -0
- package/dist/commands/version-info.d.ts.map +1 -0
- package/dist/components/index.d.ts +1 -0
- package/dist/components/index.d.ts.map +1 -0
- package/dist/components/k-app-selector.d.ts +17 -0
- package/dist/components/k-app-selector.d.ts.map +1 -0
- package/dist/components/k-app-switcher.d.ts +13 -0
- package/dist/components/k-app-switcher.d.ts.map +1 -0
- package/dist/components/k-command.d.ts +31 -0
- package/dist/components/k-command.d.ts.map +1 -0
- package/dist/components/k-extensions.d.ts +32 -0
- package/dist/components/k-extensions.d.ts.map +1 -0
- package/dist/components/k-fastviews.d.ts +34 -0
- package/dist/components/k-fastviews.d.ts.map +1 -0
- package/dist/components/k-filebrowser.d.ts +40 -0
- package/dist/components/k-filebrowser.d.ts.map +1 -0
- package/dist/components/k-language-selector.d.ts +12 -0
- package/dist/components/k-language-selector.d.ts.map +1 -0
- package/dist/components/k-log-terminal.d.ts +36 -0
- package/dist/components/k-log-terminal.d.ts.map +1 -0
- package/dist/components/k-part-name.d.ts +12 -0
- package/dist/components/k-part-name.d.ts.map +1 -0
- package/dist/components/k-tasks.d.ts +13 -0
- package/dist/components/k-tasks.d.ts.map +1 -0
- package/dist/components/k-workspace-name.d.ts +14 -0
- package/dist/components/k-workspace-name.d.ts.map +1 -0
- package/dist/contributions/default-ui-contributions.d.ts +2 -0
- package/dist/contributions/default-ui-contributions.d.ts.map +1 -0
- package/dist/contributions/index.d.ts +1 -0
- package/dist/contributions/index.d.ts.map +1 -0
- package/dist/contributions/marketplace-catalog-contributions.d.ts +2 -0
- package/dist/contributions/marketplace-catalog-contributions.d.ts.map +1 -0
- package/dist/core/app-host-config.d.ts +7 -0
- package/dist/core/app-host-config.d.ts.map +1 -0
- package/dist/core/apploader.d.ts +214 -0
- package/dist/core/apploader.d.ts.map +1 -0
- package/dist/core/appstate.d.ts +12 -0
- package/dist/core/appstate.d.ts.map +1 -0
- package/dist/core/commandregistry.d.ts +79 -0
- package/dist/core/commandregistry.d.ts.map +1 -0
- package/dist/core/config.d.ts +15 -0
- package/dist/core/config.d.ts.map +1 -0
- package/dist/core/constants.d.ts +21 -0
- package/dist/core/constants.d.ts.map +1 -0
- package/dist/core/contributionregistry.d.ts +49 -0
- package/dist/core/contributionregistry.d.ts.map +1 -0
- package/dist/core/di.d.ts +18 -0
- package/dist/core/di.d.ts.map +1 -0
- package/dist/core/dialogservice.d.ts +33 -0
- package/dist/core/dialogservice.d.ts.map +1 -0
- package/dist/core/editorregistry.d.ts +73 -0
- package/dist/core/editorregistry.d.ts.map +1 -0
- package/dist/core/esmsh-service.d.ts +40 -0
- package/dist/core/esmsh-service.d.ts.map +1 -0
- package/dist/core/events.d.ts +7 -0
- package/dist/core/events.d.ts.map +1 -0
- package/dist/core/events.js +63 -0
- package/dist/core/events.js.map +1 -0
- package/dist/core/extensionregistry.d.ts +98 -0
- package/dist/core/extensionregistry.d.ts.map +1 -0
- package/dist/core/filesys.d.ts +139 -0
- package/dist/core/filesys.d.ts.map +1 -0
- package/dist/core/i18n.d.ts +50 -0
- package/dist/core/i18n.d.ts.map +1 -0
- package/dist/core/index.d.ts +1 -0
- package/dist/core/index.d.ts.map +1 -0
- package/dist/core/k-utils.d.ts +2 -0
- package/dist/core/k-utils.d.ts.map +1 -0
- package/dist/core/keybindings.d.ts +67 -0
- package/dist/core/keybindings.d.ts.map +1 -0
- package/dist/core/logger.d.ts +44 -0
- package/dist/core/logger.d.ts.map +1 -0
- package/dist/core/marketplaceregistry.d.ts +25 -0
- package/dist/core/marketplaceregistry.d.ts.map +1 -0
- package/dist/core/packageinfoservice.d.ts +16 -0
- package/dist/core/packageinfoservice.d.ts.map +1 -0
- package/dist/core/persistenceservice.d.ts +6 -0
- package/dist/core/persistenceservice.d.ts.map +1 -0
- package/dist/core/settingsservice.d.ts +19 -0
- package/dist/core/settingsservice.d.ts.map +1 -0
- package/dist/core/signals.d.ts +3 -0
- package/dist/core/signals.d.ts.map +1 -0
- package/dist/core/taskservice.d.ts +20 -0
- package/dist/core/taskservice.d.ts.map +1 -0
- package/dist/core/toast.d.ts +4 -0
- package/dist/core/toast.d.ts.map +1 -0
- package/dist/core/tree-utils.d.ts +16 -0
- package/dist/core/tree-utils.d.ts.map +1 -0
- package/dist/dialogs/confirm-dialog.d.ts +14 -0
- package/dist/dialogs/confirm-dialog.d.ts.map +1 -0
- package/dist/dialogs/index.d.ts +5 -0
- package/dist/dialogs/index.d.ts.map +1 -0
- package/dist/dialogs/info-dialog.d.ts +13 -0
- package/dist/dialogs/info-dialog.d.ts.map +1 -0
- package/dist/dialogs/navigable-info-dialog.d.ts +33 -0
- package/dist/dialogs/navigable-info-dialog.d.ts.map +1 -0
- package/dist/dialogs/prompt-dialog.d.ts +21 -0
- package/dist/dialogs/prompt-dialog.d.ts.map +1 -0
- package/dist/externals/lit.d.ts +20 -0
- package/dist/externals/lit.d.ts.map +1 -0
- package/dist/externals/lit.js +15 -0
- package/dist/externals/lit.js.map +1 -0
- package/dist/externals/third-party.d.ts +7 -0
- package/dist/externals/third-party.d.ts.map +1 -0
- package/dist/externals/third-party.js +2 -0
- package/dist/externals/third-party.js.map +1 -0
- package/dist/externals/webawesome.d.ts +1 -0
- package/dist/externals/webawesome.d.ts.map +1 -0
- package/dist/externals/webawesome.js +52 -0
- package/dist/externals/webawesome.js.map +1 -0
- package/dist/i18n/extensions.json.d.ts +42 -0
- package/dist/i18n/fastviews.json.d.ts +13 -0
- package/dist/i18n/filebrowser.json.d.ts +35 -0
- package/dist/i18n/index.d.ts +2 -0
- package/dist/i18n/index.d.ts.map +1 -0
- package/dist/i18n/logterminal.json.d.ts +45 -0
- package/dist/i18n/partname.json.d.ts +15 -0
- package/dist/i18n/tasks.json.d.ts +15 -0
- package/dist/i18n/workspace.json.d.ts +15 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +80 -0
- package/dist/index.js.map +1 -0
- package/dist/k-icon-BZC7dQV0.js +492 -0
- package/dist/k-icon-BZC7dQV0.js.map +1 -0
- package/dist/k-nocontent-Bh_yToGh.js +48 -0
- package/dist/k-nocontent-Bh_yToGh.js.map +1 -0
- package/dist/k-resizable-grid-Ch3iWZaL.js +3157 -0
- package/dist/k-resizable-grid-Ch3iWZaL.js.map +1 -0
- package/dist/k-standard-layout-CQ1VZoxa.js +5011 -0
- package/dist/k-standard-layout-CQ1VZoxa.js.map +1 -0
- package/dist/layouts/k-standard-layout.d.ts +16 -0
- package/dist/layouts/k-standard-layout.d.ts.map +1 -0
- package/dist/parts/index.d.ts +1 -0
- package/dist/parts/index.d.ts.map +1 -0
- package/dist/parts/index.js +53 -0
- package/dist/parts/index.js.map +1 -0
- package/dist/parts/k-app.d.ts +11 -0
- package/dist/parts/k-app.d.ts.map +1 -0
- package/dist/parts/k-container.d.ts +4 -0
- package/dist/parts/k-container.d.ts.map +1 -0
- package/dist/parts/k-contextmenu.d.ts +38 -0
- package/dist/parts/k-contextmenu.d.ts.map +1 -0
- package/dist/parts/k-dialog-content.d.ts +9 -0
- package/dist/parts/k-dialog-content.d.ts.map +1 -0
- package/dist/parts/k-element.d.ts +36 -0
- package/dist/parts/k-element.d.ts.map +1 -0
- package/dist/parts/k-part.d.ts +96 -0
- package/dist/parts/k-part.d.ts.map +1 -0
- package/dist/parts/k-resizable-grid.d.ts +31 -0
- package/dist/parts/k-resizable-grid.d.ts.map +1 -0
- package/dist/parts/k-tabs.d.ts +74 -0
- package/dist/parts/k-tabs.d.ts.map +1 -0
- package/dist/parts/k-toolbar.d.ts +21 -0
- package/dist/parts/k-toolbar.d.ts.map +1 -0
- package/dist/widgets/index.d.ts +1 -0
- package/dist/widgets/index.d.ts.map +1 -0
- package/dist/widgets/index.js +3 -0
- package/dist/widgets/index.js.map +1 -0
- package/dist/widgets/k-icon.d.ts +10 -0
- package/dist/widgets/k-icon.d.ts.map +1 -0
- package/dist/widgets/k-nocontent.d.ts +13 -0
- package/dist/widgets/k-nocontent.d.ts.map +1 -0
- package/dist/widgets/k-widget.d.ts +25 -0
- package/dist/widgets/k-widget.d.ts.map +1 -0
- package/package.json +81 -0
- package/src/api/base-classes.ts +10 -0
- package/src/api/constants.ts +3 -0
- package/src/api/index.ts +31 -0
- package/src/api/services.ts +52 -0
- package/src/api/types.ts +46 -0
- package/src/commands/files.ts +829 -0
- package/src/commands/global.ts +225 -0
- package/src/commands/index.ts +4 -0
- package/src/commands/version-info.ts +214 -0
- package/src/components/index.ts +10 -0
- package/src/components/k-app-selector.ts +233 -0
- package/src/components/k-app-switcher.ts +126 -0
- package/src/components/k-command.ts +236 -0
- package/src/components/k-extensions.ts +615 -0
- package/src/components/k-fastviews.ts +314 -0
- package/src/components/k-filebrowser.ts +442 -0
- package/src/components/k-language-selector.ts +166 -0
- package/src/components/k-log-terminal.ts +337 -0
- package/src/components/k-part-name.ts +54 -0
- package/src/components/k-tasks.ts +267 -0
- package/src/components/k-workspace-name.ts +56 -0
- package/src/contributions/default-ui-contributions.ts +51 -0
- package/src/contributions/index.ts +3 -0
- package/src/contributions/marketplace-catalog-contributions.ts +6 -0
- package/src/core/app-host-config.ts +23 -0
- package/src/core/apploader.ts +630 -0
- package/src/core/appstate.ts +15 -0
- package/src/core/commandregistry.ts +210 -0
- package/src/core/config.ts +29 -0
- package/src/core/constants.ts +27 -0
- package/src/core/contributionregistry.ts +77 -0
- package/src/core/di.ts +54 -0
- package/src/core/dialogservice.ts +266 -0
- package/src/core/editorregistry.ts +303 -0
- package/src/core/esmsh-service.ts +404 -0
- package/src/core/events.ts +68 -0
- package/src/core/extensionregistry.ts +399 -0
- package/src/core/filesys.ts +618 -0
- package/src/core/i18n.ts +221 -0
- package/src/core/index.ts +51 -0
- package/src/core/k-utils.ts +11 -0
- package/src/core/keybindings.ts +274 -0
- package/src/core/logger.ts +187 -0
- package/src/core/marketplaceregistry.ts +197 -0
- package/src/core/packageinfoservice.ts +56 -0
- package/src/core/persistenceservice.ts +15 -0
- package/src/core/settingsservice.ts +70 -0
- package/src/core/signals.ts +18 -0
- package/src/core/taskservice.ts +72 -0
- package/src/core/toast.ts +11 -0
- package/src/core/tree-utils.ts +24 -0
- package/src/dialogs/confirm-dialog.ts +72 -0
- package/src/dialogs/index.ts +4 -0
- package/src/dialogs/info-dialog.ts +67 -0
- package/src/dialogs/navigable-info-dialog.ts +256 -0
- package/src/dialogs/prompt-dialog.ts +123 -0
- package/src/externals/lit.ts +26 -0
- package/src/externals/third-party.ts +9 -0
- package/src/externals/webawesome.ts +54 -0
- package/src/i18n/extensions.json +39 -0
- package/src/i18n/fastviews.json +10 -0
- package/src/i18n/filebrowser.json +33 -0
- package/src/i18n/index.ts +25 -0
- package/src/i18n/logterminal.json +42 -0
- package/src/i18n/partname.json +12 -0
- package/src/i18n/tasks.json +12 -0
- package/src/i18n/workspace.json +12 -0
- package/src/icons/icons.txt +3 -0
- package/src/icons/js.svg +6 -0
- package/src/icons/jupyter.svg +18 -0
- package/src/icons/python.svg +15 -0
- package/src/index.ts +3 -0
- package/src/layouts/k-standard-layout.ts +174 -0
- package/src/parts/index.ts +6 -0
- package/src/parts/k-app.ts +29 -0
- package/src/parts/k-container.ts +4 -0
- package/src/parts/k-contextmenu.ts +245 -0
- package/src/parts/k-dialog-content.ts +31 -0
- package/src/parts/k-element.ts +100 -0
- package/src/parts/k-part.ts +158 -0
- package/src/parts/k-resizable-grid.ts +366 -0
- package/src/parts/k-tabs.ts +574 -0
- package/src/parts/k-toolbar.ts +158 -0
- package/src/vite-env.d.ts +2 -0
- package/src/widgets/index.ts +2 -0
- package/src/widgets/k-icon.ts +39 -0
- package/src/widgets/k-nocontent.ts +40 -0
- package/src/widgets/k-widget.ts +90 -0
|
@@ -0,0 +1,618 @@
|
|
|
1
|
+
import {persistenceService} from "./persistenceservice";
|
|
2
|
+
import {publish} from "./events";
|
|
3
|
+
import {rootContext} from "./di";
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
export const TOPIC_WORKSPACE_CHANGED = "events/filesys/workspaceChanged"
|
|
7
|
+
export const TOPIC_WORKSPACE_CONNECTED = "events/filesys/workspaceConnected"
|
|
8
|
+
|
|
9
|
+
export abstract class Resource {
|
|
10
|
+
public state: { [p: string]: any } = {};
|
|
11
|
+
|
|
12
|
+
public abstract getName(): string;
|
|
13
|
+
|
|
14
|
+
public abstract getParent(): Directory | undefined;
|
|
15
|
+
|
|
16
|
+
public abstract delete(name?: string, recursive?: boolean): Promise<void>;
|
|
17
|
+
|
|
18
|
+
public abstract copyTo(targetPath: string): Promise<void>;
|
|
19
|
+
|
|
20
|
+
public abstract rename(newName: string): Promise<void>;
|
|
21
|
+
|
|
22
|
+
public getWorkspacePath(): string {
|
|
23
|
+
const paths: string[] = []
|
|
24
|
+
let current: Resource | undefined = this
|
|
25
|
+
while (current) {
|
|
26
|
+
paths.push(current.getName())
|
|
27
|
+
current = current.getParent()
|
|
28
|
+
}
|
|
29
|
+
paths.reverse()
|
|
30
|
+
// the first path is the workspace itself, remove it as the path is always realtive to the workspace
|
|
31
|
+
paths.shift()
|
|
32
|
+
return paths.join("/");
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
public getWorkspace(): Directory {
|
|
36
|
+
let current: Resource | undefined = this
|
|
37
|
+
while (current) {
|
|
38
|
+
const parent: any = current.getParent()
|
|
39
|
+
if (parent) {
|
|
40
|
+
current = parent
|
|
41
|
+
} else {
|
|
42
|
+
break
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return current as Directory
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export const resourceComparator = (c1: Resource, c2: Resource) => {
|
|
50
|
+
if (c1 instanceof Directory && c2 instanceof File) {
|
|
51
|
+
return -1
|
|
52
|
+
}
|
|
53
|
+
if (c1 instanceof File && c2 instanceof Directory) {
|
|
54
|
+
return 1
|
|
55
|
+
}
|
|
56
|
+
return c1.getName().localeCompare(c2.getName())
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export enum FileContentType {
|
|
60
|
+
TEXT, BINARY
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export enum FileContentEncoding {
|
|
64
|
+
BASE64
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export interface FileContentsOptions {
|
|
68
|
+
contentType?: FileContentType;
|
|
69
|
+
encoding?: FileContentEncoding;
|
|
70
|
+
uri?: boolean;
|
|
71
|
+
blob?: boolean;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export interface GetResourceOptions {
|
|
75
|
+
create?: boolean
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export abstract class File extends Resource {
|
|
79
|
+
public abstract getContents(options?: FileContentsOptions): Promise<any>;
|
|
80
|
+
|
|
81
|
+
public abstract saveContents(contents: any, options?: FileContentsOptions): Promise<void>;
|
|
82
|
+
|
|
83
|
+
public abstract size(): Promise<number | null>;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export abstract class Directory extends Resource {
|
|
87
|
+
public abstract listChildren(forceRefresh: boolean): Promise<Resource[]>;
|
|
88
|
+
|
|
89
|
+
public abstract getResource(path: string, options?: GetResourceOptions): Promise<Resource | null>;
|
|
90
|
+
|
|
91
|
+
public abstract touch(): void;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export class StringFile extends File {
|
|
95
|
+
private contents: string;
|
|
96
|
+
private name: string;
|
|
97
|
+
|
|
98
|
+
constructor(contents: string, name: string) {
|
|
99
|
+
super();
|
|
100
|
+
this.contents = contents
|
|
101
|
+
this.name = name
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
async getContents(_options?: FileContentsOptions): Promise<any> {
|
|
105
|
+
return this.contents
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
async saveContents(contents: any, _options?: FileContentsOptions): Promise<void> {
|
|
109
|
+
this.contents = contents
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
async size(): Promise<number | null> {
|
|
113
|
+
return this.contents.length || null;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
async copyTo(_targetPath: string): Promise<void> {
|
|
117
|
+
throw Error(`Not supported`);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
delete(_name?: string, _recursive?: boolean): Promise<void> {
|
|
121
|
+
throw Error(`Not supported`);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
async rename(_newName: string): Promise<void> {
|
|
125
|
+
throw Error(`Not supported`);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
getName(): string {
|
|
129
|
+
return this.name;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
getParent(): Directory | undefined {
|
|
133
|
+
return undefined;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
export class FileSysFileHandleResource extends File {
|
|
138
|
+
private fileHandle: FileSystemFileHandle;
|
|
139
|
+
private parent: Directory;
|
|
140
|
+
|
|
141
|
+
constructor(fileHandle: FileSystemFileHandle, parent: Directory) {
|
|
142
|
+
super();
|
|
143
|
+
this.fileHandle = fileHandle;
|
|
144
|
+
this.parent = parent;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
getName(): string {
|
|
148
|
+
return this.fileHandle.name;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
getParent(): Directory {
|
|
152
|
+
return this.parent;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
async delete() {
|
|
156
|
+
return this.getParent().delete(this.getName());
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
async getContents(options?: FileContentsOptions): Promise<any> {
|
|
160
|
+
const file = await this.fileHandle.getFile();
|
|
161
|
+
|
|
162
|
+
if (!options || options?.contentType == FileContentType.TEXT) {
|
|
163
|
+
return await file.text()
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
if (options?.encoding == FileContentEncoding.BASE64 || options?.uri) {
|
|
167
|
+
return URL.createObjectURL(file)
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
if (options?.blob) {
|
|
171
|
+
return file
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
return file.stream()
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
async size(): Promise<number | null> {
|
|
178
|
+
try {
|
|
179
|
+
const file = await this.fileHandle.getFile();
|
|
180
|
+
return file.size;
|
|
181
|
+
} catch {
|
|
182
|
+
return null;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
async saveContents(contents: any, _options?: FileContentsOptions) {
|
|
187
|
+
const writable = await this.fileHandle.createWritable()
|
|
188
|
+
|
|
189
|
+
// Check if contents is a ReadableStream (for streaming large files)
|
|
190
|
+
if (contents && typeof contents.pipeTo === 'function') {
|
|
191
|
+
await contents.pipeTo(writable)
|
|
192
|
+
} else {
|
|
193
|
+
// Traditional approach for blobs, strings, etc.
|
|
194
|
+
const writer = writable.getWriter()
|
|
195
|
+
try {
|
|
196
|
+
await writer.write(contents)
|
|
197
|
+
} finally {
|
|
198
|
+
await writer.close()
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
async copyTo(targetPath: string): Promise<void> {
|
|
204
|
+
const contents = await this.getContents({blob: true})
|
|
205
|
+
const targetFile = await this.getWorkspace().getResource(targetPath, {create: true}) as File
|
|
206
|
+
await targetFile.saveContents(contents)
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
async rename(newName: string): Promise<void> {
|
|
210
|
+
const parent = this.getParent() as FileSysDirHandleResource;
|
|
211
|
+
if (!parent) {
|
|
212
|
+
throw new Error('Cannot rename root resource');
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
if (this.getName() === newName) {
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
if (!('move' in this.fileHandle) || typeof (this.fileHandle as any).move !== 'function') {
|
|
220
|
+
throw new Error('File rename not supported in this browser. Please use a browser with File System Access API move() support.');
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
try {
|
|
224
|
+
await (this.fileHandle as any).move(newName);
|
|
225
|
+
} catch (error: any) {
|
|
226
|
+
if (error.name === 'NotAllowedError' ||
|
|
227
|
+
error.message?.includes('not allowed') ||
|
|
228
|
+
error.message?.includes('user agent')) {
|
|
229
|
+
throw new Error('File rename failed: The operation took too long and user activation expired. Please try again.');
|
|
230
|
+
}
|
|
231
|
+
throw error;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
await parent.listChildren(true);
|
|
235
|
+
publish(TOPIC_WORKSPACE_CHANGED, this.getWorkspace());
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
export interface ResourceMap {
|
|
240
|
+
[key: string]: Resource;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
export class FileSysDirHandleResource extends Directory {
|
|
244
|
+
private dirHandle: FileSystemDirectoryHandle;
|
|
245
|
+
private files?: ResourceMap;
|
|
246
|
+
private parent?: Directory;
|
|
247
|
+
private loadingPromise?: Promise<Resource[]>;
|
|
248
|
+
|
|
249
|
+
constructor(dirHandle: FileSystemDirectoryHandle, parent?: Directory) {
|
|
250
|
+
super();
|
|
251
|
+
this.dirHandle = dirHandle;
|
|
252
|
+
this.parent = parent;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
getHandle() {
|
|
256
|
+
return this.dirHandle
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
getParent(): Directory | undefined {
|
|
260
|
+
return this.parent;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
getName(): string {
|
|
264
|
+
return this.dirHandle.name;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
async listChildren(forceRefresh: boolean = false): Promise<Resource[]> {
|
|
268
|
+
if (forceRefresh || !this.files) {
|
|
269
|
+
if (this.loadingPromise) {
|
|
270
|
+
return this.loadingPromise;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
this.loadingPromise = (async () => {
|
|
274
|
+
try {
|
|
275
|
+
const files: ResourceMap = {};
|
|
276
|
+
|
|
277
|
+
try {
|
|
278
|
+
// @ts-ignore
|
|
279
|
+
for await (const entry of this.dirHandle.values()) {
|
|
280
|
+
const isFile = (<FileSystemHandle>entry).kind === "file"
|
|
281
|
+
const child = isFile ?
|
|
282
|
+
new FileSysFileHandleResource(entry, this)
|
|
283
|
+
: new FileSysDirHandleResource(entry, this);
|
|
284
|
+
files[child.getName()] = child;
|
|
285
|
+
}
|
|
286
|
+
} catch (error: any) {
|
|
287
|
+
if (error.name === 'NotFoundError') {
|
|
288
|
+
this.files = {};
|
|
289
|
+
return [];
|
|
290
|
+
}
|
|
291
|
+
throw error;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
this.files = files;
|
|
295
|
+
return Object.values(this.files);
|
|
296
|
+
} finally {
|
|
297
|
+
this.loadingPromise = undefined;
|
|
298
|
+
}
|
|
299
|
+
})();
|
|
300
|
+
|
|
301
|
+
return this.loadingPromise;
|
|
302
|
+
}
|
|
303
|
+
return Object.values(this.files);
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
async getResource(path: string, options?: GetResourceOptions): Promise<Resource | null> {
|
|
307
|
+
if (!path) {
|
|
308
|
+
throw new Error("No path provided");
|
|
309
|
+
}
|
|
310
|
+
const segments = path.split("/")
|
|
311
|
+
let currentResource: Resource = this
|
|
312
|
+
let workspaceChanged = false;
|
|
313
|
+
try {
|
|
314
|
+
for (let i = 0; i < segments.length; i++) {
|
|
315
|
+
let segment = segments[i]
|
|
316
|
+
if (segment) {
|
|
317
|
+
segment = segment.trim()
|
|
318
|
+
}
|
|
319
|
+
if (!segment) {
|
|
320
|
+
break
|
|
321
|
+
}
|
|
322
|
+
if (currentResource instanceof FileSysDirHandleResource) {
|
|
323
|
+
await currentResource.listChildren()
|
|
324
|
+
if (!currentResource.files) {
|
|
325
|
+
return null
|
|
326
|
+
}
|
|
327
|
+
const next = currentResource.files[segment]
|
|
328
|
+
if (!next) {
|
|
329
|
+
if (options?.create) {
|
|
330
|
+
workspaceChanged = true;
|
|
331
|
+
if (i < segments.length - 1) {
|
|
332
|
+
try {
|
|
333
|
+
const newDirHandle = await currentResource.dirHandle.getDirectoryHandle(segment, {create: true})
|
|
334
|
+
const nextResource = new FileSysDirHandleResource(newDirHandle, currentResource)
|
|
335
|
+
currentResource.files[segment] = nextResource
|
|
336
|
+
currentResource = nextResource
|
|
337
|
+
if (currentResource instanceof FileSysDirHandleResource) {
|
|
338
|
+
await currentResource.listChildren()
|
|
339
|
+
}
|
|
340
|
+
continue
|
|
341
|
+
} catch (error: any) {
|
|
342
|
+
if (error.name === 'NotFoundError') {
|
|
343
|
+
throw new Error(`Directory not found or not accessible: ${segments.slice(0, i + 1).join('/')}`)
|
|
344
|
+
}
|
|
345
|
+
throw error
|
|
346
|
+
}
|
|
347
|
+
} else {
|
|
348
|
+
try {
|
|
349
|
+
const newFileHandle = await currentResource.dirHandle.getFileHandle(segment, {create: true})
|
|
350
|
+
const nextResource = new FileSysFileHandleResource(newFileHandle, currentResource)
|
|
351
|
+
currentResource.files[segment] = nextResource
|
|
352
|
+
return nextResource
|
|
353
|
+
} catch (error: any) {
|
|
354
|
+
if (error.name === 'NotFoundError') {
|
|
355
|
+
throw new Error(`File not found or not accessible: ${segments.join('/')}`)
|
|
356
|
+
}
|
|
357
|
+
throw error
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
} else {
|
|
361
|
+
return null
|
|
362
|
+
}
|
|
363
|
+
} else {
|
|
364
|
+
currentResource = next
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
} finally {
|
|
369
|
+
if (workspaceChanged) {
|
|
370
|
+
publish(TOPIC_WORKSPACE_CHANGED, this.getWorkspace());
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
return currentResource;
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
public touch() {
|
|
377
|
+
publish(TOPIC_WORKSPACE_CHANGED, this.getWorkspace());
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
async delete(name?: string, recursive: boolean = true) {
|
|
381
|
+
if (!name) {
|
|
382
|
+
const parent = this.getParent()
|
|
383
|
+
if (parent instanceof FileSysDirHandleResource) {
|
|
384
|
+
await parent.listChildren()
|
|
385
|
+
if (parent.files) {
|
|
386
|
+
delete parent.files[this.getName()]
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
this.files = undefined
|
|
390
|
+
this.loadingPromise = undefined
|
|
391
|
+
return parent?.delete(this.getName())
|
|
392
|
+
}
|
|
393
|
+
return this.dirHandle.removeEntry(name, {
|
|
394
|
+
recursive: recursive
|
|
395
|
+
}).then(async () => {
|
|
396
|
+
if (this.files) {
|
|
397
|
+
delete this.files[name]
|
|
398
|
+
}
|
|
399
|
+
publish(TOPIC_WORKSPACE_CHANGED, this.getWorkspace());
|
|
400
|
+
})
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
async copyTo(targetPath: string) {
|
|
404
|
+
for (const resource of (await this.listChildren())) {
|
|
405
|
+
const targetResourceName = [targetPath, resource.getName()].join("/")
|
|
406
|
+
await resource.copyTo(targetResourceName)
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
async rename(newName: string): Promise<void> {
|
|
411
|
+
const parent = this.getParent() as FileSysDirHandleResource;
|
|
412
|
+
if (!parent) {
|
|
413
|
+
throw new Error('Cannot rename workspace root');
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
if (this.getName() === newName) {
|
|
417
|
+
return;
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
if (!('move' in this.dirHandle) || typeof (this.dirHandle as any).move !== 'function') {
|
|
421
|
+
throw new Error('Directory rename not supported in this browser. Please use a browser with File System Access API move() support.');
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
try {
|
|
425
|
+
await (this.dirHandle as any).move(newName);
|
|
426
|
+
} catch (error: any) {
|
|
427
|
+
if (error.name === 'NotAllowedError' ||
|
|
428
|
+
error.message?.includes('not allowed') ||
|
|
429
|
+
error.message?.includes('user agent')) {
|
|
430
|
+
throw new Error('Directory rename failed: The operation took too long and user activation expired. Please try again.');
|
|
431
|
+
}
|
|
432
|
+
throw error;
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
await parent.listChildren(true);
|
|
436
|
+
publish(TOPIC_WORKSPACE_CHANGED, this.getWorkspace());
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
/**
|
|
441
|
+
* Interface for workspace contributions
|
|
442
|
+
*
|
|
443
|
+
* Allows extensions to register custom workspace implementations
|
|
444
|
+
*/
|
|
445
|
+
export interface WorkspaceContribution {
|
|
446
|
+
/**
|
|
447
|
+
* Unique identifier for this workspace type (e.g., 'filesystem', 'webdav')
|
|
448
|
+
*/
|
|
449
|
+
type: string;
|
|
450
|
+
|
|
451
|
+
/**
|
|
452
|
+
* Display name for this workspace type
|
|
453
|
+
*/
|
|
454
|
+
name: string;
|
|
455
|
+
|
|
456
|
+
/**
|
|
457
|
+
* Check if this contribution can handle the given connection input
|
|
458
|
+
*/
|
|
459
|
+
canHandle(input: any): boolean;
|
|
460
|
+
|
|
461
|
+
/**
|
|
462
|
+
* Create a Directory from the given connection input
|
|
463
|
+
*/
|
|
464
|
+
connect(input: any): Promise<Directory>;
|
|
465
|
+
|
|
466
|
+
/**
|
|
467
|
+
* Restore a workspace from persisted data
|
|
468
|
+
*/
|
|
469
|
+
restore?(data: any): Promise<Directory | undefined>;
|
|
470
|
+
|
|
471
|
+
/**
|
|
472
|
+
* Prepare data for persistence
|
|
473
|
+
*/
|
|
474
|
+
persist?(workspace: Directory): Promise<any>;
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
interface PersistedWorkspaceData {
|
|
478
|
+
type: string;
|
|
479
|
+
data: any;
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
export class WorkspaceService {
|
|
483
|
+
private workspace?: Promise<Directory | undefined>;
|
|
484
|
+
private currentType?: string;
|
|
485
|
+
private contributions: Map<string, WorkspaceContribution> = new Map();
|
|
486
|
+
|
|
487
|
+
constructor() {
|
|
488
|
+
this.workspace = this.loadPersistedWorkspace();
|
|
489
|
+
this.workspace.then(workspace => {
|
|
490
|
+
if (workspace) {
|
|
491
|
+
publish(TOPIC_WORKSPACE_CONNECTED, workspace);
|
|
492
|
+
}
|
|
493
|
+
});
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
/**
|
|
497
|
+
* Register a workspace contribution
|
|
498
|
+
*/
|
|
499
|
+
registerContribution(contribution: WorkspaceContribution): void {
|
|
500
|
+
this.contributions.set(contribution.type, contribution);
|
|
501
|
+
console.log(`Workspace contribution registered: ${contribution.name} (${contribution.type})`);
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
/**
|
|
505
|
+
* Get all registered workspace contributions
|
|
506
|
+
*/
|
|
507
|
+
getContributions(): WorkspaceContribution[] {
|
|
508
|
+
return Array.from(this.contributions.values());
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
private async loadPersistedWorkspace(): Promise<Directory | undefined> {
|
|
512
|
+
const persistedData = await persistenceService.getObject("workspace_data") as PersistedWorkspaceData | null;
|
|
513
|
+
|
|
514
|
+
if (!persistedData) {
|
|
515
|
+
return undefined;
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
const contribution = this.contributions.get(persistedData.type);
|
|
519
|
+
if (!contribution) {
|
|
520
|
+
console.warn(`No contribution found for workspace type: ${persistedData.type}`);
|
|
521
|
+
return undefined;
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
try {
|
|
525
|
+
if (contribution.restore) {
|
|
526
|
+
const workspace = await contribution.restore(persistedData.data);
|
|
527
|
+
if (workspace) {
|
|
528
|
+
this.currentType = persistedData.type;
|
|
529
|
+
}
|
|
530
|
+
return workspace;
|
|
531
|
+
}
|
|
532
|
+
} catch (error) {
|
|
533
|
+
console.error(`Failed to restore workspace of type ${persistedData.type}:`, error);
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
return undefined;
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
async connectWorkspace(input: any): Promise<Directory> {
|
|
540
|
+
// Find a contribution that can handle this input
|
|
541
|
+
const contribution = Array.from(this.contributions.values()).find(c => c.canHandle(input));
|
|
542
|
+
|
|
543
|
+
if (!contribution) {
|
|
544
|
+
throw new Error('No workspace contribution can handle this input');
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
// Connect using the contribution
|
|
548
|
+
const workspace = await contribution.connect(input);
|
|
549
|
+
|
|
550
|
+
// Persist the workspace data
|
|
551
|
+
const persistData = contribution.persist ? await contribution.persist(workspace) : input;
|
|
552
|
+
const workspaceData: PersistedWorkspaceData = {
|
|
553
|
+
type: contribution.type,
|
|
554
|
+
data: persistData
|
|
555
|
+
};
|
|
556
|
+
await persistenceService.persistObject("workspace_data", workspaceData);
|
|
557
|
+
|
|
558
|
+
// Update current workspace
|
|
559
|
+
this.currentType = contribution.type;
|
|
560
|
+
this.workspace = Promise.resolve(workspace);
|
|
561
|
+
publish(TOPIC_WORKSPACE_CONNECTED, workspace);
|
|
562
|
+
|
|
563
|
+
return workspace;
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
public async getWorkspace(): Promise<Directory | undefined> {
|
|
567
|
+
if (!this.workspace) {
|
|
568
|
+
throw new Error('No workspace connected.');
|
|
569
|
+
}
|
|
570
|
+
return await this.workspace;
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
public isConnected(): boolean {
|
|
574
|
+
return !!this.workspace;
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
public getWorkspaceType(): string | undefined {
|
|
578
|
+
return this.currentType;
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
public async disconnectWorkspace(): Promise<void> {
|
|
582
|
+
this.workspace = undefined;
|
|
583
|
+
this.currentType = undefined;
|
|
584
|
+
await persistenceService.persistObject("workspace_data", null);
|
|
585
|
+
await persistenceService.persistObject("workspace", null); // Clean up legacy
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
export const workspaceService = new WorkspaceService();
|
|
590
|
+
rootContext.put("workspaceService", workspaceService);
|
|
591
|
+
|
|
592
|
+
// Register default filesystem contribution
|
|
593
|
+
workspaceService.registerContribution({
|
|
594
|
+
type: 'filesystem',
|
|
595
|
+
name: 'Local File System',
|
|
596
|
+
|
|
597
|
+
canHandle(input: any): boolean {
|
|
598
|
+
return input && 'kind' in input && input.kind === 'directory';
|
|
599
|
+
},
|
|
600
|
+
|
|
601
|
+
async connect(input: FileSystemDirectoryHandle): Promise<Directory> {
|
|
602
|
+
return new FileSysDirHandleResource(input);
|
|
603
|
+
},
|
|
604
|
+
|
|
605
|
+
async restore(data: any): Promise<Directory | undefined> {
|
|
606
|
+
if (data && 'kind' in data && data.kind === 'directory') {
|
|
607
|
+
return new FileSysDirHandleResource(data, undefined);
|
|
608
|
+
}
|
|
609
|
+
return undefined;
|
|
610
|
+
},
|
|
611
|
+
|
|
612
|
+
async persist(workspace: Directory): Promise<any> {
|
|
613
|
+
if (workspace instanceof FileSysDirHandleResource) {
|
|
614
|
+
return workspace.getHandle();
|
|
615
|
+
}
|
|
616
|
+
return null;
|
|
617
|
+
}
|
|
618
|
+
});
|