Skip to content
This repository has been archived by the owner on Jun 4, 2019. It is now read-only.

Commit

Permalink
Dashboards + misc browser UI adjustments (#8)
Browse files Browse the repository at this point in the history
* utils/reactTools: createFactory: hoist statics

* utils/AWS/Signer: s3 url signing

* app: change AWS service providers order

* components/Markdown: pluggable image and link processing

* Browser: refactor, *dashboards* aka quilt_summarize.json

* Browser: summary stats WIP

* further work on dashboards feature

* package: add ramda

* utils/reactTools: extractProp helper

* utils/tagged: tagged union constructor

* utils: add AsyncResult, Maybe and Result types

* utils: add Resource type / tools

* work on Browser and AWS stuff

* package: rm react-modal

* Browser: adjust stat bar color

* Browser: download button

* Browser: adjust preview error text color

* SearchResults: fix link / path

* Browser: fix overflow on narrow widths

* Browser: thumbnails

* Browser/Listing: stats: show 0 files / 0 bytes

* Browser: vega spec validation, better error handling

* UserDocs: summaries description
  • Loading branch information
nl0 authored and akarve committed Nov 1, 2018
1 parent 8f90daf commit 0ea7fcd
Show file tree
Hide file tree
Showing 24 changed files with 1,521 additions and 427 deletions.
32 changes: 32 additions & 0 deletions UserDocs.md
Expand Up @@ -298,3 +298,35 @@ Refer to [Amazon's documentation](https://docs.aws.amazon.com/AmazonS3/latest/us
`he.put_file("local_directory/", "bucket/path/")` - this will perform a recursive copy, and is correct


## Catalog

### Summaries

Quilt summaries summarize data in your bucket.
Summaries combine several file types:

* Markdown (`.md`)
* [Vega specs](https://github.com/vega/vega) (`.json`)
* Jupyter notebooks (`.ipynb`)
* Images (`.jpe?g`, `.png`, `.gif`)
* HTML (`.html`)

Upload `quilt_summarize.json` to any directory where you want a summary
to appear.

`quilt_summarize.json` is a JSON list of supported files in your S3 bucket.
All files in the list are signed (for security) and rendered in order
when you visit the containing directory in the Quilt web catalog.

Paths are resolved relative to the containing `quilt_summarize.json` file.

Example:

```
[
"/vega_specs/chloropleth.json",
"./image.jpg",
"../notebooks/JupyterCon.ipynb",
"description.md"
]
```
2 changes: 1 addition & 1 deletion catalog/app/app.js
Expand Up @@ -74,8 +74,8 @@ const render = (messages) => {
credentialsSelector: AWSAuth.selectors.credentials,
region: config.aws.region,
}],
AWS.Signer.Provider,
AWS.S3.Provider,
AWS.Signer.Provider,
[AWS.ES.Provider, {
host: config.aws.elasticSearchUrl,
log: 'trace',
Expand Down
101 changes: 81 additions & 20 deletions catalog/app/components/Markdown/Markdown.js
@@ -1,16 +1,20 @@
import hljs from 'highlight.js';
import flow from 'lodash/flow';
import id from 'lodash/identity';
import memoize from 'lodash/memoize';
import PT from 'prop-types';
import React from 'react';
import { setPropTypes } from 'recompose';
import Remarkable from 'remarkable';
import { replaceEntities, escapeHtml } from 'remarkable/lib/common/utils';
import { replaceEntities, escapeHtml, unescapeMd } from 'remarkable/lib/common/utils';
import styled from 'styled-components';

import { composeComponent } from 'utils/reactTools';


// TODO: switch to pluggable react-aware renderer
// TODO: use react-router's Link component for local links

const highlight = (str, lang) => {
if (lang === 'none') {
return '';
Expand All @@ -36,34 +40,69 @@ const highlight = (str, lang) => {
const escape = flow(replaceEntities, escapeHtml);

/**
* Plugin for remarkable that disables image rendering.
* A Markdown (Remarkable) plugin. Takes a Remarkable instance and adjusts it.
*
* @typedef {function} MarkdownPlugin
*
* @param {Object} md Remarkable instance
* @param {Object} md Remarkable instance.
*/
const disableImg = (md) => {

/**
* Create a plugin for remarkable that does custom processing of image tags.
*
* @param {Object} options
* @param {bool} options.disable
* Don't show images, render them as they are in markdown contents (escaped).
* @param {function} options.process
* Function that takes an image object ({ alt, src, title }) and returns a
* (possibly modified) image object.
*
* @returns {MarkdownPlugin}
*/
const imageHandler = ({
disable = false,
process = id,
}) => (md) => {
// eslint-disable-next-line no-param-reassign
md.renderer.rules.image = (tokens, idx) => {
const t = tokens[idx];
const src = escape(t.src);
const title = t.title ? ` "${escape(t.title)}"` : '';
const alt = t.alt ? escape(t.alt) : '';
return `<span>![${alt}](${src}${title})</span>`;
const t = process(tokens[idx]);

if (disable) {
const alt = t.alt ? escape(t.alt) : '';
const src = escape(t.src);
const title = t.title ? ` "${escape(t.title)}"` : '';
return `<span>![${alt}](${src}${title})</span>`;
}

const src = escapeHtml(t.src);
const alt = t.alt ? escape(unescapeMd(t.alt)) : '';
const title = t.title ? ` title="${escape(t.title)}"` : '';
return `<img src="${src}" alt="${alt}"${title} />`;
};
};

/**
* Plugin for remarkable that adjusts link rendering:
* Create a plugin for remarkable that does custom processing of links.
*
* - adds rel="nofollow" attribute
* @param {Object} options
* @param {bool} options.nofollow
* Add rel="nofollow" attribute if true (default).
* @param {function} options.process
* Function that takes a link object ({ href, title }) and returns a
* (possibly modified) link object.
*
* @param {Object} md Remarkable instance
* @returns {MarkdownPlugin}
*/
const adjustLinks = (md) => {
const linkHandler = ({
nofollow = true,
process = id,
}) => (md) => {
// eslint-disable-next-line no-param-reassign
md.renderer.rules.link_open = (tokens, idx) => {
const t = tokens[idx];
const t = process(tokens[idx]);
const title = t.title ? ` title="${escape(t.title)}"` : '';
return `<a href="${escapeHtml(t.href)}" rel="nofollow"${title}>`;
const rel = nofollow ? ' rel="nofollow"' : '';
return `<a href="${escapeHtml(t.href)}"${rel}${title}>`;
};
};

Expand All @@ -77,15 +116,24 @@ const adjustLinks = (md) => {
*
* @returns {Object} Remarakable instance
*/
const getRenderer = memoize(({ images }) => {
const getRenderer = memoize(({
images,
processImg,
processLink,
}) => {
const md = new Remarkable('full', {
highlight,
html: false,
linkify: true,
typographer: true,
});
md.use(adjustLinks);
if (!images) md.use(disableImg);
md.use(linkHandler({
process: processLink,
}));
md.use(imageHandler({
disable: !images,
process: processImg,
}));
return md;
});

Expand All @@ -110,14 +158,27 @@ export default composeComponent('Markdown',
data: PT.string,
className: PT.string,
images: PT.bool,
processImg: PT.func,
processLink: PT.func,
}),
({ data, className = '', images = true }) => (
({
data,
className = '',
images = true,
processImg,
processLink,
}) => (
<Style
className={`markdown ${className}`}
dangerouslySetInnerHTML={{
// would prefer to render in a saga but md.render() fails when called
// in a generator
__html: getRenderer({ images }).render(data),
__html:
getRenderer({
images,
processImg,
processLink,
}).render(data),
}}
/>
));

0 comments on commit 0ea7fcd

Please sign in to comment.