diff --git a/.changeset/clean-feet-mate.md b/.changeset/clean-feet-mate.md new file mode 100644 index 00000000..8481e8b4 --- /dev/null +++ b/.changeset/clean-feet-mate.md @@ -0,0 +1,33 @@ +--- +"@difizen/libro-cofine-editor-contribution": patch +"@difizen/libro-cofine-editor-core": patch +"@difizen/libro-search-code-cell": patch +"@difizen/libro-cofine-textmate": patch +"@difizen/libro-language-client": patch +"@difizen/libro-cofine-editor": patch +"@difizen/libro-markdown-cell": patch +"@difizen/libro-shared-model": patch +"@difizen/libro-code-editor": patch +"@difizen/libro-prompt-cell": patch +"@difizen/libro-virtualized": patch +"@difizen/libro-codemirror": patch +"@difizen/libro-rendermime": patch +"@difizen/libro-code-cell": patch +"@difizen/libro-markdown": patch +"@difizen/libro-raw-cell": patch +"@difizen/libro-terminal": patch +"@difizen/libro-jupyter": patch +"@difizen/libro-common": patch +"@difizen/libro-kernel": patch +"@difizen/libro-output": patch +"@difizen/libro-search": patch +"@difizen/libro-widget": patch +"@difizen/libro-core": patch +"@difizen/libro-l10n": patch +"@difizen/libro-lab": patch +"@difizen/libro-lsp": patch +"@difizen/libro-toc": patch +"@difizen/libro-docs": patch +--- + +Enhance widgets underlying capabilities diff --git a/.changeset/gold-lions-deny.md b/.changeset/gold-lions-deny.md new file mode 100644 index 00000000..10b113f2 --- /dev/null +++ b/.changeset/gold-lions-deny.md @@ -0,0 +1,33 @@ +--- +"@difizen/libro-cofine-editor-contribution": patch +"@difizen/libro-cofine-editor-core": patch +"@difizen/libro-search-code-cell": patch +"@difizen/libro-cofine-textmate": patch +"@difizen/libro-language-client": patch +"@difizen/libro-cofine-editor": patch +"@difizen/libro-markdown-cell": patch +"@difizen/libro-shared-model": patch +"@difizen/libro-code-editor": patch +"@difizen/libro-prompt-cell": patch +"@difizen/libro-virtualized": patch +"@difizen/libro-codemirror": patch +"@difizen/libro-rendermime": patch +"@difizen/libro-code-cell": patch +"@difizen/libro-markdown": patch +"@difizen/libro-raw-cell": patch +"@difizen/libro-terminal": patch +"@difizen/libro-jupyter": patch +"@difizen/libro-common": patch +"@difizen/libro-kernel": patch +"@difizen/libro-output": patch +"@difizen/libro-search": patch +"@difizen/libro-widget": patch +"@difizen/libro-core": patch +"@difizen/libro-l10n": patch +"@difizen/libro-lab": patch +"@difizen/libro-lsp": patch +"@difizen/libro-toc": patch +"@difizen/libro-docs": patch +--- + +Support widget interactive! diff --git a/packages/libro-core/src/cell/libro-cell-view.tsx b/packages/libro-core/src/cell/libro-cell-view.tsx index e9120f11..e6c6bc27 100644 --- a/packages/libro-core/src/cell/libro-cell-view.tsx +++ b/packages/libro-core/src/cell/libro-cell-view.tsx @@ -1,4 +1,5 @@ import type { ViewComponent } from '@difizen/mana-app'; +import { Deferred } from '@difizen/mana-app'; import { useInject, watch } from '@difizen/mana-app'; import { BaseView, view, ViewInstance, ViewOption } from '@difizen/mana-app'; import { inject } from '@difizen/mana-app'; @@ -36,7 +37,22 @@ export class LibroCellView extends BaseView implements CellView { model: CellModel; protected cellService: CellService; override view: ViewComponent = LibroCellComponent; - parent: NotebookView; + + protected _parent: NotebookView; + + get parent() { + return this._parent; + } + set parent(value: NotebookView) { + this._parent = value; + this.parentDefer.resolve(this.parent); + } + + protected parentDefer = new Deferred(); + + get parentReady() { + return this.parentDefer.promise; + } @prop() override className?: string | undefined = 'libro-cell-view-container'; diff --git a/packages/libro-core/src/index.tsx b/packages/libro-core/src/index.tsx index 24730413..7d14d780 100644 --- a/packages/libro-core/src/index.tsx +++ b/packages/libro-core/src/index.tsx @@ -22,3 +22,4 @@ export * from './settings/index.js'; export * from './virtualized-manager.js'; export * from './virtualized-manager-helper.js'; export * from './libro-workspace-service.js'; +export * from './libro-context-key.js'; diff --git a/packages/libro-core/src/output/output-area.tsx b/packages/libro-core/src/output/output-area.tsx index b4938103..e37a520e 100644 --- a/packages/libro-core/src/output/output-area.tsx +++ b/packages/libro-core/src/output/output-area.tsx @@ -35,7 +35,15 @@ const LibroOutputAreaRender = forwardRef( useEffect(() => { outputArea.onUpdateEmitter.fire(); }, [outputArea.onUpdateEmitter, outputArea.outputs]); - + const childrenCannotClear = []; + const children = []; + for (const output of outputArea.outputs) { + if (output.allowClear === false) { + childrenCannotClear.push(output); + } else { + children.push(output); + } + } return (
( //设置最小高度,用于优化长文本输出再次执行时的页面的滚动控制 // style={{ minHeight: `${executing ? outputArea.lastOutputContainerHeight + 'px' : 'unset'}` }} > - {outputArea.outputs.map((output) => { + {childrenCannotClear.map((output) => { + return ; + })} + {children.map((output) => { return ; })}
@@ -103,10 +114,10 @@ export class LibroOutputArea extends BaseView implements BaseOutputArea { }); } add = async (output: IOutput): Promise => { - // if (this.clearNext) { - // this.clear(); - // this.clearNext = false; - // } + if (this.clearNext) { + this.clear(); + this.clearNext = false; + } // Consolidate outputs if they are stream outputs of the same kind. if ( isStream(output) && @@ -141,13 +152,28 @@ export class LibroOutputArea extends BaseView implements BaseOutputArea { } else { this.lastStream = ''; } - return this.outputs.push(await outputModel); + const model = await outputModel; + model.onDisposed(() => { + this.remove(model); + }); + return this.outputs.push(model); }; + + protected remove(model: BaseOutputView) { + let outputs = [...this.outputs]; + outputs = outputs.filter((item) => item !== model); + this.outputs = outputs; + } + set = async (index: number, output: IOutput) => { const outputModel = this.doCreateOutput(output); const current = this.outputs[index]; current.dispose(); - this.outputs[index] = await outputModel; + const model = await outputModel; + model.onDisposed(() => { + this.remove(model); + }); + this.outputs[index] = model; }; clear(wait?: boolean | undefined) { this.lastStream = ''; @@ -158,7 +184,6 @@ export class LibroOutputArea extends BaseView implements BaseOutputArea { this.outputs.forEach((output) => { output.dispose(); }); - this.outputs = []; } fromJSON = async (values: IOutput[]) => { if (!values) { diff --git a/packages/libro-core/src/output/output-model.tsx b/packages/libro-core/src/output/output-model.tsx index 3970be45..431cbdbc 100644 --- a/packages/libro-core/src/output/output-model.tsx +++ b/packages/libro-core/src/output/output-model.tsx @@ -26,6 +26,10 @@ export class LibroOutputView extends BaseView implements BaseOutputView { @prop() raw: IOutput; + + @prop() + allowClear = true; + @prop() data: JSONObject; @prop() @@ -45,7 +49,7 @@ export class LibroOutputView extends BaseView implements BaseOutputView { render: FC<{ output: BaseOutputView }> = LibroOutputModelRender; override dispose() { - // + super.dispose(); } toJSON() { return this.raw; diff --git a/packages/libro-core/src/output/output-protocol.ts b/packages/libro-core/src/output/output-protocol.ts index 14ee35a4..13ba1f76 100644 --- a/packages/libro-core/src/output/output-protocol.ts +++ b/packages/libro-core/src/output/output-protocol.ts @@ -126,6 +126,11 @@ export interface BaseOutputView extends View { * Depending on the implementation of the mime model, */ setData(options: ISetDataOptions): void; + + /** + * undefined is considered allowed + */ + allowClear?: boolean; } /** diff --git a/packages/libro-jupyter/package.json b/packages/libro-jupyter/package.json index d4cdbb50..437613f4 100644 --- a/packages/libro-jupyter/package.json +++ b/packages/libro-jupyter/package.json @@ -60,7 +60,6 @@ "@difizen/libro-markdown-cell": "^0.1.33", "@difizen/libro-raw-cell": "^0.1.33", "@difizen/libro-language-client": "^0.1.33", - "@difizen/libro-widget": "^0.1.33", "@difizen/mana-app": "latest", "@difizen/mana-l10n": "latest", "@ant-design/colors": "^7.0.0", diff --git a/packages/libro-jupyter/src/cell/jupyter-code-cell-view.tsx b/packages/libro-jupyter/src/cell/jupyter-code-cell-view.tsx index eccfdd2a..6f6a48fa 100644 --- a/packages/libro-jupyter/src/cell/jupyter-code-cell-view.tsx +++ b/packages/libro-jupyter/src/cell/jupyter-code-cell-view.tsx @@ -39,7 +39,16 @@ const JupyterCodeCellComponent = forwardRef( @transient() @view('jupyter-code-cell-view') export class JupyterCodeCellView extends LibroCodeCellView { - declare parent: LibroJupyterView; + protected declare _parent: LibroJupyterView; + + override get parent() { + return this._parent; + } + override set parent(value: LibroJupyterView) { + this._parent = value; + this.parentDefer.resolve(this.parent); + } + override view = JupyterCodeCellComponent; declare model: JupyterCodeCellModel; diff --git a/packages/libro-jupyter/src/index.ts b/packages/libro-jupyter/src/index.ts index 97b5a8b6..9c6b1759 100644 --- a/packages/libro-jupyter/src/index.ts +++ b/packages/libro-jupyter/src/index.ts @@ -32,3 +32,4 @@ export * from './toolbar/index.js'; export * from './file/index.js'; export * from './libro-jupyter-view.js'; export * from './config/index.js'; +export * from './widget/index.js'; diff --git a/packages/libro-jupyter/src/module.ts b/packages/libro-jupyter/src/module.ts index e412897f..c8e9ebdf 100644 --- a/packages/libro-jupyter/src/module.ts +++ b/packages/libro-jupyter/src/module.ts @@ -25,7 +25,6 @@ import { import { RawCellModule } from '@difizen/libro-raw-cell'; import { LibroSearchModule } from '@difizen/libro-search'; import { SearchCodeCellModule } from '@difizen/libro-search-code-cell'; -import { WidgetModule } from '@difizen/libro-widget'; import { ManaModule } from '@difizen/mana-app'; import { LibroBetweenCellModule } from './add-between-cell/index.js'; @@ -57,7 +56,7 @@ import { LibroJupyterToolbarContribution, SaveFileErrorContribution, } from './toolbar/index.js'; -import { LibroWidgetMimeContribution } from './widget/index.js'; +import { WidgetModule } from './widget/index.js'; export const LibroJupyterModule = ManaModule.create() .register( @@ -76,7 +75,6 @@ export const LibroJupyterModule = ManaModule.create() LibroJupyterSettingContribution, JupyterServerLaunchManager, LibroJupyterView, - LibroWidgetMimeContribution, { token: CellExecutionTimeProvider, useValue: CellExecutionTip, diff --git a/packages/libro-jupyter/src/output/libro-jupyter-outputarea.tsx b/packages/libro-jupyter/src/output/libro-jupyter-outputarea.tsx index ef043790..8cb99a94 100644 --- a/packages/libro-jupyter/src/output/libro-jupyter-outputarea.tsx +++ b/packages/libro-jupyter/src/output/libro-jupyter-outputarea.tsx @@ -6,8 +6,10 @@ import type { } from '@difizen/libro-core'; import { LibroOutputArea } from '@difizen/libro-core'; import { + isClearOutputMsg, isDisplayDataMsg, isErrorMsg, + isExecuteInputMsg, isExecuteReplyMsg, isExecuteResultMsg, isStreamMsg, @@ -31,58 +33,61 @@ export class LibroJupyterOutputArea extends LibroOutputArea { cellModel.msgChangeEmitter.event((msg) => { const transientMsg = (msg.content.transient || {}) as nbformat.JSONObject; const displayId = transientMsg['display_id'] as string; - if (msg.header.msg_type !== 'status') { - if (msg.header.msg_type === 'execute_input') { - cellModel.executeCount = msg.content.execution_count; - } - if ( - isDisplayDataMsg(msg) || - isStreamMsg(msg) || - isErrorMsg(msg) || - isExecuteResultMsg(msg) - ) { - const output: nbformat.IOutput = { - ...msg.content, - output_type: msg.header.msg_type, - }; - this.add(output); - } - if (isUpdateDisplayDataMsg(msg)) { - const output = { ...msg.content, output_type: 'display_data' }; - const targets = this.displayIdMap.get(displayId); - if (targets) { - for (const index of targets) { - this.set(index, output); - } + if (isExecuteInputMsg(msg)) { + cellModel.executeCount = msg.content.execution_count; + } + if ( + isDisplayDataMsg(msg) || + isStreamMsg(msg) || + isErrorMsg(msg) || + isExecuteResultMsg(msg) + ) { + const output: nbformat.IOutput = { + ...msg.content, + output_type: msg.header.msg_type, + }; + this.add(output); + } + if (isUpdateDisplayDataMsg(msg)) { + const output = { ...msg.content, output_type: 'display_data' }; + const targets = this.displayIdMap.get(displayId); + if (targets) { + for (const index of targets) { + this.set(index, output); } } - if (displayId && isDisplayDataMsg(msg)) { - const targets = this.displayIdMap.get(displayId) || []; - targets.push(this.outputs.length); - this.displayIdMap.set(displayId, targets); + } + if (displayId && isDisplayDataMsg(msg)) { + const targets = this.displayIdMap.get(displayId) || []; + targets.push(this.outputs.length); + this.displayIdMap.set(displayId, targets); + } + //Handle an execute reply message. + if (isExecuteReplyMsg(msg)) { + const content = msg.content; + if (content.status !== 'ok') { + return; } - //Handle an execute reply message. - if (isExecuteReplyMsg(msg)) { - const content = msg.content; - if (content.status !== 'ok') { - return; - } - const payload = content && content.payload; - if (!payload || !payload.length) { - return; - } - const pages = payload.filter((i: any) => i.source === 'page'); - if (!pages.length) { - return; - } - const page = JSON.parse(JSON.stringify(pages[0])); - const output: nbformat.IOutput = { - output_type: 'display_data', - data: page.data as nbformat.IMimeBundle, - metadata: {}, - }; - this.add(output); + const payload = content && content.payload; + if (!payload || !payload.length) { + return; + } + const pages = payload.filter((i: any) => i.source === 'page'); + if (!pages.length) { + return; } + const page = JSON.parse(JSON.stringify(pages[0])); + const output: nbformat.IOutput = { + output_type: 'display_data', + data: page.data as nbformat.IMimeBundle, + metadata: {}, + }; + this.add(output); + } + + if (isClearOutputMsg(msg)) { + const wait = msg.content.wait; + this.clear(wait); } }); } @@ -94,6 +99,8 @@ export class LibroJupyterOutputArea extends LibroOutputArea { override clear(wait?: boolean | undefined): void { super.clear(wait); - this.displayIdMap.clear(); + if (!wait) { + this.displayIdMap.clear(); + } } } diff --git a/packages/libro-jupyter/src/widget/box/contribution.ts b/packages/libro-jupyter/src/widget/box/contribution.ts new file mode 100644 index 00000000..ba6ff4ec --- /dev/null +++ b/packages/libro-jupyter/src/widget/box/contribution.ts @@ -0,0 +1,29 @@ +import { ViewManager, inject, singleton } from '@difizen/mana-app'; + +import type { IWidgetViewProps } from '../protocol.js'; +import { WidgetViewContribution } from '../protocol.js'; + +import { VBoxWidget } from './view.js'; + +@singleton({ contrib: WidgetViewContribution }) +export class VBoxWidgetContribution implements WidgetViewContribution { + @inject(ViewManager) viewManager: ViewManager; + canHandle = (attributes: any) => { + if (attributes._model_name === 'VBoxModel') { + return 100; + } + if (attributes.__view_name === 'VBoxView') { + return 100; + } + if (attributes._model_name === 'HBoxModel') { + return 100; + } + if (attributes.__view_name === 'HBoxView') { + return 100; + } + return 1; + }; + factory(props: IWidgetViewProps) { + return this.viewManager.getOrCreateView(VBoxWidget, props); + } +} diff --git a/packages/libro-jupyter/src/widget/box/index.less b/packages/libro-jupyter/src/widget/box/index.less new file mode 100644 index 00000000..07c65c69 --- /dev/null +++ b/packages/libro-jupyter/src/widget/box/index.less @@ -0,0 +1,3 @@ +.libro-widget-hbox { + display: flex; +} diff --git a/packages/libro-jupyter/src/widget/box/index.ts b/packages/libro-jupyter/src/widget/box/index.ts new file mode 100644 index 00000000..7be67da4 --- /dev/null +++ b/packages/libro-jupyter/src/widget/box/index.ts @@ -0,0 +1,2 @@ +export * from './contribution.js'; +export * from './view.js'; diff --git a/packages/libro-jupyter/src/widget/box/view.tsx b/packages/libro-jupyter/src/widget/box/view.tsx new file mode 100644 index 00000000..254edf6c --- /dev/null +++ b/packages/libro-jupyter/src/widget/box/view.tsx @@ -0,0 +1,112 @@ +import type { CellView } from '@difizen/libro-core'; +import { LibroContextKey } from '@difizen/libro-core'; +import { + view, + transient, + useInject, + ViewInstance, + prop, + inject, + ViewOption, + ViewRender, + getOrigin, +} from '@difizen/mana-app'; +import { forwardRef } from 'react'; + +import type { IWidgets, IWidgetViewProps, WidgetState } from '../protocol.js'; +import { defaultWidgetState } from '../protocol.js'; +import { WidgetView } from '../widget-view.js'; + +import './index.less'; + +const WidgetRender = (props: { + cell?: CellView; + widgets: IWidgets | undefined; + modelId: string; +}) => { + const { widgets, modelId, cell } = props; + if (!widgets) { + return null; + } + let widgetView; + try { + widgetView = widgets.getModel(modelId); + } catch (ex) { + // + } + if (!widgetView) { + return null; + } + if (cell) { + widgetView.setCell(getOrigin(cell)); + } + if (widgetView.isCommClosed) { + return null; + } + return ( +
+
+ +
+
+ ); +}; + +export const LibroWidgetBoxComponent = forwardRef( + function LibroWidgetBoxComponent(props, ref) { + const widget = useInject(ViewInstance); + + return ( +
+ {widget.state.children.map((modelId) => ( + + ))} +
+ ); + }, +); + +interface BoxState extends WidgetState { + children: string[]; + box_style?: string; +} + +@transient() +@view('libro-widget-box-view') +export class VBoxWidget extends WidgetView { + override view = LibroWidgetBoxComponent; + + @prop() + override state: BoxState = { + ...defaultWidgetState, + children: [], + }; + + constructor( + @inject(ViewOption) props: IWidgetViewProps, + @inject(LibroContextKey) libroContextKey: LibroContextKey, + ) { + super(props, libroContextKey); + this.initialize(props); + } + + protected initialize(props: IWidgetViewProps): void { + const attributes = props.attributes; + this.setState(attributes); + } + + getCls = () => { + if (this.model_name === 'HBoxModel') { + return 'libro-widget-hbox'; + } + if (this.model_name === 'VBoxModel') { + return 'libro-widget-vbox'; + } + return ''; + }; +} diff --git a/packages/libro-widget/src/base/comm.ts b/packages/libro-jupyter/src/widget/comm.ts similarity index 94% rename from packages/libro-widget/src/base/comm.ts rename to packages/libro-jupyter/src/widget/comm.ts index 8c87a67d..7ac5b278 100644 --- a/packages/libro-widget/src/base/comm.ts +++ b/packages/libro-jupyter/src/widget/comm.ts @@ -2,8 +2,8 @@ import type { JSONObject } from '@difizen/libro-common'; import type { IComm, IKernelConnection, IShellFuture } from '@difizen/libro-kernel'; import { inject, transient } from '@difizen/mana-app'; -import type { ICallbacks, IClassicComm } from './protocal.js'; -import { WidgetCommOption } from './protocal.js'; +import type { ICallbacks, IClassicComm } from './protocol.js'; +import { WidgetCommOption } from './protocol.js'; /** * Public constructor @@ -90,7 +90,7 @@ export class Comm implements IClassicComm { * Register a message handler * @param callback, which is given a message */ - on_msg(callback: (x: any) => void): void { + onMsg(callback: (x: any) => void): void { this.jsServicesComm.onMsg = callback.bind(this); } @@ -98,7 +98,7 @@ export class Comm implements IClassicComm { * Register a handler for when the comm is closed by the backend * @param callback, which is given a message */ - on_close(callback: (x: any) => void): void { + onClose(callback: (x: any) => void): void { this.jsServicesComm.onClose = callback.bind(this); } diff --git a/packages/libro-jupyter/src/widget/index.ts b/packages/libro-jupyter/src/widget/index.ts index edb5f0ab..4a7a9687 100644 --- a/packages/libro-jupyter/src/widget/index.ts +++ b/packages/libro-jupyter/src/widget/index.ts @@ -1,2 +1,9 @@ export * from './widget-render.js'; export * from './widget-rendermime-contribution.js'; +export * from './libro-widgets.js'; +export * from './widget-view.js'; +export * from './widget-manager.js'; +export * from './comm.js'; +export * from './widget-view-contribution.js'; +export * from './module.js'; +export * from './protocol.js'; diff --git a/packages/libro-widget/src/widgets/instances-progress-widget-view-contribution.ts b/packages/libro-jupyter/src/widget/instance-progress/contribution.ts similarity index 72% rename from packages/libro-widget/src/widgets/instances-progress-widget-view-contribution.ts rename to packages/libro-jupyter/src/widget/instance-progress/contribution.ts index a10069b9..4ad06c6b 100644 --- a/packages/libro-widget/src/widgets/instances-progress-widget-view-contribution.ts +++ b/packages/libro-jupyter/src/widget/instance-progress/contribution.ts @@ -1,9 +1,9 @@ import { ViewManager, inject, singleton } from '@difizen/mana-app'; -import type { IWidgetViewProps } from '../base/protocal.js'; -import { WidgetViewContribution } from '../base/protocal.js'; +import type { IWidgetViewProps } from '../protocol.js'; +import { WidgetViewContribution } from '../protocol.js'; -import { InstancesProgressWidget } from './instances-progress-widget-view.js'; +import { InstancesProgressWidget } from './view.js'; @singleton({ contrib: WidgetViewContribution }) export class InstancesProgressWidgetViewContribution implements WidgetViewContribution { diff --git a/packages/libro-jupyter/src/widget/instance-progress/index.ts b/packages/libro-jupyter/src/widget/instance-progress/index.ts new file mode 100644 index 00000000..7be67da4 --- /dev/null +++ b/packages/libro-jupyter/src/widget/instance-progress/index.ts @@ -0,0 +1,2 @@ +export * from './contribution.js'; +export * from './view.js'; diff --git a/packages/libro-widget/src/widgets/instances-progress-widget-view.tsx b/packages/libro-jupyter/src/widget/instance-progress/view.tsx similarity index 95% rename from packages/libro-widget/src/widgets/instances-progress-widget-view.tsx rename to packages/libro-jupyter/src/widget/instance-progress/view.tsx index ce35a07f..3ac3a835 100644 --- a/packages/libro-widget/src/widgets/instances-progress-widget-view.tsx +++ b/packages/libro-jupyter/src/widget/instance-progress/view.tsx @@ -11,14 +11,9 @@ import { } from '@difizen/mana-app'; import { forwardRef } from 'react'; -import type { - InstanceRecord, - InstancesRecords, - ProgressItem, -} from '../base/protocal.js'; -import type { IWidgetViewProps } from '../base/protocal.js'; -import { WidgetView } from '../base/widget-view.js'; -import './index.less'; +import type { IWidgetViewProps } from '../protocol.js'; +import type { InstanceRecord, InstancesRecords, ProgressItem } from '../protocol.js'; +import { WidgetView } from '../widget-view.js'; export interface ProgressOverviewProps { progressMap: Record; diff --git a/packages/libro-widget/src/base/libro-widgets.ts b/packages/libro-jupyter/src/widget/libro-widgets.ts similarity index 90% rename from packages/libro-widget/src/base/libro-widgets.ts rename to packages/libro-jupyter/src/widget/libro-widgets.ts index ebd133fa..15a08808 100644 --- a/packages/libro-widget/src/base/libro-widgets.ts +++ b/packages/libro-jupyter/src/widget/libro-widgets.ts @@ -11,12 +11,12 @@ import type { IWidgetViewOptions, IClassicComm, WidgetCommOption, -} from './protocal.js'; +} from './protocol.js'; import { LibroWidgetCommFactory, WidgetsOption, WidgetViewContribution, -} from './protocal.js'; +} from './protocol.js'; import { put_buffers, reject } from './utils.js'; import { PROTOCOL_VERSION } from './version.js'; import type { WidgetView } from './widget-view.js'; @@ -140,9 +140,10 @@ export class LibroWidgets implements IWidgets { registerWidgetView(model_id: string, model: Promise): void { model - .then((WidgetView) => { - this.models.set(model_id, WidgetView); - this.widgetEmitter.fire({ WidgetViewName: WidgetView.model_name }); + .then((model) => { + this.models.set(model_id, model); + this.models.set(model.toModelKey(), model); + this.widgetEmitter.fire({ WidgetViewName: model.model_name }); return; }) .catch(() => { @@ -174,7 +175,11 @@ export class LibroWidgets implements IWidgets { } options.model_id = model_id; const provider = this.findProvider(attributes); - const WidgetView = provider.factory({ attributes: attributes, options: options }); + const WidgetView = provider.factory({ + attributes: attributes, + options: options, + widgetsId: this.id, + }); this.registerWidgetView(model_id, WidgetView); return WidgetView; } @@ -204,4 +209,15 @@ export class LibroWidgets implements IWidgets { */ id: string; readonly commTargetName = 'jupyter.widget'; + + /** + * Serialize the model. See the deserialization function at the top of this file + * and the kernel-side serializer/deserializer. + */ + toJSON(): string { + return JSON.stringify({ + kc_id: this.kernelConnection.id, + id: this.id, + }); + } } diff --git a/packages/libro-widget/src/widget-module.ts b/packages/libro-jupyter/src/widget/module.ts similarity index 57% rename from packages/libro-widget/src/widget-module.ts rename to packages/libro-jupyter/src/widget/module.ts index aab45462..3aeb6c4d 100644 --- a/packages/libro-widget/src/widget-module.ts +++ b/packages/libro-jupyter/src/widget/module.ts @@ -1,28 +1,27 @@ import { LibroKernelManageModule } from '@difizen/libro-kernel'; import { ManaModule } from '@difizen/mana-app'; -import { Comm } from './base/comm.js'; +import { VBoxWidget, VBoxWidgetContribution } from './box/index.js'; +import { Comm } from './comm.js'; +import { + InstancesProgressWidget, + InstancesProgressWidgetViewContribution, +} from './instance-progress/index.js'; +import { LibroWidgets } from './libro-widgets.js'; +import { ProgressWidget, ProgressWidgetViewContribution } from './progress/index.js'; import { - WidgetsOption, LibroWidgetCommFactory, - WidgetCommOption, - LibroWidgets, LibroWidgetsFactory, - WidgetView, + WidgetCommOption, + WidgetsOption, WidgetViewContribution, -} from './base/index.js'; -import { LibroWidgetManager } from './base/widget-manager.js'; -import { DefaultWidgetViewContribution } from './base/widget-view-contribution.js'; -import { HBoxModelContribution } from './widgets/hbox-widget-view-contribution.js'; -import { HBoxWidget } from './widgets/hbox-widget-view.js'; -import { ProgressWidget } from './widgets/index.js'; -import { InstancesProgressWidgetViewContribution } from './widgets/instances-progress-widget-view-contribution.js'; -import { InstancesProgressWidget } from './widgets/instances-progress-widget-view.js'; -import { ProgressWidgetViewContribution } from './widgets/progress-widget-view-contribution.js'; -import { TextModelContribution } from './widgets/text-widget-view-contribution.js'; -import { LibroTextWidget } from './widgets/text-widget-view.js'; +} from './protocol.js'; +import { LibroWidgetManager } from './widget-manager.js'; +import { LibroWidgetMimeContribution } from './widget-rendermime-contribution.js'; +import { DefaultWidgetViewContribution } from './widget-view-contribution.js'; +import { WidgetView } from './widget-view.js'; -export const WidgetModule = ManaModule.create() +export const BaseWidgetModule = ManaModule.create() .contribution(WidgetViewContribution) .register( Comm, @@ -55,14 +54,20 @@ export const WidgetModule = ManaModule.create() }, LibroWidgetManager, WidgetView, - ProgressWidget, DefaultWidgetViewContribution, + LibroWidgetMimeContribution, + ) + .dependOn(LibroKernelManageModule); + +export const WidgetModule = ManaModule.create() + .register( + VBoxWidget, + VBoxWidgetContribution, + + ProgressWidget, ProgressWidgetViewContribution, - InstancesProgressWidgetViewContribution, + InstancesProgressWidget, - HBoxModelContribution, - HBoxWidget, - LibroTextWidget, - TextModelContribution, + InstancesProgressWidgetViewContribution, ) - .dependOn(LibroKernelManageModule); + .dependOn(BaseWidgetModule); diff --git a/packages/libro-widget/src/widgets/progress-widget-view-contribution.ts b/packages/libro-jupyter/src/widget/progress/contribution.ts similarity index 77% rename from packages/libro-widget/src/widgets/progress-widget-view-contribution.ts rename to packages/libro-jupyter/src/widget/progress/contribution.ts index b51e7446..0bc76d96 100644 --- a/packages/libro-widget/src/widgets/progress-widget-view-contribution.ts +++ b/packages/libro-jupyter/src/widget/progress/contribution.ts @@ -1,9 +1,9 @@ import { ViewManager, inject, singleton } from '@difizen/mana-app'; -import type { IWidgetViewProps } from '../base/protocal.js'; -import { WidgetViewContribution } from '../base/protocal.js'; +import type { IWidgetViewProps } from '../protocol.js'; +import { WidgetViewContribution } from '../protocol.js'; -import { ProgressWidget } from './progress-widget-view.js'; +import { ProgressWidget } from './view.js'; @singleton({ contrib: WidgetViewContribution }) export class ProgressWidgetViewContribution implements WidgetViewContribution { diff --git a/packages/libro-jupyter/src/widget/progress/index.ts b/packages/libro-jupyter/src/widget/progress/index.ts new file mode 100644 index 00000000..7be67da4 --- /dev/null +++ b/packages/libro-jupyter/src/widget/progress/index.ts @@ -0,0 +1,2 @@ +export * from './contribution.js'; +export * from './view.js'; diff --git a/packages/libro-widget/src/components/progressBar.tsx b/packages/libro-jupyter/src/widget/progress/progressBar.tsx similarity index 100% rename from packages/libro-widget/src/components/progressBar.tsx rename to packages/libro-jupyter/src/widget/progress/progressBar.tsx diff --git a/packages/libro-jupyter/src/widget/progress/view.tsx b/packages/libro-jupyter/src/widget/progress/view.tsx new file mode 100644 index 00000000..df008273 --- /dev/null +++ b/packages/libro-jupyter/src/widget/progress/view.tsx @@ -0,0 +1,70 @@ +import type { JSONObject } from '@difizen/libro-common'; +import { LibroContextKey } from '@difizen/libro-core'; +import { + view, + ViewOption, + transient, + useInject, + ViewInstance, + inject, + prop, +} from '@difizen/mana-app'; +import { forwardRef } from 'react'; + +import type { IWidgetViewProps, WidgetState } from '../protocol.js'; +import { defaultWidgetState } from '../protocol.js'; +import { WidgetView } from '../widget-view.js'; + +import { ProgressBar } from './progressBar.js'; + +export const LibroProgressWidgetComponent = forwardRef( + function LibroProgressWidgetComponent() { + const widgetView = useInject(ViewInstance); + const percent = + widgetView.state.max && widgetView.state.min + ? widgetView.state.value / ((widgetView.state.max - widgetView.state.min) / 100) + : 0; + if (widgetView.isCommClosed) { + return null; + } + return ( +
+
+ {widgetView.state.description} +
+ +
+ ); + }, +); + +interface ProgressState extends WidgetState { + max?: number; + min?: number; + bar_style?: string; + value: number; +} +@transient() +@view('libro-widget-progress-view') +export class ProgressWidget extends WidgetView { + override view = LibroProgressWidgetComponent; + + @prop() + override state: JSONObject & ProgressState = { + ...defaultWidgetState, + max: 1, + min: 0, + value: 0, + }; + constructor( + @inject(ViewOption) props: IWidgetViewProps, + @inject(LibroContextKey) libroContextKey: LibroContextKey, + ) { + super(props, libroContextKey); + + const attributes = props.attributes; + this.state.max = attributes.max; + this.state.min = attributes.min; + this.setState(attributes); + } +} diff --git a/packages/libro-widget/src/base/protocal.ts b/packages/libro-jupyter/src/widget/protocol.ts similarity index 84% rename from packages/libro-widget/src/base/protocal.ts rename to packages/libro-jupyter/src/widget/protocol.ts index 0035d52f..64621a0c 100644 --- a/packages/libro-widget/src/base/protocal.ts +++ b/packages/libro-jupyter/src/widget/protocol.ts @@ -12,6 +12,7 @@ export interface IWidgetViewOptions { export interface IWidgetViewProps { attributes: any; options: IWidgetViewOptions; + widgetsId: string; } /** @@ -68,9 +69,8 @@ export interface WidgetsOption { } export interface IWidgetView { - initialize: (props: IWidgetViewProps) => void; toJSON: () => string; - set_state: (state: Dict) => void; + setState: (state: Dict) => void; handleCommMsg: (msg: KernelMessage.ICommMsgMsg) => Promise; model_id: string; name: string; @@ -208,11 +208,48 @@ export interface IClassicComm { * Register a message handler * @param callback, which is given a message */ - on_msg(callback: (x: any) => void): void; + onMsg(callback: (x: any) => void): void; /** * Register a handler for when the comm is closed by the backend * @param callback, which is given a message */ - on_close(callback: (x: any) => void): void; + onClose(callback: (x: any) => void): void; +} + +export interface WidgetState { + msg_id?: string; + behavior?: string; + continuous_update: boolean; + description: string; + description_allow_html: boolean; + disabled: boolean; + layout?: string; + readout: boolean; + readout_format: string; + style?: string; + [key: string]: any; +} + +export const defaultWidgetState: WidgetState = { + continuous_update: false, + description_allow_html: false, + description: '', + disabled: false, + readout: true, + readout_format: 'd', +}; + +export interface FormattableState { + readout: boolean; + readout_format: string; +} + +export const defaultFormattableState: FormattableState = { + readout: true, + readout_format: 'd', +}; + +export interface OrientableState { + orientation: 'horizontal' | 'vertical'; } diff --git a/packages/libro-widget/src/base/utils.ts b/packages/libro-jupyter/src/widget/utils.ts similarity index 97% rename from packages/libro-widget/src/base/utils.ts rename to packages/libro-jupyter/src/widget/utils.ts index 2157f199..740a659f 100644 --- a/packages/libro-widget/src/base/utils.ts +++ b/packages/libro-jupyter/src/widget/utils.ts @@ -1,4 +1,4 @@ -import type { BufferJSON, Dict } from './protocal.js'; +import type { BufferJSON, Dict } from './protocol.js'; /** * Takes an object 'state' and fills in buffer[i] at 'path' buffer_paths[i] diff --git a/packages/libro-widget/src/base/version.ts b/packages/libro-jupyter/src/widget/version.ts similarity index 100% rename from packages/libro-widget/src/base/version.ts rename to packages/libro-jupyter/src/widget/version.ts diff --git a/packages/libro-widget/src/base/widget-manager.ts b/packages/libro-jupyter/src/widget/widget-manager.ts similarity index 88% rename from packages/libro-widget/src/base/widget-manager.ts rename to packages/libro-jupyter/src/widget/widget-manager.ts index 33ce442e..2e483111 100644 --- a/packages/libro-widget/src/base/widget-manager.ts +++ b/packages/libro-jupyter/src/widget/widget-manager.ts @@ -4,8 +4,8 @@ import { KernelConnection, LibroKernelManager } from '@difizen/libro-kernel'; import { inject, prop, singleton, ApplicationContribution } from '@difizen/mana-app'; import type { LibroWidgets } from './libro-widgets.js'; -import { LibroWidgetsFactory } from './protocal.js'; -import type { WidgetsOption } from './protocal.js'; +import { LibroWidgetsFactory } from './protocol.js'; +import type { WidgetsOption } from './protocol.js'; @singleton({ contrib: ApplicationContribution }) export class LibroWidgetManager implements ApplicationContribution { @@ -33,6 +33,10 @@ export class LibroWidgetManager implements ApplicationContribution { return newWidgets; }; + getWidgets(id: string) { + return this.widgets.get(id); + } + /** * Dictionary of model ids and model instance promises */ diff --git a/packages/libro-jupyter/src/widget/widget-render.tsx b/packages/libro-jupyter/src/widget/widget-render.tsx index 2b955b86..b81551f3 100644 --- a/packages/libro-jupyter/src/widget/widget-render.tsx +++ b/packages/libro-jupyter/src/widget/widget-render.tsx @@ -1,18 +1,22 @@ -import type { BaseOutputView } from '@difizen/libro-core'; +import type { BaseOutputView, LibroOutputView } from '@difizen/libro-core'; import { RenderMimeRegistry } from '@difizen/libro-rendermime'; import type { IRenderMimeRegistry } from '@difizen/libro-rendermime'; -import { LibroWidgetManager } from '@difizen/libro-widget'; -import { getOrigin, useInject, ViewRender } from '@difizen/mana-app'; +import { getOrigin, useInject, ViewInstance, ViewRender } from '@difizen/mana-app'; import React from 'react'; -import './index.less'; +import './index.less'; import { LibroJupyterModel } from '../libro-jupyter-model.js'; +import { LibroWidgetManager } from './widget-manager.js'; + export const WidgetRender: React.FC<{ model: BaseOutputView }> = (props: { model: BaseOutputView; }) => { const { model } = props; + // The widget will be rendered in the output through the MIME mechanism, obtaining the output context. + const output = useInject(ViewInstance); + const widgetManager = useInject(LibroWidgetManager); const defaultRenderMime = useInject(RenderMimeRegistry); const libro = model.cell.parent; @@ -27,6 +31,7 @@ export const WidgetRender: React.FC<{ model: BaseOutputView }> = (props: { const model_id = JSON.parse(JSON.stringify(model.data[mimeType])).model_id; if (model_id) { const widgetView = widgets.getModel(model_id); + widgetView.setCell(getOrigin(output.cell)); if (widgetView.isCommClosed) { return null; } @@ -41,7 +46,7 @@ export const WidgetRender: React.FC<{ model: BaseOutputView }> = (props: { } return (
-
+
); }; diff --git a/packages/libro-jupyter/src/widget/widget-rendermime-contribution.ts b/packages/libro-jupyter/src/widget/widget-rendermime-contribution.ts index 4e4310cf..bb0df4ac 100644 --- a/packages/libro-jupyter/src/widget/widget-rendermime-contribution.ts +++ b/packages/libro-jupyter/src/widget/widget-rendermime-contribution.ts @@ -1,10 +1,10 @@ import type { BaseOutputView } from '@difizen/libro-core'; import { RenderMimeContribution } from '@difizen/libro-rendermime'; -import { LibroWidgetManager } from '@difizen/libro-widget'; import { inject, singleton } from '@difizen/mana-app'; import { LibroJupyterModel } from '../libro-jupyter-model.js'; +import { LibroWidgetManager } from './widget-manager.js'; import { WidgetRender } from './widget-render.js'; @singleton({ contrib: RenderMimeContribution }) @@ -31,5 +31,6 @@ export class LibroWidgetMimeContribution implements RenderMimeContribution { renderType = 'widgetRenderer'; safe = true; mimeTypes = ['application/vnd.jupyter.widget-view+json']; + allowClear = false; render = WidgetRender; } diff --git a/packages/libro-widget/src/base/widget-view-contribution.ts b/packages/libro-jupyter/src/widget/widget-view-contribution.ts similarity index 79% rename from packages/libro-widget/src/base/widget-view-contribution.ts rename to packages/libro-jupyter/src/widget/widget-view-contribution.ts index 80d197b7..fbaf24df 100644 --- a/packages/libro-widget/src/base/widget-view-contribution.ts +++ b/packages/libro-jupyter/src/widget/widget-view-contribution.ts @@ -1,7 +1,7 @@ import { ViewManager, inject, singleton } from '@difizen/mana-app'; -import type { IWidgetViewProps } from './protocal.js'; -import { WidgetViewContribution } from './protocal.js'; +import type { IWidgetViewProps } from './protocol.js'; +import { WidgetViewContribution } from './protocol.js'; import { WidgetView } from './widget-view.js'; @singleton({ contrib: WidgetViewContribution }) diff --git a/packages/libro-jupyter/src/widget/widget-view.tsx b/packages/libro-jupyter/src/widget/widget-view.tsx new file mode 100644 index 00000000..ef3af7a2 --- /dev/null +++ b/packages/libro-jupyter/src/widget/widget-view.tsx @@ -0,0 +1,259 @@ +import type { JSONObject, JSONValue, IOutput, OutputType } from '@difizen/libro-common'; +import type { CellView, LibroExecutableCellView } from '@difizen/libro-core'; +import { ExecutableCellView } from '@difizen/libro-core'; +import { LibroContextKey } from '@difizen/libro-core'; +import type { KernelMessage } from '@difizen/libro-kernel'; +import { + inject, + transient, + ViewOption, + view, + BaseView, + prop, + watch, +} from '@difizen/mana-app'; +import type { ViewComponent, Disposable } from '@difizen/mana-app'; +import { forwardRef } from 'react'; + +import { LibroJupyterModel } from '../libro-jupyter-model.js'; + +import { defaultWidgetState } from './protocol.js'; +import type { + Dict, + IWidgets, + IWidgetView, + IClassicComm, + ICallbacks, + IWidgetViewProps, + WidgetState, +} from './protocol.js'; +import { LibroWidgetManager } from './widget-manager.js'; + +export const LibroWidgetComponent = forwardRef( + function LibroWidgetComponent() { + return <>; + }, +); + +@transient() +@view('libro-widget-view') +export class WidgetView extends BaseView implements IWidgetView { + override view: ViewComponent = LibroWidgetComponent; + libroContextKey: LibroContextKey; + widgetsId: string; + protected _msgHook: (msg: KernelMessage.IIOPubMessage) => boolean; + + @inject(LibroWidgetManager) libroWidgetManager: LibroWidgetManager; + + @prop() + state: JSONObject & WidgetState = defaultWidgetState; + + cell?: LibroExecutableCellView; + + get outputs() { + if (this.cell) { + return this.cell.outputArea; + } + return undefined; + } + + disableCommandMode = true; + + toDisposeOnMsgChanged?: Disposable; + + constructor( + @inject(ViewOption) props: IWidgetViewProps, + @inject(LibroContextKey) libroContextKey: LibroContextKey, + ) { + super(); + this.widgetsId = props.widgetsId; + const attributes = props.attributes; + this.model_module = attributes._model_module; + this.model_name = attributes._model_name; + this.model_module_version = attributes._model_module_version; + this.view_module = attributes._view_module; + this.view_name = attributes._view_name; + this.view_module_version = attributes._view_module_version; + this.view_count = attributes._view_count; + + this._msgHook = (msg: KernelMessage.IIOPubMessage): boolean => { + this.addFromMessage(msg); + return false; + }; + + // Attributes should be initialized here, since user initialization may depend on it + const comm = props.options.comm; + if (comm) { + // Remember comm associated with the model. + this.comm = comm; + + // Hook comm messages up to model. + comm.onClose(this.handleCommClosed.bind(this)); + comm.onMsg(this.handleCommMsg.bind(this)); + } else { + this.isCommClosed = false; + } + this.model_id = props.options.model_id; + + this.state_change = Promise.resolve(); + this.setState(attributes); + this.libroContextKey = libroContextKey; + } + + setCell(cell: CellView) { + if (ExecutableCellView.is(cell)) { + this.cell = cell as LibroExecutableCellView; + if (this.cell) { + this.cell.parentReady + .then(() => { + const notebookModel = this.cell?.parent.model; + if (notebookModel instanceof LibroJupyterModel) { + watch(notebookModel, 'kernelConnection', this.handleKernelChanged); + } + return; + }) + .catch(console.error); + } + } + } + + /** + * Register a new kernel + */ + handleKernelChanged = (): void => { + this.setState({ msg_id: undefined }); + }; + + /** + * Reset the message id. + */ + resetMsgId(): void { + this.toDisposeOnMsgChanged?.dispose(); + const notebookModel = this.cell?.parent?.model; + + if (notebookModel instanceof LibroJupyterModel) { + const kernel = notebookModel.kernelConnection; + if (kernel && this.state['msg_id']) { + this.toDisposeOnMsgChanged = kernel.registerMessageHook( + this.state['msg_id'], + this._msgHook, + ); + } + } + } + + addFromMessage(msg: KernelMessage.IIOPubMessage) { + const msgType = msg.header.msg_type; + switch (msgType) { + case 'execute_result': + case 'display_data': + case 'stream': + case 'error': { + const model = msg.content as IOutput; + model.output_type = msgType as OutputType; + this.outputs?.add(model); + break; + } + case 'clear_output': + this.clearOutput((msg as KernelMessage.IClearOutputMsg).content.wait); + break; + default: + break; + } + } + + clearOutput(wait = false): void { + this.outputs?.clear(wait); + } + + override onViewMount() { + this.widgets = this.libroWidgetManager.getWidgets(this.widgetsId)!; + + if (this.container && this.container.current && this.disableCommandMode) { + this.container.current.addEventListener('focusin', () => { + this.libroContextKey.disableCommandMode(); + }); + this.container.current.addEventListener('blur', (e) => { + if (this.container?.current?.contains(e.relatedTarget as Node)) { + this.libroContextKey.disableCommandMode(); + } else { + this.libroContextKey.enableCommandMode(); + } + }); + } + } + + /** + * Handle incoming comm msg. + */ + handleCommMsg(msg: KernelMessage.ICommMsgMsg): Promise { + const data = msg.content.data as any; + const method = data.method; + switch (method) { + case 'update': + case 'echo_update': + this.setState(data.state); + } + return Promise.resolve(); + } + + handleCommClosed = () => { + this.isCommClosed = true; + }; + /** + * Handle when a widget is updated from the backend. + * + * This function is meant for internal use only. Values set here will not be propagated on a sync. + */ + setState(state: Dict): void { + for (const key in state) { + const oldMsgId = this.state['msg_id']; + this.state[key] = state[key]; + if (key === 'msg_id' && oldMsgId !== state['msg_id']) { + this.resetMsgId(); + } + } + } + + /** + * Serialize the model. See the deserialization function at the top of this file + * and the kernel-side serializer/deserializer. + */ + toJSON(): string { + return this.toModelKey(); + } + + toModelKey(): string { + return `IPY_MODEL_${this.model_id}`; + } + + /** + * Send a custom msg over the comm. + */ + send = ( + data: JSONValue, + callbacks?: ICallbacks, + buffers?: ArrayBuffer[] | ArrayBufferView[], + ) => { + if (this.comm !== undefined) { + return this.comm.send(data, callbacks, {}, buffers); + } + return undefined; + }; + + comm: IClassicComm; + @prop() widgets?: IWidgets; + model_id: string; + state_change: Promise; + name: string; + module: string; + isCommClosed = false; + + model_module: string; + model_name: string; + model_module_version: string; + view_module: string; + view_name: string | null; + view_module_version: string; + view_count: number | null; +} diff --git a/packages/libro-kernel/src/kernel/future.ts b/packages/libro-kernel/src/kernel/future.ts index 4e72b1a9..e78e9571 100644 --- a/packages/libro-kernel/src/kernel/future.ts +++ b/packages/libro-kernel/src/kernel/future.ts @@ -1,4 +1,4 @@ -import type { Disposable } from '@difizen/mana-app'; +import { Disposable } from '@difizen/mana-app'; import { Deferred } from '@difizen/mana-app'; import type { @@ -280,11 +280,14 @@ export abstract class KernelFutureHandler< */ registerMessageHook( hook: (msg: KernelMessage.IIOPubMessage) => boolean | PromiseLike, - ): void { + ): Disposable { if (this.disposed) { throw new Error('Kernel future is disposed'); } this._hooks.add(hook); + return Disposable.create(() => { + this.removeMessageHook(hook); + }); } /** diff --git a/packages/libro-kernel/src/kernel/kernel-connection.ts b/packages/libro-kernel/src/kernel/kernel-connection.ts index 4cc51837..475998ef 100644 --- a/packages/libro-kernel/src/kernel/kernel-connection.ts +++ b/packages/libro-kernel/src/kernel/kernel-connection.ts @@ -1,6 +1,7 @@ import type { JSONObject } from '@difizen/libro-common'; import { deepCopy, URL } from '@difizen/libro-common'; -import type { Disposable, Event as ManaEvent } from '@difizen/mana-app'; +import type { Event as ManaEvent } from '@difizen/mana-app'; +import { Disposable } from '@difizen/mana-app'; import { prop } from '@difizen/mana-app'; import { Deferred, Emitter } from '@difizen/mana-app'; import { inject, transient } from '@difizen/mana-app'; @@ -1070,11 +1071,12 @@ export class KernelConnection implements IKernelConnection { registerMessageHook( msgId: string, hook: (msg: KernelMessage.IIOPubMessage) => boolean | PromiseLike, - ): void { + ): Disposable { const future = this._futures?.get(msgId); if (future) { - future.registerMessageHook(hook); + return future.registerMessageHook(hook); } + return Disposable.NONE; } /** @@ -1324,7 +1326,6 @@ export class KernelConnection implements IKernelConnection { } const onMsg = comm.onMsg; if (onMsg) { - // tslint:disable-next-line:await-promise await onMsg(msg); } } diff --git a/packages/libro-kernel/src/kernel/libro-kernel-protocol.ts b/packages/libro-kernel/src/kernel/libro-kernel-protocol.ts index 8a51cb22..6536376e 100644 --- a/packages/libro-kernel/src/kernel/libro-kernel-protocol.ts +++ b/packages/libro-kernel/src/kernel/libro-kernel-protocol.ts @@ -739,7 +739,7 @@ export interface IKernelConnection extends ObservableDisposable { registerMessageHook: ( msgId: string, hook: (msg: KernelMessage.IIOPubMessage) => boolean | PromiseLike, - ) => void; + ) => Disposable; /** * Remove an IOPub message hook. diff --git a/packages/libro-lab/package.json b/packages/libro-lab/package.json index 54fe6c98..0addee81 100644 --- a/packages/libro-lab/package.json +++ b/packages/libro-lab/package.json @@ -55,6 +55,7 @@ "@difizen/libro-toc": "^0.1.33", "@difizen/libro-cofine-editor-core": "^0.1.33", "@difizen/libro-language-client": "^0.1.33", + "@difizen/libro-widget": "^0.1.31", "@difizen/mana-app": "latest", "@difizen/mana-common": "latest", "@difizen/mana-react": "latest", diff --git a/packages/libro-lab/src/module.tsx b/packages/libro-lab/src/module.tsx index 1d86455d..937611a4 100644 --- a/packages/libro-lab/src/module.tsx +++ b/packages/libro-lab/src/module.tsx @@ -1,6 +1,7 @@ import { FileView, LibroJupyterModule } from '@difizen/libro-jupyter'; import { LibroPromptCellModule } from '@difizen/libro-prompt-cell'; import { TerminalModule } from '@difizen/libro-terminal'; +import { CommonWidgetsModule } from '@difizen/libro-widget'; import { ManaModule, createSlotPreference, @@ -98,6 +99,7 @@ export const LibroLabModule = ManaModule.create() ) .dependOn( LibroJupyterModule, + CommonWidgetsModule, LibroLabLayoutModule, LibroLabHeaderMenuModule, LibroLabTocModule, diff --git a/packages/libro-output/src/display-data-output/display-data-output-model.tsx b/packages/libro-output/src/display-data-output/display-data-output-model.tsx index e3d762c3..0cd01e6d 100644 --- a/packages/libro-output/src/display-data-output/display-data-output-model.tsx +++ b/packages/libro-output/src/display-data-output/display-data-output-model.tsx @@ -2,7 +2,7 @@ import type { JSONObject } from '@difizen/libro-common'; import { LibroOutputView } from '@difizen/libro-core'; import type { BaseOutputView, IOutputOptions } from '@difizen/libro-core'; import { RenderMimeRegistry } from '@difizen/libro-rendermime'; -import type { IRenderMimeRegistry } from '@difizen/libro-rendermime'; +import type { IRenderMimeRegistry, IRendererFactory } from '@difizen/libro-rendermime'; import { getOrigin, useInject, @@ -19,16 +19,11 @@ import '../index.less'; const DisplayDataOutputModelRender = forwardRef( function DisplayDataOutputModelRender(_props, ref) { const output = useInject(ViewInstance); - const defaultRenderMime = useInject(RenderMimeRegistry); - const model = getOrigin(output); - const defaultRenderMimeType = defaultRenderMime.preferredMimeType(model); + const factory = model.getRenderFactory(); let children = null; - if (defaultRenderMimeType) { - const OutputRender = defaultRenderMime.createRenderer( - defaultRenderMimeType, - model, - ); + if (factory) { + const OutputRender = factory.render; children = ; } return ( @@ -41,6 +36,8 @@ const DisplayDataOutputModelRender = forwardRef( @transient() @view('libro-display-data-output-model') export class DisplayDataOutputModel extends LibroOutputView implements BaseOutputView { + @inject(RenderMimeRegistry) renderMimeRegistry: IRenderMimeRegistry; + renderFactory?: IRendererFactory; constructor(@inject(ViewOption) options: IOutputOptions) { super(options); const { data, metadata } = getBundleOptions(options.output); @@ -48,6 +45,17 @@ export class DisplayDataOutputModel extends LibroOutputView implements BaseOutpu this.data = data as JSONObject; this.metadata = metadata; } + + getRenderFactory() { + const renderMimeType = this.renderMimeRegistry.preferredMimeType(this); + if (renderMimeType) { + const renderMime = this.renderMimeRegistry.createRenderer(renderMimeType, this); + this.renderFactory = getOrigin(renderMime); + this.allowClear = renderMime.allowClear === false ? false : true; + return renderMime; + } + return undefined; + } override view = DisplayDataOutputModelRender; override toJSON() { if (this.raw.execution_count !== undefined) { @@ -64,4 +72,11 @@ export class DisplayDataOutputModel extends LibroOutputView implements BaseOutpu metadata: this.raw.metadata, }; } + + override dispose(force?: boolean): void { + if (!force && this.allowClear === false) { + return; + } + super.dispose(); + } } diff --git a/packages/libro-output/src/stream-output/stream-output-model.tsx b/packages/libro-output/src/stream-output/stream-output-model.tsx index 36fa5f06..3a74c0b9 100644 --- a/packages/libro-output/src/stream-output/stream-output-model.tsx +++ b/packages/libro-output/src/stream-output/stream-output-model.tsx @@ -2,7 +2,7 @@ import type { JSONObject } from '@difizen/libro-common'; import { LibroOutputView } from '@difizen/libro-core'; import type { BaseOutputView, IOutputOptions } from '@difizen/libro-core'; import { RenderMimeRegistry } from '@difizen/libro-rendermime'; -import type { IRenderMimeRegistry } from '@difizen/libro-rendermime'; +import type { IRenderMimeRegistry, IRendererFactory } from '@difizen/libro-rendermime'; import { inject, transient } from '@difizen/mana-app'; import { getOrigin, @@ -21,13 +21,9 @@ const StreamOutputModelRender = forwardRef( function StreamOutputModelRender(_props, ref) { const output = useInject(ViewInstance); const model = getOrigin(output); - const defaultRenderMime = useInject(RenderMimeRegistry); - const defaultRenderMimeType = defaultRenderMime.preferredMimeType(model); - if (defaultRenderMimeType) { - const OutputRender = defaultRenderMime.createRenderer( - defaultRenderMimeType, - model, - ); + const factory = model.getRenderFactory(); + if (factory) { + const OutputRender = factory.render; const children = ; return (
@@ -42,6 +38,9 @@ const StreamOutputModelRender = forwardRef( @transient() @view('libro-stream-output-model') export class StreamOutputModel extends LibroOutputView implements BaseOutputView { + @inject(RenderMimeRegistry) renderMimeRegistry: IRenderMimeRegistry; + renderFactory?: IRendererFactory; + constructor(@inject(ViewOption) options: IOutputOptions) { super(options); const { data, metadata } = getBundleOptions(options.output); @@ -49,6 +48,15 @@ export class StreamOutputModel extends LibroOutputView implements BaseOutputView this.data = data as JSONObject; this.metadata = metadata; } + getRenderFactory() { + const renderMimeType = this.renderMimeRegistry.preferredMimeType(this); + if (renderMimeType) { + const renderMime = this.renderMimeRegistry.createRenderer(renderMimeType, this); + this.renderFactory = getOrigin(renderMime); + return renderMime; + } + return undefined; + } override view = StreamOutputModelRender; override toJSON() { return { diff --git a/packages/libro-rendermime/src/rendermime-protocol.ts b/packages/libro-rendermime/src/rendermime-protocol.ts index 8917babc..d2e9cb38 100644 --- a/packages/libro-rendermime/src/rendermime-protocol.ts +++ b/packages/libro-rendermime/src/rendermime-protocol.ts @@ -6,7 +6,7 @@ import { Syringe } from '@difizen/mana-app'; export const DefaultRenderMimeRegistry = Symbol('RenderMimeRegistry'); export const IRenderMimeRegistryOptions = Symbol('IRenderMimeRegistryOptions'); export const RenderMimeContribution = Syringe.defineToken('RenderMimeTypeContribution'); -export interface RenderMimeContribution { +export interface RenderMimeContribution extends IRendererFactory { canHandle: (model: BaseOutputView) => number; safe: boolean; renderType: string; @@ -17,6 +17,7 @@ export interface RenderMimeContribution { * The interface for a renderer factory. */ export interface IRendererFactory { + allowClear?: boolean; /** * Whether the factory is a "safe" factory. * @@ -198,7 +199,7 @@ export interface IRenderMimeRegistry { mimeType: string, model: BaseOutputView, // model: BaseOutputModel, // host: HTMLElement, - ) => React.FC<{ model: BaseOutputView }>; + ) => IRendererFactory; // /** // * Create a new mime model. This is a convenience method. // * diff --git a/packages/libro-rendermime/src/rendermime-registry.ts b/packages/libro-rendermime/src/rendermime-registry.ts index f3d6dce5..a3bdc231 100644 --- a/packages/libro-rendermime/src/rendermime-registry.ts +++ b/packages/libro-rendermime/src/rendermime-registry.ts @@ -184,17 +184,16 @@ export class RenderMimeRegistry implements IRenderMimeRegistry { mimeType: string, model: BaseOutputView, // model: BaseOutputModel, // host: HTMLElement, - ): React.FC<{ model: BaseOutputView }> { + ): IRendererFactory { const renderMimes = this.getSortedRenderMimes(model); for (const renderMime of renderMimes) { for (const mt of renderMime.mimeTypes) { if (mimeType === mt) { - const OutputRender = renderMime.render; this.renderMimeEmitter.fire({ renderType: renderMime.renderType, mimeType, }); - return OutputRender; + return renderMime; } } } @@ -203,13 +202,13 @@ export class RenderMimeRegistry implements IRenderMimeRegistry { if (!(mimeType in this._factories)) { throw new Error(`No factory for mime type: '${mimeType}'`); } - const OutputRender = this._factories[mimeType].render; + const renderMime = this._factories[mimeType]; this.renderMimeEmitter.fire({ renderType: this._factories[mimeType].renderType, mimeType, }); // Invoke the best factory for the given mime type. - return OutputRender; + return renderMime; } /** diff --git a/packages/libro-widget/package.json b/packages/libro-widget/package.json index 6b840343..b6a0ea69 100644 --- a/packages/libro-widget/package.json +++ b/packages/libro-widget/package.json @@ -48,6 +48,7 @@ "@difizen/libro-common": "^0.1.33", "@difizen/libro-kernel": "^0.1.33", "@difizen/libro-rendermime": "^0.1.33", + "@difizen/libro-jupyter": "^0.1.33", "@difizen/mana-app": "latest" }, "peerDependencies": { diff --git a/packages/libro-widget/src/base/index.ts b/packages/libro-widget/src/base/index.ts deleted file mode 100644 index 3579120e..00000000 --- a/packages/libro-widget/src/base/index.ts +++ /dev/null @@ -1,6 +0,0 @@ -export * from './libro-widgets.js'; -export * from './widget-view.js'; -export * from './protocal.js'; -export * from './widget-manager.js'; -export * from './comm.js'; -export * from './widget-view-contribution.js'; diff --git a/packages/libro-widget/src/base/widget-view.tsx b/packages/libro-widget/src/base/widget-view.tsx deleted file mode 100644 index 8fb04031..00000000 --- a/packages/libro-widget/src/base/widget-view.tsx +++ /dev/null @@ -1,146 +0,0 @@ -import type { JSONValue } from '@difizen/libro-common'; -import { LibroContextKey } from '@difizen/libro-core'; -import type { KernelMessage } from '@difizen/libro-kernel'; -import { inject, transient, ViewOption, view, BaseView } from '@difizen/mana-app'; -import type { ViewComponent } from '@difizen/mana-app'; -import { forwardRef } from 'react'; - -import type { IWidgetViewProps } from './protocal.js'; -import type { - BufferJSON, - Dict, - IWidgets, - IWidgetView, - IClassicComm, - ICallbacks, -} from './protocal.js'; -import { assign } from './utils.js'; - -export const LibroWidgetComponent = forwardRef( - function LibroWidgetComponent() { - return <>; - }, -); - -@transient() -@view('libro-widget-view') -export class WidgetView extends BaseView implements IWidgetView { - override view: ViewComponent = LibroWidgetComponent; - libroContextKey: LibroContextKey; - disableCommandMode = true; - constructor( - @inject(ViewOption) props: IWidgetViewProps, - @inject(LibroContextKey) libroContextKey: LibroContextKey, - ) { - super(); - this.initialize(props); - this.libroContextKey = libroContextKey; - } - - override onViewMount() { - if (this.container && this.container.current && this.disableCommandMode) { - this.container.current.addEventListener('focusin', () => { - this.libroContextKey.disableCommandMode(); - }); - this.container.current.addEventListener('blur', (e) => { - if (this.container?.current?.contains(e.relatedTarget as Node)) { - this.libroContextKey.disableCommandMode(); - } else { - this.libroContextKey.enableCommandMode(); - } - }); - } - } - - initialize(props: IWidgetViewProps): void { - this.model_module = props.attributes._model_module; - this.model_name = props.attributes._model_name; - this.model_module_version = props.attributes._model_module_version; - this.view_module = props.attributes._view_module; - this.view_name = props.attributes._view_name; - this.view_module_version = props.attributes._view_module_version; - this.view_count = props.attributes._view_count; - - // Attributes should be initialized here, since user initialization may depend on it - // this.libroWidgets = props.options.libroWidgets; - const comm = props.options.comm; - if (comm) { - // Remember comm associated with the model. - this.comm = comm; - - // Hook comm messages up to model. - comm.on_close(this.handleCommClosed.bind(this)); - comm.on_msg(this.handleCommMsg.bind(this)); - } else { - this.isCommClosed = false; - } - this.model_id = props.options.model_id; - - this.state_change = Promise.resolve(); - } - - /** - * Handle incoming comm msg. - */ - handleCommMsg(msg: KernelMessage.ICommMsgMsg): Promise { - const data = msg.content.data as any; - const method = data.method; - switch (method) { - case 'update': - case 'echo_update': - // eslint-disable-next-line no-case-declarations - const state: Dict = data.state; - this.set_state(state); - } - return Promise.resolve(); - } - - handleCommClosed = () => { - this.isCommClosed = true; - }; - /** - * Handle when a widget is updated from the backend. - * - * This function is meant for internal use only. Values set here will not be propagated on a sync. - */ - set_state(state: Dict): void { - assign(this, state); - } - - /** - * Serialize the model. See the deserialization function at the top of this file - * and the kernel-side serializer/deserializer. - */ - toJSON(): string { - return `IPY_MODEL_${this.model_id}`; - } - - /** - * Send a custom msg over the comm. - */ - send = ( - data: JSONValue, - callbacks?: ICallbacks, - buffers?: ArrayBuffer[] | ArrayBufferView[], - ) => { - if (this.comm !== undefined) { - this.comm.send(data, callbacks, {}, buffers); - } - }; - - comm: IClassicComm; - libroWidgets: IWidgets; - model_id: string; - state_change: Promise; - name: string; - module: string; - isCommClosed = false; - - model_module: string; - model_name: string; - model_module_version: string; - view_module: string; - view_name: string | null; - view_module_version: string; - view_count: number | null; -} diff --git a/packages/libro-widget/src/components/index.ts b/packages/libro-widget/src/components/index.ts deleted file mode 100644 index 7102c9f7..00000000 --- a/packages/libro-widget/src/components/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export * from './progressBar.js'; - -export * from './progressCircle.js'; diff --git a/packages/libro-widget/src/components/progressCircle.tsx b/packages/libro-widget/src/components/progressCircle.tsx deleted file mode 100644 index 86b45b30..00000000 --- a/packages/libro-widget/src/components/progressCircle.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import { Progress } from 'antd'; -/** - * Props for the ProgressBar. - */ -export interface IProgressCircleProps { - /** - * The current progress percentage, from 0 to 100 - */ - percent: number; -} - -export function ProgressCircle(props: IProgressCircleProps) { - return ( - <> - ; - - ); -} diff --git a/packages/libro-widget/src/index.spec.ts b/packages/libro-widget/src/index.spec.ts index b78af1c7..cabad93e 100644 --- a/packages/libro-widget/src/index.spec.ts +++ b/packages/libro-widget/src/index.spec.ts @@ -1,11 +1,10 @@ import assert from 'assert'; -import { WidgetView, LibroWidgetManager } from './index.js'; +// import { SliderWidget } from './index.js'; import 'reflect-metadata'; describe('libro-widget', () => { it('#import', () => { - assert(LibroWidgetManager); - assert(WidgetView); + assert(true); }); }); diff --git a/packages/libro-widget/src/index.ts b/packages/libro-widget/src/index.ts index cb80df22..0012a049 100644 --- a/packages/libro-widget/src/index.ts +++ b/packages/libro-widget/src/index.ts @@ -1,4 +1,3 @@ -export * from './components/index.js'; -export * from './base/index.js'; -export * from './widget-module.js'; -export * from './widgets/index.js'; +export * from './module.js'; +export * from './slider/index.js'; +export * from './text/index.js'; diff --git a/packages/libro-widget/src/module.ts b/packages/libro-widget/src/module.ts new file mode 100644 index 00000000..26bd1bf0 --- /dev/null +++ b/packages/libro-widget/src/module.ts @@ -0,0 +1,15 @@ +import { WidgetModule } from '@difizen/libro-jupyter'; +import { ManaModule } from '@difizen/mana-app'; + +import { SilderWidgetContribution, SliderWidget } from './slider/index.js'; +import { TextModelContribution, TextWidget } from './text/index.js'; + +export const CommonWidgetsModule = ManaModule.create() + .register( + SliderWidget, + SilderWidgetContribution, + + TextWidget, + TextModelContribution, + ) + .dependOn(WidgetModule); diff --git a/packages/libro-widget/src/slider/contribution.ts b/packages/libro-widget/src/slider/contribution.ts new file mode 100644 index 00000000..64bb00c8 --- /dev/null +++ b/packages/libro-widget/src/slider/contribution.ts @@ -0,0 +1,25 @@ +import type { IWidgetViewProps } from '@difizen/libro-jupyter'; +import { WidgetViewContribution } from '@difizen/libro-jupyter'; +import { ViewManager, inject, singleton } from '@difizen/mana-app'; + +import { SliderWidget } from './view.js'; + +@singleton({ contrib: WidgetViewContribution }) +export class SilderWidgetContribution implements WidgetViewContribution { + @inject(ViewManager) viewManager: ViewManager; + canHandle = (attributes: any) => { + if (attributes._model_name === 'IntSliderModel') { + return 100; + } + if (attributes.__view_name === 'IntSliderView') { + return 100; + } + if (attributes._model_name === 'FloatSliderModel') { + return 100; + } + return 1; + }; + factory(props: IWidgetViewProps) { + return this.viewManager.getOrCreateView(SliderWidget, props); + } +} diff --git a/packages/libro-widget/src/slider/index.less b/packages/libro-widget/src/slider/index.less new file mode 100644 index 00000000..e6588a9f --- /dev/null +++ b/packages/libro-widget/src/slider/index.less @@ -0,0 +1,3 @@ +.libro-widget-slider { + display: block; +} diff --git a/packages/libro-widget/src/slider/index.ts b/packages/libro-widget/src/slider/index.ts new file mode 100644 index 00000000..7be67da4 --- /dev/null +++ b/packages/libro-widget/src/slider/index.ts @@ -0,0 +1,2 @@ +export * from './contribution.js'; +export * from './view.js'; diff --git a/packages/libro-widget/src/slider/view.tsx b/packages/libro-widget/src/slider/view.tsx new file mode 100644 index 00000000..fb7dd106 --- /dev/null +++ b/packages/libro-widget/src/slider/view.tsx @@ -0,0 +1,92 @@ +import type { JSONObject } from '@difizen/libro-common'; +import { LibroContextKey } from '@difizen/libro-core'; +import type { IWidgetViewProps } from '@difizen/libro-jupyter'; +import type { OrientableState, WidgetState } from '@difizen/libro-jupyter'; +import { WidgetView } from '@difizen/libro-jupyter'; +import { defaultWidgetState } from '@difizen/libro-jupyter'; +import { + view, + transient, + useInject, + ViewInstance, + prop, + inject, + ViewOption, +} from '@difizen/mana-app'; +import { Slider } from 'antd'; +import { forwardRef } from 'react'; + +import './index.less'; + +export const LibroWidgetIntSliderComponent = forwardRef( + function LibroWidgetIntSliderComponent(props, ref) { + const widget = useInject(ViewInstance); + + if (widget.isCommClosed) { + return null; + } + + return ( +
+ +
+ ); + }, +); + +interface SliderState extends WidgetState, OrientableState { + max?: number; + min?: number; + step: number; + value: number; +} + +@transient() +@view('libro-widget-slider-view') +export class SliderWidget extends WidgetView { + override view = LibroWidgetIntSliderComponent; + + @prop() + override state: JSONObject & SliderState = { + ...defaultWidgetState, + orientation: 'horizontal', + step: 1, + value: 0, + }; + + constructor( + @inject(ViewOption) props: IWidgetViewProps, + @inject(LibroContextKey) libroContextKey: LibroContextKey, + ) { + super(props, libroContextKey); + this.initialize(props); + } + + protected initialize(props: IWidgetViewProps): void { + if (this.model_name === 'FloatSliderModel') { + if (!this.state.step) { + this.state.step === 0.01; + } + } + const attributes = props.attributes; + this.state.max = attributes.max; + this.state.min = attributes.min; + this.setState(attributes); + } + + handleChange = (value: number) => { + const data = { + buffer_paths: [], + method: 'update', + state: { value: value }, + }; + this.send(data); + }; +} diff --git a/packages/libro-widget/src/widgets/text-widget-view-contribution.ts b/packages/libro-widget/src/text/contribution.ts similarity index 62% rename from packages/libro-widget/src/widgets/text-widget-view-contribution.ts rename to packages/libro-widget/src/text/contribution.ts index 5d3efcea..4623886a 100644 --- a/packages/libro-widget/src/widgets/text-widget-view-contribution.ts +++ b/packages/libro-widget/src/text/contribution.ts @@ -1,9 +1,8 @@ +import type { IWidgetViewProps } from '@difizen/libro-jupyter'; +import { WidgetViewContribution } from '@difizen/libro-jupyter'; import { ViewManager, inject, singleton } from '@difizen/mana-app'; -import type { IWidgetViewProps } from '../base/protocal.js'; -import { WidgetViewContribution } from '../base/protocal.js'; - -import { LibroTextWidget } from './text-widget-view.js'; +import { TextWidget } from './view.js'; @singleton({ contrib: WidgetViewContribution }) export class TextModelContribution implements WidgetViewContribution { @@ -15,6 +14,6 @@ export class TextModelContribution implements WidgetViewContribution { return 1; }; factory(props: IWidgetViewProps) { - return this.viewManager.getOrCreateView(LibroTextWidget, props); + return this.viewManager.getOrCreateView(TextWidget, props); } } diff --git a/packages/libro-widget/src/text/index.ts b/packages/libro-widget/src/text/index.ts new file mode 100644 index 00000000..7be67da4 --- /dev/null +++ b/packages/libro-widget/src/text/index.ts @@ -0,0 +1,2 @@ +export * from './contribution.js'; +export * from './view.js'; diff --git a/packages/libro-widget/src/widgets/text-widget-view.tsx b/packages/libro-widget/src/text/view.tsx similarity index 81% rename from packages/libro-widget/src/widgets/text-widget-view.tsx rename to packages/libro-widget/src/text/view.tsx index 6e841126..aee70e0f 100644 --- a/packages/libro-widget/src/widgets/text-widget-view.tsx +++ b/packages/libro-widget/src/text/view.tsx @@ -1,4 +1,6 @@ import { LibroContextKey } from '@difizen/libro-core'; +import type { IWidgetViewProps } from '@difizen/libro-jupyter'; +import { WidgetView } from '@difizen/libro-jupyter'; import { view, ViewOption, @@ -11,14 +13,9 @@ import { import { Input } from 'antd'; import { forwardRef } from 'react'; -import type { IWidgetViewProps } from '../base/protocal.js'; -import { WidgetView } from '../base/widget-view.js'; - -import './index.less'; - -export const LibroTextWidgetComponent = forwardRef( - function LibroTextWidgetComponent(_props, ref) { - const widgetView = useInject(ViewInstance); +export const TextWidgetComponent = forwardRef( + function TextWidgetComponent(_props, ref) { + const widgetView = useInject(ViewInstance); if (widgetView.isCommClosed) { return null; } @@ -55,8 +52,8 @@ export const LibroTextWidgetComponent = forwardRef( ); @transient() @view('libro-widget-text-view') -export class LibroTextWidget extends WidgetView { - override view = LibroTextWidgetComponent; +export class TextWidget extends WidgetView { + override view = TextWidgetComponent; bar_style: string; description: string; description_tooltip: null; diff --git a/packages/libro-widget/src/widgets/hbox-widget-view-contribution.ts b/packages/libro-widget/src/widgets/hbox-widget-view-contribution.ts deleted file mode 100644 index c86b10a8..00000000 --- a/packages/libro-widget/src/widgets/hbox-widget-view-contribution.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { ViewManager, inject, singleton } from '@difizen/mana-app'; - -import type { IWidgetViewProps } from '../base/protocal.js'; -import { WidgetViewContribution } from '../base/protocal.js'; - -import { HBoxWidget } from './hbox-widget-view.js'; - -@singleton({ contrib: WidgetViewContribution }) -export class HBoxModelContribution implements WidgetViewContribution { - @inject(ViewManager) viewManager: ViewManager; - canHandle = (attributes: any) => { - if (attributes._model_name === 'HBoxModel') { - return 100; - } - return 1; - }; - factory(props: IWidgetViewProps) { - return this.viewManager.getOrCreateView(HBoxWidget, props); - } -} diff --git a/packages/libro-widget/src/widgets/hbox-widget-view.tsx b/packages/libro-widget/src/widgets/hbox-widget-view.tsx deleted file mode 100644 index 73e6b755..00000000 --- a/packages/libro-widget/src/widgets/hbox-widget-view.tsx +++ /dev/null @@ -1,64 +0,0 @@ -import { LibroContextKey } from '@difizen/libro-core'; -import { - view, - ViewOption, - ViewRender, - transient, - useInject, - ViewInstance, - inject, -} from '@difizen/mana-app'; -import { forwardRef } from 'react'; - -import { LibroWidgets } from '../base/libro-widgets.js'; -import type { IWidgetViewProps } from '../base/protocal.js'; -import { WidgetView } from '../base/widget-view.js'; -import './index.less'; - -export const HBoxWidgetComponent = forwardRef( - function HBoxWidgetComponent() { - const widgetView = useInject(ViewInstance); - if (widgetView.isCommClosed) { - return null; - } - const hboxChildrenWidget = widgetView.get_child_model(); - return ( -
- {hboxChildrenWidget.map((childrenWidgets) => ( - - ))} -
- ); - }, -); -@transient() -@view('libro-hbox-widget-view') -export class HBoxWidget extends WidgetView { - override view = HBoxWidgetComponent; - children: string[]; - layout: string; - box_style: string; - override libroWidgets: LibroWidgets; - constructor( - @inject(ViewOption) props: IWidgetViewProps, - @inject(LibroContextKey) libroContextKey: LibroContextKey, - @inject(LibroWidgets) libroWidgets: LibroWidgets, - ) { - super(props, libroContextKey); - this.box_style = props.attributes.bar_style; - this.layout = props.attributes.layout; - this.children = props.attributes.children; - this.libroContextKey = libroContextKey; - this.libroWidgets = libroWidgets; - } - - get_child_model(): WidgetView[] { - const childrenWidgets: WidgetView[] = []; - this.children.forEach((child) => { - if (this.libroWidgets.hasModel(child.substring(10))) { - childrenWidgets.push(this.libroWidgets.getModel(child.substring(10))); - } - }); - return childrenWidgets; - } -} diff --git a/packages/libro-widget/src/widgets/index.less b/packages/libro-widget/src/widgets/index.less deleted file mode 100644 index 111f11dc..00000000 --- a/packages/libro-widget/src/widgets/index.less +++ /dev/null @@ -1,3 +0,0 @@ -.libro-input-widget { - display: flex; -} diff --git a/packages/libro-widget/src/widgets/index.ts b/packages/libro-widget/src/widgets/index.ts deleted file mode 100644 index 454320fe..00000000 --- a/packages/libro-widget/src/widgets/index.ts +++ /dev/null @@ -1,8 +0,0 @@ -export * from './hbox-widget-view.js'; -export * from './hbox-widget-view-contribution.js'; -export * from './instances-progress-widget-view.js'; -export * from './instances-progress-widget-view-contribution.js'; -export * from './progress-widget-view.js'; -export * from './progress-widget-view-contribution.js'; -export * from './text-widget-view.js'; -export * from './text-widget-view-contribution.js'; diff --git a/packages/libro-widget/src/widgets/progress-widget-view.tsx b/packages/libro-widget/src/widgets/progress-widget-view.tsx deleted file mode 100644 index 0c5d05f3..00000000 --- a/packages/libro-widget/src/widgets/progress-widget-view.tsx +++ /dev/null @@ -1,85 +0,0 @@ -import { LibroContextKey } from '@difizen/libro-core'; -import type { KernelMessage } from '@difizen/libro-kernel'; -import { - view, - ViewOption, - transient, - useInject, - ViewInstance, - inject, - prop, -} from '@difizen/mana-app'; -import { forwardRef } from 'react'; - -import type { IWidgetViewProps } from '../base/protocal.js'; -import { WidgetView } from '../base/widget-view.js'; -import { ProgressBar } from '../components/index.js'; -import './index.less'; - -export const LibroProgressWidgetComponent = forwardRef( - function LibroProgressWidgetComponent() { - const widgetView = useInject(ViewInstance); - const percent = widgetView.value / ((widgetView.max - widgetView.min) / 100); - if (widgetView.isCommClosed) { - return null; - } - return ( -
-
- {widgetView.description} -
- -
- ); - }, -); -@transient() -@view('libro-widget-progress-view') -export class ProgressWidget extends WidgetView { - override view = LibroProgressWidgetComponent; - bar_style: string; - description: string; - description_tooltip: null; - layout: string; - @prop() - max: number; - @prop() - min: number; - orientation: string; - style: string; - @prop() - value: number; - constructor( - @inject(ViewOption) props: IWidgetViewProps, - @inject(LibroContextKey) libroContextKey: LibroContextKey, - ) { - super(props, libroContextKey); - this.bar_style = props.attributes.bar_style; - this.description = props.attributes.description; - this.description_tooltip = props.attributes.description_tooltip; - this.layout = props.attributes.layout; - this.max = props.attributes.max; - this.min = props.attributes.min; - this.orientation = props.attributes.orientation; - this.style = props.attributes.style; - this.value = props.attributes.value; - } - /** - * Handle incoming comm msg. - */ - override handleCommMsg(msg: KernelMessage.ICommMsgMsg): Promise { - const data = msg.content.data as any; - const method = data.method; - switch (method) { - case 'update': - case 'echo_update': - if (data.state.value) { - this.value = data.state.value; - } - if (data.state.description) { - this.description = data.state.description; - } - } - return Promise.resolve(); - } -}