diff --git a/.eslintrc b/.eslintrc index 06ed7ddb93..6260aa925a 100644 --- a/.eslintrc +++ b/.eslintrc @@ -14,7 +14,8 @@ "import/named": 0, "import/namespace": 0, "import/no-unresolved": 0, - "import/no-named-as-default": 2, + "import/no-named-as-default": 0, + "import/no-named-as-default-member": 0, "comma-dangle": 0, // not sure why airbnb turned this on. gross! "indent": 0, "no-console": 0, diff --git a/.github/workflows/deploy-staging.yml b/.github/workflows/deploy-staging.yml index f60eef8aa6..e708b71558 100644 --- a/.github/workflows/deploy-staging.yml +++ b/.github/workflows/deploy-staging.yml @@ -19,16 +19,16 @@ jobs: runs-on: ubuntu-latest steps: - name: Check out the repo - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v1 + uses: docker/setup-buildx-action@v2 - name: Login to Docker Hub - uses: docker/login-action@v1 + uses: docker/login-action@v2 with: username: ${{ secrets.DOCKER_USERNAME }} password: ${{ secrets.DOCKER_PASSWORD }} - name: Build and push to Docker Hub - uses: docker/build-push-action@v2 + uses: docker/build-push-action@v4 with: context: . file: ./Dockerfile diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 883224dc76..08e8f4ffe5 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -19,7 +19,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Check out the repo - uses: actions/checkout@v2 + uses: actions/checkout@v3 with: ref: release - name: Set up Docker Buildx @@ -27,12 +27,12 @@ jobs: with: platforms: linux/amd64,linux/arm64 - name: Login to Docker Hub - uses: docker/login-action@v1 + uses: docker/login-action@v2 with: username: ${{ secrets.DOCKER_USERNAME }} password: ${{ secrets.DOCKER_PASSWORD }} - name: Build and push to Docker Hub - uses: docker/build-push-action@v2 + uses: docker/build-push-action@v4 with: context: . file: ./Dockerfile diff --git a/.storybook/main.js b/.storybook/main.js index 1d892d7bbe..3596c88b9e 100644 --- a/.storybook/main.js +++ b/.storybook/main.js @@ -4,7 +4,8 @@ const config = { addons: [ '@storybook/addon-links', '@storybook/addon-essentials', - '@storybook/addon-interactions' + '@storybook/addon-interactions', + '@storybook/addon-mdx-gfm' ], framework: { name: '@storybook/react-webpack5', diff --git a/Dockerfile b/Dockerfile index 4e25bed7c5..ac21908220 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM node:16.14.2 as base +FROM node:16.14.2 AS base ENV APP_HOME=/usr/src/app \ TERM=xterm RUN mkdir -p $APP_HOME @@ -6,7 +6,7 @@ WORKDIR $APP_HOME EXPOSE 8000 EXPOSE 8002 -FROM base as development +FROM base AS development ENV NODE_ENV development COPY package.json package-lock.json ./ RUN npm install @@ -18,11 +18,11 @@ COPY translations/locales ./translations/locales COPY public ./public CMD ["npm", "start"] -FROM development as build +FROM development AS build ENV NODE_ENV production RUN npm run build -FROM base as production +FROM base AS production ENV NODE_ENV=production COPY package.json package-lock.json index.js ./ RUN npm install --production diff --git a/README.md b/README.md index cdd7a447d0..36d7195f88 100644 --- a/README.md +++ b/README.md @@ -22,16 +22,9 @@ Make your first sketch in the [p5.js Editor](https://editor.p5js.org/)! Learn mo If you have found a bug in the p5.js Web Editor, you can file it under the ["issues" tab](https://github.com/processing/p5.js-web-editor/issues). You can also request new features here. A set of templates for reporting issues and requesting features are provided to assist you (and us!). The p5.js Editor is maintained mostly by volunteers, so we thank you for your patience as we try to address your issues as soon as we can. Please post bugs and feature requests in the correct repository if you can: * p5.js library: [https://github.com/processing/p5.js/issues](https://github.com/processing/p5.js/issues) -* p5.accessibility: [https://github.com/processing/p5.accessibility/issues](https://github.com/processing/p5.accessibility/issues) -* p5.sound: [https://github.com/processing/p5.js-sound/issues](https://github.com/processing/p5.js-sound/issues) * p5.js website: [https://github.com/processing/p5.js-website/issues](https://github.com/processing/p5.js-website/issues) -### How Do I Know My Issue or Pull Request is Getting Reviewed? - -To see which pull requests and issues are currently being reviewed, check the [PR Review Board](https://github.com/processing/p5.js-web-editor/projects/9) or the following Milestones: [MINOR Release](https://github.com/processing/p5.js-web-editor/milestone/8). - - ## References for Contributing to the p5.js Web Editor [Code of Conduct](https://editor.p5js.org/code-of-conduct) diff --git a/client/common/useKeyDownHandlers.js b/client/common/useKeyDownHandlers.js index ce57ab5925..dbe2ee06bb 100644 --- a/client/common/useKeyDownHandlers.js +++ b/client/common/useKeyDownHandlers.js @@ -33,7 +33,14 @@ export default function useKeyDownHandlers(keyHandlers) { const isMac = navigator.userAgent.toLowerCase().indexOf('mac') !== -1; const isCtrl = isMac ? e.metaKey : e.ctrlKey; if (e.shiftKey && isCtrl) { - handlers.current[`ctrl-shift-${e.key.toLowerCase()}`]?.(e); + handlers.current[ + `ctrl-shift-${ + /^\d+$/.test(e.code.at(-1)) ? e.code.at(-1) : e.key.toLowerCase() + }` + ]?.(e); + } else if (isCtrl && e.altKey && e.code === 'KeyN') { + // specifically for creating a new file + handlers.current[`ctrl-alt-n`]?.(e); } else if (isCtrl) { handlers.current[`ctrl-${e.key.toLowerCase()}`]?.(e); } diff --git a/client/components/Dropdown/DropdownMenu.jsx b/client/components/Dropdown/DropdownMenu.jsx index da41b30101..3371d1d6cc 100644 --- a/client/components/Dropdown/DropdownMenu.jsx +++ b/client/components/Dropdown/DropdownMenu.jsx @@ -38,7 +38,7 @@ const DropdownMenu = forwardRef( }; return ( -
+
); diff --git a/client/components/Nav/NavDropdownMenu.jsx b/client/components/Nav/NavDropdownMenu.jsx index 7fb8eda34f..d2c5744c46 100644 --- a/client/components/Nav/NavDropdownMenu.jsx +++ b/client/components/Nav/NavDropdownMenu.jsx @@ -24,7 +24,12 @@ function NavDropdownMenu({ id, title, children }) { return (
  • - -
  • - + `; @@ -464,26 +485,35 @@ exports[`Nav renders dashboard version for mobile 1`] = ` exports[`Nav renders editor version for desktop 1`] = `
    - +
    `; @@ -896,7 +967,7 @@ exports[`Nav renders editor version for mobile 1`] = ` }
    - +
    `; diff --git a/client/modules/IDE/components/IDEKeyHandlers.jsx b/client/modules/IDE/components/IDEKeyHandlers.jsx index c7a285e627..fc0219f27d 100644 --- a/client/modules/IDE/components/IDEKeyHandlers.jsx +++ b/client/modules/IDE/components/IDEKeyHandlers.jsx @@ -8,7 +8,8 @@ import { expandSidebar, showErrorModal, startSketch, - stopSketch + stopSketch, + newFile } from '../actions/ide'; import { setAllAccessibleOutput } from '../actions/preferences'; import { cloneProject, saveProject } from '../actions/project'; @@ -25,6 +26,10 @@ export const useIDEKeyHandlers = ({ getContent }) => { const sidebarIsExpanded = useSelector((state) => state.ide.sidebarIsExpanded); const consoleIsExpanded = useSelector((state) => state.ide.consoleIsExpanded); + const rootFile = useSelector( + (state) => state.files.filter((file) => file.name === 'root')[0] + ); + const isUserOwner = useSelector(getIsUserOwner); const isAuthenticated = useSelector(getAuthenticated); const sketchOwner = useSelector(getSketchOwner); @@ -72,6 +77,11 @@ export const useIDEKeyHandlers = ({ getContent }) => { sidebarIsExpanded ? collapseSidebar() : expandSidebar() ); }, + 'ctrl-alt-n': (e) => { + e.preventDefault(); + e.stopPropagation(); + dispatch(newFile(rootFile.id)); + }, 'ctrl-`': (e) => { e.preventDefault(); dispatch(consoleIsExpanded ? collapseConsole() : expandConsole()); diff --git a/client/modules/IDE/components/KeyboardShortcutModal.jsx b/client/modules/IDE/components/KeyboardShortcutModal.jsx index 093630498a..fbca28a572 100644 --- a/client/modules/IDE/components/KeyboardShortcutModal.jsx +++ b/client/modules/IDE/components/KeyboardShortcutModal.jsx @@ -6,6 +6,8 @@ function KeyboardShortcutModal() { const { t } = useTranslation(); const replaceCommand = metaKey === 'Ctrl' ? `${metaKeyName} + H` : `${metaKeyName} + ⌥ + F`; + const newFileCommand = + metaKey === 'Ctrl' ? `${metaKeyName} + Alt + N` : `${metaKeyName} + ⌥ + N`; return (

    @@ -25,7 +27,7 @@ function KeyboardShortcutModal() {

    General

    diff --git a/client/modules/IDE/components/NewFileForm.jsx b/client/modules/IDE/components/NewFileForm.jsx index 2cc93566ae..9620e321dc 100644 --- a/client/modules/IDE/components/NewFileForm.jsx +++ b/client/modules/IDE/components/NewFileForm.jsx @@ -55,11 +55,15 @@ function NewFileForm() { )} - + + {() => ( + + )} +
    - {touched.name && errors.name && ( + {touched.submitButton && errors.name && ( {errors.name} )} diff --git a/client/modules/IDE/components/NewFolderForm.jsx b/client/modules/IDE/components/NewFolderForm.jsx index bc305b8b18..b0935397ff 100644 --- a/client/modules/IDE/components/NewFolderForm.jsx +++ b/client/modules/IDE/components/NewFolderForm.jsx @@ -52,11 +52,15 @@ function NewFolderForm() { )} - + + {() => ( + + )} + - {touched.name && errors.name && ( + {touched.submitButton && errors.name && ( {errors.name} )} diff --git a/client/modules/IDE/components/QuickAddList/QuickAddList.jsx b/client/modules/IDE/components/QuickAddList/QuickAddList.jsx index 3011fd51ad..3ab1ecb593 100644 --- a/client/modules/IDE/components/QuickAddList/QuickAddList.jsx +++ b/client/modules/IDE/components/QuickAddList/QuickAddList.jsx @@ -10,8 +10,21 @@ const Item = ({ isAdded, onSelect, name, url }) => { const buttonLabel = isAdded ? t('QuickAddList.ButtonRemoveARIA') : t('QuickAddList.ButtonAddToCollectionARIA'); + + const handleKeyDown = (event) => { + if (event.key === 'Enter' || event.key === ' ') { + onSelect(event); + } + }; + return ( -
  • { /* eslint-disable-line */ } +
  • + ); }; diff --git a/client/modules/IDE/components/Searchbar/Collection.jsx b/client/modules/IDE/components/Searchbar/Collection.jsx index e36c4bf174..92d55ebe3f 100644 --- a/client/modules/IDE/components/Searchbar/Collection.jsx +++ b/client/modules/IDE/components/Searchbar/Collection.jsx @@ -1,5 +1,5 @@ -import { bindActionCreators } from 'redux'; -import { connect } from 'react-redux'; +import React from 'react'; +import { useDispatch, useSelector } from 'react-redux'; import i18next from 'i18next'; import * as SortingActions from '../../actions/sorting'; @@ -7,19 +7,27 @@ import Searchbar from './Searchbar'; const scope = 'collection'; -function mapStateToProps(state) { - return { - searchLabel: i18next.t('Searchbar.SearchCollection'), - searchTerm: state.search[`${scope}SearchTerm`] +const SearchbarContainer = () => { + const dispatch = useDispatch(); + const searchLabel = i18next.t('Searchbar.SearchCollection'); + const searchTerm = useSelector((state) => state.search[`${scope}SearchTerm`]); + + const setSearchTerm = (term) => { + dispatch(SortingActions.setSearchTerm(scope, term)); }; -} -function mapDispatchToProps(dispatch) { - const actions = { - setSearchTerm: (term) => SortingActions.setSearchTerm(scope, term), - resetSearchTerm: () => SortingActions.resetSearchTerm(scope) + const resetSearchTerm = () => { + dispatch(SortingActions.resetSearchTerm(scope)); }; - return bindActionCreators(Object.assign({}, actions), dispatch); -} -export default connect(mapStateToProps, mapDispatchToProps)(Searchbar); + return ( + + ); +}; + +export default SearchbarContainer; diff --git a/client/modules/IDE/components/Searchbar/Searchbar.jsx b/client/modules/IDE/components/Searchbar/Searchbar.jsx index a7c79d1f46..68344950b1 100644 --- a/client/modules/IDE/components/Searchbar/Searchbar.jsx +++ b/client/modules/IDE/components/Searchbar/Searchbar.jsx @@ -1,82 +1,75 @@ +import React, { useState, useCallback, useEffect } from 'react'; import PropTypes from 'prop-types'; -import React from 'react'; import { throttle } from 'lodash'; -import { withTranslation } from 'react-i18next'; +import { useTranslation } from 'react-i18next'; import i18next from 'i18next'; import SearchIcon from '../../../../images/magnifyingglass.svg'; -class Searchbar extends React.Component { - constructor(props) { - super(props); - this.state = { - searchValue: this.props.searchTerm - }; - this.throttledSearchChange = throttle(this.searchChange, 500); - } +const Searchbar = ({ + searchTerm, + setSearchTerm, + resetSearchTerm, + searchLabel +}) => { + const { t } = useTranslation(); + const [searchValue, setSearchValue] = useState(searchTerm); - componentWillUnmount() { - this.props.resetSearchTerm(); - } + const throttledSearchChange = useCallback( + throttle((value) => { + setSearchTerm(value.trim()); + }, 500), + [setSearchTerm] + ); - handleResetSearch = () => { - this.setState({ searchValue: '' }, () => { - this.props.resetSearchTerm(); - }); + const handleResetSearch = () => { + setSearchValue(''); + resetSearchTerm(); }; - searchChange = () => { - this.props.setSearchTerm(this.state.searchValue.trim()); + const handleSearchChange = (e) => { + const { value } = e.target; + setSearchValue(value); + throttledSearchChange(value.trim()); }; - handleSearchChange = (e) => { - this.setState({ searchValue: e.target.value }, () => { - this.throttledSearchChange(this.state.searchValue.trim()); - }); - }; + useEffect(() => { + setSearchValue(searchTerm); + }, [searchTerm]); - render() { - const { searchValue } = this.state; - return ( -
    -
    -
    - +
    +
    - ); - } -} + + +
    + ); +}; Searchbar.propTypes = { searchTerm: PropTypes.string.isRequired, setSearchTerm: PropTypes.func.isRequired, resetSearchTerm: PropTypes.func.isRequired, - searchLabel: PropTypes.string, - t: PropTypes.func.isRequired + searchLabel: PropTypes.string }; Searchbar.defaultProps = { searchLabel: i18next.t('Searchbar.SearchSketch') }; -export default withTranslation()(Searchbar); +export default Searchbar; diff --git a/client/modules/IDE/components/Searchbar/Sketch.jsx b/client/modules/IDE/components/Searchbar/Sketch.jsx index 5d4f840bb0..921c33a6c9 100644 --- a/client/modules/IDE/components/Searchbar/Sketch.jsx +++ b/client/modules/IDE/components/Searchbar/Sketch.jsx @@ -1,25 +1,32 @@ -import { bindActionCreators } from 'redux'; -import { connect } from 'react-redux'; +import React from 'react'; +import { useDispatch, useSelector } from 'react-redux'; import i18next from 'i18next'; import * as SortingActions from '../../actions/sorting'; - import Searchbar from './Searchbar'; const scope = 'sketch'; -function mapStateToProps(state) { - return { - searchLabel: i18next.t('Searchbar.SearchSketch'), - searchTerm: state.search[`${scope}SearchTerm`] +const SearchbarContainer = () => { + const dispatch = useDispatch(); + const searchLabel = i18next.t('Searchbar.SearchSketch'); + const searchTerm = useSelector((state) => state.search[`${scope}SearchTerm`]); + + const setSearchTerm = (term) => { + dispatch(SortingActions.setSearchTerm(scope, term)); }; -} -function mapDispatchToProps(dispatch) { - const actions = { - setSearchTerm: (term) => SortingActions.setSearchTerm(scope, term), - resetSearchTerm: () => SortingActions.resetSearchTerm(scope) + const resetSearchTerm = () => { + dispatch(SortingActions.resetSearchTerm(scope)); }; - return bindActionCreators(Object.assign({}, actions), dispatch); -} -export default connect(mapStateToProps, mapDispatchToProps)(Searchbar); + return ( + + ); +}; + +export default SearchbarContainer; diff --git a/client/modules/IDE/components/Sidebar.jsx b/client/modules/IDE/components/Sidebar.jsx index c01815d339..79ffed3b86 100644 --- a/client/modules/IDE/components/Sidebar.jsx +++ b/client/modules/IDE/components/Sidebar.jsx @@ -1,4 +1,4 @@ -import React, { useRef, useState } from 'react'; +import React, { useRef } from 'react'; import classNames from 'classnames'; import { useTranslation } from 'react-i18next'; import { useDispatch, useSelector } from 'react-redux'; @@ -33,21 +33,10 @@ export default function SideBar() { const sidebarOptionsRef = useRef(null); - const [isFocused, setIsFocused] = useState(false); - const isAuthenticated = useSelector(getAuthenticated); const onBlurComponent = () => { - setIsFocused(false); - setTimeout(() => { - if (!isFocused) { - dispatch(closeProjectOptions()); - } - }, 200); - }; - - const onFocusComponent = () => { - setIsFocused(true); + setTimeout(() => dispatch(closeProjectOptions()), 200); }; const toggleProjectOptions = (e) => { @@ -96,7 +85,6 @@ export default function SideBar() { ref={sidebarOptionsRef} onClick={toggleProjectOptions} onBlur={onBlurComponent} - onFocus={onFocusComponent} >