@allternit/gizzi-code 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/WORKSPACE_INTEGRATION.md +290 -0
- package/chat-sessions/sessions-index.json +98 -0
- package/package.json +43 -0
- package/package.json.original +31 -0
- package/package.json.publish +47 -0
- package/src/bootstrap/state.d.ts +169 -0
- package/src/bootstrap/state.js +261 -0
- package/src/bootstrap/state.ts +371 -0
- package/src/context/pack-builder.d.ts +24 -0
- package/src/context/pack-builder.js +281 -0
- package/src/context/pack-builder.ts +334 -0
- package/src/hooks/useExitOnCtrlCD.d.ts +86 -0
- package/src/hooks/useExitOnCtrlCD.js +130 -0
- package/src/hooks/useExitOnCtrlCD.ts +202 -0
- package/src/hooks/useKeybinding.d.ts +89 -0
- package/src/hooks/useKeybinding.js +163 -0
- package/src/hooks/useKeybinding.ts +226 -0
- package/src/hooks/useTerminalSize.d.ts +41 -0
- package/src/hooks/useTerminalSize.js +90 -0
- package/src/hooks/useTerminalSize.ts +117 -0
- package/src/index.d.ts +23 -0
- package/src/index.js +24 -0
- package/src/index.ts +132 -0
- package/src/runtime/brand/brand.d.ts +488 -0
- package/src/runtime/brand/brand.js +464 -0
- package/src/runtime/brand/brand.ts +667 -0
- package/src/runtime/services/tokenEstimation.d.ts +187 -0
- package/src/runtime/services/tokenEstimation.js +423 -0
- package/src/runtime/services/tokenEstimation.ts +547 -0
- package/src/runtime/tools/Tool.d.ts +239 -0
- package/src/runtime/tools/Tool.js +285 -0
- package/src/runtime/tools/Tool.ts +524 -0
- package/src/scheduler/TaskScheduler.d.ts +98 -0
- package/src/scheduler/TaskScheduler.js +201 -0
- package/src/scheduler/TaskScheduler.ts +259 -0
- package/src/state/AppStateStore.d.ts +347 -0
- package/src/state/AppStateStore.js +439 -0
- package/src/state/AppStateStore.ts +658 -0
- package/src/state/WorkspaceStateStore.d.ts +43 -0
- package/src/state/WorkspaceStateStore.js +224 -0
- package/src/state/WorkspaceStateStore.ts +297 -0
- package/src/types/message.d.ts +190 -0
- package/src/types/message.js +77 -0
- package/src/types/message.ts +277 -0
- package/src/utils/format.d.ts +126 -0
- package/src/utils/format.js +230 -0
- package/src/utils/format.ts +336 -0
- package/src/utils/fullscreen.d.ts +161 -0
- package/src/utils/fullscreen.js +277 -0
- package/src/utils/fullscreen.ts +382 -0
- package/src/utils/sessionStorage.d.ts +210 -0
- package/src/utils/sessionStorage.js +435 -0
- package/src/utils/sessionStorage.ts +559 -0
- package/src/watcher/WorkspaceWatcher.d.ts +70 -0
- package/src/watcher/WorkspaceWatcher.js +191 -0
- package/src/watcher/WorkspaceWatcher.ts +251 -0
- package/src/workspace/loader.d.ts +30 -0
- package/src/workspace/loader.js +117 -0
- package/src/workspace/loader.ts +171 -0
- package/src/workspace/types.d.ts +91 -0
- package/src/workspace/types.js +7 -0
- package/src/workspace/types.ts +122 -0
- package/threads/thread_1775361175022.json +9 -0
- package/tsconfig.json +14 -0
|
@@ -0,0 +1,439 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Application state store with React integration
|
|
3
|
+
* @module state/AppStateStore
|
|
4
|
+
*
|
|
5
|
+
* Centralized state management using the store pattern with subscription-based
|
|
6
|
+
* reactivity. Provides React hooks for component integration.
|
|
7
|
+
*/
|
|
8
|
+
import { useState, useEffect, useCallback, useSyncExternalStore } from 'react';
|
|
9
|
+
/**
|
|
10
|
+
* Default user preferences
|
|
11
|
+
* @constant {UserPreferences}
|
|
12
|
+
*/
|
|
13
|
+
const DEFAULT_PREFERENCES = {
|
|
14
|
+
theme: 'system',
|
|
15
|
+
language: 'en',
|
|
16
|
+
notifications: {
|
|
17
|
+
enabled: true,
|
|
18
|
+
sound: true,
|
|
19
|
+
desktop: false,
|
|
20
|
+
},
|
|
21
|
+
editor: {
|
|
22
|
+
fontSize: 14,
|
|
23
|
+
fontFamily: 'monospace',
|
|
24
|
+
wordWrap: true,
|
|
25
|
+
lineNumbers: true,
|
|
26
|
+
tabSize: 2,
|
|
27
|
+
},
|
|
28
|
+
};
|
|
29
|
+
/**
|
|
30
|
+
* Initial application state
|
|
31
|
+
* @constant {AppState}
|
|
32
|
+
*/
|
|
33
|
+
const INITIAL_STATE = {
|
|
34
|
+
session: null,
|
|
35
|
+
preferences: DEFAULT_PREFERENCES,
|
|
36
|
+
messages: [],
|
|
37
|
+
connectionStatus: 'connecting',
|
|
38
|
+
isLoading: false,
|
|
39
|
+
loadingMessage: null,
|
|
40
|
+
errors: [],
|
|
41
|
+
currentView: '/',
|
|
42
|
+
viewState: {},
|
|
43
|
+
lastUpdated: new Date(),
|
|
44
|
+
};
|
|
45
|
+
/**
|
|
46
|
+
* Application state store class
|
|
47
|
+
* Implements the observable store pattern with subscription-based updates
|
|
48
|
+
*
|
|
49
|
+
* @example
|
|
50
|
+
* ```typescript
|
|
51
|
+
* const store = new AppStateStore();
|
|
52
|
+
* const unsubscribe = store.subscribe((state, prevState) => {
|
|
53
|
+
* console.log('State updated:', state);
|
|
54
|
+
* });
|
|
55
|
+
* store.setState({ isLoading: true });
|
|
56
|
+
* ```
|
|
57
|
+
*/
|
|
58
|
+
export class AppStateStore {
|
|
59
|
+
constructor(initialState = {}) {
|
|
60
|
+
this.listeners = new Set();
|
|
61
|
+
this.isBatching = false;
|
|
62
|
+
this.pendingUpdates = {};
|
|
63
|
+
this.state = {
|
|
64
|
+
...INITIAL_STATE,
|
|
65
|
+
...initialState,
|
|
66
|
+
lastUpdated: new Date(),
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Gets the current state
|
|
71
|
+
* @returns {Readonly<AppState>} The current application state
|
|
72
|
+
*/
|
|
73
|
+
getState() {
|
|
74
|
+
return Object.freeze({ ...this.state });
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Gets a derived value from state using a selector function
|
|
78
|
+
*
|
|
79
|
+
* @template T
|
|
80
|
+
* @param {StateSelector<T>} selector - Function to extract value from state
|
|
81
|
+
* @returns {T} The selected value
|
|
82
|
+
* @example
|
|
83
|
+
* ```typescript
|
|
84
|
+
* const userName = store.select(state => state.session?.displayName);
|
|
85
|
+
* ```
|
|
86
|
+
*/
|
|
87
|
+
select(selector) {
|
|
88
|
+
return selector(this.state);
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Updates the state with partial changes
|
|
92
|
+
* Automatically notifies all subscribers
|
|
93
|
+
*
|
|
94
|
+
* @param {Partial<AppState>} updates - Partial state updates
|
|
95
|
+
* @param {Object} [options] - Update options
|
|
96
|
+
* @param {boolean} [options.silent=false] - If true, does not notify subscribers
|
|
97
|
+
* @returns {Readonly<AppState>} The updated state
|
|
98
|
+
*/
|
|
99
|
+
setState(updates, options = {}) {
|
|
100
|
+
if (this.isBatching) {
|
|
101
|
+
this.pendingUpdates = { ...this.pendingUpdates, ...updates };
|
|
102
|
+
return this.getState();
|
|
103
|
+
}
|
|
104
|
+
const prevState = this.state;
|
|
105
|
+
this.state = {
|
|
106
|
+
...this.state,
|
|
107
|
+
...updates,
|
|
108
|
+
// Merge nested objects
|
|
109
|
+
preferences: updates.preferences
|
|
110
|
+
? { ...this.state.preferences, ...updates.preferences }
|
|
111
|
+
: this.state.preferences,
|
|
112
|
+
viewState: updates.viewState
|
|
113
|
+
? { ...this.state.viewState, ...updates.viewState }
|
|
114
|
+
: this.state.viewState,
|
|
115
|
+
lastUpdated: new Date(),
|
|
116
|
+
};
|
|
117
|
+
if (!options.silent) {
|
|
118
|
+
this.notifyListeners(prevState);
|
|
119
|
+
}
|
|
120
|
+
return this.getState();
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Batches multiple state updates into a single notification
|
|
124
|
+
*
|
|
125
|
+
* @param {() => void} callback - Function containing state updates to batch
|
|
126
|
+
* @returns {Readonly<AppState>} The final state after all updates
|
|
127
|
+
* @example
|
|
128
|
+
* ```typescript
|
|
129
|
+
* store.batch(() => {
|
|
130
|
+
* store.setState({ isLoading: true });
|
|
131
|
+
* store.setState({ loadingMessage: 'Fetching data...' });
|
|
132
|
+
* });
|
|
133
|
+
* // Subscribers are notified once after both updates
|
|
134
|
+
* ```
|
|
135
|
+
*/
|
|
136
|
+
batch(callback) {
|
|
137
|
+
this.isBatching = true;
|
|
138
|
+
try {
|
|
139
|
+
callback();
|
|
140
|
+
}
|
|
141
|
+
finally {
|
|
142
|
+
this.isBatching = false;
|
|
143
|
+
const pending = this.pendingUpdates;
|
|
144
|
+
this.pendingUpdates = {};
|
|
145
|
+
if (Object.keys(pending).length > 0) {
|
|
146
|
+
return this.setState(pending);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
return this.getState();
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Subscribes to state changes
|
|
153
|
+
*
|
|
154
|
+
* @param {StateListener} listener - Callback invoked on state changes
|
|
155
|
+
* @returns {() => void} Unsubscribe function
|
|
156
|
+
*/
|
|
157
|
+
subscribe(listener) {
|
|
158
|
+
this.listeners.add(listener);
|
|
159
|
+
// Immediately notify with current state
|
|
160
|
+
listener(this.state, this.state);
|
|
161
|
+
return () => {
|
|
162
|
+
this.listeners.delete(listener);
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Subscribes to a specific derived value using a selector
|
|
167
|
+
* Only notifies when the selected value changes
|
|
168
|
+
*
|
|
169
|
+
* @template T
|
|
170
|
+
* @param {StateSelector<T>} selector - Function to extract value
|
|
171
|
+
* @param {(value: T) => void} callback - Callback invoked when value changes
|
|
172
|
+
* @returns {() => void} Unsubscribe function
|
|
173
|
+
* @example
|
|
174
|
+
* ```typescript
|
|
175
|
+
* store.subscribeTo(
|
|
176
|
+
* state => state.connectionStatus,
|
|
177
|
+
* status => console.log('Connection:', status)
|
|
178
|
+
* );
|
|
179
|
+
* ```
|
|
180
|
+
*/
|
|
181
|
+
subscribeTo(selector, callback) {
|
|
182
|
+
let lastValue = selector(this.state);
|
|
183
|
+
return this.subscribe((state) => {
|
|
184
|
+
const newValue = selector(state);
|
|
185
|
+
if (newValue !== lastValue) {
|
|
186
|
+
lastValue = newValue;
|
|
187
|
+
callback(newValue);
|
|
188
|
+
}
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* Resets the store to initial state
|
|
193
|
+
*
|
|
194
|
+
* @param {Partial<AppState>} [mergeState] - Optional state to merge after reset
|
|
195
|
+
* @returns {Readonly<AppState>} The reset state
|
|
196
|
+
*/
|
|
197
|
+
reset(mergeState = {}) {
|
|
198
|
+
const prevState = this.state;
|
|
199
|
+
this.state = {
|
|
200
|
+
...INITIAL_STATE,
|
|
201
|
+
...mergeState,
|
|
202
|
+
lastUpdated: new Date(),
|
|
203
|
+
};
|
|
204
|
+
this.notifyListeners(prevState);
|
|
205
|
+
return this.getState();
|
|
206
|
+
}
|
|
207
|
+
/**
|
|
208
|
+
* Adds an error to the state
|
|
209
|
+
*
|
|
210
|
+
* @param {Omit<AppError, 'id' | 'timestamp' | 'dismissed'>} error - Error details
|
|
211
|
+
* @returns {Readonly<AppState>} The updated state
|
|
212
|
+
*/
|
|
213
|
+
addError(error) {
|
|
214
|
+
const newError = {
|
|
215
|
+
...error,
|
|
216
|
+
id: `err_${Date.now()}_${Math.random().toString(36).substring(2, 11)}`,
|
|
217
|
+
timestamp: new Date(),
|
|
218
|
+
dismissed: false,
|
|
219
|
+
};
|
|
220
|
+
return this.setState({
|
|
221
|
+
errors: [...this.state.errors, newError],
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
/**
|
|
225
|
+
* Dismisses an error by ID
|
|
226
|
+
*
|
|
227
|
+
* @param {string} errorId - The error ID to dismiss
|
|
228
|
+
* @returns {Readonly<AppState>} The updated state
|
|
229
|
+
*/
|
|
230
|
+
dismissError(errorId) {
|
|
231
|
+
return this.setState({
|
|
232
|
+
errors: this.state.errors.map((err) => err.id === errorId ? { ...err, dismissed: true } : err),
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
/**
|
|
236
|
+
* Clears all dismissed errors
|
|
237
|
+
*
|
|
238
|
+
* @returns {Readonly<AppState>} The updated state
|
|
239
|
+
*/
|
|
240
|
+
clearDismissedErrors() {
|
|
241
|
+
return this.setState({
|
|
242
|
+
errors: this.state.errors.filter((err) => !err.dismissed),
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
/**
|
|
246
|
+
* Adds a message to the conversation
|
|
247
|
+
*
|
|
248
|
+
* @param {TypedMessage} message - The message to add
|
|
249
|
+
* @returns {Readonly<AppState>} The updated state
|
|
250
|
+
*/
|
|
251
|
+
addMessage(message) {
|
|
252
|
+
return this.setState({
|
|
253
|
+
messages: [...this.state.messages, message],
|
|
254
|
+
});
|
|
255
|
+
}
|
|
256
|
+
/**
|
|
257
|
+
* Clears all messages
|
|
258
|
+
*
|
|
259
|
+
* @returns {Readonly<AppState>} The updated state
|
|
260
|
+
*/
|
|
261
|
+
clearMessages() {
|
|
262
|
+
return this.setState({ messages: [] });
|
|
263
|
+
}
|
|
264
|
+
/**
|
|
265
|
+
* Sets the user session
|
|
266
|
+
*
|
|
267
|
+
* @param {UserSession | null} session - The session or null to logout
|
|
268
|
+
* @returns {Readonly<AppState>} The updated state
|
|
269
|
+
*/
|
|
270
|
+
setSession(session) {
|
|
271
|
+
return this.setState({ session });
|
|
272
|
+
}
|
|
273
|
+
/**
|
|
274
|
+
* Updates user preferences
|
|
275
|
+
*
|
|
276
|
+
* @param {Partial<UserPreferences>} prefs - Preference updates
|
|
277
|
+
* @returns {Readonly<AppState>} The updated state
|
|
278
|
+
*/
|
|
279
|
+
setPreferences(prefs) {
|
|
280
|
+
return this.setState({
|
|
281
|
+
preferences: { ...this.state.preferences, ...prefs },
|
|
282
|
+
});
|
|
283
|
+
}
|
|
284
|
+
/**
|
|
285
|
+
* Sets the loading state
|
|
286
|
+
*
|
|
287
|
+
* @param {boolean} isLoading - Whether loading is active
|
|
288
|
+
* @param {string} [message] - Optional loading message
|
|
289
|
+
* @returns {Readonly<AppState>} The updated state
|
|
290
|
+
*/
|
|
291
|
+
setLoading(isLoading, message) {
|
|
292
|
+
return this.setState({
|
|
293
|
+
isLoading,
|
|
294
|
+
loadingMessage: isLoading ? message ?? null : null,
|
|
295
|
+
});
|
|
296
|
+
}
|
|
297
|
+
/**
|
|
298
|
+
* Updates view-specific state
|
|
299
|
+
*
|
|
300
|
+
* @param {string} view - View identifier
|
|
301
|
+
* @param {unknown} data - View state data
|
|
302
|
+
* @returns {Readonly<AppState>} The updated state
|
|
303
|
+
*/
|
|
304
|
+
setViewState(view, data) {
|
|
305
|
+
return this.setState({
|
|
306
|
+
currentView: view,
|
|
307
|
+
viewState: { ...this.state.viewState, [view]: data },
|
|
308
|
+
});
|
|
309
|
+
}
|
|
310
|
+
notifyListeners(prevState) {
|
|
311
|
+
queueMicrotask(() => {
|
|
312
|
+
this.listeners.forEach((listener) => {
|
|
313
|
+
try {
|
|
314
|
+
listener(this.state, prevState);
|
|
315
|
+
}
|
|
316
|
+
catch (error) {
|
|
317
|
+
console.error('AppStateStore listener error:', error);
|
|
318
|
+
}
|
|
319
|
+
});
|
|
320
|
+
});
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
// Global store instance
|
|
324
|
+
let globalStore = null;
|
|
325
|
+
/**
|
|
326
|
+
* Gets or creates the global AppStateStore instance
|
|
327
|
+
*
|
|
328
|
+
* @returns {AppStateStore} The global store instance
|
|
329
|
+
*/
|
|
330
|
+
export function getGlobalStore() {
|
|
331
|
+
if (!globalStore) {
|
|
332
|
+
globalStore = new AppStateStore();
|
|
333
|
+
}
|
|
334
|
+
return globalStore;
|
|
335
|
+
}
|
|
336
|
+
/**
|
|
337
|
+
* Sets the global store instance (useful for testing)
|
|
338
|
+
*
|
|
339
|
+
* @param {AppStateStore | null} store - The store instance or null to reset
|
|
340
|
+
*/
|
|
341
|
+
export function setGlobalStore(store) {
|
|
342
|
+
globalStore = store;
|
|
343
|
+
}
|
|
344
|
+
/**
|
|
345
|
+
* React hook for accessing the app state store
|
|
346
|
+
* Provides reactive updates and selector support
|
|
347
|
+
*
|
|
348
|
+
* @template T
|
|
349
|
+
* @param {StateSelector<T>} [selector] - Optional selector function
|
|
350
|
+
* @returns {T | AppState} The selected state or full state
|
|
351
|
+
* @example
|
|
352
|
+
* ```typescript
|
|
353
|
+
* // Get full state
|
|
354
|
+
* const state = useAppStateStore();
|
|
355
|
+
*
|
|
356
|
+
* // Get specific value
|
|
357
|
+
* const isLoading = useAppStateStore(state => state.isLoading);
|
|
358
|
+
*
|
|
359
|
+
* // Get derived value
|
|
360
|
+
* const messageCount = useAppStateStore(state => state.messages.length);
|
|
361
|
+
* ```
|
|
362
|
+
*/
|
|
363
|
+
export function useAppStateStore(selector) {
|
|
364
|
+
const store = getGlobalStore();
|
|
365
|
+
return useSyncExternalStore((callback) => store.subscribe(() => callback()), () => {
|
|
366
|
+
const state = store.getState();
|
|
367
|
+
return selector ? selector(state) : state;
|
|
368
|
+
}, () => {
|
|
369
|
+
const state = store.getState();
|
|
370
|
+
return selector ? selector(state) : state;
|
|
371
|
+
});
|
|
372
|
+
}
|
|
373
|
+
/**
|
|
374
|
+
* React hook for accessing a specific slice of state with optional transformation
|
|
375
|
+
*
|
|
376
|
+
* @template T
|
|
377
|
+
* @param {StateSelector<T>} selector - Function to extract value from state
|
|
378
|
+
* @param {(left: T, right: T) => boolean} [isEqual] - Custom equality function
|
|
379
|
+
* @returns {T} The selected value
|
|
380
|
+
* @example
|
|
381
|
+
* ```typescript
|
|
382
|
+
* const userName = useAppStateSelector(state => state.session?.displayName);
|
|
383
|
+
* ```
|
|
384
|
+
*/
|
|
385
|
+
export function useAppStateSelector(selector, isEqual = Object.is) {
|
|
386
|
+
const store = getGlobalStore();
|
|
387
|
+
const [selectedState, setSelectedState] = useState(() => selector(store.getState()));
|
|
388
|
+
useEffect(() => {
|
|
389
|
+
let lastSelectedState = selectedState;
|
|
390
|
+
return store.subscribe((state) => {
|
|
391
|
+
const newSelectedState = selector(state);
|
|
392
|
+
if (!isEqual(lastSelectedState, newSelectedState)) {
|
|
393
|
+
lastSelectedState = newSelectedState;
|
|
394
|
+
setSelectedState(newSelectedState);
|
|
395
|
+
}
|
|
396
|
+
});
|
|
397
|
+
}, [selector, isEqual, store]);
|
|
398
|
+
return selectedState;
|
|
399
|
+
}
|
|
400
|
+
/**
|
|
401
|
+
* React hook for state setters
|
|
402
|
+
* Returns stable callback functions for common state operations
|
|
403
|
+
*
|
|
404
|
+
* @returns {Object} Object containing state setter callbacks
|
|
405
|
+
* @example
|
|
406
|
+
* ```typescript
|
|
407
|
+
* const { setLoading, addMessage, setSession } = useAppStateActions();
|
|
408
|
+
* ```
|
|
409
|
+
*/
|
|
410
|
+
export function useAppStateActions() {
|
|
411
|
+
const store = getGlobalStore();
|
|
412
|
+
const setState = useCallback((updates) => store.setState(updates), [store]);
|
|
413
|
+
const setLoading = useCallback((isLoading, message) => store.setLoading(isLoading, message), [store]);
|
|
414
|
+
const addMessage = useCallback((message) => store.addMessage(message), [store]);
|
|
415
|
+
const setSession = useCallback((session) => store.setSession(session), [store]);
|
|
416
|
+
const addError = useCallback((error) => store.addError(error), [store]);
|
|
417
|
+
const dismissError = useCallback((errorId) => store.dismissError(errorId), [store]);
|
|
418
|
+
const setPreferences = useCallback((prefs) => store.setPreferences(prefs), [store]);
|
|
419
|
+
const clearMessages = useCallback(() => store.clearMessages(), [store]);
|
|
420
|
+
return {
|
|
421
|
+
setState,
|
|
422
|
+
setLoading,
|
|
423
|
+
addMessage,
|
|
424
|
+
setSession,
|
|
425
|
+
addError,
|
|
426
|
+
dismissError,
|
|
427
|
+
setPreferences,
|
|
428
|
+
clearMessages,
|
|
429
|
+
};
|
|
430
|
+
}
|
|
431
|
+
// Default export
|
|
432
|
+
export default {
|
|
433
|
+
AppStateStore,
|
|
434
|
+
getGlobalStore,
|
|
435
|
+
setGlobalStore,
|
|
436
|
+
useAppStateStore,
|
|
437
|
+
useAppStateSelector,
|
|
438
|
+
useAppStateActions,
|
|
439
|
+
};
|