@fgv/ts-res-ui-components 5.0.0-21 → 5.0.0-22
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +230 -35
- package/dist/ts-res-ui-components.d.ts +283 -19
- package/lib/components/orchestrator/ResourceOrchestrator.js +3 -1
- package/lib/components/pickers/ResourcePicker/ResourcePickerTree.js +29 -10
- package/lib/components/pickers/ResourcePicker/index.js +4 -2
- package/lib/components/views/ResolutionView/index.js +1 -1
- package/lib/hooks/useResolutionState.js +836 -235
- package/lib/namespaces/ResolutionTools.d.ts +2 -1
- package/lib/namespaces/ResolutionTools.js +2 -0
- package/lib/test/unit/workflows/resolutionWorkflows.test.d.ts +2 -0
- package/lib/test/unit/workflows/resourceCreation.test.d.ts +2 -0
- package/lib/test/unit/workflows/resultPatternExtensions.test.d.ts +2 -0
- package/lib/test/unit/workflows/validation.test.d.ts +2 -0
- package/lib/types/index.d.ts +124 -19
- package/lib/types/index.js +2 -1
- package/lib/utils/resolutionEditing.js +2 -1
- package/lib/utils/resourceSelectors.d.ts +146 -0
- package/lib/utils/resourceSelectors.js +233 -0
- package/lib-commonjs/components/orchestrator/ResourceOrchestrator.js +3 -1
- package/lib-commonjs/components/pickers/ResourcePicker/ResourcePickerTree.js +29 -10
- package/lib-commonjs/components/pickers/ResourcePicker/index.js +4 -2
- package/lib-commonjs/components/views/ResolutionView/index.js +1 -1
- package/lib-commonjs/hooks/useResolutionState.js +835 -234
- package/lib-commonjs/namespaces/ResolutionTools.js +10 -1
- package/lib-commonjs/types/index.js +10 -0
- package/lib-commonjs/utils/resolutionEditing.js +2 -1
- package/lib-commonjs/utils/resourceSelectors.js +242 -0
- package/package.json +7 -7
- package/src/components/orchestrator/ResourceOrchestrator.tsx +3 -1
- package/src/components/pickers/ResourcePicker/ResourcePickerTree.tsx +30 -10
- package/src/components/pickers/ResourcePicker/index.tsx +7 -2
- package/src/components/views/ResolutionView/index.tsx +1 -1
- package/src/hooks/useResolutionState.ts +1054 -257
- package/src/namespaces/ResolutionTools.ts +13 -1
- package/src/types/index.ts +131 -20
- package/src/utils/resolutionEditing.ts +2 -2
- package/src/utils/resourceSelectors.ts +278 -0
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @fgv/ts-res-ui-components
|
|
2
2
|
|
|
3
|
-
React components for building user interfaces that work with the [ts-res](https://github.com/fgv/ts-res) multidimensional resource management library.
|
|
3
|
+
React components for building user interfaces that work with the [ts-res](https://github.com/ErikFortune/fgv/tree/main/libraries/ts-res) multidimensional resource management library.
|
|
4
4
|
|
|
5
5
|
## Overview
|
|
6
6
|
|
|
@@ -45,16 +45,69 @@ This library requires the following peer dependencies:
|
|
|
45
45
|
|
|
46
46
|
```json
|
|
47
47
|
{
|
|
48
|
-
"@fgv/ts-res": "
|
|
49
|
-
"@fgv/ts-utils": "
|
|
50
|
-
"@fgv/ts-json-base": "
|
|
51
|
-
"
|
|
52
|
-
"react
|
|
48
|
+
"@fgv/ts-res": ">=5.0.0",
|
|
49
|
+
"@fgv/ts-utils": ">=5.0.0",
|
|
50
|
+
"@fgv/ts-json-base": ">=5.0.0",
|
|
51
|
+
"@fgv/ts-json": ">=5.0.0",
|
|
52
|
+
"react": ">=18.0.0",
|
|
53
|
+
"react-dom": ">=18.0.0"
|
|
53
54
|
}
|
|
54
55
|
```
|
|
55
56
|
|
|
56
57
|
## Quick Start
|
|
57
58
|
|
|
59
|
+
### Canonical New Resource Flow
|
|
60
|
+
|
|
61
|
+
The recommended approach for programmatic resource creation:
|
|
62
|
+
|
|
63
|
+
```tsx
|
|
64
|
+
import { ResolutionTools } from '@fgv/ts-res-ui-components';
|
|
65
|
+
|
|
66
|
+
// Single atomic operation (recommended)
|
|
67
|
+
async function createResourceAtomic(actions) {
|
|
68
|
+
const result = await actions.createPendingResource({
|
|
69
|
+
id: 'platform.languages.az-AZ',
|
|
70
|
+
resourceTypeName: 'json',
|
|
71
|
+
json: { text: 'Welcome', locale: 'az-AZ' }
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
if (result.isSuccess()) {
|
|
75
|
+
console.log('Resource created successfully');
|
|
76
|
+
await actions.applyPendingResources();
|
|
77
|
+
} else {
|
|
78
|
+
console.error('Creation failed:', result.message);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Step-by-step workflow (if needed)
|
|
83
|
+
function createResourceStepByStep(actions) {
|
|
84
|
+
// 1) Start new resource
|
|
85
|
+
const startResult = actions.startNewResource({ defaultTypeName: 'json' });
|
|
86
|
+
if (!startResult.success) return;
|
|
87
|
+
|
|
88
|
+
// 2) Update resource ID
|
|
89
|
+
const idResult = actions.updateNewResourceId('platform.languages.az-AZ');
|
|
90
|
+
if (!idResult.success) return;
|
|
91
|
+
|
|
92
|
+
// 3) Select resource type (if step 1 didn't set it)
|
|
93
|
+
const typeResult = actions.selectResourceType('json');
|
|
94
|
+
if (!typeResult.success) return;
|
|
95
|
+
|
|
96
|
+
// 4) Update JSON content (optional, recommended)
|
|
97
|
+
const jsonResult = actions.updateNewResourceJson({
|
|
98
|
+
text: 'Welcome',
|
|
99
|
+
locale: 'az-AZ'
|
|
100
|
+
});
|
|
101
|
+
if (!jsonResult.success) return;
|
|
102
|
+
|
|
103
|
+
// 5) Save as pending
|
|
104
|
+
const saveResult = actions.saveNewResourceAsPending();
|
|
105
|
+
if (!saveResult.success) return;
|
|
106
|
+
|
|
107
|
+
// Note: Do NOT call saveResourceEdit for brand-new resources
|
|
108
|
+
}
|
|
109
|
+
```
|
|
110
|
+
|
|
58
111
|
### Minimal Editing App (Unified Apply)
|
|
59
112
|
|
|
60
113
|
```tsx
|
|
@@ -79,9 +132,12 @@ export default function App() {
|
|
|
79
132
|
hasEdit: actions.hasResourceEdit,
|
|
80
133
|
clearEdits: actions.clearResourceEdits,
|
|
81
134
|
discardEdits: actions.discardResourceEdits,
|
|
135
|
+
// Enhanced resource creation actions with atomic API
|
|
136
|
+
createPendingResource: actions.createPendingResource,
|
|
82
137
|
startNewResource: actions.startNewResource,
|
|
83
138
|
updateNewResourceId: actions.updateNewResourceId,
|
|
84
139
|
selectResourceType: actions.selectResourceType,
|
|
140
|
+
updateNewResourceJson: actions.updateNewResourceJson,
|
|
85
141
|
saveNewResourceAsPending: actions.saveNewResourceAsPending,
|
|
86
142
|
cancelNewResource: actions.cancelNewResource,
|
|
87
143
|
removePendingResource: actions.removePendingResource,
|
|
@@ -515,47 +571,128 @@ This is particularly useful when:
|
|
|
515
571
|
|
|
516
572
|
#### Resource Creation
|
|
517
573
|
|
|
518
|
-
ResolutionView
|
|
574
|
+
ResolutionView supports both atomic and sequential resource creation workflows:
|
|
575
|
+
|
|
576
|
+
##### Atomic Resource Creation (Recommended)
|
|
577
|
+
|
|
578
|
+
Use the new `createPendingResource` API for simple, robust resource creation:
|
|
579
|
+
|
|
580
|
+
```tsx
|
|
581
|
+
// Single atomic call to create a pending resource
|
|
582
|
+
const result = await actions.createPendingResource({
|
|
583
|
+
id: 'platform.languages.az-AZ', // Full resource ID (required)
|
|
584
|
+
resourceTypeName: 'json', // Resource type (required)
|
|
585
|
+
json: { text: 'Welcome', locale: 'az-AZ' } // JSON content (optional - uses base template if omitted)
|
|
586
|
+
});
|
|
587
|
+
|
|
588
|
+
if (result.isSuccess()) {
|
|
589
|
+
console.log('Resource created and added to pending resources');
|
|
590
|
+
// Apply all pending changes when ready
|
|
591
|
+
await actions.applyPendingResources();
|
|
592
|
+
} else {
|
|
593
|
+
console.error('Failed to create resource:', result.message);
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
// Example: Creating a resource with base template (no JSON content)
|
|
597
|
+
const baseTemplateResult = await actions.createPendingResource({
|
|
598
|
+
id: 'platform.languages.fr-FR',
|
|
599
|
+
resourceTypeName: 'json'
|
|
600
|
+
// json omitted - resource type will provide base template (typically {})
|
|
601
|
+
});
|
|
602
|
+
|
|
603
|
+
if (baseTemplateResult.isSuccess()) {
|
|
604
|
+
console.log('Resource created using base template from resource type');
|
|
605
|
+
// You can then edit the resource using the UI or saveEdit()
|
|
606
|
+
}
|
|
607
|
+
```
|
|
608
|
+
|
|
609
|
+
**Benefits of the atomic API:**
|
|
610
|
+
- **Single Operation**: No multi-step sequencing required
|
|
611
|
+
- **Better Error Handling**: Returns `Result<void>` with detailed error messages
|
|
612
|
+
- **Validation**: Comprehensive validation of ID, type, and JSON content
|
|
613
|
+
- **Safe**: Prevents temporary IDs and ensures uniqueness
|
|
614
|
+
- **Context Stamping**: Automatically applies current context as conditions
|
|
615
|
+
|
|
616
|
+
##### Sequential Resource Creation (Legacy)
|
|
617
|
+
|
|
618
|
+
The traditional step-by-step workflow is still available:
|
|
619
|
+
|
|
620
|
+
```tsx
|
|
621
|
+
// Step 1: Start a new resource
|
|
622
|
+
const startResult = actions.startNewResource({ defaultTypeName: 'json' });
|
|
623
|
+
if (startResult.isFailure()) {
|
|
624
|
+
console.error('Failed to start resource:', startResult.message);
|
|
625
|
+
return;
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
// Step 2: Set the final resource ID
|
|
629
|
+
const idResult = actions.updateNewResourceId('platform.languages.az-AZ');
|
|
630
|
+
if (idResult.isFailure()) {
|
|
631
|
+
console.error('Invalid resource ID:', idResult.message);
|
|
632
|
+
return;
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
// Step 3: Update JSON content (optional)
|
|
636
|
+
const jsonResult = actions.updateNewResourceJson({
|
|
637
|
+
text: 'Welcome',
|
|
638
|
+
locale: 'az-AZ'
|
|
639
|
+
});
|
|
640
|
+
if (jsonResult.isFailure()) {
|
|
641
|
+
console.error('Invalid JSON:', jsonResult.message);
|
|
642
|
+
return;
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
// Step 4: Save as pending
|
|
646
|
+
const saveResult = actions.saveNewResourceAsPending();
|
|
647
|
+
if (saveResult.isFailure()) {
|
|
648
|
+
console.error('Failed to save:', saveResult.message);
|
|
649
|
+
return;
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
// Step 5: Apply all pending changes
|
|
653
|
+
await actions.applyPendingResources();
|
|
654
|
+
```
|
|
655
|
+
|
|
656
|
+
**Enhanced Action Return Values:**
|
|
657
|
+
|
|
658
|
+
All resource creation actions now return `Result<T>` objects following the standard Result pattern:
|
|
659
|
+
- Use `.isSuccess()` and `.isFailure()` to check status
|
|
660
|
+
- Access success values with `.value` property (contains `draft`/`pendingResources` and `diagnostics`)
|
|
661
|
+
- Access error messages with `.message` property
|
|
662
|
+
- Multiline error messages include diagnostic information
|
|
663
|
+
|
|
664
|
+
```tsx
|
|
665
|
+
// Example: Working with Result values
|
|
666
|
+
const startResult = actions.startNewResource({ defaultTypeName: 'json' });
|
|
667
|
+
if (startResult.isSuccess()) {
|
|
668
|
+
console.log('Started draft:', startResult.value.draft);
|
|
669
|
+
console.log('Diagnostics:', startResult.value.diagnostics);
|
|
670
|
+
} else {
|
|
671
|
+
console.error('Error:', startResult.message); // May include diagnostics
|
|
672
|
+
}
|
|
673
|
+
```
|
|
674
|
+
|
|
675
|
+
##### UI Integration Example
|
|
519
676
|
|
|
520
677
|
```tsx
|
|
521
|
-
// Basic resource creation - user selects resource type
|
|
522
678
|
<ResolutionView
|
|
523
679
|
resources={state.processedResources}
|
|
524
680
|
resolutionState={resolutionState}
|
|
525
681
|
resolutionActions={resolutionActions}
|
|
526
682
|
allowResourceCreation={true}
|
|
683
|
+
defaultResourceType="json" // Optional: Host-controlled resource type
|
|
684
|
+
showPendingResourcesInList={true} // Show pending resources with visual distinction
|
|
527
685
|
onPendingResourcesApplied={(added, deleted) => {
|
|
686
|
+
console.log(`Applied ${added.length} additions, ${deleted.length} deletions`);
|
|
528
687
|
// Rebuild resource manager with new resources
|
|
529
688
|
const updatedResources = rebuildWithResources(added, deleted);
|
|
530
689
|
setState({ processedResources: updatedResources });
|
|
531
690
|
}}
|
|
532
691
|
/>
|
|
533
|
-
|
|
534
|
-
// Host-controlled resource type
|
|
535
|
-
<ResolutionView
|
|
536
|
-
resources={state.processedResources}
|
|
537
|
-
resolutionState={resolutionState}
|
|
538
|
-
resolutionActions={resolutionActions}
|
|
539
|
-
allowResourceCreation={true}
|
|
540
|
-
defaultResourceType="json" // Type selector hidden, always creates JSON resources
|
|
541
|
-
onPendingResourcesApplied={(added, deleted) => {
|
|
542
|
-
console.log(`Applied ${added.length} additions, ${deleted.length} deletions`);
|
|
543
|
-
}}
|
|
544
|
-
/>
|
|
545
|
-
|
|
546
|
-
// With custom resource types
|
|
547
|
-
<ResolutionView
|
|
548
|
-
resources={state.processedResources}
|
|
549
|
-
resolutionState={resolutionState}
|
|
550
|
-
resolutionActions={resolutionActions}
|
|
551
|
-
allowResourceCreation={true}
|
|
552
|
-
resourceTypeFactory={[customType1, customType2]} // Provide custom resource types
|
|
553
|
-
showPendingResourcesInList={true} // Show pending resources with visual distinction
|
|
554
|
-
/>
|
|
555
692
|
```
|
|
556
693
|
|
|
557
694
|
**Unified Change Workflow (Edits, Additions, Deletions):**
|
|
558
|
-
1.
|
|
695
|
+
1. Use `createPendingResource()` or the traditional UI workflow to create resources
|
|
559
696
|
2. Edit existing resources from the results pane using the JSON or custom editors
|
|
560
697
|
3. Mark resources for deletion where supported
|
|
561
698
|
4. All changes appear in a single "Pending Changes" bar with counts (edits/additions/deletions)
|
|
@@ -563,12 +700,70 @@ ResolutionView now supports creating new resources directly in the UI with a pen
|
|
|
563
700
|
6. Or click "Discard Changes" to remove all pending changes
|
|
564
701
|
|
|
565
702
|
**Key features:**
|
|
703
|
+
- **Atomic Creation**: Single API call creates complete resources
|
|
704
|
+
- **Enhanced Error Handling**: Detailed validation and error messages
|
|
566
705
|
- **Template-based creation**: Resource types provide default templates for new resources
|
|
567
706
|
- **Pending workflow**: Resources aren't added immediately, allowing review before applying
|
|
568
707
|
- **Host control**: Can specify a default resource type to hide the type selector
|
|
569
708
|
- **Batch operations**: Create multiple resources before applying
|
|
570
709
|
- **Visual feedback**: Pending resources shown with distinct styling
|
|
571
|
-
- **Validation**:
|
|
710
|
+
- **ID Validation**: Comprehensive resource ID validation and uniqueness checking
|
|
711
|
+
|
|
712
|
+
##### Resource ID Requirements
|
|
713
|
+
|
|
714
|
+
**IMPORTANT**: All pending resources use **full resource IDs** as keys, never leaf IDs:
|
|
715
|
+
|
|
716
|
+
```tsx
|
|
717
|
+
// ✅ Correct: Full resource IDs
|
|
718
|
+
createPendingResource({
|
|
719
|
+
id: 'platform.languages.az-AZ', // Full path
|
|
720
|
+
resourceTypeName: 'json',
|
|
721
|
+
json: { text: 'Azərbaycan dili' }
|
|
722
|
+
});
|
|
723
|
+
|
|
724
|
+
// ❌ Wrong: Leaf IDs
|
|
725
|
+
createPendingResource({
|
|
726
|
+
id: 'az-AZ', // Just the leaf - will be rejected
|
|
727
|
+
resourceTypeName: 'json',
|
|
728
|
+
json: { text: 'Azərbaycan dili' }
|
|
729
|
+
});
|
|
730
|
+
```
|
|
731
|
+
|
|
732
|
+
##### Helper Functions for Pending Resources
|
|
733
|
+
|
|
734
|
+
```tsx
|
|
735
|
+
import { ResolutionTools } from '@fgv/ts-res-ui-components';
|
|
736
|
+
|
|
737
|
+
// Get pending resources by type
|
|
738
|
+
const jsonResources = ResolutionTools.getPendingAdditionsByType(
|
|
739
|
+
resolutionState.pendingResources,
|
|
740
|
+
'json'
|
|
741
|
+
);
|
|
742
|
+
|
|
743
|
+
// Check if a resource is pending
|
|
744
|
+
const isPending = ResolutionTools.isPendingAddition(
|
|
745
|
+
'platform.languages.az-AZ',
|
|
746
|
+
resolutionState.pendingResources
|
|
747
|
+
);
|
|
748
|
+
|
|
749
|
+
// Work with resource IDs
|
|
750
|
+
const leafIdResult = ResolutionTools.deriveLeafId('platform.languages.az-AZ');
|
|
751
|
+
console.log(leafIdResult.value); // 'az-AZ'
|
|
752
|
+
|
|
753
|
+
const fullIdResult = ResolutionTools.deriveFullId('platform.languages', 'az-AZ');
|
|
754
|
+
console.log(fullIdResult.value); // 'platform.languages.az-AZ'
|
|
755
|
+
|
|
756
|
+
// Get statistics
|
|
757
|
+
const stats = ResolutionTools.getPendingResourceStats(resolutionState.pendingResources);
|
|
758
|
+
console.log(`${stats.totalCount} pending resources`);
|
|
759
|
+
console.log(`Types: ${Object.keys(stats.byType).join(', ')}`);
|
|
760
|
+
|
|
761
|
+
// Validate pending resource keys (diagnostic)
|
|
762
|
+
const validation = ResolutionTools.validatePendingResourceKeys(resolutionState.pendingResources);
|
|
763
|
+
if (validation.isFailure()) {
|
|
764
|
+
console.error('Key validation failed:', validation.message);
|
|
765
|
+
}
|
|
766
|
+
```
|
|
572
767
|
|
|
573
768
|
#### Custom Resource Editors
|
|
574
769
|
|
|
@@ -1596,6 +1791,6 @@ The API documentation includes detailed examples, usage patterns, and type infor
|
|
|
1596
1791
|
For questions and support, please:
|
|
1597
1792
|
|
|
1598
1793
|
1. Check the [API documentation](./docs/index.md) for detailed component usage
|
|
1599
|
-
2. Review the [ts-res documentation](https://
|
|
1600
|
-
3. Search [existing issues](https://github.com/fgv/
|
|
1601
|
-
4. Create a [new issue](https://github.com/fgv/
|
|
1794
|
+
2. Review the [ts-res documentation](https://github.com/ErikFortune/fgv/tree/main/libraries/ts-res) for core concepts
|
|
1795
|
+
3. Search [existing issues](https://github.com/ErikFortune/fgv/issues)
|
|
1796
|
+
4. Create a [new issue](https://github.com/ErikFortune/fgv/issues/new)
|