diff --git a/catalog/app/containers/Bucket/Code.js b/catalog/app/containers/Bucket/CodeSamples/Code.tsx
similarity index 59%
rename from catalog/app/containers/Bucket/Code.js
rename to catalog/app/containers/Bucket/CodeSamples/Code.tsx
index f27e888ae25..973772b1b34 100644
--- a/catalog/app/containers/Bucket/Code.js
+++ b/catalog/app/containers/Bucket/CodeSamples/Code.tsx
@@ -5,11 +5,12 @@ import * as M from '@material-ui/core'
import * as Lab from '@material-ui/lab'
import * as Notifications from 'containers/Notifications'
+import StyledLink from 'utils/StyledLink'
import copyToClipboard from 'utils/clipboard'
-import Section from './Section'
+import Section, { SectionProps } from '../Section'
-function highlight(lang, str) {
+function highlight(str: string, lang?: string) {
if (lang && hljs.getLanguage(lang)) {
try {
const { value } = hljs.highlight(str, { language: lang })
@@ -23,6 +24,46 @@ function highlight(lang, str) {
return str
}
+const useLineOfCodeStyles = M.makeStyles((t) => ({
+ root: {
+ fontFamily: t.typography.monospace.fontFamily,
+ fontSize: t.typography.body2.fontSize,
+ overflowX: 'auto',
+ overflowY: 'hidden',
+ whiteSpace: 'pre',
+ minHeight: t.typography.body2.fontSize,
+ display: 'flex',
+ alignItems: 'flex-end',
+ '&:hover $help': {
+ opacity: 1,
+ },
+ },
+ help: {
+ display: 'inline-flex',
+ marginLeft: t.spacing(0.5),
+ opacity: 0.3,
+ },
+}))
+
+interface LineOfCodeProps {
+ lang?: string
+ text: string
+ help?: string
+}
+function LineOfCode({ lang, text, help }: LineOfCodeProps) {
+ const classes = useLineOfCodeStyles()
+ return (
+
+ {highlight(text, lang)}
+ {help && (
+
+ [?]
+
+ )}
+
+ )
+}
+
const useStyles = M.makeStyles((t) => ({
container: {
alignItems: 'center',
@@ -35,16 +76,17 @@ const useStyles = M.makeStyles((t) => ({
height: 32,
},
code: {
- fontFamily: t.typography.monospace.fontFamily,
- fontSize: t.typography.body2.fontSize,
- overflowX: 'auto',
- overflowY: 'hidden',
- whiteSpace: 'pre',
+ width: '100%',
},
}))
+interface CodeProps extends Partial {
+ children: { label: string; contents: string; hl?: string }[]
+ defaultSelected?: number
+}
+
// children: [{ label: str, contents: str, hl: lang }]
-export default function Code({ defaultSelected = 0, children, ...props }) {
+export default function Code({ defaultSelected = 0, children, ...props }: CodeProps) {
const classes = useStyles()
const { push } = Notifications.use()
@@ -69,6 +111,27 @@ export default function Code({ defaultSelected = 0, children, ...props }) {
[selected.contents, push],
)
+ const lines = React.useMemo(
+ () =>
+ selected.contents.split('\n').map((line, index) => {
+ // Find [[ URL ]] and put it to help prop
+ const matched = line.match(/(.*) \[\[(.*)\]\]/)
+ const key = selected.label + index
+ if (!matched || !matched[1] || !matched[2]) {
+ return {
+ key,
+ text: line,
+ }
+ }
+ return {
+ help: matched[2],
+ key,
+ text: matched[1],
+ }
+ }),
+ [selected.contents, selected.label],
+ )
+
return (
- {highlight(selected.hl, selected.contents)}
+
+ {lines.map((line) => (
+
+ ))}
+
)
}
diff --git a/catalog/app/containers/Bucket/CodeSamples/Dir.tsx b/catalog/app/containers/Bucket/CodeSamples/Dir.tsx
new file mode 100644
index 00000000000..bfd77c042be
--- /dev/null
+++ b/catalog/app/containers/Bucket/CodeSamples/Dir.tsx
@@ -0,0 +1,54 @@
+import { basename } from 'path'
+
+import dedent from 'dedent'
+import * as React from 'react'
+
+import { docs } from 'constants/urls'
+
+import type { SectionProps } from '../Section'
+
+import Code from './Code'
+
+const TEMPLATES = {
+ PY: (bucket: string, path: string, dest: string) =>
+ dedent`
+ import quilt3 as q3
+ b = q3.Bucket("s3://${bucket}")
+ # List files [[${docs}/api-reference/bucket#bucket.ls]]
+ b.ls("${path}")
+ # Download [[${docs}/api-reference/bucket#bucket.fetch]]
+ b.fetch("${path}", "./${dest}")
+ `,
+ CLI: (bucket: string, path: string, dest: string) =>
+ dedent`
+ # List files [[https://docs.aws.amazon.com/cli/latest/reference/s3/ls.html]]
+ aws s3 ls "s3://${bucket}/${path}"
+ # Download [[https://docs.aws.amazon.com/cli/latest/reference/s3/cp.html]]
+ aws s3 cp --recursive "s3://${bucket}/${path}" "./${dest}"
+ `,
+}
+
+interface DirCodeSamplesProps extends Partial {
+ bucket: string
+ path: string
+}
+
+export default function DirCodeSamples({ bucket, path, ...props }: DirCodeSamplesProps) {
+ const dest = path ? basename(path) : bucket
+ const code = React.useMemo(
+ () => [
+ {
+ label: 'Python',
+ hl: 'python',
+ contents: TEMPLATES.PY(bucket, path, dest),
+ },
+ {
+ label: 'CLI',
+ hl: 'bash',
+ contents: TEMPLATES.CLI(bucket, path, dest),
+ },
+ ],
+ [bucket, path, dest],
+ )
+ return {code}
+}
diff --git a/catalog/app/containers/Bucket/CodeSamples/File.tsx b/catalog/app/containers/Bucket/CodeSamples/File.tsx
new file mode 100644
index 00000000000..f1eea186809
--- /dev/null
+++ b/catalog/app/containers/Bucket/CodeSamples/File.tsx
@@ -0,0 +1,53 @@
+import { basename } from 'path'
+
+import dedent from 'dedent'
+import * as React from 'react'
+
+import { docs } from 'constants/urls'
+
+import type { SectionProps } from '../Section'
+
+import Code from './Code'
+
+const TEMPLATES = {
+ PY: (bucket: string, path: string) =>
+ dedent`
+ import quilt3 as q3
+ b = q3.Bucket("s3://${bucket}")
+ # Download [[${docs}/api-reference/bucket#bucket.fetch]]
+ b.fetch("${path}", "./${basename(path)}")
+ `,
+ CLI: (bucket: string, path: string) =>
+ dedent`
+ # Download [[https://docs.aws.amazon.com/cli/latest/reference/s3/cp.html]]
+ aws s3 cp "s3://${bucket}/${path}" .
+ `,
+}
+
+interface FileCodeSamplesProps extends Partial {
+ bucket: string
+ path: string
+}
+
+export default function FileCodeSamples({
+ bucket,
+ path,
+ ...props
+}: FileCodeSamplesProps) {
+ const code = React.useMemo(
+ () => [
+ {
+ label: 'Python',
+ hl: 'python',
+ contents: TEMPLATES.PY(bucket, path),
+ },
+ {
+ label: 'CLI',
+ hl: 'bash',
+ contents: TEMPLATES.CLI(bucket, path),
+ },
+ ],
+ [bucket, path],
+ )
+ return {code}
+}
diff --git a/catalog/app/containers/Bucket/CodeSamples/Package.tsx b/catalog/app/containers/Bucket/CodeSamples/Package.tsx
new file mode 100644
index 00000000000..6a074666f0c
--- /dev/null
+++ b/catalog/app/containers/Bucket/CodeSamples/Package.tsx
@@ -0,0 +1,91 @@
+import dedent from 'dedent'
+
+import * as R from 'ramda'
+import * as React from 'react'
+
+import { docs } from 'constants/urls'
+import * as PackageUri from 'utils/PackageUri'
+import * as s3paths from 'utils/s3paths'
+
+import type { SectionProps } from '../Section'
+
+import Code from './Code'
+
+const TEMPLATES = {
+ PY: (bucket: string, name: string, path: string, hashDisplay: string) => {
+ const pathPy = path && `, path="${s3paths.ensureNoSlash(path)}"`
+ const hashPy = hashDisplay && `, top_hash="${hashDisplay}"`
+ return dedent`
+ import quilt3 as q3
+ # Browse [[${docs}/api-reference/package#package.browse]]
+ p = q3.Package.browse("${name}"${hashPy}, registry="s3://${bucket}")
+ # make changes to package adding individual files [[${docs}/api-reference/package#package.set]]
+ p.set("data.csv", "data.csv")
+ # or whole directories [[${docs}/api-reference/package#package.set_dir]]
+ p.set_dir("subdir", "subdir")
+ # and push changes [[${docs}/api-reference/package#package.push]]
+ p.push("${name}", registry="s3://${bucket}", message="Hello World")
+
+ # Download (be mindful of large packages) [[${docs}/api-reference/package#package.push]]
+ q3.Package.install("${name}"${pathPy}${hashPy}, registry="s3://${bucket}", dest=".")
+ `
+ },
+ CLI_DOWNLOAD: (bucket: string, name: string, path: string, hashDisplay: string) => {
+ const pathCli = path && ` --path "${s3paths.ensureNoSlash(path)}"`
+ const hashCli = hashDisplay && ` --top-hash ${hashDisplay}`
+ return dedent`
+ # Download package [[${docs}/api-reference/cli#install]]
+ quilt3 install "${name}"${pathCli}${hashCli} --registry s3://${bucket} --dest .
+ `
+ },
+ CLI_UPLOAD: (bucket: string, name: string) =>
+ dedent`
+ # Upload package [[${docs}/api-reference/cli#push]]
+ echo "Hello World" > README.md
+ quilt3 push "${name}" --registry s3://${bucket} --dir .
+ `,
+}
+
+interface PackageCodeSamplesProps extends Partial {
+ bucket: string
+ name: string
+ hash: string
+ hashOrTag: string
+ path: string
+}
+
+export default function PackageCodeSamples({
+ bucket,
+ name,
+ hash,
+ hashOrTag,
+ path,
+ ...props
+}: PackageCodeSamplesProps) {
+ const hashDisplay = hashOrTag === 'latest' ? '' : R.take(10, hash)
+ const code = React.useMemo(
+ () => [
+ {
+ label: 'Python',
+ hl: 'python',
+ contents: TEMPLATES.PY(bucket, name, path, hashDisplay),
+ },
+ {
+ label: 'CLI',
+ hl: 'bash',
+ contents: [
+ TEMPLATES.CLI_DOWNLOAD(bucket, name, path, hashDisplay),
+ !path ? TEMPLATES.CLI_UPLOAD(bucket, name) : '',
+ ]
+ .filter(Boolean)
+ .join('\n\n'),
+ },
+ {
+ label: 'URI',
+ contents: PackageUri.stringify({ bucket, name, hash, path }),
+ },
+ ],
+ [bucket, name, hashDisplay, hash, path],
+ )
+ return {code}
+}
diff --git a/catalog/app/containers/Bucket/Dir.tsx b/catalog/app/containers/Bucket/Dir.tsx
index f0d841c8db4..3c3f2f8f69c 100644
--- a/catalog/app/containers/Bucket/Dir.tsx
+++ b/catalog/app/containers/Bucket/Dir.tsx
@@ -1,6 +1,5 @@
-import { basename, join } from 'path'
+import { join } from 'path'
-import dedent from 'dedent'
import * as R from 'ramda'
import * as React from 'react'
import * as RRDom from 'react-router-dom'
@@ -23,7 +22,7 @@ import parseSearch from 'utils/parseSearch'
import * as s3paths from 'utils/s3paths'
import type * as workflows from 'utils/workflows'
-import Code from './Code'
+import DirCodeSamples from './CodeSamples/Dir'
import * as FileView from './FileView'
import { Item, Listing, PrefixFilter } from './Listing'
import Menu from './Menu'
@@ -289,35 +288,6 @@ export default function Dir({
const prefs = BucketPreferences.use()
const { prefix } = parseSearch(l.search)
const path = s3paths.decode(encodedPath)
- const dest = path ? basename(path) : bucket
-
- const code = React.useMemo(
- () => [
- {
- label: 'Python',
- hl: 'python',
- contents: dedent`
- import quilt3 as q3
- b = q3.Bucket("s3://${bucket}")
- # list files
- b.ls("${path}")
- # download
- b.fetch("${path}", "./${dest}")
- `,
- },
- {
- label: 'CLI',
- hl: 'bash',
- contents: dedent`
- # list files
- aws s3 ls "s3://${bucket}/${path}"
- # download
- aws s3 cp --recursive "s3://${bucket}/${path}" "./${dest}"
- `,
- },
- ],
- [bucket, path, dest],
- )
const [prev, setPrev] = React.useState(null)
@@ -417,7 +387,8 @@ export default function Dir({
{BucketPreferences.Result.match(
{
- Ok: ({ ui: { blocks } }) => blocks.code && {code}
,
+ Ok: ({ ui: { blocks } }) =>
+ blocks.code && ,
Pending: () => null,
Init: () => null,
},
diff --git a/catalog/app/containers/Bucket/File.js b/catalog/app/containers/Bucket/File.js
index a34f316dcb7..0050c71efb3 100644
--- a/catalog/app/containers/Bucket/File.js
+++ b/catalog/app/containers/Bucket/File.js
@@ -1,7 +1,6 @@
import { basename } from 'path'
import * as dateFns from 'date-fns'
-import dedent from 'dedent'
import * as R from 'ramda'
import * as React from 'react'
import { Link, useHistory } from 'react-router-dom'
@@ -30,7 +29,7 @@ import parseSearch from 'utils/parseSearch'
import { up, decode, handleToHttpsUri } from 'utils/s3paths'
import { readableBytes, readableQuantity } from 'utils/string'
-import Code from './Code'
+import FileCodeSamples from './CodeSamples/File'
import FileProperties from './FileProperties'
import * as FileView from './FileView'
import Section from './Section'
@@ -336,28 +335,6 @@ export default function File({
const path = decode(encodedPath)
- const code = React.useMemo(
- () => [
- {
- label: 'Python',
- hl: 'python',
- contents: dedent`
- import quilt3 as q3
- b = q3.Bucket("s3://${bucket}")
- b.fetch("${path}", "./${basename(path)}")
- `,
- },
- {
- label: 'CLI',
- hl: 'bash',
- contents: dedent`
- aws s3 cp "s3://${bucket}/${path}" .
- `,
- },
- ],
- [bucket, path],
- )
-
const [resetKey, setResetKey] = React.useState(0)
const objExistsData = useData(requests.getObjectExistence, {
s3,
@@ -521,7 +498,7 @@ export default function File({
{
Ok: ({ ui: { blocks } }) => (
<>
- {blocks.code && {code}
}
+ {blocks.code && }
{!!cfg.analyticsBucket && !!blocks.analytics && (
)}
diff --git a/catalog/app/containers/Bucket/PackageTree/PackageTree.tsx b/catalog/app/containers/Bucket/PackageTree/PackageTree.tsx
index 72ab422f8e6..4420286d0c1 100644
--- a/catalog/app/containers/Bucket/PackageTree/PackageTree.tsx
+++ b/catalog/app/containers/Bucket/PackageTree/PackageTree.tsx
@@ -1,6 +1,5 @@
import { basename } from 'path'
-import dedent from 'dedent'
import * as R from 'ramda'
import * as React from 'react'
import * as RRDom from 'react-router-dom'
@@ -21,18 +20,16 @@ import * as AWS from 'utils/AWS'
import * as BucketPreferences from 'utils/BucketPreferences'
import Data from 'utils/Data'
import * as GQL from 'utils/GraphQL'
-// import * as LinkedData from 'utils/LinkedData'
import * as LogicalKeyResolver from 'utils/LogicalKeyResolver'
import MetaTitle from 'utils/MetaTitle'
import * as NamedRoutes from 'utils/NamedRoutes'
-import * as PackageUri from 'utils/PackageUri'
import assertNever from 'utils/assertNever'
import parseSearch from 'utils/parseSearch'
import * as s3paths from 'utils/s3paths'
import usePrevious from 'utils/usePrevious'
import * as workflows from 'utils/workflows'
-import Code from '../Code'
+import PackageCodeSamples from '../CodeSamples/Package'
import * as Download from '../Download'
import { FileProperties } from '../FileProperties'
import * as FileView from '../FileView'
@@ -58,63 +55,6 @@ import DIR_QUERY from './gql/Dir.generated'
import FILE_QUERY from './gql/File.generated'
import DELETE_REVISION from './gql/DeleteRevision.generated'
-interface PkgCodeProps {
- bucket: string
- name: string
- hash: string
- hashOrTag: string
- path: string
-}
-
-function PkgCode({ bucket, name, hash, hashOrTag, path }: PkgCodeProps) {
- const pathCli = path && ` --path "${s3paths.ensureNoSlash(path)}"`
- const pathPy = path && `, path="${s3paths.ensureNoSlash(path)}"`
- const hashDisplay = hashOrTag === 'latest' ? '' : R.take(10, hash)
- const hashPy = hashDisplay && `, top_hash="${hashDisplay}"`
- const hashCli = hashDisplay && ` --top-hash ${hashDisplay}`
- const code = [
- {
- label: 'Python',
- hl: 'python',
- contents: dedent`
- import quilt3 as q3
- # Browse
- p = q3.Package.browse("${name}"${hashPy}, registry="s3://${bucket}")
- # make changes to package adding individual files
- p.set("data.csv", "data.csv")
- # or whole directories
- p.set_dir("subdir", "subdir")
- # and push changes
- p.push("${name}", registry="s3://${bucket}", message="Hello World")
-
- # Download (be mindful of large packages)
- q3.Package.install("${name}"${pathPy}${hashPy}, registry="s3://${bucket}", dest=".")
- `,
- },
- {
- label: 'CLI',
- hl: 'bash',
- contents:
- dedent`
- # Download package
- quilt3 install "${name}"${pathCli}${hashCli} --registry s3://${bucket} --dest .
- ` +
- (!path
- ? dedent`\n
- # Upload package
- echo "Hello World" > README.md
- quilt3 push "${name}" --registry s3://${bucket} --dir .
- `
- : ''),
- },
- {
- label: 'URI',
- contents: PackageUri.stringify({ bucket, name, hash, path }),
- },
- ]
- return {code}
-}
-
const useTopBarStyles = M.makeStyles((t) => ({
topBar: {
alignItems: 'flex-end',
@@ -454,7 +394,7 @@ function DirDisplay({
Ok: ({ ui: { blocks } }) => (
<>
{blocks.code && (
-
+
)}
{blocks.meta && (
@@ -726,7 +666,7 @@ function FileDisplay({
Ok: ({ ui: { blocks } }) => (
<>
{blocks.code && (
-
+
)}
{blocks.meta && (
diff --git a/catalog/app/embed/Dir.js b/catalog/app/embed/Dir.js
index 589402f627e..9992cebce12 100644
--- a/catalog/app/embed/Dir.js
+++ b/catalog/app/embed/Dir.js
@@ -1,6 +1,5 @@
import { basename } from 'path'
-import dedent from 'dedent'
import * as R from 'ramda'
import * as React from 'react'
import { useHistory } from 'react-router-dom'
@@ -15,7 +14,7 @@ import * as NamedRoutes from 'utils/NamedRoutes'
import parseSearch from 'utils/parseSearch'
import * as s3paths from 'utils/s3paths'
-import Code from 'containers/Bucket/Code'
+import DirCodeSamples from 'containers/Bucket/CodeSamples/Dir'
import * as FileView from 'containers/Bucket/FileView'
import { Listing, PrefixFilter } from 'containers/Bucket/Listing'
import Summary from 'containers/Bucket/Summary'
@@ -71,35 +70,6 @@ export default function Dir({
const s3 = AWS.S3.use()
const { prefix } = parseSearch(l.search)
const path = s3paths.decode(encodedPath)
- const dest = path ? basename(path) : bucket
-
- const code = React.useMemo(
- () => [
- {
- label: 'Python',
- hl: 'python',
- contents: dedent`
- import quilt3 as q3
- b = q3.Bucket("s3://${bucket}")
- # list files
- b.ls("${path}")
- # download
- b.fetch("${path}", "./${dest}")
- `,
- },
- {
- label: 'CLI',
- hl: 'bash',
- contents: dedent`
- # list files
- aws s3 ls "s3://${bucket}/${path}"
- # download
- aws s3 cp --recursive "s3://${bucket}/${path}" "./${dest}"
- `,
- },
- ],
- [bucket, path, dest],
- )
const [prev, setPrev] = React.useState(null)
@@ -164,7 +134,7 @@ export default function Dir({
)}
- {!ecfg.hideCode && {code}
}
+ {!ecfg.hideCode && }
{data.case({
Err: displayError(),
diff --git a/catalog/app/embed/File.js b/catalog/app/embed/File.js
index e6f9a2dca74..cd3787ea7e9 100644
--- a/catalog/app/embed/File.js
+++ b/catalog/app/embed/File.js
@@ -1,7 +1,6 @@
import { basename } from 'path'
import * as dateFns from 'date-fns'
-import dedent from 'dedent'
import * as R from 'ramda'
import * as React from 'react'
import { Link } from 'react-router-dom'
@@ -25,7 +24,7 @@ import parseSearch from 'utils/parseSearch'
import * as s3paths from 'utils/s3paths'
import { readableBytes, readableQuantity } from 'utils/string'
-import Code from 'containers/Bucket/Code'
+import FileCodeSamples from 'containers/Bucket/CodeSamples/File'
import FileProperties from 'containers/Bucket/FileProperties'
import * as FileView from 'containers/Bucket/FileView'
import Section from 'containers/Bucket/Section'
@@ -363,28 +362,6 @@ export default function File({
const path = s3paths.decode(encodedPath)
- const code = React.useMemo(
- () => [
- {
- label: 'Python',
- hl: 'python',
- contents: dedent`
- import quilt3 as q3
- b = q3.Bucket("s3://${bucket}")
- b.fetch("${path}", "./${basename(path)}")
- `,
- },
- {
- label: 'CLI',
- hl: 'bash',
- contents: dedent`
- aws s3 cp "s3://${bucket}/${path}" .
- `,
- },
- ],
- [bucket, path],
- )
-
const objExistsData = useData(requests.getObjectExistence, { s3, bucket, key: path })
const versionExistsData = useData(requests.getObjectExistence, {
s3,
@@ -483,7 +460,7 @@ export default function File({
Ok: requests.ObjectExistence.case({
Exists: () => (
<>
- {!ecfg.hideCode && {code}
}
+ {!ecfg.hideCode && }
{!ecfg.hideAnalytics && !!cfg.analyticsBucket && (
)}
diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md
index ac50c175dd8..4df6c5d8055 100644
--- a/docs/CHANGELOG.md
+++ b/docs/CHANGELOG.md
@@ -25,6 +25,7 @@ Entries inside each section should be ordered by type:
* [Fixed] Fix Header's orange flash on load ([#3487](https://github.com/quiltdata/quilt/pull/3487))
* [Fixed] Fix code sample for package push ([#3499](https://github.com/quiltdata/quilt/pull/3499))
* [Added] Add filter for users and buckets tables in Admin dashboards ([#3480](https://github.com/quiltdata/quilt/pull/3480))
+* [Added] Add links to documentation and re-use code samples ([#3496](https://github.com/quiltdata/quilt/pull/3496))
* [Changed] Enable user selection in perspective grids ([#3453](https://github.com/quiltdata/quilt/pull/3453))
# 5.3.1 - 2023-05-02