@@ -14,7 +14,7 @@ import type {
14
14
} from "@cocalc/widgets" ;
15
15
import {
16
16
IpywidgetsState ,
17
- ModelState ,
17
+ SerializedModelState ,
18
18
} from "@cocalc/sync/editor/generic/ipywidgets-state" ;
19
19
import { once } from "@cocalc/util/async-utils" ;
20
20
import { is_array , is_object , len , uuid } from "@cocalc/util/misc" ;
@@ -26,6 +26,8 @@ import { size } from "lodash";
26
26
import type { JupyterActions } from "@cocalc/frontend/jupyter/browser-actions" ;
27
27
import { FileContext } from "@cocalc/frontend/lib/file-context" ;
28
28
29
+ type DeserializedModelState = { [ key : string ] : any } ;
30
+
29
31
export type SendCommFunction = ( string , data ) => string ;
30
32
31
33
const log = console . log ;
@@ -57,7 +59,7 @@ export class WidgetManager {
57
59
this . ipywidgets_state . on ( "change" , async ( keys ) => {
58
60
for ( const key of keys ) {
59
61
const [ , model_id , type ] = JSON . parse ( key ) ;
60
- this . handleIpWidgetsChange ( { model_id, type } ) ;
62
+ this . handleIpwidgetsTableChange ( { model_id, type } ) ;
61
63
}
62
64
} ) ;
63
65
}
@@ -86,6 +88,7 @@ VBox([s1, s2])
86
88
if ( type == "state" ) {
87
89
( async ( ) => {
88
90
try {
91
+ // causes initialization:
89
92
await this . manager . get_model ( model_id ) ;
90
93
// also ensure any buffers are set, e.g., this is needed when loading
91
94
// https://ipywidgets.readthedocs.io/en/latest/examples/Widget%20List.html#image
@@ -99,11 +102,17 @@ VBox([s1, s2])
99
102
}
100
103
} ;
101
104
102
- private handleIpWidgetsChange = async ( { model_id, type } ) => {
103
- log ( "handleIpWidgetsChange " , { model_id, type } ) ;
105
+ private handleIpwidgetsTableChange = async ( { model_id, type } ) => {
106
+ log ( "handleIpwidgetsTableChange " , { model_id, type } ) ;
104
107
switch ( type ) {
105
108
case "state" :
106
- await this . ipywidgets_state_StateChange ( model_id ) ;
109
+ // Overall state is only used for creating widget when it is
110
+ // rendered for the first time as part of Environment.getSerializedModelState
111
+ // So do NOT call it again here on any change. Updates for RTC use
112
+ // type='value'. Doing this causes a lot of noise, which e.g., completely
113
+ // breaks rendering using the threejs custom widgets, e.g., this breaks;
114
+ // from pythreejs import DodecahedronGeometry; DodecahedronGeometry()
115
+ // await this.ipywidgets_state_StateChange(model_id);
107
116
break ;
108
117
case "value" :
109
118
await this . ipywidgets_state_ValueChange ( model_id ) ;
@@ -120,19 +129,19 @@ VBox([s1, s2])
120
129
}
121
130
} ;
122
131
123
- private ipywidgets_state_StateChange = async ( model_id : string ) => {
124
- log ( "handleStateChange : " , model_id ) ;
125
- const state = this . ipywidgets_state . get_model_state ( model_id ) ;
126
- log ( "handleStateChange : state=" , state ) ;
127
- if ( state == null ) {
128
- return ;
129
- }
130
- await this . updateModel ( model_id , state ! , false ) ;
131
- } ;
132
+ // private ipywidgets_state_StateChange = async (model_id: string) => {
133
+ // log("ipywidgets_state_StateChange : ", model_id);
134
+ // const state = this.ipywidgets_state.getSerializedModelState (model_id);
135
+ // log("ipywidgets_state_StateChange : state=", JSON.stringify( state) );
136
+ // if (state == null) {
137
+ // return;
138
+ // }
139
+ // await this.updateModel(model_id, state!, false);
140
+ // };
132
141
133
142
private updateModel = async (
134
143
model_id : string ,
135
- changed : ModelState ,
144
+ changed : SerializedModelState ,
136
145
merge : boolean ,
137
146
) : Promise < void > => {
138
147
const model : base . DOMWidgetModel | undefined =
@@ -145,14 +154,9 @@ VBox([s1, s2])
145
154
if ( changed . last_changed != null ) {
146
155
this . last_changed [ model_id ] = changed ;
147
156
}
148
- const success = await this . dereferenceModelLinks ( changed ) ;
149
- if ( ! success ) {
150
- console . warn (
151
- "update model suddenly references not known models -- can't handle this yet (TODO!); ignoring update." ,
152
- ) ;
153
- return ;
154
- }
157
+
155
158
changed = await this . deserializeState ( model , changed ) ;
159
+
156
160
if ( changed . hasOwnProperty ( "outputs" ) && changed [ "outputs" ] == null ) {
157
161
// It can definitely be 'undefined' but set, e.g., the 'out.clear_output()' example at
158
162
// https://ipywidgets.readthedocs.io/en/latest/examples/Output%20Widget.html
@@ -161,23 +165,40 @@ VBox([s1, s2])
161
165
changed [ "outputs" ] = [ ] ;
162
166
}
163
167
if ( merge ) {
164
- const state = model . get_state ( false ) ;
165
- const x : ModelState = { } ;
168
+ const deserializedState = model . get_state ( false ) ;
169
+ const x : DeserializedModelState = { } ;
166
170
for ( const k in changed ) {
167
- if ( state [ k ] != null && is_object ( state [ k ] ) && is_object ( changed [ k ] ) ) {
168
- x [ k ] = { ...state [ k ] , ...changed [ k ] } ;
171
+ if (
172
+ deserializedState [ k ] != null &&
173
+ is_object ( deserializedState [ k ] ) &&
174
+ is_object ( changed [ k ] )
175
+ ) {
176
+ x [ k ] = { ...deserializedState [ k ] , ...changed [ k ] } ;
169
177
} else {
170
178
x [ k ] = changed [ k ] ;
171
179
}
172
180
}
173
181
changed = x ;
174
182
}
175
- log ( "updateModel -- doing set_state" , { model_id, merge, changed } ) ;
183
+
184
+ const success = await this . dereferenceModelLinks ( changed ) ;
185
+ if ( ! success ) {
186
+ console . warn (
187
+ "update model suddenly references not known models -- can't handle this yet (TODO!); ignoring update." ,
188
+ ) ;
189
+ return ;
190
+ }
191
+
192
+ log ( "updateModel -- doing set" , {
193
+ model_id,
194
+ merge,
195
+ changed : { ...changed } ,
196
+ } ) ;
176
197
try {
177
198
model . set ( changed ) ;
178
199
} catch ( err ) {
179
- //window.z = { merge, model, model_id, changed };
180
- console . error ( "saved to z " , err ) ;
200
+ // window.z = { merge, model, model_id, changed: { ...changed } };
201
+ console . error ( "updateModel set failed -- " , err ) ;
181
202
}
182
203
} ;
183
204
@@ -235,10 +256,11 @@ VBox([s1, s2])
235
256
// via ipywidgets_state_BuffersChange!
236
257
private setBuffers = async (
237
258
model_id : string ,
238
- state : ModelState ,
259
+ state : SerializedModelState ,
239
260
) : Promise < void > => {
240
261
const { buffer_paths, buffers } =
241
262
await this . ipywidgets_state . get_model_buffers ( model_id ) ;
263
+ log ( "setBuffers" , model_id , buffer_paths ) ;
242
264
if ( buffer_paths . length == 0 ) {
243
265
return ; // nothing to do
244
266
}
@@ -273,7 +295,7 @@ VBox([s1, s2])
273
295
return ;
274
296
}
275
297
log ( "handleBuffersChange: " , { model_id, buffer_paths, buffers } ) ;
276
- const state = this . ipywidgets_state . get_model_state ( model_id ) ;
298
+ const state = this . ipywidgets_state . getSerializedModelState ( model_id ) ;
277
299
if ( state == null ) {
278
300
log ( "handleBuffersChange: no state data" , { model_id } ) ;
279
301
return ;
@@ -295,7 +317,7 @@ VBox([s1, s2])
295
317
model . set ( deserializedChange ) ;
296
318
} catch ( err ) {
297
319
// window.y = { model_id, model, change, buffer_paths, buffers };
298
- console . error ( "saved to y " , err ) ;
320
+ console . error ( "ipywidgets_state_BuffersChange failed -- " , err ) ;
299
321
}
300
322
}
301
323
} ;
@@ -372,8 +394,8 @@ VBox([s1, s2])
372
394
373
395
private deserializeState = async (
374
396
model : base . DOMWidgetModel ,
375
- serialized_state : ModelState ,
376
- ) : Promise < ModelState > => {
397
+ serialized_state : SerializedModelState ,
398
+ ) : Promise < DeserializedModelState > => {
377
399
// log("deserializeState", { model, serialized_state });
378
400
// NOTE: this is a reimplementation of soemething in
379
401
// ipywidgets/packages/base/src/widget.ts
@@ -393,8 +415,8 @@ VBox([s1, s2])
393
415
private _deserializeState = async (
394
416
model_id : string ,
395
417
constructor : any ,
396
- serialized_state : ModelState ,
397
- ) : Promise < ModelState > => {
418
+ serialized_state : SerializedModelState ,
419
+ ) : Promise < DeserializedModelState > => {
398
420
// console.log("_deserialize_state", {
399
421
// model_id,
400
422
// constructor,
@@ -409,11 +431,15 @@ VBox([s1, s2])
409
431
// We skip deserialize if the deserialize function is unpack_model,
410
432
// since we do our own model unpacking, due to issues with ordering
411
433
// and RTC.
412
- const deserialized : ModelState = { } ;
434
+ const deserialized : DeserializedModelState = { } ;
413
435
await this . setBuffers ( model_id , serialized_state ) ;
414
436
for ( const k in serialized_state ) {
415
437
const deserialize = serializers [ k ] ?. deserialize ;
416
- if ( deserialize != null && ! is_unpack_models ( deserialize ) ) {
438
+ if (
439
+ deserialize != null &&
440
+ ! is_unpack_models ( deserialize ) &&
441
+ ! isModelReference ( serialized_state [ k ] )
442
+ ) {
417
443
deserialized [ k ] = deserialize ( serialized_state [ k ] ) ;
418
444
} else {
419
445
deserialized [ k ] = serialized_state [ k ] ;
@@ -445,7 +471,7 @@ VBox([s1, s2])
445
471
private dereferenceModelLink = async (
446
472
val : string ,
447
473
) : Promise < base . DOMWidgetModel | undefined > => {
448
- if ( val . slice ( 0 , 10 ) !== "IPY_MODEL_" ) {
474
+ if ( ! isModelReference ( val ) ) {
449
475
throw Error ( `val (="${ val } ") is not a model reference.` ) ;
450
476
}
451
477
@@ -454,27 +480,21 @@ VBox([s1, s2])
454
480
} ;
455
481
456
482
dereferenceModelLinks = async ( state ) : Promise < boolean > => {
457
- // log("dereferenceModelLinks", "BEFORE", state);
483
+ // log("dereferenceModelLinks", "BEFORE", { ... state } );
458
484
for ( const key in state ) {
459
485
const val = state [ key ] ;
460
- if ( typeof val === "string" ) {
461
- // single string
462
- if ( val . slice ( 0 , 10 ) === "IPY_MODEL_" ) {
463
- // that is a reference
464
- const model = await this . dereferenceModelLink ( val ) ;
465
- if ( model != null ) {
466
- state [ key ] = model ;
467
- } else {
468
- return false ; // something can't be resolved yet.
469
- }
486
+ if ( isModelReference ( val ) ) {
487
+ // that is a reference
488
+ const model = await this . dereferenceModelLink ( val ) ;
489
+ if ( model != null ) {
490
+ state [ key ] = model ;
491
+ } else {
492
+ return false ; // something can't be resolved yet.
470
493
}
471
494
} else if ( is_array ( val ) ) {
472
495
// array of stuff
473
496
for ( const i in val ) {
474
- if (
475
- typeof val [ i ] === "string" &&
476
- val [ i ] . slice ( 0 , 10 ) === "IPY_MODEL_"
477
- ) {
497
+ if ( isModelReference ( val [ i ] ) ) {
478
498
// this one is a string reference
479
499
const model = await this . dereferenceModelLink ( val [ i ] ) ;
480
500
if ( model != null ) {
@@ -487,7 +507,7 @@ VBox([s1, s2])
487
507
} else if ( is_object ( val ) ) {
488
508
for ( const key in val ) {
489
509
const z = val [ key ] ;
490
- if ( typeof z == "string" && z . slice ( 0 , 10 ) == "IPY_MODEL_" ) {
510
+ if ( isModelReference ( z ) ) {
491
511
const model = await this . dereferenceModelLink ( z ) ;
492
512
if ( model != null ) {
493
513
val [ key ] = model ;
@@ -498,7 +518,7 @@ VBox([s1, s2])
498
518
}
499
519
}
500
520
}
501
- // log("dereferenceModelLinks", "AFTER (success)", state);
521
+ // log("dereferenceModelLinks", "AFTER (success)", { ... state } );
502
522
return true ;
503
523
} ;
504
524
@@ -602,16 +622,16 @@ class Environment implements WidgetEnvironment {
602
622
}
603
623
604
624
async getSerializedModelState ( model_id ) {
605
- // log("getModelState ", model_id);
625
+ // log("getSerializedModelState ", model_id);
606
626
if ( this . manager . ipywidgets_state . get_state ( ) != "ready" ) {
607
627
await once ( this . manager . ipywidgets_state , "ready" ) ;
608
628
}
609
- let state = this . manager . ipywidgets_state . get_model_state ( model_id ) ;
629
+ let state = this . manager . ipywidgets_state . getSerializedModelState ( model_id ) ;
610
630
if ( ! state ) {
611
- log ( "getModelState " , model_id , "not yet known -- waiting" ) ;
631
+ log ( "getSerializedModelState " , model_id , "not yet known -- waiting" ) ;
612
632
while ( state == null ) {
613
633
await once ( this . manager . ipywidgets_state , "change" ) ;
614
- state = this . manager . ipywidgets_state . get_model_state ( model_id ) ;
634
+ state = this . manager . ipywidgets_state . getSerializedModelState ( model_id ) ;
615
635
}
616
636
}
617
637
if ( state == null ) {
@@ -620,13 +640,13 @@ class Environment implements WidgetEnvironment {
620
640
if ( state . _model_module == "k3d" && state . type != null ) {
621
641
while ( ! state ?. type || ! state ?. id ) {
622
642
log (
623
- "getModelState " ,
643
+ "getSerializedModelState " ,
624
644
model_id ,
625
645
"k3d: waiting for state.type to be defined" ,
626
646
) ;
627
647
628
648
await once ( this . manager . ipywidgets_state , "change" ) ;
629
- state = this . manager . ipywidgets_state . get_model_state ( model_id ) ;
649
+ state = this . manager . ipywidgets_state . getSerializedModelState ( model_id ) ;
630
650
}
631
651
}
632
652
if ( state == null ) {
@@ -649,7 +669,7 @@ class Environment implements WidgetEnvironment {
649
669
}
650
670
}
651
671
652
- log ( "getModelState " , { model_id, state } ) ;
672
+ log ( "getSerializedModelState " , { model_id, state } ) ;
653
673
setTimeout ( ( ) => this . manager . watchModel ( model_id ) , 1 ) ;
654
674
return {
655
675
modelName : state . _model_name ,
@@ -737,3 +757,8 @@ function setInObject(obj: any, path: string[], value: any) {
737
757
// and then set: obj[z] = value
738
758
obj [ path [ path . length - 1 ] ] = value ;
739
759
}
760
+
761
+ const IPY_MODEL = "IPY_MODEL_" ;
762
+ function isModelReference ( value ) : boolean {
763
+ return typeof value == "string" && value . startsWith ( IPY_MODEL ) ;
764
+ }
0 commit comments