@tolle_/tolle-ui 18.2.28 → 18.2.30
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/esm2022/lib/accordion-item.component.mjs +5 -11
- package/esm2022/lib/accordion.component.mjs +4 -9
- package/esm2022/lib/alert-dialog-dynamic.component.mjs +5 -10
- package/esm2022/lib/alert-dialog.component.mjs +44 -59
- package/esm2022/lib/alert.component.mjs +5 -11
- package/esm2022/lib/aspect-ratio.component.mjs +119 -0
- package/esm2022/lib/attachment.component.mjs +334 -0
- package/esm2022/lib/avatar-fallback.component.mjs +3 -3
- package/esm2022/lib/avatar.component.mjs +4 -9
- package/esm2022/lib/badge.component.mjs +4 -9
- package/esm2022/lib/breadcrumb-item.component.mjs +3 -3
- package/esm2022/lib/breadcrumb-link.component.mjs +3 -3
- package/esm2022/lib/breadcrumb-separator.component.mjs +3 -3
- package/esm2022/lib/breadcrumb.component.mjs +3 -3
- package/esm2022/lib/bubble.component.mjs +301 -0
- package/esm2022/lib/button.component.mjs +5 -11
- package/esm2022/lib/calendar.component.mjs +5 -11
- package/esm2022/lib/card.component.mjs +14 -43
- package/esm2022/lib/carousel/carousel.component.mjs +5 -12
- package/esm2022/lib/chain-of-thought.component.mjs +380 -0
- package/esm2022/lib/chart-pie.component.mjs +386 -0
- package/esm2022/lib/chart.component.mjs +1146 -0
- package/esm2022/lib/chart.service.mjs +555 -0
- package/esm2022/lib/checkbox.component.mjs +5 -11
- package/esm2022/lib/checkpoint.component.mjs +134 -0
- package/esm2022/lib/collapsible.component.mjs +4 -11
- package/esm2022/lib/combobox.component.mjs +372 -0
- package/esm2022/lib/command-dialog.component.mjs +164 -0
- package/esm2022/lib/command.component.mjs +514 -0
- package/esm2022/lib/command.service.mjs +130 -0
- package/esm2022/lib/confirmation.component.mjs +219 -0
- package/esm2022/lib/context-menu-trigger.directive.mjs +36 -0
- package/esm2022/lib/context-menu.component.mjs +460 -0
- package/esm2022/lib/context-menu.service.mjs +121 -0
- package/esm2022/lib/context.component.mjs +377 -0
- package/esm2022/lib/conversation.component.mjs +442 -0
- package/esm2022/lib/country-selector.component.mjs +5 -11
- package/esm2022/lib/data-table.component.mjs +63 -12
- package/esm2022/lib/date-picker.component.mjs +251 -198
- package/esm2022/lib/date-range-picker.component.mjs +224 -116
- package/esm2022/lib/date-time-picker.component.mjs +506 -0
- package/esm2022/lib/direction.component.mjs +79 -0
- package/esm2022/lib/dropdown-item.component.mjs +3 -3
- package/esm2022/lib/dropdown-label.component.mjs +3 -3
- package/esm2022/lib/dropdown-menu.component.mjs +3 -3
- package/esm2022/lib/dropdown-separator.component.mjs +3 -3
- package/esm2022/lib/dropdown-trigger.directive.mjs +6 -4
- package/esm2022/lib/empty-state.component.mjs +4 -9
- package/esm2022/lib/field.component.mjs +394 -0
- package/esm2022/lib/hover-card.component.mjs +6 -16
- package/esm2022/lib/inline-citation.component.mjs +263 -0
- package/esm2022/lib/input-group.component.mjs +418 -0
- package/esm2022/lib/input.component.mjs +5 -11
- package/esm2022/lib/item.component.mjs +243 -0
- package/esm2022/lib/kbd.component.mjs +82 -0
- package/esm2022/lib/label.component.mjs +4 -9
- package/esm2022/lib/marker.component.mjs +135 -0
- package/esm2022/lib/masked-input.component.mjs +5 -11
- package/esm2022/lib/menubar.component.mjs +683 -0
- package/esm2022/lib/message-scroller.component.mjs +353 -0
- package/esm2022/lib/message-scroller.service.mjs +157 -0
- package/esm2022/lib/message.component.mjs +228 -0
- package/esm2022/lib/model-selector.component.mjs +310 -0
- package/esm2022/lib/multi-select.component.mjs +155 -72
- package/esm2022/lib/native-select.component.mjs +201 -0
- package/esm2022/lib/navigation-menu.component.mjs +642 -0
- package/esm2022/lib/otp-group.component.mjs +3 -3
- package/esm2022/lib/otp-slot.component.mjs +4 -9
- package/esm2022/lib/otp.component.mjs +5 -11
- package/esm2022/lib/pagination.component.mjs +7 -10
- package/esm2022/lib/plan.component.mjs +335 -0
- package/esm2022/lib/popover-content.component.mjs +4 -9
- package/esm2022/lib/popover.component.mjs +15 -13
- package/esm2022/lib/progress.component.mjs +4 -9
- package/esm2022/lib/prompt-input.component.mjs +526 -0
- package/esm2022/lib/queue.component.mjs +280 -0
- package/esm2022/lib/radio-group.component.mjs +6 -13
- package/esm2022/lib/radio-item.component.mjs +4 -9
- package/esm2022/lib/range-calendar.component.mjs +5 -11
- package/esm2022/lib/reasoning.component.mjs +335 -0
- package/esm2022/lib/resizable.component.mjs +219 -0
- package/esm2022/lib/segment.component.mjs +17 -7
- package/esm2022/lib/select-group.component.mjs +4 -8
- package/esm2022/lib/select-item.component.mjs +4 -9
- package/esm2022/lib/select-separator.component.mjs +4 -8
- package/esm2022/lib/select.component.mjs +133 -49
- package/esm2022/lib/separator.component.mjs +4 -9
- package/esm2022/lib/sheet.component.mjs +38 -56
- package/esm2022/lib/shimmer.component.mjs +84 -0
- package/esm2022/lib/sidebar.component.mjs +11 -1
- package/esm2022/lib/skeleton.component.mjs +4 -9
- package/esm2022/lib/sources.component.mjs +452 -0
- package/esm2022/lib/spinner.component.mjs +68 -0
- package/esm2022/lib/suggestion.component.mjs +207 -0
- package/esm2022/lib/switch.component.mjs +5 -11
- package/esm2022/lib/table.component.mjs +213 -0
- package/esm2022/lib/tabs.component.mjs +4 -11
- package/esm2022/lib/tag-input.component.mjs +5 -11
- package/esm2022/lib/task.component.mjs +359 -0
- package/esm2022/lib/textarea.component.mjs +5 -11
- package/esm2022/lib/theme.service.mjs +17 -6
- package/esm2022/lib/time-picker.component.mjs +923 -0
- package/esm2022/lib/toast.service.mjs +3 -2
- package/esm2022/lib/toaster.component.mjs +76 -39
- package/esm2022/lib/toggle-group.component.mjs +4 -9
- package/esm2022/lib/toggle.component.mjs +5 -11
- package/esm2022/lib/tool.component.mjs +462 -0
- package/esm2022/lib/typography.component.mjs +83 -0
- package/esm2022/lib/utils/color.mjs +140 -19
- package/esm2022/public-api.mjs +61 -1
- package/fesm2022/tolle-ui.mjs +16457 -1024
- package/fesm2022/tolle-ui.mjs.map +1 -1
- package/lib/alert-dialog.component.d.ts +10 -2
- package/lib/aspect-ratio.component.d.ts +20 -0
- package/lib/attachment.component.d.ts +101 -0
- package/lib/bubble.component.d.ts +112 -0
- package/lib/chain-of-thought.component.d.ts +166 -0
- package/lib/chart-pie.component.d.ts +108 -0
- package/lib/chart.component.d.ts +369 -0
- package/lib/chart.service.d.ts +222 -0
- package/lib/checkpoint.component.d.ts +51 -0
- package/lib/combobox.component.d.ts +90 -0
- package/lib/command-dialog.component.d.ts +53 -0
- package/lib/command.component.d.ts +189 -0
- package/lib/command.service.d.ts +67 -0
- package/lib/confirmation.component.d.ts +70 -0
- package/lib/context-menu-trigger.directive.d.ts +12 -0
- package/lib/context-menu.component.d.ts +41 -0
- package/lib/context-menu.service.d.ts +38 -0
- package/lib/context.component.d.ts +134 -0
- package/lib/conversation.component.d.ts +165 -0
- package/lib/data-table.component.d.ts +27 -1
- package/lib/date-picker.component.d.ts +72 -11
- package/lib/date-range-picker.component.d.ts +63 -6
- package/lib/date-time-picker.component.d.ts +119 -0
- package/lib/direction.component.d.ts +45 -0
- package/lib/field.component.d.ts +201 -0
- package/lib/inline-citation.component.d.ts +103 -0
- package/lib/input-group.component.d.ts +150 -0
- package/lib/item.component.d.ts +154 -0
- package/lib/kbd.component.d.ts +50 -0
- package/lib/marker.component.d.ts +58 -0
- package/lib/menubar.component.d.ts +247 -0
- package/lib/message-scroller.component.d.ts +159 -0
- package/lib/message-scroller.service.d.ts +73 -0
- package/lib/message.component.d.ts +128 -0
- package/lib/model-selector.component.d.ts +103 -0
- package/lib/multi-select.component.d.ts +66 -14
- package/lib/native-select.component.d.ts +57 -0
- package/lib/navigation-menu.component.d.ts +230 -0
- package/lib/plan.component.d.ts +120 -0
- package/lib/popover.component.d.ts +1 -0
- package/lib/prompt-input.component.d.ts +194 -0
- package/lib/queue.component.d.ts +106 -0
- package/lib/reasoning.component.d.ts +127 -0
- package/lib/resizable.component.d.ts +43 -0
- package/lib/segment.component.d.ts +13 -3
- package/lib/select.component.d.ts +37 -5
- package/lib/sheet.component.d.ts +3 -0
- package/lib/shimmer.component.d.ts +38 -0
- package/lib/sidebar.component.d.ts +2 -0
- package/lib/sources.component.d.ts +156 -0
- package/lib/spinner.component.d.ts +29 -0
- package/lib/suggestion.component.d.ts +96 -0
- package/lib/table.component.d.ts +106 -0
- package/lib/task.component.d.ts +151 -0
- package/lib/theme.service.d.ts +9 -0
- package/lib/time-picker.component.d.ts +243 -0
- package/lib/toast.service.d.ts +3 -1
- package/lib/toaster.component.d.ts +26 -9
- package/lib/tool.component.d.ts +158 -0
- package/lib/typography.component.d.ts +32 -0
- package/lib/utils/color.d.ts +19 -5
- package/package.json +1 -1
- package/public-api.d.ts +50 -0
- package/registry/docs-content.json +8497 -1553
- package/registry/llms-full.txt +2679 -318
- package/registry/llms.txt +43 -4
- package/registry/manifest.json +9093 -1739
- package/registry/r/accordion-item.json +1 -1
- package/registry/r/accordion.json +1 -1
- package/registry/r/alert-dialog-dynamic.json +1 -1
- package/registry/r/alert-dialog.json +1 -1
- package/registry/r/alert.json +1 -1
- package/registry/r/attachment.json +23 -0
- package/registry/r/avatar-fallback.json +1 -1
- package/registry/r/avatar.json +1 -1
- package/registry/r/badge.json +1 -1
- package/registry/r/breadcrumb-item.json +1 -1
- package/registry/r/breadcrumb-link.json +1 -1
- package/registry/r/breadcrumb-separator.json +1 -1
- package/registry/r/breadcrumb.json +1 -1
- package/registry/r/bubble.json +23 -0
- package/registry/r/button.json +1 -1
- package/registry/r/calendar.json +1 -1
- package/registry/r/card.json +1 -1
- package/registry/r/carousel.json +1 -1
- package/registry/r/chain-of-thought.json +23 -0
- package/registry/r/chart-pie.json +30 -0
- package/registry/r/chart.json +28 -0
- package/registry/r/checkbox.json +1 -1
- package/registry/r/checkpoint.json +23 -0
- package/registry/r/collapsible.json +1 -1
- package/registry/r/combobox.json +31 -0
- package/registry/r/command-dialog.json +21 -0
- package/registry/r/command.json +26 -0
- package/registry/r/confirmation.json +23 -0
- package/registry/r/context.json +26 -0
- package/registry/r/conversation.json +23 -0
- package/registry/r/country-selector.json +1 -1
- package/registry/r/data-table.json +1 -1
- package/registry/r/date-picker.json +4 -4
- package/registry/r/date-range-picker.json +3 -3
- package/registry/r/date-time-picker.json +28 -0
- package/registry/r/direction.json +16 -0
- package/registry/r/dropdown-item.json +1 -1
- package/registry/r/dropdown-label.json +1 -1
- package/registry/r/dropdown-menu.json +1 -1
- package/registry/r/dropdown-separator.json +1 -1
- package/registry/r/dropdown-trigger.json +1 -1
- package/registry/r/empty-state.json +1 -1
- package/registry/r/field.json +23 -0
- package/registry/r/hover-card.json +1 -1
- package/registry/r/inline-citation.json +26 -0
- package/registry/r/input-group.json +23 -0
- package/registry/r/input.json +1 -1
- package/registry/r/item.json +23 -0
- package/registry/r/kbd.json +23 -0
- package/registry/r/label.json +1 -1
- package/registry/r/marker.json +23 -0
- package/registry/r/masked-input.json +1 -1
- package/registry/r/menubar.json +24 -0
- package/registry/r/message-scroller.json +26 -0
- package/registry/r/message.json +23 -0
- package/registry/r/model-selector.json +32 -0
- package/registry/r/multi-select.json +2 -2
- package/registry/r/native-select.json +23 -0
- package/registry/r/navigation-menu.json +24 -0
- package/registry/r/otp-group.json +1 -1
- package/registry/r/otp-slot.json +1 -1
- package/registry/r/otp.json +1 -1
- package/registry/r/pagination.json +3 -2
- package/registry/r/plan.json +23 -0
- package/registry/r/popover-content.json +1 -1
- package/registry/r/popover.json +1 -1
- package/registry/r/progress.json +1 -1
- package/registry/r/prompt-input.json +23 -0
- package/registry/r/queue.json +23 -0
- package/registry/r/radio-group.json +1 -1
- package/registry/r/radio-item.json +1 -1
- package/registry/r/range-calendar.json +1 -1
- package/registry/r/reasoning.json +23 -0
- package/registry/r/segment.json +1 -1
- package/registry/r/select-group.json +1 -1
- package/registry/r/select-item.json +1 -1
- package/registry/r/select-separator.json +1 -1
- package/registry/r/select.json +4 -3
- package/registry/r/separator.json +1 -1
- package/registry/r/sheet.json +1 -1
- package/registry/r/shimmer.json +23 -0
- package/registry/r/sidebar.json +1 -1
- package/registry/r/skeleton.json +1 -1
- package/registry/r/sources.json +23 -0
- package/registry/r/spinner.json +23 -0
- package/registry/r/suggestion.json +23 -0
- package/registry/r/switch.json +1 -1
- package/registry/r/table.json +21 -0
- package/registry/r/tabs.json +1 -1
- package/registry/r/tag-input.json +1 -1
- package/registry/r/task.json +23 -0
- package/registry/r/textarea.json +1 -1
- package/registry/r/time-picker.json +24 -0
- package/registry/r/toaster.json +2 -2
- package/registry/r/toggle-group.json +1 -1
- package/registry/r/toggle.json +1 -1
- package/registry/r/tool.json +23 -0
- package/registry/r/typography.json +23 -0
- package/registry/registry.json +915 -22
- package/theme.css +109 -8
|
@@ -0,0 +1,353 @@
|
|
|
1
|
+
import { Component, Directive, Input, Output, EventEmitter, ElementRef, NgZone, ChangeDetectionStrategy, ChangeDetectorRef, inject } from '@angular/core';
|
|
2
|
+
import { CommonModule } from '@angular/common';
|
|
3
|
+
import { Subscription } from 'rxjs';
|
|
4
|
+
import { cn } from './utils/cn';
|
|
5
|
+
import { MessageScrollerService } from './message-scroller.service';
|
|
6
|
+
import * as i0 from "@angular/core";
|
|
7
|
+
import * as i1 from "@angular/common";
|
|
8
|
+
/**
|
|
9
|
+
* A scroll container built for conversations, where content streams in while
|
|
10
|
+
* the reader is still reading.
|
|
11
|
+
*
|
|
12
|
+
* A plain overflow-scroll div gets this wrong in both directions: it either
|
|
13
|
+
* pins to the bottom and yanks the page mid-sentence, or it never follows and
|
|
14
|
+
* the reader misses the reply. This handles the cases that are easy to get
|
|
15
|
+
* wrong — following the live edge only while the reader is already there,
|
|
16
|
+
* opening a saved thread at the newest turn rather than the absolute end, and
|
|
17
|
+
* holding position when older history loads in above.
|
|
18
|
+
*
|
|
19
|
+
* ```html
|
|
20
|
+
* <tolle-message-scroller startPosition="last-anchor">
|
|
21
|
+
* <tolle-message-scroller-viewport>
|
|
22
|
+
* <tolle-message-scroller-content>
|
|
23
|
+
* <div tolleMessageScrollerItem [scrollAnchor]="turn.isNewTurn" *ngFor="let turn of turns">
|
|
24
|
+
* …
|
|
25
|
+
* </div>
|
|
26
|
+
* </tolle-message-scroller-content>
|
|
27
|
+
* </tolle-message-scroller-viewport>
|
|
28
|
+
* <tolle-message-scroller-button />
|
|
29
|
+
* </tolle-message-scroller>
|
|
30
|
+
* ```
|
|
31
|
+
* @new
|
|
32
|
+
*/
|
|
33
|
+
export class MessageScrollerComponent {
|
|
34
|
+
cdr = inject(ChangeDetectorRef);
|
|
35
|
+
/**
|
|
36
|
+
* Angular writes a bound `class` input through its styling path, which does
|
|
37
|
+
* not mark an OnPush component dirty — without this hook the component keeps
|
|
38
|
+
* rendering the class it was born with.
|
|
39
|
+
*/
|
|
40
|
+
ngOnChanges() {
|
|
41
|
+
this.cdr.markForCheck();
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Where to land when the transcript first renders. `last-anchor` opens at the
|
|
45
|
+
* newest turn so a long answer starts from its first line.
|
|
46
|
+
* @default 'last-anchor'
|
|
47
|
+
*/
|
|
48
|
+
startPosition = 'last-anchor';
|
|
49
|
+
/**
|
|
50
|
+
* Pixels of the previous turn kept visible above an anchored turn, so the
|
|
51
|
+
* reader can see what the reply is responding to. @default 48
|
|
52
|
+
*/
|
|
53
|
+
scrollPreviousItemPeek = 48;
|
|
54
|
+
/**
|
|
55
|
+
* Distance from the bottom, in px, still treated as the live edge.
|
|
56
|
+
* @default 32
|
|
57
|
+
*/
|
|
58
|
+
threshold = 32;
|
|
59
|
+
/** Extra Tailwind classes merged onto the frame via `cn()` (last-wins). */
|
|
60
|
+
class = '';
|
|
61
|
+
/** Emitted when the reader arrives at, or leaves, the live edge. */
|
|
62
|
+
atBottomChange = new EventEmitter();
|
|
63
|
+
service = inject(MessageScrollerService);
|
|
64
|
+
sub = new Subscription();
|
|
65
|
+
cn = cn;
|
|
66
|
+
ngOnInit() {
|
|
67
|
+
this.service.threshold = this.threshold;
|
|
68
|
+
this.sub.add(this.service.atBottom$.subscribe((v) => this.atBottomChange.emit(v)));
|
|
69
|
+
}
|
|
70
|
+
ngOnDestroy() {
|
|
71
|
+
this.sub.unsubscribe();
|
|
72
|
+
}
|
|
73
|
+
/** Jumps to the newest content and resumes following. */
|
|
74
|
+
scrollToBottom(behavior = 'smooth') {
|
|
75
|
+
this.service.scrollToBottom(behavior);
|
|
76
|
+
}
|
|
77
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: MessageScrollerComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
78
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.14", type: MessageScrollerComponent, isStandalone: true, selector: "tolle-message-scroller", inputs: { startPosition: "startPosition", scrollPreviousItemPeek: "scrollPreviousItemPeek", threshold: "threshold", class: "class" }, outputs: { atBottomChange: "atBottomChange" }, providers: [MessageScrollerService], usesOnChanges: true, ngImport: i0, template: `
|
|
79
|
+
<div [class]="cn('relative flex min-h-0 flex-1 flex-col overflow-hidden', class)">
|
|
80
|
+
<ng-content></ng-content>
|
|
81
|
+
</div>
|
|
82
|
+
`, isInline: true, styles: [":host{display:flex;flex-direction:column;height:100%;min-height:0}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
83
|
+
}
|
|
84
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: MessageScrollerComponent, decorators: [{
|
|
85
|
+
type: Component,
|
|
86
|
+
args: [{ selector: 'tolle-message-scroller', standalone: true, imports: [CommonModule], providers: [MessageScrollerService], changeDetection: ChangeDetectionStrategy.OnPush, template: `
|
|
87
|
+
<div [class]="cn('relative flex min-h-0 flex-1 flex-col overflow-hidden', class)">
|
|
88
|
+
<ng-content></ng-content>
|
|
89
|
+
</div>
|
|
90
|
+
`, styles: [":host{display:flex;flex-direction:column;height:100%;min-height:0}\n"] }]
|
|
91
|
+
}], propDecorators: { startPosition: [{
|
|
92
|
+
type: Input
|
|
93
|
+
}], scrollPreviousItemPeek: [{
|
|
94
|
+
type: Input
|
|
95
|
+
}], threshold: [{
|
|
96
|
+
type: Input
|
|
97
|
+
}], class: [{
|
|
98
|
+
type: Input
|
|
99
|
+
}], atBottomChange: [{
|
|
100
|
+
type: Output
|
|
101
|
+
}] } });
|
|
102
|
+
/**
|
|
103
|
+
* The scrolling element. Watches its own size and its content's size so it can
|
|
104
|
+
* follow streamed text and hold position when history is prepended.
|
|
105
|
+
*/
|
|
106
|
+
export class MessageScrollerViewportComponent {
|
|
107
|
+
cdr = inject(ChangeDetectorRef);
|
|
108
|
+
/**
|
|
109
|
+
* Angular writes a bound `class` input through its styling path, which does
|
|
110
|
+
* not mark an OnPush component dirty — without this hook the component keeps
|
|
111
|
+
* rendering the class it was born with.
|
|
112
|
+
*/
|
|
113
|
+
ngOnChanges() {
|
|
114
|
+
this.cdr.markForCheck();
|
|
115
|
+
}
|
|
116
|
+
/** Extra Tailwind classes merged onto the viewport via `cn()` (last-wins). */
|
|
117
|
+
class = '';
|
|
118
|
+
service = inject(MessageScrollerService);
|
|
119
|
+
scroller = inject(MessageScrollerComponent);
|
|
120
|
+
host = inject(ElementRef);
|
|
121
|
+
zone = inject(NgZone);
|
|
122
|
+
viewportEl = null;
|
|
123
|
+
resizeObserver;
|
|
124
|
+
mutationObserver;
|
|
125
|
+
onScroll = () => this.service.measure();
|
|
126
|
+
/** scrollHeight captured before each DOM mutation, to detect prepends. */
|
|
127
|
+
lastScrollHeight = 0;
|
|
128
|
+
lastScrollTop = 0;
|
|
129
|
+
cn = cn;
|
|
130
|
+
ngAfterViewInit() {
|
|
131
|
+
const el = this.host.nativeElement.querySelector('div');
|
|
132
|
+
if (!el)
|
|
133
|
+
return;
|
|
134
|
+
this.viewportEl = el;
|
|
135
|
+
this.service.registerViewport(el);
|
|
136
|
+
// Scroll fires at frame rate; keeping the listener outside Angular means we
|
|
137
|
+
// only re-enter change detection when a tracked boolean actually flips.
|
|
138
|
+
this.zone.runOutsideAngular(() => {
|
|
139
|
+
el.addEventListener('scroll', this.onScroll, { passive: true });
|
|
140
|
+
if (typeof ResizeObserver !== 'undefined') {
|
|
141
|
+
this.resizeObserver = new ResizeObserver(() => this.service.handleContentGrowth());
|
|
142
|
+
this.resizeObserver.observe(el);
|
|
143
|
+
const content = el.firstElementChild;
|
|
144
|
+
if (content)
|
|
145
|
+
this.resizeObserver.observe(content);
|
|
146
|
+
}
|
|
147
|
+
if (typeof MutationObserver !== 'undefined') {
|
|
148
|
+
this.mutationObserver = new MutationObserver(() => this.afterMutation());
|
|
149
|
+
this.mutationObserver.observe(el, { childList: true, subtree: true, characterData: true });
|
|
150
|
+
}
|
|
151
|
+
});
|
|
152
|
+
this.lastScrollHeight = el.scrollHeight;
|
|
153
|
+
// Wait a frame so projected content has laid out before positioning.
|
|
154
|
+
setTimeout(() => {
|
|
155
|
+
this.service.applyStartPosition(this.scroller.startPosition, this.scroller.scrollPreviousItemPeek);
|
|
156
|
+
this.lastScrollHeight = el.scrollHeight;
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
ngOnDestroy() {
|
|
160
|
+
this.viewportEl?.removeEventListener('scroll', this.onScroll);
|
|
161
|
+
this.resizeObserver?.disconnect();
|
|
162
|
+
this.mutationObserver?.disconnect();
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* Distinguishes content appended below (stream) from content inserted above
|
|
166
|
+
* (history). Growth while the reader has NOT moved but the scrollable area
|
|
167
|
+
* above them has, means a prepend — restore their position rather than let
|
|
168
|
+
* the view lurch.
|
|
169
|
+
*/
|
|
170
|
+
afterMutation() {
|
|
171
|
+
const el = this.viewportEl;
|
|
172
|
+
if (!el)
|
|
173
|
+
return;
|
|
174
|
+
const grew = el.scrollHeight - this.lastScrollHeight;
|
|
175
|
+
const readerMoved = el.scrollTop !== this.lastScrollTop;
|
|
176
|
+
if (grew > 0 && !readerMoved && !this.service.isFollowing && el.scrollTop > 0) {
|
|
177
|
+
this.service.preserveOnPrepend(this.lastScrollHeight);
|
|
178
|
+
}
|
|
179
|
+
else if (grew > 0) {
|
|
180
|
+
this.service.handleContentGrowth();
|
|
181
|
+
}
|
|
182
|
+
this.lastScrollHeight = el.scrollHeight;
|
|
183
|
+
this.lastScrollTop = el.scrollTop;
|
|
184
|
+
this.service.measure();
|
|
185
|
+
}
|
|
186
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: MessageScrollerViewportComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
187
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.14", type: MessageScrollerViewportComponent, isStandalone: true, selector: "tolle-message-scroller-viewport", inputs: { class: "class" }, usesOnChanges: true, ngImport: i0, template: `
|
|
188
|
+
<div #viewport [class]="cn('min-h-0 flex-1 overflow-y-auto overscroll-contain', class)">
|
|
189
|
+
<ng-content></ng-content>
|
|
190
|
+
</div>
|
|
191
|
+
`, isInline: true, styles: [":host{display:flex;flex-direction:column;flex:1 1 auto;min-height:0}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
192
|
+
}
|
|
193
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: MessageScrollerViewportComponent, decorators: [{
|
|
194
|
+
type: Component,
|
|
195
|
+
args: [{ selector: 'tolle-message-scroller-viewport', standalone: true, imports: [CommonModule], changeDetection: ChangeDetectionStrategy.OnPush, template: `
|
|
196
|
+
<div #viewport [class]="cn('min-h-0 flex-1 overflow-y-auto overscroll-contain', class)">
|
|
197
|
+
<ng-content></ng-content>
|
|
198
|
+
</div>
|
|
199
|
+
`, styles: [":host{display:flex;flex-direction:column;flex:1 1 auto;min-height:0}\n"] }]
|
|
200
|
+
}], propDecorators: { class: [{
|
|
201
|
+
type: Input
|
|
202
|
+
}] } });
|
|
203
|
+
/** The transcript itself. Announced politely so streamed replies reach screen readers. */
|
|
204
|
+
export class MessageScrollerContentComponent {
|
|
205
|
+
cdr = inject(ChangeDetectorRef);
|
|
206
|
+
/**
|
|
207
|
+
* Angular writes a bound `class` input through its styling path, which does
|
|
208
|
+
* not mark an OnPush component dirty — without this hook the component keeps
|
|
209
|
+
* rendering the class it was born with.
|
|
210
|
+
*/
|
|
211
|
+
ngOnChanges() {
|
|
212
|
+
this.cdr.markForCheck();
|
|
213
|
+
}
|
|
214
|
+
/** Extra Tailwind classes merged onto the transcript via `cn()` (last-wins). */
|
|
215
|
+
class = '';
|
|
216
|
+
cn = cn;
|
|
217
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: MessageScrollerContentComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
218
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.14", type: MessageScrollerContentComponent, isStandalone: true, selector: "tolle-message-scroller-content", inputs: { class: "class" }, usesOnChanges: true, ngImport: i0, template: `
|
|
219
|
+
<div
|
|
220
|
+
role="log"
|
|
221
|
+
aria-live="polite"
|
|
222
|
+
aria-relevant="additions text"
|
|
223
|
+
[class]="cn('flex flex-col gap-4 p-4', class)"
|
|
224
|
+
>
|
|
225
|
+
<ng-content></ng-content>
|
|
226
|
+
</div>
|
|
227
|
+
`, isInline: true, styles: [":host{display:block}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
228
|
+
}
|
|
229
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: MessageScrollerContentComponent, decorators: [{
|
|
230
|
+
type: Component,
|
|
231
|
+
args: [{ selector: 'tolle-message-scroller-content', standalone: true, imports: [CommonModule], changeDetection: ChangeDetectionStrategy.OnPush, template: `
|
|
232
|
+
<div
|
|
233
|
+
role="log"
|
|
234
|
+
aria-live="polite"
|
|
235
|
+
aria-relevant="additions text"
|
|
236
|
+
[class]="cn('flex flex-col gap-4 p-4', class)"
|
|
237
|
+
>
|
|
238
|
+
<ng-content></ng-content>
|
|
239
|
+
</div>
|
|
240
|
+
`, styles: [":host{display:block}\n"] }]
|
|
241
|
+
}], propDecorators: { class: [{
|
|
242
|
+
type: Input
|
|
243
|
+
}] } });
|
|
244
|
+
/**
|
|
245
|
+
* Marks one row of the transcript. Set `scrollAnchor` on the row that begins a
|
|
246
|
+
* conversation turn so `startPosition="last-anchor"` can open there.
|
|
247
|
+
*/
|
|
248
|
+
export class MessageScrollerItemDirective {
|
|
249
|
+
/** Marks this row as the start of a turn. @default false */
|
|
250
|
+
scrollAnchor = false;
|
|
251
|
+
service = inject(MessageScrollerService);
|
|
252
|
+
host = inject(ElementRef);
|
|
253
|
+
ngOnInit() {
|
|
254
|
+
if (this.scrollAnchor)
|
|
255
|
+
this.service.registerAnchor(this.host.nativeElement);
|
|
256
|
+
}
|
|
257
|
+
ngOnDestroy() {
|
|
258
|
+
this.service.unregisterAnchor(this.host.nativeElement);
|
|
259
|
+
}
|
|
260
|
+
/** Brings this row into view — use for jump-to-message. */
|
|
261
|
+
scrollIntoView(behavior = 'smooth') {
|
|
262
|
+
this.service.scrollToItem(this.host.nativeElement, behavior);
|
|
263
|
+
}
|
|
264
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: MessageScrollerItemDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
265
|
+
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "18.2.14", type: MessageScrollerItemDirective, isStandalone: true, selector: "[tolleMessageScrollerItem]", inputs: { scrollAnchor: "scrollAnchor" }, host: { properties: { "attr.data-scroll-anchor": "scrollAnchor ? '' : null" } }, ngImport: i0 });
|
|
266
|
+
}
|
|
267
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: MessageScrollerItemDirective, decorators: [{
|
|
268
|
+
type: Directive,
|
|
269
|
+
args: [{
|
|
270
|
+
selector: '[tolleMessageScrollerItem]',
|
|
271
|
+
standalone: true,
|
|
272
|
+
host: { '[attr.data-scroll-anchor]': `scrollAnchor ? '' : null` },
|
|
273
|
+
}]
|
|
274
|
+
}], propDecorators: { scrollAnchor: [{
|
|
275
|
+
type: Input
|
|
276
|
+
}] } });
|
|
277
|
+
/** Floating "jump to latest" control. Hidden while the reader is already at the edge. */
|
|
278
|
+
export class MessageScrollerButtonComponent {
|
|
279
|
+
/**
|
|
280
|
+
* Angular writes a bound `class` input through its styling path, which does
|
|
281
|
+
* not mark an OnPush component dirty — without this hook the component keeps
|
|
282
|
+
* rendering the class it was born with.
|
|
283
|
+
*/
|
|
284
|
+
ngOnChanges() {
|
|
285
|
+
this.cdr.markForCheck();
|
|
286
|
+
}
|
|
287
|
+
/** Accessible name for the jump control. @default 'Jump to latest messages' */
|
|
288
|
+
ariaLabel = 'Jump to latest messages';
|
|
289
|
+
/** Extra Tailwind classes merged onto the button via `cn()` (last-wins). */
|
|
290
|
+
class = '';
|
|
291
|
+
service = inject(MessageScrollerService);
|
|
292
|
+
cdr = inject(ChangeDetectorRef);
|
|
293
|
+
sub = new Subscription();
|
|
294
|
+
atBottom = true;
|
|
295
|
+
cn = cn;
|
|
296
|
+
ngOnInit() {
|
|
297
|
+
this.sub.add(this.service.atBottom$.subscribe((v) => {
|
|
298
|
+
this.atBottom = v;
|
|
299
|
+
this.cdr.markForCheck();
|
|
300
|
+
}));
|
|
301
|
+
}
|
|
302
|
+
ngOnDestroy() {
|
|
303
|
+
this.sub.unsubscribe();
|
|
304
|
+
}
|
|
305
|
+
jump() {
|
|
306
|
+
this.service.scrollToBottom();
|
|
307
|
+
}
|
|
308
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: MessageScrollerButtonComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
309
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.14", type: MessageScrollerButtonComponent, isStandalone: true, selector: "tolle-message-scroller-button", inputs: { ariaLabel: "ariaLabel", class: "class" }, usesOnChanges: true, ngImport: i0, template: `
|
|
310
|
+
<button
|
|
311
|
+
*ngIf="!atBottom"
|
|
312
|
+
type="button"
|
|
313
|
+
[attr.aria-label]="ariaLabel"
|
|
314
|
+
[class]="cn(
|
|
315
|
+
'absolute bottom-4 left-1/2 z-10 flex -translate-x-1/2 items-center gap-1.5 rounded-full',
|
|
316
|
+
'border border-border bg-popover px-3 py-1.5 text-xs font-medium text-popover-foreground shadow-md',
|
|
317
|
+
'transition-colors hover:bg-accent hover:text-accent-foreground',
|
|
318
|
+
'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2',
|
|
319
|
+
class
|
|
320
|
+
)"
|
|
321
|
+
(click)="jump()"
|
|
322
|
+
>
|
|
323
|
+
<i class="ri-arrow-down-line" aria-hidden="true"></i>
|
|
324
|
+
<ng-content></ng-content>
|
|
325
|
+
</button>
|
|
326
|
+
`, isInline: true, styles: [":host{display:block}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
327
|
+
}
|
|
328
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: MessageScrollerButtonComponent, decorators: [{
|
|
329
|
+
type: Component,
|
|
330
|
+
args: [{ selector: 'tolle-message-scroller-button', standalone: true, imports: [CommonModule], changeDetection: ChangeDetectionStrategy.OnPush, template: `
|
|
331
|
+
<button
|
|
332
|
+
*ngIf="!atBottom"
|
|
333
|
+
type="button"
|
|
334
|
+
[attr.aria-label]="ariaLabel"
|
|
335
|
+
[class]="cn(
|
|
336
|
+
'absolute bottom-4 left-1/2 z-10 flex -translate-x-1/2 items-center gap-1.5 rounded-full',
|
|
337
|
+
'border border-border bg-popover px-3 py-1.5 text-xs font-medium text-popover-foreground shadow-md',
|
|
338
|
+
'transition-colors hover:bg-accent hover:text-accent-foreground',
|
|
339
|
+
'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2',
|
|
340
|
+
class
|
|
341
|
+
)"
|
|
342
|
+
(click)="jump()"
|
|
343
|
+
>
|
|
344
|
+
<i class="ri-arrow-down-line" aria-hidden="true"></i>
|
|
345
|
+
<ng-content></ng-content>
|
|
346
|
+
</button>
|
|
347
|
+
`, styles: [":host{display:block}\n"] }]
|
|
348
|
+
}], propDecorators: { ariaLabel: [{
|
|
349
|
+
type: Input
|
|
350
|
+
}], class: [{
|
|
351
|
+
type: Input
|
|
352
|
+
}] } });
|
|
353
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibWVzc2FnZS1zY3JvbGxlci5jb21wb25lbnQuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi9wcm9qZWN0cy90b2xsZS9zcmMvbGliL21lc3NhZ2Utc2Nyb2xsZXIuY29tcG9uZW50LnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sRUFBRSxTQUFTLEVBQUUsU0FBUyxFQUFFLEtBQUssRUFBRSxNQUFNLEVBQUUsWUFBWSxFQUFFLFVBQVUsRUFBRSxNQUFNLEVBQUUsdUJBQXVCLEVBQUUsaUJBQWlCLEVBQW9DLE1BQU0sRUFBYSxNQUFNLGVBQWUsQ0FBQztBQUN2TSxPQUFPLEVBQUUsWUFBWSxFQUFFLE1BQU0saUJBQWlCLENBQUM7QUFDL0MsT0FBTyxFQUFFLFlBQVksRUFBRSxNQUFNLE1BQU0sQ0FBQztBQUNwQyxPQUFPLEVBQUUsRUFBRSxFQUFFLE1BQU0sWUFBWSxDQUFDO0FBQ2hDLE9BQU8sRUFBRSxzQkFBc0IsRUFBdUIsTUFBTSw0QkFBNEIsQ0FBQzs7O0FBRXpGOzs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7R0F3Qkc7QUFpQkgsTUFBTSxPQUFPLHdCQUF3QjtJQUNsQixHQUFHLEdBQUcsTUFBTSxDQUFDLGlCQUFpQixDQUFDLENBQUM7SUFFakQ7Ozs7T0FJRztJQUNILFdBQVc7UUFDVCxJQUFJLENBQUMsR0FBRyxDQUFDLFlBQVksRUFBRSxDQUFDO0lBQzFCLENBQUM7SUFFRDs7OztPQUlHO0lBQ00sYUFBYSxHQUF3QixhQUFhLENBQUM7SUFDNUQ7OztPQUdHO0lBQ00sc0JBQXNCLEdBQUcsRUFBRSxDQUFDO0lBQ3JDOzs7T0FHRztJQUNNLFNBQVMsR0FBRyxFQUFFLENBQUM7SUFDeEIsMkVBQTJFO0lBQ2xFLEtBQUssR0FBRyxFQUFFLENBQUM7SUFFcEIsb0VBQW9FO0lBQzFELGNBQWMsR0FBRyxJQUFJLFlBQVksRUFBVyxDQUFDO0lBRXRDLE9BQU8sR0FBRyxNQUFNLENBQUMsc0JBQXNCLENBQUMsQ0FB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RSxDQUFDO0lBQ2hDLENBQUM7d0dBdENVLDhCQUE4Qjs0RkFBOUIsOEJBQThCLGtLQW5CL0I7Ozs7Ozs7Ozs7Ozs7Ozs7O0dBaUJULCtGQW5CUyxZQUFZOzs0RkFxQlgsOEJBQThCO2tCQXpCMUMsU0FBUzsrQkFDRSwrQkFBK0IsY0FFN0IsSUFBSSxXQUNQLENBQUMsWUFBWSxDQUFDLG1CQUNOLHVCQUF1QixDQUFDLE1BQU0sWUFDckM7Ozs7Ozs7Ozs7Ozs7Ozs7O0dBaUJUOzhCQWNRLFNBQVM7c0JBQWpCLEtBQUs7Z0JBRUcsS0FBSztzQkFBYixLQUFLIiwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0IHsgQ29tcG9uZW50LCBEaXJlY3RpdmUsIElucHV0LCBPdXRwdXQsIEV2ZW50RW1pdHRlciwgRWxlbWVudFJlZiwgTmdab25lLCBDaGFuZ2VEZXRlY3Rpb25TdHJhdGVneSwgQ2hhbmdlRGV0ZWN0b3JSZWYsIEFmdGVyVmlld0luaXQsIE9uSW5pdCwgT25EZXN0cm95LCBpbmplY3QsIE9uQ2hhbmdlcyB9IGZyb20gJ0Bhbmd1bGFyL2NvcmUnO1xuaW1wb3J0IHsgQ29tbW9uTW9kdWxlIH0gZnJvbSAnQGFuZ3VsYXIvY29tbW9uJztcbmltcG9ydCB7IFN1YnNjcmlwdGlvbiB9IGZyb20gJ3J4anMnO1xuaW1wb3J0IHsgY24gfSBmcm9tICcuL3V0aWxzL2NuJztcbmltcG9ydCB7IE1lc3NhZ2VTY3JvbGxlclNlcnZpY2UsIFNjcm9sbFN0YXJ0UG9zaXRpb24gfSBmcm9tICcuL21lc3NhZ2Utc2Nyb2xsZXIuc2VydmljZSc7XG5cbi8qKlxuICogQSBzY3JvbGwgY29udGFpbmVyIGJ1aWx0IGZvciBjb252ZXJzYXRpb25zLCB3aGVyZSBjb250ZW50IHN0cmVhbXMgaW4gd2hpbGVcbiAqIHRoZSByZWFkZXIgaXMgc3RpbGwgcmVhZGluZy5cbiAqXG4gKiBBIHBsYWluIG92ZXJmbG93LXNjcm9sbCBkaXYgZ2V0cyB0aGlzIHdyb25nIGluIGJvdGggZGlyZWN0aW9uczogaXQgZWl0aGVyXG4gKiBwaW5zIHRvIHRoZSBib3R0b20gYW5kIHlhbmtzIHRoZSBwYWdlIG1pZC1zZW50ZW5jZSwgb3IgaXQgbmV2ZXIgZm9sbG93cyBhbmRcbiAqIHRoZSByZWFkZXIgbWlzc2VzIHRoZSByZXBseS4gVGhpcyBoYW5kbGVzIHRoZSBjYXNlcyB0aGF0IGFyZSBlYXN5IHRvIGdldFxuICogd3Jvbmcg4oCUIGZvbGxvd2luZyB0aGUgbGl2ZSBlZGdlIG9ubHkgd2hpbGUgdGhlIHJlYWRlciBpcyBhbHJlYWR5IHRoZXJlLFxuICogb3BlbmluZyBhIHNhdmVkIHRocmVhZCBhdCB0aGUgbmV3ZXN0IHR1cm4gcmF0aGVyIHRoYW4gdGhlIGFic29sdXRlIGVuZCwgYW5kXG4gKiBob2xkaW5nIHBvc2l0aW9uIHdoZW4gb2xkZXIgaGlzdG9yeSBsb2FkcyBpbiBhYm92ZS5cbiAqXG4gKiBgYGBodG1sXG4gKiA8dG9sbGUtbWVzc2FnZS1zY3JvbGxlciBzdGFydFBvc2l0aW9uPVwibGFzdC1hbmNob3JcIj5cbiAqICAgPHRvbGxlLW1lc3NhZ2Utc2Nyb2xsZXItdmlld3BvcnQ+XG4gKiAgICAgPHRvbGxlLW1lc3NhZ2Utc2Nyb2xsZXItY29udGVudD5cbiAqICAgICAgIDxkaXYgdG9sbGVNZXNzYWdlU2Nyb2xsZXJJdGVtIFtzY3JvbGxBbmNob3JdPVwidHVybi5pc05ld1R1cm5cIiAqbmdGb3I9XCJsZXQgdHVybiBvZiB0dXJuc1wiPlxuICogICAgICAgICDigKZcbiAqICAgICAgIDwvZGl2PlxuICogICAgIDwvdG9sbGUtbWVzc2FnZS1zY3JvbGxlci1jb250ZW50PlxuICogICA8L3RvbGxlLW1lc3NhZ2Utc2Nyb2xsZXItdmlld3BvcnQ+XG4gKiAgIDx0b2xsZS1tZXNzYWdlLXNjcm9sbGVyLWJ1dHRvbiAvPlxuICogPC90b2xsZS1tZXNzYWdlLXNjcm9sbGVyPlxuICogYGBgXG4gKiBAbmV3XG4gKi9cbkBDb21wb25lbnQoe1xuICBzZWxlY3RvcjogJ3RvbGxlLW1lc3NhZ2Utc2Nyb2xsZXInLFxuICAvLyBUaGUgaG9zdCBzaXRzIGJldHdlZW4gYSBjb25zdW1lci1zaXplZCBwYXJlbnQgYW5kIHRoZSB0ZW1wbGF0ZWQgZmxleFxuICAvLyBmcmFtZSBiZWxvdzsgZGlzcGxheTpibG9jayB3b3VsZCBkcm9wIHRoZSBwYXJlbnQncyBoZWlnaHQgYW5kIHRoZVxuICAvLyBmcmFtZSdzIGBmbGV4LTEgbWluLWgtMGAgd291bGQgaGF2ZSBub3RoaW5nIHRvIHNpemUgYWdhaW5zdC5cbiAgc3R5bGVzOiBbJzpob3N0IHsgZGlzcGxheTogZmxleDsgZmxleC1kaXJlY3Rpb246IGNvbHVtbjsgaGVpZ2h0OiAxMDAlOyBtaW4taGVpZ2h0OiAwOyB9J10sXG4gIHN0YW5kYWxvbmU6IHRydWUsXG4gIGltcG9ydHM6IFtDb21tb25Nb2R1bGVdLFxuICBwcm92aWRlcnM6IFtNZXNzYWdlU2Nyb2xsZXJTZXJ2aWNlXSxcbiAgY2hhbmdlRGV0ZWN0aW9uOiBDaGFuZ2VEZXRlY3Rpb25TdHJhdGVneS5PblB1c2gsXG4gIHRlbXBsYXRlOiBgXG4gICAgPGRpdiBbY2xhc3NdPVwiY24oJ3JlbGF0aXZlIGZsZXggbWluLWgtMCBmbGV4LTEgZmxleC1jb2wgb3ZlcmZsb3ctaGlkZGVuJywgY2xhc3MpXCI+XG4gICAgICA8bmctY29udGVudD48L25nLWNvbnRlbnQ+XG4gICAgPC9kaXY+XG4gIGAsXG59KVxuZXhwb3J0IGNsYXNzIE1lc3NhZ2VTY3JvbGxlckNvbXBvbmVudCBpbXBsZW1lbnRzIE9uQ2hhbmdlcywgT25Jbml0LCBPbkRlc3Ryb3kge1xuICBwcml2YXRlIHJlYWRvbmx5IGNkciA9IGluamVjdChDaGFuZ2VEZXRlY3RvclJlZik7XG5cbiAgLyoqXG4gICAqIEFuZ3VsYXIgd3JpdGVzIGEgYm91bmQgYGNsYXNzYCBpbnB1dCB0aHJvdWdoIGl0cyBzdHlsaW5nIHBhdGgsIHdoaWNoIGRvZXNcbiAgICogbm90IG1hcmsgYW4gT25QdXNoIGNvbXBvbmVudCBkaXJ0eSDigJQgd2l0aG91dCB0aGlzIGhvb2sgdGhlIGNvbXBvbmVudCBrZWVwc1xuICAgKiByZW5kZXJpbmcgdGhlIGNsYXNzIGl0IHdhcyBib3JuIHdpdGguXG4gICAqL1xuICBuZ09uQ2hhbmdlcygpOiB2b2lkIHtcbiAgICB0aGlzLmNkci5tYXJrRm9yQ2hlY2soKTtcbiAgfVxuXG4gIC8qKlxuICAgKiBXaGVyZSB0byBsYW5kIHdoZW4gdGhlIHRyYW5zY3JpcHQgZmlyc3QgcmVuZGVycy4gYGxhc3QtYW5jaG9yYCBvcGVucyBhdCB0aGVcbiAgICogbmV3ZXN0IHR1cm4gc28gYSBsb25nIGFuc3dlciBzdGFydHMgZnJvbSBpdHMgZmlyc3QgbGluZS5cbiAgICogQGRlZmF1bHQgJ2xhc3QtYW5jaG9yJ1xuICAgKi9cbiAgQElucHV0KCkgc3RhcnRQb3NpdGlvbjogU2Nyb2xsU3RhcnRQb3NpdGlvbiA9ICdsYXN0LWFuY2hvcic7XG4gIC8qKlxuICAgKiBQaXhlbHMgb2YgdGhlIHByZXZpb3VzIHR1cm4ga2VwdCB2aXNpYmxlIGFib3ZlIGFuIGFuY2hvcmVkIHR1cm4sIHNvIHRoZVxuICAgKiByZWFkZXIgY2FuIHNlZSB3aGF0IHRoZSByZXBseSBpcyByZXNwb25kaW5nIHRvLiBAZGVmYXVsdCA0OFxuICAgKi9cbiAgQElucHV0KCkgc2Nyb2xsUHJldmlvdXNJdGVtUGVlayA9IDQ4O1xuICAvKipcbiAgICogRGlzdGFuY2UgZnJvbSB0aGUgYm90dG9tLCBpbiBweCwgc3RpbGwgdHJlYXRlZCBhcyB0aGUgbGl2ZSBlZGdlLlxuICAgKiBAZGVmYXVsdCAzMlxuICAgKi9cbiAgQElucHV0KCkgdGhyZXNob2xkID0gMzI7XG4gIC8qKiBFeHRyYSBUYWlsd2luZCBjbGFzc2VzIG1lcmdlZCBvbnRvIHRoZSBmcmFtZSB2aWEgYGNuKClgIChsYXN0LXdpbnMpLiAqL1xuICBASW5wdXQoKSBjbGFzcyA9ICcnO1xuXG4gIC8qKiBFbWl0dGVkIHdoZW4gdGhlIHJlYWRlciBhcnJpdmVzIGF0LCBvciBsZWF2ZXMsIHRoZSBsaXZlIGVkZ2UuICovXG4gIEBPdXRwdXQoKSBhdEJvdHRvbUNoYW5nZSA9IG5ldyBFdmVudEVtaXR0ZXI8Ym9vbGVhbj4oKTtcblxuICBwcml2YXRlIHJlYWRvbmx5IHNlcnZpY2UgPSBpbmplY3QoTWVzc2FnZVNjcm9sbGVyU2VydmljZSk7XG4gIHByaXZhdGUgcmVhZG9ubHkgc3ViID0gbmV3IFN1YnNjcmlwdGlvbigpO1xuICBwcm90ZWN0ZWQgY24gPSBjbjtcblxuICBuZ09uSW5pdCgpOiB2b2lkIHtcbiAgICB0aGlzLnNlcnZpY2UudGhyZXNob2xkID0gdGhpcy50aHJlc2hvbGQ7XG4gICAgdGhpcy5zdWIuYWRkKHRoaXMuc2VydmljZS5hdEJvdHRvbSQuc3Vic2NyaWJlKCh2KSA9PiB0aGlzLmF0Qm90dG9tQ2hhbmdlLmVtaXQodikpKTtcbiAgfVxuXG4gIG5nT25EZXN0cm95KCk6IHZvaWQge1xuICAgIHRoaXMuc3ViLnVuc3Vic2NyaWJlKCk7XG4gIH1cblxuICAvKiogSnVtcHMgdG8gdGhlIG5ld2VzdCBjb250ZW50IGFuZCByZXN1bWVzIGZvbGxvd2luZy4gKi9cbiAgc2Nyb2xsVG9Cb3R0b20oYmVoYXZpb3I6IFNjcm9sbEJlaGF2aW9yID0gJ3Ntb290aCcpOiB2b2lkIHtcbiAgICB0aGlzLnNlcnZpY2Uuc2Nyb2xsVG9Cb3R0b20oYmVoYXZpb3IpO1xuICB9XG59XG5cbi8qKlxuICogVGhlIHNjcm9sbGluZyBlbGVtZW50LiBXYXRjaGVzIGl0cyBvd24gc2l6ZSBhbmQgaXRzIGNvbnRlbnQncyBzaXplIHNvIGl0IGNhblxuICogZm9sbG93IHN0cmVhbWVkIHRleHQgYW5kIGhvbGQgcG9zaXRpb24gd2hlbiBoaXN0b3J5IGlzIHByZXBlbmRlZC5cbiAqL1xuQENvbXBvbmVudCh7XG4gIHNlbGVjdG9yOiAndG9sbGUtbWVzc2FnZS1zY3JvbGxlci12aWV3cG9ydCcsXG4gIC8vIFNhbWUgcmVhc29uaW5nIGFzIHRvbGxlLW1lc3NhZ2Utc2Nyb2xsZXI6IHRoaXMgaG9zdCBpcyB0aGUgZmxleCBpdGVtIHRoZVxuICAvLyBzY3JvbGxhYmxlIGlubmVyIGRpdiBzaXplcyBhZ2FpbnN0LCBzbyBpdCBtdXN0IGpvaW4gdGhlIGZsZXggY2hhaW4gaXRzZWxmLlxuICBzdHlsZXM6IFsnOmhvc3QgeyBkaXNwbGF5OiBmbGV4OyBmbGV4LWRpcmVjdGlvbjogY29sdW1uOyBmbGV4OiAxIDEgYXV0bzsgbWluLWhlaWdodDogMDsgfSddLFxuICBzdGFuZGFsb25lOiB0cnVlLFxuICBpbXBvcnRzOiBbQ29tbW9uTW9kdWxlXSxcbiAgY2hhbmdlRGV0ZWN0aW9uOiBDaGFuZ2VEZXRlY3Rpb25TdHJhdGVneS5PblB1c2gsXG4gIHRlbXBsYXRlOiBgXG4gICAgPGRpdiAjdmlld3BvcnQgW2NsYXNzXT1cImNuKCdtaW4taC0wIGZsZXgtMSBvdmVyZmxvdy15LWF1dG8gb3ZlcnNjcm9sbC1jb250YWluJywgY2xhc3MpXCI+XG4gICAgICA8bmctY29udGVudD48L25nLWNvbnRlbnQ+XG4gICAgPC9kaXY+XG4gIGAsXG59KVxuZXhwb3J0IGNsYXNzIE1lc3NhZ2VTY3JvbGxlclZpZXdwb3J0Q29tcG9uZW50IGltcGxlbWVudHMgT25DaGFuZ2VzLCBBZnRlclZpZXdJbml0LCBPbkRlc3Ryb3kge1xuICBwcml2YXRlIHJlYWRvbmx5IGNkciA9IGluamVjdChDaGFuZ2VEZXRlY3RvclJlZik7XG5cbiAgLyoqXG4gICAqIEFuZ3VsYXIgd3JpdGVzIGEgYm91bmQgYGNsYXNzYCBpbnB1dCB0aHJvdWdoIGl0cyBzdHlsaW5nIHBhdGgsIHdoaWNoIGRvZXNcbiAgICogbm90IG1hcmsgYW4gT25QdXNoIGNvbXBvbmVudCBkaXJ0eSDigJQgd2l0aG91dCB0aGlzIGhvb2sgdGhlIGNvbXBvbmVudCBrZWVwc1xuICAgKiByZW5kZXJpbmcgdGhlIGNsYXNzIGl0IHdhcyBib3JuIHdpdGguXG4gICAqL1xuICBuZ09uQ2hhbmdlcygpOiB2b2lkIHtcbiAgICB0aGlzLmNkci5tYXJrRm9yQ2hlY2soKTtcbiAgfVxuXG4gIC8qKiBFeHRyYSBUYWlsd2luZCBjbGFzc2VzIG1lcmdlZCBvbnRvIHRoZSB2aWV3cG9ydCB2aWEgYGNuKClgIChsYXN0LXdpbnMpLiAqL1xuICBASW5wdXQoKSBjbGFzcyA9ICcnO1xuXG4gIHByaXZhdGUgcmVhZG9ubHkgc2VydmljZSA9IGluamVjdChNZXNzYWdlU2Nyb2xsZXJTZXJ2aWNlKTtcbiAgcHJpdmF0ZSByZWFkb25seSBzY3JvbGxlciA9IGluamVjdChNZXNzYWdlU2Nyb2xsZXJDb21wb25lbnQpO1xuICBwcml2YXRlIHJlYWRvbmx5IGhvc3QgPSBpbmplY3Q8RWxlbWVudFJlZjxIVE1MRWxlbWVudD4+KEVsZW1lbnRSZWYpO1xuICBwcml2YXRlIHJlYWRvbmx5IHpvbmUgPSBpbmplY3QoTmdab25lKTtcblxuICBwcml2YXRlIHZpZXdwb3J0RWw6IEhUTUxFbGVtZW50IHwgbnVsbCA9IG51bGw7XG4gIHByaXZhdGUgcmVzaXplT2JzZXJ2ZXI/OiBSZXNpemVPYnNlcnZlcjtcbiAgcHJpdmF0ZSBtdXRhdGlvbk9ic2VydmVyPzogTXV0YXRpb25PYnNlcnZlcjtcbiAgcHJpdmF0ZSBvblNjcm9sbCA9ICgpID0+IHRoaXMuc2VydmljZS5tZWFzdXJlKCk7XG4gIC8qKiBzY3JvbGxIZWlnaHQgY2FwdHVyZWQgYmVmb3JlIGVhY2ggRE9NIG11dGF0aW9uLCB0byBkZXRlY3QgcHJlcGVuZHMuICovXG4gIHByaXZhdGUgbGFzdFNjcm9sbEhlaWdodCA9IDA7XG4gIHByaXZhdGUgbGFzdFNjcm9sbFRvcCA9IDA7XG4gIHByb3RlY3RlZCBjbiA9IGNuO1xuXG4gIG5nQWZ0ZXJWaWV3SW5pdCgpOiB2b2lkIHtcbiAgICBjb25zdCBlbCA9IHRoaXMuaG9zdC5uYXRpdmVFbGVtZW50LnF1ZXJ5U2VsZWN0b3I8SFRNTEVsZW1lbnQ+KCdkaXYnKTtcbiAgICBpZiAoIWVsKSByZXR1cm47XG4gICAgdGhpcy52aWV3cG9ydEVsID0gZWw7XG4gICAgdGhpcy5zZXJ2aWNlLnJlZ2lzdGVyVmlld3BvcnQoZWwpO1xuXG4gICAgLy8gU2Nyb2xsIGZpcmVzIGF0IGZyYW1lIHJhdGU7IGtlZXBpbmcgdGhlIGxpc3RlbmVyIG91dHNpZGUgQW5ndWxhciBtZWFucyB3ZVxuICAgIC8vIG9ubHkgcmUtZW50ZXIgY2hhbmdlIGRldGVjdGlvbiB3aGVuIGEgdHJhY2tlZCBib29sZWFuIGFjdHVhbGx5IGZsaXBzLlxuICAgIHRoaXMuem9uZS5ydW5PdXRzaWRlQW5ndWxhcigoKSA9PiB7XG4gICAgICBlbC5hZGRFdmVudExpc3RlbmVyKCdzY3JvbGwnLCB0aGlzLm9uU2Nyb2xsLCB7IHBhc3NpdmU6IHRydWUgfSk7XG5cbiAgICAgIGlmICh0eXBlb2YgUmVzaXplT2JzZXJ2ZXIgIT09ICd1bmRlZmluZWQnKSB7XG4gICAgICAgIHRoaXMucmVzaXplT2JzZXJ2ZXIgPSBuZXcgUmVzaXplT2JzZXJ2ZXIoKCkgPT4gdGhpcy5zZXJ2aWNlLmhhbmRsZUNvbnRlbnRHcm93dGgoKSk7XG4gICAgICAgIHRoaXMucmVzaXplT2JzZXJ2ZXIub2JzZXJ2ZShlbCk7XG4gICAgICAgIGNvbnN0IGNvbnRlbnQgPSBlbC5maXJzdEVsZW1lbnRDaGlsZDtcbiAgICAgICAgaWYgKGNvbnRlbnQpIHRoaXMucmVzaXplT2JzZXJ2ZXIub2JzZXJ2ZShjb250ZW50KTtcbiAgICAgIH1cblxuICAgICAgaWYgKHR5cGVvZiBNdXRhdGlvbk9ic2VydmVyICE9PSAndW5kZWZpbmVkJykge1xuICAgICAgICB0aGlzLm11dGF0aW9uT2JzZXJ2ZXIgPSBuZXcgTXV0YXRpb25PYnNlcnZlcigoKSA9PiB0aGlzLmFmdGVyTXV0YXRpb24oKSk7XG4gICAgICAgIHRoaXMubXV0YXRpb25PYnNlcnZlci5vYnNlcnZlKGVsLCB7IGNoaWxkTGlzdDogdHJ1ZSwgc3VidHJlZTogdHJ1ZSwgY2hhcmFjdGVyRGF0YTogdHJ1ZSB9KTtcbiAgICAgIH1cbiAgICB9KTtcblxuICAgIHRoaXMubGFzdFNjcm9sbEhlaWdodCA9IGVsLnNjcm9sbEhlaWdodDtcblxuICAgIC8vIFdhaXQgYSBmcmFtZSBzbyBwcm9qZWN0ZWQgY29udGVudCBoYXMgbGFpZCBvdXQgYmVmb3JlIHBvc2l0aW9uaW5nLlxuICAgIHNldFRpbWVvdXQoKCkgPT4ge1xuICAgICAgdGhpcy5zZXJ2aWNlLmFwcGx5U3RhcnRQb3NpdGlvbihcbiAgICAgICAgdGhpcy5zY3JvbGxlci5zdGFydFBvc2l0aW9uLFxuICAgICAgICB0aGlzLnNjcm9sbGVyLnNjcm9sbFByZXZpb3VzSXRlbVBlZWtcbiAgICAgICk7XG4gICAgICB0aGlzLmxhc3RTY3JvbGxIZWlnaHQgPSBlbC5zY3JvbGxIZWlnaHQ7XG4gICAgfSk7XG4gIH1cblxuICBuZ09uRGVzdHJveSgpOiB2b2lkIHtcbiAgICB0aGlzLnZpZXdwb3J0RWw/LnJlbW92ZUV2ZW50TGlzdGVuZXIoJ3Njcm9sbCcsIHRoaXMub25TY3JvbGwpO1xuICAgIHRoaXMucmVzaXplT2JzZXJ2ZXI/LmRpc2Nvbm5lY3QoKTtcbiAgICB0aGlzLm11dGF0aW9uT2JzZXJ2ZXI/LmRpc2Nvbm5lY3QoKTtcbiAgfVxuXG4gIC8qKlxuICAgKiBEaXN0aW5ndWlzaGVzIGNvbnRlbnQgYXBwZW5kZWQgYmVsb3cgKHN0cmVhbSkgZnJvbSBjb250ZW50IGluc2VydGVkIGFib3ZlXG4gICAqIChoaXN0b3J5KS4gR3Jvd3RoIHdoaWxlIHRoZSByZWFkZXIgaGFzIE5PVCBtb3ZlZCBidXQgdGhlIHNjcm9sbGFibGUgYXJlYVxuICAgKiBhYm92ZSB0aGVtIGhhcywgbWVhbnMgYSBwcmVwZW5kIOKAlCByZXN0b3JlIHRoZWlyIHBvc2l0aW9uIHJhdGhlciB0aGFuIGxldFxuICAgKiB0aGUgdmlldyBsdXJjaC5cbiAgICovXG4gIHByaXZhdGUgYWZ0ZXJNdXRhdGlvbigpOiB2b2lkIHtcbiAgICBjb25zdCBlbCA9IHRoaXMudmlld3BvcnRFbDtcbiAgICBpZiAoIWVsKSByZXR1cm47XG5cbiAgICBjb25zdCBncmV3ID0gZWwuc2Nyb2xsSGVpZ2h0IC0gdGhpcy5sYXN0U2Nyb2xsSGVpZ2h0O1xuICAgIGNvbnN0IHJlYWRlck1vdmVkID0gZWwuc2Nyb2xsVG9wICE9PSB0aGlzLmxhc3RTY3JvbGxUb3A7XG5cbiAgICBpZiAoZ3JldyA+IDAgJiYgIXJlYWRlck1vdmVkICYmICF0aGlzLnNlcnZpY2UuaXNGb2xsb3dpbmcgJiYgZWwuc2Nyb2xsVG9wID4gMCkge1xuICAgICAgdGhpcy5zZXJ2aWNlLnByZXNlcnZlT25QcmVwZW5kKHRoaXMubGFzdFNjcm9sbEhlaWdodCk7XG4gICAgfSBlbHNlIGlmIChncmV3ID4gMCkge1xuICAgICAgdGhpcy5zZXJ2aWNlLmhhbmRsZUNvbnRlbnRHcm93dGgoKTtcbiAgICB9XG5cbiAgICB0aGlzLmxhc3RTY3JvbGxIZWlnaHQgPSBlbC5zY3JvbGxIZWlnaHQ7XG4gICAgdGhpcy5sYXN0U2Nyb2xsVG9wID0gZWwuc2Nyb2xsVG9wO1xuICAgIHRoaXMuc2VydmljZS5tZWFzdXJlKCk7XG4gIH1cbn1cblxuLyoqIFRoZSB0cmFuc2NyaXB0IGl0c2VsZi4gQW5ub3VuY2VkIHBvbGl0ZWx5IHNvIHN0cmVhbWVkIHJlcGxpZXMgcmVhY2ggc2NyZWVuIHJlYWRlcnMuICovXG5AQ29tcG9uZW50KHtcbiAgc2VsZWN0b3I6ICd0b2xsZS1tZXNzYWdlLXNjcm9sbGVyLWNvbnRlbnQnLFxuICBzdHlsZXM6IFsnOmhvc3QgeyBkaXNwbGF5OiBibG9jazsgfSddLFxuICBzdGFuZGFsb25lOiB0cnVlLFxuICBpbXBvcnRzOiBbQ29tbW9uTW9kdWxlXSxcbiAgY2hhbmdlRGV0ZWN0aW9uOiBDaGFuZ2VEZXRlY3Rpb25TdHJhdGVneS5PblB1c2gsXG4gIHRlbXBsYXRlOiBgXG4gICAgPGRpdlxuICAgICAgcm9sZT1cImxvZ1wiXG4gICAgICBhcmlhLWxpdmU9XCJwb2xpdGVcIlxuICAgICAgYXJpYS1yZWxldmFudD1cImFkZGl0aW9ucyB0ZXh0XCJcbiAgICAgIFtjbGFzc109XCJjbignZmxleCBmbGV4LWNvbCBnYXAtNCBwLTQnLCBjbGFzcylcIlxuICAgID5cbiAgICAgIDxuZy1jb250ZW50PjwvbmctY29udGVudD5cbiAgICA8L2Rpdj5cbiAgYCxcbn0pXG5leHBvcnQgY2xhc3MgTWVzc2FnZVNjcm9sbGVyQ29udGVudENvbXBvbmVudCAgaW1wbGVtZW50cyBPbkNoYW5nZXN7XG4gIHByaXZhdGUgcmVhZG9ubHkgY2RyID0gaW5qZWN0KENoYW5nZURldGVjdG9yUmVmKTtcblxuICAvKipcbiAgICogQW5ndWxhciB3cml0ZXMgYSBib3VuZCBgY2xhc3NgIGlucHV0IHRocm91Z2ggaXRzIHN0eWxpbmcgcGF0aCwgd2hpY2ggZG9lc1xuICAgKiBub3QgbWFyayBhbiBPblB1c2ggY29tcG9uZW50IGRpcnR5IOKAlCB3aXRob3V0IHRoaXMgaG9vayB0aGUgY29tcG9uZW50IGtlZXBzXG4gICAqIHJlbmRlcmluZyB0aGUgY2xhc3MgaXQgd2FzIGJvcm4gd2l0aC5cbiAgICovXG4gIG5nT25DaGFuZ2VzKCk6IHZvaWQge1xuICAgIHRoaXMuY2RyLm1hcmtGb3JDaGVjaygpO1xuICB9XG5cbiAgLyoqIEV4dHJhIFRhaWx3aW5kIGNsYXNzZXMgbWVyZ2VkIG9udG8gdGhlIHRyYW5zY3JpcHQgdmlhIGBjbigpYCAobGFzdC13aW5zKS4gKi9cbiAgQElucHV0KCkgY2xhc3MgPSAnJztcbiAgcHJvdGVjdGVkIGNuID0gY247XG59XG5cbi8qKlxuICogTWFya3Mgb25lIHJvdyBvZiB0aGUgdHJhbnNjcmlwdC4gU2V0IGBzY3JvbGxBbmNob3JgIG9uIHRoZSByb3cgdGhhdCBiZWdpbnMgYVxuICogY29udmVyc2F0aW9uIHR1cm4gc28gYHN0YXJ0UG9zaXRpb249XCJsYXN0LWFuY2hvclwiYCBjYW4gb3BlbiB0aGVyZS5cbiAqL1xuQERpcmVjdGl2ZSh7XG4gIHNlbGVjdG9yOiAnW3RvbGxlTWVzc2FnZVNjcm9sbGVySXRlbV0nLFxuICBzdGFuZGFsb25lOiB0cnVlLFxuICBob3N0OiB7ICdbYXR0ci5kYXRhLXNjcm9sbC1hbmNob3JdJzogYHNjcm9sbEFuY2hvciA/ICcnIDogbnVsbGAgfSxcbn0pXG5leHBvcnQgY2xhc3MgTWVzc2FnZVNjcm9sbGVySXRlbURpcmVjdGl2ZSBpbXBsZW1lbnRzIE9uSW5pdCwgT25EZXN0cm95IHtcbiAgLyoqIE1hcmtzIHRoaXMgcm93IGFzIHRoZSBzdGFydCBvZiBhIHR1cm4uIEBkZWZhdWx0IGZhbHNlICovXG4gIEBJbnB1dCgpIHNjcm9sbEFuY2hvciA9IGZhbHNlO1xuXG4gIHByaXZhdGUgcmVhZG9ubHkgc2VydmljZSA9IGluamVjdChNZXNzYWdlU2Nyb2xsZXJTZXJ2aWNlKTtcbiAgcHJpdmF0ZSByZWFkb25seSBob3N0ID0gaW5qZWN0PEVsZW1lbnRSZWY8SFRNTEVsZW1lbnQ+PihFbGVtZW50UmVmKTtcblxuICBuZ09uSW5pdCgpOiB2b2lkIHtcbiAgICBpZiAodGhpcy5zY3JvbGxBbmNob3IpIHRoaXMuc2VydmljZS5yZWdpc3RlckFuY2hvcih0aGlzLmhvc3QubmF0aXZlRWxlbWVudCk7XG4gIH1cblxuICBuZ09uRGVzdHJveSgpOiB2b2lkIHtcbiAgICB0aGlzLnNlcnZpY2UudW5yZWdpc3RlckFuY2hvcih0aGlzLmhvc3QubmF0aXZlRWxlbWVudCk7XG4gIH1cblxuICAvKiogQnJpbmdzIHRoaXMgcm93IGludG8gdmlldyDigJQgdXNlIGZvciBqdW1wLXRvLW1lc3NhZ2UuICovXG4gIHNjcm9sbEludG9WaWV3KGJlaGF2aW9yOiBTY3JvbGxCZWhhdmlvciA9ICdzbW9vdGgnKTogdm9pZCB7XG4gICAgdGhpcy5zZXJ2aWNlLnNjcm9sbFRvSXRlbSh0aGlzLmhvc3QubmF0aXZlRWxlbWVudCwgYmVoYXZpb3IpO1xuICB9XG59XG5cbi8qKiBGbG9hdGluZyBcImp1bXAgdG8gbGF0ZXN0XCIgY29udHJvbC4gSGlkZGVuIHdoaWxlIHRoZSByZWFkZXIgaXMgYWxyZWFkeSBhdCB0aGUgZWRnZS4gKi9cbkBDb21wb25lbnQoe1xuICBzZWxlY3RvcjogJ3RvbGxlLW1lc3NhZ2Utc2Nyb2xsZXItYnV0dG9uJyxcbiAgc3R5bGVzOiBbJzpob3N0IHsgZGlzcGxheTogYmxvY2s7IH0nXSxcbiAgc3RhbmRhbG9uZTogdHJ1ZSxcbiAgaW1wb3J0czogW0NvbW1vbk1vZHVsZV0sXG4gIGNoYW5nZURldGVjdGlvbjogQ2hhbmdlRGV0ZWN0aW9uU3RyYXRlZ3kuT25QdXNoLFxuICB0ZW1wbGF0ZTogYFxuICAgIDxidXR0b25cbiAgICAgICpuZ0lmPVwiIWF0Qm90dG9tXCJcbiAgICAgIHR5cGU9XCJidXR0b25cIlxuICAgICAgW2F0dHIuYXJpYS1sYWJlbF09XCJhcmlhTGFiZWxcIlxuICAgICAgW2NsYXNzXT1cImNuKFxuICAgICAgICAnYWJzb2x1dGUgYm90dG9tLTQgbGVmdC0xLzIgei0xMCBmbGV4IC10cmFuc2xhdGUteC0xLzIgaXRlbXMtY2VudGVyIGdhcC0xLjUgcm91bmRlZC1mdWxsJyxcbiAgICAgICAgJ2JvcmRlciBib3JkZXItYm9yZGVyIGJnLXBvcG92ZXIgcHgtMyBweS0xLjUgdGV4dC14cyBmb250LW1lZGl1bSB0ZXh0LXBvcG92ZXItZm9yZWdyb3VuZCBzaGFkb3ctbWQnLFxuICAgICAgICAndHJhbnNpdGlvbi1jb2xvcnMgaG92ZXI6YmctYWNjZW50IGhvdmVyOnRleHQtYWNjZW50LWZvcmVncm91bmQnLFxuICAgICAgICAnZm9jdXMtdmlzaWJsZTpvdXRsaW5lLW5vbmUgZm9jdXMtdmlzaWJsZTpyaW5nLTIgZm9jdXMtdmlzaWJsZTpyaW5nLXJpbmcgZm9jdXMtdmlzaWJsZTpyaW5nLW9mZnNldC0yJyxcbiAgICAgICAgY2xhc3NcbiAgICAgIClcIlxuICAgICAgKGNsaWNrKT1cImp1bXAoKVwiXG4gICAgPlxuICAgICAgPGkgY2xhc3M9XCJyaS1hcnJvdy1kb3duLWxpbmVcIiBhcmlhLWhpZGRlbj1cInRydWVcIj48L2k+XG4gICAgICA8bmctY29udGVudD48L25nLWNvbnRlbnQ+XG4gICAgPC9idXR0b24+XG4gIGAsXG59KVxuZXhwb3J0IGNsYXNzIE1lc3NhZ2VTY3JvbGxlckJ1dHRvbkNvbXBvbmVudCBpbXBsZW1lbnRzIE9uQ2hhbmdlcywgT25Jbml0LCBPbkRlc3Ryb3kge1xuXG4gIC8qKlxuICAgKiBBbmd1bGFyIHdyaXRlcyBhIGJvdW5kIGBjbGFzc2AgaW5wdXQgdGhyb3VnaCBpdHMgc3R5bGluZyBwYXRoLCB3aGljaCBkb2VzXG4gICAqIG5vdCBtYXJrIGFuIE9uUHVzaCBjb21wb25lbnQgZGlydHkg4oCUIHdpdGhvdXQgdGhpcyBob29rIHRoZSBjb21wb25lbnQga2VlcHNcbiAgICogcmVuZGVyaW5nIHRoZSBjbGFzcyBpdCB3YXMgYm9ybiB3aXRoLlxuICAgKi9cbiAgbmdPbkNoYW5nZXMoKTogdm9pZCB7XG4gICAgdGhpcy5jZHIubWFya0ZvckNoZWNrKCk7XG4gIH1cblxuICAvKiogQWNjZXNzaWJsZSBuYW1lIGZvciB0aGUganVtcCBjb250cm9sLiBAZGVmYXVsdCAnSnVtcCB0byBsYXRlc3QgbWVzc2FnZXMnICovXG4gIEBJbnB1dCgpIGFyaWFMYWJlbCA9ICdKdW1wIHRvIGxhdGVzdCBtZXNzYWdlcyc7XG4gIC8qKiBFeHRyYSBUYWlsd2luZCBjbGFzc2VzIG1lcmdlZCBvbnRvIHRoZSBidXR0b24gdmlhIGBjbigpYCAobGFzdC13aW5zKS4gKi9cbiAgQElucHV0KCkgY2xhc3MgPSAnJztcblxuICBwcml2YXRlIHJlYWRvbmx5IHNlcnZpY2UgPSBpbmplY3QoTWVzc2FnZVNjcm9sbGVyU2VydmljZSk7XG4gIHByaXZhdGUgcmVhZG9ubHkgY2RyID0gaW5qZWN0KENoYW5nZURldGVjdG9yUmVmKTtcbiAgcHJpdmF0ZSByZWFkb25seSBzdWIgPSBuZXcgU3Vic2NyaXB0aW9uKCk7XG5cbiAgYXRCb3R0b20gPSB0cnVlO1xuICBwcm90ZWN0ZWQgY24gPSBjbjtcblxuICBuZ09uSW5pdCgpOiB2b2lkIHtcbiAgICB0aGlzLnN1Yi5hZGQoXG4gICAgICB0aGlzLnNlcnZpY2UuYXRCb3R0b20kLnN1YnNjcmliZSgodikgPT4ge1xuICAgICAgICB0aGlzLmF0Qm90dG9tID0gdjtcbiAgICAgICAgdGhpcy5jZHIubWFya0ZvckNoZWNrKCk7XG4gICAgICB9KVxuICAgICk7XG4gIH1cblxuICBuZ09uRGVzdHJveSgpOiB2b2lkIHtcbiAgICB0aGlzLnN1Yi51bnN1YnNjcmliZSgpO1xuICB9XG5cbiAganVtcCgpOiB2b2lkIHtcbiAgICB0aGlzLnNlcnZpY2Uuc2Nyb2xsVG9Cb3R0b20oKTtcbiAgfVxufVxuIl19
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import { Injectable } from '@angular/core';
|
|
2
|
+
import { BehaviorSubject } from 'rxjs';
|
|
3
|
+
import * as i0 from "@angular/core";
|
|
4
|
+
/**
|
|
5
|
+
* Scroll state for one `tolle-message-scroller`, shared with its viewport,
|
|
6
|
+
* items and buttons.
|
|
7
|
+
*
|
|
8
|
+
* The governing rule is: **never move the reader against their intent.** Every
|
|
9
|
+
* automatic scroll here is gated on the reader already being at the live edge.
|
|
10
|
+
* The moment they scroll up, auto-scroll switches off and stays off until they
|
|
11
|
+
* come back down or explicitly jump.
|
|
12
|
+
*
|
|
13
|
+
* Positions are read from and written to the DOM directly rather than mirrored
|
|
14
|
+
* into component state — scroll events fire far too often to run change
|
|
15
|
+
* detection on, and the values would be stale by the time they landed.
|
|
16
|
+
*/
|
|
17
|
+
export class MessageScrollerService {
|
|
18
|
+
/** The scrollable element, set by the viewport once it renders. */
|
|
19
|
+
viewport = null;
|
|
20
|
+
/** Rows registered as the start of a conversation turn, in DOM order. */
|
|
21
|
+
anchors = [];
|
|
22
|
+
atBottomSubject = new BehaviorSubject(true);
|
|
23
|
+
/** True while the reader is at (or within `threshold` of) the live edge. */
|
|
24
|
+
atBottom$ = this.atBottomSubject.asObservable();
|
|
25
|
+
followingSubject = new BehaviorSubject(true);
|
|
26
|
+
/**
|
|
27
|
+
* True while new content should pull the view along. Set false the instant
|
|
28
|
+
* the reader scrolls away from the bottom.
|
|
29
|
+
*/
|
|
30
|
+
following$ = this.followingSubject.asObservable();
|
|
31
|
+
/** Distance from the bottom, in px, still considered "at the live edge". */
|
|
32
|
+
threshold = 32;
|
|
33
|
+
get atBottom() {
|
|
34
|
+
return this.atBottomSubject.value;
|
|
35
|
+
}
|
|
36
|
+
get isFollowing() {
|
|
37
|
+
return this.followingSubject.value;
|
|
38
|
+
}
|
|
39
|
+
registerViewport(el) {
|
|
40
|
+
this.viewport = el;
|
|
41
|
+
}
|
|
42
|
+
registerAnchor(el) {
|
|
43
|
+
if (!this.anchors.includes(el))
|
|
44
|
+
this.anchors.push(el);
|
|
45
|
+
}
|
|
46
|
+
unregisterAnchor(el) {
|
|
47
|
+
const i = this.anchors.indexOf(el);
|
|
48
|
+
if (i > -1)
|
|
49
|
+
this.anchors.splice(i, 1);
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Recomputes whether we're at the live edge. Called from the viewport's
|
|
53
|
+
* scroll handler, which runs outside Angular — this only pushes through the
|
|
54
|
+
* subjects when the boolean actually flips, so we re-enter change detection
|
|
55
|
+
* once per state change instead of once per scroll event.
|
|
56
|
+
*/
|
|
57
|
+
measure() {
|
|
58
|
+
const el = this.viewport;
|
|
59
|
+
if (!el)
|
|
60
|
+
return;
|
|
61
|
+
const distance = el.scrollHeight - el.scrollTop - el.clientHeight;
|
|
62
|
+
const atBottom = distance <= this.threshold;
|
|
63
|
+
if (atBottom !== this.atBottomSubject.value)
|
|
64
|
+
this.atBottomSubject.next(atBottom);
|
|
65
|
+
// Leaving the bottom stops following; returning resumes it. This is the
|
|
66
|
+
// whole "don't fight the reader" contract in two lines.
|
|
67
|
+
if (atBottom !== this.followingSubject.value)
|
|
68
|
+
this.followingSubject.next(atBottom);
|
|
69
|
+
}
|
|
70
|
+
/** Jumps to the live edge and resumes following. */
|
|
71
|
+
scrollToBottom(behavior = 'smooth') {
|
|
72
|
+
const el = this.viewport;
|
|
73
|
+
if (!el)
|
|
74
|
+
return;
|
|
75
|
+
el.scrollTo({ top: el.scrollHeight, behavior });
|
|
76
|
+
if (!this.followingSubject.value)
|
|
77
|
+
this.followingSubject.next(true);
|
|
78
|
+
if (!this.atBottomSubject.value)
|
|
79
|
+
this.atBottomSubject.next(true);
|
|
80
|
+
}
|
|
81
|
+
/** Scrolls a specific row into view, e.g. jump-to-message from a search result. */
|
|
82
|
+
scrollToItem(el, behavior = 'smooth') {
|
|
83
|
+
el.scrollIntoView({ behavior, block: 'start' });
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Positions the transcript on open. `last-anchor` puts the newest turn at the
|
|
87
|
+
* top of the viewport instead of dumping the reader at the absolute end, so a
|
|
88
|
+
* long streamed answer starts from its beginning — with `peek` px of the
|
|
89
|
+
* previous turn left visible for context.
|
|
90
|
+
*/
|
|
91
|
+
applyStartPosition(position, peek) {
|
|
92
|
+
const el = this.viewport;
|
|
93
|
+
if (!el)
|
|
94
|
+
return;
|
|
95
|
+
if (position === 'top') {
|
|
96
|
+
el.scrollTop = 0;
|
|
97
|
+
this.measure();
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
if (position === 'last-anchor') {
|
|
101
|
+
const anchor = this.lastAnchorInDomOrder();
|
|
102
|
+
if (anchor) {
|
|
103
|
+
el.scrollTop = Math.max(0, anchor.offsetTop - el.offsetTop - peek);
|
|
104
|
+
this.measure();
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
// No turns marked — fall through to the bottom.
|
|
108
|
+
}
|
|
109
|
+
el.scrollTop = el.scrollHeight;
|
|
110
|
+
this.measure();
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Content grew. Follows only if the reader was already at the edge; uses
|
|
114
|
+
* 'auto' so streamed tokens don't queue a smooth animation per chunk.
|
|
115
|
+
*/
|
|
116
|
+
handleContentGrowth() {
|
|
117
|
+
if (!this.followingSubject.value) {
|
|
118
|
+
// Not following, but the scrollbar just changed size — re-measure so the
|
|
119
|
+
// jump button's visibility stays honest.
|
|
120
|
+
this.measure();
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
const el = this.viewport;
|
|
124
|
+
if (!el)
|
|
125
|
+
return;
|
|
126
|
+
el.scrollTop = el.scrollHeight;
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* History was prepended above the current view. Restores the reader's exact
|
|
130
|
+
* position by re-adding the height the new content introduced — otherwise the
|
|
131
|
+
* transcript visibly lurches downward as older messages load in.
|
|
132
|
+
*/
|
|
133
|
+
preserveOnPrepend(previousScrollHeight) {
|
|
134
|
+
const el = this.viewport;
|
|
135
|
+
if (!el)
|
|
136
|
+
return;
|
|
137
|
+
const delta = el.scrollHeight - previousScrollHeight;
|
|
138
|
+
if (delta > 0)
|
|
139
|
+
el.scrollTop += delta;
|
|
140
|
+
}
|
|
141
|
+
/** Current scrollHeight, captured before a prepend so it can be compared after. */
|
|
142
|
+
snapshotScrollHeight() {
|
|
143
|
+
return this.viewport?.scrollHeight ?? 0;
|
|
144
|
+
}
|
|
145
|
+
/** The last registered anchor in document order (registration order can vary). */
|
|
146
|
+
lastAnchorInDomOrder() {
|
|
147
|
+
if (!this.anchors.length)
|
|
148
|
+
return undefined;
|
|
149
|
+
return [...this.anchors].sort((a, b) => a.compareDocumentPosition(b) & Node.DOCUMENT_POSITION_FOLLOWING ? -1 : 1)[this.anchors.length - 1];
|
|
150
|
+
}
|
|
151
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: MessageScrollerService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
152
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: MessageScrollerService });
|
|
153
|
+
}
|
|
154
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: MessageScrollerService, decorators: [{
|
|
155
|
+
type: Injectable
|
|
156
|
+
}] });
|
|
157
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibWVzc2FnZS1zY3JvbGxlci5zZXJ2aWNlLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vLi4vcHJvamVjdHMvdG9sbGUvc3JjL2xpYi9tZXNzYWdlLXNjcm9sbGVyLnNlcnZpY2UudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxFQUFFLFVBQVUsRUFBRSxNQUFNLGVBQWUsQ0FBQztBQUMzQyxPQUFPLEVBQUUsZUFBZSxFQUFFLE1BQU0sTUFBTSxDQUFDOztBQUt2Qzs7Ozs7Ozs7Ozs7O0dBWUc7QUFFSCxNQUFNLE9BQU8sc0JBQXNCO0lBQ2pDLG1FQUFtRTtJQUMzRCxRQUFRLEdBQXVCLElBQUksQ0FBQztJQUU1Qyx5RUFBeUU7SUFDakUsT0FBTyxHQUFrQixFQUFFLENBQUM7SUFFbkIsZUFBZSxHQUFHLElBQUksZUFBZSxDQUFVLElBQUksQ0FBQyxDQUFDO0lBQ3RFLDRFQUE0RTtJQUNuRSxTQUFTLEdBQUcsSUFBSSxDQUFDLGVBQWUsQ0FBQyxZQUFZLEVBQUUsQ0FBQztJQUV4QyxnQkFBZ0IsR0FBRyxJQUFJLGVBQWUsQ0FBVSxJQUFJLENBQUMsQ0FBQztJQUN2RTs7O09BR0c7SUFDTSxVQUFVLEdBQUcsSUFBSSxDQUFDLGdCQUFnQixDQUFDLFlBQVksRUFBRSxDQUFDO0lBRTNELDRFQUE0RTtJQUM1RSxTQUFTLEdBQUcsRUFBRSxDQUFDO0lBRWYsSUFBSSxRQUFRO1FBQ1YsT0FBTyxJQUFJLENBQUMsZUFBZSxDQUFDLEtBQUssQ0FBQztJQUNwQyxDQUFDO0lBRUQsSUFBSSxXQUFXO1FBQ2IsT0FBTyxJQUFJLENBQUMsZ0JBQWdCLENBQUMsS0FBSyxDQUFDO0lBQ3JDLENBQUM7SUFFRCxnQkFBZ0IsQ0FBQyxFQUFlO1FBQzlCLElBQUksQ0FBQyxRQUFRLEdBQUcsRUFBRSxDQUFDO0lBQ3JCLENBQUM7SUFFRCxjQUFjLENBQUMsRUFBZTtRQUM1QixJQUFJLENBQUMsSUFBSSxDQUFDLE9BQU8sQ0FBQyxRQUFRLENB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QTJCLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQ3pFLENBQUMsSUFBSSxDQUFDLE9BQU8sQ0FBQyxNQUFNLEdBQUcsQ0FBQyxDQUFDLENBQUM7SUFDN0IsQ0FBQzt3R0FqSlUsc0JBQXNCOzRHQUF0QixzQkFBc0I7OzRGQUF0QixzQkFBc0I7a0JBRGxDLFVBQVUiLCJzb3VyY2VzQ29udGVudCI6WyJpbXBvcnQgeyBJbmplY3RhYmxlIH0gZnJvbSAnQGFuZ3VsYXIvY29yZSc7XG5pbXBvcnQgeyBCZWhhdmlvclN1YmplY3QgfSBmcm9tICdyeGpzJztcblxuLyoqIFdoZXJlIGEgZnJlc2hseS1tb3VudGVkIHNjcm9sbGVyIHNob3VsZCBzdGFydC4gKi9cbmV4cG9ydCB0eXBlIFNjcm9sbFN0YXJ0UG9zaXRpb24gPSAndG9wJyB8ICdib3R0b20nIHwgJ2xhc3QtYW5jaG9yJztcblxuLyoqXG4gKiBTY3JvbGwgc3RhdGUgZm9yIG9uZSBgdG9sbGUtbWVzc2FnZS1zY3JvbGxlcmAsIHNoYXJlZCB3aXRoIGl0cyB2aWV3cG9ydCxcbiAqIGl0ZW1zIGFuZCBidXR0b25zLlxuICpcbiAqIFRoZSBnb3Zlcm5pbmcgcnVsZSBpczogKipuZXZlciBtb3ZlIHRoZSByZWFkZXIgYWdhaW5zdCB0aGVpciBpbnRlbnQuKiogRXZlcnlcbiAqIGF1dG9tYXRpYyBzY3JvbGwgaGVyZSBpcyBnYXRlZCBvbiB0aGUgcmVhZGVyIGFscmVhZHkgYmVpbmcgYXQgdGhlIGxpdmUgZWRnZS5cbiAqIFRoZSBtb21lbnQgdGhleSBzY3JvbGwgdXAsIGF1dG8tc2Nyb2xsIHN3aXRjaGVzIG9mZiBhbmQgc3RheXMgb2ZmIHVudGlsIHRoZXlcbiAqIGNvbWUgYmFjayBkb3duIG9yIGV4cGxpY2l0bHkganVtcC5cbiAqXG4gKiBQb3NpdGlvbnMgYXJlIHJlYWQgZnJvbSBhbmQgd3JpdHRlbiB0byB0aGUgRE9NIGRpcmVjdGx5IHJhdGhlciB0aGFuIG1pcnJvcmVkXG4gKiBpbnRvIGNvbXBvbmVudCBzdGF0ZSDigJQgc2Nyb2xsIGV2ZW50cyBmaXJlIGZhciB0b28gb2Z0ZW4gdG8gcnVuIGNoYW5nZVxuICogZGV0ZWN0aW9uIG9uLCBhbmQgdGhlIHZhbHVlcyB3b3VsZCBiZSBzdGFsZSBieSB0aGUgdGltZSB0aGV5IGxhbmRlZC5cbiAqL1xuQEluamVjdGFibGUoKVxuZXhwb3J0IGNsYXNzIE1lc3NhZ2VTY3JvbGxlclNlcnZpY2Uge1xuICAvKiogVGhlIHNjcm9sbGFibGUgZWxlbWVudCwgc2V0IGJ5IHRoZSB2aWV3cG9ydCBvbmNlIGl0IHJlbmRlcnMuICovXG4gIHByaXZhdGUgdmlld3BvcnQ6IEhUTUxFbGVtZW50IHwgbnVsbCA9IG51bGw7XG5cbiAgLyoqIFJvd3MgcmVnaXN0ZXJlZCBhcyB0aGUgc3RhcnQgb2YgYSBjb252ZXJzYXRpb24gdHVybiwgaW4gRE9NIG9yZGVyLiAqL1xuICBwcml2YXRlIGFuY2hvcnM6IEhUTUxFbGVtZW50W10gPSBbXTtcblxuICBwcml2YXRlIHJlYWRvbmx5IGF0Qm90dG9tU3ViamVjdCA9IG5ldyBCZWhhdmlvclN1YmplY3Q8Ym9vbGVhbj4odHJ1ZSk7XG4gIC8qKiBUcnVlIHdoaWxlIHRoZSByZWFkZXIgaXMgYXQgKG9yIHdpdGhpbiBgdGhyZXNob2xkYCBvZikgdGhlIGxpdmUgZWRnZS4gKi9cbiAgcmVhZG9ubHkgYXRCb3R0b20kID0gdGhpcy5hdEJvdHRvbVN1YmplY3QuYXNPYnNlcnZhYmxlKCk7XG5cbiAgcHJpdmF0ZSByZWFkb25seSBmb2xsb3dpbmdTdWJqZWN0ID0gbmV3IEJlaGF2aW9yU3ViamVjdDxib29sZWFuPih0cnVlKTtcbiAgLyoqXG4gICAqIFRydWUgd2hpbGUgbmV3IGNvbnRlbnQgc2hvdWxkIHB1bGwgdGhlIHZpZXcgYWxvbmcuIFNldCBmYWxzZSB0aGUgaW5zdGFudFxuICAgKiB0aGUgcmVhZGVyIHNjcm9sbHMgYXdheSBmcm9tIHRoZSBib3R0b20uXG4gICAqL1xuICByZWFkb25seSBmb2xsb3dpbmckID0gdGhpcy5mb2xsb3dpbmdTdWJqZWN0LmFzT2JzZXJ2YWJsZSgpO1xuXG4gIC8qKiBEaXN0YW5jZSBmcm9tIHRoZSBib3R0b20sIGluIHB4LCBzdGlsbCBjb25zaWRlcmVkIFwiYXQgdGhlIGxpdmUgZWRnZVwiLiAqL1xuICB0aHJlc2hvbGQgPSAzMjtcblxuICBnZXQgYXRCb3R0b20oKTogYm9vbGVhbiB7XG4gICAgcmV0dXJuIHRoaXMuYXRCb3R0b21TdWJqZWN0LnZhbHVlO1xuICB9XG5cbiAgZ2V0IGlzRm9sbG93aW5nKCk6IGJvb2xlYW4ge1xuICAgIHJldHVybiB0aGlzLmZvbGxvd2luZ1N1YmplY3QudmFsdWU7XG4gIH1cblxuICByZWdpc3RlclZpZXdwb3J0KGVsOiBIVE1MRWxlbWVudCk6IHZvaWQge1xuICAgIHRoaXMudmlld3BvcnQgPSBlbDtcbiAgfVxuXG4gIHJlZ2lzdGVyQW5jaG9yKGVsOiBIVE1MRWxlbWVudCk6IHZvaWQge1xuICAgIGlmICghdGhpcy5hbmNob3JzLmluY2x1ZGVzKGVsKSkgdGhpcy5hbmNob3JzLnB1c2goZWwpO1xuICB9XG5cbiAgdW5yZWdpc3RlckFuY2hvcihlbDogSFRNTEVsZW1lbnQpOiB2b2lkIHtcbiAgICBjb25zdCBpID0gdGhpcy5hbmNob3JzLmluZGV4T2YoZWwpO1xuICAgIGlmIChpID4gLTEpIHRoaXMuYW5jaG9ycy5zcGxpY2UoaSwgMSk7XG4gIH1cblxuICAvKipcbiAgICogUmVjb21wdXRlcyB3aGV0aGVyIHdlJ3JlIGF0IHRoZSBsaXZlIGVkZ2UuIENhbGxlZCBmcm9tIHRoZSB2aWV3cG9ydCdzXG4gICAqIHNjcm9sbCBoYW5kbGVyLCB3aGljaCBydW5zIG91dHNpZGUgQW5ndWxhciDigJQgdGhpcyBvbmx5IHB1c2hlcyB0aHJvdWdoIHRoZVxuICAgKiBzdWJqZWN0cyB3aGVuIHRoZSBib29sZWFuIGFjdHVhbGx5IGZsaXBzLCBzbyB3ZSByZS1lbnRlciBjaGFuZ2UgZGV0ZWN0aW9uXG4gICAqIG9uY2UgcGVyIHN0YXRlIGNoYW5nZSBpbnN0ZWFkIG9mIG9uY2UgcGVyIHNjcm9sbCBldmVudC5cbiAgICovXG4gIG1lYXN1cmUoKTogdm9pZCB7XG4gICAgY29uc3QgZWwgPSB0aGlzLnZpZXdwb3J0O1xuICAgIGlmICghZWwpIHJldHVybjtcblxuICAgIGNvbnN0IGRpc3RhbmNlID0gZWwuc2Nyb2xsSGVpZ2h0IC0gZWwuc2Nyb2xsVG9wIC0gZWwuY2xpZW50SGVpZ2h0O1xuICAgIGNvbnN0IGF0Qm90dG9tID0gZGlzdGFuY2UgPD0gdGhpcy50aHJlc2hvbGQ7XG5cbiAgICBpZiAoYXRCb3R0b20gIT09IHRoaXMuYXRCb3R0b21TdWJqZWN0LnZhbHVlKSB0aGlzLmF0Qm90dG9tU3ViamVjdC5uZXh0KGF0Qm90dG9tKTtcblxuICAgIC8vIExlYXZpbmcgdGhlIGJvdHRvbSBzdG9wcyBmb2xsb3dpbmc7IHJldHVybmluZyByZXN1bWVzIGl0LiBUaGlzIGlzIHRoZVxuICAgIC8vIHdob2xlIFwiZG9uJ3QgZmlnaHQgdGhlIHJlYWRlclwiIGNvbnRyYWN0IGluIHR3byBsaW5lcy5cbiAgICBpZiAoYXRCb3R0b20gIT09IHRoaXMuZm9sbG93aW5nU3ViamVjdC52YWx1ZSkgdGhpcy5mb2xsb3dpbmdTdWJqZWN0Lm5leHQoYXRCb3R0b20pO1xuICB9XG5cbiAgLyoqIEp1bXBzIHRvIHRoZSBsaXZlIGVkZ2UgYW5kIHJlc3VtZXMgZm9sbG93aW5nLiAqL1xuICBzY3JvbGxUb0JvdHRvbShiZWhhdmlvcjogU2Nyb2xsQmVoYXZpb3IgPSAnc21vb3RoJyk6IHZvaWQge1xuICAgIGNvbnN0IGVsID0gdGhpcy52aWV3cG9ydDtcbiAgICBpZiAoIWVsKSByZXR1cm47XG4gICAgZWwuc2Nyb2xsVG8oeyB0b3A6IGVsLnNjcm9sbEhlaWdodCwgYmVoYXZpb3IgfSk7XG4gICAgaWYgKCF0aGlzLmZvbGxvd2luZ1N1YmplY3QudmFsdWUpIHRoaXMuZm9sbG93aW5nU3ViamVjdC5uZXh0KHRydWUpO1xuICAgIGlmICghdGhpcy5hdEJvdHRvbVN1YmplY3QudmFsdWUpIHRoaXMuYXRCb3R0b21TdWJqZWN0Lm5leHQodHJ1ZSk7XG4gIH1cblxuICAvKiogU2Nyb2xscyBhIHNwZWNpZmljIHJvdyBpbnRvIHZpZXcsIGUuZy4ganVtcC10by1tZXNzYWdlIGZyb20gYSBzZWFyY2ggcmVzdWx0LiAqL1xuICBzY3JvbGxUb0l0ZW0oZWw6IEhUTUxFbGVtZW50LCBiZWhhdmlvcjogU2Nyb2xsQmVoYXZpb3IgPSAnc21vb3RoJyk6IHZvaWQge1xuICAgIGVsLnNjcm9sbEludG9WaWV3KHsgYmVoYXZpb3IsIGJsb2NrOiAnc3RhcnQnIH0pO1xuICB9XG5cbiAgLyoqXG4gICAqIFBvc2l0aW9ucyB0aGUgdHJhbnNjcmlwdCBvbiBvcGVuLiBgbGFzdC1hbmNob3JgIHB1dHMgdGhlIG5ld2VzdCB0dXJuIGF0IHRoZVxuICAgKiB0b3Agb2YgdGhlIHZpZXdwb3J0IGluc3RlYWQgb2YgZHVtcGluZyB0aGUgcmVhZGVyIGF0IHRoZSBhYnNvbHV0ZSBlbmQsIHNvIGFcbiAgICogbG9uZyBzdHJlYW1lZCBhbnN3ZXIgc3RhcnRzIGZyb20gaXRzIGJlZ2lubmluZyDigJQgd2l0aCBgcGVla2AgcHggb2YgdGhlXG4gICAqIHByZXZpb3VzIHR1cm4gbGVmdCB2aXNpYmxlIGZvciBjb250ZXh0LlxuICAgKi9cbiAgYXBwbHlTdGFydFBvc2l0aW9uKHBvc2l0aW9uOiBTY3JvbGxTdGFydFBvc2l0aW9uLCBwZWVrOiBudW1iZXIpOiB2b2lkIHtcbiAgICBjb25zdCBlbCA9IHRoaXMudmlld3BvcnQ7XG4gICAgaWYgKCFlbCkgcmV0dXJuO1xuXG4gICAgaWYgKHBvc2l0aW9uID09PSAndG9wJykge1xuICAgICAgZWwuc2Nyb2xsVG9wID0gMDtcbiAgICAgIHRoaXMubWVhc3VyZSgpO1xuICAgICAgcmV0dXJuO1xuICAgIH1cblxuICAgIGlmIChwb3NpdGlvbiA9PT0gJ2xhc3QtYW5jaG9yJykge1xuICAgICAgY29uc3QgYW5jaG9yID0gdGhpcy5sYXN0QW5jaG9ySW5Eb21PcmRlcigpO1xuICAgICAgaWYgKGFuY2hvcikge1xuICAgICAgICBlbC5zY3JvbGxUb3AgPSBNYXRoLm1heCgwLCBhbmNob3Iub2Zmc2V0VG9wIC0gZWwub2Zmc2V0VG9wIC0gcGVlayk7XG4gICAgICAgIHRoaXMubWVhc3VyZSgpO1xuICAgICAgICByZXR1cm47XG4gICAgICB9XG4gICAgICAvLyBObyB0dXJucyBtYXJrZWQg4oCUIGZhbGwgdGhyb3VnaCB0byB0aGUgYm90dG9tLlxuICAgIH1cblxuICAgIGVsLnNjcm9sbFRvcCA9IGVsLnNjcm9sbEhlaWdodDtcbiAgICB0aGlzLm1lYXN1cmUoKTtcbiAgfVxuXG4gIC8qKlxuICAgKiBDb250ZW50IGdyZXcuIEZvbGxvd3Mgb25seSBpZiB0aGUgcmVhZGVyIHdhcyBhbHJlYWR5IGF0IHRoZSBlZGdlOyB1c2VzXG4gICAqICdhdXRvJyBzbyBzdHJlYW1lZCB0b2tlbnMgZG9uJ3QgcXVldWUgYSBzbW9vdGggYW5pbWF0aW9uIHBlciBjaHVuay5cbiAgICovXG4gIGhhbmRsZUNvbnRlbnRHcm93dGgoKTogdm9pZCB7XG4gICAgaWYgKCF0aGlzLmZvbGxvd2luZ1N1YmplY3QudmFsdWUpIHtcbiAgICAgIC8vIE5vdCBmb2xsb3dpbmcsIGJ1dCB0aGUgc2Nyb2xsYmFyIGp1c3QgY2hhbmdlZCBzaXplIOKAlCByZS1tZWFzdXJlIHNvIHRoZVxuICAgICAgLy8ganVtcCBidXR0b24ncyB2aXNpYmlsaXR5IHN0YXlzIGhvbmVzdC5cbiAgICAgIHRoaXMubWVhc3VyZSgpO1xuICAgICAgcmV0dXJuO1xuICAgIH1cbiAgICBjb25zdCBlbCA9IHRoaXMudmlld3BvcnQ7XG4gICAgaWYgKCFlbCkgcmV0dXJuO1xuICAgIGVsLnNjcm9sbFRvcCA9IGVsLnNjcm9sbEhlaWdodDtcbiAgfVxuXG4gIC8qKlxuICAgKiBIaXN0b3J5IHdhcyBwcmVwZW5kZWQgYWJvdmUgdGhlIGN1cnJlbnQgdmlldy4gUmVzdG9yZXMgdGhlIHJlYWRlcidzIGV4YWN0XG4gICAqIHBvc2l0aW9uIGJ5IHJlLWFkZGluZyB0aGUgaGVpZ2h0IHRoZSBuZXcgY29udGVudCBpbnRyb2R1Y2VkIOKAlCBvdGhlcndpc2UgdGhlXG4gICAqIHRyYW5zY3JpcHQgdmlzaWJseSBsdXJjaGVzIGRvd253YXJkIGFzIG9sZGVyIG1lc3NhZ2VzIGxvYWQgaW4uXG4gICAqL1xuICBwcmVzZXJ2ZU9uUHJlcGVuZChwcmV2aW91c1Njcm9sbEhlaWdodDogbnVtYmVyKTogdm9pZCB7XG4gICAgY29uc3QgZWwgPSB0aGlzLnZpZXdwb3J0O1xuICAgIGlmICghZWwpIHJldHVybjtcbiAgICBjb25zdCBkZWx0YSA9IGVsLnNjcm9sbEhlaWdodCAtIHByZXZpb3VzU2Nyb2xsSGVpZ2h0O1xuICAgIGlmIChkZWx0YSA+IDApIGVsLnNjcm9sbFRvcCArPSBkZWx0YTtcbiAgfVxuXG4gIC8qKiBDdXJyZW50IHNjcm9sbEhlaWdodCwgY2FwdHVyZWQgYmVmb3JlIGEgcHJlcGVuZCBzbyBpdCBjYW4gYmUgY29tcGFyZWQgYWZ0ZXIuICovXG4gIHNuYXBzaG90U2Nyb2xsSGVpZ2h0KCk6IG51bWJlciB7XG4gICAgcmV0dXJuIHRoaXMudmlld3BvcnQ/LnNjcm9sbEhlaWdodCA/PyAwO1xuICB9XG5cbiAgLyoqIFRoZSBsYXN0IHJlZ2lzdGVyZWQgYW5jaG9yIGluIGRvY3VtZW50IG9yZGVyIChyZWdpc3RyYXRpb24gb3JkZXIgY2FuIHZhcnkpLiAqL1xuICBwcml2YXRlIGxhc3RBbmNob3JJbkRvbU9yZGVyKCk6IEhUTUxFbGVtZW50IHwgdW5kZWZpbmVkIHtcbiAgICBpZiAoIXRoaXMuYW5jaG9ycy5sZW5ndGgpIHJldHVybiB1bmRlZmluZWQ7XG4gICAgcmV0dXJuIFsuLi50aGlzLmFuY2hvcnNdLnNvcnQoKGEsIGIpID0+XG4gICAgICBhLmNvbXBhcmVEb2N1bWVudFBvc2l0aW9uKGIpICYgTm9kZS5ET0NVTUVOVF9QT1NJVElPTl9GT0xMT1dJTkcgPyAtMSA6IDFcbiAgICApW3RoaXMuYW5jaG9ycy5sZW5ndGggLSAxXTtcbiAgfVxufVxuIl19
|