Skip to content

Commit

Permalink
Sync awareness between components
Browse files Browse the repository at this point in the history
  • Loading branch information
trungleduc committed Oct 24, 2022
1 parent a5e37c8 commit 4f9c099
Show file tree
Hide file tree
Showing 5 changed files with 234 additions and 124 deletions.
140 changes: 74 additions & 66 deletions src/mainview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ export class MainView extends React.Component<IProps, IStates> {
this._model.themeChanged.connect((_, arg) => {
this.handleThemeChange();
});
this._model.cameraChanged.connect(this._onCameraChanged);
this._model.clientStateChanged.connect(this._onClientSharedStateChanged);
});
}

Expand Down Expand Up @@ -251,20 +251,23 @@ export class MainView extends React.Component<IProps, IStates> {
canvas.addEventListener('mouseleave', event => {
this._model.syncCamera(undefined);
});
['wheel', 'mousemove'].forEach(evtName => {
canvas.addEventListener(
evtName as any,
(event: MouseEvent | WheelEvent) => {
this._model.syncCamera({
offsetX: event.offsetX,
offsetY: event.offsetY,
x: this._camera.position.x,
y: this._camera.position.y,
z: this._camera.position.z
});
}
);
});
// ['wheel', 'mousemove'].forEach(evtName => {
// canvas.addEventListener(
// evtName as any,
// (event: MouseEvent | WheelEvent) => {
// this._model.syncCamera(
// {
// offsetX: event.offsetX,
// offsetY: event.offsetY,
// x: this._camera.position.x,
// y: this._camera.position.y,
// z: this._camera.position.z
// },
// this.state.id
// );
// }
// );
// });
}
};

Expand Down Expand Up @@ -397,7 +400,10 @@ export class MainView extends React.Component<IProps, IStates> {
private _onClick(e: MouseEvent) {
this._selectedMesh = this._pick();

this._model.syncSelectedObject(this._selectedMesh !== null ? this._selectedMesh.name : null);
this._model.syncSelectedObject(
this._selectedMesh !== null ? this._selectedMesh.name : null,
this.state.id
);
}

private shapeToMesh = (payload: IDisplayShape['payload']) => {
Expand Down Expand Up @@ -533,61 +539,63 @@ export class MainView extends React.Component<IProps, IStates> {
}
};

private _onCameraChanged = (
private _onClientSharedStateChanged = (
sender: JupyterCadModel,
clients: Map<number, any>
): void => {
clients.forEach((client, key) => {
if (this._context.model.getClientId() !== key) {
const id = key.toString();
const mouse = client.mouse as Position;
if (mouse && this._cameraClients[id]) {
if (mouse.offsetX > 0) {
this._cameraClients[id]!.style.left = mouse.offsetX + 'px';
}
if (mouse.offsetY > 0) {
this._cameraClients[id]!.style.top = mouse.offsetY + 'px';
}
if (!this._mouseDown) {
this._camera.position.set(mouse.x, mouse.y, mouse.z);
}
} else if (mouse && !this._cameraClients[id]) {
const el = document.createElement('div');
el.className = 'jpcad-camera-client';
el.style.left = mouse.offsetX + 'px';
el.style.top = mouse.offsetY + 'px';
el.style.backgroundColor = client.user.color;
el.innerText = client.user.name;
this._cameraClients[id] = el;
this._cameraRef.current?.appendChild(el);
} else if (!mouse && this._cameraClients[id]) {
this._cameraRef.current?.removeChild(this._cameraClients[id]!);
this._cameraClients[id] = undefined;
const clientId = this._context.model.getClientId();
// TODO Handle state changes from another user in follow mode.
const targetId: number | null = null;
if (targetId) {
const remoteState = clients.get(targetId);
const mouse = remoteState.mouse.value as Position;
if (mouse && this._cameraClients[targetId]) {
if (mouse.offsetX > 0) {
this._cameraClients[targetId]!.style.left = mouse.offsetX + 'px';
}

// if (client.mouse) {
// const cameraPos = client.mouse as Position;
// if (el) {
// el.style.left = cameraPos.offsetX + 'px';
// el.style.top = cameraPos.offsetY + 'px';
// } else {
// const newEl = document.createElement('div');
// newEl.className = 'jpcad-camera-client';
// newEl.style.left = cameraPos.offsetX + 'px';
// newEl.style.top = cameraPos.offsetY + 'px';
// newEl.style.backgroundColor = client.user.color;
// newEl.innerText = client.user.name;
// this._cameraClients[id] = el;
// this._cameraRef.current?.appendChild(newEl);
// }
// } else {
// if (el) {
// this._cameraRef.current?.removeChild(el);
// this._cameraClients[id] = undefined;
// }
// }
if (mouse.offsetY > 0) {
this._cameraClients[targetId]!.style.top = mouse.offsetY + 'px';
}
if (!this._mouseDown) {
this._camera.position.set(mouse.x, mouse.y, mouse.z);
}
} else if (mouse && !this._cameraClients[targetId]) {
const el = document.createElement('div');
el.className = 'jpcad-camera-client';
el.style.left = mouse.offsetX + 'px';
el.style.top = mouse.offsetY + 'px';
el.style.backgroundColor = remoteState.user.color;
el.innerText = remoteState.user.name;
this._cameraClients[targetId] = el;
this._cameraRef.current?.appendChild(el);
} else if (!mouse && this._cameraClients[targetId]) {
this._cameraRef.current?.removeChild(this._cameraClients[targetId]!);
this._cameraClients[targetId] = undefined;
}
});
} else {
// We handle here the local state updated by other components

const localState = clients.get(clientId);

if (localState) {
if (
localState['selected'] &&
localState['selected']['emitter'] &&
localState['selected']['emitter'] !== this.state.id
) {
this._meshGroup?.children.forEach(obj => {
if (obj.name === localState['selected']['value']) {
this._selectedMesh = obj as THREE.Mesh<
THREE.BufferGeometry,
THREE.MeshBasicMaterial
>;
}
});
} else {
this._selectedMesh = null;
}
}
}
};

render(): JSX.Element {
Expand Down
26 changes: 16 additions & 10 deletions src/model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ export class JupyterCadModel implements IJupyterCadModel {
constructor(languagePreference?: string, modelDB?: IModelDB) {
this.modelDB = modelDB || new ModelDB();
this.sharedModel.changed.connect(this._onSharedModelChanged);
this.sharedModel.awareness.on('change', this._onCameraChanged);
this.sharedModel.awareness.on('change', this._onClientStateChanged);
}

get isDisposed(): boolean {
Expand Down Expand Up @@ -141,25 +141,31 @@ export class JupyterCadModel implements IJupyterCadModel {
return all;
}

syncCamera(pos: Position | undefined): void {
this.sharedModel.awareness.setLocalStateField('mouse', pos);
syncCamera(pos: Position | undefined, emitter?: any): void {
this.sharedModel.awareness.setLocalStateField('mouse', {
value: pos,
emitter: emitter
});
}

syncSelectedObject(name: string | null): void {
this.sharedModel.awareness.setLocalStateField('selected', name);
syncSelectedObject(name: string | null, emitter?: any): void {
this.sharedModel.awareness.setLocalStateField('selected', {
value: name,
emitter: emitter
});
}

getClientId(): number {
return this.sharedModel.awareness.clientID;
}

get cameraChanged(): ISignal<this, Map<number, any>> {
return this._cameraChanged;
get clientStateChanged(): ISignal<this, Map<number, any>> {
return this._clientStateChanged;
}

private _onCameraChanged = () => {
private _onClientStateChanged = () => {
const clients = this.sharedModel.awareness.getStates();
this._cameraChanged.emit(clients);
this._clientStateChanged.emit(clients);
};

private _onSharedModelChanged = (
Expand All @@ -180,7 +186,7 @@ export class JupyterCadModel implements IJupyterCadModel {
private _contentChanged = new Signal<this, void>(this);
private _stateChanged = new Signal<this, IChangedArgs<any>>(this);
private _themeChanged = new Signal<this, IChangedArgs<any>>(this);
private _cameraChanged = new Signal<this, Map<number, any>>(this);
private _clientStateChanged = new Signal<this, Map<number, any>>(this);
private _sharedModelChanged = new Signal<this, IJupyterCadDocChange>(this);
static worker: Worker;
}
Expand Down
101 changes: 64 additions & 37 deletions src/panelview/objectproperties.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,12 @@ import { Panel } from '@lumino/widgets';
import * as React from 'react';
import { itemFromName } from '../tools';

import { IControlPanelModel, IDict, IJupyterCadDocChange } from '../types';
import {
IControlPanelModel,
IDict,
IJupyterCadDocChange,
IJupyterCadModel
} from '../types';
import { IJCadModel } from '../_interface/jcad';
import { ObjectPropertiesForm } from './formbuilder';
import formSchema from '../_interface/forms.json';
Expand All @@ -27,6 +32,7 @@ interface IStates {
selectedObjectData?: IDict;
selectedObject?: string;
schema?: IDict;
clientId: number | null;
}

interface IProps {
Expand All @@ -38,7 +44,8 @@ class ObjectPropertiesReact extends React.Component<IProps, IStates> {
super(props);
this.state = {
filePath: this.props.cpModel.filePath,
jcadObject: this.props.cpModel.jcadModel?.getAllObject()
jcadObject: this.props.cpModel.jcadModel?.getAllObject(),
clientId: null
};
this.props.cpModel.jcadModel?.sharedModelChanged.connect(
this.sharedJcadModelChanged
Expand All @@ -48,10 +55,14 @@ class ObjectPropertiesReact extends React.Component<IProps, IStates> {
changed.context.model.sharedModelChanged.connect(
this.sharedJcadModelChanged
);
changed.context.model.clientStateChanged.connect(
this._onClientSharedStateChanged
);
this.setState(old => ({
...old,
filePath: changed.context.localPath,
jcadObject: this.props.cpModel.jcadModel?.getAllObject()
jcadObject: this.props.cpModel.jcadModel?.getAllObject(),
clientId: changed.context.model.getClientId()
}));
} else {
this.setState({
Expand All @@ -64,39 +75,6 @@ class ObjectPropertiesReact extends React.Component<IProps, IStates> {
});
}
});
this.props.cpModel.stateChanged.connect((changed, value) => {
const selected = '' + value.newValue;
if (selected.length === 0) {
this.setState(old => ({
...old,
schema: undefined,
selectedObjectData: undefined
}));
return;
}
if (selected.includes('#')) {
const name = selected.split('#')[0];
const objectData = this.props.cpModel.jcadModel?.getAllObject();
if (objectData) {
let schema;
const selectedObj = itemFromName(name, objectData);
if (!selectedObj) {
return;
}

if (selectedObj.shape) {
schema = formSchema[selectedObj.shape];
}
const selectedObjectData = selectedObj['parameters'];
this.setState(old => ({
...old,
selectedObjectData,
selectedObject: name,
schema
}));
}
}
});
}

sharedJcadModelChanged = (_, changed: IJupyterCadDocChange): void => {
Expand Down Expand Up @@ -145,6 +123,55 @@ class ObjectPropertiesReact extends React.Component<IProps, IStates> {
}
}

private _onClientSharedStateChanged = (
sender: IJupyterCadModel,
clients: Map<number, any>
): void => {
const targetId: number | null = null;
const clientId = this.state.clientId;
if (targetId) {
//TODO Sync with remote user in the follow-mode
} else {
// Update from other components of current client
const localState = clientId ? clients.get(clientId) : null;

if (localState) {
if (localState['selected']) {
const selected = '' + localState['selected'].value;
if (selected.length === 0) {
this.setState(old => ({
...old,
schema: undefined,
selectedObjectData: undefined
}));
return;
}

const name = selected;
const objectData = this.props.cpModel.jcadModel?.getAllObject();
if (objectData) {
let schema;
const selectedObj = itemFromName(name, objectData);
if (!selectedObj) {
return;
}

if (selectedObj.shape) {
schema = formSchema[selectedObj.shape];
}
const selectedObjectData = selectedObj['parameters'];
this.setState(old => ({
...old,
selectedObjectData,
selectedObject: name,
schema
}));
}
}
}
}
};

render(): React.ReactNode {
return this.state.schema && this.state.selectedObjectData ? (
<ObjectPropertiesForm
Expand All @@ -167,4 +194,4 @@ export namespace ObjectProperties {
export interface IOptions extends Panel.IOptions {
controlPanelModel: IControlPanelModel;
}
}
}
Loading

0 comments on commit 4f9c099

Please sign in to comment.