groove-dev 0.16.0 → 0.16.2

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 (211) hide show
  1. package/node_modules/@groove-dev/daemon/src/api.js +122 -1
  2. package/node_modules/@groove-dev/daemon/src/index.js +25 -7
  3. package/node_modules/@groove-dev/daemon/src/mimetypes.js +43 -0
  4. package/node_modules/@groove-dev/daemon/src/terminal-pty.js +129 -0
  5. package/node_modules/@groove-dev/gui/dist/assets/{index-Gfb8Zxy9.css → index-BhjOFLBc.css} +32 -1
  6. package/node_modules/@groove-dev/gui/dist/assets/index-DeXW9EFU.js +153 -0
  7. package/node_modules/@groove-dev/gui/dist/index.html +2 -2
  8. package/node_modules/@groove-dev/gui/package.json +3 -0
  9. package/node_modules/@groove-dev/gui/src/components/EditorTabs.jsx +1 -1
  10. package/node_modules/@groove-dev/gui/src/components/FileTree.jsx +308 -23
  11. package/node_modules/@groove-dev/gui/src/components/MediaViewer.jsx +104 -0
  12. package/node_modules/@groove-dev/gui/src/components/Terminal.jsx +154 -0
  13. package/node_modules/@groove-dev/gui/src/stores/groove.js +131 -2
  14. package/node_modules/@groove-dev/gui/src/views/FileEditor.jsx +104 -36
  15. package/node_modules/@xterm/addon-fit/LICENSE +19 -0
  16. package/node_modules/@xterm/addon-fit/README.md +24 -0
  17. package/node_modules/@xterm/addon-fit/lib/addon-fit.js +2 -0
  18. package/node_modules/@xterm/addon-fit/lib/addon-fit.js.map +1 -0
  19. package/node_modules/@xterm/addon-fit/lib/addon-fit.mjs +18 -0
  20. package/node_modules/@xterm/addon-fit/lib/addon-fit.mjs.map +7 -0
  21. package/node_modules/@xterm/addon-fit/package.json +26 -0
  22. package/node_modules/@xterm/addon-fit/src/FitAddon.ts +92 -0
  23. package/node_modules/@xterm/addon-fit/typings/addon-fit.d.ts +55 -0
  24. package/node_modules/@xterm/addon-web-links/LICENSE +19 -0
  25. package/node_modules/@xterm/addon-web-links/README.md +21 -0
  26. package/node_modules/@xterm/addon-web-links/lib/addon-web-links.js +2 -0
  27. package/node_modules/@xterm/addon-web-links/lib/addon-web-links.js.map +1 -0
  28. package/node_modules/@xterm/addon-web-links/lib/addon-web-links.mjs +18 -0
  29. package/node_modules/@xterm/addon-web-links/lib/addon-web-links.mjs.map +7 -0
  30. package/node_modules/@xterm/addon-web-links/package.json +26 -0
  31. package/node_modules/@xterm/addon-web-links/src/WebLinkProvider.ts +199 -0
  32. package/node_modules/@xterm/addon-web-links/src/WebLinksAddon.ts +58 -0
  33. package/node_modules/@xterm/addon-web-links/typings/addon-web-links.d.ts +57 -0
  34. package/node_modules/@xterm/xterm/LICENSE +21 -0
  35. package/node_modules/@xterm/xterm/README.md +243 -0
  36. package/node_modules/@xterm/xterm/css/xterm.css +285 -0
  37. package/node_modules/@xterm/xterm/lib/xterm.js +2 -0
  38. package/node_modules/@xterm/xterm/lib/xterm.js.map +1 -0
  39. package/node_modules/@xterm/xterm/lib/xterm.mjs +53 -0
  40. package/node_modules/@xterm/xterm/lib/xterm.mjs.map +7 -0
  41. package/node_modules/@xterm/xterm/package.json +111 -0
  42. package/node_modules/@xterm/xterm/src/browser/AccessibilityManager.ts +435 -0
  43. package/node_modules/@xterm/xterm/src/browser/Clipboard.ts +93 -0
  44. package/node_modules/@xterm/xterm/src/browser/ColorContrastCache.ts +34 -0
  45. package/node_modules/@xterm/xterm/src/browser/CoreBrowserTerminal.ts +1339 -0
  46. package/node_modules/@xterm/xterm/src/browser/Linkifier.ts +403 -0
  47. package/node_modules/@xterm/xterm/src/browser/LocalizableStrings.ts +23 -0
  48. package/node_modules/@xterm/xterm/src/browser/OscLinkProvider.ts +129 -0
  49. package/node_modules/@xterm/xterm/src/browser/RenderDebouncer.ts +84 -0
  50. package/node_modules/@xterm/xterm/src/browser/TimeBasedDebouncer.ts +86 -0
  51. package/node_modules/@xterm/xterm/src/browser/Types.ts +226 -0
  52. package/node_modules/@xterm/xterm/src/browser/Viewport.ts +192 -0
  53. package/node_modules/@xterm/xterm/src/browser/decorations/BufferDecorationRenderer.ts +139 -0
  54. package/node_modules/@xterm/xterm/src/browser/decorations/ColorZoneStore.ts +117 -0
  55. package/node_modules/@xterm/xterm/src/browser/decorations/OverviewRulerRenderer.ts +214 -0
  56. package/node_modules/@xterm/xterm/src/browser/input/CompositionHelper.ts +248 -0
  57. package/node_modules/@xterm/xterm/src/browser/input/Mouse.ts +54 -0
  58. package/node_modules/@xterm/xterm/src/browser/input/MoveToCell.ts +251 -0
  59. package/node_modules/@xterm/xterm/src/browser/public/Terminal.ts +275 -0
  60. package/node_modules/@xterm/xterm/src/browser/renderer/dom/DomRenderer.ts +542 -0
  61. package/node_modules/@xterm/xterm/src/browser/renderer/dom/DomRendererRowFactory.ts +546 -0
  62. package/node_modules/@xterm/xterm/src/browser/renderer/dom/WidthCache.ts +167 -0
  63. package/node_modules/@xterm/xterm/src/browser/renderer/shared/Constants.ts +6 -0
  64. package/node_modules/@xterm/xterm/src/browser/renderer/shared/RendererUtils.ts +95 -0
  65. package/node_modules/@xterm/xterm/src/browser/renderer/shared/SelectionRenderModel.ts +93 -0
  66. package/node_modules/@xterm/xterm/src/browser/renderer/shared/Types.ts +84 -0
  67. package/node_modules/@xterm/xterm/src/browser/selection/SelectionModel.ts +144 -0
  68. package/node_modules/@xterm/xterm/src/browser/selection/Types.ts +15 -0
  69. package/node_modules/@xterm/xterm/src/browser/services/CharSizeService.ts +127 -0
  70. package/node_modules/@xterm/xterm/src/browser/services/CharacterJoinerService.ts +339 -0
  71. package/node_modules/@xterm/xterm/src/browser/services/CoreBrowserService.ts +137 -0
  72. package/node_modules/@xterm/xterm/src/browser/services/LinkProviderService.ts +28 -0
  73. package/node_modules/@xterm/xterm/src/browser/services/MouseService.ts +46 -0
  74. package/node_modules/@xterm/xterm/src/browser/services/RenderService.ts +376 -0
  75. package/node_modules/@xterm/xterm/src/browser/services/SelectionService.ts +1039 -0
  76. package/node_modules/@xterm/xterm/src/browser/services/Services.ts +158 -0
  77. package/node_modules/@xterm/xterm/src/browser/services/ThemeService.ts +198 -0
  78. package/node_modules/@xterm/xterm/src/browser/shared/Constants.ts +8 -0
  79. package/node_modules/@xterm/xterm/src/common/CircularList.ts +241 -0
  80. package/node_modules/@xterm/xterm/src/common/Clone.ts +23 -0
  81. package/node_modules/@xterm/xterm/src/common/Color.ts +376 -0
  82. package/node_modules/@xterm/xterm/src/common/CoreTerminal.ts +283 -0
  83. package/node_modules/@xterm/xterm/src/common/InputHandler.ts +3495 -0
  84. package/node_modules/@xterm/xterm/src/common/MultiKeyMap.ts +42 -0
  85. package/node_modules/@xterm/xterm/src/common/Platform.ts +44 -0
  86. package/node_modules/@xterm/xterm/src/common/SortedList.ts +194 -0
  87. package/node_modules/@xterm/xterm/src/common/TaskQueue.ts +166 -0
  88. package/node_modules/@xterm/xterm/src/common/TypedArrayUtils.ts +17 -0
  89. package/node_modules/@xterm/xterm/src/common/Types.ts +552 -0
  90. package/node_modules/@xterm/xterm/src/common/WindowsMode.ts +27 -0
  91. package/node_modules/@xterm/xterm/src/common/buffer/AttributeData.ts +211 -0
  92. package/node_modules/@xterm/xterm/src/common/buffer/Buffer.ts +662 -0
  93. package/node_modules/@xterm/xterm/src/common/buffer/BufferLine.ts +551 -0
  94. package/node_modules/@xterm/xterm/src/common/buffer/BufferRange.ts +13 -0
  95. package/node_modules/@xterm/xterm/src/common/buffer/BufferReflow.ts +226 -0
  96. package/node_modules/@xterm/xterm/src/common/buffer/BufferSet.ts +134 -0
  97. package/node_modules/@xterm/xterm/src/common/buffer/CellData.ts +94 -0
  98. package/node_modules/@xterm/xterm/src/common/buffer/Constants.ts +157 -0
  99. package/node_modules/@xterm/xterm/src/common/buffer/Marker.ts +43 -0
  100. package/node_modules/@xterm/xterm/src/common/buffer/Types.ts +52 -0
  101. package/node_modules/@xterm/xterm/src/common/data/Charsets.ts +256 -0
  102. package/node_modules/@xterm/xterm/src/common/data/EscapeSequences.ts +153 -0
  103. package/node_modules/@xterm/xterm/src/common/input/Keyboard.ts +373 -0
  104. package/node_modules/@xterm/xterm/src/common/input/TextDecoder.ts +346 -0
  105. package/node_modules/@xterm/xterm/src/common/input/UnicodeV6.ts +145 -0
  106. package/node_modules/@xterm/xterm/src/common/input/WriteBuffer.ts +247 -0
  107. package/node_modules/@xterm/xterm/src/common/input/XParseColor.ts +80 -0
  108. package/node_modules/@xterm/xterm/src/common/parser/Constants.ts +58 -0
  109. package/node_modules/@xterm/xterm/src/common/parser/DcsParser.ts +192 -0
  110. package/node_modules/@xterm/xterm/src/common/parser/EscapeSequenceParser.ts +792 -0
  111. package/node_modules/@xterm/xterm/src/common/parser/OscParser.ts +238 -0
  112. package/node_modules/@xterm/xterm/src/common/parser/Params.ts +229 -0
  113. package/node_modules/@xterm/xterm/src/common/parser/Types.ts +275 -0
  114. package/node_modules/@xterm/xterm/src/common/public/AddonManager.ts +53 -0
  115. package/node_modules/@xterm/xterm/src/common/public/BufferApiView.ts +35 -0
  116. package/node_modules/@xterm/xterm/src/common/public/BufferLineApiView.ts +29 -0
  117. package/node_modules/@xterm/xterm/src/common/public/BufferNamespaceApi.ts +36 -0
  118. package/node_modules/@xterm/xterm/src/common/public/ParserApi.ts +37 -0
  119. package/node_modules/@xterm/xterm/src/common/public/UnicodeApi.ts +27 -0
  120. package/node_modules/@xterm/xterm/src/common/services/BufferService.ts +154 -0
  121. package/node_modules/@xterm/xterm/src/common/services/CharsetService.ts +34 -0
  122. package/node_modules/@xterm/xterm/src/common/services/CoreMouseService.ts +365 -0
  123. package/node_modules/@xterm/xterm/src/common/services/CoreService.ts +92 -0
  124. package/node_modules/@xterm/xterm/src/common/services/DecorationService.ts +140 -0
  125. package/node_modules/@xterm/xterm/src/common/services/InstantiationService.ts +85 -0
  126. package/node_modules/@xterm/xterm/src/common/services/LogService.ts +124 -0
  127. package/node_modules/@xterm/xterm/src/common/services/OptionsService.ts +212 -0
  128. package/node_modules/@xterm/xterm/src/common/services/OscLinkService.ts +115 -0
  129. package/node_modules/@xterm/xterm/src/common/services/ServiceRegistry.ts +49 -0
  130. package/node_modules/@xterm/xterm/src/common/services/Services.ts +396 -0
  131. package/node_modules/@xterm/xterm/src/common/services/UnicodeService.ts +111 -0
  132. package/node_modules/@xterm/xterm/src/vs/base/browser/browser.ts +141 -0
  133. package/node_modules/@xterm/xterm/src/vs/base/browser/canIUse.ts +49 -0
  134. package/node_modules/@xterm/xterm/src/vs/base/browser/dom.ts +2369 -0
  135. package/node_modules/@xterm/xterm/src/vs/base/browser/fastDomNode.ts +316 -0
  136. package/node_modules/@xterm/xterm/src/vs/base/browser/globalPointerMoveMonitor.ts +112 -0
  137. package/node_modules/@xterm/xterm/src/vs/base/browser/iframe.ts +135 -0
  138. package/node_modules/@xterm/xterm/src/vs/base/browser/keyboardEvent.ts +213 -0
  139. package/node_modules/@xterm/xterm/src/vs/base/browser/mouseEvent.ts +229 -0
  140. package/node_modules/@xterm/xterm/src/vs/base/browser/touch.ts +372 -0
  141. package/node_modules/@xterm/xterm/src/vs/base/browser/ui/scrollbar/abstractScrollbar.ts +303 -0
  142. package/node_modules/@xterm/xterm/src/vs/base/browser/ui/scrollbar/horizontalScrollbar.ts +114 -0
  143. package/node_modules/@xterm/xterm/src/vs/base/browser/ui/scrollbar/scrollableElement.ts +720 -0
  144. package/node_modules/@xterm/xterm/src/vs/base/browser/ui/scrollbar/scrollableElementOptions.ts +165 -0
  145. package/node_modules/@xterm/xterm/src/vs/base/browser/ui/scrollbar/scrollbarArrow.ts +114 -0
  146. package/node_modules/@xterm/xterm/src/vs/base/browser/ui/scrollbar/scrollbarState.ts +243 -0
  147. package/node_modules/@xterm/xterm/src/vs/base/browser/ui/scrollbar/scrollbarVisibilityController.ts +118 -0
  148. package/node_modules/@xterm/xterm/src/vs/base/browser/ui/scrollbar/verticalScrollbar.ts +116 -0
  149. package/node_modules/@xterm/xterm/src/vs/base/browser/ui/widget.ts +57 -0
  150. package/node_modules/@xterm/xterm/src/vs/base/browser/window.ts +14 -0
  151. package/node_modules/@xterm/xterm/src/vs/base/common/arrays.ts +887 -0
  152. package/node_modules/@xterm/xterm/src/vs/base/common/arraysFind.ts +202 -0
  153. package/node_modules/@xterm/xterm/src/vs/base/common/assert.ts +71 -0
  154. package/node_modules/@xterm/xterm/src/vs/base/common/async.ts +1992 -0
  155. package/node_modules/@xterm/xterm/src/vs/base/common/cancellation.ts +148 -0
  156. package/node_modules/@xterm/xterm/src/vs/base/common/charCode.ts +450 -0
  157. package/node_modules/@xterm/xterm/src/vs/base/common/collections.ts +140 -0
  158. package/node_modules/@xterm/xterm/src/vs/base/common/decorators.ts +130 -0
  159. package/node_modules/@xterm/xterm/src/vs/base/common/equals.ts +146 -0
  160. package/node_modules/@xterm/xterm/src/vs/base/common/errors.ts +303 -0
  161. package/node_modules/@xterm/xterm/src/vs/base/common/event.ts +1778 -0
  162. package/node_modules/@xterm/xterm/src/vs/base/common/functional.ts +32 -0
  163. package/node_modules/@xterm/xterm/src/vs/base/common/hash.ts +316 -0
  164. package/node_modules/@xterm/xterm/src/vs/base/common/iterator.ts +159 -0
  165. package/node_modules/@xterm/xterm/src/vs/base/common/keyCodes.ts +526 -0
  166. package/node_modules/@xterm/xterm/src/vs/base/common/keybindings.ts +284 -0
  167. package/node_modules/@xterm/xterm/src/vs/base/common/lazy.ts +47 -0
  168. package/node_modules/@xterm/xterm/src/vs/base/common/lifecycle.ts +801 -0
  169. package/node_modules/@xterm/xterm/src/vs/base/common/linkedList.ts +142 -0
  170. package/node_modules/@xterm/xterm/src/vs/base/common/map.ts +202 -0
  171. package/node_modules/@xterm/xterm/src/vs/base/common/numbers.ts +98 -0
  172. package/node_modules/@xterm/xterm/src/vs/base/common/observable.ts +76 -0
  173. package/node_modules/@xterm/xterm/src/vs/base/common/observableInternal/api.ts +31 -0
  174. package/node_modules/@xterm/xterm/src/vs/base/common/observableInternal/autorun.ts +281 -0
  175. package/node_modules/@xterm/xterm/src/vs/base/common/observableInternal/base.ts +489 -0
  176. package/node_modules/@xterm/xterm/src/vs/base/common/observableInternal/debugName.ts +145 -0
  177. package/node_modules/@xterm/xterm/src/vs/base/common/observableInternal/derived.ts +428 -0
  178. package/node_modules/@xterm/xterm/src/vs/base/common/observableInternal/lazyObservableValue.ts +146 -0
  179. package/node_modules/@xterm/xterm/src/vs/base/common/observableInternal/logging.ts +328 -0
  180. package/node_modules/@xterm/xterm/src/vs/base/common/observableInternal/promise.ts +209 -0
  181. package/node_modules/@xterm/xterm/src/vs/base/common/observableInternal/utils.ts +610 -0
  182. package/node_modules/@xterm/xterm/src/vs/base/common/platform.ts +281 -0
  183. package/node_modules/@xterm/xterm/src/vs/base/common/scrollable.ts +522 -0
  184. package/node_modules/@xterm/xterm/src/vs/base/common/sequence.ts +34 -0
  185. package/node_modules/@xterm/xterm/src/vs/base/common/stopwatch.ts +43 -0
  186. package/node_modules/@xterm/xterm/src/vs/base/common/strings.ts +557 -0
  187. package/node_modules/@xterm/xterm/src/vs/base/common/symbols.ts +9 -0
  188. package/node_modules/@xterm/xterm/src/vs/base/common/uint.ts +59 -0
  189. package/node_modules/@xterm/xterm/src/vs/patches/nls.ts +90 -0
  190. package/node_modules/@xterm/xterm/src/vs/typings/base-common.d.ts +20 -0
  191. package/node_modules/@xterm/xterm/src/vs/typings/require.d.ts +42 -0
  192. package/node_modules/@xterm/xterm/src/vs/typings/vscode-globals-nls.d.ts +36 -0
  193. package/node_modules/@xterm/xterm/src/vs/typings/vscode-globals-product.d.ts +33 -0
  194. package/node_modules/@xterm/xterm/typings/xterm.d.ts +1957 -0
  195. package/package.json +1 -1
  196. package/packages/daemon/src/api.js +122 -1
  197. package/packages/daemon/src/index.js +25 -7
  198. package/packages/daemon/src/mimetypes.js +43 -0
  199. package/packages/daemon/src/terminal-pty.js +129 -0
  200. package/packages/gui/dist/assets/{index-Gfb8Zxy9.css → index-BhjOFLBc.css} +32 -1
  201. package/packages/gui/dist/assets/index-DeXW9EFU.js +153 -0
  202. package/packages/gui/dist/index.html +2 -2
  203. package/packages/gui/package.json +3 -0
  204. package/packages/gui/src/components/EditorTabs.jsx +1 -1
  205. package/packages/gui/src/components/FileTree.jsx +308 -23
  206. package/packages/gui/src/components/MediaViewer.jsx +104 -0
  207. package/packages/gui/src/components/Terminal.jsx +154 -0
  208. package/packages/gui/src/stores/groove.js +131 -2
  209. package/packages/gui/src/views/FileEditor.jsx +104 -36
  210. package/node_modules/@groove-dev/gui/dist/assets/index-Dxg9hdf3.js +0 -103
  211. package/packages/gui/dist/assets/index-Dxg9hdf3.js +0 -103
@@ -4,8 +4,8 @@
4
4
  <meta charset="UTF-8" />
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
6
  <title>GROOVE</title>
7
- <script type="module" crossorigin src="/assets/index-Dxg9hdf3.js"></script>
8
- <link rel="stylesheet" crossorigin href="/assets/index-Gfb8Zxy9.css">
7
+ <script type="module" crossorigin src="/assets/index-DeXW9EFU.js"></script>
8
+ <link rel="stylesheet" crossorigin href="/assets/index-BhjOFLBc.css">
9
9
  </head>
10
10
  <body>
11
11
  <div id="root"></div>
@@ -23,6 +23,9 @@
23
23
  "@codemirror/state": "^6.6.0",
24
24
  "@codemirror/theme-one-dark": "^6.1.3",
25
25
  "@codemirror/view": "^6.41.0",
26
+ "@xterm/addon-fit": "^0.11.0",
27
+ "@xterm/addon-web-links": "^0.12.0",
28
+ "@xterm/xterm": "^6.0.0",
26
29
  "@xyflow/react": "^12.0.0",
27
30
  "react": "^19.0.0",
28
31
  "react-dom": "^19.0.0",
@@ -29,7 +29,7 @@ export default function EditorTabs() {
29
29
  style={{
30
30
  ...styles.tab,
31
31
  color: isActive ? 'var(--text-bright)' : 'var(--text-primary)',
32
- borderBottom: isActive ? '2px solid var(--accent)' : '2px solid transparent',
32
+ borderBottom: isActive ? '1px solid var(--accent)' : '1px solid transparent',
33
33
  background: isActive ? 'var(--bg-active)' : 'transparent',
34
34
  }}
35
35
  >
@@ -1,7 +1,7 @@
1
1
  // GROOVE GUI — File Tree (expandable directory browser)
2
2
  // FSL-1.1-Apache-2.0 — see LICENSE
3
3
 
4
- import React, { useState, useEffect, useCallback } from 'react';
4
+ import React, { useState, useEffect, useCallback, useRef } from 'react';
5
5
  import { useGrooveStore } from '../stores/groove';
6
6
 
7
7
  const FILE_COLORS = {
@@ -15,14 +15,14 @@ const FILE_COLORS = {
15
15
  rust: '#e06c75',
16
16
  go: '#33afbc',
17
17
  shell: '#4ae168',
18
- yaml: '#e06c75',
19
18
  text: '#abb2bf',
20
19
  };
21
20
 
22
- function TreeNode({ entry, depth, activeFile, expandedDirs, onToggleDir, onFileClick }) {
21
+ function TreeNode({ entry, depth, activeFile, expandedDirs, onToggleDir, onFileClick, onContextMenu, renamingPath, renameValue, onRenameChange, onRenameSubmit, onRenameCancel }) {
23
22
  const isDir = entry.type === 'dir';
24
23
  const isExpanded = expandedDirs.has(entry.path);
25
24
  const isActive = !isDir && entry.path === activeFile;
25
+ const isRenaming = renamingPath === entry.path;
26
26
  const treeCache = useGrooveStore((s) => s.editorTreeCache);
27
27
  const children = treeCache[entry.path] || [];
28
28
 
@@ -30,6 +30,7 @@ function TreeNode({ entry, depth, activeFile, expandedDirs, onToggleDir, onFileC
30
30
  <>
31
31
  <div
32
32
  onClick={() => isDir ? onToggleDir(entry.path, entry.hasChildren) : onFileClick(entry.path)}
33
+ onContextMenu={(e) => onContextMenu(e, entry)}
33
34
  style={{
34
35
  ...styles.row,
35
36
  paddingLeft: 12 + depth * 16,
@@ -42,14 +43,29 @@ function TreeNode({ entry, depth, activeFile, expandedDirs, onToggleDir, onFileC
42
43
  ) : (
43
44
  <span style={{ ...styles.fileDot, background: FILE_COLORS[entry.language] || FILE_COLORS.text }} />
44
45
  )}
45
- <span style={{
46
- ...styles.name,
47
- color: isDir ? 'var(--text-primary)' : (isActive ? 'var(--text-bright)' : 'var(--text-primary)'),
48
- fontWeight: isDir ? 600 : 400,
49
- }}>
50
- {entry.name}
51
- </span>
52
- {!isDir && entry.size > 0 && (
46
+ {isRenaming ? (
47
+ <input
48
+ autoFocus
49
+ value={renameValue}
50
+ onChange={(e) => onRenameChange(e.target.value)}
51
+ onKeyDown={(e) => {
52
+ if (e.key === 'Enter') onRenameSubmit();
53
+ if (e.key === 'Escape') onRenameCancel();
54
+ }}
55
+ onBlur={onRenameCancel}
56
+ onClick={(e) => e.stopPropagation()}
57
+ style={styles.renameInput}
58
+ />
59
+ ) : (
60
+ <span style={{
61
+ ...styles.name,
62
+ color: isDir ? 'var(--text-primary)' : (isActive ? 'var(--text-bright)' : 'var(--text-primary)'),
63
+ fontWeight: isDir ? 600 : 400,
64
+ }}>
65
+ {entry.name}
66
+ </span>
67
+ )}
68
+ {!isDir && !isRenaming && entry.size > 0 && (
53
69
  <span style={styles.size}>{formatSize(entry.size)}</span>
54
70
  )}
55
71
  </div>
@@ -62,6 +78,12 @@ function TreeNode({ entry, depth, activeFile, expandedDirs, onToggleDir, onFileC
62
78
  expandedDirs={expandedDirs}
63
79
  onToggleDir={onToggleDir}
64
80
  onFileClick={onFileClick}
81
+ onContextMenu={onContextMenu}
82
+ renamingPath={renamingPath}
83
+ renameValue={renameValue}
84
+ onRenameChange={onRenameChange}
85
+ onRenameSubmit={onRenameSubmit}
86
+ onRenameCancel={onRenameCancel}
65
87
  />
66
88
  ))}
67
89
  </>
@@ -74,15 +96,97 @@ function formatSize(bytes) {
74
96
  return `${(bytes / (1024 * 1024)).toFixed(1)}M`;
75
97
  }
76
98
 
99
+ // --- Context Menu ---
100
+ function ContextMenu({ x, y, entry, onClose, onAction }) {
101
+ const ref = useRef(null);
102
+
103
+ useEffect(() => {
104
+ const handle = (e) => {
105
+ if (ref.current && !ref.current.contains(e.target)) onClose();
106
+ };
107
+ document.addEventListener('mousedown', handle);
108
+ return () => document.removeEventListener('mousedown', handle);
109
+ }, [onClose]);
110
+
111
+ const isDir = entry?.type === 'dir';
112
+
113
+ const items = [
114
+ ...(isDir ? [
115
+ { label: 'New File Here', action: 'newFileIn' },
116
+ { label: 'New Folder Here', action: 'newDirIn' },
117
+ { sep: true },
118
+ ] : []),
119
+ { label: 'Rename', action: 'rename' },
120
+ { label: 'Delete', action: 'delete', danger: true },
121
+ ];
122
+
123
+ return (
124
+ <div ref={ref} style={{ ...styles.contextMenu, left: x, top: y }}>
125
+ {items.map((item, i) =>
126
+ item.sep ? (
127
+ <div key={i} style={styles.contextSep} />
128
+ ) : (
129
+ <div
130
+ key={item.action}
131
+ onClick={() => { onAction(item.action, entry); onClose(); }}
132
+ style={{
133
+ ...styles.contextItem,
134
+ color: item.danger ? 'var(--red)' : 'var(--text-primary)',
135
+ }}
136
+ >
137
+ {item.label}
138
+ </div>
139
+ )
140
+ )}
141
+ </div>
142
+ );
143
+ }
144
+
145
+ // --- Inline New-Item Input ---
146
+ function InlineInput({ placeholder, onSubmit, onCancel, depth }) {
147
+ const [value, setValue] = useState('');
148
+ return (
149
+ <div style={{ ...styles.row, paddingLeft: 12 + (depth || 0) * 16 }}>
150
+ <input
151
+ autoFocus
152
+ placeholder={placeholder}
153
+ value={value}
154
+ onChange={(e) => setValue(e.target.value)}
155
+ onKeyDown={(e) => {
156
+ if (e.key === 'Enter' && value.trim()) onSubmit(value.trim());
157
+ if (e.key === 'Escape') onCancel();
158
+ }}
159
+ onBlur={onCancel}
160
+ style={styles.renameInput}
161
+ />
162
+ </div>
163
+ );
164
+ }
165
+
166
+ // --- Main FileTree ---
77
167
  export default function FileTree() {
78
168
  const treeCache = useGrooveStore((s) => s.editorTreeCache);
79
169
  const activeFile = useGrooveStore((s) => s.editorActiveFile);
80
170
  const fetchTreeDir = useGrooveStore((s) => s.fetchTreeDir);
81
171
  const openFile = useGrooveStore((s) => s.openFile);
172
+ const createFile = useGrooveStore((s) => s.createFile);
173
+ const createDir = useGrooveStore((s) => s.createDir);
174
+ const deleteFile = useGrooveStore((s) => s.deleteFile);
175
+ const renameFile = useGrooveStore((s) => s.renameFile);
82
176
 
83
177
  const [expandedDirs, setExpandedDirs] = useState(new Set(['']));
84
178
  const [filter, setFilter] = useState('');
85
179
 
180
+ // Context menu state
181
+ const [contextMenu, setContextMenu] = useState(null); // { x, y, entry }
182
+
183
+ // Inline creation state
184
+ const [creating, setCreating] = useState(null); // { type: 'file'|'dir', parentPath: '' }
185
+
186
+ // Rename state
187
+ const [renamingPath, setRenamingPath] = useState(null);
188
+ const [renameValue, setRenameValue] = useState('');
189
+
86
190
  // Fetch root on mount
87
191
  useEffect(() => {
88
192
  fetchTreeDir('');
@@ -103,15 +207,111 @@ export default function FileTree() {
103
207
  });
104
208
  }, [fetchTreeDir, treeCache]);
105
209
 
210
+ const onContextMenu = useCallback((e, entry) => {
211
+ e.preventDefault();
212
+ e.stopPropagation();
213
+ setContextMenu({ x: e.clientX, y: e.clientY, entry });
214
+ }, []);
215
+
216
+ const handleContextAction = useCallback(async (action, entry) => {
217
+ const parentDir = entry.path.includes('/') ? entry.path.split('/').slice(0, -1).join('/') : '';
218
+
219
+ switch (action) {
220
+ case 'newFileIn':
221
+ setCreating({ type: 'file', parentPath: entry.path });
222
+ // Ensure dir is expanded
223
+ setExpandedDirs((prev) => {
224
+ const next = new Set(prev);
225
+ next.add(entry.path);
226
+ if (!treeCache[entry.path]) fetchTreeDir(entry.path);
227
+ return next;
228
+ });
229
+ break;
230
+ case 'newDirIn':
231
+ setCreating({ type: 'dir', parentPath: entry.path });
232
+ setExpandedDirs((prev) => {
233
+ const next = new Set(prev);
234
+ next.add(entry.path);
235
+ if (!treeCache[entry.path]) fetchTreeDir(entry.path);
236
+ return next;
237
+ });
238
+ break;
239
+ case 'rename':
240
+ setRenamingPath(entry.path);
241
+ setRenameValue(entry.name);
242
+ break;
243
+ case 'delete':
244
+ if (confirm(`Delete "${entry.name}"?`)) {
245
+ await deleteFile(entry.path);
246
+ }
247
+ break;
248
+ }
249
+ }, [deleteFile, fetchTreeDir, treeCache]);
250
+
251
+ const handleCreate = useCallback(async (name) => {
252
+ if (!creating) return;
253
+ const fullPath = creating.parentPath ? `${creating.parentPath}/${name}` : name;
254
+ const ok = creating.type === 'file'
255
+ ? await createFile(fullPath)
256
+ : await createDir(fullPath);
257
+ setCreating(null);
258
+ if (ok && creating.type === 'file') openFile(fullPath);
259
+ }, [creating, createFile, createDir, openFile]);
260
+
261
+ const handleRenameSubmit = useCallback(async () => {
262
+ if (!renamingPath || !renameValue.trim()) { setRenamingPath(null); return; }
263
+ const parentDir = renamingPath.includes('/') ? renamingPath.split('/').slice(0, -1).join('/') : '';
264
+ const newPath = parentDir ? `${parentDir}/${renameValue.trim()}` : renameValue.trim();
265
+ if (newPath !== renamingPath) {
266
+ await renameFile(renamingPath, newPath);
267
+ }
268
+ setRenamingPath(null);
269
+ }, [renamingPath, renameValue, renameFile]);
270
+
271
+ // Handle root-level context menu (right-click on empty space)
272
+ const handleTreeContextMenu = useCallback((e) => {
273
+ // Only if click is on the tree background, not a node
274
+ if (e.target === e.currentTarget) {
275
+ e.preventDefault();
276
+ setContextMenu({ x: e.clientX, y: e.clientY, entry: { type: 'dir', path: '', name: 'root' } });
277
+ }
278
+ }, []);
279
+
106
280
  const rootEntries = treeCache[''] || [];
107
281
 
108
- // Client-side filter
282
+ // Client-side filter — search all cached entries recursively
109
283
  const filtered = filter
110
284
  ? rootEntries.filter((e) => e.name.toLowerCase().includes(filter.toLowerCase()))
111
285
  : rootEntries;
112
286
 
287
+ // Calculate inline input depth based on parent
288
+ const creatingDepth = creating
289
+ ? (creating.parentPath === '' ? 0 : creating.parentPath.split('/').length)
290
+ : 0;
291
+
113
292
  return (
114
293
  <div style={styles.container}>
294
+ {/* Toolbar */}
295
+ <div style={styles.toolbar}>
296
+ <span style={styles.toolbarTitle}>FILES</span>
297
+ <div style={styles.toolbarActions}>
298
+ <button
299
+ onClick={() => setCreating({ type: 'file', parentPath: '' })}
300
+ title="New File"
301
+ style={styles.toolbarBtn}
302
+ >
303
+ +f
304
+ </button>
305
+ <button
306
+ onClick={() => setCreating({ type: 'dir', parentPath: '' })}
307
+ title="New Folder"
308
+ style={styles.toolbarBtn}
309
+ >
310
+ +d
311
+ </button>
312
+ </div>
313
+ </div>
314
+
115
315
  {/* Search */}
116
316
  <div style={styles.searchWrap}>
117
317
  <input
@@ -124,24 +324,62 @@ export default function FileTree() {
124
324
  </div>
125
325
 
126
326
  {/* Tree */}
127
- <div style={styles.tree}>
128
- {filtered.length === 0 && (
327
+ <div style={styles.tree} onContextMenu={handleTreeContextMenu}>
328
+ {filtered.length === 0 && !creating && (
129
329
  <div style={styles.empty}>
130
330
  {rootEntries.length === 0 ? 'Loading...' : 'No matches'}
131
331
  </div>
132
332
  )}
133
- {filtered.map((entry) => (
134
- <TreeNode
135
- key={entry.path}
136
- entry={entry}
333
+
334
+ {/* Inline creation at root level */}
335
+ {creating && creating.parentPath === '' && (
336
+ <InlineInput
337
+ placeholder={creating.type === 'file' ? 'filename.ext' : 'folder-name'}
338
+ onSubmit={handleCreate}
339
+ onCancel={() => setCreating(null)}
137
340
  depth={0}
138
- activeFile={activeFile}
139
- expandedDirs={expandedDirs}
140
- onToggleDir={onToggleDir}
141
- onFileClick={openFile}
142
341
  />
342
+ )}
343
+
344
+ {filtered.map((entry) => (
345
+ <React.Fragment key={entry.path}>
346
+ <TreeNode
347
+ entry={entry}
348
+ depth={0}
349
+ activeFile={activeFile}
350
+ expandedDirs={expandedDirs}
351
+ onToggleDir={onToggleDir}
352
+ onFileClick={openFile}
353
+ onContextMenu={onContextMenu}
354
+ renamingPath={renamingPath}
355
+ renameValue={renameValue}
356
+ onRenameChange={setRenameValue}
357
+ onRenameSubmit={handleRenameSubmit}
358
+ onRenameCancel={() => setRenamingPath(null)}
359
+ />
360
+ {/* Inline creation inside this dir */}
361
+ {creating && creating.parentPath === entry.path && expandedDirs.has(entry.path) && (
362
+ <InlineInput
363
+ placeholder={creating.type === 'file' ? 'filename.ext' : 'folder-name'}
364
+ onSubmit={handleCreate}
365
+ onCancel={() => setCreating(null)}
366
+ depth={1}
367
+ />
368
+ )}
369
+ </React.Fragment>
143
370
  ))}
144
371
  </div>
372
+
373
+ {/* Context Menu */}
374
+ {contextMenu && (
375
+ <ContextMenu
376
+ x={contextMenu.x}
377
+ y={contextMenu.y}
378
+ entry={contextMenu.entry}
379
+ onClose={() => setContextMenu(null)}
380
+ onAction={handleContextAction}
381
+ />
382
+ )}
145
383
  </div>
146
384
  );
147
385
  }
@@ -150,9 +388,29 @@ const styles = {
150
388
  container: {
151
389
  display: 'flex', flexDirection: 'column',
152
390
  height: '100%', background: 'var(--bg-chrome)',
391
+ position: 'relative',
392
+ },
393
+ toolbar: {
394
+ display: 'flex', alignItems: 'center', justifyContent: 'space-between',
395
+ padding: '6px 10px',
396
+ borderBottom: '1px solid var(--border)',
397
+ },
398
+ toolbarTitle: {
399
+ fontSize: 10, fontWeight: 700, letterSpacing: 1,
400
+ color: 'var(--text-dim)', textTransform: 'uppercase',
401
+ },
402
+ toolbarActions: {
403
+ display: 'flex', gap: 2,
404
+ },
405
+ toolbarBtn: {
406
+ background: 'none', border: 'none',
407
+ color: 'var(--text-dim)', fontSize: 11, fontWeight: 600,
408
+ cursor: 'pointer', fontFamily: 'var(--font)',
409
+ padding: '2px 5px', borderRadius: 3,
410
+ lineHeight: 1,
153
411
  },
154
412
  searchWrap: {
155
- padding: '8px 8px 6px',
413
+ padding: '6px 8px',
156
414
  borderBottom: '1px solid var(--border)',
157
415
  },
158
416
  searchInput: {
@@ -190,4 +448,31 @@ const styles = {
190
448
  padding: 16, textAlign: 'center',
191
449
  fontSize: 11, color: 'var(--text-dim)',
192
450
  },
451
+ renameInput: {
452
+ flex: 1, padding: '2px 6px',
453
+ background: 'var(--bg-base)', border: '1px solid var(--accent)',
454
+ borderRadius: 2, color: 'var(--text-bright)',
455
+ fontSize: 12, fontFamily: 'var(--font)',
456
+ outline: 'none', minWidth: 0,
457
+ },
458
+
459
+ // Context menu
460
+ contextMenu: {
461
+ position: 'fixed', zIndex: 300,
462
+ background: 'var(--bg-surface)',
463
+ border: '1px solid var(--border)',
464
+ borderRadius: 4, padding: '4px 0',
465
+ minWidth: 160,
466
+ boxShadow: '0 8px 24px rgba(0,0,0,0.4)',
467
+ fontFamily: 'var(--font)',
468
+ },
469
+ contextItem: {
470
+ padding: '6px 14px', fontSize: 11,
471
+ cursor: 'pointer',
472
+ transition: 'background 0.08s',
473
+ },
474
+ contextSep: {
475
+ height: 1, background: 'var(--border)',
476
+ margin: '4px 0',
477
+ },
193
478
  };
@@ -0,0 +1,104 @@
1
+ // GROOVE GUI — Media Viewer (images + video)
2
+ // FSL-1.1-Apache-2.0 — see LICENSE
3
+
4
+ import React from 'react';
5
+
6
+ const IMAGE_EXTS = new Set(['png', 'jpg', 'jpeg', 'gif', 'svg', 'webp', 'ico', 'bmp', 'avif']);
7
+ const VIDEO_EXTS = new Set(['mp4', 'webm', 'mov', 'avi', 'mkv', 'ogv']);
8
+
9
+ export function isMediaFile(path) {
10
+ const ext = path.split('.').pop()?.toLowerCase();
11
+ return IMAGE_EXTS.has(ext) || VIDEO_EXTS.has(ext);
12
+ }
13
+
14
+ export function isImageFile(path) {
15
+ const ext = path.split('.').pop()?.toLowerCase();
16
+ return IMAGE_EXTS.has(ext);
17
+ }
18
+
19
+ export default function MediaViewer({ path }) {
20
+ const ext = path.split('.').pop()?.toLowerCase();
21
+ const rawUrl = `/api/files/raw?path=${encodeURIComponent(path)}`;
22
+ const filename = path.split('/').pop();
23
+ const isImage = IMAGE_EXTS.has(ext);
24
+ const isVideo = VIDEO_EXTS.has(ext);
25
+
26
+ return (
27
+ <div style={styles.container}>
28
+ <div style={styles.header}>
29
+ <span style={styles.filename}>{filename}</span>
30
+ <span style={styles.badge}>{ext.toUpperCase()}</span>
31
+ </div>
32
+
33
+ <div style={styles.preview}>
34
+ {isImage && (
35
+ <img
36
+ src={rawUrl}
37
+ alt={filename}
38
+ style={styles.image}
39
+ draggable={false}
40
+ />
41
+ )}
42
+ {isVideo && (
43
+ <video
44
+ src={rawUrl}
45
+ controls
46
+ style={styles.video}
47
+ >
48
+ Your browser does not support this video format.
49
+ </video>
50
+ )}
51
+ </div>
52
+
53
+ <div style={styles.footer}>
54
+ <a href={rawUrl} target="_blank" rel="noopener noreferrer" style={styles.link}>
55
+ Open in new tab
56
+ </a>
57
+ </div>
58
+ </div>
59
+ );
60
+ }
61
+
62
+ const styles = {
63
+ container: {
64
+ flex: 1, display: 'flex', flexDirection: 'column',
65
+ background: 'var(--bg-base)', overflow: 'hidden',
66
+ },
67
+ header: {
68
+ padding: '10px 16px',
69
+ borderBottom: '1px solid var(--border)',
70
+ display: 'flex', alignItems: 'center', gap: 8,
71
+ },
72
+ filename: {
73
+ fontSize: 12, color: 'var(--text-bright)', fontWeight: 500,
74
+ },
75
+ badge: {
76
+ fontSize: 9, fontWeight: 700, letterSpacing: 0.5,
77
+ color: 'var(--text-dim)', background: 'var(--bg-active)',
78
+ padding: '2px 6px', borderRadius: 3,
79
+ },
80
+ preview: {
81
+ flex: 1, display: 'flex', alignItems: 'center', justifyContent: 'center',
82
+ overflow: 'auto', padding: 24,
83
+ background: 'repeating-conic-gradient(var(--bg-surface) 0% 25%, var(--bg-base) 0% 50%) 50% / 20px 20px',
84
+ },
85
+ image: {
86
+ maxWidth: '100%', maxHeight: '100%',
87
+ objectFit: 'contain', borderRadius: 4,
88
+ boxShadow: '0 4px 16px rgba(0,0,0,0.3)',
89
+ },
90
+ video: {
91
+ maxWidth: '100%', maxHeight: '100%',
92
+ borderRadius: 4, outline: 'none',
93
+ boxShadow: '0 4px 16px rgba(0,0,0,0.3)',
94
+ },
95
+ footer: {
96
+ padding: '8px 16px',
97
+ borderTop: '1px solid var(--border)',
98
+ display: 'flex', alignItems: 'center',
99
+ },
100
+ link: {
101
+ fontSize: 11, color: 'var(--accent)',
102
+ textDecoration: 'none', fontFamily: 'var(--font)',
103
+ },
104
+ };