@yartsun/chat-widget-types 1.0.15 → 1.0.17
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 +20 -0
- package/dist/config.types.d.ts +46 -9
- package/dist/config.types.d.ts.map +1 -1
- package/dist/default-config.d.ts +1 -1
- package/dist/default-config.d.ts.map +1 -1
- package/dist/default-config.js +2 -319
- package/dist/default-config.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/migration/commands.d.ts +1 -0
- package/dist/migration/commands.d.ts.map +1 -1
- package/dist/migration/commands.js +58 -2
- package/dist/migration/commands.js.map +1 -1
- package/dist/migration/examples.d.ts +1 -1
- package/dist/migration/facade.d.ts +8 -0
- package/dist/migration/facade.d.ts.map +1 -1
- package/dist/migration/facade.js +22 -2
- package/dist/migration/facade.js.map +1 -1
- package/dist/migration/migrator.d.ts +1 -0
- package/dist/migration/migrator.d.ts.map +1 -1
- package/dist/migration/migrator.js +23 -4
- package/dist/migration/migrator.js.map +1 -1
- package/dist/migration/strategies.d.ts +52 -0
- package/dist/migration/strategies.d.ts.map +1 -1
- package/dist/migration/strategies.js +277 -4
- package/dist/migration/strategies.js.map +1 -1
- package/dist/migration/types.d.ts +9 -1
- package/dist/migration/types.d.ts.map +1 -1
- package/dist/migration/types.js +7 -1
- package/dist/migration/types.js.map +1 -1
- package/package.json +25 -2
- package/src/config.types.ts +50 -10
- package/src/default-config.ts +3 -329
- package/src/index.ts +5 -3
- package/src/migration/commands.ts +65 -2
- package/src/migration/facade.ts +23 -2
- package/src/migration/migrator.ts +22 -6
- package/src/migration/strategies.ts +291 -4
- package/src/migration/types.ts +11 -1
- package/themes/configV3-shine.json +369 -0
- package/themes/configV3-soft.json +369 -0
- package/themes/configV3-ultra.json +369 -0
- package/themes/configV3.json +370 -0
|
@@ -231,11 +231,298 @@ export const V1_TO_V2_STRATEGIES: MigrationStrategy[] = [
|
|
|
231
231
|
new AddBottomElementsV1toV2Strategy()
|
|
232
232
|
]
|
|
233
233
|
|
|
234
|
+
/** Стратегия добавления theme и container в settings для V2->V3 */
|
|
235
|
+
export class AddThemeAndContainerV2toV3Strategy extends BaseMigrationStrategy {
|
|
236
|
+
name = 'AddThemeAndContainerV2toV3'
|
|
237
|
+
description = 'Adds theme and container (innerBorder, outerBorder, gradient) to settings'
|
|
238
|
+
appliesTo = { from: '2.0' as const, to: '3.0' as const }
|
|
239
|
+
|
|
240
|
+
apply(context: MigrationContext): MigrationStepResult {
|
|
241
|
+
try {
|
|
242
|
+
const config = { ...context.config }
|
|
243
|
+
const warnings: string[] = []
|
|
244
|
+
|
|
245
|
+
// Добавляем theme если его нет
|
|
246
|
+
if (!config.settings.theme) {
|
|
247
|
+
config.settings.theme = DEFAULT_CONFIG.settings.theme // 'auto'
|
|
248
|
+
warnings.push(`Добавлено поле theme: '${config.settings.theme}'`)
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// Добавляем container если его нет
|
|
252
|
+
if (!config.settings.container) {
|
|
253
|
+
config.settings.container = {
|
|
254
|
+
innerBorder: {
|
|
255
|
+
width: DEFAULT_CONFIG.settings.container?.innerBorder?.width || 0,
|
|
256
|
+
color: DEFAULT_CONFIG.settings.container?.innerBorder?.color || 'transparent',
|
|
257
|
+
style: DEFAULT_CONFIG.settings.container?.innerBorder?.style || 'solid',
|
|
258
|
+
},
|
|
259
|
+
outerBorder: {
|
|
260
|
+
width: DEFAULT_CONFIG.settings.container?.outerBorder?.width || 0,
|
|
261
|
+
color: DEFAULT_CONFIG.settings.container?.outerBorder?.color || 'transparent',
|
|
262
|
+
style: DEFAULT_CONFIG.settings.container?.outerBorder?.style || 'solid',
|
|
263
|
+
},
|
|
264
|
+
gradient: DEFAULT_CONFIG.settings.container?.gradient || 'transparent',
|
|
265
|
+
}
|
|
266
|
+
warnings.push('Added container object with innerBorder, outerBorder, gradient')
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// Конвертируем borderRadius из строки в число если нужно
|
|
270
|
+
if (typeof config.settings.borderRadius === 'string') {
|
|
271
|
+
const borderRadiusMap: Record<string, number> = {
|
|
272
|
+
'0': 0,
|
|
273
|
+
'sm': 4,
|
|
274
|
+
'md': 8,
|
|
275
|
+
'lg': 12,
|
|
276
|
+
}
|
|
277
|
+
config.settings.borderRadius = borderRadiusMap[config.settings.borderRadius] ?? 12
|
|
278
|
+
warnings.push(`Конвертирован borderRadius из строки в число: ${config.settings.borderRadius}`)
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
return this.createSuccessResult(config, warnings)
|
|
282
|
+
} catch (error) {
|
|
283
|
+
return this.createErrorResult([`Ошибка при добавлении theme и container: ${error}`])
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/** Стратегия добавления border, sideMargin, borderRadius в top.params для V2->V3 */
|
|
289
|
+
export class AddTopParamsV2toV3Strategy extends BaseMigrationStrategy {
|
|
290
|
+
name = 'AddTopParamsV2toV3'
|
|
291
|
+
description = 'Добавляет border, sideMargin, borderRadius в sections.top.params'
|
|
292
|
+
appliesTo = { from: '2.0' as const, to: '3.0' as const }
|
|
293
|
+
|
|
294
|
+
apply(context: MigrationContext): MigrationStepResult {
|
|
295
|
+
try {
|
|
296
|
+
const config = { ...context.config }
|
|
297
|
+
const warnings: string[] = []
|
|
298
|
+
|
|
299
|
+
if (!config.sections?.top?.params) {
|
|
300
|
+
config.sections.top = config.sections.top || {}
|
|
301
|
+
config.sections.top.params = {}
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
// Добавляем border если его нет
|
|
305
|
+
if (!config.sections.top.params.border) {
|
|
306
|
+
config.sections.top.params.border = {
|
|
307
|
+
width: DEFAULT_CONFIG.sections.top.params.border?.width || 0,
|
|
308
|
+
color: DEFAULT_CONFIG.sections.top.params.border?.color || 'transparent',
|
|
309
|
+
}
|
|
310
|
+
warnings.push('Добавлено поле border в sections.top.params')
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
// Добавляем sideMargin если его нет
|
|
314
|
+
if (config.sections.top.params.sideMargin === undefined) {
|
|
315
|
+
config.sections.top.params.sideMargin = DEFAULT_CONFIG.sections.top.params.sideMargin || 0
|
|
316
|
+
warnings.push(`Добавлено поле sideMargin: ${config.sections.top.params.sideMargin}`)
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
// Добавляем borderRadius если его нет
|
|
320
|
+
if (config.sections.top.params.borderRadius === undefined) {
|
|
321
|
+
config.sections.top.params.borderRadius = DEFAULT_CONFIG.sections.top.params.borderRadius || 12
|
|
322
|
+
warnings.push(`Добавлено поле borderRadius: ${config.sections.top.params.borderRadius}`)
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
return this.createSuccessResult(config, warnings)
|
|
326
|
+
} catch (error) {
|
|
327
|
+
return this.createErrorResult([`Ошибка при добавлении полей top.params: ${error}`])
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
/** Стратегия добавления dataAction в inside для V2->V3 */
|
|
333
|
+
export class AddDataActionV2toV3Strategy extends BaseMigrationStrategy {
|
|
334
|
+
name = 'AddDataActionV2toV3'
|
|
335
|
+
description = 'Добавляет dataAction в sections.inside'
|
|
336
|
+
appliesTo = { from: '2.0' as const, to: '3.0' as const }
|
|
337
|
+
|
|
338
|
+
apply(context: MigrationContext): MigrationStepResult {
|
|
339
|
+
try {
|
|
340
|
+
const config = { ...context.config }
|
|
341
|
+
const warnings: string[] = []
|
|
342
|
+
|
|
343
|
+
if (!config.sections?.inside) {
|
|
344
|
+
config.sections.inside = {}
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
// Normalize dataAction to the V3 object shape (supports legacy string configs).
|
|
348
|
+
const defaultDataActionRaw = (DEFAULT_CONFIG as any)?.sections?.inside?.dataAction
|
|
349
|
+
const defaultDataAction =
|
|
350
|
+
defaultDataActionRaw && typeof defaultDataActionRaw === 'object'
|
|
351
|
+
? defaultDataActionRaw
|
|
352
|
+
: {
|
|
353
|
+
type: 'steps',
|
|
354
|
+
headerFillColor: '#595959',
|
|
355
|
+
headerText: '#fff',
|
|
356
|
+
bodyFillColor: '#595959',
|
|
357
|
+
activeStepColor: '#fff',
|
|
358
|
+
pastStepColor: '#fff',
|
|
359
|
+
strokeColor: '#595959',
|
|
360
|
+
strokeWidth: 0,
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
const cloneDefault = () => JSON.parse(JSON.stringify(defaultDataAction))
|
|
364
|
+
|
|
365
|
+
const current = (config.sections.inside as any).dataAction
|
|
366
|
+
if (!current) {
|
|
367
|
+
;(config.sections.inside as any).dataAction = cloneDefault()
|
|
368
|
+
warnings.push(`Added dataAction object (type: '${(config.sections.inside as any).dataAction.type}')`)
|
|
369
|
+
} else if (typeof current === 'string') {
|
|
370
|
+
;(config.sections.inside as any).dataAction = {
|
|
371
|
+
...cloneDefault(),
|
|
372
|
+
type: current,
|
|
373
|
+
}
|
|
374
|
+
warnings.push(`Upgraded dataAction from string to object (type: '${current}')`)
|
|
375
|
+
} else if (typeof current === 'object') {
|
|
376
|
+
// Fill missing fields from defaults, but keep user's existing values.
|
|
377
|
+
;(config.sections.inside as any).dataAction = {
|
|
378
|
+
...cloneDefault(),
|
|
379
|
+
...current,
|
|
380
|
+
}
|
|
381
|
+
if (!(config.sections.inside as any).dataAction.type) {
|
|
382
|
+
;(config.sections.inside as any).dataAction.type = cloneDefault().type
|
|
383
|
+
warnings.push(`Filled missing dataAction.type with default ('${(config.sections.inside as any).dataAction.type}')`)
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
return this.createSuccessResult(config, warnings)
|
|
388
|
+
} catch (error) {
|
|
389
|
+
return this.createErrorResult([`Ошибка при добавлении dataAction: ${error}`])
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
/** Стратегия расширения welcomeMessage для V2->V3 */
|
|
395
|
+
export class EnhanceWelcomeMessageV2toV3Strategy extends BaseMigrationStrategy {
|
|
396
|
+
name = 'EnhanceWelcomeMessageV2toV3'
|
|
397
|
+
description = 'Расширяет welcomeMessage с borderColor и другими полями'
|
|
398
|
+
appliesTo = { from: '2.0' as const, to: '3.0' as const }
|
|
399
|
+
|
|
400
|
+
apply(context: MigrationContext): MigrationStepResult {
|
|
401
|
+
try {
|
|
402
|
+
const config = { ...context.config }
|
|
403
|
+
const warnings: string[] = []
|
|
404
|
+
|
|
405
|
+
if (!config.sections?.inside?.welcomeMessage) {
|
|
406
|
+
config.sections.inside.welcomeMessage = {}
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
const welcomeMsg = config.sections.inside.welcomeMessage
|
|
410
|
+
const defaultWelcome = DEFAULT_CONFIG.sections.inside.welcomeMessage
|
|
411
|
+
|
|
412
|
+
// Добавляем недостающие поля
|
|
413
|
+
if (!welcomeMsg.borderColor && defaultWelcome.borderColor) {
|
|
414
|
+
welcomeMsg.borderColor = defaultWelcome.borderColor
|
|
415
|
+
warnings.push(`Добавлено поле borderColor для welcomeMessage`)
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
if (!welcomeMsg.bgType && defaultWelcome.bgType) {
|
|
419
|
+
welcomeMsg.bgType = defaultWelcome.bgType
|
|
420
|
+
warnings.push(`Добавлено поле bgType для welcomeMessage: '${welcomeMsg.bgType}'`)
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
if (!welcomeMsg.bgColor && defaultWelcome.bgColor) {
|
|
424
|
+
welcomeMsg.bgColor = defaultWelcome.bgColor
|
|
425
|
+
warnings.push(`Добавлено поле bgColor для welcomeMessage`)
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
if (welcomeMsg.borderWidth === undefined && defaultWelcome.borderWidth !== undefined) {
|
|
429
|
+
welcomeMsg.borderWidth = defaultWelcome.borderWidth
|
|
430
|
+
warnings.push(`Добавлено поле borderWidth для welcomeMessage`)
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
if (!welcomeMsg.fontSize && defaultWelcome.fontSize) {
|
|
434
|
+
welcomeMsg.fontSize = defaultWelcome.fontSize
|
|
435
|
+
warnings.push(`Добавлено поле fontSize для welcomeMessage`)
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
if (!welcomeMsg.size && defaultWelcome.size) {
|
|
439
|
+
welcomeMsg.size = defaultWelcome.size
|
|
440
|
+
warnings.push(`Добавлено поле size для welcomeMessage`)
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
if (!welcomeMsg.borderRadius && defaultWelcome.borderRadius) {
|
|
444
|
+
welcomeMsg.borderRadius = defaultWelcome.borderRadius
|
|
445
|
+
warnings.push(`Добавлено поле borderRadius для welcomeMessage`)
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
return this.createSuccessResult(config, warnings)
|
|
449
|
+
} catch (error) {
|
|
450
|
+
return this.createErrorResult([`Ошибка при расширении welcomeMessage: ${error}`])
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
/** Стратегия добавления btnVoice в bottom для V2->V3 */
|
|
456
|
+
export class AddVoiceButtonV2toV3Strategy extends BaseMigrationStrategy {
|
|
457
|
+
name = 'AddVoiceButtonV2toV3'
|
|
458
|
+
description = 'Добавляет btnVoice в sections.bottom'
|
|
459
|
+
appliesTo = { from: '2.0' as const, to: '3.0' as const }
|
|
460
|
+
|
|
461
|
+
apply(context: MigrationContext): MigrationStepResult {
|
|
462
|
+
try {
|
|
463
|
+
const config = { ...context.config }
|
|
464
|
+
const warnings: string[] = []
|
|
465
|
+
|
|
466
|
+
if (!config.sections?.bottom) {
|
|
467
|
+
config.sections.bottom = {}
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
// Добавляем btnVoice если его нет
|
|
471
|
+
if (!config.sections.bottom.btnVoice) {
|
|
472
|
+
config.sections.bottom.btnVoice = {
|
|
473
|
+
color: DEFAULT_CONFIG.sections.bottom.btnVoice?.color || '#fff',
|
|
474
|
+
bgColor: DEFAULT_CONFIG.sections.bottom.btnVoice?.bgColor || 'rgba(255, 255, 255, 0.10)',
|
|
475
|
+
showType: DEFAULT_CONFIG.sections.bottom.btnVoice?.showType || 'icon',
|
|
476
|
+
borderRadius: DEFAULT_CONFIG.sections.bottom.btnVoice?.borderRadius || 50,
|
|
477
|
+
borderColor: DEFAULT_CONFIG.sections.bottom.btnVoice?.borderColor || '#595959',
|
|
478
|
+
borderWidth: DEFAULT_CONFIG.sections.bottom.btnVoice?.borderWidth || 1,
|
|
479
|
+
size: DEFAULT_CONFIG.sections.bottom.btnVoice?.size || 'md',
|
|
480
|
+
fontSize: DEFAULT_CONFIG.sections.bottom.btnVoice?.fontSize || 12,
|
|
481
|
+
icon: DEFAULT_CONFIG.sections.bottom.btnVoice?.icon || {
|
|
482
|
+
showIcon: true,
|
|
483
|
+
src: '<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M9 1.5C8.20156 1.5 7.5 2.20156 7.5 3V9C7.5 9.79844 8.20156 10.5 9 10.5C9.79844 10.5 10.5 9.79844 10.5 9V3C10.5 2.20156 9.79844 1.5 9 1.5Z" fill="currentColor"/><path d="M13.5 7.5C13.0852 7.5 12.75 7.83516 12.75 8.25V9C12.75 10.2422 11.7422 11.25 10.5 11.25C9.25781 11.25 8.25 10.2422 8.25 9V8.25C8.25 7.83516 7.91484 7.5 7.5 7.5C7.08516 7.5 6.75 7.83516 6.75 8.25V9C6.75 10.8609 8.13906 12.375 9.9375 12.6562V14.25H7.5C7.08516 14.25 6.75 14.5852 6.75 15C6.75 15.4148 7.08516 15.75 7.5 15.75H10.5C10.9148 15.75 11.25 15.4148 11.25 15C11.25 14.5852 10.9148 14.25 10.5 14.25H10.3125V12.6562C12.1109 12.375 13.5 10.8609 13.5 9V8.25C13.5 7.83516 13.1648 7.5 12.75 7.5Z" fill="currentColor"/></svg>',
|
|
484
|
+
color: '#fff',
|
|
485
|
+
size: 18,
|
|
486
|
+
},
|
|
487
|
+
}
|
|
488
|
+
if(!config.sections.bottom.btnVoice.bgColor) {
|
|
489
|
+
config.sections.bottom.btnVoice.bgColor = DEFAULT_CONFIG.sections.bottom.btnVoice.bgColor
|
|
490
|
+
warnings.push(`Добавлено поле bgColor для btnVoice: '${config.sections.bottom.btnVoice.bgColor}'`)
|
|
491
|
+
}
|
|
492
|
+
warnings.push('Добавлен btnVoice в sections.bottom')
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
return this.createSuccessResult(config, warnings)
|
|
496
|
+
} catch (error) {
|
|
497
|
+
return this.createErrorResult([`Ошибка при добавлении btnVoice: ${error}`])
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
/** Реестр всех стратегий миграции V2->V3 */
|
|
503
|
+
export const V2_TO_V3_STRATEGIES: MigrationStrategy[] = [
|
|
504
|
+
new AddThemeAndContainerV2toV3Strategy(),
|
|
505
|
+
new AddTopParamsV2toV3Strategy(),
|
|
506
|
+
new AddDataActionV2toV3Strategy(),
|
|
507
|
+
new EnhanceWelcomeMessageV2toV3Strategy(),
|
|
508
|
+
new AddVoiceButtonV2toV3Strategy()
|
|
509
|
+
]
|
|
510
|
+
|
|
234
511
|
/** Получить все стратегии для конкретного перехода версий */
|
|
235
512
|
export function getStrategiesForMigration(from: string, to: string): MigrationStrategy[] {
|
|
513
|
+
// Reuse V2->V3 strategies for any 3.0->3.1 hop to avoid empty step.
|
|
514
|
+
if (from === '3.0' && to === '3.1') {
|
|
515
|
+
return V2_TO_V3_STRATEGIES.map(strategy => {
|
|
516
|
+
// Clone strategy to avoid mutating original appliesTo; preserve methods.
|
|
517
|
+
const clone = Object.create(strategy) as MigrationStrategy
|
|
518
|
+
clone.appliesTo = { from: '3.0' as const, to: '3.1' as const }
|
|
519
|
+
return clone
|
|
520
|
+
})
|
|
521
|
+
}
|
|
522
|
+
|
|
236
523
|
const allStrategies = [
|
|
237
|
-
...V1_TO_V2_STRATEGIES
|
|
238
|
-
|
|
524
|
+
...V1_TO_V2_STRATEGIES,
|
|
525
|
+
...V2_TO_V3_STRATEGIES
|
|
239
526
|
]
|
|
240
527
|
|
|
241
528
|
return allStrategies.filter(strategy =>
|
|
@@ -246,7 +533,7 @@ export function getStrategiesForMigration(from: string, to: string): MigrationSt
|
|
|
246
533
|
/** Получить все доступные стратегии */
|
|
247
534
|
export function getAllStrategies(): MigrationStrategy[] {
|
|
248
535
|
return [
|
|
249
|
-
...V1_TO_V2_STRATEGIES
|
|
250
|
-
|
|
536
|
+
...V1_TO_V2_STRATEGIES,
|
|
537
|
+
...V2_TO_V3_STRATEGIES
|
|
251
538
|
]
|
|
252
539
|
}
|
package/src/migration/types.ts
CHANGED
|
@@ -5,7 +5,16 @@
|
|
|
5
5
|
import { WidgetConfig } from '../config.types'
|
|
6
6
|
|
|
7
7
|
/** Версии конфигурации */
|
|
8
|
-
export type ConfigVersion = '1.0' | '2.0' | '3.0'
|
|
8
|
+
export type ConfigVersion = '1.0' | '2.0' | '3.0' | '3.1'
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Ordered list of supported config versions.
|
|
12
|
+
* Keep this in sync with `ConfigVersion` union above.
|
|
13
|
+
*/
|
|
14
|
+
export const CONFIG_VERSIONS = ['1.0', '2.0', '3.0', '3.1'] as const satisfies ReadonlyArray<ConfigVersion>
|
|
15
|
+
|
|
16
|
+
/** Latest supported config version. */
|
|
17
|
+
export const LATEST_CONFIG_VERSION: ConfigVersion = CONFIG_VERSIONS[CONFIG_VERSIONS.length - 1]
|
|
9
18
|
|
|
10
19
|
/** Результат миграции */
|
|
11
20
|
export interface MigrationResult<T = WidgetConfig> {
|
|
@@ -174,6 +183,7 @@ export type ConfigByVersion = {
|
|
|
174
183
|
'1.0': ConfigV1
|
|
175
184
|
'2.0': ConfigV2
|
|
176
185
|
'3.0': WidgetConfig // Для будущих версий
|
|
186
|
+
'3.1': WidgetConfig
|
|
177
187
|
}
|
|
178
188
|
|
|
179
189
|
/** Фабрика для создания пустых конфигураций */
|