@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.
Files changed (272) hide show
  1. package/dist/api/base-classes.d.ts +7 -0
  2. package/dist/api/base-classes.d.ts.map +1 -0
  3. package/dist/api/constants.d.ts +2 -0
  4. package/dist/api/constants.d.ts.map +1 -0
  5. package/dist/api/index.d.ts +6 -0
  6. package/dist/api/index.d.ts.map +1 -0
  7. package/dist/api/index.js +80 -0
  8. package/dist/api/index.js.map +1 -0
  9. package/dist/api/services.d.ts +27 -0
  10. package/dist/api/services.d.ts.map +1 -0
  11. package/dist/api/types.d.ts +11 -0
  12. package/dist/api/types.d.ts.map +1 -0
  13. package/dist/commands/files.d.ts +2 -0
  14. package/dist/commands/files.d.ts.map +1 -0
  15. package/dist/commands/global.d.ts +1 -0
  16. package/dist/commands/global.d.ts.map +1 -0
  17. package/dist/commands/index.d.ts +1 -0
  18. package/dist/commands/index.d.ts.map +1 -0
  19. package/dist/commands/version-info.d.ts +2 -0
  20. package/dist/commands/version-info.d.ts.map +1 -0
  21. package/dist/components/index.d.ts +1 -0
  22. package/dist/components/index.d.ts.map +1 -0
  23. package/dist/components/k-app-selector.d.ts +17 -0
  24. package/dist/components/k-app-selector.d.ts.map +1 -0
  25. package/dist/components/k-app-switcher.d.ts +13 -0
  26. package/dist/components/k-app-switcher.d.ts.map +1 -0
  27. package/dist/components/k-command.d.ts +31 -0
  28. package/dist/components/k-command.d.ts.map +1 -0
  29. package/dist/components/k-extensions.d.ts +32 -0
  30. package/dist/components/k-extensions.d.ts.map +1 -0
  31. package/dist/components/k-fastviews.d.ts +34 -0
  32. package/dist/components/k-fastviews.d.ts.map +1 -0
  33. package/dist/components/k-filebrowser.d.ts +40 -0
  34. package/dist/components/k-filebrowser.d.ts.map +1 -0
  35. package/dist/components/k-language-selector.d.ts +12 -0
  36. package/dist/components/k-language-selector.d.ts.map +1 -0
  37. package/dist/components/k-log-terminal.d.ts +36 -0
  38. package/dist/components/k-log-terminal.d.ts.map +1 -0
  39. package/dist/components/k-part-name.d.ts +12 -0
  40. package/dist/components/k-part-name.d.ts.map +1 -0
  41. package/dist/components/k-tasks.d.ts +13 -0
  42. package/dist/components/k-tasks.d.ts.map +1 -0
  43. package/dist/components/k-workspace-name.d.ts +14 -0
  44. package/dist/components/k-workspace-name.d.ts.map +1 -0
  45. package/dist/contributions/default-ui-contributions.d.ts +2 -0
  46. package/dist/contributions/default-ui-contributions.d.ts.map +1 -0
  47. package/dist/contributions/index.d.ts +1 -0
  48. package/dist/contributions/index.d.ts.map +1 -0
  49. package/dist/contributions/marketplace-catalog-contributions.d.ts +2 -0
  50. package/dist/contributions/marketplace-catalog-contributions.d.ts.map +1 -0
  51. package/dist/core/app-host-config.d.ts +7 -0
  52. package/dist/core/app-host-config.d.ts.map +1 -0
  53. package/dist/core/apploader.d.ts +214 -0
  54. package/dist/core/apploader.d.ts.map +1 -0
  55. package/dist/core/appstate.d.ts +12 -0
  56. package/dist/core/appstate.d.ts.map +1 -0
  57. package/dist/core/commandregistry.d.ts +79 -0
  58. package/dist/core/commandregistry.d.ts.map +1 -0
  59. package/dist/core/config.d.ts +15 -0
  60. package/dist/core/config.d.ts.map +1 -0
  61. package/dist/core/constants.d.ts +21 -0
  62. package/dist/core/constants.d.ts.map +1 -0
  63. package/dist/core/contributionregistry.d.ts +49 -0
  64. package/dist/core/contributionregistry.d.ts.map +1 -0
  65. package/dist/core/di.d.ts +18 -0
  66. package/dist/core/di.d.ts.map +1 -0
  67. package/dist/core/dialogservice.d.ts +33 -0
  68. package/dist/core/dialogservice.d.ts.map +1 -0
  69. package/dist/core/editorregistry.d.ts +73 -0
  70. package/dist/core/editorregistry.d.ts.map +1 -0
  71. package/dist/core/esmsh-service.d.ts +40 -0
  72. package/dist/core/esmsh-service.d.ts.map +1 -0
  73. package/dist/core/events.d.ts +7 -0
  74. package/dist/core/events.d.ts.map +1 -0
  75. package/dist/core/events.js +63 -0
  76. package/dist/core/events.js.map +1 -0
  77. package/dist/core/extensionregistry.d.ts +98 -0
  78. package/dist/core/extensionregistry.d.ts.map +1 -0
  79. package/dist/core/filesys.d.ts +139 -0
  80. package/dist/core/filesys.d.ts.map +1 -0
  81. package/dist/core/i18n.d.ts +50 -0
  82. package/dist/core/i18n.d.ts.map +1 -0
  83. package/dist/core/index.d.ts +1 -0
  84. package/dist/core/index.d.ts.map +1 -0
  85. package/dist/core/k-utils.d.ts +2 -0
  86. package/dist/core/k-utils.d.ts.map +1 -0
  87. package/dist/core/keybindings.d.ts +67 -0
  88. package/dist/core/keybindings.d.ts.map +1 -0
  89. package/dist/core/logger.d.ts +44 -0
  90. package/dist/core/logger.d.ts.map +1 -0
  91. package/dist/core/marketplaceregistry.d.ts +25 -0
  92. package/dist/core/marketplaceregistry.d.ts.map +1 -0
  93. package/dist/core/packageinfoservice.d.ts +16 -0
  94. package/dist/core/packageinfoservice.d.ts.map +1 -0
  95. package/dist/core/persistenceservice.d.ts +6 -0
  96. package/dist/core/persistenceservice.d.ts.map +1 -0
  97. package/dist/core/settingsservice.d.ts +19 -0
  98. package/dist/core/settingsservice.d.ts.map +1 -0
  99. package/dist/core/signals.d.ts +3 -0
  100. package/dist/core/signals.d.ts.map +1 -0
  101. package/dist/core/taskservice.d.ts +20 -0
  102. package/dist/core/taskservice.d.ts.map +1 -0
  103. package/dist/core/toast.d.ts +4 -0
  104. package/dist/core/toast.d.ts.map +1 -0
  105. package/dist/core/tree-utils.d.ts +16 -0
  106. package/dist/core/tree-utils.d.ts.map +1 -0
  107. package/dist/dialogs/confirm-dialog.d.ts +14 -0
  108. package/dist/dialogs/confirm-dialog.d.ts.map +1 -0
  109. package/dist/dialogs/index.d.ts +5 -0
  110. package/dist/dialogs/index.d.ts.map +1 -0
  111. package/dist/dialogs/info-dialog.d.ts +13 -0
  112. package/dist/dialogs/info-dialog.d.ts.map +1 -0
  113. package/dist/dialogs/navigable-info-dialog.d.ts +33 -0
  114. package/dist/dialogs/navigable-info-dialog.d.ts.map +1 -0
  115. package/dist/dialogs/prompt-dialog.d.ts +21 -0
  116. package/dist/dialogs/prompt-dialog.d.ts.map +1 -0
  117. package/dist/externals/lit.d.ts +20 -0
  118. package/dist/externals/lit.d.ts.map +1 -0
  119. package/dist/externals/lit.js +15 -0
  120. package/dist/externals/lit.js.map +1 -0
  121. package/dist/externals/third-party.d.ts +7 -0
  122. package/dist/externals/third-party.d.ts.map +1 -0
  123. package/dist/externals/third-party.js +2 -0
  124. package/dist/externals/third-party.js.map +1 -0
  125. package/dist/externals/webawesome.d.ts +1 -0
  126. package/dist/externals/webawesome.d.ts.map +1 -0
  127. package/dist/externals/webawesome.js +52 -0
  128. package/dist/externals/webawesome.js.map +1 -0
  129. package/dist/i18n/extensions.json.d.ts +42 -0
  130. package/dist/i18n/fastviews.json.d.ts +13 -0
  131. package/dist/i18n/filebrowser.json.d.ts +35 -0
  132. package/dist/i18n/index.d.ts +2 -0
  133. package/dist/i18n/index.d.ts.map +1 -0
  134. package/dist/i18n/logterminal.json.d.ts +45 -0
  135. package/dist/i18n/partname.json.d.ts +15 -0
  136. package/dist/i18n/tasks.json.d.ts +15 -0
  137. package/dist/i18n/workspace.json.d.ts +15 -0
  138. package/dist/index.d.ts +2 -0
  139. package/dist/index.d.ts.map +1 -0
  140. package/dist/index.js +80 -0
  141. package/dist/index.js.map +1 -0
  142. package/dist/k-icon-BZC7dQV0.js +492 -0
  143. package/dist/k-icon-BZC7dQV0.js.map +1 -0
  144. package/dist/k-nocontent-Bh_yToGh.js +48 -0
  145. package/dist/k-nocontent-Bh_yToGh.js.map +1 -0
  146. package/dist/k-resizable-grid-Ch3iWZaL.js +3157 -0
  147. package/dist/k-resizable-grid-Ch3iWZaL.js.map +1 -0
  148. package/dist/k-standard-layout-CQ1VZoxa.js +5011 -0
  149. package/dist/k-standard-layout-CQ1VZoxa.js.map +1 -0
  150. package/dist/layouts/k-standard-layout.d.ts +16 -0
  151. package/dist/layouts/k-standard-layout.d.ts.map +1 -0
  152. package/dist/parts/index.d.ts +1 -0
  153. package/dist/parts/index.d.ts.map +1 -0
  154. package/dist/parts/index.js +53 -0
  155. package/dist/parts/index.js.map +1 -0
  156. package/dist/parts/k-app.d.ts +11 -0
  157. package/dist/parts/k-app.d.ts.map +1 -0
  158. package/dist/parts/k-container.d.ts +4 -0
  159. package/dist/parts/k-container.d.ts.map +1 -0
  160. package/dist/parts/k-contextmenu.d.ts +38 -0
  161. package/dist/parts/k-contextmenu.d.ts.map +1 -0
  162. package/dist/parts/k-dialog-content.d.ts +9 -0
  163. package/dist/parts/k-dialog-content.d.ts.map +1 -0
  164. package/dist/parts/k-element.d.ts +36 -0
  165. package/dist/parts/k-element.d.ts.map +1 -0
  166. package/dist/parts/k-part.d.ts +96 -0
  167. package/dist/parts/k-part.d.ts.map +1 -0
  168. package/dist/parts/k-resizable-grid.d.ts +31 -0
  169. package/dist/parts/k-resizable-grid.d.ts.map +1 -0
  170. package/dist/parts/k-tabs.d.ts +74 -0
  171. package/dist/parts/k-tabs.d.ts.map +1 -0
  172. package/dist/parts/k-toolbar.d.ts +21 -0
  173. package/dist/parts/k-toolbar.d.ts.map +1 -0
  174. package/dist/widgets/index.d.ts +1 -0
  175. package/dist/widgets/index.d.ts.map +1 -0
  176. package/dist/widgets/index.js +3 -0
  177. package/dist/widgets/index.js.map +1 -0
  178. package/dist/widgets/k-icon.d.ts +10 -0
  179. package/dist/widgets/k-icon.d.ts.map +1 -0
  180. package/dist/widgets/k-nocontent.d.ts +13 -0
  181. package/dist/widgets/k-nocontent.d.ts.map +1 -0
  182. package/dist/widgets/k-widget.d.ts +25 -0
  183. package/dist/widgets/k-widget.d.ts.map +1 -0
  184. package/package.json +81 -0
  185. package/src/api/base-classes.ts +10 -0
  186. package/src/api/constants.ts +3 -0
  187. package/src/api/index.ts +31 -0
  188. package/src/api/services.ts +52 -0
  189. package/src/api/types.ts +46 -0
  190. package/src/commands/files.ts +829 -0
  191. package/src/commands/global.ts +225 -0
  192. package/src/commands/index.ts +4 -0
  193. package/src/commands/version-info.ts +214 -0
  194. package/src/components/index.ts +10 -0
  195. package/src/components/k-app-selector.ts +233 -0
  196. package/src/components/k-app-switcher.ts +126 -0
  197. package/src/components/k-command.ts +236 -0
  198. package/src/components/k-extensions.ts +615 -0
  199. package/src/components/k-fastviews.ts +314 -0
  200. package/src/components/k-filebrowser.ts +442 -0
  201. package/src/components/k-language-selector.ts +166 -0
  202. package/src/components/k-log-terminal.ts +337 -0
  203. package/src/components/k-part-name.ts +54 -0
  204. package/src/components/k-tasks.ts +267 -0
  205. package/src/components/k-workspace-name.ts +56 -0
  206. package/src/contributions/default-ui-contributions.ts +51 -0
  207. package/src/contributions/index.ts +3 -0
  208. package/src/contributions/marketplace-catalog-contributions.ts +6 -0
  209. package/src/core/app-host-config.ts +23 -0
  210. package/src/core/apploader.ts +630 -0
  211. package/src/core/appstate.ts +15 -0
  212. package/src/core/commandregistry.ts +210 -0
  213. package/src/core/config.ts +29 -0
  214. package/src/core/constants.ts +27 -0
  215. package/src/core/contributionregistry.ts +77 -0
  216. package/src/core/di.ts +54 -0
  217. package/src/core/dialogservice.ts +266 -0
  218. package/src/core/editorregistry.ts +303 -0
  219. package/src/core/esmsh-service.ts +404 -0
  220. package/src/core/events.ts +68 -0
  221. package/src/core/extensionregistry.ts +399 -0
  222. package/src/core/filesys.ts +618 -0
  223. package/src/core/i18n.ts +221 -0
  224. package/src/core/index.ts +51 -0
  225. package/src/core/k-utils.ts +11 -0
  226. package/src/core/keybindings.ts +274 -0
  227. package/src/core/logger.ts +187 -0
  228. package/src/core/marketplaceregistry.ts +197 -0
  229. package/src/core/packageinfoservice.ts +56 -0
  230. package/src/core/persistenceservice.ts +15 -0
  231. package/src/core/settingsservice.ts +70 -0
  232. package/src/core/signals.ts +18 -0
  233. package/src/core/taskservice.ts +72 -0
  234. package/src/core/toast.ts +11 -0
  235. package/src/core/tree-utils.ts +24 -0
  236. package/src/dialogs/confirm-dialog.ts +72 -0
  237. package/src/dialogs/index.ts +4 -0
  238. package/src/dialogs/info-dialog.ts +67 -0
  239. package/src/dialogs/navigable-info-dialog.ts +256 -0
  240. package/src/dialogs/prompt-dialog.ts +123 -0
  241. package/src/externals/lit.ts +26 -0
  242. package/src/externals/third-party.ts +9 -0
  243. package/src/externals/webawesome.ts +54 -0
  244. package/src/i18n/extensions.json +39 -0
  245. package/src/i18n/fastviews.json +10 -0
  246. package/src/i18n/filebrowser.json +33 -0
  247. package/src/i18n/index.ts +25 -0
  248. package/src/i18n/logterminal.json +42 -0
  249. package/src/i18n/partname.json +12 -0
  250. package/src/i18n/tasks.json +12 -0
  251. package/src/i18n/workspace.json +12 -0
  252. package/src/icons/icons.txt +3 -0
  253. package/src/icons/js.svg +6 -0
  254. package/src/icons/jupyter.svg +18 -0
  255. package/src/icons/python.svg +15 -0
  256. package/src/index.ts +3 -0
  257. package/src/layouts/k-standard-layout.ts +174 -0
  258. package/src/parts/index.ts +6 -0
  259. package/src/parts/k-app.ts +29 -0
  260. package/src/parts/k-container.ts +4 -0
  261. package/src/parts/k-contextmenu.ts +245 -0
  262. package/src/parts/k-dialog-content.ts +31 -0
  263. package/src/parts/k-element.ts +100 -0
  264. package/src/parts/k-part.ts +158 -0
  265. package/src/parts/k-resizable-grid.ts +366 -0
  266. package/src/parts/k-tabs.ts +574 -0
  267. package/src/parts/k-toolbar.ts +158 -0
  268. package/src/vite-env.d.ts +2 -0
  269. package/src/widgets/index.ts +2 -0
  270. package/src/widgets/k-icon.ts +39 -0
  271. package/src/widgets/k-nocontent.ts +40 -0
  272. package/src/widgets/k-widget.ts +90 -0
@@ -0,0 +1,442 @@
1
+
2
+ import { css, html, TemplateResult } from 'lit'
3
+ import { customElement, state } from 'lit/decorators.js'
4
+ import { KPart } from "../parts/k-part";
5
+ import {
6
+ Directory,
7
+ File,
8
+ Resource,
9
+ TOPIC_WORKSPACE_CHANGED,
10
+ TOPIC_WORKSPACE_CONNECTED,
11
+ workspaceService
12
+ } from "../core/filesys";
13
+ import { when } from "lit/directives/when.js";
14
+ import { subscribe } from "../core/events";
15
+ import { createRef, ref } from "lit/directives/ref.js";
16
+ import { HIDE_DOT_RESOURCE } from "../core/constants";
17
+
18
+ import { commandRegistry } from "../core/commandregistry";
19
+ import { TreeNode, treeNodeComparator } from "../core/tree-utils";
20
+ import { activeSelectionSignal } from "../core/appstate";
21
+ import { confirmDialog } from "../dialogs";
22
+ import { editorRegistry } from "../core/editorregistry";
23
+ import { TOPIC_CONTRIBUTEIONS_CHANGED, type ContributionChangeEvent } from '../core/contributionregistry';
24
+ import { i18n } from '../core/i18n';
25
+
26
+ const t = i18n('filebrowser');
27
+
28
+ @customElement('k-filebrowser')
29
+ export class KFileBrowser extends KPart {
30
+
31
+ @state()
32
+ private root?: TreeNode;
33
+ private workspaceDir?: Directory
34
+ private treeRef = createRef<HTMLElement>();
35
+ private loadingNodes = new Set<TreeNode>();
36
+
37
+ protected doBeforeUI() {
38
+ this.initializeWorkspace();
39
+
40
+ subscribe(TOPIC_CONTRIBUTEIONS_CHANGED, (event: ContributionChangeEvent) => {
41
+ if (event.target === 'system.icons') {
42
+ this.requestUpdate();
43
+ }
44
+ });
45
+
46
+ this.subscribe(TOPIC_WORKSPACE_CHANGED, (workspaceDir: Directory) => this.onWorkspaceChanged(workspaceDir));
47
+ this.subscribe(TOPIC_WORKSPACE_CONNECTED, (workspaceDir: Directory) => this.onWorkspaceConnected(workspaceDir));
48
+ }
49
+
50
+ protected firstUpdated(changedProperties: Map<string, any>) {
51
+ super.firstUpdated(changedProperties);
52
+ this.setupDragAndDrop();
53
+ }
54
+
55
+ protected updated(changedProperties: Map<string, any>) {
56
+ super.updated(changedProperties);
57
+ if (changedProperties.has('workspaceDir') && this.workspaceDir) {
58
+ this.setupDragAndDrop();
59
+ }
60
+ }
61
+
62
+ private async initializeWorkspace() {
63
+ const workspaceDir = await workspaceService.getWorkspace()
64
+ if (workspaceDir) {
65
+ await this.loadWorkspace(workspaceDir!)
66
+ } else {
67
+ commandRegistry.execute("help")
68
+ }
69
+ }
70
+
71
+ protected renderToolbar() {
72
+ const canModify = activeSelectionSignal.get() instanceof Resource;
73
+
74
+ return html`
75
+ <k-command icon="folder-open" title="${t('CONNECT_WORKSPACE')}" dropdown="filebrowser.connections"></k-command>
76
+ <k-command cmd="reload_workspace" icon="repeat" title="${t('RELOAD_WORKSPACE')}"></k-command>
77
+ <k-command cmd="create_file" icon="plus" title="${t('CREATE_NEW')}" dropdown="filebrowser.create"></k-command>
78
+ <k-command cmd="rename_resource" icon="pen" ?disabled=${!canModify} title="${t('RENAME_RESOURCE')}"></k-command>
79
+ <k-command cmd="delete_resource" icon="trash" ?disabled=${!canModify} title="${t('DELETE_RESOURCE')}"></k-command>
80
+ `;
81
+ }
82
+
83
+ protected renderContextMenu() {
84
+ const canModify = activeSelectionSignal.get() instanceof Resource;
85
+
86
+ return html`
87
+ <k-command cmd="open_editor" icon="folder-open">${t('OPEN')}</k-command>
88
+ <k-command cmd="create_file" icon="plus" dropdown="filebrowser.create">${t('CREATE_NEW')}</k-command>
89
+ <k-command cmd="rename_resource" icon="pen" ?disabled=${!canModify}>${t('RENAME')}</k-command>
90
+ <k-command cmd="delete_resource" icon="trash" ?disabled=${!canModify}>${t('DELETE')}</k-command>
91
+ `;
92
+ }
93
+
94
+ async onWorkspaceChanged(workspaceDir: Directory) {
95
+ activeSelectionSignal.set(undefined)
96
+ await this.loadWorkspace(workspaceDir)
97
+ await this.syncTreeSelection()
98
+ }
99
+
100
+ async onWorkspaceConnected(workspaceDir: Directory) {
101
+ await this.loadWorkspace(workspaceDir)
102
+ }
103
+
104
+ async loadWorkspace(workspaceDir?: Directory) {
105
+ this.workspaceDir = workspaceDir
106
+ if (!workspaceDir) {
107
+ this.root = undefined
108
+ } else {
109
+ this.root = await this.resourceToTreeNode(workspaceDir, true)
110
+ }
111
+ }
112
+
113
+ private async syncTreeSelection() {
114
+ await this.updateComplete
115
+ const waTree = this.treeRef.value?.querySelector('wa-tree')
116
+ // @ts-ignore
117
+ const selectedItems = waTree?.selectedItems || []
118
+ if (selectedItems.length > 0) {
119
+ // @ts-ignore
120
+ activeSelectionSignal.set(selectedItems[0].model?.data)
121
+ }
122
+ }
123
+
124
+ async resourceToTreeNode(resource: Resource, loadChildren: boolean = false): Promise<TreeNode> {
125
+ const isFile = resource instanceof File;
126
+ const node: TreeNode = {
127
+ data: resource,
128
+ label: resource.getName(),
129
+ leaf: isFile,
130
+ children: []
131
+ };
132
+
133
+ if (resource instanceof Directory && loadChildren) {
134
+ for (const childResource of await resource.listChildren(false)) {
135
+ if (HIDE_DOT_RESOURCE && childResource.getName().startsWith(".")) {
136
+ continue
137
+ }
138
+ const child = await this.resourceToTreeNode(childResource, false);
139
+ node.children.push(child);
140
+ }
141
+ node.children.sort(treeNodeComparator)
142
+ }
143
+
144
+ return node;
145
+ }
146
+
147
+ createTreeItems(node: TreeNode, expanded = false): TemplateResult {
148
+ if (!node) {
149
+ return html``
150
+ }
151
+
152
+ const isLazy = !node.leaf && node.children.length === 0;
153
+ const resource = node.data as Resource;
154
+ const isFile = resource instanceof File;
155
+ const icon = isFile
156
+ ? editorRegistry.getFileIcon(resource.getName())
157
+ : (node.icon || "folder-open");
158
+
159
+ return html`
160
+ <wa-tree-item
161
+ draggable=${isFile}
162
+ @dragstart=${isFile ? this.nobubble((e: Event) => this.onDragStart(e as DragEvent, resource as File)) : undefined}
163
+ @dblclick=${this.nobubble(this.onFileDoubleClicked)}
164
+ @wa-lazy-load=${this.nobubble((e: Event) => this.onLazyLoad(e, node))}
165
+ .model=${node}
166
+ ?expanded=${expanded}
167
+ ?lazy=${isLazy}>
168
+ <span><wa-icon name=${icon} label="${node.leaf ? t('FILE') : t('FOLDER')}"></wa-icon> ${node.label}</span>
169
+ ${node.children.map(child => this.createTreeItems(child, false))}
170
+ </wa-tree-item>`
171
+ }
172
+
173
+ private onDragStart(e: DragEvent, file: File) {
174
+ if (!e.dataTransfer) return;
175
+
176
+ const filePath = file.getWorkspacePath();
177
+ const fileName = file.getName();
178
+
179
+ e.dataTransfer.effectAllowed = 'copy';
180
+ e.dataTransfer.setData('text/plain', filePath);
181
+ e.dataTransfer.setData('application/x-workspace-file', filePath);
182
+ e.dataTransfer.setData('text/uri-list', filePath);
183
+
184
+ if (e.dataTransfer.setDragImage) {
185
+ const dragImage = document.createElement('div');
186
+ dragImage.textContent = fileName;
187
+ dragImage.style.position = 'absolute';
188
+ dragImage.style.top = '-1000px';
189
+ dragImage.style.padding = '4px 8px';
190
+ dragImage.style.background = 'var(--wa-color-neutral-10)';
191
+ dragImage.style.border = '1px solid var(--wa-color-neutral-30)';
192
+ dragImage.style.borderRadius = '4px';
193
+ document.body.appendChild(dragImage);
194
+ e.dataTransfer.setDragImage(dragImage, 0, 0);
195
+ setTimeout(() => document.body.removeChild(dragImage), 0);
196
+ }
197
+ }
198
+
199
+ private async onLazyLoad(event: Event, node: TreeNode) {
200
+ const resource = node.data as Resource;
201
+ if (resource instanceof Directory && node.children.length === 0) {
202
+ await this.loadNodeChildren(node, resource);
203
+ }
204
+ }
205
+
206
+ private async loadNodeChildren(node: TreeNode, resource: Directory) {
207
+ if (this.loadingNodes.has(node)) {
208
+ return;
209
+ }
210
+
211
+ this.loadingNodes.add(node);
212
+ try {
213
+ for (const childResource of await resource.listChildren(false)) {
214
+ if (HIDE_DOT_RESOURCE && childResource.getName().startsWith(".")) {
215
+ continue
216
+ }
217
+ const child = await this.resourceToTreeNode(childResource, false);
218
+ node.children.push(child);
219
+ }
220
+ node.children.sort(treeNodeComparator);
221
+ this.requestUpdate();
222
+ } catch (error) {
223
+ console.error('Failed to load directory children:', error);
224
+ } finally {
225
+ this.loadingNodes.delete(node);
226
+ }
227
+ }
228
+
229
+ async onFileDoubleClicked(event: Event) {
230
+ // @ts-ignore
231
+ const node: TreeNode = event.currentTarget.model
232
+ const filePath = (node.data as Resource).getWorkspacePath()
233
+ this.executeCommand("open_editor", {
234
+ "path": filePath
235
+ })
236
+ }
237
+
238
+ onSelectionChanged(event: Event) {
239
+ // @ts-ignore
240
+ const selection = event.detail.selection
241
+ if (selection && selection.length > 0) {
242
+ const node: TreeNode = selection[0].model
243
+ activeSelectionSignal.set(node.data)
244
+ } else {
245
+ activeSelectionSignal.set(undefined)
246
+ }
247
+ }
248
+
249
+ private currentDropTarget?: HTMLElement;
250
+
251
+ private setupDragAndDrop() {
252
+ const treeElement = this.treeRef.value;
253
+ if (!treeElement) return;
254
+
255
+ const dragOverHandler = (e: DragEvent) => {
256
+ if (!e.dataTransfer?.types.includes('Files')) return;
257
+
258
+ e.preventDefault();
259
+ e.dataTransfer.dropEffect = 'copy';
260
+
261
+ treeElement.classList.add('drag-over');
262
+
263
+ const target = e.target as HTMLElement;
264
+ const treeItem = target.closest('wa-tree-item') as HTMLElement;
265
+
266
+ if (treeItem && treeItem !== this.currentDropTarget) {
267
+ this.currentDropTarget?.classList.remove('drop-target');
268
+ this.currentDropTarget = treeItem;
269
+ treeItem.classList.add('drop-target');
270
+ }
271
+ };
272
+
273
+ const dragEnterHandler = (e: DragEvent) => {
274
+ if (!e.dataTransfer?.types.includes('Files')) return;
275
+
276
+ e.preventDefault();
277
+ treeElement.classList.add('drag-over');
278
+ };
279
+
280
+ const dragLeaveHandler = (e: DragEvent) => {
281
+ const rect = treeElement.getBoundingClientRect();
282
+ const x = e.clientX;
283
+ const y = e.clientY;
284
+
285
+ if (x <= rect.left || x >= rect.right || y <= rect.top || y >= rect.bottom) {
286
+ treeElement.classList.remove('drag-over');
287
+ this.currentDropTarget?.classList.remove('drop-target');
288
+ this.currentDropTarget = undefined;
289
+ }
290
+ };
291
+
292
+ const dropHandler = async (e: DragEvent) => {
293
+ e.preventDefault();
294
+ treeElement.classList.remove('drag-over');
295
+ this.currentDropTarget?.classList.remove('drop-target');
296
+ this.currentDropTarget = undefined;
297
+
298
+ if (!e.dataTransfer || !this.workspaceDir) return;
299
+
300
+ const files = Array.from(e.dataTransfer.files);
301
+ if (files.length === 0) return;
302
+
303
+ const targetDir = await this.getDropTarget(e);
304
+ await this.handleFilesDrop(files, targetDir);
305
+ };
306
+
307
+ treeElement.removeEventListener('dragover', dragOverHandler as any);
308
+ treeElement.removeEventListener('dragenter', dragEnterHandler as any);
309
+ treeElement.removeEventListener('dragleave', dragLeaveHandler as any);
310
+ treeElement.removeEventListener('drop', dropHandler as any);
311
+
312
+ treeElement.addEventListener('dragover', dragOverHandler);
313
+ treeElement.addEventListener('dragenter', dragEnterHandler);
314
+ treeElement.addEventListener('dragleave', dragLeaveHandler);
315
+ treeElement.addEventListener('drop', dropHandler);
316
+ }
317
+
318
+ private async getDropTarget(e: DragEvent): Promise<Directory> {
319
+ const target = e.target as HTMLElement;
320
+ const treeItem = target.closest('wa-tree-item');
321
+
322
+ if (treeItem) {
323
+ const node: TreeNode = (treeItem as any).model;
324
+ const resource = node.data as Resource;
325
+
326
+ if (resource instanceof Directory) {
327
+ return resource;
328
+ }
329
+ const parent = resource.getParent();
330
+ if (parent) {
331
+ return parent;
332
+ }
333
+ }
334
+
335
+ return this.workspaceDir!;
336
+ }
337
+
338
+ private async handleFilesDrop(files: globalThis.File[], targetDir: Directory) {
339
+ const total = files.length;
340
+ let processed = 0;
341
+ let failed = 0;
342
+ let skipped = 0;
343
+
344
+ for (const file of files) {
345
+ try {
346
+ const targetPath = this.buildTargetPath(targetDir, file.name);
347
+
348
+ const existingFile = await this.workspaceDir!.getResource(targetPath);
349
+ if (existingFile) {
350
+ const overwrite = await confirmDialog(t('FILE_EXISTS_OVERWRITE', { fileName: file.name }));
351
+ if (!overwrite) {
352
+ skipped++;
353
+ continue;
354
+ }
355
+ }
356
+
357
+ const workspaceFile = await this.workspaceDir!.getResource(
358
+ targetPath,
359
+ { create: true }
360
+ ) as File;
361
+
362
+ await workspaceFile.saveContents(file);
363
+
364
+ processed++;
365
+ } catch (error) {
366
+ console.error(`Failed to upload ${file.name}:`, error);
367
+ failed++;
368
+ }
369
+ }
370
+
371
+ console.log(`Uploaded ${processed}/${total} files${skipped > 0 ? `, ${skipped} skipped` : ''}${failed > 0 ? `, ${failed} failed` : ''}`);
372
+
373
+ await this.loadWorkspace(this.workspaceDir);
374
+ }
375
+
376
+ private buildTargetPath(targetDir: Directory, fileName: string): string {
377
+ const dirPath = targetDir.getWorkspacePath();
378
+ return dirPath ? `${dirPath}/${fileName}` : fileName;
379
+ }
380
+
381
+ render() {
382
+ return html`
383
+ <div class="tree" ${ref(this.treeRef)} style="--drop-files-text: '${t('DROP_FILES_HERE')}'">
384
+ ${when(!this.workspaceDir, () => html`
385
+ <k-no-content message="${t('SELECT_WORKSPACE')}"></k-no-content>`, () => html`
386
+ `)}
387
+ <wa-tree @wa-selection-change=${this.nobubble(this.onSelectionChanged)}
388
+ style="--indent-guide-width: 1px;">
389
+ ${this.createTreeItems(this.root!, true)}
390
+ </wa-tree>
391
+ </div>
392
+ `
393
+ }
394
+
395
+ static styles = css`
396
+ :host {
397
+ }
398
+
399
+ .tree {
400
+ height: 100%;
401
+ position: relative;
402
+ transition: all 0.2s ease;
403
+ }
404
+
405
+ .tree.drag-over {
406
+ background-color: var(--wa-color-brand-fill-quiet);
407
+ outline: 2px dashed var(--wa-color-brand-border-normal);
408
+ outline-offset: -4px;
409
+ border-radius: var(--wa-border-radius-medium);
410
+ }
411
+
412
+ .tree.drag-over::before {
413
+ content: var(--drop-files-text);
414
+ position: absolute;
415
+ top: 50%;
416
+ left: 50%;
417
+ transform: translate(-50%, -50%);
418
+ background: var(--wa-color-brand-fill-loud);
419
+ color: var(--wa-color-brand-on-loud);
420
+ padding: var(--wa-spacing-large);
421
+ border-radius: var(--wa-border-radius-large);
422
+ font-weight: bold;
423
+ pointer-events: none;
424
+ z-index: 1000;
425
+ opacity: 0.3;
426
+ }
427
+
428
+ wa-tree-item.drop-target {
429
+ background-color: var(--wa-color-brand-fill-loud);
430
+ color: var(--wa-color-brand-on-loud);
431
+ border-radius: var(--wa-border-radius-small);
432
+ outline: 2px solid var(--wa-color-brand-border-loud);
433
+ outline-offset: -2px;
434
+ }
435
+ `;
436
+ }
437
+
438
+ declare global {
439
+ interface HTMLElementTagNameMap {
440
+ 'k-filebrowser': KFileBrowser
441
+ }
442
+ }
@@ -0,0 +1,166 @@
1
+ import { html, render, css } from "lit";
2
+ import { customElement } from "lit/decorators.js";
3
+ import { KElement } from "../parts/k-element";
4
+ import { currentLanguageSignal, languageContributionsSignal, SETTINGS_KEY_LANGUAGE } from "../core/i18n";
5
+ import { appSettings } from "../core/settingsservice";
6
+
7
+ function getDialogContainer(): HTMLElement {
8
+ let container = document.getElementById('global-dialog-container');
9
+ if (!container) {
10
+ container = document.createElement('div');
11
+ container.id = 'global-dialog-container';
12
+ document.body.appendChild(container);
13
+ }
14
+ return container;
15
+ }
16
+
17
+ const getLanguageName = (code: string): string => {
18
+ try {
19
+ return new Intl.DisplayNames([code], { type: 'language' }).of(code) || code.toUpperCase();
20
+ } catch {
21
+ return code.toUpperCase();
22
+ }
23
+ };
24
+
25
+ const getAvailableLanguages = (): string[] => {
26
+ const contributions = languageContributionsSignal.get();
27
+ const languages = new Set<string>();
28
+
29
+ for (const contribution of contributions) {
30
+ if (contribution.namespace) {
31
+ const contributionObj = contribution as any;
32
+ for (const key in contributionObj) {
33
+ if (key !== 'namespace' && key !== 'label' && key !== 'language' && key !== 'translations' && typeof contributionObj[key] === 'object') {
34
+ languages.add(key);
35
+ }
36
+ }
37
+ }
38
+ }
39
+
40
+ return Array.from(languages).sort();
41
+ };
42
+
43
+ export const showLanguageSelectorDialog = async (): Promise<void> => {
44
+ const availableLanguages = getAvailableLanguages();
45
+ const currentLanguage = currentLanguageSignal.get();
46
+
47
+ return new Promise((resolve) => {
48
+ const container = getDialogContainer();
49
+
50
+ let isResolved = false;
51
+
52
+ const handleClose = () => {
53
+ const dialog = container.querySelector('wa-dialog') as any;
54
+ if (dialog && !isResolved) {
55
+ dialog.open = false;
56
+ }
57
+ };
58
+
59
+ const handleAfterHide = () => {
60
+ if (isResolved) return;
61
+ isResolved = true;
62
+ render(html``, container);
63
+ resolve();
64
+ };
65
+
66
+ const selectLanguage = async (language: string) => {
67
+ await appSettings.set(SETTINGS_KEY_LANGUAGE, language);
68
+ handleClose();
69
+ };
70
+
71
+ const template = html`
72
+ <wa-dialog
73
+ label="Select Language"
74
+ open
75
+ light-dismiss
76
+ @wa-request-close=${handleClose}
77
+ @wa-after-hide=${handleAfterHide}>
78
+ <style>
79
+ .language-list {
80
+ display: flex;
81
+ flex-direction: column;
82
+ gap: 0.5rem;
83
+ padding: 1rem;
84
+ min-width: 300px;
85
+ max-height: 400px;
86
+ overflow-y: auto;
87
+ }
88
+
89
+ .language-item {
90
+ display: flex;
91
+ align-items: center;
92
+ padding: 0.75rem;
93
+ border-radius: var(--wa-border-radius-small);
94
+ cursor: pointer;
95
+ transition: background-color 0.2s;
96
+ }
97
+
98
+ .language-item:hover {
99
+ background-color: var(--wa-color-neutral-fill-quiet);
100
+ }
101
+
102
+ .language-item.active {
103
+ background-color: var(--wa-color-brand-fill-quiet);
104
+ font-weight: 600;
105
+ }
106
+
107
+ .language-code {
108
+ font-family: monospace;
109
+ margin-right: 0.75rem;
110
+ min-width: 3rem;
111
+ color: var(--wa-color-neutral-600);
112
+ }
113
+
114
+ .language-name {
115
+ flex: 1;
116
+ }
117
+ </style>
118
+
119
+ <div class="language-list">
120
+ ${availableLanguages.map(lang => html`
121
+ <div
122
+ class="language-item ${lang === currentLanguage ? 'active' : ''}"
123
+ @click=${() => selectLanguage(lang)}>
124
+ <span class="language-code">${lang.toUpperCase()}</span>
125
+ <span class="language-name">${getLanguageName(lang)}</span>
126
+ </div>
127
+ `)}
128
+ </div>
129
+ </wa-dialog>
130
+ `;
131
+
132
+ render(template, container);
133
+ });
134
+ };
135
+
136
+ @customElement('k-language-selector')
137
+ export class KLanguageSelector extends KElement {
138
+ static styles = css`
139
+ :host {
140
+ display: inline-block;
141
+ }
142
+ `;
143
+
144
+ protected render() {
145
+ const currentLanguage = currentLanguageSignal.get();
146
+ const languageName = getLanguageName(currentLanguage);
147
+ const buttonLabel = `${currentLanguage.toUpperCase()} ${languageName}`;
148
+
149
+ return html`
150
+ <wa-button
151
+ appearance="plain"
152
+ size="small"
153
+ title="Current language: ${languageName}"
154
+ @click=${() => showLanguageSelectorDialog()}>
155
+ ${buttonLabel}
156
+ </wa-button>
157
+ `;
158
+ }
159
+ }
160
+
161
+ declare global {
162
+ interface HTMLElementTagNameMap {
163
+ 'k-language-selector': KLanguageSelector;
164
+ }
165
+ }
166
+