@banta/sdk 4.0.17 → 4.0.20

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.
@@ -9611,6 +9611,34 @@
9611
9611
  if (window.innerWidth < 430)
9612
9612
  alert(message);
9613
9613
  };
9614
+ Object.defineProperty(CommentFieldComponent.prototype, "isValidMessage", {
9615
+ get: function () {
9616
+ return (this.text || this.chatMessageAttachments.length > 0);
9617
+ },
9618
+ enumerable: false,
9619
+ configurable: true
9620
+ });
9621
+ Object.defineProperty(CommentFieldComponent.prototype, "hasPendingAttachments", {
9622
+ get: function () {
9623
+ return this.chatMessageAttachments.some(function (x) { return x.transientState; });
9624
+ },
9625
+ enumerable: false,
9626
+ configurable: true
9627
+ });
9628
+ Object.defineProperty(CommentFieldComponent.prototype, "sendButtonEnabled", {
9629
+ get: function () {
9630
+ if (!this.canComment) {
9631
+ // In this case, we want to enable the button because we want to be able to
9632
+ // send the permissionDenied message up to the host.
9633
+ return true;
9634
+ }
9635
+ return this.isValidMessage
9636
+ && !this.hasPendingAttachments
9637
+ && !this.sending;
9638
+ },
9639
+ enumerable: false,
9640
+ configurable: true
9641
+ });
9614
9642
  CommentFieldComponent.prototype.autocomplete = function (replacement) {
9615
9643
  return __awaiter(this, void 0, void 0, function () {
9616
9644
  var el;
@@ -9766,7 +9794,7 @@
9766
9794
  case 1:
9767
9795
  _b.trys.push([1, , 7, 8]);
9768
9796
  text = (this.text || '').trim();
9769
- if (text === '')
9797
+ if (this.canComment && !this.isValidMessage)
9770
9798
  return [2 /*return*/];
9771
9799
  message = {
9772
9800
  user: this.user,
@@ -9774,7 +9802,7 @@
9774
9802
  url: location.href,
9775
9803
  likes: 0,
9776
9804
  message: text,
9777
- attachments: this.chatMessageAttachments
9805
+ attachments: this.chatMessageAttachments.filter(function (x) { return x.url; })
9778
9806
  };
9779
9807
  _b.label = 2;
9780
9808
  case 2:
@@ -9783,6 +9811,7 @@
9783
9811
  case 3:
9784
9812
  _b.sent();
9785
9813
  this.text = '';
9814
+ this.chatMessageAttachments = [];
9786
9815
  return [3 /*break*/, 6];
9787
9816
  case 4:
9788
9817
  e_1 = _b.sent();
@@ -9800,8 +9829,14 @@
9800
9829
  });
9801
9830
  });
9802
9831
  };
9803
- CommentFieldComponent.prototype.addedAttachment = function (file) {
9804
- this.chatMessageAttachments.push(file);
9832
+ CommentFieldComponent.prototype.addedAttachment = function (attachment) {
9833
+ this.chatMessageAttachments.push(attachment);
9834
+ };
9835
+ CommentFieldComponent.prototype.attachmentError = function (attachment) {
9836
+ var _this = this;
9837
+ setTimeout(function () {
9838
+ _this.chatMessageAttachments = _this.chatMessageAttachments.filter(function (x) { return x !== attachment; });
9839
+ }, 3000);
9805
9840
  };
9806
9841
  CommentFieldComponent.prototype.removeAttachment = function (index) {
9807
9842
  this.chatMessageAttachments.splice(index, 1);
@@ -9816,8 +9851,8 @@
9816
9851
  CommentFieldComponent.decorators = [
9817
9852
  { type: core.Component, args: [{
9818
9853
  selector: 'banta-comment-field',
9819
- template: "<form class=\"new-message\" (submit)=\"sendMessage()\">\r\n <div class=\"avatar-container\">\r\n <a href=\"javascript:;\"\r\n class=\"avatar\"\r\n (click)=\"showEditAvatar()\"\r\n [style.background-image]=\"'url(' + userAvatarUrl + ')'\"\r\n ></a>\r\n </div>\r\n <div class=\"text-container\">\r\n <div class=\"field-container\">\r\n <div class=\"field-row\">\r\n <mat-form-field appearance=\"outline\" floatLabel=\"always\">\r\n <mat-label>{{label}}</mat-label>\r\n <textarea\r\n #textarea\r\n name=\"message\"\r\n [placeholder]=\"placeholder\"\r\n matInput\r\n cdkTextareaAutosize\r\n (keydown)=\"onKeyDown($event)\"\r\n (blur)=\"onBlur()\"\r\n [disabled]=\"sending\"\r\n [(ngModel)]=\"text\"></textarea>\r\n </mat-form-field>\r\n <div class=\"options-line\">\r\n <mat-spinner *ngIf=\"sending\" class=\"icon loading\" diameter=\"18\" strokeWidth=\"2\"></mat-spinner>\r\n <div *ngIf=\"sendError\" class=\"error-message\" [class.expanded]=\"expandError\" [matTooltip]=\"sendError.message\" (click)=\"alertError()\">\r\n <mat-icon *ngIf=\"sendError\">error</mat-icon>\r\n {{sendError.message}}\r\n </div>\r\n <div class=\"spacer\"></div>\r\n <div class=\"custom\">\r\n <ng-content></ng-content>\r\n </div>\r\n <banta-attachment-button (addedAttachment)=\"addedAttachment($event)\" *ngIf=\"allowAttachments\"></banta-attachment-button>\r\n <emoji-selector-button (selected)=\"insertEmoji($event)\"></emoji-selector-button>\r\n </div>\r\n \r\n </div>\r\n <div #autocompleteContainer class=\"autocomplete-container\">\r\n <div #autocomplete class=\"autocomplete\" [class.visible]=\"autocompleteVisible\">\r\n\r\n <div>\r\n <strong>{{completionPrefix}}</strong>...\r\n </div>\r\n <a\r\n mat-button\r\n *ngFor=\"let option of autocompleteOptions; index as index\"\r\n (click)=\"activateAutoComplete(option)\"\r\n [class.active]=\"autoCompleteSelected === index\"\r\n >\r\n {{option.label}}\r\n </a>\r\n </div>\r\n </div>\r\n\r\n\r\n <div *ngIf=\"chatMessageAttachments && chatMessageAttachments.length\" class=\"message-attachments-container\">\r\n <div *ngFor=\"let attachment of chatMessageAttachments; index as attachmentIndex\"\r\n class=\"message-attachment\">\r\n <button (click)=\"removeAttachment(attachmentIndex)\" mat-mini-fab color=\"primary\" class=\"remove-img\">\r\n <mat-icon>close</mat-icon>\r\n </button>\r\n <img [src]=\"attachment.url\" alt=\"Message Attachment\">\r\n </div>\r\n </div>\r\n\r\n </div>\r\n </div>\r\n <div class=\"actions\">\r\n <ng-container *ngIf=\"!user\">\r\n <button\r\n mat-raised-button\r\n color=\"primary\"\r\n type=\"button\"\r\n (click)=\"showSignIn()\"\r\n >{{signInLabel}}</button>\r\n </ng-container>\r\n <ng-container *ngIf=\"user\">\r\n <button\r\n mat-raised-button\r\n class=\"send\"\r\n color=\"primary\"\r\n [disabled]=\"canComment && ((!text && !chatMessageAttachments.length) || sending)\"\r\n >\r\n <ng-container *ngIf=\"canComment\">\r\n <mat-icon *ngIf=\"!sending\">chevron_right</mat-icon>\r\n <mat-spinner *ngIf=\"sending\" class=\"icon\" diameter=\"18\" strokeWidth=\"2\"></mat-spinner>\r\n </ng-container>\r\n <span class=\"label\">\r\n <ng-container *ngIf=\"!canComment\">\r\n {{permissionDeniedLabel}}\r\n </ng-container>\r\n <ng-container *ngIf=\"canComment\">\r\n <ng-container *ngIf=\"!sending\">\r\n {{sendLabel}}\r\n </ng-container>\r\n <ng-container *ngIf=\"sending\">\r\n {{sendingLabel}}\r\n </ng-container>\r\n </ng-container>\r\n </span>\r\n </button>\r\n </ng-container>\r\n </div>\r\n</form>",
9820
- styles: ["@-webkit-keyframes comment-field-appear{0%{transform:translateY(128px);opacity:0}to{transform:translate(0);opacity:1}}@keyframes comment-field-appear{0%{transform:translateY(128px);opacity:0}to{transform:translate(0);opacity:1}}:host{margin:0 2em 0 0;display:block;-webkit-animation-name:comment-field-appear;animation-name:comment-field-appear;-webkit-animation-duration:.8s;animation-duration:.8s;-webkit-animation-delay:.4s;animation-delay:.4s;-webkit-animation-fill-mode:both;animation-fill-mode:both;position:relative;z-index:20}.avatar-container{width:calc(48px + 1.75em);display:flex;justify-content:flex-end;flex-shrink:0}.avatar-container .avatar{width:48px;height:48px;background:#000;border-radius:100%;background-size:cover;background-repeat:no-repeat;background-position:50%;margin-top:.75em;margin-right:.75em}form{display:flex;padding:.5em;align-items:center}form .text-container{position:relative;display:flex;flex-grow:1;min-width:0}form .text-container textarea{font-size:14pt;width:100%}form .text-container textarea[disabled]{opacity:.5}form .text-container mat-spinner.loading{position:absolute;left:.5em;bottom:.5em}form .text-container .options-line{display:flex;align-items:center}form .text-container .options-line>*{flex-shrink:0}form .text-container .options-line .error-message{left:.5em;bottom:.5em;color:#683333;overflow-x:hidden;max-width:1.5em;white-space:nowrap;transition:max-width 2s ease-in-out;text-overflow:ellipsis;overflow:hidden;flex-shrink:1}form .text-container .options-line .error-message.expanded,form .text-container .options-line .error-message:hover{max-width:100%}form .text-container .options-line .error-message mat-icon{vertical-align:middle}form input[type=text]{background:#000;color:#fff;border:1px solid #333;width:100%;height:1em}form .actions{margin-left:1em;flex-shrink:0}form button{display:block;margin:0 0 0 auto}form.new-message{display:flex;align-items:flex-start;min-width:0}form.new-message .field-container{flex-grow:1;display:flex;flex-direction:column;min-width:0}form.new-message mat-form-field{width:100%}form.new-message mat-form-field ::ng-deep .mat-form-field-wrapper{padding-bottom:0}form.new-message button{margin:1.25em 0 0}button.send{min-width:9em}textarea{max-height:7em}.autocomplete-container{width:calc(100% - 2em);position:relative;pointer-events:none;top:-2em}.autocomplete{visibility:hidden;pointer-events:none;position:absolute;background:#333;padding:.5em;display:flex;flex-direction:column;z-index:100}.autocomplete.visible{visibility:visible;pointer-events:auto}.autocomplete a{width:100%;text-align:left}.autocomplete a.active{background:#555}@media (max-width:500px){:host{margin:0}.avatar-container{width:auto;flex-shrink:0}.avatar-container .avatar{width:32px;height:32px;margin-top:1.5em}button.send{min-width:auto;margin-top:1.5em}button.send .label{display:none}}:host-context(.banta-mobile) :host{margin:0}:host-context(.banta-mobile) .avatar-container{width:auto;flex-shrink:0}:host-context(.banta-mobile) .avatar-container .avatar{width:32px;height:32px;margin-top:1.5em}:host-context(.banta-mobile) button.send{min-width:auto;margin-top:1.5em}:host-context(.banta-mobile) button.send .label{display:none}.message-attachments-container{display:flex;gap:20px}.message-attachments-container .message-attachment{width:300px;position:relative}.message-attachments-container .message-attachment img{width:300px;border-radius:10px}.message-attachments-container .message-attachment .remove-img{position:absolute;right:10px;top:10px;margin:0}.field-row{position:relative}"]
9854
+ template: "<form class=\"new-message\" (submit)=\"sendMessage()\">\r\n <div class=\"avatar-container\">\r\n <a href=\"javascript:;\"\r\n class=\"avatar\"\r\n (click)=\"showEditAvatar()\"\r\n [style.background-image]=\"'url(' + userAvatarUrl + ')'\"\r\n ></a>\r\n </div>\r\n <div class=\"text-container\">\r\n <div class=\"field-container\">\r\n <div class=\"field-row\">\r\n <mat-form-field appearance=\"outline\" floatLabel=\"always\">\r\n <mat-label>{{label}}</mat-label>\r\n <textarea\r\n #textarea\r\n name=\"message\"\r\n [placeholder]=\"placeholder\"\r\n matInput\r\n cdkTextareaAutosize\r\n (keydown)=\"onKeyDown($event)\"\r\n (blur)=\"onBlur()\"\r\n [disabled]=\"sending\"\r\n [(ngModel)]=\"text\"></textarea>\r\n </mat-form-field>\r\n <div class=\"options-line\">\r\n <mat-spinner *ngIf=\"sending\" class=\"icon loading\" diameter=\"18\" strokeWidth=\"2\"></mat-spinner>\r\n <div *ngIf=\"sendError\" class=\"error-message\" [class.expanded]=\"expandError\" [matTooltip]=\"sendError.message\" (click)=\"alertError()\">\r\n <mat-icon *ngIf=\"sendError\">error</mat-icon>\r\n {{sendError.message}}\r\n </div>\r\n <div class=\"spacer\"></div>\r\n <div class=\"custom\">\r\n <ng-content></ng-content>\r\n </div>\r\n <banta-attachment-button \r\n *ngIf=\"allowAttachments\"\r\n (addedAttachment)=\"addedAttachment($event)\"\r\n (attachmentError)=\"attachmentError($event)\"\r\n ></banta-attachment-button>\r\n <emoji-selector-button (selected)=\"insertEmoji($event)\"></emoji-selector-button>\r\n </div>\r\n \r\n </div>\r\n <div #autocompleteContainer class=\"autocomplete-container\">\r\n <div #autocomplete class=\"autocomplete\" [class.visible]=\"autocompleteVisible\">\r\n\r\n <div>\r\n <strong>{{completionPrefix}}</strong>...\r\n </div>\r\n <a\r\n mat-button\r\n *ngFor=\"let option of autocompleteOptions; index as index\"\r\n (click)=\"activateAutoComplete(option)\"\r\n [class.active]=\"autoCompleteSelected === index\"\r\n >\r\n {{option.label}}\r\n </a>\r\n </div>\r\n </div>\r\n\r\n\r\n <div *ngIf=\"chatMessageAttachments && chatMessageAttachments.length\" class=\"message-attachments-container\">\r\n <div *ngFor=\"let attachment of chatMessageAttachments; index as attachmentIndex\"\r\n class=\"message-attachment\" [class.with-border]=\"!attachment.url\">\r\n <button (click)=\"removeAttachment(attachmentIndex)\" mat-mini-fab color=\"primary\" class=\"remove-img\">\r\n <mat-icon>close</mat-icon>\r\n </button>\r\n <ng-container *ngIf=\"attachment.transientState?.error\">\r\n <mat-icon class=\"error\">close</mat-icon>\r\n <em class=\"error\">{{attachment.transientState?.errorMessage || 'Error'}}</em>\r\n </ng-container>\r\n <ng-container *ngIf=\"!attachment.transientState?.error\">\r\n <ng-container *ngIf=\"attachment.transientState?.uploading\">\r\n <mat-spinner></mat-spinner>\r\n <em>Uploading...</em>\r\n </ng-container>\r\n <ng-container *ngIf=\"!attachment.transientState?.uploading\">\r\n <img [src]=\"attachment.url\" alt=\"Message Attachment\">\r\n </ng-container>\r\n </ng-container>\r\n </div>\r\n </div>\r\n\r\n </div>\r\n </div>\r\n <div class=\"actions\">\r\n <ng-container *ngIf=\"!user\">\r\n <button\r\n mat-raised-button\r\n color=\"primary\"\r\n type=\"button\"\r\n (click)=\"showSignIn()\"\r\n >{{signInLabel}}</button>\r\n </ng-container>\r\n <ng-container *ngIf=\"user\">\r\n <button\r\n mat-raised-button\r\n class=\"send\"\r\n color=\"primary\"\r\n [disabled]=\"!sendButtonEnabled\"\r\n >\r\n <ng-container *ngIf=\"canComment\">\r\n <mat-icon *ngIf=\"!sending\">chevron_right</mat-icon>\r\n <mat-spinner *ngIf=\"sending\" class=\"icon\" diameter=\"18\" strokeWidth=\"2\"></mat-spinner>\r\n </ng-container>\r\n <span class=\"label\">\r\n <ng-container *ngIf=\"!canComment\">\r\n {{permissionDeniedLabel}}\r\n </ng-container>\r\n <ng-container *ngIf=\"canComment\">\r\n <ng-container *ngIf=\"!sending\">\r\n {{sendLabel}}\r\n </ng-container>\r\n <ng-container *ngIf=\"sending\">\r\n {{sendingLabel}}\r\n </ng-container>\r\n </ng-container>\r\n </span>\r\n </button>\r\n </ng-container>\r\n </div>\r\n</form>",
9855
+ styles: ["@-webkit-keyframes comment-field-appear{0%{transform:translateY(128px);opacity:0}to{transform:translate(0);opacity:1}}@keyframes comment-field-appear{0%{transform:translateY(128px);opacity:0}to{transform:translate(0);opacity:1}}:host{margin:0 2em 0 0;display:block;-webkit-animation-name:comment-field-appear;animation-name:comment-field-appear;-webkit-animation-duration:.8s;animation-duration:.8s;-webkit-animation-delay:.4s;animation-delay:.4s;-webkit-animation-fill-mode:both;animation-fill-mode:both;position:relative;z-index:20}.avatar-container{width:calc(48px + 1.75em);display:flex;justify-content:flex-end;flex-shrink:0}.avatar-container .avatar{width:48px;height:48px;background:#000;border-radius:100%;background-size:cover;background-repeat:no-repeat;background-position:50%;margin-top:.75em;margin-right:.75em}form{display:flex;padding:.5em;align-items:center}form .text-container{position:relative;display:flex;flex-grow:1;min-width:0}form .text-container textarea{font-size:14pt;width:100%}form .text-container textarea[disabled]{opacity:.5}form .text-container mat-spinner.loading{position:absolute;left:.5em;bottom:.5em}form .text-container .options-line{display:flex;align-items:center}form .text-container .options-line>*{flex-shrink:0}form .text-container .options-line .error-message{left:.5em;bottom:.5em;color:#683333;overflow-x:hidden;max-width:1.5em;white-space:nowrap;transition:max-width 2s ease-in-out;text-overflow:ellipsis;overflow:hidden;flex-shrink:1}form .text-container .options-line .error-message.expanded,form .text-container .options-line .error-message:hover{max-width:100%}form .text-container .options-line .error-message mat-icon{vertical-align:middle}form input[type=text]{background:#000;color:#fff;border:1px solid #333;width:100%;height:1em}form .actions{margin-left:1em;flex-shrink:0}form button{display:block;margin:0 0 0 auto}form.new-message{display:flex;align-items:flex-start;min-width:0}form.new-message .field-container{flex-grow:1;display:flex;flex-direction:column;min-width:0}form.new-message mat-form-field{width:100%}form.new-message mat-form-field ::ng-deep .mat-form-field-wrapper{padding-bottom:0}form.new-message button{margin:1.25em 0 0}button.send{min-width:9em}textarea{max-height:7em}.autocomplete-container{width:calc(100% - 2em);position:relative;pointer-events:none;top:-2em}.autocomplete{visibility:hidden;pointer-events:none;position:absolute;background:#333;padding:.5em;display:flex;flex-direction:column;z-index:100}.autocomplete.visible{visibility:visible;pointer-events:auto}.autocomplete a{width:100%;text-align:left}.autocomplete a.active{background:#555}@media (max-width:500px){:host{margin:0}.avatar-container{width:auto;flex-shrink:0}.avatar-container .avatar{width:32px;height:32px;margin-top:1.5em}button.send{min-width:auto;margin-top:1.5em}button.send .label{display:none}}:host-context(.banta-mobile) :host{margin:0}:host-context(.banta-mobile) .avatar-container{width:auto;flex-shrink:0}:host-context(.banta-mobile) .avatar-container .avatar{width:32px;height:32px;margin-top:1.5em}:host-context(.banta-mobile) button.send{min-width:auto;margin-top:1.5em}:host-context(.banta-mobile) button.send .label{display:none}.message-attachments-container{display:flex;gap:20px}.message-attachments-container .message-attachment{width:300px;position:relative;text-align:center}.message-attachments-container .message-attachment.with-border{outline:1px solid #333;padding:1em 0}.message-attachments-container .message-attachment mat-spinner{display:block;margin:0 auto .5em;width:-webkit-fit-content;width:-moz-fit-content;width:fit-content}.message-attachments-container .message-attachment mat-icon.error{display:block;font-size:48px;width:48px;height:48px;margin:0 auto .5em}.message-attachments-container .message-attachment .error{color:#b76363}.message-attachments-container .message-attachment img{width:300px;border-radius:10px}.message-attachments-container .message-attachment .remove-img{position:absolute;right:10px;top:10px;margin:0}.field-row{position:relative}"]
9821
9856
  },] }
9822
9857
  ];
9823
9858
  CommentFieldComponent.propDecorators = {
@@ -9890,11 +9925,15 @@
9890
9925
  function AttachmentButtonComponent(cdnProvider) {
9891
9926
  this.cdnProvider = cdnProvider;
9892
9927
  this._addedAttachment = new rxjs.Subject();
9928
+ this._attachmentError = new rxjs.Subject();
9893
9929
  }
9894
9930
  Object.defineProperty(AttachmentButtonComponent.prototype, "addedAttachment", {
9895
- get: function () {
9896
- return this._addedAttachment.asObservable();
9897
- },
9931
+ get: function () { return this._addedAttachment.asObservable(); },
9932
+ enumerable: false,
9933
+ configurable: true
9934
+ });
9935
+ Object.defineProperty(AttachmentButtonComponent.prototype, "attachmentError", {
9936
+ get: function () { return this._attachmentError.asObservable(); },
9898
9937
  enumerable: false,
9899
9938
  configurable: true
9900
9939
  });
@@ -9903,7 +9942,7 @@
9903
9942
  };
9904
9943
  AttachmentButtonComponent.prototype.fileChange = function (event) {
9905
9944
  return __awaiter(this, void 0, void 0, function () {
9906
- var element, file, publicURL, e_1;
9945
+ var element, file, publicURL, attachment, e_1;
9907
9946
  return __generator(this, function (_a) {
9908
9947
  switch (_a.label) {
9909
9948
  case 0:
@@ -9912,6 +9951,16 @@
9912
9951
  console.log('[Banta] File Added to comment');
9913
9952
  file = element.files[0];
9914
9953
  publicURL = void 0;
9954
+ attachment = {
9955
+ type: file.type,
9956
+ url: undefined,
9957
+ transientState: {
9958
+ uploading: true,
9959
+ error: false,
9960
+ errorMessage: undefined
9961
+ }
9962
+ };
9963
+ this._addedAttachment.next(attachment);
9915
9964
  _a.label = 1;
9916
9965
  case 1:
9917
9966
  _a.trys.push([1, 3, , 4]);
@@ -9921,19 +9970,20 @@
9921
9970
  return [3 /*break*/, 4];
9922
9971
  case 3:
9923
9972
  e_1 = _a.sent();
9973
+ attachment.transientState.error = true;
9974
+ attachment.transientState.errorMessage = "Failed to upload";
9924
9975
  console.error("[Banta] Caught an error while uploading image to CDN:");
9925
9976
  console.error(e_1);
9926
9977
  alert("Failed to upload image. Please try again later.");
9978
+ this._attachmentError.next(attachment);
9927
9979
  return [2 /*return*/];
9928
9980
  case 4:
9929
9981
  // If no URL was returned, then an error must have occurred. Presumably the CDN
9930
9982
  // provider has conveyed an error to the user.
9931
9983
  if (!publicURL)
9932
9984
  return [2 /*return*/];
9933
- this._addedAttachment.next({
9934
- type: file.type,
9935
- url: publicURL
9936
- });
9985
+ attachment.url = publicURL;
9986
+ attachment.transientState = undefined;
9937
9987
  _a.label = 5;
9938
9988
  case 5: return [2 /*return*/];
9939
9989
  }
@@ -9954,7 +10004,8 @@
9954
10004
  ]; };
9955
10005
  AttachmentButtonComponent.propDecorators = {
9956
10006
  fileInput: [{ type: core.ViewChild, args: ['fileUpload', { static: false },] }],
9957
- addedAttachment: [{ type: core.Output }]
10007
+ addedAttachment: [{ type: core.Output }],
10008
+ attachmentError: [{ type: core.Output }]
9958
10009
  };
9959
10010
 
9960
10011
  var COMPONENTS$3 = [