35 | Sorry{' '}
36 |
37 | 😔
38 | {' '}
39 | we couldn’t find what you were looking for.
40 |
41 | {process.env.NODE_ENV === 'development' ? (
42 | <>
43 |
44 | Try creating a page in src/pages/.
45 |
46 | >
47 | ) : null}
48 |
49 | Go home.
50 |
51 |
52 | );
53 |
54 | export default NotFoundPage;
55 |
56 | NotFoundPage.propTypes = {
57 | location: PropTypes.shape({
58 | pathname: PropTypes.string.isRequired,
59 | }),
60 | };
61 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | BSD 3-Clause License
2 |
3 | Copyright (c) 2020, Astropy Developers
4 | All rights reserved.
5 |
6 | Redistribution and use in source and binary forms, with or without
7 | modification, are permitted provided that the following conditions are met:
8 |
9 | 1. Redistributions of source code must retain the above copyright notice, this
10 | list of conditions and the following disclaimer.
11 |
12 | 2. Redistributions in binary form must reproduce the above copyright notice,
13 | this list of conditions and the following disclaimer in the documentation
14 | and/or other materials provided with the distribution.
15 |
16 | 3. Neither the name of the copyright holder nor the names of its
17 | contributors may be used to endorse or promote products derived from
18 | this software without specific prior written permission.
19 |
20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30 |
--------------------------------------------------------------------------------
/src/components/instantsearch/hits.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Extension to the Algolia Hits widget that passes props through to individual
3 | * Hit components.
4 | */
5 |
6 | import React from 'react';
7 | import PropTypes from 'prop-types';
8 | import styled from 'styled-components';
9 | import { connectHits } from 'react-instantsearch-dom';
10 |
11 | /**
12 | * Custom Hits component that passes props to individual Hit components.
13 | */
14 | const Hits = ({ hits, hitComponent, className = '' }) => {
15 | const HitComponent = hitComponent;
16 |
17 | return (
18 |
61 |
62 | >
63 | );
64 | }
65 |
66 | Layout.propTypes = {
67 | children: PropTypes.node.isRequired,
68 | };
69 |
--------------------------------------------------------------------------------
/src/components/seo.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import Helmet from 'react-helmet';
4 | import { useStaticQuery, graphql } from 'gatsby';
5 |
6 | /*
7 | * SEO component that adds tags to the page's header using react-helmet.
8 | */
9 | export default function SEO({ children, location, title, description, image }) {
10 | const { site } = useStaticQuery(
11 | graphql`
12 | query {
13 | site {
14 | siteMetadata {
15 | title
16 | description
17 | author
18 | siteUrl
19 | twitter
20 | }
21 | }
22 | }
23 | `
24 | );
25 |
26 | // The description can be overidden for individual pages via description
27 | // prop.
28 | const desc = description || site.siteMetadata.description;
29 |
30 | // The page's canonical URL
31 | const canonicalUrl = site.siteMetadata.siteUrl + location.pathname;
32 |
33 | return (
34 |
35 |
36 | {title}
37 | {/* Favicon */}
38 |
39 |
40 | {/* General meta tags */}
41 |
42 |
43 |
44 |
45 | {/* Open Graph */}
46 |
47 |
52 |
53 |
58 |
59 | {/* Twitter card */}
60 |
65 | {children}
66 |
67 | );
68 | }
69 |
70 | SEO.propTypes = {
71 | children: PropTypes.node,
72 | title: PropTypes.string.isRequired,
73 | description: PropTypes.string,
74 | location: PropTypes.shape({
75 | pathname: PropTypes.string.isRequired,
76 | }),
77 | image: PropTypes.string,
78 | };
79 |
--------------------------------------------------------------------------------
/src/styles/globalStyles.js:
--------------------------------------------------------------------------------
1 | import { createGlobalStyle } from 'styled-components';
2 |
3 | import { normalize } from 'polished';
4 |
5 | const GlobalStyles = createGlobalStyle`
6 | /*
7 | * CSS reset via normalize.
8 | */
9 | ${normalize()}
10 |
11 | html {
12 | box-sizing: border-box;
13 | }
14 |
15 | /*
16 | * Inherit border-box sizing from html
17 | * https://css-tricks.com/inheriting-box-sizing-probably-slightly-better-best-practice/
18 | */
19 | *,
20 | *:before,
21 | *:after {
22 | box-sizing: inherit;
23 | }
24 |
25 | :root {
26 | /*
27 | * Reinforce that we're respecting the user's ability to set a default
28 | * font size. The rem unit now becomes relative to this.
29 | * Flexible Typesetting, Tim Brown, ch 2 and 4
30 | */
31 | font-size: 1.1rem;
32 |
33 | /*
34 | * Design tokens: Color palette
35 | */
36 | --astropy-primary-color: #fa743b;
37 | --astropy-neutral-100: #111111;
38 | --astropy-neutral-900: #ffffff;
39 | --algolia-primary-color: #182359;
40 |
41 | /*
42 | * Design tokens: Sizes
43 | */
44 | --astropy-size-xxs: 0.125rem;
45 | --astropy-size-xs: 0.25rem;
46 | --astropy-size-s: 0.5rem;
47 | --astropy-size-m: 1rem;
48 | --astropy-size-ml: 1.2rem;
49 | --astropy-size-l: 2rem;
50 | --astropy-size-xl: 4rem;
51 |
52 | /*
53 | * Design tokens: font sizes
54 | */
55 | --astropy-font-size-s: 0.8rem;
56 | --astropy-font-size-m: 1rem;
57 | --astropy-font-size-ml: 1.2rem;
58 |
59 | /*
60 | * Design tokens: border radii
61 | */
62 | --astropy-border-radius-s: 0.125rem;
63 | --astropy-border-radius-m: 0.25rem;
64 | --astropy-border-radius-l: 0.5rem;
65 |
66 | /*
67 | * Applied colors
68 | */
69 | --astropy-text-color: var(--astropy-neutral-100);
70 | --astropy-page-background-color: var(--astropy-neutral-900);
71 | --astropy-nav-header-color: var(--astropy-neutral-100);
72 | --astropy-nav-header-text-color: var(--astropy-neutral-900);
73 |
74 | /*
75 | * Applied sizes
76 | */
77 | --astropy-content-width: 60em;
78 | }
79 |
80 | html, body {
81 | padding: 0;
82 | margin: 0;
83 | font-family: "Source Sans Pro", -apple-system, BlinkMacSystemFont, Segoe UI,
84 | Roboto, Oxygen, Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue,
85 | sans-serif;
86 | line-height: 1.45;
87 | color: var(--astropy-text-color);
88 | background-color: var(--astropy-page-background-color);
89 | }
90 |
91 | a {
92 | color: var(--astropy-primary-color);
93 | font-weight: 700;
94 | text-decoration: none;
95 | }
96 |
97 | a:hover {
98 | text-decoration: solid underline var(--astropy-primary-color) 2px;
99 | }
100 | `;
101 |
102 | export default GlobalStyles;
103 |
--------------------------------------------------------------------------------
/src/components/header.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import styled from 'styled-components';
3 | import { Link } from 'gatsby';
4 |
5 | import logo from '../../static/learn-astropy-logo.png';
6 |
7 | const HeaderContainer = styled.header`
8 | width: 100%;
9 | padding: var(--astropy-size-s) var(--astropy-size-m);
10 | margin: 0;
11 | background-color: var(--astropy-nav-header-color);
12 | color: var(--astropy-nav-header-text-color);
13 |
14 | display: flex;
15 | flex-direction: row;
16 | flex-wrap: nowrap;
17 | justify-content: flex-start;
18 | align-items: center;
19 |
20 | .learn-astropy-logo {
21 | width: 12rem;
22 | }
23 |
24 | .main-nav {
25 | display: flex;
26 | margin-left: 2rem;
27 | flex-direction: row
28 | flex-wrap: nowrap;
29 | justify-content: flex-start;
30 | align-items: flex-start;
31 |
32 | @media screen and (max-width: 600px) {
33 | margin-left: -1rem;
34 | }
35 | }
36 |
37 | .astropy-link {
38 | margin-left: auto;
39 | }
40 |
41 | a {
42 | color: var(--astropy-neutral-900);
43 | }
44 |
45 | a:hover {
46 | text-decoration: none;
47 | }
48 |
49 | @media screen and (max-width: 600px) {
50 | display: flex;
51 | flex-direction: column;
52 | flex-wrap: nowrap;
53 | justify-content: flex-start;
54 | align-items: flex-start;
55 | }
56 | `;
57 |
58 | const NavItem = styled.div`
59 | transition: all 0.2s ease-in-out;
60 | margin: 0 1em;
61 | border-bottom: 2px solid transparent;
62 |
63 | &:hover {
64 | border-bottom: 2px solid var(--astropy-primary-color);
65 | color: var(--astropy-primary-color);
66 | }
67 |
68 | // .astropy-link {
69 | // margin-left: auto;
70 | // }
71 |
72 | @media screen and (max-width: 600px) {
73 | width: 100vw;
74 | display: flex;
75 | flex-direction: column;
76 | align-items: flex-start;
77 | padding-top: 10px;
78 | padding-left: 0.5rem;
79 | }
80 | `;
81 |
82 | /*
83 | * Header component that includes the logo, search bar, and navigation tabs.
84 | */
85 | export default function NavHeader() {
86 | return (
87 | <>
88 |
89 |
90 |
95 |
96 |
114 |
115 | >
116 | );
117 | }
118 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Astropy Learn
2 |
3 | This repository hosts the homepage of the Astropy Learn project, https://learn.astropy.org, and serves the tutorial content from the [astropy-learn](https://github.com/astropy-learn) organization. The site itself is built with [Gatsby](https://www.gatsbyjs.com/) and the [Algolia](https://www.algolia.com) search service. Records for the Algolia database are curated and formatted by the [learn-astropy-librarian](https://github.com/astropy-learn/learn-astropy-librarian) app.
4 |
5 | ## Developer guide
6 |
7 | ### Initial set up
8 |
9 | Create a fork on https://github.com/astropy-learn/learn.
10 |
11 | ```bash
12 | npm install
13 | ```
14 |
15 | ### Run a development server
16 |
17 | You can run a development server that will serve the site and reload as you develop the app:
18 |
19 | ```bash
20 | npm run develop
21 | ```
22 |
23 | By default the app is hosted at http://localhost:8000. You can also interact with the GraphQL data layer by browsing
24 |
25 | ### Build for production
26 |
27 | ```bash
28 | npm run build
29 | ```
30 |
31 | Preview the built site by running:
32 |
33 | ```bash
34 | npm run serve
35 | ```
36 |
37 | ### Linting and autoformatting
38 |
39 | This app uses ESLint to lint JavaScript, which in turn runs Prettier to format JavaScript. The configuration is based on [wesbos/eslint-config-wesbos](https://github.com/wesbos/eslint-config-wesbos).
40 |
41 | A Git pre-commit hooks runs both ESLint and Prettier and automatically lints and reformats code before every commit. These hooks are run by [husky](https://typicode.github.io/husky/#/) and should already be installed when you ran `npm install`.
42 |
43 | To manually lint the code base:
44 |
45 | ```bash
46 | npm run lint
47 | ```
48 |
49 | To also fix issues and format the code base:
50 |
51 | ```bash
52 | npm run lint:fix
53 | ```
54 |
55 | Ideally your editor will also apply eslint/prettier on save, though these commands are handy as a fallback.
56 |
57 | ### About the node version
58 |
59 | This project is intended to be built with a Node.js version that's encoded in the [`.nvmrc`](./.nvmrc) file. To adopt this Node version, we recommend installing and using the [node version manager](https://github.com/nvm-sh/nvm).
60 |
61 | Then you can use the preferred node version by running `nvm` from the project root:
62 |
63 | ```sh
64 | nvm use
65 | ```
66 |
67 | ### Additional resources for developers
68 |
69 | Learn more about Gatsby:
70 |
71 | - [Documentation](https://www.gatsbyjs.com/docs/?utm_source=starter&utm_medium=readme&utm_campaign=minimal-starter)
72 | - [Tutorials](https://www.gatsbyjs.com/tutorial/?utm_source=starter&utm_medium=readme&utm_campaign=minimal-starter)
73 | - [Guides](https://www.gatsbyjs.com/tutorial/?utm_source=starter&utm_medium=readme&utm_campaign=minimal-starter)
74 | - [API Reference](https://www.gatsbyjs.com/docs/api-reference/?utm_source=starter&utm_medium=readme&utm_campaign=minimal-starter)
75 | - [Plugin Library](https://www.gatsbyjs.com/plugins?utm_source=starter&utm_medium=readme&utm_campaign=minimal-starter)
76 | - [Cheat Sheet](https://www.gatsbyjs.com/docs/cheat-sheet/?utm_source=starter&utm_medium=readme&utm_campaign=minimal-starter)
77 |
78 | Learn more about Algolia:
79 |
80 | - [Documentation](https://www.algolia.com/doc/)
81 | - [React instantsearch](https://www.algolia.com/doc/guides/building-search-ui/what-is-instantsearch/react/)
82 |
--------------------------------------------------------------------------------
/src/pages/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 |
4 | import { InstantSearch, Configure } from 'react-instantsearch-dom';
5 |
6 | import Layout from '../components/layout';
7 | import {
8 | SearchLayout,
9 | SearchRefinementsSection,
10 | } from '../components/searchLayout';
11 | import SEO from '../components/seo';
12 | import PageCover from '../components/pageCover';
13 | import searchClient from '../searchClient';
14 | import { StyledHits } from '../components/instantsearch/hits';
15 | import RefinementList from '../components/instantsearch/refinementList';
16 | import SearchBox from '../components/instantsearch/searchBox';
17 | import PrioritySort from '../components/instantsearch/virtualPrioritySort';
18 | import ResultCard from '../components/resultCard';
19 |
20 | export default function IndexPage({ location }) {
21 | return (
22 |
23 |
24 |
25 |
Learn Astropy
26 |
27 | Learn how to use Python for research in astronomy with tutorials and
28 | guides covering Astropy and the broader astronomy Python ecosystem.
29 |
93 | The Astropy project is committed to fostering an inclusive
94 | community. The community of participants in open source Astronomy
95 | projects is made up of members from around the globe with a
96 | diverse set of skills, personalities, and experiences. It is
97 | through these differences that our community experiences success
98 | and continued growth.{' '}
99 |
100 | Learn more.
101 |
102 |
157 |
158 | );
159 | };
160 |
161 | ResultCard.propTypes = {
162 | hit: PropTypes.object.isRequired,
163 | };
164 |
165 | export default ResultCard;
166 |
--------------------------------------------------------------------------------
/deployment/installtutorials.py:
--------------------------------------------------------------------------------
1 | """Install the built tutorials HTML from astropy-learn/astropy-tutorials into the
2 | built Gatsby site.
3 | """
4 |
5 | from __future__ import annotations
6 |
7 | import argparse
8 | import os
9 | from pathlib import Path
10 | import tempfile
11 | from subprocess import CalledProcessError, check_call
12 | import glob
13 | import shutil
14 | import requests
15 |
16 |
17 | def parse_args() -> argparse.Namespace:
18 | parser = argparse.ArgumentParser(
19 | description=(
20 | "Install the tutorials HTML files from "
21 | "each tutorial repo into the build Gatsby site."
22 | ),
23 | formatter_class=argparse.RawDescriptionHelpFormatter,
24 | )
25 | parser.add_argument(
26 | "--dest",
27 | required=True,
28 | help="Directory where the tutorials are installed. This should be "
29 | "inside the Gatsby 'public' directory.",
30 | )
31 |
32 | return parser.parse_args()
33 |
34 |
35 | def process_repo(repo, destination_directory):
36 | """Process a tutorial repository to copy its rendered tutorial(s) into `destination_directory`."""
37 | os.makedirs(destination_directory, exist_ok=True)
38 |
39 | repo_name = repo["full_name"]
40 | if not repo_name.split("/")[1].startswith("tutorial--"):
41 | return
42 | if repo_name.split("/")[1] == "tutorial--template":
43 | return
44 |
45 | print(f"\nProcessing {repo_name}")
46 |
47 | with tempfile.TemporaryDirectory() as tmp:
48 | branch_name = "converted"
49 | try:
50 | check_call(
51 | f"git clone --depth 1 --branch {branch_name} https://github.com/{repo_name}.git {tmp}".split()
52 | )
53 | except CalledProcessError:
54 | print(f"Failed to clone {repo_name}")
55 | return
56 |
57 | repo = Path(tmp)
58 | # os.system(f'tree {repo}')
59 | tutorials = glob.glob(f"{repo}/_sources/*.ipynb")
60 | for t in tutorials:
61 | tname = os.path.splitext(os.path.basename(t))[0]
62 | print(f"Copying tutorial {tname}")
63 | shutil.copy(t, destination_directory)
64 | shutil.copy(
65 | f"{repo}/{tname}.html",
66 | destination_directory,
67 | )
68 | # if len(tutorials) > 1:
69 | # index_files = glob.glob(f"{repo}/index-*.html")
70 | # if index_files:
71 | # index = index_files[0]
72 | # print(f"More than 1 tutorial found; also copying index file {index}")
73 | # shutil.copy(index, destination_directory)
74 | # else:
75 | # raise FileNotFoundError(f"No index-*.html file found for {repo_name}.")
76 |
77 | # copy files for in-notebook search bar
78 | # shutil.copy(
79 | # f"{repo}/search.html",
80 | # destination_directory,
81 | # )
82 | # shutil.copy(
83 | # f"{repo}/searchindex.js",
84 | # destination_directory,
85 | # )
86 |
87 | # copy _static files (CSS, JS) for page rendering
88 | shutil.copytree(
89 | f"{repo}/_static",
90 | f"{destination_directory}/_static",
91 | dirs_exist_ok=True,
92 | )
93 |
94 | # copy images (plots) in notebook for faster page loading
95 | images = glob.glob(f"{repo}/_images/*.png")
96 | if len(images) > 0:
97 | print("Copying notebook cell output images")
98 | image_dir = f"{destination_directory}/_images"
99 | os.makedirs(image_dir, exist_ok=True)
100 | for i in images:
101 | shutil.copy(i, image_dir)
102 | else:
103 | print("No notebook cell output images found to copy")
104 |
105 |
106 | if __name__ == "__main__":
107 | args = parse_args()
108 | dest_dir = args.dest
109 | url = "https://api.github.com/orgs/astropy-learn/repos"
110 | with requests.Session() as s:
111 | while True:
112 | response = s.get(url)
113 | response.raise_for_status()
114 | data = response.json()
115 | list(map(process_repo, data, [dest_dir] * len(data)))
116 | url = response.links.get("next", {}).get("url")
117 | if not url:
118 | break
119 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "learn-astropy",
3 | "version": "1.0.0",
4 | "private": true,
5 | "description": "Learn Astropy",
6 | "author": "Jonathan Sick",
7 | "keywords": [
8 | "gatsby"
9 | ],
10 | "scripts": {
11 | "develop": "gatsby develop",
12 | "start": "gatsby develop",
13 | "build": "gatsby build",
14 | "serve": "gatsby serve",
15 | "clean": "gatsby clean",
16 | "lint": "eslint .",
17 | "lint:fix": "eslint . --fix",
18 | "prettier": "npx prettier . --check",
19 | "prettier:fix": "npm run prettier -- --write",
20 | "prepare": "husky install"
21 | },
22 | "eslintConfig": {
23 | "extends": [
24 | "airbnb",
25 | "prettier"
26 | ],
27 | "parser": "babel-eslint",
28 | "plugins": [
29 | "html",
30 | "react-hooks"
31 | ],
32 | "rules": {
33 | "no-debugger": 0,
34 | "no-alert": 0,
35 | "no-await-in-loop": 0,
36 | "no-return-assign": [
37 | "error",
38 | "except-parens"
39 | ],
40 | "no-restricted-syntax": [
41 | 2,
42 | "ForInStatement",
43 | "LabeledStatement",
44 | "WithStatement"
45 | ],
46 | "no-unused-vars": [
47 | 1,
48 | {
49 | "ignoreRestSiblings": true,
50 | "argsIgnorePattern": "res|next|^err"
51 | }
52 | ],
53 | "prefer-const": [
54 | "error",
55 | {
56 | "destructuring": "all"
57 | }
58 | ],
59 | "arrow-body-style": [
60 | 2,
61 | "as-needed"
62 | ],
63 | "no-unused-expressions": [
64 | 2,
65 | {
66 | "allowTaggedTemplates": true
67 | }
68 | ],
69 | "no-param-reassign": [
70 | 2,
71 | {
72 | "props": false
73 | }
74 | ],
75 | "no-console": 0,
76 | "import/prefer-default-export": 0,
77 | "import": 0,
78 | "func-names": 0,
79 | "space-before-function-paren": 0,
80 | "comma-dangle": 0,
81 | "max-len": 0,
82 | "import/extensions": 0,
83 | "no-underscore-dangle": 0,
84 | "consistent-return": 0,
85 | "react/display-name": 1,
86 | "react/no-array-index-key": 0,
87 | "react/react-in-jsx-scope": 0,
88 | "react/prefer-stateless-function": 0,
89 | "react/forbid-prop-types": 0,
90 | "react/no-unescaped-entities": 0,
91 | "jsx-a11y/accessible-emoji": 0,
92 | "jsx-a11y/label-has-associated-control": [
93 | "error",
94 | {
95 | "assert": "either"
96 | }
97 | ],
98 | "react/require-default-props": 0,
99 | "react/jsx-filename-extension": [
100 | 1,
101 | {
102 | "extensions": [
103 | ".js",
104 | ".jsx"
105 | ]
106 | }
107 | ],
108 | "radix": 0,
109 | "no-shadow": [
110 | 2,
111 | {
112 | "hoist": "all",
113 | "allow": [
114 | "resolve",
115 | "reject",
116 | "done",
117 | "next",
118 | "err",
119 | "error"
120 | ]
121 | }
122 | ],
123 | "quotes": [
124 | 2,
125 | "single",
126 | {
127 | "avoidEscape": true,
128 | "allowTemplateLiterals": true
129 | }
130 | ],
131 | "jsx-a11y/href-no-hash": "off",
132 | "jsx-a11y/anchor-is-valid": [
133 | "warn",
134 | {
135 | "aspects": [
136 | "invalidHref"
137 | ]
138 | }
139 | ],
140 | "react-hooks/rules-of-hooks": "error",
141 | "react-hooks/exhaustive-deps": "warn"
142 | }
143 | },
144 | "lint-staged": {
145 | "*.js": "eslint"
146 | },
147 | "dependencies": {
148 | "@fontsource/source-sans-pro": "^4.5.0",
149 | "algoliasearch": "^4.10.3",
150 | "babel-plugin-styled-components": "^1.13.2",
151 | "gatsby": "^3.15.0",
152 | "gatsby-image": "^3.10.0",
153 | "gatsby-plugin-react-helmet": "^4.10.0",
154 | "gatsby-plugin-styled-components": "^4.12.0",
155 | "gatsby-source-filesystem": "^3.10.0",
156 | "gatsby-transformer-remark": "^4.7.0",
157 | "instantsearch.css": "^7.4.5",
158 | "polished": "^4.1.3",
159 | "prop-types": "^15.7.2",
160 | "react": "^17.0.2",
161 | "react-dom": "^17.0.2",
162 | "react-helmet": "^6.1.0",
163 | "react-instantsearch-dom": "^6.12.1",
164 | "styled-components": "^5.3.1"
165 | },
166 | "devDependencies": {
167 | "babel-eslint": "^10.1.0",
168 | "eslint": "^7.32.0",
169 | "eslint-config-airbnb": "^18.2.1",
170 | "eslint-config-prettier": "^8.3.0",
171 | "eslint-plugin-html": "^6.1.2",
172 | "eslint-plugin-import": "^2.23.4",
173 | "eslint-plugin-jsx-a11y": "^6.4.1",
174 | "eslint-plugin-react": "^7.24.0",
175 | "eslint-plugin-react-hooks": "^4.2.0",
176 | "husky": "^7.0.1",
177 | "lint-staged": "^11.1.1",
178 | "prettier": "^2.3.2",
179 | "pretty-quick": "^3.1.1"
180 | }
181 | }
182 |
--------------------------------------------------------------------------------
/.github/workflows/deploy.yaml:
--------------------------------------------------------------------------------
1 | name: Deploy
2 |
3 | 'on':
4 | push:
5 | branches:
6 | - main
7 | schedule:
8 | # weekly on Sunday
9 | - cron: "20 10 * * 0"
10 | # allow manual triggering of workflow
11 | workflow_dispatch:
12 |
13 | jobs:
14 | build:
15 | runs-on: ubuntu-latest
16 |
17 | steps:
18 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
19 |
20 | - name: Read .nvmrc
21 | id: node_version
22 | run: echo ::set-output name=NODE_VERSION::$(cat .nvmrc)
23 |
24 | - name: Set up node
25 | uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
26 | with:
27 | node-version: ${{ steps.node_version.outputs.NODE_VERSION }}
28 |
29 | - name: Cache dependencies
30 | uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3
31 | with:
32 | path: ~/.npm
33 | key: ${{ runner.os }}-node-${{ steps.node_version.outputs.NODE_VERSION }}-${{ hashFiles('**/package-lock.json') }}
34 | restore-keys: |
35 | ${{ runner.os }}-node-${{ steps.node_version.outputs.NODE_VERSION }}
36 |
37 | - run: npm ci
38 | name: Install
39 |
40 | - run: npm run build
41 | name: Build
42 |
43 | - name: Upload gatsby artifact
44 | uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
45 | with:
46 | name: gatsby-build
47 | path: ./public
48 |
49 | deploy:
50 | runs-on: ubuntu-latest
51 | needs: build
52 |
53 | steps:
54 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
55 |
56 | - name: Set up Python
57 | uses: actions/setup-python@v5
58 | with:
59 | python-version: 3.12
60 |
61 | - name: Install Python dependencies
62 | run: |
63 | python -m pip install -U pip
64 | python -m pip install -r deployment/requirements.txt
65 |
66 | - name: Download gatsby artifact
67 | uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
68 | with:
69 | name: gatsby-build
70 | path: ./public
71 |
72 | - name: Install tutorials into site
73 | run: |
74 | python deployment/installtutorials.py --dest public/tutorials
75 |
76 | - name: List website content
77 | run: |
78 | tree public
79 |
80 | - name: Deploy to gh-pages
81 | uses: peaceiris/actions-gh-pages@4f9cc6602d3f66b9c108549d475ec49e8ef4d45e # v4.0.0
82 | with:
83 | github_token: ${{ secrets.GITHUB_TOKEN }}
84 | publish_dir: ./public
85 |
86 | - name: Upload site for indexing
87 | uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
88 | with:
89 | name: site-for-indexing
90 | path: ./public
91 |
92 | index:
93 | # this job effectively tests the indexing before the next job that
94 | # clears it (so that the site doesn't end up empty if the indexing fails)
95 | runs-on: ubuntu-latest
96 | needs: deploy
97 |
98 | steps:
99 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
100 |
101 | - name: Download site for indexing
102 | uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
103 | with:
104 | name: site-for-indexing
105 | path: ./public
106 |
107 | - name: Set up Python
108 | uses: actions/setup-python@v5
109 | with:
110 | python-version: 3.12
111 |
112 | - name: Install Python dependencies
113 | run: |
114 | python -m pip install -U pip
115 | python -m pip install -r deployment/requirements.txt
116 |
117 | - name: Pre-index tutorials
118 | id: preindex
119 | env:
120 | ALGOLIA_ID: ${{ secrets.ALGOLIA_ID }}
121 | ALGOLIA_KEY: ${{ secrets.ALGOLIA_KEY }}
122 | ALGOLIA_INDEX: ${{ secrets.ALGOLIA_INDEX }}
123 | run: |
124 | astropylibrarian index tutorial-site \
125 | public/tutorials \
126 | https://learn.astropy.org/tutorials
127 |
128 | - name: sleep
129 | run: sleep 5
130 |
131 | - name: Clear Algolia index
132 | id: clearindex
133 | if: steps.preindex.outcome == 'success'
134 | env:
135 | ALGOLIA_ID: ${{ secrets.ALGOLIA_ID }}
136 | ALGOLIA_KEY: ${{ secrets.ALGOLIA_KEY }}
137 | ALGOLIA_INDEX: ${{ secrets.ALGOLIA_INDEX }}
138 | run: |
139 | astropylibrarian clear-index \
140 | --algolia-id "$ALGOLIA_ID" \
141 | --algolia-key "$ALGOLIA_KEY" \
142 | --index "$ALGOLIA_INDEX"
143 |
144 | - name: sleep
145 | run: sleep 5
146 |
147 | - name: Index tutorials
148 | if: steps.clearindex.outcome == 'success'
149 | env:
150 | ALGOLIA_ID: ${{ secrets.ALGOLIA_ID }}
151 | ALGOLIA_KEY: ${{ secrets.ALGOLIA_KEY }}
152 | ALGOLIA_INDEX: ${{ secrets.ALGOLIA_INDEX }}
153 | run: |
154 | astropylibrarian index tutorial-site \
155 | public/tutorials \
156 | https://learn.astropy.org/tutorials
157 |
158 | - name: Index guides
159 | # continue on error because guides can be externally hosted; avoid a change to
160 | # them from preventing the tutorials updating on the site
161 | continue-on-error: true
162 | env:
163 | ALGOLIA_ID: ${{ secrets.ALGOLIA_ID }}
164 | ALGOLIA_KEY: ${{ secrets.ALGOLIA_KEY }}
165 | ALGOLIA_INDEX: ${{ secrets.ALGOLIA_INDEX }}
166 | run: |
167 | astropylibrarian index guide \
168 | http://www.astropy.org/ccd-reduction-and-photometry-guide/ \
169 | --algolia-id "$ALGOLIA_ID" \
170 | --algolia-key "$ALGOLIA_KEY" \
171 | --index "$ALGOLIA_INDEX" \
172 | --priority 2
173 |
--------------------------------------------------------------------------------
/src/contributing/index.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: 'Contributing to Learn Astropy'
3 | slug: '/contributing/'
4 | ---
5 |
6 | We are always interested in incorporating new tutorials into Learn Astropy and the Astropy Tutorials series. We welcome tutorials covering astro-relevant topics; they do not need to use the Astropy package in order to be hosted or indexed here. If you have astronomy tutorials that you would like to contribute, or if you have a separate tutorial series that you would like indexed by the Learn Astropy website, see below.
7 |
8 | ## Content Guidelines
9 |
10 | ### Overview
11 |
12 | - Each tutorial should have 3–5 explicit [Learning Goals](http://tll.mit.edu/help/intended-learning-outcomes), demonstrate ~2–3 pieces of functionality relevant to astronomy, and contain 2–3 demonstrations of generic but commonly used functionality (e.g., `numpy`, `matplotlib`).
13 | - Each tutorial should roughly follow this progression:
14 | - _Input/Output_: read in some data (use [astroquery](https://astroquery.readthedocs.io/en/latest/) where possible to query real astronomical datasets)
15 | - _Analysis_: do something insightful / useful with the data
16 | - _Visualization_: make a pretty figure (use [astropy.visualization](http://docs.astropy.org/en/stable/visualization/) where possible)
17 | - The tutorials must be compatible with the versions supported by the latest major release of the Astropy core package
18 |
19 | ### Introduction cell template
20 |
21 | The first cell in every tutorial notebook is a markdown cell used for the title, author list, keywords, and summary. All of this information should be contained in a single cell and should adhere to the following format:
22 |
23 | ```
24 | # Title name
25 |
26 | ## Authors
27 | Jane Smith (@GITHUB-ID, ORCID-ID), Jose Jones (@GITHUB-ID, ORCID-ID)
28 |
29 | ## Learning Goals
30 | * Query the ... dataset
31 | * Calculate ...
32 | * Display ...
33 |
34 | ## Keywords
35 | Example, example, example
36 |
37 | ## Companion Content
38 | Carroll & Ostlie 10.3, Binney & Tremaine 1.5
39 |
40 | ## Summary
41 | In this tutorial, we will download a data file, do something to it, and then
42 | visualize it.
43 | ```
44 |
45 | Please draw keywords from [this list](https://github.com/astropy-learn/astropy-tutorials/blob/main/resources/keywords.md).
46 |
47 | ### Code
48 |
49 | - Demonstrate good commenting practice
50 | - Add comments to sections of code that use concepts not included in the Learning Goals
51 | - Demonstrate best practices of variable names
52 | - Variables should be all lower case with words separated by underscores
53 | - Variable names should be descriptive, e.g., `galaxy_mass`, `u_mag`
54 | - Use the print function explicitly to display information about variables
55 | - As much as possible, comply with [PEP8](https://www.python.org/dev/peps/pep-0008/).
56 | - As much as possible, comply with Jupyter notebook style guides - [STScI style guide](https://github.com/spacetelescope/style-guides/blob/master/guides/jupyter-notebooks.md) and [Official Coding Style](https://docs.jupyter.org/en/stable/contributing/ipython-dev-guide/coding_style.html).
57 | - Imports
58 | - Do not use `from package import *`; import packages, classes, and functions explicitly
59 | - Follow recommended package name abbreviations:
60 | - `import numpy as np`
61 | - `import matplotlib as mpl`
62 | - `import matplotlib.pyplot as plt`
63 | - `import astropy.units as u`
64 | - `import astropy.coordinates as coord`
65 | - `from astropy.io import fits`
66 | - Display figures inline using matplotlib's inline backend:
67 | - `%matplotlib inline # make plots display in notebooks`
68 |
69 | ### Narrative
70 |
71 | - Please read through the other tutorials to get a sense of the desired tone and length.
72 | - Use [Markdown formatting](http://jupyter-notebook.readthedocs.io/en/latest/examples/Notebook/Working%20With%20Markdown%20Cells.html) in text cells for formatting, links, latex, and code snippets.
73 | - Titles should be short yet descriptive and emphasize the learning goals of the tutorial. Try to make the title appeal to a broad audience and avoid referencing a specific instrument, catalog, or anything wavelength dependent.
74 | - List all authors' full names (comma separated) and link to GitHub profiles and/or [ORCID iD](https://orcid.org/) when relevant.
75 | - Include [Learning Goals](http://tll.mit.edu/help/intended-learning-outcomes) at the top as a bulleted list.
76 | - Include Keywords as a comma separated list of topics, packages, and functions demonstrated.
77 | - The first paragraph should give a brief overview of the entire tutorial including relevant astronomy concepts.
78 | - Use the first-person inclusive plural ("we"). For example, "We are going to make a plot which...", or "Above, we did it the hard way, but here is the easier way..."
79 | - Section headings should be in the imperative mood. For example, "Download the data."
80 | - Avoid extraneous words such as "obviously", "just", "simply", or "easily." For example, avoid phrases like "we just have to do this one thing."
81 | - Use `
Note
` for Notes and `
Warning
` for Warnings (Markdown supports raw HTML)
82 |
83 | ## Procedure for contributing a notebook or set of notebooks
84 |
85 | To contribute tutorial content, open an issue in the [astropy-tutorials repository](https://github.com/astropy-learn/astropy-tutorials/issues). When you click 'New issue', select the 'Tutorial submission' option, completing all fields of that form. You have the option to have your tutorial made citable via an upload (by the Astropy Learn maintainers) to Zenodo. If you have any data files needed by the notebook to run, see the 'Data files' section below.
86 |
87 | Maintainers will review your notebook and may ask questions and/or suggest edits (e.g., to conform to the above content guidelines). When the review is complete and the tutorial is ready to be incorporated, maintainers will create a new repository for the tutorial, add the notebook(s), and upload your tutorial(s) to this website.
88 |
89 | ### Data files
90 |
91 | If your tutorial includes large data files (where large means >~ 1 MB), including them in the tutorial's repository would drastically slow down cloning of the repository. Instead, for files < 10 MB, we encourage use of the `astropy.utils.download_files` function, and we will host data files on the http://data.astropy.org server (or you can do this directly by opening a PR at the https://github.com/astropy/astropy-data repository). Alternatively, if the file size is > 10 MB, the data should be hosted on Zenodo. To do the former, use the following procedure:
92 |
93 | - Assuming you have a data file named `mydatafile.fits`, you can access the file in the notebook with something like this at the top of the notebook:
94 |
95 | ```
96 | from astropy.utils.data import download_file
97 |
98 | tutorialpath = ''
99 | mydatafilename1 = download_file(tutorialpath + 'mydatafile1.fits', cache=True)
100 | mydatafilename2 = download_file(tutorialpath + 'mydatafile2.dat', cache=True)
101 | ```
102 |
103 | And then use them like this:
104 |
105 | ```
106 | fits.open(mydatafilename1)
107 | ...
108 | with open(mydatafilename2) as f:
109 | ...
110 | ```
111 |
112 | If you do this, the only change necessary in your submission of the notebook to [astropy-tutorials](https://github.com/astropy-learn/astropy-tutorials/issues) will be to set `tutorialpath` to `'http://data.astropy.org/tutorials/My-tutorial-name/'`.
113 |
114 | For data files that are larger than 10 MB in size, we recommend hosting with Zenodo. To use this approach, follow these steps:
115 |
116 | - Sign up for an account at https://zenodo.org/ if you do not have one already.
117 |
118 | - Log in to Zenodo and perform a new upload. Follow the Zenodo instructions and complete all the required fields in order to have the data file(s) uploaded to their records. Once this is done you will have a link to share the data.
119 |
120 | - With the link to the data file record, which has the format `https://zenodo.org/api/records/:id`, an example HTTP GET request needed to retrieve the data using the Python package `requests` is shown below:
121 |
122 | ```
123 | import requests
124 | r = requests.get("https://zenodo.org/api/records/1234)
125 | ```
126 |
127 | To use the output as a locally stored file, you would first need to write the file contents to a file, for example:
128 |
129 | ```
130 | with open('./some-data-file.fits', 'wb') as f:
131 | f.write(r.content)
132 | ```
133 |
--------------------------------------------------------------------------------
/static/astropy_logo_notext.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
253 |
--------------------------------------------------------------------------------