Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

✨ improved the core demo to use typesciript interfaces fully #603

Merged
merged 3 commits into from
Feb 21, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ on:
- pull_request
jobs:
lint:
runs-on: ubuntu-18.04
runs-on: ubuntu-20.04
steps:
# Action Repo: https://github.com/actions/checkout
- name: 'Checkout repo'
Expand All @@ -28,7 +28,7 @@ jobs:
- name:
run: npm run lint
test:
runs-on: ubuntu-18.04
runs-on: ubuntu-20.04
needs: lint
steps:
- name: 'Checkout repo'
Expand Down
226 changes: 226 additions & 0 deletions apps/demo-core/src/app.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,226 @@
import { ThebeEvents, ThebeEventType, ThebeSession } from 'thebe-core';
import { shortId, ThebeNotebook, makeConfiguration, ThebeServer } from 'thebe-core';
import { code, options } from './setup';

export type ServerType = 'local' | 'lite' | 'binder';
export type ExampleType = 'basic' | 'ipywidgets' | 'ipyleaflet' | 'tqdm';

class App {
options: Record<string, any>;
serverType: ServerType;
exampleType: ExampleType;

localMessageEl: HTMLDivElement;
connectEl: HTMLDivElement;
typeLocalEl: HTMLInputElement;
typeLiteEl: HTMLInputElement;
typeBinderEl: HTMLInputElement;
exampleBasicEl: HTMLElement;
exampleIpywidgetsEl: HTMLElement;
exampleIpyleafletEl: HTMLElement;

runAllButtonEl: HTMLElement;
runLastButtonEl: HTMLElement;
restartButtonEl: HTMLElement;

serverStatusEl: HTMLElement;
sessionStatusEl: HTMLElement;
thebeErrorEl: HTMLElement;

outputEl: HTMLElement;

events: ThebeEvents;
server: ThebeServer | null;
session: ThebeSession | null;
notebook: ThebeNotebook | null;

constructor() {
this.localMessageEl = document.getElementById('local-message') as HTMLDivElement;
this.connectEl = document.getElementById('connect') as HTMLDivElement;
this.typeLocalEl = document.getElementById('local') as HTMLInputElement;
this.typeLiteEl = document.getElementById('lite') as HTMLInputElement;
this.typeBinderEl = document.getElementById('binder') as HTMLInputElement;
this.exampleBasicEl = document.getElementById('basic') as HTMLInputElement;
this.exampleIpywidgetsEl = document.getElementById('ipywidgets') as HTMLInputElement;
this.exampleIpyleafletEl = document.getElementById('ipyleaflet') as HTMLInputElement;

this.runAllButtonEl = document.getElementById('run-all') as HTMLInputElement;
this.runLastButtonEl = document.getElementById('run-last') as HTMLInputElement;
this.restartButtonEl = document.getElementById('restart') as HTMLInputElement;

this.serverStatusEl = document.getElementById('server-status') as HTMLSpanElement;
this.sessionStatusEl = document.getElementById('session-status') as HTMLSpanElement;
this.thebeErrorEl = document.getElementById('thebe-error') as HTMLSpanElement;

this.outputEl = document.querySelector('[data-output]') as HTMLDivElement;

this.serverType = 'local';
this.exampleType = 'basic';
this.options = options.local;

this.events = new ThebeEvents();
this.server = null;
this.session = null;
this.notebook = null;

this.setupUI();
}

handleServerTypeChange(evt: MouseEvent) {
console.log('handleServerTypeChange', this);
if ((evt.target as Element).id === 'local') {
this._showLocalMessage();
this.options = options.local;
this.serverType = 'local';
} else if ((evt.target as Element).id === 'lite') {
this._hideLocalMessage();
this.options = options.lite;
this.serverType = 'lite';
} else if ((evt.target as Element).id === 'binder') {
this._hideLocalMessage();
this.options = options.binder;
this.serverType = 'binder';
}
this.resetUI();
this.disconnect();
}

handleExampleTypeChange(evt: MouseEvent) {
if ((evt.target as Element).id === 'basic') {
this.resetUI();
this.exampleType = 'basic';
} else if ((evt.target as Element).id === 'ipywidgets') {
this.resetUI();
this.exampleType = 'ipywidgets';
} else if ((evt.target as Element).id === 'ipyleaflet') {
this.resetUI();
this.exampleType = 'ipyleaflet';
}
this._writeCode(code[this.exampleType]);
}

setupUI() {
this.connectEl.onclick = () => {
this.connectEl.setAttribute('disabled', 'true');
this.connect();
};

this.typeLocalEl.onclick = this.handleServerTypeChange.bind(this);
this.typeLiteEl.onclick = this.handleServerTypeChange.bind(this);
this.typeBinderEl.onclick = this.handleServerTypeChange.bind(this);

this.exampleBasicEl.onclick = this.handleExampleTypeChange.bind(this);
this.exampleIpywidgetsEl.onclick = this.handleExampleTypeChange.bind(this);
this.exampleIpyleafletEl.onclick = this.handleExampleTypeChange.bind(this);

this.runAllButtonEl.onclick = () => this.notebook?.executeAll();
this.runLastButtonEl.onclick = () => this.notebook?.executeOnly(this.notebook.lastCell().id);
this.restartButtonEl.onclick = () => this.session?.restart();

this.events.on(ThebeEventType.status, (evt, data) => {
const { subject, status } = data;
if (subject === 'server') this.serverStatusEl.innerText = status as string;
if (subject === 'session') this.sessionStatusEl.innerText = status as string;
});

this.events.on(ThebeEventType.error, (evt, data) => {
const { subject, message } = data;
console.error(evt, data);
this.thebeErrorEl.innerText = `${[subject]}: ${message}`;
});

this._writeCode(code[this.exampleType]);
}

_showLocalMessage() {
this.localMessageEl.style.visibility = 'visible';
this.localMessageEl.style.height = 'auto';
}

_hideLocalMessage() {
this.localMessageEl.style.visibility = 'hidden';
this.localMessageEl.style.height = '0px';
}

_enableCellControls() {
this.runAllButtonEl.removeAttribute('disabled');
this.runLastButtonEl.removeAttribute('disabled');
this.restartButtonEl.removeAttribute('disabled');
}

_disableCellControls() {
this.runAllButtonEl.setAttribute('disabled', 'true');
this.runLastButtonEl.setAttribute('disabled', 'true');
this.restartButtonEl.setAttribute('disabled', 'true');
}

_writeCode(source: string[]) {
const codeArea = document.getElementById('code_area');
if (codeArea) {
codeArea.innerHTML = '';
source.forEach((block) => {
const node = document.createElement('pre');
node.innerHTML = block.trim();
codeArea.appendChild(node);
});
}
}

resetUI() {
this.connectEl.removeAttribute('disabled');
document.getElementById('run-all')?.setAttribute('disabled', 'true');
document.getElementById('run-last')?.setAttribute('disabled', 'true');
document.getElementById('restart')?.setAttribute('disabled', 'true');

const dataOutput = document.getElementById('data-output');
if (dataOutput) dataOutput.innerHTML = '<img src="output.png" />';

const serverStatus = document.getElementById('server-status');
if (serverStatus) serverStatus.innerHTML = 'unknown';

const sessionStatus = document.getElementById('session-status');
if (sessionStatus) sessionStatus.innerHTML = 'unknown';

this.thebeErrorEl.innerText = '';
}

async connect() {
this.server = new ThebeServer(makeConfiguration(this.options, this.events));
if (this.serverType === 'binder') {
await this.server?.connectToServerViaBinder();
} else if (this.serverType === 'lite') {
await this.server?.connectToJupyterLiteServer();
} else {
// local
await this.server?.connectToJupyterServer();
}

this.session = await this.server.startNewSession();

this.notebook = ThebeNotebook.fromCodeBlocks(
code[this.exampleType].map((source) => ({
id: shortId(),
source,
})),
this.server.config,
);

if (this.session == null) console.error('could not start session');
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
this.notebook.attachSession(this.session!);
const last = this.notebook.lastCell();
last.attachToDOM(this.outputEl);

this._enableCellControls();
}

async disconnect() {
await this.session?.shutdown();
this.server = null;
this.session = null;
this.notebook = null;
this._disableCellControls();
}
}

export default App;
7 changes: 6 additions & 1 deletion apps/demo-core/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
import { setupThebeCore } from 'thebe-core';
import App from './app';

setupThebeCore();
document.addEventListener('DOMContentLoaded', async function () {
console.log('Starting Demo... loading App...');
setupThebeCore();
(window as any).app = new App();
});
129 changes: 129 additions & 0 deletions apps/demo-core/src/setup.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
export const code = {
basic: [
`
%matplotlib inline
import numpy as np
import matplotlib.pyplot as plt`,
`fs = 1
phi = np.pi/5`,
`plt.ion()
fig, ax = plt.subplots()
x = np.arange(-np.pi/2,np.pi/2,0.01)
y = np.cos(2*np.pi*fs*x+phi)
yy = np.sin(2*np.pi*fs*x+phi)
ax.plot(x,y);
ax.plot(x,yy);
plt.grid(True)`,
],
ipywidgets: [
`%matplotlib widget
import ipywidgets as widgets
import matplotlib.pyplot as plt
import numpy as np
from ipywidgets import Output, FloatSlider, Layout
from IPython.display import clear_output
w_output = Output(layout=Layout(border="1px solid blue"))

x = np.linspace(0,10, 100)
w = 1.3
amp = 1.0

w_w = FloatSlider(min=0.1, max=5.0, step=0.05, value=0.8, description="W")
w_a = FloatSlider(min=0.1, max=3.0, step=0.1, value=1.0, description="Amplitude")

props = dict(
linewidth=1
)

#plt.ioff()
fig_two, ax_two = plt.subplots(1,1)
#plt.ion()

with w_output:
display(fig_two.canvas)

def on_pick(evt):
print(evt)
props["linewidth"] = props["linewidth"] + 1
redraw_plot({})

cid = fig_two.canvas.mpl_connect('pick_event', on_pick)

def sine_func(x, w, amp):
return amp*np.sin(w*x)

def redraw_plot(evt):
with w_output:
ax_two.cla()
ax_two.set_ylim(-4, 4)
ax_two.plot(x, sine_func(x, w_w.value, w_a.value), **props, picker=True)
clear_output()

redraw_plot({})

w_w.observe(redraw_plot, names=["value"])
w_a.observe(redraw_plot, names=["value"])

print("")
print("Move the sliders to change the waveform")
print("Click on the waverform to increase the linewidth.")
display(w_w)
display(w_a)`,
],
ipyleaflet: [
`from ipyleaflet import Map
from ipywidgets import Layout
# Collect common parameters
zoom = 8
place = None

# Create ipyleaflet tile layer from that server

places = dict(
orotava=dict(center=(28.389216, -16.520283), zoom=12),
pico_tiede=dict(center=(28.272401, -16.642457), zoom=12),
santa_cruz=dict(center=(28.466965, -16.249938), zoom=12)
)

center = places[place]["center"] if place is not None else (28.586850, -15.648742)
zoom = places[place]["zoom"] if place is not None else zoom

# Create ipyleaflet map, add layers, add controls, and display
m = Map(center=center, zoom=zoom, layout=Layout( height='800px'))
m`,
],
tqdm: [
`from tqdm.auto import tqdm
from time import sleep

for i in tqdm(range(100)):
sleep(0.05)`,
],
};

export const options = {
lite: {
requestKernel: true,
kernelOptions: {
name: 'python',
kernelName: 'python',
},
},
local: {
kernelOptions: {
name: 'python3',
serverSettings: {
appendToken: true,
baseUrl: 'http://localhost:8888',
token: 'test-secret',
},
},
},
binder: {
binderOptions: {
repo: 'stevejpurves/ipympl-binder-base',
ref: 'main',
binderUrl: 'https://mybinder.org',
},
},
};
2 changes: 1 addition & 1 deletion apps/demo-core/static/demo.css
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ body > div {
}
pre {
border: 1px gray solid;
max-width: 500px;
width: fit-content;
padding: 10px;
}
button {
Expand Down
Loading