diff --git a/.changeset/calm-items-think.md b/.changeset/calm-items-think.md
new file mode 100644
index 00000000..137ac1e7
--- /dev/null
+++ b/.changeset/calm-items-think.md
@@ -0,0 +1,5 @@
+---
+'squareone': minor
+---
+
+Users can now download the Jupyter Notebook (ipynb) file that they are viewing, with the current parameters filled in. This enables further interactive exploration.
diff --git a/.changeset/smart-pandas-deny.md b/.changeset/smart-pandas-deny.md
new file mode 100644
index 00000000..e3a0e9dd
--- /dev/null
+++ b/.changeset/smart-pandas-deny.md
@@ -0,0 +1,5 @@
+---
+'squareone': minor
+---
+
+Times Square notebook pages show a link to the source notebook on GitHub.
diff --git a/apps/squareone/package.json b/apps/squareone/package.json
index 4e752ad5..50c76929 100644
--- a/apps/squareone/package.json
+++ b/apps/squareone/package.json
@@ -37,6 +37,7 @@
"dependencies": {
"@fontsource/source-sans-pro": "^4.5.11",
"@fortawesome/fontawesome-svg-core": "^6.3.0",
+ "@fortawesome/free-brands-svg-icons": "^6.5.2",
"@fortawesome/free-solid-svg-icons": "^6.3.0",
"@fortawesome/react-fontawesome": "^0.2.0",
"@lsst-sqre/global-css": "workspace:*",
diff --git a/apps/squareone/src/components/TimesSquareGitHubPagePanel/GitHubEditLink.js b/apps/squareone/src/components/TimesSquareGitHubPagePanel/GitHubEditLink.js
new file mode 100644
index 00000000..0e980bd6
--- /dev/null
+++ b/apps/squareone/src/components/TimesSquareGitHubPagePanel/GitHubEditLink.js
@@ -0,0 +1,25 @@
+import styled from 'styled-components';
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+
+export default function GitHubEditLink({ owner, repository, sourcePath }) {
+ if (!owner || !repository || !sourcePath) {
+ return null;
+ }
+
+ const editUrl = `https://github.com/${owner}/${repository}/blob/main/${sourcePath}`;
+
+ return (
+
+
+
+ {owner}/{repository}
+
+
+ );
+}
+
+const StyledFontAwesomeIcon = styled(FontAwesomeIcon)`
+ margin-right: 0.2em;
+ font-size: 1em;
+ color: ${(props) => props.color || 'inherit'};
+`;
diff --git a/apps/squareone/src/components/TimesSquareGitHubPagePanel/GitHubEditLink.stories.js b/apps/squareone/src/components/TimesSquareGitHubPagePanel/GitHubEditLink.stories.js
new file mode 100644
index 00000000..079b475d
--- /dev/null
+++ b/apps/squareone/src/components/TimesSquareGitHubPagePanel/GitHubEditLink.stories.js
@@ -0,0 +1,31 @@
+import React from 'react';
+
+import GitHubEditLink from './GitHubEditLink';
+
+export default {
+ component: GitHubEditLink,
+ title: 'Components/TimesSquare/GitHubEditLink',
+ parameters: {
+ viewport: {
+ viewports: {
+ sidebar: {
+ name: 'Sidebar',
+ styles: {
+ width: '280px',
+ height: '900px',
+ },
+ },
+ },
+ },
+ defaultViewport: 'sidebar',
+ },
+};
+
+const Template = (args) => ;
+
+export const Default = Template.bind({});
+Default.args = {
+ owner: 'lsst-sqre',
+ repository: 'times-square-demo',
+ sourcePath: 'demo.ipynb',
+};
diff --git a/apps/squareone/src/components/TimesSquareGitHubPagePanel/IpynbDownloadLink.js b/apps/squareone/src/components/TimesSquareGitHubPagePanel/IpynbDownloadLink.js
new file mode 100644
index 00000000..99838a3f
--- /dev/null
+++ b/apps/squareone/src/components/TimesSquareGitHubPagePanel/IpynbDownloadLink.js
@@ -0,0 +1,25 @@
+import styled from 'styled-components';
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+
+export default function IpynbDownloadLink({ url, sourcePath }) {
+ // get the filename from the sourcePath
+ const filename = sourcePath.split('/').pop();
+
+ return (
+
+
+ Download notebook
+
+
+ );
+}
+
+const StyledP = styled.p`
+ margin-top: 2rem;
+`;
+
+const StyledFontAwesomeIcon = styled(FontAwesomeIcon)`
+ margin-right: 0.2em;
+ font-size: 1em;
+ color: ${(props) => props.color || 'inherit'};
+`;
diff --git a/apps/squareone/src/components/TimesSquareGitHubPagePanel/TimesSquareGitHubPagePanel.js b/apps/squareone/src/components/TimesSquareGitHubPagePanel/TimesSquareGitHubPagePanel.js
index d8ec2a6e..b6f852f4 100644
--- a/apps/squareone/src/components/TimesSquareGitHubPagePanel/TimesSquareGitHubPagePanel.js
+++ b/apps/squareone/src/components/TimesSquareGitHubPagePanel/TimesSquareGitHubPagePanel.js
@@ -11,11 +11,15 @@ import Head from 'next/head';
import Error from 'next/error';
import useTimesSquarePage from '../../hooks/useTimesSquarePage';
+import { TimesSquareUrlParametersContext } from '../TimesSquareUrlParametersProvider';
import TimesSquareParameters from '../TimesSquareParameters';
import ExecStats from './ExecStats';
+import GitHubEditLink from './GitHubEditLink';
+import IpynbDownloadLink from './IpynbDownloadLink';
export default function TimesSquareGitHubPagePanel({}) {
const { publicRuntimeConfig } = getConfig();
+ const { urlQueryString } = React.useContext(TimesSquareUrlParametersContext);
const pageData = useTimesSquarePage();
if (pageData.loading) {
@@ -27,6 +31,8 @@ export default function TimesSquareGitHubPagePanel({}) {
const { title, description } = pageData;
+ const ipynbDownloadUrl = `${pageData.renderedIpynbUrl}?${urlQueryString}`;
+
return (
@@ -37,8 +43,19 @@ export default function TimesSquareGitHubPagePanel({}) {
{description && (
)}
+
+
+
+
diff --git a/apps/squareone/src/hooks/useTimesSquarePage.js b/apps/squareone/src/hooks/useTimesSquarePage.js
index fb203aea..79aba8ae 100644
--- a/apps/squareone/src/hooks/useTimesSquarePage.js
+++ b/apps/squareone/src/hooks/useTimesSquarePage.js
@@ -9,6 +9,15 @@ function useTimesSquarePage() {
const { tsPageUrl } = React.useContext(TimesSquareUrlParametersContext);
const { data, error } = useSWR(tsPageUrl, fetcher);
+ const githubInfo = data
+ ? {
+ owner: data.github.owner ? data.github.owner : null,
+ repository: data.github.repository ? data.github.repository : null,
+ sourcePath: data.github.source_path ? data.github.source_path : null,
+ sidecarPath: data.github.sidecar_path ? data.github.sidecar_path : null,
+ }
+ : { owner: null, repository: null, sourcePath: null, sidecarPath: null };
+
return {
error: error,
loading: !error && !data,
@@ -18,6 +27,8 @@ function useTimesSquarePage() {
htmlUrl: data ? data.html_url : null,
htmlStatusUrl: data ? data.html_status_url : null,
htmlEventsUrl: data ? data.html_events_url : null,
+ renderedIpynbUrl: data ? data.rendered_url : null,
+ github: githubInfo,
};
}
diff --git a/apps/squareone/src/styles/icons.js b/apps/squareone/src/styles/icons.js
index 790a3900..eac8a248 100644
--- a/apps/squareone/src/styles/icons.js
+++ b/apps/squareone/src/styles/icons.js
@@ -13,7 +13,9 @@ import {
faCircleCheck,
faCircleMinus,
faCodeCommit,
+ faDownload,
} from '@fortawesome/free-solid-svg-icons';
+import { faGithub } from '@fortawesome/free-brands-svg-icons';
// Add icons to the global Font Awesome library
library.add(faAngleDown);
@@ -24,3 +26,5 @@ library.add(faCircleXmark);
library.add(faCircleCheck);
library.add(faCircleMinus);
library.add(faCodeCommit);
+library.add(faDownload);
+library.add(faGithub);
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 0fc7ff48..0fb39130 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -41,6 +41,9 @@ importers:
'@fortawesome/fontawesome-svg-core':
specifier: ^6.3.0
version: 6.3.0
+ '@fortawesome/free-brands-svg-icons':
+ specifier: ^6.5.2
+ version: 6.5.2
'@fortawesome/free-solid-svg-icons':
specifier: ^6.3.0
version: 6.3.0
@@ -3605,6 +3608,12 @@ packages:
requiresBuild: true
dev: false
+ /@fortawesome/fontawesome-common-types@6.5.2:
+ resolution: {integrity: sha512-gBxPg3aVO6J0kpfHNILc+NMhXnqHumFxOmjYCFfOiLZfwhnnfhtsdA2hfJlDnj+8PjAs6kKQPenOTKj3Rf7zHw==}
+ engines: {node: '>=6'}
+ requiresBuild: true
+ dev: false
+
/@fortawesome/fontawesome-svg-core@6.3.0:
resolution: {integrity: sha512-uz9YifyKlixV6AcKlOX8WNdtF7l6nakGyLYxYaCa823bEBqyj/U2ssqtctO38itNEwXb8/lMzjdoJ+aaJuOdrw==}
engines: {node: '>=6'}
@@ -3613,6 +3622,14 @@ packages:
'@fortawesome/fontawesome-common-types': 6.3.0
dev: false
+ /@fortawesome/free-brands-svg-icons@6.5.2:
+ resolution: {integrity: sha512-zi5FNYdmKLnEc0jc0uuHH17kz/hfYTg4Uei0wMGzcoCL/4d3WM3u1VMc0iGGa31HuhV5i7ZK8ZlTCQrHqRHSGQ==}
+ engines: {node: '>=6'}
+ requiresBuild: true
+ dependencies:
+ '@fortawesome/fontawesome-common-types': 6.5.2
+ dev: false
+
/@fortawesome/free-solid-svg-icons@6.3.0:
resolution: {integrity: sha512-x5tMwzF2lTH8pyv8yeZRodItP2IVlzzmBuD1M7BjawWgg9XAvktqJJ91Qjgoaf8qJpHQ8FEU9VxRfOkLhh86QA==}
engines: {node: '>=6'}