@design.estate/dees-catalog 10.0.0 → 11.1.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 +914 -780
- package/dist_bundle/bundle.js.map +1 -1
- package/dist_ts_web/00_commitinfo_data.js +1 -1
- package/dist_ts_web/elements/00group-harness/dees-harness-chat/dees-harness-chat.demo.js +24 -1
- package/dist_ts_web/elements/00group-harness/dees-harness-message-list/dees-harness-message-list.js +13 -1
- package/dist_ts_web/elements/00group-harness/dees-harness-session-list/dees-harness-session-list.d.ts +62 -48
- package/dist_ts_web/elements/00group-harness/dees-harness-session-list/dees-harness-session-list.demo.js +97 -57
- package/dist_ts_web/elements/00group-harness/dees-harness-session-list/dees-harness-session-list.js +541 -666
- package/dist_ts_web/elements/00group-harness/dees-harness-tool-card/dees-harness-tool-card.js +19 -1
- package/dist_ts_web/elements/00group-harness/dees-terminal-view/dees-terminal-view.d.ts +45 -6
- package/dist_ts_web/elements/00group-harness/dees-terminal-view/dees-terminal-view.js +184 -27
- package/dist_ts_web/elements/00group-harness/interfaces.d.ts +59 -20
- package/dist_ts_web/services/DeesServiceLibLoader.d.ts +7 -0
- package/dist_ts_web/services/DeesServiceLibLoader.js +45 -22
- package/package.json +1 -2
- package/readme.md +37 -17
- package/ts_web/00_commitinfo_data.ts +1 -1
- package/ts_web/elements/00group-harness/dees-harness-chat/dees-harness-chat.demo.ts +23 -0
- package/ts_web/elements/00group-harness/dees-harness-message-list/dees-harness-message-list.ts +12 -0
- package/ts_web/elements/00group-harness/dees-harness-session-list/dees-harness-session-list.demo.ts +107 -55
- package/ts_web/elements/00group-harness/dees-harness-session-list/dees-harness-session-list.ts +622 -756
- package/ts_web/elements/00group-harness/dees-harness-tool-card/dees-harness-tool-card.ts +18 -0
- package/ts_web/elements/00group-harness/dees-terminal-view/dees-terminal-view.ts +196 -24
- package/ts_web/elements/00group-harness/interfaces.ts +63 -19
- package/ts_web/services/DeesServiceLibLoader.ts +150 -102
- package/.smartconfig.json +0 -86
package/ts_web/elements/00group-harness/dees-harness-session-list/dees-harness-session-list.ts
CHANGED
|
@@ -13,15 +13,19 @@ import {
|
|
|
13
13
|
import { themeDefaultStyles } from '../../00theme.js';
|
|
14
14
|
import { harnessActivityStyles } from '../harness.styles.js';
|
|
15
15
|
import type {
|
|
16
|
+
IDropSlot,
|
|
16
17
|
IHarnessGroupContextDetail,
|
|
18
|
+
IHarnessItemMoveDetail,
|
|
17
19
|
IHarnessResourceAssociation,
|
|
18
20
|
IHarnessResourceContextDetail,
|
|
19
21
|
IHarnessResourceEventDetail,
|
|
20
22
|
IHarnessResourceMeta,
|
|
21
23
|
IHarnessSessionContextDetail,
|
|
24
|
+
IHarnessSessionEventDetail,
|
|
22
25
|
IHarnessSessionGroup,
|
|
26
|
+
IHarnessSessionListItemRef,
|
|
23
27
|
IHarnessSessionMeta,
|
|
24
|
-
|
|
28
|
+
THarnessItemMoveSource,
|
|
25
29
|
THarnessResourceAssociationInteractionSource,
|
|
26
30
|
THarnessResourceAssociationRequestDetail,
|
|
27
31
|
THarnessResourceContextSource,
|
|
@@ -39,17 +43,47 @@ declare global {
|
|
|
39
43
|
}
|
|
40
44
|
}
|
|
41
45
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
+
/** One ordered row. Sessions and resources are peers in a single order. */
|
|
47
|
+
type TListItem =
|
|
48
|
+
| {
|
|
49
|
+
kind: 'session';
|
|
50
|
+
key: string;
|
|
51
|
+
id: string;
|
|
52
|
+
title: string;
|
|
53
|
+
sortable: boolean;
|
|
54
|
+
timestamp: number;
|
|
55
|
+
session: IHarnessSessionMeta;
|
|
56
|
+
}
|
|
57
|
+
| {
|
|
58
|
+
kind: 'resource';
|
|
59
|
+
key: string;
|
|
60
|
+
id: string;
|
|
61
|
+
title: string;
|
|
62
|
+
sortable: boolean;
|
|
63
|
+
timestamp: number;
|
|
64
|
+
resource: IHarnessResourceMeta;
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
const itemKey = (refArg: IHarnessSessionListItemRef): string => `${refArg.kind}:${refArg.id}`;
|
|
68
|
+
|
|
69
|
+
const itemRef = (itemArg: IHarnessSessionListItemRef): IHarnessSessionListItemRef => ({
|
|
70
|
+
kind: itemArg.kind,
|
|
71
|
+
id: itemArg.id,
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
const sameItem = (
|
|
75
|
+
leftArg: IHarnessSessionListItemRef | undefined | null,
|
|
76
|
+
rightArg: IHarnessSessionListItemRef | undefined | null,
|
|
77
|
+
): boolean => !!leftArg
|
|
78
|
+
&& !!rightArg
|
|
79
|
+
&& leftArg.kind === rightArg.kind
|
|
80
|
+
&& leftArg.id === rightArg.id;
|
|
46
81
|
|
|
47
82
|
interface IPointerDragGesture {
|
|
48
|
-
type: 'session';
|
|
49
83
|
pointerId: number;
|
|
50
84
|
handle: HTMLElement;
|
|
51
85
|
source: HTMLElement;
|
|
52
|
-
|
|
86
|
+
item: IHarnessSessionListItemRef;
|
|
53
87
|
sourceGroupId: string | null;
|
|
54
88
|
sourceLogicalIndex: number;
|
|
55
89
|
startClientX: number;
|
|
@@ -59,38 +93,21 @@ interface IPointerDragGesture {
|
|
|
59
93
|
phase: 'pending' | 'active';
|
|
60
94
|
}
|
|
61
95
|
|
|
62
|
-
interface IResourcePointerDragGesture {
|
|
63
|
-
type: 'resource';
|
|
64
|
-
pointerId: number;
|
|
65
|
-
handle: HTMLElement;
|
|
66
|
-
source: HTMLElement;
|
|
67
|
-
resourceId: string;
|
|
68
|
-
fromSessionId: string | null;
|
|
69
|
-
eligibleSessionIds: ReadonlySet<string>;
|
|
70
|
-
startClientX: number;
|
|
71
|
-
startClientY: number;
|
|
72
|
-
grabOffsetX: number;
|
|
73
|
-
grabOffsetY: number;
|
|
74
|
-
phase: 'pending' | 'active';
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
type TPointerDragGesture = IPointerDragGesture | IResourcePointerDragGesture;
|
|
78
|
-
|
|
79
96
|
interface IPendingResourceAssociationRequest {
|
|
80
97
|
resourceId: string;
|
|
81
98
|
resourceTitle: string;
|
|
82
|
-
|
|
83
|
-
|
|
99
|
+
fromTarget: IHarnessSessionListItemRef | null;
|
|
100
|
+
toTarget: IHarnessSessionListItemRef | null;
|
|
84
101
|
intent: THarnessResourceAssociationRequestDetail['intent'];
|
|
85
102
|
}
|
|
86
103
|
|
|
87
104
|
interface IKeyboardMoveFocus {
|
|
88
|
-
|
|
105
|
+
item: IHarnessSessionListItemRef;
|
|
89
106
|
groupId: string | null;
|
|
90
107
|
}
|
|
91
108
|
|
|
92
109
|
type TSectionRenderEntry =
|
|
93
|
-
| { type: '
|
|
110
|
+
| { type: 'item'; key: string; item: TListItem; logicalIndex: number }
|
|
94
111
|
| { type: 'placeholder'; key: string };
|
|
95
112
|
|
|
96
113
|
/**
|
|
@@ -99,16 +116,22 @@ type TSectionRenderEntry =
|
|
|
99
116
|
* double-click or Enter, and `harness-session-context` with viewport
|
|
100
117
|
* coordinates on right-click, Actions, or ContextMenu/Shift+F10.
|
|
101
118
|
*
|
|
102
|
-
*
|
|
103
|
-
* be dragged
|
|
104
|
-
*
|
|
105
|
-
*
|
|
106
|
-
* `harness-
|
|
107
|
-
*
|
|
108
|
-
*
|
|
109
|
-
*
|
|
110
|
-
*
|
|
111
|
-
*
|
|
119
|
+
* Sessions and host resources are peers in ONE ordered list: a resource row
|
|
120
|
+
* can be dragged anywhere a session can, including between sessions and into
|
|
121
|
+
* or out of groups. Attaching a resource to a session is a separate explicit
|
|
122
|
+
* host action (`requestResourceAssociation()` /
|
|
123
|
+
* `harness-resource-association-request`) that never moves a row; the
|
|
124
|
+
* association only renders as a marker on the resource row.
|
|
125
|
+
*
|
|
126
|
+
* The component never mutates its inputs: reordering emits `harness-item-move`,
|
|
127
|
+
* the "new group" button emits `harness-group-create-request`, and
|
|
128
|
+
* right-clicking a group header emits `harness-group-context` — the host
|
|
129
|
+
* persists changes and passes fresh `groups` / `ungroupedItemIds` back in.
|
|
130
|
+
* Items in no group render in the trailing ungrouped section with pinned
|
|
131
|
+
* entries first, then explicit order, then new entries by recency. With
|
|
132
|
+
* `enableGrouping` the list additionally renders group chrome. While a search
|
|
133
|
+
* query is active the list is one flat, non-sortable, recency-ordered section
|
|
134
|
+
* in which sessions and resources match as peers.
|
|
112
135
|
*/
|
|
113
136
|
@customElement('dees-harness-session-list')
|
|
114
137
|
export class DeesHarnessSessionList extends DeesElement {
|
|
@@ -133,9 +156,9 @@ export class DeesHarnessSessionList extends DeesElement {
|
|
|
133
156
|
@property({ type: Boolean })
|
|
134
157
|
accessor enableGrouping: boolean = false;
|
|
135
158
|
|
|
136
|
-
/** Explicit order for sortable
|
|
159
|
+
/** Explicit order for sortable items of both kinds outside user-defined groups. */
|
|
137
160
|
@property({ attribute: false })
|
|
138
|
-
accessor
|
|
161
|
+
accessor ungroupedItemIds: IHarnessSessionListItemRef[] = [];
|
|
139
162
|
|
|
140
163
|
@property({ attribute: false })
|
|
141
164
|
accessor resources: IHarnessResourceMeta[] = [];
|
|
@@ -146,21 +169,18 @@ export class DeesHarnessSessionList extends DeesElement {
|
|
|
146
169
|
@property()
|
|
147
170
|
accessor selectedResourceId = '';
|
|
148
171
|
|
|
149
|
-
/**
|
|
172
|
+
/**
|
|
173
|
+
* Serializable attach/reassign targets for host menus. A missing entry leaves
|
|
174
|
+
* the resource unrestricted; an empty array means no eligible target.
|
|
175
|
+
*/
|
|
150
176
|
@property({ attribute: false })
|
|
151
|
-
accessor
|
|
177
|
+
accessor eligibleTargetsByResourceId: Record<string, IHarnessSessionListItemRef[]> = {};
|
|
152
178
|
|
|
153
179
|
@state()
|
|
154
180
|
accessor search: string = '';
|
|
155
181
|
|
|
156
182
|
@state()
|
|
157
|
-
accessor
|
|
158
|
-
|
|
159
|
-
@state()
|
|
160
|
-
accessor draggingResourceId = '';
|
|
161
|
-
|
|
162
|
-
@state()
|
|
163
|
-
accessor resourceDropTargetSessionId = '';
|
|
183
|
+
accessor draggingItem: IHarnessSessionListItemRef | undefined = undefined;
|
|
164
184
|
|
|
165
185
|
@state()
|
|
166
186
|
accessor dropSlot: IDropSlot | undefined = undefined;
|
|
@@ -188,7 +208,7 @@ export class DeesHarnessSessionList extends DeesElement {
|
|
|
188
208
|
|
|
189
209
|
private reflowGeneration = 0;
|
|
190
210
|
private readonly reflowAnimations = new Set<Animation>();
|
|
191
|
-
private pointerDragGesture:
|
|
211
|
+
private pointerDragGesture: IPointerDragGesture | undefined;
|
|
192
212
|
private pendingResourceAssociationRequest: IPendingResourceAssociationRequest | undefined;
|
|
193
213
|
private resourceContextFocusTarget: HTMLElement | undefined;
|
|
194
214
|
private resourceContextFocusClass = '';
|
|
@@ -367,7 +387,9 @@ export class DeesHarnessSessionList extends DeesElement {
|
|
|
367
387
|
border-color var(--dees-transition-fast) var(--dees-ease-standard);
|
|
368
388
|
}
|
|
369
389
|
|
|
370
|
-
|
|
390
|
+
/* One drag preview for both item kinds. */
|
|
391
|
+
.item.dragging,
|
|
392
|
+
.resourceRow.dragging {
|
|
371
393
|
position: fixed;
|
|
372
394
|
inset: 0 auto auto 0;
|
|
373
395
|
width: var(--drag-preview-width);
|
|
@@ -410,12 +432,6 @@ export class DeesHarnessSessionList extends DeesElement {
|
|
|
410
432
|
box-shadow: inset 3px 0 var(--card-accent);
|
|
411
433
|
}
|
|
412
434
|
|
|
413
|
-
.item.resourceDropTarget {
|
|
414
|
-
outline: 2px solid var(--dees-color-accent-primary);
|
|
415
|
-
outline-offset: 2px;
|
|
416
|
-
box-shadow: 0 0 0 5px var(--dees-color-focus-ring), var(--dees-shadow-lg);
|
|
417
|
-
}
|
|
418
|
-
|
|
419
435
|
.itemHeader {
|
|
420
436
|
display: grid;
|
|
421
437
|
grid-template-columns: minmax(0, 1fr) 30px 30px;
|
|
@@ -512,7 +528,8 @@ export class DeesHarnessSessionList extends DeesElement {
|
|
|
512
528
|
line-height: 14px;
|
|
513
529
|
}
|
|
514
530
|
|
|
515
|
-
.harnessName
|
|
531
|
+
.harnessName,
|
|
532
|
+
.projectName {
|
|
516
533
|
min-width: 0;
|
|
517
534
|
overflow: hidden;
|
|
518
535
|
text-overflow: ellipsis;
|
|
@@ -592,75 +609,31 @@ export class DeesHarnessSessionList extends DeesElement {
|
|
|
592
609
|
font-size: var(--dees-font-caption2-size, 10px);
|
|
593
610
|
}
|
|
594
611
|
|
|
595
|
-
.
|
|
596
|
-
margin: 0 10px 10px;
|
|
597
|
-
padding: 6px 9px;
|
|
598
|
-
border-radius: var(--dees-radius-md);
|
|
599
|
-
background: color-mix(in srgb, var(--dees-color-accent-primary) 14%, transparent);
|
|
600
|
-
color: var(--dees-color-accent-primary);
|
|
601
|
-
font-size: var(--dees-font-caption1-size, 11px);
|
|
602
|
-
font-weight: 680;
|
|
603
|
-
text-align: center;
|
|
604
|
-
}
|
|
605
|
-
|
|
606
|
-
.resourceChildren {
|
|
607
|
-
display: grid;
|
|
608
|
-
gap: 4px;
|
|
609
|
-
margin: 0 8px 8px 24px;
|
|
610
|
-
padding: 0;
|
|
611
|
-
list-style: none;
|
|
612
|
-
}
|
|
613
|
-
|
|
614
|
-
.resourceSection {
|
|
615
|
-
margin-top: var(--dees-spacing-md);
|
|
616
|
-
}
|
|
617
|
-
|
|
618
|
-
.resourceSectionTitle {
|
|
619
|
-
margin: 0 0 7px;
|
|
620
|
-
padding: var(--dees-spacing-xs) 6px;
|
|
621
|
-
color: var(--dees-color-text-secondary);
|
|
622
|
-
font-size: var(--dees-font-caption1-size, 11px);
|
|
623
|
-
font-weight: 600;
|
|
624
|
-
}
|
|
625
|
-
|
|
626
|
-
.resourceList {
|
|
627
|
-
display: grid;
|
|
628
|
-
gap: var(--dees-spacing-sm);
|
|
629
|
-
margin: 0;
|
|
630
|
-
padding: 0;
|
|
631
|
-
list-style: none;
|
|
632
|
-
}
|
|
633
|
-
|
|
612
|
+
/* Resource rows are peers of session cards in the same ordered section. */
|
|
634
613
|
.resourceRow {
|
|
614
|
+
position: relative;
|
|
635
615
|
display: grid;
|
|
636
|
-
grid-template-columns:
|
|
616
|
+
grid-template-columns: minmax(0, 1fr) 38px;
|
|
637
617
|
min-width: 0;
|
|
638
618
|
overflow: hidden;
|
|
639
619
|
border: 1px solid var(--dees-color-border-subtle);
|
|
640
620
|
border-radius: var(--dees-radius-lg);
|
|
621
|
+
corner-shape: var(--dees-corner-shape);
|
|
641
622
|
background: color-mix(in srgb, var(--dees-color-bg-secondary) 76%, transparent);
|
|
642
623
|
color: var(--dees-color-text-primary);
|
|
643
624
|
box-shadow: var(--dees-shadow-xs);
|
|
644
625
|
box-sizing: border-box;
|
|
645
626
|
}
|
|
646
627
|
|
|
628
|
+
.resourceRow.draggable {
|
|
629
|
+
grid-template-columns: 26px minmax(0, 1fr) 38px;
|
|
630
|
+
}
|
|
631
|
+
|
|
647
632
|
.resourceRow.selected {
|
|
648
633
|
border-color: var(--dees-color-accent-primary);
|
|
649
634
|
background: color-mix(in srgb, var(--dees-color-accent-primary) 10%, var(--dees-color-bg-secondary));
|
|
650
635
|
}
|
|
651
636
|
|
|
652
|
-
.resourceRow.resourceDragging {
|
|
653
|
-
position: fixed;
|
|
654
|
-
inset: 0 auto auto 0;
|
|
655
|
-
width: var(--resource-drag-preview-width);
|
|
656
|
-
max-width: none;
|
|
657
|
-
margin: 0;
|
|
658
|
-
opacity: 0.96;
|
|
659
|
-
pointer-events: none;
|
|
660
|
-
transform: translate3d(var(--resource-drag-preview-x), var(--resource-drag-preview-y), 0);
|
|
661
|
-
box-shadow: var(--dees-shadow-xl);
|
|
662
|
-
}
|
|
663
|
-
|
|
664
637
|
.resourceDragHandle,
|
|
665
638
|
.resourcePrimary,
|
|
666
639
|
.resourceActions {
|
|
@@ -672,22 +645,7 @@ export class DeesHarnessSessionList extends DeesElement {
|
|
|
672
645
|
}
|
|
673
646
|
|
|
674
647
|
.resourceDragHandle {
|
|
675
|
-
display: grid;
|
|
676
|
-
place-items: center;
|
|
677
648
|
min-width: 26px;
|
|
678
|
-
padding: 0;
|
|
679
|
-
color: var(--dees-color-text-muted);
|
|
680
|
-
cursor: grab;
|
|
681
|
-
touch-action: none;
|
|
682
|
-
}
|
|
683
|
-
|
|
684
|
-
.resourceDragHandle:disabled {
|
|
685
|
-
opacity: 0.35;
|
|
686
|
-
cursor: default;
|
|
687
|
-
}
|
|
688
|
-
|
|
689
|
-
.resourceDragHandle:not(:disabled):active {
|
|
690
|
-
cursor: grabbing;
|
|
691
649
|
}
|
|
692
650
|
|
|
693
651
|
.resourcePrimary {
|
|
@@ -741,6 +699,29 @@ export class DeesHarnessSessionList extends DeesElement {
|
|
|
741
699
|
white-space: nowrap;
|
|
742
700
|
}
|
|
743
701
|
|
|
702
|
+
/* Decorative marker for the controlled association; never interactive. */
|
|
703
|
+
.resourceAttachment {
|
|
704
|
+
display: flex;
|
|
705
|
+
min-width: 0;
|
|
706
|
+
align-items: center;
|
|
707
|
+
gap: 4px;
|
|
708
|
+
margin-top: 2px;
|
|
709
|
+
color: var(--dees-color-link);
|
|
710
|
+
font-size: var(--dees-font-caption2-size, 10px);
|
|
711
|
+
font-weight: 600;
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
.resourceAttachment dees-icon {
|
|
715
|
+
flex: 0 0 auto;
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
.resourceAttachmentName {
|
|
719
|
+
min-width: 0;
|
|
720
|
+
overflow: hidden;
|
|
721
|
+
text-overflow: ellipsis;
|
|
722
|
+
white-space: nowrap;
|
|
723
|
+
}
|
|
724
|
+
|
|
744
725
|
.resourceActions {
|
|
745
726
|
display: grid;
|
|
746
727
|
place-items: center;
|
|
@@ -778,7 +759,7 @@ export class DeesHarnessSessionList extends DeesElement {
|
|
|
778
759
|
.emptyDropZone {
|
|
779
760
|
display: grid;
|
|
780
761
|
place-items: center;
|
|
781
|
-
|
|
762
|
+
overflow: hidden;
|
|
782
763
|
border: 1px dashed color-mix(in srgb, var(--dees-color-accent-primary) 68%, transparent);
|
|
783
764
|
border-radius: var(--dees-radius-xl);
|
|
784
765
|
background: color-mix(in srgb, var(--dees-color-accent-primary) 8%, transparent);
|
|
@@ -786,9 +767,14 @@ export class DeesHarnessSessionList extends DeesElement {
|
|
|
786
767
|
box-sizing: border-box;
|
|
787
768
|
}
|
|
788
769
|
|
|
770
|
+
/* The placeholder matches the dragged row exactly; only the empty zone has a floor. */
|
|
771
|
+
.emptyDropZone {
|
|
772
|
+
min-height: 46px;
|
|
773
|
+
}
|
|
774
|
+
|
|
789
775
|
.dropPlaceholder::after,
|
|
790
776
|
.emptyDropZone::after {
|
|
791
|
-
content: 'Drop
|
|
777
|
+
content: 'Drop here';
|
|
792
778
|
font-size: var(--dees-font-caption2-size, 10px);
|
|
793
779
|
font-weight: 650;
|
|
794
780
|
letter-spacing: 0.02em;
|
|
@@ -931,6 +917,10 @@ export class DeesHarnessSessionList extends DeesElement {
|
|
|
931
917
|
.expandToggle { margin: 0 3px 0 0; }
|
|
932
918
|
|
|
933
919
|
.resourceRow {
|
|
920
|
+
grid-template-columns: minmax(0, 1fr) 44px;
|
|
921
|
+
}
|
|
922
|
+
|
|
923
|
+
.resourceRow.draggable {
|
|
934
924
|
grid-template-columns: 44px minmax(0, 1fr) 44px;
|
|
935
925
|
}
|
|
936
926
|
|
|
@@ -947,10 +937,6 @@ export class DeesHarnessSessionList extends DeesElement {
|
|
|
947
937
|
}
|
|
948
938
|
|
|
949
939
|
@media (max-width: 280px) {
|
|
950
|
-
.resourceChildren {
|
|
951
|
-
margin-left: 8px;
|
|
952
|
-
}
|
|
953
|
-
|
|
954
940
|
.resourcePreview {
|
|
955
941
|
display: none;
|
|
956
942
|
}
|
|
@@ -958,10 +944,6 @@ export class DeesHarnessSessionList extends DeesElement {
|
|
|
958
944
|
`,
|
|
959
945
|
];
|
|
960
946
|
|
|
961
|
-
private get sessionsById(): Map<string, IHarnessSessionMeta> {
|
|
962
|
-
return new Map(this.sessions.map((session) => [session.id, session]));
|
|
963
|
-
}
|
|
964
|
-
|
|
965
947
|
private get resourcesById(): Map<string, IHarnessResourceMeta> {
|
|
966
948
|
const result = new Map<string, IHarnessResourceMeta>();
|
|
967
949
|
for (const resource of this.resources) {
|
|
@@ -971,18 +953,23 @@ export class DeesHarnessSessionList extends DeesElement {
|
|
|
971
953
|
return result;
|
|
972
954
|
}
|
|
973
955
|
|
|
974
|
-
/**
|
|
975
|
-
|
|
956
|
+
/**
|
|
957
|
+
* First valid record wins; unknown records, self-references and duplicates
|
|
958
|
+
* remain untouched but are not projected. Keyed by resource id.
|
|
959
|
+
*/
|
|
960
|
+
private get acceptedResourceAssociations(): Map<string, IHarnessSessionListItemRef> {
|
|
976
961
|
const resourcesById = this.resourcesById;
|
|
977
|
-
const
|
|
978
|
-
const result = new Map<string,
|
|
962
|
+
const itemsByKey = this.itemsByKey;
|
|
963
|
+
const result = new Map<string, IHarnessSessionListItemRef>();
|
|
979
964
|
for (const association of this.resourceAssociations) {
|
|
980
965
|
if (
|
|
981
|
-
!
|
|
982
|
-
|| !
|
|
966
|
+
!association.target
|
|
967
|
+
|| !resourcesById.has(association.resourceId)
|
|
968
|
+
|| !itemsByKey.has(itemKey(association.target))
|
|
969
|
+
|| sameItem(association.target, { kind: 'resource', id: association.resourceId })
|
|
983
970
|
|| result.has(association.resourceId)
|
|
984
971
|
) continue;
|
|
985
|
-
result.set(association.resourceId, association.
|
|
972
|
+
result.set(association.resourceId, itemRef(association.target));
|
|
986
973
|
}
|
|
987
974
|
return result;
|
|
988
975
|
}
|
|
@@ -991,8 +978,95 @@ export class DeesHarnessSessionList extends DeesElement {
|
|
|
991
978
|
return [...this.resourcesById.values()];
|
|
992
979
|
}
|
|
993
980
|
|
|
981
|
+
/** Sessions and resources as one pool of orderable items, before grouping and search. */
|
|
982
|
+
private get acceptedItems(): TListItem[] {
|
|
983
|
+
const result: TListItem[] = [];
|
|
984
|
+
const seenSessionIds = new Set<string>();
|
|
985
|
+
for (const session of this.sessions) {
|
|
986
|
+
if (!session.id || seenSessionIds.has(session.id)) continue;
|
|
987
|
+
seenSessionIds.add(session.id);
|
|
988
|
+
result.push({
|
|
989
|
+
kind: 'session',
|
|
990
|
+
key: `session:${session.id}`,
|
|
991
|
+
id: session.id,
|
|
992
|
+
title: session.title || session.id,
|
|
993
|
+
sortable: session.draggable !== false,
|
|
994
|
+
timestamp: session.updatedAt ?? session.createdAt ?? 0,
|
|
995
|
+
session,
|
|
996
|
+
});
|
|
997
|
+
}
|
|
998
|
+
for (const resource of this.resourcesById.values()) {
|
|
999
|
+
result.push({
|
|
1000
|
+
kind: 'resource',
|
|
1001
|
+
key: `resource:${resource.id}`,
|
|
1002
|
+
id: resource.id,
|
|
1003
|
+
title: resource.title || resource.id,
|
|
1004
|
+
sortable: resource.draggable !== false,
|
|
1005
|
+
timestamp: resource.updatedAt ?? resource.createdAt ?? 0,
|
|
1006
|
+
resource,
|
|
1007
|
+
});
|
|
1008
|
+
}
|
|
1009
|
+
return result;
|
|
1010
|
+
}
|
|
1011
|
+
|
|
1012
|
+
private get itemsByKey(): Map<string, TListItem> {
|
|
1013
|
+
return new Map(this.acceptedItems.map((item) => [item.key, item]));
|
|
1014
|
+
}
|
|
1015
|
+
|
|
1016
|
+
/** Groups only organize the list while grouping is enabled. */
|
|
1017
|
+
private get effectiveGroups(): IHarnessSessionGroup[] {
|
|
1018
|
+
return this.enableGrouping ? this.groups : [];
|
|
1019
|
+
}
|
|
1020
|
+
|
|
1021
|
+
private get groupedItemKeys(): Set<string> {
|
|
1022
|
+
const keys = new Set<string>();
|
|
1023
|
+
for (const group of this.effectiveGroups) {
|
|
1024
|
+
for (const ref of group.itemIds) keys.add(itemKey(ref));
|
|
1025
|
+
}
|
|
1026
|
+
return keys;
|
|
1027
|
+
}
|
|
1028
|
+
|
|
1029
|
+
private itemsForGroup(groupIdArg: string | null): TListItem[] {
|
|
1030
|
+
if (groupIdArg === null) return this.ungroupedItems;
|
|
1031
|
+
const group = this.effectiveGroups.find((candidate) => candidate.id === groupIdArg);
|
|
1032
|
+
if (!group) return [];
|
|
1033
|
+
const byKey = this.itemsByKey;
|
|
1034
|
+
const seen = new Set<string>();
|
|
1035
|
+
const result: TListItem[] = [];
|
|
1036
|
+
for (const ref of group.itemIds) {
|
|
1037
|
+
const key = itemKey(ref);
|
|
1038
|
+
const item = byKey.get(key);
|
|
1039
|
+
if (!item || seen.has(key)) continue;
|
|
1040
|
+
seen.add(key);
|
|
1041
|
+
result.push(item);
|
|
1042
|
+
}
|
|
1043
|
+
return result;
|
|
1044
|
+
}
|
|
1045
|
+
|
|
1046
|
+
/** Pinned entries first, then explicitly ordered items, then unknown items by recency. */
|
|
1047
|
+
private get ungroupedItems(): TListItem[] {
|
|
1048
|
+
const grouped = this.groupedItemKeys;
|
|
1049
|
+
const available = this.acceptedItems.filter((item) => !grouped.has(item.key));
|
|
1050
|
+
const pinned = available.filter((item) => !item.sortable);
|
|
1051
|
+
const sortableByKey = new Map(
|
|
1052
|
+
available.filter((item) => item.sortable).map((item) => [item.key, item]),
|
|
1053
|
+
);
|
|
1054
|
+
const ordered: TListItem[] = [];
|
|
1055
|
+
for (const ref of this.ungroupedItemIds) {
|
|
1056
|
+
const item = sortableByKey.get(itemKey(ref));
|
|
1057
|
+
if (!item) continue;
|
|
1058
|
+
ordered.push(item);
|
|
1059
|
+
sortableByKey.delete(item.key);
|
|
1060
|
+
}
|
|
1061
|
+
const unknown = [...sortableByKey.values()].sort(
|
|
1062
|
+
(left, right) => right.timestamp - left.timestamp,
|
|
1063
|
+
);
|
|
1064
|
+
return [...pinned, ...ordered, ...unknown];
|
|
1065
|
+
}
|
|
1066
|
+
|
|
994
1067
|
private resourceMatchesSearch(resourceArg: IHarnessResourceMeta, queryArg: string): boolean {
|
|
995
|
-
return [resourceArg.id, resourceArg.kind, resourceArg.title, resourceArg.preview
|
|
1068
|
+
return [resourceArg.id, resourceArg.kind, resourceArg.title, resourceArg.preview,
|
|
1069
|
+
resourceArg.projectLabel]
|
|
996
1070
|
.filter(Boolean)
|
|
997
1071
|
.join(' ')
|
|
998
1072
|
.toLowerCase()
|
|
@@ -1001,78 +1075,53 @@ export class DeesHarnessSessionList extends DeesElement {
|
|
|
1001
1075
|
|
|
1002
1076
|
private sessionMatchesSearch(sessionArg: IHarnessSessionMeta, queryArg: string): boolean {
|
|
1003
1077
|
return [sessionArg.title, sessionArg.preview, sessionArg.id, sessionArg.harness?.id,
|
|
1004
|
-
sessionArg.harness?.name,
|
|
1078
|
+
sessionArg.harness?.name, sessionArg.projectLabel,
|
|
1079
|
+
this.sessionStatusLabel(sessionArg), this.sessionStateLabel(sessionArg.state ?? 'normal')]
|
|
1005
1080
|
.filter(Boolean)
|
|
1006
1081
|
.join(' ')
|
|
1007
1082
|
.toLowerCase()
|
|
1008
1083
|
.includes(queryArg);
|
|
1009
1084
|
}
|
|
1010
1085
|
|
|
1011
|
-
private
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
);
|
|
1016
|
-
const query = this.search.trim().toLowerCase();
|
|
1017
|
-
if (!query || this.sessionMatchesSearch(sessionArg, query)) return associated;
|
|
1018
|
-
return associated.filter((resource) => this.resourceMatchesSearch(resource, query));
|
|
1086
|
+
private itemMatchesSearch(itemArg: TListItem, queryArg: string): boolean {
|
|
1087
|
+
return itemArg.kind === 'session'
|
|
1088
|
+
? this.sessionMatchesSearch(itemArg.session, queryArg)
|
|
1089
|
+
: this.resourceMatchesSearch(itemArg.resource, queryArg);
|
|
1019
1090
|
}
|
|
1020
1091
|
|
|
1021
|
-
|
|
1022
|
-
|
|
1092
|
+
/** Search flattens the list: sessions and resources match as peers, newest first. */
|
|
1093
|
+
private get searchItems(): TListItem[] {
|
|
1023
1094
|
const query = this.search.trim().toLowerCase();
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
)
|
|
1095
|
+
const sorted = [...this.acceptedItems].sort(
|
|
1096
|
+
(left, right) => right.timestamp - left.timestamp,
|
|
1097
|
+
);
|
|
1098
|
+
if (!query) return sorted;
|
|
1099
|
+
return sorted.filter((item) => this.itemMatchesSearch(item, query));
|
|
1028
1100
|
}
|
|
1029
1101
|
|
|
1030
|
-
private get
|
|
1031
|
-
|
|
1032
|
-
for (const group of this.groups) {
|
|
1033
|
-
for (const id of group.sessionIds) ids.add(id);
|
|
1034
|
-
}
|
|
1035
|
-
return ids;
|
|
1102
|
+
private get searchActive(): boolean {
|
|
1103
|
+
return this.search.trim().length > 0;
|
|
1036
1104
|
}
|
|
1037
1105
|
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
const sortableById = new Map(
|
|
1044
|
-
available.filter((session) => session.draggable !== false).map((session) => [session.id, session]),
|
|
1045
|
-
);
|
|
1046
|
-
const ordered: IHarnessSessionMeta[] = [];
|
|
1047
|
-
for (const sessionId of this.ungroupedSessionIds) {
|
|
1048
|
-
const session = sortableById.get(sessionId);
|
|
1049
|
-
if (!session) continue;
|
|
1050
|
-
ordered.push(session);
|
|
1051
|
-
sortableById.delete(sessionId);
|
|
1052
|
-
}
|
|
1053
|
-
const newSessions = [...sortableById.values()].sort(
|
|
1054
|
-
(left, right) =>
|
|
1055
|
-
(right.updatedAt ?? right.createdAt ?? 0) - (left.updatedAt ?? left.createdAt ?? 0),
|
|
1056
|
-
);
|
|
1057
|
-
return [...pinned, ...ordered, ...newSessions];
|
|
1106
|
+
private sortableItems(itemsArg: TListItem[], excludeDraggingArg = true): TListItem[] {
|
|
1107
|
+
return itemsArg.filter((item) => (
|
|
1108
|
+
item.sortable
|
|
1109
|
+
&& (!excludeDraggingArg || !sameItem(this.draggingItem, item))
|
|
1110
|
+
));
|
|
1058
1111
|
}
|
|
1059
1112
|
|
|
1060
|
-
private
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
);
|
|
1072
|
-
if (!query) return sorted;
|
|
1073
|
-
return sorted.filter(
|
|
1074
|
-
(session) => this.sessionMatchesSearch(session, query) || matchingAttachedSessionIds.has(session.id),
|
|
1075
|
-
);
|
|
1113
|
+
private groupIdForItem(refArg: IHarnessSessionListItemRef): string | null {
|
|
1114
|
+
return this.effectiveGroups.find(
|
|
1115
|
+
(group) => group.itemIds.some((candidate) => sameItem(candidate, refArg)),
|
|
1116
|
+
)?.id ?? null;
|
|
1117
|
+
}
|
|
1118
|
+
|
|
1119
|
+
private rowElementFor(refArg: IHarnessSessionListItemRef): HTMLElement | undefined {
|
|
1120
|
+
return Array.from(
|
|
1121
|
+
this.shadowRoot?.querySelectorAll<HTMLElement>('[data-item-kind][data-item-id]') ?? [],
|
|
1122
|
+
).find((candidate) => (
|
|
1123
|
+
candidate.dataset.itemKind === refArg.kind && candidate.dataset.itemId === refArg.id
|
|
1124
|
+
));
|
|
1076
1125
|
}
|
|
1077
1126
|
|
|
1078
1127
|
public focusSearch(): void {
|
|
@@ -1081,46 +1130,51 @@ export class DeesHarnessSessionList extends DeesElement {
|
|
|
1081
1130
|
|
|
1082
1131
|
public requestResourceAssociation(
|
|
1083
1132
|
resourceIdArg: string,
|
|
1084
|
-
|
|
1133
|
+
toTargetArg: IHarnessSessionListItemRef | null,
|
|
1085
1134
|
sourceArg: THarnessResourceAssociationInteractionSource = 'action',
|
|
1086
1135
|
): boolean {
|
|
1087
1136
|
const resource = this.resourcesById.get(resourceIdArg);
|
|
1088
1137
|
if (!resource) return false;
|
|
1089
|
-
const
|
|
1090
|
-
if (
|
|
1138
|
+
const fromTarget = this.acceptedResourceAssociations.get(resourceIdArg) ?? null;
|
|
1139
|
+
if (sameItem(fromTarget, toTargetArg)) return false;
|
|
1091
1140
|
if (
|
|
1092
|
-
|
|
1093
|
-
&& (
|
|
1141
|
+
toTargetArg !== null
|
|
1142
|
+
&& (
|
|
1143
|
+
!this.itemsByKey.has(itemKey(toTargetArg))
|
|
1144
|
+
|| sameItem(toTargetArg, { kind: 'resource', id: resourceIdArg })
|
|
1145
|
+
|| !this.isResourceTargetEligible(resourceIdArg, toTargetArg)
|
|
1146
|
+
)
|
|
1094
1147
|
) return false;
|
|
1095
|
-
if (
|
|
1096
|
-
const
|
|
1148
|
+
if (toTargetArg === null && fromTarget === null) return false;
|
|
1149
|
+
const toTarget = toTargetArg === null ? null : itemRef(toTargetArg);
|
|
1150
|
+
const detail: THarnessResourceAssociationRequestDetail = fromTarget === null
|
|
1097
1151
|
? {
|
|
1098
1152
|
intent: 'attach',
|
|
1099
1153
|
resource,
|
|
1100
|
-
|
|
1101
|
-
|
|
1154
|
+
fromTarget: null,
|
|
1155
|
+
toTarget: toTarget!,
|
|
1102
1156
|
source: sourceArg,
|
|
1103
1157
|
}
|
|
1104
|
-
:
|
|
1158
|
+
: toTarget === null
|
|
1105
1159
|
? {
|
|
1106
1160
|
intent: 'detach',
|
|
1107
1161
|
resource,
|
|
1108
|
-
|
|
1109
|
-
|
|
1162
|
+
fromTarget,
|
|
1163
|
+
toTarget: null,
|
|
1110
1164
|
source: sourceArg,
|
|
1111
1165
|
}
|
|
1112
1166
|
: {
|
|
1113
1167
|
intent: 'reassign',
|
|
1114
1168
|
resource,
|
|
1115
|
-
|
|
1116
|
-
|
|
1169
|
+
fromTarget,
|
|
1170
|
+
toTarget,
|
|
1117
1171
|
source: sourceArg,
|
|
1118
1172
|
};
|
|
1119
1173
|
this.pendingResourceAssociationRequest = {
|
|
1120
1174
|
resourceId: resource.id,
|
|
1121
1175
|
resourceTitle: resource.title || resource.id,
|
|
1122
|
-
|
|
1123
|
-
|
|
1176
|
+
fromTarget,
|
|
1177
|
+
toTarget,
|
|
1124
1178
|
intent: detail.intent,
|
|
1125
1179
|
};
|
|
1126
1180
|
this.moveAnnouncement = this.resourceAssociationAnnouncement(detail, false);
|
|
@@ -1147,12 +1201,6 @@ export class DeesHarnessSessionList extends DeesElement {
|
|
|
1147
1201
|
}
|
|
1148
1202
|
if (changedProperties.has('sessions')) {
|
|
1149
1203
|
const knownIds = new Set(this.sessions.map((session) => session.id));
|
|
1150
|
-
if (
|
|
1151
|
-
this.pointerDragGesture?.type === 'session'
|
|
1152
|
-
&& !knownIds.has(this.pointerDragGesture.sessionId)
|
|
1153
|
-
) {
|
|
1154
|
-
this.finishPointerDrag(false);
|
|
1155
|
-
}
|
|
1156
1204
|
const nextExpanded = new Set(
|
|
1157
1205
|
[...this.expandedSessionIds].filter((sessionId) => knownIds.has(sessionId)),
|
|
1158
1206
|
);
|
|
@@ -1164,13 +1212,6 @@ export class DeesHarnessSessionList extends DeesElement {
|
|
|
1164
1212
|
}
|
|
1165
1213
|
}
|
|
1166
1214
|
if (changedProperties.has('resources') || changedProperties.has('resourceAssociations')) {
|
|
1167
|
-
const gesture = this.pointerDragGesture;
|
|
1168
|
-
if (gesture?.type === 'resource') {
|
|
1169
|
-
const currentSessionId = this.acceptedResourceAssociations.get(gesture.resourceId) ?? null;
|
|
1170
|
-
if (!this.resourcesById.has(gesture.resourceId) || currentSessionId !== gesture.fromSessionId) {
|
|
1171
|
-
this.finishPointerDrag(false);
|
|
1172
|
-
}
|
|
1173
|
-
}
|
|
1174
1215
|
if (
|
|
1175
1216
|
this.resourceContextResourceId
|
|
1176
1217
|
&& !this.resourcesById.has(this.resourceContextResourceId)
|
|
@@ -1179,16 +1220,15 @@ export class DeesHarnessSessionList extends DeesElement {
|
|
|
1179
1220
|
}
|
|
1180
1221
|
if (
|
|
1181
1222
|
changedProperties.has('sessions')
|
|
1182
|
-
|| changedProperties.has('
|
|
1223
|
+
|| changedProperties.has('resources')
|
|
1224
|
+
|| changedProperties.has('search')
|
|
1183
1225
|
) {
|
|
1226
|
+
// A drag is positional only: it survives association and eligibility
|
|
1227
|
+
// updates, but never outlives its own row.
|
|
1184
1228
|
const gesture = this.pointerDragGesture;
|
|
1185
|
-
if (
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
gesture.eligibleSessionIds,
|
|
1189
|
-
new Set(this.eligibleSessionIds(gesture.resourceId)),
|
|
1190
|
-
)
|
|
1191
|
-
) this.finishPointerDrag(false);
|
|
1229
|
+
if (gesture && (this.searchActive || !this.itemsByKey.has(itemKey(gesture.item)))) {
|
|
1230
|
+
this.finishPointerDrag(false);
|
|
1231
|
+
}
|
|
1192
1232
|
}
|
|
1193
1233
|
}
|
|
1194
1234
|
|
|
@@ -1211,7 +1251,6 @@ export class DeesHarnessSessionList extends DeesElement {
|
|
|
1211
1251
|
}
|
|
1212
1252
|
|
|
1213
1253
|
public render(): TemplateResult {
|
|
1214
|
-
const groupingActive = this.enableGrouping && !this.search.trim();
|
|
1215
1254
|
return html`
|
|
1216
1255
|
<div class="searchRow">
|
|
1217
1256
|
<div class="searchField">
|
|
@@ -1249,15 +1288,15 @@ export class DeesHarnessSessionList extends DeesElement {
|
|
|
1249
1288
|
<div class="listFrame">
|
|
1250
1289
|
<div class="scrollShadow top ${this.topScrollShadowVisible ? 'visible' : ''}"></div>
|
|
1251
1290
|
<div
|
|
1252
|
-
class="list ${this.
|
|
1291
|
+
class="list ${this.draggingItem ? 'dragActive' : ''}"
|
|
1253
1292
|
@scroll=${this.syncScrollShadowState}
|
|
1254
1293
|
>
|
|
1255
1294
|
<div class="listContent">
|
|
1256
1295
|
${this.loading
|
|
1257
1296
|
? html`<div class="emptyNote">Loading conversations…</div>`
|
|
1258
|
-
:
|
|
1259
|
-
? this.
|
|
1260
|
-
: this.
|
|
1297
|
+
: this.searchActive
|
|
1298
|
+
? this.renderSearchList()
|
|
1299
|
+
: this.renderOrderedList()}
|
|
1261
1300
|
</div>
|
|
1262
1301
|
</div>
|
|
1263
1302
|
<div class="scrollShadow bottom ${this.bottomScrollShadowVisible ? 'visible' : ''}"></div>
|
|
@@ -1266,84 +1305,33 @@ export class DeesHarnessSessionList extends DeesElement {
|
|
|
1266
1305
|
`;
|
|
1267
1306
|
}
|
|
1268
1307
|
|
|
1269
|
-
private
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
? 'No conversations match this search.'
|
|
1280
|
-
: this.emptyText}
|
|
1281
|
-
</div>`;
|
|
1282
|
-
}
|
|
1283
|
-
return html`
|
|
1284
|
-
<div class="section">
|
|
1285
|
-
${repeat(
|
|
1286
|
-
sessions,
|
|
1287
|
-
(session) => session.id,
|
|
1288
|
-
(session) => this.renderItem(session, null, -1),
|
|
1289
|
-
)}
|
|
1290
|
-
</div>
|
|
1291
|
-
${this.renderUnattachedResources(unattachedResources)}
|
|
1292
|
-
`;
|
|
1308
|
+
private renderEmptyNote(): TemplateResult {
|
|
1309
|
+
return html`<div class="emptyNote">
|
|
1310
|
+
${this.acceptedResources.length
|
|
1311
|
+
? this.sessions.length
|
|
1312
|
+
? 'No conversations or resources match this search.'
|
|
1313
|
+
: 'No resources match this search.'
|
|
1314
|
+
: this.sessions.length
|
|
1315
|
+
? 'No conversations match this search.'
|
|
1316
|
+
: this.emptyText}
|
|
1317
|
+
</div>`;
|
|
1293
1318
|
}
|
|
1294
1319
|
|
|
1295
|
-
private
|
|
1296
|
-
const
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1320
|
+
private renderSearchList(): TemplateResult {
|
|
1321
|
+
const items = this.searchItems;
|
|
1322
|
+
if (!items.length) return this.renderEmptyNote();
|
|
1323
|
+
return this.renderSection(items, null, false);
|
|
1324
|
+
}
|
|
1325
|
+
|
|
1326
|
+
private renderOrderedList(): TemplateResult {
|
|
1327
|
+
const groups = this.effectiveGroups;
|
|
1328
|
+
const ungrouped = this.ungroupedItems;
|
|
1329
|
+
if (!this.acceptedItems.length && !groups.length) {
|
|
1300
1330
|
return html`<div class="emptyNote">${this.emptyText}</div>`;
|
|
1301
1331
|
}
|
|
1302
1332
|
return html`
|
|
1303
|
-
${
|
|
1304
|
-
|
|
1305
|
-
.map((id) => byId.get(id))
|
|
1306
|
-
.filter((session): session is IHarnessSessionMeta => session !== undefined);
|
|
1307
|
-
const collapsed = this.collapsedGroupIds.has(group.id);
|
|
1308
|
-
const attentionCount = members.filter((session) => session.state === 'attention' || session.state === 'error').length;
|
|
1309
|
-
const isHeaderDrop =
|
|
1310
|
-
this.dropSlot?.groupId === group.id
|
|
1311
|
-
&& this.dropSlot.index === this.sortableSessions(members).length
|
|
1312
|
-
&& collapsed;
|
|
1313
|
-
return html`
|
|
1314
|
-
<button
|
|
1315
|
-
class="groupHeader ${collapsed ? 'collapsed' : ''} ${isHeaderDrop ? 'dropTarget' : ''}"
|
|
1316
|
-
data-drop-group-id=${group.id}
|
|
1317
|
-
data-drop-index=${this.sortableSessions(members).length}
|
|
1318
|
-
data-drop-kind="slot"
|
|
1319
|
-
type="button"
|
|
1320
|
-
@click=${() => this.toggleGroupCollapsed(group.id)}
|
|
1321
|
-
@contextmenu=${(event: MouseEvent) => {
|
|
1322
|
-
event.preventDefault();
|
|
1323
|
-
const detail: IHarnessGroupContextDetail = {
|
|
1324
|
-
group,
|
|
1325
|
-
clientX: event.clientX,
|
|
1326
|
-
clientY: event.clientY,
|
|
1327
|
-
originalEvent: event,
|
|
1328
|
-
};
|
|
1329
|
-
this.dispatchEvent(
|
|
1330
|
-
new CustomEvent('harness-group-context', { detail, bubbles: true, composed: true }),
|
|
1331
|
-
);
|
|
1332
|
-
}}
|
|
1333
|
-
>
|
|
1334
|
-
<dees-icon class="chevron" icon="lucide:ChevronDown" iconSize="12"></dees-icon>
|
|
1335
|
-
<span>${group.name}</span>
|
|
1336
|
-
${attentionCount ? html`<span class="groupAttention" aria-label=${`${attentionCount} need attention`}>
|
|
1337
|
-
<dees-icon aria-hidden="true" icon="lucide:CircleAlert" iconSize="11"></dees-icon>${attentionCount}
|
|
1338
|
-
</span>` : ''}
|
|
1339
|
-
<span class="groupCount">${members.length}</span>
|
|
1340
|
-
</button>
|
|
1341
|
-
${collapsed
|
|
1342
|
-
? ''
|
|
1343
|
-
: this.renderSection(members, group.id)}
|
|
1344
|
-
`;
|
|
1345
|
-
})}
|
|
1346
|
-
${this.groups.length && (ungrouped.length || this.draggingSessionId)
|
|
1333
|
+
${groups.map((group) => this.renderGroup(group))}
|
|
1334
|
+
${groups.length && (ungrouped.length || this.draggingItem)
|
|
1347
1335
|
? html`
|
|
1348
1336
|
<button
|
|
1349
1337
|
class="groupHeader"
|
|
@@ -1358,56 +1346,107 @@ export class DeesHarnessSessionList extends DeesElement {
|
|
|
1358
1346
|
</button>
|
|
1359
1347
|
`
|
|
1360
1348
|
: ''}
|
|
1361
|
-
${this.renderSection(ungrouped, null)}
|
|
1362
|
-
|
|
1349
|
+
${this.renderSection(ungrouped, null, true)}
|
|
1350
|
+
`;
|
|
1351
|
+
}
|
|
1352
|
+
|
|
1353
|
+
private renderGroup(groupArg: IHarnessSessionGroup): TemplateResult {
|
|
1354
|
+
const members = this.itemsForGroup(groupArg.id);
|
|
1355
|
+
const collapsed = this.collapsedGroupIds.has(groupArg.id);
|
|
1356
|
+
const attentionCount = members.filter((item) => (
|
|
1357
|
+
item.kind === 'session' && (item.session.state === 'attention' || item.session.state === 'error')
|
|
1358
|
+
)).length;
|
|
1359
|
+
const appendIndex = this.sortableItems(members).length;
|
|
1360
|
+
const isHeaderDrop = this.dropSlot?.groupId === groupArg.id
|
|
1361
|
+
&& this.dropSlot.index === appendIndex
|
|
1362
|
+
&& collapsed;
|
|
1363
|
+
return html`
|
|
1364
|
+
<button
|
|
1365
|
+
class="groupHeader ${collapsed ? 'collapsed' : ''} ${isHeaderDrop ? 'dropTarget' : ''}"
|
|
1366
|
+
data-drop-group-id=${groupArg.id}
|
|
1367
|
+
data-drop-index=${appendIndex}
|
|
1368
|
+
data-drop-kind="slot"
|
|
1369
|
+
type="button"
|
|
1370
|
+
@click=${() => this.toggleGroupCollapsed(groupArg.id)}
|
|
1371
|
+
@contextmenu=${(event: MouseEvent) => {
|
|
1372
|
+
event.preventDefault();
|
|
1373
|
+
const detail: IHarnessGroupContextDetail = {
|
|
1374
|
+
group: groupArg,
|
|
1375
|
+
clientX: event.clientX,
|
|
1376
|
+
clientY: event.clientY,
|
|
1377
|
+
originalEvent: event,
|
|
1378
|
+
};
|
|
1379
|
+
this.dispatchEvent(
|
|
1380
|
+
new CustomEvent('harness-group-context', { detail, bubbles: true, composed: true }),
|
|
1381
|
+
);
|
|
1382
|
+
}}
|
|
1383
|
+
>
|
|
1384
|
+
<dees-icon class="chevron" icon="lucide:ChevronDown" iconSize="12"></dees-icon>
|
|
1385
|
+
<span>${groupArg.name}</span>
|
|
1386
|
+
${attentionCount ? html`<span class="groupAttention" aria-label=${`${attentionCount} need attention`}>
|
|
1387
|
+
<dees-icon aria-hidden="true" icon="lucide:CircleAlert" iconSize="11"></dees-icon>${attentionCount}
|
|
1388
|
+
</span>` : ''}
|
|
1389
|
+
<span class="groupCount">${members.length}</span>
|
|
1390
|
+
</button>
|
|
1391
|
+
${collapsed ? '' : this.renderSection(members, groupArg.id, true)}
|
|
1363
1392
|
`;
|
|
1364
1393
|
}
|
|
1365
1394
|
|
|
1366
1395
|
private renderSection(
|
|
1367
|
-
|
|
1368
|
-
|
|
1396
|
+
itemsArg: TListItem[],
|
|
1397
|
+
groupIdArg: string | null,
|
|
1398
|
+
sortableArg: boolean,
|
|
1369
1399
|
): TemplateResult {
|
|
1370
|
-
const slot = this.dropSlot;
|
|
1400
|
+
const slot = sortableArg ? this.dropSlot : undefined;
|
|
1371
1401
|
const entries: TSectionRenderEntry[] = [];
|
|
1372
1402
|
let logicalIndex = 0;
|
|
1373
|
-
for (const
|
|
1374
|
-
const isDragging =
|
|
1375
|
-
const sortable =
|
|
1403
|
+
for (const item of itemsArg) {
|
|
1404
|
+
const isDragging = sameItem(this.draggingItem, item);
|
|
1405
|
+
const sortable = sortableArg && item.sortable;
|
|
1376
1406
|
if (
|
|
1377
1407
|
slot
|
|
1378
|
-
&& slot.groupId ===
|
|
1408
|
+
&& slot.groupId === groupIdArg
|
|
1379
1409
|
&& slot.index === logicalIndex
|
|
1380
1410
|
&& !isDragging
|
|
1381
1411
|
&& sortable
|
|
1382
1412
|
) {
|
|
1383
|
-
entries.push({ type: 'placeholder', key: `placeholder:${
|
|
1413
|
+
entries.push({ type: 'placeholder', key: `placeholder:${groupIdArg ?? 'ungrouped'}` });
|
|
1384
1414
|
}
|
|
1385
1415
|
entries.push({
|
|
1386
|
-
type: '
|
|
1387
|
-
key:
|
|
1388
|
-
|
|
1416
|
+
type: 'item',
|
|
1417
|
+
key: item.key,
|
|
1418
|
+
item,
|
|
1389
1419
|
logicalIndex: sortable ? logicalIndex : -1,
|
|
1390
1420
|
});
|
|
1391
1421
|
if (sortable && !isDragging) logicalIndex += 1;
|
|
1392
1422
|
}
|
|
1393
|
-
if (slot && slot.groupId ===
|
|
1394
|
-
entries.push({ type: 'placeholder', key: `placeholder:${
|
|
1423
|
+
if (slot && slot.groupId === groupIdArg && slot.index === logicalIndex) {
|
|
1424
|
+
entries.push({ type: 'placeholder', key: `placeholder:${groupIdArg ?? 'ungrouped'}` });
|
|
1395
1425
|
}
|
|
1396
1426
|
const hasPlaceholder = entries.some((entry) => entry.type === 'placeholder');
|
|
1427
|
+
// Resolve the controlled associations once per section, not once per row.
|
|
1428
|
+
const associations = this.acceptedResourceAssociations;
|
|
1397
1429
|
return html`
|
|
1398
|
-
<div class="section" data-group-id=${
|
|
1430
|
+
<div class="section" data-group-id=${groupIdArg ?? ''}>
|
|
1399
1431
|
${repeat(
|
|
1400
1432
|
entries,
|
|
1401
1433
|
(entry) => entry.key,
|
|
1402
1434
|
(entry) => entry.type === 'placeholder'
|
|
1403
|
-
? this.renderDropPlaceholder(
|
|
1404
|
-
:
|
|
1435
|
+
? this.renderDropPlaceholder(groupIdArg, slot?.index ?? 0)
|
|
1436
|
+
: entry.item.kind === 'session'
|
|
1437
|
+
? this.renderSessionItem(entry.item.session, groupIdArg, entry.logicalIndex)
|
|
1438
|
+
: this.renderResourceItem(
|
|
1439
|
+
entry.item.resource,
|
|
1440
|
+
groupIdArg,
|
|
1441
|
+
entry.logicalIndex,
|
|
1442
|
+
associations.get(entry.item.id) ?? null,
|
|
1443
|
+
),
|
|
1405
1444
|
)}
|
|
1406
|
-
${this.
|
|
1445
|
+
${this.draggingItem && sortableArg && !hasPlaceholder && this.sortableItems(itemsArg).length === 0
|
|
1407
1446
|
? html`
|
|
1408
1447
|
<div
|
|
1409
1448
|
class="emptyDropZone"
|
|
1410
|
-
data-drop-group-id=${
|
|
1449
|
+
data-drop-group-id=${groupIdArg ?? ''}
|
|
1411
1450
|
data-drop-index="0"
|
|
1412
1451
|
data-drop-kind="slot"
|
|
1413
1452
|
></div>
|
|
@@ -1424,46 +1463,73 @@ export class DeesHarnessSessionList extends DeesElement {
|
|
|
1424
1463
|
data-drop-group-id=${groupId ?? ''}
|
|
1425
1464
|
data-drop-index=${index}
|
|
1426
1465
|
data-drop-kind="slot"
|
|
1427
|
-
style=${`height: ${Math.max(
|
|
1466
|
+
style=${`height: ${Math.max(24, this.draggedItemHeight)}px`}
|
|
1428
1467
|
></div>
|
|
1429
1468
|
`;
|
|
1430
1469
|
}
|
|
1431
1470
|
|
|
1432
|
-
|
|
1471
|
+
/** One grip for both kinds; only its size and extra class differ. */
|
|
1472
|
+
private renderDragHandle(
|
|
1473
|
+
refArg: IHarnessSessionListItemRef,
|
|
1474
|
+
titleArg: string,
|
|
1475
|
+
groupIdArg: string | null,
|
|
1476
|
+
logicalIndexArg: number,
|
|
1477
|
+
): TemplateResult {
|
|
1478
|
+
const isResource = refArg.kind === 'resource';
|
|
1479
|
+
return html`
|
|
1480
|
+
<button
|
|
1481
|
+
class="dragHandle ${isResource ? 'resourceDragHandle' : ''}"
|
|
1482
|
+
type="button"
|
|
1483
|
+
title="Drag to reorder; use arrow, Home, or End keys for keyboard reordering"
|
|
1484
|
+
aria-label=${`Reorder ${titleArg}`}
|
|
1485
|
+
aria-keyshortcuts="ArrowUp ArrowDown Home End"
|
|
1486
|
+
@pointerdown=${(event: PointerEvent) => {
|
|
1487
|
+
this.handlePointerDown(event, refArg, groupIdArg, logicalIndexArg);
|
|
1488
|
+
}}
|
|
1489
|
+
@pointermove=${this.handlePointerMove}
|
|
1490
|
+
@pointerup=${this.handlePointerUp}
|
|
1491
|
+
@pointercancel=${this.handlePointerCancel}
|
|
1492
|
+
@lostpointercapture=${this.handleLostPointerCapture}
|
|
1493
|
+
@keydown=${(event: KeyboardEvent) => {
|
|
1494
|
+
this.handleKeyboardMove(event, refArg, titleArg, groupIdArg);
|
|
1495
|
+
}}
|
|
1496
|
+
>
|
|
1497
|
+
<dees-icon icon="lucide:GripVertical" iconSize=${isResource ? '13' : '14'}></dees-icon>
|
|
1498
|
+
</button>
|
|
1499
|
+
`;
|
|
1500
|
+
}
|
|
1501
|
+
|
|
1502
|
+
private renderSessionItem(
|
|
1433
1503
|
session: IHarnessSessionMeta,
|
|
1434
1504
|
groupId: string | null,
|
|
1435
1505
|
index: number,
|
|
1436
1506
|
): TemplateResult {
|
|
1437
|
-
const draggable =
|
|
1438
|
-
this.enableGrouping
|
|
1439
|
-
&& session.draggable !== false
|
|
1440
|
-
&& index >= 0
|
|
1441
|
-
);
|
|
1507
|
+
const draggable = index >= 0;
|
|
1442
1508
|
const expanded = this.expandedSessionIds.has(session.id);
|
|
1443
1509
|
const state = session.state ?? 'normal';
|
|
1444
1510
|
const statusLabel = this.sessionStatusLabel(session);
|
|
1445
1511
|
const active = (state === 'normal' || state === 'working') && (session.working ?? state === 'working');
|
|
1446
1512
|
const detailsId = `session-details-${session.id}`;
|
|
1447
1513
|
const dropIndex = index < 0 ? 0 : index;
|
|
1448
|
-
const
|
|
1449
|
-
const
|
|
1514
|
+
const title = session.title || session.id;
|
|
1515
|
+
const projectLabel = session.projectLabel?.trim() ?? '';
|
|
1450
1516
|
return html`
|
|
1451
1517
|
<article
|
|
1452
1518
|
class="item state-${state} ${active ? 'active' : ''} ${expanded ? 'expanded' : ''} ${
|
|
1453
1519
|
session.id === this.selectedSessionId ? 'selected' : ''
|
|
1454
1520
|
} ${
|
|
1455
|
-
this.
|
|
1456
|
-
} ${
|
|
1457
|
-
isResourceDropTarget ? 'resourceDropTarget' : ''
|
|
1521
|
+
sameItem(this.draggingItem, { kind: 'session', id: session.id }) ? 'dragging' : ''
|
|
1458
1522
|
}"
|
|
1459
1523
|
data-session-id=${session.id}
|
|
1524
|
+
data-item-kind="session"
|
|
1525
|
+
data-item-id=${session.id}
|
|
1460
1526
|
data-drop-group-id=${groupId ?? ''}
|
|
1461
1527
|
data-drop-index=${dropIndex}
|
|
1462
1528
|
data-drop-kind=${index < 0 ? 'slot' : 'card'}
|
|
1463
1529
|
@dblclick=${(event: MouseEvent) => {
|
|
1464
1530
|
if (this.eventTargetsExpandToggle(event) || this.eventTargetsDragHandle(event)
|
|
1465
1531
|
|| this.eventTargetsSessionActions(event)) return;
|
|
1466
|
-
this.
|
|
1532
|
+
this.emitSession('harness-session-open', session);
|
|
1467
1533
|
}}
|
|
1468
1534
|
@contextmenu=${(event: MouseEvent) => {
|
|
1469
1535
|
if (this.eventTargetsExpandToggle(event)) return;
|
|
@@ -1483,39 +1549,19 @@ export class DeesHarnessSessionList extends DeesElement {
|
|
|
1483
1549
|
<div class="itemInner">
|
|
1484
1550
|
<div class="itemHeader ${draggable ? 'draggable' : ''}">
|
|
1485
1551
|
${draggable
|
|
1486
|
-
?
|
|
1487
|
-
<button
|
|
1488
|
-
class="dragHandle"
|
|
1489
|
-
type="button"
|
|
1490
|
-
title="Drag to reorder; use arrow, Home, or End keys for keyboard reordering"
|
|
1491
|
-
aria-label=${`Reorder ${session.title || session.id}`}
|
|
1492
|
-
aria-keyshortcuts="ArrowUp ArrowDown Home End"
|
|
1493
|
-
@pointerdown=${(event: PointerEvent) => {
|
|
1494
|
-
this.handlePointerDown(event, session, groupId, index);
|
|
1495
|
-
}}
|
|
1496
|
-
@pointermove=${this.handlePointerMove}
|
|
1497
|
-
@pointerup=${this.handlePointerUp}
|
|
1498
|
-
@pointercancel=${this.handlePointerCancel}
|
|
1499
|
-
@lostpointercapture=${this.handleLostPointerCapture}
|
|
1500
|
-
@keydown=${(event: KeyboardEvent) => {
|
|
1501
|
-
this.handleKeyboardMove(event, session, groupId);
|
|
1502
|
-
}}
|
|
1503
|
-
>
|
|
1504
|
-
<dees-icon icon="lucide:GripVertical" iconSize="14"></dees-icon>
|
|
1505
|
-
</button>
|
|
1506
|
-
`
|
|
1552
|
+
? this.renderDragHandle({ kind: 'session', id: session.id }, title, groupId, index)
|
|
1507
1553
|
: ''}
|
|
1508
1554
|
<button
|
|
1509
1555
|
class="itemPrimary"
|
|
1510
1556
|
type="button"
|
|
1511
1557
|
aria-keyshortcuts="ContextMenu Shift+F10"
|
|
1512
1558
|
aria-current=${session.id === this.selectedSessionId ? 'true' : 'false'}
|
|
1513
|
-
@click=${() => this.
|
|
1559
|
+
@click=${() => this.emitSession('harness-session-select', session)}
|
|
1514
1560
|
@keydown=${(event: KeyboardEvent) => {
|
|
1515
1561
|
if (this.handleSessionContextKey(event)) return;
|
|
1516
1562
|
if (event.key !== 'Enter') return;
|
|
1517
1563
|
event.preventDefault();
|
|
1518
|
-
this.
|
|
1564
|
+
this.emitSession('harness-session-open', session);
|
|
1519
1565
|
}}
|
|
1520
1566
|
>
|
|
1521
1567
|
<span class="stateDot" aria-hidden="true">
|
|
@@ -1526,7 +1572,7 @@ export class DeesHarnessSessionList extends DeesElement {
|
|
|
1526
1572
|
${session.icon
|
|
1527
1573
|
? html`<dees-icon class="itemIcon" icon=${session.icon} iconSize="13"></dees-icon>`
|
|
1528
1574
|
: ''}
|
|
1529
|
-
<span class="titleText">${
|
|
1575
|
+
<span class="titleText">${title}</span>
|
|
1530
1576
|
</span>
|
|
1531
1577
|
<span class="itemStatus" title=${statusLabel}>${statusLabel}</span>
|
|
1532
1578
|
<span class="itemIdentity">
|
|
@@ -1535,6 +1581,10 @@ export class DeesHarnessSessionList extends DeesElement {
|
|
|
1535
1581
|
<span class="harnessName" title=${session.harness.name}>${session.harness.name}</span>
|
|
1536
1582
|
<span aria-hidden="true">·</span>
|
|
1537
1583
|
` : ''}
|
|
1584
|
+
${projectLabel ? html`
|
|
1585
|
+
<span class="projectName" title=${projectLabel}>${projectLabel}</span>
|
|
1586
|
+
<span aria-hidden="true">·</span>
|
|
1587
|
+
` : ''}
|
|
1538
1588
|
<span class="itemTime">${harnessFormatRelativeTime(session.updatedAt ?? session.createdAt)}</span>
|
|
1539
1589
|
</span>
|
|
1540
1590
|
</span>
|
|
@@ -1543,7 +1593,7 @@ export class DeesHarnessSessionList extends DeesElement {
|
|
|
1543
1593
|
class="sessionActions"
|
|
1544
1594
|
type="button"
|
|
1545
1595
|
title="Actions"
|
|
1546
|
-
aria-label=${`Actions for ${
|
|
1596
|
+
aria-label=${`Actions for ${title}`}
|
|
1547
1597
|
aria-haspopup="menu"
|
|
1548
1598
|
aria-keyshortcuts="ContextMenu Shift+F10"
|
|
1549
1599
|
@click=${this.invokeSessionContext}
|
|
@@ -1565,20 +1615,6 @@ export class DeesHarnessSessionList extends DeesElement {
|
|
|
1565
1615
|
<dees-icon icon="lucide:ChevronDown" iconSize="15"></dees-icon>
|
|
1566
1616
|
</button>
|
|
1567
1617
|
</div>
|
|
1568
|
-
${isResourceDropTarget
|
|
1569
|
-
? html`<div class="resourceDropLabel">Associate resource with ${session.title || session.id}</div>`
|
|
1570
|
-
: ''}
|
|
1571
|
-
${resources.length
|
|
1572
|
-
? html`
|
|
1573
|
-
<ul class="resourceChildren" aria-label=${`Resources for ${session.title || session.id}`}>
|
|
1574
|
-
${repeat(
|
|
1575
|
-
resources,
|
|
1576
|
-
(resource) => resource.id,
|
|
1577
|
-
(resource) => this.renderResource(resource, session.id),
|
|
1578
|
-
)}
|
|
1579
|
-
</ul>
|
|
1580
|
-
`
|
|
1581
|
-
: ''}
|
|
1582
1618
|
${expanded
|
|
1583
1619
|
? html`
|
|
1584
1620
|
<div class="itemDetails" id=${detailsId}>
|
|
@@ -1594,41 +1630,36 @@ export class DeesHarnessSessionList extends DeesElement {
|
|
|
1594
1630
|
`;
|
|
1595
1631
|
}
|
|
1596
1632
|
|
|
1597
|
-
private
|
|
1598
|
-
if (!resourcesArg.length) return html``;
|
|
1599
|
-
return html`
|
|
1600
|
-
<section class="resourceSection" aria-labelledby="unattached-resources-heading">
|
|
1601
|
-
<h2 class="resourceSectionTitle" id="unattached-resources-heading">Resources</h2>
|
|
1602
|
-
<ul class="resourceList" aria-label="Unattached resources">
|
|
1603
|
-
${repeat(
|
|
1604
|
-
resourcesArg,
|
|
1605
|
-
(resource) => resource.id,
|
|
1606
|
-
(resource) => this.renderResource(resource, null),
|
|
1607
|
-
)}
|
|
1608
|
-
</ul>
|
|
1609
|
-
</section>
|
|
1610
|
-
`;
|
|
1611
|
-
}
|
|
1612
|
-
|
|
1613
|
-
private renderResource(
|
|
1633
|
+
private renderResourceItem(
|
|
1614
1634
|
resourceArg: IHarnessResourceMeta,
|
|
1615
|
-
|
|
1635
|
+
groupIdArg: string | null,
|
|
1636
|
+
indexArg: number,
|
|
1637
|
+
attachedTarget: IHarnessSessionListItemRef | null,
|
|
1616
1638
|
): TemplateResult {
|
|
1617
1639
|
const selected = resourceArg.id === this.selectedResourceId;
|
|
1618
|
-
const
|
|
1619
|
-
const
|
|
1620
|
-
(sessionId) => sessionId !== sessionIdArg,
|
|
1621
|
-
).length;
|
|
1640
|
+
const draggable = indexArg >= 0;
|
|
1641
|
+
const dragging = sameItem(this.draggingItem, { kind: 'resource', id: resourceArg.id });
|
|
1622
1642
|
const contextOpen = resourceArg.id === this.resourceContextResourceId;
|
|
1643
|
+
const attachedTitle = attachedTarget ? this.itemTitleFor(attachedTarget) : '';
|
|
1644
|
+
const title = resourceArg.title || resourceArg.id;
|
|
1645
|
+
const projectLabel = resourceArg.projectLabel?.trim() ?? '';
|
|
1646
|
+
const dropIndex = indexArg < 0 ? 0 : indexArg;
|
|
1623
1647
|
return html`
|
|
1624
|
-
<
|
|
1625
|
-
class="resourceRow ${selected ? 'selected' : ''} ${
|
|
1648
|
+
<article
|
|
1649
|
+
class="resourceRow ${selected ? 'selected' : ''} ${draggable ? 'draggable' : ''} ${
|
|
1650
|
+
dragging ? 'dragging' : ''
|
|
1651
|
+
}"
|
|
1626
1652
|
data-resource-id=${resourceArg.id}
|
|
1627
|
-
data-resource
|
|
1653
|
+
data-item-kind="resource"
|
|
1654
|
+
data-item-id=${resourceArg.id}
|
|
1655
|
+
data-resource-target=${attachedTarget ? `${attachedTarget.kind}:${attachedTarget.id}` : ''}
|
|
1656
|
+
data-drop-group-id=${groupIdArg ?? ''}
|
|
1657
|
+
data-drop-index=${dropIndex}
|
|
1658
|
+
data-drop-kind=${indexArg < 0 ? 'slot' : 'card'}
|
|
1628
1659
|
@dblclick=${(event: MouseEvent) => {
|
|
1629
1660
|
event.stopPropagation();
|
|
1630
|
-
if (this.eventTargetsResourceControl(event)) return;
|
|
1631
|
-
this.emitResource('harness-resource-open', resourceArg,
|
|
1661
|
+
if (this.eventTargetsResourceControl(event) || this.eventTargetsDragHandle(event)) return;
|
|
1662
|
+
this.emitResource('harness-resource-open', resourceArg, attachedTarget);
|
|
1632
1663
|
}}
|
|
1633
1664
|
@contextmenu=${(event: MouseEvent) => {
|
|
1634
1665
|
event.preventDefault();
|
|
@@ -1639,43 +1670,29 @@ export class DeesHarnessSessionList extends DeesElement {
|
|
|
1639
1670
|
)) as HTMLElement | undefined)
|
|
1640
1671
|
?? (event.currentTarget as HTMLElement).querySelector<HTMLElement>('.resourcePrimary')
|
|
1641
1672
|
?? event.currentTarget as HTMLElement;
|
|
1642
|
-
this.emitResourceContext(event, resourceArg,
|
|
1673
|
+
this.emitResourceContext(event, resourceArg, attachedTarget, 'pointer', focusTarget);
|
|
1643
1674
|
}}
|
|
1644
1675
|
>
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
?disabled=${eligibleTargetCount === 0}
|
|
1654
|
-
@pointerdown=${(event: PointerEvent) => {
|
|
1655
|
-
this.handleResourcePointerDown(event, resourceArg, sessionIdArg);
|
|
1656
|
-
}}
|
|
1657
|
-
@pointermove=${this.handlePointerMove}
|
|
1658
|
-
@pointerup=${this.handlePointerUp}
|
|
1659
|
-
@pointercancel=${this.handlePointerCancel}
|
|
1660
|
-
@lostpointercapture=${this.handleLostPointerCapture}
|
|
1661
|
-
@keydown=${(event: KeyboardEvent) => {
|
|
1662
|
-
this.handleResourceContextKey(event, resourceArg, sessionIdArg);
|
|
1663
|
-
}}
|
|
1664
|
-
>
|
|
1665
|
-
<dees-icon icon="lucide:GripVertical" iconSize="13"></dees-icon>
|
|
1666
|
-
</button>
|
|
1676
|
+
${draggable
|
|
1677
|
+
? this.renderDragHandle(
|
|
1678
|
+
{ kind: 'resource', id: resourceArg.id },
|
|
1679
|
+
title,
|
|
1680
|
+
groupIdArg,
|
|
1681
|
+
indexArg,
|
|
1682
|
+
)
|
|
1683
|
+
: ''}
|
|
1667
1684
|
<button
|
|
1668
1685
|
class="resourcePrimary"
|
|
1669
1686
|
type="button"
|
|
1670
1687
|
aria-current=${selected ? 'true' : 'false'}
|
|
1671
|
-
@click=${() => this.emitResource('harness-resource-select', resourceArg,
|
|
1688
|
+
@click=${() => this.emitResource('harness-resource-select', resourceArg, attachedTarget)}
|
|
1672
1689
|
@keydown=${(event: KeyboardEvent) => {
|
|
1673
1690
|
if (event.key === 'Enter') {
|
|
1674
1691
|
event.preventDefault();
|
|
1675
|
-
this.emitResource('harness-resource-open', resourceArg,
|
|
1692
|
+
this.emitResource('harness-resource-open', resourceArg, attachedTarget);
|
|
1676
1693
|
return;
|
|
1677
1694
|
}
|
|
1678
|
-
this.handleResourceContextKey(event, resourceArg,
|
|
1695
|
+
this.handleResourceContextKey(event, resourceArg, attachedTarget);
|
|
1679
1696
|
}}
|
|
1680
1697
|
>
|
|
1681
1698
|
<dees-icon
|
|
@@ -1684,36 +1701,53 @@ export class DeesHarnessSessionList extends DeesElement {
|
|
|
1684
1701
|
iconSize="14"
|
|
1685
1702
|
></dees-icon>
|
|
1686
1703
|
<span class="resourceCopy">
|
|
1687
|
-
<span class="resourceTitle">${
|
|
1704
|
+
<span class="resourceTitle">${title}</span>
|
|
1688
1705
|
${resourceArg.preview ? html`<span class="resourcePreview">${resourceArg.preview}</span>` : ''}
|
|
1689
1706
|
<span class="resourceMeta">
|
|
1690
|
-
${resourceArg.kind}${
|
|
1707
|
+
${resourceArg.kind}${projectLabel ? ` · ${projectLabel}` : ''}${
|
|
1708
|
+
resourceArg.state ? ` · ${resourceArg.state}` : ''
|
|
1709
|
+
}${
|
|
1691
1710
|
resourceArg.updatedAt || resourceArg.createdAt
|
|
1692
1711
|
? ` · ${harnessFormatRelativeTime(resourceArg.updatedAt ?? resourceArg.createdAt)}`
|
|
1693
1712
|
: ''
|
|
1694
1713
|
}
|
|
1695
1714
|
</span>
|
|
1715
|
+
${attachedTarget
|
|
1716
|
+
? html`
|
|
1717
|
+
<span class="resourceAttachment" title=${`Attached to ${attachedTitle}`}>
|
|
1718
|
+
<dees-icon aria-hidden="true" icon="lucide:Link" iconSize="10"></dees-icon>
|
|
1719
|
+
<span class="visuallyHidden">Attached to </span>
|
|
1720
|
+
<span class="resourceAttachmentName">${attachedTitle}</span>
|
|
1721
|
+
</span>
|
|
1722
|
+
`
|
|
1723
|
+
: ''}
|
|
1696
1724
|
</span>
|
|
1697
1725
|
</button>
|
|
1698
1726
|
<button
|
|
1699
1727
|
class="resourceActions"
|
|
1700
1728
|
type="button"
|
|
1701
1729
|
title="Resource actions"
|
|
1702
|
-
aria-label=${`Actions for ${
|
|
1730
|
+
aria-label=${`Actions for ${title}`}
|
|
1703
1731
|
aria-haspopup="menu"
|
|
1704
1732
|
aria-expanded=${contextOpen ? 'true' : 'false'}
|
|
1705
1733
|
aria-keyshortcuts="ContextMenu Shift+F10"
|
|
1706
1734
|
@click=${(event: MouseEvent) => {
|
|
1707
1735
|
event.stopPropagation();
|
|
1708
|
-
this.emitResourceContext(
|
|
1736
|
+
this.emitResourceContext(
|
|
1737
|
+
event,
|
|
1738
|
+
resourceArg,
|
|
1739
|
+
attachedTarget,
|
|
1740
|
+
'action',
|
|
1741
|
+
event.currentTarget as HTMLElement,
|
|
1742
|
+
);
|
|
1709
1743
|
}}
|
|
1710
1744
|
@keydown=${(event: KeyboardEvent) => {
|
|
1711
|
-
this.handleResourceContextKey(event, resourceArg,
|
|
1745
|
+
this.handleResourceContextKey(event, resourceArg, attachedTarget);
|
|
1712
1746
|
}}
|
|
1713
1747
|
>
|
|
1714
1748
|
<dees-icon icon="lucide:Ellipsis" iconSize="15"></dees-icon>
|
|
1715
1749
|
</button>
|
|
1716
|
-
</
|
|
1750
|
+
</article>
|
|
1717
1751
|
`;
|
|
1718
1752
|
}
|
|
1719
1753
|
|
|
@@ -1755,10 +1789,7 @@ export class DeesHarnessSessionList extends DeesElement {
|
|
|
1755
1789
|
|
|
1756
1790
|
private eventTargetsResourceControl(event: Event): boolean {
|
|
1757
1791
|
return event.composedPath().some(
|
|
1758
|
-
(target) => target instanceof HTMLElement && (
|
|
1759
|
-
target.classList.contains('resourceDragHandle')
|
|
1760
|
-
|| target.classList.contains('resourceActions')
|
|
1761
|
-
),
|
|
1792
|
+
(target) => target instanceof HTMLElement && target.classList.contains('resourceActions'),
|
|
1762
1793
|
);
|
|
1763
1794
|
}
|
|
1764
1795
|
|
|
@@ -1803,29 +1834,9 @@ export class DeesHarnessSessionList extends DeesElement {
|
|
|
1803
1834
|
this.collapsedGroupIds = next;
|
|
1804
1835
|
}
|
|
1805
1836
|
|
|
1806
|
-
private sortableSessions(
|
|
1807
|
-
sessionsArg: IHarnessSessionMeta[],
|
|
1808
|
-
excludeDraggingArg = true,
|
|
1809
|
-
): IHarnessSessionMeta[] {
|
|
1810
|
-
return sessionsArg.filter((session) => (
|
|
1811
|
-
session.draggable !== false
|
|
1812
|
-
&& (!excludeDraggingArg || session.id !== this.draggingSessionId)
|
|
1813
|
-
));
|
|
1814
|
-
}
|
|
1815
|
-
|
|
1816
|
-
private sessionsForGroup(groupIdArg: string | null): IHarnessSessionMeta[] {
|
|
1817
|
-
if (groupIdArg === null) return this.ungroupedSessions;
|
|
1818
|
-
const group = this.groups.find((candidate) => candidate.id === groupIdArg);
|
|
1819
|
-
if (!group) return [];
|
|
1820
|
-
const byId = this.sessionsById;
|
|
1821
|
-
return group.sessionIds
|
|
1822
|
-
.map((sessionId) => byId.get(sessionId))
|
|
1823
|
-
.filter((session): session is IHarnessSessionMeta => session !== undefined);
|
|
1824
|
-
}
|
|
1825
|
-
|
|
1826
1837
|
private handlePointerDown(
|
|
1827
1838
|
eventArg: PointerEvent,
|
|
1828
|
-
|
|
1839
|
+
refArg: IHarnessSessionListItemRef,
|
|
1829
1840
|
groupIdArg: string | null,
|
|
1830
1841
|
logicalIndexArg: number,
|
|
1831
1842
|
): void {
|
|
@@ -1836,15 +1847,14 @@ export class DeesHarnessSessionList extends DeesElement {
|
|
|
1836
1847
|
|| logicalIndexArg < 0
|
|
1837
1848
|
) return;
|
|
1838
1849
|
const handle = eventArg.currentTarget as HTMLElement;
|
|
1839
|
-
const source = handle.closest<HTMLElement>('.item');
|
|
1850
|
+
const source = handle.closest<HTMLElement>('.item, .resourceRow');
|
|
1840
1851
|
if (!source) return;
|
|
1841
1852
|
const sourceRect = source.getBoundingClientRect();
|
|
1842
1853
|
this.pointerDragGesture = {
|
|
1843
|
-
type: 'session',
|
|
1844
1854
|
pointerId: eventArg.pointerId,
|
|
1845
1855
|
handle,
|
|
1846
1856
|
source,
|
|
1847
|
-
|
|
1857
|
+
item: itemRef(refArg),
|
|
1848
1858
|
sourceGroupId: groupIdArg,
|
|
1849
1859
|
sourceLogicalIndex: logicalIndexArg,
|
|
1850
1860
|
startClientX: eventArg.clientX,
|
|
@@ -1862,44 +1872,6 @@ export class DeesHarnessSessionList extends DeesElement {
|
|
|
1862
1872
|
}
|
|
1863
1873
|
}
|
|
1864
1874
|
|
|
1865
|
-
private handleResourcePointerDown(
|
|
1866
|
-
eventArg: PointerEvent,
|
|
1867
|
-
resourceArg: IHarnessResourceMeta,
|
|
1868
|
-
fromSessionIdArg: string | null,
|
|
1869
|
-
): void {
|
|
1870
|
-
if (
|
|
1871
|
-
this.pointerDragGesture
|
|
1872
|
-
|| eventArg.button !== 0
|
|
1873
|
-
|| !eventArg.isPrimary
|
|
1874
|
-
|| this.eligibleSessionIds(resourceArg.id).every((sessionId) => sessionId === fromSessionIdArg)
|
|
1875
|
-
) return;
|
|
1876
|
-
const handle = eventArg.currentTarget as HTMLElement;
|
|
1877
|
-
const source = handle.closest<HTMLElement>('.resourceRow');
|
|
1878
|
-
if (!source) return;
|
|
1879
|
-
const sourceRect = source.getBoundingClientRect();
|
|
1880
|
-
this.pointerDragGesture = {
|
|
1881
|
-
type: 'resource',
|
|
1882
|
-
pointerId: eventArg.pointerId,
|
|
1883
|
-
handle,
|
|
1884
|
-
source,
|
|
1885
|
-
resourceId: resourceArg.id,
|
|
1886
|
-
fromSessionId: fromSessionIdArg,
|
|
1887
|
-
eligibleSessionIds: new Set(this.eligibleSessionIds(resourceArg.id)),
|
|
1888
|
-
startClientX: eventArg.clientX,
|
|
1889
|
-
startClientY: eventArg.clientY,
|
|
1890
|
-
grabOffsetX: eventArg.clientX - sourceRect.left,
|
|
1891
|
-
grabOffsetY: eventArg.clientY - sourceRect.top,
|
|
1892
|
-
phase: 'pending',
|
|
1893
|
-
};
|
|
1894
|
-
window.addEventListener('keydown', this.handleDragKeyDown, true);
|
|
1895
|
-
window.addEventListener('blur', this.handleDragWindowBlur);
|
|
1896
|
-
try {
|
|
1897
|
-
handle.setPointerCapture(eventArg.pointerId);
|
|
1898
|
-
} catch {
|
|
1899
|
-
this.finishPointerDrag(false);
|
|
1900
|
-
}
|
|
1901
|
-
}
|
|
1902
|
-
|
|
1903
1875
|
private readonly handlePointerMove = (eventArg: PointerEvent): void => {
|
|
1904
1876
|
const gesture = this.pointerDragGesture;
|
|
1905
1877
|
if (!gesture || eventArg.pointerId !== gesture.pointerId) return;
|
|
@@ -1914,21 +1886,8 @@ export class DeesHarnessSessionList extends DeesElement {
|
|
|
1914
1886
|
eventArg.clientY - gesture.startClientY,
|
|
1915
1887
|
) < dragThresholdPixels
|
|
1916
1888
|
) return;
|
|
1917
|
-
if (
|
|
1918
|
-
gesture.phase === 'pending'
|
|
1919
|
-
&& !(gesture.type === 'session'
|
|
1920
|
-
? this.startPointerDrag(eventArg, gesture)
|
|
1921
|
-
: this.startResourcePointerDrag(eventArg, gesture))
|
|
1922
|
-
) return;
|
|
1889
|
+
if (gesture.phase === 'pending' && !this.startPointerDrag(eventArg, gesture)) return;
|
|
1923
1890
|
eventArg.preventDefault();
|
|
1924
|
-
if (gesture.type === 'resource') {
|
|
1925
|
-
this.moveResourceDragPreview(eventArg.clientX, eventArg.clientY, gesture);
|
|
1926
|
-
this.dragAutoScrollClientX = eventArg.clientX;
|
|
1927
|
-
this.dragAutoScrollClientY = eventArg.clientY;
|
|
1928
|
-
this.updateDragAutoScrollVelocity();
|
|
1929
|
-
this.updateResourceDropTargetFromPointer(eventArg.clientX, eventArg.clientY);
|
|
1930
|
-
return;
|
|
1931
|
-
}
|
|
1932
1891
|
this.moveDragPreview(eventArg.clientX, eventArg.clientY, gesture);
|
|
1933
1892
|
this.dragAutoScrollClientX = eventArg.clientX;
|
|
1934
1893
|
this.dragAutoScrollClientY = eventArg.clientY;
|
|
@@ -1940,14 +1899,6 @@ export class DeesHarnessSessionList extends DeesElement {
|
|
|
1940
1899
|
const gesture = this.pointerDragGesture;
|
|
1941
1900
|
if (!gesture || eventArg.pointerId !== gesture.pointerId) return;
|
|
1942
1901
|
if (gesture.phase === 'active') {
|
|
1943
|
-
if (gesture.type === 'resource') {
|
|
1944
|
-
this.dragAutoScrollClientX = eventArg.clientX;
|
|
1945
|
-
this.dragAutoScrollClientY = eventArg.clientY;
|
|
1946
|
-
this.moveResourceDragPreview(eventArg.clientX, eventArg.clientY, gesture);
|
|
1947
|
-
this.updateResourceDropTargetFromPointer(eventArg.clientX, eventArg.clientY);
|
|
1948
|
-
this.finishPointerDrag(true);
|
|
1949
|
-
return;
|
|
1950
|
-
}
|
|
1951
1902
|
this.dragAutoScrollClientX = eventArg.clientX;
|
|
1952
1903
|
this.dragAutoScrollClientY = eventArg.clientY;
|
|
1953
1904
|
this.moveDragPreview(eventArg.clientX, eventArg.clientY, gesture);
|
|
@@ -1993,9 +1944,9 @@ export class DeesHarnessSessionList extends DeesElement {
|
|
|
1993
1944
|
gestureArg.phase = 'active';
|
|
1994
1945
|
this.reflowGeneration += 1;
|
|
1995
1946
|
this.cancelReflowAnimations();
|
|
1996
|
-
this.draggedItemHeight =
|
|
1997
|
-
this.
|
|
1998
|
-
const remainingCount = this.
|
|
1947
|
+
this.draggedItemHeight = sourceRect.height;
|
|
1948
|
+
this.draggingItem = itemRef(gestureArg.item);
|
|
1949
|
+
const remainingCount = this.sortableItems(this.itemsForGroup(gestureArg.sourceGroupId)).length;
|
|
1999
1950
|
this.dropSlot = {
|
|
2000
1951
|
groupId: gestureArg.sourceGroupId,
|
|
2001
1952
|
index: Math.min(gestureArg.sourceLogicalIndex, remainingCount),
|
|
@@ -2003,27 +1954,6 @@ export class DeesHarnessSessionList extends DeesElement {
|
|
|
2003
1954
|
return true;
|
|
2004
1955
|
}
|
|
2005
1956
|
|
|
2006
|
-
private startResourcePointerDrag(
|
|
2007
|
-
eventArg: PointerEvent,
|
|
2008
|
-
gestureArg: IResourcePointerDragGesture,
|
|
2009
|
-
): boolean {
|
|
2010
|
-
const sourceRect = gestureArg.source.getBoundingClientRect();
|
|
2011
|
-
gestureArg.source.classList.add('resourceDragging');
|
|
2012
|
-
gestureArg.source.style.setProperty('--resource-drag-preview-width', `${sourceRect.width}px`);
|
|
2013
|
-
this.moveResourceDragPreview(eventArg.clientX, eventArg.clientY, gestureArg);
|
|
2014
|
-
gestureArg.source.setAttribute('popover', 'manual');
|
|
2015
|
-
try {
|
|
2016
|
-
gestureArg.source.showPopover();
|
|
2017
|
-
} catch {
|
|
2018
|
-
this.finishPointerDrag(false);
|
|
2019
|
-
return false;
|
|
2020
|
-
}
|
|
2021
|
-
gestureArg.phase = 'active';
|
|
2022
|
-
this.draggingResourceId = gestureArg.resourceId;
|
|
2023
|
-
this.resourceDropTargetSessionId = '';
|
|
2024
|
-
return true;
|
|
2025
|
-
}
|
|
2026
|
-
|
|
2027
1957
|
private moveDragPreview(
|
|
2028
1958
|
clientXArg: number,
|
|
2029
1959
|
clientYArg: number,
|
|
@@ -2039,49 +1969,21 @@ export class DeesHarnessSessionList extends DeesElement {
|
|
|
2039
1969
|
);
|
|
2040
1970
|
}
|
|
2041
1971
|
|
|
2042
|
-
private moveResourceDragPreview(
|
|
2043
|
-
clientXArg: number,
|
|
2044
|
-
clientYArg: number,
|
|
2045
|
-
gestureArg: IResourcePointerDragGesture,
|
|
2046
|
-
): void {
|
|
2047
|
-
gestureArg.source.style.setProperty(
|
|
2048
|
-
'--resource-drag-preview-x',
|
|
2049
|
-
`${clientXArg - gestureArg.grabOffsetX}px`,
|
|
2050
|
-
);
|
|
2051
|
-
gestureArg.source.style.setProperty(
|
|
2052
|
-
'--resource-drag-preview-y',
|
|
2053
|
-
`${clientYArg - gestureArg.grabOffsetY}px`,
|
|
2054
|
-
);
|
|
2055
|
-
}
|
|
2056
|
-
|
|
2057
|
-
private updateResourceDropTargetFromPointer(clientXArg: number, clientYArg: number): void {
|
|
2058
|
-
const gesture = this.pointerDragGesture;
|
|
2059
|
-
if (gesture?.type !== 'resource' || gesture.phase !== 'active') return;
|
|
2060
|
-
const hit = this.shadowRoot?.elementFromPoint(clientXArg, clientYArg);
|
|
2061
|
-
const target = hit?.closest<HTMLElement>('.item[data-session-id]');
|
|
2062
|
-
const sessionId = target?.dataset.sessionId ?? '';
|
|
2063
|
-
this.resourceDropTargetSessionId = (
|
|
2064
|
-
sessionId
|
|
2065
|
-
&& sessionId !== gesture.fromSessionId
|
|
2066
|
-
&& this.isResourceTargetEligible(gesture.resourceId, sessionId)
|
|
2067
|
-
) ? sessionId : '';
|
|
2068
|
-
}
|
|
2069
|
-
|
|
2070
1972
|
private setDropSlot(groupIdArg: string | null, indexArg: number): void {
|
|
2071
1973
|
const current = this.dropSlot;
|
|
2072
1974
|
if (current && current.groupId === groupIdArg && current.index === indexArg) return;
|
|
2073
|
-
const previousRects = this.
|
|
1975
|
+
const previousRects = this.captureRowRects();
|
|
2074
1976
|
const generation = ++this.reflowGeneration;
|
|
2075
1977
|
this.dropSlot = { groupId: groupIdArg, index: indexArg };
|
|
2076
|
-
void this.
|
|
1978
|
+
void this.animateRowReflow(previousRects, generation);
|
|
2077
1979
|
}
|
|
2078
1980
|
|
|
2079
1981
|
private clearDropSlot(): void {
|
|
2080
1982
|
if (!this.dropSlot) return;
|
|
2081
|
-
const previousRects = this.
|
|
1983
|
+
const previousRects = this.captureRowRects();
|
|
2082
1984
|
const generation = ++this.reflowGeneration;
|
|
2083
1985
|
this.dropSlot = undefined;
|
|
2084
|
-
void this.
|
|
1986
|
+
void this.animateRowReflow(previousRects, generation);
|
|
2085
1987
|
}
|
|
2086
1988
|
|
|
2087
1989
|
private updateDragAutoScrollVelocity(): void {
|
|
@@ -2134,14 +2036,7 @@ export class DeesHarnessSessionList extends DeesElement {
|
|
|
2134
2036
|
this.stopDragAutoScroll();
|
|
2135
2037
|
return;
|
|
2136
2038
|
}
|
|
2137
|
-
|
|
2138
|
-
this.updateResourceDropTargetFromPointer(
|
|
2139
|
-
this.dragAutoScrollClientX,
|
|
2140
|
-
this.dragAutoScrollClientY,
|
|
2141
|
-
);
|
|
2142
|
-
} else {
|
|
2143
|
-
this.updateDropSlotFromPointer();
|
|
2144
|
-
}
|
|
2039
|
+
this.updateDropSlotFromPointer();
|
|
2145
2040
|
this.dragAutoScrollFrame = requestAnimationFrame(this.runDragAutoScroll);
|
|
2146
2041
|
};
|
|
2147
2042
|
|
|
@@ -2165,6 +2060,7 @@ export class DeesHarnessSessionList extends DeesElement {
|
|
|
2165
2060
|
}
|
|
2166
2061
|
}
|
|
2167
2062
|
const selector = '[data-drop-group-id][data-drop-index][data-drop-kind]';
|
|
2063
|
+
const dragging = this.draggingItem;
|
|
2168
2064
|
let target: HTMLElement | undefined;
|
|
2169
2065
|
const offsets = this.dragAutoScrollVelocity === 0
|
|
2170
2066
|
? [0, -8, 8, -16, 16, -32, 32]
|
|
@@ -2176,7 +2072,11 @@ export class DeesHarnessSessionList extends DeesElement {
|
|
|
2176
2072
|
const candidate = hit?.closest<HTMLElement>(selector);
|
|
2177
2073
|
if (
|
|
2178
2074
|
candidate
|
|
2179
|
-
&&
|
|
2075
|
+
&& !(
|
|
2076
|
+
dragging
|
|
2077
|
+
&& candidate.dataset.itemKind === dragging.kind
|
|
2078
|
+
&& candidate.dataset.itemId === dragging.id
|
|
2079
|
+
)
|
|
2180
2080
|
&& Number(candidate.dataset.dropIndex) >= 0
|
|
2181
2081
|
) {
|
|
2182
2082
|
target = candidate;
|
|
@@ -2235,70 +2135,69 @@ export class DeesHarnessSessionList extends DeesElement {
|
|
|
2235
2135
|
}
|
|
2236
2136
|
|
|
2237
2137
|
private createMoveDetail(
|
|
2238
|
-
|
|
2239
|
-
|
|
2138
|
+
refArg: IHarnessSessionListItemRef,
|
|
2139
|
+
toGroupIdArg: string | null,
|
|
2240
2140
|
indexArg: number,
|
|
2241
|
-
|
|
2242
|
-
|
|
2243
|
-
const
|
|
2244
|
-
const
|
|
2245
|
-
const sourceSessions = this.sortableSessions(this.sessionsForGroup(sourceGroupId), false);
|
|
2246
|
-
const sourceIndex = sourceSessions.findIndex((session) => session.id === sessionIdArg);
|
|
2247
|
-
const compatibilityIndex = (
|
|
2248
|
-
sourceGroupId === groupIdArg
|
|
2249
|
-
&& sourceIndex >= 0
|
|
2250
|
-
&& indexArg > sourceIndex
|
|
2251
|
-
) ? indexArg + 1 : indexArg;
|
|
2141
|
+
sourceArg: THarnessItemMoveSource,
|
|
2142
|
+
): IHarnessItemMoveDetail {
|
|
2143
|
+
const targetItems = this.sortableItems(this.itemsForGroup(toGroupIdArg));
|
|
2144
|
+
const beforeItem = targetItems[Math.max(0, indexArg)];
|
|
2252
2145
|
return {
|
|
2253
|
-
|
|
2254
|
-
|
|
2255
|
-
|
|
2256
|
-
|
|
2146
|
+
item: itemRef(refArg),
|
|
2147
|
+
fromGroupId: this.groupIdForItem(refArg),
|
|
2148
|
+
toGroupId: toGroupIdArg,
|
|
2149
|
+
beforeItem: beforeItem ? itemRef(beforeItem) : null,
|
|
2150
|
+
source: sourceArg,
|
|
2257
2151
|
};
|
|
2258
2152
|
}
|
|
2259
2153
|
|
|
2154
|
+
private emitItemMove(detailArg: IHarnessItemMoveDetail): void {
|
|
2155
|
+
this.dispatchEvent(
|
|
2156
|
+
new CustomEvent('harness-item-move', { detail: detailArg, bubbles: true, composed: true }),
|
|
2157
|
+
);
|
|
2158
|
+
}
|
|
2159
|
+
|
|
2260
2160
|
private handleKeyboardMove(
|
|
2261
2161
|
eventArg: KeyboardEvent,
|
|
2262
|
-
|
|
2162
|
+
refArg: IHarnessSessionListItemRef,
|
|
2163
|
+
titleArg: string,
|
|
2263
2164
|
groupIdArg: string | null,
|
|
2264
2165
|
): void {
|
|
2265
2166
|
if (!['ArrowUp', 'ArrowDown', 'Home', 'End'].includes(eventArg.key)) return;
|
|
2266
2167
|
eventArg.preventDefault();
|
|
2267
2168
|
eventArg.stopPropagation();
|
|
2268
|
-
const
|
|
2269
|
-
const sourceIndex =
|
|
2169
|
+
const items = this.sortableItems(this.itemsForGroup(groupIdArg), false);
|
|
2170
|
+
const sourceIndex = items.findIndex((item) => sameItem(item, refArg));
|
|
2270
2171
|
if (sourceIndex < 0) return;
|
|
2271
|
-
const sectionIds: Array<string | null> = [...this.
|
|
2172
|
+
const sectionIds: Array<string | null> = [...this.effectiveGroups.map((group) => group.id), null];
|
|
2272
2173
|
const sectionIndex = sectionIds.indexOf(groupIdArg);
|
|
2273
2174
|
const adjacentGroupId = eventArg.key === 'ArrowUp' && sourceIndex === 0 && sectionIndex > 0
|
|
2274
2175
|
? sectionIds[sectionIndex - 1]
|
|
2275
2176
|
: eventArg.key === 'ArrowDown'
|
|
2276
|
-
&& sourceIndex ===
|
|
2177
|
+
&& sourceIndex === items.length - 1
|
|
2277
2178
|
&& sectionIndex >= 0
|
|
2278
2179
|
&& sectionIndex < sectionIds.length - 1
|
|
2279
2180
|
? sectionIds[sectionIndex + 1]
|
|
2280
2181
|
: undefined;
|
|
2281
2182
|
if (adjacentGroupId !== undefined) {
|
|
2282
|
-
const
|
|
2283
|
-
const targetIndex = eventArg.key === 'ArrowUp' ?
|
|
2284
|
-
const
|
|
2285
|
-
const detail: IHarnessSessionMoveDetail = {
|
|
2286
|
-
sessionId: sessionArg.id,
|
|
2287
|
-
groupId: adjacentGroupId,
|
|
2288
|
-
index: targetIndex,
|
|
2289
|
-
...(beforeSession ? { beforeSessionId: beforeSession.id } : {}),
|
|
2290
|
-
};
|
|
2183
|
+
const targetItems = this.sortableItems(this.itemsForGroup(adjacentGroupId), false);
|
|
2184
|
+
const targetIndex = eventArg.key === 'ArrowUp' ? targetItems.length : 0;
|
|
2185
|
+
const beforeItem = targetItems[targetIndex];
|
|
2291
2186
|
const groupName = adjacentGroupId === null
|
|
2292
2187
|
? 'Ungrouped'
|
|
2293
|
-
: this.
|
|
2294
|
-
this.moveAnnouncement = `Moved ${
|
|
2295
|
-
this.keyboardMoveFocus = {
|
|
2296
|
-
this.
|
|
2297
|
-
|
|
2298
|
-
|
|
2188
|
+
: this.effectiveGroups.find((group) => group.id === adjacentGroupId)?.name ?? adjacentGroupId;
|
|
2189
|
+
this.moveAnnouncement = `Moved ${titleArg} to ${groupName}.`;
|
|
2190
|
+
this.keyboardMoveFocus = { item: itemRef(refArg), groupId: adjacentGroupId };
|
|
2191
|
+
this.emitItemMove({
|
|
2192
|
+
item: itemRef(refArg),
|
|
2193
|
+
fromGroupId: groupIdArg,
|
|
2194
|
+
toGroupId: adjacentGroupId,
|
|
2195
|
+
beforeItem: beforeItem ? itemRef(beforeItem) : null,
|
|
2196
|
+
source: 'keyboard',
|
|
2197
|
+
});
|
|
2299
2198
|
return;
|
|
2300
2199
|
}
|
|
2301
|
-
const remaining =
|
|
2200
|
+
const remaining = items.filter((item) => !sameItem(item, refArg));
|
|
2302
2201
|
const targetIndex = eventArg.key === 'Home'
|
|
2303
2202
|
? 0
|
|
2304
2203
|
: eventArg.key === 'End'
|
|
@@ -2307,27 +2206,30 @@ export class DeesHarnessSessionList extends DeesElement {
|
|
|
2307
2206
|
? Math.max(0, sourceIndex - 1)
|
|
2308
2207
|
: Math.min(remaining.length, sourceIndex + 1);
|
|
2309
2208
|
if (targetIndex === sourceIndex) return;
|
|
2310
|
-
const
|
|
2311
|
-
|
|
2312
|
-
|
|
2313
|
-
|
|
2314
|
-
|
|
2315
|
-
|
|
2316
|
-
|
|
2317
|
-
|
|
2318
|
-
|
|
2319
|
-
|
|
2320
|
-
|
|
2321
|
-
|
|
2322
|
-
|
|
2323
|
-
|
|
2209
|
+
const beforeItem = remaining[targetIndex];
|
|
2210
|
+
this.moveAnnouncement = beforeItem
|
|
2211
|
+
? `Moved ${titleArg} before ${beforeItem.title}.`
|
|
2212
|
+
: `Moved ${titleArg} to the end.`;
|
|
2213
|
+
this.emitItemMove({
|
|
2214
|
+
item: itemRef(refArg),
|
|
2215
|
+
fromGroupId: groupIdArg,
|
|
2216
|
+
toGroupId: groupIdArg,
|
|
2217
|
+
beforeItem: beforeItem ? itemRef(beforeItem) : null,
|
|
2218
|
+
source: 'keyboard',
|
|
2219
|
+
});
|
|
2220
|
+
}
|
|
2221
|
+
|
|
2222
|
+
private rowKey(rowArg: HTMLElement): string {
|
|
2223
|
+
return `${rowArg.dataset.itemKind}:${rowArg.dataset.itemId}`;
|
|
2324
2224
|
}
|
|
2325
2225
|
|
|
2326
|
-
private
|
|
2226
|
+
private captureRowRects(): Map<string, DOMRect> {
|
|
2327
2227
|
const result = new Map<string, DOMRect>();
|
|
2328
|
-
|
|
2329
|
-
|
|
2330
|
-
|
|
2228
|
+
const draggingKey = this.draggingItem ? itemKey(this.draggingItem) : '';
|
|
2229
|
+
for (const row of this.shadowRoot?.querySelectorAll<HTMLElement>('[data-item-kind][data-item-id]') ?? []) {
|
|
2230
|
+
const key = this.rowKey(row);
|
|
2231
|
+
if (key === draggingKey) continue;
|
|
2232
|
+
result.set(key, row.getBoundingClientRect());
|
|
2331
2233
|
}
|
|
2332
2234
|
return result;
|
|
2333
2235
|
}
|
|
@@ -2335,14 +2237,8 @@ export class DeesHarnessSessionList extends DeesElement {
|
|
|
2335
2237
|
private restoreKeyboardMoveFocus(): void {
|
|
2336
2238
|
const pending = this.keyboardMoveFocus;
|
|
2337
2239
|
if (!pending) return;
|
|
2338
|
-
|
|
2339
|
-
|
|
2340
|
-
)?.id ?? null;
|
|
2341
|
-
if (currentGroupId !== pending.groupId) return;
|
|
2342
|
-
const item = Array.from(
|
|
2343
|
-
this.shadowRoot?.querySelectorAll<HTMLElement>('.item[data-session-id]') ?? [],
|
|
2344
|
-
).find((candidate) => candidate.dataset.sessionId === pending.sessionId);
|
|
2345
|
-
const handle = item?.querySelector<HTMLElement>('.dragHandle');
|
|
2240
|
+
if (this.groupIdForItem(pending.item) !== pending.groupId) return;
|
|
2241
|
+
const handle = this.rowElementFor(pending.item)?.querySelector<HTMLElement>('.dragHandle');
|
|
2346
2242
|
if (handle) {
|
|
2347
2243
|
handle.focus();
|
|
2348
2244
|
this.keyboardMoveFocus = undefined;
|
|
@@ -2357,35 +2253,52 @@ export class DeesHarnessSessionList extends DeesElement {
|
|
|
2357
2253
|
}
|
|
2358
2254
|
}
|
|
2359
2255
|
|
|
2360
|
-
|
|
2361
|
-
|
|
2362
|
-
const
|
|
2256
|
+
/** Known, de-duplicated targets the host allows; an absent entry means unrestricted. */
|
|
2257
|
+
private eligibleTargets(resourceIdArg: string): IHarnessSessionListItemRef[] | undefined {
|
|
2258
|
+
const declared = this.eligibleTargetsByResourceId[resourceIdArg];
|
|
2259
|
+
if (!declared) return undefined;
|
|
2260
|
+
const itemsByKey = this.itemsByKey;
|
|
2261
|
+
const result: IHarnessSessionListItemRef[] = [];
|
|
2363
2262
|
const seen = new Set<string>();
|
|
2364
|
-
for (const
|
|
2365
|
-
|
|
2366
|
-
|
|
2367
|
-
|
|
2263
|
+
for (const target of declared) {
|
|
2264
|
+
const key = target && itemKey(target);
|
|
2265
|
+
if (
|
|
2266
|
+
!key
|
|
2267
|
+
|| seen.has(key)
|
|
2268
|
+
|| !itemsByKey.has(key)
|
|
2269
|
+
|| sameItem(target, { kind: 'resource', id: resourceIdArg })
|
|
2270
|
+
) continue;
|
|
2271
|
+
seen.add(key);
|
|
2272
|
+
result.push(itemRef(target));
|
|
2368
2273
|
}
|
|
2369
2274
|
return result;
|
|
2370
2275
|
}
|
|
2371
2276
|
|
|
2372
|
-
private
|
|
2373
|
-
|
|
2374
|
-
|
|
2277
|
+
private isResourceTargetEligible(
|
|
2278
|
+
resourceIdArg: string,
|
|
2279
|
+
targetArg: IHarnessSessionListItemRef,
|
|
2280
|
+
): boolean {
|
|
2281
|
+
const eligible = this.eligibleTargets(resourceIdArg);
|
|
2282
|
+
return eligible === undefined || eligible.some((candidate) => sameItem(candidate, targetArg));
|
|
2283
|
+
}
|
|
2284
|
+
|
|
2285
|
+
private itemTitleFor(refArg: IHarnessSessionListItemRef): string {
|
|
2286
|
+
return this.itemsByKey.get(itemKey(refArg))?.title ?? refArg.id;
|
|
2375
2287
|
}
|
|
2376
2288
|
|
|
2377
|
-
private
|
|
2378
|
-
|
|
2289
|
+
private emitSession(nameArg: string, sessionArg: IHarnessSessionMeta): void {
|
|
2290
|
+
const detail: IHarnessSessionEventDetail = { session: sessionArg };
|
|
2291
|
+
this.dispatchEvent(new CustomEvent(nameArg, { detail, bubbles: true, composed: true }));
|
|
2379
2292
|
}
|
|
2380
2293
|
|
|
2381
2294
|
private emitResource(
|
|
2382
2295
|
nameArg: string,
|
|
2383
2296
|
resourceArg: IHarnessResourceMeta,
|
|
2384
|
-
|
|
2297
|
+
targetArg: IHarnessSessionListItemRef | null,
|
|
2385
2298
|
): void {
|
|
2386
2299
|
const detail: IHarnessResourceEventDetail = {
|
|
2387
2300
|
resource: resourceArg,
|
|
2388
|
-
|
|
2301
|
+
target: targetArg,
|
|
2389
2302
|
};
|
|
2390
2303
|
this.dispatchEvent(new CustomEvent(nameArg, { detail, bubbles: true, composed: true }));
|
|
2391
2304
|
}
|
|
@@ -2393,7 +2306,7 @@ export class DeesHarnessSessionList extends DeesElement {
|
|
|
2393
2306
|
private handleResourceContextKey(
|
|
2394
2307
|
eventArg: KeyboardEvent,
|
|
2395
2308
|
resourceArg: IHarnessResourceMeta,
|
|
2396
|
-
|
|
2309
|
+
targetArg: IHarnessSessionListItemRef | null,
|
|
2397
2310
|
): void {
|
|
2398
2311
|
if (!(eventArg.key === 'ContextMenu' || (eventArg.key === 'F10' && eventArg.shiftKey))) return;
|
|
2399
2312
|
eventArg.preventDefault();
|
|
@@ -2401,7 +2314,7 @@ export class DeesHarnessSessionList extends DeesElement {
|
|
|
2401
2314
|
this.emitResourceContext(
|
|
2402
2315
|
eventArg,
|
|
2403
2316
|
resourceArg,
|
|
2404
|
-
|
|
2317
|
+
targetArg,
|
|
2405
2318
|
'keyboard',
|
|
2406
2319
|
eventArg.currentTarget as HTMLElement,
|
|
2407
2320
|
);
|
|
@@ -2410,7 +2323,7 @@ export class DeesHarnessSessionList extends DeesElement {
|
|
|
2410
2323
|
private emitResourceContext(
|
|
2411
2324
|
eventArg: MouseEvent | KeyboardEvent,
|
|
2412
2325
|
resourceArg: IHarnessResourceMeta,
|
|
2413
|
-
|
|
2326
|
+
targetArg: IHarnessSessionListItemRef | null,
|
|
2414
2327
|
sourceArg: THarnessResourceContextSource,
|
|
2415
2328
|
focusTargetArg: HTMLElement,
|
|
2416
2329
|
): void {
|
|
@@ -2425,7 +2338,7 @@ export class DeesHarnessSessionList extends DeesElement {
|
|
|
2425
2338
|
].find((className) => focusTargetArg.classList.contains(className)) ?? 'resourcePrimary';
|
|
2426
2339
|
const detail: IHarnessResourceContextDetail = {
|
|
2427
2340
|
resource: resourceArg,
|
|
2428
|
-
|
|
2341
|
+
target: targetArg,
|
|
2429
2342
|
clientX: sourceArg === 'pointer' && eventArg instanceof MouseEvent
|
|
2430
2343
|
? eventArg.clientX
|
|
2431
2344
|
: rect.left + rect.width / 2,
|
|
@@ -2434,9 +2347,9 @@ export class DeesHarnessSessionList extends DeesElement {
|
|
|
2434
2347
|
: rect.bottom,
|
|
2435
2348
|
source: sourceArg,
|
|
2436
2349
|
originalEvent: eventArg,
|
|
2437
|
-
requestAssociation: (
|
|
2350
|
+
requestAssociation: (toTargetArg) => this.requestResourceAssociation(
|
|
2438
2351
|
resourceArg.id,
|
|
2439
|
-
|
|
2352
|
+
toTargetArg,
|
|
2440
2353
|
sourceArg === 'keyboard' ? 'keyboard' : 'action',
|
|
2441
2354
|
),
|
|
2442
2355
|
close: () => this.closeResourceContext(contextGeneration),
|
|
@@ -2462,9 +2375,7 @@ export class DeesHarnessSessionList extends DeesElement {
|
|
|
2462
2375
|
focusTarget.focus();
|
|
2463
2376
|
return;
|
|
2464
2377
|
}
|
|
2465
|
-
const row =
|
|
2466
|
-
this.shadowRoot?.querySelectorAll<HTMLElement>('.resourceRow[data-resource-id]') ?? [],
|
|
2467
|
-
).find((candidate) => candidate.dataset.resourceId === resourceId);
|
|
2378
|
+
const row = this.rowElementFor({ kind: 'resource', id: resourceId });
|
|
2468
2379
|
row?.querySelector<HTMLElement>(`.${focusClass || 'resourcePrimary'}`)?.focus();
|
|
2469
2380
|
});
|
|
2470
2381
|
}
|
|
@@ -2483,28 +2394,22 @@ export class DeesHarnessSessionList extends DeesElement {
|
|
|
2483
2394
|
this.pendingResourceAssociationRequest = undefined;
|
|
2484
2395
|
return;
|
|
2485
2396
|
}
|
|
2486
|
-
const
|
|
2487
|
-
if (
|
|
2488
|
-
if (
|
|
2489
|
-
const
|
|
2490
|
-
|
|
2491
|
-
: 'unattached';
|
|
2492
|
-
this.moveAnnouncement = `Association request for ${pending.resourceTitle} was not applied; controlled association is ${controlledSession}.`;
|
|
2397
|
+
const currentTarget = this.acceptedResourceAssociations.get(pending.resourceId) ?? null;
|
|
2398
|
+
if (!sameItem(currentTarget, pending.toTarget) && !(currentTarget === null && pending.toTarget === null)) {
|
|
2399
|
+
if (!sameItem(currentTarget, pending.fromTarget) && !(currentTarget === null && pending.fromTarget === null)) {
|
|
2400
|
+
const controlledTitle = currentTarget ? this.itemTitleFor(currentTarget) : 'unattached';
|
|
2401
|
+
this.moveAnnouncement = `Association request for ${pending.resourceTitle} was not applied; controlled association is ${controlledTitle}.`;
|
|
2493
2402
|
this.pendingResourceAssociationRequest = undefined;
|
|
2494
2403
|
}
|
|
2495
2404
|
return;
|
|
2496
2405
|
}
|
|
2497
|
-
const
|
|
2498
|
-
|
|
2499
|
-
: '';
|
|
2500
|
-
const toSession = pending.toSessionId
|
|
2501
|
-
? this.sessionsById.get(pending.toSessionId)?.title ?? pending.toSessionId
|
|
2502
|
-
: '';
|
|
2406
|
+
const fromTitle = pending.fromTarget ? this.itemTitleFor(pending.fromTarget) : '';
|
|
2407
|
+
const toTitle = pending.toTarget ? this.itemTitleFor(pending.toTarget) : '';
|
|
2503
2408
|
this.moveAnnouncement = pending.intent === 'attach'
|
|
2504
|
-
? `${pending.resourceTitle} is now attached to ${
|
|
2409
|
+
? `${pending.resourceTitle} is now attached to ${toTitle}.`
|
|
2505
2410
|
: pending.intent === 'detach'
|
|
2506
|
-
? `${pending.resourceTitle} is now detached from ${
|
|
2507
|
-
: `${pending.resourceTitle} is now reassigned from ${
|
|
2411
|
+
? `${pending.resourceTitle} is now detached from ${fromTitle}.`
|
|
2412
|
+
: `${pending.resourceTitle} is now reassigned from ${fromTitle} to ${toTitle}.`;
|
|
2508
2413
|
this.pendingResourceAssociationRequest = undefined;
|
|
2509
2414
|
}
|
|
2510
2415
|
|
|
@@ -2513,12 +2418,8 @@ export class DeesHarnessSessionList extends DeesElement {
|
|
|
2513
2418
|
completedArg: boolean,
|
|
2514
2419
|
): string {
|
|
2515
2420
|
const resourceTitle = detailArg.resource.title || detailArg.resource.id;
|
|
2516
|
-
const fromTitle = detailArg.
|
|
2517
|
-
|
|
2518
|
-
: '';
|
|
2519
|
-
const toTitle = detailArg.toSessionId
|
|
2520
|
-
? this.sessionsById.get(detailArg.toSessionId)?.title ?? detailArg.toSessionId
|
|
2521
|
-
: '';
|
|
2421
|
+
const fromTitle = detailArg.fromTarget ? this.itemTitleFor(detailArg.fromTarget) : '';
|
|
2422
|
+
const toTitle = detailArg.toTarget ? this.itemTitleFor(detailArg.toTarget) : '';
|
|
2522
2423
|
const verb = completedArg ? 'completed' : 'requested';
|
|
2523
2424
|
return detailArg.intent === 'attach'
|
|
2524
2425
|
? `Attach ${verb} for ${resourceTitle} to ${toTitle}.`
|
|
@@ -2527,7 +2428,7 @@ export class DeesHarnessSessionList extends DeesElement {
|
|
|
2527
2428
|
: `Reassign ${verb} for ${resourceTitle} from ${fromTitle} to ${toTitle}.`;
|
|
2528
2429
|
}
|
|
2529
2430
|
|
|
2530
|
-
private async
|
|
2431
|
+
private async animateRowReflow(
|
|
2531
2432
|
previousRectsArg: Map<string, DOMRect>,
|
|
2532
2433
|
generationArg: number,
|
|
2533
2434
|
): Promise<void> {
|
|
@@ -2538,15 +2439,16 @@ export class DeesHarnessSessionList extends DeesElement {
|
|
|
2538
2439
|
|| globalThis.matchMedia?.('(prefers-reduced-motion: reduce)').matches
|
|
2539
2440
|
) return;
|
|
2540
2441
|
this.cancelReflowAnimations();
|
|
2541
|
-
|
|
2542
|
-
|
|
2543
|
-
|
|
2544
|
-
|
|
2442
|
+
const draggingKey = this.draggingItem ? itemKey(this.draggingItem) : '';
|
|
2443
|
+
for (const row of this.shadowRoot?.querySelectorAll<HTMLElement>('[data-item-kind][data-item-id]') ?? []) {
|
|
2444
|
+
const key = this.rowKey(row);
|
|
2445
|
+
if (key === draggingKey) continue;
|
|
2446
|
+
const previous = previousRectsArg.get(key);
|
|
2545
2447
|
if (!previous) continue;
|
|
2546
|
-
const current =
|
|
2448
|
+
const current = row.getBoundingClientRect();
|
|
2547
2449
|
const deltaY = previous.top - current.top;
|
|
2548
2450
|
if (Math.abs(deltaY) < 0.5) continue;
|
|
2549
|
-
const animation =
|
|
2451
|
+
const animation = row.animate(
|
|
2550
2452
|
[
|
|
2551
2453
|
{ transform: `translateY(${deltaY}px)` },
|
|
2552
2454
|
{ transform: 'translateY(0)' },
|
|
@@ -2568,24 +2470,8 @@ export class DeesHarnessSessionList extends DeesElement {
|
|
|
2568
2470
|
private finishPointerDrag(commitArg: boolean): void {
|
|
2569
2471
|
const gesture = this.pointerDragGesture;
|
|
2570
2472
|
if (!gesture) return;
|
|
2571
|
-
const
|
|
2572
|
-
gesture.
|
|
2573
|
-
&& commitArg
|
|
2574
|
-
&& gesture.phase === 'active'
|
|
2575
|
-
&& this.dropSlot
|
|
2576
|
-
)
|
|
2577
|
-
? this.createMoveDetail(gesture.sessionId, this.dropSlot.groupId, this.dropSlot.index)
|
|
2578
|
-
: undefined;
|
|
2579
|
-
const resourceRequest = (
|
|
2580
|
-
gesture.type === 'resource'
|
|
2581
|
-
&& commitArg
|
|
2582
|
-
&& gesture.phase === 'active'
|
|
2583
|
-
&& this.resourceDropTargetSessionId
|
|
2584
|
-
)
|
|
2585
|
-
? {
|
|
2586
|
-
resourceId: gesture.resourceId,
|
|
2587
|
-
toSessionId: this.resourceDropTargetSessionId,
|
|
2588
|
-
}
|
|
2473
|
+
const moveDetail = (commitArg && gesture.phase === 'active' && this.dropSlot)
|
|
2474
|
+
? this.createMoveDetail(gesture.item, this.dropSlot.groupId, this.dropSlot.index, 'pointer')
|
|
2589
2475
|
: undefined;
|
|
2590
2476
|
this.pointerDragGesture = undefined;
|
|
2591
2477
|
window.removeEventListener('keydown', this.handleDragKeyDown, true);
|
|
@@ -2595,19 +2481,14 @@ export class DeesHarnessSessionList extends DeesElement {
|
|
|
2595
2481
|
this.cancelReflowAnimations();
|
|
2596
2482
|
if (gesture.source.matches(':popover-open')) gesture.source.hidePopover();
|
|
2597
2483
|
gesture.source.removeAttribute('popover');
|
|
2598
|
-
gesture.source.classList.remove('dragging'
|
|
2484
|
+
gesture.source.classList.remove('dragging');
|
|
2599
2485
|
for (const property of [
|
|
2600
2486
|
'--drag-preview-width',
|
|
2601
2487
|
'--drag-preview-height',
|
|
2602
2488
|
'--drag-preview-x',
|
|
2603
2489
|
'--drag-preview-y',
|
|
2604
|
-
'--resource-drag-preview-width',
|
|
2605
|
-
'--resource-drag-preview-x',
|
|
2606
|
-
'--resource-drag-preview-y',
|
|
2607
2490
|
]) gesture.source.style.removeProperty(property);
|
|
2608
|
-
this.
|
|
2609
|
-
this.draggingResourceId = '';
|
|
2610
|
-
this.resourceDropTargetSessionId = '';
|
|
2491
|
+
this.draggingItem = undefined;
|
|
2611
2492
|
this.draggedItemHeight = 0;
|
|
2612
2493
|
this.dropSlot = undefined;
|
|
2613
2494
|
try {
|
|
@@ -2617,21 +2498,6 @@ export class DeesHarnessSessionList extends DeesElement {
|
|
|
2617
2498
|
} catch {
|
|
2618
2499
|
// Capture may already be released or the handle may have disconnected.
|
|
2619
2500
|
}
|
|
2620
|
-
if (
|
|
2621
|
-
this.dispatchEvent(
|
|
2622
|
-
new CustomEvent('harness-session-move', {
|
|
2623
|
-
detail: sessionMoveDetail,
|
|
2624
|
-
bubbles: true,
|
|
2625
|
-
composed: true,
|
|
2626
|
-
}),
|
|
2627
|
-
);
|
|
2628
|
-
}
|
|
2629
|
-
if (resourceRequest) {
|
|
2630
|
-
this.requestResourceAssociation(resourceRequest.resourceId, resourceRequest.toSessionId, 'drop');
|
|
2631
|
-
}
|
|
2632
|
-
}
|
|
2633
|
-
|
|
2634
|
-
private emit(name: string, session: IHarnessSessionMeta): void {
|
|
2635
|
-
this.dispatchEvent(new CustomEvent(name, { detail: { session }, bubbles: true, composed: true }));
|
|
2501
|
+
if (moveDetail) this.emitItemMove(moveDetail);
|
|
2636
2502
|
}
|
|
2637
2503
|
}
|