Skip to content

Commit c5edf6f

Browse files
committed
AEDebugging_ Timeline improvements _ add overview, make reactive, layouting
SQUASHED: AEDebugging_-Timeline-improvements-_-add-overview-make-reactive-layouting,
1 parent 23edf6c commit c5edf6f

File tree

4 files changed

+149
-79
lines changed

4 files changed

+149
-79
lines changed

src/client/reactive/active-expression/active-expression.js

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,16 @@ export const AExprRegistry = {
7373
*/
7474
allAsArray() {
7575
return Array.from(self.__aexprRegistry_aexprs__);
76+
},
77+
78+
addEventListener(reference, callback) {
79+
if(!this.listeners) this.listeners = []
80+
this.listeners.push({ reference, callback });
81+
},
82+
83+
eventListeners() {
84+
this.listeners = this.listeners.filter(listener => listener.reference);
85+
return this.listeners;
7686
}
7787
};
7888

@@ -321,9 +331,8 @@ export class BaseActiveExpression {
321331
const lastValue = this.lastValue;
322332
this.storeResult(value);
323333
this.findCallee().then(trigger => {
324-
this.logEvent('changed value', {value, trigger});
325-
})
326-
334+
this.logEvent('changed value', { value, trigger, lastValue });
335+
});
327336

328337
this.notify(value, {
329338
lastValue,
@@ -335,9 +344,9 @@ export class BaseActiveExpression {
335344
async findCallee() {
336345
const stack = lively.stack();
337346
const frames = stack.frames;
338-
347+
339348
for (let frame of frames) {
340-
if(!frame.file.includes("active-expression") && frame.file !== "<anonymous>") {
349+
if (!frame.file.includes("active-expression") && frame.file !== "<anonymous>") {
341350
return await frame.getSourceLoc();
342351
}
343352
}
@@ -572,7 +581,9 @@ export class BaseActiveExpression {
572581
if (this.isMeta()) return;
573582
//if(!this.meta().has('events'))this.meta({events : new Array()});
574583
let events = this.meta().get('events');
575-
events.push({ timestamp: new Date(), type, value });
584+
const event = { timestamp: new Date(), type, value };
585+
AExprRegistry.eventListeners().forEach(listener => listener.callback(this, event));
586+
events.push(event);
576587
if (events.length > 5000) events.shift();
577588
}
578589

Lines changed: 52 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,56 @@
11
<template id="aexpr-timeline" >
2-
<style data-src="/src/external/font-awesome/css/font-awesome.css"></style>
3-
<style data-src="/templates/livelystyle.css"></style>
4-
<style data-src="/src/client/reactive/components/basic/aexpr-timeline.css"></style>
5-
<style data-src="/src/external/jstree/themes/default/style.css"></style>
6-
<script src="https://lively-kernel.org/lively4/lively4-markus/src/external/jquery.js"></script>
7-
<script src="https://lively-kernel.org/lively4/lively4-markus/src/external/jstree/jstree.js"></script>
8-
9-
<style>
10-
:host {
11-
12-
}
13-
#content {
14-
background-color: gray;
15-
}
16-
</style>
17-
<div id="aeOverview"></div>
18-
<div id="diagram" style="width: 100%; height: calc(100% - 80px); overflow: auto"></div>
19-
<p class="infos">
20-
<span id="numberEvents"></span> events <span class="light">found between</span> <br />
21-
<span id="zoomStart"></span> <span class="light">and</span> <span id="zoomEnd"></span>
22-
</p>
23-
<input type="checkbox" id="groupByLine" name="group by line" checked>
24-
<label for="groupByLine"> Group by line </label><br>
2+
<style data-src="/src/external/font-awesome/css/font-awesome.css"></style>
3+
<style data-src="/templates/livelystyle.css"></style>
4+
<style data-src="/src/client/reactive/components/basic/aexpr-timeline.css"></style>
5+
<style data-src="/src/external/jstree/themes/default/style.css"></style>
6+
<script src="https://lively-kernel.org/lively4/lively4-markus/src/external/jquery.js"></script>
7+
<script src="https://lively-kernel.org/lively4/lively4-markus/src/external/jstree/jstree.js"></script>
8+
9+
<style>
10+
:host {
11+
}
12+
#content {
13+
background-color: gray;
14+
}
15+
.container {
16+
display: grid;
17+
grid-template-rows: auto 1fr auto;
18+
align-items:stretch;
19+
justify-items:stretch;
20+
height: 100%;
21+
}
22+
.row {
23+
display: grid;
24+
grid-template-columns: 1fr 1fr;
25+
align-items:stretch;
26+
justify-items:stretch;
27+
width: 100%;
28+
}
29+
.pane {
30+
margin: 1px;
31+
border: 1px solid #d5d5d5;
32+
}
33+
</style>
34+
35+
<div class="container">
36+
<div class="row">
37+
<div id="aeOverview" class="pane"></div>
38+
<div>
39+
Values over time
40+
</div>
41+
</div>
42+
<div id="diagram" class="pane"></div>
43+
<div id="footer" class="pane">
44+
<p class="infos">
45+
<span id="numberEvents"></span> events <span class="light">found between</span> <br />
46+
<span id="zoomStart"></span> <span class="light">and</span> <span id="zoomEnd"></span>
47+
</p>
48+
<center>
49+
<input type="checkbox" id="groupByLine" name="group by line" checked>
50+
<label for="groupByLine"> Group by line </label><br>
51+
</center>
52+
</div>
53+
</div>
2554
<content></content>
2655
</template>
2756

src/client/reactive/components/basic/aexpr-timeline.js

Lines changed: 75 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,14 @@ import eventDrops from 'src/external/event-drops.js';
66
import jQuery from 'src/external/jquery.js';
77
import jstree from 'src/external/jstree/jstree.js';
88
import d3 from 'src/external/d3.v5.js';
9+
import { debounce } from "utils";
910

1011
import { AExprRegistry } from 'src/client/reactive/active-expression/active-expression.js';
1112

1213
export default class EventDrops extends Morph {
1314
async initialize() {
1415
this.windowTitle = "Active Expression Event Timeline";
16+
this.setWindowSize(1200, 800);
1517
this.config = {
1618
d3,
1719
bound: { format: () => undefined },
@@ -26,7 +28,7 @@ export default class EventDrops extends Morph {
2628
}
2729
},
2830
label: {
29-
width: 250,
31+
width: 300,
3032
text: d => `${d.name.substring(d.name.lastIndexOf("/") + 1)} (${d.data.length})`
3133
},
3234
restrictPan: true,
@@ -92,33 +94,49 @@ export default class EventDrops extends Morph {
9294
};
9395
this.chart = eventDrops(this.config);
9496

95-
this.groupByLine.addEventListener('change', () => {
96-
if (this.groupByLine.checked) {
97-
this.groupingFunction = this.locationGrouping();
98-
} else {
99-
this.groupingFunction = this.instanceGrouping();
100-
}
101-
});
102-
10397
this.numberEventsContainer = this.numberEvents;
10498
document.body.querySelectorAll('#event-drops-tooltip').forEach(each => each.remove());
10599
this.d3 = d3;
106100
jQuery(this.aeOverview).jstree({
107-
"plugins" : [ "wholerow", "checkbox" ],
101+
"plugins": ["wholerow", "checkbox"],
108102
"checkbox": {
109103
"keep_selected_style": false
110104
},
111105
'core': {
112-
"themes" : { "icons" : false },
106+
"themes": { "icons": false }
107+
}
108+
});
109+
110+
this.aeChangedDebounced = (() => this.setAexprs(this.getDataFromSource())).debounce(10, 300);
111+
this.eventsChangedDebounced = (() => this.updateTimeline(this.getDataFromSource())).debounce(100, 1000);
112+
this.activeExpressionsChanged();
113+
//Register to AE changes
114+
AExprRegistry.addEventListener(this, (ae, event) => {
115+
if(event.type === "created" || event.type === "disposed") {
116+
this.activeExpressionsChanged()
117+
} else {
118+
this.eventsChanged()
113119
}
114120
});
115-
this.update();
121+
//Register to overview selection changes
122+
jQuery(this.aeOverview).on("changed.jstree", (e, data) => {
123+
this.eventsChanged();
124+
})
125+
//Register to grouping change
126+
this.groupByLine.addEventListener('change', () => {
127+
if (this.groupByLine.checked) {
128+
this.groupingFunction = this.locationGrouping();
129+
} else {
130+
this.groupingFunction = this.instanceGrouping();
131+
}
132+
this.eventsChanged();
133+
});
116134
}
117135

118136
humanizeEventData(event) {
119137
switch (event.type) {
120138
case 'changed value':
121-
return this.humanizePosition(event.value.trigger.source, event.value.trigger.line);
139+
return <div>{this.humanizePosition(event.value.trigger.source, event.value.trigger.line)} <br /> <span style="color:#00AAAA">{event.value.lastValue}</span><span style="color:#00AAAA">{event.value.value}</span></div>;
122140
case 'created':
123141
case 'disposed':
124142
{
@@ -142,24 +160,33 @@ export default class EventDrops extends Morph {
142160
let dataFromSource = this.dataFromSource || (() => AExprRegistry.allAsArray());
143161
if (_.isFunction(dataFromSource)) return dataFromSource();else return dataFromSource;
144162
}
145-
163+
146164
fileGrouping() {
147165
let fileName = string => string.substring(0, string.lastIndexOf("@"));
148-
return (each => fileName(each.meta().get('id')))
166+
return each => fileName(each.meta().get('id'));
149167
}
150-
168+
151169
locationGrouping() {
152170
let locationID = string => string.substring(0, string.lastIndexOf("#"));
153-
return (each => locationID(each.meta().get('id')))
171+
return each => locationID(each.meta().get('id'));
154172
}
155-
173+
156174
instanceGrouping() {
157-
return (each => each.meta().get('id'));
175+
return each => each.meta().get('id');
158176
}
159177

160178
getGroupingFunction() {
161179
return this.groupingFunction || this.locationGrouping();
162180
}
181+
182+
183+
activeExpressionsChanged() {
184+
this.aeChangedDebounced();
185+
}
186+
187+
eventsChanged() {
188+
this.eventsChangedDebounced();
189+
}
163190

164191
update() {
165192
if (this.detached) return;
@@ -170,17 +197,22 @@ export default class EventDrops extends Morph {
170197
}
171198

172199
setAexprs(aexprs) {
173-
for(let i = 0; i < aexprs.length; i++) {
200+
for (let i = 0; i < aexprs.length; i++) {
174201
aexprs[i].timelineID = i;
175202
}
176-
const checkedIndices = jQuery(this.aeOverview).jstree(true).get_bottom_selected();
177-
const selectedAEs = checkedIndices.map(i => aexprs[i - 1]);
178203
this.updateOverview(aexprs);
204+
this.updateTimeline(aexprs)
205+
}
206+
207+
updateTimeline(aexprs) {
208+
const checkedIndices = jQuery(this.aeOverview).jstree(true).get_bottom_selected();
209+
const selectedAEs = checkedIndices.map(i => aexprs[i - 1]).filter(ae => ae);
179210
let scrollBefore = this.diagram.scrollTop;
180-
if (selectedAEs.length == 0) return;
181211
let groups = selectedAEs.groupBy(this.getGroupingFunction());
182212
groups = Object.keys(groups).map(each => ({ name: each, data: groups[each].flatMap(ae => ae.meta().get('events')) }));
183213
this.setData(groups);
214+
if (selectedAEs.length == 0) return;
215+
184216
let newDomain = this.zoomedTo;
185217
if (!newDomain) {
186218
let difference = 0;
@@ -200,47 +232,40 @@ export default class EventDrops extends Morph {
200232
newDomain = [min, max];
201233
}
202234
}
235+
203236
this.chart.scale().domain(newDomain);
204237
this.chart.zoomToDomain(newDomain);
205238
this.diagram.scrollTop = scrollBefore;
206239
}
207-
240+
208241
updateOverview(aexprs) {
209-
jQuery(this.aeOverview).jstree(true).settings.core.data = this.generateOverviewJSON(aexprs);
242+
jQuery(this.aeOverview).jstree(true).settings.core.data = this.generateOverviewJSON(aexprs);
210243
jQuery(this.aeOverview).jstree(true).refresh();
211244
}
212-
245+
213246
generateOverviewJSON(aexprs) {
214247
let json = [];
215248
let files = aexprs.groupBy(this.fileGrouping());
216-
for(const file of Object.keys(files)) {
249+
for (const file of Object.keys(files)) {
217250
let locations = files[file].groupBy(this.locationGrouping());
218-
const children = Object.keys(locations).map(location => {return {
219-
"text": "line " + location.substring(location.lastIndexOf("@") + 1),
220-
"children": locations[location].map((ae) => {
221-
const id = ae.meta().get('id');
222-
return {
223-
"id": ae.timelineID + 1,
224-
"text": id.substring(id.lastIndexOf("#") + 1)
225-
}
226-
})
227-
}});
251+
const children = Object.keys(locations).map(location => {
252+
return {
253+
"text": "line " + location.substring(location.lastIndexOf("@") + 1),
254+
"children": locations[location].map(ae => {
255+
const id = ae.meta().get('id');
256+
return {
257+
"id": ae.timelineID + 1,
258+
"text": id.substring(id.lastIndexOf("#") + 1)
259+
};
260+
})
261+
};
262+
});
228263
json.push({
229264
"text": file,
230-
"children": children,
231-
})
265+
"children": children
266+
});
232267
}
233268
return json;
234-
/*[
235-
{
236-
'text': 'root',
237-
"icon" : "/static/3.3.11/assets/images/tree_icon.png",
238-
'children': [
239-
'child1',
240-
'child2'
241-
]
242-
}
243-
]*/
244269
}
245270

246271
setData(data) {
@@ -259,7 +284,7 @@ export default class EventDrops extends Morph {
259284
`;
260285
}
261286
humanizePosition(file, line) {
262-
return "in " + file.substring(file.lastIndexOf('/') + 1) + " line " + line;
287+
return <div>in <span style="color:#0000FF">{file.substring(file.lastIndexOf('/') + 1)}</span> line <span style="color:#0000FF">{line}</span></div>;
263288
}
264289

265290
livelyMigrate(other) {

src/components/widgets/lively-morph.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,11 @@ export default class Morph extends HTMLElement {
2727
}
2828
return morph;
2929
}
30+
31+
setWindowSize(width, height) {
32+
this.parentElement.style["width"] = width + "px";
33+
this.parentElement.style["height"] = height + "px";
34+
}
3035

3136
set windowTitle(string){
3237
this._windowTitle = string;

0 commit comments

Comments
 (0)