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

🪐 Thebe integration #21

Merged
merged 41 commits into from
Apr 26, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
42b9ff4
WIP
rowanc1 Apr 19, 2023
9abd19c
👆🏻moved core provider up to Root, trigger intial load from first outp…
stevejpurves Apr 19, 2023
5afdc5e
session available on each article page
stevejpurves Apr 19, 2023
356bd74
named sessions mean separate sessions per page
stevejpurves Apr 19, 2023
393ecae
✨ initial notebook provider with thebe
stevejpurves Apr 19, 2023
a45e8ac
✨ initial notebook provider with thebe
stevejpurves Apr 19, 2023
b4e31d5
🧹 cleanup
stevejpurves Apr 19, 2023
7544706
wip - break compute, but favours initial widgets fallbacks
stevejpurves Apr 19, 2023
1552840
📽only one output renderer with nice ipywidgets fallback
stevejpurves Apr 19, 2023
bf69dd3
🥌 no component wrapping, base node renderers now doing tha' thing
stevejpurves Apr 19, 2023
a15febf
using `SourceFileKind`
stevejpurves Apr 19, 2023
824758a
lint
stevejpurves Apr 19, 2023
f4e2d75
no parent or context
stevejpurves Apr 19, 2023
15204ac
✨ux improvements
stevejpurves Apr 19, 2023
049fd3a
🎯 moved `thebe` providers to `jupyter`
stevejpurves Apr 19, 2023
e8fd869
🔔 passive and active renderers in place
stevejpurves Apr 19, 2023
f50ac20
🎚disable compute when no thebe options
stevejpurves Apr 19, 2023
9a33723
🚀 launch in jupyter
stevejpurves Apr 19, 2023
3a9f886
📡 launch in binder badge
stevejpurves Apr 19, 2023
4b98a51
🛠 fix alignment on badge for articles
stevejpurves Apr 19, 2023
b56d2e1
🎑 better passive rendering for widgets
stevejpurves Apr 19, 2023
ba034d0
👊🏽 bump `thebe-react`
stevejpurves Apr 19, 2023
392a958
🧹 tidy
stevejpurves Apr 19, 2023
7261bd7
📇 named group hover for notebook cells
stevejpurves Apr 19, 2023
4d756c3
remove patches
stevejpurves Apr 19, 2023
8e455c8
🗂added patches as submodule
stevejpurves Apr 19, 2023
db82f53
updates `postinstall` for `patch-package` to use explicit path
stevejpurves Apr 19, 2023
5e9af7c
consistent behaviour when navigating between notebooks
stevejpurves Apr 19, 2023
2953f4b
fixed disappearing linked figure issue
stevejpurves Apr 19, 2023
b532103
🪄 remove 2 extra page renders on outward transition
stevejpurves Apr 19, 2023
87925c9
added TODO
stevejpurves Apr 19, 2023
7f70de9
🧹remove debug elements
stevejpurves Apr 19, 2023
2798316
consistent use of active outputs, `ipywidgets` friendly
stevejpurves Apr 19, 2023
c62fd07
🧹tidy
stevejpurves Apr 19, 2023
b66989c
import heroicons singularly
stevejpurves Apr 19, 2023
7e8a9ce
👊🏽 `thebe-react`
stevejpurves Apr 19, 2023
b24abd9
📏 sized frontmatter header track to avoid jitter
stevejpurves Apr 19, 2023
f8240f9
Merge branch 'main' into feat/thebe
stevejpurves Apr 24, 2023
cc533d7
removing old tests
stevejpurves Apr 24, 2023
6eb3810
🔧 Consume new thebe myst frontmatter (#57)
fwkoch Apr 26, 2023
0d2e4cb
📦 Remove deps from frontmatter
rowanc1 Apr 26, 2023
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
9 changes: 9 additions & 0 deletions .changeset/rotten-rules-juggle.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
'myst-to-react': patch
'@myst-theme/frontmatter': patch
'@myst-theme/providers': patch
'@myst-theme/jupyter': patch
'@myst-theme/site': patch
---

Add Thebe to theme
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,6 @@
[submodule "themes/article-theme"]
path = themes/article-theme
url = https://github.com/executablebooks/myst-article-theme
[submodule "patches"]
path = patches
url = https://github.com/executablebooks/myst-theme-patches
4,920 changes: 2,811 additions & 2,109 deletions package-lock.json

Large diffs are not rendered by default.

5 changes: 4 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
"docs"
],
"scripts": {
"postinstall": "patch-package --patch-dir patches",
"compile": "turbo run compile",
"build": "turbo run build",
"dev": "turbo run dev --parallel --filter='./packages/*'",
Expand Down Expand Up @@ -36,6 +37,7 @@
"@types/react-dom": "^18.0.10",
"eslint-config-curvenote": "latest",
"jest": "^29.5.0",
"patch-package": "^6.5.1",
"prettier": "latest",
"react": "^18.2.0",
"react-dom": "^18.2.0",
Expand All @@ -54,5 +56,6 @@
"react-dom": "^18.2.0"
}
},
"packageManager": "npm@8.10.0"
"packageManager": "npm@8.10.0",
"dependencies": {}
}
6 changes: 3 additions & 3 deletions packages/frontmatter/src/FrontmatterBlock.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -290,7 +290,7 @@ export function FrontmatterBlock({
return (
<>
{hasHeaders && (
<div className="flex mt-3 mb-5 text-sm font-light">
<div className="flex mt-3 mb-5 text-sm font-light items-center h-6">
{subject && (
<div
className={classNames('flex-none pr-2 smallcaps', {
Expand All @@ -306,9 +306,9 @@ export function FrontmatterBlock({
<OpenAccessBadge open_access={open_access} />
<GitHubLink github={github} />
{isJupyter && (
<span>
<div className="inline-block mr-1">
<JupyterIcon className="h-5 w-5 inline-block" />
</span>
</div>
)}
<DownloadsDropdown exports={exports as any} />
</div>
Expand Down
2 changes: 1 addition & 1 deletion packages/frontmatter/src/downloads.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ export function Download({
export function DownloadsDropdown({ exports }: HasExports) {
if (!exports || exports.length === 0) return null;
return (
<Menu as="div" className="relative grow-0 inline-block mx-1">
<Menu as="div" className="flex relative grow-0 inline-block mx-1">
<Menu.Button className="relative">
<span className="sr-only">Downloads</span>
<ArrowDownTrayIcon className="w-5 h-5 ml-2 -mr-1" aria-hidden="true" />
Expand Down
10 changes: 0 additions & 10 deletions packages/jupyter/jest.config.js

This file was deleted.

9 changes: 5 additions & 4 deletions packages/jupyter/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,7 @@
"build:cjs": "tsc --module commonjs --outDir dist/cjs",
"build:esm": "tsc --module es2020 --outDir dist/esm",
"build:types": "tsc --declaration --emitDeclarationOnly --declarationMap --outDir dist/types",
"build": "npm-run-all -l clean -p build:cjs build:esm build:types",
"test": "jest"
"build": "npm-run-all -l clean -p build:cjs build:esm build:types"
},
"dependencies": {
"@headlessui/react": "^1.7.13",
Expand All @@ -28,14 +27,16 @@
"buffer": "^6.0.3",
"classnames": "^2.3.2",
"myst-common": "^0.0.16",
"myst-config": "^0.0.12",
"myst-config": "^0.0.14",
"myst-frontmatter": "^0.0.13",
"myst-spec": "^0.0.4",
"nanoid": "^4.0.0",
"nbtx": "^0.2.3",
"react-popper": "^2.3.0",
"react-syntax-highlighter": "^15.5.0",
"swr": "^1.3.0",
"thebe-core": "^0.1.1",
"thebe-core": "^0.1.6",
"thebe-react": "^0.0.7",
Comment on lines +38 to +39
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@stevejpurves I think it is worth pinning the thebe packages to the same release number?! This is how we are doing it in theme, and is a few less numbers that we have to keep in mind. :)

"unist-util-select": "^4.0.1"
},
"peerDependencies": {
Expand Down
75 changes: 75 additions & 0 deletions packages/jupyter/src/BinderBadge.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
function BinderBadgeLogo() {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In future, I think this can go in icons.

return (
<svg xmlns="http://www.w3.org/2000/svg" width="109" height="20">
<linearGradient id="b" x2="0" y2="100%">
<stop offset="0" stopColor="#bbb" stopOpacity=".1" />
<stop offset="1" stopOpacity=".1" />
</linearGradient>
<clipPath id="a">
<rect width="109" height="20" fill="#fff" rx="3" />
</clipPath>
<g clipPath="url(#a)">
<path fill="#555" d="M0 0h64v20H0z" />
<path fill="#579aca" d="M64 0h45v20H64z" />
<path fill="url(#b)" d="M0 0h109v20H0z" />
</g>
<g
fill="#fff"
fontFamily="DejaVu Sans,Verdana,Geneva,sans-serif"
fontSize="110"
textAnchor="middle"
>
<image
width="14"
height="14"
x="5"
y="3"
href="data:image/png;base64,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"
/>
<text
x="415"
y="150"
fill="#010101"
fillOpacity=".3"
textLength="370"
transform="scale(.1)"
>
launch
</text>
<text x="415" y="140" textLength="370" transform="scale(.1)">
launch
</text>
<text
x="855"
y="150"
fill="#010101"
fillOpacity=".3"
textLength="350"
transform="scale(.1)"
>
binder
</text>
<text x="855" y="140" textLength="350" transform="scale(.1)">
binder
</text>
</g>
</svg>
);
}

export function BinderBadge({ binder }: { binder?: string }) {
if (!binder) return null;
return (
<div className="inline-block mr-1 opacity-80 hover:opacity-100">
<a
href={binder}
title={`Launch Binder Session: ${binder}`}
target="_blank"
rel="noopener noreferrer"
className="text-inherit hover:text-inherit"
>
<BinderBadgeLogo />
</a>
</div>
);
}
2 changes: 1 addition & 1 deletion packages/jupyter/src/ClientOnly.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { useEffect, useState } from 'react';
import type { ReactNode } from 'react';

export default function ClientOnly({ children }: { children: ReactNode }) {
export function ClientOnly({ children }: { children: ReactNode }) {
const [mounted, setMounted] = useState(false);
useEffect(() => {
setMounted(true);
Expand Down
3 changes: 3 additions & 0 deletions packages/jupyter/src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,7 @@ const OUTPUT_RENDERERS = {
output: Output,
};

export * from './BinderBadge';
export * from './providers';

export default OUTPUT_RENDERERS;
85 changes: 66 additions & 19 deletions packages/jupyter/src/jupyter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,35 +4,66 @@ import { useFetchAnyTruncatedContent } from './hooks';
import type { MinifiedOutput } from 'nbtx';
import { convertToIOutputs } from 'nbtx';
import { fetchAndEncodeOutputImages } from './convertImages';
import type { ThebeCore } from './thebe-provider';
import { useThebeCore } from './thebe-provider';
import type { ThebeCore } from 'thebe-core';
import { useThebeCore } from 'thebe-react';
import { useCellRef, useCellRefRegistry, useNotebookCellExecution } from './providers';
import { SourceFileKind } from 'myst-common';

function OutputRenderer({ id, data, core }: { id: string; data: IOutput[]; core: ThebeCore }) {
const [cell] = useState(new core.PassiveCellRenderer(id));
function ActiveOutputRenderer({ id, data }: { id: string; data: IOutput[] }) {
const ref = useCellRef(id);
const exec = useNotebookCellExecution(id);

useEffect(() => {
if (!ref?.el || !exec?.cell) return;
console.debug(`Attaching cell ${exec.cell.id} to DOM at:`, {
el: ref.el,
connected: ref.el.isConnected,
data,
});
exec.cell.attachToDOM(ref.el);
exec.cell.render(data);
}, [ref?.el, exec?.cell]);

return null;
}

function PassiveOutputRenderer({
id,
data,
core,
kind,
}: {
id: string;
data: IOutput[];
core: ThebeCore;
kind: SourceFileKind;
}) {
const [cell] = useState(new core.PassiveCellRenderer(id, undefined, undefined));
const ref = useRef<HTMLDivElement>(null);
useEffect(() => {
cell.render(data);
cell.render(data, kind === SourceFileKind.Article);
}, [data, cell]);
useEffect(() => {
if (!ref.current) return;
cell.attachToDOM(ref.current);
cell.attachToDOM(ref.current, true);
}, [ref]);
return <div ref={ref} />;
return <div ref={ref} data-thebe-passive-ref="true" />;
}

const MemoOutputRenderer = React.memo(OutputRenderer);
const MemoPassiveOutputRenderer = React.memo(PassiveOutputRenderer);

export const NativeJupyterOutputs = ({
id,
outputs,
}: {
id: string;
outputs: MinifiedOutput[];
}) => {
const { core } = useThebeCore();
export const JupyterOutputs = ({ id, outputs }: { id: string; outputs: MinifiedOutput[] }) => {
const { core, load } = useThebeCore();
const { data, error } = useFetchAnyTruncatedContent(outputs);
const [loaded, setLoaded] = useState(false);
const [fullOutputs, setFullOutputs] = useState<IOutput[] | null>(null);
const registry = useCellRefRegistry();
const exec = useNotebookCellExecution(id);

useEffect(() => {
if (core) return;
load();
}, [core, load]);

useEffect(() => {
if (!data || loaded) return;
Expand All @@ -47,10 +78,26 @@ export const NativeJupyterOutputs = ({
return <div className="text-red-500">Error rendering output: {error.message}</div>;
}

if (registry && exec?.cell) {
return (
<div ref={registry?.register(id)} data-thebe-active-ref="true">
{!fullOutputs && <div className="p-2.5">Loading...</div>}
{fullOutputs && <ActiveOutputRenderer id={id} data={fullOutputs} />}
</div>
);
}

return (
<div>
<>
{!fullOutputs && <div className="p-2.5">Loading...</div>}
{fullOutputs && core && <MemoOutputRenderer id={id} core={core} data={fullOutputs} />}
</div>
{fullOutputs && core && (
<MemoPassiveOutputRenderer
id={id}
data={fullOutputs}
core={core}
kind={exec?.kind ?? SourceFileKind.Notebook}
></MemoPassiveOutputRenderer>
)}
</>
);
};
19 changes: 7 additions & 12 deletions packages/jupyter/src/output.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,8 @@ import { KnownCellOutputMimeTypes } from 'nbtx';
import type { MinifiedMimeOutput, MinifiedOutput } from 'nbtx';
import classNames from 'classnames';
import { SafeOutputs } from './safe';
import { NativeJupyterOutputs as JupyterOutputs } from './jupyter';
import ClientOnly from './ClientOnly';
import { ThebeCoreProvider } from './thebe-provider';
import { JupyterOutputs } from './jupyter';
import { useNotebookCellExecution } from './providers';

export const DIRECT_OUTPUT_TYPES = new Set(['stream', 'error']);

Expand Down Expand Up @@ -35,27 +34,23 @@ export function allOutputsAreSafe(
}

export function Output(node: GenericNode) {
const exec = useNotebookCellExecution(node.key);
const outputs: MinifiedOutput[] = node.data;
const allSafe = allOutputsAreSafe(outputs, DIRECT_OUTPUT_TYPES, DIRECT_MIME_TYPES);

let component;
if (allSafe) {
if (allSafe && !exec?.ready) {
component = <SafeOutputs keyStub={node.key} outputs={outputs} />;
} else {
// Hide the iframe if rendering on the server
component = (
<ClientOnly>
<ThebeCoreProvider>
<JupyterOutputs id={node.key} outputs={outputs} />
</ThebeCoreProvider>
</ClientOnly>
);
component = <JupyterOutputs id={node.key} outputs={outputs} />;
}

return (
<figure
key={node.key}
id={node.identifier || undefined}
data-mdast-node-type={node.type}
data-mdast-node-id={node.key}
className={classNames('max-w-full overflow-auto m-0 group not-prose relative', {
'text-left': !node.align || node.align === 'left',
'text-center': node.align === 'center',
Expand Down
Loading