
Research
Two Malicious Rust Crates Impersonate Popular Logger to Steal Wallet Keys
Socket uncovers malicious Rust crates impersonating fast_log to steal Solana and Ethereum wallet keys from source code.
@fgv/ts-res-ui-components
Advanced tools
Reusable React components for ts-res resource visualization and management
React components for building user interfaces that work with the ts-res multidimensional resource management library.
This library provides a complete set of React components, hooks, and utilities for creating applications that visualize, manage, and interact with ts-res resource systems. It supports the full workflow from importing configurations to resolving resources with dynamic context.
This packlet is largely AI written, and it shows.
npm install @fgv/ts-res-ui-components
This library requires the following peer dependencies:
{
"@fgv/ts-res": ">=5.0.0",
"@fgv/ts-utils": ">=5.0.0",
"@fgv/ts-json-base": ">=5.0.0",
"@fgv/ts-json": ">=5.0.0",
"react": ">=18.0.0",
"react-dom": ">=18.0.0"
}
import React from 'react';
import { ResourceOrchestrator, ObservabilityProvider, ObservabilityTools } from '@fgv/ts-res-ui-components';
function App() {
// Set up observability context for development
const observabilityContext = ObservabilityTools.createConsoleObservabilityContext('info', 'info');
return (
<ObservabilityProvider observabilityContext={observabilityContext}>
<ResourceOrchestrator />
</ObservabilityProvider>
);
}
The ResourceOrchestrator
provides a complete resource management interface with import, filtering, resolution, and editing capabilities. The ObservabilityProvider
enables built-in logging and debugging features throughout the component tree.
The central component that coordinates all resource operations. It manages the complete workflow:
The library is organized into specialized namespaces, each containing related components and utilities:
All operations include comprehensive logging for debugging and user feedback through the enhanced observability context system:
useSmartObservability()
hook automatically detects and connects to observability contextso11y.user.*
for user-facing messages, o11y.diag.*
for diagnostic informationFor Component Developers:
// Add observability to any component
import { useSmartObservability } from '@fgv/ts-res-ui-components';
function MyComponent() {
const o11y = useSmartObservability();
o11y.user.info('User message'); // Shows in UI
o11y.diag.info('Debug message'); // Shows in console/logs
}
For Host Applications:
// Provide observability context
import { ObservabilityProvider, ObservabilityTools } from '@fgv/ts-res-ui-components';
<ObservabilityProvider observabilityContext={myContext}>
<MyComponents />
</ObservabilityProvider>
import { ObservabilityProvider, ObservabilityTools } from '@fgv/ts-res-ui-components';
// Development: Console logging
const devContext = ObservabilityTools.createConsoleObservabilityContext('debug', 'info');
// Production: Silent operation
const prodContext = ObservabilityTools.createNoOpObservabilityContext();
// With ViewState integration (recommended)
function App() {
const { viewState } = useViewState();
const observabilityContext = ObservabilityTools.createViewStateObservabilityContext(
viewState.addMessage
);
return (
<ObservabilityProvider observabilityContext={observabilityContext}>
<YourComponents />
</ObservabilityProvider>
);
}
// Simple development setup
<ObservabilityProvider observabilityContext={devContext}>
<ResourceOrchestrator />
</ObservabilityProvider>
When creating new components, opt into observability with the useSmartObservability()
hook:
import { useSmartObservability } from '@fgv/ts-res-ui-components';
function MyCustomComponent({ data }: { data: any[] }) {
const o11y = useSmartObservability();
const handleOperation = async () => {
try {
o11y.user.info('Starting data processing...');
o11y.diag.info('Processing items:', data.length);
const result = await processData(data);
o11y.user.success(`Successfully processed ${result.length} items`);
o11y.diag.info('Processing result:', result);
return result;
} catch (error) {
o11y.user.error(`Processing failed: ${error.message}`);
o11y.diag.error('Full error details:', error);
throw error;
}
};
return (
<div>
<button onClick={handleOperation}>Process Data</button>
{/* Component automatically sends messages to observability context */}
</div>
);
}
Key Guidelines:
o11y.user.*
for messages users should see (success, error, warnings)o11y.diag.*
for developer/debugging informationHosts can provide their own observability implementation for complete control over logging:
import { IObservabilityContext, ObservabilityProvider } from '@fgv/ts-res-ui-components';
// Create custom observability context
const customObservabilityContext: IObservabilityContext = {
user: {
info: (message: string) => myNotificationSystem.showInfo(message),
success: (message: string) => myNotificationSystem.showSuccess(message),
warn: (message: string) => myNotificationSystem.showWarning(message),
error: (message: string) => myNotificationSystem.showError(message)
},
diag: {
info: (message: string, ...args: any[]) => myLogger.info(message, ...args),
warn: (message: string, ...args: any[]) => myLogger.warn(message, ...args),
error: (message: string, ...args: any[]) => myLogger.error(message, ...args)
}
};
// Use your custom context
function App() {
return (
<ObservabilityProvider observabilityContext={customObservabilityContext}>
<MyComponents />
</ObservabilityProvider>
);
}
Built-in Context Factories:
// Console logging for development
const devContext = ObservabilityTools.createConsoleObservabilityContext('debug', 'info');
// Silent operation for production
const prodContext = ObservabilityTools.createNoOpObservabilityContext();
// Integration with external message system
const customContext = ObservabilityTools.createViewStateObservabilityContext(
(type, message) => myMessageHandler(type, message)
);
Conditional Observability in Libraries:
// For library components that may or may not have observability
function MyLibraryComponent() {
const o11y = useSmartObservability();
const handleCriticalOperation = () => {
// This will work whether observability context exists or not
o11y.user.info('Operation started');
try {
const result = performOperation();
o11y.user.success('Operation completed');
return result;
} catch (error) {
// Always log errors, even without observability context
o11y.user.error('Operation failed');
o11y.diag.error('Details:', error);
throw error;
}
};
}
Multi-Level Observability (Host + ViewState):
function App() {
// Host-level notifications for critical errors
const hostObservability: IObservabilityContext = {
user: {
error: (msg) => showCriticalAlert(msg),
// Other messages go to ViewState
info: (msg) => viewState.addMessage('info', msg),
success: (msg) => viewState.addMessage('success', msg),
warn: (msg) => viewState.addMessage('warning', msg)
},
diag: {
error: (msg, ...args) => console.error(msg, ...args),
warn: (msg, ...args) => console.warn(msg, ...args),
info: (msg, ...args) => console.info(msg, ...args)
}
};
return (
<ObservabilityProvider observabilityContext={hostObservability}>
<MyApplication />
</ObservabilityProvider>
);
}
Environment-Specific Contexts:
const observabilityContext = process.env.NODE_ENV === 'production'
? ObservabilityTools.createNoOpObservabilityContext()
: ObservabilityTools.createConsoleObservabilityContext('debug', 'info');
ResourceOrchestrator (state management)
├── ImportView (file/bundle/ZIP import)
├── SourceView (resource collection display)
├── FilterView (context filtering)
├── CompiledView (compiled resource structure)
├── ResolutionView (resource resolution testing)
└── ConfigurationView (system configuration)
ConfigurationView
ImportView
or programmatically
ProcessedResources
object containing:
ResourceManagerBuilder
for build-time operationsCompiledResourceCollection
for runtime efficiencyResourceResolver
for resource resolutionFilterView
SourceView
and CompiledView
ResolutionView
The main orchestration component that manages all state and provides actions via render props.
<ResourceOrchestrator
initialConfiguration={myConfig}
onStateChange={(state) => console.log('State changed:', state)}
>
{({ state, actions }) => (
// Your UI components
)}
</ResourceOrchestrator>
Visual configuration management for ts-res system settings including qualifier types, qualifiers, and resource types.
<ObservabilityProvider observabilityContext={myObservabilityContext}>
<ConfigurationView
configuration={state.activeConfiguration}
onConfigurationChange={actions.applyConfiguration}
onSave={actions.saveConfiguration}
hasUnsavedChanges={state.hasConfigurationChanges}
/>
</ObservabilityProvider>
Key features:
Handles importing resource files, directories, bundles, and ZIP files. Uses the ts-res zip-archive packlet for all ZIP operations.
<ObservabilityProvider observabilityContext={myObservabilityContext}>
<ImportView
onImport={actions.importDirectory}
onBundleImport={actions.importBundle}
onZipImport={(zipData, config) => {
// zipData contains files and directory structure from ZIP
// config contains any configuration found in the ZIP
actions.importDirectory(zipData, config);
}}
acceptedFileTypes={['.json', '.ts', '.js']}
/>
</ObservabilityProvider>
Core component for browsing and selecting resources with advanced features like search, annotations, and pending resource support. The resource picker is a generic component used by all of the views, which can also be used to power other application-specific views.
<ResourcePicker
resources={state.processedResources}
selectedResourceId={selectedId}
onResourceSelect={(selection) => {
setSelectedId(selection.resourceId);
// Access resource data directly without additional lookups
if (selection.resourceData) {
handleResourceData(selection.resourceData);
}
// Handle pending resources
if (selection.isPending) {
console.log(`Pending ${selection.pendingType} operation`);
}
}}
options={{
defaultView: "tree",
enableSearch: true,
searchPlaceholder: "Search resources...",
height: "500px"
}}
resourceAnnotations={{
'user.welcome': {
badge: { text: '3', variant: 'info' },
suffix: '(3 candidates)'
}
}}
pendingResources={pendingChanges}
/>
Key features:
A debugging/design tool for interactively configuring ResourcePicker behavior. Hidden by default for production use, but can be enabled in development:
// All view components support pickerOptionsPresentation
<ObservabilityProvider observabilityContext={myObservabilityContext}>
<SourceView
resources={state.processedResources}
pickerOptionsPresentation="collapsible" // Enable picker options UI
/>
</ObservabilityProvider>
// Direct usage in custom components
<PickerTools.ResourcePickerOptionsControl
options={pickerOptions}
onOptionsChange={setPickerOptions}
presentation="popup" // 'hidden' | 'inline' | 'collapsible' | 'popup' | 'popover'
title="Picker Configuration"
showAdvanced={true}
/>
Presentation modes:
'hidden'
: Not displayed (default for production)'inline'
: Always visible with expanded controls'collapsible'
: Expandable/collapsible section'popup'
: Full modal dialog overlay'popover'
: Small dropdown overlayDisplays the source resource collection with search and navigation capabilities using the enhanced ResourcePicker.
<ObservabilityProvider observabilityContext={myObservabilityContext}>
<SourceView
resources={state.processedResources}
onExport={actions.exportData}
pickerOptions={{
defaultView: "list",
enableSearch: true,
searchPlaceholder: "Search resources..."
}}
/>
</ObservabilityProvider>
Provides filtering capabilities with context value specification and dual-resource comparison.
<ObservabilityProvider observabilityContext={myObservabilityContext}>
<FilterView
resources={state.processedResources}
filterState={filterState}
filterActions={filterActions}
filterResult={filterResult}
onFilterResult={setFilterResult}
pickerOptions={{
enableSearch: true,
searchPlaceholder: "Search resources..."
}}
/>
</ObservabilityProvider>
Shows the compiled resource structure with detailed candidate information using the enhanced ResourcePicker.
<ObservabilityProvider observabilityContext={myObservabilityContext}>
<CompiledView
resources={state.processedResources}
filterResult={filterResult}
useNormalization={true}
onExport={(data, type) => exportData(data, type)}
pickerOptions={{
defaultView: "tree",
enableSearch: true
}}
/>
</ObservabilityProvider>
Interactive resource resolution testing with context management and support for custom resource editors via the ResourceEditorFactory pattern. Supports locking to a single view mode to simplify the interface for specific use cases. Now includes host-controlled resolution support for programmatic qualifier value management and the ability to create new resources with a pending/apply workflow.
<ObservabilityProvider observabilityContext={myObservabilityContext}>
<ResolutionView
resources={state.processedResources}
resolutionState={resolutionState}
resolutionActions={resolutionActions}
availableQualifiers={availableQualifiers}
resourceEditorFactory={myResourceEditorFactory}
pickerOptions={{
defaultView: "list",
enableSearch: true,
searchPlaceholder: "Search resources for resolution testing..."
}}
// Optional: Host-controlled resolution
contextOptions={{
hostManagedValues: {
language: 'en-US',
platform: 'web',
market: 'eastern-europe'
},
showContextControls: true // Can hide controls for host-only resolution
}}
// Optional: Resource creation with pending/apply workflow
allowResourceCreation={true}
defaultResourceType="json" // Optional: Host-controlled resource type
showPendingResourcesInList={true} // Show pending resources in the picker (default: true)
onPendingResourcesApplied={(added, deleted) => {
console.log(`Applied ${added.length} new resources, ${deleted.length} deletions`);
}}
/>
</ObservabilityProvider>
The ResolutionView now supports a three-layer context system that allows host applications to programmatically control qualifier values while still allowing user interaction:
This is particularly useful when:
// Example: Host-controlled resolution with hidden UI
<ResolutionView
resources={state.processedResources}
resolutionState={resolutionState}
resolutionActions={resolutionActions}
availableQualifiers={['language', 'platform', 'market']}
contextOptions={{
hostManagedValues: {
language: userProfile.language,
platform: detectPlatform(),
market: userProfile.region
},
showContextControls: false, // Hide the context controls
qualifierOptions: {
language: { visible: false }, // Hide specific qualifiers
platform: { visible: false }
}
}}
/>
// Example: Mixed host and user control
<ResolutionView
resources={state.processedResources}
resolutionState={resolutionState}
resolutionActions={resolutionActions}
availableQualifiers={availableQualifiers}
contextOptions={{
hostManagedValues: {
platform: 'web' // Platform is always controlled by host
},
qualifierOptions: {
platform: {
visible: true,
hostValue: 'web', // Shows as read-only in UI
disabled: true
}
}
}}
/>
Key features of host-controlled resolution:
ResolutionView supports both atomic and sequential resource creation workflows:
Use the new createPendingResource
API for simple, robust resource creation:
// Single atomic call to create a pending resource
const result = await actions.createPendingResource({
id: 'platform.languages.az-AZ', // Full resource ID (required)
resourceTypeName: 'json', // Resource type (required)
json: { text: 'Welcome', locale: 'az-AZ' } // JSON content (optional - uses base template if omitted)
});
if (result.isSuccess()) {
console.log('Resource created and added to pending resources');
// Apply all pending changes when ready
await actions.applyPendingResources();
} else {
console.error('Failed to create resource:', result.message);
}
// Example: Creating a resource with base template (no JSON content)
const baseTemplateResult = await actions.createPendingResource({
id: 'platform.languages.fr-FR',
resourceTypeName: 'json'
// json omitted - resource type will provide base template (typically {})
});
if (baseTemplateResult.isSuccess()) {
console.log('Resource created using base template from resource type');
// You can then edit the resource using the UI or saveEdit()
}
Benefits of the atomic API:
Result<void>
with detailed error messagesThe traditional step-by-step workflow is still available:
// Step 1: Start a new resource
const startResult = actions.startNewResource({ defaultTypeName: 'json' });
if (startResult.isFailure()) {
console.error('Failed to start resource:', startResult.message);
return;
}
// Step 2: Set the final resource ID
const idResult = actions.updateNewResourceId('platform.languages.az-AZ');
if (idResult.isFailure()) {
console.error('Invalid resource ID:', idResult.message);
return;
}
// Step 3: Update JSON content (optional)
const jsonResult = actions.updateNewResourceJson({
text: 'Welcome',
locale: 'az-AZ'
});
if (jsonResult.isFailure()) {
console.error('Invalid JSON:', jsonResult.message);
return;
}
// Step 4: Save as pending
const saveResult = actions.saveNewResourceAsPending();
if (saveResult.isFailure()) {
console.error('Failed to save:', saveResult.message);
return;
}
// Step 5: Apply all pending changes
await actions.applyPendingResources();
Enhanced Action Return Values:
All resource creation actions now return Result<T>
objects following the standard Result pattern:
.isSuccess()
and .isFailure()
to check status.value
property (contains draft
/pendingResources
and diagnostics
).message
property// Example: Working with Result values
const startResult = actions.startNewResource({ defaultTypeName: 'json' });
if (startResult.isSuccess()) {
console.log('Started draft:', startResult.value.draft);
console.log('Diagnostics:', startResult.value.diagnostics);
} else {
console.error('Error:', startResult.message); // May include diagnostics
}
<ResolutionView
resources={state.processedResources}
resolutionState={resolutionState}
resolutionActions={resolutionActions}
allowResourceCreation={true}
defaultResourceType="json" // Optional: Host-controlled resource type
showPendingResourcesInList={true} // Show pending resources with visual distinction
onPendingResourcesApplied={(added, deleted) => {
console.log(`Applied ${added.length} additions, ${deleted.length} deletions`);
// Rebuild resource manager with new resources
const updatedResources = rebuildWithResources(added, deleted);
setState({ processedResources: updatedResources });
}}
/>
Unified Change Workflow (Edits, Additions, Deletions):
createPendingResource()
or the traditional UI workflow to create resourcesKey features:
IMPORTANT: All pending resources use full resource IDs as keys, never leaf IDs:
// ✅ Correct: Full resource IDs
createPendingResource({
id: 'platform.languages.az-AZ', // Full path
resourceTypeName: 'json',
json: { text: 'Azərbaycan dili' }
});
// ❌ Wrong: Leaf IDs
createPendingResource({
id: 'az-AZ', // Just the leaf - will be rejected
resourceTypeName: 'json',
json: { text: 'Azərbaycan dili' }
});
import { ResolutionTools } from '@fgv/ts-res-ui-components';
// Get pending resources by type
const jsonResources = ResolutionTools.getPendingAdditionsByType(
resolutionState.pendingResources,
'json'
);
// Check if a resource is pending
const isPending = ResolutionTools.isPendingAddition(
'platform.languages.az-AZ',
resolutionState.pendingResources
);
// Work with resource IDs
const leafIdResult = ResolutionTools.deriveLeafId('platform.languages.az-AZ');
console.log(leafIdResult.value); // 'az-AZ'
const fullIdResult = ResolutionTools.deriveFullId('platform.languages', 'az-AZ');
console.log(fullIdResult.value); // 'platform.languages.az-AZ'
// Get statistics
const stats = ResolutionTools.getPendingResourceStats(resolutionState.pendingResources);
console.log(`${stats.totalCount} pending resources`);
console.log(`Types: ${Object.keys(stats.byType).join(', ')}`);
// Validate pending resource keys (diagnostic)
const validation = ResolutionTools.validatePendingResourceKeys(resolutionState.pendingResources);
if (validation.isFailure()) {
console.error('Key validation failed:', validation.message);
}
The ResolutionView supports custom editors for specific resource types through the ResourceEditorFactory
interface:
import { ResourceEditorFactory, ResourceEditorResult, ResourceEditorProps } from '@fgv/ts-res-ui-components';
// Custom editor component
const MarketInfoEditor: React.FC<ResourceEditorProps> = ({
value,
resourceId,
isEdited,
editedValue,
onSave,
onCancel,
disabled,
className
}) => {
// Custom form-based editor implementation
return (
<div className={`market-info-editor ${className}`}>
{/* Custom editing interface for market information */}
</div>
);
};
// Resource editor factory
class MyResourceEditorFactory implements ResourceEditorFactory {
createEditor(resourceId: string, resourceType: string, value: any): ResourceEditorResult {
if (resourceType === 'marketInfo') {
return {
success: true,
editor: MarketInfoEditor
};
}
return {
success: false,
message: `No custom editor available for resource type '${resourceType}'`
};
}
}
// Usage
const editorFactory = new MyResourceEditorFactory();
<ResolutionView
resources={resources}
resourceEditorFactory={editorFactory}
// ... other props
/>
Benefits of Custom Editors:
ResolutionView provides comprehensive control over the context configuration UI, allowing hosts to customize which qualifiers are editable, provide external values, and control the presentation. This is especially useful for applications that need to drive context externally or provide selective user control.
Hide Context UI Entirely (Host-Driven Context):
<ResolutionView
resources={processedResources}
resolutionState={resolutionState}
resolutionActions={resolutionActions}
contextOptions={{
showContextControls: false // Hide all context UI
}}
/>
Lock to Single View Mode:
<ResolutionView
resources={processedResources}
resolutionState={resolutionState}
resolutionActions={resolutionActions}
lockedViewMode="composed" // Lock to composed view, hide view selector
/>
Lock to Best View Mode:
<ResolutionView
resources={processedResources}
resolutionState={resolutionState}
resolutionActions={resolutionActions}
lockedViewMode="best" // Lock to best view, hide view selector
/>
Custom Section Titles:
<ResolutionView
resources={processedResources}
resolutionState={resolutionState}
resolutionActions={resolutionActions}
sectionTitles={{
resources: "Available Items", // Custom title for resources picker
results: "Resolution Output" // Custom title for results section
}}
/>
Fine-Grained Qualifier Control:
<ResolutionView
resources={processedResources}
resolutionState={resolutionState}
resolutionActions={resolutionActions}
contextOptions={{
// Panel visibility control
showContextControls: true,
showCurrentContext: true,
showContextActions: true,
// Per-qualifier configuration
qualifierOptions: {
language: {
editable: true,
placeholder: "Select language..."
},
platform: {
editable: false,
hostValue: "web",
showHostValue: true
},
environment: {
visible: false // Hidden from UI entirely
}
},
// Host-managed values (invisible but active in context)
hostManagedValues: {
environment: "production",
deployment: "us-east-1"
},
// Appearance customization
contextPanelTitle: "Resolution Context",
globalPlaceholder: "Enter {qualifierName}..."
}}
/>
Visual Indicators:
Development & Debug Controls: Enable the context options gear icon during development for interactive configuration:
<ResolutionView
resources={processedResources}
pickerOptionsPresentation="collapsible" // Shows both picker & context gear icons
resolutionState={resolutionState}
resolutionActions={resolutionActions}
/>
The gear icon provides a live configuration interface for:
This extensibility is perfect for:
Provides context-based resource filtering with candidate count analysis and export functionality.
import { FilterView, ResourceTools } from '@fgv/ts-res-ui-components';
function MyFilterTool() {
const { state, actions } = ResourceTools.useResourceData();
return (
<FilterView
resources={state.resources}
filterState={state.filterState}
filterActions={{
updateFilterEnabled: (enabled) => actions.updateFilterState({ enabled }),
updateFilterValues: (values) => actions.updateFilterState({ values }),
applyFilterValues: () => actions.applyFilter(),
resetFilterValues: () => actions.resetFilter(),
updateReduceQualifiers: (reduce) => actions.updateFilterState({ reduceQualifiers: reduce })
}}
filterResult={state.filterResult}
/>
);
}
FilterView provides the same comprehensive context control features as ResolutionView, allowing hosts to customize which qualifiers are editable for filtering, provide external values, and control the presentation. This is especially useful for filtering applications that need to drive filter context externally or provide selective user control.
Hide Context UI Entirely (Host-Driven Filtering):
<FilterView
resources={processedResources}
filterState={filterState}
filterActions={filterActions}
filterResult={filterResult}
contextOptions={{
showContextControls: false // Hide all context filter controls
}}
/>
Fine-Grained Filter Context Control:
<FilterView
resources={processedResources}
filterState={filterState}
filterActions={filterActions}
filterResult={filterResult}
contextOptions={{
// Panel appearance
contextPanelTitle: "Filter Criteria",
globalPlaceholder: "Filter by {qualifierName}...",
// Per-qualifier configuration
qualifierOptions: {
language: {
editable: true,
placeholder: "Filter by language..."
},
platform: {
editable: false,
hostValue: "web",
showHostValue: true
},
environment: {
visible: false // Hidden from filter UI entirely
}
},
// Host-managed filter values (invisible but active in filter context)
hostManagedValues: {
environment: "production",
deployment: "us-east-1"
},
// UI visibility control
showCurrentContext: true, // Show pending/applied filter summary
showContextActions: true // Show filter reset/apply buttons
}}
/>
Combined with External Filter Logic:
function SmartFilterView() {
const [externalContext, setExternalContext] = useState({
userRole: 'admin',
tenant: 'enterprise'
});
return (
<FilterView
resources={processedResources}
filterState={filterState}
filterActions={filterActions}
contextOptions={{
contextPanelTitle: "Available Filters",
qualifierOptions: {
// Hide sensitive qualifiers from basic users
environment: {
visible: externalContext.userRole === 'admin'
},
// Lock tenant-specific qualifiers
tenant: {
editable: false,
hostValue: externalContext.tenant
}
}
}}
/>
);
}
Development & Debug Controls: Enable the context options gear icon during development for interactive filter configuration:
<FilterView
resources={processedResources}
pickerOptionsPresentation="collapsible" // Shows both picker & context gear icons
filterState={filterState}
filterActions={filterActions}
/>
The gear icon provides a live configuration interface for:
This filter extensibility is perfect for:
Displays and manages application messages with filtering, search, and copy functionality. Perfect for debugging interfaces and development tools where message visibility is critical.
import { MessagesWindow, ViewTools } from '@fgv/ts-res-ui-components';
function MyApplication() {
const [messages, setMessages] = useState<ViewTools.Message[]>([]);
const addMessage = (type: ViewTools.Message['type'], text: string) => {
const newMessage: ViewTools.Message = {
id: `msg-${Date.now()}-${Math.random()}`,
type,
message: text,
timestamp: new Date()
};
setMessages(prev => [...prev, newMessage]);
};
const clearMessages = () => {
setMessages([]);
};
return (
<div className="flex flex-col h-screen">
<div className="flex-1">
{/* Main application content */}
<button onClick={() => addMessage('info', 'Processing started')}>
Add Info Message
</button>
<button onClick={() => addMessage('success', 'Operation completed')}>
Add Success Message
</button>
<button onClick={() => addMessage('error', 'Something went wrong')}>
Add Error Message
</button>
</div>
{/* Messages window at bottom */}
<MessagesWindow
messages={messages}
onClearMessages={clearMessages}
/>
</div>
);
}
Key features:
To integrate MessagesWindow into your application and provide user feedback during operations:
import { ViewTools } from '@fgv/ts-res-ui-components';
function useMessages() {
const [messages, setMessages] = useState<ViewTools.Message[]>([]);
const addMessage = useCallback((type: ViewTools.Message['type'], message: string) => {
const newMessage: ViewTools.Message = {
id: `msg-${Date.now()}-${Math.random()}`,
type,
message,
timestamp: new Date()
};
setMessages(prev => [...prev, newMessage]);
}, []);
const clearMessages = useCallback(() => {
setMessages([]);
}, []);
return { messages, addMessage, clearMessages };
}
function MyResourceTool() {
const { messages, addMessage, clearMessages } = useMessages();
const [resources, setResources] = useState(null);
const handleFileImport = async (files) => {
try {
addMessage('info', 'Starting file import...');
const processed = await processFiles(files);
setResources(processed);
addMessage('success', `Successfully imported ${files.length} files`);
} catch (error) {
addMessage('error', `Import failed: ${error.message}`);
}
};
const handleResourceFilter = (filterValues) => {
if (Object.keys(filterValues).length === 0) {
addMessage('warning', 'No filter values provided');
return;
}
try {
const filtered = applyFilters(resources, filterValues);
addMessage('success', `Filtered to ${filtered.length} resources`);
} catch (error) {
addMessage('error', `Filter failed: ${error.message}`);
}
};
return (
<div className="flex flex-col h-screen">
<div className="flex-1">
<ObservabilityProvider observabilityContext={observabilityContext}>
<ImportView onImport={handleFileImport} />
<FilterView onFilter={handleResourceFilter} />
{/* Components automatically use observability context */}
</ObservabilityProvider>
</div>
<ViewTools.MessagesWindow
messages={messages}
onClearMessages={clearMessages}
/>
</div>
);
}
Common patterns for adding messages:
Observability Integration:
All components in this library automatically use the observability context when available. This provides consistent feedback across all operations through the unified o11y.user.*
and o11y.diag.*
patterns.
📚 See complete hooks documentation → for detailed examples and patterns
All hooks are organized within their respective namespaces alongside their related components and utilities for better discoverability and logical grouping.
Main orchestrator hook for resource processing, configuration, and resolution.
import { ResourceTools } from '@fgv/ts-res-ui-components';
const { state, actions } = ResourceTools.useResourceData();
// Process files
await actions.processFiles(importedFiles);
// Resolve a resource
const result = await actions.resolveResource('my.resource', {
language: 'en-US',
environment: 'production'
});
// Apply configuration
actions.applyConfiguration(newConfig);
// Check processing state
if (state.isProcessing) {
console.log('Processing resources...');
} else if (state.error) {
console.error('Processing failed:', state.error);
} else if (state.processedResources) {
console.log('Resources ready!');
}
Manages view state including messages and resource selection.
import { ViewTools } from '@fgv/ts-res-ui-components';
const { messages, selectedResourceId, addMessage, clearMessages, selectResource } = ViewTools.useViewState();
// Display operation feedback
const handleOperation = async () => {
try {
await someAsyncOperation();
addMessage('success', 'Operation completed successfully');
} catch (error) {
addMessage('error', `Operation failed: ${error.message}`);
}
};
// Use with MessagesWindow component
return (
<div>
<button onClick={handleOperation}>Run Operation</button>
<ViewTools.MessagesWindow
messages={messages}
onClearMessages={clearMessages}
/>
</div>
);
Manages resource filtering state with change tracking and validation.
import { FilterTools } from '@fgv/ts-res-ui-components';
const { state, actions } = FilterTools.useFilterState({
enabled: true,
values: { platform: 'web', locale: 'en' }
});
// Update filter values with change tracking
actions.updateFilterValue('language', 'en-US');
actions.updateFilterValue('environment', 'prod');
// Apply filters when ready
if (state.hasPendingChanges) {
actions.applyFilters();
}
Comprehensive state management for resource resolution and editing.
import { ResolutionTools } from '@fgv/ts-res-ui-components';
const { state, actions, availableQualifiers } = ResolutionTools.useResolutionState(
processedResources,
(type, message) => addMessage(type, message),
(updatedResources) => setProcessedResources(updatedResources)
);
// Set context for resolution testing
actions.updateContext({ language: 'en-US', platform: 'web' });
// Start editing a resource
actions.selectResource('user.welcome');
actions.startEditing();
// Save edits with validation
actions.saveEdit(editedValue);
Manages system configuration state with change tracking and import/export capabilities.
import { ConfigurationTools } from '@fgv/ts-res-ui-components';
const { state, actions, templates } = ConfigurationTools.useConfigurationState(
undefined,
(config) => console.log('Configuration changed:', config),
(hasChanges) => setHasUnsavedChanges(hasChanges)
);
// Load a template
const loadResult = actions.loadTemplate('minimal');
// Add a new qualifier with validation
actions.addQualifier({
name: 'language',
typeName: 'language',
defaultPriority: 100
});
// Check for unsaved changes
if (state.hasUnsavedChanges) {
actions.applyConfiguration();
}
// Export configuration
const exportResult = actions.exportToJson({ pretty: true });
if (exportResult.isSuccess()) {
downloadFile(exportResult.value, 'configuration.json');
}
All hooks are organized within logical namespaces alongside their related components and utilities:
ResourceTools.useResourceData
- Core data orchestration (import, processing, configuration, resolution)ViewTools.useViewState
- View state management (messages, resource selection)FilterTools.useFilterState
- Resource filtering with change trackingResolutionTools.useResolutionState
- Resource resolution and editingConfigurationTools.useConfigurationState
- System configuration managementThis organization provides:
FilterTools.useFilterState
is self-documentingThis library uses Tailwind CSS for styling. Make sure to include Tailwind CSS in your project:
npm install -D tailwindcss
Add the library's source files to your Tailwind content configuration:
// tailwind.config.js
module.exports = {
content: [
'./src/**/*.{js,jsx,ts,tsx}',
'./node_modules/@fgv/ts-res-ui-components/**/*.{js,jsx,ts,tsx}'
],
theme: {
extend: {},
},
plugins: [],
}
All components accept a className
prop for custom styling:
<SourceView
className="my-custom-class"
resources={resources}
/>
The recommended approach for programmatic resource creation:
import { ResolutionTools } from '@fgv/ts-res-ui-components';
// Single atomic operation (recommended)
async function createResourceAtomic(actions) {
const result = await actions.createPendingResource({
id: 'platform.languages.az-AZ',
resourceTypeName: 'json',
json: { text: 'Welcome', locale: 'az-AZ' }
});
if (result.isSuccess()) {
console.log('Resource created successfully');
await actions.applyPendingResources();
} else {
console.error('Creation failed:', result.message);
}
}
// Step-by-step workflow (if needed)
function createResourceStepByStep(actions) {
// 1) Start new resource
const startResult = actions.startNewResource({ defaultTypeName: 'json' });
if (!startResult.success) return;
// 2) Update resource ID
const idResult = actions.updateNewResourceId('platform.languages.az-AZ');
if (!idResult.success) return;
// 3) Select resource type (if step 1 didn't set it)
const typeResult = actions.selectResourceType('json');
if (!typeResult.success) return;
// 4) Update JSON content (optional, recommended)
const jsonResult = actions.updateNewResourceJson({
text: 'Welcome',
locale: 'az-AZ'
});
if (!jsonResult.success) return;
// 5) Save as pending
const saveResult = actions.saveNewResourceAsPending();
if (!saveResult.success) return;
// Note: Do NOT call saveResourceEdit for brand-new resources
}
import React from 'react';
import { ResourceOrchestrator, ResolutionView } from '@fgv/ts-res-ui-components';
export default function App() {
return (
<ResourceOrchestrator>
{({ state, actions }) => (
<ResolutionView
resources={state.resources}
resolutionState={state.resolutionState}
resolutionActions={{
updateContextValue: actions.updateResolutionContext,
applyContext: actions.applyResolutionContext,
selectResource: actions.selectResourceForResolution,
setViewMode: actions.setResolutionViewMode,
resetCache: actions.resetResolutionCache,
saveEdit: actions.saveResourceEdit,
getEditedValue: actions.getEditedValue,
hasEdit: actions.hasResourceEdit,
clearEdits: actions.clearResourceEdits,
discardEdits: actions.discardResourceEdits,
// Enhanced resource creation actions with atomic API
createPendingResource: actions.createPendingResource,
startNewResource: actions.startNewResource,
updateNewResourceId: actions.updateNewResourceId,
selectResourceType: actions.selectResourceType,
updateNewResourceJson: actions.updateNewResourceJson,
saveNewResourceAsPending: actions.saveNewResourceAsPending,
cancelNewResource: actions.cancelNewResource,
removePendingResource: actions.removePendingResource,
markResourceForDeletion: actions.markResourceForDeletion,
applyPendingResources: actions.applyPendingResources,
discardPendingResources: actions.discardPendingResources
}}
allowResourceCreation
defaultResourceType="json"
showPendingResourcesInList
/>
)}
</ResourceOrchestrator>
);
}
The ResourceOrchestrator
component provides centralized state management for all ts-res UI functionality:
import React from 'react';
import { ResourceOrchestrator, ImportView, SourceView } from '@fgv/ts-res-ui-components';
function App() {
return (
<ResourceOrchestrator>
{({ state, actions }) => (
<div className="min-h-screen bg-gray-50">
<div className="container mx-auto px-4 py-8">
<h1 className="text-3xl font-bold mb-8">Resource Manager</h1>
{!state.processedResources ? (
<ImportView
onImport={actions.importDirectory}
onBundleImport={actions.importBundle}
onZipImport={(zipData, config) => {
if (config) actions.applyConfiguration(config);
if (zipData.directory) actions.importDirectory(zipData.directory);
else if (zipData.files?.length) actions.importFiles(zipData.files);
}}
/>
) : (
<SourceView
resources={state.processedResources}
onExport={actions.exportData}
/>
)}
</div>
</div>
)}
</ResourceOrchestrator>
);
}
export default App;
For more granular control, you can use individual hooks:
import React from 'react';
import { useResourceData, SourceView } from '@fgv/ts-res-ui-components';
function MyResourceViewer() {
const { state, actions } = useResourceData();
const handleFileImport = async (files: File[]) => {
const importedFiles = await processFiles(files); // Your file processing logic
await actions.processFiles(importedFiles);
};
return (
<div>
{state.isProcessing && <div>Processing...</div>}
{state.error && <div className="error">{state.error}</div>}
{state.processedResources && (
<SourceView
resources={state.processedResources}
onExport={(data) => console.log('Export:', data)}
/>
)}
</div>
);
}
import { TsResTools } from '@fgv/ts-res-ui-components';
// Custom processing pipeline
const customProcessor = async (files: ImportedFile[]) => {
// Pre-process files
const processedFiles = files.map(transformFile);
// Create configuration
const config = await createConfigFromFiles(processedFiles);
// Create ts-res system
const system = await TsResTools.createTsResSystemFromConfig(config);
return system;
};
const { state, actions } = useResourceData();
// Complex context resolution
const resolveWithComplexContext = async (resourceId: string) => {
const context = {
language: getUserLanguage(),
region: getUserRegion(),
theme: getThemePreference(),
featureFlags: await getFeatureFlags()
};
return await actions.resolveResource(resourceId, context);
};
const { state, actions } = useResourceData();
// Create and export bundle
const exportBundle = async () => {
if (state.processedResources) {
const bundleData = {
...state.processedResources.compiledCollection,
metadata: {
version: '1.0.0',
created: new Date().toISOString(),
description: 'My resource bundle'
}
};
actions.exportData(bundleData, 'bundle');
}
};
The library provides comprehensive error handling through the state management system:
<ResourceOrchestrator>
{({ state, actions }) => (
<div>
{state.error && (
<div className="bg-red-50 border border-red-200 rounded p-4 mb-4">
<h3 className="text-red-800 font-medium">Error</h3>
<p className="text-red-600">{state.error}</p>
<button
onClick={actions.clearError}
className="mt-2 px-3 py-1 bg-red-600 text-white rounded text-sm"
>
Dismiss
</button>
</div>
)}
{/* Rest of your UI */}
</div>
)}
</ResourceOrchestrator>
For better organization and discoverability, utility functions are organized into logical namespaces alongside their related view components:
import {
FilterTools, // FilterView + filtering utilities
ResolutionTools, // ResolutionView + resolution utilities
ConfigurationTools, // ConfigurationView + configuration utilities
TsResTools, // SourceView, CompiledView + ts-res utilities
ViewStateTools, // MessagesWindow + view state utilities
ZipTools, // ImportView + ZIP processing helpers
ObservabilityTools, // Observability context and loggers
GridTools, // GridView + data grid utilities
PickerTools, // ResourcePicker + picker utilities
ImportTools // Import utilities and types
} from '@fgv/ts-res-ui-components';
// Use view components from namespaces
<FilterTools.FilterView {...filterProps} />
<ResolutionTools.ResolutionView {...resolutionProps} />
<ViewStateTools.MessagesWindow {...messageProps} />
<TsResTools.SourceView {...sourceProps} />
<ZipTools.ImportView {...importProps} />
<GridTools.GridView {...gridProps} />
// Use utility functions from namespaces
const hasFilters = FilterTools.hasFilterValues(filterState.values);
const resolver = ResolutionTools.createResolverWithContext(resources, context);
const system = await TsResTools.createTsResSystemFromConfig(config);
// ZIP processing helpers for ts-res-ui-components integration
const processResult = await ZipTools.processZipLoadResult(zipData, config);
// Observability context and loggers
const consoleContext = ObservabilityTools.createConsoleObservabilityContext('debug', 'info');
const noOpContext = ObservabilityTools.createNoOpObservabilityContext();
const customLogger = new ObservabilityTools.ConsoleUserLogger('info');
// Grid utilities and selectors
const gridSelector = new GridTools.ResourceSelector();
const validation = GridTools.validateCellValue(value, rules);
All components are also available at the top level for backward compatibility.
This library is written in TypeScript and provides comprehensive type definitions with enhanced support for resource selection and generic resource data.
import type {
ProcessedResources,
FilterState,
ResolutionResult,
Message,
ImportedFile,
// Enhanced ResourcePicker types
ResourceSelection,
ResourcePickerProps,
ResourceAnnotation,
ResourceAnnotations,
PendingResource,
// Custom editor factory types
ResourceEditorFactory,
ResourceEditorResult,
ResourceEditorProps,
// Hook return types
UseViewStateReturn,
UseFilterStateReturn,
UseResolutionStateReturn
} from '@fgv/ts-res-ui-components';
// Import organized namespaces for components and utilities
import {
FilterTools,
ResolutionTools,
TsResTools,
ZipTools
} from '@fgv/ts-res-ui-components';
// Type-safe component with enhanced resource selection
interface MyResourceViewProps<T = unknown> {
resources: ProcessedResources;
onResourceSelect: (selection: ResourceSelection<T>) => void;
}
const MyResourceView = <T = unknown>({ resources, onResourceSelect }: MyResourceViewProps<T>) => {
return (
<ResourcePicker<T>
resources={resources}
onResourceSelect={(selection) => {
// TypeScript knows selection has resourceId, resourceData, isPending, etc.
onResourceSelect(selection);
}}
resourceAnnotations={{
'user.welcome': {
badge: { text: 'NEW', variant: 'new' },
suffix: '(3 candidates)'
}
}}
/>
);
};
// Type-safe custom editor factory
class TypedResourceEditorFactory implements ResourceEditorFactory {
createEditor(resourceId: string, resourceType: string, value: any): ResourceEditorResult {
// Full type safety for factory pattern
if (resourceType === 'marketInfo') {
return { success: true, editor: MarketInfoEditor };
}
return { success: false, message: `No editor for ${resourceType}` };
}
}
This library is part of a Rush.js monorepo. Rush is a build orchestrator for JavaScript monorepos that provides scalable build performance and consistent package management.
If you're new to this monorepo, follow these steps to get started:
Install Rush globally (if not already installed):
npm install -g @microsoft/rush
Clone the repository and install dependencies:
git clone https://github.com/ErikFortune/fgv.git
cd fgv
rush install
Build all projects (including dependencies):
rush build
📚 Learn more about Rush: Official Rush Documentation
All development commands use Rush's rushx
tool to run scripts within this specific project:
# Build this project only
rushx build
# Build all projects in the monorepo (from root)
rush build
# Test this project (includes coverage by default)
rushx test
# Test all projects in the monorepo (from root)
rush test
# Test specific projects with dependencies
rush test --to ts-res-ui-components
# Test only this project without dependencies
rush test --only ts-res-ui-components
# Lint this project
rushx lint
# Fix lint issues automatically
rushx fixlint
# Lint all projects in the monorepo (from root)
rush prettier
# Clean build artifacts
rushx clean
# Update dependencies (from repository root)
rush update
# Add a new dependency to this project (from repository root)
rush add -p <package-name>
# Check for security vulnerabilities (from repository root)
rush audit
This library is located at libraries/ts-res-ui-components/
within the monorepo and depends on several other libraries in the workspace:
@fgv/ts-res
- Core resource management library@fgv/ts-utils
- Utility functions and Result pattern@fgv/ts-json-base
- JSON validation and processing@fgv/ts-bcp47
- BCP47 language tag processingAll workspace dependencies use workspace:*
version ranges for automatic version resolution.
MIT License - see LICENSE file for details.
Please read CONTRIBUTING.md for details on our code of conduct and the process for submitting pull requests.
Comprehensive API documentation is available in the docs directory:
The API documentation includes detailed examples, usage patterns, and type information for all public APIs.
For questions and support, please:
FAQs
Reusable React components for ts-res resource visualization and management
The npm package @fgv/ts-res-ui-components receives a total of 198 weekly downloads. As such, @fgv/ts-res-ui-components popularity was classified as not popular.
We found that @fgv/ts-res-ui-components demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Research
Socket uncovers malicious Rust crates impersonating fast_log to steal Solana and Ethereum wallet keys from source code.
Research
A malicious package uses a QR code as steganography in an innovative technique.
Research
/Security News
Socket identified 80 fake candidates targeting engineering roles, including suspected North Korean operators, exposing the new reality of hiring as a security function.