@abi-software/flatmap-viewer 2.5.0-a.2 → 2.5.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/README.rst +1 -1
- package/package.json +2 -3
- package/src/controls/annotation.js +246 -0
- package/src/controls/controls.js +68 -0
- package/src/controls/minimap.js +3 -8
- package/src/controls/paths3d.js +14 -0
- package/src/flatmap-viewer.js +103 -35
- package/src/interactions.js +187 -49
- package/src/layers/paths3d.js +252 -88
- package/src/main.js +55 -1
- package/src/pathways.js +29 -9
- package/static/css/flatmap-viewer.css +3 -133
package/src/interactions.js
CHANGED
|
@@ -40,11 +40,13 @@ import {Paths3DLayer} from './layers/paths3d'
|
|
|
40
40
|
import {SystemsManager} from './systems';
|
|
41
41
|
|
|
42
42
|
import {displayedProperties, InfoControl} from './controls/info';
|
|
43
|
-
import {BackgroundControl, LayerControl, NerveControl,
|
|
43
|
+
import {AnnotatorControl, BackgroundControl, LayerControl, NerveControl,
|
|
44
44
|
SCKANControl} from './controls/controls';
|
|
45
|
+
import {AnnotationDrawControl, DRAW_ANNOTATION_LAYERS} from './controls/annotation'
|
|
45
46
|
import {PathControl} from './controls/paths';
|
|
46
47
|
import {Path3DControl} from './controls/paths3d'
|
|
47
48
|
import {SearchControl} from './controls/search';
|
|
49
|
+
import {MinimapControl} from './controls/minimap';
|
|
48
50
|
import {SystemsControl} from './controls/systems';
|
|
49
51
|
import {TaxonsControl} from './controls/taxons';
|
|
50
52
|
import {latex2Svg} from './mathjax';
|
|
@@ -110,10 +112,13 @@ function labelPosition(feature)
|
|
|
110
112
|
function getRenderedLabel(properties)
|
|
111
113
|
{
|
|
112
114
|
if (!('renderedLabel' in properties)) {
|
|
113
|
-
const label = ('label' in properties) ?
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
115
|
+
const label = ('label' in properties) ? properties.label
|
|
116
|
+
: ('user_label' in properties) ? properties.user_label
|
|
117
|
+
: ''
|
|
118
|
+
const uppercaseLabel = (label !== '') ? (label.substr(0, 1).toUpperCase()
|
|
119
|
+
+ label.substr(1)).replaceAll("\n", "<br/>")
|
|
120
|
+
: ''
|
|
121
|
+
properties.renderedLabel = uppercaseLabel.replaceAll(/`\$([^\$]*)\$`/g, math => latex2Svg(math.slice(2, -2)));
|
|
117
122
|
}
|
|
118
123
|
return properties.renderedLabel;
|
|
119
124
|
}
|
|
@@ -122,14 +127,16 @@ function getRenderedLabel(properties)
|
|
|
122
127
|
|
|
123
128
|
export class UserInteractions
|
|
124
129
|
{
|
|
125
|
-
#
|
|
130
|
+
#annotationDrawControl = null
|
|
131
|
+
#minimap = null
|
|
132
|
+
#paths3dLayer = null
|
|
126
133
|
|
|
127
134
|
constructor(flatmap)
|
|
128
135
|
{
|
|
129
136
|
this._flatmap = flatmap;
|
|
130
137
|
this._map = flatmap.map;
|
|
131
138
|
|
|
132
|
-
this._activeFeatures =
|
|
139
|
+
this._activeFeatures = new Set()
|
|
133
140
|
this._selectedFeatureIds = new Map();
|
|
134
141
|
this._currentPopup = null;
|
|
135
142
|
this._infoControl = null;
|
|
@@ -139,7 +146,6 @@ export class UserInteractions
|
|
|
139
146
|
this._modal = false;
|
|
140
147
|
|
|
141
148
|
// Default colour settings
|
|
142
|
-
|
|
143
149
|
this.__colourOptions = {colour: true, outline: true};
|
|
144
150
|
|
|
145
151
|
// Marker placement and interaction
|
|
@@ -187,6 +193,26 @@ export class UserInteractions
|
|
|
187
193
|
// All taxons of connectivity paths are enabled by default
|
|
188
194
|
this.__enabledConnectivityTaxons = new Set(this._flatmap.taxonIdentifiers);
|
|
189
195
|
|
|
196
|
+
// Add a minimap if option set
|
|
197
|
+
if (flatmap.options.minimap) {
|
|
198
|
+
this.#minimap = new MinimapControl(flatmap, flatmap.options.minimap);
|
|
199
|
+
this._map.addControl(this.#minimap);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// Do we want a fullscreen control?
|
|
203
|
+
if (flatmap.options.fullscreenControl === true) {
|
|
204
|
+
this._map.addControl(new maplibregl.FullscreenControl(), 'top-right');
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// Add navigation controls if option set
|
|
208
|
+
if (flatmap.options.navigationControl) {
|
|
209
|
+
const value = flatmap.options.navigationControl;
|
|
210
|
+
const position = ((typeof value === 'string')
|
|
211
|
+
&& ['top-left', 'top-right', 'bottom-right', 'bottom-left'].includes(value))
|
|
212
|
+
? value : 'bottom-right';
|
|
213
|
+
this._map.addControl(new NavigationControl(flatmap), position);
|
|
214
|
+
}
|
|
215
|
+
|
|
190
216
|
// Support 3D path view
|
|
191
217
|
this.#paths3dLayer = new Paths3DLayer(flatmap, this)
|
|
192
218
|
|
|
@@ -223,8 +249,16 @@ export class UserInteractions
|
|
|
223
249
|
}
|
|
224
250
|
|
|
225
251
|
this._map.addControl(new Path3DControl(this));
|
|
252
|
+
|
|
253
|
+
if (flatmap.options.annotator) {
|
|
254
|
+
this._map.addControl(new AnnotatorControl(flatmap));
|
|
255
|
+
}
|
|
226
256
|
}
|
|
227
257
|
|
|
258
|
+
// Add an initially hidden tool for drawing on the map.
|
|
259
|
+
this.#annotationDrawControl = new AnnotationDrawControl(flatmap, false)
|
|
260
|
+
this._map.addControl(this.#annotationDrawControl)
|
|
261
|
+
|
|
228
262
|
// Handle mouse events
|
|
229
263
|
|
|
230
264
|
this._map.on('click', this.clickEvent_.bind(this));
|
|
@@ -239,6 +273,12 @@ export class UserInteractions
|
|
|
239
273
|
this.__pan_zoom_enabled = false;
|
|
240
274
|
}
|
|
241
275
|
|
|
276
|
+
get minimap()
|
|
277
|
+
//===========
|
|
278
|
+
{
|
|
279
|
+
return this.#minimap
|
|
280
|
+
}
|
|
281
|
+
|
|
242
282
|
get pathManager()
|
|
243
283
|
//===============
|
|
244
284
|
{
|
|
@@ -278,11 +318,60 @@ export class UserInteractions
|
|
|
278
318
|
}
|
|
279
319
|
}
|
|
280
320
|
|
|
321
|
+
showAnnotator(visible=true)
|
|
322
|
+
//=========================
|
|
323
|
+
{
|
|
324
|
+
if (this.#annotationDrawControl) {
|
|
325
|
+
this.#annotationDrawControl.show(visible)
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
commitAnnotationEvent(event)
|
|
330
|
+
//==========================
|
|
331
|
+
{
|
|
332
|
+
if (this.#annotationDrawControl) {
|
|
333
|
+
this.#annotationDrawControl.commitEvent(event)
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
rollbackAnnotationEvent(event)
|
|
338
|
+
//==========================
|
|
339
|
+
{
|
|
340
|
+
if (this.#annotationDrawControl) {
|
|
341
|
+
this.#annotationDrawControl.rollbackEvent(event)
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
addAnnotationFeature(feature)
|
|
346
|
+
//===========================
|
|
347
|
+
{
|
|
348
|
+
if (this.#annotationDrawControl) {
|
|
349
|
+
this.#annotationDrawControl.addFeature(feature)
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
refreshAnnotationFeatureGeometry(feature)
|
|
354
|
+
//=======================================
|
|
355
|
+
{
|
|
356
|
+
if (this.#annotationDrawControl) {
|
|
357
|
+
this.#annotationDrawControl.refreshGeometry(feature)
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
#setPaint(options)
|
|
362
|
+
//================
|
|
363
|
+
{
|
|
364
|
+
this._layerManager.setPaint(options)
|
|
365
|
+
if (this.#paths3dLayer) {
|
|
366
|
+
this.#paths3dLayer.setPaint(options)
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
|
|
281
370
|
setPaint(options)
|
|
282
371
|
//===============
|
|
283
372
|
{
|
|
284
373
|
this.__colourOptions = options;
|
|
285
|
-
this
|
|
374
|
+
this.#setPaint(options);
|
|
286
375
|
}
|
|
287
376
|
|
|
288
377
|
getLayers()
|
|
@@ -300,7 +389,9 @@ export class UserInteractions
|
|
|
300
389
|
enable3dPaths(enable=true)
|
|
301
390
|
//========================
|
|
302
391
|
{
|
|
303
|
-
this.#paths3dLayer
|
|
392
|
+
if (this.#paths3dLayer) {
|
|
393
|
+
this.#paths3dLayer.enable(enable)
|
|
394
|
+
}
|
|
304
395
|
}
|
|
305
396
|
|
|
306
397
|
getSystems()
|
|
@@ -332,19 +423,43 @@ export class UserInteractions
|
|
|
332
423
|
return undefined;
|
|
333
424
|
}
|
|
334
425
|
|
|
426
|
+
#getFeatureState(feature)
|
|
427
|
+
//=======================
|
|
428
|
+
{
|
|
429
|
+
return this._map.getFeatureState(feature)
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
#removeFeatureState(feature, key)
|
|
433
|
+
//===============================
|
|
434
|
+
{
|
|
435
|
+
this._map.removeFeatureState(feature, key)
|
|
436
|
+
if (this.#paths3dLayer) {
|
|
437
|
+
this.#paths3dLayer.removeFeatureState(feature.id, key)
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
#setFeatureState(feature, state)
|
|
442
|
+
//==============================
|
|
443
|
+
{
|
|
444
|
+
this._map.setFeatureState(feature, state)
|
|
445
|
+
if (this.#paths3dLayer) {
|
|
446
|
+
this.#paths3dLayer.setFeatureState(feature.id, state)
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
|
|
335
450
|
enableMapFeature(feature, enable=true)
|
|
336
451
|
//====================================
|
|
337
452
|
{
|
|
338
453
|
if (feature !== undefined) {
|
|
339
|
-
const state = this
|
|
454
|
+
const state = this.#getFeatureState(feature);
|
|
340
455
|
if ('hidden' in state) {
|
|
341
456
|
if (enable) {
|
|
342
|
-
this
|
|
457
|
+
this.#removeFeatureState(feature, 'hidden');
|
|
343
458
|
} else if (!state.hidden) {
|
|
344
|
-
this
|
|
459
|
+
this.#setFeatureState(feature, { hidden: true });
|
|
345
460
|
}
|
|
346
461
|
} else if (!enable) {
|
|
347
|
-
this
|
|
462
|
+
this.#setFeatureState(feature, { hidden: true });
|
|
348
463
|
}
|
|
349
464
|
this.__enableFeatureMarker(feature.id, enable);
|
|
350
465
|
}
|
|
@@ -392,11 +507,11 @@ export class UserInteractions
|
|
|
392
507
|
//=======================
|
|
393
508
|
{
|
|
394
509
|
if (feature.id) {
|
|
395
|
-
const state = this
|
|
510
|
+
const state = this.#getFeatureState(feature);
|
|
396
511
|
return (state !== undefined
|
|
397
512
|
&& (!('hidden' in state) || !state.hidden));
|
|
398
513
|
}
|
|
399
|
-
return
|
|
514
|
+
return DRAW_ANNOTATION_LAYERS.includes(feature.layer.id)
|
|
400
515
|
}
|
|
401
516
|
|
|
402
517
|
featureSelected_(featureId)
|
|
@@ -409,7 +524,7 @@ export class UserInteractions
|
|
|
409
524
|
//================================
|
|
410
525
|
{
|
|
411
526
|
const ann = this._flatmap.annotation(featureId);
|
|
412
|
-
if ('sckan' in ann) {
|
|
527
|
+
if (ann && 'sckan' in ann) {
|
|
413
528
|
const sckanState = this._layerManager.sckanState;
|
|
414
529
|
if (sckanState === 'none'
|
|
415
530
|
|| sckanState === 'valid' && !ann.sckan
|
|
@@ -426,16 +541,16 @@ export class UserInteractions
|
|
|
426
541
|
} else {
|
|
427
542
|
const feature = this.mapFeature(featureId);
|
|
428
543
|
if (feature !== undefined) {
|
|
429
|
-
const state = this
|
|
544
|
+
const state = this.#getFeatureState(feature);
|
|
430
545
|
if (state !== undefined && (!('hidden' in state) || !state.hidden)) {
|
|
431
|
-
this
|
|
546
|
+
this.#setFeatureState(feature, { selected: true });
|
|
432
547
|
this._selectedFeatureIds.set(featureId, 1);
|
|
433
548
|
result = true;
|
|
434
549
|
}
|
|
435
550
|
}
|
|
436
551
|
}
|
|
437
552
|
if (result && noSelection) {
|
|
438
|
-
this
|
|
553
|
+
this.#setPaint({...this.__colourOptions, dimmed: dim});
|
|
439
554
|
}
|
|
440
555
|
return result;
|
|
441
556
|
}
|
|
@@ -451,13 +566,13 @@ export class UserInteractions
|
|
|
451
566
|
} else {
|
|
452
567
|
const feature = this.mapFeature(featureId);
|
|
453
568
|
if (feature !== undefined) {
|
|
454
|
-
this
|
|
569
|
+
this.#removeFeatureState(feature, 'selected');
|
|
455
570
|
this._selectedFeatureIds.delete(+featureId);
|
|
456
571
|
}
|
|
457
572
|
}
|
|
458
573
|
}
|
|
459
574
|
if (this._selectedFeatureIds.size === 0) {
|
|
460
|
-
this
|
|
575
|
+
this.#setPaint({...this.__colourOptions, dimmed: false});
|
|
461
576
|
}
|
|
462
577
|
}
|
|
463
578
|
|
|
@@ -467,28 +582,42 @@ export class UserInteractions
|
|
|
467
582
|
for (const featureId of this._selectedFeatureIds.keys()) {
|
|
468
583
|
const feature = this.mapFeature(featureId);
|
|
469
584
|
if (feature !== undefined) {
|
|
470
|
-
this
|
|
585
|
+
this.#removeFeatureState(feature, 'selected');
|
|
471
586
|
}
|
|
472
587
|
}
|
|
473
588
|
this._selectedFeatureIds.clear();
|
|
474
|
-
this
|
|
589
|
+
this.#setPaint({...this.__colourOptions, dimmed: false});
|
|
475
590
|
}
|
|
476
591
|
|
|
477
592
|
activateFeature(feature)
|
|
478
593
|
//======================
|
|
479
594
|
{
|
|
480
595
|
if (feature !== undefined) {
|
|
481
|
-
this
|
|
482
|
-
this._activeFeatures.
|
|
596
|
+
this.#setFeatureState(feature, { active: true });
|
|
597
|
+
this._activeFeatures.add(feature);
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
activateLineFeatures(lineFeatures)
|
|
602
|
+
//================================
|
|
603
|
+
{
|
|
604
|
+
for (const lineFeature of lineFeatures) {
|
|
605
|
+
const lineFeatureId = +lineFeature.properties.featureId // Ensure numeric
|
|
606
|
+
this.activateFeature(lineFeature)
|
|
607
|
+
const lineIds = new Set(lineFeatures.map(f => f.properties.featureId))
|
|
608
|
+
for (const featureId of this.__pathManager.lineFeatureIds(lineIds)) {
|
|
609
|
+
this.activateFeature(this.mapFeature(featureId))
|
|
610
|
+
}
|
|
483
611
|
}
|
|
484
612
|
}
|
|
485
613
|
|
|
486
614
|
resetActiveFeatures_()
|
|
487
615
|
//====================
|
|
488
616
|
{
|
|
489
|
-
|
|
490
|
-
this
|
|
617
|
+
for (const feature of this._activeFeatures) {
|
|
618
|
+
this.#removeFeatureState(feature, 'active');
|
|
491
619
|
}
|
|
620
|
+
this._activeFeatures.clear()
|
|
492
621
|
}
|
|
493
622
|
|
|
494
623
|
smallestAnnotatedPolygonFeature_(features)
|
|
@@ -500,7 +629,7 @@ export class UserInteractions
|
|
|
500
629
|
let smallestFeature = null;
|
|
501
630
|
for (const feature of features) {
|
|
502
631
|
if (feature.geometry.type.includes('Polygon')
|
|
503
|
-
&& this
|
|
632
|
+
&& this.#getFeatureState(feature)['map-annotation']) {
|
|
504
633
|
const polygon = turf.geometry(feature.geometry.type, feature.geometry.coordinates);
|
|
505
634
|
const area = turfArea(polygon);
|
|
506
635
|
if (smallestFeature === null || smallestArea > area) {
|
|
@@ -733,7 +862,9 @@ export class UserInteractions
|
|
|
733
862
|
tooltip.push(`<div class="feature-error">Warning: ${properties.warning}</div>`)
|
|
734
863
|
}
|
|
735
864
|
let renderedLabel;
|
|
736
|
-
if (('label' in properties
|
|
865
|
+
if (('label' in properties
|
|
866
|
+
|| 'hyperlink' in properties
|
|
867
|
+
|| 'user_label' in properties)
|
|
737
868
|
&& (forceLabel || !('tooltip' in properties) || properties.tooltip)) {
|
|
738
869
|
const renderedLabel = getRenderedLabel(properties);
|
|
739
870
|
if ('hyperlink' in properties) {
|
|
@@ -775,6 +906,19 @@ export class UserInteractions
|
|
|
775
906
|
this.resetActiveFeatures_();
|
|
776
907
|
}
|
|
777
908
|
|
|
909
|
+
#renderedFeatures(point)
|
|
910
|
+
//======================
|
|
911
|
+
{
|
|
912
|
+
let features = []
|
|
913
|
+
if (this.#paths3dLayer) {
|
|
914
|
+
features = this.#paths3dLayer.queryFeaturesAtPoint(point)
|
|
915
|
+
}
|
|
916
|
+
if (features.length === 0) {
|
|
917
|
+
features = this._map.queryRenderedFeatures(point)
|
|
918
|
+
}
|
|
919
|
+
return features.filter(feature => this.__featureEnabled(feature));
|
|
920
|
+
}
|
|
921
|
+
|
|
778
922
|
mouseMoveEvent_(event)
|
|
779
923
|
//====================
|
|
780
924
|
{
|
|
@@ -793,8 +937,7 @@ export class UserInteractions
|
|
|
793
937
|
}
|
|
794
938
|
|
|
795
939
|
// Get all the features at the current point
|
|
796
|
-
const features = this
|
|
797
|
-
.filter(feature => this.__featureEnabled(feature));
|
|
940
|
+
const features = this.#renderedFeatures(event.point)
|
|
798
941
|
if (features.length === 0) {
|
|
799
942
|
this._lastFeatureMouseEntered = null;
|
|
800
943
|
this._lastFeatureModelsMouse = null;
|
|
@@ -834,19 +977,11 @@ export class UserInteractions
|
|
|
834
977
|
if (lineFeatures.length > 0) {
|
|
835
978
|
tooltip = this.lineTooltip_(lineFeatures);
|
|
836
979
|
tooltipFeature = lineFeatures[0];
|
|
837
|
-
|
|
838
|
-
const lineFeatureId = +lineFeature.properties.featureId; // Ensure numeric
|
|
839
|
-
this.activateFeature(lineFeature);
|
|
840
|
-
const lineIds = new Set(lineFeatures.map(f => f.properties.featureId));
|
|
841
|
-
for (const featureId of this.__pathManager.lineFeatureIds(lineIds)) {
|
|
842
|
-
if (+featureId !== lineFeatureId) {
|
|
843
|
-
this.activateFeature(this.mapFeature(featureId));
|
|
844
|
-
}
|
|
845
|
-
}
|
|
846
|
-
}
|
|
980
|
+
this.activateLineFeatures(lineFeatures)
|
|
847
981
|
} else {
|
|
848
982
|
let labelledFeatures = features.filter(feature => (('hyperlink' in feature.properties
|
|
849
|
-
|| 'label' in feature.properties
|
|
983
|
+
|| 'label' in feature.properties
|
|
984
|
+
|| 'user_label' in feature.properties)
|
|
850
985
|
&& (!('tooltip' in feature.properties)
|
|
851
986
|
|| feature.properties.tooltip)))
|
|
852
987
|
.sort((a, b) => (a.properties.area - b.properties.area));
|
|
@@ -859,6 +994,9 @@ export class UserInteractions
|
|
|
859
994
|
labelledFeatures = groupFeatures;
|
|
860
995
|
}
|
|
861
996
|
const feature = labelledFeatures[0];
|
|
997
|
+
if (feature.properties.user_drawn) {
|
|
998
|
+
feature.id = feature.properties.id
|
|
999
|
+
}
|
|
862
1000
|
tooltip = this.tooltipHtml_(feature.properties);
|
|
863
1001
|
tooltipFeature = feature;
|
|
864
1002
|
if (this._flatmap.options.debug) { // Do this when Info on and not debug??
|
|
@@ -943,7 +1081,7 @@ export class UserInteractions
|
|
|
943
1081
|
//=============================
|
|
944
1082
|
{
|
|
945
1083
|
if (feature !== undefined) {
|
|
946
|
-
const clickedFeatureId = feature.id;
|
|
1084
|
+
const clickedFeatureId = +feature.id;
|
|
947
1085
|
const dim = !('properties' in feature
|
|
948
1086
|
&& 'kind' in feature.properties
|
|
949
1087
|
&& ['cell-type', 'scaffold', 'tissue'].includes(feature.properties.kind));
|
|
@@ -982,9 +1120,9 @@ export class UserInteractions
|
|
|
982
1120
|
}
|
|
983
1121
|
|
|
984
1122
|
this.__clearActiveMarker();
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
if (clickedFeatures.length == 0){
|
|
1123
|
+
|
|
1124
|
+
const clickedFeatures = this.#renderedFeatures(event.point)
|
|
1125
|
+
if (clickedFeatures.length == 0) {
|
|
988
1126
|
this.unselectFeatures();
|
|
989
1127
|
return;
|
|
990
1128
|
}
|
|
@@ -1060,7 +1198,7 @@ export class UserInteractions
|
|
|
1060
1198
|
//=========================================
|
|
1061
1199
|
{
|
|
1062
1200
|
this.__pathManager.enablePathsByType('centreline', enable, force);
|
|
1063
|
-
this
|
|
1201
|
+
this.#setPaint({showCentrelines: enable});
|
|
1064
1202
|
}
|
|
1065
1203
|
|
|
1066
1204
|
enableSckanPaths(sckanState, enable=true)
|
|
@@ -1087,7 +1225,7 @@ export class UserInteractions
|
|
|
1087
1225
|
excludeAnnotated(exclude=false)
|
|
1088
1226
|
//=============================
|
|
1089
1227
|
{
|
|
1090
|
-
this
|
|
1228
|
+
this.#setPaint({excludeAnnotated: exclude});
|
|
1091
1229
|
}
|
|
1092
1230
|
|
|
1093
1231
|
//==============================================================================
|