@kteneyck/cesium-timeline-angular 0.8.0 → 0.10.0
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.
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
2
|
import { EventEmitter, ViewChild, Output, Input, ChangeDetectionStrategy, Component } from '@angular/core';
|
|
3
3
|
import * as Cesium from 'cesium';
|
|
4
|
-
import { splitForDisplay, formatDateTime, getTimezoneAbbr, DEFAULT_LABELS, resolveLabel, MIN_SPAN_MS, MAX_SPAN_MS, drawTimeline, hitTestSwimLane, hitTestLaneLabel, isInSwimLaneRegion, zoomRange, DEFAULT_LANE_HEIGHT, LANE_GAP, totalSwimLaneHeight,
|
|
4
|
+
import { splitForDisplay, formatDateTime, getTimezoneAbbr, DEFAULT_LABELS, resolveLabel, MIN_SPAN_MS, MAX_SPAN_MS, drawTimeline, hitTestSwimLane, hitTestLaneLabel, isInSwimLaneRegion, zoomRange, TICK_AREA_HEIGHT, DEFAULT_LANE_HEIGHT, LANE_GAP, totalSwimLaneHeight, SWIM_LANE_SCROLL_SPEED, zoomAroundMs, defaultTheme, toJulianDate } from '@kteneyck/cesium-timeline-core';
|
|
5
5
|
export * from '@kteneyck/cesium-timeline-core';
|
|
6
6
|
export { TICK_AREA_HEIGHT } from '@kteneyck/cesium-timeline-core';
|
|
7
7
|
|
|
@@ -19,6 +19,10 @@ class TimelineControlsComponent {
|
|
|
19
19
|
theme;
|
|
20
20
|
swimLanesVisible;
|
|
21
21
|
labels;
|
|
22
|
+
liveButtonSize = 'md';
|
|
23
|
+
liveButtonPosition = 'left';
|
|
24
|
+
/** @see TimelineBaseProps.live */
|
|
25
|
+
live = false;
|
|
22
26
|
dateTimeClick = new EventEmitter();
|
|
23
27
|
playPause = new EventEmitter();
|
|
24
28
|
jumpToStart = new EventEmitter();
|
|
@@ -36,6 +40,12 @@ class TimelineControlsComponent {
|
|
|
36
40
|
get isNormalSpeed() { return this.multiplier === 1; }
|
|
37
41
|
get absMultiplier() { return Math.abs(this.multiplier); }
|
|
38
42
|
get hasSwimLaneToggle() { return this.swimLanesVisible != null; }
|
|
43
|
+
static LIVE_SIZE_MAP = {
|
|
44
|
+
sm: { width: 44, height: 18, fontSize: '10px', dot: 5, borderRadius: '3px' },
|
|
45
|
+
md: { width: 56, height: 22, fontSize: '11px', dot: 6, borderRadius: '3px' },
|
|
46
|
+
lg: { width: 72, height: 30, fontSize: '13px', dot: 8, borderRadius: '4px' },
|
|
47
|
+
};
|
|
48
|
+
get liveSize() { return TimelineControlsComponent.LIVE_SIZE_MAP[this.liveButtonSize]; }
|
|
39
49
|
get timeFormat() { return splitForDisplay(this.dateTimeFormat).timeFormat; }
|
|
40
50
|
get dateFormat() { return splitForDisplay(this.dateTimeFormat).dateFormat; }
|
|
41
51
|
get formattedTime() { return formatDateTime(this.currentTime, this.timeFormat, this.timezone); }
|
|
@@ -58,7 +68,7 @@ class TimelineControlsComponent {
|
|
|
58
68
|
this.ro?.disconnect();
|
|
59
69
|
}
|
|
60
70
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.10", ngImport: i0, type: TimelineControlsComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
61
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.10", type: TimelineControlsComponent, isStandalone: true, selector: "ct-timeline-controls", inputs: { currentTime: "currentTime", isPlaying: "isPlaying", multiplier: "multiplier", dateTimeFormat: "dateTimeFormat", timezone: "timezone", isLive: "isLive", hasStartTime: "hasStartTime", hasEndTime: "hasEndTime", showJumpToStart: "showJumpToStart", showJumpToEnd: "showJumpToEnd", theme: "theme", swimLanesVisible: "swimLanesVisible", labels: "labels" }, outputs: { dateTimeClick: "dateTimeClick", playPause: "playPause", jumpToStart: "jumpToStart", rewind: "rewind", fastForward: "fastForward", jumpToEnd: "jumpToEnd", jumpToLive: "jumpToLive", resetSpeed: "resetSpeed", toggleSwimLanes: "toggleSwimLanes" }, viewQueries: [{ propertyName: "containerRef", first: true, predicate: ["container"], descendants: true }], ngImport: i0, template: `
|
|
71
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.10", type: TimelineControlsComponent, isStandalone: true, selector: "ct-timeline-controls", inputs: { currentTime: "currentTime", isPlaying: "isPlaying", multiplier: "multiplier", dateTimeFormat: "dateTimeFormat", timezone: "timezone", isLive: "isLive", hasStartTime: "hasStartTime", hasEndTime: "hasEndTime", showJumpToStart: "showJumpToStart", showJumpToEnd: "showJumpToEnd", theme: "theme", swimLanesVisible: "swimLanesVisible", labels: "labels", liveButtonSize: "liveButtonSize", liveButtonPosition: "liveButtonPosition", live: "live" }, outputs: { dateTimeClick: "dateTimeClick", playPause: "playPause", jumpToStart: "jumpToStart", rewind: "rewind", fastForward: "fastForward", jumpToEnd: "jumpToEnd", jumpToLive: "jumpToLive", resetSpeed: "resetSpeed", toggleSwimLanes: "toggleSwimLanes" }, viewQueries: [{ propertyName: "containerRef", first: true, predicate: ["container"], descendants: true }], ngImport: i0, template: `
|
|
62
72
|
<div
|
|
63
73
|
#container
|
|
64
74
|
[style.display]="isNarrow ? 'flex' : 'grid'"
|
|
@@ -69,14 +79,14 @@ class TimelineControlsComponent {
|
|
|
69
79
|
[style.border-bottom]="'1px solid ' + theme.controlBarBorder"
|
|
70
80
|
[style.font-family]="'system-ui, -apple-system, sans-serif'"
|
|
71
81
|
>
|
|
72
|
-
<!-- Left: Datetime + LIVE
|
|
82
|
+
<!-- Left: Datetime + LIVE (if position=left) -->
|
|
73
83
|
<div style="display:flex;align-items:center;gap:8px;flex-shrink:0">
|
|
74
84
|
<div
|
|
75
|
-
(click)="dateTimeClick.emit()"
|
|
76
|
-
[title]="dateTimeClick.observed ? l.dateTimeClickTooltip : ''"
|
|
85
|
+
(click)="!live && dateTimeClick.emit()"
|
|
86
|
+
[title]="(!live && dateTimeClick.observed) ? l.dateTimeClickTooltip : ''"
|
|
77
87
|
[style.color]="theme.labelColor"
|
|
78
88
|
style="font-family:monospace;line-height:1.15;border-radius:4px;padding:2px 4px;transition:background 0.15s"
|
|
79
|
-
[style.cursor]="dateTimeClick.observed ? 'pointer' : 'default'"
|
|
89
|
+
[style.cursor]="(!live && dateTimeClick.observed) ? 'pointer' : 'default'"
|
|
80
90
|
>
|
|
81
91
|
@if (timeFormat) {
|
|
82
92
|
<div style="font-size:2em;font-weight:bold;letter-spacing:0.02em">
|
|
@@ -98,35 +108,47 @@ class TimelineControlsComponent {
|
|
|
98
108
|
}
|
|
99
109
|
</div>
|
|
100
110
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
111
|
+
@if (liveButtonPosition === 'left') {
|
|
112
|
+
<div style="display:flex;align-items:center;gap:4px">
|
|
113
|
+
<button
|
|
114
|
+
(click)="!live && jumpToLive.emit()"
|
|
115
|
+
[style.color]="(live || isLive) ? theme.controlBarBackground : theme.buttonActiveColor"
|
|
116
|
+
[style.background-color]="(live || isLive) ? theme.buttonActiveColor : 'transparent'"
|
|
117
|
+
[style.border-color]="theme.buttonActiveColor"
|
|
118
|
+
[style.opacity]="1"
|
|
119
|
+
[style.width.px]="liveSize.width"
|
|
120
|
+
[style.min-width.px]="liveSize.width"
|
|
121
|
+
[style.height.px]="liveSize.height"
|
|
122
|
+
[style.font-size]="liveSize.fontSize"
|
|
123
|
+
[style.border-radius]="liveSize.borderRadius"
|
|
124
|
+
[style.cursor]="live ? 'default' : 'pointer'"
|
|
125
|
+
style="background:none;border:1px solid;font-weight:bold;letter-spacing:0.05em;display:flex;align-items:center;justify-content:center;padding:0;gap:4px;font-family:system-ui,-apple-system,sans-serif;transition:opacity 0.15s"
|
|
126
|
+
[title]="(live || isLive) ? l.liveActiveTooltip : l.liveTooltip"
|
|
127
|
+
>
|
|
128
|
+
@if (live || isLive) {
|
|
129
|
+
<span
|
|
130
|
+
[style.width.px]="liveSize.dot"
|
|
131
|
+
[style.height.px]="liveSize.dot"
|
|
132
|
+
[style.background-color]="theme.liveDotColor"
|
|
133
|
+
style="border-radius:50%;display:inline-block;flex-shrink:0"
|
|
134
|
+
></span>
|
|
135
|
+
}
|
|
136
|
+
{{ (live || isLive) ? l.liveActiveLabel : l.liveLabel }}
|
|
137
|
+
</button>
|
|
138
|
+
@if (!isNormalSpeed && !live) {
|
|
118
139
|
<button
|
|
119
140
|
(click)="resetSpeed.emit()"
|
|
120
141
|
[style.color]="theme.buttonActiveColor"
|
|
121
142
|
[style.border-color]="theme.buttonActiveColor + '44'"
|
|
122
|
-
style="
|
|
143
|
+
[style.width.px]="liveSize.width"
|
|
144
|
+
[style.min-width.px]="liveSize.width"
|
|
145
|
+
[style.height.px]="liveSize.height"
|
|
146
|
+
style="background:none;border:1px solid;cursor:pointer;font-size:11px;border-radius:4px;display:flex;align-items:center;justify-content:center;padding:0;font-family:system-ui,-apple-system,sans-serif;transition:background-color 0.15s"
|
|
123
147
|
[title]="l.resetSpeedTooltip"
|
|
124
|
-
>
|
|
125
|
-
{{ isRewinding ? '◀ ' + absMultiplier + '×' : absMultiplier + '× ▶' }}
|
|
126
|
-
</button>
|
|
148
|
+
>{{ isRewinding ? '◀ ' + absMultiplier + '×' : absMultiplier + '× ▶' }}</button>
|
|
127
149
|
}
|
|
128
150
|
</div>
|
|
129
|
-
|
|
151
|
+
}
|
|
130
152
|
</div>
|
|
131
153
|
|
|
132
154
|
<!-- Center: Transport buttons -->
|
|
@@ -135,7 +157,7 @@ class TimelineControlsComponent {
|
|
|
135
157
|
[style.flex]="isNarrow ? '1' : undefined"
|
|
136
158
|
[style.justify-content]="isNarrow ? 'center' : undefined"
|
|
137
159
|
>
|
|
138
|
-
@if (showJumpToStart !== false) {
|
|
160
|
+
@if (!live && showJumpToStart !== false) {
|
|
139
161
|
<button
|
|
140
162
|
(click)="hasStartTime && jumpToStart.emit()"
|
|
141
163
|
[disabled]="!hasStartTime"
|
|
@@ -147,53 +169,59 @@ class TimelineControlsComponent {
|
|
|
147
169
|
>⏮</button>
|
|
148
170
|
}
|
|
149
171
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
172
|
+
@if (!live) {
|
|
173
|
+
<button
|
|
174
|
+
(click)="rewind.emit()"
|
|
175
|
+
[style.color]="isRewinding ? theme.buttonActiveColor : theme.buttonColor"
|
|
176
|
+
[style.border-color]="isRewinding ? theme.buttonActiveColor + '33' : 'transparent'"
|
|
177
|
+
class="ct-btn ct-btn-wide"
|
|
178
|
+
[title]="isRewinding ? resolveRewindActive(absMultiplier) : l.rewindTooltip"
|
|
179
|
+
>
|
|
180
|
+
@if (isRewinding) {
|
|
181
|
+
<span style="font-size:11px;font-weight:bold">{{ absMultiplier }}×</span>◀◀
|
|
182
|
+
} @else {
|
|
183
|
+
◀◀
|
|
184
|
+
}
|
|
185
|
+
</button>
|
|
186
|
+
}
|
|
163
187
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
<
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
188
|
+
@if (!live) {
|
|
189
|
+
<button
|
|
190
|
+
(click)="playPause.emit(!isPlaying)"
|
|
191
|
+
[style.color]="theme.buttonActiveColor"
|
|
192
|
+
[style.border-color]="theme.buttonActiveColor + '55'"
|
|
193
|
+
[style.padding-left]="isPlaying ? '0' : '2px'"
|
|
194
|
+
class="ct-btn ct-btn-play"
|
|
195
|
+
[title]="isPlaying ? l.pauseTooltip : (isRewinding ? l.playFromRewindTooltip : l.playTooltip)"
|
|
196
|
+
>
|
|
197
|
+
@if (isPlaying) {
|
|
198
|
+
<svg width="14" height="16" viewBox="0 0 14 16" fill="currentColor">
|
|
199
|
+
<rect x="1" y="0" width="4" height="16" rx="1"/>
|
|
200
|
+
<rect x="9" y="0" width="4" height="16" rx="1"/>
|
|
201
|
+
</svg>
|
|
202
|
+
} @else {
|
|
203
|
+
▶
|
|
204
|
+
}
|
|
205
|
+
</button>
|
|
206
|
+
}
|
|
181
207
|
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
208
|
+
@if (!live) {
|
|
209
|
+
<button
|
|
210
|
+
(click)="fastForward.emit()"
|
|
211
|
+
[style.color]="isFastForward ? theme.buttonActiveColor : theme.buttonColor"
|
|
212
|
+
[style.border-color]="isFastForward ? theme.buttonActiveColor + '33' : 'transparent'"
|
|
213
|
+
class="ct-btn ct-btn-wide"
|
|
214
|
+
[title]="isFastForward ? resolveFastForwardActive(absMultiplier) : l.fastForwardTooltip"
|
|
215
|
+
>
|
|
216
|
+
@if (isFastForward) {
|
|
217
|
+
▶▶<span style="font-size:11px;font-weight:bold">{{ absMultiplier }}×</span>
|
|
218
|
+
} @else {
|
|
219
|
+
▶▶
|
|
220
|
+
}
|
|
221
|
+
</button>
|
|
222
|
+
}
|
|
195
223
|
|
|
196
|
-
@if (showJumpToEnd !== false) {
|
|
224
|
+
@if (!live && showJumpToEnd !== false) {
|
|
197
225
|
<button
|
|
198
226
|
(click)="hasEndTime && jumpToEnd.emit()"
|
|
199
227
|
[disabled]="!hasEndTime"
|
|
@@ -206,9 +234,50 @@ class TimelineControlsComponent {
|
|
|
206
234
|
}
|
|
207
235
|
</div>
|
|
208
236
|
|
|
209
|
-
<!-- Right: swim-lane toggle -->
|
|
237
|
+
<!-- Right: LIVE (if position=right) + swim-lane toggle -->
|
|
210
238
|
@if (!isNarrow) {
|
|
211
|
-
<div style="display:flex;justify-content:flex-end;align-items:center">
|
|
239
|
+
<div style="display:flex;justify-content:flex-end;align-items:center;gap:8px">
|
|
240
|
+
@if (liveButtonPosition === 'right') {
|
|
241
|
+
<div style="display:flex;align-items:center;gap:4px">
|
|
242
|
+
<button
|
|
243
|
+
(click)="!live && jumpToLive.emit()"
|
|
244
|
+
[style.color]="(live || isLive) ? theme.controlBarBackground : theme.buttonActiveColor"
|
|
245
|
+
[style.background-color]="(live || isLive) ? theme.buttonActiveColor : 'transparent'"
|
|
246
|
+
[style.border-color]="theme.buttonActiveColor"
|
|
247
|
+
[style.opacity]="1"
|
|
248
|
+
[style.width.px]="liveSize.width"
|
|
249
|
+
[style.min-width.px]="liveSize.width"
|
|
250
|
+
[style.height.px]="liveSize.height"
|
|
251
|
+
[style.font-size]="liveSize.fontSize"
|
|
252
|
+
[style.border-radius]="liveSize.borderRadius"
|
|
253
|
+
[style.cursor]="live ? 'default' : 'pointer'"
|
|
254
|
+
style="background:none;border:1px solid;font-weight:bold;letter-spacing:0.05em;display:flex;align-items:center;justify-content:center;padding:0;gap:4px;font-family:system-ui,-apple-system,sans-serif;transition:opacity 0.15s"
|
|
255
|
+
[title]="(live || isLive) ? l.liveActiveTooltip : l.liveTooltip"
|
|
256
|
+
>
|
|
257
|
+
@if (live || isLive) {
|
|
258
|
+
<span
|
|
259
|
+
[style.width.px]="liveSize.dot"
|
|
260
|
+
[style.height.px]="liveSize.dot"
|
|
261
|
+
[style.background-color]="theme.liveDotColor"
|
|
262
|
+
style="border-radius:50%;display:inline-block;flex-shrink:0"
|
|
263
|
+
></span>
|
|
264
|
+
}
|
|
265
|
+
{{ (live || isLive) ? l.liveActiveLabel : l.liveLabel }}
|
|
266
|
+
</button>
|
|
267
|
+
@if (!isNormalSpeed && !live) {
|
|
268
|
+
<button
|
|
269
|
+
(click)="resetSpeed.emit()"
|
|
270
|
+
[style.color]="theme.buttonActiveColor"
|
|
271
|
+
[style.border-color]="theme.buttonActiveColor + '44'"
|
|
272
|
+
[style.width.px]="liveSize.width"
|
|
273
|
+
[style.min-width.px]="liveSize.width"
|
|
274
|
+
[style.height.px]="liveSize.height"
|
|
275
|
+
style="background:none;border:1px solid;cursor:pointer;font-size:11px;border-radius:4px;display:flex;align-items:center;justify-content:center;padding:0;font-family:system-ui,-apple-system,sans-serif;transition:background-color 0.15s"
|
|
276
|
+
[title]="l.resetSpeedTooltip"
|
|
277
|
+
>{{ isRewinding ? '◀ ' + absMultiplier + '×' : absMultiplier + '× ▶' }}</button>
|
|
278
|
+
}
|
|
279
|
+
</div>
|
|
280
|
+
}
|
|
212
281
|
@if (hasSwimLaneToggle) {
|
|
213
282
|
<button
|
|
214
283
|
(click)="toggleSwimLanes.emit()"
|
|
@@ -263,14 +332,14 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.10", ngImpo
|
|
|
263
332
|
[style.border-bottom]="'1px solid ' + theme.controlBarBorder"
|
|
264
333
|
[style.font-family]="'system-ui, -apple-system, sans-serif'"
|
|
265
334
|
>
|
|
266
|
-
<!-- Left: Datetime + LIVE
|
|
335
|
+
<!-- Left: Datetime + LIVE (if position=left) -->
|
|
267
336
|
<div style="display:flex;align-items:center;gap:8px;flex-shrink:0">
|
|
268
337
|
<div
|
|
269
|
-
(click)="dateTimeClick.emit()"
|
|
270
|
-
[title]="dateTimeClick.observed ? l.dateTimeClickTooltip : ''"
|
|
338
|
+
(click)="!live && dateTimeClick.emit()"
|
|
339
|
+
[title]="(!live && dateTimeClick.observed) ? l.dateTimeClickTooltip : ''"
|
|
271
340
|
[style.color]="theme.labelColor"
|
|
272
341
|
style="font-family:monospace;line-height:1.15;border-radius:4px;padding:2px 4px;transition:background 0.15s"
|
|
273
|
-
[style.cursor]="dateTimeClick.observed ? 'pointer' : 'default'"
|
|
342
|
+
[style.cursor]="(!live && dateTimeClick.observed) ? 'pointer' : 'default'"
|
|
274
343
|
>
|
|
275
344
|
@if (timeFormat) {
|
|
276
345
|
<div style="font-size:2em;font-weight:bold;letter-spacing:0.02em">
|
|
@@ -292,35 +361,47 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.10", ngImpo
|
|
|
292
361
|
}
|
|
293
362
|
</div>
|
|
294
363
|
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
364
|
+
@if (liveButtonPosition === 'left') {
|
|
365
|
+
<div style="display:flex;align-items:center;gap:4px">
|
|
366
|
+
<button
|
|
367
|
+
(click)="!live && jumpToLive.emit()"
|
|
368
|
+
[style.color]="(live || isLive) ? theme.controlBarBackground : theme.buttonActiveColor"
|
|
369
|
+
[style.background-color]="(live || isLive) ? theme.buttonActiveColor : 'transparent'"
|
|
370
|
+
[style.border-color]="theme.buttonActiveColor"
|
|
371
|
+
[style.opacity]="1"
|
|
372
|
+
[style.width.px]="liveSize.width"
|
|
373
|
+
[style.min-width.px]="liveSize.width"
|
|
374
|
+
[style.height.px]="liveSize.height"
|
|
375
|
+
[style.font-size]="liveSize.fontSize"
|
|
376
|
+
[style.border-radius]="liveSize.borderRadius"
|
|
377
|
+
[style.cursor]="live ? 'default' : 'pointer'"
|
|
378
|
+
style="background:none;border:1px solid;font-weight:bold;letter-spacing:0.05em;display:flex;align-items:center;justify-content:center;padding:0;gap:4px;font-family:system-ui,-apple-system,sans-serif;transition:opacity 0.15s"
|
|
379
|
+
[title]="(live || isLive) ? l.liveActiveTooltip : l.liveTooltip"
|
|
380
|
+
>
|
|
381
|
+
@if (live || isLive) {
|
|
382
|
+
<span
|
|
383
|
+
[style.width.px]="liveSize.dot"
|
|
384
|
+
[style.height.px]="liveSize.dot"
|
|
385
|
+
[style.background-color]="theme.liveDotColor"
|
|
386
|
+
style="border-radius:50%;display:inline-block;flex-shrink:0"
|
|
387
|
+
></span>
|
|
388
|
+
}
|
|
389
|
+
{{ (live || isLive) ? l.liveActiveLabel : l.liveLabel }}
|
|
390
|
+
</button>
|
|
391
|
+
@if (!isNormalSpeed && !live) {
|
|
312
392
|
<button
|
|
313
393
|
(click)="resetSpeed.emit()"
|
|
314
394
|
[style.color]="theme.buttonActiveColor"
|
|
315
395
|
[style.border-color]="theme.buttonActiveColor + '44'"
|
|
316
|
-
style="
|
|
396
|
+
[style.width.px]="liveSize.width"
|
|
397
|
+
[style.min-width.px]="liveSize.width"
|
|
398
|
+
[style.height.px]="liveSize.height"
|
|
399
|
+
style="background:none;border:1px solid;cursor:pointer;font-size:11px;border-radius:4px;display:flex;align-items:center;justify-content:center;padding:0;font-family:system-ui,-apple-system,sans-serif;transition:background-color 0.15s"
|
|
317
400
|
[title]="l.resetSpeedTooltip"
|
|
318
|
-
>
|
|
319
|
-
{{ isRewinding ? '◀ ' + absMultiplier + '×' : absMultiplier + '× ▶' }}
|
|
320
|
-
</button>
|
|
401
|
+
>{{ isRewinding ? '◀ ' + absMultiplier + '×' : absMultiplier + '× ▶' }}</button>
|
|
321
402
|
}
|
|
322
403
|
</div>
|
|
323
|
-
|
|
404
|
+
}
|
|
324
405
|
</div>
|
|
325
406
|
|
|
326
407
|
<!-- Center: Transport buttons -->
|
|
@@ -329,7 +410,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.10", ngImpo
|
|
|
329
410
|
[style.flex]="isNarrow ? '1' : undefined"
|
|
330
411
|
[style.justify-content]="isNarrow ? 'center' : undefined"
|
|
331
412
|
>
|
|
332
|
-
@if (showJumpToStart !== false) {
|
|
413
|
+
@if (!live && showJumpToStart !== false) {
|
|
333
414
|
<button
|
|
334
415
|
(click)="hasStartTime && jumpToStart.emit()"
|
|
335
416
|
[disabled]="!hasStartTime"
|
|
@@ -341,53 +422,59 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.10", ngImpo
|
|
|
341
422
|
>⏮</button>
|
|
342
423
|
}
|
|
343
424
|
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
425
|
+
@if (!live) {
|
|
426
|
+
<button
|
|
427
|
+
(click)="rewind.emit()"
|
|
428
|
+
[style.color]="isRewinding ? theme.buttonActiveColor : theme.buttonColor"
|
|
429
|
+
[style.border-color]="isRewinding ? theme.buttonActiveColor + '33' : 'transparent'"
|
|
430
|
+
class="ct-btn ct-btn-wide"
|
|
431
|
+
[title]="isRewinding ? resolveRewindActive(absMultiplier) : l.rewindTooltip"
|
|
432
|
+
>
|
|
433
|
+
@if (isRewinding) {
|
|
434
|
+
<span style="font-size:11px;font-weight:bold">{{ absMultiplier }}×</span>◀◀
|
|
435
|
+
} @else {
|
|
436
|
+
◀◀
|
|
437
|
+
}
|
|
438
|
+
</button>
|
|
439
|
+
}
|
|
357
440
|
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
<
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
441
|
+
@if (!live) {
|
|
442
|
+
<button
|
|
443
|
+
(click)="playPause.emit(!isPlaying)"
|
|
444
|
+
[style.color]="theme.buttonActiveColor"
|
|
445
|
+
[style.border-color]="theme.buttonActiveColor + '55'"
|
|
446
|
+
[style.padding-left]="isPlaying ? '0' : '2px'"
|
|
447
|
+
class="ct-btn ct-btn-play"
|
|
448
|
+
[title]="isPlaying ? l.pauseTooltip : (isRewinding ? l.playFromRewindTooltip : l.playTooltip)"
|
|
449
|
+
>
|
|
450
|
+
@if (isPlaying) {
|
|
451
|
+
<svg width="14" height="16" viewBox="0 0 14 16" fill="currentColor">
|
|
452
|
+
<rect x="1" y="0" width="4" height="16" rx="1"/>
|
|
453
|
+
<rect x="9" y="0" width="4" height="16" rx="1"/>
|
|
454
|
+
</svg>
|
|
455
|
+
} @else {
|
|
456
|
+
▶
|
|
457
|
+
}
|
|
458
|
+
</button>
|
|
459
|
+
}
|
|
375
460
|
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
461
|
+
@if (!live) {
|
|
462
|
+
<button
|
|
463
|
+
(click)="fastForward.emit()"
|
|
464
|
+
[style.color]="isFastForward ? theme.buttonActiveColor : theme.buttonColor"
|
|
465
|
+
[style.border-color]="isFastForward ? theme.buttonActiveColor + '33' : 'transparent'"
|
|
466
|
+
class="ct-btn ct-btn-wide"
|
|
467
|
+
[title]="isFastForward ? resolveFastForwardActive(absMultiplier) : l.fastForwardTooltip"
|
|
468
|
+
>
|
|
469
|
+
@if (isFastForward) {
|
|
470
|
+
▶▶<span style="font-size:11px;font-weight:bold">{{ absMultiplier }}×</span>
|
|
471
|
+
} @else {
|
|
472
|
+
▶▶
|
|
473
|
+
}
|
|
474
|
+
</button>
|
|
475
|
+
}
|
|
389
476
|
|
|
390
|
-
@if (showJumpToEnd !== false) {
|
|
477
|
+
@if (!live && showJumpToEnd !== false) {
|
|
391
478
|
<button
|
|
392
479
|
(click)="hasEndTime && jumpToEnd.emit()"
|
|
393
480
|
[disabled]="!hasEndTime"
|
|
@@ -400,9 +487,50 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.10", ngImpo
|
|
|
400
487
|
}
|
|
401
488
|
</div>
|
|
402
489
|
|
|
403
|
-
<!-- Right: swim-lane toggle -->
|
|
490
|
+
<!-- Right: LIVE (if position=right) + swim-lane toggle -->
|
|
404
491
|
@if (!isNarrow) {
|
|
405
|
-
<div style="display:flex;justify-content:flex-end;align-items:center">
|
|
492
|
+
<div style="display:flex;justify-content:flex-end;align-items:center;gap:8px">
|
|
493
|
+
@if (liveButtonPosition === 'right') {
|
|
494
|
+
<div style="display:flex;align-items:center;gap:4px">
|
|
495
|
+
<button
|
|
496
|
+
(click)="!live && jumpToLive.emit()"
|
|
497
|
+
[style.color]="(live || isLive) ? theme.controlBarBackground : theme.buttonActiveColor"
|
|
498
|
+
[style.background-color]="(live || isLive) ? theme.buttonActiveColor : 'transparent'"
|
|
499
|
+
[style.border-color]="theme.buttonActiveColor"
|
|
500
|
+
[style.opacity]="1"
|
|
501
|
+
[style.width.px]="liveSize.width"
|
|
502
|
+
[style.min-width.px]="liveSize.width"
|
|
503
|
+
[style.height.px]="liveSize.height"
|
|
504
|
+
[style.font-size]="liveSize.fontSize"
|
|
505
|
+
[style.border-radius]="liveSize.borderRadius"
|
|
506
|
+
[style.cursor]="live ? 'default' : 'pointer'"
|
|
507
|
+
style="background:none;border:1px solid;font-weight:bold;letter-spacing:0.05em;display:flex;align-items:center;justify-content:center;padding:0;gap:4px;font-family:system-ui,-apple-system,sans-serif;transition:opacity 0.15s"
|
|
508
|
+
[title]="(live || isLive) ? l.liveActiveTooltip : l.liveTooltip"
|
|
509
|
+
>
|
|
510
|
+
@if (live || isLive) {
|
|
511
|
+
<span
|
|
512
|
+
[style.width.px]="liveSize.dot"
|
|
513
|
+
[style.height.px]="liveSize.dot"
|
|
514
|
+
[style.background-color]="theme.liveDotColor"
|
|
515
|
+
style="border-radius:50%;display:inline-block;flex-shrink:0"
|
|
516
|
+
></span>
|
|
517
|
+
}
|
|
518
|
+
{{ (live || isLive) ? l.liveActiveLabel : l.liveLabel }}
|
|
519
|
+
</button>
|
|
520
|
+
@if (!isNormalSpeed && !live) {
|
|
521
|
+
<button
|
|
522
|
+
(click)="resetSpeed.emit()"
|
|
523
|
+
[style.color]="theme.buttonActiveColor"
|
|
524
|
+
[style.border-color]="theme.buttonActiveColor + '44'"
|
|
525
|
+
[style.width.px]="liveSize.width"
|
|
526
|
+
[style.min-width.px]="liveSize.width"
|
|
527
|
+
[style.height.px]="liveSize.height"
|
|
528
|
+
style="background:none;border:1px solid;cursor:pointer;font-size:11px;border-radius:4px;display:flex;align-items:center;justify-content:center;padding:0;font-family:system-ui,-apple-system,sans-serif;transition:background-color 0.15s"
|
|
529
|
+
[title]="l.resetSpeedTooltip"
|
|
530
|
+
>{{ isRewinding ? '◀ ' + absMultiplier + '×' : absMultiplier + '× ▶' }}</button>
|
|
531
|
+
}
|
|
532
|
+
</div>
|
|
533
|
+
}
|
|
406
534
|
@if (hasSwimLaneToggle) {
|
|
407
535
|
<button
|
|
408
536
|
(click)="toggleSwimLanes.emit()"
|
|
@@ -469,6 +597,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.10", ngImpo
|
|
|
469
597
|
type: Input
|
|
470
598
|
}], labels: [{
|
|
471
599
|
type: Input
|
|
600
|
+
}], liveButtonSize: [{
|
|
601
|
+
type: Input
|
|
602
|
+
}], liveButtonPosition: [{
|
|
603
|
+
type: Input
|
|
604
|
+
}], live: [{
|
|
605
|
+
type: Input
|
|
472
606
|
}], dateTimeClick: [{
|
|
473
607
|
type: Output
|
|
474
608
|
}], playPause: [{
|
|
@@ -511,6 +645,10 @@ class TimelineCanvasComponent {
|
|
|
511
645
|
months;
|
|
512
646
|
swimLanes;
|
|
513
647
|
showSwimLanes;
|
|
648
|
+
/** When true, needle scrub is disabled (left-click becomes pan). Zoom and pan remain active. */
|
|
649
|
+
disableNeedleDrag = false;
|
|
650
|
+
/** @see TimelineBaseProps.invertScrollZoom */
|
|
651
|
+
invertScrollZoom = false;
|
|
514
652
|
timeChange = new EventEmitter();
|
|
515
653
|
dragStart = new EventEmitter();
|
|
516
654
|
dragEnd = new EventEmitter();
|
|
@@ -519,6 +657,7 @@ class TimelineCanvasComponent {
|
|
|
519
657
|
swimLaneItemDoubleClick = new EventEmitter();
|
|
520
658
|
swimLaneItemContextMenu = new EventEmitter();
|
|
521
659
|
swimLaneReorder = new EventEmitter();
|
|
660
|
+
rangeSelect = new EventEmitter();
|
|
522
661
|
canvasRef;
|
|
523
662
|
// ── Mutable render state (equivalent to React refs) ────────────────────
|
|
524
663
|
startMs = 0;
|
|
@@ -534,6 +673,12 @@ class TimelineCanvasComponent {
|
|
|
534
673
|
mouseX = 0;
|
|
535
674
|
scrubClientX = 0;
|
|
536
675
|
swimLaneDownTime = 0;
|
|
676
|
+
// Range-selection state
|
|
677
|
+
rangeAnchorMs = 0;
|
|
678
|
+
rangeAnchorX = 0;
|
|
679
|
+
rangeSelection = null;
|
|
680
|
+
// Ghost needle (hover preview)
|
|
681
|
+
hoverMs = null;
|
|
537
682
|
// Touch state
|
|
538
683
|
touchMode = 'none';
|
|
539
684
|
touchX = 0;
|
|
@@ -722,6 +867,8 @@ class TimelineCanvasComponent {
|
|
|
722
867
|
showSwimLanes: this.showSwimLanesState,
|
|
723
868
|
scrollTop: this.scrollTop,
|
|
724
869
|
reorderState: this.reorderState,
|
|
870
|
+
rangeSelection: this.rangeSelection,
|
|
871
|
+
hoverMs: this.hoverMs,
|
|
725
872
|
});
|
|
726
873
|
if (clampedScrollTop !== this.scrollTop) {
|
|
727
874
|
this.scrollTop = clampedScrollTop;
|
|
@@ -823,14 +970,32 @@ class TimelineCanvasComponent {
|
|
|
823
970
|
}
|
|
824
971
|
}
|
|
825
972
|
if (e.button === 0) {
|
|
826
|
-
this.
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
this.
|
|
833
|
-
|
|
973
|
+
if (this.disableNeedleDrag) {
|
|
974
|
+
// In live mode left-click becomes a pan; needle scrub is disabled.
|
|
975
|
+
this.mouseMode = 'slide';
|
|
976
|
+
this.mouseX = e.clientX;
|
|
977
|
+
return;
|
|
978
|
+
}
|
|
979
|
+
const needleX = ((this.curMs - this.startMs) / (this.endMs - this.startMs)) * rect.width;
|
|
980
|
+
const nearNeedle = Math.abs(x - needleX) <= 10;
|
|
981
|
+
const inTickArea = y >= rect.height - TICK_AREA_HEIGHT;
|
|
982
|
+
if (!nearNeedle && inTickArea) {
|
|
983
|
+
this.mouseMode = 'rangeSelectPending';
|
|
984
|
+
this.rangeAnchorX = x;
|
|
985
|
+
this.rangeAnchorMs = this.startMs + (x / rect.width) * (this.endMs - this.startMs);
|
|
986
|
+
canvas.style.cursor = 'crosshair';
|
|
987
|
+
this.ngZone.run(() => this.dragStart.emit());
|
|
988
|
+
}
|
|
989
|
+
else {
|
|
990
|
+
this.mouseMode = 'scrub';
|
|
991
|
+
this.scrubClientX = e.clientX;
|
|
992
|
+
canvas.style.cursor = 'grabbing';
|
|
993
|
+
this.ngZone.run(() => this.dragStart.emit());
|
|
994
|
+
const ms = this.startMs + (x / rect.width) * (this.endMs - this.startMs);
|
|
995
|
+
this.curMs = ms;
|
|
996
|
+
this.draw();
|
|
997
|
+
this.ngZone.run(() => this.timeChange.emit(Cesium.JulianDate.fromDate(new Date(ms))));
|
|
998
|
+
}
|
|
834
999
|
}
|
|
835
1000
|
else if (e.button === 1) {
|
|
836
1001
|
this.mouseMode = 'slide';
|
|
@@ -892,6 +1057,19 @@ class TimelineCanvasComponent {
|
|
|
892
1057
|
this.draw();
|
|
893
1058
|
this.ngZone.run(() => this.timeChange.emit(Cesium.JulianDate.fromDate(new Date(ms))));
|
|
894
1059
|
}
|
|
1060
|
+
else if (this.mouseMode === 'rangeSelectPending' || this.mouseMode === 'rangeSelect') {
|
|
1061
|
+
const x = e.clientX - rect.left;
|
|
1062
|
+
const dx = Math.abs(x - this.rangeAnchorX);
|
|
1063
|
+
if (this.mouseMode === 'rangeSelectPending' && dx >= 3) {
|
|
1064
|
+
this.mouseMode = 'rangeSelect';
|
|
1065
|
+
}
|
|
1066
|
+
if (this.mouseMode === 'rangeSelect') {
|
|
1067
|
+
const cx = Math.max(0, Math.min(w, x));
|
|
1068
|
+
const curMs = this.startMs + (cx / w) * (this.endMs - this.startMs);
|
|
1069
|
+
this.rangeSelection = { startMs: this.rangeAnchorMs, endMs: curMs };
|
|
1070
|
+
this.draw();
|
|
1071
|
+
}
|
|
1072
|
+
}
|
|
895
1073
|
else if (this.mouseMode === 'slide') {
|
|
896
1074
|
const dx = this.mouseX - e.clientX;
|
|
897
1075
|
this.mouseX = e.clientX;
|
|
@@ -932,6 +1110,53 @@ class TimelineCanvasComponent {
|
|
|
932
1110
|
return;
|
|
933
1111
|
}
|
|
934
1112
|
this.stopEdgeScroll();
|
|
1113
|
+
if (this.mouseMode === 'rangeSelectPending') {
|
|
1114
|
+
// Short click — commit needle to anchor position
|
|
1115
|
+
this.curMs = this.rangeAnchorMs;
|
|
1116
|
+
this.rangeSelection = null;
|
|
1117
|
+
this.mouseMode = 'none';
|
|
1118
|
+
const canvas = this.canvasRef?.nativeElement;
|
|
1119
|
+
if (canvas)
|
|
1120
|
+
canvas.style.cursor = 'default';
|
|
1121
|
+
this.draw();
|
|
1122
|
+
this.ngZone.run(() => {
|
|
1123
|
+
this.timeChange.emit(Cesium.JulianDate.fromDate(new Date(this.rangeAnchorMs)));
|
|
1124
|
+
this.dragEnd.emit();
|
|
1125
|
+
});
|
|
1126
|
+
return;
|
|
1127
|
+
}
|
|
1128
|
+
if (this.mouseMode === 'rangeSelect') {
|
|
1129
|
+
const sel = this.rangeSelection;
|
|
1130
|
+
this.rangeSelection = null;
|
|
1131
|
+
if (sel) {
|
|
1132
|
+
const selStart = Math.min(sel.startMs, sel.endMs);
|
|
1133
|
+
const selEnd = Math.max(sel.startMs, sel.endMs);
|
|
1134
|
+
this.startMs = selStart;
|
|
1135
|
+
this.endMs = selEnd;
|
|
1136
|
+
// If the needle is outside the selected range, clamp it to the nearest
|
|
1137
|
+
// edge so the clock-tick auto-scroll doesn't immediately override the zoom.
|
|
1138
|
+
const clampedMs = Math.max(selStart, Math.min(selEnd, this.curMs));
|
|
1139
|
+
const needleMoved = clampedMs !== this.curMs;
|
|
1140
|
+
if (needleMoved) {
|
|
1141
|
+
this.curMs = clampedMs;
|
|
1142
|
+
}
|
|
1143
|
+
const startJd = Cesium.JulianDate.fromDate(new Date(selStart));
|
|
1144
|
+
const endJd = Cesium.JulianDate.fromDate(new Date(selEnd));
|
|
1145
|
+
this.ngZone.run(() => {
|
|
1146
|
+
this.rangeSelect.emit({ start: startJd, end: endJd });
|
|
1147
|
+
if (needleMoved) {
|
|
1148
|
+
this.timeChange.emit(Cesium.JulianDate.fromDate(new Date(clampedMs)));
|
|
1149
|
+
}
|
|
1150
|
+
});
|
|
1151
|
+
}
|
|
1152
|
+
this.mouseMode = 'none';
|
|
1153
|
+
const canvas = this.canvasRef?.nativeElement;
|
|
1154
|
+
if (canvas)
|
|
1155
|
+
canvas.style.cursor = 'default';
|
|
1156
|
+
this.draw();
|
|
1157
|
+
this.ngZone.run(() => this.dragEnd.emit());
|
|
1158
|
+
return;
|
|
1159
|
+
}
|
|
935
1160
|
this.mouseMode = 'none';
|
|
936
1161
|
const canvas = this.canvasRef?.nativeElement;
|
|
937
1162
|
if (canvas)
|
|
@@ -939,14 +1164,25 @@ class TimelineCanvasComponent {
|
|
|
939
1164
|
this.ngZone.run(() => this.dragEnd.emit());
|
|
940
1165
|
}
|
|
941
1166
|
onCanvasMouseMove(e) {
|
|
942
|
-
if (this.mouseMode !== 'none')
|
|
943
|
-
return;
|
|
944
1167
|
const canvas = this.canvasRef.nativeElement;
|
|
945
1168
|
const rect = canvas.getBoundingClientRect();
|
|
946
1169
|
const x = e.clientX - rect.left;
|
|
947
1170
|
const y = e.clientY - rect.top;
|
|
1171
|
+
// Update ghost needle while idle
|
|
1172
|
+
if (this.mouseMode === 'none') {
|
|
1173
|
+
if (!this.disableNeedleDrag) {
|
|
1174
|
+
this.hoverMs = this.startMs + (Math.max(0, Math.min(rect.width, x)) / rect.width) * (this.endMs - this.startMs);
|
|
1175
|
+
}
|
|
1176
|
+
else if (this.hoverMs !== null) {
|
|
1177
|
+
this.hoverMs = null;
|
|
1178
|
+
}
|
|
1179
|
+
}
|
|
1180
|
+
else {
|
|
1181
|
+
this.hoverMs = null;
|
|
1182
|
+
return;
|
|
1183
|
+
}
|
|
948
1184
|
const needleX = ((this.curMs - this.startMs) / (this.endMs - this.startMs)) * rect.width;
|
|
949
|
-
const nearNeedle = Math.abs(x - needleX) <= 10;
|
|
1185
|
+
const nearNeedle = !this.disableNeedleDrag && Math.abs(x - needleX) <= 10;
|
|
950
1186
|
if (this.isInSwimLaneRegion(y, rect.height)) {
|
|
951
1187
|
const hit = this.hitTestSwimLane(x, y, rect.width, rect.height);
|
|
952
1188
|
const prev = this.hoveredItem;
|
|
@@ -955,14 +1191,12 @@ class TimelineCanvasComponent {
|
|
|
955
1191
|
if (!prev || prev.item.id !== hit.item.id || prev.lane.id !== hit.lane.id) {
|
|
956
1192
|
this.hoveredItem = hit;
|
|
957
1193
|
this.ngZone.run(() => this.swimLaneItemHover.emit({ laneId: hit.lane.id, item: hit.item, originalEvent: e }));
|
|
958
|
-
this.draw();
|
|
959
1194
|
}
|
|
960
1195
|
}
|
|
961
1196
|
else {
|
|
962
1197
|
if (prev) {
|
|
963
1198
|
this.hoveredItem = null;
|
|
964
1199
|
this.ngZone.run(() => this.swimLaneItemHover.emit(null));
|
|
965
|
-
this.draw();
|
|
966
1200
|
}
|
|
967
1201
|
if (nearNeedle) {
|
|
968
1202
|
canvas.style.cursor = 'grab';
|
|
@@ -972,14 +1206,23 @@ class TimelineCanvasComponent {
|
|
|
972
1206
|
canvas.style.cursor = labelLane && this.swimLaneReorder.observed ? 'grab' : 'default';
|
|
973
1207
|
}
|
|
974
1208
|
}
|
|
1209
|
+
this.draw();
|
|
975
1210
|
return;
|
|
976
1211
|
}
|
|
977
1212
|
if (this.hoveredItem) {
|
|
978
1213
|
this.hoveredItem = null;
|
|
979
1214
|
this.ngZone.run(() => this.swimLaneItemHover.emit(null));
|
|
980
|
-
this.draw();
|
|
981
1215
|
}
|
|
982
|
-
|
|
1216
|
+
if (nearNeedle) {
|
|
1217
|
+
canvas.style.cursor = 'grab';
|
|
1218
|
+
}
|
|
1219
|
+
else if (!this.disableNeedleDrag && y >= rect.height - TICK_AREA_HEIGHT) {
|
|
1220
|
+
canvas.style.cursor = 'crosshair';
|
|
1221
|
+
}
|
|
1222
|
+
else {
|
|
1223
|
+
canvas.style.cursor = 'default';
|
|
1224
|
+
}
|
|
1225
|
+
this.draw();
|
|
983
1226
|
}
|
|
984
1227
|
onCanvasClick(e) {
|
|
985
1228
|
const elapsed = performance.now() - this.swimLaneDownTime;
|
|
@@ -1019,11 +1262,12 @@ class TimelineCanvasComponent {
|
|
|
1019
1262
|
if (this.hoveredItem) {
|
|
1020
1263
|
this.hoveredItem = null;
|
|
1021
1264
|
this.ngZone.run(() => this.swimLaneItemHover.emit(null));
|
|
1022
|
-
this.draw();
|
|
1023
1265
|
}
|
|
1266
|
+
this.hoverMs = null;
|
|
1024
1267
|
const canvas = this.canvasRef?.nativeElement;
|
|
1025
1268
|
if (this.mouseMode === 'none' && canvas)
|
|
1026
1269
|
canvas.style.cursor = 'default';
|
|
1270
|
+
this.draw();
|
|
1027
1271
|
}
|
|
1028
1272
|
// ── Wheel handler ──────────────────────────────────────────────────────
|
|
1029
1273
|
onWheel(e) {
|
|
@@ -1044,7 +1288,7 @@ class TimelineCanvasComponent {
|
|
|
1044
1288
|
return;
|
|
1045
1289
|
}
|
|
1046
1290
|
}
|
|
1047
|
-
this.zoomFrom(Math.pow(1.05, e.deltaY > 0 ? -1 : 1));
|
|
1291
|
+
this.zoomFrom(Math.pow(1.05, e.deltaY > 0 === this.invertScrollZoom ? -1 : 1));
|
|
1048
1292
|
}
|
|
1049
1293
|
// ── Touch handlers ─────────────────────────────────────────────────────
|
|
1050
1294
|
onTouchStart(e) {
|
|
@@ -1056,15 +1300,22 @@ class TimelineCanvasComponent {
|
|
|
1056
1300
|
const cx = Math.max(0, Math.min(rect.width, x));
|
|
1057
1301
|
const ms = this.startMs + (cx / rect.width) * (this.endMs - this.startMs);
|
|
1058
1302
|
this.prePinchCurMs = this.curMs;
|
|
1059
|
-
this.
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
this.
|
|
1066
|
-
this.
|
|
1067
|
-
|
|
1303
|
+
if (this.disableNeedleDrag) {
|
|
1304
|
+
// In live mode single-finger becomes a pan, not a scrub.
|
|
1305
|
+
this.touchMode = 'slide';
|
|
1306
|
+
this.touchX = e.touches[0].clientX;
|
|
1307
|
+
}
|
|
1308
|
+
else {
|
|
1309
|
+
this.touchMode = 'scrub';
|
|
1310
|
+
this.touchX = e.touches[0].clientX;
|
|
1311
|
+
this.scrubClientX = e.touches[0].clientX;
|
|
1312
|
+
this.curMs = ms;
|
|
1313
|
+
this.draw();
|
|
1314
|
+
this.ngZone.run(() => {
|
|
1315
|
+
this.dragStart.emit();
|
|
1316
|
+
this.timeChange.emit(Cesium.JulianDate.fromDate(new Date(ms)));
|
|
1317
|
+
});
|
|
1318
|
+
}
|
|
1068
1319
|
}
|
|
1069
1320
|
else if (e.touches.length >= 2) {
|
|
1070
1321
|
// If we were scrubbing, undo the needle move — pinch-zoom should not
|
|
@@ -1140,7 +1391,7 @@ class TimelineCanvasComponent {
|
|
|
1140
1391
|
}
|
|
1141
1392
|
}
|
|
1142
1393
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.10", ngImport: i0, type: TimelineCanvasComponent, deps: [{ token: i0.NgZone }], target: i0.ɵɵFactoryTarget.Component });
|
|
1143
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.10", type: TimelineCanvasComponent, isStandalone: true, selector: "ct-timeline-canvas", inputs: { currentTime: "currentTime", defaultStartMs: "defaultStartMs", defaultEndMs: "defaultEndMs", theme: "theme", maxTicks: "maxTicks", timezone: "timezone", dateTimeFormat: "dateTimeFormat", months: "months", swimLanes: "swimLanes", showSwimLanes: "showSwimLanes" }, outputs: { timeChange: "timeChange", dragStart: "dragStart", dragEnd: "dragEnd", swimLaneItemClick: "swimLaneItemClick", swimLaneItemHover: "swimLaneItemHover", swimLaneItemDoubleClick: "swimLaneItemDoubleClick", swimLaneItemContextMenu: "swimLaneItemContextMenu", swimLaneReorder: "swimLaneReorder" }, viewQueries: [{ propertyName: "canvasRef", first: true, predicate: ["canvas"], descendants: true }], usesOnChanges: true, ngImport: i0, template: `<canvas #canvas
|
|
1394
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.10", type: TimelineCanvasComponent, isStandalone: true, selector: "ct-timeline-canvas", inputs: { currentTime: "currentTime", defaultStartMs: "defaultStartMs", defaultEndMs: "defaultEndMs", theme: "theme", maxTicks: "maxTicks", timezone: "timezone", dateTimeFormat: "dateTimeFormat", months: "months", swimLanes: "swimLanes", showSwimLanes: "showSwimLanes", disableNeedleDrag: "disableNeedleDrag", invertScrollZoom: "invertScrollZoom" }, outputs: { timeChange: "timeChange", dragStart: "dragStart", dragEnd: "dragEnd", swimLaneItemClick: "swimLaneItemClick", swimLaneItemHover: "swimLaneItemHover", swimLaneItemDoubleClick: "swimLaneItemDoubleClick", swimLaneItemContextMenu: "swimLaneItemContextMenu", swimLaneReorder: "swimLaneReorder", rangeSelect: "rangeSelect" }, viewQueries: [{ propertyName: "canvasRef", first: true, predicate: ["canvas"], descendants: true }], usesOnChanges: true, ngImport: i0, template: `<canvas #canvas
|
|
1144
1395
|
style="width:100%;flex:1;min-height:0;display:block;cursor:default"
|
|
1145
1396
|
(mousedown)="onCanvasMouseDown($event)"
|
|
1146
1397
|
(mousemove)="onCanvasMouseMove($event)"
|
|
@@ -1181,6 +1432,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.10", ngImpo
|
|
|
1181
1432
|
type: Input
|
|
1182
1433
|
}], showSwimLanes: [{
|
|
1183
1434
|
type: Input
|
|
1435
|
+
}], disableNeedleDrag: [{
|
|
1436
|
+
type: Input
|
|
1437
|
+
}], invertScrollZoom: [{
|
|
1438
|
+
type: Input
|
|
1184
1439
|
}], timeChange: [{
|
|
1185
1440
|
type: Output
|
|
1186
1441
|
}], dragStart: [{
|
|
@@ -1197,6 +1452,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.10", ngImpo
|
|
|
1197
1452
|
type: Output
|
|
1198
1453
|
}], swimLaneReorder: [{
|
|
1199
1454
|
type: Output
|
|
1455
|
+
}], rangeSelect: [{
|
|
1456
|
+
type: Output
|
|
1200
1457
|
}], canvasRef: [{
|
|
1201
1458
|
type: ViewChild,
|
|
1202
1459
|
args: ['canvas']
|
|
@@ -1230,6 +1487,13 @@ class TimelineComponent {
|
|
|
1230
1487
|
swimLaneTransition = 'animated';
|
|
1231
1488
|
/** Overrides for control-bar labels and tooltips (i18n / custom verbiage). */
|
|
1232
1489
|
labels;
|
|
1490
|
+
/** @see TimelineBaseProps.liveButtonSize */
|
|
1491
|
+
liveButtonSize;
|
|
1492
|
+
/** @see TimelineBaseProps.liveButtonPosition */
|
|
1493
|
+
liveButtonPosition;
|
|
1494
|
+
/** @see TimelineBaseProps.live */
|
|
1495
|
+
live = false;
|
|
1496
|
+
invertScrollZoom = false;
|
|
1233
1497
|
// ── Outputs ────────────────────────────────────────────────────────────
|
|
1234
1498
|
timeChange = new EventEmitter();
|
|
1235
1499
|
playPause = new EventEmitter();
|
|
@@ -1241,6 +1505,7 @@ class TimelineComponent {
|
|
|
1241
1505
|
swimLaneItemDoubleClick = new EventEmitter();
|
|
1242
1506
|
swimLaneItemContextMenu = new EventEmitter();
|
|
1243
1507
|
swimLaneReorder = new EventEmitter();
|
|
1508
|
+
rangeSelect = new EventEmitter();
|
|
1244
1509
|
// ── ViewChild refs ─────────────────────────────────────────────────────
|
|
1245
1510
|
canvasComp;
|
|
1246
1511
|
controlsRef;
|
|
@@ -1265,7 +1530,7 @@ class TimelineComponent {
|
|
|
1265
1530
|
return this.swimLanes != null && this.swimLanes.length > 0;
|
|
1266
1531
|
}
|
|
1267
1532
|
get isLive() {
|
|
1268
|
-
return Math.abs(Cesium.JulianDate.toDate(this.currentTimeState).getTime() - Date.now()) <
|
|
1533
|
+
return Math.abs(Cesium.JulianDate.toDate(this.currentTimeState).getTime() - Date.now()) < 2_000;
|
|
1269
1534
|
}
|
|
1270
1535
|
get isCollapsed() {
|
|
1271
1536
|
return this.hasSwimLanes && !this.swimLanesExpanded;
|
|
@@ -1316,7 +1581,7 @@ class TimelineComponent {
|
|
|
1316
1581
|
this.cleanupClockSync();
|
|
1317
1582
|
this.setupClockSync();
|
|
1318
1583
|
}
|
|
1319
|
-
if (changes['jumpToTime'] && this.jumpToTime) {
|
|
1584
|
+
if (changes['jumpToTime'] && this.jumpToTime && !this.live) {
|
|
1320
1585
|
const t = toJulianDate(this.jumpToTime);
|
|
1321
1586
|
this.handleTimeChange(t);
|
|
1322
1587
|
if (this.canvasComp) {
|
|
@@ -1470,7 +1735,7 @@ class TimelineComponent {
|
|
|
1470
1735
|
this.cdr.markForCheck();
|
|
1471
1736
|
}
|
|
1472
1737
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.10", ngImport: i0, type: TimelineComponent, deps: [{ token: i0.ChangeDetectorRef }, { token: i0.NgZone }], target: i0.ɵɵFactoryTarget.Component });
|
|
1473
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.10", type: TimelineComponent, isStandalone: true, selector: "ct-timeline", inputs: { startTime: "startTime", endTime: "endTime", currentTime: "currentTime", clock: "clock", height: "height", showControls: "showControls", showJumpToStart: "showJumpToStart", showJumpToEnd: "showJumpToEnd", enableDrag: "enableDrag", dateTimeFormat: "dateTimeFormat", jumpToTime: "jumpToTime", maxTicks: "maxTicks", ffSpeeds: "ffSpeeds", rwSpeeds: "rwSpeeds", theme: "theme", cssClass: "cssClass", timezone: "timezone", swimLanes: "swimLanes", showSwimLanes: "showSwimLanes", swimLaneTransition: "swimLaneTransition", labels: "labels" }, outputs: { timeChange: "timeChange", playPause: "playPause", multiplierChange: "multiplierChange", dateTimeClick: "dateTimeClick", showSwimLanesChange: "showSwimLanesChange", swimLaneItemClick: "swimLaneItemClick", swimLaneItemHover: "swimLaneItemHover", swimLaneItemDoubleClick: "swimLaneItemDoubleClick", swimLaneItemContextMenu: "swimLaneItemContextMenu", swimLaneReorder: "swimLaneReorder" }, viewQueries: [{ propertyName: "canvasComp", first: true, predicate: TimelineCanvasComponent, descendants: true }, { propertyName: "controlsRef", first: true, predicate: ["controlsEl"], descendants: true }], usesOnChanges: true, ngImport: i0, template: `
|
|
1738
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.10", type: TimelineComponent, isStandalone: true, selector: "ct-timeline", inputs: { startTime: "startTime", endTime: "endTime", currentTime: "currentTime", clock: "clock", height: "height", showControls: "showControls", showJumpToStart: "showJumpToStart", showJumpToEnd: "showJumpToEnd", enableDrag: "enableDrag", dateTimeFormat: "dateTimeFormat", jumpToTime: "jumpToTime", maxTicks: "maxTicks", ffSpeeds: "ffSpeeds", rwSpeeds: "rwSpeeds", theme: "theme", cssClass: "cssClass", timezone: "timezone", swimLanes: "swimLanes", showSwimLanes: "showSwimLanes", swimLaneTransition: "swimLaneTransition", labels: "labels", liveButtonSize: "liveButtonSize", liveButtonPosition: "liveButtonPosition", live: "live", invertScrollZoom: "invertScrollZoom" }, outputs: { timeChange: "timeChange", playPause: "playPause", multiplierChange: "multiplierChange", dateTimeClick: "dateTimeClick", showSwimLanesChange: "showSwimLanesChange", swimLaneItemClick: "swimLaneItemClick", swimLaneItemHover: "swimLaneItemHover", swimLaneItemDoubleClick: "swimLaneItemDoubleClick", swimLaneItemContextMenu: "swimLaneItemContextMenu", swimLaneReorder: "swimLaneReorder", rangeSelect: "rangeSelect" }, viewQueries: [{ propertyName: "canvasComp", first: true, predicate: TimelineCanvasComponent, descendants: true }, { propertyName: "controlsRef", first: true, predicate: ["controlsEl"], descendants: true }], usesOnChanges: true, ngImport: i0, template: `
|
|
1474
1739
|
<div
|
|
1475
1740
|
[class]="cssClass"
|
|
1476
1741
|
[style.width]="'100%'"
|
|
@@ -1506,11 +1771,14 @@ class TimelineComponent {
|
|
|
1506
1771
|
(dateTimeClick)="dateTimeClick.emit()"
|
|
1507
1772
|
(toggleSwimLanes)="handleToggleSwimLanes()"
|
|
1508
1773
|
[labels]="labels"
|
|
1774
|
+
[liveButtonSize]="liveButtonSize"
|
|
1775
|
+
[liveButtonPosition]="liveButtonPosition"
|
|
1776
|
+
[live]="live"
|
|
1509
1777
|
/>
|
|
1510
1778
|
</div>
|
|
1511
1779
|
}
|
|
1512
1780
|
|
|
1513
|
-
@if (enableDrag !== false) {
|
|
1781
|
+
@if (enableDrag !== false || live) {
|
|
1514
1782
|
<ct-timeline-canvas
|
|
1515
1783
|
[currentTime]="currentTimeState"
|
|
1516
1784
|
[defaultStartMs]="defaultStartMs"
|
|
@@ -1522,6 +1790,8 @@ class TimelineComponent {
|
|
|
1522
1790
|
[months]="labels?.months"
|
|
1523
1791
|
[swimLanes]="swimLanes"
|
|
1524
1792
|
[showSwimLanes]="swimLanesExpanded"
|
|
1793
|
+
[disableNeedleDrag]="live"
|
|
1794
|
+
[invertScrollZoom]="invertScrollZoom"
|
|
1525
1795
|
(timeChange)="handleTimeChange($event)"
|
|
1526
1796
|
(dragStart)="isDragging = true"
|
|
1527
1797
|
(dragEnd)="isDragging = false"
|
|
@@ -1530,10 +1800,11 @@ class TimelineComponent {
|
|
|
1530
1800
|
(swimLaneItemDoubleClick)="swimLaneItemDoubleClick.emit($event)"
|
|
1531
1801
|
(swimLaneItemContextMenu)="swimLaneItemContextMenu.emit($event)"
|
|
1532
1802
|
(swimLaneReorder)="swimLaneReorder.emit($event)"
|
|
1803
|
+
(rangeSelect)="rangeSelect.emit($event)"
|
|
1533
1804
|
/>
|
|
1534
1805
|
}
|
|
1535
1806
|
</div>
|
|
1536
|
-
`, isInline: true, styles: [":host{display:block}\n"], dependencies: [{ kind: "component", type: TimelineControlsComponent, selector: "ct-timeline-controls", inputs: ["currentTime", "isPlaying", "multiplier", "dateTimeFormat", "timezone", "isLive", "hasStartTime", "hasEndTime", "showJumpToStart", "showJumpToEnd", "theme", "swimLanesVisible", "labels"], outputs: ["dateTimeClick", "playPause", "jumpToStart", "rewind", "fastForward", "jumpToEnd", "jumpToLive", "resetSpeed", "toggleSwimLanes"] }, { kind: "component", type: TimelineCanvasComponent, selector: "ct-timeline-canvas", inputs: ["currentTime", "defaultStartMs", "defaultEndMs", "theme", "maxTicks", "timezone", "dateTimeFormat", "months", "swimLanes", "showSwimLanes"], outputs: ["timeChange", "dragStart", "dragEnd", "swimLaneItemClick", "swimLaneItemHover", "swimLaneItemDoubleClick", "swimLaneItemContextMenu", "swimLaneReorder"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
1807
|
+
`, isInline: true, styles: [":host{display:block}\n"], dependencies: [{ kind: "component", type: TimelineControlsComponent, selector: "ct-timeline-controls", inputs: ["currentTime", "isPlaying", "multiplier", "dateTimeFormat", "timezone", "isLive", "hasStartTime", "hasEndTime", "showJumpToStart", "showJumpToEnd", "theme", "swimLanesVisible", "labels", "liveButtonSize", "liveButtonPosition", "live"], outputs: ["dateTimeClick", "playPause", "jumpToStart", "rewind", "fastForward", "jumpToEnd", "jumpToLive", "resetSpeed", "toggleSwimLanes"] }, { kind: "component", type: TimelineCanvasComponent, selector: "ct-timeline-canvas", inputs: ["currentTime", "defaultStartMs", "defaultEndMs", "theme", "maxTicks", "timezone", "dateTimeFormat", "months", "swimLanes", "showSwimLanes", "disableNeedleDrag", "invertScrollZoom"], outputs: ["timeChange", "dragStart", "dragEnd", "swimLaneItemClick", "swimLaneItemHover", "swimLaneItemDoubleClick", "swimLaneItemContextMenu", "swimLaneReorder", "rangeSelect"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
1537
1808
|
}
|
|
1538
1809
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.10", ngImport: i0, type: TimelineComponent, decorators: [{
|
|
1539
1810
|
type: Component,
|
|
@@ -1573,11 +1844,14 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.10", ngImpo
|
|
|
1573
1844
|
(dateTimeClick)="dateTimeClick.emit()"
|
|
1574
1845
|
(toggleSwimLanes)="handleToggleSwimLanes()"
|
|
1575
1846
|
[labels]="labels"
|
|
1847
|
+
[liveButtonSize]="liveButtonSize"
|
|
1848
|
+
[liveButtonPosition]="liveButtonPosition"
|
|
1849
|
+
[live]="live"
|
|
1576
1850
|
/>
|
|
1577
1851
|
</div>
|
|
1578
1852
|
}
|
|
1579
1853
|
|
|
1580
|
-
@if (enableDrag !== false) {
|
|
1854
|
+
@if (enableDrag !== false || live) {
|
|
1581
1855
|
<ct-timeline-canvas
|
|
1582
1856
|
[currentTime]="currentTimeState"
|
|
1583
1857
|
[defaultStartMs]="defaultStartMs"
|
|
@@ -1589,6 +1863,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.10", ngImpo
|
|
|
1589
1863
|
[months]="labels?.months"
|
|
1590
1864
|
[swimLanes]="swimLanes"
|
|
1591
1865
|
[showSwimLanes]="swimLanesExpanded"
|
|
1866
|
+
[disableNeedleDrag]="live"
|
|
1867
|
+
[invertScrollZoom]="invertScrollZoom"
|
|
1592
1868
|
(timeChange)="handleTimeChange($event)"
|
|
1593
1869
|
(dragStart)="isDragging = true"
|
|
1594
1870
|
(dragEnd)="isDragging = false"
|
|
@@ -1597,6 +1873,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.10", ngImpo
|
|
|
1597
1873
|
(swimLaneItemDoubleClick)="swimLaneItemDoubleClick.emit($event)"
|
|
1598
1874
|
(swimLaneItemContextMenu)="swimLaneItemContextMenu.emit($event)"
|
|
1599
1875
|
(swimLaneReorder)="swimLaneReorder.emit($event)"
|
|
1876
|
+
(rangeSelect)="rangeSelect.emit($event)"
|
|
1600
1877
|
/>
|
|
1601
1878
|
}
|
|
1602
1879
|
</div>
|
|
@@ -1643,6 +1920,14 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.10", ngImpo
|
|
|
1643
1920
|
type: Input
|
|
1644
1921
|
}], labels: [{
|
|
1645
1922
|
type: Input
|
|
1923
|
+
}], liveButtonSize: [{
|
|
1924
|
+
type: Input
|
|
1925
|
+
}], liveButtonPosition: [{
|
|
1926
|
+
type: Input
|
|
1927
|
+
}], live: [{
|
|
1928
|
+
type: Input
|
|
1929
|
+
}], invertScrollZoom: [{
|
|
1930
|
+
type: Input
|
|
1646
1931
|
}], timeChange: [{
|
|
1647
1932
|
type: Output
|
|
1648
1933
|
}], playPause: [{
|
|
@@ -1663,6 +1948,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.10", ngImpo
|
|
|
1663
1948
|
type: Output
|
|
1664
1949
|
}], swimLaneReorder: [{
|
|
1665
1950
|
type: Output
|
|
1951
|
+
}], rangeSelect: [{
|
|
1952
|
+
type: Output
|
|
1666
1953
|
}], canvasComp: [{
|
|
1667
1954
|
type: ViewChild,
|
|
1668
1955
|
args: [TimelineCanvasComponent]
|