Skip to content

Commit adeb3aa

Browse files
committed
Merge branch 'gh-pages' of https://github.com/LivelyKernel/lively4-core into gh-pages
2 parents 38be941 + f9c6ff4 commit adeb3aa

File tree

8 files changed

+420
-81
lines changed

8 files changed

+420
-81
lines changed

demos/markdown/mle-workspaces.md

Lines changed: 151 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,154 @@
11
# Workspace Example
22

33
This is an Example of a workspace embedded in HTML/Markdown that will persist it's code in browser local storage.
4-
<lively-script><script>import focalStorage from 'src/external/focalStorage.js'; import {SocketSingleton} from 'src/components/mle/socket.js'; const idMap = new Map(); const enclosingDiv = <div />; var mle = {}; async function startUp(){ await SocketSingleton.get(); let i = 0; while(await focalStorage.getItem(`markdown_workspace_${i}`)){ await new Pane(i, await focalStorage.getItem(`markdown_workspace_${i}_kind`)).create(); i++; } } class Pane { constructor(id, kind){ if(id !== undefined){ this._id = id; this.kind = kind; } else { this._id = idMap.size; } idMap.set(this.textStorageId, this); } onDoIt() { this.saveText() this.workspace.tryBoundEval(this.workspace.value) } async onReset() { if(this.kind) this.socket = await SocketSingleton.reset(); this.logarea.value = "LOGS"; this.drawarea.innerHTML = ""; } get defaultText() { return "sql`SELECT * FROM students`" } get textStorageId() { return `markdown_workspace_${this._id}` } async loadText() { var loaded = await focalStorage.getItem(this.textStorageId); if (loaded) return loaded; return this.defaultText; } async saveText() { focalStorage.setItem(this.textStorageId, this.workspace.value); focalStorage.setItem(`${this.textStorageId}_kind`, this.kind); } log(s) { this.logarea.value += s + ""; } async create() { // #TODO #META style and pre tags are problematic in Markdown scripts this.kind = this.kind === undefined ? await lively.confirm("MLE Workspace? Ok for yes, Cancel for No.") : this.kind; var style = document.createElement("style"); style.textContent = ` lively-code-mirror { border: 1px solid gray; flex: 4; }`; var buttons = <div> <button click={() => {this.onDoIt()} }>DoIt</button> <button click={() => {this.onReset()} }>reset</button> </div>; this.workspace = await (<lively-code-mirror></lively-code-mirror>); this.workspace.value = await this.loadText(); this.workspace.doitContext = this; this.logarea = <textarea disabled style="flex: 2;"/>; this.logarea.value = "LOGS"; this.drawarea = <div></div>; if(this.kind){ this.workspace.boundEval = async function(s) { this.socket = this.socket || await SocketSingleton.get(); this.socket.emit("test", {id: this.textStorageId, func: "evaluate", parameters: [s]}); const value = await new Promise((res) => { this.socket.on("result", r => { if(!r) return; if(r.id !== this.textStorageId) return; console.log(r.data) res(JSON.parse(r.data)); }); }); this.outData = value; Object.defineProperty(window, `$${this._id}`, {configurable: true, value}); this.log(JSON.stringify(value)); this.saveText(); return {value}; } this.workspace.boundEval = this.workspace.boundEval.bind(this); } enclosingDiv.appendChild( <div style="padding: 10px; width:90%;"> {style} <h4>{this.kind ? "MLE" : "DOM"} Workspace ${this._id}</h4> {buttons} <div style="display: flex;"> {this.workspace} {this.logarea} </div> {this.drawarea} </div> ); } } startUp().then(t => <div> <button click={() => new Pane().create()}>New workspace</button> {enclosingDiv} </div>)</script> </lively-script>
4+
<script>
5+
import focalStorage from 'src/external/focalStorage.js';
6+
import {SocketSingleton} from 'src/components/mle/socket.js';
7+
const idMap = new Map();
8+
const enclosingDiv = <div />;
9+
var mle = {};
10+
async function startUp(){
11+
await SocketSingleton.get();
12+
let i = 0;
13+
while(await focalStorage.getItem(`markdown_workspace_${i}`)){
14+
await new Pane(i, await focalStorage.getItem(`markdown_workspace_${i}_kind`)).create();
15+
i++;
16+
}
17+
}
18+
class Pane {
19+
constructor(id, kind){
20+
if(id !== undefined){
21+
this._id = id;
22+
this.kind = kind;
23+
} else {
24+
this._id = idMap.size;
25+
}
26+
idMap.set(this.textStorageId, this);
27+
}
28+
29+
onDoIt() {
30+
this.saveText();
31+
this.workspace.tryBoundEval(this.workspace.value);
32+
}
33+
34+
async onReset() {
35+
if(this.kind) this.socket = await SocketSingleton.reset();
36+
this.logarea.value = "LOGS";
37+
this.drawarea.innerHTML = "";
38+
}
39+
40+
get defaultText() {
41+
return "sql`SELECT * FROM students`"
42+
}
43+
44+
get textStorageId() {
45+
return `markdown_workspace_${this._id}`
46+
}
47+
48+
async loadText() {
49+
var loaded = await focalStorage.getItem(this.textStorageId);
50+
if (loaded) return loaded;
51+
return this.defaultText;
52+
}
53+
54+
async saveText() {
55+
focalStorage.setItem(this.textStorageId, this.workspace.value);
56+
focalStorage.setItem(`${this.textStorageId}_kind`, this.kind);
57+
}
58+
59+
log(s) {
60+
this.logarea.value += s + "";
61+
}
62+
63+
async create() {
64+
// #TODO #META style and pre tags are problematic in Markdown scripts
65+
this.kind = this.kind === undefined ? await lively.confirm("MLE Workspace? Ok for yes, Cancel for No.") : this.kind;
66+
var style = document.createElement("style");
67+
style.textContent = ` lively-code-mirror { border: 1px solid gray; flex: 4; }`;
68+
var buttons = <div>
69+
<button click={() => {this.onDoIt()} }>DoIt</button>
70+
<button click={() => {this.onReset()} }>reset</button>
71+
</div>;
72+
this.workspace = await (<lively-code-mirror></lively-code-mirror>);
73+
this.workspace.value = await this.loadText();
74+
this.workspace.doitContext = this;
75+
this.logarea = <textarea disabled style="flex: 2;"/>;
76+
this.logarea.value = "LOGS";
77+
this.drawarea = <div></div>;
78+
if(this.kind){
79+
this.workspace.boundEval = async function(s) {
80+
this.socket = this.socket || await SocketSingleton.get();
81+
this.socket.emit("test", {
82+
id: this.textStorageId,
83+
func: "evaluate",
84+
parameters: [s]
85+
});
86+
const value = await new Promise((res) => {
87+
this.socket.on("result", r => {
88+
if(!r) return;
89+
if(r.id !== this.textStorageId) return;
90+
res(JSON.parse(r.data));
91+
});
92+
});
93+
this.outData = value;
94+
Object.defineProperty(window, `$${this._id}`, {configurable: true, value});
95+
this.log(JSON.stringify(value));
96+
this.saveText();
97+
return {value};
98+
}
99+
this.workspace.boundEval = this.workspace.boundEval.bind(this);
100+
}
101+
102+
enclosingDiv.appendChild(<div style="padding: 10px; width:90%;">
103+
{style}
104+
<h4>{this.kind ? "MLE" : "DOM"} Workspace ${this._id}</h4>
105+
{buttons}
106+
<div style="display: flex;">
107+
{this.workspace}
108+
{this.logarea}
109+
</div>
110+
{this.drawarea}
111+
</div>);
112+
}
113+
}
114+
115+
startUp().then(t => <div>
116+
<button click={() => new Pane().create()}>New workspace</button>
117+
{enclosingDiv}
118+
</div>)
119+
</script>
120+
121+
Example Query:
122+
```
123+
const projects = sql`SELECT * FROM projects where id < 20`.map(
124+
({id, url}) => {
125+
const prs = sql`SELECT * FROM pull_requests where head_repo_id=${id}`.map(({id}) => {
126+
const events = sql`SELECT * FROM pull_request_history where pull_request_id=${id}`.reduce(
127+
({opened, last}, {created_at, action}) => {
128+
if(action.startsWith("opened")){
129+
if(!opened || opened > created_at) {
130+
return {opened: created_at, last};
131+
}
132+
}
133+
if(action.startsWith("closed") || action.startsWith("merged")){
134+
if(!last || last < created_at){
135+
return {opened, last: created_at}
136+
}
137+
}
138+
return {opened, last};
139+
}, {opened: false, last: false});
140+
return events.last - events.opened;
141+
}).reduce(({count, sum}, curr) => ({count: count+1, sum: sum+curr}), {count: 0, sum: 0});
142+
return {repo: url, avg: prs.sum/prs.count};
143+
}
144+
).reduce((prev, curr) => {
145+
if(!curr.avg || curr.avg <0) return prev;
146+
if(prev.length < 3) return [...prev, curr];
147+
const t = prev.sort((a, b) => a.avg-b.avg);
148+
if(t[0].avg < curr.avg){
149+
[...t.slice(1), curr]
150+
}
151+
return t;
152+
}, []).map(({repo, ...t}) => ({...t, repo:repo.split("/").slice(4).join("/")}));
153+
projects
154+
```

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: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,9 @@
1-
here we go....
1+
'pi'
2+
import { PIScheme } from 'polymorphic-identifiers';
3+
class fourtyTwo extends PIScheme {
4+
read() {
5+
return 42;
6+
}
7+
}
8+
9+
fourtyTwo``
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

0 commit comments

Comments
 (0)