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

🪝move launch binder logic to reusable hook #249

Merged
merged 3 commits into from
Oct 13, 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
5 changes: 5 additions & 0 deletions .changeset/warm-mice-repair.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@myst-theme/jupyter': patch
---

Separate out launch binder logic into a reusable hook
38 changes: 5 additions & 33 deletions packages/jupyter/src/controls/Buttons.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,7 @@ import {
import { BoltIcon as BoltIconSolid } from '@heroicons/react/24/solid';
import classNames from 'classnames';
import { Spinner } from './Spinner.js';
import { useThebeServer } from 'thebe-react';
import { useCallback, useState } from 'react';
import { useLaunchBinder } from '../hooks.js';

function BinderButton({
icon,
Expand Down Expand Up @@ -56,19 +55,8 @@ function BinderButton({
}

export function LaunchBinder({ style, location }: { style: 'link' | 'button'; location?: string }) {
const { connect, connecting, ready, server, error } = useThebeServer();
const [autoOpen, setAutoOpen] = useState(false);

// automatically click the link when the server is ready
// but only if the connection was initiated in this component by the user
const autoClick = useCallback(
(node: HTMLAnchorElement) => {
if (node != null && autoOpen) {
node.click();
}
},
[autoOpen],
);
const { connecting, ready, error, autoClickRef, handleStart, getUserServerUrl } =
useLaunchBinder();

let btnStyles =
'flex gap-1 px-2 py-1 font-normal no-underline border rounded bg-slate-200 border-slate-600 hover:bg-slate-800 hover:text-white hover:border-transparent';
Expand All @@ -85,29 +73,13 @@ export function LaunchBinder({ style, location }: { style: 'link' | 'button'; lo
'inline-flex items-center mr-2 font-medium no-underline text-gray-900 lg:mr-0 lg:flex';
}

const handleStart = () => {
if (!connect) {
console.debug("LaunchBinder: Trying to start a connection but connect() isn't defined");
return;
}
setAutoOpen(true);
connect();
};

if (ready) {
// we expect ?token= to be in the url
let userServerUrl = server?.userServerUrl;
if (userServerUrl && location) {
// add the location to the url pathname
const url = new URL(userServerUrl);
if (url.pathname.endsWith('/')) url.pathname = url.pathname.slice(0, -1);
url.pathname = `${url.pathname}/lab/tree${location}`;
userServerUrl = url.toString();
}
const userServerUrl = getUserServerUrl(location);

return (
<a
ref={autoClick}
ref={autoClickRef}
className={btnStyles}
href={userServerUrl}
target="_blank"
Expand Down
52 changes: 51 additions & 1 deletion packages/jupyter/src/hooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ import type {
MinifiedStreamOutput,
} from 'nbtx';
import { walkOutputs } from 'nbtx';
import { useState, useLayoutEffect } from 'react';
import { useState, useLayoutEffect, useCallback } from 'react';
import { useThebeServer } from 'thebe-react';

interface LongContent {
content_type?: string;
Expand Down Expand Up @@ -136,3 +137,52 @@ export default function useWindowSize() {

return windowSize;
}

export function useLaunchBinder() {
const { connect, connecting, ready, server, error } = useThebeServer();
const [autoOpen, setAutoOpen] = useState(false);

// automatically click the link when the server is ready
// but only if the connection was initiated in this component by the user
const autoClickRef = useCallback(
(node: HTMLAnchorElement) => {
if (node != null && autoOpen) {
node.click();
}
},
[autoOpen],
);

const handleStart = useCallback(() => {
if (!connect) {
console.debug("LaunchBinder: Trying to start a connection but connect() isn't defined");
return;
}
setAutoOpen(true);
connect();
}, [connect]);

const getUserServerUrl = useCallback(
(location?: string) => {
let userServerUrl = server?.userServerUrl;
if (userServerUrl && location) {
// add the location to the url pathname
const url = new URL(userServerUrl);
if (url.pathname.endsWith('/')) url.pathname = url.pathname.slice(0, -1);
url.pathname = `${url.pathname}/lab/tree${location}`;
userServerUrl = url.toString();
}
return userServerUrl;
},
[server],
);

return {
connecting,
ready,
error,
autoClickRef,
handleStart,
getUserServerUrl,
};
}
2 changes: 2 additions & 0 deletions packages/jupyter/src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,7 @@ export * from './ConnectionStatusTray.js';
export * from './providers.js';
export * from './execute/index.js';
export * from './controls/index.js';
export * from './utils.js';
export { useLaunchBinder } from './hooks.js';

export default OUTPUT_RENDERERS;
7 changes: 4 additions & 3 deletions packages/jupyter/src/providers.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import type { RepoProviderSpec } from 'thebe-core';

function makeThebeOptions(
siteManifest: SiteManifest | undefined,
optionsOverrideFn = (opts?: ExtendedCoreOptions) => opts,
optionsOverrideFn = (opts: ExtendedCoreOptions) => opts,
): {
options?: ExtendedCoreOptions;
githubBadgeUrl?: string;
Expand All @@ -27,7 +27,8 @@ function makeThebeOptions(
binderBadgeUrl,
);

const options = optionsOverrideFn(thebeFrontmatter ? optionsFromFrontmatter : undefined);
let options = optionsFromFrontmatter;
if (options) options = optionsOverrideFn(options);

return {
options,
Expand All @@ -51,7 +52,7 @@ export function ConfiguredThebeServerProvider({
children,
}: React.PropsWithChildren<{
siteManifest?: SiteManifest;
optionOverrideFn?: (opts?: ExtendedCoreOptions) => ExtendedCoreOptions;
optionOverrideFn?: (opts: ExtendedCoreOptions) => ExtendedCoreOptions;
customRepoProviders?: RepoProviderSpec[];
}>) {
const thebe = React.useMemo(
Expand Down