Skip to content

Commit d526259

Browse files
authored
Support JupyterLite (#331)
* Make IDrive optional * Update dirty flag * Refactor doc model * Add lite deploy * Update build script
1 parent 2096747 commit d526259

File tree

15 files changed

+530
-418
lines changed

15 files changed

+530
-418
lines changed

.github/workflows/build.yml

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -195,3 +195,65 @@ jobs:
195195
- uses: actions/checkout@v3
196196
- uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1
197197
- uses: jupyterlab/maintainer-tools/.github/actions/check-links@v1
198+
199+
build-lite:
200+
name: Build JupyterLite
201+
needs: integration-tests
202+
runs-on: ubuntu-latest
203+
204+
steps:
205+
- name: Checkout
206+
uses: actions/checkout@v3
207+
208+
- name: Install Conda environment with Micromamba
209+
uses: mamba-org/setup-micromamba@v1
210+
with:
211+
micromamba-version: '1.5.5-0'
212+
environment-name: build-env
213+
create-args: >-
214+
python=3.10
215+
pip
216+
jupyterlite-core>=0.2.0,<0.3.0
217+
jupyterlite-xeus>=0.1.2,<0.2
218+
219+
- name: Download extension package
220+
uses: actions/download-artifact@v3
221+
with:
222+
name: extension-artifacts
223+
224+
- name: Install the extension
225+
shell: bash -l {0}
226+
run: |
227+
set -eux
228+
cp ./jupytercad_core/dist/jupytercad*.whl ./jupytercad_lab/dist/jupytercad*.whl ./jupytercad_app/dist/jupytercad*.whl .
229+
python -m pip install jupytercad*.whl
230+
231+
- name: Build the lite site
232+
shell: bash -l {0}
233+
working-directory: lite
234+
run: |
235+
set -eux
236+
mkdir -p content && cp ../examples/test.jcad ./content
237+
jupyter lite build --contents content --output-dir dist
238+
239+
- name: Upload artifact
240+
uses: actions/upload-pages-artifact@v1
241+
with:
242+
path: ./lite/dist
243+
244+
deploy:
245+
needs: build-lite
246+
if: github.ref == 'refs/heads/main'
247+
permissions:
248+
pages: write
249+
id-token: write
250+
251+
environment:
252+
name: github-pages
253+
url: ${{ steps.deployment.outputs.page_url }}
254+
255+
runs-on: ubuntu-latest
256+
steps:
257+
- name: Deploy to GitHub Pages
258+
id: deployment
259+
uses: actions/deploy-pages@v1

examples/test.jcad

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -97,9 +97,7 @@
9797
}
9898
}
9999
],
100-
"options": {
101-
"foo": 1
102-
},
100+
"options": {},
103101
"metadata": {},
104102
"outputs": {}
105103
}

lite/environment.yml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
name: xeus-python-kernel
2+
channels:
3+
- https://repo.mamba.pm/emscripten-forge
4+
- conda-forge
5+
dependencies:
6+
- xeus-python

lite/jupyter-lite.json

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"jupyter-lite-schema-version": 0,
3+
"jupyter-config-data": {
4+
"appName": "My JupyterCAD App",
5+
"disabledExtensions": ["@jupyter/collaboration-extension"]
6+
}
7+
}

packages/base/src/mainview.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -796,17 +796,17 @@ export class MainView extends React.Component<IProps, IStates> {
796796
}
797797
}
798798

799-
private _createPointer(user: User.IIdentity): BasicMesh {
799+
private _createPointer(user?: User.IIdentity): BasicMesh {
800800
let clientColor: Color.RGBColor | null = null;
801801

802-
if (user.color?.startsWith('var')) {
802+
if (user?.color?.startsWith('var')) {
803803
clientColor = Color.color(
804804
getComputedStyle(document.documentElement).getPropertyValue(
805805
user.color.slice(4, -1)
806806
)
807807
) as Color.RGBColor;
808808
} else {
809-
clientColor = Color.color(user.color) as Color.RGBColor;
809+
clientColor = Color.color(user?.color ?? 'steelblue') as Color.RGBColor;
810810
}
811811

812812
const material = new THREE.MeshBasicMaterial({

packages/schema/src/doc.ts

Lines changed: 248 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,248 @@
1+
import { MapChange, YDocument } from '@jupyter/ydoc';
2+
import { JSONExt, JSONObject } from '@lumino/coreutils';
3+
import { ISignal, Signal } from '@lumino/signaling';
4+
import * as Y from 'yjs';
5+
6+
import { IJCadObject, IJCadOptions } from './_interface/jcad';
7+
import {
8+
IDict,
9+
IJcadObjectDocChange,
10+
IJupyterCadDoc,
11+
IJupyterCadDocChange,
12+
IPostResult
13+
} from './interfaces';
14+
15+
export class JupyterCadDoc
16+
extends YDocument<IJupyterCadDocChange>
17+
implements IJupyterCadDoc
18+
{
19+
constructor() {
20+
super();
21+
22+
this._options = this.ydoc.getMap<Y.Map<any>>('options');
23+
this._objects = this.ydoc.getArray<Y.Map<any>>('objects');
24+
this._metadata = this.ydoc.getMap<string>('metadata');
25+
this._outputs = this.ydoc.getMap<IPostResult>('outputs');
26+
this.undoManager.addToScope(this._objects);
27+
28+
this._objects.observeDeep(this._objectsObserver);
29+
this._metadata.observe(this._metaObserver);
30+
this._options.observe(this._optionsObserver);
31+
}
32+
33+
dispose(): void {
34+
super.dispose();
35+
}
36+
37+
get version(): string {
38+
return '0.1.0';
39+
}
40+
41+
get objects(): Array<IJCadObject> {
42+
const objs = this._objects.map(
43+
obj => JSONExt.deepCopy(obj.toJSON()) as IJCadObject
44+
);
45+
return objs;
46+
}
47+
48+
get options(): JSONObject {
49+
return JSONExt.deepCopy(this._options.toJSON());
50+
}
51+
52+
get metadata(): JSONObject {
53+
return JSONExt.deepCopy(this._metadata.toJSON());
54+
}
55+
56+
get outputs(): JSONObject {
57+
return JSONExt.deepCopy(this._outputs.toJSON());
58+
}
59+
60+
get objectsChanged(): ISignal<IJupyterCadDoc, IJcadObjectDocChange> {
61+
return this._objectsChanged;
62+
}
63+
64+
get optionsChanged(): ISignal<IJupyterCadDoc, MapChange> {
65+
return this._optionsChanged;
66+
}
67+
68+
get metadataChanged(): ISignal<IJupyterCadDoc, MapChange> {
69+
return this._metadataChanged;
70+
}
71+
72+
objectExists(name: string): boolean {
73+
return Boolean(this._getObjectAsYMapByName(name));
74+
}
75+
76+
getObjectByName(name: string): IJCadObject | undefined {
77+
const obj = this._getObjectAsYMapByName(name);
78+
if (obj) {
79+
return JSONExt.deepCopy(obj.toJSON()) as IJCadObject;
80+
}
81+
return undefined;
82+
}
83+
84+
removeObjectByName(name: string): void {
85+
let index = 0;
86+
for (const obj of this._objects) {
87+
if (obj.get('name') === name) {
88+
break;
89+
}
90+
index++;
91+
}
92+
93+
if (this._objects.length > index) {
94+
this.transact(() => {
95+
this._objects.delete(index);
96+
const guidata = this.getOption('guidata');
97+
if (guidata) {
98+
delete guidata[name];
99+
this.setOption('guidata', guidata);
100+
}
101+
this.removeOutput(name);
102+
});
103+
}
104+
}
105+
106+
addObject(value: IJCadObject): void {
107+
this.addObjects([value]);
108+
}
109+
110+
addObjects(value: Array<IJCadObject>): void {
111+
this.transact(() => {
112+
value.map(obj => {
113+
if (!this.objectExists(obj.name)) {
114+
this._objects.push([new Y.Map(Object.entries(obj))]);
115+
} else {
116+
console.error('There is already an object with the name:', obj.name);
117+
}
118+
});
119+
});
120+
}
121+
122+
updateObjectByName(name: string, key: string, value: any): void {
123+
const obj = this._getObjectAsYMapByName(name);
124+
if (!obj) {
125+
return;
126+
}
127+
this.transact(() => obj.set(key, value));
128+
}
129+
130+
getOption(key: keyof IJCadOptions): IDict | undefined {
131+
const content = this._options.get(key);
132+
if (!content) {
133+
return;
134+
}
135+
return JSONExt.deepCopy(content) as IDict;
136+
}
137+
138+
setOption(key: keyof IJCadOptions, value: IDict): void {
139+
this.transact(() => void this._options.set(key, value));
140+
}
141+
142+
setOptions(options: IJCadOptions): void {
143+
this.transact(() => {
144+
for (const [key, value] of Object.entries(options)) {
145+
this._options.set(key, value);
146+
}
147+
});
148+
}
149+
150+
getMetadata(key: string): string | undefined {
151+
return this._metadata.get(key);
152+
}
153+
154+
setMetadata(key: string, value: string): void {
155+
this.transact(() => void this._metadata.set(key, value));
156+
}
157+
158+
removeMetadata(key: string): void {
159+
if (this._metadata.has(key)) {
160+
this._metadata.delete(key);
161+
}
162+
}
163+
164+
getOutput(key: string): IPostResult | undefined {
165+
return this._outputs.get(key);
166+
}
167+
168+
setOutput(key: string, value: IPostResult): void {
169+
this.transact(() => void this._outputs.set(key, value));
170+
}
171+
172+
removeOutput(key: string): void {
173+
if (this._outputs.has(key)) {
174+
this._outputs.delete(key);
175+
}
176+
}
177+
178+
setShapeMeta(name: string, meta?: IDict): void {
179+
const obj = this._getObjectAsYMapByName(name);
180+
if (meta && obj) {
181+
this.transact(() => void obj.set('shapeMetadata', meta));
182+
}
183+
}
184+
185+
static create(): IJupyterCadDoc {
186+
return new JupyterCadDoc();
187+
}
188+
189+
editable = true;
190+
exportable = false;
191+
192+
private _getObjectAsYMapByName(name: string): Y.Map<any> | undefined {
193+
for (const obj of this._objects) {
194+
if (obj.get('name') === name) {
195+
return obj;
196+
}
197+
}
198+
return undefined;
199+
}
200+
201+
private _objectsObserver = (events: Y.YEvent<any>[]): void => {
202+
const changes: Array<{
203+
name: string;
204+
key: keyof IJCadObject;
205+
newValue: IJCadObject;
206+
}> = [];
207+
let needEmit = false;
208+
events.forEach(event => {
209+
const name = event.target.get('name');
210+
211+
if (name) {
212+
event.keys.forEach((change, key) => {
213+
if (!needEmit && key !== 'shapeMetadata') {
214+
needEmit = true;
215+
}
216+
changes.push({
217+
name,
218+
key: key as any,
219+
newValue: JSONExt.deepCopy(event.target.toJSON())
220+
});
221+
});
222+
}
223+
});
224+
needEmit = changes.length === 0 ? true : needEmit;
225+
if (needEmit) {
226+
this._objectsChanged.emit({ objectChange: changes });
227+
}
228+
this._changed.emit({ objectChange: changes });
229+
};
230+
231+
private _metaObserver = (event: Y.YMapEvent<string>): void => {
232+
this._metadataChanged.emit(event.keys);
233+
};
234+
235+
private _optionsObserver = (event: Y.YMapEvent<Y.Map<string>>): void => {
236+
this._optionsChanged.emit(event.keys);
237+
};
238+
239+
private _objects: Y.Array<Y.Map<any>>;
240+
private _options: Y.Map<any>;
241+
private _metadata: Y.Map<string>;
242+
private _outputs: Y.Map<IPostResult>;
243+
private _metadataChanged = new Signal<IJupyterCadDoc, MapChange>(this);
244+
private _optionsChanged = new Signal<IJupyterCadDoc, MapChange>(this);
245+
private _objectsChanged = new Signal<IJupyterCadDoc, IJcadObjectDocChange>(
246+
this
247+
);
248+
}

packages/schema/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
export * from './interfaces';
22
export * from './model';
33
export * from './token';
4+
export * from './doc';

0 commit comments

Comments
 (0)