@datagrok/peptides 1.9.0 → 1.9.2
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/dist/168.js +2 -0
- package/dist/478.js +2 -2
- package/dist/802.js +2 -2
- package/dist/951.js +2 -0
- package/dist/package-test.js +2 -2
- package/dist/package.js +2 -2
- package/package.json +4 -4
- package/setup-unlink-clean.sh +24 -0
- package/setup.sh +2 -0
- package/src/model.ts +43 -32
- package/src/tests/core.ts +2 -2
- package/src/tests/model.ts +4 -3
- package/src/tests/table-view.ts +5 -4
- package/src/tests/utils.ts +6 -0
- package/src/tests/viewers.ts +10 -10
- package/src/tests/widgets.ts +96 -23
- package/src/utils/algorithms.ts +3 -3
- package/src/utils/cell-renderer.ts +1 -1
- package/src/utils/constants.ts +1 -2
- package/src/utils/distance-matrix.worker.ts +1 -1
- package/src/utils/misc.ts +2 -2
- package/src/utils/peptide-similarity-space.ts +2 -2
- package/src/viewers/logo-summary.ts +24 -20
- package/src/viewers/peptide-space-viewer.ts +2 -2
- package/src/viewers/sar-viewer.ts +6 -6
- package/src/widgets/mutation-cliffs.ts +1 -1
- package/src/widgets/peptides.ts +8 -8
- package/src/widgets/settings.ts +2 -2
- package/dist/563.js +0 -2
- package/dist/96.js +0 -2
package/src/utils/misc.ts
CHANGED
|
@@ -59,8 +59,8 @@ export function calculateSelected(df: DG.DataFrame): type.MonomerSelectionStats
|
|
|
59
59
|
}
|
|
60
60
|
|
|
61
61
|
// export function isGridCellInvalid(gc: DG.GridCell | null): boolean {
|
|
62
|
-
// return !gc || !gc.cell.value || !gc.tableColumn || gc.tableRowIndex
|
|
63
|
-
// gc.cell.value
|
|
62
|
+
// return !gc || !gc.cell.value || !gc.tableColumn || gc.tableRowIndex === null || gc.tableRowIndex === -1 ||
|
|
63
|
+
// gc.cell.value === DG.INT_NULL || gc.cell.value === DG.FLOAT_NULL;
|
|
64
64
|
// }
|
|
65
65
|
|
|
66
66
|
export function extractColInfo(col: DG.Column<string>): type.RawColumn {
|
|
@@ -68,7 +68,7 @@ export async function createPeptideSimilaritySpaceViewer(table: DG.DataFrame, me
|
|
|
68
68
|
const axisCol = table.col(axis);
|
|
69
69
|
const newCol = edf.getCol(axis);
|
|
70
70
|
|
|
71
|
-
if (axisCol
|
|
71
|
+
if (axisCol !== null) {
|
|
72
72
|
for (let i = 0; i < newCol.length; ++i) {
|
|
73
73
|
const v = newCol.get(i);
|
|
74
74
|
table.set(axis, i, v);
|
|
@@ -204,7 +204,7 @@ export class PeptideSimilaritySpaceWidget {
|
|
|
204
204
|
for (const v of this.view.viewers) {
|
|
205
205
|
const opts = v.getOptions() as {[name: string]: any};
|
|
206
206
|
|
|
207
|
-
if (opts.type
|
|
207
|
+
if (opts.type === 'Scatter plot' && opts.look.xColumnName === '~X' && opts.look.yColumnName === '~Y')
|
|
208
208
|
found = true;
|
|
209
209
|
}
|
|
210
210
|
|
|
@@ -81,7 +81,7 @@ export class LogoSummaryTable extends DG.JsViewer {
|
|
|
81
81
|
|
|
82
82
|
onPropertyChanged(property: DG.Property): void {
|
|
83
83
|
super.onPropertyChanged(property);
|
|
84
|
-
if (property.name
|
|
84
|
+
if (property.name === 'membersRatioThreshold')
|
|
85
85
|
this.updateFilter();
|
|
86
86
|
this.render();
|
|
87
87
|
}
|
|
@@ -218,7 +218,7 @@ export class LogoSummaryTable extends DG.JsViewer {
|
|
|
218
218
|
this.viewerGrid.sort([C.LST_COLUMN_NAMES.MEMBERS], [false]);
|
|
219
219
|
this.updateFilter();
|
|
220
220
|
const gridClustersCol = this.viewerGrid.col(clustersColName)!;
|
|
221
|
-
gridClustersCol.name = C.LST_COLUMN_NAMES.CLUSTER;
|
|
221
|
+
gridClustersCol.column!.name = C.LST_COLUMN_NAMES.CLUSTER;
|
|
222
222
|
gridClustersCol.visible = true;
|
|
223
223
|
this.viewerGrid.columns.setOrder([C.LST_COLUMN_NAMES.CLUSTER, C.LST_COLUMN_NAMES.MEMBERS,
|
|
224
224
|
C.LST_COLUMN_NAMES.WEB_LOGO, C.LST_COLUMN_NAMES.DISTRIBUTION, C.LST_COLUMN_NAMES.MEAN_DIFFERENCE,
|
|
@@ -227,11 +227,11 @@ export class LogoSummaryTable extends DG.JsViewer {
|
|
|
227
227
|
this.viewerGrid.props.rowHeight = 55;
|
|
228
228
|
this.viewerGrid.onCellPrepare((cell) => {
|
|
229
229
|
const currentRowIdx = cell.tableRowIndex;
|
|
230
|
-
if (!cell.isTableCell || currentRowIdx
|
|
230
|
+
if (!cell.isTableCell || currentRowIdx === null || currentRowIdx === -1)
|
|
231
231
|
return;
|
|
232
232
|
|
|
233
233
|
const height = cell.bounds.height;
|
|
234
|
-
if (cell.tableColumn?.name
|
|
234
|
+
if (cell.tableColumn?.name === C.LST_COLUMN_NAMES.WEB_LOGO) {
|
|
235
235
|
const webLogoTable = this.webLogoDfPlot[currentRowIdx];
|
|
236
236
|
const webLogoTableRowCount = webLogoTable.rowCount;
|
|
237
237
|
const webLogoTablePepCol = webLogoTable.getCol(pepCol.name);
|
|
@@ -248,7 +248,7 @@ export class LogoSummaryTable extends DG.JsViewer {
|
|
|
248
248
|
.fromType('WebLogo', {positionHeight: this.webLogoMode, horizontalAlignment: HorizontalAlignments.LEFT,
|
|
249
249
|
maxHeight: 1000, minHeight: height - 2, positionWidth: positionWidth})
|
|
250
250
|
.then((viewer) => cell.element = viewer.root);
|
|
251
|
-
} else if (cell.tableColumn?.name
|
|
251
|
+
} else if (cell.tableColumn?.name === C.LST_COLUMN_NAMES.DISTRIBUTION) {
|
|
252
252
|
const viewerRoot = this.distributionDfPlot[currentRowIdx].plot.histogram({
|
|
253
253
|
filteringEnabled: false,
|
|
254
254
|
valueColumnName: C.COLUMNS_NAMES.ACTIVITY_SCALED,
|
|
@@ -269,7 +269,7 @@ export class LogoSummaryTable extends DG.JsViewer {
|
|
|
269
269
|
});
|
|
270
270
|
this.viewerGrid.root.addEventListener('click', (ev) => {
|
|
271
271
|
const cell = this.viewerGrid.hitTest(ev.offsetX, ev.offsetY);
|
|
272
|
-
if (!cell || !cell.isTableCell || cell.tableColumn?.name
|
|
272
|
+
if (!cell || !cell.isTableCell || cell.tableColumn?.name !== clustersColName)
|
|
273
273
|
return;
|
|
274
274
|
|
|
275
275
|
summaryTable.currentRowIdx = -1;
|
|
@@ -370,13 +370,13 @@ export class LogoSummaryTable extends DG.JsViewer {
|
|
|
370
370
|
|
|
371
371
|
for (let i = 0; i < viewerDfColsLength; ++i) {
|
|
372
372
|
const col = viewerDfCols.byIndex(i);
|
|
373
|
-
newClusterVals[i] = col.name
|
|
374
|
-
col.name
|
|
375
|
-
col.name
|
|
376
|
-
col.name
|
|
377
|
-
col.name
|
|
378
|
-
col.name
|
|
379
|
-
col.name
|
|
373
|
+
newClusterVals[i] = col.name === C.LST_COLUMN_NAMES.CLUSTER ? newClusterName :
|
|
374
|
+
col.name === C.LST_COLUMN_NAMES.MEMBERS ? selection.trueCount :
|
|
375
|
+
col.name === C.LST_COLUMN_NAMES.WEB_LOGO ? null :
|
|
376
|
+
col.name === C.LST_COLUMN_NAMES.DISTRIBUTION ? null :
|
|
377
|
+
col.name === C.LST_COLUMN_NAMES.MEAN_DIFFERENCE ? stats.meanDifference:
|
|
378
|
+
col.name === C.LST_COLUMN_NAMES.P_VALUE ? stats.pValue:
|
|
379
|
+
col.name === C.LST_COLUMN_NAMES.RATIO ? stats.ratio:
|
|
380
380
|
col.name in aggregatedValues ? aggregatedValues[col.name] :
|
|
381
381
|
console.warn(`PeptidesLSTWarn: value for column ${col.name} is undefined`)! || null;
|
|
382
382
|
}
|
|
@@ -388,16 +388,18 @@ export class LogoSummaryTable extends DG.JsViewer {
|
|
|
388
388
|
|
|
389
389
|
removeCluster(): void {
|
|
390
390
|
const lss = this.model.clusterSelection;
|
|
391
|
-
const
|
|
391
|
+
const customClusters = wu(this.model.customClusters).map((cluster) => cluster.name).toArray();
|
|
392
392
|
|
|
393
393
|
// Names of the clusters to remove
|
|
394
|
-
const clustNames = lss.filter((cluster) =>
|
|
395
|
-
if (clustNames.length
|
|
396
|
-
return grok.shell.warning('
|
|
394
|
+
const clustNames = lss.filter((cluster) => customClusters.includes(cluster));
|
|
395
|
+
if (clustNames.length === 0)
|
|
396
|
+
return grok.shell.warning('No custom clusters selected to be removed');
|
|
397
397
|
|
|
398
398
|
const viewerDf = this.viewerGrid.dataFrame;
|
|
399
399
|
const viewerDfRows = viewerDf.rows;
|
|
400
|
-
const
|
|
400
|
+
const clustCol = viewerDf.getCol(C.LST_COLUMN_NAMES.CLUSTER);
|
|
401
|
+
const clustColCat = clustCol.categories;
|
|
402
|
+
const dfCols = this.dataFrame.columns;
|
|
401
403
|
|
|
402
404
|
for (const cluster of clustNames) {
|
|
403
405
|
lss.splice(lss.indexOf(cluster), 1);
|
|
@@ -409,6 +411,8 @@ export class LogoSummaryTable extends DG.JsViewer {
|
|
|
409
411
|
this.distributionDfPlot.splice(clustIdx, 1);
|
|
410
412
|
}
|
|
411
413
|
|
|
414
|
+
clustCol.compact();
|
|
415
|
+
|
|
412
416
|
this.model.clusterSelection = lss;
|
|
413
417
|
this.render();
|
|
414
418
|
}
|
|
@@ -423,8 +427,8 @@ export class LogoSummaryTable extends DG.JsViewer {
|
|
|
423
427
|
const activityColData = activityCol.getRawData();
|
|
424
428
|
|
|
425
429
|
|
|
426
|
-
if (clustType
|
|
427
|
-
const origClustCol = filteredDf.getCol(
|
|
430
|
+
if (clustType === CLUSTER_TYPE.ORIGINAL) {
|
|
431
|
+
const origClustCol = filteredDf.getCol(C.LST_COLUMN_NAMES.CLUSTER);
|
|
428
432
|
const origClustColData = origClustCol.getRawData();
|
|
429
433
|
const origClustColCategories = origClustCol.categories;
|
|
430
434
|
const seekValue = origClustColCategories.indexOf(clustName);
|
|
@@ -47,7 +47,7 @@ export class PeptideSpaceViewer extends DG.JsViewer {
|
|
|
47
47
|
|
|
48
48
|
async onPropertyChanged(property: DG.Property | null): Promise<void> {
|
|
49
49
|
super.onPropertyChanged(property);
|
|
50
|
-
if (this.prevProps[property?.name as 'method' | 'measure' | 'cyclesCount' ?? '']
|
|
50
|
+
if (this.prevProps[property?.name as 'method' | 'measure' | 'cyclesCount' ?? ''] === property?.get(this))
|
|
51
51
|
return;
|
|
52
52
|
|
|
53
53
|
if (this.model)
|
|
@@ -90,7 +90,7 @@ export class PeptideSpaceViewer extends DG.JsViewer {
|
|
|
90
90
|
|
|
91
91
|
viewerRoot.addEventListener('mousemove', (ev) => {
|
|
92
92
|
const idx = scatterPlot.hitTest(ev.offsetX, ev.offsetY);
|
|
93
|
-
if (idx
|
|
93
|
+
if (idx !== -1) {
|
|
94
94
|
const table = ui.tableFromMap({
|
|
95
95
|
'Activity': colorCol.get(idx),
|
|
96
96
|
'Sequence': alignedSeqCol.get(idx),
|
|
@@ -94,7 +94,7 @@ export class MonomerPosition extends DG.JsViewer {
|
|
|
94
94
|
this.viewerGrid.onCellTooltip((cell: DG.GridCell, x: number, y: number) => showTooltip(cell, x, y, this.model));
|
|
95
95
|
this.viewerGrid.root.addEventListener('click', (ev) => {
|
|
96
96
|
const gridCell = this.viewerGrid.hitTest(ev.offsetX, ev.offsetY);
|
|
97
|
-
if (!gridCell?.isTableCell || gridCell?.tableColumn?.name
|
|
97
|
+
if (!gridCell?.isTableCell || gridCell?.tableColumn?.name === C.COLUMNS_NAMES.MONOMER)
|
|
98
98
|
return;
|
|
99
99
|
|
|
100
100
|
const position = gridCell!.tableColumn!.name;
|
|
@@ -112,7 +112,7 @@ export class MonomerPosition extends DG.JsViewer {
|
|
|
112
112
|
if (!refreshOnly) {
|
|
113
113
|
$(this.root).empty();
|
|
114
114
|
let switchHost = ui.divText(VIEWER_TYPE.MOST_POTENT_RESIDUES, {id: 'pep-viewer-title'});
|
|
115
|
-
if (this.name
|
|
115
|
+
if (this.name === VIEWER_TYPE.MONOMER_POSITION) {
|
|
116
116
|
const mutationCliffsMode = ui.boolInput('', this.mode === MONOMER_POSITION_MODE.MUTATION_CLIFFS);
|
|
117
117
|
mutationCliffsMode.root.addEventListener('click', () => {
|
|
118
118
|
invariantMapMode.value = false;
|
|
@@ -212,7 +212,7 @@ export class MostPotentResiduesViewer extends DG.JsViewer {
|
|
|
212
212
|
this.viewerGrid.onCellTooltip((cell: DG.GridCell, x: number, y: number) => showTooltip(cell, x, y, this.model));
|
|
213
213
|
this.viewerGrid.root.addEventListener('click', (ev) => {
|
|
214
214
|
const gridCell = this.viewerGrid.hitTest(ev.offsetX, ev.offsetY);
|
|
215
|
-
if (!gridCell?.isTableCell || gridCell!.tableColumn!.name
|
|
215
|
+
if (!gridCell?.isTableCell || gridCell!.tableColumn!.name !== C.COLUMNS_NAMES.MEAN_DIFFERENCE)
|
|
216
216
|
return;
|
|
217
217
|
|
|
218
218
|
const tableRowIdx = gridCell!.tableRowIndex!;
|
|
@@ -269,7 +269,7 @@ function renderCell(args: DG.GridCellRenderArgs, model: PeptidesModel, isInvaria
|
|
|
269
269
|
|
|
270
270
|
const tableColName = cell.tableColumn?.name;
|
|
271
271
|
const tableRowIndex = cell.tableRowIndex!;
|
|
272
|
-
if (!cell.isTableCell || renderColNames.indexOf(tableColName!)
|
|
272
|
+
if (!cell.isTableCell || renderColNames.indexOf(tableColName!) === -1) {
|
|
273
273
|
canvasContext.restore();
|
|
274
274
|
return;
|
|
275
275
|
}
|
|
@@ -318,11 +318,11 @@ export function showTooltip(cell: DG.GridCell, x: number, y: number, model: Pept
|
|
|
318
318
|
const tableColName = tableCol?.name;
|
|
319
319
|
const tableRowIndex = cell.tableRowIndex;
|
|
320
320
|
|
|
321
|
-
if (!cell.isRowHeader && !cell.isColHeader && tableCol && tableRowIndex
|
|
321
|
+
if (!cell.isRowHeader && !cell.isColHeader && tableCol && tableRowIndex !== null) {
|
|
322
322
|
const table = cell.grid.table;
|
|
323
323
|
const currentAAR = table.get(C.COLUMNS_NAMES.MONOMER, tableRowIndex);
|
|
324
324
|
|
|
325
|
-
if (tableCol.semType
|
|
325
|
+
if (tableCol.semType === C.SEM_TYPES.MONOMER)
|
|
326
326
|
model.showMonomerTooltip(currentAAR, x, y);
|
|
327
327
|
else if (renderColNames.includes(tableColName!)) {
|
|
328
328
|
const currentPosition = tableColName !== C.COLUMNS_NAMES.MEAN_DIFFERENCE ? tableColName :
|
|
@@ -76,7 +76,7 @@ export function mutationCliffsWidget(table: DG.DataFrame, model: PeptidesModel):
|
|
|
76
76
|
|
|
77
77
|
const aminoToInput = ui.stringInput('Mutated to:', '', () => {
|
|
78
78
|
const substitutedToAar = aminoToInput.stringValue;
|
|
79
|
-
if (substitutedToAar
|
|
79
|
+
if (substitutedToAar !== '')
|
|
80
80
|
substTable.filter.init((idx) => hiddenSubstToAarCol.get(idx) === substitutedToAar);
|
|
81
81
|
else
|
|
82
82
|
substTable.filter.setAll(true);
|
package/src/widgets/peptides.ts
CHANGED
|
@@ -30,7 +30,7 @@ export function analyzePeptidesUI(df: DG.DataFrame, col?: DG.Column<string>):
|
|
|
30
30
|
|
|
31
31
|
seqColInput = ui.columnInput('Sequence', df, potentialCol, () => {
|
|
32
32
|
const seqCol = seqColInput!.value;
|
|
33
|
-
if (!(seqCol.getTag(DG.TAGS.SEMTYPE)
|
|
33
|
+
if (!(seqCol.getTag(DG.TAGS.SEMTYPE) === DG.SEMTYPE.MACROMOLECULE)) {
|
|
34
34
|
grok.shell.warning('Peptides analysis only works with macromolecules');
|
|
35
35
|
seqColInput!.value = potentialCol;
|
|
36
36
|
}
|
|
@@ -40,7 +40,7 @@ export function analyzePeptidesUI(df: DG.DataFrame, col?: DG.Column<string>):
|
|
|
40
40
|
return viewer.root;
|
|
41
41
|
}));
|
|
42
42
|
});
|
|
43
|
-
} else if (!(col.getTag(bioTAGS.aligned)
|
|
43
|
+
} else if (!(col.getTag(bioTAGS.aligned) === ALIGNMENT.SEQ_MSA) &&
|
|
44
44
|
col.getTag(DG.TAGS.UNITS) !== NOTATION.HELM) {
|
|
45
45
|
return {
|
|
46
46
|
host: ui.label('Peptides analysis only works with aligned sequences'),
|
|
@@ -49,7 +49,7 @@ export function analyzePeptidesUI(df: DG.DataFrame, col?: DG.Column<string>):
|
|
|
49
49
|
}
|
|
50
50
|
|
|
51
51
|
let funcs = DG.Func.find({package: 'Bio', name: 'webLogoViewer'});
|
|
52
|
-
if (funcs.length
|
|
52
|
+
if (funcs.length === 0) {
|
|
53
53
|
return {
|
|
54
54
|
host: ui.label('Bio package is missing or out of date. Please install the latest version.'),
|
|
55
55
|
callback: async (): Promise<boolean> => false,
|
|
@@ -57,7 +57,7 @@ export function analyzePeptidesUI(df: DG.DataFrame, col?: DG.Column<string>):
|
|
|
57
57
|
}
|
|
58
58
|
|
|
59
59
|
funcs = DG.Func.find({package: 'Bio', name: 'getBioLib'});
|
|
60
|
-
if (funcs.length
|
|
60
|
+
if (funcs.length === 0) {
|
|
61
61
|
return {
|
|
62
62
|
host: ui.label('Bio package is missing or out of date. Please install the latest version.'),
|
|
63
63
|
callback: async (): Promise<boolean> => false,
|
|
@@ -121,7 +121,7 @@ export function analyzePeptidesUI(df: DG.DataFrame, col?: DG.Column<string>):
|
|
|
121
121
|
if (sequencesCol) {
|
|
122
122
|
const model = await startAnalysis(activityColumnChoice.value!, sequencesCol, clustersColumnChoice.value, df,
|
|
123
123
|
scaledCol, activityScalingMethod.value ?? C.SCALING_METHODS.NONE, targetColumnChoice.value);
|
|
124
|
-
return model
|
|
124
|
+
return model !== null;
|
|
125
125
|
}
|
|
126
126
|
return false;
|
|
127
127
|
};
|
|
@@ -183,19 +183,19 @@ export async function startAnalysis(activityColumn: DG.Column<number>, peptidesC
|
|
|
183
183
|
|
|
184
184
|
if (clustersColumn) {
|
|
185
185
|
const clusterCol = newDf.getCol(clustersColumn.name);
|
|
186
|
-
if (clusterCol.type
|
|
186
|
+
if (clusterCol.type !== DG.COLUMN_TYPE.STRING)
|
|
187
187
|
newDfCols.replace(clusterCol, clusterCol.convertTo(DG.COLUMN_TYPE.STRING));
|
|
188
188
|
settings.clustersColumnName = clustersColumn.name;
|
|
189
189
|
}
|
|
190
190
|
newDf.setTag(C.TAGS.SETTINGS, JSON.stringify(settings));
|
|
191
191
|
|
|
192
192
|
let monomerType = 'HELM_AA';
|
|
193
|
-
if (peptidesCol.getTag(DG.TAGS.UNITS)
|
|
193
|
+
if (peptidesCol.getTag(DG.TAGS.UNITS) === NOTATION.HELM) {
|
|
194
194
|
const sampleSeq = peptidesCol.get(0)!;
|
|
195
195
|
monomerType = sampleSeq.startsWith('PEPTIDE') ? 'HELM_AA' : 'HELM_BASE';
|
|
196
196
|
} else {
|
|
197
197
|
const alphabet = peptidesCol.tags[C.TAGS.ALPHABET];
|
|
198
|
-
monomerType = alphabet
|
|
198
|
+
monomerType = alphabet === 'DNA' || alphabet === 'RNA' ? 'HELM_BASE' : 'HELM_AA';
|
|
199
199
|
}
|
|
200
200
|
const dfUuid = uuid.v4();
|
|
201
201
|
newDf.setTag(C.TAGS.UUID, dfUuid);
|
package/src/widgets/settings.ts
CHANGED
|
@@ -104,7 +104,7 @@ export function getSettingsDialog(model: PeptidesModel): SettingsElements {
|
|
|
104
104
|
const includedColumnsInputs: DG.InputBase[] = [];
|
|
105
105
|
for (const col of model.df.columns.numerical) {
|
|
106
106
|
const colName = col.name;
|
|
107
|
-
if (colName
|
|
107
|
+
if (colName === settings.activityColumnName || colName === C.COLUMNS_NAMES.ACTIVITY_SCALED)
|
|
108
108
|
continue;
|
|
109
109
|
|
|
110
110
|
const isIncludedInput = ui.boolInput(COLUMNS_INPUTS.IS_INCLUDED,
|
|
@@ -127,7 +127,7 @@ export function getSettingsDialog(model: PeptidesModel): SettingsElements {
|
|
|
127
127
|
$(inputsRow).find('div.ui-div').css('display', 'inline-flex');
|
|
128
128
|
inputsRows.push(inputsRow);
|
|
129
129
|
}
|
|
130
|
-
if (inputsRows.length
|
|
130
|
+
if (inputsRows.length !== 0) {
|
|
131
131
|
accordion.addPane(SETTINGS_PANES.COLUMNS, () => ui.divV(inputsRows), false);
|
|
132
132
|
inputs[SETTINGS_PANES.COLUMNS] = includedColumnsInputs;
|
|
133
133
|
}
|