@@ -17,7 +17,7 @@ import {
17
17
ModelState ,
18
18
} from "@cocalc/sync/editor/generic/ipywidgets-state" ;
19
19
import { once } from "@cocalc/util/async-utils" ;
20
- import { copy , is_array , is_object , len , uuid } from "@cocalc/util/misc" ;
20
+ import { is_array , is_object , len , uuid } from "@cocalc/util/misc" ;
21
21
import { fromJS } from "immutable" ;
22
22
import { CellOutputMessage } from "@cocalc/frontend/jupyter/output-messages/message" ;
23
23
import React from "react" ;
@@ -116,16 +116,17 @@ export class WidgetManager {
116
116
if ( state == null ) {
117
117
return ;
118
118
}
119
- await this . updateModel ( model_id , state ! ) ;
119
+ await this . updateModel ( model_id , state ! , false ) ;
120
120
} ;
121
121
122
122
private updateModel = async (
123
123
model_id : string ,
124
124
changed : ModelState ,
125
+ merge : boolean ,
125
126
) : Promise < void > => {
126
127
const model : base . DOMWidgetModel | undefined =
127
128
await this . manager . get_model ( model_id ) ;
128
- log ( "updateModel" , { model , changed } ) ;
129
+ log ( "updateModel" , { model_id , merge , changed } ) ;
129
130
if ( model == null ) {
130
131
return ;
131
132
}
@@ -140,17 +141,33 @@ export class WidgetManager {
140
141
) ;
141
142
return ;
142
143
}
143
- const state = await this . deserializeState ( model , changed ) ;
144
- if ( state . hasOwnProperty ( "outputs" ) && state [ "outputs" ] == null ) {
144
+ changed = await this . deserializeState ( model , changed ) ;
145
+ if ( changed . hasOwnProperty ( "outputs" ) && changed [ "outputs" ] == null ) {
145
146
// It can definitely be 'undefined' but set, e.g., the 'out.clear_output()' example at
146
147
// https://ipywidgets.readthedocs.io/en/latest/examples/Output%20Widget.html
147
148
// causes this, which then totally breaks rendering (due to how the
148
149
// upstream widget manager works). This works around that.
149
- state [ "outputs" ] = [ ] ;
150
+ changed [ "outputs" ] = [ ] ;
151
+ }
152
+ if ( merge ) {
153
+ const state = model . get_state ( false ) ;
154
+ const x : ModelState = { } ;
155
+ for ( const k in changed ) {
156
+ if ( state [ k ] != null && is_object ( state [ k ] ) && is_object ( changed [ k ] ) ) {
157
+ x [ k ] = { ...state [ k ] , ...changed [ k ] } ;
158
+ } else {
159
+ x [ k ] = changed [ k ] ;
160
+ }
161
+ }
162
+ changed = x ;
163
+ }
164
+ log ( "updateModel -- doing set_state" , { model_id, merge, changed } ) ;
165
+ try {
166
+ model . set ( changed ) ;
167
+ } catch ( err ) {
168
+ //window.z = { merge, model, model_id, changed };
169
+ console . error ( "saved to z" , err ) ;
150
170
}
151
-
152
- log ( "set_state" , state ) ;
153
- model . set_state ( state ) ;
154
171
} ;
155
172
156
173
// ipywidgets_state_ValueChange is called when a value entry of the ipywidgets_state
@@ -192,7 +209,7 @@ export class WidgetManager {
192
209
}
193
210
this . state_lock . add ( model_id ) ;
194
211
log ( "handleValueChange: got model and now making this change -- " , changed ) ;
195
- await this . updateModel ( model_id , changed ) ;
212
+ await this . updateModel ( model_id , changed , true ) ;
196
213
const model = await this . manager . get_model ( model_id ) ;
197
214
if ( model != null ) {
198
215
await model . state_change ;
@@ -239,20 +256,31 @@ export class WidgetManager {
239
256
A simple example that uses buffers is this image one:
240
257
https://ipywidgets.readthedocs.io/en/latest/examples/Widget%20List.html#image
241
258
*/
242
- const model = await this . manager . get_model ( model_id ) ;
243
259
const { buffer_paths, buffers } =
244
260
await this . ipywidgets_state . get_model_buffers ( model_id ) ;
245
261
log ( "handleBuffersChange: " , { model_id, buffer_paths, buffers } ) ;
246
- const deserialized_state = model . get_state ( true ) ;
262
+ if ( buffer_paths . length == 0 ) {
263
+ return ;
264
+ }
265
+ const state = this . ipywidgets_state . get_model_state ( model_id ) ;
266
+ if ( state == null ) {
267
+ return ;
268
+ }
247
269
const change : { [ key : string ] : any } = { } ;
248
270
for ( let i = 0 ; i < buffer_paths . length ; i ++ ) {
249
271
const key = buffer_paths [ i ] [ 0 ] ;
250
- setInObject ( deserialized_state [ key ] , buffer_paths [ i ] , buffers [ i ] ) ;
251
- change [ key ] = deserialized_state [ key ] ;
272
+ setInObject ( state , buffer_paths [ i ] , buffers [ i ] ) ;
273
+ change [ key ] = state [ key ] ;
252
274
}
253
- log ( "handleBuffersChange: " , model_id , change ) ;
275
+ log ( "handleBuffersChange: " , model_id , { change } ) ;
254
276
if ( len ( change ) > 0 ) {
255
- model . set_state ( change ) ;
277
+ const model = await this . manager . get_model ( model_id ) ;
278
+ try {
279
+ model . set ( change ) ;
280
+ } catch ( err ) {
281
+ // window.y = { model_id, model, change, buffer_paths, buffers };
282
+ console . error ( "saved to y" , err ) ;
283
+ }
256
284
}
257
285
} ;
258
286
@@ -301,18 +329,20 @@ export class WidgetManager {
301
329
// ipywidgets_state, so that it is gets sync'd to the backend
302
330
// and any other clients.
303
331
private handleModelChange = async ( model ) : Promise < void > => {
304
- log ( "handleModelChange" , model ) ;
305
332
const { model_id } = model ;
333
+ let changed = model . changed ;
334
+ log ( "handleModelChange" , model_id , changed ) ;
306
335
await model . state_change ;
307
336
if ( this . state_lock . has ( model_id ) ) {
308
- // log("handleModelChange: ignoring change due to state lock");
337
+ log ( "handleModelChange: ignoring change due to state lock" ) ;
309
338
return ;
310
339
}
311
- const changed : any = copy ( model . serialize ( model . changed ) ) ;
340
+ changed = model . serialize ( changed ) ;
312
341
delete changed . children ; // sometimes they are in there, but shouldn't be sync'ed.
313
342
const { last_changed } = changed ;
314
343
delete changed . last_changed ;
315
344
if ( len ( changed ) == 0 ) {
345
+ log ( "handleModelChange: nothing changed" ) ;
316
346
return ; // nothing
317
347
}
318
348
// increment sequence number.
@@ -543,6 +573,18 @@ class Environment implements WidgetEnvironment {
543
573
this . manager = manager ;
544
574
}
545
575
576
+ async loadClass (
577
+ className : string ,
578
+ moduleName : string ,
579
+ _moduleVersion : string ,
580
+ ) : Promise < any > {
581
+ if ( false && moduleName === "k3d" ) {
582
+ // NOTE: I completely rewrote the entire k3d widget interface...
583
+ console . log ( "using builtin k3d" ) ;
584
+ return await import ( "k3d" ) [ className ] ;
585
+ }
586
+ }
587
+
546
588
async getModelState ( model_id ) {
547
589
// log("getModelState", model_id);
548
590
if ( this . manager . ipywidgets_state . get_state ( ) != "ready" ) {
@@ -556,6 +598,23 @@ class Environment implements WidgetEnvironment {
556
598
state = this . manager . ipywidgets_state . get_model_state ( model_id ) ;
557
599
}
558
600
}
601
+ if ( state == null ) {
602
+ throw Error ( "bug" ) ;
603
+ }
604
+ if ( state . _model_module == "k3d" && state . type != null ) {
605
+ while ( ! state ?. type || ! state ?. id ) {
606
+ log (
607
+ "getModelState" ,
608
+ model_id ,
609
+ "k3d: waiting for state.type to be defined" ,
610
+ ) ;
611
+ await once ( this . manager . ipywidgets_state , "change" ) ;
612
+ state = this . manager . ipywidgets_state . get_model_state ( model_id ) ;
613
+ }
614
+ }
615
+ if ( state == null ) {
616
+ throw Error ( "bug" ) ;
617
+ }
559
618
if ( state . hasOwnProperty ( "outputs" ) && state [ "outputs" ] == null ) {
560
619
// It can definitely be 'undefined' but set, e.g., the 'out.clear_output()' example at
561
620
// https://ipywidgets.readthedocs.io/en/latest/examples/Output%20Widget.html
@@ -566,12 +625,15 @@ class Environment implements WidgetEnvironment {
566
625
const { buffer_paths, buffers } =
567
626
await this . manager . ipywidgets_state . get_model_buffers ( model_id ) ;
568
627
569
- for ( let i = 0 ; i < buffer_paths . length ; i ++ ) {
570
- const buffer = buffers [ i ] ;
571
- setInObject ( state , buffer_paths [ i ] , buffer ) ;
628
+ if ( buffers . length > 0 ) {
629
+ for ( let i = 0 ; i < buffer_paths . length ; i ++ ) {
630
+ const buffer = buffers [ i ] ;
631
+ setInObject ( state , buffer_paths [ i ] , buffer ) ;
632
+ }
572
633
}
573
-
574
634
setTimeout ( ( ) => this . manager . watchModel ( model_id ) , 1 ) ;
635
+
636
+ log ( "getModelState" , { model_id, state } ) ;
575
637
return {
576
638
modelName : state . _model_name ,
577
639
modelModule : state . _model_module ,
0 commit comments