@design.estate/dees-catalog 9.8.0 → 11.0.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 +5626 -5343
- package/dist_bundle/bundle.js.map +1 -1
- package/dist_bundle/third-party-notices.md +783 -10
- package/dist_ts_web/00_commitinfo_data.js +2 -2
- package/dist_ts_web/elements/00actionerror.d.ts +32 -0
- package/dist_ts_web/elements/00actionerror.js +45 -0
- package/dist_ts_web/elements/00group-appui/dees-appui-tabs/dees-appui-tabs.d.ts +16 -0
- package/dist_ts_web/elements/00group-appui/dees-appui-tabs/dees-appui-tabs.js +40 -3
- package/dist_ts_web/elements/00group-dataview/dees-table/dees-table.d.ts +7 -0
- package/dist_ts_web/elements/00group-dataview/dees-table/dees-table.js +17 -17
- package/dist_ts_web/elements/00group-feedback/dees-hint/dees-hint.d.ts +14 -3
- package/dist_ts_web/elements/00group-feedback/dees-hint/dees-hint.demo.js +23 -4
- package/dist_ts_web/elements/00group-feedback/dees-hint/dees-hint.js +77 -5
- 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 +89 -51
- package/dist_ts_web/elements/00group-harness/dees-harness-session-list/dees-harness-session-list.js +528 -662
- 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 +55 -20
- package/dist_ts_web/elements/00group-input/dees-input-text/dees-input-text.d.ts +23 -3
- package/dist_ts_web/elements/00group-input/dees-input-text/dees-input-text.js +71 -11
- package/dist_ts_web/elements/00group-input/index.d.ts +0 -1
- package/dist_ts_web/elements/00group-input/index.js +1 -2
- package/dist_ts_web/elements/00group-overlay/dees-modal/dees-modal.d.ts +7 -0
- package/dist_ts_web/elements/00group-overlay/dees-modal/dees-modal.js +13 -3
- package/dist_ts_web/elements/index.d.ts +1 -2
- package/dist_ts_web/elements/index.js +2 -4
- package/dist_ts_web/services/DeesServiceLibLoader.d.ts +7 -0
- package/dist_ts_web/services/DeesServiceLibLoader.js +45 -22
- package/package.json +3 -4
- package/readme.md +105 -18
- package/scripts/check-third-party-notices.mjs +261 -0
- package/third-party-notices.md +783 -10
- package/ts_web/00_commitinfo_data.ts +1 -1
- package/ts_web/elements/00actionerror.ts +66 -0
- package/ts_web/elements/00group-appui/dees-appui-tabs/dees-appui-tabs.ts +38 -2
- package/ts_web/elements/00group-dataview/dees-table/dees-table.ts +18 -16
- package/ts_web/elements/00group-feedback/dees-hint/dees-hint.demo.ts +22 -3
- package/ts_web/elements/00group-feedback/dees-hint/dees-hint.ts +71 -3
- package/ts_web/elements/00group-harness/dees-harness-session-list/dees-harness-session-list.demo.ts +99 -49
- package/ts_web/elements/00group-harness/dees-harness-session-list/dees-harness-session-list.ts +607 -752
- package/ts_web/elements/00group-harness/dees-terminal-view/dees-terminal-view.ts +196 -24
- package/ts_web/elements/00group-harness/interfaces.ts +59 -19
- package/ts_web/elements/00group-input/dees-input-text/dees-input-text.ts +73 -13
- package/ts_web/elements/00group-input/index.ts +0 -1
- package/ts_web/elements/00group-overlay/dees-modal/dees-modal.ts +13 -2
- package/ts_web/elements/index.ts +1 -4
- package/ts_web/services/DeesServiceLibLoader.ts +150 -102
- package/.smartconfig.json +0 -86
- package/dist_ts_web/elements/00group-input/dees-input-searchselect/dees-input-searchselect.d.ts +0 -1
- package/dist_ts_web/elements/00group-input/dees-input-searchselect/dees-input-searchselect.js +0 -2
- package/dist_ts_web/elements/00group-input/dees-input-searchselect/index.d.ts +0 -1
- package/dist_ts_web/elements/00group-input/dees-input-searchselect/index.js +0 -2
- package/dist_ts_web/elements/dees-search/dees-search.d.ts +0 -1
- package/dist_ts_web/elements/dees-search/dees-search.js +0 -2
- package/dist_ts_web/elements/dees-search/index.d.ts +0 -1
- package/dist_ts_web/elements/dees-search/index.js +0 -2
- package/dist_ts_web/elements/dees-tooltip/dees-tooltip.d.ts +0 -1
- package/dist_ts_web/elements/dees-tooltip/dees-tooltip.js +0 -2
- package/dist_ts_web/elements/dees-tooltip/index.d.ts +0 -1
- package/dist_ts_web/elements/dees-tooltip/index.js +0 -2
- package/ts_web/elements/00group-input/dees-input-searchselect/dees-input-searchselect.ts +0 -0
- package/ts_web/elements/00group-input/dees-input-searchselect/index.ts +0 -1
- package/ts_web/elements/dees-search/dees-search.ts +0 -0
- package/ts_web/elements/dees-search/index.ts +0 -1
- package/ts_web/elements/dees-tooltip/dees-tooltip.ts +0 -1
- package/ts_web/elements/dees-tooltip/index.ts +0 -1
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;
|
|
@@ -592,75 +608,31 @@ export class DeesHarnessSessionList extends DeesElement {
|
|
|
592
608
|
font-size: var(--dees-font-caption2-size, 10px);
|
|
593
609
|
}
|
|
594
610
|
|
|
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
|
-
|
|
611
|
+
/* Resource rows are peers of session cards in the same ordered section. */
|
|
634
612
|
.resourceRow {
|
|
613
|
+
position: relative;
|
|
635
614
|
display: grid;
|
|
636
|
-
grid-template-columns:
|
|
615
|
+
grid-template-columns: minmax(0, 1fr) 38px;
|
|
637
616
|
min-width: 0;
|
|
638
617
|
overflow: hidden;
|
|
639
618
|
border: 1px solid var(--dees-color-border-subtle);
|
|
640
619
|
border-radius: var(--dees-radius-lg);
|
|
620
|
+
corner-shape: var(--dees-corner-shape);
|
|
641
621
|
background: color-mix(in srgb, var(--dees-color-bg-secondary) 76%, transparent);
|
|
642
622
|
color: var(--dees-color-text-primary);
|
|
643
623
|
box-shadow: var(--dees-shadow-xs);
|
|
644
624
|
box-sizing: border-box;
|
|
645
625
|
}
|
|
646
626
|
|
|
627
|
+
.resourceRow.draggable {
|
|
628
|
+
grid-template-columns: 26px minmax(0, 1fr) 38px;
|
|
629
|
+
}
|
|
630
|
+
|
|
647
631
|
.resourceRow.selected {
|
|
648
632
|
border-color: var(--dees-color-accent-primary);
|
|
649
633
|
background: color-mix(in srgb, var(--dees-color-accent-primary) 10%, var(--dees-color-bg-secondary));
|
|
650
634
|
}
|
|
651
635
|
|
|
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
636
|
.resourceDragHandle,
|
|
665
637
|
.resourcePrimary,
|
|
666
638
|
.resourceActions {
|
|
@@ -672,22 +644,7 @@ export class DeesHarnessSessionList extends DeesElement {
|
|
|
672
644
|
}
|
|
673
645
|
|
|
674
646
|
.resourceDragHandle {
|
|
675
|
-
display: grid;
|
|
676
|
-
place-items: center;
|
|
677
647
|
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
648
|
}
|
|
692
649
|
|
|
693
650
|
.resourcePrimary {
|
|
@@ -741,6 +698,29 @@ export class DeesHarnessSessionList extends DeesElement {
|
|
|
741
698
|
white-space: nowrap;
|
|
742
699
|
}
|
|
743
700
|
|
|
701
|
+
/* Decorative marker for the controlled association; never interactive. */
|
|
702
|
+
.resourceAttachment {
|
|
703
|
+
display: flex;
|
|
704
|
+
min-width: 0;
|
|
705
|
+
align-items: center;
|
|
706
|
+
gap: 4px;
|
|
707
|
+
margin-top: 2px;
|
|
708
|
+
color: var(--dees-color-link);
|
|
709
|
+
font-size: var(--dees-font-caption2-size, 10px);
|
|
710
|
+
font-weight: 600;
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
.resourceAttachment dees-icon {
|
|
714
|
+
flex: 0 0 auto;
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
.resourceAttachmentName {
|
|
718
|
+
min-width: 0;
|
|
719
|
+
overflow: hidden;
|
|
720
|
+
text-overflow: ellipsis;
|
|
721
|
+
white-space: nowrap;
|
|
722
|
+
}
|
|
723
|
+
|
|
744
724
|
.resourceActions {
|
|
745
725
|
display: grid;
|
|
746
726
|
place-items: center;
|
|
@@ -778,7 +758,7 @@ export class DeesHarnessSessionList extends DeesElement {
|
|
|
778
758
|
.emptyDropZone {
|
|
779
759
|
display: grid;
|
|
780
760
|
place-items: center;
|
|
781
|
-
|
|
761
|
+
overflow: hidden;
|
|
782
762
|
border: 1px dashed color-mix(in srgb, var(--dees-color-accent-primary) 68%, transparent);
|
|
783
763
|
border-radius: var(--dees-radius-xl);
|
|
784
764
|
background: color-mix(in srgb, var(--dees-color-accent-primary) 8%, transparent);
|
|
@@ -786,9 +766,14 @@ export class DeesHarnessSessionList extends DeesElement {
|
|
|
786
766
|
box-sizing: border-box;
|
|
787
767
|
}
|
|
788
768
|
|
|
769
|
+
/* The placeholder matches the dragged row exactly; only the empty zone has a floor. */
|
|
770
|
+
.emptyDropZone {
|
|
771
|
+
min-height: 46px;
|
|
772
|
+
}
|
|
773
|
+
|
|
789
774
|
.dropPlaceholder::after,
|
|
790
775
|
.emptyDropZone::after {
|
|
791
|
-
content: 'Drop
|
|
776
|
+
content: 'Drop here';
|
|
792
777
|
font-size: var(--dees-font-caption2-size, 10px);
|
|
793
778
|
font-weight: 650;
|
|
794
779
|
letter-spacing: 0.02em;
|
|
@@ -931,6 +916,10 @@ export class DeesHarnessSessionList extends DeesElement {
|
|
|
931
916
|
.expandToggle { margin: 0 3px 0 0; }
|
|
932
917
|
|
|
933
918
|
.resourceRow {
|
|
919
|
+
grid-template-columns: minmax(0, 1fr) 44px;
|
|
920
|
+
}
|
|
921
|
+
|
|
922
|
+
.resourceRow.draggable {
|
|
934
923
|
grid-template-columns: 44px minmax(0, 1fr) 44px;
|
|
935
924
|
}
|
|
936
925
|
|
|
@@ -947,10 +936,6 @@ export class DeesHarnessSessionList extends DeesElement {
|
|
|
947
936
|
}
|
|
948
937
|
|
|
949
938
|
@media (max-width: 280px) {
|
|
950
|
-
.resourceChildren {
|
|
951
|
-
margin-left: 8px;
|
|
952
|
-
}
|
|
953
|
-
|
|
954
939
|
.resourcePreview {
|
|
955
940
|
display: none;
|
|
956
941
|
}
|
|
@@ -958,10 +943,6 @@ export class DeesHarnessSessionList extends DeesElement {
|
|
|
958
943
|
`,
|
|
959
944
|
];
|
|
960
945
|
|
|
961
|
-
private get sessionsById(): Map<string, IHarnessSessionMeta> {
|
|
962
|
-
return new Map(this.sessions.map((session) => [session.id, session]));
|
|
963
|
-
}
|
|
964
|
-
|
|
965
946
|
private get resourcesById(): Map<string, IHarnessResourceMeta> {
|
|
966
947
|
const result = new Map<string, IHarnessResourceMeta>();
|
|
967
948
|
for (const resource of this.resources) {
|
|
@@ -971,18 +952,23 @@ export class DeesHarnessSessionList extends DeesElement {
|
|
|
971
952
|
return result;
|
|
972
953
|
}
|
|
973
954
|
|
|
974
|
-
/**
|
|
975
|
-
|
|
955
|
+
/**
|
|
956
|
+
* First valid record wins; unknown records, self-references and duplicates
|
|
957
|
+
* remain untouched but are not projected. Keyed by resource id.
|
|
958
|
+
*/
|
|
959
|
+
private get acceptedResourceAssociations(): Map<string, IHarnessSessionListItemRef> {
|
|
976
960
|
const resourcesById = this.resourcesById;
|
|
977
|
-
const
|
|
978
|
-
const result = new Map<string,
|
|
961
|
+
const itemsByKey = this.itemsByKey;
|
|
962
|
+
const result = new Map<string, IHarnessSessionListItemRef>();
|
|
979
963
|
for (const association of this.resourceAssociations) {
|
|
980
964
|
if (
|
|
981
|
-
!
|
|
982
|
-
|| !
|
|
965
|
+
!association.target
|
|
966
|
+
|| !resourcesById.has(association.resourceId)
|
|
967
|
+
|| !itemsByKey.has(itemKey(association.target))
|
|
968
|
+
|| sameItem(association.target, { kind: 'resource', id: association.resourceId })
|
|
983
969
|
|| result.has(association.resourceId)
|
|
984
970
|
) continue;
|
|
985
|
-
result.set(association.resourceId, association.
|
|
971
|
+
result.set(association.resourceId, itemRef(association.target));
|
|
986
972
|
}
|
|
987
973
|
return result;
|
|
988
974
|
}
|
|
@@ -991,6 +977,92 @@ export class DeesHarnessSessionList extends DeesElement {
|
|
|
991
977
|
return [...this.resourcesById.values()];
|
|
992
978
|
}
|
|
993
979
|
|
|
980
|
+
/** Sessions and resources as one pool of orderable items, before grouping and search. */
|
|
981
|
+
private get acceptedItems(): TListItem[] {
|
|
982
|
+
const result: TListItem[] = [];
|
|
983
|
+
const seenSessionIds = new Set<string>();
|
|
984
|
+
for (const session of this.sessions) {
|
|
985
|
+
if (!session.id || seenSessionIds.has(session.id)) continue;
|
|
986
|
+
seenSessionIds.add(session.id);
|
|
987
|
+
result.push({
|
|
988
|
+
kind: 'session',
|
|
989
|
+
key: `session:${session.id}`,
|
|
990
|
+
id: session.id,
|
|
991
|
+
title: session.title || session.id,
|
|
992
|
+
sortable: session.draggable !== false,
|
|
993
|
+
timestamp: session.updatedAt ?? session.createdAt ?? 0,
|
|
994
|
+
session,
|
|
995
|
+
});
|
|
996
|
+
}
|
|
997
|
+
for (const resource of this.resourcesById.values()) {
|
|
998
|
+
result.push({
|
|
999
|
+
kind: 'resource',
|
|
1000
|
+
key: `resource:${resource.id}`,
|
|
1001
|
+
id: resource.id,
|
|
1002
|
+
title: resource.title || resource.id,
|
|
1003
|
+
sortable: resource.draggable !== false,
|
|
1004
|
+
timestamp: resource.updatedAt ?? resource.createdAt ?? 0,
|
|
1005
|
+
resource,
|
|
1006
|
+
});
|
|
1007
|
+
}
|
|
1008
|
+
return result;
|
|
1009
|
+
}
|
|
1010
|
+
|
|
1011
|
+
private get itemsByKey(): Map<string, TListItem> {
|
|
1012
|
+
return new Map(this.acceptedItems.map((item) => [item.key, item]));
|
|
1013
|
+
}
|
|
1014
|
+
|
|
1015
|
+
/** Groups only organize the list while grouping is enabled. */
|
|
1016
|
+
private get effectiveGroups(): IHarnessSessionGroup[] {
|
|
1017
|
+
return this.enableGrouping ? this.groups : [];
|
|
1018
|
+
}
|
|
1019
|
+
|
|
1020
|
+
private get groupedItemKeys(): Set<string> {
|
|
1021
|
+
const keys = new Set<string>();
|
|
1022
|
+
for (const group of this.effectiveGroups) {
|
|
1023
|
+
for (const ref of group.itemIds) keys.add(itemKey(ref));
|
|
1024
|
+
}
|
|
1025
|
+
return keys;
|
|
1026
|
+
}
|
|
1027
|
+
|
|
1028
|
+
private itemsForGroup(groupIdArg: string | null): TListItem[] {
|
|
1029
|
+
if (groupIdArg === null) return this.ungroupedItems;
|
|
1030
|
+
const group = this.effectiveGroups.find((candidate) => candidate.id === groupIdArg);
|
|
1031
|
+
if (!group) return [];
|
|
1032
|
+
const byKey = this.itemsByKey;
|
|
1033
|
+
const seen = new Set<string>();
|
|
1034
|
+
const result: TListItem[] = [];
|
|
1035
|
+
for (const ref of group.itemIds) {
|
|
1036
|
+
const key = itemKey(ref);
|
|
1037
|
+
const item = byKey.get(key);
|
|
1038
|
+
if (!item || seen.has(key)) continue;
|
|
1039
|
+
seen.add(key);
|
|
1040
|
+
result.push(item);
|
|
1041
|
+
}
|
|
1042
|
+
return result;
|
|
1043
|
+
}
|
|
1044
|
+
|
|
1045
|
+
/** Pinned entries first, then explicitly ordered items, then unknown items by recency. */
|
|
1046
|
+
private get ungroupedItems(): TListItem[] {
|
|
1047
|
+
const grouped = this.groupedItemKeys;
|
|
1048
|
+
const available = this.acceptedItems.filter((item) => !grouped.has(item.key));
|
|
1049
|
+
const pinned = available.filter((item) => !item.sortable);
|
|
1050
|
+
const sortableByKey = new Map(
|
|
1051
|
+
available.filter((item) => item.sortable).map((item) => [item.key, item]),
|
|
1052
|
+
);
|
|
1053
|
+
const ordered: TListItem[] = [];
|
|
1054
|
+
for (const ref of this.ungroupedItemIds) {
|
|
1055
|
+
const item = sortableByKey.get(itemKey(ref));
|
|
1056
|
+
if (!item) continue;
|
|
1057
|
+
ordered.push(item);
|
|
1058
|
+
sortableByKey.delete(item.key);
|
|
1059
|
+
}
|
|
1060
|
+
const unknown = [...sortableByKey.values()].sort(
|
|
1061
|
+
(left, right) => right.timestamp - left.timestamp,
|
|
1062
|
+
);
|
|
1063
|
+
return [...pinned, ...ordered, ...unknown];
|
|
1064
|
+
}
|
|
1065
|
+
|
|
994
1066
|
private resourceMatchesSearch(resourceArg: IHarnessResourceMeta, queryArg: string): boolean {
|
|
995
1067
|
return [resourceArg.id, resourceArg.kind, resourceArg.title, resourceArg.preview]
|
|
996
1068
|
.filter(Boolean)
|
|
@@ -1008,71 +1080,45 @@ export class DeesHarnessSessionList extends DeesElement {
|
|
|
1008
1080
|
.includes(queryArg);
|
|
1009
1081
|
}
|
|
1010
1082
|
|
|
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));
|
|
1083
|
+
private itemMatchesSearch(itemArg: TListItem, queryArg: string): boolean {
|
|
1084
|
+
return itemArg.kind === 'session'
|
|
1085
|
+
? this.sessionMatchesSearch(itemArg.session, queryArg)
|
|
1086
|
+
: this.resourceMatchesSearch(itemArg.resource, queryArg);
|
|
1019
1087
|
}
|
|
1020
1088
|
|
|
1021
|
-
|
|
1022
|
-
|
|
1089
|
+
/** Search flattens the list: sessions and resources match as peers, newest first. */
|
|
1090
|
+
private get searchItems(): TListItem[] {
|
|
1023
1091
|
const query = this.search.trim().toLowerCase();
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
)
|
|
1092
|
+
const sorted = [...this.acceptedItems].sort(
|
|
1093
|
+
(left, right) => right.timestamp - left.timestamp,
|
|
1094
|
+
);
|
|
1095
|
+
if (!query) return sorted;
|
|
1096
|
+
return sorted.filter((item) => this.itemMatchesSearch(item, query));
|
|
1028
1097
|
}
|
|
1029
1098
|
|
|
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;
|
|
1099
|
+
private get searchActive(): boolean {
|
|
1100
|
+
return this.search.trim().length > 0;
|
|
1036
1101
|
}
|
|
1037
1102
|
|
|
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];
|
|
1103
|
+
private sortableItems(itemsArg: TListItem[], excludeDraggingArg = true): TListItem[] {
|
|
1104
|
+
return itemsArg.filter((item) => (
|
|
1105
|
+
item.sortable
|
|
1106
|
+
&& (!excludeDraggingArg || !sameItem(this.draggingItem, item))
|
|
1107
|
+
));
|
|
1058
1108
|
}
|
|
1059
1109
|
|
|
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
|
-
);
|
|
1110
|
+
private groupIdForItem(refArg: IHarnessSessionListItemRef): string | null {
|
|
1111
|
+
return this.effectiveGroups.find(
|
|
1112
|
+
(group) => group.itemIds.some((candidate) => sameItem(candidate, refArg)),
|
|
1113
|
+
)?.id ?? null;
|
|
1114
|
+
}
|
|
1115
|
+
|
|
1116
|
+
private rowElementFor(refArg: IHarnessSessionListItemRef): HTMLElement | undefined {
|
|
1117
|
+
return Array.from(
|
|
1118
|
+
this.shadowRoot?.querySelectorAll<HTMLElement>('[data-item-kind][data-item-id]') ?? [],
|
|
1119
|
+
).find((candidate) => (
|
|
1120
|
+
candidate.dataset.itemKind === refArg.kind && candidate.dataset.itemId === refArg.id
|
|
1121
|
+
));
|
|
1076
1122
|
}
|
|
1077
1123
|
|
|
1078
1124
|
public focusSearch(): void {
|
|
@@ -1081,46 +1127,51 @@ export class DeesHarnessSessionList extends DeesElement {
|
|
|
1081
1127
|
|
|
1082
1128
|
public requestResourceAssociation(
|
|
1083
1129
|
resourceIdArg: string,
|
|
1084
|
-
|
|
1130
|
+
toTargetArg: IHarnessSessionListItemRef | null,
|
|
1085
1131
|
sourceArg: THarnessResourceAssociationInteractionSource = 'action',
|
|
1086
1132
|
): boolean {
|
|
1087
1133
|
const resource = this.resourcesById.get(resourceIdArg);
|
|
1088
1134
|
if (!resource) return false;
|
|
1089
|
-
const
|
|
1090
|
-
if (
|
|
1135
|
+
const fromTarget = this.acceptedResourceAssociations.get(resourceIdArg) ?? null;
|
|
1136
|
+
if (sameItem(fromTarget, toTargetArg)) return false;
|
|
1091
1137
|
if (
|
|
1092
|
-
|
|
1093
|
-
&& (
|
|
1138
|
+
toTargetArg !== null
|
|
1139
|
+
&& (
|
|
1140
|
+
!this.itemsByKey.has(itemKey(toTargetArg))
|
|
1141
|
+
|| sameItem(toTargetArg, { kind: 'resource', id: resourceIdArg })
|
|
1142
|
+
|| !this.isResourceTargetEligible(resourceIdArg, toTargetArg)
|
|
1143
|
+
)
|
|
1094
1144
|
) return false;
|
|
1095
|
-
if (
|
|
1096
|
-
const
|
|
1145
|
+
if (toTargetArg === null && fromTarget === null) return false;
|
|
1146
|
+
const toTarget = toTargetArg === null ? null : itemRef(toTargetArg);
|
|
1147
|
+
const detail: THarnessResourceAssociationRequestDetail = fromTarget === null
|
|
1097
1148
|
? {
|
|
1098
1149
|
intent: 'attach',
|
|
1099
1150
|
resource,
|
|
1100
|
-
|
|
1101
|
-
|
|
1151
|
+
fromTarget: null,
|
|
1152
|
+
toTarget: toTarget!,
|
|
1102
1153
|
source: sourceArg,
|
|
1103
1154
|
}
|
|
1104
|
-
:
|
|
1155
|
+
: toTarget === null
|
|
1105
1156
|
? {
|
|
1106
1157
|
intent: 'detach',
|
|
1107
1158
|
resource,
|
|
1108
|
-
|
|
1109
|
-
|
|
1159
|
+
fromTarget,
|
|
1160
|
+
toTarget: null,
|
|
1110
1161
|
source: sourceArg,
|
|
1111
1162
|
}
|
|
1112
1163
|
: {
|
|
1113
1164
|
intent: 'reassign',
|
|
1114
1165
|
resource,
|
|
1115
|
-
|
|
1116
|
-
|
|
1166
|
+
fromTarget,
|
|
1167
|
+
toTarget,
|
|
1117
1168
|
source: sourceArg,
|
|
1118
1169
|
};
|
|
1119
1170
|
this.pendingResourceAssociationRequest = {
|
|
1120
1171
|
resourceId: resource.id,
|
|
1121
1172
|
resourceTitle: resource.title || resource.id,
|
|
1122
|
-
|
|
1123
|
-
|
|
1173
|
+
fromTarget,
|
|
1174
|
+
toTarget,
|
|
1124
1175
|
intent: detail.intent,
|
|
1125
1176
|
};
|
|
1126
1177
|
this.moveAnnouncement = this.resourceAssociationAnnouncement(detail, false);
|
|
@@ -1147,12 +1198,6 @@ export class DeesHarnessSessionList extends DeesElement {
|
|
|
1147
1198
|
}
|
|
1148
1199
|
if (changedProperties.has('sessions')) {
|
|
1149
1200
|
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
1201
|
const nextExpanded = new Set(
|
|
1157
1202
|
[...this.expandedSessionIds].filter((sessionId) => knownIds.has(sessionId)),
|
|
1158
1203
|
);
|
|
@@ -1164,13 +1209,6 @@ export class DeesHarnessSessionList extends DeesElement {
|
|
|
1164
1209
|
}
|
|
1165
1210
|
}
|
|
1166
1211
|
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
1212
|
if (
|
|
1175
1213
|
this.resourceContextResourceId
|
|
1176
1214
|
&& !this.resourcesById.has(this.resourceContextResourceId)
|
|
@@ -1179,16 +1217,15 @@ export class DeesHarnessSessionList extends DeesElement {
|
|
|
1179
1217
|
}
|
|
1180
1218
|
if (
|
|
1181
1219
|
changedProperties.has('sessions')
|
|
1182
|
-
|| changedProperties.has('
|
|
1220
|
+
|| changedProperties.has('resources')
|
|
1221
|
+
|| changedProperties.has('search')
|
|
1183
1222
|
) {
|
|
1223
|
+
// A drag is positional only: it survives association and eligibility
|
|
1224
|
+
// updates, but never outlives its own row.
|
|
1184
1225
|
const gesture = this.pointerDragGesture;
|
|
1185
|
-
if (
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
gesture.eligibleSessionIds,
|
|
1189
|
-
new Set(this.eligibleSessionIds(gesture.resourceId)),
|
|
1190
|
-
)
|
|
1191
|
-
) this.finishPointerDrag(false);
|
|
1226
|
+
if (gesture && (this.searchActive || !this.itemsByKey.has(itemKey(gesture.item)))) {
|
|
1227
|
+
this.finishPointerDrag(false);
|
|
1228
|
+
}
|
|
1192
1229
|
}
|
|
1193
1230
|
}
|
|
1194
1231
|
|
|
@@ -1211,7 +1248,6 @@ export class DeesHarnessSessionList extends DeesElement {
|
|
|
1211
1248
|
}
|
|
1212
1249
|
|
|
1213
1250
|
public render(): TemplateResult {
|
|
1214
|
-
const groupingActive = this.enableGrouping && !this.search.trim();
|
|
1215
1251
|
return html`
|
|
1216
1252
|
<div class="searchRow">
|
|
1217
1253
|
<div class="searchField">
|
|
@@ -1249,15 +1285,15 @@ export class DeesHarnessSessionList extends DeesElement {
|
|
|
1249
1285
|
<div class="listFrame">
|
|
1250
1286
|
<div class="scrollShadow top ${this.topScrollShadowVisible ? 'visible' : ''}"></div>
|
|
1251
1287
|
<div
|
|
1252
|
-
class="list ${this.
|
|
1288
|
+
class="list ${this.draggingItem ? 'dragActive' : ''}"
|
|
1253
1289
|
@scroll=${this.syncScrollShadowState}
|
|
1254
1290
|
>
|
|
1255
1291
|
<div class="listContent">
|
|
1256
1292
|
${this.loading
|
|
1257
1293
|
? html`<div class="emptyNote">Loading conversations…</div>`
|
|
1258
|
-
:
|
|
1259
|
-
? this.
|
|
1260
|
-
: this.
|
|
1294
|
+
: this.searchActive
|
|
1295
|
+
? this.renderSearchList()
|
|
1296
|
+
: this.renderOrderedList()}
|
|
1261
1297
|
</div>
|
|
1262
1298
|
</div>
|
|
1263
1299
|
<div class="scrollShadow bottom ${this.bottomScrollShadowVisible ? 'visible' : ''}"></div>
|
|
@@ -1266,84 +1302,33 @@ export class DeesHarnessSessionList extends DeesElement {
|
|
|
1266
1302
|
`;
|
|
1267
1303
|
}
|
|
1268
1304
|
|
|
1269
|
-
private
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
return
|
|
1284
|
-
|
|
1285
|
-
${repeat(
|
|
1286
|
-
sessions,
|
|
1287
|
-
(session) => session.id,
|
|
1288
|
-
(session) => this.renderItem(session, null, -1),
|
|
1289
|
-
)}
|
|
1290
|
-
</div>
|
|
1291
|
-
${this.renderUnattachedResources(unattachedResources)}
|
|
1292
|
-
`;
|
|
1305
|
+
private renderEmptyNote(): TemplateResult {
|
|
1306
|
+
return html`<div class="emptyNote">
|
|
1307
|
+
${this.acceptedResources.length
|
|
1308
|
+
? this.sessions.length
|
|
1309
|
+
? 'No conversations or resources match this search.'
|
|
1310
|
+
: 'No resources match this search.'
|
|
1311
|
+
: this.sessions.length
|
|
1312
|
+
? 'No conversations match this search.'
|
|
1313
|
+
: this.emptyText}
|
|
1314
|
+
</div>`;
|
|
1315
|
+
}
|
|
1316
|
+
|
|
1317
|
+
private renderSearchList(): TemplateResult {
|
|
1318
|
+
const items = this.searchItems;
|
|
1319
|
+
if (!items.length) return this.renderEmptyNote();
|
|
1320
|
+
return this.renderSection(items, null, false);
|
|
1293
1321
|
}
|
|
1294
1322
|
|
|
1295
|
-
private
|
|
1296
|
-
const
|
|
1297
|
-
const ungrouped = this.
|
|
1298
|
-
|
|
1299
|
-
if (!this.sessions.length && !this.groups.length && !unattachedResources.length) {
|
|
1323
|
+
private renderOrderedList(): TemplateResult {
|
|
1324
|
+
const groups = this.effectiveGroups;
|
|
1325
|
+
const ungrouped = this.ungroupedItems;
|
|
1326
|
+
if (!this.acceptedItems.length && !groups.length) {
|
|
1300
1327
|
return html`<div class="emptyNote">${this.emptyText}</div>`;
|
|
1301
1328
|
}
|
|
1302
1329
|
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)
|
|
1330
|
+
${groups.map((group) => this.renderGroup(group))}
|
|
1331
|
+
${groups.length && (ungrouped.length || this.draggingItem)
|
|
1347
1332
|
? html`
|
|
1348
1333
|
<button
|
|
1349
1334
|
class="groupHeader"
|
|
@@ -1358,56 +1343,107 @@ export class DeesHarnessSessionList extends DeesElement {
|
|
|
1358
1343
|
</button>
|
|
1359
1344
|
`
|
|
1360
1345
|
: ''}
|
|
1361
|
-
${this.renderSection(ungrouped, null)}
|
|
1362
|
-
|
|
1346
|
+
${this.renderSection(ungrouped, null, true)}
|
|
1347
|
+
`;
|
|
1348
|
+
}
|
|
1349
|
+
|
|
1350
|
+
private renderGroup(groupArg: IHarnessSessionGroup): TemplateResult {
|
|
1351
|
+
const members = this.itemsForGroup(groupArg.id);
|
|
1352
|
+
const collapsed = this.collapsedGroupIds.has(groupArg.id);
|
|
1353
|
+
const attentionCount = members.filter((item) => (
|
|
1354
|
+
item.kind === 'session' && (item.session.state === 'attention' || item.session.state === 'error')
|
|
1355
|
+
)).length;
|
|
1356
|
+
const appendIndex = this.sortableItems(members).length;
|
|
1357
|
+
const isHeaderDrop = this.dropSlot?.groupId === groupArg.id
|
|
1358
|
+
&& this.dropSlot.index === appendIndex
|
|
1359
|
+
&& collapsed;
|
|
1360
|
+
return html`
|
|
1361
|
+
<button
|
|
1362
|
+
class="groupHeader ${collapsed ? 'collapsed' : ''} ${isHeaderDrop ? 'dropTarget' : ''}"
|
|
1363
|
+
data-drop-group-id=${groupArg.id}
|
|
1364
|
+
data-drop-index=${appendIndex}
|
|
1365
|
+
data-drop-kind="slot"
|
|
1366
|
+
type="button"
|
|
1367
|
+
@click=${() => this.toggleGroupCollapsed(groupArg.id)}
|
|
1368
|
+
@contextmenu=${(event: MouseEvent) => {
|
|
1369
|
+
event.preventDefault();
|
|
1370
|
+
const detail: IHarnessGroupContextDetail = {
|
|
1371
|
+
group: groupArg,
|
|
1372
|
+
clientX: event.clientX,
|
|
1373
|
+
clientY: event.clientY,
|
|
1374
|
+
originalEvent: event,
|
|
1375
|
+
};
|
|
1376
|
+
this.dispatchEvent(
|
|
1377
|
+
new CustomEvent('harness-group-context', { detail, bubbles: true, composed: true }),
|
|
1378
|
+
);
|
|
1379
|
+
}}
|
|
1380
|
+
>
|
|
1381
|
+
<dees-icon class="chevron" icon="lucide:ChevronDown" iconSize="12"></dees-icon>
|
|
1382
|
+
<span>${groupArg.name}</span>
|
|
1383
|
+
${attentionCount ? html`<span class="groupAttention" aria-label=${`${attentionCount} need attention`}>
|
|
1384
|
+
<dees-icon aria-hidden="true" icon="lucide:CircleAlert" iconSize="11"></dees-icon>${attentionCount}
|
|
1385
|
+
</span>` : ''}
|
|
1386
|
+
<span class="groupCount">${members.length}</span>
|
|
1387
|
+
</button>
|
|
1388
|
+
${collapsed ? '' : this.renderSection(members, groupArg.id, true)}
|
|
1363
1389
|
`;
|
|
1364
1390
|
}
|
|
1365
1391
|
|
|
1366
1392
|
private renderSection(
|
|
1367
|
-
|
|
1368
|
-
|
|
1393
|
+
itemsArg: TListItem[],
|
|
1394
|
+
groupIdArg: string | null,
|
|
1395
|
+
sortableArg: boolean,
|
|
1369
1396
|
): TemplateResult {
|
|
1370
|
-
const slot = this.dropSlot;
|
|
1397
|
+
const slot = sortableArg ? this.dropSlot : undefined;
|
|
1371
1398
|
const entries: TSectionRenderEntry[] = [];
|
|
1372
1399
|
let logicalIndex = 0;
|
|
1373
|
-
for (const
|
|
1374
|
-
const isDragging =
|
|
1375
|
-
const sortable =
|
|
1400
|
+
for (const item of itemsArg) {
|
|
1401
|
+
const isDragging = sameItem(this.draggingItem, item);
|
|
1402
|
+
const sortable = sortableArg && item.sortable;
|
|
1376
1403
|
if (
|
|
1377
1404
|
slot
|
|
1378
|
-
&& slot.groupId ===
|
|
1405
|
+
&& slot.groupId === groupIdArg
|
|
1379
1406
|
&& slot.index === logicalIndex
|
|
1380
1407
|
&& !isDragging
|
|
1381
1408
|
&& sortable
|
|
1382
1409
|
) {
|
|
1383
|
-
entries.push({ type: 'placeholder', key: `placeholder:${
|
|
1410
|
+
entries.push({ type: 'placeholder', key: `placeholder:${groupIdArg ?? 'ungrouped'}` });
|
|
1384
1411
|
}
|
|
1385
1412
|
entries.push({
|
|
1386
|
-
type: '
|
|
1387
|
-
key:
|
|
1388
|
-
|
|
1413
|
+
type: 'item',
|
|
1414
|
+
key: item.key,
|
|
1415
|
+
item,
|
|
1389
1416
|
logicalIndex: sortable ? logicalIndex : -1,
|
|
1390
1417
|
});
|
|
1391
1418
|
if (sortable && !isDragging) logicalIndex += 1;
|
|
1392
1419
|
}
|
|
1393
|
-
if (slot && slot.groupId ===
|
|
1394
|
-
entries.push({ type: 'placeholder', key: `placeholder:${
|
|
1420
|
+
if (slot && slot.groupId === groupIdArg && slot.index === logicalIndex) {
|
|
1421
|
+
entries.push({ type: 'placeholder', key: `placeholder:${groupIdArg ?? 'ungrouped'}` });
|
|
1395
1422
|
}
|
|
1396
1423
|
const hasPlaceholder = entries.some((entry) => entry.type === 'placeholder');
|
|
1424
|
+
// Resolve the controlled associations once per section, not once per row.
|
|
1425
|
+
const associations = this.acceptedResourceAssociations;
|
|
1397
1426
|
return html`
|
|
1398
|
-
<div class="section" data-group-id=${
|
|
1427
|
+
<div class="section" data-group-id=${groupIdArg ?? ''}>
|
|
1399
1428
|
${repeat(
|
|
1400
1429
|
entries,
|
|
1401
1430
|
(entry) => entry.key,
|
|
1402
1431
|
(entry) => entry.type === 'placeholder'
|
|
1403
|
-
? this.renderDropPlaceholder(
|
|
1404
|
-
:
|
|
1432
|
+
? this.renderDropPlaceholder(groupIdArg, slot?.index ?? 0)
|
|
1433
|
+
: entry.item.kind === 'session'
|
|
1434
|
+
? this.renderSessionItem(entry.item.session, groupIdArg, entry.logicalIndex)
|
|
1435
|
+
: this.renderResourceItem(
|
|
1436
|
+
entry.item.resource,
|
|
1437
|
+
groupIdArg,
|
|
1438
|
+
entry.logicalIndex,
|
|
1439
|
+
associations.get(entry.item.id) ?? null,
|
|
1440
|
+
),
|
|
1405
1441
|
)}
|
|
1406
|
-
${this.
|
|
1442
|
+
${this.draggingItem && sortableArg && !hasPlaceholder && this.sortableItems(itemsArg).length === 0
|
|
1407
1443
|
? html`
|
|
1408
1444
|
<div
|
|
1409
1445
|
class="emptyDropZone"
|
|
1410
|
-
data-drop-group-id=${
|
|
1446
|
+
data-drop-group-id=${groupIdArg ?? ''}
|
|
1411
1447
|
data-drop-index="0"
|
|
1412
1448
|
data-drop-kind="slot"
|
|
1413
1449
|
></div>
|
|
@@ -1424,46 +1460,72 @@ export class DeesHarnessSessionList extends DeesElement {
|
|
|
1424
1460
|
data-drop-group-id=${groupId ?? ''}
|
|
1425
1461
|
data-drop-index=${index}
|
|
1426
1462
|
data-drop-kind="slot"
|
|
1427
|
-
style=${`height: ${Math.max(
|
|
1463
|
+
style=${`height: ${Math.max(24, this.draggedItemHeight)}px`}
|
|
1428
1464
|
></div>
|
|
1429
1465
|
`;
|
|
1430
1466
|
}
|
|
1431
1467
|
|
|
1432
|
-
|
|
1468
|
+
/** One grip for both kinds; only its size and extra class differ. */
|
|
1469
|
+
private renderDragHandle(
|
|
1470
|
+
refArg: IHarnessSessionListItemRef,
|
|
1471
|
+
titleArg: string,
|
|
1472
|
+
groupIdArg: string | null,
|
|
1473
|
+
logicalIndexArg: number,
|
|
1474
|
+
): TemplateResult {
|
|
1475
|
+
const isResource = refArg.kind === 'resource';
|
|
1476
|
+
return html`
|
|
1477
|
+
<button
|
|
1478
|
+
class="dragHandle ${isResource ? 'resourceDragHandle' : ''}"
|
|
1479
|
+
type="button"
|
|
1480
|
+
title="Drag to reorder; use arrow, Home, or End keys for keyboard reordering"
|
|
1481
|
+
aria-label=${`Reorder ${titleArg}`}
|
|
1482
|
+
aria-keyshortcuts="ArrowUp ArrowDown Home End"
|
|
1483
|
+
@pointerdown=${(event: PointerEvent) => {
|
|
1484
|
+
this.handlePointerDown(event, refArg, groupIdArg, logicalIndexArg);
|
|
1485
|
+
}}
|
|
1486
|
+
@pointermove=${this.handlePointerMove}
|
|
1487
|
+
@pointerup=${this.handlePointerUp}
|
|
1488
|
+
@pointercancel=${this.handlePointerCancel}
|
|
1489
|
+
@lostpointercapture=${this.handleLostPointerCapture}
|
|
1490
|
+
@keydown=${(event: KeyboardEvent) => {
|
|
1491
|
+
this.handleKeyboardMove(event, refArg, titleArg, groupIdArg);
|
|
1492
|
+
}}
|
|
1493
|
+
>
|
|
1494
|
+
<dees-icon icon="lucide:GripVertical" iconSize=${isResource ? '13' : '14'}></dees-icon>
|
|
1495
|
+
</button>
|
|
1496
|
+
`;
|
|
1497
|
+
}
|
|
1498
|
+
|
|
1499
|
+
private renderSessionItem(
|
|
1433
1500
|
session: IHarnessSessionMeta,
|
|
1434
1501
|
groupId: string | null,
|
|
1435
1502
|
index: number,
|
|
1436
1503
|
): TemplateResult {
|
|
1437
|
-
const draggable =
|
|
1438
|
-
this.enableGrouping
|
|
1439
|
-
&& session.draggable !== false
|
|
1440
|
-
&& index >= 0
|
|
1441
|
-
);
|
|
1504
|
+
const draggable = index >= 0;
|
|
1442
1505
|
const expanded = this.expandedSessionIds.has(session.id);
|
|
1443
1506
|
const state = session.state ?? 'normal';
|
|
1444
1507
|
const statusLabel = this.sessionStatusLabel(session);
|
|
1445
1508
|
const active = (state === 'normal' || state === 'working') && (session.working ?? state === 'working');
|
|
1446
1509
|
const detailsId = `session-details-${session.id}`;
|
|
1447
1510
|
const dropIndex = index < 0 ? 0 : index;
|
|
1448
|
-
const
|
|
1449
|
-
const isResourceDropTarget = this.resourceDropTargetSessionId === session.id;
|
|
1511
|
+
const title = session.title || session.id;
|
|
1450
1512
|
return html`
|
|
1451
1513
|
<article
|
|
1452
1514
|
class="item state-${state} ${active ? 'active' : ''} ${expanded ? 'expanded' : ''} ${
|
|
1453
1515
|
session.id === this.selectedSessionId ? 'selected' : ''
|
|
1454
1516
|
} ${
|
|
1455
|
-
this.
|
|
1456
|
-
} ${
|
|
1457
|
-
isResourceDropTarget ? 'resourceDropTarget' : ''
|
|
1517
|
+
sameItem(this.draggingItem, { kind: 'session', id: session.id }) ? 'dragging' : ''
|
|
1458
1518
|
}"
|
|
1459
1519
|
data-session-id=${session.id}
|
|
1520
|
+
data-item-kind="session"
|
|
1521
|
+
data-item-id=${session.id}
|
|
1460
1522
|
data-drop-group-id=${groupId ?? ''}
|
|
1461
1523
|
data-drop-index=${dropIndex}
|
|
1462
1524
|
data-drop-kind=${index < 0 ? 'slot' : 'card'}
|
|
1463
1525
|
@dblclick=${(event: MouseEvent) => {
|
|
1464
1526
|
if (this.eventTargetsExpandToggle(event) || this.eventTargetsDragHandle(event)
|
|
1465
1527
|
|| this.eventTargetsSessionActions(event)) return;
|
|
1466
|
-
this.
|
|
1528
|
+
this.emitSession('harness-session-open', session);
|
|
1467
1529
|
}}
|
|
1468
1530
|
@contextmenu=${(event: MouseEvent) => {
|
|
1469
1531
|
if (this.eventTargetsExpandToggle(event)) return;
|
|
@@ -1483,39 +1545,19 @@ export class DeesHarnessSessionList extends DeesElement {
|
|
|
1483
1545
|
<div class="itemInner">
|
|
1484
1546
|
<div class="itemHeader ${draggable ? 'draggable' : ''}">
|
|
1485
1547
|
${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
|
-
`
|
|
1548
|
+
? this.renderDragHandle({ kind: 'session', id: session.id }, title, groupId, index)
|
|
1507
1549
|
: ''}
|
|
1508
1550
|
<button
|
|
1509
1551
|
class="itemPrimary"
|
|
1510
1552
|
type="button"
|
|
1511
1553
|
aria-keyshortcuts="ContextMenu Shift+F10"
|
|
1512
1554
|
aria-current=${session.id === this.selectedSessionId ? 'true' : 'false'}
|
|
1513
|
-
@click=${() => this.
|
|
1555
|
+
@click=${() => this.emitSession('harness-session-select', session)}
|
|
1514
1556
|
@keydown=${(event: KeyboardEvent) => {
|
|
1515
1557
|
if (this.handleSessionContextKey(event)) return;
|
|
1516
1558
|
if (event.key !== 'Enter') return;
|
|
1517
1559
|
event.preventDefault();
|
|
1518
|
-
this.
|
|
1560
|
+
this.emitSession('harness-session-open', session);
|
|
1519
1561
|
}}
|
|
1520
1562
|
>
|
|
1521
1563
|
<span class="stateDot" aria-hidden="true">
|
|
@@ -1526,7 +1568,7 @@ export class DeesHarnessSessionList extends DeesElement {
|
|
|
1526
1568
|
${session.icon
|
|
1527
1569
|
? html`<dees-icon class="itemIcon" icon=${session.icon} iconSize="13"></dees-icon>`
|
|
1528
1570
|
: ''}
|
|
1529
|
-
<span class="titleText">${
|
|
1571
|
+
<span class="titleText">${title}</span>
|
|
1530
1572
|
</span>
|
|
1531
1573
|
<span class="itemStatus" title=${statusLabel}>${statusLabel}</span>
|
|
1532
1574
|
<span class="itemIdentity">
|
|
@@ -1543,7 +1585,7 @@ export class DeesHarnessSessionList extends DeesElement {
|
|
|
1543
1585
|
class="sessionActions"
|
|
1544
1586
|
type="button"
|
|
1545
1587
|
title="Actions"
|
|
1546
|
-
aria-label=${`Actions for ${
|
|
1588
|
+
aria-label=${`Actions for ${title}`}
|
|
1547
1589
|
aria-haspopup="menu"
|
|
1548
1590
|
aria-keyshortcuts="ContextMenu Shift+F10"
|
|
1549
1591
|
@click=${this.invokeSessionContext}
|
|
@@ -1565,20 +1607,6 @@ export class DeesHarnessSessionList extends DeesElement {
|
|
|
1565
1607
|
<dees-icon icon="lucide:ChevronDown" iconSize="15"></dees-icon>
|
|
1566
1608
|
</button>
|
|
1567
1609
|
</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
1610
|
${expanded
|
|
1583
1611
|
? html`
|
|
1584
1612
|
<div class="itemDetails" id=${detailsId}>
|
|
@@ -1594,41 +1622,35 @@ export class DeesHarnessSessionList extends DeesElement {
|
|
|
1594
1622
|
`;
|
|
1595
1623
|
}
|
|
1596
1624
|
|
|
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(
|
|
1625
|
+
private renderResourceItem(
|
|
1614
1626
|
resourceArg: IHarnessResourceMeta,
|
|
1615
|
-
|
|
1627
|
+
groupIdArg: string | null,
|
|
1628
|
+
indexArg: number,
|
|
1629
|
+
attachedTarget: IHarnessSessionListItemRef | null,
|
|
1616
1630
|
): TemplateResult {
|
|
1617
1631
|
const selected = resourceArg.id === this.selectedResourceId;
|
|
1618
|
-
const
|
|
1619
|
-
const
|
|
1620
|
-
(sessionId) => sessionId !== sessionIdArg,
|
|
1621
|
-
).length;
|
|
1632
|
+
const draggable = indexArg >= 0;
|
|
1633
|
+
const dragging = sameItem(this.draggingItem, { kind: 'resource', id: resourceArg.id });
|
|
1622
1634
|
const contextOpen = resourceArg.id === this.resourceContextResourceId;
|
|
1635
|
+
const attachedTitle = attachedTarget ? this.itemTitleFor(attachedTarget) : '';
|
|
1636
|
+
const title = resourceArg.title || resourceArg.id;
|
|
1637
|
+
const dropIndex = indexArg < 0 ? 0 : indexArg;
|
|
1623
1638
|
return html`
|
|
1624
|
-
<
|
|
1625
|
-
class="resourceRow ${selected ? 'selected' : ''} ${
|
|
1639
|
+
<article
|
|
1640
|
+
class="resourceRow ${selected ? 'selected' : ''} ${draggable ? 'draggable' : ''} ${
|
|
1641
|
+
dragging ? 'dragging' : ''
|
|
1642
|
+
}"
|
|
1626
1643
|
data-resource-id=${resourceArg.id}
|
|
1627
|
-
data-resource
|
|
1644
|
+
data-item-kind="resource"
|
|
1645
|
+
data-item-id=${resourceArg.id}
|
|
1646
|
+
data-resource-target=${attachedTarget ? `${attachedTarget.kind}:${attachedTarget.id}` : ''}
|
|
1647
|
+
data-drop-group-id=${groupIdArg ?? ''}
|
|
1648
|
+
data-drop-index=${dropIndex}
|
|
1649
|
+
data-drop-kind=${indexArg < 0 ? 'slot' : 'card'}
|
|
1628
1650
|
@dblclick=${(event: MouseEvent) => {
|
|
1629
1651
|
event.stopPropagation();
|
|
1630
|
-
if (this.eventTargetsResourceControl(event)) return;
|
|
1631
|
-
this.emitResource('harness-resource-open', resourceArg,
|
|
1652
|
+
if (this.eventTargetsResourceControl(event) || this.eventTargetsDragHandle(event)) return;
|
|
1653
|
+
this.emitResource('harness-resource-open', resourceArg, attachedTarget);
|
|
1632
1654
|
}}
|
|
1633
1655
|
@contextmenu=${(event: MouseEvent) => {
|
|
1634
1656
|
event.preventDefault();
|
|
@@ -1639,43 +1661,29 @@ export class DeesHarnessSessionList extends DeesElement {
|
|
|
1639
1661
|
)) as HTMLElement | undefined)
|
|
1640
1662
|
?? (event.currentTarget as HTMLElement).querySelector<HTMLElement>('.resourcePrimary')
|
|
1641
1663
|
?? event.currentTarget as HTMLElement;
|
|
1642
|
-
this.emitResourceContext(event, resourceArg,
|
|
1664
|
+
this.emitResourceContext(event, resourceArg, attachedTarget, 'pointer', focusTarget);
|
|
1643
1665
|
}}
|
|
1644
1666
|
>
|
|
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>
|
|
1667
|
+
${draggable
|
|
1668
|
+
? this.renderDragHandle(
|
|
1669
|
+
{ kind: 'resource', id: resourceArg.id },
|
|
1670
|
+
title,
|
|
1671
|
+
groupIdArg,
|
|
1672
|
+
indexArg,
|
|
1673
|
+
)
|
|
1674
|
+
: ''}
|
|
1667
1675
|
<button
|
|
1668
1676
|
class="resourcePrimary"
|
|
1669
1677
|
type="button"
|
|
1670
1678
|
aria-current=${selected ? 'true' : 'false'}
|
|
1671
|
-
@click=${() => this.emitResource('harness-resource-select', resourceArg,
|
|
1679
|
+
@click=${() => this.emitResource('harness-resource-select', resourceArg, attachedTarget)}
|
|
1672
1680
|
@keydown=${(event: KeyboardEvent) => {
|
|
1673
1681
|
if (event.key === 'Enter') {
|
|
1674
1682
|
event.preventDefault();
|
|
1675
|
-
this.emitResource('harness-resource-open', resourceArg,
|
|
1683
|
+
this.emitResource('harness-resource-open', resourceArg, attachedTarget);
|
|
1676
1684
|
return;
|
|
1677
1685
|
}
|
|
1678
|
-
this.handleResourceContextKey(event, resourceArg,
|
|
1686
|
+
this.handleResourceContextKey(event, resourceArg, attachedTarget);
|
|
1679
1687
|
}}
|
|
1680
1688
|
>
|
|
1681
1689
|
<dees-icon
|
|
@@ -1684,7 +1692,7 @@ export class DeesHarnessSessionList extends DeesElement {
|
|
|
1684
1692
|
iconSize="14"
|
|
1685
1693
|
></dees-icon>
|
|
1686
1694
|
<span class="resourceCopy">
|
|
1687
|
-
<span class="resourceTitle">${
|
|
1695
|
+
<span class="resourceTitle">${title}</span>
|
|
1688
1696
|
${resourceArg.preview ? html`<span class="resourcePreview">${resourceArg.preview}</span>` : ''}
|
|
1689
1697
|
<span class="resourceMeta">
|
|
1690
1698
|
${resourceArg.kind}${resourceArg.state ? ` · ${resourceArg.state}` : ''}${
|
|
@@ -1693,27 +1701,42 @@ export class DeesHarnessSessionList extends DeesElement {
|
|
|
1693
1701
|
: ''
|
|
1694
1702
|
}
|
|
1695
1703
|
</span>
|
|
1704
|
+
${attachedTarget
|
|
1705
|
+
? html`
|
|
1706
|
+
<span class="resourceAttachment" title=${`Attached to ${attachedTitle}`}>
|
|
1707
|
+
<dees-icon aria-hidden="true" icon="lucide:Link" iconSize="10"></dees-icon>
|
|
1708
|
+
<span class="visuallyHidden">Attached to </span>
|
|
1709
|
+
<span class="resourceAttachmentName">${attachedTitle}</span>
|
|
1710
|
+
</span>
|
|
1711
|
+
`
|
|
1712
|
+
: ''}
|
|
1696
1713
|
</span>
|
|
1697
1714
|
</button>
|
|
1698
1715
|
<button
|
|
1699
1716
|
class="resourceActions"
|
|
1700
1717
|
type="button"
|
|
1701
1718
|
title="Resource actions"
|
|
1702
|
-
aria-label=${`Actions for ${
|
|
1719
|
+
aria-label=${`Actions for ${title}`}
|
|
1703
1720
|
aria-haspopup="menu"
|
|
1704
1721
|
aria-expanded=${contextOpen ? 'true' : 'false'}
|
|
1705
1722
|
aria-keyshortcuts="ContextMenu Shift+F10"
|
|
1706
1723
|
@click=${(event: MouseEvent) => {
|
|
1707
1724
|
event.stopPropagation();
|
|
1708
|
-
this.emitResourceContext(
|
|
1725
|
+
this.emitResourceContext(
|
|
1726
|
+
event,
|
|
1727
|
+
resourceArg,
|
|
1728
|
+
attachedTarget,
|
|
1729
|
+
'action',
|
|
1730
|
+
event.currentTarget as HTMLElement,
|
|
1731
|
+
);
|
|
1709
1732
|
}}
|
|
1710
1733
|
@keydown=${(event: KeyboardEvent) => {
|
|
1711
|
-
this.handleResourceContextKey(event, resourceArg,
|
|
1734
|
+
this.handleResourceContextKey(event, resourceArg, attachedTarget);
|
|
1712
1735
|
}}
|
|
1713
1736
|
>
|
|
1714
1737
|
<dees-icon icon="lucide:Ellipsis" iconSize="15"></dees-icon>
|
|
1715
1738
|
</button>
|
|
1716
|
-
</
|
|
1739
|
+
</article>
|
|
1717
1740
|
`;
|
|
1718
1741
|
}
|
|
1719
1742
|
|
|
@@ -1755,10 +1778,7 @@ export class DeesHarnessSessionList extends DeesElement {
|
|
|
1755
1778
|
|
|
1756
1779
|
private eventTargetsResourceControl(event: Event): boolean {
|
|
1757
1780
|
return event.composedPath().some(
|
|
1758
|
-
(target) => target instanceof HTMLElement && (
|
|
1759
|
-
target.classList.contains('resourceDragHandle')
|
|
1760
|
-
|| target.classList.contains('resourceActions')
|
|
1761
|
-
),
|
|
1781
|
+
(target) => target instanceof HTMLElement && target.classList.contains('resourceActions'),
|
|
1762
1782
|
);
|
|
1763
1783
|
}
|
|
1764
1784
|
|
|
@@ -1803,29 +1823,9 @@ export class DeesHarnessSessionList extends DeesElement {
|
|
|
1803
1823
|
this.collapsedGroupIds = next;
|
|
1804
1824
|
}
|
|
1805
1825
|
|
|
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
1826
|
private handlePointerDown(
|
|
1827
1827
|
eventArg: PointerEvent,
|
|
1828
|
-
|
|
1828
|
+
refArg: IHarnessSessionListItemRef,
|
|
1829
1829
|
groupIdArg: string | null,
|
|
1830
1830
|
logicalIndexArg: number,
|
|
1831
1831
|
): void {
|
|
@@ -1836,15 +1836,14 @@ export class DeesHarnessSessionList extends DeesElement {
|
|
|
1836
1836
|
|| logicalIndexArg < 0
|
|
1837
1837
|
) return;
|
|
1838
1838
|
const handle = eventArg.currentTarget as HTMLElement;
|
|
1839
|
-
const source = handle.closest<HTMLElement>('.item');
|
|
1839
|
+
const source = handle.closest<HTMLElement>('.item, .resourceRow');
|
|
1840
1840
|
if (!source) return;
|
|
1841
1841
|
const sourceRect = source.getBoundingClientRect();
|
|
1842
1842
|
this.pointerDragGesture = {
|
|
1843
|
-
type: 'session',
|
|
1844
1843
|
pointerId: eventArg.pointerId,
|
|
1845
1844
|
handle,
|
|
1846
1845
|
source,
|
|
1847
|
-
|
|
1846
|
+
item: itemRef(refArg),
|
|
1848
1847
|
sourceGroupId: groupIdArg,
|
|
1849
1848
|
sourceLogicalIndex: logicalIndexArg,
|
|
1850
1849
|
startClientX: eventArg.clientX,
|
|
@@ -1862,44 +1861,6 @@ export class DeesHarnessSessionList extends DeesElement {
|
|
|
1862
1861
|
}
|
|
1863
1862
|
}
|
|
1864
1863
|
|
|
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
1864
|
private readonly handlePointerMove = (eventArg: PointerEvent): void => {
|
|
1904
1865
|
const gesture = this.pointerDragGesture;
|
|
1905
1866
|
if (!gesture || eventArg.pointerId !== gesture.pointerId) return;
|
|
@@ -1914,21 +1875,8 @@ export class DeesHarnessSessionList extends DeesElement {
|
|
|
1914
1875
|
eventArg.clientY - gesture.startClientY,
|
|
1915
1876
|
) < dragThresholdPixels
|
|
1916
1877
|
) return;
|
|
1917
|
-
if (
|
|
1918
|
-
gesture.phase === 'pending'
|
|
1919
|
-
&& !(gesture.type === 'session'
|
|
1920
|
-
? this.startPointerDrag(eventArg, gesture)
|
|
1921
|
-
: this.startResourcePointerDrag(eventArg, gesture))
|
|
1922
|
-
) return;
|
|
1878
|
+
if (gesture.phase === 'pending' && !this.startPointerDrag(eventArg, gesture)) return;
|
|
1923
1879
|
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
1880
|
this.moveDragPreview(eventArg.clientX, eventArg.clientY, gesture);
|
|
1933
1881
|
this.dragAutoScrollClientX = eventArg.clientX;
|
|
1934
1882
|
this.dragAutoScrollClientY = eventArg.clientY;
|
|
@@ -1940,14 +1888,6 @@ export class DeesHarnessSessionList extends DeesElement {
|
|
|
1940
1888
|
const gesture = this.pointerDragGesture;
|
|
1941
1889
|
if (!gesture || eventArg.pointerId !== gesture.pointerId) return;
|
|
1942
1890
|
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
1891
|
this.dragAutoScrollClientX = eventArg.clientX;
|
|
1952
1892
|
this.dragAutoScrollClientY = eventArg.clientY;
|
|
1953
1893
|
this.moveDragPreview(eventArg.clientX, eventArg.clientY, gesture);
|
|
@@ -1993,9 +1933,9 @@ export class DeesHarnessSessionList extends DeesElement {
|
|
|
1993
1933
|
gestureArg.phase = 'active';
|
|
1994
1934
|
this.reflowGeneration += 1;
|
|
1995
1935
|
this.cancelReflowAnimations();
|
|
1996
|
-
this.draggedItemHeight =
|
|
1997
|
-
this.
|
|
1998
|
-
const remainingCount = this.
|
|
1936
|
+
this.draggedItemHeight = sourceRect.height;
|
|
1937
|
+
this.draggingItem = itemRef(gestureArg.item);
|
|
1938
|
+
const remainingCount = this.sortableItems(this.itemsForGroup(gestureArg.sourceGroupId)).length;
|
|
1999
1939
|
this.dropSlot = {
|
|
2000
1940
|
groupId: gestureArg.sourceGroupId,
|
|
2001
1941
|
index: Math.min(gestureArg.sourceLogicalIndex, remainingCount),
|
|
@@ -2003,27 +1943,6 @@ export class DeesHarnessSessionList extends DeesElement {
|
|
|
2003
1943
|
return true;
|
|
2004
1944
|
}
|
|
2005
1945
|
|
|
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
1946
|
private moveDragPreview(
|
|
2028
1947
|
clientXArg: number,
|
|
2029
1948
|
clientYArg: number,
|
|
@@ -2039,49 +1958,21 @@ export class DeesHarnessSessionList extends DeesElement {
|
|
|
2039
1958
|
);
|
|
2040
1959
|
}
|
|
2041
1960
|
|
|
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
1961
|
private setDropSlot(groupIdArg: string | null, indexArg: number): void {
|
|
2071
1962
|
const current = this.dropSlot;
|
|
2072
1963
|
if (current && current.groupId === groupIdArg && current.index === indexArg) return;
|
|
2073
|
-
const previousRects = this.
|
|
1964
|
+
const previousRects = this.captureRowRects();
|
|
2074
1965
|
const generation = ++this.reflowGeneration;
|
|
2075
1966
|
this.dropSlot = { groupId: groupIdArg, index: indexArg };
|
|
2076
|
-
void this.
|
|
1967
|
+
void this.animateRowReflow(previousRects, generation);
|
|
2077
1968
|
}
|
|
2078
1969
|
|
|
2079
1970
|
private clearDropSlot(): void {
|
|
2080
1971
|
if (!this.dropSlot) return;
|
|
2081
|
-
const previousRects = this.
|
|
1972
|
+
const previousRects = this.captureRowRects();
|
|
2082
1973
|
const generation = ++this.reflowGeneration;
|
|
2083
1974
|
this.dropSlot = undefined;
|
|
2084
|
-
void this.
|
|
1975
|
+
void this.animateRowReflow(previousRects, generation);
|
|
2085
1976
|
}
|
|
2086
1977
|
|
|
2087
1978
|
private updateDragAutoScrollVelocity(): void {
|
|
@@ -2134,14 +2025,7 @@ export class DeesHarnessSessionList extends DeesElement {
|
|
|
2134
2025
|
this.stopDragAutoScroll();
|
|
2135
2026
|
return;
|
|
2136
2027
|
}
|
|
2137
|
-
|
|
2138
|
-
this.updateResourceDropTargetFromPointer(
|
|
2139
|
-
this.dragAutoScrollClientX,
|
|
2140
|
-
this.dragAutoScrollClientY,
|
|
2141
|
-
);
|
|
2142
|
-
} else {
|
|
2143
|
-
this.updateDropSlotFromPointer();
|
|
2144
|
-
}
|
|
2028
|
+
this.updateDropSlotFromPointer();
|
|
2145
2029
|
this.dragAutoScrollFrame = requestAnimationFrame(this.runDragAutoScroll);
|
|
2146
2030
|
};
|
|
2147
2031
|
|
|
@@ -2165,6 +2049,7 @@ export class DeesHarnessSessionList extends DeesElement {
|
|
|
2165
2049
|
}
|
|
2166
2050
|
}
|
|
2167
2051
|
const selector = '[data-drop-group-id][data-drop-index][data-drop-kind]';
|
|
2052
|
+
const dragging = this.draggingItem;
|
|
2168
2053
|
let target: HTMLElement | undefined;
|
|
2169
2054
|
const offsets = this.dragAutoScrollVelocity === 0
|
|
2170
2055
|
? [0, -8, 8, -16, 16, -32, 32]
|
|
@@ -2176,7 +2061,11 @@ export class DeesHarnessSessionList extends DeesElement {
|
|
|
2176
2061
|
const candidate = hit?.closest<HTMLElement>(selector);
|
|
2177
2062
|
if (
|
|
2178
2063
|
candidate
|
|
2179
|
-
&&
|
|
2064
|
+
&& !(
|
|
2065
|
+
dragging
|
|
2066
|
+
&& candidate.dataset.itemKind === dragging.kind
|
|
2067
|
+
&& candidate.dataset.itemId === dragging.id
|
|
2068
|
+
)
|
|
2180
2069
|
&& Number(candidate.dataset.dropIndex) >= 0
|
|
2181
2070
|
) {
|
|
2182
2071
|
target = candidate;
|
|
@@ -2235,70 +2124,69 @@ export class DeesHarnessSessionList extends DeesElement {
|
|
|
2235
2124
|
}
|
|
2236
2125
|
|
|
2237
2126
|
private createMoveDetail(
|
|
2238
|
-
|
|
2239
|
-
|
|
2127
|
+
refArg: IHarnessSessionListItemRef,
|
|
2128
|
+
toGroupIdArg: string | null,
|
|
2240
2129
|
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;
|
|
2130
|
+
sourceArg: THarnessItemMoveSource,
|
|
2131
|
+
): IHarnessItemMoveDetail {
|
|
2132
|
+
const targetItems = this.sortableItems(this.itemsForGroup(toGroupIdArg));
|
|
2133
|
+
const beforeItem = targetItems[Math.max(0, indexArg)];
|
|
2252
2134
|
return {
|
|
2253
|
-
|
|
2254
|
-
|
|
2255
|
-
|
|
2256
|
-
|
|
2135
|
+
item: itemRef(refArg),
|
|
2136
|
+
fromGroupId: this.groupIdForItem(refArg),
|
|
2137
|
+
toGroupId: toGroupIdArg,
|
|
2138
|
+
beforeItem: beforeItem ? itemRef(beforeItem) : null,
|
|
2139
|
+
source: sourceArg,
|
|
2257
2140
|
};
|
|
2258
2141
|
}
|
|
2259
2142
|
|
|
2143
|
+
private emitItemMove(detailArg: IHarnessItemMoveDetail): void {
|
|
2144
|
+
this.dispatchEvent(
|
|
2145
|
+
new CustomEvent('harness-item-move', { detail: detailArg, bubbles: true, composed: true }),
|
|
2146
|
+
);
|
|
2147
|
+
}
|
|
2148
|
+
|
|
2260
2149
|
private handleKeyboardMove(
|
|
2261
2150
|
eventArg: KeyboardEvent,
|
|
2262
|
-
|
|
2151
|
+
refArg: IHarnessSessionListItemRef,
|
|
2152
|
+
titleArg: string,
|
|
2263
2153
|
groupIdArg: string | null,
|
|
2264
2154
|
): void {
|
|
2265
2155
|
if (!['ArrowUp', 'ArrowDown', 'Home', 'End'].includes(eventArg.key)) return;
|
|
2266
2156
|
eventArg.preventDefault();
|
|
2267
2157
|
eventArg.stopPropagation();
|
|
2268
|
-
const
|
|
2269
|
-
const sourceIndex =
|
|
2158
|
+
const items = this.sortableItems(this.itemsForGroup(groupIdArg), false);
|
|
2159
|
+
const sourceIndex = items.findIndex((item) => sameItem(item, refArg));
|
|
2270
2160
|
if (sourceIndex < 0) return;
|
|
2271
|
-
const sectionIds: Array<string | null> = [...this.
|
|
2161
|
+
const sectionIds: Array<string | null> = [...this.effectiveGroups.map((group) => group.id), null];
|
|
2272
2162
|
const sectionIndex = sectionIds.indexOf(groupIdArg);
|
|
2273
2163
|
const adjacentGroupId = eventArg.key === 'ArrowUp' && sourceIndex === 0 && sectionIndex > 0
|
|
2274
2164
|
? sectionIds[sectionIndex - 1]
|
|
2275
2165
|
: eventArg.key === 'ArrowDown'
|
|
2276
|
-
&& sourceIndex ===
|
|
2166
|
+
&& sourceIndex === items.length - 1
|
|
2277
2167
|
&& sectionIndex >= 0
|
|
2278
2168
|
&& sectionIndex < sectionIds.length - 1
|
|
2279
2169
|
? sectionIds[sectionIndex + 1]
|
|
2280
2170
|
: undefined;
|
|
2281
2171
|
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
|
-
};
|
|
2172
|
+
const targetItems = this.sortableItems(this.itemsForGroup(adjacentGroupId), false);
|
|
2173
|
+
const targetIndex = eventArg.key === 'ArrowUp' ? targetItems.length : 0;
|
|
2174
|
+
const beforeItem = targetItems[targetIndex];
|
|
2291
2175
|
const groupName = adjacentGroupId === null
|
|
2292
2176
|
? 'Ungrouped'
|
|
2293
|
-
: this.
|
|
2294
|
-
this.moveAnnouncement = `Moved ${
|
|
2295
|
-
this.keyboardMoveFocus = {
|
|
2296
|
-
this.
|
|
2297
|
-
|
|
2298
|
-
|
|
2177
|
+
: this.effectiveGroups.find((group) => group.id === adjacentGroupId)?.name ?? adjacentGroupId;
|
|
2178
|
+
this.moveAnnouncement = `Moved ${titleArg} to ${groupName}.`;
|
|
2179
|
+
this.keyboardMoveFocus = { item: itemRef(refArg), groupId: adjacentGroupId };
|
|
2180
|
+
this.emitItemMove({
|
|
2181
|
+
item: itemRef(refArg),
|
|
2182
|
+
fromGroupId: groupIdArg,
|
|
2183
|
+
toGroupId: adjacentGroupId,
|
|
2184
|
+
beforeItem: beforeItem ? itemRef(beforeItem) : null,
|
|
2185
|
+
source: 'keyboard',
|
|
2186
|
+
});
|
|
2299
2187
|
return;
|
|
2300
2188
|
}
|
|
2301
|
-
const remaining =
|
|
2189
|
+
const remaining = items.filter((item) => !sameItem(item, refArg));
|
|
2302
2190
|
const targetIndex = eventArg.key === 'Home'
|
|
2303
2191
|
? 0
|
|
2304
2192
|
: eventArg.key === 'End'
|
|
@@ -2307,27 +2195,30 @@ export class DeesHarnessSessionList extends DeesElement {
|
|
|
2307
2195
|
? Math.max(0, sourceIndex - 1)
|
|
2308
2196
|
: Math.min(remaining.length, sourceIndex + 1);
|
|
2309
2197
|
if (targetIndex === sourceIndex) return;
|
|
2310
|
-
const
|
|
2311
|
-
|
|
2312
|
-
|
|
2313
|
-
|
|
2314
|
-
|
|
2315
|
-
|
|
2316
|
-
|
|
2317
|
-
|
|
2318
|
-
|
|
2319
|
-
|
|
2320
|
-
|
|
2321
|
-
|
|
2322
|
-
|
|
2323
|
-
|
|
2198
|
+
const beforeItem = remaining[targetIndex];
|
|
2199
|
+
this.moveAnnouncement = beforeItem
|
|
2200
|
+
? `Moved ${titleArg} before ${beforeItem.title}.`
|
|
2201
|
+
: `Moved ${titleArg} to the end.`;
|
|
2202
|
+
this.emitItemMove({
|
|
2203
|
+
item: itemRef(refArg),
|
|
2204
|
+
fromGroupId: groupIdArg,
|
|
2205
|
+
toGroupId: groupIdArg,
|
|
2206
|
+
beforeItem: beforeItem ? itemRef(beforeItem) : null,
|
|
2207
|
+
source: 'keyboard',
|
|
2208
|
+
});
|
|
2209
|
+
}
|
|
2210
|
+
|
|
2211
|
+
private rowKey(rowArg: HTMLElement): string {
|
|
2212
|
+
return `${rowArg.dataset.itemKind}:${rowArg.dataset.itemId}`;
|
|
2324
2213
|
}
|
|
2325
2214
|
|
|
2326
|
-
private
|
|
2215
|
+
private captureRowRects(): Map<string, DOMRect> {
|
|
2327
2216
|
const result = new Map<string, DOMRect>();
|
|
2328
|
-
|
|
2329
|
-
|
|
2330
|
-
|
|
2217
|
+
const draggingKey = this.draggingItem ? itemKey(this.draggingItem) : '';
|
|
2218
|
+
for (const row of this.shadowRoot?.querySelectorAll<HTMLElement>('[data-item-kind][data-item-id]') ?? []) {
|
|
2219
|
+
const key = this.rowKey(row);
|
|
2220
|
+
if (key === draggingKey) continue;
|
|
2221
|
+
result.set(key, row.getBoundingClientRect());
|
|
2331
2222
|
}
|
|
2332
2223
|
return result;
|
|
2333
2224
|
}
|
|
@@ -2335,14 +2226,8 @@ export class DeesHarnessSessionList extends DeesElement {
|
|
|
2335
2226
|
private restoreKeyboardMoveFocus(): void {
|
|
2336
2227
|
const pending = this.keyboardMoveFocus;
|
|
2337
2228
|
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');
|
|
2229
|
+
if (this.groupIdForItem(pending.item) !== pending.groupId) return;
|
|
2230
|
+
const handle = this.rowElementFor(pending.item)?.querySelector<HTMLElement>('.dragHandle');
|
|
2346
2231
|
if (handle) {
|
|
2347
2232
|
handle.focus();
|
|
2348
2233
|
this.keyboardMoveFocus = undefined;
|
|
@@ -2357,35 +2242,52 @@ export class DeesHarnessSessionList extends DeesElement {
|
|
|
2357
2242
|
}
|
|
2358
2243
|
}
|
|
2359
2244
|
|
|
2360
|
-
|
|
2361
|
-
|
|
2362
|
-
const
|
|
2245
|
+
/** Known, de-duplicated targets the host allows; an absent entry means unrestricted. */
|
|
2246
|
+
private eligibleTargets(resourceIdArg: string): IHarnessSessionListItemRef[] | undefined {
|
|
2247
|
+
const declared = this.eligibleTargetsByResourceId[resourceIdArg];
|
|
2248
|
+
if (!declared) return undefined;
|
|
2249
|
+
const itemsByKey = this.itemsByKey;
|
|
2250
|
+
const result: IHarnessSessionListItemRef[] = [];
|
|
2363
2251
|
const seen = new Set<string>();
|
|
2364
|
-
for (const
|
|
2365
|
-
|
|
2366
|
-
|
|
2367
|
-
|
|
2252
|
+
for (const target of declared) {
|
|
2253
|
+
const key = target && itemKey(target);
|
|
2254
|
+
if (
|
|
2255
|
+
!key
|
|
2256
|
+
|| seen.has(key)
|
|
2257
|
+
|| !itemsByKey.has(key)
|
|
2258
|
+
|| sameItem(target, { kind: 'resource', id: resourceIdArg })
|
|
2259
|
+
) continue;
|
|
2260
|
+
seen.add(key);
|
|
2261
|
+
result.push(itemRef(target));
|
|
2368
2262
|
}
|
|
2369
2263
|
return result;
|
|
2370
2264
|
}
|
|
2371
2265
|
|
|
2372
|
-
private
|
|
2373
|
-
|
|
2374
|
-
|
|
2266
|
+
private isResourceTargetEligible(
|
|
2267
|
+
resourceIdArg: string,
|
|
2268
|
+
targetArg: IHarnessSessionListItemRef,
|
|
2269
|
+
): boolean {
|
|
2270
|
+
const eligible = this.eligibleTargets(resourceIdArg);
|
|
2271
|
+
return eligible === undefined || eligible.some((candidate) => sameItem(candidate, targetArg));
|
|
2272
|
+
}
|
|
2273
|
+
|
|
2274
|
+
private itemTitleFor(refArg: IHarnessSessionListItemRef): string {
|
|
2275
|
+
return this.itemsByKey.get(itemKey(refArg))?.title ?? refArg.id;
|
|
2375
2276
|
}
|
|
2376
2277
|
|
|
2377
|
-
private
|
|
2378
|
-
|
|
2278
|
+
private emitSession(nameArg: string, sessionArg: IHarnessSessionMeta): void {
|
|
2279
|
+
const detail: IHarnessSessionEventDetail = { session: sessionArg };
|
|
2280
|
+
this.dispatchEvent(new CustomEvent(nameArg, { detail, bubbles: true, composed: true }));
|
|
2379
2281
|
}
|
|
2380
2282
|
|
|
2381
2283
|
private emitResource(
|
|
2382
2284
|
nameArg: string,
|
|
2383
2285
|
resourceArg: IHarnessResourceMeta,
|
|
2384
|
-
|
|
2286
|
+
targetArg: IHarnessSessionListItemRef | null,
|
|
2385
2287
|
): void {
|
|
2386
2288
|
const detail: IHarnessResourceEventDetail = {
|
|
2387
2289
|
resource: resourceArg,
|
|
2388
|
-
|
|
2290
|
+
target: targetArg,
|
|
2389
2291
|
};
|
|
2390
2292
|
this.dispatchEvent(new CustomEvent(nameArg, { detail, bubbles: true, composed: true }));
|
|
2391
2293
|
}
|
|
@@ -2393,7 +2295,7 @@ export class DeesHarnessSessionList extends DeesElement {
|
|
|
2393
2295
|
private handleResourceContextKey(
|
|
2394
2296
|
eventArg: KeyboardEvent,
|
|
2395
2297
|
resourceArg: IHarnessResourceMeta,
|
|
2396
|
-
|
|
2298
|
+
targetArg: IHarnessSessionListItemRef | null,
|
|
2397
2299
|
): void {
|
|
2398
2300
|
if (!(eventArg.key === 'ContextMenu' || (eventArg.key === 'F10' && eventArg.shiftKey))) return;
|
|
2399
2301
|
eventArg.preventDefault();
|
|
@@ -2401,7 +2303,7 @@ export class DeesHarnessSessionList extends DeesElement {
|
|
|
2401
2303
|
this.emitResourceContext(
|
|
2402
2304
|
eventArg,
|
|
2403
2305
|
resourceArg,
|
|
2404
|
-
|
|
2306
|
+
targetArg,
|
|
2405
2307
|
'keyboard',
|
|
2406
2308
|
eventArg.currentTarget as HTMLElement,
|
|
2407
2309
|
);
|
|
@@ -2410,7 +2312,7 @@ export class DeesHarnessSessionList extends DeesElement {
|
|
|
2410
2312
|
private emitResourceContext(
|
|
2411
2313
|
eventArg: MouseEvent | KeyboardEvent,
|
|
2412
2314
|
resourceArg: IHarnessResourceMeta,
|
|
2413
|
-
|
|
2315
|
+
targetArg: IHarnessSessionListItemRef | null,
|
|
2414
2316
|
sourceArg: THarnessResourceContextSource,
|
|
2415
2317
|
focusTargetArg: HTMLElement,
|
|
2416
2318
|
): void {
|
|
@@ -2425,7 +2327,7 @@ export class DeesHarnessSessionList extends DeesElement {
|
|
|
2425
2327
|
].find((className) => focusTargetArg.classList.contains(className)) ?? 'resourcePrimary';
|
|
2426
2328
|
const detail: IHarnessResourceContextDetail = {
|
|
2427
2329
|
resource: resourceArg,
|
|
2428
|
-
|
|
2330
|
+
target: targetArg,
|
|
2429
2331
|
clientX: sourceArg === 'pointer' && eventArg instanceof MouseEvent
|
|
2430
2332
|
? eventArg.clientX
|
|
2431
2333
|
: rect.left + rect.width / 2,
|
|
@@ -2434,9 +2336,9 @@ export class DeesHarnessSessionList extends DeesElement {
|
|
|
2434
2336
|
: rect.bottom,
|
|
2435
2337
|
source: sourceArg,
|
|
2436
2338
|
originalEvent: eventArg,
|
|
2437
|
-
requestAssociation: (
|
|
2339
|
+
requestAssociation: (toTargetArg) => this.requestResourceAssociation(
|
|
2438
2340
|
resourceArg.id,
|
|
2439
|
-
|
|
2341
|
+
toTargetArg,
|
|
2440
2342
|
sourceArg === 'keyboard' ? 'keyboard' : 'action',
|
|
2441
2343
|
),
|
|
2442
2344
|
close: () => this.closeResourceContext(contextGeneration),
|
|
@@ -2462,9 +2364,7 @@ export class DeesHarnessSessionList extends DeesElement {
|
|
|
2462
2364
|
focusTarget.focus();
|
|
2463
2365
|
return;
|
|
2464
2366
|
}
|
|
2465
|
-
const row =
|
|
2466
|
-
this.shadowRoot?.querySelectorAll<HTMLElement>('.resourceRow[data-resource-id]') ?? [],
|
|
2467
|
-
).find((candidate) => candidate.dataset.resourceId === resourceId);
|
|
2367
|
+
const row = this.rowElementFor({ kind: 'resource', id: resourceId });
|
|
2468
2368
|
row?.querySelector<HTMLElement>(`.${focusClass || 'resourcePrimary'}`)?.focus();
|
|
2469
2369
|
});
|
|
2470
2370
|
}
|
|
@@ -2483,28 +2383,22 @@ export class DeesHarnessSessionList extends DeesElement {
|
|
|
2483
2383
|
this.pendingResourceAssociationRequest = undefined;
|
|
2484
2384
|
return;
|
|
2485
2385
|
}
|
|
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}.`;
|
|
2386
|
+
const currentTarget = this.acceptedResourceAssociations.get(pending.resourceId) ?? null;
|
|
2387
|
+
if (!sameItem(currentTarget, pending.toTarget) && !(currentTarget === null && pending.toTarget === null)) {
|
|
2388
|
+
if (!sameItem(currentTarget, pending.fromTarget) && !(currentTarget === null && pending.fromTarget === null)) {
|
|
2389
|
+
const controlledTitle = currentTarget ? this.itemTitleFor(currentTarget) : 'unattached';
|
|
2390
|
+
this.moveAnnouncement = `Association request for ${pending.resourceTitle} was not applied; controlled association is ${controlledTitle}.`;
|
|
2493
2391
|
this.pendingResourceAssociationRequest = undefined;
|
|
2494
2392
|
}
|
|
2495
2393
|
return;
|
|
2496
2394
|
}
|
|
2497
|
-
const
|
|
2498
|
-
|
|
2499
|
-
: '';
|
|
2500
|
-
const toSession = pending.toSessionId
|
|
2501
|
-
? this.sessionsById.get(pending.toSessionId)?.title ?? pending.toSessionId
|
|
2502
|
-
: '';
|
|
2395
|
+
const fromTitle = pending.fromTarget ? this.itemTitleFor(pending.fromTarget) : '';
|
|
2396
|
+
const toTitle = pending.toTarget ? this.itemTitleFor(pending.toTarget) : '';
|
|
2503
2397
|
this.moveAnnouncement = pending.intent === 'attach'
|
|
2504
|
-
? `${pending.resourceTitle} is now attached to ${
|
|
2398
|
+
? `${pending.resourceTitle} is now attached to ${toTitle}.`
|
|
2505
2399
|
: pending.intent === 'detach'
|
|
2506
|
-
? `${pending.resourceTitle} is now detached from ${
|
|
2507
|
-
: `${pending.resourceTitle} is now reassigned from ${
|
|
2400
|
+
? `${pending.resourceTitle} is now detached from ${fromTitle}.`
|
|
2401
|
+
: `${pending.resourceTitle} is now reassigned from ${fromTitle} to ${toTitle}.`;
|
|
2508
2402
|
this.pendingResourceAssociationRequest = undefined;
|
|
2509
2403
|
}
|
|
2510
2404
|
|
|
@@ -2513,12 +2407,8 @@ export class DeesHarnessSessionList extends DeesElement {
|
|
|
2513
2407
|
completedArg: boolean,
|
|
2514
2408
|
): string {
|
|
2515
2409
|
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
|
-
: '';
|
|
2410
|
+
const fromTitle = detailArg.fromTarget ? this.itemTitleFor(detailArg.fromTarget) : '';
|
|
2411
|
+
const toTitle = detailArg.toTarget ? this.itemTitleFor(detailArg.toTarget) : '';
|
|
2522
2412
|
const verb = completedArg ? 'completed' : 'requested';
|
|
2523
2413
|
return detailArg.intent === 'attach'
|
|
2524
2414
|
? `Attach ${verb} for ${resourceTitle} to ${toTitle}.`
|
|
@@ -2527,7 +2417,7 @@ export class DeesHarnessSessionList extends DeesElement {
|
|
|
2527
2417
|
: `Reassign ${verb} for ${resourceTitle} from ${fromTitle} to ${toTitle}.`;
|
|
2528
2418
|
}
|
|
2529
2419
|
|
|
2530
|
-
private async
|
|
2420
|
+
private async animateRowReflow(
|
|
2531
2421
|
previousRectsArg: Map<string, DOMRect>,
|
|
2532
2422
|
generationArg: number,
|
|
2533
2423
|
): Promise<void> {
|
|
@@ -2538,15 +2428,16 @@ export class DeesHarnessSessionList extends DeesElement {
|
|
|
2538
2428
|
|| globalThis.matchMedia?.('(prefers-reduced-motion: reduce)').matches
|
|
2539
2429
|
) return;
|
|
2540
2430
|
this.cancelReflowAnimations();
|
|
2541
|
-
|
|
2542
|
-
|
|
2543
|
-
|
|
2544
|
-
|
|
2431
|
+
const draggingKey = this.draggingItem ? itemKey(this.draggingItem) : '';
|
|
2432
|
+
for (const row of this.shadowRoot?.querySelectorAll<HTMLElement>('[data-item-kind][data-item-id]') ?? []) {
|
|
2433
|
+
const key = this.rowKey(row);
|
|
2434
|
+
if (key === draggingKey) continue;
|
|
2435
|
+
const previous = previousRectsArg.get(key);
|
|
2545
2436
|
if (!previous) continue;
|
|
2546
|
-
const current =
|
|
2437
|
+
const current = row.getBoundingClientRect();
|
|
2547
2438
|
const deltaY = previous.top - current.top;
|
|
2548
2439
|
if (Math.abs(deltaY) < 0.5) continue;
|
|
2549
|
-
const animation =
|
|
2440
|
+
const animation = row.animate(
|
|
2550
2441
|
[
|
|
2551
2442
|
{ transform: `translateY(${deltaY}px)` },
|
|
2552
2443
|
{ transform: 'translateY(0)' },
|
|
@@ -2568,24 +2459,8 @@ export class DeesHarnessSessionList extends DeesElement {
|
|
|
2568
2459
|
private finishPointerDrag(commitArg: boolean): void {
|
|
2569
2460
|
const gesture = this.pointerDragGesture;
|
|
2570
2461
|
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
|
-
}
|
|
2462
|
+
const moveDetail = (commitArg && gesture.phase === 'active' && this.dropSlot)
|
|
2463
|
+
? this.createMoveDetail(gesture.item, this.dropSlot.groupId, this.dropSlot.index, 'pointer')
|
|
2589
2464
|
: undefined;
|
|
2590
2465
|
this.pointerDragGesture = undefined;
|
|
2591
2466
|
window.removeEventListener('keydown', this.handleDragKeyDown, true);
|
|
@@ -2595,19 +2470,14 @@ export class DeesHarnessSessionList extends DeesElement {
|
|
|
2595
2470
|
this.cancelReflowAnimations();
|
|
2596
2471
|
if (gesture.source.matches(':popover-open')) gesture.source.hidePopover();
|
|
2597
2472
|
gesture.source.removeAttribute('popover');
|
|
2598
|
-
gesture.source.classList.remove('dragging'
|
|
2473
|
+
gesture.source.classList.remove('dragging');
|
|
2599
2474
|
for (const property of [
|
|
2600
2475
|
'--drag-preview-width',
|
|
2601
2476
|
'--drag-preview-height',
|
|
2602
2477
|
'--drag-preview-x',
|
|
2603
2478
|
'--drag-preview-y',
|
|
2604
|
-
'--resource-drag-preview-width',
|
|
2605
|
-
'--resource-drag-preview-x',
|
|
2606
|
-
'--resource-drag-preview-y',
|
|
2607
2479
|
]) gesture.source.style.removeProperty(property);
|
|
2608
|
-
this.
|
|
2609
|
-
this.draggingResourceId = '';
|
|
2610
|
-
this.resourceDropTargetSessionId = '';
|
|
2480
|
+
this.draggingItem = undefined;
|
|
2611
2481
|
this.draggedItemHeight = 0;
|
|
2612
2482
|
this.dropSlot = undefined;
|
|
2613
2483
|
try {
|
|
@@ -2617,21 +2487,6 @@ export class DeesHarnessSessionList extends DeesElement {
|
|
|
2617
2487
|
} catch {
|
|
2618
2488
|
// Capture may already be released or the handle may have disconnected.
|
|
2619
2489
|
}
|
|
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 }));
|
|
2490
|
+
if (moveDetail) this.emitItemMove(moveDetail);
|
|
2636
2491
|
}
|
|
2637
2492
|
}
|