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,190 @@
1
+ import { DeepCopyKind } from 'glass-easel'
2
+ import { autorun } from 'mobx-miniprogram'
3
+ import { protocol, sendRequest } from '../../message_channel'
4
+ import { store } from '../store'
5
+ import { attributeModified } from '../../events'
6
+
7
+ const DEFAULT_NODE_DATA = {
8
+ glassEaselNodeType: 0,
9
+ virtual: true,
10
+ is: '',
11
+ id: '',
12
+ class: '',
13
+ slot: '',
14
+ slotName: undefined,
15
+ slotValues: undefined,
16
+ eventBindings: [],
17
+ dataset: [],
18
+ marks: [],
19
+ }
20
+
21
+ Component()
22
+ .options({
23
+ dataDeepCopy: DeepCopyKind.None,
24
+ propertyPassingDeepCopy: DeepCopyKind.None,
25
+ })
26
+ .data(() => ({
27
+ nodeId: 0 as protocol.NodeId,
28
+ nodeTypeName: '',
29
+ info: DEFAULT_NODE_DATA as protocol.dom.GetGlassEaselAttributes['response'],
30
+ boxModel: null as null | protocol.dom.GetBoxModel['response'],
31
+ boxModelCollapsed: false,
32
+ styles: null as null | protocol.css.GetMatchedStylesForNode['response'],
33
+ styleCollapsed: false,
34
+ computedStyles: null as null | { name: string; value: string }[],
35
+ computedStyleCollapsed: true,
36
+ }))
37
+ .init(({ self, data, setData, lifetime, method }) => {
38
+ lifetime('attached', () => {
39
+ autorun(async () => {
40
+ const nodeId = store.selectedNodeId
41
+ if (!nodeId) {
42
+ setData({ info: DEFAULT_NODE_DATA })
43
+ return
44
+ }
45
+
46
+ // register attributes listeners
47
+ updateListener(nodeId)
48
+
49
+ // fetch normal attributes
50
+ const info = await sendRequest('DOM.getGlassEaselAttributes', { nodeId })
51
+ if (store.selectedNodeId !== nodeId) return
52
+ let nodeTypeName = 'Node (unknown)'
53
+ if (info.glassEaselNodeType === protocol.dom.GlassEaselNodeType.Component) {
54
+ nodeTypeName = 'Component'
55
+ } else if (info.glassEaselNodeType === protocol.dom.GlassEaselNodeType.NativeNode) {
56
+ nodeTypeName = 'Native Node'
57
+ } else if (info.glassEaselNodeType === protocol.dom.GlassEaselNodeType.VirtualNode) {
58
+ nodeTypeName = 'Virtual Node'
59
+ } else if (info.glassEaselNodeType === protocol.dom.GlassEaselNodeType.TextNode) {
60
+ nodeTypeName = 'Text Node'
61
+ }
62
+ setData({ nodeId, nodeTypeName, info })
63
+
64
+ // fetch box model
65
+ if (!data.boxModelCollapsed) {
66
+ await refreshBoxModel()
67
+ }
68
+
69
+ // fetch style
70
+ if (!data.styleCollapsed) {
71
+ await refreshStyles()
72
+ }
73
+
74
+ // fetch computed style
75
+ if (!data.computedStyleCollapsed) {
76
+ await refreshComputedStyles()
77
+ }
78
+ })
79
+ })
80
+
81
+ let listeningNodeId = 0
82
+ const updateListenerFunc = (args: protocol.dom.AttributeModified['detail']) => {
83
+ const { name, value, detail, nameType } = args
84
+ if (nameType === 'attribute') {
85
+ const index = data.info.normalAttributes?.map((x) => x.name).indexOf(name) ?? -1
86
+ if (index >= 0) {
87
+ self.groupUpdates(() => {
88
+ self.replaceDataOnPath(
89
+ ['info', 'normalAttributes', index, 'value'],
90
+ detail as unknown as never,
91
+ )
92
+ })
93
+ }
94
+ } else if (nameType === 'component-property') {
95
+ const index = data.info.properties?.map((x) => x.name).indexOf(name) ?? -1
96
+ if (index >= 0) {
97
+ self.groupUpdates(() => {
98
+ self.replaceDataOnPath(
99
+ ['info', 'properties', index, 'value'],
100
+ detail as unknown as never,
101
+ )
102
+ })
103
+ }
104
+ } else if (nameType === 'slot-value') {
105
+ const index = data.info.slotValues?.map((x) => x.name).indexOf(name) ?? -1
106
+ if (index >= 0) {
107
+ self.groupUpdates(() => {
108
+ self.replaceDataOnPath(
109
+ ['info', 'slotValues', index, 'value'],
110
+ detail as unknown as never,
111
+ )
112
+ })
113
+ }
114
+ } else if (nameType === 'dataset' && name.startsWith('data:')) {
115
+ const index = data.info.dataset.map((x) => x.name).indexOf(name.slice(5)) ?? -1
116
+ if (index >= 0) {
117
+ self.groupUpdates(() => {
118
+ self.replaceDataOnPath(['info', 'dataset', index, 'value'], detail)
119
+ })
120
+ }
121
+ } else if (nameType === 'mark' && name.startsWith('mark:')) {
122
+ const index = data.info.marks.map((x) => x.name).indexOf(name.slice(5)) ?? -1
123
+ if (index >= 0) {
124
+ self.groupUpdates(() => {
125
+ self.replaceDataOnPath(['info', 'marks', index, 'value'], detail)
126
+ })
127
+ }
128
+ } else if (nameType === 'external-class') {
129
+ const index = data.info.externalClasses?.map((x) => x.name).indexOf(name) ?? -1
130
+ if (index >= 0) {
131
+ self.groupUpdates(() => {
132
+ self.replaceDataOnPath(
133
+ ['info', 'externalClasses', index, 'value'],
134
+ value as unknown as never,
135
+ )
136
+ })
137
+ }
138
+ } else if (name === 'slot') {
139
+ self.groupUpdates(() => {
140
+ self.replaceDataOnPath(['info', 'slot'], value)
141
+ })
142
+ } else if (name === 'id') {
143
+ self.groupUpdates(() => {
144
+ self.replaceDataOnPath(['info', 'id'], value)
145
+ })
146
+ } else if (name === 'class') {
147
+ self.groupUpdates(() => {
148
+ self.replaceDataOnPath(['info', 'class'], value)
149
+ })
150
+ } else if (name === 'name') {
151
+ self.groupUpdates(() => {
152
+ self.replaceDataOnPath(['info', 'slotName'], value)
153
+ })
154
+ }
155
+ }
156
+ const updateListener = (nodeId: protocol.NodeId) => {
157
+ if (listeningNodeId) {
158
+ attributeModified.removeListener(listeningNodeId, updateListenerFunc)
159
+ }
160
+ if (nodeId) {
161
+ attributeModified.addListener(nodeId, updateListenerFunc)
162
+ }
163
+ listeningNodeId = nodeId
164
+ }
165
+
166
+ const refreshBoxModel = method(async () => {
167
+ const nodeId = store.selectedNodeId
168
+ const boxModel = data.info.virtual ? null : await sendRequest('DOM.getBoxModel', { nodeId })
169
+ setData({ boxModel })
170
+ })
171
+
172
+ const refreshStyles = method(async () => {
173
+ const nodeId = store.selectedNodeId
174
+ const styles = data.info.virtual
175
+ ? null
176
+ : await sendRequest('CSS.getMatchedStylesForNode', { nodeId })
177
+ setData({ styles })
178
+ })
179
+
180
+ const refreshComputedStyles = method(async () => {
181
+ const nodeId = store.selectedNodeId
182
+ const computedStyles = data.info.virtual
183
+ ? null
184
+ : (await sendRequest('CSS.getComputedStyleForNode', { nodeId })).computedStyle
185
+ setData({ computedStyles })
186
+ })
187
+
188
+ return { refreshBoxModel, refreshStyles, refreshComputedStyles }
189
+ })
190
+ .register()
@@ -0,0 +1,179 @@
1
+ <detail-section class="section" title="{{ nodeTypeName }}">
2
+ <block wx:if="{{ info.glassEaselNodeType === 0x102 }}">
3
+ <view wx:if="{{ info.id }}" class="section-item">
4
+ <view class="section-key-core">id</view> = <value primitive-value="{{ info.id }}" node-id="{{ nodeId }}" />
5
+ </view>
6
+ <view wx:if="{{ info.class }}" class="section-item">
7
+ <view class="section-key-core">class</view> = <value primitive-value="{{ info.class }}" node-id="{{ nodeId }}" />
8
+ </view>
9
+ <view wx:if="{{ info.slot }}" class="section-item">
10
+ <view class="section-key-core">slot</view> = <value primitive-value="{{ info.slot }}" node-id="{{ nodeId }}" />
11
+ </view>
12
+ </block>
13
+ <block wx:elif="{{ info.glassEaselNodeType === 0x103 }}">
14
+ <view class="section-item">
15
+ <view class="section-key-core">is</view> = <value primitive-value="{{ info.is }}" node-id="{{ nodeId }}" />
16
+ </view>
17
+ <view wx:if="{{ info.id }}" class="section-item">
18
+ <view class="section-key-core">id</view> = <value primitive-value="{{ info.id }}" node-id="{{ nodeId }}" />
19
+ </view>
20
+ <view wx:if="{{ info.class }}" class="section-item">
21
+ <view class="section-key-core">class</view> = <value primitive-value="{{ info.class }}" node-id="{{ nodeId }}" />
22
+ </view>
23
+ <view wx:if="{{ info.slot }}" class="section-item">
24
+ <view class="section-key-core">slot</view> = <value primitive-value="{{ info.slot }}" node-id="{{ nodeId }}" />
25
+ </view>
26
+ </block>
27
+ <block wx:elif="{{ info.glassEaselNodeType === 0x104 }}">
28
+ <view class="section-item">
29
+ <view class="section-key-core">is</view> = <value primitive-value="{{ info.is }}" node-id="{{ nodeId }}" />
30
+ </view>
31
+ <view wx:if="{{ info.id }}" class="section-item">
32
+ <view class="section-key-core">id</view> = <value primitive-value="{{ info.id }}" node-id="{{ nodeId }}" />
33
+ </view>
34
+ <view wx:if="{{ info.slot }}" class="section-item">
35
+ <view class="section-key-core">slot</view> = <value primitive-value="{{ info.slot }}" node-id="{{ nodeId }}" />
36
+ </view>
37
+ </block>
38
+ </detail-section>
39
+
40
+ <view hidden="{{ info.slotName === undefined }}">
41
+ <detail-section class="section" title="slot">
42
+ <view class="section-item">
43
+ <view class="section-key-core">name</view> = <value primitive-value="{{ info.slotName }}" node-id="{{ nodeId }}" />
44
+ </view>
45
+ <view wx:for="{{ info.slotValues ?? [] }}" class="section-item">
46
+ <view class="section-key">{{ item.name }}</view> = <value value="{{ item.value }}" node-id="{{ nodeId }}" />
47
+ </view>
48
+ </detail-section>
49
+ </view>
50
+
51
+ <view hidden="{{ !info.normalAttributes }}">
52
+ <detail-section class="section" title="attributes">
53
+ <block wx:if="{{ info.normalAttributes.length }}">
54
+ <view wx:for="{{ info.normalAttributes }}" class="section-item">
55
+ <view class="section-key">{{ item.name }}</view> = <value value="{{ item.value }}" node-id="{{ nodeId }}" attribute="{{ item.name }}" />
56
+ </view>
57
+ </block>
58
+ <view wx:else class="section-empty">empty</view>
59
+ </detail-section>
60
+ </view>
61
+
62
+ <view hidden="{{ !info.properties }}">
63
+ <detail-section class="section" title="properties">
64
+ <block wx:if="{{ info.properties.length }}">
65
+ <view wx:for="{{ info.properties }}" class="section-item">
66
+ <view class="section-key">{{ item.name }}</view> = <value value="{{ item.value }}" node-id="{{ nodeId }}" attribute="{{ item.name }}" />
67
+ </view>
68
+ </block>
69
+ <view wx:else class="section-empty">empty</view>
70
+ </detail-section>
71
+ </view>
72
+
73
+ <view hidden="{{ !info.externalClasses }}">
74
+ <detail-section class="section" title="external classes">
75
+ <block wx:if="{{ info.externalClasses.length }}">
76
+ <view wx:for="{{ info.externalClasses }}" class="section-item">
77
+ <view class="section-key">{{ item.name }}</view> = <value primitive-value="{{ item.value }}" node-id="{{ nodeId }}" attribute="{{ item.name }}" />
78
+ </view>
79
+ </block>
80
+ <view wx:else class="section-empty">empty</view>
81
+ </detail-section>
82
+ </view>
83
+
84
+ <detail-section class="section" title="dataset" collapsed>
85
+ <block wx:if="{{ info.dataset.length }}">
86
+ <view wx:for="{{ info.dataset }}" class="section-item">
87
+ <view class="section-key">{{ item.name }}</view> = <value value="{{ item.value }}" node-id="{{ nodeId }}" attribute="data:{{ item.name }}" />
88
+ </view>
89
+ </block>
90
+ <view wx:else class="section-empty">empty</view>
91
+ </detail-section>
92
+
93
+ <detail-section class="section" title="marks" collapsed>
94
+ <block wx:if="{{ info.marks.length }}">
95
+ <view wx:for="{{ info.marks }}" class="section-item">
96
+ <view class="section-key">{{ item.name }}</view> = <value value="{{ item.value }}" node-id="{{ nodeId }}" attribute="mark:{{ item.name }}" />
97
+ </view>
98
+ </block>
99
+ <view wx:else class="section-empty">empty</view>
100
+ </detail-section>
101
+
102
+ <detail-section
103
+ class="section"
104
+ title="box model"
105
+ show-refresh
106
+ model:collapsed="{{ boxModelCollapsed }}"
107
+ catch:refresh="refreshBoxModel"
108
+ >
109
+ <block wx:if="{{ !boxModelCollapsed && boxModel }}">
110
+ <view class="box-model">
111
+ <view class="box-model-margin">
112
+ <view class="box-model-text">{{ boxModel.border[1] - boxModel.margin[1] }}</view>
113
+ <view class="box-model-line">
114
+ <view class="box-model-text">{{ boxModel.border[0] - boxModel.margin[0] }}</view>
115
+ <view class="box-model-border">
116
+ <view class="box-model-text">{{ boxModel.padding[1] - boxModel.border[1] }}</view>
117
+ <view class="box-model-line">
118
+ <view class="box-model-text">{{ boxModel.padding[0] - boxModel.border[0] }}</view>
119
+ <view class="box-model-padding">
120
+ <view class="box-model-text">{{ boxModel.content[1] - boxModel.padding[1] }}</view>
121
+ <view class="box-model-line">
122
+ <view class="box-model-text">{{ boxModel.content[0] - boxModel.padding[0] }}</view>
123
+ <view class="box-model-content">
124
+ <view class="box-model-text">{{ boxModel.width }}</view>x<view class="box-model-text">{{ boxModel.height }}</view>
125
+ </view>
126
+ <view class="box-model-text">{{ boxModel.padding[4] - boxModel.content[4] }}</view>
127
+ </view>
128
+ <view class="box-model-text">{{ boxModel.padding[5] - boxModel.content[5] }}</view>
129
+ </view>
130
+ <view class="box-model-text">{{ boxModel.border[4] - boxModel.padding[4] }}</view>
131
+ </view>
132
+ <view class="box-model-text">{{ boxModel.border[5] - boxModel.padding[5] }}</view>
133
+ </view>
134
+ <view class="box-model-text">{{ boxModel.margin[4] - boxModel.border[4] }}</view>
135
+ </view>
136
+ <view class="box-model-text">{{ boxModel.margin[5] - boxModel.border[5] }}</view>
137
+ </view>
138
+ </view>
139
+ </block>
140
+ </detail-section>
141
+
142
+ <detail-section
143
+ class="section"
144
+ title="style"
145
+ show-refresh
146
+ model:collapsed="{{ styleCollapsed }}"
147
+ catch:refresh="refreshStyles"
148
+ >
149
+ <block wx:if="{{ !styleCollapsed && styles }}">
150
+ <view wx:if="{{ styles.crossOriginFailing }}">Failed to display styles due to inaccessable style sheets. This happens when style sheets are cross-origin served, or the page is visited as a local file.</view>
151
+ <view wx:if="{{ styles.inlineStyle.cssProperties.length }}" class="style-rule">
152
+ <view class="style-rule-prefix">Inline Style</view>
153
+ <view wx:for="{{ styles.inlineStyle.cssProperties }}" class="section-item">
154
+ <view class="section-key">{{ item.name }}</view>: <view class="section-value">{{ item.value }}</view> <view wx:if="{{ item.important }}" class="section-value-extra">{{ item.important ? '!important' : '' }}</view>
155
+ </view>
156
+ </view>
157
+ <view wx:for="{{ styles.matchedCSSRules }}" class="style-rule {{ item.rule.inactive ? 'style-rule_inactive' : '' }}">
158
+ <view wx:for="{{ item.rule.media ?? [] }}" class="style-rule-prefix">{{ item.text }}</view>
159
+ <view class="style-rule-title">{{ item.rule.selectorList.text }}</view>
160
+ <view wx:for="{{ item.rule.style.cssProperties }}" class="section-item">
161
+ <view class="section-key">{{ item.name }}</view>: <view class="section-value">{{ item.value }}</view> <view wx:if="{{ item.important }}" class="section-value-extra">{{ item.important ? '!important' : '' }}</view>
162
+ </view>
163
+ </view>
164
+ </block>
165
+ </detail-section>
166
+
167
+ <detail-section
168
+ class="section"
169
+ title="computed style"
170
+ show-refresh
171
+ model:collapsed="{{ computedStyleCollapsed }}"
172
+ catch:refresh="refreshComputedStyles"
173
+ >
174
+ <block wx:if="{{ !computedStyleCollapsed && computedStyles }}">
175
+ <view wx:for="{{ computedStyles }}" class="section-item">
176
+ <view class="section-key">{{ item.name }}</view>: <view class="section-value">{{ item.value }}</view>
177
+ </view>
178
+ </block>
179
+ </detail-section>
@@ -0,0 +1,84 @@
1
+ @import url('../common.wxss');
2
+
3
+ :host {
4
+ display: block;
5
+ }
6
+
7
+ .section {
8
+ margin-bottom: 5px;
9
+ color: @common-text;
10
+ }
11
+ .section-item {
12
+ white-space: nowrap;
13
+ user-select: text;
14
+ }
15
+ .section-key {
16
+ display: inline;
17
+ color: @property-name;
18
+ }
19
+ .section-key-core {
20
+ display: inline;
21
+ color: @core-attribute-name;
22
+ }
23
+ .section-value {
24
+ display: inline;
25
+ color: @attribute-value;
26
+ }
27
+ .section-value-extra {
28
+ color: @important-text;
29
+ }
30
+
31
+ .section-empty {
32
+ text-align: center;
33
+ font-style: italic;
34
+ }
35
+
36
+ .box-model {
37
+ margin: 0 auto;
38
+ }
39
+ .box-model-text {
40
+ flex: none;
41
+ text-align: center;
42
+ white-space: nowrap;
43
+ color: @important-text;
44
+ margin: 1px;
45
+ }
46
+ .box-model-content {
47
+ display: flex;
48
+ border: 1px solid #888;
49
+ padding: 1px;
50
+ }
51
+ .box-model-padding {
52
+ border: 1px solid #888;
53
+ padding: 1px;
54
+ background: @primary-bg;
55
+ }
56
+ .box-model-border {
57
+ border: 1px solid #888;
58
+ padding: 1px;
59
+ background: @secondary-bg;
60
+ }
61
+ .box-model-margin {
62
+ padding: 1px;
63
+ }
64
+ .box-model-line {
65
+ display: flex;
66
+ justify-content: center;
67
+ align-items: center;
68
+ }
69
+
70
+ .style-rule {
71
+ padding-left: 1em;
72
+ margin: 5px 0;
73
+ }
74
+ .style-rule_inactive {
75
+ text-decoration: line-through @important-text;
76
+ }
77
+ .style-rule-prefix {
78
+ margin-left: -1em;
79
+ font-style: italic;
80
+ }
81
+ .style-rule-title {
82
+ margin-left: -1em;
83
+ color: @important-text;
84
+ }
@@ -0,0 +1,3 @@
1
+ {
2
+ "component": true
3
+ }
@@ -0,0 +1,17 @@
1
+ Component()
2
+ .property('title', String)
3
+ .property('collapsed', Boolean)
4
+ .property('showRefresh', Boolean)
5
+ .init(({ self, data, setData, listener }) => {
6
+ const toggleBody = listener(() => {
7
+ setData({ collapsed: !data.collapsed })
8
+ if (!data.collapsed) {
9
+ self.triggerEvent('refresh', null, {})
10
+ }
11
+ })
12
+ const refresh = listener(() => {
13
+ self.triggerEvent('refresh', null, {})
14
+ })
15
+ return { toggleBody, refresh }
16
+ })
17
+ .register()
@@ -0,0 +1,8 @@
1
+ <view class="title" catch:tap="toggleBody">
2
+ <view class="arrow {{ collapsed ? '' : 'arrow_open' }}">▶</view>
3
+ <view class="title-text">{{ title }}</view>
4
+ <view wx:if="{{ !collapsed && showRefresh }}" class="title-refresh" hover-class="title-refresh_hover" catch:tap="refresh">⟳</view>
5
+ </view>
6
+ <view class="body {{ collapsed ? 'body_collapsed' : '' }}">
7
+ <slot />
8
+ </view>
@@ -0,0 +1,47 @@
1
+ @import url('../common.wxss');
2
+
3
+ :host {
4
+ display: block;
5
+ }
6
+
7
+ .title {
8
+ padding: 0 5px;
9
+ background: @secondary-bg;
10
+ color: @important-text;
11
+ line-height: 1.5;
12
+ white-space: nowrap;
13
+ }
14
+ .title-text {
15
+ display: inline-block;
16
+ }
17
+ .title-refresh {
18
+ display: inline-block;
19
+ font-size: 1.5em;
20
+ width: 1em;
21
+ height: 1em;
22
+ line-height: 1;
23
+ text-align: center;
24
+ margin-left: 0.5em;
25
+ }
26
+ .title-refresh_hover {
27
+ background-color: @hover-bg;
28
+ }
29
+ .arrow {
30
+ display: inline-block;
31
+ width: 1em;
32
+ height: 1.5em;
33
+ text-align: center;
34
+ transition: transform 200ms ease;
35
+ margin-right: 0.25em;
36
+ }
37
+ .arrow_open {
38
+ transform: rotate(90deg);
39
+ }
40
+
41
+ .body {
42
+ padding: 0 5px;
43
+ overflow: hidden;
44
+ }
45
+ .body_collapsed {
46
+ height: 0;
47
+ }
@@ -0,0 +1,3 @@
1
+ {
2
+ "component": true
3
+ }
@@ -0,0 +1,107 @@
1
+ import * as glassEasel from 'glass-easel'
2
+ import { type protocol } from 'glass-easel-devtools-agent'
3
+ import { sendRequest } from '../../message_channel'
4
+
5
+ Component()
6
+ .options({
7
+ virtualHost: true,
8
+ dataDeepCopy: glassEasel.DeepCopyKind.None,
9
+ propertyPassingDeepCopy: glassEasel.DeepCopyKind.None,
10
+ })
11
+ .property('primitiveValue', null)
12
+ .property('value', {
13
+ type: Object,
14
+ value: null as null | protocol.GlassEaselVar,
15
+ })
16
+ .property('varName', String)
17
+ .property('nodeId', Number)
18
+ .property('attribute', String)
19
+ .data(() => ({
20
+ slices: [] as { dynamic: boolean; str: string }[],
21
+ allowInspect: false,
22
+ updateHighlight: false,
23
+ }))
24
+ .init(({ self, data, setData, observer, method }) => {
25
+ let prevNodeId = 0
26
+ observer(['value', 'primitiveValue'], () => {
27
+ const v: protocol.GlassEaselVar = data.value ?? {
28
+ type: 'primitive',
29
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
30
+ value: data.primitiveValue,
31
+ }
32
+ if (!v) {
33
+ setData({ slices: [], allowInspect: false })
34
+ return
35
+ }
36
+ if (data.nodeId === prevNodeId) {
37
+ updatedAni()
38
+ } else {
39
+ prevNodeId = data.nodeId
40
+ }
41
+ if (v.type === 'primitive') {
42
+ if (v.value === null) {
43
+ setData({ slices: [{ dynamic: true, str: 'null' }], allowInspect: false })
44
+ } else if (v.value === undefined) {
45
+ setData({ slices: [{ dynamic: true, str: 'undefined' }], allowInspect: false })
46
+ } else if (typeof v.value === 'string') {
47
+ setData({
48
+ slices: [
49
+ { dynamic: false, str: '"' },
50
+ { dynamic: true, str: v.value },
51
+ { dynamic: false, str: '"' },
52
+ ],
53
+ allowInspect: false,
54
+ })
55
+ } else {
56
+ setData({ slices: [{ dynamic: true, str: String(v.value) }], allowInspect: false })
57
+ }
58
+ } else if (v.type === 'symbol') {
59
+ setData({
60
+ slices: [
61
+ { dynamic: false, str: 'Symbol(' },
62
+ { dynamic: true, str: v.value },
63
+ { dynamic: false, str: ')' },
64
+ ],
65
+ allowInspect: false,
66
+ })
67
+ } else if (v.type === 'function') {
68
+ setData({ slices: [{ dynamic: true, str: 'Function' }], allowInspect: data.nodeId > 0 })
69
+ } else if (v.type === 'object') {
70
+ setData({ slices: [{ dynamic: true, str: 'Object' }], allowInspect: data.nodeId > 0 })
71
+ } else if (v.type === 'array') {
72
+ setData({ slices: [{ dynamic: true, str: 'Array' }], allowInspect: data.nodeId > 0 })
73
+ }
74
+ })
75
+
76
+ let updateAniEndTimeout = 0
77
+ const updatedAni = method(() => {
78
+ setTimeout(() => {
79
+ if (data.updateHighlight) {
80
+ setData({ updateHighlight: false })
81
+ updatedAni()
82
+ return
83
+ }
84
+ if (updateAniEndTimeout) {
85
+ clearTimeout(updateAniEndTimeout)
86
+ updateAniEndTimeout = 0
87
+ }
88
+ self.setData({ updateHighlight: true, varName: '' })
89
+ updateAniEndTimeout = setTimeout(() => {
90
+ updateAniEndTimeout = 0
91
+ setData({ updateHighlight: false })
92
+ }, 1000)
93
+ }, 200)
94
+ })
95
+
96
+ const useInConsole = method(async () => {
97
+ const { nodeId, attribute } = data
98
+ const { varName } = await sendRequest('DOM.useGlassEaselAttributeInConsole', {
99
+ nodeId,
100
+ attribute,
101
+ })
102
+ self.setData({ varName })
103
+ })
104
+
105
+ return { useInConsole }
106
+ })
107
+ .register()
@@ -0,0 +1,7 @@
1
+ <view class="wrapper {{ updateHighlight ? 'updated' : '' }}">
2
+ <view wx:for="{{ slices }}" class="slice {{ item.dynamic ? 'slice_dynamic' : '' }}">{{ item.str }}</view>
3
+ </view>
4
+ <block wx:if="{{ allowInspect }}">
5
+ <view wx:if="{{ varName }}" class="var-name">={{ varName }}</view>
6
+ <view wx:else class="var-name" hover-class="var-name_hover" catch:tap="useInConsole">↖</view>
7
+ </block>