@fgv/ts-res-browser 5.0.0-2 → 5.0.0-21

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 (99) hide show
  1. package/cli.js +132 -21
  2. package/dist/bundle.js +1 -1
  3. package/dist/bundle.js.LICENSE.txt +11 -1
  4. package/package.json +42 -36
  5. package/.rush/temp/587277bfc395406ad444e989b6c309b9698f534c.tar.log +0 -15
  6. package/.rush/temp/chunked-rush-logs/ts-res-browser.build.chunks.jsonl +0 -35
  7. package/.rush/temp/operation/build/all.log +0 -35
  8. package/.rush/temp/operation/build/log-chunks.jsonl +0 -35
  9. package/.rush/temp/operation/build/state.json +0 -3
  10. package/.rush/temp/shrinkwrap-deps.json +0 -1225
  11. package/TS_RES_BROWSER_PLAN.md +0 -689
  12. package/babel.config.js +0 -17
  13. package/config/jest.config.json +0 -13
  14. package/config/rig.json +0 -16
  15. package/postcss.config.js +0 -3
  16. package/public/index.html +0 -15
  17. package/rush-logs/ts-res-browser.build.cache.log +0 -3
  18. package/rush-logs/ts-res-browser.build.log +0 -35
  19. package/src/App.tsx +0 -274
  20. package/src/cli.ts +0 -44
  21. package/src/components/common/FileImporter.tsx +0 -143
  22. package/src/components/common/NavigationWarningModal.tsx +0 -62
  23. package/src/components/layout/AppLayout.tsx +0 -46
  24. package/src/components/layout/Header.tsx +0 -22
  25. package/src/components/layout/MessagesWindow.tsx +0 -108
  26. package/src/components/layout/Sidebar.tsx +0 -99
  27. package/src/components/tools/CompiledBrowser.tsx +0 -949
  28. package/src/components/tools/ConfigurationTool.tsx +0 -1405
  29. package/src/components/tools/FilterTool.tsx +0 -1114
  30. package/src/components/tools/ImportTool.tsx +0 -249
  31. package/src/components/tools/ResolutionViewer.tsx +0 -1672
  32. package/src/components/tools/SourceBrowser.tsx +0 -496
  33. package/src/components/tools/ZipLoader.tsx +0 -302
  34. package/src/hooks/useAppState.ts +0 -111
  35. package/src/hooks/useFileImport.ts +0 -86
  36. package/src/hooks/useNavigationWarning.ts +0 -67
  37. package/src/hooks/useResourceManager.ts +0 -295
  38. package/src/hooks/useUrlParams.ts +0 -56
  39. package/src/index.css +0 -38
  40. package/src/main.tsx +0 -16
  41. package/src/types/app.ts +0 -70
  42. package/src/types/fileSystemAccess.d.ts +0 -94
  43. package/src/utils/browserFileTreeAccessors.ts +0 -245
  44. package/src/utils/fileImport.ts +0 -336
  45. package/src/utils/fileTreeConverter.ts +0 -160
  46. package/src/utils/filterResources.ts +0 -677
  47. package/src/utils/tsResIntegration.ts +0 -467
  48. package/src/utils/urlParams.ts +0 -238
  49. package/src/utils/zip/browserZipFileTreeAccessors.ts +0 -385
  50. package/tailwind.config.js +0 -21
  51. package/test-data/README.md +0 -184
  52. package/test-data/config-variations/README.md +0 -64
  53. package/test-data/config-variations/composition-terminology.json +0 -112
  54. package/test-data/config-variations/enterprise-complex.json +0 -129
  55. package/test-data/config-variations/gaming-app.json +0 -87
  56. package/test-data/config-variations/high-priority-language.json +0 -65
  57. package/test-data/config-variations/minimal-basic.json +0 -37
  58. package/test-data/config-variations/qualifier-default-values.json +0 -71
  59. package/test-data/config-variations/territory-first.json +0 -68
  60. package/test-data/config-variations/territory-hierarchy-test.json +0 -31
  61. package/test-data/config-variations/territory-hierarchy-unconstrained.json +0 -32
  62. package/test-data/default-matching-test/greeting.res.json +0 -83
  63. package/test-data/default-matching-test/only-defaults.res.json +0 -48
  64. package/test-data/sample-project/package.json +0 -27
  65. package/test-data/sample-project/src/resources/app-config.json +0 -27
  66. package/test-data/sample-project/src/resources/app.json +0 -177
  67. package/test-data/sample-project/src/resources/config/app.env=development.json +0 -21
  68. package/test-data/sample-project/src/resources/config/app.json +0 -21
  69. package/test-data/sample-project/src/resources/config/app.platform=mobile.json +0 -23
  70. package/test-data/sample-project/src/resources/feature-flags.json +0 -23
  71. package/test-data/sample-project/src/resources/home=CA/common.json +0 -16
  72. package/test-data/sample-project/src/resources/images/density=hdpi.json +0 -29
  73. package/test-data/sample-project/src/resources/images/home.json +0 -29
  74. package/test-data/sample-project/src/resources/images/logout.json +0 -14
  75. package/test-data/sample-project/src/resources/images/settings.json +0 -14
  76. package/test-data/sample-project/src/resources/images/ui-icons.json +0 -29
  77. package/test-data/sample-project/src/resources/images/user.density=hdpi.json +0 -14
  78. package/test-data/sample-project/src/resources/images/user.json +0 -14
  79. package/test-data/sample-project/src/resources/lang=en-US/common.json +0 -15
  80. package/test-data/sample-project/src/resources/language=en-CA/ui-terms.json +0 -19
  81. package/test-data/sample-project/src/resources/language=en-GB/ui-terms.json +0 -30
  82. package/test-data/sample-project/src/resources/language=fr-CA/ui-terms.json +0 -25
  83. package/test-data/sample-project/src/resources/language=nl-BE/ui-terms.json +0 -17
  84. package/test-data/sample-project/src/resources/region=europe/feature-flags.json +0 -17
  85. package/test-data/sample-project/src/resources/region=north-america/feature-flags.json +0 -18
  86. package/test-data/sample-project/src/resources/strings/dashboard.home=CA,language=fr.json +0 -20
  87. package/test-data/sample-project/src/resources/strings/dashboard.json +0 -18
  88. package/test-data/sample-project/src/resources/strings/dashboard.role=admin.json +0 -23
  89. package/test-data/sample-project/src/resources/strings/dashboard.role=user.json +0 -21
  90. package/test-data/sample-project/src/resources/strings/language=en-US.json +0 -16
  91. package/test-data/sample-project/src/resources/strings.json +0 -78
  92. package/test-data/sample-project/src/resources/territory=BE/app-config.json +0 -19
  93. package/test-data/sample-project/src/resources/territory=CA/app-config.json +0 -21
  94. package/test-data/sample-project/src/resources/territory=GB/app-config.json +0 -25
  95. package/test-data/sample-project/src/resources/territory=NL/app-config.json +0 -25
  96. package/test-data/sample-project/src/resources/ui-terms.json +0 -96
  97. package/test-data/sample-project/src/resources-config.json +0 -111
  98. package/tsconfig.json +0 -27
  99. package/webpack.config.js +0 -60
@@ -1,496 +0,0 @@
1
- import React, { useState, useMemo, useCallback } from 'react';
2
- import {
3
- DocumentTextIcon,
4
- MagnifyingGlassIcon,
5
- DocumentArrowDownIcon,
6
- CodeBracketIcon,
7
- ChevronDownIcon,
8
- ChevronUpIcon
9
- } from '@heroicons/react/24/outline';
10
- import { UseResourceManagerReturn } from '../../hooks/useResourceManager';
11
- import { Message } from '../../types/app';
12
- import { NoMatch } from '@fgv/ts-res';
13
-
14
- interface SourceBrowserProps {
15
- onMessage?: (type: Message['type'], message: string) => void;
16
- resourceManager: UseResourceManagerReturn;
17
- }
18
-
19
- const SourceBrowser: React.FC<SourceBrowserProps> = ({ onMessage, resourceManager }) => {
20
- const { state: resourceState } = resourceManager;
21
- const [selectedResourceId, setSelectedResourceId] = useState<string | null>(null);
22
- const [searchTerm, setSearchTerm] = useState('');
23
- const [showJsonView, setShowJsonView] = useState(false);
24
-
25
- // Sort and filter resource IDs
26
- const filteredResourceIds = useMemo(() => {
27
- if (!resourceState.processedResources?.summary.resourceIds) {
28
- return [];
29
- }
30
-
31
- const resourceIds = resourceState.processedResources.summary.resourceIds;
32
-
33
- // Filter by search term
34
- const filtered = searchTerm
35
- ? resourceIds.filter((id: string) => id.toLowerCase().includes(searchTerm.toLowerCase()))
36
- : resourceIds;
37
-
38
- // Sort alphabetically
39
- return filtered.sort();
40
- }, [resourceState.processedResources?.summary.resourceIds, searchTerm]);
41
-
42
- const handleResourceSelect = (resourceId: string) => {
43
- setSelectedResourceId(resourceId);
44
- onMessage?.('info', `Selected resource: ${resourceId}`);
45
- };
46
-
47
- const handleSearch = (term: string) => {
48
- setSearchTerm(term);
49
- setSelectedResourceId(null); // Clear selection when searching
50
- };
51
-
52
- // Get full resource collection data using the new method
53
- const getResourceCollectionData = useCallback(() => {
54
- if (!resourceState.processedResources?.system.resourceManager) {
55
- return null;
56
- }
57
-
58
- try {
59
- const collectionResult =
60
- resourceState.processedResources.system.resourceManager.getResourceCollectionDecl();
61
- if (collectionResult.isSuccess()) {
62
- return {
63
- ...collectionResult.value,
64
- metadata: {
65
- exportedAt: new Date().toISOString(),
66
- totalResources: resourceState.processedResources.summary.totalResources,
67
- type: 'ts-res-resource-collection'
68
- }
69
- };
70
- } else {
71
- onMessage?.('error', `Failed to get resource collection: ${collectionResult.message}`);
72
- return null;
73
- }
74
- } catch (error) {
75
- onMessage?.(
76
- 'error',
77
- `Error getting resource collection: ${error instanceof Error ? error.message : String(error)}`
78
- );
79
- return null;
80
- }
81
- }, [resourceState.processedResources, onMessage]);
82
-
83
- // Export source data to JSON file
84
- const handleExportSourceData = useCallback(() => {
85
- try {
86
- const collectionData = getResourceCollectionData();
87
- if (!collectionData) {
88
- onMessage?.('error', 'No source collection data available to export');
89
- return;
90
- }
91
-
92
- const sourceJson = JSON.stringify(collectionData, null, 2);
93
- const blob = new Blob([sourceJson], { type: 'application/json' });
94
- const url = URL.createObjectURL(blob);
95
-
96
- const a = document.createElement('a');
97
- a.href = url;
98
- a.download = 'resource-collection.json';
99
- document.body.appendChild(a);
100
- a.click();
101
- document.body.removeChild(a);
102
- URL.revokeObjectURL(url);
103
-
104
- onMessage?.('success', 'Resource collection exported successfully');
105
- } catch (error) {
106
- onMessage?.(
107
- 'error',
108
- `Failed to export resource collection: ${error instanceof Error ? error.message : String(error)}`
109
- );
110
- }
111
- }, [getResourceCollectionData, onMessage]);
112
-
113
- if (!resourceState.processedResources) {
114
- return (
115
- <div className="p-6">
116
- <div className="flex items-center space-x-3 mb-6">
117
- <DocumentTextIcon className="h-8 w-8 text-blue-600" />
118
- <h2 className="text-2xl font-bold text-gray-900">Source Browser</h2>
119
- </div>
120
-
121
- <div className="bg-white rounded-lg shadow-sm border border-gray-200 p-8 text-center">
122
- <div className="max-w-2xl mx-auto">
123
- <h3 className="text-xl font-semibold text-gray-900 mb-4">No Resources Loaded</h3>
124
- <p className="text-gray-600 mb-6">Import resources using the Import Tool to explore them here.</p>
125
- <div className="bg-blue-50 rounded-lg p-4">
126
- <p className="text-sm text-blue-800">
127
- <strong>Tip:</strong> Use the Import Tool to load ts-res resource files or directories, then
128
- return here to browse and explore the loaded resources.
129
- </p>
130
- </div>
131
- </div>
132
- </div>
133
- </div>
134
- );
135
- }
136
-
137
- return (
138
- <div className="p-6">
139
- <div className="flex items-center justify-between mb-6">
140
- <div className="flex items-center space-x-3">
141
- <DocumentTextIcon className="h-8 w-8 text-blue-600" />
142
- <h2 className="text-2xl font-bold text-gray-900">Source Browser</h2>
143
- </div>
144
- {resourceState.processedResources && (
145
- <div className="flex items-center space-x-2">
146
- <button
147
- onClick={handleExportSourceData}
148
- className="inline-flex items-center px-3 py-1.5 border border-gray-300 text-xs font-medium rounded text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500"
149
- >
150
- <DocumentArrowDownIcon className="h-4 w-4 mr-1" />
151
- Export JSON
152
- </button>
153
- </div>
154
- )}
155
- </div>
156
-
157
- {/* JSON View Toggle */}
158
- {resourceState.processedResources && (
159
- <div className="bg-white rounded-lg shadow-sm border border-gray-200 p-4 mb-6">
160
- <button
161
- onClick={() => setShowJsonView(!showJsonView)}
162
- className="inline-flex items-center px-3 py-1.5 text-sm font-medium text-gray-700 hover:text-gray-900 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500"
163
- >
164
- <CodeBracketIcon className="h-4 w-4 mr-2" />
165
- {showJsonView ? 'Hide' : 'Show'} JSON Resource Collection
166
- {showJsonView ? (
167
- <ChevronUpIcon className="h-4 w-4 ml-2" />
168
- ) : (
169
- <ChevronDownIcon className="h-4 w-4 ml-2" />
170
- )}
171
- </button>
172
-
173
- {/* JSON View */}
174
- {showJsonView && (
175
- <div className="mt-4">
176
- <div className="bg-gray-50 rounded-lg border border-gray-200 p-4">
177
- <div className="flex items-center justify-between mb-2">
178
- <h3 className="text-sm font-medium text-gray-900">Resource Collection (JSON)</h3>
179
- <button
180
- onClick={handleExportSourceData}
181
- className="inline-flex items-center px-2 py-1 text-xs font-medium text-gray-700 bg-gray-100 rounded hover:bg-gray-200 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500"
182
- >
183
- <DocumentArrowDownIcon className="h-3 w-3 mr-1" />
184
- Export
185
- </button>
186
- </div>
187
- <pre className="text-xs text-gray-800 bg-white p-3 rounded border overflow-x-auto max-h-64 overflow-y-auto">
188
- {JSON.stringify(getResourceCollectionData(), null, 2)}
189
- </pre>
190
- </div>
191
- </div>
192
- )}
193
- </div>
194
- )}
195
-
196
- <div className="bg-white rounded-lg shadow-sm border border-gray-200 p-6">
197
- <div className="flex flex-col lg:flex-row gap-6 h-[600px]">
198
- {/* Left side: Resource List */}
199
- <div className="lg:w-1/2 flex flex-col">
200
- <div className="flex items-center space-x-3 mb-4">
201
- <h3 className="text-lg font-semibold text-gray-900">
202
- Resources ({filteredResourceIds.length})
203
- </h3>
204
- </div>
205
-
206
- {/* Search Box */}
207
- <div className="relative mb-4">
208
- <MagnifyingGlassIcon className="absolute left-3 top-1/2 transform -translate-y-1/2 h-4 w-4 text-gray-400" />
209
- <input
210
- type="text"
211
- placeholder="Search resources..."
212
- value={searchTerm}
213
- onChange={(e) => handleSearch(e.target.value)}
214
- className="w-full pl-10 pr-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
215
- />
216
- </div>
217
-
218
- {/* Resource List */}
219
- <div className="flex-1 overflow-y-auto border border-gray-200 rounded-lg">
220
- {filteredResourceIds.length === 0 ? (
221
- <div className="p-4 text-center text-gray-500">
222
- {searchTerm ? 'No resources match your search' : 'No resources available'}
223
- </div>
224
- ) : (
225
- <div className="divide-y divide-gray-200">
226
- {filteredResourceIds.map((resourceId: string) => (
227
- <button
228
- key={resourceId}
229
- onClick={() => handleResourceSelect(resourceId)}
230
- className={`w-full px-4 py-3 text-left hover:bg-gray-50 transition-colors ${
231
- selectedResourceId === resourceId ? 'bg-blue-50 border-r-2 border-blue-500' : ''
232
- }`}
233
- >
234
- <div className="font-medium text-gray-900 truncate">
235
- {searchTerm ? (
236
- <span
237
- dangerouslySetInnerHTML={{
238
- __html: resourceId.replace(
239
- new RegExp(`(${searchTerm})`, 'gi'),
240
- '<mark class="bg-yellow-200">$1</mark>'
241
- )
242
- }}
243
- />
244
- ) : (
245
- resourceId
246
- )}
247
- </div>
248
- </button>
249
- ))}
250
- </div>
251
- )}
252
- </div>
253
- </div>
254
-
255
- {/* Right side: Resource Details */}
256
- <div className="lg:w-1/2 flex flex-col">
257
- {selectedResourceId ? (
258
- <ResourceDetail
259
- resourceId={selectedResourceId}
260
- processedResources={resourceState.processedResources}
261
- onMessage={onMessage}
262
- />
263
- ) : (
264
- <div className="flex-1 flex items-center justify-center border border-gray-200 rounded-lg bg-gray-50">
265
- <div className="text-center">
266
- <DocumentTextIcon className="h-12 w-12 text-gray-400 mx-auto mb-4" />
267
- <p className="text-gray-500">Select a resource to view details</p>
268
- </div>
269
- </div>
270
- )}
271
- </div>
272
- </div>
273
- </div>
274
- </div>
275
- );
276
- };
277
-
278
- interface ResourceDetailProps {
279
- resourceId: string;
280
- processedResources: any;
281
- onMessage?: (type: Message['type'], message: string) => void;
282
- }
283
-
284
- interface ResourceDetailData {
285
- id: string;
286
- resourceType: string;
287
- candidateCount: number;
288
- candidates: Array<{
289
- json: any;
290
- conditions: Array<{
291
- qualifier: string;
292
- operator: string;
293
- value: string;
294
- priority: number;
295
- }>;
296
- isPartial: boolean;
297
- mergeMethod: string;
298
- }>;
299
- }
300
-
301
- const ResourceDetail: React.FC<ResourceDetailProps> = ({ resourceId, processedResources, onMessage }) => {
302
- const [resourceDetail, setResourceDetail] = useState<ResourceDetailData | null>(null);
303
- const [isLoading, setIsLoading] = useState(false);
304
- const [error, setError] = useState<string | null>(null);
305
-
306
- React.useEffect(() => {
307
- const loadResourceDetail = () => {
308
- setIsLoading(true);
309
- setError(null);
310
-
311
- try {
312
- const resourceManager = processedResources.system.resourceManager;
313
- const resourceResult = resourceManager.getBuiltResource(resourceId);
314
-
315
- if (resourceResult.isSuccess()) {
316
- const resource = resourceResult.value;
317
-
318
- const detail: ResourceDetailData = {
319
- id: resource.id,
320
- resourceType: resource.resourceType.key,
321
- candidateCount: resource.candidates.length,
322
- candidates: resource.candidates.map((candidate: any) => ({
323
- json: candidate.json,
324
- conditions: candidate.conditions.conditions.map((condition: any) => ({
325
- qualifier: condition.qualifier.name,
326
- operator: condition.operator,
327
- value: condition.value,
328
- priority: condition.priority,
329
- scoreAsDefault: condition.scoreAsDefault
330
- })),
331
- isPartial: candidate.isPartial,
332
- mergeMethod: candidate.mergeMethod
333
- }))
334
- };
335
-
336
- setResourceDetail(detail);
337
- onMessage?.('info', `Loaded details for resource: ${resourceId}`);
338
- } else {
339
- setError(`Failed to load resource details: ${resourceResult.message}`);
340
- onMessage?.('error', `Failed to load resource details: ${resourceResult.message}`);
341
- }
342
- } catch (err) {
343
- const errorMsg = `Error loading resource details: ${
344
- err instanceof Error ? err.message : String(err)
345
- }`;
346
- setError(errorMsg);
347
- onMessage?.('error', errorMsg);
348
- } finally {
349
- setIsLoading(false);
350
- }
351
- };
352
-
353
- loadResourceDetail();
354
- }, [resourceId, processedResources, onMessage]);
355
-
356
- if (isLoading) {
357
- return (
358
- <div className="flex flex-col h-full">
359
- <h3 className="text-lg font-semibold text-gray-900 mb-4">Resource Details</h3>
360
- <div className="flex-1 flex items-center justify-center border border-gray-200 rounded-lg bg-gray-50">
361
- <div className="text-center">
362
- <div className="animate-spin h-8 w-8 border-4 border-blue-600 border-t-transparent rounded-full mx-auto mb-4"></div>
363
- <p className="text-gray-500">Loading resource details...</p>
364
- </div>
365
- </div>
366
- </div>
367
- );
368
- }
369
-
370
- if (error) {
371
- return (
372
- <div className="flex flex-col h-full">
373
- <h3 className="text-lg font-semibold text-gray-900 mb-4">Resource Details</h3>
374
- <div className="flex-1 border border-gray-200 rounded-lg p-4 bg-red-50">
375
- <div className="text-center">
376
- <p className="text-red-600 font-medium mb-2">Error Loading Resource</p>
377
- <p className="text-red-500 text-sm">{error}</p>
378
- </div>
379
- </div>
380
- </div>
381
- );
382
- }
383
-
384
- if (!resourceDetail) {
385
- return (
386
- <div className="flex flex-col h-full">
387
- <h3 className="text-lg font-semibold text-gray-900 mb-4">Resource Details</h3>
388
- <div className="flex-1 flex items-center justify-center border border-gray-200 rounded-lg bg-gray-50">
389
- <p className="text-gray-500">No resource details available</p>
390
- </div>
391
- </div>
392
- );
393
- }
394
-
395
- return (
396
- <div className="flex flex-col h-full">
397
- <h3 className="text-lg font-semibold text-gray-900 mb-4">Resource Details</h3>
398
-
399
- <div className="flex-1 overflow-y-auto border border-gray-200 rounded-lg p-4 bg-gray-50">
400
- <div className="space-y-6">
401
- {/* Resource Overview */}
402
- <div>
403
- <h4 className="font-medium text-gray-700 mb-3">Resource Overview</h4>
404
- <div className="bg-white p-4 rounded-lg border space-y-3">
405
- <div>
406
- <span className="text-sm font-medium text-gray-600">Fully Qualified ID:</span>
407
- <code className="text-sm bg-gray-100 px-2 py-1 rounded ml-2 break-all">
408
- {resourceDetail.id}
409
- </code>
410
- </div>
411
- <div>
412
- <span className="text-sm font-medium text-gray-600">Resource Type:</span>
413
- <span className="ml-2 text-sm">{resourceDetail.resourceType}</span>
414
- </div>
415
- <div>
416
- <span className="text-sm font-medium text-gray-600">Candidate Count:</span>
417
- <span className="ml-2 text-sm font-medium text-blue-600">
418
- {resourceDetail.candidateCount}
419
- </span>
420
- </div>
421
- </div>
422
- </div>
423
-
424
- {/* Candidates */}
425
- <div>
426
- <h4 className="font-medium text-gray-700 mb-3">
427
- Candidates ({resourceDetail.candidates.length})
428
- </h4>
429
- <div className="space-y-4">
430
- {resourceDetail.candidates.map((candidate, index) => (
431
- <div key={index} className="bg-white p-4 rounded-lg border">
432
- <div className="flex items-center justify-between mb-3">
433
- <h5 className="font-medium text-gray-800">Candidate {index + 1}</h5>
434
- <div className="flex items-center space-x-2 text-xs">
435
- {candidate.isPartial && (
436
- <span className="bg-yellow-100 text-yellow-800 px-2 py-1 rounded">Partial</span>
437
- )}
438
- <span className="bg-gray-100 text-gray-700 px-2 py-1 rounded">
439
- {candidate.mergeMethod}
440
- </span>
441
- </div>
442
- </div>
443
-
444
- {/* Conditions */}
445
- {candidate.conditions.length > 0 && (
446
- <div className="mb-3">
447
- <h6 className="text-sm font-medium text-gray-600 mb-2">Conditions:</h6>
448
- <div className="space-y-1">
449
- {candidate.conditions.map((condition, condIndex) => (
450
- <div
451
- key={condIndex}
452
- className="flex items-center text-xs bg-blue-50 px-2 py-1 rounded"
453
- >
454
- <span className="font-medium text-blue-800">{condition.qualifier}</span>
455
- <span className="mx-1 text-blue-600">{condition.operator}</span>
456
- <span className="text-blue-700">{condition.value}</span>
457
- <div className="ml-auto flex items-center space-x-2">
458
- <span className="text-blue-500">priority: {condition.priority}</span>
459
- {condition.scoreAsDefault !== undefined && (
460
- <span className="text-amber-600 font-medium">
461
- default: {condition.scoreAsDefault}
462
- </span>
463
- )}
464
- </div>
465
- </div>
466
- ))}
467
- </div>
468
- </div>
469
- )}
470
-
471
- {candidate.conditions.length === 0 && (
472
- <div className="mb-3">
473
- <span className="text-xs text-gray-500 bg-gray-100 px-2 py-1 rounded">
474
- No conditions (default candidate)
475
- </span>
476
- </div>
477
- )}
478
-
479
- {/* JSON Content */}
480
- <div>
481
- <h6 className="text-sm font-medium text-gray-600 mb-2">Content:</h6>
482
- <pre className="text-xs bg-gray-50 p-3 rounded border overflow-x-auto max-h-40">
483
- {JSON.stringify(candidate.json, null, 2)}
484
- </pre>
485
- </div>
486
- </div>
487
- ))}
488
- </div>
489
- </div>
490
- </div>
491
- </div>
492
- </div>
493
- );
494
- };
495
-
496
- export default SourceBrowser;