Skip to content

Commit ba9d58b

Browse files
committed
widgets: rewrite opencomm to use current upstream api instead of ancient colab widgets one
1 parent 71fe442 commit ba9d58b

File tree

5 files changed

+213
-183
lines changed

5 files changed

+213
-183
lines changed

src/packages/frontend/jupyter/browser-actions.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -471,15 +471,19 @@ export class JupyterActions extends JupyterActions0 {
471471
};
472472

473473
send_comm_message_to_kernel = async ({
474+
msg_id,
474475
comm_id,
475476
target_name,
476477
data,
477478
}: {
479+
msg_id?: string;
478480
comm_id: string;
479481
target_name: string;
480482
data: any;
481483
}): Promise<string> => {
482-
const msg_id = uuid();
484+
if (!msg_id) {
485+
msg_id = uuid();
486+
}
483487
const msg = { msg_id, target_name, comm_id, data };
484488
await this.api_call("comm", msg);
485489
// console.log("send_comm_message_to_kernel", "sent", msg);

src/packages/frontend/jupyter/widgets/manager.ts

Lines changed: 0 additions & 114 deletions
Original file line numberDiff line numberDiff line change
@@ -49,9 +49,6 @@ const MAX_DEREF_WAIT_MS = 1000 * 15;
4949

5050
export type SendCommFunction = (string, data) => string;
5151

52-
// v2 wip
53-
import { createWidgetManager, WidgetEnvironment, Comm } from "@cocalc/widgets";
54-
5552
export class WidgetManager extends base.ManagerBase<HTMLElement> {
5653
public ipywidgets_state: IpywidgetsState;
5754
private setWidgetModelIdState: (
@@ -76,117 +73,6 @@ export class WidgetManager extends base.ManagerBase<HTMLElement> {
7673
this.init_ipywidgets_state();
7774
}
7875

79-
private _v2;
80-
v2() {
81-
if (this._v2 != null) {
82-
return this._v2;
83-
}
84-
class Environment implements WidgetEnvironment {
85-
private manager: WidgetManager;
86-
constructor(manager) {
87-
this.manager = manager;
88-
}
89-
90-
async getModelState(modelId) {
91-
const state = this.manager.ipywidgets_state.get_model_state(modelId);
92-
if (!state) {
93-
return undefined;
94-
}
95-
return {
96-
modelName: state._model_name,
97-
modelModule: state._model_module,
98-
modelModuleVersion: state._model_module_version,
99-
state,
100-
};
101-
}
102-
103-
async openCommChannel(
104-
targetName: string,
105-
data?: unknown,
106-
buffers?: ArrayBuffer[],
107-
): Promise<Comm> {
108-
console.log("openCommChannel", { targetName, data, buffers });
109-
const comm = {
110-
send(data: unknown, opts?: { buffers?: ArrayBuffer[] }) {
111-
return new Promise<void>((resolve, _reject) => {
112-
console.log("Data sent:", data, "With options:", opts);
113-
resolve();
114-
});
115-
},
116-
117-
close() {
118-
console.log("Connection closed");
119-
},
120-
121-
get messages() {
122-
const message = {
123-
data: "Hello",
124-
buffers: [new ArrayBuffer(8)],
125-
};
126-
return {
127-
[Symbol.asyncIterator]: async function* () {
128-
yield message;
129-
},
130-
};
131-
},
132-
};
133-
return comm;
134-
}
135-
136-
async renderOutput(
137-
outputItem: unknown,
138-
destination: Element,
139-
): Promise<void> {
140-
console.log("renderOutput", { outputItem, destination });
141-
}
142-
}
143-
144-
const provider = new Environment(this);
145-
const manager = createWidgetManager(provider);
146-
this.manager = manager;
147-
148-
// not efficient -- just a proof of concept/
149-
/*
150-
this.ipywidgets_state.on("change", async (keys) => {
151-
for (const key of keys) {
152-
const [, model_id] = JSON.parse(key);
153-
const state = this.ipywidgets_state.get_model_state(model_id);
154-
await this.dereference_model_links(state);
155-
const model = await manager.get_model(model_id);
156-
console.log("updating rendering of ", model_id);
157-
model.set_state(state);
158-
}
159-
});
160-
*/
161-
this._v2 = manager;
162-
return this._v2;
163-
/*
164-
from IPython.display import display, HTML; display(HTML('<div id="v2">widget here</div>'))
165-
166-
---
167-
168-
id = "c5cba2c1634b4b9c8b0a2dbed33af91a"
169-
manager = y.cocalc_manager.v2()
170-
await manager.render(id, $("#v2")[0]);
171-
model = await manager.get_model(id)
172-
173-
// make changes to widget in cocalc, even from another browser...
174-
175-
state = y.cocalc_manager.ipywidgets_state.get_model_state(id)
176-
await y.cocalc_manager.dereference_model_links(state)
177-
model.set_state(state)
178-
179-
// this works when changing the v2 model to sync back, but how can we
180-
// do this directly with ipywidgets_state instead?
181-
cmodel = await y.cocalc_manager.get_model(id)
182-
cmodel.set_state(model.get_state())
183-
184-
// maybe this? yes this works, but you only see the change
185-
// on another browser, which seems reasonable.
186-
await y.cocalc_manager.handle_model_change(model)
187-
*/
188-
}
189-
19076
private async init_ipywidgets_state(): Promise<void> {
19177
if (this.ipywidgets_state.get_state() != "ready") {
19278
// wait until ready to use.

src/packages/frontend/jupyter/widgets/manager2.ts

Lines changed: 68 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,19 @@
55

66
import * as base from "@jupyter-widgets/base";
77
import { createWidgetManager, is_unpack_models } from "@cocalc/widgets";
8-
import type { WidgetEnvironment, Comm } from "@cocalc/widgets";
8+
import type {
9+
WidgetEnvironment,
10+
IClassicComm,
11+
ICallbacks,
12+
JSONValue,
13+
JSONObject,
14+
} from "@cocalc/widgets";
915
import {
1016
IpywidgetsState,
1117
ModelState,
1218
} from "@cocalc/sync/editor/generic/ipywidgets-state";
1319
import { once } from "@cocalc/util/async-utils";
14-
import { copy, is_array, is_object, len } from "@cocalc/util/misc";
20+
import { copy, is_array, is_object, len, uuid } from "@cocalc/util/misc";
1521
import { fromJS } from "immutable";
1622
import { CellOutputMessage } from "@cocalc/frontend/jupyter/output-messages/message";
1723
import React from "react";
@@ -459,41 +465,78 @@ export class WidgetManager {
459465
comm_id,
460466
target_name,
461467
data,
468+
metadata,
462469
buffers,
463470
}: {
464471
comm_id: string;
465472
target_name: string;
466-
data?: unknown;
473+
data?: JSONValue;
474+
metadata?: JSONValue;
467475
buffers?: ArrayBuffer[];
468-
}): Promise<Comm> => {
469-
log("openCommChannel", { comm_id, target_name, data, buffers });
476+
}): Promise<IClassicComm> => {
477+
log("openCommChannel", { comm_id, target_name, data, buffers, metadata });
470478
const { send_comm_message_to_kernel } = this.actions;
479+
480+
// TODO: we do not currently have anything at all that
481+
// routes messages to this.
482+
type Handler = (x: any) => void;
483+
const messageHandlers: Handler[] = [];
484+
const closeHandlers: Handler[] = [];
485+
471486
const comm = {
472-
async send(data: unknown, opts?: { buffers?: ArrayBuffer[] }) {
487+
comm_id,
488+
489+
target_name,
490+
491+
open(
492+
data: JSONValue,
493+
callbacks?: ICallbacks,
494+
metadata?: JSONObject,
495+
buffers?: ArrayBuffer[] | ArrayBufferView[],
496+
): string {
497+
log("comm.open", { data, callbacks, metadata, buffers });
498+
throw Error("comm.open is not implemented");
499+
},
500+
501+
send(
502+
data: JSONValue,
503+
callbacks?: ICallbacks,
504+
metadata?: JSONObject,
505+
buffers?: ArrayBuffer[] | ArrayBufferView[],
506+
): string {
473507
// TODO: buffers! These need to get sent somehow.
474-
log("comm.send", data, opts);
475-
await send_comm_message_to_kernel({ comm_id, target_name, data });
508+
log("comm.send", { data, buffers, metadata, callbacks });
509+
const msg_id = uuid();
510+
send_comm_message_to_kernel({ msg_id, comm_id, target_name, data });
511+
return msg_id;
512+
},
513+
514+
close(
515+
data?: JSONValue,
516+
callbacks?: ICallbacks,
517+
metadata?: JSONObject,
518+
buffers?: ArrayBuffer[] | ArrayBufferView[],
519+
): string {
520+
log("comm.close", { data, callbacks, metadata, buffers });
521+
throw Error("comm.close not implemented");
476522
},
477523

478-
close() {
479-
// nothing to actually do (?)
480-
log("Connection closed");
524+
on_msg(callback: Handler): void {
525+
log("comm.on_msg -- adding a handler");
526+
messageHandlers.push(callback);
481527
},
482528

483-
get messages() {
484-
// TODO:
485-
log("comm.message: Not Implemented");
486-
// upstream widgets handles these via
487-
// comm.on_msg(this._handle_comm_msg.bind(this));
488-
// see ipywidgets/packages/base/src/widget.ts
489-
return {
490-
[Symbol.asyncIterator]: async function* () {},
491-
};
529+
on_close(callback: Handler): void {
530+
log("comm.on_close -- adding a handler");
531+
closeHandlers.push(callback);
492532
},
493533
};
494534

495-
if (data != null || buffers != null) {
496-
await comm.send(data, { buffers });
535+
if (data != null) {
536+
// [ ] TODO: I think we need a flag so that this is a *create* comm message...
537+
// unless that just happens automatically.
538+
// [ ] TODO: what about metadata?
539+
await comm.send(data, undefined, undefined, buffers);
497540
}
498541
return comm;
499542
};
@@ -534,13 +577,9 @@ class Environment implements WidgetEnvironment {
534577
};
535578
}
536579

537-
async openCommChannel(
538-
target_name: string,
539-
data?: unknown,
540-
buffers?: ArrayBuffer[],
541-
): Promise<Comm> {
542-
log("Environment: openCommChannel", { target_name, data, buffers });
543-
throw Error("Not implemented!");
580+
async openCommChannel(opts) {
581+
log("Environment: openCommChannel", opts);
582+
return await this.manager.openCommChannel(opts);
544583
}
545584

546585
async renderOutput(outputItem: any, destination: Element): Promise<void> {

src/packages/frontend/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@
4545
"@cocalc/local-storage-lru": "^2.4.3",
4646
"@cocalc/sync": "workspace:*",
4747
"@cocalc/util": "workspace:*",
48-
"@cocalc/widgets": "^1.0.4",
48+
"@cocalc/widgets": "^1.1.0",
4949
"@cocalc/xpra-lz4": "^1.1.0",
5050
"@dnd-kit/core": "^6.0.7",
5151
"@dnd-kit/modifiers": "^6.0.1",

0 commit comments

Comments
 (0)