orcas-angular 1.0.0
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/LICENSE +159 -0
- package/README.md +17 -0
- package/async/README.md +46 -0
- package/async/async.ts +16 -0
- package/async/cancellation-token.ts +90 -0
- package/dev/README.md +41 -0
- package/dev/console-hook.ts +25 -0
- package/dev/debug.service.ts.example +29 -0
- package/framework/README.md +34 -0
- package/framework/services-init.ts +25 -0
- package/index.ts +21 -0
- package/localization/README.md +73 -0
- package/localization/localization.interface.ts +18 -0
- package/localization/localization.service.ts +131 -0
- package/localization/localize.pipe.ts +30 -0
- package/log/README.md +275 -0
- package/log/echo-provider.ts +27 -0
- package/log/echo.ts +635 -0
- package/log/index.ts +6 -0
- package/log/log-systems.ts +20 -0
- package/navigation/README.md +47 -0
- package/navigation/back-on-click.directive.ts +19 -0
- package/navigation/index.ts +3 -0
- package/navigation/navigation-stack.service.ts +33 -0
- package/package.json +29 -0
- package/storage/README.md +75 -0
- package/storage/capacitor-files.service.ts +38 -0
- package/storage/file-box.service.ts +112 -0
- package/storage/files.ts +42 -0
- package/storage/key-signals.ts +49 -0
- package/storage/local-storage-files.service.ts +49 -0
- package/storage/settings-signals.service.ts +24 -0
- package/storage/settings.service.ts +24 -0
- package/storage/tauri-files.service.ts +69 -0
- package/theme/README.md +44 -0
- package/theme/theme.service.ts +33 -0
- package/ui/README.md +42 -0
- package/ui/context-menu/context-button.component.ts +55 -0
- package/ui/context-menu/context-header.component.ts +15 -0
- package/ui/context-menu/context-menu-trigger.directive.ts +26 -0
- package/ui/context-menu/context-menu.component.ts +95 -0
- package/ui/context-menu/index.ts +4 -0
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import { computed, inject, Injectable, signal } from '@angular/core';
|
|
2
|
+
import { HttpClient } from '@angular/common/http';
|
|
3
|
+
import { ILocalizationService } from './localization.interface';
|
|
4
|
+
|
|
5
|
+
@Injectable({
|
|
6
|
+
providedIn: 'root'
|
|
7
|
+
})
|
|
8
|
+
export class LocalizationService implements ILocalizationService {
|
|
9
|
+
private defaultLanguage = 'en';
|
|
10
|
+
private storageKey = 'orcas-language';
|
|
11
|
+
|
|
12
|
+
private translations: any = {};
|
|
13
|
+
private loaded = false;
|
|
14
|
+
|
|
15
|
+
private $language: ReturnType<typeof signal<string>>;
|
|
16
|
+
public $currentLang = computed(() => this.$language());
|
|
17
|
+
|
|
18
|
+
private http = inject(HttpClient);
|
|
19
|
+
|
|
20
|
+
constructor() {
|
|
21
|
+
this.$language = signal(this.getStoredLanguage());
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
public async init(
|
|
25
|
+
jsonPath: string = 'assets/translations.json',
|
|
26
|
+
defaultLanguage: string = 'en',
|
|
27
|
+
storageKey: string = 'orcas-language'
|
|
28
|
+
): Promise<void> {
|
|
29
|
+
this.defaultLanguage = defaultLanguage;
|
|
30
|
+
this.storageKey = storageKey;
|
|
31
|
+
this.$language.set(this.getStoredLanguage());
|
|
32
|
+
|
|
33
|
+
try {
|
|
34
|
+
this.translations = await this.http.get(jsonPath).toPromise();
|
|
35
|
+
this.loaded = true;
|
|
36
|
+
}
|
|
37
|
+
catch (err) {
|
|
38
|
+
console.error('Failed to load translations:', err);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
getLanguage(): string {
|
|
43
|
+
return this.$language();
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
getDefaultLanguage(): string {
|
|
47
|
+
return this.defaultLanguage;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
setActiveLanguage(lang: string): void {
|
|
51
|
+
if (lang !== this.$language()) {
|
|
52
|
+
localStorage.setItem(this.storageKey, lang);
|
|
53
|
+
this.$language.set(lang);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
translate(key: string, params?: any, language?: string): string {
|
|
58
|
+
const lang = language || this.getLanguage();
|
|
59
|
+
|
|
60
|
+
if (!this.loaded) {
|
|
61
|
+
console.error('Localization: Translations not loaded yet!');
|
|
62
|
+
return key;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
let translation = null;
|
|
66
|
+
|
|
67
|
+
// Handle pluralization: try singular suffix __1 first if count is 1
|
|
68
|
+
if (params && params.count === 1)
|
|
69
|
+
translation = this.resolveKey(`${key}__1`);
|
|
70
|
+
|
|
71
|
+
if (!translation)
|
|
72
|
+
translation = this.resolveKey(key);
|
|
73
|
+
|
|
74
|
+
if (!translation) {
|
|
75
|
+
console.warn(`Localization: Key not found for "${key}".`);
|
|
76
|
+
return key;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
let translatedText = translation[lang];
|
|
80
|
+
|
|
81
|
+
if (!translatedText) {
|
|
82
|
+
console.warn(`Localization: Key "${key}" not found for language "${lang}". Falling back to default language.`);
|
|
83
|
+
translatedText = translation[this.defaultLanguage];
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (!translatedText) {
|
|
87
|
+
console.error(`Localization: Key "${key}" not found for default language "${this.defaultLanguage}".`);
|
|
88
|
+
return key;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (params) {
|
|
92
|
+
if (Array.isArray(params))
|
|
93
|
+
return this.replaceArrayParams(translatedText, params);
|
|
94
|
+
else
|
|
95
|
+
return this.replaceObjectParams(translatedText, params);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return translatedText;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
private resolveKey(key: string): any {
|
|
102
|
+
const keys = key.split('.');
|
|
103
|
+
let translation = this.translations;
|
|
104
|
+
for (const k of keys) {
|
|
105
|
+
if (!translation[k])
|
|
106
|
+
return null;
|
|
107
|
+
translation = translation[k];
|
|
108
|
+
}
|
|
109
|
+
return translation;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
private getStoredLanguage(): string {
|
|
113
|
+
return localStorage.getItem(this.storageKey) || this.defaultLanguage;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
private replaceArrayParams(text: string, params: any[]): string {
|
|
117
|
+
let result = text;
|
|
118
|
+
params.forEach((param, index) => {
|
|
119
|
+
result = result.replace(new RegExp(`\\{\\{${index}\\}\\}`, 'g'), param.toString());
|
|
120
|
+
});
|
|
121
|
+
return result;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
private replaceObjectParams(text: string, params: any): string {
|
|
125
|
+
let result = text;
|
|
126
|
+
Object.keys(params).forEach(key => {
|
|
127
|
+
result = result.replace(new RegExp(`\\{\\{${key}\\}\\}`, 'g'), params[key].toString());
|
|
128
|
+
});
|
|
129
|
+
return result;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import {Pipe, PipeTransform} from '@angular/core';
|
|
2
|
+
import {LocalizationService} from './localization.service';
|
|
3
|
+
|
|
4
|
+
@Pipe({
|
|
5
|
+
name: 'localize',
|
|
6
|
+
standalone: true,
|
|
7
|
+
pure: false
|
|
8
|
+
})
|
|
9
|
+
export class LocalizePipe implements PipeTransform {
|
|
10
|
+
private lastLanguage: string = '';
|
|
11
|
+
private lastKey: string = '';
|
|
12
|
+
private lastParams: any;
|
|
13
|
+
private lastResult: string = '';
|
|
14
|
+
|
|
15
|
+
constructor(private localizationService: LocalizationService) {
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
transform(key: string, params?: any): string {
|
|
19
|
+
if (this.localizationService.$currentLang() !== this.lastLanguage
|
|
20
|
+
|| key !== this.lastKey
|
|
21
|
+
|| params !== this.lastParams) {
|
|
22
|
+
this.lastLanguage = this.localizationService.$currentLang();
|
|
23
|
+
this.lastKey = key;
|
|
24
|
+
this.lastParams = params;
|
|
25
|
+
this.lastResult = this.localizationService.translate(key, params);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return this.lastResult;
|
|
29
|
+
}
|
|
30
|
+
}
|
package/log/README.md
ADDED
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
# Echo TypeScript
|
|
2
|
+
|
|
3
|
+
TypeScript port of the Echo.cs logging library. This is a flexible and powerful logging library with support for log levels, system-based organization, and custom log writers.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- Structured logging with system tags
|
|
8
|
+
- Customizable log levels (per system or global)
|
|
9
|
+
- String formatting with parameters (only formatted when log will be written)
|
|
10
|
+
- Log-once functionality to prevent duplicate messages
|
|
11
|
+
- Extensible with custom log writers
|
|
12
|
+
- Console log writer with colors and timestamps included
|
|
13
|
+
- TypeScript type safety
|
|
14
|
+
- Performance optimized - no allocations when logs are filtered out
|
|
15
|
+
|
|
16
|
+
## Installation
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
# Install dependencies
|
|
20
|
+
npm install
|
|
21
|
+
|
|
22
|
+
# Build the library
|
|
23
|
+
npm run build
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
The compiled JavaScript and type definitions will be in the `dist/` folder.
|
|
27
|
+
|
|
28
|
+
## Quick Start
|
|
29
|
+
|
|
30
|
+
```typescript
|
|
31
|
+
import { EchoConsole, LogLevel } from './dist/echo';
|
|
32
|
+
|
|
33
|
+
// Create an Echo instance with default console writer
|
|
34
|
+
const echo = EchoConsole.new();
|
|
35
|
+
|
|
36
|
+
// Get a logger
|
|
37
|
+
const logger = echo.getLogger();
|
|
38
|
+
|
|
39
|
+
// Log messages
|
|
40
|
+
logger.debug("System", "Debug message");
|
|
41
|
+
logger.info("System", "Info message");
|
|
42
|
+
logger.warn("System", "Warning message");
|
|
43
|
+
logger.error("System", "Error message");
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Usage Examples
|
|
47
|
+
|
|
48
|
+
### Basic Logging
|
|
49
|
+
|
|
50
|
+
```typescript
|
|
51
|
+
import { EchoConsole } from './echo';
|
|
52
|
+
|
|
53
|
+
const echo = EchoConsole.new();
|
|
54
|
+
const logger = echo.getLogger();
|
|
55
|
+
|
|
56
|
+
// Log with different levels
|
|
57
|
+
logger.debug("GUI", "This is a debug message from the GUI system.");
|
|
58
|
+
logger.info("Physics", "This is an info message from the Physics system.");
|
|
59
|
+
logger.warn("AI", "This is a warning message from the AI system.");
|
|
60
|
+
logger.error("Rendering", "This is an error message from the Rendering system.");
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### System Logger
|
|
64
|
+
|
|
65
|
+
```typescript
|
|
66
|
+
// Get a system-specific logger (cached per system)
|
|
67
|
+
const animationLogger = echo.getSystemLogger("Animation");
|
|
68
|
+
|
|
69
|
+
// No need to specify system in subsequent calls
|
|
70
|
+
animationLogger.debug("This is a debug message from the Animation system.");
|
|
71
|
+
animationLogger.info("This is an info message from the Animation system.");
|
|
72
|
+
animationLogger.warn("This is a warning message from the Animation system.");
|
|
73
|
+
animationLogger.error("This is an error message from the Animation system.");
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### String Formatting
|
|
77
|
+
|
|
78
|
+
```typescript
|
|
79
|
+
// Use formatted strings with parameters
|
|
80
|
+
// Formatting is only done IF the log will be written (performance optimization)
|
|
81
|
+
const playerName = "John";
|
|
82
|
+
const playerHealth = 100;
|
|
83
|
+
logger.info("General", "Player {0} has {1} health.", playerName, playerHealth);
|
|
84
|
+
// Output: Player John has 100 health.
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
### Log Level Management
|
|
88
|
+
|
|
89
|
+
```typescript
|
|
90
|
+
import { LogLevel } from './echo';
|
|
91
|
+
|
|
92
|
+
// Set default log level (applies to all systems)
|
|
93
|
+
echo.settings.setDefaultLevel(LogLevel.Warn); // Only Warn and Error will be logged
|
|
94
|
+
|
|
95
|
+
// Set system-specific log level
|
|
96
|
+
echo.settings.setSystemLevel("Physics", LogLevel.Debug); // Physics logs everything
|
|
97
|
+
|
|
98
|
+
// Get current log level for a system
|
|
99
|
+
const level = echo.settings.getSystemLevel("Physics");
|
|
100
|
+
|
|
101
|
+
// Clear a system-specific level (reverts to default)
|
|
102
|
+
echo.settings.clearSystemLevel("Physics");
|
|
103
|
+
|
|
104
|
+
// Clear all system-specific levels
|
|
105
|
+
echo.settings.clearSystemLevels();
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
### Log Once
|
|
109
|
+
|
|
110
|
+
```typescript
|
|
111
|
+
// Log a message only once, even if called multiple times
|
|
112
|
+
logger.debug1("System", "This will only appear once");
|
|
113
|
+
logger.debug1("System", "This will only appear once"); // Won't be logged
|
|
114
|
+
logger.debug1("System", "This will only appear once"); // Won't be logged
|
|
115
|
+
|
|
116
|
+
// Also works with other log levels
|
|
117
|
+
logger.info1("System", "Info once");
|
|
118
|
+
logger.warn1("System", "Warn once");
|
|
119
|
+
logger.error1("System", "Error once");
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
### Custom Configuration
|
|
123
|
+
|
|
124
|
+
```typescript
|
|
125
|
+
import { LogWriterConfig, SystemColor } from './echo';
|
|
126
|
+
|
|
127
|
+
const config = new LogWriterConfig();
|
|
128
|
+
config.timestamp = true; // Include timestamps (default: true)
|
|
129
|
+
config.levelLabels = true; // Include log level labels (default: true)
|
|
130
|
+
config.levelColors = true; // Use colors for log levels (default: true)
|
|
131
|
+
config.systemColor = SystemColor.LabelAndMessage; // Color both label and message
|
|
132
|
+
|
|
133
|
+
const echo = EchoConsole.new(config);
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
### Custom Log Writer
|
|
137
|
+
|
|
138
|
+
```typescript
|
|
139
|
+
import { Echo, EchoLogWriter, LogLevel } from './echo';
|
|
140
|
+
|
|
141
|
+
class CustomLogWriter implements EchoLogWriter {
|
|
142
|
+
writeLog(level: LogLevel, system: string, message: string): void {
|
|
143
|
+
// Custom log writing logic here
|
|
144
|
+
const timestamp = new Date().toISOString();
|
|
145
|
+
console.log(`${timestamp} | ${level} | [${system}] ${message}`);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// Use custom writer
|
|
150
|
+
const customWriter = new CustomLogWriter();
|
|
151
|
+
const echo = new Echo(customWriter);
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
## API Reference
|
|
155
|
+
|
|
156
|
+
### Echo Class
|
|
157
|
+
|
|
158
|
+
Main entry point for the library.
|
|
159
|
+
|
|
160
|
+
```typescript
|
|
161
|
+
constructor(writer: EchoLogWriter)
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
- **getLogger()**: Returns the main logger instance (cached)
|
|
165
|
+
- **getSystemLogger(system: string)**: Returns a system-specific logger (cached per system)
|
|
166
|
+
- **settings**: Access to EchoSettings for configuration
|
|
167
|
+
|
|
168
|
+
### EchoLogger Class
|
|
169
|
+
|
|
170
|
+
Main logger with system parameter required.
|
|
171
|
+
|
|
172
|
+
**Methods** (all have the same signature pattern):
|
|
173
|
+
- **debug(system: string, message: string, ...params: any[])**
|
|
174
|
+
- **info(system: string, message: string, ...params: any[])**
|
|
175
|
+
- **warn(system: string, message: string, ...params: any[])**
|
|
176
|
+
- **error(system: string, message: string, ...params: any[])**
|
|
177
|
+
|
|
178
|
+
**Log-once variants** (append `1` to method name):
|
|
179
|
+
- **debug1, info1, warn1, error1** - Same signatures as above
|
|
180
|
+
|
|
181
|
+
### EchoSystemLogger Class
|
|
182
|
+
|
|
183
|
+
System-specific logger (system is set at creation).
|
|
184
|
+
|
|
185
|
+
**Methods** (no system parameter needed):
|
|
186
|
+
- **debug(message: string, ...params: any[])**
|
|
187
|
+
- **info(message: string, ...params: any[])**
|
|
188
|
+
- **warn(message: string, ...params: any[])**
|
|
189
|
+
- **error(message: string, ...params: any[])**
|
|
190
|
+
|
|
191
|
+
**Log-once variants**:
|
|
192
|
+
- **debug1, info1, warn1, error1** - Same signatures as above
|
|
193
|
+
|
|
194
|
+
### EchoSettings Class
|
|
195
|
+
|
|
196
|
+
Configuration for log levels.
|
|
197
|
+
|
|
198
|
+
- **defaultLevel**: Get/set the default log level
|
|
199
|
+
- **setDefaultLevel(level: LogLevel)**: Set default log level for all systems
|
|
200
|
+
- **setSystemLevel(system: string, level: LogLevel)**: Set log level for specific system
|
|
201
|
+
- **getSystemLevel(system: string)**: Get log level for specific system
|
|
202
|
+
- **clearSystemLevel(system: string)**: Remove system-specific level
|
|
203
|
+
- **clearSystemLevels()**: Remove all system-specific levels
|
|
204
|
+
- **getAllSystemLevels()**: Get all system-specific levels
|
|
205
|
+
- **onUpdated(callback: () => void)**: Register callback for settings updates
|
|
206
|
+
|
|
207
|
+
### LogLevel Enum
|
|
208
|
+
|
|
209
|
+
```typescript
|
|
210
|
+
enum LogLevel {
|
|
211
|
+
None = 0, // No logging
|
|
212
|
+
Error = 1, // Errors only
|
|
213
|
+
Warn = 2, // Warnings and errors
|
|
214
|
+
Info = 3, // Info, warnings, and errors
|
|
215
|
+
Debug = 4 // All logs (most verbose)
|
|
216
|
+
}
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
### LogWriterConfig Class
|
|
220
|
+
|
|
221
|
+
Configuration for the console log writer.
|
|
222
|
+
|
|
223
|
+
- **timestamp**: Include timestamps (default: true)
|
|
224
|
+
- **levelLabels**: Include log level labels (default: true)
|
|
225
|
+
- **levelColors**: Use colors for log levels (default: true)
|
|
226
|
+
- **systemColor**: Control system color usage (default: SystemColor.LabelOnly)
|
|
227
|
+
|
|
228
|
+
### SystemColor Enum
|
|
229
|
+
|
|
230
|
+
```typescript
|
|
231
|
+
enum SystemColor {
|
|
232
|
+
None, // No color
|
|
233
|
+
LabelOnly, // Color only the system label
|
|
234
|
+
LabelAndMessage // Color both label and message
|
|
235
|
+
}
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
### EchoConsole Helper
|
|
239
|
+
|
|
240
|
+
Factory for creating Echo instances with console writer.
|
|
241
|
+
|
|
242
|
+
```typescript
|
|
243
|
+
EchoConsole.new(config?: LogWriterConfig): Echo
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
## Differences from C# Version
|
|
247
|
+
|
|
248
|
+
The TypeScript port maintains the same API signatures as the C# version with these differences:
|
|
249
|
+
|
|
250
|
+
1. **Case Convention**: TypeScript methods use camelCase (e.g., `getLogger()`) instead of PascalCase
|
|
251
|
+
2. **Generic Overloads**: Instead of C# generic type parameters, TypeScript uses rest parameters (`...params: any[]`)
|
|
252
|
+
3. **Events**: Instead of C# events, use `onUpdated(callback)` method to register callbacks
|
|
253
|
+
4. **Properties**: C# properties become TypeScript getters/setters
|
|
254
|
+
5. **No Unity**: This port excludes all Unity-specific features
|
|
255
|
+
|
|
256
|
+
## Running the Demo
|
|
257
|
+
|
|
258
|
+
```bash
|
|
259
|
+
# Compile
|
|
260
|
+
tsc echo.demo.ts
|
|
261
|
+
|
|
262
|
+
# Run
|
|
263
|
+
node echo.demo.js
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
## Running Tests
|
|
267
|
+
|
|
268
|
+
```bash
|
|
269
|
+
# Compile and run tests
|
|
270
|
+
tsc && node echo.test.js
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
## License
|
|
274
|
+
|
|
275
|
+
Copyright © 2025 Racso
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Echo provider for Angular dependency injection.
|
|
3
|
+
* Provides a singleton Echo instance with console writer.
|
|
4
|
+
*/
|
|
5
|
+
import { InjectionToken, Provider } from '@angular/core';
|
|
6
|
+
import { Echo, EchoConsole } from './echo';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Injection token for Echo logger instance
|
|
10
|
+
*/
|
|
11
|
+
export const ECHO = new InjectionToken<Echo>('Echo Logger Instance');
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Factory function to create Echo instance
|
|
15
|
+
*/
|
|
16
|
+
export function echoFactory(): Echo {
|
|
17
|
+
return EchoConsole.new();
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Provider for Echo logger
|
|
22
|
+
* Use this in your module providers or inject it in services
|
|
23
|
+
*/
|
|
24
|
+
export const ECHO_PROVIDER: Provider = {
|
|
25
|
+
provide: ECHO,
|
|
26
|
+
useFactory: echoFactory
|
|
27
|
+
};
|