@fgv/ts-res-ui-components 5.0.0-10

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 (231) hide show
  1. package/.rush/temp/03c8b056281d9db0a97d8a6e25eea798a160d393.tar.log +271 -0
  2. package/.rush/temp/chunked-rush-logs/ts-res-ui-components.build.chunks.jsonl +9 -0
  3. package/.rush/temp/operation/build/all.log +9 -0
  4. package/.rush/temp/operation/build/log-chunks.jsonl +9 -0
  5. package/.rush/temp/operation/build/state.json +3 -0
  6. package/.rush/temp/shrinkwrap-deps.json +1111 -0
  7. package/README.md +18 -0
  8. package/REFACTORING_PLAN.md +171 -0
  9. package/config/jest.config.json +16 -0
  10. package/config/jest.setup.js +64 -0
  11. package/config/rig.json +16 -0
  12. package/lib/components/common/QualifierContextControl.d.ts +14 -0
  13. package/lib/components/common/QualifierContextControl.d.ts.map +1 -0
  14. package/lib/components/common/QualifierContextControl.js +78 -0
  15. package/lib/components/common/QualifierContextControl.js.map +1 -0
  16. package/lib/components/common/ResourceListView.d.ts +11 -0
  17. package/lib/components/common/ResourceListView.d.ts.map +1 -0
  18. package/lib/components/common/ResourceListView.js +20 -0
  19. package/lib/components/common/ResourceListView.js.map +1 -0
  20. package/lib/components/common/ResourceTreeView.d.ts +12 -0
  21. package/lib/components/common/ResourceTreeView.d.ts.map +1 -0
  22. package/lib/components/common/ResourceTreeView.js +162 -0
  23. package/lib/components/common/ResourceTreeView.js.map +1 -0
  24. package/lib/components/forms/HierarchyEditor.d.ts +10 -0
  25. package/lib/components/forms/HierarchyEditor.d.ts.map +1 -0
  26. package/lib/components/forms/HierarchyEditor.js +106 -0
  27. package/lib/components/forms/HierarchyEditor.js.map +1 -0
  28. package/lib/components/forms/QualifierEditForm.d.ts +11 -0
  29. package/lib/components/forms/QualifierEditForm.d.ts.map +1 -0
  30. package/lib/components/forms/QualifierEditForm.js +181 -0
  31. package/lib/components/forms/QualifierEditForm.js.map +1 -0
  32. package/lib/components/forms/QualifierTypeEditForm.d.ts +10 -0
  33. package/lib/components/forms/QualifierTypeEditForm.d.ts.map +1 -0
  34. package/lib/components/forms/QualifierTypeEditForm.js +172 -0
  35. package/lib/components/forms/QualifierTypeEditForm.js.map +1 -0
  36. package/lib/components/forms/ResourceTypeEditForm.d.ts +10 -0
  37. package/lib/components/forms/ResourceTypeEditForm.d.ts.map +1 -0
  38. package/lib/components/forms/ResourceTypeEditForm.js +188 -0
  39. package/lib/components/forms/ResourceTypeEditForm.js.map +1 -0
  40. package/lib/components/forms/index.d.ts +9 -0
  41. package/lib/components/forms/index.d.ts.map +1 -0
  42. package/lib/components/forms/index.js +5 -0
  43. package/lib/components/forms/index.js.map +1 -0
  44. package/lib/components/orchestrator/ResourceOrchestrator.d.ts +14 -0
  45. package/lib/components/orchestrator/ResourceOrchestrator.d.ts.map +1 -0
  46. package/lib/components/orchestrator/ResourceOrchestrator.js +278 -0
  47. package/lib/components/orchestrator/ResourceOrchestrator.js.map +1 -0
  48. package/lib/components/views/CompiledView/index.d.ts +5 -0
  49. package/lib/components/views/CompiledView/index.d.ts.map +1 -0
  50. package/lib/components/views/CompiledView/index.js +595 -0
  51. package/lib/components/views/CompiledView/index.js.map +1 -0
  52. package/lib/components/views/ConfigurationView/index.d.ts +5 -0
  53. package/lib/components/views/ConfigurationView/index.d.ts.map +1 -0
  54. package/lib/components/views/ConfigurationView/index.js +363 -0
  55. package/lib/components/views/ConfigurationView/index.js.map +1 -0
  56. package/lib/components/views/FilterView/index.d.ts +5 -0
  57. package/lib/components/views/FilterView/index.d.ts.map +1 -0
  58. package/lib/components/views/FilterView/index.js +463 -0
  59. package/lib/components/views/FilterView/index.js.map +1 -0
  60. package/lib/components/views/ImportView/index.d.ts +5 -0
  61. package/lib/components/views/ImportView/index.d.ts.map +1 -0
  62. package/lib/components/views/ImportView/index.js +514 -0
  63. package/lib/components/views/ImportView/index.js.map +1 -0
  64. package/lib/components/views/ResolutionView/EditableJsonView.d.ts +21 -0
  65. package/lib/components/views/ResolutionView/EditableJsonView.d.ts.map +1 -0
  66. package/lib/components/views/ResolutionView/EditableJsonView.js +109 -0
  67. package/lib/components/views/ResolutionView/EditableJsonView.js.map +1 -0
  68. package/lib/components/views/ResolutionView/ResolutionEditControls.d.ts +19 -0
  69. package/lib/components/views/ResolutionView/ResolutionEditControls.d.ts.map +1 -0
  70. package/lib/components/views/ResolutionView/ResolutionEditControls.js +82 -0
  71. package/lib/components/views/ResolutionView/ResolutionEditControls.js.map +1 -0
  72. package/lib/components/views/ResolutionView/index.d.ts +5 -0
  73. package/lib/components/views/ResolutionView/index.d.ts.map +1 -0
  74. package/lib/components/views/ResolutionView/index.js +255 -0
  75. package/lib/components/views/ResolutionView/index.js.map +1 -0
  76. package/lib/components/views/SourceView/index.d.ts +5 -0
  77. package/lib/components/views/SourceView/index.d.ts.map +1 -0
  78. package/lib/components/views/SourceView/index.js +316 -0
  79. package/lib/components/views/SourceView/index.js.map +1 -0
  80. package/lib/components/views/ZipLoaderView/index.d.ts +5 -0
  81. package/lib/components/views/ZipLoaderView/index.d.ts.map +1 -0
  82. package/lib/components/views/ZipLoaderView/index.js +313 -0
  83. package/lib/components/views/ZipLoaderView/index.js.map +1 -0
  84. package/lib/hooks/useConfigurationState.d.ts +46 -0
  85. package/lib/hooks/useConfigurationState.d.ts.map +1 -0
  86. package/lib/hooks/useConfigurationState.js +239 -0
  87. package/lib/hooks/useConfigurationState.js.map +1 -0
  88. package/lib/hooks/useFilterState.d.ts +7 -0
  89. package/lib/hooks/useFilterState.d.ts.map +1 -0
  90. package/lib/hooks/useFilterState.js +80 -0
  91. package/lib/hooks/useFilterState.js.map +1 -0
  92. package/lib/hooks/useResolutionState.d.ts +8 -0
  93. package/lib/hooks/useResolutionState.d.ts.map +1 -0
  94. package/lib/hooks/useResolutionState.js +253 -0
  95. package/lib/hooks/useResolutionState.js.map +1 -0
  96. package/lib/hooks/useResourceData.d.ts +19 -0
  97. package/lib/hooks/useResourceData.d.ts.map +1 -0
  98. package/lib/hooks/useResourceData.js +368 -0
  99. package/lib/hooks/useResourceData.js.map +1 -0
  100. package/lib/hooks/useViewState.d.ts +10 -0
  101. package/lib/hooks/useViewState.d.ts.map +1 -0
  102. package/lib/hooks/useViewState.js +29 -0
  103. package/lib/hooks/useViewState.js.map +1 -0
  104. package/lib/index.d.ts +27 -0
  105. package/lib/index.d.ts.map +1 -0
  106. package/lib/index.js +34 -0
  107. package/lib/index.js.map +1 -0
  108. package/lib/test/helpers/testDataLoader.d.ts +37 -0
  109. package/lib/test/helpers/testDataLoader.d.ts.map +1 -0
  110. package/lib/test/helpers/testDataLoader.js +171 -0
  111. package/lib/test/helpers/testDataLoader.js.map +1 -0
  112. package/lib/test/unit/utils/configurationUtils.test.d.ts +2 -0
  113. package/lib/test/unit/utils/configurationUtils.test.d.ts.map +1 -0
  114. package/lib/test/unit/utils/configurationUtils.test.js +497 -0
  115. package/lib/test/unit/utils/configurationUtils.test.js.map +1 -0
  116. package/lib/test/unit/utils/fileProcessing.test.d.ts +2 -0
  117. package/lib/test/unit/utils/fileProcessing.test.d.ts.map +1 -0
  118. package/lib/test/unit/utils/fileProcessing.test.js +321 -0
  119. package/lib/test/unit/utils/fileProcessing.test.js.map +1 -0
  120. package/lib/test/unit/utils/filterResources.test.d.ts +2 -0
  121. package/lib/test/unit/utils/filterResources.test.d.ts.map +1 -0
  122. package/lib/test/unit/utils/filterResources.test.js +403 -0
  123. package/lib/test/unit/utils/filterResources.test.js.map +1 -0
  124. package/lib/test/unit/utils/resolutionEditing.test.d.ts +2 -0
  125. package/lib/test/unit/utils/resolutionEditing.test.d.ts.map +1 -0
  126. package/lib/test/unit/utils/resolutionEditing.test.js +439 -0
  127. package/lib/test/unit/utils/resolutionEditing.test.js.map +1 -0
  128. package/lib/test/unit/utils/resolutionUtils.test.d.ts +2 -0
  129. package/lib/test/unit/utils/resolutionUtils.test.d.ts.map +1 -0
  130. package/lib/test/unit/utils/resolutionUtils.test.js +397 -0
  131. package/lib/test/unit/utils/resolutionUtils.test.js.map +1 -0
  132. package/lib/test/unit/utils/tsResIntegration.test.d.ts +2 -0
  133. package/lib/test/unit/utils/tsResIntegration.test.d.ts.map +1 -0
  134. package/lib/test/unit/utils/tsResIntegration.test.js +376 -0
  135. package/lib/test/unit/utils/tsResIntegration.test.js.map +1 -0
  136. package/lib/types/index.d.ts +251 -0
  137. package/lib/types/index.d.ts.map +1 -0
  138. package/lib/types/index.js +2 -0
  139. package/lib/types/index.js.map +1 -0
  140. package/lib/utils/configurationUtils.d.ts +74 -0
  141. package/lib/utils/configurationUtils.d.ts.map +1 -0
  142. package/lib/utils/configurationUtils.js +359 -0
  143. package/lib/utils/configurationUtils.js.map +1 -0
  144. package/lib/utils/fileProcessing.d.ts +18 -0
  145. package/lib/utils/fileProcessing.d.ts.map +1 -0
  146. package/lib/utils/fileProcessing.js +142 -0
  147. package/lib/utils/fileProcessing.js.map +1 -0
  148. package/lib/utils/filterResources.d.ts +38 -0
  149. package/lib/utils/filterResources.d.ts.map +1 -0
  150. package/lib/utils/filterResources.js +153 -0
  151. package/lib/utils/filterResources.js.map +1 -0
  152. package/lib/utils/resolutionEditing.d.ts +58 -0
  153. package/lib/utils/resolutionEditing.d.ts.map +1 -0
  154. package/lib/utils/resolutionEditing.js +246 -0
  155. package/lib/utils/resolutionEditing.js.map +1 -0
  156. package/lib/utils/resolutionUtils.d.ts +28 -0
  157. package/lib/utils/resolutionUtils.d.ts.map +1 -0
  158. package/lib/utils/resolutionUtils.js +216 -0
  159. package/lib/utils/resolutionUtils.js.map +1 -0
  160. package/lib/utils/tsResIntegration.d.ts +71 -0
  161. package/lib/utils/tsResIntegration.d.ts.map +1 -0
  162. package/lib/utils/tsResIntegration.js +294 -0
  163. package/lib/utils/tsResIntegration.js.map +1 -0
  164. package/lib/utils/zipLoader/browserZipLoader.d.ts +48 -0
  165. package/lib/utils/zipLoader/browserZipLoader.d.ts.map +1 -0
  166. package/lib/utils/zipLoader/browserZipLoader.js +247 -0
  167. package/lib/utils/zipLoader/browserZipLoader.js.map +1 -0
  168. package/lib/utils/zipLoader/index.d.ts +8 -0
  169. package/lib/utils/zipLoader/index.d.ts.map +1 -0
  170. package/lib/utils/zipLoader/index.js +13 -0
  171. package/lib/utils/zipLoader/index.js.map +1 -0
  172. package/lib/utils/zipLoader/nodeZipBuilder.d.ts +55 -0
  173. package/lib/utils/zipLoader/nodeZipBuilder.d.ts.map +1 -0
  174. package/lib/utils/zipLoader/nodeZipBuilder.js +98 -0
  175. package/lib/utils/zipLoader/nodeZipBuilder.js.map +1 -0
  176. package/lib/utils/zipLoader/types.d.ts +139 -0
  177. package/lib/utils/zipLoader/types.d.ts.map +1 -0
  178. package/lib/utils/zipLoader/types.js +2 -0
  179. package/lib/utils/zipLoader/types.js.map +1 -0
  180. package/lib/utils/zipLoader/zipUtils.d.ts +53 -0
  181. package/lib/utils/zipLoader/zipUtils.d.ts.map +1 -0
  182. package/lib/utils/zipLoader/zipUtils.js +229 -0
  183. package/lib/utils/zipLoader/zipUtils.js.map +1 -0
  184. package/package.json +69 -0
  185. package/rush-logs/ts-res-ui-components.build.cache.log +3 -0
  186. package/rush-logs/ts-res-ui-components.build.log +9 -0
  187. package/src/components/common/QualifierContextControl.tsx +151 -0
  188. package/src/components/common/ResourceListView.tsx +63 -0
  189. package/src/components/common/ResourceTreeView.tsx +271 -0
  190. package/src/components/forms/HierarchyEditor.tsx +204 -0
  191. package/src/components/forms/QualifierEditForm.tsx +355 -0
  192. package/src/components/forms/QualifierTypeEditForm.tsx +347 -0
  193. package/src/components/forms/ResourceTypeEditForm.tsx +331 -0
  194. package/src/components/forms/index.ts +11 -0
  195. package/src/components/orchestrator/ResourceOrchestrator.tsx +372 -0
  196. package/src/components/views/CompiledView/index.tsx +922 -0
  197. package/src/components/views/ConfigurationView/index.tsx +800 -0
  198. package/src/components/views/FilterView/index.tsx +825 -0
  199. package/src/components/views/ImportView/index.tsx +717 -0
  200. package/src/components/views/ResolutionView/EditableJsonView.tsx +214 -0
  201. package/src/components/views/ResolutionView/ResolutionEditControls.tsx +170 -0
  202. package/src/components/views/ResolutionView/index.tsx +591 -0
  203. package/src/components/views/SourceView/index.tsx +536 -0
  204. package/src/components/views/ZipLoaderView/index.tsx +485 -0
  205. package/src/hooks/useConfigurationState.ts +374 -0
  206. package/src/hooks/useFilterState.ts +97 -0
  207. package/src/hooks/useResolutionState.ts +355 -0
  208. package/src/hooks/useResourceData.ts +467 -0
  209. package/src/hooks/useViewState.ts +44 -0
  210. package/src/index.ts +45 -0
  211. package/src/test/helpers/testDataLoader.ts +195 -0
  212. package/src/test/unit/utils/configurationUtils.test.ts +630 -0
  213. package/src/test/unit/utils/fileProcessing.test.ts +391 -0
  214. package/src/test/unit/utils/filterResources.test.ts +574 -0
  215. package/src/test/unit/utils/resolutionEditing.test.ts +556 -0
  216. package/src/test/unit/utils/resolutionUtils.test.ts +521 -0
  217. package/src/test/unit/utils/tsResIntegration.test.ts +433 -0
  218. package/src/types/index.ts +322 -0
  219. package/src/utils/configurationUtils.ts +424 -0
  220. package/src/utils/fileProcessing.ts +160 -0
  221. package/src/utils/filterResources.ts +206 -0
  222. package/src/utils/resolutionEditing.ts +319 -0
  223. package/src/utils/resolutionUtils.ts +289 -0
  224. package/src/utils/tsResIntegration.ts +440 -0
  225. package/src/utils/zipLoader/browserZipLoader.ts +319 -0
  226. package/src/utils/zipLoader/index.ts +26 -0
  227. package/src/utils/zipLoader/nodeZipBuilder.ts +153 -0
  228. package/src/utils/zipLoader/types.ts +175 -0
  229. package/src/utils/zipLoader/zipUtils.ts +266 -0
  230. package/temp/build/typescript/ts_gZid87Hu.json +1 -0
  231. package/tsconfig.json +15 -0
@@ -0,0 +1,266 @@
1
+ import { Result, succeed, fail } from '@fgv/ts-utils';
2
+ import { Config } from '@fgv/ts-res';
3
+ import { ZipManifest, ZipFileItem, ZipFileTree } from './types';
4
+ import { ImportedDirectory, ImportedFile } from '../../types';
5
+
6
+ /**
7
+ * Generate a timestamp-based filename for ZIP archives
8
+ */
9
+ export function generateZipFilename(customName?: string): string {
10
+ const timestamp = new Date().toISOString().replace(/[:.]/g, '-').replace('T', '_').slice(0, -5);
11
+ return customName ? `${customName}-${timestamp}.zip` : `ts-res-bundle-${timestamp}.zip`;
12
+ }
13
+
14
+ /**
15
+ * Create a ZIP manifest object
16
+ */
17
+ export function createManifest(
18
+ inputType: 'file' | 'directory',
19
+ originalPath: string,
20
+ archivePath: string,
21
+ configPath?: string
22
+ ): ZipManifest {
23
+ const manifest: ZipManifest = {
24
+ timestamp: new Date().toISOString(),
25
+ input: {
26
+ type: inputType,
27
+ originalPath,
28
+ archivePath
29
+ }
30
+ };
31
+
32
+ if (configPath) {
33
+ manifest.config = {
34
+ type: 'file',
35
+ originalPath: configPath,
36
+ archivePath: 'config.json'
37
+ };
38
+ }
39
+
40
+ return manifest;
41
+ }
42
+
43
+ /**
44
+ * Parse and validate a ZIP manifest
45
+ */
46
+ export function parseManifest(manifestData: string): Result<ZipManifest> {
47
+ try {
48
+ const parsed = JSON.parse(manifestData);
49
+
50
+ // Basic validation
51
+ if (!parsed.timestamp || typeof parsed.timestamp !== 'string') {
52
+ return fail('Invalid manifest: missing or invalid timestamp');
53
+ }
54
+
55
+ // Validate input section if present
56
+ if (parsed.input) {
57
+ if (!parsed.input.type || !['file', 'directory'].includes(parsed.input.type)) {
58
+ return fail('Invalid manifest: invalid input type');
59
+ }
60
+ if (!parsed.input.originalPath || !parsed.input.archivePath) {
61
+ return fail('Invalid manifest: missing input paths');
62
+ }
63
+ }
64
+
65
+ // Validate config section if present
66
+ if (parsed.config) {
67
+ if (parsed.config.type !== 'file') {
68
+ return fail('Invalid manifest: invalid config type');
69
+ }
70
+ if (!parsed.config.originalPath || !parsed.config.archivePath) {
71
+ return fail('Invalid manifest: missing config paths');
72
+ }
73
+ }
74
+
75
+ return succeed(parsed as ZipManifest);
76
+ } catch (error) {
77
+ return fail(`Failed to parse manifest: ${error instanceof Error ? error.message : String(error)}`);
78
+ }
79
+ }
80
+
81
+ /**
82
+ * Parse and validate configuration JSON
83
+ */
84
+ export function parseConfiguration(configData: string): Result<Config.Model.ISystemConfiguration> {
85
+ try {
86
+ const parsed = JSON.parse(configData);
87
+
88
+ // Basic validation - check for expected properties
89
+ if (typeof parsed !== 'object' || parsed === null) {
90
+ return fail('Invalid configuration: not an object');
91
+ }
92
+
93
+ // More detailed validation could be added here using ts-res validators
94
+ return succeed(parsed as Config.Model.ISystemConfiguration);
95
+ } catch (error) {
96
+ return fail(`Failed to parse configuration: ${error instanceof Error ? error.message : String(error)}`);
97
+ }
98
+ }
99
+
100
+ /**
101
+ * Convert ZIP file tree to ImportedFiles array
102
+ */
103
+ export function zipTreeToFiles(tree: ZipFileTree): ImportedFile[] {
104
+ const files: ImportedFile[] = [];
105
+
106
+ for (const [path, item] of Array.from(tree.files.entries())) {
107
+ if (!item.isDirectory && item.content && typeof item.content === 'string') {
108
+ files.push({
109
+ name: item.name,
110
+ path: path,
111
+ content: item.content,
112
+ type: getFileType(item.name)
113
+ });
114
+ }
115
+ }
116
+
117
+ return files;
118
+ }
119
+
120
+ /**
121
+ * Convert ZIP file tree to ImportedDirectory structure
122
+ */
123
+ export function zipTreeToDirectory(tree: ZipFileTree): ImportedDirectory | null {
124
+ if (tree.files.size === 0) {
125
+ return null;
126
+ }
127
+
128
+ // Build directory structure
129
+ const directories = new Map<string, ImportedDirectory>();
130
+ const rootDir: ImportedDirectory = {
131
+ name: tree.root || 'root',
132
+ files: [],
133
+ subdirectories: []
134
+ };
135
+ directories.set('', rootDir);
136
+
137
+ // Process all paths to build directory structure
138
+ for (const [path, item] of Array.from(tree.files.entries())) {
139
+ const pathParts = path.split('/').filter((part: string) => part.length > 0);
140
+
141
+ if (pathParts.length === 0) continue;
142
+
143
+ // Ensure all parent directories exist
144
+ let currentPath = '';
145
+ for (let i = 0; i < pathParts.length - 1; i++) {
146
+ const parentPath = currentPath;
147
+ currentPath = currentPath ? `${currentPath}/${pathParts[i]}` : pathParts[i];
148
+
149
+ if (!directories.has(currentPath)) {
150
+ const newDir: ImportedDirectory = {
151
+ name: pathParts[i],
152
+ files: [],
153
+ subdirectories: []
154
+ };
155
+ directories.set(currentPath, newDir);
156
+
157
+ // Add to parent directory
158
+ const parentDir = directories.get(parentPath);
159
+ if (parentDir) {
160
+ parentDir.subdirectories = parentDir.subdirectories || [];
161
+ parentDir.subdirectories.push(newDir);
162
+ }
163
+ }
164
+ }
165
+
166
+ // Add file to its parent directory
167
+ if (!item.isDirectory && item.content && typeof item.content === 'string') {
168
+ const fileName = pathParts[pathParts.length - 1];
169
+ const parentPath = pathParts.length > 1 ? pathParts.slice(0, -1).join('/') : '';
170
+ const parentDir = directories.get(parentPath);
171
+
172
+ if (parentDir) {
173
+ parentDir.files.push({
174
+ name: fileName,
175
+ path: path,
176
+ content: item.content,
177
+ type: getFileType(fileName)
178
+ });
179
+ }
180
+ }
181
+ }
182
+
183
+ return rootDir;
184
+ }
185
+
186
+ /**
187
+ * Get file type based on extension
188
+ */
189
+ function getFileType(filename: string): string {
190
+ const ext = filename.toLowerCase().split('.').pop();
191
+ switch (ext) {
192
+ case 'json':
193
+ return 'application/json';
194
+ case 'yaml':
195
+ case 'yml':
196
+ return 'application/yaml';
197
+ case 'xml':
198
+ return 'application/xml';
199
+ case 'txt':
200
+ return 'text/plain';
201
+ case 'md':
202
+ return 'text/markdown';
203
+ case 'js':
204
+ return 'application/javascript';
205
+ case 'ts':
206
+ return 'application/typescript';
207
+ default:
208
+ return 'application/octet-stream';
209
+ }
210
+ }
211
+
212
+ /**
213
+ * Normalize path separators for cross-platform compatibility
214
+ */
215
+ export function normalizePath(path: string): string {
216
+ return path.replace(/\\/g, '/').replace(/\/+/g, '/');
217
+ }
218
+
219
+ /**
220
+ * Extract directory name from a file path
221
+ */
222
+ export function getDirectoryName(path: string): string {
223
+ const normalized = normalizePath(path);
224
+ const parts = normalized.split('/');
225
+ return parts[parts.length - 1] || 'archive';
226
+ }
227
+
228
+ /**
229
+ * Create a safe filename by removing invalid characters
230
+ */
231
+ export function sanitizeFilename(filename: string): string {
232
+ return filename
233
+ .replace(/[<>:"/\\|?*]/g, '_')
234
+ .replace(/\s+/g, '_')
235
+ .replace(/_+/g, '_')
236
+ .trim();
237
+ }
238
+
239
+ /**
240
+ * Format file size in human readable format
241
+ */
242
+ export function formatFileSize(bytes: number): string {
243
+ if (bytes === 0) return '0 B';
244
+
245
+ const sizes = ['B', 'KB', 'MB', 'GB'];
246
+ const i = Math.floor(Math.log(bytes) / Math.log(1024));
247
+ const size = bytes / Math.pow(1024, i);
248
+
249
+ return `${size.toFixed(i === 0 ? 0 : 1)} ${sizes[i]}`;
250
+ }
251
+
252
+ /**
253
+ * Validate ZIP file extension
254
+ */
255
+ export function isZipFile(filename: string): boolean {
256
+ return filename.toLowerCase().endsWith('.zip');
257
+ }
258
+
259
+ /**
260
+ * Extract base name from filename (without extension)
261
+ */
262
+ export function getBaseName(filename: string): string {
263
+ const name = filename.split('/').pop() || filename;
264
+ const dotIndex = name.lastIndexOf('.');
265
+ return dotIndex > 0 ? name.substring(0, dotIndex) : name;
266
+ }