Skip to content

Commit e95ee59

Browse files
authored
Add zustand (#30)
* Make room code required * Make the app data object more convenient * Fix bug with enable/disable second model button * Fix bug with model names on scale slider * Save template * Start switching to Zustand * Convert theme changed signal to use state store * Remove renderer from app data * Refactor model registry state stuff * Refactor canLoadModel to use store * Refactor modelInScene to use store * Refactor scenesWithModel to use store * Remove dead code * Refactor isSecondScene to use store * Refactor isConnecting to use store * Refactor config to use store * Refactor selected to use store* * Refactor arCube to use store* * Add a clean up method when stopping presentation * Small fixes to get working again * lint * Clean up cube file
1 parent 4f60a17 commit e95ee59

16 files changed

+3356
-3354
lines changed

RELEASE.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,6 @@ Here is a summary of the steps to cut a new release:
8282
- If the repo generates PyPI release(s), create a scoped PyPI [token](https://packaging.python.org/guides/publishing-package-distribution-releases-using-github-actions-ci-cd-workflows/#saving-credentials-on-github). We recommend using a scoped token for security reasons.
8383

8484
- You can store the token as `PYPI_TOKEN` in your fork's `Secrets`.
85-
8685
- Advanced usage: if you are releasing multiple repos, you can create a secret named `PYPI_TOKEN_MAP` instead of `PYPI_TOKEN` that is formatted as follows:
8786

8887
```text

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,8 @@
8080
"three": "^0.162.0",
8181
"ts-xor": "^1.3.0",
8282
"unique-names-generator": "^4.7.1",
83-
"use-file-picker": "^2.1.2"
83+
"use-file-picker": "^2.1.2",
84+
"zustand": "^4.5.2"
8485
},
8586
"devDependencies": {
8687
"@jupyterlab/builder": "^4.0.0",

src/arCube.ts

Lines changed: 45 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,4 @@
1-
import {
2-
selectAppData,
3-
selectIsSomeoneScreenSharing
4-
} from '@100mslive/react-sdk';
1+
import { selectIsSomeoneScreenSharing } from '@100mslive/react-sdk';
52
//@ts-expect-error AR.js doesn't have type definitions
63
import * as THREEx from '@ar-js-org/ar.js/three.js/build/ar-threex.js';
74
import { IThemeManager } from '@jupyterlab/apputils';
@@ -10,9 +7,9 @@ import { ISignal, Signal } from '@lumino/signaling';
107
import * as THREE from 'three';
118
import { RoundedBoxGeometry } from 'three/examples/jsm/geometries/RoundedBoxGeometry';
129
import { GLTF, GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader';
13-
import { APP_DATA } from './constants';
14-
import { hmsActions, hmsStore } from './hms';
10+
import { hmsStore } from './hms';
1511
import { IModelRegistryData } from './registry';
12+
import { useCubeStore } from './store';
1613

1714
const FIRST_SCENE = 0;
1815
const SECOND_SCENE = 1;
@@ -29,19 +26,9 @@ class ArCube {
2926
* Construct a new JupyterLab-Gather widget.
3027
*/
3128
constructor() {
32-
this.secondSceneSignal = new Signal(this);
3329
this.scaleSignal = new Signal(this);
3430
this.bgCubeCenter = new THREE.Vector3();
3531
this.initialize();
36-
37-
// this.animate();
38-
// window.addEventListener('markerFound', () => {
39-
// console.log('Marker found');
40-
// });
41-
42-
// window.addEventListener('markerLost', () => {
43-
// console.log('Marker lost');
44-
// });
4532
}
4633

4734
modelInScene: string[];
@@ -66,59 +53,45 @@ class ArCube {
6653
resolve: any;
6754
deltaTime: number;
6855
totalTime: number;
69-
okToLoadModel: boolean;
7056
renderTarget: THREE.WebGLRenderTarget;
7157
sceneGroups: THREE.Group[];
7258
isSecondScene: boolean;
7359
bgCubeBoundingBox: THREE.Box3;
7460
readonly existingWebcam: HTMLVideoElement | null;
7561
readonly newWebcam: HTMLVideoElement | undefined;
76-
readonly secondSceneSignal: Signal<this, boolean>;
7762
readonly scaleSignal: Signal<this, IScaleSignal>;
7863
bgCubeCenter: THREE.Vector3;
7964
arjsVid: HTMLElement | null;
80-
// sceneGroup: THREE.Group;
81-
// sceneGroupArray: THREE.Group[];
82-
// edgeGroup: THREE.Group;
83-
// gltfModel: THREE.Group;
84-
// observer: IntersectionObserver;
85-
// readonly markerControls: any;
86-
// readonly ambientLight: THREE.AmbientLight;
87-
// readonly rotationArray: THREE.Vector3[];
88-
// readonly markerRoot: THREE.Group;
89-
// readonly markerGroup: THREE.Group;
90-
// readonly pointLight: THREE.PointLight;
91-
// readonly loader: THREE.TextureLoader;
92-
// readonly stageMesh: THREE.MeshBasicMaterial;
93-
// readonly stage: THREE.Mesh;
94-
// readonly edgeGeometry: THREE.CylinderGeometry;
95-
// readonly edgeCenters: THREE.Vector3[];
96-
// readonly edgeRotations: THREE.Vector3[];
97-
// readonly animationRequestId: number | undefined;
98-
// readonly now: number;
99-
// readonly then: number;
100-
// readonly elapsed: number;
101-
// readonly fpsInterval: number;
102-
// readonly webcamFromArjs: HTMLElement | null;
103-
// model: IModelRegistryData;
65+
videoDeviceIdUnsub: () => void;
66+
isSecondSceneUnsub: () => void;
10467
themeChangedSignal: ISignal<
10568
IThemeManager,
10669
IChangedArgs<string, string | null>
107-
>;
70+
> | null;
10871

10972
initialize() {
11073
this.sceneGroups = [];
11174
this.modelInScene = new Array(2);
11275
this.scenesWithModel = {};
113-
hmsStore.subscribe(
114-
this.setupSource.bind(this),
115-
selectAppData(APP_DATA.videoDeviceId)
76+
77+
this.videoDeviceIdUnsub = useCubeStore.subscribe(
78+
state => state.videoDeviceId,
79+
videoDeviceId => {
80+
console.log('dev - videoDeviceId', videoDeviceId);
81+
this.setupSource();
82+
}
11683
);
11784

118-
this.themeChangedSignal = hmsStore.getState(
119-
selectAppData(APP_DATA.themeChanged)
85+
this.isSecondSceneUnsub = useCubeStore.subscribe(
86+
state => state.isSecondScene,
87+
isSecondScene => (this.isSecondScene = isSecondScene)
12088
);
121-
this.themeChangedSignal.connect(this.handleThemeChange.bind(this));
89+
90+
this.themeChangedSignal = useCubeStore.getState().themeChangedSignal;
91+
92+
this.themeChangedSignal
93+
? this.themeChangedSignal.connect(this.handleThemeChange.bind(this))
94+
: console.log('Theme change signal not found');
12295

12396
this.setupThreeStuff();
12497

@@ -131,10 +104,21 @@ class ArCube {
131104
this.setupScene(FIRST_SCENE);
132105
}
133106

107+
cleanUp() {
108+
this.videoDeviceIdUnsub();
109+
this.isSecondSceneUnsub();
110+
111+
useCubeStore.setState({
112+
canLoadModel: true,
113+
modelInScene: [],
114+
scenesWithModel: {},
115+
isSecondScene: false
116+
});
117+
}
118+
134119
setupThreeStuff() {
135120
console.log('setting up three stuff');
136121

137-
this.okToLoadModel = true;
138122
this.scene = new THREE.Scene();
139123

140124
// promise to track if AR.js has loaded the webcam
@@ -199,13 +183,11 @@ class ArCube {
199183
this.clock = new THREE.Clock();
200184
this.deltaTime = 0;
201185
this.totalTime = 0;
202-
203-
hmsActions.setAppData(APP_DATA.renderer, this.renderer);
204186
}
205187

206188
setupSource() {
207189
console.log('setting up source');
208-
const deviceId = hmsStore.getState(selectAppData(APP_DATA.videoDeviceId));
190+
const deviceId = useCubeStore.getState().videoDeviceId;
209191

210192
this.arToolkitSource = new THREEx.ArToolkitSource({
211193
sourceType: 'webcam',
@@ -228,22 +210,6 @@ class ArCube {
228210
return cubeColorValue;
229211
}
230212

231-
// updateSource() {
232-
// const deviceId = hmsStore.getState(selectAppData('videoDeviceId'));
233-
234-
// this.arToolkitSource = new THREEx.ArToolkitSource({
235-
// sourceType: 'webcam',
236-
// deviceId
237-
// });
238-
239-
// this.arjsVid = document.getElementById('arjs-video');
240-
241-
// if (this.arjsVid) {
242-
// this.arjsVid.remove();
243-
// }
244-
// this.arToolkitSource.init();
245-
// }
246-
247213
setupContext() {
248214
console.log('setting up context');
249215

@@ -390,10 +356,9 @@ class ArCube {
390356
// }
391357

392358
// load model
393-
this.okToLoadModel = false;
394-
hmsActions.setAppData(APP_DATA.canLoadModel, false);
359+
useCubeStore.setState({ canLoadModel: false });
395360

396-
if ('url' in model) {
361+
if ('url' in model!) {
397362
this.gltfLoader.load(
398363
model.url,
399364
gltf => {
@@ -406,7 +371,7 @@ class ArCube {
406371
console.log('Error loading model url', error);
407372
}
408373
);
409-
} else if ('gltf' in model) {
374+
} else if ('gltf' in model!) {
410375
// const data = JSON.stringify(model.gltf);
411376
const data = model.gltf;
412377
this.gltfLoader.parse(
@@ -462,7 +427,6 @@ class ArCube {
462427

463428
// add model to scene
464429
this.sceneGroups[sceneNumber].add(gltfModel);
465-
this.okToLoadModel = true;
466430

467431
// Track which scenes a model is loaded in
468432
// This is mostly to reflect changes to a model in JupyterCAD if it's loaded in multiple scenes
@@ -484,10 +448,11 @@ class ArCube {
484448
// Track which model is loaded in which scene
485449
// This is to get model names on the scale sliders
486450
this.modelInScene[sceneNumber] = modelName;
451+
useCubeStore.setState({ modelInScene: this.modelInScene });
487452

488453
// update app data state
489-
hmsActions.setAppData(APP_DATA.loadedModels, updatedScenesWithModel);
490-
hmsActions.setAppData(APP_DATA.canLoadModel, true);
454+
useCubeStore.setState({ canLoadModel: true });
455+
useCubeStore.setState({ scenesWithModel: updatedScenesWithModel });
491456

492457
// Send scale value to right sidebar
493458
this.scaleSignal.emit({ sceneNumber, scale: minRatio });
@@ -522,15 +487,14 @@ class ArCube {
522487
}
523488

524489
findModelByName(name: string) {
525-
const modelRegistry = hmsStore.getState(
526-
selectAppData(APP_DATA.modelRegistry)
527-
);
490+
const modelRegistry = useCubeStore.getState().modelRegistry;
528491
return modelRegistry.find(
529492
(model: IModelRegistryData) => model.name === name
530493
);
531494
}
532495

533496
changeModelInScene(sceneNumber: number, modelName: string) {
497+
console.log('dev - change model in scene', sceneNumber, modelName);
534498
// update tracking stuff
535499
const modelNameToRemove = this.modelInScene[sceneNumber];
536500
const updatedModels = { ...this.scenesWithModel };
@@ -558,14 +522,12 @@ class ArCube {
558522

559523
enableSecondScene() {
560524
console.log('enabling second');
561-
this.isSecondScene = true;
562525
this.setupScene(SECOND_SCENE);
563-
this.secondSceneSignal.emit(true);
526+
useCubeStore.setState({ isSecondScene: true });
564527
}
565528

566529
disableSecondScene() {
567530
console.log('disabling second');
568-
this.isSecondScene = false;
569531
//TODO this won't work with more than two scenes but it's fine for now
570532
this.sceneGroups.pop();
571533

@@ -575,7 +537,7 @@ class ArCube {
575537
}
576538
});
577539

578-
this.secondSceneSignal.emit(false);
540+
useCubeStore.setState({ isSecondScene: false });
579541
}
580542

581543
resizeCanvasToDisplaySize() {

src/arCubePlugin.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,7 @@ import {
66
} from '@100mslive/hms-video-store';
77
import * as THREE from 'three';
88
import ArCube from './arCube';
9-
import { APP_DATA } from './constants';
10-
import { hmsActions } from './hms';
9+
import { useCubeStore } from './store';
1110

1211
class ArCubePlugin implements HMSVideoPlugin {
1312
input: HTMLCanvasElement | null;
@@ -147,7 +146,7 @@ class ArCubePlugin implements HMSVideoPlugin {
147146

148147
async init() {
149148
this.arCube = new ArCube();
150-
hmsActions.setAppData(APP_DATA.arCube, this.arCube);
149+
useCubeStore.setState({ arCube: this.arCube });
151150

152151
this.arCube.animate();
153152
}
@@ -161,6 +160,8 @@ class ArCubePlugin implements HMSVideoPlugin {
161160
}
162161

163162
stop() {
163+
useCubeStore.setState({ arCube: null });
164+
164165
// Remove video element added by AR.js
165166
const video = document.getElementById('arjs-video') as HTMLVideoElement;
166167

src/components/MainDisplay.tsx

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import {
2-
selectAppData,
32
selectIsConnectedToRoom,
43
selectSessionStore,
54
useHMSActions,
@@ -8,11 +7,12 @@ import {
87
import { IStateDB } from '@jupyterlab/statedb';
98
import React, { useEffect } from 'react';
109

11-
import { APP_DATA, SESSION_STORE } from '../constants';
10+
import { SESSION_STORE } from '../constants';
1211
import GridView from '../layouts/GridView';
1312
import JoinFormView from '../layouts/JoinFormView';
1413
import PresenterView from '../layouts/PresenterView';
1514
import PreviewView from '../layouts/PreviewView';
15+
import { useCubeStore } from '../store';
1616
import ControlBar from './ControlBar';
1717

1818
interface IMainDisplayProps {
@@ -22,14 +22,16 @@ interface IMainDisplayProps {
2222
export const MainDisplay = ({ state }: IMainDisplayProps) => {
2323
const hmsActions = useHMSActions();
2424
const isConnected = useHMSStore(selectIsConnectedToRoom);
25-
const isConnecting = useHMSStore(selectAppData(APP_DATA.isConnecting));
2625
const isPresenting = useHMSStore(
2726
selectSessionStore(SESSION_STORE.isPresenting)
2827
);
2928

29+
const isConnecting = useCubeStore.use.isConnecting();
30+
const updateIsConnecting = useCubeStore.use.updateIsConnecting();
31+
3032
useEffect(() => {
3133
if (isConnected) {
32-
hmsActions.setAppData(APP_DATA.isConnecting, false);
34+
updateIsConnecting(false);
3335
}
3436
}, [isConnected]);
3537

src/components/buttons/PluginButton.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
1313
import React, { useEffect, useState } from 'react';
1414
import ArCubePlugin from '../../arCubePlugin';
1515
import { SESSION_STORE } from '../../constants';
16+
import { useCubeStore } from '../../store';
1617

1718
const PluginButton = () => {
1819
const hmsActions = useHMSActions();
@@ -33,6 +34,8 @@ const PluginButton = () => {
3334
const [isDisabled, setIsDisabled] = useState(false);
3435
const [prevRole, setPrevRole] = useState<HMSRole>();
3536

37+
const arCube = useCubeStore.use.arCube();
38+
3639
useEffect(() => {
3740
hmsActions.sessionStore.observe(SESSION_STORE.isPresenting);
3841
hmsActions.sessionStore.observe(SESSION_STORE.presenterId);
@@ -62,6 +65,7 @@ const PluginButton = () => {
6265
// togglePresenterRole(prevRole?.name);
6366
hmsActions.sessionStore.set(SESSION_STORE.isPresenting, false);
6467
hmsActions.sessionStore.set(SESSION_STORE.presenterId, '');
68+
arCube?.cleanUp();
6569

6670
await hmsActions.removePluginFromVideoTrack(arPlugin);
6771
}

src/components/modals/AddNewFileModal.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import {
55
uniqueNamesGenerator
66
} from 'unique-names-generator';
77
import { useFilePicker } from 'use-file-picker';
8+
//@ts-expect-error no types
89
import { FileTypeValidator } from 'use-file-picker/validators';
910
import { IModelRegistryData } from '../../registry';
1011
import Modal from './Modal';

0 commit comments

Comments
 (0)