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": {
|
||||
"production": {
|
||||
"plugins": [
|
||||
"babel-plugin-styled-components",
|
||||
"transform-react-remove-prop-types",
|
||||
"@babel/plugin-transform-react-constant-elements",
|
||||
"@babel/plugin-transform-react-inline-elements",
|
||||
|
@ -48,6 +49,7 @@
|
|||
},
|
||||
"development": {
|
||||
"plugins": [
|
||||
"babel-plugin-styled-components",
|
||||
"react-hot-loader/babel"
|
||||
]
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ API_URL=/editor
|
|||
AWS_ACCESS_KEY=<your-aws-access-key>
|
||||
AWS_REGION=<your-aws-region>
|
||||
AWS_SECRET_KEY=<your-aws-secret-key>
|
||||
CORS_ALLOW_LOCALHOST=true
|
||||
EMAIL_SENDER=<transactional-email-sender>
|
||||
EMAIL_VERIFY_SECRET_TOKEN=whatever_you_want_this_to_be_it_only_matters_for_production
|
||||
EXAMPLE_USER_EMAIL=examples@p5js.org
|
||||
|
|
|
@ -77,5 +77,13 @@
|
|||
"__SERVER__": true,
|
||||
"__DISABLE_SSR__": 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.key
|
||||
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"?>
|
||||
<!-- Generator: Adobe Illustrator 19.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<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"
|
||||
viewBox="0 0 512 512" style="enable-background:new 0 0 512 512;" xml:space="preserve">
|
||||
<path style="fill:#FBBB00;" d="M113.47,309.408L95.648,375.94l-65.139,1.378C11.042,341.211,0,299.9,0,256
|
||||
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
|
||||
C103.821,274.792,107.225,292.797,113.47,309.408z"/>
|
||||
<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
|
||||
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
|
||||
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 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">
|
||||
<g>
|
||||
<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 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>
|
||||
<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>
|
||||
<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>
|
||||
</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"?>
|
||||
<!-- 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">
|
||||
<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"
|
||||
width="40px" height="30px" viewBox="0 0 100 100" style="enable-background:new 0 0 100 100;" xml:space="preserve">
|
||||
<g>
|
||||
<path d="M49.761,67.969l-17.36-30.241h34.561L49.761,67.969z"/>
|
||||
</g>
|
||||
<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">
|
||||
<g transform="translate(0.666667, 0.333333)">
|
||||
<polygon points="4.56711429 7.96345714 0.103114286 0.1872 8.99022857 0.1872"></polygon>
|
||||
</g>
|
||||
</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"?>
|
||||
<!-- 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">
|
||||
<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"
|
||||
width="40px" height="30px" viewBox="0 0 100 100" style="enable-background:new 0 0 100 100;" xml:space="preserve">
|
||||
<g>
|
||||
<path d="M49.761,37.728l17.36,30.241H32.561L49.761,37.728z"/>
|
||||
</g>
|
||||
<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">
|
||||
<g transform="translate(-0.200000, -0.200000)" >
|
||||
<polygon points="4.93361111 0.202222222 9.75583333 8.6025 0.155833333 8.6025"></polygon>
|
||||
</g>
|
||||
</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 { Provider } from 'react-redux';
|
||||
import { Router, browserHistory } from 'react-router';
|
||||
|
||||
import configureStore from './store';
|
||||
import routes from './routes';
|
||||
import ThemeProvider from './modules/App/components/ThemeProvider';
|
||||
|
||||
require('./styles/main.scss');
|
||||
|
||||
|
@ -18,7 +20,9 @@ const store = configureStore(initialState);
|
|||
|
||||
const App = () => (
|
||||
<Provider store={store}>
|
||||
<ThemeProvider>
|
||||
<Router history={history} routes={routes(store)} />
|
||||
</ThemeProvider>
|
||||
</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) => {
|
||||
dispatch(justOpenedProject());
|
||||
axios.get(`${ROOT_URL}/projects/${id}`, { withCredentials: true })
|
||||
axios.get(`${ROOT_URL}/${username}/projects/${id}`, { withCredentials: true })
|
||||
.then((response) => {
|
||||
dispatch(setProject(response.data));
|
||||
dispatch(setUnsavedChanges(false));
|
||||
|
|
|
@ -3,17 +3,16 @@ import { metaKeyName, } from '../../../utils/metaKey';
|
|||
|
||||
function KeyboardShortcutModal() {
|
||||
return (
|
||||
<ul className="keyboard-shortcuts" title="keyboard shortcuts">
|
||||
<div className="keyboard-shortcuts">
|
||||
<h3 className="keyboard-shortcuts__title">Code Editing</h3>
|
||||
<p className="keyboard-shortcuts__description">
|
||||
Code editing keyboard shortcuts follow <a href="https://shortcuts.design/toolspage-sublimetext.html" target="_blank" rel="noopener noreferrer">Sublime Text shortcuts</a>.
|
||||
</p>
|
||||
<ul className="keyboard-shortcuts__list">
|
||||
<li className="keyboard-shortcut-item">
|
||||
<span className="keyboard-shortcut__command">{'\u21E7'} + Tab</span>
|
||||
<span>Tidy</span>
|
||||
</li>
|
||||
<li className="keyboard-shortcut-item">
|
||||
<span className="keyboard-shortcut__command">
|
||||
{metaKeyName} + S
|
||||
</span>
|
||||
<span>Save</span>
|
||||
</li>
|
||||
<li className="keyboard-shortcut-item">
|
||||
<span className="keyboard-shortcut__command">
|
||||
{metaKeyName} + F
|
||||
|
@ -50,6 +49,15 @@ function KeyboardShortcutModal() {
|
|||
</span>
|
||||
<span>Comment Line</span>
|
||||
</li>
|
||||
</ul>
|
||||
<h3 className="keyboard-shortcuts__title">General</h3>
|
||||
<ul className="keyboard-shortcuts__list">
|
||||
<li className="keyboard-shortcut-item">
|
||||
<span className="keyboard-shortcut__command">
|
||||
{metaKeyName} + S
|
||||
</span>
|
||||
<span>Save</span>
|
||||
</li>
|
||||
<li className="keyboard-shortcut-item">
|
||||
<span className="keyboard-shortcut__command">
|
||||
{metaKeyName} + Enter
|
||||
|
@ -74,19 +82,8 @@ function KeyboardShortcutModal() {
|
|||
</span>
|
||||
<span>Turn off Accessible Output</span>
|
||||
</li>
|
||||
<li className="keyboard-shortcut-item">
|
||||
<span className="keyboard-shortcut__command">
|
||||
{metaKeyName} + B
|
||||
</span>
|
||||
<span>Toggle Sidebar</span>
|
||||
</li>
|
||||
<li className="keyboard-shortcut-item">
|
||||
<span className="keyboard-shortcut__command">
|
||||
Ctrl + `
|
||||
</span>
|
||||
<span>Toggle Console</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -2,6 +2,8 @@ import PropTypes from 'prop-types';
|
|||
import React from 'react';
|
||||
import { domOnlyProps } from '../../../utils/reduxFormUtils';
|
||||
|
||||
import Button from '../../../common/Button';
|
||||
|
||||
class NewFileForm extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
@ -33,12 +35,10 @@ class NewFileForm extends React.Component {
|
|||
{...domOnlyProps(name)}
|
||||
ref={(element) => { this.fileName = element; }}
|
||||
/>
|
||||
<input
|
||||
<Button
|
||||
type="submit"
|
||||
value="Add File"
|
||||
aria-label="add file"
|
||||
className="new-file-form__submit"
|
||||
/>
|
||||
>Add File
|
||||
</Button>
|
||||
</div>
|
||||
{name.touched && name.error && <span className="form-error">{name.error}</span>}
|
||||
</form>
|
||||
|
|
|
@ -2,6 +2,8 @@ import PropTypes from 'prop-types';
|
|||
import React from 'react';
|
||||
import { domOnlyProps } from '../../../utils/reduxFormUtils';
|
||||
|
||||
import Button from '../../../common/Button';
|
||||
|
||||
class NewFolderForm extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
@ -34,12 +36,10 @@ class NewFolderForm extends React.Component {
|
|||
ref={(element) => { this.fileName = element; }}
|
||||
{...domOnlyProps(name)}
|
||||
/>
|
||||
<input
|
||||
<Button
|
||||
type="submit"
|
||||
value="Add Folder"
|
||||
aria-label="add folder"
|
||||
className="new-folder-form__submit"
|
||||
/>
|
||||
>Add Folder
|
||||
</Button>
|
||||
</div>
|
||||
{name.touched && name.error && <span className="form-error">{name.error}</span>}
|
||||
</form>
|
||||
|
|
|
@ -53,9 +53,9 @@ class IDEView extends React.Component {
|
|||
|
||||
this.props.stopSketch();
|
||||
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) {
|
||||
this.props.getProject(id);
|
||||
this.props.getProject(id, username);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -123,6 +123,11 @@ class IDEView extends React.Component {
|
|||
this.autosaveInterval = null;
|
||||
}
|
||||
|
||||
getTitle = () => {
|
||||
const { id } = this.props.project;
|
||||
return id ? `p5.js Web Editor | ${this.props.project.name}` : 'p5.js Web Editor';
|
||||
}
|
||||
|
||||
isUserOwner() {
|
||||
return this.props.project.owner && this.props.project.owner.id === this.props.user.id;
|
||||
}
|
||||
|
@ -203,7 +208,7 @@ class IDEView extends React.Component {
|
|||
return (
|
||||
<div className="ide">
|
||||
<Helmet>
|
||||
<title>p5.js Web Editor | {this.props.project.name}</title>
|
||||
<title>{this.getTitle()}</title>
|
||||
</Helmet>
|
||||
{this.props.toast.isVisible && <Toast />}
|
||||
<Nav
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
|
||||
import Button from '../../../common/Button';
|
||||
import { PlusIcon } from '../../../common/icons';
|
||||
import CopyableInput from '../../IDE/components/CopyableInput';
|
||||
|
||||
import APIKeyList from './APIKeyList';
|
||||
|
||||
import PlusIcon from '../../../images/plus-icon.svg';
|
||||
|
||||
export const APIKeyPropType = PropTypes.shape({
|
||||
id: PropTypes.object.isRequired,
|
||||
token: PropTypes.object,
|
||||
|
@ -80,14 +80,14 @@ class APIKeyForm extends React.Component {
|
|||
type="text"
|
||||
value={this.state.keyLabel}
|
||||
/>
|
||||
<button
|
||||
className="api-key-form__create-button"
|
||||
<Button
|
||||
disabled={this.state.keyLabel === ''}
|
||||
iconBefore={<PlusIcon />}
|
||||
label="Create new key"
|
||||
type="submit"
|
||||
>
|
||||
<PlusIcon className="api-key-form__create-icon" focusable="false" aria-hidden="true" />
|
||||
Create
|
||||
</button>
|
||||
</Button>
|
||||
</form>
|
||||
|
||||
{
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import { domOnlyProps } from '../../../utils/reduxFormUtils';
|
||||
import Button from '../../../common/Button';
|
||||
|
||||
function AccountForm(props) {
|
||||
const {
|
||||
|
@ -44,11 +45,10 @@ function AccountForm(props) {
|
|||
<span className="form__status"> Confirmation sent, check your email.</span>
|
||||
) :
|
||||
(
|
||||
<button
|
||||
className="form__action"
|
||||
<Button
|
||||
onClick={handleInitiateVerification}
|
||||
>Resend confirmation email
|
||||
</button>
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
</p>
|
||||
|
@ -92,12 +92,11 @@ function AccountForm(props) {
|
|||
/>
|
||||
{newPassword.touched && newPassword.error && <span className="form-error">{newPassword.error}</span>}
|
||||
</p>
|
||||
<input
|
||||
<Button
|
||||
type="submit"
|
||||
disabled={submitting || invalid || pristine}
|
||||
value="Save All Settings"
|
||||
aria-label="updateSettings"
|
||||
/>
|
||||
>Save All Settings
|
||||
</Button>
|
||||
</form>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -6,6 +6,9 @@ import { connect } from 'react-redux';
|
|||
import { Link } from 'react-router';
|
||||
import { bindActionCreators } from 'redux';
|
||||
import classNames from 'classnames';
|
||||
|
||||
import Button from '../../../common/Button';
|
||||
import { DropdownArrowIcon } from '../../../common/icons';
|
||||
import * as ProjectActions from '../../IDE/actions/project';
|
||||
import * as ProjectsActions from '../../IDE/actions/projects';
|
||||
import * as CollectionsActions from '../../IDE/actions/collections';
|
||||
|
@ -20,7 +23,6 @@ import AddToCollectionSketchList from '../../IDE/components/AddToCollectionSketc
|
|||
import CopyableInput from '../../IDE/components/CopyableInput';
|
||||
import { SketchSearchbar } from '../../IDE/components/Searchbar';
|
||||
|
||||
import DropdownArrowIcon from '../../../images/down-arrow.svg';
|
||||
import ArrowUpIcon from '../../../images/sort-arrow-up.svg';
|
||||
import ArrowDownIcon from '../../../images/sort-arrow-down.svg';
|
||||
import RemoveIcon from '../../../images/close.svg';
|
||||
|
@ -50,14 +52,12 @@ const ShareURL = ({ value }) => {
|
|||
|
||||
return (
|
||||
<div className="collection-share" ref={node}>
|
||||
<button
|
||||
className="collection-share__button"
|
||||
<Button
|
||||
onClick={() => setShowURL(!showURL)}
|
||||
aria-label="Show collection share URL"
|
||||
iconAfter={<DropdownArrowIcon />}
|
||||
>
|
||||
<span>Share</span>
|
||||
<DropdownArrowIcon className="collection-share__arrow" focusable="false" aria-hidden="true" />
|
||||
</button>
|
||||
Share
|
||||
</Button>
|
||||
{ showURL &&
|
||||
<div className="collection__share-dropdown">
|
||||
<CopyableInput value={value} label="Link to Collection" />
|
||||
|
@ -264,9 +264,9 @@ class Collection extends React.Component {
|
|||
</p>
|
||||
{
|
||||
this.isOwner() &&
|
||||
<button className="collection-metadata__add-button" onClick={this.showAddSketches}>
|
||||
<Button onClick={this.showAddSketches}>
|
||||
Add Sketch
|
||||
</button>
|
||||
</Button>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
@ -317,7 +317,7 @@ class Collection extends React.Component {
|
|||
_renderFieldHeader(fieldName, displayName) {
|
||||
const { field, direction } = this.props.sorting;
|
||||
const headerClass = classNames({
|
||||
'sketches-table__header': true,
|
||||
'arrowDown': true,
|
||||
'sketches-table__header--selected': field === fieldName
|
||||
});
|
||||
const buttonLabel = this._getButtonLabel(fieldName, displayName);
|
||||
|
|
|
@ -6,6 +6,7 @@ import { bindActionCreators } from 'redux';
|
|||
import * as CollectionsActions from '../../IDE/actions/collections';
|
||||
|
||||
import { generateCollectionName } from '../../../utils/generateRandomName';
|
||||
import Button from '../../../common/Button';
|
||||
|
||||
class CollectionCreate extends React.Component {
|
||||
constructor() {
|
||||
|
@ -81,7 +82,7 @@ class CollectionCreate extends React.Component {
|
|||
rows="4"
|
||||
/>
|
||||
</p>
|
||||
<input type="submit" disabled={invalid} value="Create collection" aria-label="create collection" />
|
||||
<Button type="submit" disabled={invalid}>Create collection</Button>
|
||||
</form>
|
||||
</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 React from 'react';
|
||||
|
||||
import Button from '../../../common/Button';
|
||||
|
||||
import { domOnlyProps } from '../../../utils/reduxFormUtils';
|
||||
|
||||
function LoginForm(props) {
|
||||
|
@ -30,7 +33,11 @@ function LoginForm(props) {
|
|||
/>
|
||||
{password.touched && password.error && <span className="form-error">{password.error}</span>}
|
||||
</p>
|
||||
<input type="submit" disabled={submitting || pristine} value="Log In" aria-label="login" />
|
||||
<Button
|
||||
type="submit"
|
||||
disabled={submitting || pristine}
|
||||
>Log In
|
||||
</Button>
|
||||
</form>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
|
||||
import { domOnlyProps } from '../../../utils/reduxFormUtils';
|
||||
import Button from '../../../common/Button';
|
||||
|
||||
function NewPasswordForm(props) {
|
||||
const {
|
||||
|
@ -34,7 +36,7 @@ function NewPasswordForm(props) {
|
|||
<span className="form-error">{confirmPassword.error}</span>
|
||||
}
|
||||
</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>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
|
||||
import { domOnlyProps } from '../../../utils/reduxFormUtils';
|
||||
import Button from '../../../common/Button';
|
||||
|
||||
function ResetPasswordForm(props) {
|
||||
const {
|
||||
|
@ -19,12 +21,11 @@ function ResetPasswordForm(props) {
|
|||
/>
|
||||
{email.touched && email.error && <span className="form-error">{email.error}</span>}
|
||||
</p>
|
||||
<input
|
||||
<Button
|
||||
type="submit"
|
||||
disabled={submitting || invalid || pristine || props.user.resetPasswordInitiate}
|
||||
value="Send Password Reset Email"
|
||||
aria-label="Send email to reset password"
|
||||
/>
|
||||
>Send Password Reset Email
|
||||
</Button>
|
||||
</form>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
|
||||
import { domOnlyProps } from '../../../utils/reduxFormUtils';
|
||||
import Button from '../../../common/Button';
|
||||
|
||||
function SignupForm(props) {
|
||||
const {
|
||||
|
@ -58,7 +60,11 @@ function SignupForm(props) {
|
|||
<span className="form-error">{confirmPassword.error}</span>
|
||||
}
|
||||
</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>
|
||||
);
|
||||
}
|
||||
|
|
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 AccountForm from '../components/AccountForm';
|
||||
import { validateSettings } from '../../../utils/reduxFormUtils';
|
||||
import GithubButton from '../components/GithubButton';
|
||||
import GoogleButton from '../components/GoogleButton';
|
||||
import SocialAuthButton from '../components/SocialAuthButton';
|
||||
import APIKeyForm from '../components/APIKeyForm';
|
||||
import Nav from '../../../components/Nav';
|
||||
|
||||
|
@ -24,8 +23,10 @@ function SocialLoginPanel(props) {
|
|||
<p className="account__social-text">
|
||||
Use your GitHub or Google account to log into the p5.js Web Editor.
|
||||
</p>
|
||||
<GithubButton buttonText="Login with GitHub" />
|
||||
<GoogleButton buttonText="Login with Google" />
|
||||
<div className="account__social-stack">
|
||||
<SocialAuthButton service={SocialAuthButton.services.github} />
|
||||
<SocialAuthButton service={SocialAuthButton.services.google} />
|
||||
</div>
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -2,8 +2,11 @@ import PropTypes from 'prop-types';
|
|||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { bindActionCreators } from 'redux';
|
||||
import { browserHistory, Link } from 'react-router';
|
||||
import { browserHistory } from 'react-router';
|
||||
import { updateSettings, initiateVerification, createApiKey, removeApiKey } from '../actions';
|
||||
|
||||
import Button from '../../../common/Button';
|
||||
|
||||
import Nav from '../../../components/Nav';
|
||||
import Overlay from '../../App/components/Overlay';
|
||||
|
||||
|
@ -79,16 +82,16 @@ class DashboardView extends React.Component {
|
|||
case TabKey.collections:
|
||||
return this.isOwner() && (
|
||||
<React.Fragment>
|
||||
<Link className="dashboard__action-button" to={`/${username}/collections/create`}>
|
||||
<Button to={`/${username}/collections/create`}>
|
||||
Create collection
|
||||
</Link>
|
||||
</Button>
|
||||
<CollectionSearchbar />
|
||||
</React.Fragment>);
|
||||
case TabKey.sketches:
|
||||
default:
|
||||
return (
|
||||
<React.Fragment>
|
||||
{this.isOwner() && <Link className="dashboard__action-button" to="/">New sketch</Link>}
|
||||
{this.isOwner() && <Button to="/">New sketch</Button>}
|
||||
<SketchSearchbar />
|
||||
</React.Fragment>
|
||||
);
|
||||
|
|
|
@ -6,8 +6,7 @@ import { Helmet } from 'react-helmet';
|
|||
import { validateAndLoginUser } from '../actions';
|
||||
import LoginForm from '../components/LoginForm';
|
||||
import { validateLogin } from '../../../utils/reduxFormUtils';
|
||||
import GithubButton from '../components/GithubButton';
|
||||
import GoogleButton from '../components/GoogleButton';
|
||||
import SocialAuthButton from '../components/SocialAuthButton';
|
||||
import Nav from '../../../components/Nav';
|
||||
|
||||
class LoginView extends React.Component {
|
||||
|
@ -41,8 +40,10 @@ class LoginView extends React.Component {
|
|||
<h2 className="form-container__title">Log In</h2>
|
||||
<LoginForm {...this.props} />
|
||||
<h2 className="form-container__divider">Or</h2>
|
||||
<GithubButton buttonText="Login with Github" />
|
||||
<GoogleButton buttonText="Login with Google" />
|
||||
<div className="form-container__stack">
|
||||
<SocialAuthButton service={SocialAuthButton.services.github} />
|
||||
<SocialAuthButton service={SocialAuthButton.services.google} />
|
||||
</div>
|
||||
<p className="form__navigation-options">
|
||||
Don't have an account?
|
||||
<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 SignupForm from '../components/SignupForm';
|
||||
import { validateSignup } from '../../../utils/reduxFormUtils';
|
||||
import SocialAuthButton from '../components/SocialAuthButton';
|
||||
import Nav from '../../../components/Nav';
|
||||
|
||||
const __process = (typeof global !== 'undefined' ? global : window).process;
|
||||
|
@ -33,6 +34,11 @@ class SignupView extends React.Component {
|
|||
<div className="form-container__content">
|
||||
<h2 className="form-container__title">Sign Up</h2>
|
||||
<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">
|
||||
Already have an account?
|
||||
<Link className="form__login-button" to="/login">Log In</Link>
|
||||
|
|
|
@ -74,6 +74,7 @@ h2 {
|
|||
|
||||
h3 {
|
||||
font-weight: normal;
|
||||
font-size: #{16 / $base-font-size}rem;
|
||||
}
|
||||
h4 {
|
||||
font-weight: normal;
|
||||
|
|
|
@ -20,3 +20,12 @@
|
|||
.account__social-text {
|
||||
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;
|
||||
}
|
||||
|
||||
.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 {
|
||||
display: block;
|
||||
max-width: 900px;
|
||||
|
|
|
@ -94,16 +94,6 @@
|
|||
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 {
|
||||
padding-bottom: 0;
|
||||
}
|
||||
|
@ -114,11 +104,6 @@
|
|||
width: #{350 / $base-font-size}rem;
|
||||
}
|
||||
|
||||
.collection-metadata__add-button {
|
||||
@extend %button;
|
||||
flex-grow: 0;
|
||||
}
|
||||
|
||||
.collection-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
|
|
@ -83,8 +83,3 @@
|
|||
.dashboard-header__actions > *:not(:first-child) {
|
||||
margin-left: #{15 / $base-font-size}rem;
|
||||
}
|
||||
|
||||
.dashboard__action-button {
|
||||
flex-grow: 0;
|
||||
@extend %button;
|
||||
}
|
||||
|
|
|
@ -57,3 +57,7 @@
|
|||
.form-container__exit-button {
|
||||
@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 {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
@ -79,20 +83,25 @@
|
|||
}
|
||||
|
||||
.form [type="submit"] {
|
||||
@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;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
.form [type="submit"][disabled] {
|
||||
cursor: not-allowed;
|
||||
}
|
||||
// .form [type="submit"] {
|
||||
// @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"] {
|
||||
margin: 0 0 0 #{24 / $base-font-size}rem;
|
||||
}
|
||||
// .form [type="submit"][disabled] {
|
||||
// cursor: not-allowed;
|
||||
// }
|
||||
|
||||
.form [type="submit"][disabled],
|
||||
.form--inline [type="submit"][disabled] {
|
||||
cursor: not-allowed;
|
||||
}
|
||||
// .form--inline [type="submit"] {
|
||||
// margin: 0 0 0 #{24 / $base-font-size}rem;
|
||||
// }
|
||||
|
||||
// .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;
|
||||
}
|
||||
|
||||
.keyboard-shortcuts-note {
|
||||
text-align: center;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.keyboard-shortcut-item {
|
||||
display: flex;
|
||||
& + & {
|
||||
|
@ -13,8 +18,31 @@
|
|||
}
|
||||
|
||||
.keyboard-shortcut__command {
|
||||
width: 50%;
|
||||
font-weight: bold;
|
||||
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;
|
||||
align-items: center;
|
||||
height: #{35 / $base-font-size}rem;
|
||||
|
||||
& .isvg {
|
||||
margin-left: #{8 / $base-font-size}rem;
|
||||
}
|
||||
|
||||
& svg {
|
||||
@include themify() {
|
||||
fill: getThemifyVariable('inactive-text-color')
|
||||
|
|
|
@ -31,7 +31,6 @@
|
|||
@import 'components/resizer';
|
||||
@import 'components/overlay';
|
||||
@import 'components/about';
|
||||
@import 'components/github-button';
|
||||
@import 'components/forms';
|
||||
@import 'components/toast';
|
||||
@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-ml5:prod": "cross-env NODE_ENV=production node ./dist/fetch-examples-ml5.bundle.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": {
|
||||
"hooks": {
|
||||
|
@ -69,6 +71,12 @@
|
|||
"@babel/plugin-transform-react-inline-elements": "^7.8.3",
|
||||
"@babel/preset-env": "^7.8.4",
|
||||
"@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",
|
||||
"babel-core": "^7.0.0-bridge.0",
|
||||
"babel-eslint": "^9.0.0",
|
||||
|
@ -81,7 +89,7 @@
|
|||
"enzyme-adapter-react-16": "^1.15.2",
|
||||
"eslint": "^4.19.1",
|
||||
"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-react": "^7.18.3",
|
||||
"file-loader": "^2.0.0",
|
||||
|
@ -99,6 +107,7 @@
|
|||
"react-test-renderer": "^16.12.0",
|
||||
"rimraf": "^2.7.1",
|
||||
"sass-loader": "^6.0.7",
|
||||
"storybook-addon-theme-playground": "^1.2.0",
|
||||
"style-loader": "^1.1.3",
|
||||
"terser-webpack-plugin": "^1.4.3",
|
||||
"webpack-cli": "^3.3.11",
|
||||
|
@ -117,6 +126,7 @@
|
|||
"archiver": "^1.1.0",
|
||||
"async": "^2.6.3",
|
||||
"axios": "^0.18.1",
|
||||
"babel-plugin-styled-components": "^1.10.6",
|
||||
"bcrypt-nodejs": "0.0.3",
|
||||
"blob-util": "^1.2.1",
|
||||
"body-parser": "^1.18.3",
|
||||
|
@ -191,6 +201,8 @@
|
|||
"sinon-mongoose": "^2.3.0",
|
||||
"slugify": "^1.3.6",
|
||||
"srcdoc-polyfill": "^0.2.0",
|
||||
"styled-components": "^5.0.0",
|
||||
"styled-theming": "^2.2.0",
|
||||
"url": "^0.11.0",
|
||||
"webpack": "^4.41.6",
|
||||
"webpack-dev-middleware": "^2.0.6",
|
||||
|
|
|
@ -63,24 +63,20 @@ export function updateProject(req, res) {
|
|||
}
|
||||
|
||||
export function getProject(req, res) {
|
||||
const projectId = req.params.project_id;
|
||||
Project.findById(projectId)
|
||||
const { project_id: projectId, username } = req.params;
|
||||
User.findOne({ username }, (err, user) => { // eslint-disable-line
|
||||
if (!user) {
|
||||
return res.status(404).send({ message: 'Project with that username does not exist' });
|
||||
}
|
||||
Project.findOne({ user: user._id, $or: [{ _id: projectId }, { slug: projectId }] })
|
||||
.populate('user', 'username')
|
||||
.exec((err, project) => { // eslint-disable-line
|
||||
if (err) {
|
||||
return res.status(404).send({ message: 'Project with that id does not exist' });
|
||||
} else if (!project) {
|
||||
Project.findOne({ slug: projectId })
|
||||
.populate('user', 'username')
|
||||
.exec((innerErr, projectBySlug) => {
|
||||
if (innerErr || !projectBySlug) {
|
||||
console.log(err);
|
||||
return res.status(404).send({ message: 'Project with that id does not exist' });
|
||||
}
|
||||
return res.json(projectBySlug);
|
||||
});
|
||||
} else {
|
||||
return res.json(project);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -150,18 +146,10 @@ export function projectForUserExists(username, projectId, callback) {
|
|||
callback(false);
|
||||
return;
|
||||
}
|
||||
Project.findOne({ _id: projectId, user: user._id }, (innerErr, project) => {
|
||||
Project.findOne({ user: user._id, $or: [{ _id: projectId }, { slug: projectId }] }, (innerErr, project) => {
|
||||
if (project) {
|
||||
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.get('/projects/:project_id', ProjectController.getProject);
|
||||
router.get('/:username/projects/:project_id', ProjectController.getProject);
|
||||
|
||||
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$/,
|
||||
];
|
||||
|
||||
// to allow client-only development
|
||||
if (process.env.CORS_ALLOW_LOCALHOST === 'true') {
|
||||
allowedCorsOrigins.push(/localhost/);
|
||||
}
|
||||
|
||||
// Run Webpack dev server in development mode
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
const compiler = webpack(config);
|
||||
app.use(webpackDevMiddleware(compiler, { noInfo: true, publicPath: config.output.publicPath }));
|
||||
app.use(webpackHotMiddleware(compiler));
|
||||
|
||||
corsOriginsWhitelist.push(/localhost/);
|
||||
}
|
||||
|
||||
const mongoConnectionString = process.env.MONGO_URL;
|
||||
|
@ -65,7 +68,7 @@ app.set('trust proxy', true);
|
|||
// Enable Cross-Origin Resource Sharing (CORS) for all origins
|
||||
const corsMiddleware = cors({
|
||||
credentials: true,
|
||||
origin: corsOriginsWhitelist,
|
||||
origin: allowedCorsOrigins,
|
||||
});
|
||||
app.use(corsMiddleware);
|
||||
// Enable pre-flight OPTIONS route for all end-points
|
||||
|
|
Loading…
Reference in a new issue