marquee-selection 0.0.11
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/README.html +442 -0
- package/README.md +204 -0
- package/dist/index.d.ts +215 -0
- package/dist/infinite-canvas.es.js +475 -0
- package/dist/infinite-canvas.es.js.map +1 -0
- package/dist/infinite-canvas.umd.js +2 -0
- package/dist/infinite-canvas.umd.js.map +1 -0
- package/dist/marquee-selection.es.js +672 -0
- package/dist/marquee-selection.es.js.map +1 -0
- package/dist/marquee-selection.umd.js +2 -0
- package/dist/marquee-selection.umd.js.map +1 -0
- package/index.html +740 -0
- package/package.json +49 -0
package/index.html
ADDED
|
@@ -0,0 +1,740 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="zh-CN">
|
|
3
|
+
|
|
4
|
+
<head>
|
|
5
|
+
<meta charset="UTF-8">
|
|
6
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
7
|
+
<title>圈选DOM</title>
|
|
8
|
+
<script src="https://cdn.tailwindcss.com"></script>
|
|
9
|
+
<style>
|
|
10
|
+
body {
|
|
11
|
+
font-family: Arial, sans-serif;
|
|
12
|
+
margin: 20px;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
#container {
|
|
16
|
+
width: 100vw;
|
|
17
|
+
height: 100vh;
|
|
18
|
+
border: 1px solid #ccc;
|
|
19
|
+
position: relative;
|
|
20
|
+
user-select: none;
|
|
21
|
+
/* visibility: hidden; */
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
.item {
|
|
25
|
+
display: inline-block;
|
|
26
|
+
width: 100px;
|
|
27
|
+
height: 100px;
|
|
28
|
+
margin: 10px;
|
|
29
|
+
background-color: #007bff;
|
|
30
|
+
color: white;
|
|
31
|
+
text-align: center;
|
|
32
|
+
line-height: 100px;
|
|
33
|
+
cursor: pointer;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/* 圈选后高亮。此类名与 src/index.ts 中 selectedClass 默认为 selected 对应 */
|
|
37
|
+
.selected {
|
|
38
|
+
outline: 1px solid #268aff;
|
|
39
|
+
/* outline-offset: 1px; */
|
|
40
|
+
/* box-shadow: 0 0 0 3px rgba(38, 138, 255, 0.2); */
|
|
41
|
+
/* border-radius: 4px; */
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/* 悬浮高亮样式 */
|
|
45
|
+
.hovered {
|
|
46
|
+
outline: 1px solid rgba(38, 138, 255, 0.7);
|
|
47
|
+
outline-style: dashed;
|
|
48
|
+
background-color: rgba(38, 138, 255, 0.12);
|
|
49
|
+
/* 更浅的投影作为提示 */
|
|
50
|
+
/* box-shadow: 0 0 0 2px rgba(38, 138, 255, 0.12); */
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/* 结果面板 */
|
|
54
|
+
#selection-panel {
|
|
55
|
+
position: fixed;
|
|
56
|
+
right: 16px;
|
|
57
|
+
top: 16px;
|
|
58
|
+
width: 350px;
|
|
59
|
+
max-height: 60vh;
|
|
60
|
+
overflow: auto;
|
|
61
|
+
background: #fff;
|
|
62
|
+
border: 1px solid #e5e7eb;
|
|
63
|
+
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.08);
|
|
64
|
+
border-radius: 8px;
|
|
65
|
+
padding: 10px 12px;
|
|
66
|
+
font-size: 12px;
|
|
67
|
+
line-height: 1.4;
|
|
68
|
+
color: #111827;
|
|
69
|
+
z-index: 2147483647;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
#selection-panel .panel-title {
|
|
73
|
+
font-weight: 600;
|
|
74
|
+
margin-bottom: 8px;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
#selection-panel .group-row {
|
|
78
|
+
display: flex;
|
|
79
|
+
justify-content: space-between;
|
|
80
|
+
padding: 4px 0;
|
|
81
|
+
border-bottom: 1px dashed #eee;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
#selection-panel .muted {
|
|
85
|
+
color: #6b7280;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/* 树状列表样式 */
|
|
89
|
+
.tree {
|
|
90
|
+
margin: 6px 0 8px;
|
|
91
|
+
padding-left: 12px;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
.tree ul {
|
|
95
|
+
list-style: none;
|
|
96
|
+
margin: 0;
|
|
97
|
+
padding-left: 14px;
|
|
98
|
+
border-left: 1px dashed #e5e7eb;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
.tree li {
|
|
102
|
+
margin: 2px 0;
|
|
103
|
+
padding-left: 6px;
|
|
104
|
+
position: relative;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
.tree li::before {
|
|
108
|
+
content: "";
|
|
109
|
+
position: absolute;
|
|
110
|
+
left: -12px;
|
|
111
|
+
top: 10px;
|
|
112
|
+
width: 12px;
|
|
113
|
+
height: 1px;
|
|
114
|
+
background: #e5e7eb;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
.node-label {
|
|
118
|
+
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
|
|
119
|
+
font-size: 11px;
|
|
120
|
+
color: #374151;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
details.group {
|
|
124
|
+
border: 1px solid #f3f4f6;
|
|
125
|
+
border-radius: 6px;
|
|
126
|
+
margin: 8px 0;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
details.group details.group {
|
|
130
|
+
margin: 8px;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
details.group>summary {
|
|
134
|
+
cursor: pointer;
|
|
135
|
+
padding: 6px 8px;
|
|
136
|
+
background: #f9fafb;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
details.group[open]>summary {
|
|
140
|
+
background: #f3f4f6;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
.node-content {
|
|
144
|
+
margin-left: 4px;
|
|
145
|
+
/* tag */
|
|
146
|
+
font-size: 10px;
|
|
147
|
+
color: #6b7280;
|
|
148
|
+
background: #e0e7ff;
|
|
149
|
+
padding: 1px 4px;
|
|
150
|
+
border-radius: 4px;
|
|
151
|
+
}
|
|
152
|
+
</style>
|
|
153
|
+
</head>
|
|
154
|
+
|
|
155
|
+
<body>
|
|
156
|
+
<div id="container">
|
|
157
|
+
<div style="position: relative; width: 674px; height: 796px; overflow: hidden">
|
|
158
|
+
<div style=" position: absolute; z-index: 1; left: 0px; top: 0px; font-size: 34px; font-family: "PingFang SC"; font-weight: 600; line-height: 47.6px; color: rgb(26, 26, 26); justify-content: left; align-items: flex-start; height: 48px; text-align: left; width: 136px; white-space: nowrap; "
|
|
159
|
+
data-id="1332:4545" data-name="专属推荐" data-type="TEXT"> 专属推荐 </div><img
|
|
160
|
+
src="https://figma-alpha-api.s3.us-west-2.amazonaws.com/images/d97ea177-fc53-414e-b12c-a4cfe04992fa"
|
|
161
|
+
style="position: absolute; z-index: 3; left: 550px; top: 11px; width: 28px; height: 25px"
|
|
162
|
+
data-id="1332:4547" data-name="形状" data-type="ICON">
|
|
163
|
+
<div style=" position: absolute; z-index: 4; left: 592px; top: 6px; font-size: 26px; font-family: "PingFang SC"; font-weight: 400; line-height: 36.4px; color: rgb(140, 140, 140); justify-content: left; align-items: flex-start; height: 36px; text-align: left; width: 78px; white-space: nowrap; "
|
|
164
|
+
data-id="1332:4548" data-name="换一批" data-type="TEXT"> 换一批 </div>
|
|
165
|
+
<div style=" position: absolute; z-index: 6; left: 0px; top: 304px; font-size: 24px; font-family: "PingFang SC"; font-weight: 400; line-height: 26px; color: rgb(35, 35, 35); justify-content: left; align-items: flex-start; height: 26px; text-align: left; width: 187px; white-space: nowrap; "
|
|
166
|
+
data-id="1332:4550" data-name="Brooklyn Simm..." data-type="TEXT"> Brooklyn Simm... </div>
|
|
167
|
+
<div style=" position: absolute; z-index: 7; left: 0px; top: 342px; font-size: 22px; font-family: "PingFang SC"; font-weight: 400; line-height: 30.8px; color: rgb(140, 140, 140); justify-content: left; align-items: flex-start; height: 31px; text-align: left; width: 156px; white-space: nowrap; "
|
|
168
|
+
data-id="1332:4551" data-name="“你关注了歌手”" data-type="TEXT"> “你关注了歌手” </div><img
|
|
169
|
+
src="https://figma-alpha-api.s3.us-west-2.amazonaws.com/images/7b54ff4c-4a31-4f62-9bc1-0a9f5e14877a"
|
|
170
|
+
style=" position: absolute; z-index: 9; left: 0px; top: 68px; width: 216px; height: 216px; border-radius: 16px; "
|
|
171
|
+
data-id="1332:4553" data-name="Rectangle 6187" data-type="IMG">
|
|
172
|
+
<div style=" position: absolute; z-index: 10; left: 164px; top: 81px; width: 40px; height: 40px; overflow: hidden; filter: drop-shadow(0px 4px 18px rgba(0, 0, 0, 0.15)); "
|
|
173
|
+
data-id="1332:4554" data-name="Frame 427323199" data-type="CONTAINER"></div><img
|
|
174
|
+
src="https://figma-alpha-api.s3.us-west-2.amazonaws.com/images/e81a7e4e-1b2d-4ba6-ad03-ab9c6593b7a0"
|
|
175
|
+
style="position: absolute; z-index: 11; left: 168px; top: 84px; width: 32px; height: 30px"
|
|
176
|
+
data-id="1332:4555" data-name="Star 1" data-type="ICON"><img
|
|
177
|
+
src="https://figma-alpha-api.s3.us-west-2.amazonaws.com/images/7331967a-7f98-400b-8f39-da98073801ea"
|
|
178
|
+
style="position: absolute; z-index: 12; left: 0px; top: 244px; width: 187px; height: 40px"
|
|
179
|
+
data-id="1332:4556" data-name="Group 44753" data-type="IMG">
|
|
180
|
+
<div style=" position: absolute; z-index: 13; left: 50px; top: 250px; font-size: 22px; font-family: "PingFang SC"; font-weight: 400; line-height: 30.8px; color: rgb(255, 255, 255); justify-content: left; align-items: flex-start; height: 31px; text-align: left; width: 125px; "
|
|
181
|
+
data-id="1332:4561" data-name="王嘉尔 | 邓..." data-type="TEXT"> 王嘉尔 | 邓... </div>
|
|
182
|
+
<div style=" position: absolute; z-index: 15; left: 0px; top: 380px; width: 116px; height: 30px; border-radius: 8px; background-color: rgb(255, 242, 233); "
|
|
183
|
+
data-id="1332:4563" data-name="Rectangle 540" data-type="CONTAINER"></div>
|
|
184
|
+
<div style=" position: absolute; z-index: 16; left: 8px; top: 381px; font-size: 20px; font-family: "PingFang SC"; font-weight: 400; line-height: 28px; color: rgb(255, 124, 36); justify-content: left; align-items: flex-start; height: 28px; text-align: left; width: 100px; white-space: nowrap; "
|
|
185
|
+
data-id="1332:4564" data-name="翻唱获收益" data-type="TEXT"> 翻唱获收益 </div>
|
|
186
|
+
<div style=" position: absolute; z-index: 18; left: 0px; top: 688px; font-size: 24px; font-family: "PingFang SC"; font-weight: 400; line-height: 26px; color: rgb(35, 35, 35); justify-content: left; align-items: flex-start; height: 26px; text-align: left; width: 187px; white-space: nowrap; "
|
|
187
|
+
data-id="1332:4566" data-name="Brooklyn Simm..." data-type="TEXT"> Brooklyn Simm... </div>
|
|
188
|
+
<div style=" position: absolute; z-index: 19; left: 0px; top: 726px; font-size: 22px; font-family: "PingFang SC"; font-weight: 400; line-height: 30.8px; color: rgb(140, 140, 140); justify-content: left; align-items: flex-start; height: 31px; text-align: left; width: 156px; white-space: nowrap; "
|
|
189
|
+
data-id="1332:4567" data-name="“你关注了歌手”" data-type="TEXT"> “你关注了歌手” </div><img
|
|
190
|
+
src="https://figma-alpha-api.s3.us-west-2.amazonaws.com/images/77fef8ae-6c22-4428-a552-12ab3d5d0c92"
|
|
191
|
+
style=" position: absolute; z-index: 21; left: 0px; top: 452px; width: 216px; height: 216px; border-radius: 16px; "
|
|
192
|
+
data-id="1332:4569" data-name="Rectangle 6187" data-type="IMG">
|
|
193
|
+
<div style=" position: absolute; z-index: 22; left: 164px; top: 464px; width: 40px; height: 40px; overflow: hidden; filter: drop-shadow(0px 4px 18px rgba(0, 0, 0, 0.15)); "
|
|
194
|
+
data-id="1332:4570" data-name="Frame 427323199" data-type="CONTAINER"></div><img
|
|
195
|
+
src="https://figma-alpha-api.s3.us-west-2.amazonaws.com/images/e254caf8-dc51-4a88-a193-0179b2145bdf"
|
|
196
|
+
style="position: absolute; z-index: 23; left: 168px; top: 467px; width: 32px; height: 30px"
|
|
197
|
+
data-id="1332:4571" data-name="Star 1" data-type="ICON"><img
|
|
198
|
+
src="https://figma-alpha-api.s3.us-west-2.amazonaws.com/images/9c4d7c17-1b61-4c9c-bf8e-383439c3e9a5"
|
|
199
|
+
style="position: absolute; z-index: 24; left: 0px; top: 628px; width: 187px; height: 40px"
|
|
200
|
+
data-id="1332:4572" data-name="Group 44753" data-type="IMG">
|
|
201
|
+
<div style=" position: absolute; z-index: 25; left: 50px; top: 634px; font-size: 22px; font-family: "PingFang SC"; font-weight: 400; line-height: 30.8px; color: rgb(255, 255, 255); justify-content: left; align-items: flex-start; height: 31px; text-align: left; width: 131px; "
|
|
202
|
+
data-id="1332:4577" data-name="王嘉尔 | 邓..." data-type="TEXT"> 王嘉尔 | 邓... </div>
|
|
203
|
+
<div style=" position: absolute; z-index: 27; left: 0px; top: 764px; width: 116px; height: 30px; border-radius: 8px; background-color: rgb(255, 242, 233); "
|
|
204
|
+
data-id="1332:4579" data-name="Rectangle 540" data-type="CONTAINER"></div>
|
|
205
|
+
<div style=" position: absolute; z-index: 28; left: 8px; top: 765px; font-size: 20px; font-family: "PingFang SC"; font-weight: 400; line-height: 28px; color: rgb(255, 124, 36); justify-content: left; align-items: flex-start; height: 28px; text-align: left; width: 100px; white-space: nowrap; "
|
|
206
|
+
data-id="1332:4580" data-name="翻唱获收益" data-type="TEXT"> 翻唱获收益 </div>
|
|
207
|
+
<div style=" position: absolute; z-index: 30; left: 457px; top: 304px; font-size: 24px; font-family: "PingFang SC"; font-weight: 400; line-height: 26px; color: rgb(35, 35, 35); justify-content: left; align-items: flex-start; height: 26px; text-align: left; width: 187px; white-space: nowrap; "
|
|
208
|
+
data-id="1332:4582" data-name="Brooklyn Simm..." data-type="TEXT"> Brooklyn Simm... </div>
|
|
209
|
+
<div style=" position: absolute; z-index: 31; left: 457px; top: 342px; font-size: 24px; font-family: "PingFang SC"; font-weight: 400; line-height: 33.6px; color: rgb(140, 140, 140); justify-content: left; align-items: flex-start; height: 34px; text-align: left; width: 203px; white-space: nowrap; "
|
|
210
|
+
data-id="1332:4583" data-name="“你唱过的歌手”" data-type="TEXT"> “你唱过的歌手” </div><img
|
|
211
|
+
src="https://figma-alpha-api.s3.us-west-2.amazonaws.com/images/9e211cfc-a225-4c05-bdd0-ab0d112e15be"
|
|
212
|
+
style=" position: absolute; z-index: 32; left: 457px; top: 69px; width: 216px; height: 216px; border-radius: 20px; "
|
|
213
|
+
data-id="1332:4584" data-name="Rectangle 6189" data-type="IMG"><img
|
|
214
|
+
src="https://figma-alpha-api.s3.us-west-2.amazonaws.com/images/dcf36dca-9c50-4f94-bd6b-0222693c1d0d"
|
|
215
|
+
style="position: absolute; z-index: 34; left: 458px; top: 244px; width: 115px; height: 40px"
|
|
216
|
+
data-id="1332:4586" data-name="Rectangle 1284" data-type="IMG">
|
|
217
|
+
<div style=" position: absolute; z-index: 35; left: 508px; top: 248px; font-size: 22px; font-family: "PingFang SC"; font-weight: 400; line-height: 30.8px; color: rgb(255, 255, 255); justify-content: left; align-items: flex-start; height: 31px; text-align: left; width: 50px; white-space: nowrap; "
|
|
218
|
+
data-id="1332:4587" data-name="Ailee" data-type="TEXT"> Ailee </div><img
|
|
219
|
+
src="https://figma-alpha-api.s3.us-west-2.amazonaws.com/images/0a4a4918-0ac0-48bd-a1d3-3e18676a09a2"
|
|
220
|
+
style="position: absolute; z-index: 38; left: 483px; top: 256px; width: 14px; height: 15px"
|
|
221
|
+
data-id="1332:4590" data-name="Polygon 3" data-type="ICON">
|
|
222
|
+
<div style=" position: absolute; z-index: 39; left: 621px; top: 81px; width: 40px; height: 40px; overflow: hidden; filter: drop-shadow(0px 4px 18px rgba(0, 0, 0, 0.15)); "
|
|
223
|
+
data-id="1332:4591" data-name="Frame 340" data-type="CONTAINER"></div><img
|
|
224
|
+
src="https://figma-alpha-api.s3.us-west-2.amazonaws.com/images/7d4add74-87c4-4469-af90-dc49a3d10901"
|
|
225
|
+
style="position: absolute; z-index: 40; left: 625px; top: 84px; width: 32px; height: 30px"
|
|
226
|
+
data-id="1332:4592" data-name="Star 1" data-type="ICON">
|
|
227
|
+
<div style=" position: absolute; z-index: 42; left: 457px; top: 688px; font-size: 24px; font-family: "PingFang SC"; font-weight: 400; line-height: 26px; color: rgb(35, 35, 35); justify-content: left; align-items: flex-start; height: 26px; text-align: left; width: 187px; white-space: nowrap; "
|
|
228
|
+
data-id="1332:4594" data-name="Brooklyn Simm..." data-type="TEXT"> Brooklyn Simm... </div>
|
|
229
|
+
<div style=" position: absolute; z-index: 43; left: 457px; top: 726px; font-size: 24px; font-family: "PingFang SC"; font-weight: 400; line-height: 33.6px; color: rgb(140, 140, 140); justify-content: left; align-items: flex-start; height: 34px; text-align: left; width: 203px; white-space: nowrap; "
|
|
230
|
+
data-id="1332:4595" data-name="“你唱过的歌手”" data-type="TEXT"> “你唱过的歌手” </div><img
|
|
231
|
+
src="https://figma-alpha-api.s3.us-west-2.amazonaws.com/images/b646c719-2c10-4a27-984b-96c2291f81a5"
|
|
232
|
+
style=" position: absolute; z-index: 44; left: 457px; top: 452px; width: 216px; height: 216px; border-radius: 20px; "
|
|
233
|
+
data-id="1332:4596" data-name="Rectangle 6189" data-type="IMG"><img
|
|
234
|
+
src="https://figma-alpha-api.s3.us-west-2.amazonaws.com/images/b5de5fd9-b2d0-4d2d-8f0e-7283044f4d8b"
|
|
235
|
+
style="position: absolute; z-index: 46; left: 458px; top: 627px; width: 115px; height: 40px"
|
|
236
|
+
data-id="1332:4598" data-name="Rectangle 1284" data-type="IMG">
|
|
237
|
+
<div style=" position: absolute; z-index: 47; left: 508px; top: 631px; font-size: 22px; font-family: "PingFang SC"; font-weight: 400; line-height: 30.8px; color: rgb(255, 255, 255); justify-content: left; align-items: flex-start; height: 31px; text-align: left; width: 50px; white-space: nowrap; "
|
|
238
|
+
data-id="1332:4599" data-name="Ailee" data-type="TEXT"> Ailee </div><img
|
|
239
|
+
src="https://figma-alpha-api.s3.us-west-2.amazonaws.com/images/b532516a-c698-4667-8fdf-285a091df1a4"
|
|
240
|
+
style="position: absolute; z-index: 50; left: 483px; top: 639px; width: 14px; height: 15px"
|
|
241
|
+
data-id="1332:4602" data-name="Polygon 3" data-type="ICON">
|
|
242
|
+
<div style=" position: absolute; z-index: 51; left: 621px; top: 464px; width: 40px; height: 40px; overflow: hidden; filter: drop-shadow(0px 4px 18px rgba(0, 0, 0, 0.15)); "
|
|
243
|
+
data-id="1332:4603" data-name="Frame 340" data-type="CONTAINER"></div><img
|
|
244
|
+
src="https://figma-alpha-api.s3.us-west-2.amazonaws.com/images/0fdb105d-057c-4e2c-824d-b6dce362acd6"
|
|
245
|
+
style="position: absolute; z-index: 52; left: 625px; top: 467px; width: 32px; height: 30px"
|
|
246
|
+
data-id="1332:4604" data-name="Star 1" data-type="ICON">
|
|
247
|
+
<div style=" position: absolute; z-index: 54; left: 229px; top: 304px; font-size: 24px; font-family: "PingFang SC"; font-weight: 400; line-height: 26px; color: rgb(35, 35, 35); justify-content: left; align-items: flex-start; height: 26px; text-align: left; width: 187px; white-space: nowrap; "
|
|
248
|
+
data-id="1332:4606" data-name="Brooklyn Simm..." data-type="TEXT"> Brooklyn Simm... </div>
|
|
249
|
+
<div style=" position: absolute; z-index: 55; left: 229px; top: 342px; font-size: 22px; font-family: "PingFang SC"; font-weight: 400; line-height: 30.8px; color: rgb(140, 140, 140); justify-content: left; align-items: flex-start; height: 31px; text-align: left; width: 222px; white-space: nowrap; "
|
|
250
|
+
data-id="1332:4607" data-name="“热门歌曲飙升第一”" data-type="TEXT"> “热门歌曲飙升第一” </div>
|
|
251
|
+
<div style=" position: absolute; z-index: 57; left: 228px; top: 381px; width: 116px; height: 30px; border-radius: 8px; background-color: rgb(255, 242, 233); "
|
|
252
|
+
data-id="1332:4609" data-name="Rectangle 540" data-type="CONTAINER"></div>
|
|
253
|
+
<div style=" position: absolute; z-index: 58; left: 236px; top: 382px; font-size: 20px; font-family: "PingFang SC"; font-weight: 400; line-height: 28px; color: rgb(255, 124, 36); justify-content: left; align-items: flex-start; height: 28px; text-align: left; width: 100px; white-space: nowrap; "
|
|
254
|
+
data-id="1332:4610" data-name="翻唱获收益" data-type="TEXT"> 翻唱获收益 </div><img
|
|
255
|
+
src="https://figma-alpha-api.s3.us-west-2.amazonaws.com/images/080ab0b9-97dc-4347-ab1a-7c52efc3a1da"
|
|
256
|
+
style=" position: absolute; z-index: 59; left: 229px; top: 68px; width: 216px; height: 216px; border-radius: 20px; "
|
|
257
|
+
data-id="1332:4611" data-name="Rectangle 6188" data-type="IMG"><img
|
|
258
|
+
src="https://figma-alpha-api.s3.us-west-2.amazonaws.com/images/8b626f81-832f-4e19-901d-3df7186b98f0"
|
|
259
|
+
style="position: absolute; z-index: 61; left: 229px; top: 244px; width: 115px; height: 40px"
|
|
260
|
+
data-id="1332:4613" data-name="Rectangle 1284" data-type="IMG">
|
|
261
|
+
<div style=" position: absolute; z-index: 62; left: 279px; top: 248px; font-size: 22px; font-family: "PingFang SC"; font-weight: 400; line-height: 30.8px; color: rgb(255, 255, 255); justify-content: left; align-items: flex-start; height: 31px; text-align: left; width: 50px; white-space: nowrap; "
|
|
262
|
+
data-id="1332:4614" data-name="Ailee" data-type="TEXT"> Ailee </div><img
|
|
263
|
+
src="https://figma-alpha-api.s3.us-west-2.amazonaws.com/images/9bfc76e6-c043-4d44-bbd9-8b25b82c3087"
|
|
264
|
+
style="position: absolute; z-index: 65; left: 254px; top: 256px; width: 14px; height: 15px"
|
|
265
|
+
data-id="1332:4617" data-name="Polygon 3" data-type="ICON">
|
|
266
|
+
<div style=" position: absolute; z-index: 66; left: 393px; top: 81px; width: 40px; height: 40px; overflow: hidden; filter: drop-shadow(0px 4px 18px rgba(0, 0, 0, 0.15)); "
|
|
267
|
+
data-id="1332:4618" data-name="Frame 427323199" data-type="CONTAINER"></div><img
|
|
268
|
+
src="https://figma-alpha-api.s3.us-west-2.amazonaws.com/images/33772b18-02e2-48e4-8dbc-01e37286d366"
|
|
269
|
+
style="position: absolute; z-index: 67; left: 397px; top: 84px; width: 32px; height: 30px"
|
|
270
|
+
data-id="1332:4619" data-name="Star 1" data-type="ICON">
|
|
271
|
+
<div style=" position: absolute; z-index: 69; left: 229px; top: 688px; font-size: 24px; font-family: "PingFang SC"; font-weight: 400; line-height: 26px; color: rgb(35, 35, 35); justify-content: left; align-items: flex-start; height: 26px; text-align: left; width: 187px; white-space: nowrap; "
|
|
272
|
+
data-id="1332:4621" data-name="Brooklyn Simm..." data-type="TEXT"> Brooklyn Simm... </div>
|
|
273
|
+
<div style=" position: absolute; z-index: 70; left: 229px; top: 726px; font-size: 22px; font-family: "PingFang SC"; font-weight: 400; line-height: 30.8px; color: rgb(140, 140, 140); justify-content: left; align-items: flex-start; height: 31px; text-align: left; width: 222px; white-space: nowrap; "
|
|
274
|
+
data-id="1332:4622" data-name="“热门歌曲飙升第一”" data-type="TEXT"> “热门歌曲飙升第一” </div>
|
|
275
|
+
<div style=" position: absolute; z-index: 72; left: 228px; top: 765px; width: 116px; height: 30px; border-radius: 8px; background-color: rgb(255, 242, 233); "
|
|
276
|
+
data-id="1332:4624" data-name="Rectangle 540" data-type="CONTAINER"></div>
|
|
277
|
+
<div style=" position: absolute; z-index: 73; left: 236px; top: 766px; font-size: 20px; font-family: "PingFang SC"; font-weight: 400; line-height: 28px; color: rgb(255, 124, 36); justify-content: left; align-items: flex-start; height: 28px; text-align: left; width: 100px; white-space: nowrap; "
|
|
278
|
+
data-id="1332:4625" data-name="翻唱获收益" data-type="TEXT"> 翻唱获收益 </div><img
|
|
279
|
+
src="https://figma-alpha-api.s3.us-west-2.amazonaws.com/images/cb33ef76-bff0-430b-b437-01231b928289"
|
|
280
|
+
style=" position: absolute; z-index: 74; left: 229px; top: 452px; width: 216px; height: 216px; border-radius: 20px; "
|
|
281
|
+
data-id="1332:4626" data-name="Rectangle 6188" data-type="IMG"><img
|
|
282
|
+
src="https://figma-alpha-api.s3.us-west-2.amazonaws.com/images/98a53c9f-0c6f-43b1-8399-f3df1b4d59a3"
|
|
283
|
+
style="position: absolute; z-index: 76; left: 229px; top: 628px; width: 115px; height: 40px"
|
|
284
|
+
data-id="1332:4628" data-name="Rectangle 1284" data-type="IMG">
|
|
285
|
+
<div style=" position: absolute; z-index: 77; left: 279px; top: 632px; font-size: 22px; font-family: "PingFang SC"; font-weight: 400; line-height: 30.8px; color: rgb(255, 255, 255); justify-content: left; align-items: flex-start; height: 31px; text-align: left; width: 50px; white-space: nowrap; "
|
|
286
|
+
data-id="1332:4629" data-name="Ailee" data-type="TEXT"> Ailee </div><img
|
|
287
|
+
src="https://figma-alpha-api.s3.us-west-2.amazonaws.com/images/028cfc63-d000-4731-8700-91df61cda7fd"
|
|
288
|
+
style="position: absolute; z-index: 80; left: 254px; top: 639px; width: 14px; height: 15px"
|
|
289
|
+
data-id="1332:4632" data-name="Polygon 3" data-type="ICON">
|
|
290
|
+
<div style=" position: absolute; z-index: 81; left: 393px; top: 464px; width: 40px; height: 40px; overflow: hidden; filter: drop-shadow(0px 4px 18px rgba(0, 0, 0, 0.15)); "
|
|
291
|
+
data-id="1332:4633" data-name="Frame 427323199" data-type="CONTAINER"></div><img
|
|
292
|
+
src="https://figma-alpha-api.s3.us-west-2.amazonaws.com/images/23e44c65-c515-4d29-96f6-8470068f49a4"
|
|
293
|
+
style="position: absolute; z-index: 82; left: 397px; top: 467px; width: 32px; height: 30px"
|
|
294
|
+
data-id="1332:4634" data-name="Star 1" data-type="ICON">
|
|
295
|
+
</div>
|
|
296
|
+
|
|
297
|
+
</div>
|
|
298
|
+
<div id="selection-panel" style="display:none">
|
|
299
|
+
<div class="panel-title">圈选结果</div>
|
|
300
|
+
<div id="selection-panel-toolbar" style="display:flex;gap:8px;align-items:center;margin-bottom:6px;">
|
|
301
|
+
<button id="btn-toggle-nested"
|
|
302
|
+
style="padding:2px 8px;border:1px solid #e5e7eb;border-radius:6px;background:#f9fafb;cursor:pointer;">
|
|
303
|
+
组嵌套查看
|
|
304
|
+
</button>
|
|
305
|
+
</div>
|
|
306
|
+
<div id="selection-panel-content" class="muted">暂无</div>
|
|
307
|
+
</div>
|
|
308
|
+
<div>
|
|
309
|
+
<input id="group-selector-input" type="text" placeholder="CSS 选择器,如 img 或 [data-type='IMG']"
|
|
310
|
+
style="flex:1;min-width:120px;padding:4px 8px;border:1px solid #e5e7eb;border-radius:6px;outline:none;" />
|
|
311
|
+
<button id="btn-add-group-by-selector"
|
|
312
|
+
style="padding:4px 10px;border:1px solid #10b981;border-radius:6px;background:#10b981;color:#fff;cursor:pointer;">
|
|
313
|
+
成组
|
|
314
|
+
</button>
|
|
315
|
+
</div>
|
|
316
|
+
<script type="module">
|
|
317
|
+
import { InfiniteCanvas, marqueeSelection } from '/src/index.ts'
|
|
318
|
+
// import { InfiniteCanvas } from './dist/infinite-canvas.es.js'
|
|
319
|
+
// import { marqueeSelection } from './dist/marquee-selection.es.js'
|
|
320
|
+
|
|
321
|
+
const container = document.getElementById('container')
|
|
322
|
+
// 初始化无限画布(双指缩放/平移)
|
|
323
|
+
// if (container) {
|
|
324
|
+
// const canvas = new InfiniteCanvas({
|
|
325
|
+
// container,
|
|
326
|
+
// initialScale: 1,
|
|
327
|
+
// minScale: 0.1,
|
|
328
|
+
// maxScale: 6,
|
|
329
|
+
// contentClassName: 'stage-content',
|
|
330
|
+
// // 初始化时让内容完整可见且居中(也可改为 cover 铺满)
|
|
331
|
+
// // autoFit: { mode: 'contain', alignX: 'center', alignY: 'center', padding: 0 },
|
|
332
|
+
// // autoFit: { mode: 'cover', alignX: 'center', alignY: 'top' },
|
|
333
|
+
// // autoRefitOnResize: true,
|
|
334
|
+
// onReady: (container) => {
|
|
335
|
+
// console.log('InfiniteCanvas is ready:', container);
|
|
336
|
+
// container.style.visibility = 'visible'
|
|
337
|
+
// }
|
|
338
|
+
// ,
|
|
339
|
+
// // 使用回调替代事件监听:开始时隐藏覆盖层,结束时显示并刷新
|
|
340
|
+
// onTransformStart: () => {
|
|
341
|
+
// // @ts-ignore
|
|
342
|
+
// if (window.ctrl && typeof window.ctrl.hideOverlays === 'function') {
|
|
343
|
+
// // @ts-ignore
|
|
344
|
+
// window.ctrl.hideOverlays()
|
|
345
|
+
// }
|
|
346
|
+
// },
|
|
347
|
+
// onTransformEnd: () => {
|
|
348
|
+
// // @ts-ignore
|
|
349
|
+
// if (window.ctrl && typeof window.ctrl.showAndRefreshOverlays === 'function') {
|
|
350
|
+
// // @ts-ignore
|
|
351
|
+
// window.ctrl.showAndRefreshOverlays()
|
|
352
|
+
// }
|
|
353
|
+
// }
|
|
354
|
+
// })
|
|
355
|
+
// }
|
|
356
|
+
let nestedMode = false
|
|
357
|
+
let lastSnapshot = null
|
|
358
|
+
// AI 悬浮层层级管理
|
|
359
|
+
let aiZ = 2147492000
|
|
360
|
+
// 可拖动 AI 悬浮层(无遮罩)
|
|
361
|
+
const openAIDialog = (ctx) => {
|
|
362
|
+
const groupIdx = (typeof ctx?.index === 'number' && ctx.index >= 0) ? ctx.index : null
|
|
363
|
+
const panelId = groupIdx !== null ? `ai-dialog-${groupIdx}` : `ai-dialog-${Date.now()}`
|
|
364
|
+
// 若该组已存在面板,直接置顶与聚焦
|
|
365
|
+
let panel = document.getElementById(panelId)
|
|
366
|
+
if (panel) {
|
|
367
|
+
panel.style.zIndex = String(++aiZ)
|
|
368
|
+
const ta = panel.querySelector('textarea')
|
|
369
|
+
ta && ta.focus()
|
|
370
|
+
return
|
|
371
|
+
}
|
|
372
|
+
panel = document.createElement('div')
|
|
373
|
+
panel.id = panelId
|
|
374
|
+
panel.className = 'ai-dialog'
|
|
375
|
+
panel.style.position = 'fixed'
|
|
376
|
+
panel.style.left = '0'
|
|
377
|
+
panel.style.top = '0'
|
|
378
|
+
panel.style.width = '560px'
|
|
379
|
+
panel.style.maxWidth = '92vw'
|
|
380
|
+
panel.style.background = '#fff'
|
|
381
|
+
panel.style.border = '1px solid #e5e7eb'
|
|
382
|
+
panel.style.borderRadius = '10px'
|
|
383
|
+
panel.style.boxShadow = '0 12px 28px rgba(0,0,0,0.18)'
|
|
384
|
+
panel.style.padding = '12px'
|
|
385
|
+
panel.style.fontSize = '14px'
|
|
386
|
+
panel.style.color = '#111827'
|
|
387
|
+
panel.style.display = 'flex'
|
|
388
|
+
panel.style.flexDirection = 'column'
|
|
389
|
+
panel.style.gap = '10px'
|
|
390
|
+
panel.style.zIndex = String(++aiZ)
|
|
391
|
+
panel.style.cursor = 'default'
|
|
392
|
+
|
|
393
|
+
// 标题栏(用于拖拽)
|
|
394
|
+
const head = document.createElement('div')
|
|
395
|
+
head.style.display = 'flex'
|
|
396
|
+
head.style.alignItems = 'center'
|
|
397
|
+
head.style.justifyContent = 'space-between'
|
|
398
|
+
head.style.userSelect = 'none'
|
|
399
|
+
head.style.cursor = 'move'
|
|
400
|
+
const title = document.createElement('div')
|
|
401
|
+
const idx = typeof ctx?.index === 'number' ? ctx.index + 1 : '-'
|
|
402
|
+
const count = (ctx?.group || []).length
|
|
403
|
+
title.textContent = `AI 对话 · 组 ${idx}(元素 ${count})`
|
|
404
|
+
title.style.fontWeight = '600'
|
|
405
|
+
const closeBtn = document.createElement('button')
|
|
406
|
+
closeBtn.textContent = '×'
|
|
407
|
+
closeBtn.style.fontSize = '18px'
|
|
408
|
+
closeBtn.style.lineHeight = '18px'
|
|
409
|
+
closeBtn.style.width = '28px'
|
|
410
|
+
closeBtn.style.height = '28px'
|
|
411
|
+
closeBtn.style.border = 'none'
|
|
412
|
+
closeBtn.style.borderRadius = '6px'
|
|
413
|
+
closeBtn.style.background = 'transparent'
|
|
414
|
+
closeBtn.style.cursor = 'pointer'
|
|
415
|
+
closeBtn.onclick = () => panel.remove()
|
|
416
|
+
head.appendChild(title)
|
|
417
|
+
head.appendChild(closeBtn)
|
|
418
|
+
|
|
419
|
+
const ta = document.createElement('textarea')
|
|
420
|
+
ta.placeholder = '请输入 Prompt(例如:总结这些元素的文本内容,或生成描述)\n(Cmd/Ctrl+Enter 发送)'
|
|
421
|
+
ta.style.width = '100%'
|
|
422
|
+
ta.style.minHeight = '110px'
|
|
423
|
+
ta.style.padding = '8px 10px'
|
|
424
|
+
ta.style.border = '1px solid #e5e7eb'
|
|
425
|
+
ta.style.borderRadius = '8px'
|
|
426
|
+
ta.style.outline = 'none'
|
|
427
|
+
ta.addEventListener('keydown', (e) => {
|
|
428
|
+
if ((e.metaKey || e.ctrlKey) && e.key === 'Enter') submit()
|
|
429
|
+
})
|
|
430
|
+
|
|
431
|
+
const footer = document.createElement('div')
|
|
432
|
+
footer.style.display = 'flex'
|
|
433
|
+
footer.style.justifyContent = 'flex-end'
|
|
434
|
+
footer.style.gap = '8px'
|
|
435
|
+
const send = document.createElement('button')
|
|
436
|
+
send.textContent = '发送'
|
|
437
|
+
send.style.padding = '6px 12px'
|
|
438
|
+
send.style.border = '1px solid #2563eb'
|
|
439
|
+
send.style.background = '#2563eb'
|
|
440
|
+
send.style.color = '#fff'
|
|
441
|
+
send.style.borderRadius = '8px'
|
|
442
|
+
send.style.cursor = 'pointer'
|
|
443
|
+
const submit = () => {
|
|
444
|
+
const prompt = ta.value.trim()
|
|
445
|
+
const snapshot = ctx?.getSnapshot ? ctx.getSnapshot() : null
|
|
446
|
+
console.log('[AI] prompt:', prompt, { groupIndex: ctx?.index, group: ctx?.group, snapshot })
|
|
447
|
+
// TODO: 在此处对接你的 AI 服务
|
|
448
|
+
// panel.remove() // 若发送后需要关闭,可取消注释
|
|
449
|
+
}
|
|
450
|
+
send.onclick = submit
|
|
451
|
+
footer.appendChild(send)
|
|
452
|
+
|
|
453
|
+
panel.appendChild(head)
|
|
454
|
+
panel.appendChild(ta)
|
|
455
|
+
panel.appendChild(footer)
|
|
456
|
+
document.body.appendChild(panel)
|
|
457
|
+
|
|
458
|
+
// 跟随时的基础偏移(相对锚点),用于保持用户拖拽后的相对位置
|
|
459
|
+
const pad = 10
|
|
460
|
+
let deltaX = 0, deltaY = 0
|
|
461
|
+
let initialOriginX = null, initialOriginY = null
|
|
462
|
+
|
|
463
|
+
// 计算锚点矩形:优先使用组外接框(实时快照),其次 toolbar 元素,最后初次 anchorRect
|
|
464
|
+
const getAnchorRect = () => {
|
|
465
|
+
const idx = (typeof ctx?.index === 'number' && ctx.index >= 0) ? ctx.index : null
|
|
466
|
+
if (idx !== null && ctx?.getSnapshot) {
|
|
467
|
+
const snap = ctx.getSnapshot()
|
|
468
|
+
if (snap && snap.type === 'groups') {
|
|
469
|
+
const r = snap.groupRects[idx]
|
|
470
|
+
if (r) return { left: r.left, top: r.top, right: r.left + r.width, bottom: r.top + r.height }
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
if (ctx?.anchorEl) {
|
|
474
|
+
const r = ctx.anchorEl.getBoundingClientRect()
|
|
475
|
+
return { left: r.left, top: r.top, right: r.right, bottom: r.bottom }
|
|
476
|
+
}
|
|
477
|
+
if (ctx?.anchorRect) {
|
|
478
|
+
const r = ctx.anchorRect
|
|
479
|
+
return { left: r.left, top: r.top, right: r.right, bottom: r.bottom }
|
|
480
|
+
}
|
|
481
|
+
return null
|
|
482
|
+
}
|
|
483
|
+
// 初始位置:基于锚点/鼠标定位,避免出屏
|
|
484
|
+
const placeInitial = () => {
|
|
485
|
+
const w = panel.offsetWidth
|
|
486
|
+
const h = panel.offsetHeight
|
|
487
|
+
let px = typeof ctx?.mouseX === 'number' ? ctx.mouseX + pad : window.innerWidth - w - 20
|
|
488
|
+
let py = typeof ctx?.mouseY === 'number' ? ctx.mouseY + pad : 20
|
|
489
|
+
const ar = getAnchorRect()
|
|
490
|
+
if (ar) { px = ar.right + pad; py = Math.max(pad, ar.bottom + pad) }
|
|
491
|
+
// 视口内裁剪
|
|
492
|
+
px = Math.min(Math.max(pad, px), window.innerWidth - w - pad)
|
|
493
|
+
py = Math.min(Math.max(pad, py), window.innerHeight - h - pad)
|
|
494
|
+
panel.style.left = px + 'px'
|
|
495
|
+
panel.style.top = py + 'px'
|
|
496
|
+
// 初始化相对锚点的偏移(作为滚动跟随的持久 delta)
|
|
497
|
+
if (ar) { initialOriginX = ar.right + pad; initialOriginY = ar.bottom + pad }
|
|
498
|
+
else if (typeof ctx?.mouseX === 'number' && typeof ctx?.mouseY === 'number') { initialOriginX = ctx.mouseX + pad; initialOriginY = ctx.mouseY + pad }
|
|
499
|
+
else { initialOriginX = px; initialOriginY = py }
|
|
500
|
+
deltaX = px - initialOriginX
|
|
501
|
+
deltaY = py - initialOriginY
|
|
502
|
+
}
|
|
503
|
+
placeInitial()
|
|
504
|
+
setTimeout(() => ta.focus(), 0)
|
|
505
|
+
// 关闭&清理
|
|
506
|
+
const cleanup = () => {
|
|
507
|
+
window.removeEventListener('keydown', onKey)
|
|
508
|
+
window.removeEventListener('scroll', onScroll, true)
|
|
509
|
+
window.removeEventListener('resize', onScroll)
|
|
510
|
+
}
|
|
511
|
+
// ESC 关闭(仅最顶层)
|
|
512
|
+
const onKey = (e) => {
|
|
513
|
+
if (e.key !== 'Escape') return
|
|
514
|
+
// 只关闭当前最顶层的 AI 面板
|
|
515
|
+
const panels = Array.from(document.querySelectorAll('.ai-dialog'))
|
|
516
|
+
const topZ = panels.reduce((m, el) => Math.max(m, parseInt(getComputedStyle(el).zIndex || '0', 10) || 0), 0)
|
|
517
|
+
const myZ = parseInt(getComputedStyle(panel).zIndex || '0', 10) || 0
|
|
518
|
+
if (myZ === topZ) {
|
|
519
|
+
cleanup()
|
|
520
|
+
panel.remove()
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
window.addEventListener('keydown', onKey)
|
|
524
|
+
|
|
525
|
+
// 拖拽逻辑
|
|
526
|
+
let dragging = false
|
|
527
|
+
let offsetX = 0, offsetY = 0
|
|
528
|
+
const onMouseDown = (e) => {
|
|
529
|
+
dragging = true
|
|
530
|
+
panel.style.zIndex = String(++aiZ)
|
|
531
|
+
const rect = panel.getBoundingClientRect()
|
|
532
|
+
offsetX = e.clientX - rect.left
|
|
533
|
+
offsetY = e.clientY - rect.top
|
|
534
|
+
document.addEventListener('mousemove', onMouseMove)
|
|
535
|
+
document.addEventListener('mouseup', onMouseUp, { once: true })
|
|
536
|
+
}
|
|
537
|
+
const onMouseMove = (e) => {
|
|
538
|
+
if (!dragging) return
|
|
539
|
+
const nx = Math.min(Math.max(0, e.clientX - offsetX), window.innerWidth - panel.offsetWidth)
|
|
540
|
+
const ny = Math.min(Math.max(0, e.clientY - offsetY), window.innerHeight - panel.offsetHeight)
|
|
541
|
+
panel.style.left = nx + 'px'
|
|
542
|
+
panel.style.top = ny + 'px'
|
|
543
|
+
}
|
|
544
|
+
const onMouseUp = () => {
|
|
545
|
+
dragging = false
|
|
546
|
+
document.removeEventListener('mousemove', onMouseMove)
|
|
547
|
+
// 更新拖拽后的相对偏移:后续滚动/缩放按该偏移跟随
|
|
548
|
+
const ar = getAnchorRect()
|
|
549
|
+
const originX = ar ? (ar.right + pad) : (initialOriginX ?? 0)
|
|
550
|
+
const originY = ar ? (ar.bottom + pad) : (initialOriginY ?? 0)
|
|
551
|
+
const left = parseFloat(panel.style.left || '0')
|
|
552
|
+
const top = parseFloat(panel.style.top || '0')
|
|
553
|
+
deltaX = left - originX
|
|
554
|
+
deltaY = top - originY
|
|
555
|
+
}
|
|
556
|
+
head.addEventListener('mousedown', onMouseDown)
|
|
557
|
+
|
|
558
|
+
// 滚动/缩放时跟随组位置(通过快照实时计算)
|
|
559
|
+
const onScroll = () => {
|
|
560
|
+
if (dragging) return
|
|
561
|
+
const ar = getAnchorRect()
|
|
562
|
+
if (!ar) return
|
|
563
|
+
let px = ar.right + pad + deltaX
|
|
564
|
+
let py = ar.bottom + pad + deltaY
|
|
565
|
+
panel.style.left = px + 'px'
|
|
566
|
+
panel.style.top = py + 'px'
|
|
567
|
+
}
|
|
568
|
+
window.addEventListener('scroll', onScroll, true)
|
|
569
|
+
window.addEventListener('resize', onScroll)
|
|
570
|
+
|
|
571
|
+
// 关闭按钮清理
|
|
572
|
+
const origClose = closeBtn.onclick
|
|
573
|
+
closeBtn.onclick = () => { cleanup(); origClose && origClose(null) }
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
|
|
577
|
+
if (container) {
|
|
578
|
+
console.log({ container })
|
|
579
|
+
// 初始化:在容器内,允许框选所有图片 img,也可以改为其他选择器,如 '.card'
|
|
580
|
+
const ctrl = marqueeSelection({
|
|
581
|
+
container,
|
|
582
|
+
selectable: '*',
|
|
583
|
+
// 排除 InfiniteCanvas 外层容器与内部 content 包裹节点
|
|
584
|
+
exclude: ['#container', '.stage-content'],
|
|
585
|
+
toolbarButtons: [
|
|
586
|
+
{
|
|
587
|
+
label: 'AI',
|
|
588
|
+
title: 'AI 对话',
|
|
589
|
+
onClick: (ctx) => openAIDialog(ctx)
|
|
590
|
+
}
|
|
591
|
+
],
|
|
592
|
+
quickGroupOnDblClick: true, // 双击快速创建一个新组
|
|
593
|
+
selectionMode: 'intersects', // 或 'center' | 'contains'
|
|
594
|
+
minOverlapRatio: 0.95, // 要求至少 80% 的元素面积被覆盖
|
|
595
|
+
overlapMetric: 'element',// 或 'iou'
|
|
596
|
+
conflictStrategy: 'none', // 父子冲突保留得分更高者
|
|
597
|
+
multi: true, // 开启多次圈选
|
|
598
|
+
combineMode: 'auto', // Shift=加选 Alt=减选 Ctrl/⌘=切换
|
|
599
|
+
groupMode: true, // 开启编组模式
|
|
600
|
+
hoverHighlight: true,
|
|
601
|
+
hoverClass: 'hovered',
|
|
602
|
+
allowIntersectionSelection: false, // 允许框选与元素相交即选中
|
|
603
|
+
allowUnionSelection: true, // 允许框选包含元素的并集
|
|
604
|
+
allowContainmentSelection:true,
|
|
605
|
+
// 组主题颜色:启用随机配色与调色板(演示)
|
|
606
|
+
groupRandomColor: true,
|
|
607
|
+
// groupColorPalette: ['#f97316', '#eab308', '#22c55e', '#06b6d4', '#3b82f6', '#8b5cf6', '#ec4899'],
|
|
608
|
+
onChange: (payload) => {
|
|
609
|
+
// if (payload.type === 'groups') {
|
|
610
|
+
// console.log('组变更', payload.groups.map(g => g.length), '合并选中', payload.flat.length)
|
|
611
|
+
// } else {
|
|
612
|
+
// console.log('单次选中', payload.selected.length)
|
|
613
|
+
// }
|
|
614
|
+
},
|
|
615
|
+
onSelectionEnd: (snapshot) => {
|
|
616
|
+
console.log({ snapshot })
|
|
617
|
+
lastSnapshot = snapshot
|
|
618
|
+
const panel = document.getElementById('selection-panel')
|
|
619
|
+
const content = document.getElementById('selection-panel-content')
|
|
620
|
+
const btn = document.getElementById('btn-toggle-nested')
|
|
621
|
+
if (!panel || !content || !btn) return
|
|
622
|
+
panel.style.display = 'block'
|
|
623
|
+
|
|
624
|
+
const labelOf = (el) => {
|
|
625
|
+
// 暴露 ctrl 为全局,供 InfiniteCanvas 的回调与调试使用
|
|
626
|
+
// @ts-ignore
|
|
627
|
+
window.ctrl = ctrl
|
|
628
|
+
if (!el || !el.tagName) return String(el)
|
|
629
|
+
const id = el.id ? `#${el.id}` : ''
|
|
630
|
+
const text = (el.firstChild?.textContent || '').trim().slice(0, 20)
|
|
631
|
+
return `<span class=\"node-label\">${el.tagName.toLowerCase()}${id}${text ? `<span class=\"node-content\">${text}</span>` : ''}</span>`
|
|
632
|
+
}
|
|
633
|
+
// 将 els 构造成仅包含直系选中后代的树(过滤中间层)
|
|
634
|
+
const buildTree = (els) => {
|
|
635
|
+
const roots = els.filter(el => !els.some(other => other !== el && other.contains(el)))
|
|
636
|
+
const buildNode = (el) => {
|
|
637
|
+
const children = els.filter(ch => ch !== el && el.contains(ch) && !els.some(o => o !== ch && o !== el && o.contains(ch) && el.contains(o)))
|
|
638
|
+
return { el, children: children.map(buildNode) }
|
|
639
|
+
}
|
|
640
|
+
return roots.map(buildNode)
|
|
641
|
+
}
|
|
642
|
+
const renderTree = (nodes) => {
|
|
643
|
+
const renderUl = (ns) => `<ul>${ns.map(n => `<li>${labelOf(n.el)}${n.children?.length ? renderUl(n.children) : ''}</li>`).join('')}</ul>`
|
|
644
|
+
return `<div class=\"tree\">${renderUl(nodes)}</div>`
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
const renderFlat = (snap) => {
|
|
648
|
+
if (snap.type === 'groups') {
|
|
649
|
+
const head = [`组数:${snap.groups.length}`, `并集元素:${snap.flat.length}`].map(l => `<div class=\"group-row\">${l}</div>`).join('')
|
|
650
|
+
const groupsHtml = snap.groups.map((g, i) => {
|
|
651
|
+
const rect = snap.groupRects[i]
|
|
652
|
+
const vis = snap.hidden[i] ? '隐藏' : '显示'
|
|
653
|
+
const title = `第${i + 1}组:元素${g.length}个,${vis}${rect ? `,框(${Math.round(rect.left)},${Math.round(rect.top)} ${Math.round(rect.width)}×${Math.round(rect.height)})` : ''}`
|
|
654
|
+
const tree = renderTree(buildTree(g))
|
|
655
|
+
return `<details class=\"group\"><summary>${title}</summary>${tree}</details>`
|
|
656
|
+
}).join('')
|
|
657
|
+
content.innerHTML = head + groupsHtml
|
|
658
|
+
} else {
|
|
659
|
+
content.innerHTML = `<div>单次选中:${snap.selected.length}</div>`
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
// 组级嵌套:基于 snapshot.groupNesting(库层提供)
|
|
664
|
+
const renderNested = (snap) => {
|
|
665
|
+
if (snap.type !== 'groups') {
|
|
666
|
+
const tree = renderTree(buildTree(snap.selected))
|
|
667
|
+
content.innerHTML = `<details class=\"group\" open><summary>单次选中嵌套</summary>${tree}</details>`
|
|
668
|
+
return
|
|
669
|
+
}
|
|
670
|
+
const { parents, children, roots } = snap.groupNesting
|
|
671
|
+
const titleOf = (i) => {
|
|
672
|
+
const g = snap.groups[i]
|
|
673
|
+
const rect = snap.groupRects[i]
|
|
674
|
+
const vis = snap.hidden[i] ? '隐藏' : '显示'
|
|
675
|
+
return `第${i + 1}组:元素${g.length}个,${vis}${rect ? `,框(${Math.round(rect.left)},${Math.round(rect.top)} ${Math.round(rect.width)}×${Math.round(rect.height)})` : ''}`
|
|
676
|
+
}
|
|
677
|
+
const renderGroupNode = (i) => {
|
|
678
|
+
const tree = renderTree(buildTree(snap.groups[i]))
|
|
679
|
+
const kids = (children[i] || [])?.map(renderGroupNode).join('') || ''
|
|
680
|
+
return `<details class=\"group\"><summary>${titleOf(i)}</summary>${tree}${kids}</details>`
|
|
681
|
+
}
|
|
682
|
+
const head = [`组嵌套查看`, `组数:${snap.groups.length}`].map(l => `<div class=\"group-row\">${l}</div>`).join('')
|
|
683
|
+
content.innerHTML = head + roots.map(renderGroupNode).join('')
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
const renderPanel = () => nestedMode ? renderNested(lastSnapshot) : renderFlat(lastSnapshot)
|
|
687
|
+
btn.textContent = nestedMode ? '平铺查看' : '组嵌套查看'
|
|
688
|
+
btn.onclick = (ev) => {
|
|
689
|
+
ev.stopPropagation()
|
|
690
|
+
nestedMode = !nestedMode
|
|
691
|
+
btn.textContent = nestedMode ? '平铺查看' : '组嵌套查看'
|
|
692
|
+
renderPanel()
|
|
693
|
+
}
|
|
694
|
+
renderPanel()
|
|
695
|
+
}
|
|
696
|
+
})
|
|
697
|
+
// 暴露 ctrl 为全局,方便调试或外部调用
|
|
698
|
+
// @ts-ignore
|
|
699
|
+
window.ctrl = ctrl
|
|
700
|
+
|
|
701
|
+
// 绑定:根据选择器成组
|
|
702
|
+
const selectorInput = document.getElementById('group-selector-input')
|
|
703
|
+
const addGroupBtn = document.getElementById('btn-add-group-by-selector')
|
|
704
|
+
const handleAddGroup = () => {
|
|
705
|
+
if (!selectorInput) return
|
|
706
|
+
const qs = selectorInput.value.trim()
|
|
707
|
+
if (!qs) {
|
|
708
|
+
selectorInput.focus()
|
|
709
|
+
return
|
|
710
|
+
}
|
|
711
|
+
let list = []
|
|
712
|
+
try {
|
|
713
|
+
list = Array.from(container.querySelectorAll(qs))
|
|
714
|
+
} catch (err) {
|
|
715
|
+
console.warn('无效的 CSS 选择器:', qs, err)
|
|
716
|
+
alert('无效的 CSS 选择器,请检查输入。')
|
|
717
|
+
return
|
|
718
|
+
}
|
|
719
|
+
if (!list.length) {
|
|
720
|
+
alert('未匹配到任何元素。')
|
|
721
|
+
return
|
|
722
|
+
}
|
|
723
|
+
const ok = ctrl && typeof ctrl.addGroup === 'function' ? ctrl.addGroup(list) : false
|
|
724
|
+
if (!ok) {
|
|
725
|
+
// 失败原因可能:重复编组、交/并限制冲突、元素不在容器/被排除/不匹配 selectable
|
|
726
|
+
alert('成组失败:可能与当前规则冲突或与已有组重复。')
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
addGroupBtn?.addEventListener('click', handleAddGroup)
|
|
730
|
+
selectorInput?.addEventListener('keydown', (e) => {
|
|
731
|
+
if (e.key === 'Enter') handleAddGroup()
|
|
732
|
+
})
|
|
733
|
+
} else {
|
|
734
|
+
console.error('container not found')
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
</script>
|
|
738
|
+
</body>
|
|
739
|
+
|
|
740
|
+
</html>
|