glass-easel-devtools-panel 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (63) hide show
  1. package/.eslintignore +2 -0
  2. package/dist/app.d.ts +0 -0
  3. package/dist/bootstrap.d.ts +5 -0
  4. package/dist/bootstrap.js +3 -0
  5. package/dist/bootstrap.js.LICENSE.txt +1 -0
  6. package/dist/events.d.ts +41 -0
  7. package/dist/global_components/image/image.d.ts +1 -0
  8. package/dist/global_components/index.d.ts +2 -0
  9. package/dist/global_components/view/view.d.ts +1 -0
  10. package/dist/index.css +17 -0
  11. package/dist/message_channel.d.ts +11 -0
  12. package/dist/pages/detail/detail.d.ts +1 -0
  13. package/dist/pages/detail/section.d.ts +0 -0
  14. package/dist/pages/detail/value.d.ts +1 -0
  15. package/dist/pages/index/index.d.ts +1 -0
  16. package/dist/pages/store.d.ts +9 -0
  17. package/dist/pages/tree/element.d.ts +1 -0
  18. package/dist/resources/logo_256.png +0 -0
  19. package/dist/utils.d.ts +4 -0
  20. package/package.json +21 -0
  21. package/src/app.ts +1 -0
  22. package/src/app.wxss +1 -0
  23. package/src/bootstrap.ts +80 -0
  24. package/src/events.ts +97 -0
  25. package/src/global_components/image/image.json +3 -0
  26. package/src/global_components/image/image.ts +1 -0
  27. package/src/global_components/image/image.wxml +1 -0
  28. package/src/global_components/image/image.wxss +3 -0
  29. package/src/global_components/index.ts +12 -0
  30. package/src/global_components/view/view.json +3 -0
  31. package/src/global_components/view/view.ts +40 -0
  32. package/src/global_components/view/view.wxml +14 -0
  33. package/src/global_components/view/view.wxss +0 -0
  34. package/src/message_channel.ts +70 -0
  35. package/src/pages/common.wxss +12 -0
  36. package/src/pages/detail/detail.json +7 -0
  37. package/src/pages/detail/detail.ts +190 -0
  38. package/src/pages/detail/detail.wxml +179 -0
  39. package/src/pages/detail/detail.wxss +84 -0
  40. package/src/pages/detail/section.json +3 -0
  41. package/src/pages/detail/section.ts +17 -0
  42. package/src/pages/detail/section.wxml +8 -0
  43. package/src/pages/detail/section.wxss +47 -0
  44. package/src/pages/detail/value.json +3 -0
  45. package/src/pages/detail/value.ts +107 -0
  46. package/src/pages/detail/value.wxml +7 -0
  47. package/src/pages/detail/value.wxss +44 -0
  48. package/src/pages/index/index.json +6 -0
  49. package/src/pages/index/index.ts +121 -0
  50. package/src/pages/index/index.wxml +29 -0
  51. package/src/pages/index/index.wxss +75 -0
  52. package/src/pages/store.ts +33 -0
  53. package/src/pages/tree/element.json +6 -0
  54. package/src/pages/tree/element.ts +295 -0
  55. package/src/pages/tree/element.wxml +47 -0
  56. package/src/pages/tree/element.wxss +113 -0
  57. package/src/resources/logo_256.png +0 -0
  58. package/src/utils.ts +16 -0
  59. package/src.d.ts +10 -0
  60. package/tsconfig.json +11 -0
  61. package/typings/miniprogram.d.ts +6 -0
  62. package/webpack.config.js +79 -0
  63. package/webpack.dev.config.js +12 -0
@@ -0,0 +1,44 @@
1
+ @import url('../common.wxss');
2
+
3
+ @keyframes value-update-ani {
4
+ from {
5
+ background: @update-bg;
6
+ }
7
+ to {
8
+ background: transparent;
9
+ }
10
+ }
11
+ .updated {
12
+ background: @update-bg;
13
+ animation-duration: 1s;
14
+ animation-fill-mode: both;
15
+ animation-name: value-update-ani;
16
+ animation-timing-function: ease;
17
+ }
18
+
19
+ .wrapper {
20
+ display: inline-block;
21
+ }
22
+
23
+ .slice {
24
+ display: inline;
25
+ color: @common-text;
26
+ }
27
+ .slice_dynamic {
28
+ color: @attribute-value;
29
+ }
30
+
31
+ .var-name {
32
+ display: inline-block;
33
+ margin-left: 0.25em;
34
+ height: 1em;
35
+ line-height: 1em;
36
+ min-width: 1em;
37
+ text-align: center;
38
+ font-size: 0.8em;
39
+ font-style: italic;
40
+ color: @important-text;
41
+ }
42
+ .var-name_hover {
43
+ background: @hover-bg;
44
+ }
@@ -0,0 +1,6 @@
1
+ {
2
+ "usingComponents": {
3
+ "element": "/pages/tree/element",
4
+ "detail": "/pages/detail/detail"
5
+ }
6
+ }
@@ -0,0 +1,121 @@
1
+ import { DeepCopyKind } from 'glass-easel'
2
+ import { initStoreBindings } from 'mobx-miniprogram-bindings'
3
+ import { type protocol, sendRequest, setEventHandler } from '../../message_channel'
4
+ import { compDef as treeCompDef } from '../tree/element'
5
+ import { store } from '../store'
6
+ import { childNodeInserted } from '../../events'
7
+ import { error } from '../../utils'
8
+
9
+ export const componentDefinition = Component()
10
+ .options({
11
+ dataDeepCopy: DeepCopyKind.None,
12
+ propertyPassingDeepCopy: DeepCopyKind.None,
13
+ })
14
+ .data(() => ({
15
+ mountPoints: [] as protocol.dom.Node[],
16
+ inSelectMode: false,
17
+ detailWidth: 300,
18
+ }))
19
+ .init((ctx) => {
20
+ const { self, data, setData, method, listener } = ctx
21
+
22
+ // collect document
23
+ const initDocument = async () => {
24
+ await sendRequest('DOM.enable', {})
25
+ const res = await sendRequest('DOM.getDocument', { depth: 3 })
26
+ setData({ mountPoints: res.root.children ?? [] })
27
+ }
28
+ childNodeInserted.bindComponentLifetimes(
29
+ ctx,
30
+ () => 1,
31
+ ({ previousNodeId, node }) => {
32
+ const after = data.mountPoints.map((x) => x.nodeId).indexOf(previousNodeId)
33
+ const before = after + 1
34
+ self.groupUpdates(() => {
35
+ self.spliceArrayDataOnPath(['mountPoints'], before, 0, [node])
36
+ })
37
+ },
38
+ )
39
+
40
+ initStoreBindings(ctx, { store, fields: ['selectedNodeId', 'sideBarShown'] })
41
+
42
+ const closeSideBar = listener(() => {
43
+ store.hideSideBar()
44
+ })
45
+
46
+ // node select
47
+ const toggleSelectMode = listener(() => {
48
+ if (!data.inSelectMode) {
49
+ setData({ inSelectMode: true })
50
+ // eslint-disable-next-line @typescript-eslint/no-floating-promises
51
+ sendRequest('Overlay.setInspectMode', { mode: 'searchForNode' })
52
+ } else {
53
+ setData({ inSelectMode: false })
54
+ // eslint-disable-next-line @typescript-eslint/no-floating-promises
55
+ sendRequest('Overlay.setInspectMode', { mode: 'none' })
56
+ }
57
+ })
58
+ setEventHandler('Overlay.inspectModeCanceled', () => {
59
+ setData({ inSelectMode: false })
60
+ store.setHighlightNode(0)
61
+ })
62
+ setEventHandler('Overlay.nodeHighlightRequested', ({ nodeId }) => {
63
+ store.setHighlightNode(nodeId)
64
+ })
65
+ setEventHandler('Overlay.inspectNodeRequested', ({ backendNodeId }) => {
66
+ const nodePath: protocol.dom.Node[] = []
67
+ const rec = async (backendNodeId: protocol.NodeId) => {
68
+ const { node } = await sendRequest('DOM.describeNode', { backendNodeId, depth: 2 })
69
+ if (node.parentId) await rec(node.parentId)
70
+ nodePath.push(node)
71
+ }
72
+ // eslint-disable-next-line @typescript-eslint/no-floating-promises, promise/catch-or-return
73
+ Promise.resolve().then(async () => {
74
+ await rec(backendNodeId)
75
+ const tree = self.selectComponent(`#mount-point-${nodePath[0].nodeId}`, treeCompDef)
76
+ if (tree) {
77
+ await tree.visitChildNodePath(nodePath)
78
+ } else {
79
+ error(`cannot find child node id ${nodePath[0].nodeId}`)
80
+ }
81
+ return undefined
82
+ })
83
+ })
84
+
85
+ // split drag & resize
86
+ let startPosX: null | number = null
87
+ const splitDragStart = listener<{ clientX: number; clientY: number; button: number }>((ev) => {
88
+ const { button, clientX } = ev.detail
89
+ if (button && button !== 0) return
90
+ startPosX = clientX
91
+ })
92
+ const splitDragMove = listener<{ clientX: number; clientY: number; button: number }>((ev) => {
93
+ if (startPosX === null) return
94
+ const newPosX = ev.detail.clientX
95
+ setData({ detailWidth: data.detailWidth + startPosX - newPosX })
96
+ startPosX = newPosX
97
+ })
98
+ const splitDragEnd = listener<{ clientX: number; clientY: number; button: number }>((ev) => {
99
+ const { button } = ev.detail
100
+ if (button && button !== 0) return
101
+ startPosX = null
102
+ })
103
+
104
+ // reconnect, clear elements, and fetch all elements
105
+ const restart = method(() => {
106
+ store.selectNode(0)
107
+ setData({ mountPoints: [] })
108
+ // eslint-disable-next-line @typescript-eslint/no-floating-promises
109
+ initDocument()
110
+ })
111
+
112
+ return {
113
+ toggleSelectMode,
114
+ closeSideBar,
115
+ splitDragStart,
116
+ splitDragMove,
117
+ splitDragEnd,
118
+ restart,
119
+ }
120
+ })
121
+ .register()
@@ -0,0 +1,29 @@
1
+ <view class="wrapper">
2
+ <view wx:if="{{ mountPoints.length === 0 }}" class="empty">
3
+ <view>No glass-easel mount points found. (Is glass-easel built in development mode?)</view>
4
+ </view>
5
+
6
+ <view wx:else class="main">
7
+ <view class="tree-detail" bind:mousemove="splitDragMove" bind:mouseup="splitDragEnd">
8
+ <view class="left">
9
+ <view class="toolbar">
10
+ <view class="tool {{ inSelectMode ? 'tool_active' : '' }}" hover-class="tool_hover" catch:tap="toggleSelectMode">Tap Select</view>
11
+ <view class="tool-space" />
12
+ <view hidden="{{ !selectedNodeId || !sideBarShown }}" class="tool" hover-class="tool_hover" catch:tap="closeSideBar">▶</view>
13
+ </view>
14
+ <view class="tree">
15
+ <block wx:for="{{ mountPoints }}">
16
+ <element id="mount-point-{{ item.nodeId }}" node-info="{{ item }}" />
17
+ </block>
18
+ </view>
19
+ </view>
20
+ <view
21
+ class="tree-detail-split"
22
+ bind:mousedown="splitDragStart"
23
+ />
24
+ <view class="detail" hidden="{{ !selectedNodeId || !sideBarShown }}" style="width: {{ detailWidth }}px">
25
+ <detail />
26
+ </view>
27
+ </view>
28
+ </view>
29
+ </view>
@@ -0,0 +1,75 @@
1
+ @import url('../common.wxss');
2
+
3
+ body {
4
+ margin: 0;
5
+ height: 100vh;
6
+ }
7
+
8
+ .wrapper {
9
+ font-family: monospace;
10
+ font-size: 12px;
11
+ background: @primary-bg;
12
+ height: 100%;
13
+ cursor: default;
14
+ user-select: none;
15
+ }
16
+ .empty {
17
+ color: @common-text;
18
+ font-style: italic;
19
+ padding: 5px;
20
+ }
21
+
22
+ .main {
23
+ height: 100%;
24
+ }
25
+ .tree-detail {
26
+ display: flex;
27
+ height: 100%;
28
+ overflow: hidden;
29
+ }
30
+ .tree-detail-split {
31
+ width: 2px;
32
+ background-color: #888;
33
+ cursor: w-resize;
34
+ }
35
+ .left {
36
+ flex: 1 1 0;
37
+ display: flex;
38
+ flex-direction: column;
39
+ overflow-x: hidden;
40
+ }
41
+
42
+ .toolbar {
43
+ flex: none;
44
+ background: @secondary-bg;
45
+ color: @important-text;
46
+ border-bottom: 2px solid #888;
47
+ display: flex;
48
+ }
49
+ .tool-space {
50
+ flex: auto;
51
+ }
52
+ .tool {
53
+ flex: none;
54
+ display: inline-block;
55
+ padding: 5px 10px;
56
+ }
57
+ .tool_hover {
58
+ background: @hover-bg;
59
+ }
60
+ .tool_active {
61
+ background: @selected-bg;
62
+ }
63
+
64
+ .tree {
65
+ flex: auto;
66
+ margin: 5px;
67
+ overflow: auto;
68
+ user-select: text;
69
+ }
70
+
71
+ .detail {
72
+ flex: none;
73
+ box-sizing: border-box;
74
+ overflow: auto;
75
+ }
@@ -0,0 +1,33 @@
1
+ import { makeAutoObservable } from 'mobx-miniprogram'
2
+ import { type protocol } from 'glass-easel-devtools-agent'
3
+ import { sendRequest } from '../message_channel'
4
+
5
+ export const store = makeAutoObservable({
6
+ selectedNodeId: 0 as protocol.NodeId,
7
+ highlightNodeId: 0 as protocol.NodeId,
8
+ sideBarShown: false,
9
+
10
+ selectNode(n: protocol.NodeId) {
11
+ this.selectedNodeId = n
12
+ this.sideBarShown = n > 0
13
+ // if (n > 0) {
14
+ // // eslint-disable-next-line @typescript-eslint/no-floating-promises
15
+ // sendRequest('DOM.setInspectedNode', { nodeId: n })
16
+ // }
17
+ },
18
+
19
+ hideSideBar() {
20
+ this.sideBarShown = false
21
+ },
22
+
23
+ setHighlightNode(n: protocol.NodeId) {
24
+ this.highlightNodeId = n
25
+ if (n > 0) {
26
+ // eslint-disable-next-line @typescript-eslint/no-floating-promises
27
+ sendRequest('Overlay.highlightNode', { nodeId: n })
28
+ } else {
29
+ // eslint-disable-next-line @typescript-eslint/no-floating-promises
30
+ sendRequest('Overlay.hideHighlight', {})
31
+ }
32
+ },
33
+ })
@@ -0,0 +1,6 @@
1
+ {
2
+ "component": true,
3
+ "usingComponents": {
4
+ "element": "./element"
5
+ }
6
+ }
@@ -0,0 +1,295 @@
1
+ import { DeepCopyKind } from 'glass-easel'
2
+ import { initStoreBindings } from 'mobx-miniprogram-bindings'
3
+ import { protocol } from 'glass-easel-devtools-agent'
4
+ import {
5
+ childNodeCountUpdated,
6
+ childNodeInserted,
7
+ childNodeRemoved,
8
+ setChildNodes,
9
+ characterDataModified,
10
+ attributeModified,
11
+ } from '../../events'
12
+ import { sendRequest } from '../../message_channel'
13
+ import { error, warn } from '../../utils'
14
+ import { store } from '../store'
15
+
16
+ type AttributeMeta = { name: string; value: string; isProperty: boolean; updateAniTs: number }
17
+
18
+ const enum DisplayKind {
19
+ Text = 0,
20
+ Tag = 1,
21
+ VirtualTag = 2,
22
+ }
23
+
24
+ export const compDef = Component()
25
+ .options({
26
+ dataDeepCopy: DeepCopyKind.None,
27
+ propertyPassingDeepCopy: DeepCopyKind.None,
28
+ propertyEarlyInit: true,
29
+ })
30
+ .property('nodeInfo', {
31
+ type: Object,
32
+ value: null as protocol.dom.Node | null,
33
+ })
34
+ .data(() => ({
35
+ kind: DisplayKind.Text,
36
+ textContent: '',
37
+ tagName: '',
38
+ attributes: [] as AttributeMeta[],
39
+ hasShadowRoot: false,
40
+ shadowRoots: [] as protocol.dom.Node[],
41
+ hasChildNodes: true,
42
+ hasSlotContent: false,
43
+ showChildNodes: false,
44
+ children: [] as protocol.dom.Node[],
45
+ tagVarName: '',
46
+ tagUpdateHighlight: false,
47
+ }))
48
+ .init((ctx) => {
49
+ // eslint-disable-next-line @typescript-eslint/unbound-method
50
+ const { self, data, setData, observer, listener, method } = ctx
51
+ let nodeId = 0
52
+ const initNodeId = (n: protocol.NodeId) => {
53
+ if (n === 0) {
54
+ warn('illegal node id received')
55
+ return
56
+ }
57
+ nodeId = n
58
+ }
59
+
60
+ // store bindings
61
+ initStoreBindings(ctx, {
62
+ store,
63
+ fields: ['selectedNodeId', 'highlightNodeId'],
64
+ })
65
+
66
+ // child nodes listeners
67
+ childNodeCountUpdated.bindComponentLifetimes(
68
+ ctx,
69
+ () => nodeId,
70
+ (args) => {
71
+ setData({ hasChildNodes: args.childNodeCount > 0 })
72
+ },
73
+ )
74
+ setChildNodes.bindComponentLifetimes(
75
+ ctx,
76
+ () => nodeId,
77
+ ({ nodes }) => {
78
+ setData({
79
+ hasChildNodes: nodes.length > 0,
80
+ children: nodes,
81
+ })
82
+ },
83
+ )
84
+ childNodeInserted.bindComponentLifetimes(
85
+ ctx,
86
+ () => nodeId,
87
+ ({ previousNodeId, node }) => {
88
+ const after = data.children.map((x) => x.nodeId).indexOf(previousNodeId)
89
+ const before = after + 1
90
+ self.groupUpdates(() => {
91
+ self.spliceArrayDataOnPath(['children'], before, 0, [node])
92
+ })
93
+ const childComp = self.selectComponent(`#child-${node.nodeId}`, compDef)
94
+ childComp?.tagUpdatedAni()
95
+ },
96
+ )
97
+ childNodeRemoved.bindComponentLifetimes(
98
+ ctx,
99
+ () => nodeId,
100
+ ({ nodeId: childNodeId }) => {
101
+ const index = data.children.map((x) => x.nodeId).indexOf(childNodeId)
102
+ if (index < 0) return
103
+ self.groupUpdates(() => {
104
+ self.spliceArrayDataOnPath(['children'], index, 1, [])
105
+ })
106
+ tagUpdatedAni()
107
+ },
108
+ )
109
+ characterDataModified.bindComponentLifetimes(
110
+ ctx,
111
+ () => nodeId,
112
+ ({ characterData }) => {
113
+ setData({ textContent: characterData })
114
+ tagUpdatedAni()
115
+ },
116
+ )
117
+ let updateAniEndTimeout = 0
118
+ const tagUpdatedAni = method(() => {
119
+ setTimeout(() => {
120
+ if (data.tagUpdateHighlight) {
121
+ setData({ tagUpdateHighlight: false })
122
+ tagUpdatedAni()
123
+ return
124
+ }
125
+ if (updateAniEndTimeout) {
126
+ clearTimeout(updateAniEndTimeout)
127
+ updateAniEndTimeout = 0
128
+ }
129
+ self.setData({ tagUpdateHighlight: true })
130
+ updateAniEndTimeout = setTimeout(() => {
131
+ updateAniEndTimeout = 0
132
+ setData({ tagUpdateHighlight: false })
133
+ }, 1000)
134
+ }, 200)
135
+ })
136
+
137
+ // attribute listeners
138
+ attributeModified.bindComponentLifetimes(
139
+ ctx,
140
+ () => nodeId,
141
+ ({ name, value }) => {
142
+ data.attributes.forEach((attr, index) => {
143
+ if (attr.name === name) {
144
+ self.groupUpdates(() => {
145
+ self.replaceDataOnPath(['attributes', index, 'value'], value)
146
+ })
147
+ attrUpdatedAni(index)
148
+ }
149
+ })
150
+ },
151
+ )
152
+ const attrUpdatedAni = (index: number) => {
153
+ setTimeout(() => {
154
+ if (data.attributes[index].updateAniTs) {
155
+ self.groupUpdates(() => {
156
+ self.replaceDataOnPath(['attributes', index, 'updateAniTs'], 0)
157
+ })
158
+ attrUpdatedAni(index)
159
+ return
160
+ }
161
+ const now = Date.now()
162
+ self.groupUpdates(() => {
163
+ self.replaceDataOnPath(['attributes', index, 'updateAniTs'], now)
164
+ })
165
+ updateAniEndTimeout = setTimeout(() => {
166
+ if (data.attributes[index].updateAniTs !== now) return
167
+ self.groupUpdates(() => {
168
+ self.replaceDataOnPath(['attributes', index, 'updateAniTs'], 0)
169
+ })
170
+ }, 1000)
171
+ }, 200)
172
+ }
173
+
174
+ // init node info
175
+ observer('nodeInfo', (nodeInfo) => {
176
+ initNodeId(nodeInfo?.nodeId ?? 0)
177
+ const nodeType = nodeInfo?.glassEaselNodeType
178
+ if (
179
+ nodeType === protocol.dom.GlassEaselNodeType.NativeNode ||
180
+ nodeType === protocol.dom.GlassEaselNodeType.Component
181
+ ) {
182
+ const tagName = nodeInfo?.nodeName ?? ''
183
+ const nv = nodeInfo?.attributes ?? []
184
+ const attributes = [] as AttributeMeta[]
185
+ const coreAttrCount = nodeInfo?.glassEaselAttributeCount ?? 0
186
+ for (let i = 0; i < nv.length; i += 2) {
187
+ const name = nv[i]
188
+ const value = nv[i + 1] ?? ''
189
+ const isProperty = i / 2 >= coreAttrCount
190
+ attributes.push({ name, value, isProperty, updateAniTs: 0 })
191
+ }
192
+ const hasShadowRoot = nodeInfo?.shadowRootType === 'open'
193
+ const shadowRoots = nodeInfo?.shadowRoots ?? []
194
+ setData({
195
+ kind: DisplayKind.Tag,
196
+ tagName,
197
+ attributes,
198
+ hasShadowRoot,
199
+ shadowRoots,
200
+ })
201
+ } else if (nodeType === protocol.dom.GlassEaselNodeType.TextNode) {
202
+ setData({
203
+ kind: DisplayKind.Text,
204
+ textContent: nodeInfo?.nodeValue ?? '',
205
+ })
206
+ } else {
207
+ let tagName = nodeInfo?.nodeName ?? ''
208
+ if (nodeType === protocol.dom.GlassEaselNodeType.Unknown) tagName = 'unknown'
209
+ setData({
210
+ kind: DisplayKind.VirtualTag,
211
+ tagName,
212
+ })
213
+ }
214
+ const children = nodeInfo?.children
215
+ if (children !== undefined) {
216
+ setData({
217
+ hasChildNodes: children.length > 0,
218
+ showChildNodes: true,
219
+ children,
220
+ })
221
+ }
222
+ const distributedNodes = nodeInfo?.distributedNodes
223
+ if (distributedNodes !== undefined) {
224
+ setData({
225
+ hasSlotContent: distributedNodes.length > 0,
226
+ showChildNodes: false,
227
+ })
228
+ }
229
+ })
230
+
231
+ // toggle children events
232
+ const updateChildren = async () => {
233
+ const distributedNodes = data.nodeInfo?.distributedNodes
234
+ if (distributedNodes) {
235
+ const { nodes } = await sendRequest('DOM.getGlassEaselComposedChildren', { nodeId })
236
+ setData({ children: nodes })
237
+ } else {
238
+ // eslint-disable-next-line @typescript-eslint/no-floating-promises
239
+ sendRequest('DOM.requestChildNodes', { nodeId })
240
+ }
241
+ }
242
+ const toggleChildren = listener(() => {
243
+ setData({
244
+ showChildNodes: !data.showChildNodes,
245
+ })
246
+ if (data.showChildNodes) {
247
+ // eslint-disable-next-line @typescript-eslint/no-floating-promises, promise/catch-or-return
248
+ Promise.resolve().then(updateChildren)
249
+ }
250
+ })
251
+ const visitChildNodePath = method(async (nodePath: protocol.dom.Node[]) => {
252
+ const [node, ...childPath] = nodePath
253
+ if (childPath.length === 0) {
254
+ setData({ children: node.children })
255
+ store.selectNode(nodeId)
256
+ return
257
+ }
258
+ setData({
259
+ showChildNodes: true,
260
+ children: node.children,
261
+ })
262
+ const childComp = self.selectComponent(`#child-${childPath[0].nodeId}`, compDef)
263
+ if (childComp) {
264
+ await childComp.visitChildNodePath(childPath)
265
+ } else {
266
+ error(`cannot find child node id ${childPath[0].nodeId}`)
267
+ }
268
+ })
269
+
270
+ // tag events
271
+ const selectTag = listener(() => {
272
+ store.selectNode(nodeId)
273
+ })
274
+ const startHoverTag = listener(() => {
275
+ store.setHighlightNode(nodeId)
276
+ })
277
+ const endHoverTag = listener(() => {
278
+ if (store.highlightNodeId === nodeId) store.setHighlightNode(0)
279
+ })
280
+ const useElementInConsole = method(async () => {
281
+ const { varName } = await sendRequest('DOM.useGlassEaselElementInConsole', { nodeId })
282
+ setData({ tagVarName: varName })
283
+ })
284
+
285
+ return {
286
+ toggleChildren,
287
+ visitChildNodePath,
288
+ selectTag,
289
+ startHoverTag,
290
+ endHoverTag,
291
+ useElementInConsole,
292
+ tagUpdatedAni,
293
+ }
294
+ })
295
+ .register()
@@ -0,0 +1,47 @@
1
+ <view wx:if="{{ kind === 1 || kind === 2 }}" class="tag {{ tagUpdateHighlight ? 'updated' : '' }}">
2
+ <view class="fold-arrow">
3
+ <view wx:if="{{ hasChildNodes || hasSlotContent || hasShadowRoot }}" class="fold-arrow-icon {{ showChildNodes ? 'fold-arrow-icon_open' : '' }}" catch:tap="toggleChildren">▶</view>
4
+ </view>
5
+
6
+ <view
7
+ class="tag-body {{ selectedNodeId === nodeInfo.nodeId ? 'tag-body_selected' : '' }} {{ highlightNodeId === nodeInfo.nodeId ? 'tag-body_highlight' : '' }}"
8
+ hover-class="tag-body_hover"
9
+ catch:tap="selectTag"
10
+ bind:mouseenter="startHoverTag"
11
+ bind:mouseleave="endHoverTag"
12
+ >
13
+ <block wx:if="{{ kind === 1 }}">
14
+ <view class="tag-text">&lt;</view>
15
+ <view class="tag-name">{{ tagName }}</view>
16
+ <block wx:for="{{ attributes }}">
17
+ <view class="attribute">
18
+ <block>{{ ' ' }}</block>
19
+ <view class="attribute-name {{ item.isProperty ? 'attribute-name_property' : '' }}">{{ item.name }}</view>
20
+ <block>{{ '="' }}</block>
21
+ <view class="attribute-value {{ item.updateAniTs ? 'updated' : '' }}">{{ item.value }}</view>
22
+ <block>{{ '"' }}</block>
23
+ </view>
24
+ </block>
25
+ <view class="tag-text">&gt;</view>
26
+ </block>
27
+ <block wx:else>
28
+ <view class="virtual-tag-name">{{ tagName }}</view>
29
+ </block>
30
+ </view>
31
+
32
+ <view class="tag-var-name-wrapper" hidden="{{ selectedNodeId !== nodeInfo.nodeId }}">
33
+ <view wx:if="{{ tagVarName }}" class="tag-var-name">={{ tagVarName }}</view>
34
+ <view wx:else class="tag-var-name" hover-class="tag-var-name_hover" catch:tap="useElementInConsole">↖</view>
35
+ </view>
36
+
37
+ <view hidden="{{ !showChildNodes }}" class="children">
38
+ <view wx:if="{{ hasShadowRoot }}" class="shadow-roots">
39
+ <element wx:for="{{ shadowRoots }}" wx:key="nodeId" id="child-{{ item.nodeId }}" node-info="{{ item }}" />
40
+ </view>
41
+ <element wx:for="{{ children }}" wx:key="nodeId" id="child-{{ item.nodeId }}" node-info="{{ item }}" />
42
+ </view>
43
+ </view>
44
+
45
+ <view wx:elif="{{ kind === 0 }}" class="text-content {{ tagUpdateHighlight ? 'updated' : '' }}">
46
+ <view>{{ textContent }}</view>
47
+ </view>