diff --git a/.eslintrc b/.eslintrc index 0c9597ce98..e9539865dd 100644 --- a/.eslintrc +++ b/.eslintrc @@ -29,6 +29,7 @@ "tsx": "never" } ], + "import/prefer-default-export": "off", "react/jsx-filename-extension": [1, { "extensions": [".jsx", ".tsx"] }], "comma-dangle": 0, // not sure why airbnb turned this on. gross! "default-param-last": 0, @@ -41,6 +42,7 @@ "no-restricted-exports": 1, "no-underscore-dangle": 0, "no-useless-catch": 2, + "no-plusplus": "off", "prefer-object-spread": 0, "max-len": [1, 120, 2, {"ignoreComments": true, "ignoreTemplateLiterals": true}], "max-classes-per-file": 0, @@ -131,7 +133,9 @@ "rules": { "no-use-before-define": "off", "import/no-extraneous-dependencies": "off", - "no-unused-vars": "off" + "no-unused-vars": "off", + "import/no-default-export": "warn", + "no-underscore-dangle": "warn", } }, { diff --git a/.prettierrc b/.prettierrc index e2da8bb25a..df6b0841b0 100644 --- a/.prettierrc +++ b/.prettierrc @@ -5,7 +5,6 @@ "insertPragma": false, "jsxBracketSameLine": false, "jsxSingleQuote": false, - "parser": "babel", "printWidth": 80, "proseWrap": "never", "requirePragma": false, diff --git a/README.md b/README.md index 2eaffbddde..4a8dcfcc8c 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,14 @@ We are a community of, and in solidarity with, people from every gender identity Learn more about [our community](https://p5js.org/community/) and read our [Community Statement and Code of Conduct](./.github/CODE_OF_CONDUCT.md). You can directly support our work with p5.js by [donating to the Processing Foundation](https://processingfoundation.org/support). +Stay in touch with Processing Foundation across other platforms: +- [Instagram](https://www.instagram.com/p5xjs) +- [Youtube](https://www.youtube.com/@ProcessingFoundation) +- [X](https://x.com/p5xjs) +- Discord (invitation link coming soon!) +- [Forum](https://discourse.processing.org) + + ## Using the p5.js Editor 🤔 Make your first sketch in the [p5.js Editor](https://editor.p5js.org/)! Learn more about sketching with p5.js on the [Get Started](https://p5js.org/tutorials/get-started/) and find everything you can do in the [Reference](https://p5js.org/reference/). You can also look at [examples](https://editor.p5js.org/p5/sketches) and remix them in the p5.js Editor. diff --git a/client/common/useKeyDownHandlers.js b/client/common/useKeyDownHandlers.js index 7259574e82..f47fbc7c8a 100644 --- a/client/common/useKeyDownHandlers.js +++ b/client/common/useKeyDownHandlers.js @@ -1,6 +1,7 @@ import { mapKeys } from 'lodash'; import PropTypes from 'prop-types'; import { useCallback, useEffect, useRef } from 'react'; +import { isMac } from '../utils/device'; /** * Attaches keydown handlers to the global document. @@ -30,8 +31,7 @@ export default function useKeyDownHandlers(keyHandlers) { */ const handleEvent = useCallback((e) => { if (!e.key) return; - const isMac = navigator.userAgent.toLowerCase().indexOf('mac') !== -1; - const isCtrl = isMac ? e.metaKey : e.ctrlKey; + const isCtrl = isMac() ? e.metaKey : e.ctrlKey; if (e.shiftKey && isCtrl) { handlers.current[ `ctrl-shift-${ diff --git a/client/components/SkipLink.test.tsx b/client/components/SkipLink.test.tsx index ba6d78517d..692a08a616 100644 --- a/client/components/SkipLink.test.tsx +++ b/client/components/SkipLink.test.tsx @@ -2,7 +2,7 @@ import { render, screen, fireEvent } from '@testing-library/react'; import React from 'react'; import { useTranslation } from 'react-i18next'; import '@testing-library/jest-dom'; -import SkipLink from './SkipLink'; +import { SkipLink } from './SkipLink'; jest.mock('react-i18next', () => ({ useTranslation: () => ({ diff --git a/client/components/SkipLink.tsx b/client/components/SkipLink.tsx index d70af6a999..28b02b8b5c 100644 --- a/client/components/SkipLink.tsx +++ b/client/components/SkipLink.tsx @@ -2,12 +2,12 @@ import React, { useState } from 'react'; import classNames from 'classnames'; import { useTranslation } from 'react-i18next'; -type SkipLinkProps = { - targetId: string, - text: string -}; +interface SkipLinkProps { + targetId: string; + text: string; +} -const SkipLink = ({ targetId, text }: SkipLinkProps) => { +export const SkipLink = ({ targetId, text }: SkipLinkProps) => { const [focus, setFocus] = useState(false); const { t } = useTranslation(); const handleFocus = () => { @@ -30,5 +30,3 @@ const SkipLink = ({ targetId, text }: SkipLinkProps) => { ); }; - -export default SkipLink; diff --git a/client/i18n.js b/client/i18n.js index 25ce8f19d9..8fa6959f6a 100644 --- a/client/i18n.js +++ b/client/i18n.js @@ -3,7 +3,7 @@ import { initReactI18next } from 'react-i18next'; import Backend from 'i18next-http-backend'; import { - be, + bn, enUS, es, ja, @@ -21,12 +21,12 @@ import { enIN } from 'date-fns/locale'; -import getPreferredLanguage from './utils/language-utils'; +import { getPreferredLanguage } from './utils/language-utils'; const fallbackLng = ['en-US']; export const availableLanguages = [ - 'be', + 'bn', 'de', 'en-US', 'es-419', @@ -61,7 +61,7 @@ if ( export function languageKeyToLabel(lang) { const languageMap = { - be: 'বাংলা', + bn: 'বাংলা', de: 'Deutsch', 'en-US': 'English', 'es-419': 'Español', @@ -83,7 +83,7 @@ export function languageKeyToLabel(lang) { export function languageKeyToDateLocale(lang) { const languageMap = { - be, + bn, de, 'en-US': enUS, 'es-419': es, diff --git a/client/index.jsx b/client/index.jsx index 27befe8421..0c1ca04082 100644 --- a/client/index.jsx +++ b/client/index.jsx @@ -9,7 +9,7 @@ import Routing from './routes'; import ThemeProvider from './modules/App/components/ThemeProvider'; import Loader from './modules/App/components/loader'; import './i18n'; -import SkipLink from './components/SkipLink'; +import { SkipLink } from './components/SkipLink'; require('./styles/main.scss'); diff --git a/client/modules/IDE/actions/assets.js b/client/modules/IDE/actions/assets.js index ecd16fb7c6..0aa9085da3 100644 --- a/client/modules/IDE/actions/assets.js +++ b/client/modules/IDE/actions/assets.js @@ -1,4 +1,4 @@ -import apiClient from '../../../utils/apiClient'; +import { apiClient } from '../../../utils/apiClient'; import * as ActionTypes from '../../../constants'; import { startLoader, stopLoader } from '../reducers/loading'; import { assetsActions } from '../reducers/assets'; diff --git a/client/modules/IDE/actions/collections.js b/client/modules/IDE/actions/collections.js index 32790e681e..68a42f500d 100644 --- a/client/modules/IDE/actions/collections.js +++ b/client/modules/IDE/actions/collections.js @@ -1,5 +1,5 @@ import browserHistory from '../../../browserHistory'; -import apiClient from '../../../utils/apiClient'; +import { apiClient } from '../../../utils/apiClient'; import * as ActionTypes from '../../../constants'; import { startLoader, stopLoader } from '../reducers/loading'; import { setToastText, showToast } from './toast'; diff --git a/client/modules/IDE/actions/files.js b/client/modules/IDE/actions/files.js index 84b76b6f41..3d406bcff6 100644 --- a/client/modules/IDE/actions/files.js +++ b/client/modules/IDE/actions/files.js @@ -1,6 +1,6 @@ import objectID from 'bson-objectid'; import blobUtil from 'blob-util'; -import apiClient from '../../../utils/apiClient'; +import { apiClient } from '../../../utils/apiClient'; import * as ActionTypes from '../../../constants'; import { setUnsavedChanges, diff --git a/client/modules/IDE/actions/preferences.js b/client/modules/IDE/actions/preferences.js index ebaefd1625..f6e71504ee 100644 --- a/client/modules/IDE/actions/preferences.js +++ b/client/modules/IDE/actions/preferences.js @@ -1,5 +1,5 @@ import i18next from 'i18next'; -import apiClient from '../../../utils/apiClient'; +import { apiClient } from '../../../utils/apiClient'; import * as ActionTypes from '../../../constants'; function updatePreferences(formParams, dispatch) { diff --git a/client/modules/IDE/actions/project.js b/client/modules/IDE/actions/project.js index cdd30cf6ae..5b3aaf7743 100644 --- a/client/modules/IDE/actions/project.js +++ b/client/modules/IDE/actions/project.js @@ -2,8 +2,8 @@ import objectID from 'bson-objectid'; import each from 'async/each'; import { isEqual } from 'lodash'; import browserHistory from '../../../browserHistory'; -import apiClient from '../../../utils/apiClient'; -import getConfig from '../../../utils/getConfig'; +import { apiClient } from '../../../utils/apiClient'; +import { getConfig } from '../../../utils/getConfig'; import * as ActionTypes from '../../../constants'; import { showToast, setToastText } from './toast'; import { @@ -308,6 +308,8 @@ export function cloneProject(project) { (file, callback) => { if ( file.url && + S3_BUCKET && + S3_BUCKET_URL_BASE && (file.url.includes(S3_BUCKET_URL_BASE) || file.url.includes(S3_BUCKET)) ) { diff --git a/client/modules/IDE/actions/projects.js b/client/modules/IDE/actions/projects.js index 34ca2a35bf..06c50cbbfa 100644 --- a/client/modules/IDE/actions/projects.js +++ b/client/modules/IDE/actions/projects.js @@ -1,4 +1,4 @@ -import apiClient from '../../../utils/apiClient'; +import { apiClient } from '../../../utils/apiClient'; import * as ActionTypes from '../../../constants'; import { startLoader, stopLoader } from '../reducers/loading'; diff --git a/client/modules/IDE/actions/uploader.js b/client/modules/IDE/actions/uploader.js index e2831df75f..8b79ef688b 100644 --- a/client/modules/IDE/actions/uploader.js +++ b/client/modules/IDE/actions/uploader.js @@ -1,13 +1,21 @@ import { TEXT_FILE_REGEX } from '../../../../server/utils/fileUtils'; -import apiClient from '../../../utils/apiClient'; -import getConfig from '../../../utils/getConfig'; +import { apiClient } from '../../../utils/apiClient'; +import { getConfig } from '../../../utils/getConfig'; +import { isTestEnvironment } from '../../../utils/checkTestEnv'; import { handleCreateFile } from './files'; +const s3BucketUrlBase = getConfig('S3_BUCKET_URL_BASE'); +const awsRegion = getConfig('AWS_REGION'); +const s3Bucket = getConfig('S3_BUCKET'); + +if (!isTestEnvironment && !s3BucketUrlBase && !(awsRegion && s3Bucket)) { + throw new Error(`S3 bucket address not configured. + Configure either S3_BUCKET_URL_BASE or both AWS_REGION & S3_BUCKET in env vars`); +} + export const s3BucketHttps = - getConfig('S3_BUCKET_URL_BASE') || - `https://s3-${getConfig('AWS_REGION')}.amazonaws.com/${getConfig( - 'S3_BUCKET' - )}/`; + s3BucketUrlBase || `https://s3-${awsRegion}.amazonaws.com/${s3Bucket}/`; + const MAX_LOCAL_FILE_SIZE = 80000; // bytes, aka 80 KB function isS3Upload(file) { diff --git a/client/modules/IDE/components/AssetSize.jsx b/client/modules/IDE/components/AssetSize.jsx index 853e9d3ea4..42ca4871da 100644 --- a/client/modules/IDE/components/AssetSize.jsx +++ b/client/modules/IDE/components/AssetSize.jsx @@ -1,10 +1,11 @@ import React from 'react'; +import { useTranslation } from 'react-i18next'; import { useSelector } from 'react-redux'; import prettyBytes from 'pretty-bytes'; +import { getConfig } from '../../../utils/getConfig'; +import { parseNumber } from '../../../utils/parseStringToType'; -import getConfig from '../../../utils/getConfig'; - -const limit = getConfig('UPLOAD_LIMIT') || 250000000; +const limit = parseNumber(getConfig('UPLOAD_LIMIT')) || 250000000; const MAX_SIZE_B = limit; const formatPercent = (percent) => { @@ -18,6 +19,8 @@ const formatPercent = (percent) => { /* Eventually, this copy should be Total / 250 MB Used */ const AssetSize = () => { + const { t } = useTranslation(); + const totalSize = useSelector( (state) => state.user.totalSize || state.assets.totalSize ); @@ -38,7 +41,9 @@ const AssetSize = () => {
{currentSize} ({percent})
-Max: {sizeLimit}
++ {t('AssetList.maximum')}: {sizeLimit} +
); }; diff --git a/client/modules/IDE/components/CollectionList/CollectionListRow.jsx b/client/modules/IDE/components/CollectionList/CollectionListRow.jsx index a2096fc788..35b0ae6b09 100644 --- a/client/modules/IDE/components/CollectionList/CollectionListRow.jsx +++ b/client/modules/IDE/components/CollectionList/CollectionListRow.jsx @@ -11,7 +11,7 @@ import * as ProjectActions from '../../actions/project'; import * as CollectionsActions from '../../actions/collections'; import * as IdeActions from '../../actions/ide'; import * as ToastActions from '../../actions/toast'; -import dates from '../../../../utils/formatDate'; +import { formatDateToString } from '../../../../utils/formatDate'; import { remSize, prop } from '../../../../theme'; const SketchsTableRow = styled.tr` @@ -93,7 +93,7 @@ const SketchlistDropdownColumn = styled.td` } `; const formatDateCell = (date, mobile = false) => - dates.format(date, { showTime: !mobile }); + formatDateToString(date, { showTime: !mobile }); const CollectionListRowBase = (props) => { const [renameOpen, setRenameOpen] = useState(false); diff --git a/client/modules/IDE/components/Header/Nav.jsx b/client/modules/IDE/components/Header/Nav.jsx index 151e2d8212..f8f19cdb9f 100644 --- a/client/modules/IDE/components/Header/Nav.jsx +++ b/client/modules/IDE/components/Header/Nav.jsx @@ -7,7 +7,8 @@ import { useTranslation } from 'react-i18next'; import MenubarSubmenu from '../../../../components/Menubar/MenubarSubmenu'; import MenubarItem from '../../../../components/Menubar/MenubarItem'; import { availableLanguages, languageKeyToLabel } from '../../../../i18n'; -import getConfig from '../../../../utils/getConfig'; +import { getConfig } from '../../../../utils/getConfig'; +import { parseBoolean } from '../../../../utils/parseStringToType'; import { showToast } from '../../actions/toast'; import { setLanguage } from '../../actions/preferences'; import Menubar from '../../../../components/Menubar/Menubar'; @@ -80,8 +81,14 @@ LeftLayout.defaultProps = { layout: 'project' }; +const isLoginEnabled = parseBoolean(getConfig('LOGIN_ENABLED'), true); +const isUiCollectionsEnabled = parseBoolean( + getConfig('UI_COLLECTIONS_ENABLED'), + true +); +const isExamplesEnabled = parseBoolean(getConfig('EXAMPLES_ENABLED'), true); + const UserMenu = () => { - const isLoginEnabled = getConfig('LOGIN_ENABLED'); const isAuthenticated = useSelector(getAuthenticated); if (isLoginEnabled && isAuthenticated) { @@ -177,7 +184,7 @@ const ProjectMenu = () => { id="file-save" isDisabled={ !user.authenticated || - !getConfig('LOGIN_ENABLED') || + !isLoginEnabled || (project?.owner && !isUserOwner) } onClick={() => saveSketch(cmRef.current)} @@ -216,9 +223,7 @@ const ProjectMenu = () => {+ {t('Toolbar.By')}{' '} + + {project.owner.username} + +
+ )}- {t('Toolbar.By')}{' '} - - {project.owner.username} - -
- )} -