@rtsee/ngx 0.0.112 → 0.0.113

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.
@@ -10,23 +10,23 @@ import * as i4 from 'primeng/button';
10
10
  import { Button, ButtonModule } from 'primeng/button';
11
11
  import { interval, tap, map, filter } from 'rxjs';
12
12
  import { RTSeeTextLabelKeys, RTSeeCore, hasPermission, RTSeePeerPermissions } from '@rtsee/core';
13
- import * as i2$2 from 'primeng/message';
14
- import { Message, MessageModule } from 'primeng/message';
15
13
  import { AuthEndpointsKeys, LS_TOKEN_LABEL, LS_USER_LABEL, DEFAULT_ROUTE_NAMES } from '@rtsee/auth';
16
14
  import * as i2 from '@angular/router';
17
15
  import { RouterLink, RouterLinkActive, NavigationEnd } from '@angular/router';
18
- import { Divider } from 'primeng/divider';
19
16
  import * as i1$1 from '@angular/common/http';
20
17
  import { RTSeeChatMemberRoles, RTSeeIllustratorEvents, RTSeeChatTypes, OzzyTaskStatus, getStoryTranslatorLanguage, STORY_TRANSLATOR_LANGUAGES } from '@rtsee/common';
21
18
  import { sanitizeImageUrl, ManageAgentForm, RTSeeForm, RTSeeFormFieldRadioGroup, RTSeeFormField, RTSeeFormFieldImageUploader, fetchImageBlob, RTSeeRater } from '@rtsee/utilities-ui';
22
19
  import { InputText } from 'primeng/inputtext';
23
20
  import { InputGroup } from 'primeng/inputgroup';
21
+ import * as i2$2 from 'primeng/message';
22
+ import { Message, MessageModule } from 'primeng/message';
24
23
  import { Textarea } from 'primeng/textarea';
25
24
  import { Checkbox } from 'primeng/checkbox';
26
25
  import { RadioButton } from 'primeng/radiobutton';
27
26
  import { InputNumber } from 'primeng/inputnumber';
28
27
  import * as i1$2 from 'primeng/autocomplete';
29
28
  import { AutoCompleteModule } from 'primeng/autocomplete';
29
+ import { Divider } from 'primeng/divider';
30
30
  import { ImageCropperComponent } from 'ngx-image-cropper';
31
31
  import { Tooltip } from 'primeng/tooltip';
32
32
  import { Fieldset } from 'primeng/fieldset';
@@ -51,7 +51,6 @@ import { Tabs, TabList, Tab, TabPanels, TabPanel } from 'primeng/tabs';
51
51
  import { ColorPicker } from 'primeng/colorpicker';
52
52
  import { Slider } from 'primeng/slider';
53
53
  import { Tag } from 'primeng/tag';
54
- import { Listbox } from 'primeng/listbox';
55
54
  import { RTSeeRoutesList } from '@rtsee/factory';
56
55
  import { Avatar } from 'primeng/avatar';
57
56
  import { Image } from 'primeng/image';
@@ -284,21 +283,81 @@ class ProfileComponent {
284
283
  this.RTSeeTextLabelKeys = RTSeeTextLabelKeys;
285
284
  this.authConfiguration = this.authService.rtseeAuth.authConfiguration;
286
285
  }
286
+ get targetPeer() {
287
+ if (this.peer) {
288
+ return { id: this.peer.clientId, name: this.peer.name, imageUrl: this.peer.imageUrl };
289
+ }
290
+ if (this.messenger?.openedChat) {
291
+ const myId = this.messenger.myPeer?.clientId || this.messenger.clientId;
292
+ const other = this.messenger.openedChat.members.find((m) => m.id !== myId && m.clientId !== myId);
293
+ if (other) {
294
+ return other;
295
+ }
296
+ }
297
+ if (this.messenger?.myPeer) {
298
+ return {
299
+ id: this.messenger.myPeer.clientId,
300
+ name: this.messenger.myPeer.name,
301
+ imageUrl: this.messenger.myPeer.imageUrl,
302
+ };
303
+ }
304
+ return undefined;
305
+ }
306
+ get isOtherUser() {
307
+ if (!this.messenger?.ownProfileOpened) {
308
+ return true;
309
+ }
310
+ const myId = this.messenger?.myPeer?.clientId || this.messenger?.clientId;
311
+ if (this.targetPeer && myId && this.targetPeer.id !== myId) {
312
+ return true;
313
+ }
314
+ return false;
315
+ }
316
+ get displayName() {
317
+ return (this.targetPeer?.name ||
318
+ this.messenger?.openedChat?.name ||
319
+ this.messenger?.myPeer?.name ||
320
+ 'User');
321
+ }
322
+ get displayInitials() {
323
+ const name = this.displayName.trim();
324
+ if (!name)
325
+ return '?';
326
+ const parts = name.split(' ');
327
+ if (parts.length >= 2 && parts[1]) {
328
+ return (parts[0][0] + parts[1][0]).toUpperCase();
329
+ }
330
+ return name.slice(0, 2).toUpperCase();
331
+ }
332
+ get displayImageUrl() {
333
+ return this.targetPeer?.imageUrl || (this.isOtherUser ? undefined : this.messenger?.myPeer?.imageUrl);
334
+ }
335
+ get displaySubtitle() {
336
+ if (this.isOtherUser) {
337
+ return this.messenger?.openedChat ? 'Direct Chat' : 'Contact';
338
+ }
339
+ if (!this.messenger?.myPeer?.name) {
340
+ return '';
341
+ }
342
+ return 'My Profile';
343
+ }
287
344
  setLanguage() {
288
345
  this.messenger.setLanguage(this.messenger.currentLanguage);
289
346
  void this.messenger.propagateLanguageChange(this.messenger.currentLanguage);
290
347
  }
291
348
  call() {
292
- if (!this.messenger.openedChat) {
349
+ const chat = this.messenger.openedChat;
350
+ if (!chat) {
293
351
  return;
294
352
  }
295
- void this.messenger.initializeVideoCall(this.messenger.openedChat);
353
+ void this.messenger.initializeVideoCall(chat);
296
354
  }
297
355
  videoCall() {
298
- if (!this.messenger.openedChat) {
356
+ const chat = this.messenger.openedChat;
357
+ if (!chat) {
299
358
  return;
300
359
  }
301
- void this.messenger.initializeVideoCall(this.messenger.openedChat);
360
+ void this.messenger.initializeVideoCall(chat);
302
361
  }
303
362
  logOut() {
304
363
  this.authService.rtseeAuth.logOut();
@@ -308,11 +367,11 @@ class ProfileComponent {
308
367
  }, 200);
309
368
  }
310
369
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.4", ngImport: i0, type: ProfileComponent, deps: [{ token: RTSeeAuthService }, { token: i2.Router }], target: i0.ɵɵFactoryTarget.Component }); }
311
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.4", type: ProfileComponent, isStandalone: true, selector: "rtsee-profile", inputs: { messenger: "messenger", peer: "peer" }, ngImport: i0, template: "<div class=\"rts-messenger-profile\">\n @if (!messenger.ownProfileOpened) {\n <ul class=\"rts-messenger-profile-actions\">\n <li class=\"rts-messenger-profile-actions-item\">\n <p-button\n (click)=\"call()\"\n size=\"large\"\n severity=\"secondary\"\n icon=\"pi pi-phone\"\n />\n </li>\n <li class=\"rts-messenger-profile-actions-item\">\n <p-button\n (click)=\"videoCall()\"\n size=\"large\"\n severity=\"secondary\"\n icon=\"pi pi-video\"\n />\n </li>\n </ul>\n }\n\n @if (messenger.ownProfileOpened && messenger.myPeer && !messenger.myPeer.isAnonymous) {\n <div class=\"rts-messenger-profile-info\">\n <div class=\"rts-messenger-profile-glass-card\">\n <p class=\"rts-messenger-profile-info-name\">{{messenger.myPeer.name}}</p>\n <div class=\"rts-messenger-profile-info-language\">\n <p class=\"rts-messenger-profile-info-language-label\">Language:</p>\n <p-select [options]=\"messenger.languageOptions\"\n [(ngModel)]=\"messenger.currentLanguage\"\n (ngModelChange)=\"setLanguage()\"\n optionLabel=\"key\"\n optionValue=\"key\"\n [placeholder]=\"messenger.labels[RTSeeTextLabelKeys.MESSANGER_LANGUAGE_PLACEHOLDER]\"\n class=\"rts-messenger-profile-info-language-select\"\n />\n </div>\n <div class=\"rts-messenger-profile-edit-btn\">\n <p-button [label]=\"messenger.labels[RTSeeTextLabelKeys.MESSENGER_EDIT_PROFILE_BTN_LABEL]\"\n (click)=\"messenger.openManageProfile()\"\n ></p-button>\n </div>\n </div>\n\n @if (messenger.myPeer.plan) {\n <p-divider></p-divider>\n <div class=\"rts-messenger-profile-glass-card\">\n <div class=\"rts-messenger-profile-plan-status\">\n <p-message severity=\"success\">\n <span>{{messenger.myPeer.plan.productName}}</span>\n </p-message>\n </div>\n <div class=\"rts-messenger-profile-manage-plan-btn\">\n <p-button [label]=\"messenger.labels[RTSeeTextLabelKeys.MANAGE_PLAN]\"\n (click)=\"messenger.openManagePlanPage()\"\n ></p-button>\n </div>\n </div>\n } @else {\n <p-divider></p-divider>\n <div class=\"rts-messenger-profile-subscribe-btn\">\n <p-button [label]=\"messenger.labels[RTSeeTextLabelKeys.SUBSCRIBE_LABEL]\"\n (click)=\"messenger.openPaymentPage()\"\n ></p-button>\n </div>\n }\n </div>\n }\n\n @if (messenger.ownProfileOpened && messenger.myPeer?.isAnonymous) {\n <div class=\"rts-messenger-profile-auth\">\n <div class=\"rts-messenger-profile-anon-message\">\n @if (!messenger.currentLanguage || messenger.currentLanguage === 'eng') {\n <p-message severity=\"warn\">\n <span>This is an anonymous identity we've generated for you.</span>\n <span>&nbsp;</span>\n <span>You can use it to play around.</span>\n <span>&nbsp;</span>\n <span>But we are guilty of erasing these time after time.</span>\n <span>&nbsp;</span>\n <span>So please Sign Up if you're looking for something more stable :)</span>\n </p-message>\n } @else if (messenger.currentLanguage === 'ua') {\n <p-message severity=\"warn\">\n <span>\u041C\u0438 \u0437\u0433\u0435\u043D\u0435\u0440\u0443\u0432\u0430\u043B\u0438 \u0434\u043B\u044F \u0432\u0430\u0441 \u0430\u043D\u043E\u043D\u0456\u043C\u043D\u0443 \u043E\u0441\u043E\u0431\u0438\u0441\u0442\u0456\u0441\u0442\u044C.</span>\n <span>&nbsp;</span>\n <span>\u0421\u043A\u043E\u0440\u0438\u0441\u0442\u0430\u0439\u0442\u0435\u0441\u044F \u043D\u0435\u044E \u0434\u043B\u044F \u043E\u0437\u043D\u0430\u0439\u043E\u043C\u043B\u0435\u043D\u043D\u044F \u0437 \u0441\u0430\u0439\u0442\u043E\u043C.</span>\n <span>&nbsp;</span>\n <span>\u0422\u0430, \u0447\u0430\u0441-\u0432\u0456\u0434-\u0447\u0430\u0441\u0443, \u043C\u0438 \u0457\u0445 \u0432\u0438\u0434\u0430\u043B\u044F\u0454\u043C\u043E.</span>\n <span>&nbsp;</span>\n <span>\u0422\u043E\u0436 \u043A\u0440\u0430\u0449\u0435 \u0437\u0430\u0440\u0435\u0454\u0441\u0442\u0440\u0443\u0442\u0435\u0441\u044F, \u0430\u0431\u0438 \u043D\u0435 \u0432\u0442\u0440\u0430\u0442\u0438\u0442\u0438 \u0434\u0430\u043D\u0456 :)</span>\n </p-message>\n }\n </div>\n\n <div class=\"rts-messenger-profile-auth-buttons\">\n <p-button [label]=\"messenger.labels[RTSeeTextLabelKeys.SIGN_UP_BUTTON]\"\n severity=\"primary\"\n [routerLink]=\"authConfiguration.getRoutePathSplit(AuthEndpointsKeys.SIGN_UP)\"\n class=\"rts-messenger-profile-auth-button\"\n ></p-button>\n <p-button [label]=\"messenger.labels[RTSeeTextLabelKeys.SIGN_IN_BUTTON]\"\n severity=\"secondary\"\n [routerLink]=\"authConfiguration.getRoutePathSplit(AuthEndpointsKeys.SIGN_IN)\"\n class=\"rts-messenger-profile-auth-button\"\n ></p-button>\n </div>\n </div>\n } @else if (messenger.ownProfileOpened && !messenger.myPeer?.isAnonymous) {\n <p-divider></p-divider>\n <div class=\"rts-messenger-profile-logout-btn\">\n <p-button [label]=\"messenger.labels[RTSeeTextLabelKeys.LOGOUT_BUTTON]\"\n severity=\"danger\"\n (click)=\"logOut()\"\n ></p-button>\n </div>\n }\n</div>\n", dependencies: [{ kind: "component", type: Button, selector: "p-button", inputs: ["type", "iconPos", "icon", "badge", "label", "disabled", "loading", "loadingIcon", "raised", "rounded", "text", "plain", "severity", "outlined", "link", "tabindex", "size", "variant", "style", "styleClass", "badgeClass", "badgeSeverity", "ariaLabel", "buttonProps", "autofocus", "fluid"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "component", type: Message, selector: "p-message", inputs: ["severity", "text", "escape", "style", "styleClass", "closable", "icon", "closeIcon", "life", "showTransitionOptions", "hideTransitionOptions", "size", "variant"], outputs: ["onClose"] }, { kind: "directive", type: RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }, { kind: "component", type: Select, selector: "p-select", inputs: ["id", "scrollHeight", "filter", "panelStyle", "styleClass", "panelStyleClass", "readonly", "editable", "tabindex", "placeholder", "loadingIcon", "filterPlaceholder", "filterLocale", "inputId", "dataKey", "filterBy", "filterFields", "autofocus", "resetFilterOnHide", "checkmark", "dropdownIcon", "loading", "optionLabel", "optionValue", "optionDisabled", "optionGroupLabel", "optionGroupChildren", "group", "showClear", "emptyFilterMessage", "emptyMessage", "lazy", "virtualScroll", "virtualScrollItemSize", "virtualScrollOptions", "overlayOptions", "ariaFilterLabel", "ariaLabel", "ariaLabelledBy", "filterMatchMode", "tooltip", "tooltipPosition", "tooltipPositionStyle", "tooltipStyleClass", "focusOnHover", "selectOnFocus", "autoOptionFocus", "autofocusFilter", "filterValue", "options", "appendTo"], outputs: ["onChange", "onFilter", "onFocus", "onBlur", "onClick", "onShow", "onHide", "onClear", "onLazyLoad"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: Divider, selector: "p-divider", inputs: ["styleClass", "layout", "type", "align"] }] }); }
370
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.4", type: ProfileComponent, isStandalone: true, selector: "rtsee-profile", inputs: { messenger: "messenger", peer: "peer" }, ngImport: i0, template: "<div class=\"rts-chat-info rts-messenger-profile\">\n @if (displaySubtitle) {\n <div class=\"rts-chat-info-meta\">\n <p class=\"rts-chat-info-type\">{{ displaySubtitle }}</p>\n </div>\n }\n\n @if (isOtherUser) {\n <div class=\"rts-chat-info-call-actions\">\n <button class=\"rts-call-btn\"\n (click)=\"call()\"\n type=\"button\"\n title=\"Audio Call\"\n >\n <span class=\"material-icons-outlined\">call</span>\n <span>Audio</span>\n </button>\n <button class=\"rts-call-btn rts-call-btn-video\"\n (click)=\"videoCall()\"\n type=\"button\"\n title=\"Video Call\"\n >\n <span class=\"material-icons-outlined\">videocam</span>\n <span>Video</span>\n </button>\n </div>\n }\n\n @if (!isOtherUser && messenger.myPeer && !messenger.myPeer.isAnonymous) {\n <div class=\"rts-chat-info-actions\">\n <button class=\"rts-btn rts-btn-ghost rts-btn-block\"\n (click)=\"messenger.openManageProfile()\"\n >\n <span class=\"material-icons-outlined\">edit</span>\n {{ messenger.labels[RTSeeTextLabelKeys.MESSENGER_EDIT_PROFILE_BTN_LABEL] || 'Edit Profile' }}\n </button>\n </div>\n\n <hr class=\"rts-divider\" />\n\n <div class=\"rts-profile-section\">\n <div class=\"rts-profile-field-row\">\n <label class=\"rts-profile-field-label\">Language</label>\n <p-select [options]=\"messenger.languageOptions\"\n [(ngModel)]=\"messenger.currentLanguage\"\n (ngModelChange)=\"setLanguage()\"\n optionLabel=\"key\"\n optionValue=\"key\"\n [placeholder]=\"messenger.labels[RTSeeTextLabelKeys.MESSANGER_LANGUAGE_PLACEHOLDER]\"\n class=\"rts-profile-language-select\"\n />\n </div>\n </div>\n\n @if (messenger.myPeer.plan) {\n <hr class=\"rts-divider\" />\n <div class=\"rts-profile-section\">\n <div class=\"rts-profile-plan-badge\">\n <span class=\"material-icons-outlined\">verified</span>\n <span>{{ messenger.myPeer.plan.productName }}</span>\n </div>\n <button class=\"rts-btn rts-btn-ghost rts-btn-block\"\n (click)=\"messenger.openManagePlanPage()\"\n >\n {{ messenger.labels[RTSeeTextLabelKeys.MANAGE_PLAN] || 'Manage Plan' }}\n </button>\n </div>\n } @else {\n <hr class=\"rts-divider\" />\n <div class=\"rts-profile-section\">\n <button class=\"rts-btn rts-btn-primary rts-btn-block\"\n (click)=\"messenger.openPaymentPage()\"\n >\n <span class=\"material-icons-outlined\">stars</span>\n {{ messenger.labels[RTSeeTextLabelKeys.SUBSCRIBE_LABEL] || 'Upgrade Plan' }}\n </button>\n </div>\n }\n\n <hr class=\"rts-divider\" />\n <div class=\"rts-chat-info-danger-zone\">\n <button class=\"rts-btn rts-btn-danger rts-btn-block\"\n (click)=\"logOut()\"\n >\n {{ messenger.labels[RTSeeTextLabelKeys.LOGOUT_BUTTON] || 'Logout' }}\n </button>\n </div>\n }\n\n @if (!isOtherUser && messenger.myPeer?.isAnonymous) {\n <hr class=\"rts-divider\" />\n <div class=\"rts-profile-anon-box\">\n <div class=\"rts-profile-anon-icon\">\n <span class=\"material-icons-outlined\">shield</span>\n </div>\n <div class=\"rts-profile-anon-text\">\n @if (!messenger.currentLanguage || messenger.currentLanguage === 'eng') {\n <p>This is a temporary anonymous identity. Sign up or sign in to save your history permanently.</p>\n } @else if (messenger.currentLanguage === 'ua') {\n <p>\u0426\u0435 \u0442\u0438\u043C\u0447\u0430\u0441\u043E\u0432\u0430 \u0430\u043D\u043E\u043D\u0456\u043C\u043D\u0430 \u043E\u0441\u043E\u0431\u0438\u0441\u0442\u0456\u0441\u0442\u044C. \u0417\u0430\u0440\u0435\u0454\u0441\u0442\u0440\u0443\u0439\u0442\u0435\u0441\u044F, \u0430\u0431\u0438 \u0437\u0431\u0435\u0440\u0435\u0433\u0442\u0438 \u0456\u0441\u0442\u043E\u0440\u0456\u044E \u0441\u043F\u0456\u043B\u043A\u0443\u0432\u0430\u043D\u043D\u044F.</p>\n }\n </div>\n </div>\n\n <div class=\"rts-profile-auth-actions\">\n <button class=\"rts-btn rts-btn-primary rts-btn-block\"\n [routerLink]=\"authConfiguration.getRoutePathSplit(AuthEndpointsKeys.SIGN_UP)\"\n >\n {{ messenger.labels[RTSeeTextLabelKeys.SIGN_UP_BUTTON] || 'Sign Up' }}\n </button>\n <button class=\"rts-btn rts-btn-ghost rts-btn-block\"\n [routerLink]=\"authConfiguration.getRoutePathSplit(AuthEndpointsKeys.SIGN_IN)\"\n >\n {{ messenger.labels[RTSeeTextLabelKeys.SIGN_IN_BUTTON] || 'Sign In' }}\n </button>\n </div>\n }\n</div>\n", dependencies: [{ kind: "directive", type: RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }, { kind: "component", type: Select, selector: "p-select", inputs: ["id", "scrollHeight", "filter", "panelStyle", "styleClass", "panelStyleClass", "readonly", "editable", "tabindex", "placeholder", "loadingIcon", "filterPlaceholder", "filterLocale", "inputId", "dataKey", "filterBy", "filterFields", "autofocus", "resetFilterOnHide", "checkmark", "dropdownIcon", "loading", "optionLabel", "optionValue", "optionDisabled", "optionGroupLabel", "optionGroupChildren", "group", "showClear", "emptyFilterMessage", "emptyMessage", "lazy", "virtualScroll", "virtualScrollItemSize", "virtualScrollOptions", "overlayOptions", "ariaFilterLabel", "ariaLabel", "ariaLabelledBy", "filterMatchMode", "tooltip", "tooltipPosition", "tooltipPositionStyle", "tooltipStyleClass", "focusOnHover", "selectOnFocus", "autoOptionFocus", "autofocusFilter", "filterValue", "options", "appendTo"], outputs: ["onChange", "onFilter", "onFocus", "onBlur", "onClick", "onShow", "onHide", "onClear", "onLazyLoad"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }] }); }
312
371
  }
313
372
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.4", ngImport: i0, type: ProfileComponent, decorators: [{
314
373
  type: Component,
315
- args: [{ selector: 'rtsee-profile', imports: [Button, Message, RouterLink, Select, FormsModule, Divider], standalone: true, template: "<div class=\"rts-messenger-profile\">\n @if (!messenger.ownProfileOpened) {\n <ul class=\"rts-messenger-profile-actions\">\n <li class=\"rts-messenger-profile-actions-item\">\n <p-button\n (click)=\"call()\"\n size=\"large\"\n severity=\"secondary\"\n icon=\"pi pi-phone\"\n />\n </li>\n <li class=\"rts-messenger-profile-actions-item\">\n <p-button\n (click)=\"videoCall()\"\n size=\"large\"\n severity=\"secondary\"\n icon=\"pi pi-video\"\n />\n </li>\n </ul>\n }\n\n @if (messenger.ownProfileOpened && messenger.myPeer && !messenger.myPeer.isAnonymous) {\n <div class=\"rts-messenger-profile-info\">\n <div class=\"rts-messenger-profile-glass-card\">\n <p class=\"rts-messenger-profile-info-name\">{{messenger.myPeer.name}}</p>\n <div class=\"rts-messenger-profile-info-language\">\n <p class=\"rts-messenger-profile-info-language-label\">Language:</p>\n <p-select [options]=\"messenger.languageOptions\"\n [(ngModel)]=\"messenger.currentLanguage\"\n (ngModelChange)=\"setLanguage()\"\n optionLabel=\"key\"\n optionValue=\"key\"\n [placeholder]=\"messenger.labels[RTSeeTextLabelKeys.MESSANGER_LANGUAGE_PLACEHOLDER]\"\n class=\"rts-messenger-profile-info-language-select\"\n />\n </div>\n <div class=\"rts-messenger-profile-edit-btn\">\n <p-button [label]=\"messenger.labels[RTSeeTextLabelKeys.MESSENGER_EDIT_PROFILE_BTN_LABEL]\"\n (click)=\"messenger.openManageProfile()\"\n ></p-button>\n </div>\n </div>\n\n @if (messenger.myPeer.plan) {\n <p-divider></p-divider>\n <div class=\"rts-messenger-profile-glass-card\">\n <div class=\"rts-messenger-profile-plan-status\">\n <p-message severity=\"success\">\n <span>{{messenger.myPeer.plan.productName}}</span>\n </p-message>\n </div>\n <div class=\"rts-messenger-profile-manage-plan-btn\">\n <p-button [label]=\"messenger.labels[RTSeeTextLabelKeys.MANAGE_PLAN]\"\n (click)=\"messenger.openManagePlanPage()\"\n ></p-button>\n </div>\n </div>\n } @else {\n <p-divider></p-divider>\n <div class=\"rts-messenger-profile-subscribe-btn\">\n <p-button [label]=\"messenger.labels[RTSeeTextLabelKeys.SUBSCRIBE_LABEL]\"\n (click)=\"messenger.openPaymentPage()\"\n ></p-button>\n </div>\n }\n </div>\n }\n\n @if (messenger.ownProfileOpened && messenger.myPeer?.isAnonymous) {\n <div class=\"rts-messenger-profile-auth\">\n <div class=\"rts-messenger-profile-anon-message\">\n @if (!messenger.currentLanguage || messenger.currentLanguage === 'eng') {\n <p-message severity=\"warn\">\n <span>This is an anonymous identity we've generated for you.</span>\n <span>&nbsp;</span>\n <span>You can use it to play around.</span>\n <span>&nbsp;</span>\n <span>But we are guilty of erasing these time after time.</span>\n <span>&nbsp;</span>\n <span>So please Sign Up if you're looking for something more stable :)</span>\n </p-message>\n } @else if (messenger.currentLanguage === 'ua') {\n <p-message severity=\"warn\">\n <span>\u041C\u0438 \u0437\u0433\u0435\u043D\u0435\u0440\u0443\u0432\u0430\u043B\u0438 \u0434\u043B\u044F \u0432\u0430\u0441 \u0430\u043D\u043E\u043D\u0456\u043C\u043D\u0443 \u043E\u0441\u043E\u0431\u0438\u0441\u0442\u0456\u0441\u0442\u044C.</span>\n <span>&nbsp;</span>\n <span>\u0421\u043A\u043E\u0440\u0438\u0441\u0442\u0430\u0439\u0442\u0435\u0441\u044F \u043D\u0435\u044E \u0434\u043B\u044F \u043E\u0437\u043D\u0430\u0439\u043E\u043C\u043B\u0435\u043D\u043D\u044F \u0437 \u0441\u0430\u0439\u0442\u043E\u043C.</span>\n <span>&nbsp;</span>\n <span>\u0422\u0430, \u0447\u0430\u0441-\u0432\u0456\u0434-\u0447\u0430\u0441\u0443, \u043C\u0438 \u0457\u0445 \u0432\u0438\u0434\u0430\u043B\u044F\u0454\u043C\u043E.</span>\n <span>&nbsp;</span>\n <span>\u0422\u043E\u0436 \u043A\u0440\u0430\u0449\u0435 \u0437\u0430\u0440\u0435\u0454\u0441\u0442\u0440\u0443\u0442\u0435\u0441\u044F, \u0430\u0431\u0438 \u043D\u0435 \u0432\u0442\u0440\u0430\u0442\u0438\u0442\u0438 \u0434\u0430\u043D\u0456 :)</span>\n </p-message>\n }\n </div>\n\n <div class=\"rts-messenger-profile-auth-buttons\">\n <p-button [label]=\"messenger.labels[RTSeeTextLabelKeys.SIGN_UP_BUTTON]\"\n severity=\"primary\"\n [routerLink]=\"authConfiguration.getRoutePathSplit(AuthEndpointsKeys.SIGN_UP)\"\n class=\"rts-messenger-profile-auth-button\"\n ></p-button>\n <p-button [label]=\"messenger.labels[RTSeeTextLabelKeys.SIGN_IN_BUTTON]\"\n severity=\"secondary\"\n [routerLink]=\"authConfiguration.getRoutePathSplit(AuthEndpointsKeys.SIGN_IN)\"\n class=\"rts-messenger-profile-auth-button\"\n ></p-button>\n </div>\n </div>\n } @else if (messenger.ownProfileOpened && !messenger.myPeer?.isAnonymous) {\n <p-divider></p-divider>\n <div class=\"rts-messenger-profile-logout-btn\">\n <p-button [label]=\"messenger.labels[RTSeeTextLabelKeys.LOGOUT_BUTTON]\"\n severity=\"danger\"\n (click)=\"logOut()\"\n ></p-button>\n </div>\n }\n</div>\n" }]
374
+ args: [{ selector: 'rtsee-profile', imports: [RouterLink, Select, FormsModule], standalone: true, template: "<div class=\"rts-chat-info rts-messenger-profile\">\n @if (displaySubtitle) {\n <div class=\"rts-chat-info-meta\">\n <p class=\"rts-chat-info-type\">{{ displaySubtitle }}</p>\n </div>\n }\n\n @if (isOtherUser) {\n <div class=\"rts-chat-info-call-actions\">\n <button class=\"rts-call-btn\"\n (click)=\"call()\"\n type=\"button\"\n title=\"Audio Call\"\n >\n <span class=\"material-icons-outlined\">call</span>\n <span>Audio</span>\n </button>\n <button class=\"rts-call-btn rts-call-btn-video\"\n (click)=\"videoCall()\"\n type=\"button\"\n title=\"Video Call\"\n >\n <span class=\"material-icons-outlined\">videocam</span>\n <span>Video</span>\n </button>\n </div>\n }\n\n @if (!isOtherUser && messenger.myPeer && !messenger.myPeer.isAnonymous) {\n <div class=\"rts-chat-info-actions\">\n <button class=\"rts-btn rts-btn-ghost rts-btn-block\"\n (click)=\"messenger.openManageProfile()\"\n >\n <span class=\"material-icons-outlined\">edit</span>\n {{ messenger.labels[RTSeeTextLabelKeys.MESSENGER_EDIT_PROFILE_BTN_LABEL] || 'Edit Profile' }}\n </button>\n </div>\n\n <hr class=\"rts-divider\" />\n\n <div class=\"rts-profile-section\">\n <div class=\"rts-profile-field-row\">\n <label class=\"rts-profile-field-label\">Language</label>\n <p-select [options]=\"messenger.languageOptions\"\n [(ngModel)]=\"messenger.currentLanguage\"\n (ngModelChange)=\"setLanguage()\"\n optionLabel=\"key\"\n optionValue=\"key\"\n [placeholder]=\"messenger.labels[RTSeeTextLabelKeys.MESSANGER_LANGUAGE_PLACEHOLDER]\"\n class=\"rts-profile-language-select\"\n />\n </div>\n </div>\n\n @if (messenger.myPeer.plan) {\n <hr class=\"rts-divider\" />\n <div class=\"rts-profile-section\">\n <div class=\"rts-profile-plan-badge\">\n <span class=\"material-icons-outlined\">verified</span>\n <span>{{ messenger.myPeer.plan.productName }}</span>\n </div>\n <button class=\"rts-btn rts-btn-ghost rts-btn-block\"\n (click)=\"messenger.openManagePlanPage()\"\n >\n {{ messenger.labels[RTSeeTextLabelKeys.MANAGE_PLAN] || 'Manage Plan' }}\n </button>\n </div>\n } @else {\n <hr class=\"rts-divider\" />\n <div class=\"rts-profile-section\">\n <button class=\"rts-btn rts-btn-primary rts-btn-block\"\n (click)=\"messenger.openPaymentPage()\"\n >\n <span class=\"material-icons-outlined\">stars</span>\n {{ messenger.labels[RTSeeTextLabelKeys.SUBSCRIBE_LABEL] || 'Upgrade Plan' }}\n </button>\n </div>\n }\n\n <hr class=\"rts-divider\" />\n <div class=\"rts-chat-info-danger-zone\">\n <button class=\"rts-btn rts-btn-danger rts-btn-block\"\n (click)=\"logOut()\"\n >\n {{ messenger.labels[RTSeeTextLabelKeys.LOGOUT_BUTTON] || 'Logout' }}\n </button>\n </div>\n }\n\n @if (!isOtherUser && messenger.myPeer?.isAnonymous) {\n <hr class=\"rts-divider\" />\n <div class=\"rts-profile-anon-box\">\n <div class=\"rts-profile-anon-icon\">\n <span class=\"material-icons-outlined\">shield</span>\n </div>\n <div class=\"rts-profile-anon-text\">\n @if (!messenger.currentLanguage || messenger.currentLanguage === 'eng') {\n <p>This is a temporary anonymous identity. Sign up or sign in to save your history permanently.</p>\n } @else if (messenger.currentLanguage === 'ua') {\n <p>\u0426\u0435 \u0442\u0438\u043C\u0447\u0430\u0441\u043E\u0432\u0430 \u0430\u043D\u043E\u043D\u0456\u043C\u043D\u0430 \u043E\u0441\u043E\u0431\u0438\u0441\u0442\u0456\u0441\u0442\u044C. \u0417\u0430\u0440\u0435\u0454\u0441\u0442\u0440\u0443\u0439\u0442\u0435\u0441\u044F, \u0430\u0431\u0438 \u0437\u0431\u0435\u0440\u0435\u0433\u0442\u0438 \u0456\u0441\u0442\u043E\u0440\u0456\u044E \u0441\u043F\u0456\u043B\u043A\u0443\u0432\u0430\u043D\u043D\u044F.</p>\n }\n </div>\n </div>\n\n <div class=\"rts-profile-auth-actions\">\n <button class=\"rts-btn rts-btn-primary rts-btn-block\"\n [routerLink]=\"authConfiguration.getRoutePathSplit(AuthEndpointsKeys.SIGN_UP)\"\n >\n {{ messenger.labels[RTSeeTextLabelKeys.SIGN_UP_BUTTON] || 'Sign Up' }}\n </button>\n <button class=\"rts-btn rts-btn-ghost rts-btn-block\"\n [routerLink]=\"authConfiguration.getRoutePathSplit(AuthEndpointsKeys.SIGN_IN)\"\n >\n {{ messenger.labels[RTSeeTextLabelKeys.SIGN_IN_BUTTON] || 'Sign In' }}\n </button>\n </div>\n }\n</div>\n" }]
316
375
  }], ctorParameters: () => [{ type: RTSeeAuthService }, { type: i2.Router }], propDecorators: { messenger: [{
317
376
  type: Input
318
377
  }], peer: [{
@@ -1356,15 +1415,27 @@ class ChatInfoComponent {
1356
1415
  onEditChatInfo() {
1357
1416
  this.editChatInfo.emit();
1358
1417
  }
1418
+ call() {
1419
+ if (!this.chatInfo?.chat) {
1420
+ return;
1421
+ }
1422
+ void this.messenger.initializeVideoCall(this.chatInfo.chat);
1423
+ }
1424
+ videoCall() {
1425
+ if (!this.chatInfo?.chat) {
1426
+ return;
1427
+ }
1428
+ void this.messenger.initializeVideoCall(this.chatInfo.chat);
1429
+ }
1359
1430
  async onDeleteChat() {
1360
1431
  await this.chatInfo.deleteChat();
1361
1432
  }
1362
1433
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.4", ngImport: i0, type: ChatInfoComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
1363
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.4", type: ChatInfoComponent, isStandalone: true, selector: "rtsee-chat-info", inputs: { messenger: "messenger", chatInfo: "chatInfo" }, outputs: { editChatInfo: "editChatInfo", chatDeleted: "chatDeleted" }, ngImport: i0, template: "<div class=\"rts-chat-info\">\n <div class=\"rts-chat-info-meta\">\n <p class=\"rts-chat-info-type\">{{ chatInfo.chat.type }}</p>\n <p class=\"rts-chat-info-member-count\">{{ chatInfo.chat.members.length }} members</p>\n </div>\n\n @if (chatInfo.canManageChatInfo) {\n <div class=\"rts-chat-info-actions\">\n <button class=\"rts-btn rts-btn-ghost rts-btn-block\"\n (click)=\"onEditChatInfo()\"\n >\n <span class=\"material-icons-outlined\">edit</span>\n Edit Chat Info\n </button>\n </div>\n }\n\n <hr class=\"rts-divider\" />\n\n <rtsee-chat-members\n [chatInfo]=\"chatInfo\"\n [messenger]=\"messenger\"\n ></rtsee-chat-members>\n\n @if (chatInfo.myRole === RTSeeChatMemberRoles.ADMIN) {\n <hr class=\"rts-divider\" />\n <div class=\"rts-chat-info-danger-zone\">\n <button class=\"rts-btn rts-btn-danger rts-btn-block\"\n (click)=\"onDeleteChat()\"\n >\n Delete Chat\n </button>\n </div>\n }\n</div>\n", dependencies: [{ kind: "component", type: ChatMembersComponent, selector: "rtsee-chat-members", inputs: ["chatInfo", "messenger"] }] }); }
1434
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.4", type: ChatInfoComponent, isStandalone: true, selector: "rtsee-chat-info", inputs: { messenger: "messenger", chatInfo: "chatInfo" }, outputs: { editChatInfo: "editChatInfo", chatDeleted: "chatDeleted" }, ngImport: i0, template: "<div class=\"rts-chat-info\">\n <div class=\"rts-chat-info-meta\">\n <p class=\"rts-chat-info-type\">{{ chatInfo.chat.type }}</p>\n <p class=\"rts-chat-info-member-count\">{{ chatInfo.chat.members.length }} members</p>\n </div>\n\n <div class=\"rts-chat-info-call-actions\">\n <button class=\"rts-call-btn\"\n (click)=\"call()\"\n type=\"button\"\n title=\"Audio Call\"\n >\n <span class=\"material-icons-outlined\">call</span>\n <span>Audio</span>\n </button>\n <button class=\"rts-call-btn rts-call-btn-video\"\n (click)=\"videoCall()\"\n type=\"button\"\n title=\"Video Call\"\n >\n <span class=\"material-icons-outlined\">videocam</span>\n <span>Video</span>\n </button>\n </div>\n\n @if (chatInfo.canManageChatInfo) {\n <div class=\"rts-chat-info-actions\">\n <button class=\"rts-btn rts-btn-ghost rts-btn-block\"\n (click)=\"onEditChatInfo()\"\n >\n <span class=\"material-icons-outlined\">edit</span>\n Edit Chat Info\n </button>\n </div>\n }\n\n <hr class=\"rts-divider\" />\n\n <rtsee-chat-members\n [chatInfo]=\"chatInfo\"\n [messenger]=\"messenger\"\n ></rtsee-chat-members>\n\n @if (chatInfo.myRole === RTSeeChatMemberRoles.ADMIN) {\n <hr class=\"rts-divider\" />\n <div class=\"rts-chat-info-danger-zone\">\n <button class=\"rts-btn rts-btn-danger rts-btn-block\"\n (click)=\"onDeleteChat()\"\n >\n Delete Chat\n </button>\n </div>\n }\n</div>\n", dependencies: [{ kind: "component", type: ChatMembersComponent, selector: "rtsee-chat-members", inputs: ["chatInfo", "messenger"] }] }); }
1364
1435
  }
1365
1436
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.4", ngImport: i0, type: ChatInfoComponent, decorators: [{
1366
1437
  type: Component,
1367
- args: [{ selector: 'rtsee-chat-info', standalone: true, imports: [PreloaderComponent, ChatMembersComponent], template: "<div class=\"rts-chat-info\">\n <div class=\"rts-chat-info-meta\">\n <p class=\"rts-chat-info-type\">{{ chatInfo.chat.type }}</p>\n <p class=\"rts-chat-info-member-count\">{{ chatInfo.chat.members.length }} members</p>\n </div>\n\n @if (chatInfo.canManageChatInfo) {\n <div class=\"rts-chat-info-actions\">\n <button class=\"rts-btn rts-btn-ghost rts-btn-block\"\n (click)=\"onEditChatInfo()\"\n >\n <span class=\"material-icons-outlined\">edit</span>\n Edit Chat Info\n </button>\n </div>\n }\n\n <hr class=\"rts-divider\" />\n\n <rtsee-chat-members\n [chatInfo]=\"chatInfo\"\n [messenger]=\"messenger\"\n ></rtsee-chat-members>\n\n @if (chatInfo.myRole === RTSeeChatMemberRoles.ADMIN) {\n <hr class=\"rts-divider\" />\n <div class=\"rts-chat-info-danger-zone\">\n <button class=\"rts-btn rts-btn-danger rts-btn-block\"\n (click)=\"onDeleteChat()\"\n >\n Delete Chat\n </button>\n </div>\n }\n</div>\n" }]
1438
+ args: [{ selector: 'rtsee-chat-info', standalone: true, imports: [PreloaderComponent, ChatMembersComponent], template: "<div class=\"rts-chat-info\">\n <div class=\"rts-chat-info-meta\">\n <p class=\"rts-chat-info-type\">{{ chatInfo.chat.type }}</p>\n <p class=\"rts-chat-info-member-count\">{{ chatInfo.chat.members.length }} members</p>\n </div>\n\n <div class=\"rts-chat-info-call-actions\">\n <button class=\"rts-call-btn\"\n (click)=\"call()\"\n type=\"button\"\n title=\"Audio Call\"\n >\n <span class=\"material-icons-outlined\">call</span>\n <span>Audio</span>\n </button>\n <button class=\"rts-call-btn rts-call-btn-video\"\n (click)=\"videoCall()\"\n type=\"button\"\n title=\"Video Call\"\n >\n <span class=\"material-icons-outlined\">videocam</span>\n <span>Video</span>\n </button>\n </div>\n\n @if (chatInfo.canManageChatInfo) {\n <div class=\"rts-chat-info-actions\">\n <button class=\"rts-btn rts-btn-ghost rts-btn-block\"\n (click)=\"onEditChatInfo()\"\n >\n <span class=\"material-icons-outlined\">edit</span>\n Edit Chat Info\n </button>\n </div>\n }\n\n <hr class=\"rts-divider\" />\n\n <rtsee-chat-members\n [chatInfo]=\"chatInfo\"\n [messenger]=\"messenger\"\n ></rtsee-chat-members>\n\n @if (chatInfo.myRole === RTSeeChatMemberRoles.ADMIN) {\n <hr class=\"rts-divider\" />\n <div class=\"rts-chat-info-danger-zone\">\n <button class=\"rts-btn rts-btn-danger rts-btn-block\"\n (click)=\"onDeleteChat()\"\n >\n Delete Chat\n </button>\n </div>\n }\n</div>\n" }]
1368
1439
  }], propDecorators: { messenger: [{
1369
1440
  type: Input
1370
1441
  }], chatInfo: [{
@@ -1517,13 +1588,19 @@ class MessengerHeaderComponent {
1517
1588
  constructor(defaultImagesService) {
1518
1589
  this.defaultImagesService = defaultImagesService;
1519
1590
  this.isDropdownOpen = false;
1520
- this.chatImageExpanded = false;
1591
+ this.imageExpandedToSquare = false;
1592
+ this.lastSquareToggleTime = 0;
1521
1593
  this.RTSeeTextLabelKeys = RTSeeTextLabelKeys;
1522
1594
  }
1523
1595
  openProfile(id) {
1524
1596
  if (this.messenger.ownProfileOpened) {
1525
1597
  return;
1526
1598
  }
1599
+ this.imageExpandedToSquare = false;
1600
+ if (this.messenger.myPeer && id === this.messenger.myPeer.clientId) {
1601
+ this.messenger.ownProfileOpened = true;
1602
+ this.messenger.openedProfile = this.messenger.myPeer;
1603
+ }
1527
1604
  void this.messenger.openProfile(id);
1528
1605
  }
1529
1606
  toggleDropdown() {
@@ -1542,35 +1619,85 @@ class MessengerHeaderComponent {
1542
1619
  onChatSaved(chat) {
1543
1620
  this.messenger.closeManageChatInfo();
1544
1621
  }
1622
+ onMyAvatarClick() {
1623
+ if (!this.messenger.ownProfileOpened) {
1624
+ if (this.messenger.myPeer) {
1625
+ this.openProfile(this.messenger.myPeer.clientId);
1626
+ }
1627
+ }
1628
+ else if (this.imageExpandedToSquare) {
1629
+ if (Date.now() - this.lastSquareToggleTime > 300) {
1630
+ this.imageExpandedToSquare = false;
1631
+ this.lastSquareToggleTime = Date.now();
1632
+ }
1633
+ }
1634
+ }
1635
+ onMyAvatarDblClick() {
1636
+ if (this.messenger.ownProfileOpened) {
1637
+ if (Date.now() - this.lastSquareToggleTime > 300) {
1638
+ this.imageExpandedToSquare = !this.imageExpandedToSquare;
1639
+ this.lastSquareToggleTime = Date.now();
1640
+ }
1641
+ }
1642
+ }
1545
1643
  onChatImageClick() {
1546
- if (!this.messenger.openedChatDetails) {
1547
- // First click: open chat info (circle image)
1548
- this.chatImageExpanded = false;
1644
+ const isExpanded = !!(this.messenger.openedProfile || this.messenger.openedChatDetails);
1645
+ if (!isExpanded) {
1646
+ // First click: open chat info (enters State 2: centered circle)
1647
+ this.imageExpandedToSquare = false;
1549
1648
  this.messenger.openChatInfo();
1550
1649
  }
1551
- else if (!this.chatImageExpanded) {
1552
- // Second click: expand image to full width square
1553
- this.chatImageExpanded = true;
1650
+ else if (this.imageExpandedToSquare) {
1651
+ // In square state: single click collapses back to circle
1652
+ if (Date.now() - this.lastSquareToggleTime > 300) {
1653
+ this.imageExpandedToSquare = false;
1654
+ this.lastSquareToggleTime = Date.now();
1655
+ }
1554
1656
  }
1555
- else {
1556
- // Third click: collapse back to circle
1557
- this.chatImageExpanded = false;
1657
+ }
1658
+ onChatImageDblClick() {
1659
+ const isExpanded = !!(this.messenger.openedProfile || this.messenger.openedChatDetails);
1660
+ if (isExpanded) {
1661
+ if (Date.now() - this.lastSquareToggleTime > 300) {
1662
+ this.imageExpandedToSquare = !this.imageExpandedToSquare;
1663
+ this.lastSquareToggleTime = Date.now();
1664
+ }
1558
1665
  }
1559
1666
  }
1560
1667
  collapseInfo() {
1561
- this.chatImageExpanded = false;
1668
+ this.imageExpandedToSquare = false;
1562
1669
  this.messenger.collapseInfo();
1563
1670
  }
1564
1671
  get isGroupOrChannelChat() {
1565
1672
  return this.messenger.openedChat?.type === RTSeeChatTypes.GROUP ||
1566
1673
  this.messenger.openedChat?.type === RTSeeChatTypes.CHANNEL;
1567
1674
  }
1675
+ get headerTitle() {
1676
+ if (this.messenger.ownProfileOpened) {
1677
+ return this.messenger.myPeer?.name || 'My Profile';
1678
+ }
1679
+ if (this.messenger.openedProfile?.name) {
1680
+ return this.messenger.openedProfile.name;
1681
+ }
1682
+ if (this.messenger.openedChat?.name) {
1683
+ return this.messenger.openedChat.name;
1684
+ }
1685
+ return '';
1686
+ }
1687
+ get myProfileImageUrl() {
1688
+ return this.messenger?.myPeer?.imageUrl || this.defaultImagesService.PROFILE;
1689
+ }
1690
+ get chatImageUrl() {
1691
+ return this.messenger?.openedProfile?.imageUrl ||
1692
+ this.messenger?.openedChat?.imageUrl ||
1693
+ this.defaultImagesService.PROFILE;
1694
+ }
1568
1695
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.4", ngImport: i0, type: MessengerHeaderComponent, deps: [{ token: DefaultImagesService }], target: i0.ɵɵFactoryTarget.Component }); }
1569
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.4", type: MessengerHeaderComponent, isStandalone: true, selector: "rtsee-messenger-header", inputs: { messenger: "messenger" }, ngImport: i0, template: "<div class=\"rts-messenger-header\"\n [ngClass]=\"{\n 'rts-messenger-header-chat-opened': messenger.openedChat && !messenger.manageChatInfo,\n 'rts-chat-header-unread': messenger.openedChat && !messenger.unreadMessagesCount,\n 'rts-messenger-header-expanded': messenger.openedProfile || messenger.openedChatDetails || messenger.manageChatInfo,\n 'rts-messenger-header-create-chat-mode': messenger.manageChatInfo,\n 'rts-messenger-header-my-profile-mode': messenger.ownProfileOpened,\n 'rts-messenger-header-chat-info-mode': (messenger.openedProfile || messenger.openedChatDetails) && !messenger.ownProfileOpened && !messenger.manageChatInfo,\n 'rts-messenger-header-chat-image-expanded': chatImageExpanded\n }\"\n>\n <span class=\"rts-messenger-header-collapse-button\"\n (click)=\"messenger.manageChatInfo ? closeManageChatDialog() : collapseInfo()\"\n >\n <span class=\"pi pi-arrow-left\"></span>\n </span>\n <div class=\"rts-messenger-header-left\">\n @if (!messenger.openedChat && messenger.myPeer) {\n <div class=\"rts-messenger-header-avatar\"\n (click)=\"openProfile(messenger.myPeer.clientId)\"\n >\n <div [style.background-image]=\"'url(' + messenger.myPeer.imageUrl || defaultImagesService.PROFILE + ')'\"\n class=\"rts-messenger-header-avatar-image\">\n </div>\n </div>\n } @else if (messenger.openedChat) {\n <button class=\"rts-messenger-close-chat\"\n (click)=\"messenger.closeChat()\"\n >\n <span class=\"material-icons-outlined\">arrow_back</span>\n @if (messenger.unreadMessagesCount) {\n <span class=\"unread-count\">{{messenger.unreadMessagesCount}}</span>\n }\n </button>\n }\n </div>\n <div class=\"rts-messenger-header-right\">\n @if (messenger.openedChat) {\n <div class=\"rts-messenger-chat-image-container rts-messenger-header-avatar\"\n (click)=\"onChatImageClick()\"\n >\n <div class=\"rts-messenger-chat-image rts-messenger-header-avatar-image\"\n [style.background-image]=\"'url(' + messenger.openedChat.imageUrl || defaultImagesService.PROFILE + ')'\"\n ></div>\n </div>\n } @else {\n <div class=\"rts-messenger-header-actions\">\n <button class=\"rts-messenger-header-add-button\"\n (click)=\"toggleDropdown()\"\n >\n <span class=\"material-icons-outlined\">add</span>\n </button>\n\n @if (isDropdownOpen) {\n <div class=\"rts-messenger-header-dropdown-backdrop\"\n (click)=\"closeDropdown()\"\n ></div>\n <div class=\"rts-messenger-header-dropdown\">\n <button class=\"rts-messenger-header-dropdown-item\"\n (click)=\"openNewChatDialog()\"\n >\n <span class=\"material-icons-outlined rts-messenger-header-dropdown-icon\">chat_bubble_outline</span>\n <span>New Chat</span>\n </button>\n </div>\n }\n </div>\n }\n </div>\n <div class=\"rts-messenger-header-center\">\n @if (messenger.manageChatInfo) {\n <div class=\"rts-messenger-header-create-chat-container\">\n <rtsee-manage-chat-info [messenger]=\"messenger\"\n [chat]=\"messenger.manageChatInfo.chat || undefined\"\n (chatSaved)=\"onChatSaved($event)\"\n (cancelled)=\"closeManageChatDialog()\"\n ></rtsee-manage-chat-info>\n </div>\n } @else if (messenger.openedChat) {\n <div [ngClass]=\"{'rts-messenger-header-opened-chat-info-expanded': messenger.openedProfile || messenger.openedChatDetails}\"\n class=\"rts-messenger-header-opened-chat-info\">\n <div class=\"rts-messenger-profile-header-placeholder\">\n <p class=\"rts-messenger-header-chat-title\"\n >{{ messenger.openedChat.name }}</p>\n </div>\n\n <div class=\"rts-messenger-profile-data-container\">\n @if (messenger.openedChatDetails && isGroupOrChannelChat && messenger.chatInfo) {\n <rtsee-chat-info [messenger]=\"messenger\"\n [chatInfo]=\"messenger.chatInfo\"\n (editChatInfo)=\"messenger.openManageChatInfo(messenger.openedChat)\"\n (chatDeleted)=\"messenger.closeChatInfo()\"\n ></rtsee-chat-info>\n } @else if (messenger.openedChatDetails) {\n <rtsee-profile [messenger]=\"messenger\"\n ></rtsee-profile>\n }\n </div>\n </div>\n } @else {\n <p class=\"rts-messenger-header-title\"\n >{{messenger.labels[RTSeeTextLabelKeys.MESSANGER_TITLE]}}</p>\n <rtsee-profile [messenger]=\"messenger\"\n [peer]=\"messenger.myPeer\"\n ></rtsee-profile>\n }\n </div>\n</div>\n", dependencies: [{ kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "component", type: ProfileComponent, selector: "rtsee-profile", inputs: ["messenger", "peer"] }, { kind: "component", type: ChatInfoComponent, selector: "rtsee-chat-info", inputs: ["messenger", "chatInfo"], outputs: ["editChatInfo", "chatDeleted"] }, { kind: "component", type: ManageChatInfoComponent, selector: "rtsee-manage-chat-info", inputs: ["messenger", "chat"], outputs: ["chatSaved", "cancelled"] }] }); }
1696
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.4", type: MessengerHeaderComponent, isStandalone: true, selector: "rtsee-messenger-header", inputs: { messenger: "messenger" }, ngImport: i0, template: "<div class=\"rts-messenger-header\"\n [ngClass]=\"{\n 'rts-messenger-header-chat-opened': messenger.openedChat && !messenger.manageChatInfo,\n 'rts-chat-header-unread': messenger.openedChat && !messenger.unreadMessagesCount,\n 'rts-messenger-header-expanded': messenger.openedProfile || messenger.openedChatDetails || messenger.manageChatInfo,\n 'rts-messenger-header-create-chat-mode': messenger.manageChatInfo,\n 'rts-messenger-header-my-profile-mode': messenger.ownProfileOpened,\n 'rts-messenger-header-chat-info-mode': (messenger.openedProfile || messenger.openedChatDetails) && !messenger.ownProfileOpened && !messenger.manageChatInfo,\n 'rts-messenger-header-chat-image-expanded': imageExpandedToSquare,\n 'rts-messenger-header-image-square': imageExpandedToSquare\n }\"\n>\n <span class=\"rts-messenger-header-collapse-button\"\n (click)=\"messenger.manageChatInfo ? closeManageChatDialog() : collapseInfo()\"\n >\n <span class=\"pi pi-arrow-left\"></span>\n </span>\n <div class=\"rts-messenger-header-left\">\n @if (!messenger.openedChat && messenger.myPeer) {\n <div class=\"rts-messenger-header-avatar\"\n (click)=\"onMyAvatarClick()\"\n (dblclick)=\"onMyAvatarDblClick()\"\n >\n <div [style.background-image]=\"'url(' + myProfileImageUrl + ')'\"\n class=\"rts-messenger-header-avatar-image\">\n </div>\n </div>\n } @else if (messenger.openedChat) {\n <button class=\"rts-messenger-close-chat\"\n (click)=\"messenger.closeChat()\"\n >\n <span class=\"material-icons-outlined\">arrow_back</span>\n @if (messenger.unreadMessagesCount) {\n <span class=\"unread-count\">{{messenger.unreadMessagesCount}}</span>\n }\n </button>\n }\n </div>\n <div class=\"rts-messenger-header-right\">\n @if (messenger.openedChat) {\n <div class=\"rts-messenger-chat-image-container rts-messenger-header-avatar\"\n (click)=\"onChatImageClick()\"\n (dblclick)=\"onChatImageDblClick()\"\n >\n <div class=\"rts-messenger-chat-image rts-messenger-header-avatar-image\"\n [style.background-image]=\"'url(' + chatImageUrl + ')'\"\n ></div>\n </div>\n } @else {\n <div class=\"rts-messenger-header-actions\">\n <button class=\"rts-messenger-header-add-button\"\n (click)=\"toggleDropdown()\"\n >\n <span class=\"material-icons-outlined\">add</span>\n </button>\n\n @if (isDropdownOpen) {\n <div class=\"rts-messenger-header-dropdown-backdrop\"\n (click)=\"closeDropdown()\"\n ></div>\n <div class=\"rts-messenger-header-dropdown\">\n <button class=\"rts-messenger-header-dropdown-item\"\n (click)=\"openNewChatDialog()\"\n >\n <span class=\"material-icons-outlined rts-messenger-header-dropdown-icon\">chat_bubble_outline</span>\n <span>New Chat</span>\n </button>\n </div>\n }\n </div>\n }\n </div>\n <div class=\"rts-messenger-header-center\">\n @if (messenger.manageChatInfo) {\n <div class=\"rts-messenger-header-create-chat-container\">\n <rtsee-manage-chat-info [messenger]=\"messenger\"\n [chat]=\"messenger.manageChatInfo.chat || undefined\"\n (chatSaved)=\"onChatSaved($event)\"\n (cancelled)=\"closeManageChatDialog()\"\n ></rtsee-manage-chat-info>\n </div>\n } @else if (messenger.openedChat) {\n <div [ngClass]=\"{'rts-messenger-header-opened-chat-info-expanded': messenger.openedProfile || messenger.openedChatDetails}\"\n class=\"rts-messenger-header-opened-chat-info\">\n <div class=\"rts-messenger-profile-header-placeholder\">\n <p class=\"rts-messenger-header-chat-title\"\n >{{ headerTitle }}</p>\n </div>\n\n <div class=\"rts-messenger-profile-data-container\">\n @if (messenger.openedChatDetails && isGroupOrChannelChat && messenger.chatInfo) {\n <rtsee-chat-info [messenger]=\"messenger\"\n [chatInfo]=\"messenger.chatInfo\"\n (editChatInfo)=\"messenger.openManageChatInfo(messenger.openedChat)\"\n (chatDeleted)=\"messenger.closeChatInfo()\"\n ></rtsee-chat-info>\n } @else if (messenger.openedChatDetails || messenger.openedProfile) {\n <rtsee-profile [messenger]=\"messenger\"\n ></rtsee-profile>\n }\n </div>\n </div>\n } @else {\n @if (messenger.ownProfileOpened) {\n <div class=\"rts-messenger-header-opened-chat-info rts-messenger-header-opened-chat-info-expanded\">\n <div class=\"rts-messenger-profile-header-placeholder\">\n <p class=\"rts-messenger-header-chat-title\">{{ headerTitle }}</p>\n </div>\n <div class=\"rts-messenger-profile-data-container\">\n <rtsee-profile [messenger]=\"messenger\"\n [peer]=\"messenger.myPeer\"\n ></rtsee-profile>\n </div>\n </div>\n } @else {\n <p class=\"rts-messenger-header-title\"\n >{{messenger.labels[RTSeeTextLabelKeys.MESSANGER_TITLE]}}</p>\n <rtsee-profile [messenger]=\"messenger\"\n [peer]=\"messenger.myPeer\"\n ></rtsee-profile>\n }\n }\n </div>\n</div>\n", dependencies: [{ kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "component", type: ProfileComponent, selector: "rtsee-profile", inputs: ["messenger", "peer"] }, { kind: "component", type: ChatInfoComponent, selector: "rtsee-chat-info", inputs: ["messenger", "chatInfo"], outputs: ["editChatInfo", "chatDeleted"] }, { kind: "component", type: ManageChatInfoComponent, selector: "rtsee-manage-chat-info", inputs: ["messenger", "chat"], outputs: ["chatSaved", "cancelled"] }] }); }
1570
1697
  }
1571
1698
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.4", ngImport: i0, type: MessengerHeaderComponent, decorators: [{
1572
1699
  type: Component,
1573
- args: [{ selector: 'rtsee-messenger-header', imports: [NgClass, ProfileComponent, ChatInfoComponent, ManageChatInfoComponent], standalone: true, template: "<div class=\"rts-messenger-header\"\n [ngClass]=\"{\n 'rts-messenger-header-chat-opened': messenger.openedChat && !messenger.manageChatInfo,\n 'rts-chat-header-unread': messenger.openedChat && !messenger.unreadMessagesCount,\n 'rts-messenger-header-expanded': messenger.openedProfile || messenger.openedChatDetails || messenger.manageChatInfo,\n 'rts-messenger-header-create-chat-mode': messenger.manageChatInfo,\n 'rts-messenger-header-my-profile-mode': messenger.ownProfileOpened,\n 'rts-messenger-header-chat-info-mode': (messenger.openedProfile || messenger.openedChatDetails) && !messenger.ownProfileOpened && !messenger.manageChatInfo,\n 'rts-messenger-header-chat-image-expanded': chatImageExpanded\n }\"\n>\n <span class=\"rts-messenger-header-collapse-button\"\n (click)=\"messenger.manageChatInfo ? closeManageChatDialog() : collapseInfo()\"\n >\n <span class=\"pi pi-arrow-left\"></span>\n </span>\n <div class=\"rts-messenger-header-left\">\n @if (!messenger.openedChat && messenger.myPeer) {\n <div class=\"rts-messenger-header-avatar\"\n (click)=\"openProfile(messenger.myPeer.clientId)\"\n >\n <div [style.background-image]=\"'url(' + messenger.myPeer.imageUrl || defaultImagesService.PROFILE + ')'\"\n class=\"rts-messenger-header-avatar-image\">\n </div>\n </div>\n } @else if (messenger.openedChat) {\n <button class=\"rts-messenger-close-chat\"\n (click)=\"messenger.closeChat()\"\n >\n <span class=\"material-icons-outlined\">arrow_back</span>\n @if (messenger.unreadMessagesCount) {\n <span class=\"unread-count\">{{messenger.unreadMessagesCount}}</span>\n }\n </button>\n }\n </div>\n <div class=\"rts-messenger-header-right\">\n @if (messenger.openedChat) {\n <div class=\"rts-messenger-chat-image-container rts-messenger-header-avatar\"\n (click)=\"onChatImageClick()\"\n >\n <div class=\"rts-messenger-chat-image rts-messenger-header-avatar-image\"\n [style.background-image]=\"'url(' + messenger.openedChat.imageUrl || defaultImagesService.PROFILE + ')'\"\n ></div>\n </div>\n } @else {\n <div class=\"rts-messenger-header-actions\">\n <button class=\"rts-messenger-header-add-button\"\n (click)=\"toggleDropdown()\"\n >\n <span class=\"material-icons-outlined\">add</span>\n </button>\n\n @if (isDropdownOpen) {\n <div class=\"rts-messenger-header-dropdown-backdrop\"\n (click)=\"closeDropdown()\"\n ></div>\n <div class=\"rts-messenger-header-dropdown\">\n <button class=\"rts-messenger-header-dropdown-item\"\n (click)=\"openNewChatDialog()\"\n >\n <span class=\"material-icons-outlined rts-messenger-header-dropdown-icon\">chat_bubble_outline</span>\n <span>New Chat</span>\n </button>\n </div>\n }\n </div>\n }\n </div>\n <div class=\"rts-messenger-header-center\">\n @if (messenger.manageChatInfo) {\n <div class=\"rts-messenger-header-create-chat-container\">\n <rtsee-manage-chat-info [messenger]=\"messenger\"\n [chat]=\"messenger.manageChatInfo.chat || undefined\"\n (chatSaved)=\"onChatSaved($event)\"\n (cancelled)=\"closeManageChatDialog()\"\n ></rtsee-manage-chat-info>\n </div>\n } @else if (messenger.openedChat) {\n <div [ngClass]=\"{'rts-messenger-header-opened-chat-info-expanded': messenger.openedProfile || messenger.openedChatDetails}\"\n class=\"rts-messenger-header-opened-chat-info\">\n <div class=\"rts-messenger-profile-header-placeholder\">\n <p class=\"rts-messenger-header-chat-title\"\n >{{ messenger.openedChat.name }}</p>\n </div>\n\n <div class=\"rts-messenger-profile-data-container\">\n @if (messenger.openedChatDetails && isGroupOrChannelChat && messenger.chatInfo) {\n <rtsee-chat-info [messenger]=\"messenger\"\n [chatInfo]=\"messenger.chatInfo\"\n (editChatInfo)=\"messenger.openManageChatInfo(messenger.openedChat)\"\n (chatDeleted)=\"messenger.closeChatInfo()\"\n ></rtsee-chat-info>\n } @else if (messenger.openedChatDetails) {\n <rtsee-profile [messenger]=\"messenger\"\n ></rtsee-profile>\n }\n </div>\n </div>\n } @else {\n <p class=\"rts-messenger-header-title\"\n >{{messenger.labels[RTSeeTextLabelKeys.MESSANGER_TITLE]}}</p>\n <rtsee-profile [messenger]=\"messenger\"\n [peer]=\"messenger.myPeer\"\n ></rtsee-profile>\n }\n </div>\n</div>\n" }]
1700
+ args: [{ selector: 'rtsee-messenger-header', imports: [NgClass, ProfileComponent, ChatInfoComponent, ManageChatInfoComponent], standalone: true, template: "<div class=\"rts-messenger-header\"\n [ngClass]=\"{\n 'rts-messenger-header-chat-opened': messenger.openedChat && !messenger.manageChatInfo,\n 'rts-chat-header-unread': messenger.openedChat && !messenger.unreadMessagesCount,\n 'rts-messenger-header-expanded': messenger.openedProfile || messenger.openedChatDetails || messenger.manageChatInfo,\n 'rts-messenger-header-create-chat-mode': messenger.manageChatInfo,\n 'rts-messenger-header-my-profile-mode': messenger.ownProfileOpened,\n 'rts-messenger-header-chat-info-mode': (messenger.openedProfile || messenger.openedChatDetails) && !messenger.ownProfileOpened && !messenger.manageChatInfo,\n 'rts-messenger-header-chat-image-expanded': imageExpandedToSquare,\n 'rts-messenger-header-image-square': imageExpandedToSquare\n }\"\n>\n <span class=\"rts-messenger-header-collapse-button\"\n (click)=\"messenger.manageChatInfo ? closeManageChatDialog() : collapseInfo()\"\n >\n <span class=\"pi pi-arrow-left\"></span>\n </span>\n <div class=\"rts-messenger-header-left\">\n @if (!messenger.openedChat && messenger.myPeer) {\n <div class=\"rts-messenger-header-avatar\"\n (click)=\"onMyAvatarClick()\"\n (dblclick)=\"onMyAvatarDblClick()\"\n >\n <div [style.background-image]=\"'url(' + myProfileImageUrl + ')'\"\n class=\"rts-messenger-header-avatar-image\">\n </div>\n </div>\n } @else if (messenger.openedChat) {\n <button class=\"rts-messenger-close-chat\"\n (click)=\"messenger.closeChat()\"\n >\n <span class=\"material-icons-outlined\">arrow_back</span>\n @if (messenger.unreadMessagesCount) {\n <span class=\"unread-count\">{{messenger.unreadMessagesCount}}</span>\n }\n </button>\n }\n </div>\n <div class=\"rts-messenger-header-right\">\n @if (messenger.openedChat) {\n <div class=\"rts-messenger-chat-image-container rts-messenger-header-avatar\"\n (click)=\"onChatImageClick()\"\n (dblclick)=\"onChatImageDblClick()\"\n >\n <div class=\"rts-messenger-chat-image rts-messenger-header-avatar-image\"\n [style.background-image]=\"'url(' + chatImageUrl + ')'\"\n ></div>\n </div>\n } @else {\n <div class=\"rts-messenger-header-actions\">\n <button class=\"rts-messenger-header-add-button\"\n (click)=\"toggleDropdown()\"\n >\n <span class=\"material-icons-outlined\">add</span>\n </button>\n\n @if (isDropdownOpen) {\n <div class=\"rts-messenger-header-dropdown-backdrop\"\n (click)=\"closeDropdown()\"\n ></div>\n <div class=\"rts-messenger-header-dropdown\">\n <button class=\"rts-messenger-header-dropdown-item\"\n (click)=\"openNewChatDialog()\"\n >\n <span class=\"material-icons-outlined rts-messenger-header-dropdown-icon\">chat_bubble_outline</span>\n <span>New Chat</span>\n </button>\n </div>\n }\n </div>\n }\n </div>\n <div class=\"rts-messenger-header-center\">\n @if (messenger.manageChatInfo) {\n <div class=\"rts-messenger-header-create-chat-container\">\n <rtsee-manage-chat-info [messenger]=\"messenger\"\n [chat]=\"messenger.manageChatInfo.chat || undefined\"\n (chatSaved)=\"onChatSaved($event)\"\n (cancelled)=\"closeManageChatDialog()\"\n ></rtsee-manage-chat-info>\n </div>\n } @else if (messenger.openedChat) {\n <div [ngClass]=\"{'rts-messenger-header-opened-chat-info-expanded': messenger.openedProfile || messenger.openedChatDetails}\"\n class=\"rts-messenger-header-opened-chat-info\">\n <div class=\"rts-messenger-profile-header-placeholder\">\n <p class=\"rts-messenger-header-chat-title\"\n >{{ headerTitle }}</p>\n </div>\n\n <div class=\"rts-messenger-profile-data-container\">\n @if (messenger.openedChatDetails && isGroupOrChannelChat && messenger.chatInfo) {\n <rtsee-chat-info [messenger]=\"messenger\"\n [chatInfo]=\"messenger.chatInfo\"\n (editChatInfo)=\"messenger.openManageChatInfo(messenger.openedChat)\"\n (chatDeleted)=\"messenger.closeChatInfo()\"\n ></rtsee-chat-info>\n } @else if (messenger.openedChatDetails || messenger.openedProfile) {\n <rtsee-profile [messenger]=\"messenger\"\n ></rtsee-profile>\n }\n </div>\n </div>\n } @else {\n @if (messenger.ownProfileOpened) {\n <div class=\"rts-messenger-header-opened-chat-info rts-messenger-header-opened-chat-info-expanded\">\n <div class=\"rts-messenger-profile-header-placeholder\">\n <p class=\"rts-messenger-header-chat-title\">{{ headerTitle }}</p>\n </div>\n <div class=\"rts-messenger-profile-data-container\">\n <rtsee-profile [messenger]=\"messenger\"\n [peer]=\"messenger.myPeer\"\n ></rtsee-profile>\n </div>\n </div>\n } @else {\n <p class=\"rts-messenger-header-title\"\n >{{messenger.labels[RTSeeTextLabelKeys.MESSANGER_TITLE]}}</p>\n <rtsee-profile [messenger]=\"messenger\"\n [peer]=\"messenger.myPeer\"\n ></rtsee-profile>\n }\n }\n </div>\n</div>\n" }]
1574
1701
  }], ctorParameters: () => [{ type: DefaultImagesService }], propDecorators: { messenger: [{
1575
1702
  type: Input
1576
1703
  }] } });
@@ -1819,26 +1946,109 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.4", ngImpor
1819
1946
 
1820
1947
  class CallWidgetComponent {
1821
1948
  ngOnInit() { }
1822
- joinCall() {
1823
- console.log('message data: ', this.message.data);
1824
- if (!this.messenger ||
1825
- (!this.message.data?.roomId && !this.chat.conferenceRoom)) {
1826
- console.log('No Room Id specified or no messenger!');
1827
- return;
1949
+ get roomId() {
1950
+ return this.message?.data?.roomId || this.chat?.conferenceRoom?.id || null;
1951
+ }
1952
+ get callerName() {
1953
+ return this.message?.from?.name || 'A participant';
1954
+ }
1955
+ get isUserInCall() {
1956
+ const videoChat = this.messenger?.videoChat;
1957
+ const currentRoomId = videoChat?.roomId;
1958
+ return !!(videoChat?.isInActiveCall &&
1959
+ currentRoomId &&
1960
+ currentRoomId === this.roomId);
1961
+ }
1962
+ get presentMembers() {
1963
+ const membersMap = new Map();
1964
+ // 1. Caller who started the call
1965
+ if (this.message?.from) {
1966
+ const from = this.message.from;
1967
+ const id = from.id || from.clientId;
1968
+ const name = from.name || 'Caller';
1969
+ if (id) {
1970
+ membersMap.set(id, {
1971
+ id,
1972
+ name,
1973
+ imageUrl: from.imageUrl,
1974
+ initials: this.getInitials(name),
1975
+ });
1976
+ }
1828
1977
  }
1829
- const roomId = this.message.data?.roomId || this.chat.conferenceRoom?.id || null;
1830
- if (!roomId) {
1831
- console.log('Room Id unavailable!');
1978
+ // 2. Members specified in call message data
1979
+ const callMembers = this.message?.data?.members;
1980
+ if (Array.isArray(callMembers)) {
1981
+ for (const m of callMembers) {
1982
+ const id = m.clientId || m.id;
1983
+ if (id && !membersMap.has(id)) {
1984
+ const name = m.name || id;
1985
+ membersMap.set(id, {
1986
+ id,
1987
+ name,
1988
+ imageUrl: m.imageUrl,
1989
+ initials: this.getInitials(name),
1990
+ });
1991
+ }
1992
+ }
1993
+ }
1994
+ // 3. Live connected peers from WebRTC conference if active
1995
+ const videoChat = this.messenger?.videoChat;
1996
+ if (videoChat) {
1997
+ // Add current user if in active call for this room
1998
+ if (videoChat.isInActiveCall && videoChat.roomId === this.roomId && this.messenger.myPeer) {
1999
+ const myId = this.messenger.myPeer.clientId;
2000
+ const myName = this.messenger.myPeer.name || 'You';
2001
+ if (myId && !membersMap.has(myId)) {
2002
+ membersMap.set(myId, {
2003
+ id: myId,
2004
+ name: myName,
2005
+ imageUrl: this.messenger.myPeer.imageUrl,
2006
+ initials: this.getInitials(myName),
2007
+ });
2008
+ }
2009
+ }
2010
+ // Add connected peers
2011
+ const conns = videoChat.rtcPeerConnections?.connections || [];
2012
+ for (const conn of conns) {
2013
+ const peerId = conn.clientId;
2014
+ if (peerId && !membersMap.has(peerId)) {
2015
+ const chatMember = this.chat?.getMemberById(peerId);
2016
+ const name = conn.name || chatMember?.name || peerId;
2017
+ const imageUrl = conn.imageUrl || chatMember?.imageUrl;
2018
+ membersMap.set(peerId, {
2019
+ id: peerId,
2020
+ name,
2021
+ imageUrl,
2022
+ initials: this.getInitials(name),
2023
+ });
2024
+ }
2025
+ }
2026
+ }
2027
+ return Array.from(membersMap.values());
2028
+ }
2029
+ getInitials(name) {
2030
+ const trimmed = name.trim();
2031
+ if (!trimmed)
2032
+ return '?';
2033
+ const parts = trimmed.split(' ');
2034
+ if (parts.length >= 2 && parts[1]) {
2035
+ return (parts[0][0] + parts[1][0]).toUpperCase();
2036
+ }
2037
+ return trimmed.slice(0, 2).toUpperCase();
2038
+ }
2039
+ joinCall() {
2040
+ if (!this.messenger || !this.roomId) {
2041
+ console.log('Room Id unavailable or no messenger!');
1832
2042
  return;
1833
2043
  }
1834
- void this.messenger.joinCall(roomId);
2044
+ void this.messenger.joinCall(this.roomId);
1835
2045
  }
1836
2046
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.4", ngImport: i0, type: CallWidgetComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
1837
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.4", type: CallWidgetComponent, isStandalone: true, selector: "ngx-call-widget", inputs: { message: "message", messenger: "messenger", chat: "chat" }, ngImport: i0, template: "<div class=\"rts-call-widget\">\n <p> The call has started</p>\n <button class=\"rts-join-call-btn\"\n (click)=\"joinCall()\">\n Join Call\n </button>\n</div>\n", styles: [""] }); }
2047
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.4", type: CallWidgetComponent, isStandalone: true, selector: "ngx-call-widget", inputs: { message: "message", messenger: "messenger", chat: "chat" }, ngImport: i0, template: "<div class=\"rts-call-widget\" [class.rts-call-widget-active]=\"isUserInCall\">\n <div class=\"rts-call-widget-header\">\n <div class=\"rts-call-widget-badge\">\n <span class=\"rts-call-live-dot\"></span>\n <span class=\"rts-call-badge-text\">{{ isUserInCall ? 'Connected' : 'Live Call' }}</span>\n </div>\n <span class=\"material-icons-outlined rts-call-widget-icon\">videocam</span>\n </div>\n\n <div class=\"rts-call-widget-content\">\n <p class=\"rts-call-widget-subtitle\">\n Started by <strong>{{ callerName }}</strong>\n </p>\n\n @if (presentMembers.length > 0) {\n <div class=\"rts-call-members-section\">\n <span class=\"rts-call-members-label\">In call ({{ presentMembers.length }}):</span>\n <div class=\"rts-call-avatar-stack\">\n @for (member of presentMembers; track member.id) {\n <div class=\"rts-call-avatar-item\" [title]=\"member.name\">\n @if (member.imageUrl) {\n <img [src]=\"member.imageUrl\" [alt]=\"member.name\" class=\"rts-call-avatar-img\" />\n } @else {\n <div class=\"rts-call-avatar-fallback\">\n {{ member.initials }}\n </div>\n }\n </div>\n }\n </div>\n </div>\n }\n </div>\n\n <div class=\"rts-call-widget-actions\">\n @if (isUserInCall) {\n <button class=\"rts-call-action-btn rts-call-btn-connected\" disabled type=\"button\">\n <span class=\"material-icons-outlined\">check_circle</span>\n <span>In Call</span>\n </button>\n } @else {\n <button class=\"rts-call-action-btn rts-join-call-btn\"\n (click)=\"joinCall()\"\n type=\"button\"\n >\n <span class=\"material-icons-outlined\">call</span>\n <span>Join Call</span>\n </button>\n }\n </div>\n</div>\n", styles: [".rts-theme-dark{--rts-bg-primary: #0e0e10;--rts-bg-surface: #1a1a1e;--rts-bg-surface-elevated: #242428;--rts-bg-surface-hover: rgba(255, 255, 255, .05);--rts-bg-gradient: linear-gradient(135deg, #1b0533 0%, #0e0e10 60%, #0a1628 100%);--rts-text-primary: #e5e5e7;--rts-text-secondary: #8e8e93;--rts-text-muted: #6a6a7a;--rts-accent: #6c5ce7;--rts-accent-light: #a29bfe;--rts-accent-blue: #3b82f7;--rts-accent-highlight: #bf8bff;--rts-accent-highlight-dim: rgba(191, 139, 255, .15);--rts-border-dim: rgba(255, 255, 255, .05);--rts-border-med: rgba(255, 255, 255, .08);--rts-border-bright: rgba(255, 255, 255, .15);--rts-card-bg-start: rgba(28, 28, 40, .8);--rts-card-bg-end: rgba(20, 20, 30, .9);--rts-danger: #e84118;--rts-danger-bg: rgba(232, 65, 24, .05);--rts-danger-border: rgba(232, 65, 24, .1);--rts-success: #2ecc71;--rts-warning: #f9ca24;--rts-shadow-rgb: 0, 0, 0;--rts-glass-tint: rgba(255, 255, 255, .03)}.rts-theme-light{--rts-bg-primary: #f8f9fa;--rts-bg-surface: #ffffff;--rts-bg-surface-elevated: #f0f0f4;--rts-bg-surface-hover: rgba(0, 0, 0, .04);--rts-bg-gradient: linear-gradient(135deg, #f0ecff 0%, #f8f9fa 60%, #edf4ff 100%);--rts-text-primary: #1a1a2e;--rts-text-secondary: #555570;--rts-text-muted: #8888a0;--rts-accent: #6c5ce7;--rts-accent-light: #a29bfe;--rts-accent-blue: #3b82f7;--rts-accent-highlight: #bf8bff;--rts-accent-highlight-dim: rgba(191, 139, 255, .1);--rts-border-dim: rgba(0, 0, 0, .06);--rts-border-med: rgba(0, 0, 0, .1);--rts-border-bright: rgba(0, 0, 0, .15);--rts-card-bg-start: rgba(255, 255, 255, .95);--rts-card-bg-end: rgba(248, 248, 255, .98);--rts-danger: #e84118;--rts-danger-bg: rgba(232, 65, 24, .06);--rts-danger-border: rgba(232, 65, 24, .15);--rts-success: #2ecc71;--rts-warning: #f9ca24;--rts-shadow-rgb: 0, 0, 0;--rts-glass-tint: rgba(0, 0, 0, .02)}.rts-call-widget{min-width:250px;max-width:320px;background:linear-gradient(135deg,#ffffff12,#ffffff05);border:1px solid var(--rts-border-med, rgba(255, 255, 255, .12));-webkit-backdrop-filter:blur(16px);backdrop-filter:blur(16px);border-radius:14px;padding:14px;box-shadow:0 8px 32px #00000040;transition:transform .2s ease,box-shadow .2s ease,border-color .2s ease}.rts-call-widget:hover{border-color:#10b98159;box-shadow:0 10px 36px #10b9811f}.rts-call-widget.rts-call-widget-active{border-color:#10b98180;background:linear-gradient(135deg,#10b9811f,#ffffff05)}.rts-call-widget .rts-call-widget-header{display:flex;align-items:center;justify-content:space-between;margin-bottom:10px}.rts-call-widget .rts-call-widget-header .rts-call-widget-badge{display:inline-flex;align-items:center;gap:6px;padding:3px 8px;background:#10b98126;border:1px solid rgba(16,185,129,.3);border-radius:20px}.rts-call-widget .rts-call-widget-header .rts-call-widget-badge .rts-call-live-dot{width:7px;height:7px;border-radius:50%;background-color:#10b981;box-shadow:0 0 8px #10b981;animation:rts-call-pulse 1.8s infinite}.rts-call-widget .rts-call-widget-header .rts-call-widget-badge .rts-call-badge-text{font-size:11px;font-weight:700;color:#10b981;text-transform:uppercase;letter-spacing:.5px}.rts-call-widget .rts-call-widget-header .rts-call-widget-icon{font-size:18px;color:var(--rts-text-muted, rgba(255, 255, 255, .5))}.rts-call-widget .rts-call-widget-content{margin-bottom:12px}.rts-call-widget .rts-call-widget-content .rts-call-widget-subtitle{margin:0 0 8px;font-size:13px;color:var(--rts-text-secondary, rgba(255, 255, 255, .7))}.rts-call-widget .rts-call-widget-content .rts-call-widget-subtitle strong{color:var(--rts-text-primary, #fff)}.rts-call-widget .rts-call-widget-content .rts-call-members-section{display:flex;align-items:center;gap:8px;margin-top:6px}.rts-call-widget .rts-call-widget-content .rts-call-members-section .rts-call-members-label{font-size:11px;color:var(--rts-text-muted, rgba(255, 255, 255, .5));font-weight:500}.rts-call-widget .rts-call-widget-content .rts-call-members-section .rts-call-avatar-stack{display:flex;align-items:center}.rts-call-widget .rts-call-widget-content .rts-call-members-section .rts-call-avatar-stack .rts-call-avatar-item{width:26px;height:26px;border-radius:50%;border:2px solid var(--rts-bg-primary, #1e1e2d);margin-left:-6px;position:relative;overflow:hidden;transition:transform .2s ease,z-index .2s ease}.rts-call-widget .rts-call-widget-content .rts-call-members-section .rts-call-avatar-stack .rts-call-avatar-item:first-child{margin-left:0}.rts-call-widget .rts-call-widget-content .rts-call-members-section .rts-call-avatar-stack .rts-call-avatar-item:hover{transform:translateY(-2px) scale(1.15);z-index:10}.rts-call-widget .rts-call-widget-content .rts-call-members-section .rts-call-avatar-stack .rts-call-avatar-item .rts-call-avatar-img{width:100%;height:100%;object-fit:cover;border-radius:50%}.rts-call-widget .rts-call-widget-content .rts-call-members-section .rts-call-avatar-stack .rts-call-avatar-item .rts-call-avatar-fallback{width:100%;height:100%;display:flex;align-items:center;justify-content:center;background:linear-gradient(135deg,#6c5ce7,#a29bfe);color:#fff;font-size:10px;font-weight:700}.rts-call-widget .rts-call-widget-actions .rts-call-action-btn{width:100%;display:inline-flex;align-items:center;justify-content:center;gap:6px;padding:8px 14px;border-radius:10px;font-size:13px;font-weight:600;cursor:pointer;border:none;transition:all .2s ease}.rts-call-widget .rts-call-widget-actions .rts-call-action-btn .material-icons-outlined{font-size:16px}.rts-call-widget .rts-call-widget-actions .rts-join-call-btn{background:linear-gradient(135deg,#10b981,#059669);color:#fff;box-shadow:0 4px 14px #10b98159}.rts-call-widget .rts-call-widget-actions .rts-join-call-btn:hover{transform:translateY(-1px);box-shadow:0 6px 20px #10b98173}.rts-call-widget .rts-call-widget-actions .rts-join-call-btn:active{transform:translateY(0)}.rts-call-widget .rts-call-widget-actions .rts-call-btn-connected{background:#10b98126;border:1px solid rgba(16,185,129,.3);color:#10b981;cursor:default}@keyframes rts-call-pulse{0%{transform:scale(.95);box-shadow:0 0 #10b981b3}70%{transform:scale(1);box-shadow:0 0 0 6px #10b98100}to{transform:scale(.95);box-shadow:0 0 #10b98100}}\n"] }); }
1838
2048
  }
1839
2049
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.4", ngImport: i0, type: CallWidgetComponent, decorators: [{
1840
2050
  type: Component,
1841
- args: [{ selector: 'ngx-call-widget', imports: [NgClass], standalone: true, template: "<div class=\"rts-call-widget\">\n <p> The call has started</p>\n <button class=\"rts-join-call-btn\"\n (click)=\"joinCall()\">\n Join Call\n </button>\n</div>\n" }]
2051
+ args: [{ selector: 'ngx-call-widget', standalone: true, template: "<div class=\"rts-call-widget\" [class.rts-call-widget-active]=\"isUserInCall\">\n <div class=\"rts-call-widget-header\">\n <div class=\"rts-call-widget-badge\">\n <span class=\"rts-call-live-dot\"></span>\n <span class=\"rts-call-badge-text\">{{ isUserInCall ? 'Connected' : 'Live Call' }}</span>\n </div>\n <span class=\"material-icons-outlined rts-call-widget-icon\">videocam</span>\n </div>\n\n <div class=\"rts-call-widget-content\">\n <p class=\"rts-call-widget-subtitle\">\n Started by <strong>{{ callerName }}</strong>\n </p>\n\n @if (presentMembers.length > 0) {\n <div class=\"rts-call-members-section\">\n <span class=\"rts-call-members-label\">In call ({{ presentMembers.length }}):</span>\n <div class=\"rts-call-avatar-stack\">\n @for (member of presentMembers; track member.id) {\n <div class=\"rts-call-avatar-item\" [title]=\"member.name\">\n @if (member.imageUrl) {\n <img [src]=\"member.imageUrl\" [alt]=\"member.name\" class=\"rts-call-avatar-img\" />\n } @else {\n <div class=\"rts-call-avatar-fallback\">\n {{ member.initials }}\n </div>\n }\n </div>\n }\n </div>\n </div>\n }\n </div>\n\n <div class=\"rts-call-widget-actions\">\n @if (isUserInCall) {\n <button class=\"rts-call-action-btn rts-call-btn-connected\" disabled type=\"button\">\n <span class=\"material-icons-outlined\">check_circle</span>\n <span>In Call</span>\n </button>\n } @else {\n <button class=\"rts-call-action-btn rts-join-call-btn\"\n (click)=\"joinCall()\"\n type=\"button\"\n >\n <span class=\"material-icons-outlined\">call</span>\n <span>Join Call</span>\n </button>\n }\n </div>\n</div>\n", styles: [".rts-theme-dark{--rts-bg-primary: #0e0e10;--rts-bg-surface: #1a1a1e;--rts-bg-surface-elevated: #242428;--rts-bg-surface-hover: rgba(255, 255, 255, .05);--rts-bg-gradient: linear-gradient(135deg, #1b0533 0%, #0e0e10 60%, #0a1628 100%);--rts-text-primary: #e5e5e7;--rts-text-secondary: #8e8e93;--rts-text-muted: #6a6a7a;--rts-accent: #6c5ce7;--rts-accent-light: #a29bfe;--rts-accent-blue: #3b82f7;--rts-accent-highlight: #bf8bff;--rts-accent-highlight-dim: rgba(191, 139, 255, .15);--rts-border-dim: rgba(255, 255, 255, .05);--rts-border-med: rgba(255, 255, 255, .08);--rts-border-bright: rgba(255, 255, 255, .15);--rts-card-bg-start: rgba(28, 28, 40, .8);--rts-card-bg-end: rgba(20, 20, 30, .9);--rts-danger: #e84118;--rts-danger-bg: rgba(232, 65, 24, .05);--rts-danger-border: rgba(232, 65, 24, .1);--rts-success: #2ecc71;--rts-warning: #f9ca24;--rts-shadow-rgb: 0, 0, 0;--rts-glass-tint: rgba(255, 255, 255, .03)}.rts-theme-light{--rts-bg-primary: #f8f9fa;--rts-bg-surface: #ffffff;--rts-bg-surface-elevated: #f0f0f4;--rts-bg-surface-hover: rgba(0, 0, 0, .04);--rts-bg-gradient: linear-gradient(135deg, #f0ecff 0%, #f8f9fa 60%, #edf4ff 100%);--rts-text-primary: #1a1a2e;--rts-text-secondary: #555570;--rts-text-muted: #8888a0;--rts-accent: #6c5ce7;--rts-accent-light: #a29bfe;--rts-accent-blue: #3b82f7;--rts-accent-highlight: #bf8bff;--rts-accent-highlight-dim: rgba(191, 139, 255, .1);--rts-border-dim: rgba(0, 0, 0, .06);--rts-border-med: rgba(0, 0, 0, .1);--rts-border-bright: rgba(0, 0, 0, .15);--rts-card-bg-start: rgba(255, 255, 255, .95);--rts-card-bg-end: rgba(248, 248, 255, .98);--rts-danger: #e84118;--rts-danger-bg: rgba(232, 65, 24, .06);--rts-danger-border: rgba(232, 65, 24, .15);--rts-success: #2ecc71;--rts-warning: #f9ca24;--rts-shadow-rgb: 0, 0, 0;--rts-glass-tint: rgba(0, 0, 0, .02)}.rts-call-widget{min-width:250px;max-width:320px;background:linear-gradient(135deg,#ffffff12,#ffffff05);border:1px solid var(--rts-border-med, rgba(255, 255, 255, .12));-webkit-backdrop-filter:blur(16px);backdrop-filter:blur(16px);border-radius:14px;padding:14px;box-shadow:0 8px 32px #00000040;transition:transform .2s ease,box-shadow .2s ease,border-color .2s ease}.rts-call-widget:hover{border-color:#10b98159;box-shadow:0 10px 36px #10b9811f}.rts-call-widget.rts-call-widget-active{border-color:#10b98180;background:linear-gradient(135deg,#10b9811f,#ffffff05)}.rts-call-widget .rts-call-widget-header{display:flex;align-items:center;justify-content:space-between;margin-bottom:10px}.rts-call-widget .rts-call-widget-header .rts-call-widget-badge{display:inline-flex;align-items:center;gap:6px;padding:3px 8px;background:#10b98126;border:1px solid rgba(16,185,129,.3);border-radius:20px}.rts-call-widget .rts-call-widget-header .rts-call-widget-badge .rts-call-live-dot{width:7px;height:7px;border-radius:50%;background-color:#10b981;box-shadow:0 0 8px #10b981;animation:rts-call-pulse 1.8s infinite}.rts-call-widget .rts-call-widget-header .rts-call-widget-badge .rts-call-badge-text{font-size:11px;font-weight:700;color:#10b981;text-transform:uppercase;letter-spacing:.5px}.rts-call-widget .rts-call-widget-header .rts-call-widget-icon{font-size:18px;color:var(--rts-text-muted, rgba(255, 255, 255, .5))}.rts-call-widget .rts-call-widget-content{margin-bottom:12px}.rts-call-widget .rts-call-widget-content .rts-call-widget-subtitle{margin:0 0 8px;font-size:13px;color:var(--rts-text-secondary, rgba(255, 255, 255, .7))}.rts-call-widget .rts-call-widget-content .rts-call-widget-subtitle strong{color:var(--rts-text-primary, #fff)}.rts-call-widget .rts-call-widget-content .rts-call-members-section{display:flex;align-items:center;gap:8px;margin-top:6px}.rts-call-widget .rts-call-widget-content .rts-call-members-section .rts-call-members-label{font-size:11px;color:var(--rts-text-muted, rgba(255, 255, 255, .5));font-weight:500}.rts-call-widget .rts-call-widget-content .rts-call-members-section .rts-call-avatar-stack{display:flex;align-items:center}.rts-call-widget .rts-call-widget-content .rts-call-members-section .rts-call-avatar-stack .rts-call-avatar-item{width:26px;height:26px;border-radius:50%;border:2px solid var(--rts-bg-primary, #1e1e2d);margin-left:-6px;position:relative;overflow:hidden;transition:transform .2s ease,z-index .2s ease}.rts-call-widget .rts-call-widget-content .rts-call-members-section .rts-call-avatar-stack .rts-call-avatar-item:first-child{margin-left:0}.rts-call-widget .rts-call-widget-content .rts-call-members-section .rts-call-avatar-stack .rts-call-avatar-item:hover{transform:translateY(-2px) scale(1.15);z-index:10}.rts-call-widget .rts-call-widget-content .rts-call-members-section .rts-call-avatar-stack .rts-call-avatar-item .rts-call-avatar-img{width:100%;height:100%;object-fit:cover;border-radius:50%}.rts-call-widget .rts-call-widget-content .rts-call-members-section .rts-call-avatar-stack .rts-call-avatar-item .rts-call-avatar-fallback{width:100%;height:100%;display:flex;align-items:center;justify-content:center;background:linear-gradient(135deg,#6c5ce7,#a29bfe);color:#fff;font-size:10px;font-weight:700}.rts-call-widget .rts-call-widget-actions .rts-call-action-btn{width:100%;display:inline-flex;align-items:center;justify-content:center;gap:6px;padding:8px 14px;border-radius:10px;font-size:13px;font-weight:600;cursor:pointer;border:none;transition:all .2s ease}.rts-call-widget .rts-call-widget-actions .rts-call-action-btn .material-icons-outlined{font-size:16px}.rts-call-widget .rts-call-widget-actions .rts-join-call-btn{background:linear-gradient(135deg,#10b981,#059669);color:#fff;box-shadow:0 4px 14px #10b98159}.rts-call-widget .rts-call-widget-actions .rts-join-call-btn:hover{transform:translateY(-1px);box-shadow:0 6px 20px #10b98173}.rts-call-widget .rts-call-widget-actions .rts-join-call-btn:active{transform:translateY(0)}.rts-call-widget .rts-call-widget-actions .rts-call-btn-connected{background:#10b98126;border:1px solid rgba(16,185,129,.3);color:#10b981;cursor:default}@keyframes rts-call-pulse{0%{transform:scale(.95);box-shadow:0 0 #10b981b3}70%{transform:scale(1);box-shadow:0 0 0 6px #10b98100}to{transform:scale(.95);box-shadow:0 0 #10b98100}}\n"] }]
1842
2052
  }], propDecorators: { message: [{
1843
2053
  type: Input
1844
2054
  }], messenger: [{
@@ -1890,6 +2100,8 @@ class MediaWidgetComponent {
1890
2100
  this.hiddenCount = 0;
1891
2101
  this.showAll = false;
1892
2102
  this.downloadingUrl = null;
2103
+ this.loadedImageUrls = new Set();
2104
+ this.erroredImageUrls = new Set();
1893
2105
  this.MAX_GRID = 4;
1894
2106
  }
1895
2107
  ngOnInit() {
@@ -1906,6 +2118,22 @@ class MediaWidgetComponent {
1906
2118
  previewUrl(img) {
1907
2119
  return img.smallVersionUrl || img.url;
1908
2120
  }
2121
+ isImageLoaded(img) {
2122
+ const url = this.previewUrl(img);
2123
+ return this.loadedImageUrls.has(url);
2124
+ }
2125
+ isImageError(img) {
2126
+ const url = this.previewUrl(img);
2127
+ return this.erroredImageUrls.has(url);
2128
+ }
2129
+ onImageLoad(img) {
2130
+ const url = this.previewUrl(img);
2131
+ this.loadedImageUrls.add(url);
2132
+ }
2133
+ onImageError(img) {
2134
+ const url = this.previewUrl(img);
2135
+ this.erroredImageUrls.add(url);
2136
+ }
1909
2137
  async downloadOriginal(event, img) {
1910
2138
  event.stopPropagation();
1911
2139
  event.preventDefault();
@@ -2012,11 +2240,11 @@ class MediaWidgetComponent {
2012
2240
  }
2013
2241
  }
2014
2242
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.4", ngImport: i0, type: MediaWidgetComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
2015
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.4", type: MediaWidgetComponent, isStandalone: true, selector: "rtsee-media-widget", inputs: { message: "message", messenger: "messenger", chat: "chat", showCost: "showCost" }, usesOnChanges: true, ngImport: i0, template: "<div class=\"rts-media-widget\">\n <!-- Video player -->\n @if (message.video) {\n <div class=\"rts-media-video\">\n <video\n controls\n playsinline\n preload=\"metadata\"\n [src]=\"message.video.url\"\n class=\"rts-video-player\"\n >\n Your browser does not support video playback.\n </video>\n </div>\n }\n\n <!-- Image grid -->\n @if (hasImages) {\n <div class=\"rts-media-images\" [ngClass]=\"gridClass\">\n @for (img of visibleImages; track img.id || img.url) {\n <div class=\"rts-media-image-wrapper\">\n <img [src]=\"previewUrl(img)\" [alt]=\"img.name\" class=\"rts-media-image\" loading=\"lazy\" />\n <button\n type=\"button\"\n class=\"rts-media-btn-download\"\n [class.rts-downloading]=\"downloadingUrl === img.url\"\n title=\"Download original image\"\n aria-label=\"Download original image\"\n (click)=\"downloadOriginal($event, img)\"\n >\n <i class=\"pi\" [ngClass]=\"downloadingUrl === img.url ? 'pi-spin pi-spinner' : 'pi-download'\"></i>\n </button>\n <div class=\"rts-media-image-actions\">\n <button\n type=\"button\"\n class=\"rts-media-btn-paint\"\n title=\"Send image to Illustrator (Review & Iterate)\"\n (click)=\"sendToIllustrator($event, img)\"\n >\n <i class=\"pi pi-palette\"></i>\n <span>+ paint</span>\n </button>\n <button\n type=\"button\"\n class=\"rts-media-btn-context\"\n title=\"Add image to Illustrator context references\"\n (click)=\"addToContext($event, img)\"\n >\n <i class=\"pi pi-plus\"></i>\n <span>+ context</span>\n </button>\n </div>\n </div>\n }\n </div>\n }\n\n <!-- Show more / less toggle -->\n @if (hiddenCount > 0 || showAll) {\n <button class=\"rts-media-toggle\" (click)=\"toggleShowAll()\">\n {{ showAll ? 'Show less' : 'Show ' + hiddenCount + ' more' }}\n </button>\n }\n\n <!-- Cost display (optional, disabled when nested inside agentic widget) -->\n @if (showCost && message.cost) {\n <div class=\"rts-media-cost\">\n <rtsee-cost-display [cost]=\"message.cost\"></rtsee-cost-display>\n </div>\n }\n</div>\n", styles: [".rts-media-widget{max-width:100%}.rts-media-video{border-radius:8px;overflow:hidden;margin-bottom:6px}.rts-video-player{width:100%;max-height:360px;border-radius:8px;background:#000;display:block}.rts-media-images{display:grid;gap:4px;border-radius:8px;overflow:hidden;margin-bottom:4px}.rts-media-grid-1{grid-template-columns:1fr}.rts-media-grid-2{grid-template-columns:1fr 1fr}.rts-media-grid-4{grid-template-columns:1fr 1fr;grid-template-rows:auto auto}.rts-media-image-wrapper{position:relative;overflow:hidden;border-radius:6px;background:var(--rts-bg-secondary, #1e1e2e)}.rts-media-image-actions{position:absolute;top:8px;right:8px;z-index:5;display:inline-flex;align-items:center;gap:6px}.rts-media-btn-paint{display:inline-flex;align-items:center;gap:4px;padding:4px 8px;background:#12121cd9;-webkit-backdrop-filter:blur(8px);backdrop-filter:blur(8px);color:#e9d5ff;border:1px solid rgba(168,85,247,.45);border-radius:6px;font-size:11px;font-weight:600;cursor:pointer;transition:all .2s cubic-bezier(.34,1.56,.64,1);box-shadow:0 2px 8px #00000080}.rts-media-btn-paint i{font-size:11px;color:#c084fc}.rts-media-btn-paint span{line-height:1}.rts-media-btn-paint:hover{background:#9333eae6;color:#fff;border-color:#a855f7;transform:translateY(-1px) scale(1.04);box-shadow:0 4px 12px #a855f773}.rts-media-btn-paint:hover i{color:#fff}.rts-media-btn-paint:active{transform:translateY(0) scale(.98)}.rts-media-btn-context{display:inline-flex;align-items:center;gap:4px;padding:4px 8px;background:#12121cd9;-webkit-backdrop-filter:blur(8px);backdrop-filter:blur(8px);color:#bae6fd;border:1px solid rgba(56,189,248,.45);border-radius:6px;font-size:11px;font-weight:600;cursor:pointer;transition:all .2s cubic-bezier(.34,1.56,.64,1);box-shadow:0 2px 8px #00000080}.rts-media-btn-context i{font-size:11px;color:#38bdf8}.rts-media-btn-context span{line-height:1}.rts-media-btn-context:hover{background:#0ea5e9e6;color:#fff;border-color:#38bdf8;transform:translateY(-1px) scale(1.04);box-shadow:0 4px 12px #38bdf873}.rts-media-btn-context:hover i{color:#fff}.rts-media-btn-context:active{transform:translateY(0) scale(.98)}.rts-media-btn-download{position:absolute;left:8px;bottom:8px;z-index:5;width:30px;height:30px;display:inline-flex;align-items:center;justify-content:center;padding:0;background:#12121cd9;-webkit-backdrop-filter:blur(8px);backdrop-filter:blur(8px);color:#d1fae5;border:1px solid rgba(52,211,153,.45);border-radius:50%;cursor:pointer;opacity:.85;transition:all .2s cubic-bezier(.34,1.56,.64,1);box-shadow:0 2px 8px #00000080}.rts-media-btn-download i{font-size:12px;color:#34d399}.rts-media-btn-download:hover{opacity:1;background:#10b981e6;border-color:#34d399;transform:translateY(-1px) scale(1.08);box-shadow:0 4px 12px #34d39973}.rts-media-btn-download:hover i{color:#fff}.rts-media-btn-download:active{transform:translateY(0) scale(.96)}.rts-media-btn-download.rts-downloading{cursor:progress;opacity:1}.rts-media-image{width:100%;height:auto;max-height:280px;object-fit:cover;display:block;transition:transform .2s ease;cursor:pointer}.rts-media-image:hover{transform:scale(1.02)}.rts-media-grid-1 .rts-media-image{max-height:400px;object-fit:contain}.rts-media-toggle{display:block;width:100%;padding:6px 0;border:none;background:none;color:var(--rts-agent-accent, #8b5cf6);font-size:12px;font-weight:500;cursor:pointer;text-align:center;transition:opacity .15s ease}.rts-media-toggle:hover{opacity:.8}.rts-media-cost{display:flex;justify-content:flex-end;padding-top:4px}\n"], dependencies: [{ kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "component", type: CostDisplayComponent, selector: "rtsee-cost-display", inputs: ["cost"] }] }); }
2243
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.4", type: MediaWidgetComponent, isStandalone: true, selector: "rtsee-media-widget", inputs: { message: "message", messenger: "messenger", chat: "chat", showCost: "showCost" }, usesOnChanges: true, ngImport: i0, template: "<div class=\"rts-media-widget\">\n <!-- Video player -->\n @if (message.video) {\n <div class=\"rts-media-video\">\n <video\n controls\n playsinline\n preload=\"metadata\"\n [src]=\"message.video.url\"\n class=\"rts-video-player\"\n >\n Your browser does not support video playback.\n </video>\n </div>\n }\n\n <!-- Image grid -->\n @if (hasImages) {\n <div class=\"rts-media-images\" [ngClass]=\"gridClass\">\n @for (img of visibleImages; track img.id || img.url) {\n <div class=\"rts-media-image-wrapper\" [class.rts-image-loaded]=\"isImageLoaded(img)\">\n <!-- Media Placeholder (preview before image loads) -->\n @if (!isImageLoaded(img)) {\n <div class=\"rts-media-placeholder\" [class.rts-placeholder-error]=\"isImageError(img)\">\n <div class=\"rts-media-placeholder-shimmer\"></div>\n <div class=\"rts-media-placeholder-content\">\n @if (isImageError(img)) {\n <span class=\"material-icons-outlined rts-media-placeholder-icon\">broken_image</span>\n <span class=\"rts-media-placeholder-text\">Failed to load</span>\n } @else {\n <div class=\"rts-media-placeholder-icon-wrap\">\n <span class=\"material-icons-outlined rts-media-placeholder-icon\">image</span>\n </div>\n <span class=\"rts-media-placeholder-text\">{{ img.name || 'Loading media preview...' }}</span>\n }\n </div>\n </div>\n }\n\n <img\n [src]=\"previewUrl(img)\"\n [alt]=\"img.name\"\n class=\"rts-media-image\"\n [class.rts-media-image-loaded]=\"isImageLoaded(img)\"\n (load)=\"onImageLoad(img)\"\n (error)=\"onImageError(img)\"\n loading=\"lazy\"\n />\n\n @if (isImageLoaded(img)) {\n <button\n type=\"button\"\n class=\"rts-media-btn-download\"\n [class.rts-downloading]=\"downloadingUrl === img.url\"\n title=\"Download original image\"\n aria-label=\"Download original image\"\n (click)=\"downloadOriginal($event, img)\"\n >\n <i class=\"pi\" [ngClass]=\"downloadingUrl === img.url ? 'pi-spin pi-spinner' : 'pi-download'\"></i>\n </button>\n <div class=\"rts-media-image-actions\">\n <button\n type=\"button\"\n class=\"rts-media-btn-paint\"\n title=\"Send image to Illustrator (Review & Iterate)\"\n (click)=\"sendToIllustrator($event, img)\"\n >\n <i class=\"pi pi-palette\"></i>\n <span>+ paint</span>\n </button>\n <button\n type=\"button\"\n class=\"rts-media-btn-context\"\n title=\"Add image to Illustrator context references\"\n (click)=\"addToContext($event, img)\"\n >\n <i class=\"pi pi-plus\"></i>\n <span>+ context</span>\n </button>\n </div>\n }\n </div>\n }\n </div>\n }\n\n <!-- Show more / less toggle -->\n @if (hiddenCount > 0 || showAll) {\n <button class=\"rts-media-toggle\" (click)=\"toggleShowAll()\">\n {{ showAll ? 'Show less' : 'Show ' + hiddenCount + ' more' }}\n </button>\n }\n\n <!-- Cost display (optional, disabled when nested inside agentic widget) -->\n @if (showCost && message.cost) {\n <div class=\"rts-media-cost\">\n <rtsee-cost-display [cost]=\"message.cost\"></rtsee-cost-display>\n </div>\n }\n</div>\n", styles: [".rts-media-widget{max-width:100%}.rts-media-video{border-radius:8px;overflow:hidden;margin-bottom:6px;aspect-ratio:16/9;background:#000}.rts-video-player{width:100%;height:100%;max-height:360px;border-radius:8px;background:#000;display:block}.rts-media-images{display:grid;gap:4px;border-radius:8px;overflow:hidden;margin-bottom:4px}.rts-media-grid-1{grid-template-columns:1fr}.rts-media-grid-1 .rts-media-image-wrapper{min-height:220px;aspect-ratio:16/10}.rts-media-grid-1 .rts-media-image-wrapper.rts-image-loaded{min-height:0;aspect-ratio:auto}.rts-media-grid-2{grid-template-columns:1fr 1fr}.rts-media-grid-2 .rts-media-image-wrapper{aspect-ratio:1/1;min-height:120px}.rts-media-grid-4{grid-template-columns:1fr 1fr;grid-template-rows:auto auto}.rts-media-grid-4 .rts-media-image-wrapper{aspect-ratio:1/1;min-height:120px}.rts-media-image-wrapper{position:relative;overflow:hidden;border-radius:6px;background:var(--rts-bg-secondary, #1e1e2e);display:flex;align-items:center;justify-content:center}.rts-media-placeholder{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;background:linear-gradient(135deg,#1a1a2af5,#12121cfa);border:1px solid rgba(255,255,255,.06);border-radius:6px;z-index:2;overflow:hidden;pointer-events:none}.rts-media-placeholder .rts-media-placeholder-shimmer{position:absolute;top:0;left:-150%;width:150%;height:100%;background:linear-gradient(90deg,transparent 0%,rgba(255,255,255,.03) 30%,rgba(168,85,247,.12) 50%,rgba(255,255,255,.03) 70%,transparent 100%);animation:rts-media-shimmer 2.2s infinite ease-in-out}.rts-media-placeholder .rts-media-placeholder-content{position:relative;z-index:3;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:8px;padding:12px;text-align:center;max-width:90%}.rts-media-placeholder .rts-media-placeholder-icon-wrap{width:42px;height:42px;border-radius:50%;background:#6c5ce726;border:1px solid rgba(168,85,247,.35);display:flex;align-items:center;justify-content:center;animation:rts-media-pulse 2s infinite ease-in-out;box-shadow:0 4px 16px #6c5ce733}.rts-media-placeholder .rts-media-placeholder-icon{font-size:22px;color:var(--rts-accent-light, #c084fc)}.rts-media-placeholder .rts-media-placeholder-text{font-size:11px;font-weight:500;color:var(--rts-text-muted, #94a3b8);max-width:180px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;line-height:1.2}.rts-media-placeholder.rts-placeholder-error .rts-media-placeholder-icon-wrap{background:#ef444426;border-color:#ef444459;animation:none;box-shadow:none}.rts-media-placeholder.rts-placeholder-error .rts-media-placeholder-icon{color:#ef4444}.rts-media-placeholder.rts-placeholder-error .rts-media-placeholder-text{color:#f87171}@keyframes rts-media-shimmer{0%{transform:translate(0)}to{transform:translate(200%)}}@keyframes rts-media-pulse{0%,to{transform:scale(1);opacity:.85}50%{transform:scale(1.08);opacity:1;box-shadow:0 0 16px #a855f766}}.rts-media-image-actions{position:absolute;top:8px;right:8px;z-index:5;display:inline-flex;align-items:center;gap:6px}.rts-media-btn-paint{display:inline-flex;align-items:center;gap:4px;padding:4px 8px;background:#12121cd9;-webkit-backdrop-filter:blur(8px);backdrop-filter:blur(8px);color:#e9d5ff;border:1px solid rgba(168,85,247,.45);border-radius:6px;font-size:11px;font-weight:600;cursor:pointer;transition:all .2s cubic-bezier(.34,1.56,.64,1);box-shadow:0 2px 8px #00000080}.rts-media-btn-paint i{font-size:11px;color:#c084fc}.rts-media-btn-paint span{line-height:1}.rts-media-btn-paint:hover{background:#9333eae6;color:#fff;border-color:#a855f7;transform:translateY(-1px) scale(1.04);box-shadow:0 4px 12px #a855f773}.rts-media-btn-paint:hover i{color:#fff}.rts-media-btn-paint:active{transform:translateY(0) scale(.98)}.rts-media-btn-context{display:inline-flex;align-items:center;gap:4px;padding:4px 8px;background:#12121cd9;-webkit-backdrop-filter:blur(8px);backdrop-filter:blur(8px);color:#bae6fd;border:1px solid rgba(56,189,248,.45);border-radius:6px;font-size:11px;font-weight:600;cursor:pointer;transition:all .2s cubic-bezier(.34,1.56,.64,1);box-shadow:0 2px 8px #00000080}.rts-media-btn-context i{font-size:11px;color:#38bdf8}.rts-media-btn-context span{line-height:1}.rts-media-btn-context:hover{background:#0ea5e9e6;color:#fff;border-color:#38bdf8;transform:translateY(-1px) scale(1.04);box-shadow:0 4px 12px #38bdf873}.rts-media-btn-context:hover i{color:#fff}.rts-media-btn-context:active{transform:translateY(0) scale(.98)}.rts-media-btn-download{position:absolute;left:8px;bottom:8px;z-index:5;width:30px;height:30px;display:inline-flex;align-items:center;justify-content:center;padding:0;background:#12121cd9;-webkit-backdrop-filter:blur(8px);backdrop-filter:blur(8px);color:#d1fae5;border:1px solid rgba(52,211,153,.45);border-radius:50%;cursor:pointer;opacity:.85;transition:all .2s cubic-bezier(.34,1.56,.64,1);box-shadow:0 2px 8px #00000080}.rts-media-btn-download i{font-size:12px;color:#34d399}.rts-media-btn-download:hover{opacity:1;background:#10b981e6;border-color:#34d399;transform:translateY(-1px) scale(1.08);box-shadow:0 4px 12px #34d39973}.rts-media-btn-download:hover i{color:#fff}.rts-media-btn-download:active{transform:translateY(0) scale(.96)}.rts-media-btn-download.rts-downloading{cursor:progress;opacity:1}.rts-media-image{width:100%;height:100%;max-height:280px;object-fit:cover;display:block;opacity:0;transition:opacity .25s ease-out,transform .2s ease;cursor:pointer}.rts-media-image.rts-media-image-loaded{opacity:1}.rts-media-image:hover{transform:scale(1.02)}.rts-media-grid-1 .rts-media-image{height:auto;max-height:400px;object-fit:contain}.rts-media-toggle{display:block;width:100%;padding:6px 0;border:none;background:none;color:var(--rts-agent-accent, #8b5cf6);font-size:12px;font-weight:500;cursor:pointer;text-align:center;transition:opacity .15s ease}.rts-media-toggle:hover{opacity:.8}.rts-media-cost{display:flex;justify-content:flex-end;padding-top:4px}\n"], dependencies: [{ kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "component", type: CostDisplayComponent, selector: "rtsee-cost-display", inputs: ["cost"] }] }); }
2016
2244
  }
2017
2245
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.4", ngImport: i0, type: MediaWidgetComponent, decorators: [{
2018
2246
  type: Component,
2019
- args: [{ selector: 'rtsee-media-widget', standalone: true, imports: [NgClass, CostDisplayComponent], template: "<div class=\"rts-media-widget\">\n <!-- Video player -->\n @if (message.video) {\n <div class=\"rts-media-video\">\n <video\n controls\n playsinline\n preload=\"metadata\"\n [src]=\"message.video.url\"\n class=\"rts-video-player\"\n >\n Your browser does not support video playback.\n </video>\n </div>\n }\n\n <!-- Image grid -->\n @if (hasImages) {\n <div class=\"rts-media-images\" [ngClass]=\"gridClass\">\n @for (img of visibleImages; track img.id || img.url) {\n <div class=\"rts-media-image-wrapper\">\n <img [src]=\"previewUrl(img)\" [alt]=\"img.name\" class=\"rts-media-image\" loading=\"lazy\" />\n <button\n type=\"button\"\n class=\"rts-media-btn-download\"\n [class.rts-downloading]=\"downloadingUrl === img.url\"\n title=\"Download original image\"\n aria-label=\"Download original image\"\n (click)=\"downloadOriginal($event, img)\"\n >\n <i class=\"pi\" [ngClass]=\"downloadingUrl === img.url ? 'pi-spin pi-spinner' : 'pi-download'\"></i>\n </button>\n <div class=\"rts-media-image-actions\">\n <button\n type=\"button\"\n class=\"rts-media-btn-paint\"\n title=\"Send image to Illustrator (Review & Iterate)\"\n (click)=\"sendToIllustrator($event, img)\"\n >\n <i class=\"pi pi-palette\"></i>\n <span>+ paint</span>\n </button>\n <button\n type=\"button\"\n class=\"rts-media-btn-context\"\n title=\"Add image to Illustrator context references\"\n (click)=\"addToContext($event, img)\"\n >\n <i class=\"pi pi-plus\"></i>\n <span>+ context</span>\n </button>\n </div>\n </div>\n }\n </div>\n }\n\n <!-- Show more / less toggle -->\n @if (hiddenCount > 0 || showAll) {\n <button class=\"rts-media-toggle\" (click)=\"toggleShowAll()\">\n {{ showAll ? 'Show less' : 'Show ' + hiddenCount + ' more' }}\n </button>\n }\n\n <!-- Cost display (optional, disabled when nested inside agentic widget) -->\n @if (showCost && message.cost) {\n <div class=\"rts-media-cost\">\n <rtsee-cost-display [cost]=\"message.cost\"></rtsee-cost-display>\n </div>\n }\n</div>\n", styles: [".rts-media-widget{max-width:100%}.rts-media-video{border-radius:8px;overflow:hidden;margin-bottom:6px}.rts-video-player{width:100%;max-height:360px;border-radius:8px;background:#000;display:block}.rts-media-images{display:grid;gap:4px;border-radius:8px;overflow:hidden;margin-bottom:4px}.rts-media-grid-1{grid-template-columns:1fr}.rts-media-grid-2{grid-template-columns:1fr 1fr}.rts-media-grid-4{grid-template-columns:1fr 1fr;grid-template-rows:auto auto}.rts-media-image-wrapper{position:relative;overflow:hidden;border-radius:6px;background:var(--rts-bg-secondary, #1e1e2e)}.rts-media-image-actions{position:absolute;top:8px;right:8px;z-index:5;display:inline-flex;align-items:center;gap:6px}.rts-media-btn-paint{display:inline-flex;align-items:center;gap:4px;padding:4px 8px;background:#12121cd9;-webkit-backdrop-filter:blur(8px);backdrop-filter:blur(8px);color:#e9d5ff;border:1px solid rgba(168,85,247,.45);border-radius:6px;font-size:11px;font-weight:600;cursor:pointer;transition:all .2s cubic-bezier(.34,1.56,.64,1);box-shadow:0 2px 8px #00000080}.rts-media-btn-paint i{font-size:11px;color:#c084fc}.rts-media-btn-paint span{line-height:1}.rts-media-btn-paint:hover{background:#9333eae6;color:#fff;border-color:#a855f7;transform:translateY(-1px) scale(1.04);box-shadow:0 4px 12px #a855f773}.rts-media-btn-paint:hover i{color:#fff}.rts-media-btn-paint:active{transform:translateY(0) scale(.98)}.rts-media-btn-context{display:inline-flex;align-items:center;gap:4px;padding:4px 8px;background:#12121cd9;-webkit-backdrop-filter:blur(8px);backdrop-filter:blur(8px);color:#bae6fd;border:1px solid rgba(56,189,248,.45);border-radius:6px;font-size:11px;font-weight:600;cursor:pointer;transition:all .2s cubic-bezier(.34,1.56,.64,1);box-shadow:0 2px 8px #00000080}.rts-media-btn-context i{font-size:11px;color:#38bdf8}.rts-media-btn-context span{line-height:1}.rts-media-btn-context:hover{background:#0ea5e9e6;color:#fff;border-color:#38bdf8;transform:translateY(-1px) scale(1.04);box-shadow:0 4px 12px #38bdf873}.rts-media-btn-context:hover i{color:#fff}.rts-media-btn-context:active{transform:translateY(0) scale(.98)}.rts-media-btn-download{position:absolute;left:8px;bottom:8px;z-index:5;width:30px;height:30px;display:inline-flex;align-items:center;justify-content:center;padding:0;background:#12121cd9;-webkit-backdrop-filter:blur(8px);backdrop-filter:blur(8px);color:#d1fae5;border:1px solid rgba(52,211,153,.45);border-radius:50%;cursor:pointer;opacity:.85;transition:all .2s cubic-bezier(.34,1.56,.64,1);box-shadow:0 2px 8px #00000080}.rts-media-btn-download i{font-size:12px;color:#34d399}.rts-media-btn-download:hover{opacity:1;background:#10b981e6;border-color:#34d399;transform:translateY(-1px) scale(1.08);box-shadow:0 4px 12px #34d39973}.rts-media-btn-download:hover i{color:#fff}.rts-media-btn-download:active{transform:translateY(0) scale(.96)}.rts-media-btn-download.rts-downloading{cursor:progress;opacity:1}.rts-media-image{width:100%;height:auto;max-height:280px;object-fit:cover;display:block;transition:transform .2s ease;cursor:pointer}.rts-media-image:hover{transform:scale(1.02)}.rts-media-grid-1 .rts-media-image{max-height:400px;object-fit:contain}.rts-media-toggle{display:block;width:100%;padding:6px 0;border:none;background:none;color:var(--rts-agent-accent, #8b5cf6);font-size:12px;font-weight:500;cursor:pointer;text-align:center;transition:opacity .15s ease}.rts-media-toggle:hover{opacity:.8}.rts-media-cost{display:flex;justify-content:flex-end;padding-top:4px}\n"] }]
2247
+ args: [{ selector: 'rtsee-media-widget', standalone: true, imports: [NgClass, CostDisplayComponent], template: "<div class=\"rts-media-widget\">\n <!-- Video player -->\n @if (message.video) {\n <div class=\"rts-media-video\">\n <video\n controls\n playsinline\n preload=\"metadata\"\n [src]=\"message.video.url\"\n class=\"rts-video-player\"\n >\n Your browser does not support video playback.\n </video>\n </div>\n }\n\n <!-- Image grid -->\n @if (hasImages) {\n <div class=\"rts-media-images\" [ngClass]=\"gridClass\">\n @for (img of visibleImages; track img.id || img.url) {\n <div class=\"rts-media-image-wrapper\" [class.rts-image-loaded]=\"isImageLoaded(img)\">\n <!-- Media Placeholder (preview before image loads) -->\n @if (!isImageLoaded(img)) {\n <div class=\"rts-media-placeholder\" [class.rts-placeholder-error]=\"isImageError(img)\">\n <div class=\"rts-media-placeholder-shimmer\"></div>\n <div class=\"rts-media-placeholder-content\">\n @if (isImageError(img)) {\n <span class=\"material-icons-outlined rts-media-placeholder-icon\">broken_image</span>\n <span class=\"rts-media-placeholder-text\">Failed to load</span>\n } @else {\n <div class=\"rts-media-placeholder-icon-wrap\">\n <span class=\"material-icons-outlined rts-media-placeholder-icon\">image</span>\n </div>\n <span class=\"rts-media-placeholder-text\">{{ img.name || 'Loading media preview...' }}</span>\n }\n </div>\n </div>\n }\n\n <img\n [src]=\"previewUrl(img)\"\n [alt]=\"img.name\"\n class=\"rts-media-image\"\n [class.rts-media-image-loaded]=\"isImageLoaded(img)\"\n (load)=\"onImageLoad(img)\"\n (error)=\"onImageError(img)\"\n loading=\"lazy\"\n />\n\n @if (isImageLoaded(img)) {\n <button\n type=\"button\"\n class=\"rts-media-btn-download\"\n [class.rts-downloading]=\"downloadingUrl === img.url\"\n title=\"Download original image\"\n aria-label=\"Download original image\"\n (click)=\"downloadOriginal($event, img)\"\n >\n <i class=\"pi\" [ngClass]=\"downloadingUrl === img.url ? 'pi-spin pi-spinner' : 'pi-download'\"></i>\n </button>\n <div class=\"rts-media-image-actions\">\n <button\n type=\"button\"\n class=\"rts-media-btn-paint\"\n title=\"Send image to Illustrator (Review & Iterate)\"\n (click)=\"sendToIllustrator($event, img)\"\n >\n <i class=\"pi pi-palette\"></i>\n <span>+ paint</span>\n </button>\n <button\n type=\"button\"\n class=\"rts-media-btn-context\"\n title=\"Add image to Illustrator context references\"\n (click)=\"addToContext($event, img)\"\n >\n <i class=\"pi pi-plus\"></i>\n <span>+ context</span>\n </button>\n </div>\n }\n </div>\n }\n </div>\n }\n\n <!-- Show more / less toggle -->\n @if (hiddenCount > 0 || showAll) {\n <button class=\"rts-media-toggle\" (click)=\"toggleShowAll()\">\n {{ showAll ? 'Show less' : 'Show ' + hiddenCount + ' more' }}\n </button>\n }\n\n <!-- Cost display (optional, disabled when nested inside agentic widget) -->\n @if (showCost && message.cost) {\n <div class=\"rts-media-cost\">\n <rtsee-cost-display [cost]=\"message.cost\"></rtsee-cost-display>\n </div>\n }\n</div>\n", styles: [".rts-media-widget{max-width:100%}.rts-media-video{border-radius:8px;overflow:hidden;margin-bottom:6px;aspect-ratio:16/9;background:#000}.rts-video-player{width:100%;height:100%;max-height:360px;border-radius:8px;background:#000;display:block}.rts-media-images{display:grid;gap:4px;border-radius:8px;overflow:hidden;margin-bottom:4px}.rts-media-grid-1{grid-template-columns:1fr}.rts-media-grid-1 .rts-media-image-wrapper{min-height:220px;aspect-ratio:16/10}.rts-media-grid-1 .rts-media-image-wrapper.rts-image-loaded{min-height:0;aspect-ratio:auto}.rts-media-grid-2{grid-template-columns:1fr 1fr}.rts-media-grid-2 .rts-media-image-wrapper{aspect-ratio:1/1;min-height:120px}.rts-media-grid-4{grid-template-columns:1fr 1fr;grid-template-rows:auto auto}.rts-media-grid-4 .rts-media-image-wrapper{aspect-ratio:1/1;min-height:120px}.rts-media-image-wrapper{position:relative;overflow:hidden;border-radius:6px;background:var(--rts-bg-secondary, #1e1e2e);display:flex;align-items:center;justify-content:center}.rts-media-placeholder{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;background:linear-gradient(135deg,#1a1a2af5,#12121cfa);border:1px solid rgba(255,255,255,.06);border-radius:6px;z-index:2;overflow:hidden;pointer-events:none}.rts-media-placeholder .rts-media-placeholder-shimmer{position:absolute;top:0;left:-150%;width:150%;height:100%;background:linear-gradient(90deg,transparent 0%,rgba(255,255,255,.03) 30%,rgba(168,85,247,.12) 50%,rgba(255,255,255,.03) 70%,transparent 100%);animation:rts-media-shimmer 2.2s infinite ease-in-out}.rts-media-placeholder .rts-media-placeholder-content{position:relative;z-index:3;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:8px;padding:12px;text-align:center;max-width:90%}.rts-media-placeholder .rts-media-placeholder-icon-wrap{width:42px;height:42px;border-radius:50%;background:#6c5ce726;border:1px solid rgba(168,85,247,.35);display:flex;align-items:center;justify-content:center;animation:rts-media-pulse 2s infinite ease-in-out;box-shadow:0 4px 16px #6c5ce733}.rts-media-placeholder .rts-media-placeholder-icon{font-size:22px;color:var(--rts-accent-light, #c084fc)}.rts-media-placeholder .rts-media-placeholder-text{font-size:11px;font-weight:500;color:var(--rts-text-muted, #94a3b8);max-width:180px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;line-height:1.2}.rts-media-placeholder.rts-placeholder-error .rts-media-placeholder-icon-wrap{background:#ef444426;border-color:#ef444459;animation:none;box-shadow:none}.rts-media-placeholder.rts-placeholder-error .rts-media-placeholder-icon{color:#ef4444}.rts-media-placeholder.rts-placeholder-error .rts-media-placeholder-text{color:#f87171}@keyframes rts-media-shimmer{0%{transform:translate(0)}to{transform:translate(200%)}}@keyframes rts-media-pulse{0%,to{transform:scale(1);opacity:.85}50%{transform:scale(1.08);opacity:1;box-shadow:0 0 16px #a855f766}}.rts-media-image-actions{position:absolute;top:8px;right:8px;z-index:5;display:inline-flex;align-items:center;gap:6px}.rts-media-btn-paint{display:inline-flex;align-items:center;gap:4px;padding:4px 8px;background:#12121cd9;-webkit-backdrop-filter:blur(8px);backdrop-filter:blur(8px);color:#e9d5ff;border:1px solid rgba(168,85,247,.45);border-radius:6px;font-size:11px;font-weight:600;cursor:pointer;transition:all .2s cubic-bezier(.34,1.56,.64,1);box-shadow:0 2px 8px #00000080}.rts-media-btn-paint i{font-size:11px;color:#c084fc}.rts-media-btn-paint span{line-height:1}.rts-media-btn-paint:hover{background:#9333eae6;color:#fff;border-color:#a855f7;transform:translateY(-1px) scale(1.04);box-shadow:0 4px 12px #a855f773}.rts-media-btn-paint:hover i{color:#fff}.rts-media-btn-paint:active{transform:translateY(0) scale(.98)}.rts-media-btn-context{display:inline-flex;align-items:center;gap:4px;padding:4px 8px;background:#12121cd9;-webkit-backdrop-filter:blur(8px);backdrop-filter:blur(8px);color:#bae6fd;border:1px solid rgba(56,189,248,.45);border-radius:6px;font-size:11px;font-weight:600;cursor:pointer;transition:all .2s cubic-bezier(.34,1.56,.64,1);box-shadow:0 2px 8px #00000080}.rts-media-btn-context i{font-size:11px;color:#38bdf8}.rts-media-btn-context span{line-height:1}.rts-media-btn-context:hover{background:#0ea5e9e6;color:#fff;border-color:#38bdf8;transform:translateY(-1px) scale(1.04);box-shadow:0 4px 12px #38bdf873}.rts-media-btn-context:hover i{color:#fff}.rts-media-btn-context:active{transform:translateY(0) scale(.98)}.rts-media-btn-download{position:absolute;left:8px;bottom:8px;z-index:5;width:30px;height:30px;display:inline-flex;align-items:center;justify-content:center;padding:0;background:#12121cd9;-webkit-backdrop-filter:blur(8px);backdrop-filter:blur(8px);color:#d1fae5;border:1px solid rgba(52,211,153,.45);border-radius:50%;cursor:pointer;opacity:.85;transition:all .2s cubic-bezier(.34,1.56,.64,1);box-shadow:0 2px 8px #00000080}.rts-media-btn-download i{font-size:12px;color:#34d399}.rts-media-btn-download:hover{opacity:1;background:#10b981e6;border-color:#34d399;transform:translateY(-1px) scale(1.08);box-shadow:0 4px 12px #34d39973}.rts-media-btn-download:hover i{color:#fff}.rts-media-btn-download:active{transform:translateY(0) scale(.96)}.rts-media-btn-download.rts-downloading{cursor:progress;opacity:1}.rts-media-image{width:100%;height:100%;max-height:280px;object-fit:cover;display:block;opacity:0;transition:opacity .25s ease-out,transform .2s ease;cursor:pointer}.rts-media-image.rts-media-image-loaded{opacity:1}.rts-media-image:hover{transform:scale(1.02)}.rts-media-grid-1 .rts-media-image{height:auto;max-height:400px;object-fit:contain}.rts-media-toggle{display:block;width:100%;padding:6px 0;border:none;background:none;color:var(--rts-agent-accent, #8b5cf6);font-size:12px;font-weight:500;cursor:pointer;text-align:center;transition:opacity .15s ease}.rts-media-toggle:hover{opacity:.8}.rts-media-cost{display:flex;justify-content:flex-end;padding-top:4px}\n"] }]
2020
2248
  }], propDecorators: { message: [{
2021
2249
  type: Input
2022
2250
  }], messenger: [{
@@ -2850,7 +3078,7 @@ class MessageComponent {
2850
3078
  /* eslint-enable */
2851
3079
  }
2852
3080
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.4", ngImport: i0, type: MessageComponent, deps: [{ token: MessageWidgetsService }, { token: i0.ComponentFactoryResolver }, { token: TimeFormatHelperService }], target: i0.ɵɵFactoryTarget.Component }); }
2853
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.4", type: MessageComponent, isStandalone: true, selector: "rtsee-message", inputs: { chat: "chat", message: "message", member: "member", messenger: "messenger" }, viewQueries: [{ propertyName: "widgetHost", first: true, predicate: WidgetDirective, descendants: true }], usesOnChanges: true, ngImport: i0, template: "<div class=\"rts-message-container\" #rtsMessageContainer\n [ngClass]=\"{'rts-message-from-me': message.from.id === messenger.clientId}\"\n>\n <div class=\"rts-messenger-message\">\n @if (message.widget) {\n <ng-template widgetHost></ng-template>\n }\n @if (!message.widget) {\n <p class=\"rts-messenger-message-text\"\n >{{ message.text }}</p>\n }\n <div class=\"rts-messenger-message-info\" #rtsMessageInfo>\n <rtsee-message-time-and-status [message]=\"message\"\n [inChat]=\"true\"\n [hideStatus]=\"message.from.id !== messenger.clientId\"\n ></rtsee-message-time-and-status>\n </div>\n </div>\n</div>\n", dependencies: [{ kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "component", type: MessageTimeAndStatusComponent, selector: "rtsee-message-time-and-status", inputs: ["message", "hideStatus", "inChat"] }, { kind: "directive", type: WidgetDirective, selector: "[widgetHost]" }] }); }
3081
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.4", type: MessageComponent, isStandalone: true, selector: "rtsee-message", inputs: { chat: "chat", message: "message", member: "member", messenger: "messenger" }, viewQueries: [{ propertyName: "widgetHost", first: true, predicate: WidgetDirective, descendants: true }], usesOnChanges: true, ngImport: i0, template: "<div class=\"rts-message-container\" #rtsMessageContainer\n [ngClass]=\"{\n 'rts-message-from-me': message.from.id === messenger.clientId,\n 'rts-message-is-widget': !!message.widget\n }\">\n <div class=\"rts-messenger-message\">\n @if (message.widget) {\n <ng-template widgetHost></ng-template>\n }\n @if (!message.widget) {\n <p class=\"rts-messenger-message-text\"\n >{{ message.text }}</p>\n }\n <div class=\"rts-messenger-message-info\" #rtsMessageInfo>\n <rtsee-message-time-and-status [message]=\"message\"\n [inChat]=\"true\"\n [hideStatus]=\"message.from.id !== messenger.clientId\"\n ></rtsee-message-time-and-status>\n </div>\n </div>\n</div>\n", dependencies: [{ kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "component", type: MessageTimeAndStatusComponent, selector: "rtsee-message-time-and-status", inputs: ["message", "hideStatus", "inChat"] }, { kind: "directive", type: WidgetDirective, selector: "[widgetHost]" }] }); }
2854
3082
  }
2855
3083
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.4", ngImport: i0, type: MessageComponent, decorators: [{
2856
3084
  type: Component,
@@ -2859,7 +3087,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.4", ngImpor
2859
3087
  MessageTimeAndStatusComponent,
2860
3088
  WidgetDirective,
2861
3089
  RatingComponent,
2862
- ], standalone: true, template: "<div class=\"rts-message-container\" #rtsMessageContainer\n [ngClass]=\"{'rts-message-from-me': message.from.id === messenger.clientId}\"\n>\n <div class=\"rts-messenger-message\">\n @if (message.widget) {\n <ng-template widgetHost></ng-template>\n }\n @if (!message.widget) {\n <p class=\"rts-messenger-message-text\"\n >{{ message.text }}</p>\n }\n <div class=\"rts-messenger-message-info\" #rtsMessageInfo>\n <rtsee-message-time-and-status [message]=\"message\"\n [inChat]=\"true\"\n [hideStatus]=\"message.from.id !== messenger.clientId\"\n ></rtsee-message-time-and-status>\n </div>\n </div>\n</div>\n" }]
3090
+ ], standalone: true, template: "<div class=\"rts-message-container\" #rtsMessageContainer\n [ngClass]=\"{\n 'rts-message-from-me': message.from.id === messenger.clientId,\n 'rts-message-is-widget': !!message.widget\n }\">\n <div class=\"rts-messenger-message\">\n @if (message.widget) {\n <ng-template widgetHost></ng-template>\n }\n @if (!message.widget) {\n <p class=\"rts-messenger-message-text\"\n >{{ message.text }}</p>\n }\n <div class=\"rts-messenger-message-info\" #rtsMessageInfo>\n <rtsee-message-time-and-status [message]=\"message\"\n [inChat]=\"true\"\n [hideStatus]=\"message.from.id !== messenger.clientId\"\n ></rtsee-message-time-and-status>\n </div>\n </div>\n</div>\n" }]
2863
3091
  }], ctorParameters: () => [{ type: MessageWidgetsService }, { type: i0.ComponentFactoryResolver }, { type: TimeFormatHelperService }], propDecorators: { chat: [{
2864
3092
  type: Input
2865
3093
  }], message: [{
@@ -2881,12 +3109,24 @@ class MessagesListComponent {
2881
3109
  this.messagesGroups = [];
2882
3110
  this.firstGroupedMessage = null;
2883
3111
  this.lastGroupedMessage = null;
3112
+ this.shouldStickToBottom = true;
3113
+ this.resizeObserver = null;
3114
+ this.onMediaCapture = null;
3115
+ this.initialScrollInterval = null;
2884
3116
  this.RTSeeChatTypes = RTSeeChatTypes;
2885
- this.bindScrollFunction = this.scrollToBottom.bind(this);
2886
- this.bindedGroupFunction = this.groupMessages.bind(this);
3117
+ this.bindScrollFunction = () => {
3118
+ this.shouldStickToBottom = true;
3119
+ this.scrollToBottom();
3120
+ };
3121
+ this.bindedGroupFunction = () => {
3122
+ this.groupMessages();
3123
+ if (this.shouldStickToBottom) {
3124
+ this.scrollToBottom();
3125
+ }
3126
+ };
2887
3127
  }
2888
3128
  onWindowScroll(event) {
2889
- // Get the vertical scroll position
3129
+ this.checkScrollPosition();
2890
3130
  const verticalOffset = window.pageYOffset ||
2891
3131
  this.document.documentElement.scrollTop ||
2892
3132
  this.document.body.scrollTop ||
@@ -2894,28 +3134,76 @@ class MessagesListComponent {
2894
3134
  if (window.innerWidth >= COMMON_CONSTANTS.mobileBreakpoint) {
2895
3135
  return;
2896
3136
  }
2897
- // You can add logic here, e.g., show a 'scroll to top' button
2898
3137
  if (verticalOffset < 300) {
2899
3138
  this.onScrolledToTop();
2900
3139
  }
2901
3140
  }
3141
+ onWindowResize() {
3142
+ if (this.shouldStickToBottom) {
3143
+ this.scrollToBottom();
3144
+ }
3145
+ }
2902
3146
  ngOnInit() {
2903
- this.chat.on(RTSeeChatEvents.MESSAGES_LIST_CHANGED, this.bindedGroupFunction);
2904
- this.chat.on(RTSeeChatEvents.MESSAGE_ADDED, this.bindScrollFunction);
3147
+ this.bindChatEvents(this.chat);
2905
3148
  this.groupMessages();
2906
3149
  }
3150
+ ngOnChanges(changes) {
3151
+ if (changes['chat'] && !changes['chat'].firstChange) {
3152
+ const prevChat = changes['chat'].previousValue;
3153
+ this.unbindChatEvents(prevChat);
3154
+ this.bindChatEvents(this.chat);
3155
+ this.groupMessages();
3156
+ this.enforceInitialScroll();
3157
+ }
3158
+ }
2907
3159
  ngAfterViewInit() {
2908
- if (this.chat.isStandalone) {
3160
+ if (this.chat?.isStandalone) {
2909
3161
  return;
2910
3162
  }
2911
- this.scrollToBottom();
3163
+ this.enforceInitialScroll();
3164
+ // Observe size changes of the messages list content (e.g. media widgets expanding/collapsing, images loading)
3165
+ if (typeof ResizeObserver !== 'undefined' && this.contentBlock?.nativeElement) {
3166
+ this.resizeObserver = new ResizeObserver(() => {
3167
+ if (this.shouldStickToBottom) {
3168
+ this.scrollToBottom();
3169
+ }
3170
+ });
3171
+ this.resizeObserver.observe(this.contentBlock.nativeElement);
3172
+ }
3173
+ // Capture media load/error events on all nested img and video elements
3174
+ if (this.scrollContainer?.nativeElement) {
3175
+ this.onMediaCapture = () => {
3176
+ if (this.shouldStickToBottom) {
3177
+ this.scrollToBottom();
3178
+ }
3179
+ };
3180
+ this.scrollContainer.nativeElement.addEventListener('load', this.onMediaCapture, true);
3181
+ this.scrollContainer.nativeElement.addEventListener('error', this.onMediaCapture, true);
3182
+ }
2912
3183
  }
2913
3184
  onMessageSent() {
3185
+ this.shouldStickToBottom = true;
2914
3186
  this.scrollToBottom();
2915
3187
  }
2916
3188
  ngOnDestroy() {
2917
- this.chat.off(RTSeeChatEvents.MESSAGE_ADDED, this.bindScrollFunction);
2918
- this.chat.off(RTSeeChatEvents.MESSAGES_LIST_CHANGED, this.bindedGroupFunction);
3189
+ this.unbindChatEvents(this.chat);
3190
+ if (this.initialScrollInterval) {
3191
+ clearInterval(this.initialScrollInterval);
3192
+ this.initialScrollInterval = null;
3193
+ }
3194
+ if (this.resizeObserver) {
3195
+ this.resizeObserver.disconnect();
3196
+ this.resizeObserver = null;
3197
+ }
3198
+ if (this.scrollContainer?.nativeElement && this.onMediaCapture) {
3199
+ this.scrollContainer.nativeElement.removeEventListener('load', this.onMediaCapture, true);
3200
+ this.scrollContainer.nativeElement.removeEventListener('error', this.onMediaCapture, true);
3201
+ this.onMediaCapture = null;
3202
+ }
3203
+ }
3204
+ onContainerScroll(event) {
3205
+ this.checkScrollPosition();
3206
+ this.onScroll();
2919
3207
  }
2920
3208
  onScrolledToTop() {
2921
3209
  if (this.chat.messagesLoadInProgress || this.chat.firstMessageLoaded) {
@@ -2929,30 +3217,99 @@ class MessagesListComponent {
2929
3217
  }
2930
3218
  this.chat.loadRemoteMessages('front');
2931
3219
  }
2932
- scrollToBottom() {
2933
- this.groupMessages();
2934
- if (this.chat.isStandalone) {
3220
+ scrollToBottom(immediate = false) {
3221
+ if (this.chat?.isStandalone) {
2935
3222
  return;
2936
3223
  }
2937
- setTimeout(() => {
2938
- if (window.innerWidth < COMMON_CONSTANTS.mobileBreakpoint) {
2939
- this.scroller.scrollToPosition([0, document.body.scrollHeight]);
2940
- }
2941
- if (!this.scrollContainer?.nativeElement?.scrollHeight) {
2942
- return;
3224
+ const performScroll = () => {
3225
+ if (this.bottomAnchor?.nativeElement) {
3226
+ try {
3227
+ this.bottomAnchor.nativeElement.scrollIntoView({
3228
+ block: 'end',
3229
+ inline: 'nearest',
3230
+ behavior: 'instant',
3231
+ });
3232
+ }
3233
+ catch {
3234
+ // ignore
3235
+ }
2943
3236
  }
2944
- try {
2945
- this.scrollContainer.nativeElement.scrollTop =
2946
- this.scrollContainer.nativeElement.scrollHeight;
3237
+ if (window.innerWidth < COMMON_CONSTANTS.mobileBreakpoint) {
3238
+ const target = Math.max(document.documentElement.scrollHeight, document.body.scrollHeight);
3239
+ window.scrollTo({ top: target, behavior: 'instant' });
3240
+ this.scroller.scrollToPosition([0, target]);
2947
3241
  }
2948
- catch (err) {
2949
- console.log('Failed to Scroll');
3242
+ if (this.scrollContainer?.nativeElement) {
3243
+ const el = this.scrollContainer.nativeElement;
3244
+ el.scrollTop = el.scrollHeight;
2950
3245
  }
2951
- }, 0);
3246
+ };
3247
+ performScroll();
3248
+ if (!immediate) {
3249
+ requestAnimationFrame(() => {
3250
+ performScroll();
3251
+ });
3252
+ setTimeout(() => {
3253
+ if (this.shouldStickToBottom) {
3254
+ performScroll();
3255
+ }
3256
+ }, 50);
3257
+ }
2952
3258
  }
2953
3259
  formatDate(message) {
2954
3260
  return this.timeFormatService.formatDate(message.createdAt);
2955
3261
  }
3262
+ checkScrollPosition() {
3263
+ this.shouldStickToBottom = this.isAtBottom();
3264
+ }
3265
+ isAtBottom() {
3266
+ const threshold = 150; // px threshold from bottom
3267
+ if (window.innerWidth < COMMON_CONSTANTS.mobileBreakpoint) {
3268
+ const scrollHeight = Math.max(document.documentElement.scrollHeight, document.body.scrollHeight);
3269
+ const scrollTop = window.pageYOffset ||
3270
+ document.documentElement.scrollTop ||
3271
+ document.body.scrollTop ||
3272
+ 0;
3273
+ const clientHeight = window.innerHeight;
3274
+ return scrollHeight - (scrollTop + clientHeight) <= threshold;
3275
+ }
3276
+ else {
3277
+ const el = this.scrollContainer?.nativeElement;
3278
+ if (!el)
3279
+ return true;
3280
+ return el.scrollHeight - (el.scrollTop + el.clientHeight) <= threshold;
3281
+ }
3282
+ }
3283
+ enforceInitialScroll() {
3284
+ this.shouldStickToBottom = true;
3285
+ this.scrollToBottom();
3286
+ if (this.initialScrollInterval) {
3287
+ clearInterval(this.initialScrollInterval);
3288
+ }
3289
+ let ticks = 0;
3290
+ this.initialScrollInterval = setInterval(() => {
3291
+ ticks++;
3292
+ if (this.shouldStickToBottom) {
3293
+ this.scrollToBottom(true);
3294
+ }
3295
+ if (ticks >= 20 || !this.shouldStickToBottom) {
3296
+ clearInterval(this.initialScrollInterval);
3297
+ this.initialScrollInterval = null;
3298
+ }
3299
+ }, 100);
3300
+ }
3301
+ bindChatEvents(chat) {
3302
+ if (!chat)
3303
+ return;
3304
+ chat.on(RTSeeChatEvents.MESSAGES_LIST_CHANGED, this.bindedGroupFunction);
3305
+ chat.on(RTSeeChatEvents.MESSAGE_ADDED, this.bindScrollFunction);
3306
+ }
3307
+ unbindChatEvents(chat) {
3308
+ if (!chat)
3309
+ return;
3310
+ chat.off(RTSeeChatEvents.MESSAGE_ADDED, this.bindScrollFunction);
3311
+ chat.off(RTSeeChatEvents.MESSAGES_LIST_CHANGED, this.bindedGroupFunction);
3312
+ }
2956
3313
  groupMessages() {
2957
3314
  if (!this.chat || !this.chat.messages.length) {
2958
3315
  this.messagesGroups.length = 0;
@@ -2999,11 +3356,11 @@ class MessagesListComponent {
2999
3356
  this.lastGroupedMessage = this.chat.messages[this.chat.messages.length - 1];
3000
3357
  }
3001
3358
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.4", ngImport: i0, type: MessagesListComponent, deps: [{ token: TimeFormatHelperService }, { token: i2$1.ViewportScroller }, { token: DOCUMENT }], target: i0.ɵɵFactoryTarget.Component }); }
3002
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.4", type: MessagesListComponent, isStandalone: true, selector: "rtsee-messages-list", inputs: { chat: "chat", messenger: "messenger" }, host: { listeners: { "window:scroll": "onWindowScroll($event)" } }, viewQueries: [{ propertyName: "scrollContainer", first: true, predicate: ["scrollBlock"], descendants: true }], ngImport: i0, template: "<div class=\"rts-messages-list\"\n #scrollBlock\n infiniteScroll\n [infiniteScrollUpDistance]=\"2\"\n [infiniteScrollDistance]=\"2\"\n [infiniteScrollThrottle]=\"50\"\n [scrollWindow]=\"false\"\n (scrolledUp)=\"onScrolledToTop()\"\n (scroll)=\"onScroll()\"\n [style.padding-bottom]=\"messenger.chatInputHeight + 'px'\"\n>\n @for (messageGroup of messagesGroups; track messageGroup.id) {\n <div class=\"rts-messenger-message-wrapper\" [ngClass]=\"{\n 'rts-messenger-message-wrapper-with-sender': messageGroup.senderVisible\n }\">\n <div class=\"rts-messenger-message-inset\">\n <img [src]=\"messageGroup.sender.imageUrl\"\n class=\"rts-messenger-message-sender-image\"\n >\n </div>\n\n <div class=\"rts-messenger-message-sender-name\">\n <p>{{messageGroup.sender.name}}</p>\n </div>\n\n @for (message of messageGroup.messages; track message.id) {\n <rtsee-message [message]=\"message\"\n [messenger]=\"messenger\"\n [chat]=\"chat\"\n ></rtsee-message>\n }\n </div>\n }\n</div>\n", dependencies: [{ kind: "directive", type: InfiniteScrollDirective, selector: "[infiniteScroll], [infinite-scroll], [data-infinite-scroll]", inputs: ["infiniteScrollDistance", "infiniteScrollUpDistance", "infiniteScrollThrottle", "infiniteScrollDisabled", "infiniteScrollContainer", "scrollWindow", "immediateCheck", "horizontal", "alwaysCallback", "fromRoot"], outputs: ["scrolled", "scrolledUp"] }, { kind: "component", type: MessageComponent, selector: "rtsee-message", inputs: ["chat", "message", "member", "messenger"] }, { kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }] }); }
3359
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.4", type: MessagesListComponent, isStandalone: true, selector: "rtsee-messages-list", inputs: { chat: "chat", messenger: "messenger" }, host: { listeners: { "window:scroll": "onWindowScroll($event)", "window:resize": "onWindowResize()" } }, viewQueries: [{ propertyName: "scrollContainer", first: true, predicate: ["scrollBlock"], descendants: true }, { propertyName: "contentBlock", first: true, predicate: ["contentBlock"], descendants: true }, { propertyName: "bottomAnchor", first: true, predicate: ["bottomAnchor"], descendants: true }], usesOnChanges: true, ngImport: i0, template: "<div class=\"rts-messages-list\"\n #scrollBlock\n infiniteScroll\n [infiniteScrollUpDistance]=\"2\"\n [infiniteScrollDistance]=\"2\"\n [infiniteScrollThrottle]=\"50\"\n [scrollWindow]=\"false\"\n (scrolledUp)=\"onScrolledToTop()\"\n (scroll)=\"onContainerScroll($event)\"\n [style.padding-bottom]=\"messenger.chatInputHeight + 'px'\"\n>\n <div class=\"rts-messages-list-inner\" #contentBlock>\n @for (messageGroup of messagesGroups; track messageGroup.id) {\n <div class=\"rts-messenger-message-wrapper\" [ngClass]=\"{\n 'rts-messenger-message-wrapper-with-sender': messageGroup.senderVisible\n }\">\n <div class=\"rts-messenger-message-inset\">\n <img [src]=\"messageGroup.sender.imageUrl\"\n class=\"rts-messenger-message-sender-image\"\n >\n </div>\n\n <div class=\"rts-messenger-message-sender-name\">\n <p>{{messageGroup.sender.name}}</p>\n </div>\n\n @for (message of messageGroup.messages; track message.id) {\n <rtsee-message [message]=\"message\"\n [messenger]=\"messenger\"\n [chat]=\"chat\"\n ></rtsee-message>\n }\n </div>\n }\n <div class=\"rts-messages-list-bottom-anchor\" #bottomAnchor></div>\n </div>\n</div>\n", dependencies: [{ kind: "directive", type: InfiniteScrollDirective, selector: "[infiniteScroll], [infinite-scroll], [data-infinite-scroll]", inputs: ["infiniteScrollDistance", "infiniteScrollUpDistance", "infiniteScrollThrottle", "infiniteScrollDisabled", "infiniteScrollContainer", "scrollWindow", "immediateCheck", "horizontal", "alwaysCallback", "fromRoot"], outputs: ["scrolled", "scrolledUp"] }, { kind: "component", type: MessageComponent, selector: "rtsee-message", inputs: ["chat", "message", "member", "messenger"] }, { kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }] }); }
3003
3360
  }
3004
3361
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.4", ngImport: i0, type: MessagesListComponent, decorators: [{
3005
3362
  type: Component,
3006
- args: [{ selector: 'rtsee-messages-list', imports: [InfiniteScrollDirective, MessageComponent, NgClass], standalone: true, template: "<div class=\"rts-messages-list\"\n #scrollBlock\n infiniteScroll\n [infiniteScrollUpDistance]=\"2\"\n [infiniteScrollDistance]=\"2\"\n [infiniteScrollThrottle]=\"50\"\n [scrollWindow]=\"false\"\n (scrolledUp)=\"onScrolledToTop()\"\n (scroll)=\"onScroll()\"\n [style.padding-bottom]=\"messenger.chatInputHeight + 'px'\"\n>\n @for (messageGroup of messagesGroups; track messageGroup.id) {\n <div class=\"rts-messenger-message-wrapper\" [ngClass]=\"{\n 'rts-messenger-message-wrapper-with-sender': messageGroup.senderVisible\n }\">\n <div class=\"rts-messenger-message-inset\">\n <img [src]=\"messageGroup.sender.imageUrl\"\n class=\"rts-messenger-message-sender-image\"\n >\n </div>\n\n <div class=\"rts-messenger-message-sender-name\">\n <p>{{messageGroup.sender.name}}</p>\n </div>\n\n @for (message of messageGroup.messages; track message.id) {\n <rtsee-message [message]=\"message\"\n [messenger]=\"messenger\"\n [chat]=\"chat\"\n ></rtsee-message>\n }\n </div>\n }\n</div>\n" }]
3363
+ args: [{ selector: 'rtsee-messages-list', imports: [InfiniteScrollDirective, MessageComponent, NgClass], standalone: true, template: "<div class=\"rts-messages-list\"\n #scrollBlock\n infiniteScroll\n [infiniteScrollUpDistance]=\"2\"\n [infiniteScrollDistance]=\"2\"\n [infiniteScrollThrottle]=\"50\"\n [scrollWindow]=\"false\"\n (scrolledUp)=\"onScrolledToTop()\"\n (scroll)=\"onContainerScroll($event)\"\n [style.padding-bottom]=\"messenger.chatInputHeight + 'px'\"\n>\n <div class=\"rts-messages-list-inner\" #contentBlock>\n @for (messageGroup of messagesGroups; track messageGroup.id) {\n <div class=\"rts-messenger-message-wrapper\" [ngClass]=\"{\n 'rts-messenger-message-wrapper-with-sender': messageGroup.senderVisible\n }\">\n <div class=\"rts-messenger-message-inset\">\n <img [src]=\"messageGroup.sender.imageUrl\"\n class=\"rts-messenger-message-sender-image\"\n >\n </div>\n\n <div class=\"rts-messenger-message-sender-name\">\n <p>{{messageGroup.sender.name}}</p>\n </div>\n\n @for (message of messageGroup.messages; track message.id) {\n <rtsee-message [message]=\"message\"\n [messenger]=\"messenger\"\n [chat]=\"chat\"\n ></rtsee-message>\n }\n </div>\n }\n <div class=\"rts-messages-list-bottom-anchor\" #bottomAnchor></div>\n </div>\n</div>\n" }]
3007
3364
  }], ctorParameters: () => [{ type: TimeFormatHelperService }, { type: i2$1.ViewportScroller }, { type: Document, decorators: [{
3008
3365
  type: Inject,
3009
3366
  args: [DOCUMENT]
@@ -3014,9 +3371,18 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.4", ngImpor
3014
3371
  }], scrollContainer: [{
3015
3372
  type: ViewChild,
3016
3373
  args: ['scrollBlock']
3374
+ }], contentBlock: [{
3375
+ type: ViewChild,
3376
+ args: ['contentBlock']
3377
+ }], bottomAnchor: [{
3378
+ type: ViewChild,
3379
+ args: ['bottomAnchor']
3017
3380
  }], onWindowScroll: [{
3018
3381
  type: HostListener,
3019
3382
  args: ['window:scroll', ['$event']]
3383
+ }], onWindowResize: [{
3384
+ type: HostListener,
3385
+ args: ['window:resize']
3020
3386
  }] } });
3021
3387
 
3022
3388
  class ChatMemberSuggestionsComponent {
@@ -3246,6 +3612,12 @@ class ChatInputComponent {
3246
3612
  get isGroupChat() {
3247
3613
  return this.chat.type !== RTSeeChatTypes.INDIVIDUAL;
3248
3614
  }
3615
+ startVideoCall() {
3616
+ if (!this.chat) {
3617
+ return;
3618
+ }
3619
+ void this.messenger.initializeVideoCall(this.chat);
3620
+ }
3249
3621
  sendMessage() {
3250
3622
  const text = this.sendMessageForm.get('message')?.value || '';
3251
3623
  const trimmedText = text.trim();
@@ -3420,11 +3792,11 @@ class ChatInputComponent {
3420
3792
  }, 0);
3421
3793
  }
3422
3794
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.4", ngImport: i0, type: ChatInputComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
3423
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.4", type: ChatInputComponent, isStandalone: true, selector: "rtsee-chat-input", inputs: { messenger: "messenger", chat: "chat" }, viewQueries: [{ propertyName: "chatInput", first: true, predicate: ["chatInput"], descendants: true }, { propertyName: "messageTextarea", first: true, predicate: ["messageTextarea"], descendants: true }], ngImport: i0, template: "<div #chatInput\n class=\"rts-chat-input\"\n [class.rts-chat-input-streaming]=\"chat.agentStreaming\"\n>\n <rtsee-tool-use-request [chat]=\"chat\"></rtsee-tool-use-request>\n <rtsee-chat-member-suggestions\n [suggestions]=\"autocomplete.suggestions\"\n [selectedIndex]=\"autocomplete.selectedIndex\"\n [visible]=\"autocomplete.visible\"\n (memberSelected)=\"onMemberSelected($event)\"\n ></rtsee-chat-member-suggestions>\n <rtsee-workflow-suggestions\n [suggestions]=\"workflowAutocomplete.suggestions\"\n [selectedIndex]=\"workflowAutocomplete.selectedIndex\"\n [visible]=\"workflowAutocomplete.visible\"\n (workflowSelected)=\"onWorkflowSelected($event)\"\n ></rtsee-workflow-suggestions>\n @if (agentAccessDenied) {\n <div class=\"rts-agent-access-denied-banner\">\n <span class=\"rts-agent-access-denied-icon\">\uD83D\uDD12</span>\n <span class=\"rts-agent-access-denied-text\">You don't have permission to call agents in this chat.</span>\n <button class=\"rts-agent-access-denied-dismiss\" (click)=\"dismissAccessDenied()\">Got it!</button>\n </div>\n }\n <div class=\"rts-send-message\">\n <form [formGroup]=\"sendMessageForm\" class=\"rts-chat-input-form\">\n <textarea #messageTextarea\n [invalid]=\"false\"\n [autoResize]=\"true\"\n (keydown)=\"onKeyDown($event)\"\n (keyup)=\"onKeyUp($event)\"\n (input)=\"onInput()\"\n [attr.disabled]=\"chat.agentStreaming ? '' : null\"\n formControlName=\"message\"\n pTextarea\n [placeholder]=\"chat.agentStreaming ? 'Agent is responding...' : 'Type a message...'\"\n class=\"rts-chat-input-field\"\n rows=\"1\"\n cols=\"30\"\n ></textarea>\n </form>\n </div>\n @if (chat.agentStreaming) {\n <button (click)=\"stopAgent()\"\n class=\"rts-send-message-button rts-send-message-button-stop\"\n type=\"button\"\n title=\"Stop agent response\"\n >\n <span class=\"pi pi-stop-circle\"></span>\n </button>\n } @else {\n <button (click)=\"sendMessage()\"\n class=\"rts-send-message-button\"\n type=\"submit\"\n >\n <span class=\"pi pi-send\"></span>\n </button>\n }\n</div>\n", dependencies: [{ kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i1.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "directive", type: Textarea, selector: "[pTextarea], [pInputTextarea]", inputs: ["autoResize", "pSize", "variant", "fluid", "invalid"], outputs: ["onResize"] }, { kind: "component", type: ChatMemberSuggestionsComponent, selector: "rtsee-chat-member-suggestions", inputs: ["suggestions", "selectedIndex", "visible"], outputs: ["memberSelected"] }, { kind: "component", type: ToolUseRequestComponent, selector: "rtsee-tool-use-request", inputs: ["chat"] }, { kind: "component", type: WorkflowSuggestionsComponent, selector: "rtsee-workflow-suggestions", inputs: ["suggestions", "selectedIndex", "visible"], outputs: ["workflowSelected"] }] }); }
3795
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.4", type: ChatInputComponent, isStandalone: true, selector: "rtsee-chat-input", inputs: { messenger: "messenger", chat: "chat" }, viewQueries: [{ propertyName: "chatInput", first: true, predicate: ["chatInput"], descendants: true }, { propertyName: "messageTextarea", first: true, predicate: ["messageTextarea"], descendants: true }], ngImport: i0, template: "<div #chatInput\n class=\"rts-chat-input\"\n [class.rts-chat-input-streaming]=\"chat.agentStreaming\"\n>\n <rtsee-tool-use-request [chat]=\"chat\"></rtsee-tool-use-request>\n <rtsee-chat-member-suggestions\n [suggestions]=\"autocomplete.suggestions\"\n [selectedIndex]=\"autocomplete.selectedIndex\"\n [visible]=\"autocomplete.visible\"\n (memberSelected)=\"onMemberSelected($event)\"\n ></rtsee-chat-member-suggestions>\n <rtsee-workflow-suggestions\n [suggestions]=\"workflowAutocomplete.suggestions\"\n [selectedIndex]=\"workflowAutocomplete.selectedIndex\"\n [visible]=\"workflowAutocomplete.visible\"\n (workflowSelected)=\"onWorkflowSelected($event)\"\n ></rtsee-workflow-suggestions>\n @if (agentAccessDenied) {\n <div class=\"rts-agent-access-denied-banner\">\n <span class=\"rts-agent-access-denied-icon\">\uD83D\uDD12</span>\n <span class=\"rts-agent-access-denied-text\">You don't have permission to call agents in this chat.</span>\n <button class=\"rts-agent-access-denied-dismiss\" (click)=\"dismissAccessDenied()\">Got it!</button>\n </div>\n }\n <button (click)=\"startVideoCall()\"\n class=\"rts-start-call-button\"\n type=\"button\"\n title=\"Start Video Call\"\n >\n <span class=\"material-icons-outlined\">videocam</span>\n </button>\n <div class=\"rts-send-message\">\n <form [formGroup]=\"sendMessageForm\" class=\"rts-chat-input-form\">\n <textarea #messageTextarea\n [invalid]=\"false\"\n [autoResize]=\"true\"\n (keydown)=\"onKeyDown($event)\"\n (keyup)=\"onKeyUp($event)\"\n (input)=\"onInput()\"\n [attr.disabled]=\"chat.agentStreaming ? '' : null\"\n formControlName=\"message\"\n pTextarea\n [placeholder]=\"chat.agentStreaming ? 'Agent is responding...' : 'Type a message...'\"\n class=\"rts-chat-input-field\"\n rows=\"1\"\n cols=\"30\"\n ></textarea>\n </form>\n </div>\n @if (chat.agentStreaming) {\n <button (click)=\"stopAgent()\"\n class=\"rts-send-message-button rts-send-message-button-stop\"\n type=\"button\"\n title=\"Stop agent response\"\n >\n <span class=\"pi pi-stop-circle\"></span>\n </button>\n } @else {\n <button (click)=\"sendMessage()\"\n class=\"rts-send-message-button\"\n type=\"submit\"\n >\n <span class=\"pi pi-send\"></span>\n </button>\n }\n</div>\n", dependencies: [{ kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i1.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "directive", type: Textarea, selector: "[pTextarea], [pInputTextarea]", inputs: ["autoResize", "pSize", "variant", "fluid", "invalid"], outputs: ["onResize"] }, { kind: "component", type: ChatMemberSuggestionsComponent, selector: "rtsee-chat-member-suggestions", inputs: ["suggestions", "selectedIndex", "visible"], outputs: ["memberSelected"] }, { kind: "component", type: ToolUseRequestComponent, selector: "rtsee-tool-use-request", inputs: ["chat"] }, { kind: "component", type: WorkflowSuggestionsComponent, selector: "rtsee-workflow-suggestions", inputs: ["suggestions", "selectedIndex", "visible"], outputs: ["workflowSelected"] }] }); }
3424
3796
  }
3425
3797
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.4", ngImport: i0, type: ChatInputComponent, decorators: [{
3426
3798
  type: Component,
3427
- args: [{ selector: 'rtsee-chat-input', imports: [ReactiveFormsModule, Textarea, ChatMemberSuggestionsComponent, ToolUseRequestComponent, WorkflowSuggestionsComponent], standalone: true, template: "<div #chatInput\n class=\"rts-chat-input\"\n [class.rts-chat-input-streaming]=\"chat.agentStreaming\"\n>\n <rtsee-tool-use-request [chat]=\"chat\"></rtsee-tool-use-request>\n <rtsee-chat-member-suggestions\n [suggestions]=\"autocomplete.suggestions\"\n [selectedIndex]=\"autocomplete.selectedIndex\"\n [visible]=\"autocomplete.visible\"\n (memberSelected)=\"onMemberSelected($event)\"\n ></rtsee-chat-member-suggestions>\n <rtsee-workflow-suggestions\n [suggestions]=\"workflowAutocomplete.suggestions\"\n [selectedIndex]=\"workflowAutocomplete.selectedIndex\"\n [visible]=\"workflowAutocomplete.visible\"\n (workflowSelected)=\"onWorkflowSelected($event)\"\n ></rtsee-workflow-suggestions>\n @if (agentAccessDenied) {\n <div class=\"rts-agent-access-denied-banner\">\n <span class=\"rts-agent-access-denied-icon\">\uD83D\uDD12</span>\n <span class=\"rts-agent-access-denied-text\">You don't have permission to call agents in this chat.</span>\n <button class=\"rts-agent-access-denied-dismiss\" (click)=\"dismissAccessDenied()\">Got it!</button>\n </div>\n }\n <div class=\"rts-send-message\">\n <form [formGroup]=\"sendMessageForm\" class=\"rts-chat-input-form\">\n <textarea #messageTextarea\n [invalid]=\"false\"\n [autoResize]=\"true\"\n (keydown)=\"onKeyDown($event)\"\n (keyup)=\"onKeyUp($event)\"\n (input)=\"onInput()\"\n [attr.disabled]=\"chat.agentStreaming ? '' : null\"\n formControlName=\"message\"\n pTextarea\n [placeholder]=\"chat.agentStreaming ? 'Agent is responding...' : 'Type a message...'\"\n class=\"rts-chat-input-field\"\n rows=\"1\"\n cols=\"30\"\n ></textarea>\n </form>\n </div>\n @if (chat.agentStreaming) {\n <button (click)=\"stopAgent()\"\n class=\"rts-send-message-button rts-send-message-button-stop\"\n type=\"button\"\n title=\"Stop agent response\"\n >\n <span class=\"pi pi-stop-circle\"></span>\n </button>\n } @else {\n <button (click)=\"sendMessage()\"\n class=\"rts-send-message-button\"\n type=\"submit\"\n >\n <span class=\"pi pi-send\"></span>\n </button>\n }\n</div>\n" }]
3799
+ args: [{ selector: 'rtsee-chat-input', imports: [ReactiveFormsModule, Textarea, ChatMemberSuggestionsComponent, ToolUseRequestComponent, WorkflowSuggestionsComponent], standalone: true, template: "<div #chatInput\n class=\"rts-chat-input\"\n [class.rts-chat-input-streaming]=\"chat.agentStreaming\"\n>\n <rtsee-tool-use-request [chat]=\"chat\"></rtsee-tool-use-request>\n <rtsee-chat-member-suggestions\n [suggestions]=\"autocomplete.suggestions\"\n [selectedIndex]=\"autocomplete.selectedIndex\"\n [visible]=\"autocomplete.visible\"\n (memberSelected)=\"onMemberSelected($event)\"\n ></rtsee-chat-member-suggestions>\n <rtsee-workflow-suggestions\n [suggestions]=\"workflowAutocomplete.suggestions\"\n [selectedIndex]=\"workflowAutocomplete.selectedIndex\"\n [visible]=\"workflowAutocomplete.visible\"\n (workflowSelected)=\"onWorkflowSelected($event)\"\n ></rtsee-workflow-suggestions>\n @if (agentAccessDenied) {\n <div class=\"rts-agent-access-denied-banner\">\n <span class=\"rts-agent-access-denied-icon\">\uD83D\uDD12</span>\n <span class=\"rts-agent-access-denied-text\">You don't have permission to call agents in this chat.</span>\n <button class=\"rts-agent-access-denied-dismiss\" (click)=\"dismissAccessDenied()\">Got it!</button>\n </div>\n }\n <button (click)=\"startVideoCall()\"\n class=\"rts-start-call-button\"\n type=\"button\"\n title=\"Start Video Call\"\n >\n <span class=\"material-icons-outlined\">videocam</span>\n </button>\n <div class=\"rts-send-message\">\n <form [formGroup]=\"sendMessageForm\" class=\"rts-chat-input-form\">\n <textarea #messageTextarea\n [invalid]=\"false\"\n [autoResize]=\"true\"\n (keydown)=\"onKeyDown($event)\"\n (keyup)=\"onKeyUp($event)\"\n (input)=\"onInput()\"\n [attr.disabled]=\"chat.agentStreaming ? '' : null\"\n formControlName=\"message\"\n pTextarea\n [placeholder]=\"chat.agentStreaming ? 'Agent is responding...' : 'Type a message...'\"\n class=\"rts-chat-input-field\"\n rows=\"1\"\n cols=\"30\"\n ></textarea>\n </form>\n </div>\n @if (chat.agentStreaming) {\n <button (click)=\"stopAgent()\"\n class=\"rts-send-message-button rts-send-message-button-stop\"\n type=\"button\"\n title=\"Stop agent response\"\n >\n <span class=\"pi pi-stop-circle\"></span>\n </button>\n } @else {\n <button (click)=\"sendMessage()\"\n class=\"rts-send-message-button\"\n type=\"submit\"\n >\n <span class=\"pi pi-send\"></span>\n </button>\n }\n</div>\n" }]
3428
3800
  }], propDecorators: { messenger: [{
3429
3801
  type: Input
3430
3802
  }], chat: [{
@@ -3437,6 +3809,75 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.4", ngImpor
3437
3809
  args: ['messageTextarea']
3438
3810
  }] } });
3439
3811
 
3812
+ class InviteLinkComponent {
3813
+ constructor() {
3814
+ this.inviteUrl = '';
3815
+ this.disclaimer = 'Anyone who opens this link can join the chat.';
3816
+ this.copied = false;
3817
+ this.loading = false;
3818
+ }
3819
+ async ngOnInit() {
3820
+ if (!this.inviteUrl && this.chatId && this.messenger) {
3821
+ await this.loadInviteLink();
3822
+ }
3823
+ }
3824
+ async loadInviteLink() {
3825
+ if (!this.chatId || !this.messenger)
3826
+ return;
3827
+ this.loading = true;
3828
+ try {
3829
+ const result = await this.messenger.remoteActions.getChatInvitationLink.send({
3830
+ chatId: this.chatId,
3831
+ });
3832
+ if (result?.invitationLink) {
3833
+ this.inviteUrl = result.invitationLink;
3834
+ }
3835
+ }
3836
+ catch (e) {
3837
+ console.log('Failed to fetch chat invitation link', e);
3838
+ }
3839
+ finally {
3840
+ this.loading = false;
3841
+ }
3842
+ }
3843
+ async copyToClipboard() {
3844
+ try {
3845
+ await navigator.clipboard.writeText(this.inviteUrl);
3846
+ this.copied = true;
3847
+ setTimeout(() => {
3848
+ this.copied = false;
3849
+ }, 2000);
3850
+ }
3851
+ catch {
3852
+ // Fallback for older browsers
3853
+ const textArea = document.createElement('textarea');
3854
+ textArea.value = this.inviteUrl;
3855
+ document.body.appendChild(textArea);
3856
+ textArea.select();
3857
+ document.execCommand('copy');
3858
+ document.body.removeChild(textArea);
3859
+ this.copied = true;
3860
+ setTimeout(() => {
3861
+ this.copied = false;
3862
+ }, 2000);
3863
+ }
3864
+ }
3865
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.4", ngImport: i0, type: InviteLinkComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
3866
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.4", type: InviteLinkComponent, isStandalone: true, selector: "rtsee-invite-link", inputs: { inviteUrl: "inviteUrl", chatId: "chatId", messenger: "messenger", disclaimer: "disclaimer" }, ngImport: i0, template: "<div class=\"rts-invite-link\">\n <div class=\"rts-invite-link-info\">\n <span class=\"rts-invite-link-icon material-icons-outlined\">link</span>\n <div class=\"rts-invite-link-info-text\">\n <h4>Invite Link</h4>\n <p>{{ disclaimer }}</p>\n </div>\n </div>\n\n @if (loading) {\n <div class=\"rts-invite-link-loading\">\n <span class=\"material-icons-outlined rts-spin-icon\">autorenew</span>\n <span>Generating invite link...</span>\n </div>\n } @else {\n <div class=\"rts-invite-link-url-container\">\n <p-inputgroup>\n <input pInputText\n [value]=\"inviteUrl\"\n readonly\n class=\"rts-invite-link-url-input\"\n />\n <p-button [icon]=\"copied ? 'pi pi-check' : 'pi pi-copy'\"\n [severity]=\"copied ? 'success' : 'primary'\"\n (click)=\"copyToClipboard()\"\n [label]=\"copied ? 'Copied!' : 'Copy'\"\n />\n </p-inputgroup>\n </div>\n }\n</div>\n", dependencies: [{ kind: "component", type: Button, selector: "p-button", inputs: ["type", "iconPos", "icon", "badge", "label", "disabled", "loading", "loadingIcon", "raised", "rounded", "text", "plain", "severity", "outlined", "link", "tabindex", "size", "variant", "style", "styleClass", "badgeClass", "badgeSeverity", "ariaLabel", "buttonProps", "autofocus", "fluid"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "directive", type: InputText, selector: "[pInputText]", inputs: ["pSize", "variant", "fluid", "invalid"] }, { kind: "component", type: InputGroup, selector: "p-inputgroup, p-inputGroup, p-input-group", inputs: ["styleClass"] }] }); }
3867
+ }
3868
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.4", ngImport: i0, type: InviteLinkComponent, decorators: [{
3869
+ type: Component,
3870
+ args: [{ selector: 'rtsee-invite-link', standalone: true, imports: [Button, InputText, InputGroup], template: "<div class=\"rts-invite-link\">\n <div class=\"rts-invite-link-info\">\n <span class=\"rts-invite-link-icon material-icons-outlined\">link</span>\n <div class=\"rts-invite-link-info-text\">\n <h4>Invite Link</h4>\n <p>{{ disclaimer }}</p>\n </div>\n </div>\n\n @if (loading) {\n <div class=\"rts-invite-link-loading\">\n <span class=\"material-icons-outlined rts-spin-icon\">autorenew</span>\n <span>Generating invite link...</span>\n </div>\n } @else {\n <div class=\"rts-invite-link-url-container\">\n <p-inputgroup>\n <input pInputText\n [value]=\"inviteUrl\"\n readonly\n class=\"rts-invite-link-url-input\"\n />\n <p-button [icon]=\"copied ? 'pi pi-check' : 'pi pi-copy'\"\n [severity]=\"copied ? 'success' : 'primary'\"\n (click)=\"copyToClipboard()\"\n [label]=\"copied ? 'Copied!' : 'Copy'\"\n />\n </p-inputgroup>\n </div>\n }\n</div>\n" }]
3871
+ }], propDecorators: { inviteUrl: [{
3872
+ type: Input
3873
+ }], chatId: [{
3874
+ type: Input
3875
+ }], messenger: [{
3876
+ type: Input
3877
+ }], disclaimer: [{
3878
+ type: Input
3879
+ }] } });
3880
+
3440
3881
  class ViewportObserverDirective {
3441
3882
  constructor(element) {
3442
3883
  this.element = element;
@@ -3477,6 +3918,26 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.4", ngImpor
3477
3918
  }] } });
3478
3919
 
3479
3920
  class ChatComponent {
3921
+ constructor() {
3922
+ this.showInvite = false;
3923
+ }
3924
+ get isSoleAdmin() {
3925
+ if (!this.chat)
3926
+ return false;
3927
+ const members = this.chat.members || [];
3928
+ if (members.length !== 1)
3929
+ return false;
3930
+ const myClientId = this.messenger?.myPeer?.clientId || this.chat.clientId;
3931
+ const soleMember = members[0];
3932
+ const isMe = soleMember.id === myClientId || soleMember.clientId === myClientId;
3933
+ const isAdmin = soleMember.role === RTSeeChatMemberRoles.ADMIN ||
3934
+ this.chat.creator?.id === myClientId ||
3935
+ this.chat.creator?.clientId === myClientId;
3936
+ return isMe && isAdmin;
3937
+ }
3938
+ toggleInvite() {
3939
+ this.showInvite = !this.showInvite;
3940
+ }
3480
3941
  onInViewPortChange(value) {
3481
3942
  console.log('Received inViewPortChange', value);
3482
3943
  this.chat.isInViewPort = value;
@@ -3503,16 +3964,17 @@ class ChatComponent {
3503
3964
  }
3504
3965
  }
3505
3966
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.4", ngImport: i0, type: ChatComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
3506
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.4", type: ChatComponent, isStandalone: true, selector: "rtsee-chat", inputs: { chat: "chat", chatData: "chatData", entity: "entity", messenger: "messenger" }, ngImport: i0, template: "<div class=\"rts-chat\"\n [ngClass]=\"{\n 'rts-chat-standalone': chat.isStandalone,\n 'rts-chat-in-viewport': chat.isInViewPort\n }\"\n rtseeViewportObserver\n (inViewport)=\"onInViewPortChange($event)\"\n>\n <div class=\"rts-chat-footer-container\">\n <div class=\"rts-chat-footer-container-inner\">\n <rtsee-chat-input [messenger]=\"messenger\" [chat]=\"chat\"></rtsee-chat-input>\n </div>\n </div>\n <div class=\"rts-chat-body-container\">\n <rtsee-messages-list [messenger]=\"messenger\" [chat]=\"chat\"></rtsee-messages-list>\n </div>\n</div>\n", dependencies: [{ kind: "component", type: MessagesListComponent, selector: "rtsee-messages-list", inputs: ["chat", "messenger"] }, { kind: "component", type: ChatInputComponent, selector: "rtsee-chat-input", inputs: ["messenger", "chat"] }, { kind: "directive", type: ViewportObserverDirective, selector: "[rtseeViewportObserver]", outputs: ["inViewport"] }, { kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }] }); }
3967
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.4", type: ChatComponent, isStandalone: true, selector: "rtsee-chat", inputs: { chat: "chat", chatData: "chatData", entity: "entity", messenger: "messenger" }, ngImport: i0, template: "<div class=\"rts-chat\"\n [ngClass]=\"{\n 'rts-chat-standalone': chat.isStandalone,\n 'rts-chat-in-viewport': chat.isInViewPort\n }\"\n rtseeViewportObserver\n (inViewport)=\"onInViewPortChange($event)\"\n>\n @if (isSoleAdmin) {\n <div class=\"rts-chat-sole-admin-banner\">\n <div class=\"rts-chat-sole-admin-header\">\n <div class=\"rts-chat-sole-admin-badge\">\n <span class=\"material-icons-outlined\">group_add</span>\n </div>\n <div class=\"rts-chat-sole-admin-info\">\n <h4>You are the only member in this chat</h4>\n <p>Invite people to start chatting</p>\n </div>\n <button class=\"rts-btn rts-btn-primary rts-chat-invite-toggle-btn\"\n (click)=\"toggleInvite()\"\n type=\"button\"\n >\n <span class=\"material-icons-outlined\">{{ showInvite ? 'expand_less' : 'person_add' }}</span>\n <span>{{ showInvite ? 'Hide Invite' : 'Invite to Chat' }}</span>\n </button>\n </div>\n\n @if (showInvite) {\n <div class=\"rts-chat-sole-admin-invite\">\n <rtsee-invite-link\n [chatId]=\"chat.id\"\n [messenger]=\"messenger\"\n ></rtsee-invite-link>\n </div>\n }\n </div>\n }\n\n <div class=\"rts-chat-footer-container\">\n <div class=\"rts-chat-footer-container-inner\">\n <rtsee-chat-input [messenger]=\"messenger\" [chat]=\"chat\"></rtsee-chat-input>\n </div>\n </div>\n <div class=\"rts-chat-body-container\">\n <rtsee-messages-list [messenger]=\"messenger\" [chat]=\"chat\"></rtsee-messages-list>\n </div>\n</div>\n", dependencies: [{ kind: "component", type: MessagesListComponent, selector: "rtsee-messages-list", inputs: ["chat", "messenger"] }, { kind: "component", type: ChatInputComponent, selector: "rtsee-chat-input", inputs: ["messenger", "chat"] }, { kind: "component", type: InviteLinkComponent, selector: "rtsee-invite-link", inputs: ["inviteUrl", "chatId", "messenger", "disclaimer"] }, { kind: "directive", type: ViewportObserverDirective, selector: "[rtseeViewportObserver]", outputs: ["inViewport"] }, { kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }] }); }
3507
3968
  }
3508
3969
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.4", ngImport: i0, type: ChatComponent, decorators: [{
3509
3970
  type: Component,
3510
3971
  args: [{ selector: 'rtsee-chat', imports: [
3511
3972
  MessagesListComponent,
3512
3973
  ChatInputComponent,
3974
+ InviteLinkComponent,
3513
3975
  ViewportObserverDirective,
3514
3976
  NgClass,
3515
- ], standalone: true, template: "<div class=\"rts-chat\"\n [ngClass]=\"{\n 'rts-chat-standalone': chat.isStandalone,\n 'rts-chat-in-viewport': chat.isInViewPort\n }\"\n rtseeViewportObserver\n (inViewport)=\"onInViewPortChange($event)\"\n>\n <div class=\"rts-chat-footer-container\">\n <div class=\"rts-chat-footer-container-inner\">\n <rtsee-chat-input [messenger]=\"messenger\" [chat]=\"chat\"></rtsee-chat-input>\n </div>\n </div>\n <div class=\"rts-chat-body-container\">\n <rtsee-messages-list [messenger]=\"messenger\" [chat]=\"chat\"></rtsee-messages-list>\n </div>\n</div>\n" }]
3977
+ ], standalone: true, template: "<div class=\"rts-chat\"\n [ngClass]=\"{\n 'rts-chat-standalone': chat.isStandalone,\n 'rts-chat-in-viewport': chat.isInViewPort\n }\"\n rtseeViewportObserver\n (inViewport)=\"onInViewPortChange($event)\"\n>\n @if (isSoleAdmin) {\n <div class=\"rts-chat-sole-admin-banner\">\n <div class=\"rts-chat-sole-admin-header\">\n <div class=\"rts-chat-sole-admin-badge\">\n <span class=\"material-icons-outlined\">group_add</span>\n </div>\n <div class=\"rts-chat-sole-admin-info\">\n <h4>You are the only member in this chat</h4>\n <p>Invite people to start chatting</p>\n </div>\n <button class=\"rts-btn rts-btn-primary rts-chat-invite-toggle-btn\"\n (click)=\"toggleInvite()\"\n type=\"button\"\n >\n <span class=\"material-icons-outlined\">{{ showInvite ? 'expand_less' : 'person_add' }}</span>\n <span>{{ showInvite ? 'Hide Invite' : 'Invite to Chat' }}</span>\n </button>\n </div>\n\n @if (showInvite) {\n <div class=\"rts-chat-sole-admin-invite\">\n <rtsee-invite-link\n [chatId]=\"chat.id\"\n [messenger]=\"messenger\"\n ></rtsee-invite-link>\n </div>\n }\n </div>\n }\n\n <div class=\"rts-chat-footer-container\">\n <div class=\"rts-chat-footer-container-inner\">\n <rtsee-chat-input [messenger]=\"messenger\" [chat]=\"chat\"></rtsee-chat-input>\n </div>\n </div>\n <div class=\"rts-chat-body-container\">\n <rtsee-messages-list [messenger]=\"messenger\" [chat]=\"chat\"></rtsee-messages-list>\n </div>\n</div>\n" }]
3516
3978
  }], propDecorators: { chat: [{
3517
3979
  type: Input
3518
3980
  }], chatData: [{
@@ -4413,11 +4875,11 @@ class PresentationHeaderComponent {
4413
4875
  this.RTSeeTextLabelKeys = RTSeeTextLabelKeys;
4414
4876
  }
4415
4877
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.4", ngImport: i0, type: PresentationHeaderComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
4416
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.4", type: PresentationHeaderComponent, isStandalone: true, selector: "rtsee-presentation-header", inputs: { presentation: "presentation" }, ngImport: i0, template: "<header class=\"rtsee-presentation-header\"\n [ngClass]=\"{\n 'rtsee-presentation-header-convertable': presentation.activeStory,\n 'rtsee-presentation-header-visible': presentation.activeStory?.headerVisible\n }\"\n>\n @if (!presentation.activeStory && !presentation.openedStoryDetails) {\n <div class=\"rtsee-presentations-header-title\">\n <div class=\"rtsee-presentations-header-title-overlay\"></div>\n <div class=\"rtsee-presentations-header-title-content\">\n <h1>{{presentation.labels[RTSeeTextLabelKeys.PRESENTATION_TITLE_LABEL]}}</h1>\n <p class=\"rtsee-presentations-header-title-subtitle\">\n <span>{{presentation.labels[RTSeeTextLabelKeys.PRESENTATION_SUBTITLE_LABEL]}}</span>\n </p>\n </div>\n </div>\n }\n\n @if (presentation.activeRoom && !presentation.activeStory && !presentation.openedStoryDetails) {\n <div class=\"rtsee-presentation-room-block\">\n <rtsee-join-call [room]=\"presentation.activeRoom\"\n [presentation]=\"presentation\"\n ></rtsee-join-call>\n </div>\n }\n\n @if (presentation.activeStory) {\n <div class=\"rtsee-presentation-header-active-story-info\">\n <div class=\"rtsee-presentation-header-active-story-info-close\"\n [routerLink]=\"[presentation.homePage]\"\n (click)=\"presentation.disableActiveStory()\"\n >\n <i class=\"pi pi-times rtsee-presentation-header-active-story-info-close-icon\"></i>\n </div>\n <div class=\"rtsee-presentation-header-active-story-info-details\">\n <p-chip [label]=\"presentation.activeStory.name\"\n [image]=\"presentation.activeStory.imageUrl || undefined\"\n class=\"rtsee-presentation-header-active-story-name\"\n alt=\"Story image\" />\n </div>\n <div class=\"rtsee-presentation-header-active-story-info-settings\"\n (click)=\"presentation.settings.expand()\"\n >\n <i class=\"pi pi-cog-h rtsee-presentation-header-active-story-info-settings-icon\"></i>\n </div>\n </div>\n }\n @if (presentation.dashboard && !presentation.activeStory && !presentation.isDashboardOpen && !presentation.openedStoryDetails) {\n <p-button [label]=\"presentation.labels[RTSeeTextLabelKeys.DASHBOARD_BUTTON]\"\n (click)=\"presentation.toggleDashboardState()\"\n ></p-button>\n }\n</header>\n", dependencies: [{ kind: "component", type: Button, selector: "p-button", inputs: ["type", "iconPos", "icon", "badge", "label", "disabled", "loading", "loadingIcon", "raised", "rounded", "text", "plain", "severity", "outlined", "link", "tabindex", "size", "variant", "style", "styleClass", "badgeClass", "badgeSeverity", "ariaLabel", "buttonProps", "autofocus", "fluid"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "component", type: Chip, selector: "p-chip", inputs: ["label", "icon", "image", "alt", "styleClass", "disabled", "removable", "removeIcon", "chipProps"], outputs: ["onRemove", "onImageError"] }, { kind: "component", type: JoinCallComponent, selector: "rtsee-join-call", inputs: ["presentation", "room"] }, { kind: "directive", type: RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }] }); }
4878
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.4", type: PresentationHeaderComponent, isStandalone: true, selector: "rtsee-presentation-header", inputs: { presentation: "presentation" }, ngImport: i0, template: "<header class=\"rtsee-presentation-header\"\n [ngClass]=\"{\n 'rtsee-presentation-header-convertable': presentation.activeStory,\n 'rtsee-presentation-header-visible': presentation.activeStory?.headerVisible\n }\"\n>\n @if (!presentation.activeStory && !presentation.openedStoryDetails) {\n <div class=\"rtsee-presentations-header-title\">\n <div class=\"rtsee-presentations-header-title-overlay\"></div>\n <div class=\"rtsee-presentations-header-title-content\">\n <h1>{{presentation.labels[RTSeeTextLabelKeys.PRESENTATION_TITLE_LABEL]}}</h1>\n <p class=\"rtsee-presentations-header-title-subtitle\">\n <span>{{presentation.labels[RTSeeTextLabelKeys.PRESENTATION_SUBTITLE_LABEL]}}</span>\n </p>\n </div>\n </div>\n }\n\n @if (presentation.activeStory) {\n <div class=\"rtsee-presentation-header-active-story-info\">\n <div class=\"rtsee-presentation-header-active-story-info-close\"\n [routerLink]=\"[presentation.homePage]\"\n (click)=\"presentation.disableActiveStory()\"\n >\n <i class=\"pi pi-times rtsee-presentation-header-active-story-info-close-icon\"></i>\n </div>\n <div class=\"rtsee-presentation-header-active-story-info-details\">\n <p-chip [label]=\"presentation.activeStory.name\"\n [image]=\"presentation.activeStory.imageUrl || undefined\"\n class=\"rtsee-presentation-header-active-story-name\"\n alt=\"Story image\" />\n </div>\n <div class=\"rtsee-presentation-header-active-story-info-settings\"\n (click)=\"presentation.settings.expand()\"\n >\n <i class=\"pi pi-cog-h rtsee-presentation-header-active-story-info-settings-icon\"></i>\n </div>\n </div>\n }\n @if (presentation.dashboard && !presentation.activeStory && !presentation.isDashboardOpen && !presentation.openedStoryDetails) {\n <p-button [label]=\"presentation.labels[RTSeeTextLabelKeys.DASHBOARD_BUTTON]\"\n (click)=\"presentation.toggleDashboardState()\"\n ></p-button>\n }\n</header>\n", dependencies: [{ kind: "component", type: Button, selector: "p-button", inputs: ["type", "iconPos", "icon", "badge", "label", "disabled", "loading", "loadingIcon", "raised", "rounded", "text", "plain", "severity", "outlined", "link", "tabindex", "size", "variant", "style", "styleClass", "badgeClass", "badgeSeverity", "ariaLabel", "buttonProps", "autofocus", "fluid"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "component", type: Chip, selector: "p-chip", inputs: ["label", "icon", "image", "alt", "styleClass", "disabled", "removable", "removeIcon", "chipProps"], outputs: ["onRemove", "onImageError"] }, { kind: "directive", type: RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }] }); }
4417
4879
  }
4418
4880
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.4", ngImport: i0, type: PresentationHeaderComponent, decorators: [{
4419
4881
  type: Component,
4420
- args: [{ selector: 'rtsee-presentation-header', imports: [Button, NgClass, Chip, JoinCallComponent, RouterLink], standalone: true, template: "<header class=\"rtsee-presentation-header\"\n [ngClass]=\"{\n 'rtsee-presentation-header-convertable': presentation.activeStory,\n 'rtsee-presentation-header-visible': presentation.activeStory?.headerVisible\n }\"\n>\n @if (!presentation.activeStory && !presentation.openedStoryDetails) {\n <div class=\"rtsee-presentations-header-title\">\n <div class=\"rtsee-presentations-header-title-overlay\"></div>\n <div class=\"rtsee-presentations-header-title-content\">\n <h1>{{presentation.labels[RTSeeTextLabelKeys.PRESENTATION_TITLE_LABEL]}}</h1>\n <p class=\"rtsee-presentations-header-title-subtitle\">\n <span>{{presentation.labels[RTSeeTextLabelKeys.PRESENTATION_SUBTITLE_LABEL]}}</span>\n </p>\n </div>\n </div>\n }\n\n @if (presentation.activeRoom && !presentation.activeStory && !presentation.openedStoryDetails) {\n <div class=\"rtsee-presentation-room-block\">\n <rtsee-join-call [room]=\"presentation.activeRoom\"\n [presentation]=\"presentation\"\n ></rtsee-join-call>\n </div>\n }\n\n @if (presentation.activeStory) {\n <div class=\"rtsee-presentation-header-active-story-info\">\n <div class=\"rtsee-presentation-header-active-story-info-close\"\n [routerLink]=\"[presentation.homePage]\"\n (click)=\"presentation.disableActiveStory()\"\n >\n <i class=\"pi pi-times rtsee-presentation-header-active-story-info-close-icon\"></i>\n </div>\n <div class=\"rtsee-presentation-header-active-story-info-details\">\n <p-chip [label]=\"presentation.activeStory.name\"\n [image]=\"presentation.activeStory.imageUrl || undefined\"\n class=\"rtsee-presentation-header-active-story-name\"\n alt=\"Story image\" />\n </div>\n <div class=\"rtsee-presentation-header-active-story-info-settings\"\n (click)=\"presentation.settings.expand()\"\n >\n <i class=\"pi pi-cog-h rtsee-presentation-header-active-story-info-settings-icon\"></i>\n </div>\n </div>\n }\n @if (presentation.dashboard && !presentation.activeStory && !presentation.isDashboardOpen && !presentation.openedStoryDetails) {\n <p-button [label]=\"presentation.labels[RTSeeTextLabelKeys.DASHBOARD_BUTTON]\"\n (click)=\"presentation.toggleDashboardState()\"\n ></p-button>\n }\n</header>\n" }]
4882
+ args: [{ selector: 'rtsee-presentation-header', imports: [Button, NgClass, Chip, JoinCallComponent, RouterLink], standalone: true, template: "<header class=\"rtsee-presentation-header\"\n [ngClass]=\"{\n 'rtsee-presentation-header-convertable': presentation.activeStory,\n 'rtsee-presentation-header-visible': presentation.activeStory?.headerVisible\n }\"\n>\n @if (!presentation.activeStory && !presentation.openedStoryDetails) {\n <div class=\"rtsee-presentations-header-title\">\n <div class=\"rtsee-presentations-header-title-overlay\"></div>\n <div class=\"rtsee-presentations-header-title-content\">\n <h1>{{presentation.labels[RTSeeTextLabelKeys.PRESENTATION_TITLE_LABEL]}}</h1>\n <p class=\"rtsee-presentations-header-title-subtitle\">\n <span>{{presentation.labels[RTSeeTextLabelKeys.PRESENTATION_SUBTITLE_LABEL]}}</span>\n </p>\n </div>\n </div>\n }\n\n @if (presentation.activeStory) {\n <div class=\"rtsee-presentation-header-active-story-info\">\n <div class=\"rtsee-presentation-header-active-story-info-close\"\n [routerLink]=\"[presentation.homePage]\"\n (click)=\"presentation.disableActiveStory()\"\n >\n <i class=\"pi pi-times rtsee-presentation-header-active-story-info-close-icon\"></i>\n </div>\n <div class=\"rtsee-presentation-header-active-story-info-details\">\n <p-chip [label]=\"presentation.activeStory.name\"\n [image]=\"presentation.activeStory.imageUrl || undefined\"\n class=\"rtsee-presentation-header-active-story-name\"\n alt=\"Story image\" />\n </div>\n <div class=\"rtsee-presentation-header-active-story-info-settings\"\n (click)=\"presentation.settings.expand()\"\n >\n <i class=\"pi pi-cog-h rtsee-presentation-header-active-story-info-settings-icon\"></i>\n </div>\n </div>\n }\n @if (presentation.dashboard && !presentation.activeStory && !presentation.isDashboardOpen && !presentation.openedStoryDetails) {\n <p-button [label]=\"presentation.labels[RTSeeTextLabelKeys.DASHBOARD_BUTTON]\"\n (click)=\"presentation.toggleDashboardState()\"\n ></p-button>\n }\n</header>\n" }]
4421
4883
  }], propDecorators: { presentation: [{
4422
4884
  type: Input
4423
4885
  }] } });
@@ -5489,7 +5951,7 @@ class PresentationSettingsComponent {
5489
5951
  this.window = window;
5490
5952
  }
5491
5953
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.4", ngImport: i0, type: PresentationSettingsComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
5492
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.4", type: PresentationSettingsComponent, isStandalone: true, selector: "rtsee-presentation-settings", inputs: { settings: "settings", presentation: "presentation" }, ngImport: i0, template: "<div class=\"rtsee-presentation-settings\">\n <div class=\"rtsee-presentation-settings-header\">\n <p-chip label=\"Settings\" />\n <p-button label=\"Close\"\n (click)=\"settings.collapse()\"\n class=\"rtsee-presentations-dashboard-close-btn\"\n ></p-button>\n </div>\n\n <div class=\"rtsee-presentation-rooms\">\n <p-fieldset legend=\"Rooms\">\n <p-listbox [options]=\"presentation.rooms\"\n [(ngModel)]=\"presentation.selectedRoom\"\n (onChange)=\"presentation.setActiveRoom()\"\n class=\"rtsee-presentation-settings-rooms-list\"\n >\n <ng-template #item let-room>\n <div class=\"rtsee-presentation-settings-rooms-list-item\">\n\n <img [src]=\"room.imageUrl || COMMON_CONSTANTS.defaultProfileImgUrl\"\n class=\"rtsee-presentation-settings-rooms-list-item-image\" />\n <div>{{ room.name }}</div>\n </div>\n </ng-template>\n </p-listbox>\n @if (presentation.selectedRoom) {\n <rtsee-join-call [room]=\"presentation.selectedRoom\"\n [presentation]=\"presentation\"\n ></rtsee-join-call>\n }\n </p-fieldset>\n </div>\n <div class=\"rtsee-presentation-sound-settings\">\n <p-fieldset legend=\"Volume\" class=\"rtsee-presentation-sound-settings-fieldset\">\n <div class=\"rtsee-presentation-sound-settings-inner\">\n <div class=\"rtsee-presentation-sound-settings-inner-left\">\n <p-slider [(ngModel)]=\"settings.volume\"\n (onChange)=\"settings.setVolume(settings.volume)\"\n />\n </div>\n <div class=\"rtsee-presentation-sound-settings-inner-right\">\n <p-tag severity=\"info\"\n class=\"rtsee-presentation-sound-value-label\"\n [icon]=\"settings.volume ? 'pi pi-volume-up' : 'pi pi-volume-off'\"\n [value]=\"settings.volume.toString()\"\n [rounded]=\"true\" />\n </div>\n </div>\n </p-fieldset>\n </div>\n <div class=\"rtsee-presentation-font-size-settings\">\n <p-fieldset legend=\"Font Size\">\n <div class=\"rtsee-presentation-font-size-range\">\n <p-slider [(ngModel)]=\"settings.fontSizeOption\"\n (onChange)=\"settings.setFontSize(settings.fontSizeOption)\"\n [min]=\"1\"\n [max]=\"10\"\n />\n </div>\n <p-message severity=\"secondary\" class=\"rtsee-presentation-font-size-example\">\n <span [style.font-size]=\"settings.fontSize + 'rem'\">A long time ago in a galaxy far, far away...</span>\n </p-message>\n </p-fieldset>\n </div>\n</div>\n", dependencies: [{ kind: "component", type: Slider, selector: "p-slider", inputs: ["animate", "min", "max", "orientation", "step", "range", "styleClass", "ariaLabel", "ariaLabelledBy", "tabindex", "autofocus"], outputs: ["onChange", "onSlideEnd"] }, { kind: "component", type: Fieldset, selector: "p-fieldset", inputs: ["legend", "toggleable", "collapsed", "style", "styleClass", "transitionOptions"], outputs: ["collapsedChange", "onBeforeToggle", "onAfterToggle"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: Button, selector: "p-button", inputs: ["type", "iconPos", "icon", "badge", "label", "disabled", "loading", "loadingIcon", "raised", "rounded", "text", "plain", "severity", "outlined", "link", "tabindex", "size", "variant", "style", "styleClass", "badgeClass", "badgeSeverity", "ariaLabel", "buttonProps", "autofocus", "fluid"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "component", type: Chip, selector: "p-chip", inputs: ["label", "icon", "image", "alt", "styleClass", "disabled", "removable", "removeIcon", "chipProps"], outputs: ["onRemove", "onImageError"] }, { kind: "component", type: Message, selector: "p-message", inputs: ["severity", "text", "escape", "style", "styleClass", "closable", "icon", "closeIcon", "life", "showTransitionOptions", "hideTransitionOptions", "size", "variant"], outputs: ["onClose"] }, { kind: "component", type: Tag, selector: "p-tag", inputs: ["styleClass", "severity", "value", "icon", "rounded"] }, { kind: "component", type: Listbox, selector: "p-listbox, p-listBox, p-list-box", inputs: ["id", "searchMessage", "emptySelectionMessage", "selectionMessage", "autoOptionFocus", "ariaLabel", "selectOnFocus", "searchLocale", "focusOnHover", "filterMessage", "filterFields", "lazy", "virtualScroll", "virtualScrollItemSize", "virtualScrollOptions", "scrollHeight", "tabindex", "multiple", "styleClass", "listStyle", "listStyleClass", "readonly", "checkbox", "filter", "filterBy", "filterMatchMode", "filterLocale", "metaKeySelection", "dataKey", "showToggleAll", "optionLabel", "optionValue", "optionGroupChildren", "optionGroupLabel", "optionDisabled", "ariaFilterLabel", "filterPlaceHolder", "emptyFilterMessage", "emptyMessage", "group", "options", "filterValue", "selectAll", "striped", "highlightOnSelect", "checkmark", "dragdrop", "dropListData", "fluid"], outputs: ["onChange", "onClick", "onDblClick", "onFilter", "onFocus", "onBlur", "onSelectAllChange", "onLazyLoad", "onDrop"] }, { kind: "component", type: JoinCallComponent, selector: "rtsee-join-call", inputs: ["presentation", "room"] }] }); }
5954
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.4", type: PresentationSettingsComponent, isStandalone: true, selector: "rtsee-presentation-settings", inputs: { settings: "settings", presentation: "presentation" }, ngImport: i0, template: "<div class=\"rtsee-presentation-settings\">\n <div class=\"rtsee-presentation-settings-header\">\n <p-chip label=\"Settings\" />\n <p-button label=\"Close\"\n (click)=\"settings.collapse()\"\n class=\"rtsee-presentations-dashboard-close-btn\"\n ></p-button>\n </div>\n <div class=\"rtsee-presentation-sound-settings\">\n <p-fieldset legend=\"Volume\" class=\"rtsee-presentation-sound-settings-fieldset\">\n <div class=\"rtsee-presentation-sound-settings-inner\">\n <div class=\"rtsee-presentation-sound-settings-inner-left\">\n <p-slider [(ngModel)]=\"settings.volume\"\n (onChange)=\"settings.setVolume(settings.volume)\"\n />\n </div>\n <div class=\"rtsee-presentation-sound-settings-inner-right\">\n <p-tag severity=\"info\"\n class=\"rtsee-presentation-sound-value-label\"\n [icon]=\"settings.volume ? 'pi pi-volume-up' : 'pi pi-volume-off'\"\n [value]=\"settings.volume.toString()\"\n [rounded]=\"true\" />\n </div>\n </div>\n </p-fieldset>\n </div>\n <div class=\"rtsee-presentation-font-size-settings\">\n <p-fieldset legend=\"Font Size\">\n <div class=\"rtsee-presentation-font-size-range\">\n <p-slider [(ngModel)]=\"settings.fontSizeOption\"\n (onChange)=\"settings.setFontSize(settings.fontSizeOption)\"\n [min]=\"1\"\n [max]=\"10\"\n />\n </div>\n <p-message severity=\"secondary\" class=\"rtsee-presentation-font-size-example\">\n <span [style.font-size]=\"settings.fontSize + 'rem'\">A long time ago in a galaxy far, far away...</span>\n </p-message>\n </p-fieldset>\n </div>\n</div>\n", dependencies: [{ kind: "component", type: Slider, selector: "p-slider", inputs: ["animate", "min", "max", "orientation", "step", "range", "styleClass", "ariaLabel", "ariaLabelledBy", "tabindex", "autofocus"], outputs: ["onChange", "onSlideEnd"] }, { kind: "component", type: Fieldset, selector: "p-fieldset", inputs: ["legend", "toggleable", "collapsed", "style", "styleClass", "transitionOptions"], outputs: ["collapsedChange", "onBeforeToggle", "onAfterToggle"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: Button, selector: "p-button", inputs: ["type", "iconPos", "icon", "badge", "label", "disabled", "loading", "loadingIcon", "raised", "rounded", "text", "plain", "severity", "outlined", "link", "tabindex", "size", "variant", "style", "styleClass", "badgeClass", "badgeSeverity", "ariaLabel", "buttonProps", "autofocus", "fluid"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "component", type: Chip, selector: "p-chip", inputs: ["label", "icon", "image", "alt", "styleClass", "disabled", "removable", "removeIcon", "chipProps"], outputs: ["onRemove", "onImageError"] }, { kind: "component", type: Message, selector: "p-message", inputs: ["severity", "text", "escape", "style", "styleClass", "closable", "icon", "closeIcon", "life", "showTransitionOptions", "hideTransitionOptions", "size", "variant"], outputs: ["onClose"] }, { kind: "component", type: Tag, selector: "p-tag", inputs: ["styleClass", "severity", "value", "icon", "rounded"] }] }); }
5493
5955
  }
5494
5956
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.4", ngImport: i0, type: PresentationSettingsComponent, decorators: [{
5495
5957
  type: Component,
@@ -5500,10 +5962,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.4", ngImpor
5500
5962
  Button,
5501
5963
  Chip,
5502
5964
  Message,
5503
- Tag,
5504
- Listbox,
5505
- JoinCallComponent,
5506
- ], template: "<div class=\"rtsee-presentation-settings\">\n <div class=\"rtsee-presentation-settings-header\">\n <p-chip label=\"Settings\" />\n <p-button label=\"Close\"\n (click)=\"settings.collapse()\"\n class=\"rtsee-presentations-dashboard-close-btn\"\n ></p-button>\n </div>\n\n <div class=\"rtsee-presentation-rooms\">\n <p-fieldset legend=\"Rooms\">\n <p-listbox [options]=\"presentation.rooms\"\n [(ngModel)]=\"presentation.selectedRoom\"\n (onChange)=\"presentation.setActiveRoom()\"\n class=\"rtsee-presentation-settings-rooms-list\"\n >\n <ng-template #item let-room>\n <div class=\"rtsee-presentation-settings-rooms-list-item\">\n\n <img [src]=\"room.imageUrl || COMMON_CONSTANTS.defaultProfileImgUrl\"\n class=\"rtsee-presentation-settings-rooms-list-item-image\" />\n <div>{{ room.name }}</div>\n </div>\n </ng-template>\n </p-listbox>\n @if (presentation.selectedRoom) {\n <rtsee-join-call [room]=\"presentation.selectedRoom\"\n [presentation]=\"presentation\"\n ></rtsee-join-call>\n }\n </p-fieldset>\n </div>\n <div class=\"rtsee-presentation-sound-settings\">\n <p-fieldset legend=\"Volume\" class=\"rtsee-presentation-sound-settings-fieldset\">\n <div class=\"rtsee-presentation-sound-settings-inner\">\n <div class=\"rtsee-presentation-sound-settings-inner-left\">\n <p-slider [(ngModel)]=\"settings.volume\"\n (onChange)=\"settings.setVolume(settings.volume)\"\n />\n </div>\n <div class=\"rtsee-presentation-sound-settings-inner-right\">\n <p-tag severity=\"info\"\n class=\"rtsee-presentation-sound-value-label\"\n [icon]=\"settings.volume ? 'pi pi-volume-up' : 'pi pi-volume-off'\"\n [value]=\"settings.volume.toString()\"\n [rounded]=\"true\" />\n </div>\n </div>\n </p-fieldset>\n </div>\n <div class=\"rtsee-presentation-font-size-settings\">\n <p-fieldset legend=\"Font Size\">\n <div class=\"rtsee-presentation-font-size-range\">\n <p-slider [(ngModel)]=\"settings.fontSizeOption\"\n (onChange)=\"settings.setFontSize(settings.fontSizeOption)\"\n [min]=\"1\"\n [max]=\"10\"\n />\n </div>\n <p-message severity=\"secondary\" class=\"rtsee-presentation-font-size-example\">\n <span [style.font-size]=\"settings.fontSize + 'rem'\">A long time ago in a galaxy far, far away...</span>\n </p-message>\n </p-fieldset>\n </div>\n</div>\n" }]
5965
+ Tag
5966
+ ], template: "<div class=\"rtsee-presentation-settings\">\n <div class=\"rtsee-presentation-settings-header\">\n <p-chip label=\"Settings\" />\n <p-button label=\"Close\"\n (click)=\"settings.collapse()\"\n class=\"rtsee-presentations-dashboard-close-btn\"\n ></p-button>\n </div>\n <div class=\"rtsee-presentation-sound-settings\">\n <p-fieldset legend=\"Volume\" class=\"rtsee-presentation-sound-settings-fieldset\">\n <div class=\"rtsee-presentation-sound-settings-inner\">\n <div class=\"rtsee-presentation-sound-settings-inner-left\">\n <p-slider [(ngModel)]=\"settings.volume\"\n (onChange)=\"settings.setVolume(settings.volume)\"\n />\n </div>\n <div class=\"rtsee-presentation-sound-settings-inner-right\">\n <p-tag severity=\"info\"\n class=\"rtsee-presentation-sound-value-label\"\n [icon]=\"settings.volume ? 'pi pi-volume-up' : 'pi pi-volume-off'\"\n [value]=\"settings.volume.toString()\"\n [rounded]=\"true\" />\n </div>\n </div>\n </p-fieldset>\n </div>\n <div class=\"rtsee-presentation-font-size-settings\">\n <p-fieldset legend=\"Font Size\">\n <div class=\"rtsee-presentation-font-size-range\">\n <p-slider [(ngModel)]=\"settings.fontSizeOption\"\n (onChange)=\"settings.setFontSize(settings.fontSizeOption)\"\n [min]=\"1\"\n [max]=\"10\"\n />\n </div>\n <p-message severity=\"secondary\" class=\"rtsee-presentation-font-size-example\">\n <span [style.font-size]=\"settings.fontSize + 'rem'\">A long time ago in a galaxy far, far away...</span>\n </p-message>\n </p-fieldset>\n </div>\n</div>\n" }]
5507
5967
  }], propDecorators: { settings: [{
5508
5968
  type: Input
5509
5969
  }], presentation: [{