@design.estate/dees-catalog 6.6.0 → 6.7.1
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 +1609 -751
- package/dist_ts_web/00_commitinfo_data.js +1 -1
- package/dist_ts_web/elements/00group-harness/dees-harness-message/dees-harness-message.js +26 -15
- package/dist_ts_web/elements/00group-harness/dees-harness-session-list/dees-harness-session-list.d.ts +41 -2
- package/dist_ts_web/elements/00group-harness/dees-harness-session-list/dees-harness-session-list.demo.js +100 -2
- package/dist_ts_web/elements/00group-harness/dees-harness-session-list/dees-harness-session-list.js +846 -40
- package/dist_ts_web/elements/00group-harness/dees-harness-tool-card/dees-harness-tool-card.js +12 -8
- package/dist_ts_web/elements/00group-harness/harness.styles.js +16 -13
- package/dist_ts_web/elements/00group-harness/interfaces.d.ts +56 -0
- package/package.json +2 -2
- package/readme.hints.md +3 -0
- package/readme.md +49 -0
- package/ts_web/00_commitinfo_data.ts +1 -1
- package/ts_web/elements/00group-harness/dees-harness-message/dees-harness-message.ts +25 -14
- package/ts_web/elements/00group-harness/dees-harness-session-list/dees-harness-session-list.demo.ts +123 -1
- package/ts_web/elements/00group-harness/dees-harness-session-list/dees-harness-session-list.ts +952 -40
- package/ts_web/elements/00group-harness/dees-harness-tool-card/dees-harness-tool-card.ts +11 -7
- package/ts_web/elements/00group-harness/harness.styles.ts +15 -12
- package/ts_web/elements/00group-harness/interfaces.ts +67 -0
package/ts_web/elements/00group-harness/dees-harness-session-list/dees-harness-session-list.ts
CHANGED
|
@@ -13,10 +13,17 @@ import {
|
|
|
13
13
|
import { themeDefaultStyles } from '../../00theme.js';
|
|
14
14
|
import type {
|
|
15
15
|
IHarnessGroupContextDetail,
|
|
16
|
+
IHarnessResourceAssociation,
|
|
17
|
+
IHarnessResourceContextDetail,
|
|
18
|
+
IHarnessResourceEventDetail,
|
|
19
|
+
IHarnessResourceMeta,
|
|
16
20
|
IHarnessSessionContextDetail,
|
|
17
21
|
IHarnessSessionGroup,
|
|
18
22
|
IHarnessSessionMeta,
|
|
19
23
|
IHarnessSessionMoveDetail,
|
|
24
|
+
THarnessResourceAssociationInteractionSource,
|
|
25
|
+
THarnessResourceAssociationRequestDetail,
|
|
26
|
+
THarnessResourceContextSource,
|
|
20
27
|
} from '../interfaces.js';
|
|
21
28
|
import { harnessFormatRelativeTime } from '../harness.helpers.js';
|
|
22
29
|
import '../../00group-utility/dees-icon/dees-icon.js';
|
|
@@ -37,6 +44,7 @@ interface IDropSlot {
|
|
|
37
44
|
}
|
|
38
45
|
|
|
39
46
|
interface IPointerDragGesture {
|
|
47
|
+
type: 'session';
|
|
40
48
|
pointerId: number;
|
|
41
49
|
handle: HTMLElement;
|
|
42
50
|
source: HTMLElement;
|
|
@@ -50,6 +58,30 @@ interface IPointerDragGesture {
|
|
|
50
58
|
phase: 'pending' | 'active';
|
|
51
59
|
}
|
|
52
60
|
|
|
61
|
+
interface IResourcePointerDragGesture {
|
|
62
|
+
type: 'resource';
|
|
63
|
+
pointerId: number;
|
|
64
|
+
handle: HTMLElement;
|
|
65
|
+
source: HTMLElement;
|
|
66
|
+
resourceId: string;
|
|
67
|
+
fromSessionId: string | null;
|
|
68
|
+
startClientX: number;
|
|
69
|
+
startClientY: number;
|
|
70
|
+
grabOffsetX: number;
|
|
71
|
+
grabOffsetY: number;
|
|
72
|
+
phase: 'pending' | 'active';
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
type TPointerDragGesture = IPointerDragGesture | IResourcePointerDragGesture;
|
|
76
|
+
|
|
77
|
+
interface IPendingResourceAssociationRequest {
|
|
78
|
+
resourceId: string;
|
|
79
|
+
resourceTitle: string;
|
|
80
|
+
fromSessionId: string | null;
|
|
81
|
+
toSessionId: string | null;
|
|
82
|
+
intent: THarnessResourceAssociationRequestDetail['intent'];
|
|
83
|
+
}
|
|
84
|
+
|
|
53
85
|
interface IKeyboardMoveFocus {
|
|
54
86
|
sessionId: string;
|
|
55
87
|
groupId: string | null;
|
|
@@ -72,7 +104,9 @@ type TSectionRenderEntry =
|
|
|
72
104
|
* `harness-group-context` — the host persists changes and passes fresh
|
|
73
105
|
* `groups` back in. Sessions in no group render in the trailing ungrouped
|
|
74
106
|
* section with pinned entries first, then explicit order, then new sessions by
|
|
75
|
-
* recency. While a search query is active the list is flat.
|
|
107
|
+
* recency. While a search query is active the list is flat. Generic resources
|
|
108
|
+
* project as controlled child rows or into a trailing unattached Resources
|
|
109
|
+
* section; association interactions only emit host requests.
|
|
76
110
|
*/
|
|
77
111
|
@customElement('dees-harness-session-list')
|
|
78
112
|
export class DeesHarnessSessionList extends DeesElement {
|
|
@@ -101,12 +135,31 @@ export class DeesHarnessSessionList extends DeesElement {
|
|
|
101
135
|
@property({ attribute: false })
|
|
102
136
|
accessor ungroupedSessionIds: string[] = [];
|
|
103
137
|
|
|
138
|
+
@property({ attribute: false })
|
|
139
|
+
accessor resources: IHarnessResourceMeta[] = [];
|
|
140
|
+
|
|
141
|
+
@property({ attribute: false })
|
|
142
|
+
accessor resourceAssociations: IHarnessResourceAssociation[] = [];
|
|
143
|
+
|
|
144
|
+
@property()
|
|
145
|
+
accessor selectedResourceId = '';
|
|
146
|
+
|
|
147
|
+
/** Serializable attach/reassign targets. A missing resource entry means no eligible targets. */
|
|
148
|
+
@property({ attribute: false })
|
|
149
|
+
accessor eligibleSessionIdsByResourceId: Record<string, string[]> = {};
|
|
150
|
+
|
|
104
151
|
@state()
|
|
105
152
|
accessor search: string = '';
|
|
106
153
|
|
|
107
154
|
@state()
|
|
108
155
|
accessor draggingSessionId: string = '';
|
|
109
156
|
|
|
157
|
+
@state()
|
|
158
|
+
accessor draggingResourceId = '';
|
|
159
|
+
|
|
160
|
+
@state()
|
|
161
|
+
accessor resourceDropTargetSessionId = '';
|
|
162
|
+
|
|
110
163
|
@state()
|
|
111
164
|
accessor dropSlot: IDropSlot | undefined = undefined;
|
|
112
165
|
|
|
@@ -128,9 +181,16 @@ export class DeesHarnessSessionList extends DeesElement {
|
|
|
128
181
|
@state()
|
|
129
182
|
private accessor moveAnnouncement = '';
|
|
130
183
|
|
|
184
|
+
@state()
|
|
185
|
+
private accessor resourceContextResourceId = '';
|
|
186
|
+
|
|
131
187
|
private reflowGeneration = 0;
|
|
132
188
|
private readonly reflowAnimations = new Set<Animation>();
|
|
133
|
-
private pointerDragGesture:
|
|
189
|
+
private pointerDragGesture: TPointerDragGesture | undefined;
|
|
190
|
+
private pendingResourceAssociationRequest: IPendingResourceAssociationRequest | undefined;
|
|
191
|
+
private resourceContextFocusTarget: HTMLElement | undefined;
|
|
192
|
+
private resourceContextFocusClass = '';
|
|
193
|
+
private resourceContextGeneration = 0;
|
|
134
194
|
private keyboardMoveFocus: IKeyboardMoveFocus | undefined;
|
|
135
195
|
private scrollResizeObserver: ResizeObserver | undefined;
|
|
136
196
|
private observedScrollContainer: HTMLElement | undefined;
|
|
@@ -307,6 +367,8 @@ export class DeesHarnessSessionList extends DeesElement {
|
|
|
307
367
|
}
|
|
308
368
|
|
|
309
369
|
.itemInner {
|
|
370
|
+
position: relative;
|
|
371
|
+
isolation: isolate;
|
|
310
372
|
min-width: 0;
|
|
311
373
|
overflow: hidden;
|
|
312
374
|
border: 1px solid var(--card-inner-border);
|
|
@@ -339,24 +401,34 @@ export class DeesHarnessSessionList extends DeesElement {
|
|
|
339
401
|
|
|
340
402
|
@keyframes workingBackgroundScan {
|
|
341
403
|
from {
|
|
342
|
-
|
|
404
|
+
transform: translate3d(-60%, 0, 0);
|
|
343
405
|
}
|
|
344
406
|
to {
|
|
345
|
-
|
|
407
|
+
transform: translate3d(0, 0, 0);
|
|
346
408
|
}
|
|
347
409
|
}
|
|
348
410
|
|
|
349
411
|
.item.state-working .itemInner {
|
|
350
|
-
background:
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
412
|
+
background: var(--dees-color-bg-primary);
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
.item.state-working .itemInner::before {
|
|
416
|
+
content: '';
|
|
417
|
+
position: absolute;
|
|
418
|
+
z-index: -1;
|
|
419
|
+
inset: 0 auto 0 0;
|
|
420
|
+
width: 250%;
|
|
421
|
+
pointer-events: none;
|
|
422
|
+
background: linear-gradient(
|
|
423
|
+
105deg,
|
|
424
|
+
transparent 40%,
|
|
425
|
+
color-mix(in srgb, var(--session-working) 6%, transparent) 45%,
|
|
426
|
+
color-mix(in srgb, var(--session-working) 20%, transparent) 50%,
|
|
427
|
+
color-mix(in srgb, var(--session-working) 6%, transparent) 55%,
|
|
428
|
+
transparent 60%
|
|
429
|
+
);
|
|
430
|
+
transform: translate3d(-60%, 0, 0);
|
|
431
|
+
will-change: transform;
|
|
360
432
|
animation: workingBackgroundScan 2s linear infinite;
|
|
361
433
|
}
|
|
362
434
|
|
|
@@ -387,6 +459,12 @@ export class DeesHarnessSessionList extends DeesElement {
|
|
|
387
459
|
--card-accent: var(--session-error);
|
|
388
460
|
}
|
|
389
461
|
|
|
462
|
+
.item.resourceDropTarget {
|
|
463
|
+
outline: 2px solid var(--dees-color-accent-primary);
|
|
464
|
+
outline-offset: 2px;
|
|
465
|
+
box-shadow: 0 0 0 5px var(--dees-color-focus-ring), var(--dees-shadow-lg);
|
|
466
|
+
}
|
|
467
|
+
|
|
390
468
|
@keyframes attentionPulse {
|
|
391
469
|
0%, 100% {
|
|
392
470
|
background: color-mix(in srgb, var(--dees-color-bg-primary) 94%, var(--session-attention) 6%);
|
|
@@ -561,6 +639,183 @@ export class DeesHarnessSessionList extends DeesElement {
|
|
|
561
639
|
font-size: var(--dees-font-caption2-size, 10px);
|
|
562
640
|
}
|
|
563
641
|
|
|
642
|
+
.resourceDropLabel {
|
|
643
|
+
margin: 0 10px 10px;
|
|
644
|
+
padding: 6px 9px;
|
|
645
|
+
border-radius: var(--dees-radius-md);
|
|
646
|
+
background: color-mix(in srgb, var(--dees-color-accent-primary) 14%, transparent);
|
|
647
|
+
color: var(--dees-color-accent-primary);
|
|
648
|
+
font-size: var(--dees-font-caption1-size, 11px);
|
|
649
|
+
font-weight: 680;
|
|
650
|
+
text-align: center;
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
.resourceChildren {
|
|
654
|
+
display: grid;
|
|
655
|
+
gap: 4px;
|
|
656
|
+
margin: 0 8px 8px 24px;
|
|
657
|
+
padding: 0;
|
|
658
|
+
list-style: none;
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
.resourceSection {
|
|
662
|
+
margin-top: var(--dees-spacing-md);
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
.resourceSectionTitle {
|
|
666
|
+
margin: 0 0 7px;
|
|
667
|
+
padding: var(--dees-spacing-xs) 6px;
|
|
668
|
+
color: var(--dees-color-text-secondary);
|
|
669
|
+
font-size: var(--dees-font-caption1-size, 11px);
|
|
670
|
+
font-weight: 650;
|
|
671
|
+
letter-spacing: 0.05em;
|
|
672
|
+
text-transform: uppercase;
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
.resourceList {
|
|
676
|
+
display: grid;
|
|
677
|
+
gap: var(--dees-spacing-sm);
|
|
678
|
+
margin: 0;
|
|
679
|
+
padding: 0;
|
|
680
|
+
list-style: none;
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
.resourceRow {
|
|
684
|
+
display: grid;
|
|
685
|
+
grid-template-columns: 26px minmax(0, 1fr) 38px;
|
|
686
|
+
min-width: 0;
|
|
687
|
+
overflow: hidden;
|
|
688
|
+
border: 1px solid var(--dees-color-border-subtle);
|
|
689
|
+
border-radius: var(--dees-radius-lg);
|
|
690
|
+
background: color-mix(in srgb, var(--dees-color-bg-secondary) 76%, transparent);
|
|
691
|
+
color: var(--dees-color-text-primary);
|
|
692
|
+
box-shadow: var(--dees-shadow-xs);
|
|
693
|
+
box-sizing: border-box;
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
.resourceRow.selected {
|
|
697
|
+
border-color: var(--dees-color-accent-primary);
|
|
698
|
+
background: color-mix(in srgb, var(--dees-color-accent-primary) 10%, var(--dees-color-bg-secondary));
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
.resourceRow.resourceDragging {
|
|
702
|
+
position: fixed;
|
|
703
|
+
inset: 0 auto auto 0;
|
|
704
|
+
width: var(--resource-drag-preview-width);
|
|
705
|
+
max-width: none;
|
|
706
|
+
margin: 0;
|
|
707
|
+
opacity: 0.96;
|
|
708
|
+
pointer-events: none;
|
|
709
|
+
transform: translate3d(var(--resource-drag-preview-x), var(--resource-drag-preview-y), 0);
|
|
710
|
+
box-shadow: var(--dees-shadow-xl);
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
.resourceDragHandle,
|
|
714
|
+
.resourcePrimary,
|
|
715
|
+
.resourceActions {
|
|
716
|
+
appearance: none;
|
|
717
|
+
border: 0;
|
|
718
|
+
background: transparent;
|
|
719
|
+
color: inherit;
|
|
720
|
+
font-family: inherit;
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
.resourceDragHandle {
|
|
724
|
+
display: grid;
|
|
725
|
+
place-items: center;
|
|
726
|
+
min-width: 26px;
|
|
727
|
+
padding: 0;
|
|
728
|
+
color: var(--dees-color-text-muted);
|
|
729
|
+
cursor: grab;
|
|
730
|
+
touch-action: none;
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
.resourceDragHandle:disabled {
|
|
734
|
+
opacity: 0.35;
|
|
735
|
+
cursor: default;
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
.resourceDragHandle:not(:disabled):active {
|
|
739
|
+
cursor: grabbing;
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
.resourcePrimary {
|
|
743
|
+
display: flex;
|
|
744
|
+
min-width: 0;
|
|
745
|
+
min-height: 38px;
|
|
746
|
+
align-items: center;
|
|
747
|
+
gap: 7px;
|
|
748
|
+
padding: 5px 4px 5px 7px;
|
|
749
|
+
text-align: left;
|
|
750
|
+
cursor: pointer;
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
.resourceIcon {
|
|
754
|
+
flex: 0 0 auto;
|
|
755
|
+
color: var(--dees-color-text-secondary);
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
.resourceCopy {
|
|
759
|
+
min-width: 0;
|
|
760
|
+
flex: 1;
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
.resourceTitle {
|
|
764
|
+
display: block;
|
|
765
|
+
overflow: hidden;
|
|
766
|
+
color: var(--dees-color-text-primary);
|
|
767
|
+
font-size: var(--dees-font-caption1-size, 11px);
|
|
768
|
+
font-weight: 620;
|
|
769
|
+
text-overflow: ellipsis;
|
|
770
|
+
white-space: nowrap;
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
.resourcePreview {
|
|
774
|
+
display: block;
|
|
775
|
+
overflow: hidden;
|
|
776
|
+
margin-top: 1px;
|
|
777
|
+
color: var(--dees-color-text-secondary);
|
|
778
|
+
font-size: var(--dees-font-caption2-size, 10px);
|
|
779
|
+
text-overflow: ellipsis;
|
|
780
|
+
white-space: nowrap;
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
.resourceMeta {
|
|
784
|
+
display: block;
|
|
785
|
+
overflow: hidden;
|
|
786
|
+
margin-top: 1px;
|
|
787
|
+
color: var(--dees-color-text-muted);
|
|
788
|
+
font-size: var(--dees-font-caption2-size, 10px);
|
|
789
|
+
text-overflow: ellipsis;
|
|
790
|
+
white-space: nowrap;
|
|
791
|
+
}
|
|
792
|
+
|
|
793
|
+
.resourceActions {
|
|
794
|
+
display: grid;
|
|
795
|
+
place-items: center;
|
|
796
|
+
min-width: 34px;
|
|
797
|
+
min-height: 34px;
|
|
798
|
+
margin: 2px;
|
|
799
|
+
padding: 0;
|
|
800
|
+
border-radius: var(--dees-radius-md);
|
|
801
|
+
color: var(--dees-color-text-muted);
|
|
802
|
+
cursor: pointer;
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
.resourceActions:hover {
|
|
806
|
+
background: var(--dees-color-hover);
|
|
807
|
+
color: var(--dees-color-text-primary);
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
.resourceDragHandle:focus-visible,
|
|
811
|
+
.resourcePrimary:focus-visible,
|
|
812
|
+
.resourceActions:focus-visible {
|
|
813
|
+
z-index: 1;
|
|
814
|
+
outline: 2px solid var(--dees-color-accent-primary);
|
|
815
|
+
outline-offset: -3px;
|
|
816
|
+
border-radius: var(--dees-radius-md);
|
|
817
|
+
}
|
|
818
|
+
|
|
564
819
|
.visuallyHidden {
|
|
565
820
|
position: absolute;
|
|
566
821
|
width: 1px;
|
|
@@ -680,12 +935,45 @@ export class DeesHarnessSessionList extends DeesElement {
|
|
|
680
935
|
.item,
|
|
681
936
|
.itemInner,
|
|
682
937
|
.item.state-attention .itemInner,
|
|
938
|
+
.item.state-working .itemInner::before,
|
|
683
939
|
.item.state-working .itemHeader .stateDot,
|
|
684
940
|
.scrollShadow,
|
|
685
941
|
.expandToggle dees-icon {
|
|
686
942
|
animation: none;
|
|
687
943
|
transition: none;
|
|
688
944
|
}
|
|
945
|
+
|
|
946
|
+
.item.state-working .itemInner::before {
|
|
947
|
+
transform: translate3d(-60%, 0, 0);
|
|
948
|
+
will-change: auto;
|
|
949
|
+
}
|
|
950
|
+
}
|
|
951
|
+
|
|
952
|
+
@media (pointer: coarse) {
|
|
953
|
+
.resourceRow {
|
|
954
|
+
grid-template-columns: 44px minmax(0, 1fr) 44px;
|
|
955
|
+
}
|
|
956
|
+
|
|
957
|
+
.resourceDragHandle,
|
|
958
|
+
.resourceActions {
|
|
959
|
+
min-width: 44px;
|
|
960
|
+
min-height: 44px;
|
|
961
|
+
margin: 0;
|
|
962
|
+
}
|
|
963
|
+
|
|
964
|
+
.resourcePrimary {
|
|
965
|
+
min-height: 44px;
|
|
966
|
+
}
|
|
967
|
+
}
|
|
968
|
+
|
|
969
|
+
@media (max-width: 280px) {
|
|
970
|
+
.resourceChildren {
|
|
971
|
+
margin-left: 8px;
|
|
972
|
+
}
|
|
973
|
+
|
|
974
|
+
.resourcePreview {
|
|
975
|
+
display: none;
|
|
976
|
+
}
|
|
689
977
|
}
|
|
690
978
|
`,
|
|
691
979
|
];
|
|
@@ -694,6 +982,70 @@ export class DeesHarnessSessionList extends DeesElement {
|
|
|
694
982
|
return new Map(this.sessions.map((session) => [session.id, session]));
|
|
695
983
|
}
|
|
696
984
|
|
|
985
|
+
private get resourcesById(): Map<string, IHarnessResourceMeta> {
|
|
986
|
+
const result = new Map<string, IHarnessResourceMeta>();
|
|
987
|
+
for (const resource of this.resources) {
|
|
988
|
+
if (!resource.id || !resource.kind || !resource.title || result.has(resource.id)) continue;
|
|
989
|
+
result.set(resource.id, resource);
|
|
990
|
+
}
|
|
991
|
+
return result;
|
|
992
|
+
}
|
|
993
|
+
|
|
994
|
+
/** First valid record wins; unknown and duplicate records remain untouched but are not projected. */
|
|
995
|
+
private get acceptedResourceAssociations(): Map<string, string> {
|
|
996
|
+
const resourcesById = this.resourcesById;
|
|
997
|
+
const sessionsById = this.sessionsById;
|
|
998
|
+
const result = new Map<string, string>();
|
|
999
|
+
for (const association of this.resourceAssociations) {
|
|
1000
|
+
if (
|
|
1001
|
+
!resourcesById.has(association.resourceId)
|
|
1002
|
+
|| !sessionsById.has(association.sessionId)
|
|
1003
|
+
|| result.has(association.resourceId)
|
|
1004
|
+
) continue;
|
|
1005
|
+
result.set(association.resourceId, association.sessionId);
|
|
1006
|
+
}
|
|
1007
|
+
return result;
|
|
1008
|
+
}
|
|
1009
|
+
|
|
1010
|
+
private get acceptedResources(): IHarnessResourceMeta[] {
|
|
1011
|
+
return [...this.resourcesById.values()];
|
|
1012
|
+
}
|
|
1013
|
+
|
|
1014
|
+
private resourceMatchesSearch(resourceArg: IHarnessResourceMeta, queryArg: string): boolean {
|
|
1015
|
+
return [resourceArg.id, resourceArg.kind, resourceArg.title, resourceArg.preview]
|
|
1016
|
+
.filter(Boolean)
|
|
1017
|
+
.join(' ')
|
|
1018
|
+
.toLowerCase()
|
|
1019
|
+
.includes(queryArg);
|
|
1020
|
+
}
|
|
1021
|
+
|
|
1022
|
+
private sessionMatchesSearch(sessionArg: IHarnessSessionMeta, queryArg: string): boolean {
|
|
1023
|
+
return [sessionArg.title, sessionArg.preview, sessionArg.id]
|
|
1024
|
+
.filter(Boolean)
|
|
1025
|
+
.join(' ')
|
|
1026
|
+
.toLowerCase()
|
|
1027
|
+
.includes(queryArg);
|
|
1028
|
+
}
|
|
1029
|
+
|
|
1030
|
+
private resourcesForSession(sessionArg: IHarnessSessionMeta): IHarnessResourceMeta[] {
|
|
1031
|
+
const associations = this.acceptedResourceAssociations;
|
|
1032
|
+
const associated = this.acceptedResources.filter(
|
|
1033
|
+
(resource) => associations.get(resource.id) === sessionArg.id,
|
|
1034
|
+
);
|
|
1035
|
+
const query = this.search.trim().toLowerCase();
|
|
1036
|
+
if (!query || this.sessionMatchesSearch(sessionArg, query)) return associated;
|
|
1037
|
+
return associated.filter((resource) => this.resourceMatchesSearch(resource, query));
|
|
1038
|
+
}
|
|
1039
|
+
|
|
1040
|
+
private get unattachedResources(): IHarnessResourceMeta[] {
|
|
1041
|
+
const associations = this.acceptedResourceAssociations;
|
|
1042
|
+
const query = this.search.trim().toLowerCase();
|
|
1043
|
+
return this.acceptedResources.filter((resource) => (
|
|
1044
|
+
!associations.has(resource.id)
|
|
1045
|
+
&& (!query || this.resourceMatchesSearch(resource, query))
|
|
1046
|
+
));
|
|
1047
|
+
}
|
|
1048
|
+
|
|
697
1049
|
private get groupedSessionIds(): Set<string> {
|
|
698
1050
|
const ids = new Set<string>();
|
|
699
1051
|
for (const group of this.groups) {
|
|
@@ -726,12 +1078,19 @@ export class DeesHarnessSessionList extends DeesElement {
|
|
|
726
1078
|
|
|
727
1079
|
private get filteredSessions(): IHarnessSessionMeta[] {
|
|
728
1080
|
const query = this.search.trim().toLowerCase();
|
|
1081
|
+
const associations = this.acceptedResourceAssociations;
|
|
1082
|
+
const matchingAttachedSessionIds = new Set(
|
|
1083
|
+
this.acceptedResources
|
|
1084
|
+
.filter((resource) => query && this.resourceMatchesSearch(resource, query))
|
|
1085
|
+
.map((resource) => associations.get(resource.id))
|
|
1086
|
+
.filter((sessionId): sessionId is string => sessionId !== undefined),
|
|
1087
|
+
);
|
|
729
1088
|
const sorted = [...this.sessions].sort(
|
|
730
1089
|
(left, right) => (right.updatedAt ?? right.createdAt ?? 0) - (left.updatedAt ?? left.createdAt ?? 0),
|
|
731
1090
|
);
|
|
732
1091
|
if (!query) return sorted;
|
|
733
|
-
return sorted.filter(
|
|
734
|
-
|
|
1092
|
+
return sorted.filter(
|
|
1093
|
+
(session) => this.sessionMatchesSearch(session, query) || matchingAttachedSessionIds.has(session.id),
|
|
735
1094
|
);
|
|
736
1095
|
}
|
|
737
1096
|
|
|
@@ -739,6 +1098,61 @@ export class DeesHarnessSessionList extends DeesElement {
|
|
|
739
1098
|
this.shadowRoot?.querySelector<HTMLInputElement>('.searchRow input')?.focus();
|
|
740
1099
|
}
|
|
741
1100
|
|
|
1101
|
+
public requestResourceAssociation(
|
|
1102
|
+
resourceIdArg: string,
|
|
1103
|
+
toSessionIdArg: string | null,
|
|
1104
|
+
sourceArg: THarnessResourceAssociationInteractionSource = 'action',
|
|
1105
|
+
): boolean {
|
|
1106
|
+
const resource = this.resourcesById.get(resourceIdArg);
|
|
1107
|
+
if (!resource) return false;
|
|
1108
|
+
const fromSessionId = this.acceptedResourceAssociations.get(resourceIdArg) ?? null;
|
|
1109
|
+
if (fromSessionId === toSessionIdArg) return false;
|
|
1110
|
+
if (
|
|
1111
|
+
toSessionIdArg !== null
|
|
1112
|
+
&& (!this.sessionsById.has(toSessionIdArg) || !this.isResourceTargetEligible(resourceIdArg, toSessionIdArg))
|
|
1113
|
+
) return false;
|
|
1114
|
+
if (toSessionIdArg === null && fromSessionId === null) return false;
|
|
1115
|
+
const detail: THarnessResourceAssociationRequestDetail = fromSessionId === null
|
|
1116
|
+
? {
|
|
1117
|
+
intent: 'attach',
|
|
1118
|
+
resource,
|
|
1119
|
+
fromSessionId: null,
|
|
1120
|
+
toSessionId: toSessionIdArg!,
|
|
1121
|
+
source: sourceArg,
|
|
1122
|
+
}
|
|
1123
|
+
: toSessionIdArg === null
|
|
1124
|
+
? {
|
|
1125
|
+
intent: 'detach',
|
|
1126
|
+
resource,
|
|
1127
|
+
fromSessionId,
|
|
1128
|
+
toSessionId: null,
|
|
1129
|
+
source: sourceArg,
|
|
1130
|
+
}
|
|
1131
|
+
: {
|
|
1132
|
+
intent: 'reassign',
|
|
1133
|
+
resource,
|
|
1134
|
+
fromSessionId,
|
|
1135
|
+
toSessionId: toSessionIdArg,
|
|
1136
|
+
source: sourceArg,
|
|
1137
|
+
};
|
|
1138
|
+
this.pendingResourceAssociationRequest = {
|
|
1139
|
+
resourceId: resource.id,
|
|
1140
|
+
resourceTitle: resource.title || resource.id,
|
|
1141
|
+
fromSessionId,
|
|
1142
|
+
toSessionId: toSessionIdArg,
|
|
1143
|
+
intent: detail.intent,
|
|
1144
|
+
};
|
|
1145
|
+
this.moveAnnouncement = this.resourceAssociationAnnouncement(detail, false);
|
|
1146
|
+
this.dispatchEvent(
|
|
1147
|
+
new CustomEvent('harness-resource-association-request', {
|
|
1148
|
+
detail,
|
|
1149
|
+
bubbles: true,
|
|
1150
|
+
composed: true,
|
|
1151
|
+
}),
|
|
1152
|
+
);
|
|
1153
|
+
return true;
|
|
1154
|
+
}
|
|
1155
|
+
|
|
742
1156
|
public updated(changedProperties: Map<PropertyKey, unknown>): void {
|
|
743
1157
|
super.updated(changedProperties);
|
|
744
1158
|
this.observeScrollGeometry();
|
|
@@ -750,19 +1164,41 @@ export class DeesHarnessSessionList extends DeesElement {
|
|
|
750
1164
|
) {
|
|
751
1165
|
this.finishPointerDrag(false);
|
|
752
1166
|
}
|
|
753
|
-
if (
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
1167
|
+
if (changedProperties.has('sessions')) {
|
|
1168
|
+
const knownIds = new Set(this.sessions.map((session) => session.id));
|
|
1169
|
+
if (
|
|
1170
|
+
this.pointerDragGesture?.type === 'session'
|
|
1171
|
+
&& !knownIds.has(this.pointerDragGesture.sessionId)
|
|
1172
|
+
) {
|
|
1173
|
+
this.finishPointerDrag(false);
|
|
1174
|
+
}
|
|
1175
|
+
const nextExpanded = new Set(
|
|
1176
|
+
[...this.expandedSessionIds].filter((sessionId) => knownIds.has(sessionId)),
|
|
1177
|
+
);
|
|
1178
|
+
if (
|
|
1179
|
+
nextExpanded.size !== this.expandedSessionIds.size
|
|
1180
|
+
|| [...nextExpanded].some((sessionId) => !this.expandedSessionIds.has(sessionId))
|
|
1181
|
+
) {
|
|
1182
|
+
this.expandedSessionIds = nextExpanded;
|
|
1183
|
+
}
|
|
757
1184
|
}
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
1185
|
+
if (changedProperties.has('resources') || changedProperties.has('resourceAssociations')) {
|
|
1186
|
+
const gesture = this.pointerDragGesture;
|
|
1187
|
+
if (gesture?.type === 'resource') {
|
|
1188
|
+
const currentSessionId = this.acceptedResourceAssociations.get(gesture.resourceId) ?? null;
|
|
1189
|
+
if (!this.resourcesById.has(gesture.resourceId) || currentSessionId !== gesture.fromSessionId) {
|
|
1190
|
+
this.finishPointerDrag(false);
|
|
1191
|
+
}
|
|
1192
|
+
}
|
|
1193
|
+
if (
|
|
1194
|
+
this.resourceContextResourceId
|
|
1195
|
+
&& !this.resourcesById.has(this.resourceContextResourceId)
|
|
1196
|
+
) this.closeResourceContext(this.resourceContextGeneration);
|
|
1197
|
+
this.reconcilePendingResourceAssociation();
|
|
1198
|
+
}
|
|
1199
|
+
if (changedProperties.has('eligibleSessionIdsByResourceId')) {
|
|
1200
|
+
const gesture = this.pointerDragGesture;
|
|
1201
|
+
if (gesture?.type === 'resource') this.finishPointerDrag(false);
|
|
766
1202
|
}
|
|
767
1203
|
}
|
|
768
1204
|
|
|
@@ -776,6 +1212,7 @@ export class DeesHarnessSessionList extends DeesElement {
|
|
|
776
1212
|
|
|
777
1213
|
public async disconnectedCallback(): Promise<void> {
|
|
778
1214
|
this.finishPointerDrag(false);
|
|
1215
|
+
this.clearResourceContextState();
|
|
779
1216
|
this.scrollResizeObserver?.disconnect();
|
|
780
1217
|
this.scrollResizeObserver = undefined;
|
|
781
1218
|
this.observedScrollContainer = undefined;
|
|
@@ -789,7 +1226,12 @@ export class DeesHarnessSessionList extends DeesElement {
|
|
|
789
1226
|
<div class="searchRow">
|
|
790
1227
|
<dees-icon class="searchIcon" icon="lucide:Search" iconSize="13"></dees-icon>
|
|
791
1228
|
<input
|
|
792
|
-
placeholder
|
|
1229
|
+
placeholder=${this.acceptedResources.length
|
|
1230
|
+
? 'Search conversations and resources'
|
|
1231
|
+
: 'Search conversations'}
|
|
1232
|
+
aria-label=${this.acceptedResources.length
|
|
1233
|
+
? 'Search conversations and resources'
|
|
1234
|
+
: 'Search conversations'}
|
|
793
1235
|
.value=${this.search}
|
|
794
1236
|
@input=${(event: Event) => {
|
|
795
1237
|
this.search = (event.currentTarget as HTMLInputElement).value;
|
|
@@ -839,9 +1281,16 @@ export class DeesHarnessSessionList extends DeesElement {
|
|
|
839
1281
|
|
|
840
1282
|
private renderFlatList(): TemplateResult {
|
|
841
1283
|
const sessions = this.filteredSessions;
|
|
842
|
-
|
|
1284
|
+
const unattachedResources = this.unattachedResources;
|
|
1285
|
+
if (!sessions.length && !unattachedResources.length) {
|
|
843
1286
|
return html`<div class="emptyNote">
|
|
844
|
-
${this.
|
|
1287
|
+
${this.acceptedResources.length
|
|
1288
|
+
? this.sessions.length
|
|
1289
|
+
? 'No conversations or resources match this search.'
|
|
1290
|
+
: 'No resources match this search.'
|
|
1291
|
+
: this.sessions.length
|
|
1292
|
+
? 'No conversations match this search.'
|
|
1293
|
+
: this.emptyText}
|
|
845
1294
|
</div>`;
|
|
846
1295
|
}
|
|
847
1296
|
return html`
|
|
@@ -852,13 +1301,15 @@ export class DeesHarnessSessionList extends DeesElement {
|
|
|
852
1301
|
(session) => this.renderItem(session, null, -1),
|
|
853
1302
|
)}
|
|
854
1303
|
</div>
|
|
1304
|
+
${this.renderUnattachedResources(unattachedResources)}
|
|
855
1305
|
`;
|
|
856
1306
|
}
|
|
857
1307
|
|
|
858
1308
|
private renderGroupedList(): TemplateResult {
|
|
859
1309
|
const byId = this.sessionsById;
|
|
860
1310
|
const ungrouped = this.ungroupedSessions;
|
|
861
|
-
|
|
1311
|
+
const unattachedResources = this.unattachedResources;
|
|
1312
|
+
if (!this.sessions.length && !this.groups.length && !unattachedResources.length) {
|
|
862
1313
|
return html`<div class="emptyNote">${this.emptyText}</div>`;
|
|
863
1314
|
}
|
|
864
1315
|
return html`
|
|
@@ -917,6 +1368,7 @@ export class DeesHarnessSessionList extends DeesElement {
|
|
|
917
1368
|
`
|
|
918
1369
|
: ''}
|
|
919
1370
|
${this.renderSection(ungrouped, null)}
|
|
1371
|
+
${this.renderUnattachedResources(unattachedResources)}
|
|
920
1372
|
`;
|
|
921
1373
|
}
|
|
922
1374
|
|
|
@@ -1000,12 +1452,16 @@ export class DeesHarnessSessionList extends DeesElement {
|
|
|
1000
1452
|
const state = session.state ?? 'normal';
|
|
1001
1453
|
const detailsId = `session-details-${session.id}`;
|
|
1002
1454
|
const dropIndex = index < 0 ? 0 : index;
|
|
1455
|
+
const resources = this.resourcesForSession(session);
|
|
1456
|
+
const isResourceDropTarget = this.resourceDropTargetSessionId === session.id;
|
|
1003
1457
|
return html`
|
|
1004
1458
|
<article
|
|
1005
1459
|
class="item state-${state} ${expanded ? 'expanded' : ''} ${
|
|
1006
1460
|
session.id === this.selectedSessionId ? 'selected' : ''
|
|
1007
1461
|
} ${
|
|
1008
1462
|
this.draggingSessionId === session.id ? 'dragging' : ''
|
|
1463
|
+
} ${
|
|
1464
|
+
isResourceDropTarget ? 'resourceDropTarget' : ''
|
|
1009
1465
|
}"
|
|
1010
1466
|
data-session-id=${session.id}
|
|
1011
1467
|
data-drop-group-id=${groupId ?? ''}
|
|
@@ -1091,6 +1547,20 @@ export class DeesHarnessSessionList extends DeesElement {
|
|
|
1091
1547
|
<dees-icon icon="lucide:ChevronDown" iconSize="15"></dees-icon>
|
|
1092
1548
|
</button>
|
|
1093
1549
|
</div>
|
|
1550
|
+
${isResourceDropTarget
|
|
1551
|
+
? html`<div class="resourceDropLabel">Associate resource with ${session.title || session.id}</div>`
|
|
1552
|
+
: ''}
|
|
1553
|
+
${resources.length
|
|
1554
|
+
? html`
|
|
1555
|
+
<ul class="resourceChildren" aria-label=${`Resources for ${session.title || session.id}`}>
|
|
1556
|
+
${repeat(
|
|
1557
|
+
resources,
|
|
1558
|
+
(resource) => resource.id,
|
|
1559
|
+
(resource) => this.renderResource(resource, session.id),
|
|
1560
|
+
)}
|
|
1561
|
+
</ul>
|
|
1562
|
+
`
|
|
1563
|
+
: ''}
|
|
1094
1564
|
${expanded
|
|
1095
1565
|
? html`
|
|
1096
1566
|
<div class="itemDetails" id=${detailsId}>
|
|
@@ -1110,6 +1580,129 @@ export class DeesHarnessSessionList extends DeesElement {
|
|
|
1110
1580
|
`;
|
|
1111
1581
|
}
|
|
1112
1582
|
|
|
1583
|
+
private renderUnattachedResources(resourcesArg: IHarnessResourceMeta[]): TemplateResult {
|
|
1584
|
+
if (!resourcesArg.length) return html``;
|
|
1585
|
+
return html`
|
|
1586
|
+
<section class="resourceSection" aria-labelledby="unattached-resources-heading">
|
|
1587
|
+
<h2 class="resourceSectionTitle" id="unattached-resources-heading">Resources</h2>
|
|
1588
|
+
<ul class="resourceList" aria-label="Unattached resources">
|
|
1589
|
+
${repeat(
|
|
1590
|
+
resourcesArg,
|
|
1591
|
+
(resource) => resource.id,
|
|
1592
|
+
(resource) => this.renderResource(resource, null),
|
|
1593
|
+
)}
|
|
1594
|
+
</ul>
|
|
1595
|
+
</section>
|
|
1596
|
+
`;
|
|
1597
|
+
}
|
|
1598
|
+
|
|
1599
|
+
private renderResource(
|
|
1600
|
+
resourceArg: IHarnessResourceMeta,
|
|
1601
|
+
sessionIdArg: string | null,
|
|
1602
|
+
): TemplateResult {
|
|
1603
|
+
const selected = resourceArg.id === this.selectedResourceId;
|
|
1604
|
+
const dragging = resourceArg.id === this.draggingResourceId;
|
|
1605
|
+
const eligibleTargetCount = this.eligibleSessionIds(resourceArg.id).filter(
|
|
1606
|
+
(sessionId) => sessionId !== sessionIdArg,
|
|
1607
|
+
).length;
|
|
1608
|
+
const contextOpen = resourceArg.id === this.resourceContextResourceId;
|
|
1609
|
+
return html`
|
|
1610
|
+
<li
|
|
1611
|
+
class="resourceRow ${selected ? 'selected' : ''} ${dragging ? 'resourceDragging' : ''}"
|
|
1612
|
+
data-resource-id=${resourceArg.id}
|
|
1613
|
+
data-resource-session-id=${sessionIdArg ?? ''}
|
|
1614
|
+
@dblclick=${(event: MouseEvent) => {
|
|
1615
|
+
event.stopPropagation();
|
|
1616
|
+
if (this.eventTargetsResourceControl(event)) return;
|
|
1617
|
+
this.emitResource('harness-resource-open', resourceArg, sessionIdArg);
|
|
1618
|
+
}}
|
|
1619
|
+
@contextmenu=${(event: MouseEvent) => {
|
|
1620
|
+
event.preventDefault();
|
|
1621
|
+
event.stopPropagation();
|
|
1622
|
+
const focusTarget = (event.composedPath().find((target) => (
|
|
1623
|
+
target instanceof HTMLElement
|
|
1624
|
+
&& target.matches('button:not(:disabled)')
|
|
1625
|
+
)) as HTMLElement | undefined)
|
|
1626
|
+
?? (event.currentTarget as HTMLElement).querySelector<HTMLElement>('.resourcePrimary')
|
|
1627
|
+
?? event.currentTarget as HTMLElement;
|
|
1628
|
+
this.emitResourceContext(event, resourceArg, sessionIdArg, 'pointer', focusTarget);
|
|
1629
|
+
}}
|
|
1630
|
+
>
|
|
1631
|
+
<button
|
|
1632
|
+
class="resourceDragHandle"
|
|
1633
|
+
type="button"
|
|
1634
|
+
title=${eligibleTargetCount
|
|
1635
|
+
? 'Drag to associate with an eligible conversation'
|
|
1636
|
+
: 'No eligible conversations'}
|
|
1637
|
+
aria-label=${`Associate ${resourceArg.title || resourceArg.id} by dragging`}
|
|
1638
|
+
aria-keyshortcuts="ContextMenu Shift+F10"
|
|
1639
|
+
?disabled=${eligibleTargetCount === 0}
|
|
1640
|
+
@pointerdown=${(event: PointerEvent) => {
|
|
1641
|
+
this.handleResourcePointerDown(event, resourceArg, sessionIdArg);
|
|
1642
|
+
}}
|
|
1643
|
+
@pointermove=${this.handlePointerMove}
|
|
1644
|
+
@pointerup=${this.handlePointerUp}
|
|
1645
|
+
@pointercancel=${this.handlePointerCancel}
|
|
1646
|
+
@lostpointercapture=${this.handleLostPointerCapture}
|
|
1647
|
+
@keydown=${(event: KeyboardEvent) => {
|
|
1648
|
+
this.handleResourceContextKey(event, resourceArg, sessionIdArg);
|
|
1649
|
+
}}
|
|
1650
|
+
>
|
|
1651
|
+
<dees-icon icon="lucide:GripVertical" iconSize="13"></dees-icon>
|
|
1652
|
+
</button>
|
|
1653
|
+
<button
|
|
1654
|
+
class="resourcePrimary"
|
|
1655
|
+
type="button"
|
|
1656
|
+
aria-current=${selected ? 'true' : 'false'}
|
|
1657
|
+
@click=${() => this.emitResource('harness-resource-select', resourceArg, sessionIdArg)}
|
|
1658
|
+
@keydown=${(event: KeyboardEvent) => {
|
|
1659
|
+
if (event.key === 'Enter') {
|
|
1660
|
+
event.preventDefault();
|
|
1661
|
+
this.emitResource('harness-resource-open', resourceArg, sessionIdArg);
|
|
1662
|
+
return;
|
|
1663
|
+
}
|
|
1664
|
+
this.handleResourceContextKey(event, resourceArg, sessionIdArg);
|
|
1665
|
+
}}
|
|
1666
|
+
>
|
|
1667
|
+
<dees-icon
|
|
1668
|
+
class="resourceIcon"
|
|
1669
|
+
icon=${resourceArg.icon || 'lucide:Box'}
|
|
1670
|
+
iconSize="14"
|
|
1671
|
+
></dees-icon>
|
|
1672
|
+
<span class="resourceCopy">
|
|
1673
|
+
<span class="resourceTitle">${resourceArg.title || resourceArg.id}</span>
|
|
1674
|
+
${resourceArg.preview ? html`<span class="resourcePreview">${resourceArg.preview}</span>` : ''}
|
|
1675
|
+
<span class="resourceMeta">
|
|
1676
|
+
${resourceArg.kind}${resourceArg.state ? ` · ${resourceArg.state}` : ''}${
|
|
1677
|
+
resourceArg.updatedAt || resourceArg.createdAt
|
|
1678
|
+
? ` · ${harnessFormatRelativeTime(resourceArg.updatedAt ?? resourceArg.createdAt)}`
|
|
1679
|
+
: ''
|
|
1680
|
+
}
|
|
1681
|
+
</span>
|
|
1682
|
+
</span>
|
|
1683
|
+
</button>
|
|
1684
|
+
<button
|
|
1685
|
+
class="resourceActions"
|
|
1686
|
+
type="button"
|
|
1687
|
+
title="Resource actions"
|
|
1688
|
+
aria-label=${`Actions for ${resourceArg.title || resourceArg.id}`}
|
|
1689
|
+
aria-haspopup="menu"
|
|
1690
|
+
aria-expanded=${contextOpen ? 'true' : 'false'}
|
|
1691
|
+
aria-keyshortcuts="ContextMenu Shift+F10"
|
|
1692
|
+
@click=${(event: MouseEvent) => {
|
|
1693
|
+
event.stopPropagation();
|
|
1694
|
+
this.emitResourceContext(event, resourceArg, sessionIdArg, 'action', event.currentTarget as HTMLElement);
|
|
1695
|
+
}}
|
|
1696
|
+
@keydown=${(event: KeyboardEvent) => {
|
|
1697
|
+
this.handleResourceContextKey(event, resourceArg, sessionIdArg);
|
|
1698
|
+
}}
|
|
1699
|
+
>
|
|
1700
|
+
<dees-icon icon="lucide:Ellipsis" iconSize="15"></dees-icon>
|
|
1701
|
+
</button>
|
|
1702
|
+
</li>
|
|
1703
|
+
`;
|
|
1704
|
+
}
|
|
1705
|
+
|
|
1113
1706
|
private eventTargetsExpandToggle(event: Event): boolean {
|
|
1114
1707
|
return event.composedPath().some(
|
|
1115
1708
|
(target) => target instanceof HTMLElement && target.classList.contains('expandToggle'),
|
|
@@ -1122,6 +1715,15 @@ export class DeesHarnessSessionList extends DeesElement {
|
|
|
1122
1715
|
);
|
|
1123
1716
|
}
|
|
1124
1717
|
|
|
1718
|
+
private eventTargetsResourceControl(event: Event): boolean {
|
|
1719
|
+
return event.composedPath().some(
|
|
1720
|
+
(target) => target instanceof HTMLElement && (
|
|
1721
|
+
target.classList.contains('resourceDragHandle')
|
|
1722
|
+
|| target.classList.contains('resourceActions')
|
|
1723
|
+
),
|
|
1724
|
+
);
|
|
1725
|
+
}
|
|
1726
|
+
|
|
1125
1727
|
private sessionStateLabel(state: NonNullable<IHarnessSessionMeta['state']>): string {
|
|
1126
1728
|
switch (state) {
|
|
1127
1729
|
case 'working': return 'Working';
|
|
@@ -1186,6 +1788,7 @@ export class DeesHarnessSessionList extends DeesElement {
|
|
|
1186
1788
|
if (!source) return;
|
|
1187
1789
|
const sourceRect = source.getBoundingClientRect();
|
|
1188
1790
|
this.pointerDragGesture = {
|
|
1791
|
+
type: 'session',
|
|
1189
1792
|
pointerId: eventArg.pointerId,
|
|
1190
1793
|
handle,
|
|
1191
1794
|
source,
|
|
@@ -1207,6 +1810,43 @@ export class DeesHarnessSessionList extends DeesElement {
|
|
|
1207
1810
|
}
|
|
1208
1811
|
}
|
|
1209
1812
|
|
|
1813
|
+
private handleResourcePointerDown(
|
|
1814
|
+
eventArg: PointerEvent,
|
|
1815
|
+
resourceArg: IHarnessResourceMeta,
|
|
1816
|
+
fromSessionIdArg: string | null,
|
|
1817
|
+
): void {
|
|
1818
|
+
if (
|
|
1819
|
+
this.pointerDragGesture
|
|
1820
|
+
|| eventArg.button !== 0
|
|
1821
|
+
|| !eventArg.isPrimary
|
|
1822
|
+
|| this.eligibleSessionIds(resourceArg.id).every((sessionId) => sessionId === fromSessionIdArg)
|
|
1823
|
+
) return;
|
|
1824
|
+
const handle = eventArg.currentTarget as HTMLElement;
|
|
1825
|
+
const source = handle.closest<HTMLElement>('.resourceRow');
|
|
1826
|
+
if (!source) return;
|
|
1827
|
+
const sourceRect = source.getBoundingClientRect();
|
|
1828
|
+
this.pointerDragGesture = {
|
|
1829
|
+
type: 'resource',
|
|
1830
|
+
pointerId: eventArg.pointerId,
|
|
1831
|
+
handle,
|
|
1832
|
+
source,
|
|
1833
|
+
resourceId: resourceArg.id,
|
|
1834
|
+
fromSessionId: fromSessionIdArg,
|
|
1835
|
+
startClientX: eventArg.clientX,
|
|
1836
|
+
startClientY: eventArg.clientY,
|
|
1837
|
+
grabOffsetX: eventArg.clientX - sourceRect.left,
|
|
1838
|
+
grabOffsetY: eventArg.clientY - sourceRect.top,
|
|
1839
|
+
phase: 'pending',
|
|
1840
|
+
};
|
|
1841
|
+
window.addEventListener('keydown', this.handleDragKeyDown, true);
|
|
1842
|
+
window.addEventListener('blur', this.handleDragWindowBlur);
|
|
1843
|
+
try {
|
|
1844
|
+
handle.setPointerCapture(eventArg.pointerId);
|
|
1845
|
+
} catch {
|
|
1846
|
+
this.finishPointerDrag(false);
|
|
1847
|
+
}
|
|
1848
|
+
}
|
|
1849
|
+
|
|
1210
1850
|
private readonly handlePointerMove = (eventArg: PointerEvent): void => {
|
|
1211
1851
|
const gesture = this.pointerDragGesture;
|
|
1212
1852
|
if (!gesture || eventArg.pointerId !== gesture.pointerId) return;
|
|
@@ -1221,8 +1861,21 @@ export class DeesHarnessSessionList extends DeesElement {
|
|
|
1221
1861
|
eventArg.clientY - gesture.startClientY,
|
|
1222
1862
|
) < dragThresholdPixels
|
|
1223
1863
|
) return;
|
|
1224
|
-
if (
|
|
1864
|
+
if (
|
|
1865
|
+
gesture.phase === 'pending'
|
|
1866
|
+
&& !(gesture.type === 'session'
|
|
1867
|
+
? this.startPointerDrag(eventArg, gesture)
|
|
1868
|
+
: this.startResourcePointerDrag(eventArg, gesture))
|
|
1869
|
+
) return;
|
|
1225
1870
|
eventArg.preventDefault();
|
|
1871
|
+
if (gesture.type === 'resource') {
|
|
1872
|
+
this.moveResourceDragPreview(eventArg.clientX, eventArg.clientY, gesture);
|
|
1873
|
+
this.dragAutoScrollClientX = eventArg.clientX;
|
|
1874
|
+
this.dragAutoScrollClientY = eventArg.clientY;
|
|
1875
|
+
this.updateDragAutoScrollVelocity();
|
|
1876
|
+
this.updateResourceDropTargetFromPointer(eventArg.clientX, eventArg.clientY);
|
|
1877
|
+
return;
|
|
1878
|
+
}
|
|
1226
1879
|
this.moveDragPreview(eventArg.clientX, eventArg.clientY, gesture);
|
|
1227
1880
|
this.dragAutoScrollClientX = eventArg.clientX;
|
|
1228
1881
|
this.dragAutoScrollClientY = eventArg.clientY;
|
|
@@ -1234,6 +1887,14 @@ export class DeesHarnessSessionList extends DeesElement {
|
|
|
1234
1887
|
const gesture = this.pointerDragGesture;
|
|
1235
1888
|
if (!gesture || eventArg.pointerId !== gesture.pointerId) return;
|
|
1236
1889
|
if (gesture.phase === 'active') {
|
|
1890
|
+
if (gesture.type === 'resource') {
|
|
1891
|
+
this.dragAutoScrollClientX = eventArg.clientX;
|
|
1892
|
+
this.dragAutoScrollClientY = eventArg.clientY;
|
|
1893
|
+
this.moveResourceDragPreview(eventArg.clientX, eventArg.clientY, gesture);
|
|
1894
|
+
this.updateResourceDropTargetFromPointer(eventArg.clientX, eventArg.clientY);
|
|
1895
|
+
this.finishPointerDrag(true);
|
|
1896
|
+
return;
|
|
1897
|
+
}
|
|
1237
1898
|
this.dragAutoScrollClientX = eventArg.clientX;
|
|
1238
1899
|
this.dragAutoScrollClientY = eventArg.clientY;
|
|
1239
1900
|
this.moveDragPreview(eventArg.clientX, eventArg.clientY, gesture);
|
|
@@ -1289,6 +1950,27 @@ export class DeesHarnessSessionList extends DeesElement {
|
|
|
1289
1950
|
return true;
|
|
1290
1951
|
}
|
|
1291
1952
|
|
|
1953
|
+
private startResourcePointerDrag(
|
|
1954
|
+
eventArg: PointerEvent,
|
|
1955
|
+
gestureArg: IResourcePointerDragGesture,
|
|
1956
|
+
): boolean {
|
|
1957
|
+
const sourceRect = gestureArg.source.getBoundingClientRect();
|
|
1958
|
+
gestureArg.source.classList.add('resourceDragging');
|
|
1959
|
+
gestureArg.source.style.setProperty('--resource-drag-preview-width', `${sourceRect.width}px`);
|
|
1960
|
+
this.moveResourceDragPreview(eventArg.clientX, eventArg.clientY, gestureArg);
|
|
1961
|
+
gestureArg.source.setAttribute('popover', 'manual');
|
|
1962
|
+
try {
|
|
1963
|
+
gestureArg.source.showPopover();
|
|
1964
|
+
} catch {
|
|
1965
|
+
this.finishPointerDrag(false);
|
|
1966
|
+
return false;
|
|
1967
|
+
}
|
|
1968
|
+
gestureArg.phase = 'active';
|
|
1969
|
+
this.draggingResourceId = gestureArg.resourceId;
|
|
1970
|
+
this.resourceDropTargetSessionId = '';
|
|
1971
|
+
return true;
|
|
1972
|
+
}
|
|
1973
|
+
|
|
1292
1974
|
private moveDragPreview(
|
|
1293
1975
|
clientXArg: number,
|
|
1294
1976
|
clientYArg: number,
|
|
@@ -1304,6 +1986,34 @@ export class DeesHarnessSessionList extends DeesElement {
|
|
|
1304
1986
|
);
|
|
1305
1987
|
}
|
|
1306
1988
|
|
|
1989
|
+
private moveResourceDragPreview(
|
|
1990
|
+
clientXArg: number,
|
|
1991
|
+
clientYArg: number,
|
|
1992
|
+
gestureArg: IResourcePointerDragGesture,
|
|
1993
|
+
): void {
|
|
1994
|
+
gestureArg.source.style.setProperty(
|
|
1995
|
+
'--resource-drag-preview-x',
|
|
1996
|
+
`${clientXArg - gestureArg.grabOffsetX}px`,
|
|
1997
|
+
);
|
|
1998
|
+
gestureArg.source.style.setProperty(
|
|
1999
|
+
'--resource-drag-preview-y',
|
|
2000
|
+
`${clientYArg - gestureArg.grabOffsetY}px`,
|
|
2001
|
+
);
|
|
2002
|
+
}
|
|
2003
|
+
|
|
2004
|
+
private updateResourceDropTargetFromPointer(clientXArg: number, clientYArg: number): void {
|
|
2005
|
+
const gesture = this.pointerDragGesture;
|
|
2006
|
+
if (gesture?.type !== 'resource' || gesture.phase !== 'active') return;
|
|
2007
|
+
const hit = this.shadowRoot?.elementFromPoint(clientXArg, clientYArg);
|
|
2008
|
+
const target = hit?.closest<HTMLElement>('.item[data-session-id]');
|
|
2009
|
+
const sessionId = target?.dataset.sessionId ?? '';
|
|
2010
|
+
this.resourceDropTargetSessionId = (
|
|
2011
|
+
sessionId
|
|
2012
|
+
&& sessionId !== gesture.fromSessionId
|
|
2013
|
+
&& this.isResourceTargetEligible(gesture.resourceId, sessionId)
|
|
2014
|
+
) ? sessionId : '';
|
|
2015
|
+
}
|
|
2016
|
+
|
|
1307
2017
|
private setDropSlot(groupIdArg: string | null, indexArg: number): void {
|
|
1308
2018
|
const current = this.dropSlot;
|
|
1309
2019
|
if (current && current.groupId === groupIdArg && current.index === indexArg) return;
|
|
@@ -1323,7 +2033,8 @@ export class DeesHarnessSessionList extends DeesElement {
|
|
|
1323
2033
|
|
|
1324
2034
|
private updateDragAutoScrollVelocity(): void {
|
|
1325
2035
|
const container = this.scrollContainer;
|
|
1326
|
-
|
|
2036
|
+
const gesture = this.pointerDragGesture;
|
|
2037
|
+
if (!container || gesture?.phase !== 'active') return;
|
|
1327
2038
|
const rect = container.getBoundingClientRect();
|
|
1328
2039
|
if (this.dragAutoScrollClientX < rect.left || this.dragAutoScrollClientX > rect.right) {
|
|
1329
2040
|
this.stopDragAutoScroll();
|
|
@@ -1356,7 +2067,8 @@ export class DeesHarnessSessionList extends DeesElement {
|
|
|
1356
2067
|
private readonly runDragAutoScroll = (timestampArg: number): void => {
|
|
1357
2068
|
this.dragAutoScrollFrame = undefined;
|
|
1358
2069
|
const container = this.scrollContainer;
|
|
1359
|
-
|
|
2070
|
+
const gesture = this.pointerDragGesture;
|
|
2071
|
+
if (!container || gesture?.phase !== 'active' || this.dragAutoScrollVelocity === 0) return;
|
|
1360
2072
|
const elapsedMs = Math.min(34, Math.max(0, timestampArg - this.dragAutoScrollLastFrame));
|
|
1361
2073
|
this.dragAutoScrollLastFrame = timestampArg;
|
|
1362
2074
|
const previousScrollTop = container.scrollTop;
|
|
@@ -1369,7 +2081,14 @@ export class DeesHarnessSessionList extends DeesElement {
|
|
|
1369
2081
|
this.stopDragAutoScroll();
|
|
1370
2082
|
return;
|
|
1371
2083
|
}
|
|
1372
|
-
|
|
2084
|
+
if (gesture.type === 'resource') {
|
|
2085
|
+
this.updateResourceDropTargetFromPointer(
|
|
2086
|
+
this.dragAutoScrollClientX,
|
|
2087
|
+
this.dragAutoScrollClientY,
|
|
2088
|
+
);
|
|
2089
|
+
} else {
|
|
2090
|
+
this.updateDropSlotFromPointer();
|
|
2091
|
+
}
|
|
1373
2092
|
this.dragAutoScrollFrame = requestAnimationFrame(this.runDragAutoScroll);
|
|
1374
2093
|
};
|
|
1375
2094
|
|
|
@@ -1585,6 +2304,171 @@ export class DeesHarnessSessionList extends DeesElement {
|
|
|
1585
2304
|
}
|
|
1586
2305
|
}
|
|
1587
2306
|
|
|
2307
|
+
private eligibleSessionIds(resourceIdArg: string): string[] {
|
|
2308
|
+
const knownSessionIds = new Set(this.sessions.map((session) => session.id));
|
|
2309
|
+
const result: string[] = [];
|
|
2310
|
+
const seen = new Set<string>();
|
|
2311
|
+
for (const sessionId of this.eligibleSessionIdsByResourceId[resourceIdArg] ?? []) {
|
|
2312
|
+
if (!knownSessionIds.has(sessionId) || seen.has(sessionId)) continue;
|
|
2313
|
+
seen.add(sessionId);
|
|
2314
|
+
result.push(sessionId);
|
|
2315
|
+
}
|
|
2316
|
+
return result;
|
|
2317
|
+
}
|
|
2318
|
+
|
|
2319
|
+
private isResourceTargetEligible(resourceIdArg: string, sessionIdArg: string): boolean {
|
|
2320
|
+
return this.eligibleSessionIds(resourceIdArg).includes(sessionIdArg);
|
|
2321
|
+
}
|
|
2322
|
+
|
|
2323
|
+
private emitResource(
|
|
2324
|
+
nameArg: string,
|
|
2325
|
+
resourceArg: IHarnessResourceMeta,
|
|
2326
|
+
sessionIdArg: string | null,
|
|
2327
|
+
): void {
|
|
2328
|
+
const detail: IHarnessResourceEventDetail = {
|
|
2329
|
+
resource: resourceArg,
|
|
2330
|
+
sessionId: sessionIdArg,
|
|
2331
|
+
};
|
|
2332
|
+
this.dispatchEvent(new CustomEvent(nameArg, { detail, bubbles: true, composed: true }));
|
|
2333
|
+
}
|
|
2334
|
+
|
|
2335
|
+
private handleResourceContextKey(
|
|
2336
|
+
eventArg: KeyboardEvent,
|
|
2337
|
+
resourceArg: IHarnessResourceMeta,
|
|
2338
|
+
sessionIdArg: string | null,
|
|
2339
|
+
): void {
|
|
2340
|
+
if (!(eventArg.key === 'ContextMenu' || (eventArg.key === 'F10' && eventArg.shiftKey))) return;
|
|
2341
|
+
eventArg.preventDefault();
|
|
2342
|
+
eventArg.stopPropagation();
|
|
2343
|
+
this.emitResourceContext(
|
|
2344
|
+
eventArg,
|
|
2345
|
+
resourceArg,
|
|
2346
|
+
sessionIdArg,
|
|
2347
|
+
'keyboard',
|
|
2348
|
+
eventArg.currentTarget as HTMLElement,
|
|
2349
|
+
);
|
|
2350
|
+
}
|
|
2351
|
+
|
|
2352
|
+
private emitResourceContext(
|
|
2353
|
+
eventArg: MouseEvent | KeyboardEvent,
|
|
2354
|
+
resourceArg: IHarnessResourceMeta,
|
|
2355
|
+
sessionIdArg: string | null,
|
|
2356
|
+
sourceArg: THarnessResourceContextSource,
|
|
2357
|
+
focusTargetArg: HTMLElement,
|
|
2358
|
+
): void {
|
|
2359
|
+
const rect = focusTargetArg.getBoundingClientRect();
|
|
2360
|
+
const contextGeneration = ++this.resourceContextGeneration;
|
|
2361
|
+
this.resourceContextResourceId = resourceArg.id;
|
|
2362
|
+
this.resourceContextFocusTarget = focusTargetArg;
|
|
2363
|
+
this.resourceContextFocusClass = [
|
|
2364
|
+
'resourceActions',
|
|
2365
|
+
'resourcePrimary',
|
|
2366
|
+
'resourceDragHandle',
|
|
2367
|
+
].find((className) => focusTargetArg.classList.contains(className)) ?? 'resourcePrimary';
|
|
2368
|
+
const detail: IHarnessResourceContextDetail = {
|
|
2369
|
+
resource: resourceArg,
|
|
2370
|
+
sessionId: sessionIdArg,
|
|
2371
|
+
clientX: sourceArg === 'pointer' && eventArg instanceof MouseEvent
|
|
2372
|
+
? eventArg.clientX
|
|
2373
|
+
: rect.left + rect.width / 2,
|
|
2374
|
+
clientY: sourceArg === 'pointer' && eventArg instanceof MouseEvent
|
|
2375
|
+
? eventArg.clientY
|
|
2376
|
+
: rect.bottom,
|
|
2377
|
+
source: sourceArg,
|
|
2378
|
+
originalEvent: eventArg,
|
|
2379
|
+
requestAssociation: (toSessionIdArg) => this.requestResourceAssociation(
|
|
2380
|
+
resourceArg.id,
|
|
2381
|
+
toSessionIdArg,
|
|
2382
|
+
sourceArg === 'keyboard' ? 'keyboard' : 'action',
|
|
2383
|
+
),
|
|
2384
|
+
close: () => this.closeResourceContext(contextGeneration),
|
|
2385
|
+
};
|
|
2386
|
+
this.dispatchEvent(
|
|
2387
|
+
new CustomEvent('harness-resource-context', { detail, bubbles: true, composed: true }),
|
|
2388
|
+
);
|
|
2389
|
+
}
|
|
2390
|
+
|
|
2391
|
+
private closeResourceContext(contextGenerationArg: number): void {
|
|
2392
|
+
if (
|
|
2393
|
+
!this.isConnected
|
|
2394
|
+
|| contextGenerationArg !== this.resourceContextGeneration
|
|
2395
|
+
) return;
|
|
2396
|
+
const focusTarget = this.resourceContextFocusTarget;
|
|
2397
|
+
const resourceId = this.resourceContextResourceId;
|
|
2398
|
+
const focusClass = this.resourceContextFocusClass;
|
|
2399
|
+
this.clearResourceContextState();
|
|
2400
|
+
const closeGeneration = this.resourceContextGeneration;
|
|
2401
|
+
void this.updateComplete.then(() => {
|
|
2402
|
+
if (!this.isConnected || closeGeneration !== this.resourceContextGeneration) return;
|
|
2403
|
+
if (focusTarget?.isConnected) {
|
|
2404
|
+
focusTarget.focus();
|
|
2405
|
+
return;
|
|
2406
|
+
}
|
|
2407
|
+
const row = Array.from(
|
|
2408
|
+
this.shadowRoot?.querySelectorAll<HTMLElement>('.resourceRow[data-resource-id]') ?? [],
|
|
2409
|
+
).find((candidate) => candidate.dataset.resourceId === resourceId);
|
|
2410
|
+
row?.querySelector<HTMLElement>(`.${focusClass || 'resourcePrimary'}`)?.focus();
|
|
2411
|
+
});
|
|
2412
|
+
}
|
|
2413
|
+
|
|
2414
|
+
private clearResourceContextState(): void {
|
|
2415
|
+
this.resourceContextGeneration += 1;
|
|
2416
|
+
this.resourceContextResourceId = '';
|
|
2417
|
+
this.resourceContextFocusTarget = undefined;
|
|
2418
|
+
this.resourceContextFocusClass = '';
|
|
2419
|
+
}
|
|
2420
|
+
|
|
2421
|
+
private reconcilePendingResourceAssociation(): void {
|
|
2422
|
+
const pending = this.pendingResourceAssociationRequest;
|
|
2423
|
+
if (!pending) return;
|
|
2424
|
+
if (!this.resourcesById.has(pending.resourceId)) {
|
|
2425
|
+
this.pendingResourceAssociationRequest = undefined;
|
|
2426
|
+
return;
|
|
2427
|
+
}
|
|
2428
|
+
const currentSessionId = this.acceptedResourceAssociations.get(pending.resourceId) ?? null;
|
|
2429
|
+
if (currentSessionId !== pending.toSessionId) {
|
|
2430
|
+
if (currentSessionId !== pending.fromSessionId) {
|
|
2431
|
+
const controlledSession = currentSessionId
|
|
2432
|
+
? this.sessionsById.get(currentSessionId)?.title ?? currentSessionId
|
|
2433
|
+
: 'unattached';
|
|
2434
|
+
this.moveAnnouncement = `Association request for ${pending.resourceTitle} was not applied; controlled association is ${controlledSession}.`;
|
|
2435
|
+
this.pendingResourceAssociationRequest = undefined;
|
|
2436
|
+
}
|
|
2437
|
+
return;
|
|
2438
|
+
}
|
|
2439
|
+
const fromSession = pending.fromSessionId
|
|
2440
|
+
? this.sessionsById.get(pending.fromSessionId)?.title ?? pending.fromSessionId
|
|
2441
|
+
: '';
|
|
2442
|
+
const toSession = pending.toSessionId
|
|
2443
|
+
? this.sessionsById.get(pending.toSessionId)?.title ?? pending.toSessionId
|
|
2444
|
+
: '';
|
|
2445
|
+
this.moveAnnouncement = pending.intent === 'attach'
|
|
2446
|
+
? `${pending.resourceTitle} is now attached to ${toSession}.`
|
|
2447
|
+
: pending.intent === 'detach'
|
|
2448
|
+
? `${pending.resourceTitle} is now detached from ${fromSession}.`
|
|
2449
|
+
: `${pending.resourceTitle} is now reassigned from ${fromSession} to ${toSession}.`;
|
|
2450
|
+
this.pendingResourceAssociationRequest = undefined;
|
|
2451
|
+
}
|
|
2452
|
+
|
|
2453
|
+
private resourceAssociationAnnouncement(
|
|
2454
|
+
detailArg: THarnessResourceAssociationRequestDetail,
|
|
2455
|
+
completedArg: boolean,
|
|
2456
|
+
): string {
|
|
2457
|
+
const resourceTitle = detailArg.resource.title || detailArg.resource.id;
|
|
2458
|
+
const fromTitle = detailArg.fromSessionId
|
|
2459
|
+
? this.sessionsById.get(detailArg.fromSessionId)?.title ?? detailArg.fromSessionId
|
|
2460
|
+
: '';
|
|
2461
|
+
const toTitle = detailArg.toSessionId
|
|
2462
|
+
? this.sessionsById.get(detailArg.toSessionId)?.title ?? detailArg.toSessionId
|
|
2463
|
+
: '';
|
|
2464
|
+
const verb = completedArg ? 'completed' : 'requested';
|
|
2465
|
+
return detailArg.intent === 'attach'
|
|
2466
|
+
? `Attach ${verb} for ${resourceTitle} to ${toTitle}.`
|
|
2467
|
+
: detailArg.intent === 'detach'
|
|
2468
|
+
? `Detach ${verb} for ${resourceTitle} from ${fromTitle}.`
|
|
2469
|
+
: `Reassign ${verb} for ${resourceTitle} from ${fromTitle} to ${toTitle}.`;
|
|
2470
|
+
}
|
|
2471
|
+
|
|
1588
2472
|
private async animateCardReflow(
|
|
1589
2473
|
previousRectsArg: Map<string, DOMRect>,
|
|
1590
2474
|
generationArg: number,
|
|
@@ -1626,9 +2510,25 @@ export class DeesHarnessSessionList extends DeesElement {
|
|
|
1626
2510
|
private finishPointerDrag(commitArg: boolean): void {
|
|
1627
2511
|
const gesture = this.pointerDragGesture;
|
|
1628
2512
|
if (!gesture) return;
|
|
1629
|
-
const
|
|
2513
|
+
const sessionMoveDetail = (
|
|
2514
|
+
gesture.type === 'session'
|
|
2515
|
+
&& commitArg
|
|
2516
|
+
&& gesture.phase === 'active'
|
|
2517
|
+
&& this.dropSlot
|
|
2518
|
+
)
|
|
1630
2519
|
? this.createMoveDetail(gesture.sessionId, this.dropSlot.groupId, this.dropSlot.index)
|
|
1631
2520
|
: undefined;
|
|
2521
|
+
const resourceRequest = (
|
|
2522
|
+
gesture.type === 'resource'
|
|
2523
|
+
&& commitArg
|
|
2524
|
+
&& gesture.phase === 'active'
|
|
2525
|
+
&& this.resourceDropTargetSessionId
|
|
2526
|
+
)
|
|
2527
|
+
? {
|
|
2528
|
+
resourceId: gesture.resourceId,
|
|
2529
|
+
toSessionId: this.resourceDropTargetSessionId,
|
|
2530
|
+
}
|
|
2531
|
+
: undefined;
|
|
1632
2532
|
this.pointerDragGesture = undefined;
|
|
1633
2533
|
window.removeEventListener('keydown', this.handleDragKeyDown, true);
|
|
1634
2534
|
window.removeEventListener('blur', this.handleDragWindowBlur);
|
|
@@ -1637,14 +2537,19 @@ export class DeesHarnessSessionList extends DeesElement {
|
|
|
1637
2537
|
this.cancelReflowAnimations();
|
|
1638
2538
|
if (gesture.source.matches(':popover-open')) gesture.source.hidePopover();
|
|
1639
2539
|
gesture.source.removeAttribute('popover');
|
|
1640
|
-
gesture.source.classList.remove('dragging');
|
|
2540
|
+
gesture.source.classList.remove('dragging', 'resourceDragging');
|
|
1641
2541
|
for (const property of [
|
|
1642
2542
|
'--drag-preview-width',
|
|
1643
2543
|
'--drag-preview-height',
|
|
1644
2544
|
'--drag-preview-x',
|
|
1645
2545
|
'--drag-preview-y',
|
|
2546
|
+
'--resource-drag-preview-width',
|
|
2547
|
+
'--resource-drag-preview-x',
|
|
2548
|
+
'--resource-drag-preview-y',
|
|
1646
2549
|
]) gesture.source.style.removeProperty(property);
|
|
1647
2550
|
this.draggingSessionId = '';
|
|
2551
|
+
this.draggingResourceId = '';
|
|
2552
|
+
this.resourceDropTargetSessionId = '';
|
|
1648
2553
|
this.draggedItemHeight = 0;
|
|
1649
2554
|
this.dropSlot = undefined;
|
|
1650
2555
|
try {
|
|
@@ -1654,11 +2559,18 @@ export class DeesHarnessSessionList extends DeesElement {
|
|
|
1654
2559
|
} catch {
|
|
1655
2560
|
// Capture may already be released or the handle may have disconnected.
|
|
1656
2561
|
}
|
|
1657
|
-
if (
|
|
2562
|
+
if (sessionMoveDetail) {
|
|
1658
2563
|
this.dispatchEvent(
|
|
1659
|
-
new CustomEvent('harness-session-move', {
|
|
2564
|
+
new CustomEvent('harness-session-move', {
|
|
2565
|
+
detail: sessionMoveDetail,
|
|
2566
|
+
bubbles: true,
|
|
2567
|
+
composed: true,
|
|
2568
|
+
}),
|
|
1660
2569
|
);
|
|
1661
2570
|
}
|
|
2571
|
+
if (resourceRequest) {
|
|
2572
|
+
this.requestResourceAssociation(resourceRequest.resourceId, resourceRequest.toSessionId, 'drop');
|
|
2573
|
+
}
|
|
1662
2574
|
}
|
|
1663
2575
|
|
|
1664
2576
|
private emit(name: string, session: IHarnessSessionMeta): void {
|