Merge branch 'release-1.0.2' into release
This commit is contained in:
commit
435f40406c
56 changed files with 9201 additions and 385 deletions
2
.babelrc
2
.babelrc
|
@ -6,6 +6,7 @@
|
||||||
"env": {
|
"env": {
|
||||||
"production": {
|
"production": {
|
||||||
"plugins": [
|
"plugins": [
|
||||||
|
"babel-plugin-styled-components",
|
||||||
"transform-react-remove-prop-types",
|
"transform-react-remove-prop-types",
|
||||||
"@babel/plugin-transform-react-constant-elements",
|
"@babel/plugin-transform-react-constant-elements",
|
||||||
"@babel/plugin-transform-react-inline-elements",
|
"@babel/plugin-transform-react-inline-elements",
|
||||||
|
@ -48,6 +49,7 @@
|
||||||
},
|
},
|
||||||
"development": {
|
"development": {
|
||||||
"plugins": [
|
"plugins": [
|
||||||
|
"babel-plugin-styled-components",
|
||||||
"react-hot-loader/babel"
|
"react-hot-loader/babel"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@ API_URL=/editor
|
||||||
AWS_ACCESS_KEY=<your-aws-access-key>
|
AWS_ACCESS_KEY=<your-aws-access-key>
|
||||||
AWS_REGION=<your-aws-region>
|
AWS_REGION=<your-aws-region>
|
||||||
AWS_SECRET_KEY=<your-aws-secret-key>
|
AWS_SECRET_KEY=<your-aws-secret-key>
|
||||||
|
CORS_ALLOW_LOCALHOST=true
|
||||||
EMAIL_SENDER=<transactional-email-sender>
|
EMAIL_SENDER=<transactional-email-sender>
|
||||||
EMAIL_VERIFY_SECRET_TOKEN=whatever_you_want_this_to_be_it_only_matters_for_production
|
EMAIL_VERIFY_SECRET_TOKEN=whatever_you_want_this_to_be_it_only_matters_for_production
|
||||||
EXAMPLE_USER_EMAIL=examples@p5js.org
|
EXAMPLE_USER_EMAIL=examples@p5js.org
|
||||||
|
|
10
.eslintrc
10
.eslintrc
|
@ -77,5 +77,13 @@
|
||||||
"__SERVER__": true,
|
"__SERVER__": true,
|
||||||
"__DISABLE_SSR__": true,
|
"__DISABLE_SSR__": true,
|
||||||
"__DEVTOOLS__": true
|
"__DEVTOOLS__": true
|
||||||
}
|
},
|
||||||
|
"overrides": [
|
||||||
|
{
|
||||||
|
"files": ["*.stories.jsx"],
|
||||||
|
"rules": {
|
||||||
|
"import/no-extraneous-dependencies": "off"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -17,3 +17,5 @@ cert_chain.crt
|
||||||
localhost.crt
|
localhost.crt
|
||||||
localhost.key
|
localhost.key
|
||||||
privkey.pem
|
privkey.pem
|
||||||
|
|
||||||
|
storybook-static
|
||||||
|
|
29
.storybook/main.js
Normal file
29
.storybook/main.js
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
const path = require('path');
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
stories: ['../client/**/*.stories.(jsx|mdx)'],
|
||||||
|
addons: [
|
||||||
|
'@storybook/addon-actions',
|
||||||
|
'@storybook/addon-docs',
|
||||||
|
'@storybook/addon-knobs',
|
||||||
|
'@storybook/addon-links',
|
||||||
|
'storybook-addon-theme-playground/dist/register'
|
||||||
|
],
|
||||||
|
webpackFinal: async config => {
|
||||||
|
// do mutation to the config
|
||||||
|
|
||||||
|
const rules = config.module.rules;
|
||||||
|
|
||||||
|
// modify storybook's file-loader rule to avoid conflicts with svgr
|
||||||
|
const fileLoaderRule = rules.find(rule => rule.test.test('.svg'));
|
||||||
|
fileLoaderRule.exclude = path.resolve(__dirname, '../client');
|
||||||
|
|
||||||
|
// use svgr for svg files
|
||||||
|
rules.push({
|
||||||
|
test: /\.svg$/,
|
||||||
|
use: ["@svgr/webpack"],
|
||||||
|
})
|
||||||
|
|
||||||
|
return config;
|
||||||
|
},
|
||||||
|
};
|
31
.storybook/preview.js
Normal file
31
.storybook/preview.js
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { addDecorator, addParameters } from '@storybook/react';
|
||||||
|
import { withKnobs } from "@storybook/addon-knobs";
|
||||||
|
import { withThemePlayground } from 'storybook-addon-theme-playground';
|
||||||
|
import { ThemeProvider } from "styled-components";
|
||||||
|
|
||||||
|
import theme, { Theme } from '../client/theme';
|
||||||
|
|
||||||
|
addDecorator(withKnobs);
|
||||||
|
|
||||||
|
const themeConfigs = Object.values(Theme).map(
|
||||||
|
name => {
|
||||||
|
return { name, theme: theme[name] };
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
addDecorator(withThemePlayground({
|
||||||
|
theme: themeConfigs,
|
||||||
|
provider: ThemeProvider
|
||||||
|
}));
|
||||||
|
|
||||||
|
addParameters({
|
||||||
|
options: {
|
||||||
|
/**
|
||||||
|
* display the top-level grouping as a "root" in the sidebar
|
||||||
|
*/
|
||||||
|
showRoots: true,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
// addDecorator(storyFn => <ThemeProvider theme={theme}>{storyFn()}</ThemeProvider>);
|
240
client/common/Button.jsx
Normal file
240
client/common/Button.jsx
Normal file
|
@ -0,0 +1,240 @@
|
||||||
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import styled from 'styled-components';
|
||||||
|
import { Link } from 'react-router';
|
||||||
|
|
||||||
|
import { remSize, prop } from '../theme';
|
||||||
|
|
||||||
|
const kinds = {
|
||||||
|
block: 'block',
|
||||||
|
icon: 'icon',
|
||||||
|
inline: 'inline',
|
||||||
|
};
|
||||||
|
|
||||||
|
// The '&&&' will increase the specificity of the
|
||||||
|
// component's CSS so that it overrides the more
|
||||||
|
// general global styles
|
||||||
|
const StyledButton = styled.button`
|
||||||
|
&&& {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
width: max-content;
|
||||||
|
text-decoration: none;
|
||||||
|
|
||||||
|
color: ${prop('Button.default.foreground')};
|
||||||
|
background-color: ${prop('Button.default.background')};
|
||||||
|
cursor: pointer;
|
||||||
|
border: 2px solid ${prop('Button.default.border')};
|
||||||
|
border-radius: 2px;
|
||||||
|
padding: ${remSize(8)} ${remSize(25)};
|
||||||
|
line-height: 1;
|
||||||
|
|
||||||
|
svg * {
|
||||||
|
fill: ${prop('Button.default.foreground')};
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover:not(:disabled) {
|
||||||
|
color: ${prop('Button.hover.foreground')};
|
||||||
|
background-color: ${prop('Button.hover.background')};
|
||||||
|
border-color: ${prop('Button.hover.border')};
|
||||||
|
|
||||||
|
svg * {
|
||||||
|
fill: ${prop('Button.hover.foreground')};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&:active:not(:disabled) {
|
||||||
|
color: ${prop('Button.active.foreground')};
|
||||||
|
background-color: ${prop('Button.active.background')};
|
||||||
|
|
||||||
|
svg * {
|
||||||
|
fill: ${prop('Button.active.foreground')};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&:disabled {
|
||||||
|
color: ${prop('Button.disabled.foreground')};
|
||||||
|
background-color: ${prop('Button.disabled.background')};
|
||||||
|
border-color: ${prop('Button.disabled.border')};
|
||||||
|
cursor: not-allowed;
|
||||||
|
|
||||||
|
svg * {
|
||||||
|
fill: ${prop('Button.disabled.foreground')};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
> * + * {
|
||||||
|
margin-left: ${remSize(8)};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const StyledInlineButton = styled.button`
|
||||||
|
&&& {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
text-decoration: none;
|
||||||
|
|
||||||
|
color: ${prop('primaryTextColor')};
|
||||||
|
cursor: pointer;
|
||||||
|
border: none;
|
||||||
|
line-height: 1;
|
||||||
|
|
||||||
|
svg * {
|
||||||
|
fill: ${prop('primaryTextColor')};
|
||||||
|
}
|
||||||
|
|
||||||
|
&:disabled {
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
> * + * {
|
||||||
|
margin-left: ${remSize(8)};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const StyledIconButton = styled.button`
|
||||||
|
&&& {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
width: ${remSize(32)}px;
|
||||||
|
height: ${remSize(32)}px;
|
||||||
|
text-decoration: none;
|
||||||
|
|
||||||
|
color: ${prop('Button.default.foreground')};
|
||||||
|
background-color: ${prop('Button.hover.background')};
|
||||||
|
cursor: pointer;
|
||||||
|
border: 1px solid transparent;
|
||||||
|
border-radius: 50%;
|
||||||
|
padding: ${remSize(8)} ${remSize(25)};
|
||||||
|
line-height: 1;
|
||||||
|
|
||||||
|
&:hover:not(:disabled) {
|
||||||
|
color: ${prop('Button.hover.foreground')};
|
||||||
|
background-color: ${prop('Button.hover.background')};
|
||||||
|
|
||||||
|
svg * {
|
||||||
|
fill: ${prop('Button.hover.foreground')};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&:active:not(:disabled) {
|
||||||
|
color: ${prop('Button.active.foreground')};
|
||||||
|
background-color: ${prop('Button.active.background')};
|
||||||
|
|
||||||
|
svg * {
|
||||||
|
fill: ${prop('Button.active.foreground')};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&:disabled {
|
||||||
|
color: ${prop('Button.disabled.foreground')};
|
||||||
|
background-color: ${prop('Button.disabled.background')};
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
> * + * {
|
||||||
|
margin-left: ${remSize(8)};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A Button performs an primary action
|
||||||
|
*/
|
||||||
|
const Button = ({
|
||||||
|
children, href, kind, iconBefore, iconAfter, 'aria-label': ariaLabel, to, type, ...props
|
||||||
|
}) => {
|
||||||
|
const hasChildren = React.Children.count(children) > 0;
|
||||||
|
const content = <>{iconBefore}{hasChildren && <span>{children}</span>}{iconAfter}</>;
|
||||||
|
let StyledComponent = StyledButton;
|
||||||
|
|
||||||
|
if (kind === kinds.inline) {
|
||||||
|
StyledComponent = StyledInlineButton;
|
||||||
|
} else if (kind === kinds.icon) {
|
||||||
|
StyledComponent = StyledIconButton;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (href) {
|
||||||
|
return (
|
||||||
|
<StyledComponent
|
||||||
|
kind={kind}
|
||||||
|
as="a"
|
||||||
|
aria-label={ariaLabel}
|
||||||
|
href={href}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
{content}
|
||||||
|
</StyledComponent>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (to) {
|
||||||
|
return <StyledComponent kind={kind} as={Link} aria-label={ariaLabel} to={to} {...props}>{content}</StyledComponent>;
|
||||||
|
}
|
||||||
|
|
||||||
|
return <StyledComponent kind={kind} aria-label={ariaLabel} type={type} {...props}>{content}</StyledComponent>;
|
||||||
|
};
|
||||||
|
|
||||||
|
Button.defaultProps = {
|
||||||
|
'children': null,
|
||||||
|
'disabled': false,
|
||||||
|
'iconAfter': null,
|
||||||
|
'iconBefore': null,
|
||||||
|
'kind': kinds.block,
|
||||||
|
'href': null,
|
||||||
|
'aria-label': null,
|
||||||
|
'to': null,
|
||||||
|
'type': 'button',
|
||||||
|
};
|
||||||
|
|
||||||
|
Button.kinds = kinds;
|
||||||
|
|
||||||
|
Button.propTypes = {
|
||||||
|
/**
|
||||||
|
* The visible part of the button, telling the user what
|
||||||
|
* the action is
|
||||||
|
*/
|
||||||
|
'children': PropTypes.element,
|
||||||
|
/**
|
||||||
|
If the button can be activated or not
|
||||||
|
*/
|
||||||
|
'disabled': PropTypes.bool,
|
||||||
|
/**
|
||||||
|
* SVG icon to place after child content
|
||||||
|
*/
|
||||||
|
'iconAfter': PropTypes.element,
|
||||||
|
/**
|
||||||
|
* SVG icon to place before child content
|
||||||
|
*/
|
||||||
|
'iconBefore': PropTypes.element,
|
||||||
|
/**
|
||||||
|
* The kind of button - determines how it appears visually
|
||||||
|
*/
|
||||||
|
'kind': PropTypes.oneOf(Object.values(kinds)),
|
||||||
|
/**
|
||||||
|
* Specifying an href will use an <a> to link to the URL
|
||||||
|
*/
|
||||||
|
'href': PropTypes.string,
|
||||||
|
/*
|
||||||
|
* An ARIA Label used for accessibility
|
||||||
|
*/
|
||||||
|
'aria-label': PropTypes.string,
|
||||||
|
/**
|
||||||
|
* Specifying a to URL will use a react-router Link
|
||||||
|
*/
|
||||||
|
'to': PropTypes.string,
|
||||||
|
/**
|
||||||
|
* If using a button, then type is defines the type of button
|
||||||
|
*/
|
||||||
|
'type': PropTypes.oneOf(['button', 'submit']),
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Button;
|
70
client/common/Button.stories.jsx
Normal file
70
client/common/Button.stories.jsx
Normal file
|
@ -0,0 +1,70 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { action } from '@storybook/addon-actions';
|
||||||
|
import { boolean, text } from '@storybook/addon-knobs';
|
||||||
|
|
||||||
|
import Button from './Button';
|
||||||
|
import { GithubIcon, DropdownArrowIcon, PlusIcon } from './icons';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
title: 'Common/Button',
|
||||||
|
component: Button
|
||||||
|
};
|
||||||
|
|
||||||
|
export const AllFeatures = () => (
|
||||||
|
<Button
|
||||||
|
disabled={boolean('disabled', false)}
|
||||||
|
type="submit"
|
||||||
|
label={text('label', 'submit')}
|
||||||
|
>
|
||||||
|
{text('children', 'this is the button')}
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
|
||||||
|
export const SubmitButton = () => (
|
||||||
|
<Button type="submit" label="submit">This is a submit button</Button>
|
||||||
|
);
|
||||||
|
|
||||||
|
export const DefaultTypeButton = () => <Button label="login" onClick={action('onClick')}>Log In</Button>;
|
||||||
|
|
||||||
|
export const DisabledButton = () => <Button disabled label="login" onClick={action('onClick')}>Log In</Button>;
|
||||||
|
|
||||||
|
export const AnchorButton = () => (
|
||||||
|
<Button href="http://p5js.org" label="submit">Actually an anchor</Button>
|
||||||
|
);
|
||||||
|
|
||||||
|
export const ReactRouterLink = () => (
|
||||||
|
<Button to="./somewhere" label="submit">Actually a Link</Button>
|
||||||
|
);
|
||||||
|
|
||||||
|
export const ButtonWithIconBefore = () => (
|
||||||
|
<Button
|
||||||
|
iconBefore={<GithubIcon aria-label="Github logo" />}
|
||||||
|
>
|
||||||
|
Create
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
|
||||||
|
export const ButtonWithIconAfter = () => (
|
||||||
|
<Button
|
||||||
|
iconAfter={<GithubIcon aria-label="Github logo" />}
|
||||||
|
>
|
||||||
|
Create
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
|
||||||
|
export const InlineButtonWithIconAfter = () => (
|
||||||
|
<Button
|
||||||
|
iconAfter={<DropdownArrowIcon />}
|
||||||
|
kind={Button.kinds.inline}
|
||||||
|
>
|
||||||
|
File name
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
|
||||||
|
export const InlineIconOnlyButton = () => (
|
||||||
|
<Button
|
||||||
|
aria-label="Add to collection"
|
||||||
|
iconBefore={<PlusIcon />}
|
||||||
|
kind={Button.kinds.inline}
|
||||||
|
/>
|
||||||
|
);
|
51
client/common/Icons.jsx
Normal file
51
client/common/Icons.jsx
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import SortArrowUp from '../images/sort-arrow-up.svg';
|
||||||
|
import SortArrowDown from '../images/sort-arrow-down.svg';
|
||||||
|
import Github from '../images/github.svg';
|
||||||
|
import Google from '../images/google.svg';
|
||||||
|
import Plus from '../images/plus-icon.svg';
|
||||||
|
import Close from '../images/close.svg';
|
||||||
|
import DropdownArrow from '../images/down-filled-triangle.svg';
|
||||||
|
|
||||||
|
// HOC that adds the right web accessibility props
|
||||||
|
// https://www.scottohara.me/blog/2019/05/22/contextual-images-svgs-and-a11y.html
|
||||||
|
|
||||||
|
// could also give these a default size, color, etc. based on the theme
|
||||||
|
// Need to add size to these - like small icon, medium icon, large icon. etc.
|
||||||
|
function withLabel(SvgComponent) {
|
||||||
|
const Icon = (props) => {
|
||||||
|
const { 'aria-label': ariaLabel } = props;
|
||||||
|
if (ariaLabel) {
|
||||||
|
return (<SvgComponent
|
||||||
|
{...props}
|
||||||
|
aria-label={ariaLabel}
|
||||||
|
role="img"
|
||||||
|
focusable="false"
|
||||||
|
/>);
|
||||||
|
}
|
||||||
|
return (<SvgComponent
|
||||||
|
{...props}
|
||||||
|
aria-hidden
|
||||||
|
focusable="false"
|
||||||
|
/>);
|
||||||
|
};
|
||||||
|
|
||||||
|
Icon.propTypes = {
|
||||||
|
'aria-label': PropTypes.string
|
||||||
|
};
|
||||||
|
|
||||||
|
Icon.defaultProps = {
|
||||||
|
'aria-label': null
|
||||||
|
};
|
||||||
|
|
||||||
|
return Icon;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const SortArrowUpIcon = withLabel(SortArrowUp);
|
||||||
|
export const SortArrowDownIcon = withLabel(SortArrowDown);
|
||||||
|
export const GithubIcon = withLabel(Github);
|
||||||
|
export const GoogleIcon = withLabel(Google);
|
||||||
|
export const PlusIcon = withLabel(Plus);
|
||||||
|
export const CloseIcon = withLabel(Close);
|
||||||
|
export const DropdownArrowIcon = withLabel(DropdownArrow);
|
18
client/common/Icons.stories.jsx
Normal file
18
client/common/Icons.stories.jsx
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { select } from '@storybook/addon-knobs';
|
||||||
|
|
||||||
|
import * as icons from './icons';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
title: 'Common/Icons',
|
||||||
|
component: icons
|
||||||
|
};
|
||||||
|
|
||||||
|
export const AllIcons = () => {
|
||||||
|
const names = Object.keys(icons);
|
||||||
|
|
||||||
|
const SelectedIcon = icons[select('name', names, names[0])];
|
||||||
|
return (
|
||||||
|
<SelectedIcon />
|
||||||
|
);
|
||||||
|
};
|
51
client/common/icons.jsx
Normal file
51
client/common/icons.jsx
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import SortArrowUp from '../images/sort-arrow-up.svg';
|
||||||
|
import SortArrowDown from '../images/sort-arrow-down.svg';
|
||||||
|
import Github from '../images/github.svg';
|
||||||
|
import Google from '../images/google.svg';
|
||||||
|
import Plus from '../images/plus-icon.svg';
|
||||||
|
import Close from '../images/close.svg';
|
||||||
|
import DropdownArrow from '../images/down-filled-triangle.svg';
|
||||||
|
|
||||||
|
// HOC that adds the right web accessibility props
|
||||||
|
// https://www.scottohara.me/blog/2019/05/22/contextual-images-svgs-and-a11y.html
|
||||||
|
|
||||||
|
// could also give these a default size, color, etc. based on the theme
|
||||||
|
// Need to add size to these - like small icon, medium icon, large icon. etc.
|
||||||
|
function withLabel(SvgComponent) {
|
||||||
|
const Icon = (props) => {
|
||||||
|
const { 'aria-label': ariaLabel } = props;
|
||||||
|
if (ariaLabel) {
|
||||||
|
return (<SvgComponent
|
||||||
|
{...props}
|
||||||
|
aria-label={ariaLabel}
|
||||||
|
role="img"
|
||||||
|
focusable="false"
|
||||||
|
/>);
|
||||||
|
}
|
||||||
|
return (<SvgComponent
|
||||||
|
{...props}
|
||||||
|
aria-hidden
|
||||||
|
focusable="false"
|
||||||
|
/>);
|
||||||
|
};
|
||||||
|
|
||||||
|
Icon.propTypes = {
|
||||||
|
'aria-label': PropTypes.string
|
||||||
|
};
|
||||||
|
|
||||||
|
Icon.defaultProps = {
|
||||||
|
'aria-label': null
|
||||||
|
};
|
||||||
|
|
||||||
|
return Icon;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const SortArrowUpIcon = withLabel(SortArrowUp);
|
||||||
|
export const SortArrowDownIcon = withLabel(SortArrowDown);
|
||||||
|
export const GithubIcon = withLabel(Github);
|
||||||
|
export const GoogleIcon = withLabel(Google);
|
||||||
|
export const PlusIcon = withLabel(Plus);
|
||||||
|
export const CloseIcon = withLabel(Close);
|
||||||
|
export const DropdownArrowIcon = withLabel(DropdownArrow);
|
18
client/common/icons.stories.jsx
Normal file
18
client/common/icons.stories.jsx
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { select } from '@storybook/addon-knobs';
|
||||||
|
|
||||||
|
import * as icons from './icons';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
title: 'Common/Icons',
|
||||||
|
component: icons
|
||||||
|
};
|
||||||
|
|
||||||
|
export const AllIcons = () => {
|
||||||
|
const names = Object.keys(icons);
|
||||||
|
|
||||||
|
const SelectedIcon = icons[select('name', names, names[0])];
|
||||||
|
return (
|
||||||
|
<SelectedIcon />
|
||||||
|
);
|
||||||
|
};
|
|
@ -1,47 +1,9 @@
|
||||||
<?xml version="1.0" encoding="iso-8859-1"?>
|
<?xml version="1.0" encoding="iso-8859-1"?>
|
||||||
<!-- Generator: Adobe Illustrator 19.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
<svg width="32px" height="32px" viewBox="0 0 32 32" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
<g>
|
||||||
viewBox="0 0 512 512" style="enable-background:new 0 0 512 512;" xml:space="preserve">
|
<path d="M7.091875,19.338 L5.978,23.49625 L1.9068125,23.582375 C0.690125,21.3256875 0,18.74375 0,16 C0,13.3468125 0.64525,10.8448125 1.789,8.64175 L1.789875,8.64175 L5.414375,9.30625 L7.002125,12.909 C6.6698125,13.8778125 6.4886875,14.9178125 6.4886875,16 C6.4888125,17.1745 6.7015625,18.2998125 7.091875,19.338 Z" fill="#FBBB00"></path>
|
||||||
<path style="fill:#FBBB00;" d="M113.47,309.408L95.648,375.94l-65.139,1.378C11.042,341.211,0,299.9,0,256
|
<path d="M31.7204375,13.011 C31.9041875,13.978875 32,14.9784375 32,16 C32,17.1455 31.8795625,18.262875 31.650125,19.3406875 C30.87125,23.008375 28.8360625,26.211 26.01675,28.477375 L26.015875,28.4765 L21.450625,28.2435625 L20.8045,24.210125 C22.67525,23.113 24.13725,21.3960625 24.907375,19.3406875 L16.35175,19.3406875 L16.35175,13.011 L25.0321875,13.011 L31.7204375,13.011 Z" fill="#518EF8"></path>
|
||||||
c0-42.451,10.324-82.483,28.624-117.732h0.014l57.992,10.632l25.404,57.644c-5.317,15.501-8.215,32.141-8.215,49.456
|
<path d="M26.0158125,28.4765 L26.0166875,28.477375 C23.27475,30.6813125 19.791625,32 16,32 C9.9068125,32 4.60925,28.5943125 1.9068125,23.5824375 L7.091875,19.3380625 C8.4430625,22.9441875 11.92175,25.51125 16,25.51125 C17.7529375,25.51125 19.3951875,25.037375 20.804375,24.210125 L26.0158125,28.4765 Z" fill="#28B446"></path>
|
||||||
C103.821,274.792,107.225,292.797,113.47,309.408z"/>
|
<path d="M26.21275,3.6835 L21.0294375,7.927 C19.571,7.015375 17.847,6.48875 16,6.48875 C11.8294375,6.48875 8.2856875,9.1735625 7.0021875,12.909 L1.789875,8.64175 L1.789,8.64175 C4.451875,3.5076875 9.81625,0 16,0 C19.8821875,0 23.44175,1.382875 26.21275,3.6835 Z" fill="#F14336"></path>
|
||||||
<path style="fill:#518EF8;" d="M507.527,208.176C510.467,223.662,512,239.655,512,256c0,18.328-1.927,36.206-5.598,53.451
|
</g>
|
||||||
c-12.462,58.683-45.025,109.925-90.134,146.187l-0.014-0.014l-73.044-3.727l-10.338-64.535
|
</svg>
|
||||||
c29.932-17.554,53.324-45.025,65.646-77.911h-136.89V208.176h138.887L507.527,208.176L507.527,208.176z"/>
|
|
||||||
<path style="fill:#28B446;" d="M416.253,455.624l0.014,0.014C372.396,490.901,316.666,512,256,512
|
|
||||||
c-97.491,0-182.252-54.491-225.491-134.681l82.961-67.91c21.619,57.698,77.278,98.771,142.53,98.771
|
|
||||||
c28.047,0,54.323-7.582,76.87-20.818L416.253,455.624z"/>
|
|
||||||
<path style="fill:#F14336;" d="M419.404,58.936l-82.933,67.896c-23.335-14.586-50.919-23.012-80.471-23.012
|
|
||||||
c-66.729,0-123.429,42.957-143.965,102.724l-83.397-68.276h-0.014C71.23,56.123,157.06,0,256,0
|
|
||||||
C318.115,0,375.068,22.126,419.404,58.936z"/>
|
|
||||||
<g>
|
|
||||||
</g>
|
|
||||||
<g>
|
|
||||||
</g>
|
|
||||||
<g>
|
|
||||||
</g>
|
|
||||||
<g>
|
|
||||||
</g>
|
|
||||||
<g>
|
|
||||||
</g>
|
|
||||||
<g>
|
|
||||||
</g>
|
|
||||||
<g>
|
|
||||||
</g>
|
|
||||||
<g>
|
|
||||||
</g>
|
|
||||||
<g>
|
|
||||||
</g>
|
|
||||||
<g>
|
|
||||||
</g>
|
|
||||||
<g>
|
|
||||||
</g>
|
|
||||||
<g>
|
|
||||||
</g>
|
|
||||||
<g>
|
|
||||||
</g>
|
|
||||||
<g>
|
|
||||||
</g>
|
|
||||||
<g>
|
|
||||||
</g>
|
|
||||||
</svg>
|
|
||||||
|
|
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.5 KiB |
|
@ -1,9 +1,8 @@
|
||||||
<?xml version="1.0" encoding="iso-8859-1"?>
|
<?xml version="1.0" encoding="iso-8859-1"?>
|
||||||
<!-- Generator: Adobe Illustrator 15.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
<!-- Generator: Adobe Illustrator 15.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN" "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
|
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN" "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
|
||||||
<svg version="1.0" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
<svg width="10px" height="9px" viewBox="0 0 10 9" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||||
width="40px" height="30px" viewBox="0 0 100 100" style="enable-background:new 0 0 100 100;" xml:space="preserve">
|
<g transform="translate(0.666667, 0.333333)">
|
||||||
<g>
|
<polygon points="4.56711429 7.96345714 0.103114286 0.1872 8.99022857 0.1872"></polygon>
|
||||||
<path d="M49.761,67.969l-17.36-30.241h34.561L49.761,67.969z"/>
|
</g>
|
||||||
</g>
|
|
||||||
</svg>
|
</svg>
|
||||||
|
|
Before Width: | Height: | Size: 568 B After Width: | Height: | Size: 539 B |
|
@ -1,9 +1,8 @@
|
||||||
<?xml version="1.0" encoding="iso-8859-1"?>
|
<?xml version="1.0" encoding="iso-8859-1"?>
|
||||||
<!-- Generator: Adobe Illustrator 15.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
<!-- Generator: Adobe Illustrator 15.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN" "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
|
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN" "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
|
||||||
<svg version="1.0" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
<svg width="10px" height="9px" viewBox="0 0 10 9" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||||
width="40px" height="30px" viewBox="0 0 100 100" style="enable-background:new 0 0 100 100;" xml:space="preserve">
|
<g transform="translate(-0.200000, -0.200000)" >
|
||||||
<g>
|
<polygon points="4.93361111 0.202222222 9.75583333 8.6025 0.155833333 8.6025"></polygon>
|
||||||
<path d="M49.761,37.728l17.36,30.241H32.561L49.761,37.728z"/>
|
</g>
|
||||||
</g>
|
|
||||||
</svg>
|
</svg>
|
||||||
|
|
Before Width: | Height: | Size: 567 B After Width: | Height: | Size: 543 B |
|
@ -3,8 +3,10 @@ import { render } from 'react-dom';
|
||||||
import { hot } from 'react-hot-loader/root';
|
import { hot } from 'react-hot-loader/root';
|
||||||
import { Provider } from 'react-redux';
|
import { Provider } from 'react-redux';
|
||||||
import { Router, browserHistory } from 'react-router';
|
import { Router, browserHistory } from 'react-router';
|
||||||
|
|
||||||
import configureStore from './store';
|
import configureStore from './store';
|
||||||
import routes from './routes';
|
import routes from './routes';
|
||||||
|
import ThemeProvider from './modules/App/components/ThemeProvider';
|
||||||
|
|
||||||
require('./styles/main.scss');
|
require('./styles/main.scss');
|
||||||
|
|
||||||
|
@ -18,7 +20,9 @@ const store = configureStore(initialState);
|
||||||
|
|
||||||
const App = () => (
|
const App = () => (
|
||||||
<Provider store={store}>
|
<Provider store={store}>
|
||||||
<Router history={history} routes={routes(store)} />
|
<ThemeProvider>
|
||||||
|
<Router history={history} routes={routes(store)} />
|
||||||
|
</ThemeProvider>
|
||||||
</Provider>
|
</Provider>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
7
client/index.stories.mdx
Normal file
7
client/index.stories.mdx
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
import { Meta } from '@storybook/addon-docs/blocks';
|
||||||
|
|
||||||
|
<Meta title=" |Intro" />
|
||||||
|
|
||||||
|
# Welcome to the P5.js Web Editor Style Guide
|
||||||
|
|
||||||
|
This guide will contain all the components in the project, with examples of how they can be reused.
|
26
client/modules/App/components/ThemeProvider.jsx
Normal file
26
client/modules/App/components/ThemeProvider.jsx
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
import { ThemeProvider } from 'styled-components';
|
||||||
|
|
||||||
|
import theme, { Theme } from '../../../theme';
|
||||||
|
|
||||||
|
const Provider = ({ children, currentTheme }) => (
|
||||||
|
<ThemeProvider theme={{ ...theme[currentTheme] }}>
|
||||||
|
{children}
|
||||||
|
</ThemeProvider>
|
||||||
|
);
|
||||||
|
|
||||||
|
Provider.propTypes = {
|
||||||
|
children: PropTypes.node.isRequired,
|
||||||
|
currentTheme: PropTypes.oneOf(Object.keys(Theme)).isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
|
function mapStateToProps(state) {
|
||||||
|
return {
|
||||||
|
currentTheme: state.preferences.theme,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export default connect(mapStateToProps)(Provider);
|
|
@ -49,10 +49,10 @@ export function setNewProject(project) {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getProject(id) {
|
export function getProject(id, username) {
|
||||||
return (dispatch, getState) => {
|
return (dispatch, getState) => {
|
||||||
dispatch(justOpenedProject());
|
dispatch(justOpenedProject());
|
||||||
axios.get(`${ROOT_URL}/projects/${id}`, { withCredentials: true })
|
axios.get(`${ROOT_URL}/${username}/projects/${id}`, { withCredentials: true })
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
dispatch(setProject(response.data));
|
dispatch(setProject(response.data));
|
||||||
dispatch(setUnsavedChanges(false));
|
dispatch(setUnsavedChanges(false));
|
||||||
|
|
|
@ -3,90 +3,87 @@ import { metaKeyName, } from '../../../utils/metaKey';
|
||||||
|
|
||||||
function KeyboardShortcutModal() {
|
function KeyboardShortcutModal() {
|
||||||
return (
|
return (
|
||||||
<ul className="keyboard-shortcuts" title="keyboard shortcuts">
|
<div className="keyboard-shortcuts">
|
||||||
<li className="keyboard-shortcut-item">
|
<h3 className="keyboard-shortcuts__title">Code Editing</h3>
|
||||||
<span className="keyboard-shortcut__command">{'\u21E7'} + Tab</span>
|
<p className="keyboard-shortcuts__description">
|
||||||
<span>Tidy</span>
|
Code editing keyboard shortcuts follow <a href="https://shortcuts.design/toolspage-sublimetext.html" target="_blank" rel="noopener noreferrer">Sublime Text shortcuts</a>.
|
||||||
</li>
|
</p>
|
||||||
<li className="keyboard-shortcut-item">
|
<ul className="keyboard-shortcuts__list">
|
||||||
<span className="keyboard-shortcut__command">
|
<li className="keyboard-shortcut-item">
|
||||||
{metaKeyName} + S
|
<span className="keyboard-shortcut__command">{'\u21E7'} + Tab</span>
|
||||||
</span>
|
<span>Tidy</span>
|
||||||
<span>Save</span>
|
</li>
|
||||||
</li>
|
<li className="keyboard-shortcut-item">
|
||||||
<li className="keyboard-shortcut-item">
|
<span className="keyboard-shortcut__command">
|
||||||
<span className="keyboard-shortcut__command">
|
{metaKeyName} + F
|
||||||
{metaKeyName} + F
|
</span>
|
||||||
</span>
|
<span>Find Text</span>
|
||||||
<span>Find Text</span>
|
</li>
|
||||||
</li>
|
<li className="keyboard-shortcut-item">
|
||||||
<li className="keyboard-shortcut-item">
|
<span className="keyboard-shortcut__command">
|
||||||
<span className="keyboard-shortcut__command">
|
{metaKeyName} + G
|
||||||
{metaKeyName} + G
|
</span>
|
||||||
</span>
|
<span>Find Next Text Match</span>
|
||||||
<span>Find Next Text Match</span>
|
</li>
|
||||||
</li>
|
<li className="keyboard-shortcut-item">
|
||||||
<li className="keyboard-shortcut-item">
|
<span className="keyboard-shortcut__command">
|
||||||
<span className="keyboard-shortcut__command">
|
{metaKeyName} + {'\u21E7'} + G
|
||||||
{metaKeyName} + {'\u21E7'} + G
|
</span>
|
||||||
</span>
|
<span>Find Previous Text Match</span>
|
||||||
<span>Find Previous Text Match</span>
|
</li>
|
||||||
</li>
|
<li className="keyboard-shortcut-item">
|
||||||
<li className="keyboard-shortcut-item">
|
<span className="keyboard-shortcut__command">
|
||||||
<span className="keyboard-shortcut__command">
|
{metaKeyName} + [
|
||||||
{metaKeyName} + [
|
</span>
|
||||||
</span>
|
<span>Indent Code Left</span>
|
||||||
<span>Indent Code Left</span>
|
</li>
|
||||||
</li>
|
<li className="keyboard-shortcut-item">
|
||||||
<li className="keyboard-shortcut-item">
|
<span className="keyboard-shortcut__command">
|
||||||
<span className="keyboard-shortcut__command">
|
{metaKeyName} + ]
|
||||||
{metaKeyName} + ]
|
</span>
|
||||||
</span>
|
<span>Indent Code Right</span>
|
||||||
<span>Indent Code Right</span>
|
</li>
|
||||||
</li>
|
<li className="keyboard-shortcut-item">
|
||||||
<li className="keyboard-shortcut-item">
|
<span className="keyboard-shortcut__command">
|
||||||
<span className="keyboard-shortcut__command">
|
{metaKeyName} + /
|
||||||
{metaKeyName} + /
|
</span>
|
||||||
</span>
|
<span>Comment Line</span>
|
||||||
<span>Comment Line</span>
|
</li>
|
||||||
</li>
|
</ul>
|
||||||
<li className="keyboard-shortcut-item">
|
<h3 className="keyboard-shortcuts__title">General</h3>
|
||||||
<span className="keyboard-shortcut__command">
|
<ul className="keyboard-shortcuts__list">
|
||||||
{metaKeyName} + Enter
|
<li className="keyboard-shortcut-item">
|
||||||
</span>
|
<span className="keyboard-shortcut__command">
|
||||||
<span>Start Sketch</span>
|
{metaKeyName} + S
|
||||||
</li>
|
</span>
|
||||||
<li className="keyboard-shortcut-item">
|
<span>Save</span>
|
||||||
<span className="keyboard-shortcut__command">
|
</li>
|
||||||
{metaKeyName} + {'\u21E7'} + Enter
|
<li className="keyboard-shortcut-item">
|
||||||
</span>
|
<span className="keyboard-shortcut__command">
|
||||||
<span>Stop Sketch</span>
|
{metaKeyName} + Enter
|
||||||
</li>
|
</span>
|
||||||
<li className="keyboard-shortcut-item">
|
<span>Start Sketch</span>
|
||||||
<span className="keyboard-shortcut__command">
|
</li>
|
||||||
{metaKeyName} + {'\u21E7'} + 1
|
<li className="keyboard-shortcut-item">
|
||||||
</span>
|
<span className="keyboard-shortcut__command">
|
||||||
<span>Turn on Accessible Output</span>
|
{metaKeyName} + {'\u21E7'} + Enter
|
||||||
</li>
|
</span>
|
||||||
<li className="keyboard-shortcut-item">
|
<span>Stop Sketch</span>
|
||||||
<span className="keyboard-shortcut__command">
|
</li>
|
||||||
{metaKeyName} + {'\u21E7'} + 2
|
<li className="keyboard-shortcut-item">
|
||||||
</span>
|
<span className="keyboard-shortcut__command">
|
||||||
<span>Turn off Accessible Output</span>
|
{metaKeyName} + {'\u21E7'} + 1
|
||||||
</li>
|
</span>
|
||||||
<li className="keyboard-shortcut-item">
|
<span>Turn on Accessible Output</span>
|
||||||
<span className="keyboard-shortcut__command">
|
</li>
|
||||||
{metaKeyName} + B
|
<li className="keyboard-shortcut-item">
|
||||||
</span>
|
<span className="keyboard-shortcut__command">
|
||||||
<span>Toggle Sidebar</span>
|
{metaKeyName} + {'\u21E7'} + 2
|
||||||
</li>
|
</span>
|
||||||
<li className="keyboard-shortcut-item">
|
<span>Turn off Accessible Output</span>
|
||||||
<span className="keyboard-shortcut__command">
|
</li>
|
||||||
Ctrl + `
|
</ul>
|
||||||
</span>
|
</div>
|
||||||
<span>Toggle Console</span>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,6 +2,8 @@ import PropTypes from 'prop-types';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { domOnlyProps } from '../../../utils/reduxFormUtils';
|
import { domOnlyProps } from '../../../utils/reduxFormUtils';
|
||||||
|
|
||||||
|
import Button from '../../../common/Button';
|
||||||
|
|
||||||
class NewFileForm extends React.Component {
|
class NewFileForm extends React.Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
|
@ -33,12 +35,10 @@ class NewFileForm extends React.Component {
|
||||||
{...domOnlyProps(name)}
|
{...domOnlyProps(name)}
|
||||||
ref={(element) => { this.fileName = element; }}
|
ref={(element) => { this.fileName = element; }}
|
||||||
/>
|
/>
|
||||||
<input
|
<Button
|
||||||
type="submit"
|
type="submit"
|
||||||
value="Add File"
|
>Add File
|
||||||
aria-label="add file"
|
</Button>
|
||||||
className="new-file-form__submit"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
{name.touched && name.error && <span className="form-error">{name.error}</span>}
|
{name.touched && name.error && <span className="form-error">{name.error}</span>}
|
||||||
</form>
|
</form>
|
||||||
|
|
|
@ -2,6 +2,8 @@ import PropTypes from 'prop-types';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { domOnlyProps } from '../../../utils/reduxFormUtils';
|
import { domOnlyProps } from '../../../utils/reduxFormUtils';
|
||||||
|
|
||||||
|
import Button from '../../../common/Button';
|
||||||
|
|
||||||
class NewFolderForm extends React.Component {
|
class NewFolderForm extends React.Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
|
@ -34,12 +36,10 @@ class NewFolderForm extends React.Component {
|
||||||
ref={(element) => { this.fileName = element; }}
|
ref={(element) => { this.fileName = element; }}
|
||||||
{...domOnlyProps(name)}
|
{...domOnlyProps(name)}
|
||||||
/>
|
/>
|
||||||
<input
|
<Button
|
||||||
type="submit"
|
type="submit"
|
||||||
value="Add Folder"
|
>Add Folder
|
||||||
aria-label="add folder"
|
</Button>
|
||||||
className="new-folder-form__submit"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
{name.touched && name.error && <span className="form-error">{name.error}</span>}
|
{name.touched && name.error && <span className="form-error">{name.error}</span>}
|
||||||
</form>
|
</form>
|
||||||
|
|
|
@ -53,9 +53,9 @@ class IDEView extends React.Component {
|
||||||
|
|
||||||
this.props.stopSketch();
|
this.props.stopSketch();
|
||||||
if (this.props.params.project_id) {
|
if (this.props.params.project_id) {
|
||||||
const id = this.props.params.project_id;
|
const { project_id: id, username } = this.props.params;
|
||||||
if (id !== this.props.project.id) {
|
if (id !== this.props.project.id) {
|
||||||
this.props.getProject(id);
|
this.props.getProject(id, username);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -123,6 +123,11 @@ class IDEView extends React.Component {
|
||||||
this.autosaveInterval = null;
|
this.autosaveInterval = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getTitle = () => {
|
||||||
|
const { id } = this.props.project;
|
||||||
|
return id ? `p5.js Web Editor | ${this.props.project.name}` : 'p5.js Web Editor';
|
||||||
|
}
|
||||||
|
|
||||||
isUserOwner() {
|
isUserOwner() {
|
||||||
return this.props.project.owner && this.props.project.owner.id === this.props.user.id;
|
return this.props.project.owner && this.props.project.owner.id === this.props.user.id;
|
||||||
}
|
}
|
||||||
|
@ -203,7 +208,7 @@ class IDEView extends React.Component {
|
||||||
return (
|
return (
|
||||||
<div className="ide">
|
<div className="ide">
|
||||||
<Helmet>
|
<Helmet>
|
||||||
<title>p5.js Web Editor | {this.props.project.name}</title>
|
<title>{this.getTitle()}</title>
|
||||||
</Helmet>
|
</Helmet>
|
||||||
{this.props.toast.isVisible && <Toast />}
|
{this.props.toast.isVisible && <Toast />}
|
||||||
<Nav
|
<Nav
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
|
import Button from '../../../common/Button';
|
||||||
|
import { PlusIcon } from '../../../common/icons';
|
||||||
import CopyableInput from '../../IDE/components/CopyableInput';
|
import CopyableInput from '../../IDE/components/CopyableInput';
|
||||||
|
|
||||||
import APIKeyList from './APIKeyList';
|
import APIKeyList from './APIKeyList';
|
||||||
|
|
||||||
import PlusIcon from '../../../images/plus-icon.svg';
|
|
||||||
|
|
||||||
export const APIKeyPropType = PropTypes.shape({
|
export const APIKeyPropType = PropTypes.shape({
|
||||||
id: PropTypes.object.isRequired,
|
id: PropTypes.object.isRequired,
|
||||||
token: PropTypes.object,
|
token: PropTypes.object,
|
||||||
|
@ -80,14 +80,14 @@ class APIKeyForm extends React.Component {
|
||||||
type="text"
|
type="text"
|
||||||
value={this.state.keyLabel}
|
value={this.state.keyLabel}
|
||||||
/>
|
/>
|
||||||
<button
|
<Button
|
||||||
className="api-key-form__create-button"
|
|
||||||
disabled={this.state.keyLabel === ''}
|
disabled={this.state.keyLabel === ''}
|
||||||
|
iconBefore={<PlusIcon />}
|
||||||
|
label="Create new key"
|
||||||
type="submit"
|
type="submit"
|
||||||
>
|
>
|
||||||
<PlusIcon className="api-key-form__create-icon" focusable="false" aria-hidden="true" />
|
|
||||||
Create
|
Create
|
||||||
</button>
|
</Button>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { domOnlyProps } from '../../../utils/reduxFormUtils';
|
import { domOnlyProps } from '../../../utils/reduxFormUtils';
|
||||||
|
import Button from '../../../common/Button';
|
||||||
|
|
||||||
function AccountForm(props) {
|
function AccountForm(props) {
|
||||||
const {
|
const {
|
||||||
|
@ -44,11 +45,10 @@ function AccountForm(props) {
|
||||||
<span className="form__status"> Confirmation sent, check your email.</span>
|
<span className="form__status"> Confirmation sent, check your email.</span>
|
||||||
) :
|
) :
|
||||||
(
|
(
|
||||||
<button
|
<Button
|
||||||
className="form__action"
|
|
||||||
onClick={handleInitiateVerification}
|
onClick={handleInitiateVerification}
|
||||||
>Resend confirmation email
|
>Resend confirmation email
|
||||||
</button>
|
</Button>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
</p>
|
</p>
|
||||||
|
@ -92,12 +92,11 @@ function AccountForm(props) {
|
||||||
/>
|
/>
|
||||||
{newPassword.touched && newPassword.error && <span className="form-error">{newPassword.error}</span>}
|
{newPassword.touched && newPassword.error && <span className="form-error">{newPassword.error}</span>}
|
||||||
</p>
|
</p>
|
||||||
<input
|
<Button
|
||||||
type="submit"
|
type="submit"
|
||||||
disabled={submitting || invalid || pristine}
|
disabled={submitting || invalid || pristine}
|
||||||
value="Save All Settings"
|
>Save All Settings
|
||||||
aria-label="updateSettings"
|
</Button>
|
||||||
/>
|
|
||||||
</form>
|
</form>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,9 @@ import { connect } from 'react-redux';
|
||||||
import { Link } from 'react-router';
|
import { Link } from 'react-router';
|
||||||
import { bindActionCreators } from 'redux';
|
import { bindActionCreators } from 'redux';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
|
|
||||||
|
import Button from '../../../common/Button';
|
||||||
|
import { DropdownArrowIcon } from '../../../common/icons';
|
||||||
import * as ProjectActions from '../../IDE/actions/project';
|
import * as ProjectActions from '../../IDE/actions/project';
|
||||||
import * as ProjectsActions from '../../IDE/actions/projects';
|
import * as ProjectsActions from '../../IDE/actions/projects';
|
||||||
import * as CollectionsActions from '../../IDE/actions/collections';
|
import * as CollectionsActions from '../../IDE/actions/collections';
|
||||||
|
@ -20,7 +23,6 @@ import AddToCollectionSketchList from '../../IDE/components/AddToCollectionSketc
|
||||||
import CopyableInput from '../../IDE/components/CopyableInput';
|
import CopyableInput from '../../IDE/components/CopyableInput';
|
||||||
import { SketchSearchbar } from '../../IDE/components/Searchbar';
|
import { SketchSearchbar } from '../../IDE/components/Searchbar';
|
||||||
|
|
||||||
import DropdownArrowIcon from '../../../images/down-arrow.svg';
|
|
||||||
import ArrowUpIcon from '../../../images/sort-arrow-up.svg';
|
import ArrowUpIcon from '../../../images/sort-arrow-up.svg';
|
||||||
import ArrowDownIcon from '../../../images/sort-arrow-down.svg';
|
import ArrowDownIcon from '../../../images/sort-arrow-down.svg';
|
||||||
import RemoveIcon from '../../../images/close.svg';
|
import RemoveIcon from '../../../images/close.svg';
|
||||||
|
@ -50,14 +52,12 @@ const ShareURL = ({ value }) => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="collection-share" ref={node}>
|
<div className="collection-share" ref={node}>
|
||||||
<button
|
<Button
|
||||||
className="collection-share__button"
|
|
||||||
onClick={() => setShowURL(!showURL)}
|
onClick={() => setShowURL(!showURL)}
|
||||||
aria-label="Show collection share URL"
|
iconAfter={<DropdownArrowIcon />}
|
||||||
>
|
>
|
||||||
<span>Share</span>
|
Share
|
||||||
<DropdownArrowIcon className="collection-share__arrow" focusable="false" aria-hidden="true" />
|
</Button>
|
||||||
</button>
|
|
||||||
{ showURL &&
|
{ showURL &&
|
||||||
<div className="collection__share-dropdown">
|
<div className="collection__share-dropdown">
|
||||||
<CopyableInput value={value} label="Link to Collection" />
|
<CopyableInput value={value} label="Link to Collection" />
|
||||||
|
@ -264,9 +264,9 @@ class Collection extends React.Component {
|
||||||
</p>
|
</p>
|
||||||
{
|
{
|
||||||
this.isOwner() &&
|
this.isOwner() &&
|
||||||
<button className="collection-metadata__add-button" onClick={this.showAddSketches}>
|
<Button onClick={this.showAddSketches}>
|
||||||
Add Sketch
|
Add Sketch
|
||||||
</button>
|
</Button>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -317,7 +317,7 @@ class Collection extends React.Component {
|
||||||
_renderFieldHeader(fieldName, displayName) {
|
_renderFieldHeader(fieldName, displayName) {
|
||||||
const { field, direction } = this.props.sorting;
|
const { field, direction } = this.props.sorting;
|
||||||
const headerClass = classNames({
|
const headerClass = classNames({
|
||||||
'sketches-table__header': true,
|
'arrowDown': true,
|
||||||
'sketches-table__header--selected': field === fieldName
|
'sketches-table__header--selected': field === fieldName
|
||||||
});
|
});
|
||||||
const buttonLabel = this._getButtonLabel(fieldName, displayName);
|
const buttonLabel = this._getButtonLabel(fieldName, displayName);
|
||||||
|
|
|
@ -6,6 +6,7 @@ import { bindActionCreators } from 'redux';
|
||||||
import * as CollectionsActions from '../../IDE/actions/collections';
|
import * as CollectionsActions from '../../IDE/actions/collections';
|
||||||
|
|
||||||
import { generateCollectionName } from '../../../utils/generateRandomName';
|
import { generateCollectionName } from '../../../utils/generateRandomName';
|
||||||
|
import Button from '../../../common/Button';
|
||||||
|
|
||||||
class CollectionCreate extends React.Component {
|
class CollectionCreate extends React.Component {
|
||||||
constructor() {
|
constructor() {
|
||||||
|
@ -81,7 +82,7 @@ class CollectionCreate extends React.Component {
|
||||||
rows="4"
|
rows="4"
|
||||||
/>
|
/>
|
||||||
</p>
|
</p>
|
||||||
<input type="submit" disabled={invalid} value="Create collection" aria-label="create collection" />
|
<Button type="submit" disabled={invalid}>Create collection</Button>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,22 +0,0 @@
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import React from 'react';
|
|
||||||
|
|
||||||
import GithubIcon from '../../../images/github.svg';
|
|
||||||
|
|
||||||
function GithubButton(props) {
|
|
||||||
return (
|
|
||||||
<a
|
|
||||||
className="github-button"
|
|
||||||
href="/auth/github"
|
|
||||||
>
|
|
||||||
<GithubIcon className="github-icon" role="img" aria-label="GitHub Logo" focusable="false" />
|
|
||||||
<span>{props.buttonText}</span>
|
|
||||||
</a>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
GithubButton.propTypes = {
|
|
||||||
buttonText: PropTypes.string.isRequired
|
|
||||||
};
|
|
||||||
|
|
||||||
export default GithubButton;
|
|
|
@ -1,22 +0,0 @@
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import React from 'react';
|
|
||||||
|
|
||||||
import GoogleIcon from '../../../images/google.svg';
|
|
||||||
|
|
||||||
function GoogleButton(props) {
|
|
||||||
return (
|
|
||||||
<a
|
|
||||||
className="google-button"
|
|
||||||
href="/auth/google/"
|
|
||||||
>
|
|
||||||
<GoogleIcon className="google-icon" role="img" aria-label="Google Logo" focusable="false" />
|
|
||||||
<span>{props.buttonText}</span>
|
|
||||||
</a>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
GoogleButton.propTypes = {
|
|
||||||
buttonText: PropTypes.string.isRequired
|
|
||||||
};
|
|
||||||
|
|
||||||
export default GoogleButton;
|
|
|
@ -1,5 +1,8 @@
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
|
import Button from '../../../common/Button';
|
||||||
|
|
||||||
import { domOnlyProps } from '../../../utils/reduxFormUtils';
|
import { domOnlyProps } from '../../../utils/reduxFormUtils';
|
||||||
|
|
||||||
function LoginForm(props) {
|
function LoginForm(props) {
|
||||||
|
@ -30,7 +33,11 @@ function LoginForm(props) {
|
||||||
/>
|
/>
|
||||||
{password.touched && password.error && <span className="form-error">{password.error}</span>}
|
{password.touched && password.error && <span className="form-error">{password.error}</span>}
|
||||||
</p>
|
</p>
|
||||||
<input type="submit" disabled={submitting || pristine} value="Log In" aria-label="login" />
|
<Button
|
||||||
|
type="submit"
|
||||||
|
disabled={submitting || pristine}
|
||||||
|
>Log In
|
||||||
|
</Button>
|
||||||
</form>
|
</form>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import { domOnlyProps } from '../../../utils/reduxFormUtils';
|
import { domOnlyProps } from '../../../utils/reduxFormUtils';
|
||||||
|
import Button from '../../../common/Button';
|
||||||
|
|
||||||
function NewPasswordForm(props) {
|
function NewPasswordForm(props) {
|
||||||
const {
|
const {
|
||||||
|
@ -34,7 +36,7 @@ function NewPasswordForm(props) {
|
||||||
<span className="form-error">{confirmPassword.error}</span>
|
<span className="form-error">{confirmPassword.error}</span>
|
||||||
}
|
}
|
||||||
</p>
|
</p>
|
||||||
<input type="submit" disabled={submitting || invalid || pristine} value="Set New Password" aria-label="sign up" />
|
<Button type="submit" disabled={submitting || invalid || pristine}>Set New Password</Button>
|
||||||
</form>
|
</form>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import { domOnlyProps } from '../../../utils/reduxFormUtils';
|
import { domOnlyProps } from '../../../utils/reduxFormUtils';
|
||||||
|
import Button from '../../../common/Button';
|
||||||
|
|
||||||
function ResetPasswordForm(props) {
|
function ResetPasswordForm(props) {
|
||||||
const {
|
const {
|
||||||
|
@ -19,12 +21,11 @@ function ResetPasswordForm(props) {
|
||||||
/>
|
/>
|
||||||
{email.touched && email.error && <span className="form-error">{email.error}</span>}
|
{email.touched && email.error && <span className="form-error">{email.error}</span>}
|
||||||
</p>
|
</p>
|
||||||
<input
|
<Button
|
||||||
type="submit"
|
type="submit"
|
||||||
disabled={submitting || invalid || pristine || props.user.resetPasswordInitiate}
|
disabled={submitting || invalid || pristine || props.user.resetPasswordInitiate}
|
||||||
value="Send Password Reset Email"
|
>Send Password Reset Email
|
||||||
aria-label="Send email to reset password"
|
</Button>
|
||||||
/>
|
|
||||||
</form>
|
</form>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import { domOnlyProps } from '../../../utils/reduxFormUtils';
|
import { domOnlyProps } from '../../../utils/reduxFormUtils';
|
||||||
|
import Button from '../../../common/Button';
|
||||||
|
|
||||||
function SignupForm(props) {
|
function SignupForm(props) {
|
||||||
const {
|
const {
|
||||||
|
@ -58,7 +60,11 @@ function SignupForm(props) {
|
||||||
<span className="form-error">{confirmPassword.error}</span>
|
<span className="form-error">{confirmPassword.error}</span>
|
||||||
}
|
}
|
||||||
</p>
|
</p>
|
||||||
<input type="submit" disabled={submitting || invalid || pristine} value="Sign Up" aria-label="sign up" />
|
<Button
|
||||||
|
type="submit"
|
||||||
|
disabled={submitting || invalid || pristine}
|
||||||
|
>Sign Up
|
||||||
|
</Button>
|
||||||
</form>
|
</form>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
52
client/modules/User/components/SocialAuthButton.jsx
Normal file
52
client/modules/User/components/SocialAuthButton.jsx
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import React from 'react';
|
||||||
|
import styled from 'styled-components';
|
||||||
|
|
||||||
|
import { remSize } from '../../../theme';
|
||||||
|
|
||||||
|
import { GithubIcon, GoogleIcon } from '../../../common/icons';
|
||||||
|
import Button from '../../../common/Button';
|
||||||
|
|
||||||
|
const authUrls = {
|
||||||
|
github: '/auth/github',
|
||||||
|
google: '/auth/google'
|
||||||
|
};
|
||||||
|
|
||||||
|
const labels = {
|
||||||
|
github: 'Login with GitHub',
|
||||||
|
google: 'Login with Google'
|
||||||
|
};
|
||||||
|
|
||||||
|
const icons = {
|
||||||
|
github: GithubIcon,
|
||||||
|
google: GoogleIcon
|
||||||
|
};
|
||||||
|
|
||||||
|
const services = {
|
||||||
|
github: 'github',
|
||||||
|
google: 'google'
|
||||||
|
};
|
||||||
|
|
||||||
|
const StyledButton = styled(Button)`
|
||||||
|
width: ${remSize(300)};
|
||||||
|
`;
|
||||||
|
|
||||||
|
function SocialAuthButton({ service }) {
|
||||||
|
const ServiceIcon = icons[service];
|
||||||
|
return (
|
||||||
|
<StyledButton
|
||||||
|
iconBefore={<ServiceIcon aria-label={`${service} logo`} />}
|
||||||
|
href={authUrls[service]}
|
||||||
|
>
|
||||||
|
{labels[service]}
|
||||||
|
</StyledButton>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
SocialAuthButton.services = services;
|
||||||
|
|
||||||
|
SocialAuthButton.propTypes = {
|
||||||
|
service: PropTypes.oneOf(['github', 'google']).isRequired
|
||||||
|
};
|
||||||
|
|
||||||
|
export default SocialAuthButton;
|
16
client/modules/User/components/SocialAuthButton.stories.jsx
Normal file
16
client/modules/User/components/SocialAuthButton.stories.jsx
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import SocialAuthButton from './SocialAuthButton';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
title: 'User/components/SocialAuthButton',
|
||||||
|
component: SocialAuthButton
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Github = () => (
|
||||||
|
<SocialAuthButton service={SocialAuthButton.services.github}>Log in with Github</SocialAuthButton>
|
||||||
|
);
|
||||||
|
|
||||||
|
export const Google = () => (
|
||||||
|
<SocialAuthButton service={SocialAuthButton.services.google}>Sign up with Google</SocialAuthButton>
|
||||||
|
);
|
|
@ -8,8 +8,7 @@ import { Helmet } from 'react-helmet';
|
||||||
import { updateSettings, initiateVerification, createApiKey, removeApiKey } from '../actions';
|
import { updateSettings, initiateVerification, createApiKey, removeApiKey } from '../actions';
|
||||||
import AccountForm from '../components/AccountForm';
|
import AccountForm from '../components/AccountForm';
|
||||||
import { validateSettings } from '../../../utils/reduxFormUtils';
|
import { validateSettings } from '../../../utils/reduxFormUtils';
|
||||||
import GithubButton from '../components/GithubButton';
|
import SocialAuthButton from '../components/SocialAuthButton';
|
||||||
import GoogleButton from '../components/GoogleButton';
|
|
||||||
import APIKeyForm from '../components/APIKeyForm';
|
import APIKeyForm from '../components/APIKeyForm';
|
||||||
import Nav from '../../../components/Nav';
|
import Nav from '../../../components/Nav';
|
||||||
|
|
||||||
|
@ -24,8 +23,10 @@ function SocialLoginPanel(props) {
|
||||||
<p className="account__social-text">
|
<p className="account__social-text">
|
||||||
Use your GitHub or Google account to log into the p5.js Web Editor.
|
Use your GitHub or Google account to log into the p5.js Web Editor.
|
||||||
</p>
|
</p>
|
||||||
<GithubButton buttonText="Login with GitHub" />
|
<div className="account__social-stack">
|
||||||
<GoogleButton buttonText="Login with Google" />
|
<SocialAuthButton service={SocialAuthButton.services.github} />
|
||||||
|
<SocialAuthButton service={SocialAuthButton.services.google} />
|
||||||
|
</div>
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,8 +2,11 @@ import PropTypes from 'prop-types';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { bindActionCreators } from 'redux';
|
import { bindActionCreators } from 'redux';
|
||||||
import { browserHistory, Link } from 'react-router';
|
import { browserHistory } from 'react-router';
|
||||||
import { updateSettings, initiateVerification, createApiKey, removeApiKey } from '../actions';
|
import { updateSettings, initiateVerification, createApiKey, removeApiKey } from '../actions';
|
||||||
|
|
||||||
|
import Button from '../../../common/Button';
|
||||||
|
|
||||||
import Nav from '../../../components/Nav';
|
import Nav from '../../../components/Nav';
|
||||||
import Overlay from '../../App/components/Overlay';
|
import Overlay from '../../App/components/Overlay';
|
||||||
|
|
||||||
|
@ -79,16 +82,16 @@ class DashboardView extends React.Component {
|
||||||
case TabKey.collections:
|
case TabKey.collections:
|
||||||
return this.isOwner() && (
|
return this.isOwner() && (
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
<Link className="dashboard__action-button" to={`/${username}/collections/create`}>
|
<Button to={`/${username}/collections/create`}>
|
||||||
Create collection
|
Create collection
|
||||||
</Link>
|
</Button>
|
||||||
<CollectionSearchbar />
|
<CollectionSearchbar />
|
||||||
</React.Fragment>);
|
</React.Fragment>);
|
||||||
case TabKey.sketches:
|
case TabKey.sketches:
|
||||||
default:
|
default:
|
||||||
return (
|
return (
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
{this.isOwner() && <Link className="dashboard__action-button" to="/">New sketch</Link>}
|
{this.isOwner() && <Button to="/">New sketch</Button>}
|
||||||
<SketchSearchbar />
|
<SketchSearchbar />
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
);
|
);
|
||||||
|
|
|
@ -6,8 +6,7 @@ import { Helmet } from 'react-helmet';
|
||||||
import { validateAndLoginUser } from '../actions';
|
import { validateAndLoginUser } from '../actions';
|
||||||
import LoginForm from '../components/LoginForm';
|
import LoginForm from '../components/LoginForm';
|
||||||
import { validateLogin } from '../../../utils/reduxFormUtils';
|
import { validateLogin } from '../../../utils/reduxFormUtils';
|
||||||
import GithubButton from '../components/GithubButton';
|
import SocialAuthButton from '../components/SocialAuthButton';
|
||||||
import GoogleButton from '../components/GoogleButton';
|
|
||||||
import Nav from '../../../components/Nav';
|
import Nav from '../../../components/Nav';
|
||||||
|
|
||||||
class LoginView extends React.Component {
|
class LoginView extends React.Component {
|
||||||
|
@ -41,8 +40,10 @@ class LoginView extends React.Component {
|
||||||
<h2 className="form-container__title">Log In</h2>
|
<h2 className="form-container__title">Log In</h2>
|
||||||
<LoginForm {...this.props} />
|
<LoginForm {...this.props} />
|
||||||
<h2 className="form-container__divider">Or</h2>
|
<h2 className="form-container__divider">Or</h2>
|
||||||
<GithubButton buttonText="Login with Github" />
|
<div className="form-container__stack">
|
||||||
<GoogleButton buttonText="Login with Google" />
|
<SocialAuthButton service={SocialAuthButton.services.github} />
|
||||||
|
<SocialAuthButton service={SocialAuthButton.services.google} />
|
||||||
|
</div>
|
||||||
<p className="form__navigation-options">
|
<p className="form__navigation-options">
|
||||||
Don't have an account?
|
Don't have an account?
|
||||||
<Link className="form__signup-button" to="/signup">Sign Up</Link>
|
<Link className="form__signup-button" to="/signup">Sign Up</Link>
|
||||||
|
|
|
@ -8,6 +8,7 @@ import { reduxForm } from 'redux-form';
|
||||||
import * as UserActions from '../actions';
|
import * as UserActions from '../actions';
|
||||||
import SignupForm from '../components/SignupForm';
|
import SignupForm from '../components/SignupForm';
|
||||||
import { validateSignup } from '../../../utils/reduxFormUtils';
|
import { validateSignup } from '../../../utils/reduxFormUtils';
|
||||||
|
import SocialAuthButton from '../components/SocialAuthButton';
|
||||||
import Nav from '../../../components/Nav';
|
import Nav from '../../../components/Nav';
|
||||||
|
|
||||||
const __process = (typeof global !== 'undefined' ? global : window).process;
|
const __process = (typeof global !== 'undefined' ? global : window).process;
|
||||||
|
@ -33,6 +34,11 @@ class SignupView extends React.Component {
|
||||||
<div className="form-container__content">
|
<div className="form-container__content">
|
||||||
<h2 className="form-container__title">Sign Up</h2>
|
<h2 className="form-container__title">Sign Up</h2>
|
||||||
<SignupForm {...this.props} />
|
<SignupForm {...this.props} />
|
||||||
|
<h2 className="form-container__divider">Or</h2>
|
||||||
|
<div className="form-container__stack">
|
||||||
|
<SocialAuthButton service={SocialAuthButton.services.github} />
|
||||||
|
<SocialAuthButton service={SocialAuthButton.services.google} />
|
||||||
|
</div>
|
||||||
<p className="form__navigation-options">
|
<p className="form__navigation-options">
|
||||||
Already have an account?
|
Already have an account?
|
||||||
<Link className="form__login-button" to="/login">Log In</Link>
|
<Link className="form__login-button" to="/login">Log In</Link>
|
||||||
|
|
|
@ -74,6 +74,7 @@ h2 {
|
||||||
|
|
||||||
h3 {
|
h3 {
|
||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
|
font-size: #{16 / $base-font-size}rem;
|
||||||
}
|
}
|
||||||
h4 {
|
h4 {
|
||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
|
|
|
@ -20,3 +20,12 @@
|
||||||
.account__social-text {
|
.account__social-text {
|
||||||
padding-bottom: #{15 / $base-font-size}rem;
|
padding-bottom: #{15 / $base-font-size}rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.account__social-stack {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.account__social-stack > * {
|
||||||
|
margin-right: #{15 / $base-font-size}rem;
|
||||||
|
}
|
||||||
|
|
|
@ -12,22 +12,6 @@
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
|
|
||||||
.api-key-form__create-button {
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.api-key-form__create-icon {
|
|
||||||
display: flex;
|
|
||||||
height: #{12 / $base-font-size}rem;
|
|
||||||
margin-right: #{3 / $base-font-size}rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.api-key-form__create-button .isvg {
|
|
||||||
padding-right: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.api-key-list {
|
.api-key-list {
|
||||||
display: block;
|
display: block;
|
||||||
max-width: 900px;
|
max-width: 900px;
|
||||||
|
|
|
@ -94,16 +94,6 @@
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
.collection-share__button {
|
|
||||||
@extend %button;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.collection-share__arrow {
|
|
||||||
margin-left: #{5 / $base-font-size}rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.collection-share .copyable-input {
|
.collection-share .copyable-input {
|
||||||
padding-bottom: 0;
|
padding-bottom: 0;
|
||||||
}
|
}
|
||||||
|
@ -114,11 +104,6 @@
|
||||||
width: #{350 / $base-font-size}rem;
|
width: #{350 / $base-font-size}rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.collection-metadata__add-button {
|
|
||||||
@extend %button;
|
|
||||||
flex-grow: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.collection-content {
|
.collection-content {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|
|
@ -83,8 +83,3 @@
|
||||||
.dashboard-header__actions > *:not(:first-child) {
|
.dashboard-header__actions > *:not(:first-child) {
|
||||||
margin-left: #{15 / $base-font-size}rem;
|
margin-left: #{15 / $base-font-size}rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dashboard__action-button {
|
|
||||||
flex-grow: 0;
|
|
||||||
@extend %button;
|
|
||||||
}
|
|
||||||
|
|
|
@ -57,3 +57,7 @@
|
||||||
.form-container__exit-button {
|
.form-container__exit-button {
|
||||||
@include icon();
|
@include icon();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.form-container__stack > * + * {
|
||||||
|
margin-top: #{10 / $base-font-size}rem;
|
||||||
|
}
|
||||||
|
|
|
@ -9,6 +9,10 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.form > * + * {
|
||||||
|
margin-top: #{12 / $base-font-size}rem;
|
||||||
|
}
|
||||||
|
|
||||||
.form--inline {
|
.form--inline {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
@ -79,20 +83,25 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.form [type="submit"] {
|
.form [type="submit"] {
|
||||||
@extend %button;
|
margin-left: auto;
|
||||||
padding: #{8 / $base-font-size}rem #{25 / $base-font-size}rem;
|
margin-right: auto;
|
||||||
margin: #{39 / $base-font-size}rem 0 #{24 / $base-font-size}rem 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.form [type="submit"][disabled] {
|
// .form [type="submit"] {
|
||||||
cursor: not-allowed;
|
// @extend %button;
|
||||||
}
|
// padding: #{8 / $base-font-size}rem #{25 / $base-font-size}rem;
|
||||||
|
// margin: #{39 / $base-font-size}rem 0 #{24 / $base-font-size}rem 0;
|
||||||
|
// }
|
||||||
|
|
||||||
.form--inline [type="submit"] {
|
// .form [type="submit"][disabled] {
|
||||||
margin: 0 0 0 #{24 / $base-font-size}rem;
|
// cursor: not-allowed;
|
||||||
}
|
// }
|
||||||
|
|
||||||
.form [type="submit"][disabled],
|
// .form--inline [type="submit"] {
|
||||||
.form--inline [type="submit"][disabled] {
|
// margin: 0 0 0 #{24 / $base-font-size}rem;
|
||||||
cursor: not-allowed;
|
// }
|
||||||
}
|
|
||||||
|
// .form [type="submit"][disabled],
|
||||||
|
// .form--inline [type="submit"][disabled] {
|
||||||
|
// cursor: not-allowed;
|
||||||
|
// }
|
||||||
|
|
|
@ -1,35 +0,0 @@
|
||||||
.github-button,
|
|
||||||
.google-button {
|
|
||||||
@include themify() {
|
|
||||||
@extend %button;
|
|
||||||
& path {
|
|
||||||
color: getThemifyVariable('primary-text-color');
|
|
||||||
}
|
|
||||||
&:hover path, &:active path {
|
|
||||||
fill: $white;
|
|
||||||
}
|
|
||||||
&:hover, &:active {
|
|
||||||
color: getThemifyVariable('button-hover-color');
|
|
||||||
background-color: getThemifyVariable('button-background-hover-color');
|
|
||||||
border-color: getThemifyVariable('button-background-hover-color');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
width: #{300 / $base-font-size}rem;
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
|
|
||||||
& + & {
|
|
||||||
margin-top: #{10 / $base-font-size}rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.github-icon {
|
|
||||||
margin-right: #{10 / $base-font-size}rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.google-icon {
|
|
||||||
width: #{32 / $base-font-size}rem;
|
|
||||||
height: #{32 / $base-font-size}rem;
|
|
||||||
margin-right: #{10 / $base-font-size}rem;
|
|
||||||
}
|
|
|
@ -4,6 +4,11 @@
|
||||||
width: #{450 / $base-font-size}rem;
|
width: #{450 / $base-font-size}rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.keyboard-shortcuts-note {
|
||||||
|
text-align: center;
|
||||||
|
margin-bottom: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
.keyboard-shortcut-item {
|
.keyboard-shortcut-item {
|
||||||
display: flex;
|
display: flex;
|
||||||
& + & {
|
& + & {
|
||||||
|
@ -13,8 +18,31 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.keyboard-shortcut__command {
|
.keyboard-shortcut__command {
|
||||||
width: 50%;
|
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
text-align: right;
|
text-align: right;
|
||||||
padding-right: #{10 / $base-font-size}rem;
|
margin-right: #{10 / $base-font-size}rem;
|
||||||
|
padding: #{3 / $base-font-size}rem;
|
||||||
|
@include themify {
|
||||||
|
border: 1px solid getThemifyVariable("button-border-color");
|
||||||
|
border-radius: 3px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.keyboard-shortcuts__title {
|
||||||
|
padding-bottom: #{10 / $base-font-size}rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.keyboard-shortcuts__description {
|
||||||
|
padding-bottom: #{10 / $base-font-size}rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.keyboard-shortcuts__list:not(:last-of-type) {
|
||||||
|
padding-bottom: #{10 / $base-font-size}rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.keyboard-shortcuts__title:not(:first-of-type) {
|
||||||
|
@include themify() {
|
||||||
|
border-top: 1px dashed getThemifyVariable("button-border-color");
|
||||||
|
padding-top: #{10 / $base-font-size}rem;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,6 +29,11 @@
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
height: #{35 / $base-font-size}rem;
|
height: #{35 / $base-font-size}rem;
|
||||||
|
|
||||||
|
& .isvg {
|
||||||
|
margin-left: #{8 / $base-font-size}rem;
|
||||||
|
}
|
||||||
|
|
||||||
& svg {
|
& svg {
|
||||||
@include themify() {
|
@include themify() {
|
||||||
fill: getThemifyVariable('inactive-text-color')
|
fill: getThemifyVariable('inactive-text-color')
|
||||||
|
|
|
@ -31,7 +31,6 @@
|
||||||
@import 'components/resizer';
|
@import 'components/resizer';
|
||||||
@import 'components/overlay';
|
@import 'components/overlay';
|
||||||
@import 'components/about';
|
@import 'components/about';
|
||||||
@import 'components/github-button';
|
|
||||||
@import 'components/forms';
|
@import 'components/forms';
|
||||||
@import 'components/toast';
|
@import 'components/toast';
|
||||||
@import 'components/timer';
|
@import 'components/timer';
|
||||||
|
|
145
client/theme.js
Normal file
145
client/theme.js
Normal file
|
@ -0,0 +1,145 @@
|
||||||
|
import lodash from 'lodash';
|
||||||
|
|
||||||
|
export const Theme = {
|
||||||
|
contrast: 'contrast',
|
||||||
|
dark: 'dark',
|
||||||
|
light: 'light',
|
||||||
|
};
|
||||||
|
|
||||||
|
export const colors = {
|
||||||
|
p5jsPink: '#ed225d',
|
||||||
|
processingBlue: '#007BBB',
|
||||||
|
p5jsActivePink: '#f10046',
|
||||||
|
white: '#fff',
|
||||||
|
black: '#000',
|
||||||
|
yellow: '#f5dc23',
|
||||||
|
orange: '#ffa500',
|
||||||
|
red: '#ff0000',
|
||||||
|
lightsteelblue: '#B0C4DE',
|
||||||
|
dodgerblue: '#1E90FF',
|
||||||
|
p5ContrastPink: ' #FFA9D9',
|
||||||
|
|
||||||
|
borderColor: ' #B5B5B5',
|
||||||
|
outlineColor: '#0F9DD7',
|
||||||
|
};
|
||||||
|
|
||||||
|
export const grays = {
|
||||||
|
lightest: '#FFF', // primary
|
||||||
|
lighter: '#FBFBFB',
|
||||||
|
|
||||||
|
light: '#F0F0F0', // primary
|
||||||
|
mediumLight: '#D9D9D9',
|
||||||
|
middleLight: '#A6A6A6',
|
||||||
|
|
||||||
|
middleGray: '#747474', // primary
|
||||||
|
middleDark: '#666',
|
||||||
|
mediumDark: '#4D4D4D',
|
||||||
|
|
||||||
|
dark: '#333', // primary
|
||||||
|
darker: '#1C1C1C',
|
||||||
|
darkest: '#000',
|
||||||
|
};
|
||||||
|
|
||||||
|
export const common = {
|
||||||
|
baseFontSize: 12
|
||||||
|
};
|
||||||
|
|
||||||
|
export const remSize = size => `${size / common.baseFontSize}rem`;
|
||||||
|
|
||||||
|
export const prop = key => (props) => {
|
||||||
|
const keypath = `theme.${key}`;
|
||||||
|
const value = lodash.get(props, keypath);
|
||||||
|
|
||||||
|
if (value == null) {
|
||||||
|
throw new Error(`themed prop ${key} not found`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return value;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default {
|
||||||
|
[Theme.light]: {
|
||||||
|
colors,
|
||||||
|
...common,
|
||||||
|
primaryTextColor: grays.dark,
|
||||||
|
|
||||||
|
Button: {
|
||||||
|
default: {
|
||||||
|
foreground: colors.black,
|
||||||
|
background: grays.light,
|
||||||
|
border: grays.middleLight,
|
||||||
|
},
|
||||||
|
hover: {
|
||||||
|
foreground: grays.lightest,
|
||||||
|
background: colors.p5jsPink,
|
||||||
|
border: colors.p5jsPink,
|
||||||
|
},
|
||||||
|
active: {
|
||||||
|
foreground: grays.lightest,
|
||||||
|
background: colors.p5jsActivePink,
|
||||||
|
border: colors.p5jsActivePink,
|
||||||
|
},
|
||||||
|
disabled: {
|
||||||
|
foreground: colors.black,
|
||||||
|
background: grays.light,
|
||||||
|
border: grays.middleLight,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
[Theme.dark]: {
|
||||||
|
colors,
|
||||||
|
...common,
|
||||||
|
primaryTextColor: grays.lightest,
|
||||||
|
|
||||||
|
Button: {
|
||||||
|
default: {
|
||||||
|
foreground: grays.light,
|
||||||
|
background: grays.dark,
|
||||||
|
border: grays.middleDark,
|
||||||
|
},
|
||||||
|
hover: {
|
||||||
|
foreground: grays.lightest,
|
||||||
|
background: colors.p5jsPink,
|
||||||
|
border: colors.p5jsPink,
|
||||||
|
},
|
||||||
|
active: {
|
||||||
|
foreground: grays.lightest,
|
||||||
|
background: colors.p5jsActivePink,
|
||||||
|
border: colors.p5jsActivePink,
|
||||||
|
},
|
||||||
|
disabled: {
|
||||||
|
foreground: grays.light,
|
||||||
|
background: grays.dark,
|
||||||
|
border: grays.middleDark,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
[Theme.contrast]: {
|
||||||
|
colors,
|
||||||
|
...common,
|
||||||
|
primaryTextColor: grays.lightest,
|
||||||
|
|
||||||
|
Button: {
|
||||||
|
default: {
|
||||||
|
foreground: grays.light,
|
||||||
|
background: grays.dark,
|
||||||
|
border: grays.middleDark,
|
||||||
|
},
|
||||||
|
hover: {
|
||||||
|
foreground: grays.dark,
|
||||||
|
background: colors.yellow,
|
||||||
|
border: colors.yellow,
|
||||||
|
},
|
||||||
|
active: {
|
||||||
|
foreground: grays.dark,
|
||||||
|
background: colors.p5jsActivePink,
|
||||||
|
border: colors.p5jsActivePink,
|
||||||
|
},
|
||||||
|
disabled: {
|
||||||
|
foreground: grays.light,
|
||||||
|
background: grays.dark,
|
||||||
|
border: grays.middleDark,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
8153
package-lock.json
generated
8153
package-lock.json
generated
File diff suppressed because it is too large
Load diff
16
package.json
16
package.json
|
@ -21,7 +21,9 @@
|
||||||
"fetch-examples-gg:prod": "cross-env NODE_ENV=production node ./dist/fetch-examples-gg.bundle.js",
|
"fetch-examples-gg:prod": "cross-env NODE_ENV=production node ./dist/fetch-examples-gg.bundle.js",
|
||||||
"fetch-examples-ml5:prod": "cross-env NODE_ENV=production node ./dist/fetch-examples-ml5.bundle.js",
|
"fetch-examples-ml5:prod": "cross-env NODE_ENV=production node ./dist/fetch-examples-ml5.bundle.js",
|
||||||
"update-syntax-highlighting": "node ./server/scripts/update-syntax-highlighting.js",
|
"update-syntax-highlighting": "node ./server/scripts/update-syntax-highlighting.js",
|
||||||
"heroku-postbuild": "touch .env; npm run build"
|
"heroku-postbuild": "touch .env; npm run build",
|
||||||
|
"storybook": "start-storybook -p 6006",
|
||||||
|
"build-storybook": "build-storybook"
|
||||||
},
|
},
|
||||||
"husky": {
|
"husky": {
|
||||||
"hooks": {
|
"hooks": {
|
||||||
|
@ -69,6 +71,12 @@
|
||||||
"@babel/plugin-transform-react-inline-elements": "^7.8.3",
|
"@babel/plugin-transform-react-inline-elements": "^7.8.3",
|
||||||
"@babel/preset-env": "^7.8.4",
|
"@babel/preset-env": "^7.8.4",
|
||||||
"@babel/preset-react": "^7.8.3",
|
"@babel/preset-react": "^7.8.3",
|
||||||
|
"@storybook/addon-actions": "^5.3.6",
|
||||||
|
"@storybook/addon-docs": "^5.3.6",
|
||||||
|
"@storybook/addon-knobs": "^5.3.6",
|
||||||
|
"@storybook/addon-links": "^5.3.6",
|
||||||
|
"@storybook/addons": "^5.3.6",
|
||||||
|
"@storybook/react": "^5.3.6",
|
||||||
"@svgr/webpack": "^5.4.0",
|
"@svgr/webpack": "^5.4.0",
|
||||||
"babel-core": "^7.0.0-bridge.0",
|
"babel-core": "^7.0.0-bridge.0",
|
||||||
"babel-eslint": "^9.0.0",
|
"babel-eslint": "^9.0.0",
|
||||||
|
@ -81,7 +89,7 @@
|
||||||
"enzyme-adapter-react-16": "^1.15.2",
|
"enzyme-adapter-react-16": "^1.15.2",
|
||||||
"eslint": "^4.19.1",
|
"eslint": "^4.19.1",
|
||||||
"eslint-config-airbnb": "^16.1.0",
|
"eslint-config-airbnb": "^16.1.0",
|
||||||
"eslint-plugin-import": "^2.20.1",
|
"eslint-plugin-import": "^2.20.2",
|
||||||
"eslint-plugin-jsx-a11y": "^6.2.3",
|
"eslint-plugin-jsx-a11y": "^6.2.3",
|
||||||
"eslint-plugin-react": "^7.18.3",
|
"eslint-plugin-react": "^7.18.3",
|
||||||
"file-loader": "^2.0.0",
|
"file-loader": "^2.0.0",
|
||||||
|
@ -99,6 +107,7 @@
|
||||||
"react-test-renderer": "^16.12.0",
|
"react-test-renderer": "^16.12.0",
|
||||||
"rimraf": "^2.7.1",
|
"rimraf": "^2.7.1",
|
||||||
"sass-loader": "^6.0.7",
|
"sass-loader": "^6.0.7",
|
||||||
|
"storybook-addon-theme-playground": "^1.2.0",
|
||||||
"style-loader": "^1.1.3",
|
"style-loader": "^1.1.3",
|
||||||
"terser-webpack-plugin": "^1.4.3",
|
"terser-webpack-plugin": "^1.4.3",
|
||||||
"webpack-cli": "^3.3.11",
|
"webpack-cli": "^3.3.11",
|
||||||
|
@ -117,6 +126,7 @@
|
||||||
"archiver": "^1.1.0",
|
"archiver": "^1.1.0",
|
||||||
"async": "^2.6.3",
|
"async": "^2.6.3",
|
||||||
"axios": "^0.18.1",
|
"axios": "^0.18.1",
|
||||||
|
"babel-plugin-styled-components": "^1.10.6",
|
||||||
"bcrypt-nodejs": "0.0.3",
|
"bcrypt-nodejs": "0.0.3",
|
||||||
"blob-util": "^1.2.1",
|
"blob-util": "^1.2.1",
|
||||||
"body-parser": "^1.18.3",
|
"body-parser": "^1.18.3",
|
||||||
|
@ -191,6 +201,8 @@
|
||||||
"sinon-mongoose": "^2.3.0",
|
"sinon-mongoose": "^2.3.0",
|
||||||
"slugify": "^1.3.6",
|
"slugify": "^1.3.6",
|
||||||
"srcdoc-polyfill": "^0.2.0",
|
"srcdoc-polyfill": "^0.2.0",
|
||||||
|
"styled-components": "^5.0.0",
|
||||||
|
"styled-theming": "^2.2.0",
|
||||||
"url": "^0.11.0",
|
"url": "^0.11.0",
|
||||||
"webpack": "^4.41.6",
|
"webpack": "^4.41.6",
|
||||||
"webpack-dev-middleware": "^2.0.6",
|
"webpack-dev-middleware": "^2.0.6",
|
||||||
|
|
|
@ -63,25 +63,21 @@ export function updateProject(req, res) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getProject(req, res) {
|
export function getProject(req, res) {
|
||||||
const projectId = req.params.project_id;
|
const { project_id: projectId, username } = req.params;
|
||||||
Project.findById(projectId)
|
User.findOne({ username }, (err, user) => { // eslint-disable-line
|
||||||
.populate('user', 'username')
|
if (!user) {
|
||||||
.exec((err, project) => { // eslint-disable-line
|
return res.status(404).send({ message: 'Project with that username does not exist' });
|
||||||
if (err) {
|
}
|
||||||
return res.status(404).send({ message: 'Project with that id does not exist' });
|
Project.findOne({ user: user._id, $or: [{ _id: projectId }, { slug: projectId }] })
|
||||||
} else if (!project) {
|
.populate('user', 'username')
|
||||||
Project.findOne({ slug: projectId })
|
.exec((err, project) => { // eslint-disable-line
|
||||||
.populate('user', 'username')
|
if (err) {
|
||||||
.exec((innerErr, projectBySlug) => {
|
console.log(err);
|
||||||
if (innerErr || !projectBySlug) {
|
return res.status(404).send({ message: 'Project with that id does not exist' });
|
||||||
return res.status(404).send({ message: 'Project with that id does not exist' });
|
}
|
||||||
}
|
|
||||||
return res.json(projectBySlug);
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
return res.json(project);
|
return res.json(project);
|
||||||
}
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getProjectsForUserId(userId) {
|
export function getProjectsForUserId(userId) {
|
||||||
|
@ -150,18 +146,10 @@ export function projectForUserExists(username, projectId, callback) {
|
||||||
callback(false);
|
callback(false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
Project.findOne({ _id: projectId, user: user._id }, (innerErr, project) => {
|
Project.findOne({ user: user._id, $or: [{ _id: projectId }, { slug: projectId }] }, (innerErr, project) => {
|
||||||
if (project) {
|
if (project) {
|
||||||
callback(true);
|
callback(true);
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
Project.findOne({ slug: projectId, user: user._id }, (slugError, projectBySlug) => {
|
|
||||||
if (projectBySlug) {
|
|
||||||
callback(true);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
callback(false);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,7 +8,7 @@ router.post('/projects', isAuthenticated, ProjectController.createProject);
|
||||||
|
|
||||||
router.put('/projects/:project_id', isAuthenticated, ProjectController.updateProject);
|
router.put('/projects/:project_id', isAuthenticated, ProjectController.updateProject);
|
||||||
|
|
||||||
router.get('/projects/:project_id', ProjectController.getProject);
|
router.get('/:username/projects/:project_id', ProjectController.getProject);
|
||||||
|
|
||||||
router.delete('/projects/:project_id', isAuthenticated, ProjectController.deleteProject);
|
router.delete('/projects/:project_id', isAuthenticated, ProjectController.deleteProject);
|
||||||
|
|
||||||
|
|
|
@ -46,17 +46,20 @@ if (process.env.BASIC_USERNAME && process.env.BASIC_PASSWORD) {
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
const corsOriginsWhitelist = [
|
const allowedCorsOrigins = [
|
||||||
/p5js\.org$/,
|
/p5js\.org$/,
|
||||||
];
|
];
|
||||||
|
|
||||||
|
// to allow client-only development
|
||||||
|
if (process.env.CORS_ALLOW_LOCALHOST === 'true') {
|
||||||
|
allowedCorsOrigins.push(/localhost/);
|
||||||
|
}
|
||||||
|
|
||||||
// Run Webpack dev server in development mode
|
// Run Webpack dev server in development mode
|
||||||
if (process.env.NODE_ENV === 'development') {
|
if (process.env.NODE_ENV === 'development') {
|
||||||
const compiler = webpack(config);
|
const compiler = webpack(config);
|
||||||
app.use(webpackDevMiddleware(compiler, { noInfo: true, publicPath: config.output.publicPath }));
|
app.use(webpackDevMiddleware(compiler, { noInfo: true, publicPath: config.output.publicPath }));
|
||||||
app.use(webpackHotMiddleware(compiler));
|
app.use(webpackHotMiddleware(compiler));
|
||||||
|
|
||||||
corsOriginsWhitelist.push(/localhost/);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const mongoConnectionString = process.env.MONGO_URL;
|
const mongoConnectionString = process.env.MONGO_URL;
|
||||||
|
@ -65,7 +68,7 @@ app.set('trust proxy', true);
|
||||||
// Enable Cross-Origin Resource Sharing (CORS) for all origins
|
// Enable Cross-Origin Resource Sharing (CORS) for all origins
|
||||||
const corsMiddleware = cors({
|
const corsMiddleware = cors({
|
||||||
credentials: true,
|
credentials: true,
|
||||||
origin: corsOriginsWhitelist,
|
origin: allowedCorsOrigins,
|
||||||
});
|
});
|
||||||
app.use(corsMiddleware);
|
app.use(corsMiddleware);
|
||||||
// Enable pre-flight OPTIONS route for all end-points
|
// Enable pre-flight OPTIONS route for all end-points
|
||||||
|
|
Loading…
Reference in a new issue