@design.estate/dees-catalog 3.100.0 → 3.102.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.
- package/dist_bundle/bundle.js +286 -8
- package/dist_ts_web/00_commitinfo_data.js +1 -1
- package/dist_ts_web/elements/00group-harness/dees-harness-session-list/dees-harness-session-list.d.ts +33 -3
- package/dist_ts_web/elements/00group-harness/dees-harness-session-list/dees-harness-session-list.js +334 -11
- package/dist_ts_web/elements/00group-harness/interfaces.d.ts +30 -0
- package/package.json +1 -1
- package/ts_web/00_commitinfo_data.ts +1 -1
- package/ts_web/elements/00group-harness/dees-harness-session-list/dees-harness-session-list.ts +342 -9
- package/ts_web/elements/00group-harness/interfaces.ts +34 -0
package/ts_web/elements/00group-harness/dees-harness-session-list/dees-harness-session-list.ts
CHANGED
|
@@ -10,7 +10,13 @@ import {
|
|
|
10
10
|
cssManager,
|
|
11
11
|
} from '@design.estate/dees-element';
|
|
12
12
|
import { themeDefaultStyles } from '../../00theme.js';
|
|
13
|
-
import type {
|
|
13
|
+
import type {
|
|
14
|
+
IHarnessGroupContextDetail,
|
|
15
|
+
IHarnessSessionContextDetail,
|
|
16
|
+
IHarnessSessionGroup,
|
|
17
|
+
IHarnessSessionMeta,
|
|
18
|
+
IHarnessSessionMoveDetail,
|
|
19
|
+
} from '../interfaces.js';
|
|
14
20
|
import { harnessFormatRelativeTime } from '../harness.helpers.js';
|
|
15
21
|
import '../../00group-utility/dees-icon/dees-icon.js';
|
|
16
22
|
|
|
@@ -20,10 +26,24 @@ declare global {
|
|
|
20
26
|
}
|
|
21
27
|
}
|
|
22
28
|
|
|
29
|
+
interface IDropSlot {
|
|
30
|
+
groupId: string | null;
|
|
31
|
+
index: number;
|
|
32
|
+
}
|
|
33
|
+
|
|
23
34
|
/**
|
|
24
35
|
* Searchable conversation list, embeddable in a sidebar or a DeesModal.
|
|
25
|
-
* Emits `harness-session-select` on click
|
|
26
|
-
* double-click or Enter
|
|
36
|
+
* Emits `harness-session-select` on click, `harness-session-open` on
|
|
37
|
+
* double-click or Enter, and `harness-session-context` with viewport
|
|
38
|
+
* pointer coordinates on right-click (the browser menu is suppressed).
|
|
39
|
+
*
|
|
40
|
+
* With `enableGrouping`, sessions render under user-defined `groups` and can
|
|
41
|
+
* be dragged between groups and reordered. The component never mutates its
|
|
42
|
+
* inputs: drops emit `harness-session-move`, the "new group" button emits
|
|
43
|
+
* `harness-group-create-request`, and right-clicking a group header emits
|
|
44
|
+
* `harness-group-context` — the host persists changes and passes fresh
|
|
45
|
+
* `groups` back in. Sessions in no group render in the trailing ungrouped
|
|
46
|
+
* section, recency-sorted. While a search query is active the list is flat.
|
|
27
47
|
*/
|
|
28
48
|
@customElement('dees-harness-session-list')
|
|
29
49
|
export class DeesHarnessSessionList extends DeesElement {
|
|
@@ -42,9 +62,24 @@ export class DeesHarnessSessionList extends DeesElement {
|
|
|
42
62
|
@property()
|
|
43
63
|
accessor emptyText: string = 'No conversations yet.';
|
|
44
64
|
|
|
65
|
+
@property({ attribute: false })
|
|
66
|
+
accessor groups: IHarnessSessionGroup[] = [];
|
|
67
|
+
|
|
68
|
+
@property({ type: Boolean })
|
|
69
|
+
accessor enableGrouping: boolean = false;
|
|
70
|
+
|
|
45
71
|
@state()
|
|
46
72
|
accessor search: string = '';
|
|
47
73
|
|
|
74
|
+
@state()
|
|
75
|
+
accessor draggingSessionId: string = '';
|
|
76
|
+
|
|
77
|
+
@state()
|
|
78
|
+
accessor dropSlot: IDropSlot | undefined = undefined;
|
|
79
|
+
|
|
80
|
+
@state()
|
|
81
|
+
accessor collapsedGroupIds: ReadonlySet<string> = new Set<string>();
|
|
82
|
+
|
|
48
83
|
public static styles = [
|
|
49
84
|
themeDefaultStyles,
|
|
50
85
|
cssManager.defaultStyles,
|
|
@@ -117,6 +152,10 @@ export class DeesHarnessSessionList extends DeesElement {
|
|
|
117
152
|
box-shadow: inset 2px 0 0 var(--dees-color-accent-primary);
|
|
118
153
|
}
|
|
119
154
|
|
|
155
|
+
.item.dragging {
|
|
156
|
+
opacity: 0.4;
|
|
157
|
+
}
|
|
158
|
+
|
|
120
159
|
.itemTitle {
|
|
121
160
|
display: flex;
|
|
122
161
|
align-items: baseline;
|
|
@@ -154,9 +193,109 @@ export class DeesHarnessSessionList extends DeesElement {
|
|
|
154
193
|
font-size: var(--dees-font-control-size-sm, 12px);
|
|
155
194
|
text-align: center;
|
|
156
195
|
}
|
|
196
|
+
|
|
197
|
+
.groupHeader {
|
|
198
|
+
display: flex;
|
|
199
|
+
align-items: center;
|
|
200
|
+
gap: var(--dees-spacing-xs);
|
|
201
|
+
width: 100%;
|
|
202
|
+
margin-top: var(--dees-spacing-xs);
|
|
203
|
+
padding: 2px var(--dees-spacing-sm);
|
|
204
|
+
border: 0;
|
|
205
|
+
border-radius: var(--dees-radius-md);
|
|
206
|
+
background: transparent;
|
|
207
|
+
color: var(--dees-color-text-secondary);
|
|
208
|
+
cursor: pointer;
|
|
209
|
+
font-family: inherit;
|
|
210
|
+
font-size: var(--dees-font-caption1-size, 11px);
|
|
211
|
+
font-weight: 650;
|
|
212
|
+
text-transform: uppercase;
|
|
213
|
+
letter-spacing: 0.05em;
|
|
214
|
+
text-align: left;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
.groupHeader:hover {
|
|
218
|
+
background: var(--dees-color-hover);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
.groupHeader .chevron {
|
|
222
|
+
transition: transform var(--dees-transition-fast) var(--dees-ease-standard);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
.groupHeader.collapsed .chevron {
|
|
226
|
+
transform: rotate(-90deg);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
.groupHeader .groupCount {
|
|
230
|
+
margin-left: auto;
|
|
231
|
+
font-weight: 500;
|
|
232
|
+
text-transform: none;
|
|
233
|
+
letter-spacing: normal;
|
|
234
|
+
color: var(--dees-color-text-muted);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
.groupHeader.dropTarget {
|
|
238
|
+
outline: 1px dashed var(--dees-color-accent-primary);
|
|
239
|
+
outline-offset: -1px;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
.dropLine {
|
|
243
|
+
height: 2px;
|
|
244
|
+
margin: 0 var(--dees-spacing-sm);
|
|
245
|
+
border-radius: 1px;
|
|
246
|
+
background: var(--dees-color-accent-primary);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
.newGroupRow {
|
|
250
|
+
flex: 0 0 auto;
|
|
251
|
+
padding: var(--dees-spacing-xs) var(--dees-spacing-sm) var(--dees-spacing-sm);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
.newGroupButton {
|
|
255
|
+
display: flex;
|
|
256
|
+
align-items: center;
|
|
257
|
+
gap: var(--dees-spacing-xs);
|
|
258
|
+
width: 100%;
|
|
259
|
+
padding: var(--dees-spacing-xs) var(--dees-spacing-sm);
|
|
260
|
+
border: 1px dashed var(--dees-color-border-default);
|
|
261
|
+
border-radius: var(--dees-radius-md);
|
|
262
|
+
background: transparent;
|
|
263
|
+
color: var(--dees-color-text-secondary);
|
|
264
|
+
cursor: pointer;
|
|
265
|
+
font-family: inherit;
|
|
266
|
+
font-size: var(--dees-font-control-size-sm, 12px);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
.newGroupButton:hover {
|
|
270
|
+
border-color: var(--dees-color-accent-primary);
|
|
271
|
+
color: var(--dees-color-text-primary);
|
|
272
|
+
}
|
|
157
273
|
`,
|
|
158
274
|
];
|
|
159
275
|
|
|
276
|
+
private get sessionsById(): Map<string, IHarnessSessionMeta> {
|
|
277
|
+
return new Map(this.sessions.map((session) => [session.id, session]));
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
private get groupedSessionIds(): Set<string> {
|
|
281
|
+
const ids = new Set<string>();
|
|
282
|
+
for (const group of this.groups) {
|
|
283
|
+
for (const id of group.sessionIds) ids.add(id);
|
|
284
|
+
}
|
|
285
|
+
return ids;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/** Ungrouped sessions, most recent first. */
|
|
289
|
+
private get ungroupedSessions(): IHarnessSessionMeta[] {
|
|
290
|
+
const grouped = this.groupedSessionIds;
|
|
291
|
+
return [...this.sessions]
|
|
292
|
+
.filter((session) => !grouped.has(session.id))
|
|
293
|
+
.sort(
|
|
294
|
+
(left, right) =>
|
|
295
|
+
(right.updatedAt ?? right.createdAt ?? 0) - (left.updatedAt ?? left.createdAt ?? 0),
|
|
296
|
+
);
|
|
297
|
+
}
|
|
298
|
+
|
|
160
299
|
private get filteredSessions(): IHarnessSessionMeta[] {
|
|
161
300
|
const query = this.search.trim().toLowerCase();
|
|
162
301
|
const sorted = [...this.sessions].sort(
|
|
@@ -173,7 +312,7 @@ export class DeesHarnessSessionList extends DeesElement {
|
|
|
173
312
|
}
|
|
174
313
|
|
|
175
314
|
public render(): TemplateResult {
|
|
176
|
-
const
|
|
315
|
+
const groupingActive = this.enableGrouping && !this.search.trim();
|
|
177
316
|
return html`
|
|
178
317
|
<div class="searchRow">
|
|
179
318
|
<input
|
|
@@ -187,23 +326,178 @@ export class DeesHarnessSessionList extends DeesElement {
|
|
|
187
326
|
<div class="list">
|
|
188
327
|
${this.loading
|
|
189
328
|
? html`<div class="emptyNote">Loading conversations…</div>`
|
|
190
|
-
:
|
|
191
|
-
?
|
|
192
|
-
:
|
|
329
|
+
: groupingActive
|
|
330
|
+
? this.renderGroupedList()
|
|
331
|
+
: this.renderFlatList()}
|
|
193
332
|
</div>
|
|
333
|
+
${groupingActive
|
|
334
|
+
? html`
|
|
335
|
+
<div class="newGroupRow">
|
|
336
|
+
<button
|
|
337
|
+
class="newGroupButton"
|
|
338
|
+
type="button"
|
|
339
|
+
@click=${() => {
|
|
340
|
+
this.dispatchEvent(
|
|
341
|
+
new CustomEvent('harness-group-create-request', {
|
|
342
|
+
bubbles: true,
|
|
343
|
+
composed: true,
|
|
344
|
+
}),
|
|
345
|
+
);
|
|
346
|
+
}}
|
|
347
|
+
>
|
|
348
|
+
<dees-icon icon="lucide:FolderPlus" iconSize="13"></dees-icon>
|
|
349
|
+
New group
|
|
350
|
+
</button>
|
|
351
|
+
</div>
|
|
352
|
+
`
|
|
353
|
+
: ''}
|
|
354
|
+
`;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
private renderFlatList(): TemplateResult {
|
|
358
|
+
const sessions = this.filteredSessions;
|
|
359
|
+
if (!sessions.length) {
|
|
360
|
+
return html`<div class="emptyNote">
|
|
361
|
+
${this.sessions.length ? 'No conversations match this search.' : this.emptyText}
|
|
362
|
+
</div>`;
|
|
363
|
+
}
|
|
364
|
+
return html`${sessions.map((session) => this.renderItem(session, null, -1))}`;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
private renderGroupedList(): TemplateResult {
|
|
368
|
+
const byId = this.sessionsById;
|
|
369
|
+
const ungrouped = this.ungroupedSessions;
|
|
370
|
+
if (!this.sessions.length && !this.groups.length) {
|
|
371
|
+
return html`<div class="emptyNote">${this.emptyText}</div>`;
|
|
372
|
+
}
|
|
373
|
+
return html`
|
|
374
|
+
${this.groups.map((group) => {
|
|
375
|
+
const members = group.sessionIds
|
|
376
|
+
.map((id) => byId.get(id))
|
|
377
|
+
.filter((session): session is IHarnessSessionMeta => session !== undefined);
|
|
378
|
+
const collapsed = this.collapsedGroupIds.has(group.id);
|
|
379
|
+
const isHeaderDrop =
|
|
380
|
+
this.dropSlot?.groupId === group.id && this.dropSlot.index === members.length && collapsed;
|
|
381
|
+
return html`
|
|
382
|
+
<button
|
|
383
|
+
class="groupHeader ${collapsed ? 'collapsed' : ''} ${isHeaderDrop ? 'dropTarget' : ''}"
|
|
384
|
+
type="button"
|
|
385
|
+
@click=${() => this.toggleGroupCollapsed(group.id)}
|
|
386
|
+
@contextmenu=${(event: MouseEvent) => {
|
|
387
|
+
event.preventDefault();
|
|
388
|
+
const detail: IHarnessGroupContextDetail = {
|
|
389
|
+
group,
|
|
390
|
+
clientX: event.clientX,
|
|
391
|
+
clientY: event.clientY,
|
|
392
|
+
originalEvent: event,
|
|
393
|
+
};
|
|
394
|
+
this.dispatchEvent(
|
|
395
|
+
new CustomEvent('harness-group-context', { detail, bubbles: true, composed: true }),
|
|
396
|
+
);
|
|
397
|
+
}}
|
|
398
|
+
@dragover=${(event: DragEvent) => this.handleDragOverSlot(event, group.id, members.length)}
|
|
399
|
+
@drop=${(event: DragEvent) => this.handleDrop(event, group.id, members.length)}
|
|
400
|
+
>
|
|
401
|
+
<dees-icon class="chevron" icon="lucide:ChevronDown" iconSize="12"></dees-icon>
|
|
402
|
+
<span>${group.name}</span>
|
|
403
|
+
<span class="groupCount">${members.length}</span>
|
|
404
|
+
</button>
|
|
405
|
+
${collapsed
|
|
406
|
+
? ''
|
|
407
|
+
: this.renderSection(members, group.id)}
|
|
408
|
+
`;
|
|
409
|
+
})}
|
|
410
|
+
${this.groups.length && ungrouped.length
|
|
411
|
+
? html`
|
|
412
|
+
<button
|
|
413
|
+
class="groupHeader"
|
|
414
|
+
type="button"
|
|
415
|
+
tabindex="-1"
|
|
416
|
+
@dragover=${(event: DragEvent) => this.handleDragOverSlot(event, null, 0)}
|
|
417
|
+
@drop=${(event: DragEvent) => this.handleDrop(event, null, 0)}
|
|
418
|
+
>
|
|
419
|
+
<span>Ungrouped</span>
|
|
420
|
+
<span class="groupCount">${ungrouped.length}</span>
|
|
421
|
+
</button>
|
|
422
|
+
`
|
|
423
|
+
: ''}
|
|
424
|
+
${this.renderSection(ungrouped, null)}
|
|
425
|
+
`;
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
private renderSection(
|
|
429
|
+
sessions: IHarnessSessionMeta[],
|
|
430
|
+
groupId: string | null,
|
|
431
|
+
): TemplateResult {
|
|
432
|
+
const slot = this.dropSlot;
|
|
433
|
+
return html`
|
|
434
|
+
${sessions.map(
|
|
435
|
+
(session, index) => html`
|
|
436
|
+
${slot && slot.groupId === groupId && slot.index === index
|
|
437
|
+
? html`<div class="dropLine"></div>`
|
|
438
|
+
: ''}
|
|
439
|
+
${this.renderItem(session, groupId, index)}
|
|
440
|
+
`,
|
|
441
|
+
)}
|
|
442
|
+
${slot && slot.groupId === groupId && slot.index === sessions.length
|
|
443
|
+
? html`<div class="dropLine"></div>`
|
|
444
|
+
: ''}
|
|
194
445
|
`;
|
|
195
446
|
}
|
|
196
447
|
|
|
197
|
-
private renderItem(
|
|
448
|
+
private renderItem(
|
|
449
|
+
session: IHarnessSessionMeta,
|
|
450
|
+
groupId: string | null,
|
|
451
|
+
index: number,
|
|
452
|
+
): TemplateResult {
|
|
453
|
+
const draggable = this.enableGrouping && index >= 0;
|
|
198
454
|
return html`
|
|
199
455
|
<button
|
|
200
|
-
class="item ${session.id === this.selectedSessionId ? 'selected' : ''}
|
|
456
|
+
class="item ${session.id === this.selectedSessionId ? 'selected' : ''} ${
|
|
457
|
+
this.draggingSessionId === session.id ? 'dragging' : ''
|
|
458
|
+
}"
|
|
201
459
|
type="button"
|
|
460
|
+
draggable=${draggable ? 'true' : 'false'}
|
|
202
461
|
@click=${() => this.emit('harness-session-select', session)}
|
|
203
462
|
@dblclick=${() => this.emit('harness-session-open', session)}
|
|
204
463
|
@keydown=${(event: KeyboardEvent) => {
|
|
205
464
|
if (event.key === 'Enter') this.emit('harness-session-open', session);
|
|
206
465
|
}}
|
|
466
|
+
@contextmenu=${(event: MouseEvent) => {
|
|
467
|
+
event.preventDefault();
|
|
468
|
+
const detail: IHarnessSessionContextDetail = {
|
|
469
|
+
session,
|
|
470
|
+
clientX: event.clientX,
|
|
471
|
+
clientY: event.clientY,
|
|
472
|
+
originalEvent: event,
|
|
473
|
+
};
|
|
474
|
+
this.dispatchEvent(
|
|
475
|
+
new CustomEvent('harness-session-context', { detail, bubbles: true, composed: true }),
|
|
476
|
+
);
|
|
477
|
+
}}
|
|
478
|
+
@dragstart=${(event: DragEvent) => {
|
|
479
|
+
if (!draggable) return;
|
|
480
|
+
this.draggingSessionId = session.id;
|
|
481
|
+
event.dataTransfer?.setData('text/plain', session.id);
|
|
482
|
+
if (event.dataTransfer) event.dataTransfer.effectAllowed = 'move';
|
|
483
|
+
}}
|
|
484
|
+
@dragend=${() => {
|
|
485
|
+
this.draggingSessionId = '';
|
|
486
|
+
this.dropSlot = undefined;
|
|
487
|
+
}}
|
|
488
|
+
@dragover=${(event: DragEvent) => {
|
|
489
|
+
if (!this.draggingSessionId || index < 0) return;
|
|
490
|
+
event.preventDefault();
|
|
491
|
+
const rect = (event.currentTarget as HTMLElement).getBoundingClientRect();
|
|
492
|
+
const before = event.clientY < rect.top + rect.height / 2;
|
|
493
|
+
this.setDropSlot(groupId, before ? index : index + 1);
|
|
494
|
+
}}
|
|
495
|
+
@drop=${(event: DragEvent) => {
|
|
496
|
+
if (index < 0) return;
|
|
497
|
+
const rect = (event.currentTarget as HTMLElement).getBoundingClientRect();
|
|
498
|
+
const before = event.clientY < rect.top + rect.height / 2;
|
|
499
|
+
this.handleDrop(event, groupId, before ? index : index + 1);
|
|
500
|
+
}}
|
|
207
501
|
>
|
|
208
502
|
<span class="itemTitle">
|
|
209
503
|
<span class="titleText">${session.title || session.id}</span>
|
|
@@ -216,6 +510,45 @@ export class DeesHarnessSessionList extends DeesElement {
|
|
|
216
510
|
`;
|
|
217
511
|
}
|
|
218
512
|
|
|
513
|
+
private toggleGroupCollapsed(groupIdArg: string): void {
|
|
514
|
+
const next = new Set(this.collapsedGroupIds);
|
|
515
|
+
if (next.has(groupIdArg)) {
|
|
516
|
+
next.delete(groupIdArg);
|
|
517
|
+
} else {
|
|
518
|
+
next.add(groupIdArg);
|
|
519
|
+
}
|
|
520
|
+
this.collapsedGroupIds = next;
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
private setDropSlot(groupIdArg: string | null, indexArg: number): void {
|
|
524
|
+
const current = this.dropSlot;
|
|
525
|
+
if (current && current.groupId === groupIdArg && current.index === indexArg) return;
|
|
526
|
+
this.dropSlot = { groupId: groupIdArg, index: indexArg };
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
private handleDragOverSlot(event: DragEvent, groupIdArg: string | null, indexArg: number): void {
|
|
530
|
+
if (!this.draggingSessionId) return;
|
|
531
|
+
event.preventDefault();
|
|
532
|
+
this.setDropSlot(groupIdArg, indexArg);
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
private handleDrop(event: DragEvent, groupIdArg: string | null, indexArg: number): void {
|
|
536
|
+
event.preventDefault();
|
|
537
|
+
event.stopPropagation();
|
|
538
|
+
const sessionId = this.draggingSessionId || event.dataTransfer?.getData('text/plain') || '';
|
|
539
|
+
this.draggingSessionId = '';
|
|
540
|
+
this.dropSlot = undefined;
|
|
541
|
+
if (!sessionId) return;
|
|
542
|
+
const detail: IHarnessSessionMoveDetail = {
|
|
543
|
+
sessionId,
|
|
544
|
+
groupId: groupIdArg,
|
|
545
|
+
index: Math.max(0, indexArg),
|
|
546
|
+
};
|
|
547
|
+
this.dispatchEvent(
|
|
548
|
+
new CustomEvent('harness-session-move', { detail, bubbles: true, composed: true }),
|
|
549
|
+
);
|
|
550
|
+
}
|
|
551
|
+
|
|
219
552
|
private emit(name: string, session: IHarnessSessionMeta): void {
|
|
220
553
|
this.dispatchEvent(new CustomEvent(name, { detail: { session }, bubbles: true, composed: true }));
|
|
221
554
|
}
|
|
@@ -200,6 +200,40 @@ export interface IHarnessSessionMeta {
|
|
|
200
200
|
preview?: string;
|
|
201
201
|
}
|
|
202
202
|
|
|
203
|
+
/** Detail of `harness-session-context`, fired on right-click of a session item. */
|
|
204
|
+
export interface IHarnessSessionContextDetail {
|
|
205
|
+
session: IHarnessSessionMeta;
|
|
206
|
+
/** Viewport coordinates of the pointer, for positioning a context menu. */
|
|
207
|
+
clientX: number;
|
|
208
|
+
clientY: number;
|
|
209
|
+
/** The suppressed contextmenu event, e.g. for DeesContextmenu.openContextMenuWithOptions. */
|
|
210
|
+
originalEvent: MouseEvent;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/** A user-defined session group; ordering of `sessionIds` is the display order. */
|
|
214
|
+
export interface IHarnessSessionGroup {
|
|
215
|
+
id: string;
|
|
216
|
+
name: string;
|
|
217
|
+
sessionIds: string[];
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/** Detail of `harness-session-move`: a session was dropped into a group slot. */
|
|
221
|
+
export interface IHarnessSessionMoveDetail {
|
|
222
|
+
sessionId: string;
|
|
223
|
+
/** Target group, or null for the ungrouped section. */
|
|
224
|
+
groupId: string | null;
|
|
225
|
+
/** Insertion index within the target section's current display order. */
|
|
226
|
+
index: number;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/** Detail of `harness-group-context`, fired on right-click of a group header. */
|
|
230
|
+
export interface IHarnessGroupContextDetail {
|
|
231
|
+
group: IHarnessSessionGroup;
|
|
232
|
+
clientX: number;
|
|
233
|
+
clientY: number;
|
|
234
|
+
originalEvent: MouseEvent;
|
|
235
|
+
}
|
|
236
|
+
|
|
203
237
|
export type THarnessTodoStatus = 'pending' | 'in_progress' | 'completed';
|
|
204
238
|
|
|
205
239
|
export interface IHarnessTodoItem {
|