@chat21/chat21-web-widget 5.0.65 → 5.0.67
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/CHANGELOG.md +8 -0
- package/package.json +1 -1
- package/src/app/app.component.ts +12 -9
- package/src/app/app.module.ts +4 -1
- package/src/app/component/conversation-detail/conversation-emojii/conversation-emojii.component.html +11 -0
- package/src/app/component/conversation-detail/conversation-emojii/conversation-emojii.component.scss +8 -0
- package/src/app/component/conversation-detail/conversation-emojii/conversation-emojii.component.spec.ts +23 -0
- package/src/app/component/conversation-detail/conversation-emojii/conversation-emojii.component.ts +32 -0
- package/src/app/component/conversation-detail/conversation-footer/conversation-footer.component.html +2 -1
- package/src/app/component/conversation-detail/conversation-footer/conversation-footer.component.ts +34 -3
- package/src/app/component/conversation-detail/conversation-preview/conversation-preview.component.ts +3 -2
- package/src/app/component/message/audio/audio.component.html +20 -0
- package/src/app/component/message/audio/audio.component.scss +118 -0
- package/src/app/component/message/audio/audio.component.spec.ts +23 -0
- package/src/app/component/message/audio/audio.component.ts +123 -0
- package/src/app/component/message/bubble-message/bubble-message.component.html +5 -0
- package/src/app/component/message/bubble-message/bubble-message.component.ts +2 -1
- package/src/app/utils/globals.ts +1 -1
- package/src/assets/js/chat21client.js +6 -6
- package/src/chat21-core/providers/mqtt/mqtt-conversation-handler.ts +63 -56
- package/src/chat21-core/providers/mqtt/mqtt-presence.service.ts +3 -3
- package/src/chat21-core/utils/utils-message.ts +13 -6
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
# chat21-web-widget ver 5.0
|
|
2
2
|
|
|
3
|
+
### 5.0.67 in PROD
|
|
4
|
+
- bug-fixed: when refresh the page and an already open conversation is selected, footer is blocked
|
|
5
|
+
- bug-fix: pdf preview image is not contained into its container
|
|
6
|
+
- bug-fixed: cannot push isHere() for presence management if user is disconnected and a notification is received
|
|
7
|
+
|
|
8
|
+
### 5.0.66 in PROD
|
|
9
|
+
- added: LWT e imHere() for presence management
|
|
10
|
+
|
|
3
11
|
### 5.0.65 in PROD
|
|
4
12
|
|
|
5
13
|
### 5.0.65-rc.1
|
package/package.json
CHANGED
package/src/app/app.component.ts
CHANGED
|
@@ -432,7 +432,7 @@ export class AppComponent implements OnInit, AfterViewInit, OnDestroy {
|
|
|
432
432
|
|
|
433
433
|
const subUserLogOut = this.messagingAuthService.BSSignOut.subscribe((state) => {
|
|
434
434
|
// that.ngZone.run(() => {
|
|
435
|
-
|
|
435
|
+
this.logger.log('[FORCE] messagingAuthService BSSignOut', state, this.forceDisconnect)
|
|
436
436
|
if (state === true && !this.forceDisconnect) { //state = true -> user has logged out
|
|
437
437
|
/** ho effettuato il logout: nascondo il widget */
|
|
438
438
|
that.logger.debug('[APP-COMP] sono nel caso logout -1');
|
|
@@ -1507,7 +1507,9 @@ export class AppComponent implements OnInit, AfterViewInit, OnDestroy {
|
|
|
1507
1507
|
// this.g.windowContext.window.document.title = this.tabTitle
|
|
1508
1508
|
} else {
|
|
1509
1509
|
// TAB IS ACTIVE --> restore title and DO NOT SOUND
|
|
1510
|
-
this.
|
|
1510
|
+
if(!this.forceDisconnect){
|
|
1511
|
+
this.presenceService.imHere()
|
|
1512
|
+
}
|
|
1511
1513
|
clearInterval(this.setIntervalTime)
|
|
1512
1514
|
this.setIntervalTime = null;
|
|
1513
1515
|
this.isTabVisible = true;
|
|
@@ -1587,7 +1589,7 @@ export class AppComponent implements OnInit, AfterViewInit, OnDestroy {
|
|
|
1587
1589
|
this.logger.debug('[APP-COMP] openCloseWidget', recipientId, this.g.isOpen, this.g.startFromHome);
|
|
1588
1590
|
if (this.g.isOpen === false) {
|
|
1589
1591
|
if(this.forceDisconnect){
|
|
1590
|
-
|
|
1592
|
+
this.logger.log('[FORCE] onOpenCloseWidget --> reconnect', this.forceDisconnect)
|
|
1591
1593
|
this.messagingAuthService.createCustomToken(this.g.tiledeskToken)
|
|
1592
1594
|
this.forceDisconnect = false;
|
|
1593
1595
|
}
|
|
@@ -1729,7 +1731,7 @@ export class AppComponent implements OnInit, AfterViewInit, OnDestroy {
|
|
|
1729
1731
|
if (this.g.isOpen === false) {
|
|
1730
1732
|
|
|
1731
1733
|
if(this.forceDisconnect){
|
|
1732
|
-
|
|
1734
|
+
this.logger.log('[FORCE] onSelectedConversation --> reconnect', this.forceDisconnect)
|
|
1733
1735
|
this.messagingAuthService.createCustomToken(this.g.tiledeskToken)
|
|
1734
1736
|
this.forceDisconnect = false;
|
|
1735
1737
|
}
|
|
@@ -2095,14 +2097,15 @@ export class AppComponent implements OnInit, AfterViewInit, OnDestroy {
|
|
|
2095
2097
|
|
|
2096
2098
|
|
|
2097
2099
|
private listenToWidgetClick(){
|
|
2100
|
+
const that = this
|
|
2098
2101
|
let clickTimeout = setTimeout(() => {
|
|
2099
|
-
|
|
2102
|
+
that.logger.log('[FORCE] --> NO INTERACTION: disconnection... <--- ')
|
|
2100
2103
|
//disconnect
|
|
2101
|
-
|
|
2102
|
-
|
|
2103
|
-
},
|
|
2104
|
+
that.forceDisconnect = true
|
|
2105
|
+
that.messagingAuthService.logout()
|
|
2106
|
+
}, that.g.disconnetTime * 1000);
|
|
2104
2107
|
window.addEventListener("click", function(){
|
|
2105
|
-
|
|
2108
|
+
that.logger.log('[FORCE] <<INTERACTION>> within 1 minute')
|
|
2106
2109
|
clearTimeout(clickTimeout)
|
|
2107
2110
|
})
|
|
2108
2111
|
}
|
package/src/app/app.module.ts
CHANGED
|
@@ -23,6 +23,7 @@ import { ImageComponent } from './component/message/image/image.component';
|
|
|
23
23
|
import { InfoMessageComponent } from './component/message/info-message/info-message.component';
|
|
24
24
|
import { HtmlComponent } from './component/message/html/html.component';
|
|
25
25
|
import { FrameComponent } from './component/message/frame/frame.component';
|
|
26
|
+
import { AudioComponent } from './component/message/audio/audio.component';
|
|
26
27
|
import { UserTypingComponent } from './../chat21-core/utils/user-typing/user-typing.component';
|
|
27
28
|
/** MESSAGE ATTACHMENTS COMPONENTS */
|
|
28
29
|
import { MessageAttachmentComponent } from './component/message-attachment/message-attachment.component';
|
|
@@ -132,6 +133,7 @@ import { Rules } from './utils/rules';
|
|
|
132
133
|
import { ScriptService } from 'src/chat21-core/providers/scripts/script.service';
|
|
133
134
|
|
|
134
135
|
|
|
136
|
+
|
|
135
137
|
const appInitializerFn = (appConfig: AppConfigService, logger: NGXLogger) => {
|
|
136
138
|
return () => {
|
|
137
139
|
let customLogger = new CustomLogger(logger)
|
|
@@ -287,7 +289,8 @@ export function uploadFactory(http: HttpClient, appConfig: AppConfigService, app
|
|
|
287
289
|
DateAgoPipe,
|
|
288
290
|
SafeHtmlPipe,
|
|
289
291
|
LikeUnlikeComponent,
|
|
290
|
-
TooltipDirective
|
|
292
|
+
TooltipDirective,
|
|
293
|
+
AudioComponent
|
|
291
294
|
],
|
|
292
295
|
imports: [
|
|
293
296
|
BrowserModule,
|
package/src/app/component/conversation-detail/conversation-emojii/conversation-emojii.component.html
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
<emoji-mart id="emoji-mart"
|
|
2
|
+
class="emoji-mart"
|
|
3
|
+
[showPreview]="emojiiOptions?.showPreview"
|
|
4
|
+
[color]="stylesMap?.get('themeColor')"
|
|
5
|
+
[perLine]="emojiiOptions?.emojiPerLine"
|
|
6
|
+
[totalFrequentLines]="emojiiOptions?.totalFrequentLines"
|
|
7
|
+
[enableSearch]="emojiiOptions?.enableSearch"
|
|
8
|
+
[darkMode]="emojiiOptions?.darkMode"
|
|
9
|
+
[include]="emojiiOptions?.include"
|
|
10
|
+
(emojiSelect)="addEmojiFN($event)">
|
|
11
|
+
</emoji-mart>
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
|
2
|
+
|
|
3
|
+
import { ConversationEmojiiComponent } from './conversation-emojii.component';
|
|
4
|
+
|
|
5
|
+
describe('ConversationEmojiiComponent', () => {
|
|
6
|
+
let component: ConversationEmojiiComponent;
|
|
7
|
+
let fixture: ComponentFixture<ConversationEmojiiComponent>;
|
|
8
|
+
|
|
9
|
+
beforeEach(async () => {
|
|
10
|
+
await TestBed.configureTestingModule({
|
|
11
|
+
declarations: [ ConversationEmojiiComponent ]
|
|
12
|
+
})
|
|
13
|
+
.compileComponents();
|
|
14
|
+
|
|
15
|
+
fixture = TestBed.createComponent(ConversationEmojiiComponent);
|
|
16
|
+
component = fixture.componentInstance;
|
|
17
|
+
fixture.detectChanges();
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it('should create', () => {
|
|
21
|
+
expect(component).toBeTruthy();
|
|
22
|
+
});
|
|
23
|
+
});
|
package/src/app/component/conversation-detail/conversation-emojii/conversation-emojii.component.ts
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
|
|
2
|
+
|
|
3
|
+
@Component({
|
|
4
|
+
selector: 'chat-conversation-emojii',
|
|
5
|
+
templateUrl: './conversation-emojii.component.html',
|
|
6
|
+
styleUrls: ['./conversation-emojii.component.scss']
|
|
7
|
+
})
|
|
8
|
+
export class ConversationEmojiiComponent implements OnInit {
|
|
9
|
+
|
|
10
|
+
@Input() var: string;
|
|
11
|
+
@Output() addEmoji = new EventEmitter();
|
|
12
|
+
|
|
13
|
+
emojiiOptions = {
|
|
14
|
+
emojiPerLine : 9,
|
|
15
|
+
totalFrequentLines: 1,
|
|
16
|
+
showPreview: false,
|
|
17
|
+
darkMode: false,
|
|
18
|
+
enableSearch: false,
|
|
19
|
+
include: [ 'recent', 'people', 'nature', 'activity', 'flags']
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
constructor(){}
|
|
23
|
+
|
|
24
|
+
ngOnInit(): void {
|
|
25
|
+
console.log('varrrrr', this.var)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
addEmojiFN(event){
|
|
29
|
+
this.addEmoji.emit(event)
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
}
|
package/src/app/component/conversation-detail/conversation-footer/conversation-footer.component.html
CHANGED
|
@@ -78,7 +78,8 @@
|
|
|
78
78
|
|
|
79
79
|
|
|
80
80
|
<!-- EMOJII PICKER-->
|
|
81
|
-
<div [style.visibility]="isEmojiiPickerShow?'visible':'hidden'" class="emoji-container">
|
|
81
|
+
<div [style.visibility]="isEmojiiPickerShow?'visible':'hidden'" class="emoji-container" id="emoji-mart-container" #emoji_mart_container>
|
|
82
|
+
<!-- <ng-container #emojii_container></ng-container> -->
|
|
82
83
|
<emoji-mart id="emoji-mart"
|
|
83
84
|
*ngIf="showEmojiPicker"
|
|
84
85
|
class="emoji-mart"
|
package/src/app/component/conversation-detail/conversation-footer/conversation-footer.component.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Component, ElementRef, EventEmitter, Input, OnChanges, OnInit, Output, SimpleChanges, ViewChild } from '@angular/core';
|
|
1
|
+
import { Component, ComponentFactoryResolver, ElementRef, EventEmitter, Input, OnChanges, OnInit, Output, SimpleChanges, ViewChild, ViewContainerRef } from '@angular/core';
|
|
2
2
|
import { Globals } from 'src/app/utils/globals';
|
|
3
3
|
import { MessageModel } from 'src/chat21-core/models/message';
|
|
4
4
|
import { UploadModel } from 'src/chat21-core/models/upload';
|
|
@@ -48,7 +48,8 @@ export class ConversationFooterComponent implements OnInit, OnChanges {
|
|
|
48
48
|
@Output() onBackButton = new EventEmitter()
|
|
49
49
|
|
|
50
50
|
@ViewChild('chat21_file') public chat21_file: ElementRef;
|
|
51
|
-
|
|
51
|
+
// @ViewChild('emojii_container', {read: ViewContainerRef}) selector;
|
|
52
|
+
@ViewChild('emoji_mart_container', {read: ViewContainerRef}) public divEmojiiContainer: ViewContainerRef;
|
|
52
53
|
// ========= begin:: send image ======= //
|
|
53
54
|
selectedFiles: FileList;
|
|
54
55
|
isFilePendingToUpload: Boolean = false;
|
|
@@ -62,6 +63,7 @@ export class ConversationFooterComponent implements OnInit, OnChanges {
|
|
|
62
63
|
conversationHandlerService: ConversationHandlerService
|
|
63
64
|
|
|
64
65
|
showEmojiPicker: boolean = false; //To show/hide emoji picker
|
|
66
|
+
loadPickerModule: boolean = true;
|
|
65
67
|
emojiiOptions = {
|
|
66
68
|
emojiPerLine : 9,
|
|
67
69
|
totalFrequentLines: 1,
|
|
@@ -450,7 +452,36 @@ export class ConversationFooterComponent implements OnInit, OnChanges {
|
|
|
450
452
|
}
|
|
451
453
|
}
|
|
452
454
|
|
|
453
|
-
onEmojiiPickerClicked(){
|
|
455
|
+
async onEmojiiPickerClicked(){
|
|
456
|
+
// if(this.loadPickerModule){
|
|
457
|
+
// this.loadPickerModule = false;
|
|
458
|
+
// // this.divEmojiiContainer.clear();
|
|
459
|
+
// this.vcref.clear();
|
|
460
|
+
// const { PickerModule } = await import("@ctrl/ngx-emoji-mart");
|
|
461
|
+
// import('../conversation-emojii/conversation-emojii.component').then(({ConversationEmojiiComponent})=> {
|
|
462
|
+
// // const instance = this.divEmojiiContainer.createComponent(ConversationEmojiiComponent);
|
|
463
|
+
// // // this.divEmojiiContainer.createEmbeddedView(instance.hostView)
|
|
464
|
+
// // this.divEmojiiContainer.insert(instance.hostView);
|
|
465
|
+
// // this.divEmojiiContainer = this.selector.createComponent(ConversationEmojiiComponent);
|
|
466
|
+
// let greetcomp = this.vcref.createComponent(
|
|
467
|
+
// this.cfr.resolveComponentFactory(ConversationEmojiiComponent)
|
|
468
|
+
// );
|
|
469
|
+
// greetcomp.instance.var = 'ssssss'
|
|
470
|
+
// });
|
|
471
|
+
// // this.divEmojiiContainer.nativeElement.insertAdjacentHTML('afterbegin', '<emoji-mart id="emoji-mart"' +
|
|
472
|
+
// // // '*ngIf="showEmojiPicker"'+
|
|
473
|
+
// // 'class="emoji-mart"'+
|
|
474
|
+
// // '[showPreview]="emojiiOptions?.showPreview"'+
|
|
475
|
+
// // // '[color]="stylesMap?.get("themeColor")"' +
|
|
476
|
+
// // '[perLine]="emojiiOptions?.emojiPerLine"'+
|
|
477
|
+
// // '[totalFrequentLines]="emojiiOptions?.totalFrequentLines"'+
|
|
478
|
+
// // '[enableSearch]="emojiiOptions?.enableSearch"'+
|
|
479
|
+
// // '[darkMode]="emojiiOptions?.darkMode"'+
|
|
480
|
+
// // '[include]="emojiiOptions?.include"'+
|
|
481
|
+
// // '(emojiSelect)="addEmoji($event)">'+
|
|
482
|
+
// // '</emoji-mart>')
|
|
483
|
+
|
|
484
|
+
// }
|
|
454
485
|
//OPEN EMOJII PICKER
|
|
455
486
|
this.onEmojiiPickerShow.emit(!this.isEmojiiPickerShow)
|
|
456
487
|
}
|
package/src/app/component/conversation-detail/conversation-preview/conversation-preview.component.ts
CHANGED
|
@@ -148,8 +148,8 @@ export class ConversationPreviewComponent implements OnInit {
|
|
|
148
148
|
const metadata = {
|
|
149
149
|
'name': imageXLoad.title,
|
|
150
150
|
'src': that.sanitizer.bypassSecurityTrustUrl(imageXLoad.src),
|
|
151
|
-
'width':
|
|
152
|
-
'height':
|
|
151
|
+
'width': '80px',
|
|
152
|
+
'height': '106px',
|
|
153
153
|
'type': attachment.metadata.type,
|
|
154
154
|
'uid': attachment.metadata.uid
|
|
155
155
|
};
|
|
@@ -158,6 +158,7 @@ export class ConversationPreviewComponent implements OnInit {
|
|
|
158
158
|
that.arrayFiles.push({metadata});
|
|
159
159
|
if (!that.fileSelected) {
|
|
160
160
|
that.fileSelected = Object.assign({}, metadata)
|
|
161
|
+
that.fileSelected = Object.assign(that.fileSelected, that.getMetadataSize(that.fileSelected))
|
|
161
162
|
}
|
|
162
163
|
};
|
|
163
164
|
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
|
|
2
|
+
|
|
3
|
+
<div id="audio_container" #audio_container>
|
|
4
|
+
|
|
5
|
+
<audio aria-label="traccia audio" #audio_msg controls controlsList="nodownload" id="audio_msg" (pause)="pauseAudioMsg($event)" (play)="playAudioMsg($event)" (timeupdate)="updateTimeAudioMsg($event)">
|
|
6
|
+
<source [src]="metadata?.src" [type]="metadata?.type">
|
|
7
|
+
<!-- {{metadata?.src}} -->
|
|
8
|
+
<!-- controlsList="nodownload" -->
|
|
9
|
+
</audio>
|
|
10
|
+
|
|
11
|
+
<!-- <button id="play-icon" (click)="onPlayPause('play')" *ngIf="status === 'play'">
|
|
12
|
+
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px" fill="#000000"><path d="M0 0h24v24H0V0z" fill="none"/><path d="M10 8.64L15.27 12 10 15.36V8.64M8 5v14l11-7L8 5z"/></svg>
|
|
13
|
+
</button>
|
|
14
|
+
<button id="pause-icon" (click)="onPlayPause('pause')" *ngIf="status === 'pause'">
|
|
15
|
+
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px" fill="#000000"><path d="M0 0h24v24H0V0z" fill="none"/><path d="M6 19h4V5H6v14zm8-14v14h4V5h-4z"/></svg>
|
|
16
|
+
</button>
|
|
17
|
+
<div id="duration" #duration>0:00</div>
|
|
18
|
+
<input type="range" id="seek-slider" max="100" value="0"> -->
|
|
19
|
+
</div>
|
|
20
|
+
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
#play-icon,
|
|
2
|
+
#pause-icon {
|
|
3
|
+
margin: 20px 2.5% 10px 2.5%;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
.time {
|
|
7
|
+
display: inline-block;
|
|
8
|
+
width: 37px;
|
|
9
|
+
text-align: center;
|
|
10
|
+
font-size: 20px;
|
|
11
|
+
margin: 28.5px 0 18.5px 0;
|
|
12
|
+
float: left;
|
|
13
|
+
}
|
|
14
|
+
output {
|
|
15
|
+
display: inline-block;
|
|
16
|
+
width: 32px;
|
|
17
|
+
text-align: center;
|
|
18
|
+
font-size: 20px;
|
|
19
|
+
margin: 10px 2.5% 0 5%;
|
|
20
|
+
float: left;
|
|
21
|
+
clear: left;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
input[type="range"] {
|
|
25
|
+
position: relative;
|
|
26
|
+
-webkit-appearance: none;
|
|
27
|
+
width: 48%;
|
|
28
|
+
margin: 0;
|
|
29
|
+
padding: 0;
|
|
30
|
+
height: 19px;
|
|
31
|
+
margin: 30px 2.5% 20px 2.5%;
|
|
32
|
+
float: left;
|
|
33
|
+
outline: none;
|
|
34
|
+
}
|
|
35
|
+
input[type="range"]::-webkit-slider-runnable-track {
|
|
36
|
+
width: 100%;
|
|
37
|
+
height: 3px;
|
|
38
|
+
cursor: pointer;
|
|
39
|
+
background: linear-gradient(to right, rgba(0, 125, 181, 0.6) var(--buffered-width), rgba(0, 125, 181, 0.2) var(--buffered-width));
|
|
40
|
+
}
|
|
41
|
+
input[type="range"]::before {
|
|
42
|
+
position: absolute;
|
|
43
|
+
content: "";
|
|
44
|
+
top: 8px;
|
|
45
|
+
left: 0;
|
|
46
|
+
width: var(--seek-before-width);
|
|
47
|
+
height: 3px;
|
|
48
|
+
background-color: #007db5;
|
|
49
|
+
cursor: pointer;
|
|
50
|
+
}
|
|
51
|
+
input[type="range"]::-webkit-slider-thumb {
|
|
52
|
+
position: relative;
|
|
53
|
+
-webkit-appearance: none;
|
|
54
|
+
box-sizing: content-box;
|
|
55
|
+
border: 1px solid #007db5;
|
|
56
|
+
height: 15px;
|
|
57
|
+
width: 15px;
|
|
58
|
+
border-radius: 50%;
|
|
59
|
+
background-color: #fff;
|
|
60
|
+
cursor: pointer;
|
|
61
|
+
margin: -7px 0 0 0;
|
|
62
|
+
}
|
|
63
|
+
input[type="range"]:active::-webkit-slider-thumb {
|
|
64
|
+
transform: scale(1.2);
|
|
65
|
+
background: #007db5;
|
|
66
|
+
}
|
|
67
|
+
input[type="range"]::-moz-range-track {
|
|
68
|
+
width: 100%;
|
|
69
|
+
height: 3px;
|
|
70
|
+
cursor: pointer;
|
|
71
|
+
background: linear-gradient(to right, rgba(0, 125, 181, 0.6) var(--buffered-width), rgba(0, 125, 181, 0.2) var(--buffered-width));
|
|
72
|
+
}
|
|
73
|
+
input[type="range"]::-moz-range-progress {
|
|
74
|
+
background-color: #007db5;
|
|
75
|
+
}
|
|
76
|
+
input[type="range"]::-moz-focus-outer {
|
|
77
|
+
border: 0;
|
|
78
|
+
}
|
|
79
|
+
input[type="range"]::-moz-range-thumb {
|
|
80
|
+
box-sizing: content-box;
|
|
81
|
+
border: 1px solid #007db5;
|
|
82
|
+
height: 15px;
|
|
83
|
+
width: 15px;
|
|
84
|
+
border-radius: 50%;
|
|
85
|
+
background-color: #fff;
|
|
86
|
+
cursor: pointer;
|
|
87
|
+
}
|
|
88
|
+
input[type="range"]:active::-moz-range-thumb {
|
|
89
|
+
transform: scale(1.2);
|
|
90
|
+
background: #007db5;
|
|
91
|
+
}
|
|
92
|
+
input[type="range"]::-ms-track {
|
|
93
|
+
width: 100%;
|
|
94
|
+
height: 3px;
|
|
95
|
+
cursor: pointer;
|
|
96
|
+
background: transparent;
|
|
97
|
+
border: solid transparent;
|
|
98
|
+
color: transparent;
|
|
99
|
+
}
|
|
100
|
+
input[type="range"]::-ms-fill-lower {
|
|
101
|
+
background-color: #007db5;
|
|
102
|
+
}
|
|
103
|
+
input[type="range"]::-ms-fill-upper {
|
|
104
|
+
background: linear-gradient(to right, rgba(0, 125, 181, 0.6) var(--buffered-width), rgba(0, 125, 181, 0.2) var(--buffered-width));
|
|
105
|
+
}
|
|
106
|
+
input[type="range"]::-ms-thumb {
|
|
107
|
+
box-sizing: content-box;
|
|
108
|
+
border: 1px solid #007db5;
|
|
109
|
+
height: 15px;
|
|
110
|
+
width: 15px;
|
|
111
|
+
border-radius: 50%;
|
|
112
|
+
background-color: #fff;
|
|
113
|
+
cursor: pointer;
|
|
114
|
+
}
|
|
115
|
+
input[type="range"]:active::-ms-thumb {
|
|
116
|
+
transform: scale(1.2);
|
|
117
|
+
background: #007db5;
|
|
118
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
|
2
|
+
|
|
3
|
+
import { AudioComponent } from './audio.component';
|
|
4
|
+
|
|
5
|
+
describe('AudioComponent', () => {
|
|
6
|
+
let component: AudioComponent;
|
|
7
|
+
let fixture: ComponentFixture<AudioComponent>;
|
|
8
|
+
|
|
9
|
+
beforeEach(async () => {
|
|
10
|
+
await TestBed.configureTestingModule({
|
|
11
|
+
declarations: [ AudioComponent ]
|
|
12
|
+
})
|
|
13
|
+
.compileComponents();
|
|
14
|
+
|
|
15
|
+
fixture = TestBed.createComponent(AudioComponent);
|
|
16
|
+
component = fixture.componentInstance;
|
|
17
|
+
fixture.detectChanges();
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it('should create', () => {
|
|
21
|
+
expect(component).toBeTruthy();
|
|
22
|
+
});
|
|
23
|
+
});
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { Component, ElementRef, EventEmitter, Input, OnInit, Output, ViewChild } from '@angular/core';
|
|
2
|
+
// import lottieWeb from 'https://cdn.skypack.dev/lottie-web';
|
|
3
|
+
|
|
4
|
+
@Component({
|
|
5
|
+
selector: 'chat-audio',
|
|
6
|
+
templateUrl: './audio.component.html',
|
|
7
|
+
styleUrls: ['./audio.component.scss']
|
|
8
|
+
})
|
|
9
|
+
export class AudioComponent implements OnInit {
|
|
10
|
+
|
|
11
|
+
@Input() metadata: any;
|
|
12
|
+
@Output() onElementRendered = new EventEmitter<{element: string, status: boolean}>();
|
|
13
|
+
|
|
14
|
+
uidAudioPlayng: string = ''
|
|
15
|
+
divPlay: HTMLAudioElement
|
|
16
|
+
playState: HTMLElement
|
|
17
|
+
status: 'play' | 'pause' = 'play'
|
|
18
|
+
|
|
19
|
+
constructor(private elementRef: ElementRef) { }
|
|
20
|
+
|
|
21
|
+
ngOnInit() {
|
|
22
|
+
// console.log('metadataaaaaa', this.metadata)
|
|
23
|
+
// this.divPlay = this.elementRef.nativeElement.querySelector('#audio_container').querySelector('#audio_msg')
|
|
24
|
+
// this.playState= this.elementRef.nativeElement.querySelector('#audio_container').querySelector('#duration')
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
onPlayPause(status: string){
|
|
28
|
+
// const divPlay = (<HTMLAudioElement>document.getElementById('audio_msg'));
|
|
29
|
+
if(status === 'play') {
|
|
30
|
+
this.divPlay.play();
|
|
31
|
+
this.status = 'pause'
|
|
32
|
+
} else {
|
|
33
|
+
this.divPlay.pause();
|
|
34
|
+
this.status = 'play'
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
pauseAudioMsg(e) {
|
|
39
|
+
try {
|
|
40
|
+
// stop all audio
|
|
41
|
+
if (this.uidAudioPlayng) {
|
|
42
|
+
const divPlay = (<HTMLAudioElement>document.getElementById(this.uidAudioPlayng));
|
|
43
|
+
divPlay.pause();
|
|
44
|
+
// console.log('> pausa: ', divPlay);
|
|
45
|
+
}
|
|
46
|
+
} catch (error) {
|
|
47
|
+
console.log('> Error is: ', error);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
try {
|
|
51
|
+
// console.log(e.target.id);
|
|
52
|
+
if (this.uidAudioPlayng) {
|
|
53
|
+
this.uidAudioPlayng = '';
|
|
54
|
+
}
|
|
55
|
+
} catch (error) {
|
|
56
|
+
console.log('> Error is: ', error);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
playAudioMsg(e) {
|
|
61
|
+
// stop all audio
|
|
62
|
+
if (this.uidAudioPlayng) {
|
|
63
|
+
const divPlay = (<HTMLAudioElement>document.getElementById(this.uidAudioPlayng));
|
|
64
|
+
divPlay.pause();
|
|
65
|
+
// console.log('> pausa: ', divPlay);
|
|
66
|
+
}
|
|
67
|
+
try {
|
|
68
|
+
// console.log(e.target.id);
|
|
69
|
+
// set uid audio playng
|
|
70
|
+
this.uidAudioPlayng = e.target.id;
|
|
71
|
+
} catch (error) {
|
|
72
|
+
console.log('> Error is: ', error);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
updateTimeAudioMsg(ev){
|
|
77
|
+
var currTime = Math.floor(ev.target.currentTime);
|
|
78
|
+
var duration = Math.floor(ev.target.duration);
|
|
79
|
+
|
|
80
|
+
let minutes = 0;
|
|
81
|
+
if(currTime > 60){
|
|
82
|
+
minutes = Math.floor(currTime / 60);
|
|
83
|
+
}
|
|
84
|
+
const seconds = currTime - minutes * 60
|
|
85
|
+
// console.log('timeeee', minutes + ':' + seconds )
|
|
86
|
+
// this.playState.innerHTML = minutes + ':' + seconds
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
*
|
|
93
|
+
* @param uid
|
|
94
|
+
*/
|
|
95
|
+
playPausaAudioMsg(uid: string) {
|
|
96
|
+
// console.log('playPausaAudioMsg: ', uid);
|
|
97
|
+
const that = this;
|
|
98
|
+
try {
|
|
99
|
+
const divPause = (<HTMLAudioElement>document.getElementById(that.uidAudioPlayng));
|
|
100
|
+
const divPlay = (<HTMLAudioElement>document.getElementById(uid));
|
|
101
|
+
if (divPause) {
|
|
102
|
+
divPause.pause();
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
if (that.uidAudioPlayng === uid) {
|
|
106
|
+
that.uidAudioPlayng = '';
|
|
107
|
+
} else {
|
|
108
|
+
if (divPlay) {
|
|
109
|
+
setTimeout(function() {
|
|
110
|
+
// if (that.g.autoplay_activated) {
|
|
111
|
+
// divPlay.play();
|
|
112
|
+
// }
|
|
113
|
+
this.uidAudioPlayng = uid;
|
|
114
|
+
}, 300);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
} catch (error) {
|
|
119
|
+
console.log('> Error is: ', error);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
}
|
|
@@ -36,6 +36,11 @@
|
|
|
36
36
|
(onElementRendered)="onElementRenderedFN($event)">
|
|
37
37
|
</chat-frame>
|
|
38
38
|
|
|
39
|
+
<chat-audio *ngIf="isAudio(message)"
|
|
40
|
+
[metadata]="message.metadata"
|
|
41
|
+
(onElementRendered)="onElementRenderedFN($event)">
|
|
42
|
+
</chat-audio>
|
|
43
|
+
|
|
39
44
|
<!-- <chat-frame *ngIf="message.metadata && message.metadata.type && message.metadata.type.includes('video')"
|
|
40
45
|
[metadata]="message.metadata"
|
|
41
46
|
[width]="message.metadata.width"
|
|
@@ -5,7 +5,7 @@ import { LoggerService } from 'src/chat21-core/providers/abstract/logger.service
|
|
|
5
5
|
import { LoggerInstance } from 'src/chat21-core/providers/logger/loggerInstance';
|
|
6
6
|
import { MAX_WIDTH_IMAGES, MESSAGE_TYPE_MINE, MESSAGE_TYPE_OTHERS, MIN_WIDTH_IMAGES } from 'src/chat21-core/utils/constants';
|
|
7
7
|
import { convertColorToRGBA } from 'src/chat21-core/utils/utils';
|
|
8
|
-
import { isFile, isFrame, isImage, messageType } from 'src/chat21-core/utils/utils-message';
|
|
8
|
+
import { isAudio, isFile, isFrame, isImage, messageType } from 'src/chat21-core/utils/utils-message';
|
|
9
9
|
import { getColorBck } from 'src/chat21-core/utils/utils-user';
|
|
10
10
|
|
|
11
11
|
@Component({
|
|
@@ -27,6 +27,7 @@ export class BubbleMessageComponent implements OnInit {
|
|
|
27
27
|
isImage = isImage;
|
|
28
28
|
isFile = isFile;
|
|
29
29
|
isFrame = isFrame;
|
|
30
|
+
isAudio = isAudio;
|
|
30
31
|
convertColorToRGBA = convertColorToRGBA
|
|
31
32
|
|
|
32
33
|
// ========== begin:: check message type functions ======= //
|
package/src/app/utils/globals.ts
CHANGED
|
@@ -389,7 +389,7 @@ export class Globals {
|
|
|
389
389
|
/**enable user to set a telegram number to chat with */
|
|
390
390
|
this.fileUploadAccept = "image/*,.pdf,.txt"
|
|
391
391
|
/**enable auto disconnect from messaging after a defined amount of time (s)*/
|
|
392
|
-
this.disconnetTime =
|
|
392
|
+
this.disconnetTime = 60
|
|
393
393
|
|
|
394
394
|
this.showWaitTime = true;
|
|
395
395
|
|
|
@@ -979,12 +979,12 @@ class Chat21Client {
|
|
|
979
979
|
// clean: true,
|
|
980
980
|
reconnectPeriod: 1000,
|
|
981
981
|
// connectTimeout: 30 * 1000,
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
982
|
+
will: {
|
|
983
|
+
topic: this.presence_topic,
|
|
984
|
+
payload: '{"disconnected":true}',
|
|
985
|
+
qos: 1,
|
|
986
|
+
retain: true
|
|
987
|
+
},
|
|
988
988
|
clientId: this.client_id,
|
|
989
989
|
username: 'JWT',
|
|
990
990
|
password: jwt,
|
|
@@ -101,11 +101,14 @@ export class MQTTConversationHandler extends ConversationHandlerService {
|
|
|
101
101
|
this.logger.error('[MQTTConversationHandlerSERVICE] cant connect invalid this.conversationWith', this.conversationWith);
|
|
102
102
|
return;
|
|
103
103
|
}
|
|
104
|
-
this.chat21Service.chatClient.lastMessages(this.conversationWith, (err, messages) => {
|
|
104
|
+
this.chat21Service.chatClient.lastMessages(this.conversationWith, async (err, messages) => {
|
|
105
105
|
if (!err) {
|
|
106
106
|
this.logger.log('[MQTTConversationHandlerSERVICE] message lastMessages:', messages);
|
|
107
107
|
messages.sort(compareValues('timestamp', 'asc'));
|
|
108
|
-
|
|
108
|
+
const that = this
|
|
109
|
+
|
|
110
|
+
// messages.forEach(async (message) => {
|
|
111
|
+
for (const message of messages){
|
|
109
112
|
// this.addedMessage(msg);
|
|
110
113
|
const msg: MessageModel = message;
|
|
111
114
|
msg.uid = message.message_id;
|
|
@@ -115,18 +118,18 @@ export class MQTTConversationHandler extends ConversationHandlerService {
|
|
|
115
118
|
return;
|
|
116
119
|
}
|
|
117
120
|
|
|
118
|
-
if (msg.attributes && msg.attributes.commands
|
|
121
|
+
if (msg.attributes && msg.attributes.commands) {
|
|
119
122
|
this.logger.debug('[MQTTConversationHandlerSERVICE] splitted message::::', this.messages, msg)
|
|
120
|
-
this.addCommandMessage(msg)
|
|
123
|
+
await this.addCommandMessage(msg)
|
|
121
124
|
} else {
|
|
122
125
|
this.logger.debug('[MQTTConversationHandlerSERVICE] NOT splitted message::::', msg)
|
|
123
126
|
this.addedMessage(msg)
|
|
124
127
|
}
|
|
125
|
-
}
|
|
128
|
+
};
|
|
126
129
|
}
|
|
127
130
|
});
|
|
128
131
|
const handler_message_added = this.chat21Service.chatClient.onMessageAddedInConversation(
|
|
129
|
-
this.conversationWith, (message, topic) => {
|
|
132
|
+
this.conversationWith, async (message, topic) => {
|
|
130
133
|
this.logger.log('[MQTTConversationHandlerSERVICE] message added:', message, 'on topic:', topic, this.messages);
|
|
131
134
|
// this.addedMessage(msg);
|
|
132
135
|
const msg: MessageModel = message;
|
|
@@ -143,9 +146,9 @@ export class MQTTConversationHandler extends ConversationHandlerService {
|
|
|
143
146
|
// return;
|
|
144
147
|
// }
|
|
145
148
|
|
|
146
|
-
if (msg.attributes && msg.attributes.commands
|
|
149
|
+
if (msg.attributes && msg.attributes.commands) {
|
|
147
150
|
this.logger.debug('[MQTTConversationHandlerSERVICE] splitted message::::', msg)
|
|
148
|
-
this.addCommandMessage(msg)
|
|
151
|
+
await this.addCommandMessage(msg)
|
|
149
152
|
} else {
|
|
150
153
|
this.logger.debug('[MQTTConversationHandlerSERVICE] NOT splitted message::::', msg)
|
|
151
154
|
this.addedMessage(msg)
|
|
@@ -264,7 +267,7 @@ export class MQTTConversationHandler extends ConversationHandlerService {
|
|
|
264
267
|
}
|
|
265
268
|
|
|
266
269
|
/** */
|
|
267
|
-
private addedMessage(messageSnapshot: MessageModel) {
|
|
270
|
+
private addedMessage(messageSnapshot: MessageModel): Promise<boolean> {
|
|
268
271
|
const msg = this.messageGenerate(messageSnapshot);
|
|
269
272
|
let isInfoMessage = messageType(MESSAGE_TYPE_INFO, msg)
|
|
270
273
|
if(isInfoMessage){
|
|
@@ -461,60 +464,64 @@ export class MQTTConversationHandler extends ConversationHandlerService {
|
|
|
461
464
|
});
|
|
462
465
|
}
|
|
463
466
|
|
|
464
|
-
private addCommandMessage(msg: MessageModel){
|
|
467
|
+
private async addCommandMessage(msg: MessageModel): Promise<boolean>{
|
|
465
468
|
const that = this;
|
|
466
469
|
const commands = msg.attributes.commands;
|
|
467
470
|
let i=0;
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
that.generateMessageObject(msg, command.message, i, function () {
|
|
491
|
-
i += 1
|
|
492
|
-
if (i < commands.length) {
|
|
493
|
-
execute(commands[i])
|
|
471
|
+
return new Promise((resolve, reject)=>{
|
|
472
|
+
function execute(command){
|
|
473
|
+
if(command.type === "message"){
|
|
474
|
+
that.logger.debug('[MQTTConversationHandlerSERVICE] addCommandMessage --> type="message"', command, i)
|
|
475
|
+
if (i >= 2) {
|
|
476
|
+
|
|
477
|
+
//check if previus wait message type has time value, otherwize set to 1000ms
|
|
478
|
+
!commands[i-1].time? commands[i-1].time= 1000 : commands[i-1].time
|
|
479
|
+
command.message.timestamp = commands[i-2].message.timestamp + commands[i-1].time;
|
|
480
|
+
|
|
481
|
+
/** CHECK IF MESSAGE IS JUST RECEIVED: IF false, set next message time (if object exist) to 0 -> this allow to show it immediately */
|
|
482
|
+
if(!isJustRecived(that.startTime.getTime(), msg.timestamp)){
|
|
483
|
+
let previewsTimeMsg = msg.timestamp;
|
|
484
|
+
commands[i-2]? previewsTimeMsg = commands[i-2].message.timestamp : null;
|
|
485
|
+
command.message.timestamp = previewsTimeMsg + 100
|
|
486
|
+
commands[i+1]? commands[i+1].time = 0 : null
|
|
487
|
+
}
|
|
488
|
+
} else { /**MANAGE FIRST MESSAGE */
|
|
489
|
+
command.message.timestamp = msg.timestamp;
|
|
490
|
+
if(!isJustRecived(that.startTime.getTime(), msg.timestamp)){
|
|
491
|
+
commands[i+1]? commands[i+1].time = 0 : null
|
|
492
|
+
}
|
|
494
493
|
}
|
|
495
|
-
|
|
496
|
-
|
|
494
|
+
that.generateMessageObject(msg, command.message, i, function () {
|
|
495
|
+
i += 1
|
|
496
|
+
if (i < commands.length) {
|
|
497
|
+
execute(commands[i])
|
|
498
|
+
}
|
|
499
|
+
else {
|
|
500
|
+
that.logger.debug('[MQTTConversationHandlerSERVICE] addCommandMessage --> last command executed (wait), exit')
|
|
501
|
+
resolve(true)
|
|
502
|
+
}
|
|
503
|
+
})
|
|
504
|
+
}else if(command.type === "wait"){
|
|
505
|
+
that.logger.debug('[MQTTConversationHandlerSERVICE] addCommandMessage --> type="wait"', command, i, commands.length)
|
|
506
|
+
//publish waiting event to simulate user typing
|
|
507
|
+
if(isJustRecived(that.startTime.getTime(), msg.timestamp)){
|
|
508
|
+
// console.log('message just received::', command, i, commands)
|
|
509
|
+
that.messageWait.next({uid: that.conversationWith, uidUserTypingNow: msg.sender, nameUserTypingNow: msg.sender_fullname, waitTime: command.time, command: command})
|
|
497
510
|
}
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
511
|
+
setTimeout(function() {
|
|
512
|
+
i += 1
|
|
513
|
+
if (i < commands.length) {
|
|
514
|
+
execute(commands[i])
|
|
515
|
+
}
|
|
516
|
+
else {
|
|
517
|
+
that.logger.debug('[MQTTConversationHandlerSERVICE] addCommandMessage --> last command executed (send message), exit')
|
|
518
|
+
resolve(true)
|
|
519
|
+
}
|
|
520
|
+
},command.time)
|
|
505
521
|
}
|
|
506
|
-
setTimeout(function() {
|
|
507
|
-
i += 1
|
|
508
|
-
if (i < commands.length) {
|
|
509
|
-
execute(commands[i])
|
|
510
|
-
}
|
|
511
|
-
else {
|
|
512
|
-
that.logger.debug('[MQTTConversationHandlerSERVICE] addCommandMessage --> last command executed (send message), exit')
|
|
513
|
-
}
|
|
514
|
-
},command.time)
|
|
515
522
|
}
|
|
516
|
-
|
|
517
|
-
|
|
523
|
+
execute(commands[0]) //START render first message
|
|
524
|
+
})
|
|
518
525
|
}
|
|
519
526
|
|
|
520
527
|
private generateMessageObject(message, command_message, index, callback) {
|
|
@@ -72,9 +72,9 @@ export class MQTTPresenceService extends PresenceService {
|
|
|
72
72
|
|
|
73
73
|
public imHere(): void {
|
|
74
74
|
this.logger.debug('[MQTT-PRESENCE] imHere', this.tenant);
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
75
|
+
setTimeout(() => {
|
|
76
|
+
this.chat21Service.chatClient.ImHere()
|
|
77
|
+
}, 2000);
|
|
78
78
|
}
|
|
79
79
|
|
|
80
80
|
/**
|
|
@@ -12,14 +12,14 @@ import {
|
|
|
12
12
|
|
|
13
13
|
/** */
|
|
14
14
|
export function isImage(message: any) {
|
|
15
|
-
if (message && message.type && message.
|
|
15
|
+
if (message && message.type && message.type === 'image' && message.metadata && message.metadata.src) {
|
|
16
16
|
return true;
|
|
17
17
|
}
|
|
18
18
|
return false;
|
|
19
19
|
}
|
|
20
20
|
|
|
21
21
|
export function isFrame(message: any) {
|
|
22
|
-
if (message && message.type && message.
|
|
22
|
+
if (message && message.type && message.type === 'frame' && message.metadata && message.metadata.src) {
|
|
23
23
|
return true;
|
|
24
24
|
}
|
|
25
25
|
return false;
|
|
@@ -27,10 +27,17 @@ export function isFrame(message: any) {
|
|
|
27
27
|
|
|
28
28
|
/** */
|
|
29
29
|
export function isFile(message: any) {
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
30
|
+
if (message && message.type && message.type === 'file' && message.metadata && message.metadata.src && !message.metadata.includes('audio')) {
|
|
31
|
+
return true;
|
|
32
|
+
}
|
|
33
|
+
return false;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function isAudio(message: any) {
|
|
37
|
+
if (message && message.type && message.type === 'file' && message.metadata && message.metadata.src && message.metadata.includes('audio') ) {
|
|
38
|
+
return true;
|
|
39
|
+
}
|
|
40
|
+
return false;
|
|
34
41
|
}
|
|
35
42
|
|
|
36
43
|
/** */
|