From 052a5ab04902000caecb35e8e8f1c785fd11f56e Mon Sep 17 00:00:00 2001 From: siva kumar Date: Wed, 1 Aug 2018 18:32:05 +0530 Subject: [PATCH] [ADF-3141] ProcessList Enanchement (#3454) * * Process list enhancements * * Updated doc for the recent changes * * After rebase * * Require changes done * after rebase --- .../process-list.component.md | 2 +- .../process/process-instances-list.mock.ts | 30 +++ .../components/process-list.component.html | 2 + .../components/process-list.component.spec.ts | 217 ++++-------------- .../components/process-list.component.ts | 141 ++---------- package-lock.json | 2 +- 6 files changed, 99 insertions(+), 295 deletions(-) diff --git a/docs/process-services/process-list.component.md b/docs/process-services/process-list.component.md index e3fb0f6137..79cf8935de 100644 --- a/docs/process-services/process-list.component.md +++ b/docs/process-services/process-list.component.md @@ -43,7 +43,7 @@ Renders a list containing all the process instances matched by the parameters sp | Name | Type | Default value | Description | | -- | -- | -- | -- | | appId | `number` | | The id of the app. | -| data | [`DataTableAdapter`](../../lib/core/datatable/data/datatable-adapter.ts) | | Data source to define the datatable. | +| data | [`DataTableAdapter`](../../lib/core/datatable/data/datatable-adapter.ts) | |(**Deprecated:** 2.4.0) Data source to define the datatable.| | multiselect | `boolean` | false | Toggles multiple row selection, which renders checkboxes at the beginning of each row | | page | `number` | 0 | The page number of the processes to fetch. | | presetColumn | `string` | | Name of a custom schema to fetch from `app.config.json`. | diff --git a/lib/process-services/mock/process/process-instances-list.mock.ts b/lib/process-services/mock/process/process-instances-list.mock.ts index 7d3c42df73..041640d4b2 100644 --- a/lib/process-services/mock/process/process-instances-list.mock.ts +++ b/lib/process-services/mock/process/process-instances-list.mock.ts @@ -15,6 +15,8 @@ * limitations under the License. */ +import { ObjectDataColumn } from '@alfresco/adf-core'; + export let fakeProcessInstance = { size: 2, total: 2, start: 0, data: [ @@ -87,3 +89,31 @@ export let fakeProcessInstancesWithNoName = { } ] }; + +export let fakeProcessCutomSchema = + [ + new ObjectDataColumn({ + 'key': 'fakeName', + 'type': 'text', + 'title': 'ADF_PROCESS_LIST.PROPERTIES.FAKE', + 'sortable': true + }), + new ObjectDataColumn({ + 'key': 'fakeProcessName', + 'type': 'text', + 'title': 'ADF_PROCESS_LIST.PROPERTIES.PROCESS_FAKE', + 'sortable': true + }) + ]; + +export let fakeProcessColumnSchema = { + 'default': [ + { + 'key': 'name', + 'type': 'text', + 'title': 'ADF_PROCESS_LIST.PROPERTIES.NAME', + 'sortable': true + } + ] + , fakeProcessCutomSchema +}; diff --git a/lib/process-services/process-list/components/process-list.component.html b/lib/process-services/process-list/components/process-list.component.html index 8d0fecea57..a1786d66df 100644 --- a/lib/process-services/process-list/components/process-list.component.html +++ b/lib/process-services/process-list/components/process-list.component.html @@ -1,5 +1,7 @@ { @@ -37,39 +38,6 @@ describe('ProcessInstanceListComponent', () => { let getProcessInstancesSpy: jasmine.Spy; let appConfig: AppConfigService; - let fakeCutomSchema = [ - { - 'key': 'fakeName', - 'type': 'text', - 'title': 'ADF_PROCESS_LIST.PROPERTIES.FAKE', - 'sortable': true - }, - { - 'key': 'fakeProcessName', - 'type': 'text', - 'title': 'ADF_PROCESS_LIST.PROPERTIES.PROCESS_FAKE', - 'sortable': true - } - ]; - - let fakeColumnSchema = { - 'default': [ - { - 'key': 'name', - 'type': 'text', - 'title': 'ADF_PROCESS_LIST.PROPERTIES.NAME', - 'sortable': true - }, - { - 'key': 'created', - 'type': 'text', - 'title': 'ADF_PROCESS_LIST.PROPERTIES.CREATED', - 'cssClass': 'hidden', - 'sortable': true - } - ] - , fakeCutomSchema }; - setupTestBed({ imports: [ ProcessTestingModule @@ -85,7 +53,7 @@ describe('ProcessInstanceListComponent', () => { getProcessInstancesSpy = spyOn(service, 'getProcessInstances').and.returnValue(Observable.of(fakeProcessInstance)); appConfig.config['adf-process-list'] = { 'presets': { - 'fakeCutomSchema': [ + 'fakeProcessCutomSchema': [ { 'key': 'fakeName', 'type': 'text', @@ -105,8 +73,8 @@ describe('ProcessInstanceListComponent', () => { it('should use the default schemaColumn as default', () => { component.ngAfterContentInit(); - expect(component.data.getColumns()).toBeDefined(); - expect(component.data.getColumns().length).toEqual(2); + expect(component.columns).toBeDefined(); + expect(component.columns.length).toEqual(2); }); it('should use the schemaColumn passed in input', () => { @@ -123,22 +91,23 @@ describe('ProcessInstanceListComponent', () => { }); it('should fetch the custom schemaColumn from app.config.json', () => { + component.presetColumn = 'fakeProcessCutomSchema'; component.ngAfterContentInit(); fixture.detectChanges(); - expect(component.layoutPresets).toEqual(fakeColumnSchema); + expect(component.columns).toEqual(fakeProcessCutomSchema); }); it('should fetch custom schemaColumn when the input presetColumn is defined', () => { - component.presetColumn = 'fakeCutomSchema'; + component.presetColumn = 'fakeProcessCutomSchema'; component.ngAfterContentInit(); fixture.detectChanges(); - expect(component.data.getColumns()).toBeDefined(); - expect(component.data.getColumns().length).toEqual(2); + expect(component.columns).toBeDefined(); + expect(component.columns.length).toEqual(2); }); it('should return an empty process list when no input parameters are passed', () => { component.ngAfterContentInit(); - expect(component.data).toBeDefined(); + expect(component.rows).toBeDefined(); expect(component.isListEmpty()).toBeTruthy(); }); @@ -164,74 +133,11 @@ describe('ProcessInstanceListComponent', () => { component.processDefinitionKey = null; component.success.subscribe((res) => { expect(res).toBeDefined(); - expect(component.data).toBeDefined(); + expect(component.rows).toBeDefined(); expect(component.isListEmpty()).not.toBeTruthy(); - expect(component.data.getRows().length).toEqual(2); - expect(component.data.getRows()[0].getValue('name')).toEqual('Process 773443333'); - expect(component.data.getRows()[1].getValue('name')).toEqual('Process 382927392'); - }); - fixture.detectChanges(); - })); - - it('should order the process instances by name column when no sort passed', async(() => { - component.appId = 1; - component.state = 'open'; - component.processDefinitionKey = null; - component.success.subscribe((res) => { - expect(res).toBeDefined(); - expect(component.data).toBeDefined(); - expect(component.isListEmpty()).not.toBeTruthy(); - expect(component.data.getRows().length).toEqual(2); - expect(component.data.getRows()[0].getValue('name')).toEqual('Process 382927392'); - expect(component.data.getRows()[1].getValue('name')).toEqual('Process 773443333'); - }); - fixture.detectChanges(); - })); - - it('should order the process instances by descending column when specified', async(() => { - component.appId = 1; - component.state = 'open'; - component.processDefinitionKey = null; - component.sort = 'name-desc'; - component.success.subscribe((res) => { - expect(res).toBeDefined(); - expect(component.data).toBeDefined(); - expect(component.isListEmpty()).not.toBeTruthy(); - expect(component.data.getRows().length).toEqual(2); - expect(component.data.getRows()[0].getValue('name')).toEqual('Process 773443333'); - expect(component.data.getRows()[1].getValue('name')).toEqual('Process 382927392'); - }); - fixture.detectChanges(); - })); - - it('should order the process instances by ascending column when specified', async(() => { - component.appId = 1; - component.state = 'open'; - component.processDefinitionKey = null; - component.sort = 'started-asc'; - component.success.subscribe((res) => { - expect(res).toBeDefined(); - expect(component.data).toBeDefined(); - expect(component.isListEmpty()).not.toBeTruthy(); - expect(component.data.getRows().length).toEqual(2); - expect(component.data.getRows()[0].getValue('name')).toEqual('Process 773443333'); - expect(component.data.getRows()[1].getValue('name')).toEqual('Process 382927392'); - }); - fixture.detectChanges(); - })); - - it('should order the process instances by descending start date when specified', async(() => { - component.appId = 1; - component.state = 'open'; - component.processDefinitionKey = null; - component.sort = 'started-desc'; - component.success.subscribe((res) => { - expect(res).toBeDefined(); - expect(component.data).toBeDefined(); - expect(component.isListEmpty()).not.toBeTruthy(); - expect(component.data.getRows().length).toEqual(2); - expect(component.data.getRows()[0].getValue('name')).toEqual('Process 382927392'); - expect(component.data.getRows()[1].getValue('name')).toEqual('Process 773443333'); + expect(component.rows.length).toEqual(2); + expect(component.rows[0]['name']).toEqual('Process 773443333'); + expect(component.rows[1]['name']).toEqual('Process 382927392'); }); fixture.detectChanges(); })); @@ -242,8 +148,8 @@ describe('ProcessInstanceListComponent', () => { component.state = 'open'; component.processDefinitionKey = 'fakeprocess'; component.success.subscribe( (res) => { - expect(component.data.getRows()[0].getValue('name')).toEqual('Fake Process Name - Nov 9, 2017, 12:36:14 PM'); - expect(component.data.getRows()[1].getValue('name')).toEqual('Fake Process Name - Nov 9, 2017, 12:37:25 PM'); + expect(component.rows[0]['name']).toEqual('Fake Process Name - Nov 9, 2017, 12:36:14 PM'); + expect(component.rows[1]['name']).toEqual('Fake Process Name - Nov 9, 2017, 12:37:25 PM'); }); fixture.detectChanges(); })); @@ -254,44 +160,28 @@ describe('ProcessInstanceListComponent', () => { }); it('should return selected true for the selected process', () => { - component.data = new ObjectDataTableAdapter( + component.rows = [ { id: '999', name: 'Fake-name' }, { id: '888', name: 'Fake-name-888' } - ], - [ - { type: 'text', key: 'id', title: 'Id' }, - { type: 'text', key: 'name', title: 'Name' } - ] - ); + ]; component.selectFirst(); - const dataRow = component.data.getRows(); + const dataRow = component.rows[0]; expect(dataRow).toBeDefined(); - expect(dataRow[0].getValue('id')).toEqual('999'); - expect(dataRow[0].isSelected).toEqual(true); - expect(dataRow[1].getValue('id')).toEqual('888'); - expect(dataRow[1].isSelected).toEqual(false); + expect(component.currentInstanceId).toEqual('999'); }); it('should not select first row when selectFirstRow is false', () => { - component.data = new ObjectDataTableAdapter( + component.rows = [ { id: '999', name: 'Fake-name' }, { id: '888', name: 'Fake-name-888' } - ], - [ - { type: 'text', key: 'id', title: 'Id' }, - { type: 'text', key: 'name', title: 'Name' } - ] - ); + ]; component.selectFirstRow = false; component.selectFirst(); - const dataRow = component.data.getRows(); + const dataRow = component.rows; expect(dataRow).toBeDefined(); - expect(dataRow[0].getValue('id')).toEqual('999'); - expect(dataRow[0].isSelected).toEqual(false); - expect(dataRow[1].getValue('id')).toEqual('888'); - expect(dataRow[1].isSelected).toEqual(false); + expect(dataRow[0]['id']).toEqual('999'); }); it('should throw an exception when the response is wrong', fakeAsync(() => { @@ -327,10 +217,10 @@ describe('ProcessInstanceListComponent', () => { component.state = 'open'; component.success.subscribe( (res) => { expect(res).toBeDefined(); - expect(component.data).toBeDefined(); + expect(component.rows).toBeDefined(); expect(component.isListEmpty()).not.toBeTruthy(); - expect(component.data.getRows().length).toEqual(2); - expect(component.data.getRows()[0].getValue('name')).toEqual('Process 773443333'); + expect(component.rows.length).toEqual(2); + expect(component.rows[0]['name']).toEqual('Process 773443333'); done(); }); component.reload(); @@ -410,8 +300,8 @@ describe('ProcessInstanceListComponent', () => { expect(res).toBeDefined(); expect(component.data).toBeDefined(); expect(component.isListEmpty()).not.toBeTruthy(); - expect(component.data.getRows().length).toEqual(2); - expect(component.data.getRows()[0].getValue('name')).toEqual('Process 773443333'); + expect(component.rows.length).toEqual(2); + expect(component.rows[0]['name']).toEqual('Process 773443333'); done(); }); @@ -424,10 +314,10 @@ describe('ProcessInstanceListComponent', () => { component.success.subscribe((res) => { expect(res).toBeDefined(); - expect(component.data).toBeDefined(); + expect(component.rows).toBeDefined(); expect(component.isListEmpty()).not.toBeTruthy(); - expect(component.data.getRows().length).toEqual(2); - expect(component.data.getRows()[0].getValue('name')).toEqual('Process 773443333'); + expect(component.rows.length).toEqual(2); + expect(component.rows[0]['name']).toEqual('Process 773443333'); done(); }); @@ -440,10 +330,10 @@ describe('ProcessInstanceListComponent', () => { component.success.subscribe((res) => { expect(res).toBeDefined(); - expect(component.data).toBeDefined(); + expect(component.rows).toBeDefined(); expect(component.isListEmpty()).not.toBeTruthy(); - expect(component.data.getRows().length).toEqual(2); - expect(component.data.getRows()[0].getValue('name')).toEqual('Process 773443333'); + expect(component.rows.length).toEqual(2); + expect(component.rows[0]['name']).toEqual('Process 773443333'); done(); }); @@ -456,31 +346,16 @@ describe('ProcessInstanceListComponent', () => { component.success.subscribe((res) => { expect(res).toBeDefined(); - expect(component.data).toBeDefined(); + expect(component.rows).toBeDefined(); expect(component.isListEmpty()).not.toBeTruthy(); - expect(component.data.getRows().length).toEqual(2); - expect(component.data.getRows()[0].getValue('name')).toEqual('Process 773443333'); + expect(component.rows.length).toEqual(2); + expect(component.rows[0]['name']).toEqual('Process 773443333'); done(); }); component.ngOnChanges({'sort': change}); }); - it('should sort the list when the sort parameter changes', (done) => { - const sort = 'created-asc'; - let change = new SimpleChange(null, sort, true); - let sortSpy = spyOn(component.data, 'setSorting'); - - component.success.subscribe((res) => { - expect(res).toBeDefined(); - expect(sortSpy).toHaveBeenCalledWith(new DataSorting('started', 'asc')); - done(); - }); - - component.sort = sort; - component.ngOnChanges({'sort': change}); - }); - it('should reload the process list when the processDefinitionKey parameter changes', (done) => { const processDefinitionKey = 'SimpleProcess'; let change = new SimpleChange(null, processDefinitionKey, true); @@ -567,10 +442,10 @@ describe('ProcessInstanceListComponent', () => { component.success.subscribe((res) => { expect(res).toBeDefined(); - expect(component.data).toBeDefined(); + expect(component.rows).toBeDefined(); expect(component.isListEmpty()).not.toBeTruthy(); - expect(component.data.getRows().length).toEqual(2); - expect(component.data.getRows()[0].getValue('name')).toEqual('Process 773443333'); + expect(component.rows.length).toEqual(2); + expect(component.rows[0]['name']).toEqual('Process 773443333'); done(); }); @@ -622,10 +497,10 @@ describe('CustomProcessListComponent', () => { it('should fetch custom schemaColumn from html', () => { fixture.detectChanges(); - expect(component.processList.data.getColumns()).toBeDefined(); - expect(component.processList.data.getColumns()[1].title).toEqual('ADF_PROCESS_LIST.PROPERTIES.END_DATE'); - expect(component.processList.data.getColumns()[2].title).toEqual('ADF_PROCESS_LIST.PROPERTIES.CREATED'); - expect(component.processList.data.getColumns().length).toEqual(3); + expect(component.processList.columns).toBeDefined(); + expect(component.processList.columns.length).toEqual(3); + expect(component.processList.columns[1]['title']).toEqual('ADF_PROCESS_LIST.PROPERTIES.END_DATE'); + expect(component.processList.columns[2]['title']).toEqual('ADF_PROCESS_LIST.PROPERTIES.CREATED'); }); }); diff --git a/lib/process-services/process-list/components/process-list.component.ts b/lib/process-services/process-list/components/process-list.component.ts index e0786e2db9..5ad9626edf 100644 --- a/lib/process-services/process-list/components/process-list.component.ts +++ b/lib/process-services/process-list/components/process-list.component.ts @@ -16,19 +16,13 @@ */ import { - DataColumn, + DataTableSchema, DataRowEvent, - DataSorting, - DataTableComponent, DataTableAdapter, - ObjectDataColumn, - ObjectDataRow, - ObjectDataTableAdapter, EmptyCustomContentDirective } from '@alfresco/adf-core'; import { AppConfigService, - DataColumnListComponent, PaginatedComponent, PaginationComponent, PaginationModel, @@ -43,8 +37,7 @@ import { Input, OnChanges, Output, - SimpleChanges, - ViewChild + SimpleChanges } from '@angular/core'; import { ProcessFilterParamRepresentationModel } from '../models/filter-process.model'; import { processPresetsDefaultModel } from '../models/process-preset.model'; @@ -57,14 +50,12 @@ import { ProcessListModel } from '../models/process-list.model'; styleUrls: ['./process-list.component.css'], templateUrl: './process-list.component.html' }) -export class ProcessInstanceListComponent implements OnChanges, AfterContentInit, PaginatedComponent { +export class ProcessInstanceListComponent extends DataTableSchema implements OnChanges, AfterContentInit, PaginatedComponent { - @ContentChild(DataColumnListComponent) columnList: DataColumnListComponent; + static PRESET_KEY = 'adf-process-list.presets'; @ContentChild(EmptyCustomContentDirective) emptyCustomContent: EmptyCustomContentDirective; - @ViewChild('dataTable') dataTable: DataTableComponent; - /** The id of the app. */ @Input() appId: number; @@ -101,10 +92,6 @@ export class ProcessInstanceListComponent implements OnChanges, AfterContentInit @Input() size: number = PaginationComponent.DEFAULT_PAGINATION.maxItems; - /** Name of a custom schema to fetch from `app.config.json`. */ - @Input() - presetColumn: string; - /** Data source to define the datatable. */ @Input() data: DataTableAdapter; @@ -139,14 +126,15 @@ export class ProcessInstanceListComponent implements OnChanges, AfterContentInit requestNode: ProcessFilterParamRepresentationModel; currentInstanceId: string; isLoading: boolean = true; - layoutPresets = {}; + rows: any[] = []; sorting: any[] = ['created', 'desc']; pagination: BehaviorSubject; constructor(private processService: ProcessService, private userPreferences: UserPreferencesService, - private appConfig: AppConfigService) { + appConfig: AppConfigService) { + super(appConfig, ProcessInstanceListComponent.PRESET_KEY, processPresetsDefaultModel); this.size = this.userPreferences.paginationSize; this.pagination = new BehaviorSubject( { @@ -157,27 +145,16 @@ export class ProcessInstanceListComponent implements OnChanges, AfterContentInit } ngAfterContentInit() { - this.loadLayoutPresets(); - this.setupSchema(); + this.createDatatableSchema(); + if (this.data && this.data.getColumns().length === 0) { + this.data.setColumns(this.columns); + } if (this.appId != null) { this.reload(); } } - /** - * Setup html-based (html definitions) or code behind (data adapter) schema. - * If component is assigned with an empty data adater the default schema settings applied. - */ - setupSchema() { - let schema = this.getSchema(); - if (!this.data) { - this.data = new ObjectDataTableAdapter([], schema); - } else if (this.data.getColumns().length === 0) { - this.data.setColumns(schema); - } - } - ngOnChanges(changes: SimpleChanges) { if (this.isPropertyChanged(changes)) { if (this.isSortChanged(changes)) { @@ -234,8 +211,7 @@ export class ProcessInstanceListComponent implements OnChanges, AfterContentInit this.processService.getProcessInstances(requestNode, this.processDefinitionKey) .subscribe( (response) => { - let instancesRow = this.createDataRow(response.data); - this.renderInstances(instancesRow); + this.rows = this.optimizeNames(response.data); this.selectFirst(); this.success.emit(response); this.isLoading = false; @@ -252,59 +228,16 @@ export class ProcessInstanceListComponent implements OnChanges, AfterContentInit }); } - /** - * Create an array of ObjectDataRow - * @param instances - */ - private createDataRow(instances: any[]): ObjectDataRow[] { - let instancesRows: ObjectDataRow[] = []; - instances.forEach((row) => { - instancesRows.push(new ObjectDataRow(row)); - }); - return instancesRows; - } - - /** - * Render the instances list - * - * @param instances - */ - private renderInstances(instances: any[]) { - instances = this.optimizeNames(instances); - this.dataTable.resetSelection(); - this.setDatatableSorting(); - this.data.setRows(instances); - } - - /** - * Sort the datatable rows based on current value of 'sort' property - */ - private setDatatableSorting() { - if (!this.sort) { - return; - } - let sortingParams: string[] = this.sort.split('-'); - if (sortingParams.length === 2) { - let sortColumn = sortingParams[0] === 'created' ? 'started' : sortingParams[0]; - let sortOrder = sortingParams[1]; - this.data.setSorting(new DataSorting(sortColumn, sortOrder)); - } - } - /** * Select the first instance of a list if present */ selectFirst() { if (this.selectFirstRow) { if (!this.isListEmpty()) { - let row = this.data.getRows()[0]; - row.isSelected = true; - this.data.selectedRow = row; - this.currentInstanceId = row.getValue('id'); + let dataRow = this.rows[0]; + dataRow.isSelected = true; + this.currentInstanceId = dataRow['id']; } else { - if (this.data) { - this.data.selectedRow = null; - } this.currentInstanceId = null; } } @@ -321,8 +254,7 @@ export class ProcessInstanceListComponent implements OnChanges, AfterContentInit * Check if the list is empty */ isListEmpty(): boolean { - return this.data === undefined || - (this.data && this.data.getRows() && this.data.getRows().length === 0); + return !this.rows || this.rows.length === 0; } /** @@ -352,9 +284,9 @@ export class ProcessInstanceListComponent implements OnChanges, AfterContentInit * @param instances */ private optimizeNames(instances: any[]): any[] { - instances = instances.map(t => { - t.obj.name = this.getProcessNameOrDescription(t.obj, 'medium'); - return t; + instances = instances.map(instance => { + instance.name = this.getProcessNameOrDescription(instance, 'medium'); + return instance; }); return instances; } @@ -391,41 +323,6 @@ export class ProcessInstanceListComponent implements OnChanges, AfterContentInit return new ProcessFilterParamRepresentationModel(requestNode); } - private loadLayoutPresets(): void { - const externalSettings = this.appConfig.get('adf-process-list.presets', null); - - if (externalSettings) { - this.layoutPresets = Object.assign({}, processPresetsDefaultModel, externalSettings); - } else { - this.layoutPresets = processPresetsDefaultModel; - } - } - - getSchema(): any { - let customSchemaColumns = []; - customSchemaColumns = this.getSchemaFromConfig(this.presetColumn).concat(this.getSchemaFromHtml()); - if (customSchemaColumns.length === 0) { - customSchemaColumns = this.getDefaultLayoutPreset(); - } - return customSchemaColumns; - } - - getSchemaFromHtml(): any { - let schema = []; - if (this.columnList && this.columnList.columns && this.columnList.columns.length > 0) { - schema = this.columnList.columns.map(c => c); - } - return schema; - } - - private getSchemaFromConfig(name: string): DataColumn[] { - return name ? (this.layoutPresets[name]).map(col => new ObjectDataColumn(col)) : []; - } - - private getDefaultLayoutPreset(): DataColumn[] { - return (this.layoutPresets['default']).map(col => new ObjectDataColumn(col)); - } - updatePagination(params: PaginationModel) { const needsReload = params.maxItems || params.skipCount; this.size = params.maxItems; diff --git a/package-lock.json b/package-lock.json index 16003998a0..8f9807e658 100644 --- a/package-lock.json +++ b/package-lock.json @@ -20872,4 +20872,4 @@ "integrity": "sha512-W9Nj+UmBJG251wkCacIkETgra4QgBo/vgoEkb4a2uoLzpQG7qF9nzwoLXWU5xj3Fg2mxGvEDh47mg24vXccYjA==" } } -} +} \ No newline at end of file